Kubernetes Token Review And Authentication

Sanad Haj
5 min readSep 5, 2020

--

In building web and REST API application, it is very important to build a system users can trust and rely on.

Authentication is important because it enables organizations and apps to keep their networks secure by permitting only authenticated users to access its protected resources.

In this tutorial, We’ll be discussing how pod authenticate pod running in k8s cluster using Golang and Go-Guardian library along with k8s token review API.

Go-Guardian Overview

Go-Guardian is a golang library that provides a simple, clean, and idiomatic way to create powerful modern API and web authentication.

Go-Guardian sole purpose is to authenticate requests, which it does through an extensible set of authentication methods known as strategies.
Go-Guardian does not mount routes or assume any particular database schema, which maximizes flexibility and allows decisions to be made by the developer.
The API is simple: you provide go-guardian a request to authenticate, and go-guardian invoke strategies to authenticate end-user request.
Strategies provide callbacks for controlling what occurs when authentication should succeeds or fails.

Why Go-Guardian?

When building a modern application, you don’t want to implement authentication module from scratch;
you want to focus on building awesome software. go-guardian is here to help with that.

Here are a few bullet point reasons you might like to try it out:

  • provides simple, clean, and idiomatic API.
  • provides top trends and traditional authentication methods.
  • provides a package to caches the authentication decisions, based on different mechanisms and algorithms.
  • provides two-factor authentication and one-time password as defined in RFC-4226 and RFC-6238

Token Review

K8s Token Review attempts to authenticate a token to a known user or service account. In other words, k8s token review API enables access token verification and returning user information.
This authentication method makes it easy to introduce apps into a k8s pod and allows the pod to authenticate other pods.

Use Case And Token Review Flow

assume we have two pods(A,B) running in k8s cluster, pods A attempts to access a protected resource in the pod B where the authentication required.
To solve the above use case pod B sends an HTTP request to the k8s API server to authenticate pod A.
To successfully make HTTP requests to the k8s token review API a bearer token for pod B must be included as an authorization header and pod A access token in request body.

curl -k -X "POST" "https://127.0.0.1:8443/apis/authentication.k8s.io/v1/tokenreviews" \
-H 'Authorization: Bearer <Pod B Token>' \
-H 'Content-Type: application/json; charset=utf-8' \
-d $'{
"kind": "TokenReview",
"apiVersion": "authentication.k8s.io/v1",
"spec": {
"token": "<Pod A Token>"
}
}'

Prerequisite

Note: if you wish to follow this tutorial without running or setup Kubernetes use the following mock and skip this section.

Before we start our tutorial you must have a running k8s cluster with service-account-lookup flag. This is defaulted to true in Kubernetes 1.7, but any versions prior should ensure the Kubernetes API server is started with this setting.

We need two service accounts for pod A and B
besides grant pod B permission to access token review API If Kubernetes is configured to use RBAC roles.

$ cat << EOF > token.yaml 
apiVersion: v1
kind: ServiceAccount
metadata:
name: PodA
namespace: default
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: PodB
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: role-tokenreview-binding
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: PodB
namespace: default
EOF
$ kubectl apply -f token.yaml

Let’s get access tokens for both pods we will use them later in this tutorial.

$ kubectl describe secret  podA
$ kubectl describe secret podB

Note: To simplify the tutorial we will not deploy our code to Kubernetes, so we will assume our code process as pod B and curl as pod A

Creating our project

We are going to start by creating our project.

mkdir k8s-auth && cd k8s-auth && go mod init k8s-auth && touch main.go

This will create a new directory called “k8s-auth” and and initialize the project with go.mod.

We also need to install gorilla mux, go-guardian.

go get github.com/gorilla/mux
go get github.com/shaj13/go-guardian

Our first lines of code

Before we write any code we need to write some mandatory code to make the program run.

package main
import (
"log"
)
func main() {
log.Println("Auth !!")
}

Creating our endpoints

We are going to remove the line that prints out “Auth !!” and add the gorilla Mux package and initialize the router.

package main
import (
"github.com/gorilla/mux"
)
func main() {
router := mux.NewRouter()
}

Now we are going to establish the endpoints of our API, the way we will set this up is to create all of our endpoints in the main function, every endpoint needs a function to handle the request and we will define those below the main function.

package main
import (
"net/http"
"log"
"github.com/gorilla/mux"
)
func main() {
router := mux.NewRouter()
router.HandleFunc("/v1/book/{id}", getBookAuthor).Methods("GET")
log.Println("server started and listening on http://127.0.0.1:8080")
http.ListenAndServe("127.0.0.1:8080", router)
}

What I’ve done is to create a route to access a protected resource that returns a book author by id.

Route handlers

Now we just need to define the functions that will handle the requests.

getBookAuthor()

func getBookAuthor(w http.ResponseWriter, r *http.Request) {    vars := mux.Vars(r)
id := vars["id"]
books := map[string]string{
"1449311601": "Ryan Boyd",
"148425094X": "Yvonne Wilson",
"1484220498": "Prabath Siriwarden",
}
body := fmt.Sprintf("Author: %s \n", books[id])
w.Write([]byte(body))
}

Your file should now look something like this:

Now lets run sample requests to test our code !!

curl  -k http://127.0.0.1:8080/v1/book/1449311601 
Author: Ryan Boyd

Integrate with Go-Guardian

first let’s add the following line before the main function.

var authenticator auth.Authenticator
var cache store.Cache

now we need a function to setup Go-Guardian.

func setupGoGuardian() {                        
authenticator = auth.New()
cache = store.NewFIFO(context.Background(), time.Minute*10)
account := kubernetes.SetServiceAccountToken("<PodB Token/secrets>")
address := kubernetes.SetAddress("http://<k8s host ip>:<k8s port>")
kubeStrategy := kubernetes.New(cache, account, address) authenticator.EnableStrategy(token.CachedStrategyKey, kubeStrategy) }

What I’ve done is to setup Go-Gurdian, by creating authenticator that takes request and dispatch it to kubernetes strategy and return user information when successfully authenticates the request.
besides, initialize a cache to caches the authentication decision to improve server performance.

Note: make sure to replace <PodB Token/secrets> with actual secret that we done in Prerequisite section. and also replace <k8s host ip>:<k8s port> with the k8s API server IP and port

Before we continue, we need an HTTP middleware to intercept the request and authenticate users before it reaches the final route.

func middleware(next http.Handler) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Println("Executing Auth Middleware")
user, err := authenticator.Authenticate(r)
if err != nil {
code := http.StatusUnauthorized
http.Error(w, http.StatusText(code), code)
return
}
log.Printf("User %s Authenticated\n", user.UserName())
next.ServeHTTP(w, r)
})
}

finally, we wrap the getBookAuthor function in our main with the middleware to authenticate the request.

middleware(http.HandlerFunc(getBookAuthor))

Your file should now look something like this:

Testing our API

curl  -k http://127.0.0.1:8080/v1/book/1449311601 -H "Authorization: Bearer <Pod A Token>"Author: Ryan Boydcurl  -k http://127.0.0.1:8080/v1/book/1449311601 -H "Authorization: Bearer invalid"Unauthorized

Thank you so much for reading!

I hope you found this article helpful, I really hope I helped at least one person get familiar with the basics of building Server Authentication in Golang Using Go-Guardian.

More About Go-Guardian can be found in GitHub and GoDoc

--

--