* request_id: add shared request middleware
* s3err: preserve request ids in responses and logs
* iam: reuse request ids in XML responses
* sts: reuse request ids in XML responses
* request_id: drop legacy header fallback
* request_id: use AWS-style request id format
* iam: fix AWS-compatible XML format for ErrorResponse and field ordering
- ErrorResponse uses bare <RequestId> at root level instead of
<ResponseMetadata> wrapper, matching the AWS IAM error response spec
- Move CommonResponse to last field in success response structs so
<ResponseMetadata> serializes after result elements
- Add randomness to request ID generation to avoid collisions
- Add tests for XML ordering and ErrorResponse format
* iam: remove duplicate error_response_test.go
Test is already covered by responses_test.go.
* address PR review comments
- Guard against typed nil pointers in SetResponseRequestID before
interface assertion (CodeRabbit)
- Use regexp instead of strings.Index in test helpers for extracting
request IDs (Gemini)
* request_id: prevent spoofing, fix nil-error branch, thread reqID to error writers
- Ensure() now always generates a server-side ID, ignoring client-sent
x-amz-request-id headers to prevent request ID spoofing. Uses a
private context key (contextKey{}) instead of the header string.
- writeIamErrorResponse in both iamapi and embedded IAM now accepts
reqID as a parameter instead of calling Ensure() internally, ensuring
a single request ID per request lifecycle.
- The nil-iamError branch in writeIamErrorResponse now writes a 500
Internal Server Error response instead of returning silently.
- Updated tests to set request IDs via context (not headers) and added
tests for spoofing prevention and context reuse.
* sts: add request-id consistency assertions to ActionInBody tests
* test: update admin test to expect server-generated request IDs
The test previously sent a client x-amz-request-id header and expected
it echoed back. Since Ensure() now ignores client headers to prevent
spoofing, update the test to verify the server returns a non-empty
server-generated request ID instead.
* iam: add generic WithRequestID helper alongside reflection-based fallback
Add WithRequestID[T] that uses generics to take the address of a value
type, satisfying the pointer receiver on SetRequestId without reflection.
The existing SetResponseRequestID is kept for the two call sites that
operate on interface{} (from large action switches where the concrete
type varies at runtime). Generics cannot replace reflection there since
Go cannot infer type parameters from interface{}.
* Remove reflection and generics from request ID setting
Call SetRequestId directly on concrete response types in each switch
branch before boxing into interface{}, eliminating the need for
WithRequestID (generics) and SetResponseRequestID (reflection).
* iam: return pointer responses in action dispatch
* Fix IAM error handling consistency and ensure request IDs on all responses
- UpdateUser/CreatePolicy error branches: use writeIamErrorResponse instead
of s3err.WriteErrorResponse to preserve IAM formatting and request ID
- ExecuteAction: accept reqID parameter and generate one if empty, ensuring
every response carries a RequestId regardless of caller
* Clean up inline policies on DeleteUser and UpdateUser rename
DeleteUser: remove InlinePolicies[userName] from policy storage before
removing the identity, so policies are not orphaned.
UpdateUser: move InlinePolicies[userName] to InlinePolicies[newUserName]
when renaming, so GetUserPolicy/DeleteUserPolicy work under the new name.
Both operations persist the updated policies and return an error if
the storage write fails, preventing partial state.
283 lines
10 KiB
Go
283 lines
10 KiB
Go
package iam
|
|
|
|
import (
|
|
"encoding/xml"
|
|
|
|
"github.com/aws/aws-sdk-go/service/iam"
|
|
)
|
|
|
|
// CommonResponse is embedded in IAM success response types to provide RequestId.
|
|
type CommonResponse struct {
|
|
ResponseMetadata struct {
|
|
RequestId string `xml:"RequestId"`
|
|
} `xml:"ResponseMetadata"`
|
|
}
|
|
|
|
// SetRequestId stores the request ID generated for the current HTTP request.
|
|
func (r *CommonResponse) SetRequestId(requestID string) {
|
|
r.ResponseMetadata.RequestId = requestID
|
|
}
|
|
|
|
// RequestIDSetter is implemented by IAM responses that can carry a RequestId.
|
|
type RequestIDSetter interface {
|
|
SetRequestId(string)
|
|
}
|
|
|
|
// ListUsersResponse is the response for ListUsers action.
|
|
type ListUsersResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ListUsersResponse"`
|
|
ListUsersResult struct {
|
|
Users []*iam.User `xml:"Users>member"`
|
|
IsTruncated bool `xml:"IsTruncated"`
|
|
} `xml:"ListUsersResult"`
|
|
CommonResponse
|
|
}
|
|
|
|
// ListAccessKeysResponse is the response for ListAccessKeys action.
|
|
type ListAccessKeysResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ListAccessKeysResponse"`
|
|
ListAccessKeysResult struct {
|
|
AccessKeyMetadata []*iam.AccessKeyMetadata `xml:"AccessKeyMetadata>member"`
|
|
IsTruncated bool `xml:"IsTruncated"`
|
|
} `xml:"ListAccessKeysResult"`
|
|
CommonResponse
|
|
}
|
|
|
|
// DeleteAccessKeyResponse is the response for DeleteAccessKey action.
|
|
type DeleteAccessKeyResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ DeleteAccessKeyResponse"`
|
|
CommonResponse
|
|
}
|
|
|
|
// CreatePolicyResponse is the response for CreatePolicy action.
|
|
type CreatePolicyResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreatePolicyResponse"`
|
|
CreatePolicyResult struct {
|
|
Policy iam.Policy `xml:"Policy"`
|
|
} `xml:"CreatePolicyResult"`
|
|
CommonResponse
|
|
}
|
|
|
|
// DeletePolicyResponse is the response for DeletePolicy action.
|
|
type DeletePolicyResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ DeletePolicyResponse"`
|
|
CommonResponse
|
|
}
|
|
|
|
// ListPoliciesResponse is the response for ListPolicies action.
|
|
type ListPoliciesResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ListPoliciesResponse"`
|
|
ListPoliciesResult struct {
|
|
Policies []*iam.Policy `xml:"Policies>member"`
|
|
IsTruncated bool `xml:"IsTruncated"`
|
|
Marker string `xml:"Marker,omitempty"`
|
|
} `xml:"ListPoliciesResult"`
|
|
CommonResponse
|
|
}
|
|
|
|
// GetPolicyResponse is the response for GetPolicy action.
|
|
type GetPolicyResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ GetPolicyResponse"`
|
|
GetPolicyResult struct {
|
|
Policy iam.Policy `xml:"Policy"`
|
|
} `xml:"GetPolicyResult"`
|
|
CommonResponse
|
|
}
|
|
|
|
// ListPolicyVersionsResponse is the response for ListPolicyVersions action.
|
|
type ListPolicyVersionsResponse struct {
|
|
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"`
|
|
CommonResponse
|
|
}
|
|
|
|
// GetPolicyVersionResponse is the response for GetPolicyVersion action.
|
|
type GetPolicyVersionResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ GetPolicyVersionResponse"`
|
|
GetPolicyVersionResult struct {
|
|
PolicyVersion iam.PolicyVersion `xml:"PolicyVersion"`
|
|
} `xml:"GetPolicyVersionResult"`
|
|
CommonResponse
|
|
}
|
|
|
|
// CreateUserResponse is the response for CreateUser action.
|
|
type CreateUserResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreateUserResponse"`
|
|
CreateUserResult struct {
|
|
User iam.User `xml:"User"`
|
|
} `xml:"CreateUserResult"`
|
|
CommonResponse
|
|
}
|
|
|
|
// DeleteUserResponse is the response for DeleteUser action.
|
|
type DeleteUserResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ DeleteUserResponse"`
|
|
CommonResponse
|
|
}
|
|
|
|
// GetUserResponse is the response for GetUser action.
|
|
type GetUserResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ GetUserResponse"`
|
|
GetUserResult struct {
|
|
User iam.User `xml:"User"`
|
|
} `xml:"GetUserResult"`
|
|
CommonResponse
|
|
}
|
|
|
|
// UpdateUserResponse is the response for UpdateUser action.
|
|
type UpdateUserResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ UpdateUserResponse"`
|
|
CommonResponse
|
|
}
|
|
|
|
// CreateAccessKeyResponse is the response for CreateAccessKey action.
|
|
type CreateAccessKeyResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreateAccessKeyResponse"`
|
|
CreateAccessKeyResult struct {
|
|
AccessKey iam.AccessKey `xml:"AccessKey"`
|
|
} `xml:"CreateAccessKeyResult"`
|
|
CommonResponse
|
|
}
|
|
|
|
// PutUserPolicyResponse is the response for PutUserPolicy action.
|
|
type PutUserPolicyResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ PutUserPolicyResponse"`
|
|
CommonResponse
|
|
}
|
|
|
|
// DeleteUserPolicyResponse is the response for DeleteUserPolicy action.
|
|
type DeleteUserPolicyResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ DeleteUserPolicyResponse"`
|
|
CommonResponse
|
|
}
|
|
|
|
// AttachUserPolicyResponse is the response for AttachUserPolicy action.
|
|
type AttachUserPolicyResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ AttachUserPolicyResponse"`
|
|
CommonResponse
|
|
}
|
|
|
|
// DetachUserPolicyResponse is the response for DetachUserPolicy action.
|
|
type DetachUserPolicyResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ DetachUserPolicyResponse"`
|
|
CommonResponse
|
|
}
|
|
|
|
// ListAttachedUserPoliciesResponse is the response for ListAttachedUserPolicies action.
|
|
type ListAttachedUserPoliciesResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ListAttachedUserPoliciesResponse"`
|
|
ListAttachedUserPoliciesResult struct {
|
|
AttachedPolicies []*iam.AttachedPolicy `xml:"AttachedPolicies>member"`
|
|
IsTruncated bool `xml:"IsTruncated"`
|
|
Marker string `xml:"Marker,omitempty"`
|
|
} `xml:"ListAttachedUserPoliciesResult"`
|
|
CommonResponse
|
|
}
|
|
|
|
// GetUserPolicyResponse is the response for GetUserPolicy action.
|
|
type GetUserPolicyResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ GetUserPolicyResponse"`
|
|
GetUserPolicyResult struct {
|
|
UserName string `xml:"UserName"`
|
|
PolicyName string `xml:"PolicyName"`
|
|
PolicyDocument string `xml:"PolicyDocument"`
|
|
} `xml:"GetUserPolicyResult"`
|
|
CommonResponse
|
|
}
|
|
|
|
// ErrorResponse is the IAM error response format.
|
|
// AWS IAM uses a bare <RequestId> at root level for errors, not <ResponseMetadata>.
|
|
type ErrorResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ErrorResponse"`
|
|
Error struct {
|
|
iam.ErrorDetails
|
|
Type string `xml:"Type"`
|
|
} `xml:"Error"`
|
|
RequestId string `xml:"RequestId"`
|
|
}
|
|
|
|
// SetRequestId stores the request ID generated for the current HTTP request.
|
|
func (r *ErrorResponse) SetRequestId(requestID string) {
|
|
r.RequestId = requestID
|
|
}
|
|
|
|
// Error represents an IAM API error with code and underlying error.
|
|
type Error struct {
|
|
Code string
|
|
Error error
|
|
}
|
|
|
|
// Policies stores IAM policies (used for managed policy storage).
|
|
type Policies struct {
|
|
Policies map[string]interface{} `json:"policies"`
|
|
}
|
|
|
|
// SetUserStatusResponse is the response for SetUserStatus action.
|
|
// This is a SeaweedFS extension to enable/disable users without deleting them.
|
|
type SetUserStatusResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ SetUserStatusResponse"`
|
|
CommonResponse
|
|
}
|
|
|
|
// UpdateAccessKeyResponse is the response for UpdateAccessKey action.
|
|
type UpdateAccessKeyResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ UpdateAccessKeyResponse"`
|
|
CommonResponse
|
|
}
|
|
|
|
// ServiceAccountInfo contains service account details for API responses.
|
|
type ServiceAccountInfo struct {
|
|
ServiceAccountId string `xml:"ServiceAccountId"`
|
|
ParentUser string `xml:"ParentUser"`
|
|
Description string `xml:"Description,omitempty"`
|
|
AccessKeyId string `xml:"AccessKeyId"`
|
|
SecretAccessKey *string `xml:"SecretAccessKey,omitempty"` // Only returned in Create response
|
|
Status string `xml:"Status"`
|
|
Expiration *string `xml:"Expiration,omitempty"` // ISO 8601 format, nil = no expiration
|
|
CreateDate string `xml:"CreateDate"`
|
|
}
|
|
|
|
// CreateServiceAccountResponse is the response for CreateServiceAccount action.
|
|
type CreateServiceAccountResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ CreateServiceAccountResponse"`
|
|
CreateServiceAccountResult struct {
|
|
ServiceAccount ServiceAccountInfo `xml:"ServiceAccount"`
|
|
} `xml:"CreateServiceAccountResult"`
|
|
CommonResponse
|
|
}
|
|
|
|
// DeleteServiceAccountResponse is the response for DeleteServiceAccount action.
|
|
type DeleteServiceAccountResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ DeleteServiceAccountResponse"`
|
|
CommonResponse
|
|
}
|
|
|
|
// ListServiceAccountsResponse is the response for ListServiceAccounts action.
|
|
type ListServiceAccountsResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ ListServiceAccountsResponse"`
|
|
ListServiceAccountsResult struct {
|
|
ServiceAccounts []*ServiceAccountInfo `xml:"ServiceAccounts>member"`
|
|
IsTruncated bool `xml:"IsTruncated"`
|
|
} `xml:"ListServiceAccountsResult"`
|
|
CommonResponse
|
|
}
|
|
|
|
// GetServiceAccountResponse is the response for GetServiceAccount action.
|
|
type GetServiceAccountResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ GetServiceAccountResponse"`
|
|
GetServiceAccountResult struct {
|
|
ServiceAccount ServiceAccountInfo `xml:"ServiceAccount"`
|
|
} `xml:"GetServiceAccountResult"`
|
|
CommonResponse
|
|
}
|
|
|
|
// UpdateServiceAccountResponse is the response for UpdateServiceAccount action.
|
|
type UpdateServiceAccountResponse struct {
|
|
XMLName xml.Name `xml:"https://iam.amazonaws.com/doc/2010-05-08/ UpdateServiceAccountResponse"`
|
|
CommonResponse
|
|
}
|