- Notifications
You must be signed in to change notification settings - Fork1k
feat: add API key metadata to audit logs#19996
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
base:thomask33/09-26-add_detailed_scope_auth_metrics
Are you sure you want to change the base?
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
ThomasK33 marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
package audit | ||
import ( | ||
"context" | ||
"encoding/json" | ||
"cdr.dev/slog" | ||
"github.com/coder/coder/v2/coderd/database" | ||
"github.com/coder/coder/v2/coderd/rbac" | ||
) | ||
type APIKeyAuditFields struct { | ||
ID string `json:"id"` | ||
TokenName string `json:"token_name,omitempty"` | ||
Scopes []string `json:"scopes,omitempty"` | ||
AllowList []string `json:"allow_list,omitempty"` | ||
EffectiveScope *APIEffectiveScopeFields `json:"effective_scope,omitempty"` | ||
} | ||
type APIEffectiveScopeFields struct { | ||
AllowList []string `json:"allow_list,omitempty"` | ||
Site []string `json:"site_permissions,omitempty"` | ||
Org map[string][]string `json:"org_permissions,omitempty"` | ||
User []string `json:"user_permissions,omitempty"` | ||
} | ||
func APIKeyFields(ctx context.Context, log slog.Logger, key database.APIKey) APIKeyAuditFields { | ||
fields := APIKeyAuditFields{ | ||
ID: key.ID, | ||
TokenName: key.TokenName, | ||
Scopes: apiKeyScopesToStrings(key.Scopes), | ||
AllowList: allowListToStrings(key.AllowList), | ||
} | ||
expanded, err := key.ScopeSet().Expand() | ||
if err != nil { | ||
log.Warn(ctx, "expand api key effective scope", slog.Error(err)) | ||
return fields | ||
} | ||
fields.EffectiveScope = &APIEffectiveScopeFields{ | ||
AllowList: allowListElementsToStrings(expanded.AllowIDList), | ||
Site: permissionsToStrings(expanded.Site), | ||
Org: orgPermissionsToStrings(expanded.Org), | ||
User: permissionsToStrings(expanded.User), | ||
} | ||
return fields | ||
} | ||
func WrapAPIKeyFields(fields APIKeyAuditFields) map[string]any { | ||
return map[string]any{"api_key": fields} | ||
} | ||
func mergeAdditionalFields(ctx context.Context, log slog.Logger, existing json.RawMessage, apiKeyFields APIKeyAuditFields) json.RawMessage { | ||
base := map[string]any{} | ||
if len(existing) > 0 { | ||
if err := json.Unmarshal(existing, &base); err != nil { | ||
log.Warn(ctx, "unmarshal audit additional fields", slog.Error(err)) | ||
base = map[string]any{} | ||
} | ||
} | ||
base["request_api_key"] = apiKeyFields | ||
merged, err := json.Marshal(base) | ||
if err != nil { | ||
log.Warn(ctx, "marshal audit additional fields", slog.Error(err)) | ||
return existing | ||
} | ||
return json.RawMessage(merged) | ||
} | ||
func apiKeyScopesToStrings(scopes database.APIKeyScopes) []string { | ||
if len(scopes) == 0 { | ||
return nil | ||
} | ||
out := make([]string, 0, len(scopes)) | ||
for _, scope := range scopes { | ||
out = append(out, string(scope)) | ||
} | ||
return out | ||
} | ||
func allowListToStrings(list database.AllowList) []string { | ||
if len(list) == 0 { | ||
return nil | ||
} | ||
out := make([]string, 0, len(list)) | ||
for _, entry := range list { | ||
out = append(out, entry.String()) | ||
} | ||
return out | ||
} | ||
func allowListElementsToStrings(list []rbac.AllowListElement) []string { | ||
if len(list) == 0 { | ||
return nil | ||
} | ||
out := make([]string, 0, len(list)) | ||
for _, entry := range list { | ||
out = append(out, entry.String()) | ||
} | ||
return out | ||
} | ||
func permissionsToStrings(perms []rbac.Permission) []string { | ||
if len(perms) == 0 { | ||
return nil | ||
} | ||
out := make([]string, 0, len(perms)) | ||
for _, perm := range perms { | ||
out = append(out, perm.ResourceType+":"+string(perm.Action)) | ||
} | ||
return out | ||
} | ||
func orgPermissionsToStrings(perms map[string][]rbac.Permission) map[string][]string { | ||
if len(perms) == 0 { | ||
return nil | ||
} | ||
out := make(map[string][]string, len(perms)) | ||
for orgID, list := range perms { | ||
out[orgID] = permissionsToStrings(list) | ||
} | ||
return out | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -58,6 +58,12 @@ func (r *Request[T]) UpdateOrganizationID(id uuid.UUID) { | ||
r.params.OrganizationID = id | ||
} | ||
// SetAdditionalFields allows callers to attach custom metadata that will be | ||
// merged into the audit log payload. | ||
func (r *Request[T]) SetAdditionalFields(fields interface{}) { | ||
r.params.AdditionalFields = fields | ||
} | ||
type BackgroundAuditParams[T Auditable] struct { | ||
Audit Auditor | ||
Log slog.Logger | ||
@@ -397,6 +403,11 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request | ||
} | ||
} | ||
if key, ok := httpmw.APIKeyOptional(p.Request); ok { | ||
fields := APIKeyFields(logCtx, p.Log, key) | ||
additionalFieldsRaw = mergeAdditionalFields(logCtx, p.Log, additionalFieldsRaw, fields) | ||
} | ||
Comment on lines +406 to +409 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Is this is a lot of extra data to staple to every audit log? If we are trying to debug rbac failures, the raw input is logged on authz failures. Do we need to have scope metadata on all audit log entries? | ||
var userID uuid.UUID | ||
key, ok := httpmw.APIKeyOptional(p.Request) | ||
switch { | ||
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.