Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion charts/zero-trust-mesh/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
apiVersion: v2
name: zero-trust-mesh
version: 0.1.3
version: 0.1.4
description: Helm chart for Kubernetes NetworkPolicy + Istio zero-trust service communication
appVersion: "1.0"
type: application
100 changes: 76 additions & 24 deletions charts/zero-trust-mesh/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Enable namespace-wide resources:
```yaml
namespace: default
namespaceResourcesEnabled: true
allowTo: []
allowPolicies: []
```

This creates namespace-scoped defaults:
Expand All @@ -37,48 +37,94 @@ This creates namespace-scoped defaults:

### 2) Service rules

Enable only per-service allow entries (minimal values):
Enable per-service deny-all first, then add explicit allow entries as traffic is validated:

```yaml
workload: frontend
service: frontend
namespaceResourcesEnabled: false
allowTo:
- service: backend
targetPodLabels:
denyAll:
enabled: true
allowPolicies:
- type: ingress
service: gateway
podLabels:
app: gateway
port: 80
- type: ingress
service: ingress-nginx
namespace: ingress-nginx
podLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/component: controller
port: 80
allowUnauthenticated: true
- type: egress
service: backend
podLabels:
app: backend
port: 8080
methods: ["GET", "POST"]
paths: ["/api/*"]
- hosts: ["api.stripe.com"]
- ips: ["192.0.2.10"]
- type: egress
hosts: ["api.stripe.com"]
- type: egress
ips: ["192.0.2.10"]
ports:
- number: 443
protocol: TCP
```

## Values design

`allowTo` is a single list with three entry types:
`denyAll` is a service-scoped deny-all switch:

- `enabled` (optional, default `false`)
- `podLabels` (optional selector override; defaults to `app.kubernetes.io/name: <service>`)

When enabled, it renders:

- a service-scoped NetworkPolicy with both `Ingress` and `Egress` policy types and no allow rules
- a service-scoped Istio AuthorizationPolicy default deny for inbound mesh traffic
- service-scoped DNS and `istiod` egress NetworkPolicy exceptions so injected
sidecars can keep receiving mesh configuration while application traffic stays denied

`allowPolicies` is the preferred typed allow list owned by the current service.

For `type: ingress` entries:

- `service` (required source service name; `workload` is accepted as a legacy alias)
- `namespace` (optional source namespace, defaults to Helm release namespace)
- `podLabels` (optional source pod selector override; defaults to `app.kubernetes.io/name: <service>`)
- `sourceIpBlocks` (optional source CIDR allow list for non-pod sources such as AWS ALB target-type `ip`)
- `serviceAccount` (optional source service account override; defaults to `service`)
- `port` / `protocol` (optional, defaults to `80` / `TCP`)
- `methods` / `paths` (optional Istio operation filters)
- `allowUnauthenticated` (optional, default `false`): omit the Istio source principal match for non-mesh sources such as `ingress-nginx`; keep `podLabels` set so NetworkPolicy still restricts packet sources

For `type: egress` entries, service destinations use `service` as the peer
service name. For service destinations it renders service-scoped egress
`NetworkPolicy` rules only; the destination service must open inbound traffic
with its own ingress allow policy if it has `denyAll.enabled: true`.

`type: egress` supports three destination forms:

- Service rule:
- `service` (required)
- `service` (required; `workload` is accepted as a legacy alias)
- `namespace` (optional, defaults to Helm release namespace)
- `targetPodLabels` (optional target pod selector override for NetworkPolicy and AuthorizationPolicy; defaults to `app.kubernetes.io/name: <service>`)
- `podLabels` (optional target pod selector override for generated egress `NetworkPolicy`; defaults to `app.kubernetes.io/name: <service>`)
- `port` (optional, default `8080`)
- `protocol` (optional, default `TCP`)
- `serviceAccount` (optional target service account override)
- `methods` / `paths` (optional Istio operation filters)
- `serviceAccount` / `methods` / `paths` are only used by the legacy target-side ingress mode
- Host rule:
- `hosts` (list of approved external hosts)
- `ports` (optional list; merged with defaults `80/HTTP` and `443/HTTPS`)
- `paths` can be provided in values for future/egress-gateway routing use, but are not enforced by `ServiceEntry`-only mode
- renders Istio `ServiceEntry` resources and, when service deny-all is enabled, a service-scoped public-IP egress `NetworkPolicy` for the selected ports
- IP rule:
- `ips` (list of approved external destination IPs or CIDR blocks)
- `ports` (optional list; defaults to `443/TCP`)
- single IPv4 addresses are rendered as `/32` CIDRs for `NetworkPolicy` `ipBlock`
- renders both an Istio `ServiceEntry` with `resolution: NONE` and a workload-scoped egress `NetworkPolicy`

Source service account defaults to `workload`, or can be set with top-level `serviceAccount`.
Source service account defaults to `service`, or can be set with top-level `serviceAccount`.

If your cluster does not have an `istio-egressgateway` Service name, set:
- `istio.egressGateway.serviceName` to your real gateway Service
Expand All @@ -91,16 +137,22 @@ Most security defaults are now implicit in templates. Advanced overrides can sti

| Key | Description | Default / Example |
|-----|-------------|-------------------|
| `workload` | Source workload name used for source pod selectors and default source service account | Helm release name |
| `service` | Current service name used for pod selectors and default source service account | Helm release name |
| `workload` | Deprecated alias for `service` | Helm release name |
| `serviceAccount` | Source service account override | `""` |
| `namespaceResourcesEnabled` | Enables namespace-wide default deny, DNS, egress gateway, mTLS, and default-deny AuthorizationPolicy resources | `false` |
| `allowTo` | Service, host, and IP allow rules | `[]` |
| `allowTo[].service` | Destination service rule name | `backend` |
| `allowTo[].targetPodLabels` | Optional target pod selector override for generated NetworkPolicy and AuthorizationPolicy resources | `{ app: backend }` |
| `allowTo[].serviceAccount` | Optional target service account override for AuthorizationPolicy naming | `allowTo[].service` |
| `allowTo[].methods` / `allowTo[].paths` | Optional Istio operation filters | `["GET"]`, `["/api/*"]` |
| `allowTo[].hosts` | Approved external hosts for ServiceEntry-based egress | `["api.stripe.com"]` |
| `allowTo[].ips` | Approved external destination IPs or CIDR blocks for direct IP egress | `["192.0.2.10"]` |
| `denyAll.enabled` | Enables service-scoped deny-all for both inbound and outbound traffic | `false` |
| `denyAll.podLabels` | Optional pod selector override for service-level deny-all resources | Not set; defaults to `app.kubernetes.io/name: <service>` |
| `allowPolicies` | Preferred typed inbound/outbound allow rules owned by the current service | `[]` |
| `allowPolicies[].type` | Policy direction, either `ingress` or `egress` | `ingress` |
| `allowPolicies[].service` | Peer service name for ingress source or egress destination | `backend` |
| `allowPolicies[].workload` | Deprecated alias for `allowPolicies[].service` | `backend` |
| `allowPolicies[].podLabels` | Optional peer pod selector override for generated NetworkPolicy | `{ app: backend }` |
| `allowPolicies[].sourceIpBlocks` | Optional ingress CIDR allow list for non-pod sources such as AWS ALB target-type `ip` | `["172.31.0.0/16"]` |
| `allowPolicies[].serviceAccount` | Optional peer service account override for AuthorizationPolicy principals | `allowPolicies[].service` |
| `allowPolicies[].allowUnauthenticated` | Allow non-mesh inbound sources; NetworkPolicy should still restrict source pods or CIDRs | `false` |
| `allowPolicies[].hosts` | Approved external hosts for ServiceEntry-based egress | `["api.stripe.com"]` |
| `allowPolicies[].ips` | Approved external destination IPs or CIDR blocks for direct IP egress | `["192.0.2.10"]` |

## Install

Expand Down
25 changes: 21 additions & 4 deletions charts/zero-trust-mesh/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ TCP

{{- define "ztm.workloadName" -}}
{{- $svc := .Values.serviceConfig | default (dict) -}}
{{- .Values.workload | default ($svc.workload | default .Release.Name) -}}
{{- default (.Values.workload | default ($svc.workload | default ($svc.service | default .Release.Name))) .Values.service -}}
{{- end -}}

