# Clientless Access for Workloads to Kubernetes

> Octelium documentation. Canonical page: <https://octelium.com/docs/octelium/latest/management/guide/service/http/k8s-clientless>.

You can easily protect access to all your Kubernetes clusters as Octelium *Services* and provide your clientless `WORKLOAD` *Users* such as your Golang-based microservices and applications with secretless access without having to expose, manage and share Kubeconfigs, mTLS client private keys or access tokens required to access such Kubernetes clusters. In this short guide, we're going to use the Golang SDK (read more [here](https://octelium.com/docs/octelium/latest/management/guide/sdk.md)) together with the official Kubernetes [Golang client](https://github.com/kubernetes/client-go) to access the resources of a protected Kubernetes cluster.

We first create a *Secret* that contains the kubeconfig file required to access the Kubernetes cluster (read more [here](https://octelium.com/docs/octelium/latest/management/core/secret.md)) as follows:

```bash
octeliumctl create secret kubeconfig-k8s1 --file /PATH/TO/KUBECONFIG
```

Note that Octelium also supports secretless access to Kubernetes clusters via access tokens and mTLS client certificates. You can read more [here](https://octelium.com/docs/octelium/latest/management/core/service/kubernetes.md#secretless-access).

Now we create the `KUBERNETES` *Service* representing our Kubernetes cluster that needs to be protected. The *Service* needs to be publicly exposed in order to be accessible via the clientless mode (read more [here](https://octelium.com/docs/octelium/latest/management/core/service/clientless.md)) as follows:

```yaml
kind: Service
metadata:
  name: k8s1
spec:
  mode: KUBERNETES
  # !mark
  isPublic: true
  config:
    upstream:
      url: https://k8s-cluster.example.com:6443
    kubernetes:
      kubeconfig:
        fromSecret: kubeconfig-k8s1
```

You can now apply the *Service* `k8s1` as follows:

```bash
octeliumctl apply /PATH/TO/SERVICE.YAML
```

Now you have multiple options to access this *Service* from your applications, namely via the Octelium Golang SDK, via OAuth2 client credentials or directly via bearer access tokens. The last 2 options allow you to use standard OAuth2 client credentials and bearer tokens without having to use any special SDKs or being aware of the Octelium *Cluster* existence at all.

## Golang SDK

We first create an authentication token *Credential* for the `WORKLOAD` *User* representing our application, which we name here `microservice1`, whose *Sessions* are `CLIENTLESS` (read more about authentication token *Credentials* [here](https://octelium.com/docs/octelium/latest/management/core/credential.md#authentication-tokens) and *Session* types [here](https://octelium.com/docs/octelium/latest/management/core/credential.md#session-type)) as follows:

```bash
octeliumctl create cred --user microservice1 --session-type clientless microservice1-cred

# The output should be something like
Authentication Token: AQpA0f-8AQE2SLLZG3UDvvi...
```

Now we add the `octelium-go` library as well as the Kubernetes Golang client to our application as follows:

```bash
go get github.com/octelium/octelium/octelium-go
go get k8s.io/client-go@latest
```

The `HTTPClient()` method of the `octelium-go` client returns a standard `*http.Client` that attaches the *Session* access token to every request and automatically refreshes it before it expires. We can simply feed it to the `NewForConfigAndClient()` function in order to create a Kubernetes client as follows:

```go
package main

import (
	"context"
	"fmt"
	"os"

	octelium "github.com/octelium/octelium/octelium-go"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

func main() {
	if err := run(context.Background()); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

func run(ctx context.Context) error {
	client, err := octelium.New(ctx,
		octelium.WithDomain("example.com"),
		octelium.WithAuthenticator(
			octelium.AuthenticationToken(os.Getenv("OCTELIUM_AUTH_TOKEN")),
		),
	)
	if err != nil {
		return err
	}
	defer client.Close()

	k8sClient, err := kubernetes.NewForConfigAndClient(&rest.Config{
		Host: "https://k8s1.example.com",
	}, client.HTTPClient())
	if err != nil {
		return err
	}

	pods, err := k8sClient.CoreV1().Pods("").List(ctx, metav1.ListOptions{})
	if err != nil {
		return err
	}

	for _, pod := range pods.Items {
		fmt.Printf("Pod %s/%s is %s\n", pod.Namespace, pod.Name, pod.Status.Phase)
	}

	return nil
}
```

Note that the host must be the *Service*'s public HTTPS URL, since the access token is only attached to HTTPS requests to the *Cluster* domain and its subdomains by default. Since the *Service* provides secretless access to the upstream Kubernetes cluster, your application does not need any kubeconfig, client certificates or Kubernetes tokens at all.

> **Note:**
>
> You can also omit the options and let `octelium.New()` read the *Cluster* domain and the authentication token from the `OCTELIUM_DOMAIN` and `OCTELIUM_AUTH_TOKEN` environment variables respectively.

## Secretless Authentication via OIDC Assertions

If your application itself runs inside a Kubernetes cluster, then it does not even need an authentication token *Credential*. Instead, it can authenticate itself to the *Cluster* using a Kubernetes-issued OIDC identity token of its own Kubernetes service account (read more about assertion-based *IdentityProviders* [here](https://octelium.com/docs/octelium/latest/management/core/identity-providers.md#oidc-assertion)). This way no secret at all is stored inside the Kubernetes cluster hosting your application.

First we create an `oidcIdentityToken` *IdentityProvider* for the Kubernetes cluster hosting your application as follows:

```yaml
kind: IdentityProvider
metadata:
  name: apps-cluster
spec:
  oidcIdentityToken:
    issuerURL: https://oidc.apps-cluster.example.com
    audience: https://example.com
```

> **Note:**
>
> Many managed Kubernetes providers publish their OIDC discovery endpoints publicly. If your Kubernetes cluster does not, then you can set the JWKS content of the cluster manually instead of the `issuerURL` field (read more [here](https://octelium.com/docs/octelium/latest/management/core/identity-providers.md#jwks-content)).

Now your `WORKLOAD` *User* can set the identity that corresponds to the Kubernetes service account of your application as follows:

```yaml
kind: User
metadata:
  name: microservice1
spec:
  type: WORKLOAD
  authentication:
    identities:
    - identityProvider: apps-cluster
      identifier: system:serviceaccount:default:microservice1
```

The `identifier` of the *User*'s identity is the `sub` claim of the Kubernetes service account token which takes the form `system:serviceaccount:<K8S_NAMESPACE>:<K8S_SERVICE_ACCOUNT_NAME>`. Now we mount a short-lived, audience-bound projected service account token in the container of your application. Note that the token `audience` has to match the `audience` field of the *IdentityProvider*:

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: microservice1
  namespace: default
spec:
  selector:
    matchLabels:
      app: microservice1
  template:
    metadata:
      labels:
        app: microservice1
    spec:
      serviceAccountName: microservice1
      containers:
        - name: microservice1
          image: <YOUR_IMAGE>
          env:
            - name: OCTELIUM_DOMAIN
              value: example.com
            - name: OCTELIUM_ASSERTION_FILE
              value: /var/run/secrets/octelium/token
          volumeMounts:
            - name: octelium-token
              mountPath: /var/run/secrets/octelium
              readOnly: true
      volumes:
        - name: octelium-token
          projected:
            sources:
              - serviceAccountToken:
                  path: token
                  audience: https://example.com
                  expirationSeconds: 3600
```

Kubernetes itself rotates the projected token before it expires. Since `OCTELIUM_DOMAIN` and `OCTELIUM_ASSERTION_FILE` are set, the application can now simply create its `octelium-go` client without any options:

```go
client, err := octelium.New(ctx)
```

Which is equivalent to explicitly setting the authenticator as follows:

```go
client, err := octelium.New(ctx,
	octelium.WithDomain("example.com"),
	octelium.WithAuthenticator(
		// !mark
		octelium.AssertionFromFile("/var/run/secrets/octelium/token"),
	),
)
```

The assertion file is read upon every authentication, so the library can always create a new *Session* with the freshly rotated token whenever the previous *Session* expires.

## OAuth2 Client Credentials

If you do not want to use the Octelium SDK, you can instead use the standard OAuth2 client credentials flow (read more [here](https://octelium.com/docs/octelium/latest/user/clientless/oauth2.md)). You can create an OAuth2 client credentials *Credential* as follows:

```bash
octeliumctl create cred --type oauth2 --user microservice1 cred02

Client ID: spxg-cdyx
Client Secret: AQpAN9OT1az6DQH69dNklhr...
```

Now you can use the [`clientcredentials`](https://pkg.go.dev/golang.org/x/oauth2/clientcredentials) package to obtain the access token and automatically obtain a new one once it expires, and wrap the Kubernetes client transport in order to attach the access token to every request as follows:

```go
package main

import (
	"context"
	"fmt"
	"net/http"
	"os"

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/clientcredentials"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
)

func main() {
	if err := run(context.Background()); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

func run(ctx context.Context) error {
	domain := "example.com"

	config := clientcredentials.Config{
		ClientID:     os.Getenv("OCTELIUM_CLIENT_ID"),
		ClientSecret: os.Getenv("OCTELIUM_CLIENT_SECRET"),
		TokenURL:     fmt.Sprintf("https://%s/oauth2/token", domain),
		AuthStyle:    oauth2.AuthStyleInParams,
	}

	// The token source caches the access token and obtains a new one before
	// it expires.
	tokenSource := config.TokenSource(ctx)

	k8sClient, err := kubernetes.NewForConfig(&rest.Config{
		Host: fmt.Sprintf("https://k8s1.%s", domain),
		WrapTransport: func(rt http.RoundTripper) http.RoundTripper {
			return &oauth2.Transport{Source: tokenSource, Base: rt}
		},
	})
	if err != nil {
		return err
	}

	pods, err := k8sClient.CoreV1().Pods("").List(ctx, metav1.ListOptions{})
	if err != nil {
		return err
	}

	for _, pod := range pods.Items {
		fmt.Printf("Pod %s/%s is %s\n", pod.Namespace, pod.Name, pod.Status.Phase)
	}

	return nil
}
```

If you want to use access token *Credentials* (read more [here](https://octelium.com/docs/octelium/latest/management/core/credential.md#access-tokens)) instead of OAuth2 client credentials *Credentials*, you can simply set the access token in the `BearerToken` field of the `rest.Config` instead.

Octelium also provides OpenTelemetry-ready, application-layer L7 aware visibility and access logging in real time (see an example for Kubernetes [here](https://octelium.com/docs/octelium/latest/management/core/service/kubernetes.md#visibility)). You can read more about visibility [here](https://octelium.com/docs/octelium/latest/management/core/visibility.md).
