Kubernetes (yaml manifest)
Multiple RSTUF API instances/replicas can be deployed in a distributed environment to support multiple requests.
This example uses Kubernetes
Note
This deployment guide does not show the Kubernetes cluster configuration.
It requires a Kubernetes cluster running with kubectl
configured.
Note
For this deployment Redis is used as the message queue/broker.
Warning
This deployment does not have Authentication/Authorization for the API.
This API is fully accessible for unauthorized users. Consider using an API Authentication/Authorization service.
Note
See the complete Deployment Configuration for in-depth details.
Requirements
Software and tools
Kubernetes Cluster
kubectl
Python >= 3.10
pip
Online Key
This deployment requires the Online Key. See the chapter Signing Keys
Secrets
Caution
Do not use this credential, it is just an example.
onlinekey secret
Generating the secrets for RSTUF Online Key
Get the key content and encode it to base 64
❯ base64 -i tests/files/keys/0d9d3d4bad91c455bc03921daa95774576b86625ac45570d0cac025b08e65043
YWI2ODU0YzE5MjlmNjYxNTc4MTBhNDBjMjQ1ZGU0NGJAQEBAMTAwMDAwQEBAQGUxZTk4MzMyZTZiYmNkM2M2ZjdmMGFiZjczNTdmNDNmZGJhN2QwMTRkYzYwMDk2YzNkODRkZjIyYTc3NDUwYmVAQEBANDgxNWEzNTgxZTB
postgrespassword secret
Generating the secrets for Postgres password
❯ echo -n "PostgreSQLstrongPassword" | base64
UG9zdGdyZVNRTHN0cm9uZ1Bhc3N3b3Jk
Secrets example
k8s/secrets.yml
1##########################################
2# SECRETS
3##########################################
4# RSTUF online key password
5apiVersion: v1
6kind: Secret
7metadata:
8 # the name must to be the keyid
9 name: 0d9d3d4bad91c455bc03921daa95774576b86625ac45570d0cac025b08e65043
10type: Opaque
11data:
12 key: YmFzZTY0fFlXSTJPRFUwWXpFNU1qbG1Oall4TlRjNE1UQmhOREJqTWpRMVpHVTBOR0pBUUVCQU1UQXdNREF3UUVCQVFHVXhaVGs0TXpNeVpUWmlZbU5rTTJNMlpqZG1NR0ZpWmpjek5UZG1ORE5tWkdKaE4yUXdNVFJrWXpZd01EazJZek5rT0RSa1pqSXlZVGMzTkRVd1ltVkFRRUJBTkRneE5XRXpOVGd4WlRCbU5HTTNNRFUyT1RobVpUY3pNbUUyWldFd05EUkFRRUJBTW1KbVlqRXdNbU5qWlRnNE9XRTBaV1JtWkRBMFlqVTBOR1ExTVRFMk0yRmpOR1UwTlRZNE9XVTNObVUzWlRkbFpUTm1NRFEzWm1FME1UUXdNMlppTm1WaVl6STNNRGRqTWpNM056Z3lOakprTlRRd01HVTNZV1psWW1RNE1XUTBObUUyT1dFd1pHVXdPV1ptTmprME4yWTBZemxrWTJRek16a3dPRGxoWVRreU5EQTBOVEkyTTJaak9EZzBZemcxTURGaU4yTmpZalU1TURaalpUTXpZamN5WTJKa01EYzFOV1l3WWpoaE16VmxZemMxTTJFMU1EQXlNbU5rTTJSa1pEVTVNamN4TUdReU5qbGhZamswWmpNek9UQmhZalZtT1daak5UUmxPR1V6WkdKbU16QmpNR1V4TmpObVltUmhZV1EzTnpWaU1qRTRNemRtTXpjd09EZGtOREF4TjJOaFlqQTJZalUxTmpJMU5tRTVPRFF3TmpCbVltRXdNV1E1WkRRME1qZGlOelExT1RaaU9EUTVOMlJtWWpNM1pXUmlNVFV3Wm1FMk9UaGtNbU5pTnpWbVlXVmtZamhpTlRGaVptUmpaalV3WkROaE0ySmxORE0zTVRoa00yRmtNMkUxWVRVNU1UTmxZMll5TlRBNE56VXpZVEF4T0dWbE0yWTVOVEkwTW1SaE5qaGpNbU15WmpZeE5EbG1aR0prTW1VNE9HUTFPVEJtWW1ZME4yWXpZVEUwTVdKaE1EVmtNalJoWVRBMU1EZzFNamczTkRNd1pEZzBNbVF3Wm1JM05tRTBZekJqTmpneE4yWmtObVZqWWpNMU5HTTFZbU5tWldVeVlUazROVFJoTlRjNE5UTXdZVEk0WkRreE1ERTRNell4TW1aaFl6QmlNamt3TW1NMVpHTm1PREkzWlRBMFlUa3dPREl3WW1SaE5HWXlOV0prTWpZek1XUTJPRGxtWVdZd01XTTRNVE16TldJMk9HTXpZamt3TWpFellqbGhNRFJqTldGalpHTTVPVGRtTlRBMVltSmtOREE1TnpaaU5UVTJZMk5oTm1FellqVTBaakkzTUdFM1pXSTJZakJrT1dJMlpqRTVZbVV5T1dRMllXSTRaRFJoTXpGaFpXUm1ZbUkzTW1FMFl6aGpaRGsyWTJFeFkySm1abVU1TURFeE9HSmgsc3Ryb25nUGFzc3dvcmQ=
13---
14# POSTGRES password
15apiVersion: v1
16kind: Secret
17metadata:
18 name: postgrespassword
19data:
20 password: UG9zdGdyZVNRTHN0cm9uZ1Bhc3N3b3Jk
Applying secrets
❯ kubectl apply -f secrets.yml
secret/onlinekey created
secret/postgrespassword created
❯ kubectl get secrets
NAME TYPE DATA AGE
default-token-dqt9k kubernetes.io/service-account-token 3 22d
onlinekey Opaque 1 2s
postgrespassword Opaque 1 2s
Volumes
The following volumes will be defined:
rstuf-storage persistent volume
RSTUF Workers will use the Storage Backend Service
type as LocalStorage
which requires a
LocalStorage [Optional]
RSTUF Workers will use the
RSTUF_STORAGE_BACKEND_PATH
as this volumeWebServer will use the same volume the HTTP root document (htdocs) to expose the TUF Metadata at
http://webserver/
redis-data persistent volume
Redis persistent data volume
postgres-data persistent volume
Postgres persistent data volume
Volumes example
1##########################################
2# VOLUMES
3##########################################
4# RSTUF Storage (backend storage)
5apiVersion: v1
6kind: PersistentVolumeClaim
7metadata:
8 labels:
9 io.kompose.service: rstuf-storage
10 name: rstuf-storage
11spec:
12 accessModes:
13 - ReadWriteOnce
14 resources:
15 requests:
16 storage: 100Mi
17status: {}
18---
19# Redis persistent data volume
20apiVersion: v1
21kind: PersistentVolumeClaim
22metadata:
23 labels:
24 io.kompose.service: redis-data
25 name: redis-data
26spec:
27 accessModes:
28 - ReadWriteOnce
29 resources:
30 requests:
31 storage: 100Mi
32status: {}
33---
34# Postgres persistent data volume
35apiVersion: v1
36kind: PersistentVolumeClaim
37metadata:
38 labels:
39 io.kompose.service: postgres-data
40 name: postgres-data
41spec:
42 accessModes:
43 - ReadWriteOnce
44 resources:
45 requests:
46 storage: 250Mi
47status: {}
Applying Volumes
❯ kubectl apply -f volumes.yml
persistentvolumeclaim/rstuf-storage created
persistentvolumeclaim/redis-data created
persistentvolumeclaim/postgres-data created
❯ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM REASON AGE
pvc-5184d98009604a73 10Gi RWO Retain Bound default/redis-data 7s
pvc-79aff76456bd433f 10Gi RWO Retain Bound default/rstuf-storage 9s
pvc-c3ee2645d4114816 10Gi RWO Retain Bound default/postgres-data 8s
Services
redis service
Exposes the port 6379 in the deployment network
postgres service
Exposes the port 5432 in the deployment network
web-metadata
Exposes the port 80 externally, using a load balancer
rstuf-api
Exposes the port 80 externally, using a load balancer
Services example
1##########################################
2# SERVICES
3##########################################
4
5# Redis service
6apiVersion: v1
7kind: Service
8metadata:
9 labels:
10 io.kompose.service: redis
11 name: redis
12spec:
13 ports:
14 - name: "6379"
15 port: 6379
16 targetPort: 6379
17 selector:
18 io.kompose.service: redis
19status:
20 loadBalancer: {}
21---
22# Postgres service
23apiVersion: v1
24kind: Service
25metadata:
26 labels:
27 io.kompose.service: postgres
28 name: postgres
29spec:
30 ports:
31 - name: "5432"
32 port: 5432
33 targetPort: 5432
34 selector:
35 io.kompose.service: postgres
36status:
37 loadBalancer: {}
38---
39# http service (exposing TUF metadata plublic)
40apiVersion: v1
41kind: Service
42metadata:
43 labels:
44 io.kompose.service: web-metadata
45 name: web-metadata
46spec:
47 type: LoadBalancer
48 ports:
49 - name: "http"
50 port: 80
51 targetPort: 80
52 selector:
53 io.kompose.service: web-metadata
54---
55# RSTUF API service
56apiVersion: v1
57kind: Service
58metadata:
59 labels:
60 io.kompose.service: rstuf-api
61 name: rstuf-api
62spec:
63 type: LoadBalancer
64 ports:
65 - name: "http"
66 port: 80
67 targetPort: 80
68 selector:
69 io.kompose.service: rstuf-api
70# RSTUF Worker is not a service, only a consumer backend
Applying services
❯ kubectl apply -f services.yml
service/redis created
service/postgres created
service/web-metadata created
service/rstuf-api created
❯ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.128.0.1 <none> 443/TCP 32d
postgres ClusterIP 10.128.54.79 <none> 5432/TCP 30s
redis ClusterIP 10.128.202.49 <none> 6379/TCP 30s
rstuf-api LoadBalancer 10.128.44.53 <PUBLIC_IP> 80:30158/TCP 30s
web-metadata LoadBalancer 10.128.135.249 <PUBLIC_IP> 80:32744/TCP 30s
Deployment
The following deployment will be applied:
redis deployment
Redis will use the redis-data persistent volume mounted on
/data
Redis container will use port as 6379 (default)
postgres deployment
Postgres will use the postgres-data persistent volume mounted on
/var/lib/postgresql/data/rstuf
Postgres will use environment variable
POSTGRES_PASSWORD
set as the secretspostgrespassword
web-metadata deployment
web-metadata is the Web Server is Apache which exposes the TUF Metadata
Web Server will use rstuf-storage persistent volume mounted on
/usr/local/apache2/htdocs
Web Server container will use port 80
rstuf-api deployment
RSTUF API will use environment variables
RSTUF_BROKER_SERVER
andRSTUF_REDIS_SERVER
as redis deployment address (redis://redis
).RSTUF API container will use port 80 to serve the API (internally)
rstuf-worker deployment
RSTUF Worker will use:
rstuf-storage persistent volume mounted on
/var/opt/repository-service-tuf/storage
environment variables
RSTUF_BROKER_SERVER
andRSTUF_REDIS_SERVER
as redis deployment (redis://redis
).environment variables
RSTUF_SQL_SERVER
as the postgres deployment (postgres:5432
),RSTUF_SQL_USER
aspostgres
, andRSTUF_SQL_PASSWORD
as the postgrespassword secret.environment variable
RSTUF_ONLINE_KEY_DIR
as onlinekey secretenvironment variable
RSTUF_STORAGE_BACKEND
asLocalStorage
environment variable
RSTUF_LOCAL_STORAGE_BACKEND_PATH
as/var/opt/repository-service-tuf/storage
Deployment example
1##########################################
2# DEPLOYMENT
3##########################################
4
5# Redis deployment
6apiVersion: apps/v1
7kind: Deployment
8metadata:
9 labels:
10 io.kompose.service: redis
11 name: redis
12spec:
13 replicas: 1
14 selector:
15 matchLabels:
16 io.kompose.service: redis
17 strategy:
18 type: Recreate
19 template:
20 metadata:
21 labels:
22 io.kompose.service: redis
23 spec:
24 containers:
25 - image: redis:4.0
26 livenessProbe:
27 exec:
28 command:
29 - redis-cli
30 - ping
31 name: redis
32 ports:
33 - containerPort: 6379
34 resources: {}
35 tty: true
36 volumeMounts:
37 - mountPath: /data
38 name: redis-data
39 restartPolicy: Always
40 volumes:
41 - name: redis-data
42 persistentVolumeClaim:
43 claimName: redis-data
44status: {}
45---
46# Postgres Deployment
47apiVersion: apps/v1
48kind: Deployment
49metadata:
50 labels:
51 io.kompose.service: postgres
52 name: postgres
53spec:
54 replicas: 1
55 selector:
56 matchLabels:
57 io.kompose.service: postgres
58 strategy:
59 type: Recreate
60 template:
61 metadata:
62 labels:
63 io.kompose.service: postgres
64 spec:
65 containers:
66 - env:
67 - name: POSTGRES_PASSWORD
68 valueFrom:
69 secretKeyRef:
70 name: postgrespassword
71 key: password
72 - name: PGDATA
73 value: /var/lib/postgresql/data/rstuf
74 image: postgres:15.1
75 livenessProbe:
76 exec:
77 command:
78 - pg_isready
79 - -U
80 - postgres
81 - -d
82 - postgres
83 name: postgres
84 resources: {}
85 tty: true
86 volumeMounts:
87 - mountPath: /var/lib/postgresql/data
88 name: postgres-data
89 restartPolicy: Always
90 volumes:
91 - name: postgres-data
92 persistentVolumeClaim:
93 claimName: postgres-data
94status: {}
95---
96# http deployment (Web Server to expose metadata public)
97apiVersion: apps/v1
98kind: Deployment
99metadata:
100 name: web-metadata
101 labels:
102 io.kompose.service: web-metadata
103spec:
104 replicas: 1
105 selector:
106 matchLabels:
107 io.kompose.service: web-metadata
108 template:
109 metadata:
110 labels:
111 io.kompose.service: web-metadata
112 spec:
113 containers:
114 - name: web-metadata
115 image: httpd
116 ports:
117 - containerPort: 80
118 resources: {}
119 volumeMounts:
120 - mountPath: /usr/local/apache2/htdocs
121 name: rstuf-storage
122 restartPolicy: Always
123 volumes:
124 - name: rstuf-storage
125 persistentVolumeClaim:
126 claimName: rstuf-storage
127---
128# RSTUF Worker deployment
129apiVersion: apps/v1
130kind: Deployment
131metadata:
132 labels:
133 io.kompose.service: rstuf-worker
134 name: rstuf-worker
135spec:
136 replicas: 5
137 selector:
138 matchLabels:
139 io.kompose.service: rstuf-worker
140 strategy:
141 type: Recreate
142 template:
143 metadata:
144 labels:
145 io.kompose.service: rstuf-worker
146 spec:
147 containers:
148 - env:
149 - name: RSTUF_BROKER_SERVER
150 value: redis://redis
151 - name: RSTUF_REDIS_SERVER
152 value: redis://redis
153 - name: RSTUF_SQL_SERVER
154 value: postgres:5433
155 - name: RSTUF_SQL_USER
156 value: postgres
157 - name: RSTUF_SQL_PASSWORD
158 valueFrom:
159 secretKeyRef:
160 name: postgrespassword
161 key: password
162 - name: RSTUF_ONLINE_KEY_DIR
163 value: /run/secrets
164 - name: RSTUF_STORAGE_BACKEND
165 value: LocalStorage
166 - name: RSTUF_LOCAL_STORAGE_BACKEND_PATH
167 value: /var/opt/repository-service-tuf/storage
168 image: ghcr.io/repository-service-tuf/repository-service-tuf-worker:latest
169 name: rstuf-worker
170 resources: {}
171 tty: true
172 volumeMounts:
173 - name: 0d9d3d4bad91c455bc03921daa95774576b86625ac45570d0cac025b08e65043
174 mountPath: "/run/secrets/0d9d3d4bad91c455bc03921daa95774576b86625ac45570d0cac025b08e65043"
175 readOnly: true
176 - name: postgrespassword
177 mountPath: "/run/secrets/postgrespassword"
178 readOnly: true
179 - mountPath: /var/opt/repository-service-tuf/storage
180 name: rstuf-storage
181 - mountPath: /var/opt/repository-service-tuf/keystorage
182 name: rstuf-keystorage
183 restartPolicy: Always
184 volumes:
185 - name: rstuf-storage
186 persistentVolumeClaim:
187 claimName: rstuf-storage
188 - name: rstuf-keystorage
189 persistentVolumeClaim:
190 claimName: rstuf-keystorage
191 - name: onlinekey
192 secret:
193 secretName: onlinekey
194 - name: postgrespassword
195 secret:
196 secretName: postgrespassword
197status: {}
198---
199# RSTUF API deployment
200apiVersion: apps/v1
201kind: Deployment
202metadata:
203 labels:
204 io.kompose.service: rstuf-api
205 name: rstuf-api
206spec:
207 replicas: 1
208 selector:
209 matchLabels:
210 io.kompose.service: rstuf-api
211 strategy:
212 type: Recreate
213 template:
214 metadata:
215 labels:
216 io.kompose.service: rstuf-api
217 spec:
218 containers:
219 - env:
220 - name: RSTUF_BROKER_SERVER
221 value: redis://redis
222 - name: RSTUF_REDIS_SERVER
223 value: redis://redis
224 image: ghcr.io/repository-service-tuf/repository-service-tuf-api:latest
225 name: rstuf-api
226 ports:
227 - containerPort: 80
228 resources: {}
229 volumes:
230 restartPolicy: Always
231status: {}
Applying deployment
❯ kubectl apply -f deployment.yml
deployment.apps/redis created
deployment.apps/postgres created
deployment.apps/web-metadata created
deployment.apps/rstuf-worker created
deployment.apps/rstuf-api created
❯ kubectl get pods
NAME READY STATUS RESTARTS AGE
postgres-64866cf7f9-p4clt 1/1 Running 0 4m12s
redis-6f6fbbd9ff-ckrrt 1/1 Running 0 4m12s
rstuf-api-7cd78b65f7-sn56n 1/1 Running 0 4m12s
rstuf-worker-5b89554c95-xrlgb 1/1 Running 0 4m12s
web-metadata-75f59886f-h274h 1/1 Running 0 4m12s
RSTUF Ceremony and Bootstrap
Repository Service for TUF (RSTUF) has two specific processes as part of the initial setup: Ceremony and Bootstrap.
Note
The setup and configuration requirements:
Set of root key(s) and online key for signing
RSTUF service deployed
Note
It is a one-time process to setup the RSTUF service. If this process is completed during the deployment do not run it again.
RSTUF Command Line Interface provides a guided process for the Ceremony and Bootstrap.
To make this process easier, the Repository Service for TUF CLI provides an interactive guided process to perform the Ceremony.
Note
Required RSTUF CLI installed (See Installation)
❯ rstuf admin ceremony -hThe Ceremony defines the RSTUF settings/configuration and generates the initial signed TUF root metadata.
It does not activate the RSTUF service. It generates the required JSON payload to bootstrap the RSTUF service.
The Ceremony can be done Connected as a specific step or Disconnected, combined with Bootstrap.
This process generates the initial metadata and defines some settings of the TUF service.
See settings details
Timestamp, Snapshot, and Targets metadata expiration policy
Defines how many days this metadata is valid. The metadata is invalid when it expires.
Delegations
Bins (online key only)
The target metadata file might contain a large number of artifacts. The target role delegates trust to the hash bin roles to reduce the metadata overhead for clients.
This metadata is signed using the online key.
Custom Delegations (online/offline keys)
Allows the RSTUF admin to create custom delegation that can use the online key or offline key(s) to sign the metadata. The custom delegation can be used to define the roles and paths for the target metadata.
Root metadata expiration policy
Defines how long this metadata is valid, for example, 365 days (year). This metadata is invalid when it expires.
Root threshold
It defines the number of keys required to sign the Root metadata before it’s considered trusted and will be published.
That’s the minimum number of keys required to update and sign the TUF Root metadata. It’s required to be at least 2.
Note
Updating the Root metadata with new expiration, changing/updating keys or the number of keys, threshold, or rotating a new online key and sign requires following the Metadata Update process.
Note
RSTUF requires at least a threshold number of Root key(s) defined to finish the ceremony. The same applies when performing Metadata Update.
Signing
This process will also require the Online Key and Root Key(s) (offline) for signing the initial root TUF metadata.
The settings are guided during Ceremony.
The disconnected Ceremony will only generate the required JSON payload (
ceremony-payload.json
) file. The Bootstrap requires the payload.Note
The payload (
ceremony-payload.json
) contains only public data, it does not contain any private keys.This process is appropriate when performing the Ceremony on a disconnected computer to RSTUF API to perform the Bootstrap later as a separate step.
❯ rstuf admin ceremony --out Saved result to 'ceremony-payload.json'If the Ceremony is done disconnected, the next step is to perform the bootstrap.
The connected Ceremony generates the JSON payload file and run the Bootstrap request to RSTUF API.
This process is appropriate when performing the Ceremony on a computer connected to RSTUF API. It does not require a Bootstrap step.
❯ rstuf admin --api-server https://rstuf-api-url ceremonyIf a Ceremony Connected is complete, skip this, as the RSTUF service is ready.
To perform the boostrap you require the payload generated during the Bootstrap.
You can do it using the rstuf admin-legacy
❯ rstuf admin --api-server http://rstuf-api-url send bootstrap ceremony-payload.json Starting online bootstrap Bootstrap status: ACCEPTED (c1d2356d25784ecf90ce373dc65b05c7) Bootstrap status: STARTED Bootstrap status: SUCCESS Bootstrap completed using `ceremony-payload.json`. 🔐 🎉