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

[Storage-backend][vault pki] Add storage backend vault pki to template certs from vault #774

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
bin/
.vscode/
Empty file added .vscode/confd.go
Empty file.
16 changes: 16 additions & 0 deletions backends/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/kelseyhightower/confd/backends/redis"
"github.com/kelseyhightower/confd/backends/ssm"
"github.com/kelseyhightower/confd/backends/vault"
"github.com/kelseyhightower/confd/backends/vaultpki"
"github.com/kelseyhightower/confd/backends/zookeeper"
"github.com/kelseyhightower/confd/log"
)
Expand Down Expand Up @@ -79,6 +80,21 @@ func New(config Config) (StoreClient, error) {
"path": config.Path,
}
return vault.New(backendNodes[0], config.AuthType, vaultConfig)
case "vaultpki":
vaultPkiConfig := map[string]string{
"app-id": config.AppID,
"user-id": config.UserID,
"role-id": config.RoleID,
"secret-id": config.SecretID,
"username": config.Username,
"password": config.Password,
"token": config.AuthToken,
"cert": config.ClientCert,
"key": config.ClientKey,
"caCert": config.ClientCaKeys,
"path": config.Path,
}
return vaultpki.NewClient(backendNodes[0], config.AuthType, vaultPkiConfig)
case "dynamodb":
table := config.Table
log.Info("DynamoDB table set to " + table)
Expand Down
28 changes: 15 additions & 13 deletions backends/vault/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ func panicToError(err *error) {
}
}

