Documentation
¶
Index ¶
- Constants
- Variables
- func BuildAssumedRoleArn(accountID, roleName, roleSessionName string) string
- func BuildOIDCProviderArn(accountID, url string) string
- func BuildRoleArn(accountID, path, roleName string) string
- func BuildUserArn(accountID, path, userName string) string
- func EncodePolicyDocument(s string) string
- func ExtractClaimContext(claims jwt.MapClaims) map[string][]string
- func FetchThumbprint(ctx context.Context, providerURL string) (string, error)
- func GenerateAccessKeyID() (string, error)
- func GenerateRoleID() (string, error)
- func GenerateSecretAccessKey() (string, error)
- func GenerateSessionToken() (string, error)
- func GenerateTempAccessKeyID() (string, error)
- func GenerateUserID() (string, error)
- func GetOIDCProviderArn(ctx fiber.Ctx, operation string) (string, error)
- func GetRoleName(ctx fiber.Ctx, operation string, maxLen int, missingErr error) (string, error)
- func GetUserName(ctx fiber.Ctx, operation string, maxLen int, missingErr error) (string, error)
- func GetUserNameOrCaller(ctx fiber.Ctx, operation string, maxLen int) (string, error)
- func HasRequestParamPrefix(ctx fiber.Ctx, prefix string) bool
- func IsTempAccessKeyID(accessKeyID string) bool
- func MatchQueryOrFormArgs(args ...string) fiber.Handler
- func NormalizeThumbprintList(thumbprints []string) []string
- func PackedPolicySize(sessionPolicy string) *int64
- func ParseDurationSeconds(ctx fiber.Ctx) (int32, error)
- func ParseMaxItems(ctx fiber.Ctx, operation string) (int32, error)
- func ParseMaxSessionDuration(ctx fiber.Ctx) (int32, error)
- func ParseOIDCProviderArn(arn string) (string, error)
- func ParseStringList(ctx fiber.Ctx, paramName string) []string
- func ParseTagKeys(ctx fiber.Ctx) ([]string, error)
- func ParseTags(ctx fiber.Ctx, keyCase TagKeyCase) ([]types.Tag, error)
- func ParseWebIdentityClaims(tokenString string) (jwt.MapClaims, error)
- func RequestParam(ctx fiber.Ctx, key string) (string, bool)
- func ResolveSessionByToken(ctx context.Context, store IdentityStore, accessKeyID, token string) (*types.Identity, string, error)
- func ResolveUserIdentity(ctx context.Context, store IdentityStore, accessKeyID string) (*types.Identity, string, error)
- func RoleNameFromAssumeArn(arn, accountID string) (roleName string, ok bool)
- func ThumbprintFromChain(chain []*x509.Certificate) (string, error)
- func ValidateAccessKeyID(accessKeyID string) error
- func ValidateAccessKeyStatus(status string) error
- func ValidateDescription(field, desc string) error
- func ValidateName(field, name string, maxLength int) error
- func ValidateOIDCProviderURL(rawURL string) (string, error)
- func ValidatePath(field, path string) error
- func ValidatePathPrefix(pathPrefix string) error
- func ValidateRoleArnLength(arn string) error
- func ValidateRoleSessionName(name string) error
- func ValidateThumbprintList(thumbprints []string, required bool) error
- func ValidateWebIdentityTokenLength(token string) error
- func VerifyWebIdentityExpiration(claims jwt.MapClaims, now time.Time) error
- func VerifyWebIdentityRequiredClaims(claims jwt.MapClaims, now time.Time) error
- func VerifyWebIdentitySignature(ctx context.Context, tokenString, issuerURL string, thumbprints []string) (jwt.MapClaims, error)
- func WebIdentityAudience(claims jwt.MapClaims) (audience string, original []string, err error)
- func WebIdentityIssuer(claims jwt.MapClaims) (string, bool)
- type IdentityStore
- type TagKeyCase
Constants ¶
const ( AccessKeyStatusActive = "Active" AccessKeyStatusInactive = "Inactive" )
const ( MinOIDCProviderArnLen = 20 MaxOIDCProviderArnLen = 2048 MaxOIDCProviderURLLen = 255 MaxOIDCClientIDLen = 255 MaxThumbprintsPerOIDCProvider = 5 OIDCThumbprintLen = 40 )
const ( DefaultAccountID = "000000000000" DefaultUserPath = "/" DefaultMaxItems = 100 MaxListItems = 1000 MaxUserNameLen = 64 MaxUserLookupLen = 128 MaxPathLen = 512 MaxTagMembersPerRequest = 50 MaxRoleNameLen = 64 MaxRoleDescriptionLen = 1000 DefaultMaxSessionDuration = 3600 MinMaxSessionDuration = 3600 MaxMaxSessionDuration = 43200 )
const ( MinRoleSessionNameLen = 2 MaxRoleSessionNameLen = 64 MinWebIdentityTokenLen = 4 MaxWebIdentityTokenLen = 20000 MinRoleArnLen = 20 MaxRoleArnLen = 2048 MinDurationSeconds = 900 MaxDurationSeconds = 43200 DefaultDurationSeconds = 3600 )
Variables ¶
var ( ErrIdentityNotFound = errors.New("identity not found") ErrInvalidSessionToken = errors.New("invalid session token") )
ErrIdentityNotFound and ErrInvalidSessionToken are returned by ResolveSessionByToken and ResolveUserIdentity, so a caller that needs to know *which* failure occurred — the standalone-IAM private endpoints, and iammiddleware's own sigv4 pipeline, which turn them into S3's own InvalidAccessKeyId and InvalidToken respectively — can distinguish them.
The public IAM control plane deliberately collapses both into a single InvalidClientTokenId: an unauthenticated caller must not learn whether an access key exists.
Functions ¶
func BuildAssumedRoleArn ¶
BuildAssumedRoleArn constructs the ARN a role's temporary session credentials are identified by. Unlike the role's own ARN (arn:aws:iam::...:role/...), an assumed session uses the sts service.
func BuildOIDCProviderArn ¶
BuildOIDCProviderArn constructs the ARN for an IAM OIDC identity provider. url must already have its "https://" scheme stripped.
func BuildRoleArn ¶
BuildRoleArn constructs the ARN for an IAM role.
func BuildUserArn ¶
BuildUserArn constructs the ARN for an IAM user.
func EncodePolicyDocument ¶
EncodePolicyDocument RFC 3986 percent-encodes a policy document string the way real IAM encodes the PolicyDocument element of GetUserPolicy (and will for GetRolePolicy) responses: every character outside the unreserved set is percent-encoded, with the space character encoded as %20 rather than the "+" that url.QueryEscape alone would produce.
func ExtractClaimContext ¶
ExtractClaimContext projects every other top-level scalar or scalar-array claim from a web identity token into a plain map, for trust-policy Condition keys beyond the well-known "aud"/"sub" (e.g. a custom "amr" or "groups" claim, or a Bool/Numeric/Date condition against a custom "admin"/"tier"/"level" claim).
func FetchThumbprint ¶
FetchThumbprint implements CreateOpenIDConnectProvider's auto-fetch behavior: it opens a TLS handshake (crypto/tls, not a full HTTP GET) to host:443, where host is derived from providerURL (a scheme-stripped OIDC provider Url), verifying the presented chain against the system trust store and the provider's own hostname like any normal TLS client, and returns the SHA-1 thumbprint of the last (top-most/intermediate CA) certificate in the peer's presented chain.
SSRF hardening (mandatory): the hostname is resolved once via net.DefaultResolver.LookupIP; if any resolved address is loopback/private/link-local/unspecified/multicast (this range covers 169.254.169.254 and other cloud metadata endpoints), the fetch is rejected before any connection attempt. The TLS dial then targets one of the pre-validated IPs directly (never re-resolving the hostname at dial time, closing the DNS-rebinding TOCTOU gap) while presenting the original hostname via tls.Config.ServerName for SNI/certificate purposes.
Verification is deliberately NOT skipped here: unlike a one-shot connection whose result is used and discarded, the certificate observed during this handshake is persisted as a long-lived trust anchor, compared against every future JWKS fetch for this provider. An unauthenticated handshake would let an active network/DNS attacker present any chain they control at enrollment time and have it pinned as trusted, then later present a matching leaf issued by that same chain — with attacker-chosen signing keys — to any subsequent (equally unauthenticated) JWKS fetch. A provider whose certificate doesn't chain to a system-trusted root (e.g. a private/self-hosted IdP on an internal CA) simply can't use auto-fetch: the caller gets an error and must supply ThumbprintList explicitly, having obtained the fingerprint through some independently verified channel — the same operational shape WithOIDCThumbprintAutoFetchDisabled already provides unconditionally, scoped here to just the providers that fail public verification.
func GenerateAccessKeyID ¶
GenerateAccessKeyID returns a new cryptographically random IAM access key id in the AKIA… format.
func GenerateRoleID ¶
GenerateRoleID returns a new cryptographically random IAM role ID in the AROA… format.
func GenerateSecretAccessKey ¶
GenerateSecretAccessKey returns a new cryptographically random 40 character secret access key.
func GenerateSessionToken ¶
GenerateSessionToken returns a new cryptographically random opaque session token for temporary credentials. Unlike AWS's own STS, whose session token self-encodes the session (so any STS host can validate it without shared state), this gateway looks the token up in its own session store, so an opaque random value is sufficient.
func GenerateTempAccessKeyID ¶
GenerateTempAccessKeyID returns a new cryptographically random temporary access key id in the ASIA… format, for credentials minted by AssumeRoleWithWebIdentity.
func GenerateUserID ¶
GenerateUserID returns a new cryptographically random IAM user ID in the AIDA… format.
func GetOIDCProviderArn ¶
GetOIDCProviderArn resolves the OpenIDConnectProviderArn request parameter, validates its shape via ParseOIDCProviderArn, and returns the ARN exactly as supplied by the caller (used verbatim in NoSuchEntity messages, which echo the full ARN, not just the url). A missing parameter is rejected with iamerr.MissingValue — every OIDC action taking this parameter reports it identically.
func GetRoleName ¶
GetRoleName resolves the RoleName request parameter and validates it against maxLen, returning missingErr if the parameter is absent or empty.
func GetUserName ¶
GetUserName resolves the UserName request parameter and validates it against maxLen, returning missingErr if the parameter is absent or empty. operation is included in the debug log on failure (e.g. "DeleteUser"). missingErr lets callers match the exact AWS error their operation is verified against (e.g. iamerr.MissingValue vs iamerr.MissingParameter).
func GetUserNameOrCaller ¶
GetUserNameOrCaller resolves the UserName request parameter like GetUserName, except that an omitted parameter resolves to the calling user's own name instead of being an error — matching real IAM, which infers the user from the access key signing the request when UserName is left out.
Only an entirely absent parameter is inferred. A UserName that is present but empty stays a ValidateName rejection, as on real IAM, so a client that sends the parameter with no value is told the value is invalid rather than silently acting on a different user than it named.
func HasRequestParamPrefix ¶
HasRequestParamPrefix reports whether any query or form parameter key (regardless of its value, including empty) starts with prefix. Unlike RequestParam, which probes one exact name, this scans every key actually present — needed to reject an AWS Query-protocol indexed-list parameter (e.g. "PolicyArns.member.N.arn") for every N a caller might supply, instead of only a fixed index like ".1.", which a caller could bypass entirely by supplying a different index, a gap, or several members.
func IsTempAccessKeyID ¶
IsTempAccessKeyID reports whether accessKeyID has the ASIA… prefix used for temporary credentials minted by AssumeRoleWithWebIdentity, as opposed to a long-term AKIA… access key. It delegates to sigv4auth so the S3 gateway, which cannot import this package, shares one definition.
func MatchQueryOrFormArgs ¶
MatchQueryOrFormArgs matches AWS Query-style requests that contain all provided parameters in either the URL query string or a form body.
func NormalizeThumbprintList ¶
NormalizeThumbprintList lowercases every entry: AWS stores/returns thumbprints lowercased regardless of submitted case.
func PackedPolicySize ¶
PackedPolicySize reports the percentage of policy.MaxSessionPolicyBytes sessionPolicy consumes, or nil if no session Policy parameter was supplied at all — matching how AWS omits PackedPolicySize entirely in that case rather than reporting 0%.
func ParseDurationSeconds ¶
ParseDurationSeconds parses AssumeRoleWithWebIdentity's optional DurationSeconds request parameter, returning DefaultDurationSeconds (always 1 hour, regardless of the role's own MaxSessionDuration) when absent.
func ParseMaxItems ¶
ParseMaxItems reads the MaxItems request parameter, defaulting to DefaultMaxItems when absent. operation is included in the debug log on parse failure (e.g. "ListUsers", "ListAccessKeys").
func ParseMaxSessionDuration ¶
ParseMaxSessionDuration reads the MaxSessionDuration request parameter, defaulting to DefaultMaxSessionDuration when absent, and validates it falls within [MinMaxSessionDuration, MaxMaxSessionDuration].
func ParseOIDCProviderArn ¶
ParseOIDCProviderArn validates arn's overall length and structural shape (arn:aws:iam::<account>:<resource-type>/<resource>) and, on success, returns the resource segment — the provider's Url with "https://" already stripped, exactly as stored. The account-id segment must match DefaultAccountID; any other value is rejected with AccessDenied, matching real AWS's behavior for a well-formed ARN referencing a foreign account.
Beyond the length and account-id checks, real AWS produces several more specific messages for structurally-malformed ARNs this function does not reproduce byte-for-byte — e.g. "Invalid service in ARN" for a non-iam service segment (a check this function does not perform at all), and a bare "Invalid ARN" (no echoed value) for a present-but-empty resource — this function falls back to a generic "Invalid ARN: %s" for those cases instead.
func ParseStringList ¶
ParseStringList reads flat indexed list members "<paramName>.member.1", "<paramName>.member.2", ... — the AWS Query-protocol wire form for a bare []string (distinct from ParseTags's Key/Value-pair member form, used by ClientIDList/ThumbprintList) — stopping at the first missing index. Returns nil if no entries are present.
func ParseTagKeys ¶
ParseTagKeys reads the request's TagKeys members (up to MaxTagMembersPerRequest), validates each, and returns the list. Unlike ParseTags, duplicate keys are accepted: removing the same key twice is a no-op, so AWS has no reason to reject it.
func ParseTags ¶
ParseTags reads IAM tag members from the request (up to MaxTagMembersPerRequest), validates each, and returns the list. Duplicate keys are detected under keyCase, the tagged resource's own key-comparison rule.
func ParseWebIdentityClaims ¶
ParseWebIdentityClaims parses tokenString as a JWT without verifying its signature, returning its claims. This is the first step of AssumeRoleWithWebIdentity validation: the token's iss claim must be read before it's known which OIDC provider (and therefore which signing keys) to verify against.
func RequestParam ¶
RequestParam looks up key first in URL query args, then in the POST body.
func ResolveSessionByToken ¶
func ResolveSessionByToken(ctx context.Context, store IdentityStore, accessKeyID, token string) (*types.Identity, string, error)
ResolveSessionByToken resolves a temporary (ASIA…) access key to the session that owns it, requiring token to match the session's stored SessionToken. The empty-token rejection and the constant-time comparison both live here rather than in any caller: this is the only function that may turn a session access key id into a secret, so no caller can be written that skips them.
It does not itself verify a SigV4 signature — request-pipeline callers do that next, so a stolen or guessed access key id plus token is never sufficient on its own.
func ResolveUserIdentity ¶
func ResolveUserIdentity(ctx context.Context, store IdentityStore, accessKeyID string) (*types.Identity, string, error)
ResolveUserIdentity resolves accessKeyID to its long-term (AKIA…) IAM user and secret, reporting the ErrIdentityNotFound/ErrInvalidSessionToken sentinels rather than an opaque API error — callers on the public control plane that want the opaque error do that translation themselves.
Temporary (ASIA…) session access keys are rejected here: resolving one safely requires validating its security token, which this function has no parameter for. A caller that can supply a token uses ResolveSessionByToken instead. Silently resolving a session's secret from its access key id alone, with no token check at all, would let anyone who merely knows the id impersonate the session.
func RoleNameFromAssumeArn ¶
RoleNameFromAssumeArn extracts the role name from a RoleArn of the shape arn:aws:iam::<account>:role/<path/><name>, for an assumed-role account matching accountID. Any other shape (wrong account, wrong resource type, not even ARN-shaped) reports ok=false: AssumeRoleWithWebIdentity treats all such cases identically (AccessDenied), never distinguishing "no such role" from "malformed ARN" the way other IAM actions do, so no error value is returned here.
func ThumbprintFromChain ¶
func ThumbprintFromChain(chain []*x509.Certificate) (string, error)
ThumbprintFromChain computes AWS's documented OIDC thumbprint: the SHA-1 hash of the DER bytes of the last (top-most/intermediate CA) certificate in chain, hex-encoded and lowercased. Split out from FetchThumbprint as a pure function specifically so it is unit-testable (e.g. against a chain obtained from httptest.NewTLSServer) without going through FetchThumbprint's SSRF guard, which must always reject loopback targets and therefore can never itself be exercised against a same-process test server.
func ValidateAccessKeyID ¶
ValidateAccessKeyID checks that accessKeyID fits within the allowed length range and character set.
func ValidateAccessKeyStatus ¶
ValidateAccessKeyStatus checks that status is either Active or Inactive.
func ValidateDescription ¶
ValidateDescription checks that the IAM role "Description" fits within MaxRoleDescriptionLen and uses the allowed charset — printable Latin-1 (excluding 0x7F-0xA0) plus tab/LF/CR
func ValidateName ¶
ValidateName checks that name (an IAM identity or policy name, e.g. userName or policyName) is non-empty, matches the allowed character set, and fits within maxLength.
func ValidateOIDCProviderURL ¶
ValidateOIDCProviderURL validates the Url parameter of CreateOpenIDConnectProvider and returns it with its "https://" scheme stripped (the canonical form used for ARN construction, storage keys, and GetOpenIDConnectProvider's own Url response field).
This implements a pragmatic subset of AWS's real validation: scheme must be exactly "https", no userinfo/port/query/fragment, host must be a syntactically plausible RFC-1123-ish hostname or IP literal, overall length <= MaxOIDCProviderURLLen. It does not attempt to reproduce every hostname-shape check AWS performs; it returns clear InvalidInput/ ValidationError messages instead of chasing every malformed edge case.
func ValidatePath ¶
ValidatePath checks that path is a valid IAM path (must start and end with '/') within MaxPathLen.
func ValidatePathPrefix ¶
ValidatePathPrefix checks that pathPrefix is a non-empty printable ASCII string starting with '/'.
func ValidateRoleArnLength ¶
ValidateRoleArnLength checks RoleArn against STS's length constraints.
func ValidateRoleSessionName ¶
ValidateRoleSessionName checks RoleSessionName against STS's length and charset constraints.
func ValidateThumbprintList ¶
ValidateThumbprintList validates a parsed ThumbprintList: at most MaxThumbprintsPerOIDCProvider entries, each exactly OIDCThumbprintLen characters (no hex-charset check — any 40-char string is accepted). If required is true, an empty list is rejected (UpdateOpenIDConnectProviderThumbprint, no auto-fetch fallback exists there); if false, an empty list passes through untouched (CreateOpenIDConnectProvider, whose caller handles empty via auto-fetch before calling this).
func ValidateWebIdentityTokenLength ¶
ValidateWebIdentityTokenLength checks WebIdentityToken against STS's length constraints (content/structure is validated separately by ParseWebIdentityClaims).
func VerifyWebIdentityExpiration ¶
VerifyWebIdentityExpiration checks claims' exp against now, allowing webIdentityExpLeeway of clock skew.
func VerifyWebIdentityRequiredClaims ¶
VerifyWebIdentityRequiredClaims checks claims for AWS's other mandatory web identity token claims beyond exp (already checked separately by VerifyWebIdentityExpiration): iat and sub must both be present, and nbf (if present) must not be in the future beyond webIdentityExpLeeway of clock skew. A token with exp but no iat, or with iat but no sub, is rejected with InvalidIdentityToken "Missing a required claim: <iat|sub>." — without this check, such a token would otherwise obtain credentials whenever the role's trust policy doesn't itself require sub via Condition.
func VerifyWebIdentitySignature ¶
func VerifyWebIdentitySignature(ctx context.Context, tokenString, issuerURL string, thumbprints []string) (jwt.MapClaims, error)
VerifyWebIdentitySignature fetches issuerURL's OIDC discovery document and JWKS (from cache when a fresh-enough entry exists), then verifies tokenString's signature against the matching key. On success it returns the token's verified claims (exp/nbf/iat are not re-checked here — callers that need those checks perform them separately with AWS-matching messages and leeway).
thumbprints is the OIDC provider's registered ThumbprintList, used as a pinned-certificate fallback when the JWKS endpoint's TLS certificate doesn't chain to a trusted root (self-signed/private-CA providers).
If the cached key set doesn't contain the token's kid, the cache is bypassed for one forced refresh before giving up — the provider may have rotated its signing key since the cache entry was fetched.
func WebIdentityAudience ¶
WebIdentityAudience resolves a web identity token's "effective audience" (the value AWS maps to the <provider>:aud trust-policy condition key) along with its original aud claim value(s) (mapped to <provider>:oaud whenever azp overrides them).
Whenever azp (authorized party) is present, it is always the effective audience — regardless of whether aud itself carries one value or many — and the original aud claim value(s) are additionally returned for the oaud mapping; this matters for Google hybrid clients, where aud names the backend project and azp names the actual OAuth client that requested the token. A multi-valued aud with no azp is rejected — per OpenID Connect Core, a multi-audience ID token must carry azp to disambiguate which audience the token was issued for, and AWS enforces this as a hard requirement rather than a recommendation.
func WebIdentityIssuer ¶
WebIdentityIssuer returns claims' iss value, scheme-stripped to match the stored form of a registered OIDC provider's Url.
Only an "https://" prefix is stripped — OIDC issuer identifiers are compared exactly, scheme included, and CreateOpenIDConnectProvider already requires every registered provider's Url to be https. An iss using any other scheme (or none at all) therefore can never legitimately equal a registered provider; returning it unstripped in that case (rather than also trimming a bare "http://") guarantees it stays distinguishable from a same-host https issuer instead of being silently treated as equivalent.
Types ¶
type IdentityStore ¶
type IdentityStore interface {
GetSession(ctx context.Context, accessKeyID string) (*types.Session, error)
GetRole(ctx context.Context, roleName string) (*types.Role, error)
GetUserByAccessKeyID(ctx context.Context, accessKeyID string) (*types.User, error)
GetUser(ctx context.Context, username string) (*types.User, error)
GetOIDCProvider(ctx context.Context, arn string) (*types.OIDCProvider, error)
RecordAccessKeyUsage(ctx context.Context, accessKeyID, service, region string, when time.Time) error
RecordRoleUsage(ctx context.Context, roleName, region string, when time.Time) error
}
IdentityStore resolves an access key id to the session or long-term user that owns it, and resolves named resources for policy evaluation. storage.Storer satisfies this directly.
type TagKeyCase ¶
type TagKeyCase int
TagKeyCase selects how a resource's tag keys are compared. IAM users and roles fold key case, so "env" and "ENV" name the same tag; OIDC providers compare keys exactly, so both can be carried at once.
const ( TagKeysFolded TagKeyCase = iota TagKeysExact )
func (TagKeyCase) Equal ¶
func (c TagKeyCase) Equal(a, b string) bool
Equal reports whether a and b name the same tag key under c.