From c77f44a6882c7222c98e7c52bb1d19f01b77f94b Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Wed, 20 Feb 2019 18:18:29 +0100 Subject: [PATCH 1/4] Document how to use OIDC authentication --- .../templates/kubeapps-frontend-config.yaml | 3 + docs/img/auth-proxy-login.png | Bin 0 -> 43965 bytes docs/user/login-alternatives.md | 36 ++++ docs/user/using-an-OIDC-provider.md | 162 ++++++++++++++++++ 4 files changed, 201 insertions(+) create mode 100644 docs/img/auth-proxy-login.png create mode 100644 docs/user/login-alternatives.md create mode 100644 docs/user/using-an-OIDC-provider.md diff --git a/chart/kubeapps/templates/kubeapps-frontend-config.yaml b/chart/kubeapps/templates/kubeapps-frontend-config.yaml index 415359d859a..5fac867e365 100644 --- a/chart/kubeapps/templates/kubeapps-frontend-config.yaml +++ b/chart/kubeapps/templates/kubeapps-frontend-config.yaml @@ -64,6 +64,9 @@ data: } location / { + # Add the Authorization header if exists + add_header Authorization $http_authorization; + proxy_pass http://{{ template "kubeapps.dashboard.fullname" . }}:{{ .Values.dashboard.service.port }}; } } diff --git a/docs/img/auth-proxy-login.png b/docs/img/auth-proxy-login.png new file mode 100644 index 0000000000000000000000000000000000000000..41e2780e7820426af269777f5e95f23fb4bb44f4 GIT binary patch literal 43965 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 IQn3pCA1-q?P5=M^ literal 0 HcmV?d00001 diff --git a/docs/user/login-alternatives.md b/docs/user/login-alternatives.md new file mode 100644 index 00000000000..23f5073c47e --- /dev/null +++ b/docs/user/login-alternatives.md @@ -0,0 +1,36 @@ +# Kubeapps Login Form + +By default, when first visiting Kubeapps, a login form is shown for the user to introduce a personal token for a specific service account: + +![Dashboard Login](../img/dashboard-login.png) + +You can find more information about access control in Kubeapps in this [document](./access-control.md). + +However, it's possible to disable this form or delegate the authentication to an OIDC provider so Kubeapps users don't need to introduce a token in the login form. + +## Bypassing authentication + +Kubeapps expects an `Authorization` header that will be used to validate operations agains the Kubernetes API. This is usually set with the login form but if Kubeapps is exposed with an Ingress object, it's possible to hardcode a valid token in the Ingress configuration and automatically include it in all the requests. + +**NOTE**: This is not suitable for production since anyone with access to Kubeapps would be granted with the permissions associated with the hardcoded token. + +This is an example of the values that you can configure in the Kubeapps chart in order to set a valid token: + +```yaml +ingress: + enabled: true + hosts: + - name: kubeapps.local + path: / + tls: false + annotations: + nginx.ingress.kubernetes.io/proxy-read-timeout: "600" + nginx.ingress.kubernetes.io/configuration-snippet: | + add_header Authorization "Bearer TOKEN"; +``` + +You just need to substitute TOKEN with the actual value of the token. The above assumes an Nginx [Ingress Controller](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-controllers), in case a different controller is being used that annotation will need to be adapted. + +# Using an OIDC provider + +In case you want to use OAuth 2.0 to authenticate Kubeapps users follow this [guide](./using-an-OIDC-provider.md). diff --git a/docs/user/using-an-OIDC-provider.md b/docs/user/using-an-OIDC-provider.md new file mode 100644 index 00000000000..b904cd83462 --- /dev/null +++ b/docs/user/using-an-OIDC-provider.md @@ -0,0 +1,162 @@ +# Using an OIDC Provider with Kubeapps + +OpenID Connect (OIDC) is a simple identity layer on top of the OAuth 2.0 protocol, which allows computing clients to verify the identity of an end-user based on the authentication performed by an authorization server, as well as to obtain basic profile information about the end-user. + +It is possible to configure your Kubernetes cluster to use an OIDC provider in order to manage accounts, groups and roles with a single application. This guide will explain how you can use an existing OIDC provider to authenticate users within Kubeapps. + +## Pre-requisites + +For this guide we assume that you have a Kubernetes cluster that is properly configured to use an Identity Provider to handle the authorization of your cluster. You can find more information about how Kubernetes uses OIDC tokens [here](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens). This means that the Kubernetes API server should be configured to use that OIDC provider. + +There are several Identity Providers (IdP) that can be used in a Kubernetes cluster. The steps of this guide have been validated using the following providers: + +- [Keycloak](https://www.keycloak.org/): Open Source Identity and Access Management. +- [Dex](https://github.com/dexidp/dex): Open Source OIDC and OAuth 2.0 Provider with Pluggable Connectors. +- [Azure Active Directory](https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-whatis): Identity Provider that can be used for AKS. +- [Google OpenID Connect](https://developers.google.com/identity/protocols/OpenIDConnect): OAuth 2.0 for Google accounts. + +For Kubeapps to use an Identity Provider it's necessary to configure at least the following parameters: + +- Client ID: Client ID of the IdP. +- Client Secret: (If configured) Secret used to validate the Client ID. +- OIDC Issuer URL (or discovery URL): URL of the OIDC issuer. + +**Note**: Depending on the configuration of the Identity Provider more parameters may be needed. + +In the next sections we will explain how you can find the parameters above for some of the identity providers tested. + +### Keycloak + +In the case of Keycloak, you can find the parameters in the Keycloak admin console: + +- Client-ID: Keycloak client ID. +- Client-secret: Secret associated to the client above. +- OIDC Issuer URL: `https:///auth/realms/`. + +### Dex + +For Dex, you can find the parameters that you need to set in the configuration (a ConfigMap if Dex is deployed within the cluster) that the server reads the configuration from. Note that since Dex is only a connector you need to configure it with some third-party credentials that may be a client-id and client-secret as well. Don't confuse those credentials with the ones of the application that you can find under the `staticClients` section. + +- Client-ID: Static client ID. +- Client-secret: Static client secret. +- OIDC Issuer URL: Dex URL (i.e. https://dex.example.com:32000). + +### Azure Active Directory + +For setting up an Azure Kubernetes cluster (aks) with Azure Active Directory you can follow [this guide](https://docs.microsoft.com/en-us/azure/aks/aad-integration). At the end of the tutorial you should have an Active Directory Application (Server). That's the Application from which we will get the needed parameters. + +- Client-ID: Azure Active Directory server Application ID. +- Client-secret: A "Password" Key of the server Application. +- OIDC Issuer URL: `https://sts.windows.net//`. The Tenant-ID can be found at `Home > Default Directory - Properties > Directory ID`. + +### Google OIDC + +In the case of Google we can use an OAuth 2.0 client ID. You can find more information [here](https://developers.google.com/identity/protocols/OpenIDConnect). In particular we will use a "Web Application". + +- Client-ID: `.apps.googleusercontent.com`. +- Client-Secret: Secret for the Web application. +- OIDC Issuer URL: https://accounts.google.com. + +## Deploying a proxy to access Kubeapps + +The main difference is that instead of accessing the Kubeapps service, we will be using a proxy service that would be in charge of authenticating users against the IdP and inject the required credentials in the requests to Kubeapps. There are some available solutions for this like [keycloak-gatekeeper](https://github.com/keycloak/keycloak-gatekeeper) and [oauth2_proxy](https://github.com/pusher/oauth2_proxy). For this guide we will use `keycloak-gatekeeper` since it has support for WebSockets. + +First we will create a Kubernetes deployment and service for the proxy. For the snippet below, you need to set the environment variables `AUTH_PROXY_CLIENT_ID`, `AUTH_PROXY_CLIENT_SECRET` and `AUTH_PROXY_DISCOVERY_URL` with the information from the IdP and `KUBEAPPS_NAMESPACE`. + +``` +export AUTH_PROXY_CLIENT_ID= +export AUTH_PROXY_CLIENT_SECRET= +export AUTH_PROXY_DISCOVERY_URL= +kubectl create -n $KUBEAPPS_NAMESPACE -f - -o yaml << EOF +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + labels: + name: kubeapps-auth-proxy + name: kubeapps-auth-proxy +spec: + replicas: 1 + selector: + matchLabels: + name: kubeapps-auth-proxy + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + labels: + name: kubeapps-auth-proxy + spec: + containers: + - args: + - --client-id=$AUTH_PROXY_CLIENT_ID + - --client-secret=$AUTH_PROXY_CLIENT_SECRET + - --discovery-url=$AUTH_PROXY_DISCOVERY_URL + - --skip-openid-provider-tls-verify + - --secure-cookie=false + - --upstream-url=http://kubeapps/ + - --resources=uri=/api/kube/*|white-listed=true + - --listen=0.0.0.0:3000 + image: keycloak/keycloak-gatekeeper + imagePullPolicy: IfNotPresent + name: kubeapps-auth-proxy +--- +apiVersion: v1 +kind: Service +metadata: + labels: + name: kubeapps-auth-proxy + name: kubeapps-auth-proxy +spec: + ports: + - name: http + port: 3000 + protocol: TCP + targetPort: 3000 + selector: + name: kubeapps-auth-proxy + sessionAffinity: None + type: ClusterIP +EOF +``` + +The above is a sample deployment, depending on the configuration of the Identity Provider those flags may vary. For this example we use: + +- `--client-id`, `--client-secret` and `--discovery-url`: Client ID, Secret and IdP URL as stated in the section above. +- `--skip-openid-provider-tls-verify`, `--secure-cookie=false`: If the `discovery-url` is served through HTTPS with a self-signed certificate (i.e. Keycloak or Dex), those flags are necessary to avoid errors while validating the TLS certificate. +- `--upstream-url`: Internal URL for the `kubeapps` service. +- `--resources=uri=/api/kube/*|white-listed=true`: In order to use WebSockets we need to bypass the authentication for those request. In that case, Kubeapps will inject the `Bearer` token manually. +- `listen=0.0.0.0:3000`: Listen in all the interfaces. + +## Exposing the proxy + +Once the proxy is in place and it's able to connect to the IdP we will need to expose it to access it as the main endpoint for Kubeapps (instead of the `kubeapps` service). We can do that with an Ingress object. Note that for doing so an [Ingress Controller](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-controllers) is needed. There are also other methods to expose the `kubeapps-auth-proxy` service, for example using `LoadBalancer` as type in a cloud environment. In case an Ingress is used, remember to modify the host `kubeapps.local` for the value that you want to use as hostname for your application: + +``` +kubectl create -n $KUBEAPPS_NAMESPACE -f - -o yaml << EOF +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + annotations: + nginx.ingress.kubernetes.io/connection-proxy-header: keep-alive + nginx.ingress.kubernetes.io/proxy-read-timeout: "600" + name: kubeapps +spec: + rules: + - host: kubeapps.local + http: + paths: + - backend: + serviceName: kubeapps-auth-proxy + servicePort: 3000 + path: / +EOF +``` + +Once you do that you can access the application, you will be prompted to introduce your credentials: + +![Proxy Login](../img/auth-proxy-login.png) + +After introducing those credentials you will be redirected to the Kubeapps application. From 01430be94932a3255058ad5cc9eae57e689166b1 Mon Sep 17 00:00:00 2001 From: Adnan Abdulhussein Date: Thu, 21 Feb 2019 12:31:25 +0100 Subject: [PATCH 2/4] Apply suggested changes Co-Authored-By: andresmgot --- docs/user/login-alternatives.md | 6 +++--- docs/user/using-an-OIDC-provider.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/user/login-alternatives.md b/docs/user/login-alternatives.md index 23f5073c47e..465a973ec06 100644 --- a/docs/user/login-alternatives.md +++ b/docs/user/login-alternatives.md @@ -10,9 +10,9 @@ However, it's possible to disable this form or delegate the authentication to an ## Bypassing authentication -Kubeapps expects an `Authorization` header that will be used to validate operations agains the Kubernetes API. This is usually set with the login form but if Kubeapps is exposed with an Ingress object, it's possible to hardcode a valid token in the Ingress configuration and automatically include it in all the requests. +Kubeapps will skip the login form if it detects that an `Authorization` header is already being set by a reverse-proxy, such as an Ingress. An Ingress object can be configured to inject a hardcoded valid Kubernetes API token on every request to Kubeapps to force the login form to be skipped and enable all API requests to use the hardcoded token. -**NOTE**: This is not suitable for production since anyone with access to Kubeapps would be granted with the permissions associated with the hardcoded token. +**NOTE**: This is not recommended in production since anyone with access to Kubeapps would be granted with the permissions associated with the hardcoded token. This is an example of the values that you can configure in the Kubeapps chart in order to set a valid token: @@ -33,4 +33,4 @@ You just need to substitute TOKEN with the actual value of the token. The above # Using an OIDC provider -In case you want to use OAuth 2.0 to authenticate Kubeapps users follow this [guide](./using-an-OIDC-provider.md). +In case you want to use OpenID Connect to authenticate Kubeapps users, and your Kubernetes API server is configured to use the same OIDC provider, follow this [guide](./using-an-OIDC-provider.md). diff --git a/docs/user/using-an-OIDC-provider.md b/docs/user/using-an-OIDC-provider.md index b904cd83462..b2c3839f065 100644 --- a/docs/user/using-an-OIDC-provider.md +++ b/docs/user/using-an-OIDC-provider.md @@ -6,7 +6,7 @@ It is possible to configure your Kubernetes cluster to use an OIDC provider in o ## Pre-requisites -For this guide we assume that you have a Kubernetes cluster that is properly configured to use an Identity Provider to handle the authorization of your cluster. You can find more information about how Kubernetes uses OIDC tokens [here](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens). This means that the Kubernetes API server should be configured to use that OIDC provider. +For this guide we assume that you have a Kubernetes cluster that is properly configured to use an Identity Provider (IdP) to handle the authorization of your cluster. You can find more information about how Kubernetes uses OIDC tokens [here](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens). This means that the Kubernetes API server should be configured to use that OIDC provider. There are several Identity Providers (IdP) that can be used in a Kubernetes cluster. The steps of this guide have been validated using the following providers: @@ -127,12 +127,12 @@ The above is a sample deployment, depending on the configuration of the Identity - `--client-id`, `--client-secret` and `--discovery-url`: Client ID, Secret and IdP URL as stated in the section above. - `--skip-openid-provider-tls-verify`, `--secure-cookie=false`: If the `discovery-url` is served through HTTPS with a self-signed certificate (i.e. Keycloak or Dex), those flags are necessary to avoid errors while validating the TLS certificate. - `--upstream-url`: Internal URL for the `kubeapps` service. -- `--resources=uri=/api/kube/*|white-listed=true`: In order to use WebSockets we need to bypass the authentication for those request. In that case, Kubeapps will inject the `Bearer` token manually. +- `--resources=uri=/api/kube/*|white-listed=true`: This setting enables WebSockets to work correctly, which Kubeapps relies on to show up-to-date information. Kubeapps handles the injection of the OIDC token into every Kubernetes API request, so it is not necessary for Keycloak Gatekeeper to do it. - `listen=0.0.0.0:3000`: Listen in all the interfaces. ## Exposing the proxy -Once the proxy is in place and it's able to connect to the IdP we will need to expose it to access it as the main endpoint for Kubeapps (instead of the `kubeapps` service). We can do that with an Ingress object. Note that for doing so an [Ingress Controller](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-controllers) is needed. There are also other methods to expose the `kubeapps-auth-proxy` service, for example using `LoadBalancer` as type in a cloud environment. In case an Ingress is used, remember to modify the host `kubeapps.local` for the value that you want to use as hostname for your application: +Once the proxy is in place and it's able to connect to the IdP we will need to expose it to access it as the main endpoint for Kubeapps (instead of the `kubeapps` service). We can do that with an Ingress object. Note that for doing so an [Ingress Controller](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-controllers) is needed. There are also other methods to expose the `kubeapps-auth-proxy` service, for example using `LoadBalancer` as type in a cloud environment. In case an Ingress is used, remember to modify the host `kubeapps.local` for the value that you want to use as a hostname for Kubeapps: ``` kubectl create -n $KUBEAPPS_NAMESPACE -f - -o yaml << EOF From efc85408ed873f56adb5bd5d866b2e8a67349bc9 Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Thu, 21 Feb 2019 12:51:38 +0100 Subject: [PATCH 3/4] Apply review changes --- docs/user/login-alternatives.md | 8 ++++---- docs/user/using-an-OIDC-provider.md | 13 ++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/docs/user/login-alternatives.md b/docs/user/login-alternatives.md index 465a973ec06..fe36f9b7f65 100644 --- a/docs/user/login-alternatives.md +++ b/docs/user/login-alternatives.md @@ -1,12 +1,12 @@ -# Kubeapps Login Form +# Kubeapps Login -By default, when first visiting Kubeapps, a login form is shown for the user to introduce a personal token for a specific service account: +By default, when first visiting Kubeapps, a login form is shown for the user to introduce a Kubernetes API token: ![Dashboard Login](../img/dashboard-login.png) -You can find more information about access control in Kubeapps in this [document](./access-control.md). +The goal of the login form is to identify the user and associate it with a Kubernetes service account. This identity information will be used by Kubeapps to authenticate the user against the Kubernetes API. You can find more information about access control in Kubeapps in this [document](./access-control.md). -However, it's possible to disable this form or delegate the authentication to an OIDC provider so Kubeapps users don't need to introduce a token in the login form. +However, it's possible to disable the form or delegate the authentication to an OIDC provider so users don't need to introduce a token in the login form. ## Bypassing authentication diff --git a/docs/user/using-an-OIDC-provider.md b/docs/user/using-an-OIDC-provider.md index b2c3839f065..87fdc49afc1 100644 --- a/docs/user/using-an-OIDC-provider.md +++ b/docs/user/using-an-OIDC-provider.md @@ -1,6 +1,6 @@ # Using an OIDC Provider with Kubeapps -OpenID Connect (OIDC) is a simple identity layer on top of the OAuth 2.0 protocol, which allows computing clients to verify the identity of an end-user based on the authentication performed by an authorization server, as well as to obtain basic profile information about the end-user. +OpenID Connect (OIDC) is a simple identity layer on top of the OAuth 2.0 protocol, which allows clients to verify the identity of an user based on the authentication performed by an authorization server, as well as to obtain basic profile information about the user. It is possible to configure your Kubernetes cluster to use an OIDC provider in order to manage accounts, groups and roles with a single application. This guide will explain how you can use an existing OIDC provider to authenticate users within Kubeapps. @@ -23,7 +23,7 @@ For Kubeapps to use an Identity Provider it's necessary to configure at least th **Note**: Depending on the configuration of the Identity Provider more parameters may be needed. -In the next sections we will explain how you can find the parameters above for some of the identity providers tested. +In the next sections we will explain how you can find the parameters above for some of the identity providers tested. If you have configured your cluster to use an Identity Provider you will already know some of these parameters. ### Keycloak @@ -94,8 +94,6 @@ spec: - --client-id=$AUTH_PROXY_CLIENT_ID - --client-secret=$AUTH_PROXY_CLIENT_SECRET - --discovery-url=$AUTH_PROXY_DISCOVERY_URL - - --skip-openid-provider-tls-verify - - --secure-cookie=false - --upstream-url=http://kubeapps/ - --resources=uri=/api/kube/*|white-listed=true - --listen=0.0.0.0:3000 @@ -125,11 +123,12 @@ EOF The above is a sample deployment, depending on the configuration of the Identity Provider those flags may vary. For this example we use: - `--client-id`, `--client-secret` and `--discovery-url`: Client ID, Secret and IdP URL as stated in the section above. -- `--skip-openid-provider-tls-verify`, `--secure-cookie=false`: If the `discovery-url` is served through HTTPS with a self-signed certificate (i.e. Keycloak or Dex), those flags are necessary to avoid errors while validating the TLS certificate. - `--upstream-url`: Internal URL for the `kubeapps` service. - `--resources=uri=/api/kube/*|white-listed=true`: This setting enables WebSockets to work correctly, which Kubeapps relies on to show up-to-date information. Kubeapps handles the injection of the OIDC token into every Kubernetes API request, so it is not necessary for Keycloak Gatekeeper to do it. - `listen=0.0.0.0:3000`: Listen in all the interfaces. +**NOTE**: If the identity provider is deployed with a self-signed certificate (which may be the case for Keycloak or Dex) you will need to disable the TLS and cookie verification. For doing so you can add the flags `--skip-openid-provider-tls-verify` and `--secure-cookie=false` to the deployment above. You can find more options for the `keycloak-gatekeeper` proxy [here](https://www.keycloak.org/docs/latest/securing_apps/index.html#_keycloak_generic_adapter). + ## Exposing the proxy Once the proxy is in place and it's able to connect to the IdP we will need to expose it to access it as the main endpoint for Kubeapps (instead of the `kubeapps` service). We can do that with an Ingress object. Note that for doing so an [Ingress Controller](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-controllers) is needed. There are also other methods to expose the `kubeapps-auth-proxy` service, for example using `LoadBalancer` as type in a cloud environment. In case an Ingress is used, remember to modify the host `kubeapps.local` for the value that you want to use as a hostname for Kubeapps: @@ -155,8 +154,8 @@ spec: EOF ``` -Once you do that you can access the application, you will be prompted to introduce your credentials: +Once you create the ingress rule and you access the proxy, you will be redirected to your IdP. There, you will be prompted to introduce your credentials: ![Proxy Login](../img/auth-proxy-login.png) -After introducing those credentials you will be redirected to the Kubeapps application. +After successfully introduce the credentials, you will be finally redirected to Kubeapps. From 27e8e8c45ee575a7cd2427e1aa260f4501c3dcfa Mon Sep 17 00:00:00 2001 From: Adnan Abdulhussein Date: Fri, 22 Feb 2019 10:00:48 +0100 Subject: [PATCH 4/4] Update docs/user/using-an-OIDC-provider.md Co-Authored-By: andresmgot --- docs/user/using-an-OIDC-provider.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/using-an-OIDC-provider.md b/docs/user/using-an-OIDC-provider.md index 87fdc49afc1..1a3ecd6096b 100644 --- a/docs/user/using-an-OIDC-provider.md +++ b/docs/user/using-an-OIDC-provider.md @@ -158,4 +158,4 @@ Once you create the ingress rule and you access the proxy, you will be redirecte ![Proxy Login](../img/auth-proxy-login.png) -After successfully introduce the credentials, you will be finally redirected to Kubeapps. +After successfully logging in with your IdP, you will be redirected to Kubeapps and be authenticated with your user's OIDC token.