Skip to content

Commit

Permalink
Add vault integration
Browse files Browse the repository at this point in the history
  • Loading branch information
dvob committed Dec 4, 2020
1 parent 60641c6 commit 2ee4dcc
Show file tree
Hide file tree
Showing 7 changed files with 368 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.15

require (
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/hashicorp/vault/api v1.0.4
github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e // indirect
github.com/spf13/cobra v1.1.1
gopkg.in/square/go-jose.v2 v2.5.1
Expand Down
47 changes: 47 additions & 0 deletions go.sum

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func newRootCmd() *cobra.Command {
cmd.AddCommand(
newServerCmd(),
newClientCmd(),
newAppCmd(),
)
return cmd
}
Expand Down
121 changes: 121 additions & 0 deletions vault/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Vault

# Setup

## Prepare Certificate
* Create CA
```
mkdir ~/.pcert
cd ~/.pcert
pcert create ca --ca --subject "/CN=My CA"
```

* Add to trusted certificates
```
mkdir /usr/local/share/ca-certificates/local
cp ~/.pcert/ca.crt /usr/local/share/ca-certificates/local/
update-ca-certificates
```

* (optional) Add to minikube. If you do this the control plane (api-server, ...) also trusts the certificate which can be useful for certain experiments.
```
mkdir -p $HOME/.minikube/certs
cp ~/.pcert/ca.crt ~/.minikube/certs/local.crt
```

## Start Minikube and Ingress Controller
* Start minikube
```
minikube start
```

* Install NGINX (we don't use the minikube addon that we're able to configure nginx according to our needs)
```
kubectl create ns ingress
pcert create ingress --server --with ~/.pcert/ca --dns *.k8s.example.com
kubectl create secret --namespace=ingress tls default-certificate --key=ingress.key --cert=ingress.crt
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm install --namespace ingress nginx ingress-nginx/ingress-nginx \
--set controller.admissionWebhooks.enabled=false \
--set controller.hostPort.enabled=true \
--set controller.kind=DaemonSet \
--set controller.extraArgs.default-ssl-certificate="ingress/default-certificate" \
--set-string controller.config.force-ssl-redirect=true
```

* Configure DNS wildcard record for `*.k8s.example.com` to `$( minikube ip )`

## Install Vault
* https://www.vaultproject.io/docs/platform/k8s/helm
```
kubectl create ns vault
helm repo add hashicorp https://helm.releases.hashicorp.com
helm install --namespace vault vault hashicorp/vault --values values.yaml
```

* Initialize the vault and save the root token (`$ROOT_TOKEN`) and the unseal key (`$UNSEAL_KEY`) somewhere:
```
kubectl -n vault exec -ti vault-0 -- vault operator init -key-shares=1 -key-threshold=1
ROOT_TOKEN=...
UNSEAL_KEY=...
```
* Unseal
```
kubectl -n vault exec -ti vault-0 -- vault operator unseal $UNSEAL_KEY
```

## Configure Vault
* Login
```
export VAULT_ADDR=https://vault.k8s.example.com
vault login
# enter $ROOT_TOKEN
```

* Enable KV secret engine
```
vault secrets enable -version=2 kv
```

* Store example secret
```
vault kv put kv/k8s/default password="secret from vault"
```

* Enable Kubernetes Auth
https://www.vaultproject.io/docs/auth/kubernetes
```
vault auth enable kubernetes
```

* Create reader policy
```
vault policy write reader - <<EOF
path "kv/data/k8s/*" {
capabilities = ["read"]
}
EOF
```

* Configure Kubernetes Auth
https://www.vaultproject.io/api/auth/kubernetes
```
vault write auth/kubernetes/config kubernetes_host=https://kubernetes.default.svc
```

* Create role for kubernetes auth
```
vault write auth/kubernetes/role/default-default \
bound_service_account_names=default \
bound_service_account_namespaces=default \
policies=reader \
token_num_uses=1 \
ttl=1m
```

* Deploy example app
```
kubectl apply -f app.yaml
```
61 changes: 61 additions & 0 deletions vault/app.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: app
name: app
spec:
replicas: 1
selector:
matchLabels:
app: app
template:
metadata:
labels:
app: app
spec:
containers:
- name: http-server
image: dsbrng25b/k8s-s2s-auth:latest
imagePullPolicy: IfNotPresent
args:
- app
- --mode
- vault
env:
- name: VAULT_ADDR
value: http://vault.vault.svc:8200
- name: SECRET
value: secret from env
---
apiVersion: v1
kind: Service
metadata:
name: app
labels:
app: app
spec:
ports:
- port: 80
protocol: TCP
targetPort: 8080
selector:
app: app
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app
spec:
rules:
- host: app.k8s.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app
port:
number: 80
11 changes: 11 additions & 0 deletions vault/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
injector:
enabled: false

server:
ingress:
enabled: true
hosts:
- host: vault.k8s.example.com
path: /
#annotations:
# nginx.ingress.kubernetes.io/backend-protocol: HTTPS
126 changes: 126 additions & 0 deletions vault_app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package main

import (
"fmt"
"io/ioutil"
"log"
"net/http"
"os"

vault "github.com/hashicorp/vault/api"
"github.com/spf13/cobra"
)

func getSecret(client *vault.Client, kubeToken, path, key string) (string, error) {
data := map[string]interface{}{
"role": "default-default",
"jwt": kubeToken,
}

loginResp, err := client.Logical().Write("auth/kubernetes/login", data)
if err != nil {
return "", err
}

if loginResp.Auth == nil {
return "", fmt.Errorf("failed to get vault token")
}

token := loginResp.Auth.ClientToken
client.SetToken(token)

resp, err := client.Logical().Read(path)
if err != nil {
return "", err
}

if resp.Data == nil {
return "", fmt.Errorf("no data found under '%s'", path)
}

m, ok := resp.Data["data"].(map[string]interface{})
if !ok {
return "", fmt.Errorf("secret not found at '%s'", path)
}

secret, ok := m[key].(string)
if !ok {
return "", fmt.Errorf("secret '%s' does no exist", key)
}

return secret, nil
}

func newAppCmd() *cobra.Command {
var (
tokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
secretPath = "kv/data/k8s/default"
secretKey = "password"
mode = "vault"
envVar = "SECRET"
secretFile = "/var/run/secrets/app/password"
addr = ":8080"
)
config := vault.DefaultConfig()
_ = config.ReadEnvironment()

cmd := &cobra.Command{
Use: "app",
Short: "An app which reads a secrets.",
RunE: func(cmd *cobra.Command, args []string) error {
var secret string

switch mode {
case "env":
secret = os.Getenv(envVar)
case "file":
raw, err := ioutil.ReadFile(secretFile)
if err != nil {
return err
}
secret = string(raw)
case "vault":
client, err := vault.NewClient(config)
if err != nil {
return err
}

token, err := ioutil.ReadFile(tokenFile)
if err != nil {
return err
}

secret, err = getSecret(client, string(token), secretPath, secretKey)
if err != nil {
return err
}

default:
return fmt.Errorf("unknown mode '%s'", mode)
}

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "the secret is: %s", secret)
})

log.Fatal(http.ListenAndServe(addr, nil))

return nil
},
}
cmd.Flags().StringVar(&mode, "mode", mode, "Mode to get the secret (env|file|vault).")
cmd.Flags().StringVar(&addr, "addr", addr, "Address to listen on.")

// env
cmd.Flags().StringVar(&envVar, "env", envVar, "Environment variable to read the secret from.")

// file
cmd.Flags().StringVar(&secretFile, "file", secretFile, "File to read the secret from.")

// vault
cmd.Flags().StringVar(&tokenFile, "token-file", tokenFile, "Path to read the service account token from.")
cmd.Flags().StringVar(&config.Address, "vault-address", config.Address, "Vault address")
cmd.Flags().StringVar(&secretPath, "secret-path", secretPath, "Secret Path")
cmd.Flags().StringVar(&secretKey, "secret-key", secretKey, "Secret Key")
return cmd
}

0 comments on commit 2ee4dcc

Please sign in to comment.