High Performance OPC UA Server SDK  1.7.1.383
Authorization

The authorization module is responsible for controlling the access of users to different nodes. It is mostly independent from the Authentication module which verifies user identities. Since version 1.6.0 of the SDK the Rolepermissions as defined in the OPC Specification are implemented for authorization control.

Concepts

This section gives an overview of the basic authorization concepts in the SDK and the OPC UA Specification before diving deeper into the details in the following sections. It is strongly recommended to also consult the OPC UA Specification, Part 18 on Role-Based Security for a better understanding of Rolepermissions.

Backend

The authorization is implemented in the authorization backend, which can be selected through cmake by the option UA_AUTHORIZATION_BACKEND. A backend needs to implement the functions from the frontend src/uaserver/session/authorization/authorization.h and certain types from src/uaserver/session/authorization/authorization_types.h. The frontend functions and the higher level convenience functions outside this module (like node rolepermissions) work independent from the current backend being used. Thus it is preferred to use these functions when possible, however some functionality like role management is backend specific.

The default backend is rolepermissions which is the SDK implementation of UA Rolepermissions and will be described further in the following sections below.

Another included backend is null which implements all the required frontend functions, however these are only dummy implementations, so authorization is effectively disabled and every user has full access to every node. However, Accessrestrictions are implemented outside of the backend and still enforced also when using the null backend.

Roles

Roles are used to manage permissions for different users, when a client connects to the server, it is assigned one to multiple roles. The roles are assigned based on the identity the client provides and the mapping rules configured in the server. The mapping rules are defined in the OPC Specification and allow different criteria to map user identities to roles, here is a small example for such a mapping:

Role CriteriaType Criteria Comment
Anonymous Anonymous <none> Assigns the Anonymous role to all clients with an anonymous token
Observer AuthenticatedUser <none> Assigns the Observer role to all client with a non-anonymous token
Engineer Username John Assigns the Engineer role to all clients with a username token and username either John or Sue
(and a correct password for that username)
^ Username Sue ^

In this case both the Engineer and Observer role would be assigned to the users John and Sue.

The server exposes its configured roles as Objects in the addressspace at Root->Objects->Server->ServerCapabilities->RoleSet, the Identities Property shows the mapping rules, further Properties show the further configuration of the role. However, the role Objects and/or Properties might only be visible to certain roles and only when using an encrypted connection.

In the above example the name of the role is used as identifier, but to uniquely identify roles in UA every role has a nodeid assigned. The nodeid is also used as identifier for the role Object in the addressspace, the Object's browsename is mostly simply called the role's name in the SDK. When combining parts of roles from different sources, roles with the same nodeid also need to have matching names.

Nodepermissions

For the individual permissions attached to each node the SDK uses the term Nodepermissions, this is to avoid name clashes with rolepermission types defined in namespace 0 and to have a distinct name rather than the more broadly used term rolepermissions.

The nodepermissions consists of a role and and the permissions for that role in the form of OR-ed bits from ua_permissiontype. An array of these nodepermissions is assigned to each node like this:

Node Role Permissions
NodeA Anonymous BROWSE
^ AuthenticatedUser BROWSE | CALL
NodeB Anonymous BROWSE
^ AuthenticatedUser BROWSE | READ
^ Engineer BROWSE | READ | WRITE

In contrast to individual nodepermissions, a node may also use the namespace default permissions. These also have the form of nodepermissions and are used for every node which does not have explicit nodepermissions set. In case the namespace default permissions are changed, these are changed for all nodes without individual permissions.

Accessrestrictions

Accessrestrictions can be set on individual nodes to further limit access to that node, e.g. only to clients which use an encrypted connection. Accessrestrictions are OR-ed bits from ua_accessrestrictiontype.

XML/BIN Files

The NodeSet2.xml format allows to specify Accessrestrictions, permissions for each node and namespace default permissions. Roles can also be present as normal Objects of type RoleType below the RoleSet Object. The xml2bin/xml2c tools support all these elements, so generated addressspaces will have these set correctly if the source XML already contains these. Note that even when loading role Objects from XML, the content of their Properties must still configured in one of the ways mentioned below.

