# Authentication

# Keycloak

Install keycloak helm chart (opens new window) and modify the default values (opens new window).

helm repo add --force-update bitnami https://charts.bitnami.com/bitnami
helm upgrade --install --version 5.0.6 --namespace keycloak --create-namespace --values - keycloak bitnami/keycloak << EOF
clusterDomain: ${CLUSTER_FQDN}
auth:
  adminUser: admin
  adminPassword: ${MY_PASSWORD}
  managementUser: admin
  managementPassword: ${MY_PASSWORD}
proxyAddressForwarding: true
# https://stackoverflow.com/questions/51616770/keycloak-restricting-user-management-to-certain-groups-while-enabling-manage-us
extraStartupArgs: "-Dkeycloak.profile.feature.admin_fine_grained_authz=enabled"
# keycloakConfigCli:
#   enabled: true
#   # Workaround for bug: https://github.com/bitnami/charts/issues/6823
#   image:
#     repository: adorsys/keycloak-config-cli
#     tag: latest-15.0.1
#   configuration:
#     myrealm.yaml: |
#       realm: myrealm
#       enabled: true
#       displayName: My Realm
#       rememberMe: true
#       userManagedAccessAllowed: true
#       smtpServer:
#         from: myrealm-keycloak@${CLUSTER_FQDN}
#         fromDisplayName: Keycloak
#         host: mailhog.mailhog.svc.cluster.local
#         port: 1025
#       clients:
#       # https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider/#keycloak-auth-provider
#       - clientId: oauth2-proxy-keycloak.${CLUSTER_FQDN}
#         name: oauth2-proxy-keycloak.${CLUSTER_FQDN}
#         description: "OAuth2 Proxy for Keycloak"
#         secret: ${MY_PASSWORD}
#         redirectUris:
#         - "https://oauth2-proxy-keycloak.${CLUSTER_FQDN}/oauth2/callback"
#         protocolMappers:
#         - name: groupMapper
#           protocol: openid-connect
#           protocolMapper: oidc-group-membership-mapper
#           config:
#             userinfo.token.claim: "true"
#             id.token.claim: "true"
#             access.token.claim: "true"
#             claim.name: groups
#             full.path: "true"
#       identityProviders:
#       # https://ultimatesecurity.pro/post/okta-oidc/
#       - alias: keycloak-oidc-okta
#         displayName: "Okta"
#         providerId: keycloak-oidc
#         trustEmail: true
#         config:
#           clientId: ${OKTA_CLIENT_ID}
#           clientSecret: ${OKTA_CLIENT_SECRET}
#           tokenUrl: "${OKTA_ISSUER}/oauth2/default/v1/token"
#           authorizationUrl: "${OKTA_ISSUER}/oauth2/default/v1/authorize"
#           defaultScope: "openid profile email"
#           syncMode: IMPORT
#       - alias: dex
#         displayName: "Dex"
#         providerId: keycloak-oidc
#         trustEmail: true
#         config:
#           clientId: keycloak.${CLUSTER_FQDN}
#           clientSecret: ${MY_PASSWORD}
#           tokenUrl: https://dex.${CLUSTER_FQDN}/token
#           authorizationUrl: https://dex.${CLUSTER_FQDN}/auth
#           syncMode: IMPORT
#       - alias: github
#         displayName: "Github"
#         providerId: github
#         trustEmail: true
#         config:
#           clientId: ${MY_GITHUB_ORG_OAUTH_KEYCLOAK_CLIENT_ID}
#           clientSecret: ${MY_GITHUB_ORG_OAUTH_KEYCLOAK_CLIENT_SECRET}
#       users:
#       - username: myuser1
#         email: myuser1@${CLUSTER_FQDN}
#         enabled: true
#         firstName: My Firstname 1
#         lastName: My Lastname 1
#         groups:
#           - group-admins
#         credentials:
#         - type: password
#           value: ${MY_PASSWORD}
#       - username: myuser2
#         email: myuser2@${CLUSTER_FQDN}
#         enabled: true
#         firstName: My Firstname 2
#         lastName: My Lastname 2
#         groups:
#           - group-admins
#         credentials:
#         - type: password
#           value: ${MY_PASSWORD}
#       - username: myuser3
#         email: myuser3@${CLUSTER_FQDN}
#         enabled: true
#         firstName: My Firstname 3
#         lastName: My Lastname 3
#         groups:
#           - group-users
#         credentials:
#         - type: password
#           value: ${MY_PASSWORD}
#       - username: myuser4
#         email: myuser4@${CLUSTER_FQDN}
#         enabled: true
#         firstName: My Firstname 4
#         lastName: My Lastname 4
#         groups:
#           - group-users
#           - group-test
#         credentials:
#         - type: password
#           value: ${MY_PASSWORD}
#       groups:
#       - name: group-users
#       - name: group-admins
#       - name: group-test
service:
  type: ClusterIP
ingress:
  enabled: true
  hostname: keycloak.${CLUSTER_FQDN}
  extraTls:
  - hosts:
      - keycloak.${CLUSTER_FQDN}
    secretName: ingress-cert-${LETSENCRYPT_ENVIRONMENT}
networkPolicy:
  enabled: true
metrics:
  enabled: true
  serviceMonitor:
    enabled: true
postgresql:
  persistence:
    enabled: false
EOF

# oauth2-proxy - Keycloak

Install oauth2-proxy (opens new window) to secure the endpoints like (prometheus., alertmanager.).

Install oauth2-proxy helm chart (opens new window) and modify the default values (opens new window).

helm repo add --force-update oauth2-proxy https://oauth2-proxy.github.io/manifests
helm upgrade --install --version 4.2.0 --namespace oauth2-proxy-keycloak --create-namespace --values - oauth2-proxy oauth2-proxy/oauth2-proxy << EOF
config:
  clientID: oauth2-proxy-keycloak.${CLUSTER_FQDN}
  clientSecret: "${MY_PASSWORD}"
  cookieSecret: "$(openssl rand -base64 32 | head -c 32 | base64)"
  configFile: |-
    email_domains = [ "*" ]
    upstreams = [ "file:///dev/null" ]
    whitelist_domains = ".${CLUSTER_FQDN}"
    cookie_domains = ".${CLUSTER_FQDN}"
    provider = "keycloak"
    login_url = "https://keycloak.${CLUSTER_FQDN}/auth/realms/myrealm/protocol/openid-connect/auth"
    redeem_url = "https://keycloak.${CLUSTER_FQDN}/auth/realms/myrealm/protocol/openid-connect/token"
    profile_url = "https://keycloak.${CLUSTER_FQDN}/auth/realms/myrealm/protocol/openid-connect/userinfo"
    validate_url = "https://keycloak.${CLUSTER_FQDN}/auth/realms/myrealm/protocol/openid-connect/userinfo"
    scope = "openid email profile"
    ssl_insecure_skip_verify = "true"
    insecure_oidc_skip_issuer_verification = "true"
ingress:
  enabled: true
  hosts:
    - oauth2-proxy-keycloak.${CLUSTER_FQDN}
  tls:
    - secretName: ingress-cert-${LETSENCRYPT_ENVIRONMENT}
      hosts:
        - oauth2-proxy-keycloak.${CLUSTER_FQDN}
metrics:
  servicemonitor:
    enabled: true
EOF

# Dex

Install dex helm chart (opens new window) and modify the default values (opens new window).

helm repo add --force-update dex https://charts.dexidp.io
helm upgrade --install --version 0.6.0 --namespace dex --create-namespace --values - dex dex/dex << EOF
ingress:
  enabled: true
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
  hosts:
    - host: dex.${CLUSTER_FQDN}
      paths:
        - path: /
          pathType: ImplementationSpecific
  tls:
    - secretName: ingress-cert-${LETSENCRYPT_ENVIRONMENT}
      hosts:
        - dex.${CLUSTER_FQDN}
config:
  issuer: https://dex.${CLUSTER_FQDN}
  storage:
    type: kubernetes
    config:
      inCluster: true
  oauth2:
    skipApprovalScreen: true
  connectors:
    - type: github
      id: github
      name: GitHub
      config:
        clientID: ${MY_GITHUB_ORG_OAUTH_DEX_CLIENT_ID}
        clientSecret: ${MY_GITHUB_ORG_OAUTH_DEX_CLIENT_SECRET}
        redirectURI: https://dex.${CLUSTER_FQDN}/callback
        orgs:
          - name: ${MY_GITHUB_ORG_NAME}
    - type: oidc
      id: okta
      name: Okta
      config:
        issuer: ${OKTA_ISSUER}
        clientID: ${OKTA_CLIENT_ID}
        clientSecret: ${OKTA_CLIENT_SECRET}
        redirectURI: https://dex.${CLUSTER_FQDN}/callback
        scopes:
          - openid
          - profile
          - email
        getUserInfo: true
  staticClients:
    - id: argocd.${CLUSTER_FQDN}
      redirectURIs:
        - https://argocd.${CLUSTER_FQDN}/auth/callback
      name: ArgoCD
      secret: ${MY_PASSWORD}
    - id: gangway.${CLUSTER_FQDN}
      redirectURIs:
        - https://gangway.${CLUSTER_FQDN}/callback
      name: Gangway
      secret: ${MY_PASSWORD}
    - id: harbor.${CLUSTER_FQDN}
      redirectURIs:
        - https://harbor.${CLUSTER_FQDN}/c/oidc/callback
      name: Harbor
      secret: ${MY_PASSWORD}
    - id: kiali.${CLUSTER_FQDN}
      redirectURIs:
        - https://kiali.${CLUSTER_FQDN}
      name: Kiali
      secret: ${MY_PASSWORD}
    - id: keycloak.${CLUSTER_FQDN}
      redirectURIs:
        - https://keycloak.${CLUSTER_FQDN}/auth/realms/myrealm/broker/dex/endpoint
      name: Keycloak
      secret: ${MY_PASSWORD}
    - id: oauth2-proxy.${CLUSTER_FQDN}
      redirectURIs:
        - https://oauth2-proxy.${CLUSTER_FQDN}/oauth2/callback
      name: OAuth2 Proxy
      secret: ${MY_PASSWORD}
    - id: vault.${CLUSTER_FQDN}
      redirectURIs:
        - https://vault.${CLUSTER_FQDN}/ui/vault/auth/oidc/oidc/callback
        - http://localhost:8250/oidc/callback
      name: Vault
      secret: ${MY_PASSWORD}
  enablePasswordDB: false
EOF

# oauth2-proxy

Install oauth2-proxy (opens new window) to secure the endpoints like (prometheus., alertmanager.).

Install oauth2-proxy helm chart (opens new window) and modify the default values (opens new window).

helm upgrade --install --version 4.2.0 --namespace oauth2-proxy --create-namespace --values - oauth2-proxy oauth2-proxy/oauth2-proxy << EOF
config:
  clientID: oauth2-proxy.${CLUSTER_FQDN}
  clientSecret: "${MY_PASSWORD}"
  cookieSecret: "$(openssl rand -base64 32 | head -c 32 | base64)"
  configFile: |-
    email_domains = [ "*" ]
    upstreams = [ "file:///dev/null" ]
    whitelist_domains = ".${CLUSTER_FQDN}"
    cookie_domains = ".${CLUSTER_FQDN}"
    provider = "oidc"
    oidc_issuer_url = "https://dex.${CLUSTER_FQDN}"
    ssl_insecure_skip_verify = "true"
    insecure_oidc_skip_issuer_verification = "true"
