Commit Graph
19 Commits
Author SHA1 Message Date
niksis02 966a6a8650 feat: admin API path prefix, Helm WebUI prefixes and separate IAM storage
Closes #2322

The admin API could not be served under a path prefix. Admin requests are SigV4-signed over the full path, so a reverse proxy or Gateway API route cannot strip a prefix before forwarding. That made it impossible to serve the WebUI and the admin API under one hostname. The new `--admin-path-prefix` (`VGW_ADMIN_PATH_PREFIX`) option mounts the admin routes under a single-segment prefix such as `/admin`, on `--admin-port` or, when that is unset, on the S3 port. Admin clients include the prefix in their endpoint URL, which the admin CLI and the WebUI already support.

The prefix is limited to unreserved characters, because clients and the gateway encode other characters differently when signing. When the admin API shares the S3 port, the prefix must also differ from `--webui-s3-prefix`, otherwise the WebUI mount answers the admin requests. Auto-detected WebUI admin gateway URLs and the startup banner now include the prefix. The admin routes served on the S3 port now reuse `S3AdminRouter` instead of a duplicated route list. This also stops the standalone admin server from recording bucket creation under the `ActionAdminListBuckets` action.

The Helm chart now exposes `webui.pathPrefix`, `webui.s3Prefix` and `admin.pathPrefix`. The WebUI gateway lists are also passed when only `webui.s3Prefix` is set, since the WebUI hosted on the S3 port does not require `webui.enabled`.

Internal IAM data can now live on its own volume. `iam.dir` sets the IAM directory, and `iam.persistence` creates or references a dedicated PVC when `iam.enabled` is true and `iam.type` is `internal`. Otherwise IAM data stays in the `iam` subdirectory of the backend data volume, so existing releases are unaffected.

The chart now rejects overlapping storage directories. `iam.dir` must be an absolute path outside `/mnt/data`, `gateway.backend.sidecarDir` and `gateway.backend.versioningDir`, and those three directories must not overlap each other. Deleting a bucket removes `<dir>/<bucket>` from the sidecar and versioning directories, so with nested directories, deleting a suitably named bucket could wipe IAM accounts, object versions or the entire backend data directory.
2026-09-16 23:09:14 +04:00
Jihyeon Gim 60bec2c00d rdma: gate session admission on audit publication backlog
The native side releases its session quota when it fires the
teardown notification, before the audit record lands in a sink,
so session turnover can queue more unpublished records than the
live-session limit allows. Hold an admission credit per session
from registration until its record is published, and refuse new
sessions while the backlog of unpublished session records reaches
the native session quota: the refusal rolls the prepare back,
still publishes the request-level audit record, and answers
SlowDown so the client retries. A stalled sink now turns into
latency instead of unbounded memory.

Count dropped request records under the publication mutex so the
shutdown drop-count report cannot miss an increment racing it.
2026-09-09 13:16:52 +09:00
Jihyeon Gim e9a2a2f98b rdma: never run operational sinks on callback threads
Rework the publication handoff so a native callback can never
execute a sink: the channel buffer absorbs the common case, and a
full buffer appends to an overflow list that the worker drains
after the channel instead of falling back to inline publication.
Queued records accumulate across successive sessions and failed
authentications consume no session at all, so capacity accounting
cannot bound the backlog; only removing the fallback closes the
stall. Drop the now-unused session-limit accessor.

Move signature verification back outside the admission barrier:
IAM lookups carry no cancellation, so holding the barrier across
verification let one stalled lookup defer RC shutdown
indefinitely. The handlers enforce admission themselves, and a
failure publication checks the drain state before dispatching, so
it cannot land after the sinks close.

Synchronize metrics producers with Close: the manager now marks
itself closed before closing the datapoint channel, and a
producer that still races the closure recovers instead of
panicking on a send over a closed channel.

Exercise real stale tokens in the reservation generation test:
the original token attempts both release and publish after a
newer claim took over.
2026-09-09 13:16:52 +09:00
Jihyeon Gim 55c82f5e4e rdma: size publications to session capacity and preserve committed PUT facts
Size the publication queue from the configured session limit instead
of a fixed depth with an overflow semaphore: each session publishes
exactly one terminal record, so the queue can never fill and native
callbacks hand off without waiting. Remove the semaphore fallback.

Admit verification failures under the shutdown barrier so an
authentication publication cannot land after the sinks close. Give
the metrics manager a lifetime independent of the gateway context so
shutdown drain updates are counted, and pass the captured bucket
explicitly so RC datapoints appear in bucket-filtered metrics.

