Add the policy name to the audit logs tags when doing policy-based API calls. Add retention settings to tags (#20638)

* Add the policy name to the audit log tags when doing policy-based API calls

* Audit log the retention settings requested in the API call

* Audit log of retention on PutObjectRetention API path too
This commit is contained in:
Mark Theunissen 2024-11-26 04:17:12 +11:00 committed by GitHub
parent c07e5b49d4
commit d202fdd022
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 112 additions and 1 deletions

View File

@ -37,6 +37,7 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/config/dns" "github.com/minio/minio/internal/config/dns"
"github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
xldap "github.com/minio/pkg/v3/ldap" xldap "github.com/minio/pkg/v3/ldap"
"github.com/minio/pkg/v3/policy" "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) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errTooManyPolicies), r.URL)
return return
} }
setReqInfoPolicyName(ctx, name)
policyDoc, err := globalIAMSys.InfoPolicy(name) policyDoc, err := globalIAMSys.InfoPolicy(name)
if err != nil { if err != nil {
@ -1682,6 +1684,7 @@ func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Requ
vars := mux.Vars(r) vars := mux.Vars(r)
policyName := vars["name"] policyName := vars["name"]
setReqInfoPolicyName(ctx, policyName)
if err := globalIAMSys.DeletePolicy(ctx, policyName, true); err != nil { if err := globalIAMSys.DeletePolicy(ctx, policyName, true); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) 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) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
return return
} }
setReqInfoPolicyName(ctx, policyName)
// Error out if Content-Length is missing. // Error out if Content-Length is missing.
if r.ContentLength <= 0 { if r.ContentLength <= 0 {
@ -1779,6 +1783,7 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http
policyName := vars["policyName"] policyName := vars["policyName"]
entityName := vars["userOrGroup"] entityName := vars["userOrGroup"]
isGroup := vars["isGroup"] == "true" isGroup := vars["isGroup"] == "true"
setReqInfoPolicyName(ctx, policyName)
if !isGroup { if !isGroup {
ok, _, err := globalIAMSys.IsTempUser(entityName) 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) { func (a adminAPIHandlers) ListPolicyMappingEntities(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
@ -1966,6 +1971,7 @@ func (a adminAPIHandlers) AttachDetachPolicyBuiltin(w http.ResponseWriter, r *ht
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
setReqInfoPolicyName(ctx, strings.Join(addedOrRemoved, ","))
respBody := madmin.PolicyAssociationResp{ respBody := madmin.PolicyAssociationResp{
UpdatedAt: updatedAt, UpdatedAt: updatedAt,
@ -2812,3 +2818,10 @@ func commonAddServiceAccount(r *http.Request, ldap bool) (context.Context, auth.
return ctx, cred, opts, createReq, targetUser, APIError{} 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)
}

View File

@ -1809,6 +1809,10 @@ func (api objectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWri
return return
} }
// Audit log tags.
reqInfo := logger.GetReqInfo(ctx)
reqInfo.SetTags("retention", config.String())
configData, err := xml.Marshal(config) configData, err := xml.Marshal(config)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)

View File

@ -2899,6 +2899,8 @@ func (api objectAPIHandlers) PutObjectRetentionHandler(w http.ResponseWriter, r
writeErrorResponse(ctx, w, apiErr, r.URL) writeErrorResponse(ctx, w, apiErr, r.URL)
return return
} }
reqInfo := logger.GetReqInfo(ctx)
reqInfo.SetTags("retention", objRetention.String())
opts, err := getOpts(ctx, r, bucket, object) opts, err := getOpts(ctx, r, bucket, object)
if err != nil { if err != nil {

View File

@ -237,6 +237,25 @@ type Config struct {
} `xml:"Rule,omitempty"` } `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 // Enabled returns true if config.ObjectLockEnabled is set to Enabled
func (config *Config) Enabled() bool { func (config *Config) Enabled() bool {
return config.ObjectLockEnabled == Enabled return config.ObjectLockEnabled == Enabled
@ -349,6 +368,10 @@ type ObjectRetention struct {
RetainUntilDate RetentionDate `xml:"RetainUntilDate,omitempty"` 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. // Maximum 4KiB size per object retention config.
const maxObjectRetentionSize = 1 << 12 const maxObjectRetentionSize = 1 << 12

View File

@ -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)
}
}
}