From ba16684d0337d09f9e5541f2c33fe8d13a5e89f2 Mon Sep 17 00:00:00 2001 From: Robert <17119716+robmonte@users.noreply.github.com> Date: Wed, 9 Feb 2022 15:44:00 -0600 Subject: [PATCH] secret/consul: Add support for consul namespaces and admin partitions (#13850) * Add support for consul namespaces and admin partitions --- builtin/logical/consul/backend_test.go | 280 +++++++++++++++++- builtin/logical/consul/path_roles.go | 62 ++-- builtin/logical/consul/path_token.go | 64 ++-- changelog/13850.txt | 3 + go.mod | 8 +- go.sum | 12 + helper/testhelpers/consul/consulhelper.go | 97 +++++- .../testhelpers/teststorage/consul/consul.go | 2 +- physical/consul/consul_test.go | 6 +- .../consul_service_registration_test.go | 2 +- 10 files changed, 460 insertions(+), 76 deletions(-) create mode 100644 changelog/13850.txt diff --git a/builtin/logical/consul/backend_test.go b/builtin/logical/consul/backend_test.go index 0856a1664d12..d2c7a481c7fc 100644 --- a/builtin/logical/consul/backend_test.go +++ b/builtin/logical/consul/backend_test.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "fmt" "log" + "os" "reflect" "testing" "time" @@ -38,7 +39,7 @@ func testBackendConfigAccess(t *testing.T, version string) { t.Fatal(err) } - cleanup, consulConfig := consul.PrepareTestContainer(t, version) + cleanup, consulConfig := consul.PrepareTestContainer(t, version, false) defer cleanup() connData := map[string]interface{}{ @@ -87,7 +88,7 @@ func TestBackend_Renew_Revoke(t *testing.T) { t.Parallel() t.Run("legacy", func(t *testing.T) { t.Parallel() - testBackendRenewRevoke(t, "") + testBackendRenewRevoke(t, "1.4.4") }) testBackendRenewRevoke14(t, "") @@ -103,7 +104,7 @@ func testBackendRenewRevoke(t *testing.T, version string) { t.Fatal(err) } - cleanup, consulConfig := consul.PrepareTestContainer(t, version) + cleanup, consulConfig := consul.PrepareTestContainer(t, version, false) defer cleanup() connData := map[string]interface{}{ @@ -196,7 +197,7 @@ func testBackendRenewRevoke(t *testing.T, version string) { Value: []byte("bar"), }, nil) if err == nil { - t.Fatal("expected error") + t.Fatal("err: expected error") } } @@ -208,7 +209,7 @@ func testBackendRenewRevoke14(t *testing.T, version string) { t.Fatal(err) } - cleanup, consulConfig := consul.PrepareTestContainer(t, version) + cleanup, consulConfig := consul.PrepareTestContainer(t, version, false) defer cleanup() connData := map[string]interface{}{ @@ -320,7 +321,7 @@ func TestBackend_LocalToken(t *testing.T) { t.Fatal(err) } - cleanup, consulConfig := consul.PrepareTestContainer(t, "") + cleanup, consulConfig := consul.PrepareTestContainer(t, "", false) defer cleanup() connData := map[string]interface{}{ @@ -450,7 +451,7 @@ func TestBackend_Management(t *testing.T) { }) t.Run("post-1.4.0", func(t *testing.T) { t.Parallel() - testBackendManagement(t, "") + testBackendManagement(t, "1.4.4") }) }) } @@ -463,7 +464,7 @@ func testBackendManagement(t *testing.T, version string) { t.Fatal(err) } - cleanup, consulConfig := consul.PrepareTestContainer(t, version) + cleanup, consulConfig := consul.PrepareTestContainer(t, version, false) defer cleanup() connData := map[string]interface{}{ @@ -492,10 +493,9 @@ func TestBackend_Basic(t *testing.T) { t.Parallel() t.Run("legacy", func(t *testing.T) { t.Parallel() - testBackendRenewRevoke(t, "") + testBackendRenewRevoke(t, "1.4.4") }) - - testBackendBasic(t, "") + testBackendBasic(t, "1.4.4") }) }) } @@ -508,7 +508,7 @@ func testBackendBasic(t *testing.T, version string) { t.Fatal(err) } - cleanup, consulConfig := consul.PrepareTestContainer(t, version) + cleanup, consulConfig := consul.PrepareTestContainer(t, version, false) defer cleanup() connData := map[string]interface{}{ @@ -702,6 +702,262 @@ func testAccStepDeletePolicy(t *testing.T, name string) logicaltest.TestStep { } } +func TestBackend_Enterprise_Namespace(t *testing.T) { + if _, hasLicense := os.LookupEnv("CONSUL_LICENSE"); !hasLicense { + t.Skip("Skipping: No enterprise license found") + } + + testBackendEntNamespace(t) +} + +func TestBackend_Enterprise_Partition(t *testing.T) { + if _, hasLicense := os.LookupEnv("CONSUL_LICENSE"); !hasLicense { + t.Skip("Skipping: No enterprise license found") + } + + testBackendEntPartition(t) +} + +func testBackendEntNamespace(t *testing.T) { + config := logical.TestBackendConfig() + config.StorageView = &logical.InmemStorage{} + b, err := Factory(context.Background(), config) + if err != nil { + t.Fatal(err) + } + + cleanup, consulConfig := consul.PrepareTestContainer(t, "", true) + defer cleanup() + + connData := map[string]interface{}{ + "address": consulConfig.Address(), + "token": consulConfig.Token, + } + + req := &logical.Request{ + Storage: config.StorageView, + Operation: logical.UpdateOperation, + Path: "config/access", + Data: connData, + } + resp, err := b.HandleRequest(context.Background(), req) + if err != nil { + t.Fatal(err) + } + + // Create the role in namespace "ns1" + req.Path = "roles/test-ns" + req.Data = map[string]interface{}{ + "policies": []string{"ns-test"}, + "lease": "6h", + "consul_namespace": "ns1", + } + resp, err = b.HandleRequest(context.Background(), req) + if err != nil { + t.Fatal(err) + } + + req.Operation = logical.ReadOperation + req.Path = "creds/test-ns" + resp, err = b.HandleRequest(context.Background(), 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.TTL = 6 * time.Hour + + var d struct { + Token string `mapstructure:"token"` + Accessor string `mapstructure:"accessor"` + ConsulNamespace string `mapstructure:"consul_namespace"` + } + if err := mapstructure.Decode(resp.Data, &d); err != nil { + t.Fatal(err) + } + t.Logf("Generated namespace '%s' token: %s with accessor %s", d.ConsulNamespace, d.Token, d.Accessor) + + if d.ConsulNamespace != "ns1" { + t.Fatalf("Failed to access namespace") + } + + // Build a client and verify that the credentials work + consulapiConfig := consulapi.DefaultNonPooledConfig() + consulapiConfig.Address = connData["address"].(string) + consulapiConfig.Token = d.Token + client, err := consulapi.NewClient(consulapiConfig) + if err != nil { + t.Fatal(err) + } + + t.Log("Verifying that the generated token works...") + _, err = client.Catalog(), nil + if err != nil { + t.Fatal(err) + } + + req.Operation = logical.RenewOperation + req.Secret = generatedSecret + resp, err = b.HandleRequest(context.Background(), 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(context.Background(), req) + if err != nil { + t.Fatal(err) + } + + // Build a management client and verify that the token does not exist anymore + consulmgmtConfig := consulapi.DefaultNonPooledConfig() + consulmgmtConfig.Address = connData["address"].(string) + consulmgmtConfig.Token = connData["token"].(string) + mgmtclient, err := consulapi.NewClient(consulmgmtConfig) + if err != nil { + t.Fatal(err) + } + q := &consulapi.QueryOptions{ + Datacenter: "DC1", + Namespace: "ns1", + } + + t.Log("Verifying that the generated token does not exist...") + _, _, err = mgmtclient.ACL().TokenRead(d.Accessor, q) + if err == nil { + t.Fatal("err: expected error") + } +} + +func testBackendEntPartition(t *testing.T) { + config := logical.TestBackendConfig() + config.StorageView = &logical.InmemStorage{} + b, err := Factory(context.Background(), config) + if err != nil { + t.Fatal(err) + } + + cleanup, consulConfig := consul.PrepareTestContainer(t, "", true) + defer cleanup() + + connData := map[string]interface{}{ + "address": consulConfig.Address(), + "token": consulConfig.Token, + } + + req := &logical.Request{ + Storage: config.StorageView, + Operation: logical.UpdateOperation, + Path: "config/access", + Data: connData, + } + resp, err := b.HandleRequest(context.Background(), req) + if err != nil { + t.Fatal(err) + } + + // Create the role in partition "part1" + req.Path = "roles/test-part" + req.Data = map[string]interface{}{ + "policies": []string{"part-test"}, + "lease": "6h", + "partition": "part1", + } + resp, err = b.HandleRequest(context.Background(), req) + if err != nil { + t.Fatal(err) + } + + req.Operation = logical.ReadOperation + req.Path = "creds/test-part" + resp, err = b.HandleRequest(context.Background(), 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.TTL = 6 * time.Hour + + var d struct { + Token string `mapstructure:"token"` + Accessor string `mapstructure:"accessor"` + Partition string `mapstructure:"partition"` + } + if err := mapstructure.Decode(resp.Data, &d); err != nil { + t.Fatal(err) + } + t.Logf("Generated partition '%s' token: %s with accessor %s", d.Partition, d.Token, d.Accessor) + + if d.Partition != "part1" { + t.Fatalf("Failed to access partition") + } + + // Build a client and verify that the credentials work + consulapiConfig := consulapi.DefaultNonPooledConfig() + consulapiConfig.Address = connData["address"].(string) + consulapiConfig.Token = d.Token + client, err := consulapi.NewClient(consulapiConfig) + if err != nil { + t.Fatal(err) + } + + t.Log("Verifying that the generated token works...") + _, err = client.Catalog(), nil + if err != nil { + t.Fatal(err) + } + + req.Operation = logical.RenewOperation + req.Secret = generatedSecret + resp, err = b.HandleRequest(context.Background(), 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(context.Background(), req) + if err != nil { + t.Fatal(err) + } + + // Build a management client and verify that the token does not exist anymore + consulmgmtConfig := consulapi.DefaultNonPooledConfig() + consulmgmtConfig.Address = connData["address"].(string) + consulmgmtConfig.Token = connData["token"].(string) + mgmtclient, err := consulapi.NewClient(consulmgmtConfig) + if err != nil { + t.Fatal(err) + } + q := &consulapi.QueryOptions{ + Datacenter: "DC1", + Partition: "test1", + } + + t.Log("Verifying that the generated token does not exist...") + _, _, err = mgmtclient.ACL().TokenRead(d.Accessor, q) + if err == nil { + t.Fatal("err: expected error") + } +} + const testPolicy = ` key "" { policy = "write" diff --git a/builtin/logical/consul/path_roles.go b/builtin/logical/consul/path_roles.go index cc92c7d0e6b2..ee82dbc84d7a 100644 --- a/builtin/logical/consul/path_roles.go +++ b/builtin/logical/consul/path_roles.go @@ -26,7 +26,7 @@ func pathRoles(b *backend) *framework.Path { Fields: map[string]*framework.FieldSchema{ "name": { Type: framework.TypeString, - Description: "Name of the role", + Description: "Name of the role.", }, "policy": { @@ -71,6 +71,18 @@ Defaults to 'client'.`, Description: "Use ttl instead.", Deprecated: true, }, + + "consul_namespace": { + Type: framework.TypeString, + Description: `Indicates which namespace that the token will be +created within. Defaults to 'default'. Available in Consul 1.7 and above.`, + }, + + "partition": { + Type: framework.TypeString, + Description: `Indicates which admin partition that the token +will be created within. Defaults to 'default'. Available in Consul 1.11 and above.`, + }, }, Callbacks: map[logical.Operation]framework.OperationFunc{ @@ -113,11 +125,13 @@ func (b *backend) pathRolesRead(ctx context.Context, req *logical.Request, d *fr // Generate the response resp := &logical.Response{ Data: map[string]interface{}{ - "lease": int64(result.TTL.Seconds()), - "ttl": int64(result.TTL.Seconds()), - "max_ttl": int64(result.MaxTTL.Seconds()), - "token_type": result.TokenType, - "local": result.Local, + "lease": int64(result.TTL.Seconds()), + "ttl": int64(result.TTL.Seconds()), + "max_ttl": int64(result.MaxTTL.Seconds()), + "token_type": result.TokenType, + "local": result.Local, + "consul_namespace": result.ConsulNamespace, + "partition": result.Partition, }, } if result.Policy != "" { @@ -126,16 +140,14 @@ func (b *backend) pathRolesRead(ctx context.Context, req *logical.Request, d *fr if len(result.Policies) > 0 { resp.Data["policies"] = result.Policies } + return resp, nil } func (b *backend) pathRolesWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { tokenType := d.Get("token_type").(string) policy := d.Get("policy").(string) - name := d.Get("name").(string) policies := d.Get("policies").([]string) - local := d.Get("local").(bool) - if len(policies) == 0 { switch tokenType { case "client": @@ -173,13 +185,19 @@ func (b *backend) pathRolesWrite(ctx context.Context, req *logical.Request, d *f maxTTL = time.Second * time.Duration(maxTTLRaw.(int)) } + name := d.Get("name").(string) + local := d.Get("local").(bool) + namespace := d.Get("consul_namespace").(string) + partition := d.Get("partition").(string) entry, err := logical.StorageEntryJSON("policy/"+name, roleConfig{ - Policy: string(policyRaw), - Policies: policies, - TokenType: tokenType, - TTL: ttl, - MaxTTL: maxTTL, - Local: local, + Policy: string(policyRaw), + Policies: policies, + TokenType: tokenType, + TTL: ttl, + MaxTTL: maxTTL, + Local: local, + ConsulNamespace: namespace, + Partition: partition, }) if err != nil { return nil, err @@ -201,10 +219,12 @@ func (b *backend) pathRolesDelete(ctx context.Context, req *logical.Request, d * } type roleConfig struct { - Policy string `json:"policy"` - Policies []string `json:"policies"` - TTL time.Duration `json:"lease"` - MaxTTL time.Duration `json:"max_ttl"` - TokenType string `json:"token_type"` - Local bool `json:"local"` + Policy string `json:"policy"` + Policies []string `json:"policies"` + TTL time.Duration `json:"lease"` + MaxTTL time.Duration `json:"max_ttl"` + TokenType string `json:"token_type"` + Local bool `json:"local"` + ConsulNamespace string `json:"consul_namespace"` + Partition string `json:"partition"` } diff --git a/builtin/logical/consul/path_token.go b/builtin/logical/consul/path_token.go index 0bc774952568..bbcf7ec30599 100644 --- a/builtin/logical/consul/path_token.go +++ b/builtin/logical/consul/path_token.go @@ -20,7 +20,22 @@ func pathToken(b *backend) *framework.Path { Fields: map[string]*framework.FieldSchema{ "role": { Type: framework.TypeString, - Description: "Name of the role", + Description: "Name of the role.", + }, + + "policies": { + Type: framework.TypeCommaStringSlice, + Description: `List of policies to attach to the token.`, + }, + + "consul_namespace": { + Type: framework.TypeString, + Description: "Namespace to create the token in.", + }, + + "partition": { + Type: framework.TypeString, + Description: "Admin partition to create the token in.", }, }, @@ -32,7 +47,6 @@ func pathToken(b *backend) *framework.Path { func (b *backend) pathTokenRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { role := d.Get("role").(string) - entry, err := req.Storage.Get(ctx, "policy/"+role) if err != nil { return nil, fmt.Errorf("error retrieving role: %w", err) @@ -41,13 +55,13 @@ func (b *backend) pathTokenRead(ctx context.Context, req *logical.Request, d *fr return logical.ErrorResponse(fmt.Sprintf("role %q not found", role)), nil } - var result roleConfig - if err := entry.DecodeJSON(&result); err != nil { + var roleConfigData roleConfig + if err := entry.DecodeJSON(&roleConfigData); err != nil { return nil, err } - if result.TokenType == "" { - result.TokenType = "client" + if roleConfigData.TokenType == "" { + roleConfigData.TokenType = "client" } // Get the consul client @@ -66,12 +80,12 @@ func (b *backend) pathTokenRead(ctx context.Context, req *logical.Request, d *fr writeOpts = writeOpts.WithContext(ctx) // Create an ACLEntry for Consul pre 1.4 - if (result.Policy != "" && result.TokenType == "client") || - (result.Policy == "" && result.TokenType == "management") { + if (roleConfigData.Policy != "" && roleConfigData.TokenType == "client") || + (roleConfigData.Policy == "" && roleConfigData.TokenType == "management") { token, _, err := c.ACL().Create(&api.ACLEntry{ Name: tokenName, - Type: result.TokenType, - Rules: result.Policy, + Type: roleConfigData.TokenType, + Rules: roleConfigData.Policy, }, writeOpts) if err != nil { return logical.ErrorResponse(err.Error()), nil @@ -84,22 +98,26 @@ func (b *backend) pathTokenRead(ctx context.Context, req *logical.Request, d *fr "token": token, "role": role, }) - s.Secret.TTL = result.TTL - s.Secret.MaxTTL = result.MaxTTL + s.Secret.TTL = roleConfigData.TTL + s.Secret.MaxTTL = roleConfigData.MaxTTL return s, nil } // Create an ACLToken for Consul 1.4 and above - policyLink := []*api.ACLTokenPolicyLink{} - for _, policyName := range result.Policies { - policyLink = append(policyLink, &api.ACLTokenPolicyLink{ + policyLinks := []*api.ACLTokenPolicyLink{} + + for _, policyName := range roleConfigData.Policies { + policyLinks = append(policyLinks, &api.ACLTokenPolicyLink{ Name: policyName, }) } + token, _, err := c.ACL().TokenCreate(&api.ACLToken{ Description: tokenName, - Policies: policyLink, - Local: result.Local, + Policies: policyLinks, + Local: roleConfigData.Local, + Namespace: roleConfigData.ConsulNamespace, + Partition: roleConfigData.Partition, }, writeOpts) if err != nil { return logical.ErrorResponse(err.Error()), nil @@ -107,16 +125,18 @@ func (b *backend) pathTokenRead(ctx context.Context, req *logical.Request, d *fr // Use the helper to create the secret s := b.Secret(SecretTokenType).Response(map[string]interface{}{ - "token": token.SecretID, - "accessor": token.AccessorID, - "local": token.Local, + "token": token.SecretID, + "accessor": token.AccessorID, + "local": token.Local, + "consul_namespace": token.Namespace, + "partition": token.Partition, }, map[string]interface{}{ "token": token.AccessorID, "role": role, "version": tokenPolicyType, }) - s.Secret.TTL = result.TTL - s.Secret.MaxTTL = result.MaxTTL + s.Secret.TTL = roleConfigData.TTL + s.Secret.MaxTTL = roleConfigData.MaxTTL return s, nil } diff --git a/changelog/13850.txt b/changelog/13850.txt new file mode 100644 index 000000000000..6e600d3fd917 --- /dev/null +++ b/changelog/13850.txt @@ -0,0 +1,3 @@ +```release-note:improvement +secrets/consul: Add support for consul enterprise namespaces and admin partitions. +``` \ No newline at end of file diff --git a/go.mod b/go.mod index 25745927a58a..7b50f30c9fe7 100644 --- a/go.mod +++ b/go.mod @@ -57,7 +57,7 @@ require ( github.com/google/go-metrics-stackdriver v0.2.0 github.com/hashicorp/cap v0.1.1 github.com/hashicorp/consul-template v0.27.2-0.20211014231529-4ff55381f1c4 - github.com/hashicorp/consul/api v1.11.0 + github.com/hashicorp/consul/api v1.12.0 github.com/hashicorp/errwrap v1.1.0 github.com/hashicorp/go-cleanhttp v0.5.2 github.com/hashicorp/go-discover v0.0.0-20210818145131-c573d69da192 @@ -283,8 +283,8 @@ require ( github.com/hashicorp/go-version v1.3.0 // indirect github.com/hashicorp/jsonapi v0.0.0-20210826224640-ee7dae0fb22d // indirect github.com/hashicorp/logutils v1.0.0 // indirect - github.com/hashicorp/mdns v1.0.1 // indirect - github.com/hashicorp/serf v0.9.5 // indirect + github.com/hashicorp/mdns v1.0.4 // indirect + github.com/hashicorp/serf v0.9.6 // indirect github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 // indirect github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect github.com/huandu/xstrings v1.3.2 // indirect @@ -305,7 +305,7 @@ require ( github.com/mattn/go-ieproxy v0.0.1 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect - github.com/miekg/dns v1.1.40 // indirect + github.com/miekg/dns v1.1.41 // indirect github.com/mitchellh/hashstructure v1.0.0 // indirect github.com/mitchellh/iochan v1.0.0 // indirect github.com/mitchellh/pointerstructure v1.2.0 // indirect diff --git a/go.sum b/go.sum index 4d9ad46edaae..beb4d322cdd7 100644 --- a/go.sum +++ b/go.sum @@ -793,6 +793,8 @@ github.com/hashicorp/consul-template v0.27.2-0.20211014231529-4ff55381f1c4/go.mo github.com/hashicorp/consul/api v1.4.0/go.mod h1:xc8u05kyMa3Wjr9eEAsIAo3dg8+LywT5E/Cl7cNS5nU= github.com/hashicorp/consul/api v1.11.0 h1:Hw/G8TtRvOElqxVIhBzXciiSTbapq8hZ2XKZsXk5ZCE= github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/api v1.12.0 h1:k3y1FYv6nuKyNTqj6w9gXOx5r5CfLj/k/euUeBXj1OY= +github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.4.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= github.com/hashicorp/consul/sdk v0.4.1-0.20200910203702-bb2b5dd871ca/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= @@ -911,9 +913,13 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/mdns v1.0.1 h1:XFSOubp8KWB+Jd2PDyaX5xUd5bhSP/+pTDZVDMzZJM8= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/mdns v1.0.4 h1:sY0CMhFmjIPDMlTB+HfymFHCaYLhgifZ0QhjaYKD/UQ= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC7AO2g= github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.3.0 h1:8+567mCcFDnS5ADl7lrpxPMWiFCElyUEeW0gtj34fMA= +github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/nomad/api v0.0.0-20211006193434-215bf04bc650 h1:pSi8Q6BuijRU9vK/b4/evBeDMXSFBlOX5CTUo3iY4HY= github.com/hashicorp/nomad/api v0.0.0-20211006193434-215bf04bc650/go.mod h1:vYHP9jMXk4/T2qNUbWlQ1OHCA1hHLil3nvqSmz8mtgc= github.com/hashicorp/raft v1.0.1/go.mod h1:DVSAWItjLjTOkVbSpWQ0j0kUADIvDaCtBxIcbNAQLkI= @@ -934,6 +940,8 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/hashicorp/serf v0.9.4/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= github.com/hashicorp/serf v0.9.5 h1:EBWvyu9tcRszt3Bxp3KNssBMP1KuHWyO51lz9+786iM= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/serf v0.9.6 h1:uuEX1kLR6aoda1TBttmJQKDLZE1Ob7KN0NPdE7EtCDc= +github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hashicorp/vault-plugin-auth-alicloud v0.10.0 h1:ujwHy67QeSwIWN2OLw4K/9ImcZaNU2jeNpWDI17/aQk= github.com/hashicorp/vault-plugin-auth-alicloud v0.10.0/go.mod h1:GqQnzKRACjoUJCq8cHXJKPIMbFpIwxaLTwz8dyYghvM= github.com/hashicorp/vault-plugin-auth-azure v0.9.2 h1:Q2+z7tAMfc141CWA/4RemI/VtrnuJ1UMwz80EYP73gA= @@ -1149,6 +1157,8 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA= github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -1730,6 +1740,7 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -1859,6 +1870,7 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/helper/testhelpers/consul/consulhelper.go b/helper/testhelpers/consul/consulhelper.go index cd112bc1e2ae..be21a53bbf8e 100644 --- a/helper/testhelpers/consul/consulhelper.go +++ b/helper/testhelpers/consul/consulhelper.go @@ -26,7 +26,9 @@ func (c *Config) APIConfig() *consulapi.Config { // the Consul version used will be given by the environment variable // CONSUL_DOCKER_VERSION, or if that's empty, whatever we've hardcoded as the // the latest Consul version. -func PrepareTestContainer(t *testing.T, version string) (func(), *Config) { +func PrepareTestContainer(t *testing.T, version string, isEnterprise bool) (func(), *Config) { + t.Helper() + if retAddress := os.Getenv("CONSUL_HTTP_ADDR"); retAddress != "" { shp, err := docker.NewServiceHostPortParse(retAddress) if err != nil { @@ -41,21 +43,38 @@ func PrepareTestContainer(t *testing.T, version string) (func(), *Config) { if consulVersion != "" { version = consulVersion } else { - version = "1.7.2" // Latest Consul version, update as new releases come out + version = "1.11.2" // Latest Consul version, update as new releases come out } } if strings.HasPrefix(version, "1.3") { config = `datacenter = "test" acl_default_policy = "deny" acl_datacenter = "test" acl_master_token = "test"` } - repo := os.Getenv("CONSUL_DOCKER_REPO") - if repo == "" { - repo = "consul" + name := "consul" + repo := "consul" + var envVars []string + // If running the enterprise container, set the appropriate values below. + if isEnterprise { + version += "-ent" + name = "consul-enterprise" + repo = "hashicorp/consul-enterprise" + license, hasLicense := os.LookupEnv("CONSUL_LICENSE") + envVars = append(envVars, "CONSUL_LICENSE="+license) + + if !hasLicense { + t.Fatalf("Failed to find enterprise license") + } + } + + if dockerRepo, hasEnvRepo := os.LookupEnv("CONSUL_DOCKER_REPO"); hasEnvRepo { + repo = dockerRepo } + runner, err := docker.NewServiceRunner(docker.RunOptions{ - ContainerName: "consul", + ContainerName: name, ImageRepo: repo, ImageTag: version, + Env: envVars, Cmd: []string{"agent", "-dev", "-client", "0.0.0.0", "-hcl", config}, Ports: []string{"8500/tcp"}, AuthUsername: os.Getenv("CONSUL_DOCKER_USERNAME"), @@ -102,13 +121,12 @@ func PrepareTestContainer(t *testing.T, version string) (func(), *Config) { Name: "test", Description: "test", Rules: `node_prefix "" { - policy = "write" - } + policy = "write" + } - service_prefix "" { - policy = "read" - } - `, + service_prefix "" { + policy = "read" + }`, } q := &consulapi.WriteOptions{ Token: consulToken, @@ -117,13 +135,68 @@ func PrepareTestContainer(t *testing.T, version string) (func(), *Config) { if err != nil { return nil, err } + + // Configure a namespace and parition if testing enterprise Consul + if isEnterprise { + + // Namespaces require Consul 1.7 or newer + namespace := &consulapi.Namespace{ + Name: "ns1", + Description: "ns1 test", + } + + _, _, err = consul.Namespaces().Create(namespace, q) + if err != nil { + return nil, err + } + + nsPolicy := &consulapi.ACLPolicy{ + Name: "ns-test", + Description: "namespace test", + Namespace: "ns1", + Rules: `service_prefix "" { + policy = "read" + }`, + } + _, _, err = consul.ACL().PolicyCreate(nsPolicy, q) + if err != nil { + return nil, err + } + + // Partitions require Consul 1.11 or newer + partition := &consulapi.Partition{ + Name: "part1", + Description: "part1 test", + } + + _, _, err = consul.Partitions().Create(ctx, partition, q) + if err != nil { + return nil, err + } + + partPolicy := &consulapi.ACLPolicy{ + Name: "part-test", + Description: "partition test", + Partition: "part1", + Rules: `service_prefix "" { + policy = "read" + }`, + } + _, _, err = consul.ACL().PolicyCreate(partPolicy, q) + if err != nil { + return nil, err + } + } + return &Config{ ServiceHostPort: *shp, Token: consulToken, }, nil }) + if err != nil { t.Fatalf("Could not start docker Consul: %s", err) } + return svc.Cleanup, svc.Config.(*Config) } diff --git a/helper/testhelpers/teststorage/consul/consul.go b/helper/testhelpers/teststorage/consul/consul.go index 8fc5de269b07..13c4f15d8331 100644 --- a/helper/testhelpers/teststorage/consul/consul.go +++ b/helper/testhelpers/teststorage/consul/consul.go @@ -12,7 +12,7 @@ import ( ) func MakeConsulBackend(t testing.T, logger hclog.Logger) *vault.PhysicalBackendBundle { - cleanup, config := consul.PrepareTestContainer(t.(*realtesting.T), "") + cleanup, config := consul.PrepareTestContainer(t.(*realtesting.T), "", false) consulConf := map[string]string{ "address": config.Address(), diff --git a/physical/consul/consul_test.go b/physical/consul/consul_test.go index 2f1e71ca2f4d..973c8d464fe0 100644 --- a/physical/consul/consul_test.go +++ b/physical/consul/consul_test.go @@ -157,7 +157,7 @@ func TestConsul_newConsulBackend(t *testing.T) { } func TestConsulBackend(t *testing.T) { - cleanup, config := consul.PrepareTestContainer(t, "1.4.4") + cleanup, config := consul.PrepareTestContainer(t, "1.4.4", false) defer cleanup() client, err := api.NewClient(config.APIConfig()) @@ -187,7 +187,7 @@ func TestConsulBackend(t *testing.T) { } func TestConsul_TooLarge(t *testing.T) { - cleanup, config := consul.PrepareTestContainer(t, "1.4.4") + cleanup, config := consul.PrepareTestContainer(t, "1.4.4", false) defer cleanup() client, err := api.NewClient(config.APIConfig()) @@ -250,7 +250,7 @@ func TestConsul_TooLarge(t *testing.T) { } func TestConsulHABackend(t *testing.T) { - cleanup, config := consul.PrepareTestContainer(t, "1.4.4") + cleanup, config := consul.PrepareTestContainer(t, "1.4.4", false) defer cleanup() client, err := api.NewClient(config.APIConfig()) diff --git a/serviceregistration/consul/consul_service_registration_test.go b/serviceregistration/consul/consul_service_registration_test.go index ba0822f3dbbb..dc6749a87a66 100644 --- a/serviceregistration/consul/consul_service_registration_test.go +++ b/serviceregistration/consul/consul_service_registration_test.go @@ -50,7 +50,7 @@ func testConsulServiceRegistrationConfig(t *testing.T, conf *consulConf) *servic // TestConsul_ServiceRegistration tests whether consul ServiceRegistration works func TestConsul_ServiceRegistration(t *testing.T) { // Prepare a docker-based consul instance - cleanup, config := consul.PrepareTestContainer(t, "") + cleanup, config := consul.PrepareTestContainer(t, "", false) defer cleanup() // Create a consul client