{{- define "ztm.workloadServiceAccount" -}}
Expand All @@ -46,10 +46,27 @@ TCP
{{- end -}}

{{- define "ztm.targetPodLabels" -}}
{{- if .targetPodLabels -}}
{{- toYaml .targetPodLabels -}}
{{- if .podLabels -}}
{{- toYaml .podLabels -}}
{{- else -}}
app.kubernetes.io/name: {{ .service }}
app.kubernetes.io/name: {{ default .workload .service }}
{{- end -}}
{{- end -}}

{{- define "ztm.sourcePodLabels" -}}
{{- if .podLabels -}}
{{- toYaml .podLabels -}}
{{- else -}}
app.kubernetes.io/name: {{ default .workload .service }}
{{- end -}}
{{- end -}}

{{- define "ztm.denyAllPodLabels" -}}
{{- $denyAll := .Values.denyAll | default (dict) -}}
{{- if $denyAll.podLabels -}}
{{- toYaml $denyAll.podLabels -}}
{{- else -}}
app.kubernetes.io/name: {{ include "ztm.workloadName" . }}
{{- end -}}
{{- end -}}

Expand Down
53 changes: 53 additions & 0 deletions charts/zero-trust-mesh/templates/istio-allow-from.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{{- $denyAll := .Values.denyAll | default (dict) -}}
{{- $istio := .Values.istio | default (dict) -}}
{{- $ingress := list -}}
{{- range (.Values.allowPolicies | default (list)) -}}
{{- if eq (default "" .type) "ingress" -}}
{{- $ingress = append $ingress . -}}
{{- end -}}
{{- end -}}
{{- if and (default false $denyAll.enabled) (default true $istio.enabled) $ingress }}
{{- $targetNamespace := include "ztm.workloadNamespace" . -}}
{{- $targetServiceAccount := include "ztm.workloadServiceAccount" . -}}
{{- range $ingress }}
{{- $sourceWorkload := required "ingress[].service is required" (default .workload .service) -}}
{{- $sourceNamespace := default $targetNamespace .namespace -}}
{{- $sourceServiceAccount := default $sourceWorkload .serviceAccount -}}
{{- $policyName := printf "allow-%s-ingress-from-%s" $targetServiceAccount $sourceServiceAccount -}}
---
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: {{ include "ztm.sanitizeName" $policyName }}
namespace: {{ $targetNamespace }}
spec:
selector:
matchLabels:
{{- include "ztm.denyAllPodLabels" $ | nindent 6 }}
action: ALLOW
rules:
{{- if and .allowUnauthenticated (not (or .methods .paths)) }}
- {}
{{- else }}
-
{{- if not .allowUnauthenticated }}
from:
- source:
principals:
- {{ printf "cluster.local/ns/%s/sa/%s" $sourceNamespace $sourceServiceAccount | quote }}
{{- end }}
{{- if or .methods .paths }}
to:
- operation:
{{- if .methods }}
methods:
{{- toYaml .methods | nindent 14 }}
{{- end }}
{{- if .paths }}
paths:
{{- toYaml .paths | nindent 14 }}
{{- end }}
{{- end }}
{{- end }}
{{ end }}
{{- end }}
38 changes: 0 additions & 38 deletions charts/zero-trust-mesh/templates/istio-authorizations.yaml

This file was deleted.

