lcp-reference-platform 1.0.0__tar.gz

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.
Files changed (47) hide show
  1. lcp_reference_platform-1.0.0/.gitignore +16 -0
  2. lcp_reference_platform-1.0.0/Dockerfile +33 -0
  3. lcp_reference_platform-1.0.0/PKG-INFO +22 -0
  4. lcp_reference_platform-1.0.0/README.md +248 -0
  5. lcp_reference_platform-1.0.0/docker-compose.yml +34 -0
  6. lcp_reference_platform-1.0.0/kubernetes/README.md +94 -0
  7. lcp_reference_platform-1.0.0/kubernetes/configmap.yaml +21 -0
  8. lcp_reference_platform-1.0.0/kubernetes/deployment.yaml +161 -0
  9. lcp_reference_platform-1.0.0/kubernetes/hpa.example.yaml +43 -0
  10. lcp_reference_platform-1.0.0/kubernetes/network-policy.example.yaml +80 -0
  11. lcp_reference_platform-1.0.0/kubernetes/pod-disruption-budget.yaml +19 -0
  12. lcp_reference_platform-1.0.0/kubernetes/pvc.example.yaml +11 -0
  13. lcp_reference_platform-1.0.0/kubernetes/secret.example.yaml +12 -0
  14. lcp_reference_platform-1.0.0/kubernetes/service.yaml +13 -0
  15. lcp_reference_platform-1.0.0/kubernetes/tests/verify-images/README.md +42 -0
  16. lcp_reference_platform-1.0.0/kubernetes/tests/verify-images/kyverno-test.yaml +32 -0
  17. lcp_reference_platform-1.0.0/kubernetes/tests/verify-images/provenance-policy.yaml +33 -0
  18. lcp_reference_platform-1.0.0/kubernetes/tests/verify-images/resources.yaml +35 -0
  19. lcp_reference_platform-1.0.0/kubernetes/tests/verify-images/sbom-policy.yaml +33 -0
  20. lcp_reference_platform-1.0.0/kubernetes/tests/verify-images/signature-policy.yaml +31 -0
  21. lcp_reference_platform-1.0.0/kubernetes/verify-images-kyverno.example.yaml +91 -0
  22. lcp_reference_platform-1.0.0/lcp_platform/__init__.py +17 -0
  23. lcp_reference_platform-1.0.0/lcp_platform/admin.py +90 -0
  24. lcp_reference_platform-1.0.0/lcp_platform/api_keys.py +50 -0
  25. lcp_reference_platform-1.0.0/lcp_platform/attachments.py +361 -0
  26. lcp_reference_platform-1.0.0/lcp_platform/auth.py +202 -0
  27. lcp_reference_platform-1.0.0/lcp_platform/config.py +157 -0
  28. lcp_reference_platform-1.0.0/lcp_platform/crypto.py +94 -0
  29. lcp_reference_platform-1.0.0/lcp_platform/database.py +18 -0
  30. lcp_reference_platform-1.0.0/lcp_platform/mapping.py +309 -0
  31. lcp_reference_platform-1.0.0/lcp_platform/matching.py +147 -0
  32. lcp_reference_platform-1.0.0/lcp_platform/messages.py +265 -0
  33. lcp_reference_platform-1.0.0/lcp_platform/postgres.py +235 -0
  34. lcp_reference_platform-1.0.0/lcp_platform/router.py +1116 -0
  35. lcp_reference_platform-1.0.0/lcp_platform/secrets.py +70 -0
  36. lcp_reference_platform-1.0.0/lcp_platform/security.py +76 -0
  37. lcp_reference_platform-1.0.0/lcp_platform/server.py +97 -0
  38. lcp_reference_platform-1.0.0/lcp_platform/service.py +147 -0
  39. lcp_reference_platform-1.0.0/lcp_platform/storage.py +1300 -0
  40. lcp_reference_platform-1.0.0/lcp_platform/validation.py +118 -0
  41. lcp_reference_platform-1.0.0/lcp_platform/worker.py +30 -0
  42. lcp_reference_platform-1.0.0/lcp_platform/wsgi.py +50 -0
  43. lcp_reference_platform-1.0.0/pyproject.toml +33 -0
  44. lcp_reference_platform-1.0.0/tests/test_api_keys.py +142 -0
  45. lcp_reference_platform-1.0.0/tests/test_new_flows.py +387 -0
  46. lcp_reference_platform-1.0.0/tests/test_platform.py +409 -0
  47. lcp_reference_platform-1.0.0/tests/test_postgres_integration.py +140 -0
