Authorization, AccessLevel, UserAccessLevel

1,175 views
Skip to first unread message

Wolfgang Hottgenroth

unread,
Jun 16, 2017, 6:28:32 AM6/16/17
to open62541
Hi,


I'm wondering how authorization in OPC-UA / open62541 is meant to work.

There are the attributes AccessLevel and UserAccessLevel. But it seems to me, that using AccessLevel a node can only be set to read or read/write (and the same for history data). The same for UserAccessLevel: an user can read or an user can read and write. (Somewhere I read that this mechanisms are not yet implemented in open62541.)
Using this mechanism it is not possible to introduce a kind of role-based authorization: this user can read this but can not write that, this user can read not this but something completely different and so on. Or am I wrong?

Additionally, there is section 4.8 "User Authorization" in the OPC-UA Spec 1.03, Part 2, "Security Model". It says: "Client and Server applications may determine in their own way what data is accessible and what operations are authorized."
Am I right that open62541 currently doesn't have any support for such a fine-grained role-based authorization model?

Given that I want to introduce such an authorization model on my own: would you agree that the Service_Read / Service_Read_single and Service_Write / UA_Server_editNode (Is there no Service_Write_single any more?) functions are the right place to call callbacks in the server application to determine based on the information in UA_Session* session and UA_NodeId id->nodeId (or the node it is pointing to) whether a specify user can read, read&write or has no access at all?


Cheers and thanks,
Wolfgang

Julius Pfrommer

unread,
Jun 16, 2017, 7:08:37 AM6/16/17
to open62541
Hello Wolfgang,

access control is a new addition to open62541. You can find it on the master branch.
The API closely follows the OPC UA mechanisms. See the spec for comparison.
With this addition, the UserAccessLevel attribute is different for every user.
The value is not stored in the node, but provided by a callback mechanism to an access control layer.

We define a plugin API that does two things:
- Callback to userland to authenticate a session. There, user-defined data can be attached to every session.
- Callback to the access control plugin to allow operations for a session. The user-defined session data is forwarded to the plugin.

The access control plugin can only take away rights that are basically permitted for the node.
There is a default implementation that allows "everything".
Allowing "everything" in that context means returning a bitfield with all ones. This is then XORed with the AccessLevel of the node.
https://github.com/open62541/open62541/blob/master/plugins/ua_accesscontrol_default.h

Yes, the services you mentioned are the right place to add this feature.
On master, it is already done in many places. But it is currently not verified to be complete.

Best regards,
Julius
Message has been deleted

Julius Pfrommer

unread,
Jun 16, 2017, 7:12:33 AM6/16/17
to open62541
Erratum: Of course, I meant ANDed instead of XOred.

Wolfgang Hottgenroth

unread,
Jun 16, 2017, 10:19:33 AM6/16/17
to open62541
Hello Julius,


aha! I see what you mean. The getUserAccessLevel callback is called for every single node to access and I can return from my implementation of the callback specific values for the UserAccessLevel based on the particular accessed node and the session information (which contains or can contain information on the user). Thus I can implement a fine-grained user and node considering authorization model on application level.

However, it seems to me, that getUserAccessLevel is currently in the master branch only consider when writing a node:

static UA_StatusCode
copyAttributeIntoNode(UA_Server *server, UA_Session *session,
                      UA_Node *node, const UA_WriteValue *wvalue) {   
...
    case UA_ATTRIBUTEID_VALUE:
        CHECK_NODECLASS_WRITE(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
        if(node->nodeClass == UA_NODECLASS_VARIABLE) {
            /* The access to a value variable is granted via the AccessLevel Byte */
            UA_Byte userAccessLevel = getUserAccessLevel(server, session, (const UA_VariableNode*)node);
            if(!(userAccessLevel & 0x02)) {
                retval = UA_STATUSCODE_BADUSERACCESSDENIED;
                break;
            }
        } else { /* UA_NODECLASS_VARIABLETYPE */
            CHECK_USERWRITEMASK(21);
        }
        retval = writeValueAttribute(server, (UA_VariableNode*)node,
                                     &wvalue->value, &wvalue->indexRange);
        break;


This is the only place where it is called. <Besserwisser>There should be certainly UA_ACCESSLEVELMASK_WRITE instead of 0x02 in the second if-statement, right?</Besserwisser>
So, for the read direction I would put a kind of that second if-statement it into Service_Read_single, of course with 0x01, oops, I mean UA_ACCESSLEVELMASK_READ. Or is this too short-sighted?


Cheers and thanks,
Wolfgang



Am Freitag, 16. Juni 2017 13:08:37 UTC+2 schrieb Julius Pfrommer:

Julius Pfrommer

unread,
Jun 16, 2017, 12:46:20 PM6/16/17
to open62541
You are right.
The suggestions you made are implemented in the following PR:
https://github.com/open62541/open62541/pull/1077

I changed the server code to give a read-accesslevel by default.
Now we can check for the accesslevel without breaking too much legacy code.

Feel free to post more suggestions for improvements and / or patches.

Best regards,
Julius
Reply all
Reply to author
Forward
0 new messages