diff --git a/public/access-control.md b/public/access-control.md index 80369420..53f41c91 100644 --- a/public/access-control.md +++ b/public/access-control.md @@ -78,7 +78,6 @@ Phase's RBAC system allows you to define permissions for Create, Read, Update, a | **Secrets** | Manage access to app secrets | | **Lockbox** | Control access to Lockbox secret sharing | | **Logs** | Manage access to app and secret audit logs | -| **Tokens** | Control creation and management of access tokens | | **Members** | Manage user access within the app | | **Integrations** | Control setup and management of app integrations | | **Encryption Mode** | Manage encryption settings for the app | diff --git a/public/access-control/authentication/account.md b/public/access-control/authentication/account.md index e2300874..bb6e60ef 100644 --- a/public/access-control/authentication/account.md +++ b/public/access-control/authentication/account.md @@ -102,4 +102,4 @@ To confirm deletion, type your email address into the confirmation dialog. Organisation audit logs are preserved for compliance. Events you performed remain in your organisations' logs, with the actor shown as *"Deleted account"*. Your account and its personal data are removed. Audit records can still contain identifying fields captured at the time of each event. -Any active dynamic secret leases you hold are revoked at the provider before your account is removed. Existing service account tokens and organisation resources you created (network policies, service tokens) are unaffected: they belong to the organisation, not to you. +Any active dynamic secret leases you hold are revoked at the provider before your account is removed. Existing service account tokens and organisation resources you created, such as network policies, are unaffected: they belong to the organisation, not to you. diff --git a/public/access-control/roles.md b/public/access-control/roles.md index 13e9cfb1..99730334 100644 --- a/public/access-control/roles.md +++ b/public/access-control/roles.md @@ -64,7 +64,6 @@ The organization owner. This role is automatically assigned when a user creates | **RotatingSecrets** | Full access | ✅ | ✅ | ✅ | ✅ | | **Lockbox** | Full access | ✅ | ✅ | ✅ | ✅ | | **Logs** | Full access | ✅ | ✅ | ✅ | ✅ | -| **Tokens (Legacy)** | Full access | ✅ | ✅ | ✅ | ✅ | | **Members** | Full access | ✅ | ✅ | ✅ | ✅ | | **Service Accounts** | Full access | ✅ | ✅ | ✅ | ✅ | | **Integrations** | Full access | ✅ | ✅ | ✅ | ✅ | @@ -106,7 +105,6 @@ Admin users have access to most resources and permissions, and have global acces | **RotatingSecrets** | Full access | ✅ | ✅ | ✅ | ✅ | | **Lockbox** | Full access | ✅ | ✅ | ✅ | ✅ | | **Logs** | Full access | ✅ | ✅ | ✅ | ✅ | -| **Tokens (Legacy)** | Full access | ✅ | ✅ | ✅ | ✅ | | **Members** | Full access | ✅ | ✅ | ✅ | ✅ | | **Service Accounts** | Full access | ✅ | ✅ | ✅ | ✅ | | **Integrations** | Full access | ✅ | ✅ | ✅ | ✅ | @@ -150,7 +148,6 @@ Management users with broad access to environments, secrets, and service account | **RotatingSecrets** | Full access | ✅ | ✅ | ✅ | ✅ | | **Lockbox** | Full access | ✅ | ✅ | ✅ | ✅ | | **Logs** | Full access | ✅ | ✅ | ✅ | ✅ | -| **Tokens (Legacy)** | Full access | ✅ | ✅ | ✅ | ✅ | | **Members** | Full access | ✅ | ✅ | ✅ | ✅ | | **Service Accounts** | Full access | ✅ | ✅ | ✅ | ✅ | | **Integrations** | Full access | ✅ | ✅ | ✅ | ✅ | @@ -192,7 +189,6 @@ Default role for Service Accounts, providing programmatic access to secrets with | **RotatingSecrets** | Read access | ✅ | ❌ | ❌ | ❌ | | **Lockbox** | No access | ❌ | ❌ | ❌ | ❌ | | **Logs** | No access | ❌ | ❌ | ❌ | ❌ | -| **Tokens (Legacy)** | No access | ❌ | ❌ | ❌ | ❌ | | **Members** | Read access | ✅ | ❌ | ❌ | ❌ | | **Service Accounts** | Read access | ✅ | ❌ | ❌ | ❌ | | **Integrations** | Read access | ✅ | ❌ | ❌ | ❌ | @@ -234,7 +230,6 @@ Developers have limited permissions at the organization level and must be given | **RotatingSecrets** | Read access | ✅ | ❌ | ❌ | ❌ | | **Lockbox** | Full access | ✅ | ✅ | ✅ | ✅ | | **Logs** | Read access | ✅ | ❌ | ❌ | ❌ | -| **Tokens (Legacy)** | Custom access | ✅ | ✅ | ❌ | ❌ | | **Members** | Read access | ✅ | ❌ | ❌ | ❌ | | **Service Accounts** | Custom access | ❌ | ✅ | ❌ | ❌ | | **Integrations** | Full access | ✅ | ✅ | ✅ | ✅ | @@ -293,9 +288,6 @@ Some actions require a combination of permissions across multiple resources. Bel - Creating a new third party integration inside of an App - `Integrations:create` - `Environments:read` -- Creating a new Service Token: - - `Tokens:create` - - `Environments:read` - Enable or disable SSE (Server-side Encryption): - `EncryptionMode:update` - `Environments:read` diff --git a/public/console/apps.md b/public/console/apps.md index 2915a4aa..46dfe66f 100644 --- a/public/console/apps.md +++ b/public/console/apps.md @@ -148,6 +148,6 @@ Once enabled, the settings page will show you the updated Encryption mode: To delete an App, click the "Delete" button in the Settings tab. - Deleting an App will permanently delete all Environments, Secrets, and Tokens + Deleting an App will permanently delete all Environments and Secrets associated with it. diff --git a/public/console/logstreams.md b/public/console/logstreams.md index 01772404..fa57083e 100644 --- a/public/console/logstreams.md +++ b/public/console/logstreams.md @@ -36,7 +36,7 @@ Log Streams continuously ship organisation audit logs and secret events from Pha ## Exported events -Every event is a structured JSON envelope (`schema_version: 1`) aligned with OpenTelemetry semantic conventions. `event.category` is `secrets` or `org_audit`; `event.type` is one of `create`, `read`, `update`, `delete` or `access`. The `actor` block identifies who acted (a `user`, `service_account` with its token, `service_token`, or `phase` for system actions), and `phase.description` carries a human-readable summary of every event. +Every event is a structured JSON envelope (`schema_version: 1`) aligned with OpenTelemetry semantic conventions. `event.category` is `secrets` or `org_audit`; `event.type` is one of `create`, `read`, `update`, `delete` or `access`. The `actor` block identifies who acted (a `user`, `service_account` with its token, `service_token` for historical events from a legacy service token, or `phase` for system actions), and `phase.description` carries a human-readable summary of every event. Secret events carry a `phase.secret` block (id, path, version, type — never the name or value). Organisation audit events instead carry a `phase.resource` block with a readable `type` slug (e.g. `app`, `environment`, `member`, `invite`, `role`, `service_account_token`, `rotating_secret`, `network_access_policy`, `log_stream`), the resource `id` and `metadata`, plus `old_values` / `new_values` for changes. diff --git a/public/integrations/platforms/docker.md b/public/integrations/platforms/docker.md index 35c6d116..0215b5f1 100644 --- a/public/integrations/platforms/docker.md +++ b/public/integrations/platforms/docker.md @@ -117,7 +117,7 @@ CMD ["sh", "-c", "phase run \"python manage.py migrate && python manage.py runse Ensure the `PHASE_SERVICE_TOKEN` is securely provided to your container for authentication with Phase services. ```fish -export PHASE_SERVICE_TOKEN=[pss_env:...] +export PHASE_SERVICE_TOKEN=pss_service:v2:... ``` ```fish diff --git a/public/integrations/platforms/hashicorp-terraform.md b/public/integrations/platforms/hashicorp-terraform.md index 0629dc23..91bb6d2d 100644 --- a/public/integrations/platforms/hashicorp-terraform.md +++ b/public/integrations/platforms/hashicorp-terraform.md @@ -55,7 +55,7 @@ To configure the provider, you need to provide your Phase API credentials. We re ```hcl provider "phase" { - phase_token = "pss_service:v1:..." # or "pss_user:v1:..." // A Phase Service Token or a Phase User Token (PAT) + phase_token = "pss_service:v2:..." # or "pss_user:v1:..." // A Phase Service Account Token or a Phase User Token (PAT) // Alternatively supply a PHASE_TOKEN environment variable } ``` @@ -66,7 +66,7 @@ If you are using a self-hosted instance of Phase, you can specify the API host u provider "phase" { host = "https://phase.example.io" skip_tls_verification = true # Optional, if your Phase instance is using a self-signed certificate, you can set this to true to skip TLS verification. - phase_token = "pss_service:v1:..." # or "pss_user:v1:..." // A Phase Service Token or a Phase User Token (PAT) + phase_token = "pss_service:v2:..." # or "pss_user:v1:..." // A Phase Service Account Token or a Phase User Token (PAT) } ``` diff --git a/public/public-api/roles.md b/public/public-api/roles.md index aa0cbc34..f2575541 100644 --- a/public/public-api/roles.md +++ b/public/public-api/roles.md @@ -189,7 +189,6 @@ Responses use camelCase keys (`appPermissions`, `globalAccess`). On POST and PUT "appPermissions": { "Environments": ["read", "create", "update"], "Secrets": ["create", "read", "update", "delete"], - "Tokens": ["read", "create"], "Members": ["read"] }, "globalAccess": false diff --git a/public/sdks/go.md b/public/sdks/go.md index 78ca8629..eabfb3e9 100644 --- a/public/sdks/go.md +++ b/public/sdks/go.md @@ -80,7 +80,7 @@ Before interacting with the Phase service, initialize the SDK with your service Parameters: -- `token` type `string`: Your Phase Service Token (`pss_service:v1:...` or `pss_service:v2:...`) or User Token (`pss_user:v1:...`) +- `token` type `string`: Your Phase Service Token (`pss_service:v2:...`) or User Token (`pss_user:v1:...`) - `host` type `string`: The URL of the Phase Console instance. Defaults to `https://console.phase.dev` if empty. - `debug` type `bool`: Setting to true will result in a higher level of log verbosity useful when debugging @@ -93,7 +93,7 @@ import ( ) func main() { - token := "pss_service:v1:....." + token := "pss_service:v2:....." host := "https://console.phase.dev" // Adjust this for a self-hosted instance of Phase debug := false // For logging verbosity, disable in production diff --git a/public/security/architecture.md b/public/security/architecture.md index 130d267a..18fa7b17 100644 --- a/public/security/architecture.md +++ b/public/security/architecture.md @@ -211,28 +211,28 @@ Once reconstructed, the user's private key can be used to unwrap the *Wrapped Se -### Service Tokens +### Service Account Tokens -Service tokens are used to programmatically access secrets stored in Phase. They can be scoped to one or more environments of an application. Service tokens work analogously to User tokens, but instead of using a pre-existing asymmetric keypair, a new random key pair is generated. +Service account tokens are used to programmatically access secrets stored in Phase as a [Service Account](/access-control/service-accounts). The scope of the token depends on the Apps and Environments the service account has access to, and the role associated with the account. -- A random public/private Curve25519 keypair (Ktoken, ktoken) is generated -- Additionally, a 32-byte *Token ID* and a random *Wrapping Key* Kwrapping are generated -- The token private key ktoken is split into 2 shares: s0 and s1 -- The token string is assembled using s0, Kwrapping, the token's public key Ktoken, and the *Token ID* -- s1 is encrypted with Kwrapping to compute a wrapped share s1wrapped and stored on the backend -- The token string has the format: Ktoken||s0||Kwrapping||TokenId +Each service account has its own set of keys. When a service account is created in the Console, its keys are generated client-side: a random 24-word mnemonic is used to derive an *Account Seed* and an Ed25519 signing key pair, from which an X25519 key-exchange keypair (Ksakx, ksakx) is derived, exactly as described in [user keys](#user-keys). The account keyring and mnemonic are encrypted asymmetrically (wrapped) with the public key Kuserkx of each organization member whose role has access to Service Accounts, and stored on the backend. If server-side key management is enabled for the account, the keyring and mnemonic are additionally wrapped with the server's public key. Service accounts created via the [API](/public-api/service-accounts) always use server-side key management: the server generates a random Ed25519 key pair and wraps the keyring with its own public key. -![service token construction](/assets/images/security/service-token-construction.png) +Service account tokens work analogously to User tokens: they are generated by securely splitting the service account's private key and distributing these shares between the server and client token string: -For each environment that the service token is scoped to, the respective *Environment Salt* and *Environment Seed* are encrypted asymmetrically (wrapped) with the service token publicKey Ktoken and stored on the backend. +- A random 32-byte *Token ID* and a random 32-byte *Wrapping key* Kwrapping are generated +- The service account's private key ksakx is split into 2 shares: s0 and s1 +- The token string is assembled using the *Token ID*, the service account's public key Ksakx, s0 and Kwrapping +- s1 is encrypted with Kwrapping to compute a wrapped share s1wrapped and stored on the backend along with the *Token ID* +- The token string has the format: `pss_service:v2:`TokenId:Ksakx:s0:Kwrapping -When a service token is invoked, the token private key is re-assembled client-side: - - Fetch s1wrapped from the server - - Decrypt s1wrapped with Kwrapping to retrieve s1 - - Reconstruct the token private key ktoken from s0 and s1. +These steps are performed in the browser by a user who can unwrap the service account keyring with their own private key. If server-side key management is enabled, the server can instead unwrap the keyring with its own private key, perform the same steps and return the token string once. In both cases the server stores the *Token ID*, the public key Ksakx and s1wrapped; s0 and Kwrapping exist only in the token string. +A service account is granted access to an environment in the same way as a user: the *Environment Seed* and *Environment Salt* are wrapped with the service account's public key Ksakx and stored on the backend as an EnvironmentKey object. Access is provisioned to the account and not to individual tokens, so every token of a service account can access the same environments across any number of Apps, and creating or deleting a token does not require environment keys to be re-wrapped. -![service token usage](/assets/images/security/service-token-use.png) +When a service account token is invoked, the service account's private key is re-assembled client-side: + - Fetch s1wrapped from the server + - Decrypt s1wrapped with Kwrapping to retrieve s1 + - Reconstruct the service account's private key ksakx from s0 and s1 The private key is used to unwrap the *Environment Seed*, which in turn can be used to derive the Environment keys and decrypt secrets. See [environment access provisioning](#environment-access-provisioning) for details. diff --git a/src/pages/access-control/authentication/account.mdx b/src/pages/access-control/authentication/account.mdx index e2300874..bb6e60ef 100644 --- a/src/pages/access-control/authentication/account.mdx +++ b/src/pages/access-control/authentication/account.mdx @@ -102,4 +102,4 @@ To confirm deletion, type your email address into the confirmation dialog. Organisation audit logs are preserved for compliance. Events you performed remain in your organisations' logs, with the actor shown as *"Deleted account"*. Your account and its personal data are removed. Audit records can still contain identifying fields captured at the time of each event. -Any active dynamic secret leases you hold are revoked at the provider before your account is removed. Existing service account tokens and organisation resources you created (network policies, service tokens) are unaffected: they belong to the organisation, not to you. +Any active dynamic secret leases you hold are revoked at the provider before your account is removed. Existing service account tokens and organisation resources you created, such as network policies, are unaffected: they belong to the organisation, not to you. diff --git a/src/pages/access-control/index.mdx b/src/pages/access-control/index.mdx index 80369420..53f41c91 100644 --- a/src/pages/access-control/index.mdx +++ b/src/pages/access-control/index.mdx @@ -78,7 +78,6 @@ Phase's RBAC system allows you to define permissions for Create, Read, Update, a | **Secrets** | Manage access to app secrets | | **Lockbox** | Control access to Lockbox secret sharing | | **Logs** | Manage access to app and secret audit logs | -| **Tokens** | Control creation and management of access tokens | | **Members** | Manage user access within the app | | **Integrations** | Control setup and management of app integrations | | **Encryption Mode** | Manage encryption settings for the app | diff --git a/src/pages/access-control/roles.mdx b/src/pages/access-control/roles.mdx index 13e9cfb1..99730334 100644 --- a/src/pages/access-control/roles.mdx +++ b/src/pages/access-control/roles.mdx @@ -64,7 +64,6 @@ The organization owner. This role is automatically assigned when a user creates | **RotatingSecrets** | Full access | ✅ | ✅ | ✅ | ✅ | | **Lockbox** | Full access | ✅ | ✅ | ✅ | ✅ | | **Logs** | Full access | ✅ | ✅ | ✅ | ✅ | -| **Tokens (Legacy)** | Full access | ✅ | ✅ | ✅ | ✅ | | **Members** | Full access | ✅ | ✅ | ✅ | ✅ | | **Service Accounts** | Full access | ✅ | ✅ | ✅ | ✅ | | **Integrations** | Full access | ✅ | ✅ | ✅ | ✅ | @@ -106,7 +105,6 @@ Admin users have access to most resources and permissions, and have global acces | **RotatingSecrets** | Full access | ✅ | ✅ | ✅ | ✅ | | **Lockbox** | Full access | ✅ | ✅ | ✅ | ✅ | | **Logs** | Full access | ✅ | ✅ | ✅ | ✅ | -| **Tokens (Legacy)** | Full access | ✅ | ✅ | ✅ | ✅ | | **Members** | Full access | ✅ | ✅ | ✅ | ✅ | | **Service Accounts** | Full access | ✅ | ✅ | ✅ | ✅ | | **Integrations** | Full access | ✅ | ✅ | ✅ | ✅ | @@ -150,7 +148,6 @@ Management users with broad access to environments, secrets, and service account | **RotatingSecrets** | Full access | ✅ | ✅ | ✅ | ✅ | | **Lockbox** | Full access | ✅ | ✅ | ✅ | ✅ | | **Logs** | Full access | ✅ | ✅ | ✅ | ✅ | -| **Tokens (Legacy)** | Full access | ✅ | ✅ | ✅ | ✅ | | **Members** | Full access | ✅ | ✅ | ✅ | ✅ | | **Service Accounts** | Full access | ✅ | ✅ | ✅ | ✅ | | **Integrations** | Full access | ✅ | ✅ | ✅ | ✅ | @@ -192,7 +189,6 @@ Default role for Service Accounts, providing programmatic access to secrets with | **RotatingSecrets** | Read access | ✅ | ❌ | ❌ | ❌ | | **Lockbox** | No access | ❌ | ❌ | ❌ | ❌ | | **Logs** | No access | ❌ | ❌ | ❌ | ❌ | -| **Tokens (Legacy)** | No access | ❌ | ❌ | ❌ | ❌ | | **Members** | Read access | ✅ | ❌ | ❌ | ❌ | | **Service Accounts** | Read access | ✅ | ❌ | ❌ | ❌ | | **Integrations** | Read access | ✅ | ❌ | ❌ | ❌ | @@ -234,7 +230,6 @@ Developers have limited permissions at the organization level and must be given | **RotatingSecrets** | Read access | ✅ | ❌ | ❌ | ❌ | | **Lockbox** | Full access | ✅ | ✅ | ✅ | ✅ | | **Logs** | Read access | ✅ | ❌ | ❌ | ❌ | -| **Tokens (Legacy)** | Custom access | ✅ | ✅ | ❌ | ❌ | | **Members** | Read access | ✅ | ❌ | ❌ | ❌ | | **Service Accounts** | Custom access | ❌ | ✅ | ❌ | ❌ | | **Integrations** | Full access | ✅ | ✅ | ✅ | ✅ | @@ -293,9 +288,6 @@ Some actions require a combination of permissions across multiple resources. Bel - Creating a new third party integration inside of an App - `Integrations:create` - `Environments:read` -- Creating a new Service Token: - - `Tokens:create` - - `Environments:read` - Enable or disable SSE (Server-side Encryption): - `EncryptionMode:update` - `Environments:read` diff --git a/src/pages/console/apps.mdx b/src/pages/console/apps.mdx index 2915a4aa..46dfe66f 100644 --- a/src/pages/console/apps.mdx +++ b/src/pages/console/apps.mdx @@ -148,6 +148,6 @@ Once enabled, the settings page will show you the updated Encryption mode: To delete an App, click the "Delete" button in the Settings tab. - Deleting an App will permanently delete all Environments, Secrets, and Tokens + Deleting an App will permanently delete all Environments and Secrets associated with it. diff --git a/src/pages/console/logstreams.mdx b/src/pages/console/logstreams.mdx index 01772404..fa57083e 100644 --- a/src/pages/console/logstreams.mdx +++ b/src/pages/console/logstreams.mdx @@ -36,7 +36,7 @@ Log Streams continuously ship organisation audit logs and secret events from Pha ## Exported events -Every event is a structured JSON envelope (`schema_version: 1`) aligned with OpenTelemetry semantic conventions. `event.category` is `secrets` or `org_audit`; `event.type` is one of `create`, `read`, `update`, `delete` or `access`. The `actor` block identifies who acted (a `user`, `service_account` with its token, `service_token`, or `phase` for system actions), and `phase.description` carries a human-readable summary of every event. +Every event is a structured JSON envelope (`schema_version: 1`) aligned with OpenTelemetry semantic conventions. `event.category` is `secrets` or `org_audit`; `event.type` is one of `create`, `read`, `update`, `delete` or `access`. The `actor` block identifies who acted (a `user`, `service_account` with its token, `service_token` for historical events from a legacy service token, or `phase` for system actions), and `phase.description` carries a human-readable summary of every event. Secret events carry a `phase.secret` block (id, path, version, type — never the name or value). Organisation audit events instead carry a `phase.resource` block with a readable `type` slug (e.g. `app`, `environment`, `member`, `invite`, `role`, `service_account_token`, `rotating_secret`, `network_access_policy`, `log_stream`), the resource `id` and `metadata`, plus `old_values` / `new_values` for changes. diff --git a/src/pages/integrations/platforms/docker.mdx b/src/pages/integrations/platforms/docker.mdx index 35c6d116..0215b5f1 100644 --- a/src/pages/integrations/platforms/docker.mdx +++ b/src/pages/integrations/platforms/docker.mdx @@ -117,7 +117,7 @@ CMD ["sh", "-c", "phase run \"python manage.py migrate && python manage.py runse Ensure the `PHASE_SERVICE_TOKEN` is securely provided to your container for authentication with Phase services. ```fish -export PHASE_SERVICE_TOKEN=[pss_env:...] +export PHASE_SERVICE_TOKEN=pss_service:v2:... ``` ```fish diff --git a/src/pages/integrations/platforms/hashicorp-terraform.mdx b/src/pages/integrations/platforms/hashicorp-terraform.mdx index 0629dc23..91bb6d2d 100644 --- a/src/pages/integrations/platforms/hashicorp-terraform.mdx +++ b/src/pages/integrations/platforms/hashicorp-terraform.mdx @@ -55,7 +55,7 @@ To configure the provider, you need to provide your Phase API credentials. We re ```hcl provider "phase" { - phase_token = "pss_service:v1:..." # or "pss_user:v1:..." // A Phase Service Token or a Phase User Token (PAT) + phase_token = "pss_service:v2:..." # or "pss_user:v1:..." // A Phase Service Account Token or a Phase User Token (PAT) // Alternatively supply a PHASE_TOKEN environment variable } ``` @@ -66,7 +66,7 @@ If you are using a self-hosted instance of Phase, you can specify the API host u provider "phase" { host = "https://phase.example.io" skip_tls_verification = true # Optional, if your Phase instance is using a self-signed certificate, you can set this to true to skip TLS verification. - phase_token = "pss_service:v1:..." # or "pss_user:v1:..." // A Phase Service Token or a Phase User Token (PAT) + phase_token = "pss_service:v2:..." # or "pss_user:v1:..." // A Phase Service Account Token or a Phase User Token (PAT) } ``` diff --git a/src/pages/public-api/roles.mdx b/src/pages/public-api/roles.mdx index aa0cbc34..f2575541 100644 --- a/src/pages/public-api/roles.mdx +++ b/src/pages/public-api/roles.mdx @@ -189,7 +189,6 @@ Responses use camelCase keys (`appPermissions`, `globalAccess`). On POST and PUT "appPermissions": { "Environments": ["read", "create", "update"], "Secrets": ["create", "read", "update", "delete"], - "Tokens": ["read", "create"], "Members": ["read"] }, "globalAccess": false diff --git a/src/pages/sdks/go.mdx b/src/pages/sdks/go.mdx index 78ca8629..eabfb3e9 100644 --- a/src/pages/sdks/go.mdx +++ b/src/pages/sdks/go.mdx @@ -80,7 +80,7 @@ Before interacting with the Phase service, initialize the SDK with your service Parameters: -- `token` type `string`: Your Phase Service Token (`pss_service:v1:...` or `pss_service:v2:...`) or User Token (`pss_user:v1:...`) +- `token` type `string`: Your Phase Service Token (`pss_service:v2:...`) or User Token (`pss_user:v1:...`) - `host` type `string`: The URL of the Phase Console instance. Defaults to `https://console.phase.dev` if empty. - `debug` type `bool`: Setting to true will result in a higher level of log verbosity useful when debugging @@ -93,7 +93,7 @@ import ( ) func main() { - token := "pss_service:v1:....." + token := "pss_service:v2:....." host := "https://console.phase.dev" // Adjust this for a self-hosted instance of Phase debug := false // For logging verbosity, disable in production diff --git a/src/pages/security/architecture.mdx b/src/pages/security/architecture.mdx index 130d267a..18fa7b17 100644 --- a/src/pages/security/architecture.mdx +++ b/src/pages/security/architecture.mdx @@ -211,28 +211,28 @@ Once reconstructed, the user's private key can be used to unwrap the *Wrapped Se -### Service Tokens +### Service Account Tokens -Service tokens are used to programmatically access secrets stored in Phase. They can be scoped to one or more environments of an application. Service tokens work analogously to User tokens, but instead of using a pre-existing asymmetric keypair, a new random key pair is generated. +Service account tokens are used to programmatically access secrets stored in Phase as a [Service Account](/access-control/service-accounts). The scope of the token depends on the Apps and Environments the service account has access to, and the role associated with the account. -- A random public/private Curve25519 keypair (Ktoken, ktoken) is generated -- Additionally, a 32-byte *Token ID* and a random *Wrapping Key* Kwrapping are generated -- The token private key ktoken is split into 2 shares: s0 and s1 -- The token string is assembled using s0, Kwrapping, the token's public key Ktoken, and the *Token ID* -- s1 is encrypted with Kwrapping to compute a wrapped share s1wrapped and stored on the backend -- The token string has the format: Ktoken||s0||Kwrapping||TokenId +Each service account has its own set of keys. When a service account is created in the Console, its keys are generated client-side: a random 24-word mnemonic is used to derive an *Account Seed* and an Ed25519 signing key pair, from which an X25519 key-exchange keypair (Ksakx, ksakx) is derived, exactly as described in [user keys](#user-keys). The account keyring and mnemonic are encrypted asymmetrically (wrapped) with the public key Kuserkx of each organization member whose role has access to Service Accounts, and stored on the backend. If server-side key management is enabled for the account, the keyring and mnemonic are additionally wrapped with the server's public key. Service accounts created via the [API](/public-api/service-accounts) always use server-side key management: the server generates a random Ed25519 key pair and wraps the keyring with its own public key. -![service token construction](/assets/images/security/service-token-construction.png) +Service account tokens work analogously to User tokens: they are generated by securely splitting the service account's private key and distributing these shares between the server and client token string: -For each environment that the service token is scoped to, the respective *Environment Salt* and *Environment Seed* are encrypted asymmetrically (wrapped) with the service token publicKey Ktoken and stored on the backend. +- A random 32-byte *Token ID* and a random 32-byte *Wrapping key* Kwrapping are generated +- The service account's private key ksakx is split into 2 shares: s0 and s1 +- The token string is assembled using the *Token ID*, the service account's public key Ksakx, s0 and Kwrapping +- s1 is encrypted with Kwrapping to compute a wrapped share s1wrapped and stored on the backend along with the *Token ID* +- The token string has the format: `pss_service:v2:`TokenId:Ksakx:s0:Kwrapping -When a service token is invoked, the token private key is re-assembled client-side: - - Fetch s1wrapped from the server - - Decrypt s1wrapped with Kwrapping to retrieve s1 - - Reconstruct the token private key ktoken from s0 and s1. +These steps are performed in the browser by a user who can unwrap the service account keyring with their own private key. If server-side key management is enabled, the server can instead unwrap the keyring with its own private key, perform the same steps and return the token string once. In both cases the server stores the *Token ID*, the public key Ksakx and s1wrapped; s0 and Kwrapping exist only in the token string. +A service account is granted access to an environment in the same way as a user: the *Environment Seed* and *Environment Salt* are wrapped with the service account's public key Ksakx and stored on the backend as an EnvironmentKey object. Access is provisioned to the account and not to individual tokens, so every token of a service account can access the same environments across any number of Apps, and creating or deleting a token does not require environment keys to be re-wrapped. -![service token usage](/assets/images/security/service-token-use.png) +When a service account token is invoked, the service account's private key is re-assembled client-side: + - Fetch s1wrapped from the server + - Decrypt s1wrapped with Kwrapping to retrieve s1 + - Reconstruct the service account's private key ksakx from s0 and s1 The private key is used to unwrap the *Environment Seed*, which in turn can be used to derive the Environment keys and decrypt secrets. See [environment access provisioning](#environment-access-provisioning) for details.