This commit is contained in:
Alex Dadgar
2016-08-18 10:50:47 -07:00
parent 3a857b65a1
commit 87f2d5c17b
15 changed files with 288 additions and 143 deletions

View File

@@ -874,3 +874,85 @@ func (b *batchFuture) Respond(index uint64, err error) {
b.err = err
close(b.doneCh)
}
// DeriveVaultToken is used by the clients to request wrapped Vault tokens for
// tasks
func (n *Node) DeriveVaultToken(args *structs.DeriveVaultTokenRequest,
reply *structs.DeriveVaultTokenResponse) error {
if done, err := n.srv.forward("Node.DeriveVaultToken", args, args, reply); done {
return err
}
defer metrics.MeasureSince([]string{"nomad", "client", "derive_vault_token"}, time.Now())
// Verify the arguments
if args.NodeID == "" {
return fmt.Errorf("missing node ID")
}
if args.SecretID == "" {
return fmt.Errorf("missing node SecretID")
}
if args.AllocID == "" {
return fmt.Errorf("missing allocation ID")
}
if len(args.Tasks) == 0 {
return fmt.Errorf("no tasks specified")
}
// Verify the following:
// * The Node exists and has the correct SecretID
// * The Allocation exists on the specified node
// * The allocation contains the given tasks and they each require Vault
// tokens
snap, err := n.srv.fsm.State().Snapshot()
if err != nil {
return err
}
node, err := snap.NodeByID(args.NodeID)
if err != nil {
return err
}
if node == nil {
return fmt.Errorf("Node %q does not exist", args.NodeID)
}
//if node.SecretID != args.SecretID {
//return fmt.Errorf("SecretID mismatch")
//}
alloc, err := snap.AllocByID(args.AllocID)
if err != nil {
return err
}
if alloc == nil {
return fmt.Errorf("Allocation %q does not exist", args.AllocID)
}
if alloc.NodeID != args.NodeID {
return fmt.Errorf("Allocation %q not running on Node %q", args.AllocID, args.NodeID)
}
// Check the policies
policies := alloc.Job.VaultPolicies()
if policies == nil {
return fmt.Errorf("Job doesn't require Vault policies")
}
tg, ok := policies[alloc.TaskGroup]
if !ok {
return fmt.Errorf("Task group does not require Vault policies")
}
var unneeded []string
for _, task := range args.Tasks {
taskVault := tg[task]
if len(taskVault.Policies) == 0 {
unneeded = append(unneeded, task)
}
}
if len(unneeded) != 0 {
return fmt.Errorf("Requested Vault tokens for tasks without defined Vault policies: %s",
strings.Join(unneeded, ", "))
}
// At this point the request is valid and we should contact Vault for tokens
return nil
}

View File

