fix: check sub-policy properly when present (#21642)

This fixes a security issue where sub-policy attached to a service
account or STS account is not properly validated under certain "own"
account operations (like creating new service accounts). This allowed a
service account to create new service accounts for the same user
bypassing the inline policy restriction.
This commit is contained in:
Aditya Manthramurthy
2025-10-15 10:00:45 -07:00
committed by GitHub
parent 334c313da4
commit c1a49490c7
3 changed files with 228 additions and 15 deletions

View File

@@ -2400,21 +2400,8 @@ func isAllowedBySessionPolicyForServiceAccount(args policy.Args) (hasSessionPoli
// policy, regardless of whether the number of statements is 0, this
// includes `null`, `{}` and `{"Statement": null}`. In fact, MinIO Console
// sends `null` when no policy is set and the intended behavior is that the
// service account should inherit parent policy.
//
// However, for a policy like `{"Statement":[]}`, the intention is to not
// provide any permissions via the session policy - i.e. the service account
// can do nothing (such a JSON could be generated by an external application
// as the policy for the service account). Inheriting the parent policy in
// such a case, is a security issue. Ideally, we should not allow such
// behavior, but for compatibility with the Console, we currently allow it.
//
// TODO:
//
// 1. fix console behavior and allow this inheritance for service accounts
// created before a certain (TBD) future date.
//
// 2. do not allow empty statement policies for service accounts.
// service account should inherit parent policy. So when policy is empty in
// all fields we return hasSessionPolicy=false.
if subPolicy.Version == "" && subPolicy.Statements == nil && subPolicy.ID == "" {
hasSessionPolicy = false
return hasSessionPolicy, isAllowed
@@ -2423,8 +2410,16 @@ func isAllowedBySessionPolicyForServiceAccount(args policy.Args) (hasSessionPoli
// As the session policy exists, even if the parent is the root account, it
// must be restricted by it. So, we set `.IsOwner` to false here
// unconditionally.
//
// We also set `DenyOnly` arg to false here - this is an IMPORTANT corner
// case: DenyOnly is used only for allowing an account to do actions related
// to its own account (like create service accounts for itself, among
// others). However when a session policy is present, we need to validate
// that the action is actually allowed, rather than checking if the action
// is only disallowed.
sessionPolicyArgs := args
sessionPolicyArgs.IsOwner = false
sessionPolicyArgs.DenyOnly = false
// Sub policy is set and valid.
return hasSessionPolicy, subPolicy.IsAllowed(sessionPolicyArgs)
@@ -2465,8 +2460,16 @@ func isAllowedBySessionPolicy(args policy.Args) (hasSessionPolicy bool, isAllowe
// As the session policy exists, even if the parent is the root account, it
// must be restricted by it. So, we set `.IsOwner` to false here
// unconditionally.
//
// We also set `DenyOnly` arg to false here - this is an IMPORTANT corner
// case: DenyOnly is used only for allowing an account to do actions related
// to its own account (like create service accounts for itself, among
// others). However when a session policy is present, we need to validate
// that the action is actually allowed, rather than checking if the action
// is only disallowed.
sessionPolicyArgs := args
sessionPolicyArgs.IsOwner = false
sessionPolicyArgs.DenyOnly = false
// Sub policy is set and valid.
return hasSessionPolicy, subPolicy.IsAllowed(sessionPolicyArgs)