Merge pull request #1655 from hashicorp/cluster-id
Vault cluster name and ID
This commit is contained in:
commit
c7bcaa5bb6
|
@ -187,6 +187,7 @@ func (c *ServerCommand) Run(args []string) int {
|
|||
DisableMlock: config.DisableMlock,
|
||||
MaxLeaseTTL: config.MaxLeaseTTL,
|
||||
DefaultLeaseTTL: config.DefaultLeaseTTL,
|
||||
ClusterName: config.ClusterName,
|
||||
}
|
||||
|
||||
// Initialize the separate HA physical backend, if it exists
|
||||
|
|
|
@ -33,6 +33,8 @@ type Config struct {
|
|||
MaxLeaseTTLRaw string `hcl:"max_lease_ttl"`
|
||||
DefaultLeaseTTL time.Duration `hcl:"-"`
|
||||
DefaultLeaseTTLRaw string `hcl:"default_lease_ttl"`
|
||||
|
||||
ClusterName string `hcl:"cluster_name"`
|
||||
}
|
||||
|
||||
// DevConfig is a Config that is used for dev mode of Vault.
|
||||
|
@ -210,6 +212,11 @@ func (c *Config) Merge(c2 *Config) *Config {
|
|||
result.DefaultLeaseTTL = c2.DefaultLeaseTTL
|
||||
}
|
||||
|
||||
result.ClusterName = c.ClusterName
|
||||
if c2.ClusterName != "" {
|
||||
result.ClusterName = c2.ClusterName
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -277,6 +284,7 @@ func ParseConfig(d string) (*Config, error) {
|
|||
"telemetry",
|
||||
"default_lease_ttl",
|
||||
"max_lease_ttl",
|
||||
"cluster_name",
|
||||
|
||||
// TODO: Remove in 0.6.0
|
||||
// Deprecated keys
|
||||
|
|
|
@ -61,6 +61,7 @@ func TestLoadConfigFile(t *testing.T) {
|
|||
MaxLeaseTTLRaw: "10h",
|
||||
DefaultLeaseTTL: 10 * time.Hour,
|
||||
DefaultLeaseTTLRaw: "10h",
|
||||
ClusterName: "testcluster",
|
||||
}
|
||||
if !reflect.DeepEqual(config, expected) {
|
||||
t.Fatalf("expected \n\n%#v\n\n to be \n\n%#v\n\n", config, expected)
|
||||
|
@ -120,6 +121,7 @@ func TestLoadConfigFile_json(t *testing.T) {
|
|||
MaxLeaseTTLRaw: "10h",
|
||||
DefaultLeaseTTL: 10 * time.Hour,
|
||||
DefaultLeaseTTLRaw: "10h",
|
||||
ClusterName: "testcluster",
|
||||
}
|
||||
if !reflect.DeepEqual(config, expected) {
|
||||
t.Fatalf("expected \n\n%#v\n\n to be \n\n%#v\n\n", config, expected)
|
||||
|
@ -218,6 +220,7 @@ func TestLoadConfigDir(t *testing.T) {
|
|||
|
||||
MaxLeaseTTL: 10 * time.Hour,
|
||||
DefaultLeaseTTL: 10 * time.Hour,
|
||||
ClusterName: "testcluster",
|
||||
}
|
||||
if !reflect.DeepEqual(config, expected) {
|
||||
t.Fatalf("bad: %#v", config)
|
||||
|
|
|
@ -5,3 +5,4 @@ telemetry {
|
|||
}
|
||||
|
||||
default_lease_ttl = "10h"
|
||||
cluster_name = "testcluster"
|
||||
|
|
|
@ -26,3 +26,4 @@ ha_backend "consul" {
|
|||
|
||||
max_lease_ttl = "10h"
|
||||
default_lease_ttl = "10h"
|
||||
cluster_name = "testcluster"
|
||||
|
|
|
@ -20,5 +20,6 @@
|
|||
"statsite_address": "baz"
|
||||
},
|
||||
"max_lease_ttl": "10h",
|
||||
"default_lease_ttl": "10h"
|
||||
"default_lease_ttl": "10h",
|
||||
"cluster_name":"testcluster"
|
||||
}
|
||||
|
|
|
@ -114,6 +114,21 @@ func getSysHealth(core *vault.Core, r *http.Request) (int, *HealthResponse, erro
|
|||
code = standbyCode
|
||||
}
|
||||
|
||||
// Fetch the local cluster name and identifier
|
||||
var clusterName, clusterID string
|
||||
if !sealed {
|
||||
cluster, err := core.Cluster()
|
||||
|
||||
// Don't set the cluster details in the health status when Vault is sealed
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, nil, err
|
||||
}
|
||||
if cluster != nil {
|
||||
clusterName = cluster.Name
|
||||
clusterID = cluster.ID
|
||||
}
|
||||
}
|
||||
|
||||
// Format the body
|
||||
body := &HealthResponse{
|
||||
Initialized: init,
|
||||
|
@ -121,6 +136,8 @@ func getSysHealth(core *vault.Core, r *http.Request) (int, *HealthResponse, erro
|
|||
Standby: standby,
|
||||
ServerTimeUTC: time.Now().UTC().Unix(),
|
||||
Version: version.GetVersion().String(),
|
||||
ClusterName: clusterName,
|
||||
ClusterID: clusterID,
|
||||
}
|
||||
return code, body, nil
|
||||
}
|
||||
|
@ -131,4 +148,6 @@ type HealthResponse struct {
|
|||
Standby bool `json:"standby"`
|
||||
ServerTimeUTC int64 `json:"server_time_utc"`
|
||||
Version string `json:"version"`
|
||||
ClusterName string `json:"cluster_name,omitempty"`
|
||||
ClusterID string `json:"cluster_id,omitempty"`
|
||||
}
|
||||
|
|
|
@ -31,6 +31,8 @@ func TestSysHealth_get(t *testing.T) {
|
|||
testResponseBody(t, resp, &actual)
|
||||
expected["server_time_utc"] = actual["server_time_utc"]
|
||||
expected["version"] = actual["version"]
|
||||
expected["cluster_name"] = actual["cluster_name"]
|
||||
expected["cluster_id"] = actual["cluster_id"]
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: expected:%#v\nactual:%#v", expected, actual)
|
||||
}
|
||||
|
@ -52,6 +54,8 @@ func TestSysHealth_get(t *testing.T) {
|
|||
testResponseBody(t, resp, &actual)
|
||||
expected["server_time_utc"] = actual["server_time_utc"]
|
||||
expected["version"] = actual["version"]
|
||||
expected["cluster_name"] = actual["cluster_name"]
|
||||
expected["cluster_id"] = actual["cluster_id"]
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: expected:%#v\nactual:%#v", expected, actual)
|
||||
}
|
||||
|
@ -82,6 +86,8 @@ func TestSysHealth_customcodes(t *testing.T) {
|
|||
|
||||
expected["server_time_utc"] = actual["server_time_utc"]
|
||||
expected["version"] = actual["version"]
|
||||
expected["cluster_name"] = actual["cluster_name"]
|
||||
expected["cluster_id"] = actual["cluster_id"]
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: expected:%#v\nactual:%#v", expected, actual)
|
||||
}
|
||||
|
@ -107,6 +113,8 @@ func TestSysHealth_customcodes(t *testing.T) {
|
|||
testResponseBody(t, resp, &actual)
|
||||
expected["server_time_utc"] = actual["server_time_utc"]
|
||||
expected["version"] = actual["version"]
|
||||
expected["cluster_name"] = actual["cluster_name"]
|
||||
expected["cluster_id"] = actual["cluster_id"]
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: expected:%#v\nactual:%#v", expected, actual)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package http
|
|||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
@ -173,7 +172,6 @@ func TestSysUnseal_Reset(t *testing.T) {
|
|||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("\nexpected:\n%#v\nactual:\n%#v\n", expected, actual)
|
||||
}
|
||||
log.Printf("reached here\n")
|
||||
}
|
||||
|
||||
resp = testHttpPut(t, "", addr+"/v1/sys/unseal", map[string]interface{}{
|
||||
|
|
94
vault/cluster.go
Normal file
94
vault/cluster.go
Normal file
|
@ -0,0 +1,94 @@
|
|||
package vault
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/helper/jsonutil"
|
||||
)
|
||||
|
||||
const (
|
||||
// Storage path where the local cluster name and identifier are stored
|
||||
coreClusterPath = "core/cluster/local"
|
||||
)
|
||||
|
||||
// Structure representing the storage entry that holds cluster information
|
||||
type Cluster struct {
|
||||
// Name of the cluster
|
||||
Name string `json:"name" structs:"name" mapstructure:"name"`
|
||||
|
||||
// Identifier of the cluster
|
||||
ID string `json:"id" structs:"id" mapstructure:"id"`
|
||||
}
|
||||
|
||||
// Cluster fetches the details of either local or global cluster based on the
|
||||
// input. This method errors out when Vault is sealed.
|
||||
func (c *Core) Cluster() (*Cluster, error) {
|
||||
// Fetch the storage entry. This call fails when Vault is sealed.
|
||||
entry, err := c.barrier.Get(coreClusterPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if entry == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Decode the cluster information
|
||||
var cluster Cluster
|
||||
if err = jsonutil.DecodeJSON(entry.Value, &cluster); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode cluster details: %v", err)
|
||||
}
|
||||
|
||||
return &cluster, nil
|
||||
}
|
||||
|
||||
// setupCluster creates storage entries for holding Vault cluster information.
|
||||
// Entries will be created only if they are not already present. If clusterName
|
||||
// is not supplied, this method will auto-generate it.
|
||||
func (c *Core) setupCluster() error {
|
||||
// Check if storage index is already present or not
|
||||
cluster, err := c.Cluster()
|
||||
if err != nil {
|
||||
c.logger.Printf("[ERR] core: failed to get cluster details: %v", err)
|
||||
return err
|
||||
}
|
||||
if cluster != nil {
|
||||
// If index is already present, don't update it
|
||||
return nil
|
||||
}
|
||||
|
||||
// If cluster name is not supplied, generate one
|
||||
if c.clusterName == "" {
|
||||
clusterNameBytes, err := uuid.GenerateRandomBytes(4)
|
||||
if err != nil {
|
||||
c.logger.Printf("[ERR] core: failed to generate cluster name: %v", err)
|
||||
return err
|
||||
}
|
||||
c.clusterName = fmt.Sprintf("vault-cluster-%08x", clusterNameBytes)
|
||||
}
|
||||
|
||||
// Generate a clusterID
|
||||
clusterID, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
c.logger.Printf("[ERR] core: failed to generate cluster identifier: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Encode the cluster information into as a JSON string
|
||||
rawCluster, err := json.Marshal(&Cluster{
|
||||
Name: c.clusterName,
|
||||
ID: clusterID,
|
||||
})
|
||||
if err != nil {
|
||||
c.logger.Printf("[ERR] core: failed to encode cluster details: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Store it
|
||||
return c.barrier.Put(&Entry{
|
||||
Key: coreClusterPath,
|
||||
Value: rawCluster,
|
||||
})
|
||||
|
||||
}
|
14
vault/cluster_test.go
Normal file
14
vault/cluster_test.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package vault
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCluster(t *testing.T) {
|
||||
c, _, _ := TestCoreUnsealed(t)
|
||||
cluster, err := c.Cluster()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cluster == nil || cluster.Name == "" || cluster.ID == "" {
|
||||
t.Fatalf("cluster information missing: cluster:%#v", cluster)
|
||||
}
|
||||
}
|
|
@ -218,23 +218,44 @@ type Core struct {
|
|||
|
||||
// cachingDisabled indicates whether caches are disabled
|
||||
cachingDisabled bool
|
||||
|
||||
clusterName string
|
||||
}
|
||||
|
||||
// CoreConfig is used to parameterize a core
|
||||
type CoreConfig struct {
|
||||
LogicalBackends map[string]logical.Factory
|
||||
CredentialBackends map[string]logical.Factory
|
||||
AuditBackends map[string]audit.Factory
|
||||
Physical physical.Backend
|
||||
HAPhysical physical.HABackend // May be nil, which disables HA operations
|
||||
Seal Seal
|
||||
Logger *log.Logger
|
||||
DisableCache bool // Disables the LRU cache on the physical backend
|
||||
DisableMlock bool // Disables mlock syscall
|
||||
CacheSize int // Custom cache size of zero for default
|
||||
AdvertiseAddr string // Set as the leader address for HA
|
||||
DefaultLeaseTTL time.Duration
|
||||
MaxLeaseTTL time.Duration
|
||||
LogicalBackends map[string]logical.Factory `json:"logical_backends" structs:"logical_backends" mapstructure:"logical_backends"`
|
||||
|
||||
CredentialBackends map[string]logical.Factory `json:"credential_backends" structs:"credential_backends" mapstructure:"credential_backends"`
|
||||
|
||||
AuditBackends map[string]audit.Factory `json:"audit_backends" structs:"audit_backends" mapstructure:"audit_backends"`
|
||||
|
||||
Physical physical.Backend `json:"physical" structs:"physical" mapstructure:"physical"`
|
||||
|
||||
// May be nil, which disables HA operations
|
||||
HAPhysical physical.HABackend `json:"ha_physical" structs:"ha_physical" mapstructure:"ha_physical"`
|
||||
|
||||
Seal Seal `json:"seal" structs:"seal" mapstructure:"seal"`
|
||||
|
||||
Logger *log.Logger `json:"logger" structs:"logger" mapstructure:"logger"`
|
||||
|
||||
// Disables the LRU cache on the physical backend
|
||||
DisableCache bool `json:"disable_cache" structs:"disable_cache" mapstructure:"disable_cache"`
|
||||
|
||||
// Disables mlock syscall
|
||||
DisableMlock bool `json:"disable_mlock" structs:"disable_mlock" mapstructure:"disable_mlock"`
|
||||
|
||||
// Custom cache size of zero for default
|
||||
CacheSize int `json:"cache_size" structs:"cache_size" mapstructure:"cache_size"`
|
||||
|
||||
// Set as the leader address for HA
|
||||
AdvertiseAddr string `json:"advertise_addr" structs:"advertise_addr" mapstructure:"advertise_addr"`
|
||||
|
||||
DefaultLeaseTTL time.Duration `json:"default_lease_ttl" structs:"default_lease_ttl" mapstructure:"default_lease_ttl"`
|
||||
|
||||
MaxLeaseTTL time.Duration `json:"max_lease_ttl" structs:"max_lease_ttl" mapstructure:"max_lease_ttl"`
|
||||
|
||||
ClusterName string `json:"cluster_name" structs:"cluster_name" mapstructure:"cluster_name"`
|
||||
}
|
||||
|
||||
// NewCore is used to construct a new core
|
||||
|
@ -315,6 +336,7 @@ func NewCore(conf *CoreConfig) (*Core, error) {
|
|||
defaultLeaseTTL: conf.DefaultLeaseTTL,
|
||||
maxLeaseTTL: conf.MaxLeaseTTL,
|
||||
cachingDisabled: conf.DisableCache,
|
||||
clusterName: conf.ClusterName,
|
||||
}
|
||||
|
||||
if conf.HAPhysical != nil && conf.HAPhysical.HAEnabled() {
|
||||
|
@ -970,6 +992,9 @@ func (c *Core) postUnseal() (retErr error) {
|
|||
if err := c.setupAudits(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.setupCluster(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.metricsCh = make(chan struct{})
|
||||
go c.emitMetrics(c.metricsCh)
|
||||
c.logger.Printf("[INFO] core: post-unseal setup complete")
|
||||
|
|
|
@ -54,8 +54,10 @@ description: |-
|
|||
|
||||
```javascript
|
||||
{
|
||||
"version": "Vault v0.6.1-dev ('418257d27c67fabc4fdd831a6a750d54d8bed76f+CHANGES')",
|
||||
"server_time_utc": 1469226751,
|
||||
"cluster_id": "c9abceea-4f46-4dab-a688-5ce55f89e228",
|
||||
"cluster_name": "vault-cluster-5515c810",
|
||||
"version": "Vault v0.6.1-dev ('f76c926b0a36e55e71190eb3e2da312f29aca54e+CHANGES')",
|
||||
"server_time_utc": 1469555798,
|
||||
"standby": false,
|
||||
"sealed": false,
|
||||
"initialized": true
|
||||
|
|
Loading…
Reference in a new issue