3.9 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 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.