Microsoft Azure/Entra SSO + AWS EKS + Oauth2-Proxy with Kubernetes-Dashboard
My goal was to deploy the Kubernetes Dashboard in a managed Kubernetes cluster with AWS EKS. The dashboard is secured via single sign-on via Microsoft Entra in combination with the OAuth2 Proxy and NGINX Ingress controller.
In the following I will show the those steps:
- Microsoft Entra OAuth2 Application with terraform
- OAuth2 Proxy setup with Microsoft Entra
- Kubernetes Dashboard configured with authorization header for authenticating users
- AWS EKS with Entra as OIDC provider because the Kubernetes Dashboard uses the Kubernetes API for authorization
- Troubleshooting
Microsoft Entra OAuth 2.0 Application
The application will have two roles, one for Support users that can only manage deployments in some namespaces via the Kubernetes Dashboard and Admins who can access all deployments/namespaces. Only users that have one of those roles are allowed to access the dashboard via Oauth2 Proxy, we will configure that later in the Oauth2 Proxy config.
The application roles are assigned to existing entra groups to control which entra users are allowed to access the dashboard.
How to setup an Entra application for OAuth2 Proxy via the GUI is very well documented.
I will show how those steps look like as Infrastructure as code (IaC) in terraform code.
locals {
# permission ids taken from: https://learn.microsoft.com/en-us/graph/permissions-reference
msgraph_user_read_permission_id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"
msgraph_openid_profile_permission_id = "14dad69e-099b-42c9-810b-d002981feec1"
msgraph_openid_permission_id = "37f7f235-527c-4136-accd-4a02d197296e"
msgraph_openid_email_permission_id = "64a6cdd6-aab1-4aaf-94b8-3cc8405e90d0"
}
# Used to get ID of predefined Graph API permissions
resource "azuread_service_principal" "msgraph" {
client_id = data.azuread_application_published_app_ids.well_known.result.MicrosoftGraph
use_existing = true
}
resource "random_uuid" "aws_oauth_proxy_support_role" {
}
resource "random_uuid" "aws_oauth_proxy_admin_role" {
}
resource "azuread_application" "aws_oauth2_proxy" {
display_name = "Oauth2 Proxy"
# required permissions for Oauth2-Proxy
required_resource_access {
resource_app_id = data.azuread_application_published_app_ids.well_known.result.MicrosoftGraph
resource_access {
id = local.msgraph_openid_email_permission_id
type = "Scope"
}
resource_access {
id = local.msgraph_openid_permission_id
type = "Scope"
}
resource_access {
id = local.msgraph_openid_profile_permission_id
type = "Scope"
}
resource_access {
id = local.msgraph_user_read_permission_id
type = "Scope"
}
resource_access {
id = azuread_service_principal.msgraph.app_role_ids["Group.Read.All"]
type = "Scope"
}
}
# fill oauth2 groups claim with ids of security and microsoft 365 groups
group_membership_claims = ["All"]
optional_claims {
id_token {
additional_properties = []
essential = false
name = "groups"
}
}
# application roles, will be in the oauth2 roles claim and will be assigned to entra groups to control which users can access the dashboard
app_role {
allowed_member_types = ["User"]
description = "Can access the kubernetes-dashboard"
display_name = "Support"
id = random_uuid.aws_oauth_proxy_cloud_support_role.id
enabled = true
value = "Support"
}
app_role {
allowed_member_types = ["User"]
description = "Can access the kubernetes-dashboard"
display_name = "Admin"
id = random_uuid.aws_oauth_proxy_admin_role.id
enabled = true
value = "Admin"
}
# temporary used oauthdebugger.com for troubleshooting
web {
redirect_uris = [
"https://your-oauth2-domain/oauth2/callback",
# "https://oauthdebugger.com/debug"
]
}
# To assign entra users/groups via the GUI
feature_tags {
enterprise = true
}
# Not needed for Oauth2-Proxy but for the AWS EKS Identity Provider we will connect later
fallback_public_client_enabled = true
}
# Applicaton role to entra group mapping
resource "azuread_app_role_assignment" "oauth2_proxy_admin" {
app_role_id = random_uuid.aws_oauth_proxy_admin_role.id
principal_object_id = azuread_group.admin.object_id
resource_object_id = azuread_service_principal.oauth2_proxy.object_id
}
# Applicaton role to entra group mapping
resource "azuread_app_role_assignment" "oauth2_proxy_support" {
app_role_id = random_uuid.aws_oauth_proxy_support_role.id
principal_object_id = azuread_group.support.object_id
resource_object_id = azuread_service_principal.oauth2_proxy.object_id
}
#
resource "azuread_service_principal" "oauth2_proxy" {
client_id = azuread_application.aws_oauth2_proxy.client_id
preferred_single_sign_on_mode = "oidc"
owners = [data.azuread_client_config.current.object_id]
}
# to grant admin consent on the necessary permissions we declared in the resources azuread_application.aws_oauth2_proxy required_permissions_block
resource "azuread_service_principal_delegated_permission_grant" "oauth2_proxy_admin_consent" {
service_principal_object_id = azuread_service_principal.oauth2_proxy.object_id
resource_service_principal_object_id = azuread_service_principal.msgraph.object_id
claim_values = ["openid", "profile", "email", "Group.Read.All", "User.Read"]
}
# client secret
resource "azuread_application_password" "aws_oauth2_proxy_client_secret" {
application_id = azuread_application.aws_oauth2_proxy.id
display_name = "OAuth2 Proxy"
end_date = "2026-03-28T06:00:00Z"
}
# For convenience to extract the secret via terraform output
output "aws_oauth_proxy_client_secret" {
value = azuread_application_password.aws_oauth2_proxy_client_secret.value
sensitive = true
}
output "aws_oauth_proxy_client_id" {
value = azuread_application.aws_oauth2_proxy.client_id
}
Having configured the entra side, we continue to configure the Oauth2 Proxy to use this application.
Troubleshooting
If you want to see the OAuth2 response of your entra application you can use https://oauthdebugger.com/ To make it work, add https://oauthdebugger.com/debug as redirect url to your application.
- Authorize URI (required):
Entra -> Applications -> App registrations -> Your Oauth2 application -> Overview -> Endpoints
and copy theOAuth 2.0 authorization endpoint (v2)
URL e.g. https://login.microsoftonline.com/#application-id#/oauth2/v2.0/authorize - Scope:
openid email profile
- Response type: Code
After sending the request a POST Request template will be shown to you. Just replace the clientSecret
placeholder with your real secret of the entra application (terraform output aws_oauth_proxy_client_secret
)
The Post request will look like this:
POST https://login.microsoftonline.com/xxx/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&
code=XXX&
client_id=XXX&
client_secret={clientSecret}&
redirect_uri=https%3A%2F%2Foauthdebugger.com%2Fdebug
You can copy this code and execute it directly e.g. in a http File with IntelliJ
The result is a reponse with the access_token / id_token. To decode those two tokens und can use the jwt debugger. In your id token you should see the configured roles/groups claim.
OAuth2 Proxy
I used OAuth2 Proxy in Version 7.5.1, deployed as Helm Chart in Kubernetes.
Here is my OAuth2 Proxy configuration file for the Chart:
extraArgs:
provider: azure
set-authorization-header: true
provider-display-name: SSO Login
client-id: {{ .Values.entra.oauth2Proxy.clientId }} # Entra Application (Client) ID
client-secret: {{ .Values.entra.oauth2Proxy.clientSecret }} # Entra app registration client secret from terraform output
oidc-issuer-url: "https://login.microsoftonline.com/{{ .Values.entra.tenantId }}/v2.0"
user-id-claim: oid
oidc-groups-claim: roles # i used roles instead of groups because the groups are just IDs
allowed-group: "Support,Admin" # mapped as app roles in entra, see Step [Microsoft Entra OAuth 2.0 Application](#microsoft-entra-application)
azure-tenant: {{ .Values.entra.tenantId }} # Entra Tenant ID
whitelist-domain: "{{ .Values.entra.oauth2Proxy.cookieDomain }},login.microsoftonline.com"
cookie-domain: {{ .Values.entra.oauth2Proxy.cookieDomain }}
scope: "openid profile email"
ingress:
enabled: true
path: /
hosts:
- {{ .Values.entra.oauth2Proxy.host }}
Any user/group that has assigned one of the groups in allowed-group
should be able to login. Users that are not part of one of those groups will receive a 403 forbidden status.
Important: The flag set-authorization-header
(Docs: set Authorization Bearer response header (useful in Nginx auth_request mode)) is necessary to pass the oauth2 token via header to the Kubernetes Dashboard.
Troubleshooting
To seet the information returned by OAuth2 Proxy you can use the /oauth2/auth
endpoint. Open your Browser developer tools and go to https://your-oauth2-proxy/oauth2/auth - In the Network-Tab you will see the returned token which you can decode with jwt debugger.
Kubernetes Dashboard
After setting up OAuth2 Proxy, we can provide authentication for the Kubernetes-Dashboard and we will use Header Authorization. The authorization token is passed to the Dashboard. The Dashboard was installed via Helm Chart in version v2.7.0.
This is my configuration file for the Chart, the important part is the ingress configuration
ingress:
enabled: true
hosts:
- {{ .Values.monitoring.kubernetesDashboardIngressHost }}
className: nginx
annotations:
nginx.ingress.kubernetes.io/proxy-buffering: "on"
nginx.ingress.kubernetes.io/proxy-buffer-size: "1M"
nginx.ingress.kubernetes.io/auth-signin: "https://{{ .Values.entra.oauth2Proxy.host }}/oauth2/start"
nginx.ingress.kubernetes.io/auth-url: "https://{{ .Values.entra.oauth2Proxy.host }}/oauth2/auth"
nginx.ingress.kubernetes.io/configuration-snippet: |
auth_request_set $token $upstream_http_authorization;
proxy_set_header Authorization $token;
# debug "Header: $token"; not necessary, could be used to show the token in your browsers developer tools
extraArgs:
- --enable-insecure-login=true {{/*SSL Termination at ingress*/}}
Important: The annotation configuration-snippet
makes the magic happen and sets the authorization header. auth-signin
/ auth-url
are necessary to protect the dashboard with OAuth2 Proxy.
If you open the dashboard now, you would not see any information about your cluster. We still have to implement the following two points:
- Roles and permissions with Kubernetes RBAC
- The dashboard just passes the authorization token to the Kubernetes API server and we have not properly configured our cluster yet to accept these tokens.
The following example shows the use case: All users who are authenticated can see all namespaces so that they are available for selection in the dashboard. In addition, users of a specific Entra group can manage deployments in a namespace.
Authenticated users can see all Namespaces, therefore we use the Cluster wide resources ClusterRole / ClusterRoleBinding
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: read-namespaces
rules:
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- list
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: dasbhoard-read-namespaces
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: read-namespaces
subjects:
- kind: Group
name: "system:authenticated"
apiGroup: rbac.authorization.k8s.io
And to limit the management of deployments/pods to a Namespace, we use a RoleBinding
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: kubernetes-dashboard-manage-deployments
rules:
- apiGroups:
- ""
resources:
- pods
- pods/log
verbs:
- get
- list
- apiGroups:
- ""
resources:
- events
verbs:
- get
- list
- apiGroups:
- "apps"
resources:
- deployments
verbs:
- get
- list
- update
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: dasbhoard-manage-deployments
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kubernetes-dashboard-manage-deployments
subjects:
- kind: Group
name: "entra-group:id-of-your-entra-group-which-should-see-all-deployments"
apiGroup: rbac.authorization.k8s.io
Important: In RoleBinding dasbhoard-manage-deployments
i refer to an entra group (name: "entra-group:id-of-your-entra-group-which-should-see-all
) with the prefix entra-group
. The prefix is attached by the EKS OIDC provider we will set up in the next part. You could also refer to single users:
subjects:
- kind: User
name: "entra-email:entra-user-principal-name"
apiGroup: rbac.authorization.k8s.io
EKS OIDC Provider
The configuration of the EKS OIDC Provider is very simple:
resource "aws_eks_identity_provider_config" "provider" {
cluster_name = "your-eks-cluster-name"
oidc {
client_id = "xxxx" # Entra application ID
identity_provider_config_name = "Entra SSO"
issuer_url = "https://login.microsoftonline.com/<your-tenant-id>/v2.0"
username_claim = "email"
username_prefix = "entra-email:"
groups_claim = "roles"
groups_prefix = "entra-role:"
}
}
After applying the terraform code, everything is ready. You can now log into the Kubernetes-Dashboard via SSO and restrict access of who can login via OAuth2-Proxy and what each users can see in the Dashboard via RBAC.