Skip to content

Commit

Permalink
secrets/consul: Add support to auto-bootstrap Consul ACL system (#10751)
Browse files Browse the repository at this point in the history
* Automatically bootstraps the Consul ACL system if no management token is given on the access config
  • Loading branch information
remilapeyre authored and Matt Schultz committed Apr 27, 2022
1 parent 54218fb commit 372a8ae
Show file tree
Hide file tree
Showing 10 changed files with 193 additions and 139 deletions.
32 changes: 20 additions & 12 deletions builtin/logical/consul/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,32 @@ 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.1")
testBackendConfigAccess(t, "1.3.1", true)
})
t.Run("post-1.4.0", func(t *testing.T) {
t.Parallel()
testBackendConfigAccess(t, "")
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) {
func testBackendConfigAccess(t *testing.T, version string, bootstrap 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)
cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, bootstrap)
defer cleanup()

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

cleanup, consulConfig := consul.PrepareTestContainer(t, version, false)
cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, true)
defer cleanup()

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

cleanup, consulConfig := consul.PrepareTestContainer(t, version, false)
cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, true)
defer cleanup()

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

cleanup, consulConfig := consul.PrepareTestContainer(t, "", false)
cleanup, consulConfig := consul.PrepareTestContainer(t, "", false, true)
defer cleanup()

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

cleanup, consulConfig := consul.PrepareTestContainer(t, version, false)
cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, true)
defer cleanup()

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

cleanup, consulConfig := consul.PrepareTestContainer(t, version, false)
cleanup, consulConfig := consul.PrepareTestContainer(t, version, false, true)
defer cleanup()

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

cleanup, consulConfig := consul.PrepareTestContainer(t, "", false)
cleanup, consulConfig := consul.PrepareTestContainer(t, "", false, true)
defer cleanup()

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

cleanup, consulConfig := consul.PrepareTestContainer(t, "", true)
cleanup, consulConfig := consul.PrepareTestContainer(t, "", true, true)
defer cleanup()

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

cleanup, consulConfig := consul.PrepareTestContainer(t, "", true)
cleanup, consulConfig := consul.PrepareTestContainer(t, "", true, true)
defer cleanup()

connData := map[string]interface{}{
Expand Down
9 changes: 1 addition & 8 deletions builtin/logical/consul/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,7 @@ func (b *backend) client(ctx context.Context, s logical.Storage) (*api.Client, e
return nil, nil, fmt.Errorf("no error received but no configuration found")
}

consulConf := api.DefaultNonPooledConfig()
consulConf.Address = conf.Address
consulConf.Scheme = conf.Scheme
consulConf.Token = conf.Token
consulConf.TLSConfig.CAPem = []byte(conf.CACert)
consulConf.TLSConfig.CertPEM = []byte(conf.ClientCert)
consulConf.TLSConfig.KeyPEM = []byte(conf.ClientKey)

consulConf := conf.NewConfig()
client, err := api.NewClient(consulConf)
return client, nil, err
}
34 changes: 32 additions & 2 deletions builtin/logical/consul/path_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"

"github.com/hashicorp/consul/api"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
Expand Down Expand Up @@ -96,14 +97,31 @@ func (b *backend) pathConfigAccessRead(ctx context.Context, req *logical.Request
}

func (b *backend) pathConfigAccessWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
entry, err := logical.StorageEntryJSON("config/access", accessConfig{
config := accessConfig{
Address: data.Get("address").(string),
Scheme: data.Get("scheme").(string),
Token: data.Get("token").(string),
CACert: data.Get("ca_cert").(string),
ClientCert: data.Get("client_cert").(string),
ClientKey: data.Get("client_key").(string),
})
}

// If a token has not been given by the user, we try to boostrap the ACL
// support
if config.Token == "" {
consulConf := config.NewConfig()
client, err := api.NewClient(consulConf)
if err != nil {
return nil, err
}
token, _, err := client.ACL().Bootstrap()
if err != nil {
return logical.ErrorResponse("Token not provided and failed to bootstrap ACLs"), err
}
config.Token = token.SecretID
}

entry, err := logical.StorageEntryJSON("config/access", config)
if err != nil {
return nil, err
}
Expand All @@ -123,3 +141,15 @@ type accessConfig struct {
ClientCert string `json:"client_cert"`
ClientKey string `json:"client_key"`
}

func (conf *accessConfig) NewConfig() *api.Config {
consulConf := api.DefaultNonPooledConfig()
consulConf.Address = conf.Address
consulConf.Scheme = conf.Scheme
consulConf.Token = conf.Token
consulConf.TLSConfig.CAPem = []byte(conf.CACert)
consulConf.TLSConfig.CertPEM = []byte(conf.ClientCert)
consulConf.TLSConfig.KeyPEM = []byte(conf.ClientKey)

return consulConf
}
3 changes: 3 additions & 0 deletions changelog/10751.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
secrets/consul: Vault is now able to automatically bootstrap the Consul ACL system.
```
162 changes: 85 additions & 77 deletions helper/testhelpers/consul/consulhelper.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ 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, isEnterprise bool) (func(), *Config) {
func PrepareTestContainer(t *testing.T, version string, isEnterprise bool, bootstrap bool) (func(), *Config) {
t.Helper()

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

// Make sure Consul is up
if _, err = consul.Status().Leader(); err != nil {
return nil, err
}

// For version of Consul < 1.4
if strings.HasPrefix(version, "1.3") {
consulToken := "test"
Expand All @@ -113,101 +118,104 @@ func PrepareTestContainer(t *testing.T, version string, isEnterprise bool) (func
}

// New default behavior
aclbootstrap, _, err := consul.ACL().Bootstrap()
if err != nil {
return nil, err
}
consulToken := aclbootstrap.SecretID
policy := &consulapi.ACLPolicy{
Name: "test",
Description: "test",
Rules: `node_prefix "" {
policy = "write"
var consulToken string
if bootstrap {
aclbootstrap, _, err := consul.ACL().Bootstrap()
if err != nil {
return nil, err
}
consulToken = aclbootstrap.SecretID
policy := &consulapi.ACLPolicy{
Name: "test",
Description: "test",
Rules: `node_prefix "" {
policy = "write"
}
service_prefix "" {
policy = "read"
}`,
}
q := &consulapi.WriteOptions{
Token: consulToken,
}
_, _, err = consul.ACL().PolicyCreate(policy, q)
if err != nil {
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,
service_prefix "" {
policy = "read"
}`,
}

_, _, err = consul.ACL().RoleCreate(role, q)
q := &consulapi.WriteOptions{
Token: consulToken,
}
_, _, err = consul.ACL().PolicyCreate(policy, 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
namespaceVersion, _ := goversion.NewVersion("1.7")
if currVersion.GreaterThanOrEqual(namespaceVersion) {
namespace := &consulapi.Namespace{
Name: "ns1",
Description: "ns1 test",
}
// 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"}}

_, _, err = consul.Namespaces().Create(namespace, q)
if err != nil {
return nil, err
role := &consulapi.ACLRole{
Name: "role-test",
Description: "consul roles test",
Policies: ACLList,
}

nsPolicy := &consulapi.ACLPolicy{
Name: "ns-test",
Description: "namespace test",
Namespace: "ns1",
Rules: `service_prefix "" {
policy = "read"
}`,
}
_, _, err = consul.ACL().PolicyCreate(nsPolicy, q)
_, _, err = consul.ACL().RoleCreate(role, q)
if err != nil {
return nil, err
}
}

// Partitions require Consul 1.11 or newer
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
// Configure a namespace and parition if testing enterprise Consul
if isEnterprise {
// Namespaces require Consul 1.7 or newer
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
}

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
}
}

partPolicy := &consulapi.ACLPolicy{
Name: "part-test",
Description: "partition test",
Partition: "part1",
Rules: `service_prefix "" {
// Partitions require Consul 1.11 or newer
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
}

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
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion helper/testhelpers/teststorage/consul/consul.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

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

consulConf := map[string]string{
"address": config.Address(),
Expand Down
Loading

0 comments on commit 372a8ae

Please sign in to comment.