From f56d470929512d509913ef74e13dc6137ed972c8 Mon Sep 17 00:00:00 2001 From: Mark Gritter Date: Wed, 20 Jan 2021 14:04:24 -0600 Subject: [PATCH 1/3] Implement sys/seal-status and sys/leader in system backend (#10725) * Implement sys/seal-status and sys/leader as normal API calls (so that they can be used in namespaces.) * Added changelog. --- changelog/10725.txt | 3 + http/sys_leader.go | 39 +------- http/sys_seal.go | 80 +---------------- vault/logical_system.go | 164 ++++++++++++++++++++++++++++++++++ vault/logical_system_paths.go | 52 ++++++----- 5 files changed, 201 insertions(+), 137 deletions(-) create mode 100644 changelog/10725.txt diff --git a/changelog/10725.txt b/changelog/10725.txt new file mode 100644 index 000000000000..e7bb8fbefabf --- /dev/null +++ b/changelog/10725.txt @@ -0,0 +1,3 @@ +```release-note: improvement +core (enterprise): "vault status" command works when a namespace is set. +``` diff --git a/http/sys_leader.go b/http/sys_leader.go index 2772d98f0fd3..8c2ce21e5001 100644 --- a/http/sys_leader.go +++ b/http/sys_leader.go @@ -3,10 +3,11 @@ package http import ( "net/http" - "github.com/hashicorp/errwrap" "github.com/hashicorp/vault/vault" ) +// This endpoint is needed to answer queries before Vault unseals +// or becomes the leader. func handleSysLeader(core *vault.Core) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method { @@ -19,44 +20,10 @@ func handleSysLeader(core *vault.Core) http.Handler { } func handleSysLeaderGet(core *vault.Core, w http.ResponseWriter, r *http.Request) { - haEnabled := true - isLeader, address, clusterAddr, err := core.Leader() - if errwrap.Contains(err, vault.ErrHANotEnabled.Error()) { - haEnabled = false - err = nil - } + resp, err := core.GetLeaderStatus() if err != nil { respondError(w, http.StatusInternalServerError, err) return } - resp := &LeaderResponse{ - HAEnabled: haEnabled, - IsSelf: isLeader, - LeaderAddress: address, - LeaderClusterAddress: clusterAddr, - PerfStandby: core.PerfStandby(), - } - if resp.PerfStandby { - resp.PerfStandbyLastRemoteWAL = vault.LastRemoteWAL(core) - } else if isLeader || !haEnabled { - resp.LastWAL = vault.LastWAL(core) - } - - resp.RaftCommittedIndex, resp.RaftAppliedIndex = core.GetRaftIndexes() - respondOk(w, resp) } - -type LeaderResponse struct { - HAEnabled bool `json:"ha_enabled"` - IsSelf bool `json:"is_self"` - LeaderAddress string `json:"leader_address"` - LeaderClusterAddress string `json:"leader_cluster_address"` - PerfStandby bool `json:"performance_standby"` - PerfStandbyLastRemoteWAL uint64 `json:"performance_standby_last_remote_wal"` - LastWAL uint64 `json:"last_wal,omitempty"` - - // Raft Indexes for this node - RaftCommittedIndex uint64 `json:"raft_committed_index,omitempty"` - RaftAppliedIndex uint64 `json:"raft_applied_index,omitempty"` -} diff --git a/http/sys_seal.go b/http/sys_seal.go index 7b86e43645f7..24f491b65d1d 100644 --- a/http/sys_seal.go +++ b/http/sys_seal.go @@ -5,13 +5,11 @@ import ( "encoding/base64" "encoding/hex" "errors" - "fmt" "net/http" "github.com/hashicorp/errwrap" "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/logical" - "github.com/hashicorp/vault/sdk/version" "github.com/hashicorp/vault/vault" ) @@ -164,87 +162,13 @@ func handleSysSealStatus(core *vault.Core) http.Handler { func handleSysSealStatusRaw(core *vault.Core, w http.ResponseWriter, r *http.Request) { ctx := context.Background() - - sealed := core.Sealed() - - initialized, err := core.Initialized(ctx) - if err != nil { - respondError(w, http.StatusInternalServerError, err) - return - } - - var sealConfig *vault.SealConfig - if core.SealAccess().RecoveryKeySupported() { - sealConfig, err = core.SealAccess().RecoveryConfig(ctx) - } else { - sealConfig, err = core.SealAccess().BarrierConfig(ctx) - } + status, err := core.GetSealStatus(ctx) if err != nil { respondError(w, http.StatusInternalServerError, err) return } - if sealConfig == nil { - respondOk(w, &SealStatusResponse{ - Type: core.SealAccess().BarrierType(), - Initialized: initialized, - Sealed: true, - RecoverySeal: core.SealAccess().RecoveryKeySupported(), - StorageType: core.StorageType(), - Version: version.GetVersion().VersionNumber(), - }) - return - } - - // Fetch the local cluster name and identifier - var clusterName, clusterID string - if !sealed { - cluster, err := core.Cluster(ctx) - if err != nil { - respondError(w, http.StatusInternalServerError, err) - return - } - if cluster == nil { - respondError(w, http.StatusInternalServerError, fmt.Errorf("failed to fetch cluster details")) - return - } - clusterName = cluster.Name - clusterID = cluster.ID - } - - progress, nonce := core.SecretProgress() - - respondOk(w, &SealStatusResponse{ - Type: sealConfig.Type, - Initialized: initialized, - Sealed: sealed, - T: sealConfig.SecretThreshold, - N: sealConfig.SecretShares, - Progress: progress, - Nonce: nonce, - Version: version.GetVersion().VersionNumber(), - Migration: core.IsInSealMigrationMode() && !core.IsSealMigrated(), - ClusterName: clusterName, - ClusterID: clusterID, - RecoverySeal: core.SealAccess().RecoveryKeySupported(), - StorageType: core.StorageType(), - }) -} - -type SealStatusResponse struct { - Type string `json:"type"` - Initialized bool `json:"initialized"` - Sealed bool `json:"sealed"` - T int `json:"t"` - N int `json:"n"` - Progress int `json:"progress"` - Nonce string `json:"nonce"` - Version string `json:"version"` - Migration bool `json:"migration"` - ClusterName string `json:"cluster_name,omitempty"` - ClusterID string `json:"cluster_id,omitempty"` - RecoverySeal bool `json:"recovery_seal"` - StorageType string `json:"storage_type,omitempty"` + respondOk(w, status) } // Note: because we didn't provide explicit tagging in the past we can't do it diff --git a/vault/logical_system.go b/vault/logical_system.go index 30c95c943f3b..dfa55d501f35 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -38,6 +38,7 @@ import ( "github.com/hashicorp/vault/sdk/helper/strutil" "github.com/hashicorp/vault/sdk/helper/wrapping" "github.com/hashicorp/vault/sdk/logical" + "github.com/hashicorp/vault/sdk/version" "github.com/mitchellh/mapstructure" ) @@ -150,6 +151,7 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend { b.Backend.Paths = append(b.Backend.Paths, b.configPaths()...) b.Backend.Paths = append(b.Backend.Paths, b.rekeyPaths()...) b.Backend.Paths = append(b.Backend.Paths, b.sealPaths()...) + b.Backend.Paths = append(b.Backend.Paths, b.statusPaths()...) b.Backend.Paths = append(b.Backend.Paths, b.pluginsCatalogListPaths()...) b.Backend.Paths = append(b.Backend.Paths, b.pluginsCatalogCRUDPath()) b.Backend.Paths = append(b.Backend.Paths, b.pluginsReloadPath()) @@ -3660,6 +3662,168 @@ func (b *SystemBackend) pathInternalOpenAPI(ctx context.Context, req *logical.Re return resp, nil } +type SealStatusResponse struct { + Type string `json:"type"` + Initialized bool `json:"initialized"` + Sealed bool `json:"sealed"` + T int `json:"t"` + N int `json:"n"` + Progress int `json:"progress"` + Nonce string `json:"nonce"` + Version string `json:"version"` + Migration bool `json:"migration"` + ClusterName string `json:"cluster_name,omitempty"` + ClusterID string `json:"cluster_id,omitempty"` + RecoverySeal bool `json:"recovery_seal"` + StorageType string `json:"storage_type,omitempty"` +} + +func (core *Core) GetSealStatus(ctx context.Context) (*SealStatusResponse, error) { + sealed := core.Sealed() + + initialized, err := core.Initialized(ctx) + if err != nil { + return nil, err + } + + var sealConfig *SealConfig + if core.SealAccess().RecoveryKeySupported() { + sealConfig, err = core.SealAccess().RecoveryConfig(ctx) + } else { + sealConfig, err = core.SealAccess().BarrierConfig(ctx) + } + if err != nil { + return nil, err + } + + if sealConfig == nil { + return &SealStatusResponse{ + Type: core.SealAccess().BarrierType(), + Initialized: initialized, + Sealed: true, + RecoverySeal: core.SealAccess().RecoveryKeySupported(), + StorageType: core.StorageType(), + Version: version.GetVersion().VersionNumber(), + }, nil + } + + // Fetch the local cluster name and identifier + var clusterName, clusterID string + if !sealed { + cluster, err := core.Cluster(ctx) + if err != nil { + return nil, err + } + if cluster == nil { + return nil, fmt.Errorf("failed to fetch cluster details") + } + clusterName = cluster.Name + clusterID = cluster.ID + } + + progress, nonce := core.SecretProgress() + + return &SealStatusResponse{ + Type: sealConfig.Type, + Initialized: initialized, + Sealed: sealed, + T: sealConfig.SecretThreshold, + N: sealConfig.SecretShares, + Progress: progress, + Nonce: nonce, + Version: version.GetVersion().VersionNumber(), + Migration: core.IsInSealMigrationMode() && !core.IsSealMigrated(), + ClusterName: clusterName, + ClusterID: clusterID, + RecoverySeal: core.SealAccess().RecoveryKeySupported(), + StorageType: core.StorageType(), + }, nil +} + +type LeaderResponse struct { + HAEnabled bool `json:"ha_enabled"` + IsSelf bool `json:"is_self"` + ActiveTime time.Time `json:"active_time,omitempty"` + LeaderAddress string `json:"leader_address"` + LeaderClusterAddress string `json:"leader_cluster_address"` + PerfStandby bool `json:"performance_standby"` + PerfStandbyLastRemoteWAL uint64 `json:"performance_standby_last_remote_wal"` + LastWAL uint64 `json:"last_wal,omitempty"` + + // Raft Indexes for this node + RaftCommittedIndex uint64 `json:"raft_committed_index,omitempty"` + RaftAppliedIndex uint64 `json:"raft_applied_index,omitempty"` +} + +func (core *Core) GetLeaderStatus() (*LeaderResponse, error) { + haEnabled := true + isLeader, address, clusterAddr, err := core.Leader() + if errwrap.Contains(err, ErrHANotEnabled.Error()) { + haEnabled = false + err = nil + } + if err != nil { + return nil, err + } + + resp := &LeaderResponse{ + HAEnabled: haEnabled, + IsSelf: isLeader, + LeaderAddress: address, + LeaderClusterAddress: clusterAddr, + PerfStandby: core.PerfStandby(), + } + if isLeader { + resp.ActiveTime = core.ActiveTime() + } + if resp.PerfStandby { + resp.PerfStandbyLastRemoteWAL = LastRemoteWAL(core) + } else if isLeader || !haEnabled { + resp.LastWAL = LastWAL(core) + } + + resp.RaftCommittedIndex, resp.RaftAppliedIndex = core.GetRaftIndexes() + return resp, nil +} + +func (b *SystemBackend) handleSealStatus(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + status, err := b.Core.GetSealStatus(ctx) + if err != nil { + return nil, err + } + buf, err := json.Marshal(status) + if err != nil { + return nil, err + } + httpResp := &logical.Response{ + Data: map[string]interface{}{ + logical.HTTPStatusCode: 200, + logical.HTTPRawBody: buf, + logical.HTTPContentType: "application/json", + }, + } + return httpResp, nil +} + +func (b *SystemBackend) handleLeaderStatus(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { + status, err := b.Core.GetLeaderStatus() + if err != nil { + return nil, err + } + buf, err := json.Marshal(status) + if err != nil { + return nil, err + } + httpResp := &logical.Response{ + Data: map[string]interface{}{ + logical.HTTPStatusCode: 200, + logical.HTTPRawBody: buf, + logical.HTTPContentType: "application/json", + }, + } + return httpResp, nil +} + func sanitizePath(path string) string { if !strings.HasSuffix(path, "/") { path += "/" diff --git a/vault/logical_system_paths.go b/vault/logical_system_paths.go index 0ce856ae0c39..aedba60a0dc0 100644 --- a/vault/logical_system_paths.go +++ b/vault/logical_system_paths.go @@ -254,17 +254,6 @@ func (b *SystemBackend) configPaths() []*framework.Path { HelpSynopsis: strings.TrimSpace(sysHelp["init"][0]), HelpDescription: strings.TrimSpace(sysHelp["init"][1]), }, - { - Pattern: "leader$", - - Operations: map[logical.Operation]framework.OperationHandler{ - logical.ReadOperation: &framework.PathOperation{ - Summary: "Returns the high availability status and current leader instance of Vault.", - }, - }, - - HelpSynopsis: "Check the high availability status and current leader of Vault", - }, { Pattern: "step-down$", @@ -404,18 +393,6 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path { }, }, - { - Pattern: "seal-status$", - Operations: map[logical.Operation]framework.OperationHandler{ - logical.ReadOperation: &framework.PathOperation{ - Summary: "Check the seal status of a Vault.", - }, - }, - - HelpSynopsis: strings.TrimSpace(sysHelp["seal-status"][0]), - HelpDescription: strings.TrimSpace(sysHelp["seal-status"][1]), - }, - { Pattern: "seal$", Operations: map[logical.Operation]framework.OperationHandler{ @@ -452,6 +429,35 @@ func (b *SystemBackend) rekeyPaths() []*framework.Path { } } +func (b *SystemBackend) statusPaths() []*framework.Path { + return []*framework.Path{ + { + Pattern: "leader$", + + Operations: map[logical.Operation]framework.OperationHandler{ + logical.ReadOperation: &framework.PathOperation{ + Callback: b.handleLeaderStatus, + Summary: "Returns the high availability status and current leader instance of Vault.", + }, + }, + + HelpSynopsis: "Check the high availability status and current leader of Vault", + }, + { + Pattern: "seal-status$", + Operations: map[logical.Operation]framework.OperationHandler{ + logical.ReadOperation: &framework.PathOperation{ + Callback: b.handleSealStatus, + Summary: "Check the seal status of a Vault.", + }, + }, + + HelpSynopsis: strings.TrimSpace(sysHelp["seal-status"][0]), + HelpDescription: strings.TrimSpace(sysHelp["seal-status"][1]), + }, + } +} + func (b *SystemBackend) auditPaths() []*framework.Path { return []*framework.Path{ { From 26820ec3c65258e357cfbdc75b151f72db69461a Mon Sep 17 00:00:00 2001 From: Mark Gritter Date: Mon, 25 Jan 2021 11:25:54 -0600 Subject: [PATCH 2/3] Unconditionally use the root namespace when calling sys/seal-status. (#10742) --- command/status.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/command/status.go b/command/status.go index 0e6be18f75aa..5d3c8eec51b5 100644 --- a/command/status.go +++ b/command/status.go @@ -71,6 +71,11 @@ func (c *StatusCommand) Run(args []string) int { return 1 } + // Always query in the root namespace. + // Although seal-status is present in other namespaces, it will not + // be available until Vault is unsealed. + client.SetNamespace("") + status, err := client.Sys().SealStatus() if err != nil { c.UI.Error(fmt.Sprintf("Error checking seal status: %s", err)) From 5968a433436a4144a670a3624cf12b87fc091446 Mon Sep 17 00:00:00 2001 From: mgritter Date: Mon, 25 Jan 2021 12:34:43 -0800 Subject: [PATCH 3/3] Removing a feature added in 1.7 from backport. --- vault/logical_system.go | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/vault/logical_system.go b/vault/logical_system.go index dfa55d501f35..dd831b17938c 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -3741,14 +3741,14 @@ func (core *Core) GetSealStatus(ctx context.Context) (*SealStatusResponse, error } type LeaderResponse struct { - HAEnabled bool `json:"ha_enabled"` - IsSelf bool `json:"is_self"` - ActiveTime time.Time `json:"active_time,omitempty"` - LeaderAddress string `json:"leader_address"` - LeaderClusterAddress string `json:"leader_cluster_address"` - PerfStandby bool `json:"performance_standby"` - PerfStandbyLastRemoteWAL uint64 `json:"performance_standby_last_remote_wal"` - LastWAL uint64 `json:"last_wal,omitempty"` + HAEnabled bool `json:"ha_enabled"` + IsSelf bool `json:"is_self"` + + LeaderAddress string `json:"leader_address"` + LeaderClusterAddress string `json:"leader_cluster_address"` + PerfStandby bool `json:"performance_standby"` + PerfStandbyLastRemoteWAL uint64 `json:"performance_standby_last_remote_wal"` + LastWAL uint64 `json:"last_wal,omitempty"` // Raft Indexes for this node RaftCommittedIndex uint64 `json:"raft_committed_index,omitempty"` @@ -3773,9 +3773,6 @@ func (core *Core) GetLeaderStatus() (*LeaderResponse, error) { LeaderClusterAddress: clusterAddr, PerfStandby: core.PerfStandby(), } - if isLeader { - resp.ActiveTime = core.ActiveTime() - } if resp.PerfStandby { resp.PerfStandbyLastRemoteWAL = LastRemoteWAL(core) } else if isLeader || !haEnabled {