Skip to content

Namespaces and permissions

Workflow jobs run on execution environments.

Sometimes you want to limit some execution environments to some workflows. It can be that you have a preprod environment and a noprod environment, and they should not be mixed, or that you want to share an orchestrator instance with multiple departments within your organization.

namespaces are used to provide this functionality.

Sometimes you want to further control access to resources. You may want to give one of your user read-only access to your running workflows, or you may want to prevent another one from registering new agents.

Access control grants permission to access resources.

In the first example below you will learn how to assign namespaces to trusted authorities, and in the second example you will learn to assign permissions to authentication tokens.

What is a namespace?

A namespace has a name (letters, digits, and hyphens are allowed). It ties resources such as workflows, execution environments, and authentication tokens.

There is no limit on the number of namespaces you can use in an orchestrator instance.

There is a default namespace, default, which is used if you do not specify a specific namespace.

Configuring

The OpenTestFactory orchestrator uses tokens to authenticate users. Each request to the orchestrator needs a bearer token.

Typically, on startup, you provide a public key, or a set of public keys, and you use those public keys to verify incoming bearer tokens.

If the signature is verified, the access request is granted. If not otherwise specified, the access request is granted to the default namespace only.

To enable namespaces on your orchestrator instance, you have to declare which namespace is accessible to which token or set of tokens (tokens whose signatures is matched by a given public key).

Depending on your organization size, you can generate and allocate the tokens, or delegate the creation of those tokens to a trusted authority. You have a finer control on accesses if you generate and allocate the tokens yourself, but this can be time-consuming.

Assigning namespaces to trusted authorities

In this first example you have an administration team and two departments, A and B. Those 3 entities all manage their own tokens.

The administration team members must have full access to the orchestrator. Members of A should have access to the a and a1 namespaces, and members of B should only have access to the b namespace.

You will deploy this configuration using docker-compose.

Lets start by creating a directory in which you will put all the relevant elements:

mkdir example1
cd example1

Trusted keys

In the real world, those teams would provide you with a public key to use to validate their tokens. Here, create three private/public key pairs in a data sub-directory:

mkdir data
cd data
openssl genrsa -out admin.pem 4096
openssl rsa -pubout -in admin.pem -out admin.pub
openssl genrsa -out department_a.pem 4096
openssl rsa -pubout -in department_a.pem -out department_a.pub
openssl genrsa -out department_b.pem 4096
openssl rsa -pubout -in department_b.pem -out department_b.pub
cd ..

Each orchestrator service has a trusted_authorities entry in its configuration file. This entry is typically something like:

