diff --git a/audit/format.go b/audit/format.go index 3e5bce19ea..d2171642c7 100644 --- a/audit/format.go +++ b/audit/format.go @@ -64,9 +64,18 @@ func (f *AuditFormatter) FormatRequest( if err := Hash(config.Salt, auth); err != nil { return err } + + // Cache and restore accessor in the request + var clientTokenAccessor string + if !config.HMACAccessor && req != nil && req.ClientTokenAccessor != "" { + clientTokenAccessor = req.ClientTokenAccessor + } if err := Hash(config.Salt, req); err != nil { return err } + if clientTokenAccessor != "" { + req.ClientTokenAccessor = clientTokenAccessor + } } // If auth is nil, make an empty one @@ -89,13 +98,14 @@ func (f *AuditFormatter) FormatRequest( }, Request: AuditRequest{ - ID: req.ID, - ClientToken: req.ClientToken, - Operation: req.Operation, - Path: req.Path, - Data: req.Data, - RemoteAddr: getRemoteAddr(req), - WrapTTL: int(req.WrapTTL / time.Second), + ID: req.ID, + ClientToken: req.ClientToken, + ClientTokenAccessor: req.ClientTokenAccessor, + Operation: req.Operation, + Path: req.Path, + Data: req.Data, + RemoteAddr: getRemoteAddr(req), + WrapTTL: int(req.WrapTTL / time.Second), }, } @@ -167,9 +177,17 @@ func (f *AuditFormatter) FormatResponse( auth.Accessor = accessor } + // Cache and restore accessor in the request + var clientTokenAccessor string + if !config.HMACAccessor && req != nil && req.ClientTokenAccessor != "" { + clientTokenAccessor = req.ClientTokenAccessor + } if err := Hash(config.Salt, req); err != nil { return err } + if clientTokenAccessor != "" { + req.ClientTokenAccessor = clientTokenAccessor + } // Cache and restore accessor in the response accessor = "" @@ -241,13 +259,14 @@ func (f *AuditFormatter) FormatResponse( }, Request: AuditRequest{ - ID: req.ID, - ClientToken: req.ClientToken, - Operation: req.Operation, - Path: req.Path, - Data: req.Data, - RemoteAddr: getRemoteAddr(req), - WrapTTL: int(req.WrapTTL / time.Second), + ID: req.ID, + ClientToken: req.ClientToken, + ClientTokenAccessor: req.ClientTokenAccessor, + Operation: req.Operation, + Path: req.Path, + Data: req.Data, + RemoteAddr: getRemoteAddr(req), + WrapTTL: int(req.WrapTTL / time.Second), }, Response: AuditResponse{ @@ -286,13 +305,14 @@ type AuditResponseEntry struct { } type AuditRequest struct { - ID string `json:"id"` - Operation logical.Operation `json:"operation"` - ClientToken string `json:"client_token"` - Path string `json:"path"` - Data map[string]interface{} `json:"data"` - RemoteAddr string `json:"remote_address"` - WrapTTL int `json:"wrap_ttl"` + ID string `json:"id"` + Operation logical.Operation `json:"operation"` + ClientToken string `json:"client_token"` + ClientTokenAccessor string `json:"client_token_accessor"` + Path string `json:"path"` + Data map[string]interface{} `json:"data"` + RemoteAddr string `json:"remote_address"` + WrapTTL int `json:"wrap_ttl"` } type AuditResponse struct { diff --git a/audit/format_jsonx_test.go b/audit/format_jsonx_test.go index 50da62b5d3..16515a84f1 100644 --- a/audit/format_jsonx_test.go +++ b/audit/format_jsonx_test.go @@ -32,7 +32,7 @@ func TestFormatJSONx_formatRequest(t *testing.T) { }, errors.New("this is an error"), "", - `rootthis is an errorupdate/foo127.0.0.160request`, + `rootthis is an errorupdate/foo127.0.0.160request`, }, } diff --git a/audit/hashstructure.go b/audit/hashstructure.go index f428847b0d..ff51f5ea15 100644 --- a/audit/hashstructure.go +++ b/audit/hashstructure.go @@ -49,6 +49,10 @@ func Hash(salter *salt.Salt, raw interface{}) error { s.ClientToken = fn(s.ClientToken) } + if s.ClientTokenAccessor != "" { + s.ClientTokenAccessor = fn(s.ClientTokenAccessor) + } + data, err := HashStructure(s.Data, fn) if err != nil { return err diff --git a/http/handler.go b/http/handler.go index 4ad99216d5..775fce9970 100644 --- a/http/handler.go +++ b/http/handler.go @@ -245,10 +245,19 @@ func respondStandby(core *vault.Core, w http.ResponseWriter, reqURL *url.URL) { } // requestAuth adds the token to the logical.Request if it exists. -func requestAuth(r *http.Request, req *logical.Request) *logical.Request { +func requestAuth(core *vault.Core, r *http.Request, req *logical.Request) *logical.Request { // Attach the header value if we have it if v := r.Header.Get(AuthHeaderName); v != "" { req.ClientToken = v + + // Also attach the accessor if we have it. This doesn't fail if it + // doesn't exist because the request may be to an unauthenticated + // endpoint/login endpoint where a bad current token doesn't matter, or + // a token from a Vault version pre-accessors. + te, err := core.LookupToken(v) + if err == nil && te != nil { + req.ClientTokenAccessor = te.Accessor + } } return req diff --git a/http/help.go b/http/help.go index e5aac56cde..6b617f9cf7 100644 --- a/http/help.go +++ b/http/help.go @@ -27,11 +27,13 @@ func handleHelp(core *vault.Core, w http.ResponseWriter, req *http.Request) { return } - resp, err := core.HandleRequest(requestAuth(req, &logical.Request{ + lreq := requestAuth(core, req, &logical.Request{ Operation: logical.HelpOperation, Path: path, Connection: getConnection(req), - })) + }) + + resp, err := core.HandleRequest(lreq) if err != nil { respondError(w, http.StatusInternalServerError, err) return diff --git a/http/logical.go b/http/logical.go index 2a84aac904..edf1eabf9b 100644 --- a/http/logical.go +++ b/http/logical.go @@ -16,7 +16,7 @@ import ( type PrepareRequestFunc func(*vault.Core, *logical.Request) error -func buildLogicalRequest(w http.ResponseWriter, r *http.Request) (*logical.Request, int, error) { +func buildLogicalRequest(core *vault.Core, w http.ResponseWriter, r *http.Request) (*logical.Request, int, error) { // Determine the path... if !strings.HasPrefix(r.URL.Path, "/v1/") { return nil, http.StatusNotFound, nil @@ -72,13 +72,14 @@ func buildLogicalRequest(w http.ResponseWriter, r *http.Request) (*logical.Reque return nil, http.StatusBadRequest, errwrap.Wrapf("failed to generate identifier for the request: {{err}}", err) } - req := requestAuth(r, &logical.Request{ + req := requestAuth(core, r, &logical.Request{ ID: request_id, Operation: op, Path: path, Data: data, Connection: getConnection(r), }) + req, err = requestWrapTTL(r, req) if err != nil { return nil, http.StatusBadRequest, errwrap.Wrapf("error parsing X-Vault-Wrap-TTL header: {{err}}", err) @@ -89,7 +90,7 @@ func buildLogicalRequest(w http.ResponseWriter, r *http.Request) (*logical.Reque func handleLogical(core *vault.Core, dataOnly bool, prepareRequestCallback PrepareRequestFunc) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - req, statusCode, err := buildLogicalRequest(w, r) + req, statusCode, err := buildLogicalRequest(core, w, r) if err != nil || statusCode != 0 { respondError(w, statusCode, err) return diff --git a/http/sys_seal.go b/http/sys_seal.go index 642410a833..2e02f7308a 100644 --- a/http/sys_seal.go +++ b/http/sys_seal.go @@ -15,7 +15,7 @@ import ( func handleSysSeal(core *vault.Core) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - req, statusCode, err := buildLogicalRequest(w, r) + req, statusCode, err := buildLogicalRequest(core, w, r) if err != nil || statusCode != 0 { respondError(w, statusCode, err) return @@ -40,7 +40,7 @@ func handleSysSeal(core *vault.Core) http.Handler { func handleSysStepDown(core *vault.Core) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - req, statusCode, err := buildLogicalRequest(w, r) + req, statusCode, err := buildLogicalRequest(core, w, r) if err != nil || statusCode != 0 { respondError(w, statusCode, err) return diff --git a/logical/request.go b/logical/request.go index 5cd7f69a99..e6f7a32475 100644 --- a/logical/request.go +++ b/logical/request.go @@ -47,6 +47,10 @@ type Request struct { // hashed. ClientToken string `json:"client_token" structs:"client_token" mapstructure:"client_token"` + // ClientTokenAccessor is provided to the core so that the it can get + // logged as part of request audit logging. + ClientTokenAccessor string `json:"client_token_accessor" structs:"client_token_accessor" mapstructure:"client_token_accessor"` + // DisplayName is provided to the logical backend to help associate // dynamic secrets with the source entity. This is not a sensitive // name, but is useful for operators. diff --git a/vault/core.go b/vault/core.go index 21c152a3e2..76333c5d8b 100644 --- a/vault/core.go +++ b/vault/core.go @@ -13,12 +13,12 @@ import ( "sync" "time" + "github.com/armon/go-metrics" log "github.com/mgutz/logxi/v1" "golang.org/x/net/context" "google.golang.org/grpc" - "github.com/armon/go-metrics" "github.com/hashicorp/errwrap" "github.com/hashicorp/go-multierror" "github.com/hashicorp/go-uuid" @@ -492,6 +492,23 @@ func (c *Core) Shutdown() error { return c.sealInternal() } +// LookupToken returns the properties of the token from the token store. This +// is particularly useful to fetch the accessor of the client token and get it +// populated in the logical request along with the client token. The accessor +// of the client token can get audit logged. +func (c *Core) LookupToken(token string) (*TokenEntry, error) { + if token == "" { + return nil, fmt.Errorf("missing client token") + } + + // Many tests don't have a token store running + if c.tokenStore == nil { + return nil, nil + } + + return c.tokenStore.Lookup(token) +} + func (c *Core) fetchACLandTokenEntry(req *logical.Request) (*ACL, *TokenEntry, error) { defer metrics.MeasureSince([]string{"core", "fetch_acl_and_token"}, time.Now())