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

Nomad Secret Backend integration #3401

Merged
merged 57 commits into from
Dec 18, 2017
Merged
Show file tree
Hide file tree
Changes from 56 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
4cda42a
MVP of working Nomad Secret Backend
ncorrare Sep 20, 2017
393e7bf
Fixing data model
ncorrare Sep 20, 2017
bcd1477
Adding Nomad secret backend documentation
ncorrare Sep 20, 2017
bc1ea9a
Adding Nomad Secret Backend API documentation
ncorrare Sep 21, 2017
9338277
Added tests
ncorrare Sep 28, 2017
ca9ad73
Adding Global tokens to the data model
ncorrare Sep 28, 2017
bf68079
Various fixes (Null pointer, wait for Nomad go up, Auth before policy…
ncorrare Sep 28, 2017
7e5c465
Working tests
ncorrare Sep 29, 2017
222b9d1
Removing ignore to cleanup function
ncorrare Sep 29, 2017
b581716
Updated API Docs with the Global Token Parameter
ncorrare Sep 29, 2017
84b57b2
Adding vendor dependency
ncorrare Sep 29, 2017
ff4f920
Adding vendor dir
ncorrare Sep 29, 2017
331068d
Added nomad as dependency after include fix
ncorrare Oct 2, 2017
6a33bf0
Adding further nomad deps
ncorrare Oct 2, 2017
44aed8f
fixing dependencies
chrishoffman Oct 2, 2017
a393b20
fixing dependencies
chrishoffman Oct 2, 2017
72b0a2f
Adding Nomad docs to the nav. Minor cosmetics fixes
ncorrare Oct 6, 2017
482d73a
Minor/Cosmetic fixes
ncorrare Oct 31, 2017
3a0d7ac
Unifying Storage and API path in role
ncorrare Oct 31, 2017
ffb9343
Should return an error if trying create a management token with polic…
ncorrare Oct 31, 2017
5d3513b
tokenType can never be nil/empty string as there are default values
ncorrare Nov 1, 2017
c4bf80c
Ignoring userErr as it will be nil anyway
ncorrare Nov 1, 2017
dcaec0a
Refactored config error to just have a single error exit path
ncorrare Nov 1, 2017
ca92922
Refactoring readAcessConfig to return a single type of error instead …
ncorrare Nov 1, 2017
f3aaacc
Overhauling the client method and attaching it to the backend
ncorrare Nov 3, 2017
7015139
Not storing the Nomad token as we have the accesor for administrative…
ncorrare Nov 3, 2017
6560e3c
Attaching secretToken to backend
ncorrare Nov 6, 2017
d1e3eff
Refactored Lease into the Backend configuration
ncorrare Nov 6, 2017
f9c30bf
Updated documentation
ncorrare Nov 6, 2017
cbe172f
minor cleanup
Nov 6, 2017
26daf9d
minor cleanup
Nov 6, 2017
b2549f3
adding ttl to secret, refactoring for consistency
Nov 7, 2017
e1e63f8
Removing legacy field scheme that belonged to the Consul API
ncorrare Nov 29, 2017
a280884
Validating that Address and Token are provided in path_config_access.go
ncorrare Nov 29, 2017
3134c72
Updating descriptions, defaults for roles
ncorrare Nov 29, 2017
e3a73ea
Renaming tokenRaw to accessorIDRaw to avoid confusion, as the token i…
ncorrare Nov 29, 2017
a5f01d4
Sanitizing error outputs
ncorrare Nov 29, 2017
1db26e7
Return error before creating a client if conf is nil
ncorrare Nov 29, 2017
f8babf1
Moving LeaseConfig function to path_config_lease.go
ncorrare Nov 29, 2017
cfa0715
Returning nil config if is actually nil, and catching the error befor…
ncorrare Nov 29, 2017
e6b3438
Return an error if accesor_id is nil
ncorrare Nov 29, 2017
a3df394
Pull master into f-nomad
ncorrare Nov 29, 2017
9d78bfa
Refactoring check for empty accessor as per Vishals suggestion
ncorrare Nov 29, 2017
66840ac
%q quotes automatically
ncorrare Nov 29, 2017
0780c62
Checking if client is not nil before deleting token
ncorrare Nov 29, 2017
12e77fa
Rename policy into policies
ncorrare Nov 29, 2017
ea66973
Fix docs up to current standards
ncorrare Nov 29, 2017
884e250
Adding SealWrap configuration, protecting the config/access path
ncorrare Nov 29, 2017
96b0c31
Merge branch 'master' into f-nomad
jefferai Dec 14, 2017
16e2edf
Merge remote-tracking branch 'oss/master' into f-nomad
Dec 15, 2017
152b6e4
address some feedback
Dec 15, 2017
b82493f
adding access config existence check and delete endpoint
Dec 16, 2017
20aac4d
adding existence check for roles
Dec 16, 2017
6c19fa3
Merge remote-tracking branch 'oss/master' into f-nomad
Dec 16, 2017
737dbca
fixing up config to allow environment vars supported by api client
Dec 17, 2017
abbb1c6
use defaultconfig as base, adding env var test
Dec 17, 2017
4f31ee7
Merge branch 'master' into f-nomad
jefferai Dec 18, 2017
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: 68 additions & 0 deletions builtin/logical/nomad/backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package nomad

import (
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)

func Factory(conf *logical.BackendConfig) (logical.Backend, error) {
b := Backend()
if err := b.Setup(conf); err != nil {
return nil, err
}
return b, nil
}

func Backend() *backend {
var b backend
b.Backend = &framework.Backend{
PathsSpecial: &logical.Paths{
SealWrapStorage: []string{
"config/access",
},
},

Paths: []*framework.Path{
pathConfigAccess(&b),
pathConfigLease(&b),
pathListRoles(&b),
pathRoles(&b),
pathCredsCreate(&b),
},

Secrets: []*framework.Secret{
secretToken(&b),
},
BackendType: logical.TypeLogical,
}

return &b
}

type backend struct {
*framework.Backend
}

func (b *backend) client(s logical.Storage) (*api.Client, error) {
conf, err := b.readConfigAccess(s)
if err != nil {
return nil, err
}

nomadConf := api.DefaultConfig()
if conf != nil {
if conf.Address != "" {
nomadConf.Address = conf.Address
}
if conf.Token != "" {
nomadConf.SecretID = conf.Token
}
}

client, err := api.NewClient(nomadConf)
if err != nil {
return nil, err
}
return client, nil
}
302 changes: 302 additions & 0 deletions builtin/logical/nomad/backend_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
package nomad

import (
"fmt"
"os"
"reflect"
"testing"
"time"

nomadapi "github.com/hashicorp/nomad/api"
"github.com/hashicorp/vault/logical"
"github.com/mitchellh/mapstructure"
dockertest "gopkg.in/ory-am/dockertest.v3"
)

func prepareTestContainer(t *testing.T) (cleanup func(), retAddress string, nomadToken string) {
nomadToken = os.Getenv("NOMAD_TOKEN")

retAddress = os.Getenv("NOMAD_ADDR")

if retAddress != "" {
return func() {}, retAddress, nomadToken
}

pool, err := dockertest.NewPool("")
if err != nil {
t.Fatalf("Failed to connect to docker: %s", err)
}

dockerOptions := &dockertest.RunOptions{
Repository: "djenriquez/nomad",
Tag: "latest",
Cmd: []string{"agent", "-dev"},
Env: []string{`NOMAD_LOCAL_CONFIG=bind_addr = "0.0.0.0" acl { enabled = true }`},
}
resource, err := pool.RunWithOptions(dockerOptions)
if err != nil {
t.Fatalf("Could not start local Nomad docker container: %s", err)
}

cleanup = func() {
err := pool.Purge(resource)
if err != nil {
t.Fatalf("Failed to cleanup local container: %s", err)
}
}

retAddress = fmt.Sprintf("http://localhost:%s/", resource.GetPort("4646/tcp"))
// Give Nomad time to initialize

time.Sleep(5000 * time.Millisecond)
// exponential backoff-retry
if err = pool.Retry(func() error {
var err error
nomadapiConfig := nomadapi.DefaultConfig()
nomadapiConfig.Address = retAddress
nomad, err := nomadapi.NewClient(nomadapiConfig)
if err != nil {
return err
}
aclbootstrap, _, err := nomad.ACLTokens().Bootstrap(nil)
if err != nil {
t.Fatalf("err: %v", err)
}
nomadToken = aclbootstrap.SecretID
t.Logf("[WARN] Generated Master token: %s", nomadToken)
policy := &nomadapi.ACLPolicy{
Name: "test",
Description: "test",
Rules: `namespace "default" {
policy = "read"
}
`,
}
anonPolicy := &nomadapi.ACLPolicy{
Name: "anonymous",
Description: "Deny all access for anonymous requests",
Rules: `namespace "default" {
policy = "deny"
}
agent {
policy = "deny"
}
node {
policy = "deny"
}
`,
}
nomadAuthConfig := nomadapi.DefaultConfig()
nomadAuthConfig.Address = retAddress
nomadAuthConfig.SecretID = nomadToken
nomadAuth, err := nomadapi.NewClient(nomadAuthConfig)
_, err = nomadAuth.ACLPolicies().Upsert(policy, nil)
if err != nil {
t.Fatal(err)
}
_, err = nomadAuth.ACLPolicies().Upsert(anonPolicy, nil)
if err != nil {
t.Fatal(err)
}
return err
}); err != nil {
cleanup()
t.Fatalf("Could not connect to docker: %s", err)
}
return cleanup, retAddress, nomadToken
}

func TestBackend_config_access(t *testing.T) {
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
b, err := Factory(config)
if err != nil {
t.Fatal(err)
}

cleanup, connURL, connToken := prepareTestContainer(t)
defer cleanup()

connData := map[string]interface{}{
"address": connURL,
"token": connToken,
}

confReq := &logical.Request{
Operation: logical.UpdateOperation,
Path: "config/access",
Storage: config.StorageView,
Data: connData,
}

resp, err := b.HandleRequest(confReq)
if err != nil || (resp != nil && resp.IsError()) || resp != nil {
t.Fatalf("failed to write configuration: resp:%#v err:%s", resp, err)
}

confReq.Operation = logical.ReadOperation
resp, err = b.HandleRequest(confReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("failed to write configuration: resp:%#v err:%s", resp, err)
}

expected := map[string]interface{}{
"address": connData["address"].(string),
}
if !reflect.DeepEqual(expected, resp.Data) {
t.Fatalf("bad: expected:%#v\nactual:%#v\n", expected, resp.Data)
}
if resp.Data["token"] != nil {
t.Fatalf("token should not be set in the response")
}
}

func TestBackend_renew_revoke(t *testing.T) {
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
b, err := Factory(config)
if err != nil {
t.Fatal(err)
}

cleanup, connURL, connToken := prepareTestContainer(t)
defer cleanup()
connData := map[string]interface{}{
"address": connURL,
"token": connToken,
}

req := &logical.Request{
Storage: config.StorageView,
Operation: logical.UpdateOperation,
Path: "config/access",
Data: connData,
}
resp, err := b.HandleRequest(req)
if err != nil {
t.Fatal(err)
}

req.Path = "role/test"
req.Data = map[string]interface{}{
"policies": []string{"policy"},
"lease": "6h",
}
resp, err = b.HandleRequest(req)
if err != nil {
t.Fatal(err)
}

req.Operation = logical.ReadOperation
req.Path = "creds/test"
resp, err = b.HandleRequest(req)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("resp nil")
}
if resp.IsError() {
t.Fatalf("resp is error: %v", resp.Error())
}

generatedSecret := resp.Secret
generatedSecret.IssueTime = time.Now()
generatedSecret.TTL = 6 * time.Hour

var d struct {
Token string `mapstructure:"secret_id"`
Accessor string `mapstructure:"accessor_id"`
}
if err := mapstructure.Decode(resp.Data, &d); err != nil {
t.Fatal(err)
}
t.Logf("[WARN] Generated token: %s with accesor %s", d.Token, d.Accessor)

// Build a client and verify that the credentials work
nomadapiConfig := nomadapi.DefaultConfig()
nomadapiConfig.Address = connData["address"].(string)
nomadapiConfig.SecretID = d.Token
client, err := nomadapi.NewClient(nomadapiConfig)
if err != nil {
t.Fatal(err)
}

t.Log("[WARN] Verifying that the generated token works...")
_, err = client.Agent().Members, nil
if err != nil {
t.Fatal(err)
}

req.Operation = logical.RenewOperation
req.Secret = generatedSecret
resp, err = b.HandleRequest(req)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("got nil response from renew")
}

req.Operation = logical.RevokeOperation
resp, err = b.HandleRequest(req)
if err != nil {
t.Fatal(err)
}

// Build a management client and verify that the token does not exist anymore
nomadmgmtConfig := nomadapi.DefaultConfig()
nomadmgmtConfig.Address = connData["address"].(string)
nomadmgmtConfig.SecretID = connData["token"].(string)
mgmtclient, err := nomadapi.NewClient(nomadmgmtConfig)

q := &nomadapi.QueryOptions{
Namespace: "default",
}

t.Log("[WARN] Verifying that the generated token does not exist...")
_, _, err = mgmtclient.ACLTokens().Info(d.Accessor, q)
if err == nil {
t.Fatal("err: expected error")
}
}

func TestBackend_CredsCreateEnvVar(t *testing.T) {
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
b, err := Factory(config)
if err != nil {
t.Fatal(err)
}

cleanup, connURL, connToken := prepareTestContainer(t)
defer cleanup()

req := logical.TestRequest(t, logical.UpdateOperation, "role/test")
req.Data = map[string]interface{}{
"policies": []string{"policy"},
"lease": "6h",
}
resp, err := b.HandleRequest(req)
if err != nil {
t.Fatal(err)
}

os.Setenv("NOMAD_TOKEN", connToken)
defer os.Unsetenv("NOMAD_TOKEN")
os.Setenv("NOMAD_ADDR", connURL)
defer os.Unsetenv("NOMAD_ADDR")

req.Operation = logical.ReadOperation
req.Path = "creds/test"
resp, err = b.HandleRequest(req)
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("resp nil")
}
if resp.IsError() {
t.Fatalf("resp is error: %v", resp.Error())
}
}
Loading