diff --git a/cmd/admin-handlers-users.go b/cmd/admin-handlers-users.go index 0e54c13de..641d58195 100644 --- a/cmd/admin-handlers-users.go +++ b/cmd/admin-handlers-users.go @@ -37,6 +37,7 @@ import ( "github.com/minio/madmin-go/v3" "github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/config/dns" + "github.com/minio/minio/internal/logger" "github.com/minio/mux" xldap "github.com/minio/pkg/v3/ldap" "github.com/minio/pkg/v3/policy" @@ -1579,6 +1580,7 @@ func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Reques writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errTooManyPolicies), r.URL) return } + setReqInfoPolicyName(ctx, name) policyDoc, err := globalIAMSys.InfoPolicy(name) if err != nil { @@ -1682,6 +1684,7 @@ func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Requ vars := mux.Vars(r) policyName := vars["name"] + setReqInfoPolicyName(ctx, policyName) if err := globalIAMSys.DeletePolicy(ctx, policyName, true); err != nil { writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) @@ -1714,6 +1717,7 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL) return } + setReqInfoPolicyName(ctx, policyName) // Error out if Content-Length is missing. if r.ContentLength <= 0 { @@ -1779,6 +1783,7 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http policyName := vars["policyName"] entityName := vars["userOrGroup"] isGroup := vars["isGroup"] == "true" + setReqInfoPolicyName(ctx, policyName) if !isGroup { ok, _, err := globalIAMSys.IsTempUser(entityName) @@ -1864,7 +1869,7 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http })) } -// ListPolicyMappingEntities - GET /minio/admin/v3/idp/builtin/polciy-entities?policy=xxx&user=xxx&group=xxx +// ListPolicyMappingEntities - GET /minio/admin/v3/idp/builtin/policy-entities?policy=xxx&user=xxx&group=xxx func (a adminAPIHandlers) ListPolicyMappingEntities(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -1966,6 +1971,7 @@ func (a adminAPIHandlers) AttachDetachPolicyBuiltin(w http.ResponseWriter, r *ht writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) return } + setReqInfoPolicyName(ctx, strings.Join(addedOrRemoved, ",")) respBody := madmin.PolicyAssociationResp{ UpdatedAt: updatedAt, @@ -2812,3 +2818,10 @@ func commonAddServiceAccount(r *http.Request, ldap bool) (context.Context, auth. return ctx, cred, opts, createReq, targetUser, APIError{} } + +// setReqInfoPolicyName will set the given policyName as a tag on the context's request info, +// so that it appears in audit logs. +func setReqInfoPolicyName(ctx context.Context, policyName string) { + reqInfo := logger.GetReqInfo(ctx) + reqInfo.SetTags("policyName", policyName) +} diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go index 41a57e025..6db63a042 100644 --- a/cmd/bucket-handlers.go +++ b/cmd/bucket-handlers.go @@ -1809,6 +1809,10 @@ func (api objectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWri return } + // Audit log tags. + reqInfo := logger.GetReqInfo(ctx) + reqInfo.SetTags("retention", config.String()) + configData, err := xml.Marshal(config) if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index cf0377fa7..b99fb7e00 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -2899,6 +2899,8 @@ func (api objectAPIHandlers) PutObjectRetentionHandler(w http.ResponseWriter, r writeErrorResponse(ctx, w, apiErr, r.URL) return } + reqInfo := logger.GetReqInfo(ctx) + reqInfo.SetTags("retention", objRetention.String()) opts, err := getOpts(ctx, r, bucket, object) if err != nil { diff --git a/internal/bucket/object/lock/lock.go b/internal/bucket/object/lock/lock.go index 572e9db66..79f1421f5 100644 --- a/internal/bucket/object/lock/lock.go +++ b/internal/bucket/object/lock/lock.go @@ -237,6 +237,25 @@ type Config struct { } `xml:"Rule,omitempty"` } +// String returns the human readable format of object lock configuration, used in audit logs. +func (config Config) String() string { + parts := []string{ + fmt.Sprintf("Enabled: %v", config.Enabled()), + } + if config.Rule != nil { + if config.Rule.DefaultRetention.Mode != "" { + parts = append(parts, fmt.Sprintf("Mode: %s", config.Rule.DefaultRetention.Mode)) + } + if config.Rule.DefaultRetention.Days != nil { + parts = append(parts, fmt.Sprintf("Days: %d", *config.Rule.DefaultRetention.Days)) + } + if config.Rule.DefaultRetention.Years != nil { + parts = append(parts, fmt.Sprintf("Years: %d", *config.Rule.DefaultRetention.Years)) + } + } + return strings.Join(parts, ", ") +} + // Enabled returns true if config.ObjectLockEnabled is set to Enabled func (config *Config) Enabled() bool { return config.ObjectLockEnabled == Enabled @@ -349,6 +368,10 @@ type ObjectRetention struct { RetainUntilDate RetentionDate `xml:"RetainUntilDate,omitempty"` } +func (o ObjectRetention) String() string { + return fmt.Sprintf("Mode: %s, RetainUntilDate: %s", o.Mode, o.RetainUntilDate.Time) +} + // Maximum 4KiB size per object retention config. const maxObjectRetentionSize = 1 << 12 diff --git a/internal/bucket/object/lock/lock_test.go b/internal/bucket/object/lock/lock_test.go index 91313482d..e31586a76 100644 --- a/internal/bucket/object/lock/lock_test.go +++ b/internal/bucket/object/lock/lock_test.go @@ -611,3 +611,72 @@ func TestFilterObjectLockMetadata(t *testing.T) { } } } + +func TestToString(t *testing.T) { + days := uint64(30) + daysPtr := &days + years := uint64(2) + yearsPtr := &years + + tests := []struct { + name string + c Config + want string + }{ + { + name: "happy case", + c: Config{ + ObjectLockEnabled: "Enabled", + }, + want: "Enabled: true", + }, + { + name: "with default retention days", + c: Config{ + ObjectLockEnabled: "Enabled", + Rule: &struct { + DefaultRetention DefaultRetention `xml:"DefaultRetention"` + }{ + DefaultRetention: DefaultRetention{ + Mode: RetGovernance, + Days: daysPtr, + }, + }, + }, + want: "Enabled: true, Mode: GOVERNANCE, Days: 30", + }, + { + name: "with default retention years", + c: Config{ + ObjectLockEnabled: "Enabled", + Rule: &struct { + DefaultRetention DefaultRetention `xml:"DefaultRetention"` + }{ + DefaultRetention: DefaultRetention{ + Mode: RetCompliance, + Years: yearsPtr, + }, + }, + }, + want: "Enabled: true, Mode: COMPLIANCE, Years: 2", + }, + { + name: "disabled case", + c: Config{ + ObjectLockEnabled: "Disabled", + }, + want: "Enabled: false", + }, + { + name: "empty case", + c: Config{}, + want: "Enabled: false", + }, + } + for _, tt := range tests { + got := tt.c.String() + if got != tt.want { + t.Errorf("test: %s, got: '%v', want: '%v'", tt.name, got, tt.want) + } + } +}