mirror of
https://github.com/versity/versitygw.git
synced 2026-08-17 20:56:21 +00:00
Implements the `AssumeRoleWithWebIdentity` and `GetCallerIdentity` STS actions, letting callers exchange an external OIDC token for temporary credentials scoped to an IAM role. Token handling covers JWT claim parsing, issuer/audience resolution (including `azp` override semantics), JWKS fetching and caching with `singleflight`-deduplicated refresh, and rate-limited forced refresh on unrecognized `kid` values. OIDC provider thumbprint fetching now performs a real TLS handshake verified against the system trust store and the provider hostname (previously `InsecureSkipVerify`), since the observed certificate is persisted as a long-lived trust anchor rather than used once and discarded; all discovery-document and JWKS fetches go through an SSRF-safe HTTP client with bounded redirects and response size.
Adds policy `Condition` block evaluation, supporting `String`, `Numeric`, `Date`, `Bool`, `BinaryEquals`, and `IpAddress` operators along with their `IfExists`/`Not` variants and `ForAllValues`/`ForAnyValues` set qualifiers, plus policy variable substitution (e.g. `${aws:username}`) in supported operators. Adds identity-based inline policy evaluation and a new IAM authorization middleware that authorizes each request against action, resource, and condition context together, applying the session-policy-intersects-role-policy semantics for assumed-role sessions.
Adds a new debug logger `--log-level` flag (`silent`/`debug`/`unsafe`), along with a tree-based XML masker that redacts secrets and tokens at the property level in logged request/response bodies instead of skipping the whole body. The old `--debug/VGW_DEBUG` flag is kept as a deprecated alias for `--log-level=debug`, printing a console warning that points users at `--log-level` for finer-grained control.
Fixes a Vault storage bug where CAS (check-and-set) writes always read the current document version as 0 because `kvVersion` asserted metadata as `float64` while the Vault client actually returns `json.Number`, causing every write past the first to be rejected as a concurrent modification. Also adds a constant-time `SecureCompare` for signature/token comparisons in sigv4 auth.
Adds an integration test suite (`iam_access_control.go`) covering IAM access control across user, role, and session identities.
607 lines
24 KiB
Go
607 lines
24 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
|
|
ErrInvalidPathPrefix
|
|
ErrDuplicateTagKeys
|
|
ErrInvalidAccessKeyIDChars
|
|
ErrDeleteConflict
|
|
ErrDeleteConflictPolicies
|
|
)
|
|
|
|
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,
|
|
},
|
|
ErrDuplicateTagKeys: {
|
|
Type: TypeSender,
|
|
Code: "InvalidInput",
|
|
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,
|
|
},
|
|
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 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 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))
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
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[:])
|
|
}
|