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.
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.
➜ kubectl apply -f postgres-secret.yaml
secret/postgres-secret created
➜ 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.
➜ 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.
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.
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.
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
.
➜ 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.
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.
➜ 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.
➜ 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.