@ -10,6 +10,7 @@ import (
"fmt"
"fmt"
"net/http"
"net/http"
"net/url"
"net/url"
"sort"
"strconv"
"strconv"
"strings"
"strings"
"sync"
"sync"
@ -71,6 +72,9 @@ type (
iamListAccessKeysResponse = iamlib . ListAccessKeysResponse
iamListAccessKeysResponse = iamlib . ListAccessKeysResponse
iamDeleteAccessKeyResponse = iamlib . DeleteAccessKeyResponse
iamDeleteAccessKeyResponse = iamlib . DeleteAccessKeyResponse
iamCreatePolicyResponse = iamlib . CreatePolicyResponse
iamCreatePolicyResponse = iamlib . CreatePolicyResponse
iamDeletePolicyResponse = iamlib . DeletePolicyResponse
iamListPoliciesResponse = iamlib . ListPoliciesResponse
iamGetPolicyResponse = iamlib . GetPolicyResponse
iamCreateUserResponse = iamlib . CreateUserResponse
iamCreateUserResponse = iamlib . CreateUserResponse
iamDeleteUserResponse = iamlib . DeleteUserResponse
iamDeleteUserResponse = iamlib . DeleteUserResponse
iamGetUserResponse = iamlib . GetUserResponse
iamGetUserResponse = iamlib . GetUserResponse
@ -172,6 +176,8 @@ func (e *EmbeddedIamApi) writeIamErrorResponse(w http.ResponseWriter, r *http.Re
s3err . WriteXMLResponse ( w , r , http . StatusInternalServerError , internalErrorResponse )
s3err . WriteXMLResponse ( w , r , http . StatusInternalServerError , internalErrorResponse )
case "NotImplemented" :
case "NotImplemented" :
s3err . WriteXMLResponse ( w , r , http . StatusNotImplemented , errorResp )
s3err . WriteXMLResponse ( w , r , http . StatusNotImplemented , errorResp )
case iam . ErrCodeDeleteConflictException :
s3err . WriteXMLResponse ( w , r , http . StatusConflict , errorResp )
default :
default :
s3err . WriteXMLResponse ( w , r , http . StatusInternalServerError , internalErrorResponse )
s3err . WriteXMLResponse ( w , r , http . StatusInternalServerError , internalErrorResponse )
}
}
@ -378,23 +384,196 @@ func (e *EmbeddedIamApi) GetPolicyDocument(policy *string) (policy_engine.Policy
}
}
// CreatePolicy validates and creates a new IAM managed policy.
// CreatePolicy validates and creates a new IAM managed policy.
// NOTE: Currently this only validates the policy document and returns policy metadata.
// The policy is not persisted to a managed policy store. To apply permissions to a user,
// use PutUserPolicy which stores the policy inline on the user's identity.
// TODO: Implement managed policy storage for full AWS IAM compatibility (ListPolicies, GetPolicy).
func ( e * EmbeddedIamApi ) CreatePolicy ( s3cfg * iam_pb . S3ApiConfiguration , values url . Values ) ( iamCreatePolicyResponse , * iamError ) {
func ( e * EmbeddedIamApi ) CreatePolicy ( ctx context . Context , values url . Values ) ( iamCreatePolicyResponse , * iamError ) {
var resp iamCreatePolicyResponse
var resp iamCreatePolicyResponse
policyName := values . Get ( "PolicyName" )
policyName := values . Get ( "PolicyName" )
policyDocumentString := values . Get ( "PolicyDocument" )
policyDocumentString := values . Get ( "PolicyDocument" )
_ , err := e . GetPolicyDocument ( & policyDocumentString )
if policyName == "" {
return resp , & iamError { Code : iam . ErrCodeInvalidInputException , Error : fmt . Errorf ( "PolicyName is required" ) }
}
if policyDocumentString == "" {
return resp , & iamError { Code : iam . ErrCodeInvalidInputException , Error : fmt . Errorf ( "PolicyDocument is required" ) }
}
if e . credentialManager == nil {
return resp , & iamError { Code : iam . ErrCodeServiceFailureException , Error : fmt . Errorf ( "credential manager not configured" ) }
}
policyDocument , err := e . GetPolicyDocument ( & policyDocumentString )
if err != nil {
if err != nil {
return resp , & iamError { Code : iam . ErrCodeMalformedPolicyDocumentException , Error : err }
return resp , & iamError { Code : iam . ErrCodeMalformedPolicyDocumentException , Error : err }
}
}
policyId := iamHash ( & policyDocumentString )
arn := fmt . Sprintf ( "arn:aws:iam:::policy/%s" , policyName )
existing , err := e . credentialManager . GetPolicy ( ctx , policyName )
if err != nil {
return resp , & iamError { Code : iam . ErrCodeServiceFailureException , Error : err }
}
if existing != nil {
return resp , & iamError { Code : iam . ErrCodeEntityAlreadyExistsException , Error : fmt . Errorf ( "policy %s already exists" , policyName ) }
}
if err := e . credentialManager . CreatePolicy ( ctx , policyName , policyDocument ) ; err != nil {
return resp , & iamError { Code : iam . ErrCodeServiceFailureException , Error : err }
}
policyId := iamHash ( & policyName )
arn := iamPolicyArn ( policyName )
resp . CreatePolicyResult . Policy . PolicyName = & policyName
resp . CreatePolicyResult . Policy . PolicyName = & policyName
resp . CreatePolicyResult . Policy . Arn = & arn
resp . CreatePolicyResult . Policy . Arn = & arn
resp . CreatePolicyResult . Policy . PolicyId = & policyId
resp . CreatePolicyResult . Policy . PolicyId = & policyId
path := "/"
defaultVersionId := "v1"
isAttachable := true
resp . CreatePolicyResult . Policy . Path = & path
resp . CreatePolicyResult . Policy . DefaultVersionId = & defaultVersionId
resp . CreatePolicyResult . Policy . IsAttachable = & isAttachable
return resp , nil
}
// DeletePolicy deletes a managed policy by ARN.
func ( e * EmbeddedIamApi ) DeletePolicy ( ctx context . Context , values url . Values ) ( iamDeletePolicyResponse , * iamError ) {
var resp iamDeletePolicyResponse
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 ) }
}
users , err := e . credentialManager . ListUsers ( ctx )
if err != nil {
return resp , & iamError { Code : iam . ErrCodeServiceFailureException , Error : err }
}
for _ , user := range users {
attachedPolicies , err := e . credentialManager . ListAttachedUserPolicies ( ctx , user )
if err != nil {
return resp , & iamError { Code : iam . ErrCodeServiceFailureException , Error : err }
}
for _ , attached := range attachedPolicies {
if attached == policyName {
return resp , & iamError {
Code : iam . ErrCodeDeleteConflictException ,
Error : fmt . Errorf ( "policy %s is attached to user %s" , policyName , user ) ,
}
}
}
}
if err := e . credentialManager . DeletePolicy ( ctx , policyName ) ; err != nil {
return resp , & iamError { Code : iam . ErrCodeServiceFailureException , Error : err }
}
return resp , nil
}
// ListPolicies lists managed policies.
func ( e * EmbeddedIamApi ) ListPolicies ( ctx context . Context , values url . Values ) ( iamListPoliciesResponse , * iamError ) {
var resp iamListPoliciesResponse
pathPrefix := values . Get ( "PathPrefix" )
if pathPrefix == "" {
pathPrefix = "/"
}
maxItems := 0
if maxItemsStr := values . Get ( "MaxItems" ) ; maxItemsStr != "" {
parsedMaxItems , err := strconv . Atoi ( maxItemsStr )
if err != nil || parsedMaxItems <= 0 {
return resp , & iamError { Code : iam . ErrCodeInvalidInputException , Error : fmt . Errorf ( "MaxItems must be a positive integer" ) }
}
maxItems = parsedMaxItems
}
marker := values . Get ( "Marker" )
if e . credentialManager == nil {
return resp , & iamError { Code : iam . ErrCodeServiceFailureException , Error : fmt . Errorf ( "credential manager not configured" ) }
}
if pathPrefix != "/" {
return resp , & iamError { Code : "NotImplemented" , Error : fmt . Errorf ( "PathPrefix filtering is not supported yet" ) }
}
policyNames , err := e . credentialManager . ListPolicyNames ( ctx )
if err != nil {
return resp , & iamError { Code : iam . ErrCodeServiceFailureException , Error : err }
}
sort . Strings ( policyNames )
if marker != "" {
i := sort . SearchStrings ( policyNames , marker )
if i < len ( policyNames ) && policyNames [ i ] == marker {
policyNames = policyNames [ i + 1 : ]
} else if i < len ( policyNames ) {
policyNames = policyNames [ i : ]
} else {
policyNames = nil
}
}
// Policy paths are not tracked in the current configuration, so PathPrefix filtering is not supported yet.
for _ , name := range policyNames {
policyNameCopy := name
policyArnCopy := iamPolicyArn ( name )
policyId := iamHash ( & policyNameCopy )
path := "/"
defaultVersionId := "v1"
isAttachable := true
resp . ListPoliciesResult . Policies = append ( resp . ListPoliciesResult . Policies , & iam . Policy {
PolicyName : & policyNameCopy ,
Arn : & policyArnCopy ,
PolicyId : & policyId ,
Path : & path ,
DefaultVersionId : & defaultVersionId ,
IsAttachable : & isAttachable ,
} )
}
if maxItems > 0 && len ( resp . ListPoliciesResult . Policies ) > maxItems {
resp . ListPoliciesResult . Policies = resp . ListPoliciesResult . Policies [ : maxItems ]
resp . ListPoliciesResult . IsTruncated = true
if name := resp . ListPoliciesResult . Policies [ maxItems - 1 ] . PolicyName ; name != nil {
resp . ListPoliciesResult . Marker = * name
}
return resp , nil
}
resp . ListPoliciesResult . IsTruncated = false
return resp , nil
}
// GetPolicy returns metadata for a managed policy.
func ( e * EmbeddedIamApi ) GetPolicy ( ctx context . Context , values url . Values ) ( iamGetPolicyResponse , * iamError ) {
var resp iamGetPolicyResponse
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 ) }
}
policyNameCopy := policyName
policyArnCopy := iamPolicyArn ( policyName )
policyId := iamHash ( & policyNameCopy )
path := "/"
defaultVersionId := "v1"
isAttachable := true
resp . GetPolicyResult . Policy = iam . Policy {
PolicyName : & policyNameCopy ,
Arn : & policyArnCopy ,
PolicyId : & policyId ,
Path : & path ,
DefaultVersionId : & defaultVersionId ,
IsAttachable : & isAttachable ,
}
return resp , nil
return resp , nil
}
}
@ -1274,7 +1453,7 @@ func (e *EmbeddedIamApi) ExecuteAction(ctx context.Context, values url.Values, s
action := values . Get ( "Action" )
action := values . Get ( "Action" )
if e . readOnly {
if e . readOnly {
switch action {
switch action {
case "ListUsers" , "ListAccessKeys" , "GetUser" , "GetUserPolicy" , "ListAttachedUserPolicies" , "ListServiceAccounts" , "GetServiceAccount" :
case "ListUsers" , "ListAccessKeys" , "GetUser" , "GetUserPolicy" , "ListAttachedUserPolicies" , "ListPolicies" , "GetPolicy" , "List ServiceAccounts" , "GetServiceAccount" :
// Allowed read-only actions
// Allowed read-only actions
default :
default :
return nil , & iamError { Code : s3err . GetAPIError ( s3err . ErrAccessDenied ) . Code , Error : fmt . Errorf ( "IAM write operations are disabled on this server" ) }
return nil , & iamError { Code : s3err . GetAPIError ( s3err . ErrAccessDenied ) . Code , Error : fmt . Errorf ( "IAM write operations are disabled on this server" ) }
@ -1331,15 +1510,18 @@ func (e *EmbeddedIamApi) ExecuteAction(ctx context.Context, values url.Values, s
case "DeleteAccessKey" :
case "DeleteAccessKey" :
response = e . DeleteAccessKey ( s3cfg , values )
response = e . DeleteAccessKey ( s3cfg , values )
case "CreatePolicy" :
case "CreatePolicy" :
response , iamErr = e . CreatePolicy ( s3cfg , values )
response , iamErr = e . CreatePolicy ( ctx , values )
if iamErr != nil {
if iamErr != nil {
glog . Errorf ( "CreatePolicy: %+v" , iamErr . Error )
glog . Errorf ( "CreatePolicy: %+v" , iamErr . Error )
return nil , iamErr
return nil , iamErr
}
}
changed = false
case "DeletePolicy" :
case "DeletePolicy" :
// Managed policies are not stored separately, so deletion is a no-op.
// Returns success for AWS compatibility.
response = struct { } { }
response , iamErr = e . DeletePolicy ( ctx , values )
if iamErr != nil {
glog . Errorf ( "DeletePolicy: %+v" , iamErr . Error )
return nil , iamErr
}
changed = false
changed = false
case "PutUserPolicy" :
case "PutUserPolicy" :
response , iamErr = e . PutUserPolicy ( s3cfg , values )
response , iamErr = e . PutUserPolicy ( s3cfg , values )
@ -1376,6 +1558,18 @@ func (e *EmbeddedIamApi) ExecuteAction(ctx context.Context, values url.Values, s
return nil , iamErr
return nil , iamErr
}
}
changed = false
changed = false
case "ListPolicies" :
response , iamErr = e . ListPolicies ( ctx , values )
if iamErr != nil {
return nil , iamErr
}
changed = false
case "GetPolicy" :
response , iamErr = e . GetPolicy ( ctx , values )
if iamErr != nil {
return nil , iamErr
}
changed = false
case "SetUserStatus" :
case "SetUserStatus" :
response , iamErr = e . SetUserStatus ( s3cfg , values )
response , iamErr = e . SetUserStatus ( s3cfg , values )
if iamErr != nil {
if iamErr != nil {
@ -1428,7 +1622,7 @@ func (e *EmbeddedIamApi) ExecuteAction(ctx context.Context, values url.Values, s
glog . Errorf ( "Failed to reload IAM configuration after mutation: %v" , err )
glog . Errorf ( "Failed to reload IAM configuration after mutation: %v" , err )
// Don't fail the request since the persistent save succeeded
// Don't fail the request since the persistent save succeeded
}
}
} else if iamErr == nil && ( action == "AttachUserPolicy" || action == "DetachUserPolicy" ) {
} else if action == "AttachUserPolicy" || action == "DetachUserPolicy" || action == "CreatePolicy" || action == "DeletePolicy" {
// Even if changed=false (persisted via credentialManager), we should still reload
// Even if changed=false (persisted via credentialManager), we should still reload
// if we are utilizing the local in-memory cache for speed
// if we are utilizing the local in-memory cache for speed
if err := e . ReloadConfiguration ( ) ; err != nil {
if err := e . ReloadConfiguration ( ) ; err != nil {