diff --git a/charts/streaming-hub/templates/_deployment.tpl b/charts/streaming-hub/templates/_deployment.tpl index 7baae9eb1..760800801 100644 --- a/charts/streaming-hub/templates/_deployment.tpl +++ b/charts/streaming-hub/templates/_deployment.tpl @@ -20,6 +20,8 @@ STREAMING_HUB_ROLE / the pool vars. {{- $component := .component -}} {{- $cfg := index $.Values.streamingHub $component -}} {{- $sh := $.Values.streamingHub -}} +{{/* Hoisted so the four Roles Anywhere sites below cannot drift apart. */}} +{{- $rolesAnywhere := and $.Values.aws $.Values.aws.rolesAnywhere $.Values.aws.rolesAnywhere.enabled -}} apiVersion: apps/v1 kind: Deployment metadata: @@ -59,10 +61,18 @@ spec: serviceAccountName: {{ include "streaming-hub.serviceAccountName" $ }} automountServiceAccountToken: {{ $sh.serviceAccount.automountServiceAccountToken | default false }} terminationGracePeriodSeconds: {{ $sh.terminationGracePeriodSeconds | default 80 }} + {{- if $rolesAnywhere }} + # IAM Roles Anywhere: fsGroup lets the sidecar's non-root user (65532) read the + # 0440 iam-certs projection. MERGED into the chart's podSecurityContext — fsGroup + # is enforced, every other pod-level setting the deployer configured survives. + securityContext: + {{- toYaml (merge (dict "fsGroup" 65532) (default (dict) $sh.podSecurityContext)) | nindent 8 }} + {{- else }} {{- with $sh.podSecurityContext }} securityContext: {{- toYaml . | nindent 8 }} {{- end }} + {{- end }} containers: - name: streaming-hub securityContext: @@ -102,6 +112,14 @@ spec: - name: OTEL_EXPORTER_OTLP_ENDPOINT value: "$(HOST_IP):4317" {{- end }} + {{- if $rolesAnywhere }} + # Point the AWS SDK's IMDS lookup at the aws-signing-helper sidecar, which + # vends short-lived credentials from the IAM Roles Anywhere exchange. + - name: AWS_EC2_METADATA_SERVICE_ENDPOINT + value: "http://127.0.0.1:{{ $.Values.aws.rolesAnywhere.sidecar.port | default 9911 }}" + - name: AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE + value: "IPv4" + {{- end }} livenessProbe: httpGet: path: /healthz @@ -122,6 +140,66 @@ spec: failureThreshold: {{ $sh.readinessProbe.failureThreshold | default 3 }} resources: {{- toYaml $cfg.resources | nindent 12 }} + {{- if $rolesAnywhere }} + # IAM Roles Anywhere credential sidecar. Serves an IMDS-compatible endpoint on + # 127.0.0.1:, exchanging the X.509 client cert (mounted from iam-certs) + # for short-lived AWS credentials. Shared by every role, since this define + # renders all of them (all / ingest / delivery). + - name: aws-signing-helper + image: "{{ $.Values.aws.rolesAnywhere.sidecar.image.repository }}:{{ $.Values.aws.rolesAnywhere.sidecar.image.tag }}" + imagePullPolicy: {{ $.Values.aws.rolesAnywhere.sidecar.image.pullPolicy | default "IfNotPresent" }} + args: + - serve + - --certificate + - /certs/tls.crt + - --private-key + - /certs/tls.key + - --trust-anchor-arn + - "{{ required "aws.rolesAnywhere.trustAnchorArn is required when rolesAnywhere is enabled" $.Values.aws.rolesAnywhere.trustAnchorArn }}" + - --profile-arn + - "{{ required "aws.rolesAnywhere.profileArn is required when rolesAnywhere is enabled" $.Values.aws.rolesAnywhere.profileArn }}" + - --role-arn + - "{{ required "aws.rolesAnywhere.roleArn is required when rolesAnywhere is enabled" $.Values.aws.rolesAnywhere.roleArn }}" + - --region + - "{{ $.Values.aws.rolesAnywhere.region | default "us-east-2" }}" + - --session-duration + - "{{ $.Values.aws.rolesAnywhere.sessionDuration | default 3600 }}" + - --port + - "{{ $.Values.aws.rolesAnywhere.sidecar.port | default 9911 }}" + ports: + - name: imds + containerPort: {{ $.Values.aws.rolesAnywhere.sidecar.port | default 9911 }} + protocol: TCP + volumeMounts: + - name: iam-certs + mountPath: /certs + readOnly: true + securityContext: + runAsNonRoot: true + runAsUser: 65532 + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + resources: + {{- toYaml $.Values.aws.rolesAnywhere.sidecar.resources | nindent 12 }} + {{- end }} + {{- if $rolesAnywhere }} + # X.509 client cert/key for the Roles Anywhere exchange. Produced outside the + # chart (a cert-manager Certificate in the deploying overlay) and mounted 0440 + # so only the sidecar's fsGroup can read it. + volumes: + - name: iam-certs + secret: + secretName: {{ $.Values.aws.rolesAnywhere.certificateSecretName | default (printf "%s-iam-tls" (include "streaming-hub.fullname" $)) }} + defaultMode: 0440 + items: + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key + {{- end }} {{- with $cfg.nodeSelector | default $sh.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/charts/streaming-hub/values.schema.json b/charts/streaming-hub/values.schema.json index 7d0c17a0e..99fc768cf 100644 --- a/charts/streaming-hub/values.schema.json +++ b/charts/streaming-hub/values.schema.json @@ -3,6 +3,10 @@ "type": "object", "additionalProperties": true, "properties": { + "aws": { + "type": "object", + "additionalProperties": true + }, "nameOverride": { "type": "string" }, @@ -22,7 +26,10 @@ "properties": { "mode": { "type": "string", - "enum": ["all", "split"], + "enum": [ + "all", + "split" + ], "description": "Topology switch. 'all' = one Deployment (role=all); 'split' = ingest + delivery. NEVER run both against one Kafka cluster (double-consume)." }, "common": { diff --git a/charts/streaming-hub/values.yaml b/charts/streaming-hub/values.yaml index 6921ebd0f..f099e85c7 100644 --- a/charts/streaming-hub/values.yaml +++ b/charts/streaming-hub/values.yaml @@ -431,6 +431,63 @@ streamingHub: nodeSelector: {} tolerations: {} affinity: {} + +# ============================================================================= +# aws — AWS IAM Roles Anywhere (X.509 -> short-lived AWS credentials). +# +# Default OFF. When enabled, every role's pod (all / ingest / delivery) gets an +# `aws-signing-helper` sidecar serving an IMDS-compatible endpoint on +# 127.0.0.1:; the app's AWS SDK is pointed at it via +# AWS_EC2_METADATA_SERVICE_ENDPOINT. Same shape and sidecar contract used by +# fetcher, reporter, matcher and plugin-fees. +# +# The X.509 cert/key are NOT produced by this chart: mount a Secret of type +# kubernetes.io/tls (keys tls.crt / tls.key) named by certificateSecretName — +# in the Lerian environments a cert-manager Certificate in the deploying overlay +# creates it. fsGroup 65532 is applied to the pod so the sidecar's non-root user +# can read the 0440 projection; that REPLACES streamingHub.podSecurityContext +# while rolesAnywhere is enabled. +# +# trustAnchorArn / profileArn / roleArn are `required` when enabled — the chart +# fails to render rather than starting a sidecar that cannot authenticate. +# ============================================================================= +aws: + rolesAnywhere: + # -- Enable the IAM Roles Anywhere credential sidecar. + enabled: false + # -- Trust anchor ARN (required when enabled). + trustAnchorArn: "" + # -- Profile ARN (required when enabled). + profileArn: "" + # -- Role ARN the sidecar assumes (required when enabled). + roleArn: "" + # -- AWS region for the credential exchange. + region: "us-east-2" + # -- Credential session duration in seconds. + sessionDuration: 3600 + # -- Name of the kubernetes.io/tls Secret holding tls.crt / tls.key. + # Left empty on purpose so the template fallback in _deployment.tpl actually + # fires: it resolves to "-iam-tls". Note that fullname + # is fullnameOverride if set, else nameOverride if set, else the CHART name + # (never the Helm release name), so the fallback yields "streaming-hub-iam-tls" + # unless one of those overrides is set (e.g. nameOverride: foo -> "foo-iam-tls"). + # A non-empty default here made that branch unreachable and pinned every install + # to one literal name, ignoring the overrides. + certificateSecretName: "" + sidecar: + image: + repository: public.ecr.aws/rolesanywhere/credential-helper + tag: "latest-amd64" + pullPolicy: IfNotPresent + # -- Port the sidecar serves the IMDS-compatible endpoint on. + port: 9911 + resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi streaming-hub: image: tag: 1.0.1