Skip to main content

Programming with Strongbox

There are a few different ways to consume Strongbox services.

  1. Implicitly using Strongbox secrets by mounting them as environment variables and/or as files.
  2. In an application running inside a container started by the Avassa System using Avassa System APIs
  3. In an application running inside a Kubernetes pod using Avassa System APIs and credentials from the Kubernetes environment.
  4. In an application running outside the Avassa System and Kubernetes using temporary credentials, or manually managed approles.

API

The Avassa System provides a REST API that can be accessed using any REST tools or HTTPS library. The examples in this tutorial uses a python code and the curl command.

API CA Certificate

The API CA Certificate should be used to authenticate the Avassa System API endpoint. The CA certificate can be obtained using the get-api-ca-cert endpoint, or through the SYS_API_CA_CERT variable in the application specification.

Authentication

Almost all REST API endpoints require authentication. Authentication is provided through a token. The token, in its turn, encapsulates information about the session. It stipulates the policies associated with the session and for how long the token is valid.

A token can be obtained through one of several authentication methods.

  • A user logs in through the userpass or oidc facility to obtain a token.
  • An application started by the Avassa system may authenticate through the approle-login endpoint to obtain a token.
  • An application running inside Kubernetes may use its Service Account token to authenticate through the k8s-login endpoint to obtain a token.
  • A token may be explicitly created by an existing session and provided to an application by some other means.

It is important to be careful when it comes to authentication of applications in order to not leak credentials to adversaries.

Approles should be used for applications started by the Avassa System and Service Account tokens for applications started by Kubernetes. The best approach for authenticating applications run outside both the Avassa System and Kubernetes is to use tightly locked down approles where the secrets-ids are created explicitly for each instance of the application. An approle can be limited such that approle-login is only possible from certain subnets, and such that a given secret-id can only be used a limited number of times (for example one).

An secret-id must be created explicitly using the create-secret-id endpoint for each external application instance.

Note that root privileges are required to modify the authentication and policy configuration, and to use most of the endpoints under authentication and policy. This is to prevent a non-privileged user from privilege escalation by modifying the authentication or policy configuration.

Policies

It is important to only allow an application to have access to what it needs access to. We define a policy theater-ops-policy that allows read access to the operations credentials vault secret, and access to the popcorn-data transit key for encryption and decryption of data.

We also define a policy digital-assets-manager-policy that has full access to cluster configuration, and to the vault operations.

