build(rc06): wire hardened publication facade
This commit is contained in:
+194
@@ -0,0 +1,194 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from yuqei_contract_api.config import (
|
||||
ContractApiSettings,
|
||||
validate_rc06_publication_startup,
|
||||
)
|
||||
from yuqei_contract_api.main import create_app
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[4]
|
||||
|
||||
|
||||
class _ComposeLoader(yaml.SafeLoader):
|
||||
pass
|
||||
|
||||
|
||||
_ComposeLoader.add_constructor("!reset", lambda _loader, _node: None)
|
||||
|
||||
|
||||
def _write(path: Path, value: str) -> str:
|
||||
path.write_text(value, encoding="utf-8")
|
||||
return str(path.resolve())
|
||||
|
||||
|
||||
def _facade_settings(tmp_path: Path, **overrides) -> ContractApiSettings:
|
||||
values = {
|
||||
"environment": "production",
|
||||
"database_url": f"sqlite:///{tmp_path / 'must-not-be-created.sqlite3'}",
|
||||
"database_auto_create": False,
|
||||
"seed_demo_data": False,
|
||||
"ai_platform_base_url": "https://ai-platform.internal.example.com",
|
||||
"ai_platform_service_key": "general-ai-key-000000000000000000000001",
|
||||
"ai_platform_rc06_publication_key": "rc06-ai-key-00000000000000000000000001",
|
||||
"web_proxy_shared_secret": "web-proxy-key-000000000000000000000001",
|
||||
"rc06_publication_facade_enabled": True,
|
||||
"rc06_low_level_coordinator_http_enabled": False,
|
||||
"rc06_publication_environment_id": "production-cn-1",
|
||||
"rc06_publication_tenant_id": "rc06-production-tenant",
|
||||
"rc06_publication_bearer_token_file": _write(
|
||||
tmp_path / "facade.token", "facade-key-0000000000000000000000000001"
|
||||
),
|
||||
"rc06_frozen_candidate_commit": "a" * 40,
|
||||
"rc06_release_image_digest": "sha256:" + "b" * 64,
|
||||
"rc06_ai_release_image_digest": "sha256:" + "c" * 64,
|
||||
"rc06_approval_root_public_key_file": _write(
|
||||
tmp_path / "approval-root.pem", "approval-root-public-material"
|
||||
),
|
||||
"rc06_approval_root_public_key_sha256": "d" * 64,
|
||||
"rc06_approval_root_key_id": "yuqei-rc06-approval-root-v1",
|
||||
"rc06_approval_keyring_epoch": 1,
|
||||
"rc06_apply_service_public_key_file": _write(
|
||||
tmp_path / "apply-service.pem", "apply-service-public-material"
|
||||
),
|
||||
"rc06_apply_service_public_key_sha256": "e" * 64,
|
||||
"rc06_legal_review_keyring_sha256": "f" * 64,
|
||||
"rc06_online_signing_provider": "fixture-kms",
|
||||
"rc06_online_signing_key_reference": "kms://fixture/rc06-online",
|
||||
"rc06_online_signing_key_version": "version-1",
|
||||
"rc06_online_signing_algorithm": "RSA_PSS_SHA_256",
|
||||
"rc06_online_signing_public_key_spki_sha256": "1" * 64,
|
||||
"reliable_evidence_signing_backend": "kms_gateway",
|
||||
"reliable_evidence_signing_endpoint": "https://kms.internal.example.com",
|
||||
"reliable_evidence_signing_credential_file": _write(
|
||||
tmp_path / "kms.credential", "kms-credential-material"
|
||||
),
|
||||
"reliable_evidence_signing_ca_file": _write(
|
||||
tmp_path / "kms-ca.pem", "kms-ca-public-material"
|
||||
),
|
||||
}
|
||||
values.update(overrides)
|
||||
return ContractApiSettings(**values)
|
||||
|
||||
|
||||
def test_enabled_facade_startup_checks_all_independent_trust_boundaries(tmp_path: Path) -> None:
|
||||
validate_rc06_publication_startup(_facade_settings(tmp_path))
|
||||
|
||||
with pytest.raises(RuntimeError, match="low-level coordinator HTTP routes"):
|
||||
create_app(
|
||||
_facade_settings(
|
||||
tmp_path,
|
||||
rc06_low_level_coordinator_http_enabled=True,
|
||||
rc06_publication_shared_secret="low-level-key-000000000000000000000001",
|
||||
)
|
||||
)
|
||||
assert not (tmp_path / "must-not-be-created.sqlite3").exists()
|
||||
|
||||
with pytest.raises(RuntimeError, match="must be independent"):
|
||||
validate_rc06_publication_startup(
|
||||
_facade_settings(
|
||||
tmp_path,
|
||||
ai_platform_rc06_publication_key=(
|
||||
tmp_path / "facade.token"
|
||||
).read_text(encoding="utf-8"),
|
||||
)
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError, match="KMS evidence signing requires"):
|
||||
validate_rc06_publication_startup(
|
||||
_facade_settings(tmp_path, reliable_evidence_signing_endpoint="")
|
||||
)
|
||||
bearer = (tmp_path / "facade.token").read_text(encoding="utf-8")
|
||||
with pytest.raises(RuntimeError, match="independent from the KMS credential"):
|
||||
validate_rc06_publication_startup(
|
||||
_facade_settings(
|
||||
tmp_path,
|
||||
reliable_evidence_signing_credential_file=_write(
|
||||
tmp_path / "reused-kms.credential", bearer
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def test_production_compose_and_edge_expose_only_the_hardened_facade() -> None:
|
||||
compose_path = REPO_ROOT / "yuqei-infra/compose/docker-compose.production.yml"
|
||||
model = yaml.load(compose_path.read_text(encoding="utf-8"), Loader=_ComposeLoader)
|
||||
contract = model["services"]["contract-api"]
|
||||
assert "ports" in contract and contract["ports"] is None
|
||||
contract_env = contract["environment"]
|
||||
assert contract_env["CONTRACT_API_RC06_PUBLICATION_FACADE_ENABLED"] == "true"
|
||||
assert contract_env["CONTRACT_API_RC06_LOW_LEVEL_COORDINATOR_HTTP_ENABLED"] == "false"
|
||||
assert contract_env["CONTRACT_API_RC06_PUBLICATION_TENANT_ID"].startswith(
|
||||
"${RC06_PUBLICATION_TENANT_ID:"
|
||||
)
|
||||
assert contract_env["CONTRACT_API_RC06_PUBLICATION_BEARER_TOKEN_FILE"] == (
|
||||
"/run/secrets/rc06_facade_bearer_token"
|
||||
)
|
||||
assert contract_env["CONTRACT_API_RELIABLE_EVIDENCE_SIGNING_BACKEND"] == "kms_gateway"
|
||||
assert contract_env["CONTRACT_API_RC06_AI_RELEASE_IMAGE_DIGEST"].startswith(
|
||||
"${RC06_AI_RELEASE_IMAGE_DIGEST:"
|
||||
)
|
||||
assert {item["source"] for item in contract["secrets"]} == {
|
||||
"rc06_facade_bearer_token",
|
||||
"rc06_kms_credential",
|
||||
}
|
||||
assert all(
|
||||
mount.endswith(":ro")
|
||||
for mount in contract["volumes"]
|
||||
if "/run/yuqei/rc06/" in mount
|
||||
)
|
||||
assert "config_status" in " ".join(contract["healthcheck"]["test"])
|
||||
|
||||
ai_env = model["services"]["ai-platform-api"]["environment"]
|
||||
assert (
|
||||
"ports" in model["services"]["ai-platform-api"]
|
||||
and model["services"]["ai-platform-api"]["ports"] is None
|
||||
)
|
||||
assert ai_env["AI_PLATFORM_RC06_PUBLICATION_SHARED_SECRET"].startswith(
|
||||
"${RC06_AI_PLATFORM_PUBLICATION_SERVICE_KEY:"
|
||||
)
|
||||
assert ai_env["AI_PLATFORM_RC06_FROZEN_CANDIDATE_COMMIT"].startswith(
|
||||
"${RC06_FROZEN_CANDIDATE_COMMIT:"
|
||||
)
|
||||
assert ai_env["AI_PLATFORM_RC06_RELEASE_IMAGE_DIGEST"].startswith(
|
||||
"${RC06_AI_RELEASE_IMAGE_DIGEST:"
|
||||
)
|
||||
|
||||
edge = model["services"]["edge"]
|
||||
assert edge["environment"]["RC06_PUBLICATION_TENANT_ID"].startswith(
|
||||
"${RC06_PUBLICATION_TENANT_ID:"
|
||||
)
|
||||
nginx = (
|
||||
REPO_ROOT / "yuqei-infra/nginx/templates/default.conf.template"
|
||||
).read_text(encoding="utf-8")
|
||||
assert "location = /internal/rc06/publication-transactions/apply" in nginx
|
||||
assert "client_max_body_size 16m;" in nginx
|
||||
assert "proxy_set_header X-RC06-Tenant-ID ${RC06_PUBLICATION_TENANT_ID};" in nginx
|
||||
assert "location ~ ^/contract/api/v1/internal/rc06-publications" in nginx
|
||||
assert "location ~ ^/ai/api/v1/internal/rc06-publications" in nginx
|
||||
facade_block = nginx.split(
|
||||
"location = /internal/rc06/publication-transactions/apply", 1
|
||||
)[1].split("location ~", 1)[0]
|
||||
assert "contract_api_upstream" in facade_block
|
||||
assert "ai_platform_upstream" not in facade_block
|
||||
|
||||
env_example = (
|
||||
REPO_ROOT / "yuqei-infra/compose/.env.production.example"
|
||||
).read_text(encoding="utf-8")
|
||||
for key in (
|
||||
"RC06_FACADE_BEARER_TOKEN_FILE",
|
||||
"RC06_APPROVAL_ROOT_PUBLIC_KEY_FILE_HOST",
|
||||
"RC06_APPLY_SERVICE_PUBLIC_KEY_FILE_HOST",
|
||||
"RC06_KMS_CREDENTIAL_FILE",
|
||||
"RC06_KMS_CA_FILE_HOST",
|
||||
"RC06_AI_PLATFORM_PUBLICATION_SERVICE_KEY",
|
||||
):
|
||||
assert f"{key}=" in env_example
|
||||
|
||||
compose_source = compose_path.read_text(encoding="utf-8")
|
||||
assert compose_source.count("ports: !reset []") == 2
|
||||
@@ -145,6 +145,34 @@ CONTRACT_API_ANTI_ABUSE_BYTES_DAILY_LIMIT=524288000
|
||||
CONTRACT_API_BATCH_DOWNLOAD_MAX_FILES=20
|
||||
CONTRACT_API_BATCH_DOWNLOAD_MAX_BYTES=104857600
|
||||
|
||||
# RC06 public publication facade. All hashes and deployment identities must be
|
||||
# regenerated from one frozen candidate; CHANGE_ME values intentionally fail
|
||||
# startup/compose interpolation. The three service credentials below are
|
||||
# independent and must not be reused by the web proxy or general AI transport.
|
||||
RC06_AI_PLATFORM_BASE_URL=https://ai-platform-api.internal.example.com
|
||||
RC06_AI_PLATFORM_PUBLICATION_SERVICE_KEY=CHANGE_ME_USE_A_DEDICATED_RANDOM_32_BYTE_SECRET
|
||||
RC06_PUBLICATION_ENVIRONMENT_ID=CHANGE_ME_STABLE_RC06_ENVIRONMENT_ID
|
||||
RC06_PUBLICATION_TENANT_ID=CHANGE_ME_FIXED_RC06_TENANT_ID
|
||||
RC06_FACADE_BEARER_TOKEN_FILE=/etc/yuqei/secrets/rc06-facade-bearer-token
|
||||
RC06_FROZEN_CANDIDATE_COMMIT=CHANGE_ME_40_OR_64_LOWERCASE_HEX_COMMIT
|
||||
RC06_CONTRACT_RELEASE_IMAGE_DIGEST=sha256:CHANGE_ME_64_HEX_DIGEST
|
||||
RC06_AI_RELEASE_IMAGE_DIGEST=sha256:CHANGE_ME_64_HEX_DIGEST
|
||||
RC06_APPROVAL_ROOT_PUBLIC_KEY_FILE_HOST=/etc/yuqei/rc06/approval-root-public.pem
|
||||
RC06_APPROVAL_ROOT_PUBLIC_KEY_SHA256=CHANGE_ME_64_HEX_SHA256
|
||||
RC06_APPROVAL_ROOT_KEY_ID=CHANGE_ME_FROZEN_APPROVAL_ROOT_KEY_ID
|
||||
RC06_APPROVAL_KEYRING_EPOCH=CHANGE_ME_POSITIVE_INTEGER
|
||||
RC06_APPLY_SERVICE_PUBLIC_KEY_FILE_HOST=/etc/yuqei/rc06/apply-service-public.pem
|
||||
RC06_APPLY_SERVICE_PUBLIC_KEY_SHA256=CHANGE_ME_64_HEX_SHA256
|
||||
RC06_LEGAL_REVIEW_KEYRING_SHA256=CHANGE_ME_64_HEX_SHA256
|
||||
RC06_ONLINE_SIGNING_PROVIDER=CHANGE_ME_KMS_PROVIDER
|
||||
RC06_ONLINE_SIGNING_KEY_REFERENCE=CHANGE_ME_KMS_SIGNING_KEY_REFERENCE
|
||||
RC06_ONLINE_SIGNING_KEY_VERSION=CHANGE_ME_IMMUTABLE_KMS_KEY_VERSION
|
||||
RC06_ONLINE_SIGNING_ALGORITHM=RSA_PSS_SHA_256
|
||||
RC06_ONLINE_SIGNING_PUBLIC_KEY_SPKI_SHA256=CHANGE_ME_64_HEX_SHA256
|
||||
RC06_KMS_SIGNING_ENDPOINT=https://kms-gateway.internal.example.com
|
||||
RC06_KMS_CREDENTIAL_FILE=/etc/yuqei/secrets/rc06-kms-credential
|
||||
RC06_KMS_CA_FILE_HOST=/etc/yuqei/rc06/kms-ca.pem
|
||||
|
||||
# Enterprise dual-cloud evidence trust is a paid optional capability. Keep it
|
||||
# disabled for the core product release; a dedicated UAT overlay sets required.
|
||||
CONTRACT_API_RELIABLE_EVIDENCE_EXTERNAL_TRUST_MODE=disabled
|
||||
|
||||
@@ -28,9 +28,38 @@ services:
|
||||
contract-api:
|
||||
image: ${CONTRACT_API_IMAGE:?CONTRACT_API_IMAGE must reference an immutable production image}
|
||||
build: !reset null
|
||||
# The base developer compose publishes 8100. Production traffic must enter
|
||||
# through the TLS edge only; remove the inherited host binding entirely.
|
||||
ports: !reset []
|
||||
pull_policy: always
|
||||
# SMTP remains opt-in through docker-compose.production.smtp.yml so disabled mode has no secret-file dependency.
|
||||
environment:
|
||||
CONTRACT_API_AI_PLATFORM_BASE_URL: ${RC06_AI_PLATFORM_BASE_URL:?RC06_AI_PLATFORM_BASE_URL must be an internal HTTPS endpoint}
|
||||
CONTRACT_API_AI_PLATFORM_RC06_PUBLICATION_KEY: ${RC06_AI_PLATFORM_PUBLICATION_SERVICE_KEY:?RC06_AI_PLATFORM_PUBLICATION_SERVICE_KEY is required}
|
||||
CONTRACT_API_RC06_PUBLICATION_FACADE_ENABLED: "true"
|
||||
CONTRACT_API_RC06_LOW_LEVEL_COORDINATOR_HTTP_ENABLED: "false"
|
||||
CONTRACT_API_RC06_PUBLICATION_ENVIRONMENT_ID: ${RC06_PUBLICATION_ENVIRONMENT_ID:?RC06_PUBLICATION_ENVIRONMENT_ID is required}
|
||||
CONTRACT_API_RC06_PUBLICATION_TENANT_ID: ${RC06_PUBLICATION_TENANT_ID:?RC06_PUBLICATION_TENANT_ID is required}
|
||||
CONTRACT_API_RC06_PUBLICATION_BEARER_TOKEN_FILE: /run/secrets/rc06_facade_bearer_token
|
||||
CONTRACT_API_RC06_FROZEN_CANDIDATE_COMMIT: ${RC06_FROZEN_CANDIDATE_COMMIT:?RC06_FROZEN_CANDIDATE_COMMIT is required}
|
||||
CONTRACT_API_RC06_RELEASE_IMAGE_DIGEST: ${RC06_CONTRACT_RELEASE_IMAGE_DIGEST:?RC06_CONTRACT_RELEASE_IMAGE_DIGEST is required}
|
||||
CONTRACT_API_RC06_AI_RELEASE_IMAGE_DIGEST: ${RC06_AI_RELEASE_IMAGE_DIGEST:?RC06_AI_RELEASE_IMAGE_DIGEST is required}
|
||||
CONTRACT_API_RC06_APPROVAL_ROOT_PUBLIC_KEY_FILE: /run/yuqei/rc06/approval-root-public.pem
|
||||
CONTRACT_API_RC06_APPROVAL_ROOT_PUBLIC_KEY_SHA256: ${RC06_APPROVAL_ROOT_PUBLIC_KEY_SHA256:?RC06_APPROVAL_ROOT_PUBLIC_KEY_SHA256 is required}
|
||||
CONTRACT_API_RC06_APPROVAL_ROOT_KEY_ID: ${RC06_APPROVAL_ROOT_KEY_ID:?RC06_APPROVAL_ROOT_KEY_ID is required}
|
||||
CONTRACT_API_RC06_APPROVAL_KEYRING_EPOCH: ${RC06_APPROVAL_KEYRING_EPOCH:?RC06_APPROVAL_KEYRING_EPOCH is required}
|
||||
CONTRACT_API_RC06_APPLY_SERVICE_PUBLIC_KEY_FILE: /run/yuqei/rc06/apply-service-public.pem
|
||||
CONTRACT_API_RC06_APPLY_SERVICE_PUBLIC_KEY_SHA256: ${RC06_APPLY_SERVICE_PUBLIC_KEY_SHA256:?RC06_APPLY_SERVICE_PUBLIC_KEY_SHA256 is required}
|
||||
CONTRACT_API_RC06_LEGAL_REVIEW_KEYRING_SHA256: ${RC06_LEGAL_REVIEW_KEYRING_SHA256:?RC06_LEGAL_REVIEW_KEYRING_SHA256 is required}
|
||||
CONTRACT_API_RC06_ONLINE_SIGNING_PROVIDER: ${RC06_ONLINE_SIGNING_PROVIDER:?RC06_ONLINE_SIGNING_PROVIDER is required}
|
||||
CONTRACT_API_RC06_ONLINE_SIGNING_KEY_REFERENCE: ${RC06_ONLINE_SIGNING_KEY_REFERENCE:?RC06_ONLINE_SIGNING_KEY_REFERENCE is required}
|
||||
CONTRACT_API_RC06_ONLINE_SIGNING_KEY_VERSION: ${RC06_ONLINE_SIGNING_KEY_VERSION:?RC06_ONLINE_SIGNING_KEY_VERSION is required}
|
||||
CONTRACT_API_RC06_ONLINE_SIGNING_ALGORITHM: ${RC06_ONLINE_SIGNING_ALGORITHM:-RSA_PSS_SHA_256}
|
||||
CONTRACT_API_RC06_ONLINE_SIGNING_PUBLIC_KEY_SPKI_SHA256: ${RC06_ONLINE_SIGNING_PUBLIC_KEY_SPKI_SHA256:?RC06_ONLINE_SIGNING_PUBLIC_KEY_SPKI_SHA256 is required}
|
||||
CONTRACT_API_RELIABLE_EVIDENCE_SIGNING_BACKEND: kms_gateway
|
||||
CONTRACT_API_RELIABLE_EVIDENCE_SIGNING_ENDPOINT: ${RC06_KMS_SIGNING_ENDPOINT:?RC06_KMS_SIGNING_ENDPOINT is required}
|
||||
CONTRACT_API_RELIABLE_EVIDENCE_SIGNING_CREDENTIAL_FILE: /run/secrets/rc06_kms_credential
|
||||
CONTRACT_API_RELIABLE_EVIDENCE_SIGNING_CA_FILE: /run/yuqei/rc06/kms-ca.pem
|
||||
CONTRACT_API_PUBLIC_POLICY_CANDIDATE_DIR: /run/yuqei/public-policy/candidate
|
||||
CONTRACT_API_PUBLIC_POLICY_LEGAL_APPROVAL_FILE: /run/yuqei/public-policy/named-policy-legal-approval.json
|
||||
CONTRACT_API_PUBLIC_POLICY_TRUSTED_KEYRING_FILE: /run/yuqei/public-policy/policy-legal-keyring.trusted.json
|
||||
@@ -41,6 +70,19 @@ services:
|
||||
- ${PUBLIC_POLICY_CANDIDATE_DIR_HOST:?PUBLIC_POLICY_CANDIDATE_DIR_HOST is required}:/run/yuqei/public-policy/candidate:ro
|
||||
- ${PUBLIC_POLICY_LEGAL_APPROVAL_FILE_HOST:?PUBLIC_POLICY_LEGAL_APPROVAL_FILE_HOST is required}:/run/yuqei/public-policy/named-policy-legal-approval.json:ro
|
||||
- ${PUBLIC_POLICY_TRUSTED_KEYRING_FILE_HOST:?PUBLIC_POLICY_TRUSTED_KEYRING_FILE_HOST is required}:/run/yuqei/public-policy/policy-legal-keyring.trusted.json:ro
|
||||
- ${RC06_APPROVAL_ROOT_PUBLIC_KEY_FILE_HOST:?RC06_APPROVAL_ROOT_PUBLIC_KEY_FILE_HOST is required}:/run/yuqei/rc06/approval-root-public.pem:ro
|
||||
- ${RC06_APPLY_SERVICE_PUBLIC_KEY_FILE_HOST:?RC06_APPLY_SERVICE_PUBLIC_KEY_FILE_HOST is required}:/run/yuqei/rc06/apply-service-public.pem:ro
|
||||
- ${RC06_KMS_CA_FILE_HOST:?RC06_KMS_CA_FILE_HOST is required}:/run/yuqei/rc06/kms-ca.pem:ro
|
||||
secrets:
|
||||
- source: rc06_facade_bearer_token
|
||||
target: rc06_facade_bearer_token
|
||||
- source: rc06_kms_credential
|
||||
target: rc06_kms_credential
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import json,urllib.request; d=json.load(urllib.request.urlopen('http://127.0.0.1:8100/api/v1/health',timeout=5)); assert d['status']=='ok' and d['config_status']!='error'"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 6
|
||||
|
||||
contract-review-worker:
|
||||
image: ${CONTRACT_API_IMAGE:?CONTRACT_API_IMAGE must reference an immutable production image}
|
||||
@@ -70,9 +112,15 @@ services:
|
||||
ai-platform-api:
|
||||
image: ${AI_PLATFORM_API_IMAGE:?AI_PLATFORM_API_IMAGE must reference an immutable production image}
|
||||
build: !reset null
|
||||
# Keep participant/internal routes off the host network. Contract reaches
|
||||
# this service only over the private Compose network.
|
||||
ports: !reset []
|
||||
pull_policy: always
|
||||
environment:
|
||||
AI_PLATFORM_SECRET_FILE_ROOT: /run/secrets
|
||||
AI_PLATFORM_RC06_PUBLICATION_SHARED_SECRET: ${RC06_AI_PLATFORM_PUBLICATION_SERVICE_KEY:?RC06_AI_PLATFORM_PUBLICATION_SERVICE_KEY is required}
|
||||
AI_PLATFORM_RC06_FROZEN_CANDIDATE_COMMIT: ${RC06_FROZEN_CANDIDATE_COMMIT:?RC06_FROZEN_CANDIDATE_COMMIT is required}
|
||||
AI_PLATFORM_RC06_RELEASE_IMAGE_DIGEST: ${RC06_AI_RELEASE_IMAGE_DIGEST:?RC06_AI_RELEASE_IMAGE_DIGEST is required}
|
||||
AI_PLATFORM_PROVIDER_LEGAL_ATTESTATION_SECRET: ""
|
||||
AI_PLATFORM_PROVIDER_ACCEPTANCE_RECEIPT_FILE: /run/yuqei/provider-acceptance/receipt.json
|
||||
AI_PLATFORM_PROVIDER_ACCEPTANCE_TRUSTED_KEYS_FILE: /run/yuqei/provider-acceptance/trusted-keys.json
|
||||
@@ -105,6 +153,7 @@ services:
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
BACKEND_SERVER_NAME: ${BACKEND_SERVER_NAME:?BACKEND_SERVER_NAME is required}
|
||||
RC06_PUBLICATION_TENANT_ID: ${RC06_PUBLICATION_TENANT_ID:?RC06_PUBLICATION_TENANT_ID is required}
|
||||
NGINX_ENVSUBST_OUTPUT_DIR: /etc/nginx/conf.d
|
||||
ports:
|
||||
- "${BACKEND_HTTP_BIND_ADDRESS:-0.0.0.0}:${BACKEND_HTTP_PORT:-80}:80"
|
||||
@@ -178,3 +227,7 @@ volumes:
|
||||
secrets:
|
||||
ai_provider_api_key:
|
||||
file: ${AI_PROVIDER_API_KEY_FILE:?AI_PROVIDER_API_KEY_FILE must point to a protected provider key file}
|
||||
rc06_facade_bearer_token:
|
||||
file: ${RC06_FACADE_BEARER_TOKEN_FILE:?RC06_FACADE_BEARER_TOKEN_FILE must point to a protected token file}
|
||||
rc06_kms_credential:
|
||||
file: ${RC06_KMS_CREDENTIAL_FILE:?RC06_KMS_CREDENTIAL_FILE must point to a protected credential file}
|
||||
|
||||
@@ -58,6 +58,52 @@ server {
|
||||
proxy_send_timeout 660s;
|
||||
}
|
||||
|
||||
# RC06 has one public service boundary. The tenant is deployment-bound;
|
||||
# a caller-supplied tenant header is never forwarded as authority.
|
||||
location = /internal/rc06/publication-transactions/apply {
|
||||
client_max_body_size 16m;
|
||||
limit_req zone=yuqei_api_per_ip burst=5 nodelay;
|
||||
proxy_pass http://contract_api_upstream/internal/rc06/publication-transactions/apply;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header X-RC06-Tenant-ID ${RC06_PUBLICATION_TENANT_ID};
|
||||
proxy_set_header X-Tenant-Id "";
|
||||
proxy_set_header X-User-Id "";
|
||||
proxy_set_header Connection "";
|
||||
proxy_connect_timeout 5s;
|
||||
proxy_read_timeout 660s;
|
||||
proxy_send_timeout 660s;
|
||||
}
|
||||
|
||||
location ~ ^/internal/rc06/publication-transactions/[A-Za-z0-9][A-Za-z0-9._:-]{7,63}/(status|rollback|finalize)$ {
|
||||
client_max_body_size 16m;
|
||||
limit_req zone=yuqei_api_per_ip burst=10 nodelay;
|
||||
proxy_pass http://contract_api_upstream$request_uri;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto https;
|
||||
proxy_set_header X-RC06-Tenant-ID ${RC06_PUBLICATION_TENANT_ID};
|
||||
proxy_set_header X-Tenant-Id "";
|
||||
proxy_set_header X-User-Id "";
|
||||
proxy_set_header Connection "";
|
||||
proxy_connect_timeout 5s;
|
||||
proxy_read_timeout 660s;
|
||||
proxy_send_timeout 660s;
|
||||
}
|
||||
|
||||
location ~ ^/contract/api/v1/internal/rc06-publications(?:/|$) {
|
||||
return 404;
|
||||
}
|
||||
|
||||
location ~ ^/ai/api/v1/internal/rc06-publications(?:/|$) {
|
||||
return 404;
|
||||
}
|
||||
|
||||
location /contract/ {
|
||||
limit_req zone=yuqei_api_per_ip burst=40 nodelay;
|
||||
proxy_pass http://contract_api_upstream/;
|
||||
|
||||
Reference in New Issue
Block a user