From 3deedfe863e9f943d27e334725aadae5dc8a115a Mon Sep 17 00:00:00 2001 From: Florian Forster Date: Tue, 1 Dec 2020 16:35:58 +0100 Subject: [PATCH] chore: docker site gen for docs, update npm base image, fix chrome in docs page, jwt profile (#1019) * initial version with docker * move folder * use correct path * remove typo scanner * change in site * move dockerignore * use proper path * docs: chrome moving header, max width table, overflow on mobile (#1012) * fix: table renderer, chrome moving header, mobile table * card elevation * chore(deps): bump node from 12 to 15 in /build (#967) Bumps node from 12 to 15. Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * docs(oauth2): jwt profile (#954) * first draft of JWT profile * additional infos * WIP Claim matrix * restructure docs * extend matrix * typo * use correct translation * order tables a to z * claim description * remark * describe username Co-authored-by: Florian Forster Co-authored-by: Max Peintner Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/dependabot.yml | 8 + .github/workflows/docs.yml | 14 +- .github/workflows/spellcheck.yml | 19 -- .typo-ci.yml | 42 ---- .dockerignore => build/.dockerignore | 0 build/dockerfile | 2 +- site/README.md | 16 +- site/docker-compose.yml | 10 + site/dockerfile | 13 ++ site/docs/documentation/03-openidoauth.en.md | 194 ++++++++++++++++--- site/package.json | 4 +- site/src/components/Docs.svelte | 23 ++- site/src/components/GuideContents.svelte | 2 +- site/src/components/LanguageSwitcher.svelte | 1 - site/src/messages/de.json | 4 +- site/src/routes/index.svelte | 5 + site/src/utils/generate_docs.js | 13 ++ site/static/base.css | 28 ++- 18 files changed, 293 insertions(+), 105 deletions(-) delete mode 100644 .github/workflows/spellcheck.yml delete mode 100644 .typo-ci.yml rename .dockerignore => build/.dockerignore (100%) create mode 100644 site/docker-compose.yml create mode 100644 site/dockerfile diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 35500edd3b..e398907d3b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -32,3 +32,11 @@ updates: commit-message: prefix: chore include: scope +- package-ecosystem: "docker" + directory: "/site/" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + commit-message: + prefix: chore + include: scope \ No newline at end of file diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ad85adab0d..004a2c1e1b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,15 +17,19 @@ jobs: steps: - name: Checkout Repo uses: actions/checkout@v2 - - name: Install and Build - run: | - npm install - npx sapper export --legacy + - uses: docker/build-push-action@v2 + with: + context: . + file: ./site/dockerfile + platforms: linux/amd64 + tags: zitadel:docs + push: false + outputs: type=local,dest=output - name: Archive Production Artifact uses: actions/upload-artifact@master with: name: export - path: site/__sapper__/export + path: output deploydocs: name: Deploy needs: builddocs diff --git a/.github/workflows/spellcheck.yml b/.github/workflows/spellcheck.yml deleted file mode 100644 index a2590ac451..0000000000 --- a/.github/workflows/spellcheck.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Spellcheck - -on: - push: - branches: [master] - pull_request: - branches: [master] - -jobs: - spellcheck: - name: Typo CI (GitHub Action) - runs-on: ubuntu-latest - timeout-minutes: 4 - if: "!contains(github.event.head_commit.message, '[ci skip]')" - steps: - - name: TypoCheck - uses: typoci/spellcheck-action@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.typo-ci.yml b/.typo-ci.yml deleted file mode 100644 index 0f9c20faf8..0000000000 --- a/.typo-ci.yml +++ /dev/null @@ -1,42 +0,0 @@ -# What language dictionaries should it use? Currently Typo CI supports: -# de -# en -# en_GB -# es -# fr -# it -# pt -# pt_BR -dictionaries: - - en - - en_GB - - de - -# Any files/folders we should ignore? -excluded_files: - - ".codecov/*" - - ".github/*" - - "build/*" - - "k8s/*" - - "*.min.css" - - "*.css.map" - - "*.min.js" - - "*.js.map" - - "package-lock.json" - - "package.json" - - ".releaserc.js" - - ".typo-ci.yml" - - ".gitignore" - - "go.mod" - - "go.sum" - -# Any typos we should ignore? -excluded_words: - - typoci - - idps - - ZITADEL's - - otel - - otlp - -# Would you like filenames to also be spellchecked? -spellcheck_filenames: false \ No newline at end of file diff --git a/.dockerignore b/build/.dockerignore similarity index 100% rename from .dockerignore rename to build/.dockerignore diff --git a/build/dockerfile b/build/dockerfile index ca583d221a..5128278580 100644 --- a/build/dockerfile +++ b/build/dockerfile @@ -34,7 +34,7 @@ COPY internal/protoc/protoc-gen-authoption/authoption/options.proto authoption/o ## With this step we prepare all node_modules, this helps caching the build ## Speed up this step by mounting your local node_modules directory ####################### -FROM node:12 as npm-base +FROM node:15 as npm-base WORKDIR console COPY console/package.json console/package-lock.json ./ RUN npm install \ diff --git a/site/README.md b/site/README.md index 39a375934b..8516a6a1f7 100644 --- a/site/README.md +++ b/site/README.md @@ -5,14 +5,20 @@ The documentation is built according to the structure of a docs `folder`[Folder] ## Running locally -Set up the project: +You can simply run the static site by using the docker-compose command below. -```bash -npm i +```Bash +COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose -f site/docker-compose.yml up --build ``` -Start the server with `npm run dev`, and navigate to [localhost:3000](http://localhost:3000). +## Building locally + +You can simply run the static site by using the docker-compose command below. + +```Bash +DOCKER_BUILDKIT=1 docker build -f site/dockerfile . -t zitadel:docs -o docs +``` ## Honorable Mentions -This project was created with the help of some components from [svelte](https://github.com/sveltejs/svelte)([MIT](https://github.com/sveltejs/svelte/blob/master/LICENSE)) as well as [site-kit](https://github.com/sveltejs/site-kit)([MIT](https://github.com/sveltejs/site-kit/blob/master/LICENSE)). \ No newline at end of file +This project was created with the help of some components from [svelte](https://github.com/sveltejs/svelte)([MIT](https://github.com/sveltejs/svelte/blob/master/LICENSE)) as well as [site-kit](https://github.com/sveltejs/site-kit)([MIT](https://github.com/sveltejs/site-kit/blob/master/LICENSE)). diff --git a/site/docker-compose.yml b/site/docker-compose.yml new file mode 100644 index 0000000000..3da8edfc92 --- /dev/null +++ b/site/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3.8" + +services: + docs: + build: + context: .. + dockerfile: site/dockerfile + command: sh -c "npm run dev" + ports: + - 3000:3000 \ No newline at end of file diff --git a/site/dockerfile b/site/dockerfile new file mode 100644 index 0000000000..ff55563bee --- /dev/null +++ b/site/dockerfile @@ -0,0 +1,13 @@ +FROM node:15 as builder + +COPY site/ /site/ + +WORKDIR /site + +RUN npm install + +RUN npx sapper export --legacy + +FROM scratch as final + +COPY --from=builder /site/__sapper__/export . \ No newline at end of file diff --git a/site/docs/documentation/03-openidoauth.en.md b/site/docs/documentation/03-openidoauth.en.md index 4cd14dff10..c001fbe567 100644 --- a/site/docs/documentation/03-openidoauth.en.md +++ b/site/docs/documentation/03-openidoauth.en.md @@ -8,12 +8,12 @@ This chapter documents the [OpenID Connect 1.0](https://openid.net/connect/) and Under normal circumstances **ZITADEL** need four domain names to operate properly. -| Domain Name | Example | Description | -|:------------|:--------------------|--------------------------------------------------------------------------------------------------------------------------------------| -| issuer | issuer.zitadel.ch | Provides the [OpenID Connect 1.0 Discovery Endpoint](#openid-connect-10-discovery) | -| api | api.zitadel.ch | All ZITADEL API's are located under this domain see [API explanation](develop#APIs) for details | -| login | accounts.zitadel.ch | The accounts.* page provides server renderer pages like login and register and as well the authorization_endpoint for OpenID Connect | -| console | console.zitadel.ch | With the console.* domain we serve the assets for the management gui | +| Domain Name | Example | Description | +|:------------|:----------------------|--------------------------------------------------------------------------------------------------------------------------------------| +| issuer | `issuer.zitadel.ch` | Provides the [OpenID Connect 1.0 Discovery Endpoint](#openid-connect-10-discovery) | +| api | `api.zitadel.ch` | All ZITADEL API's are located under this domain see [API explanation](develop#APIs) for details | +| login | `accounts.zitadel.ch` | The accounts.* page provides server renderer pages like login and register and as well the authorization_endpoint for OpenID Connect | +| console | `console.zitadel.ch` | With the console.* domain we serve the assets for the management gui | #### OpenID Connect 1.0 Discovery @@ -50,36 +50,106 @@ For example with [zitadel.ch](zitadel.ch) this would be the domain [issuer.zitad #### OAuth 2.0 Metadata -**ZITADEL** does not provide a OAuth 2.0 Metadata endpoint but instead provides a [OpenID Connect Discovery Endpoint](#openid-connect-10-discovery). +**ZITADEL** does not yet provide a OAuth 2.0 Metadata endpoint but instead provides a [OpenID Connect Discovery Endpoint](#openid-connect-10-discovery). ### Scopes -#### How scopes work +ZITADEL supports the usage of scopes as way of requesting information from the IAM and also instruct ZITADEL to do certain operations. -> TODO describe +#### Standard Scopes + +| Scopes | Example | Description | +|:--------|:----------|------------------------------------------------------| +| openid | `openid` | When using openid connect this is a mandatory scope | +| profile | `profile` | Optional scope to request the profile of the subject | +| email | `email` | Optional scope to request the email of the subject | +| address | `address` | Optional scope to request the address of the subject | + +#### Custom Scopes + +> This feature is not yet released #### Reserved Scopes In addition to the standard compliant scopes we utilize the following scopes. -| Scope | Description | Example | -|:------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------| -| urn:zitadel:iam:org:project:role:{rolename} | By using this scope a [client](administrate#clients) can request the claim urn:zitadel:iam:roles:rolename} to be asserted when possible. As an alternative approach you can enable all [roles](administrate#Roles) to be asserted from the [project](administrate#projects) a [client](administrate#clients) belongs to. See details [here](administrate#RBAC_Settings) | urn:zitadel:iam:org:project:role:user | -| urn:zitadel:iam:org:domain:primary:{domainname} | When requesting this scope **ZITADEL** will enforce that the user is a member of the selected organisation. If the organisation does not exist a failure is displayed | urn:zitadel:iam:org:domain:primary:acme.ch | -| urn:zitadel:iam:role:{rolename} | | | -| urn:zitadel:iam:org:project:id:{projectid}:aud | By adding this scope, the requested projectid will be added to the audience of the access and id token | ZITADEL Project: urn:zitadel:iam:org:project:id:69234237810729019:aud | +| Scopes | Example | Description | +|:------------------------------------------------|:-------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| urn:zitadel:iam:org:project:role:{rolename} | `urn:zitadel:iam:org:project:role:user` | By using this scope a [client](administrate#clients) can request the claim urn:zitadel:iam:roles:rolename} to be asserted when possible. As an alternative approach you can enable all [roles](administrate#Roles) to be asserted from the [project](administrate#projects) a [client](administrate#clients) belongs to. See details [here](administrate#RBAC_Settings) | +| urn:zitadel:iam:org:domain:primary:{domainname} | `urn:zitadel:iam:org:domain:primary:acme.ch` | When requesting this scope **ZITADEL** will enforce that the user is a member of the selected organization. If the organization does not exist a failure is displayed | +| urn:zitadel:iam:role:{rolename} | | | +| urn:zitadel:iam:org:project:id:{projectid}:aud | ZITADEL's Project id is `urn:zitadel:iam:org:project:id:69234237810729019:aud` | By adding this scope, the requested projectid will be added to the audience of the access and id token | + +> If access to ZITADEL's API's is needed with a service user the scope `urn:zitadel:iam:org:project:id:69234237810729019:aud` needs to be used with the JWT Profile request ### Claims -> TODO describe +ZITADEL asserts claims on different places according to the corresponding specifications or project and clients settings. +Please check below the matrix for an overview where which scope is asserted. + +| Claims | Userinfo | ID Token | Access Token | +|:------------------------------------------------|:-------------------|----------------------------------------|------------------------------------------| +| acr | Yes | Yes | No | +| address | Yes when requested | Yes only when response type `id_token` | No | +| amr | Yes | Yes | No | +| aud | No | Yes | Yes when JWT | +| auth_time | Yes | Yes | No | +| azp | No | Yes | Yes when JWT | +| email | Yes when requested | Yes only when response type `id_token` | No | +| email_verified | Yes when requested | Yes only when response type `id_token` | No | +| exp | No | Yes | Yes when JWT | +| family_name | Yes when requested | Yes when requested | No | +| gender | Yes when requested | Yes when requested | No | +| given_name | Yes when requested | Yes when requested | No | +| iat | No | Yes | Yes when JWT | +| iss | No | Yes | Yes when JWT | +| locale | Yes when requested | Yes when requested | No | +| name | Yes when requested | Yes when requested | No | +| nonce | No | Yes | No | +| phone | Yes when requested | Yes only when response type `id_token` | No | +| preferred_username | Yes when requested | Yes | No | +| sub | Yes | Yes | Yes when JWT | +| urn:zitadel:iam:org:domain:primary:{domainname} | Yes when requested | Yes when requested | Yes when JWT and requested | +| urn:zitadel:iam:org:project:roles:{rolename} | Yes when requested | Yes when requested or configured | Yes when JWT and requested or configured | + +#### Standard Claims + +| Claims | Example | Description | +|:-------------------|:-----------------------------------------|-----------------------------------------------------------------------------------------------| +| acr | TBA | TBA | +| address | `Teufener Strasse 19, 9000 St. Gallen` | TBA | +| amr | `pwd mfa` | Authentication Method References as defined in [RFC8176](https://tools.ietf.org/html/rfc8176) | +| aud | `69234237810729019` | By default all client id's and the project id is included | +| auth_time | `1311280969` | Unix time of the authentication | +| azp | `69234237810729234` | Client id of the client who requested the token | +| email | `road.runner@acme.ch` | Email Address of the subject | +| email_verified | `true` | Boolean if the email was verified by ZITADEL | +| exp | `1311281970` | Time the token expires as unix time | +| family_name | `Runner` | The subjects family name | +| gender | `other` | Gender of the subject | +| given_name | `Road` | Given name of the subject | +| iat | `1311280970` | Issued at time of the token as unix time | +| iss | `https://issuer.zitadel.ch` | Issuing domain of a token | +| locale | `en` | Language from the subject | +| name | `Road Runner` | The subjects full name | +| nonce | `blQtVEJHNTF0WHhFQmhqZ0RqeHJsdzdkd2d...` | The nonce provided by the client | +| phone | `+41 79 XXX XX XX` | Phone number provided by the user | +| preferred_username | `road.runner@acme.caos.ch` | ZITADEL's login name of the user. Consist of `username@primarydomain` | +| sub | `77776025198584418` | Subject ID of the user | + +#### Custom Claims + +> This feature is not yet released #### Reserved Claims -| Claims | Description | Example | -|:------------------------------------------------|:------------|----------------------------------------------------------------------------------| -| urn:zitadel:iam:org:domain:primary:{domainname} | | `{"urn:zitadel:iam:org:domain:primary": "acme.ch"}` | -| urn:zitadel:iam:org:project:roles:{rolename} | | `{"urn:zitadel:iam:org:project:roles": [ {"user": {"id1": "acme.zitade.ch", "id2": "caos.ch"} } ] }` | -| urn:zitadel:iam:roles:{rolename} | | | +ZITADEL reserves some claims to assert certain data. + +| Claims | Example | Description | +|:------------------------------------------------|:-----------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| urn:zitadel:iam:org:domain:primary:{domainname} | `{"urn:zitadel:iam:org:domain:primary": "acme.ch"}` | This claim represents the primary domain of the organization the user belongs to. | +| urn:zitadel:iam:org:project:roles:{rolename} | `{"urn:zitadel:iam:org:project:roles": [ {"user": {"id1": "acme.zitade.ch", "id2": "caos.ch"} } ] }` | When roles are asserted, ZITADEL does this by providing the `id` and `primaryDomain` below the role. This gives you the option to check in which organization a user has the role. | +| urn:zitadel:iam:roles:{rolename} | TBA | TBA | ### Grant Types @@ -89,12 +159,12 @@ For a list of supported or unsupported `Grant Types` please have a look at the t |:------------------------------------------------------|:--------------------| | Authorization Code | yes | | Authorization Code with PKCE | yes | -| Implicit | yes | -| Resource Owner Password Credentials | no | | Client Credentials | yes | | Device Authorization | under consideration | -| Refresh Token | work in progress | +| Implicit | yes | | JSON Web Token (JWT) Profile | partially | +| Refresh Token | work in progress | +| Resource Owner Password Credentials | no | | Security Assertion Markup Language (SAML) 2.0 Profile | no | | Token Exchange | work in progress | @@ -122,6 +192,82 @@ For a list of supported or unsupported `Grant Types` please have a look at the t **Link to spec.** [JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants](https://tools.ietf.org/html/rfc7523) +##### Using JWTs as Authorization Grants + +Our service user work with the JWT profile to authenticate them against ZITADEL. + +1. Create or use an existing service user +2. Create a new key and download it +3. Generate a JWT with the structure below and sing it with the downloaded key +4. Send the JWT Base64 encoded to ZITADEL's token endpoint +5. Use the received access token + +--- + +Key JSON + +| Key | Example | Description | +|:-------|:--------------------------------------------------------------------|:-------------------------------------------------------------------| +| type | `"serviceaccount"` | The type of account, right now only serviceaccount is valid | +| keyId | `"81693565968772648"` | This is unique ID of the key | +| key | `"-----BEGIN RSA PRIVATE KEY-----...-----END RSA PRIVATE KEY-----"` | The private key generated by ZITADEL, this can not be regenerated! | +| userId | `78366401571647008` | The service users ID, this is the same as the subject from tokens | + +```JSON +{ + "type": "serviceaccount", + "keyId": "81693565968772648", + "key": "-----BEGIN RSA PRIVATE KEY-----...-----END RSA PRIVATE KEY-----", + "userId": "78366401571647008" +} +``` + +--- + +JWT + +| Claim | Example | Description | +|:------|:------------------------------|:---------------------------------------------------------------------------------| +| aud | `"https://issuer.zitadel.ch"` | String or Array of intended audiences MUST include ZITADEL's issuing domain | +| exp | `1605183582` | Unix timestamp of the expiry, MUST NOT be longer than 1h | +| iat | `1605179982` | Unix timestamp of the creation singing time of the JWT | +| iss | `"http://localhost:50003"` | String which represents the requesting party | +| sub | `"77479219772321307"` | The subject ID of the service user, normally the `userId` from the json key file | + +```JSON +{ + "iss": "http://localhost:50003", + "sub": "77479219772321307", + "aud": "https://issuer.zitadel.ch", + "exp": 1605183582, + "iat": 1605179982 +} +``` + +--- + +Access Token Request + +| Parameter | Example | Description | +|:-------------|:----------------------------------------------------------------------------|:----------------------------------------------| +| Content-Type | `application/x-www-form-urlencoded` | | +| grant_type | `urn:ietf:params:oauth:grant-type:jwt-bearer` | Using JWTs as Authorization Grants | +| assertion | `eyJhbGciOiJSUzI1Ni...` | The base64 encoded JWT created above | +| scope | `openid profile email urn:zitadel:iam:org:project:id:69234237810729019:aud` | Scopes you would like to request from ZITADEL | + +```BASH +curl --request POST \ + --url https://api.zitadel.ch/oauth/v2/token \ + --header 'Content-Type: application/x-www-form-urlencoded' \ + --data grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \ + --data assertion=eyJhbGciOiJSUzI1Ni... + --data scope=openid profile email address +``` + +##### Using JWTs for Client Authentication + +> Not yet supported + #### Token Exchange **Link to spec.** [OAuth 2.0 Token Exchange](https://tools.ietf.org/html/rfc8693) diff --git a/site/package.json b/site/package.json index 39e4dc0efa..62887a9457 100644 --- a/site/package.json +++ b/site/package.json @@ -9,8 +9,8 @@ "start": "node __sapper__/build", "cy:run": "cypress run", "cy:open": "cypress open", - "test": "run-p --race dev cy:run", - "imageoptim": "imageoptim --imagealpha 'static/img/*.png'" + "test": "run-p --race dev cy:run", + "imageoptim": "imageoptim --imagealpha 'static/img/*.png'" }, "dependencies": { "@polka/send": "^0.4.0", diff --git a/site/src/components/Docs.svelte b/site/src/components/Docs.svelte index d903ddc74c..e1b6bf5fb6 100644 --- a/site/src/components/Docs.svelte +++ b/site/src/components/Docs.svelte @@ -84,6 +84,21 @@