# ...
contexts:
- context:
    trusted_authorities:
    - /etc/squashtf/*
    # ...
  name: allinone

You will set up your docker-compose manifest to place the public keys there.

Defining trusted authorities attributes

You then create a mapping file (a ‘trusted authorities attributes file’) with the following content, to match your access requirements:

trustedkeys_auth_file
/etc/squashtf/admin.pub,Administrator,,"*"
/etc/squashtf/department_a.pub,Department A,,"a,a1"
/etc/squashtf/department_b.pub,Department B,,"b"

Trusted authorities are tested in order. If there are other public keys in the /etc/squashtf directory, they will only allow access to the default namespace.

If you to replace "b" in the example above with "b,b1" (be sure to keep the surrounding double quotes), tokens whose signatures are verified by department_b.pub will have access to both b and b1 namespaces.

If you replace "a,a1" in the example above with "*", tokens whose signatures are verified by department_a.pub will have access to all namespaces (including b and b1). In other words, they will have the same accesses as those whose signatures are verified by admin.pub.

This trusted authorities attributes file is specified by setting the OPENTF_TRUSTEDKEYS_AUTH_FILE environment variable in your orchestrator container instance.

Deployment

Your docker-compose.yml is straightforward. You create volumes so that the public keys and configuration file are available to your instance, you expose the standard ports, define the required variable, and that’s it:

docker-compose.yml
version: "3.4"
services:
  orchestrator:
    container_name: orchestrator
    image: opentestfactory/allinone:latest
    restart: always
    environment:
      OPENTF_TRUSTEDKEYS_AUTH_FILE: "/app/trustedkeys_auth_file"
    volumes:
    - type: bind
      source: ./data/admin.pub
      target: /etc/squashtf/admin.pub
    - type: bind
      source: ./data/department_a.pub
      target: /etc/squashtf/department_a.pub
    - type: bind
      source: ./data/department_b.pub
      target: /etc/squashtf/department_b.pub
    - type: bind
      source: ./trustedkeys_auth_file
      target: /app/trustedkeys_auth_file
    ports:
    - "7774:7774"    # receptionist
    - "7775:7775"    # observer
    - "7776:7776"    # killswitch
    - "38368:38368"  # eventbus
    - "24368:24368"  # agent channel
    - "12312:12312"  # quality gate

If you have many public keys to configure, you may want to bind a directory, not each public key, using something like the following. Be sure to move the private keys (*.pem) out of your local data directory, though, as they do not have to be on your orchestrator instance.

    - type: bind
      source: ./data
      target: /etc/squashtf
    # ...

Running docker-compose up -d will start your instance.

For more information, see “Authenticating.”

Observable effects

In this section you will use the opentf-ctl tool and a token generated from each of your above trusted keys. You will tie them to users ‘admin’, ‘alice’, and ‘bob’.

You can use the opentf-ctl tool to generate your tokens from your private keys:

opentf-ctl generate token using data/admin.pem
opentf-ctl generate token using data/department_a.pem
opentf-ctl generate token using data/department_b.pem

The configuration file below, once completed with the tokens you generated above, can be saved to ~/.opentf/config (or %HOME%\.opentf\config if you are using Windows):

~/.opentf/config
apiVersion: opentestfactory.org/v1alpha1
kind: CtlConfig
contexts:
- context:
    orchestrator: default
    user: admin
  name: default
current-context: default
orchestrators:
- name: default
  orchestrator:
    insecure-skip-tls-verify: true
    ports:
      eventbus: 38368
      killswitch: 7776
      observer: 7775
      receptionist: 7774
      qualitygate: 12312
    server: http://127.0.0.1
users:
- name: admin
  user:
    token: ey...
- name: alice
  user:
    token: ey...
- name: bob
  user:
    token: ey...

When Alice attempts to list available execution environments, she will get an empty list, as there are currently no execution environments accessible from the a or a1 namespaces:

opentf-ctl get channels --user alice
NAME,NAMESPACES,TAGS,LAST_REFRESH_TIMESTAMP,STATUS

Performing the same command using admin’s token will provide results, as admin has access to the default namespace:

opentf-ctl get channels --user admin
NAME,NAMESPACES,TAGS,LAST_REFRESH_TIMESTAMP,STATUS
robotframework,default,ssh:linux:robotframework,2022-06-08T10:14:50.39,IDLE
cypress,default,ssh:linux:cypress,2022-06-08T10:14:50.39,IDLE
cucumber,default,ssh:linux:cucumber,2022-06-08T10:14:50.39,IDLE
junit,foo:bar,ssh:linux:junit,2022-06-08T10:14:50.39,IDLE

When Bob tries to run a workflow on the foo namespace, he gets an error:

bad.yaml
metadata:
  name: Oops
  namespace: foo
jobs:
  job_1:
    runs-on: inception
    steps:
      - run: echo 'oh no'
opentf-ctl run workflow bad.yaml --user bob
Error: Token not allowed to run workflows in namespace foo.

Assigning permissions to tokens

You may want finer control on namespaces accesses, or finer token/namespace associations.

To do so, you need to know the tokens you want to grant specific permissions to, and enable the Attributes-based access control (ABAC) mode.

This second example builds on the first one.

Enabling ABAC

This mode is enabled by setting the OPENTF_AUTHORIZATION_MODE environment variable to ABAC,JWT in your orchestrator container instance.

Note

The order is important here. Using JWT,ABAC results in different effects: if JWT is specified first, a token verified by a public key known to the orchestrator would match, and the ABAC policies would be skipped: the token would have no specific permissions.

Conversely, using ABAC,JWT implies that a token known to the ABAC module does not inherit the permissions granted by a verifying public key.

Token association

You then have to define the mapping you want between the tokens and their permissions.

Here, you will reuse the tokens you generated in the previous example. Alice would normally only have access to namespaces a and a1, but you will grant her more privileges, and Bob has left the company, so you will remove his privileges.

For each token you want to grant specific permissions to, give it a name and a unique ID:

token_auth_file
ey...,Alice Doe,alice
ey...,Bob Doe,bob

You only add entries in this file for tokens you want to refine. Other tokens, as long as they are verified by one of your trusted authorities, will have access to the resources granted by that trusted authority.

Make this file available to your orchestrator container instance, and set the OPENTF_TOKEN_AUTH_FILE environment variable to this file’s path.

For more information, see “Static Token File.”

Policies

Finally, you have to define the policies that apply to those unique IDs.

For example, you want to grant Alice read-only access on all namespaces’ workflows, and full access to the a, b, and c namespaces. And Bob should have his privileges removed.

policy.jsonl
{"apiVersion": "abac.opentestfactory.org/v1alpha1", "kind": "Policy", "spec": {"user": "alice", "namespace": "*", "resource": "workflows", "apiGroup": "*", "readonly": true}}
{"apiVersion": "abac.opentestfactory.org/v1alpha1", "kind": "Policy", "spec": {"user": "alice", "namespace": "a", "resource": "*", "apiGroup": "*"}}
{"apiVersion": "abac.opentestfactory.org/v1alpha1", "kind": "Policy", "spec": {"user": "alice", "namespace": "b", "resource": "*", "apiGroup": "*"}}
{"apiVersion": "abac.opentestfactory.org/v1alpha1", "kind": "Policy", "spec": {"user": "alice", "namespace": "c", "resource": "*", "apiGroup": "*"}}

Policies are checked in order. If a policy matches the request, access is granted. If no policy matches the request, access is rejected.

Make this file available to your orchestrator container instance, and set the OPENTF_AUTHORIZATION_POLICY_FILE environment variable to this file’s path.

For more information, see “Policy File Format.”

Deployment

Your docker-compose.yml is straightforward. You create volumes so that the public keys and configuration files are available to your instance, you expose the standard ports, define the required variables, and that’s it:

docker-compose.yml
version: "3.4"
services:
  orchestrator:
    container_name: orchestrator
    image: opentestfactory/allinone:latest
    restart: always
    environment:
      OPENTF_TRUSTEDKEYS_AUTH_FILE: "/app/trustedkeys_auth_file"
      OPENTF_AUTHORIZATION_MODE: "ABAC,JWT"
      OPENTF_TOKEN_AUTH_FILE: "/app/token_auth_file"
      OPENTF_AUTHORIZATION_POLICY_FILE: "/app/policy.jsonl"
      DEBUG_LEVEL: "DEBUG"
    volumes:
    - type: bind
      source: ./data/admin.pub
      target: /etc/squashtf/admin.pub
    - type: bind
      source: ./data/department_a.pub
      target: /etc/squashtf/department_a.pub
    - type: bind
      source: ./data/department_b.pub
      target: /etc/squashtf/department_b.pub
    - type: bind
      source: ./trustedkeys_auth_file
      target: /app/trustedkeys_auth_file
    - type: bind
      source: ./token_auth_file
      target: /app/token_auth_file
    - type: bind
      source: ./policy.jsonl
      target: /app/policy.jsonl
    ports:
    - "7774:7774"    # receptionist
    - "7775:7775"    # observer
    - "7776:7776"    # killswitch
    - "38368:38368"  # eventbus
    - "24368:24368"  # agent channel
    - "12312:12312"  # quality gate

Running docker-compose up -d will start your instance.

Observable effects

Alice no longer has full access to resources on namespace a1. She is listed in the static token file; hence her permissions are granted by the defined policies, overriding those granted by the trusted authority used to sign her token.

She can still view workflows on namespace a1 (as well as on all other namespaces) due to the first policy, though.

Bob is listed in the static token file above but has no policy, so his requests are rejected.

And admin, not being listed in the static token file but having her token validated by the admin.pub trusted authority, will keep her full access on all resources of this orchestrator’s instance.

Next steps

Namespaces are only the beginning of what you can do to control access to your orchestrator resources. Here are some helpful resources for taking your next steps with namespaces and permissions: