From 0ab645efdc0368052f40c77ee3d40c92b3d8ec25 Mon Sep 17 00:00:00 2001 From: "Jose I. Paris" Date: Fri, 21 Feb 2025 11:08:25 +0100 Subject: [PATCH] feat(tsa): support timestamp authorities in chart (#1846) Signed-off-by: Jose I. Paris --- deployment/chainloop/README.md | 180 ++++++++++-------- .../templates/controlplane/configmap.yaml | 9 + .../templates/controlplane/deployment.yaml | 9 + .../templates/controlplane/tsa-configmap.yaml | 21 ++ deployment/chainloop/values.yaml | 25 +++ docs/docs/reference/signing.md | 3 + 6 files changed, 171 insertions(+), 76 deletions(-) create mode 100644 deployment/chainloop/templates/controlplane/tsa-configmap.yaml diff --git a/deployment/chainloop/README.md b/deployment/chainloop/README.md index a718c71bf..a0ff49127 100644 --- a/deployment/chainloop/README.md +++ b/deployment/chainloop/README.md @@ -390,8 +390,6 @@ secretsBackend: ### Deploy in keyless mode with file-based CA -*This feature is experimental, as it doesn't yet support verification.* - You can enable keyless signing mode by providing a custom Certificate Authority. For example, these commands generate a self-signed certificate with an RSA private key of length 4096 and AES256 encryption with a validity of 365 days: @@ -408,18 +406,35 @@ Then you can configure your deployment values with: controlplane: keylessSigning: enabled: true - backend: fileCA - fileCA: - cert: | - -----BEGIN CERTIFICATE----- - ... - -----END CERTIFICATE----- - key: | - -----BEGIN ENCRYPTED PRIVATE KEY----- - ... - -----END ENCRYPTED PRIVATE KEY----- - keyPass: "REDACTED" + backends: + - fileCA: + cert: | + -----BEGIN CERTIFICATE----- + ... + -----END CERTIFICATE----- + key: | + -----BEGIN ENCRYPTED PRIVATE KEY----- + ... + -----END ENCRYPTED PRIVATE KEY----- + keyPass: "REDACTED" +``` + +### Configure a Timestamp Authority for attestation timestamping + +If configured, the TSA will be used in all attestations to generate a timestamped signature. +```yaml +controlplane: + timestampAuthorities: + - issuer: true + url: https://freetsa.org/tsr + certChain: | + -----BEGIN CERTIFICATE----- + -----END CERTIFICATE----- + + -----BEGIN CERTIFICATE----- + -----END CERTIFICATE----- ``` +Timestamped attestations will be verified against the root material of the TSA, ensuring that any attempt to tamper with it will be detected. ### Insert custom Certificate Authorities (CAs) @@ -662,69 +677,82 @@ chainloop config save \ ### Keyless signing configuration -| Name | Description | Value | -| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------- | -| `controlplane.keylessSigning.enabled` | Activates or deactivates the feature | `false` | -| `controlplane.keylessSigning.backends[0].issuer` | Whether this backend should be used to issue new certificates. Only one can be set at a time. | `true` | -| `controlplane.keylessSigning.backends[0].type` | backend type. Only "fileCA" and "ejbcaCA" are supported | `fileCA` | -| `controlplane.keylessSigning.backends[0].fileCA.cert` | The PEM-encoded certificate of the file based CA | `""` | -| `controlplane.keylessSigning.backends[0].fileCA.key` | The PEM-encoded private key of the file based CA | `""` | -| `controlplane.keylessSigning.backends[0].fileCA.keyPass` | The secret key pass | `foo` | -| `controlplane.keylessSigning.backends[1].type` | backend type. Only "fileCA" and "ejbcaCA" are supported | `ejbcaCA` | -| `controlplane.keylessSigning.backends[1].ejbcaCA.serverURL` | The url of the EJBCA service ("https://host/ejbca") | `""` | -| `controlplane.keylessSigning.backends[1].ejbcaCA.clientKey` | PEM-encoded the private key for EJBCA cert authentication | `""` | -| `controlplane.keylessSigning.backends[1].ejbcaCA.clientCert` | PEM-encoded certificate for EJBCA cert authentication | `""` | -| `controlplane.keylessSigning.backends[1].ejbcaCA.caCert` | PEM-encoded certificate of the root CA | `""` | -| `controlplane.keylessSigning.backends[1].ejbcaCA.certProfileName` | Name of the certificate profile to use in EJBCA | `""` | -| `controlplane.keylessSigning.backends[1].ejbcaCA.endEntityProfileName` | Name of the Entity Profile to use in EJBCA | `""` | -| `controlplane.keylessSigning.backends[1].ejbcaCA.caName` | Name of the CA issuer to use in EJBCA | `""` | -| `controlplane.customCAs` | List of custom CA certificates content | `[]` | -| `controlplane.automountServiceAccountToken` | Mount Service Account token in controlplane pods | `false` | -| `controlplane.hostAliases` | controlplane pods host aliases | `[]` | -| `controlplane.deploymentAnnotations` | Annotations for controlplane deployment | `{}` | -| `controlplane.podLabels` | Extra labels for controlplane pods | `{}` | -| `controlplane.podAffinityPreset` | Pod affinity preset. Ignored if `controlplane.affinity` is set. Allowed values: `soft` or `hard` | `""` | -| `controlplane.podAntiAffinityPreset` | Pod anti-affinity preset. Ignored if `controlplane.affinity` is set. Allowed values: `soft` or `hard` | `soft` | -| `controlplane.nodeAffinityPreset.type` | Node affinity preset type. Ignored if `controlplane.affinity` is set. Allowed values: `soft` or `hard` | `""` | -| `controlplane.nodeAffinityPreset.key` | Node label key to match. Ignored if `controlplane.affinity` is set | `""` | -| `controlplane.nodeAffinityPreset.values` | Node label values to match. Ignored if `controlplane.affinity` is set | `[]` | -| `controlplane.affinity` | Affinity for controlplane pods assignment | `{}` | -| `controlplane.nodeSelector` | Node labels for controlplane pods assignment | `{}` | -| `controlplane.tolerations` | Tolerations for controlplane pods assignment | `[]` | -| `controlplane.updateStrategy.type` | controlplane deployment strategy type | `RollingUpdate` | -| `controlplane.priorityClassName` | controlplane pods' priorityClassName | `""` | -| `controlplane.topologySpreadConstraints` | Topology Spread Constraints for controlplane pod assignment spread across your cluster among failure-domains | `[]` | -| `controlplane.schedulerName` | Name of the k8s scheduler (other than default) for controlplane pods | `""` | -| `controlplane.terminationGracePeriodSeconds` | Seconds controlplane pods need to terminate gracefully | `""` | -| `controlplane.lifecycleHooks` | for controlplane containers to automate configuration before or after startup | `{}` | -| `controlplane.extraEnvVars` | Array with extra environment variables to add to controlplane containers | `[]` | -| `controlplane.extraEnvVarsCM` | Name of existing ConfigMap containing extra env vars for controlplane containers | `""` | -| `controlplane.extraEnvVarsSecret` | Name of existing Secret containing extra env vars for controlplane containers | `""` | -| `controlplane.extraVolumes` | Optionally specify extra list of additional volumes for the controlplane pods | `[]` | -| `controlplane.extraVolumeMounts` | Optionally specify extra list of additional volumeMounts for the controlplane containers | `[]` | -| `controlplane.sidecars` | Add additional sidecar containers to the controlplane pods | `[]` | -| `controlplane.initContainers` | Add additional init containers to the controlplane pods | `[]` | -| `controlplane.pdb.create` | Enable/disable a Pod Disruption Budget creation | `true` | -| `controlplane.pdb.minAvailable` | Minimum number/percentage of pods that should remain scheduled | `""` | -| `controlplane.pdb.maxUnavailable` | Maximum number/percentage of pods that may be made unavailable. Defaults to `1` if both `controlplane.pdb.minAvailable` and `controlplane.pdb.maxUnavailable` are empty. | `""` | -| `controlplane.autoscaling.vpa.enabled` | Enable VPA for controlplane pods | `false` | -| `controlplane.autoscaling.vpa.annotations` | Annotations for VPA resource | `{}` | -| `controlplane.autoscaling.vpa.controlledResources` | VPA List of resources that the vertical pod autoscaler can control. Defaults to cpu and memory | `[]` | -| `controlplane.autoscaling.vpa.maxAllowed` | VPA Max allowed resources for the pod | `{}` | -| `controlplane.autoscaling.vpa.minAllowed` | VPA Min allowed resources for the pod | `{}` | -| `controlplane.autoscaling.vpa.updatePolicy.updateMode` | Autoscaling update policy | `Auto` | -| `controlplane.autoscaling.hpa.enabled` | Enable HPA for controlplane pods | `false` | -| `controlplane.autoscaling.hpa.minReplicas` | Minimum number of replicas | `""` | -| `controlplane.autoscaling.hpa.maxReplicas` | Maximum number of replicas | `""` | -| `controlplane.autoscaling.hpa.targetCPU` | Target CPU utilization percentage | `""` | -| `controlplane.autoscaling.hpa.targetMemory` | Target Memory utilization percentage | `""` | -| `controlplane.networkPolicy.enabled` | Specifies whether a NetworkPolicy should be created | `true` | -| `controlplane.networkPolicy.allowExternal` | Don't require client label for connections | `true` | -| `controlplane.networkPolicy.allowExternalEgress` | Allow the pod to access any range of port and all destinations. | `true` | -| `controlplane.networkPolicy.extraIngress` | Add extra ingress rules to the NetworkPolicy | `[]` | -| `controlplane.networkPolicy.extraEgress` | Add extra ingress rules to the NetworkPolicy | `[]` | -| `controlplane.networkPolicy.ingressNSMatchLabels` | Labels to match to allow traffic from other namespaces | `{}` | -| `controlplane.networkPolicy.ingressNSPodMatchLabels` | Pod labels to match to allow traffic from other namespaces | `{}` | +| Name | Description | Value | +| ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------- | +| `controlplane.keylessSigning.enabled` | Activates or deactivates the feature | `false` | +| `controlplane.keylessSigning.backends[0].issuer` | Whether this backend should be used to issue new certificates. Only one can be set at a time. | | +| `controlplane.keylessSigning.backends[0].type` | backend type. Only "fileCA" and "ejbcaCA" are supported | | +| `controlplane.keylessSigning.backends[0].fileCA.cert` | The PEM-encoded certificate of the file based CA | | +| `controlplane.keylessSigning.backends[0].fileCA.key` | The PEM-encoded private key of the file based CA | | +| `controlplane.keylessSigning.backends[0].fileCA.keyPass` | The secret key pass | | +| `controlplane.keylessSigning.backends[1].type` | backend type. Only "fileCA" and "ejbcaCA" are supported | | +| `controlplane.keylessSigning.backends[1].ejbcaCA.serverURL` | The url of the EJBCA service ("https://host/ejbca") | | +| `controlplane.keylessSigning.backends[1].ejbcaCA.clientKey` | PEM-encoded the private key for EJBCA cert authentication | | +| `controlplane.keylessSigning.backends[1].ejbcaCA.clientCert` | PEM-encoded certificate for EJBCA cert authentication | | +| `controlplane.keylessSigning.backends[1].ejbcaCA.caCert` | PEM-encoded certificate of the root CA | | +| `controlplane.keylessSigning.backends[1].ejbcaCA.certProfileName` | Name of the certificate profile to use in EJBCA | | +| `controlplane.keylessSigning.backends[1].ejbcaCA.endEntityProfileName` | Name of the Entity Profile to use in EJBCA | | +| `controlplane.keylessSigning.backends[1].ejbcaCA.caName` | Name of the CA issuer to use in EJBCA | | + +### Timestamp authorities + +| Name | Description | Value | +| ------------------------------------------------ | ------------------------------------------------------------------ | ----- | +| `controlplane.timestampAuthorities[0].issuer` | whether this TSA should be used for signing (only one at a time) | | +| `controlplane.timestampAuthorities[0].url` | the TSA service URL | | +| `controlplane.timestampAuthorities[0].certChain` | PEM encoded certificate chain (from leaf to root) for verification | | + +### Other settings + +| Name | Description | Value | +| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------- | +| `controlplane.customCAs` | List of custom CA certificates content | `[]` | +| `controlplane.automountServiceAccountToken` | Mount Service Account token in controlplane pods | `false` | +| `controlplane.hostAliases` | controlplane pods host aliases | `[]` | +| `controlplane.deploymentAnnotations` | Annotations for controlplane deployment | `{}` | +| `controlplane.podLabels` | Extra labels for controlplane pods | `{}` | +| `controlplane.podAffinityPreset` | Pod affinity preset. Ignored if `controlplane.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `controlplane.podAntiAffinityPreset` | Pod anti-affinity preset. Ignored if `controlplane.affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `controlplane.nodeAffinityPreset.type` | Node affinity preset type. Ignored if `controlplane.affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `controlplane.nodeAffinityPreset.key` | Node label key to match. Ignored if `controlplane.affinity` is set | `""` | +| `controlplane.nodeAffinityPreset.values` | Node label values to match. Ignored if `controlplane.affinity` is set | `[]` | +| `controlplane.affinity` | Affinity for controlplane pods assignment | `{}` | +| `controlplane.nodeSelector` | Node labels for controlplane pods assignment | `{}` | +| `controlplane.tolerations` | Tolerations for controlplane pods assignment | `[]` | +| `controlplane.updateStrategy.type` | controlplane deployment strategy type | `RollingUpdate` | +| `controlplane.priorityClassName` | controlplane pods' priorityClassName | `""` | +| `controlplane.topologySpreadConstraints` | Topology Spread Constraints for controlplane pod assignment spread across your cluster among failure-domains | `[]` | +| `controlplane.schedulerName` | Name of the k8s scheduler (other than default) for controlplane pods | `""` | +| `controlplane.terminationGracePeriodSeconds` | Seconds controlplane pods need to terminate gracefully | `""` | +| `controlplane.lifecycleHooks` | for controlplane containers to automate configuration before or after startup | `{}` | +| `controlplane.extraEnvVars` | Array with extra environment variables to add to controlplane containers | `[]` | +| `controlplane.extraEnvVarsCM` | Name of existing ConfigMap containing extra env vars for controlplane containers | `""` | +| `controlplane.extraEnvVarsSecret` | Name of existing Secret containing extra env vars for controlplane containers | `""` | +| `controlplane.extraVolumes` | Optionally specify extra list of additional volumes for the controlplane pods | `[]` | +| `controlplane.extraVolumeMounts` | Optionally specify extra list of additional volumeMounts for the controlplane containers | `[]` | +| `controlplane.sidecars` | Add additional sidecar containers to the controlplane pods | `[]` | +| `controlplane.initContainers` | Add additional init containers to the controlplane pods | `[]` | +| `controlplane.pdb.create` | Enable/disable a Pod Disruption Budget creation | `true` | +| `controlplane.pdb.minAvailable` | Minimum number/percentage of pods that should remain scheduled | `""` | +| `controlplane.pdb.maxUnavailable` | Maximum number/percentage of pods that may be made unavailable. Defaults to `1` if both `controlplane.pdb.minAvailable` and `controlplane.pdb.maxUnavailable` are empty. | `""` | +| `controlplane.autoscaling.vpa.enabled` | Enable VPA for controlplane pods | `false` | +| `controlplane.autoscaling.vpa.annotations` | Annotations for VPA resource | `{}` | +| `controlplane.autoscaling.vpa.controlledResources` | VPA List of resources that the vertical pod autoscaler can control. Defaults to cpu and memory | `[]` | +| `controlplane.autoscaling.vpa.maxAllowed` | VPA Max allowed resources for the pod | `{}` | +| `controlplane.autoscaling.vpa.minAllowed` | VPA Min allowed resources for the pod | `{}` | +| `controlplane.autoscaling.vpa.updatePolicy.updateMode` | Autoscaling update policy | `Auto` | +| `controlplane.autoscaling.hpa.enabled` | Enable HPA for controlplane pods | `false` | +| `controlplane.autoscaling.hpa.minReplicas` | Minimum number of replicas | `""` | +| `controlplane.autoscaling.hpa.maxReplicas` | Maximum number of replicas | `""` | +| `controlplane.autoscaling.hpa.targetCPU` | Target CPU utilization percentage | `""` | +| `controlplane.autoscaling.hpa.targetMemory` | Target Memory utilization percentage | `""` | +| `controlplane.networkPolicy.enabled` | Specifies whether a NetworkPolicy should be created | `true` | +| `controlplane.networkPolicy.allowExternal` | Don't require client label for connections | `true` | +| `controlplane.networkPolicy.allowExternalEgress` | Allow the pod to access any range of port and all destinations. | `true` | +| `controlplane.networkPolicy.extraIngress` | Add extra ingress rules to the NetworkPolicy | `[]` | +| `controlplane.networkPolicy.extraEgress` | Add extra ingress rules to the NetworkPolicy | `[]` | +| `controlplane.networkPolicy.ingressNSMatchLabels` | Labels to match to allow traffic from other namespaces | `{}` | +| `controlplane.networkPolicy.ingressNSPodMatchLabels` | Pod labels to match to allow traffic from other namespaces | `{}` | ### Artifact Content Addressable (CAS) API diff --git a/deployment/chainloop/templates/controlplane/configmap.yaml b/deployment/chainloop/templates/controlplane/configmap.yaml index e3d77bde8..8d68fa77f 100644 --- a/deployment/chainloop/templates/controlplane/configmap.yaml +++ b/deployment/chainloop/templates/controlplane/configmap.yaml @@ -63,3 +63,12 @@ data: federated_authentication: {{- toYaml .Values.controlplane.federatedAuthentication | nindent 6 }} {{- end }} + {{- if .Values.controlplane.timestampAuthorities }} + tsa.yaml: | + timestampAuthorities: + {{- range $index, $tsa := .Values.controlplane.timestampAuthorities }} + - issuer: {{$tsa.issuer}} + url: {{$tsa.url}} + cert_chain_path: /tsa_roots/chain-{{$index}}.pem + {{- end }} + {{- end }} diff --git a/deployment/chainloop/templates/controlplane/deployment.yaml b/deployment/chainloop/templates/controlplane/deployment.yaml index ef6d750a8..668b270dd 100644 --- a/deployment/chainloop/templates/controlplane/deployment.yaml +++ b/deployment/chainloop/templates/controlplane/deployment.yaml @@ -179,6 +179,10 @@ spec: mountPath: /etc/pki/tls/certs readOnly: true {{- end }} + {{- if .Values.controlplane.timestampAuthorities }} + - name: tsa-roots + mountPath: /tsa_roots/ + {{- end }} {{- if .Values.controlplane.extraVolumeMounts }} {{- include "common.tplvalues.render" (dict "value" .Values.controlplane.extraVolumeMounts "context" $) | nindent 12 }} {{- end }} @@ -237,4 +241,9 @@ spec: {{- end }} {{- if .Values.controlplane.extraVolumes }} {{- include "common.tplvalues.render" (dict "value" .Values.controlplane.extraVolumes "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.controlplane.timestampAuthorities }} + - name: tsa-roots + configMap: + name: {{ include "chainloop.controlplane.fullname" $ }}-tsa {{- end }} \ No newline at end of file diff --git a/deployment/chainloop/templates/controlplane/tsa-configmap.yaml b/deployment/chainloop/templates/controlplane/tsa-configmap.yaml new file mode 100644 index 000000000..3f59b4406 --- /dev/null +++ b/deployment/chainloop/templates/controlplane/tsa-configmap.yaml @@ -0,0 +1,21 @@ +{{- /* +Copyright Chainloop, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if .Values.controlplane.timestampAuthorities }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "chainloop.controlplane.fullname" . }}-tsa + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "chainloop.controlplane.labels" . | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +data: + {{- range $index, $tsa := .Values.controlplane.timestampAuthorities }} + chain-{{$index}}.pem: | + {{$tsa.certChain | nindent 4 }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/deployment/chainloop/values.yaml b/deployment/chainloop/values.yaml index 8ff6b1b44..9216b26a7 100644 --- a/deployment/chainloop/values.yaml +++ b/deployment/chainloop/values.yaml @@ -703,6 +703,31 @@ controlplane: # endEntityProfileName: "" # caName: "" + ## @section Timestamp authorities + + ## Configuration for RFC3161 timestamp authorities used for signing + ## @extra controlplane.timestampAuthorities[0].issuer whether this TSA should be used for signing (only one at a time) + ## @extra controlplane.timestampAuthorities[0].url the TSA service URL + ## @extra controlplane.timestampAuthorities[0].certChain PEM encoded certificate chain (from leaf to root) for verification + ## -----BEGIN CERTIFICATE----- + ## ... + ## -----END CERTIFICATE----- + ## -----BEGIN CERTIFICATE----- + ## ... + ## -----END CERTIFICATE----- + # timestampAuthorities: + # - issuer: true + # url: https://freetsa.org/tsr + # certChain: | + # -----BEGIN CERTIFICATE----- + # -----END CERTIFICATE----- + # + # -----BEGIN CERTIFICATE----- + # -----END CERTIFICATE----- + + +## @section Other settings + ## Inject custom CA certificates to the controlplane container ## @param controlplane.customCAs List of custom CA certificates content customCAs: [] diff --git a/docs/docs/reference/signing.md b/docs/docs/reference/signing.md index 796befb26..b167496c2 100644 --- a/docs/docs/reference/signing.md +++ b/docs/docs/reference/signing.md @@ -49,6 +49,9 @@ And verify it: ``` Also note that `chainloop wf run describe` already detects a verifiable attestation and tries to perform the verification automatically. In these cases, you'll see "Verified: true" in the command output. +### Timestamp service +Chainloop can be configured to send the attestation signature to a timestamp service (TSA) and include the result as part of the attestation bundle. If available, the TSA signature will be used during the verification process. + ### Not yet supported The following methods are work in progress and **not yet supported**.