Bootstrap Nomad ACL system if no token is given (#12451)
* Bootstrap Nomad ACL system if no token is given Similar to the [Bootstrap the Consul ACL system if no token is given][boostrap-consul] it would be very useful to bootstrap Nomads ACL system and manage it in Vault. [boostrap-consul]:https://github.com/hashicorp/vault/pull/10751 * Add changelog entry * Remove debug log line * Remove redundant else * Rename Nomad acl bootstrap param * Replace sleep with attempt to list nomad leader, setup will retry until successful * fmt
This commit is contained in:
parent
cb16c478e7
commit
9c294f1ef0
|
@ -48,12 +48,7 @@ type backend struct {
|
||||||
*framework.Backend
|
*framework.Backend
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *backend) client(ctx context.Context, s logical.Storage) (*api.Client, error) {
|
func clientFromConfig(conf *accessConfig) (*api.Client, error) {
|
||||||
conf, err := b.readConfigAccess(ctx, s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
nomadConf := api.DefaultConfig()
|
nomadConf := api.DefaultConfig()
|
||||||
if conf != nil {
|
if conf != nil {
|
||||||
if conf.Address != "" {
|
if conf.Address != "" {
|
||||||
|
@ -72,11 +67,14 @@ func (b *backend) client(ctx context.Context, s logical.Storage) (*api.Client, e
|
||||||
nomadConf.TLSConfig.ClientKeyPEM = []byte(conf.ClientKey)
|
nomadConf.TLSConfig.ClientKeyPEM = []byte(conf.ClientKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return api.NewClient(nomadConf)
|
||||||
|
}
|
||||||
|
|
||||||
client, err := api.NewClient(nomadConf)
|
func (b *backend) client(ctx context.Context, s logical.Storage) (*api.Client, error) {
|
||||||
|
conf, err := b.readConfigAccess(ctx, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return client, nil
|
return clientFromConfig(conf)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -27,7 +28,13 @@ func (c *Config) APIConfig() *nomadapi.Config {
|
||||||
return apiConfig
|
return apiConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareTestContainer(t *testing.T) (func(), *Config) {
|
func (c *Config) Client() (*nomadapi.Client, error) {
|
||||||
|
apiConfig := c.APIConfig()
|
||||||
|
|
||||||
|
return nomadapi.NewClient(apiConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareTestContainer(t *testing.T, bootstrap bool) (func(), *Config) {
|
||||||
if retAddress := os.Getenv("NOMAD_ADDR"); retAddress != "" {
|
if retAddress := os.Getenv("NOMAD_ADDR"); retAddress != "" {
|
||||||
s, err := docker.NewServiceURLParse(retAddress)
|
s, err := docker.NewServiceURLParse(retAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -37,8 +44,8 @@ func prepareTestContainer(t *testing.T) (func(), *Config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
runner, err := docker.NewServiceRunner(docker.RunOptions{
|
runner, err := docker.NewServiceRunner(docker.RunOptions{
|
||||||
ImageRepo: "catsby/nomad",
|
ImageRepo: "multani/nomad",
|
||||||
ImageTag: "0.8.4",
|
ImageTag: "1.1.6",
|
||||||
ContainerName: "nomad",
|
ContainerName: "nomad",
|
||||||
Ports: []string{"4646/tcp"},
|
Ports: []string{"4646/tcp"},
|
||||||
Cmd: []string{"agent", "-dev"},
|
Cmd: []string{"agent", "-dev"},
|
||||||
|
@ -57,12 +64,53 @@ func prepareTestContainer(t *testing.T) (func(), *Config) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = nomad.Status().Leader()
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("[DEBUG] Nomad is not ready yet: %s", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if bootstrap {
|
||||||
aclbootstrap, _, err := nomad.ACLTokens().Bootstrap(nil)
|
aclbootstrap, _, err := nomad.ACLTokens().Bootstrap(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
nomadToken = aclbootstrap.SecretID
|
nomadToken = aclbootstrap.SecretID
|
||||||
t.Logf("[WARN] Generated Master token: %s", nomadToken)
|
t.Logf("[WARN] Generated Master token: %s", nomadToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
nomadAuthConfig := nomadapi.DefaultConfig()
|
||||||
|
nomadAuthConfig.Address = nomad.Address()
|
||||||
|
|
||||||
|
if bootstrap {
|
||||||
|
nomadAuthConfig.SecretID = nomadToken
|
||||||
|
|
||||||
|
nomadAuth, err := nomadapi.NewClient(nomadAuthConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = preprePolicies(nomadAuth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u, _ := docker.NewServiceURLParse(nomadapiConfig.Address)
|
||||||
|
return &Config{
|
||||||
|
ServiceURL: *u,
|
||||||
|
Token: nomadToken,
|
||||||
|
}, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not start docker Nomad: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return svc.Cleanup, svc.Config.(*Config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func preprePolicies(nomadClient *nomadapi.Client) error {
|
||||||
policy := &nomadapi.ACLPolicy{
|
policy := &nomadapi.ACLPolicy{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Description: "test",
|
Description: "test",
|
||||||
|
@ -85,32 +133,76 @@ func prepareTestContainer(t *testing.T) (func(), *Config) {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
nomadAuthConfig := nomadapi.DefaultConfig()
|
|
||||||
nomadAuthConfig.Address = nomad.Address()
|
_, err := nomadClient.ACLPolicies().Upsert(policy, nil)
|
||||||
nomadAuthConfig.SecretID = nomadToken
|
|
||||||
nomadAuth, err := nomadapi.NewClient(nomadAuthConfig)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
|
||||||
_, err = nomadAuth.ACLPolicies().Upsert(policy, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
_, err = nomadAuth.ACLPolicies().Upsert(anonPolicy, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
u, _ := docker.NewServiceURLParse(nomadapiConfig.Address)
|
|
||||||
return &Config{
|
|
||||||
ServiceURL: *u,
|
|
||||||
Token: nomadToken,
|
|
||||||
}, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not start docker Nomad: %s", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return svc.Cleanup, svc.Config.(*Config)
|
_, err = nomadClient.ACLPolicies().Upsert(anonPolicy, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackend_config_Bootstrap(t *testing.T) {
|
||||||
|
config := logical.TestBackendConfig()
|
||||||
|
config.StorageView = &logical.InmemStorage{}
|
||||||
|
b, err := Factory(context.Background(), config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup, svccfg := prepareTestContainer(t, false)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
connData := map[string]interface{}{
|
||||||
|
"address": svccfg.URL().String(),
|
||||||
|
"token": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
confReq := &logical.Request{
|
||||||
|
Operation: logical.UpdateOperation,
|
||||||
|
Path: "config/access",
|
||||||
|
Storage: config.StorageView,
|
||||||
|
Data: connData,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := b.HandleRequest(context.Background(), 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(context.Background(), 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),
|
||||||
|
"max_token_name_length": 0,
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expected, resp.Data) {
|
||||||
|
t.Fatalf("bad: expected:%#v\nactual:%#v\n", expected, resp.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
nomadClient, err := svccfg.Client()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to construct nomaad client, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
token, _, err := nomadClient.ACLTokens().Bootstrap(nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected acl system to be bootstrapped already, but was able to get the bootstrap token : %v", token)
|
||||||
|
}
|
||||||
|
// NOTE: fragile test, but it's the only way, AFAIK, to check that nomad is
|
||||||
|
// bootstrapped
|
||||||
|
if !strings.Contains(err.Error(), "bootstrap already done") {
|
||||||
|
t.Fatalf("expected acl system to be bootstrapped already: err: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBackend_config_access(t *testing.T) {
|
func TestBackend_config_access(t *testing.T) {
|
||||||
|
@ -121,7 +213,7 @@ func TestBackend_config_access(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup, svccfg := prepareTestContainer(t)
|
cleanup, svccfg := prepareTestContainer(t, true)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
connData := map[string]interface{}{
|
connData := map[string]interface{}{
|
||||||
|
@ -167,7 +259,7 @@ func TestBackend_renew_revoke(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup, svccfg := prepareTestContainer(t)
|
cleanup, svccfg := prepareTestContainer(t, true)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
connData := map[string]interface{}{
|
connData := map[string]interface{}{
|
||||||
|
@ -280,7 +372,7 @@ func TestBackend_CredsCreateEnvVar(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup, svccfg := prepareTestContainer(t)
|
cleanup, svccfg := prepareTestContainer(t, true)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
req := logical.TestRequest(t, logical.UpdateOperation, "role/test")
|
req := logical.TestRequest(t, logical.UpdateOperation, "role/test")
|
||||||
|
@ -320,7 +412,7 @@ func TestBackend_max_token_name_length(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanup, svccfg := prepareTestContainer(t)
|
cleanup, svccfg := prepareTestContainer(t, true)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
|
|
|
@ -129,6 +129,18 @@ func (b *backend) pathConfigAccessWrite(ctx context.Context, req *logical.Reques
|
||||||
conf.ClientKey = clientKey.(string)
|
conf.ClientKey = clientKey.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if conf.Token == "" {
|
||||||
|
client, err := clientFromConfig(conf)
|
||||||
|
if err != nil {
|
||||||
|
return logical.ErrorResponse("Token not provided and failed to constuct client"), err
|
||||||
|
}
|
||||||
|
token, _, err := client.ACLTokens().Bootstrap(nil)
|
||||||
|
if err != nil {
|
||||||
|
return logical.ErrorResponse("Token not provided and failed to bootstrap ACLs"), err
|
||||||
|
}
|
||||||
|
conf.Token = token.SecretID
|
||||||
|
}
|
||||||
|
|
||||||
conf.MaxTokenNameLength = data.Get("max_token_name_length").(int)
|
conf.MaxTokenNameLength = data.Get("max_token_name_length").(int)
|
||||||
|
|
||||||
entry, err := logical.StorageEntryJSON("config/access", conf)
|
entry, err := logical.StorageEntryJSON("config/access", conf)
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:feature
|
||||||
|
nomad: Bootstrap Nomad ACL system if no token is provided
|
||||||
|
```
|
Loading…
Reference in New Issue