Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add static init for instances of Partition and Authentication #766

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
102 changes: 53 additions & 49 deletions .docs/content/1.concepts/3.authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,19 @@ ArmoniK allows users to impersonate other users by adding an impersonation heade

ArmoniK uses a User-Role-Permission based approach to handle authorization. Each user in the database can have a set of **Roles**. Each role contains a set of **Permissions**. A user cannot receive permissions directly, instead roles containing the permissions have to be created and given to the user. A permission is defined as a string in a specific format.
The current version handles the following types of permissions :
|Format|Example|Parameters|Description|
---|---|---|---|
``General:Impersonate:<Rolename>``|``General:Impersonate:Monitoring``|**Rolename**: Name of a role|Grants the right to impersonate a user with the role named \<Rolename\>. See [Impersonation](#impersonation) for details|
|``<Service>:<Name>``|``Submitter:CreateSession``|**Service**: Name of an ArmoniK web service<br>**Name**: Name of the endpoint|Grants the right to use the endpoint named \<Name\> of the service named \<Service\>|
|``<Service>:<Name>:<Target>``|``Submitter:CancelSession:Self``|**Service**: Name of an ArmoniK web service<br>**Name**: Name of the endpoint<br>**Target**: Target or scope of the permission|Same as ``<Service>:<Name>`` as ``<Target>`` is currently unused|
| Format | Example | Parameters | Description |
|------------------------------------|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|
| ``General:Impersonate:<Rolename>`` | ``General:Impersonate:Monitoring`` | **Rolename**: Name of a role | Grants the right to impersonate a user with the role named \<Rolename\>. See [Impersonation](#impersonation) for details |
| ``<Service>:<Name>`` | ``Submitter:CreateSession`` | **Service**: Name of an ArmoniK web service<br>**Name**: Name of the endpoint | Grants the right to use the endpoint named \<Name\> of the service named \<Service\> |
| ``<Service>:<Name>:<Target>`` | ``Submitter:CancelSession:Self`` | **Service**: Name of an ArmoniK web service<br>**Name**: Name of the endpoint<br>**Target**: Target or scope of the permission | Same as ``<Service>:<Name>`` as ``<Target>`` is currently unused |

## User authorization

When an user sends a request to an endpoint, they need to be authenticated, and they need the permission to use this endpoint. If they lack the permission, then they will be forbidden from using it.

## Request authorization flowchart

<Mermaid>
```mermaid
flowchart TB
User([User Certificate])
OK(OK)
Expand All @@ -56,85 +56,93 @@ flowchart TB
CheckPerm --> |No|DENIED
CheckPerm --> |Yes|OK
Cache --> |Yes|CheckPerm
</Mermaid>
```

## User administration

Users, roles, permissions and certificates are stored and managed in a database. Administrators in charge of handling user permissions can refer to this section to manage user permissions.

### Authentication database deployments

The following database deployments can be used to handle authentication data :

- Internal MongoDB
- External MongoDB (Currently not supported)
- External SQL (Currently not supported)
Users, roles, permissions and certificates are stored and managed by ArmoniK via environment variables provided to the control plane and compute plane by the administrator during deployment. Administrators in charge of handling user permissions can refer to this section to manage user permissions.

### Populating the internal MongoDB when deploying ArmoniK

> **NOTE :** This is the current recommended method

You can define the users' roles and certificates using a json configuration during deployment. Check the [ArmoniK Authentication Configuration Guide](https://github.com/aneoconsulting/ArmoniK/blob/main/.docs/content/2.guide/1.how-to/how-to-configure-authentication.md) for more details.

### Using MongoDB directly

<!-- TODO: could be converted to ::alert -->
> **NOTE :** Using a MongoDB instance different from the one used in the rest of ArmoniK is currently not supported
> **NOTE :** If the deployment used is the internal MongoDB, to send commands to the database you need to use a script like the one available [here](https://github.com/aneoconsulting/ArmoniK/blob/main/tools/access-mongo-as-user.sh) to connect to the database as a user. Another script to connect to the database as admin is available [here](https://github.com/aneoconsulting/ArmoniK/blob/main/tools/access-mongo-as-admin.sh).
> **NOTE :** This method is available to anyone having access to the deployed cluster's secrets and an access to the MongoDB host and port. **Use it at your own risk**.
### Using ArmoniK environment variables

In order to function properly, the MongoDB database needs to have the following collections:
In order to function properly, the authentication needs to have the following collections:

- AuthData
- List of [Certificate](../../../Common/src/Injection/Options/Database/Certificate.cs)
- Handles the association between certificates and users
- Requires the following fields:
- UserId : _id field of the UserData object associated with this certificate
- User : Name field of the User object associated with this certificate
- CN : Certificate's Common name
- Fingerprint : null if this entry should match all certificates with the given CN, otherwise, the certificate's fingerprint
- The CN and Fingerprint fields form a unique compound index.
- UserData
- List of [User](../../../Common/src/Injection/Options/Database/User.cs)
- Handles the association between a user and its roles
- Requires the following fields
- Username : Unique user name
- Roles : list of objectIds, each matching the _id field in RoleData of the roles given to the user
- RoleData
- Name : Unique user name
- Roles : list of role names, each matching the Name field of the roles given to the user
- List of [Role](../../../Common/src/Injection/Options/Database/User.cs)
- Handles the association between a role and its permissions
- Requires the following fields
- RoleName : Unique role name
- Name : Unique role name
- Permissions : list of strings corresponding to the permissions of the role

#### Insert a role
These collections of object need to be provided as JSON objects as detailled in the following sections.

To insert a role with the name "Role1" granting the permissions "Submitter:ListTasks" and "General:Impersonate:Role2", use the following command :
#### Environment variables base

```javascript
db.RoleData.insertOne({RoleName:"Role1", Permissions:["Submitter:ListTasks", "General:Impersonate:Role2"]})
An InitServices options class was introduced to initialize services.
It contains two classes : Authentication and Partitionning to configure authentication and partitions respectively.
Authentication has several list of strings as fields: UserCertificates, Roles and Users.
Those fields are JSON strings that are deserialized into corresponding objects that will be inserted into the database.

These options can be configured as environment variables.
Lists should be converted into an environment variable per element with its index as shown below.

For example, the environment variables for the first and second roles are:

```bash
InitServices__Authentication__Roles__0=<json string containing a [Role]>
InitServices__Authentication__Roles__1=<json string containing a [Role]>
```

See the [MongoDB documentation](https://www.mongodb.com/docs/manual/reference/method/db.collection.insertOne/) to get the RoleId (_id field) of the inserted role from the command's response.
#### Specify roles

#### Insert a user
To specify a role with the name "Role1" granting the permissions "Submitter:ListTasks" and "General:Impersonate:Role2", use the following environment variable:

To insert a user with the name "User1" with the role "Role1", use the following command :
```bash
InitServices__Authentication__Roles__0='{"Name": "Role1", "Permissions": ["Submitter:ListTasks", "General:Impersonate:Role2"]}'
```

```javascript
db.UserData.insertOne({User:"User1", Roles:["Role1"]})
If you need a second role, you can add the following environment variable too:

```bash
InitServices__Authentication__Roles__1='{"Name": "Role2", "Permissions": ["Submitter:ListTasks", "General:Impersonate:Role3"]}'
```

See the [MongoDB documentation](https://www.mongodb.com/docs/manual/reference/method/db.collection.insertOne/) to get the UserId (_id field) of the inserted user from the command's response.
#### Specify users

#### Insert a certificate
To specify a user with the name "User1" with the role "Role1", use the following environment variable:

```bash
InitServices__Authentication__Users__0='{"Name": "User1", "Roles": ["Role1"]})'
```

To insert a certificate with Common Name "CN1" and Fingerprint "FP1" associated with the user with UserId "62f4efe6d82645e26e09584f", use the following command :
#### Specify certificates

To insert a certificate with Common Name "CN1" and Fingerprint "FP1" associated with the User called "User1", use the following environment variable:

```javascript
db.AuthData.insertOne({UserId:"62f4efe6d82645e26e09584f", CN:"CN1", Fingerprint:"FP1"})
InitServices__Authentication__UserCertificates__0='{"User": "User1", "CN": "CN1", "Fingerprint": "FP1"}'
```

To insert an entry matching all certificates with Common Name "CN1" associated with user with UserId "62f4efe6d82645e26e09584f", use the following command :
To insert an entry matching all certificates with Common Name "CN1" associated with the User called "User1", use the following environment variable:

```javascript
db.AuthData.insertOne({UserId:"62f4efe6d82645e26e09584f", CN:"CN1"})
InitServices__Authentication__UserCertificates__0='{"User": "User1", "CN": "CN1"}'
```

#### Edit a user/role/certificate
Expand All @@ -145,10 +153,6 @@ Refer to [this link](https://www.mongodb.com/docs/manual/reference/method/db.col

Refer to [this link](https://www.mongodb.com/docs/manual/reference/method/db.collection.findOneAndDelete/) for the procedure to delete MongoDB entries.

### Using SQL directly

Currently not implemented

### Using the User Administration gRPC service

Currently not implemented
56 changes: 35 additions & 21 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -431,20 +431,34 @@ jobs:
strategy:
fail-fast: false
matrix:
queue:
- activemq
- rabbitmq
- rabbitmq091
- pubsub
- sqs
object:
- redis
- minio
- embed
log-level:
- Information
- Verbose
name: HtcMock ${{ matrix.queue }} ${{ matrix.object }} ${{ matrix.log-level }}
target:
- { queue: activemq, object: redis, log-level: Information, cinit: true }
- { queue: rabbitmq, object: redis, log-level: Information, cinit: true }
- { queue: rabbitmq091, object: redis, log-level: Information, cinit: true }
- { queue: pubsub, object: redis, log-level: Information, cinit: true }
- { queue: sqs, object: redis, log-level: Information, cinit: true }

- { queue: activemq, object: redis, log-level: Verbose, cinit: true }
- { queue: rabbitmq, object: redis, log-level: Verbose, cinit: true }
- { queue: rabbitmq091, object: redis, log-level: Verbose, cinit: true }
- { queue: pubsub, object: redis, log-level: Verbose, cinit: true }
- { queue: sqs, object: redis, log-level: Verbose, cinit: true }

- { queue: activemq, object: minio, log-level: Information, cinit: true }
- { queue: rabbitmq, object: minio, log-level: Information, cinit: true }
- { queue: rabbitmq091, object: minio, log-level: Information, cinit: true }
- { queue: pubsub, object: minio, log-level: Information, cinit: true }
- { queue: sqs, object: minio, log-level: Information, cinit: true }

- { queue: activemq, object: embed, log-level: Information, cinit: true }
- { queue: rabbitmq, object: embed, log-level: Information, cinit: true }
- { queue: rabbitmq091, object: embed, log-level: Information, cinit: true }
- { queue: pubsub, object: embed, log-level: Information, cinit: true }
- { queue: sqs, object: embed, log-level: Information, cinit: true }

- { queue: activemq, object: redis, log-level: Information, cinit: false }

name: HtcMock ${{ matrix.target.queue }} ${{ matrix.target.object }} ${{ matrix.target.log-level }} ${{ matrix.target.cinit }}
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
Expand All @@ -468,7 +482,7 @@ jobs:
- name: Deploy Core
run: |
MONITOR_PREFIX="monitor/deploy/" tools/retry.sh -w 30 -- tools/monitor.sh \
just log_level=${{ matrix.log-level }} tag=${VERSION} queue=${{ matrix.queue }} object=${{ matrix.object }} worker=htcmock deploy
just log_level=${{ matrix.target.log-level }} tag=${VERSION} queue=${{ matrix.target.queue }} object=${{ matrix.target.object }} cinit=${{ matrix.target.cinit }} worker=htcmock deploy
sleep 10

- name: Print And Time Metrics
Expand Down Expand Up @@ -537,7 +551,7 @@ jobs:

- name: Run HtcMock test 1000 tasks 1 level
timeout-minutes: 3
if: ${{ matrix.log-level != 'Verbose' }}
if: ${{ matrix.target.log-level != 'Verbose' }}
run: |
MONITOR_PREFIX="monitor/htcmock-1000-1/" tools/monitor.sh \
docker run --net armonik_network --rm \
Expand All @@ -552,7 +566,7 @@ jobs:

- name: Run HtcMock test 1000 tasks 4 levels
timeout-minutes: 3
if: ${{ matrix.log-level != 'Verbose' }}
if: ${{ matrix.target.log-level != 'Verbose' }}
run: |
MONITOR_PREFIX="monitor/htcmock-1000-4/" tools/monitor.sh \
docker run --net armonik_network --rm \
Expand All @@ -576,8 +590,8 @@ jobs:
run: |
export AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }}
export AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }}
docker cp fluentd:/armonik-logs.json - | gzip -c | aws s3 cp - s3://${{ secrets.AWS_LOG_BUCKET_NAME }}/core-pipeline/${{ github.run_number }}/${{ github.run_attempt }}/htcmock-${{ matrix.queue }}-${{ matrix.object }}-${{ matrix.log-level }}.json.gz
tar -czf - monitor/ | aws s3 cp - s3://${{ secrets.AWS_LOG_BUCKET_NAME }}/core-pipeline/${{ github.run_number }}/${{ github.run_attempt }}/htcmock-${{ matrix.queue }}-${{ matrix.object }}-${{ matrix.log-level }}-monitor.tar.gz
docker cp fluentd:/armonik-logs.json - | gzip -c | aws s3 cp - s3://${{ secrets.AWS_LOG_BUCKET_NAME }}/core-pipeline/${{ github.run_number }}/${{ github.run_attempt }}/htcmock-${{ matrix.target.queue }}-${{ matrix.target.object }}-${{ matrix.target.log-level }}-${{ matrix.target.cinit }}.json.gz
tar -czf - monitor/ | aws s3 cp - s3://${{ secrets.AWS_LOG_BUCKET_NAME }}/core-pipeline/${{ github.run_number }}/${{ github.run_attempt }}/htcmock-${{ matrix.target.queue }}-${{ matrix.target.object }}-${{ matrix.target.log-level }}-${{ matrix.target.cinit }}-monitor.tar.gz

- name: Collect docker container logs
uses: jwalton/gh-docker-logs@2741064ab9d7af54b0b1ffb6076cf64c16f0220e # v2
Expand All @@ -589,14 +603,14 @@ jobs:
run: |
export AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }}
export AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }}
tar -cvf - ./container-logs | aws s3 cp - s3://${{ secrets.AWS_LOG_BUCKET_NAME }}/core-pipeline/${{ github.run_number }}/${{ github.run_attempt }}/htcmock-${{ matrix.queue }}-${{ matrix.object }}-${{ matrix.log-level }}-container-logs.tar.gz
tar -cvf - ./container-logs | aws s3 cp - s3://${{ secrets.AWS_LOG_BUCKET_NAME }}/core-pipeline/${{ github.run_number }}/${{ github.run_attempt }}/htcmock-${{ matrix.target.queue }}-${{ matrix.target.object }}-${{ matrix.target.log-level }}-${{ matrix.target.cinit }}-container-logs.tar.gz
- name: Export and upload database
if: always()
run: |
export AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }}
export AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }}
bash tools/export_mongodb.sh
tar -cvf - *.json | aws s3 cp - s3://${{ secrets.AWS_LOG_BUCKET_NAME }}/core-pipeline/${{ github.run_number }}/${{ github.run_attempt }}/htcmock-${{ matrix.queue }}-${{ matrix.object }}-${{ matrix.log-level }}-database.tar.gz
tar -cvf - *.json | aws s3 cp - s3://${{ secrets.AWS_LOG_BUCKET_NAME }}/core-pipeline/${{ github.run_number }}/${{ github.run_attempt }}/htcmock-${{ matrix.target.queue }}-${{ matrix.target.object }}-${{ matrix.target.log-level }}-${{ matrix.target.cinit }}-database.tar.gz

