Skip to content

Commit

Permalink
Implements a feature to support the Submodel-based RBAC rules backend…
Browse files Browse the repository at this point in the history
… (also Dynamic RBAC rules management) (#407)

* Updates Dynamic RBAC Management

Signed-off-by: Mohammad Ghazanfar Ali Danish <[email protected]>

* Updates SMAuth Rbac Storgae

Signed-off-by: Mohammad Ghazanfar Ali Danish <[email protected]>

* Adds test suite

Signed-off-by: Mohammad Ghazanfar Ali Danish <[email protected]>

* Adds tests

Signed-off-by: Mohammad Ghazanfar Ali Danish <[email protected]>

* Updates tests

Signed-off-by: Mohammad Ghazanfar Ali Danish <[email protected]>

* Adds documentation and fixes RbacRuleDeserializer

Signed-off-by: Mohammad Ghazanfar Ali Danish <[email protected]>

* Adds version to SM Client

Signed-off-by: Mohammad Ghazanfar Ali Danish <[email protected]>

* Cleans up the code

Signed-off-by: Mohammad Ghazanfar Ali Danish <[email protected]>

* Includes AAS Registry

Signed-off-by: Mohammad Ghazanfar Ali Danish <[email protected]>

* Updates documentation for AAS-Registry

Signed-off-by: Mohammad Ghazanfar Ali Danish <[email protected]>

---------

Signed-off-by: Mohammad Ghazanfar Ali Danish <[email protected]>
  • Loading branch information
mdanish98 authored Sep 9, 2024
1 parent 75d5956 commit 13251a6
Show file tree
Hide file tree
Showing 41 changed files with 2,250 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,9 @@
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.digitaltwin.basyx</groupId>
<artifactId>basyx.authorization.rules.rbac.backend.inmemory</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,9 @@
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.digitaltwin.basyx</groupId>
<artifactId>basyx.authorization.rules.rbac.backend.inmemory</artifactId>
</dependency>
</dependencies>
</project>
194 changes: 184 additions & 10 deletions basyx.aasregistry/basyx.aasregistry-feature-authorization/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,44 +28,218 @@ spring.security.oauth2.resourceserver.jwt.issuer-uri= http://localhost:9096/real

## RBAC rule configuration

For configuring RBAC rules, all the rbac rules should be configured inside a json file, the rules are defined as below:
This section outlines how RBAC rules are defined, stored, and managed, and provides detailed explanations of the backend persistency options available.

### What is a Rule in RBAC?

A rule in the context of RBAC is a policy that defines the actions a specific role can perform on certain resources. Each rule typically consists of the following components:

* **Role**: The entity (user or group) that the rule applies to, such as `admin` or `basyx-reader`.
* **Action**: The operation permitted by the role, such as `CREATE`, `READ`, `UPDATE`, or `DELETE`.
* **Target Information**: The resource(s) that the action can be performed on, like `aas` (Asset Administration Shell) IDs. The targetInformation defines coarse-grained control over the resource, you may define the aasIds with a wildcard (\*), it means the defined role x with action y can access any Asset Administration Shell on the repository. You can also define a specific AAS Identifier in place of the wildcard (\*), then the role x with action y could be performed only on that particular AAS. There could be a single aasId or multiple aasIds as a list.

### Example of a Simple RBAC Rule
```json
{
"role": "admin",
"action": "READ",
"targetInformation": {
"@type": "aas-registry",
"aasIds": "*"
}
}
```

In this example, the rule grants the admin role permission to perform the READ action on all resources of type aas.

## Persistency Support for RBAC Rules

The AAS Repository supports two backend persistency mechanisms for storing RBAC rules:

### InMemory RBAC Storage

* InMemory RBAC stores all rules directly in the application's memory.
* The rules must be configured during the application startup, and they remain in memory for the duration of the application's runtime.
* Rules are defined using a JSON format as defined below.

```json
[
{
"role": "basyx-reader",
"action": "READ",
"targetInformation": {
"@type": "aas-registry",
"aasId": "*"
"aasIds": "*"
}
},
{
"role": "admin",
"action": ["CREATE", "READ", "UPDATE", "DELETE"],
"targetInformation": {
"@type": "aas-registry",
"aasId": "*"
"aasIds": "*"
}
},
{
"role": "basyx-deleter",
"action": "DELETE",
"targetInformation": {
"@type": "aas-registry",
"aasId": "specificAasId"
"aasIds": ["testAasId1", "specificAasId", "testAasId2"]
}
}
]
]
```

[!Note]
* The Action are fixed as of now and limited to (CREATE, READ, UPDATE, DELETE, and EXECUTE).
* Rules cannot be modified after the application has started.
* This is suitable for simple, small-scale infrastructure or testing environment where the rule set remains static.

#### How to enable this storage?

The InMemory rule storage is used by default, but to explicitly configure, below propertiy needs to be configured inorder to enable the InMemory Storage:

```
basyx.feature.authorization.rules.backend=InMemory
```

### Submodel-based RBAC Storage

* RBAC rules are stored in a dedicated Security Submodel within a Configuration-Submodel Repository.
* The Configuration-Submodel Repository is a general-purpose repository that supports various configuration models, with RBAC rules being part of its Security Submodel.
* This repository is equipped with Authorization to ensure that only designated entities (like administrators or maintainers) can manage the rules inside the Security Submodel.
* Similar to InMemory RBAC storage, initial rules can be defined in JSON format. However, upon application startup, these rules are automatically adapted and stored in the Security Submodel.
* This allows for a more flexible and persistent management of rules, which can be updated or extended.

#### Example Configuration Process:
* Define initial rules in JSON format. (Not mandatory)
* On application startup, these rules are validated and stored in the Security Submodel.

The below JSON RBAC rule is automatically adapted to Submodel-based RBAC rule:

```json
{
"role": "admin",
"action": "DELETE",
"targetInformation": {
"@type": "aas-registry",
"aasIds": "*"
}
}
```

The equivalent of above rule in Submodel-based RBAC rule (below in JSON serialized format):
```json
{
"modelType": "SubmodelElementCollection",
"idShort": "YWRtaW5ERUxFVEVvcmcuZWNsaXBzZS5kaWdpdGFsdHdpbi5iYXN5eC5hYXNyZXBvc2l0b3J5LmZlYXR1cmUuYXV0aG9yaXphdGlvbi5BYXNUYXJnZXRJbmZvcm1hdGlvbg==",
"value": [
{
"modelType": "Property",
"value": "admin",
"idShort": "role"
},
{
"modelType": "SubmodelElementList",
"idShort": "action",
"orderRelevant": true,
"value": [
{
"modelType": "Property",
"value": "DELETE"
}
]
},
{
"modelType": "SubmodelElementCollection",
"idShort": "targetInformation",
"value": [
{
"modelType": "SubmodelElementList",
"idShort": "aasIds",
"orderRelevant": true,
"value": [
{
"modelType": "Property",
"value": "*"
}
]
}
]
}
]
}
```

[!Note]
* The API for adding and removing rules is consistent with that of a standard Submodel Repository.
* The IdShort of the rule is automatically generated and it will replace the original IdShort configured while adding the rule.
* Only addition and removal of rules are supported; updating existing rules is not allowed due to [constraints](#constraints-and-rule-management).
* Only the responsible entity (typically an administrator or maintainer) should be permitted to manage the rules within the Security Submodel.
* This ensures that the RBAC policies are strictly controlled and secure.

#### How to enable this storage?

The following properties needs to be configured inorder to enable the Submodel-based Storage:

```
basyx.feature.authorization.rules.backend=Submodel
basyx.feature.authorization.rules.backend.submodel.authorization.endpoint=<Endpoint of the Security Submodel>
basyx.feature.authorization.rules.backend.submodel.authorization.token-endpoint=<Token Endpoint>
basyx.feature.authorization.rules.backend.submodel.authorization.grant-type = <CLIENT_CREDENTIALS> or <PASSWORD>
basyx.feature.authorization.rules.backend.submodel.authorization.client-id=<client-id>
basyx.feature.authorization.rules.backend.submodel.authorization.client-secret=<client-id>
basyx.feature.authorization.rules.backend.submodel.authorization.username=<username>
basyx.feature.authorization.rules.backend.submodel.authorization.password=<password>
```

The role defines which role is allowed to perform the defined actions. The role is as per the configuration of identity providers or based on the organization. Action could be CREATE, READ, UPDATE, DELETE, and EXECUTE, there could be a single action or multiple actions as a list (cf. admin configuration above).
## Constraints and Rule Management

The targetInformation defines coarse-grained control over the resource, you may define the aasId with a wildcard (\*), it means the defined role x with action y can access any Asset Administration Shell Descriptors on the registry. You can also define a specific AAS Identifier in place of the wildcard (\*), then the role x with action y could be performed only on that particular AAS Descriptor.
* To ensure quick access to rules, a hash key is generated based on the role, single action, and target information type.
* Duplicate entries with the same role, action, and target information type are not allowed.
* This necessitates the splitting of rules and prevents the update of existing rules (since modifying a rule would alter the hash key, which could impact performance).
* Due to the constraint on hash key generation, updates to existing rules are not supported.
* If a rule needs to be changed, the existing rule must be removed, and a new rule must be added with the updated information.
* Applies to both the storage backend.

Note:
* The Action are fixed as of now and limited to (CREATE, READ, UPDATE, DELETE, and EXECUTE) but later user configurable mapping of these actions would be provided.
* Each rule should be unique in combination of role + action + target information
### Automatic Rule Splitting

* If a rule specifies multiple actions (e.g., ["CREATE", "READ"]), it will automatically be split into individual rules, each with a single action.

Example of Automatic Splitting:

```json
{
"role": "admin",
"action": ["CREATE", "READ"],
"targetInformation": {
"@type": "aas-registry",
"aasIds": "*"
}
}
```

Will be split into:

```json
{
"role": "admin",
"action": "CREATE",
"targetInformation": {
"@type": "aas-registry",
"aasIds": "*"
}
},
{
"role": "admin",
"action": "READ",
"targetInformation": {
"@type": "aas-registry",
"aasIds": "*"
}
}
```

## Action table for RBAC

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,13 @@
<artifactId>basyx.aasregistry-service-inmemory-storage</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.digitaltwin.basyx</groupId>
<artifactId>basyx.authorization.rules.rbac.backend.inmemory</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.digitaltwin.basyx</groupId>
<artifactId>basyx.authorization.rules.rbac.backend.submodel</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@
package org.eclipse.digitaltwin.basyx.aasregistry.feature.authorization;

import org.eclipse.digitaltwin.basyx.aasregistry.feature.authorization.rbac.AasRegistryTargetPermissionVerifier;
import org.eclipse.digitaltwin.basyx.aasregistry.feature.authorization.rbac.backend.submodel.AasRegistryTargetInformationAdapter;
import org.eclipse.digitaltwin.basyx.authorization.CommonAuthorizationProperties;
import org.eclipse.digitaltwin.basyx.authorization.rbac.RbacPermissionResolver;
import org.eclipse.digitaltwin.basyx.authorization.rbac.RbacStorage;
import org.eclipse.digitaltwin.basyx.authorization.rbac.RoleProvider;
import org.eclipse.digitaltwin.basyx.authorization.rbac.SimpleRbacPermissionResolver;
import org.eclipse.digitaltwin.basyx.authorization.rbac.TargetPermissionVerifier;
import org.eclipse.digitaltwin.basyx.authorization.rules.rbac.backend.submodel.TargetInformationAdapter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -54,5 +56,11 @@ public TargetPermissionVerifier<AasRegistryTargetInformation> getAasTargetPermis
public RbacPermissionResolver<AasRegistryTargetInformation> getAasPermissionResolver(RbacStorage rbacStorage, RoleProvider roleProvider, TargetPermissionVerifier<AasRegistryTargetInformation> targetPermissionVerifier) {
return new SimpleRbacPermissionResolver<>(rbacStorage, roleProvider, targetPermissionVerifier);
}

@Bean
public TargetInformationAdapter getAasRegistryTargetInformationAdapter() {

return new AasRegistryTargetInformationAdapter();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*******************************************************************************
* Copyright (C) 2024 the Eclipse BaSyx Authors
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* SPDX-License-Identifier: MIT
******************************************************************************/

package org.eclipse.digitaltwin.basyx.aasregistry.feature.authorization.rbac.backend.submodel;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import org.eclipse.digitaltwin.aas4j.v3.model.Property;
import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement;
import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection;
import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList;
import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty;
import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementCollection;
import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementList;
import org.eclipse.digitaltwin.basyx.aasregistry.feature.authorization.AasRegistryTargetInformation;
import org.eclipse.digitaltwin.basyx.authorization.rbac.TargetInformation;
import org.eclipse.digitaltwin.basyx.authorization.rules.rbac.backend.submodel.TargetInformationAdapter;
import org.eclipse.digitaltwin.basyx.core.exceptions.InvalidTargetInformationException;

/**
* An implementation of the {@link TargetInformationAdapter} to adapt with Aas
* {@link TargetInformation}
*
* @author danish
*/
public class AasRegistryTargetInformationAdapter implements TargetInformationAdapter {

@Override
public SubmodelElementCollection adapt(TargetInformation targetInformation) {

SubmodelElementCollection targetInformationSMC = new DefaultSubmodelElementCollection.Builder().idShort("targetInformation").build();

SubmodelElementList aasId = new DefaultSubmodelElementList.Builder().idShort("aasIds").build();

List<SubmodelElement> aasIds = ((AasRegistryTargetInformation) targetInformation).getAasIds().stream().map(this::transform).collect(Collectors.toList());
aasId.setValue(aasIds);

targetInformationSMC.setValue(Arrays.asList(aasId));

return targetInformationSMC;
}

@Override
public TargetInformation adapt(SubmodelElementCollection targetInformation) {

SubmodelElement aasIdSubmodelElement = targetInformation.getValue().stream().filter(sme -> sme.getIdShort().equals("aasIds")).findAny().orElseThrow(
() -> new InvalidTargetInformationException("The TargetInformation defined in the SubmodelElementCollection Rule with id: " + targetInformation.getIdShort() + " is not compatible with the " + getClass().getName()));

if (!(aasIdSubmodelElement instanceof SubmodelElementList))
throw new InvalidTargetInformationException("The TargetInformation defined in the SubmodelElementCollection Rule with id: " + targetInformation.getIdShort() + " is not compatible with the " + getClass().getName());

SubmodelElementList aasIdList = (SubmodelElementList) aasIdSubmodelElement;

List<String> aasIds = aasIdList.getValue().stream().map(Property.class::cast).map(Property::getValue).collect(Collectors.toList());

return new AasRegistryTargetInformation(aasIds);
}

private Property transform(String aasId) {
return new DefaultProperty.Builder().value(aasId).build();
}

}
Loading

0 comments on commit 13251a6

Please sign in to comment.