mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-02 03:27:54 +00:00
audit: log invalid wrapping token request/response (#6541)
* audit: log invalid wrapping token request/response * Update helper/consts/error.go Co-Authored-By: calvn <cleung2010@gmail.com> * update error comments * Update vault/wrapping.go Co-Authored-By: calvn <cleung2010@gmail.com> * update comment * move validateWrappingToken out of http and into logical * minor refactor, add test cases * comment rewording * refactor validateWrappingToken to perform audit logging * move ValidateWrappingToken back to wrappingVerificationFunc * Fix tests * Review feedback
This commit is contained in:
committed by
Brian Kassouf
parent
3cc7f4a68c
commit
c3f0f96e7e
@@ -308,7 +308,7 @@ func wrappingVerificationFunc(ctx context.Context, core *vault.Core, req *logica
|
||||
return errwrap.Wrapf("error validating wrapping token: {{err}}", err)
|
||||
}
|
||||
if !valid {
|
||||
return fmt.Errorf("wrapping token is not valid or does not exist")
|
||||
return consts.ErrInvalidWrappingToken
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -22,32 +22,36 @@ func testHttpGet(t *testing.T, token string, addr string) *http.Response {
|
||||
loggedToken = "<empty>"
|
||||
}
|
||||
t.Logf("Token is %s", loggedToken)
|
||||
return testHttpData(t, "GET", token, addr, nil, false)
|
||||
return testHttpData(t, "GET", token, addr, nil, false, 0)
|
||||
}
|
||||
|
||||
func testHttpDelete(t *testing.T, token string, addr string) *http.Response {
|
||||
return testHttpData(t, "DELETE", token, addr, nil, false)
|
||||
return testHttpData(t, "DELETE", token, addr, nil, false, 0)
|
||||
}
|
||||
|
||||
// Go 1.8+ clients redirect automatically which breaks our 307 standby testing
|
||||
func testHttpDeleteDisableRedirect(t *testing.T, token string, addr string) *http.Response {
|
||||
return testHttpData(t, "DELETE", token, addr, nil, true)
|
||||
return testHttpData(t, "DELETE", token, addr, nil, true, 0)
|
||||
}
|
||||
|
||||
func testHttpPostWrapped(t *testing.T, token string, addr string, body interface{}, wrapTTL time.Duration) *http.Response {
|
||||
return testHttpData(t, "POST", token, addr, body, false, wrapTTL)
|
||||
}
|
||||
|
||||
func testHttpPost(t *testing.T, token string, addr string, body interface{}) *http.Response {
|
||||
return testHttpData(t, "POST", token, addr, body, false)
|
||||
return testHttpData(t, "POST", token, addr, body, false, 0)
|
||||
}
|
||||
|
||||
func testHttpPut(t *testing.T, token string, addr string, body interface{}) *http.Response {
|
||||
return testHttpData(t, "PUT", token, addr, body, false)
|
||||
return testHttpData(t, "PUT", token, addr, body, false, 0)
|
||||
}
|
||||
|
||||
// Go 1.8+ clients redirect automatically which breaks our 307 standby testing
|
||||
func testHttpPutDisableRedirect(t *testing.T, token string, addr string, body interface{}) *http.Response {
|
||||
return testHttpData(t, "PUT", token, addr, body, true)
|
||||
return testHttpData(t, "PUT", token, addr, body, true, 0)
|
||||
}
|
||||
|
||||
func testHttpData(t *testing.T, method string, token string, addr string, body interface{}, disableRedirect bool) *http.Response {
|
||||
func testHttpData(t *testing.T, method string, token string, addr string, body interface{}, disableRedirect bool, wrapTTL time.Duration) *http.Response {
|
||||
bodyReader := new(bytes.Buffer)
|
||||
if body != nil {
|
||||
enc := json.NewEncoder(bodyReader)
|
||||
@@ -68,6 +72,10 @@ func testHttpData(t *testing.T, method string, token string, addr string, body i
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
if wrapTTL > 0 {
|
||||
req.Header.Set("X-Vault-Wrap-TTL", wrapTTL.String())
|
||||
}
|
||||
|
||||
if len(token) != 0 {
|
||||
req.Header.Set(consts.AuthHeaderName, token)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/helper/namespace"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
@@ -208,6 +208,7 @@ func handleLogicalInternal(core *vault.Core, injectDataIntoTopLevel bool) http.H
|
||||
}
|
||||
}
|
||||
switch req.Path {
|
||||
// Route the token wrapping request to its respective sys NS
|
||||
case "sys/wrapping/lookup", "sys/wrapping/rewrap", "sys/wrapping/unwrap":
|
||||
r = r.WithContext(newCtx)
|
||||
if err := wrappingVerificationFunc(r.Context(), core, req); err != nil {
|
||||
|
||||
@@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
"github.com/go-test/deep"
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
|
||||
"github.com/hashicorp/vault/audit"
|
||||
"github.com/hashicorp/vault/helper/namespace"
|
||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||
"github.com/hashicorp/vault/sdk/helper/logging"
|
||||
@@ -347,3 +349,91 @@ func TestLogical_RespondWithStatusCode(t *testing.T) {
|
||||
t.Fatalf("bad response: %s", string(bodyRaw[:]))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogical_Audit_invalidWrappingToken(t *testing.T) {
|
||||
// Create a noop audit backend
|
||||
var noop *vault.NoopAudit
|
||||
c, _, root := vault.TestCoreUnsealedWithConfig(t, &vault.CoreConfig{
|
||||
AuditBackends: map[string]audit.Factory{
|
||||
"noop": func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
|
||||
noop = &vault.NoopAudit{
|
||||
Config: config,
|
||||
}
|
||||
return noop, nil
|
||||
},
|
||||
},
|
||||
})
|
||||
ln, addr := TestServer(t, c)
|
||||
defer ln.Close()
|
||||
|
||||
// Enable the audit backend
|
||||
|
||||
resp := testHttpPost(t, root, addr+"/v1/sys/audit/noop", map[string]interface{}{
|
||||
"type": "noop",
|
||||
})
|
||||
testResponseStatus(t, resp, 204)
|
||||
|
||||
{
|
||||
// Make a wrapping/unwrap request with an invalid token
|
||||
resp := testHttpPost(t, root, addr+"/v1/sys/wrapping/unwrap", map[string]interface{}{
|
||||
"token": "foo",
|
||||
})
|
||||
testResponseStatus(t, resp, 400)
|
||||
body := map[string][]string{}
|
||||
testResponseBody(t, resp, &body)
|
||||
if body["errors"][0] != "wrapping token is not valid or does not exist" {
|
||||
t.Fatal(body)
|
||||
}
|
||||
|
||||
// Check the audit trail on request and response
|
||||
if len(noop.ReqAuth) != 1 {
|
||||
t.Fatalf("bad: %#v", noop)
|
||||
}
|
||||
auth := noop.ReqAuth[0]
|
||||
if auth.ClientToken != root {
|
||||
t.Fatalf("bad client token: %#v", auth)
|
||||
}
|
||||
if len(noop.Req) != 1 || noop.Req[0].Path != "sys/wrapping/unwrap" {
|
||||
t.Fatalf("bad:\ngot:\n%#v", noop.Req[0])
|
||||
}
|
||||
|
||||
if len(noop.ReqErrs) != 1 {
|
||||
t.Fatalf("bad: %#v", noop.RespErrs)
|
||||
}
|
||||
if noop.ReqErrs[0] != consts.ErrInvalidWrappingToken {
|
||||
t.Fatalf("bad: %#v", noop.ReqErrs)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
resp := testHttpPostWrapped(t, root, addr+"/v1/auth/token/create", nil, 10*time.Second)
|
||||
testResponseStatus(t, resp, 200)
|
||||
body := map[string]interface{}{}
|
||||
testResponseBody(t, resp, &body)
|
||||
|
||||
wrapToken := body["wrap_info"].(map[string]interface{})["token"].(string)
|
||||
|
||||
// Make a wrapping/unwrap request with an invalid token
|
||||
resp = testHttpPost(t, root, addr+"/v1/sys/wrapping/unwrap", map[string]interface{}{
|
||||
"token": wrapToken,
|
||||
})
|
||||
testResponseStatus(t, resp, 200)
|
||||
|
||||
// Check the audit trail on request and response
|
||||
if len(noop.ReqAuth) != 3 {
|
||||
t.Fatalf("bad: %#v", noop)
|
||||
}
|
||||
auth := noop.ReqAuth[2]
|
||||
if auth.ClientToken != root {
|
||||
t.Fatalf("bad client token: %#v", auth)
|
||||
}
|
||||
if len(noop.Req) != 3 || noop.Req[2].Path != "sys/wrapping/unwrap" {
|
||||
t.Fatalf("bad:\ngot:\n%#v", noop.Req[2])
|
||||
}
|
||||
|
||||
// Make sure there is only one error in the logs
|
||||
if noop.ReqErrs[1] != nil || noop.ReqErrs[2] != nil {
|
||||
t.Fatalf("bad: %#v", noop.RespErrs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,11 @@ var (
|
||||
// No operation is expected to succeed until active.
|
||||
ErrStandby = errors.New("Vault is in standby mode")
|
||||
|
||||
// Used when .. is used in a path
|
||||
// ErrPathContainsParentReferences is returned when a path contains parent
|
||||
// references.
|
||||
ErrPathContainsParentReferences = errors.New("path cannot contain parent references")
|
||||
|
||||
// ErrInvalidWrappingToken is returned when checking for the validity of
|
||||
// a wrapping token that turns out to be invalid.
|
||||
ErrInvalidWrappingToken = errors.New("wrapping token is not valid or does not exist")
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -18,93 +17,10 @@ import (
|
||||
"github.com/hashicorp/vault/helper/namespace"
|
||||
"github.com/hashicorp/vault/sdk/helper/jsonutil"
|
||||
"github.com/hashicorp/vault/sdk/helper/logging"
|
||||
"github.com/hashicorp/vault/sdk/helper/salt"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/mitchellh/copystructure"
|
||||
)
|
||||
|
||||
type NoopAudit struct {
|
||||
Config *audit.BackendConfig
|
||||
ReqErr error
|
||||
ReqAuth []*logical.Auth
|
||||
Req []*logical.Request
|
||||
ReqHeaders []map[string][]string
|
||||
ReqNonHMACKeys []string
|
||||
ReqErrs []error
|
||||
|
||||
RespErr error
|
||||
RespAuth []*logical.Auth
|
||||
RespReq []*logical.Request
|
||||
Resp []*logical.Response
|
||||
RespNonHMACKeys []string
|
||||
RespReqNonHMACKeys []string
|
||||
RespErrs []error
|
||||
|
||||
salt *salt.Salt
|
||||
saltMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (n *NoopAudit) LogRequest(ctx context.Context, in *logical.LogInput) error {
|
||||
n.ReqAuth = append(n.ReqAuth, in.Auth)
|
||||
n.Req = append(n.Req, in.Request)
|
||||
n.ReqHeaders = append(n.ReqHeaders, in.Request.Headers)
|
||||
n.ReqNonHMACKeys = in.NonHMACReqDataKeys
|
||||
n.ReqErrs = append(n.ReqErrs, in.OuterErr)
|
||||
return n.ReqErr
|
||||
}
|
||||
|
||||
func (n *NoopAudit) LogResponse(ctx context.Context, in *logical.LogInput) error {
|
||||
n.RespAuth = append(n.RespAuth, in.Auth)
|
||||
n.RespReq = append(n.RespReq, in.Request)
|
||||
n.Resp = append(n.Resp, in.Response)
|
||||
n.RespErrs = append(n.RespErrs, in.OuterErr)
|
||||
|
||||
if in.Response != nil {
|
||||
n.RespNonHMACKeys = in.NonHMACRespDataKeys
|
||||
n.RespReqNonHMACKeys = in.NonHMACReqDataKeys
|
||||
}
|
||||
|
||||
return n.RespErr
|
||||
}
|
||||
|
||||
func (n *NoopAudit) Salt(ctx context.Context) (*salt.Salt, error) {
|
||||
n.saltMutex.RLock()
|
||||
if n.salt != nil {
|
||||
defer n.saltMutex.RUnlock()
|
||||
return n.salt, nil
|
||||
}
|
||||
n.saltMutex.RUnlock()
|
||||
n.saltMutex.Lock()
|
||||
defer n.saltMutex.Unlock()
|
||||
if n.salt != nil {
|
||||
return n.salt, nil
|
||||
}
|
||||
salt, err := salt.NewSalt(ctx, n.Config.SaltView, n.Config.SaltConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n.salt = salt
|
||||
return salt, nil
|
||||
}
|
||||
|
||||
func (n *NoopAudit) GetHash(ctx context.Context, data string) (string, error) {
|
||||
salt, err := n.Salt(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return salt.GetIdentifiedHMAC(data), nil
|
||||
}
|
||||
|
||||
func (n *NoopAudit) Reload(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NoopAudit) Invalidate(ctx context.Context) {
|
||||
n.saltMutex.Lock()
|
||||
defer n.saltMutex.Unlock()
|
||||
n.salt = nil
|
||||
}
|
||||
|
||||
func TestAudit_ReadOnlyViewDuringMount(t *testing.T) {
|
||||
c, _, _ := TestCoreUnsealed(t)
|
||||
c.auditBackends["noop"] = func(ctx context.Context, config *audit.BackendConfig) (audit.Backend, error) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/go-test/deep"
|
||||
"github.com/hashicorp/errwrap"
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/vault/audit"
|
||||
"github.com/hashicorp/vault/helper/namespace"
|
||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||
@@ -904,7 +904,6 @@ func TestCore_HandleRequest_AuditTrail_noHMACKeys(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we get a client token
|
||||
func TestCore_HandleLogin_AuditTrail(t *testing.T) {
|
||||
// Create a badass credential backend that always logs in as armon
|
||||
noop := &NoopAudit{}
|
||||
|
||||
@@ -1739,3 +1739,85 @@ func (m *mockBuiltinRegistry) Keys(pluginType consts.PluginType) []string {
|
||||
func (m *mockBuiltinRegistry) Contains(name string, pluginType consts.PluginType) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type NoopAudit struct {
|
||||
Config *audit.BackendConfig
|
||||
ReqErr error
|
||||
ReqAuth []*logical.Auth
|
||||
Req []*logical.Request
|
||||
ReqHeaders []map[string][]string
|
||||
ReqNonHMACKeys []string
|
||||
ReqErrs []error
|
||||
|
||||
RespErr error
|
||||
RespAuth []*logical.Auth
|
||||
RespReq []*logical.Request
|
||||
Resp []*logical.Response
|
||||
RespNonHMACKeys []string
|
||||
RespReqNonHMACKeys []string
|
||||
RespErrs []error
|
||||
|
||||
salt *salt.Salt
|
||||
saltMutex sync.RWMutex
|
||||
}
|
||||
|
||||
func (n *NoopAudit) LogRequest(ctx context.Context, in *logical.LogInput) error {
|
||||
n.ReqAuth = append(n.ReqAuth, in.Auth)
|
||||
n.Req = append(n.Req, in.Request)
|
||||
n.ReqHeaders = append(n.ReqHeaders, in.Request.Headers)
|
||||
n.ReqNonHMACKeys = in.NonHMACReqDataKeys
|
||||
n.ReqErrs = append(n.ReqErrs, in.OuterErr)
|
||||
return n.ReqErr
|
||||
}
|
||||
|
||||
func (n *NoopAudit) LogResponse(ctx context.Context, in *logical.LogInput) error {
|
||||
n.RespAuth = append(n.RespAuth, in.Auth)
|
||||
n.RespReq = append(n.RespReq, in.Request)
|
||||
n.Resp = append(n.Resp, in.Response)
|
||||
n.RespErrs = append(n.RespErrs, in.OuterErr)
|
||||
|
||||
if in.Response != nil {
|
||||
n.RespNonHMACKeys = in.NonHMACRespDataKeys
|
||||
n.RespReqNonHMACKeys = in.NonHMACReqDataKeys
|
||||
}
|
||||
|
||||
return n.RespErr
|
||||
}
|
||||
|
||||
func (n *NoopAudit) Salt(ctx context.Context) (*salt.Salt, error) {
|
||||
n.saltMutex.RLock()
|
||||
if n.salt != nil {
|
||||
defer n.saltMutex.RUnlock()
|
||||
return n.salt, nil
|
||||
}
|
||||
n.saltMutex.RUnlock()
|
||||
n.saltMutex.Lock()
|
||||
defer n.saltMutex.Unlock()
|
||||
if n.salt != nil {
|
||||
return n.salt, nil
|
||||
}
|
||||
salt, err := salt.NewSalt(ctx, n.Config.SaltView, n.Config.SaltConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n.salt = salt
|
||||
return salt, nil
|
||||
}
|
||||
|
||||
func (n *NoopAudit) GetHash(ctx context.Context, data string) (string, error) {
|
||||
salt, err := n.Salt(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return salt.GetIdentifiedHMAC(data), nil
|
||||
}
|
||||
|
||||
func (n *NoopAudit) Reload(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NoopAudit) Invalidate(ctx context.Context) {
|
||||
n.saltMutex.Lock()
|
||||
defer n.saltMutex.Unlock()
|
||||
n.salt = nil
|
||||
}
|
||||
|
||||
@@ -309,16 +309,46 @@ DONELISTHANDLING:
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ValidateWrappingToken checks whether a token is a wrapping token.
|
||||
func (c *Core) ValidateWrappingToken(ctx context.Context, req *logical.Request) (bool, error) {
|
||||
// validateWrappingToken checks whether a token is a wrapping token. The passed
|
||||
// in logical request will be updated if the wrapping token was provided within
|
||||
// a JWT token.
|
||||
func (c *Core) ValidateWrappingToken(ctx context.Context, req *logical.Request) (valid bool, err error) {
|
||||
defer func() {
|
||||
// Perform audit logging before returning if there's an issue with checking
|
||||
// the wrapping token
|
||||
if err != nil || !valid {
|
||||
// We log the Auth object like so here since the wrapping token can
|
||||
// come from the header, which gets set as the ClientToken
|
||||
auth := &logical.Auth{
|
||||
ClientToken: req.ClientToken,
|
||||
Accessor: req.ClientTokenAccessor,
|
||||
}
|
||||
|
||||
logInput := &logical.LogInput{
|
||||
Auth: auth,
|
||||
Request: req,
|
||||
}
|
||||
if err != nil {
|
||||
logInput.OuterErr = errors.New("error validating wrapping token")
|
||||
}
|
||||
if !valid {
|
||||
logInput.OuterErr = consts.ErrInvalidWrappingToken
|
||||
}
|
||||
if err := c.auditBroker.LogRequest(ctx, logInput, c.auditedHeaders); err != nil {
|
||||
c.logger.Error("failed to audit request", "path", req.Path, "error", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if req == nil {
|
||||
return false, fmt.Errorf("invalid request")
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
var token string
|
||||
var thirdParty bool
|
||||
|
||||
// Check if the wrapping token is coming from the request body, and if not
|
||||
// assume that req.ClientToken is the wrapping token
|
||||
if req.Data != nil && req.Data["token"] != nil {
|
||||
thirdParty = true
|
||||
if tokenStr, ok := req.Data["token"].(string); !ok {
|
||||
@@ -365,6 +395,7 @@ func (c *Core) ValidateWrappingToken(ctx context.Context, req *logical.Request)
|
||||
} else {
|
||||
req.Data["token"] = claims.ID
|
||||
}
|
||||
|
||||
token = claims.ID
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user