2023-03-15 16:00:52 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2016-09-21 14:29:42 +00:00
|
|
|
package audit
|
|
|
|
|
|
|
|
import (
|
2018-03-08 19:21:11 +00:00
|
|
|
"context"
|
2019-05-22 22:52:53 +00:00
|
|
|
"crypto/tls"
|
2016-09-21 14:29:42 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2017-01-05 04:50:24 +00:00
|
|
|
"strings"
|
2016-09-21 14:29:42 +00:00
|
|
|
"time"
|
|
|
|
|
2019-03-20 18:54:03 +00:00
|
|
|
squarejwt "gopkg.in/square/go-jose.v2/jwt"
|
|
|
|
|
2018-09-18 03:03:00 +00:00
|
|
|
"github.com/hashicorp/vault/helper/namespace"
|
2019-04-12 21:54:35 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/helper/salt"
|
|
|
|
"github.com/hashicorp/vault/sdk/logical"
|
2016-09-21 14:29:42 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type AuditFormatWriter interface {
|
2019-07-02 22:18:40 +00:00
|
|
|
// WriteRequest writes the request entry to the writer or returns an error.
|
2016-09-21 14:29:42 +00:00
|
|
|
WriteRequest(io.Writer, *AuditRequestEntry) error
|
2019-07-02 22:18:40 +00:00
|
|
|
// WriteResponse writes the response entry to the writer or returns an error.
|
2016-09-21 14:29:42 +00:00
|
|
|
WriteResponse(io.Writer, *AuditResponseEntry) error
|
2019-07-02 22:18:40 +00:00
|
|
|
// Salt returns a non-nil salt or an error.
|
2018-03-08 19:21:11 +00:00
|
|
|
Salt(context.Context) (*salt.Salt, error)
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// AuditFormatter implements the Formatter interface, and allows the underlying
|
|
|
|
// marshaller to be swapped out
|
|
|
|
type AuditFormatter struct {
|
|
|
|
AuditFormatWriter
|
|
|
|
}
|
|
|
|
|
2018-03-02 17:18:39 +00:00
|
|
|
var _ Formatter = (*AuditFormatter)(nil)
|
|
|
|
|
2019-05-22 22:52:53 +00:00
|
|
|
func (f *AuditFormatter) FormatRequest(ctx context.Context, w io.Writer, config FormatterConfig, in *logical.LogInput) error {
|
2018-03-02 17:18:39 +00:00
|
|
|
if in == nil || in.Request == nil {
|
2017-02-16 18:09:53 +00:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2018-03-08 19:21:11 +00:00
|
|
|
salt, err := f.Salt(ctx)
|
2017-05-24 00:36:20 +00:00
|
|
|
if err != nil {
|
2021-04-22 15:20:59 +00:00
|
|
|
return fmt.Errorf("error fetching salt: %w", err)
|
2017-05-24 00:36:20 +00:00
|
|
|
}
|
|
|
|
|
2018-03-02 17:18:39 +00:00
|
|
|
// Set these to the input values at first
|
|
|
|
auth := in.Auth
|
|
|
|
req := in.Request
|
2019-05-22 22:52:53 +00:00
|
|
|
var connState *tls.ConnectionState
|
2019-07-02 22:18:40 +00:00
|
|
|
if auth == nil {
|
|
|
|
auth = new(logical.Auth)
|
|
|
|
}
|
2019-05-22 22:52:53 +00:00
|
|
|
|
|
|
|
if in.Request.Connection != nil && in.Request.Connection.ConnState != nil {
|
|
|
|
connState = in.Request.Connection.ConnState
|
|
|
|
}
|
2018-03-02 17:18:39 +00:00
|
|
|
|
2016-09-21 14:29:42 +00:00
|
|
|
if !config.Raw {
|
2019-07-02 22:18:40 +00:00
|
|
|
auth, err = HashAuth(salt, auth, config.HMACAccessor)
|
2016-09-21 14:29:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-07-02 22:18:40 +00:00
|
|
|
req, err = HashRequest(salt, req, config.HMACAccessor, in.NonHMACReqDataKeys)
|
|
|
|
if err != nil {
|
2016-09-21 14:29:42 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var errString string
|
2018-03-02 17:18:39 +00:00
|
|
|
if in.OuterErr != nil {
|
|
|
|
errString = in.OuterErr.Error()
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
|
|
|
|
2018-09-18 03:03:00 +00:00
|
|
|
ns, err := namespace.FromContext(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-05-22 22:52:53 +00:00
|
|
|
reqType := in.Type
|
|
|
|
if reqType == "" {
|
|
|
|
reqType = "request"
|
|
|
|
}
|
2016-09-21 14:29:42 +00:00
|
|
|
reqEntry := &AuditRequestEntry{
|
2019-05-22 22:52:53 +00:00
|
|
|
Type: reqType,
|
2016-09-21 14:29:42 +00:00
|
|
|
Error: errString,
|
|
|
|
|
2019-05-28 21:24:30 +00:00
|
|
|
Auth: &AuditAuth{
|
2018-09-18 03:03:00 +00:00
|
|
|
ClientToken: auth.ClientToken,
|
|
|
|
Accessor: auth.Accessor,
|
|
|
|
DisplayName: auth.DisplayName,
|
|
|
|
Policies: auth.Policies,
|
|
|
|
TokenPolicies: auth.TokenPolicies,
|
|
|
|
IdentityPolicies: auth.IdentityPolicies,
|
|
|
|
ExternalNamespacePolicies: auth.ExternalNamespacePolicies,
|
2019-06-14 14:17:04 +00:00
|
|
|
NoDefaultPolicy: auth.NoDefaultPolicy,
|
2018-09-18 03:03:00 +00:00
|
|
|
Metadata: auth.Metadata,
|
|
|
|
EntityID: auth.EntityID,
|
|
|
|
RemainingUses: req.ClientTokenRemainingUses,
|
2018-10-15 16:56:24 +00:00
|
|
|
TokenType: auth.TokenType.String(),
|
2020-05-29 17:30:47 +00:00
|
|
|
TokenTTL: int64(auth.TTL.Seconds()),
|
2016-09-21 14:29:42 +00:00
|
|
|
},
|
|
|
|
|
2019-05-28 21:24:30 +00:00
|
|
|
Request: &AuditRequest{
|
2023-04-06 07:41:07 +00:00
|
|
|
ID: req.ID,
|
|
|
|
ClientID: req.ClientID,
|
|
|
|
ClientToken: req.ClientToken,
|
|
|
|
ClientTokenAccessor: req.ClientTokenAccessor,
|
|
|
|
Operation: req.Operation,
|
|
|
|
MountType: req.MountType,
|
|
|
|
MountAccessor: req.MountAccessor,
|
|
|
|
MountRunningVersion: req.MountRunningVersion(),
|
|
|
|
MountRunningSha256: req.MountRunningSha256(),
|
|
|
|
MountIsExternalPlugin: req.MountIsExternalPlugin(),
|
|
|
|
MountClass: req.MountClass(),
|
2019-05-28 21:24:30 +00:00
|
|
|
Namespace: &AuditNamespace{
|
2018-09-18 03:03:00 +00:00
|
|
|
ID: ns.ID,
|
|
|
|
Path: ns.Path,
|
|
|
|
},
|
2019-05-22 22:52:53 +00:00
|
|
|
Path: req.Path,
|
|
|
|
Data: req.Data,
|
|
|
|
PolicyOverride: req.PolicyOverride,
|
|
|
|
RemoteAddr: getRemoteAddr(req),
|
Add remote_port in the audit logs when it is available (#12790)
* Add remote_port in the audit logs when it is available
The `request.remote_port` field is now present in the audit log when it
is available:
```
{
"time": "2021-10-10T13:53:51.760039Z",
"type": "response",
"auth": {
"client_token": "hmac-sha256:1304aab0ac65747684e1b58248cc16715fa8f558f8d27e90fcbcb213220c0edf",
"accessor": "hmac-sha256:f8cf0601dadd19aac84f205ded44c62898e3746a42108a51105a92ccc39baa43",
"display_name": "root",
"policies": [
"root"
],
"token_policies": [
"root"
],
"token_type": "service",
"token_issue_time": "2021-10-10T15:53:44+02:00"
},
"request": {
"id": "829c04a1-0352-2d9d-9bc9-00b928d33df5",
"operation": "update",
"mount_type": "system",
"client_token": "hmac-sha256:1304aab0ac65747684e1b58248cc16715fa8f558f8d27e90fcbcb213220c0edf",
"client_token_accessor": "hmac-sha256:f8cf0601dadd19aac84f205ded44c62898e3746a42108a51105a92ccc39baa43",
"namespace": {
"id": "root"
},
"path": "sys/audit/file",
"data": {
"description": "hmac-sha256:321a1d105f8c6fd62be4f34c4da4f0e6d1cdee9eb2ff4af0b59e1410950fe86b",
"local": false,
"options": {
"file_path": "hmac-sha256:2421b5bf8dab1f9775b2e6e66e58d7bca99ab729f3f311782fda50717eee55b3"
},
"type": "hmac-sha256:30dff9607b4087e3ae6808b4a3aa395b1fc064e467748c55c25ddf0e9b150fcc"
},
"remote_address": "127.0.0.1",
"remote_port": 54798
},
"response": {
"mount_type": "system"
}
}
```
Closes https://github.com/hashicorp/vault/issues/7716
* Add changelog entry
* Empty commit to trigger CI
* Add test and explicit error handling
* Change temporary file pattern in test
2022-01-26 23:47:15 +00:00
|
|
|
RemotePort: getRemotePort(req),
|
2019-05-22 22:52:53 +00:00
|
|
|
ReplicationCluster: req.ReplicationCluster,
|
|
|
|
Headers: req.Headers,
|
|
|
|
ClientCertificateSerialNumber: getClientCertificateSerialNumber(connState),
|
2016-09-21 14:29:42 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2020-05-29 17:30:47 +00:00
|
|
|
if !auth.IssueTime.IsZero() {
|
|
|
|
reqEntry.Auth.TokenIssueTime = auth.IssueTime.Format(time.RFC3339)
|
|
|
|
}
|
|
|
|
|
2022-05-16 23:23:08 +00:00
|
|
|
if auth.PolicyResults != nil {
|
|
|
|
reqEntry.Auth.PolicyResults = &AuditPolicyResults{
|
|
|
|
Allowed: auth.PolicyResults.Allowed,
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, p := range auth.PolicyResults.GrantingPolicies {
|
|
|
|
reqEntry.Auth.PolicyResults.GrantingPolicies = append(reqEntry.Auth.PolicyResults.GrantingPolicies, PolicyInfo{
|
|
|
|
Name: p.Name,
|
|
|
|
NamespaceId: p.NamespaceId,
|
|
|
|
Type: p.Type,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-05 04:50:24 +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 {
|
2017-11-07 23:09:54 +00:00
|
|
|
reqEntry.Time = time.Now().UTC().Format(time.RFC3339Nano)
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return f.AuditFormatWriter.WriteRequest(w, reqEntry)
|
|
|
|
}
|
|
|
|
|
2019-05-22 22:52:53 +00:00
|
|
|
func (f *AuditFormatter) FormatResponse(ctx context.Context, w io.Writer, config FormatterConfig, in *logical.LogInput) error {
|
2018-03-02 17:18:39 +00:00
|
|
|
if in == nil || in.Request == nil {
|
2017-02-16 18:09:53 +00:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2018-03-08 19:21:11 +00:00
|
|
|
salt, err := f.Salt(ctx)
|
2017-05-24 00:36:20 +00:00
|
|
|
if err != nil {
|
2021-04-22 15:20:59 +00:00
|
|
|
return fmt.Errorf("error fetching salt: %w", err)
|
2017-05-24 00:36:20 +00:00
|
|
|
}
|
|
|
|
|
2018-03-02 17:18:39 +00:00
|
|
|
// Set these to the input values at first
|
2019-07-02 22:18:40 +00:00
|
|
|
auth, req, resp := in.Auth, in.Request, in.Response
|
|
|
|
if auth == nil {
|
|
|
|
auth = new(logical.Auth)
|
|
|
|
}
|
|
|
|
if resp == nil {
|
|
|
|
resp = new(logical.Response)
|
|
|
|
}
|
2019-05-22 22:52:53 +00:00
|
|
|
var connState *tls.ConnectionState
|
|
|
|
|
|
|
|
if in.Request.Connection != nil && in.Request.Connection.ConnState != nil {
|
|
|
|
connState = in.Request.Connection.ConnState
|
|
|
|
}
|
2018-03-02 17:18:39 +00:00
|
|
|
|
Add option 'elide_list_responses' to audit backends (#18128)
This PR relates to a feature request logged through HashiCorp commercial
support.
Vault lacks pagination in its APIs. As a result, certain list operations
can return **very** large responses. The user's chosen audit sinks may
experience difficulty consuming audit records that swell to tens of
megabytes of JSON.
In our case, one of the systems consuming audit log data could not cope,
and failed.
The responses of list operations are typically not very interesting, as
they are mostly lists of keys, or, even when they include a "key_info"
field, are not returning confidential information. They become even less
interesting once HMAC-ed by the audit system.
Some example Vault "list" operations that are prone to becoming very
large in an active Vault installation are:
auth/token/accessors/
identity/entity/id/
identity/entity-alias/id/
pki/certs/
In response, I've coded a new option that can be applied to audit
backends, `elide_list_responses`. When enabled, response data is elided
from audit logs, only when the operation type is "list".
For added safety, the elision only applies to the "keys" and "key_info"
fields within the response data - these are conventionally the only
fields present in a list response - see logical.ListResponse, and
logical.ListResponseWithInfo. However, other fields are technically
possible if a plugin author writes unusual code, and these will be
preserved in the audit log even with this option enabled.
The elision replaces the values of the "keys" and "key_info" fields with
an integer count of the number of entries. This allows even the elided
audit logs to still be useful for answering questions like "Was any data
returned?" or "How many records were listed?".
2023-01-11 21:15:52 +00:00
|
|
|
elideListResponseData := config.ElideListResponses && req.Operation == logical.ListOperation
|
|
|
|
|
|
|
|
var respData map[string]interface{}
|
|
|
|
if config.Raw {
|
|
|
|
// In the non-raw case, elision of list response data occurs inside HashResponse, to avoid redundant deep
|
|
|
|
// copies and hashing of data only to elide it later. In the raw case, we need to do it here.
|
|
|
|
if elideListResponseData && resp.Data != nil {
|
|
|
|
// Copy the data map before making changes, but we only need to go one level deep in this case
|
|
|
|
respData = make(map[string]interface{}, len(resp.Data))
|
|
|
|
for k, v := range resp.Data {
|
|
|
|
respData[k] = v
|
|
|
|
}
|
|
|
|
|
|
|
|
doElideListResponseData(respData)
|
|
|
|
} else {
|
|
|
|
respData = resp.Data
|
|
|
|
}
|
|
|
|
} else {
|
2019-07-02 22:18:40 +00:00
|
|
|
auth, err = HashAuth(salt, auth, config.HMACAccessor)
|
2016-09-21 14:29:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-07-02 22:18:40 +00:00
|
|
|
req, err = HashRequest(salt, req, config.HMACAccessor, in.NonHMACReqDataKeys)
|
|
|
|
if err != nil {
|
2016-09-21 14:29:42 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
Add option 'elide_list_responses' to audit backends (#18128)
This PR relates to a feature request logged through HashiCorp commercial
support.
Vault lacks pagination in its APIs. As a result, certain list operations
can return **very** large responses. The user's chosen audit sinks may
experience difficulty consuming audit records that swell to tens of
megabytes of JSON.
In our case, one of the systems consuming audit log data could not cope,
and failed.
The responses of list operations are typically not very interesting, as
they are mostly lists of keys, or, even when they include a "key_info"
field, are not returning confidential information. They become even less
interesting once HMAC-ed by the audit system.
Some example Vault "list" operations that are prone to becoming very
large in an active Vault installation are:
auth/token/accessors/
identity/entity/id/
identity/entity-alias/id/
pki/certs/
In response, I've coded a new option that can be applied to audit
backends, `elide_list_responses`. When enabled, response data is elided
from audit logs, only when the operation type is "list".
For added safety, the elision only applies to the "keys" and "key_info"
fields within the response data - these are conventionally the only
fields present in a list response - see logical.ListResponse, and
logical.ListResponseWithInfo. However, other fields are technically
possible if a plugin author writes unusual code, and these will be
preserved in the audit log even with this option enabled.
The elision replaces the values of the "keys" and "key_info" fields with
an integer count of the number of entries. This allows even the elided
audit logs to still be useful for answering questions like "Was any data
returned?" or "How many records were listed?".
2023-01-11 21:15:52 +00:00
|
|
|
resp, err = HashResponse(salt, resp, config.HMACAccessor, in.NonHMACRespDataKeys, elideListResponseData)
|
2019-07-02 22:18:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
Add option 'elide_list_responses' to audit backends (#18128)
This PR relates to a feature request logged through HashiCorp commercial
support.
Vault lacks pagination in its APIs. As a result, certain list operations
can return **very** large responses. The user's chosen audit sinks may
experience difficulty consuming audit records that swell to tens of
megabytes of JSON.
In our case, one of the systems consuming audit log data could not cope,
and failed.
The responses of list operations are typically not very interesting, as
they are mostly lists of keys, or, even when they include a "key_info"
field, are not returning confidential information. They become even less
interesting once HMAC-ed by the audit system.
Some example Vault "list" operations that are prone to becoming very
large in an active Vault installation are:
auth/token/accessors/
identity/entity/id/
identity/entity-alias/id/
pki/certs/
In response, I've coded a new option that can be applied to audit
backends, `elide_list_responses`. When enabled, response data is elided
from audit logs, only when the operation type is "list".
For added safety, the elision only applies to the "keys" and "key_info"
fields within the response data - these are conventionally the only
fields present in a list response - see logical.ListResponse, and
logical.ListResponseWithInfo. However, other fields are technically
possible if a plugin author writes unusual code, and these will be
preserved in the audit log even with this option enabled.
The elision replaces the values of the "keys" and "key_info" fields with
an integer count of the number of entries. This allows even the elided
audit logs to still be useful for answering questions like "Was any data
returned?" or "How many records were listed?".
2023-01-11 21:15:52 +00:00
|
|
|
|
|
|
|
respData = resp.Data
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var errString string
|
2018-03-02 17:18:39 +00:00
|
|
|
if in.OuterErr != nil {
|
|
|
|
errString = in.OuterErr.Error()
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
|
|
|
|
2018-09-18 03:03:00 +00:00
|
|
|
ns, err := namespace.FromContext(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-09-21 14:29:42 +00:00
|
|
|
var respAuth *AuditAuth
|
|
|
|
if resp.Auth != nil {
|
|
|
|
respAuth = &AuditAuth{
|
2018-09-18 03:03:00 +00:00
|
|
|
ClientToken: resp.Auth.ClientToken,
|
|
|
|
Accessor: resp.Auth.Accessor,
|
|
|
|
DisplayName: resp.Auth.DisplayName,
|
|
|
|
Policies: resp.Auth.Policies,
|
|
|
|
TokenPolicies: resp.Auth.TokenPolicies,
|
|
|
|
IdentityPolicies: resp.Auth.IdentityPolicies,
|
|
|
|
ExternalNamespacePolicies: resp.Auth.ExternalNamespacePolicies,
|
2019-06-14 14:17:04 +00:00
|
|
|
NoDefaultPolicy: resp.Auth.NoDefaultPolicy,
|
2018-09-18 03:03:00 +00:00
|
|
|
Metadata: resp.Auth.Metadata,
|
|
|
|
NumUses: resp.Auth.NumUses,
|
2018-10-15 16:56:24 +00:00
|
|
|
EntityID: resp.Auth.EntityID,
|
|
|
|
TokenType: resp.Auth.TokenType.String(),
|
2020-05-29 17:30:47 +00:00
|
|
|
TokenTTL: int64(resp.Auth.TTL.Seconds()),
|
|
|
|
}
|
|
|
|
if !resp.Auth.IssueTime.IsZero() {
|
|
|
|
respAuth.TokenIssueTime = resp.Auth.IssueTime.Format(time.RFC3339)
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2017-01-05 04:50:24 +00:00
|
|
|
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),
|
2017-01-05 04:50:24 +00:00
|
|
|
Token: token,
|
2017-11-13 20:31:32 +00:00
|
|
|
Accessor: resp.WrapInfo.Accessor,
|
2018-03-29 21:49:21 +00:00
|
|
|
CreationTime: resp.WrapInfo.CreationTime.UTC().Format(time.RFC3339Nano),
|
2017-08-02 22:28:58 +00:00
|
|
|
CreationPath: resp.WrapInfo.CreationPath,
|
2016-09-21 14:29:42 +00:00
|
|
|
WrappedAccessor: resp.WrapInfo.WrappedAccessor,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-22 22:52:53 +00:00
|
|
|
respType := in.Type
|
|
|
|
if respType == "" {
|
|
|
|
respType = "response"
|
|
|
|
}
|
2016-09-21 14:29:42 +00:00
|
|
|
respEntry := &AuditResponseEntry{
|
2019-05-22 22:52:53 +00:00
|
|
|
Type: respType,
|
2016-09-21 14:29:42 +00:00
|
|
|
Error: errString,
|
2019-05-28 21:24:30 +00:00
|
|
|
Auth: &AuditAuth{
|
2018-10-15 16:56:24 +00:00
|
|
|
ClientToken: auth.ClientToken,
|
|
|
|
Accessor: auth.Accessor,
|
2018-09-18 03:03:00 +00:00
|
|
|
DisplayName: auth.DisplayName,
|
|
|
|
Policies: auth.Policies,
|
|
|
|
TokenPolicies: auth.TokenPolicies,
|
|
|
|
IdentityPolicies: auth.IdentityPolicies,
|
|
|
|
ExternalNamespacePolicies: auth.ExternalNamespacePolicies,
|
2019-06-14 14:17:04 +00:00
|
|
|
NoDefaultPolicy: auth.NoDefaultPolicy,
|
2018-09-18 03:03:00 +00:00
|
|
|
Metadata: auth.Metadata,
|
|
|
|
RemainingUses: req.ClientTokenRemainingUses,
|
|
|
|
EntityID: auth.EntityID,
|
2022-05-18 16:16:13 +00:00
|
|
|
EntityCreated: auth.EntityCreated,
|
2018-10-15 16:56:24 +00:00
|
|
|
TokenType: auth.TokenType.String(),
|
2020-05-29 17:30:47 +00:00
|
|
|
TokenTTL: int64(auth.TTL.Seconds()),
|
2016-09-21 14:29:42 +00:00
|
|
|
},
|
|
|
|
|
2019-05-28 21:24:30 +00:00
|
|
|
Request: &AuditRequest{
|
2023-04-06 07:41:07 +00:00
|
|
|
ID: req.ID,
|
|
|
|
ClientToken: req.ClientToken,
|
|
|
|
ClientTokenAccessor: req.ClientTokenAccessor,
|
|
|
|
ClientID: req.ClientID,
|
|
|
|
Operation: req.Operation,
|
|
|
|
MountType: req.MountType,
|
|
|
|
MountAccessor: req.MountAccessor,
|
|
|
|
MountRunningVersion: req.MountRunningVersion(),
|
|
|
|
MountRunningSha256: req.MountRunningSha256(),
|
|
|
|
MountIsExternalPlugin: req.MountIsExternalPlugin(),
|
|
|
|
MountClass: req.MountClass(),
|
2019-05-28 21:24:30 +00:00
|
|
|
Namespace: &AuditNamespace{
|
2018-09-18 03:03:00 +00:00
|
|
|
ID: ns.ID,
|
|
|
|
Path: ns.Path,
|
|
|
|
},
|
2019-05-22 22:52:53 +00:00
|
|
|
Path: req.Path,
|
|
|
|
Data: req.Data,
|
|
|
|
PolicyOverride: req.PolicyOverride,
|
|
|
|
RemoteAddr: getRemoteAddr(req),
|
Add remote_port in the audit logs when it is available (#12790)
* Add remote_port in the audit logs when it is available
The `request.remote_port` field is now present in the audit log when it
is available:
```
{
"time": "2021-10-10T13:53:51.760039Z",
"type": "response",
"auth": {
"client_token": "hmac-sha256:1304aab0ac65747684e1b58248cc16715fa8f558f8d27e90fcbcb213220c0edf",
"accessor": "hmac-sha256:f8cf0601dadd19aac84f205ded44c62898e3746a42108a51105a92ccc39baa43",
"display_name": "root",
"policies": [
"root"
],
"token_policies": [
"root"
],
"token_type": "service",
"token_issue_time": "2021-10-10T15:53:44+02:00"
},
"request": {
"id": "829c04a1-0352-2d9d-9bc9-00b928d33df5",
"operation": "update",
"mount_type": "system",
"client_token": "hmac-sha256:1304aab0ac65747684e1b58248cc16715fa8f558f8d27e90fcbcb213220c0edf",
"client_token_accessor": "hmac-sha256:f8cf0601dadd19aac84f205ded44c62898e3746a42108a51105a92ccc39baa43",
"namespace": {
"id": "root"
},
"path": "sys/audit/file",
"data": {
"description": "hmac-sha256:321a1d105f8c6fd62be4f34c4da4f0e6d1cdee9eb2ff4af0b59e1410950fe86b",
"local": false,
"options": {
"file_path": "hmac-sha256:2421b5bf8dab1f9775b2e6e66e58d7bca99ab729f3f311782fda50717eee55b3"
},
"type": "hmac-sha256:30dff9607b4087e3ae6808b4a3aa395b1fc064e467748c55c25ddf0e9b150fcc"
},
"remote_address": "127.0.0.1",
"remote_port": 54798
},
"response": {
"mount_type": "system"
}
}
```
Closes https://github.com/hashicorp/vault/issues/7716
* Add changelog entry
* Empty commit to trigger CI
* Add test and explicit error handling
* Change temporary file pattern in test
2022-01-26 23:47:15 +00:00
|
|
|
RemotePort: getRemotePort(req),
|
2019-05-22 22:52:53 +00:00
|
|
|
ClientCertificateSerialNumber: getClientCertificateSerialNumber(connState),
|
|
|
|
ReplicationCluster: req.ReplicationCluster,
|
|
|
|
Headers: req.Headers,
|
2016-09-21 14:29:42 +00:00
|
|
|
},
|
|
|
|
|
2019-05-28 21:24:30 +00:00
|
|
|
Response: &AuditResponse{
|
2023-04-06 07:41:07 +00:00
|
|
|
MountType: req.MountType,
|
|
|
|
MountAccessor: req.MountAccessor,
|
|
|
|
MountRunningVersion: req.MountRunningVersion(),
|
|
|
|
MountRunningSha256: req.MountRunningSha256(),
|
|
|
|
MountIsExternalPlugin: req.MountIsExternalPlugin(),
|
|
|
|
MountClass: req.MountClass(),
|
|
|
|
Auth: respAuth,
|
|
|
|
Secret: respSecret,
|
|
|
|
Data: respData,
|
|
|
|
Warnings: resp.Warnings,
|
|
|
|
Redirect: resp.Redirect,
|
|
|
|
WrapInfo: respWrapInfo,
|
|
|
|
Headers: resp.Headers,
|
2016-09-21 14:29:42 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2022-05-16 23:23:08 +00:00
|
|
|
if auth.PolicyResults != nil {
|
|
|
|
respEntry.Auth.PolicyResults = &AuditPolicyResults{
|
|
|
|
Allowed: auth.PolicyResults.Allowed,
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, p := range auth.PolicyResults.GrantingPolicies {
|
|
|
|
respEntry.Auth.PolicyResults.GrantingPolicies = append(respEntry.Auth.PolicyResults.GrantingPolicies, PolicyInfo{
|
|
|
|
Name: p.Name,
|
|
|
|
NamespaceId: p.NamespaceId,
|
|
|
|
Type: p.Type,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-29 17:30:47 +00:00
|
|
|
if !auth.IssueTime.IsZero() {
|
|
|
|
respEntry.Auth.TokenIssueTime = auth.IssueTime.Format(time.RFC3339)
|
|
|
|
}
|
2017-01-05 04:50:24 +00:00
|
|
|
if req.WrapInfo != nil {
|
|
|
|
respEntry.Request.WrapTTL = int(req.WrapInfo.TTL / time.Second)
|
|
|
|
}
|
|
|
|
|
2016-09-21 14:29:42 +00:00
|
|
|
if !config.OmitTime {
|
2017-11-07 23:09:54 +00:00
|
|
|
respEntry.Time = time.Now().UTC().Format(time.RFC3339Nano)
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return f.AuditFormatWriter.WriteResponse(w, respEntry)
|
|
|
|
}
|
|
|
|
|
2018-03-02 17:18:39 +00:00
|
|
|
// AuditRequestEntry is the structure of a request audit log entry in Audit.
|
2016-09-21 14:29:42 +00:00
|
|
|
type AuditRequestEntry struct {
|
2019-05-28 21:24:30 +00:00
|
|
|
Time string `json:"time,omitempty"`
|
|
|
|
Type string `json:"type,omitempty"`
|
|
|
|
Auth *AuditAuth `json:"auth,omitempty"`
|
|
|
|
Request *AuditRequest `json:"request,omitempty"`
|
|
|
|
Error string `json:"error,omitempty"`
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// AuditResponseEntry is the structure of a response audit log entry in Audit.
|
|
|
|
type AuditResponseEntry struct {
|
2019-05-28 21:24:30 +00:00
|
|
|
Time string `json:"time,omitempty"`
|
|
|
|
Type string `json:"type,omitempty"`
|
|
|
|
Auth *AuditAuth `json:"auth,omitempty"`
|
|
|
|
Request *AuditRequest `json:"request,omitempty"`
|
|
|
|
Response *AuditResponse `json:"response,omitempty"`
|
|
|
|
Error string `json:"error,omitempty"`
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type AuditRequest struct {
|
2019-05-28 21:25:23 +00:00
|
|
|
ID string `json:"id,omitempty"`
|
2021-10-26 16:02:13 +00:00
|
|
|
ClientID string `json:"client_id,omitempty"`
|
2019-05-28 21:25:23 +00:00
|
|
|
ReplicationCluster string `json:"replication_cluster,omitempty"`
|
|
|
|
Operation logical.Operation `json:"operation,omitempty"`
|
2020-06-16 12:22:33 +00:00
|
|
|
MountType string `json:"mount_type,omitempty"`
|
2022-05-12 17:28:00 +00:00
|
|
|
MountAccessor string `json:"mount_accessor,omitempty"`
|
2023-04-06 07:41:07 +00:00
|
|
|
MountRunningVersion string `json:"mount_running_version,omitempty"`
|
|
|
|
MountRunningSha256 string `json:"mount_running_sha256,omitempty"`
|
|
|
|
MountClass string `json:"mount_class,omitempty"`
|
|
|
|
MountIsExternalPlugin bool `json:"mount_is_external_plugin,omitempty"`
|
2019-05-28 21:25:23 +00:00
|
|
|
ClientToken string `json:"client_token,omitempty"`
|
|
|
|
ClientTokenAccessor string `json:"client_token_accessor,omitempty"`
|
|
|
|
Namespace *AuditNamespace `json:"namespace,omitempty"`
|
|
|
|
Path string `json:"path,omitempty"`
|
|
|
|
Data map[string]interface{} `json:"data,omitempty"`
|
|
|
|
PolicyOverride bool `json:"policy_override,omitempty"`
|
|
|
|
RemoteAddr string `json:"remote_address,omitempty"`
|
Add remote_port in the audit logs when it is available (#12790)
* Add remote_port in the audit logs when it is available
The `request.remote_port` field is now present in the audit log when it
is available:
```
{
"time": "2021-10-10T13:53:51.760039Z",
"type": "response",
"auth": {
"client_token": "hmac-sha256:1304aab0ac65747684e1b58248cc16715fa8f558f8d27e90fcbcb213220c0edf",
"accessor": "hmac-sha256:f8cf0601dadd19aac84f205ded44c62898e3746a42108a51105a92ccc39baa43",
"display_name": "root",
"policies": [
"root"
],
"token_policies": [
"root"
],
"token_type": "service",
"token_issue_time": "2021-10-10T15:53:44+02:00"
},
"request": {
"id": "829c04a1-0352-2d9d-9bc9-00b928d33df5",
"operation": "update",
"mount_type": "system",
"client_token": "hmac-sha256:1304aab0ac65747684e1b58248cc16715fa8f558f8d27e90fcbcb213220c0edf",
"client_token_accessor": "hmac-sha256:f8cf0601dadd19aac84f205ded44c62898e3746a42108a51105a92ccc39baa43",
"namespace": {
"id": "root"
},
"path": "sys/audit/file",
"data": {
"description": "hmac-sha256:321a1d105f8c6fd62be4f34c4da4f0e6d1cdee9eb2ff4af0b59e1410950fe86b",
"local": false,
"options": {
"file_path": "hmac-sha256:2421b5bf8dab1f9775b2e6e66e58d7bca99ab729f3f311782fda50717eee55b3"
},
"type": "hmac-sha256:30dff9607b4087e3ae6808b4a3aa395b1fc064e467748c55c25ddf0e9b150fcc"
},
"remote_address": "127.0.0.1",
"remote_port": 54798
},
"response": {
"mount_type": "system"
}
}
```
Closes https://github.com/hashicorp/vault/issues/7716
* Add changelog entry
* Empty commit to trigger CI
* Add test and explicit error handling
* Change temporary file pattern in test
2022-01-26 23:47:15 +00:00
|
|
|
RemotePort int `json:"remote_port,omitempty"`
|
2019-05-28 21:25:23 +00:00
|
|
|
WrapTTL int `json:"wrap_ttl,omitempty"`
|
|
|
|
Headers map[string][]string `json:"headers,omitempty"`
|
|
|
|
ClientCertificateSerialNumber string `json:"client_certificate_serial_number,omitempty"`
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type AuditResponse struct {
|
2023-04-06 07:41:07 +00:00
|
|
|
Auth *AuditAuth `json:"auth,omitempty"`
|
|
|
|
MountType string `json:"mount_type,omitempty"`
|
|
|
|
MountAccessor string `json:"mount_accessor,omitempty"`
|
|
|
|
MountRunningVersion string `json:"mount_running_plugin_version,omitempty"`
|
|
|
|
MountRunningSha256 string `json:"mount_running_sha256,omitempty"`
|
|
|
|
MountClass string `json:"mount_class,omitempty"`
|
|
|
|
MountIsExternalPlugin bool `json:"mount_is_external_plugin,omitempty"`
|
|
|
|
Secret *AuditSecret `json:"secret,omitempty"`
|
|
|
|
Data map[string]interface{} `json:"data,omitempty"`
|
|
|
|
Warnings []string `json:"warnings,omitempty"`
|
|
|
|
Redirect string `json:"redirect,omitempty"`
|
|
|
|
WrapInfo *AuditResponseWrapInfo `json:"wrap_info,omitempty"`
|
|
|
|
Headers map[string][]string `json:"headers,omitempty"`
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type AuditAuth struct {
|
2019-05-28 21:24:30 +00:00
|
|
|
ClientToken string `json:"client_token,omitempty"`
|
|
|
|
Accessor string `json:"accessor,omitempty"`
|
|
|
|
DisplayName string `json:"display_name,omitempty"`
|
|
|
|
Policies []string `json:"policies,omitempty"`
|
2018-09-18 03:03:00 +00:00
|
|
|
TokenPolicies []string `json:"token_policies,omitempty"`
|
|
|
|
IdentityPolicies []string `json:"identity_policies,omitempty"`
|
|
|
|
ExternalNamespacePolicies map[string][]string `json:"external_namespace_policies,omitempty"`
|
2019-06-14 14:17:04 +00:00
|
|
|
NoDefaultPolicy bool `json:"no_default_policy,omitempty"`
|
2022-05-16 23:23:08 +00:00
|
|
|
PolicyResults *AuditPolicyResults `json:"policy_results,omitempty"`
|
2019-05-28 21:24:30 +00:00
|
|
|
Metadata map[string]string `json:"metadata,omitempty"`
|
2018-09-18 03:03:00 +00:00
|
|
|
NumUses int `json:"num_uses,omitempty"`
|
|
|
|
RemainingUses int `json:"remaining_uses,omitempty"`
|
2019-05-28 21:24:30 +00:00
|
|
|
EntityID string `json:"entity_id,omitempty"`
|
2022-05-18 16:16:13 +00:00
|
|
|
EntityCreated bool `json:"entity_created,omitempty"`
|
2019-05-28 21:24:30 +00:00
|
|
|
TokenType string `json:"token_type,omitempty"`
|
2020-05-29 17:30:47 +00:00
|
|
|
TokenTTL int64 `json:"token_ttl,omitempty"`
|
|
|
|
TokenIssueTime string `json:"token_issue_time,omitempty"`
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
|
|
|
|
2022-05-16 23:23:08 +00:00
|
|
|
type AuditPolicyResults struct {
|
|
|
|
Allowed bool `json:"allowed"`
|
|
|
|
GrantingPolicies []PolicyInfo `json:"granting_policies,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type PolicyInfo struct {
|
|
|
|
Name string `json:"name,omitempty"`
|
|
|
|
NamespaceId string `json:"namespace_id,omitempty"`
|
|
|
|
Type string `json:"type"`
|
|
|
|
}
|
|
|
|
|
2016-09-21 14:29:42 +00:00
|
|
|
type AuditSecret struct {
|
2019-05-28 21:24:30 +00:00
|
|
|
LeaseID string `json:"lease_id,omitempty"`
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
|
|
|
|
2017-01-04 21:44:03 +00:00
|
|
|
type AuditResponseWrapInfo struct {
|
2019-05-28 21:24:30 +00:00
|
|
|
TTL int `json:"ttl,omitempty"`
|
|
|
|
Token string `json:"token,omitempty"`
|
|
|
|
Accessor string `json:"accessor,omitempty"`
|
|
|
|
CreationTime string `json:"creation_time,omitempty"`
|
|
|
|
CreationPath string `json:"creation_path,omitempty"`
|
2016-09-23 16:32:07 +00:00
|
|
|
WrappedAccessor string `json:"wrapped_accessor,omitempty"`
|
2016-09-21 14:29:42 +00:00
|
|
|
}
|
|
|
|
|
2018-09-18 03:03:00 +00:00
|
|
|
type AuditNamespace struct {
|
2019-05-28 21:24:30 +00:00
|
|
|
ID string `json:"id,omitempty"`
|
|
|
|
Path string `json:"path,omitempty"`
|
2018-09-18 03:03:00 +00:00
|
|
|
}
|
|
|
|
|
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 ""
|
|
|
|
}
|
2017-01-05 04:50:24 +00:00
|
|
|
|
Add remote_port in the audit logs when it is available (#12790)
* Add remote_port in the audit logs when it is available
The `request.remote_port` field is now present in the audit log when it
is available:
```
{
"time": "2021-10-10T13:53:51.760039Z",
"type": "response",
"auth": {
"client_token": "hmac-sha256:1304aab0ac65747684e1b58248cc16715fa8f558f8d27e90fcbcb213220c0edf",
"accessor": "hmac-sha256:f8cf0601dadd19aac84f205ded44c62898e3746a42108a51105a92ccc39baa43",
"display_name": "root",
"policies": [
"root"
],
"token_policies": [
"root"
],
"token_type": "service",
"token_issue_time": "2021-10-10T15:53:44+02:00"
},
"request": {
"id": "829c04a1-0352-2d9d-9bc9-00b928d33df5",
"operation": "update",
"mount_type": "system",
"client_token": "hmac-sha256:1304aab0ac65747684e1b58248cc16715fa8f558f8d27e90fcbcb213220c0edf",
"client_token_accessor": "hmac-sha256:f8cf0601dadd19aac84f205ded44c62898e3746a42108a51105a92ccc39baa43",
"namespace": {
"id": "root"
},
"path": "sys/audit/file",
"data": {
"description": "hmac-sha256:321a1d105f8c6fd62be4f34c4da4f0e6d1cdee9eb2ff4af0b59e1410950fe86b",
"local": false,
"options": {
"file_path": "hmac-sha256:2421b5bf8dab1f9775b2e6e66e58d7bca99ab729f3f311782fda50717eee55b3"
},
"type": "hmac-sha256:30dff9607b4087e3ae6808b4a3aa395b1fc064e467748c55c25ddf0e9b150fcc"
},
"remote_address": "127.0.0.1",
"remote_port": 54798
},
"response": {
"mount_type": "system"
}
}
```
Closes https://github.com/hashicorp/vault/issues/7716
* Add changelog entry
* Empty commit to trigger CI
* Add test and explicit error handling
* Change temporary file pattern in test
2022-01-26 23:47:15 +00:00
|
|
|
// getRemotePort safely gets the remote port avoiding a nil pointer
|
|
|
|
func getRemotePort(req *logical.Request) int {
|
|
|
|
if req != nil && req.Connection != nil {
|
|
|
|
return req.Connection.RemotePort
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2019-05-22 22:52:53 +00:00
|
|
|
func getClientCertificateSerialNumber(connState *tls.ConnectionState) string {
|
|
|
|
if connState == nil || len(connState.VerifiedChains) == 0 || len(connState.VerifiedChains[0]) == 0 {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
return connState.VerifiedChains[0][0].SerialNumber.String()
|
|
|
|
}
|
|
|
|
|
2017-01-05 04:50:24 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2019-03-20 18:54:03 +00:00
|
|
|
parsedJWT, err := squarejwt.ParseSigned(token)
|
|
|
|
if err != nil {
|
2017-01-05 04:50:24 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-03-20 18:54:03 +00:00
|
|
|
var claims squarejwt.Claims
|
|
|
|
if err = parsedJWT.UnsafeClaimsWithoutVerification(&claims); err != nil {
|
|
|
|
return nil
|
|
|
|
}
|
2017-01-05 04:50:24 +00:00
|
|
|
|
2019-03-20 18:54:03 +00:00
|
|
|
return &claims.ID
|
2017-01-05 04:50:24 +00:00
|
|
|
}
|
2020-12-16 22:00:32 +00:00
|
|
|
|
Add option 'elide_list_responses' to audit backends (#18128)
This PR relates to a feature request logged through HashiCorp commercial
support.
Vault lacks pagination in its APIs. As a result, certain list operations
can return **very** large responses. The user's chosen audit sinks may
experience difficulty consuming audit records that swell to tens of
megabytes of JSON.
In our case, one of the systems consuming audit log data could not cope,
and failed.
The responses of list operations are typically not very interesting, as
they are mostly lists of keys, or, even when they include a "key_info"
field, are not returning confidential information. They become even less
interesting once HMAC-ed by the audit system.
Some example Vault "list" operations that are prone to becoming very
large in an active Vault installation are:
auth/token/accessors/
identity/entity/id/
identity/entity-alias/id/
pki/certs/
In response, I've coded a new option that can be applied to audit
backends, `elide_list_responses`. When enabled, response data is elided
from audit logs, only when the operation type is "list".
For added safety, the elision only applies to the "keys" and "key_info"
fields within the response data - these are conventionally the only
fields present in a list response - see logical.ListResponse, and
logical.ListResponseWithInfo. However, other fields are technically
possible if a plugin author writes unusual code, and these will be
preserved in the audit log even with this option enabled.
The elision replaces the values of the "keys" and "key_info" fields with
an integer count of the number of entries. This allows even the elided
audit logs to still be useful for answering questions like "Was any data
returned?" or "How many records were listed?".
2023-01-11 21:15:52 +00:00
|
|
|
// NewTemporaryFormatter creates a formatter not backed by a persistent salt
|
2020-12-16 22:00:32 +00:00
|
|
|
func NewTemporaryFormatter(format, prefix string) *AuditFormatter {
|
|
|
|
temporarySalt := func(ctx context.Context) (*salt.Salt, error) {
|
|
|
|
return salt.NewNonpersistentSalt(), nil
|
|
|
|
}
|
|
|
|
ret := &AuditFormatter{}
|
|
|
|
|
|
|
|
switch format {
|
|
|
|
case "jsonx":
|
|
|
|
ret.AuditFormatWriter = &JSONxFormatWriter{
|
|
|
|
Prefix: prefix,
|
|
|
|
SaltFunc: temporarySalt,
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
ret.AuditFormatWriter = &JSONFormatWriter{
|
|
|
|
Prefix: prefix,
|
|
|
|
SaltFunc: temporarySalt,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
Add option 'elide_list_responses' to audit backends (#18128)
This PR relates to a feature request logged through HashiCorp commercial
support.
Vault lacks pagination in its APIs. As a result, certain list operations
can return **very** large responses. The user's chosen audit sinks may
experience difficulty consuming audit records that swell to tens of
megabytes of JSON.
In our case, one of the systems consuming audit log data could not cope,
and failed.
The responses of list operations are typically not very interesting, as
they are mostly lists of keys, or, even when they include a "key_info"
field, are not returning confidential information. They become even less
interesting once HMAC-ed by the audit system.
Some example Vault "list" operations that are prone to becoming very
large in an active Vault installation are:
auth/token/accessors/
identity/entity/id/
identity/entity-alias/id/
pki/certs/
In response, I've coded a new option that can be applied to audit
backends, `elide_list_responses`. When enabled, response data is elided
from audit logs, only when the operation type is "list".
For added safety, the elision only applies to the "keys" and "key_info"
fields within the response data - these are conventionally the only
fields present in a list response - see logical.ListResponse, and
logical.ListResponseWithInfo. However, other fields are technically
possible if a plugin author writes unusual code, and these will be
preserved in the audit log even with this option enabled.
The elision replaces the values of the "keys" and "key_info" fields with
an integer count of the number of entries. This allows even the elided
audit logs to still be useful for answering questions like "Was any data
returned?" or "How many records were listed?".
2023-01-11 21:15:52 +00:00
|
|
|
|
|
|
|
// doElideListResponseData performs the actual elision of list operation response data, once surrounding code has
|
|
|
|
// determined it should apply to a particular request. The data map that is passed in must be a copy that is safe to
|
|
|
|
// modify in place, but need not be a full recursive deep copy, as only top-level keys are changed.
|
|
|
|
//
|
|
|
|
// See the documentation of the controlling option in FormatterConfig for more information on the purpose.
|
|
|
|
func doElideListResponseData(data map[string]interface{}) {
|
|
|
|
for k, v := range data {
|
|
|
|
if k == "keys" {
|
|
|
|
if vSlice, ok := v.([]string); ok {
|
|
|
|
data[k] = len(vSlice)
|
|
|
|
}
|
|
|
|
} else if k == "key_info" {
|
|
|
|
if vMap, ok := v.(map[string]interface{}); ok {
|
|
|
|
data[k] = len(vMap)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|