From 7ac4cf405234bb1bd1c97e25afb36f008b59f7bf Mon Sep 17 00:00:00 2001 From: yuqei Date: Wed, 7 Oct 2026 01:22:14 +0800 Subject: [PATCH] build(rc06): wire hardened publication facade --- .../tests/test_rc06_publication_deployment.py | 194 ++++++++++++++++++ yuqei-infra/compose/.env.production.example | 28 +++ .../compose/docker-compose.production.yml | 53 +++++ .../nginx/templates/default.conf.template | 46 +++++ 4 files changed, 321 insertions(+) create mode 100644 yuqei-contract-platform/services/contract-api/tests/test_rc06_publication_deployment.py diff --git a/yuqei-contract-platform/services/contract-api/tests/test_rc06_publication_deployment.py b/yuqei-contract-platform/services/contract-api/tests/test_rc06_publication_deployment.py new file mode 100644 index 00000000..3402f593 --- /dev/null +++ b/yuqei-contract-platform/services/contract-api/tests/test_rc06_publication_deployment.py @@ -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 diff --git a/yuqei-infra/compose/.env.production.example b/yuqei-infra/compose/.env.production.example index e1ea808d..4d278590 100644 --- a/yuqei-infra/compose/.env.production.example +++ b/yuqei-infra/compose/.env.production.example @@ -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 diff --git a/yuqei-infra/compose/docker-compose.production.yml b/yuqei-infra/compose/docker-compose.production.yml index 2f1822ac..6ae4a7b8 100644 --- a/yuqei-infra/compose/docker-compose.production.yml +++ b/yuqei-infra/compose/docker-compose.production.yml @@ -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} diff --git a/yuqei-infra/nginx/templates/default.conf.template b/yuqei-infra/nginx/templates/default.conf.template index 05dbefea..bcebeba8 100644 --- a/yuqei-infra/nginx/templates/default.conf.template +++ b/yuqei-infra/nginx/templates/default.conf.template @@ -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/;