Configuring the Dex Identity Provider

Motivation

Most organizations have an IT department that handle new staff on boarding and provide them a new set of credentials. These credentials are stored in centralized directory service, most commonly Active Directory. Some cases expose these services through an Identity Provider using OIDC or SAML.

Authentication and Authorization is a common topic on how you can provide Kubernetes cluster access and the resources to the intended user; e.g.: Developers, Kubernetes Admin, Backup Admin, etc. Rather than create and manage a new user repository, you can leverage Dex and Gangway to enable kubernetes cluster authentication using an existing Identity Provider.

Gangway is a web application that enables the OIDC authentication flow which results in the minting of the ID Token.Dex acts as a portal to other identity providers through “connectors.”

Configuration

Dex Gangway Setup Figure 1: Dex - Gangway Identity Federation

Assuming you have either Active Directory or IDP server that provide centralized user management and you wish all your Kubernetes user is authenticated before they are able to access the resource. This scenario is the typical usage for Dex and Gangway and will be explained further in the following sections

Dex and Gangway

Dex Gangway Setup Figure 2: Dex - Gangway Configuration

Dex provide common OIDC endpoints for multiple Identity providers. To configure Dex and Gangway communication, you need to collect some information from Dex. Following OIDC standard, Dex provides a URL where its OIDC configuration can be gathered. This URL is located at .well-known/openid-configuration and by accessing the URL, Dex will produce the following response

## Sample '.well-known/openid-configuration' DEX response
{
  "issuer": "http://app.example.com",
  "authorization_endpoint": "http://app.example.com/auth",
  "token_endpoint": "http://app.example.com/token",
  "jwks_uri": "http:/app.example.com/keys",
  "response_types_supported": ["code"],
  "subject_types_supported": ["public"],
  "id_token_signing_alg_values_supported": ["RS256"],
  "scopes_supported":
    ["openid", "email", "groups", "profile", "offline_access"],
  "token_endpoint_auth_methods_supported": ["client_secret_basic"],
  "claims_supported":
    [
      "aud",
      "email",
      "email_verified",
      "exp",
      "iat",
      "iss",
      "locale",
      "name",
      "sub",
    ],
}

With the above response from Dex, Gangway configuration can be defined as follows.

## Sample Gangway ConfigMap configuration
apiVersion: v1
kind: ConfigMap
metadata:
  name: gangway
data:
  gangway.yaml: |
    serveTLS: false
    certFile: /tls/tls.crt
    keyFile: /tls/tls.key
    clusterName: "{{Gangway cluster name}}"

    #!! Dex URL for token auth request
    authorizeURL: "{{ Dex authorization_endpoint }}"

    #!! Dex URL to obtain access tokens.
    tokenURL: "{{ Dex token_endpoint }} "

    # Where to redirect back to. This should be a URL where gangway is reachable.
    # Typically this also needs to be registered as part of the oauth application
    # with the oAuth provider.
    redirectURL: "{{Gangway hostname}}/callback"

    # Used to specify the scope of the requested Oauth authorization.
    #!! Refer to dex `scopes_supported` response above
    scopes: ["openid", "profile", "email", "offline_access"]

    # API client ID as indicated by the identity provider
    clientID: "{{Gangway Client Id}}"

    #!! refer to dex `claims_supported` response above.
    usernameClaim: "email"

    # The API server endpoint used to configure kubectl
    apiServerURL: "{{ Kubernetes API hostname }}:6443/"

    # The path to a root CA to trust for self signed certificates
    # at the Oauth2 URLs.
    # the dex-ca.crt can be mounted as secret in kubernetes
    trustedCAPath: /etc/dex/dex-ca.crt
    idpCAPath: /etc/dex/dex-ca.crt    

Registering Gangway

In order to establish communication between Dex and Gangway, Dex require a registration of Gangway through Client id, and Client secret exchange. This allow Dex to authenticate that the intended requestor (in this case Gangway) is registered within Dex and is allowed to retrieve access token through Dex.

The configuration for Client id and Secret in Dex and Gangway can be define as follows

## Sample Dex Config Map
apiVersion: v1
kind: ConfigMap
metadata:
  name: dex
data:
  config.yaml: |
    ...
    staticClients: 
    - id: {{Gangway Client Id}}
      redirectURIs:
      - '{{ Gangway Callback URL}}'
      name: '{{Gangway Client Id}}'
      secret: {{Decoded Gangway Client Secret}}
    ....    
## Sample Gangway Secret
apiVersion: v1
kind: Secret
metadata:
  name: gangway
type: Opaque
data:
  #! Key to generate secret session.
  #! Command to run : openssl rand -base64 32 | pbcopy
  sesssionKey: { { session key for dex } }
  #! base64 encoded client secret , shoudl match with Dex config Client Secret
  #! echo -n "$clientSecret" | base64
  clientSecret: { { encoded Gangway Client Secret } }

Identity Federation

dex-federation Figure 3: Dex - Identity Federation

Dex acts as a portal to other identity providers through connectors. This lets Dex defer authentication to LDAP servers, SAML providers, or established identity providers like GitHub, Google, and Active Directory. Clients write their authentication logic once to talk to Dex, then Dex handles the protocols for a given backend.

Upon successful authentication, signed JWT token returned by Dex as part of the authentication (OAuth2) response that attest to the end user’s identity. The JWT token issued by Dex contains standard claims which can be consumed by other system for service to service call.

// Sample JWT claims response from Dex.
{
  "iss": "http://127.0.0.1:5556/dex",
  "sub": "CgcyMzQyNzQ5EgZnaXRodWI",
  "aud": "example-app",
  "exp": 1492882042,
  "iat": 1492795642,
  "at_hash": "bi96gOXZShvlWYtal9Eqiw",
  "email": "jane.doe@ldap.com",
  "email_verified": true,
  "groups": ["admins", "developers"],
  "name": "Jane Doe"
}

Connector

A connector is a plugin that used by Dex for authenticating a user against another identity provider. Dex adopted connectors that target specific platforms such as LDAP, OIDC, Github, SAML, etc. A list of available connectors is available here.

Depending on the connectors limitations in protocols can prevent Dex from issuing refresh tokens or returning group membership claims. For example, because SAML doesn’t provide a non-interactive way to refresh assertions, if a user logs in through the SAML connector Dex won’t issue a refresh token to its client.

With the example from the above diagrams (figure 1), the configuration for the connectors can be defined as follows

For LDAP Connector
connectors:
  - type: ldap
    id: ldap
    name: LDAP
    config:
      host: ldap.example.com:636
      insecureNoSSL: true
      insecureSkipVerify: true
      startTLS: true
      rootCA: /etc/dex/ldap.ca
      bindDN: uid=serviceaccount,cn=users,dc=example,dc=com
      bindPW: password
      usernamePrompt: SSO Username
      userSearch:
        baseDN: cn=users,dc=example,dc=com
        filter: "(objectClass=person)"
        username: uid
        idAttr: uid
        emailAttr: mail
        nameAttr: name
      groupSearch:
        baseDN: cn=groups,dc=freeipa,dc=example,dc=com
        filter: "(objectClass=group)"
        userMatchers:
          - userAttr: uid
            groupAttr: member
        nameAttr: name
For OIDC Connector
connectors:
  - type: oidc
    id: oidcServer
    name: oidcServer
    config:
      issuer: https://accounts.google.com
      clientID: $CLIENT_ID
      clientSecret: $CLIENT_SECRET
      # Dex's issuer URL + "/callback"
      redirectURI: http://127.0.0.1:5556/callback

      # Some providers require passing client_secret via POST parameters instead
      # of basic auth, despite the OAuth2 RFC discouraging it. Many of these
      # cases are caught internally, but some may need to uncomment the
      # following field.
      #
      basicAuthUnsupported: true

      # List of additional scopes to request in token response
      # Default is profile and email
      # Full list at https://github.com/dexidp/dex/blob/master/Documentation/custom-scopes-claims-clients.md
      scopes:
        - profile
        - email
        - groups
      insecureSkipEmailVerified: true
      insecureEnableGroups: true

      # When enabled, the OpenID Connector will query the UserInfo endpoint for additional claims. UserInfo claims
      # take priority over claims returned by the IDToken. This option should be used when the IDToken doesn't contain
      # all the claims requested.
      # https://openid.net/specs/openid-connect-core-1_0.html#UserInfo
      getUserInfo: true

      # The set claim is used as user id.
      # Default: sub
      # Claims list at https://openid.net/specs/openid-connect-core-1_0.html#Claims
      userIDKey: nickname

      # The set claim is used as user name.
      # Default: name
      userNameKey: nickname

      # For offline_access, the prompt parameter is set by default to "prompt=consent".
      # However this is not supported by all OIDC providers, some of them support different
      # value for prompt, like "prompt=login" or "prompt=none"
      promptType: consent
For SAML Connector
connectors:
  - type: saml
    # Required field for connector id.
    id: saml
    # Required field for connector name.
    name: SAML
    config:
      # SSO URL used for POST value.
      ssoURL: https://saml.example.com/sso

      # CA to use when validating the signature of the SAML response.
      ca: /path/to/ca.pem

      # Dex's callback URL.
      redirectURI: https://dex.example.com/callback

      # Name of attributes in the returned assertions to map to ID token claims.
      usernameAttr: name
      emailAttr: email
      groupsAttr: groups # optional

      # To skip signature validation, uncomment the following field. This should
      # only be used during testing and may be removed in the future.
      #
      insecureSkipSignatureValidation: true

      # Optional: Manually specify dex's Issuer value.
      entityIssuer: https://dex.example.com/callback

      # Optional: Issuer value expected in the SAML response.
      ssoIssuer: https://saml.example.com/sso

      # Optional: Delimiter for splitting groups returned as a single string.
      groupsDelim: ", "

      # Optional: Requested format of the NameID.
      nameIDPolicyFormat: persistent