Files
matrix/cygnus-cloud-service/README.md

3.3 KiB

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 client_id, installation_id, and the approved scope. The identity endpoint requires the identity.login scope and verifies the same machine 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

Configure clients in an external Spring YAML file rather than the packaged application.yml:

cygnus:
  security:
    enabled: true
    issuer-uri: https://cloud.example.com
    audience: cygnus-cloud-api
    token-audience: https://cloud.example.com/oauth2/token
    clients:
      customer-a:
        enabled: true
        installation-id: site-01
        assertion-public-key: file:/secure/clients/customer-a/public.pem
        scopes:
          - identity.login

Start with that protected file using --spring.config.additional-location=file:/secure/cygnus/clients.yml. Never place cloud private keys, customer assertions, or installation private keys in the repository or container image.