|
1 | 1 | # Configuring Keycloak as an OIDC provider |
2 | 2 |
|
3 | | -This document explains how to configure Keycloak as an OIDC provider (check general information and pre-requisites for [using an OAuth2/OIDC Provider with Kubeapps](../using-an-OIDC-provider.md)). |
| 3 | +This document explains how to configure Keycloak as an IDP + OIDC provider (check general information and pre-requisites for [using an OAuth2/OIDC Provider with Kubeapps](../using-an-OIDC-provider.md)). |
| 4 | +It covers the installation and documentation for Kubeapps interacting with two Kubernetes clusters. |
4 | 5 |
|
5 | | -In the case of Keycloak, you can find the parameters in the Keycloak admin console: |
| 6 | +The installation used the [bitnami chart for Keycloak](https://github.com/bitnami/charts/tree/master/bitnami/keycloak) (version 12.0.4/2.4.8) and [bitnami chart for Kubeapps](https://github.com/bitnami/charts/tree/master/bitnami/kubeapps) (version 7.0.0/2.3.2) |
6 | 7 |
|
7 | | -- **Client-ID**: Keycloak client ID. |
8 | | -- **Client-secret**: Secret associated to the client above. |
9 | | -- **OIDC Issuer URL**: `https://<keycloak.domain>/auth/realms/<realm>`. |
| 8 | +# Keycloak Installation |
| 9 | + |
| 10 | +## SSL |
| 11 | + |
| 12 | +In order to support OIDC or OAuth, most servers and proxies require HTTPS. By default, the certificate created by the helm chart / Keycloak server is both invalid (error with `notBefore` attribute) and also based on a deprecated certificate version making it incompatible to use (i.e. is it based on Common Name instead of SAN and is rejected). |
| 13 | + |
| 14 | +In this section, we will see how to configure Keycloak with its `auth.tls` enabled. Note that there are other options to configure TLS, for example via ingress TLS, either manually or using a cert manager. This section will focus on TLS at the server level. |
| 15 | + |
| 16 | +Keycloak is a java-based server and is using JDK keystore/truststore. Both a keystore and a truststore must be created or the helm installation will fail. The keystore and truststore must then be installed on the cluster as a k8s Secret. |
| 17 | + |
| 18 | +#### Step 1: create private key + certificate |
| 19 | + |
| 20 | +The certificate must be a SAN certificate and should be capable of using wildcard alternative names. Keytool which is typically used for creating certificates does not support SANs with wildcards and is also using jks by default. We will use openssl instead. |
| 21 | + |
| 22 | +To create the certificate, it is useful to create a openssl config file, something like the following (_certificate.config_): |
| 23 | +```properties |
| 24 | +[req] |
| 25 | +default_bits = 2048 |
| 26 | +distinguished_name = req_dn |
| 27 | +x509_extensions = req_ext |
| 28 | + |
| 29 | +[req_dn] |
| 30 | +countryName = Country Name (2 letter code) |
| 31 | +countryName_default = US |
| 32 | +stateOrProvinceName = State or Province Name (full name) |
| 33 | +stateOrProvinceName_default = CA |
| 34 | +localityName = Locality Name (eg, city) |
| 35 | +localityName_default = Campbell |
| 36 | +organizationName = Organization Name (eg, company) |
| 37 | +organizationName_default = vmware.com |
| 38 | +commonName = Common Name (e.g. server FQDN or YOUR name) |
| 39 | +commonName_default = keycloak |
| 40 | + |
| 41 | +[req_ext] |
| 42 | +subjectAltName = @alt_names |
| 43 | + |
| 44 | +[alt_names] |
| 45 | +DNS.1 = *.us-east-2.elb.amazonaws.com |
| 46 | +``` |
| 47 | + |
| 48 | +The important section of the config is the `[req_ext]` extension section with alternate names. As the load balancer URL is created during installation, we use the wildcard mechanism so the final hostname can be verified by the certificates (here `DNS.1` illustrates a cluster on AWS). Other hostnames, e.g. localhost, can also be added following the numbers pattern (`DNS.x`). |
| 49 | + |
| 50 | +The following command will generate the private key and certificate files: |
| 51 | + |
| 52 | +```shell |
| 53 | +openssl req \ |
| 54 | + -newkey rsa:2048 -nodes -keyout certificate.key \ |
| 55 | + -x509 -days 365 -config certificate.config -out certificate.crt |
| 56 | +``` |
| 57 | + |
| 58 | +#### Step 2: create the keystore and truststore |
| 59 | + |
| 60 | +Creating the keystore is done in two steps, starting by creating a pkcs12 keystore and then importing it using keytool (this will require a password, that we will need in the helm values.yaml): |
| 61 | + |
| 62 | +```shell |
| 63 | +openssl pkcs12 -export -in certificate.crt -inkey certificate.key -name keycloak -out certificate.p12 |
| 64 | + |
| 65 | +keytool -importkeystore -srckeystore certificate.p12 -srcstoretype pkcs12 -destkeystore keycloak-0.keystore.jks -alias keycloak |
| 66 | +``` |
| 67 | + |
| 68 | +Then we need to create the truststore: |
| 69 | +```shell |
| 70 | +keytool -import -alias keycloak -file certificate.crt -keystore keycloak.truststore.jks |
| 71 | +``` |
| 72 | + |
| 73 | +#### Step 3: create a k8s secret |
| 74 | + |
| 75 | +The last step is to create a secret in the namespace where Keycloak will be installed. |
| 76 | + |
| 77 | +Note that the names of the keystore and truststore matters and must be exactly as written here (make sure to rename them if the names have been changed in the commands in Step 2): |
| 78 | + |
| 79 | +```shell |
| 80 | +kubectl create secret generic keycloak-tls --from-file=./keycloak-0.keystore.jks --from-file=./keycloak.truststore.jks |
| 81 | +``` |
| 82 | + |
| 83 | +## Helm Install |
| 84 | + |
| 85 | +To provide a default install, not many values must be provided in the values file - the values are mostly default passwords and the name of the secret created in Step 3 above. |
| 86 | + |
| 87 | +Here is the `my-values.yaml` file that was applied when installing the Helm Chart: |
| 88 | + |
| 89 | +```yaml |
| 90 | +## Keycloak authentication parameters |
| 91 | +## ref: https://github.com/bitnami/bitnami-docker-keycloak#admin-credentials |
| 92 | +## |
| 93 | +auth: |
| 94 | + |
| 95 | + ## Create administrator user on boot. |
| 96 | + ## |
| 97 | + createAdminUser: true |
| 98 | + |
| 99 | + ## Keycloak administrator user and password |
| 100 | + ## |
| 101 | + adminUser: admin |
| 102 | + adminPassword: Vmware!23 |
| 103 | + |
| 104 | + ## Wildfly management user and password |
| 105 | + ## |
| 106 | + managementUser: manager |
| 107 | + managementPassword: Vmware!23 |
| 108 | + |
| 109 | + ## TLS encryption parameters |
| 110 | + ## ref: https://github.com/bitnami/bitnami-docker-keycloak#tls-encryption |
| 111 | + ## |
| 112 | + tls: |
| 113 | + enabled: true |
| 114 | + |
| 115 | + ## Name of the existing secret containing the truststore and one keystore per Keycloak replica |
| 116 | + ## |
| 117 | + jksSecret: keycloak-tls |
| 118 | + |
| 119 | + ## Password to access the keystore when it's password-protected. |
| 120 | + ## |
| 121 | + keystorePassword: Vmware!23 |
| 122 | + ## Password to access the truststore when it's password-protected. |
| 123 | + ## |
| 124 | + truststorePassword: Vmware!23 |
| 125 | +``` |
| 126 | +
|
| 127 | +Then just deploy Keycloak either using Kubeapps UI or helm cli as follows: |
| 128 | +```shell |
| 129 | +helm install keycloak bitnami/keycloak --values my-values.yaml |
| 130 | +``` |
| 131 | + |
| 132 | + |
| 133 | +# Keycloak Configuration |
| 134 | + |
| 135 | +Follow the [Keycloak documentation](https://www.keycloak.org/documentation) to create and configure a new Realm to work with. |
| 136 | + |
| 137 | +This section will focus on a few aspects to configure for the SSO scenario to work. |
| 138 | + |
| 139 | +## Groups Claim |
| 140 | + |
| 141 | +By default, there is no "groups" scope/claim. We will create a global client scope for groups. |
| 142 | + |
| 143 | +In the admin console: |
| 144 | + - Click “Client Scopes” from the left navigator menu |
| 145 | + - Click on “Create” from the table (top right corner) |
| 146 | + - Provide a name, ensure the protocol is set to “openid-connect” and that the option “Include in Token Scope” is on. |
| 147 | + |
| 148 | +Once the client scope is created, you should be redirected to a page with several tabs. Navigate to the “Mappers” tab as we need to create a mapper to populate the value of the associated claim: |
| 149 | + - Click on the “Mappers” tab |
| 150 | + - Click on “Create” from the table to create a new mapper |
| 151 | + - Configure: |
| 152 | + - Enter a name |
| 153 | + - Select “Group Membership” as the claim type |
| 154 | + - Enter “groups” as the token claim name |
| 155 | + - Ensure the “Full group path” is OFF |
| 156 | + - Keep the other knobs as ON |
| 157 | + - Click ‘Save’ |
| 158 | + |
| 159 | +Note: if you navigate to “Client Scopes” and then select the tab “Default Client Scopes” you should be able to see the newly created “groups” scope in the “available client scopes” lists. |
| 160 | + |
| 161 | +## Clients |
| 162 | + |
| 163 | +In probably a very simplified view, Clients represent the application to be protected and accessed via SSO and OIDC. Here, the environment consisted of the Kubeapps web app and two Kubernetes clusters. So we need to create three clients. |
| 164 | + |
| 165 | +### Cluster clients |
| 166 | + |
| 167 | +For each of the two Kubernetes clusters, we will create a client as follows: |
| 168 | + - Click “Clients” from the left navigator |
| 169 | + - Click “Create” from the table |
| 170 | + - Enter an “id” and Save (for example, `cluster1`and `cluster2` respectively) |
| 171 | + |
| 172 | +Once created, configure the authentication as follows: |
| 173 | + - Ensure the protocol is set to “openid-connect” |
| 174 | + - Configure the “Access Type” to be “confidential”. This will add a new “Credentials” tab from which you can get the client secret |
| 175 | + - If you just want to use tokens to access the cluster, you can disable the “Standard Flow Enabled”. Keep this option if you plan to use a local browser login screen (e.g. if using pinniped cli). |
| 176 | + - Ensure “Direct Access Grants Enabled” is enabled, as this is how we can get the tokens via API. |
| 177 | + - Save |
| 178 | + |
| 179 | +You then need to configure the client scopes that will be available: |
| 180 | + - Click the “Client Scopes” tab |
| 181 | + - Ensure the “email” scope is available either in the “Assigned Default Client Scopes” list or the “Assigned Optional Client Scopes” list |
| 182 | + - The “groups” client scope should be available in the lists on the left. Add it either to the “Assigned Default Client Scopes” list or the “Assigned Optional Client Scopes” list. |
| 183 | + |
| 184 | +### Kubeapps client |
| 185 | + |
| 186 | +We need to first create the client: |
| 187 | + - Click “Clients” from the left navigator |
| 188 | + - Click “Create” from the table |
| 189 | + - Enter an “id” and Save (e.g. `kubeapps`) |
| 190 | + |
| 191 | +Once created, configure the authentication as follows: |
| 192 | + - Ensure the protocol is set to “openid-connect” |
| 193 | + - Configure the “Access Type” to be “confidential”. This will add a new “Credentials” tab from which you can get the client secret |
| 194 | + - Ensure “Standard Flow Enabled” is enabled, this is required for the login screen. |
| 195 | + - “Direct Access Grants Enabled” can be disabled. |
| 196 | + - In the “Valid Redirect URIs” field, enter “http://localhost:8000/*” as a placeholder. We will need to revisit this field once we know the public hostname of kubeapps |
| 197 | + - Save |
| 198 | + |
| 199 | +As for the cluster clients, we need to configure the client scopes: |
| 200 | + - Click the “Client Scopes” tab |
| 201 | + - Ensure the “email” scope is available either in the “Assigned Default Client Scopes” list or the “Assigned Optional Client Scopes” list |
| 202 | + - The “groups” client scope should be available in the lists on the left. Add it either to the “Assigned Default Client Scopes” list or the “Assigned Optional Client Scopes” list. |
| 203 | + |
| 204 | +The last step is to configure the `kubeapps` client to be aware of the two cluster clients and be allowed to invoke them. There are different ways to configure Keycloak: |
| 205 | + - Using automatic audience resolution. We haven't explored this method yet, therefore it won't be covered in this guide. |
| 206 | + - Via Client Scopes: define the cluster clients as Client Scopes and add them to `kubeapps`. |
| 207 | + - Via Mappers in the client: define a mapper attached to the `kubeapps` client that will inject the client ids in th audience claim. |
| 208 | + |
| 209 | +#### Option #2 |
| 210 | + |
| 211 | +In this option, we create a client scope similar to how we created the groups client scope. This solution is better than solution #3 as the client ids are injected in the audience claim only if they were asked for in the scope request field. |
| 212 | + - Click “Client Scopes” from the left navigator menu |
| 213 | + - Click on “Create” from the table (top right corner) |
| 214 | + - Provide a name (e.g. `cluster1-client`), ensure the protocol is set to “openid-connect” and that the option “Include in Token Scope” is on. |
| 215 | + - Click the Mappers tab |
| 216 | + - Click Create from the table to create a new mapper |
| 217 | + - Enter a name |
| 218 | + - Select the mapper type “Audience” |
| 219 | + - In “Included Client Audience” select the cluster client created above (e.g. `cluster1`) |
| 220 | + - Ensure “Add to ID token” is enabled |
| 221 | + - Save |
| 222 | + - Repeat for the second cluster |
| 223 | + |
| 224 | +Then in the `kubeapps` client: |
| 225 | + - Navigate to the “Client Scope” tab |
| 226 | + - The two new scopes created above should be available in the lists on the left. You can choose to add them to either the default list or the optional list. |
| 227 | + |
| 228 | +#### Option #3 |
| 229 | + |
| 230 | +In this option, the claim is statically defined via a mapper similar to the one created in option #2. |
| 231 | + - Navigate to the Mappers tab of the `kubeapps` client |
| 232 | + - Click Create from the table |
| 233 | + - Enter a name |
| 234 | + - For Mapper Type select “Audience” |
| 235 | + - In “Included Client Audience” select the cluster client(e.g. `cluster1`) |
| 236 | + - Ensure “Add to ID token” is enabled |
| 237 | + - Save |
| 238 | + - Repeat for the second cluster |
| 239 | + |
| 240 | +The two client ids will be injected in the audience claim automatically. |
| 241 | + |
| 242 | +## Users |
| 243 | + |
| 244 | +Users are intuitive to create. But they must be configured with a “verified” email address. |
| 245 | + |
| 246 | +The oauth proxy used in kubeapps requires email as the username. Furthermore, if the email is not marked as verified, JWT validation will fail and authentication will fail. |
| 247 | + |
| 248 | +In order to test multiple users with different levels of authorization, it is useful to create them with multiple dummy email addresses. This can be done by ensuring that when the user is created, the field “email verified” is ON (skipping an actual email verification workflow). |
| 249 | + |
| 250 | +# Kubeapps Installation |
| 251 | + |
| 252 | +## Helm Install |
| 253 | + |
| 254 | +Few changes are required to values.yaml for the helm installation: |
| 255 | + - The `frontend` service type is set to LoadBalancer so we can have a public hostname for the callback. Using an ingress could be another alternative. |
| 256 | + - The auth proxy must be configured. Here we will be using the default one. |
| 257 | + - the `provider` field must be set to oidc |
| 258 | + - The `clientID` and `clientSecret` field values can be retrieved from the `kubeapps` client in Keycloak |
| 259 | + - the flag `--oidc-issuer-url` is the url to the Keycloak realm |
| 260 | + |
| 261 | +The following excerpt shows the relevant values used in values.yaml when installing the Helm Chart: |
| 262 | + |
| 263 | +```yaml |
| 264 | +## Frontend parameters |
| 265 | +## |
| 266 | +frontend: |
| 267 | + service: |
| 268 | + type: LoadBalancer |
| 269 | + |
| 270 | + |
| 271 | + |
| 272 | +# Auth Proxy configuration for OIDC support |
| 273 | +# ref: https://github.com/kubeapps/kubeapps/blob/master/docs/user/using-an-OIDC-provider.md |
| 274 | +authProxy: |
| 275 | + ## Set to true if Kubeapps should configure the OAuth login/logout URIs defined below. |
| 276 | + # |
| 277 | + enabled: true |
| 278 | + |
| 279 | + ## Skip the Kubeapps login page when using OIDC and directly redirect to the IdP |
| 280 | + ## |
| 281 | + skipKubeappsLoginPage: false |
| 282 | + |
| 283 | + ## Mandatory parameters for the internal auth-proxy. |
| 284 | + ## those are the client id and secret from the oidc provider |
| 285 | + provider: oidc |
| 286 | + clientID: kubeapps |
| 287 | + clientSecret: 5b824b57-dc17-4ac8-8043-947d5edcfb03 |
| 288 | + |
| 289 | + ## cookieSecret is used by oauth2-proxy to encrypt any credentials so that it requires |
| 290 | + ## no storage. Note that it must be a particular number of bytes. Recommend using the |
| 291 | + ## following to generate a cookieSecret as per the oauth2 configuration documentation |
| 292 | + ## at https://pusher.github.io/oauth2_proxy/configuration : |
| 293 | + ## python -c 'import os,base64; print base64.urlsafe_b64encode(os.urandom(16))' |
| 294 | + cookieSecret: Y29va2llLXNlY3JldC0xNg== |
| 295 | + ## Use "example.com" to restrict logins to emails from example.com |
| 296 | + emailDomain: "*" |
| 297 | + ## Additional flags for oauth2-proxy |
| 298 | + ## |
| 299 | + additionalFlags: |
| 300 | + - --ssl-insecure-skip-verify |
| 301 | + - --cookie-secure=false |
| 302 | + - --scope=openid email groups |
| 303 | + - --oidc-issuer-url=https://<xxx>.us-east-2.elb.amazonaws.com/auth/realms/AWS |
| 304 | +``` |
| 305 | +
|
| 306 | +## Configuration |
| 307 | +
|
| 308 | +Once Kubeapps is installed and the load balancer is ready, we need to go back to Keycloak to configure the callback URL: |
| 309 | + - Navigate to the `kubeapps` Client |
| 310 | + - In the “Valid Redirect URIs” enter the callback URL for Kubeapps. It will be of the form “http://`<hostname>`/oauth2/callback” (where `<hostname>` is the load balancer hostname) |
| 311 | + |
| 312 | +## Users |
| 313 | + |
| 314 | +Users created in Keycloak will be authenticated but they will not have access to the cluster resources by default. Make sure to create role bindings to users and/or groups in both clusters. |
0 commit comments