2017-08-03 17:24:27 +00:00
|
|
|
package consul
|
2015-03-02 18:48:53 +00:00
|
|
|
|
2015-04-03 23:44:32 +00:00
|
|
|
import (
|
2018-01-19 06:44:44 +00:00
|
|
|
"context"
|
2017-02-17 14:15:35 +00:00
|
|
|
"errors"
|
2015-04-03 23:44:32 +00:00
|
|
|
"fmt"
|
2020-01-24 17:42:03 +00:00
|
|
|
"net/http"
|
2015-11-03 20:26:07 +00:00
|
|
|
"strconv"
|
2015-04-03 23:44:32 +00:00
|
|
|
"strings"
|
2015-04-14 18:09:24 +00:00
|
|
|
"time"
|
2015-07-28 09:00:42 +00:00
|
|
|
|
2020-01-24 17:42:03 +00:00
|
|
|
"github.com/armon/go-metrics"
|
2015-04-03 23:44:32 +00:00
|
|
|
"github.com/hashicorp/consul/api"
|
2020-01-24 17:42:03 +00:00
|
|
|
log "github.com/hashicorp/go-hclog"
|
|
|
|
"github.com/hashicorp/go-multierror"
|
2021-07-16 00:17:31 +00:00
|
|
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
|
|
|
"github.com/hashicorp/go-secure-stdlib/tlsutil"
|
2020-01-24 17:42:03 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
2019-04-12 21:54:35 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/physical"
|
2021-04-28 15:55:18 +00:00
|
|
|
"github.com/hashicorp/vault/vault/diagnose"
|
2020-01-24 17:42:03 +00:00
|
|
|
"golang.org/x/net/http2"
|
2015-04-03 23:44:32 +00:00
|
|
|
)
|
|
|
|
|
Teach Vault how to register with Consul
Vault will now register itself with Consul. The active node can be found using `active.vault.service.consul`. All standby vaults are available via `standby.vault.service.consul`. All unsealed vaults are considered healthy and available via `vault.service.consul`. Change in status and registration is event driven and should happen at the speed of a write to Consul (~network RTT + ~1x fsync(2)).
Healthy/active:
```
curl -X GET 'http://127.0.0.1:8500/v1/health/service/vault?pretty' && echo;
[
{
"Node": {
"Node": "vm1",
"Address": "127.0.0.1",
"TaggedAddresses": {
"wan": "127.0.0.1"
},
"CreateIndex": 3,
"ModifyIndex": 20
},
"Service": {
"ID": "vault:127.0.0.1:8200",
"Service": "vault",
"Tags": [
"active"
],
"Address": "127.0.0.1",
"Port": 8200,
"EnableTagOverride": false,
"CreateIndex": 17,
"ModifyIndex": 20
},
"Checks": [
{
"Node": "vm1",
"CheckID": "serfHealth",
"Name": "Serf Health Status",
"Status": "passing",
"Notes": "",
"Output": "Agent alive and reachable",
"ServiceID": "",
"ServiceName": "",
"CreateIndex": 3,
"ModifyIndex": 3
},
{
"Node": "vm1",
"CheckID": "vault-sealed-check",
"Name": "Vault Sealed Status",
"Status": "passing",
"Notes": "Vault service is healthy when Vault is in an unsealed status and can become an active Vault server",
"Output": "",
"ServiceID": "vault:127.0.0.1:8200",
"ServiceName": "vault",
"CreateIndex": 19,
"ModifyIndex": 19
}
]
}
]
```
Healthy/standby:
```
[snip]
"Service": {
"ID": "vault:127.0.0.2:8200",
"Service": "vault",
"Tags": [
"standby"
],
"Address": "127.0.0.2",
"Port": 8200,
"EnableTagOverride": false,
"CreateIndex": 17,
"ModifyIndex": 20
},
"Checks": [
{
"Node": "vm2",
"CheckID": "serfHealth",
"Name": "Serf Health Status",
"Status": "passing",
"Notes": "",
"Output": "Agent alive and reachable",
"ServiceID": "",
"ServiceName": "",
"CreateIndex": 3,
"ModifyIndex": 3
},
{
"Node": "vm2",
"CheckID": "vault-sealed-check",
"Name": "Vault Sealed Status",
"Status": "passing",
"Notes": "Vault service is healthy when Vault is in an unsealed status and can become an active Vault server",
"Output": "",
"ServiceID": "vault:127.0.0.2:8200",
"ServiceName": "vault",
"CreateIndex": 19,
"ModifyIndex": 19
}
]
}
]
```
Sealed:
```
"Checks": [
{
"Node": "vm2",
"CheckID": "serfHealth",
"Name": "Serf Health Status",
"Status": "passing",
"Notes": "",
"Output": "Agent alive and reachable",
"ServiceID": "",
"ServiceName": "",
"CreateIndex": 3,
"ModifyIndex": 3
},
{
"Node": "vm2",
"CheckID": "vault-sealed-check",
"Name": "Vault Sealed Status",
"Status": "critical",
"Notes": "Vault service is healthy when Vault is in an unsealed status and can become an active Vault server",
"Output": "Vault Sealed",
"ServiceID": "vault:127.0.0.2:8200",
"ServiceName": "vault",
"CreateIndex": 19,
"ModifyIndex": 38
}
]
```
2016-04-24 00:15:05 +00:00
|
|
|
const (
|
2017-01-19 22:36:33 +00:00
|
|
|
// 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"
|
Teach Vault how to register with Consul
Vault will now register itself with Consul. The active node can be found using `active.vault.service.consul`. All standby vaults are available via `standby.vault.service.consul`. All unsealed vaults are considered healthy and available via `vault.service.consul`. Change in status and registration is event driven and should happen at the speed of a write to Consul (~network RTT + ~1x fsync(2)).
Healthy/active:
```
curl -X GET 'http://127.0.0.1:8500/v1/health/service/vault?pretty' && echo;
[
{
"Node": {
"Node": "vm1",
"Address": "127.0.0.1",
"TaggedAddresses": {
"wan": "127.0.0.1"
},
"CreateIndex": 3,
"ModifyIndex": 20
},
"Service": {
"ID": "vault:127.0.0.1:8200",
"Service": "vault",
"Tags": [
"active"
],
"Address": "127.0.0.1",
"Port": 8200,
"EnableTagOverride": false,
"CreateIndex": 17,
"ModifyIndex": 20
},
"Checks": [
{
"Node": "vm1",
"CheckID": "serfHealth",
"Name": "Serf Health Status",
"Status": "passing",
"Notes": "",
"Output": "Agent alive and reachable",
"ServiceID": "",
"ServiceName": "",
"CreateIndex": 3,
"ModifyIndex": 3
},
{
"Node": "vm1",
"CheckID": "vault-sealed-check",
"Name": "Vault Sealed Status",
"Status": "passing",
"Notes": "Vault service is healthy when Vault is in an unsealed status and can become an active Vault server",
"Output": "",
"ServiceID": "vault:127.0.0.1:8200",
"ServiceName": "vault",
"CreateIndex": 19,
"ModifyIndex": 19
}
]
}
]
```
Healthy/standby:
```
[snip]
"Service": {
"ID": "vault:127.0.0.2:8200",
"Service": "vault",
"Tags": [
"standby"
],
"Address": "127.0.0.2",
"Port": 8200,
"EnableTagOverride": false,
"CreateIndex": 17,
"ModifyIndex": 20
},
"Checks": [
{
"Node": "vm2",
"CheckID": "serfHealth",
"Name": "Serf Health Status",
"Status": "passing",
"Notes": "",
"Output": "Agent alive and reachable",
"ServiceID": "",
"ServiceName": "",
"CreateIndex": 3,
"ModifyIndex": 3
},
{
"Node": "vm2",
"CheckID": "vault-sealed-check",
"Name": "Vault Sealed Status",
"Status": "passing",
"Notes": "Vault service is healthy when Vault is in an unsealed status and can become an active Vault server",
"Output": "",
"ServiceID": "vault:127.0.0.2:8200",
"ServiceName": "vault",
"CreateIndex": 19,
"ModifyIndex": 19
}
]
}
]
```
Sealed:
```
"Checks": [
{
"Node": "vm2",
"CheckID": "serfHealth",
"Name": "Serf Health Status",
"Status": "passing",
"Notes": "",
"Output": "Agent alive and reachable",
"ServiceID": "",
"ServiceName": "",
"CreateIndex": 3,
"ModifyIndex": 3
},
{
"Node": "vm2",
"CheckID": "vault-sealed-check",
"Name": "Vault Sealed Status",
"Status": "critical",
"Notes": "Vault service is healthy when Vault is in an unsealed status and can become an active Vault server",
"Output": "Vault Sealed",
"ServiceID": "vault:127.0.0.2:8200",
"ServiceName": "vault",
"CreateIndex": 19,
"ModifyIndex": 38
}
]
```
2016-04-24 00:15:05 +00:00
|
|
|
)
|
|
|
|
|
2018-01-20 01:44:24 +00:00
|
|
|
// Verify ConsulBackend satisfies the correct interfaces
|
2021-04-08 16:43:39 +00:00
|
|
|
var (
|
|
|
|
_ physical.Backend = (*ConsulBackend)(nil)
|
|
|
|
_ physical.HABackend = (*ConsulBackend)(nil)
|
|
|
|
_ physical.Lock = (*ConsulLock)(nil)
|
|
|
|
_ physical.Transactional = (*ConsulBackend)(nil)
|
|
|
|
)
|
2018-02-12 21:11:59 +00:00
|
|
|
|
2015-03-02 18:48:53 +00:00
|
|
|
// 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.
|
|
|
|
type ConsulBackend struct {
|
2020-01-24 17:42:03 +00:00
|
|
|
client *api.Client
|
2019-12-06 14:46:39 +00:00
|
|
|
path string
|
|
|
|
kv *api.KV
|
|
|
|
permitPool *physical.PermitPool
|
|
|
|
consistencyMode string
|
2018-04-18 17:09:55 +00:00
|
|
|
|
|
|
|
sessionTTL string
|
|
|
|
lockWaitTime time.Duration
|
2015-03-02 18:48:53 +00:00
|
|
|
}
|
|
|
|
|
2017-08-03 17:24:27 +00:00
|
|
|
// NewConsulBackend constructs a Consul backend using the given API client
|
2015-03-02 18:48:53 +00:00
|
|
|
// and the prefix in the KV store.
|
2017-08-03 17:24:27 +00:00
|
|
|
func NewConsulBackend(conf map[string]string, logger log.Logger) (physical.Backend, error) {
|
2015-04-03 23:44:32 +00:00
|
|
|
// Get the path in Consul
|
|
|
|
path, ok := conf["path"]
|
2015-04-04 00:05:18 +00:00
|
|
|
if !ok {
|
|
|
|
path = "vault/"
|
2015-04-03 23:44:32 +00:00
|
|
|
}
|
2016-08-19 20:45:17 +00:00
|
|
|
if logger.IsDebug() {
|
2018-04-03 00:46:59 +00:00
|
|
|
logger.Debug("config path set", "path", path)
|
2016-08-19 20:45:17 +00:00
|
|
|
}
|
2015-04-04 00:05:18 +00:00
|
|
|
|
|
|
|
// Ensure path is suffixed but not prefixed
|
2015-04-03 23:44:32 +00:00
|
|
|
if !strings.HasSuffix(path, "/") {
|
2018-04-03 00:46:59 +00:00
|
|
|
logger.Warn("appending trailing forward slash to path")
|
2015-04-03 23:44:32 +00:00
|
|
|
path += "/"
|
|
|
|
}
|
2015-04-04 00:05:18 +00:00
|
|
|
if strings.HasPrefix(path, "/") {
|
2018-04-03 00:46:59 +00:00
|
|
|
logger.Warn("trimming path of its forward slash")
|
2015-04-04 00:05:18 +00:00
|
|
|
path = strings.TrimPrefix(path, "/")
|
|
|
|
}
|
2015-04-03 23:44:32 +00:00
|
|
|
|
2018-04-18 17:09:55 +00:00
|
|
|
sessionTTL := api.DefaultLockSessionTTL
|
|
|
|
sessionTTLStr, ok := conf["session_ttl"]
|
|
|
|
if ok {
|
|
|
|
_, err := parseutil.ParseDurationSecond(sessionTTLStr)
|
|
|
|
if err != nil {
|
2021-05-31 16:54:05 +00:00
|
|
|
return nil, fmt.Errorf("invalid session_ttl: %w", err)
|
2018-04-18 17:09:55 +00:00
|
|
|
}
|
|
|
|
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 {
|
2021-05-31 16:54:05 +00:00
|
|
|
return nil, fmt.Errorf("invalid lock_wait_time: %w", err)
|
2018-04-18 17:09:55 +00:00
|
|
|
}
|
|
|
|
lockWaitTime = d
|
|
|
|
if logger.IsDebug() {
|
|
|
|
logger.Debug("config lock_wait_time set", "lock_wait_time", d)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-03 20:26:07 +00:00
|
|
|
maxParStr, ok := conf["max_parallel"]
|
|
|
|
var maxParInt int
|
|
|
|
if ok {
|
2020-01-24 17:42:03 +00:00
|
|
|
maxParInt, err := strconv.Atoi(maxParStr)
|
2015-11-03 20:26:07 +00:00
|
|
|
if err != nil {
|
2021-05-31 16:54:05 +00:00
|
|
|
return nil, fmt.Errorf("failed parsing max_parallel parameter: %w", err)
|
2015-11-03 20:26:07 +00:00
|
|
|
}
|
2016-08-19 20:45:17 +00:00
|
|
|
if logger.IsDebug() {
|
2018-04-03 00:46:59 +00:00
|
|
|
logger.Debug("max_parallel set", "max_parallel", maxParInt)
|
2016-08-19 20:45:17 +00:00
|
|
|
}
|
2015-04-03 23:44:32 +00:00
|
|
|
}
|
|
|
|
|
2017-01-19 22:36:33 +00:00
|
|
|
consistencyMode, ok := conf["consistency_mode"]
|
2017-01-13 17:49:04 +00:00
|
|
|
if ok {
|
2017-01-19 22:36:33 +00:00
|
|
|
switch consistencyMode {
|
|
|
|
case consistencyModeDefault, consistencyModeStrong:
|
|
|
|
default:
|
2018-04-05 15:49:21 +00:00
|
|
|
return nil, fmt.Errorf("invalid consistency_mode value: %q", consistencyMode)
|
2017-01-13 17:49:04 +00:00
|
|
|
}
|
2017-01-19 22:36:33 +00:00
|
|
|
} else {
|
|
|
|
consistencyMode = consistencyModeDefault
|
2017-01-13 17:49:04 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 17:42:03 +00:00
|
|
|
// Configure the client
|
|
|
|
consulConf := api.DefaultConfig()
|
|
|
|
// Set MaxIdleConnsPerHost to the number of processes used in expiration.Restore
|
|
|
|
consulConf.Transport.MaxIdleConnsPerHost = consts.ExpirationRestoreWorkerCount
|
|
|
|
|
2021-12-21 17:38:58 +00:00
|
|
|
if err := SetupSecureTLS(context.Background(), consulConf, conf, logger, false); err != nil {
|
|
|
|
return nil, fmt.Errorf("client setup failed: %w", err)
|
|
|
|
}
|
2021-04-28 15:55:18 +00:00
|
|
|
|
|
|
|
consulConf.HttpClient = &http.Client{Transport: consulConf.Transport}
|
|
|
|
client, err := api.NewClient(consulConf)
|
|
|
|
if err != nil {
|
2021-05-31 16:54:05 +00:00
|
|
|
return nil, fmt.Errorf("client setup failed: %w", err)
|
2021-04-28 15:55:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Setup the backend
|
|
|
|
c := &ConsulBackend{
|
|
|
|
path: path,
|
|
|
|
client: client,
|
|
|
|
kv: client.KV(),
|
|
|
|
permitPool: physical.NewPermitPool(maxParInt),
|
|
|
|
consistencyMode: consistencyMode,
|
|
|
|
|
|
|
|
sessionTTL: sessionTTL,
|
|
|
|
lockWaitTime: lockWaitTime,
|
|
|
|
}
|
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
|
2021-06-15 16:53:29 +00:00
|
|
|
func SetupSecureTLS(ctx context.Context, consulConf *api.Config, conf map[string]string, logger log.Logger, isDiagnose bool) error {
|
2020-01-24 17:42:03 +00:00
|
|
|
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" {
|
2021-04-28 15:55:18 +00:00
|
|
|
if isDiagnose {
|
|
|
|
certPath, okCert := conf["tls_cert_file"]
|
|
|
|
keyPath, okKey := conf["tls_key_file"]
|
|
|
|
if okCert && okKey {
|
2021-06-15 16:53:29 +00:00
|
|
|
warnings, err := diagnose.TLSFileChecks(certPath, keyPath)
|
|
|
|
for _, warning := range warnings {
|
|
|
|
diagnose.Warn(ctx, warning)
|
|
|
|
}
|
2021-04-28 15:55:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-06-15 16:53:29 +00:00
|
|
|
return nil
|
2021-04-28 15:55:18 +00:00
|
|
|
}
|
2021-06-15 16:53:29 +00:00
|
|
|
return fmt.Errorf("key or cert path: %s, %s, cannot be loaded from consul config file", certPath, keyPath)
|
2021-04-28 15:55:18 +00:00
|
|
|
}
|
|
|
|
|
2020-01-24 17:42:03 +00:00
|
|
|
// Use the parsed Address instead of the raw conf['address']
|
|
|
|
tlsClientConfig, err := tlsutil.SetupTLSConfig(conf, consulConf.Address)
|
|
|
|
if err != nil {
|
2021-04-28 15:55:18 +00:00
|
|
|
return err
|
2020-01-24 17:42:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
consulConf.Transport.TLSClientConfig = tlsClientConfig
|
|
|
|
if err := http2.ConfigureTransport(consulConf.Transport); err != nil {
|
2021-04-28 15:55:18 +00:00
|
|
|
return err
|
2020-01-24 17:42:03 +00:00
|
|
|
}
|
|
|
|
logger.Debug("configured TLS")
|
2021-07-07 18:35:25 +00:00
|
|
|
} else {
|
|
|
|
if isDiagnose {
|
|
|
|
diagnose.Skipped(ctx, "HTTPS is not used, Skipping TLS verification.")
|
|
|
|
}
|
2020-01-24 17:42:03 +00:00
|
|
|
}
|
2021-04-28 15:55:18 +00:00
|
|
|
return nil
|
2015-07-28 09:00:42 +00:00
|
|
|
}
|
|
|
|
|
2017-02-17 14:15:35 +00:00
|
|
|
// Used to run multiple entries via a transaction
|
2018-01-19 06:44:44 +00:00
|
|
|
func (c *ConsulBackend) Transaction(ctx context.Context, txns []*physical.TxnEntry) error {
|
2017-02-17 14:15:35 +00:00
|
|
|
if len(txns) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
2020-12-03 19:19:08 +00:00
|
|
|
defer metrics.MeasureSince([]string{"consul", "transaction"}, time.Now())
|
2017-02-17 14:15:35 +00:00
|
|
|
|
|
|
|
ops := make([]*api.KVTxnOp, 0, len(txns))
|
|
|
|
|
|
|
|
for _, op := range txns {
|
|
|
|
cop := &api.KVTxnOp{
|
|
|
|
Key: c.path + op.Entry.Key,
|
|
|
|
}
|
|
|
|
switch op.Operation {
|
2017-08-03 17:24:27 +00:00
|
|
|
case physical.DeleteOperation:
|
2017-02-17 14:15:35 +00:00
|
|
|
cop.Verb = api.KVDelete
|
2017-08-03 17:24:27 +00:00
|
|
|
case physical.PutOperation:
|
2017-02-17 14:15:35 +00:00
|
|
|
cop.Verb = api.KVSet
|
|
|
|
cop.Value = op.Entry.Value
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("%q is not a supported transaction operation", op.Operation)
|
|
|
|
}
|
|
|
|
|
|
|
|
ops = append(ops, cop)
|
|
|
|
}
|
|
|
|
|
2017-03-09 17:59:35 +00:00
|
|
|
c.permitPool.Acquire()
|
|
|
|
defer c.permitPool.Release()
|
|
|
|
|
2018-06-11 15:03:00 +00:00
|
|
|
queryOpts := &api.QueryOptions{}
|
|
|
|
queryOpts = queryOpts.WithContext(ctx)
|
|
|
|
|
|
|
|
ok, resp, _, err := c.kv.Txn(ops, queryOpts)
|
2017-02-17 14:15:35 +00:00
|
|
|
if err != nil {
|
2019-05-01 17:47:41 +00:00
|
|
|
if strings.Contains(err.Error(), "is too large") {
|
2021-05-31 16:54:05 +00:00
|
|
|
return fmt.Errorf("%s: %w", physical.ErrValueTooLarge, err)
|
2019-05-01 17:47:41 +00:00
|
|
|
}
|
2017-02-17 14:15:35 +00:00
|
|
|
return err
|
|
|
|
}
|
2017-10-23 20:42:56 +00:00
|
|
|
if ok && len(resp.Errors) == 0 {
|
2017-02-17 14:15:35 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var retErr *multierror.Error
|
|
|
|
for _, res := range resp.Errors {
|
|
|
|
retErr = multierror.Append(retErr, errors.New(res.What))
|
|
|
|
}
|
|
|
|
|
|
|
|
return retErr
|
|
|
|
}
|
|
|
|
|
2015-04-03 23:44:32 +00:00
|
|
|
// Put is used to insert or update an entry
|
2018-01-19 06:44:44 +00:00
|
|
|
func (c *ConsulBackend) Put(ctx context.Context, entry *physical.Entry) error {
|
2015-04-14 18:09:24 +00:00
|
|
|
defer metrics.MeasureSince([]string{"consul", "put"}, time.Now())
|
2017-02-17 14:15:35 +00:00
|
|
|
|
|
|
|
c.permitPool.Acquire()
|
|
|
|
defer c.permitPool.Release()
|
|
|
|
|
2015-04-03 23:44:32 +00:00
|
|
|
pair := &api.KVPair{
|
|
|
|
Key: c.path + entry.Key,
|
|
|
|
Value: entry.Value,
|
|
|
|
}
|
2015-11-03 16:47:16 +00:00
|
|
|
|
2018-06-11 15:03:00 +00:00
|
|
|
writeOpts := &api.WriteOptions{}
|
|
|
|
writeOpts = writeOpts.WithContext(ctx)
|
|
|
|
|
|
|
|
_, err := c.kv.Put(pair, writeOpts)
|
2019-05-01 17:47:41 +00:00
|
|
|
if err != nil {
|
|
|
|
if strings.Contains(err.Error(), "Value exceeds") {
|
2021-05-31 16:54:05 +00:00
|
|
|
return fmt.Errorf("%s: %w", physical.ErrValueTooLarge, err)
|
2019-05-01 17:47:41 +00:00
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
2015-04-03 23:44:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get is used to fetch an entry
|
2018-01-19 06:44:44 +00:00
|
|
|
func (c *ConsulBackend) Get(ctx context.Context, key string) (*physical.Entry, error) {
|
2015-04-14 18:09:24 +00:00
|
|
|
defer metrics.MeasureSince([]string{"consul", "get"}, time.Now())
|
2015-11-03 16:47:16 +00:00
|
|
|
|
|
|
|
c.permitPool.Acquire()
|
|
|
|
defer c.permitPool.Release()
|
|
|
|
|
2018-06-11 15:03:00 +00:00
|
|
|
queryOpts := &api.QueryOptions{}
|
|
|
|
queryOpts = queryOpts.WithContext(ctx)
|
|
|
|
|
2017-01-19 22:36:33 +00:00
|
|
|
if c.consistencyMode == consistencyModeStrong {
|
2018-06-11 15:03:00 +00:00
|
|
|
queryOpts.RequireConsistent = true
|
2017-01-13 17:49:04 +00:00
|
|
|
}
|
|
|
|
|
2018-06-11 15:03:00 +00:00
|
|
|
pair, _, err := c.kv.Get(c.path+key, queryOpts)
|
2015-04-03 23:44:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if pair == nil {
|
|
|
|
return nil, nil
|
|
|
|
}
|
2017-08-03 17:24:27 +00:00
|
|
|
ent := &physical.Entry{
|
2015-04-03 23:44:32 +00:00
|
|
|
Key: key,
|
|
|
|
Value: pair.Value,
|
|
|
|
}
|
|
|
|
return ent, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete is used to permanently delete an entry
|
2018-01-19 06:44:44 +00:00
|
|
|
func (c *ConsulBackend) Delete(ctx context.Context, key string) error {
|
2015-04-14 18:09:24 +00:00
|
|
|
defer metrics.MeasureSince([]string{"consul", "delete"}, time.Now())
|
2015-11-03 16:47:16 +00:00
|
|
|
|
|
|
|
c.permitPool.Acquire()
|
|
|
|
defer c.permitPool.Release()
|
|
|
|
|
2018-06-11 15:03:00 +00:00
|
|
|
writeOpts := &api.WriteOptions{}
|
|
|
|
writeOpts = writeOpts.WithContext(ctx)
|
|
|
|
|
|
|
|
_, err := c.kv.Delete(c.path+key, writeOpts)
|
2015-04-03 23:44:32 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-05-09 06:37:16 +00:00
|
|
|
// List is used to list all the keys under a given
|
2015-04-03 23:44:32 +00:00
|
|
|
// prefix, up to the next prefix.
|
2018-01-19 06:44:44 +00:00
|
|
|
func (c *ConsulBackend) List(ctx context.Context, prefix string) ([]string, error) {
|
2015-04-14 18:09:24 +00:00
|
|
|
defer metrics.MeasureSince([]string{"consul", "list"}, time.Now())
|
2015-04-03 23:44:32 +00:00
|
|
|
scan := c.path + prefix
|
2015-11-03 16:47:16 +00:00
|
|
|
|
2016-01-19 22:05:01 +00:00
|
|
|
// 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]
|
|
|
|
}
|
|
|
|
|
2015-11-03 16:47:16 +00:00
|
|
|
c.permitPool.Acquire()
|
|
|
|
defer c.permitPool.Release()
|
|
|
|
|
2018-06-11 15:03:00 +00:00
|
|
|
queryOpts := &api.QueryOptions{}
|
|
|
|
queryOpts = queryOpts.WithContext(ctx)
|
|
|
|
|
|
|
|
out, _, err := c.kv.Keys(scan, "/", queryOpts)
|
2015-04-03 23:44:32 +00:00
|
|
|
for idx, val := range out {
|
|
|
|
out[idx] = strings.TrimPrefix(val, scan)
|
|
|
|
}
|
2016-01-19 22:05:01 +00:00
|
|
|
|
2015-04-03 23:44:32 +00:00
|
|
|
return out, err
|
2015-03-02 18:48:53 +00:00
|
|
|
}
|
2015-04-14 18:49:46 +00:00
|
|
|
|
|
|
|
// Lock is used for mutual exclusion based on the given key.
|
2017-08-03 17:24:27 +00:00
|
|
|
func (c *ConsulBackend) LockWith(key, value string) (physical.Lock, error) {
|
2015-04-14 18:49:46 +00:00
|
|
|
// Create the lock
|
|
|
|
opts := &api.LockOptions{
|
2015-12-01 05:08:14 +00:00
|
|
|
Key: c.path + key,
|
|
|
|
Value: []byte(value),
|
|
|
|
SessionName: "Vault Lock",
|
|
|
|
MonitorRetries: 5,
|
2018-04-18 17:09:55 +00:00
|
|
|
SessionTTL: c.sessionTTL,
|
|
|
|
LockWaitTime: c.lockWaitTime,
|
2015-04-14 18:49:46 +00:00
|
|
|
}
|
2020-01-24 17:42:03 +00:00
|
|
|
lock, err := c.client.LockOpts(opts)
|
2015-04-14 18:49:46 +00:00
|
|
|
if err != nil {
|
2021-05-31 16:54:05 +00:00
|
|
|
return nil, fmt.Errorf("failed to create lock: %w", err)
|
2015-04-14 18:49:46 +00:00
|
|
|
}
|
2015-04-14 23:36:53 +00:00
|
|
|
cl := &ConsulLock{
|
2020-01-24 17:42:03 +00:00
|
|
|
client: c.client,
|
2017-01-19 22:36:33 +00:00
|
|
|
key: c.path + key,
|
|
|
|
lock: lock,
|
|
|
|
consistencyMode: c.consistencyMode,
|
2015-04-14 23:36:53 +00:00
|
|
|
}
|
|
|
|
return cl, nil
|
|
|
|
}
|
|
|
|
|
2016-07-18 17:19:58 +00:00
|
|
|
// HAEnabled indicates whether the HA functionality should be exposed.
|
|
|
|
// Currently always returns true.
|
|
|
|
func (c *ConsulBackend) HAEnabled() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2015-05-02 22:34:39 +00:00
|
|
|
// DetectHostAddr is used to detect the host address by asking the Consul agent
|
|
|
|
func (c *ConsulBackend) DetectHostAddr() (string, error) {
|
2020-01-24 17:42:03 +00:00
|
|
|
agent := c.client.Agent()
|
2015-05-02 22:34:39 +00:00
|
|
|
self, err := agent.Self()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2016-04-24 00:17:07 +00:00
|
|
|
addr, ok := self["Member"]["Addr"].(string)
|
|
|
|
if !ok {
|
2018-04-05 15:49:21 +00:00
|
|
|
return "", fmt.Errorf("unable to convert an address to string")
|
2016-04-24 00:17:07 +00:00
|
|
|
}
|
2015-05-02 22:34:39 +00:00
|
|
|
return addr, nil
|
|
|
|
}
|
|
|
|
|
2015-04-14 23:36:53 +00:00
|
|
|
// ConsulLock is used to provide the Lock interface backed by Consul
|
|
|
|
type ConsulLock struct {
|
2017-01-19 22:36:33 +00:00
|
|
|
client *api.Client
|
|
|
|
key string
|
|
|
|
lock *api.Lock
|
|
|
|
consistencyMode string
|
2015-04-14 23:36:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *ConsulLock) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) {
|
|
|
|
return c.lock.Lock(stopCh)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *ConsulLock) Unlock() error {
|
|
|
|
return c.lock.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *ConsulLock) Value() (bool, string, error) {
|
|
|
|
kv := c.client.KV()
|
2015-11-03 16:47:16 +00:00
|
|
|
|
2017-01-13 20:22:14 +00:00
|
|
|
var queryOptions *api.QueryOptions
|
2017-01-19 22:36:33 +00:00
|
|
|
if c.consistencyMode == consistencyModeStrong {
|
2017-01-13 20:22:14 +00:00
|
|
|
queryOptions = &api.QueryOptions{
|
2017-01-19 22:36:33 +00:00
|
|
|
RequireConsistent: true,
|
2017-01-13 20:22:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pair, _, err := kv.Get(c.key, queryOptions)
|
2015-04-14 23:36:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return false, "", err
|
|
|
|
}
|
|
|
|
if pair == nil {
|
|
|
|
return false, "", nil
|
|
|
|
}
|
|
|
|
held := pair.Session != ""
|
|
|
|
value := string(pair.Value)
|
|
|
|
return held, value, nil
|
2015-04-14 18:49:46 +00:00
|
|
|
}
|