Skip to content

fix(request): no dangling ? on empty query strings; feat(auth): doc-level OCI env var indirections - #125

Open
jeffreyaven wants to merge 3 commits into
mainfrom
fix/empty-query-request-target
Open

fix(request): no dangling ? on empty query strings; feat(auth): doc-level OCI env var indirections#125
jeffreyaven wants to merge 3 commits into
mainfrom
fix/empty-query-request-target

Conversation

@jeffreyaven

Copy link
Copy Markdown
Member

Two changes, bundled because both gate the oci provider release: the first is required for any
OCI write operation to work at all, the second enables zero-config auth from the provider doc.

1. Never emit a dangling ? for an empty query string (fix)

operation_store.go built every request URL as path + "?" + query even when the encoded query
was empty. url.Parse records the trailing ? as ForceQuery and reproduces it on the wire and
inside the signed (request-target). Servers that normalise the request line before signature
verification then reconstruct post /20160918/vcns against a signature over
post /20160918/vcns? -> verification fails. Live OCI rejects every no-query body verb with
401 NotAuthenticated / Failed to verify the HTTP(S) Signature; GETs escape because OCI list
calls always carry real query params (?compartmentId=...).

Signed-mock suites cannot catch this class of defect: the dangling ? is self-consistent between
signing and wire, so digest and signed-header checks all pass. It was found by bisecting a live
failure (official-SDK signer probe succeeded where the engine failed) and confirmed with a raw
header/URL dump at the mock.

The fix appends ? (or & for paths with embedded queries) only when the encoded query is
non-empty:

encodedQuery := q.Encode()
rawURL := path
if encodedQuery != "" {
    separator := "?"
    if strings.Contains(path, "?") {
        separator = "&"
    }
    rawURL = fmt.Sprintf("%s%s%s", path, separator, encodedQuery)
}
u, err := url.Parse(rawURL)

2. Doc-level OCI env var indirections for oci_signing_v1 (feat)

A provider doc may now ship default env var names in its config.auth block, so a populated
environment needs no runtime --auth context - matching the credentialsenvvar convention of
the API-key auth types:

config:
  auth:
    type: oci_signing_v1
    tenancy_ocid_envvar: OCI_TENANCY
    user_ocid_envvar: OCI_USER
    oci_fingerprint_envvar: OCI_FINGERPRINT
    oci_private_key_path_envvar: OCI_KEY_FILE
    oci_passphrase_envvar: OCI_PASSPHRASE

Key set: tenancy_ocid_envvar, user_ocid_envvar, oci_fingerprint_envvar, oci_private_key_envvar,
oci_private_key_path_envvar, oci_passphrase_envvar, oci_region_envvar. Indirections only - literal
credential values remain runtime-auth-context territory. Plumbed through
internal/anysdk/auth_dto.go, pkg/authsurface.AuthDTO and the public/formulation wrapper
(new getters GetOci*EnvVar()); the API growth check is unaffected (methods on existing
interfaces, no new exported types). The consumer wire-through (mapping the doc-level getters onto
the runtime AuthCtx in transformOpenapiStackqlAuthToLocal) is tracked as a stackql issue.

Verification

  • go build ./... and go vet clean (vet findings in registry.go are pre-existing).
  • Consumed by a local stackql build (go.mod replace) and verified against a live OCI Always
    Free tenancy (ap-melbourne-1): 18/18 smoke assertions including the full instance state walk
    (launch -> RUNNING -> STOP -> STOPPED -> START -> RUNNING -> rename -> terminate ->
    TERMINATED), VCN/subnet/bucket lifecycles, and both signed forms (three-header GET/DELETE,
    six-header POST/PUT).
  • The oci provider's signed-mock integration suite (26 checks) now rejects a dangling ? exactly
    as live OCI does, so a regression fails the suite rather than only surfacing live.

Release

Suggest cutting v0.5.4-alpha02 from this branch for the pending stackql release - the oci
provider publish is gated on a released stackql that carries change 1.

jeffreyaven and others added 3 commits August 6, 2026 18:23
url.Parse records a trailing ? as ForceQuery and reproduces it on the
wire. Signature schemes covering (request-target) then fail against
servers that normalise the request line before verification - live OCI
rejects every no-query body verb with 401 NotAuthenticated (GETs escape
because they always carry real query params). Found by the oci provider
live smoke; the signed-mock integration suite could not catch it because
the dangling ? is self-consistent between signing and wire.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A provider doc may now ship default env var names in its config.auth
block (tenancy_ocid_envvar, user_ocid_envvar, fingerprint_envvar,
private_key_envvar, private_key_path_envvar, passphrase_envvar,
region_envvar) so a populated environment needs no runtime --auth
context, matching the credentialsenvvar convention of the API-key auth
types. Indirections only - literal credential values remain runtime
auth context territory. Plumbed through the internal DTO, authsurface
and formulation wrapper layers; consumers map these onto the runtime
AuthCtx Oci* fields (stackql wire-through tracked separately).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fingerprint, private key and passphrase are generic terms other auth
types may legitimately claim; the doc-level OCI keys now all carry an
OCI reference at the operator surface (tenancy_ocid_envvar,
user_ocid_envvar via 'ocid'; oci_fingerprint_envvar,
oci_private_key_envvar, oci_private_key_path_envvar,
oci_passphrase_envvar, oci_region_envvar). Go field and getter names
unchanged - consumers are unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jeffreyaven jeffreyaven self-assigned this Aug 6, 2026
@jeffreyaven jeffreyaven closed this Aug 6, 2026
@jeffreyaven jeffreyaven reopened this Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant