@pidgeon-ai/issuer 0.1.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A Post issuer you run yourself, inside your own network. Your own domain
4
4
  signs your AI agents' cards, sign-ins and grants, with a key that never
5
- leaves you, for the agents your identity provider says you have. Nothing is
5
+ leaves you, for the agents your identity provider (Keycloak, Auth0, Okta) says you have. Nothing is
6
6
  sent to Pidgeon: not the key, not the list of agents.
7
7
 
8
8
  It is for companies that may not hand authentication to a third party. Pidgeon
@@ -32,8 +32,8 @@ with `pidgeon-issuer export <dir>` and serve it from any static host.
32
32
 
33
33
  1. **The issuer's key**, once: `pidgeon-issuer keygen acme.com`. It writes
34
34
  `pidgeon-issuer-key.json` (mode 0600). Mount it as a secret, or put the
35
- seed in `PIDGEON_ISSUER_KEY`. Any `Signer` fits in its place, so a KMS or
36
- an HSM needs no change to the rest.
35
+ seed in `PIDGEON_ISSUER_KEY`. Or keep it in AWS KMS, below. Any `Signer`
36
+ fits in its place, so an HSM needs no change to the rest.
37
37
  2. **Each agent's key**, on the agent's machine: `pidgeon-issuer keygen
38
38
  acme.com --out ~/.config/pidgeon/id/acme.com.json`. Give the identity
39
39
  provider the `key` line it prints; the private half stays with the agent.
@@ -73,6 +73,37 @@ rest, and its groups are what `grants` maps.
73
73
  secret is `KEYCLOAK_CLIENT_SECRET`. `examples/self-hosted-issuer` runs all of
74
74
  it in Docker.
75
75
 
76
+ ## Auth0
77
+
78
+ An agent is a machine-to-machine application whose metadata names it:
79
+ `pidgeon_key` (the agent's public key), `pidgeon_address`, and optionally
80
+ `pidgeon_kind`, `pidgeon_groups` (comma-separated) and `pidgeon_disabled`
81
+ (`true` to switch it off; Auth0 applications have no enabled switch). The
82
+ issuer reads them with an application of its own authorized for the
83
+ Management API with `read:clients` only; its secret is
84
+ `AUTH0_CLIENT_SECRET`.
85
+
86
+ ```json
87
+ "directory": { "auth0": { "tenant": "acme.eu.auth0.com", "client_id": "<the issuer's app>" } }
88
+ ```
89
+
90
+ ## Okta
91
+
92
+ An agent is an active OAuth service app whose profile names it:
93
+ `"profile": { "pidgeon": { "key": "…", "address": "…", "kind": "agent",
94
+ "groups": ["refunds"] } }`. Deactivate the app to switch it off. The issuer
95
+ reads them with a service app of its own granted `okta.apps.read` and a
96
+ read-only admin role, signing in with Private Key JWT; its RSA or P-256
97
+ private key is `OKTA_PRIVATE_KEY`.
98
+
99
+ ```json
100
+ "directory": { "okta": { "org": "https://acme.okta.com", "client_id": "<the issuer's app>" } }
101
+ ```
102
+
103
+ On Auth0 and Okta the agent authenticates to the provider with its own RSA
104
+ or EC credential: neither accepts an Ed25519 key for that, so the one-key
105
+ trick is Keycloak's alone.
106
+
76
107
  ## Sign in to a site
77
108
 
78
109
  A site lists your issuer beside Pidgeon's:
@@ -100,6 +131,56 @@ const { site } = await signInWithPidgeon(await loadIdentity('acme.com'), {
100
131
  });
101
132
  ```
102
133
 
134
+ ## The key in AWS KMS
135
+
136
+ Make an Ed25519 key that never leaves KMS and name it instead of a file:
137
+
138
+ ```bash
139
+ aws kms create-key --key-spec ECC_NIST_EDWARDS25519 --key-usage SIGN_VERIFY
140
+ aws kms create-alias --alias-name alias/pidgeon-issuer --target-key-id <key id>
141
+ ```
142
+
143
+ ```json
144
+ "key": { "kms": "alias/pidgeon-issuer", "region": "eu-west-1" }
145
+ ```
146
+
147
+ The issuer's role needs `kms:GetPublicKey` and `kms:Sign` on that key and
148
+ nothing else; credentials come the AWS SDK's usual way. Install
149
+ `@aws-sdk/client-kms` beside the library (the container image has it). The
150
+ key is read once at start: an alias is pinned to the key it names then, and
151
+ every signature is checked against that key before it leaves.
152
+
153
+ ## A person signs in, through your identity provider
154
+
155
+ Your staff sign in to the same sites as themselves, through your own OpenID
156
+ Connect provider (Keycloak, Auth0, Okta, Entra), with the flow Pidgeon Cloud
157
+ uses. Register the issuer at the provider as a confidential client whose
158
+ redirect URI is `https://post.acme.com/api/post/oidc/callback`, then:
159
+
160
+ ```json
161
+ "person": {
162
+ "oidc": { "issuer": "https://sso.acme.com/realms/acme", "client_id": "pidgeon-issuer" }
163
+ },
164
+ "clients": [
165
+ {
166
+ "client_id": "client_shop",
167
+ "host": "shop.example",
168
+ "redirect_uris": ["https://shop.example/api/auth/pidgeon/callback"]
169
+ }
170
+ ]
171
+ ```
172
+
173
+ with the client's secret in `PIDGEON_PERSON_CLIENT_SECRET` and a random
174
+ `PIDGEON_ISSUER_SECRET` of 32 characters or more (`openssl rand -base64 32`),
175
+ which seals the sign-in in progress and the sixty-second code, so there is
176
+ still no database. The issuer will not start without it. Rotating it only
177
+ ends the sign-ins in flight.
178
+
179
+ A site that sets `issuer: 'https://post.acme.com'` signs people in there.
180
+ The person must have a verified address at your domain that is not an
181
+ agent's; the site gets `kind: "person"`, your provider's `sub` and that
182
+ address.
183
+
103
184
  ## What it checks before it signs anything
104
185
 
105
186
  - The token request is signed by a key your identity provider gives an
@@ -113,7 +194,7 @@ const { site } = await signInWithPidgeon(await loadIdentity('acme.com'), {
113
194
 
114
195
  ## Left out
115
196
 
116
- The person's sign-in, userinfo, introspection and revocation endpoints (an ID
197
+ Userinfo, introspection and revocation endpoints (an ID
117
198
  token lasts five minutes; revoking an agent is disabling it), dynamic client
118
199
  registration, and Pidgeon's card log, which would mean sending us your agents.
119
200
  Authorization stays yours: a grant is a relation (`customer:42#refunds.issue`
package/dist/bin.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  UsageError,
4
4
  runIssuer
5
- } from "./chunk-CCOFMK5K.js";
5
+ } from "./chunk-5GZMAASA.js";
6
6
 
7
7
  // src/bin.ts
8
8
  async function main() {