supctl create policy policies <<EOF
name: theater-ops-policy
rest-api:
rules:
- path: /v1/*/strongbox/vaults/operations/secrets/credentials
operations:
read: allow
- path: /v1/*/strongbox/transit-keys/popcorn-data/**
operations:
read: allow
execute: allow
EOF

supctl create policy policies <<EOF
name: digital-assets-manager-policy
rest-api:
rules:
- path: /v1/*/cluster/**
operations:
all: allow
- path: /v1/config/strongbox/vaults/operations/**
operations:
all: allow
- path: /v1/state/strongbox/vaults/operations/**
operations:
all: allow
EOF

Note that other operations are denied by default.

Obtaining a Token

A token is obtained by authenticating towards the Avassa System.

Approles

Approles is the recommended way for an application to authenticate towards the Avassa System.

The approle-login combines two secret parts, the role-id and the secret-id. The role-id is stored in the application and is usually compiled into the container as part of the build process. The secret-id is provided by the scheduler when an application instance is created, and is unique for each instance. By default it can only be used once to log on, and for 30 minutes after it has been created.

For externally running applications the secret-id can be explicitly created using the create-secret-id endpoint of an approle

Both the role-id and the secret-id should be considered sensitive information.

An approle needs to be created, for example

supctl create strongbox authentication approles <<EOF
name: digital-assets-manager
token-policies:
- digital-assets-manager-policy
EOF

The role-id for digital-assets-manager can be retrieved using the following command:

supctl show strongbox authentication approles digital-assets-manager | grep role-id
role-id: 324bc0bf-40c3-4aa5-bb58-360b830405ac

Just like secrets, the distribution of approles may be limited to a subset of sites. By default an approle is distributed to all sites, which means an application on any site may refer to it. A stricter security practice would be to configure the distribution policy for the approles to explicitly specify the set of sites or to follow an application deployment:

supctl create strongbox authentication approles <<EOF
name: digital-assets-manager
token-policies:
- digital-assets-manager-policy
distribute:
deployments:
- theater-room-manager-deployment
EOF

In order for an secret-id to be created the application specification needs to specify which approle should be used when starting the application. This is done using the approle parameter, ie

name: theater-room-manager
version: "1.0"
services:
- name: theater-operations
variables:
- name: OPERATIONS_USERNAME
value-from-vault-secret:
vault: operations
secret: credentials
key: username
containers:
- name: projector-operations
image: "registry.gitlab.com/avassa-public/movie-theaters-demo/projector-operations:v1.0"
- name: digital-assets-manager
image: "registry.gitlab.com/avassa-public/movie-theaters-demo/digital-assets-manager:v1.0"
approle: digital-assets-manager
env:
USERNAME: ${OPERATIONS_USERNAME}
APPROLE_SECRET_ID: "${SYS_APPROLE_SECRET_ID}"
API_CA_CERT: "${SYS_API_CA_CERT}"
mounts:
- volume-name: credentials
mount-path: /credentials
- volume-name: api-cert
mount-path: /certs
volumes:
- name: credentials
vault-secret:
vault: operations
secret: credentials
- name: api-cert
config-map:
items:
- name: api-ca-cert.pem
data: |
${SYS_API_CA_CERT}
mode: replicated
replicas: 1
- name: curtain-controller
containers:
- name: curtain-controller
image: "registry.gitlab.com/avassa-public/movie-theaters-demo/curtain-controller:v1.0"
mode: replicated
replicas: 1

In the above appspec the secret-id is communicated to the application through an environment variable APPROLE_SECRET_ID. The API ca-cert is communicated both as an environment variable (API_CA_CERT) for the Python client, and as a file (/certs/api-ca-cert.pem) to be used by curl.

The application authenticates towards the Avassa System using the approle-login endpoint to obtain a token. For example, using curl:

curl --cacert /certs/api-ca-cert.pem \
-X POST https://api.internal:4646/v1/approle-login \
-H "Content-Type: application/json" \
-d @- <<EOF
{ "role-id": "324bc0bf-40c3-4aa5-bb58-360b830405ac",
"secret-id": "$APPROLE_SECRET_ID"
}
EOF

and using Python

import avassa_client
import os

host = "https://api.internal:4646"
role_id = '324bc0bf-40c3-4aa5-bb58-360b830405ac'
secret_id = os.environ['APPROLE_SECRET_ID']
user_agent = 'myapp/0.0.0'
session = avassa_client.approle_login(host=host,
role_id=role_id,
secret_id=secret_id,
user_agent=user_agent)

Note that by default the secret-id can only be used once. The token obtained from approle-login should be stored and maintained by the application. It will survive restart and upgrade of the Avassa System. However, it must be renewed before it expires by the application.

Explicitly created secret-ids

For applications running outside the Avassa System that still needs to access the Avassa APIs it is possible to authenticate using approle-login provided that a secret-id has been explicitly created for the application.

This can be done using the create-secret-id endpoint, eg

supctl do --site gothenburg-bergakungen strongbox authentication \
approles digital-assets-manager create-secret-id \
--meta foo bar --bound-cidrs 192.168.0.0/24 \
--token-bound-cidrs 192.168.0.0/24

Note that the secret-id is only valid on the site is was created on.

Fixed role-id

For contains that are intended to be used on multiple systems, or by multiple tenants, it is problematic that the role-id is system and tenant specific. There are two solutions, one is to use the fixed-role-id to set the role-id to a predefined value. The value can be any string, but it should be considered sensitive.

Another, less secure, alternative is to set the weak-secret-id option. This allows the container to authenticate with the credentials provided by the scheduler when the container is started, and is thus open to spoofing of the container. Ie, if an adversary manages to replace the container with another malicious container it may gain access to the APIs.

Kubernetes authentication

Strongbox can be configured to accept Kubernetes Service Account credentials when logging in as an application.

For this to work both the Kubernetes environment and the Avassa system needs to be set up properly.

Kubernetes needs to be configured to use Service Account tokens.

Configure Kubernetes

First, create a service account for the application by applying the following using kubectl.

apiVersion: v1
kind: ServiceAccount
metadata:
name: theater-operations
namespace: default

Assign the system:auth-delegator role to the service account token to allow the remote Strongbox authenticator to use the applications service account token to invoke the token review endpoint to validate the service account token.

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: role-tokenreview-binding
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: theater-operations
namespace: default

Assign a service account to a pod using the serviceAccountName setting, eg:

apiVersion: v1
kind: Pod
metadata:
name: curtain-control
spec:
serviceAccountName: theater-operations
containers:
- name: alpine
image: alpine
command:
- /bin/sh
- "-c"
- "sleep 60m"

Configure Strongbox for Kubernetes authentication

Strongbox also needs to be configured to accept service account tokens as valid credentials. First extract the API certificate from Kubernetes (used for the api-ca-cert parameter below).

kubectl config view --raw --minify --flatten --output \
'jsonpath={.clusters[].cluster.certificate-authority-data}' | \
base64 --decode
-----BEGIN CERTIFICATE-----
MIIDBjCCAe6gAwIBAgIBATANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p
a3ViZUNBMB4XDTIxMDkyNjA4NTE1NloXDTMxMDkyNTA4NTE1NlowFTETMBEGA1UE
AxMKbWluaWt1YmVDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPj
RABeQOTAY9Vj4IORJ1oeAdHCXRvu6sLWokuX0rZuKj80YIJSiqXOsh2Djp8aD1fG
EU89+fJNVV/3Ba5pe9NODbp597xAs/wpzmlurM/O99czoOxNsfd6pu0KpsRZgzBv
UXzc9offqfr+bbFBrb6VD0fZ8q5ZJ+BupQcdc46/pm31AEG3MfmGv0jVU4d/RZTm
+s4vrjrCEb255XTdWFHMi/bKMRNKRCoTHVAi01FbdHEivzVlFzObgp+UbldZf1v9
GvqXzSmXLX87nn4Fa3JroPhNCajYiJikEBV5hIAe6RpGg42zr8GDrDj0e5CvpTOu
F8leq8hjEvknEk3XQBECAwEAAaNhMF8wDgYDVR0PAQH/BAQDAgKkMB0GA1UdJQQW
MBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
BBTBXYN94F5bU0shR57gmFiNtyRahDANBgkqhkiG9w0BAQsFAAOCAQEAl9mr8t+M
FCAsn2ZIuILwZeiOhNXqyftFR2la47jJmYFtV6FZwMG7zNWOZlXYKrfw8mrRsaAB
a2OvItNipywU54mJaN4OV9PuOadz3KjB3sZ78yXsteXgH3+TxNOwe4LW1mxeyxdL
2JBCPmgrAt0VqekN1q0RRFyI/5JkU6Q+y1rpFV+cLvJGnX47Icgbgf/BGM4Bo30q
1U0iTWrfcVUNQwpYtbHYPnbDD3F8E4FuX+G8J2QNgjHXGKHdCkM/s9IBKr/SLgiM
kv/++NI+D3yABup4kHRnAWPLxAsBVB/v4p8o6yLB3jwIDxHwC3LU4tyQ2n3a/RFR
ceaTK5VvI8G+xg==
-----END CERTIFICATE-----

Then extract a Service Account token that can be used to access the Token Review service in Kubernetes (used in the token-review-jwt parameter below).

kubectl describe secret theater-operations
Name: theater-operations-token-ljckk
Namespace: default
Labels: <none>
Annotations: kubernetes.io/service-account.name: theater-operations
kubernetes.io/service-account.uid: 66b26fef-6e9a-47ba-bea1-8ec1936b9551

Type: kubernetes.io/service-account-token

Data
====
ca.crt: 1111 bytes
namespace: 7 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6ImVGRXpUeGJORzI3Q3A0R1RDT1d1dWhINHU2c09CSDlqdjZINWZ6dXdYejAifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6InRoZWF0ZXItb3BlcmF0aW9ucy10b2tlbi1samNrayIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJ0aGVhdGVyLW9wZXJhdGlvbnMiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI2NmIyNmZlZi02ZTlhLTQ3YmEtYmVhMS04ZWMxOTM2Yjk1NTEiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDp0aGVhdGVyLW9wZXJhdGlvbnMifQ.DKzkbgRseQWIkSv6q7QHOlS3z9f8Y0NFNWFQsfMnyLBIIq2xsvSfRCAqh2Ps4vUr5xzCqlA16p0llFQs-MVwTvN6StXaAw6MfgAXQcqSOWYmPi5nKRTIyXQtsz5rEBZaz6hKVilek6KhIYCNeCAgpLilVcz7i55qtdHsz1JNUgWd7HqUDPwBLfx4BILY3B3L7myX3EE7WgpxLqqAKQ40mL9TVD2CpOa6Zw1X1ovP5ANTgUVjrDAosDmlL-BKNMsJOmb4LRzMr5OlY6oFRbuSOsZZ_NtI7WOE5zA8yoy18jSOqg33FYwMfFixjvjZRm1XQMV5DYD7JZH_GmrAJksUVw

Then configure Strongbox to accept service account tokens as valid credentials.

In Kubernetes default tokens use 'kubernetes/serviceaccount' as issuer, however ephemeral tokens use the api-servers service-account-issuer setting, which is often a URL. Both may have to be configured as valid issuers.

supctl create strongbox authentication kubernetes <<EOF
name: theater-k8s
host: 192.168.0.147
jwks-uri: "https://192.168.0.147/openid/v1/jwks"
server-name-indication: kubernetes
api-ca-cert: |-
-----BEGIN CERTIFICATE-----
MIIDBjCCAe6gAwIBAgIBATANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p
a3ViZUNBMB4XDTIxMDkyNjA4NTE1NloXDTMxMDkyNTA4NTE1NlowFTETMBEGA1UE
AxMKbWluaWt1YmVDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALPj
RABeQOTAY9Vj4IORJ1oeAdHCXRvu6sLWokuX0rZuKj80YIJSiqXOsh2Djp8aD1fG
EU89+fJNVV/3Ba5pe9NODbp597xAs/wpzmlurM/O99czoOxNsfd6pu0KpsRZgzBv
UXzc9offqfr+bbFBrb6VD0fZ8q5ZJ+BupQcdc46/pm31AEG3MfmGv0jVU4d/RZTm
+s4vrjrCEb255XTdWFHMi/bKMRNKRCoTHVAi01FbdHEivzVlFzObgp+UbldZf1v9
GvqXzSmXLX87nn4Fa3JroPhNCajYiJikEBV5hIAe6RpGg42zr8GDrDj0e5CvpTOu
F8leq8hjEvknEk3XQBECAwEAAaNhMF8wDgYDVR0PAQH/BAQDAgKkMB0GA1UdJQQW
MBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
BBTBXYN94F5bU0shR57gmFiNtyRahDANBgkqhkiG9w0BAQsFAAOCAQEAl9mr8t+M
FCAsn2ZIuILwZeiOhNXqyftFR2la47jJmYFtV6FZwMG7zNWOZlXYKrfw8mrRsaAB
a2OvItNipywU54mJaN4OV9PuOadz3KjB3sZ78yXsteXgH3+TxNOwe4LW1mxeyxdL
2JBCPmgrAt0VqekN1q0RRFyI/5JkU6Q+y1rpFV+cLvJGnX47Icgbgf/BGM4Bo30q
1U0iTWrfcVUNQwpYtbHYPnbDD3F8E4FuX+G8J2QNgjHXGKHdCkM/s9IBKr/SLgiM
kv/++NI+D3yABup4kHRnAWPLxAsBVB/v4p8o6yLB3jwIDxHwC3LU4tyQ2n3a/RFR
ceaTK5VvI8G+xg==
-----END CERTIFICATE-----
token-review-jwt: eyJhbGciOiJSUzI1NiIsImtpZCI6ImVGRXpUeGJORzI3Q3A0R1RDT1d1dWhINHU2c09CSDlqdjZINWZ6dXdYejAifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6InRoZWF0ZXItb3BlcmF0aW9ucy10b2tlbi1samNrayIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJ0aGVhdGVyLW9wZXJhdGlvbnMiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI2NmIyNmZlZi02ZTlhLTQ3YmEtYmVhMS04ZWMxOTM2Yjk1NTEiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDp0aGVhdGVyLW9wZXJhdGlvbnMifQ.DKzkbgRseQWIkSv6q7QHOlS3z9f8Y0NFNWFQsfMnyLBIIq2xsvSfRCAqh2Ps4vUr5xzCqlA16p0llFQs-MVwTvN6StXaAw6MfgAXQcqSOWYmPi5nKRTIyXQtsz5rEBZaz6hKVilek6KhIYCNeCAgpLilVcz7i55qtdHsz1JNUgWd7HqUDPwBLfx4BILY3B3L7myX3EE7WgpxLqqAKQ40mL9TVD2CpOa6Zw1X1ovP5ANTgUVjrDAosDmlL-BKNMsJOmb4LRzMr5OlY6oFRbuSOsZZ_NtI7WOE5zA8yoy18jSOqg33FYwMfFixjvjZRm1XQMV5DYD7JZH_GmrAJksUVw
valid-issuers:
- https://kubernetes.default.svc.cluster.local
- kubernetes/serviceaccount
EOF

A kubernetes login role must also be configured, eg

supctl create strongbox authentication kubernetes theater-ops roles <<EOF
name: theater-ops-role
token-policies:
- theater-ops-policy
verbose-logging: true
bound-service-account-names:
- "*"
bound-service-account-namespaces:
- default
EOF

Logging in using kubernetes-login

An application running in a pod in the Kubernetes cluster may then use the kubernetes-login endpoint at the Avassa system.

Using Python and the avassa_client module found in the avassa-client-py repository at gitlab (https://gitlab.com/avassa-public/avassa-client-py.git) as well as on PyPi

import avassa_client
import os

auth_host="https://api.avassa.io:4646"
avassa_tenant = 'theater-operation'
auth_service = 'theater-k8s'
auth_role = 'theader-ops-role'

jwt_file = open("/var/run/secrets/kubernetes.io/serviceaccount/token", "r")
service_account_jwt = jwt_file.read()
jwt_file.close()

session = avassa_client.kubernetes_login(host=auth_host,
tenant=avassa_tenant,
service=auth_service,
role=auth_role,
jwt=service_account_jwt)

Using the curl command

curl --cacert api-ca-cert.pem \
-X POST https://api.avassa.io:4646/v1/kubernetes-login \
-H "Content-Type: application/json" \
-d @- <<EOF
{ "tenant": "theater-operation",
"service": "theater-k8s",
"role": "theater-ops-role",
"jwt": "$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"
}
EOF

Username/password authentication

It is also possible to use username/password authentication when interacting with the Avassa System API from an application running outside both Kubernetes and the Avassa System, but it is not recommended.

A user is created in the Avassa System.

supctl create strongbox authentication userpass <<EOF
name: theater-ops@theater-operation.com
password: verysecret
token-policies:
- theater-ops-policy
token-ttl: 10d
distribute:
to: all
EOF

Then the application can use the login endpoint to authenticate towards the system and obtain a token. For example using curl.

curl --cacert /certs/api-ca-cert.pem \
-X POST https://api.avassa.io:4646/v1/login \
-H "Content-Type: application/json" \
-d @- <<EOF
{ "username": "theater-ops@theater-operation.com",
"password": "verysecret"
}
EOF

and using Python

import avassa_client
import os

host = "https://api.avassa.io:4646"
username = 'theater-ops@theater-operation.com'
password = 'verysecret'
user_agent = 'myapp/0.0.0'
session = avassa_client.login(host=host,
username=username,
password=password,
user_agent=user_agent)

Application token

When accessing the Avassa System API externally it is possible to create an application specific token with limited privileges and a limited lifetime. The token can be created using supctl, eg

supctl do --site stockholm-sergel strongbox token create-token \
--ttl 30d \
--policies theater-ops-policy \
--display-name external-popcorn-app

Note that this token is only valid on the site it is issued on. It is not, for example, possible to use a token created in the Control Tower at any other site.

The token can then be used to access the API through both curl and Python.

The token is used in the authorization header, eg:

curl --cacert /certs/api-ca-cert.pem \
https://api.internal:4646/v1/state/strongbox/vaults/operations/secrets/credentials \
-H 'Authorization: Bearer b78ae5c6-c116-4869-99c0-ca1154039b57' \
-H 'Accept: application/json'

In Python a Session object must be created with the supplied token.

import avassa_client
import os
import ssl

host = "https://api.internal:4646"
user_agent = 'myapp/0.0.0'
kwargs = {}
if 'API_CA_CERT' in os.environ:
kwargs['cadata'] = os.environ['API_CA_CERT']
sslc = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, **kwargs)
session = avassa_client.Session(host=host,
token="${token}",
ssl_context=sslc,
user_agent=user_agent)
EOF

Maintaining a Token

All tokens will eventually expires. Depending on authentication model it might be possible to re-authenticate to obtain a new token, but some authentication models are one time operations, ie, approle-login and explicit application token.

The proper model is to invoke the strongbox/token/refresh endpoint before the token expires. When a token is obtained it is accompanied by a number of other values, eg,

{
"token": "ca9fa123-aea7-476c-b0de-43e4421cdc88",
"expires-in": 900,
"expires": "2022-05-05T08:39:09.863764Z",
"accessor": "c7ea437f-a313-4ce9-8b8b-fbfeea5075a7",
"creation-time": "2022-05-05T08:24:07.639162Z",
"renewal-time": "2022-05-05T08:24:09.863764Z"
}

There are several values that can be used to determine that a token needs to be refreshed, both expires and expires-in are good candidates. A good heuristics is to refresh the token when about 25% of its lifetime remains.

The renewal-time indicates the last time the token was refreshed and can be used as a basis for calculating when the token expires (by adding the expires-in value). This is what the expires value is.

Reading a secret

Reading a vault secret is fairly straightforward once there is an authorized session. Using curl with the token in the authorization header.

curl --cacert /certs/api-ca-cert.pem \
https://api.internal:4646/v1/state/strongbox/vaults/operations/secrets/credentials \
-H 'Authorization: Bearer b78ae5c6-c116-4869-99c0-ca1154039b57' \
-H 'Accept: application/json'

and in Python the avassa_client.get_request() method can be used.

import json
import avassa_client

...

url = "{}/v1/state/strongbox/vaults/operations/secrets/credentials".format(
session.get_base_url().geturl())
(code, msg, headers, body)= avassa_client.get_request(session, url)
if msg != 'OK':
raise GetError('Get secret failed: ' + msg)

creds = json.loads(body)['dict']

It is straightforward to define a function for reading a secret from Strongbox.

import avassa_client
import json
from typing import Dict, Optional
from avassa_client import Session

def get_secret(session: avassa_client.Session,
vault_name: str,
secret_name: str) -> Dict[str, str]:
url = "{}/v1/state/strongbox/vaults/{}/secrets/{}".format(
session.get_base_url().geturl(), vault_name, secret_name)
(code, msg, headers, body)= avassa_client.get_request(session, url)
if msg != 'OK':
raise GetError('Get secret failed: ' + msg)
return json.loads(body)['dict']

Encryping/decrypting data

All the Strongbox APIs are available through different REST endpoints provided there is a valid session that has a policy that allows access to the endpoint. The theater-ops-policy is defined to allow access to the popcorn-data transit key, ie

supctl show policy policies theater-ops-policy
name: theater-ops-policy
rest-api:
rules:
- path: /v1/*/strongbox/vaults/operations/secrets/credentials
operations:
read: allow
- path: /v1/*/strongbox/transit-keys/popcorn-data/**
operations:
read: allow
execute: allow

A transit key is created in Strongbox.

supctl create strongbox transit-keys <<EOF
name: popcorn-data
distribute:
deployments:
- theater-room-manager-deployment
EOF

Encrypt/Decrypt with transit-key

The transit-key encrypt endpoint accepts both base64 encoded data and plain strings. Binary data must be base64 encoded before being sent to the endpoint. The resulting ciphertext is always base64 encoded.

The endpoints can be accessed using curl, eg

encrypt:

curl --cacert /certs/api-ca-cert.pem \
-X POST https://api.internal:4646/v1/state/strongbox/transit-keys/popcorn-data/encrypt \
-H 'Authorization: Bearer b78ae5c6-c116-4869-99c0-ca1154039b57' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d @- <<EOF
{ "plaintext": "the quick brown fox",
"base64-encoded": false
}
EOF

decrypt:

curl --cacert /certs/api-ca-cert.pem \
-X POST https://api.internal:4646/v1/state/strongbox/transit-keys/popcorn-data/decrypt \
-H 'Authorization: Bearer b78ae5c6-c116-4869-99c0-ca1154039b57' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-d @- <<EOF
{ "ciphertext": "sbox:v1:1OFfsHC1VzK2EYVS0QUHl6b4UIFncgTob/cBBuKRwPVVbcgjKvwW1lU87w5Fiwk=",
"base64-encoded": false
}
EOF

Two python methods can be defined to facilitate encryption and decryption using a transit-key like this:

import avassa_client
import os
import ssl
import json
import base64
from typing import Dict, Optional
from avassa_client import Session
from avassa_client import post_request

def encrypt(session: Session, key_name: str, plain_text: str) -> str:
url = "{}/v1/state/strongbox/transit-keys/{}/encrypt".format(
session.get_base_url().geturl(), key_name)
payload = {
"plaintext": str(base64.b64encode(plain_text.encode('utf-8')), 'utf-8')
}
(code, msg, headers, body) = post_request(session, url, payload)
del payload['plaintext']
if msg != "OK":
raise PostError("Encrypt failed: " + msg)
return json.loads(body)["ciphertext"]

def decrypt(session: Session, key_name: str, cipher_text: str) -> str:
url = "{}/v1/state/strongbox/transit-keys/{}/decrypt".format(
session.get_base_url().geturl(), key_name)
payload = {
"ciphertext": cipher_text,
}
(code, msg, headers, body) = post_request(session, url, payload)
if msg != "OK":
raise PostError("Decrypt failed: " + msg)
return str(base64.b64decode(json.loads(body)["plaintext"]), 'utf-8')