// authenticate with the remote client
func authenticate(c *vaultapi.Client, authType string, params map[string]string) (err error) {
// Authenticate with the remote client
func Authenticate(c *vaultapi.Client, authType string, params map[string]string) (err error) {
var secret *vaultapi.Secret

// handle panics gracefully by creating an error
Expand Down Expand Up @@ -116,7 +116,8 @@ func authenticate(c *vaultapi.Client, authType string, params map[string]string)
return nil
}

func getConfig(address, cert, key, caCert string) (*vaultapi.Config, error) {
// GetConfig methot in vault backedn to Get Config
func GetConfig(address, cert, key, caCert string) (*vaultapi.Config, error) {
conf := vaultapi.DefaultConfig()
conf.Address = address

Expand Down Expand Up @@ -154,7 +155,7 @@ func New(address, authType string, params map[string]string) (*Client, error) {
return nil, errors.New("you have to set the auth type when using the vault backend")
}
log.Info("Vault authentication backend set to %s", authType)
conf, err := getConfig(address, params["cert"], params["key"], params["caCert"])
conf, err := GetConfig(address, params["cert"], params["key"], params["caCert"])

if err != nil {
return nil, err
Expand All @@ -165,14 +166,15 @@ func New(address, authType string, params map[string]string) (*Client, error) {
return nil, err
}

if err := authenticate(c, authType, params); err != nil {
if err := Authenticate(c, authType, params); err != nil {
return nil, err
}
return &Client{c}, nil
}

// GetValues queries etcd for keys prefixed by prefix.
func (c *Client) GetValues(keys []string) (map[string]string, error) {
log.Debug("%+v : keys", keys)
branches := make(map[string]bool)
for _, key := range keys {
walkTree(c, key, branches)
Expand All @@ -192,22 +194,22 @@ func (c *Client) GetValues(keys []string) (map[string]string, error) {

// if the key has only one string value
// treat it as a string and not a map of values
if val, ok := isKV(resp.Data); ok {
if val, ok := IsKV(resp.Data); ok {
vars[key] = val
} else {
// save the json encoded response
// and flatten it to allow usage of gets & getvs
// and Flatten it to allow usage of gets & getvs
js, _ := json.Marshal(resp.Data)
vars[key] = string(js)
flatten(key, resp.Data, vars)
Flatten(key, resp.Data, vars)
}
}
return vars, nil
}

// isKV checks if a given map has only one key of type string
// IsKV checks if a given map has only one key of type string
// if so, returns the value of that key
func isKV(data map[string]interface{}) (string, bool) {
func IsKV(data map[string]interface{}) (string, bool) {
if len(data) == 1 {
if value, ok := data["value"]; ok {
if text, ok := value.(string); ok {
Expand All @@ -218,8 +220,8 @@ func isKV(data map[string]interface{}) (string, bool) {
return "", false
}

// recursively walks on all the values of a specific key and set them in the variables map
func flatten(key string, value interface{}, vars map[string]string) {
// Flatten recursively walks on all the values of a specific key and set them in the variables map
func Flatten(key string, value interface{}, vars map[string]string) {
switch value.(type) {
case string:
log.Debug("setting key %s to: %s", key, value)
Expand All @@ -228,7 +230,7 @@ func flatten(key string, value interface{}, vars map[string]string) {
inner := value.(map[string]interface{})
for innerKey, innerValue := range inner {
innerKey = path.Join(key, "/", innerKey)
flatten(innerKey, innerValue, vars)
Flatten(innerKey, innerValue, vars)
}
default: // we don't know how to handle non string or maps of strings
log.Warning("type of '%s' is not supported (%T)", key, value)
Expand Down
189 changes: 189 additions & 0 deletions backends/vaultpki/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package vaultpki

import (
"encoding/json"
"errors"
"path"
"strings"

vaultapi "github.com/hashicorp/vault/api"
vaultbackend "github.com/kelseyhightower/confd/backends/vault"
"github.com/kelseyhightower/confd/log"
)

// Client Embed from vault client into vault pki client
type Client struct {
*vaultapi.Client
}

// NewClient "inherit" the new method from the vault client
func NewClient(address, authType string, params map[string]string) (*Client, error) {
if authType == "" {
return nil, errors.New("you have to set the auth type when using the vault backend")
}
log.Info("Vault authentication backend set to %s", authType)
conf, err := vaultbackend.GetConfig(address, params["cert"], params["key"], params["caCert"])

if err != nil {
return nil, err
}

c, err := vaultapi.NewClient(conf)
if err != nil {
return nil, err
}

if err := vaultbackend.Authenticate(c, authType, params); err != nil {
return nil, err
}

return &Client{c}, err
}

// request this is the struct that will be sent to vault to issue a cert
type request struct {
mountPath string
role string
commonName string
}

// parseCommonName this function parses the key return a request struct
func parseCommonName(key string) *request {
// The key must be of the format /mountpath/issue/rolename/commonname
if strings.Contains(key, "issue") {
splitKeyList := strings.Split(key, "issue")
splitRoleList := strings.Split(splitKeyList[1], "/")
log.Debug("getCommonName path: %s, role: %s, commonName: %s", splitKeyList[0], splitRoleList[1], splitRoleList[2])
k := request{splitKeyList[0], splitRoleList[1], splitRoleList[2]}
return &k
}

return &request{}

}

func issueCert(c *Client, r *request) (map[string]string, error) {
log.Debug("issueCert path: %s, role: %s, commonName: %s", r.mountPath, r.role, r.commonName)
writePath := r.mountPath + "issue" + "/my-role"

payload := map[string]interface{}{
"common_name": r.commonName,
}
resp, err := c.Logical().Write(writePath, payload)
vars := make(map[string]string)

if err != nil {
log.Debug("there was an error issuing a cert for role %s", r.role)
return nil, err
}

// save the json encoded response
// and flatten it to allow usage of gets & getvs
js, _ := json.Marshal(resp.Data)
vars[writePath] = string(js)
vaultbackend.Flatten(writePath+"/"+r.commonName, resp.Data, vars)

return vars, nil
}

func walkTree(c *Client, key string, branches map[string]bool) error {
log.Debug("listing %s from vault", key)

// strip trailing slash as long as it's not the only character
if last := len(key) - 1; last > 0 && key[last] == '/' {
key = key[:last]
}
if branches[key] {
// already processed this branch
return nil
}
branches[key] = true

resp, err := c.Logical().List(key)

if err != nil {
log.Debug("there was an error extracting %s", key)
return err
}
if resp == nil || resp.Data == nil || resp.Data["keys"] == nil {
return nil
}

switch resp.Data["keys"].(type) {
case []interface{}:
// expected
default:
log.Warning("key list type of '%s' is not supported (%T)", key, resp.Data["keys"])
return nil
}

keyList := resp.Data["keys"].([]interface{})
for _, innerKey := range keyList {
switch innerKey.(type) {

case string:
innerKey = path.Join(key, "/", innerKey.(string))
walkTree(c, innerKey.(string), branches)

default: // we don't know how to handle other data types
log.Warning("type of '%s' is not supported (%T)", key, keyList)
}
}
return nil
}

func (c *Client) getKvVault(keys []string) (map[string]string, error) {
branches := make(map[string]bool)
for _, key := range keys {
walkTree(c, key, branches)
}
vars := make(map[string]string)
for key := range branches {
log.Debug("getting %s from vault", key)
resp, err := c.Logical().Read(key)

if err != nil {
log.Debug("there was an error extracting %s", key)
return nil, err
}
if resp == nil || resp.Data == nil {
continue
}

// if the key has only one string value
// treat it as a string and not a map of values
if val, ok := vaultbackend.IsKV(resp.Data); ok {
vars[key] = val
} else {
// save the json encoded response
// and Flatten it to allow usage of gets & getvs
js, _ := json.Marshal(resp.Data)
vars[key] = string(js)
vaultbackend.Flatten(key, resp.Data, vars)
}
}
return vars, nil
}

// GetValues to be exported
func (c *Client) GetValues(keys []string) (map[string]string, error) {
vars := make(map[string]string)
var err error
for _, key := range keys {
log.Debug("key: %+v", key)
k := parseCommonName(key)
if (request{}) == *k {
vars, err = c.getKvVault(keys)
break
}
vars, err = issueCert(c, k)
}

return vars, err
}

// WatchPrefix - not implemented at the moment
func (c *Client) WatchPrefix(prefix string, keys []string, waitIndex uint64, stopChan chan bool) (uint64, error) {
<-stopChan
return 0, nil
}
5 changes: 5 additions & 0 deletions integration/confdir/conf.d/01-cert-split.sh.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[template]
mode = "0744"
src = "split.sh.tmpl"
dest = "/tmp/split.sh"
keys = []
8 changes: 8 additions & 0 deletions integration/confdir/conf.d/cert.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[template]
mode = "0644"
src = "certkey.pem.tmpl"
dest = "/tmp/certkey.pem"
keys = [
"/pki/issue/my-role/www.example.com",
]
reload_cmd = "/tmp/split.sh"
8 changes: 8 additions & 0 deletions integration/confdir/templates/certkey.pem.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{{if exists "/pki/issue/my-role/www.example.com/certificate"}}
{{- $certificate := getv "/pki/issue/my-role/www.example.com/certificate" -}}
{{- $private_key := getv "/pki/issue/my-role/www.example.com/private_key" -}}
{
"certificate": "{{ replace $certificate "\n" "\\n" -1 }}",
"key": "{{ replace $private_key "\n" "\\n" -1 }}"
}
{{end}}
3 changes: 3 additions & 0 deletions integration/confdir/templates/split.sh.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash
cat /tmp/certkey.pem | jq -r .certificate > /tmp/certificate.pem && chmod 644 /tmp/certificate.pem
cat /tmp/certkey.pem | jq -r .key > /tmp/key.pem && chmod 600 /tmp/key.pem
5 changes: 5 additions & 0 deletions integration/consul/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

export HOSTNAME="localhost"


# Configure consul
curl -X PUT http://127.0.0.1:8500/v1/kv/key -d 'foobar'
curl -X PUT http://127.0.0.1:8500/v1/kv/database/host -d '127.0.0.1'
Expand All @@ -12,6 +13,8 @@ curl -X PUT http://127.0.0.1:8500/v1/kv/upstream/app1 -d '10.0.1.10:8080'
curl -X PUT http://127.0.0.1:8500/v1/kv/upstream/app2 -d '10.0.1.11:8080'
curl -X PUT http://127.0.0.1:8500/v1/kv/nested/east/app1 -d '10.0.1.10:8080'
curl -X PUT http://127.0.0.1:8500/v1/kv/nested/west/app2 -d '10.0.1.11:8080'
curl -X PUT http://127.0.0.1:8500/v1/kv/pki/issue/my-role/www.example.com/certificate -d "-----BEGIN CERTIFICATE-----\nMIIDwDCCAqigAwIBAgIUfU+/v4dE7TV6U5Jm/C9mbjC/ySkwDQYJKoZIhvcNAQEL\nBQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTkwMjE5MDUxNDUyWhcNMTkw\nMjE5MTMxNTIyWjAaMRgwFgYDVQQDEw93d3cuZXhhbXBsZS5jb20wggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAm3/jVUMkMSrQMwtASFgK8T01sagq98lt\nWWT0A15PGeTSbnWQ3eKbnHzXldGggQz0yxqc8m1oBUvgCZ8I6Kbk1/ooxc/8wO43\nlZ7a341gATrZgzY0cobHIZTjliJN1z1O0Owgko9ddmzVkkHENu07YpIns+WgU4ua\nXA94GmO2+2S78F2Kdh+HckauRNdoYqNQpMRis0F3HvWD+Qju9tGvIrNdD/HMCRXs\nVOMdw4e8rpaHuNZ9OiA148mqSvAWhLr1qCM2DGIOS9q2q4kNkscg5YOXVpY3IppV\nCfl6WxoEj65zS3o+SdjHx8cr9rQakmbvahzt04ShtoG8CHCGLCYTAgMBAAGjggEA\nMIH9MA4GA1UdDwEB/wQEAwIDqDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH\nAwIwHQYDVR0OBBYEFDlhtX2jLH/SyC+2jeLLQkN2VUsOMB8GA1UdIwQYMBaAFNtj\nRIJq7XalG/c3tG7dIW3J+M9rMDwGCCsGAQUFBwEBBDAwLjAsBggrBgEFBQcwAoYg\naHR0cDovLzEyNy4wLjAuMTo4MjAwLy92MS9wa2kvY2EwGgYDVR0RBBMwEYIPd3d3\nLmV4YW1wbGUuY29tMDIGA1UdHwQrMCkwJ6AloCOGIWh0dHA6Ly8xMjcuMC4wLjE6\nODIwMC8vdjEvcGtpL2NybDANBgkqhkiG9w0BAQsFAAOCAQEALE7GKP8PXJ5CKH3J\n016Ug+1yEan7CLpaD31YmD0uIfTHM8QmbTG/MzGXg2zkxm6h98Ns6uA+WGCiwVqX\nfi+4y5q13IqA0y2ljBfYaJirxdoIYAG10phzXgLCLbMMgGC+8X3Hg6Te07vqINE1\nQNgs0E+oggVFmc8eXzqrQh2u2wovPguiM3JHp6esmA/j4hvMqQGenCLhWC+jQ1bO\nIhV/HxPfHN3Ogm9GQ++ZyxgLRlB8PxJZHAPztHXnNHXB47a9Wfi+9VdiM9jgiuir\nRfThdllPvBksR6G0FzCBN1vbmGlEnt9Rm726hjbKJC3ESQpGC9Lv81C9OvMdqiWw\n72ZTzw==\n-----END CERTIFICATE-----"
curl -X PUT http://127.0.0.1:8500/v1/kv/pki/issue/my-role/www.example.com/private_key -d "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAwJt/41VDJDEq0DMLQEhYCvE9NbGoKvfJbVlk9ANeTxnk0m51\nkN3im5x815XRoIEM9MsanPJtaAVL4AmfCOim5Nf6KMXP/MDuN5We2t+NYAE62YM2\nNHKGxyGU45YiTdc9TtDsIJKPXXZs1ZJBxDbtO2KSJ7PloFOLmlwPeBpjtvtku/Bd\ninYfh3JGrkTXaGKjUKTEYrNBdx71g/kI7vbRryKzXQ/xzAkV7FTjHcOHvK6Wh7jW\nfTogNePJqkrwFoS69agjNgxiDkvatquJDZLHIOWDl1aWNyKaVQn5elsaBI+uc0t6\nPknYx8fHK/a0GpJm72oc7dOEobaBvAhwhiwmEwIDAQABAoIBAQCx6DBF1QCyknPA\nYhW3Z9tjKBdo3FPAdKZqydLVDbN0Dy/sK7mOeVWSdQZfv/QkdG96QYywgcEK/zFp\nnJl4iiV2ZgSc2rLV/YNMdniIJUwZ7KjmNyu/YDYcA2namlfPXMw1XAdvwtCH/RZk\nY7c5vZ59ZvwnjiTBZcoiZ3ymbIHEhnA94OoQVQQ27/ep9vH5NUEbPTJggSU6+kM3\nsWSpPjykOuEZblbzD2S0uqcMuf/V47oocrd0G477MKQBoVr5LlLUemTAw/GzNnNt\nNMoRmXk5eHgFUq3mc4wK0ZYbwJFq7l6p5B5mQ4olkj9q0UIeKs8fOHXRdwN0kbmr\nftdWEPdJAoGBAOvLL/uJZrWrO2dgBDTITJ5BljtQNpz4nNTstZZZZNClfgKV37uj\n228mFvwhHSiedjsfzQsqBtzuCjlxduUgD2QlHKM+vBzx5rl0DvQW++fltYqK5DWx\n926eS522Rb34bAeEEbdZssDpXE0EhMbjVhQG61z5YqXQ6wf4EMqGggrHAoGBANEc\n5Z1lA/nhvrvGNWLw2HDJA/b0WnTvTiPQQ1T4bDOug/lTFatDeYywjMpLylhNKuvk\nTKvwO+6KtywykE0CIhz4xiilVzhsYpkqUdEk1tPInQ+cahvE+1J96YT/Mnq9NOda\nZICNOF33sumr45Awh2DRLawMqS3mHdESSAMVKt5VAoGBAOYWZOEQJ/CYgaQTVqdm\n2RUIrR9923z7QJap0VxAKRdMlhTRyPuiHktsoLsxWPG9B2QUWRJO1Vma0tFQ/hMB\nYON5L2PAoPGhv2IydTEMiI22Ypspgx0+Z1NDFkh0h8OjeU8wOdVvqvWCAfaJtUMa\nrXFnex5DoFZr8hzZnRDzhkwbAoGAcJbUclgvOd136nYfzHPMtX0lq1OJWKh4NAQw\nHJHdAD6YRCed5SZhTYTJaSpBeiWiVHwJZBHm0trRIPTgiPX7FApF9yB+w5xnwfvt\nLWReXo0HM56N6wG2J4YvszIMJdW1pFMhBa4DiWSSagnobnwSh+hYZOg0NshNiYIE\nT9SXzjkCgYEA3duQFwdgBZaihBQYtWSbblz/LdQC8hn4COYkE+sYPPBKsNkO305E\nB4Uj2gOIR2AsPg3PVvli0BfCeiO1MGS1mIyNBL2/rTZt2HjgJrbSpH04WCvbSrnw\nLE/mjwGTFQiCDzeR/TybB+eFDkzxCdDLiR/SzpPB1NQ/eZCw2TwHvIE=\n-----END RSA PRIVATE KEY-----"
curl -X PUT http://127.0.0.1:8500/v1/kv/prefix/database/host -d '127.0.0.1'
curl -X PUT http://127.0.0.1:8500/v1/kv/prefix/database/password -d 'p@sSw0rd'
curl -X PUT http://127.0.0.1:8500/v1/kv/prefix/database/port -d '3306'
Expand All @@ -20,6 +23,8 @@ curl -X PUT http://127.0.0.1:8500/v1/kv/prefix/upstream/app1 -d '10.0.1.10:8080'
curl -X PUT http://127.0.0.1:8500/v1/kv/prefix/upstream/app2 -d '10.0.1.11:8080'
curl -X PUT http://127.0.0.1:8500/v1/kv/prefix/nested/east/app1 -d '10.0.1.10:8080'
curl -X PUT http://127.0.0.1:8500/v1/kv/prefix/nested/west/app2 -d '10.0.1.11:8080'
curl -X PUT http://127.0.0.1:8500/v1/kv/prefix/pki/issue/my-role/www.example.com/certificate -d "-----BEGIN CERTIFICATE-----\nMIIDwDCCAqigAwIBAgIUfU+/v4dE7TV6U5Jm/C9mbjC/ySkwDQYJKoZIhvcNAQEL\nBQAwFjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wHhcNMTkwMjE5MDUxNDUyWhcNMTkw\nMjE5MTMxNTIyWjAaMRgwFgYDVQQDEw93d3cuZXhhbXBsZS5jb20wggEiMA0GCSqG\nSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAm3/jVUMkMSrQMwtASFgK8T01sagq98lt\nWWT0A15PGeTSbnWQ3eKbnHzXldGggQz0yxqc8m1oBUvgCZ8I6Kbk1/ooxc/8wO43\nlZ7a341gATrZgzY0cobHIZTjliJN1z1O0Owgko9ddmzVkkHENu07YpIns+WgU4ua\nXA94GmO2+2S78F2Kdh+HckauRNdoYqNQpMRis0F3HvWD+Qju9tGvIrNdD/HMCRXs\nVOMdw4e8rpaHuNZ9OiA148mqSvAWhLr1qCM2DGIOS9q2q4kNkscg5YOXVpY3IppV\nCfl6WxoEj65zS3o+SdjHx8cr9rQakmbvahzt04ShtoG8CHCGLCYTAgMBAAGjggEA\nMIH9MA4GA1UdDwEB/wQEAwIDqDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH\nAwIwHQYDVR0OBBYEFDlhtX2jLH/SyC+2jeLLQkN2VUsOMB8GA1UdIwQYMBaAFNtj\nRIJq7XalG/c3tG7dIW3J+M9rMDwGCCsGAQUFBwEBBDAwLjAsBggrBgEFBQcwAoYg\naHR0cDovLzEyNy4wLjAuMTo4MjAwLy92MS9wa2kvY2EwGgYDVR0RBBMwEYIPd3d3\nLmV4YW1wbGUuY29tMDIGA1UdHwQrMCkwJ6AloCOGIWh0dHA6Ly8xMjcuMC4wLjE6\nODIwMC8vdjEvcGtpL2NybDANBgkqhkiG9w0BAQsFAAOCAQEALE7GKP8PXJ5CKH3J\n016Ug+1yEan7CLpaD31YmD0uIfTHM8QmbTG/MzGXg2zkxm6h98Ns6uA+WGCiwVqX\nfi+4y5q13IqA0y2ljBfYaJirxdoIYAG10phzXgLCLbMMgGC+8X3Hg6Te07vqINE1\nQNgs0E+oggVFmc8eXzqrQh2u2wovPguiM3JHp6esmA/j4hvMqQGenCLhWC+jQ1bO\nIhV/HxPfHN3Ogm9GQ++ZyxgLRlB8PxJZHAPztHXnNHXB47a9Wfi+9VdiM9jgiuir\nRfThdllPvBksR6G0FzCBN1vbmGlEnt9Rm726hjbKJC3ESQpGC9Lv81C9OvMdqiWw\n72ZTzw==\n-----END CERTIFICATE-----"
curl -X PUT http://127.0.0.1:8500/v1/kv/prefix/pki/issue/my-role/www.example.com/private_key -d "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEAwJt/41VDJDEq0DMLQEhYCvE9NbGoKvfJbVlk9ANeTxnk0m51\nkN3im5x815XRoIEM9MsanPJtaAVL4AmfCOim5Nf6KMXP/MDuN5We2t+NYAE62YM2\nNHKGxyGU45YiTdc9TtDsIJKPXXZs1ZJBxDbtO2KSJ7PloFOLmlwPeBpjtvtku/Bd\ninYfh3JGrkTXaGKjUKTEYrNBdx71g/kI7vbRryKzXQ/xzAkV7FTjHcOHvK6Wh7jW\nfTogNePJqkrwFoS69agjNgxiDkvatquJDZLHIOWDl1aWNyKaVQn5elsaBI+uc0t6\nPknYx8fHK/a0GpJm72oc7dOEobaBvAhwhiwmEwIDAQABAoIBAQCx6DBF1QCyknPA\nYhW3Z9tjKBdo3FPAdKZqydLVDbN0Dy/sK7mOeVWSdQZfv/QkdG96QYywgcEK/zFp\nnJl4iiV2ZgSc2rLV/YNMdniIJUwZ7KjmNyu/YDYcA2namlfPXMw1XAdvwtCH/RZk\nY7c5vZ59ZvwnjiTBZcoiZ3ymbIHEhnA94OoQVQQ27/ep9vH5NUEbPTJggSU6+kM3\nsWSpPjykOuEZblbzD2S0uqcMuf/V47oocrd0G477MKQBoVr5LlLUemTAw/GzNnNt\nNMoRmXk5eHgFUq3mc4wK0ZYbwJFq7l6p5B5mQ4olkj9q0UIeKs8fOHXRdwN0kbmr\nftdWEPdJAoGBAOvLL/uJZrWrO2dgBDTITJ5BljtQNpz4nNTstZZZZNClfgKV37uj\n228mFvwhHSiedjsfzQsqBtzuCjlxduUgD2QlHKM+vBzx5rl0DvQW++fltYqK5DWx\n926eS522Rb34bAeEEbdZssDpXE0EhMbjVhQG61z5YqXQ6wf4EMqGggrHAoGBANEc\n5Z1lA/nhvrvGNWLw2HDJA/b0WnTvTiPQQ1T4bDOug/lTFatDeYywjMpLylhNKuvk\nTKvwO+6KtywykE0CIhz4xiilVzhsYpkqUdEk1tPInQ+cahvE+1J96YT/Mnq9NOda\nZICNOF33sumr45Awh2DRLawMqS3mHdESSAMVKt5VAoGBAOYWZOEQJ/CYgaQTVqdm\n2RUIrR9923z7QJap0VxAKRdMlhTRyPuiHktsoLsxWPG9B2QUWRJO1Vma0tFQ/hMB\nYON5L2PAoPGhv2IydTEMiI22Ypspgx0+Z1NDFkh0h8OjeU8wOdVvqvWCAfaJtUMa\nrXFnex5DoFZr8hzZnRDzhkwbAoGAcJbUclgvOd136nYfzHPMtX0lq1OJWKh4NAQw\nHJHdAD6YRCed5SZhTYTJaSpBeiWiVHwJZBHm0trRIPTgiPX7FApF9yB+w5xnwfvt\nLWReXo0HM56N6wG2J4YvszIMJdW1pFMhBa4DiWSSagnobnwSh+hYZOg0NshNiYIE\nT9SXzjkCgYEA3duQFwdgBZaihBQYtWSbblz/LdQC8hn4COYkE+sYPPBKsNkO305E\nB4Uj2gOIR2AsPg3PVvli0BfCeiO1MGS1mIyNBL2/rTZt2HjgJrbSpH04WCvbSrnw\nLE/mjwGTFQiCDzeR/TybB+eFDkzxCdDLiR/SzpPB1NQ/eZCw2TwHvIE=\n-----END RSA PRIVATE KEY-----"

# Run confd
confd --onetime --log-level debug --confdir ./integration/confdir --backend consul --node 127.0.0.1:8500
Loading