diff --git a/openstack/barbican/Chart.yaml b/openstack/barbican/Chart.yaml index b6cb09415fc..7eeee56ccd3 100644 --- a/openstack/barbican/Chart.yaml +++ b/openstack/barbican/Chart.yaml @@ -4,7 +4,7 @@ appVersion: flamingo description: A Helm chart for Openstack Barbican icon: https://www.openstack.org/themes/openstack/images/project-mascots/Barbican/OpenStack_Project_Barbican_vertical.png name: barbican -version: 0.8.6 +version: 0.9.1 dependencies: - condition: mariadb.enabled name: mariadb diff --git a/openstack/barbican/ci/test-values.yaml b/openstack/barbican/ci/test-values.yaml index eedc3354b2e..a25a922110f 100644 --- a/openstack/barbican/ci/test-values.yaml +++ b/openstack/barbican/ci/test-values.yaml @@ -53,6 +53,9 @@ api: resources: enabled: false +tls: + enabled: false + audit: central_service: user: barbican diff --git a/openstack/barbican/templates/api-deployment.yaml b/openstack/barbican/templates/api-deployment.yaml index 75be3d9ea90..2a046d9576c 100644 --- a/openstack/barbican/templates/api-deployment.yaml +++ b/openstack/barbican/templates/api-deployment.yaml @@ -12,7 +12,7 @@ metadata: type: api component: barbican annotations: - secret.reloader.stakater.com/reload: "{{ .Release.Name }}-secrets" + secret.reloader.stakater.com/reload: "{{ .Release.Name }}-secrets{{- if .Values.tls.enabled }},{{ .Release.Name }}-tls{{- end }}" deployment.reloader.stakater.com/pause-period: "60s" spec: replicas: {{ required ".Values.api.replicas is missing" .Values.api.replicas }} @@ -38,11 +38,15 @@ spec: {{- include "utils.topology.pod_label" . | nindent 8 }} annotations: configmap-etc-hash: {{ include (print $.Template.BasePath "/etc-configmap.yaml") . | sha256sum }} + configmap-bin-hash: {{ include (print $.Template.BasePath "/configmap-bin.yaml") . | sha256sum }} {{- if .Values.proxysql.mode }} prometheus.io/scrape: "true" prometheus.io/targets: {{ required ".Values.alerts.prometheus missing" .Values.alerts.prometheus | quote }} {{- end }} {{- include "utils.linkerd.pod_and_service_annotation" . | indent 8 }} + {{- if .Values.tls.enabled }} + config.linkerd.io/skip-inbound-ports: "{{ .Values.tls.httpsPort }}" + {{- end }} spec: {{- if .Values.rbac.enabled }} serviceAccountName: {{ .Release.Name }} @@ -60,11 +64,11 @@ spec: image: {{required ".Values.global.registry is missing" .Values.global.registry }}/loci-barbican:{{required "Values.imageVersionBarbicanApi is missing" .Values.imageVersionBarbicanApi}} imagePullPolicy: IfNotPresent securityContext: - readOnlyRootFilesystem: true + readOnlyRootFilesystem: false allowPrivilegeEscalation: false command: - - dumb-init - - barbican-api + - /scripts/barbican-api.sh + - start env: {{- if .Values.sentry.enabled }} - name: SENTRY_DSN @@ -92,7 +96,10 @@ spec: {{- end }} lifecycle: preStop: - {{- include "utils.snippets.pre_stop_graceful_shutdown" . | indent 14 }} + exec: + command: + - /scripts/barbican-api.sh + - stop livenessProbe: httpGet: path: / @@ -108,6 +115,10 @@ spec: ports: - name: barbican-api containerPort: {{.Values.api_port_internal}} + {{- if .Values.tls.enabled }} + - name: barbican-https + containerPort: {{ .Values.tls.httpsPort }} + {{- end }} volumeMounts: - name: etcbarbican mountPath: /etc/barbican @@ -134,6 +145,23 @@ spec: - mountPath: /etc/barbican/barbican.conf.d name: barbican-etc-confd readOnly: true + - name: barbican-etc + mountPath: /etc/apache2/conf-enabled/wsgi-barbican.conf + subPath: wsgi-barbican.conf + readOnly: true + {{- if .Values.tls.enabled }} + - name: barbican-etc + mountPath: /etc/apache2/conf-enabled/tls-hardening.conf + subPath: tls-hardening.conf + readOnly: true + - name: tls-certs + mountPath: /etc/barbican/tls + readOnly: true + {{- end }} + - name: wsgi-barbican + mountPath: /var/www/cgi-bin/barbican + - name: barbican-bin + mountPath: /scripts {{- if .Values.watcher.enabled }} - name: barbican-etc mountPath: /etc/barbican/watcher.yaml @@ -269,6 +297,18 @@ spec: emptyDir: {} - name: barbican-run emptyDir: {} + - name: wsgi-barbican + emptyDir: {} + - name: barbican-bin + configMap: + name: barbican-bin + defaultMode: 0555 + {{- if .Values.tls.enabled }} + - name: tls-certs + secret: + secretName: {{ .Release.Name }}-tls + defaultMode: 0400 + {{- end }} {{- if .Values.hsm.enabled }} - name: hsm secret: diff --git a/openstack/barbican/templates/bin/_barbican_api.sh.tpl b/openstack/barbican/templates/bin/_barbican_api.sh.tpl new file mode 100644 index 00000000000..230d8a0f5cb --- /dev/null +++ b/openstack/barbican/templates/bin/_barbican_api.sh.tpl @@ -0,0 +1,34 @@ +#!/bin/bash + +set -ex + +COMMAND="${@:-start}" + +function start () { + for BARBICAN_WSGI_SCRIPT in barbican-wsgi-api; do + cp -a $(type -p ${BARBICAN_WSGI_SCRIPT}) /var/www/cgi-bin/barbican/ + done + + a2dismod status + + if [ -f /etc/apache2/envvars ]; then + source /etc/apache2/envvars + fi + + if [ ! -d "$APACHE_RUN_DIR" ]; then + mkdir -p "$APACHE_RUN_DIR" + fi + + if [ -f "$APACHE_PID_FILE" ]; then + rm -f "$APACHE_PID_FILE" + fi + + exec apache2 -DFOREGROUND +} + +function stop () { + sleep {{ coalesce .Values.shutdownDelaySeconds .Values.global.shutdownDelaySeconds 10 }} + apachectl -k graceful-stop +} + +$COMMAND diff --git a/openstack/barbican/templates/configmap-bin.yaml b/openstack/barbican/templates/configmap-bin.yaml new file mode 100644 index 00000000000..f1334158292 --- /dev/null +++ b/openstack/barbican/templates/configmap-bin.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: barbican-bin + labels: + app: {{ template "fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + system: openstack + component: barbican + type: config +data: + barbican-api.sh: | +{{ include (print .Template.BasePath "/bin/_barbican_api.sh.tpl") . | indent 4 }} diff --git a/openstack/barbican/templates/cronjob-tls-rotation.yaml b/openstack/barbican/templates/cronjob-tls-rotation.yaml new file mode 100644 index 00000000000..dd79e65c3c0 --- /dev/null +++ b/openstack/barbican/templates/cronjob-tls-rotation.yaml @@ -0,0 +1,66 @@ +{{- if and .Values.tls.enabled .Values.tls.rotation.enabled }} +apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{ .Release.Name }}-tls-rotation + labels: + app: {{ template "fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + system: openstack + component: barbican + type: tls-rotation +spec: + schedule: {{ .Values.tls.rotation.schedule | quote }} + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 1 + jobTemplate: + spec: + backoffLimit: 2 + activeDeadlineSeconds: 600 + template: + metadata: + labels: + app: {{ template "fullname" . }} + name: {{ .Release.Name }}-tls-rotation + system: openstack + component: barbican + type: tls-rotation + spec: + serviceAccountName: {{ .Release.Name }}-tls-manager + restartPolicy: OnFailure + containers: + - name: tls-rotation + image: {{ required ".Values.global.registry is missing" .Values.global.registry }}/{{ required ".Values.tls.lifecycleManager.image is missing" .Values.tls.lifecycleManager.image }}:{{ required ".Values.tls.lifecycleManager.imageTag is missing" .Values.tls.lifecycleManager.imageTag }} + imagePullPolicy: IfNotPresent + args: + - --mode=rotation + - --namespace={{ .Release.Namespace }} + - --secret-name={{ .Release.Name }}-tls + - --deployment-name=barbican-api + - --renewal-threshold-days={{ .Values.tls.rotation.renewalThresholdDays }} + - --issuer-name={{ .Values.tls.issuer.name }} + - --issuer-kind={{ .Values.tls.issuer.kind }} + - --issuer-group={{ .Values.tls.issuer.group }} + - --common-name={{ include "barbican_api_endpoint_host_public" . }} + - --san-dns={{ include "barbican_api_endpoint_host_public" . }} + - --envelope-encryption={{ .Values.tls.envelopeEncryption }} + {{- if eq .Values.tls.envelopeEncryption "vault-transit" }} + - --vault-addr={{ .Values.tls.vault.addr }} + - --vault-transit-path={{ .Values.tls.vault.transitPath }} + {{- end }} + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + resources: + limits: + cpu: "100m" + memory: "128Mi" + requests: + cpu: "50m" + memory: "64Mi" +{{- end }} diff --git a/openstack/barbican/templates/etc-configmap.yaml b/openstack/barbican/templates/etc-configmap.yaml index 32b4aeabb64..42cf7c07fff 100644 --- a/openstack/barbican/templates/etc-configmap.yaml +++ b/openstack/barbican/templates/etc-configmap.yaml @@ -15,6 +15,12 @@ data: {{ include (print .Template.BasePath "/etc/_barbican-policy.yaml.tpl") . | indent 4 }} logging.ini: | {{ include "loggerIni" .Values.logging | indent 4 }} + wsgi-barbican.conf: | +{{ include "wsgi_barbican_conf" . | indent 4 }} +{{- if .Values.tls.enabled }} + tls-hardening.conf: | +{{ include "tls_hardening_conf" . | indent 4 }} +{{- end }} {{- if .Values.watcher.enabled }} watcher.yaml: | {{ include (print .Template.BasePath "/etc/_watcher.yaml.tpl") . | indent 4 }} diff --git a/openstack/barbican/templates/etc/_tls-hardening.conf.tpl b/openstack/barbican/templates/etc/_tls-hardening.conf.tpl new file mode 100644 index 00000000000..b13fd7aad81 --- /dev/null +++ b/openstack/barbican/templates/etc/_tls-hardening.conf.tpl @@ -0,0 +1,19 @@ +{{- define "tls_hardening_conf" }} +# BSI TR-02102-2 compliant TLS settings + +# Disable all protocols, then enable only TLS 1.2 and TLS 1.3 +SSLProtocol -all +TLSv1.2 +TLSv1.3 + +# TLS 1.3 cipher suites (BSI TR-02102-2 SF.Eco.4) +SSLCipherSuite TLSv1.3 {{ .Values.tls.hardening.cipherSuitesTLS13 | default "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" }} + +# TLS 1.2 cipher suites (BSI TR-02102-2 SF.Eco.4, OpenSSL names) +SSLCipherSuite {{ .Values.tls.hardening.cipherSuitesTLS12 | default "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256" }} + +SSLHonorCipherOrder on +SSLCompression off +SSLSessionTickets off + +# ECDHE curve preference (BSI TR-02102-2 SF.Eco.3) +SSLOpenSSLConfCmd Curves {{ .Values.tls.hardening.ecdheCurves | default "brainpoolP256r1:brainpoolP384r1:prime256v1:secp384r1" }} +{{- end }} diff --git a/openstack/barbican/templates/etc/_wsgi-barbican.conf.tpl b/openstack/barbican/templates/etc/_wsgi-barbican.conf.tpl new file mode 100644 index 00000000000..3dbdf52b1be --- /dev/null +++ b/openstack/barbican/templates/etc/_wsgi-barbican.conf.tpl @@ -0,0 +1,67 @@ +{{- define "wsgi_barbican_conf" }} +ErrorLog /dev/stderr + +LogFormat "%{%Y-%m-%d %T}t.%{msec_frac}t %{pid}P INFO apache \"%{X-Openstack-Request-ID}i\" %h %l %u \"%r\" %>s %b %{ms}T \"%{Referer}i\" \"%{User-Agent}i\"" combined +LogFormat "%{%Y-%m-%d %T}t.%{msec_frac}t %{pid}P INFO apache \"%{X-Openstack-Request-ID}i\" %{X-Forwarded-For}i %l %u \"%r\" %>s %b %{ms}T \"%{Referer}i\" \"%{User-Agent}i\"" proxy + +SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded +CustomLog /dev/stdout combined env=!forwarded +CustomLog /dev/stdout proxy env=forwarded + +WSGIDaemonProcess barbican-api processes={{ .Values.api.processes | default 4 }} threads={{ .Values.api.threads | default 1 }} \ + user=barbican group=barbican display-name=%{GROUP} + +{{- if .Values.tls.enabled }} +Listen 0.0.0.0:{{ .Values.tls.httpsPort }} + + + ServerName {{ include "barbican_api_endpoint_host_public" . }} + + SSLEngine on + SSLCertificateFile /etc/barbican/tls/tls.crt + SSLCertificateKeyFile /etc/barbican/tls/tls.key + + Include /etc/apache2/conf-enabled/tls-hardening.conf + + WSGIProcessGroup barbican-api + WSGIScriptAlias / /var/www/cgi-bin/barbican/barbican-wsgi-api + WSGIApplicationGroup %{GLOBAL} + WSGIPassAuthorization On + LimitRequestBody 114688 + + + Require all granted + + + ErrorLog /dev/stderr + SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded + CustomLog /dev/stdout combined env=!forwarded + CustomLog /dev/stdout proxy env=forwarded + + KeepAliveTimeout 61 + +{{- end }} + +Listen 0.0.0.0:{{ .Values.api_port_internal }} + + + ServerName {{ include "barbican_api_endpoint_host_public" . }} + + WSGIProcessGroup barbican-api + WSGIScriptAlias / /var/www/cgi-bin/barbican/barbican-wsgi-api + WSGIApplicationGroup %{GLOBAL} + WSGIPassAuthorization On + LimitRequestBody 114688 + + + Require all granted + + + ErrorLog /dev/stderr + SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded + CustomLog /dev/stdout combined env=!forwarded + CustomLog /dev/stdout proxy env=forwarded + + KeepAliveTimeout 61 + +{{- end }} \ No newline at end of file diff --git a/openstack/barbican/templates/job-tls-bootstrap.yaml b/openstack/barbican/templates/job-tls-bootstrap.yaml new file mode 100644 index 00000000000..4156b7e4fd0 --- /dev/null +++ b/openstack/barbican/templates/job-tls-bootstrap.yaml @@ -0,0 +1,62 @@ +{{- if .Values.tls.enabled }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ .Release.Name }}-tls-bootstrap + labels: + app: {{ template "fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + system: openstack + component: barbican + type: tls-bootstrap + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-5" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +spec: + backoffLimit: 3 + activeDeadlineSeconds: 300 + template: + metadata: + labels: + app: {{ template "fullname" . }} + name: {{ .Release.Name }}-tls-bootstrap + system: openstack + component: barbican + type: tls-bootstrap + spec: + serviceAccountName: {{ .Release.Name }}-tls-manager + restartPolicy: Never + containers: + - name: tls-bootstrap + image: {{ required ".Values.global.registry is missing" .Values.global.registry }}/{{ required ".Values.tls.lifecycleManager.image is missing" .Values.tls.lifecycleManager.image }}:{{ required ".Values.tls.lifecycleManager.imageTag is missing" .Values.tls.lifecycleManager.imageTag }} + imagePullPolicy: IfNotPresent + args: + - --mode=bootstrap + - --namespace={{ .Release.Namespace }} + - --secret-name={{ .Release.Name }}-tls + - --issuer-name={{ .Values.tls.issuer.name }} + - --issuer-kind={{ .Values.tls.issuer.kind }} + - --issuer-group={{ .Values.tls.issuer.group }} + - --common-name={{ include "barbican_api_endpoint_host_public" . }} + - --san-dns={{ include "barbican_api_endpoint_host_public" . }} + - --envelope-encryption={{ .Values.tls.envelopeEncryption }} + {{- if eq .Values.tls.envelopeEncryption "vault-transit" }} + - --vault-addr={{ .Values.tls.vault.addr }} + - --vault-transit-path={{ .Values.tls.vault.transitPath }} + {{- end }} + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + resources: + limits: + cpu: "100m" + memory: "128Mi" + requests: + cpu: "50m" + memory: "64Mi" +{{- end }} diff --git a/openstack/barbican/templates/rbac-tls.yaml b/openstack/barbican/templates/rbac-tls.yaml new file mode 100644 index 00000000000..3b9c0aa4df2 --- /dev/null +++ b/openstack/barbican/templates/rbac-tls.yaml @@ -0,0 +1,77 @@ +{{- if .Values.tls.enabled }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Release.Name }}-tls-manager + labels: + app: {{ template "fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + system: openstack + component: barbican + type: tls-manager + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ .Release.Name }}-tls-manager + labels: + app: {{ template "fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + system: openstack + component: barbican + type: tls-manager +rules: + # Read and mutate the specific TLS secret (resourceNames restricts to it) + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "watch", "update", "patch", "delete"] + resourceNames: ["{{ .Release.Name }}-tls"] + # create and list cannot use resourceNames (resource doesn't exist yet / no name filter) + - apiGroups: [""] + resources: ["secrets"] + verbs: ["create", "list"] + + # Trigger rolling restart of deployment (via annotation patch) + - apiGroups: ["apps"] + resources: ["deployments"] + verbs: ["get", "patch"] + resourceNames: ["barbican-api"] + + # Create and manage CertificateRequest resources (cert-manager) + - apiGroups: ["cert-manager.io"] + resources: ["certificaterequests"] + verbs: ["get", "list", "watch", "create", "delete"] + + # Access custom certificate issuers (e.g., ClusterDigicertIssuer) + - apiGroups: ["certmanager.cloud.sap"] + resources: ["clusterdigicertissuers"] + verbs: ["get", "list"] + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ .Release.Name }}-tls-manager + labels: + app: {{ template "fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + system: openstack + component: barbican + type: tls-manager +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ .Release.Name }}-tls-manager +subjects: + - kind: ServiceAccount + name: {{ .Release.Name }}-tls-manager + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/openstack/barbican/templates/service-external.yaml b/openstack/barbican/templates/service-external.yaml new file mode 100644 index 00000000000..11f9814c39b --- /dev/null +++ b/openstack/barbican/templates/service-external.yaml @@ -0,0 +1,30 @@ +{{- if and (.Values.tls).enabled .Values.global.barbican_external_ip }} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-external + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "fullname" . }} + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + system: openstack + component: barbican + type: api-external + annotations: + {{- include "utils.linkerd.pod_and_service_annotation" . | indent 4 }} + projectcalico.org/loadBalancerIPs: '["{{ .Values.global.barbican_external_ip }}"]' +spec: + type: LoadBalancer + externalTrafficPolicy: Local + selector: + name: barbican-api + ports: + - name: public + protocol: TCP + port: {{ .Values.tls.httpsPort }} + targetPort: {{ .Values.tls.httpsPort }} + externalIPs: + - {{ .Values.global.barbican_external_ip | quote }} +{{- end }} diff --git a/openstack/barbican/values.yaml b/openstack/barbican/values.yaml index a4c7603cbbb..bd33709db5b 100644 --- a/openstack/barbican/values.yaml +++ b/openstack/barbican/values.yaml @@ -11,10 +11,37 @@ global: domain_seeds: skip_hcm_domain: false linkerd_requested: false + # Set to a Calico-managed IP to expose Barbican directly via BGP (LoadBalancer service) + # Required when tls.enabled=true for end-to-end TLS without ingress passthrough + barbican_external_ip: "" rbac: enabled: true +# TLS configuration for BSI-compliant end-to-end encryption +# When enabled, Apache terminates TLS and traffic is exposed via a +# LoadBalancer service using Calico BGP (set global.barbican_external_ip) +tls: + enabled: false + httpsPort: 443 + issuer: + name: digicert-issuer + kind: ClusterDigicertIssuer + group: certmanager.cloud.sap + envelopeEncryption: none + lifecycleManager: + image: tls-lifecycle-manager + imageTag: "" + rotation: + enabled: true + schedule: "0 2 * * 0" + renewalThresholdDays: 30 + hardening: + minVersion: "TLSv1.2" + cipherSuitesTLS13: "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" + cipherSuitesTLS12: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256" + ecdheCurves: "brainpoolP256r1:brainpoolP384r1:prime256v1:secp384r1" + owner-info: support-group: identity service: barbican @@ -36,6 +63,23 @@ pod: api_port_internal: '9311' debug: "True" +# API configuration +api: + # Number of replicas + replicas: 2 + # Apache mod_wsgi processes and threads + processes: 4 + threads: 1 + # Resource limits + resources: + enabled: true + limits: + cpu: "2" + memory: "2Gi" + requests: + cpu: "500m" + memory: "1Gi" + statsd: port: 9102 image: 'shared-app-images/statsd-exporter'