10 changes: 8 additions & 2 deletions charts/zero-trust-mesh/templates/istio-egress-gateway.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@
{{- $egw := $istio.egressGateway | default (dict) -}}
{{- $egwNamespace := default "istio-system" $egw.namespace -}}
{{- $egwServiceName := default "istio-egressgateway" $egw.serviceName -}}
{{- if and (ne $istio.enabled false) (eq $egw.enabled true) .Values.allowTo }}
{{- $egress := list -}}
{{- range (.Values.allowPolicies | default (list)) -}}
{{- if eq (default "" .type) "egress" -}}
{{- $egress = append $egress . -}}
{{- end -}}
{{- end -}}
{{- if and (ne $istio.enabled false) (eq $egw.enabled true) $egress }}
{{- $workloadNamespace := include "ztm.workloadNamespace" . -}}
{{- range .Values.allowTo }}
{{- range $egress }}
{{- if .hosts }}
{{- range .hosts }}
---
Expand Down
15 changes: 15 additions & 0 deletions charts/zero-trust-mesh/templates/istio-service-deny-all.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{{- $denyAll := .Values.denyAll | default (dict) -}}
{{- $istio := .Values.istio | default (dict) -}}
{{- if and (default false $denyAll.enabled) (default true $istio.enabled) }}
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: {{ include "ztm.fullname" . }}-service-deny-all
namespace: {{ include "ztm.workloadNamespace" . }}
labels:
{{- include "ztm.labels" . | nindent 4 }}
spec:
selector:
matchLabels:
{{- include "ztm.denyAllPodLabels" . | nindent 6 }}
{{- end }}
12 changes: 9 additions & 3 deletions charts/zero-trust-mesh/templates/istio-serviceentries.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
{{- $istio := .Values.istio | default (dict) -}}
{{- if and (default true $istio.enabled) .Values.allowTo }}
{{- $egress := list -}}
{{- range (.Values.allowPolicies | default (list)) -}}
{{- if eq (default "" .type) "egress" -}}
{{- $egress = append $egress . -}}
{{- end -}}
{{- end -}}
{{- if and (default true $istio.enabled) $egress }}
{{- $workloadNamespace := include "ztm.workloadNamespace" . -}}
{{- range .Values.allowTo }}
{{- range $egress }}
{{- if .hosts }}
{{- $defaultPorts := list (dict "number" 80 "protocol" "HTTP") (dict "number" 443 "protocol" "HTTPS") -}}
{{- $userPorts := .ports | default (list) -}}
Expand Down Expand Up @@ -75,7 +81,7 @@ spec:
resolution: NONE
ports:
{{- range $port := $ports }}
{{- $number := required "allowTo[].ips ports[].number is required" $port.number }}
{{- $number := required "egress[].ips ports[].number is required" $port.number }}
{{- $protocol := default "TCP" $port.protocol | upper }}
- number: {{ $number }}
name: {{ default (include "ztm.sanitizeName" (printf "%s-%v" ($protocol | lower) $number)) $port.name }}
Expand Down
48 changes: 48 additions & 0 deletions charts/zero-trust-mesh/templates/networkpolicy-allow-from.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{{- $denyAll := .Values.denyAll | default (dict) -}}
{{- $np := .Values.networkPolicy | default (dict) -}}
{{- $labels := .Values.labels | default (dict) -}}
{{- $ingress := list -}}
{{- range (.Values.allowPolicies | default (list)) -}}
{{- if eq (default "" .type) "ingress" -}}
{{- $ingress = append $ingress . -}}
{{- end -}}
{{- end -}}
{{- if and (default false $denyAll.enabled) (default true $np.enabled) $ingress }}
{{- $targetNamespace := include "ztm.workloadNamespace" . -}}
{{- $targetWorkload := include "ztm.workloadName" . -}}
{{- range $ingress }}
{{- $sourceWorkload := required "ingress[].service is required" (default .workload .service) -}}
{{- $sourceNamespace := default $targetNamespace .namespace -}}
{{- $ruleName := printf "%s-ingress-from-%s-%v" $targetWorkload $sourceWorkload (.port | default 80) -}}
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-{{ include "ztm.sanitizeName" $ruleName }}
namespace: {{ $targetNamespace }}
spec:
podSelector:
matchLabels:
{{- include "ztm.denyAllPodLabels" $ | nindent 6 }}
policyTypes:
- Ingress
ingress:
- from:
{{- if .sourceIpBlocks }}
{{- range .sourceIpBlocks }}
- ipBlock:
cidr: {{ . | quote }}
{{- end }}
{{- else }}
- namespaceSelector:
matchLabels:
{{ default "kubernetes.io/metadata.name" $labels.namespaceLabelKey }}: {{ $sourceNamespace }}
podSelector:
matchLabels:
{{- include "ztm.sourcePodLabels" . | nindent 14 }}
{{- end }}
ports:
- port: {{ .port | default 80 }}
protocol: {{ .protocol | default "TCP" }}
{{ end }}
{{- end }}
Loading
Loading