diff options
18 files changed, 144 insertions, 14 deletions
diff --git a/docs/configuration.md b/docs/configuration.md index 15335971..e3294cda 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -198,18 +198,19 @@ these are rarely used for various reasons. | config file | environment | example value | description | | ----------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `saml` | | `{idpSsoUrl: ..., idpCert: ..., issuer: ..., identifierFormat: ..., disableRequestedAuthnContext: ..., groupAttribute: ..., externalGroups: [], requiredGroups: [], attribute: {id: ..., username: ..., email: ...}}` | An object detailing your SAML provider. Refer to the [OneLogin](guides/auth/saml-onelogin.md) and [SAML](guides/auth/saml.md) guides for more details! | -| | `CMD_SAML_IDPSSOURL` | `https://idp.example.com/sso` | authentication endpoint of IdP. for details, see [guide](guides/auth/saml-onelogin.md). | -| | `CMD_SAML_IDPCERT` | `/path/to/cert.pem` | certificate file path of IdP in PEM format | -| | `CMD_SAML_ISSUER` | no example | Issuer to supply to identity provider (optional, default: `serverURL` config)" | -| | `CMD_SAML_DISABLEREQUESTEDAUTHNCONTEXT` | `true` or `false` | true to allow any authentication method, false restricts to password authentication (PasswordProtectedTransport) method (default: false) | -| | `CMD_SAML_IDENTIFIERFORMAT` | no example | name identifier format (optional, default: `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`) | -| | `CMD_SAML_GROUPATTRIBUTE` | `memberOf` | attribute name for group list (optional) | -| | `CMD_SAML_REQUIREDGROUPS` | `codimd-users` | group names that allowed (use vertical bar to separate) (optional) | -| | `CMD_SAML_EXTERNALGROUPS` | `Temporary-staff` | group names that not allowed (use vertical bar to separate) (optional) | -| | `CMD_SAML_ATTRIBUTE_ID` | `sAMAccountName` | attribute map for `id` (optional, default: NameID of SAML response) | -| | `CMD_SAML_ATTRIBUTE_USERNAME` | `mailNickname` | attribute map for `username` (optional, default: NameID of SAML response) | -| | `CMD_SAML_ATTRIBUTE_EMAIL` | `mail` | attribute map for `email` (optional, default: NameID of SAML response if `CMD_SAML_IDENTIFIERFORMAT` is default) | +| `saml` | | `{idpSsoUrl: ..., idpCert: ..., clientCert: ..., issuer: ..., identifierFormat: ..., disableRequestedAuthnContext: ..., groupAttribute: ..., externalGroups: [], requiredGroups: [], attribute: {id: ..., username: ..., email: ...}}` | An object detailing your SAML provider. Refer to the [OneLogin](guides/auth/saml-onelogin.md) and [SAML](guides/auth/saml.md) guides for more details! | +| | `CMD_SAML_IDPSSOURL` | `https://idp.example.com/sso` | authentication endpoint of IdP. for details, see [guide](guides/auth/saml-onelogin.md). | +| | `CMD_SAML_IDPCERT` | `/path/to/cert.pem` | certificate file path of IdP in PEM format | +| | `CMD_SAML_CLIENTCERT` | `/path/to/privatecert.pem` | certificate file path for the client in PEM format (optional) | +| | `CMD_SAML_ISSUER` | no example | Issuer to supply to identity provider (optional, default: `serverURL` config)" | +| | `CMD_SAML_DISABLEREQUESTEDAUTHNCONTEXT` | `true` or `false` | true to allow any authentication method, false restricts to password authentication (PasswordProtectedTransport) method (default: false) | +| | `CMD_SAML_IDENTIFIERFORMAT` | no example | name identifier format (optional, default: `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress`) | +| | `CMD_SAML_GROUPATTRIBUTE` | `memberOf` | attribute name for group list (optional) | +| | `CMD_SAML_REQUIREDGROUPS` | `codimd-users` | group names that allowed (use vertical bar to separate) (optional) | +| | `CMD_SAML_EXTERNALGROUPS` | `Temporary-staff` | group names that not allowed (use vertical bar to separate) (optional) | +| | `CMD_SAML_ATTRIBUTE_ID` | `sAMAccountName` | attribute map for `id` (optional, default: NameID of SAML response) | +| | `CMD_SAML_ATTRIBUTE_USERNAME` | `mailNickname` | attribute map for `username` (optional, default: NameID of SAML response) | +| | `CMD_SAML_ATTRIBUTE_EMAIL` | `mail` | attribute map for `email` (optional, default: NameID of SAML response if `CMD_SAML_IDENTIFIERFORMAT` is default) | ### Twitter Login diff --git a/docs/guides/auth/keycloak.md b/docs/guides/auth/keycloak.md index cf667774..16e24bc5 100644 --- a/docs/guides/auth/keycloak.md +++ b/docs/guides/auth/keycloak.md @@ -1,4 +1,4 @@ -# Keycloak/Red Hat SSO (self-hosted) +OAuth with Keycloak/Red Hat SSO (self-hosted) ## Prerequisites diff --git a/docs/guides/auth/saml-keycloak.md b/docs/guides/auth/saml-keycloak.md new file mode 100644 index 00000000..5caf3766 --- /dev/null +++ b/docs/guides/auth/saml-keycloak.md @@ -0,0 +1,113 @@ +# How to setup CodiMD SAML with Keycloak +## Configuring Keycloak +### Get the public certificate +1. Select the Realm you want to use for your CodiMD SAML +2. Select "Realm Settings" in left sidebar +3. Select the "Keys" tab +4. Click the button "Certificate" at `RS256` algorithm +![keycloak_idp_cert](../../images/auth/keycloak_idp_cert.png) +5. Copy this key and save it to the file specified in `saml.idpCert` property of the CodiMD configuration or `CMD_SAML_IDPCERT` environment variable + +### Create a new client +1. Select "Client" in left sidebar +![keycloak_clients_overview](../../images/auth/keycloak_clients_overview.png) +2. Click on the "Create" button +3. Set a Client ID and specify this in `saml.issuer` property of the CodiMD configuration or `CMD_SAML_ISSUER` environment variable +4. Select `SAML` as Client Protocol +5. Set Client SAML Endpoint to `https://codimd.example.com/auth/saml` (replace `https://codimd.example.com` with the base URL of your CodiMD installation) +![keycloak_add_client](../../images/auth/keycloak_add_client.png) +6. Leave "Client Signature Required" enabled +7. Set Root URL to `https://codimd.example.com` (replace it here also with the base URL of your CodiMD installation) +8. Set Valid Redirect URIs to `https://codimd.example.com/auth/saml/callback` (you should also define all other domains of your CodiMD installtion with the suffix `/auth/saml/callback`) +9. Set Base URL to `/` +![keycloak_client_overview](../../images/auth/keycloak_client_overview.png) +10. _(optional)_ You can set which Name ID Format should be used + +## Configure CodiMD +### Config file +You have to put the following block inside your `config.json`: +```json +"saml": { + "issuer": "codimd", // Change to the "Client ID" specified in the Keycloak Client + "identifierFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", + "idpSsoUrl": "https://keycloak.example.org/auth/realms/test/protocol/saml", // replace keycloak.example.org with the url of your keycloak server + "idpCert": "/path/to/the/cert.pem", + "clientCert": "/path/to/the/key.pem" // this one is optional, see below +} +``` + +### Environment Variables +- `CMD_SAML_IDPSSOURL`: `https://keycloak.example.org/auth/realms/test/protocol/saml` (replace keycloak.example.org with the url of your keycloak server) +- `CMD_SAML_IDPCERT`: `/path/to/the/cert.pem` +- *(optional, see below)* `CMD_SAML_CLIENTCERT`: `/path/to/the/key.pem` +- `CMD_SAML_ISSUER`: `codimd` (Change to the "Client ID" specified in the Keycloak Client) +- `CMD_SAML_IDENTIFIERFORMAT`: `urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified` + + +## Client certificate *(optional)* +If you want keycloak to be able to verify CodiMD, you hava to create a client certificate. There are two options for this: + +### Create Private Keys for Signing +1. Generate the private key and certificate with the following commands: +```shell +openssl genrsa -out priv.pem 2048 +openssl req -new -x509 -key priv.pem -out cert.pem +``` +*execute the following steps in keycloak* + +2. Select "Client" in left sidebar +3. Go to your CodiMD-Client +4. Select the "SAML Keys" tab +![keycloak_saml_import_cert](../../images/auth/keycloak_saml_import_cert.png) +5. Click on "Import" +6. Select `Certificate PEM` as "Archive Format" +7. Now upload the generated cert.pem (in this case named `cert.pem`) +![keycloak_saml_import_cert_details](../../images/auth/keycloak_saml_import_cert_details.png) +8. Click on "Import" +9. Move or copy this key (in this case named `key.pem`) and save it to the file specified in `saml.clientCert` property of the CodiMD configuration or in the enviroment-variable `CMD_SAML_CLIENTCERT` + + +### Convert Private Certificate generated by KeyCloak +Instead if generating you own certificate, you can also use the one generated by keycloak. + +1. Select "Client" in left sidebar +2. Go to your CodiMD-Client +3. Select the "SAML Keys" tab +![keycloak_saml_export_cert](../../images/auth/keycloak_saml_export_cert.png) + +5. Now click on "Export" +6. Here you can select the output format, choose `PKCS12`. You also have to set a password. Choose your own. +![keycloak_saml_export_cert_details](../../images/auth/keycloak_saml_export_cert_details.png) +6. Click on "Download" and save the file somewhere on you computer +7. You now have to extract the private Key. You can do this with the following command. WHen asked, enter your password. +```shell +openssl pkcs12 -in keystore.p12 -out key.pem -nocerts -nodes +``` +8. Move or copy this key (in this case named `key.pem`) and save it to the file specified in `saml.idpCert` property of the CodiMD configuration or in the enviroment-variable `CMD_SAML_CLIENTCERT` + +## Use Persistent Identifiers +Instead of using the username as the owner-key in the CodiMD database, you can also use a persistent identifier. This allows to change the username, without them loosing access to their notes. + +1. Go to the CodiMD-Client in keycloak. Now enable the option "Force Name ID Format" and select "persistent" as the "Name ID Format". +![keycloak_force_idformat](../../images/auth/keycloak_force_idformat.png) +2. For codimd to be able to use the username and email configured in keycloak, you have to create the following SAML protocol mappers: + 2.1. Create a mapper with the type `User Property`. Set the Name, Property and SAML Attribute Name to `username`. Now you can specify a friendly name (for example `Username`) +![keycloak_mapper_username](../../images/auth/keycloak_mapper_username.png) + 2.2 Create a mapper with the type `User Property`. Set the Name, Property and SAML Attribute Name to `email`. Now you can specify a friendly name (for example `E-Mail`) +![keycloak_mapper_email](../../images/auth/keycloak_mapper_email.png) + +The configured mappers should look like this: +![keycloak_mapper_overview](../../images/auth/keycloak_mapper_overview.png) + +3. You now have to add the following block to the saml-definition inside your `config.json`: +```json +"attribute": { + "username": "username" + "email": "email", +} +``` +It you configure CodiMD with enviroment variables, these are the ones you have to set: +```bash +CMD_SAML_ATTRIBUTE_USERNAME=username +CMD_SAML_ATTRIBUTE_EMAIL=email +``` diff --git a/docs/images/auth/keycloak_add_client.png b/docs/images/auth/keycloak_add_client.png Binary files differnew file mode 100644 index 00000000..79121b15 --- /dev/null +++ b/docs/images/auth/keycloak_add_client.png diff --git a/docs/images/auth/keycloak_client_overview.png b/docs/images/auth/keycloak_client_overview.png Binary files differnew file mode 100644 index 00000000..1ff9d986 --- /dev/null +++ b/docs/images/auth/keycloak_client_overview.png diff --git a/docs/images/auth/keycloak_clients_overview.png b/docs/images/auth/keycloak_clients_overview.png Binary files differnew file mode 100644 index 00000000..388b3e00 --- /dev/null +++ b/docs/images/auth/keycloak_clients_overview.png diff --git a/docs/images/auth/keycloak_force_idformat.png b/docs/images/auth/keycloak_force_idformat.png Binary files differnew file mode 100644 index 00000000..1b1bf302 --- /dev/null +++ b/docs/images/auth/keycloak_force_idformat.png diff --git a/docs/images/auth/keycloak_idp_cert.png b/docs/images/auth/keycloak_idp_cert.png Binary files differnew file mode 100644 index 00000000..1b899283 --- /dev/null +++ b/docs/images/auth/keycloak_idp_cert.png diff --git a/docs/images/auth/keycloak_mapper_email.png b/docs/images/auth/keycloak_mapper_email.png Binary files differnew file mode 100644 index 00000000..b0ad667a --- /dev/null +++ b/docs/images/auth/keycloak_mapper_email.png diff --git a/docs/images/auth/keycloak_mapper_overview.png b/docs/images/auth/keycloak_mapper_overview.png Binary files differnew file mode 100644 index 00000000..8402a0bc --- /dev/null +++ b/docs/images/auth/keycloak_mapper_overview.png diff --git a/docs/images/auth/keycloak_mapper_username.png b/docs/images/auth/keycloak_mapper_username.png Binary files differnew file mode 100644 index 00000000..ccaa8954 --- /dev/null +++ b/docs/images/auth/keycloak_mapper_username.png diff --git a/docs/images/auth/keycloak_saml_export_cert.png b/docs/images/auth/keycloak_saml_export_cert.png Binary files differnew file mode 100644 index 00000000..e9fa5722 --- /dev/null +++ b/docs/images/auth/keycloak_saml_export_cert.png diff --git a/docs/images/auth/keycloak_saml_export_cert_details.png b/docs/images/auth/keycloak_saml_export_cert_details.png Binary files differnew file mode 100644 index 00000000..7f4c9e0c --- /dev/null +++ b/docs/images/auth/keycloak_saml_export_cert_details.png diff --git a/docs/images/auth/keycloak_saml_import_cert.png b/docs/images/auth/keycloak_saml_import_cert.png Binary files differnew file mode 100644 index 00000000..9295edab --- /dev/null +++ b/docs/images/auth/keycloak_saml_import_cert.png diff --git a/docs/images/auth/keycloak_saml_import_cert_details.png b/docs/images/auth/keycloak_saml_import_cert_details.png Binary files differnew file mode 100644 index 00000000..bb9d1d6c --- /dev/null +++ b/docs/images/auth/keycloak_saml_import_cert_details.png diff --git a/lib/config/default.js b/lib/config/default.js index 9b852d1e..9284882a 100644 --- a/lib/config/default.js +++ b/lib/config/default.js @@ -143,6 +143,7 @@ module.exports = { saml: { idpSsoUrl: undefined, idpCert: undefined, + clientCert: undefined, issuer: undefined, identifierFormat: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', disableRequestedAuthnContext: false, diff --git a/lib/config/environment.js b/lib/config/environment.js index 87a7e3ee..2d76286f 100644 --- a/lib/config/environment.js +++ b/lib/config/environment.js @@ -120,6 +120,7 @@ module.exports = { saml: { idpSsoUrl: process.env.CMD_SAML_IDPSSOURL, idpCert: process.env.CMD_SAML_IDPCERT, + clientCert: process.env.CMD_SAML_CLIENTCERT, issuer: process.env.CMD_SAML_ISSUER, identifierFormat: process.env.CMD_SAML_IDENTIFIERFORMAT, disableRequestedAuthnContext: toBooleanConfig(process.env.CMD_SAML_DISABLEREQUESTEDAUTHNCONTEXT), diff --git a/lib/web/auth/saml/index.js b/lib/web/auth/saml/index.js index 40a6f8b3..14f3966d 100644 --- a/lib/web/auth/saml/index.js +++ b/lib/web/auth/saml/index.js @@ -16,7 +16,21 @@ passport.use(new SamlStrategy({ callbackUrl: config.serverURL + '/auth/saml/callback', entryPoint: config.saml.idpSsoUrl, issuer: config.saml.issuer || config.serverURL, - cert: fs.readFileSync(config.saml.idpCert, 'utf-8'), + privateCert: config.saml.clientCert === undefined ? undefined : (function () { + try { + return fs.readFileSync(config.saml.clientCert, 'utf-8') + } catch (e) { + logger.error(`SAML client certificate: ${e.message}`) + } + }()), + cert: (function () { + try { + return fs.readFileSync(config.saml.idpCert, 'utf-8') + } catch (e) { + logger.error(`SAML idp certificate: ${e.message}`) + process.exit(1) + } + }()), identifierFormat: config.saml.identifierFormat, disableRequestedAuthnContext: config.saml.disableRequestedAuthnContext }, function (user, done) { |