@@ -253,12 +253,12 @@ func SliceStringIsSubset(larger, smaller []string) (bool, []string) {
// VaultPoliciesSet takes the structure returned by VaultPolicies and returns
// the set of required policies
func VaultPoliciesSet(policies map[string]map[string][]string) []string {
func VaultPoliciesSet(policies map[string]map[string]*Vault) []string {
set := make(map[string]struct{})
for _, tgp := range policies {
for _, tp := range tgp {
for _, p := range tp {
for _, p := range tp.Policies {
set[p] = struct{}{}
}
}

View File

@@ -354,6 +354,22 @@ type PeriodicForceRequest struct {
WriteRequest
}
type DeriveVaultTokenRequest struct {
NodeID string
SecretID string
AllocID string
Tasks []string
QueryOptions
}
type DeriveVaultTokenResponse struct {
NodeID string
SecretID string
AllocID string
Tasks []string
QueryMeta
}
// GenericRequest is used to request where no
// specific information is needed.
type GenericRequest struct {
@@ -1230,11 +1246,11 @@ func (j *Job) IsPeriodic() bool {
}
// VaultPolicies returns the set of Vault policies per task group, per task
func (j *Job) VaultPolicies() map[string]map[string][]string {
policies := make(map[string]map[string][]string, len(j.TaskGroups))
func (j *Job) VaultPolicies() map[string]map[string]*Vault {
policies := make(map[string]map[string]*Vault, len(j.TaskGroups))
for _, tg := range j.TaskGroups {
tgPolicies := make(map[string][]string, len(tg.Tasks))
tgPolicies := make(map[string]*Vault, len(tg.Tasks))
policies[tg.Name] = tgPolicies
for _, task := range tg.Tasks {
@@ -1242,7 +1258,7 @@ func (j *Job) VaultPolicies() map[string]map[string][]string {
continue
}
tgPolicies[task.Name] = task.Vault.Policies
tgPolicies[task.Name] = task.Vault
}
}

View File

@@ -224,8 +224,25 @@ func TestJob_SystemJob_Validate(t *testing.T) {
func TestJob_VaultPolicies(t *testing.T) {
j0 := &Job{}
e0 := make(map[string]map[string][]string, 0)
e0 := make(map[string]map[string]*Vault, 0)
vj1 := &Vault{
Policies: []string{
"p1",
"p2",
},
}
vj2 := &Vault{
Policies: []string{
"p3",
"p4",
},
}
vj3 := &Vault{
Policies: []string{
"p5",
},
}
j1 := &Job{
TaskGroups: []*TaskGroup{
&TaskGroup{
@@ -235,13 +252,8 @@ func TestJob_VaultPolicies(t *testing.T) {
Name: "t1",
},
&Task{
Name: "t2",
Vault: &Vault{
Policies: []string{
"p1",
"p2",
},
},
Name: "t2",
Vault: vj1,
},
},
},
@@ -249,40 +261,31 @@ func TestJob_VaultPolicies(t *testing.T) {
Name: "bar",
Tasks: []*Task{
&Task{
Name: "t3",
Vault: &Vault{
Policies: []string{
"p3",
"p4",
},
},
Name: "t3",
Vault: vj2,
},
&Task{
Name: "t4",
Vault: &Vault{
Policies: []string{
"p5",
},
},
Name: "t4",
Vault: vj3,
},
},
},
},
}
e1 := map[string]map[string][]string{
"foo": map[string][]string{
"t2": []string{"p1", "p2"},
e1 := map[string]map[string]*Vault{
"foo": map[string]*Vault{
"t2": vj1,
},
"bar": map[string][]string{
"t3": []string{"p3", "p4"},
"t4": []string{"p5"},
"bar": map[string]*Vault{
"t3": vj2,
"t4": vj3,
},
}
cases := []struct {
Job *Job
Expected map[string]map[string][]string
Expected map[string]map[string]*Vault
}{
{
Job: j0,

View File

@@ -21,6 +21,11 @@ const (
// minimumTokenTTL is the minimum Token TTL allowed for child tokens.
minimumTokenTTL = 5 * time.Minute
// defaultTokenTTL is the default Token TTL used when the passed token is a
// root token such that child tokens aren't being created against a role
// that has defined a TTL
defaultTokenTTL = "72h"
)
// VaultClient is the Servers interface for interfacing with Vault
@@ -131,6 +136,9 @@ func NewVaultClient(c *config.VaultConfig, logger *log.Logger) (*vaultClient, er
}
v.childTTL = c.TaskTokenTTL
} else {
// Default the TaskTokenTTL
v.childTTL = defaultTokenTTL
}
// Get the Vault API configuration
@@ -414,8 +422,58 @@ func (v *vaultClient) ConnectionEstablished() bool {
return v.connEstablished
}
// CreateToken takes the allocation and task and returns an appropriate Vault
// token
func (v *vaultClient) CreateToken(a *structs.Allocation, task string) (*vapi.Secret, error) {
return nil, nil
// Nothing to do
if !v.enabled {
return nil, fmt.Errorf("Vault integration disabled")
}
// Check if we have established a connection with Vault
if !v.ConnectionEstablished() {
return nil, fmt.Errorf("Connection to Vault has not been established. Retry")
}
// Retrieve the Vault block for the task
policies := a.Job.VaultPolicies()
if policies == nil {
return nil, fmt.Errorf("Job doesn't require Vault policies")
}
tg, ok := policies[a.TaskGroup]
if !ok {
return nil, fmt.Errorf("Task group does not require Vault policies")
}
taskVault, ok := tg[task]
if !ok {
return nil, fmt.Errorf("Task does not require Vault policies")
}
// Build the creation request
req := &vapi.TokenCreateRequest{
Policies: taskVault.Policies,
Metadata: map[string]string{
"AllocationID": a.ID,
"Task": task,
"NodeID": a.NodeID,
},
TTL: v.childTTL,
DisplayName: fmt.Sprintf("%s: %s", a.ID, task),
}
// Make the request and switch depending on whether we are using a root
// token or a role based token
var secret *vapi.Secret
var err error
if v.token.Root {
req.Period = v.childTTL
secret, err = v.auth.Create(req)
} else {
// Make the token using the role
secret, err = v.auth.CreateWithRole(req, v.token.Role)
}
return secret, err
}
// LookupToken takes a Vault token and does a lookup against Vault

View File

@@ -170,6 +170,7 @@ type TokenCreateRequest struct {
Lease string `json:"lease,omitempty"`
TTL string `json:"ttl,omitempty"`
ExplicitMaxTTL string `json:"explicit_max_ttl,omitempty"`
Period string `json:"period,omitempty"`
NoParent bool `json:"no_parent,omitempty"`
NoDefaultPolicy bool `json:"no_default_policy,omitempty"`
DisplayName string `json:"display_name"`

View File

@@ -22,21 +22,12 @@ func (c *Sys) AuditHash(path string, input string) (string, error) {
}
defer resp.Body.Close()
secret, err := ParseSecret(resp.Body)
if err != nil {
return "", err
}
if secret == nil || secret.Data == nil || len(secret.Data) == 0 {
return "", nil
}
type d struct {
Hash string
Hash string `json:"hash"`
}
var result d
err = mapstructure.Decode(secret.Data, &result)
err = resp.DecodeJSON(&result)
if err != nil {
return "", err
}
@@ -52,26 +43,32 @@ func (c *Sys) ListAudit() (map[string]*Audit, error) {
}
defer resp.Body.Close()
secret, err := ParseSecret(resp.Body)
var result map[string]interface{}
err = resp.DecodeJSON(&result)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil || len(secret.Data) == 0 {
return nil, nil
}
result := map[string]*Audit{}
for k, v := range secret.Data {
mounts := map[string]*Audit{}
for k, v := range result {
switch v.(type) {
case map[string]interface{}:
default:
continue
}
var res Audit
err = mapstructure.Decode(v, &res)
if err != nil {
return nil, err
}
result[k] = &res
// Not a mount, some other api.Secret data
if res.Type == "" {
continue
}
mounts[k] = &res
}
return result, err
return mounts, nil
}
func (c *Sys) EnableAudit(
@@ -106,7 +103,7 @@ func (c *Sys) DisableAudit(path string) error {
}
// Structures for the requests/resposne are all down here. They aren't
// individually documentd because the map almost directly to the raw HTTP API
// individually documented because the map almost directly to the raw HTTP API
// documentation. Please refer to that documentation for more details.
type Audit struct {

View File

@@ -14,26 +14,32 @@ func (c *Sys) ListAuth() (map[string]*AuthMount, error) {
}
defer resp.Body.Close()
secret, err := ParseSecret(resp.Body)
var result map[string]interface{}
err = resp.DecodeJSON(&result)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil || len(secret.Data) == 0 {
return nil, nil
}
result := map[string]*AuthMount{}
for k, v := range secret.Data {
mounts := map[string]*AuthMount{}
for k, v := range result {
switch v.(type) {
case map[string]interface{}:
default:
continue
}
var res AuthMount
err = mapstructure.Decode(v, &res)
if err != nil {
return nil, err
}
result[k] = &res
// Not a mount, some other api.Secret data
if res.Type == "" {
continue
}
mounts[k] = &res
}
return result, err
return mounts, nil
}
func (c *Sys) EnableAuth(path, authType, desc string) error {

View File

@@ -28,17 +28,14 @@ func (c *Sys) Capabilities(token, path string) ([]string, error) {
}
defer resp.Body.Close()
secret, err := ParseSecret(resp.Body)
var result map[string]interface{}
err = resp.DecodeJSON(&result)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil || len(secret.Data) == 0 {
return nil, nil
}
var capabilities []string
capabilitiesRaw := secret.Data["capabilities"].([]interface{})
capabilitiesRaw := result["capabilities"].([]interface{})
for _, capability := range capabilitiesRaw {
capabilities = append(capabilities, capability.(string))
}

View File

@@ -45,7 +45,9 @@ type InitStatusResponse struct {
}
type InitResponse struct {
Keys []string `json:"keys"`
RecoveryKeys []string `json:"recovery_keys"`
RootToken string `json:"root_token"`
Keys []string `json:"keys"`
KeysB64 []string `json:"keys_base64"`
RecoveryKeys []string `json:"recovery_keys"`
RecoveryKeysB64 []string `json:"recovery_keys_base64"`
RootToken string `json:"root_token"`
}

View File

@@ -15,26 +15,32 @@ func (c *Sys) ListMounts() (map[string]*MountOutput, error) {
}
defer resp.Body.Close()
secret, err := ParseSecret(resp.Body)
var result map[string]interface{}
err = resp.DecodeJSON(&result)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil || len(secret.Data) == 0 {
return nil, nil
}
result := map[string]*MountOutput{}
for k, v := range secret.Data {
mounts := map[string]*MountOutput{}
for k, v := range result {
switch v.(type) {
case map[string]interface{}:
default:
continue
}
var res MountOutput
err = mapstructure.Decode(v, &res)
if err != nil {
return nil, err
}
result[k] = &res
// Not a mount, some other api.Secret data
if res.Type == "" {
continue
}
mounts[k] = &res
}
return result, nil
return mounts, nil
}
func (c *Sys) Mount(path string, mountInfo *MountInput) error {
@@ -104,17 +110,8 @@ func (c *Sys) MountConfig(path string) (*MountConfigOutput, error) {
}
defer resp.Body.Close()
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil || len(secret.Data) == 0 {
return nil, nil
}
var result MountConfigOutput
err = mapstructure.Decode(secret.Data, &result)
err = resp.DecodeJSON(&result)
if err != nil {
return nil, err
}

View File

@@ -1,10 +1,6 @@
package api
import (
"fmt"
"github.com/mitchellh/mapstructure"
)
import "fmt"
func (c *Sys) ListPolicies() ([]string, error) {
r := c.c.NewRequest("GET", "/v1/sys/policy")
@@ -14,22 +10,25 @@ func (c *Sys) ListPolicies() ([]string, error) {
}
defer resp.Body.Close()
secret, err := ParseSecret(resp.Body)
var result map[string]interface{}
err = resp.DecodeJSON(&result)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil || len(secret.Data) == 0 {
return nil, nil
var ok bool
if _, ok = result["policies"]; !ok {
return nil, fmt.Errorf("policies not found in response")
}
var result listPoliciesResp
err = mapstructure.Decode(secret.Data, &result)
if err != nil {
return nil, err
listRaw := result["policies"].([]interface{})
var policies []string
for _, val := range listRaw {
policies = append(policies, val.(string))
}
return result.Policies, err
return policies, err
}
func (c *Sys) GetPolicy(name string) (string, error) {
@@ -45,22 +44,18 @@ func (c *Sys) GetPolicy(name string) (string, error) {
return "", err
}
secret, err := ParseSecret(resp.Body)
var result map[string]interface{}
err = resp.DecodeJSON(&result)
if err != nil {
return "", err
}
if secret == nil || secret.Data == nil || len(secret.Data) == 0 {
return "", nil
var ok bool
if _, ok = result["rules"]; !ok {
return "", fmt.Errorf("rules not found in response")
}
var result getPoliciesResp
err = mapstructure.Decode(secret.Data, &result)
if err != nil {
return "", err
}
return result.Rules, err
return result["rules"].(string), nil
}
func (c *Sys) PutPolicy(name, rules string) error {

View File

@@ -190,11 +190,13 @@ type RekeyUpdateResponse struct {
Nonce string
Complete bool
Keys []string
KeysB64 []string `json:"keys_base64"`
PGPFingerprints []string `json:"pgp_fingerprints"`
Backup bool
}
type RekeyRetrieveResponse struct {
Nonce string
Keys map[string][]string
Nonce string
Keys map[string][]string
KeysB64 map[string][]string `json:"keys_base64"`
}

View File

@@ -1,10 +1,6 @@
package api
import (
"time"
"github.com/mitchellh/mapstructure"
)
import "time"
func (c *Sys) Rotate() error {
r := c.c.NewRequest("POST", "/v1/sys/rotate")
@@ -23,25 +19,12 @@ func (c *Sys) KeyStatus() (*KeyStatus, error) {
}
defer resp.Body.Close()
secret, err := ParseSecret(resp.Body)
if err != nil {
return nil, err
}
if secret == nil || secret.Data == nil || len(secret.Data) == 0 {
return nil, nil
}
var result KeyStatus
err = mapstructure.Decode(secret.Data, &result)
if err != nil {
return nil, err
}
return &result, err
result := new(KeyStatus)
err = resp.DecodeJSON(result)
return result, err
}
type KeyStatus struct {
Term int
Term int `json:"term"`
InstallTime time.Time `json:"install_time"`
}

12
vendor/vendor.json vendored
View File

@@ -623,10 +623,16 @@
"revisionTime": "2016-06-09T00:18:40Z"
},
{
"checksumSHA1": "0rkVtm9F1/pW9EGhHYJpCnY99O8=",
"checksumSHA1": "RAJfRxZ8UmcL6+7VuXAZxBlnM/4=",
"path": "github.com/hashicorp/vault",
"revision": "fece3ca069fc5bafec5280bbcb0c0693ff69fdaf",
"revisionTime": "2016-08-17T21:47:06Z"
},
{
"checksumSHA1": "JH8wmQ8cWdn7mYu1T7gJ3IMIrec=",
"path": "github.com/hashicorp/vault/api",
"revision": "fbecd94926e289d3b81d8dae6136452a6c4c93f6",
"revisionTime": "2016-08-13T15:54:01Z"
"revision": "fece3ca069fc5bafec5280bbcb0c0693ff69fdaf",
"revisionTime": "2016-08-17T21:47:06Z"
},
{
"checksumSHA1": "5lR6EdY0ARRdKAq3hZcL38STD8Q=",