Track reservations by claim generation: release and publication
validate the generation, so a stale claim cannot consume a newer
owner's record. Install the reservation cleanup defer immediately
after acquisition so a panic during authorization cannot orphan it.

Preserve committed PUT facts independent of the native finalizer:
when the backend object exists, record the commit, keep the
committed byte count on the error publication, and still emit the
object-created event.
2026-09-09 13:16:52 +09:00
Jihyeon Gim 96be4d1407 rdma: bound publication backpressure and close the reservation window
Overflow publications ran one goroutine per job: a stalled sink
with a full queue grew them without bound (a probe reached a
thousand blocked calls). Overflow now runs inline under a bounded
semaphore, so at most a fixed number of callers wait and every
record still publishes.

The publication worker outlived the operational sinks: gateway
shutdown closed the RC service and then the sinks while
publications were still queued, losing terminal records (reached
"file already closed" with the real file logger). The shutdown
wrapper now drains the publication queue before closing the RC
service, and the route handler exposes the drain; late terminals
after the drain publish inline instead of queueing behind a
stopped worker.

The publication reservation happened after the transfer claim
returned: in the window between the claim and the reservation, a
concurrent READY's re-authorization denial consumed the unreserved
record, so the claimant's successful transfer lost its publication
to the denial. The READY handler now reserves before the claim and
before re-authorization; a rolled-back claim (wire failure, peer
busy) releases the reservation instead of publishing, so the
session keeps its record for the next claimant or the reaper.

The tracker test now joins the worker through the shutdown drain
and asserts per-session outcomes and byte counts instead of an
aggregate count that a pending fifth record could satisfy.
2026-09-09 13:16:52 +09:00
Jihyeon Gim 39aae5435c rdma: close publication gaps for RC pre-session failures
Review of the operational publication found four gaps where the
records disagreed with the S3 surface or were missing entirely.

Authorization-failure records lost the requester because the
pre-session publisher did not carry the authenticated account; the
account now flows into the record, so the audit trail names who was
denied.

Signature failures and malformed PREPARE headers ended the request
before any publication point. The auth adapter now publishes
authentication failures through the route handler, and header
validation failures publish with whatever object identity the
headers still carried, matching the S3 surface where every denied
request still logs.

RC requests carried no region: the custom routes run before the
middleware that stores the region local, so event records lacked
awsRegion and audit host headers read s3..amazonaws.com. The auth
adapter sets the region for every verified RC request.

Operational records reported generic 500 statuses for protocol
errors that the wire answers with a specific status (a resource
limit rejection logged 500 while the client saw 429). Status
mapping now reuses the route error mapping, so the recorded status
always equals the wire status.
2026-09-09 13:16:52 +09:00
Jihyeon Gim 49b1c6f6bc rdma: publish RC transfer outcomes into the gateway operational services
Two-phase RC transfers were invisible to the access log, metrics,
and bucket notifications: every outcome record on the S3 surface is
driven by the fiber request context, and the RC wire requests carry
the transfer session rather than the object.

Publish one record per session by tracking the operational context
from PREPARE through completion. A tracker table registers each
successfully prepared session with its account, region, object, and
start time; whichever path confirms the final outcome first (READY
completion, READY failure, or the reaper teardown callback) claims
the entry and publishes exactly once. Sessions that end before
PREPARE succeeds publish a request record directly.

The record is emitted through a synthetic fiber context carrying
the object path and the captured locals, so the existing logger,
metrics manager, and event sender produce the same schema as the
S3 surface without any interface change. Bucket notifications fire
for completed PUTs.

The gateway creates the operational services inside RunVersityGW,
after the RC routes exist. Add an OnServicesReady callback to the
embedded gateway config and wire it in vgwrdma to hand the services
to the RC routes and install the teardown callback.
2026-09-09 13:16:52 +09:00
Jihyeon Gim 6eb574dc1a rdma: expose live RC sessions on the admin server
Add a SessionsSnapshot view over the new C ABI entry point and
serve it from the admin server as GET /rc-sessions. The admin server
gains a WithAdminRoute option so an embedding binary can register
extra admin routes that run with the same signature verification and
admin checks as the built-in endpoints; vgwrdma registers the
snapshot there when the RC feature is enabled. The route replies
with the usual XML error surface so unsigned or non-admin requests
get a 403 rather than a generic 500. Stub builds return a not
supported error, keeping the build matrix unchanged.
2026-09-07 16:14:53 +09:00
Jihyeon Gim d782e622fc rdma: add a log callback ABI to the RC data plane
Wire C-side diagnostics (session reap, READY data phase outcome,
init failures) through a sink callback so the gateway can surface
them next to its own logs instead of losing them in stderr noise.

