Skip to content

Commit

Permalink
secret/consul: Add support for consul namespaces and admin partitions (
Browse files Browse the repository at this point in the history
…#13850)

* Add support for consul namespaces and admin partitions
  • Loading branch information
robmonte authored Feb 9, 2022
1 parent 9560353 commit ba16684
Show file tree
Hide file tree
Showing 10 changed files with 460 additions and 76 deletions.
280 changes: 268 additions & 12 deletions builtin/logical/consul/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/base64"
"fmt"
"log"
"os"
"reflect"
"testing"
"time"
Expand Down Expand Up @@ -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{}{
Expand Down Expand Up @@ -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, "")
Expand All @@ -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{}{
Expand Down Expand Up @@ -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")
}
}

Expand All @@ -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{}{
Expand Down Expand Up @@ -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{}{
Expand Down Expand Up @@ -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")
})
})
}
Expand All @@ -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{}{
Expand Down Expand Up @@ -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")
})
})
}
Expand All @@ -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{}{
Expand Down Expand Up @@ -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"
Expand Down
Loading

0 comments on commit ba16684

Please sign in to comment.