ANSI C Based OPC UA Client/Server SDK  1.8.4.410
Security Lesson 3: Assigning Access Rights to Nodes

This lesson provides an example on how to use the SDK’s authorization module.

For detailed information about this module, please refer to Authorization.

Files used in this lesson:

Step 1: Configuring the Endpoint

For demonstrating the abilities of the SDK’s authorization module, we will allow the clients to connect either anonymously or with username and password. This is done by configuring two user token policies (see servermain.c):

/* Set the endpoint configuration to allow Anonymous and Username/Password logon */
pEndpoint->uNoOfUserTokenPolicy = 2;
pEndpoint->pUserTokenPolicy = OpcUa_Alloc(pEndpoint->uNoOfUserTokenPolicy * sizeof(OpcUa_UserTokenPolicy));
OpcUa_UserTokenPolicy_Initialize(&pEndpoint->pUserTokenPolicy[0]);
OpcUa_String_AttachReadOnly(&pEndpoint->pUserTokenPolicy[0].PolicyId, "Anonymous");
OpcUa_UserTokenPolicy_Initialize(&pEndpoint->pUserTokenPolicy[1]);
OpcUa_String_AttachReadOnly(&pEndpoint->pUserTokenPolicy[1].PolicyId, "UserName");
OpcUa_String_AttachReadOnly(&pEndpoint->pUserTokenPolicy[1].SecurityPolicyUri, OpcUa_SecurityPolicy_Basic256Sha256);

Step 2: Creating Nodes with Custom Access Rights

Additionally we add a custom provider, similarly to Lesson 2: Extending the Address Space with Real World Data. The code of the custom provider is the same, apart of some slight differences which are shown in the following (see custom_provider.c for full source code).

Instead of creating object types and their instances, we just create a new folder and three nodes in it, using the function CustomProvider_CreateSampleNodes. They will be used as examples on how to set user rights with the authorization module.

The first node should be read and writable by all users. Access rights are set using UaServer_UserMgt_SetPermissions. The user and group id don’t matter in this case, as everyone can do everything with the node, so we set them to 0 which represents the anonymous user in our custom passwd file. The mode parameter is set to a value of 0x0FFF, which is equivalent to all access rights OR’ed together.

