refactor to move audit event message types to their own pkg

This commit is contained in:
Ryan Richard
2024-11-07 14:15:04 -08:00
committed by Joshua Casey
parent 088556193d
commit 8cf9c59957
13 changed files with 102 additions and 92 deletions

View File

@@ -1,68 +0,0 @@
// Copyright 2024 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package plog
import (
"net/url"
"k8s.io/apimachinery/pkg/util/sets"
)
type AuditEventMessage string
const (
AuditEventHTTPRequestReceived AuditEventMessage = "HTTP Request Received"
AuditEventHTTPRequestCompleted AuditEventMessage = "HTTP Request Completed"
AuditEventHTTPRequestParameters AuditEventMessage = "HTTP Request Parameters"
AuditEventHTTPRequestCustomHeadersUsed AuditEventMessage = "HTTP Request Custom Headers Used"
AuditEventUsingUpstreamIDP AuditEventMessage = "Using Upstream IDP"
AuditEventAuthorizeIDFromParameters AuditEventMessage = "AuthorizeID From Parameters"
AuditEventIdentityFromUpstreamIDP AuditEventMessage = "Identity From Upstream IDP"
AuditEventIdentityRefreshedFromUpstreamIDP AuditEventMessage = "Identity Refreshed From Upstream IDP"
AuditEventSessionStarted AuditEventMessage = "Session Started"
AuditEventSessionRefreshed AuditEventMessage = "Session Refreshed"
AuditEventAuthenticationRejectedByTransforms AuditEventMessage = "Authentication Rejected By Transforms"
AuditEventUpstreamOIDCTokenRevoked AuditEventMessage = "Upstream OIDC Token Revoked" //nolint:gosec // this is not a credential
AuditEventSessionGarbageCollected AuditEventMessage = "Session Garbage Collected"
AuditEventTokenCredentialRequest AuditEventMessage = "TokenCredentialRequest" //nolint:gosec // this is not a credential
AuditEventUpstreamAuthorizeRedirect AuditEventMessage = "Upstream Authorize Redirect"
)
// SanitizeParams can be used to redact all params not included in the allowedKeys set.
// Useful when audit logging AuditEventHTTPRequestParameters events.
func SanitizeParams(inputParams url.Values, allowedKeys sets.Set[string]) []any {
params := make(map[string]string)
multiValueParams := make(url.Values)
transform := func(key, value string) string {
if !allowedKeys.Has(key) {
return "redacted"
}
unescape, err := url.QueryUnescape(value)
if err != nil {
// ignore these errors and just use the original query parameter
unescape = value
}
return unescape
}
for key := range inputParams {
for i, p := range inputParams[key] {
transformed := transform(key, p)
if i == 0 {
params[key] = transformed
}
if len(inputParams[key]) > 1 {
multiValueParams[key] = append(multiValueParams[key], transformed)
}
}
}
if len(multiValueParams) > 0 {
return []any{"params", params, "multiValueParams", multiValueParams}
}
return []any{"params", params}
}

View File

