Skip to content

Commit

Permalink
Merge pull request #350 from n0emis/saml-client-cert
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmehren committed Jul 11, 2020
2 parents 3db8b0d + a134aa3 commit 2a52805
Show file tree
Hide file tree
Showing 18 changed files with 144 additions and 14 deletions.
25 changes: 13 additions & 12 deletions docs/configuration.md
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion docs/guides/auth/keycloak.md
@@ -1,4 +1,4 @@
# Keycloak/Red Hat SSO (self-hosted)
OAuth with Keycloak/Red Hat SSO (self-hosted)

## Prerequisites

Expand Down
113 changes: 113 additions & 0 deletions 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
```
Binary file added docs/images/auth/keycloak_add_client.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/auth/keycloak_client_overview.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/auth/keycloak_clients_overview.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/auth/keycloak_force_idformat.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/auth/keycloak_idp_cert.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/auth/keycloak_mapper_email.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/auth/keycloak_mapper_overview.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/auth/keycloak_mapper_username.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/auth/keycloak_saml_export_cert.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/auth/keycloak_saml_import_cert.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions lib/config/default.js
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions lib/config/environment.js
Expand Up @@ -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),
Expand Down
16 changes: 15 additions & 1 deletion lib/web/auth/saml/index.js
Expand Up @@ -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) {
Expand Down

0 comments on commit 2a52805

Please sign in to comment.