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
20 changes: 20 additions & 0 deletions infrastructure/monitoring/grafana/loki-datasource.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-loki-datasource
namespace: monitoring
labels:
grafana_datasource: "1"
data:
loki-datasource.yaml: |
apiVersion: 1
datasources:
- name: Loki
uid: loki
type: loki
access: proxy
url: http://loki:3100
isDefault: false
editable: true
jsonData:
maxLines: 1000
110 changes: 110 additions & 0 deletions infrastructure/monitoring/loki-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: loki-config
namespace: monitoring
data:
loki.yaml: |
auth_enabled: false

server:
http_listen_port: 3100

common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory

schema_config:
configs:
- from: 2024-01-01
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h

storage_config:
filesystem:
directory: /loki/chunks

compactor:
working_directory: /loki/boltdb-shipper-compactor
shared_store: filesystem
retention_enabled: true

limits_config:
retention_period: 168h
ingestion_rate_mb: 16
ingestion_burst_size_mb: 32
reject_old_samples: true
reject_old_samples_max_age: 168h

chunk_store_config:
max_look_back_period: 168h

table_manager:
retention_deletes_enabled: true
retention_period: 168h
---
apiVersion: v1
kind: Service
metadata:
name: loki
namespace: monitoring
spec:
selector:
app: loki
ports:
- name: http
port: 3100
targetPort: 3100
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: loki
namespace: monitoring
spec:
serviceName: loki
replicas: 1
selector:
matchLabels:
app: loki
template:
metadata:
labels:
app: loki
spec:
containers:
- name: loki
image: grafana/loki:3.0.0
args:
- -config.file=/etc/loki/loki.yaml
ports:
- containerPort: 3100
name: http
volumeMounts:
- name: config
mountPath: /etc/loki
- name: storage
mountPath: /loki
volumes:
- name: config
configMap:
name: loki-config
volumeClaimTemplates:
- metadata:
name: storage
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
114 changes: 114 additions & 0 deletions infrastructure/monitoring/promtail-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: promtail-config
namespace: monitoring
data:
promtail.yaml: |
server:
http_listen_port: 9080
grpc_listen_port: 0

positions:
filename: /tmp/positions.yaml

clients:
- url: http://loki:3100/loki/api/v1/push

scrape_configs:
- job_name: kubernetes-pods
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_namespace]
target_label: namespace
- source_labels: [__meta_kubernetes_pod_name]
target_label: pod
- source_labels: [__meta_kubernetes_pod_container_name]
target_label: container
- source_labels: [__meta_kubernetes_pod_label_app]
target_label: app
- source_labels: [__meta_kubernetes_pod_node_name]
target_label: node
- action: replace
source_labels: [__meta_kubernetes_namespace]
target_label: kubernetes_namespace
- action: replace
source_labels: [__meta_kubernetes_pod_name]
target_label: kubernetes_pod
pipeline_stages:
- cri: {}
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: promtail
namespace: monitoring
spec:
selector:
matchLabels:
app: promtail
template:
metadata:
labels:
app: promtail
spec:
serviceAccountName: promtail
containers:
- name: promtail
image: grafana/promtail:3.0.0
args:
- -config.file=/etc/promtail/promtail.yaml
volumeMounts:
- name: config
mountPath: /etc/promtail
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
- name: positions
mountPath: /tmp
volumes:
- name: config
configMap:
name: promtail-config
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
- name: positions
emptyDir: {}
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: promtail
namespace: monitoring
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: promtail
rules:
- apiGroups: [""]
resources:
- pods
- nodes
- namespaces
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: promtail
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: promtail
subjects:
- kind: ServiceAccount
name: promtail
namespace: monitoring
41 changes: 41 additions & 0 deletions infrastructure/security/zero-trust/cert-manager.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: gistpin-mtls-issuer
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: gistpin-mtls-cert
namespace: gistpin
spec:
secretName: gistpin-mtls-tls
duration: 2160h # 90 days
renewBefore: 216h # 9 days
commonName: "*.gistpin.svc.cluster.local"
dnsNames:
- "*.gistpin.svc.cluster.local"
issuerRef:
name: gistpin-mtls-issuer
kind: ClusterIssuer
privateKey:
algorithm: ECDSA
size: 256
usages:
- digital signature
- key encipherment
- server auth
- client auth
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: cert-manager-webhook
namespace: cert-manager
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/name: webhook
45 changes: 45 additions & 0 deletions infrastructure/security/zero-trust/mtls-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default-strict-mtls
namespace: gistpin
spec:
selector:
matchLabels:
app.kubernetes.io/part-of: gistpin
mtls:
mode: STRICT
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: gistpin-mtls
namespace: gistpin
spec:
host: "*.gistpin.svc.cluster.local"
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
loadBalancer:
simple: ROUND_ROBIN
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: backend-api-allow
namespace: gistpin
spec:
selector:
matchLabels:
app: backend
action: ALLOW
rules:
- from:
- source:
principals:
- cluster.local/ns/gistpin/sa/frontend
- cluster.local/ns/gistpin/sa/worker
to:
- operation:
methods: ["GET", "POST"]
paths: ["/api/*"]