1
0

remove consul

serviceregistration/consul is preserved
This commit is contained in:
Konstantin Demin 2024-07-01 15:56:57 +03:00
parent 747df1e9e8
commit 2acf0a21fd
21 changed files with 3 additions and 4048 deletions

View File

@ -1,54 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package consul
import (
"context"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
const operationPrefixConsul = "consul"
// ReportedVersion is used to report a specific version to Vault.
var ReportedVersion = ""
func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
b := Backend()
if err := b.Setup(ctx, conf); err != nil {
return nil, err
}
return b, nil
}
func Backend() *backend {
var b backend
b.Backend = &framework.Backend{
PathsSpecial: &logical.Paths{
SealWrapStorage: []string{
"config/access",
},
},
Paths: []*framework.Path{
pathConfigAccess(&b),
pathListRoles(&b),
pathRoles(&b),
pathToken(&b),
},
Secrets: []*framework.Secret{
secretToken(&b),
},
BackendType: logical.TypeLogical,
RunningVersion: ReportedVersion,
}
return &b
}
type backend struct {
*framework.Backend
}

File diff suppressed because it is too large Load Diff

View File

@ -1,29 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package consul
import (
"context"
"fmt"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/vault/sdk/logical"
)
func (b *backend) client(ctx context.Context, s logical.Storage) (*api.Client, error, error) {
conf, userErr, intErr := b.readConfigAccess(ctx, s)
if intErr != nil {
return nil, nil, intErr
}
if userErr != nil {
return nil, userErr, nil
}
if conf == nil {
return nil, nil, fmt.Errorf("no error received but no configuration found")
}
consulConf := conf.NewConfig()
client, err := api.NewClient(consulConf)
return client, nil, err
}

View File

@ -1,34 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package main
import (
"os"
hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/builtin/logical/consul"
"github.com/hashicorp/vault/sdk/plugin"
)
func main() {
apiClientMeta := &api.PluginAPIClientMeta{}
flags := apiClientMeta.FlagSet()
flags.Parse(os.Args[1:])
tlsConfig := apiClientMeta.GetTLSConfig()
tlsProviderFunc := api.VaultPluginTLSProvider(tlsConfig)
if err := plugin.ServeMultiplex(&plugin.ServeOpts{
BackendFactoryFunc: consul.Factory,
// set the TLSProviderFunc so that the plugin maintains backwards
// compatibility with Vault versions that dont support plugin AutoMTLS
TLSProviderFunc: tlsProviderFunc,
}); err != nil {
logger := hclog.New(&hclog.LoggerOptions{})
logger.Error("plugin shutting down", "error", err)
os.Exit(1)
}
}

View File

@ -1,174 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package consul
import (
"context"
"fmt"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
func pathConfigAccess(b *backend) *framework.Path {
return &framework.Path{
Pattern: "config/access",
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixConsul,
},
Fields: map[string]*framework.FieldSchema{
"address": {
Type: framework.TypeString,
Description: "Consul server address",
},
"scheme": {
Type: framework.TypeString,
Description: "URI scheme for the Consul address",
// https would be a better default but Consul on its own
// defaults to HTTP access, and when HTTPS is enabled it
// disables HTTP, so there isn't really any harm done here.
Default: "http",
},
"token": {
Type: framework.TypeString,
Description: "Token for API calls",
},
"ca_cert": {
Type: framework.TypeString,
Description: `CA certificate to use when verifying Consul server certificate,
must be x509 PEM encoded.`,
},
"client_cert": {
Type: framework.TypeString,
Description: `Client certificate used for Consul's TLS communication,
must be x509 PEM encoded and if this is set you need to also set client_key.`,
},
"client_key": {
Type: framework.TypeString,
Description: `Client key used for Consul's TLS communication,
must be x509 PEM encoded and if this is set you need to also set client_cert.`,
},
},
Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{
Callback: b.pathConfigAccessRead,
DisplayAttrs: &framework.DisplayAttributes{
OperationSuffix: "access-configuration",
},
},
logical.UpdateOperation: &framework.PathOperation{
Callback: b.pathConfigAccessWrite,
DisplayAttrs: &framework.DisplayAttributes{
OperationVerb: "configure",
OperationSuffix: "access",
},
},
},
}
}
func (b *backend) readConfigAccess(ctx context.Context, storage logical.Storage) (*accessConfig, error, error) {
entry, err := storage.Get(ctx, "config/access")
if err != nil {
return nil, nil, err
}
if entry == nil {
return nil, fmt.Errorf("access credentials for the backend itself haven't been configured; please configure them at the '/config/access' endpoint"), nil
}
conf := &accessConfig{}
if err := entry.DecodeJSON(conf); err != nil {
return nil, nil, fmt.Errorf("error reading consul access configuration: %w", err)
}
return conf, nil, nil
}
func (b *backend) pathConfigAccessRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
conf, userErr, intErr := b.readConfigAccess(ctx, req.Storage)
if intErr != nil {
return nil, intErr
}
if userErr != nil {
return logical.ErrorResponse(userErr.Error()), nil
}
if conf == nil {
return nil, fmt.Errorf("no user error reported but consul access configuration not found")
}
return &logical.Response{
Data: map[string]interface{}{
"address": conf.Address,
"scheme": conf.Scheme,
},
}, nil
}
func (b *backend) pathConfigAccessWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
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: %s", err), nil
}
config.Token = token.SecretID
}
entry, err := logical.StorageEntryJSON("config/access", config)
if err != nil {
return nil, err
}
if err := req.Storage.Put(ctx, entry); err != nil {
return nil, err
}
return nil, nil
}
type accessConfig struct {
Address string `json:"address"`
Scheme string `json:"scheme"`
Token string `json:"token"`
CACert string `json:"ca_cert"`
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
}

View File

@ -1,294 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package consul
import (
"context"
"encoding/base64"
"fmt"
"time"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
func pathListRoles(b *backend) *framework.Path {
return &framework.Path{
Pattern: "roles/?$",
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixConsul,
OperationSuffix: "roles",
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ListOperation: b.pathRoleList,
},
}
}
func pathRoles(b *backend) *framework.Path {
return &framework.Path{
Pattern: "roles/" + framework.GenericNameRegex("name"),
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixConsul,
OperationSuffix: "role",
},
Fields: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
Description: "Name of the role.",
},
// The "policy" and "token_type" parameters were deprecated in Consul back in version 1.4.
// They have been removed from Consul as of version 1.11. Consider removing them here in the future.
"policy": {
Type: framework.TypeString,
Description: `Policy document, base64 encoded. Required
for 'client' tokens. Required for Consul pre-1.4.`,
Deprecated: true,
},
"token_type": {
Type: framework.TypeString,
Default: "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'.`,
Deprecated: true,
},
"policies": {
Type: framework.TypeCommaStringSlice,
Description: `Use "consul_policies" instead.`,
Deprecated: true,
},
"consul_policies": {
Type: framework.TypeCommaStringSlice,
Description: `List of policies to attach to the token. Either "consul_policies"
or "consul_roles" are required for Consul 1.5 and above, or just "consul_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.`,
},
"ttl": {
Type: framework.TypeDurationSecond,
Description: "TTL for the Consul token created from the role.",
},
"max_ttl": {
Type: framework.TypeDurationSecond,
Description: "Max TTL for the Consul token created from the role.",
},
"lease": {
Type: framework.TypeDurationSecond,
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.`,
},
"service_identities": {
Type: framework.TypeStringSlice,
Description: `List of Service Identities to attach to the
token, separated by semicolons. Available in Consul 1.5 or above.`,
},
"node_identities": {
Type: framework.TypeStringSlice,
Description: `List of Node Identities to attach to the
token. Available in Consul 1.8.1 or above.`,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathRolesRead,
logical.UpdateOperation: b.pathRolesWrite,
logical.DeleteOperation: b.pathRolesDelete,
},
}
}
func (b *backend) pathRoleList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
entries, err := req.Storage.List(ctx, "policy/")
if err != nil {
return nil, err
}
return logical.ListResponse(entries), nil
}
func (b *backend) pathRolesRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
name := d.Get("name").(string)
entry, err := req.Storage.Get(ctx, "policy/"+name)
if err != nil {
return nil, err
}
if entry == nil {
return nil, nil
}
var roleConfigData roleConfig
if err := entry.DecodeJSON(&roleConfigData); err != nil {
return nil, err
}
if roleConfigData.TokenType == "" {
roleConfigData.TokenType = "client"
}
// Generate the response
resp := &logical.Response{
Data: map[string]interface{}{
"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 roleConfigData.Policy != "" {
resp.Data["policy"] = base64.StdEncoding.EncodeToString([]byte(roleConfigData.Policy))
}
if len(roleConfigData.Policies) > 0 {
resp.Data["consul_policies"] = roleConfigData.Policies
}
if len(roleConfigData.ConsulRoles) > 0 {
resp.Data["consul_roles"] = roleConfigData.ConsulRoles
}
if len(roleConfigData.ServiceIdentities) > 0 {
resp.Data["service_identities"] = roleConfigData.ServiceIdentities
}
if len(roleConfigData.NodeIdentities) > 0 {
resp.Data["node_identities"] = roleConfigData.NodeIdentities
}
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)
consulPolicies := d.Get("consul_policies").([]string)
policies := d.Get("policies").([]string)
roles := d.Get("consul_roles").([]string)
serviceIdentities := d.Get("service_identities").([]string)
nodeIdentities := d.Get("node_identities").([]string)
switch tokenType {
case "client":
if policy == "" && len(policies) == 0 && len(consulPolicies) == 0 &&
len(roles) == 0 && len(serviceIdentities) == 0 && len(nodeIdentities) == 0 {
return logical.ErrorResponse(
"Use either a policy document, a list of policies or roles, or a set of service or node identities, depending on your Consul version"), nil
}
case "management":
default:
return logical.ErrorResponse("token_type must be \"client\" or \"management\""), nil
}
if len(consulPolicies) == 0 {
consulPolicies = policies
}
policyRaw, err := base64.StdEncoding.DecodeString(policy)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf(
"Error decoding policy base64: %s", err)), nil
}
var ttl time.Duration
ttlRaw, ok := d.GetOk("ttl")
if ok {
ttl = time.Second * time.Duration(ttlRaw.(int))
} else {
leaseParamRaw, ok := d.GetOk("lease")
if ok {
ttl = time.Second * time.Duration(leaseParamRaw.(int))
}
}
var maxTTL time.Duration
maxTTLRaw, ok := d.GetOk("max_ttl")
if ok {
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: consulPolicies,
ConsulRoles: roles,
ServiceIdentities: serviceIdentities,
NodeIdentities: nodeIdentities,
TokenType: tokenType,
TTL: ttl,
MaxTTL: maxTTL,
Local: local,
ConsulNamespace: namespace,
Partition: partition,
})
if err != nil {
return nil, err
}
if err := req.Storage.Put(ctx, entry); err != nil {
return nil, err
}
return nil, nil
}
func (b *backend) pathRolesDelete(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
name := d.Get("name").(string)
if err := req.Storage.Delete(ctx, "policy/"+name); err != nil {
return nil, err
}
return nil, nil
}
type roleConfig struct {
Policy string `json:"policy"`
Policies []string `json:"policies"`
ConsulRoles []string `json:"consul_roles"`
ServiceIdentities []string `json:"service_identities"`
NodeIdentities []string `json:"node_identities"`
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"`
}

