feat: add IAM Role CRUD

Adds `CreateRole`, `GetRole`, `ListRoles`, `DeleteRole`, and `UpdateAssumeRolePolicy` to the standalone IAM service, following the same controller/storage patterns established for users. Both the internal filesystem/S3-backed store and the Vault-backed store implement the new `Storer` methods, with role-specific indexing and lookup helpers mirroring the existing user ones.

Role creation requires a trust policy, passed as `AssumeRolePolicyDocument`. A trust policy is a distinct kind of IAM policy document that governs who (or what) is allowed to assume a role, rather than what actions the role itself is permitted to perform. Its grammar is effectively the inverse of an identity policy: `Principal` is required, `Action`/`NotAction` values must carry the `sts:` prefix, and `Resource`/`NotResource` are forbidden. This is implemented in `iamapi/policy/trust.go` as a new validation path alongside the existing identity-policy validation, and is reused by `UpdateAssumeRolePolicy` when replacing a role's trust policy.

Also fixes user name uniqueness enforcement to be case-insensitive, matching AWS IAM behavior, and applies the same case-insensitive handling to role names. The internal store now maintains lowercase name indexes for both users and roles, and the Vault store resolves the canonical stored key via a case-insensitive list-and-compare fallback since Vault's KV paths are case-sensitive.
This commit is contained in:
niksis02
2026-08-25 01:03:22 +04:00
parent 3328501fea
commit cbcc656f53
22 changed files with 3434 additions and 33 deletions
+29 -1
View File
@@ -113,7 +113,7 @@ func (e Error) XMLBody(requestID string) []byte {
type errorXML struct {
Type ErrorType
Code string
Message string
Message string `xml:",omitempty"`
}
var errorCodeResponse = map[ErrorCode]Error{
@@ -345,10 +345,22 @@ func NoSuchEntityAccessKey(accessKeyID string) Error {
return newSenderError("NoSuchEntity", fmt.Sprintf("The Access Key with id %s cannot be found", accessKeyID), http.StatusNotFound)
}
func EntityAlreadyExistsRole(roleName string) Error {
return newSenderError("EntityAlreadyExists", fmt.Sprintf("Role with name %s already exists.", roleName), http.StatusConflict)
}
func NoSuchEntityRole(roleName string) Error {
return newSenderError("NoSuchEntity", fmt.Sprintf("The role with name %s cannot be found.", roleName), http.StatusNotFound)
}
func AccessKeysLimitExceeded(maxKeys int) Error {
return newSenderError("LimitExceeded", fmt.Sprintf("Cannot exceed quota for AccessKeysPerUser: %d", maxKeys), http.StatusConflict)
}
func TrustPolicySizeLimitExceeded(maxBytes int) Error {
return newSenderError("LimitExceeded", fmt.Sprintf("Cannot exceed quota for ACLSizePerRole: %d", maxBytes), http.StatusConflict)
}
func ValidationError(message string) Error {
return newSenderError("ValidationError", message, http.StatusBadRequest)
}
@@ -417,6 +429,22 @@ func InvalidCharset(field string) Error {
return ValidationError(fmt.Sprintf("The specified value for %s is invalid. It must contain only printable ASCII characters.", field))
}
func InvalidDescriptionCharset(field string) Error {
return ValidationError(fmt.Sprintf("1 validation error detected: Value at '%s' failed to satisfy constraint: Member must satisfy regular expression pattern: [\\u0009\\u000A\\u000D\\u0020-\\u007E\\u00A1-\\u00FF]*", field))
}
func MaxSessionDurationTooLow() Error {
return ValidationError("1 validation error detected: Value at 'maxSessionDuration' failed to satisfy constraint: Member must have value greater than or equal to 3600")
}
func MaxSessionDurationTooHigh() Error {
return ValidationError("1 validation error detected: Value at 'maxSessionDuration' failed to satisfy constraint: Member must have value less than or equal to 43200")
}
func MalformedInput() Error {
return newSenderError("MalformedInput", "", http.StatusBadRequest)
}
func MalformedPolicyDocument(message string) Error {
return newSenderError("MalformedPolicyDocument", message, http.StatusBadRequest)
}