Skip to content

Registry Management API

The Registry API is a Quarkus-based credential and configuration gateway that serves as the central secrets broker for ETSI HypO. It acts as a wrapper around HashiCorp Vault, providing secure storage and retrieval of service registry credentials, image pull secrets, Kubernetes kubeconfigs, and Fabric controller credentials. All secrets are scoped to the caller's Keycloak group, enforcing strict multi-tenant isolation.

The service exposes both a REST northbound interface — consumed by ETSI HypO operators — and a Kafka-driven southbound interface consumed by the Package Manager during chart parsing, deployment, activation, and termination flows.

REST API

All REST endpoints are served at port 8091 under the /registry-api root path. Every request must carry a Keycloak JWT in the Authorization: Bearer <token> header.

Table 1: Registry API endpoint summary.
Resource Base path Purpose
Image pull secrets /registry-api/image-pull Manage Docker image registry credentials
Package registry secrets /registry-api/package-registry Manage Helm / artifact registry credentials
Kubernetes configuration /registry-api/kube-conf List stored kubeconfigs and issue terminal access tokens
Vault group management /registry-api/group Create, read, update, and delete Vault groups
Vault policy management /registry-api/policy Create, read, update, and delete Vault policies
Group-policy provisioning /registry-api/policy-group-flow Atomically provision a group with its default policies
Fabric Ziti credentials /registry-api/fabric Retrieve Fabric network controller credentials
Log level control /registry-api/admin/logs Dynamically change runtime log level

Image Pull Secrets

Method Path Description
GET /secret/{secret-name} Retrieve an image pull secret
GET /secrets List all image pull secrets for the caller's group
POST /secret/{secret-name} Create a new image pull secret
PATCH /secret/{secret-name} Update an existing image pull secret
DELETE /secret/{secret-name} Delete an image pull secret

Request body (POST / PATCH):

{
  "username": "my-user",
  "password": "my-password",
  "url": "https://registry.example.com"
}

Package Registry Secrets

Method Path Description
GET /secret/{secret-name} Retrieve package registry credentials
GET /secrets List all package registry secrets for the caller's group
POST /secret/{secret-name} Store new package registry credentials
PATCH /secret/{secret-name} Update existing package registry credentials
DELETE /secret/{secret-name} Delete package registry credentials

Secret names containing URL path segments (e.g., registry.example.eu/myrepo/charts) are sanitised to valid Vault path keys before storage.

Request body (POST / PATCH):

{
  "username": "my-user",
  "password": "my-password",
  "url": "https://charts.example.com/myrepo"
}

Kubernetes Configuration

Method Path Description
GET /secrets List all kubeconfigs stored for the caller's group
GET /token/{serviceId}/{id} Generate a terminal access token for a stored kubeconfig

Kubeconfigs are not stored via this REST resource — they arrive through the dedicated Kafka channel described below.

Vault Group and Policy Management

These endpoints provide programmatic access to the Vault identity store. They are typically called during onboarding to provision a new ETSI HypO tenant.

Group operations (/group):

Method Path Description
GET /{group-name} Retrieve group information from Vault
POST / Create a new Vault group
PATCH /{group-name} Update group metadata or attached policies
DELETE /{group-name} Delete a Vault group

Policy operations (/policy):

Method Path Description
GET /{policy-name} Retrieve a policy and its HCL rules
POST /{policy-name} Create a new Vault policy
PATCH /{policy-name} Update policy rules
DELETE /{policy-name} Delete a policy

Atomic group-policy provisioning (/policy-group-flow):

Method Path Description
POST / Create a single Vault policy covering three secret paths and bind it to a new Vault group in one call

This endpoint extracts the group name from the caller's Keycloak JWT, creates a single Vault policy named {groupName}_policy containing path rules for image-pull, package-registry, and kubernetes-config, and then creates the Vault group with that policy attached. If group creation fails, the previously created policy is automatically rolled back.

Kafka API

In addition to the REST interface, the Registry API participates in the ETSI HypO Kafka bus. The Package Manager uses this channel to request credentials asynchronously without holding them itself.