@@ -0,0 +1,16 @@
1
+ # macOS
2
+ .DS_Store
3
+
4
+ # Local tooling artifacts — never published
5
+ .gitnexus/
6
+ .claude/
7
+ graphify-out/
8
+ CLAUDE.md
9
+ .hermes.md
10
+ *.pyc
11
+ __pycache__/
12
+
13
+ # Local platform/runtime state
14
+ .venv/
15
+ data/
16
+ *.sqlite3
@@ -0,0 +1,33 @@
1
+ FROM python:3.12-alpine@sha256:78098ea6a3a9c6a7727a5d4674e4a44e57e01fac878ee9cb4d24a86bd93916ff
2
+
3
+ ENV PYTHONDONTWRITEBYTECODE=1 \
4
+ PYTHONUNBUFFERED=1 \
5
+ PIP_DISABLE_PIP_VERSION_CHECK=1 \
6
+ PIP_NO_CACHE_DIR=1 \
7
+ LCP_SCHEMA_DIR=/app/schemas \
8
+ LCP_DATABASE_PATH=/app/data/lcp.sqlite3 \
9
+ LCP_HOST=0.0.0.0 \
10
+ LCP_PORT=8080
11
+
12
+ WORKDIR /app
13
+
14
+ COPY schemas /app/schemas
15
+ COPY verticals /app/verticals
16
+ COPY examples/sandbox /app/examples/sandbox
17
+ COPY implementations/reference-platform /app/implementations/reference-platform
18
+
19
+ RUN python -m pip install --no-cache-dir --upgrade pip 'setuptools>=78.1.1' \
20
+ && python -m pip install --no-cache-dir -e '/app/implementations/reference-platform[production]' \
21
+ && adduser -D -u 10001 -h /home/lcp -s /sbin/nologin lcp \
22
+ && mkdir -p /app/data \
23
+ && chown -R lcp:lcp /app/data /app/implementations
24
+
25
+ # The image has no compiler toolchain or application build dependencies. The
26
+ # Kubernetes example additionally enforces read-only rootfs, no privilege
27
+ # escalation, and zero Linux capabilities.
28
+ USER lcp
29
+
30
+ EXPOSE 8080
31
+ STOPSIGNAL SIGTERM
32
+
33
+ CMD ["lcp-platform"]
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.5
2
+ Name: lcp-reference-platform
3
+ Version: 1.0.0
4
+ Summary: Production-oriented reference platform and router for LCP Lead Context Protocol deployments.
5
+ Project-URL: Documentation, https://github.com/SpearSystems/LCP
6
+ Project-URL: Source, https://github.com/SpearSystems/LCP
7
+ License-Expression: Apache-2.0
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: cryptography>=42.0.0
10
+ Requires-Dist: httpx>=0.27.0
11
+ Requires-Dist: jsonschema>=4.20.0
12
+ Requires-Dist: referencing>=0.35.0
13
+ Provides-Extra: object-storage
14
+ Requires-Dist: boto3>=1.35.0; extra == 'object-storage'
15
+ Requires-Dist: clamd>=1.0.2; extra == 'object-storage'
16
+ Provides-Extra: postgres
17
+ Requires-Dist: psycopg[binary]>=3.2; extra == 'postgres'
18
+ Provides-Extra: production
19
+ Requires-Dist: boto3>=1.35.0; extra == 'production'
20
+ Requires-Dist: clamd>=1.0.2; extra == 'production'
21
+ Requires-Dist: gunicorn>=23.0; extra == 'production'
22
+ Requires-Dist: psycopg[binary]>=3.2; extra == 'production'
@@ -0,0 +1,248 @@
1
+ # LCP Reference Platform
2
+
3
+ Production-oriented reference HTTP platform and router for the Lead Context
4
+ Protocol. It accepts publisher leads, applies buyer offers, runs the
5
+ ping/bid/post flow, persists state, and delivers signed webhooks with retries.
6
+
7
+ This is an implementation foundation, not a hosted exchange. Production
8
+ operators must still provide TLS termination, secret management, database
9
+ backups, observability, access control, retention policies, and deployment
10
+ hardening.
11
+
12
+ ## Components
13
+
14
+ - SQLite persistence for local/single-node use and a Postgres backend for multi-node production; replaceable through the `Store` boundary.
15
+ - HTTP intake for `lead`, `call`, `bid`, and lifecycle `event` messages.
16
+ - Declarative publisher form mapping with versioned brand/flow normalization and audit digests.
17
+ - Encrypted out-of-band attachment upload/download for contracts, evidence, and call records, with a local AES-GCM backend and production S3-compatible/SSE-KMS adapter, residency controls, and fail-closed ClamAV scanning.
18
+ - Structured call payable evaluation and per-offer monthly quota reporting.
19
+ - JSON Schema and strict ping-safe validation.
20
+ - Bearer API-key and HMAC authentication.
21
+ - Deterministic offer matching and auction selection.
22
+ - At-least-once buyer webhook delivery.
23
+ - Standalone delivery worker for multi-process deployments.
24
+ - WSGI entry point for a production HTTP process manager.
25
+ - Admin CLI for credentials, offers, and controlled lead erasure.
26
+
27
+ See [the implementation decisions](../../docs/IMPLEMENTATION-DECISIONS.md) for
28
+ matching, signing, retry, and sandbox behavior. See the
29
+ [supply-chain security guide](../../docs/SUPPLY-CHAIN-SECURITY.md) for package,
30
+ image, and release controls, and the [container verification guide](../../docs/CONTAINER-SUPPLY-CHAIN.md)
31
+ for signing, provenance, and admission enforcement.
32
+
33
+ ## Install
34
+
35
+ From the repository root:
36
+
37
+ ```bash
38
+ # Local/single-node profile.
39
+ python3 -m pip install -e implementations/reference-platform
40
+ export LCP_SCHEMA_DIR="$PWD/schemas"
41
+ export LCP_DATABASE_PATH="$PWD/data/lcp.sqlite3"
42
+ # Generate once, store securely, and reuse for this database:
43
+ # python3 -c 'import base64,secrets; print(base64.urlsafe_b64encode(secrets.token_bytes(32)).decode())'
44
+ export LCP_PII_ENCRYPTION_KEY="<persisted-urlsafe-base64-32-byte-key>"
45
+
46
+ # Production profile.
47
+ python3 -m pip install -e 'implementations/reference-platform[production]'
48
+ export LCP_DATABASE_URL='postgresql://lcp_app:<password>@postgres.internal:5432/lcp?sslmode=verify-full'
49
+ export LCP_PII_ENCRYPTION_KEY='<urlsafe-base64-32-byte-key>'
50
+ ```
51
+
52
+ The package targets Python 3.10+. The `[production]` extra adds Gunicorn, Psycopg, boto3, and the ClamAV client
53
+ for the Postgres/WSGI/object-storage deployment profile. Configure
54
+ `LCP_ATTACHMENT_DIRECTORY`, `LCP_MAX_ATTACHMENT_BYTES`, and
55
+ `LCP_ALLOWED_ATTACHMENT_CONTENT_TYPES` for the reference file backend. For
56
+ multi-node production, use `LCP_ATTACHMENT_BACKEND=s3` with an SSE-KMS key,
57
+ explicit residency, and a private ClamAV service; see
58
+ [the attachment deployment guide](../../docs/MVA-ATTACHMENTS.md).
59
+
60
+ ## Validate a real Postgres deployment
61
+
62
+ The repository's normal CI runs the reference-platform suite against a fresh
63
+ Postgres 16 service, including encrypted intake, duplicate idempotency, and
64
+ privacy erasure. To run the same test locally, provision an isolated
65
+ throwaway database, install the Postgres extra, and set its URL:
66
+
67
+ ```bash
68
+ python3 -m pip install -e 'implementations/reference-platform[production]'
69
+ export LCP_TEST_POSTGRES_URL='postgresql://lcp_test:<password>@127.0.0.1:5432/lcp_test'
70
+ PYTHONPATH=implementations/reference-platform \
71
+ python3 -m unittest discover -s implementations/reference-platform/tests -v
72
+ ```
73
+
74
+ Never point the integration test at a shared or production database. The test
75
+ creates its own uniquely named tenants and IDs but does not perform a full
76
+ schema reset.
77
+
78
+ ## Configure credentials and offers
79
+
80
+ Do not put production credentials in source control. For local setup, add a
81
+ publisher and buyer credential with least-privilege scopes:
82
+
83
+ ```bash
84
+ lcp-platform-admin credential upsert \
85
+ --sender-id publisher_001 \
86
+ --tenant-id publisher_tenant \
87
+ --scope lead:submit --scope lead:read \
88
+ --hmac-secret publisher-secret
89
+
90
+ lcp-platform-admin credential upsert \
91
+ --sender-id buyer_001 \
92
+ --tenant-id buyer_tenant \
93
+ --scope bid:submit --scope lead:read \
94
+ --hmac-secret buyer-secret
95
+ ```
96
+
97
+ Create an offer file:
98
+
99
+ ```json
100
+ {
101
+ "offer_id": "mortgage-au",
102
+ "buyer_id": "buyer_001",
103
+ "active": true,
104
+ "routing_mode": "auction",
105
+ "vertical": "mortgage",
106
+ "countries": ["AU"],
107
+ "floor_price_cents": 1500,
108
+ "currency": "AUD",
109
+ "require_consent_evidence": true,
110
+ "require_verified_phone": true,
111
+ "reject_incentivized": true,
112
+ "max_spam_risk_score": 25,
113
+ "daily_cap": 100,
114
+ "ping_timeout_seconds": 30,
115
+ "webhook_url": "https://buyer.example/lcp/webhook"
116
+ }
117
+ ```
118
+
119
+ Validate and store it:
120
+
121
+ ```bash
122
+ lcp-platform-admin offer upsert --file offer.json
123
+
124
+ # Controlled privacy operation; verify the legal request and retain the audit record.
125
+ lcp-platform-admin privacy erase-lead --lead-id lead_abc123 --actor-id privacy_operator
126
+ ```
127
+
128
+ Offer management is intentionally an operator/admin concern. The public LCP
129
+ surface exposes active offers through `GET /v1/lcp/offers`.
130
+
131
+ ## Run locally
132
+
133
+ For a single-process local deployment, the HTTP server and delivery worker run
134
+ together:
135
+
136
+ ```bash
137
+ export LCP_REQUIRE_AUTH=true
138
+ # Keep this key in a secret manager for any non-test deployment.
139
+ # Generate once, store securely, and reuse for this database:
140
+ # python3 -c 'import base64,secrets; print(base64.urlsafe_b64encode(secrets.token_bytes(32)).decode())'
141
+ export LCP_PII_ENCRYPTION_KEY="<persisted-urlsafe-base64-32-byte-key>"
142
+ lcp-platform
143
+ ```
144
+
145
+ The default listener is `127.0.0.1:8080`. Configure `LCP_HOST`, `LCP_PORT`,
146
+ and the other settings in `lcp_platform/config.py` through environment
147
+ variables.
148
+
149
+ ## Production process layout
150
+
151
+ For a production deployment, run the HTTP application and delivery worker
152
+ under a process supervisor:
153
+
154
+ ```text
155
+ TLS reverse proxy
156
+ ↓
157
+ WSGI/HTTP application processes ←→ shared database
158
+ ↑
159
+ Delivery worker process
160
+ ```
161
+
162
+ The WSGI entry point is `lcp_platform.wsgi:application`. With the
163
+ `[production]` extra, a typical process command is:
164
+
165
+ ```bash
166
+ gunicorn --bind 0.0.0.0:8080 --workers 3 --threads 4 \
167
+ --access-logfile - lcp_platform.wsgi:application
168
+ ```
169
+
170
+ Run `lcp-platform-worker` as a separate supervised process. Place the
171
+ application behind TLS; the included threaded server is intended for local
172
+ operation and smoke tests.
173
+
174
+ SQLite is appropriate for a single-node deployment or controlled starting
175
+ point. Use a compatible production store implementation when operating at
176
+ multiple nodes or high throughput.
177
+
178
+ ## HTTP endpoints
179
+
180
+ | Method | Path | Purpose |
181
+ |---|---|---|
182
+ | `POST` | `/v1/lcp/leads` | Publisher lead intake (direct or offer-routed) |
183
+ | `POST` | `/v1/lcp/calls` | Publisher call intake |
184
+ | `POST` | `/v1/lcp/bids` | Buyer bid submission |
185
+ | `POST` | `/v1/lcp/events` | Call outcomes and lifecycle/payable updates |
186
+ | `POST` | `/v1/lcp/attachments` | Authenticated scanned/encrypted binary upload |
187
+ | `GET` | `/v1/lcp/attachments/{attachment_id}` | Authenticated attachment download |
188
+ | `GET` | `/v1/lcp/offers/{offer_id}/quota` | Monthly payable quota and pacing report |
189
+ | `GET` | `/v1/lcp/leads/{lead_id}` | Lead status and lifecycle |
190
+ | `GET` | `/health/live` | Unauthenticated process liveness |
191
+ | `GET` | `/health/ready` | Database-backed readiness |
192
+ | `GET` | `/v1/lcp/capabilities` | Capability discovery |
193
+ | `GET` | `/v1/lcp/offers` | Active offer discovery |
194
+ | `GET` | `/v1/lcp/schemas/{name}` | Schema discovery |
195
+
196
+ The complete transport contract is [api/lcp-openapi.yaml](../../api/lcp-openapi.yaml).
197
+
198
+ ## Tenant and credential model
199
+
200
+ Every sender has a tenant ID and scopes. Examples include `lead:submit`,
201
+ `lead:read`, `bid:submit`, `event:submit`, `attachment:write`, `offer:read`,
202
+ and `platform:admin`. Lead status is
203
+ only visible to the submitting sender, an authorized buyer that received the
204
+ lead, or a platform administrator. Offers are routed within the configured
205
+ `LCP_ROUTING_TENANT_ID` and are not writable through participant endpoints.
206
+
207
+ Production credentials should be mounted from a secret manager using
208
+ `LCP_SECRETS_FILE`; the database-backed credential path is intended for local
209
+ administration. Production startup also requires `LCP_PII_ENCRYPTION_KEY`, a
210
+ URL-safe base64 encoding of a random 32-byte AES-GCM key. Persisted lead,
211
+ ping, bid, post, and event envelopes are encrypted and authenticated before
212
+ being written to SQLite or Postgres. Store that key in a KMS/HSM-backed secret
213
+ manager and plan an explicit re-encryption migration before rotating it.
214
+
215
+ ## Authentication
216
+
217
+ HMAC requests use:
218
+
219
+ ```text
220
+ <timestamp>\n<idempotency-key>\n<raw-request-body>
221
+ ```
222
+
223
+ The exact byte sequence is signed with HMAC-SHA256. Requests carry
224
+ `X-LCP-Timestamp`, `X-LCP-Idempotency-Key`, `X-LCP-Signature`, and
225
+ `X-LCP-Sender-Id`. The default replay window is five minutes.
226
+
227
+ Bearer requests use `Authorization: Bearer <token>` and still carry the
228
+ sender and idempotency headers for mutating operations.
229
+
230
+ ## Package publishing
231
+
232
+ A repository-level GitHub Actions workflow can publish the SDK, MCP adapter,
233
+ and reference platform to PyPI through Trusted Publishing on `v*` tags. Each
234
+ package must first be configured as a PyPI Trusted Publisher; no long-lived
235
+ PyPI token is stored in the repository.
236
+
237
+ ## Sandbox parity
238
+
239
+ The Docker sandbox uses this same platform package, schemas, matcher, auth,
240
+ and delivery worker. It uses a separate test database and should only receive
241
+ synthetic test messages marked with `X-LCP-Test: true` and envelope `test: true`.
242
+
243
+ ```bash
244
+ docker compose -f implementations/reference-platform/docker-compose.yml up --build
245
+ ```
246
+
247
+ See [examples/sandbox/README.md](../../examples/sandbox/README.md) for a
248
+ publisher/offer/buyer smoke test.
@@ -0,0 +1,34 @@
1
+ services:
2
+ lcp-platform-sandbox:
3
+ build:
4
+ context: ../..
5
+ dockerfile: implementations/reference-platform/Dockerfile
6
+ environment:
7
+ LCP_DATABASE_PATH: /app/data/sandbox.sqlite3
8
+ LCP_REQUIRE_AUTH: "false"
9
+ LCP_TEST_MODE: "true"
10
+ LCP_ALLOW_INSECURE_WEBHOOKS: "true"
11
+ LCP_PLATFORM_ID: sandbox_platform
12
+ LCP_SCHEMA_DIR: /app/schemas
13
+ command: >-
14
+ sh -c "lcp-platform-admin credential upsert --sender-id buyer_sandbox
15
+ --hmac-secret sandbox-buyer-secret &&
16
+ lcp-platform-admin offer upsert --file /app/examples/sandbox/offer.json &&
17
+ lcp-platform"
18
+ ports:
19
+ - "8080:8080"
20
+ volumes:
21
+ - lcp-sandbox-data:/app/data
22
+
23
+ buyer-sandbox:
24
+ build:
25
+ context: ../..
26
+ dockerfile: implementations/reference-platform/Dockerfile
27
+ environment:
28
+ LCP_PLATFORM_URL: http://lcp-platform-sandbox:8080
29
+ command: ["python", "/app/examples/sandbox/buyer.py"]
30
+ depends_on:
31
+ - lcp-platform-sandbox
32
+
33
+ volumes:
34
+ lcp-sandbox-data:
@@ -0,0 +1,94 @@
1
+ # Kubernetes deployment example
2
+
3
+ These manifests demonstrate a cloud-neutral Kubernetes shape for the LCP
4
+ reference platform. They are intentionally not a complete turnkey production
5
+ cluster configuration.
6
+
7
+ ## Before applying
8
+
9
+ 1. Build and publish the reference-platform image to your registry.
10
+ 2. Provide a supported Postgres service; do not run the database in this
11
+ example for production.
12
+ 3. Provide an approved RWX attachment volume or replace the reference file
13
+ backend with a residency-controlled object-store adapter.
14
+ 4. Copy `secret.example.yaml` to a private, access-controlled Secret manifest
15
+ or use External Secrets/KMS integration.
16
+ 5. Replace the example image, database URL, tenant ID, resource limits,
17
+ namespaces, network policies, ingress, and residency controls.
18
+ 6. Configure a production WSGI server or adapt the image entry point to the
19
+ organization's process manager.
20
+ 7. Publish the image through the signed container workflow, resolve its digest,
21
+ and replace the example image reference with that digest.
22
+ 8. Add a TLS ingress/WAF and private egress policy.
23
+
24
+ ## Apply
25
+
26
+ ```bash
27
+ kubectl create namespace lcp
28
+ kubectl apply -n lcp -f secret.example.yaml # replace with a real secret workflow
29
+ kubectl apply -n lcp -f pvc.example.yaml # use approved RWX storage or an object-store adapter
30
+ kubectl apply -n lcp -f configmap.yaml
31
+ kubectl apply -n lcp -f deployment.yaml
32
+ kubectl apply -n lcp -f service.yaml
33
+ kubectl apply -n lcp -f pod-disruption-budget.yaml
34
+ # Optional; tune against queue age and database capacity before applying.
35
+ kubectl apply -n lcp -f hpa.example.yaml
36
+ ```
37
+
38
+ The API and worker are separate deployments. Both use the same image and
39
+ configuration; only the entry point differs. The worker needs access to the
40
+ same Postgres database but does not need a public Service.
41
+
42
+ ## Production additions
43
+
44
+ - `pvc.example.yaml` for the reference encrypted attachment directory. A
45
+ multi-zone production deployment should prefer an object-store adapter with
46
+ customer-managed encryption and explicit residency controls.
47
+ - `network-policy.example.yaml` restricting database and buyer egress.
48
+ - `pod-disruption-budget.yaml` and multi-zone scheduling.
49
+ - `hpa.example.yaml`, extended with queue-age and database-capacity signals.
50
+ - External Secrets/KMS and key rotation, including controlled re-encryption of persisted envelopes.
51
+ - Ingress TLS, WAF, DDoS protection, and request-size limits.
52
+ - Postgres HA, backups, encryption, and restore drills.
53
+ - Centralized redacted logs, metrics, traces, and alerts.
54
+ - Image signing, admission policy, SBOM verification, and vulnerability scans.
55
+ - `verify-images-kyverno.example.yaml` — optional Kyverno enforcement for
56
+ keyless signatures, SLSA provenance, and CycloneDX SBOM attestations.
57
+ - `tests/verify-images/` — CI fixtures proving unsigned, wrong-workflow,
58
+ missing-provenance, and missing-SBOM images are rejected.
59
+
60
+ See [container signing and provenance verification](../../../docs/CONTAINER-SUPPLY-CHAIN.md)
61
+ for Cosign/GitHub verification commands and cloud-neutral admission guidance.
62
+
63
+ ## Policy test fixtures
64
+
65
+ The `tests/verify-images/` fixture uses Kyverno's public verification-test
66
+ images and expects rejection for four negative cases:
67
+
68
+ - an unsigned image;
69
+ - an image signed by a different workflow/identity;
70
+ - an image without the required SLSA provenance attestation; and
71
+ - an image without the required CycloneDX SBOM attestation.
72
+
73
+ Run it with the Kyverno CLI and registry access:
74
+
75
+ ```bash
76
+ kyverno test implementations/reference-platform/kubernetes/tests/verify-images \
77
+ --registry --detailed-results
78
+ ```
79
+
80
+ The GitHub test workflow installs Kyverno CLI `v1.18.2`, verifies its pinned
81
+ Linux archive with the release's Sigstore bundle, and then runs this fixture.
82
+ Keep that version and verification identity under review when upgrading the
83
+ policy engine or its CLI.
84
+
85
+ The fixture is a policy-behavior test, not a substitute for a live admission
86
+ test against the organization's registry, Kyverno version, credentials, and
87
+ network policy.
88
+
89
+ The CI `admission` job also creates a disposable, pinned kind cluster, installs
90
+ Kyverno from a digest-checked release manifest, applies these policies in
91
+ `Enforce` mode, and submits the four fixtures through the Kubernetes API server
92
+ with server-side dry runs. It fails unless Kyverno rejects the unsigned,
93
+ wrong-workflow, missing-provenance, and missing-SBOM cases through the live
94
+ admission webhook.
@@ -0,0 +1,21 @@
1
+ apiVersion: v1
2
+ kind: ConfigMap
3
+ metadata:
4
+ name: lcp-platform-config
5
+ data:
6
+ LCP_SCHEMA_DIR: /app/schemas
7
+ LCP_REQUIRE_AUTH: "true"
8
+ LCP_ALLOW_INSECURE_WEBHOOKS: "false"
9
+ LCP_PLATFORM_ID: platform_001
10
+ LCP_ROUTING_TENANT_ID: default
11
+ LCP_REPLAY_WINDOW_SECONDS: "300"
12
+ LCP_MAX_BODY_BYTES: "2000000"
13
+ LCP_MAX_HEADER_BYTES: "16384"
14
+ LCP_MAX_WEBHOOK_RESPONSE_BYTES: "262144"
15
+ LCP_RATE_LIMIT_PER_MINUTE: "600"
16
+ LCP_WEBHOOK_TIMEOUT_SECONDS: "10"
17
+ LCP_MAX_DELIVERY_ATTEMPTS: "5"
18
+ LCP_WORKER_INTERVAL_SECONDS: "1"
19
+ LCP_ATTACHMENT_DIRECTORY: /var/lib/lcp/attachments
20
+ LCP_MAX_ATTACHMENT_BYTES: "104857600"
21
+ LCP_ALLOWED_ATTACHMENT_CONTENT_TYPES: application/pdf,image/jpeg,image/png,image/webp,text/plain,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document
@@ -0,0 +1,161 @@
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: lcp-platform-api
5
+ spec:
6
+ replicas: 3
7
+ selector:
8
+ matchLabels:
9
+ app: lcp-platform-api
10
+ template:
11
+ metadata:
12
+ labels:
13
+ app: lcp-platform-api
14
+ spec:
15
+ serviceAccountName: lcp-platform
16
+ securityContext:
17
+ runAsNonRoot: true
18
+ fsGroup: 10001
19
+ seccompProfile:
20
+ type: RuntimeDefault
21
+ containers:
22
+ - name: api
23
+ image: ghcr.io/replace-me/lcp-reference-platform@sha256:REPLACE_WITH_SIGNED_DIGEST
24
+ imagePullPolicy: IfNotPresent
25
+ command: ["gunicorn", "--bind", "0.0.0.0:8080", "--workers", "3", "--threads", "4", "--access-logfile", "-", "lcp_platform.wsgi:application"]
26
+ ports:
27
+ - name: http
28
+ containerPort: 8080
29
+ envFrom:
30
+ - configMapRef:
31
+ name: lcp-platform-config
32
+ env:
33
+ - name: LCP_DATABASE_URL
34
+ valueFrom:
35
+ secretKeyRef:
36
+ name: lcp-platform-secrets
37
+ key: LCP_DATABASE_URL
38
+ - name: LCP_PII_ENCRYPTION_KEY
39
+ valueFrom:
40
+ secretKeyRef:
41
+ name: lcp-platform-secrets
42
+ key: LCP_PII_ENCRYPTION_KEY
43
+ - name: LCP_SECRETS_FILE
44
+ value: /run/secrets/lcp/credentials.json
45
+ volumeMounts:
46
+ - name: lcp-credentials
47
+ mountPath: /run/secrets/lcp
48
+ readOnly: true
49
+ - name: lcp-attachments
50
+ mountPath: /var/lib/lcp/attachments
51
+ readinessProbe:
52
+ httpGet:
53
+ path: /health/ready
54
+ port: http
55
+ initialDelaySeconds: 5
56
+ periodSeconds: 10
57
+ livenessProbe:
58
+ httpGet:
59
+ path: /health/live
60
+ port: http
61
+ initialDelaySeconds: 15
62
+ periodSeconds: 20
63
+ resources:
64
+ requests:
65
+ cpu: 250m
66
+ memory: 256Mi
67
+ limits:
68
+ cpu: "2"
69
+ memory: 1Gi
70
+ securityContext:
71
+ allowPrivilegeEscalation: false
72
+ readOnlyRootFilesystem: true
73
+ capabilities:
74
+ drop: ["ALL"]
75
+ volumes:
76
+ - name: lcp-credentials
77
+ secret:
78
+ secretName: lcp-platform-secrets
79
+ defaultMode: 0440
80
+ items:
81
+ - key: LCP_CREDENTIALS_JSON
82
+ path: credentials.json
83
+ - name: lcp-attachments
84
+ persistentVolumeClaim:
85
+ claimName: lcp-attachments
86
+ ---
87
+ apiVersion: apps/v1
88
+ kind: Deployment
89
+ metadata:
90
+ name: lcp-platform-worker
91
+ spec:
92
+ replicas: 2
93
+ selector:
94
+ matchLabels:
95
+ app: lcp-platform-worker
96
+ template:
97
+ metadata:
98
+ labels:
99
+ app: lcp-platform-worker
100
+ spec:
101
+ serviceAccountName: lcp-platform
102
+ securityContext:
103
+ runAsNonRoot: true
104
+ fsGroup: 10001
105
+ seccompProfile:
106
+ type: RuntimeDefault
107
+ containers:
108
+ - name: worker
109
+ image: ghcr.io/replace-me/lcp-reference-platform@sha256:REPLACE_WITH_SIGNED_DIGEST
110
+ imagePullPolicy: IfNotPresent
111
+ command: ["lcp-platform-worker"]
112
+ envFrom:
113
+ - configMapRef:
114
+ name: lcp-platform-config
115
+ env:
116
+ - name: LCP_DATABASE_URL
117
+ valueFrom:
118
+ secretKeyRef:
119
+ name: lcp-platform-secrets
120
+ key: LCP_DATABASE_URL
121
+ - name: LCP_PII_ENCRYPTION_KEY
122
+ valueFrom:
123
+ secretKeyRef:
124
+ name: lcp-platform-secrets
125
+ key: LCP_PII_ENCRYPTION_KEY
126
+ - name: LCP_SECRETS_FILE
127
+ value: /run/secrets/lcp/credentials.json
128
+ volumeMounts:
129
+ - name: lcp-credentials
130
+ mountPath: /run/secrets/lcp
131
+ readOnly: true
132
+ - name: lcp-attachments
133
+ mountPath: /var/lib/lcp/attachments
134
+ resources:
135
+ requests:
136
+ cpu: 250m
137
+ memory: 256Mi
138
+ limits:
139
+ cpu: "2"
140
+ memory: 1Gi
141
+ securityContext:
142
+ allowPrivilegeEscalation: false
143
+ readOnlyRootFilesystem: true
144
+ capabilities:
145
+ drop: ["ALL"]
146
+ volumes:
147
+ - name: lcp-credentials
148
+ secret:
149
+ secretName: lcp-platform-secrets
150
+ defaultMode: 0440
151
+ items:
152
+ - key: LCP_CREDENTIALS_JSON
153
+ path: credentials.json
154
+ - name: lcp-attachments
155
+ persistentVolumeClaim:
156
+ claimName: lcp-attachments
157
+ ---
158
+ apiVersion: v1
159
+ kind: ServiceAccount
160
+ metadata:
161
+ name: lcp-platform
@@ -0,0 +1,43 @@
1
+ apiVersion: autoscaling/v2
2
+ kind: HorizontalPodAutoscaler
3
+ metadata:
4
+ name: lcp-platform-api
5
+ spec:
6
+ scaleTargetRef:
7
+ apiVersion: apps/v1
8
+ kind: Deployment
9
+ name: lcp-platform-api
10
+ minReplicas: 3
11
+ maxReplicas: 50
12
+ behavior:
13
+ scaleDown:
14
+ stabilizationWindowSeconds: 300
15
+ metrics:
16
+ - type: Resource
17
+ resource:
18
+ name: cpu
19
+ target:
20
+ type: Utilization
21
+ averageUtilization: 65
22
+ ---
23
+ apiVersion: autoscaling/v2
24
+ kind: HorizontalPodAutoscaler
25
+ metadata:
26
+ name: lcp-platform-worker
27
+ spec:
28
+ scaleTargetRef:
29
+ apiVersion: apps/v1
30
+ kind: Deployment
31
+ name: lcp-platform-worker
32
+ minReplicas: 2
33
+ maxReplicas: 50
34
+ behavior:
35
+ scaleDown:
36
+ stabilizationWindowSeconds: 300
37
+ metrics:
38
+ - type: Resource
39
+ resource:
40
+ name: cpu
41
+ target:
42
+ type: Utilization
43
+ averageUtilization: 65