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:
parent
066038bebd
commit
64fc18e523
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"}
|
||||
`
|
||||
|
|
|
@ -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>`,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue