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.
This commit is contained in:
parent
9a5920ba7a
commit
fd55aa8378
|
@ -0,0 +1,3 @@
|
|||
```release-note: improvement
|
||||
core (enterprise): "vault status" command works when a namespace is set.
|
||||
```
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 += "/"
|
||||
|
|
|
@ -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{
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue