diff --git a/charts/adapter-service/Chart.yaml b/charts/adapter-service/Chart.yaml index e3c0eb0d..b7e45e17 100644 --- a/charts/adapter-service/Chart.yaml +++ b/charts/adapter-service/Chart.yaml @@ -1,13 +1,8 @@ apiVersion: v2 name: adapter-service -description: >- - One chart for the OAN Beckn adapters. provider, network and consumer are the same - image and config format, so `role` selects the handler role, the step list - and the routing target; install once per role. Stateless: an adapter's - identity is a keypair held in a Secret, and everything else it reads from - the registry. +description: OAN Beckn adapter; role network-adapter runs all three tiers in single-adapter mode (default), or the network tier in multi-adapter. type: application -version: 0.5.2 +version: 0.6.0 # The adapter binary, not this chart. `/health` reports the build it is # actually running, which is the number to trust over this one. appVersion: "v1.9.0" diff --git a/charts/adapter-service/ci/lint-values.yaml b/charts/adapter-service/ci/lint-values.yaml index d01cdfad..06217d9e 100644 --- a/charts/adapter-service/ci/lint-values.yaml +++ b/charts/adapter-service/ci/lint-values.yaml @@ -3,7 +3,7 @@ # # role is first among them: it has no default, because a default would hand one # adapter another one's handler role and step list. -role: network +role: network-adapter fullnameOverride: network-adapter image: @@ -18,6 +18,6 @@ keys: registry: url: http://registry:8081/api/v1 -# Routing is no longer a value: it comes from config/routing-.yaml in +# Routing is no longer a value: it comes from config/multi-adapter/routing-.yaml in # the chart, like the adapter config. Set `routing.config` to a whole routing # file only if this deployment must differ. diff --git a/charts/adapter-service/ci/network-adapter-values.yaml b/charts/adapter-service/ci/network-adapter-values.yaml new file mode 100644 index 00000000..b3fc805d --- /dev/null +++ b/charts/adapter-service/ci/network-adapter-values.yaml @@ -0,0 +1,30 @@ +# CI only: renders single-adapter mode (single-adapter routing, tier identities) +# with ingress on and annotations null to exercise the path guard. +role: network-adapter +fullnameOverride: network-adapter + +image: + registry: ghcr.io + repository: openagrinet/network-adapter + tag: "v1.9.0" + +# registry.url, otel.*, appName, logLevel, http.timeout.*, routing.* and handler.* +# are unused in single-adapter mode: set them in config/single-adapter/network-adapter-config.yaml. + +service: + port: 9201 + targetPort: 9201 + +ingress: + enabled: true + className: nginx + annotations: null + hosts: + - host: network-adapter.ci.example.com + paths: + - path: /discover + pathType: Exact + - path: /select + pathType: Prefix + - path: /status + pathType: Exact diff --git a/charts/adapter-service/ci/otel-ingress-values.yaml b/charts/adapter-service/ci/otel-ingress-values.yaml index a0e9c203..6762dcff 100644 --- a/charts/adapter-service/ci/otel-ingress-values.yaml +++ b/charts/adapter-service/ci/otel-ingress-values.yaml @@ -4,7 +4,7 @@ # Uses the consumer role on purpose: it is the one with a different handler role and # a shorter step list, so this case renders the role branches that # ci/lint-values.yaml (network) does not. -role: consumer +role: consumer-adapter fullnameOverride: consumer-adapter image: @@ -19,7 +19,7 @@ keys: registry: url: http://registry:8081/api/v1 -# Routing is no longer a value: it comes from config/routing-.yaml in +# Routing is no longer a value: it comes from config/multi-adapter/routing-.yaml in # the chart, like the adapter config. Set `routing.config` to a whole routing # file only if this deployment must differ. diff --git a/charts/adapter-service/config/consumer-config.yaml b/charts/adapter-service/config/multi-adapter/consumer-config.yaml similarity index 100% rename from charts/adapter-service/config/consumer-config.yaml rename to charts/adapter-service/config/multi-adapter/consumer-config.yaml diff --git a/charts/adapter-service/config/network-config.yaml b/charts/adapter-service/config/multi-adapter/network-config.yaml similarity index 100% rename from charts/adapter-service/config/network-config.yaml rename to charts/adapter-service/config/multi-adapter/network-config.yaml diff --git a/charts/adapter-service/config/provider-config.yaml b/charts/adapter-service/config/multi-adapter/provider-config.yaml similarity index 100% rename from charts/adapter-service/config/provider-config.yaml rename to charts/adapter-service/config/multi-adapter/provider-config.yaml diff --git a/charts/adapter-service/config/routing-consumer.yaml b/charts/adapter-service/config/multi-adapter/routing-consumer.yaml similarity index 100% rename from charts/adapter-service/config/routing-consumer.yaml rename to charts/adapter-service/config/multi-adapter/routing-consumer.yaml diff --git a/charts/adapter-service/config/routing-network.yaml b/charts/adapter-service/config/multi-adapter/routing-network.yaml similarity index 100% rename from charts/adapter-service/config/routing-network.yaml rename to charts/adapter-service/config/multi-adapter/routing-network.yaml diff --git a/charts/adapter-service/config/routing-provider.yaml b/charts/adapter-service/config/multi-adapter/routing-provider.yaml similarity index 100% rename from charts/adapter-service/config/routing-provider.yaml rename to charts/adapter-service/config/multi-adapter/routing-provider.yaml diff --git a/charts/adapter-service/config/single-adapter/network-adapter-config.yaml b/charts/adapter-service/config/single-adapter/network-adapter-config.yaml new file mode 100644 index 00000000..4acbe2ae --- /dev/null +++ b/charts/adapter-service/config/single-adapter/network-adapter-config.yaml @@ -0,0 +1,334 @@ +# Single mode: consumer at /, /publish, /_internal/network/, /_internal/provider/ in one +# process. Each module mirrors its tier file; keys are filled in at pod start (ADR-0001). +appName: "network-adapter" + +log: + level: info + destinations: + - type: stdout + contextKeys: [transaction_id, message_id, subscriber_id, module_id] + +http: + # Also the port the single-adapter routing files use: keep it equal to the + # chart's service.targetPort. + port: 9201 + timeout: + read: 30 + write: 30 + idle: 30 + +pluginManager: + root: ./plugins + +plugins: + # OTLP/gRPC metrics, traces and logs. Turn all three off where no collector + # runs, or the exporter logs a failure every few seconds. + otelsetup: + id: otelsetup + config: + serviceName: "oan-network-adapter" + environment: "dev" + otlpEndpoint: "clickstack-otel-collector.observability.svc.cluster.local:4317" + enableMetrics: "true" + enableTracing: "true" + enableLogs: "true" + +modules: + # consumer: unsigned app calls at / (the catch-all mount, on purpose: see ADR-0001); + # discover to /_internal/network, transactions to /_internal/provider. + - name: consumer-adapter + path: / + handler: + type: std + role: bap + subscriberId: "__CONSUMER_SUBSCRIBER_ID__" + # Outbound connection pool; responseHeaderTimeout includes the upstream + # API call on the consumer -> provider hop. + httpClientConfig: + maxIdleConns: 512 + maxIdleConnsPerHost: 128 + idleConnTimeout: 90s + responseHeaderTimeout: 30s + plugins: + registry: + id: sunbirdRegistry + config: + url: http://registry.registry.svc.cluster.local:8081/api/v1 + entity: Participant + providerEntity: ProviderSchema + timeout: 2 + retry_max: 1 + retry_wait_min: 100ms + retry_wait_max: 500ms + maxResponseBytes: 1048576 + cacheTTL: 0 + keyManager: + id: simplekeymanager + config: + subscriberId: "__CONSUMER_SUBSCRIBER_ID__" + keyId: "__CONSUMER_KEY_ID__" + signingPrivateKey: "__CONSUMER_SIGNING_PRIVATE__" + signingPublicKey: "__CONSUMER_SIGNING_PUBLIC__" + encrPrivateKey: "__CONSUMER_ENCR_PRIVATE__" + encrPublicKey: "__CONSUMER_ENCR_PUBLIC__" + schemaValidator: + id: schemav2validator + config: + type: url + location: "https://raw.githubusercontent.com/beckn/protocol-specifications-v2/refs/tags/core-v2.0.0-lts/api/v2.0.0/beckn.yaml" + cacheTTL: "3600" + extendedSchema_enabled: "true" + extendedSchema_cacheTTL: "86400" + extendedSchema_maxCacheSize: "100" + extendedSchema_downloadTimeout: "30" + extendedSchema_allowedDomains: "raw.githubusercontent.com,schema.beckn.io,schema.nfh.global,openagrinet.github.io" + signValidator: + id: signvalidator + signer: + id: signer + router: + id: router + config: + routingConfig: /app/config/routing-network-adapter-consumer.yaml + middleware: + - id: reqpreprocessor + config: + contextKeys: transaction_id,message_id + role: bap + steps: + - validateSchema + - addRoute + - sign + + # network: verifies the caller, forwards discover and publish to discovery. + # Internal: never expose it on an Ingress. + - name: network-adapter + path: /_internal/network/ + handler: + type: std + role: bpp + subscriberId: "__NETWORK_SUBSCRIBER_ID__" + httpClientConfig: + maxIdleConns: 512 + maxIdleConnsPerHost: 128 + idleConnTimeout: 90s + responseHeaderTimeout: 30s + plugins: + registry: + id: sunbirdRegistry + config: + url: http://registry.registry.svc.cluster.local:8081/api/v1 + entity: Participant + providerEntity: ProviderSchema + timeout: 2 + retry_max: 1 + retry_wait_min: 100ms + retry_wait_max: 500ms + maxResponseBytes: 1048576 + cacheTTL: 0 + keyManager: + id: simplekeymanager + config: + subscriberId: "__NETWORK_SUBSCRIBER_ID__" + keyId: "__NETWORK_KEY_ID__" + signingPrivateKey: "__NETWORK_SIGNING_PRIVATE__" + signingPublicKey: "__NETWORK_SIGNING_PUBLIC__" + encrPrivateKey: "__NETWORK_ENCR_PRIVATE__" + encrPublicKey: "__NETWORK_ENCR_PUBLIC__" + # Extended layer off: it fetches each resource's @context per payload. + # The extendedSchema_* settings apply only when it is on. + schemaValidator: + id: schemav2validator + config: + type: url + location: "https://raw.githubusercontent.com/beckn/protocol-specifications-v2/refs/tags/core-v2.0.0-lts/api/v2.0.0/beckn.yaml" + cacheTTL: "3600" + extendedSchema_enabled: "false" + extendedSchema_cacheTTL: "86400" + extendedSchema_maxCacheSize: "100" + extendedSchema_downloadTimeout: "30" + extendedSchema_allowedDomains: "raw.githubusercontent.com,schema.beckn.io,schema.nfh.global,openagrinet.github.io" + signValidator: + id: signvalidator + signer: + id: signer + router: + id: router + config: + # The multi-adapter network routing, unchanged: discover and + # publish to discovery. + routingConfig: /app/config/routing-network.yaml + middleware: + - id: reqpreprocessor + config: + contextKeys: transaction_id,message_id + role: bpp + steps: + - validateSign + - addRoute + + # provider: verifies the caller and answers from the upstream APIs (no router); + # upstream credentials come from envFromSecrets: provider-upstream. + - name: oanProvider + path: /_internal/provider/ + handler: + type: std + role: bpp + subscriberId: "__PROVIDER_SUBSCRIBER_ID__" + httpClientConfig: + maxIdleConns: 512 + maxIdleConnsPerHost: 128 + idleConnTimeout: 90s + responseHeaderTimeout: 30s + plugins: + registry: + id: sunbirdRegistry + config: + url: http://registry.registry.svc.cluster.local:8081/api/v1 + entity: Participant + providerEntity: ProviderSchema + timeout: 2 + retry_max: 1 + retry_wait_min: 100ms + retry_wait_max: 500ms + maxResponseBytes: 1048576 + cacheTTL: 0 + keyManager: + id: simplekeymanager + config: + subscriberId: "__PROVIDER_SUBSCRIBER_ID__" + keyId: "__PROVIDER_KEY_ID__" + signingPrivateKey: "__PROVIDER_SIGNING_PRIVATE__" + signingPublicKey: "__PROVIDER_SIGNING_PUBLIC__" + encrPrivateKey: "__PROVIDER_ENCR_PRIVATE__" + encrPublicKey: "__PROVIDER_ENCR_PUBLIC__" + schemaValidator: + id: schemav2validator + config: + type: url + location: "https://raw.githubusercontent.com/beckn/protocol-specifications-v2/refs/tags/core-v2.0.0-lts/api/v2.0.0/beckn.yaml" + cacheTTL: "3600" + extendedSchema_enabled: "true" + extendedSchema_cacheTTL: "86400" + extendedSchema_maxCacheSize: "100" + extendedSchema_downloadTimeout: "30" + extendedSchema_allowedDomains: "raw.githubusercontent.com,schema.beckn.io,schema.nfh.global,openagrinet.github.io" + signValidator: + id: signvalidator + signer: + id: signer + mapper: + id: jsonmapper + config: + fetchTimeout: 10s + maxMappingBytes: 262144 + cacheTTL: 1m + negativeTTL: 1m + maxCacheEntries: 200 + middleware: + - id: reqpreprocessor + config: + contextKeys: transaction_id,message_id + role: bpp + providerSteps: + - id: WeatherObservation + config: + bindingKeys: "mausamgram|openagrinet:WeatherObservation" + mausamgram: + authScheme: header + headerName: Authorization + headerValueEnv: MAUSAMGRAM_AUTH + + - id: MandiPrice + config: + bindingKeys: "agmarknet|openagrinet:MandiPrice" + agmarknet: + authScheme: none + + - id: KnowledgeAdvisory + config: + bindingKeys: "bharat-vistaar|openagrinet:KnowledgeAdvisory" + bharat-vistaar: + authScheme: oauth2 + # Substituted from the provider Secret (vistaarTokenUrl) via + # adapterKeys.extraSubstitutions. + tokenUrl: "__VISTAAR_TOKEN_URL__" + clientIdEnv: VISTAAR_CLIENT_ID + clientSecretEnv: VISTAAR_CLIENT_SECRET + maxResponseBytes: 1048576 + + - id: AgricultureFacility + config: + bindingKeys: "pocra|openagrinet:AgricultureFacility" + pocra: + authScheme: none + searchConcurrency: 1 + steps: + - validateSign + - validateSchema + - WeatherObservation + - MandiPrice + - KnowledgeAdvisory + - AgricultureFacility + + # provider-publish: the catalogue system publishes at /publish, signed as the + # provider. No validateSign: never expose it on an Ingress. + - name: oanProviderPublish + path: /publish + handler: + type: std + role: bap + subscriberId: "__PROVIDER_SUBSCRIBER_ID__" + httpClientConfig: + maxIdleConns: 512 + maxIdleConnsPerHost: 128 + idleConnTimeout: 90s + responseHeaderTimeout: 30s + plugins: + registry: + id: sunbirdRegistry + config: + url: http://registry.registry.svc.cluster.local:8081/api/v1 + entity: Participant + providerEntity: ProviderSchema + timeout: 2 + retry_max: 1 + retry_wait_min: 100ms + retry_wait_max: 500ms + maxResponseBytes: 1048576 + cacheTTL: 0 + keyManager: + id: simplekeymanager + config: + subscriberId: "__PROVIDER_SUBSCRIBER_ID__" + keyId: "__PROVIDER_KEY_ID__" + signingPrivateKey: "__PROVIDER_SIGNING_PRIVATE__" + signingPublicKey: "__PROVIDER_SIGNING_PUBLIC__" + encrPrivateKey: "__PROVIDER_ENCR_PRIVATE__" + encrPublicKey: "__PROVIDER_ENCR_PUBLIC__" + schemaValidator: + id: schemav2validator + config: + type: url + location: "https://raw.githubusercontent.com/beckn/protocol-specifications-v2/refs/tags/core-v2.0.0-lts/api/v2.0.0/beckn.yaml" + cacheTTL: "3600" + extendedSchema_enabled: "true" + extendedSchema_cacheTTL: "86400" + extendedSchema_maxCacheSize: "100" + extendedSchema_downloadTimeout: "30" + extendedSchema_allowedDomains: "raw.githubusercontent.com,schema.beckn.io,schema.nfh.global,openagrinet.github.io" + signer: + id: signer + router: + id: router + config: + routingConfig: /app/config/routing-network-adapter-provider.yaml + middleware: + - id: reqpreprocessor + config: + contextKeys: transaction_id,message_id + role: bap + steps: + - validateSchema + - addRoute + - sign diff --git a/charts/adapter-service/config/single-adapter/routing-network-adapter-consumer.yaml b/charts/adapter-service/config/single-adapter/routing-network-adapter-consumer.yaml new file mode 100644 index 00000000..ef6bc99e --- /dev/null +++ b/charts/adapter-service/config/single-adapter/routing-network-adapter-consumer.yaml @@ -0,0 +1,21 @@ +# Single mode, consumer module: tier hops go to this adapter's own listener. +# localhost:9201 must equal service.targetPort (the render checks it). +routingRules: + # Discovery, via the network module. + - version: "2.0.0" + targetType: url + target: + url: http://localhost:9201/_internal/network + endpoints: + - discover + + # Transactions go to the provider module; context.bppUri passes through untouched. + - version: "2.0.0" + targetType: url + target: + url: http://localhost:9201/_internal/provider + endpoints: + - select + - init + - confirm + - status diff --git a/charts/adapter-service/config/single-adapter/routing-network-adapter-provider.yaml b/charts/adapter-service/config/single-adapter/routing-network-adapter-provider.yaml new file mode 100644 index 00000000..35fcdc52 --- /dev/null +++ b/charts/adapter-service/config/single-adapter/routing-network-adapter-provider.yaml @@ -0,0 +1,10 @@ +# Single mode, /publish: through the network module to discovery. The mount strips +# /publish, so the rule keys on "" with excludeAction; port must equal service.targetPort. +routingRules: + - version: "2.0.0" + targetType: url + target: + url: http://localhost:9201/_internal/network/publish + excludeAction: true + endpoints: + - "" diff --git a/charts/adapter-service/examples/consumer.yaml b/charts/adapter-service/examples/consumer-adapter.yaml similarity index 90% rename from charts/adapter-service/examples/consumer.yaml rename to charts/adapter-service/examples/consumer-adapter.yaml index 0b74cb68..f01e6cee 100644 --- a/charts/adapter-service/examples/consumer.yaml +++ b/charts/adapter-service/examples/consumer-adapter.yaml @@ -8,12 +8,12 @@ # # It is also why the edge should rate-limit this host and nothing else. # -# helm install consumer-adapter . -f examples/consumer.yaml +# helm install consumer-adapter . -f examples/consumer-adapter.yaml # # routing.rules points at the network adapter by RELEASE name. Rename that # release and this must change with it. # =========================================================================== -role: consumer +role: consumer-adapter # The Service name, and therefore the in-cluster DNS name other adapters use. # Set explicitly because fullname is otherwise "-" -- release @@ -46,7 +46,7 @@ handler: - addRoute - sign -# Routing is no longer a value: it comes from config/routing-.yaml in +# Routing is no longer a value: it comes from config/multi-adapter/routing-.yaml in # the chart, like the adapter config. Set `routing.config` to a whole routing # file only if this deployment must differ. diff --git a/charts/adapter-service/examples/network.yaml b/charts/adapter-service/examples/network-adapter.yaml similarity index 78% rename from charts/adapter-service/examples/network.yaml rename to charts/adapter-service/examples/network-adapter.yaml index 3b7eb061..dd40c213 100644 --- a/charts/adapter-service/examples/network.yaml +++ b/charts/adapter-service/examples/network-adapter.yaml @@ -1,15 +1,17 @@ # =========================================================================== -# network role -- the network layer of the Beckn network. +# network-adapter role. deploymentMode decides: single-adapter (default) runs all +# three tiers; multi-adapter runs the network tier alone. # # Verifies that the caller's signature checks out against the key the registry # publishes for them, hands discover and publish to the discovery service, and # signs on the way out. It answers neither action itself. # -# helm install network-adapter . -f examples/network.yaml +# helm install network-adapter . -n network-adapter -f examples/network-adapter.yaml \ +# --set deploymentMode=single-adapter # or multi-adapter # # Keep the release name `network-adapter`: consumer routes to this Service by name. # =========================================================================== -role: network +role: network-adapter # The Service name, and therefore the in-cluster DNS name other adapters use. # Set explicitly because fullname is otherwise "-" -- release @@ -36,7 +38,7 @@ service: # Both actions share one rule because they share a target: publish arrives # from the provider adapter, discover from the consumer adapter, and neither # is answered here. -# Routing is no longer a value: it comes from config/routing-.yaml in +# Routing is no longer a value: it comes from config/multi-adapter/routing-.yaml in # the chart, like the adapter config. Set `routing.config` to a whole routing # file only if this deployment must differ. diff --git a/charts/adapter-service/examples/provider.yaml b/charts/adapter-service/examples/provider-adapter.yaml similarity index 90% rename from charts/adapter-service/examples/provider.yaml rename to charts/adapter-service/examples/provider-adapter.yaml index 96f89b71..9ef4053f 100644 --- a/charts/adapter-service/examples/provider.yaml +++ b/charts/adapter-service/examples/provider-adapter.yaml @@ -5,13 +5,13 @@ # the upstream APIs that actually answer it. Unlike the other two roles it has # more than one target, which is why routing.rules is a list. # -# helm install provider-adapter . -f examples/provider.yaml +# helm install provider-adapter . -f examples/provider-adapter.yaml # # MANDI_TOKEN is the mandi upstream's credential. It is named here and read at # call time, so it never lands in a config file or in the registry -- put it in # a Secret and reference it, do not inline it. # =========================================================================== -role: provider +role: provider-adapter # The Service name, and therefore the in-cluster DNS name other adapters use. # Set explicitly because fullname is otherwise "-" -- release @@ -37,7 +37,7 @@ service: # One rule per upstream. Each target.url is a bare host and port -- the router # appends the action, so no path and no trailing slash. -# Routing is no longer a value: it comes from config/routing-.yaml in +# Routing is no longer a value: it comes from config/multi-adapter/routing-.yaml in # the chart, like the adapter config. Set `routing.config` to a whole routing # file only if this deployment must differ. diff --git a/charts/adapter-service/templates/NOTES.txt b/charts/adapter-service/templates/NOTES.txt index 9a0a7db3..9d6e968f 100644 --- a/charts/adapter-service/templates/NOTES.txt +++ b/charts/adapter-service/templates/NOTES.txt @@ -1,12 +1,21 @@ +{{- $single := eq (include "adapter-service.deploymentMode" .) "single-adapter" -}} {{ .Chart.Name }} {{ .Chart.Version }} — {{ include "adapter-service.fullname" . }} +{{- if $single }} + + mode single: consumer, network and provider in one adapter (/, /publish, /_internal/*) +{{- else }} + role {{ include "adapter-service.role" . }} ({{ include "adapter-service.handlerRole" . }}) steps {{ include "adapter-service.steps" . | fromYamlArray | join " -> " }} +{{- end }} image {{ include "adapter-service.image" . }} service {{ include "adapter-service.fullname" . }}:{{ .Values.service.port }} +{{- if not $single }} registry {{ .Values.registry.url }} - routes from config/routing-{{ include "adapter-service.role" . }}.yaml + routes from config/multi-adapter/routing-{{ include "adapter-service.role" . }}.yaml telemetry {{ if .Values.otel.enabled }}{{ .Values.otel.endpoint }}{{ else }}off{{ end }} +{{- end }} This Service is reachable in-cluster as {{ include "adapter-service.fullname" . }}:{{ .Values.service.port }}. That name comes from the RELEASE name — whatever routes here must use it, so @@ -22,19 +31,22 @@ chart's appVersion. TWO THINGS THIS CHART CANNOT CHECK FOR YOU +{{ if $single -}} +1. The Participant rows. Each tier signs from its own Secret + (adapterKeys); each registry row must carry that tier's public key. +{{- else -}} 1. The Participant row. This adapter signs as {{ .Values.keys.existingSecret.name }}'s subscriberId, and every peer verifies that signature against the public key the registry publishes for it. If the row is missing, or carries a different key, this pod runs and reports Ready while every peer rejects what it signs — and the error appears in their logs, not yours. +{{- end }} -2. Key rotation. The keys live in Secret/{{ include "adapter-service.keysSecretName" . }}, - which this chart does not render and therefore cannot include in the config - checksum. Changing that Secret does not restart anything: +2. Key rotation. Changing a keys Secret does not restart anything: kubectl -n {{ include "common.namespace" . }} rollout restart deploy/{{ include "adapter-service.fullname" . }} -{{- if not .Values.otel.enabled }} +{{- if and (not .Values.otel.enabled) (not $single) }} Telemetry is off, which is the right setting until a collector exists: with it on and nothing listening the exporter logs a failure every interval. @@ -42,7 +54,9 @@ on and nothing listening the exporter logs a failure every interval. {{- if .Values.ingress.enabled }} Ingress is ON. -{{- if eq (include "adapter-service.role" .) "consumer" }} +{{- if $single }} +/ is the unsigned consumer: rate-limit this host. /publish and /_internal* are never exposed. +{{- else if eq (include "adapter-service.role" .) "consumer" }} This is the consumer role, which accepts UNSIGNED requests — it is inside the trust boundary and has no validateSign step. An Ingress therefore exposes an unauthenticated entry point to the network: rate-limit this host, and do not diff --git a/charts/adapter-service/templates/_helpers.tpl b/charts/adapter-service/templates/_helpers.tpl index ccbf19ad..ddbdd708 100644 --- a/charts/adapter-service/templates/_helpers.tpl +++ b/charts/adapter-service/templates/_helpers.tpl @@ -14,7 +14,8 @@ */}} {{/* -The adapter's role: provider | network | consumer. +The adapter's role: provider-adapter | network-adapter | consumer-adapter (old names +provider | network | consumer still accepted). Returns the tier: provider | network | consumer. Required, with no default. A default would silently give one role's step list and handler role to a different adapter -- which renders, starts, reports Ready @@ -23,12 +24,13 @@ and then mis-handles every request, at the far end, in a peer's logs. {{- define "adapter-service.role" -}} {{- $role := .Values.role | default "" -}} {{- if not $role -}} -{{- fail (printf "%s: role is required -- one of provider, network, consumer. It decides the handler role, the step list and the routing target, so there is no safe default. See examples/ for a values file per role." .Chart.Name) -}} +{{- fail (printf "%s: role is required -- one of provider-adapter, network-adapter, consumer-adapter. It decides the handler role, the step list and the routing target, so there is no safe default. See examples/ for a values file per role." .Chart.Name) -}} {{- end -}} -{{- if not (has $role (list "provider" "network" "consumer")) -}} -{{- fail (printf "%s: role must be one of provider, network, consumer (got %q)." .Chart.Name $role) -}} +{{- $tier := trimSuffix "-adapter" $role -}} +{{- if not (has $tier (list "provider" "network" "consumer")) -}} +{{- fail (printf "%s: role must be one of provider-adapter, network-adapter, consumer-adapter (got %q)." .Chart.Name $role) -}} {{- end -}} -{{- $role -}} +{{- $tier -}} {{- end }} {{/* diff --git a/charts/adapter-service/templates/_network-adapter.tpl b/charts/adapter-service/templates/_network-adapter.tpl new file mode 100644 index 00000000..abfa1c10 --- /dev/null +++ b/charts/adapter-service/templates/_network-adapter.tpl @@ -0,0 +1,35 @@ +{{/* Single mode (role network-adapter): three tiers in one adapter; config from +config/single-adapter/network-adapter-config.yaml and its routing files (ADR-0001). */}} + +{{/* deploymentMode (single-adapter | multi-adapter); provider and consumer are always multi-adapter. */}} +{{- define "adapter-service.deploymentMode" -}} +{{- $mode := .Values.deploymentMode | default "single-adapter" -}} +{{- if not (has $mode (list "single-adapter" "multi-adapter")) -}} +{{- fail (printf "%s: deploymentMode must be single-adapter or multi-adapter (got %q)." .Chart.Name $mode) -}} +{{- end -}} +{{- ternary $mode "multi-adapter" (eq (include "adapter-service.role" .) "network") -}} +{{- end }} + +{{/* Fails unless every module's private keys are tier placeholders. +Usage: include "adapter-service.networkCheckTierKeys" (dict "ctx" $ "cfg" $parsedConfig) */}} +{{- define "adapter-service.networkCheckTierKeys" -}} +{{- range $m := .cfg.modules -}} +{{- $km := dig "handler" "plugins" "keyManager" "config" dict $m -}} +{{- range $field := list "signingPrivateKey" "encrPrivateKey" -}} +{{- $v := index $km $field | default "" | toString -}} +{{- if and $v (not (regexMatch "^__(CONSUMER|NETWORK|PROVIDER)_(SIGNING|ENCR)_PRIVATE__$" $v)) -}} +{{- fail (printf "%s: single-mode module %s sets %s to a value that is not a tier placeholder. A private key written into config reaches git, the ConfigMap and `helm get values`; use ___%s__ and the init container fills it from that tier's Secret." $.ctx.Chart.Name $m.name $field (ternary "SIGNING_PRIVATE" "ENCR_PRIVATE" (eq $field "signingPrivateKey"))) -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end }} + +{{/* One tier's key Secret; fails naming the tier when unset. +Usage: include "adapter-service.networkTierSecret" (dict "ctx" $ "tier" "network") */}} +{{- define "adapter-service.networkTierSecret" -}} +{{- $name := index .ctx.Values.adapterKeys (printf "%s-adapter" .tier) | default "" -}} +{{- if not $name -}} +{{- fail (printf "%s: adapterKeys.%s-adapter is required -- the %s tier signs as its own identity, from its own Secret (the six keys under keys.existingSecret)." .ctx.Chart.Name .tier .tier) -}} +{{- end -}} +{{- $name -}} +{{- end }} diff --git a/charts/adapter-service/templates/configmap.yaml b/charts/adapter-service/templates/configmap.yaml index f5c47701..437ccabd 100644 --- a/charts/adapter-service/templates/configmap.yaml +++ b/charts/adapter-service/templates/configmap.yaml @@ -9,6 +9,9 @@ into an emptyDir at start-up. Everything that differs between the provider, network and consumer adapters is a value: they are one image with one config format, and the role decides the identity, the handler role, the step list and where requests are routed. + +Single mode reads config/single-adapter/network-adapter-config.yaml and its routing files +(config/single-adapter/routing-network-adapter-*.yaml, config/multi-adapter/routing-network.yaml). */}} {{/* RAW CONFIG OVERRIDE. @@ -24,7 +27,25 @@ at pod start, which is why no rendered config ever holds a key. A raw config with a literal private key in it would put that key in git, in a ConfigMap, and in `helm get values` -- so the render refuses it. */}} -{{- $cfgFile := printf "config/%s-config.yaml" (include "adapter-service.role" .) }} +{{- $single := eq (include "adapter-service.deploymentMode" .) "single-adapter" }} +{{- $nlCfg := "" }} +{{- if $single }} +{{- /* Single mode: config/single-adapter/network-adapter-config.yaml unless `config` replaces it; + private keys must be tier placeholders either way. */}} +{{- $nlCfg = .Values.config | default (.Files.Get "config/single-adapter/network-adapter-config.yaml") }} +{{- if not $nlCfg }} +{{- fail (printf "%s: role network-adapter in single mode reads config/single-adapter/network-adapter-config.yaml, which is missing from the chart." .Chart.Name) }} +{{- end }} +{{- $nlParsed := $nlCfg | fromYaml }} +{{- if or (not $nlParsed) (hasKey $nlParsed "Error") }} +{{- fail (printf "%s: the single-mode adapter config (`config` override or config/single-adapter/network-adapter-config.yaml) does not parse as YAML, so its private keys cannot be checked." .Chart.Name) }} +{{- end }} +{{- include "adapter-service.networkCheckTierKeys" (dict "ctx" . "cfg" $nlParsed) }} +{{- if ne (toString (dig "http" "port" "" $nlParsed)) (toString .Values.service.targetPort) }} +{{- fail (printf "%s: the single-mode config listens on http.port %v but service.targetPort is %v. The single-mode routing sends tier hops to that port on localhost, so the two must match." .Chart.Name (dig "http" "port" "unset" $nlParsed) .Values.service.targetPort) }} +{{- end }} +{{- else }} +{{- $cfgFile := printf "config/multi-adapter/%s-config.yaml" (include "adapter-service.role" .) }} {{- $cfg := .Values.config | default (.Files.Get $cfgFile) }} {{- if $cfg }} {{- range $ph, $field := dict "__ADAPTER_SIGNING_PRIVATE__" "signingPrivateKey" "__ADAPTER_ENCR_PRIVATE__" "encrPrivateKey" }} @@ -33,6 +54,7 @@ in `helm get values` -- so the render refuses it. {{- end }} {{- end }} {{- end }} +{{- end }} apiVersion: v1 kind: ConfigMap metadata: @@ -44,6 +66,24 @@ metadata: {{- toYaml . | nindent 4 }} {{- end }} data: + {{- if $single }} + adapter.yaml: | + {{- $nlCfg | nindent 4 }} + {{- if .Values.routing.config }} + {{- fail (printf "%s: routing.config is not used in the single deployment mode. Its routing files are config/single-adapter/routing-network-adapter-{consumer,provider}.yaml and config/multi-adapter/routing-network.yaml; edit those, or set `config` to replace the whole adapter config." .Chart.Name) }} + {{- end }} + {{- range $path := list "config/single-adapter/routing-network-adapter-consumer.yaml" "config/single-adapter/routing-network-adapter-provider.yaml" "config/multi-adapter/routing-network.yaml" }} + {{- $doc := $.Files.Get $path }} + {{- if not $doc }} + {{- fail (printf "%s: single mode needs %s, which is missing from the chart." $.Chart.Name $path) }} + {{- end }} + {{- if and (contains "localhost:" $doc) (not (contains (printf "localhost:%v/" $.Values.service.targetPort) $doc)) }} + {{- fail (printf "%s: %s sends tier hops to a localhost port other than service.targetPort (%v)." $.Chart.Name $path $.Values.service.targetPort) }} + {{- end }} + {{ base $path }}: | + {{- $doc | nindent 4 }} + {{- end }} + {{- else }} adapter.yaml: | {{- /* The config comes from a FILE, not from this template. @@ -55,7 +95,7 @@ data: `config` in values wins when set, for an environment that needs to differ without a new file. */}} - {{- $file := printf "config/%s-config.yaml" (include "adapter-service.role" .) }} + {{- $file := printf "config/multi-adapter/%s-config.yaml" (include "adapter-service.role" .) }} {{- $raw := .Values.config | default ($.Files.Get $file) }} {{- if not $raw }} {{- fail (printf "%s: no config. Expected %s in the chart, or `config` set in values. The role decides the filename, so a new role needs a new file." .Chart.Name $file) }} @@ -72,7 +112,7 @@ data: `routing.config` in values wins when set, for an environment that genuinely has to differ. */}} - {{- $routeFile := printf "config/routing-%s.yaml" (include "adapter-service.role" .) }} + {{- $routeFile := printf "config/multi-adapter/routing-%s.yaml" (include "adapter-service.role" .) }} {{- $routes := .Values.routing.config | default ($.Files.Get $routeFile) }} {{- if not $routes }} {{- fail (printf "%s: no routing. Expected %s in the chart, or `routing.config` set in values. An adapter with no route accepts requests and has nowhere to send them." .Chart.Name $routeFile) }} @@ -99,3 +139,4 @@ data: {{- end }} {{- end }} {{- $routes | nindent 4 }} + {{- end }} diff --git a/charts/adapter-service/templates/deployment.yaml b/charts/adapter-service/templates/deployment.yaml index e6c623c2..aeab01ea 100644 --- a/charts/adapter-service/templates/deployment.yaml +++ b/charts/adapter-service/templates/deployment.yaml @@ -1,4 +1,5 @@ apiVersion: {{ include "common.deployment.apiVersion" . }} +{{- $single := eq (include "adapter-service.deploymentMode" .) "single-adapter" }} kind: Deployment metadata: name: {{ include "adapter-service.fullname" . }} @@ -70,13 +71,23 @@ spec: - -c - | set -eu + {{- if $single }} + # single-adapter mode: adapter.yaml plus one routing file per routed module. + cp /config-template/*.yaml /rendered/ + {{- else }} cp /config-template/adapter.yaml /rendered/adapter.yaml cp /config-template/{{ include "adapter-service.routingFile" . }} /rendered/{{ include "adapter-service.routingFile" . }} + {{- end }} subst() { # $1 placeholder, $2 file holding the value if [ ! -s "$2" ]; then + {{- if $single }} + # three tier Secrets share key names: name the tier too (e.g. network/keyId) + echo "render-config: ${2#/keys/} is missing or empty in its tier's keys Secret" >&2 + {{- else }} echo "render-config: $(basename "$2") is missing or empty in the keys Secret" >&2 + {{- end }} exit 1 fi # A value the registry has not assigned yet. keyId is the osid it @@ -85,7 +96,11 @@ spec: # -- the sentinel contains a colon -- or an adapter signing with an # identity nothing can verify. if head -c 9 "$2" | grep -q "<&2 + {{- else }} echo "render-config: $(basename "$2") is still unresolved." >&2 + {{- end }} echo " The registry assigns it when registry-seed writes this" >&2 echo " adapter's public key. Run that, then:" >&2 echo " ./scripts/resolve-key-ids.sh -e " >&2 @@ -93,6 +108,23 @@ spec: fi sed -i "s|$1|$(cat "$2")|g" /rendered/adapter.yaml } + {{- if $single }} + + {{- /* Each tier's module signs from its own Secret at /keys/. */}} + {{- $k := .Values.keys.existingSecret }} + {{- range $tier := list "consumer" "network" "provider" }} + {{- $T := upper $tier }} + subst __{{ $T }}_SUBSCRIBER_ID__ /keys/{{ $tier }}/{{ $k.subscriberIdKey }} + subst __{{ $T }}_KEY_ID__ /keys/{{ $tier }}/{{ $k.keyIdKey }} + subst __{{ $T }}_SIGNING_PRIVATE__ /keys/{{ $tier }}/{{ $k.signingPrivateKey }} + subst __{{ $T }}_SIGNING_PUBLIC__ /keys/{{ $tier }}/{{ $k.signingPublicKey }} + subst __{{ $T }}_ENCR_PRIVATE__ /keys/{{ $tier }}/{{ $k.encrPrivateKey }} + subst __{{ $T }}_ENCR_PUBLIC__ /keys/{{ $tier }}/{{ $k.encrPublicKey }} + {{- end }} + {{- range $ph, $path := .Values.adapterKeys.extraSubstitutions }} + subst {{ $ph }} /keys/{{ $path }} + {{- end }} + {{- else }} subst __ADAPTER_SUBSCRIBER_ID__ /keys/{{ .Values.keys.existingSecret.subscriberIdKey }} subst __ADAPTER_KEY_ID__ /keys/{{ .Values.keys.existingSecret.keyIdKey }} @@ -107,6 +139,7 @@ spec: {{- range $ph, $key := .Values.keys.existingSecret.extraSubstitutions }} subst {{ $ph }} /keys/{{ $key }} {{- end }} + {{- end }} # A placeholder that survived means a key was substituted with # nothing. Catch it here rather than at the far end, in a peer's @@ -116,14 +149,22 @@ spec: grep -oE '__[A-Z][A-Z0-9_]*__' /rendered/adapter.yaml | sort -u >&2 exit 1 fi - echo "render-config: config rendered for the {{ include "adapter-service.role" . }} role" + echo "render-config: config rendered for the {{ include "adapter-service.role" . }} role{{ if $single }} (single-adapter mode: all three tiers){{ end }}" volumeMounts: - name: config-template mountPath: /config-template readOnly: true + {{- if $single }} + {{- range $tier := list "consumer" "network" "provider" }} + - name: keys-{{ $tier }} + mountPath: /keys/{{ $tier }} + readOnly: true + {{- end }} + {{- else }} - name: keys mountPath: /keys readOnly: true + {{- end }} - name: config mountPath: /rendered resources: @@ -153,7 +194,11 @@ spec: {{- with .Values.extraEnv }} {{- toYaml . | nindent 12 }} {{- end }} - {{- with .Values.envFromSecrets }} + {{- $envFrom := .Values.envFromSecrets | default list }} + {{- if and $single .Values.adapterKeys.upstreamSecret }} + {{- $envFrom = append $envFrom .Values.adapterKeys.upstreamSecret }} + {{- end }} + {{- with $envFrom }} envFrom: {{- range . }} - secretRef: @@ -176,10 +221,19 @@ spec: - name: config-template configMap: name: {{ include "adapter-service.fullname" . }}-config + {{- if $single }} + {{- range $tier := list "consumer" "network" "provider" }} + - name: keys-{{ $tier }} + secret: + secretName: {{ include "adapter-service.networkTierSecret" (dict "ctx" $ "tier" $tier) }} + defaultMode: 0400 + {{- end }} + {{- else }} - name: keys secret: secretName: {{ include "adapter-service.keysSecretName" . }} defaultMode: 0400 + {{- end }} {{/* The rendered config. emptyDir rather than anything durable: it is derived from a ConfigMap and a Secret and is rebuilt on every start, diff --git a/charts/adapter-service/templates/ingress.yaml b/charts/adapter-service/templates/ingress.yaml index 529e1f6a..18339e1a 100644 --- a/charts/adapter-service/templates/ingress.yaml +++ b/charts/adapter-service/templates/ingress.yaml @@ -1,9 +1,34 @@ {{- if .Values.ingress.enabled }} +{{- $ann := merge (dict) (.Values.ingress.annotations | default dict) (.Values.commonAnnotations | default dict) }} +{{- $single := eq (include "adapter-service.deploymentMode" .) "single-adapter" }} +{{- if $single }} +{{- /* Single mode: only literal one-segment / paths (Prefix/Exact); no snippet, + rewrite or regex annotation; never /, /publish or /_internal* (ADR-0001). */}} +{{- range $k, $_ := $ann }} +{{- if regexMatch "(?i)(snippet|rewrite|regex)" $k }} +{{- fail (printf "%s: ingress annotation %s is not allowed in the single deployment mode. Snippet, rewrite and regex annotations can reach /_internal or /publish." $.Chart.Name $k) }} +{{- end }} +{{- end }} +{{- range .Values.ingress.hosts }} +{{- range .paths }} +{{- $p := .path | default "" }} +{{- $pt := .pathType | default "Prefix" }} +{{- if not (has $pt (list "Prefix" "Exact")) }} +{{- fail (printf "%s: ingress path %q has pathType %s, which is not allowed in the single deployment mode. Use Prefix or Exact." $.Chart.Name $p $pt) }} +{{- end }} +{{- if or (not (regexMatch "^/[A-Za-z0-9_.-]+$" $p)) (hasPrefix "/_internal" $p) (eq $p "/publish") }} +{{- fail (printf "%s: ingress path %q is not allowed in the single deployment mode. Allowed: one literal segment, / (e.g. /discover, /select). /, /publish and /_internal* are never public." $.Chart.Name $p) }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} {{/* Off by default, and worth a thought before turning on. -This adapter takes SIGNED requests -- validateSign is its first step -- so -publishing it is not the same risk as publishing an adapter that does not. But +The provider and network roles take SIGNED requests -- validateSign is their +first step -- so publishing them is not the same risk as publishing an adapter +that does not. The consumer role, and the single-mode consumer module +at /, take UNSIGNED requests (NOTES says so when ingress is on). Either way it is still a Beckn peer endpoint, and the thing that makes it safe is that every caller must hold a key the registry publishes. That is a property of the registry's contents, not of this Ingress. @@ -14,7 +39,7 @@ metadata: name: {{ include "adapter-service.fullname" . }} labels: {{- include "adapter-service.labels" . | nindent 4 }} - {{- with (merge (dict) .Values.ingress.annotations (.Values.commonAnnotations | default dict)) }} + {{- with $ann }} annotations: {{- toYaml . | nindent 4 }} {{- end }} diff --git a/charts/adapter-service/values.yaml b/charts/adapter-service/values.yaml index d00b624a..a00d2404 100644 --- a/charts/adapter-service/values.yaml +++ b/charts/adapter-service/values.yaml @@ -7,9 +7,12 @@ # # INSTALL ONCE PER ROLE, and keep the RELEASE name per-role: # -# helm install provider-adapter . -f examples/provider.yaml -# helm install network-adapter . -f examples/network.yaml -# helm install consumer-adapter . -f examples/consumer.yaml +# helm install provider-adapter . -f examples/provider-adapter.yaml +# helm install network-adapter . -f examples/network-adapter.yaml +# helm install consumer-adapter . -f examples/consumer-adapter.yaml +# +# Role network-adapter defaults to single-adapter mode, all three tiers in one release +# (examples/network-adapter.yaml); see deploymentMode. # # The release name is what the Service is called, which is the DNS name the # other adapters address. Rename a release and you must update whatever routes @@ -25,7 +28,8 @@ # =========================================================================== # --------------------------------------------------------------------------- -# Role: provider | network | consumer +# Role: provider-adapter | network-adapter | consumer-adapter (provider | network | consumer +# still accepted) # # Required, with no default. A default would hand one role's handler role and # step list to a different adapter, which renders, starts, reports Ready and @@ -33,6 +37,10 @@ # --------------------------------------------------------------------------- role: "" +# single-adapter (default): role network-adapter runs all three tiers; multi-adapter: the +# network tier alone. provider-adapter and consumer-adapter ignore it. +deploymentMode: single-adapter + # Overrides for what the role implies. Leave empty to take the role's default. appName: "" @@ -203,8 +211,8 @@ handler: # --------------------------------------------------------------------------- # Routing # -# WHERE THE ROUTES ACTUALLY LIVE: config/routing-.yaml in this chart, -# mounted verbatim, exactly like config/-config.yaml beside it. +# WHERE THE ROUTES ACTUALLY LIVE: config/multi-adapter/routing-.yaml in this chart, +# mounted verbatim, exactly like config/multi-adapter/-config.yaml beside it. # # Routing is topology rather than environment. The in-cluster targets follow the # namespace-per-component convention and are the same in every environment, so @@ -261,8 +269,8 @@ otel: # OTLP/gRPC, e.g. "otel-collector.observability.svc.cluster.local:4317" endpoint: "" environment: dev - # Left empty, defaults to oan--adapter, matching OTEL_SERVICE_NAME in - # the compose stack so traces from either deployment line up. + # Unused: the role's config file sets the service name (plugins.otelsetup.config.serviceName); + # only the uncalled adapter-service.otelServiceName helper reads this. serviceName: "" logLevel: info @@ -327,21 +335,34 @@ resources: # Both hit /health, which the adapter serves from its own mux and which does # not touch the registry or discovery -- so a readiness failure here means # this process is unwell, not that something downstream is. -probes: - liveness: - enabled: true +# The startup probe holds liveness off until /health answers; first boot fetches +# beckn.yaml, so it gets up to 180s. +startupProbe: + enabled: true + httpGet: path: /health - initialDelaySeconds: 10 - periodSeconds: 20 - timeoutSeconds: 3 - failureThreshold: 3 - readiness: - enabled: true + port: http + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 36 +livenessProbe: + enabled: true + httpGet: path: /health - initialDelaySeconds: 5 - periodSeconds: 10 - timeoutSeconds: 3 - failureThreshold: 3 + port: http + initialDelaySeconds: 10 + periodSeconds: 20 + timeoutSeconds: 3 + failureThreshold: 3 +readinessProbe: + enabled: true + httpGet: + path: /health + port: http + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 autoscaling: enabled: false @@ -389,3 +410,15 @@ extraVolumes: [] extraVolumeMounts: [] extraEnv: [] envFromSecrets: [] + +# Single mode only: one key Secret per tier (six keys each, as under keys.existingSecret), +# named as openbao-secrets creates them. routing.config and handler.* are unused. +adapterKeys: + consumer-adapter: consumer-adapter-keys + network-adapter: network-adapter-keys + provider-adapter: provider-adapter-keys + # The provider tier's upstream credentials, loaded as env vars in single-adapter mode. + upstreamSecret: provider-upstream + # Extra placeholders, as "/", e.g. + # __VISTAAR_TOKEN_URL__: provider/vistaarTokenUrl + extraSubstitutions: {} diff --git a/charts/openbao-secrets/Chart.yaml b/charts/openbao-secrets/Chart.yaml index 7a430ef1..b6971ecd 100644 --- a/charts/openbao-secrets/Chart.yaml +++ b/charts/openbao-secrets/Chart.yaml @@ -6,7 +6,7 @@ description: >- This is what turns an OpenBao entry into something a chart can reference by name. type: application -version: 0.1.0 +version: 0.1.1 appVersion: "0.10.7" maintainers: - name: OpenAgriNet Engineering Team diff --git a/charts/openbao-secrets/values.yaml b/charts/openbao-secrets/values.yaml index 75fc2c67..8e7942a2 100644 --- a/charts/openbao-secrets/values.yaml +++ b/charts/openbao-secrets/values.yaml @@ -57,14 +57,17 @@ secrets: # -- adapter identities. keyId inside these is filled in by # resolve-key-ids.sh AFTER the registry has been seeded, so these entries # change once per environment after the first bring-up. + # + # network-adapter (single mode) reads all three identities and provider-upstream; + # in multi mode drop it from the consumer and provider entries (ADR-0001). - name: consumer-adapter-keys - namespaces: [consumer-adapter] + namespaces: [consumer-adapter, network-adapter] - name: network-adapter-keys namespaces: [network-adapter] - name: provider-adapter-keys - namespaces: [provider-adapter] + namespaces: [provider-adapter, network-adapter] # The provider adapter's upstream credentials, named as environment variables # by its config rather than substituted into it. - name: provider-upstream - namespaces: [provider-adapter] + namespaces: [provider-adapter, network-adapter]