testWindowsDocker:
needs:
Expand Down
2 changes: 1 addition & 1 deletion Adaptors/MongoDB/src/AuthenticationTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ await authCollectionProvider_.Init(cancellationToken)
}

/// <inheritdoc />
public async Task<UserAuthenticationResult?> GetIdentityFromUserAsync(string? id,
public async Task<UserAuthenticationResult?> GetIdentityFromUserAsync(int? id,
string? username,
CancellationToken cancellationToken = default)
{
Expand Down
36 changes: 36 additions & 0 deletions Adaptors/MongoDB/src/Common/IMongoDataModelMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

using System.Threading.Tasks;

using ArmoniK.Core.Common.Injection.Options.Database;

using MongoDB.Driver;

namespace ArmoniK.Core.Adapters.MongoDB.Common;
Expand All @@ -25,10 +27,44 @@ public interface IMongoDataModelMapping<T>
{
string CollectionName { get; }

/// <summary>
/// Setup indexes for the collection
/// Can be called multiple times
/// </summary>
/// <param name="sessionHandle">MongoDB Client session</param>
/// <param name="collection">MongoDDB Collection in which to insert data</param>
/// <param name="options">Options for MongoDB</param>
/// <returns>
/// Task representing the asynchronous execution of the method
/// </returns>
Task InitializeIndexesAsync(IClientSessionHandle sessionHandle,
IMongoCollection<T> collection,
Options.MongoDB options);

/// <summary>
/// Setup sharding for the collection
/// Can be called multiple times
/// </summary>
/// <param name="sessionHandle">MongoDB Client session</param>
/// <param name="options">Options for MongoDB</param>
/// <returns>
/// Task representing the asynchronous execution of the method
/// </returns>
Task ShardCollectionAsync(IClientSessionHandle sessionHandle,
Options.MongoDB options);

/// <summary>
/// Insert data into the collection after its creation.
/// Can be called multiple times
/// </summary>
/// <param name="sessionHandle">MongoDB Client session</param>
/// <param name="collection">MongoDDB Collection in which to insert data</param>
/// <param name="initDatabase">Data to insert</param>
/// <returns>
/// Task representing the asynchronous execution of the method
/// </returns>
Task InitializeCollectionAsync(IClientSessionHandle sessionHandle,
IMongoCollection<T> collection,
InitDatabase initDatabase)
=> Task.CompletedTask;
}
Loading
Loading