Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Kubernetes auth backend #68

Merged
merged 2 commits into from
Jun 14, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 37 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
[![Docker Repository on Quay](https://quay.io/repository/ukhomeofficedigital/vault-sidekick/status "Docker Repository on Quay")](https://quay.io/repository/ukhomeofficedigital/vault-sidekick)
[![GitHub version](https://badge.fury.io/gh/UKHomeOffice%2Fvault-sidekick.svg)](https://badge.fury.io/gh/UKHomeOffice%2Fvault-sidekick)

### **Vault Side Kick**
# Vault Side Kick

**Summary:**
## Summary
Vault Sidekick is a add-on container which can be used as a generic entry-point for interacting with Hashicorp [Vault](https://vaultproject.io) service, retrieving secrets
(both static and dynamic) and PKI certs. The sidekick will take care of renewal's and extension of leases for you and renew the credentials in the specified format for you.

**Usage:**
## Usage

```shell
$ sudo docker run --rm quay.io/ukhomeofficedigital/vault-sidekick:v0.3.3 -help
Expand Down Expand Up @@ -54,49 +54,55 @@ Usage of /vault-sidekick:
comma-separated list of pattern=N settings for file-filtered logging
```

**Building**
## Building

There is a Makefile in the base repository, so assuming you have make and go:
There is a Makefile in the base repository, so assuming you have make and go: `$ make`

`$ make`

**Example Usage**
## Example Usage

The below is taken from a [Kubernetes](https://github.com/kubernetes/kubernetes) pod specification;

```YAML
spec:
containers:
- name: vault-side-kick
image: quay.io/ukhomeofficedigital/vault-sidekick:v0.3.3
args:
- -output=/etc/secrets
- -cn=pki:project1/certs/example.com:common_name=commons.example.com,revoke=true,update=2h
- -cn=secret:secret/db/prod/username:file=.credentials
- -cn=secret:secret/db/prod/password:retries=true
- -cn=aws:aws/creds/s3_backup_policy:file=.s3_creds
volumeMounts:
- name: secrets
mountPath: /etc/secrets
containers:
- name: vault-side-kick
image: quay.io/ukhomeofficedigital/vault-sidekick:v0.3.3
args:
- -output=/etc/secrets
- -cn=pki:project1/certs/example.com:common_name=commons.example.com,revoke=true,update=2h
- -cn=secret:secret/db/prod/username:file=.credentials
- -cn=secret:secret/db/prod/password:retries=true
- -cn=aws:aws/creds/s3_backup_policy:file=.s3_creds
volumeMounts:
- name: secrets
mountPath: /etc/secrets
```

The above equates to:

- Write all the secrets to the /etc/secrets directory
- Retrieve a dynamic certificate pair for me, with the common name: 'commons.example.com' and renew the cert when it expires automatically
- Retrieve the two static secrets /db/prod/{username,password} and write them to .credentials and password.secret respectively
- Apply the IAM policy, renew the policy when required and file the API tokens to .s3_creds in the /etc/secrets directory
- Read the template at /etc/templates/db.tmpl, produce the content from Vault and write to /etc/credentials file
- Write all the secrets to the /etc/secrets directory
- Retrieve a dynamic certificate pair for me, with the common name: 'commons.example.com' and renew the cert when it expires automatically
- Retrieve the two static secrets /db/prod/{username,password} and write them to .credentials and password.secret respectively
- Apply the IAM policy, renew the policy when required and file the API tokens to .s3_creds in the /etc/secrets directory
- Read the template at /etc/templates/db.tmpl, produce the content from Vault and write to /etc/credentials file

**Authentication**
## Authentication

An authentication file can be specified in either yaml of json format which contains a method field, indicating one of the authentication
methods provided by vault i.e. userpass, token, github etc and then followed by the required arguments for that plugin.

If the required arguments for that plugin are not contained in the authentication file, fallbacks from environment variables are used.
Environment variables are prefixed with `VAULT_SIDEKICK`, i.e. `VAULT_SIDEKICK_USERNAME`, `VAULT_SIDEKICK_PASSWORD`.

**Secret Renewals**
### Kubernetes Authentication

The Kubernetes auth plugin supports the following environment variables:

- `VAULT_SIDEKICK_ROLE` - The Vault role name against which to authenticate (**REQUIRED**)
- `VAULT_K8S_LOGIN_PATH` - If your Kubernetes auth backend is mounted at a path other than `kubernetes/` you will need to set this. Default `/v1/auth/kubernetes/login`
- `VAULT_K8S_TOKEN_PATH` - If you mount in-pod service account tokens to a non-default path, you will need to set this. Default `/var/run/secrets/kubernetes.io/serviceaccount/token`

## Secret Renewals

The default behaviour of vault-sidekick is **not** to renew a lease, but to retrieve a new secret and allow the previous to
expire, in order ensure the rotation of secrets. If you don't want this behaviour on a resource you can override using resource options. For exmaple,
Expand All @@ -121,12 +127,12 @@ The format is;

The sidekick supports the following resource types: mysql, postgres, pki, aws, secret, cubbyhole, raw, cassandra and transit

**Environment Variable Expansion**
## Environment Variable Expansion

The resource paths can contain environment variables which the sidekick will resolve beforehand. A use case being, using a environment
or domain within the resource e.g -cn=secret:secrets/myservice/${ENV}/config:fmt=yaml

**Output Formatting**
## Output Formatting

The following output formats are supported: json, yaml, ini, txt, cert, csv, bundle, env

Expand Down Expand Up @@ -156,7 +162,7 @@ In order to change the output format:
Format: 'cert' is less of a format of more file scheme i.e. is just extracts the 'certificate', 'issuing_ca' and 'private_key' and creates the three files FILE.{ca,key,crt}. The
bundle format is very similar in the sense it similar takes the private key and certificate and places into a single file.

**Resource Options**
## Resource Options

- **file**: (filaname) by default all file are relative to the output directory specified and will have the name NAME.RESOURCE; the fn options allows you to switch names and paths to write the files
- **mode**: (mode) overrides the default file permissions of the secret from 0664
Expand All @@ -168,4 +174,4 @@ bundle format is very similar in the sense it similar takes the private key and
- **fmt**: (format) allows you to specify the output format of the resource / secret, e.g json, yaml, ini, txt
- **exec** (execute) execute's a command when resource is updated or changed
- **retries**: (retries) the maximum number of times to retry retrieving a resource. If not set, resources will be retried indefinitely
* **jitter**: (jitter) an optional maximum jitter duration. If specified, a random duration between 0 and `jitter` will be subtracted from the renewal time for the resource
- **jitter**: (jitter) an optional maximum jitter duration. If specified, a random duration between 0 and `jitter` will be subtracted from the renewal time for the resource
84 changes: 84 additions & 0 deletions auth_kubernetes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
Copyright 2015 Home Office All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"fmt"
"io/ioutil"
"os"

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

// Kubernetes auth plugin
type authKubernetesPlugin struct {
// vault client
client *api.Client
}

type kubernetesLogin struct {
Role string `json:"role,omitempty"`
Jwt string `json:"jwt,omitempty"`
}

// Create a new Kubernetes plugin
func NewKubernetesPlugin(client *api.Client) AuthInterface {
return &authKubernetesPlugin{
client: client,
}
}

func (r authKubernetesPlugin) Create(cfg *vaultAuthOptions) (string, error) {
vaultRole, ok := os.LookupEnv("VAULT_SIDEKICK_ROLE")

if !ok {
return "", fmt.Errorf("VAULT_SIDEKICK_ROLE not provided")
}

// in case you mounted your kubernetes auth engine somewhere else
loginPath := getEnv("VAULT_K8S_LOGIN_PATH", "/v1/auth/kubernetes/login")

tokenPath := getEnv("VAULT_K8S_TOKEN_PATH", "/var/run/secrets/kubernetes.io/serviceaccount/token")

// read the JWT from the token file
token, err := ioutil.ReadFile(tokenPath)
if err != nil {
return "", err
}

// build the token request
request := r.client.NewRequest("POST", loginPath)
login := kubernetesLogin{Role: vaultRole, Jwt: string(token)}
if err := request.SetJSONBody(login); err != nil {
return "", err
}

// send the request to Vault
resp, err := r.client.RawRequest(request)
if err != nil {
return "", err
}
defer resp.Body.Close()

// parse the auth object into something useful
secret, err := api.ParseSecret(resp.Body)
if err != nil {
return "", err
}

return secret.Auth.ClientToken, nil
}
2 changes: 2 additions & 0 deletions vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,8 @@ func newVaultClient(opts *config) (*api.Client, error) {
token, err = NewAWSEC2Plugin(client).Create(opts.vaultAuthOptions)
case "gcp-gce":
token, err = NewGCPGCEPlugin(client).Create(opts.vaultAuthOptions)
case "kubernetes":
token, err = NewKubernetesPlugin(client).Create(opts.vaultAuthOptions)
case "token":
opts.vaultAuthOptions.FileName = options.vaultAuthFile
opts.vaultAuthOptions.FileFormat = options.vaultAuthFileFormat
Expand Down