Skip to content

Commit 2ee4dcc

Browse files
committed
Add vault integration
1 parent 60641c6 commit 2ee4dcc

File tree

7 files changed

+368
-0
lines changed

7 files changed

+368
-0
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.15
44

55
require (
66
github.com/coreos/go-oidc v2.2.1+incompatible
7+
github.com/hashicorp/vault/api v1.0.4
78
github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e // indirect
89
github.com/spf13/cobra v1.1.1
910
gopkg.in/square/go-jose.v2 v2.5.1

go.sum

Lines changed: 47 additions & 0 deletions
Large diffs are not rendered by default.

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func newRootCmd() *cobra.Command {
6060
cmd.AddCommand(
6161
newServerCmd(),
6262
newClientCmd(),
63+
newAppCmd(),
6364
)
6465
return cmd
6566
}

vault/README.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Vault
2+
3+
# Setup
4+
5+
## Prepare Certificate
6+
* Create CA
7+
```
8+
mkdir ~/.pcert
9+
cd ~/.pcert
10+
pcert create ca --ca --subject "/CN=My CA"
11+
```
12+
13+
* Add to trusted certificates
14+
```
15+
mkdir /usr/local/share/ca-certificates/local
16+
cp ~/.pcert/ca.crt /usr/local/share/ca-certificates/local/
17+
update-ca-certificates
18+
```
19+
20+
* (optional) Add to minikube. If you do this the control plane (api-server, ...) also trusts the certificate which can be useful for certain experiments.
21+
```
22+
mkdir -p $HOME/.minikube/certs
23+
cp ~/.pcert/ca.crt ~/.minikube/certs/local.crt
24+
```
25+
26+
## Start Minikube and Ingress Controller
27+
* Start minikube
28+
```
29+
minikube start
30+
```
31+
32+
* Install NGINX (we don't use the minikube addon that we're able to configure nginx according to our needs)
33+
```
34+
kubectl create ns ingress
35+
pcert create ingress --server --with ~/.pcert/ca --dns *.k8s.example.com
36+
kubectl create secret --namespace=ingress tls default-certificate --key=ingress.key --cert=ingress.crt
37+
38+
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
39+
helm install --namespace ingress nginx ingress-nginx/ingress-nginx \
40+
--set controller.admissionWebhooks.enabled=false \
41+
--set controller.hostPort.enabled=true \
42+
--set controller.kind=DaemonSet \
43+
--set controller.extraArgs.default-ssl-certificate="ingress/default-certificate" \
44+
--set-string controller.config.force-ssl-redirect=true
45+
```
46+
47+
* Configure DNS wildcard record for `*.k8s.example.com` to `$( minikube ip )`
48+
49+
## Install Vault
50+
* https://www.vaultproject.io/docs/platform/k8s/helm
51+
```
52+
kubectl create ns vault
53+
54+
helm repo add hashicorp https://helm.releases.hashicorp.com
55+
helm install --namespace vault vault hashicorp/vault --values values.yaml
56+
```
57+
58+
* Initialize the vault and save the root token (`$ROOT_TOKEN`) and the unseal key (`$UNSEAL_KEY`) somewhere:
59+
```
60+
kubectl -n vault exec -ti vault-0 -- vault operator init -key-shares=1 -key-threshold=1
61+
ROOT_TOKEN=...
62+
UNSEAL_KEY=...
63+
```
64+
* Unseal
65+
```
66+
kubectl -n vault exec -ti vault-0 -- vault operator unseal $UNSEAL_KEY
67+
```
68+
69+
## Configure Vault
70+
* Login
71+
```
72+
export VAULT_ADDR=https://vault.k8s.example.com
73+
vault login
74+
# enter $ROOT_TOKEN
75+
```
76+
77+
* Enable KV secret engine
78+
```
79+
vault secrets enable -version=2 kv
80+
```
81+
82+
* Store example secret
83+
```
84+
vault kv put kv/k8s/default password="secret from vault"
85+
```
86+
87+
* Enable Kubernetes Auth
88+
https://www.vaultproject.io/docs/auth/kubernetes
89+
```
90+
vault auth enable kubernetes
91+
```
92+
93+
* Create reader policy
94+
```
95+
vault policy write reader - <<EOF
96+
path "kv/data/k8s/*" {
97+
capabilities = ["read"]
98+
}
99+
EOF
100+
```
101+
102+
* Configure Kubernetes Auth
103+
https://www.vaultproject.io/api/auth/kubernetes
104+
```
105+
vault write auth/kubernetes/config kubernetes_host=https://kubernetes.default.svc
106+
```
107+
108+
* Create role for kubernetes auth
109+
```
110+
vault write auth/kubernetes/role/default-default \
111+
bound_service_account_names=default \
112+
bound_service_account_namespaces=default \
113+
policies=reader \
114+
token_num_uses=1 \
115+
ttl=1m
116+
```
117+
118+
* Deploy example app
119+
```
120+
kubectl apply -f app.yaml
121+
```

vault/app.yaml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
apiVersion: apps/v1
3+
kind: Deployment
4+
metadata:
5+
labels:
6+
app: app
7+
name: app
8+
spec:
9+
replicas: 1
10+
selector:
11+
matchLabels:
12+
app: app
13+
template:
14+
metadata:
15+
labels:
16+
app: app
17+
spec:
18+
containers:
19+
- name: http-server
20+
image: dsbrng25b/k8s-s2s-auth:latest
21+
imagePullPolicy: IfNotPresent
22+
args:
23+
- app
24+
- --mode
25+
- vault
26+
env:
27+
- name: VAULT_ADDR
28+
value: http://vault.vault.svc:8200
29+
- name: SECRET
30+
value: secret from env
31+
---
32+
apiVersion: v1
33+
kind: Service
34+
metadata:
35+
name: app
36+
labels:
37+
app: app
38+
spec:
39+
ports:
40+
- port: 80
41+
protocol: TCP
42+
targetPort: 8080
43+
selector:
44+
app: app
45+
---
46+
apiVersion: networking.k8s.io/v1
47+
kind: Ingress
48+
metadata:
49+
name: app
50+
spec:
51+
rules:
52+
- host: app.k8s.example.com
53+
http:
54+
paths:
55+
- path: /
56+
pathType: Prefix
57+
backend:
58+
service:
59+
name: app
60+
port:
61+
number: 80

vault/values.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
injector:
2+
enabled: false
3+
4+
server:
5+
ingress:
6+
enabled: true
7+
hosts:
8+
- host: vault.k8s.example.com
9+
path: /
10+
#annotations:
11+
# nginx.ingress.kubernetes.io/backend-protocol: HTTPS

vault_app.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"log"
7+
"net/http"
8+
"os"
9+
10+
vault "github.com/hashicorp/vault/api"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
func getSecret(client *vault.Client, kubeToken, path, key string) (string, error) {
15+
data := map[string]interface{}{
16+
"role": "default-default",
17+
"jwt": kubeToken,
18+
}
19+
20+
loginResp, err := client.Logical().Write("auth/kubernetes/login", data)
21+
if err != nil {
22+
return "", err
23+
}
24+
25+
if loginResp.Auth == nil {
26+
return "", fmt.Errorf("failed to get vault token")
27+
}
28+
29+
token := loginResp.Auth.ClientToken
30+
client.SetToken(token)
31+
32+
resp, err := client.Logical().Read(path)
33+
if err != nil {
34+
return "", err
35+
}
36+
37+
if resp.Data == nil {
38+
return "", fmt.Errorf("no data found under '%s'", path)
39+
}
40+
41+
m, ok := resp.Data["data"].(map[string]interface{})
42+
if !ok {
43+
return "", fmt.Errorf("secret not found at '%s'", path)
44+
}
45+
46+
secret, ok := m[key].(string)
47+
if !ok {
48+
return "", fmt.Errorf("secret '%s' does no exist", key)
49+
}
50+
51+
return secret, nil
52+
}
53+
54+
func newAppCmd() *cobra.Command {
55+
var (
56+
tokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
57+
secretPath = "kv/data/k8s/default"
58+
secretKey = "password"
59+
mode = "vault"
60+
envVar = "SECRET"
61+
secretFile = "/var/run/secrets/app/password"
62+
addr = ":8080"
63+
)
64+
config := vault.DefaultConfig()
65+
_ = config.ReadEnvironment()
66+
67+
cmd := &cobra.Command{
68+
Use: "app",
69+
Short: "An app which reads a secrets.",
70+
RunE: func(cmd *cobra.Command, args []string) error {
71+
var secret string
72+
73+
switch mode {
74+
case "env":
75+
secret = os.Getenv(envVar)
76+
case "file":
77+
raw, err := ioutil.ReadFile(secretFile)
78+
if err != nil {
79+
return err
80+
}
81+
secret = string(raw)
82+
case "vault":
83+
client, err := vault.NewClient(config)
84+
if err != nil {
85+
return err
86+
}
87+
88+
token, err := ioutil.ReadFile(tokenFile)
89+
if err != nil {
90+
return err
91+
}
92+
93+
secret, err = getSecret(client, string(token), secretPath, secretKey)
94+
if err != nil {
95+
return err
96+
}
97+
98+
default:
99+
return fmt.Errorf("unknown mode '%s'", mode)
100+
}
101+
102+
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
103+
fmt.Fprintf(w, "the secret is: %s", secret)
104+
})
105+
106+
log.Fatal(http.ListenAndServe(addr, nil))
107+
108+
return nil
109+
},
110+
}
111+
cmd.Flags().StringVar(&mode, "mode", mode, "Mode to get the secret (env|file|vault).")
112+
cmd.Flags().StringVar(&addr, "addr", addr, "Address to listen on.")
113+
114+
// env
115+
cmd.Flags().StringVar(&envVar, "env", envVar, "Environment variable to read the secret from.")
116+
117+
// file
118+
cmd.Flags().StringVar(&secretFile, "file", secretFile, "File to read the secret from.")
119+
120+
// vault
121+
cmd.Flags().StringVar(&tokenFile, "token-file", tokenFile, "Path to read the service account token from.")
122+
cmd.Flags().StringVar(&config.Address, "vault-address", config.Address, "Vault address")
123+
cmd.Flags().StringVar(&secretPath, "secret-path", secretPath, "Secret Path")
124+
cmd.Flags().StringVar(&secretKey, "secret-key", secretKey, "Secret Key")
125+
return cmd
126+
}

0 commit comments

Comments
 (0)