Roles

Well-Known Roles

The OPC Foundation has defined some roles in namespace 0, these roles are present in every server and are considered well-known. A server application does not need to facilitate them, when not assigning any identities to these roles no client will be able to gain them. However some of the roles might be used by companion specifications and there are three roles of special importance:

  • AuthenticatedUser: This role is intended for all clients authenticating with a non-anonymous token like the username token. The AUTHENTICATEDUSER criteria is usually used as the only identity mapping rule to achieve this behavior.
  • Anonymous: This role is intended for all clients authenticating with an anonymous token, but it is usually also given to any other client. This ensures authenticated users have at least the permissions of anonymous users and there are no nodes or values hidden from authenticated users while anonymous would be able to access them. So in the end the permissions given to the anonymous role apply for every connected client.
  • SecurityAdmin: This role is used by the SDK when limited access for certain nodes is required by the OPC Specification, like certificate management or session security diagnostics.

The well-known roles are exposed as objects in the addressspace like every other role. As the well-known roles are inside namespace 0, their nodeids are accessible as literals in form of the UA_ID_WELLKNOWNROLE_* defines from src/uabase/identifiers.h.

Finding and Identifying Roles

Roles are exposed as objects in the addressspace, thus the official identifier from a client's point of view for a role is the nodeid of the object. This applies to well-known roles and roles from companion specifications as well as roles specified by the server application itself. However when working with roles inside the server nodeids are not very handy, thus the role management mostly works with the internal role_ids when possible.

The role_id is returned when adding a role with ua_role_add_role or can be found with ua_authorization_find_role_by_nodeid, the reverse operation is ua_authorization_get_role_nodeid. The role_ids are valid for the complete runtime of the server, it is recommended for applications to also use the internal role_ids when doing internal work with roles and permissions.

Loading Roles

Roles exists in the SDK both as an internal struct inside the rolepermissions backend with an internal role_id to efficiently compare roles and in the addressspace as Objects of type RoleType. It is possible to create the addressspace Object from the internal struct and vice versa, the function which can do this is ua_role_synchronize_roles. It is called during server initialization and also ensures both of these role representations are consistent.

However some parts of the role are not loaded from the addressspace even if present in the source XML file, the values of the role object's Properties must be configured by one of the two ways described in the following:

Loading Roles from a Config File

The SDK allows to define and configure roles via a dedicated role configuration file, it can be specified in the main configuration file or appconfig with the setting session.authorization_roles_file and will then be loaded automatically at server startup.

For more information on this file see Roles Configuration File, here only a simple example of such a role configuration file is shown:

As the file stores nodeids to identify the roles, first a namespace table is needed:

[nstable]

nstable/size = 2
nstable/0/url = http://opcfoundation.org/UA/
nstable/1/url = <server>

The server namespace (1) may change depending on the host the server is running, thus the special placeholder <server> is always mapped to the server namespace.

The roles themselves are in an array with one entry for each role, with each role having an array of identity mapping rules:

[roles]

roles/size = 3

roles/0/name = Anonymous
roles/0/nodeid = i=15644
roles/0/identities/size = 2
roles/0/identities/0/criteria_type = ANONYMOUS
roles/0/identities/1/criteria_type = AUTHENTICATEDUSER

roles/1/name = AuthenticatedUser
roles/1/nodeid = i=15656
roles/1/identities/size = 1
roles/1/identities/0/criteria_type = AUTHENTICATEDUSER

roles/2/name = SomeDemoRole
roles/2/nodeid = ns=1;s=DemoRole
roles/2/identities/size = 2
roles/2/identities/0/criteria_type = USERNAME
roles/2/identities/0/criteria = sue
roles/2/identities/1/criteria_type = USERNAME
roles/2/identities/1/criteria = john

The nodeids i=15644 and i=15656 are well-known namespace 0 nodeids and the Objects already exist in Opc.Ua.NodeSet2.xml, these entries provide the identity mapping rules allowing the server to actually assign these roles to users. The name would not be strictly required for these two entries, but it provides better readability and it is validated against the browsename of the role Objects to help detect inconsistencies.

The Anonymous role has both the ANONYMOUS and AUTHENTICATEDUSER identity mapping, this gives the Anonymous to every user successfully connecting to the server.

The third role does not exist in the addressspace as Object and is created in the server namespace by the SDK during server startup. The role Object will use up some resources like nodes and strings in the respective namespace, so these extra resources must be allocated in that namespace.

Adding Roles in Code

Using the role configuration file is the recommended way for configuring roles, however in some cases file IO may not be possible or wanted, it might as well be desired to modify roles during server runtime. Then the functions provided by the SDK for role management must be used, as the role management is implemented in the backend, these functions can be found in RolePermission Backend.

For simple cases, when the role Objects already exits in the loaded addressspace and have numeric nodeids, like it is the case for the well-known namespace 0 roles, the helper function ua_role_add_numeric_identities can be used to just add the identity mapping rules:

static const struct ua_role_numeric_identity identities[] = {
    {0, UA_ID_WELLKNOWNROLE_ANONYMOUS,         UA_IDENTITYCRITERIATYPE_ANONYMOUS,         NULL},
    {0, UA_ID_WELLKNOWNROLE_ANONYMOUS,         UA_IDENTITYCRITERIATYPE_AUTHENTICATEDUSER, NULL},
    {0, UA_ID_WELLKNOWNROLE_AUTHENTICATEDUSER, UA_IDENTITYCRITERIATYPE_AUTHENTICATEDUSER, NULL},
    {0, UA_ID_WELLKNOWNROLE_SECURITYADMIN,     UA_IDENTITYCRITERIATYPE_USERNAME,          "root"},
};

ret = ua_role_add_numeric_identities(identities, countof(identities), false);
if (ret != 0) goto error;

When the role needs to be created first, ua_role_add_role must be called, followed by ua_role_set_name. Then the further configuration can be done, like setting the identity mapping rules with ua_role_set_identities. Afterwards it might be necessary to call ua_role_synchronize_roles.

Nodepermissions

Default Permissions

There are quite a lot of NodeSet2.xml files without any permissions specified at all, in this case the rolepermissions backend will use the namespace default permissions for every node. As the default permissions aren't given either, the SDK makes up some arbitrary permissions and uses them as default permissions for that namespace. Thus it is recommended to set explicit default permissions using ua_addressspace_set_default_rolepermissions.

Even though a XML file has permissions for nodes specified, it may not set the default permission or it has default permissions but these are completely unsuitable for the server. Thus it is also for these cases recommended to set the default permissions with ua_addressspace_set_default_rolepermissions.

Getter and Setter

The essential operations for nodepermissions are available backend independent through node rolepermissions. It has functions to get (ua_node_rp_get) nodepermissions and various setters (like ua_node_rp_set and ua_node_rp_set_ns_default). There are also functions for checking specific permissions for nodes like ua_node_allow_read or ua_node_allow_write, these are used by the SDK during the respective service calls and may also be used by applications when choosing to do own provider implementation certain services. The ua_user_ctx struct needed for these functions is embedded inside the uasession_session struct which is available inside each service call implementation.

Permissions for Methods

Permissions for Methods work a bit different than the usual permissions as to determine whether a method call is allowed the call permission bit of two nodes must be checked. For a method call two nodeids must be provided, the id of the Object and the id of the Method. The Method may be the Method directly below the Object, but it may also be the Method at the Object's typedefinition node, both are equally valid. For the permission check however the permissions of the Object and the Method directly below the Object must have the call permission bit set. So even when the Method at the Object's typedefinition is provided by the client, the server still needs to check the permission at the Object's own Method.

This lookup of the call permission bits for the correct nodes is implemented by uaserver_call_utility_check_permission. When using the SDK's default call service implementation this function is called automatically, applications doing their own implementation of the call service may call this function manually or implement that logic on their own.

Permissions for Events

Events can also only be received when the receive events bit of two nodes allow it: the event type and the source node of the event. Both are given when creating an event with uaserver_event_create and the SDK does check the receive events bit before sending the event to the client. The eventing logic is completely implemented inside the SDK, so there is nothing an application has to do.

Troubleshooting

