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

  1. 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 volume

  • WebServer 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

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 secrets postgrespassword

web-metadata deployment

web-metadata is the Web Server is Apache which exposes the TUF Metadata

rstuf-api deployment

  • RSTUF API will use environment variables RSTUF_BROKER_SERVER and RSTUF_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 and RSTUF_REDIS_SERVER as redis deployment (redis://redis).

    • environment variables RSTUF_SQL_SERVER as the postgres deployment (postgres:5432), RSTUF_SQL_USER as postgres, and RSTUF_SQL_PASSWORD as the postgrespassword secret.

    • environment variable RSTUF_ONLINE_KEY_DIR as onlinekey secret

    • environment variable RSTUF_STORAGE_BACKEND as LocalStorage

    • 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 -h

The 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 ceremony

If 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`. 🔐 🎉