From 92b7707c656a4ccee606f9922e08fc40685b2db2 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Wed, 4 Jan 2017 23:50:24 -0500 Subject: [PATCH] When a JWT wrapping token is returned, audit the inner token both for request and response. This makes it far easier to properly check validity elsewhere in Vault because we simply replace the request client token with the inner value. --- audit/format.go | 58 ++++++++++++++++++++++---------------- audit/format_json_test.go | 2 +- audit/format_jsonx_test.go | 2 +- vault/cluster.go | 9 +++--- vault/logical_system.go | 12 -------- vault/wrapping.go | 28 +++++++----------- 6 files changed, 49 insertions(+), 62 deletions(-) diff --git a/audit/format.go b/audit/format.go index c1d0126ed8..60833a2150 100644 --- a/audit/format.go +++ b/audit/format.go @@ -3,8 +3,10 @@ package audit import ( "fmt" "io" + "strings" "time" + "github.com/SermoDigital/jose/jws" "github.com/hashicorp/vault/logical" "github.com/mitchellh/copystructure" ) @@ -87,14 +89,6 @@ func (f *AuditFormatter) FormatRequest( errString = err.Error() } - var reqWrapInfo *AuditRequestWrapInfo - if req.WrapInfo != nil { - reqWrapInfo = &AuditRequestWrapInfo{ - TTL: int(req.WrapInfo.TTL / time.Second), - Format: req.WrapInfo.Format, - } - } - reqEntry := &AuditRequestEntry{ Type: "request", Error: errString, @@ -113,10 +107,13 @@ func (f *AuditFormatter) FormatRequest( Path: req.Path, Data: req.Data, RemoteAddr: getRemoteAddr(req), - WrapInfo: reqWrapInfo, }, } + if req.WrapInfo != nil { + reqEntry.Request.WrapTTL = int(req.WrapInfo.TTL / time.Second) + } + if !config.OmitTime { reqEntry.Time = time.Now().UTC().Format(time.RFC3339) } @@ -246,19 +243,15 @@ func (f *AuditFormatter) FormatResponse( } } - var reqWrapInfo *AuditRequestWrapInfo - if req.WrapInfo != nil { - reqWrapInfo = &AuditRequestWrapInfo{ - TTL: int(req.WrapInfo.TTL / time.Second), - Format: req.WrapInfo.Format, - } - } - var respWrapInfo *AuditResponseWrapInfo if resp.WrapInfo != nil { + token := resp.WrapInfo.Token + if jwtToken := parseVaultTokenFromJWT(token); jwtToken != nil { + token = *jwtToken + } respWrapInfo = &AuditResponseWrapInfo{ TTL: int(resp.WrapInfo.TTL / time.Second), - Token: resp.WrapInfo.Token, + Token: token, CreationTime: resp.WrapInfo.CreationTime.Format(time.RFC3339Nano), WrappedAccessor: resp.WrapInfo.WrappedAccessor, } @@ -282,7 +275,6 @@ func (f *AuditFormatter) FormatResponse( Path: req.Path, Data: req.Data, RemoteAddr: getRemoteAddr(req), - WrapInfo: reqWrapInfo, }, Response: AuditResponse{ @@ -294,6 +286,10 @@ func (f *AuditFormatter) FormatResponse( }, } + if req.WrapInfo != nil { + respEntry.Request.WrapTTL = int(req.WrapInfo.TTL / time.Second) + } + if !config.OmitTime { respEntry.Time = time.Now().UTC().Format(time.RFC3339) } @@ -328,7 +324,7 @@ type AuditRequest struct { Path string `json:"path"` Data map[string]interface{} `json:"data"` RemoteAddr string `json:"remote_address"` - WrapInfo *AuditRequestWrapInfo `json:"wrap_info,omitempty"` + WrapTTL int `json:"wrap_ttl"` } type AuditResponse struct { @@ -351,11 +347,6 @@ type AuditSecret struct { LeaseID string `json:"lease_id"` } -type AuditRequestWrapInfo struct { - TTL int `json:"ttl"` - Format string `json:"format"` -} - type AuditResponseWrapInfo struct { TTL int `json:"ttl"` Token string `json:"token"` @@ -370,3 +361,20 @@ func getRemoteAddr(req *logical.Request) string { } return "" } + +// parseVaultTokenFromJWT returns a string iff the token was a JWT and we could +// extract the original token ID from inside +func parseVaultTokenFromJWT(token string) *string { + if strings.Count(token, ".") != 2 { + return nil + } + + wt, err := jws.ParseJWT([]byte(token)) + if err != nil || wt == nil { + return nil + } + + result, _ := wt.Claims().JWTID() + + return &result +} diff --git a/audit/format_json_test.go b/audit/format_json_test.go index 5b2b098484..7fb7a8a15f 100644 --- a/audit/format_json_test.go +++ b/audit/format_json_test.go @@ -76,5 +76,5 @@ func TestFormatJSON_formatRequest(t *testing.T) { } } -const testFormatJSONReqBasicStr = `{"time":"2015-08-05T13:45:46Z","type":"request","auth":{"display_name":"","policies":["root"],"metadata":null},"request":{"operation":"update","path":"/foo","data":null,"wrap_ttl":60,"remote_address":"127.0.0.1","wrap_info":{"ttl":60,"format":""}},"error":"this is an error"} +const testFormatJSONReqBasicStr = `{"time":"2015-08-05T13:45:46Z","type":"request","auth":{"display_name":"","policies":["root"],"metadata":null},"request":{"operation":"update","path":"/foo","data":null,"wrap_ttl":60,"remote_address":"127.0.0.1"},"error":"this is an error"} ` diff --git a/audit/format_jsonx_test.go b/audit/format_jsonx_test.go index 1324b534a5..40d0bc572b 100644 --- a/audit/format_jsonx_test.go +++ b/audit/format_jsonx_test.go @@ -34,7 +34,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/vault/cluster.go b/vault/cluster.go index 69c171fbfc..d2dd63704a 100644 --- a/vault/cluster.go +++ b/vault/cluster.go @@ -42,11 +42,10 @@ var ( // This can be one of a few key types so the different params may or may not be filled type clusterKeyParams struct { - Type string `json:"type"` - X *big.Int `json:"x,omitempty"` - Y *big.Int `json:"y,omitempty"` - D *big.Int `json:"d,omitempty"` - ED25519Key []byte `json:"ed25519_key,omitempty"` + Type string `json:"type"` + X *big.Int `json:"x,omitempty"` + Y *big.Int `json:"y,omitempty"` + D *big.Int `json:"d,omitempty"` } type activeConnection struct { diff --git a/vault/logical_system.go b/vault/logical_system.go index f8519527ff..534651d52a 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -1528,10 +1528,6 @@ func (b *SystemBackend) handleWrappingUnwrap( token = req.ClientToken } - if wt := b.Core.parseVaultTokenFromJWT(token); wt != nil { - token = *wt - } - if thirdParty { // Use the token to decrement the use count to avoid a second operation on the token. _, err := b.Core.tokenStore.UseTokenByID(token) @@ -1592,10 +1588,6 @@ func (b *SystemBackend) handleWrappingLookup( return logical.ErrorResponse("missing \"token\" value in input"), logical.ErrInvalidRequest } - if wt := b.Core.parseVaultTokenFromJWT(token); wt != nil { - token = *wt - } - cubbyReq := &logical.Request{ Operation: logical.ReadOperation, Path: "cubbyhole/wrapinfo", @@ -1652,10 +1644,6 @@ func (b *SystemBackend) handleWrappingRewrap( token = req.ClientToken } - if wt := b.Core.parseVaultTokenFromJWT(token); wt != nil { - token = *wt - } - if thirdParty { // Use the token to decrement the use count to avoid a second operation on the token. _, err := b.Core.tokenStore.UseTokenByID(token) diff --git a/vault/wrapping.go b/vault/wrapping.go index 9e665cc837..0dc2e9b59a 100644 --- a/vault/wrapping.go +++ b/vault/wrapping.go @@ -240,7 +240,9 @@ func (c *Core) ValidateWrappingToken(req *logical.Request) (bool, error) { var err error var token string + var thirdParty bool if req.Data != nil && req.Data["token"] != nil { + thirdParty = true if tokenStr, ok := req.Data["token"].(string); !ok { return false, fmt.Errorf("could not decode token in request body") } else if tokenStr == "" { @@ -264,6 +266,14 @@ func (c *Core) ValidateWrappingToken(req *logical.Request) (bool, error) { return false, errwrap.Wrapf("wrapping token signature could not be validated: {{err}}", err) } token, _ = wt.Claims().JWTID() + // We override the given request client token so that the rest of + // Vault sees the real value. This also ensures audit logs are + // consistent with the actual token that was issued. + if !thirdParty { + req.ClientToken = token + } else { + req.Data["token"] = token + } } } @@ -298,21 +308,3 @@ func (c *Core) ValidateWrappingToken(req *logical.Request) (bool, error) { return true, nil } - -// parseVaultTokenFromJWT returns a string iff the token was a JWT and we could -// extract the original token ID from inside -func (c *Core) parseVaultTokenFromJWT(token string) *string { - var result string - if strings.Count(token, ".") != 2 { - return nil - } - - wt, err := jws.ParseJWT([]byte(token)) - if err != nil || wt == nil { - return nil - } - - result, _ = wt.Claims().JWTID() - - return &result -}