The configuration of roles and permissions can quickly get rather complex, so this section helps finding out permissions of nodes and which roles are being assigned to users.

Permissions

The permission configured for each node are visible through the UAExpert in the respective attributes, this section provides help understanding the attributes. Some of them can change with the current user being connected to the server, so it might be necessary to change the user, this can be done through the Server->Change User dialog. If a node has accessrestrictions set it might also be necessary to use an encrypted connection to see the node or certain attributes.

  • RolePermissions: The RolePermissions attribute shows all the permission configured for the selected node, however the array may also be empty, that means the node has no individual permissions configured and uses the namespace default permissions.

    This attribute has the same value for every user, but access is usually limited, so if BadUserAccessDenied is shown a user with more privileges is needed, probably a member of the role SecurityAdmin. The access to this attribute is controlled by its own permission bit: ReadRolePermissions

  • UserRolePermissions: The UserRolePermissions attribute shows the subset from RolePermissions which is valid for the current user, so this attribute shows the permissions effectively used for the particular user.

    This attribute always shows the permissions, even when the RolePermissions attribute uses the defaults and is empty, thus when the attribute is empty the user actually has zero permissions for this node (in this case however access to the attribute would be denied). Access to this attribute is usually not specially limited, it is controlled by the Browse permission bit like the other regular node attributes.

  • Default Permissions: Each namespace of the server exposes its DefaultRolePermissions and DefaultUserRolePermissions as Properties of the respective namespace Object at Root->Objects->Server->Namespaces.

    In contrast to the above, the relevant data is located in the value attribute of the Properties. These values act like the RolePermissions and UserRolePermissions above, but show the default permissions for the respective namespace instead of just a single node. Also the special meaning of empty does not exists for the DefaultRolePermissions as there is no fallback to defaults.

One more note: When it is not possible to find out the role required to access certain nodes, the SDK allows to bypass the permission check for certain roles by enabling the option ignore_permissions for a role:

roles/X/ignore_permissions = true

When connecting with a member of such a role and having encryption enabled, the client has full access to every attribute of every node in the addressspace, no matter what permissions or restrictions are configured. Of course this option is a last resort and should only be used for finding permission issues, it is not recommended for production use.

Role Assignment

To find out which roles are being assigned to a user there are two options:

  • Addressspace: This is probably the easier option when connected to the server with the UAExpert anyway, but it also gives rather limited information.

    The UserRolePermissions attribute shows the subset of the RolePermissions which apply to the current user. So the user is member of every role listed in the UserRolePermissions, however it shows only roles also listed in the RolePermissions. Thus to find out whether the user is assigned to a certain role, the UserRolePermissions of a node which also has RolePermissions for that specific role must be checked.

  • Server Trace: The server can nr started with the trace levels info and debug and for clarity limited to the session facility, for the demoserver this is possible with the following parameters:

    uaserverhp -d info,debug -f session
    

    When a client connects or changes its user, the trace now has detailed information for each role and based on which criteria it is given to the user or denied to the user.

Mapping User-Group-Other to Roles

Previous versions of the SDK included the inode authorization backend which had a user-group-other concept for permissions, similar to the Unix-like permission model. This backend was replaced by the more powerful rolepermission backend, however for new or existing applications that prefer or already have the user-group-other model it is still possible to map that model to rolepermissions. This section shows how this mapping can be conceptually done.

With the rolepermissions backend only roles are possible, so the user-group-other permissions are replaced by role-role-role permissions with the following mapping:

  • user: For each user configured in the authentication backend (e.g. by the passwd file) a role is created. As the only identity for that role the respective user configured with the username as criteria and criteria type USERNAME. This user-role now acts as the owner of the file.
  • group: The group has the most similarity to a role, so instead of a group, a role is created. The users can again be added with the USERNAME criteria type or any other if it is more suitable.
  • other: The other permissions apply to every client, so the anonymous role can be used, as it is assigned to every client when given the identities ANONYMOUS and AUTHENTICATEDUSER as recommended and shown in the above examples.

When giving each node exactly these three roles it is also possible to reduce the cmake option UASERVER_MAX_ROLES_PER_NODE to 3 which can save some amount of memory.