mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-25 22:40:44 +01:00
s3api: support group inline policies + Condition enforcement (#9569)
* test(s3api): cover IAM inline policy aws:SourceIp + group inline gap Unit tests under weed/s3api/ drive PutUserPolicy / PutGroupPolicy → reload → VerifyActionPermission with a synthetic 127.0.0.1 request and assert that the policy's IpAddress condition flips the outcome. The user-policy cases pass on master (hydrateRuntimePolicies already routes inline docs through the policy engine, so Condition blocks are honored end- to-end). The group-policy case fails: PutGroupPolicy still returns NotImplemented, so a group inline doc never lands in the engine. Integration counterparts live under test/s3/iam/ and exercise the same paths against a live SeaweedFS S3+IAM endpoint. * s3api: support group inline policies + Condition enforcement PutGroupPolicy/GetGroupPolicy/DeleteGroupPolicy/ListGroupPolicies used to return NotImplemented in embedded IAM mode, so anything attached to a group as an inline doc — including aws:SourceIp or any other Condition — was simply unreachable. Wire the four endpoints to the credential-store methods that were already in place (memory, postgres, filer_etc all implement GroupInlinePolicyStore). On every config reload, hydrateRuntimePolicies now also walks LoadGroupInlinePolicies, registers each doc in the IAM policy engine under __inline_group_policy__/<group>/<policy>, and appends that key to Group.PolicyNames so evaluateIAMPolicies picks it up through its existing group walk. PutGroupPolicy/DeleteGroupPolicy are added to the ReloadConfiguration trigger list in DoActions. Side fix: MemoryStore.LoadConfiguration now surfaces store.groups too. Without it iam.groups never repopulated on a memory-store reload, so group policy evaluation silently no-op'd whether the policy was inline or attached. The existing tests didn't notice because no test reloaded through cm after creating a group. The NotImplemented unit test is inverted to drive the new round-trip. * s3api: drop redundant refreshIAMConfiguration from Put/DeleteGroupPolicy DoActions already triggers ReloadConfiguration for both actions via the explicit reload list, so calling refreshIAMConfiguration inline runs the load twice per request. Per PR review. * s3api: scope group-policy resource names per test; tighten deny polling - Integration test resource names get a per-test suffix so retried or parallel CI jobs don't trip EntityAlreadyExists / BucketAlreadyExists. - Deny-path Eventually loops gate on AccessDenied via a typed helper rather than any non-nil error; transient setup errors no longer end the wait prematurely. - ListGroupPolicies returns ServiceFailure when the credential manager is nil, matching Put/Get/DeleteGroupPolicy. * test(s3 iam): cover both IPv4 and IPv6 loopback in allow CIDRs CI runners with happy-eyeballs resolve `localhost` to ::1 first, in which case a 127.0.0.0/8-only allow would silently never match and the deny-driven enforcement test would hang for the allow case. Add ::1/128 to every loopback-matching policy so the allow path works regardless of which loopback family the SDK lands on.
This commit is contained in:
1 parent
77ac781bbd
commit
285025eb73
6 files changed
+809
-61
No files matched your search
@@ -1671,10 +1671,18 @@ type inlinePolicyLoader interface {
|
||||
LoadInlinePolicies(ctx context.Context) (map[string]map[string]policy_engine.PolicyDocument, error)
|
||||
}
|
||||
|
||||
type groupInlinePolicyLoader interface {
|
||||
LoadGroupInlinePolicies(ctx context.Context) (map[string]map[string]policy_engine.PolicyDocument, error)
|
||||
}
|
||||
|
||||
func inlinePolicyRuntimeName(userName, policyName string) string {
|
||||
return "__inline_policy__/" + userName + "/" + policyName
|
||||
}
|
||||
|
||||
func inlineGroupPolicyRuntimeName(groupName, policyName string) string {
|
||||
return "__inline_group_policy__/" + groupName + "/" + policyName
|
||||
}
|
||||
|
||||
func mergePoliciesIntoConfiguration(config *iam_pb.S3ApiConfiguration, policies []*iam_pb.Policy) {
|
||||
if len(policies) == 0 {
|
||||
return
|
||||
@@ -1759,44 +1767,73 @@ func (iam *IdentityAccessManagement) hydrateRuntimePolicies(ctx context.Context,
|
||||
return nil
|
||||
}
|
||||
|
||||
inlineLoader, ok := store.(inlinePolicyLoader)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
inlinePoliciesByUser, err := inlineLoader.LoadInlinePolicies(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load inline policies for runtime: %w", err)
|
||||
}
|
||||
|
||||
if len(inlinePoliciesByUser) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
identityByName := make(map[string]*iam_pb.Identity, len(config.Identities))
|
||||
for _, identity := range config.Identities {
|
||||
identityByName[identity.Name] = identity
|
||||
}
|
||||
|
||||
inlinePolicies := make([]*iam_pb.Policy, 0)
|
||||
for userName, userPolicies := range inlinePoliciesByUser {
|
||||
identity, found := identityByName[userName]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
for policyName, policyDocument := range userPolicies {
|
||||
content, err := json.Marshal(policyDocument)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal inline policy %q for user %q: %w", policyName, userName, err)
|
||||
if inlineLoader, ok := store.(inlinePolicyLoader); ok {
|
||||
inlinePoliciesByUser, err := inlineLoader.LoadInlinePolicies(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load inline policies for runtime: %w", err)
|
||||
}
|
||||
if len(inlinePoliciesByUser) > 0 {
|
||||
identityByName := make(map[string]*iam_pb.Identity, len(config.Identities))
|
||||
for _, identity := range config.Identities {
|
||||
identityByName[identity.Name] = identity
|
||||
}
|
||||
|
||||
runtimePolicyName := inlinePolicyRuntimeName(userName, policyName)
|
||||
inlinePolicies = append(inlinePolicies, &iam_pb.Policy{
|
||||
Name: runtimePolicyName,
|
||||
Content: string(content),
|
||||
})
|
||||
identity.PolicyNames = appendUniquePolicyName(identity.PolicyNames, runtimePolicyName)
|
||||
for userName, userPolicies := range inlinePoliciesByUser {
|
||||
identity, found := identityByName[userName]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
for policyName, policyDocument := range userPolicies {
|
||||
content, err := json.Marshal(policyDocument)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal inline policy %q for user %q: %w", policyName, userName, err)
|
||||
}
|
||||
|
||||
runtimePolicyName := inlinePolicyRuntimeName(userName, policyName)
|
||||
inlinePolicies = append(inlinePolicies, &iam_pb.Policy{
|
||||
Name: runtimePolicyName,
|
||||
Content: string(content),
|
||||
})
|
||||
identity.PolicyNames = appendUniquePolicyName(identity.PolicyNames, runtimePolicyName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if groupLoader, ok := store.(groupInlinePolicyLoader); ok {
|
||||
groupPoliciesByName, err := groupLoader.LoadGroupInlinePolicies(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load group inline policies for runtime: %w", err)
|
||||
}
|
||||
if len(groupPoliciesByName) > 0 {
|
||||
groupByName := make(map[string]*iam_pb.Group, len(config.Groups))
|
||||
for _, group := range config.Groups {
|
||||
groupByName[group.Name] = group
|
||||
}
|
||||
|
||||
for groupName, groupPolicies := range groupPoliciesByName {
|
||||
group, found := groupByName[groupName]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
for policyName, policyDocument := range groupPolicies {
|
||||
content, err := json.Marshal(policyDocument)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal inline policy %q for group %q: %w", policyName, groupName, err)
|
||||
}
|
||||
|
||||
runtimePolicyName := inlineGroupPolicyRuntimeName(groupName, policyName)
|
||||
inlinePolicies = append(inlinePolicies, &iam_pb.Policy{
|
||||
Name: runtimePolicyName,
|
||||
Content: string(content),
|
||||
})
|
||||
group.PolicyNames = appendUniquePolicyName(group.PolicyNames, runtimePolicyName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user