Skip to content

cloud: cover AWS container/temporary-credential edge cases and decide on hostname FULL_URI policy #339

Description

@smiggleworth

Gap cloud-25 · missing-coverage (plus an adaptation-decision on hostname resolution) · confidence: verified · tracked in #242
Baseline: Midge 7d39f86 (0.1.1) vs Pants 2507c66. Midge paths are relative to the midge repo, Pants paths to this repo. Midge behavior is used as the proxy for the not-yet-extracted lsm-spec behavioral contract.

Prior: G10 (partially fixed: code is present, tests are missing)

Midge: src/storage/providers/s3.rs tests should_accept_bracketed_eks_ipv6_container_credential_endpoint (:1918),
should_reject_container_relative_uri_that_can_replace_credential_host (:1956),
should_try_every_validated_container_credential_address (:1930),
should_reject_temporary_aws_credentials_without_valid_expiration (:1980). Implementation at
s3.rs:108-225: the RELATIVE_URI is re-parsed and the host must remain 169.254.170.2 with no fragment. An HTTP
FULL_URI with a DNS host is resolved, every address must be loopback, 169.254.170.2, 169.254.170.23, or fd00:ec2::23,
and the connection is pinned to those addresses (resolve_to_addrs, :465-467). Expiry parsing is strict RFC3339 and must be in the future (:803-832).

Pants: src/Pants.Core/Cloud/Internal/Providers/Credentials/S3/RefreshingS3CredentialProvider.cs. The RELATIVE_URI
prefix check is at :273-281; it does not re-check the host afterwards, but the prefix rule already blocks host replacement. FULL_URI
validation is at :285-298. IPv6 brackets are trimmed at :441. Only literal allowed IPs or the literal name localhost are accepted
(:434-450); any other hostname is rejected instead of being resolved and pinned. Multi-address connection for localhost is left to
SocketsHttpHandler. Expiration is required and must be in the future (:418-432), but DateTimeOffset.TryParse is laxer than RFC3339.
Tests in test/Pants.Tests/Cloud/S3CredentialSourceTests.cs cover only the happy paths with
http://127.0.0.1/credentials (ShouldResolveAwsContainerCredentialsWithAuthorizationTokenFile). There is no RELATIVE_URI, IPv6, or
expired/missing-expiration test.
Minor: the container token-file read catches only IOException (:168). UnauthorizedAccessException escapes without being wrapped.

Why it matters: credential endpoints are an SSRF and credential-exfiltration surface. EKS Pod Identity uses the bracketed
IPv6 endpoint. Temporary credentials that have no usable expiry would be cached forever.

Acceptance criteria:

  • http://[fd00:ec2::23]/v1/credentials is accepted, and http://192.0.2.1/... is rejected as insecure.
  • RELATIVE_URI values //192.0.2.1/x, x, and \\evil are rejected and no request is sent. A valid relative URI targets 169.254.170.2.
  • localhost FULL_URI succeeds when only one of ::1 or 127.0.0.1 is listening.
  • JSON (container/IMDS) and XML (STS) responses with a missing, non-RFC3339, or past Expiration fail with PantsIOException.
  • Decision recorded: either resolve, validate, and pin arbitrary HTTP hostnames as Midge does, or keep the stricter literal-only rule and document it in lsm-spec behavior/.

Adaptation note: the literal-only rule is stricter than Midge and is safe, but it rejects configurations Midge accepts.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:cloudReal cloud provider backendsarea:correctnessTransaction/CF/config behavioral hardeningparity-gapFeature not yet at parity with Midgepriority:P2Normal: bounded hardening, coverage, maintainability, or non-critical behavior gapstatus:needs-decisionDifference may be a legitimate adaptation; needs an explicit maintainer/spec decision

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions