Documentation
¶
Overview ¶
Package private implements the standalone IAM service's private endpoint set: derive a SigV4 signing key, evaluate IAM identity policies, and resolve an access key id to the principal that owns it. All three exist purely so the S3 gateway can authenticate and authorize its own callers without ever holding a plaintext secret or a policy document itself. This is a separate fiber app from the public control-plane iamapi.IAMApiServer, meant to be served on its own listener(s), never the public one.
The endpoints authenticate strictly as the configured root credential — only the S3 gateway itself, signing as its own IAM-client identity, ever legitimately calls them. Transport security is enforced by ServeMultiPort, not by request handling
Index ¶
- Constants
- func ParseProtocolVersion(value string) (int, error)
- type DeriveSigningKeyRequest
- type DeriveSigningKeyResponse
- type EvaluatePolicyRequest
- type EvaluatePolicyResponse
- type PrivateAPI
- type PrivateAPIOption
- type ResolveIdentityRequest
- type ResolveIdentityResponse
- type ResolvePrincipalsRequest
- type ResolvePrincipalsResponse
- type ResolvedIdentity
- type VersionResponse
Constants ¶
const ( CodeNoSuchIdentity = "NoSuchIdentity" CodeInvalidToken = "InvalidToken" CodeBadRequest = "BadRequest" CodeProtocolMismatch = "ProtocolMismatch" )
Error codes carried in the JSON error body's "code" field. The S3 gateway maps them to distinct S3 errors — CodeNoSuchIdentity to InvalidAccessKeyId, CodeInvalidToken to InvalidToken — so an end user gets an accurate diagnosis instead of one catch-all. Without them every 403 looks identical on the wire, and a gateway whose own IAM-client credential was rotated would tell the *user* their access key doesn't exist.
const ( // These are exported so auth.IAMServiceStandalone (the S3-side client) // shares one source of truth for the routes rather than duplicating the // literal path strings. DerivePath = "/private/derive-signing-key" EvaluatePath = "/private/evaluate-policy" ResolveIdentityPath = "/private/resolve-identity" ResolvePrincipalsPath = "/private/resolve-principals" VersionPath = "/private/version" // ProtocolHeader carries the private protocol version each peer speaks. // Both send it: the S3 gateway on every request, this service on every // response, including error responses. ProtocolHeader = "X-Vgw-Private-Protocol" // ProtocolVersion is the private protocol version this build speaks, and // MinClientProtocol the oldest S3 gateway it will serve. Together they // express compatibility in both directions, since a skew can be unsafe // from either side: // // - Bump ProtocolVersion when the gateway starts relying on something // an older service would silently ignore — a new request field, or a // new endpoint. An unrecognized field is dropped by json.Unmarshal, // so an older service evaluating without one (Condition being the // worked example) is fail-open. The gateway catches this itself by // refusing a service older than the version it speaks. // // - Bump both when this service starts returning something an older // gateway must understand to stay fail-closed — a new deny dimension, // or a decision matrix that narrows an Allow, as HasSessionPolicy/ // SessionDecisions would have been had they landed later. An older // gateway cannot detect this on its own: it does not know the field // exists. This service refuses it instead. // // - Bump neither for an addition an older gateway can safely ignore. // // Changing an existing field's meaning in place is not a bump; it is a // new route. The operational rule that falls out of all this: upgrade // the IAM service before the gateways. ProtocolVersion = 1 MinClientProtocol = 1 )
const ( KindUser = "user" KindSession = "session" )
Kind values for ResolvedIdentity.Kind — strings on the wire for the same reason the Decision values below are: self-documenting, and immune to iota drift between the independently-built gateway and IAM service.
const ( DecisionAllow = "allow" DecisionDeny = "deny" DecisionNoMatch = "no_match" )
Decision values for each entry of EvaluatePolicyResponse.Decisions. Deliberately strings, not policy.Decision's int: self-documenting on the wire, and immune to iota drift between the S3 gateway and standalone IAM service — two independently-built processes speaking this protocol.
Variables ¶
This section is empty.
Functions ¶
func ParseProtocolVersion ¶
ParseProtocolVersion reads a ProtocolHeader value. It is shared by both peers so they agree on what the header means, and is deliberately strict: a value it cannot read is a mismatch, never a default. Treating an absent or unreadable version as some assumed one is the fail-open direction for the check everything else is gated on.
Types ¶
type DeriveSigningKeyRequest ¶
type DeriveSigningKeyRequest struct {
AccessKeyID string `json:"accessKeyId"`
SessionToken string `json:"sessionToken,omitempty"`
Date string `json:"date"`
Region string `json:"region"`
Service string `json:"service"`
}
DeriveSigningKeyRequest is the derive-signing-key request body. Date, Region, and Service are the request's credential-scope components (yyyymmdd/region/service), matching sigv4auth.DeriveKey's parameters.
SessionToken is required when AccessKeyID is a temporary (ASIA…) key and must be absent otherwise. It is what makes resolving a session here safe: without it, anyone who learned a session's access key id could ask for that session's signing key.
type DeriveSigningKeyResponse ¶
type DeriveSigningKeyResponse struct {
DerivedKey []byte `json:"derivedKey"`
PrincipalArn string `json:"principalArn,omitempty"`
RoleArn string `json:"roleArn,omitempty"`
}
DeriveSigningKeyResponse carries the derived signing key (kSigning) — never the underlying secret — and the identity it belongs to, named the way a bucket policy's Principal element names it.
PrincipalArn is the caller's own ARN: an IAM user's, or an assumed-role session's arn:aws:sts::…:assumed-role/<role>/<session>. RoleArn is that session's role ARN and is set only for a session, because a bucket policy Principal naming a role matches every session of it
type EvaluatePolicyRequest ¶
type EvaluatePolicyRequest struct {
AccessKeyID string `json:"accessKeyId"`
SessionToken string `json:"sessionToken,omitempty"`
Actions []string `json:"actions"`
Resources []string `json:"resources"`
Condition map[string][]string `json:"condition,omitempty"`
Region string `json:"region,omitempty"`
Service string `json:"service,omitempty"`
}
EvaluatePolicyRequest is the evaluate-policy request body.
Region and Service describe the data-plane request being authorized, and exist only so the identity's last-used metadata (GetAccessKeyLastUsed, GetRole's RoleLastUsed) can record S3 use. They are recorded rather than evaluated — nothing about the authorization decision depends on them — and they are the gateway's own configured region and a fixed "s3", never anything a client supplied: the credential scope in a request's Authorization header is attacker-controlled until the signature is verified, and this endpoint is the first one reached after that.
type EvaluatePolicyResponse ¶
type EvaluatePolicyResponse struct {
Decisions [][]string `json:"decisions"`
SessionDecisions [][]string `json:"sessionDecisions,omitempty"`
HasSessionPolicy bool `json:"hasSessionPolicy,omitempty"`
PrincipalArn string `json:"principalArn,omitempty"`
}
EvaluatePolicyResponse carries the full tri-state result for the whole requested matrix — not just whether each cell is allowed — so the S3 gateway can distinguish an explicit Deny (which must override an otherwise-allowing bucket policy) from a plain NoMatch (which doesn't), and can build an AWS-shaped denial message.
Decisions[i][j] is the decision for the request's Resources[i] and Actions[j], in the order both were sent. PrincipalArn is the resolved identity's own ARN, best-effort: "" when it can't be resolved (e.g. a session whose role no longer exists), in which case the caller falls back to the access key. It is shared by the whole batch — one request always evaluates against a single identity. SessionDecisions is the same matrix evaluated against the caller's session policy alone, and HasSessionPolicy says whether one applied — the caller must ignore SessionDecisions when it is false. They are reported separately from Decisions rather than folded into them because a session policy filters *everything*, including permissions the S3 gateway's own bucket policy grants, which this service knows nothing about. See iammiddleware.AuthorizeSplit.
type PrivateAPI ¶
type PrivateAPI struct {
// contains filtered or unexported fields
}
PrivateAPI is the standalone IAM service's private endpoint set
func New ¶
func New(store storage.Storer, root iammiddleware.RootCredentials, opts ...PrivateAPIOption) (*PrivateAPI, error)
New constructs the private endpoint set. root is the identity these endpoints authenticate every request against.
func (*PrivateAPI) ServeMultiPort ¶
func (p *PrivateAPI) ServeMultiPort(addrs []string, tlsOpts netutil.TLSOptions) error
ServeMultiPort binds and serves the private endpoints on every address in addrs. Each address is checked with netutil.RequireSecureTransport before binding anything — mTLS (server cert + mandatory client-cert verification) or a unix socket, nothing else — so a misconfiguration fails startup instead of silently serving these endpoints in the clear. tlsOpts is only applied to non-unix-socket addresses, or to a unix socket address if a server certificate is configured for it too.
func (*PrivateAPI) Shutdown ¶
func (p *PrivateAPI) Shutdown() error
Shutdown gracefully stops the private endpoint listeners.
type PrivateAPIOption ¶
type PrivateAPIOption func(*PrivateAPI)
func WithPrivateQuiet ¶
func WithPrivateQuiet() PrivateAPIOption
WithPrivateQuiet suppresses per-request summary logging, mirroring iamapi.WithQuiet for the public API. Callers should gate both on the same flag so the two log streams turn on and off together.
func WithPrivateServerVersion ¶
func WithPrivateServerVersion(version string) PrivateAPIOption
WithPrivateServerVersion sets the build version the version endpoint reports. It is what lets an operator map a protocol number back to an image, so it is worth passing even though nothing decides compatibility on it.
func WithPrivateSocketPerm ¶
func WithPrivateSocketPerm(perm os.FileMode) PrivateAPIOption
WithPrivateSocketPerm sets the file-mode permission applied to any file-backed unix-socket listener address (no effect on TCP addresses or Linux abstract-namespace sockets).
type ResolveIdentityRequest ¶
type ResolveIdentityRequest struct {
AccessKeyIDs []string `json:"accessKeyIds"`
}
ResolveIdentityRequest asks whether each access key id exists and what principal it names. It carries no session token because the response carries no credential material
type ResolveIdentityResponse ¶
type ResolveIdentityResponse struct {
Identities []ResolvedIdentity `json:"identities"`
}
ResolveIdentityResponse answers ResolveIdentityRequest positionally: one entry per requested access key id, in the same order, with Found false for one that doesn't resolve. It deliberately carries no secret, no derived key and no policy — only that makes it safe to answer for a temporary (ASIA…) key with no session token, since knowing a session exists grants nothing.
type ResolvePrincipalsRequest ¶
type ResolvePrincipalsRequest struct {
Principals []string `json:"principals"`
}
ResolvePrincipalsRequest asks whether each string is a principal an S3 bucket policy may name — the write-time check behind PutBucketPolicy.
type ResolvePrincipalsResponse ¶
type ResolvePrincipalsResponse struct {
Invalid []string `json:"invalid"`
}
ResolvePrincipalsResponse answers with only the principals that do not resolve, so an empty list means the whole policy's principals are valid. It reports no detail about the ones that do: a principal's existence is all PutBucketPolicy validation needs, and answering more would make this endpoint an identity enumerator.
type ResolvedIdentity ¶
type ResolvedIdentity struct {
Found bool `json:"found"`
Kind string `json:"kind,omitempty"`
PrincipalArn string `json:"principalArn,omitempty"`
}
ResolvedIdentity is one ResolveIdentityResponse entry. Kind is KindUser or KindSession.
type VersionResponse ¶
type VersionResponse struct {
Protocol int `json:"protocol"`
MinClient int `json:"minClient"`
ServerVersion string `json:"serverVersion,omitempty"`
AccountID string `json:"accountId,omitempty"`
}
VersionResponse is the version endpoint's body.
MinClient is load-bearing, and this is the only place it appears: the version endpoint is exempt from the service's own client-version check so that it can answer a gateway the service will not serve, which leaves the gateway to draw that conclusion itself from this field. Protocol duplicates the ProtocolHeader every response carries, and ServerVersion is the build tag — what maps a protocol number back to an image during a rollout. AccountID is the single AWS account id every identity this service holds belongs to. The gateway learns it here rather than assuming a compile-time constant the two builds happen to share: it is what a bucket policy's account-level Principal forms (the account root ARN, and the bare account id) are matched against, and getting it from the service that mints the ARNs keeps one source of truth for it.