Table 2: Registry API incoming Kafka channels.
Topic Message type Purpose
get-package-registry-incoming-channel KafkaRequestPayload (polymorphic on type) Credential requests from the Package Manager
registry-store-kubernetes-config CloudEvent with kubeconfig YAML Store a kubeconfig in Vault
registry-retrieve-kubernetes-config serviceId string Retrieve a kubeconfig from Vault
create-group-policy-incoming-channel GroupPolicyMessage Provision a Vault group and its policies
fabric-ziti-creds-persist-incoming-channel FabricAuthCreds Persist Fabric Ziti controller credentials
fabric-ziti-creds-delete-incoming-channel FabricAuthCreds Delete Fabric Ziti controller credentials
Table 3: Registry API outgoing Kafka channels.
Topic Message type Purpose
response-package-registry-outgoing-channel KafkaResponsePayload (polymorphic) Credential response to Package Manager
registry-response-kubernetes-config-outgoing-channel VaultSecretResponseMessage Kubeconfig retrieval result
registry-store-kubernetes-config-result CloudEvent result Kubeconfig store completion

Credential Request Flow (Package Manager Integration)

The get-package-registry-incoming-channel topic uses a polymorphic message format. The type field of the incoming payload determines the operation:

type value Message struct Purpose
parsing RegistryURLRequest Fetch Helm / OCI / Git repo credentials for chart acquisition
deployment KubeConfigRequest Fetch kubeconfig (and optional image pull secret) for Helm install
activation KubeConfigRequest Fetch kubeconfig for service activation or deactivation
termination KubeConfigRequest Fetch kubeconfig for Helm uninstall

Parsing request — sent by the Package Manager when the chart source is a private repository:

{
  "type": "parsing",
  "serviceOrderRequest": {
    "kogitoprocinstanceid": "7aa45064-f4a7-4a25-a7a2-667afb07c3d1",
    "serviceOrder": {
      "serviceOrderId": "2c4fecbc-c436-4f8c-a5db-4a1159ebad2a",
      "serviceId": "790daa4d-7f56-4575-8b64-c558cf518bed",
      "serviceRepositoryURL": "https://charts.example.com/myrepo",
      "serviceArtifactIdentifier": "my-chart",
      "serviceArtifactVersion": "1.2.0"
    }
  },
  "url": "https://charts.example.com/myrepo",
  "schedulerNamespace": ""
}

Parsing response — published to response-package-registry-outgoing-channel:

{
  "id": "790daa4d-7f56-4575-8b64-c558cf518bed",
  "status": "SUCCESS",
  "message": "",
  "data": {
    "type": "parsing",
    "serviceOrderRequest": { "..." : "..." },
    "repoCredentials": {
      "username": "my-user",
      "password": "my-password"
    }
  }
}

Deployment / lifecycle request — sent by the Package Manager before every Helm operation:

{
  "type": "deployment",
  "serviceOperationRequest": {
    "kogitoprocinstanceid": "7aa45064-f4a7-4a25-a7a2-667afb07c3d1",
    "serviceOrderId": "2c4fecbc-c436-4f8c-a5db-4a1159ebad2a",
    "serviceId": "790daa4d-7f56-4575-8b64-c558cf518bed",
    "activateService": true
  },
  "kubernetesServiceID": "c04f3d66-5350-49ea-8b31-ff325f149232",
  "secretName": "my-app-pull-secret"
}

Deployment / lifecycle response — published to response-package-registry-outgoing-channel:

{
  "id": "790daa4d-7f56-4575-8b64-c558cf518bed",
  "status": "SUCCESS",
  "message": "",
  "data": {
    "type": "deployment",
    "serviceOperationRequest": { "..." : "..." },
    "kubeConfigAsBase64": "<base64-encoded kubeconfig YAML>",
    "imagePullSecret": {
      "secretName": "my-app-pull-secret",
      "username": "my-user",
      "password": "my-password",
      "url": "https://registry.example.com"
    },
    "schedulerNamespace": "p2code-scheduler-system"
  }
}

