secret/consul: Add Consul ACL roles support (#14014)
Co-authored-by: Brandon Ingalls <brandon@ingalls.io>
This commit is contained in:
parent
8e504f59e8
commit
91f5069c03
|
@ -22,7 +22,7 @@ func TestBackend_Config_Access(t *testing.T) {
|
|||
t.Parallel()
|
||||
t.Run("pre-1.4.0", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testBackendConfigAccess(t, "1.3.0")
|
||||
testBackendConfigAccess(t, "1.3.1")
|
||||
})
|
||||
t.Run("post-1.4.0", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
@ -82,7 +82,7 @@ func TestBackend_Renew_Revoke(t *testing.T) {
|
|||
t.Parallel()
|
||||
t.Run("pre-1.4.0", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testBackendRenewRevoke(t, "1.3.0")
|
||||
testBackendRenewRevoke(t, "1.3.1")
|
||||
})
|
||||
t.Run("post-1.4.0", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
@ -118,7 +118,7 @@ func testBackendRenewRevoke(t *testing.T, version string) {
|
|||
Path: "config/access",
|
||||
Data: connData,
|
||||
}
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
_, err = b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -128,14 +128,14 @@ func testBackendRenewRevoke(t *testing.T, version string) {
|
|||
"policy": base64.StdEncoding.EncodeToString([]byte(testPolicy)),
|
||||
"lease": "6h",
|
||||
}
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
_, 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)
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -186,7 +186,7 @@ func testBackendRenewRevoke(t *testing.T, version string) {
|
|||
}
|
||||
|
||||
req.Operation = logical.RevokeOperation
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
_, err = b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -223,7 +223,7 @@ func testBackendRenewRevoke14(t *testing.T, version string) {
|
|||
Path: "config/access",
|
||||
Data: connData,
|
||||
}
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
_, err = b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -233,14 +233,14 @@ func testBackendRenewRevoke14(t *testing.T, version string) {
|
|||
"policies": []string{"test"},
|
||||
"lease": "6h",
|
||||
}
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
_, 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)
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -289,7 +289,7 @@ func testBackendRenewRevoke14(t *testing.T, version string) {
|
|||
}
|
||||
|
||||
req.Operation = logical.RevokeOperation
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
_, err = b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -335,7 +335,7 @@ func TestBackend_LocalToken(t *testing.T) {
|
|||
Path: "config/access",
|
||||
Data: connData,
|
||||
}
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
_, err = b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -346,7 +346,7 @@ func TestBackend_LocalToken(t *testing.T) {
|
|||
"ttl": "6h",
|
||||
"local": false,
|
||||
}
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
_, err = b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -357,14 +357,14 @@ func TestBackend_LocalToken(t *testing.T) {
|
|||
"ttl": "6h",
|
||||
"local": true,
|
||||
}
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
_, 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)
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -447,12 +447,14 @@ func TestBackend_Management(t *testing.T) {
|
|||
t.Parallel()
|
||||
t.Run("pre-1.4.0", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testBackendManagement(t, "1.3.0")
|
||||
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")
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -487,15 +489,16 @@ func TestBackend_Basic(t *testing.T) {
|
|||
t.Parallel()
|
||||
t.Run("pre-1.4.0", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
testBackendBasic(t, "1.3.0")
|
||||
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()
|
||||
testBackendRenewRevoke(t, "1.4.4")
|
||||
testBackendBasic(t, "1.4.4")
|
||||
})
|
||||
testBackendBasic(t, "1.4.4")
|
||||
|
||||
testBackendBasic(t, "1.10.8")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -702,6 +705,119 @@ func testAccStepDeletePolicy(t *testing.T, name string) logicaltest.TestStep {
|
|||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
}
|
||||
t.Logf("Generated consul_roles token: %s with accessor %s", d.Token, d.Accessor)
|
||||
|
||||
// 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
|
||||
_, 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",
|
||||
}
|
||||
|
||||
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 TestBackend_Enterprise_Namespace(t *testing.T) {
|
||||
if _, hasLicense := os.LookupEnv("CONSUL_LICENSE"); !hasLicense {
|
||||
t.Skip("Skipping: No enterprise license found")
|
||||
|
@ -740,7 +856,7 @@ func testBackendEntNamespace(t *testing.T) {
|
|||
Path: "config/access",
|
||||
Data: connData,
|
||||
}
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
_, err = b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -752,14 +868,14 @@ func testBackendEntNamespace(t *testing.T) {
|
|||
"lease": "6h",
|
||||
"consul_namespace": "ns1",
|
||||
}
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
_, 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)
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -813,7 +929,7 @@ func testBackendEntNamespace(t *testing.T) {
|
|||
}
|
||||
|
||||
req.Operation = logical.RevokeOperation
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
_, err = b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -860,7 +976,7 @@ func testBackendEntPartition(t *testing.T) {
|
|||
Path: "config/access",
|
||||
Data: connData,
|
||||
}
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
_, err = b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -872,14 +988,14 @@ func testBackendEntPartition(t *testing.T) {
|
|||
"lease": "6h",
|
||||
"partition": "part1",
|
||||
}
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
_, 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)
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -933,7 +1049,7 @@ func testBackendEntPartition(t *testing.T) {
|
|||
}
|
||||
|
||||
req.Operation = logical.RevokeOperation
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
_, err = b.HandleRequest(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -961,5 +1077,4 @@ func testBackendEntPartition(t *testing.T) {
|
|||
const testPolicy = `
|
||||
key "" {
|
||||
policy = "write"
|
||||
}
|
||||
`
|
||||
}`
|
||||
|
|
|
@ -37,23 +37,29 @@ for 'client' tokens. Required for Consul pre-1.4.`,
|
|||
|
||||
"policies": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `List of policies to attach to the token. Required
|
||||
for Consul 1.4 or above.`,
|
||||
Description: `List of policies to attach to the token. Either "policies"
|
||||
or "consul_roles" are required for Consul 1.5 and above, or just "policies" if
|
||||
using Consul 1.4.`,
|
||||
},
|
||||
|
||||
"consul_roles": {
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: `List of Consul roles to attach to the token. Either "policies"
|
||||
or "consul_roles" are required for Consul 1.5 and above.`,
|
||||
},
|
||||
|
||||
"local": {
|
||||
Type: framework.TypeBool,
|
||||
Description: `Indicates that the token should not be replicated globally
|
||||
and instead be local to the current datacenter. Available in Consul 1.4 and above.`,
|
||||
and instead be local to the current datacenter. Available in Consul 1.4 and above.`,
|
||||
},
|
||||
|
||||
"token_type": {
|
||||
Type: framework.TypeString,
|
||||
Default: "client",
|
||||
Description: `Which type of token to create: 'client'
|
||||
or 'management'. If a 'management' token,
|
||||
the "policy" parameter is not required.
|
||||
Defaults to 'client'.`,
|
||||
Description: `Which type of token to create: 'client' or 'management'. If
|
||||
a 'management' token, the "policy", "policies", and "consul_roles" parameters are not
|
||||
required. Defaults to 'client'.`,
|
||||
},
|
||||
|
||||
"ttl": {
|
||||
|
@ -68,7 +74,7 @@ Defaults to 'client'.`,
|
|||
|
||||
"lease": {
|
||||
Type: framework.TypeDurationSecond,
|
||||
Description: "Use ttl instead.",
|
||||
Description: `Use "ttl" instead.`,
|
||||
Deprecated: true,
|
||||
},
|
||||
|
||||
|
@ -113,34 +119,36 @@ func (b *backend) pathRolesRead(ctx context.Context, req *logical.Request, d *fr
|
|||
return nil, 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"
|
||||
}
|
||||
|
||||
// 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,
|
||||
"consul_namespace": result.ConsulNamespace,
|
||||
"partition": result.Partition,
|
||||
"lease": int64(roleConfigData.TTL.Seconds()),
|
||||
"ttl": int64(roleConfigData.TTL.Seconds()),
|
||||
"max_ttl": int64(roleConfigData.MaxTTL.Seconds()),
|
||||
"token_type": roleConfigData.TokenType,
|
||||
"local": roleConfigData.Local,
|
||||
"consul_namespace": roleConfigData.ConsulNamespace,
|
||||
"partition": roleConfigData.Partition,
|
||||
},
|
||||
}
|
||||
if result.Policy != "" {
|
||||
resp.Data["policy"] = base64.StdEncoding.EncodeToString([]byte(result.Policy))
|
||||
if roleConfigData.Policy != "" {
|
||||
resp.Data["policy"] = base64.StdEncoding.EncodeToString([]byte(roleConfigData.Policy))
|
||||
}
|
||||
if len(result.Policies) > 0 {
|
||||
resp.Data["policies"] = result.Policies
|
||||
if len(roleConfigData.Policies) > 0 {
|
||||
resp.Data["policies"] = roleConfigData.Policies
|
||||
}
|
||||
if len(roleConfigData.ConsulRoles) > 0 {
|
||||
resp.Data["consul_roles"] = roleConfigData.ConsulRoles
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
|
@ -148,18 +156,18 @@ func (b *backend) pathRolesWrite(ctx context.Context, req *logical.Request, d *f
|
|||
tokenType := d.Get("token_type").(string)
|
||||
policy := d.Get("policy").(string)
|
||||
policies := d.Get("policies").([]string)
|
||||
if len(policies) == 0 {
|
||||
switch tokenType {
|
||||
case "client":
|
||||
if policy == "" {
|
||||
return logical.ErrorResponse(
|
||||
"Use either a policy document, or a list of policies, depending on your Consul version"), nil
|
||||
}
|
||||
case "management":
|
||||
default:
|
||||
roles := d.Get("consul_roles").([]string)
|
||||
|
||||
switch tokenType {
|
||||
case "client":
|
||||
if policy == "" && len(policies) == 0 && len(roles) == 0 {
|
||||
return logical.ErrorResponse(
|
||||
"token_type must be \"client\" or \"management\""), nil
|
||||
"Use either a policy document, a list of policies, or a list of roles, depending on your Consul version"), nil
|
||||
}
|
||||
case "management":
|
||||
default:
|
||||
return logical.ErrorResponse(
|
||||
"token_type must be \"client\" or \"management\""), nil
|
||||
}
|
||||
|
||||
policyRaw, err := base64.StdEncoding.DecodeString(policy)
|
||||
|
@ -192,6 +200,7 @@ func (b *backend) pathRolesWrite(ctx context.Context, req *logical.Request, d *f
|
|||
entry, err := logical.StorageEntryJSON("policy/"+name, roleConfig{
|
||||
Policy: string(policyRaw),
|
||||
Policies: policies,
|
||||
ConsulRoles: roles,
|
||||
TokenType: tokenType,
|
||||
TTL: ttl,
|
||||
MaxTTL: maxTTL,
|
||||
|
@ -221,6 +230,7 @@ func (b *backend) pathRolesDelete(ctx context.Context, req *logical.Request, d *
|
|||
type roleConfig struct {
|
||||
Policy string `json:"policy"`
|
||||
Policies []string `json:"policies"`
|
||||
ConsulRoles []string `json:"consul_roles"`
|
||||
TTL time.Duration `json:"lease"`
|
||||
MaxTTL time.Duration `json:"max_ttl"`
|
||||
TokenType string `json:"token_type"`
|
||||
|
|
|
@ -22,21 +22,6 @@ func pathToken(b *backend) *framework.Path {
|
|||
Type: framework.TypeString,
|
||||
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.",
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
|
@ -105,16 +90,23 @@ func (b *backend) pathTokenRead(ctx context.Context, req *logical.Request, d *fr
|
|||
|
||||
// Create an ACLToken for Consul 1.4 and above
|
||||
policyLinks := []*api.ACLTokenPolicyLink{}
|
||||
|
||||
for _, policyName := range roleConfigData.Policies {
|
||||
policyLinks = append(policyLinks, &api.ACLTokenPolicyLink{
|
||||
Name: policyName,
|
||||
})
|
||||
}
|
||||
|
||||
roleLinks := []*api.ACLTokenRoleLink{}
|
||||
for _, roleName := range roleConfigData.ConsulRoles {
|
||||
roleLinks = append(roleLinks, &api.ACLTokenRoleLink{
|
||||
Name: roleName,
|
||||
})
|
||||
}
|
||||
|
||||
token, _, err := c.ACL().TokenCreate(&api.ACLToken{
|
||||
Description: tokenName,
|
||||
Policies: policyLinks,
|
||||
Roles: roleLinks,
|
||||
Local: roleConfigData.Local,
|
||||
Namespace: roleConfigData.ConsulNamespace,
|
||||
Partition: roleConfigData.Partition,
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
secrets/consul: Add support for consul roles.
|
||||
```
|
2
go.mod
2
go.mod
|
@ -280,7 +280,7 @@ require (
|
|||
github.com/hashicorp/go-plugin v1.4.3 // indirect
|
||||
github.com/hashicorp/go-slug v0.7.0 // indirect
|
||||
github.com/hashicorp/go-tfe v0.20.0 // indirect
|
||||
github.com/hashicorp/go-version v1.3.0 // indirect
|
||||
github.com/hashicorp/go-version v1.4.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.4 // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -897,6 +897,8 @@ github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
|
|||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw=
|
||||
github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4=
|
||||
github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"testing"
|
||||
|
||||
consulapi "github.com/hashicorp/consul/api"
|
||||
goversion "github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/vault/helper/testhelpers/docker"
|
||||
)
|
||||
|
||||
|
@ -43,7 +44,7 @@ func PrepareTestContainer(t *testing.T, version string, isEnterprise bool) (func
|
|||
if consulVersion != "" {
|
||||
version = consulVersion
|
||||
} else {
|
||||
version = "1.11.2" // Latest Consul version, update as new releases come out
|
||||
version = "1.11.3" // Latest Consul version, update as new releases come out
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(version, "1.3") {
|
||||
|
@ -136,55 +137,78 @@ func PrepareTestContainer(t *testing.T, version string, isEnterprise bool) (func
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Create a Consul role that contains the test policy, for Consul 1.5 and newer
|
||||
currVersion, _ := goversion.NewVersion(version)
|
||||
roleVersion, _ := goversion.NewVersion("1.5")
|
||||
if currVersion.GreaterThanOrEqual(roleVersion) {
|
||||
ACLList := []*consulapi.ACLLink{{Name: "test"}}
|
||||
|
||||
role := &consulapi.ACLRole{
|
||||
Name: "role-test",
|
||||
Description: "consul roles test",
|
||||
Policies: ACLList,
|
||||
}
|
||||
|
||||
_, _, err = consul.ACL().RoleCreate(role, q)
|
||||
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",
|
||||
}
|
||||
namespaceVersion, _ := goversion.NewVersion("1.7")
|
||||
if currVersion.GreaterThanOrEqual(namespaceVersion) {
|
||||
namespace := &consulapi.Namespace{
|
||||
Name: "ns1",
|
||||
Description: "ns1 test",
|
||||
}
|
||||
|
||||
_, _, err = consul.Namespaces().Create(namespace, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, _, 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 "" {
|
||||
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
|
||||
}
|
||||
_, _, 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",
|
||||
}
|
||||
partitionVersion, _ := goversion.NewVersion("1.11")
|
||||
if currVersion.GreaterThanOrEqual(partitionVersion) {
|
||||
partition := &consulapi.Partition{
|
||||
Name: "part1",
|
||||
Description: "part1 test",
|
||||
}
|
||||
|
||||
_, _, err = consul.Partitions().Create(ctx, partition, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, _, 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 "" {
|
||||
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
|
||||
}
|
||||
_, _, err = consul.ACL().PolicyCreate(partPolicy, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -79,15 +79,20 @@ updated attributes.
|
|||
which to create this Consul credential. This is part of the request URL.
|
||||
|
||||
- `token_type` `(string: "client")` - Specifies the type of token to create when
|
||||
using this role. Valid values are `"client"` or `"management"`.
|
||||
using this role. Valid values are `"client"` or `"management"`. If a `"management"`
|
||||
token, the `policy`, `policies`, and `consul_roles` parameters are not required.
|
||||
Defaults to `"client`".
|
||||
|
||||
- `policy` `(string: <policy or policies>)` – Specifies the base64 encoded ACL policy. The
|
||||
ACL format can be found in the [Consul ACL
|
||||
documentation](https://www.consul.io/docs/internals/acl). This is
|
||||
required unless the `token_type` is `management`.
|
||||
- `policy` `(string: <policy>)` – Specifies the base64-encoded ACL policy. This is
|
||||
required unless the `token_type` is `"management"`. [Deprecated as of Consul 1.4 and
|
||||
removed as of Consul 1.11.](https://www.consul.io/api/acl/legacy)
|
||||
|
||||
- `policies` `(list: <policy or policies>)` – The list of policies to assign to the generated
|
||||
token. This is only available in Consul 1.4 and greater.
|
||||
- `policies` `(list: <policy or policies>)` – The list of policies to assign to the
|
||||
generated token. Either `policies` or `consul_roles` are required for Consul 1.5 and
|
||||
above, or just `policies` if using Consul 1.4.
|
||||
|
||||
- `consul_roles` `(list: <role or roles>)` – The list of Consul roles to assign to the
|
||||
generated token. Either `policies` or `consul_roles` are required for Consul 1.5 and above.
|
||||
|
||||
- `local` `(bool: false)` - Indicates that the token should not be replicated
|
||||
globally and instead be local to the current datacenter. Only available in Consul
|
||||
|
@ -111,11 +116,19 @@ To create management tokens:
|
|||
}
|
||||
```
|
||||
|
||||
To create a client token with a custom policy:
|
||||
To create a client token with defined policies:
|
||||
|
||||
```json
|
||||
{
|
||||
"policy": "abd2...=="
|
||||
"policies": "global-management,policy-2"
|
||||
}
|
||||
```
|
||||
|
||||
To create a client token with defined roles:
|
||||
|
||||
```json
|
||||
{
|
||||
"consul_roles": "role-a,role-b"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -135,25 +148,29 @@ $ curl \
|
|||
as a string duration with a time suffix like `"30s"` or `"1h"`. If not
|
||||
provided, the default Vault lease is used.
|
||||
|
||||
- `policies` `(string: <required>)` – Comma separated list of policies to be applied
|
||||
to the tokens.
|
||||
- `policy` `(string: <policy>)` – Specifies the base64-encoded ACL policy. The
|
||||
ACL format can be found in the [Consul ACL
|
||||
documentation](https://www.consul.io/docs/security/acl/acl-legacy). This is
|
||||
required unless the `token_type` is `"management"`.
|
||||
|
||||
### Sample payload
|
||||
|
||||
To create a client token with a custom base64-encoded policy:
|
||||
|
||||
```json
|
||||
{
|
||||
"policies": "global-management"
|
||||
"policy": "a2V5ICIi...=="
|
||||
}
|
||||
```
|
||||
|
||||
### Sample request
|
||||
|
||||
```sh
|
||||
curl \
|
||||
→ --request POST \
|
||||
→ --header "X-Vault-Token: ..."\
|
||||
→ --data @payload.json \
|
||||
→ http://127.0.0.1:8200/v1/consul/roles/example-role
|
||||
```shell-session
|
||||
$ curl \
|
||||
--request POST \
|
||||
--header "X-Vault-Token: ..." \
|
||||
--data @payload.json \
|
||||
http://127.0.0.1:8200/v1/consul/roles/example-role
|
||||
```
|
||||
|
||||
## Read Role
|
||||
|
|
|
@ -29,7 +29,7 @@ management tool.
|
|||
`acl_master_token` from your Consul configuration file or another management
|
||||
token:
|
||||
|
||||
```sh
|
||||
```shell-session
|
||||
$ curl \
|
||||
--header "X-Consul-Token: my-management-token" \
|
||||
--request PUT \
|
||||
|
@ -48,7 +48,7 @@ management tool.
|
|||
|
||||
For Consul 1.4 and above, use the command line to generate a token with the appropriate policy:
|
||||
|
||||
```sh
|
||||
```text
|
||||
$ CONSUL_HTTP_TOKEN=d54fe46a-1f57-a589-3583-6b78e334b03b consul acl token create -policy-name=global-management
|
||||
AccessorID: 865dc5e9-e585-3180-7b49-4ddc0fc45135
|
||||
SecretID: ef35f0f1-885b-0cab-573c-7c91b65a7a7e
|
||||
|
@ -70,15 +70,16 @@ management tool.
|
|||
|
||||
4. Configure a role that maps a name in Vault to a Consul ACL policy. Depending on your Consul version,
|
||||
you will either provide a policy document and a token_type, or a set of policies.
|
||||
When users generate credentials, they are generated against this role. For Consul versions below 1.4:
|
||||
When users generate credentials, they are generated against this role.
|
||||
|
||||
For Consul versions below 1.4, the policy must be base64-encoded. The policy language is [documented by Consul](https://www.consul.io/docs/security/acl/acl-legacy).
|
||||
Write a policy and proceed to link it to the role:
|
||||
|
||||
```text
|
||||
$ vault write consul/roles/my-role policy=$(base64 <<< 'key "" { policy = "read" }')
|
||||
Success! Data written to: consul/roles/my-role
|
||||
```
|
||||
|
||||
The policy must be base64-encoded. The policy language is [documented by Consul](https://www.consul.io/docs/internals/acl.html).
|
||||
|
||||
For Consul versions 1.4 and above, [generate a policy in Consul](https://www.consul.io/docs/guides/acl.html), and proceed to link it to the role:
|
||||
|
||||
```text
|
||||
|
@ -86,6 +87,13 @@ management tool.
|
|||
Success! Data written to: consul/roles/my-role
|
||||
```
|
||||
|
||||
For Consul versions 1.5 and above, [generate a role in Consul](https://www.consul.io/api/acl/roles), and proceed to link it to the role:
|
||||
|
||||
```text
|
||||
$ vault write consul/roles/my-role consul_roles=api-server
|
||||
Success! Data written to: consul/roles/my-role
|
||||
```
|
||||
|
||||
-> **Token lease duration:** If you do not specify a value for `ttl` (or `lease` for Consul versions below 1.4) the tokens created using Vault's
|
||||
Consul secrets engine are created with a Time To Live (TTL) of 30 days. You can change the lease duration by passing `-ttl=<duration>` to the
|
||||
command above with "duration" being a string with a time suffix like "30s" or "1h".
|
||||
|
|
Loading…
Reference in New Issue