ingress:
  enabled: true
  hosts:
    - oauth2-proxy.${CLUSTER_FQDN}
  tls:
    - secretName: ingress-cert-${LETSENCRYPT_ENVIRONMENT}
      hosts:
        - oauth2-proxy.${CLUSTER_FQDN}
metrics:
  servicemonitor:
    enabled: true
EOF

# Gangway

Install gangway:

helm repo add --force-update stable https://charts.helm.sh/stable
helm upgrade --install --version 0.4.5 --namespace gangway --create-namespace --values - gangway stable/gangway << EOF
# https://github.com/helm/charts/blob/master/stable/gangway/values.yaml
trustedCACert: |
$(curl -s "${LETSENCRYPT_CERTIFICATE}" | sed "s/^/  /")
gangway:
  clusterName: ${CLUSTER_FQDN}
  authorizeURL: https://dex.${CLUSTER_FQDN}/auth
  tokenURL: https://dex.${CLUSTER_FQDN}/token
  audience: https://dex.${CLUSTER_FQDN}/userinfo
  redirectURL: https://gangway.${CLUSTER_FQDN}/callback
  clientID: gangway.${CLUSTER_FQDN}
  clientSecret: ${MY_PASSWORD}
  apiServerURL: https://kube-oidc-proxy.${CLUSTER_FQDN}
ingress:
  enabled: true
  annotations:
    nginx.ingress.kubernetes.io/auth-url: https://oauth2-proxy.${CLUSTER_FQDN}/oauth2/auth
    nginx.ingress.kubernetes.io/auth-signin: https://oauth2-proxy.${CLUSTER_FQDN}/oauth2/start?rd=\$scheme://\$host\$request_uri
  hosts:
    - gangway.${CLUSTER_FQDN}
  tls:
    - secretName: ingress-cert-${LETSENCRYPT_ENVIRONMENT}
      hosts:
        - gangway.${CLUSTER_FQDN}
EOF

# kube-oidc-proxy

The kube-oidc-proxy accepting connections only via HTTPS. It's necessary to configure ingress to communicate with the backend over HTTPS.

Install kube-oidc-proxy:

test -d "tmp/${CLUSTER_FQDN}/kube-oidc-proxy" || git clone --quiet https://github.com/jetstack/kube-oidc-proxy.git "tmp/${CLUSTER_FQDN}/kube-oidc-proxy"
git -C "tmp/${CLUSTER_FQDN}/kube-oidc-proxy" checkout --quiet v0.3.0

helm upgrade --install --namespace kube-oidc-proxy --create-namespace --values - kube-oidc-proxy "tmp/${CLUSTER_FQDN}/kube-oidc-proxy/deploy/charts/kube-oidc-proxy" << EOF
# https://github.com/jetstack/kube-oidc-proxy/blob/master/deploy/charts/kube-oidc-proxy/values.yaml
oidc:
  clientId: gangway.${CLUSTER_FQDN}
  issuerUrl: https://dex.${CLUSTER_FQDN}
  usernameClaim: email
  caPEM: |
$(curl -s "${LETSENCRYPT_CERTIFICATE}" | sed "s/^/    /")
ingress:
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: HTTPS
  enabled: true
  hosts:
    - host: kube-oidc-proxy.${CLUSTER_FQDN}
      paths:
        - /
  tls:
   - secretName: ingress-cert-${LETSENCRYPT_ENVIRONMENT}
     hosts:
       - kube-oidc-proxy.${CLUSTER_FQDN}
EOF

If you get the credentials form the https://gangway.kube1.k8s.mylabs.dev (opens new window) you will have the access to the cluster, but no rights there.

Add access rights to the user:

kubectl apply -f - << EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: kube-prometheus-stack
  name: secret-reader
rules:
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "watch", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-secrets
  namespace: kube-prometheus-stack
subjects:
- kind: User
  name: ${MY_EMAIL}
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: pods-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: read-pods
subjects:
- kind: User
  name: ${MY_EMAIL}
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: pods-reader
  apiGroup: rbac.authorization.k8s.io
