Programming with Strongbox
There are a few different ways to consume Strongbox services.
- Implicitly using Strongbox secrets by mounting them as environment variables and/or as files.
- In an application running inside a container started by the Avassa System using Avassa System APIs
- In an application running inside a Kubernetes pod using Avassa System APIs and credentials from the Kubernetes environment.
- 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-id
s 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-id
s
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')