Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove support for signing responses with legacy keyid #3346

Merged
merged 1 commit into from
Mar 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ services:
- DBURI=mysql://balrogadmin:balrogadmin@balrogdb/balrog
- AUTOGRAPH_URL=http://autograph:8000
- AUTOGRAPH_KEYID=normandy
- AUTOGRAPH_KEYID_LEGACY=remote-settings
- AUTOGRAPH_USERNAME=alice
- AUTOGRAPH_PASSWORD=fs5wgcer9qj819kfptdlp8gm227ewxnzvsuj9ztycsx08hfhzu
- SECRET_KEY=blahblah
Expand Down
7 changes: 2 additions & 5 deletions src/auslib/web/public/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,14 @@ def get_aus_metadata_headers(eval_metadata):
return headers


def get_content_signature_headers(content, product, legacy_key=False):
def get_content_signature_headers(content, product):
headers = {}
if product:
product += "_"
if app.config.get("AUTOGRAPH_%sURL" % product):
hash_ = make_hash(content)

keyref = "AUTOGRAPH_%sKEYID" % product
# Caveat: legacy key is only used/checked if non-legacy is present
if legacy_key and f"{keyref}_LEGACY" in app.config:
keyref = f"{keyref}_LEGACY"

def sign():
return sign_hash(
Expand All @@ -48,7 +45,7 @@ def sign():
hash_,
)

# cache with hash+keyref since headers will change based on the legacy key
# cache with hash+keyref since headers will change based on the key
signature, x5u = cache.get("content_signatures", f"{hash_}{keyref}", sign)
headers = {"Content-Signature": f"x5u={x5u}; p384ecdsa={signature}"}
log.debug("Added header: %s" % headers)
Expand Down
23 changes: 1 addition & 22 deletions src/auslib/web/public/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,10 @@

from flask import Response
from flask import current_app as app
from mozilla_version.errors import PatternNotMatchedError
from mozilla_version.version import BaseVersion

from auslib.AUS import FORCE_FALLBACK_MAPPING, FORCE_MAIN_MAPPING
from auslib.web.public.helpers import AUS, get_aus_metadata_headers, get_content_signature_headers, with_transaction

# Map of products and version.
# Uses legacy key if requested version is < value set here.
LEGACY_PRODUCT_VERSION_MAPPING = {"FirefoxVPN": "2.22.0"}


def use_legacy_key(product=None, version=None):
# Skip if product is not in the mapping or version is not provided
if product not in LEGACY_PRODUCT_VERSION_MAPPING or not version:
return False

try:
limit_version = BaseVersion.parse(LEGACY_PRODUCT_VERSION_MAPPING[product])
if BaseVersion.parse(version) < limit_version:
return True
except (PatternNotMatchedError, ValueError):
# Likely a malformed version string - ignore legacy key and sign with latest available
pass
return False


@with_transaction
def get_update(transaction, **parameters):
Expand All @@ -40,7 +19,7 @@ def get_update(transaction, **parameters):

response = json.dumps(release.getResponse(parameters, app.config["ALLOWLISTED_DOMAINS"]))

headers.update(get_content_signature_headers(response, "", use_legacy_key(parameters.get("product"), parameters.get("version"))))
headers.update(get_content_signature_headers(response, ""))

return Response(response=response, status=200, headers=headers, mimetype="application/json")

Expand Down
12 changes: 2 additions & 10 deletions tests/web/test_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ def test_get_content_signature_headers(monkeypatch):
mockapp.config = {
"AUTOGRAPH_product_URL": "foo://bar",
"AUTOGRAPH_product_KEYID": "fookeyid",
"AUTOGRAPH_product_KEYID_LEGACY": "foolegacykeyid",
"AUTOGRAPH_product_USERNAME": "foousername",
"AUTOGRAPH_product_PASSWORD": "foopassword",
}
Expand All @@ -19,24 +18,17 @@ def test_get_content_signature_headers(monkeypatch):
product = "product"

x5u = "https://this.is/a.x5u"
x5u_legacy = "https://this.is/legacy.x5u"
ecdsa = "foobar"

def mock_sign_hash(_, key, *__):
if key == "fookeyid":
return (ecdsa, x5u)
elif key == "foolegacykeyid":
return (ecdsa, x5u_legacy)
raise ValueError(f"Unexpected key {key}")

mocksign = MagicMock()
mocksign.side_effect = mock_sign_hash
monkeypatch.setattr(auslib.web.public.helpers, "sign_hash", mocksign)

# Test without legacy key
assert auslib.web.public.helpers.get_content_signature_headers(content, product, False) == {"Content-Signature": f"x5u={x5u}; p384ecdsa={ecdsa}"}
assert auslib.web.public.helpers.get_content_signature_headers(content, product) == {"Content-Signature": f"x5u={x5u}; p384ecdsa={ecdsa}"}

# Test with legacy key - should return different x5u
assert auslib.web.public.helpers.get_content_signature_headers(content, product, True) == {"Content-Signature": f"x5u={x5u_legacy}; p384ecdsa={ecdsa}"}

assert mocksign.call_count == 2
assert mocksign.call_count == 1
18 changes: 0 additions & 18 deletions tests/web/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ def disable_errorhandler(monkeypatch):
def mock_autograph(monkeypatch):
monkeypatch.setitem(app.config, "AUTOGRAPH_URL", "fake")
monkeypatch.setitem(app.config, "AUTOGRAPH_KEYID", "fake")
monkeypatch.setitem(app.config, "AUTOGRAPH_KEYID_LEGACY", "fakeLegacy")
monkeypatch.setitem(app.config, "AUTOGRAPH_USERNAME", "fake")
monkeypatch.setitem(app.config, "AUTOGRAPH_PASSWORD", "fake")

Expand Down Expand Up @@ -465,20 +464,3 @@ def testGuardianResponseV2WithGradualRollout(client, forceValue, response):
def testXMLForGuardianBlob(client):
ret = client.get("/update/1/Guardian/0.4.0.0/default/WINNT_x86_64/en-US/release/update.xml")
assert ret.status_code == 400


@pytest.mark.parametrize(
"product,version,expected",
[
("FakeApp", "2.21.0", True),
("FakeApp", "2.22.0", False),
("FakeApp", "weird.version", False),
("FakeApp", None, False),
(None, None, False),
(None, "2.21.0", False),
("FooBar", "2.21.0", False),
],
)
def test_use_legacy_key(product, version, expected, monkeypatch):
monkeypatch.setitem(auslib.web.public.json.LEGACY_PRODUCT_VERSION_MAPPING, "FakeApp", "2.22.0")
assert auslib.web.public.json.use_legacy_key(product, version) is expected
2 changes: 0 additions & 2 deletions uwsgi/public.wsgi
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,6 @@ from auslib.web.public.base import app as application # noqa
if os.environ.get("AUTOGRAPH_URL"):
application.config["AUTOGRAPH_URL"] = os.environ["AUTOGRAPH_URL"]
application.config["AUTOGRAPH_KEYID"] = os.environ["AUTOGRAPH_KEYID"]
# Optional legacy key value for different products
application.config["AUTOGRAPH_KEYID_LEGACY"] = os.environ.get("AUTOGRAPH_KEYID_LEGACY")
application.config["AUTOGRAPH_USERNAME"] = os.environ["AUTOGRAPH_USERNAME"]
application.config["AUTOGRAPH_PASSWORD"] = os.environ["AUTOGRAPH_PASSWORD"]

Expand Down