diff --git a/docs/includes/glossary.md b/docs/includes/glossary.md index a29d245259baa8a8b27cd16feade66bd5fa6c732..af5cb334a0a5bb5417b18de02a196add0967bd44 100644 --- a/docs/includes/glossary.md +++ b/docs/includes/glossary.md @@ -36,6 +36,7 @@ *[NREN]: National Research and Education Network *[OOB]: Out-of-band *[OSS]: Operational Support Systems +*[OTRS]: Trouble Ticket system software package *[PoP]: Point of Presence *[REST]: Representational State Transfer *[RFC]: Request For Comments @@ -44,6 +45,7 @@ *[SNMP]: Simple Network Management Protocol *[SOT]: Source Of Truth *[TBA]: To be added +*[TTL]: Time To Live *[UAT]: User Acceptance Testing *[VM]: Virtual Machine *[VRF]: Virtual Routing and Forwarding diff --git a/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt b/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt index aeb0c40d8ae8adc250f8ab38c568ad95d4855393..a7e4a6f7c0f83ed5c864b816583fe3e667cc4449 100644 --- a/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt +++ b/docs/vale/styles/config/vocabularies/geant-jargon/accept.txt @@ -55,6 +55,7 @@ OIDC OOB OPA (OSS|oss) +OTRS PHASE 1 Po[Pp] Pydantic @@ -69,6 +70,7 @@ SOT SURF TBA TERMINATED? +TTL TWAMP UAT UTC diff --git a/gso/cli/imports.py b/gso/cli/imports.py index 6a82ddecc513fc71496de598d83d2499272bab5a..fdcba0f6bdb0df597fc0a155b94e595a09eaa08e 100644 --- a/gso/cli/imports.py +++ b/gso/cli/imports.py @@ -259,6 +259,7 @@ class L3CoreServiceImportModel(BaseModel): is_multi_hop: bool rtbh_enabled: bool # whether Remote Triggered Blackhole is enabled prefix_limit: NonNegativeInt | None = None + ttl_security: NonNegativeInt | None = None class BFDSettingsModel(BaseModel): """BFD Settings model.""" diff --git a/gso/migrations/versions/2025-04-28_a3177c5f9641_add_optional_ttl_security_to_bgp_session.py b/gso/migrations/versions/2025-04-28_a3177c5f9641_add_optional_ttl_security_to_bgp_session.py new file mode 100644 index 0000000000000000000000000000000000000000..cbcb3558ba6586aac3d518c5cbce6f1ed74a503d --- /dev/null +++ b/gso/migrations/versions/2025-04-28_a3177c5f9641_add_optional_ttl_security_to_bgp_session.py @@ -0,0 +1,41 @@ +"""Add optional TTL security to BGP session. + +Revision ID: a3177c5f9641 +Revises: fffe36624681 +Create Date: 2025-04-28 10:21:54.820219 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = 'a3177c5f9641' +down_revision = 'fffe36624681' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + conn = op.get_bind() + conn.execute(sa.text(""" +INSERT INTO resource_types (resource_type, description) VALUES ('ttl_security', 'BGP TTL security') RETURNING resource_types.resource_type_id + """)) + conn.execute(sa.text(""" +INSERT INTO product_block_resource_types (product_block_id, resource_type_id) VALUES ((SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('BGPSession')), (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ttl_security'))) + """)) + + +def downgrade() -> None: + conn = op.get_bind() + conn.execute(sa.text(""" +DELETE FROM product_block_resource_types WHERE product_block_resource_types.product_block_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('BGPSession')) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ttl_security')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values USING product_block_resource_types WHERE subscription_instance_values.subscription_instance_id IN (SELECT subscription_instances.subscription_instance_id FROM subscription_instances WHERE subscription_instances.subscription_instance_id IN (SELECT product_blocks.product_block_id FROM product_blocks WHERE product_blocks.name IN ('BGPSession'))) AND product_block_resource_types.resource_type_id = (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ttl_security')) + """)) + conn.execute(sa.text(""" +DELETE FROM subscription_instance_values WHERE subscription_instance_values.resource_type_id IN (SELECT resource_types.resource_type_id FROM resource_types WHERE resource_types.resource_type IN ('ttl_security')) + """)) + conn.execute(sa.text(""" +DELETE FROM resource_types WHERE resource_types.resource_type IN ('ttl_security') + """)) diff --git a/gso/products/product_blocks/bgp_session.py b/gso/products/product_blocks/bgp_session.py index 863a2da0ac904a5522b58fb15e048c9ee013173b..638c06607ab5ad4690257b8d7893528469500e55 100644 --- a/gso/products/product_blocks/bgp_session.py +++ b/gso/products/product_blocks/bgp_session.py @@ -48,6 +48,7 @@ class BGPSessionInactive(ProductBlockModel, lifecycle=[SubscriptionLifecycle.INI bfd_enabled: bool = False ip_type: IPTypes | None = None prefix_limit: NonNegativeInt | None = None + ttl_security: NonNegativeInt | None = None class BGPSessionProvisioning(BGPSessionInactive, lifecycle=[SubscriptionLifecycle.PROVISIONING]): @@ -65,6 +66,7 @@ class BGPSessionProvisioning(BGPSessionInactive, lifecycle=[SubscriptionLifecycl bfd_enabled: bool ip_type: IPTypes prefix_limit: NonNegativeInt | None + ttl_security: NonNegativeInt | None class BGPSession(BGPSessionProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE]): @@ -83,6 +85,7 @@ class BGPSession(BGPSessionProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE bfd_enabled: Settings for BFD. ip_type: The IP type of the session. prefix_limit: A prefix limit, if required. + ttl_security: A limit on time-to-live used for TTL security. """ peer_address: IPAddress @@ -97,3 +100,4 @@ class BGPSession(BGPSessionProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE bfd_enabled: bool ip_type: IPTypes prefix_limit: NonNegativeInt | None + ttl_security: NonNegativeInt | None diff --git a/gso/translations/en-GB.json b/gso/translations/en-GB.json index 98d936fa0b0bbc830395531474ccf0e9b866d619..b1d6a36a4ee673823c9a346447340c9211c90bb8 100644 --- a/gso/translations/en-GB.json +++ b/gso/translations/en-GB.json @@ -58,6 +58,7 @@ "v4_bgp_bfd_enabled": "IPv4 BGP - BFD enabled", "v4_bgp_multipath_enabled": "IPv4 - BGP multipath enabled", "v4_bgp_prefix_limit": "IPv4 - BGP prefix limit", + "v4_bgp_ttl_security": "IPv4 - BGP TTL security", "v4_bgp_is_passive": "IPv4 - BGP is passive", "v4_bgp_send_default_route": "IPv4 - BGP send default route", "v4_bgp_add_v4_multicast": "IPv4 - BGP add multicast", @@ -71,6 +72,7 @@ "v6_bgp_bfd_enabled": "IPv6 - BGP BFD enabled", "v6_bgp_multipath_enabled": "IPv6 - BGP multipath enabled", "v6_bgp_prefix_limit": "IPv6 - BGP prefix limit", + "v6_bgp_ttl_security": "IPv6 - BGP TTL security", "v6_bgp_is_passive": "IPv6 - BGP is passive", "v6_bgp_send_default_route": "IPv6 - BGP send default route", "v6_bgp_add_v6_multicast": "IPv6 - BGP add multicast" diff --git a/gso/workflows/l3_core_service/base_create_imported_l3_core_service.py b/gso/workflows/l3_core_service/base_create_imported_l3_core_service.py index 3f8bf13f324771ea4dd2c4f33dffbafb65d2c26e..7261633b4439a9ab2602237865d9e80a1f8a26d2 100644 --- a/gso/workflows/l3_core_service/base_create_imported_l3_core_service.py +++ b/gso/workflows/l3_core_service/base_create_imported_l3_core_service.py @@ -40,6 +40,7 @@ def initial_input_form_generator() -> FormGenerator: is_multi_hop: bool rtbh_enabled: bool prefix_limit: NonNegativeInt | None = None + ttl_security: NonNegativeInt | None = None class ServiceBindingPort(BaseModel): edge_port: UUIDstr diff --git a/gso/workflows/l3_core_service/base_create_l3_core_service.py b/gso/workflows/l3_core_service/base_create_l3_core_service.py index 6d8c451ea82e3af081020977c05943e06797c4d2..90221ec0ed482de386762c097c88eb7a9e8298e0 100644 --- a/gso/workflows/l3_core_service/base_create_l3_core_service.py +++ b/gso/workflows/l3_core_service/base_create_l3_core_service.py @@ -70,6 +70,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: bfd_enabled: bool = False multipath_enabled: bool = False prefix_limit: NonNegativeInt | None = None + ttl_security: NonNegativeInt | None = None is_passive: bool = False add_v4_multicast: bool = Field(default=False, exclude=True) send_default_route: bool = False @@ -91,6 +92,7 @@ def initial_input_form_generator(product_name: str) -> FormGenerator: bfd_enabled: bool = False multipath_enabled: bool = False prefix_limit: NonNegativeInt | None = None + ttl_security: NonNegativeInt | None = None is_passive: bool = False add_v6_multicast: bool = Field(default=False, exclude=True) send_default_route: bool = False diff --git a/gso/workflows/l3_core_service/base_modify_l3_core_service.py b/gso/workflows/l3_core_service/base_modify_l3_core_service.py index 4185ef26965e3fe003c51dcf8cdb0c0f61db64b4..e16364ad1095734f4904c7288d54f46f9dbd808c 100644 --- a/gso/workflows/l3_core_service/base_modify_l3_core_service.py +++ b/gso/workflows/l3_core_service/base_modify_l3_core_service.py @@ -74,6 +74,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: bfd_enabled: bool = False multipath_enabled: bool = False prefix_limit: NonNegativeInt | None = None + ttl_security: NonNegativeInt | None = None is_passive: bool = False add_v4_multicast: bool = Field(default=False, exclude=True) send_default_route: bool = False @@ -95,6 +96,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: bfd_enabled: bool = False multipath_enabled: bool = False prefix_limit: NonNegativeInt | None = None + ttl_security: NonNegativeInt | None = None is_passive: bool = False add_v6_multicast: bool = Field(default=False, exclude=True) send_default_route: bool = False @@ -282,6 +284,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: v4_bgp_bfd_enabled: bool = Field(v4_peer.bfd_enabled, exclude=True) v4_bgp_multipath_enabled: bool = Field(v4_peer.multipath_enabled, exclude=True) v4_bgp_prefix_limit: NonNegativeInt | None = Field(v4_peer.prefix_limit, exclude=True) + v4_bgp_ttl_security: NonNegativeInt | None = Field(v4_peer.ttl_security, exclude=True) v4_bgp_is_passive: bool = Field(v4_peer.is_passive, exclude=True) v4_bgp_send_default_route: bool = Field(v4_peer.send_default_route, exclude=True) v4_bgp_add_v4_multicast: bool = Field(bool(IPFamily.V4MULTICAST in v4_peer.families), exclude=True) @@ -299,6 +302,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: v6_bgp_bfd_enabled: bool = Field(v6_peer.bfd_enabled, exclude=True) v6_bgp_multipath_enabled: bool = Field(v6_peer.multipath_enabled, exclude=True) v6_bgp_prefix_limit: NonNegativeInt | None = Field(v6_peer.prefix_limit, exclude=True) + v6_bgp_ttl_security: NonNegativeInt | None = Field(v6_peer.ttl_security, exclude=True) v6_bgp_is_passive: bool = Field(v6_peer.is_passive, exclude=True) v6_bgp_send_default_route: bool = Field(v6_peer.send_default_route, exclude=True) v6_bgp_add_v6_multicast: bool = Field(bool(IPFamily.V6MULTICAST in v6_peer.families), exclude=True) @@ -323,6 +327,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: bfd_enabled=self.v4_bgp_bfd_enabled, multipath_enabled=self.v4_bgp_multipath_enabled, prefix_limit=self.v4_bgp_prefix_limit, + ttl_security=self.v4_bgp_ttl_security, is_passive=self.v4_bgp_is_passive, send_default_route=self.v4_bgp_send_default_route, add_v4_multicast=self.v4_bgp_add_v4_multicast, @@ -348,6 +353,7 @@ def initial_input_form_generator(subscription_id: UUIDstr) -> FormGenerator: bfd_enabled=self.v6_bgp_bfd_enabled, multipath_enabled=self.v6_bgp_multipath_enabled, prefix_limit=self.v6_bgp_prefix_limit, + ttl_security=self.v6_bgp_ttl_security, is_passive=self.v6_bgp_is_passive, send_default_route=self.v6_bgp_send_default_route, add_v6_multicast=self.v6_bgp_add_v6_multicast, diff --git a/gso/workflows/router/validate_router.py b/gso/workflows/router/validate_router.py index 02c4b5c773d0ebe8a1ff19af199538479ce9d310..38f6c4b6e9269ff482abc542271ba256deb4fd62 100644 --- a/gso/workflows/router/validate_router.py +++ b/gso/workflows/router/validate_router.py @@ -148,7 +148,7 @@ def check_kentik_entry_exists(subscription: Router) -> None: license on it. This is because there can be multiple, valid, non-archiving licenses for devices. Raises: - ProcessFailureError when a Kentik device is missing, or configured incorrectly. + ProcessFailureError: when a Kentik device is missing, or configured incorrectly. """ client = KentikClient() diff --git a/test/fixtures/l3_core_service_fixtures.py b/test/fixtures/l3_core_service_fixtures.py index 04e9c175f48585a42f4c48e4687f00757ad7b0e0..d23bc42978c325220fa304782f6192841b25df7c 100644 --- a/test/fixtures/l3_core_service_fixtures.py +++ b/test/fixtures/l3_core_service_fixtures.py @@ -62,6 +62,7 @@ def bgp_session_subscription_factory(faker): has_custom_policies: bool = False, multipath_enabled: bool | None = True, prefix_limit: NonNegativeInt | None = None, + ttl_security: NonNegativeInt | None = None, send_default_route: bool | None = True, is_passive: bool | None = False, rtbh_enabled: bool | None = False, @@ -77,6 +78,7 @@ def bgp_session_subscription_factory(faker): authentication_key=authentication_key or faker.password(), multipath_enabled=multipath_enabled, prefix_limit=prefix_limit, + ttl_security=ttl_security, send_default_route=send_default_route, is_multi_hop=is_multi_hop, rtbh_enabled=rtbh_enabled, diff --git a/test/workflows/l3_core_service/test_modify_l3_core_service.py b/test/workflows/l3_core_service/test_modify_l3_core_service.py index 0b2a9f3f9377b3c2513f8db551d5c45fd860677c..93f8680b68dc20273030535499364f8a5a0e121f 100644 --- a/test/workflows/l3_core_service/test_modify_l3_core_service.py +++ b/test/workflows/l3_core_service/test_modify_l3_core_service.py @@ -68,7 +68,8 @@ def test_modify_l3_core_service_add_new_edge_port_success( "authentication_key": faker.password(), "peer_address": faker.ipv4(), "bfd_enabled": False, - "prefix_limit": 1000, + "prefix_limit": faker.random_int(min=500, max=1000), + "ttl_security": faker.random_int(max=255), }, "v6_bgp_peer": { "authentication_key": faker.password(), @@ -142,7 +143,8 @@ def sbp_input_form_data(faker): "v6_bgp_is_passive": True, "v6_bgp_peer_address": faker.ipv6(), "v6_bgp_add_v6_multicast": True, - "v6_bgp_prefix_limit": 3000, + "v6_bgp_prefix_limit": faker.random_int(min=2500, max=3000), + "v6_bgp_ttl_security": faker.random_int(max=255), } return _generate_form_data