Files
versitygw/auth/fixed_bucket_owner.go
T
niksis02 c84c5f645a feat: accept principal ARNs in bucket policies under standalone IAM
Bucket policy `Principal` named callers by access key id. Under the standalone IAM service it now names them by AWS-style ARN, as real S3 does: a user ARN, a role ARN (covering every session of that role), an assumed-role ARN (covering one session), the account root ARN or bare account id, or `*`. Every other IAM backend has no ARNs to name anything by and keeps access-key principals unchanged, gated on a new `auth.PrincipalResolver` capability interface that only the standalone client implements.

`auth.Account` carries `Arn` and `RoleArn`, filled at authentication time, so a session can be matched against both its own ARN and its role's. Principals are validated at PutBucketPolicy time through a new `/private/resolve-principals` endpoint, which rejects anything that does not name a live identity with `MalformedPolicy: Invalid principal in policy`.

An `Allow` naming the account root ARN or bare account id delegates to the account's own IAM rather than granting on its own, while a `Deny` naming it denies every principal in the account outright. Denial messages now name the caller by ARN wherever one exists.

Also fixes `aws:PrincipalArn` for assumed-role sessions, which reported the session ARN where AWS reports the role's, and stops an unreachable IAM service being reported as a malformed policy.
2026-09-03 23:51:55 +04:00

68 lines
2.8 KiB
Go

// Copyright 2026 Versity Software
// This file is licensed under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package auth
// FixedBucketOwner is implemented by IAM backends that give every bucket the
// same owner instead of the account that created it — currently only the
// standalone IAM service client, which has no per-user ownership to express:
// every account is a plain RoleUser, they cannot be enumerated, and access is
// decided by IAM policy rather than by ACL.
//
// Backends that do not implement it keep per-creator ownership as before.
type FixedBucketOwner interface {
BucketOwner() Account
}
// ResolveFixedBucketOwner reports the account that owns every bucket when iam
// fixes ownership, and false when ownership follows the creator instead.
func ResolveFixedBucketOwner(iam IAMService) (Account, bool) {
fbo, ok := iam.(FixedBucketOwner)
if !ok {
return Account{}, false
}
return fbo.BucketOwner(), true
}
// rootIdentity returns the account a storage backend should see for a request
// signed with the gateway's root credentials. The S3 request path knows root
// only by its access key and secret, so root would otherwise reach the
// backend with the zero uid/gid — which the posix backend's --chuid/--chgid
// then tries to chown to, an operation an unprivileged gateway can never
// perform.
//
// An IAM backend that fixes bucket ownership to root also defines the POSIX
// identity root owns those buckets with, so take it from there: root's own
// object writes then land with the same ownership as the buckets root owns.
// Backends that do not fix ownership resolve a real per-account uid/gid for
// every other account and keep root exactly as it was.
//
// The same backend is also the one that knows root's principal ARN, which
// the S3 request path likewise cannot derive: root is the only identity
// resolved locally rather than through the IAM service, so without this it
// would reach bucket-policy matching unnamed and a statement naming the
// account root ARN would miss it.
func rootIdentity(iam IAMService, root Account) Account {
owner, fixed := ResolveFixedBucketOwner(iam)
if !fixed || owner.Access != root.Access {
return root
}
root.UserID = owner.UserID
root.GroupID = owner.GroupID
root.ProjectID = owner.ProjectID
root.Arn = owner.Arn
return root
}