Skip to content

Commit

Permalink
Merge b44b14b into 738b73a
Browse files Browse the repository at this point in the history
  • Loading branch information
crungehottman committed Apr 12, 2024
2 parents 738b73a + b44b14b commit 7ea395e
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 0 deletions.
32 changes: 32 additions & 0 deletions exodus_gw/routers/publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@

import logging
import os
import re
from datetime import datetime
from uuid import uuid4

Expand Down Expand Up @@ -208,6 +209,7 @@ def update_publish_items(
env: Environment = deps.env,
db: Session = deps.db,
settings: Settings = deps.settings,
call_context: auth.CallContext = deps.call_context,
) -> dict[None, None]:
"""Add publish items to an existing publish object.
Expand Down Expand Up @@ -276,6 +278,36 @@ def update_publish_items(

db_publish.resolve_links(ln_items=resolvable)

# Prevent unauthorized users from publishing to restricted paths within
# a particular CDN environment.
#
# Some users only need to publish to certain paths. Allowing those
# users to publish to other paths increases the risk of conflicts
# between clients, or of accidents with a large impact.
username = str(
call_context.client.serviceAccountId
or call_context.user.internalUsername
)
path_restrictions = settings.publish_paths.get(env.name)

if path_restrictions and path_restrictions.get(username):
for path in path_restrictions[username]:
pattern = re.compile(path)
for i in items:
if not re.match(pattern, i.web_uri):
LOG.error(
"[%s] User '%s' is not authorized to publish to path '%s' (regex: '%s')",
publish_id,
username,
i.web_uri,
path,
)
raise HTTPException(
403,
detail="User '%s' is not authorized to publish to path '%s'"
% (username, i.web_uri),
)

# Convert the list into dict and update each dict with a publish_id.
# Each item's 'dirty' and 'updated' are refreshed to ensure it's
# written to DynamoDB with the current update, even if it was already
Expand Down
13 changes: 13 additions & 0 deletions exodus_gw/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,19 @@ class Settings(BaseSettings):
for validation. E.g., "exodus-migration-md5": "^[0-9a-f]{32}$"
"""

publish_paths: dict[str, dict[str, list[str]]] = {}
"""A set of user or service accounts which are only authorized to publish to a
particular set of path(s) in a given CDN environment and the regex(es) describing
the paths to which the user or service account is authorized to publish. The user or
service account will be prevented from publishing to any paths that do not match the
defined regular expression(s).
E.g., '{"pre": {"konflux-release-service-exodus":
["^(/content)?/origin/files/sha256/[0-f]{2}/[0-f]{64}/[^/]{1,300}$"]}}'
Any user or service account not included in this configuration is considered to have
unrestricted publish access (i.e., can publish to any path).
"""

log_config: dict[str, Any] = {
"version": 1,
"incremental": True,
Expand Down
93 changes: 93 additions & 0 deletions tests/routers/test_publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -1357,3 +1357,96 @@ def test_get_publish_not_found(auth_header, fake_publish):
assert r.json() == {
"detail": "No publish found for ID %s" % fake_publish.id
}


def test_update_user_authorized_publish_paths(db, auth_header, monkeypatch):
"""Ensure that a user can successfully publish content to any paths to
which they are authorized to publish."""

monkeypatch.setenv(
"EXODUS_GW_PUBLISH_PATHS",
json.dumps(
{
"test": {
"fake-user": [
"^(/content)?/origin/files/sha256/[0-f]{2}/[0-f]{64}/[^/]{1,300}$",
]
}
}
),
)

publish_id = "11224567-e89b-12d3-a456-426614174000"

publish = Publish(id=publish_id, env="test", state="PENDING")

db.add(publish)
db.commit()

with TestClient(app) as client:
# Try to add some items to it
r = client.put(
"/test/publish/%s" % publish_id,
json=[
{
"web_uri": "/content/origin/files/sha256/00/0044062dca731c0d5c24148722537e181d752ca8cda0097005f9268a51658b0a/test.rpm",
"object_key": "1" * 64,
"content_type": "application/octet-stream",
},
{
"web_uri": "/origin/files/sha256/00/0097062dca731c0d5c24148722537e181d752ca8cda0097005f9268a51658b0a/other-test.rpm",
"object_key": "2" * 64,
"content_type": "application/octet-stream",
},
],
headers=auth_header(roles=["test-publisher"]),
)

# It should have succeeded
assert r.status_code == 200


def test_update_user_unauthorized_publish_paths(db, auth_header, monkeypatch):
"""When a user is only authorized to publish to certain paths in a given
CDN environment, ensure that the user is prevented from publishing to any
paths to which they are unauthorized to publish."""

monkeypatch.setenv(
"EXODUS_GW_PUBLISH_PATHS",
json.dumps(
{
"test": {
"fake-user": [
"^(/content)?/origin/files/sha256/[0-f]{2}/[0-f]{64}/[^/]{1,300}$",
]
}
}
),
)

publish_id = "11224567-e89b-12d3-a456-426614174000"

publish = Publish(id=publish_id, env="test", state="PENDING")

db.add(publish)
db.commit()

with TestClient(app) as client:
# Try to add some items to it
r = client.put(
"/test/publish/%s" % publish_id,
json=[
{
"web_uri": "/content/origin/uri1",
"object_key": "1" * 64,
"content_type": "application/octet-stream",
},
],
headers=auth_header(roles=["test-publisher"]),
)

# It should have failed
assert r.status_code == 403
assert r.json() == {
"detail": "User 'fake-user' is not authorized to publish to path '/content/origin/uri1'"
}

0 comments on commit 7ea395e

Please sign in to comment.