// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package consul import ( "context" "encoding/base64" "fmt" "log" "os" "reflect" "strings" "testing" "time" consulapi "github.com/hashicorp/consul/api" "github.com/hashicorp/vault/helper/testhelpers/consul" logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical" "github.com/hashicorp/vault/sdk/logical" "github.com/mitchellh/mapstructure" ) func TestBackend_Config_Access(t *testing.T) { t.Run("config_access", func(t *testing.T) { t.Parallel() t.Run("pre-1.4.0", func(t *testing.T) { t.Parallel() testBackendConfigAccess(t, "1.3.1", true) }) t.Run("post-1.4.0", func(t *testing.T) { t.Parallel() testBackendConfigAccess(t, "", true) }) t.Run("pre-1.4.0 automatic-bootstrap", func(t *testing.T) { t.Parallel() testBackendConfigAccess(t, "1.3.1", false) }) t.Run("post-1.4.0 automatic-bootstrap", func(t *testing.T) { t.Parallel() testBackendConfigAccess(t, "", false) }) }) } func testBackendConfigAccess(t *testing.T, version string, autoBootstrap bool) { config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { t.Fatal(err) } cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, autoBootstrap) defer cleanup() connData := map[string]interface{}{ "address": consulConfig.Address(), } if autoBootstrap || strings.HasPrefix(version, "1.3") { connData["token"] = consulConfig.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), "scheme": "http", } 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) { t.Run("renew_revoke", func(t *testing.T) { t.Parallel() t.Run("pre-1.4.0", func(t *testing.T) { t.Parallel() testBackendRenewRevoke(t, "1.3.1") }) t.Run("post-1.4.0", func(t *testing.T) { t.Parallel() t.Run("legacy", func(t *testing.T) { t.Parallel() testBackendRenewRevoke(t, "1.4.4") }) t.Run("param-policies", func(t *testing.T) { t.Parallel() testBackendRenewRevoke14(t, "", "policies") }) t.Run("param-consul_policies", func(t *testing.T) { t.Parallel() testBackendRenewRevoke14(t, "", "consul_policies") }) t.Run("both-params", func(t *testing.T) { t.Parallel() testBackendRenewRevoke14(t, "", "both") }) }) }) } func testBackendRenewRevoke(t *testing.T, version string) { config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { t.Fatal(err) } cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, 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, } _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } req.Path = "roles/test" req.Data = map[string]interface{}{ "policy": base64.StdEncoding.EncodeToString([]byte(testPolicy)), "lease": "6h", } _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } req.Operation = logical.ReadOperation req.Path = "creds/test" 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"` } if err := mapstructure.Decode(resp.Data, &d); err != nil { t.Fatal(err) } // Build a client and verify that the credentials work consulapiConfig := consulapi.DefaultConfig() consulapiConfig.Address = connData["address"].(string) consulapiConfig.Token = d.Token client, err := consulapi.NewClient(consulapiConfig) if err != nil { t.Fatal(err) } _, err = client.KV().Put(&consulapi.KVPair{ Key: "foo", Value: []byte("bar"), }, 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 _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } _, err = client.KV().Put(&consulapi.KVPair{ Key: "foo", Value: []byte("bar"), }, nil) if err == nil { t.Fatal("err: expected error") } } func testBackendRenewRevoke14(t *testing.T, version string, policiesParam string) { config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { t.Fatal(err) } cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, 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, } _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } req.Path = "roles/test" req.Data = map[string]interface{}{ "lease": "6h", } if policiesParam == "both" { req.Data["policies"] = []string{"wrong-name"} req.Data["consul_policies"] = []string{"test"} } else { req.Data[policiesParam] = []string{"test"} } _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } read := &logical.Request{ Storage: config.StorageView, Operation: logical.ReadOperation, Path: "roles/test", Data: connData, } roleResp, err := b.HandleRequest(context.Background(), read) expectExtract := roleResp.Data["consul_policies"] respExtract := roleResp.Data[policiesParam] if respExtract != nil { if expectExtract.([]string)[0] != respExtract.([]string)[0] { t.Errorf("mismatch: response consul_policies '%s' does not match '[test]'", roleResp.Data["consul_policies"]) } } req.Operation = logical.ReadOperation req.Path = "creds/test" 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"` } if err := mapstructure.Decode(resp.Data, &d); err != nil { t.Fatal(err) } // 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) } _, 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 _, 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", } _, _, err = mgmtclient.ACL().TokenRead(d.Accessor, q) if err == nil { t.Fatal("err: expected error") } } func TestBackend_LocalToken(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, "", false, 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, } _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } req.Path = "roles/test" req.Data = map[string]interface{}{ "consul_policies": []string{"test"}, "ttl": "6h", "local": false, } _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } req.Path = "roles/test_local" req.Data = map[string]interface{}{ "consul_policies": []string{"test"}, "ttl": "6h", "local": true, } _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } req.Operation = logical.ReadOperation req.Path = "creds/test" 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()) } var d struct { Token string `mapstructure:"token"` Accessor string `mapstructure:"accessor"` Local bool `mapstructure:"local"` } if err := mapstructure.Decode(resp.Data, &d); err != nil { t.Fatal(err) } if d.Local { t.Fatalf("requested global token, got local one") } // 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) } _, err = client.Catalog(), nil if err != nil { t.Fatal(err) } req.Operation = logical.ReadOperation req.Path = "creds/test_local" 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()) } if err := mapstructure.Decode(resp.Data, &d); err != nil { t.Fatal(err) } if !d.Local { t.Fatalf("requested local token, got global one") } // 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) } _, err = client.Catalog(), nil if err != nil { t.Fatal(err) } } func TestBackend_Management(t *testing.T) { t.Run("management", func(t *testing.T) { t.Parallel() t.Run("pre-1.4.0", func(t *testing.T) { t.Parallel() testBackendManagement(t, "1.3.1") }) t.Run("post-1.4.0", func(t *testing.T) { t.Parallel() testBackendManagement(t, "1.4.4") }) testBackendManagement(t, "1.10.8") }) } func testBackendManagement(t *testing.T, version string) { config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { t.Fatal(err) } cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, true) defer cleanup() connData := map[string]interface{}{ "address": consulConfig.Address(), "token": consulConfig.Token, } logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ testAccStepConfig(t, connData), testAccStepWriteManagementPolicy(t, "test", ""), testAccStepReadManagementToken(t, "test", connData), }, }) } func TestBackend_Basic(t *testing.T) { t.Run("basic", func(t *testing.T) { t.Parallel() t.Run("pre-1.4.0", func(t *testing.T) { t.Parallel() testBackendBasic(t, "1.3.1") }) t.Run("post-1.4.0", func(t *testing.T) { t.Parallel() t.Run("legacy", func(t *testing.T) { t.Parallel() testBackendBasic(t, "1.4.4") }) testBackendBasic(t, "1.10.8") }) }) } func testBackendBasic(t *testing.T, version string) { config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { t.Fatal(err) } cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, true) defer cleanup() connData := map[string]interface{}{ "address": consulConfig.Address(), "token": consulConfig.Token, } logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ testAccStepConfig(t, connData), testAccStepWritePolicy(t, "test", testPolicy, ""), testAccStepReadToken(t, "test", connData), }, }) } func TestBackend_crud(t *testing.T) { b, _ := Factory(context.Background(), logical.TestBackendConfig()) logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ testAccStepWritePolicy(t, "test", testPolicy, ""), testAccStepWritePolicy(t, "test2", testPolicy, ""), testAccStepWritePolicy(t, "test3", testPolicy, ""), testAccStepReadPolicy(t, "test", testPolicy, 0), testAccStepListPolicy(t, []string{"test", "test2", "test3"}), testAccStepDeletePolicy(t, "test"), }, }) } func TestBackend_role_lease(t *testing.T) { b, _ := Factory(context.Background(), logical.TestBackendConfig()) logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ testAccStepWritePolicy(t, "test", testPolicy, "6h"), testAccStepReadPolicy(t, "test", testPolicy, 6*time.Hour), testAccStepDeletePolicy(t, "test"), }, }) } func testAccStepConfig(t *testing.T, config map[string]interface{}) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.UpdateOperation, Path: "config/access", Data: config, } } func testAccStepReadToken(t *testing.T, name string, conf map[string]interface{}) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.ReadOperation, Path: "creds/" + name, Check: func(resp *logical.Response) error { var d struct { Token string `mapstructure:"token"` } if err := mapstructure.Decode(resp.Data, &d); err != nil { return err } log.Printf("[WARN] Generated token: %s", d.Token) // Build a client and verify that the credentials work config := consulapi.DefaultConfig() config.Address = conf["address"].(string) config.Token = d.Token client, err := consulapi.NewClient(config) if err != nil { return err } log.Printf("[WARN] Verifying that the generated token works...") _, err = client.KV().Put(&consulapi.KVPair{ Key: "foo", Value: []byte("bar"), }, nil) if err != nil { return err } return nil }, } } func testAccStepReadManagementToken(t *testing.T, name string, conf map[string]interface{}) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.ReadOperation, Path: "creds/" + name, Check: func(resp *logical.Response) error { var d struct { Token string `mapstructure:"token"` } if err := mapstructure.Decode(resp.Data, &d); err != nil { return err } log.Printf("[WARN] Generated token: %s", d.Token) // Build a client and verify that the credentials work config := consulapi.DefaultConfig() config.Address = conf["address"].(string) config.Token = d.Token client, err := consulapi.NewClient(config) if err != nil { return err } log.Printf("[WARN] Verifying that the generated token works...") _, _, err = client.ACL().Create(&consulapi.ACLEntry{ Type: "management", Name: "test2", }, nil) if err != nil { return err } return nil }, } } func testAccStepWritePolicy(t *testing.T, name string, policy string, lease string) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.UpdateOperation, Path: "roles/" + name, Data: map[string]interface{}{ "policy": base64.StdEncoding.EncodeToString([]byte(policy)), "lease": lease, }, } } func testAccStepWriteManagementPolicy(t *testing.T, name string, lease string) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.UpdateOperation, Path: "roles/" + name, Data: map[string]interface{}{ "token_type": "management", "lease": lease, }, } } func testAccStepReadPolicy(t *testing.T, name string, policy string, lease time.Duration) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.ReadOperation, Path: "roles/" + name, Check: func(resp *logical.Response) error { policyRaw := resp.Data["policy"].(string) out, err := base64.StdEncoding.DecodeString(policyRaw) if err != nil { return err } if string(out) != policy { return fmt.Errorf("mismatch: %s %s", out, policy) } l := resp.Data["lease"].(int64) if lease != time.Second*time.Duration(l) { return fmt.Errorf("mismatch: %v %v", l, lease) } return nil }, } } func testAccStepListPolicy(t *testing.T, names []string) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.ListOperation, Path: "roles/", Check: func(resp *logical.Response) error { respKeys := resp.Data["keys"].([]string) if !reflect.DeepEqual(respKeys, names) { return fmt.Errorf("mismatch: %#v %#v", respKeys, names) } return nil }, } } func testAccStepDeletePolicy(t *testing.T, name string) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.DeleteOperation, Path: "roles/" + name, } } func TestBackend_Roles(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, "", false, 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, } _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } // Create the consul_roles role req.Path = "roles/test-consul-roles" req.Data = map[string]interface{}{ "consul_roles": []string{"role-test"}, "lease": "6h", } _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } req.Operation = logical.ReadOperation req.Path = "creds/test-consul-roles" 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"` } if err := mapstructure.Decode(resp.Data, &d); err != nil { t.Fatal(err) } // 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) } _, 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 _, 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", } _, _, err = mgmtclient.ACL().TokenRead(d.Accessor, q) if err == nil { t.Fatal("err: expected error") } } func TestBackend_Enterprise_Diff_Namespace_Revocation(t *testing.T) { if _, hasLicense := os.LookupEnv("CONSUL_LICENSE"); !hasLicense { t.Skip("Skipping: No enterprise license found") } testBackendEntDiffNamespaceRevocation(t) } func TestBackend_Enterprise_Diff_Partition_Revocation(t *testing.T) { if _, hasLicense := os.LookupEnv("CONSUL_LICENSE"); !hasLicense { t.Skip("Skipping: No enterprise license found") } testBackendEntDiffPartitionRevocation(t) } 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 testBackendEntDiffNamespaceRevocation(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, true) defer cleanup() // Perform additional Consul configuration consulapiConfig := consulapi.DefaultNonPooledConfig() consulapiConfig.Address = consulConfig.Address() consulapiConfig.Token = consulConfig.Token client, err := consulapi.NewClient(consulapiConfig) if err != nil { t.Fatal(err) } // Create Policy in default namespace to manage ACLs in a different // namespace nsPol := &consulapi.ACLPolicy{ Name: "diff-ns-test", Description: "policy to test management of ACLs in one ns from another", Rules: `namespace "ns1" { acl="write" } `, } pol, _, err := client.ACL().PolicyCreate(nsPol, nil) if err != nil { t.Fatal(err) } // Create new Token in default namespace with new ACL cToken, _, err := client.ACL().TokenCreate( &consulapi.ACLToken{ Policies: []*consulapi.ACLLink{{ID: pol.ID}}, }, nil) if err != nil { t.Fatal(err) } // Write backend config connData := map[string]interface{}{ "address": consulConfig.Address(), "token": cToken.SecretID, } req := &logical.Request{ Storage: config.StorageView, Operation: logical.UpdateOperation, Path: "config/access", Data: connData, } _, 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{}{ "consul_policies": []string{"ns-test"}, "lease": "6h", "consul_namespace": "ns1", } _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } // Get Token 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 // Verify Secret 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) } if d.ConsulNamespace != "ns1" { t.Fatalf("Failed to access namespace") } // Revoke the credential req.Operation = logical.RevokeOperation req.Secret = generatedSecret _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatalf("Revocation failed: %v", 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", } _, _, err = mgmtclient.ACL().TokenRead(d.Accessor, q) if err == nil { t.Fatal("err: expected error") } } func testBackendEntDiffPartitionRevocation(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, true) defer cleanup() // Perform additional Consul configuration consulapiConfig := consulapi.DefaultNonPooledConfig() consulapiConfig.Address = consulConfig.Address() consulapiConfig.Token = consulConfig.Token client, err := consulapi.NewClient(consulapiConfig) if err != nil { t.Fatal(err) } // Create Policy in default partition to manage ACLs in a different // partition partPol := &consulapi.ACLPolicy{ Name: "diff-part-test", Description: "policy to test management of ACLs in one part from another", Rules: `partition "part1" { acl="write" } `, } pol, _, err := client.ACL().PolicyCreate(partPol, nil) if err != nil { t.Fatal(err) } // Create new Token in default partition with new ACL cToken, _, err := client.ACL().TokenCreate( &consulapi.ACLToken{ Policies: []*consulapi.ACLLink{{ID: pol.ID}}, }, nil) if err != nil { t.Fatal(err) } // Write backend config connData := map[string]interface{}{ "address": consulConfig.Address(), "token": cToken.SecretID, } req := &logical.Request{ Storage: config.StorageView, Operation: logical.UpdateOperation, Path: "config/access", Data: connData, } _, 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{}{ "consul_policies": []string{"part-test"}, "lease": "6h", "partition": "part1", } _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } // Get Token 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 // Verify Secret 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) } if d.Partition != "part1" { t.Fatalf("Failed to access partition") } // Revoke the credential req.Operation = logical.RevokeOperation req.Secret = generatedSecret _, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatalf("Revocation failed: %v", 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: "part1", } _, _, err = mgmtclient.ACL().TokenRead(d.Accessor, q) if err == nil { t.Fatal("err: expected error") } } 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, 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, } _, 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{}{ "consul_policies": []string{"ns-test"}, "lease": "6h", "consul_namespace": "ns1", } _, 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) } 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) } _, 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 _, 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", } _, _, 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, 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, } _, 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{}{ "consul_policies": []string{"part-test"}, "lease": "6h", "partition": "part1", } _, 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) } 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) } _, 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 _, 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", } _, _, err = mgmtclient.ACL().TokenRead(d.Accessor, q) if err == nil { t.Fatal("err: expected error") } } func TestBackendRenewRevokeRolesAndIdentities(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, "", false, 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) } cases := map[string]struct { RoleName string RoleData map[string]interface{} }{ "just role": { "r", map[string]interface{}{ "consul_roles": []string{"role-test"}, "lease": "6h", }, }, "role and policies": { "rp", map[string]interface{}{ "consul_policies": []string{"test"}, "consul_roles": []string{"role-test"}, "lease": "6h", }, }, "service identity": { "si", map[string]interface{}{ "service_identities": "service1", "lease": "6h", }, }, "service identity and policies": { "sip", map[string]interface{}{ "consul_policies": []string{"test"}, "service_identities": "service1", "lease": "6h", }, }, "service identity and role": { "sir", map[string]interface{}{ "consul_roles": []string{"role-test"}, "service_identities": "service1", "lease": "6h", }, }, "service identity and role and policies": { "sirp", map[string]interface{}{ "consul_policies": []string{"test"}, "consul_roles": []string{"role-test"}, "service_identities": "service1", "lease": "6h", }, }, "node identity": { "ni", map[string]interface{}{ "node_identities": []string{"node1:dc1"}, "lease": "6h", }, }, "node identity and policies": { "nip", map[string]interface{}{ "consul_policies": []string{"test"}, "node_identities": []string{"node1:dc1"}, "lease": "6h", }, }, "node identity and role": { "nir", map[string]interface{}{ "consul_roles": []string{"role-test"}, "node_identities": []string{"node1:dc1"}, "lease": "6h", }, }, "node identity and role and policies": { "nirp", map[string]interface{}{ "consul_policies": []string{"test"}, "consul_roles": []string{"role-test"}, "node_identities": []string{"node1:dc1"}, "lease": "6h", }, }, "node identity and service identity": { "nisi", map[string]interface{}{ "service_identities": "service1", "node_identities": []string{"node1:dc1"}, "lease": "6h", }, }, "node identity and service identity and policies": { "nisip", map[string]interface{}{ "consul_policies": []string{"test"}, "service_identities": "service1", "node_identities": []string{"node1:dc1"}, "lease": "6h", }, }, "node identity and service identity and role": { "nisir", map[string]interface{}{ "consul_roles": []string{"role-test"}, "service_identities": "service1", "node_identities": []string{"node1:dc1"}, "lease": "6h", }, }, "node identity and service identity and role and policies": { "nisirp", map[string]interface{}{ "consul_policies": []string{"test"}, "consul_roles": []string{"role-test"}, "service_identities": "service1", "node_identities": []string{"node1:dc1"}, "lease": "6h", }, }, } for description, tc := range cases { t.Logf("Testing: %s", description) req.Operation = logical.UpdateOperation req.Path = fmt.Sprintf("roles/%s", tc.RoleName) req.Data = tc.RoleData resp, err = b.HandleRequest(context.Background(), req) if err != nil { t.Fatal(err) } req.Operation = logical.ReadOperation req.Path = fmt.Sprintf("creds/%s", tc.RoleName) 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"` } if err := mapstructure.Decode(resp.Data, &d); err != nil { t.Fatal(err) } // 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) } _, 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) q := &consulapi.QueryOptions{ Datacenter: "DC1", } _, _, err = mgmtclient.ACL().TokenRead(d.Accessor, q) if err == nil { t.Fatal("err: expected error") } } } const testPolicy = ` key "" { policy = "write" }`