mirror of
https://github.com/versity/versitygw.git
synced 2026-09-22 16:04:15 +00:00
`CreateAccessKey`, `UpdateAccessKey`, `DeleteAccessKey` and `ListAccessKeys` required an explicit `UserName`. Real IAM treats it as optional and resolves it from the access key signing the request, so an IAM user managing its own keys had to name itself. They now infer it, matching AWS: only an entirely absent parameter is inferred, while a present-but-empty one stays a `ValidationError`, and the inferred scope is strictly the caller's own user — another user's key id returns `NoSuchEntity` rather than being touched. A caller with no IAM user of its own gets IAM's own `Must specify userName when calling with non-User credentials` `ValidationError`, shared with `GetUser` as `iamerr.MustSpecifyUserName`. That covers assumed-role sessions and also the gateway's root credential, which is configured rather than stored as an IAM user and so owns no access keys the API could manage — real IAM manages the root account's own keys here, which has no equivalent in this gateway. The policy middleware resolves the same four actions through `callerOrNamedUserResource`, so the resource-level check targets the caller's own user ARN when UserName is omitted instead of falling back to no resource at all, which would have denied every request authorized by an own-ARN-scoped grant.
668 lines
26 KiB
Go
668 lines
26 KiB
Go
// Copyright 2026 Versity Software
|
|
// This file is licensed under the Apache License, Version 2.0
|
|
// (the "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package iamerr
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/xml"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
Namespace = "https://iam.amazonaws.com/doc/2010-05-08/"
|
|
AWSFaultNamespace = "http://webservices.amazon.com/AWSFault/2005-15-09"
|
|
STSNamespace = "https://sts.amazonaws.com/doc/2011-06-15/"
|
|
)
|
|
|
|
type ErrorType string
|
|
|
|
const (
|
|
TypeSender ErrorType = "Sender"
|
|
TypeReceiver ErrorType = "Receiver"
|
|
)
|
|
|
|
type ErrorCode int
|
|
|
|
const (
|
|
ErrInternalFailure ErrorCode = iota
|
|
|
|
ErrSignatureDoesNotMatch
|
|
ErrMissingAuthenticationToken
|
|
ErrIncompleteSignature
|
|
ErrUnsupportedSignatureVersion
|
|
ErrMissingAuthorizationComponents
|
|
ErrIncorrectService
|
|
ErrInvalidCredentialDate
|
|
ErrInvalidTerminal
|
|
ErrUnsupportedQueryAlgorithm
|
|
ErrInvalidRegion
|
|
ErrMissingHostSignedHeader
|
|
ErrInvalidClientTokenID
|
|
ErrInvalidContentLength
|
|
ErrThrottling
|
|
ErrTooManyTags
|
|
ErrTooManyTagKeys
|
|
ErrInvalidTagKeys
|
|
ErrTagLimitExceeded
|
|
ErrInvalidPathPrefix
|
|
ErrDuplicateTagKeys
|
|
ErrDuplicateExactTagKeys
|
|
ErrInvalidAccessKeyIDChars
|
|
ErrDeleteConflict
|
|
ErrDeleteConflictPolicies
|
|
ErrMaxItemsTooLow
|
|
ErrMaxItemsTooHigh
|
|
)
|
|
|
|
type APIError interface {
|
|
error
|
|
StatusCode() int
|
|
XMLBody(requestID string) []byte
|
|
}
|
|
|
|
type Error struct {
|
|
Type ErrorType
|
|
Code string
|
|
Message string
|
|
HTTPStatusCode int
|
|
XMLNamespace string
|
|
}
|
|
|
|
func (e Error) Error() string {
|
|
return e.Code + ": " + e.Message
|
|
}
|
|
|
|
func (e Error) StatusCode() int {
|
|
return e.HTTPStatusCode
|
|
}
|
|
|
|
func (e Error) XMLBody(requestID string) []byte {
|
|
namespace := e.XMLNamespace
|
|
if namespace == "" {
|
|
namespace = Namespace
|
|
}
|
|
|
|
body, err := xml.Marshal(struct {
|
|
XMLName xml.Name
|
|
Error errorXML
|
|
RequestID string `xml:"RequestId"`
|
|
}{
|
|
XMLName: xml.Name{Space: namespace, Local: "ErrorResponse"},
|
|
Error: errorXML{
|
|
Type: e.Type,
|
|
Code: e.Code,
|
|
Message: e.Message,
|
|
},
|
|
RequestID: requestID,
|
|
})
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
return append([]byte(xml.Header), body...)
|
|
}
|
|
|
|
type errorXML struct {
|
|
Type ErrorType
|
|
Code string
|
|
Message string `xml:",omitempty"`
|
|
}
|
|
|
|
var errorCodeResponse = map[ErrorCode]Error{
|
|
ErrInternalFailure: {
|
|
Type: TypeReceiver,
|
|
Code: "InternalFailure",
|
|
Message: "The request processing has failed because of an unknown error, exception or failure.",
|
|
HTTPStatusCode: http.StatusInternalServerError,
|
|
},
|
|
ErrInvalidContentLength: {
|
|
Type: TypeSender,
|
|
Code: "InvalidRequest",
|
|
Message: "Content-Length must be a valid integer.",
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
},
|
|
ErrThrottling: {
|
|
Type: TypeSender,
|
|
Code: "Throttling",
|
|
Message: "Rate exceeded.",
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
},
|
|
ErrMissingAuthenticationToken: {
|
|
Type: TypeSender,
|
|
Code: "MissingAuthenticationToken",
|
|
Message: "Request is missing Authentication Token",
|
|
HTTPStatusCode: http.StatusForbidden,
|
|
},
|
|
ErrUnsupportedQueryAlgorithm: {
|
|
Type: TypeSender,
|
|
Code: "MissingAuthenticationToken",
|
|
Message: "Missing Authentication Token",
|
|
HTTPStatusCode: http.StatusForbidden,
|
|
},
|
|
ErrInvalidClientTokenID: {
|
|
Type: TypeSender,
|
|
Code: "InvalidClientTokenId",
|
|
Message: "The security token included in the request is invalid.",
|
|
HTTPStatusCode: http.StatusForbidden,
|
|
},
|
|
ErrIncompleteSignature: {
|
|
Type: TypeSender,
|
|
Code: "IncompleteSignature",
|
|
Message: "The request signature does not conform to AWS standards.",
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
},
|
|
ErrUnsupportedSignatureVersion: {
|
|
Type: TypeSender,
|
|
Code: "IncompleteSignature",
|
|
Message: "AWS Signature Version 2 is not supported.",
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
},
|
|
ErrMissingAuthorizationComponents: {
|
|
Type: TypeSender,
|
|
Code: "IncompleteSignature",
|
|
Message: "Authorization header requires Credential, SignedHeaders, and Signature.",
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
},
|
|
ErrSignatureDoesNotMatch: {
|
|
Type: TypeSender,
|
|
Code: "SignatureDoesNotMatch",
|
|
Message: "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.",
|
|
HTTPStatusCode: http.StatusForbidden,
|
|
},
|
|
ErrIncorrectService: {
|
|
Type: TypeSender,
|
|
Code: "SignatureDoesNotMatch",
|
|
Message: "Credential should be scoped to correct service: 'iam'.",
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
},
|
|
ErrInvalidCredentialDate: {
|
|
Type: TypeSender,
|
|
Code: "SignatureDoesNotMatch",
|
|
Message: "Date in Credential scope does not match YYYYMMDD from ISO-8601 version of date from HTTP.",
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
},
|
|
ErrInvalidTerminal: {
|
|
Type: TypeSender,
|
|
Code: "SignatureDoesNotMatch",
|
|
Message: "Credential should be scoped with a valid terminator: 'aws4_request'.",
|
|
HTTPStatusCode: http.StatusForbidden,
|
|
},
|
|
ErrInvalidRegion: {
|
|
Type: TypeSender,
|
|
Code: "SignatureDoesNotMatch",
|
|
Message: "Credential should be scoped to a valid region. ",
|
|
HTTPStatusCode: http.StatusForbidden,
|
|
},
|
|
ErrMissingHostSignedHeader: {
|
|
Type: TypeSender,
|
|
Code: "SignatureDoesNotMatch",
|
|
Message: "'Host' or ':authority' must be a 'SignedHeader' in the AWS Authorization.",
|
|
HTTPStatusCode: http.StatusForbidden,
|
|
},
|
|
ErrInvalidPathPrefix: {
|
|
Type: TypeSender,
|
|
Code: "ValidationError",
|
|
Message: "The specified value for pathPrefix is invalid. It must begin with the / character and contain only alphanumeric characters and/or / characters.",
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
},
|
|
ErrTooManyTags: {
|
|
Type: TypeSender,
|
|
Code: "ValidationError",
|
|
Message: "1 validation error detected: Value at 'tags' failed to satisfy constraint: Member must have length less than or equal to 50",
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
},
|
|
ErrTooManyTagKeys: {
|
|
Type: TypeSender,
|
|
Code: "ValidationError",
|
|
Message: "1 validation error detected: Value at 'tagKeys' failed to satisfy constraint: Member must have length less than or equal to 50",
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
},
|
|
ErrInvalidTagKeys: {
|
|
Type: TypeSender,
|
|
Code: "ValidationError",
|
|
Message: "1 validation error detected: Value at 'tagKeys' failed to satisfy constraint: Member must satisfy constraint: [Member must have length less than or equal to 128, Member must have length greater than or equal to 1, Member must satisfy regular expression pattern: [\\p{L}\\p{Z}\\p{N}_.:/=+\\-@]+, Member must not be null]",
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
},
|
|
ErrTagLimitExceeded: {
|
|
Type: TypeSender,
|
|
Code: "LimitExceeded",
|
|
Message: "The number of tags has reached the maximum limit.",
|
|
HTTPStatusCode: http.StatusConflict,
|
|
},
|
|
ErrMaxItemsTooLow: {
|
|
Type: TypeSender,
|
|
Code: "ValidationError",
|
|
Message: "1 validation error detected: Value at 'maxItems' failed to satisfy constraint: Member must have value greater than or equal to 1",
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
},
|
|
ErrMaxItemsTooHigh: {
|
|
Type: TypeSender,
|
|
Code: "ValidationError",
|
|
Message: "1 validation error detected: Value at 'maxItems' failed to satisfy constraint: Member must have value less than or equal to 1000",
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
},
|
|
ErrDuplicateTagKeys: {
|
|
Type: TypeSender,
|
|
Code: "InvalidInput",
|
|
Message: "Duplicate tag keys found. Please note that Tag keys are case insensitive.",
|
|
HTTPStatusCode: http.StatusBadRequest,
|
|
},
|
|
ErrDuplicateExactTagKeys: {
|
|
Type: TypeSender,
|
|
Code: "InvalidInput",
|
|
Message: "Duplicate tag keys found.",
|
|
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,
|
|
},
|
|
ErrDeleteConflictPolicies: {
|
|
Type: TypeSender,
|
|
Code: "DeleteConflict",
|
|
Message: "Cannot delete entity, must delete policies first.",
|
|
HTTPStatusCode: http.StatusConflict,
|
|
},
|
|
}
|
|
|
|
func GetAPIError(code ErrorCode) Error {
|
|
if err, ok := errorCodeResponse[code]; ok {
|
|
return err
|
|
}
|
|
|
|
return errorCodeResponse[ErrInternalFailure]
|
|
}
|
|
|
|
// WithNamespace returns err with its XML namespace overridden to namespace,
|
|
// for errors that must render under a different service's namespace than
|
|
// the one they were originally constructed with (STS actions sharing this
|
|
// gateway's IAM endpoint being the only current case). It never overrides
|
|
// an already-explicit namespace (e.g. InvalidAction's AWSFaultNamespace,
|
|
// used for a request whose Version doesn't even resolve to a known
|
|
// action.
|
|
func WithNamespace(err error, namespace string) error {
|
|
var apiErr Error
|
|
if errors.As(err, &apiErr) {
|
|
if apiErr.XMLNamespace == "" {
|
|
apiErr.XMLNamespace = namespace
|
|
}
|
|
return apiErr
|
|
}
|
|
return err
|
|
}
|
|
|
|
func InvalidAction(action, version string) Error {
|
|
err := newSenderError("InvalidAction", fmt.Sprintf("Could not find operation %s for version %s", action, version), http.StatusBadRequest)
|
|
err.XMLNamespace = AWSFaultNamespace
|
|
return err
|
|
}
|
|
|
|
func MissingParameter(parameter string) Error {
|
|
return newSenderError("MissingParameter", fmt.Sprintf("The request must contain the parameter %s.", parameter), http.StatusBadRequest)
|
|
}
|
|
|
|
func IncompleteSignatureMalformedComponent(component string) Error {
|
|
err := GetAPIError(ErrIncompleteSignature)
|
|
err.Message = fmt.Sprintf("Authorization component %q is malformed.", component)
|
|
return err
|
|
}
|
|
|
|
func IncompleteSignatureMalformedCredential(credential string) Error {
|
|
return newSenderError(
|
|
"IncompleteSignature",
|
|
fmt.Sprintf("Credential must have exactly 5 slash-delimited elements, e.g. keyid/date/region/service/term, got '%s'", credential),
|
|
http.StatusBadRequest,
|
|
)
|
|
}
|
|
|
|
func IncompleteSignatureMissingAuthorizationComponent(component, authorization string) Error {
|
|
err := GetAPIError(ErrIncompleteSignature)
|
|
err.Message = fmt.Sprintf("Authorization header requires '%s' parameter. (Hashed with SHA-256 and encoded with Base64) Authorization=%s",
|
|
component,
|
|
hashAuthorization(authorization))
|
|
return err
|
|
}
|
|
|
|
func IncompleteSignatureMissingQueryParameter(parameter string) Error {
|
|
err := GetAPIError(ErrIncompleteSignature)
|
|
err.Message = fmt.Sprintf("AWS query-string parameters must include '%s'. Re-examine the query-string parameters.", parameter)
|
|
return err
|
|
}
|
|
|
|
func IncompleteSignatureMissingDate(authorization string) Error {
|
|
return newSenderError(
|
|
"IncompleteSignature",
|
|
fmt.Sprintf("Authorization header requires existence of either a 'X-Amz-Date' or a 'Date' header. (Hashed with SHA-256 and encoded with Base64) Authorization=%s", hashAuthorization(authorization)),
|
|
http.StatusBadRequest,
|
|
)
|
|
}
|
|
|
|
func IncompleteSignatureInvalidXAmzDate(date string) Error {
|
|
return newSenderError(
|
|
"IncompleteSignature",
|
|
fmt.Sprintf("Date must be in ISO-8601 'basic format'. Got '%s'. See http://en.wikipedia.org/wiki/ISO_8601", date),
|
|
http.StatusBadRequest,
|
|
)
|
|
}
|
|
|
|
func IncompleteSignatureHeadersNotSigned(headers []string) Error {
|
|
err := GetAPIError(ErrIncompleteSignature)
|
|
err.Message = fmt.Sprintf("The request signature does not conform to AWS standards. Header(s) not signed: %s.", strings.Join(headers, ", "))
|
|
return err
|
|
}
|
|
|
|
func SignatureDoesNotMatchNotYetCurrent(requestTime, serverTime time.Time, allowedSkew time.Duration) Error {
|
|
err := GetAPIError(ErrSignatureDoesNotMatch)
|
|
err.Message = fmt.Sprintf("Signature not yet current: %s is still later than %s (%s + %d min.)",
|
|
requestTime.UTC().Format("20060102T150405Z"),
|
|
serverTime.UTC().Add(allowedSkew).Format("20060102T150405Z"),
|
|
serverTime.UTC().Format("20060102T150405Z"),
|
|
allowedSkew/time.Minute)
|
|
return err
|
|
}
|
|
|
|
func SignatureDoesNotMatchExpired(requestTime, serverTime time.Time, allowedSkew time.Duration) Error {
|
|
err := GetAPIError(ErrSignatureDoesNotMatch)
|
|
err.Message = fmt.Sprintf("Signature expired: %s is now earlier than %s (%s - %d min.)",
|
|
requestTime.UTC().Format("20060102T150405Z"),
|
|
serverTime.UTC().Add(-allowedSkew).Format("20060102T150405Z"),
|
|
serverTime.UTC().Format("20060102T150405Z"),
|
|
allowedSkew/time.Minute)
|
|
return err
|
|
}
|
|
|
|
func EntityAlreadyExistsUser(userName string) Error {
|
|
return newSenderError("EntityAlreadyExists", fmt.Sprintf("User with name %s already exists.", userName), http.StatusConflict)
|
|
}
|
|
|
|
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 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)
|
|
}
|
|
|
|
func InvalidInput(message string) Error {
|
|
return newSenderError("InvalidInput", message, http.StatusBadRequest)
|
|
}
|
|
|
|
func InvalidUserName(field string) Error {
|
|
return ValidationError(fmt.Sprintf("The specified value for %s is invalid. It must contain only alphanumeric characters and/or the following: +=,.@_-", field))
|
|
}
|
|
|
|
func MustSpecifyUserName() Error {
|
|
return ValidationError("Must specify userName when calling with non-User credentials")
|
|
}
|
|
|
|
func UserNameTooLong(field string, maxLength int) Error {
|
|
return ValidationError(fmt.Sprintf("1 validation error detected: Value at '%s' failed to satisfy constraint: Member must have length less than or equal to %d", field, maxLength))
|
|
}
|
|
|
|
func InvalidPath(field string) Error {
|
|
return ValidationError(fmt.Sprintf("The specified value for %s is invalid. It must begin and end with / and contain only alphanumeric characters and/or / characters.", field))
|
|
}
|
|
|
|
func PathTooLong(field string, maxLength int) Error {
|
|
return ValidationError(fmt.Sprintf("1 validation error detected: Value at '%s' failed to satisfy constraint: Member must have length less than or equal to %d", field, maxLength))
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
func InvalidTagKey(index int) Error {
|
|
return ValidationError(fmt.Sprintf("1 validation error detected: Value at 'tags.%d.member.key' failed to satisfy constraint: Member must satisfy regular expression pattern: [\\p{L}\\p{Z}\\p{N}_.:/=+\\-@]+", index))
|
|
}
|
|
|
|
// MissingTagKey reports a Tags member supplying a Value with no Key.
|
|
func MissingTagKey(index int) Error {
|
|
return MissingValue(fmt.Sprintf("tags.%d.member.key", index))
|
|
}
|
|
|
|
// MissingTagValue reports a Tags member supplying a Key with no Value. A
|
|
// tag value may be empty, but the parameter itself must be present.
|
|
func MissingTagValue(index int) Error {
|
|
return MissingValue(fmt.Sprintf("tags.%d.member.value", index))
|
|
}
|
|
|
|
// TagKeyTooShort reports an empty tag key
|
|
func TagKeyTooShort(index int) Error {
|
|
return ValueTooShort(fmt.Sprintf("tags.%d.member.key", index), 1)
|
|
}
|
|
|
|
func TagValueTooLong(index int) Error {
|
|
return ValidationError(fmt.Sprintf("1 validation error detected: Value at 'tags.%d.member.value' failed to satisfy constraint: Member must have length less than or equal to 256", index))
|
|
}
|
|
|
|
func InvalidTagValue(index int) Error {
|
|
return ValidationError(fmt.Sprintf("1 validation error detected: Value at 'tags.%d.member.value' failed to satisfy constraint: Member must satisfy regular expression pattern: [\\p{L}\\p{Z}\\p{N}_.:/=+\\-@]*", index))
|
|
}
|
|
|
|
func MissingValue(field string) Error {
|
|
return ValidationError(fmt.Sprintf("1 validation error detected: Value at '%s' failed to satisfy constraint: Member must not be null", field))
|
|
}
|
|
|
|
func ValueTooLong(field string, maxLength int) Error {
|
|
return ValidationError(fmt.Sprintf("1 validation error detected: Value at '%s' failed to satisfy constraint: Member must have length less than or equal to %d", field, maxLength))
|
|
}
|
|
|
|
func ValueTooShort(field string, minLength int) Error {
|
|
return ValidationError(fmt.Sprintf("1 validation error detected: Value at '%s' failed to satisfy constraint: Member must have length greater than or equal to %d", field, minLength))
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
func NoSuchEntityUserPolicy(userName, policyName string) Error {
|
|
return newSenderError("NoSuchEntity", fmt.Sprintf("The user policy with name %s cannot be found.", policyName), http.StatusNotFound)
|
|
}
|
|
|
|
func NoSuchEntityRolePolicy(roleName, policyName string) Error {
|
|
return newSenderError("NoSuchEntity", fmt.Sprintf("The role policy with name %s cannot be found.", policyName), http.StatusNotFound)
|
|
}
|
|
|
|
func InlinePolicyQuotaExceeded(entityKind, entityName string, maxBytes int) Error {
|
|
return newSenderError("LimitExceeded", fmt.Sprintf("Maximum policy size of %d bytes exceeded for %s %s", maxBytes, entityKind, entityName), http.StatusConflict)
|
|
}
|
|
|
|
func EntityAlreadyExistsOIDCProvider(url string) Error {
|
|
return newSenderError("EntityAlreadyExists", fmt.Sprintf("Provider with url %s already exists.", url), http.StatusConflict)
|
|
}
|
|
|
|
func NoSuchEntityOIDCProviderGet(arn string) Error {
|
|
return newSenderError("NoSuchEntity", fmt.Sprintf("OpenIDConnect Provider not found for arn %s", arn), http.StatusNotFound)
|
|
}
|
|
|
|
// NoSuchEntityOIDCProviderDelete is the wording DeleteOpenIDConnectProvider
|
|
// and the provider tagging actions use, distinct from the one
|
|
// NoSuchEntityOIDCProviderGet reports.
|
|
func NoSuchEntityOIDCProviderDelete(arn string) Error {
|
|
return newSenderError("NoSuchEntity", fmt.Sprintf("OpenId connect Provider %s cannot be found.", arn), http.StatusNotFound)
|
|
}
|
|
|
|
// AccessDeniedOIDCProvider is returned when a well-formed OIDC provider ARN
|
|
// references an account id other than callerAccountID.
|
|
func AccessDeniedOIDCProvider(callerAccountID, resourceArn string) Error {
|
|
return newSenderError("AccessDenied", fmt.Sprintf(
|
|
"User: arn:aws:iam::%s:root is not authorized to perform this action on resource: %s",
|
|
callerAccountID, resourceArn,
|
|
), http.StatusForbidden)
|
|
}
|
|
|
|
func ClientIdsPerOpenIdConnectProviderLimitExceeded(max int) Error {
|
|
return newSenderError("LimitExceeded", fmt.Sprintf("Cannot exceed quota for ClientIdsPerOpenIdConnectProvider: %d", max), http.StatusConflict)
|
|
}
|
|
|
|
func ThumbprintListTooLong(max int) Error {
|
|
return newSenderError("InvalidInput", fmt.Sprintf("Thumbprint list must contain fewer than %d entries.", max), http.StatusBadRequest)
|
|
}
|
|
|
|
func ThumbprintListEmpty() Error {
|
|
return newSenderError("InvalidInput", "Thumbprint list must contain at least one entry.", http.StatusBadRequest)
|
|
}
|
|
|
|
func OIDCProvidersPerAccountLimitExceeded(max int) Error {
|
|
return newSenderError("LimitExceeded", fmt.Sprintf("Cannot exceed quota for OpenIDConnectProvidersPerAccount: %d", max), http.StatusConflict)
|
|
}
|
|
|
|
func OpenIdIdpCommunicationError(url string) Error {
|
|
return newSenderError("OpenIdIdpCommunicationError", fmt.Sprintf("Could not connect to %s", url), http.StatusBadRequest)
|
|
}
|
|
|
|
func IncorrectServiceScope(expectedService string) Error {
|
|
return newSenderError("SignatureDoesNotMatch", fmt.Sprintf("Credential should be scoped to correct service: '%s'.", expectedService), http.StatusBadRequest)
|
|
}
|
|
|
|
func InvalidIdentityTokenMalformed() Error {
|
|
return newSenderError("InvalidIdentityToken", "The ID Token provided is not a valid JWT. (You may see this error if you sent an Access Token)", http.StatusBadRequest)
|
|
}
|
|
|
|
func InvalidIdentityTokenClaims() Error {
|
|
return newSenderError("InvalidIdentityToken", "The web identity token provided could not be validated. See the AssumeRoleWithWebIdentity documentation for requirements.", http.StatusBadRequest)
|
|
}
|
|
|
|
func InvalidIdentityTokenMultipleAudiences() Error {
|
|
return newSenderError("InvalidIdentityToken", "Token audience contains more than one audience while authorized party is not present", http.StatusBadRequest)
|
|
}
|
|
|
|
func InvalidIdentityTokenIDPCommunicationError() Error {
|
|
return newSenderError("InvalidIdentityToken", "Couldn't retrieve verification key from your identity provider, please reference AssumeRoleWithWebIdentity documentation for requirements", http.StatusBadRequest)
|
|
}
|
|
|
|
func ExpiredWebIdentityToken(now, exp int64) Error {
|
|
return newSenderError("ExpiredTokenException", fmt.Sprintf("Token expired: current date/time %d must be before the expiration date/time %d", now, exp), http.StatusBadRequest)
|
|
}
|
|
|
|
func UnsupportedParameter(parameter string) Error {
|
|
return newSenderError("InvalidInput", fmt.Sprintf("%s is not supported by this implementation.", parameter), http.StatusBadRequest)
|
|
}
|
|
|
|
func InvalidIdentityTokenMissingClaim(claim string) Error {
|
|
return newSenderError("InvalidIdentityToken", fmt.Sprintf("Missing a required claim: %s.", claim), http.StatusBadRequest)
|
|
}
|
|
|
|
func AccessDeniedAssumeRoleWithWebIdentity() Error {
|
|
return newSenderError("AccessDenied", "Not authorized to perform sts:AssumeRoleWithWebIdentity", http.StatusForbidden)
|
|
}
|
|
|
|
func InvalidRoleSessionName(value string) Error {
|
|
return ValidationError(fmt.Sprintf("1 validation error detected: Value '%s' at 'roleSessionName' failed to satisfy constraint: Member must satisfy regular expression pattern: [\\w+=,.@-]*", value))
|
|
}
|
|
|
|
func DurationSecondsTooLow(value string) Error {
|
|
return ValidationError(fmt.Sprintf("1 validation error detected: Value '%s' at 'durationSeconds' failed to satisfy constraint: Member must have value greater than or equal to 900", value))
|
|
}
|
|
|
|
func DurationSecondsTooHigh(value string) Error {
|
|
return ValidationError(fmt.Sprintf("1 validation error detected: Value '%s' at 'durationSeconds' failed to satisfy constraint: Member must have value less than or equal to 43200", value))
|
|
}
|
|
|
|
func DurationExceedsMaxSessionDuration() Error {
|
|
return ValidationError("The requested DurationSeconds exceeds the MaxSessionDuration set for this role.")
|
|
}
|
|
|
|
func AccessDeniedIAMAction(callerArn, action string) Error {
|
|
return newSenderError("AccessDenied", fmt.Sprintf(
|
|
"User: %s is not authorized to perform: %s because no identity-based policy allows the %s action",
|
|
callerArn, action, action,
|
|
), http.StatusForbidden)
|
|
}
|
|
|
|
func ConcurrentModification() Error {
|
|
return newSenderError("ConcurrentModificationException",
|
|
"The request was rejected because multiple requests to change this object were submitted simultaneously. Wait a few minutes and submit your request again.",
|
|
http.StatusConflict)
|
|
}
|
|
|
|
func newSenderError(code, message string, statusCode int) Error {
|
|
return Error{
|
|
Type: TypeSender,
|
|
Code: code,
|
|
Message: message,
|
|
HTTPStatusCode: statusCode,
|
|
}
|
|
}
|
|
|
|
func hashAuthorization(authorization string) string {
|
|
hash := sha256.Sum256([]byte(authorization))
|
|
return base64.StdEncoding.EncodeToString(hash[:])
|
|
}
|