Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sys: add host-info endpoint #7330

Merged
merged 20 commits into from
Oct 3, 2019
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/hashicorp/hcl v1.0.0
github.com/hashicorp/vault/sdk v0.1.14-0.20190805214312-16112a336457
github.com/mitchellh/mapstructure v1.1.2
github.com/shirou/gopsutil v0.0.0-20190731134726-d80c43f9c984 // indirect
calvn marked this conversation as resolved.
Show resolved Hide resolved
golang.org/x/net v0.0.0-20190620200207-3b0461eec859
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4
gopkg.in/square/go-jose.v2 v2.3.1
Expand Down
7 changes: 7 additions & 0 deletions api/go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
Expand All @@ -10,6 +11,7 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
Expand Down Expand Up @@ -66,6 +68,9 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/shirou/gopsutil v0.0.0-20190731134726-d80c43f9c984 h1:wsZAb4P8F7uQSwsnxE1gk9AHCcc5U0wvyDzcLwFY0Eo=
github.com/shirou/gopsutil v0.0.0-20190731134726-d80c43f9c984/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
Expand All @@ -91,8 +96,10 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU=
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ require (
github.com/ryanuber/columnize v2.1.0+incompatible
github.com/ryanuber/go-glob v1.0.0
github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec
github.com/shirou/gopsutil v0.0.0-20190731134726-d80c43f9c984
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 // indirect
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94 // indirect
github.com/stretchr/testify v1.3.0
Expand Down
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ github.com/SAP/go-hdb v0.14.1 h1:hkw4ozGZ/i4eak7ZuGkY5e0hxiXFdNUBNhr4AvZVNFE=
github.com/SAP/go-hdb v0.14.1/go.mod h1:7fdQLVC2lER3urZLjZCm0AuMQfApof92n3aylBPEkMo=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14=
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
Expand Down Expand Up @@ -157,6 +158,7 @@ github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk=
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
Expand Down Expand Up @@ -508,6 +510,11 @@ github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shirou/gopsutil v0.0.0-20190731134726-d80c43f9c984 h1:wsZAb4P8F7uQSwsnxE1gk9AHCcc5U0wvyDzcLwFY0Eo=
github.com/shirou/gopsutil v0.0.0-20190731134726-d80c43f9c984/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc=
github.com/shirou/gopsutil v2.18.12+incompatible h1:1eaJvGomDnH74/5cF4CTmTbLHAriGFsTZppLXDX93OM=
github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/sirupsen/logrus v1.0.5 h1:8c8b5uO0zS4X6RPl/sd1ENwSkIc0/H2PaHxE3udaE8I=
Expand Down
1 change: 1 addition & 0 deletions http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ func Handler(props *vault.HandlerProperties) http.Handler {

// Create the muxer to handle the actual endpoints
mux := http.NewServeMux()
mux.Handle("/v1/sys/host-info", handleLogical(core))
mux.Handle("/v1/sys/init", handleSysInit(core))
mux.Handle("/v1/sys/seal-status", handleSysSealStatus(core))
mux.Handle("/v1/sys/seal", handleSysSeal(core))
Expand Down
7 changes: 7 additions & 0 deletions http/logical.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,13 @@ func handleLogicalInternal(core *vault.Core, injectDataIntoTopLevel bool) http.H
// success.
resp, ok, needsForward := request(core, w, r, req)
if needsForward {
// Do not forward these specific requests since they are only
// applicable to the local node.
switch req.Path {
case "sys/host-info":
return
}

if origBody != nil {
r.Body = origBody
}
Expand Down
69 changes: 69 additions & 0 deletions http/sys_hostinfo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package http

import (
"encoding/json"
"testing"

"github.com/hashicorp/vault/vault"
)

func TestSysHostInfo(t *testing.T) {
cluster := vault.NewTestCluster(t, &vault.CoreConfig{}, &vault.TestClusterOptions{
HandlerFunc: Handler,
})
cluster.Start()
defer cluster.Cleanup()
cores := cluster.Cores

vault.TestWaitActive(t, cores[0].Core)

// Query against the active node, should get host information back
secret, err := cores[0].Client.Logical().Read("sys/host-info")
if err != nil {
t.Fatal(err)
}
if secret == nil || secret.Data == nil {
t.Fatal("expected data in the response")
}

dataBytes, err := json.Marshal(secret.Data)
if err != nil {
t.Fatal(err)
}

var info vault.HostInfo
if err := json.Unmarshal(dataBytes, &info); err != nil {
t.Fatal(err)
}

if info.Timestamp.IsZero() {
t.Fatal("expected non-zero Timestamp")
}
if info.CPU == nil {
t.Fatal("expected non-nil CPU value")
}
if info.Disk == nil {
t.Fatal("expected disk info")
}
if info.Host == nil {
t.Fatal("expected host info")
}
if info.Memory == nil {
t.Fatal("expected memory info")
}

// Check we're standby
healthResp, err := cores[1].Client.Sys().Health()
calvn marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
t.Fatal(err)
}
if !healthResp.Standby {
t.Fatal("expected node to be standby")
}

// Query against a standby, should error
secret, err = cores[1].Client.Logical().Read("sys/host-info")
calvn marked this conversation as resolved.
Show resolved Hide resolved
if err == nil || secret != nil {
t.Fatalf("expected error on standby node, HostInfo: %v", secret)
}
}
81 changes: 81 additions & 0 deletions vault/hostinfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package vault

import (
"time"

"github.com/hashicorp/go-multierror"
"github.com/shirou/gopsutil/cpu"
"github.com/shirou/gopsutil/disk"
"github.com/shirou/gopsutil/host"
"github.com/shirou/gopsutil/mem"
)

// HostInfo holds all the information that gets captured on the host.
type HostInfo struct {
Timestamp time.Time `json:"timestamp"`
CPU []cpu.InfoStat `json:"cpu"`
Disk []*disk.UsageStat `json:"disk"`
Host *host.InfoStat `json:"host"`
Memory *mem.VirtualMemoryStat `json:"memory"`
}

// HostInfoError is a typed error for more convenient error checking.
type HostInfoError struct {
Err error
}

func (e *HostInfoError) WrappedErrors() []error {
return []error{e.Err}
}

func (e *HostInfoError) Error() string {
return e.Err.Error()
}

// CollectHostInfo returns information on the host, which includes general
// host status, CPU, memory, and disk utilization.
//
// The function does a best-effort capture on the most information possible,
// continuing on capture errors encountered and appending them to a resulting
// multierror.Error that gets returned at the end.
func (c *Core) CollectHostInfo() (*HostInfo, error) {
var retErr *multierror.Error
info := &HostInfo{Timestamp: time.Now().UTC()}

if h, err := host.Info(); err != nil {
retErr = multierror.Append(retErr, &HostInfoError{err})
} else {
info.Host = h
}

if v, err := mem.VirtualMemory(); err != nil {
retErr = multierror.Append(retErr, &HostInfoError{err})
} else {
info.Memory = v
}

parts, err := disk.Partitions(false)
if err != nil {
retErr = multierror.Append(retErr, &HostInfoError{err})
} else {
var usage []*disk.UsageStat
for _, part := range parts {
u, err := disk.Usage(part.Mountpoint)
if err != nil {
retErr = multierror.Append(retErr, &HostInfoError{err})
continue
}
usage = append(usage, u)
calvn marked this conversation as resolved.
Show resolved Hide resolved

}
info.Disk = usage
}

if c, err := cpu.Info(); err != nil {
calvn marked this conversation as resolved.
Show resolved Hide resolved
retErr = multierror.Append(retErr, &HostInfoError{err})
} else {
info.CPU = c
}

return info, retErr.ErrorOrNil()
calvn marked this conversation as resolved.
Show resolved Hide resolved
}
29 changes: 29 additions & 0 deletions vault/hostinfo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package vault

import (
"testing"
)

func TestCollectHostInfo(t *testing.T) {
c, _, _ := TestCoreUnsealed(t)

info, err := c.CollectHostInfo()
if err != nil {
t.Fatal(err)
}
if info.Timestamp.IsZero() {
t.Fatal("expected non-zero Timestamp")
}
if info.CPU == nil {
t.Fatal("expected non-nil CPU value")
}
if info.Disk == nil {
t.Fatal("expected non-nil Disk value")
}
if info.Host == nil {
t.Fatal("expected non-nil Host value")
}
if info.Memory == nil {
t.Fatal("expected non-nil Memory value")
}
}
58 changes: 58 additions & 0 deletions vault/logical_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"sync"
"time"

"github.com/hashicorp/go-multierror"
"github.com/hashicorp/vault/physical/raft"

"github.com/hashicorp/errwrap"
Expand Down Expand Up @@ -166,6 +167,7 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
b.Backend.Paths = append(b.Backend.Paths, b.internalPaths()...)
b.Backend.Paths = append(b.Backend.Paths, b.remountPath())
b.Backend.Paths = append(b.Backend.Paths, b.metricsPath())
b.Backend.Paths = append(b.Backend.Paths, b.hostInfoPath())

if core.rawEnabled {
b.Backend.Paths = append(b.Backend.Paths, &framework.Path{
Expand Down Expand Up @@ -2609,6 +2611,56 @@ func (b *SystemBackend) handleMetrics(ctx context.Context, req *logical.Request,
return b.Core.metricsHelper.ResponseForFormat(format)
}

// handleHostInfo collects and returns host-related information, which includes
// system information, cpu, disk, and memory usage. Any capture-related errors
// returned by the collection method will be returned as response warnings.
func (b *SystemBackend) handleHostInfo(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
resp := &logical.Response{}
info, err := b.Core.CollectHostInfo()
if err != nil {
// If the error is a HostInfoError, we return them as response warnings
if errs, ok := err.(*multierror.Error); ok {
var warnings []string
for _, mErr := range errs.Errors {
if errwrap.ContainsType(mErr, new(HostInfoError)) {
calvn marked this conversation as resolved.
Show resolved Hide resolved
warnings = append(warnings, mErr.Error())
} else {
// If the error is a multierror, it should only be for
// HostInfoError, but if it's not for any reason, we return
// it as an error to avoid it being swallowed.
return nil, err
}
}
resp.Warnings = warnings
} else {
return nil, err
}
}

if info == nil {
return nil, errors.New("unable to collect host information: nil HostInfo")
}

respData := map[string]interface{}{
"timestamp": info.Timestamp,
}
if info.CPU != nil {
respData["cpu"] = info.CPU
}
if info.Disk != nil {
respData["disk"] = info.Disk
}
if info.Host != nil {
respData["host"] = info.Host
}
if info.Memory != nil {
respData["memory"] = info.Memory
}
resp.Data = respData

return resp, nil
}

func (b *SystemBackend) handleWrappingLookup(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
// This ordering of lookups has been validated already in the wrapping
// validation func, we're just doing this for a safety check
Expand Down Expand Up @@ -4042,4 +4094,10 @@ This path responds to the following HTTP methods.
"Count of requests seen by this Vault cluster over time.",
"Count of requests seen by this Vault cluster over time. Not included in count: health checks, UI asset requests, requests forwarded from another cluster.",
},
"host-info": {
"Information about the the host instance that this Vault server is running on.",
`Information about the the host instance that this Vault server is running on.
The information that gets collected includes host hardware information, and CPU,
disk, and memory utilization`,
},
}
15 changes: 15 additions & 0 deletions vault/logical_system_paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,21 @@ func (b *SystemBackend) metricsPath() *framework.Path {

}

func (b *SystemBackend) hostInfoPath() *framework.Path {
return &framework.Path{
Pattern: "host-info/?",
Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{
Callback: b.handleHostInfo,
Summary: strings.TrimSpace(sysHelp["host-info"][0]),
Description: strings.TrimSpace(sysHelp["host-info"][1]),
},
},
HelpSynopsis: strings.TrimSpace(sysHelp["host-info"][0]),
HelpDescription: strings.TrimSpace(sysHelp["host-info"][1]),
}
}

func (b *SystemBackend) authPaths() []*framework.Path {
return []*framework.Path{
{
Expand Down