The sink is a plain C function pointer installed once after init
and valid until destroy: the Go side registers a fixed cgo
trampoline (closures cannot cross the boundary), copies the
message immediately per the lifetime contract, and never runs
under the session map lock. Error-level lines keep the existing
stderr output; --debug enables the level-2 diagnostic stream.
2026-09-05 19:46:00 +09:00
Jihyeon Gim 980078d822 rdma: expose the RC data plane resource limits as gateway flags
The hipobj-rc-v2 data plane started with its session, queue pair,
staging and timeout limits hardcoded at the rcserver.Init call
site, so operators could not size the RC plane for their hardware
the way they can for the cuObject backend. Add one flag per limit
plus the READY admission slot count, all defaulting to the values
the gateway passes today, and validate them through a new
rdmamode.V2ValidationError consulted only when the RC data plane
is enabled, mirroring the stale-value handling of the v1 settings.
Counts are parsed as uint64 and range-checked against the uint32
narrowing at the DeviceOpts boundary, and the timeouts carry an
upper bound that keeps the nowMs + timeout deadline arithmetic in
the C core from wrapping.
2026-09-05 17:53:05 +09:00
Jihyeon Gim e7edb71166 rdma: install the gateway shutdown wrapper at function entry
Move the shutdown-once wrapper installation from after the gateway
option validation to the top of runGateway, so every early error
return closes the backend chain exactly once instead of leaking
it.
2026-09-05 11:47:52 +09:00
Jihyeon Gim 726d65dbc9 rdma: gate the RC data plane behind a dedicated flag
Add --rdma-rc-enable (VGW_RDMA_RC_ENABLE, default false) so the
RC control routes and data plane start without implying the
cuObject v1 backend. The global CLI hook resolves the mode
first: gateway commands require either --rdma-ip or
--rdma-rc-enable, and neither path implies the other, so a
v2-only deployment boots without a v1 address.

The v1 port, retry, pool, and DCI validations also ran for every
mode, so stale v1 environment values blocked v2-only startup
with unrelated errors. Those validations moved behind the v1
check as a cgo-free helper in internal/rdmamode, exercised
alongside the mode matrix, and the CQ-depth limit keeps its
32-bit boundary check there.

The RC data plane builds its IAM service, starts the session
server, and mounts the three control routes behind SigV4.

Startup and shutdown own the backend chain through idempotent
guards: the gateway wraps the input backend in a once guard and
defers a rollback closure that follows the chain as it grows;
the completed v1 chain gets its own once owner, and the RC
service is closed first through a backend wrapper installed
right after a successful session-server init. Startup failures
close exactly what was built, the RunVersityGW lifecycle
consumes the same guards instead of closing again, and the RC
sessions drain before the backend chain shuts down.
2026-09-04 19:44:45 +09:00
Jihyeon Gim 50fc9fe941 rdma: authenticate RC routes through the terminal error path
The RC auth adapter returned signature-verification errors to
Fiber, so the production S3 error handler collapsed them into a
generic 500 response; authentication failures lost their real
status and code the same way route failures did before the
terminal serializer. The adapter now sends verification errors
through the shared serializer as well.
2026-09-04 19:44:45 +09:00
niksis02 c84c5f645a feat: accept principal ARNs in bucket policies under standalone IAM
Bucket policy `Principal` named callers by access key id. Under the standalone IAM service it now names them by AWS-style ARN, as real S3 does: a user ARN, a role ARN (covering every session of that role), an assumed-role ARN (covering one session), the account root ARN or bare account id, or `*`. Every other IAM backend has no ARNs to name anything by and keeps access-key principals unchanged, gated on a new `auth.PrincipalResolver` capability interface that only the standalone client implements.

`auth.Account` carries `Arn` and `RoleArn`, filled at authentication time, so a session can be matched against both its own ARN and its role's. Principals are validated at PutBucketPolicy time through a new `/private/resolve-principals` endpoint, which rejects anything that does not name a live identity with `MalformedPolicy: Invalid principal in policy`.

An `Allow` naming the account root ARN or bare account id delegates to the account's own IAM rather than granting on its own, while a `Deny` naming it denies every principal in the account outright. Denial messages now name the caller by ARN wherever one exists.

