Skip to content

Commit

Permalink
Audit generate root requests and responses. (#8301)
Browse files Browse the repository at this point in the history
  • Loading branch information
ncabatoff committed Feb 6, 2020
1 parent d231add commit 7c7eab5
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 10 deletions.
69 changes: 67 additions & 2 deletions http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,10 @@ func Handler(props *vault.HandlerProperties) http.Handler {
mux.Handle("/v1/sys/unseal", handleSysUnseal(core))
mux.Handle("/v1/sys/leader", handleSysLeader(core))
mux.Handle("/v1/sys/health", handleSysHealth(core))
mux.Handle("/v1/sys/generate-root/attempt", handleRequestForwarding(core, handleSysGenerateRootAttempt(core, vault.GenerateStandardRootTokenStrategy)))
mux.Handle("/v1/sys/generate-root/update", handleRequestForwarding(core, handleSysGenerateRootUpdate(core, vault.GenerateStandardRootTokenStrategy)))
mux.Handle("/v1/sys/generate-root/attempt", handleRequestForwarding(core,
handleAuditNonLogical(core, handleSysGenerateRootAttempt(core, vault.GenerateStandardRootTokenStrategy))))
mux.Handle("/v1/sys/generate-root/update", handleRequestForwarding(core,
handleAuditNonLogical(core, handleSysGenerateRootUpdate(core, vault.GenerateStandardRootTokenStrategy))))
mux.Handle("/v1/sys/rekey/init", handleRequestForwarding(core, handleSysRekeyInit(core, false)))
mux.Handle("/v1/sys/rekey/update", handleRequestForwarding(core, handleSysRekeyUpdate(core, false)))
mux.Handle("/v1/sys/rekey/verify", handleRequestForwarding(core, handleSysRekeyVerify(core, false)))
Expand Down Expand Up @@ -181,6 +183,69 @@ func Handler(props *vault.HandlerProperties) http.Handler {
return printablePathCheckHandler
}

type copyResponseWriter struct {
wrapped http.ResponseWriter
statusCode int
body *bytes.Buffer
}

// newCopyResponseWriter returns an initialized newCopyResponseWriter
func newCopyResponseWriter(wrapped http.ResponseWriter) *copyResponseWriter {
w := &copyResponseWriter{
wrapped: wrapped,
body: new(bytes.Buffer),
statusCode: 200,
}
return w
}

func (w *copyResponseWriter) Header() http.Header {
return w.wrapped.Header()
}

func (w *copyResponseWriter) Write(buf []byte) (int, error) {
w.body.Write(buf)
return w.wrapped.Write(buf)
}

func (w *copyResponseWriter) WriteHeader(code int) {
w.statusCode = code
w.wrapped.WriteHeader(code)
}

func handleAuditNonLogical(core *vault.Core, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origBody := new(bytes.Buffer)
reader := ioutil.NopCloser(io.TeeReader(r.Body, origBody))
r.Body = reader
req, _, status, err := buildLogicalRequestNoAuth(core.PerfStandby(), w, r)
if err != nil || status != 0 {
respondError(w, status, err)
return
}
if origBody != nil {
r.Body = ioutil.NopCloser(origBody)
}
input := &logical.LogInput{
Request: req,
}

core.AuditLogger().AuditRequest(r.Context(), input)
cw := newCopyResponseWriter(w)
h.ServeHTTP(cw, r)
data := make(map[string]interface{})
err = jsonutil.DecodeJSON(cw.body.Bytes(), &data)
if err != nil {
// best effort, ignore
}
httpResp := &logical.HTTPResponse{Data: data, Headers: cw.Header()}
input.Response = logical.HTTPResponseToLogicalResponse(httpResp)
core.AuditLogger().AuditResponse(r.Context(), input)
return
})

}

// wrapGenericHandler wraps the handler with an extra layer of handler where
// tasks that should be commonly handled for all the requests and/or responses
// are performed.
Expand Down
61 changes: 55 additions & 6 deletions http/sys_generate_root_test.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
package http

import (
"context"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"net"
"net/http"
"reflect"
"testing"

"github.com/go-test/deep"
"github.com/hashicorp/vault/helper/namespace"
"github.com/hashicorp/vault/helper/pgpkeys"
"github.com/hashicorp/vault/helper/xor"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/vault"
)

Expand Down Expand Up @@ -196,16 +200,58 @@ func TestSysGenerateRootAttempt_Cancel(t *testing.T) {
}
}

