diff --git a/README.md b/README.md index be15da1..7daec0a 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ On EKS, security relies on pod-level `securityContext` — which any chart consu | Capabilities | Must write `drop: [ALL]` | Dropped by default | | Trust boundary | The pod spec | The cluster admission controller | -The chart detects the target automatically: when `openshift.enabled=true`, pod-level `securityContext` is omitted to avoid conflicts with SCC. +When `openshift.enabled=true`, pod-level `securityContext` is omitted to avoid conflicts with SCC. OpenShift exposure uses a TLS Route; the chart rejects Ingress manifests and Route policies that allow clear-text HTTP. Service-account tokens are disabled by default and must be explicitly enabled only for workloads that call the Kubernetes API. --- diff --git a/policy/openshift/security.rego b/policy/openshift/security.rego index e1dfcf2..ebab33d 100644 --- a/policy/openshift/security.rego +++ b/policy/openshift/security.rego @@ -6,6 +6,18 @@ deny contains msg if { msg := "Route must specify tls.termination (edge, passthrough, or reencrypt)" } +deny contains msg if { + input.kind == "Deployment" + input.spec.template.spec.automountServiceAccountToken != false + msg := "Deployment must set automountServiceAccountToken to false unless API access is explicitly required" +} + +deny contains msg if { + input.kind == "Route" + input.spec.tls.insecureEdgeTerminationPolicy == "Allow" + msg := "Route must not allow insecure HTTP traffic" +} + deny contains msg if { input.kind == "Route" input.spec.tls.termination diff --git a/standardized-path/app/Chart.yaml b/standardized-path/app/Chart.yaml index 803ee19..a1a94f1 100644 --- a/standardized-path/app/Chart.yaml +++ b/standardized-path/app/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.0.0 +version: 1.1.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/standardized-path/app/templates/deployment.yaml b/standardized-path/app/templates/deployment.yaml index 6d7dce4..041a652 100644 --- a/standardized-path/app/templates/deployment.yaml +++ b/standardized-path/app/templates/deployment.yaml @@ -26,9 +26,10 @@ spec: {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include "app.serviceAccountName" . }} - {{- if not .Values.openshift.enabled }} + {{- end }} + serviceAccountName: {{ include "app.serviceAccountName" . }} + automountServiceAccountToken: {{ .Values.serviceAccount.automount }} + {{- if not .Values.openshift.enabled }} {{- with .Values.podSecurityContext }} securityContext: {{- toYaml . | nindent 8 }} diff --git a/standardized-path/app/templates/openshift-route.yaml b/standardized-path/app/templates/openshift-route.yaml index ac93335..0e87eb6 100644 --- a/standardized-path/app/templates/openshift-route.yaml +++ b/standardized-path/app/templates/openshift-route.yaml @@ -1,4 +1,13 @@ +{{- if and .Values.openshift.enabled .Values.ingress.enabled }} +{{- fail "ingress.enabled must be false when openshift.enabled is true; use openshift.route instead" }} +{{- end }} {{- if and .Values.openshift.enabled .Values.openshift.route.enabled }} +{{- if not .Values.openshift.route.tls.termination }} +{{- fail "openshift.route.tls.termination must be set when openshift.route.enabled is true" }} +{{- end }} +{{- if eq .Values.openshift.route.tls.insecureEdgeTerminationPolicy "Allow" }} +{{- fail "openshift.route.tls.insecureEdgeTerminationPolicy must not be Allow" }} +{{- end }} apiVersion: route.openshift.io/v1 kind: Route metadata: @@ -11,7 +20,7 @@ metadata: {{- end }} spec: {{- if .Values.openshift.route.host }} - host: {{ .Values.openshift.route.host }} + host: {{ .Values.openshift.route.host | quote }} {{- end }} to: kind: Service diff --git a/standardized-path/app/tests/deployment_test.yaml b/standardized-path/app/tests/deployment_test.yaml index 11814a2..b5200b9 100644 --- a/standardized-path/app/tests/deployment_test.yaml +++ b/standardized-path/app/tests/deployment_test.yaml @@ -65,6 +65,20 @@ tests: path: spec.template.spec.serviceAccountName value: custom-sa + - it: should not mount service account credentials by default + asserts: + - equal: + path: spec.template.spec.automountServiceAccountToken + value: false + + - it: should allow token mounting only when explicitly requested + set: + serviceAccount.automount: true + asserts: + - equal: + path: spec.template.spec.automountServiceAccountToken + value: true + - it: should set resource limits asserts: - equal: diff --git a/standardized-path/app/tests/route_test.yaml b/standardized-path/app/tests/route_test.yaml index ec15375..13b57de 100644 --- a/standardized-path/app/tests/route_test.yaml +++ b/standardized-path/app/tests/route_test.yaml @@ -9,6 +9,24 @@ tests: - hasDocuments: count: 0 + - it: should reject Ingress when targeting OpenShift + set: + openshift.enabled: true + ingress.enabled: true + asserts: + - failedTemplate: + errorMessage: ingress.enabled must be false when openshift.enabled is true; use openshift.route instead + + - it: should reject an HTTP-capable Route policy + set: + openshift.enabled: true + openshift.route.enabled: true + openshift.route.tls.insecureEdgeTerminationPolicy: Allow + ingress.enabled: false + asserts: + - failedTemplate: + errorMessage: openshift.route.tls.insecureEdgeTerminationPolicy must not be Allow + - it: should render when openshift is enabled set: openshift.enabled: true diff --git a/standardized-path/app/values.yaml b/standardized-path/app/values.yaml index 6718048..0663902 100644 --- a/standardized-path/app/values.yaml +++ b/standardized-path/app/values.yaml @@ -26,8 +26,8 @@ fullnameOverride: "" serviceAccount: # Specifies whether a service account should be created create: true - # Automatically mount a ServiceAccount's API credentials? - automount: true + # Workloads that do not call the Kubernetes API must not receive API credentials. + automount: false # Annotations to add to the service account annotations: {} # The name of the service account to use.