OpcUa_StatusCode CustomProvider_CreateSampleNodes(OpcUa_BaseNode *a_pOwner, OpcUa_NodeId *a_pStartingNodeId)
{
...
/* Create variable that is read- and writable by all users */
a_pStartingNodeId->Identifier.Numeric++;
uStatus = UaServer_CreateDataVariable(pAddressSpace,
&pVariable,
a_pOwner,
a_pStartingNodeId->Identifier.Numeric,
g_uCustomProvider_NamespaceIndex,
"Sample_WriteAll_ReadAll");
OpcUa_GotoErrorIfBad(uStatus);
OpcUa_Variable_SetDataType_Numeric(pVariable, OpcUaId_UInt32, 0);
pValue = OpcUa_Variable_GetValue(pVariable);
pValue->Datatype = OpcUaType_UInt32;
pValue->Value.UInt32 = 100;
UaServer_UserMgt_SetPermissions((OpcUa_BaseNode*)pVariable,
uidAnonymous,
gidAnonymous,
0x0FFF);

The second node shall be restricted in a way that all users are allowed to read it, but only the user “root” should be able to write values to it. To achieve this, we set the user id and group id to 1, which stands for the user and the group “root”. The access rights are a combination that gives full rights to the user, but only read and browse access for the group and others.

/* Create variable that is readable by all users and only writable by user 'root' */
a_pStartingNodeId->Identifier.Numeric++;
uStatus = UaServer_CreateDataVariable(pAddressSpace,
&pVariable,
a_pOwner,
a_pStartingNodeId->Identifier.Numeric,
g_uCustomProvider_NamespaceIndex,
"Sample_WriteRoot_ReadAll");
OpcUa_GotoErrorIfBad(uStatus);
OpcUa_Variable_SetDataType_Numeric(pVariable, OpcUaId_UInt32, 0);
pValue = OpcUa_Variable_GetValue(pVariable);
pValue->Datatype = OpcUaType_UInt32;
pValue->Value.UInt32 = 100;
UaServer_UserMgt_SetPermissions((OpcUa_BaseNode*)pVariable,
uidRoot,
gidRoot,

The third node in our example will have the most restrictive rights. It will be read and writable only for the user “root”, all other users will not even be allowed to browse it. The user and group ids are set to 1 like for the last node, the mode is now set to full access rights just for the owner of the node.

/* Create variable that is read- and writable only by user 'root' */
a_pStartingNodeId->Identifier.Numeric++;
uStatus = UaServer_CreateDataVariable(pAddressSpace,
&pVariable,
a_pOwner,
a_pStartingNodeId->Identifier.Numeric,
g_uCustomProvider_NamespaceIndex,
"Sample_WriteRoot_ReadRoot");
OpcUa_GotoErrorIfBad(uStatus);
OpcUa_Variable_SetDataType_Numeric(pVariable, OpcUaId_UInt32, 0);
pValue = OpcUa_Variable_GetValue(pVariable);
pValue->Datatype = OpcUaType_UInt32;
pValue->Value.UInt32 = 100;
UaServer_UserMgt_SetPermissions((OpcUa_BaseNode*)pVariable,
uidRoot,
gidRoot,

Step 3: Checking the Access Rights in Service Calls

Now the service call handlers have to be extended to check whether the requesting client has sufficient rights for the respective service call. As an example, the interesting part of CustomProvider_ReadAsync is shown below (for full code see custom_provider_read.c). Depending on the attribute that is read, it simply calls the helper function of the SDK to check whether the session has sufficient rights to read the attribute, passing the INode structure of the affected OpcUa_BaseNode and the UserIdentityData of the calling session. If the rights are not sufficient, the read result is set to BadNotReadable.

if ( (a_pReadCtx->pRequest->NodesToRead[i].AttributeId == OpcUa_Attributes_Value &&
a_pReadCtx->pSession->UserIdentityData)) ||
(a_pReadCtx->pRequest->NodesToRead[i].AttributeId != OpcUa_Attributes_Value &&
a_pReadCtx->pSession->UserIdentityData)))
{
pRes->Results[i].StatusCode = OpcUa_BadNotReadable;
continue;
}

Similar code for extending the other services to check the access rights of a client can be found in the files custom_provider_browse.c, custom_provider_subscription.c, and custom_provider_write.c.

Step 4: Configuring Users and Groups

The next step is to set up the user database to be used for verifying logons and authenticating users. The SDK’s internal authorization module uses the passwd file (as used in the last lesson) for this. Additionally we will need the group file provided with the example for correlating users and groups. There are some predefined users in the files, the passwords are listed in Internal Authentication Module. Simply copy the files next to the generated server application and start up the server.

Note
When starting the server from within Visual Studio, the working directory is set to the folder containing the project file by default. The server expects the group and passwd files in its working directory, so copy the files next to the project file instead.

Step 5: Testing the Access Rights with UaExpert

To test our configuration, we connect to the server using UaExpert the same way as shown in the last lesson, using encryption and the username “root”.

Being connected to the server user “root” we can read, write and subscribe to all three variables without a problem (see Figure 3-1).

Figure 3-1 Writing Variables as User “root”

gettingstarted1_lesson_security03_write_as_root_uaexpert.png

Now let’s connect to the server as anonymous user and try again. Disconnect, right click on the server in the Project window and choose “Properties...”. Select Anonymous in the “Authentication Settings”and click on “Connect Server” (see Figure 3-2).

Figure 3-2 Change to Anonymous User

gettingstarted1_lesson_security03_change_auth_uaexpert.png

When trying to write to the variable Sample_WriteRoot_ReadAll you will notice that it returns with error “BadNotWritable”. Notice that the variable Sample_WriteRoot_ReadRoot doesn’t even show up in the address space, as only the user “root” is allowed to browse, read, or write it (see Figure 3-3).

Figure 3-3 Writing Variables as Anonymous User

gettingstarted1_lesson_security03_write_as_anonymous_uaexpert.png