S3 IAM: Added ListPolicyVersions and GetPolicyVersion support (#8395)
* test(s3/iam): add managed policy CRUD lifecycle integration coverage * s3/iam: add ListPolicyVersions and GetPolicyVersion support * test(s3/iam): cover ListPolicyVersions and GetPolicyVersion
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/awserr"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -202,6 +203,116 @@ func TestIAMPolicyManagement(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("managed_policy_crud_lifecycle", func(t *testing.T) {
|
||||
policyDoc := `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:GetObject","Resource":"arn:aws:s3:::*"}]}`
|
||||
|
||||
policyNames := []string{"test-managed-policy-lifecycle-a", "test-managed-policy-lifecycle-b"}
|
||||
policyArns := make([]*string, 0, len(policyNames))
|
||||
for _, policyName := range policyNames {
|
||||
createResp, err := iamClient.CreatePolicy(&iam.CreatePolicyInput{
|
||||
PolicyName: aws.String(policyName),
|
||||
PolicyDocument: aws.String(policyDoc),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
policyArns = append(policyArns, createResp.Policy.Arn)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
for _, policyArn := range policyArns {
|
||||
_, _ = iamClient.DeletePolicy(&iam.DeletePolicyInput{PolicyArn: policyArn})
|
||||
}
|
||||
})
|
||||
|
||||
listResp, err := iamClient.ListPolicies(&iam.ListPoliciesInput{})
|
||||
require.NoError(t, err)
|
||||
|
||||
foundByName := map[string]bool{}
|
||||
for _, policy := range listResp.Policies {
|
||||
if policy.PolicyName != nil {
|
||||
foundByName[*policy.PolicyName] = true
|
||||
}
|
||||
}
|
||||
for _, policyName := range policyNames {
|
||||
assert.True(t, foundByName[policyName], "policy %s should be listed", policyName)
|
||||
}
|
||||
|
||||
getResp, err := iamClient.GetPolicy(&iam.GetPolicyInput{PolicyArn: policyArns[0]})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, getResp.Policy)
|
||||
assert.Equal(t, policyNames[0], aws.StringValue(getResp.Policy.PolicyName))
|
||||
assert.Equal(t, aws.StringValue(policyArns[0]), aws.StringValue(getResp.Policy.Arn))
|
||||
|
||||
_, err = iamClient.DeletePolicy(&iam.DeletePolicyInput{PolicyArn: policyArns[0]})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = iamClient.GetPolicy(&iam.GetPolicyInput{PolicyArn: policyArns[0]})
|
||||
require.Error(t, err)
|
||||
awsErr, ok := err.(awserr.Error)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, iam.ErrCodeNoSuchEntityException, awsErr.Code())
|
||||
|
||||
listAfterDeleteResp, err := iamClient.ListPolicies(&iam.ListPoliciesInput{})
|
||||
require.NoError(t, err)
|
||||
deletedPolicyFound := false
|
||||
remainingPolicyFound := false
|
||||
for _, policy := range listAfterDeleteResp.Policies {
|
||||
if policy.PolicyName == nil {
|
||||
continue
|
||||
}
|
||||
if *policy.PolicyName == policyNames[0] {
|
||||
deletedPolicyFound = true
|
||||
}
|
||||
if *policy.PolicyName == policyNames[1] {
|
||||
remainingPolicyFound = true
|
||||
}
|
||||
}
|
||||
assert.False(t, deletedPolicyFound, "deleted policy should no longer be listed")
|
||||
assert.True(t, remainingPolicyFound, "remaining policy should still be listed")
|
||||
|
||||
policyArns[0] = nil
|
||||
})
|
||||
|
||||
t.Run("managed_policy_versions", func(t *testing.T) {
|
||||
policyName := "test-managed-policy-version"
|
||||
policyDoc := `{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:ListBucket","Resource":"*"}]}`
|
||||
|
||||
createResp, err := iamClient.CreatePolicy(&iam.CreatePolicyInput{
|
||||
PolicyName: aws.String(policyName),
|
||||
PolicyDocument: aws.String(policyDoc),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
_, _ = iamClient.DeletePolicy(&iam.DeletePolicyInput{PolicyArn: createResp.Policy.Arn})
|
||||
})
|
||||
|
||||
listVersionsResp, err := iamClient.ListPolicyVersions(&iam.ListPolicyVersionsInput{
|
||||
PolicyArn: createResp.Policy.Arn,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, listVersionsResp.Versions)
|
||||
assert.Equal(t, "v1", aws.StringValue(listVersionsResp.Versions[0].VersionId))
|
||||
assert.Equal(t, true, aws.BoolValue(listVersionsResp.Versions[0].IsDefaultVersion))
|
||||
|
||||
getVersionResp, err := iamClient.GetPolicyVersion(&iam.GetPolicyVersionInput{
|
||||
PolicyArn: createResp.Policy.Arn,
|
||||
VersionId: aws.String("v1"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, getVersionResp.PolicyVersion)
|
||||
assert.Equal(t, "v1", aws.StringValue(getVersionResp.PolicyVersion.VersionId))
|
||||
assert.Contains(t, aws.StringValue(getVersionResp.PolicyVersion.Document), "s3:ListBucket")
|
||||
|
||||
_, err = iamClient.GetPolicyVersion(&iam.GetPolicyVersionInput{
|
||||
PolicyArn: createResp.Policy.Arn,
|
||||
VersionId: aws.String("v2"),
|
||||
})
|
||||
require.Error(t, err)
|
||||
awsErr, ok := err.(awserr.Error)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, iam.ErrCodeNoSuchEntityException, awsErr.Code())
|
||||
})
|
||||
|
||||
t.Run("user_inline_policy", func(t *testing.T) {
|
||||
userName := "test-user-policy"
|
||||
_, err := iamClient.CreateUser(&iam.CreateUserInput{
|
||||
|
||||
@@ -81,6 +81,26 @@ type GetPolicyResponse struct {
|
||||
} `xml:"GetPolicyResult"`
|
||||
}
|
||||
|
||||
// ListPolicyVersionsResponse is the response for ListPolicyVersions action.
|
||||
type ListPolicyVersionsResponse struct {
|
||||
CommonResponse
|
||||
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ListPolicyVersionsResponse"`
|
||||
ListPolicyVersionsResult struct {
|
||||
Versions []*iam.PolicyVersion `xml:"Versions>member"`
|
||||
IsTruncated bool `xml:"IsTruncated"`
|
||||
Marker string `xml:"Marker,omitempty"`
|
||||
} `xml:"ListPolicyVersionsResult"`
|
||||
}
|
||||
|
||||
// GetPolicyVersionResponse is the response for GetPolicyVersion action.
|
||||
type GetPolicyVersionResponse struct {
|
||||
CommonResponse
|
||||
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ GetPolicyVersionResponse"`
|
||||
GetPolicyVersionResult struct {
|
||||
PolicyVersion iam.PolicyVersion `xml:"PolicyVersion"`
|
||||
} `xml:"GetPolicyVersionResult"`
|
||||
}
|
||||
|
||||
// CreateUserResponse is the response for CreateUser action.
|
||||
type CreateUserResponse struct {
|
||||
CommonResponse
|
||||
|
||||
@@ -75,6 +75,8 @@ type (
|
||||
iamDeletePolicyResponse = iamlib.DeletePolicyResponse
|
||||
iamListPoliciesResponse = iamlib.ListPoliciesResponse
|
||||
iamGetPolicyResponse = iamlib.GetPolicyResponse
|
||||
iamListPolicyVersionsResponse = iamlib.ListPolicyVersionsResponse
|
||||
iamGetPolicyVersionResponse = iamlib.GetPolicyVersionResponse
|
||||
iamCreateUserResponse = iamlib.CreateUserResponse
|
||||
iamDeleteUserResponse = iamlib.DeleteUserResponse
|
||||
iamGetUserResponse = iamlib.GetUserResponse
|
||||
@@ -577,6 +579,82 @@ func (e *EmbeddedIamApi) GetPolicy(ctx context.Context, values url.Values) (iamG
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// ListPolicyVersions lists versions for a managed policy.
|
||||
// Current SeaweedFS implementation stores one version per policy (v1).
|
||||
func (e *EmbeddedIamApi) ListPolicyVersions(ctx context.Context, values url.Values) (iamListPolicyVersionsResponse, *iamError) {
|
||||
var resp iamListPolicyVersionsResponse
|
||||
policyArn := values.Get("PolicyArn")
|
||||
policyName, err := iamPolicyNameFromArn(policyArn)
|
||||
if err != nil {
|
||||
return resp, &iamError{Code: iam.ErrCodeInvalidInputException, Error: err}
|
||||
}
|
||||
if e.credentialManager == nil {
|
||||
return resp, &iamError{Code: iam.ErrCodeServiceFailureException, Error: fmt.Errorf("credential manager not configured")}
|
||||
}
|
||||
policy, err := e.credentialManager.GetPolicy(ctx, policyName)
|
||||
if err != nil {
|
||||
return resp, &iamError{Code: iam.ErrCodeServiceFailureException, Error: err}
|
||||
}
|
||||
if policy == nil {
|
||||
return resp, &iamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("policy %s not found", policyName)}
|
||||
}
|
||||
|
||||
versionID := "v1"
|
||||
isDefaultVersion := true
|
||||
createDate := time.Now().UTC()
|
||||
resp.ListPolicyVersionsResult.Versions = []*iam.PolicyVersion{{
|
||||
VersionId: &versionID,
|
||||
IsDefaultVersion: &isDefaultVersion,
|
||||
CreateDate: &createDate,
|
||||
}}
|
||||
resp.ListPolicyVersionsResult.IsTruncated = false
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetPolicyVersion returns the document for a specific policy version.
|
||||
// Current SeaweedFS implementation stores one version per policy (v1).
|
||||
func (e *EmbeddedIamApi) GetPolicyVersion(ctx context.Context, values url.Values) (iamGetPolicyVersionResponse, *iamError) {
|
||||
var resp iamGetPolicyVersionResponse
|
||||
policyArn := values.Get("PolicyArn")
|
||||
versionID := values.Get("VersionId")
|
||||
if versionID == "" {
|
||||
return resp, &iamError{Code: iam.ErrCodeInvalidInputException, Error: fmt.Errorf("VersionId is required")}
|
||||
}
|
||||
|
||||
policyName, err := iamPolicyNameFromArn(policyArn)
|
||||
if err != nil {
|
||||
return resp, &iamError{Code: iam.ErrCodeInvalidInputException, Error: err}
|
||||
}
|
||||
if e.credentialManager == nil {
|
||||
return resp, &iamError{Code: iam.ErrCodeServiceFailureException, Error: fmt.Errorf("credential manager not configured")}
|
||||
}
|
||||
policy, err := e.credentialManager.GetPolicy(ctx, policyName)
|
||||
if err != nil {
|
||||
return resp, &iamError{Code: iam.ErrCodeServiceFailureException, Error: err}
|
||||
}
|
||||
if policy == nil {
|
||||
return resp, &iamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("policy %s not found", policyName)}
|
||||
}
|
||||
if versionID != "v1" {
|
||||
return resp, &iamError{Code: iam.ErrCodeNoSuchEntityException, Error: fmt.Errorf("policy version %s not found", versionID)}
|
||||
}
|
||||
policyDocumentJSON, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
return resp, &iamError{Code: iam.ErrCodeServiceFailureException, Error: err}
|
||||
}
|
||||
|
||||
isDefaultVersion := true
|
||||
createDate := time.Now().UTC()
|
||||
document := string(policyDocumentJSON)
|
||||
resp.GetPolicyVersionResult.PolicyVersion = iam.PolicyVersion{
|
||||
VersionId: &versionID,
|
||||
IsDefaultVersion: &isDefaultVersion,
|
||||
CreateDate: &createDate,
|
||||
Document: &document,
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func iamPolicyNameFromArn(policyArn string) (string, error) {
|
||||
const policyPathDelimiter = ":policy/"
|
||||
idx := strings.Index(policyArn, policyPathDelimiter)
|
||||
@@ -1453,7 +1531,7 @@ func (e *EmbeddedIamApi) ExecuteAction(ctx context.Context, values url.Values, s
|
||||
action := values.Get("Action")
|
||||
if e.readOnly {
|
||||
switch action {
|
||||
case "ListUsers", "ListAccessKeys", "GetUser", "GetUserPolicy", "ListAttachedUserPolicies", "ListPolicies", "GetPolicy", "ListServiceAccounts", "GetServiceAccount":
|
||||
case "ListUsers", "ListAccessKeys", "GetUser", "GetUserPolicy", "ListAttachedUserPolicies", "ListPolicies", "GetPolicy", "ListPolicyVersions", "GetPolicyVersion", "ListServiceAccounts", "GetServiceAccount":
|
||||
// Allowed read-only actions
|
||||
default:
|
||||
return nil, &iamError{Code: s3err.GetAPIError(s3err.ErrAccessDenied).Code, Error: fmt.Errorf("IAM write operations are disabled on this server")}
|
||||
@@ -1570,6 +1648,18 @@ func (e *EmbeddedIamApi) ExecuteAction(ctx context.Context, values url.Values, s
|
||||
return nil, iamErr
|
||||
}
|
||||
changed = false
|
||||
case "ListPolicyVersions":
|
||||
response, iamErr = e.ListPolicyVersions(ctx, values)
|
||||
if iamErr != nil {
|
||||
return nil, iamErr
|
||||
}
|
||||
changed = false
|
||||
case "GetPolicyVersion":
|
||||
response, iamErr = e.GetPolicyVersion(ctx, values)
|
||||
if iamErr != nil {
|
||||
return nil, iamErr
|
||||
}
|
||||
changed = false
|
||||
case "SetUserStatus":
|
||||
response, iamErr = e.SetUserStatus(s3cfg, values)
|
||||
if iamErr != nil {
|
||||
|
||||
Reference in New Issue
Block a user