Skip to content

Implement providers.aws.sign_req builtin - #151

Open
ume3445 wants to merge 3 commits into
open-policy-agent:mainfrom
ume3445:implement-aws-sign-req
Open

Implement providers.aws.sign_req builtin#151
ume3445 wants to merge 3 commits into
open-policy-agent:mainfrom
ume3445:implement-aws-sign-req

Conversation

@ume3445

@ume3445 ume3445 commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements providers.aws.sign_req (AWS SigV4 header-based request signing) as described in #146.

Changes

  • Added a new opa-builtins-providers-aws module following the existing domain-specific builtin pattern (BuiltinProvider SPI, similar to opa-builtins-crypto and opa-builtins-time)
  • Implements the full SigV4 flow: canonical request construction, string-to-sign, and the HMAC-SHA256 key derivation chain (kSecret -> kDate -> kRegion -> kService -> kSigning)
  • Matched against OPA's reference Go implementation (topdown/providers.go, internal/providers/aws/signing_v4.go) rather than reverse-engineered from the fixtures alone
  • Validates required request keys (method, url) and AWS config keys (aws_access_key, aws_region, aws_secret_access_key, aws_service), with exact error message wording matched to the compliance fixtures, including alphabetically-sorted key lists in error messages
  • Handles raw_body taking precedence over body, disable_payload_signing using the literal UNSIGNED-PAYLOAD and ignoring the body, existing x-amz-content-sha256 headers being overwritten, and signed-headers list construction (lowercased, sorted, including host/x-amz-content-sha256/x-amz-date/x-amz-security-token as applicable)

Testing

  • Verified against all 18 compliance fixture cases (12 success, 6 error) from providers-aws/aws-sign_req.json and aws-sign_req-errors.json
  • Cross-checked computed signatures against expected fixture values for all 12 success cases individually
  • Full opa-evaluator suite shows no new regressions (confirmed by stashing the change and reproducing the identical pre-existing failure count)

Closes #146.

…loses open-policy-agent#146)

Signed-off-by: Muhammad Umer Hammad <umerhammad010@gmail.com>

@sspaink sspaink left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified against OPA's topdown/providers.go + internal/providers/aws/signing_v4.go, with the divergences below confirmed by running Go directly. The SigV4 chain, string-to-sign, credential scope, header precedence (including the subtle "existing x-amz-content-sha256 wins in the canonical request but computed hash wins in output"), raw_body precedence, and error-message wording all match, and the fixture-driven test is solid. Because signing is byte-exact, the findings all manifest as wrong signatures on inputs the 18 fixtures don't cover — so the suite passes while real requests would fail AWS verification. Findings 1 and 3 share a root cause: canonicalJson reimplements Go's encoding/json and misses two of its behaviors.

// disable_payload_signing (optional boolean).
boolean disablePayloadSigning = false;
RegoValue dps = awsConfigObj.getProperty("disable_payload_signing");
if (dps != null) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(minor) Negative time_ns is rejected where OPA accepts it. The ts < 0 guard throws TypeError, but OPA does time.Unix(0, ts), which happily produces a pre-1970 signing time. Divergence only for negative timestamps — unlikely in practice, flagging for completeness.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looked into matching time.Unix(0, ts) exactly, but the existing failure-simple-bad timestamp fixture explicitly expects -1e9 to throw an error, so this change breaks that fixture. Since you flagged it as minor, I held off changing shared test data unilaterally, happy to update both the code and the fixture if you'd rather match Go exactly here too.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fixture isn't rejecting -1e9 for being negative — OPA's timestamp.Int64() is strconv.ParseInt(literal, 10, 64), which rejects any literal that isn't a plain base-10 integer. Confirmed against the binary: -1e9 errors but -1000000000 signs fine (19691231T235959Z), while 1e9 and 1.0 both error. So ts < 0 satisfies the fixture by coincidence and is wrong in both directions — it rejects valid negative integers and accepts 1e9/1.0, which OPA rejects.

The correct gate is the number's literal form rather than its value, which needs RegoDecimal/RegoBigInt to retain the source text (same root cause as the float-body thread). Fine as a follow-up rather than a blocker — but worth reopening, since the fixture is currently passing for the wrong reason.

… formatting to match Go byte-exactly

Signed-off-by: Muhammad Umer Hammad <umerhammad010@gmail.com>
@ume3445
ume3445 requested a review from a team as a code owner July 16, 2026 00:55

@sspaink sspaink left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick turnaround on these!

Comment on lines +277 to +288
private void validateHttpRequestOperand(RegoObject reqObj) {
List<String> missing = new ArrayList<>();
for (String key : REQUIRED_REQUEST_KEYS) {
if (reqObj.getProperty(key) == null) {
missing.add(key);
}
}
if (!missing.isEmpty()) {
throw new TypeError(
"operand 1 missing required request parameters(s): " + formatKeySet(missing));
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validateHTTPRequestOperand also rejects keys outside http.send's allowedKeyNames whitelist, and that check runs before the missing-keys check (http.go:311) — so {"url": ..., "method": ..., "bogus_key": "x"} errors in OPA but signs successfully here. The whitelist is the 28-entry allowedKeyNames list, and note the invalid-keys error needs to precede the missing-keys one to match OPA's ordering.

…missing-key check

Signed-off-by: Muhammad Umer Hammad <umerhammad010@gmail.com>
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.

Implement providers.aws.sign_req builtin

3 participants