@intentius/chant-lexicon-k8s 0.0.14 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/integrity.json +8 -4
- package/dist/manifest.json +1 -1
- package/dist/rules/latest-image-tag.ts +121 -0
- package/dist/rules/missing-resource-limits.ts +111 -0
- package/dist/rules/wk8204.ts +33 -1
- package/dist/rules/wk8304.ts +70 -0
- package/dist/rules/wk8305.ts +115 -0
- package/dist/rules/wk8306.ts +50 -0
- package/package.json +27 -24
- package/src/codegen/docs.ts +1 -1
- package/src/composites/adot-collector.ts +8 -2
- package/src/composites/agic-ingress.ts +148 -0
- package/src/composites/aks-external-dns-agent.ts +199 -0
- package/src/composites/alb-ingress.ts +2 -1
- package/src/composites/autoscaled-service.ts +25 -7
- package/src/composites/azure-disk-storage-class.ts +82 -0
- package/src/composites/azure-file-storage-class.ts +77 -0
- package/src/composites/azure-monitor-collector.ts +232 -0
- package/src/composites/batch-job.ts +36 -3
- package/src/composites/composites.test.ts +1060 -0
- package/src/composites/config-connector-context.ts +62 -0
- package/src/composites/configured-app.ts +6 -0
- package/src/composites/cron-workload.ts +6 -0
- package/src/composites/ebs-storage-class.ts +4 -4
- package/src/composites/external-dns-agent.ts +6 -0
- package/src/composites/filestore-storage-class.ts +79 -0
- package/src/composites/fluent-bit-agent.ts +5 -0
- package/src/composites/gce-ingress.ts +143 -0
- package/src/composites/gce-pd-storage-class.ts +85 -0
- package/src/composites/gke-external-dns-agent.ts +175 -0
- package/src/composites/gke-fluent-bit-agent.ts +219 -0
- package/src/composites/gke-gateway.ts +143 -0
- package/src/composites/gke-otel-collector.ts +229 -0
- package/src/composites/index.ts +31 -0
- package/src/composites/metrics-server.ts +1 -1
- package/src/composites/monitored-service.ts +6 -0
- package/src/composites/network-isolated-app.ts +6 -0
- package/src/composites/node-agent.ts +6 -0
- package/src/composites/security-context.ts +10 -0
- package/src/composites/sidecar-app.ts +6 -0
- package/src/composites/stateful-app.ts +4 -7
- package/src/composites/web-app.ts +4 -7
- package/src/composites/worker-pool.ts +4 -7
- package/src/composites/workload-identity-sa.ts +118 -0
- package/src/composites/workload-identity-service-account.ts +116 -0
- package/src/index.ts +20 -1
- package/src/lint/post-synth/post-synth.test.ts +362 -1
- package/src/lint/post-synth/wk8204.ts +33 -1
- package/src/lint/post-synth/wk8304.ts +70 -0
- package/src/lint/post-synth/wk8305.ts +115 -0
- package/src/lint/post-synth/wk8306.ts +50 -0
- package/src/lint/rules/latest-image-tag.ts +121 -0
- package/src/lint/rules/missing-resource-limits.ts +111 -0
- package/src/lint/rules/rules.test.ts +192 -0
- package/src/plugin.test.ts +2 -2
- package/src/plugin.ts +129 -209
- package/src/serializer.test.ts +120 -0
- package/src/serializer.ts +16 -4
- package/src/skills/chant-k8s-aks.md +146 -0
- package/src/skills/chant-k8s-gke.md +191 -0
- package/src/skills/kubernetes-patterns.md +183 -0
- package/src/skills/kubernetes-security.md +237 -0
- /package/{dist → src}/skills/chant-k8s-eks.md +0 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: chant-k8s-gke
|
|
3
|
+
description: GKE-specific Kubernetes patterns and composites
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# GKE Kubernetes Patterns
|
|
8
|
+
|
|
9
|
+
## GKE Composites Overview
|
|
10
|
+
|
|
11
|
+
These composites produce K8s YAML with GKE-specific annotations and configurations.
|
|
12
|
+
|
|
13
|
+
### WorkloadIdentityServiceAccount — ServiceAccount with GCP SA annotation
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { WorkloadIdentityServiceAccount } from "@intentius/chant-lexicon-k8s";
|
|
17
|
+
|
|
18
|
+
const { serviceAccount, role, roleBinding } = WorkloadIdentityServiceAccount({
|
|
19
|
+
name: "app-sa",
|
|
20
|
+
gcpServiceAccountEmail: "app@my-project.iam.gserviceaccount.com",
|
|
21
|
+
rbacRules: [
|
|
22
|
+
{ apiGroups: [""], resources: ["secrets"], verbs: ["get"] },
|
|
23
|
+
],
|
|
24
|
+
namespace: "prod",
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Annotates the ServiceAccount with `iam.gke.io/gcp-service-account` for GKE Workload Identity.
|
|
29
|
+
|
|
30
|
+
### GceIngress — Ingress with GCE ingress class annotations
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { GceIngress } from "@intentius/chant-lexicon-k8s";
|
|
34
|
+
|
|
35
|
+
const { ingress } = GceIngress({
|
|
36
|
+
name: "api-ingress",
|
|
37
|
+
hosts: [
|
|
38
|
+
{
|
|
39
|
+
hostname: "api.example.com",
|
|
40
|
+
paths: [{ path: "/", serviceName: "api", servicePort: 80 }],
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
staticIpName: "api-ip",
|
|
44
|
+
managedCertificate: "api-cert",
|
|
45
|
+
namespace: "prod",
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Features:
|
|
50
|
+
- Sets `kubernetes.io/ingress.class: "gce"` annotation
|
|
51
|
+
- `staticIpName` binds a reserved global static IP via `kubernetes.io/ingress.global-static-ip-name`
|
|
52
|
+
- `managedCertificate` attaches a GKE-managed SSL certificate via `networking.gke.io/managed-certificates`
|
|
53
|
+
- Auto-generates FrontendConfig for SSL redirect when `managedCertificate` is set (override with `sslRedirect: false`)
|
|
54
|
+
- Pairs naturally with Config Connector `ComputeAddress` resources for static IPs
|
|
55
|
+
|
|
56
|
+
### GkeGateway — Gateway API with GKE gateway classes
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { GkeGateway } from "@intentius/chant-lexicon-k8s";
|
|
60
|
+
|
|
61
|
+
const { gateway, httpRoute } = GkeGateway({
|
|
62
|
+
name: "api-gateway",
|
|
63
|
+
gatewayClassName: "gke-l7-global-external-managed",
|
|
64
|
+
hosts: [
|
|
65
|
+
{
|
|
66
|
+
hostname: "api.example.com",
|
|
67
|
+
paths: [{ path: "/", serviceName: "api", servicePort: 80 }],
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
certificateName: "api-cert",
|
|
71
|
+
namespace: "prod",
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Gateway class options:
|
|
76
|
+
- `gke-l7-global-external-managed` — Global external (default)
|
|
77
|
+
- `gke-l7-regional-external-managed` — Regional external
|
|
78
|
+
- `gke-l7-rilb` — Regional internal
|
|
79
|
+
|
|
80
|
+
### GcePdStorageClass — StorageClass for GCE Persistent Disk CSI
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { GcePdStorageClass } from "@intentius/chant-lexicon-k8s";
|
|
84
|
+
|
|
85
|
+
const { storageClass } = GcePdStorageClass({
|
|
86
|
+
name: "pd-balanced",
|
|
87
|
+
type: "pd-balanced",
|
|
88
|
+
replicationType: "none",
|
|
89
|
+
allowVolumeExpansion: true,
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Disk types: `pd-standard`, `pd-ssd`, `pd-balanced` (default), `pd-extreme`.
|
|
94
|
+
|
|
95
|
+
### FilestoreStorageClass — StorageClass for Filestore CSI (ReadWriteMany)
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { FilestoreStorageClass } from "@intentius/chant-lexicon-k8s";
|
|
99
|
+
|
|
100
|
+
const { storageClass } = FilestoreStorageClass({
|
|
101
|
+
name: "filestore-shared",
|
|
102
|
+
tier: "standard",
|
|
103
|
+
network: "my-vpc",
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Use Filestore when you need ReadWriteMany (shared across pods/nodes). Use GCE PD for ReadWriteOnce (single pod).
|
|
108
|
+
|
|
109
|
+
### GkeExternalDnsAgent — ExternalDNS for Cloud DNS
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { GkeExternalDnsAgent } from "@intentius/chant-lexicon-k8s";
|
|
113
|
+
|
|
114
|
+
const result = GkeExternalDnsAgent({
|
|
115
|
+
gcpServiceAccountEmail: "dns@my-project.iam.gserviceaccount.com",
|
|
116
|
+
gcpProjectId: "my-project",
|
|
117
|
+
domainFilters: ["example.com"],
|
|
118
|
+
txtOwnerId: "my-cluster",
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### GkeFluentBitAgent — DaemonSet for Cloud Logging
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { GkeFluentBitAgent } from "@intentius/chant-lexicon-k8s";
|
|
126
|
+
|
|
127
|
+
const result = GkeFluentBitAgent({
|
|
128
|
+
clusterName: "my-cluster",
|
|
129
|
+
projectId: "my-project",
|
|
130
|
+
gcpServiceAccountEmail: "logging@my-project.iam.gserviceaccount.com",
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### GkeOtelCollector — OTel for Cloud Trace + Cloud Monitoring
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { GkeOtelCollector } from "@intentius/chant-lexicon-k8s";
|
|
138
|
+
|
|
139
|
+
const result = GkeOtelCollector({
|
|
140
|
+
clusterName: "my-cluster",
|
|
141
|
+
projectId: "my-project",
|
|
142
|
+
gcpServiceAccountEmail: "monitoring@my-project.iam.gserviceaccount.com",
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### ConfigConnectorContext — Config Connector namespace bootstrap
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import { ConfigConnectorContext } from "@intentius/chant-lexicon-k8s";
|
|
150
|
+
|
|
151
|
+
const { context } = ConfigConnectorContext({
|
|
152
|
+
googleServiceAccountEmail: "cc-sa@my-project.iam.gserviceaccount.com",
|
|
153
|
+
namespace: "config-connector",
|
|
154
|
+
stateIntoSpec: "absent",
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Required when using Config Connector to manage GCP resources from within the cluster.
|
|
159
|
+
|
|
160
|
+
## Workload Identity vs Key-Based Auth
|
|
161
|
+
|
|
162
|
+
| Feature | Workload Identity | Key-based (JSON key file) |
|
|
163
|
+
|---------|------------------|--------------------------|
|
|
164
|
+
| K8s annotation needed | Yes (`iam.gke.io/gcp-service-account`) | No |
|
|
165
|
+
| Composite available | **WorkloadIdentityServiceAccount** | None needed (mount key as Secret) |
|
|
166
|
+
| Setup | GKE cluster WI enabled + IAM binding | Create key → K8s Secret → volume mount |
|
|
167
|
+
| Security | No long-lived credentials, auto-rotated | Static key, must rotate manually |
|
|
168
|
+
| When to use | Always (recommended) | Legacy workloads, non-GKE clusters |
|
|
169
|
+
|
|
170
|
+
Workload Identity is the recommended approach for all GKE workloads. Key-based auth requires no K8s-side composite — create a Secret from the JSON key and mount it.
|
|
171
|
+
|
|
172
|
+
## Config Connector Considerations
|
|
173
|
+
|
|
174
|
+
Config Connector (CC) runs as a GKE add-on and manages GCP resources declaratively via K8s CRDs:
|
|
175
|
+
- **Bootstrap cluster required** — CC needs an existing GKE cluster to run in; use `npm run bootstrap` to create one
|
|
176
|
+
- **CC service account** — a GCP SA with editor/IAM roles, bound to the CC controller pod via Workload Identity
|
|
177
|
+
- **Reconciliation** — CC continuously reconciles; deleting a CC resource deletes the underlying GCP resource
|
|
178
|
+
- **ConfigConnectorContext** — use the composite to configure CC per-namespace (SA email, stateIntoSpec policy)
|
|
179
|
+
|
|
180
|
+
## GKE Add-ons
|
|
181
|
+
|
|
182
|
+
Common add-ons managed via GKE (not K8s manifests):
|
|
183
|
+
- **Config Connector** — manage GCP resources as K8s CRDs
|
|
184
|
+
- **Workload Identity** — pod-to-GCP-SA identity federation (required for WorkloadIdentityServiceAccount)
|
|
185
|
+
- **GKE Gateway Controller** — Gateway API implementation (required for GkeGateway)
|
|
186
|
+
- **GKE managed Prometheus** — alternative to GkeOtelCollector for metrics
|
|
187
|
+
- **GKE Dataplane V2** — eBPF-based networking with built-in NetworkPolicy enforcement
|
|
188
|
+
- **Filestore CSI driver** — required for FilestoreStorageClass
|
|
189
|
+
- **Compute Engine persistent disk CSI driver** — enabled by default, required for GcePdStorageClass
|
|
190
|
+
|
|
191
|
+
Configure add-ons via the GCP lexicon (`@intentius/chant-lexicon-gcp`) Config Connector resources.
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: kubernetes-patterns
|
|
3
|
+
description: Kubernetes deployment strategies, stateful workloads, RBAC, and networking patterns
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Kubernetes Infrastructure Patterns
|
|
8
|
+
|
|
9
|
+
## Deployment Strategies
|
|
10
|
+
|
|
11
|
+
### Rolling Update (default)
|
|
12
|
+
|
|
13
|
+
Gradually replaces pods with zero downtime. Configure surge and unavailability:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { WebApp } from "@intentius/chant-lexicon-k8s";
|
|
17
|
+
|
|
18
|
+
const { deployment, service } = WebApp({
|
|
19
|
+
name: "api",
|
|
20
|
+
image: "api:2.0",
|
|
21
|
+
replicas: 4,
|
|
22
|
+
strategy: {
|
|
23
|
+
type: "RollingUpdate",
|
|
24
|
+
rollingUpdate: { maxSurge: "25%", maxUnavailable: "25%" },
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Blue/Green Deployment
|
|
30
|
+
|
|
31
|
+
Run two full deployments and switch traffic by updating the Service selector:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { WebApp } from "@intentius/chant-lexicon-k8s";
|
|
35
|
+
|
|
36
|
+
const blue = WebApp({ name: "app-blue", image: "app:1.0", replicas: 3 });
|
|
37
|
+
const green = WebApp({ name: "app-green", image: "app:2.0", replicas: 3 });
|
|
38
|
+
|
|
39
|
+
// Switch traffic: update the Service selector to point at green
|
|
40
|
+
// kubectl patch svc app -p '{"spec":{"selector":{"version":"green"}}}'
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Canary Deployment
|
|
44
|
+
|
|
45
|
+
Deploy a small replica set alongside the main deployment to test new versions:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { AutoscaledService, WebApp } from "@intentius/chant-lexicon-k8s";
|
|
49
|
+
|
|
50
|
+
const main = AutoscaledService({
|
|
51
|
+
name: "app",
|
|
52
|
+
image: "app:1.0",
|
|
53
|
+
port: 8080,
|
|
54
|
+
minReplicas: 9,
|
|
55
|
+
maxReplicas: 20,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const canary = WebApp({
|
|
59
|
+
name: "app-canary",
|
|
60
|
+
image: "app:2.0",
|
|
61
|
+
replicas: 1,
|
|
62
|
+
labels: { track: "canary" },
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Both share the same `app.kubernetes.io/name` label so the Service routes traffic to both.
|
|
67
|
+
|
|
68
|
+
## Stateful Workloads
|
|
69
|
+
|
|
70
|
+
### StatefulSet with Persistent Storage
|
|
71
|
+
|
|
72
|
+
Use `StatefulApp` for databases, caches, and other stateful services:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { StatefulApp } from "@intentius/chant-lexicon-k8s";
|
|
76
|
+
|
|
77
|
+
const { statefulSet, service } = StatefulApp({
|
|
78
|
+
name: "postgres",
|
|
79
|
+
image: "postgres:16",
|
|
80
|
+
port: 5432,
|
|
81
|
+
replicas: 3,
|
|
82
|
+
storageSize: "50Gi",
|
|
83
|
+
storageClass: "gp3-encrypted",
|
|
84
|
+
env: [
|
|
85
|
+
{ name: "POSTGRES_DB", value: "app" },
|
|
86
|
+
{ name: "POSTGRES_PASSWORD", valueFrom: { secretKeyRef: { name: "pg-creds", key: "password" } } },
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Headless Service for StatefulSet Discovery
|
|
92
|
+
|
|
93
|
+
`StatefulApp` creates a headless Service (`clusterIP: None`) automatically. Pods are addressable as `postgres-0.postgres.namespace.svc.cluster.local`.
|
|
94
|
+
|
|
95
|
+
### PersistentVolumeClaim Retention
|
|
96
|
+
|
|
97
|
+
StatefulSet PVCs persist after pod deletion by default. To clean up:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
kubectl delete pvc -l app.kubernetes.io/name=postgres
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## RBAC Patterns
|
|
104
|
+
|
|
105
|
+
### Least-Privilege ServiceAccount
|
|
106
|
+
|
|
107
|
+
Use `WorkerPool` or `BatchJob` composites which create scoped RBAC automatically:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { WorkerPool } from "@intentius/chant-lexicon-k8s";
|
|
111
|
+
|
|
112
|
+
const { deployment, serviceAccount, role, roleBinding } = WorkerPool({
|
|
113
|
+
name: "queue-worker",
|
|
114
|
+
image: "worker:1.0",
|
|
115
|
+
replicas: 3,
|
|
116
|
+
rbacRules: [
|
|
117
|
+
{ apiGroups: [""], resources: ["configmaps"], verbs: ["get", "list"] },
|
|
118
|
+
{ apiGroups: ["batch"], resources: ["jobs"], verbs: ["create"] },
|
|
119
|
+
],
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Namespace-Scoped vs Cluster-Scoped
|
|
124
|
+
|
|
125
|
+
- Use `Role` + `RoleBinding` for namespace-scoped permissions (default in composites)
|
|
126
|
+
- Use `ClusterRole` + `ClusterRoleBinding` for cross-namespace access (node agents, monitoring)
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { NodeAgent } from "@intentius/chant-lexicon-k8s";
|
|
130
|
+
|
|
131
|
+
const { daemonSet, serviceAccount, clusterRole, clusterRoleBinding } = NodeAgent({
|
|
132
|
+
name: "log-agent",
|
|
133
|
+
image: "fluent-bit:2.2",
|
|
134
|
+
rbacRules: [
|
|
135
|
+
{ apiGroups: [""], resources: ["pods", "namespaces"], verbs: ["get", "list", "watch"] },
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Networking Patterns
|
|
141
|
+
|
|
142
|
+
### Default-Deny with Selective Allow
|
|
143
|
+
|
|
144
|
+
Use `NamespaceEnv` for namespace-level isolation, then `NetworkIsolatedApp` for per-app rules:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { NamespaceEnv, NetworkIsolatedApp } from "@intentius/chant-lexicon-k8s";
|
|
148
|
+
|
|
149
|
+
const ns = NamespaceEnv({
|
|
150
|
+
name: "prod",
|
|
151
|
+
defaultDenyIngress: true,
|
|
152
|
+
defaultDenyEgress: true,
|
|
153
|
+
cpuLimit: "8",
|
|
154
|
+
memoryLimit: "16Gi",
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const app = NetworkIsolatedApp({
|
|
158
|
+
name: "api",
|
|
159
|
+
image: "api:1.0",
|
|
160
|
+
port: 8080,
|
|
161
|
+
namespace: "prod",
|
|
162
|
+
allowIngressFrom: [
|
|
163
|
+
{ podSelector: { "app.kubernetes.io/name": "gateway" } },
|
|
164
|
+
],
|
|
165
|
+
allowEgressTo: [
|
|
166
|
+
{ podSelector: { "app.kubernetes.io/name": "postgres" }, ports: [{ port: 5432 }] },
|
|
167
|
+
],
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Service Mesh (Istio/Linkerd)
|
|
172
|
+
|
|
173
|
+
For mTLS between services, use a sidecar-injected namespace:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
kubectl label namespace prod istio-injection=enabled
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Then deploy with standard composites. The mesh proxy is injected automatically.
|
|
180
|
+
|
|
181
|
+
### DNS-Based Service Discovery
|
|
182
|
+
|
|
183
|
+
Services are discoverable at `<service>.<namespace>.svc.cluster.local`. For headless services (StatefulSets), individual pods are at `<pod>.<service>.<namespace>.svc.cluster.local`.
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: kubernetes-security
|
|
3
|
+
description: Kubernetes pod security, image scanning, network policies, and secrets management
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Kubernetes Security Patterns
|
|
8
|
+
|
|
9
|
+
## Pod Security
|
|
10
|
+
|
|
11
|
+
### Security Context (container-level)
|
|
12
|
+
|
|
13
|
+
All Deployment-based composites accept `securityContext` for hardened containers:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { WebApp } from "@intentius/chant-lexicon-k8s";
|
|
17
|
+
|
|
18
|
+
const { deployment, service } = WebApp({
|
|
19
|
+
name: "api",
|
|
20
|
+
image: "api:1.0",
|
|
21
|
+
port: 8080,
|
|
22
|
+
securityContext: {
|
|
23
|
+
runAsNonRoot: true,
|
|
24
|
+
runAsUser: 1000,
|
|
25
|
+
readOnlyRootFilesystem: true,
|
|
26
|
+
allowPrivilegeEscalation: false,
|
|
27
|
+
capabilities: { drop: ["ALL"] },
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Pod Security Standards
|
|
33
|
+
|
|
34
|
+
Kubernetes enforces three levels via Pod Security Admission:
|
|
35
|
+
|
|
36
|
+
| Level | What it blocks | When to use |
|
|
37
|
+
|-------|---------------|-------------|
|
|
38
|
+
| `privileged` | Nothing | System namespaces only |
|
|
39
|
+
| `baseline` | hostNetwork, hostPID, privileged containers | Development |
|
|
40
|
+
| `restricted` | Non-root, no capabilities, read-only root FS | Production |
|
|
41
|
+
|
|
42
|
+
Apply to a namespace:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
kubectl label namespace prod pod-security.kubernetes.io/enforce=restricted
|
|
46
|
+
kubectl label namespace prod pod-security.kubernetes.io/warn=restricted
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Post-Synth Security Checks
|
|
50
|
+
|
|
51
|
+
chant catches security issues at build time:
|
|
52
|
+
|
|
53
|
+
| Check | What it detects |
|
|
54
|
+
|-------|----------------|
|
|
55
|
+
| WK8005 | Secrets exposed in environment variables |
|
|
56
|
+
| WK8006 | `latest` image tags (non-deterministic) |
|
|
57
|
+
| WK8041 | API keys or tokens in plain text |
|
|
58
|
+
| WK8042 | Hardcoded passwords in container env |
|
|
59
|
+
| WK8201 | Missing resource limits (CPU/memory) |
|
|
60
|
+
| WK8202 | Privileged containers |
|
|
61
|
+
| WK8203 | Host namespace sharing (hostPID/hostNetwork) |
|
|
62
|
+
| WK8204 | Writable root filesystem |
|
|
63
|
+
| WK8205 | Containers running as root |
|
|
64
|
+
|
|
65
|
+
## Image Security
|
|
66
|
+
|
|
67
|
+
### Pin Image Digests
|
|
68
|
+
|
|
69
|
+
Use image digests instead of tags for immutability:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const { deployment } = WebApp({
|
|
73
|
+
name: "api",
|
|
74
|
+
image: "api@sha256:abc123def456...",
|
|
75
|
+
port: 8080,
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Private Registry with imagePullSecrets
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { Deployment, Secret } from "@intentius/chant-lexicon-k8s";
|
|
83
|
+
|
|
84
|
+
export const registryCreds = new Secret({
|
|
85
|
+
metadata: { name: "registry-creds" },
|
|
86
|
+
type: "kubernetes.io/dockerconfigjson",
|
|
87
|
+
data: { ".dockerconfigjson": "${DOCKER_CONFIG_JSON}" },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export const deployment = new Deployment({
|
|
91
|
+
spec: {
|
|
92
|
+
template: {
|
|
93
|
+
spec: {
|
|
94
|
+
imagePullSecrets: [{ name: "registry-creds" }],
|
|
95
|
+
containers: [{ name: "app", image: "private.registry.io/app:1.0" }],
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Image Policy
|
|
103
|
+
|
|
104
|
+
Block unsigned or unscanned images with admission controllers:
|
|
105
|
+
- **Kyverno**: policy-based, Kubernetes-native
|
|
106
|
+
- **OPA/Gatekeeper**: Rego-based policies
|
|
107
|
+
- **Sigstore/Cosign**: image signature verification
|
|
108
|
+
|
|
109
|
+
## Network Policies
|
|
110
|
+
|
|
111
|
+
### Default Deny All
|
|
112
|
+
|
|
113
|
+
Start with deny-all and add explicit allows:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { NamespaceEnv } from "@intentius/chant-lexicon-k8s";
|
|
117
|
+
|
|
118
|
+
const ns = NamespaceEnv({
|
|
119
|
+
name: "prod",
|
|
120
|
+
defaultDenyIngress: true,
|
|
121
|
+
defaultDenyEgress: true,
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Allow Specific Traffic
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { NetworkIsolatedApp } from "@intentius/chant-lexicon-k8s";
|
|
129
|
+
|
|
130
|
+
const app = NetworkIsolatedApp({
|
|
131
|
+
name: "api",
|
|
132
|
+
image: "api:1.0",
|
|
133
|
+
port: 8080,
|
|
134
|
+
namespace: "prod",
|
|
135
|
+
allowIngressFrom: [
|
|
136
|
+
{ podSelector: { "app.kubernetes.io/name": "gateway" } },
|
|
137
|
+
{ namespaceSelector: { "kubernetes.io/metadata.name": "monitoring" } },
|
|
138
|
+
],
|
|
139
|
+
allowEgressTo: [
|
|
140
|
+
{ podSelector: { "app.kubernetes.io/name": "postgres" }, ports: [{ port: 5432 }] },
|
|
141
|
+
{ ipBlock: { cidr: "10.0.0.0/8" }, ports: [{ port: 443 }] },
|
|
142
|
+
],
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Allow DNS Egress
|
|
147
|
+
|
|
148
|
+
Most pods need DNS. Always allow egress to kube-dns:
|
|
149
|
+
|
|
150
|
+
```yaml
|
|
151
|
+
egress:
|
|
152
|
+
- to:
|
|
153
|
+
- namespaceSelector:
|
|
154
|
+
matchLabels:
|
|
155
|
+
kubernetes.io/metadata.name: kube-system
|
|
156
|
+
ports:
|
|
157
|
+
- port: 53
|
|
158
|
+
protocol: UDP
|
|
159
|
+
- port: 53
|
|
160
|
+
protocol: TCP
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Secrets Management
|
|
164
|
+
|
|
165
|
+
### External Secrets Operator
|
|
166
|
+
|
|
167
|
+
Sync secrets from external providers (AWS Secrets Manager, Vault, etc.):
|
|
168
|
+
|
|
169
|
+
```yaml
|
|
170
|
+
apiVersion: external-secrets.io/v1beta1
|
|
171
|
+
kind: ExternalSecret
|
|
172
|
+
metadata:
|
|
173
|
+
name: app-secrets
|
|
174
|
+
spec:
|
|
175
|
+
refreshInterval: 1h
|
|
176
|
+
secretStoreRef:
|
|
177
|
+
name: aws-secrets
|
|
178
|
+
kind: ClusterSecretStore
|
|
179
|
+
target:
|
|
180
|
+
name: app-secrets
|
|
181
|
+
data:
|
|
182
|
+
- secretKey: db-password
|
|
183
|
+
remoteRef:
|
|
184
|
+
key: prod/db-password
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Sealed Secrets
|
|
188
|
+
|
|
189
|
+
Encrypt secrets for safe storage in Git:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
kubeseal --format yaml < secret.yaml > sealed-secret.yaml
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Secret Rotation
|
|
196
|
+
|
|
197
|
+
Use the External Secrets Operator `refreshInterval` or Reloader to restart pods on secret changes:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
kubectl annotate deployment api reloader.stakater.com/auto="true"
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## RBAC Hardening
|
|
204
|
+
|
|
205
|
+
### Audit RBAC Permissions
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
# Check what a ServiceAccount can do
|
|
209
|
+
kubectl auth can-i --list --as=system:serviceaccount:prod:api-sa
|
|
210
|
+
|
|
211
|
+
# Check specific permission
|
|
212
|
+
kubectl auth can-i create pods --as=system:serviceaccount:prod:api-sa
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Avoid Cluster-Admin
|
|
216
|
+
|
|
217
|
+
Never bind `cluster-admin` to application ServiceAccounts. Use namespace-scoped Roles with minimal verbs:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
import { WorkerPool } from "@intentius/chant-lexicon-k8s";
|
|
221
|
+
|
|
222
|
+
const worker = WorkerPool({
|
|
223
|
+
name: "processor",
|
|
224
|
+
image: "processor:1.0",
|
|
225
|
+
rbacRules: [
|
|
226
|
+
{ apiGroups: [""], resources: ["configmaps"], verbs: ["get"] },
|
|
227
|
+
],
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Service Account Token Projection
|
|
232
|
+
|
|
233
|
+
Disable auto-mounting of SA tokens when not needed:
|
|
234
|
+
|
|
235
|
+
```yaml
|
|
236
|
+
automountServiceAccountToken: false
|
|
237
|
+
```
|
|
File without changes
|