Skip to content
Open
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
53 changes: 53 additions & 0 deletions infrastructure/docs/zero-trust.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Zero-trust network access

This baseline implementation brings zero-trust controls to internal service communication for GistPin.

## Objectives

- Enforce mutual TLS between services
- Rotate certificates automatically before expiry
- Base access on workload identity rather than network location
- Emit audit logs for service-to-service access decisions
- Apply policy at the mesh layer for continuous enforcement

## Components

- mTLS policies in [infrastructure/security/zero-trust/mtls-config.yaml](../security/zero-trust/mtls-config.yaml)
- Certificate issuance and rotation in [infrastructure/security/zero-trust/cert-manager.yaml](../security/zero-trust/cert-manager.yaml)

## Implementation notes

### 1. Mutual TLS

The mesh policy in the zero-trust configuration enables strict mTLS for workloads in the `gistpin` namespace. Traffic that does not present a valid client certificate is rejected before reaching the application.

### 2. Certificate rotation

The certificate manifest uses `cert-manager` to issue a workload certificate with a 90-day validity period and a 9-day renewal window. This keeps the service identity material fresh without manual intervention.

### 3. Identity-based access

Authorization policies use SPIFFE-style service identities from Kubernetes service accounts. This allows the platform to permit only approved workloads to access specific APIs.

### 4. Audit logging

Enable access logs and policy audit logs in the service mesh control plane so each allowed and denied request is traceable. Forward these logs to the central observability stack.

### 5. Policy enforcement

Treat the manifests in this folder as the default baseline and add namespace-specific policies as new services are onboarded. Review policies regularly and keep an allow-list approach for internal API access.

## Apply the baseline

```bash
kubectl apply -f infrastructure/security/zero-trust/mtls-config.yaml
kubectl apply -f infrastructure/security/zero-trust/cert-manager.yaml
```

## Operational checklist

