GCP: LDAP authentication for Anthos VMware clusters using Anthos Identity Service

Anthos Identity Service allows an organization to tie into their existing Identity Provider to authenticate and authorize users into their Anthos clusters.

In this article, I will show how the authentication for an Anthos on VMware cluster can be integrated into an existing Active Directory deployment, and further how a user’s AD group membership can determine RBAC roles within the cluster.

Prerequisites

  • Deployment of Anthos on VMware on-premise cluster
  • Deployment of Active Directory with exposed secure LDAP (port 636).  CA cert if not public
  • LDAP service account and credentials for binding

I have written previous articles on installing a Windows 2019 Domain Controller, and installing Anthos on VMware 1.13.

LDAP validation

It can save a lot of time if you validate your LDAP connectivity and hierarchy, instead of waiting to test in-cluster.  We can use the ldapsearch utility on Ubuntu to prove out our connectivity and filters.

sudo apt install ldap-utils -y

If the CA certificate of the Windows Domain Controller is self-signed (not from a public authority), then place the CA file (in PEM format) locally and set this environment variable so it can be used to validate the chain of trust.

export LDAPTLS_CACERT=adfsCA.pem

Then setup the basic variables for connecting to AD.  Tailor to your environment, these are based off my previous article on installing a Windows 2019 Domain Controller and setting up a suite of test users using Powershell.

ldap_server=win2k19-dc1.fabian.lee
base_DC="DC=FABIAN,DC=LEE"

# Anthos Identity Service expects full DN (not just id@domain)
binding_user="CN=ldap1,CN=Users,${base_DC}"
binding_pass="ThisIsMyP4ss!"

Test connectivity using service account

Now test a basic search that returns the top level containers in AD using the service account binding credentials.

ldapsearch -LLL -H ldaps://${ldap_server}:636 -D $binding_user -w $binding_pass -b $base_DC -s one dn

If this does not work, do not move forward until you figure out if this is a credentials issue, network connectivity, or CA cert trust validation.

Test the base user filter

This will list all ‘user’ objects in CN=Users

ldapsearch -LLL -H ldaps://${ldap_server}:636 -D $binding_user -w $binding_pass -b CN=Users,$base_DC -s one "(objectClass=user)" sAMAccountName

We will use the short ‘sAMAccountName’ attribute of the user object for login (e.g. engineer1), and ‘userPrincipalName’ (e.g. engineer1@fabian.lee) for any user specific subjects.

# short login id
ldapsearch -LLL -H ldaps://${ldap_server}:636 -D $binding_user -w $binding_pass -b CN=Users,$base_DC -s one "(objectClass=user)" sAMAccountName

# RBAC role represented with unique, fully qualified domain
ldapsearch -LLL -H ldaps://${ldap_server}:636 -D $binding_user -w $binding_pass -b CN=Users,$base_DC -s one "(objectClass=user)" userPrincipalName

Test the base group filter

This will list all ‘group’ objects in CN=Users

ldapsearch -LLL -H ldaps://${ldap_server}:636 -D $binding_user -w $binding_pass -b CN=Users,$base_DC -s one "(objectClass=group)" cn

Armed with these values and filters we can now continue, confident that our cluster config settings are valid.

Configure Kubernetes cluster

Verify Anthos Identity Service deployment

Verify that Anthos Identity Service components are available on this cluster.  They come standard for Anthos on-prem installations.

# namespace should exist
kubectl get ns anthos-identity-service

# deployment of AIS should exist
kubectl get deployment -n anthos-identity-service ais

# view logs for deployment (ok to have 401 and UNAVAILABLE errors right now)
kubectl logs -n anthos-identity-service deployment/ais -c ais-container

Create Cluster RBAC roles

In anticipation of Cluster authorization based on a user’s membership in an Active Directory group, we are going to setup RBAC (Role-based Access Control) for two Active Directory groups:

  • engineers
  • managers

These are groups that we created in a previous article using a Powershell script, we will put in place the following rules:

  • Users in the ‘engineering’ group can get/watch/list pods in any namespace (ClusterRole/ClusterRoleBinding), but cannot interact with any other objects (e.g. deployments, daemonsets, secrets, etc)
  • Users in the ‘managers’ group can get/watch/list pods and secrets but only in the default namespace (Role/RoleBinding), and not in any other namespace
# download roles and bindings from my github project
project_url=https://raw.githubusercontent.com/fabianlee/anthos-nested-esx-manual/main

# ClusterRole for 'engineers' group
wget $project_url/anthos-identity/pod-reader-clusterrole.yaml
wget $project_url/anthos-identity/pod-reader-clusterrolebinding.yaml

# Role for 'managers' group
wget $project_url/anthos-identity/defaultns-reader-role.yaml
wget $project_url/anthos-identity/defaultns-rolebinding.yaml