Also fixes `aws:PrincipalArn` for assumed-role sessions, which reported the session ARN where AWS reports the role's, and stops an unreachable IAM service being reported as a malformed policy.
2026-09-03 23:51:55 +04:00
Ben McClelland 5f9041ff5f feat: support admin/access logs on standard streams
The access-log and admin-access-log options now accept stdout, stderr, or - for
stdout in addition to file paths. Standard stream destinations are kept open
during shutdown and SIGHUP handling, while file destinations continue to support
normal reopen behavior for log rotation. CLI help, embedded config comments, and
the example configuration describe the new destination values.

Fixes #2245
2026-09-02 14:21:28 -07:00
535cc9d521 feat: add the hipobj-rc-v2 control routes to the vgwrdma gateway
* rdma: add the hipobj-rc-v2 control routes to the vgwrdma gateway

Mount the three control routes (prepare, ready, cancel) on the
S3 port behind the standard SigV4 middleware. The routes own
authentication-adjacent policy the C server cannot see: the
middleware wrapper yields to the handler on success, READY and
CANCEL re-read the account through the IAM cache bypass so
mid-flow deletions and credential rotations take effect
immediately, and every object access re-authorizes against the
decoded bucket and key.

The READY handler implements the session ownership contract:
the completion-reference finalizer installs only after the
transfer claim succeeds, the PUT path hands the reference to
the put view exactly at the borrow point, and the FINAL reply
carries the stored object's metadata. Backend I/O runs under a
context merged with the RC service context so shutdown unblocks
in-flight handlers, with a bounded pool for the fresh IAM
lookups.

vgwrdma starts the session server alongside the gateway when an
RDMA interface is configured, tears it down on exit, and shuts
the IAM service down on any startup failure. embedgw learns the
readonly flag for the object access checks the routes share.

Signed-off-by: Jihyeon Gim <potatogim@potatogim.net>

* rdma: add the missing stub handlers for non-Linux builds

The non-Linux rcroutes stub exposed only Register while the vgwrdma
gateway registers the prepare/ready/cancel handlers directly, so
cross-compiling cmd/vgwrdma failed with undefined methods. Add the
three stub handlers answering 501 Not Implemented and let Register
reuse them, matching the Linux Handler API surface.

* auth: drop the duplicated GetUserAccountFresh definition

The rebase onto main (which already carries GetUserAccountFresh from
the iam-cache-fresh change) kept both copies of the method, breaking
the build with a redeclaration error. Remove the second copy so the
method is defined once.

* rdma: address the review findings on the control route wiring

Drop the unused Handler.Register from both build variants: the
gateway mounts the three control routes through s3api.WithRoute so
the SigV4 verifier wrapper (rcAuth) runs in front of each handler,
and nothing else calls Register.

Clear iamOwned only when RunVersityGW returns nil. It shuts the IAM
service down itself at the end of its shutdown sequence, but its
early failure paths return before reaching that point, so the
deferred shutdown must keep covering those errors.

Remove the unused rcserver.SessionInfo parameter from sizeOf; the
transferred byte count comes from the READY response alone.

* rdma: keep transient IAM failures retryable in the fresh revalidation

The fresh account revalidation turned every GetUserAccountFresh
error into 403, which reports transient backend failures (LDAP
timeouts, network errors) as a revoked account and leaves the
client no room to retry. Only a confirmed missing account
(auth.ErrNoSuchUser) means that; answer anything else with 503 so
clients can retry the request.

* rdma: make the IAM shutdown exactly-once and keep gateway errors visible

The gateway and RunVersityGW share the IAM service, and which side
shut it down could not be told from the return value: runtime
failures return after RunVersityGW already shut the service down,
while early setup failures return before any shutdown happens. The
iamOwned flag therefore either shut the service down twice or leaked
it depending on the error, and the error itself was dropped.

Wrap the service so Shutdown runs exactly once no matter which side
calls it, keep the deferred shutdown for every early failure path,
and return the gateway error again. The wrapper re-exposes the
optional interfaces (fresh account reads, signing keys, policy
evaluation, fixed bucket ownership) so feature detection through the
IAM service keeps working.

* rdma: reuse the SigV4 account for RC control requests

READY and CANCEL are independently authenticated SigV4 requests.
Use the account resolved by the normal SigV4 path instead of
bypassing the IAM cache a second time. This aligns RC revocation
latency with other signed S3 requests and removes the extra
backend IAM lookup, its concurrency cap, and the RC-specific IAM
error mapping. The session owner check and the READY target and
operation authorization are unchanged.

* rdma: reword the READY reauthorization comment

The comment implied a revocation inside the session window always
takes effect at READY, but the account used here is the one SigV4
resolved, which may be a cached entry. State what the check does
without claiming account-cache freshness.

* rdma: preserve IAM cache behavior and standalone region

---------