Kubeconfig Store Flow

When a new Kubernetes cluster is registered with ETSI HypO, SONATA publishes the kubeconfig to registry-store-kubernetes-config. The Registry API decodes the CloudEvent payload, stores the Base64-encoded kubeconfig in Vault, and publishes a completion acknowledgement to registry-store-kubernetes-config-result.

Two storage scopes are supported:

Scope Vault path Use case
CLUSTER (default) hypo/kubernetes-config/{group}/{serviceId} Full cluster admin kubeconfig
NAMESPACE_SA hypo/kubernetes-sa-config/{group}/{serviceId} Namespace-scoped service account kubeconfig

Vault Path Structure

All secrets are stored in the KV v2 engine. Paths are always scoped to the caller's Keycloak group, ensuring strict multi-tenant isolation:

Secret type Vault path Keys
Image pull secret hypo/image-pull/{group}/{secret-name} username, password, url
Package registry hypo/package-registry/{group}/{secret-name} username, password, url
Cluster kubeconfig hypo/kubernetes-config/{group}/{serviceId} kubeConfig (Base64 YAML)
Namespace kubeconfig hypo/kubernetes-sa-config/{group}/{serviceId} kubeConfig (Base64 YAML)
Fabric Ziti credentials hypo/fabric-secret/{group}/{controllerId} username, password
Service order metadata hypo/service-order-data/{group}/{serviceId} schedulerNamespace

Multi-Tenancy

Every REST and Kafka request carries a Keycloak JWT. The Registry API extracts the first group from the token's groups claim, then exchanges the JWT for a scoped Vault token via the /auth/jwt/login endpoint. Vault policies restrict that token exclusively to paths under the group's prefix, so one tenant cannot read or modify another tenant's secrets.

JWT validation differs between the two interfaces:

  • REST: Quarkus OIDC intercepts every request before it reaches application code. It fetches Keycloak's public signing keys (JWKS) and cryptographically verifies the JWT signature, expiry (exp), and issuer (iss). Only after this check passes is the request handled.
  • Kafka: There is no OIDC filter on the Kafka listeners. The JWT is extracted raw from the Kafka record's Authorization header and its payload is base64-decoded locally to read the groups claim — no signature or expiry verification is performed by the Registry API itself. Vault performs its own validation when the JWT is forwarded to /auth/jwt/login.

Adding Credentials

Package Registry or Helm Repository Credentials

Use these when the chart source (Helm repo, OCI registry, or Git host) requires authentication.

  1. Call POST /registry-api/package-registry/secret/{secret-name} with your Keycloak JWT.
  2. Set {secret-name} to a value that identifies the registry — typically the hostname or URL of the repository.
  3. Provide username and password (and optionally url) in the request body.

The Package Manager looks up credentials by matching the serviceRepositoryURL field of the incoming ServiceOrder against stored secret names.

Adding Image Pull Secrets

Use these when deployed containers must pull images from a private Docker registry.

  1. Call POST /registry-api/image-pull/secret/{secret-name} with your Keycloak JWT.
  2. Choose a {secret-name} you will reference in the Package Manager's deployment request (secretName field).
  3. Provide username, password, and url in the request body.

Kubernetes Kubeconfigs

Kubeconfigs are never stored via the REST API. They are pushed to the registry-store-kubernetes-config Kafka topic as a CloudEvent by SONATA after cluster registration. The Registry API stores the kubeconfig under the service ID and makes it available to the Package Manager on demand.

Vault Group and Policy Provisioning

When onboarding a new ETSI HypO tenant, call POST /registry-api/policy-group-flow/ with the tenant's Keycloak JWT. The service will:

  1. Read the group name from the JWT groups claim.
  2. Create a single Vault policy ({groupName}_policy) with path rules scoped to that group for image-pull, package-registry, and kubernetes-config secrets.
  3. Create a Vault group and attach the policy.
  4. Roll back the created policy if the group creation step fails.