Web Application Authentication and Authorization with Keycloak and OAuth2 Proxy on Kubernetes with Nginx Ingress (2023)

In this post, we are going to set up a generic solution that will allow us to add Keycloak authentication to any application simply by adding an Ingress annotation. This gives us a much more extensible and secure alternative to simple authentication.

Many third-party applications we run on Kubernetes already support OIDC or LDAP-based login. However, some do not. Also, we may want to implement our own applications and use Keycloak to manage access to them without adding the work of an OIDC or LDAP integration to them.

We use the OAuth2 proxy to add authentication to a simple Nginx container.

Next, we'll look at how the app to be authenticated can access and decrypt the Keycloak JSON web token and use it for things like group-based authorization.

In its simplest form, we would use it to secure internally managed applications. In a more complete setup, we could create a "clients" area within Keycloak and delegate all of our authentication and authorization to Keycloak.

for that we useProxy OAuth2This is thesuggested replacementfor the Keycloaks Gatekeeper/Louketo project, which hit EOL in August 2020.

  1. content and summary
  2. Instalar OpenLDAP
  3. Instalar Keycloak
  4. Linking Keycloak and OpenLDAP
  5. OIDC kubectl login com keycloak
  6. Authenticate any web application with Ingress annotations
  7. Gitea (requires LDAP)
  8. Simple Docker registration
  9. Harbor Docker Registry con ACL

requirements

This assumes that you have CLI access to a Kubernetes cluster and are working in a namespace calledidentityand have Helm 3 and Kubectl installed and running locally. Finally, you are supposed to useNGINX for inputnamed along with the certificate manager for SSL certificates on a cluster issuerLetencrypt-Production.

If your setup is different, most of the steps will be the same, but you'll need to change the input annotations accordingly.

The source for this tutorial series can be found here:https://github.com/TalkingQuickly/kubernetes-sso-guideand cloned with:

git-clon[Email protected]:TalkingQuickly/kubernetes-sso-guide.git

All the commands in the tutorial assume that they are run from the root of this cloned repository.

This post assumes that you have completed the Keycloak Installation section and have a working Keycloak installation.

Keycloak authentication to an Nginx server

First, we'll configure the OAuth2 proxy to work with our Keycloak installation and implement it using a Helm chart.

then we put themofficial Nginx containerimage using a Helm chart as a sample app, and then restrict access to it via Keycloak using Ingress annotations.

Next, we'll look at how the app we're authenticating can access information about the logged-in user, and how that information can be used to implement more granular access control.

How it works

Nginx supports authenticationbased on the result of a subquery🇧🇷 This means that when a request for a protected page arrives, a sub-request for an additional URL is made. If that URL returns a 2xx response code, the request is allowed if it returns 401 or 403 denied.

In practice, we don't need a deep understanding of the above, since OAuth2 Proxy interacts with Keycloak for actual authentication, on the one hand, it provides proper endpoints for NGinx to check whether a user is authenticated or not.

So all we have to do is configure the OAuth2 proxy and add the appropriate Ingress annotations to the service we want to protect.

Configure OAuth2 proxy

First, we need to create a client application using Keycloak. Create a new OpenID Connection application and define the following:

  • Customer identification:oauth2-proxy
  • type of access:confidential
  • valid redirect urls::https://oauth.ssotest.staging.talkingquickly.co.uk/oauth2/callbacksubstituteoauth.ssotest.staging.talkingquickly.co.ukwith the subdomain where you want to install the OAuth2 proxy

Then you need to save the entry and go to the newly available "credentials" tab and write the "secret".

Finally, we go to the "Mappers" tab, select "Create" and select:

  • Name:The group
  • type of mapper:group membership
  • Nome des Tokenanspruchs:The group
  • All other options "On"

And then select Save. This ensures that the groups the user is a member of are passed back to the OAuth2 proxy and then to the application itself.

Although the OAuth2 proxy has a "Keycloak" provider, we use the generic OIDC provider. This is a more general solution and some additional features that the Keycloak provider lacks, in particular, automatic cookie updating. There is an ongoing discussion in the OAuth2 proxy team to change the keycloak provider to use the OIDC provider.

We can then create our configuration for the OAuth2 Proxy, an example is included inoauth2-proxy/valores-oauth2-proxy.ymland it looks like this:

@TODO update for OIDC providers @TODO update for buffer sizes

