feat: add IAM user access key management

Add `CreateAccessKey`, `UpdateAccessKey`, `DeleteAccessKey`, `ListAccessKeys`, and `GetAccessKeyLastUsed` actions for managing user access keys and retrieving their latest usage details.

Generate AWS-style access key IDs and secrets, validate key identifiers and statuses, enforce per-user key quotas, and prevent deleting users that still own access keys. Persist access keys across internal and Vault storage backends with ownership indexing, pagination, and IAM-compatible errors and XML responses.
This commit is contained in:
niksis02
2026-08-25 01:03:22 +04:00
parent e012a6fd01
commit 9b352a3f00
19 changed files with 2129 additions and 27 deletions
+34 -7
View File
@@ -52,14 +52,14 @@ const (
ErrInvalidRegion
ErrMissingHostSignedHeader
ErrInvalidClientTokenID
ErrInvalidContentLength
ErrThrottling
ErrMissingUserNameValue
ErrTooManyTags
ErrInvalidPathPrefix
ErrDuplicateTagKeys
ErrInvalidAccessKeyIDChars
ErrDeleteConflict
)
type APIError interface {
@@ -123,7 +123,6 @@ var errorCodeResponse = map[ErrorCode]Error{
Message: "The request processing has failed because of an unknown error, exception or failure.",
HTTPStatusCode: http.StatusInternalServerError,
},
ErrInvalidContentLength: {
Type: TypeSender,
Code: "InvalidRequest",
@@ -136,7 +135,6 @@ var errorCodeResponse = map[ErrorCode]Error{
Message: "Rate exceeded.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrMissingAuthenticationToken: {
Type: TypeSender,
Code: "MissingAuthenticationToken",
@@ -155,7 +153,6 @@ var errorCodeResponse = map[ErrorCode]Error{
Message: "The security token included in the request is invalid.",
HTTPStatusCode: http.StatusForbidden,
},
ErrIncompleteSignature: {
Type: TypeSender,
Code: "IncompleteSignature",
@@ -174,7 +171,6 @@ var errorCodeResponse = map[ErrorCode]Error{
Message: "Authorization header requires Credential, SignedHeaders, and Signature.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrSignatureDoesNotMatch: {
Type: TypeSender,
Code: "SignatureDoesNotMatch",
@@ -211,7 +207,6 @@ var errorCodeResponse = map[ErrorCode]Error{
Message: "'Host' or ':authority' must be a 'SignedHeader' in the AWS Authorization.",
HTTPStatusCode: http.StatusForbidden,
},
ErrMissingUserNameValue: {
Type: TypeSender,
Code: "ValidationError",
@@ -236,6 +231,18 @@ var errorCodeResponse = map[ErrorCode]Error{
Message: "Duplicate tag keys found. Please note that Tag keys are case insensitive.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrInvalidAccessKeyIDChars: {
Type: TypeSender,
Code: "ValidationError",
Message: "The specified value for accessKeyId is invalid. It must contain only alphanumeric characters.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrDeleteConflict: {
Type: TypeSender,
Code: "DeleteConflict",
Message: "Cannot delete entity, must delete access keys first.",
HTTPStatusCode: http.StatusConflict,
},
}
func GetAPIError(code ErrorCode) Error {
@@ -334,6 +341,14 @@ func NoSuchEntityUser(userName string) Error {
return newSenderError("NoSuchEntity", fmt.Sprintf("The user with name %s cannot be found.", userName), http.StatusNotFound)
}
func NoSuchEntityAccessKey(accessKeyID string) Error {
return newSenderError("NoSuchEntity", fmt.Sprintf("The Access Key with id %s cannot be found", accessKeyID), http.StatusNotFound)
}
func AccessKeysLimitExceeded(maxKeys int) Error {
return newSenderError("LimitExceeded", fmt.Sprintf("Cannot exceed quota for AccessKeysPerUser: %d", maxKeys), http.StatusConflict)
}
func ValidationError(message string) Error {
return newSenderError("ValidationError", message, http.StatusBadRequest)
}
@@ -362,6 +377,18 @@ func InvalidMaxItems(value string) Error {
return ValidationError(fmt.Sprintf("1 validation error detected: Value '%s' at 'maxItems' failed to satisfy constraint: Member must have value between 1 and 1000", value))
}
func AccessKeyIDTooShort(minLength int) Error {
return ValidationError(fmt.Sprintf("1 validation error detected: Value at 'accessKeyId' failed to satisfy constraint: Member must have length greater than or equal to %d", minLength))
}
func AccessKeyIDTooLong(maxLength int) Error {
return ValidationError(fmt.Sprintf("1 validation error detected: Value at 'accessKeyId' failed to satisfy constraint: Member must have length less than or equal to %d", maxLength))
}
func InvalidAccessKeyStatus(value string) Error {
return ValidationError(fmt.Sprintf("1 validation error detected: Value '%s' at 'status' failed to satisfy constraint: Member must satisfy enum value set: [Active, Inactive]", value))
}
func TagKeyTooLong(index int) Error {
return ValidationError(fmt.Sprintf("1 validation error detected: Value at 'tags.%d.member.key' failed to satisfy constraint: Member must have length less than or equal to 128", index))
}