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 are matched by a given public key).
Depending on your organization’s size, you can generate and allocate the tokens, or delegate the creation of those tokens to a trusted authority. You have finer control over 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
.
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
subdirectory:
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:
/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 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 access 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:
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):
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:
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 namespace 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:
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 to all namespaces’ workflows, and full access
to the a
, b
, and c
namespaces. And Bob should have his privileges removed.
{"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:
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 to 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:
- “Namespaces” for an in-depth view of namespaces usage
- “Attribute-based access control (ABAC)” for an in-depth view of access control
- “Authentication” for an in-depth view of authentication