diff --git a/changelog/10725.txt b/changelog/10725.txt new file mode 100644 index 000000000..e7bb8fbef --- /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 8c8b69a50..77cc884f6 100644 --- a/http/sys_leader.go +++ b/http/sys_leader.go @@ -1,13 +1,12 @@ package http import ( - "net/http" - "time" - - "github.com/hashicorp/errwrap" "github.com/hashicorp/vault/vault" + "net/http" ) +// 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 { @@ -20,48 +19,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 isLeader { - resp.ActiveTime = core.ActiveTime() - } - 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"` - 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"` -} diff --git a/http/sys_seal.go b/http/sys_seal.go index 7b86e4364..24f491b65 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) + status, err := core.GetSealStatus(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) - } - 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 205a8e08a..a8f2fc4f5 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()) @@ -3670,6 +3672,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 dca220666..384a891c6 100644 --- a/vault/logical_system_paths.go +++ b/vault/logical_system_paths.go @@ -258,17 +258,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$", @@ -408,18 +397,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{ @@ -456,6 +433,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{ {