Added a create option for generic secrets with an optional size parameter

This commit is contained in:
Lewis Marshall 2016-03-22 18:26:22 +00:00
parent e5aa48121b
commit bcc4e3fe8f
6 changed files with 97 additions and 5 deletions

View file

@ -125,6 +125,7 @@ bundle format is very similar in the sense it similar takes the private key and
**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
- **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
- **renew**: (renewal) override the default behavour on this resource, renew the resource when coming close to expiration e.g true, TRUE
- **delay**: (renewal-delay) delay the revoking the lease of a resource for x period once time e.g 1m, 1h20s

53
generate.go Normal file
View file

@ -0,0 +1,53 @@
/*
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 (
"io"
"crypto/rand"
)
var StdChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+,.?/:;{}[]`~")
func NewPassword(length int) string {
return rand_char(length, StdChars)
}
func rand_char(length int, chars []byte) string {
new_pword := make([]byte, length)
random_data := make([]byte, length+(length/4)) // storage for random bytes.
clen := byte(len(chars))
maxrb := byte(256 - (256 % len(chars)))
i := 0
for {
if _, err := io.ReadFull(rand.Reader, random_data); err != nil {
panic(err)
}
for _, c := range random_data {
if c >= maxrb {
continue
}
new_pword[i] = chars[c%clen]
i++
if i == length {
return string(new_pword)
}
}
}
panic("unreachable")
}

View file

@ -26,11 +26,12 @@ import (
const (
Prog = "vault-sidekick"
Version = "v0.0.8"
Version = "v0.0.9"
)
func main() {
// step: parse and validate the command line / environment options
if err := parseOptions(); err != nil {
showUsage("invalid options, %s", err)
}

View file

@ -138,7 +138,6 @@ func (r *VaultService) vaultServiceProcessor() {
glog.V(10).Infof("resource: %s has a previous lease: %s", x.resource, leaseID)
}
// step: retrieve the resource from vault
err := r.get(x)
if err != nil {
glog.Errorf("failed to retrieve the resource: %s from vault, error: %s", x.resource, err)
@ -147,7 +146,7 @@ func (r *VaultService) vaultServiceProcessor() {
break
}
glog.V(4).Infof("successfully retrieved resournce: %s, leaseID: %s", x.resource, x.secret.LeaseID)
glog.V(4).Infof("successfully retrieved resource: %s, leaseID: %s", x.resource, x.secret.LeaseID)
// step: if we had a previous lease and the option is to revoke, lets throw into the revoke channel
if leaseID != "" && x.resource.revoked {
@ -315,6 +314,7 @@ func (r VaultService) revoke(lease string) error {
func (r VaultService) get(rn *watchedResource) (err error) {
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
params := make(map[string]interface{}, 0)
for k, v := range rn.resource.options {
params[k] = interface{}(v)
@ -366,6 +366,17 @@ func (r VaultService) get(rn *watchedResource) (err error) {
fallthrough
case "secret":
secret, err = r.client.Logical().Read(rn.resource.path)
// We must generate the secret if we have the create flag
if rn.resource.create && secret == nil && err == nil {
glog.V(3).Infof("Create param specified, creating resource: %s", rn.resource.path)
params["value"] = NewPassword(int(rn.resource.size))
secret, err = r.client.Logical().Write(fmt.Sprintf(rn.resource.path), params)
glog.V(3).Infof("Secret created: %s", rn.resource.path)
if err == nil {
// Populate the secret data as stored in Vault...
secret, err = r.client.Logical().Read(rn.resource.path)
}
}
}
// step: check the error if any
if err != nil {
@ -375,7 +386,7 @@ func (r VaultService) get(rn *watchedResource) (err error) {
}
return err
}
if secret == nil && err != nil {
if secret == nil && err == nil {
return fmt.Errorf("the resource does not exist")
}

View file

@ -39,6 +39,12 @@ const (
optionUpdate = "update"
// optionsExec executes something on a change
optionExec = "exec"
// optionCreate creates a secret if it doesn't exist
optionCreate = "create"
// optionSize sets the initial size of a password secret
optionSize = "size"
// defaultSize sets the default size of a generic secret
defaultSize = 20
)
var (
@ -65,6 +71,7 @@ func defaultVaultResource() *VaultResource {
renewable: false,
revoked: false,
options: make(map[string]string, 0),
size: defaultSize,
}
}
@ -84,6 +91,10 @@ type VaultResource struct {
revokeDelay time.Duration
// the lease duration
update time.Duration
// whether the resource should be created?
create bool
// the size of a secret to create
size int64
// the filename to save the secret
filename string
// the template file

View file

@ -65,7 +65,7 @@ func (r *VaultResources) Set(value string) error {
name := kp[0]
value := strings.Replace(kp[1], "|", ",", -1)
// step: extract the control options from the path resource parameteres
// step: extract the control options from the path resource parameters
switch name {
case optionFormat:
if matched := resourceFormatRegex.MatchString(value); !matched {
@ -96,6 +96,21 @@ func (r *VaultResources) Set(value string) error {
return fmt.Errorf("the renewal option: %s is invalid, should be a boolean", value)
}
rn.renewable = choice
case optionCreate:
choice, err := strconv.ParseBool(value)
if err != nil {
return fmt.Errorf("the create option: %s is invalid, should be a boolean", value)
}
if rn.resource != "secret" {
return fmt.Errorf("the create option is only supported for 'cn=secret' at this time")
}
rn.create = choice
case optionSize:
size, err := strconv.ParseInt(value, 10, 16)
if err != nil {
return fmt.Errorf("the size option: %s is invalid, should be an integer", value)
}
rn.size = size
case optionExec:
rn.execPath = value
case optionFilename: