diff --git a/Chart.yaml b/Chart.yaml index 072b0a2..5211b60 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: ztwim description: Zero Trust Workload Identity Manager Helm Chart type: application -version: 0.1.1 +version: 0.1.2 home: https://github.com/validatedpatterns/ztwim-chart maintainers: - name: Validated Patterns Team diff --git a/README.md b/README.md index f1b097d..18fe176 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ -![Version: 0.1.1](https://img.shields.io/badge/Version-0.1.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) +![Version: 0.1.2](https://img.shields.io/badge/Version-0.1.2-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) @@ -28,46 +28,143 @@ This chart is used to serve as the template for Validated Patterns Charts ## Values -| Key | Type | Default | Description | -| -------------------------------------------------------------------------------- | ------ | -------------------------------------------------------------------------------- | ----------- | -| global.hubClusterDomain | string | `"hub.example.com"` | | -| global.localClusterDomain | string | `"local.example.com"` | | -| spiffe.csi.agentSocketPath | string | `"/run/spire/agent-sockets"` | | -| spire.agent.nodeAttestor.k8sPSATEnabled | string | `"true"` | | -| spire.agent.workloadAttestors.k8sEnabled | string | `"true"` | | -| spire.agent.workloadAttestors.workloadAttestorsVerification.hostCertBasePath | string | `"/var/lib/kubelet/pki"` | | -| spire.agent.workloadAttestors.workloadAttestorsVerification.hostCertFileName | string | `""` | | -| spire.agent.workloadAttestors.workloadAttestorsVerification.type | string | `"auto"` | | -| spire.bundleConfigMap | string | `"spire-bundle"` | | -| spire.clusterName | string | `"cluster"` | | -| spire.oidcDiscoveryProvider.ingress.annotations."route.openshift.io/termination" | string | `"reencrypt"` | | -| spire.oidcDiscoveryProvider.ingress.host | string | `"spire-spiffe-oidc-discovery-provider.{{ .Values.global.localClusterDomain }}"` | | -| spire.oidcDiscoveryProvider.ingress.operatorManaged | string | `"true"` | | -| spire.oidcDiscoveryProvider.service.name | string | `"spire-spiffe-oidc-discovery-provider"` | | -| spire.oidcDiscoveryProvider.service.port | int | `443` | | -| spire.server.ca.commonName | string | `"redhat.com"` | | -| spire.server.ca.country | string | `"US"` | | -| spire.server.ca.organization | string | `"Red Hat"` | | -| spire.server.datastore.connMaxLifetime | int | `0` | | -| spire.server.datastore.connectionString | string | `"/run/spire/data/datastore.sqlite3"` | | -| spire.server.datastore.databaseType | string | `"sqlite3"` | | -| spire.server.datastore.maxIdleConns | int | `10` | | -| spire.server.datastore.maxOpenConns | int | `100` | | -| spire.server.federation.bundleEndpoint.profile | string | `"https_spiffe"` | | -| spire.server.federation.enabled | string | `"false"` | | -| spire.server.federation.federatesWith | list | `[]` | | -| spire.server.federation.ingress.annotations."route.openshift.io/termination" | string | `"passthrough"` | | -| spire.server.federation.ingress.host | string | `"spire-server.{{ .Values.global.localClusterDomain }}"` | | -| spire.server.federation.ingress.operatorManaged | string | `"true"` | | -| spire.server.persistence.accessMode | string | `"ReadWriteOnce"` | | -| spire.server.persistence.size | string | `"5Gi"` | | -| spire.server.persistence.storageClass | string | `""` | | -| spire.server.service.name | string | `"spire-server"` | | -| spire.server.service.port | int | `443` | | -| spire.trustDomain | string | `"{{ .Values.global.localClusterDomain }}"` | | +| Key | Type | Default | Description | +| -------------------------------------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| defaultDenyNetworkPolicy | object | `{"enabled":false}` | Default-deny NetworkPolicy for the ZTWIM namespace. When enabled, deploys a namespace-wide NetworkPolicy that blocks all ingress and egress for pods without an explicit allow policy. Note: spire-agent uses hostNetwork and is NOT affected by NetworkPolicies. | +| global.hubClusterDomain | string | `"hub.example.com"` | | +| global.localClusterDomain | string | `"local.example.com"` | | +| networkPolicy | object | `{"csiDriver":{"egress":[],"enabled":false},"oidcDiscoveryProvider":{"egress":[],"enabled":false,"ingress":[]},"operator":{"egress":[],"enabled":false,"ingress":[]},"spireServer":{"egress":[],"enabled":false,"ingress":[]}}` | Per-pod NetworkPolicy rules for SPIRE components and the ZTWIM operator. Only effective when defaultDenyNetworkPolicy is enabled. | +| spiffe.csi.agentSocketPath | string | `"/run/spire/agent-sockets"` | | +| spire.agent.nodeAttestor.k8sPSATEnabled | string | `"true"` | | +| spire.agent.workloadAttestors.k8sEnabled | string | `"true"` | | +| spire.agent.workloadAttestors.workloadAttestorsVerification.hostCertBasePath | string | `"/var/lib/kubelet/pki"` | | +| spire.agent.workloadAttestors.workloadAttestorsVerification.hostCertFileName | string | `""` | | +| spire.agent.workloadAttestors.workloadAttestorsVerification.type | string | `"auto"` | | +| spire.bundleConfigMap | string | `"spire-bundle"` | | +| spire.clusterName | string | `"cluster"` | | +| spire.oidcDiscoveryProvider.ingress.annotations."route.openshift.io/termination" | string | `"reencrypt"` | | +| spire.oidcDiscoveryProvider.ingress.host | string | `"spire-spiffe-oidc-discovery-provider.{{ .Values.global.localClusterDomain }}"` | | +| spire.oidcDiscoveryProvider.ingress.operatorManaged | string | `"true"` | | +| spire.oidcDiscoveryProvider.service.name | string | `"spire-spiffe-oidc-discovery-provider"` | | +| spire.oidcDiscoveryProvider.service.port | int | `443` | | +| spire.server.ca.commonName | string | `"redhat.com"` | | +| spire.server.ca.country | string | `"US"` | | +| spire.server.ca.organization | string | `"Red Hat"` | | +| spire.server.datastore.connMaxLifetime | int | `0` | | +| spire.server.datastore.connectionString | string | `"/run/spire/data/datastore.sqlite3"` | | +| spire.server.datastore.databaseType | string | `"sqlite3"` | | +| spire.server.datastore.maxIdleConns | int | `10` | | +| spire.server.datastore.maxOpenConns | int | `100` | | +| spire.server.federation.bundleEndpoint.profile | string | `"https_spiffe"` | | +| spire.server.federation.enabled | string | `"false"` | | +| spire.server.federation.federatesWith | list | `[]` | | +| spire.server.federation.ingress.annotations."route.openshift.io/termination" | string | `"passthrough"` | | +| spire.server.federation.ingress.host | string | `"spire-server.{{ .Values.global.localClusterDomain }}"` | | +| spire.server.federation.ingress.operatorManaged | string | `"true"` | | +| spire.server.persistence.accessMode | string | `"ReadWriteOnce"` | | +| spire.server.persistence.size | string | `"5Gi"` | | +| spire.server.persistence.storageClass | string | `""` | | +| spire.server.service.name | string | `"spire-server"` | | +| spire.server.service.port | int | `443` | | +| spire.trustDomain | string | `"{{ .Values.global.localClusterDomain }}"` | | --- Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) + +## Network Policies + +This chart supports deploying Kubernetes NetworkPolicies for network isolation +in the ZTWIM namespace. Two layers are available: + +### Default-deny policy + +A namespace-wide default-deny NetworkPolicy that blocks all ingress and egress +traffic for every pod in the namespace unless an explicit allow policy exists. +Enable it by setting: + +```yaml +defaultDenyNetworkPolicy: + enabled: true +``` + +**Note:** The spire-agent DaemonSet uses `hostNetwork: true` and is NOT affected +by NetworkPolicies. Agent-to-server communication uses node IPs and requires a +port-only ingress rule on the spire-server. + +### Per-pod allow rules + +When the default-deny policy is enabled, additional NetworkPolicy templates +allow defining fine-grained rules for each component: + +- `networkPolicy.spireServer` — ingress and egress rules for the spire-server + pod (includes the spire-controller-manager webhook container) +- `networkPolicy.oidcDiscoveryProvider` — ingress and egress rules for the OIDC + discovery provider pod +- `networkPolicy.csiDriver` — egress rules for the SPIFFE CSI driver pods +- `networkPolicy.operator` — ingress and egress rules for the ZTWIM operator pod + +Example — allow spire-server ingress from agents and egress to DNS: + +```yaml +defaultDenyNetworkPolicy: + enabled: true + +networkPolicy: + spireServer: + enabled: true + ingress: + - ports: + - protocol: TCP + port: 8081 + - ports: + - protocol: TCP + port: 9443 + egress: + - ports: + - protocol: UDP + port: 5353 + - protocol: TCP + port: 5353 + to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-dns + - ports: + - protocol: TCP + port: 6443 + oidcDiscoveryProvider: + enabled: true + ingress: + - ports: + - protocol: TCP + port: 8443 + from: + - namespaceSelector: + matchLabels: + policy-group.network.openshift.io/ingress: "" + egress: + - ports: + - protocol: UDP + port: 5353 + - protocol: TCP + port: 5353 + to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-dns + operator: + enabled: true + egress: + - ports: + - protocol: TCP + port: 443 + - protocol: TCP + port: 6443 +``` + +Patterns can supply these values via `extraValueFiles` in their +`values-hub.yaml` to keep network policy configuration separate from the main +chart values. diff --git a/README.md.gotmpl b/README.md.gotmpl index c426f84..243b60c 100644 --- a/README.md.gotmpl +++ b/README.md.gotmpl @@ -26,3 +26,98 @@ This chart is used to serve as the template for Validated Patterns Charts {{ template "helm-docs.versionFooter" . }} + +## Network Policies + +This chart supports deploying Kubernetes NetworkPolicies for network isolation +in the ZTWIM namespace. Two layers are available: + +### Default-deny policy + +A namespace-wide default-deny NetworkPolicy that blocks all ingress and egress +traffic for every pod in the namespace unless an explicit allow policy exists. +Enable it by setting: + +```yaml +defaultDenyNetworkPolicy: + enabled: true +``` + +**Note:** The spire-agent DaemonSet uses `hostNetwork: true` and is NOT affected +by NetworkPolicies. Agent-to-server communication uses node IPs and requires a +port-only ingress rule on the spire-server. + +### Per-pod allow rules + +When the default-deny policy is enabled, additional NetworkPolicy templates +allow defining fine-grained rules for each component: + +- `networkPolicy.spireServer` — ingress and egress rules for the spire-server + pod (includes the spire-controller-manager webhook container) +- `networkPolicy.oidcDiscoveryProvider` — ingress and egress rules for the OIDC + discovery provider pod +- `networkPolicy.csiDriver` — egress rules for the SPIFFE CSI driver pods +- `networkPolicy.operator` — ingress and egress rules for the ZTWIM operator pod + +Example — allow spire-server ingress from agents and egress to DNS: + +```yaml +defaultDenyNetworkPolicy: + enabled: true + +networkPolicy: + spireServer: + enabled: true + ingress: + - ports: + - protocol: TCP + port: 8081 + - ports: + - protocol: TCP + port: 9443 + egress: + - ports: + - protocol: UDP + port: 5353 + - protocol: TCP + port: 5353 + to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-dns + - ports: + - protocol: TCP + port: 6443 + oidcDiscoveryProvider: + enabled: true + ingress: + - ports: + - protocol: TCP + port: 8443 + from: + - namespaceSelector: + matchLabels: + policy-group.network.openshift.io/ingress: "" + egress: + - ports: + - protocol: UDP + port: 5353 + - protocol: TCP + port: 5353 + to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: openshift-dns + operator: + enabled: true + egress: + - ports: + - protocol: TCP + port: 443 + - protocol: TCP + port: 6443 +``` + +Patterns can supply these values via `extraValueFiles` in their +`values-hub.yaml` to keep network policy configuration separate from the main +chart values. diff --git a/templates/csi-driver-network-policy.yaml b/templates/csi-driver-network-policy.yaml new file mode 100644 index 0000000..bd2b703 --- /dev/null +++ b/templates/csi-driver-network-policy.yaml @@ -0,0 +1,18 @@ +{{- if and (eq (.Values.defaultDenyNetworkPolicy.enabled | toString) "true") (eq (.Values.networkPolicy.csiDriver.enabled | toString) "true") }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: csi-driver-network-policy + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: spiffe-csi-driver + policyTypes: + - Ingress + - Egress + {{- with .Values.networkPolicy.csiDriver.egress }} + egress: + {{- toYaml . | nindent 2 }} + {{- end }} +{{- end }} diff --git a/templates/default-deny-network-policy.yaml b/templates/default-deny-network-policy.yaml new file mode 100644 index 0000000..6793640 --- /dev/null +++ b/templates/default-deny-network-policy.yaml @@ -0,0 +1,12 @@ +{{- if eq (.Values.defaultDenyNetworkPolicy.enabled | toString) "true" }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-in-namespace-{{ .Release.Namespace }} + namespace: {{ .Release.Namespace }} +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress +{{- end }} diff --git a/templates/oidc-discovery-provider-network-policy.yaml b/templates/oidc-discovery-provider-network-policy.yaml new file mode 100644 index 0000000..1ed668d --- /dev/null +++ b/templates/oidc-discovery-provider-network-policy.yaml @@ -0,0 +1,22 @@ +{{- if and (eq (.Values.defaultDenyNetworkPolicy.enabled | toString) "true") (eq (.Values.networkPolicy.oidcDiscoveryProvider.enabled | toString) "true") }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: oidc-discovery-provider-network-policy + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: spiffe-oidc-discovery-provider + policyTypes: + - Ingress + - Egress + {{- with .Values.networkPolicy.oidcDiscoveryProvider.ingress }} + ingress: + {{- toYaml . | nindent 2 }} + {{- end }} + {{- with .Values.networkPolicy.oidcDiscoveryProvider.egress }} + egress: + {{- toYaml . | nindent 2 }} + {{- end }} +{{- end }} diff --git a/templates/operator-network-policy.yaml b/templates/operator-network-policy.yaml new file mode 100644 index 0000000..7779250 --- /dev/null +++ b/templates/operator-network-policy.yaml @@ -0,0 +1,22 @@ +{{- if and (eq (.Values.defaultDenyNetworkPolicy.enabled | toString) "true") (eq (.Values.networkPolicy.operator.enabled | toString) "true") }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: operator-network-policy + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + name: zero-trust-workload-identity-manager + policyTypes: + - Ingress + - Egress + {{- with .Values.networkPolicy.operator.ingress }} + ingress: + {{- toYaml . | nindent 2 }} + {{- end }} + {{- with .Values.networkPolicy.operator.egress }} + egress: + {{- toYaml . | nindent 2 }} + {{- end }} +{{- end }} diff --git a/templates/spire-server-network-policy.yaml b/templates/spire-server-network-policy.yaml new file mode 100644 index 0000000..cfb17ec --- /dev/null +++ b/templates/spire-server-network-policy.yaml @@ -0,0 +1,22 @@ +{{- if and (eq (.Values.defaultDenyNetworkPolicy.enabled | toString) "true") (eq (.Values.networkPolicy.spireServer.enabled | toString) "true") }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: spire-server-network-policy + namespace: {{ .Release.Namespace }} +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: spire-server + policyTypes: + - Ingress + - Egress + {{- with .Values.networkPolicy.spireServer.ingress }} + ingress: + {{- toYaml . | nindent 2 }} + {{- end }} + {{- with .Values.networkPolicy.spireServer.egress }} + egress: + {{- toYaml . | nindent 2 }} + {{- end }} +{{- end }} diff --git a/values.yaml b/values.yaml index e0901c4..2401819 100644 --- a/values.yaml +++ b/values.yaml @@ -2,6 +2,32 @@ global: localClusterDomain: local.example.com hubClusterDomain: hub.example.com +# -- Default-deny NetworkPolicy for the ZTWIM namespace. +# When enabled, deploys a namespace-wide NetworkPolicy that blocks all ingress and egress +# for pods without an explicit allow policy. Note: spire-agent uses hostNetwork and is +# NOT affected by NetworkPolicies. +defaultDenyNetworkPolicy: + enabled: false + +# -- Per-pod NetworkPolicy rules for SPIRE components and the ZTWIM operator. +# Only effective when defaultDenyNetworkPolicy is enabled. +networkPolicy: + spireServer: + enabled: false + ingress: [] + egress: [] + oidcDiscoveryProvider: + enabled: false + ingress: [] + egress: [] + csiDriver: + enabled: false + egress: [] + operator: + enabled: false + ingress: [] + egress: [] + spiffe: csi: agentSocketPath: "/run/spire/agent-sockets"