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
11 changes: 11 additions & 0 deletions spec/v1/17-sign-in.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ carries a `gated` Client.
| `E_CLIENT_CRD_NOT_BOOTSTRAPPED` | an Application declares `client` and the Platform document's bootstrap set does not list `identity.jorisjonkers.dev/v1` | composition |
| `E_NO_SIGN_IN_PROVIDER` | an Application declares `client` and the Platform document names no sign-in provider | composition |
| `E_CLIENT_PLACEHOLDER_WITHOUT_SECRET` | `${client:secret}` is referenced and the Client is not `credential: secret` | composition |
| `E_CLIENT_PATH_UNROUTED` | no Route of a native Client's exposure reaches its `protectedPath`, or the Route that does reaches a Process that publishes no readiness | composition |
| `E_CLIENT_OPEN` | the Release Gate's anonymous request to a native Client's `protectedPath` is answered with 2xx | release ([chapter 55](55-delivery.md#the-release-gate)) |

`E_CLIENT_OPEN` is the proof a native Client owes: the model cannot see whether
Expand All @@ -247,6 +248,16 @@ release-gate inputs ([chapter 20](20-resolved-deployment.md#the-release-gate)),
and the Release Gate requests it, anonymously, before it switches
([chapter 55](55-delivery.md#the-release-gate)).

**The path is asked of the member the edge would send it to.** Of the Routes of
the Client's exposure that reach a Process, the one whose path is the longest
match takes it: a `prefix` Route matches a path it opens, an `exact` one the
path itself, and a redirect Route reaches no Process. A tie goes to the Route
declared first. That Process is the inputs' `protectedBy`. Asking another
member would prove nothing, and a static member that answers every path with
its index page would hold every release. So the path must reach a gate member:
a Route must take it to a Process, and that Process must publish readiness.
Otherwise the Client is refused, `E_CLIENT_PATH_UNROUTED`.

## Open in this chapter

- **Clients that are not Applications.** A tool that registers itself at
Expand Down
1 change: 1 addition & 0 deletions spec/v1/20-resolved-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,7 @@ Layer 2 therefore carries, per Application a Process of which switches
| the gate deadline | `max` over the members of `progressDeadlineSeconds`, itself `startupBudget × 3` and the substrate's `imagePull` |
| the migration | where the Application moves its schema with a changelog: the migration identity, `testedAgainst` where the proof records one, and `nonTransactional`, each copied from the migration plan ([The migration](#the-migration)) |
| `protectedPath` | where the Application's Client is `native`: its `protectedPath`, which the gate requests anonymously before it switches, holding the release on a 2xx (`E_CLIENT_OPEN`, [chapter 17](17-sign-in.md#what-is-checked)); absent for any other Client and for an Application with none |
| `protectedBy` | beside `protectedPath`: the member the gate asks for it, the Process the longest Route of the Client's exposure takes the path to ([chapter 17](17-sign-in.md#what-is-checked)) |

`max` is the reading "held, not partial" requires: the unit waits for its
slowest legitimate starter. `auth` declares a 600-second budget on `auth-api`
Expand Down
12 changes: 8 additions & 4 deletions spec/v1/55-delivery.md
Original file line number Diff line number Diff line change
Expand Up @@ -401,11 +401,15 @@ is no.
**A native Client proves that it signs people in.** Where the Application's
release-gate inputs carry a `protectedPath`, which they do exactly when its
Client is `native` ([chapter 17](17-sign-in.md#what-is-checked)), the gate
requests that path without credentials from the new version before it lets any
member be promoted. A 2xx answer is `E_CLIENT_OPEN`: the image serves to anyone
requests that path without credentials from the new version of the member the
inputs' `protectedBy` names, at its Canary service, before it lets any member
be promoted. A 2xx answer is `E_CLIENT_OPEN`: the image serves to anyone
what it said it refuses, and the release is held as a failed analysis holds it.
Any other answer, a redirect to sign in or a `401` included, passes. The path is
all the model hands over; the request is the gate's own.
Any other answer, a redirect to sign in or a `401` included, passes, and no
answer at all holds the release, because no data is no. A `protectedBy` member
that did not change in the release is not asked: its image was asked when it
last did. The path and its member are all the model hands over; the request is
the gate's own.

**After promotion the gate warms up.** It requests the same paths once more
through each hostname the Canary hands it, the host of every Route the internet
Expand Down
2 changes: 1 addition & 1 deletion spec/v1/examples/identity/expected/resolved.grafana.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"apiVersion":"resolved.jorisjonkers.dev/v1","client":{"credential":"secret","description":"Metrics and dashboards","host":"grafana.jorisjonkers.dev","iconUrl":"https://grafana.jorisjonkers.dev/public/img/grafana_icon.svg","label":"Grafana","launchUrl":"https://grafana.jorisjonkers.dev/","redirectUris":["https://grafana.jorisjonkers.dev/login/generic_oauth"],"roles":["user","viewer","editor","admin"],"secretPath":"secret/data/identity-system/grafana/client","signIn":"native"},"exposure":[{"certificates":"acme","dns":{"proxied":true,"target":"ingress.jorisjonkers.dev"},"host":"grafana.jorisjonkers.dev","ingressClass":"traefik-public","listener":"tls","name":"public","proxy":{"application":"traefik-public","namespace":"edge-system"},"routes":[{"audience":"authenticated","match":"prefix","middleware":[{"kind":"security-headers"}],"path":"/","precedence":1,"process":"grafana","surface":"http"}],"tier":"public-frankfurt"}],"id":"grafana","kind":"ResolvedApplication","namespace":"identity-system","processes":[{"cpu":"50m","cutover":"continuous","deadline":"210s","egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"},{"namespace":"secrets-system","port":8200,"process":"vault","rule":"secret-store"}],"environment":[{"name":"GF_AUTH_DISABLE_LOGIN_FORM","value":"true"},{"name":"GF_AUTH_GENERIC_OAUTH_API_URL","value":"https://id.jorisjonkers.dev/userinfo"},{"name":"GF_AUTH_GENERIC_OAUTH_AUTH_URL","value":"https://id.jorisjonkers.dev/oauth2/authorize"},{"name":"GF_AUTH_GENERIC_OAUTH_CLIENT_ID","value":"grafana"},{"name":"GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET","secret":{"key":"secret","path":"secret/data/identity-system/grafana/client"}},{"name":"GF_AUTH_GENERIC_OAUTH_ENABLED","value":"true"},{"name":"GF_AUTH_GENERIC_OAUTH_NAME","value":"jorisjonkers.dev"},{"name":"GF_AUTH_GENERIC_OAUTH_SCOPES","value":"openid,profile,email"},{"name":"GF_AUTH_GENERIC_OAUTH_TOKEN_URL","value":"https://id.jorisjonkers.dev/oauth2/token"},{"name":"GF_SERVER_ROOT_URL","value":"https://grafana.jorisjonkers.dev"}],"gid":472,"hardening":"restricted","identity":"grafana","identityToken":false,"image":"docker.io/grafana/grafana@sha256:2c4e6a8b0d2f4c6e8a0b2d4f6c8e0a2b4d6f8c0e2a4b6d8f0c2e4a6b8d0f2c4e","imageVersion":"12.2.0","ingress":[{"namespace":"edge-system","port":3000,"process":"traefik-public","rule":"tier-proxy"},{"consumes":"grafana.http","port":3000,"rule":"consumer"},{"namespace":"delivery-system","port":3000,"process":"release-gate","rule":"release-gate"}],"lifecycle":"application","memory":"256Mi","name":"grafana","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3","frankfurt-contabo-1"]},"readiness":{"failures":3,"path":"/api/health","period":"10s","port":3000,"timeout":"5s"},"replicas":1,"runtime":"none","secrets":[{"access":"read","delivery":"env","destination":"grafana-identity-system-grafana-client","keys":["id","secret"],"path":"secret/data/identity-system/grafana/client","restartTargets":["grafana"]}],"surfaces":[{"name":"http","port":3000}],"switchover":"blue-green","uid":472,"writablePaths":[{"path":"/var/lib/grafana","size":"64Mi"}]}],"project":"identity","provenance":{"inputDigests":[{"digest":"sha256:ebb5134deeb55f320e28609054a0aca85a359f3869501a3a8b7560d7d05a4277","input":"intent-fragment","name":"delivery"},{"digest":"sha256:9a29fae42c0c6cd4f9824d7af3d9ecc68c068a3a00d1f9488bfbb70c882ea565","input":"intent-fragment","name":"edge"},{"digest":"sha256:c45963dcb3911475bff7d4fd17c0ae570230bdadba4ff2a68c75272d9219e762","input":"intent-fragment","name":"identity"},{"digest":"sha256:447e1500b09b608cecc0f93ebb598a60e655690822ecc63395e4e40eaadb98cb","input":"intent-fragment","name":"mail"},{"digest":"sha256:35d5e3643093422537ea72def0f4b19e93ed319a2d0192fc7846342970ce8797","input":"intent-fragment","name":"notes"},{"digest":"sha256:5916815b3b70a0de96137a6614f9356003985016bbf078bafe098433c9aab7d1","input":"intent-fragment","name":"observability"},{"digest":"sha256:31309996835674cc4f037f8e19897ddbe4a7fa8a487a3efc613e251caeed0853","input":"intent-fragment","name":"secrets"},{"digest":"sha256:f7e7e1e0a33d9528e11d956c36b5a6b8f37f758fb98aecdd74ea3eaf7286d60e","input":"platform-intent","name":"jorisjonkers.dev"},{"digest":"sha256:a989e80337efb294fa1a7175b3ad3eea8bed9bf13fbd503a26691c9265575fc2","input":"node-contract","name":"production"},{"digest":"sha256:f09f02097230414af40101e8ebe053e62d679c085a7d1fed9d860c8ba151b68b","input":"images-lock","name":"estate"},{"digest":"sha256:202e5e2a22ab20cfe5b6e85cbd8139cb13ed3b199b2f1af4380068d21d54ff1f","input":"cluster-state","name":"production"}],"renderHash":"sha256:c944fc023ec1bda7d6df2150acd36374ae0d7f8aae46e54baf8c2ec48f1e2f1a","schemaPackageIntegrity":"sha256:5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b"},"reconcileAfter":["apps-edge","apps-secrets"],"reconcileUnit":"apps-identity","releaseGate":{"analysis":{"errorRate":"1%","interval":"30s","iterations":4,"latency":"500ms","threshold":3},"deadline":"210s","endpoint":"http://release-gate.delivery-system.svc.cluster.local:8080","members":[{"process":"grafana","readiness":{"path":"/api/health","port":3000}}],"protectedPath":"/api/user"},"revision":"sha256:09c3571e7ffddafbaa594cb2b21bdd5c54fa81cfe59398c36b85bb9ddac47c85","secretStore":"http://vault.secrets-system.svc.cluster.local:8200"}
{"apiVersion":"resolved.jorisjonkers.dev/v1","client":{"credential":"secret","description":"Metrics and dashboards","host":"grafana.jorisjonkers.dev","iconUrl":"https://grafana.jorisjonkers.dev/public/img/grafana_icon.svg","label":"Grafana","launchUrl":"https://grafana.jorisjonkers.dev/","redirectUris":["https://grafana.jorisjonkers.dev/login/generic_oauth"],"roles":["user","viewer","editor","admin"],"secretPath":"secret/data/identity-system/grafana/client","signIn":"native"},"exposure":[{"certificates":"acme","dns":{"proxied":true,"target":"ingress.jorisjonkers.dev"},"host":"grafana.jorisjonkers.dev","ingressClass":"traefik-public","listener":"tls","name":"public","proxy":{"application":"traefik-public","namespace":"edge-system"},"routes":[{"audience":"authenticated","match":"prefix","middleware":[{"kind":"security-headers"}],"path":"/","precedence":1,"process":"grafana","surface":"http"}],"tier":"public-frankfurt"}],"id":"grafana","kind":"ResolvedApplication","namespace":"identity-system","processes":[{"cpu":"50m","cutover":"continuous","deadline":"210s","egress":[{"namespace":"kube-system","port":53,"rule":"cluster-dns"},{"namespace":"secrets-system","port":8200,"process":"vault","rule":"secret-store"}],"environment":[{"name":"GF_AUTH_DISABLE_LOGIN_FORM","value":"true"},{"name":"GF_AUTH_GENERIC_OAUTH_API_URL","value":"https://id.jorisjonkers.dev/userinfo"},{"name":"GF_AUTH_GENERIC_OAUTH_AUTH_URL","value":"https://id.jorisjonkers.dev/oauth2/authorize"},{"name":"GF_AUTH_GENERIC_OAUTH_CLIENT_ID","value":"grafana"},{"name":"GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET","secret":{"key":"secret","path":"secret/data/identity-system/grafana/client"}},{"name":"GF_AUTH_GENERIC_OAUTH_ENABLED","value":"true"},{"name":"GF_AUTH_GENERIC_OAUTH_NAME","value":"jorisjonkers.dev"},{"name":"GF_AUTH_GENERIC_OAUTH_SCOPES","value":"openid,profile,email"},{"name":"GF_AUTH_GENERIC_OAUTH_TOKEN_URL","value":"https://id.jorisjonkers.dev/oauth2/token"},{"name":"GF_SERVER_ROOT_URL","value":"https://grafana.jorisjonkers.dev"}],"gid":472,"hardening":"restricted","identity":"grafana","identityToken":false,"image":"docker.io/grafana/grafana@sha256:2c4e6a8b0d2f4c6e8a0b2d4f6c8e0a2b4d6f8c0e2a4b6d8f0c2e4a6b8d0f2c4e","imageVersion":"12.2.0","ingress":[{"namespace":"edge-system","port":3000,"process":"traefik-public","rule":"tier-proxy"},{"consumes":"grafana.http","port":3000,"rule":"consumer"},{"namespace":"delivery-system","port":3000,"process":"release-gate","rule":"release-gate"}],"lifecycle":"application","memory":"256Mi","name":"grafana","placement":{"eligibleNodes":["enschede-t1000-1","enschede-rx7900xtx-1","enschede-gtx-960m-1","enschede-pi-1","enschede-pi-2","enschede-pi-3","frankfurt-contabo-1"]},"readiness":{"failures":3,"path":"/api/health","period":"10s","port":3000,"timeout":"5s"},"replicas":1,"runtime":"none","secrets":[{"access":"read","delivery":"env","destination":"grafana-identity-system-grafana-client","keys":["id","secret"],"path":"secret/data/identity-system/grafana/client","restartTargets":["grafana"]}],"surfaces":[{"name":"http","port":3000}],"switchover":"blue-green","uid":472,"writablePaths":[{"path":"/var/lib/grafana","size":"64Mi"}]}],"project":"identity","provenance":{"inputDigests":[{"digest":"sha256:ebb5134deeb55f320e28609054a0aca85a359f3869501a3a8b7560d7d05a4277","input":"intent-fragment","name":"delivery"},{"digest":"sha256:9a29fae42c0c6cd4f9824d7af3d9ecc68c068a3a00d1f9488bfbb70c882ea565","input":"intent-fragment","name":"edge"},{"digest":"sha256:c45963dcb3911475bff7d4fd17c0ae570230bdadba4ff2a68c75272d9219e762","input":"intent-fragment","name":"identity"},{"digest":"sha256:447e1500b09b608cecc0f93ebb598a60e655690822ecc63395e4e40eaadb98cb","input":"intent-fragment","name":"mail"},{"digest":"sha256:35d5e3643093422537ea72def0f4b19e93ed319a2d0192fc7846342970ce8797","input":"intent-fragment","name":"notes"},{"digest":"sha256:5916815b3b70a0de96137a6614f9356003985016bbf078bafe098433c9aab7d1","input":"intent-fragment","name":"observability"},{"digest":"sha256:31309996835674cc4f037f8e19897ddbe4a7fa8a487a3efc613e251caeed0853","input":"intent-fragment","name":"secrets"},{"digest":"sha256:f7e7e1e0a33d9528e11d956c36b5a6b8f37f758fb98aecdd74ea3eaf7286d60e","input":"platform-intent","name":"jorisjonkers.dev"},{"digest":"sha256:a989e80337efb294fa1a7175b3ad3eea8bed9bf13fbd503a26691c9265575fc2","input":"node-contract","name":"production"},{"digest":"sha256:f09f02097230414af40101e8ebe053e62d679c085a7d1fed9d860c8ba151b68b","input":"images-lock","name":"estate"},{"digest":"sha256:202e5e2a22ab20cfe5b6e85cbd8139cb13ed3b199b2f1af4380068d21d54ff1f","input":"cluster-state","name":"production"}],"renderHash":"sha256:c944fc023ec1bda7d6df2150acd36374ae0d7f8aae46e54baf8c2ec48f1e2f1a","schemaPackageIntegrity":"sha256:5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b5e6f7a8b"},"reconcileAfter":["apps-edge","apps-secrets"],"reconcileUnit":"apps-identity","releaseGate":{"analysis":{"errorRate":"1%","interval":"30s","iterations":4,"latency":"500ms","threshold":3},"deadline":"210s","endpoint":"http://release-gate.delivery-system.svc.cluster.local:8080","members":[{"process":"grafana","readiness":{"path":"/api/health","port":3000}}],"protectedBy":"grafana","protectedPath":"/api/user"},"revision":"sha256:24ea08efe8b9d3470694a2dc61f8a97ee2ba90a2908546efcb4d9ff028b636c4","secretStore":"http://vault.secrets-system.svc.cluster.local:8200"}
Loading
Loading