secret/consul: Add support for consul namespaces and admin partitions (#13850)
* Add support for consul namespaces and admin partitions
This commit is contained in:
parent
571804390e
commit
d0832a1993
|
@ -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"
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
secrets/consul: Add support for consul enterprise namespaces and admin partitions.
|
||||
```
|
8
go.mod
8
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
|
||||
|
|
12
go.sum
12
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=
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue