From 67ffb35c802b6899a8e26168325711aff18894a3 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Sun, 29 Oct 2023 17:07:57 -0700 Subject: [PATCH] Added ability to specify CIDR when creating a Reservation, fixed close action for Reservations view in UI and added X-Frame-Options header to prod NGINX config --- engine/app/models.py | 20 +++++- engine/app/routers/space.py | 70 ++++++++++++++++--- lb/nginx.conf | 1 + .../block/Utils/editReservations.jsx | 1 + 4 files changed, 80 insertions(+), 12 deletions(-) diff --git a/engine/app/models.py b/engine/app/models.py index 9fc0711..c740c62 100644 --- a/engine/app/models.py +++ b/engine/app/models.py @@ -347,11 +347,27 @@ class SpaceCIDRReq(BaseModel): class BlockCIDRReq(BaseModel): """DOCSTRING""" - size: int + size: Optional[int] = None + cidr: Optional[IPv4Network] = None desc: Optional[str] = None reverse_search: Optional[bool] = False smallest_cidr: Optional[bool] = False + @model_validator(mode='before') + @classmethod + def validate_request(cls, data: Any) -> Any: + if isinstance(data, dict): + if 'cidr' in data and any(x in data for x in ['reverse_search', 'smallest_cidr']): + if data['cidr'] is not None: + raise AssertionError("the 'cidr' parameter can only be used in conjuction with 'desc'") + if 'cidr' in data and 'size' in data: + if data['cidr'] is not None: + raise AssertionError("the 'cidr' and 'size' parameters can only be used alternatively") + if 'cidr' not in data and 'size' not in data: + raise AssertionError("it is required to provide either the 'cidr' or 'size' parameter") + + return data + class JSONPatch(BaseModel): """DOCSTRING""" @@ -438,7 +454,7 @@ class Admin(BaseModel): @model_validator(mode='before') @classmethod - def check_email(cls, data: Any) -> Any: + def validate_request(cls, data: Any) -> Any: if isinstance(data, dict): if 'type' in data and 'email' in data: if data['type'] == "Principal" and data['email'] is not None: diff --git a/engine/app/routers/space.py b/engine/app/routers/space.py index 61c8fd3..9a180d0 100644 --- a/engine/app/routers/space.py +++ b/engine/app/routers/space.py @@ -1796,6 +1796,7 @@ async def create_block_reservation( Create a CIDR Reservation for the target Block with the following information: - **size**: Network mask bits + - **cidr**: Specific CIDR to reserve (optional) - **desc**: Description (optional) - **reverse_search**: - **true**: New networks will be created as close to the end of the block as possible @@ -1803,6 +1804,47 @@ async def create_block_reservation( - **smallest_cidr**: - **true**: New networks will be created using the smallest possible available block (e.g. it will not break up large CIDR blocks when possible) - **false (default)**: New networks will be created using the first available block, regardless of size + + ### Usage Examples + + #### *Request a new /24:* + + ```json + { + "size": 24 + "desc": "New CIDR for Business Unit 1" + } + ``` + + #### *Request a new /24 at the end of the block:* + + ```json + { + "size": 24, + "desc": "New CIDR for Business Unit 1", + "reverse_search": true + } + ``` + + #### *Request a new /24 at the end of the block, using the smallest available block:* + + ```json + { + "size": 24, + "desc": "New CIDR for Business Unit 1", + "reverse_search": true, + "smallest_cidr": true + } + ``` + + #### *Request a specific /24:* + + ```json + { + "cidr": "10.0.100.0/24", + "desc" "New CIDR for Business Unit 1" + } + ``` """ user_assertion = authorization.split(' ')[1] @@ -1839,20 +1881,28 @@ async def create_block_reservation( reserved_set = IPSet(block_all_cidrs) available_set = block_set ^ reserved_set - available_slicer = slice(None, None, -1) if req.reverse_search else slice(None) - next_selector = -1 if req.reverse_search else 0 + next_cidr = None + + if req.cidr is not None: + if IPNetwork(req.cidr) not in available_set: + raise HTTPException(status_code=409, detail="Requested CIDR overlaps existing network(s).") - if req.smallest_cidr: - cidr_list = list(filter(lambda x: x.prefixlen <= req.size, available_set.iter_cidrs()[available_slicer])) - min_mask = max(map(lambda x: x.prefixlen, cidr_list)) - available_block = next((net for net in list(filter(lambda network: network.prefixlen == min_mask, cidr_list))), None) + next_cidr = IPNetwork(req.cidr) else: - available_block = next((net for net in list(available_set.iter_cidrs())[available_slicer] if net.prefixlen <= req.size), None) + available_slicer = slice(None, None, -1) if req.reverse_search else slice(None) + next_selector = -1 if req.reverse_search else 0 - if not available_block: - raise HTTPException(status_code=500, detail="Network of requested size unavailable in target block.") + if req.smallest_cidr: + cidr_list = list(filter(lambda x: x.prefixlen <= req.size, available_set.iter_cidrs()[available_slicer])) + min_mask = max(map(lambda x: x.prefixlen, cidr_list)) + available_block = next((net for net in list(filter(lambda network: network.prefixlen == min_mask, cidr_list))), None) + else: + available_block = next((net for net in list(available_set.iter_cidrs())[available_slicer] if net.prefixlen <= req.size), None) - next_cidr = list(available_block.subnet(req.size))[next_selector] + if not available_block: + raise HTTPException(status_code=500, detail="Network of requested size unavailable in target block.") + + next_cidr = list(available_block.subnet(req.size))[next_selector] if "preferred_username" in decoded: creator_id = decoded["preferred_username"] diff --git a/lb/nginx.conf b/lb/nginx.conf index cc777a9..00e40cf 100644 --- a/lb/nginx.conf +++ b/lb/nginx.conf @@ -21,6 +21,7 @@ http { # Frontend location / { + add_header X-Frame-Options "DENY"; proxy_pass http://ui; proxy_intercept_errors on; proxy_set_header Host $http_host; diff --git a/ui/src/features/configure/block/Utils/editReservations.jsx b/ui/src/features/configure/block/Utils/editReservations.jsx index 4d77461..4b94836 100644 --- a/ui/src/features/configure/block/Utils/editReservations.jsx +++ b/ui/src/features/configure/block/Utils/editReservations.jsx @@ -687,6 +687,7 @@ export default function EditReservations(props) { function onClose() { handleClose(); setFilterActive(true); + setSelectionModel([]); } function onSubmit() {