Skip to content

Commit

Permalink
pkg/auth now in github.com/postfinance/vault/k8s
Browse files Browse the repository at this point in the history
  • Loading branch information
marcsauter committed Mar 18, 2019
1 parent f3f4608 commit 1f50659
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 260 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ The usual environment variables for Vault will be used:

- VAULT_TOKEN_PATH - the destination path on disk to store the token. Usually this is a shared volume.

- VAULT_K8S_MOUNT_PATH - the name of the mount where the Kubernetes auth method is enabled. This defaults to auth/kubernetes, but if you changed the mount path you will need to set this value to that path (vault auth enable -path=k8s kubernetes -> VAULT_K8S_MOUNT_PATH=auth/k8s)
- VAULT_AUTH_MOUNT_PATH - the name of the mount where the Kubernetes auth method is enabled. This defaults to kubernetes, but if you changed the mount path you will need to set this value to that path (vault auth enable -path=k8s kubernetes -> VAULT_AUTH_MOUNT_PATH=k8s)

- SERVICE_ACCOUNT_PATH - the path on disk where the Kubernetes service account jtw token lives. This defaults to /var/run/secrets/kubernetes.io/serviceaccount/token.
- SERVICE_ACCOUNT_TOKEN_PATH - the path on disk where the Kubernetes service account jtw token lives. This defaults to /var/run/secrets/kubernetes.io/serviceaccount/token.

- ALLOW_FAIL - the container will successfully terminate even if the authentication to Vault failed, no token will be written to VAULT_TOKEN_PATH. **This condition needs to be handeled in the succeeding container.** (default: "false")

Expand Down
15 changes: 6 additions & 9 deletions cmd/authenticator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,35 @@
//
// the received Vault token will be stored in VAULT_TOKEN_PATH
//
// authenticator is meant to be used in an init container on Kubernetes.
// authenticator is meant to be used in an init container on Kubernetes
package main

import (
"log"
"os"

"github.com/pkg/errors"
"github.com/postfinance/vault-kubernetes/pkg/auth"
"github.com/postfinance/vault/k8s"
)