# apply engineers RBAC at cluster level
kubectl apply -f pod-reader-clusterrole.yaml
kubectl apply -f pod-reader-clusterrolebinding.yaml

# apply managers RBAC at namespace level
kubectl apply -f defaultns-reader-role.yaml
kubectl apply -f defaultns-rolebinding.yaml

This has no effect when logging in use the default kubeconfig with client-key-data for authentication, but in later sections when users are forced to go through LDAP for authentication (they will never be given private certs), their AD group membership will determine what operations are possible.

Create LDAP service account secret

Create a secret in the ‘anthos-identity-service’ namespace that represents the LDAP service account.  Per my comment in the previous section, the binding user must be the fully qualified DN of the LDAP service account (not just id@domain) because that is what Anthos Identity Server demands.

kubectl create -f - << EOF
apiVersion: v1
kind: Secret
metadata:
  name: ldap-secret
  namespace: anthos-identity-service
type: kubernetes.io/basic-auth
stringData:
  username: $binding_user # e.g. CN=xx,CN=Users,DC=x,DC=y
  password: $binding_pass
EOF

Patch ClientConfig object

Load the custom CA pem into a single-lined base64 variable (if Domain Controller CA cert is not public).

ca_base64=$(cat adfsCA.pem | base64 | tr -d '/\n//')

Then create the ClientConfig manifest patch that we can apply to the cluster.

cat <<EOF > clientconfig-patch.yaml
kind: ClientConfig
metadata:
  name: default
  namespace: kube-public
spec:
  authentication:
  - name: ldap
    ldap:
      host: ${ldap_server}:636
      certificateAuthorityData: $ca_base64
      connectionType: ldaps
      serviceAccountSecret:
        name: ldap-secret
        namespace: anthos-identity-service
        type: basic
      user:
        baseDN: CN=Users,$base_DC
        filter: (objectClass=user)
        identifierAttribute: userPrincipalName
        loginAttribute: sAMAccountName
      group:
        baseDN: CN=Users,$base_DC
        filter: (objectClass=group)
        identifierAttribute: cn
EOF

Note that the ldap attribute names are case-sensitive,  e.g. if you see ‘userPrincipalName’ in ADSI Edit of the Domain Controller, then you must use this same case in the yaml above.

Apply this patch to the ClientConfig object of the cluster.

kubectl -n kube-public patch clientconfig/default --type=merge --patch-file clientconfig-patch.yaml

And now the logs should show messages indicating “LDAP[0] started”.

kubectl logs -n anthos-identity-service deployment/ais -c ais-container -f

Validate User access

Now let’s see what this looks like from an end user perspective, i.e. a cluster developer/deployer.

Install the Google Cloud CLI with Anthos components

Follow the instructions on the official docs for installing gcloud and its Anthos components.  On Ubuntu this can be done by adding the google apt repository.

sudo apt install -y apt-transport-https ca-certificates gnupg curl

# add key and repo
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo tee /usr/share/keyrings/cloud.google.asc
chmod 666 /usr/share/keyrings/cloud.google.asc
echo "deb [signed-by=/usr/share/keyrings/cloud.google.asc] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list

# install components
sudo apt update
sudo apt install google-cloud-cli google-cloud-sdk google-cloud-sdk-anthos-auth kubectl -y

# initialize settings
gcloud init

Generate Anthos login config (one time)

Generate the Anthos login config file with gcloud.

# create ldap config file 'kubectl-anthos-config.yaml'
gcloud anthos create-login-config --kubeconfig=$KUBECONFIG

The cluster name used at ‘.spec.name’ will be used later for ‘gcloud anthos auth login’

sudo apt install yq -y
# set variable 'cluster_name' for use later
cluster_name=$(yq eval '.spec.name' kubectl-anthos-config.yaml)
echo "cluster name is $cluster_name"

Move the login config to the expected default location.

mkdir -p  $HOME/.config/google/anthos/
mv kubectl-anthos-config.yaml $HOME/.config/google/anthos/kubectl-anthos-config.yaml

Note: ‘kubectl-anthos-config.yaml’ does not remove the need for a KUBECONFIG by end-users, this config is used to augment their KUBECONFIG.

You can safely remove the ‘group’, ‘serviceAccountSecret’, and ‘user’ sections from this file, these are values populated from the ClientConfig that are not necessary on the client side.

KUBECONFIG before modification

The KUBECONFIG used to generate the ‘kubectl-anthos-config.yaml’ login config above is the full-permission version containing the ‘client-key-data’ that provides immediate privileged access to the cluster.

Note the contexts and users currently contained in the KUBECONFIG.

kubectl config get-contexts
kubectl config get-users
kubectl config get-clusters

These contain the escalated privileges and private client-key-data that we would NOT want to provide end users.  We will remove these in a later section to prepare for end-user distribution.

LDAP authentication using login config

