File Permissions
* Added a mode option to the resource specification enabling secrets to set the file permissions * Fixed a bug in the renewal time, when a resource does not have a custom update and the lease time is 0s * Cleaned up some of the vetting issue
This commit is contained in:
parent
f0b715ce2a
commit
aeb3cb34bf
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,6 +5,7 @@
|
||||||
.idea/
|
.idea/
|
||||||
bin/
|
bin/
|
||||||
release/
|
release/
|
||||||
|
secrets/
|
||||||
|
|
||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
*.o
|
*.o
|
||||||
|
|
27
.travis.yml
27
.travis.yml
|
@ -1,3 +1,5 @@
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- secure: Vq4593WUn2Hng93OynvydesiWu7dAnhIFhX6KtmVwqyuWuk60D8bJAd9qiJYax6+jqsAVnouaD+0TtfLb+SFJ9+vFMqykP+iSVJyC6p3DCRqMmKN+KN20hmq5WSvoE9Rs1elNV770plZdDbAIJ36iFsSpJnrp6dA9vFs0G4E2ceLw0KTJDW7C/eFO1LFiWdgsIrq4DpSCGqjcAzrRKsAkajBmsLNO//MFlsUnnJX4YBQKzibSAyoHeTHPitkvZ6Qf11N7pFMqeX78Stqeat7Xks/C0RzCOZipIGX/yU5qTGg8yGS9FG1qDkNf2Vh4osZySRHy4QJfukTlrLBB1skPHfQpU6n3bNVkF2dhQv0M4TzkrFXB+DF5WyrvKhM7i/w6WvTmg6Zc66ut0qXbTUOSJeZpuHo7c195ly51GxmbNIDw9F2RrJbjRqJrrJ9EXGCBC1wbz4wIYXt5sGh2bl1HdZqGEYkzFfXsIrFc6e8mal/AEMwos3hfezoU9kyriM5sA4M4+yK4ERQYL3Kc4UsFg4oyZrbna6k4Pas/mdFYeM3jhQxOKWxCaSSoeKE/FrbKcMb2/4btQe12Ik9zUIca58VJ04KawEBoyF/+YTSCpQOapnoFmWJoCrXJZlIU56zdn4LXNjDL+pENnjDXhutQck3ZqErcewQQzSepIUtFQo=
|
- secure: Vq4593WUn2Hng93OynvydesiWu7dAnhIFhX6KtmVwqyuWuk60D8bJAd9qiJYax6+jqsAVnouaD+0TtfLb+SFJ9+vFMqykP+iSVJyC6p3DCRqMmKN+KN20hmq5WSvoE9Rs1elNV770plZdDbAIJ36iFsSpJnrp6dA9vFs0G4E2ceLw0KTJDW7C/eFO1LFiWdgsIrq4DpSCGqjcAzrRKsAkajBmsLNO//MFlsUnnJX4YBQKzibSAyoHeTHPitkvZ6Qf11N7pFMqeX78Stqeat7Xks/C0RzCOZipIGX/yU5qTGg8yGS9FG1qDkNf2Vh4osZySRHy4QJfukTlrLBB1skPHfQpU6n3bNVkF2dhQv0M4TzkrFXB+DF5WyrvKhM7i/w6WvTmg6Zc66ut0qXbTUOSJeZpuHo7c195ly51GxmbNIDw9F2RrJbjRqJrrJ9EXGCBC1wbz4wIYXt5sGh2bl1HdZqGEYkzFfXsIrFc6e8mal/AEMwos3hfezoU9kyriM5sA4M4+yK4ERQYL3Kc4UsFg4oyZrbna6k4Pas/mdFYeM3jhQxOKWxCaSSoeKE/FrbKcMb2/4btQe12Ik9zUIca58VJ04KawEBoyF/+YTSCpQOapnoFmWJoCrXJZlIU56zdn4LXNjDL+pENnjDXhutQck3ZqErcewQQzSepIUtFQo=
|
||||||
|
@ -5,30 +7,29 @@ env:
|
||||||
- AUTHOR_EMAIL=gambol99@gmail.com
|
- AUTHOR_EMAIL=gambol99@gmail.com
|
||||||
- REGISTRY_USERNAME=ukhomeofficedigital+vault_sidekick
|
- REGISTRY_USERNAME=ukhomeofficedigital+vault_sidekick
|
||||||
- REGISTRY=quay.io
|
- REGISTRY=quay.io
|
||||||
services:
|
|
||||||
- docker
|
|
||||||
language: go
|
language: go
|
||||||
go:
|
go: 1.8.1
|
||||||
- 1.7.5
|
|
||||||
install:
|
install: true
|
||||||
|
script:
|
||||||
- make test
|
- make test
|
||||||
- if ([[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} != "true" ]]) || [[ -n ${TRAVIS_TAG} ]]; then
|
- if ([[ ${TRAVIS_BRANCH} == "master" ]] && [[ ${TRAVIS_PULL_REQUEST} != "true" ]]) || [[ -n ${TRAVIS_TAG} ]]; then
|
||||||
|
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-X main.gitsha=${TRAVIS_TAG:-git+${TRAVIS_COMMIT}}" -o bin/vault-sidekick_linux_amd64;
|
||||||
|
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-X main.gitsha=${TRAVIS_TAG:-git+${TRAVIS_COMMIT}}" -o bin/vault-sidekick_darwin_amd64;
|
||||||
|
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-X main.gitsha=${TRAVIS_TAG:-git+${TRAVIS_COMMIT}}" -o bin/vault-sidekick_windows_amd64.exe;
|
||||||
docker login -u ${REGISTRY_USERNAME} -p ${REGISTRY_TOKEN} -e ${AUTHOR_EMAIL} ${REGISTRY};
|
docker login -u ${REGISTRY_USERNAME} -p ${REGISTRY_TOKEN} -e ${AUTHOR_EMAIL} ${REGISTRY};
|
||||||
VERSION=latest make docker-release;
|
VERSION=${TRAVIS_TAG:-latest} make docker-release;
|
||||||
fi
|
fi
|
||||||
before_deploy:
|
|
||||||
- NAME=GOOS=linux GOARCH=amd64 godep go build -o bin/vault-sidekick-linux-amd64
|
|
||||||
after_deploy:
|
|
||||||
- docker login -u ${REGISTRY_USERNAME} -p ${REGISTRY_TOKEN} -e ${AUTHOR_EMAIL} ${REGISTRY}
|
|
||||||
- VERSION=$TRAVIS_TAG make docker-release
|
|
||||||
deploy:
|
deploy:
|
||||||
provider: releases
|
provider: releases
|
||||||
skip_cleanup: true
|
skip_cleanup: true
|
||||||
on:
|
on:
|
||||||
go: 1.7.5
|
|
||||||
repo: UKHomeOffice/vault-sidekick
|
repo: UKHomeOffice/vault-sidekick
|
||||||
tags: true
|
tags: true
|
||||||
api_key:
|
api_key:
|
||||||
secure: "${GITHUB_TOKEN}"
|
secure: "${GITHUB_TOKEN}"
|
||||||
file:
|
file:
|
||||||
- bin/vault-sidekick-linux-amd64
|
- bin/vault-sidekick_linux_amd64
|
||||||
|
- bin/vault-sidekick_darwin_amd64
|
||||||
|
- bin/vault-sidekick_windows_amd64.exe
|
||||||
|
|
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -1,4 +1,15 @@
|
||||||
|
|
||||||
|
#### **Version v0.3.1**
|
||||||
|
|
||||||
|
##### FEATURES
|
||||||
|
|
||||||
|
* Added a mode option to the resource specification enabling secrets to set the file permissions
|
||||||
|
* Fixed a bug in the renewal time, when a resource does not have a custom update and the lease time is 0s
|
||||||
|
* Cleaned up some of the vetting issues
|
||||||
|
* Change the travis build to use golang v1.8.1
|
||||||
|
* Added a version flag -version and passing the gitsha in the version
|
||||||
|
* Updated the kubernete deployment files
|
||||||
|
|
||||||
#### **Version v0.1.0**
|
#### **Version v0.1.0**
|
||||||
|
|
||||||
##### FEATURES
|
##### FEATURES
|
||||||
|
|
10
Makefile
10
Makefile
|
@ -2,9 +2,11 @@
|
||||||
NAME=vault-sidekick
|
NAME=vault-sidekick
|
||||||
AUTHOR ?= ukhomeofficedigital
|
AUTHOR ?= ukhomeofficedigital
|
||||||
REGISTRY ?= quay.io
|
REGISTRY ?= quay.io
|
||||||
GOVERSION ?= 1.7.1
|
GOVERSION ?= 1.8.1
|
||||||
HARDWARE=$(shell uname -m)
|
HARDWARE=$(shell uname -m)
|
||||||
VERSION ?= $(shell awk '/Version =/ { print $$3 }' main.go | sed 's/"//g')
|
VERSION ?= $(shell awk '/release =/ { print $$3 }' main.go | sed 's/"//g')
|
||||||
|
GIT_SHA=$(shell git --no-pager describe --always --dirty)
|
||||||
|
LFLAGS ?= -X main.gitsha=${GIT_SHA}
|
||||||
VETARGS?=-asmdecl -atomic -bool -buildtags -copylocks -methods -nilfunc -printf -rangeloops -shift -structtags -unsafeptr
|
VETARGS?=-asmdecl -atomic -bool -buildtags -copylocks -methods -nilfunc -printf -rangeloops -shift -structtags -unsafeptr
|
||||||
|
|
||||||
.PHONY: test authors changelog build docker static release
|
.PHONY: test authors changelog build docker static release
|
||||||
|
@ -14,12 +16,12 @@ default: build
|
||||||
build: deps
|
build: deps
|
||||||
@echo "--> Compiling the project"
|
@echo "--> Compiling the project"
|
||||||
mkdir -p bin
|
mkdir -p bin
|
||||||
godep go build -o bin/${NAME}
|
godep go build -ldflags '-w ${LFLAGS}' -o bin/${NAME}
|
||||||
|
|
||||||
static: deps
|
static: deps
|
||||||
@echo "--> Compiling the static binary"
|
@echo "--> Compiling the static binary"
|
||||||
mkdir -p bin
|
mkdir -p bin
|
||||||
CGO_ENABLED=0 GOOS=linux godep go build -a -tags netgo -ldflags '-w' -o bin/${NAME}
|
CGO_ENABLED=0 GOOS=linux godep go build -a -tags netgo -ldflags '-w ${LFLAGS}' -o bin/${NAME}
|
||||||
|
|
||||||
docker-build:
|
docker-build:
|
||||||
@echo "--> Compiling the project"
|
@echo "--> Compiling the project"
|
||||||
|
|
|
@ -138,6 +138,7 @@ bundle format is very similar in the sense it similar takes the private key and
|
||||||
**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
|
- **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
|
||||||
- **create**: (create) create the resource
|
- **create**: (create) create the resource
|
||||||
- **update**: (update) override the lease time of this resource and get/renew a secret on the specified duration e.g 1m, 2d, 5m10s
|
- **update**: (update) override the lease time of this resource and get/renew a secret on the specified duration e.g 1m, 2d, 5m10s
|
||||||
- **renew**: (renewal) override the default behavour on this resource, renew the resource when coming close to expiration e.g true, TRUE
|
- **renew**: (renewal) override the default behavour on this resource, renew the resource when coming close to expiration e.g true, TRUE
|
||||||
|
|
|
@ -28,8 +28,8 @@ type authAppRolePlugin struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type appRoleLogin struct {
|
type appRoleLogin struct {
|
||||||
RoleId string `json:"role_id,omitempty"`
|
RoleID string `json:"role_id,omitempty"`
|
||||||
SecretId string `json:"secret_id,omitempty"`
|
SecretID string `json:"secret_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAppRolePlugin creates a new App Role plugin
|
// NewAppRolePlugin creates a new App Role plugin
|
||||||
|
@ -42,19 +42,19 @@ func NewAppRolePlugin(client *api.Client) AuthInterface {
|
||||||
// Create a approle plugin with the secret id and role id provided in the file
|
// Create a approle plugin with the secret id and role id provided in the file
|
||||||
func (r authAppRolePlugin) Create(cfg map[string]string) (string, error) {
|
func (r authAppRolePlugin) Create(cfg map[string]string) (string, error) {
|
||||||
// step: extract the options
|
// step: extract the options
|
||||||
roleId, _ := cfg["role_id"]
|
roleID, _ := cfg["role_id"]
|
||||||
secretId, _ := cfg["secret_id"]
|
secretID, _ := cfg["secret_id"]
|
||||||
|
|
||||||
if roleId == "" {
|
if roleID == "" {
|
||||||
roleId = os.Getenv("VAULT_SIDEKICK_ROLE_ID")
|
roleID = os.Getenv("VAULT_SIDEKICK_ROLE_ID")
|
||||||
}
|
}
|
||||||
if secretId == "" {
|
if secretID == "" {
|
||||||
secretId = os.Getenv("VAULT_SIDEKICK_SECRET_ID")
|
secretID = os.Getenv("VAULT_SIDEKICK_SECRET_ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
// step: create the token request
|
// step: create the token request
|
||||||
request := r.client.NewRequest("POST", "/v1/auth/approle/login")
|
request := r.client.NewRequest("POST", "/v1/auth/approle/login")
|
||||||
login := appRoleLogin{SecretId: secretId, RoleId: roleId}
|
login := appRoleLogin{SecretID: secretID, RoleID: roleID}
|
||||||
if err := request.SetJSONBody(login); err != nil {
|
if err := request.SetJSONBody(login); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,8 @@ type config struct {
|
||||||
statsInterval time.Duration
|
statsInterval time.Duration
|
||||||
// the timeout for a exec command
|
// the timeout for a exec command
|
||||||
execTimeout time.Duration
|
execTimeout time.Duration
|
||||||
|
// version flag
|
||||||
|
showVersion bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -63,13 +65,13 @@ func init() {
|
||||||
flag.StringVar(&options.vaultCaFile, "ca-cert", "", "the path to the file container the CA used to verify the vault service")
|
flag.StringVar(&options.vaultCaFile, "ca-cert", "", "the path to the file container the CA used to verify the vault service")
|
||||||
flag.DurationVar(&options.statsInterval, "stats", time.Duration(1)*time.Hour, "the interval to produce statistics on the accessed resources")
|
flag.DurationVar(&options.statsInterval, "stats", time.Duration(1)*time.Hour, "the interval to produce statistics on the accessed resources")
|
||||||
flag.DurationVar(&options.execTimeout, "exec-timeout", time.Duration(60)*time.Second, "the timeout applied to commands on the exec option")
|
flag.DurationVar(&options.execTimeout, "exec-timeout", time.Duration(60)*time.Second, "the timeout applied to commands on the exec option")
|
||||||
|
flag.BoolVar(&options.showVersion, "version", false, "show the vault-sidekick version")
|
||||||
flag.Var(options.resources, "cn", "a resource to retrieve and monitor from vault")
|
flag.Var(options.resources, "cn", "a resource to retrieve and monitor from vault")
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseOptions validate the command line options and validates them
|
// parseOptions validate the command line options and validates them
|
||||||
func parseOptions() error {
|
func parseOptions() error {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
return validateOptions(&options)
|
return validateOptions(&options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
59
formats.go
59
formats.go
|
@ -21,66 +21,67 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func writeIniFile(filename string, data map[string]interface{}) error {
|
func writeIniFile(filename string, data map[string]interface{}, mode os.FileMode) error {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
for key, val := range data {
|
for key, val := range data {
|
||||||
buf.WriteString(fmt.Sprintf("%s = %v\n", key, val))
|
buf.WriteString(fmt.Sprintf("%s = %v\n", key, val))
|
||||||
}
|
}
|
||||||
|
|
||||||
return writeFile(filename, buf.Bytes())
|
return writeFile(filename, buf.Bytes(), mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeCSVFile(filename string, data map[string]interface{}) error {
|
func writeCSVFile(filename string, data map[string]interface{}, mode os.FileMode) error {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
for key, val := range data {
|
for key, val := range data {
|
||||||
buf.WriteString(fmt.Sprintf("%s,%v\n", key, val))
|
buf.WriteString(fmt.Sprintf("%s,%v\n", key, val))
|
||||||
}
|
}
|
||||||
|
|
||||||
return writeFile(filename, buf.Bytes())
|
return writeFile(filename, buf.Bytes(), mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeYAMLFile(filename string, data map[string]interface{}) error {
|
func writeYAMLFile(filename string, data map[string]interface{}, mode os.FileMode) error {
|
||||||
// marshall the content to yaml
|
// marshall the content to yaml
|
||||||
content, err := yaml.Marshal(data)
|
content, err := yaml.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return writeFile(filename, content)
|
return writeFile(filename, content, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeEnvFile(filename string, data map[string]interface{}) error {
|
func writeEnvFile(filename string, data map[string]interface{}, mode os.FileMode) error {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
for key, val := range data {
|
for key, val := range data {
|
||||||
buf.WriteString(fmt.Sprintf("%s=%v\n", strings.ToUpper(key), val))
|
buf.WriteString(fmt.Sprintf("%s=%v\n", strings.ToUpper(key), val))
|
||||||
}
|
}
|
||||||
|
|
||||||
return writeFile(filename, buf.Bytes())
|
return writeFile(filename, buf.Bytes(), mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeCertificateFile(filename string, data map[string]interface{}) error {
|
func writeCertificateFile(filename string, data map[string]interface{}, mode os.FileMode) error {
|
||||||
files := map[string]string{
|
files := map[string]string{
|
||||||
"certificate": "crt",
|
"certificate": "crt",
|
||||||
"issuing_ca": "ca",
|
"issuing_ca": "ca",
|
||||||
"private_key": "key",
|
"private_key": "key",
|
||||||
}
|
}
|
||||||
for key, suffix := range files {
|
for key, suffix := range files {
|
||||||
filename := fmt.Sprintf("%s.%s", filename, suffix)
|
name := fmt.Sprintf("%s.%s", filename, suffix)
|
||||||
content, found := data[key]
|
content, found := data[key]
|
||||||
if !found {
|
if !found {
|
||||||
glog.Errorf("didn't find the certification option: %s in the resource: %s", key, filename)
|
glog.Errorf("didn't find the certification option: %s in the resource: %s", key, name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// step: write the file
|
// step: write the file
|
||||||
if err := writeFile(filename, []byte(fmt.Sprintf("%s", content))); err != nil {
|
if err := writeFile(name, []byte(fmt.Sprintf("%s", content)), mode); err != nil {
|
||||||
glog.Errorf("failed to write resource: %s, elemment: %s, filename: %s, error: %s", filename, suffix, filename, err)
|
glog.Errorf("failed to write resource: %s, element: %s, filename: %s, error: %s", filename, suffix, name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,7 +90,7 @@ func writeCertificateFile(filename string, data map[string]interface{}) error {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeCertificateBundleFile(filename string, data map[string]interface{}) error {
|
func writeCertificateBundleFile(filename string, data map[string]interface{}, mode os.FileMode) error {
|
||||||
bundleFile := fmt.Sprintf("%s-bundle.pem", filename)
|
bundleFile := fmt.Sprintf("%s-bundle.pem", filename)
|
||||||
keyFile := fmt.Sprintf("%s-key.pem", filename)
|
keyFile := fmt.Sprintf("%s-key.pem", filename)
|
||||||
caFile := fmt.Sprintf("%s-ca.pem", filename)
|
caFile := fmt.Sprintf("%s-ca.pem", filename)
|
||||||
|
@ -100,22 +101,22 @@ func writeCertificateBundleFile(filename string, data map[string]interface{}) er
|
||||||
ca := fmt.Sprintf("%s\n", data["issuing_ca"])
|
ca := fmt.Sprintf("%s\n", data["issuing_ca"])
|
||||||
certificate := fmt.Sprintf("%s\n", data["certificate"])
|
certificate := fmt.Sprintf("%s\n", data["certificate"])
|
||||||
|
|
||||||
if err := writeFile(bundleFile, []byte(bundle)); err != nil {
|
if err := writeFile(bundleFile, []byte(bundle), mode); err != nil {
|
||||||
glog.Errorf("failed to write the bundled certificate file, error: %s", err)
|
glog.Errorf("failed to write the bundled certificate file, error: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := writeFile(certFile, []byte(certificate)); err != nil {
|
if err := writeFile(certFile, []byte(certificate), mode); err != nil {
|
||||||
glog.Errorf("failed to write the certificate file, errro: %s", err)
|
glog.Errorf("failed to write the certificate file, errro: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := writeFile(caFile, []byte(ca)); err != nil {
|
if err := writeFile(caFile, []byte(ca), mode); err != nil {
|
||||||
glog.Errorf("failed to write the ca file, errro: %s", err)
|
glog.Errorf("failed to write the ca file, errro: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := writeFile(keyFile, []byte(key)); err != nil {
|
if err := writeFile(keyFile, []byte(key), mode); err != nil {
|
||||||
glog.Errorf("failed to write the key file, errro: %s", err)
|
glog.Errorf("failed to write the key file, errro: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -123,15 +124,15 @@ func writeCertificateBundleFile(filename string, data map[string]interface{}) er
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeTxtFile(filename string, data map[string]interface{}) error {
|
func writeTxtFile(filename string, data map[string]interface{}, mode os.FileMode) error {
|
||||||
keys := getKeys(data)
|
keys := getKeys(data)
|
||||||
if len(keys) > 1 {
|
if len(keys) > 1 {
|
||||||
// step: for plain formats we need to iterate the keys and produce a file per key
|
// step: for plain formats we need to iterate the keys and produce a file per key
|
||||||
for suffix, content := range data {
|
for suffix, content := range data {
|
||||||
filename := fmt.Sprintf("%s.%s", filename, suffix)
|
name := fmt.Sprintf("%s.%s", filename, suffix)
|
||||||
if err := writeFile(filename, []byte(fmt.Sprintf("%v", content))); err != nil {
|
if err := writeFile(name, []byte(fmt.Sprintf("%v", content)), mode); err != nil {
|
||||||
glog.Errorf("failed to write resource: %s, elemment: %s, filename: %s, error: %s",
|
glog.Errorf("failed to write resource: %s, elemment: %s, filename: %s, error: %s",
|
||||||
filename, suffix, filename, err)
|
filename, suffix, name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,22 +143,20 @@ func writeTxtFile(filename string, data map[string]interface{}) error {
|
||||||
value, _ := data[keys[0]]
|
value, _ := data[keys[0]]
|
||||||
content := []byte(fmt.Sprintf("%s", value))
|
content := []byte(fmt.Sprintf("%s", value))
|
||||||
|
|
||||||
return writeFile(filename, content)
|
return writeFile(filename, content, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeJSONFile(filename string, data map[string]interface{}) error {
|
func writeJSONFile(filename string, data map[string]interface{}, mode os.FileMode) error {
|
||||||
content, err := json.MarshalIndent(data, "", " ")
|
content, err := json.MarshalIndent(data, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return writeFile(filename, content)
|
return writeFile(filename, content, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeFile ... writes the content to a file .. dah
|
// writeFile writes the file to stdout or an actual file
|
||||||
// filename : the path to the file
|
func writeFile(filename string, content []byte, mode os.FileMode) error {
|
||||||
// content : the content to be written
|
|
||||||
func writeFile(filename string, content []byte) error {
|
|
||||||
if options.dryRun {
|
if options.dryRun {
|
||||||
glog.Infof("dry-run: filename: %s, content:", filename)
|
glog.Infof("dry-run: filename: %s, content:", filename)
|
||||||
fmt.Printf("%s\n", string(content))
|
fmt.Printf("%s\n", string(content))
|
||||||
|
@ -165,5 +164,5 @@ func writeFile(filename string, content []byte) error {
|
||||||
}
|
}
|
||||||
glog.V(3).Infof("saving the file: %s", filename)
|
glog.V(3).Infof("saving the file: %s", filename)
|
||||||
|
|
||||||
return ioutil.WriteFile(filename, content, 0664)
|
return ioutil.WriteFile(filename, content, mode)
|
||||||
}
|
}
|
||||||
|
|
21
generate.go
21
generate.go
|
@ -21,32 +21,31 @@ import (
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+,.?/:;{}[]`~")
|
var stdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+,.?/:;{}[]`~")
|
||||||
|
|
||||||
func NewPassword(length int) string {
|
func newPassword(length int) string {
|
||||||
return rand_char(length, StdChars)
|
return randString(length, stdChars)
|
||||||
}
|
}
|
||||||
|
|
||||||
func rand_char(length int, chars []byte) string {
|
func randString(length int, chars []byte) string {
|
||||||
new_pword := make([]byte, length)
|
pass := make([]byte, length)
|
||||||
random_data := make([]byte, length+(length/4)) // storage for random bytes.
|
data := make([]byte, length+(length/4)) // storage for random bytes.
|
||||||
clen := byte(len(chars))
|
clen := byte(len(chars))
|
||||||
maxrb := byte(256 - (256 % len(chars)))
|
maxrb := byte(256 - (256 % len(chars)))
|
||||||
i := 0
|
i := 0
|
||||||
for {
|
for {
|
||||||
if _, err := io.ReadFull(rand.Reader, random_data); err != nil {
|
if _, err := io.ReadFull(rand.Reader, data); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
for _, c := range random_data {
|
for _, c := range data {
|
||||||
if c >= maxrb {
|
if c >= maxrb {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
new_pword[i] = chars[c%clen]
|
pass[i] = chars[c%clen]
|
||||||
i++
|
i++
|
||||||
if i == length {
|
if i == length {
|
||||||
return string(new_pword)
|
return string(pass)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panic("unreachable")
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,18 +9,19 @@ spec:
|
||||||
labels:
|
labels:
|
||||||
name: vault-demo
|
name: vault-demo
|
||||||
annotations:
|
annotations:
|
||||||
repository: https://github.com/UKHomeOffice/vault-sidekick
|
build: https://github.com/UKHomeOffice/vault-sidekick
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: sidekick
|
- name: sidekick
|
||||||
image: quay.io/ukhomeofficedigital/vault-sidekick:v0.3.0
|
image: quay.io/ukhomeofficedigital/vault-sidekick:v0.3.1
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
memory: 50Mi
|
memory: 50Mi
|
||||||
args:
|
args:
|
||||||
- -tls-skip-verify=true
|
- -tls-skip-verify=false
|
||||||
- -cn=pki:services/${NAMESPACE}/pki/issue/default:fmt=bundle,common_name=demo.${NAMESPACE}.svc.cluster.local,file=platform
|
- -cn=pki:services/${NAMESPACE}/pki/issue/default:fmt=bundle,common_name=demo.${NAMESPACE}.svc.cluster.local,file=platform,mode=0640
|
||||||
|
- -ca-cert=/ca/caroot.bundle
|
||||||
- -logtostderr=true
|
- -logtostderr=true
|
||||||
- -v=3
|
- -v=3
|
||||||
env:
|
env:
|
||||||
|
@ -39,6 +40,8 @@ spec:
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: secrets
|
- name: secrets
|
||||||
mountPath: /etc/secrets
|
mountPath: /etc/secrets
|
||||||
|
- name: ca-bundle
|
||||||
|
mountPath: /ca
|
||||||
- name: nginx
|
- name: nginx
|
||||||
image: quay.io/ukhomeofficedigital/nginx-proxy:v1.5.1
|
image: quay.io/ukhomeofficedigital/nginx-proxy:v1.5.1
|
||||||
resources:
|
resources:
|
||||||
|
@ -79,3 +82,6 @@ spec:
|
||||||
volumes:
|
volumes:
|
||||||
- name: secrets
|
- name: secrets
|
||||||
emptyDir: {}
|
emptyDir: {}
|
||||||
|
- name: ca-bundle
|
||||||
|
secret:
|
||||||
|
secretName: ca-bundle
|
17
main.go
17
main.go
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
@ -24,19 +25,23 @@ import (
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
var (
|
||||||
Prog = "vault-sidekick"
|
prog = "vault-sidekick"
|
||||||
Version = "v0.3.0"
|
release = "v0.3.1"
|
||||||
|
gitsha = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
version := fmt.Sprintf("%s (git+sha %s)", release, gitsha)
|
||||||
// step: parse and validate the command line / environment options
|
// step: parse and validate the command line / environment options
|
||||||
|
|
||||||
if err := parseOptions(); err != nil {
|
if err := parseOptions(); err != nil {
|
||||||
showUsage("invalid options, %s", err)
|
showUsage("invalid options, %s", err)
|
||||||
}
|
}
|
||||||
|
if options.showVersion {
|
||||||
glog.Infof("starting the %s, version: %s", Prog, Version)
|
fmt.Printf("%s %s\n", prog, version)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
glog.Infof("starting the %s, %s", prog, version)
|
||||||
|
|
||||||
// step: create a client to vault
|
// step: create a client to vault
|
||||||
vault, err := NewVaultService(options.vaultURL)
|
vault, err := NewVaultService(options.vaultURL)
|
||||||
|
|
19
utils.go
19
utils.go
|
@ -86,7 +86,6 @@ func readConfigFile(filename string) (map[string]string, error) {
|
||||||
default:
|
default:
|
||||||
return readJSONFile(filename)
|
return readJSONFile(filename)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("unsupported config file format: %s", suffix)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// readJsonFile read in and unmarshall the data into a map
|
// readJsonFile read in and unmarshall the data into a map
|
||||||
|
@ -169,21 +168,21 @@ func processResource(rn *VaultResource, data map[string]interface{}) (err error)
|
||||||
case "yaml":
|
case "yaml":
|
||||||
fallthrough
|
fallthrough
|
||||||
case "yml":
|
case "yml":
|
||||||
err = writeYAMLFile(filename, data)
|
err = writeYAMLFile(filename, data, rn.fileMode)
|
||||||
case "json":
|
case "json":
|
||||||
err = writeJSONFile(filename, data)
|
err = writeJSONFile(filename, data, rn.fileMode)
|
||||||
case "ini":
|
case "ini":
|
||||||
err = writeIniFile(filename, data)
|
err = writeIniFile(filename, data, rn.fileMode)
|
||||||
case "csv":
|
case "csv":
|
||||||
err = writeCSVFile(filename, data)
|
err = writeCSVFile(filename, data, rn.fileMode)
|
||||||
case "env":
|
case "env":
|
||||||
err = writeEnvFile(filename, data)
|
err = writeEnvFile(filename, data, rn.fileMode)
|
||||||
case "cert":
|
case "cert":
|
||||||
err = writeCertificateFile(filename, data)
|
err = writeCertificateFile(filename, data, rn.fileMode)
|
||||||
case "txt":
|
case "txt":
|
||||||
err = writeTxtFile(filename, data)
|
err = writeTxtFile(filename, data, rn.fileMode)
|
||||||
case "bundle":
|
case "bundle":
|
||||||
err = writeCertificateBundleFile(filename, data)
|
err = writeCertificateBundleFile(filename, data, rn.fileMode)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown output format: %s", rn.format)
|
return fmt.Errorf("unknown output format: %s", rn.format)
|
||||||
}
|
}
|
||||||
|
@ -198,7 +197,7 @@ func processResource(rn *VaultResource, data map[string]interface{}) (err error)
|
||||||
cmd := exec.Command(rn.execPath, filename)
|
cmd := exec.Command(rn.execPath, filename)
|
||||||
cmd.Start()
|
cmd.Start()
|
||||||
timer := time.AfterFunc(options.execTimeout, func() {
|
timer := time.AfterFunc(options.execTimeout, func() {
|
||||||
if err := cmd.Process.Kill(); err != nil {
|
if err = cmd.Process.Kill(); err != nil {
|
||||||
glog.Errorf("failed to kill the command, pid: %d, error: %s", cmd.Process.Pid, err)
|
glog.Errorf("failed to kill the command, pid: %d, error: %s", cmd.Process.Pid, err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
8
vault.go
8
vault.go
|
@ -312,7 +312,8 @@ func (r VaultService) revoke(lease string) error {
|
||||||
|
|
||||||
// get retrieves a secret from the vault
|
// get retrieves a secret from the vault
|
||||||
// rn : the watched resource
|
// rn : the watched resource
|
||||||
func (r VaultService) get(rn *watchedResource) (err error) {
|
func (r VaultService) get(rn *watchedResource) error {
|
||||||
|
var err error
|
||||||
var secret *api.Secret
|
var secret *api.Secret
|
||||||
// step: not sure who to cast map[string]string to map[string]interface{} doesn't like it anyway i try and do it
|
// step: not sure who to cast map[string]string to map[string]interface{} doesn't like it anyway i try and do it
|
||||||
|
|
||||||
|
@ -320,7 +321,7 @@ func (r VaultService) get(rn *watchedResource) (err error) {
|
||||||
for k, v := range rn.resource.options {
|
for k, v := range rn.resource.options {
|
||||||
params[k] = interface{}(v)
|
params[k] = interface{}(v)
|
||||||
}
|
}
|
||||||
glog.V(10).Infof("get, resource: %s, path: %s, params: %v", rn.resource.resource, rn.resource.path, params)
|
glog.V(10).Infof("resource: %s, path: %s, params: %v", rn.resource.resource, rn.resource.path, params)
|
||||||
|
|
||||||
glog.V(5).Infof("attempting to retrieve the resource: %s from vault", rn.resource)
|
glog.V(5).Infof("attempting to retrieve the resource: %s from vault", rn.resource)
|
||||||
// step: perform a request to vault
|
// step: perform a request to vault
|
||||||
|
@ -332,7 +333,6 @@ func (r VaultService) get(rn *watchedResource) (err error) {
|
||||||
}
|
}
|
||||||
resp, err := r.client.RawRequest(request)
|
resp, err := r.client.RawRequest(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("FAILED HERE")
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// step: read the response
|
// step: read the response
|
||||||
|
@ -370,7 +370,7 @@ func (r VaultService) get(rn *watchedResource) (err error) {
|
||||||
// We must generate the secret if we have the create flag
|
// We must generate the secret if we have the create flag
|
||||||
if rn.resource.create && secret == nil && err == nil {
|
if rn.resource.create && secret == nil && err == nil {
|
||||||
glog.V(3).Infof("Create param specified, creating resource: %s", rn.resource.path)
|
glog.V(3).Infof("Create param specified, creating resource: %s", rn.resource.path)
|
||||||
params["value"] = NewPassword(int(rn.resource.size))
|
params["value"] = newPassword(int(rn.resource.size))
|
||||||
secret, err = r.client.Logical().Write(fmt.Sprintf(rn.resource.path), params)
|
secret, err = r.client.Logical().Write(fmt.Sprintf(rn.resource.path), params)
|
||||||
glog.V(3).Infof("Secret created: %s", rn.resource.path)
|
glog.V(3).Infof("Secret created: %s", rn.resource.path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
|
@ -18,6 +18,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -43,6 +44,8 @@ const (
|
||||||
optionCreate = "create"
|
optionCreate = "create"
|
||||||
// optionSize sets the initial size of a password secret
|
// optionSize sets the initial size of a password secret
|
||||||
optionSize = "size"
|
optionSize = "size"
|
||||||
|
// optionsMode is the file permissions on the secret
|
||||||
|
optionMode = "mode"
|
||||||
// defaultSize sets the default size of a generic secret
|
// defaultSize sets the default size of a generic secret
|
||||||
defaultSize = 20
|
defaultSize = 20
|
||||||
)
|
)
|
||||||
|
@ -67,10 +70,11 @@ var (
|
||||||
|
|
||||||
func defaultVaultResource() *VaultResource {
|
func defaultVaultResource() *VaultResource {
|
||||||
return &VaultResource{
|
return &VaultResource{
|
||||||
|
fileMode: os.FileMode(0664),
|
||||||
format: "yaml",
|
format: "yaml",
|
||||||
|
options: make(map[string]string, 0),
|
||||||
renewable: false,
|
renewable: false,
|
||||||
revoked: false,
|
revoked: false,
|
||||||
options: make(map[string]string, 0),
|
|
||||||
size: defaultSize,
|
size: defaultSize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,6 +107,8 @@ type VaultResource struct {
|
||||||
execPath string
|
execPath string
|
||||||
// additional options to the resource
|
// additional options to the resource
|
||||||
options map[string]string
|
options map[string]string
|
||||||
|
// the file permissions on the resource
|
||||||
|
fileMode os.FileMode
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFilename generates a resource filename by default the resource name and resource type, which
|
// GetFilename generates a resource filename by default the resource name and resource type, which
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -63,11 +64,23 @@ func (r *VaultResources) Set(value string) error {
|
||||||
return fmt.Errorf("invalid resource option: %s, must have a value", x)
|
return fmt.Errorf("invalid resource option: %s, must have a value", x)
|
||||||
}
|
}
|
||||||
// step: set the name and value
|
// step: set the name and value
|
||||||
name := kp[0]
|
name := strings.TrimSpace(kp[0])
|
||||||
value := strings.Replace(kp[1], "|", ",", -1)
|
value := strings.Replace(kp[1], "|", ",", -1)
|
||||||
|
|
||||||
// step: extract the control options from the path resource parameters
|
// step: extract the control options from the path resource parameters
|
||||||
switch name {
|
switch name {
|
||||||
|
case optionMode:
|
||||||
|
if !strings.HasPrefix(value, "0") {
|
||||||
|
value = "0" + value
|
||||||
|
}
|
||||||
|
if len(value) != 4 {
|
||||||
|
return errors.New("the file permission invalid, should be octal 0444 or alike")
|
||||||
|
}
|
||||||
|
v, err := strconv.ParseUint(value, 0, 32)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("invalid file permissions on resource")
|
||||||
|
}
|
||||||
|
rn.fileMode = os.FileMode(v)
|
||||||
case optionFormat:
|
case optionFormat:
|
||||||
if matched := resourceFormatRegex.MatchString(value); !matched {
|
if matched := resourceFormatRegex.MatchString(value); !matched {
|
||||||
return fmt.Errorf("unsupported output format: %s", value)
|
return fmt.Errorf("unsupported output format: %s", value)
|
||||||
|
|
|
@ -19,7 +19,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fmt"
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"github.com/hashicorp/vault/api"
|
"github.com/hashicorp/vault/api"
|
||||||
)
|
)
|
||||||
|
@ -51,8 +50,12 @@ func (r *watchedResource) notifyOnRenewal(ch chan *watchedResource) {
|
||||||
r.renewalTime = r.resource.update
|
r.renewalTime = r.resource.update
|
||||||
// step: if the answer is no, we set the notification between 80-95% of the lease time of the secret
|
// step: if the answer is no, we set the notification between 80-95% of the lease time of the secret
|
||||||
if r.renewalTime <= 0 {
|
if r.renewalTime <= 0 {
|
||||||
|
// if there is no lease time, we canout set a renewal, just fade into the background
|
||||||
|
if r.secret.LeaseDuration <= 0 {
|
||||||
|
glog.Warningf("resource: %s has no lease duration, no custom update set, so item will not be updated", r.resource.path)
|
||||||
|
return
|
||||||
|
}
|
||||||
r.renewalTime = r.calculateRenewal()
|
r.renewalTime = r.calculateRenewal()
|
||||||
fmt.Printf("seconds: %s", r.renewalTime)
|
|
||||||
}
|
}
|
||||||
glog.V(3).Infof("setting a renewal notification on resource: %s, time: %s", r.resource, r.renewalTime)
|
glog.V(3).Infof("setting a renewal notification on resource: %s, time: %s", r.resource, r.renewalTime)
|
||||||
// step: wait for the duration
|
// step: wait for the duration
|
||||||
|
|
Reference in a new issue