diff --git a/docs/reference/configuration/ingress.md b/docs/reference/configuration/ingress.md index ef52bc6d1..3591be20f 100644 --- a/docs/reference/configuration/ingress.md +++ b/docs/reference/configuration/ingress.md @@ -99,3 +99,50 @@ variables: :::note If you are using Private PKI or self-signed certificates for your tenant certificates it is necessary to additionally configure `UDS_CA_CERT` with additional [trusted certificate authorities](https://uds.defenseunicorns.com/reference/configuration/uds-operator/#trusted-certificate-authority). ::: + +### Using AWS Certificate Manager (ACM) Certificates + +When deploying UDS Core to Amazon EKS, you can leverage AWS Certificate Manager (ACM) to manage TLS certificates at the Load Balancer level instead of managing them through Istio. This approach requires: + +1. Disabling TLS termination in Istio (`tls.enabled: false`) +2. Configuring the appropriate AWS Load Balancer annotations to use ACM certificates + +Here's an example configuration in your UDS Bundle: + +```yaml +kind: UDSBundle +metadata: + name: core-with-acm-certs + description: A UDS example bundle for using ACM certificates with UDS Core + version: "0.0.1" + +packages: + - name: core + repository: oci://ghcr.io/defenseunicorns/packages/uds/core + ref: 0.23.0-upstream + overrides: + istio-tenant-gateway: + gateway: + values: + - path: service.annotations + value: + service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "http" + service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "arn:aws:acm:::certificate/" + service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "https" + uds-istio-config: + values: + - path: tls.enabled + value: false +``` + +The required annotations are: + +- `service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "http"` - Indicates that the backend protocol is HTTP +- `service.beta.kubernetes.io/aws-load-balancer-ssl-cert` - The ARN of your ACM certificate +- `service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "https"` - Specifies which ports should use SSL/TLS + +This configuration will terminate TLS at the AWS Load Balancer using your ACM certificate instead of at the Istio Gateway level. This can be particularly useful when you want to leverage AWS-native certificate management capabilities. + +:::note +If `tls.enabled` is set to `false` the above annotations must be set, otherwise the chart installation will fail. +::: diff --git a/src/istio/chart/templates/gateway.yaml b/src/istio/chart/templates/gateway.yaml index c14e81a74..dea2bdc72 100644 --- a/src/istio/chart/templates/gateway.yaml +++ b/src/istio/chart/templates/gateway.yaml @@ -27,6 +27,7 @@ spec: {{- range $server.hosts | default (list "*") }} - "{{ . }}.{{ $domain }}" {{- end }} + {{- if $.Values.tls.enabled }} port: name: "https-{{ $name }}" number: 443 @@ -38,5 +39,11 @@ spec: # if supportTLSV1_2 is both defined and true, use TLSV1_2, otherwise use TLSV1_3 minProtocolVersion: {{ if $.Values.tls.supportTLSV1_2 }}TLSV1_2{{ else }}TLSV1_3{{ end }} {{- end }} + {{ else }} + port: + name: "https-{{ $name }}" + number: 443 + protocol: HTTP + {{- end }} {{ end }} {{ end }} diff --git a/src/istio/chart/templates/tls-cert.yaml b/src/istio/chart/templates/tls-cert.yaml index 0fd4f0314..194c8a955 100644 --- a/src/istio/chart/templates/tls-cert.yaml +++ b/src/istio/chart/templates/tls-cert.yaml @@ -2,7 +2,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial {{- $tls := .Values.tls }} -{{ if $tls.cert }} +{{ if $tls.enabled }} apiVersion: v1 kind: Secret metadata: diff --git a/src/istio/values/config-admin.yaml b/src/istio/values/config-admin.yaml index e5c265887..30c7f33ef 100644 --- a/src/istio/values/config-admin.yaml +++ b/src/istio/values/config-admin.yaml @@ -4,6 +4,7 @@ name: admin domain: "admin.###ZARF_VAR_DOMAIN###" tls: + enabled: true servers: keycloak: mode: OPTIONAL_MUTUAL diff --git a/src/istio/values/config-passthrough.yaml b/src/istio/values/config-passthrough.yaml index 6c81bac5a..d8ca69cd3 100644 --- a/src/istio/values/config-passthrough.yaml +++ b/src/istio/values/config-passthrough.yaml @@ -4,6 +4,7 @@ name: passthrough tls: + enabled: true servers: passthrough: mode: PASSTHROUGH diff --git a/src/istio/values/config-tenant.yaml b/src/istio/values/config-tenant.yaml index 44a6ff6b9..b231c69e6 100644 --- a/src/istio/values/config-tenant.yaml +++ b/src/istio/values/config-tenant.yaml @@ -3,6 +3,7 @@ name: tenant tls: + enabled: true servers: keycloak: mode: OPTIONAL_MUTUAL diff --git a/src/pepr/operator/crd/generated/istio/gateway-v1.ts b/src/pepr/operator/crd/generated/istio/gateway-v1.ts new file mode 100644 index 000000000..2a7ed59f6 --- /dev/null +++ b/src/pepr/operator/crd/generated/istio/gateway-v1.ts @@ -0,0 +1,184 @@ +/** + * Copyright 2024 Defense Unicorns + * SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial + */ + +// This file is auto-generated by kubernetes-fluent-client, do not edit manually +import { GenericKind, RegisterKind } from "kubernetes-fluent-client"; +export class Gateway extends GenericKind { + /** + * Configuration affecting edge load balancer. See more details at: + * https://istio.io/docs/reference/config/networking/gateway.html + */ + spec?: Spec; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + status?: { [key: string]: any }; +} + +/** + * Configuration affecting edge load balancer. See more details at: + * https://istio.io/docs/reference/config/networking/gateway.html + */ +export interface Spec { + /** + * One or more labels that indicate a specific set of pods/VMs on which this gateway + * configuration should be applied. + */ + selector?: { [key: string]: string }; + /** + * A list of server specifications. + */ + servers?: Server[]; +} + +export interface Server { + /** + * The ip or the Unix domain socket to which the listener should be bound to. + */ + bind?: string; + defaultEndpoint?: string; + /** + * One or more hosts exposed by this gateway. + */ + hosts: string[]; + /** + * An optional name of the server, when set must be unique across all servers. + */ + name?: string; + /** + * The Port on which the proxy should listen for incoming connections. + */ + port: Port; + /** + * Set of TLS related options that govern the server's behavior. + */ + tls?: TLS; +} + +/** + * The Port on which the proxy should listen for incoming connections. + */ +export interface Port { + /** + * Label assigned to the port. + */ + name: string; + /** + * A valid non-negative integer port number. + */ + number: number; + /** + * The protocol exposed on the port. + */ + protocol: string; + targetPort?: number; +} + +/** + * Set of TLS related options that govern the server's behavior. + */ +export interface TLS { + /** + * REQUIRED if mode is `MUTUAL` or `OPTIONAL_MUTUAL`. + */ + caCertificates?: string; + /** + * OPTIONAL: The path to the file containing the certificate revocation list (CRL) to use in + * verifying a presented client side certificate. + */ + caCrl?: string; + /** + * Optional: If specified, only support the specified cipher list. + */ + cipherSuites?: string[]; + /** + * For gateways running on Kubernetes, the name of the secret that holds the TLS certs + * including the CA certificates. + */ + credentialName?: string; + /** + * If set to true, the load balancer will send a 301 redirect for all http connections, + * asking the clients to use HTTPS. + */ + httpsRedirect?: boolean; + /** + * Optional: Maximum TLS protocol version. + * + * Valid Options: TLS_AUTO, TLSV1_0, TLSV1_1, TLSV1_2, TLSV1_3 + */ + maxProtocolVersion?: ProtocolVersion; + /** + * Optional: Minimum TLS protocol version. + * + * Valid Options: TLS_AUTO, TLSV1_0, TLSV1_1, TLSV1_2, TLSV1_3 + */ + minProtocolVersion?: ProtocolVersion; + /** + * Optional: Indicates whether connections to this port should be secured using TLS. + * + * Valid Options: PASSTHROUGH, SIMPLE, MUTUAL, AUTO_PASSTHROUGH, ISTIO_MUTUAL, + * OPTIONAL_MUTUAL + */ + mode?: Mode; + /** + * REQUIRED if mode is `SIMPLE` or `MUTUAL`. + */ + privateKey?: string; + /** + * REQUIRED if mode is `SIMPLE` or `MUTUAL`. + */ + serverCertificate?: string; + /** + * A list of alternate names to verify the subject identity in the certificate presented by + * the client. + */ + subjectAltNames?: string[]; + /** + * An optional list of hex-encoded SHA-256 hashes of the authorized client certificates. + */ + verifyCertificateHash?: string[]; + /** + * An optional list of base64-encoded SHA-256 hashes of the SPKIs of authorized client + * certificates. + */ + verifyCertificateSpki?: string[]; +} + +/** + * Optional: Maximum TLS protocol version. + * + * Valid Options: TLS_AUTO, TLSV1_0, TLSV1_1, TLSV1_2, TLSV1_3 + * + * Optional: Minimum TLS protocol version. + * + * Valid Options: TLS_AUTO, TLSV1_0, TLSV1_1, TLSV1_2, TLSV1_3 + */ +export enum ProtocolVersion { + TLSAuto = "TLS_AUTO", + Tlsv10 = "TLSV1_0", + Tlsv11 = "TLSV1_1", + Tlsv12 = "TLSV1_2", + Tlsv13 = "TLSV1_3", +} + +/** + * Optional: Indicates whether connections to this port should be secured using TLS. + * + * Valid Options: PASSTHROUGH, SIMPLE, MUTUAL, AUTO_PASSTHROUGH, ISTIO_MUTUAL, + * OPTIONAL_MUTUAL + */ +export enum Mode { + AutoPassthrough = "AUTO_PASSTHROUGH", + IstioMutual = "ISTIO_MUTUAL", + Mutual = "MUTUAL", + OptionalMutual = "OPTIONAL_MUTUAL", + Passthrough = "PASSTHROUGH", + Simple = "SIMPLE", +} + +RegisterKind(Gateway, { + group: "networking.istio.io", + version: "v1", + kind: "Gateway", + plural: "gateways", +}); diff --git a/src/pepr/operator/crd/generated/istio/gateway-v1alpha3.ts b/src/pepr/operator/crd/generated/istio/gateway-v1alpha3.ts new file mode 100644 index 000000000..945e9e616 --- /dev/null +++ b/src/pepr/operator/crd/generated/istio/gateway-v1alpha3.ts @@ -0,0 +1,184 @@ +/** + * Copyright 2024 Defense Unicorns + * SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial + */ + +// This file is auto-generated by kubernetes-fluent-client, do not edit manually +import { GenericKind, RegisterKind } from "kubernetes-fluent-client"; +export class Gateway extends GenericKind { + /** + * Configuration affecting edge load balancer. See more details at: + * https://istio.io/docs/reference/config/networking/gateway.html + */ + spec?: Spec; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + status?: { [key: string]: any }; +} + +/** + * Configuration affecting edge load balancer. See more details at: + * https://istio.io/docs/reference/config/networking/gateway.html + */ +export interface Spec { + /** + * One or more labels that indicate a specific set of pods/VMs on which this gateway + * configuration should be applied. + */ + selector?: { [key: string]: string }; + /** + * A list of server specifications. + */ + servers?: Server[]; +} + +export interface Server { + /** + * The ip or the Unix domain socket to which the listener should be bound to. + */ + bind?: string; + defaultEndpoint?: string; + /** + * One or more hosts exposed by this gateway. + */ + hosts: string[]; + /** + * An optional name of the server, when set must be unique across all servers. + */ + name?: string; + /** + * The Port on which the proxy should listen for incoming connections. + */ + port: Port; + /** + * Set of TLS related options that govern the server's behavior. + */ + tls?: TLS; +} + +/** + * The Port on which the proxy should listen for incoming connections. + */ +export interface Port { + /** + * Label assigned to the port. + */ + name: string; + /** + * A valid non-negative integer port number. + */ + number: number; + /** + * The protocol exposed on the port. + */ + protocol: string; + targetPort?: number; +} + +/** + * Set of TLS related options that govern the server's behavior. + */ +export interface TLS { + /** + * REQUIRED if mode is `MUTUAL` or `OPTIONAL_MUTUAL`. + */ + caCertificates?: string; + /** + * OPTIONAL: The path to the file containing the certificate revocation list (CRL) to use in + * verifying a presented client side certificate. + */ + caCrl?: string; + /** + * Optional: If specified, only support the specified cipher list. + */ + cipherSuites?: string[]; + /** + * For gateways running on Kubernetes, the name of the secret that holds the TLS certs + * including the CA certificates. + */ + credentialName?: string; + /** + * If set to true, the load balancer will send a 301 redirect for all http connections, + * asking the clients to use HTTPS. + */ + httpsRedirect?: boolean; + /** + * Optional: Maximum TLS protocol version. + * + * Valid Options: TLS_AUTO, TLSV1_0, TLSV1_1, TLSV1_2, TLSV1_3 + */ + maxProtocolVersion?: ProtocolVersion; + /** + * Optional: Minimum TLS protocol version. + * + * Valid Options: TLS_AUTO, TLSV1_0, TLSV1_1, TLSV1_2, TLSV1_3 + */ + minProtocolVersion?: ProtocolVersion; + /** + * Optional: Indicates whether connections to this port should be secured using TLS. + * + * Valid Options: PASSTHROUGH, SIMPLE, MUTUAL, AUTO_PASSTHROUGH, ISTIO_MUTUAL, + * OPTIONAL_MUTUAL + */ + mode?: Mode; + /** + * REQUIRED if mode is `SIMPLE` or `MUTUAL`. + */ + privateKey?: string; + /** + * REQUIRED if mode is `SIMPLE` or `MUTUAL`. + */ + serverCertificate?: string; + /** + * A list of alternate names to verify the subject identity in the certificate presented by + * the client. + */ + subjectAltNames?: string[]; + /** + * An optional list of hex-encoded SHA-256 hashes of the authorized client certificates. + */ + verifyCertificateHash?: string[]; + /** + * An optional list of base64-encoded SHA-256 hashes of the SPKIs of authorized client + * certificates. + */ + verifyCertificateSpki?: string[]; +} + +/** + * Optional: Maximum TLS protocol version. + * + * Valid Options: TLS_AUTO, TLSV1_0, TLSV1_1, TLSV1_2, TLSV1_3 + * + * Optional: Minimum TLS protocol version. + * + * Valid Options: TLS_AUTO, TLSV1_0, TLSV1_1, TLSV1_2, TLSV1_3 + */ +export enum ProtocolVersion { + TLSAuto = "TLS_AUTO", + Tlsv10 = "TLSV1_0", + Tlsv11 = "TLSV1_1", + Tlsv12 = "TLSV1_2", + Tlsv13 = "TLSV1_3", +} + +/** + * Optional: Indicates whether connections to this port should be secured using TLS. + * + * Valid Options: PASSTHROUGH, SIMPLE, MUTUAL, AUTO_PASSTHROUGH, ISTIO_MUTUAL, + * OPTIONAL_MUTUAL + */ +export enum Mode { + AutoPassthrough = "AUTO_PASSTHROUGH", + IstioMutual = "ISTIO_MUTUAL", + Mutual = "MUTUAL", + OptionalMutual = "OPTIONAL_MUTUAL", + Passthrough = "PASSTHROUGH", + Simple = "SIMPLE", +} + +RegisterKind(Gateway, { + group: "networking.istio.io", + version: "v1alpha3", + kind: "Gateway", + plural: "gateways", +}); diff --git a/src/pepr/operator/crd/generated/istio/gateway-v1beta1.ts b/src/pepr/operator/crd/generated/istio/gateway-v1beta1.ts new file mode 100644 index 000000000..4d75ed04d --- /dev/null +++ b/src/pepr/operator/crd/generated/istio/gateway-v1beta1.ts @@ -0,0 +1,184 @@ +/** + * Copyright 2024 Defense Unicorns + * SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial + */ + +// This file is auto-generated by kubernetes-fluent-client, do not edit manually +import { GenericKind, RegisterKind } from "kubernetes-fluent-client"; +export class Gateway extends GenericKind { + /** + * Configuration affecting edge load balancer. See more details at: + * https://istio.io/docs/reference/config/networking/gateway.html + */ + spec?: Spec; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + status?: { [key: string]: any }; +} + +/** + * Configuration affecting edge load balancer. See more details at: + * https://istio.io/docs/reference/config/networking/gateway.html + */ +export interface Spec { + /** + * One or more labels that indicate a specific set of pods/VMs on which this gateway + * configuration should be applied. + */ + selector?: { [key: string]: string }; + /** + * A list of server specifications. + */ + servers?: Server[]; +} + +export interface Server { + /** + * The ip or the Unix domain socket to which the listener should be bound to. + */ + bind?: string; + defaultEndpoint?: string; + /** + * One or more hosts exposed by this gateway. + */ + hosts: string[]; + /** + * An optional name of the server, when set must be unique across all servers. + */ + name?: string; + /** + * The Port on which the proxy should listen for incoming connections. + */ + port: Port; + /** + * Set of TLS related options that govern the server's behavior. + */ + tls?: TLS; +} + +/** + * The Port on which the proxy should listen for incoming connections. + */ +export interface Port { + /** + * Label assigned to the port. + */ + name: string; + /** + * A valid non-negative integer port number. + */ + number: number; + /** + * The protocol exposed on the port. + */ + protocol: string; + targetPort?: number; +} + +/** + * Set of TLS related options that govern the server's behavior. + */ +export interface TLS { + /** + * REQUIRED if mode is `MUTUAL` or `OPTIONAL_MUTUAL`. + */ + caCertificates?: string; + /** + * OPTIONAL: The path to the file containing the certificate revocation list (CRL) to use in + * verifying a presented client side certificate. + */ + caCrl?: string; + /** + * Optional: If specified, only support the specified cipher list. + */ + cipherSuites?: string[]; + /** + * For gateways running on Kubernetes, the name of the secret that holds the TLS certs + * including the CA certificates. + */ + credentialName?: string; + /** + * If set to true, the load balancer will send a 301 redirect for all http connections, + * asking the clients to use HTTPS. + */ + httpsRedirect?: boolean; + /** + * Optional: Maximum TLS protocol version. + * + * Valid Options: TLS_AUTO, TLSV1_0, TLSV1_1, TLSV1_2, TLSV1_3 + */ + maxProtocolVersion?: ProtocolVersion; + /** + * Optional: Minimum TLS protocol version. + * + * Valid Options: TLS_AUTO, TLSV1_0, TLSV1_1, TLSV1_2, TLSV1_3 + */ + minProtocolVersion?: ProtocolVersion; + /** + * Optional: Indicates whether connections to this port should be secured using TLS. + * + * Valid Options: PASSTHROUGH, SIMPLE, MUTUAL, AUTO_PASSTHROUGH, ISTIO_MUTUAL, + * OPTIONAL_MUTUAL + */ + mode?: Mode; + /** + * REQUIRED if mode is `SIMPLE` or `MUTUAL`. + */ + privateKey?: string; + /** + * REQUIRED if mode is `SIMPLE` or `MUTUAL`. + */ + serverCertificate?: string; + /** + * A list of alternate names to verify the subject identity in the certificate presented by + * the client. + */ + subjectAltNames?: string[]; + /** + * An optional list of hex-encoded SHA-256 hashes of the authorized client certificates. + */ + verifyCertificateHash?: string[]; + /** + * An optional list of base64-encoded SHA-256 hashes of the SPKIs of authorized client + * certificates. + */ + verifyCertificateSpki?: string[]; +} + +/** + * Optional: Maximum TLS protocol version. + * + * Valid Options: TLS_AUTO, TLSV1_0, TLSV1_1, TLSV1_2, TLSV1_3 + * + * Optional: Minimum TLS protocol version. + * + * Valid Options: TLS_AUTO, TLSV1_0, TLSV1_1, TLSV1_2, TLSV1_3 + */ +export enum ProtocolVersion { + TLSAuto = "TLS_AUTO", + Tlsv10 = "TLSV1_0", + Tlsv11 = "TLSV1_1", + Tlsv12 = "TLSV1_2", + Tlsv13 = "TLSV1_3", +} + +/** + * Optional: Indicates whether connections to this port should be secured using TLS. + * + * Valid Options: PASSTHROUGH, SIMPLE, MUTUAL, AUTO_PASSTHROUGH, ISTIO_MUTUAL, + * OPTIONAL_MUTUAL + */ +export enum Mode { + AutoPassthrough = "AUTO_PASSTHROUGH", + IstioMutual = "ISTIO_MUTUAL", + Mutual = "MUTUAL", + OptionalMutual = "OPTIONAL_MUTUAL", + Passthrough = "PASSTHROUGH", + Simple = "SIMPLE", +} + +RegisterKind(Gateway, { + group: "networking.istio.io", + version: "v1beta1", + kind: "Gateway", + plural: "gateways", +}); diff --git a/src/pepr/policies/networking.ts b/src/pepr/policies/networking.ts index fcf02297a..d85a2da18 100644 --- a/src/pepr/policies/networking.ts +++ b/src/pepr/policies/networking.ts @@ -5,7 +5,9 @@ import { a } from "pepr"; +import { K8s } from "pepr"; import { Policy } from "../operator/crd"; +import { Gateway } from "../operator/crd/generated/istio/gateway-v1"; import { When, containers } from "./common"; import { isExempt, markExemption } from "./exemptions"; @@ -113,3 +115,53 @@ When(a.Service) return request.Approve(); }); + +/** + * This policy enforces secure traffic handling for Istio Gateways. + * + * Istio Gateway servers must either have TLS configuration or their associated services must have + * required AWS load balancer annotations for SSL termination. This policy ensures that network + * traffic is properly secured through one of these mechanisms. + * + * @related https://istio.io/latest/docs/reference/config/networking/gateway/ + */ +When(Gateway) + .IsCreatedOrUpdated() + .InNamespaceRegex(/istio-(.*)-gateway/) + .WithNameRegex(/(.*)-gateway/) + .Validate(async g => { + const serversWithTLS = g.Raw.spec?.servers?.reduce( + (count, server) => (server.tls ? count + 1 : count), + 0, + ); + + if (serversWithTLS && serversWithTLS > 0) { + return g.Approve(); + } else if (!serversWithTLS || serversWithTLS === 0) { + const services = await K8s(a.Service) + .InNamespace(g.Request.namespace ?? "default") + .WithLabel("app", g.Raw.spec?.selector?.app) + .Get(); + + const requiredAnnotations = [ + "service.beta.kubernetes.io/aws-load-balancer-backend-protocol", + "service.beta.kubernetes.io/aws-load-balancer-ssl-cert", + "service.beta.kubernetes.io/aws-load-balancer-ssl-ports", + ]; + + // Check if any service has all required annotations + const hasRequiredAnnotations = services.items.some(svc => { + const annotations = svc.metadata?.annotations || {}; + return requiredAnnotations.every(annotation => annotation in annotations); + }); + + if (!hasRequiredAnnotations) { + return g.Deny( + "Gateway servers must either have TLS configuration or associated services must have required AWS Load Balancer annotations: " + + requiredAnnotations.join(", "), + ); + } + } + + return g.Approve(); + });