Signed-off-by: Jihyeon Gim <potatogim@potatogim.net>
Co-authored-by: Ben McClelland <ben.mcclelland@versity.com>
2026-09-02 12:35:53 -07:00
niksis02 2147a0c304 feat: integrate standalone IAM service with S3 gateway for identity-based policy enforcement
Fixes #1327
Fixes #1567
Closes #2264

Wires the S3 gateway up to the standalone IAM service so identity policies, not just bucket policies and ACLs, are enforced on the S3 data plane. The gateway authenticates SigV4 requests by calling new private derive-signing-key and resolve-identity endpoints on the IAM service instead of holding secrets itself, and evaluates identity policy through the same PolicyEvaluator path added to auth.VerifyAccess, combined with the bucket policy using explicit-deny-wins precedence. The private endpoints are served over their own mTLS listener (new iamapi/private package, genmtlscerts.sh to generate test material, and client-cert support in internal/netutil), separate from the public IAM API. As part of this the vendored aws/signer/v4 package is deleted and replaced by a pure-Go SigV4 implementation in internal/sigv4auth, which now reads canonical request data directly off the fiber.Ctx instead of reconstructing an http.Request, and is shared by both the S3 request-signing verification and the new private-endpoint signing.

DeleteObjects moves from an all-or-nothing authorization check to true partial success: VerifyObjectsAccess evaluates every object in a batch independently against both the identity policy and any object lock, so a denial or a locked object only removes that key from the batch instead of failing the whole request. It also batches the identity-policy round trip and the bucket-policy fetch once per request rather than once per object, and separates plain deletes from versioned ones since a versioned delete needs s3:DeleteObjectVersion rather than s3:DeleteObject. Object lock handling got a few correctness fixes alongside this: a bypass is now modeled as BypassNone/BypassRequested/BypassOverwrite rather than a single bool, because root's blanket ability to override a GOVERNANCE retention should only apply when the client actually asked to bypass it (DeleteObject/DeleteObjects/PutObjectRetention), not when the gateway is silently replacing a locked object via an overwrite, which needs the permission from everyone including root. Retention changes are now correctly classified as an extension (allowed under plain s3:PutObjectRetention) versus a weakening (date or mode change, which needs the bypass permission), and a COMPLIANCE lock can never be weakened by anyone regardless of permissions, matching AWS. Separately, VerifyObjectCopyAccess had a readonly-mode gap: it returned early for root/admin before ever calling VerifyAccess, so the readonly check inside VerifyAccess never ran for them on CopyObject; access checks are now ordered so the readonly gate always applies before any root/admin bypass, for copy as well as every other write path.

Bucket policies also gained Condition block support, via a new shared internal/condition package moved out of the IAM policy package since both bucket and identity policies share the same evaluation semantics. It implements the full AWS operator set — String{Equals,NotEquals,EqualsIgnoreCase,NotEqualsIgnoreCase,Like,NotLike}, Numeric{Equals,NotEquals,LessThan,LessThanEquals,GreaterThan,GreaterThanEquals}, Date{Equals,NotEquals,LessThan,LessThanEquals,GreaterThan,GreaterThanEquals}, Bool, BinaryEquals, Arn{Equals,Like,NotEquals,NotLike}, IpAddress/NotIpAddress, and Null — along with the ForAllValues/ForAnyValue set qualifiers and the IfExists modifier. A new requestConditionContext builds the per-request keys a bucket policy's Condition block can reference — aws:SourceIp, aws:SecureTransport, aws:CurrentTime, aws:EpochTime, aws:UserAgent, aws:Referer, s3:prefix, s3:delimiter, s3:max-keys, s3:x-amz-acl, s3:VersionId — following AWS's own per-action rules for which keys a given S3 operation actually populates. Identity-derived keys such as aws:PrincipalArn and aws:username are deliberately left unwired here, since the gateway has no way to know them; the standalone IAM service fills those in itself when it evaluates an identity policy.

Also added new integration test suites for S3-side IAM: s3_iam_access_control.go and s3_iam_session_access_control.go cover identity-policy enforcement and session-credential requests against real S3 operations, alongside expanded OIDC/web-identity coverage and a new runoidctests.sh runner wired into the OIDC GitHub Actions workflow.
2026-08-25 01:07:36 +04:00
niksis02 ab2b816633 feat: add STS web identity federation, IAM policy Condition support, and access control enforcement
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.
2026-08-25 01:06:25 +04:00
Ben McClelland 62c6787a17 feat: add new vgwrdma service for cuObject compatible server 2026-08-04 08:48:17 -07:00