@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
package/custody-demo.mjs
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* EMILIA Gate —
|
|
2
|
+
* EMILIA Gate — issuer custody reference demo. Run: node custody-demo.mjs
|
|
3
3
|
*
|
|
4
4
|
* Shows the three custody controls a serious buyer asks for:
|
|
5
5
|
* 1. ROTATION — a new issuer key takes over without breaking the old one.
|
|
6
6
|
* 2. REVOCATION — a compromised issuer key is rejected immediately, live.
|
|
7
7
|
* 3. RETENTION — the evidence log classifies hot/cold/expired + exports.
|
|
8
|
+
* Issuer keys use the live registry path. Approver keys are embedded only to
|
|
9
|
+
* keep this reference demo self-contained; production Gate deployments pin
|
|
10
|
+
* approver keys or provide their own assurance verifier.
|
|
8
11
|
* @license Apache-2.0
|
|
9
12
|
*/
|
|
10
13
|
import { createGate, createKeyRegistry, createEg1Harness } from './index.js';
|
|
@@ -14,7 +17,14 @@ const SEL = { protocol: 'mcp', tool: 'release_payment' };
|
|
|
14
17
|
const k1 = createEg1Harness({ idPrefix: 'k1' });
|
|
15
18
|
const k2 = createEg1Harness({ idPrefix: 'k2' });
|
|
16
19
|
const registry = createKeyRegistry([{ kid: 'issuer-1', key: k1.publicKey }]);
|
|
17
|
-
const gate = createGate({
|
|
20
|
+
const gate = createGate({
|
|
21
|
+
manifest: DEFAULT_GATE_MANIFEST,
|
|
22
|
+
keyRegistry: registry,
|
|
23
|
+
rpId: k1.rpId,
|
|
24
|
+
allowedOrigins: k1.allowedOrigins,
|
|
25
|
+
allowEmbeddedApproverKeys: true,
|
|
26
|
+
allowEphemeralStore: true,
|
|
27
|
+
});
|
|
18
28
|
|
|
19
29
|
const G = (s) => `\x1b[32m${s}\x1b[0m`; const R = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
20
30
|
const line = (s) => console.log(s);
|
|
@@ -24,7 +34,7 @@ async function pay(label, harness) {
|
|
|
24
34
|
}
|
|
25
35
|
|
|
26
36
|
line('='.repeat(64));
|
|
27
|
-
line(' EMILIA Gate —
|
|
37
|
+
line(' EMILIA Gate — custody reference: rotate, revoke, retain');
|
|
28
38
|
line('='.repeat(64));
|
|
29
39
|
await pay('1. payout, issuer-1 (current) ', k1);
|
|
30
40
|
registry.revoke('issuer-1');
|
package/demo.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* EMILIA Gate — end-to-end demo. Run: node demo.mjs
|
|
3
|
-
* Shows the
|
|
3
|
+
* Shows the Consequence Firewall refusing and allowing a consequential
|
|
4
4
|
* action, defeating replay and tampering, and emitting a verifiable audit log.
|
|
5
5
|
* @license Apache-2.0
|
|
6
6
|
*/
|
|
@@ -35,13 +35,19 @@ const receipt = (outcome, extra = {}) => {
|
|
|
35
35
|
return { '@version': 'EP-RECEIPT-v1', payload, signature: { algorithm: 'Ed25519', value: crypto.sign(null, Buffer.from(canon(payload), 'utf8'), privateKey).toString('base64url') } };
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
const gate = createTrustedActionFirewall({
|
|
38
|
+
const gate = createTrustedActionFirewall({
|
|
39
|
+
trustedKeys: [pub],
|
|
40
|
+
rpId: 'emiliaprotocol.ai',
|
|
41
|
+
allowedOrigins: ['https://www.emiliaprotocol.ai'],
|
|
42
|
+
allowEmbeddedApproverKeys: true,
|
|
43
|
+
allowEphemeralStore: true,
|
|
44
|
+
});
|
|
39
45
|
const PAY = { protocol: 'mcp', tool: 'release_payment' };
|
|
40
46
|
const line = (s) => console.log(s);
|
|
41
47
|
const r = (o) => o.allow ? `\x1b[32mALLOW\x1b[0m` : `\x1b[31mREFUSE ${o.status}\x1b[0m (${o.reason})`;
|
|
42
48
|
|
|
43
49
|
line('='.repeat(64));
|
|
44
|
-
line(' EMILIA Gate —
|
|
50
|
+
line(' EMILIA Gate — Consequence Firewall (release_payment, critical)');
|
|
45
51
|
line('='.repeat(64));
|
|
46
52
|
line(`\n 1. read_status (not guarded) -> ${r(await gate.check({ selector: { protocol: 'mcp', tool: 'read_status' } }))}`);
|
|
47
53
|
line(` 2. release_payment, no receipt -> ${r(await gate.check({ selector: PAY }))}`);
|
package/deploy/helm/README.md
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
|
2
2
|
# EMILIA Gate — reference Helm chart (`emilia-gate`)
|
|
3
3
|
|
|
4
|
-
**Contract:** `EP-GATE-HELM-v1` · **Status:
|
|
4
|
+
**Contract:** `EP-GATE-HELM-v1` · **Status: deprecated, experimental, BYOC.**
|
|
5
|
+
|
|
6
|
+
This legacy chart is retained only for compatibility. It can use per-process
|
|
7
|
+
in-memory consumption and evidence state. New deployments should use
|
|
8
|
+
`emilia-gate-service`, whose runtime refuses missing durable adapters.
|
|
5
9
|
|
|
6
10
|
A reference Kubernetes deployment of the [EMILIA Gate](../../README.md) — the
|
|
7
11
|
Trusted Action Firewall. Deny-by-default enforcement for consequential machine
|
|
@@ -36,6 +40,9 @@ public image yet — build and push your own image of the gate service.
|
|
|
36
40
|
`capabilities: drop [ALL]`, `seccompProfile: RuntimeDefault`,
|
|
37
41
|
`automountServiceAccountToken: false`. Loosen deliberately via values, not
|
|
38
42
|
accidentally.
|
|
43
|
+
4. **Single replica by default**: `replicaCount` defaults to `1`. Rendering more
|
|
44
|
+
than one replica fails unless both shared consumption and evidence backends
|
|
45
|
+
are wired through complete Secret-backed `sharedState` references.
|
|
39
46
|
|
|
40
47
|
## Quick start
|
|
41
48
|
|
|
@@ -50,7 +57,7 @@ kubectl -n emilia create secret generic emilia-gate-issuer-keys \
|
|
|
50
57
|
helm install gate ./emilia-gate \
|
|
51
58
|
--namespace emilia --create-namespace \
|
|
52
59
|
--set image.repository=YOUR_REGISTRY/emilia-gate \
|
|
53
|
-
--set image.tag=0.
|
|
60
|
+
--set image.tag=0.10.0 \
|
|
54
61
|
--set issuerKeys.existingSecret=emilia-gate-issuer-keys
|
|
55
62
|
|
|
56
63
|
# 3. (Optional) expose /metrics + ServiceMonitor stub (needs Prometheus Operator).
|
|
@@ -82,8 +89,9 @@ Secrets/ConfigMaps there rather than inlining values.
|
|
|
82
89
|
|
|
83
90
|
| Value | Default | Meaning |
|
|
84
91
|
| ----- | ------- | ------- |
|
|
85
|
-
| `replicaCount` | `
|
|
86
|
-
| `
|
|
92
|
+
| `replicaCount` | `1` | Legacy Gate replicas. Values above one are refused without both Secret-backed shared-state references. |
|
|
93
|
+
| `sharedState.consumption` / `sharedState.evidence` | empty | Each requires `envName`, `existingSecret`, and `secretKey`; both are mandatory above one replica. |
|
|
94
|
+
| `image.repository` / `image.tag` | — **required** / chart `appVersion` | Operator-built BYOC image; no official public image is implied. |
|
|
87
95
|
| `issuerKeys.existingSecret` | — **required** | Name of your pre-created Secret with pinned issuer keys. |
|
|
88
96
|
| `issuerKeys.secretKey` | `issuer-keys.json` | Key inside that Secret. |
|
|
89
97
|
| `manifest.inline` | subset of default packs | Action-risk manifest rendered to the ConfigMap. Replace with your org's manifest. |
|
|
@@ -96,10 +104,10 @@ Secrets/ConfigMaps there rather than inlining values.
|
|
|
96
104
|
|
|
97
105
|
- **Experimental.** Not battle-tested; `helm lint`-clean is not a security
|
|
98
106
|
review. Read the rendered output (`helm template`) before applying.
|
|
99
|
-
- The default in-memory consumption store and evidence sink are per-pod.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
107
|
+
- The default in-memory consumption store and evidence sink are per-pod. The
|
|
108
|
+
chart now refuses fleets unless both shared backend Secret references are
|
|
109
|
+
complete. That render-time check proves wiring exists, not that a custom
|
|
110
|
+
legacy image correctly implements the adapters; prefer `emilia-gate-service`.
|
|
103
111
|
- The ServiceMonitor is a **stub**: no TLS/auth on the scrape endpoint —
|
|
104
112
|
adapt it to your monitoring stack.
|
|
105
113
|
- No Ingress/NetworkPolicy/HPA templates on purpose: the gate is an internal
|
|
@@ -15,11 +15,12 @@ description: >-
|
|
|
15
15
|
Reference chart, experimental, BYOC. Issuer keys are referenced from an
|
|
16
16
|
existing Secret and are never rendered by this chart.
|
|
17
17
|
type: application
|
|
18
|
+
deprecated: true
|
|
18
19
|
# Chart version (the packaging), independent of the gate's app version.
|
|
19
20
|
version: 0.1.0
|
|
20
21
|
# Version of @emilia-protocol/gate this chart tracks; used as the default
|
|
21
22
|
# image tag when .Values.image.tag is empty.
|
|
22
|
-
appVersion: "0.
|
|
23
|
+
appVersion: "0.10.0"
|
|
23
24
|
kubeVersion: ">=1.23.0-0"
|
|
24
25
|
home: https://emiliaprotocol.ai
|
|
25
26
|
sources:
|
|
@@ -35,5 +36,6 @@ annotations:
|
|
|
35
36
|
emiliaprotocol.ai/chart-contract: EP-GATE-HELM-v1
|
|
36
37
|
emiliaprotocol.ai/maturity: experimental
|
|
37
38
|
emiliaprotocol.ai/deployment-model: byoc
|
|
39
|
+
emiliaprotocol.ai/replacement: emilia-gate-service
|
|
38
40
|
artifacthub.io/prerelease: "true"
|
|
39
41
|
artifacthub.io/license: Apache-2.0
|
|
@@ -6,8 +6,9 @@ EMILIA Gate — Trusted Action Firewall (chart {{ .Chart.Name }}-{{ .Chart.Versi
|
|
|
6
6
|
Image: {{ .Values.image.repository }}:{{ default .Chart.AppVersion .Values.image.tag }}
|
|
7
7
|
Replicas: {{ .Values.replicaCount }}
|
|
8
8
|
|
|
9
|
-
STATUS: reference chart — EXPERIMENTAL, BYOC.
|
|
10
|
-
|
|
9
|
+
STATUS: DEPRECATED reference chart — EXPERIMENTAL, BYOC. Prefer the
|
|
10
|
+
emilia-gate-service chart. Review the rendered manifests before trusting
|
|
11
|
+
production traffic to them.
|
|
11
12
|
|
|
12
13
|
1. Issuer keys (already enforced at render time — this release referenced the
|
|
13
14
|
existing Secret "{{ .Values.issuerKeys.existingSecret }}", key
|
|
@@ -7,6 +7,42 @@
|
|
|
7
7
|
{{- $name := default .Chart.Name .Values.nameOverride }}
|
|
8
8
|
{{- $fullname := default (printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-") .Values.fullnameOverride }}
|
|
9
9
|
{{- $manifestConfigMap := default $fullname .Values.manifest.existingConfigMap }}
|
|
10
|
+
{{- $consumption := .Values.sharedState.consumption }}
|
|
11
|
+
{{- $evidence := .Values.sharedState.evidence }}
|
|
12
|
+
{{- $consumptionAny := or (ne $consumption.envName "") (ne $consumption.existingSecret "") (ne $consumption.secretKey "") }}
|
|
13
|
+
{{- $evidenceAny := or (ne $evidence.envName "") (ne $evidence.existingSecret "") (ne $evidence.secretKey "") }}
|
|
14
|
+
{{- $consumptionConfigured := and (ne $consumption.envName "") (ne $consumption.existingSecret "") (ne $consumption.secretKey "") }}
|
|
15
|
+
{{- $evidenceConfigured := and (ne $evidence.envName "") (ne $evidence.existingSecret "") (ne $evidence.secretKey "") }}
|
|
16
|
+
{{- $reservedEnv := list "NODE_ENV" "EP_GATE_PORT" "EP_GATE_LOG_LEVEL" "EP_GATE_EVIDENCE_STRICT" "EP_GATE_MANIFEST_PATH" "EP_GATE_METRICS_ENABLED" "EP_GATE_ISSUER_KEYS" }}
|
|
17
|
+
{{- if and $consumptionAny (not $consumptionConfigured) }}
|
|
18
|
+
{{- fail "sharedState.consumption requires envName, existingSecret, and secretKey together" }}
|
|
19
|
+
{{- end }}
|
|
20
|
+
{{- if and $evidenceAny (not $evidenceConfigured) }}
|
|
21
|
+
{{- fail "sharedState.evidence requires envName, existingSecret, and secretKey together" }}
|
|
22
|
+
{{- end }}
|
|
23
|
+
{{- if and $consumptionConfigured (not (regexMatch "^[A-Za-z_][A-Za-z0-9_]*$" $consumption.envName)) }}
|
|
24
|
+
{{- fail "sharedState.consumption.envName must be a valid environment variable name" }}
|
|
25
|
+
{{- end }}
|
|
26
|
+
{{- if and $evidenceConfigured (not (regexMatch "^[A-Za-z_][A-Za-z0-9_]*$" $evidence.envName)) }}
|
|
27
|
+
{{- fail "sharedState.evidence.envName must be a valid environment variable name" }}
|
|
28
|
+
{{- end }}
|
|
29
|
+
{{- if and $consumptionConfigured (has $consumption.envName $reservedEnv) }}
|
|
30
|
+
{{- fail "sharedState.consumption.envName must not override a chart-managed environment variable" }}
|
|
31
|
+
{{- end }}
|
|
32
|
+
{{- if and $evidenceConfigured (has $evidence.envName $reservedEnv) }}
|
|
33
|
+
{{- fail "sharedState.evidence.envName must not override a chart-managed environment variable" }}
|
|
34
|
+
{{- end }}
|
|
35
|
+
{{- if and $consumptionConfigured $evidenceConfigured (eq $consumption.envName $evidence.envName) }}
|
|
36
|
+
{{- fail "sharedState.consumption.envName and sharedState.evidence.envName must be distinct" }}
|
|
37
|
+
{{- end }}
|
|
38
|
+
{{- range $extra := .Values.gate.extraEnv }}
|
|
39
|
+
{{- if or (has $extra.name $reservedEnv) (and $consumptionConfigured (eq $extra.name $consumption.envName)) (and $evidenceConfigured (eq $extra.name $evidence.envName)) }}
|
|
40
|
+
{{- fail (printf "gate.extraEnv name %s duplicates a chart-managed or shared-state environment variable" $extra.name) }}
|
|
41
|
+
{{- end }}
|
|
42
|
+
{{- end }}
|
|
43
|
+
{{- if and (gt (int .Values.replicaCount) 1) (not (and $consumptionConfigured $evidenceConfigured)) }}
|
|
44
|
+
{{- fail "deprecated emilia-gate refuses replicaCount > 1 without complete Secret-backed sharedState.consumption and sharedState.evidence references; prefer emilia-gate-service" }}
|
|
45
|
+
{{- end }}
|
|
10
46
|
apiVersion: apps/v1
|
|
11
47
|
kind: Deployment
|
|
12
48
|
metadata:
|
|
@@ -21,6 +57,8 @@ metadata:
|
|
|
21
57
|
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version }}
|
|
22
58
|
emiliaprotocol.ai/chart-contract: EP-GATE-HELM-v1
|
|
23
59
|
emiliaprotocol.ai/maturity: experimental
|
|
60
|
+
emiliaprotocol.ai/deprecated: "true"
|
|
61
|
+
emiliaprotocol.ai/replacement: emilia-gate-service
|
|
24
62
|
spec:
|
|
25
63
|
replicas: {{ .Values.replicaCount }}
|
|
26
64
|
selector:
|
|
@@ -55,7 +93,7 @@ spec:
|
|
|
55
93
|
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
|
56
94
|
containers:
|
|
57
95
|
- name: gate
|
|
58
|
-
image: "{{ .Values.image.repository }}:{{ default .Chart.AppVersion .Values.image.tag }}"
|
|
96
|
+
image: "{{ required "image.repository is required; build and push a BYOC image before rendering this deprecated chart" .Values.image.repository }}:{{ default .Chart.AppVersion .Values.image.tag }}"
|
|
59
97
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
|
60
98
|
securityContext:
|
|
61
99
|
{{- toYaml .Values.containerSecurityContext | nindent 12 }}
|
|
@@ -86,6 +124,22 @@ spec:
|
|
|
86
124
|
secretKeyRef:
|
|
87
125
|
name: {{ required "issuerKeys.existingSecret is required. This chart NEVER renders key material — create the Secret out-of-band (see README.md) and set issuerKeys.existingSecret to its name." .Values.issuerKeys.existingSecret }}
|
|
88
126
|
key: {{ .Values.issuerKeys.secretKey }}
|
|
127
|
+
{{- if $consumptionConfigured }}
|
|
128
|
+
- name: {{ $consumption.envName }}
|
|
129
|
+
valueFrom:
|
|
130
|
+
secretKeyRef:
|
|
131
|
+
name: {{ $consumption.existingSecret }}
|
|
132
|
+
key: {{ $consumption.secretKey }}
|
|
133
|
+
optional: false
|
|
134
|
+
{{- end }}
|
|
135
|
+
{{- if $evidenceConfigured }}
|
|
136
|
+
- name: {{ $evidence.envName }}
|
|
137
|
+
valueFrom:
|
|
138
|
+
secretKeyRef:
|
|
139
|
+
name: {{ $evidence.existingSecret }}
|
|
140
|
+
key: {{ $evidence.secretKey }}
|
|
141
|
+
optional: false
|
|
142
|
+
{{- end }}
|
|
89
143
|
{{- with .Values.gate.extraEnv }}
|
|
90
144
|
{{- toYaml . | nindent 12 }}
|
|
91
145
|
{{- end }}
|
|
@@ -13,12 +13,15 @@
|
|
|
13
13
|
# capabilities, no service-account token). Loosen deliberately, not
|
|
14
14
|
# accidentally.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
# This deprecated chart uses in-memory state unless a BYOC image consumes both
|
|
17
|
+
# Secret-backed shared-state references below. One replica is the only safe
|
|
18
|
+
# default. Prefer the emilia-gate-service chart for production deployments.
|
|
19
|
+
replicaCount: 1
|
|
17
20
|
|
|
18
21
|
image:
|
|
19
22
|
# BYOC: build and push your own image of the gate service; there is no
|
|
20
23
|
# official public image while the chart is experimental.
|
|
21
|
-
repository:
|
|
24
|
+
repository: ""
|
|
22
25
|
# Defaults to the chart's appVersion when empty.
|
|
23
26
|
tag: ""
|
|
24
27
|
pullPolicy: IfNotPresent
|
|
@@ -43,6 +46,19 @@ gate:
|
|
|
43
46
|
# EnvVar objects — supports valueFrom for your own Secret/ConfigMap refs).
|
|
44
47
|
extraEnv: []
|
|
45
48
|
|
|
49
|
+
# Legacy multi-replica guard. These references are rendered as environment
|
|
50
|
+
# variables for a BYOC image that implements the corresponding durable adapters.
|
|
51
|
+
# Setting replicaCount > 1 is refused unless BOTH references are complete.
|
|
52
|
+
sharedState:
|
|
53
|
+
consumption:
|
|
54
|
+
envName: ""
|
|
55
|
+
existingSecret: ""
|
|
56
|
+
secretKey: ""
|
|
57
|
+
evidence:
|
|
58
|
+
envName: ""
|
|
59
|
+
existingSecret: ""
|
|
60
|
+
secretKey: ""
|
|
61
|
+
|
|
46
62
|
# ---------------------------------------------------------------------------
|
|
47
63
|
# Issuer keys — REQUIRED, referenced, never rendered.
|
|
48
64
|
# Create the Secret yourself, e.g.:
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
apiVersion: v2
|
|
2
|
+
name: emilia-gate-service
|
|
3
|
+
description: BYOC deployment chart for the EMILIA Gate service
|
|
4
|
+
type: application
|
|
5
|
+
version: 0.2.0
|
|
6
|
+
appVersion: "0.2.0"
|
|
7
|
+
kubeVersion: ">=1.27.0-0"
|
|
8
|
+
annotations:
|
|
9
|
+
emiliaprotocol.ai/deployment-model: byoc
|
|
10
|
+
emiliaprotocol.ai/maturity: reference
|
|
11
|
+
artifacthub.io/license: Apache-2.0
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# EMILIA Gate service Helm chart
|
|
2
|
+
|
|
3
|
+
This chart is a BYOC reference deployment for `apps/gate-service`. It does not
|
|
4
|
+
name or imply an official published image. Build `Dockerfile.gate`, push the
|
|
5
|
+
result to a registry you control, and pass an immutable digest where possible.
|
|
6
|
+
|
|
7
|
+
The chart intentionally refuses to render with its default values. Supply:
|
|
8
|
+
|
|
9
|
+
- `image.repository` and exactly one of `image.digest` or `image.tag`;
|
|
10
|
+
- `configuration.existingSecret` containing `gate.config.mjs` and
|
|
11
|
+
`migrate.mjs`;
|
|
12
|
+
- `secrets.postgres.existingSecret` with the database URL key;
|
|
13
|
+
- `migrations.postgres.existingSecret` with a distinct DDL-capable login URL;
|
|
14
|
+
- `secrets.apiToken.existingSecret` with the Gate action-API bearer token;
|
|
15
|
+
- optionally, `secrets.kms.existingSecret` when the operator config uses a KMS-backed extension;
|
|
16
|
+
- `secrets.issuerRoots.existingSecret` with the pinned issuer-root set.
|
|
17
|
+
|
|
18
|
+
The chart never renders a Kubernetes `Secret`. Secret names and keys are wired
|
|
19
|
+
with non-optional references. The migration hook receives the operator config
|
|
20
|
+
and a mandatory Postgres reference distinct from the runtime Secret. The
|
|
21
|
+
service receives the config and runtime
|
|
22
|
+
references. The config module must satisfy the durable store contracts in
|
|
23
|
+
`apps/gate-service/README.md`. The chart does not substitute an in-memory
|
|
24
|
+
adapter or invent a persistence schema on the operator's behalf.
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
helm upgrade --install emilia-gate \
|
|
30
|
+
packages/gate/deploy/helm/emilia-gate-service \
|
|
31
|
+
--namespace emilia-gate --create-namespace \
|
|
32
|
+
--set-string image.repository=registry.example.com/security/emilia-gate-service \
|
|
33
|
+
--set-string image.digest=sha256:REPLACE_WITH_IMAGE_DIGEST \
|
|
34
|
+
--set-string configuration.existingSecret=emilia-gate-configuration \
|
|
35
|
+
--set-string secrets.postgres.existingSecret=emilia-gate-postgres \
|
|
36
|
+
--set-string migrations.postgres.existingSecret=emilia-gate-postgres-migrate \
|
|
37
|
+
--set-string migrations.postgres.key=database-url \
|
|
38
|
+
--set-string secrets.apiToken.existingSecret=emilia-gate-api-token \
|
|
39
|
+
--set-string secrets.issuerRoots.existingSecret=emilia-gate-issuer-roots \
|
|
40
|
+
--atomic --wait
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The pre-install/pre-upgrade migration Job must complete before Helm rolls the
|
|
44
|
+
Deployment. It is bounded by an active deadline and runs non-root with a
|
|
45
|
+
read-only root filesystem. The Deployment defaults to two replicas,
|
|
46
|
+
`maxUnavailable: 0`, a one-pod PDB, resource requests/limits, startup/liveness/
|
|
47
|
+
readiness probes, and a writable memory/scratch volume only at `/tmp`.
|
|
48
|
+
Rendering fails if migrations are enabled without a migration Secret, or if
|
|
49
|
+
that Secret name equals the runtime Postgres Secret. Helm cannot compare Secret
|
|
50
|
+
contents; provision different database login credentials in the two Secrets
|
|
51
|
+
and verify the runtime login has no DDL privileges.
|
|
52
|
+
|
|
53
|
+
## Network policy
|
|
54
|
+
|
|
55
|
+
The default NetworkPolicy permits DNS, same-namespace ingress, and Postgres on
|
|
56
|
+
port 5432 only to a same-namespace pod labeled
|
|
57
|
+
`app.kubernetes.io/name=postgresql`. Managed Postgres requires an operator to
|
|
58
|
+
set `networkPolicy.egress.postgres.podSelector=null` plus stable
|
|
59
|
+
`networkPolicy.egress.postgres.cidrs` (or provide an additional CNI policy).
|
|
60
|
+
|
|
61
|
+
Standard Kubernetes NetworkPolicy does not support DNS names. GitHub API,
|
|
62
|
+
cloud KMS, and SIEM endpoints commonly use changing address ranges. Do not copy
|
|
63
|
+
today's public IP list into a long-lived manifest and assume it remains valid.
|
|
64
|
+
Prefer a controlled egress proxy with stable CIDRs or a CNI FQDN policy, then
|
|
65
|
+
set `kmsCidrs`, `githubCidrs`, and `siemCidrs` to the stable enforcement points.
|
|
66
|
+
Until configured, those outbound paths remain denied.
|
|
67
|
+
|
|
68
|
+
On a first Helm install, the migration is a pre-install hook and therefore runs
|
|
69
|
+
before this release's regular NetworkPolicy exists. Enforce a namespace-level
|
|
70
|
+
baseline egress policy before installation; subsequent upgrades are covered by
|
|
71
|
+
the existing release policy.
|
|
72
|
+
|
|
73
|
+
## Clusterless check
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
packages/gate/deploy/helm/emilia-gate-service/tests/render-check.sh
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The script runs strict Helm linting, renders with documentation-only registry
|
|
80
|
+
and Secret names, asserts the hardening resources and settings, and rejects any
|
|
81
|
+
rendered `Secret`. It does not connect to a cluster.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
EMILIA Gate service was rendered as a BYOC deployment.
|
|
2
|
+
|
|
3
|
+
Image: {{ include "emilia-gate-service.image" . }}
|
|
4
|
+
Service: {{ include "emilia-gate-service.fullname" . }}.{{ .Release.Namespace }}.svc:{{ .Values.service.port }}
|
|
5
|
+
|
|
6
|
+
The chart did not create any Secret. Operator configuration, Postgres, KMS, and
|
|
7
|
+
issuer-root values are referenced from existing Secret names supplied at
|
|
8
|
+
install time.
|
|
9
|
+
|
|
10
|
+
Readiness:
|
|
11
|
+
kubectl -n {{ .Release.Namespace }} port-forward service/{{ include "emilia-gate-service.fullname" . }} 8080:{{ .Values.service.port }}
|
|
12
|
+
curl --fail http://127.0.0.1:8080{{ .Values.probes.readiness.path }}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{{- define "emilia-gate-service.name" -}}
|
|
2
|
+
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
|
3
|
+
{{- end -}}
|
|
4
|
+
|
|
5
|
+
{{- define "emilia-gate-service.fullname" -}}
|
|
6
|
+
{{- if .Values.fullnameOverride -}}
|
|
7
|
+
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
|
8
|
+
{{- else -}}
|
|
9
|
+
{{- printf "%s-%s" .Release.Name (include "emilia-gate-service.name" .) | trunc 63 | trimSuffix "-" -}}
|
|
10
|
+
{{- end -}}
|
|
11
|
+
{{- end -}}
|
|
12
|
+
|
|
13
|
+
{{- define "emilia-gate-service.labels" -}}
|
|
14
|
+
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | quote }}
|
|
15
|
+
app.kubernetes.io/name: {{ include "emilia-gate-service.name" . }}
|
|
16
|
+
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
17
|
+
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
|
18
|
+
app.kubernetes.io/part-of: emilia-gate
|
|
19
|
+
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
|
20
|
+
emiliaprotocol.ai/deployment-model: byoc
|
|
21
|
+
{{- end -}}
|
|
22
|
+
|
|
23
|
+
{{- define "emilia-gate-service.baseSelectorLabels" -}}
|
|
24
|
+
app.kubernetes.io/name: {{ include "emilia-gate-service.name" . }}
|
|
25
|
+
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
26
|
+
{{- end -}}
|
|
27
|
+
|
|
28
|
+
{{- define "emilia-gate-service.serviceSelectorLabels" -}}
|
|
29
|
+
{{ include "emilia-gate-service.baseSelectorLabels" . }}
|
|
30
|
+
app.kubernetes.io/component: service
|
|
31
|
+
{{- end -}}
|
|
32
|
+
|
|
33
|
+
{{- define "emilia-gate-service.image" -}}
|
|
34
|
+
{{- $repository := required "image.repository is required; build and push Dockerfile.gate to a registry you control" .Values.image.repository -}}
|
|
35
|
+
{{- if and .Values.image.tag .Values.image.digest -}}
|
|
36
|
+
{{- fail "set exactly one of image.tag or image.digest" -}}
|
|
37
|
+
{{- else if .Values.image.digest -}}
|
|
38
|
+
{{- if not (regexMatch "^sha256:[a-f0-9]{64}$" .Values.image.digest) -}}
|
|
39
|
+
{{- fail "image.digest must be sha256:<64 lowercase hex characters>" -}}
|
|
40
|
+
{{- end -}}
|
|
41
|
+
{{- printf "%s@%s" $repository .Values.image.digest -}}
|
|
42
|
+
{{- else if .Values.image.tag -}}
|
|
43
|
+
{{- if eq (lower .Values.image.tag) "latest" -}}
|
|
44
|
+
{{- fail "image.tag=latest is mutable and refused; use an immutable version tag or digest" -}}
|
|
45
|
+
{{- end -}}
|
|
46
|
+
{{- printf "%s:%s" $repository .Values.image.tag -}}
|
|
47
|
+
{{- else -}}
|
|
48
|
+
{{- fail "set exactly one of image.tag or image.digest; mutable implicit latest is refused" -}}
|
|
49
|
+
{{- end -}}
|
|
50
|
+
{{- end -}}
|
|
51
|
+
|
|
52
|
+
{{- define "emilia-gate-service.postgresSecret" -}}
|
|
53
|
+
{{- required "secrets.postgres.existingSecret is required; the chart only references existing Secrets" .Values.secrets.postgres.existingSecret -}}
|
|
54
|
+
{{- end -}}
|
|
55
|
+
|
|
56
|
+
{{- define "emilia-gate-service.migrationPostgresSecret" -}}
|
|
57
|
+
{{- $runtimeSecret := include "emilia-gate-service.postgresSecret" . -}}
|
|
58
|
+
{{- $migrationSecret := required "migrations.postgres.existingSecret is required when migrations.enabled=true; migration credentials never fall back to the runtime Secret" .Values.migrations.postgres.existingSecret -}}
|
|
59
|
+
{{- if eq $migrationSecret $runtimeSecret -}}
|
|
60
|
+
{{- fail "migrations.postgres.existingSecret must name a Secret distinct from secrets.postgres.existingSecret" -}}
|
|
61
|
+
{{- end -}}
|
|
62
|
+
{{- $migrationSecret -}}
|
|
63
|
+
{{- end -}}
|
|
64
|
+
|
|
65
|
+
{{- define "emilia-gate-service.migrationPostgresKey" -}}
|
|
66
|
+
{{- required "migrations.postgres.key is required when migrations.enabled=true" .Values.migrations.postgres.key -}}
|
|
67
|
+
{{- end -}}
|
|
68
|
+
|
|
69
|
+
{{- define "emilia-gate-service.configurationSecret" -}}
|
|
70
|
+
{{- required "configuration.existingSecret is required; apps/gate-service requires an operator-owned gate.config.mjs" .Values.configuration.existingSecret -}}
|
|
71
|
+
{{- end -}}
|
|
72
|
+
|
|
73
|
+
{{- define "emilia-gate-service.apiTokenSecret" -}}
|
|
74
|
+
{{- required "secrets.apiToken.existingSecret is required; action routes require operator authentication" .Values.secrets.apiToken.existingSecret -}}
|
|
75
|
+
{{- end -}}
|
|
76
|
+
|
|
77
|
+
{{- define "emilia-gate-service.kmsSecret" -}}
|
|
78
|
+
{{- .Values.secrets.kms.existingSecret -}}
|
|
79
|
+
{{- end -}}
|
|
80
|
+
|
|
81
|
+
{{- define "emilia-gate-service.issuerRootsSecret" -}}
|
|
82
|
+
{{- required "secrets.issuerRoots.existingSecret is required; the chart only references existing Secrets" .Values.secrets.issuerRoots.existingSecret -}}
|
|
83
|
+
{{- end -}}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
apiVersion: apps/v1
|
|
2
|
+
kind: Deployment
|
|
3
|
+
metadata:
|
|
4
|
+
name: {{ include "emilia-gate-service.fullname" . }}
|
|
5
|
+
namespace: {{ .Release.Namespace }}
|
|
6
|
+
labels:
|
|
7
|
+
{{- include "emilia-gate-service.labels" . | nindent 4 }}
|
|
8
|
+
spec:
|
|
9
|
+
replicas: {{ .Values.replicaCount }}
|
|
10
|
+
revisionHistoryLimit: 3
|
|
11
|
+
strategy:
|
|
12
|
+
type: RollingUpdate
|
|
13
|
+
rollingUpdate:
|
|
14
|
+
maxUnavailable: 0
|
|
15
|
+
maxSurge: 1
|
|
16
|
+
selector:
|
|
17
|
+
matchLabels:
|
|
18
|
+
{{- include "emilia-gate-service.serviceSelectorLabels" . | nindent 6 }}
|
|
19
|
+
template:
|
|
20
|
+
metadata:
|
|
21
|
+
labels:
|
|
22
|
+
{{- include "emilia-gate-service.serviceSelectorLabels" . | nindent 8 }}
|
|
23
|
+
{{- with .Values.podLabels }}
|
|
24
|
+
{{- toYaml . | nindent 8 }}
|
|
25
|
+
{{- end }}
|
|
26
|
+
{{- with .Values.podAnnotations }}
|
|
27
|
+
annotations:
|
|
28
|
+
{{- toYaml . | nindent 8 }}
|
|
29
|
+
{{- end }}
|
|
30
|
+
spec:
|
|
31
|
+
serviceAccountName: {{ include "emilia-gate-service.fullname" . }}
|
|
32
|
+
automountServiceAccountToken: false
|
|
33
|
+
terminationGracePeriodSeconds: {{ .Values.runtime.terminationGracePeriodSeconds }}
|
|
34
|
+
securityContext:
|
|
35
|
+
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
|
36
|
+
{{- with .Values.imagePullSecrets }}
|
|
37
|
+
imagePullSecrets:
|
|
38
|
+
{{- toYaml . | nindent 8 }}
|
|
39
|
+
{{- end }}
|
|
40
|
+
containers:
|
|
41
|
+
- name: gate-service
|
|
42
|
+
image: {{ include "emilia-gate-service.image" . | quote }}
|
|
43
|
+
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
|
44
|
+
securityContext:
|
|
45
|
+
{{- toYaml .Values.containerSecurityContext | nindent 12 }}
|
|
46
|
+
ports:
|
|
47
|
+
- name: http
|
|
48
|
+
containerPort: {{ .Values.runtime.port }}
|
|
49
|
+
protocol: TCP
|
|
50
|
+
env:
|
|
51
|
+
- name: NODE_ENV
|
|
52
|
+
value: production
|
|
53
|
+
- name: HOST
|
|
54
|
+
value: 0.0.0.0
|
|
55
|
+
- name: PORT
|
|
56
|
+
value: {{ .Values.runtime.port | quote }}
|
|
57
|
+
- name: EMILIA_GATE_CONFIG
|
|
58
|
+
value: {{ printf "%s/%s" .Values.configuration.mountPath .Values.configuration.configKey | quote }}
|
|
59
|
+
- name: EP_GATE_PORT
|
|
60
|
+
value: {{ .Values.runtime.port | quote }}
|
|
61
|
+
- name: EP_GATE_LOG_LEVEL
|
|
62
|
+
value: {{ .Values.runtime.logLevel | quote }}
|
|
63
|
+
- name: {{ .Values.secrets.postgres.envName }}
|
|
64
|
+
valueFrom:
|
|
65
|
+
secretKeyRef:
|
|
66
|
+
name: {{ include "emilia-gate-service.postgresSecret" . }}
|
|
67
|
+
key: {{ .Values.secrets.postgres.key }}
|
|
68
|
+
optional: false
|
|
69
|
+
- name: {{ .Values.secrets.apiToken.envName }}
|
|
70
|
+
valueFrom:
|
|
71
|
+
secretKeyRef:
|
|
72
|
+
name: {{ include "emilia-gate-service.apiTokenSecret" . }}
|
|
73
|
+
key: {{ .Values.secrets.apiToken.key }}
|
|
74
|
+
optional: false
|
|
75
|
+
{{- if .Values.secrets.kms.existingSecret }}
|
|
76
|
+
- name: {{ .Values.secrets.kms.envName }}
|
|
77
|
+
valueFrom:
|
|
78
|
+
secretKeyRef:
|
|
79
|
+
name: {{ include "emilia-gate-service.kmsSecret" . }}
|
|
80
|
+
key: {{ .Values.secrets.kms.key }}
|
|
81
|
+
optional: false
|
|
82
|
+
{{- end }}
|
|
83
|
+
- name: {{ .Values.secrets.issuerRoots.envName }}
|
|
84
|
+
valueFrom:
|
|
85
|
+
secretKeyRef:
|
|
86
|
+
name: {{ include "emilia-gate-service.issuerRootsSecret" . }}
|
|
87
|
+
key: {{ .Values.secrets.issuerRoots.key }}
|
|
88
|
+
optional: false
|
|
89
|
+
{{- with .Values.runtime.extraEnv }}
|
|
90
|
+
{{- toYaml . | nindent 12 }}
|
|
91
|
+
{{- end }}
|
|
92
|
+
startupProbe:
|
|
93
|
+
httpGet:
|
|
94
|
+
path: {{ .Values.probes.startup.path }}
|
|
95
|
+
port: http
|
|
96
|
+
periodSeconds: {{ .Values.probes.startup.periodSeconds }}
|
|
97
|
+
timeoutSeconds: {{ .Values.probes.startup.timeoutSeconds }}
|
|
98
|
+
failureThreshold: {{ .Values.probes.startup.failureThreshold }}
|
|
99
|
+
livenessProbe:
|
|
100
|
+
httpGet:
|
|
101
|
+
path: {{ .Values.probes.liveness.path }}
|
|
102
|
+
port: http
|
|
103
|
+
periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
|
|
104
|
+
timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }}
|
|
105
|
+
failureThreshold: {{ .Values.probes.liveness.failureThreshold }}
|
|
106
|
+
readinessProbe:
|
|
107
|
+
httpGet:
|
|
108
|
+
path: {{ .Values.probes.readiness.path }}
|
|
109
|
+
port: http
|
|
110
|
+
periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
|
|
111
|
+
timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }}
|
|
112
|
+
failureThreshold: {{ .Values.probes.readiness.failureThreshold }}
|
|
113
|
+
resources:
|
|
114
|
+
{{- toYaml .Values.resources | nindent 12 }}
|
|
115
|
+
volumeMounts:
|
|
116
|
+
- name: tmp
|
|
117
|
+
mountPath: /tmp
|
|
118
|
+
- name: configuration
|
|
119
|
+
mountPath: {{ .Values.configuration.mountPath }}
|
|
120
|
+
readOnly: true
|
|
121
|
+
volumes:
|
|
122
|
+
- name: tmp
|
|
123
|
+
emptyDir:
|
|
124
|
+
sizeLimit: 64Mi
|
|
125
|
+
- name: configuration
|
|
126
|
+
secret:
|
|
127
|
+
secretName: {{ include "emilia-gate-service.configurationSecret" . }}
|
|
128
|
+
optional: false
|
|
129
|
+
defaultMode: 0440
|
|
130
|
+
items:
|
|
131
|
+
- key: {{ .Values.configuration.configKey }}
|
|
132
|
+
path: {{ .Values.configuration.configKey }}
|
|
133
|
+
topologySpreadConstraints:
|
|
134
|
+
{{- range .Values.topologySpreadConstraints }}
|
|
135
|
+
- maxSkew: {{ .maxSkew }}
|
|
136
|
+
topologyKey: {{ .topologyKey }}
|
|
137
|
+
whenUnsatisfiable: {{ .whenUnsatisfiable }}
|
|
138
|
+
labelSelector:
|
|
139
|
+
matchLabels:
|
|
140
|
+
{{- include "emilia-gate-service.serviceSelectorLabels" $ | nindent 14 }}
|
|
141
|
+
{{- end }}
|
|
142
|
+
{{- with .Values.nodeSelector }}
|
|
143
|
+
nodeSelector:
|
|
144
|
+
{{- toYaml . | nindent 8 }}
|
|
145
|
+
{{- end }}
|
|
146
|
+
{{- with .Values.tolerations }}
|
|
147
|
+
tolerations:
|
|
148
|
+
{{- toYaml . | nindent 8 }}
|
|
149
|
+
{{- end }}
|
|
150
|
+
{{- with .Values.affinity }}
|
|
151
|
+
affinity:
|
|
152
|
+
{{- toYaml . | nindent 8 }}
|
|
153
|
+
{{- end }}
|