@emilia-protocol/gate 0.9.5 → 0.10.0
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/CHANGELOG.md +70 -0
- package/LICENSE +190 -0
- package/README.md +75 -16
- package/action-control-manifest.js +26 -0
- package/adapters/_kit.js +47 -5
- package/adapters/aws.js +15 -4
- package/adapters/github-demo.mjs +25 -22
- package/adapters/github.js +1 -1
- package/adapters/jira.js +1 -0
- package/adapters/linear.js +1 -0
- package/adapters/salesforce.js +1 -0
- package/adapters/stripe.js +1 -1
- package/adapters/supabase.js +26 -4
- package/adapters/vercel.js +33 -4
- package/aec-execution.js +1 -3
- package/breakglass.js +285 -51
- package/control-plane.js +341 -0
- package/coverage.js +722 -0
- package/custody-demo.mjs +13 -3
- package/demo.mjs +9 -3
- package/deploy/helm/README.md +16 -8
- package/deploy/helm/emilia-gate/Chart.yaml +3 -1
- package/deploy/helm/emilia-gate/templates/NOTES.txt +3 -2
- package/deploy/helm/emilia-gate/templates/deployment.yaml +55 -1
- package/deploy/helm/emilia-gate/values.yaml +18 -2
- package/deploy/helm/emilia-gate-service/Chart.yaml +11 -0
- package/deploy/helm/emilia-gate-service/README.md +81 -0
- package/deploy/helm/emilia-gate-service/templates/NOTES.txt +12 -0
- package/deploy/helm/emilia-gate-service/templates/_helpers.tpl +83 -0
- package/deploy/helm/emilia-gate-service/templates/deployment.yaml +153 -0
- package/deploy/helm/emilia-gate-service/templates/migration-job.yaml +73 -0
- package/deploy/helm/emilia-gate-service/templates/networkpolicy.yaml +113 -0
- package/deploy/helm/emilia-gate-service/templates/pdb.yaml +14 -0
- package/deploy/helm/emilia-gate-service/templates/service.yaml +20 -0
- package/deploy/helm/emilia-gate-service/templates/serviceaccount.yaml +8 -0
- package/deploy/helm/emilia-gate-service/tests/fixtures/001_gate.sql +26 -0
- package/deploy/helm/emilia-gate-service/tests/fixtures/002_runtime_access.sql +32 -0
- package/deploy/helm/emilia-gate-service/tests/fixtures/gate.config.mjs +29 -0
- package/deploy/helm/emilia-gate-service/tests/render-check.sh +145 -0
- package/deploy/helm/emilia-gate-service/values.schema.json +145 -0
- package/deploy/helm/emilia-gate-service/values.yaml +166 -0
- package/deploy/sql/001-runtime.sql +707 -0
- package/deploy/terraform/README.md +24 -14
- package/deploy/terraform/main.tf +59 -0
- package/deploy/terraform/service/README.md +81 -0
- package/deploy/terraform/service/main.tf +718 -0
- package/deploy/terraform/service/outputs.tf +19 -0
- package/deploy/terraform/service/tests/validate.sh +24 -0
- package/deploy/terraform/service/tests/validation.tftest.hcl +168 -0
- package/deploy/terraform/service/variables.tf +459 -0
- package/deploy/terraform/service/versions.tf +10 -0
- package/deploy/terraform/variables.tf +95 -2
- package/deployment-attestation.js +248 -0
- package/eg1-conformance.js +46 -12
- package/enterprise.js +6 -2
- package/ep-assure.mjs +3 -2
- package/evidence-postgres.js +357 -0
- package/evidence.js +10 -3
- package/execution-binding.js +141 -26
- package/index.js +556 -115
- package/key-registry.js +51 -19
- package/mcp.js +4 -2
- package/network-witness.js +500 -0
- package/package.json +25 -5
- package/reliance-kernel.js +5 -6
- package/reliance-packet.js +43 -10
- package/reports/assurance-package.js +45 -19
- package/reports/reperform.js +78 -18
- package/reports/underwriter.js +1 -1
- package/settlement.js +300 -0
- package/store-postgres.js +14 -0
- package/store.js +26 -5
- package/strict-json.js +86 -0
- package/witness-postgres.js +97 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{{- if .Values.migrations.enabled }}
|
|
2
|
+
apiVersion: batch/v1
|
|
3
|
+
kind: Job
|
|
4
|
+
metadata:
|
|
5
|
+
name: {{ include "emilia-gate-service.fullname" . }}-migrate
|
|
6
|
+
namespace: {{ .Release.Namespace }}
|
|
7
|
+
labels:
|
|
8
|
+
{{- include "emilia-gate-service.labels" . | nindent 4 }}
|
|
9
|
+
app.kubernetes.io/component: migration
|
|
10
|
+
annotations:
|
|
11
|
+
helm.sh/hook: pre-install,pre-upgrade
|
|
12
|
+
helm.sh/hook-weight: "-5"
|
|
13
|
+
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
|
|
14
|
+
spec:
|
|
15
|
+
activeDeadlineSeconds: {{ .Values.migrations.activeDeadlineSeconds }}
|
|
16
|
+
backoffLimit: {{ .Values.migrations.backoffLimit }}
|
|
17
|
+
ttlSecondsAfterFinished: {{ .Values.migrations.ttlSecondsAfterFinished }}
|
|
18
|
+
template:
|
|
19
|
+
metadata:
|
|
20
|
+
labels:
|
|
21
|
+
{{- include "emilia-gate-service.baseSelectorLabels" . | nindent 8 }}
|
|
22
|
+
app.kubernetes.io/component: migration
|
|
23
|
+
spec:
|
|
24
|
+
automountServiceAccountToken: false
|
|
25
|
+
restartPolicy: Never
|
|
26
|
+
securityContext:
|
|
27
|
+
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
|
28
|
+
{{- with .Values.imagePullSecrets }}
|
|
29
|
+
imagePullSecrets:
|
|
30
|
+
{{- toYaml . | nindent 8 }}
|
|
31
|
+
{{- end }}
|
|
32
|
+
containers:
|
|
33
|
+
- name: migrate
|
|
34
|
+
image: {{ include "emilia-gate-service.image" . | quote }}
|
|
35
|
+
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
|
36
|
+
command:
|
|
37
|
+
{{- toYaml .Values.migrations.command | nindent 12 }}
|
|
38
|
+
env:
|
|
39
|
+
- name: NODE_ENV
|
|
40
|
+
value: production
|
|
41
|
+
- name: EMILIA_GATE_CONFIG
|
|
42
|
+
value: {{ printf "%s/%s" .Values.configuration.mountPath .Values.configuration.configKey | quote }}
|
|
43
|
+
- name: {{ .Values.secrets.postgres.envName }}
|
|
44
|
+
valueFrom:
|
|
45
|
+
secretKeyRef:
|
|
46
|
+
name: {{ include "emilia-gate-service.migrationPostgresSecret" . }}
|
|
47
|
+
key: {{ include "emilia-gate-service.migrationPostgresKey" . }}
|
|
48
|
+
optional: false
|
|
49
|
+
securityContext:
|
|
50
|
+
{{- toYaml .Values.containerSecurityContext | nindent 12 }}
|
|
51
|
+
resources:
|
|
52
|
+
{{- toYaml .Values.migrations.resources | nindent 12 }}
|
|
53
|
+
volumeMounts:
|
|
54
|
+
- name: tmp
|
|
55
|
+
mountPath: /tmp
|
|
56
|
+
- name: configuration
|
|
57
|
+
mountPath: {{ .Values.configuration.mountPath }}
|
|
58
|
+
readOnly: true
|
|
59
|
+
volumes:
|
|
60
|
+
- name: tmp
|
|
61
|
+
emptyDir:
|
|
62
|
+
sizeLimit: 32Mi
|
|
63
|
+
- name: configuration
|
|
64
|
+
secret:
|
|
65
|
+
secretName: {{ include "emilia-gate-service.configurationSecret" . }}
|
|
66
|
+
optional: false
|
|
67
|
+
defaultMode: 0440
|
|
68
|
+
items:
|
|
69
|
+
- key: {{ .Values.configuration.configKey }}
|
|
70
|
+
path: {{ .Values.configuration.configKey }}
|
|
71
|
+
- key: {{ .Values.configuration.migrationKey }}
|
|
72
|
+
path: {{ .Values.configuration.migrationKey }}
|
|
73
|
+
{{- end }}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
{{- if .Values.networkPolicy.enabled }}
|
|
2
|
+
{{- $ingressPeers := default (list) .Values.networkPolicy.ingress.additionalPeers }}
|
|
3
|
+
{{- $postgresSelector := default (dict) .Values.networkPolicy.egress.postgres.podSelector }}
|
|
4
|
+
{{- $postgresCidrs := default (list) .Values.networkPolicy.egress.postgres.cidrs }}
|
|
5
|
+
{{- $kmsCidrs := default (list) .Values.networkPolicy.egress.kmsCidrs }}
|
|
6
|
+
{{- $githubCidrs := default (list) .Values.networkPolicy.egress.githubCidrs }}
|
|
7
|
+
{{- $siemCidrs := default (list) .Values.networkPolicy.egress.siemCidrs }}
|
|
8
|
+
{{- $httpsCidrs := concat $kmsCidrs $githubCidrs $siemCidrs }}
|
|
9
|
+
{{- $additionalEgress := default (list) .Values.networkPolicy.egress.additionalRules }}
|
|
10
|
+
{{- $hasIngress := or .Values.networkPolicy.ingress.allowSameNamespace (gt (len $ingressPeers) 0) }}
|
|
11
|
+
{{- $hasBaseEgress := or .Values.networkPolicy.egress.dns.enabled (gt (len $postgresSelector) 0) (gt (len $postgresCidrs) 0) }}
|
|
12
|
+
{{- $hasServiceEgress := or (gt (len $httpsCidrs) 0) (gt (len $additionalEgress) 0) }}
|
|
13
|
+
apiVersion: networking.k8s.io/v1
|
|
14
|
+
kind: NetworkPolicy
|
|
15
|
+
metadata:
|
|
16
|
+
name: {{ include "emilia-gate-service.fullname" . }}-base
|
|
17
|
+
namespace: {{ .Release.Namespace }}
|
|
18
|
+
labels:
|
|
19
|
+
{{- include "emilia-gate-service.labels" . | nindent 4 }}
|
|
20
|
+
spec:
|
|
21
|
+
# Selects the long-lived service and the migration hook. The migration pod
|
|
22
|
+
# receives DNS/Postgres only, never service-only GitHub/KMS/SIEM egress.
|
|
23
|
+
podSelector:
|
|
24
|
+
matchLabels:
|
|
25
|
+
{{- include "emilia-gate-service.baseSelectorLabels" . | nindent 6 }}
|
|
26
|
+
policyTypes:
|
|
27
|
+
- Ingress
|
|
28
|
+
- Egress
|
|
29
|
+
ingress: []
|
|
30
|
+
egress:
|
|
31
|
+
{{- if $hasBaseEgress }}
|
|
32
|
+
{{- if .Values.networkPolicy.egress.dns.enabled }}
|
|
33
|
+
- to:
|
|
34
|
+
- namespaceSelector:
|
|
35
|
+
{{- toYaml .Values.networkPolicy.egress.dns.namespaceSelector | nindent 12 }}
|
|
36
|
+
podSelector:
|
|
37
|
+
{{- toYaml .Values.networkPolicy.egress.dns.podSelector | nindent 12 }}
|
|
38
|
+
ports:
|
|
39
|
+
- port: 53
|
|
40
|
+
protocol: UDP
|
|
41
|
+
- port: 53
|
|
42
|
+
protocol: TCP
|
|
43
|
+
{{- end }}
|
|
44
|
+
{{- with $postgresSelector }}
|
|
45
|
+
- to:
|
|
46
|
+
- podSelector:
|
|
47
|
+
{{- toYaml . | nindent 12 }}
|
|
48
|
+
{{- with $.Values.networkPolicy.egress.postgres.namespaceSelector }}
|
|
49
|
+
namespaceSelector:
|
|
50
|
+
{{- toYaml . | nindent 12 }}
|
|
51
|
+
{{- end }}
|
|
52
|
+
ports:
|
|
53
|
+
- port: {{ $.Values.networkPolicy.egress.postgres.port }}
|
|
54
|
+
protocol: TCP
|
|
55
|
+
{{- end }}
|
|
56
|
+
{{- range $postgresCidrs }}
|
|
57
|
+
- to:
|
|
58
|
+
- ipBlock:
|
|
59
|
+
cidr: {{ . | quote }}
|
|
60
|
+
ports:
|
|
61
|
+
- port: {{ $.Values.networkPolicy.egress.postgres.port }}
|
|
62
|
+
protocol: TCP
|
|
63
|
+
{{- end }}
|
|
64
|
+
{{- else }}
|
|
65
|
+
[]
|
|
66
|
+
{{- end }}
|
|
67
|
+
---
|
|
68
|
+
apiVersion: networking.k8s.io/v1
|
|
69
|
+
kind: NetworkPolicy
|
|
70
|
+
metadata:
|
|
71
|
+
name: {{ include "emilia-gate-service.fullname" . }}-service-access
|
|
72
|
+
namespace: {{ .Release.Namespace }}
|
|
73
|
+
labels:
|
|
74
|
+
{{- include "emilia-gate-service.labels" . | nindent 4 }}
|
|
75
|
+
spec:
|
|
76
|
+
podSelector:
|
|
77
|
+
matchLabels:
|
|
78
|
+
{{- include "emilia-gate-service.serviceSelectorLabels" . | nindent 6 }}
|
|
79
|
+
policyTypes:
|
|
80
|
+
- Ingress
|
|
81
|
+
- Egress
|
|
82
|
+
ingress:
|
|
83
|
+
{{- if $hasIngress }}
|
|
84
|
+
- from:
|
|
85
|
+
{{- if .Values.networkPolicy.ingress.allowSameNamespace }}
|
|
86
|
+
- podSelector: {}
|
|
87
|
+
{{- end }}
|
|
88
|
+
{{- with $ingressPeers }}
|
|
89
|
+
{{- toYaml . | nindent 8 }}
|
|
90
|
+
{{- end }}
|
|
91
|
+
ports:
|
|
92
|
+
- port: {{ .Values.runtime.port }}
|
|
93
|
+
protocol: TCP
|
|
94
|
+
{{- else }}
|
|
95
|
+
[]
|
|
96
|
+
{{- end }}
|
|
97
|
+
egress:
|
|
98
|
+
{{- if $hasServiceEgress }}
|
|
99
|
+
{{- range $httpsCidrs }}
|
|
100
|
+
- to:
|
|
101
|
+
- ipBlock:
|
|
102
|
+
cidr: {{ . | quote }}
|
|
103
|
+
ports:
|
|
104
|
+
- port: 443
|
|
105
|
+
protocol: TCP
|
|
106
|
+
{{- end }}
|
|
107
|
+
{{- with $additionalEgress }}
|
|
108
|
+
{{- toYaml . | nindent 4 }}
|
|
109
|
+
{{- end }}
|
|
110
|
+
{{- else }}
|
|
111
|
+
[]
|
|
112
|
+
{{- end }}
|
|
113
|
+
{{- end }}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{{- if .Values.podDisruptionBudget.enabled }}
|
|
2
|
+
apiVersion: policy/v1
|
|
3
|
+
kind: PodDisruptionBudget
|
|
4
|
+
metadata:
|
|
5
|
+
name: {{ include "emilia-gate-service.fullname" . }}
|
|
6
|
+
namespace: {{ .Release.Namespace }}
|
|
7
|
+
labels:
|
|
8
|
+
{{- include "emilia-gate-service.labels" . | nindent 4 }}
|
|
9
|
+
spec:
|
|
10
|
+
minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
|
|
11
|
+
selector:
|
|
12
|
+
matchLabels:
|
|
13
|
+
{{- include "emilia-gate-service.serviceSelectorLabels" . | nindent 6 }}
|
|
14
|
+
{{- end }}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
apiVersion: v1
|
|
2
|
+
kind: Service
|
|
3
|
+
metadata:
|
|
4
|
+
name: {{ include "emilia-gate-service.fullname" . }}
|
|
5
|
+
namespace: {{ .Release.Namespace }}
|
|
6
|
+
labels:
|
|
7
|
+
{{- include "emilia-gate-service.labels" . | nindent 4 }}
|
|
8
|
+
{{- with .Values.service.annotations }}
|
|
9
|
+
annotations:
|
|
10
|
+
{{- toYaml . | nindent 4 }}
|
|
11
|
+
{{- end }}
|
|
12
|
+
spec:
|
|
13
|
+
type: {{ .Values.service.type }}
|
|
14
|
+
selector:
|
|
15
|
+
{{- include "emilia-gate-service.serviceSelectorLabels" . | nindent 4 }}
|
|
16
|
+
ports:
|
|
17
|
+
- name: http
|
|
18
|
+
port: {{ .Values.service.port }}
|
|
19
|
+
targetPort: http
|
|
20
|
+
protocol: TCP
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
BEGIN;
|
|
2
|
+
|
|
3
|
+
CREATE TABLE IF NOT EXISTS ep_gate_schema_migrations (
|
|
4
|
+
version TEXT PRIMARY KEY,
|
|
5
|
+
applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
CREATE TABLE IF NOT EXISTS ep_gate_consumption (
|
|
9
|
+
consumption_key TEXT PRIMARY KEY,
|
|
10
|
+
state TEXT NOT NULL,
|
|
11
|
+
consumed_at BIGINT NOT NULL,
|
|
12
|
+
expires_at BIGINT
|
|
13
|
+
);
|
|
14
|
+
CREATE INDEX IF NOT EXISTS ep_gate_consumption_expires_idx
|
|
15
|
+
ON ep_gate_consumption (expires_at) WHERE expires_at IS NOT NULL;
|
|
16
|
+
|
|
17
|
+
CREATE TABLE IF NOT EXISTS ep_gate_actions (
|
|
18
|
+
id TEXT PRIMARY KEY,
|
|
19
|
+
record JSONB NOT NULL,
|
|
20
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
INSERT INTO ep_gate_schema_migrations (version)
|
|
24
|
+
VALUES ('gate-e2e-v1') ON CONFLICT (version) DO NOTHING;
|
|
25
|
+
|
|
26
|
+
COMMIT;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
-- Local E2E role split: migrations run as gate_owner; the service connects as
|
|
2
|
+
-- gate_runtime. The runtime can mutate replay/action state and can append
|
|
3
|
+
-- evidence only through the SECURITY DEFINER function installed by
|
|
4
|
+
-- packages/gate/deploy/sql/001-runtime.sql.
|
|
5
|
+
|
|
6
|
+
\set ON_ERROR_STOP on
|
|
7
|
+
|
|
8
|
+
SELECT format(
|
|
9
|
+
'CREATE ROLE gate_runtime LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT NOREPLICATION PASSWORD %L',
|
|
10
|
+
:'runtime_password'
|
|
11
|
+
)
|
|
12
|
+
WHERE NOT EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = 'gate_runtime')
|
|
13
|
+
\gexec
|
|
14
|
+
|
|
15
|
+
ALTER ROLE gate_runtime
|
|
16
|
+
LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT NOREPLICATION;
|
|
17
|
+
SELECT format('ALTER ROLE gate_runtime PASSWORD %L', :'runtime_password')
|
|
18
|
+
\gexec
|
|
19
|
+
|
|
20
|
+
GRANT CONNECT ON DATABASE gate_e2e TO gate_runtime;
|
|
21
|
+
GRANT USAGE ON SCHEMA public TO gate_runtime;
|
|
22
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ep_gate_consumption TO gate_runtime;
|
|
23
|
+
GRANT SELECT, INSERT, UPDATE ON TABLE ep_gate_actions TO gate_runtime;
|
|
24
|
+
REVOKE DELETE, TRUNCATE, REFERENCES, TRIGGER ON TABLE ep_gate_actions FROM gate_runtime;
|
|
25
|
+
|
|
26
|
+
GRANT emilia_gate_evidence_runtime TO gate_runtime;
|
|
27
|
+
SELECT emilia_gate_evidence.grant_runtime_scope(
|
|
28
|
+
'gate_runtime',
|
|
29
|
+
'gate-e2e-tenant',
|
|
30
|
+
'gate-e2e-service',
|
|
31
|
+
'gate-e2e'
|
|
32
|
+
);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { createProductionGateConfig } from '/app/apps/gate-service/src/production-config.js';
|
|
3
|
+
|
|
4
|
+
function readSecret(variable) {
|
|
5
|
+
const file = process.env[variable];
|
|
6
|
+
if (!file) throw new Error(`${variable}_required`);
|
|
7
|
+
const value = fs.readFileSync(file, 'utf8').trim();
|
|
8
|
+
if (!value) throw new Error(`${variable}_empty`);
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const password = readSecret('EMILIA_GATE_RUNTIME_POSTGRES_PASSWORD_FILE');
|
|
13
|
+
const githubToken = readSecret('GITHUB_TOKEN_FILE');
|
|
14
|
+
const apiToken = readSecret('EMILIA_GATE_API_TOKEN_FILE');
|
|
15
|
+
const trust = readSecret('EP_GATE_ISSUER_ROOTS_FILE');
|
|
16
|
+
|
|
17
|
+
export default await createProductionGateConfig({
|
|
18
|
+
environment: {
|
|
19
|
+
EMILIA_GATE_DATABASE_URL: `postgresql://gate_runtime:${encodeURIComponent(password)}@postgres:5432/gate_e2e`,
|
|
20
|
+
EMILIA_GATE_API_TOKEN: apiToken,
|
|
21
|
+
EMILIA_GATE_PRINCIPAL_ID: 'operator:e2e',
|
|
22
|
+
EMILIA_GATE_TENANT_ID: 'gate-e2e-tenant',
|
|
23
|
+
EMILIA_GATE_ID: 'gate-e2e-service',
|
|
24
|
+
EMILIA_GATE_EVIDENCE_STREAM_ID: 'gate-e2e',
|
|
25
|
+
EMILIA_GATE_TRUST_JSON: trust,
|
|
26
|
+
EMILIA_GATE_ALLOWED_REPOSITORIES: 'emilia-e2e/disposable-repository',
|
|
27
|
+
GITHUB_TOKEN: githubToken,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
chart_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
helm_bin="${HELM_BIN:-helm}"
|
|
6
|
+
|
|
7
|
+
if ! command -v "$helm_bin" >/dev/null 2>&1; then
|
|
8
|
+
echo "helm is required (or set HELM_BIN)" >&2
|
|
9
|
+
exit 127
|
|
10
|
+
fi
|
|
11
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
12
|
+
echo "node is required to syntax-check the E2E operator config fixture" >&2
|
|
13
|
+
exit 127
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
digest="sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
17
|
+
values=(
|
|
18
|
+
--set-string image.repository=registry.example.test/security/emilia-gate-service
|
|
19
|
+
--set-string image.digest="$digest"
|
|
20
|
+
--set-string configuration.existingSecret=gate-configuration
|
|
21
|
+
--set-string secrets.postgres.existingSecret=gate-postgres
|
|
22
|
+
--set-string migrations.postgres.existingSecret=gate-postgres-migrate
|
|
23
|
+
--set-string migrations.postgres.key=database-url
|
|
24
|
+
--set-string secrets.apiToken.existingSecret=gate-api-token
|
|
25
|
+
--set-string secrets.kms.existingSecret=gate-kms
|
|
26
|
+
--set-string secrets.issuerRoots.existingSecret=gate-issuer-roots
|
|
27
|
+
--set-string networkPolicy.egress.githubCidrs[0]=192.0.2.10/32
|
|
28
|
+
--set-string networkPolicy.egress.siemCidrs[0]=198.51.100.20/32
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
"$helm_bin" lint --strict "$chart_dir" "${values[@]}"
|
|
32
|
+
rendered="$("$helm_bin" template render-check "$chart_dir" --namespace gate-system "${values[@]}")"
|
|
33
|
+
|
|
34
|
+
assert_rendered() {
|
|
35
|
+
local expected="$1"
|
|
36
|
+
if ! grep -Fq -- "$expected" <<<"$rendered"; then
|
|
37
|
+
echo "render assertion failed: missing '$expected'" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
assert_rendered "kind: Deployment"
|
|
43
|
+
assert_rendered "kind: Job"
|
|
44
|
+
assert_rendered "kind: PodDisruptionBudget"
|
|
45
|
+
assert_rendered "kind: NetworkPolicy"
|
|
46
|
+
assert_rendered "replicas: 2"
|
|
47
|
+
assert_rendered "runAsNonRoot: true"
|
|
48
|
+
assert_rendered "readOnlyRootFilesystem: true"
|
|
49
|
+
assert_rendered "allowPrivilegeEscalation: false"
|
|
50
|
+
assert_rendered "automountServiceAccountToken: false"
|
|
51
|
+
assert_rendered "registry.example.test/security/emilia-gate-service@$digest"
|
|
52
|
+
assert_rendered "name: gate-postgres"
|
|
53
|
+
assert_rendered "name: gate-postgres-migrate"
|
|
54
|
+
assert_rendered "name: gate-api-token"
|
|
55
|
+
assert_rendered "name: gate-kms"
|
|
56
|
+
assert_rendered "name: gate-issuer-roots"
|
|
57
|
+
assert_rendered "secretName: gate-configuration"
|
|
58
|
+
assert_rendered "value: \"/app/config/gate.config.mjs\""
|
|
59
|
+
assert_rendered "path: /v1/live"
|
|
60
|
+
assert_rendered "path: /v1/ready"
|
|
61
|
+
assert_rendered "cidr: \"192.0.2.10/32\""
|
|
62
|
+
assert_rendered "cidr: \"198.51.100.20/32\""
|
|
63
|
+
|
|
64
|
+
if grep -Eq '^kind: Secret$' <<<"$rendered"; then
|
|
65
|
+
echo "chart must reference existing Secrets, never render one" >&2
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
if [[ "$(grep -Fc 'kind: NetworkPolicy' <<<"$rendered")" -ne 2 ]]; then
|
|
70
|
+
echo "chart must render separate base and service-only NetworkPolicies" >&2
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
if "$helm_bin" template missing-required "$chart_dir" >/dev/null 2>&1; then
|
|
75
|
+
echo "chart unexpectedly rendered without BYOC image and Secret references" >&2
|
|
76
|
+
exit 1
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
if "$helm_bin" template mutable-image "$chart_dir" --namespace gate-system \
|
|
80
|
+
--set-string image.repository=registry.example.test/security/emilia-gate-service \
|
|
81
|
+
--set-string image.tag=latest \
|
|
82
|
+
--set-string configuration.existingSecret=gate-configuration \
|
|
83
|
+
--set-string secrets.postgres.existingSecret=gate-postgres \
|
|
84
|
+
--set-string migrations.postgres.existingSecret=gate-postgres-migrate \
|
|
85
|
+
--set-string migrations.postgres.key=database-url \
|
|
86
|
+
--set-string secrets.apiToken.existingSecret=gate-api-token \
|
|
87
|
+
--set-string secrets.issuerRoots.existingSecret=gate-issuer-roots >/dev/null 2>&1; then
|
|
88
|
+
echo "chart unexpectedly accepted image.tag=latest" >&2
|
|
89
|
+
exit 1
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
if "$helm_bin" template missing-migration-secret "$chart_dir" --namespace gate-system \
|
|
93
|
+
--set-string image.repository=registry.example.test/security/emilia-gate-service \
|
|
94
|
+
--set-string image.digest="$digest" \
|
|
95
|
+
--set-string configuration.existingSecret=gate-configuration \
|
|
96
|
+
--set-string secrets.postgres.existingSecret=gate-postgres \
|
|
97
|
+
--set-string secrets.apiToken.existingSecret=gate-api-token \
|
|
98
|
+
--set-string secrets.issuerRoots.existingSecret=gate-issuer-roots >/dev/null 2>&1; then
|
|
99
|
+
echo "chart unexpectedly reused the runtime Postgres Secret for migrations" >&2
|
|
100
|
+
exit 1
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
if "$helm_bin" template equal-migration-secret "$chart_dir" --namespace gate-system \
|
|
104
|
+
--set-string image.repository=registry.example.test/security/emilia-gate-service \
|
|
105
|
+
--set-string image.digest="$digest" \
|
|
106
|
+
--set-string configuration.existingSecret=gate-configuration \
|
|
107
|
+
--set-string secrets.postgres.existingSecret=gate-postgres \
|
|
108
|
+
--set-string migrations.postgres.existingSecret=gate-postgres \
|
|
109
|
+
--set-string migrations.postgres.key=database-url \
|
|
110
|
+
--set-string secrets.apiToken.existingSecret=gate-api-token \
|
|
111
|
+
--set-string secrets.issuerRoots.existingSecret=gate-issuer-roots >/dev/null 2>&1; then
|
|
112
|
+
echo "chart unexpectedly accepted the runtime Postgres Secret for migrations" >&2
|
|
113
|
+
exit 1
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
migrations_disabled="$($helm_bin template migrations-disabled "$chart_dir" --namespace gate-system \
|
|
117
|
+
--set-string image.repository=registry.example.test/security/emilia-gate-service \
|
|
118
|
+
--set-string image.digest="$digest" \
|
|
119
|
+
--set-string configuration.existingSecret=gate-configuration \
|
|
120
|
+
--set-string secrets.postgres.existingSecret=gate-postgres \
|
|
121
|
+
--set-string secrets.apiToken.existingSecret=gate-api-token \
|
|
122
|
+
--set-string secrets.issuerRoots.existingSecret=gate-issuer-roots \
|
|
123
|
+
--set migrations.enabled=false)"
|
|
124
|
+
if grep -Fq 'kind: Job' <<<"$migrations_disabled"; then
|
|
125
|
+
echo "chart rendered a migration Job while migrations were disabled" >&2
|
|
126
|
+
exit 1
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
without_kms="$($helm_bin template no-kms "$chart_dir" --namespace gate-system \
|
|
130
|
+
--set-string image.repository=registry.example.test/security/emilia-gate-service \
|
|
131
|
+
--set-string image.digest="$digest" \
|
|
132
|
+
--set-string configuration.existingSecret=gate-configuration \
|
|
133
|
+
--set-string secrets.postgres.existingSecret=gate-postgres \
|
|
134
|
+
--set-string migrations.postgres.existingSecret=gate-postgres-migrate \
|
|
135
|
+
--set-string migrations.postgres.key=database-url \
|
|
136
|
+
--set-string secrets.apiToken.existingSecret=gate-api-token \
|
|
137
|
+
--set-string secrets.issuerRoots.existingSecret=gate-issuer-roots)"
|
|
138
|
+
if grep -Fq -- "EP_KMS_KEY_ID" <<<"$without_kms"; then
|
|
139
|
+
echo "chart rendered a KMS dependency even though no KMS extension was configured" >&2
|
|
140
|
+
exit 1
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
node --check "$chart_dir/tests/fixtures/gate.config.mjs"
|
|
144
|
+
|
|
145
|
+
echo "Helm lint and render assertions passed"
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "EMILIA Gate service Helm values",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": ["image", "configuration", "secrets"],
|
|
6
|
+
"properties": {
|
|
7
|
+
"replicaCount": { "type": "integer", "minimum": 1 },
|
|
8
|
+
"image": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"required": ["repository", "tag", "digest", "pullPolicy"],
|
|
11
|
+
"properties": {
|
|
12
|
+
"repository": { "type": "string" },
|
|
13
|
+
"tag": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"pattern": "^(?:$|[A-Za-z0-9_][A-Za-z0-9._-]{0,127})$",
|
|
16
|
+
"not": { "pattern": "^[Ll][Aa][Tt][Ee][Ss][Tt]$" }
|
|
17
|
+
},
|
|
18
|
+
"digest": { "type": "string" },
|
|
19
|
+
"pullPolicy": { "type": "string", "enum": ["Always", "IfNotPresent", "Never"] }
|
|
20
|
+
},
|
|
21
|
+
"additionalProperties": false
|
|
22
|
+
},
|
|
23
|
+
"imagePullSecrets": { "type": "array" },
|
|
24
|
+
"nameOverride": { "type": "string" },
|
|
25
|
+
"fullnameOverride": { "type": "string" },
|
|
26
|
+
"service": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"required": ["type", "port"],
|
|
29
|
+
"properties": {
|
|
30
|
+
"type": { "type": "string", "enum": ["ClusterIP", "NodePort", "LoadBalancer"] },
|
|
31
|
+
"port": { "type": "integer", "minimum": 1, "maximum": 65535 },
|
|
32
|
+
"annotations": { "type": "object" }
|
|
33
|
+
},
|
|
34
|
+
"additionalProperties": false
|
|
35
|
+
},
|
|
36
|
+
"runtime": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"required": ["port", "logLevel", "terminationGracePeriodSeconds", "extraEnv"],
|
|
39
|
+
"properties": {
|
|
40
|
+
"port": { "type": "integer", "minimum": 1, "maximum": 65535 },
|
|
41
|
+
"logLevel": { "type": "string", "enum": ["debug", "info", "warn", "error"] },
|
|
42
|
+
"terminationGracePeriodSeconds": { "type": "integer", "minimum": 1 },
|
|
43
|
+
"extraEnv": { "type": "array" }
|
|
44
|
+
},
|
|
45
|
+
"additionalProperties": false
|
|
46
|
+
},
|
|
47
|
+
"configuration": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"required": ["existingSecret", "configKey", "migrationKey", "mountPath"],
|
|
50
|
+
"properties": {
|
|
51
|
+
"existingSecret": { "type": "string" },
|
|
52
|
+
"configKey": { "type": "string", "minLength": 1 },
|
|
53
|
+
"migrationKey": { "type": "string", "minLength": 1 },
|
|
54
|
+
"mountPath": { "type": "string", "pattern": "^/" }
|
|
55
|
+
},
|
|
56
|
+
"additionalProperties": false
|
|
57
|
+
},
|
|
58
|
+
"secrets": {
|
|
59
|
+
"type": "object",
|
|
60
|
+
"required": ["postgres", "apiToken", "kms", "issuerRoots"],
|
|
61
|
+
"properties": {
|
|
62
|
+
"postgres": { "$ref": "#/definitions/secretReference" },
|
|
63
|
+
"apiToken": { "$ref": "#/definitions/secretReference" },
|
|
64
|
+
"kms": { "$ref": "#/definitions/secretReference" },
|
|
65
|
+
"issuerRoots": { "$ref": "#/definitions/secretReference" }
|
|
66
|
+
},
|
|
67
|
+
"additionalProperties": false
|
|
68
|
+
},
|
|
69
|
+
"migrations": {
|
|
70
|
+
"type": "object",
|
|
71
|
+
"required": ["enabled", "postgres", "command", "activeDeadlineSeconds", "backoffLimit", "ttlSecondsAfterFinished", "resources"],
|
|
72
|
+
"properties": {
|
|
73
|
+
"enabled": { "type": "boolean" },
|
|
74
|
+
"postgres": {
|
|
75
|
+
"type": "object",
|
|
76
|
+
"required": ["existingSecret", "key"],
|
|
77
|
+
"properties": {
|
|
78
|
+
"existingSecret": { "type": "string" },
|
|
79
|
+
"key": { "type": "string" }
|
|
80
|
+
},
|
|
81
|
+
"additionalProperties": false
|
|
82
|
+
},
|
|
83
|
+
"command": { "type": "array", "minItems": 1, "items": { "type": "string" } },
|
|
84
|
+
"activeDeadlineSeconds": { "type": "integer", "minimum": 1 },
|
|
85
|
+
"backoffLimit": { "type": "integer", "minimum": 0 },
|
|
86
|
+
"ttlSecondsAfterFinished": { "type": "integer", "minimum": 0 },
|
|
87
|
+
"resources": { "type": "object" }
|
|
88
|
+
},
|
|
89
|
+
"additionalProperties": false
|
|
90
|
+
},
|
|
91
|
+
"probes": {
|
|
92
|
+
"type": "object",
|
|
93
|
+
"required": ["startup", "liveness", "readiness"],
|
|
94
|
+
"properties": {
|
|
95
|
+
"startup": { "$ref": "#/definitions/probe" },
|
|
96
|
+
"liveness": { "$ref": "#/definitions/probe" },
|
|
97
|
+
"readiness": { "$ref": "#/definitions/probe" }
|
|
98
|
+
},
|
|
99
|
+
"additionalProperties": false
|
|
100
|
+
},
|
|
101
|
+
"resources": { "type": "object" },
|
|
102
|
+
"podDisruptionBudget": {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"required": ["enabled", "minAvailable"],
|
|
105
|
+
"properties": {
|
|
106
|
+
"enabled": { "type": "boolean" },
|
|
107
|
+
"minAvailable": { "oneOf": [{ "type": "integer", "minimum": 0 }, { "type": "string" }] }
|
|
108
|
+
},
|
|
109
|
+
"additionalProperties": false
|
|
110
|
+
},
|
|
111
|
+
"podSecurityContext": { "type": "object" },
|
|
112
|
+
"containerSecurityContext": { "type": "object" },
|
|
113
|
+
"networkPolicy": { "type": "object" },
|
|
114
|
+
"topologySpreadConstraints": { "type": "array" },
|
|
115
|
+
"podAnnotations": { "type": "object" },
|
|
116
|
+
"podLabels": { "type": "object" },
|
|
117
|
+
"nodeSelector": { "type": "object" },
|
|
118
|
+
"tolerations": { "type": "array" },
|
|
119
|
+
"affinity": { "type": "object" }
|
|
120
|
+
},
|
|
121
|
+
"additionalProperties": false,
|
|
122
|
+
"definitions": {
|
|
123
|
+
"secretReference": {
|
|
124
|
+
"type": "object",
|
|
125
|
+
"required": ["existingSecret", "key", "envName"],
|
|
126
|
+
"properties": {
|
|
127
|
+
"existingSecret": { "type": "string" },
|
|
128
|
+
"key": { "type": "string", "minLength": 1 },
|
|
129
|
+
"envName": { "type": "string", "pattern": "^[A-Z_][A-Z0-9_]*$" }
|
|
130
|
+
},
|
|
131
|
+
"additionalProperties": false
|
|
132
|
+
},
|
|
133
|
+
"probe": {
|
|
134
|
+
"type": "object",
|
|
135
|
+
"required": ["path", "periodSeconds", "timeoutSeconds", "failureThreshold"],
|
|
136
|
+
"properties": {
|
|
137
|
+
"path": { "type": "string", "pattern": "^/" },
|
|
138
|
+
"periodSeconds": { "type": "integer", "minimum": 1 },
|
|
139
|
+
"timeoutSeconds": { "type": "integer", "minimum": 1 },
|
|
140
|
+
"failureThreshold": { "type": "integer", "minimum": 1 }
|
|
141
|
+
},
|
|
142
|
+
"additionalProperties": false
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|