Skip to content

Reject obj signurl expiry times beyond the 7-day signed URL limit - #939

Open
lyelgate wants to merge 1 commit into
linode:devfrom
lyelgate:fix/signurl-expiry-limit
Open

lyelgate wants to merge 1 commit into
linode:devfrom
lyelgate:fix/signurl-expiry-limit

Conversation

@lyelgate

@lyelgate lyelgate commented Nov 1, 2026

Copy link
Copy Markdown

📝 Description

What does this PR do and why is this change necessary?

Fixes #894.

linode-cli obj signurl passed any expiry straight to generate_presigned_url. A URL signed with Signature Version 4, the client's default, can be valid for at most 604800 seconds (7 days), so a longer expiry printed a URL that only returns Access Denied. That matches the report: +31536000 fails while a 24-hour expiry works.

This PR validates the expiry before signing, for both forms (epoch time and +offset), and exits with ExitCodes.ARGUMENT_ERROR and a message on stderr when it is:

  • more than 7 days away: The expiry is 31536000 seconds from now, but a signed URL can be valid for at most 604800 seconds (7 days)
  • not in the future (+0, or an epoch time in the past): The expiry must be in the future
  • not an integer (e.g. +an-hour): Invalid expiry '+an-hour': expected an epoch time or an offset like +3600, in seconds. Before, this raised a ValueError traceback.

Other changes:

  • The limit is a MAX_SIGNED_URL_EXPIRY constant in obj/config.py, next to the plugin's other constants.
  • Validation runs before get_client(). Creating the client can create Object Storage keys for the user, so an invalid expiry now fails without touching their account.
  • The EXPIRY help text now gives the unit (seconds) and the 7-day maximum, which resolves the TODO next to it.

✔️ How to Test

What are the steps to reproduce the issue or verify the changes?

linode-cli obj signurl <bucket> <object> +31536000   # now an error instead of a URL that returns Access Denied
linode-cli obj signurl <bucket> <object> +604800     # still prints a URL, valid for 7 days
linode-cli obj signurl <bucket> <object> +3600       # unchanged

How do I run the relevant unit/integration tests?

make test-unit
# or just the new tests:
LINODE_CLI_TEST_MODE=1 pytest tests/unit/test_plugin_obj.py -k generate_url

The new tests in tests/unit/test_plugin_obj.py mock the S3 client, so no account is needed. They cover a valid offset, the exact 7-day boundary, a valid epoch time, and seven rejected values (over the limit by offset and by epoch time, +0, a past epoch time, and non-numeric input). The rejected cases also check that the client is never created. The rejection cases fail without this change. black, isort, autoflake and pylint pass.

`linode-cli obj signurl` passed any expiry straight to
generate_presigned_url. A URL signed with Signature Version 4 can be
valid for at most 604800 seconds (7 days), so a longer expiry produced
a URL that only returns Access Denied, with no hint as to why.

Validate the expiry before signing, for both the epoch and +offset
forms: exit with an argument error if it is not an integer, not in the
future, or more than 7 days away. Validation now happens before the
client is created, so an invalid expiry fails without creating Object
Storage keys. Document the unit and the limit in the help text, which
resolves the TODO next to it.

Fixes linode#894
@lyelgate
lyelgate requested review from a team as code owners November 1, 2026 11:20
@lyelgate
lyelgate requested review from psnoch-akamai and yec-akamai and removed request for a team November 1, 2026 11:20

This branch has not been deployed

No deployments
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.

[Bug]: obj signurl should reject times > the limit

1 participant