EKS OIDC With Keycloak
This article provides the instruction of configuring Keycloak as OIDC Identity Provider for EKS. The example is written on Terraform (HCL).
We highly recommend configuring the integration through a GitOps approach, utilizing the edp-keycloak-operator and the edp-cluster-add-ons repository. For details, please refer to the AWS EKS OIDC Integration page.
Prerequisitesβ
To follow the instruction, check the following prerequisites:
- terraform 0.14.10
- hashicorp/aws = 4.8.0
- mrparkers/keycloak >= 3.0.0
- hashicorp/kubernetes ~> 2.9.0
- kubectl = 1.22
- kubelogin >= v1.25.1
- Ensure that Keycloak has network availability for AWS (not in a private network).
To connect OIDC with a cluster, install and configure the kubelogin plugin. For Windows, it is recommended to download the kubelogin as a binary and add it to your PATH.
Solution Overviewβ
This architecture encompasses three primary resource types: AWS (EKS), Keycloak, and Kubernetes.
Within this setup, the Keycloak resources, once established, remain static, facilitating the assignment of claims based on user group memberships. This stability contrasts with the dynamic nature of other resources, which may be created, modified, or deleted as necessary.
Of particular importance within the Kubernetes ecosystem are the RoleBindings
and ClusterRoles/Roles
. These elements define a permissions framework, where Roles specify the permissions available, and RoleBindings serve to associate those Roles with specific Keycloak groups. This association ensures that members of a group are granted only the permissions that are pertinent to their role.
Keycloak Configurationβ
To configure Keycloak, follow the steps described below.
-
Create a client:
resource "keycloak_openid_client" "openid_client" {
realm_id = "openshift"
client_id = "kubernetes"
access_type = "CONFIDENTIAL"
standard_flow_enabled = true
implicit_flow_enabled = false
direct_access_grants_enabled = true
service_accounts_enabled = true
oauth2_device_authorization_grant_enabled = true
backchannel_logout_session_required = true
root_url = "http://localhost:8000/"
base_url = "http://localhost:8000/"
admin_url = "http://localhost:8000/"
web_origins = ["*"]
valid_redirect_uris = [
"http://localhost:8000/*"
]
} -
Create the client scope:
resource "keycloak_openid_client_scope" "openid_client_scope" {
realm_id = <realm_id>
name = "groups"
description = "When requested, this scope will map a user's group memberships to a claim"
include_in_token_scope = true
consent_screen_text = false
} -
Add scope to the client by selecting all default client scope:
resource "keycloak_openid_client_default_scopes" "client_default_scopes" {
realm_id = <realm_id>
client_id = keycloak_openid_client.openid_client.id
default_scopes = [
"profile",
"email",
"roles",
"web-origins",
keycloak_openid_client_scope.openid_client_scope.name,
]
} -
Add the following mapper to the client scope:
resource "keycloak_openid_group_membership_protocol_mapper" "group_membership_mapper" {
realm_id = <realm_id>
client_scope_id = keycloak_openid_client_scope.openid_client_scope.id
name = "group-membership-mapper"
add_to_id_token = true
add_to_access_token = true
add_to_userinfo = true
full_path = false
claim_name = "groups"
} -
In the authorization token, get groups membership field with the list of group membership in the realm:
...
"email_verified": false,
"name": "An User",
"groups": [
"<env_prefix_name>-oidc-viewers",
"<env_prefix_name>-oidc-cluster-admins"
],
"preferred_username": "an_user@example.com",
"given_name": "An",
"family_name": "User",
"email": "an_user@example.com"
... -
Create group/groups, e.g. admin group:
resource "keycloak_group" "oidc_tenant_admin" {
realm_id = <realm_id>
name = "kubernetes-oidc-admins"
}
EKS Configurationβ
To configure EKS, follow the steps described below. In AWS Console, open EKS home page -> Choose a cluster -> Configuration tab -> Authentication tab.
The Terraform code for association with Keycloak:
-
terraform.tfvars
...
cluster_identity_providers = {
keycloak = {
client_id = <keycloak_client_id>
identity_provider_config_name = "Keycloak"
issuer_url = "https://<keycloak_url>/auth/realms/<realm_name>"
groups_claim = "groups"
}
... -
the resource code
resource "aws_eks_identity_provider_config" "keycloak" {
for_each = { for k, v in var.cluster_identity_providers : k => v if true }
cluster_name = var.platform_name
oidc {
client_id = each.value.client_id
groups_claim = lookup(each.value, "groups_claim", null)
groups_prefix = lookup(each.value, "groups_prefix", null)
identity_provider_config_name = try(each.value.identity_provider_config_name, each.key)
issuer_url = each.value.issuer_url
required_claims = lookup(each.value, "required_claims", null)
username_claim = lookup(each.value, "username_claim", null)
username_prefix = lookup(each.value, "username_prefix", null)
}
tags = var.tags
}
The resource creation takes around 20-30 minutes. The resource doesn't support updating, so each change will lead to deletion of the old instance and creation of a new instance instead.
Kubernetes Configurationβ
To connect the created Keycloak resources with permissions, it is necessary to create Kubernetes Roles and RoleBindings:
-
ClusterRole
resource "kubernetes_cluster_role_v1" "oidc_tenant_admin" {
metadata {
name = "oidc-admin"
}
rule {
api_groups = ["*"]
resources = ["*"]
verbs = ["*"]
}
} -
ClusterRoleBinding
resource "kubernetes_cluster_role_binding_v1" "oidc_cluster_rb" {
metadata {
name = "oidc-cluster-admin"
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "ClusterRole"
name = kubernetes_cluster_role_v1.oidc_tenant_admin.metadata[0].name
}
subject {
kind = "Group"
name = keycloak_group.oidc_tenant_admin.name
api_group = "rbac.authorization.k8s.io"
# work-around due https://github.com/hashicorp/terraform-provider-kubernetes/issues/710
namespace = ""
}
}
When creating the Keycloak group, ClusterRole, and ClusterRoleBinding, a user receives cluster admin permissions. There is also an option to provide admin permissions just to a particular namespace or another resources set in another namespace. For details, please refer to the Mixing Kubernetes Roles page.
Kubeconfigβ
Template for kubeconfig:
apiVersion: v1
preferences: {}
kind: Config
clusters:
- cluster:
server: https://<eks_url>.eks.amazonaws.com
certificate-authority-data: <certificate_authority_data>
name: <cluster_name>
contexts:
- context:
cluster: <cluster_name>
user: <keycloak_user_email>
name: <cluster_name>
current-context: <cluster_name>
users:
- name: <keycloak_user_email>
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: kubectl
args:
- oidc-login
- get-token
- -v1
- --oidc-issuer-url=https://<keycloak_url>/auth/realms/<realm>
- --oidc-client-id=<keycloak_client_id>
- --oidc-client-secret=<keycloak_client_secret>
Flag -v1
can be used for debug, in a common case it's not needed and can be deleted.
To find the client secret:
- Open Keycloak
- Choose realm
- Find keycloak_client_id that was previously created
- Open Credentials tab
- Copy Secret
Testingβ
Before testing, ensure that a user is a member of the correct Keycloak group. To add a user to a Keycloak group:
- Open Keycloak
- Choose realm
- Open user screen with search field
- Find a user and open the configuration
- Open Groups tab
- In Available Groups, choose an appropriate group
- Click the Join button
- The group should appear in the Group Membership list
Follow the steps below to test the configuration:
-
Run kubectl command, it is important to specify the correct kubeconfig:
KUBECONFIG=<path_to_oidc_kubeconfig> kubectl get ingresses -n <namespace_name>
-
After the first run and redirection to the Keycloak login page, log in using credentials (login:password) or using SSO Provider. In case of the successful login, you will receive the following notification that can be closed:
-
As the result, a respective response from the Kubernetes will appear in the console in case a user is configured correctly and is a member of the correct group and Roles/RoleBindings.
-
If something is not set up correctly, the following output error will be displayed:
Error from server (Forbidden): ingresses.networking.k8s.io is forbidden:
User "https://<keycloak_url>/auth/realms/<realm>#<keycloak_user_id>"
cannot list resource "ingresses" in API group "networking.k8s.io" in the namespace "<namespace_name>"
Session Updateβ
To update the session, clear cache. The default location for the login cache:
rm -rf ~/.kube/cache
Access Cluster via Lensβ
To access the Kubernetes cluster via Lens, follow the steps below to configure it:
- Add a new kubeconfig to the location where Lens has access. The default location of the kubeconfig is ~/.kube/config but it can be changed by navigating to File -> Preferences -> Kubernetes -> Kubeconfig Syncs;
- (Optional) Using Windows, it is recommended to reboot the system after adding a new kubeconfig;
- Authenticate on the Keycloak login page to be able to access the cluster.
Lens does not add namespaces of the project automatically, so it is necessary to add them manually, simply go to Settings -> Namespaces and add the namespaces of a project.