From 633e5b5f0297a7da7ceb3ffafa8f78977b08d163 Mon Sep 17 00:00:00 2001 From: bmc-msft <41130664+bmc-msft@users.noreply.github.com> Date: Tue, 5 Jan 2021 14:40:58 -0500 Subject: [PATCH] restrict api endpoints (#404) Restrict API endpoints from agents --- .../__app__/agent_can_schedule/__init__.py | 11 ++-- .../__app__/agent_commands/__init__.py | 13 ++--- .../__app__/agent_events/__init__.py | 6 +- .../__app__/agent_registration/__init__.py | 13 ++--- .../__app__/containers/__init__.py | 4 +- src/api-service/__app__/download/__init__.py | 4 +- .../__app__/job_templates/__init__.py | 10 ++-- .../__app__/job_templates_manage/__init__.py | 12 ++-- src/api-service/__app__/jobs/__init__.py | 12 ++-- src/api-service/__app__/node/__init__.py | 14 ++--- .../__app__/notifications/__init__.py | 12 ++-- ...orization.py => endpoint_authorization.py} | 58 ++++++++++++++----- src/api-service/__app__/pool/__init__.py | 12 ++-- src/api-service/__app__/proxy/__init__.py | 14 ++--- src/api-service/__app__/repro_vms/__init__.py | 12 ++-- src/api-service/__app__/scaleset/__init__.py | 14 ++--- src/api-service/__app__/tasks/__init__.py | 12 ++-- src/api-service/__app__/webhooks/__init__.py | 14 ++--- .../__app__/webhooks_logs/__init__.py | 8 +-- .../__app__/webhooks_ping/__init__.py | 8 +-- 20 files changed, 117 insertions(+), 146 deletions(-) rename src/api-service/__app__/onefuzzlib/{agent_authorization.py => endpoint_authorization.py} (57%) diff --git a/src/api-service/__app__/agent_can_schedule/__init__.py b/src/api-service/__app__/agent_can_schedule/__init__.py index a66d707a9..b1b74343e 100644 --- a/src/api-service/__app__/agent_can_schedule/__init__.py +++ b/src/api-service/__app__/agent_can_schedule/__init__.py @@ -9,7 +9,7 @@ from onefuzztypes.models import Error from onefuzztypes.requests import CanScheduleRequest from onefuzztypes.responses import CanSchedule -from ..onefuzzlib.agent_authorization import call_if_agent +from ..onefuzzlib.endpoint_authorization import call_if_agent from ..onefuzzlib.pools import Node from ..onefuzzlib.request import not_ok, ok, parse_request from ..onefuzzlib.tasks.main import Task @@ -43,9 +43,6 @@ def post(req: func.HttpRequest) -> func.HttpResponse: def main(req: func.HttpRequest) -> func.HttpResponse: - if req.method == "POST": - m = post - else: - raise Exception("invalid method") - - return call_if_agent(req, m) + methods = {"POST": post} + method = methods[req.method] + return call_if_agent(req, method) diff --git a/src/api-service/__app__/agent_commands/__init__.py b/src/api-service/__app__/agent_commands/__init__.py index f741c85c2..09504e052 100644 --- a/src/api-service/__app__/agent_commands/__init__.py +++ b/src/api-service/__app__/agent_commands/__init__.py @@ -8,7 +8,7 @@ from onefuzztypes.models import Error, NodeCommandEnvelope from onefuzztypes.requests import NodeCommandDelete, NodeCommandGet from onefuzztypes.responses import BoolResult, PendingNodeCommand -from ..onefuzzlib.agent_authorization import call_if_agent +from ..onefuzzlib.endpoint_authorization import call_if_agent from ..onefuzzlib.pools import NodeMessage from ..onefuzzlib.request import not_ok, ok, parse_request @@ -43,11 +43,6 @@ def delete(req: func.HttpRequest) -> func.HttpResponse: def main(req: func.HttpRequest) -> func.HttpResponse: - if req.method == "GET": - m = get - elif req.method == "DELETE": - m = delete - else: - raise Exception("invalid method") - - return call_if_agent(req, m) + methods = {"DELETE": delete, "GET": get} + method = methods[req.method] + return call_if_agent(req, method) diff --git a/src/api-service/__app__/agent_events/__init__.py b/src/api-service/__app__/agent_events/__init__.py index 79c1da803..f71c949f3 100644 --- a/src/api-service/__app__/agent_events/__init__.py +++ b/src/api-service/__app__/agent_events/__init__.py @@ -16,8 +16,8 @@ from onefuzztypes.models import ( ) from onefuzztypes.responses import BoolResult -from ..onefuzzlib.agent_authorization import call_if_agent from ..onefuzzlib.agent_events import on_state_update, on_worker_event +from ..onefuzzlib.endpoint_authorization import call_if_agent from ..onefuzzlib.request import not_ok, ok, parse_request @@ -72,4 +72,6 @@ def post(req: func.HttpRequest) -> func.HttpResponse: def main(req: func.HttpRequest) -> func.HttpResponse: - return call_if_agent(req, post) + methods = {"POST": post} + method = methods[req.method] + return call_if_agent(req, method) diff --git a/src/api-service/__app__/agent_registration/__init__.py b/src/api-service/__app__/agent_registration/__init__.py index 82131b38d..fe6dc4a82 100644 --- a/src/api-service/__app__/agent_registration/__init__.py +++ b/src/api-service/__app__/agent_registration/__init__.py @@ -12,10 +12,10 @@ from onefuzztypes.models import Error from onefuzztypes.requests import AgentRegistrationGet, AgentRegistrationPost from onefuzztypes.responses import AgentRegistration -from ..onefuzzlib.agent_authorization import call_if_agent from ..onefuzzlib.azure.containers import StorageType from ..onefuzzlib.azure.creds import get_instance_url from ..onefuzzlib.azure.queue import get_queue_sas +from ..onefuzzlib.endpoint_authorization import call_if_agent from ..onefuzzlib.pools import Node, NodeMessage, NodeTasks, Pool from ..onefuzzlib.request import not_ok, ok, parse_uri @@ -116,11 +116,6 @@ def post(req: func.HttpRequest) -> func.HttpResponse: def main(req: func.HttpRequest) -> func.HttpResponse: - if req.method == "POST": - m = post - elif req.method == "GET": - m = get - else: - raise Exception("invalid method") - - return call_if_agent(req, m) + methods = {"POST": post, "GET": get} + method = methods[req.method] + return call_if_agent(req, method) diff --git a/src/api-service/__app__/containers/__init__.py b/src/api-service/__app__/containers/__init__.py index 5e5f4f824..3c5be88d8 100644 --- a/src/api-service/__app__/containers/__init__.py +++ b/src/api-service/__app__/containers/__init__.py @@ -20,6 +20,7 @@ from ..onefuzzlib.azure.containers import ( get_container_sas_url, get_containers, ) +from ..onefuzzlib.endpoint_authorization import call_if_user from ..onefuzzlib.request import not_ok, ok, parse_request @@ -90,4 +91,5 @@ def delete(req: func.HttpRequest) -> func.HttpResponse: def main(req: func.HttpRequest) -> func.HttpResponse: methods = {"GET": get, "POST": post, "DELETE": delete} - return methods[req.method](req) + method = methods[req.method] + return call_if_user(req, method) diff --git a/src/api-service/__app__/download/__init__.py b/src/api-service/__app__/download/__init__.py index 65b6f7b85..5fe9566c0 100644 --- a/src/api-service/__app__/download/__init__.py +++ b/src/api-service/__app__/download/__init__.py @@ -13,6 +13,7 @@ from ..onefuzzlib.azure.containers import ( container_exists, get_file_sas_url, ) +from ..onefuzzlib.endpoint_authorization import call_if_user from ..onefuzzlib.request import not_ok, parse_uri, redirect @@ -47,4 +48,5 @@ def get(req: func.HttpRequest) -> func.HttpResponse: def main(req: func.HttpRequest) -> func.HttpResponse: methods = {"GET": get} - return methods[req.method](req) + method = methods[req.method] + return call_if_user(req, method) diff --git a/src/api-service/__app__/job_templates/__init__.py b/src/api-service/__app__/job_templates/__init__.py index 1717576f4..dbdb1d7a4 100644 --- a/src/api-service/__app__/job_templates/__init__.py +++ b/src/api-service/__app__/job_templates/__init__.py @@ -7,6 +7,7 @@ import azure.functions as func from onefuzztypes.job_templates import JobTemplateRequest from onefuzztypes.models import Error +from ..onefuzzlib.endpoint_authorization import call_if_user from ..onefuzzlib.job_templates.templates import JobTemplateIndex from ..onefuzzlib.request import not_ok, ok, parse_request from ..onefuzzlib.user_credentials import parse_jwt_token @@ -34,9 +35,6 @@ def post(req: func.HttpRequest) -> func.HttpResponse: def main(req: func.HttpRequest) -> func.HttpResponse: - if req.method == "GET": - return get(req) - elif req.method == "POST": - return post(req) - else: - raise Exception("invalid method") + methods = {"GET": get, "POST": post} + method = methods[req.method] + return call_if_user(req, method) diff --git a/src/api-service/__app__/job_templates_manage/__init__.py b/src/api-service/__app__/job_templates_manage/__init__.py index 5b255d831..b94ef5238 100644 --- a/src/api-service/__app__/job_templates_manage/__init__.py +++ b/src/api-service/__app__/job_templates_manage/__init__.py @@ -13,6 +13,7 @@ from onefuzztypes.job_templates import ( from onefuzztypes.models import Error from onefuzztypes.responses import BoolResult +from ..onefuzzlib.endpoint_authorization import call_if_user from ..onefuzzlib.job_templates.templates import JobTemplateIndex from ..onefuzzlib.request import not_ok, ok, parse_request @@ -61,11 +62,6 @@ def delete(req: func.HttpRequest) -> func.HttpResponse: def main(req: func.HttpRequest) -> func.HttpResponse: - if req.method == "GET": - return get(req) - elif req.method == "POST": - return post(req) - elif req.method == "DELETE": - return delete(req) - else: - raise Exception("invalid method") + methods = {"GET": get, "POST": post, "DELETE": delete} + method = methods[req.method] + return call_if_user(req, method) diff --git a/src/api-service/__app__/jobs/__init__.py b/src/api-service/__app__/jobs/__init__.py index 5c9e3b42e..53c2edede 100644 --- a/src/api-service/__app__/jobs/__init__.py +++ b/src/api-service/__app__/jobs/__init__.py @@ -8,6 +8,7 @@ from onefuzztypes.enums import ErrorCode, JobState from onefuzztypes.models import Error, JobConfig, JobTaskInfo from onefuzztypes.requests import JobGet, JobSearch +from ..onefuzzlib.endpoint_authorization import call_if_user from ..onefuzzlib.jobs import Job from ..onefuzzlib.request import not_ok, ok, parse_request from ..onefuzzlib.tasks.main import Task @@ -74,11 +75,6 @@ def delete(req: func.HttpRequest) -> func.HttpResponse: def main(req: func.HttpRequest) -> func.HttpResponse: - if req.method == "GET": - return get(req) - elif req.method == "POST": - return post(req) - elif req.method == "DELETE": - return delete(req) - else: - raise Exception("invalid method") + methods = {"GET": get, "POST": post, "DELETE": delete} + method = methods[req.method] + return call_if_user(req, method) diff --git a/src/api-service/__app__/node/__init__.py b/src/api-service/__app__/node/__init__.py index 44ffc3d8f..5460fd9e0 100644 --- a/src/api-service/__app__/node/__init__.py +++ b/src/api-service/__app__/node/__init__.py @@ -9,6 +9,7 @@ from onefuzztypes.models import Error from onefuzztypes.requests import NodeGet, NodeSearch, NodeUpdate from onefuzztypes.responses import BoolResult +from ..onefuzzlib.endpoint_authorization import call_if_user from ..onefuzzlib.pools import Node, NodeTasks from ..onefuzzlib.request import not_ok, ok, parse_request @@ -100,13 +101,6 @@ def patch(req: func.HttpRequest) -> func.HttpResponse: def main(req: func.HttpRequest) -> func.HttpResponse: - if req.method == "GET": - return get(req) - elif req.method == "DELETE": - return delete(req) - elif req.method == "PATCH": - return patch(req) - elif req.method == "POST": - return post(req) - else: - raise Exception("invalid method") + methods = {"GET": get, "PATCH": patch, "DELETE": delete, "POST": post} + method = methods[req.method] + return call_if_user(req, method) diff --git a/src/api-service/__app__/notifications/__init__.py b/src/api-service/__app__/notifications/__init__.py index 48d1a1f1c..4acfd946c 100644 --- a/src/api-service/__app__/notifications/__init__.py +++ b/src/api-service/__app__/notifications/__init__.py @@ -9,6 +9,7 @@ import azure.functions as func from onefuzztypes.models import Error from onefuzztypes.requests import NotificationCreate, NotificationGet +from ..onefuzzlib.endpoint_authorization import call_if_user from ..onefuzzlib.notifications.main import Notification from ..onefuzzlib.request import not_ok, ok, parse_request @@ -49,11 +50,6 @@ def delete(req: func.HttpRequest) -> func.HttpResponse: def main(req: func.HttpRequest) -> func.HttpResponse: - if req.method == "GET": - return get(req) - elif req.method == "POST": - return post(req) - elif req.method == "DELETE": - return delete(req) - else: - raise Exception("invalid method") + methods = {"GET": get, "POST": post, "DELETE": delete} + method = methods[req.method] + return call_if_user(req, method) diff --git a/src/api-service/__app__/onefuzzlib/agent_authorization.py b/src/api-service/__app__/onefuzzlib/endpoint_authorization.py similarity index 57% rename from src/api-service/__app__/onefuzzlib/agent_authorization.py rename to src/api-service/__app__/onefuzzlib/endpoint_authorization.py index 95ed40593..32d5d7b35 100644 --- a/src/api-service/__app__/onefuzzlib/agent_authorization.py +++ b/src/api-service/__app__/onefuzzlib/endpoint_authorization.py @@ -39,25 +39,53 @@ def is_agent(token_data: UserInfo) -> bool: return False -def call_if_agent( - req: func.HttpRequest, method: Callable[[func.HttpRequest], func.HttpResponse] -) -> func.HttpResponse: +def is_user(token_data: UserInfo) -> bool: + return not is_agent(token_data) + +def reject(req: func.HttpRequest, token: UserInfo) -> func.HttpResponse: + logging.error( + "reject token. url:%s token:%s body:%s", + repr(req.url), + repr(token), + repr(req.get_body()), + ) + return not_ok( + Error(code=ErrorCode.UNAUTHORIZED, errors=["Unrecognized agent"]), + status_code=401, + context="token verification", + ) + + +def call_if( + req: func.HttpRequest, + method: Callable[[func.HttpRequest], func.HttpResponse], + *, + allow_user: bool = False, + allow_agent: bool = False +) -> func.HttpResponse: token = parse_jwt_token(req) if isinstance(token, Error): return not_ok(token, status_code=401, context="token verification") - if not is_agent(token): - logging.error( - "rejecting token url:%s token:%s body:%s", - repr(req.url), - repr(token), - repr(req.get_body()), - ) - return not_ok( - Error(code=ErrorCode.UNAUTHORIZED, errors=["Unrecognized agent"]), - status_code=401, - context="token verification", - ) + if is_user(token) and not allow_user: + return reject(req, token) + + if is_agent(token) and not allow_agent: + return reject(req, token) return method(req) + + +def call_if_user( + req: func.HttpRequest, method: Callable[[func.HttpRequest], func.HttpResponse] +) -> func.HttpResponse: + + return call_if(req, method, allow_user=True) + + +def call_if_agent( + req: func.HttpRequest, method: Callable[[func.HttpRequest], func.HttpResponse] +) -> func.HttpResponse: + + return call_if(req, method, allow_agent=True) diff --git a/src/api-service/__app__/pool/__init__.py b/src/api-service/__app__/pool/__init__.py index 64f4bbc2d..cae14eae6 100644 --- a/src/api-service/__app__/pool/__init__.py +++ b/src/api-service/__app__/pool/__init__.py @@ -21,6 +21,7 @@ from ..onefuzzlib.azure.creds import ( ) from ..onefuzzlib.azure.queue import get_queue_sas from ..onefuzzlib.azure.vmss import list_available_skus +from ..onefuzzlib.endpoint_authorization import call_if_user from ..onefuzzlib.pools import Pool from ..onefuzzlib.request import not_ok, ok, parse_request @@ -136,11 +137,6 @@ def delete(req: func.HttpRequest) -> func.HttpResponse: def main(req: func.HttpRequest) -> func.HttpResponse: - if req.method == "GET": - return get(req) - elif req.method == "POST": - return post(req) - elif req.method == "DELETE": - return delete(req) - else: - raise Exception("invalid method") + methods = {"GET": get, "POST": post, "DELETE": delete} + method = methods[req.method] + return call_if_user(req, method) diff --git a/src/api-service/__app__/proxy/__init__.py b/src/api-service/__app__/proxy/__init__.py index 0c0bd46e1..cbfc88b6f 100644 --- a/src/api-service/__app__/proxy/__init__.py +++ b/src/api-service/__app__/proxy/__init__.py @@ -11,6 +11,7 @@ from onefuzztypes.models import Error from onefuzztypes.requests import ProxyCreate, ProxyDelete, ProxyGet, ProxyReset from onefuzztypes.responses import BoolResult, ProxyGetResult +from ..onefuzzlib.endpoint_authorization import call_if_user from ..onefuzzlib.pools import Scaleset from ..onefuzzlib.proxy import Proxy from ..onefuzzlib.proxy_forward import ProxyForward @@ -114,13 +115,6 @@ def delete(req: func.HttpRequest) -> func.HttpResponse: def main(req: func.HttpRequest) -> func.HttpResponse: - if req.method == "GET": - return get(req) - elif req.method == "POST": - return post(req) - elif req.method == "DELETE": - return delete(req) - elif req.method == "PATCH": - return patch(req) - else: - raise Exception("invalid method") + methods = {"GET": get, "POST": post, "DELETE": delete, "PATCH": patch} + method = methods[req.method] + return call_if_user(req, method) diff --git a/src/api-service/__app__/repro_vms/__init__.py b/src/api-service/__app__/repro_vms/__init__.py index edb27b026..99269f00b 100644 --- a/src/api-service/__app__/repro_vms/__init__.py +++ b/src/api-service/__app__/repro_vms/__init__.py @@ -8,6 +8,7 @@ from onefuzztypes.enums import ErrorCode, VmState from onefuzztypes.models import Error, ReproConfig from onefuzztypes.requests import ReproGet +from ..onefuzzlib.endpoint_authorization import call_if_user from ..onefuzzlib.repro import Repro from ..onefuzzlib.request import not_ok, ok, parse_request from ..onefuzzlib.user_credentials import parse_jwt_token @@ -73,11 +74,6 @@ def delete(req: func.HttpRequest) -> func.HttpResponse: def main(req: func.HttpRequest) -> func.HttpResponse: - if req.method == "GET": - return get(req) - elif req.method == "POST": - return post(req) - elif req.method == "DELETE": - return delete(req) - else: - raise Exception("invalid method") + methods = {"GET": get, "POST": post, "DELETE": delete} + method = methods[req.method] + return call_if_user(req, method) diff --git a/src/api-service/__app__/scaleset/__init__.py b/src/api-service/__app__/scaleset/__init__.py index 902cdeea1..3f37cc444 100644 --- a/src/api-service/__app__/scaleset/__init__.py +++ b/src/api-service/__app__/scaleset/__init__.py @@ -16,6 +16,7 @@ from onefuzztypes.responses import BoolResult from ..onefuzzlib.azure.creds import get_base_region, get_regions from ..onefuzzlib.azure.vmss import list_available_skus +from ..onefuzzlib.endpoint_authorization import call_if_user from ..onefuzzlib.pools import Pool, Scaleset from ..onefuzzlib.request import not_ok, ok, parse_request @@ -144,13 +145,6 @@ def patch(req: func.HttpRequest) -> func.HttpResponse: def main(req: func.HttpRequest) -> func.HttpResponse: - if req.method == "GET": - return get(req) - elif req.method == "POST": - return post(req) - elif req.method == "DELETE": - return delete(req) - elif req.method == "PATCH": - return patch(req) - else: - raise Exception("invalid method") + methods = {"GET": get, "POST": post, "DELETE": delete, "PATCH": patch} + method = methods[req.method] + return call_if_user(req, method) diff --git a/src/api-service/__app__/tasks/__init__.py b/src/api-service/__app__/tasks/__init__.py index 38433d51a..e9fd24c3c 100644 --- a/src/api-service/__app__/tasks/__init__.py +++ b/src/api-service/__app__/tasks/__init__.py @@ -10,6 +10,7 @@ from onefuzztypes.models import Error, TaskConfig from onefuzztypes.requests import TaskGet, TaskSearch from onefuzztypes.responses import BoolResult +from ..onefuzzlib.endpoint_authorization import call_if_user from ..onefuzzlib.jobs import Job from ..onefuzzlib.pools import NodeTasks from ..onefuzzlib.request import not_ok, ok, parse_request @@ -99,11 +100,6 @@ def delete(req: func.HttpRequest) -> func.HttpResponse: def main(req: func.HttpRequest) -> func.HttpResponse: - if req.method == "GET": - return get(req) - elif req.method == "POST": - return post(req) - elif req.method == "DELETE": - return delete(req) - else: - raise Exception("invalid method") + methods = {"GET": get, "POST": post, "DELETE": delete} + method = methods[req.method] + return call_if_user(req, method) diff --git a/src/api-service/__app__/webhooks/__init__.py b/src/api-service/__app__/webhooks/__init__.py index a58f03844..dd413c432 100644 --- a/src/api-service/__app__/webhooks/__init__.py +++ b/src/api-service/__app__/webhooks/__init__.py @@ -15,6 +15,7 @@ from onefuzztypes.requests import ( ) from onefuzztypes.responses import BoolResult +from ..onefuzzlib.endpoint_authorization import call_if_user from ..onefuzzlib.request import not_ok, ok, parse_request from ..onefuzzlib.webhooks import Webhook @@ -105,13 +106,6 @@ def delete(req: func.HttpRequest) -> func.HttpResponse: def main(req: func.HttpRequest) -> func.HttpResponse: - if req.method == "GET": - return get(req) - elif req.method == "POST": - return post(req) - elif req.method == "DELETE": - return delete(req) - elif req.method == "PATCH": - return patch(req) - else: - raise Exception("invalid method") + methods = {"GET": get, "POST": post, "DELETE": delete, "PATCH": patch} + method = methods[req.method] + return call_if_user(req, method) diff --git a/src/api-service/__app__/webhooks_logs/__init__.py b/src/api-service/__app__/webhooks_logs/__init__.py index ceb8b8343..ce1875319 100644 --- a/src/api-service/__app__/webhooks_logs/__init__.py +++ b/src/api-service/__app__/webhooks_logs/__init__.py @@ -9,6 +9,7 @@ import azure.functions as func from onefuzztypes.models import Error from onefuzztypes.requests import WebhookGet +from ..onefuzzlib.endpoint_authorization import call_if_user from ..onefuzzlib.request import not_ok, ok, parse_request from ..onefuzzlib.webhooks import Webhook, WebhookMessageLog @@ -28,7 +29,6 @@ def post(req: func.HttpRequest) -> func.HttpResponse: def main(req: func.HttpRequest) -> func.HttpResponse: - if req.method == "POST": - return post(req) - else: - raise Exception("invalid method") + methods = {"POST": post} + method = methods[req.method] + return call_if_user(req, method) diff --git a/src/api-service/__app__/webhooks_ping/__init__.py b/src/api-service/__app__/webhooks_ping/__init__.py index e33bfcb94..148781d37 100644 --- a/src/api-service/__app__/webhooks_ping/__init__.py +++ b/src/api-service/__app__/webhooks_ping/__init__.py @@ -9,6 +9,7 @@ import azure.functions as func from onefuzztypes.models import Error from onefuzztypes.requests import WebhookGet +from ..onefuzzlib.endpoint_authorization import call_if_user from ..onefuzzlib.request import not_ok, ok, parse_request from ..onefuzzlib.webhooks import Webhook @@ -29,7 +30,6 @@ def post(req: func.HttpRequest) -> func.HttpResponse: def main(req: func.HttpRequest) -> func.HttpResponse: - if req.method == "POST": - return post(req) - else: - raise Exception("invalid method") + methods = {"POST": post} + method = methods[req.method] + return call_if_user(req, method)