func TestSysGenerateRoot_badKey(t *testing.T) {
core, _, token := vault.TestCoreUnsealed(t)
func enableNoopAudit(t *testing.T, token string, core *vault.Core) {
t.Helper()
auditReq := &logical.Request{
Operation: logical.UpdateOperation,
ClientToken: token,
Path: "sys/audit/noop",
Data: map[string]interface{}{
"type": "noop",
},
}
resp, err := core.HandleRequest(namespace.RootContext(context.Background()), auditReq)
if err != nil {
t.Fatal(err)
}

if resp.IsError() {
t.Fatal(err)
}
}

func testCoreUnsealedWithAudit(t *testing.T, records **[][]byte) (*vault.Core, [][]byte, string) {
conf := &vault.CoreConfig{
BuiltinRegistry: vault.NewMockBuiltinRegistry(),
}
vault.AddNoopAudit(conf, records)
core, keys, token := vault.TestCoreUnsealedWithConfig(t, conf)
return core, keys, token
}

func testServerWithAudit(t *testing.T, records **[][]byte) (net.Listener, string, string, [][]byte) {
core, keys, token := testCoreUnsealedWithAudit(t, records)
ln, addr := TestServer(t, core)
defer ln.Close()
TestServerAuth(t, addr, token)
enableNoopAudit(t, token, core)
return ln, addr, token, keys
}

func TestSysGenerateRoot_badKey(t *testing.T) {
var records *[][]byte
ln, addr, token, _ := testServerWithAudit(t, &records)
defer ln.Close()

resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/update", map[string]interface{}{
"key": "0123",
})
testResponseStatus(t, resp, 400)

if len(*records) < 3 {
// One record for enabling the noop audit device, two for generate root attempt
t.Fatalf("expected at least 3 audit records, got %d", len(*records))
}
t.Log(string((*records)[2]))
}

func TestSysGenerateRoot_ReAttemptUpdate(t *testing.T) {
Expand All @@ -228,10 +274,9 @@ func TestSysGenerateRoot_ReAttemptUpdate(t *testing.T) {
}

func TestSysGenerateRoot_Update_OTP(t *testing.T) {
core, keys, token := vault.TestCoreUnsealed(t)
ln, addr := TestServer(t, core)
var records *[][]byte
ln, addr, token, keys := testServerWithAudit(t, &records)
defer ln.Close()
TestServerAuth(t, addr, token)

resp := testHttpPut(t, token, addr+"/v1/sys/generate-root/attempt", map[string]interface{}{})
var rootGenerationStatus map[string]interface{}
Expand Down Expand Up @@ -317,6 +362,10 @@ func TestSysGenerateRoot_Update_OTP(t *testing.T) {
if !reflect.DeepEqual(actual["data"], expected) {
t.Fatalf("\nexpected: %#v\nactual: %#v", expected, actual["data"])
}

for _, r := range *records {
t.Log(string(r))
}
}

func TestSysGenerateRoot_Update_PGP(t *testing.T) {
Expand Down
17 changes: 17 additions & 0 deletions vault/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,23 @@ func defaultAuditTable() *MountTable {
return table
}

type AuditLogger interface {
AuditRequest(ctx context.Context, input *logical.LogInput) error
AuditResponse(ctx context.Context, input *logical.LogInput) error
}

type basicAuditor struct {
c *Core
}

func (b *basicAuditor) AuditRequest(ctx context.Context, input *logical.LogInput) error {
return b.c.auditBroker.LogRequest(ctx, input, b.c.auditedHeaders)
}

func (b *basicAuditor) AuditResponse(ctx context.Context, input *logical.LogInput) error {
return b.c.auditBroker.LogResponse(ctx, input, b.c.auditedHeaders)
}

type genericAuditor struct {
c *Core
mountType string
Expand Down
4 changes: 4 additions & 0 deletions vault/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -2251,3 +2251,7 @@ type BuiltinRegistry interface {
Get(name string, pluginType consts.PluginType) (func() (interface{}, error), bool)
Keys(pluginType consts.PluginType) []string
}

func (c *Core) AuditLogger() AuditLogger {
return &basicAuditor{c: c}
}
7 changes: 5 additions & 2 deletions vault/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@ func (n *noopAudit) Salt(ctx context.Context) (*salt.Salt, error) {
return salt, nil
}

func AddNoopAudit(conf *CoreConfig) {
func AddNoopAudit(conf *CoreConfig, records **[][]byte) {
conf.AuditBackends = map[string]audit.Factory{
"noop": func(_ context.Context, config *audit.BackendConfig) (audit.Backend, error) {
view := &logical.InmemStorage{}
Expand All @@ -691,6 +691,9 @@ func AddNoopAudit(conf *CoreConfig) {
n.formatter.AuditFormatWriter = &audit.JSONFormatWriter{
SaltFunc: n.Salt,
}
if records != nil {
*records = &n.records
}
return n, nil
},
}
Expand Down Expand Up @@ -1437,7 +1440,7 @@ func NewTestCluster(t testing.T, base *CoreConfig, opts *TestClusterOptions) *Te

addAuditBackend := len(coreConfig.AuditBackends) == 0
if addAuditBackend {
AddNoopAudit(coreConfig)
AddNoopAudit(coreConfig, nil)
}

if coreConfig.Physical == nil && (opts == nil || opts.PhysicalFactory == nil) {
Expand Down

0 comments on commit 7c7eab5

Please sign in to comment.