Module: Ruby::Rego::Builtins::Providers::Aws

Extended by:
RegistryHelpers
Defined in:
lib/ruby/rego/builtins/providers/aws.rb

Overview

providers.aws.sign_req(request, aws_config, time_ns): sign an HTTP request with AWS Signature Version 4, matching OPA’s topdown builtinAWSSigV4SignReq (internal/providers/aws/signing_v4.go). Deterministic: the signing time is the time_ns argument, not a clock. Returns the request copied with its headers replaced by the original (single-valued) headers plus the signing headers (Authorization, host, x-amz-date, and conditionally x-amz-content-sha256 / x-amz-security-token).

OPA’s SigV4 deviates from the AWS spec in three byte-affecting ways the port reproduces: the canonical query is RawQuery VERBATIM (not sorted/re-encoded); header values are signed verbatim (not trimmed); and the canonical URI is url.EscapedPath() (user %-encoding preserved, never double-encoded). The body is hashed as raw_body (string, wins over body) or json.Marshal(body) (reusing Codecs.canonical_json — sorted keys + Go HTML escaping). With the arbitrary-precision number model that canonical JSON is byte-exact with OPA, so a body number written as a Rego literal (1.50, 1e10) hashes identically to OPA; only a number reaching the body from parsed JSON input still collapses through Float (the deferred input-precision gap), which exact-byte callers avoid by passing raw_body.

Two documented divergences, both on inputs real callers don’t hit: (1) two request headers whose names differ ONLY in case (e.g. “X-Foo” and “x-foo”) — OPA’s signature is nondeterministic there (Go map iteration order decides which lowercased value wins), so the gem’s deterministic choice matches only one of OPA’s branches; (2) an invalid-UTF-8 url or header key — the gem returns undefined (like uri.parse) rather than signing, to stay total.

Any precondition failure is undefined (the registry maps BuiltinArgumentError -> undefined): a non-object request/config; a request key outside http.send’s allowed set or a missing/non-string method/url; an unparseable url; an aws_config missing one of the four required string keys (empty strings are accepted) or holding a non-string for one; a non-integer / int64-overflowing time_ns; or a non-boolean disable_payload_signing. rubocop:disable Metrics/ModuleLength – a faithful step-by-step port of OPA’s SigV4 signer.

Constant Summary collapse

AWS_FUNCTIONS =
{
  "providers.aws.sign_req" => { arity: 3, handler: :sign_req }
}.freeze
CONTEXT =
"providers.aws.sign_req"
REQUIRED_CONFIG =

aws_config keys that must be present and string-typed (empty strings allowed).

%w[aws_access_key aws_secret_access_key aws_service aws_region].freeze
ALLOWED_REQUEST_KEYS =

The http.send request keys OPA’s validateHTTPRequestOperand accepts; any other key is an error.

%w[
  method url body enable_redirect force_json_decode force_yaml_decode headers raw_body timeout
  tls_use_system_certs tls_ca_cert tls_ca_cert_file tls_ca_cert_env_variable tls_client_cert
  tls_client_cert_file tls_client_cert_env_variable tls_client_key tls_client_key_file
  tls_client_key_env_variable tls_server_name tls_insecure_skip_verify cache force_cache
  force_cache_duration_seconds raise_error caching_mode max_retry_attempts cache_ignored_headers
].freeze
IGNORED_HEADERS =

Headers excluded from signing regardless of the request (awsSigv4IgnoredHeaders), lowercased.

%w[authorization user-agent x-amzn-trace-id].freeze
PAYLOAD_HASH_SERVICES =

Services for which OPA emits the x-amz-content-sha256 header (string-equal, case-sensitive).

%w[s3 glacier].freeze
ALGORITHM =
"AWS4-HMAC-SHA256"
AMZ_DATE_FORMAT =
"%Y%m%dT%H%M%SZ"
DATE_FORMAT =
"%Y%m%d"
INT64_MIN =
-(2**63)
INT64_MAX =
(2**63) - 1

Class Method Summary collapse

Methods included from RegistryHelpers

register_configured_functions

Class Method Details

.register!Ruby::Rego::Builtins::BuiltinRegistry



72
73
74
75
76
# File 'lib/ruby/rego/builtins/providers/aws.rb', line 72

def self.register!
  registry = BuiltinRegistry.instance
  register_configured_functions(registry, AWS_FUNCTIONS)
  registry
end

.sign_req(request_value, config_value, time_ns_value) ⇒ Ruby::Rego::Value

Returns the signed request object, or undefined.

Parameters:

Returns:



84
85
86
87
88
89
90
91
92
93
# File 'lib/ruby/rego/builtins/providers/aws.rb', line 84

def self.sign_req(request_value, config_value, time_ns_value)
  request = object_or_undefined(request_value)
  config = object_or_undefined(config_value)
  time = signing_time(time_ns_value)
  validate_request_keys!(request)
  creds = credentials(config)
  signed_headers = sign(request, creds, time)

  output_request(request, signed_headers)
end