View File

@ -1,182 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package consul
import (
"context"
"fmt"
"strings"
"time"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
const (
tokenPolicyType = "token"
)
func pathToken(b *backend) *framework.Path {
return &framework.Path{
Pattern: "creds/" + framework.GenericNameRegex("role"),
DisplayAttrs: &framework.DisplayAttributes{
OperationPrefix: operationPrefixConsul,
OperationVerb: "generate",
OperationSuffix: "credentials",
},
Fields: map[string]*framework.FieldSchema{
"role": {
Type: framework.TypeString,
Description: "Name of the role.",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathTokenRead,
},
}
}
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)
}
if entry == nil {
return logical.ErrorResponse(fmt.Sprintf("role %q not found", role)), nil
}
var roleConfigData roleConfig
if err := entry.DecodeJSON(&roleConfigData); err != nil {
return nil, err
}
if roleConfigData.TokenType == "" {
roleConfigData.TokenType = "client"
}
// Get the consul client
c, userErr, intErr := b.client(ctx, req.Storage)
if intErr != nil {
return nil, intErr
}
if userErr != nil {
return logical.ErrorResponse(userErr.Error()), nil
}
// Generate a name for the token
tokenName := fmt.Sprintf("Vault %s %s %d", role, req.DisplayName, time.Now().UnixNano())
writeOpts := &api.WriteOptions{}
writeOpts = writeOpts.WithContext(ctx)
// Create an ACLEntry for Consul pre 1.4
if (roleConfigData.Policy != "" && roleConfigData.TokenType == "client") ||
(roleConfigData.Policy == "" && roleConfigData.TokenType == "management") {
token, _, err := c.ACL().Create(&api.ACLEntry{
Name: tokenName,
Type: roleConfigData.TokenType,
Rules: roleConfigData.Policy,
}, writeOpts)
if err != nil {
return logical.ErrorResponse(err.Error()), nil
}
// Use the helper to create the secret
s := b.Secret(SecretTokenType).Response(map[string]interface{}{
"token": token,
}, map[string]interface{}{
"token": token,
"role": role,
})
s.Secret.TTL = roleConfigData.TTL
s.Secret.MaxTTL = roleConfigData.MaxTTL
return s, nil
}
// 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,
})
}
aclServiceIdentities := parseServiceIdentities(roleConfigData.ServiceIdentities)
aclNodeIdentities := parseNodeIdentities(roleConfigData.NodeIdentities)
token, _, err := c.ACL().TokenCreate(&api.ACLToken{
Description: tokenName,
Policies: policyLinks,
Roles: roleLinks,
ServiceIdentities: aclServiceIdentities,
NodeIdentities: aclNodeIdentities,
Local: roleConfigData.Local,
Namespace: roleConfigData.ConsulNamespace,
Partition: roleConfigData.Partition,
}, writeOpts)
if err != nil {
return logical.ErrorResponse(err.Error()), nil
}
// Use the helper to create the secret
s := b.Secret(SecretTokenType).Response(map[string]interface{}{
"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 = roleConfigData.TTL
s.Secret.MaxTTL = roleConfigData.MaxTTL
return s, nil
}
func parseServiceIdentities(data []string) []*api.ACLServiceIdentity {
aclServiceIdentities := []*api.ACLServiceIdentity{}
for _, serviceIdentity := range data {
entry := &api.ACLServiceIdentity{}
components := strings.Split(serviceIdentity, ":")
entry.ServiceName = components[0]
if len(components) == 2 {
entry.Datacenters = strings.Split(components[1], ",")
}
aclServiceIdentities = append(aclServiceIdentities, entry)
}
return aclServiceIdentities
}
func parseNodeIdentities(data []string) []*api.ACLNodeIdentity {
aclNodeIdentities := []*api.ACLNodeIdentity{}
for _, nodeIdentity := range data {
entry := &api.ACLNodeIdentity{}
components := strings.Split(nodeIdentity, ":")
entry.NodeName = components[0]
if len(components) > 1 {
entry.Datacenter = components[1]
}
aclNodeIdentities = append(aclNodeIdentities, entry)
}
return aclNodeIdentities
}

View File

@ -1,107 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package consul
import (
"reflect"
"testing"
"github.com/hashicorp/consul/api"
)
func TestToken_parseServiceIdentities(t *testing.T) {
tests := []struct {
name string
args []string
want []*api.ACLServiceIdentity
}{
{
name: "No datacenters",
args: []string{"myservice-1"},
want: []*api.ACLServiceIdentity{{ServiceName: "myservice-1", Datacenters: nil}},
},
{
name: "One datacenter",
args: []string{"myservice-1:dc1"},
want: []*api.ACLServiceIdentity{{ServiceName: "myservice-1", Datacenters: []string{"dc1"}}},
},
{
name: "Multiple datacenters",
args: []string{"myservice-1:dc1,dc2,dc3"},
want: []*api.ACLServiceIdentity{{ServiceName: "myservice-1", Datacenters: []string{"dc1", "dc2", "dc3"}}},
},
{
name: "Missing service name with datacenter",
args: []string{":dc1"},
want: []*api.ACLServiceIdentity{{ServiceName: "", Datacenters: []string{"dc1"}}},
},
{
name: "Missing service name and missing datacenter",
args: []string{""},
want: []*api.ACLServiceIdentity{{ServiceName: "", Datacenters: nil}},
},
{
name: "Multiple service identities",
args: []string{"myservice-1:dc1", "myservice-2:dc1", "myservice-3:dc1,dc2"},
want: []*api.ACLServiceIdentity{
{ServiceName: "myservice-1", Datacenters: []string{"dc1"}},
{ServiceName: "myservice-2", Datacenters: []string{"dc1"}},
{ServiceName: "myservice-3", Datacenters: []string{"dc1", "dc2"}},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := parseServiceIdentities(tt.args); !reflect.DeepEqual(got, tt.want) {
t.Errorf("parseServiceIdentities() = {%s:%v}, want {%s:%v}", got[0].ServiceName, got[0].Datacenters, tt.want[0].ServiceName, tt.want[0].Datacenters)
}
})
}
}
func TestToken_parseNodeIdentities(t *testing.T) {
tests := []struct {
name string
args []string
want []*api.ACLNodeIdentity
}{
{
name: "No datacenter",
args: []string{"server-1"},
want: []*api.ACLNodeIdentity{{NodeName: "server-1", Datacenter: ""}},
},
{
name: "One datacenter",
args: []string{"server-1:dc1"},
want: []*api.ACLNodeIdentity{{NodeName: "server-1", Datacenter: "dc1"}},
},
{
name: "Missing node name with datacenter",
args: []string{":dc1"},
want: []*api.ACLNodeIdentity{{NodeName: "", Datacenter: "dc1"}},
},
{
name: "Missing node name and missing datacenter",
args: []string{""},
want: []*api.ACLNodeIdentity{{NodeName: "", Datacenter: ""}},
},
{
name: "Multiple node identities",
args: []string{"server-1:dc1", "server-2:dc1", "server-3:dc1"},
want: []*api.ACLNodeIdentity{
{NodeName: "server-1", Datacenter: "dc1"},
{NodeName: "server-2", Datacenter: "dc1"},
{NodeName: "server-3", Datacenter: "dc1"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := parseNodeIdentities(tt.args); !reflect.DeepEqual(got, tt.want) {
t.Errorf("parseNodeIdentities() = {%s:%s}, want {%s:%s}", got[0].NodeName, got[0].Datacenter, tt.want[0].NodeName, tt.want[0].Datacenter)
}
})
}
}

View File

@ -1,123 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package consul
import (
"context"
"fmt"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
const (
SecretTokenType = "token"
)
func secretToken(b *backend) *framework.Secret {
return &framework.Secret{
Type: SecretTokenType,
Fields: map[string]*framework.FieldSchema{
"token": {
Type: framework.TypeString,
Description: "Request token",
},
},
Renew: b.secretTokenRenew,
Revoke: b.secretTokenRevoke,
}
}
func (b *backend) secretTokenRenew(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
resp := &logical.Response{Secret: req.Secret}
roleRaw, ok := req.Secret.InternalData["role"]
if !ok {
return resp, nil
}
role, ok := roleRaw.(string)
if !ok {
return resp, nil
}
entry, err := req.Storage.Get(ctx, "policy/"+role)
if err != nil {
return nil, fmt.Errorf("error retrieving role: %w", err)
}
if entry == nil {
return logical.ErrorResponse(fmt.Sprintf("issuing role %q not found", role)), nil
}
var result roleConfig
if err := entry.DecodeJSON(&result); err != nil {
return nil, err
}
resp.Secret.TTL = result.TTL
resp.Secret.MaxTTL = result.MaxTTL
return resp, nil
}
func (b *backend) secretTokenRevoke(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
c, userErr, intErr := b.client(ctx, req.Storage)
if intErr != nil {
return nil, intErr
}
if userErr != nil {
// Returning logical.ErrorResponse from revocation function is risky
return nil, userErr
}
tokenRaw, ok := req.Secret.InternalData["token"]
if !ok {
// We return nil here because this is a pre-0.5.3 problem and there is
// nothing we can do about it. We already can't revoke the lease
// properly if it has been renewed and this is documented pre-0.5.3
// behavior with a security bulletin about it.
return nil, nil
}
var version string
versionRaw, ok := req.Secret.InternalData["version"]
if ok {
version = versionRaw.(string)
}
// Extract Consul Namespace and Partition info from secret
var revokeWriteOptions *api.WriteOptions
var namespace, partition string
namespaceRaw, ok := req.Data["consul_namespace"]
if ok {
namespace = namespaceRaw.(string)
}
partitionRaw, ok := req.Data["partition"]
if ok {
partition = partitionRaw.(string)
}
revokeWriteOptions = &api.WriteOptions{
Namespace: namespace,
Partition: partition,
}
switch version {
case "":
// Pre 1.4 tokens
_, err := c.ACL().Destroy(tokenRaw.(string), nil)
if err != nil {
return nil, err
}
case tokenPolicyType:
_, err := c.ACL().TokenDelete(tokenRaw.(string), revokeWriteOptions)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("Invalid version string in data: %s", version)
}
return nil, nil
}

View File

@ -85,7 +85,6 @@ func (b *BaseCommand) PredictVaultAvailableMounts() complete.Predictor {
// This list does not contain deprecated backends. At present, there is no
// API that lists all available secret backends, so this is hard-coded :(.
return complete.PredictSet(
"consul",
"database",
"generic",
"pki",

View File

@ -40,7 +40,6 @@ import (
logicalKv "github.com/hashicorp/vault-plugin-secrets-kv"
logicalDb "github.com/hashicorp/vault/builtin/logical/database"
physConsul "github.com/hashicorp/vault/physical/consul"
physRaft "github.com/hashicorp/vault/physical/raft"
physFile "github.com/hashicorp/vault/sdk/physical/file"
physInmem "github.com/hashicorp/vault/sdk/physical/inmem"
@ -162,7 +161,6 @@ var (
}
physicalBackends = map[string]physical.Factory{
"consul": physConsul.NewConsulBackend,
"file_transactional": physFile.NewTransactionalFileBackend,
"file": physFile.NewFileBackend,
"inmem_ha": physInmem.NewInmemHA,

View File

@ -27,7 +27,6 @@ import (
"github.com/hashicorp/vault/helper/metricsutil"
"github.com/hashicorp/vault/internalshared/configutil"
"github.com/hashicorp/vault/internalshared/listenerutil"
physconsul "github.com/hashicorp/vault/physical/consul"
"github.com/hashicorp/vault/physical/raft"
"github.com/hashicorp/vault/sdk/physical"
sr "github.com/hashicorp/vault/serviceregistration"
@ -317,28 +316,6 @@ func (c *OperatorDiagnoseCommand) offlineDiagnostics(ctx context.Context) error
diagnose.RaftStorageQuorum(ctx, (*backend).(*raft.RaftBackend))
}
// Consul storage checks
if config.Storage != nil && config.Storage.Type == storageTypeConsul {
diagnose.Test(ctx, "Check Consul TLS", func(ctx context.Context) error {
err := physconsul.SetupSecureTLS(ctx, api.DefaultConfig(), config.Storage.Config, server.logger, true)
if err != nil {
return err
}
return nil
})
diagnose.Test(ctx, "Check Consul Direct Storage Access", func(ctx context.Context) error {
dirAccess := diagnose.ConsulDirectAccess(config.Storage.Config)
if dirAccess != "" {
diagnose.Warn(ctx, dirAccess)
}
if dirAccess == diagnose.DirAccessErr {
diagnose.Advise(ctx, diagnose.DirAccessAdvice)
}
return nil
})
}
// Attempt to use storage backend
if !c.skipEndEnd && config.Storage.Type != storageTypeRaft {
diagnose.Test(ctx, "Check Storage Access", diagnose.WithTimeout(30*time.Second, func(ctx context.Context) error {
@ -552,15 +529,6 @@ SEALFAIL:
}
return nil
})
if config.HAStorage != nil && config.HAStorage.Type == storageTypeConsul {
diagnose.Test(ctx, "Check Consul TLS", func(ctx context.Context) error {
err = physconsul.SetupSecureTLS(ctx, api.DefaultConfig(), config.HAStorage.Config, server.logger, true)
if err != nil {
return err
}
return nil
})
}
return nil
})

View File

@ -86,8 +86,7 @@ const (
// Even though there are more types than the ones below, the following consts
// are declared internally for value comparison and reusability.
storageTypeRaft = "raft"
storageTypeConsul = "consul"
storageTypeRaft = "raft"
)
type ServerCommand struct {
@ -145,7 +144,6 @@ type ServerCommand struct {
flagDevClusterJson string
flagTestVerifyOnly bool
flagTestServerConfig bool
flagDevConsul bool
flagExitOnCoreShutdown bool
}
@ -367,13 +365,6 @@ func (c *ServerCommand) Flags() *FlagSets {
Hidden: true,
})
f.BoolVar(&BoolVar{
Name: "dev-consul",
Target: &c.flagDevConsul,
Default: false,
Hidden: true,
})
f.StringVar(&StringVar{
Name: "dev-cluster-json",
Target: &c.flagDevClusterJson,
@ -789,16 +780,6 @@ func (c *ServerCommand) setupStorage(config *server.Config) (physical.Backend, e
// Do any custom configuration needed per backend
switch config.Storage.Type {
case storageTypeConsul:
if config.ServiceRegistration == nil {
// If Consul is configured for storage and service registration is unconfigured,
// use Consul for service registration without requiring additional configuration.
// This maintains backward-compatibility.
config.ServiceRegistration = &server.ServiceRegistration{
Type: "consul",
Config: config.Storage.Config,
}
}
case storageTypeRaft:
if envCA := os.Getenv("VAULT_CLUSTER_ADDR"); envCA != "" {
config.ClusterAddr = envCA
@ -935,8 +916,6 @@ func configureDevTLS(c *ServerCommand) (func(), *server.Config, string, error) {
var devStorageType string
switch {
case c.flagDevConsul:
devStorageType = "consul"
case c.flagDevHA && c.flagDevTransactional:
devStorageType = "inmem_transactional_ha"
case !c.flagDevHA && c.flagDevTransactional:
@ -1017,7 +996,7 @@ func (c *ServerCommand) Run(args []string) int {
}
// Automatically enable dev mode if other dev flags are provided.
if c.flagDevConsul || c.flagDevHA || c.flagDevTransactional || c.flagDevLeasedKV || c.flagDevThreeNode || c.flagDevFourCluster || c.flagDevAutoSeal || c.flagDevKVV1 || c.flagDevTLS {
if c.flagDevHA || c.flagDevTransactional || c.flagDevLeasedKV || c.flagDevThreeNode || c.flagDevFourCluster || c.flagDevAutoSeal || c.flagDevKVV1 || c.flagDevTLS {
c.flagDev = true
}
@ -1319,7 +1298,7 @@ func (c *ServerCommand) Run(args []string) int {
if !storageSupportedForEnt(&coreConfig) {
c.UI.Warn("")
c.UI.Warn(wrapAtLength(fmt.Sprintf("WARNING: storage configured to use %q which is not supported for Vault Enterprise, must be \"raft\" or \"consul\"", coreConfig.StorageType)))
c.UI.Warn(wrapAtLength(fmt.Sprintf("WARNING: storage configured to use %q which is not supported for Vault Enterprise, must be \"raft\"", coreConfig.StorageType)))
c.UI.Warn("")
}

View File

@ -23,7 +23,6 @@ import (
credOkta "github.com/hashicorp/vault/builtin/credential/okta"
credRadius "github.com/hashicorp/vault/builtin/credential/radius"
credUserpass "github.com/hashicorp/vault/builtin/credential/userpass"
logicalConsul "github.com/hashicorp/vault/builtin/logical/consul"
logicalNomad "github.com/hashicorp/vault/builtin/logical/nomad"
logicalPki "github.com/hashicorp/vault/builtin/logical/pki"
logicalRabbit "github.com/hashicorp/vault/builtin/logical/rabbitmq"
@ -121,7 +120,6 @@ func newRegistry() *registry {
Factory: removedFactory,
DeprecationStatus: consts.Removed,
},
"consul": {Factory: logicalConsul.Factory},
"kubernetes": {Factory: logicalKube.Factory},
"kv": {Factory: logicalKv.Factory},
"mysql": {

View File

@ -1,784 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package consul
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/armon/go-metrics"
"github.com/hashicorp/consul/api"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-secure-stdlib/parseutil"
"github.com/hashicorp/go-secure-stdlib/tlsutil"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/physical"
"github.com/hashicorp/vault/vault/diagnose"
"golang.org/x/net/http2"
)
const (
// consistencyModeDefault is the configuration value used to tell
// consul to use default consistency.
consistencyModeDefault = "default"
// consistencyModeStrong is the configuration value used to tell
// consul to use strong consistency.
consistencyModeStrong = "strong"
// nonExistentKey is used as part of a capabilities check against Consul
nonExistentKey = "F35C28E1-7035-40BB-B865-6BED9E3A1B28"
)
// Verify ConsulBackend satisfies the correct interfaces
var (
_ physical.Backend = (*ConsulBackend)(nil)
_ physical.FencingHABackend = (*ConsulBackend)(nil)
_ physical.Lock = (*ConsulLock)(nil)
_ physical.Transactional = (*ConsulBackend)(nil)
GetInTxnDisabledError = errors.New("get operations inside transactions are disabled in consul backend")
)
// ConsulBackend is a physical backend that stores data at specific
// prefix within Consul. It is used for most production situations as
// it allows Vault to run on multiple machines in a highly-available manner.
// failGetInTxn is only used in tests.
type ConsulBackend struct {
logger log.Logger
client *api.Client
path string
kv *api.KV
txn *api.Txn
permitPool *physical.PermitPool
consistencyMode string
sessionTTL string
lockWaitTime time.Duration
failGetInTxn *uint32
activeNodeLock atomic.Pointer[ConsulLock]
}
// NewConsulBackend constructs a Consul backend using the given API client
// and the prefix in the KV store.
func NewConsulBackend(conf map[string]string, logger log.Logger) (physical.Backend, error) {
// Get the path in Consul
path, ok := conf["path"]
if !ok {
path = "vault/"
}
if logger.IsDebug() {
logger.Debug("config path set", "path", path)
}
// Ensure path is suffixed but not prefixed
if !strings.HasSuffix(path, "/") {
logger.Warn("appending trailing forward slash to path")
path += "/"
}
if strings.HasPrefix(path, "/") {
logger.Warn("trimming path of its forward slash")
path = strings.TrimPrefix(path, "/")
}
sessionTTL := api.DefaultLockSessionTTL
sessionTTLStr, ok := conf["session_ttl"]
if ok {
_, err := parseutil.ParseDurationSecond(sessionTTLStr)
if err != nil {
return nil, fmt.Errorf("invalid session_ttl: %w", err)
}
sessionTTL = sessionTTLStr
if logger.IsDebug() {
logger.Debug("config session_ttl set", "session_ttl", sessionTTL)
}
}
lockWaitTime := api.DefaultLockWaitTime
lockWaitTimeRaw, ok := conf["lock_wait_time"]
if ok {
d, err := parseutil.ParseDurationSecond(lockWaitTimeRaw)
if err != nil {
return nil, fmt.Errorf("invalid lock_wait_time: %w", err)
}
lockWaitTime = d
if logger.IsDebug() {
logger.Debug("config lock_wait_time set", "lock_wait_time", d)
}
}
maxParStr, ok := conf["max_parallel"]
var maxParInt int
if ok {
maxParInt, err := strconv.Atoi(maxParStr)
if err != nil {
return nil, fmt.Errorf("failed parsing max_parallel parameter: %w", err)
}
if logger.IsDebug() {
logger.Debug("max_parallel set", "max_parallel", maxParInt)
}
}
consistencyMode, ok := conf["consistency_mode"]
if ok {
switch consistencyMode {
case consistencyModeDefault, consistencyModeStrong:
default:
return nil, fmt.Errorf("invalid consistency_mode value: %q", consistencyMode)
}
} else {
consistencyMode = consistencyModeDefault
}
// Configure the client
consulConf := api.DefaultConfig()
// Set MaxIdleConnsPerHost to the number of processes used in expiration.Restore
consulConf.Transport.MaxIdleConnsPerHost = consts.ExpirationRestoreWorkerCount
if err := SetupSecureTLS(context.Background(), consulConf, conf, logger, false); err != nil {
return nil, fmt.Errorf("client setup failed: %w", err)
}
consulConf.HttpClient = &http.Client{Transport: consulConf.Transport}
client, err := api.NewClient(consulConf)
if err != nil {
return nil, fmt.Errorf("client setup failed: %w", err)
}
// Set up the backend
c := &ConsulBackend{
logger: logger,
path: path,
client: client,
kv: client.KV(),
txn: client.Txn(),
permitPool: physical.NewPermitPool(maxParInt),
consistencyMode: consistencyMode,
sessionTTL: sessionTTL,
lockWaitTime: lockWaitTime,
failGetInTxn: new(uint32),
}
return c, nil
}
func SetupSecureTLS(ctx context.Context, consulConf *api.Config, conf map[string]string, logger log.Logger, isDiagnose bool) error {
if addr, ok := conf["address"]; ok {
consulConf.Address = addr
if logger.IsDebug() {
logger.Debug("config address set", "address", addr)
}
// Copied from the Consul API module; set the Scheme based on
// the protocol field if address looks ike a URL.
// This can enable the TLS configuration below.
parts := strings.SplitN(addr, "://", 2)
if len(parts) == 2 {
if parts[0] == "http" || parts[0] == "https" {
consulConf.Scheme = parts[0]
consulConf.Address = parts[1]
if logger.IsDebug() {
logger.Debug("config address parsed", "scheme", parts[0])
logger.Debug("config scheme parsed", "address", parts[1])
}
} // allow "unix:" or whatever else consul supports in the future
}
}
if scheme, ok := conf["scheme"]; ok {
consulConf.Scheme = scheme
if logger.IsDebug() {
logger.Debug("config scheme set", "scheme", scheme)
}
}
if token, ok := conf["token"]; ok {
consulConf.Token = token
logger.Debug("config token set")
}
if consulConf.Scheme == "https" {
if isDiagnose {
certPath, okCert := conf["tls_cert_file"]
keyPath, okKey := conf["tls_key_file"]
if okCert && okKey {
warnings, err := diagnose.TLSFileChecks(certPath, keyPath)
for _, warning := range warnings {
diagnose.Warn(ctx, warning)
}
if err != nil {
return err
}
return nil
}
return fmt.Errorf("key or cert path: %s, %s, cannot be loaded from consul config file", certPath, keyPath)
}
// Use the parsed Address instead of the raw conf['address']
tlsClientConfig, err := tlsutil.SetupTLSConfig(conf, consulConf.Address)
if err != nil {
return err
}
consulConf.Transport.TLSClientConfig = tlsClientConfig
if err := http2.ConfigureTransport(consulConf.Transport); err != nil {
return err
}
logger.Debug("configured TLS")
} else {
if isDiagnose {
diagnose.Skipped(ctx, "HTTPS is not used, Skipping TLS verification.")
}
}
return nil
}
// ExpandedCapabilitiesAvailable tests to see if Consul has KVGetOrEmpty and 128 entries per transaction available
func (c *ConsulBackend) ExpandedCapabilitiesAvailable(ctx context.Context) bool {
available := false
maxEntries := 128
ops := make([]*api.TxnOp, maxEntries)
for i := 0; i < maxEntries; i++ {
ops[i] = &api.TxnOp{KV: &api.KVTxnOp{
Key: c.path + nonExistentKey,
Verb: api.KVGetOrEmpty,
}}
}
c.permitPool.Acquire()
defer c.permitPool.Release()
queryOpts := &api.QueryOptions{}
queryOpts = queryOpts.WithContext(ctx)
ok, resp, _, err := c.txn.Txn(ops, queryOpts)
if ok && len(resp.Errors) == 0 && err == nil {
available = true
}
return available
}
func (c *ConsulBackend) writeTxnOps(ctx context.Context, len int) ([]*api.TxnOp, string) {
if len < 1 {
len = 1
}
ops := make([]*api.TxnOp, 0, len+1)
// If we don't have a lock yet, return a transaction with no session check. We
// need to do this to allow writes during cluster initialization before there
// is an active node.
lock := c.activeNodeLock.Load()
if lock == nil {
return ops, ""
}
lockKey, lockSession := lock.Info()
if lockKey == "" || lockSession == "" {
return ops, ""
}
// If the context used to write has been marked as a special case write that
// happens outside of a lock then don't add the session check.
if physical.IsUnfencedWrite(ctx) {
return ops, ""
}
// Insert the session check operation at index 0. This will allow us later to
// work out easily if a write failure is because of the session check.
ops = append(ops, &api.TxnOp{
KV: &api.KVTxnOp{
Verb: api.KVCheckSession,
Key: lockKey,
Session: lockSession,
},
})
return ops, lockSession
}
// Transaction is used to run multiple entries via a transaction.
func (c *ConsulBackend) Transaction(ctx context.Context, txns []*physical.TxnEntry) error {
return c.txnInternal(ctx, txns, "transaction")
}
func (c *ConsulBackend) txnInternal(ctx context.Context, txns []*physical.TxnEntry, apiOpName string) error {
if len(txns) == 0 {
return nil
}
defer metrics.MeasureSince([]string{"consul", apiOpName}, time.Now())
failGetInTxn := atomic.LoadUint32(c.failGetInTxn)
for _, t := range txns {
if t.Operation == physical.GetOperation && failGetInTxn != 0 {
return GetInTxnDisabledError
}
}
ops, sessionID := c.writeTxnOps(ctx, len(txns))
for _, t := range txns {
o, err := c.makeApiTxn(t)
if err != nil {
return fmt.Errorf("error converting physical transactions into api transactions: %w", err)
}
ops = append(ops, o)
}
c.permitPool.Acquire()
defer c.permitPool.Release()
var retErr *multierror.Error
kvMap := make(map[string][]byte, 0)
queryOpts := &api.QueryOptions{}
queryOpts = queryOpts.WithContext(ctx)
ok, resp, _, err := c.txn.Txn(ops, queryOpts)
if err != nil {
if strings.Contains(err.Error(), "is too large") {
return fmt.Errorf("%s: %w", physical.ErrValueTooLarge, err)
}
return err
}
if ok && len(resp.Errors) == 0 {
// Loop over results and cache them in a map. Note that we're only caching
// the first time we see a key, which _should_ correspond to a Get
// operation, since we expect those come first in our txns slice (though
// after check-session).
for _, txnr := range resp.Results {
if len(txnr.KV.Value) > 0 {
// We need to trim the Consul kv path (typically "vault/") from the key
// otherwise it won't match the transaction entries we have.
key := strings.TrimPrefix(txnr.KV.Key, c.path)
if _, found := kvMap[key]; !found {
kvMap[key] = txnr.KV.Value
}
}
}
}
if len(resp.Errors) > 0 {
for _, res := range resp.Errors {
retErr = multierror.Append(retErr, errors.New(res.What))
if res.OpIndex == 0 && sessionID != "" {
// We added a session check (sessionID not empty) so an error at OpIndex
// 0 means that we failed that session check. We don't attempt to string
// match because Consul can return at least three different errors here
// with no common string. In all cases though failing this check means
// we no longer hold the lock because it was released, modified or
// deleted. Rather than just continuing to try writing until the
// blocking query manages to notice we're no longer the lock holder
// (which can take 10s of seconds even in good network conditions in my
// testing) we can now Unlock directly here. Our ConsulLock now has a
// shortcut that will cause the lock to close the leaderCh immediately
// when we call without waiting for the blocking query to return (unlike
// Consul's current Lock implementation). But before we unlock, we
// should re-load the lock and ensure it's still the same instance we
// just tried to write with in case this goroutine is somehow really
// delayed and we actually acquired a whole new lock in the meantime!
lock := c.activeNodeLock.Load()
if lock != nil {
_, lockSessionID := lock.Info()
if sessionID == lockSessionID {
c.logger.Warn("session check failed on write, we lost active node lock, stepping down", "err", res.What)
lock.Unlock()
}
}
}
}
}
if retErr != nil {
return retErr
}
// Loop over our get transactions and populate any values found in our map cache.
for _, t := range txns {
if val, ok := kvMap[t.Entry.Key]; ok && t.Operation == physical.GetOperation {
newVal := make([]byte, len(val))
copy(newVal, val)
t.Entry.Value = newVal
}
}
return nil
}
func (c *ConsulBackend) makeApiTxn(txn *physical.TxnEntry) (*api.TxnOp, error) {
op := &api.KVTxnOp{
Key: c.path + txn.Entry.Key,
}
switch txn.Operation {
case physical.GetOperation:
op.Verb = api.KVGetOrEmpty
case physical.DeleteOperation:
op.Verb = api.KVDelete
case physical.PutOperation:
op.Verb = api.KVSet
op.Value = txn.Entry.Value
default:
return nil, fmt.Errorf("%q is not a supported transaction operation", txn.Operation)
}
return &api.TxnOp{KV: op}, nil
}
// Put is used to insert or update an entry
func (c *ConsulBackend) Put(ctx context.Context, entry *physical.Entry) error {
txns := []*physical.TxnEntry{
{
Operation: physical.PutOperation,
Entry: entry,
},
}
return c.txnInternal(ctx, txns, "put")
}
// Get is used to fetch an entry
func (c *ConsulBackend) Get(ctx context.Context, key string) (*physical.Entry, error) {
defer metrics.MeasureSince([]string{"consul", "get"}, time.Now())
c.permitPool.Acquire()
defer c.permitPool.Release()
queryOpts := &api.QueryOptions{}
queryOpts = queryOpts.WithContext(ctx)
if c.consistencyMode == consistencyModeStrong {
queryOpts.RequireConsistent = true
}
pair, _, err := c.kv.Get(c.path+key, queryOpts)
if err != nil {
return nil, err
}
if pair == nil {
return nil, nil
}
ent := &physical.Entry{
Key: key,
Value: pair.Value,
}
return ent, nil
}
// Delete is used to permanently delete an entry
func (c *ConsulBackend) Delete(ctx context.Context, key string) error {
txns := []*physical.TxnEntry{
{
Operation: physical.DeleteOperation,
Entry: &physical.Entry{
Key: key,
},
},
}
return c.txnInternal(ctx, txns, "delete")
}
// List is used to list all the keys under a given
// prefix, up to the next prefix.
func (c *ConsulBackend) List(ctx context.Context, prefix string) ([]string, error) {
defer metrics.MeasureSince([]string{"consul", "list"}, time.Now())
scan := c.path + prefix
// The TrimPrefix call below will not work correctly if we have "//" at the
// end. This can happen in cases where you are e.g. listing the root of a
// prefix in a logical backend via "/" instead of ""
if strings.HasSuffix(scan, "//") {
scan = scan[:len(scan)-1]
}
c.permitPool.Acquire()
defer c.permitPool.Release()
queryOpts := &api.QueryOptions{}
queryOpts = queryOpts.WithContext(ctx)
out, _, err := c.kv.Keys(scan, "/", queryOpts)
for idx, val := range out {
out[idx] = strings.TrimPrefix(val, scan)
}
return out, err
}
func (c *ConsulBackend) FailGetInTxn(fail bool) {
var val uint32
if fail {
val = 1
}
atomic.StoreUint32(c.failGetInTxn, val)
}
// LockWith is used for mutual exclusion based on the given key.
func (c *ConsulBackend) LockWith(key, value string) (physical.Lock, error) {
cl := &ConsulLock{
logger: c.logger,
client: c.client,
key: c.path + key,
value: value,
consistencyMode: c.consistencyMode,
sessionTTL: c.sessionTTL,
lockWaitTime: c.lockWaitTime,
}
return cl, nil
}
// HAEnabled indicates whether the HA functionality should be exposed.
// Currently always returns true.
func (c *ConsulBackend) HAEnabled() bool {
return true
}
// DetectHostAddr is used to detect the host address by asking the Consul agent
func (c *ConsulBackend) DetectHostAddr() (string, error) {
agent := c.client.Agent()
self, err := agent.Self()
if err != nil {
return "", err
}
addr, ok := self["Member"]["Addr"].(string)
if !ok {
return "", fmt.Errorf("unable to convert an address to string")
}
return addr, nil
}
// RegisterActiveNodeLock is called after active node lock is obtained to allow
// us to fence future writes.
func (c *ConsulBackend) RegisterActiveNodeLock(l physical.Lock) error {
cl, ok := l.(*ConsulLock)
if !ok {
return fmt.Errorf("invalid Lock type")
}
c.activeNodeLock.Store(cl)
key, sessionID := cl.Info()
c.logger.Info("registered active node lock", "key", key, "sessionID", sessionID)
return nil
}
// ConsulLock is used to provide the Lock interface backed by Consul. We work
// around some limitations of Consuls api.Lock noted in
// https://github.com/hashicorp/consul/issues/18271 by creating and managing the
// session ourselves, while using Consul's Lock to do the heavy lifting.
type ConsulLock struct {
logger log.Logger
client *api.Client
key string
value string
consistencyMode string
sessionTTL string
lockWaitTime time.Duration
mu sync.Mutex // protects session state
session *lockSession
// sessionID is a copy of the value from session.id. We use a separate field
// because `Info` needs to keep returning the same sessionID after Unlock has
// cleaned up the session state so that we continue to fence any writes still
// in flight after the lock is Unlocked. It's easier to reason about that as a
// separate field rather than keeping an already-terminated session object
// around. Once Lock is called again this will be replaced (while mu is
// locked) with the new session ID. Must hold mu to read or write this.
sessionID string
}
type lockSession struct {
// id is immutable after the session is created so does not need mu held
id string
// mu protects the lock and unlockCh to ensure they are only cleaned up once
mu sync.Mutex
lock *api.Lock
unlockCh chan struct{}
}
func (s *lockSession) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) {
s.mu.Lock()
defer s.mu.Unlock()
lockHeld := false
defer func() {
if !lockHeld {
s.cleanupLocked()
}
}()
consulLeaderCh, err := s.lock.Lock(stopCh)
if err != nil {
return nil, err
}
if consulLeaderCh == nil {
// If both leaderCh and err are nil from Consul's Lock then it means we
// waited for the lockWait without grabbing it.
return nil, nil
}
// We got the Lock, monitor it!
lockHeld = true
leaderCh := make(chan struct{})
go s.monitorLock(leaderCh, s.unlockCh, consulLeaderCh)
return leaderCh, nil
}
// monitorLock waits for either unlockCh or consulLeaderCh to close and then
// closes leaderCh. It's designed to be run in a separate goroutine. Note that
// we pass unlockCh rather than accessing it via the member variable because it
// is mutated under the lock during Unlock so reading it from c could be racy.
// We just need the chan created at the call site here so we pass it instead of
// locking and unlocking in here.
func (s *lockSession) monitorLock(leaderCh chan struct{}, unlockCh, consulLeaderCh <-chan struct{}) {
select {
case <-unlockCh:
case <-consulLeaderCh:
}
// We lost the lock. Close the leaderCh
close(leaderCh)
// Whichever chan closed, cleanup to unwind all the state. If we were
// triggered by a cleanup call this will be a no-op, but if not it ensures all
// state is cleaned up correctly.
s.cleanup()
}
func (s *lockSession) cleanup() {
s.mu.Lock()
defer s.mu.Unlock()
s.cleanupLocked()
}
func (s *lockSession) cleanupLocked() {
if s.lock != nil {
s.lock.Unlock()
s.lock = nil
}
if s.unlockCh != nil {
close(s.unlockCh)
s.unlockCh = nil
}
// Don't bother destroying sessions as they will be destroyed after TTL
// anyway.
}
func (c *ConsulLock) createSession() (*lockSession, error) {
se := &api.SessionEntry{
Name: "Vault Lock",
TTL: c.sessionTTL,
// We use Consul's default LockDelay of 15s by not specifying it
}
session, _, err := c.client.Session().Create(se, nil)
if err != nil {
return nil, err
}
opts := &api.LockOptions{
Key: c.key,
Value: []byte(c.value),
Session: session,
MonitorRetries: 5,
LockWaitTime: c.lockWaitTime,
SessionTTL: c.sessionTTL,
}
lock, err := c.client.LockOpts(opts)
if err != nil {
// Don't bother destroying sessions as they will be destroyed after TTL
// anyway.
return nil, fmt.Errorf("failed to create lock: %w", err)
}
unlockCh := make(chan struct{})
s := &lockSession{
id: session,
lock: lock,
unlockCh: unlockCh,
}
// Start renewals of the session
go func() {
// Note we capture unlockCh here rather than s.unlockCh because s.unlockCh
// is mutated on cleanup which is racy since we don't hold a lock here.
// unlockCh will never be mutated though.
err := c.client.Session().RenewPeriodic(c.sessionTTL, session, nil, unlockCh)
if err != nil {
c.logger.Error("failed to renew consul session for more than the TTL, lock lost", "err", err)
}
// release other resources for this session only i.e. don't c.Unlock as that
// might now be locked under a different session).
s.cleanup()
}()
return s, nil
}
func (c *ConsulLock) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.session != nil {
return nil, fmt.Errorf("lock instance already locked")
}
session, err := c.createSession()
if err != nil {
return nil, err
}
leaderCh, err := session.Lock(stopCh)
if leaderCh != nil && err == nil {
// We hold the lock, store the session
c.session = session
c.sessionID = session.id
}
return leaderCh, err
}
func (c *ConsulLock) Unlock() error {
c.mu.Lock()
defer c.mu.Unlock()
if c.session != nil {
c.session.cleanup()
c.session = nil
// Don't clear c.sessionID since we rely on returning the same old ID after
// Unlock until the next Lock.
}
return nil
}
func (c *ConsulLock) Value() (bool, string, error) {
kv := c.client.KV()
var queryOptions *api.QueryOptions
if c.consistencyMode == consistencyModeStrong {
queryOptions = &api.QueryOptions{
RequireConsistent: true,
}
}
pair, _, err := kv.Get(c.key, queryOptions)
if err != nil {
return false, "", err
}
if pair == nil {
return false, "", nil
}
// Note that held is expected to mean "does _any_ node hold the lock" not
// "does this current instance hold the lock" so although we know what our own
// session ID is, we don't check it matches here only that there is _some_
// session in Consul holding the lock right now.
held := pair.Session != ""
value := string(pair.Value)
return held, value, nil
}
func (c *ConsulLock) Info() (key, sessionid string) {
c.mu.Lock()
defer c.mu.Unlock()
return c.key, c.sessionID
}

View File

@ -1,528 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package consul
import (
"bytes"
"context"
"encoding/hex"
"fmt"
"math/rand"
"reflect"
"strings"
"testing"
"time"
"github.com/hashicorp/consul/api"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/helper/testhelpers/consul"
"github.com/hashicorp/vault/sdk/helper/logging"
"github.com/hashicorp/vault/sdk/physical"
"github.com/stretchr/testify/require"
)
func TestConsul_newConsulBackend(t *testing.T) {
tests := []struct {
name string
consulConfig map[string]string
fail bool
redirectAddr string
checkTimeout time.Duration
path string
service string
address string
scheme string
token string
max_parallel int
disableReg bool
consistencyMode string
}{
{
name: "Valid default config",
consulConfig: map[string]string{},
checkTimeout: 5 * time.Second,
redirectAddr: "http://127.0.0.1:8200",
path: "vault/",
service: "vault",
address: "127.0.0.1:8500",
scheme: "http",
token: "",
max_parallel: 4,
disableReg: false,
consistencyMode: "default",
},
{
name: "Valid modified config",
consulConfig: map[string]string{
"path": "seaTech/",
"service": "astronomy",
"redirect_addr": "http://127.0.0.2:8200",
"check_timeout": "6s",
"address": "127.0.0.2",
"scheme": "https",
"token": "deadbeef-cafeefac-deadc0de-feedface",
"max_parallel": "4",
"disable_registration": "false",
"consistency_mode": "strong",
},
checkTimeout: 6 * time.Second,
path: "seaTech/",
service: "astronomy",
redirectAddr: "http://127.0.0.2:8200",
address: "127.0.0.2",
scheme: "https",
token: "deadbeef-cafeefac-deadc0de-feedface",
max_parallel: 4,
consistencyMode: "strong",
},
{
name: "Unix socket",
consulConfig: map[string]string{
"address": "unix:///tmp/.consul.http.sock",
},
address: "/tmp/.consul.http.sock",
scheme: "http", // Default, not overridden?
// Defaults
checkTimeout: 5 * time.Second,
redirectAddr: "http://127.0.0.1:8200",
path: "vault/",
service: "vault",
token: "",
max_parallel: 4,
disableReg: false,
consistencyMode: "default",
},
{
name: "Scheme in address",
consulConfig: map[string]string{
"address": "https://127.0.0.2:5000",
},
address: "127.0.0.2:5000",
scheme: "https",
// Defaults
checkTimeout: 5 * time.Second,
redirectAddr: "http://127.0.0.1:8200",
path: "vault/",
service: "vault",
token: "",
max_parallel: 4,
disableReg: false,
consistencyMode: "default",
},
}
for _, test := range tests {
logger := logging.NewVaultLogger(log.Debug)
be, err := NewConsulBackend(test.consulConfig, logger)
if test.fail {
if err == nil {
t.Fatalf(`Expected config "%s" to fail`, test.name)
} else {
continue
}
} else if !test.fail && err != nil {
t.Fatalf("Expected config %s to not fail: %v", test.name, err)
}
c, ok := be.(*ConsulBackend)
if !ok {
t.Fatalf("Expected ConsulBackend: %s", test.name)
}
if test.path != c.path {
t.Errorf("bad: %s %v != %v", test.name, test.path, c.path)
}
if test.consistencyMode != c.consistencyMode {
t.Errorf("bad consistency_mode value: %v != %v", test.consistencyMode, c.consistencyMode)
}
// The configuration stored in the Consul "client" object is not exported, so
// we either have to skip validating it, or add a method to export it, or use reflection.
consulConfig := reflect.Indirect(reflect.ValueOf(c.client)).FieldByName("config")
consulConfigScheme := consulConfig.FieldByName("Scheme").String()
consulConfigAddress := consulConfig.FieldByName("Address").String()
if test.scheme != consulConfigScheme {
t.Errorf("bad scheme value: %v != %v", test.scheme, consulConfigScheme)
}
if test.address != consulConfigAddress {
t.Errorf("bad address value: %v != %v", test.address, consulConfigAddress)
}
// FIXME(sean@): Unable to test max_parallel
// if test.max_parallel != cap(c.permitPool) {
// t.Errorf("bad: %v != %v", test.max_parallel, cap(c.permitPool))
// }
}
}
func TestConsulBackend(t *testing.T) {
cleanup, config := consul.PrepareTestContainer(t, "1.4.4", false, true)
defer cleanup()
client, err := api.NewClient(config.APIConfig())
if err != nil {
t.Fatalf("err: %v", err)
}
randPath := fmt.Sprintf("vault-%d/", time.Now().Unix())
defer func() {
client.KV().DeleteTree(randPath, nil)
}()
logger := logging.NewVaultLogger(log.Debug)
b, err := NewConsulBackend(map[string]string{
"address": config.Address(),
"token": config.Token,
"path": randPath,
"max_parallel": "256",
}, logger)
if err != nil {
t.Fatalf("err: %s", err)
}
physical.ExerciseBackend(t, b)
physical.ExerciseBackend_ListPrefix(t, b)
}
func TestConsul_TooLarge(t *testing.T) {
cleanup, config := consul.PrepareTestContainer(t, "1.4.4", false, true)
defer cleanup()
client, err := api.NewClient(config.APIConfig())
if err != nil {
t.Fatalf("err: %v", err)
}
randPath := fmt.Sprintf("vault-%d/", time.Now().Unix())
defer func() {
client.KV().DeleteTree(randPath, nil)
}()
logger := logging.NewVaultLogger(log.Debug)
b, err := NewConsulBackend(map[string]string{
"address": config.Address(),
"token": config.Token,
"path": randPath,
"max_parallel": "256",
}, logger)
if err != nil {
t.Fatalf("err: %s", err)
}
zeros := make([]byte, 600000)
n, err := rand.Read(zeros)
if n != 600000 {
t.Fatalf("expected 500k zeros, read %d", n)
}
if err != nil {
t.Fatal(err)
}
err = b.Put(context.Background(), &physical.Entry{
Key: "foo",
Value: zeros,
})
if err == nil {
t.Fatal("expected error")
}
if !strings.Contains(err.Error(), physical.ErrValueTooLarge) {
t.Fatalf("expected value too large error, got %v", err)
}
err = b.(physical.Transactional).Transaction(context.Background(), []*physical.TxnEntry{
{
Operation: physical.PutOperation,
Entry: &physical.Entry{
Key: "foo",
Value: zeros,
},
},
})
if err == nil {
t.Fatal("expected error")
}
if !strings.Contains(err.Error(), physical.ErrValueTooLarge) {
t.Fatalf("expected value too large error, got %v", err)
}
}
func TestConsul_ExpandedCapabilitiesAvailable(t *testing.T) {
testCases := map[string]bool{
"1.13.5": false,
"1.14.3": true,
}
for version, shouldBeAvailable := range testCases {
t.Run(version, func(t *testing.T) {
cleanup, config := consul.PrepareTestContainer(t, version, false, true)
defer cleanup()
logger := logging.NewVaultLogger(log.Debug)
backendConfig := map[string]string{
"address": config.Address(),
"token": config.Token,
"path": "vault/",
"max_parallel": "-1",
}
be, err := NewConsulBackend(backendConfig, logger)
if err != nil {
t.Fatal(err)
}
b := be.(*ConsulBackend)
isAvailable := b.ExpandedCapabilitiesAvailable(context.Background())
if isAvailable != shouldBeAvailable {
t.Errorf("%t != %t, version %s\n", isAvailable, shouldBeAvailable, version)
}
})
}
}
func TestConsul_TransactionalBackend_GetTransactionsForNonExistentValues(t *testing.T) {
cleanup, config := consul.PrepareTestContainer(t, "1.14.2", false, true)
defer cleanup()
client, err := api.NewClient(config.APIConfig())
if err != nil {
t.Fatal(err)
}
txns := make([]*physical.TxnEntry, 0)
ctx := context.Background()
logger := logging.NewVaultLogger(log.Debug)
backendConfig := map[string]string{
"address": config.Address(),
"token": config.Token,
"path": "vault/",
"max_parallel": "-1",
}
be, err := NewConsulBackend(backendConfig, logger)
if err != nil {
t.Fatal(err)
}
b := be.(*ConsulBackend)
defer func() {
_, _ = client.KV().DeleteTree("foo/", nil)
}()
txns = append(txns, &physical.TxnEntry{
Operation: physical.GetOperation,
Entry: &physical.Entry{
Key: "foo/bar",
},
})
txns = append(txns, &physical.TxnEntry{
Operation: physical.PutOperation,
Entry: &physical.Entry{
Key: "foo/bar",
Value: []byte("baz"),
},
})
err = b.Transaction(ctx, txns)
if err != nil {
t.Fatal(err)
}
// This should return nil, because the key foo/bar didn't exist when we ran that transaction, so the get
// should return nil, and the put always returns nil
for _, txn := range txns {
if txn.Operation == physical.GetOperation {
if txn.Entry.Value != nil {
t.Fatalf("expected txn.entry.value to be nil but it was %q", string(txn.Entry.Value))
}
}
}
}
// TestConsul_TransactionalBackend_GetTransactions tests that passing a slice of transactions to the
// consul backend will populate values for any transactions that are Get operations.
func TestConsul_TransactionalBackend_GetTransactions(t *testing.T) {
cleanup, config := consul.PrepareTestContainer(t, "1.14.2", false, true)
defer cleanup()
client, err := api.NewClient(config.APIConfig())
if err != nil {
t.Fatal(err)
}
txns := make([]*physical.TxnEntry, 0)
ctx := context.Background()
logger := logging.NewVaultLogger(log.Debug)
backendConfig := map[string]string{
"address": config.Address(),
"token": config.Token,
"path": "vault/",
"max_parallel": "-1",
}
be, err := NewConsulBackend(backendConfig, logger)
if err != nil {
t.Fatal(err)
}
b := be.(*ConsulBackend)
defer func() {
_, _ = client.KV().DeleteTree("foo/", nil)
}()
// Add some seed values to consul, and prepare our slice of transactions at the same time
for i := 0; i < 64; i++ {
key := fmt.Sprintf("foo/lol-%d", i)
err := b.Put(ctx, &physical.Entry{Key: key, Value: []byte(fmt.Sprintf("value-%d", i))})
if err != nil {
t.Fatal(err)
}
txns = append(txns, &physical.TxnEntry{
Operation: physical.GetOperation,
Entry: &physical.Entry{
Key: key,
},
})
}
for i := 0; i < 64; i++ {
key := fmt.Sprintf("foo/lol-%d", i)
if i%2 == 0 {
txns = append(txns, &physical.TxnEntry{
Operation: physical.PutOperation,
Entry: &physical.Entry{
Key: key,
Value: []byte("lmao"),
},
})
} else {
txns = append(txns, &physical.TxnEntry{
Operation: physical.DeleteOperation,
Entry: &physical.Entry{
Key: key,
},
})
}
}
if len(txns) != 128 {
t.Fatal("wrong number of transactions")
}
err = b.Transaction(ctx, txns)
if err != nil {
t.Fatal(err)
}
// Check that our Get operations were populated with their values
for i, txn := range txns {
if txn.Operation == physical.GetOperation {
val := []byte(fmt.Sprintf("value-%d", i))
if !bytes.Equal(val, txn.Entry.Value) {
t.Fatalf("expected %s to equal %s but it didn't", hex.EncodeToString(val), hex.EncodeToString(txn.Entry.Value))
}
}
}
}
func TestConsulHABackend(t *testing.T) {
cleanup, config := consul.PrepareTestContainer(t, "1.4.4", false, true)
defer cleanup()
client, err := api.NewClient(config.APIConfig())
if err != nil {
t.Fatalf("err: %v", err)
}
// We used to use a timestamp here but then if you run multiple instances in
// parallel with one Consul they end up conflicting.
randPath := fmt.Sprintf("vault-%d/", rand.Int())
defer func() {
client.KV().DeleteTree(randPath, nil)
}()
logger := logging.NewVaultLogger(log.Debug)
backendConfig := map[string]string{
"address": config.Address(),
"token": config.Token,
"path": randPath,
"max_parallel": "-1",
// We have to wait this out as part of the test so shorten it a little from
// the default 15 seconds helps with test run times, especially when running
// this in a loop to detect flakes!
"lock_wait_time": "3s",
}
b, err := NewConsulBackend(backendConfig, logger)
if err != nil {
t.Fatalf("err: %s", err)
}
b2, err := NewConsulBackend(backendConfig, logger)
if err != nil {
t.Fatalf("err: %s", err)
}
physical.ExerciseHABackend(t, b.(physical.HABackend), b2.(physical.HABackend))
detect, ok := b.(physical.RedirectDetect)
if !ok {
t.Fatalf("consul does not implement RedirectDetect")
}
host, err := detect.DetectHostAddr()
if err != nil {
t.Fatalf("err: %s", err)
}
if host == "" {
t.Fatalf("bad addr: %v", host)
}
// Calling `Info` on a Lock that has been unlocked must still return the old
// sessionID (until it is locked again) otherwise we will fail to fence writes
// that are still in flight from before (e.g. queued WAL or Merkle flushes) as
// soon as the first one unlocks the session allowing corruption again.
l, err := b.(physical.HABackend).LockWith("test-lock-session-info", "bar")
require.NoError(t, err)
expectKey := randPath + "test-lock-session-info"
cl := l.(*ConsulLock)
stopCh := make(chan struct{})
time.AfterFunc(5*time.Second, func() {
close(stopCh)
})
leaderCh, err := cl.Lock(stopCh)
require.NoError(t, err)
require.NotNil(t, leaderCh)
key, sid := cl.Info()
require.Equal(t, expectKey, key)
require.NotEmpty(t, sid)
// Now Unlock the lock, sessionID should be reset to empty string
err = cl.Unlock()
require.NoError(t, err)
key2, sid2 := cl.Info()
require.Equal(t, key, key2)
require.Equal(t, sid, sid2)
// Lock it again, this should cause a new session to be created so SID should
// change.
leaderCh, err = cl.Lock(stopCh)
require.NoError(t, err)
require.NotNil(t, leaderCh)
key3, sid3 := cl.Info()
require.Equal(t, key, key3)
require.NotEqual(t, sid, sid3)
}

View File

@ -1,43 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package consul
import (
"math/rand"
"time"
)
// DurationMinusBuffer returns a duration, minus a buffer and jitter
// subtracted from the duration. This function is used primarily for
// servicing Consul TTL Checks in advance of the TTL.
func DurationMinusBuffer(intv time.Duration, buffer time.Duration, jitter int64) time.Duration {
d := intv - buffer
if jitter == 0 {
d -= RandomStagger(d)
} else {
d -= RandomStagger(time.Duration(int64(d) / jitter))
}
return d
}
// DurationMinusBufferDomain returns the domain of valid durations from a
// call to DurationMinusBuffer. This function is used to check user
// specified input values to DurationMinusBuffer.
func DurationMinusBufferDomain(intv time.Duration, buffer time.Duration, jitter int64) (min time.Duration, max time.Duration) {
max = intv - buffer
if jitter == 0 {
min = max
} else {
min = max - time.Duration(int64(max)/jitter)
}
return min, max
}
// RandomStagger returns an interval between 0 and the duration
func RandomStagger(intv time.Duration) time.Duration {
if intv == 0 {
return 0
}
return time.Duration(uint64(rand.Int63()) % uint64(intv))
}

View File

@ -64,7 +64,6 @@ vault auth enable "radius"
vault auth enable "userpass"
# Enable secrets plugins
vault secrets enable "consul"
vault secrets enable "database"
vault secrets enable "kubernetes"
vault secrets enable -path="kv-v1/" -version=1 "kv"

View File

@ -35,11 +35,6 @@ const MOUNTABLE_SECRET_ENGINES = [
type: 'ad',
category: 'cloud',
},
{
displayName: 'Consul',
type: 'consul',
category: 'infra',
},
{
displayName: 'Databases',
type: 'database',

View File

@ -1,15 +0,0 @@
{{!
Copyright (c) HashiCorp, Inc.
SPDX-License-Identifier: BUSL-1.1
~}}
<WizardSection
@headerText="Consul"
@headerIcon="consul"
@docText="Docs: Consul Secrets"
@docPath="/docs/secrets/consul/index.html"
>
<p>
The Consul Secrets Engine generates Consul API tokens dynamically based on Consul ACL policies.
</p>
</WizardSection>

View File

@ -1 +0,0 @@
<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M23.42 27.224a3.517 3.517 0 1 1-.012-7.034 3.517 3.517 0 0 1 .011 7.034" fill="#961D59"/><path d="M30.302 25.466a1.612 1.612 0 1 1 0-3.225 1.612 1.612 0 0 1 0 3.225M36.54 26.94a1.612 1.612 0 1 1 .032-.132c-.018.04-.018.082-.032.132M35.074 22.784a1.61 1.61 0 1 1-.113-3.159 1.613 1.613 0 0 1 1.31 1.224c.046.206.051.42.018.629a1.55 1.55 0 0 1-1.212 1.306M40.957 26.772a1.603 1.603 0 0 1-1.857 1.307 1.611 1.611 0 0 1-1.3-1.867 1.603 1.603 0 0 1 1.856-1.308 1.618 1.618 0 0 1 1.34 1.71.418.418 0 0 0-.03.144M39.65 22.806a1.612 1.612 0 1 1 1.345-1.44 1.603 1.603 0 0 1-1.344 1.44M38.453 32.256a1.612 1.612 0 1 1 .197-.653c-.012.23-.08.455-.197.653M37.836 17.636a1.611 1.611 0 1 1 .61-2.195c.166.28.236.607.2.93a1.612 1.612 0 0 1-.81 1.265" fill="#D62783"/><path d="M23.702 40.12c-9.062 0-16.409-7.282-16.409-16.267 0-8.984 7.347-16.267 16.409-16.267a16.33 16.33 0 0 1 9.97 3.341l-1.994 2.593a13.198 13.198 0 0 0-13.783-1.339c-4.477 2.194-7.306 6.72-7.297 11.67-.01 4.95 2.82 9.475 7.297 11.67a13.198 13.198 0 0 0 13.783-1.34l1.994 2.596a16.358 16.358 0 0 1-9.97 3.344z" fill="#D62783" fill-rule="nonzero"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB