open-vault/audit/format.go

407 lines
10 KiB
Go
Raw Normal View History

2016-09-21 14:29:42 +00:00
package audit
import (
"fmt"
"io"
"strings"
2016-09-21 14:29:42 +00:00
"time"
"github.com/SermoDigital/jose/jws"
2016-09-21 14:29:42 +00:00
"github.com/hashicorp/vault/logical"
"github.com/mitchellh/copystructure"
)
type AuditFormatWriter interface {
WriteRequest(io.Writer, *AuditRequestEntry) error
WriteResponse(io.Writer, *AuditResponseEntry) error
}
// AuditFormatter implements the Formatter interface, and allows the underlying
// marshaller to be swapped out
type AuditFormatter struct {
AuditFormatWriter
}
func (f *AuditFormatter) FormatRequest(
w io.Writer,
config FormatterConfig,
auth *logical.Auth,
req *logical.Request,
inErr error) error {
if req == nil {
return fmt.Errorf("request to request-audit a nil request")
}
2016-09-21 14:29:42 +00:00
if w == nil {
return fmt.Errorf("writer for audit request is nil")
}
if f.AuditFormatWriter == nil {
return fmt.Errorf("no format writer specified")
}
if !config.Raw {
// Before we copy the structure we must nil out some data
// otherwise we will cause reflection to panic and die
if req.Connection != nil && req.Connection.ConnState != nil {
origReq := req
origState := req.Connection.ConnState
req.Connection.ConnState = nil
defer func() {
origReq.Connection.ConnState = origState
}()
}
// Copy the auth structure
if auth != nil {
cp, err := copystructure.Copy(auth)
if err != nil {
return err
}
auth = cp.(*logical.Auth)
2016-09-21 14:29:42 +00:00
}
cp, err := copystructure.Copy(req)
2016-09-21 14:29:42 +00:00
if err != nil {
return err
}
req = cp.(*logical.Request)
// Hash any sensitive information
if auth != nil {
if err := Hash(config.Salt, auth); err != nil {
return err
}
2016-09-21 14:29:42 +00:00
}
// Cache and restore accessor in the request
var clientTokenAccessor string
if !config.HMACAccessor && req != nil && req.ClientTokenAccessor != "" {
clientTokenAccessor = req.ClientTokenAccessor
}
2016-09-21 14:29:42 +00:00
if err := Hash(config.Salt, req); err != nil {
return err
}
if clientTokenAccessor != "" {
req.ClientTokenAccessor = clientTokenAccessor
}
2016-09-21 14:29:42 +00:00
}
// If auth is nil, make an empty one
if auth == nil {
auth = new(logical.Auth)
}
var errString string
if inErr != nil {
errString = inErr.Error()
2016-09-21 14:29:42 +00:00
}
reqEntry := &AuditRequestEntry{
Type: "request",
Error: errString,
Auth: AuditAuth{
DisplayName: auth.DisplayName,
Policies: auth.Policies,
Metadata: auth.Metadata,
},
Request: AuditRequest{
ID: req.ID,
ClientToken: req.ClientToken,
ClientTokenAccessor: req.ClientTokenAccessor,
Operation: req.Operation,
Path: req.Path,
Data: req.Data,
RemoteAddr: getRemoteAddr(req),
ReplicationCluster: req.ReplicationCluster,
Headers: req.Headers,
2016-09-21 14:29:42 +00:00
},
}
if req.WrapInfo != nil {
reqEntry.Request.WrapTTL = int(req.WrapInfo.TTL / time.Second)
}
2016-09-21 14:29:42 +00:00
if !config.OmitTime {
reqEntry.Time = time.Now().UTC().Format(time.RFC3339)
}
return f.AuditFormatWriter.WriteRequest(w, reqEntry)
}
func (f *AuditFormatter) FormatResponse(
w io.Writer,
config FormatterConfig,
auth *logical.Auth,
req *logical.Request,
resp *logical.Response,
inErr error) error {
if req == nil {
return fmt.Errorf("request to response-audit a nil request")
}
2016-09-21 14:29:42 +00:00
if w == nil {
return fmt.Errorf("writer for audit request is nil")
}
if f.AuditFormatWriter == nil {
return fmt.Errorf("no format writer specified")
}
if !config.Raw {
// Before we copy the structure we must nil out some data
// otherwise we will cause reflection to panic and die
if req.Connection != nil && req.Connection.ConnState != nil {
origReq := req
origState := req.Connection.ConnState
req.Connection.ConnState = nil
defer func() {
origReq.Connection.ConnState = origState
}()
}
// Copy the auth structure
if auth != nil {
cp, err := copystructure.Copy(auth)
if err != nil {
return err
}
auth = cp.(*logical.Auth)
2016-09-21 14:29:42 +00:00
}
cp, err := copystructure.Copy(req)
2016-09-21 14:29:42 +00:00
if err != nil {
return err
}
req = cp.(*logical.Request)
if resp != nil {
cp, err := copystructure.Copy(resp)
if err != nil {
return err
}
resp = cp.(*logical.Response)
2016-09-21 14:29:42 +00:00
}
// Hash any sensitive information
// Cache and restore accessor in the auth
if auth != nil {
var accessor string
if !config.HMACAccessor && auth.Accessor != "" {
accessor = auth.Accessor
}
if err := Hash(config.Salt, auth); err != nil {
return err
}
if accessor != "" {
auth.Accessor = accessor
}
2016-09-21 14:29:42 +00:00
}
// Cache and restore accessor in the request
var clientTokenAccessor string
if !config.HMACAccessor && req != nil && req.ClientTokenAccessor != "" {
clientTokenAccessor = req.ClientTokenAccessor
}
2016-09-21 14:29:42 +00:00
if err := Hash(config.Salt, req); err != nil {
return err
}
if clientTokenAccessor != "" {
req.ClientTokenAccessor = clientTokenAccessor
}
2016-09-21 14:29:42 +00:00
// Cache and restore accessor in the response
if resp != nil {
var accessor, wrappedAccessor string
if !config.HMACAccessor && resp != nil && resp.Auth != nil && resp.Auth.Accessor != "" {
accessor = resp.Auth.Accessor
}
if !config.HMACAccessor && resp != nil && resp.WrapInfo != nil && resp.WrapInfo.WrappedAccessor != "" {
wrappedAccessor = resp.WrapInfo.WrappedAccessor
}
if err := Hash(config.Salt, resp); err != nil {
return err
}
if accessor != "" {
resp.Auth.Accessor = accessor
}
if wrappedAccessor != "" {
resp.WrapInfo.WrappedAccessor = wrappedAccessor
}
2016-09-21 14:29:42 +00:00
}
}
// If things are nil, make empty to avoid panics
if auth == nil {
auth = new(logical.Auth)
}
if resp == nil {
resp = new(logical.Response)
}
var errString string
if inErr != nil {
errString = inErr.Error()
2016-09-21 14:29:42 +00:00
}
var respAuth *AuditAuth
if resp.Auth != nil {
respAuth = &AuditAuth{
ClientToken: resp.Auth.ClientToken,
Accessor: resp.Auth.Accessor,
DisplayName: resp.Auth.DisplayName,
Policies: resp.Auth.Policies,
Metadata: resp.Auth.Metadata,
}
}
var respSecret *AuditSecret
if resp.Secret != nil {
respSecret = &AuditSecret{
LeaseID: resp.Secret.LeaseID,
}
}
2017-01-04 21:44:03 +00:00
var respWrapInfo *AuditResponseWrapInfo
2016-09-21 14:29:42 +00:00
if resp.WrapInfo != nil {
token := resp.WrapInfo.Token
if jwtToken := parseVaultTokenFromJWT(token); jwtToken != nil {
token = *jwtToken
}
2017-01-04 21:44:03 +00:00
respWrapInfo = &AuditResponseWrapInfo{
2016-09-21 14:29:42 +00:00
TTL: int(resp.WrapInfo.TTL / time.Second),
Token: token,
CreationTime: resp.WrapInfo.CreationTime.Format(time.RFC3339Nano),
2016-09-21 14:29:42 +00:00
WrappedAccessor: resp.WrapInfo.WrappedAccessor,
}
}
respEntry := &AuditResponseEntry{
Type: "response",
Error: errString,
Auth: AuditAuth{
DisplayName: auth.DisplayName,
Policies: auth.Policies,
Metadata: auth.Metadata,
},
Request: AuditRequest{
ID: req.ID,
ClientToken: req.ClientToken,
ClientTokenAccessor: req.ClientTokenAccessor,
Operation: req.Operation,
Path: req.Path,
Data: req.Data,
RemoteAddr: getRemoteAddr(req),
ReplicationCluster: req.ReplicationCluster,
Headers: req.Headers,
2016-09-21 14:29:42 +00:00
},
Response: AuditResponse{
Auth: respAuth,
Secret: respSecret,
Data: resp.Data,
Redirect: resp.Redirect,
WrapInfo: respWrapInfo,
},
}
if req.WrapInfo != nil {
respEntry.Request.WrapTTL = int(req.WrapInfo.TTL / time.Second)
}
2016-09-21 14:29:42 +00:00
if !config.OmitTime {
respEntry.Time = time.Now().UTC().Format(time.RFC3339)
}
return f.AuditFormatWriter.WriteResponse(w, respEntry)
}
// AuditRequest is the structure of a request audit log entry in Audit.
type AuditRequestEntry struct {
Time string `json:"time,omitempty"`
Type string `json:"type"`
Auth AuditAuth `json:"auth"`
Request AuditRequest `json:"request"`
Error string `json:"error"`
}
// AuditResponseEntry is the structure of a response audit log entry in Audit.
type AuditResponseEntry struct {
Time string `json:"time,omitempty"`
Type string `json:"type"`
Auth AuditAuth `json:"auth"`
Request AuditRequest `json:"request"`
Response AuditResponse `json:"response"`
Error string `json:"error"`
2016-09-21 14:29:42 +00:00
}
type AuditRequest struct {
ID string `json:"id"`
ReplicationCluster string `json:"replication_cluster,omitempty"`
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"`
Headers map[string][]string `json:"headers"`
2016-09-21 14:29:42 +00:00
}
type AuditResponse struct {
Auth *AuditAuth `json:"auth,omitempty"`
2016-09-29 19:03:47 +00:00
Secret *AuditSecret `json:"secret,omitempty"`
Data map[string]interface{} `json:"data,omitempty"`
Redirect string `json:"redirect,omitempty"`
2017-01-04 21:44:03 +00:00
WrapInfo *AuditResponseWrapInfo `json:"wrap_info,omitempty"`
2016-09-21 14:29:42 +00:00
}
type AuditAuth struct {
2016-09-29 19:03:47 +00:00
ClientToken string `json:"client_token"`
Accessor string `json:"accessor"`
2016-09-21 14:29:42 +00:00
DisplayName string `json:"display_name"`
Policies []string `json:"policies"`
Metadata map[string]string `json:"metadata"`
}
type AuditSecret struct {
LeaseID string `json:"lease_id"`
}
2017-01-04 21:44:03 +00:00
type AuditResponseWrapInfo struct {
TTL int `json:"ttl"`
Token string `json:"token"`
CreationTime string `json:"creation_time"`
WrappedAccessor string `json:"wrapped_accessor,omitempty"`
2016-09-21 14:29:42 +00:00
}
// getRemoteAddr safely gets the remote address avoiding a nil pointer
func getRemoteAddr(req *logical.Request) string {
if req != nil && req.Connection != nil {
return req.Connection.RemoteAddr
}
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
}