Now we will use the KUBECONFIG coupled with ‘kubectl-anthos-config.yaml’ to initiate the LDAP login flow.  Per our ClientConfig, we are using ‘sAMAccountName’ as the attribute for our user login, so we use the simple name ‘engineer1’ below without a domain suffix.

# no need to specify 'login-config' flag since file is in default location
# cluster name is from anthos login config
$ gcloud anthos auth login --cluster=$cluster_name

Please enter the ldap user for [ldap] on cluster [user1]: engineer1
Please enter the ldap password for [ldap] on cluster [user1]: 
Configuring Anthos authentication 
2022/10/18 10:58:31 LDAP login successful.
Re-login after the token in the kubeconfig expires in 1 hr(s).
Configuring Anthos authentication success.

The KUBECONFIG has now been modified with an additional cluster, context, and user with ‘token’ value.

The last line in the deployment log should indicate “Kubernetes webhook adapter successfully authenticated request”.

$ kubectl logs -n anthos-identity-service deployment/ais -c ais-container -f

KUBECONFIG prepared for end-user distribution

As mentioned above, the ‘gcloud anthos auth login’ from the previous section modified our KUBECONFIG.  There is now an additional cluster, context and user entry for our LDAP access.

echo "cluster name is $cluster_name"

# should now have extra cluster entry '$cluster_name'
kubectl config get-clusters

# should now have extra context '$cluster_name-$cluster_name-anthos-default-user'
kubectl config get-contexts

# should now have extra user with '$cluster_name-anthos-default-user'
kubectl config get-users

But since we do not want the original escalated permissions and client-key-data to be provided to end-users, we can remove these to create a minimal KUBECONFIG that can be safely distributed.

kubectl config delete-context <originalContext>
kubectl config delete-user <originalUser>
kubectl config delete-cluster <originalCluster>

The KUBECONFIG should now only contain the essential information required to access the cluster with LDAP authentication.  Clear your current token value before distribution, it will be regenerated when the end-users do ‘gcloud anthos auth login’.

# minimal, without any escalated privileges or private keys
cat $KUBECONFIG

# clear your token value
sed -i 's/token: .*/token: /' $KUBECONFIG

It is this minimal KUBECONFIG paired with the ‘kubectl-anthos-config.yaml’ that will be distributed to end-user operators/developers/deployers.

Validate ‘engineers’ group access

Remember from the RBAC configuration section above, that the ‘engineers’ group has a ClusterRole to read pods from any namespace.  But it does not have access to any other objects.

# success
$ kubectl get pods -n default

# success
$ kubectl get pods -n kube-system

# informs you that trying to pull deployments from different namespace will fail
$ kubectl auth can-i get deployments -n default
no

# FAILS! because ClusterRole only grants permissions on 'pods'
$ kubectl get deployments -n default
Error from server (Forbidden): deployments.apps is forbidden: User "engineer1@test.local" cannot list resource "deployments" in API group "apps" in the namespace "default"

Validate ‘managers’ group access

Now let’s switch over to user ‘manager1’ which is a member of the ‘managers’ group.  This group has a namespace bound Role that allows it to see pods and secrets, but only in the default namespace.

$ gcloud anthos auth login --cluster=user1
Please enter the ldap user for [ldap] on cluster [user1]: manager1
Please enter the ldap password for [ldap] on cluster [user1]: 
Configuring Anthos authentication 
2022/10/18 12:34:07 LDAP login successful.
Re-login after the token in the kubeconfig expires in 1 hr(s).
Configuring Anthos authentication success.

Let’s test the ‘managers’ RBAC permissions which limit it to the default namespace.

# success
$ kubectl get pods -n default

# success
$ kubectl get secrets -n default

# informs you that trying to pull pods from different namespace will fail
$ kubectl auth can-i get pods -n kube-system
no

# FAILS! because Role only grants permission in default namespace
$ kubectl get pods -n kube-system
Error from server (Forbidden): pods is forbidden: User "manager1@test.local" cannot list resource "pods" in API group "" in the namespace "kube-system"

 

 

REFERENCES

google ref, Anthos Identity Service with LDAP

google ref, setup an LDAP provider for Anthos Identity Service

google ref, setup user access for Anthos Identity Service

google ref, install the Google Cloud CLI with Anthos components

fabianlee.org, using ldapsearch

man page, ldapsearch

google ref, gcloud anthos create-login-config

theitbros.com, using ldapsearch on active directory

NON-GKE REFERENCES

kubernetes.io, points out that CA cert check is strict and CA:true must be set

Pratishtha Agarwal, securely authenticate to gke anthos cluster with okta

docs.bitnami, applying RBAC to K8S cluster to limit namespace access

Mercy Kemei, Active Directory auth for Kubernetes

Mercy Kemei, authenticate K8S dashboard users with Active Directory

NOTES

ldapsearch needs the CA cert in pem format (not binary DER), if conversion is necessary use openssl.

openssl x509 -in myCA.der -inform DER -out myCA.pem