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
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/hashicorp/errwrap"
|
|
||||||
"github.com/hashicorp/vault/vault"
|
"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 {
|
func handleSysLeader(core *vault.Core) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.Method {
|
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) {
|
func handleSysLeaderGet(core *vault.Core, w http.ResponseWriter, r *http.Request) {
|
||||||
haEnabled := true
|
resp, err := core.GetLeaderStatus()
|
||||||
isLeader, address, clusterAddr, err := core.Leader()
|
|
||||||
if errwrap.Contains(err, vault.ErrHANotEnabled.Error()) {
|
|
||||||
haEnabled = false
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, http.StatusInternalServerError, err)
|
respondError(w, http.StatusInternalServerError, err)
|
||||||
return
|
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)
|
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/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/hashicorp/errwrap"
|
"github.com/hashicorp/errwrap"
|
||||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||||
"github.com/hashicorp/vault/sdk/logical"
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
"github.com/hashicorp/vault/sdk/version"
|
|
||||||
"github.com/hashicorp/vault/vault"
|
"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) {
|
func handleSysSealStatusRaw(core *vault.Core, w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
status, err := core.GetSealStatus(ctx)
|
||||||
sealed := core.Sealed()
|
|
||||||
|
|
||||||
initialized, err := core.Initialized(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, http.StatusInternalServerError, err)
|
respondError(w, http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var sealConfig *vault.SealConfig
|
respondOk(w, status)
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: because we didn't provide explicit tagging in the past we can't do it
|
// 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/strutil"
|
||||||
"github.com/hashicorp/vault/sdk/helper/wrapping"
|
"github.com/hashicorp/vault/sdk/helper/wrapping"
|
||||||
"github.com/hashicorp/vault/sdk/logical"
|
"github.com/hashicorp/vault/sdk/logical"
|
||||||
|
"github.com/hashicorp/vault/sdk/version"
|
||||||
"github.com/mitchellh/mapstructure"
|
"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.configPaths()...)
|
||||||
b.Backend.Paths = append(b.Backend.Paths, b.rekeyPaths()...)
|
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.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.pluginsCatalogListPaths()...)
|
||||||
b.Backend.Paths = append(b.Backend.Paths, b.pluginsCatalogCRUDPath())
|
b.Backend.Paths = append(b.Backend.Paths, b.pluginsCatalogCRUDPath())
|
||||||
b.Backend.Paths = append(b.Backend.Paths, b.pluginsReloadPath())
|
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
|
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 {
|
func sanitizePath(path string) string {
|
||||||
if !strings.HasSuffix(path, "/") {
|
if !strings.HasSuffix(path, "/") {
|
||||||
path += "/"
|
path += "/"
|
||||||
|
|
|
@ -258,17 +258,6 @@ func (b *SystemBackend) configPaths() []*framework.Path {
|
||||||
HelpSynopsis: strings.TrimSpace(sysHelp["init"][0]),
|
HelpSynopsis: strings.TrimSpace(sysHelp["init"][0]),
|
||||||
HelpDescription: strings.TrimSpace(sysHelp["init"][1]),
|
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$",
|
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$",
|
Pattern: "seal$",
|
||||||
Operations: map[logical.Operation]framework.OperationHandler{
|
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 {
|
func (b *SystemBackend) auditPaths() []*framework.Path {
|
||||||
return []*framework.Path{
|
return []*framework.Path{
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue