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.
This commit is contained in:
Jeff Mitchell 2017-01-04 23:50:24 -05:00
parent 066038bebd
commit 64fc18e523
6 changed files with 49 additions and 62 deletions

View File

@ -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
}

View File

@ -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"}
`

View File

@ -34,7 +34,7 @@ func TestFormatJSONx_formatRequest(t *testing.T) {
},
errors.New("this is an error"),
"",
`<json:object name="auth"><json:string name="accessor"></json:string><json:string name="client_token"></json:string><json:string name="display_name"></json:string><json:null name="metadata" /><json:array name="policies"><json:string>root</json:string></json:array></json:object><json:string name="error">this is an error</json:string><json:object name="request"><json:string name="client_token"></json:string><json:string name="client_token_accessor"></json:string><json:null name="data" /><json:string name="id"></json:string><json:string name="operation">update</json:string><json:string name="path">/foo</json:string><json:string name="remote_address">127.0.0.1</json:string><json:object name="wrap_info"><json:string name="format"></json:string><json:number name="ttl">60</json:number></json:object></json:object><json:string name="type">request</json:string>`,
`<json:object name="auth"><json:string name="accessor"></json:string><json:string name="client_token"></json:string><json:string name="display_name"></json:string><json:null name="metadata" /><json:array name="policies"><json:string>root</json:string></json:array></json:object><json:string name="error">this is an error</json:string><json:object name="request"><json:string name="client_token"></json:string><json:string name="client_token_accessor"></json:string><json:null name="data" /><json:string name="id"></json:string><json:string name="operation">update</json:string><json:string name="path">/foo</json:string><json:string name="remote_address">127.0.0.1</json:string><json:number name="wrap_ttl">60</json:number></json:object><json:string name="type">request</json:string>`,
},
}

View File

@ -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 {

View File

@ -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)

View File

@ -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
}