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:
Mark Gritter 2021-01-20 14:04:24 -06:00 committed by GitHub
parent 9a5920ba7a
commit fd55aa8378
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 202 additions and 144 deletions

3
changelog/10725.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note: improvement
core (enterprise): "vault status" command works when a namespace is set.
```

View File

@ -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"`
}

View File

@ -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

View File

@ -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 += "/"

View File

@ -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{
{ {