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 users 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 and execution environments together.
You grant access permissions to namespaces.
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 JWT tokens to ensure proper authorization. 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 request’s access is granted. If not otherwise specified, the
request’s access 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, ‘Triangle’ and ‘Square’. Those 3 entities manage their tokens.
The administration team members must have full access to the orchestrator. Members of the Triangle
department should have access to the triangle
and triangle1
namespaces, and members of the Square
department should only have access to the square
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 triangle.pem 4096
openssl rsa -pubout -in triangle.pem -out triangle.pub
openssl genrsa -out square.pem 4096
openssl rsa -pubout -in square.pem -out square.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/opentf/*
# ...
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’ file) with the following content, to match your access requirements:
/etc/opentf/admin.pub,Administrator,,"*"
/etc/opentf/triangle.pub,Department Triangle,,"triangle,triangle1"
/etc/opentf/square.pub,Department Square,,"square"
Trusted authorities are tested in order. If there are other public keys in the /etc/opentf
directory, they will only allow access to the default
namespace.
If you replace "square"
in the example above with "square,square1"
(be sure to keep the surrounding
double quotes), tokens whose signatures are verified by square.pub
will have access
to both square
and square1
namespaces.
If you replace "triangle,triangle1"
in the example above with "*"
, tokens whose signatures are
verified by triangle.pub
will have access to all namespaces (including square
and square1
).
In other words, they will have the same access as those whose signatures are verified
by admin.pub
.
This ‘trusted authorities’ 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/opentf/admin.pub
- type: bind
source: ./data/triangle.pub
target: /etc/opentf/triangle.pub
- type: bind
source: ./data/square.pub
target: /etc/opentf/square.pub
- type: bind
source: ./trustedkeys_auth_file
target: /app/trustedkeys_auth_file
ports:
- "7774:7774" # receptionist
- "7775:7775" # observer
- "7776:7776" # killswitch
- "7796:7796" # insightcollector
- "38368:38368" # eventbus
- "34537:34537" # localstore
- "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/opentf
# ...
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 ‘alice’,
‘carol’, and ‘dave’.
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/triangle.pem
opentf-ctl generate token using data/square.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: alice
name: default
current-context: default
orchestrators:
- name: default
orchestrator:
insecure-skip-tls-verify: true
ports:
eventbus: 38368
killswitch: 7776
observer: 7775
receptionist: 7774
insightcollector: 7796
localstore: 34537
qualitygate: 12312
server: http://127.0.0.1
users:
- name: alice
user:
token: ey...
- name: carol
user:
token: ey...
- name: dave
user:
token: ey...
When Carol attempts to list available execution environments, she will get an empty list,
as there are currently no execution environments accessible from the triangle
or
triangle1
namespaces:
opentf-ctl get channels --user carol
NAME NAMESPACES TAGS LAST_REFRESH_TIMESTAMP STATUS
Performing the same command using Alice’s token will provide results, as Alice has access
to the default
namespace:
opentf-ctl get channels --user alice
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 Dave 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 dave
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. Carol would normally only
have access to namespaces triangle
and triangle1
, but you will grant her more privileges and
Dave 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...,Carol Doe,carol
ey...,Dave Doe,dave
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 ‘static tokens’ file available to your orchestrator container instance and set
the OPENTF_TOKEN_AUTH_FILE
environment variable to its location.
For more information, see “Static Tokens File.”
Policies¶
Finally, you have to define the policies that apply to those unique IDs.
For example, you want to grant Carol read-only access to all namespaces’ workflows, and full access
to the triangle
, square
, and circle
namespaces. And Dave should have his privileges removed.
{"apiVersion": "abac.opentestfactory.org/v1alpha1", "kind": "Policy", "spec": {"user": "carol", "namespace": "*", "resource": "workflows", "apiGroup": "*", "readonly": true}}
{"apiVersion": "abac.opentestfactory.org/v1alpha1", "kind": "Policy", "spec": {"user": "carol", "namespace": "triangle", "resource": "*", "apiGroup": "*"}}
{"apiVersion": "abac.opentestfactory.org/v1alpha1", "kind": "Policy", "spec": {"user": "carol", "namespace": "square", "resource": "*", "apiGroup": "*"}}
{"apiVersion": "abac.opentestfactory.org/v1alpha1", "kind": "Policy", "spec": {"user": "carol", "namespace": "circle", "resource": "*", "apiGroup": "*"}}
Policies are checked in order. If a policy matches the request, access is granted. If no policy matches the request, access is denied.
Make this ‘policy’ 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/opentf/admin.pub
- type: bind
source: ./data/triangle.pub
target: /etc/opentf/triangle.pub
- type: bind
source: ./data/square.pub
target: /etc/opentf/square.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
- "7796:7796" # insightcollector
- "38368:38368" # eventbus
- "34537:34537" # localstore
- "24368:24368" # agent channel
- "12312:12312" # quality gate
Running docker-compose up -d
will start your instance.
Observable Effects¶
Carol no longer has full access to resources on namespace triangle1
. 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 triangle1
(as well as on all other namespaces) due to the first policy, though.
Dave is listed in the static token file above but has no policy, so his requests are rejected.
And Alice, 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” for an in-depth view of attribute-based access control
- “Authentication” for an in-depth view of authentication