> For the complete documentation index, see [llms.txt](https://bagus-cahyono.gitbook.io/programming-notes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bagus-cahyono.gitbook.io/programming-notes/cka/09_secret.md).

# Secret

Kubernetes Secrets are used to store sensitive information such as passwords, API keys, and certificates in a secure way. They help separate configuration from application code, improving security and manageability.

## Type of Secret

Here are built-in secret type available in kubernetes:

| Type                                     | Usage                                    |
| ---------------------------------------- | ---------------------------------------- |
| **Opaque**:                              | arbitrary user-defined data.             |
| **kubernetes.io/service-account-token**: | ServiceAccount token.                    |
| **kubernetes.io/dockercfg**:             | serialized `~/.dockercfg` file.          |
| **kubernetes.io/dockerconfigjson**:      | serialized `~/.docker/config.json` file. |
| **kubernetes.io/basic-auth**:            | credentials for basic authentication.    |
| **kubernetes.io/ssh-auth**:              | credentials for SSH authentication.      |
| **kubernetes.io/tls**:                   | data for a TLS client or server.         |
| **bootstrap.kubernetes.io/token**:       | bootstrap token data.                    |

## Opaque Secret

Previously we already create a postgres service with persistent volume. But we still use plain text as the username and password config. We can use `secret` to properly store the sensitive configuration and use it in our postgres deployment.

Create new file called `postgres-secret.yaml` and put the secret definition there.

```yaml
apiVersion: v1
kind: Secret
metadata:
  name: postgres-secret
type: Opaque
data:
  POSTGRES_USER: "YWRtaW4="
  POSTGRES_PASSWORD: "cGFzc3dvcmQ="
```

The `data` section contains key value pair of our sensitive data. In here we store 2 data for username and password with key `POSTGRES_USERNAME` and `POSTGRES_PASSWORD` respectively. The value of the pair need to be `base64` encoded first. We can use command `echo -n "admin" | base64` to encode it.

Lets apply and validate our secret.

```bash
➜ kubectl apply -f postgres-secret.yaml 
secret/postgres-secret created
```

```bash
➜ kubectl get secret  
NAME              TYPE     DATA   AGE
postgres-secret   Opaque   2      21s
```

As you can see above the secret is created with `DATA` length is `2`. We also can describe it to see the details.

```bash
➜ kubectl describe secrets postgres-secret
Name:         postgres-secret
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
POSTGRES_PASSWORD:  8 bytes
POSTGRES_USER:      5 bytes
```

## Using Secret

### Postgres

To use secret in our postgres deployment we need to update our env from plain text to value from secret.

```yaml
env:
- name: POSTGRES_USER
  value: "admin"
- name: POSTGRES_PASSWORD
  value: "password"
```

Replace above section with this section below. This will tell kubernetes to look the value from `secret` with name `postgres-secret` and key `POSTGRES_USER` for the user and `POSTGRES_PASSWORD` for the password.

```yaml
env:
- name: POSTGRES_USER
  valueFrom:
    secretKeyRef:
      name: postgres-secret
      key: POSTGRES_USER
- name: POSTGRES_PASSWORD
  valueFrom:
    secretKeyRef:
      name: postgres-secret
      key: POSTGRES_PASSWORD
```

Lets apply and validate if the pods running without error. We can do similar testing like before, by doing port forward and then connect using `psql`. We should still able to connect and see the data without any error.

```
admin=# \c mydb
You are now connected to database "mydb" as user "admin".
mydb=# SELECT * FROM message;
 id | content 
----+---------
  1 | Hello!
(1 row)
```

### Simple V2

Then lets update our apps to write and read from the database. You can just copy paste this and then run `go get .` to download all the dependencies.

```go
package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "os"

    "github.com/gorilla/mux"
    "github.com/jmoiron/sqlx"
    _ "github.com/lib/pq"
)

type Message struct {
    ID      int    `json:"id" db:"id"`
    Content string `json:"content" db:"content"`
}

func main() {
    // Get user and password from env
    user := os.Getenv("POSTGRES_USER")
    password := os.Getenv("POSTGRES_PASSWORD")
    // Database connection
    host := "postgres.default.svc.cluster.local"
    connectionStr := fmt.Sprintf("host=%s user=%s password=%s dbname=mydb sslmode=disable", host, user, password)
    db, err := sqlx.Connect("postgres", connectionStr)
    if err != nil {
        log.Fatal(err)
    }

    // handlers
    mux := mux.NewRouter()
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // fetch data from database
        messages := []Message{}
        err := db.SelectContext(
            r.Context(),
            &messages,
            "SELECT * FROM message",
        )
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            w.Write([]byte(err.Error()))
            return
        }
        // return it as json response
        err = json.NewEncoder(w).Encode(messages)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            w.Write([]byte(err.Error()))
            return
        }
    }).Methods(http.MethodGet)
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        // get content from json body
        message := &Message{}
        err := json.NewDecoder(r.Body).Decode(message)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            w.Write([]byte(err.Error()))
            return
        }
        // save to database
        _, err = db.NamedExecContext(
            r.Context(),
            "INSERT INTO message (content) VALUES (:content)",
            message,
        )
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            w.Write([]byte(err.Error()))
            return
        }
        w.Write([]byte("SUCCESS"))
    }).Methods(http.MethodPost)

    // start server
    fmt.Println("Server is running on port 8080...")
    err = http.ListenAndServe(":8080", mux)
    if err != nil {
        log.Fatal(err)
    }
}
```

This app will get `user` and `password` configuration from environment variable and use it to connect to the server. And to access our service (postgres) from internal cluster we can use this format `<service_name>.<namespace>.svc.cluster.local`.

Build our app and tag it as `simple-go:v2`.

```bash
➜ docker build --tag simple-go:v2 .
```

Update our deployment to use image `simple-go:v2` and replace the `env` with this one below. This will make `POSTGRES_USER` and `POSTGRES_PASSWORD` environment variable available in our apps.

```yaml
env:
- name: POSTGRES_USER
  valueFrom:
    secretKeyRef:
      name: postgres-secret
      key: POSTGRES_USER
- name: POSTGRES_PASSWORD
  valueFrom:
    secretKeyRef:
      name: postgres-secret
      key: POSTGRES_PASSWORD
```

Apply and check if the pods running as expected.

To test it we can use `minkube service` command to expose our service. Then run `curl` to the given url.

```bash
➜ curl 'http://localhost:58834'
[{"id":1,"content":"Hello!"}]
```

We should see our data there. Try to send `POST` request to insert new data and `GET` again.

```bash
➜ curl -X POST -d '{"content": "Good Morning!"}' 'http://localhost:58834/'
SUCCESS
➜ curl 'http://localhost:58834'                                           
[{"id":1,"content":"Hello!"},{"id":2,"content":"Good Morning!"}]
```

We should see new data is properly inserted and returned.

## References

* <https://kubernetes.io/docs/concepts/configuration/secret/>
* <https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/>
