mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-14 18:47:01 +02:00
* Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package. This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License. Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUS-1.1 * Fix test that expected exact offset on hcl file --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com> Co-authored-by: Sarah Thompson <sthompson@hashicorp.com> Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
243 lines
6.7 KiB
Go
243 lines
6.7 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package http
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
|
"github.com/hashicorp/vault/vault"
|
|
"github.com/hashicorp/vault/version"
|
|
)
|
|
|
|
func handleSysHealth(core *vault.Core) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case "GET":
|
|
handleSysHealthGet(core, w, r)
|
|
case "HEAD":
|
|
handleSysHealthHead(core, w, r)
|
|
default:
|
|
respondError(w, http.StatusMethodNotAllowed, nil)
|
|
}
|
|
})
|
|
}
|
|
|
|
func fetchStatusCode(r *http.Request, field string) (int, bool, bool) {
|
|
var err error
|
|
statusCode := http.StatusOK
|
|
if statusCodeStr, statusCodeOk := r.URL.Query()[field]; statusCodeOk {
|
|
statusCode, err = strconv.Atoi(statusCodeStr[0])
|
|
if err != nil || len(statusCodeStr) < 1 {
|
|
return http.StatusBadRequest, false, false
|
|
}
|
|
return statusCode, true, true
|
|
}
|
|
return statusCode, false, true
|
|
}
|
|
|
|
func handleSysHealthGet(core *vault.Core, w http.ResponseWriter, r *http.Request) {
|
|
code, body, err := getSysHealth(core, r)
|
|
if err != nil {
|
|
core.Logger().Error("error checking health", "error", err)
|
|
respondError(w, code, nil)
|
|
return
|
|
}
|
|
|
|
if body == nil {
|
|
respondError(w, code, nil)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(code)
|
|
|
|
// Generate the response
|
|
enc := json.NewEncoder(w)
|
|
enc.Encode(body)
|
|
}
|
|
|
|
func handleSysHealthHead(core *vault.Core, w http.ResponseWriter, r *http.Request) {
|
|
code, body, _ := getSysHealth(core, r)
|
|
|
|
if body != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
}
|
|
w.WriteHeader(code)
|
|
}
|
|
|
|
func getSysHealth(core *vault.Core, r *http.Request) (int, *HealthResponse, error) {
|
|
var err error
|
|
|
|
// Check if being a standby is allowed for the purpose of a 200 OK
|
|
standbyOKStr, standbyOK := r.URL.Query()["standbyok"]
|
|
if standbyOK {
|
|
standbyOK, err = parseutil.ParseBool(standbyOKStr[0])
|
|
if err != nil {
|
|
return http.StatusBadRequest, nil, fmt.Errorf("bad value for standbyok parameter: %w", err)
|
|
}
|
|
}
|
|
perfStandbyOKStr, perfStandbyOK := r.URL.Query()["perfstandbyok"]
|
|
if perfStandbyOK {
|
|
perfStandbyOK, err = parseutil.ParseBool(perfStandbyOKStr[0])
|
|
if err != nil {
|
|
return http.StatusBadRequest, nil, fmt.Errorf("bad value for perfstandbyok parameter: %w", err)
|
|
}
|
|
}
|
|
|
|
uninitCode := http.StatusNotImplemented
|
|
if code, found, ok := fetchStatusCode(r, "uninitcode"); !ok {
|
|
return http.StatusBadRequest, nil, nil
|
|
} else if found {
|
|
uninitCode = code
|
|
}
|
|
|
|
sealedCode := http.StatusServiceUnavailable
|
|
if code, found, ok := fetchStatusCode(r, "sealedcode"); !ok {
|
|
return http.StatusBadRequest, nil, nil
|
|
} else if found {
|
|
sealedCode = code
|
|
}
|
|
|
|
standbyCode := http.StatusTooManyRequests // Consul warning code
|
|
if code, found, ok := fetchStatusCode(r, "standbycode"); !ok {
|
|
return http.StatusBadRequest, nil, nil
|
|
} else if found {
|
|
standbyCode = code
|
|
}
|
|
|
|
activeCode := http.StatusOK
|
|
if code, found, ok := fetchStatusCode(r, "activecode"); !ok {
|
|
return http.StatusBadRequest, nil, nil
|
|
} else if found {
|
|
activeCode = code
|
|
}
|
|
|
|
drSecondaryCode := 472 // unofficial 4xx status code
|
|
if code, found, ok := fetchStatusCode(r, "drsecondarycode"); !ok {
|
|
return http.StatusBadRequest, nil, nil
|
|
} else if found {
|
|
drSecondaryCode = code
|
|
}
|
|
|
|
perfStandbyCode := 473 // unofficial 4xx status code
|
|
if code, found, ok := fetchStatusCode(r, "performancestandbycode"); !ok {
|
|
return http.StatusBadRequest, nil, nil
|
|
} else if found {
|
|
perfStandbyCode = code
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
// Check system status
|
|
sealed := core.Sealed()
|
|
standby, perfStandby := core.StandbyStates()
|
|
var replicationState consts.ReplicationState
|
|
if standby {
|
|
replicationState = core.ActiveNodeReplicationState()
|
|
} else {
|
|
replicationState = core.ReplicationState()
|
|
}
|
|
|
|
init, err := core.Initialized(ctx)
|
|
if err != nil {
|
|
return http.StatusInternalServerError, nil, err
|
|
}
|
|
|
|
// Determine the status code
|
|
code := activeCode
|
|
switch {
|
|
case !init:
|
|
code = uninitCode
|
|
case sealed:
|
|
code = sealedCode
|
|
case replicationState.HasState(consts.ReplicationDRSecondary):
|
|
code = drSecondaryCode
|
|
case perfStandby:
|
|
if !perfStandbyOK {
|
|
code = perfStandbyCode
|
|
}
|
|
case standby:
|
|
if !standbyOK {
|
|
code = standbyCode
|
|
}
|
|
}
|
|
|
|
// Fetch the local cluster name and identifier
|
|
var clusterName, clusterID string
|
|
if !sealed {
|
|
cluster, err := core.Cluster(ctx)
|
|
if err != nil {
|
|
return http.StatusInternalServerError, nil, err
|
|
}
|
|
if cluster == nil {
|
|
return http.StatusInternalServerError, nil, fmt.Errorf("failed to fetch cluster details")
|
|
}
|
|
clusterName = cluster.Name
|
|
clusterID = cluster.ID
|
|
}
|
|
|
|
// Format the body
|
|
body := &HealthResponse{
|
|
Initialized: init,
|
|
Sealed: sealed,
|
|
Standby: standby,
|
|
PerformanceStandby: perfStandby,
|
|
ReplicationPerformanceMode: replicationState.GetPerformanceString(),
|
|
ReplicationDRMode: replicationState.GetDRString(),
|
|
ServerTimeUTC: time.Now().UTC().Unix(),
|
|
Version: version.GetVersion().VersionNumber(),
|
|
ClusterName: clusterName,
|
|
ClusterID: clusterID,
|
|
}
|
|
|
|
licenseState, err := vault.LicenseSummary(core)
|
|
if err != nil {
|
|
return http.StatusInternalServerError, nil, err
|
|
}
|
|
|
|
if licenseState != nil {
|
|
body.License = &HealthResponseLicense{
|
|
State: licenseState.State,
|
|
Terminated: licenseState.Terminated,
|
|
}
|
|
if !licenseState.ExpiryTime.IsZero() {
|
|
body.License.ExpiryTime = licenseState.ExpiryTime.Format(time.RFC3339)
|
|
}
|
|
}
|
|
|
|
if init && !sealed && !standby {
|
|
body.LastWAL = vault.LastWAL(core)
|
|
}
|
|
|
|
return code, body, nil
|
|
}
|
|
|
|
type HealthResponseLicense struct {
|
|
State string `json:"state"`
|
|
ExpiryTime string `json:"expiry_time"`
|
|
Terminated bool `json:"terminated"`
|
|
}
|
|
|
|
type HealthResponse struct {
|
|
Initialized bool `json:"initialized"`
|
|
Sealed bool `json:"sealed"`
|
|
Standby bool `json:"standby"`
|
|
PerformanceStandby bool `json:"performance_standby"`
|
|
ReplicationPerformanceMode string `json:"replication_performance_mode"`
|
|
ReplicationDRMode string `json:"replication_dr_mode"`
|
|
ServerTimeUTC int64 `json:"server_time_utc"`
|
|
Version string `json:"version"`
|
|
ClusterName string `json:"cluster_name,omitempty"`
|
|
ClusterID string `json:"cluster_id,omitempty"`
|
|
LastWAL uint64 `json:"last_wal,omitempty"`
|
|
License *HealthResponseLicense `json:"license,omitempty"`
|
|
}
|