Skip to content

Commit a694daa

Browse files
authored
secrets/consul: Add support to auto-bootstrap Consul ACL system (hashicorp#10751)
* Automatically bootstraps the Consul ACL system if no management token is given on the access config
1 parent 9750dca commit a694daa

File tree

10 files changed

+193
-139
lines changed

10 files changed

+193
-139
lines changed

builtin/logical/consul/backend_test.go

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,32 @@ func TestBackend_Config_Access(t *testing.T) {
2222
t.Parallel()
2323
t.Run("pre-1.4.0", func(t *testing.T) {
2424
t.Parallel()
25-
testBackendConfigAccess(t, "1.3.1")
25+
testBackendConfigAccess(t, "1.3.1", true)
2626
})
2727
t.Run("post-1.4.0", func(t *testing.T) {
2828
t.Parallel()
29-
testBackendConfigAccess(t, "")
29+
testBackendConfigAccess(t, "", true)
30+
})
31+
t.Run("pre-1.4.0 automatic-bootstrap", func(t *testing.T) {
32+
t.Parallel()
33+
testBackendConfigAccess(t, "1.3.1", false)
34+
})
35+
t.Run("post-1.4.0 automatic-bootstrap", func(t *testing.T) {
36+
t.Parallel()
37+
testBackendConfigAccess(t, "", false)
3038
})
3139
})
3240
}
3341

34-
func testBackendConfigAccess(t *testing.T, version string) {
42+
func testBackendConfigAccess(t *testing.T, version string, bootstrap bool) {
3543
config := logical.TestBackendConfig()
3644
config.StorageView = &logical.InmemStorage{}
3745
b, err := Factory(context.Background(), config)
3846
if err != nil {
3947
t.Fatal(err)
4048
}
4149

42-
cleanup, consulConfig := consul.PrepareTestContainer(t, version, false)
50+
cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, bootstrap)
4351
defer cleanup()
4452

4553
connData := map[string]interface{}{
@@ -104,7 +112,7 @@ func testBackendRenewRevoke(t *testing.T, version string) {
104112
t.Fatal(err)
105113
}
106114

107-
cleanup, consulConfig := consul.PrepareTestContainer(t, version, false)
115+
cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, true)
108116
defer cleanup()
109117

110118
connData := map[string]interface{}{
@@ -209,7 +217,7 @@ func testBackendRenewRevoke14(t *testing.T, version string) {
209217
t.Fatal(err)
210218
}
211219

212-
cleanup, consulConfig := consul.PrepareTestContainer(t, version, false)
220+
cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, true)
213221
defer cleanup()
214222

215223
connData := map[string]interface{}{
@@ -321,7 +329,7 @@ func TestBackend_LocalToken(t *testing.T) {
321329
t.Fatal(err)
322330
}
323331

324-
cleanup, consulConfig := consul.PrepareTestContainer(t, "", false)
332+
cleanup, consulConfig := consul.PrepareTestContainer(t, "", false, true)
325333
defer cleanup()
326334

327335
connData := map[string]interface{}{
@@ -466,7 +474,7 @@ func testBackendManagement(t *testing.T, version string) {
466474
t.Fatal(err)
467475
}
468476

469-
cleanup, consulConfig := consul.PrepareTestContainer(t, version, false)
477+
cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, true)
470478
defer cleanup()
471479

472480
connData := map[string]interface{}{
@@ -511,7 +519,7 @@ func testBackendBasic(t *testing.T, version string) {
511519
t.Fatal(err)
512520
}
513521

514-
cleanup, consulConfig := consul.PrepareTestContainer(t, version, false)
522+
cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, true)
515523
defer cleanup()
516524

517525
connData := map[string]interface{}{
@@ -713,7 +721,7 @@ func TestBackend_Roles(t *testing.T) {
713721
t.Fatal(err)
714722
}
715723

716-
cleanup, consulConfig := consul.PrepareTestContainer(t, "", false)
724+
cleanup, consulConfig := consul.PrepareTestContainer(t, "", false, true)
717725
defer cleanup()
718726

719727
connData := map[string]interface{}{
@@ -842,7 +850,7 @@ func testBackendEntNamespace(t *testing.T) {
842850
t.Fatal(err)
843851
}
844852

845-
cleanup, consulConfig := consul.PrepareTestContainer(t, "", true)
853+
cleanup, consulConfig := consul.PrepareTestContainer(t, "", true, true)
846854
defer cleanup()
847855

848856
connData := map[string]interface{}{
@@ -962,7 +970,7 @@ func testBackendEntPartition(t *testing.T) {
962970
t.Fatal(err)
963971
}
964972

965-
cleanup, consulConfig := consul.PrepareTestContainer(t, "", true)
973+
cleanup, consulConfig := consul.PrepareTestContainer(t, "", true, true)
966974
defer cleanup()
967975

968976
connData := map[string]interface{}{

builtin/logical/consul/client.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,7 @@ func (b *backend) client(ctx context.Context, s logical.Storage) (*api.Client, e
2020
return nil, nil, fmt.Errorf("no error received but no configuration found")
2121
}
2222

23-
consulConf := api.DefaultNonPooledConfig()
24-
consulConf.Address = conf.Address
25-
consulConf.Scheme = conf.Scheme
26-
consulConf.Token = conf.Token
27-
consulConf.TLSConfig.CAPem = []byte(conf.CACert)
28-
consulConf.TLSConfig.CertPEM = []byte(conf.ClientCert)
29-
consulConf.TLSConfig.KeyPEM = []byte(conf.ClientKey)
30-
23+
consulConf := conf.NewConfig()
3124
client, err := api.NewClient(consulConf)
3225
return client, nil, err
3326
}

builtin/logical/consul/path_config.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66

7+
"github.com/hashicorp/consul/api"
78
"github.com/hashicorp/vault/sdk/framework"
89
"github.com/hashicorp/vault/sdk/logical"
910
)
@@ -96,14 +97,31 @@ func (b *backend) pathConfigAccessRead(ctx context.Context, req *logical.Request
9697
}
9798

9899
func (b *backend) pathConfigAccessWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
99-
entry, err := logical.StorageEntryJSON("config/access", accessConfig{
100+
config := accessConfig{
100101
Address: data.Get("address").(string),
101102
Scheme: data.Get("scheme").(string),
102103
Token: data.Get("token").(string),
103104
CACert: data.Get("ca_cert").(string),
104105
ClientCert: data.Get("client_cert").(string),
105106
ClientKey: data.Get("client_key").(string),
106-
})
107+
}
108+
109+
// If a token has not been given by the user, we try to boostrap the ACL
110+
// support
111+
if config.Token == "" {
112+
consulConf := config.NewConfig()
113+
client, err := api.NewClient(consulConf)
114+
if err != nil {
115+
return nil, err
116+
}
117+
token, _, err := client.ACL().Bootstrap()
118+
if err != nil {
119+
return logical.ErrorResponse("Token not provided and failed to bootstrap ACLs"), err
120+
}
121+
config.Token = token.SecretID
122+
}
123+
124+
entry, err := logical.StorageEntryJSON("config/access", config)
107125
if err != nil {
108126
return nil, err
109127
}
@@ -123,3 +141,15 @@ type accessConfig struct {
123141
ClientCert string `json:"client_cert"`
124142
ClientKey string `json:"client_key"`
125143
}
144+
145+
func (conf *accessConfig) NewConfig() *api.Config {
146+
consulConf := api.DefaultNonPooledConfig()
147+
consulConf.Address = conf.Address
148+
consulConf.Scheme = conf.Scheme
149+
consulConf.Token = conf.Token
150+
consulConf.TLSConfig.CAPem = []byte(conf.CACert)
151+
consulConf.TLSConfig.CertPEM = []byte(conf.ClientCert)
152+
consulConf.TLSConfig.KeyPEM = []byte(conf.ClientKey)
153+
154+
return consulConf
155+
}

changelog/10751.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:improvement
2+
secrets/consul: Vault is now able to automatically bootstrap the Consul ACL system.
3+
```

helper/testhelpers/consul/consulhelper.go

Lines changed: 85 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func (c *Config) APIConfig() *consulapi.Config {
2727
// the Consul version used will be given by the environment variable
2828
// CONSUL_DOCKER_VERSION, or if that's empty, whatever we've hardcoded as the
2929
// the latest Consul version.
30-
func PrepareTestContainer(t *testing.T, version string, isEnterprise bool) (func(), *Config) {
30+
func PrepareTestContainer(t *testing.T, version string, isEnterprise bool, bootstrap bool) (func(), *Config) {
3131
t.Helper()
3232

3333
if retAddress := os.Getenv("CONSUL_HTTP_ADDR"); retAddress != "" {
@@ -94,6 +94,11 @@ func PrepareTestContainer(t *testing.T, version string, isEnterprise bool) (func
9494
return nil, err
9595
}
9696

97+
// Make sure Consul is up
98+
if _, err = consul.Status().Leader(); err != nil {
99+
return nil, err
100+
}
101+
97102
// For version of Consul < 1.4
98103
if strings.HasPrefix(version, "1.3") {
99104
consulToken := "test"
@@ -113,101 +118,104 @@ func PrepareTestContainer(t *testing.T, version string, isEnterprise bool) (func
113118
}
114119

115120
// New default behavior
116-
aclbootstrap, _, err := consul.ACL().Bootstrap()
117-
if err != nil {
118-
return nil, err
119-
}
120-
consulToken := aclbootstrap.SecretID
121-
policy := &consulapi.ACLPolicy{
122-
Name: "test",
123-
Description: "test",
124-
Rules: `node_prefix "" {
125-
policy = "write"
121+
var consulToken string
122+
if bootstrap {
123+
aclbootstrap, _, err := consul.ACL().Bootstrap()
124+
if err != nil {
125+
return nil, err
126+
}
127+
consulToken = aclbootstrap.SecretID
128+
policy := &consulapi.ACLPolicy{
129+
Name: "test",
130+
Description: "test",
131+
Rules: `node_prefix "" {
132+
policy = "write"
126133
}
127134
128-
service_prefix "" {
129-
policy = "read"
130-
}`,
131-
}
132-
q := &consulapi.WriteOptions{
133-
Token: consulToken,
134-
}
135-
_, _, err = consul.ACL().PolicyCreate(policy, q)
136-
if err != nil {
137-
return nil, err
138-
}
139-
140-
// Create a Consul role that contains the test policy, for Consul 1.5 and newer
141-
currVersion, _ := goversion.NewVersion(version)
142-
roleVersion, _ := goversion.NewVersion("1.5")
143-
if currVersion.GreaterThanOrEqual(roleVersion) {
144-
ACLList := []*consulapi.ACLLink{{Name: "test"}}
145-
146-
role := &consulapi.ACLRole{
147-
Name: "role-test",
148-
Description: "consul roles test",
149-
Policies: ACLList,
135+
service_prefix "" {
136+
policy = "read"
137+
}`,
150138
}
151-
152-
_, _, err = consul.ACL().RoleCreate(role, q)
139+
q := &consulapi.WriteOptions{
140+
Token: consulToken,
141+
}
142+
_, _, err = consul.ACL().PolicyCreate(policy, q)
153143
if err != nil {
154144
return nil, err
155145
}
156-
}
157146

158-
// Configure a namespace and parition if testing enterprise Consul
159-
if isEnterprise {
160-
// Namespaces require Consul 1.7 or newer
161-
namespaceVersion, _ := goversion.NewVersion("1.7")
162-
if currVersion.GreaterThanOrEqual(namespaceVersion) {
163-
namespace := &consulapi.Namespace{
164-
Name: "ns1",
165-
Description: "ns1 test",
166-
}
147+
// Create a Consul role that contains the test policy, for Consul 1.5 and newer
148+
currVersion, _ := goversion.NewVersion(version)
149+
roleVersion, _ := goversion.NewVersion("1.5")
150+
if currVersion.GreaterThanOrEqual(roleVersion) {
151+
ACLList := []*consulapi.ACLLink{{Name: "test"}}
167152

168-
_, _, err = consul.Namespaces().Create(namespace, q)
169-
if err != nil {
170-
return nil, err
153+
role := &consulapi.ACLRole{
154+
Name: "role-test",
155+
Description: "consul roles test",
156+
Policies: ACLList,
171157
}
172158

173-
nsPolicy := &consulapi.ACLPolicy{
174-
Name: "ns-test",
175-
Description: "namespace test",
176-
Namespace: "ns1",
177-
Rules: `service_prefix "" {
178-
policy = "read"
179-
}`,
180-
}
181-
_, _, err = consul.ACL().PolicyCreate(nsPolicy, q)
159+
_, _, err = consul.ACL().RoleCreate(role, q)
182160
if err != nil {
183161
return nil, err
184162
}
185163
}
186164

187-
// Partitions require Consul 1.11 or newer
188-
partitionVersion, _ := goversion.NewVersion("1.11")
189-
if currVersion.GreaterThanOrEqual(partitionVersion) {
190-
partition := &consulapi.Partition{
191-
Name: "part1",
192-
Description: "part1 test",
193-
}
194-
195-
_, _, err = consul.Partitions().Create(ctx, partition, q)
196-
if err != nil {
197-
return nil, err
165+
// Configure a namespace and parition if testing enterprise Consul
166+
if isEnterprise {
167+
// Namespaces require Consul 1.7 or newer
168+
namespaceVersion, _ := goversion.NewVersion("1.7")
169+
if currVersion.GreaterThanOrEqual(namespaceVersion) {
170+
namespace := &consulapi.Namespace{
171+
Name: "ns1",
172+
Description: "ns1 test",
173+
}
174+
175+
_, _, err = consul.Namespaces().Create(namespace, q)
176+
if err != nil {
177+
return nil, err
178+
}
179+
180+
nsPolicy := &consulapi.ACLPolicy{
181+
Name: "ns-test",
182+
Description: "namespace test",
183+
Namespace: "ns1",
184+
Rules: `service_prefix "" {
185+
policy = "read"
186+
}`,
187+
}
188+
_, _, err = consul.ACL().PolicyCreate(nsPolicy, q)
189+
if err != nil {
190+
return nil, err
191+
}
198192
}
199193

200-
partPolicy := &consulapi.ACLPolicy{
201-
Name: "part-test",
202-
Description: "partition test",
203-
Partition: "part1",
204-
Rules: `service_prefix "" {
194+
// Partitions require Consul 1.11 or newer
195+
partitionVersion, _ := goversion.NewVersion("1.11")
196+
if currVersion.GreaterThanOrEqual(partitionVersion) {
197+
partition := &consulapi.Partition{
198+
Name: "part1",
199+
Description: "part1 test",
200+
}
201+
202+
_, _, err = consul.Partitions().Create(ctx, partition, q)
203+
if err != nil {
204+
return nil, err
205+
}
206+
207+
partPolicy := &consulapi.ACLPolicy{
208+
Name: "part-test",
209+
Description: "partition test",
210+
Partition: "part1",
211+
Rules: `service_prefix "" {
205212
policy = "read"
206213
}`,
207-
}
208-
_, _, err = consul.ACL().PolicyCreate(partPolicy, q)
209-
if err != nil {
210-
return nil, err
214+
}
215+
_, _, err = consul.ACL().PolicyCreate(partPolicy, q)
216+
if err != nil {
217+
return nil, err
218+
}
211219
}
212220
}
213221
}

helper/testhelpers/teststorage/consul/consul.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
)
1313

1414
func MakeConsulBackend(t testing.T, logger hclog.Logger) *vault.PhysicalBackendBundle {
15-
cleanup, config := consul.PrepareTestContainer(t.(*realtesting.T), "", false)
15+
cleanup, config := consul.PrepareTestContainer(t.(*realtesting.T), "", false, true)
1616

1717
consulConf := map[string]string{
1818
"address": config.Address(),

0 commit comments

Comments
 (0)