EOF

The user should be able to read the secrets in kube-prometheus-stack namespace:

kubectl describe secrets --insecure-skip-tls-verify -n kube-prometheus-stack "ingress-cert-${LETSENCRYPT_ENVIRONMENT}" # DevSkim: ignore DS126188

Output:

Name:         ingress-cert-staging
Namespace:    kube-prometheus-stack
Labels:       kubed.appscode.com/origin.cluster=kube1.k8s.mylabs.dev
              kubed.appscode.com/origin.name=ingress-cert-staging
              kubed.appscode.com/origin.namespace=cert-manager
Annotations:  cert-manager.io/alt-names: *.kube1.k8s.mylabs.dev,kube1.k8s.mylabs.dev
              cert-manager.io/certificate-name: ingress-cert-staging
              cert-manager.io/common-name: *.kube1.k8s.mylabs.dev
              cert-manager.io/ip-sans:
              cert-manager.io/issuer-group:
              cert-manager.io/issuer-kind: ClusterIssuer
              cert-manager.io/issuer-name: letsencrypt-staging-dns
              cert-manager.io/uri-sans:
              kubed.appscode.com/origin:
                {"namespace":"cert-manager","name":"ingress-cert-staging","uid":"f1ed062c-23d9-4cf7-ad51-cfafd8a3b788","resourceVersion":"5296"}

Type:  kubernetes.io/tls

Data
====
tls.crt:  3586 bytes
tls.key:  1679 bytes

But it's not allowed to delete the secrets for the user:

kubectl delete secrets --insecure-skip-tls-verify -n kube-prometheus-stack "ingress-cert-${LETSENCRYPT_ENVIRONMENT}"   # DevSkim: ignore DS126188

Output:

Error from server (Forbidden): secrets "ingress-cert-staging" is forbidden: User "petr.ruzicka@gmail.com" cannot delete resource "secrets" in API group "" in the namespace "kube-prometheus-stack"

The user can not read secrets outside the kube-prometheus-stack:

kubectl get secrets --insecure-skip-tls-verify -n kube-system                                                          # DevSkim: ignore DS126188

Output:

Error from server (Forbidden): secrets is forbidden: User "petr.ruzicka@gmail.com" cannot list resource "secrets" in API group "" in the namespace "kube-system"

You can see the pods "everywhere":

kubectl get pods --insecure-skip-tls-verify -n kube-system                                                             # DevSkim: ignore DS126188

Output:

NAME                                                         READY   STATUS    RESTARTS   AGE
aws-for-fluent-bit-5hxlt                                     1/1     Running   0          32m
aws-for-fluent-bit-dmvzq                                     1/1     Running   0          32m
aws-node-ggfft                                               1/1     Running   0          32m
aws-node-lhlvf                                               1/1     Running   0          32m
cluster-autoscaler-aws-cluster-autoscaler-7f878bccc8-s279k   1/1     Running   0          25m
coredns-59b69b4849-6v487                                     1/1     Running   0          46m
coredns-59b69b4849-tw2dg                                     1/1     Running   0          46m
ebs-csi-controller-86785d75db-7brbr                          5/5     Running   0          31m
ebs-csi-controller-86785d75db-gn4ll                          5/5     Running   0          31m
ebs-csi-node-6h9zv                                           3/3     Running   0          31m
ebs-csi-node-r5rj7                                           3/3     Running   0          31m
kube-proxy-m6dm8                                             1/1     Running   0          32m
kube-proxy-pdmv9                                             1/1     Running   0          32m

But you can not delete them:

kubectl delete pods --insecure-skip-tls-verify -n kube-oidc-proxy --all                                                # DevSkim: ignore DS126188

Output:

Error from server (Forbidden): pods "kube-oidc-proxy-74bf5679fd-jhdmr" is forbidden: User "petr.ruzicka@gmail.com" cannot delete resource "pods" in API group "" in the namespace "kube-oidc-proxy"