# Peculiarities of the OAuth client configurationsettings: Customer identification: "oauth2-proxy" client secret: "YOUR SECRET" # Create a new secret with the following command # abre ssl rand -base64 32 | head c 32 | base64 cookie secret: "TU_COOKIE_SECRET" configuration file: |- vendor="oidc" provider_display_name = "Key Layer" oidc_issuer_url = "TU_ISSIDER" domain_mail = [ "*" ] scope="openid-Perfil-E-Mail" cookie_domain = ".ssotest.staging.talkingquickly.co.uk" whitelist_domains = ".ssotest.staging.talkingquickly.co.uk" pass_authorization_header = wahr pass_access_token = verdadero pass_user_headers = wahr set_authorization_header = wahr set_xauthrequest = true cookie_refresh = "1m" cookie_expire = "30m"penetration: activated: real Largo: / host: - oauth.ssotest.staging.talkingquickly.co.uk Comments: cert-manager.io/cluster-emisor: Letencrypt-Production nginx.ingress.kubernetes.io/proxy-buffer-size: "16k" tls: - secret name: oauth-proxy-TLS host: - oauth.ssotest.staging.talkingquickly.co.uk

The key fields that need to be updated with your own values ​​are:

  • client secret: This is the client secret noted on the Keycloak credentials page
  • cookie secret: This can be generated randomly with:abre ssl rand -base64 32 | head c 32 | base64
  • Check inurl, redeemurl, validate_url: which should be updated to match the relevant URLs for the Keycloak installation and domain (in the example above, I'm using the master domain)
  • crackerdomain, whitelistDomain: which should be updated to match the base URL from which you are serving services. For example, in this example setup, I havesso.ssotest.staging.talkingquickly.co.uk,someapp.ssotest.staging.talkingquickly.co.uk,oauth.ssotest.staging.talkingquickly.co.uketc. and it would also be my base domain.ssotest.staging.talkingquickly.co.uk.
  • ingress hosts: This should be the subdomain where you want to implement the OAuth2 proxy

defining thecookie_domainmiwhitelist_domainThis is important because, by default, the OAuth2 proxy is configured to work only with the subdomain in which it is deployed. Therefore, cookies are specific to this subdomain and redirects are only allowed for this subdomain.

Thatscope="openid-Perfil-E-Mail"The line is important because the OAuth2 proxy requests a named domain by default.APIwhich does not exist in Keycloak, resulting in a 403 Invalid Scopes error.

Thatset_authorization_headerThe line ensures that the JWT is returned to the NGinx input. This is important because we can return this header to the authenticator app so it can access more information about the logged in user.

Finally, thatnginx.ingress.kubernetes.io/proxy-buffer-size: "16k"avoids an issue where large headers typically passed with OAuth requests do not exceed the buffer size in NGinx, leading to cookie detection errorsEid2"Missing" proxy and "Upstream sent headers too large when reading upstream response header."

Instalar proxy OAuth2

while we wait for youProxy OAuth2chart to get a new home after deprecating the old stable helm repository, the latest version is reflected in the sample code for this tutorial, so we can install OAuth 2 Proxy with:

rudder update --install oauth2-proxy ./charts/oauth2-proxy --values ​​oauth2-proxy/values-oauth2-proxy.yml

Then we can go to the login domain we chose for the OAuth2 proxy and we will see the "Login with Keycloak" option.

Note that if we're still logged in as an admin user (rather than a normal user in the scope we configured the OAuth2 proxy with), we'll see something like 403 Permission denied, invalid account. Incognito/private browsing windows are useful to avoid this.

After successfully logging into Keycloak, we are simply redirected to a 404 "Page Not Found" error because there is nothing to authenticate to at the moment. In practice, we will never go directly to this URL, instead the authentication flow will be triggered automatically when visiting a secure application. Visiting this URL and logging in this way is just to demonstrate that it works.

Put an application behind authentication

Now that we've configured the OAuth2 proxy, we can install a sample application and note the Ingress definition to secure it behind the authentication.

In this example, we simply install an NGINX instance that provides the default value "Welcome to nginx!". site, but requires users to log in with Keycloak before accessing it. Note that this is completely separate fromNGINX-Incomethat we use for Kubernetes.

We'll be using Bitnami Nginx's rudder graph for this, so we first need to add the repository with:

Aggregate Helm Repo Bitnami https://charts.bitnami.com/bitnami

Next, we configure our NGINX demo application according to the following pattern:

server block: | log_format withauthheaders '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" "$http_x_auth_request_access_token"'; add_header x-auth-request-access-token "$http_x_auth_request_access_token"; # Servidor HTTP server { # Port to listen, can also be set to IP:PORT format listen 8080; Include "/opt/bitnami/nginx/conf/bitnami/*.conf"; Location/State { stub_status an; access_logout; allow 127.0.0.1; deny everything; } access_log /dev/stdout with authentication headers; }penetration: activated: real host name: nginx-demo-app2.ssotest.staging.talkingquickly.co.uk tls: real Comments: cert-manager.io/cluster-emisor: letsencrypt-staging nginx.ingress.kubernetes.io/auth-url: "https://oauth.ssotest.staging.talkingquickly.co.uk/oauth2/auth" nginx.ingress.kubernetes.io/auth-signin: "https://oauth.ssotest.staging.talkingquickly.co.uk/oauth2/start?rd=$scheme://$best_http_host$request_uri" nginx.ingress.kubernetes.io/auth-response-headers: "x-auth-request-user, x-auth-solicitud-e-mail, x-auth-request-access-token" acme.cert-manager.io/http01-editar-in-place: "real" nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"Service: Writes: cluster IP

The needserver blockit has nothing to do with the actual authentication process. Instead, it does the following two things to make it easier to use NGINX as a demonstration of the authentication function:

  • Change the registry so that thex-auth-request-access tokenThe header is included in the log output, this allows us to view the logs and extract tokens for analysis and testing.
  • Automatically attach thex-auth-request-access tokenHeaders from the incoming request to the final user response so we can inspect it in the browser

Note that issuing access tokens for private registries is a security risk and should never be done in production.

The lines related to authentication are the following:

penetration: Comments: nginx.ingress.kubernetes.io/auth-url: "https://oauth.ssotest.staging.talkingquickly.co.uk/oauth2/auth" nginx.ingress.kubernetes.io/auth-signin: "https://oauth.ssotest.staging.talkingquickly.co.uk/oauth2/start?rd=$scheme://$best_http_host$request_uri" nginx.ingress.kubernetes.io/auth-response-headers: "x-auth-request-user, x-auth-solicitud-e-mail, x-auth-request-access-token" acme.cert-manager.io/http01-editar-in-place: "real" nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"

we includeacme.cert-manager.io/http01-edit-in-place: "true"to fix a problem with the Certificate Manager and setting authentication response headers. we usenginx.ingress.kubernetes.io/proxy-buffer-size: "16k"to avoid the same buffer size issue with OAuth headers that we described when installing the OAuth 2 Proxy.

The first center line isnginx.ingress.kubernetes.io/auth-urlA that specifies the URL to use to verify that the current user is authenticated.

When a request arrives, NGINX authentication first sends the request to this URL, note that it doesn't send the request body, just the headers, mainly the cookies associated with the request.

The service at this URL (in our case OAuth2 Proxy) is responsible for validating whether the user is authenticated based on existing cookies or headers.

If the user is authenticated, the service returns a 2xx status code and the request is forwarded to our application. If you are not authenticated, you will be redirected to the URL specified innginx.ingress.kubernetes.io/auth-signinto start the authentication flow.

Because of this, we had to explicitly set the domain of the OAuth2 proxy cookie as the base domain so that the cookie would be available on all subdomains we wanted to authenticate from.

Whyset_authorization_header = wahrin our configuration, when a request is authenticated, the OAuth2 proxy sets thex-auth-request-access tokenHeaders in the 2xx response that you send to NGINX to contain the authentication token, in this case a JWT containing information about the user and their session.

By default, our original app has no way to access this token, and if we want our app to know which user is logging in or which groups they belong to, it needs this information.

To get around this, the annotationnginx.ingress.kubernetes.io/auth-response-headers: "x-auth-request-user, x-auth-request-email, x-auth-request-access-token"tells NGINX Ingress to take the enumerated headers from the returned 2xx response and add them to the response that goes to the backend application.

Our backend application can take this header and decrypt the JWT to get information about the user.

For this simple example, we just dump it (uncertain) into the logs and add it to the response sent to the user. So now if we go to our login URL for our nginx demo app, in the example https://nginx-demo-app2.ssotest.staging.talkingquickly.co.uk , we are prompted to login and then redirected to "Welcome to nginx!" Side.

We can then verify the request via the "Network" tab in our browser and verify that thex-auth-request-access tokenis placed in the response.

Let's copy the value of this header into a decoder like the one inhttps://jwt.io/we see something like:

{... "reach": "openid email profile", "Email verified": INCORRECT, "Name": "Ben Dixon", "The group": [ "/DockerRegistry", "/Kubernetes Admins", "/Administrators" ], "Preferred Username": "talk fast", "First name": "ben", "Family Name": "Dixon", "Email": "[Email protected]"}

Which in a more complex system could be used by our back-end application to show the user different content based on group membership or surface profile information.

expiration of the token

We effectively have two layers of authentication in place. When a request is authenticated for the first time, the OAuth2 proxy communicates with Keycloak and obtains an access token. When future requests arrive, the request will not be re-authenticated with Keycloak as long as the OAuth2 proxy cookie is present and valid.

This poses a problem when working with JSON web tokens, as they are often issued with an expiration date (by default, it's 1 minute in Keycloak). This leads to a situation where the OAuth2 proxy considers the user to be authenticated, but the JSON web token is stored in thex-auth-request-access tokenThe header has expired. If we were to validate this token against our favorite library, we would get an exception that the token is invalid.

The solution for this is in the following part of the OAuth 2 Proxy configuration file:

cookie_refresh = "1m"cookie_expire = "30m"

The first partcookie_refresh, tells the OAuth2 proxy to refresh the access token if the OAuth2 proxy cookie is not refreshed for at least one minute. This aligns with the token expiration defined in Keycloak and prevents us from adding stale access tokens to requests. Note that the reason for using the generic OIDC provider in the OAuth2 proxy instead of the specific "Keycloak" provider is that the "Keycloak" provider does not support refresh tokens (at the time of writing).

the second partcookie_expiretells the OAuth 2 proxy to expire the cookie if it is older than 30 minutes. The user is then redirected to the KeyCloak to re-authenticate. Again, this aligns with the default session flow in Keycloak.

Restricting access to specific groups

It is possible to roughly restrict login to users in specific groups by adding:

allow_groups = ["/DemoAdmin"]

For himconfiguration fileLock in OAuth2 proxy settings. This would mean that access would only be allowed if the logged in user is in theDemo ManagerKeycloak Group. It's worth noting that the user experience with this approach is very poor at the time of writing, with the user trying to log in simply seeing a 500 Internal Server error instead of an informational error message. If we look at the NGinx input logs, we'll see something likeUnexpected authentication request status: 400 when sending to clientThis is because the OAuth2 proxy returns a 400 response when the user logs in but is not in one of the allowed groups.

So while this approach is appropriate for simple internal applications, handling group membership in the authenticated application allows for a more user-friendly experience.

Working with the Token

The filejwt-ruby-example/main.rbcontains a simple example of how we can work with this token in a Ruby application. The code itself is very simple:

demand 'jwt'public_key_string = """PUBLIC_KEY_GOES_HERE"""public key = OpenSSL::Clave::RSA.nuevo(public_key_string)Plate = "TOKEN_GOES_HERE"token_decoded = JWT.decode Plate, public key, real, { algorithm: 'RS256' }place token_decoded

Here we replacePUBLIC_KEY_GOES_HEREwith the public key, which you can find by going to "Realm Settings" on our Keycloak domain, then "Keys" and selecting "Public Key" for theRS256Forbidden.

then we replaceTOKEN_GOES_HEREwith a token that we copy from our sample app headers or logs and run the script with itmain ruby.rb(After runninginstallation packageetc).

Note that Keycloak-issued tokens have an expiration time of 1 minute by default, so you'll need to quickly copy and paste them into this script.

The output is the token decoded as a Ruby map. So, in a full web application (for example, a Rails or Sinatra application) we can make decisions based on the groups the user belongs to or show them their currently logged in email address.

  1. content and summary
  2. Instalar OpenLDAP
  3. Instalar Keycloak
  4. Linking Keycloak and OpenLDAP
  5. OIDC kubectl login com keycloak
  6. Authenticate any web application with Ingress annotations
  7. Gitea (requires LDAP)
  8. Simple Docker registration
  9. Harbor Docker Registry con ACL

FAQs

What is Nginx ingress Kubernetes IO Auth URL? ›

nginx.ingress.kubernetes.io/auth-url: "url to auth service" This annotation tells the nginx-ingress controller to forward the incoming request first to the auth-service, and then if the auth-service responds with an 200 Ok then on to the downstream route. For example: apiVersion: extensions/v1beta1. kind: Ingress.

What does Kubernetes use to connect OAuth2? ›

Kubernetes uses client certificates, bearer tokens, or an authenticating proxy to authenticate API requests through authentication plugins.

Is Keycloak an OAuth2 server? ›

Keycloak is Open Source Identity and Access Management Server, which is a OAuth2 and OpenID Connect(OIDC) protocol complaint.

What is OAuth2 proxy used for? ›

OAuth2 Proxy is a reverse proxy and static file server that provides authentication using third-party providers like Google, GitHub, and others for validating accounts by email, domain, or group.

Is Kubernetes Ingress a proxy? ›

An ingress controller acts as a reverse proxy and load balancer. It implements a Kubernetes Ingress. The ingress controller adds a layer of abstraction to traffic routing, accepting traffic from outside the Kubernetes platform and load balancing it to Pods running inside the platform.

What is difference between nginx and ingress? ›

Ingress controller get the packet, checks ingress rules and determines to which service to deliver the packet. Nginx ingress controller uses LoadBalancer type service actually as entrypoint to the cluster. Then is checks ingress rules and distributes the load.

Can I use NGINX with Kubernetes? ›

NGINX is the most popularly used ingress controller for Kubernetes clusters. NGINX has most of the features enterprises are looking for, and will work as an ingress controller for Kubernetes regardless of which cloud, virtualization platform, or Linux operating system your Kubernetes cluster is running on.

What is the difference between OAuth and oauth2? ›

OAuth 1.0 needs to generate a signature on every API call to the server resource and that should be matched with the signature generated at the receiving endpoint in order to have access for the client. OAuth 2.0 do not need to generate signatures. It uses TLS/SSL (HTTPS) for communication.

Should I use OAuth or oauth2? ›

OAuth 2.0 is much more usable, but much more difficult to build securely. Much more flexible. OAuth 1.0 only handled web workflows, but OAuth 2.0 considers non-web clients as well. Better separation of duties.

Does Keycloak use nginx? ›

Keycloak service is by default available on the path /auth. Thus we need to instruct Nginx that traffic coming to the endpoint /auth should be redirected to the Keycloak server and all the other traffic should be served with the static content. To achieve this, nginx.

What is the difference between authentication and authorization Keycloak? ›

Simply stated, authentication means who you are, while authorization means what can you do, with each approach using separate methods for validation. For example, authentication uses the user management and login form, and authorization uses role-based access control (RBAC) or an access control list (ACL).

Is Keycloak authentication or authorization? ›

Keycloak With OpenID Connect (OIDC)

OIDC is an authentication protocol that is an extension of OAuth 2.0. OAuth 3.0 is only a framework for building authorisation protocols, but OIDC is a full-fledged authentication and authorisation protocol.

What is ingress URL in Kubernetes? ›

Kubernetes Ingress is an API object that provides routing rules to manage access to the services within a Kubernetes cluster. This typically uses HTTPS and HTTP protocols to facilitate the routing. Ingress is the ideal choice for a production environment.

What is OAuth authorization URL? ›

The OAuth 2.0 authorization framework is a protocol that allows a user to grant a third-party web site or application access to the user's protected resources, without necessarily revealing their long-term credentials or even their identity.

What is the auth URL? ›

The auth-url command specifies the URL to the endpoint that authenticates user credentials. User credentials in the authorization header are validated by the endpoint that is specified in the URL. When the user is authenticated, DataPower® expects the authentication URL to return an HTTP 200 OK response status code.

How do I find my Kubernetes URL? ›

You have two ways to access it from your desktop:
  1. Create a nodeport type service and then access it via nodeip:nodeport.
  2. Use Kubectl port forward and then access it via localhost:forwardedport.
Jan 2, 2020

References

Top Articles
Latest Posts
Article information

Author: Duncan Muller

Last Updated: 11/03/2023

Views: 6404

Rating: 4.9 / 5 (59 voted)

Reviews: 82% of readers found this page helpful

Author information

Name: Duncan Muller

Birthday: 1997-01-13

Address: Apt. 505 914 Phillip Crossroad, O'Konborough, NV 62411

Phone: +8555305800947

Job: Construction Agent

Hobby: Shopping, Table tennis, Snowboarding, Rafting, Motor sports, Homebrewing, Taxidermy

Introduction: My name is Duncan Muller, I am a enchanting, good, gentle, modern, tasty, nice, elegant person who loves writing and wants to share my knowledge and understanding with you.