Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/assets/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -12761,7 +12761,7 @@
"openIdConnect": {
"type": "openIdConnect",
"openIdConnectUrl": "https://clerk.on.auto/.well-known/openid-configuration",
"description": "An access token of the Auto identity service, sent as Authorization: Bearer <token>, for a person signed in there. A server whose PUBLIC_URL is set takes a token whose audience is that URL. The person acts in the org their organization in the identity service names, over every playbook, with the permissions among the scopes their token was granted. Each permission that permits an operation is the scope of one of its requirements."
"description": "An access token of the Auto identity service, sent as Authorization: Bearer <token>, for a person signed in there. A server whose PUBLIC_URL is set takes a token whose audience is that URL. The person acts in the org their organization in the identity service names, or, where their token names none, in the org whose id is their subject, over every playbook, with the permissions among the scopes their token was granted. Each permission that permits an operation is the scope of one of its requirements."
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions docs/concepts/terminology.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,11 @@ A prediction function, the predictive model a version of it holds and the predic

| Term | Meaning |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Org | The runtime's name for an organization in the Auto identity service; an org's id is that organization's id. Every playbook and every key belongs to one org. |
| Org | The runtime's name for an organization in the Auto identity service, under that organization's id, or for the org of a person signed in with none, under their subject. Every playbook and every key belongs to one org. |
| Caller | Whoever a request acts for: a key, the local developer or a signed-in person, named on every fact it records. A run acts for the caller that started it, and a run a trigger starts acts as the playbook itself. |
| Key | A credential the runtime issues itself, for a machine, a pipeline, a trainer's agent or a grant over some of an org's playbooks. |
| Local developer | The caller of every request in local mode, on the developer's own machine, with every permission in every org. A server is in local mode when it listens only on a loopback address and neither sign-in nor keys are set. |
| Signed-in person | A person the Auto identity service signs in. They act in the org their sign-in names, with the permissions their token grants, and are recorded under their subject. |
| Signed-in person | A person the Auto identity service signs in. They act in the org their sign-in names, or in their own where it names none, with the permissions their token grants, and are recorded under their subject. |

## Supporting assets

Expand Down
55 changes: 34 additions & 21 deletions docs/reference/authentication.md

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion docs/self-host/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Inside an argument, only the value is judged, never the name the argument gives
| Secrets kept in AWS Parameter Store, not in the ledger | `SECRETS_KEEPER=aws-parameter-store:<prefix>`, and `SECRETS_KEEPER_PREVIOUS` while the secrets move from the ledger or back | [Keep secrets in your secret manager](secret-manager.md) |
| Content kept in an object store instead of the ledger | `CONTENT_STORE_URL`, an `s3://bucket/prefix` URL | [Object storage](object-storage.md) |
| Tools that functions may call | `tool_servers` in the file, each entry's `allowed` to allow only some of its tools | [Tool servers](#tool-servers) |
| People who sign in through the Auto identity service | `PUBLIC_URL`, which turns sign-in on, and `IDENTITY_ISSUER` for a development instance of the service | [Let people sign in](security.md#let-people-sign-in) |
| People who sign in through the Auto identity service | `PUBLIC_URL`, which turns sign-in on, `IDENTITY_ISSUER` for a development instance, `IDENTITY_JWKS` or `IDENTITY_JWKS_URL` for its key set | [Let people sign in](security.md#let-people-sign-in) |
| Machines and pipelines that call the API | `API_KEYS`, with an entry the key command prints for each | [API keys](security.md#api-keys) |
| Auto in a container on your own machine, without sign-in | `ACCESS_MODE=local` and `PUBLIC_URL=http://localhost:5555`, with the port published on `127.0.0.1` alone | [Run with Docker](docker.md#on-your-own-machine) |
| Studio served by the runtime, or fetched from another host | `SERVE_STUDIO` and `STUDIO_ORIGIN` | [Studio served by the runtime](security.md#studio-served-by-the-runtime) |
Expand Down Expand Up @@ -107,6 +107,8 @@ Every setting is an environment variable. The tables below group them by area.
| --------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PUBLIC_URL` | none | The URL at which callers reach the runtime, which its links point at. Without `ACCESS_MODE`, it turns sign-in through the Auto identity service on, and every access token names it as its audience; [Let people sign in](security.md#let-people-sign-in) gives the URLs it accepts. Beside `ACCESS_MODE=local`, it signs no one in, is taken only on `localhost`, `127.0.0.1` or `[::1]`, and is needed where the server listens beyond the loopback, as in a container ([Run with Docker](docker.md#on-your-own-machine)) |
| `IDENTITY_ISSUER` | `https://clerk.on.auto` | The identity service that people sign in through, an `https` URL with no path, query or fragment. A development runtime names the development instance of the service |
| `IDENTITY_JWKS` | none | The key set of the identity service, as the JSON of a JWK set, `{"keys":[…]}`, that holds at least one key, each naming its `kty`, for a service that publishes its keys another way, or a server that should read nothing of the service at start. The server then reads neither the metadata nor a key set of the service, and trusts these keys until it restarts. Set it or `IDENTITY_JWKS_URL`, not both. See [What the server checks at start](../reference/authentication.md#what-the-server-checks-at-start) |
| `IDENTITY_JWKS_URL` | none | The `https` URL of the identity service's key set, read in place of the `jwks_uri` of its metadata, which the server then does not read. The key set is kept and read again as one found through the metadata is ([How the key set is kept](../reference/authentication.md#how-the-key-set-is-kept)). Set it or `IDENTITY_JWKS`, not both |
| `API_KEYS` | none | The API keys the server accepts, as a compact JSON array of the entries the key command prints; `api_keys` in the file. See [API keys](security.md#api-keys) |
| `ACCESS_MODE` | none | `local` declares [local mode](security.md#local-mode) on any address, for a server whose port only this computer reaches, such as a container whose port Docker publishes on `127.0.0.1` alone; never with a wider publish, and never beside containers on the same Docker network that you would not let act as you. It stops the start beside `API_KEYS`, beside a `PUBLIC_URL` that is not on `localhost`, `127.0.0.1` or `[::1]`, and without a `PUBLIC_URL` where the server listens beyond the loopback, as in a container; `access_mode` in the file. See [Run with Docker](docker.md#on-your-own-machine) |
| `ALLOWED_ORIGINS` | none | Comma-separated origins a browser page may call the API from, besides the Studio host's and, where the runtime serves Studio, its own; `allowed_origins` in the file. See [Calling from a browser](security.md#calling-from-a-browser) |
Expand Down
14 changes: 7 additions & 7 deletions docs/self-host/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ Every request to the runtime comes from a caller. This page covers the tasks of

The runtime recognizes three kinds of caller:

| Caller | Who uses it | How it authenticates | What it may do |
| ------------------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| A signed-in person | People, through their agent, an agent gateway, Auto Studio or a page you allow | Signs in through the Auto identity service, then sends a token or a page's session as `Authorization: Bearer <token>` | Acts in the org they signed in to, over every playbook, with the permissions their token grants |
| An API key | A machine, a pipeline or a trainer's agent that does not sign in, or any caller that needs a narrower grant | Sends a key listed in `API_KEYS` as `Authorization: Bearer <key>` | Acts in its one org, with the permissions and playbooks its entry lists |
| The local developer | You, on your own machine, in [local mode](#local-mode) | Nothing: every request is trusted | Acts with every permission in whichever org a request names |
| Caller | Who uses it | How it authenticates | What it may do |
| ------------------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| A signed-in person | People, through their agent, an agent gateway, Auto Studio or a page you allow | Signs in through the Auto identity service, then sends a token or a page's session as `Authorization: Bearer <token>` | Acts in the org they signed in to, or in their own where they signed in to none, over every playbook, with the permissions their token grants |
| An API key | A machine, a pipeline or a trainer's agent that does not sign in, or any caller that needs a narrower grant | Sends a key listed in `API_KEYS` as `Authorization: Bearer <key>` | Acts in its one org, with the permissions and playbooks its entry lists |
| The local developer | You, on your own machine, in [local mode](#local-mode) | Nothing: every request is trusted | Acts with every permission in whichever org a request names |

An [org](../concepts/terminology.md#orgs-and-callers) is the runtime's name for an organization in the Auto identity service.
An [org](../concepts/terminology.md#orgs-and-callers) is the runtime's name for an organization in the Auto identity service, or for the org of a person who signs in with none, whose id is their subject.

## Who can call a server

Expand Down Expand Up @@ -64,7 +64,7 @@ With sign-in on, the server checks the identity service before it accepts a requ

### Organizations

An org's id is its organization's id: a person who signs in to the organization `acme` acts in the org `acme`. The runtime keeps no list of people or orgs, since each token names its org.
An org's id is its organization's id: a person who signs in to the organization `acme` acts in the org `acme`. A person who signs in with no organization of their own acts in an org of their own, whose id is the subject of their token, and in no other. The runtime keeps no list of people or orgs, since each token names its org, or, naming none, its subject does.

A company whose people sign in through its own identity provider connects that provider to the identity service in Studio, not in the runtime. The runtime trusts one issuer, whichever provider a person used.

Expand Down
14 changes: 12 additions & 2 deletions packages/api/src/authentication/page-sessions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ describe('a page at an allowed origin, sending the session the identity service
},
);

it('acts in the org of its subject when the session names no org, and is refused every other with 403', async () => {
const { server, fromPage } = await signedInServer();
const headers = await fromPage(defaultStudioOrigin, { o: undefined, sub: 'user_2ada' });

expect([
(await call(server.handler, '/v1/orgs/user_2ada/playbook-labels', { headers })).status,
(await call(server.handler, '/v1/orgs/acme/playbook-labels', { headers })).status,
]).toEqual([200, 403]);
});

it('is refused every other org with 403', async () => {
const { server, fromPage } = await signedInServer();

Expand Down Expand Up @@ -65,11 +75,11 @@ describe('a session the runtime refuses', () => {
expect(answer.headers.get('www-authenticate')).toMatch(/^Bearer error="invalid_token", /u);
});

it('is answered 401 for a session without an organization, naming the o claim', async () => {
it('is answered 401 for a session that names its org in org_id, not o, naming the o claim', async () => {
const { server, fromPage } = await signedInServer();

const answer = await call(server.handler, '/v1/orgs/acme/playbook-labels', {
headers: await fromPage(defaultStudioOrigin, { o: undefined }),
headers: await fromPage(defaultStudioOrigin, { o: undefined, org_id: 'acme' }),
});

expect(answer).toMatchObject({
Expand Down
18 changes: 17 additions & 1 deletion packages/api/src/authentication/signed-in-callers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ describe('a request without a valid token, with sign-in on', () => {
});
});

describe('a signed-in person whose token names no org, over HTTP', () => {
it('is admitted to the org of their subject, and refused every other with 403', async () => {
const { server, bearer } = await signedInServer();
const namingNoOrg = await bearer({ org_id: undefined, sub: 'user_2ada' });

expect([
(await call(server.handler, '/v1/orgs/user_2ada/playbook-labels', { headers: namingNoOrg })).status,
(await call(server.handler, '/v1/orgs/acme/playbook-labels', { headers: namingNoOrg })).status,
]).toEqual([200, 403]);
});
});

describe('a token the runtime refuses, with sign-in on', () => {
it('is answered 401 with invalid_token for an opaque token', async () => {
const { server } = await signedInServer();
Expand All @@ -122,7 +134,11 @@ describe('a token the runtime refuses, with sign-in on', () => {
});

it.each([
['no org', { org_id: undefined }, 'ERR_JWT_CLAIM_VALIDATION_FAILED on its org_id claim'],
[
'its org in o, not org_id',
{ org_id: undefined, o: { id: 'acme' } },
'ERR_JWT_CLAIM_VALIDATION_FAILED on its org_id claim',
],
['no sub', { sub: undefined }, 'ERR_JWT_CLAIM_VALIDATION_FAILED on its sub claim'],
['an expired one', { exp: 1 }, 'ERR_JWT_EXPIRED on its exp claim'],
])(
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/openapi/document-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function securitySchemesOf(identityIssuer: string): OpenApiObject {
description: [
'An access token of the Auto identity service, sent as Authorization: Bearer <token>, for a person signed in there.',
'A server whose PUBLIC_URL is set takes a token whose audience is that URL.',
'The person acts in the org their organization in the identity service names, over every playbook, with the permissions among the scopes their token was granted.',
'The person acts in the org their organization in the identity service names, or, where their token names none, in the org whose id is their subject, over every playbook, with the permissions among the scopes their token was granted.',
'Each permission that permits an operation is the scope of one of its requirements.',
].join(' '),
},
Expand Down
Loading
Loading