- Verify that the mesh control plane is installed and running
- Confirm `cert-manager` is deployed and healthy
- Check that service-to-service traffic is using mTLS
- Review certificate expiry and renewal events in the cluster
- Monitor audit logs for policy violations and unexpected access paths
118 changes: 118 additions & 0 deletions infrastructure/k8s/kyverno/cluster-policies.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Aggregated Kyverno policy bundle for GistPin.
# Apply this after installing the Kyverno controller.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-gistpin-labels
spec:
validationFailureAction: Audit
background: true
rules:
- name: require-app-label
match:
any:
- resources:
kinds:
- Deployment
- StatefulSet
- Job
- CronJob
validate:
message: "The app.kubernetes.io/name label is required."
pattern:
metadata:
labels:
app.kubernetes.io/name: "?*"
- name: require-part-of-label
match:
any:
- resources:
kinds:
- Deployment
- StatefulSet
- Job
- CronJob
validate:
message: "The app.kubernetes.io/part-of label is required."
pattern:
metadata:
labels:
app.kubernetes.io/part-of: "?*"
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: restrict-image-registries
spec:
validationFailureAction: Audit
background: true
rules:
- name: validate-registries
match:
any:
- resources:
kinds:
- Pod
- Deployment
- StatefulSet
- Job
- CronJob
validate:
message: "Only ghcr.io and docker.io images are allowed."
foreach:
- list: "request.object.spec.containers"
deny:
conditions:
any:
- key: "{{ images.containers.{{element}}.registry }}"
operator: NotIn
value:
- ghcr.io
- docker.io
- list: "request.object.spec.initContainers"
deny:
conditions:
any:
- key: "{{ images.initContainers.{{element}}.registry }}"
operator: NotIn
value:
- ghcr.io
- docker.io
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-resource-limits
spec:
validationFailureAction: Audit
background: true
rules:
- name: check-resource-limits
match:
any:
- resources:
kinds:
- Pod
- Deployment
- StatefulSet
- Job
- CronJob
validate:
message: "Every container must define requests and limits for CPU and memory."
foreach:
- list: "request.object.spec.containers"
deny:
conditions:
any:
- key: "{{ element.resources.requests.cpu || '' }}"
operator: Equals
value: ""
- key: "{{ element.resources.requests.memory || '' }}"
operator: Equals
value: ""
- key: "{{ element.resources.limits.cpu || '' }}"
operator: Equals
value: ""
- key: "{{ element.resources.limits.memory || '' }}"
operator: Equals
value: ""
93 changes: 93 additions & 0 deletions infrastructure/k8s/kyverno/install.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
apiVersion: v1
kind: Namespace
metadata:
name: kyverno
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: kyverno
namespace: kyverno
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kyverno-admin
rules:
- apiGroups: [""]
resources: ["events", "configmaps", "secrets", "serviceaccounts"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["apps", "batch", "networking.k8s.io"]
resources: ["deployments", "statefulsets", "jobs", "ingresses"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["kyverno.io"]
resources: ["clusterpolicies", "policies", "clusterpolicyreports", "policyreports"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kyverno-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kyverno-admin
subjects:
- kind: ServiceAccount
name: kyverno
namespace: kyverno
---
apiVersion: v1
kind: ConfigMap
metadata:
name: kyverno-config
namespace: kyverno
labels:
app.kubernetes.io/name: kyverno
app.kubernetes.io/part-of: kyverno
data:
validateFailureAction: Audit
backgroundScan: "true"
webhooksCleanup: "true"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: kyverno
namespace: kyverno
spec:
replicas: 1
selector:
matchLabels:
app: kyverno
template:
metadata:
labels:
app: kyverno
spec:
serviceAccountName: kyverno
containers:
- name: kyverno
image: ghcr.io/kyverno/kyverno:v1.13.0
args:
- --backgroundServiceAccount=kyverno
- --enableConfigMapCaching=false
ports:
- containerPort: 9443
name: https
env:
- name: KYVERNO_NAMESPACE
value: kyverno
---
apiVersion: v1
kind: Service
metadata:
name: kyverno
namespace: kyverno
spec:
selector:
app: kyverno
ports:
- name: https
port: 443
targetPort: 9443
11 changes: 11 additions & 0 deletions infrastructure/k8s/kyverno/policies/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Kyverno policies

This directory holds policy manifests for the GistPin Kyverno baseline.

## Included policies

- require-gistpin-labels: ensures standard workload labels are present
- restrict-image-registries: allows only approved registries
- require-resource-limits: enforces CPU and memory requests/limits

All policies are initially configured with validationFailureAction: Audit so they can be rolled out safely before enforcement is enabled.
44 changes: 44 additions & 0 deletions infrastructure/k8s/kyverno/policies/require-gistpin-labels.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-gistpin-labels
annotations:
policies.kyverno.io/title: Require GistPin labels
policies.kyverno.io/category: Best Practices
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: Enforces ownership and lifecycle labels on workloads.
spec:
validationFailureAction: Audit
background: true
rules:
- name: require-app-label
match:
any:
- resources:
kinds:
- Deployment
- StatefulSet
- Job
- CronJob
validate:
message: "The app.kubernetes.io/name label is required."
pattern:
metadata:
labels:
app.kubernetes.io/name: "?*"
- name: require-part-of-label
match:
any:
- resources:
kinds:
- Deployment
- StatefulSet
- Job
- CronJob
validate:
message: "The app.kubernetes.io/part-of label is required."
pattern:
metadata:
labels:
app.kubernetes.io/part-of: "?*"
43 changes: 43 additions & 0 deletions infrastructure/k8s/kyverno/policies/require-resource-limits.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-resource-limits
annotations:
policies.kyverno.io/title: Require resource limits
policies.kyverno.io/category: Best Practices
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: Ensures workloads define CPU and memory requests and limits.
spec:
validationFailureAction: Audit
background: true
rules:
- name: check-resource-limits
match:
any:
- resources:
kinds:
- Pod
- Deployment
- StatefulSet
- Job
- CronJob
validate:
message: "Every container must define requests and limits for CPU and memory."
foreach:
- list: "request.object.spec.containers"
deny:
conditions:
any:
- key: "{{ element.resources.requests.cpu || '' }}"
operator: Equals
value: ""
- key: "{{ element.resources.requests.memory || '' }}"
operator: Equals
value: ""
- key: "{{ element.resources.limits.cpu || '' }}"
operator: Equals
value: ""
- key: "{{ element.resources.limits.memory || '' }}"
operator: Equals
value: ""
Loading