func main() {
c, err := auth.NewConfigFromEnvironment()
c, err := k8s.NewFromEnvironment()
if err != nil {
log.Fatal(errors.Wrap(err, "failed to get config"))
log.Fatal(err)
}

token, err := c.Authenticate()
if err != nil {
if c.AllowFail {
log.Println(errors.Wrap(err, "authentication failed - ALLOW_FAIL is set therefore pod will continue"))
os.Exit(0)
} else {
log.Fatal(errors.Wrap(err, "authentication failed"))
}
log.Fatal(errors.Wrap(err, "authentication failed"))
}
log.Printf("successfully authenticated to vault")

if err := c.StoreToken(token); err != nil {
log.Fatal(err)
}
log.Printf("successfully stored vault token at %s", c.VaultTokenPath)

os.Exit(0)
log.Printf("successfully stored vault token at %s", c.TokenPath)
}
102 changes: 43 additions & 59 deletions cmd/synchronizer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import (
"path"
"strings"

"github.com/hashicorp/vault/api"
"github.com/pkg/errors"
"github.com/postfinance/vault/k8s"
"github.com/postfinance/vault/kv"
corev1 "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -31,17 +31,22 @@ const (
func main() {
c, err := newFromEnvironment()
if err != nil {
log.Fatal(errors.Wrap(err, "failed to get config"))
log.Fatal(err)
}

if err := c.loadToken(); err != nil {
token, err := c.vault.LoadToken()
if err != nil {
if err := c.checkSecrets(); err != nil {
log.Fatal(err)
}
// you get only here if ALLOW_FAIL=true was set for vault-kubernetes-auth Init Container and vault-kubernetes-auth failed to authenticate
// you get only here ...
// IF ALLOW_FAIL=true was set for vault-kubernetes-authenticator
// AND vault-kubernetes-authenticator failed to authenticate
// AND all testable secrets are present
log.Println(errors.Wrap(err, "cannot synchronize secrets - all secrets seems to be available therefore pod creation will continue"))
os.Exit(0)
}
c.vault.UseToken(token)

if err := c.prepare(); err != nil {
log.Fatal(errors.Wrap(err, "failed to prepare synchronization of secrets"))
Expand All @@ -51,25 +56,23 @@ func main() {
log.Fatal(errors.Wrap(err, "failed to synchronize secrets"))
}
log.Printf("secrets successfully synchronized")

os.Exit(0)
}

type config struct {
VaultTokenPath string
Secrets map[string]string // key = kubernetes secret name, value = vault secret name
SecretPrefix string // prefix for kubernetes secret name
Namespace string
k8sClientset *kubernetes.Clientset
vaultClient *api.Client
secretClients map[string]*kv.Client
type syncConfig struct {
Secrets map[string]string // key = kubernetes secret name, value = vault secret name
SecretPrefix string // prefix for kubernetes secret name
Namespace string
k8sClientset *kubernetes.Clientset
secretClients map[string]*kv.Client
vault *k8s.Vault
}

func newFromEnvironment() (*config, error) {
c := &config{}
c.VaultTokenPath = os.Getenv("VAULT_TOKEN_PATH")
if c.VaultTokenPath == "" {
return nil, fmt.Errorf("missing VAULT_TOKEN_PATH")
func newFromEnvironment() (*syncConfig, error) {
var err error
c := &syncConfig{}
c.vault, err = k8s.NewFromEnvironment()
if err != nil {
return nil, err
}
c.Secrets = make(map[string]string)
for _, item := range strings.Split(os.Getenv("VAULT_SECRETS"), ",") {
Expand Down Expand Up @@ -105,38 +108,19 @@ func newFromEnvironment() (*config, error) {
if err != nil {
return nil, errors.Wrap(err, "failed to get k8s k8sClientset")
}
// connect to vault
vaultConfig := api.DefaultConfig()
if err := vaultConfig.ReadEnvironment(); err != nil {
return nil, errors.Wrap(err, "failed to read environment for vault")
}
c.vaultClient, err = api.NewClient(vaultConfig)
if err != nil {
return nil, errors.Wrap(err, "failed to create vault client")
}
return c, nil
}

// loadToken from VaultTokenPath
func (c *config) loadToken() error {
content, err := ioutil.ReadFile(c.VaultTokenPath)
if err != nil {
return errors.Wrap(err, "could not get vault token")
}
c.vaultClient.SetToken(string(content))
return nil
}

// checkSecrets check the existence of a secret and not the content
func (c *config) checkSecrets() error {
func (sc *syncConfig) checkSecrets() error {
// check secrets
for k, v := range c.Secrets {
for k, v := range sc.Secrets {
if strings.HasSuffix(v, "/") {
log.Printf("WARNING: cannot check existence of secrets from vault path %s without connection to vault\n", v)
continue
}
log.Println("check k8s secret", k, "from vault secret", v)
_, err := c.k8sClientset.CoreV1().Secrets(c.Namespace).Get(k, metav1.GetOptions{})
_, err := sc.k8sClientset.CoreV1().Secrets(sc.Namespace).Get(k, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("secret %s does not exist", k)
}
Expand All @@ -145,13 +129,13 @@ func (c *config) checkSecrets() error {
}

// synchronize secret from vault to the current kubernetes namespace
func (c *config) synchronize() error {
func (sc *syncConfig) synchronize() error {
// create/update the secrets
annotations := make(map[string]string)
for k, v := range c.Secrets {
for k, v := range sc.Secrets {
// get secret from vault
log.Println("read", v, "from vault")
s, err := c.secretClients[strings.SplitN(v, "/", 2)[0]].Read(v)
s, err := sc.secretClients[strings.SplitN(v, "/", 2)[0]].Read(v)
if err != nil {
return err
}
Expand All @@ -163,25 +147,25 @@ func (c *config) synchronize() error {
// create/update k8s secret
annotations[vaultAnnotation] = v
secret := &corev1.Secret{}
secret.Name = fmt.Sprintf("%s%s", c.SecretPrefix, k)
secret.Name = fmt.Sprintf("%s%s", sc.SecretPrefix, k)
secret.Data = data
secret.Annotations = annotations
// create (insert) or update the secret
_, err = c.k8sClientset.CoreV1().Secrets(c.Namespace).Get(secret.Name, metav1.GetOptions{})
_, err = sc.k8sClientset.CoreV1().Secrets(sc.Namespace).Get(secret.Name, metav1.GetOptions{})
if apierr.IsNotFound(err) {
log.Println("create secret", secret.Name, "from vault secret", v)
if _, err := c.k8sClientset.CoreV1().Secrets(c.Namespace).Create(secret); err != nil {
if _, err := sc.k8sClientset.CoreV1().Secrets(sc.Namespace).Create(secret); err != nil {
return err
}
continue
}
log.Println("update secret", secret.Name, "from vault secret", v)
if _, err = c.k8sClientset.CoreV1().Secrets(c.Namespace).Update(secret); err != nil {
if _, err = sc.k8sClientset.CoreV1().Secrets(sc.Namespace).Update(secret); err != nil {
return err
}
}
// delete obsolete secrets
secretList, err := c.k8sClientset.CoreV1().Secrets(c.Namespace).List(metav1.ListOptions{})
secretList, err := sc.k8sClientset.CoreV1().Secrets(sc.Namespace).List(metav1.ListOptions{})
if err != nil {
log.Println(errors.Wrap(err, "cleanup of unused vault secrets failed"))
os.Exit(0)
Expand All @@ -192,45 +176,45 @@ func (c *config) synchronize() error {
continue
}
// only if vault secret is not in secrets
if _, ok := c.Secrets[strings.TrimPrefix(s.Name, c.SecretPrefix)]; ok {
if _, ok := sc.Secrets[strings.TrimPrefix(s.Name, sc.SecretPrefix)]; ok {
continue
}
log.Println("delete secret", s.Name)
if err := c.k8sClientset.CoreV1().Secrets(c.Namespace).Delete(s.Name, &metav1.DeleteOptions{}); err != nil {
if err := sc.k8sClientset.CoreV1().Secrets(sc.Namespace).Delete(s.Name, &metav1.DeleteOptions{}); err != nil {
log.Println(errors.Wrapf(err, "delete obsolete vault secret %s failed", s.Name))
}
}
return nil
}

// prepare
func (c *config) prepare() error {
c.secretClients = make(map[string]*kv.Client)
func (sc *syncConfig) prepare() error {
sc.secretClients = make(map[string]*kv.Client)
secrets := make(map[string]string)
for k, v := range c.Secrets {
for k, v := range sc.Secrets {
mount := strings.SplitN(v, "/", 2)[0]
// ensure kv.Client for mount
if _, ok := c.secretClients[mount]; !ok {
secretClient, err := kv.New(c.vaultClient, mount+"/")
if _, ok := sc.secretClients[mount]; !ok {
secretClient, err := kv.New(sc.vault.Client(), mount+"/")
if err != nil {
return err
}
c.secretClients[mount] = secretClient
sc.secretClients[mount] = secretClient
}
// v is a secret
if !strings.HasSuffix(v, "/") {
secrets[k] = v
continue
}
// v is a path -> get all secrets from v
keys, err := c.secretClients[mount].List(v)
keys, err := sc.secretClients[mount].List(v)
if err != nil {
return err
}
for _, k := range keys {
secrets[k] = path.Join(v, k)
}
}
c.Secrets = secrets
sc.Secrets = secrets
return nil
}
20 changes: 13 additions & 7 deletions cmd/token-renewer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,33 @@ import (
"syscall"

"github.com/pkg/errors"
"github.com/postfinance/vault-kubernetes/pkg/auth"
"github.com/postfinance/vault/k8s"
)

func main() {
c, err := auth.NewConfigFromEnvironment()
c, err := k8s.NewFromEnvironment()
if err != nil {
log.Fatal(errors.Wrap(err, "failed to get config"))
log.Fatal(err)
}

// get token and re-authenticate if enabled
token, err := c.GetToken()
if err != nil {
log.Fatal(errors.Wrap(err, "failed to get token"))
log.Fatal(err)
}
// store token in case re-authentication was done
if err := c.StoreToken(token); err != nil {
log.Fatal(err)
}

renewer, err := c.NewRenewer(token)
if err != nil {
log.Fatal(errors.Wrap(err, "failed to get token renewer"))
log.Fatal(err)
}

log.Println("start renewer loop")
go renewer.Renew()

exit := make(chan os.Signal, 1)
signal.Notify(exit, os.Interrupt, syscall.SIGTERM)
for {
Expand All @@ -47,9 +52,10 @@ func main() {
case <-renewer.RenewCh():
log.Println("token renewed")
case <-exit:
renewer.Stop()
log.Println("signal received - stop execution")
os.Exit(0)
renewer.Stop()
goto Exit
}
}
Exit:
}
4 changes: 2 additions & 2 deletions demo/k8s/authenticator/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ spec:
value: ${VAULT_ADDR}
- name: VAULT_SKIP_VERIFY
value: "true"
- name: VAULT_K8S_MOUNT_PATH
value: ${VAULT_K8S_MOUNT_PATH}
- name: VAULT_AUTH_MOUNT_PATH
value: ${VAULT_AUTH_MOUNT_PATH}
- name: VAULT_ROLE
value: ${VAULT_ROLE}
- name: VAULT_TOKEN_PATH
Expand Down
4 changes: 2 additions & 2 deletions demo/k8s/synchronizer/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ spec:
value: ${VAULT_ADDR}
- name: VAULT_SKIP_VERIFY
value: "true"
- name: VAULT_K8S_MOUNT_PATH
value: ${VAULT_K8S_MOUNT_PATH}
- name: VAULT_AUTH_MOUNT_PATH
value: ${VAULT_AUTH_MOUNT_PATH}
- name: VAULT_ROLE
value: ${VAULT_ROLE}
- name: VAULT_TOKEN_PATH
Expand Down
8 changes: 4 additions & 4 deletions demo/k8s/token-renewer/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ spec:
value: ${VAULT_ADDR}
- name: VAULT_SKIP_VERIFY
value: "true"
- name: VAULT_K8S_MOUNT_PATH
value: ${VAULT_K8S_MOUNT_PATH}
- name: VAULT_AUTH_MOUNT_PATH
value: ${VAULT_AUTH_MOUNT_PATH}
- name: VAULT_ROLE
value: ${VAULT_ROLE}
- name: VAULT_TOKEN_PATH
Expand All @@ -51,8 +51,8 @@ spec:
value: ${VAULT_ADDR}
- name: VAULT_SKIP_VERIFY
value: "true"
- name: VAULT_K8S_MOUNT_PATH
value: ${VAULT_K8S_MOUNT_PATH}
- name: VAULT_AUTH_MOUNT_PATH
value: ${VAULT_AUTH_MOUNT_PATH}
- name: VAULT_TOKEN_PATH
value: ${VAULT_TOKEN_PATH}
- name: kuard
Expand Down
2 changes: 1 addition & 1 deletion demo/profile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export TOKEN_RENEWER_IMAGE="vault-kubernetes-token-renewer:x.y.z"
export IMAGE="gcr.io/kuar-demo/kuard-amd64:1"

export VAULT_ADDR="http://vault-dev-server.vault-dev.svc.cluster.local:8200"
export VAULT_K8S_MOUNT_PATH="auth/${CLUSTER}"
export VAULT_AUTH_MOUNT_PATH="${CLUSTER}"
export VAULT_ROLE="${CLUSTER}-${NAMESPACE}-auth"
export VAULT_TOKEN_DIR="/home/vault"
export VAULT_TOKEN_PATH="${VAULT_TOKEN_DIR}/.vault-token"
Loading

0 comments on commit 1f50659

Please sign in to comment.