@@ -1,171 +0,0 @@
// Copyright 2024 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package plog
import (
"net/url"
"testing"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/util/sets"
)
func TestSanitizeParams(t *testing.T) {
tests := []struct {
name string
params url.Values
allowedKeys sets.Set[string]
want []any
}{
{
name: "nil values",
params: nil,
allowedKeys: nil,
want: []any{
"params",
map[string]string{},
},
},
{
name: "empty values",
params: url.Values{},
allowedKeys: nil,
want: []any{
"params",
map[string]string{},
},
},
{
name: "all allowed values",
params: url.Values{"foo": []string{"a", "b", "c"}, "bar": []string{"d", "e", "f"}},
allowedKeys: sets.New("foo", "bar"),
want: []any{
"params",
map[string]string{
"bar": "d",
"foo": "a",
},
"multiValueParams",
url.Values{
"bar": []string{"d", "e", "f"},
"foo": []string{"a", "b", "c"},
},
},
},
{
name: "all allowed values with single values",
params: url.Values{"foo": []string{"a"}, "bar": []string{"d"}},
allowedKeys: sets.New("foo", "bar"),
want: []any{
"params",
map[string]string{
"foo": "a",
"bar": "d",
},
},
},
{
name: "some allowed values",
params: url.Values{"foo": []string{"a", "b", "c"}, "bar": []string{"d", "e", "f"}},
allowedKeys: sets.New("foo"),
want: []any{
"params",
map[string]string{
"bar": "redacted",
"foo": "a",
},
"multiValueParams",
url.Values{
"bar": []string{"redacted", "redacted", "redacted"},
"foo": []string{"a", "b", "c"},
},
},
},
{
name: "some allowed values with single values",
params: url.Values{"foo": []string{"a"}, "bar": []string{"d"}},
allowedKeys: sets.New("foo"),
want: []any{
"params",
map[string]string{
"bar": "redacted",
"foo": "a",
},
},
},
{
name: "no allowed values",
params: url.Values{"foo": []string{"a", "b", "c"}, "bar": []string{"d", "e", "f"}},
allowedKeys: sets.New[string](),
want: []any{
"params",
map[string]string{
"bar": "redacted",
"foo": "redacted",
},
"multiValueParams",
url.Values{
"bar": {"redacted", "redacted", "redacted"},
"foo": {"redacted", "redacted", "redacted"},
},
},
},
{
name: "nil allowed values",
params: url.Values{"foo": []string{"a", "b", "c"}, "bar": []string{"d", "e", "f"}},
allowedKeys: nil,
want: []any{
"params",
map[string]string{
"bar": "redacted",
"foo": "redacted",
},
"multiValueParams",
url.Values{
"bar": {"redacted", "redacted", "redacted"},
"foo": {"redacted", "redacted", "redacted"},
},
},
},
{
name: "url decodes allowed values",
params: url.Values{
"foo": []string{"a%3Ab", "c", "urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Atoken-exchange"},
"bar": []string{"d", "e", "f"},
},
allowedKeys: sets.New("foo"),
want: []any{
"params",
map[string]string{
"bar": "redacted",
"foo": "a:b",
},
"multiValueParams",
url.Values{
"bar": {"redacted", "redacted", "redacted"},
"foo": {"a:b", "c", "urn:ietf:params:oauth:grant-type:token-exchange"},
},
},
},
{
name: "ignores url decode errors",
params: url.Values{
"bad_encoding": []string{"%.."},
},
allowedKeys: sets.New("bad_encoding"),
want: []any{
"params",
map[string]string{
"bad_encoding": "%..",
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// This comparison should require the exact order
require.Equal(t, test.want, SanitizeParams(test.params, test.allowedKeys))
})
}
}

View File

@@ -33,6 +33,7 @@ import (
"slices"
"github.com/go-logr/logr"
"go.pinniped.dev/internal/auditevent"
"k8s.io/apiserver/pkg/audit"
)
@@ -61,7 +62,7 @@ type AuditLogger interface {
// reqCtx and session may be null.
// When possible, pass the http request's context as reqCtx, so we may read the audit ID from the context.
// When possible, pass the fosite.Requester or fosite.Request as the session, so we can log the session ID.
Audit(msg AuditEventMessage, reqCtx context.Context, session SessionIDGetter, keysAndValues ...any)
Audit(msg auditevent.Message, reqCtx context.Context, session SessionIDGetter, keysAndValues ...any)
}
// Logger implements the plog logging convention described above. The global functions in this package
@@ -126,7 +127,7 @@ func (p pLogger) Error(msg string, err error, keysAndValues ...any) {
// by their own separate configuration. This is because Audit logs should always be printed when they are desired
// by the admin, regardless of global log level, yet the admin should also have a way to entirely disable them
// when they want to avoid potential PII (e.g. usernames) in their pod logs.
func (p pLogger) Audit(msg AuditEventMessage, reqCtx context.Context, session SessionIDGetter, keysAndValues ...any) {
func (p pLogger) Audit(msg auditevent.Message, reqCtx context.Context, session SessionIDGetter, keysAndValues ...any) {
// Always add a key/value auditEvent=true.
keysAndValues = slices.Concat([]any{"auditEvent", true}, keysAndValues)