Files
matrix/cygnus-cloud-service

Cygnus cloud service

Identity login API

POST /api/v1/identity/login requires a valid machine JWT with the identity.login scope. The JWT must carry client_id and installation_id; both must equal the values inside the encrypted payload.

The request uses a hybrid encrypted envelope:

{
  "keyId": "cygnus-login-2026-01",
  "encryptedKey": "base64 RSA-OAEP-SHA256 encrypted AES key",
  "initializationVector": "base64 12-byte AES-GCM IV",
  "encryptedPayload": "base64 AES-GCM ciphertext and tag"
}

The AES-GCM additional authenticated data is the UTF-8 keyId. The decrypted JSON is:

{
  "loginId": "user",
  "password": "password",
  "clientId": "client-id-from-jwt",
  "installationId": "installation-id-from-jwt",
  "nonce": "unique-random-value",
  "issuedAt": "2026-07-23T06:30:00Z"
}

Configure the PKCS#8 RSA private key with CYGNUS_LOGIN_PRIVATE_KEY=file:/secure/path/login-private.pem. Keep this key outside the source tree and container image. The corresponding public key is distributed to the on-prem gateway.

The database bootstrap is src/main/resources/db/identity/001_identity_login_schema.sql. It is transactional and idempotent; it copies login/menu data from matrix.public to matrix.identity. It is intended for initial migration and controlled development refreshes. Do not run it after identity becomes the production system of record because its upserts intentionally refresh rows from public.

Machine token endpoint

POST /oauth2/token implements the client-credentials flow used by the on-premises gateway. The client assertion must be:

  • an inner RS256 JWT signed with the installation private key;
  • encrypted as RSA-OAEP-256 plus AES-256-GCM using the cloud assertion key;
  • bound to the configured client ID, installation ID, and token audience;
  • unexpired and no longer-lived than CYGNUS_ASSERTION_TTL.

The endpoint returns a short-lived RS256 access token carrying the client, installation, tenant, license, security-version, and approved-scope claims. The identity endpoint requires the identity.login scope and verifies the same machine and tenant binding in the encrypted login payload.

Generate separate cloud key pairs:

mkdir -p config/keys
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 \
  -out config/keys/assertion-decryption-private.pem
openssl pkey -in config/keys/assertion-decryption-private.pem -pubout \
  -out config/keys/assertion-decryption-public.pem
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 \
  -out config/keys/access-token-private.pem
openssl pkey -in config/keys/access-token-private.pem -pubout \
  -out config/keys/access-token-public.pem
chmod 600 config/keys/*private.pem

Dynamic tenant, installation, and license registration

Machine clients are no longer configured in a runtime clients.yml. The authoritative records are:

  • identity.client_account: tenant identity and status;
  • identity.client_installation: machine identity, assertion public key, allowed scopes, enabled state, and security version;
  • identity.client_license: subscription period, package, type, status, and licensed limits.

Token issuance resolves the installation and active license through a Redis cache-aside service with PostgreSQL fallback. Cache entries have a bounded TTL and can be invalidated after administrative changes. Therefore, new customers, installations, key rotations, scope changes, and license changes do not require restarting the cloud service.

The login/menu queries are tenant-scoped. Tenant-owned identity tables carry tenant_id; identity.pages remains the shared feature catalog while permissions are assigned per tenant.

Use scripts/setup-local-communication.sh to create keys, register or update the database records, create the initial license, and generate the on-premises machine assertion. Never place cloud private keys, customer assertions, or installation private keys in the repository or container image.