Easy Auth on AKS with Ambassador

Azure Application Service has a feature often referred as “Easy Auth” (see Authentication and authorization in Azure App Service and Azure Functions), which essentially consists of enabling user authentication for an application that does not support it.

Many users love this feature, since it allows enjoying enterprise-grade authentication without writing a single line of code, just by ticking a check box. So it is not surprising that many users ask how to do something similar in Kubernetes. There are probably different ways of doing this, but I am going to present you my favorite: using Ambassador.

Ambassador is an API Gateway for Kubernetes, as they put it in their web page. It offers functionality in a way similar to an ingress controller, but much more. One of the options it offers is offloading authentication to an Identity Provider such as Azure Active Directory, and that is what we are going to try here.

The first thing you need is a Kubernetes cluster, I am not going to show you here how to create one. I am going to use an AKS cluster for this with no special features. After that, you can install Ambassador using the instructions here. For example:

# Install Ambassador
sudo curl -fL https://metriton.datawire.io/downloads/linux/edgectl -o /usr/local/bin/edgectl && sudo chmod a+x /usr/local/bin/edgectl
edgectl install

This will deploy Ambassador in your cluster with the default settings. It will enable TLS and create a digital certificate using LetsEncrypt, as well as a public DNS for you. You can of course modify those with your own domain and certificates, but we will go with these defaults to make things simple. You should see something similar to this output:

========================================================================
Beginning Ambassador Edge Stack Installation
-> Finding repositories and chart versions
-> Installing Ambassador Edge Stack 1.7.1
-> Checking the AES pod deployment
-> Provisioning a cloud load balancer
-> Your Ambassador Edge Stack installation's address is 51.124.72.179
-> Checking that Ambassador Edge Stack is responding to ACME challenge
-> Automatically configuring TLS
-> Acquiring DNS name cranky-yalow-7215.edgestack.me
-> Obtaining a TLS certificate from Let's Encrypt
Still waiting for TLS certificate acquisition. (This may take a minute.)
-> TLS configured successfully

Ambassador Edge Stack Installation Complete!
========================================================================

Congratulations! You've successfully installed the Ambassador Edge Stack in
your Kubernetes cluster. You can find it at your custom URL:
https://cranky-yalow-7215.edgestack.me/

As you can see, the FQDN that Ambassador created for us is “cranky-yalow-7215.edgestack.me”. Now we need to deploy an application: I will use a test stack I often use in my labs that consists of a web frontend, an API and a database. For this test I will go without the database:

# Deploy sample app
ns_name=whoami
kubectl create ns $ns_name
kubectl -n $ns_name run sqlapi --image=erjosito/sqlapi:0.1 --replicas=1 --port=8080
kubectl -n $ns_name expose deploy/sqlapi --name=sqlapi --port=8080 --type=ClusterIP
kubectl -n $ns_name run sqlweb --image=erjosito/whoami:0.1 --replicas=1 --env="API_URL=http://sqlapi:8080" --port=80
kubectl -n $ns_name expose deploy/sqlweb --name=sqlweb --port=80 --type=ClusterIP

Ambassador has been deployed to the “ambassador” namespace, and our application is in the “whoami” namespace with ClusterIP services (no direct access from Internet). We will configure now Ambassador to provide access to the application from the outside world. In an ingress controller world you would configure an “ingress” rule, in Ambassador this is called a “mapping”:

# Deploy mapping for web svc
cat <<EOF | kubectl -n ambassador apply -f -
apiVersion: getambassador.io/v2
kind: Mapping
metadata:
  name: web
  namespace: ambassador
spec:
  prefix: /web/
  service: sqlweb.$ns_name
EOF

We could deploy another mapping for the API, but we will skip that. As you can see, the syntax for the service is “service_name.namespace” (intuitively I would have thought it should be the other way around, but anyway). We will map the URL path /web/ to the service. Now you should go and test your Ambassador FQDN or IP with the “/web/” path (so in my particular example https://cranky-yalow-7215.edgestack.me/web/, your FQDN will be different), and you should see something like this:

Note the “Not authenticated” message on the top right of the screen, you can safely ignore everything else in that page. That part of the app is looking for a Bearer Authentication HTTP header, and if it does not find one, it just shows that message. Now we need to instruct Ambassador to first authenticate users to Azure Active Directory before sending traffic to our service.

You need to define an AAD Application. There is not much to configure, the most important thing is the callback URL as described in the Ambassador AAD integration guide:

As you can see, it is Ambassador’s FQDN for HTTPS (AAD requires SSL) with the “.ambassador/oauth2/redirection-endpoint” path. You can change further configuration such as adding claims in the “Token configuration” section, or manually define which users and groups can authenticate, but we will go with the defaults. Now we need to teach Ambassador how to use our app, for which you will need three pieces of information:

  • Application Client ID: you can find it in the Overview for the application
  • Tenant ID: same thing, you will find it in the Overview
  • Secret: you can define a secret in the “Certificates and secrets”

Armed with those variables, we can create a “Filter”, which in Ambassador will be the representation of the authentication service:

# Create filter
cat <<EOF | kubectl -n ambassador apply -f -
apiVersion: getambassador.io/v2
kind: Filter
metadata:
  name: azure-ad
spec:
  OAuth2:
    authorizationURL: <a href="https://login.microsoftonline.com/https://login.microsoftonline.com/<your_tenant_id>/v2.0
    clientID: <your_app_client_id>
    secret: <your_app_secret>
    - origin: https://cranky-yalow-7215.edgestack.me
EOF

Now Ambassador knows how to reach AAD via the filter, we need to tell it when. That is called a “filter policy”:

# Create filter policy
cat <<EOF | kubectl -n ambassador apply -f -
apiVersion: getambassador.io/v2
kind: FilterPolicy
metadata:
  name: azure-policy
spec:
  rules:
    - host: "*"
      path: "/web/*"
      filters:
        - name: azure-ad
EOF

What we are doing here is saying: for every host, if the path starts with /web/, make sure it is authenticated and if it is not, send it to our azure-ad filter. And we are done! If you refresh the page (again, in my case https://cranky-yalow-7215.edgestack.me/web/), now it should ask you to authenticate, and provided you have valid credentials in the Azure tenant, you should see something like this:

Note the “Hello, Jose” string that the web page extracted from the Bearer Token inserted by Ambassador. For completeness, here is the PHP code that extracts the information out of the Bearer Token, your programming language will vary to hopefully this illustrates how easy it is (bearer tokens are not encrypted):

<li style="color:LightGray;"><?php
    $jwt = $_SERVER['HTTP_AUTHORIZATION'];
    if (empty($jwt)) {
        print ("Not authenticated");
    } else {
        list($header, $payload, $signature) = explode(".", $jwt);
        $plainPayload = base64_decode($payload);
        $jsonPayload = json_decode($plainPayload, true);
        print("Hello, ".$jsonPayload["given_name"]); 
    }
?></li>

If you want to see the whole authentication token you can use the PHPinfo page linked from the home page:

You can copy paste that token and insert it in JWT token analysis sites such as https://jwt.io/ if you want to see what is inside.

So as you can see it is not as easy as Easy Auth with Azure Application Services, but it is not out of this world either. Now you don’t have any excuse for not having authentication on your application running Kubernetes!

Hopefully this was useful, thanks for reading!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: