enable configurable virtual network ranges (#1268)

This commit is contained in:
bmc-msft
2021-09-27 14:01:32 -04:00
committed by GitHub
parent 47bb2ce187
commit 22b2d62e29
7 changed files with 118 additions and 37 deletions

View File

@ -645,6 +645,10 @@ Each event will be submitted via HTTP POST to the user provided URL.
"allowed_aad_tenants": [ "allowed_aad_tenants": [
"00000000-0000-0000-0000-000000000000" "00000000-0000-0000-0000-000000000000"
], ],
"network_config": {
"address_space": "10.0.0.0/8",
"subnet": "10.0.0.0/16"
},
"proxy_vm_sku": "Standard_B2s" "proxy_vm_sku": "Standard_B2s"
} }
} }
@ -752,6 +756,9 @@ Each event will be submitted via HTTP POST to the user provided URL.
"extensions": { "extensions": {
"$ref": "#/definitions/AzureVmExtensionConfig" "$ref": "#/definitions/AzureVmExtensionConfig"
}, },
"network_config": {
"$ref": "#/definitions/NetworkConfig"
},
"proxy_vm_sku": { "proxy_vm_sku": {
"default": "Standard_B2s", "default": "Standard_B2s",
"title": "Proxy Vm Sku", "title": "Proxy Vm Sku",
@ -791,6 +798,22 @@ Each event will be submitted via HTTP POST to the user provided URL.
], ],
"title": "KeyvaultExtensionConfig", "title": "KeyvaultExtensionConfig",
"type": "object" "type": "object"
},
"NetworkConfig": {
"properties": {
"address_space": {
"default": "10.0.0.0/8",
"title": "Address Space",
"type": "string"
},
"subnet": {
"default": "10.0.0.0/16",
"title": "Subnet",
"type": "string"
}
},
"title": "NetworkConfig",
"type": "object"
} }
}, },
"properties": { "properties": {
@ -5804,6 +5827,9 @@ Each event will be submitted via HTTP POST to the user provided URL.
"extensions": { "extensions": {
"$ref": "#/definitions/AzureVmExtensionConfig" "$ref": "#/definitions/AzureVmExtensionConfig"
}, },
"network_config": {
"$ref": "#/definitions/NetworkConfig"
},
"proxy_vm_sku": { "proxy_vm_sku": {
"default": "Standard_B2s", "default": "Standard_B2s",
"title": "Proxy Vm Sku", "title": "Proxy Vm Sku",
@ -5895,6 +5921,22 @@ Each event will be submitted via HTTP POST to the user provided URL.
"title": "KeyvaultExtensionConfig", "title": "KeyvaultExtensionConfig",
"type": "object" "type": "object"
}, },
"NetworkConfig": {
"properties": {
"address_space": {
"default": "10.0.0.0/8",
"title": "Address Space",
"type": "string"
},
"subnet": {
"default": "10.0.0.0/16",
"title": "Subnet",
"type": "string"
}
},
"title": "NetworkConfig",
"type": "object"
},
"NoReproReport": { "NoReproReport": {
"properties": { "properties": {
"error": { "error": {

View File

@ -13,10 +13,11 @@ from msrestazure.azure_exceptions import CloudError
from msrestazure.tools import parse_resource_id from msrestazure.tools import parse_resource_id
from onefuzztypes.enums import ErrorCode from onefuzztypes.enums import ErrorCode
from onefuzztypes.models import Error from onefuzztypes.models import Error
from onefuzztypes.primitives import Region
from .creds import get_base_resource_group from .creds import get_base_resource_group
from .network import Network
from .network_mgmt_client import get_network_client from .network_mgmt_client import get_network_client
from .subnet import create_virtual_network, get_subnet_id
from .vmss import get_instance_id from .vmss import get_instance_id
@ -63,12 +64,12 @@ def delete_ip(resource_group: str, name: str) -> Any:
return network_client.public_ip_addresses.begin_delete(resource_group, name) return network_client.public_ip_addresses.begin_delete(resource_group, name)
def create_ip(resource_group: str, name: str, location: str) -> Any: def create_ip(resource_group: str, name: str, region: Region) -> Any:
logging.info("creating ip for %s:%s in %s", resource_group, name, location) logging.info("creating ip for %s:%s in %s", resource_group, name, region)
network_client = get_network_client() network_client = get_network_client()
params: Dict[str, Union[str, Dict[str, str]]] = { params: Dict[str, Union[str, Dict[str, str]]] = {
"location": location, "location": region,
"public_ip_allocation_method": "Dynamic", "public_ip_allocation_method": "Dynamic",
} }
if "ONEFUZZ_OWNER" in os.environ: if "ONEFUZZ_OWNER" in os.environ:
@ -93,21 +94,24 @@ def delete_nic(resource_group: str, name: str) -> Optional[Any]:
return network_client.network_interfaces.begin_delete(resource_group, name) return network_client.network_interfaces.begin_delete(resource_group, name)
def create_public_nic(resource_group: str, name: str, location: str) -> Optional[Error]: def create_public_nic(
logging.info("creating nic for %s:%s in %s", resource_group, name, location) resource_group: str, name: str, region: Region
) -> Optional[Error]:
logging.info("creating nic for %s:%s in %s", resource_group, name, region)
network_client = get_network_client() network = Network(region)
subnet_id = get_subnet_id(resource_group, location) subnet_id = network.get_id()
if not subnet_id: if subnet_id is None:
return create_virtual_network(resource_group, location, location) network.create()
return None
ip = get_ip(resource_group, name) ip = get_ip(resource_group, name)
if not ip: if not ip:
create_ip(resource_group, name, location) create_ip(resource_group, name, region)
return None return None
params = { params = {
"location": location, "location": region,
"ip_configurations": [ "ip_configurations": [
{ {
"name": "myIPConfig", "name": "myIPConfig",
@ -119,6 +123,7 @@ def create_public_nic(resource_group: str, name: str, location: str) -> Optional
if "ONEFUZZ_OWNER" in os.environ: if "ONEFUZZ_OWNER" in os.environ:
params["tags"] = {"OWNER": os.environ["ONEFUZZ_OWNER"]} params["tags"] = {"OWNER": os.environ["ONEFUZZ_OWNER"]}
network_client = get_network_client()
try: try:
network_client.network_interfaces.begin_create_or_update( network_client.network_interfaces.begin_create_or_update(
resource_group, name, params resource_group, name, params

View File

@ -4,43 +4,68 @@
# Licensed under the MIT License. # Licensed under the MIT License.
import logging import logging
import uuid
from typing import Optional, Union from typing import Optional, Union
from msrestazure.azure_exceptions import CloudError from msrestazure.azure_exceptions import CloudError
from onefuzztypes.enums import ErrorCode from onefuzztypes.enums import ErrorCode
from onefuzztypes.models import Error from onefuzztypes.models import Error, NetworkConfig
from onefuzztypes.primitives import Region from onefuzztypes.primitives import Region
from ..config import InstanceConfig
from .creds import get_base_resource_group from .creds import get_base_resource_group
from .subnet import create_virtual_network, delete_subnet, get_subnet_id from .subnet import create_virtual_network, get_subnet_id
# This was generated randomly and should be preserved moving forwards
NETWORK_GUID_NAMESPACE = uuid.UUID("372977ad-b533-416a-b1b4-f770898e0b11")
class Network: class Network:
def __init__(self, region: Region): def __init__(self, region: Region):
self.group = get_base_resource_group() self.group = get_base_resource_group()
self.region = region self.region = region
self.network_config = InstanceConfig.fetch().network_config
# Network names will be calculated from the address_space/subnet
# *except* if they are the original values. This allows backwards
# compatibility to existing configs if you don't change the network
# configs.
if (
self.network_config.address_space
== NetworkConfig.__fields__["address_space"].default
and self.network_config.subnet == NetworkConfig.__fields__["subnet"].default
):
self.name: str = self.region
else:
network_id = uuid.uuid5(
NETWORK_GUID_NAMESPACE,
"|".join(
[self.network_config.address_space, self.network_config.subnet]
),
)
self.name = f"{self.region}-{network_id}"
def exists(self) -> bool: def exists(self) -> bool:
return self.get_id() is not None return self.get_id() is not None
def get_id(self) -> Optional[str]: def get_id(self) -> Optional[str]:
return get_subnet_id(self.group, self.region) return get_subnet_id(self.group, self.name, self.name)
def create(self) -> Union[None, Error]: def create(self) -> Union[None, Error]:
if not self.exists(): if not self.exists():
result = create_virtual_network(self.group, self.region, self.region) result = create_virtual_network(
self.group, self.name, self.region, self.network_config
)
if isinstance(result, CloudError): if isinstance(result, CloudError):
error = Error( error = Error(
code=ErrorCode.UNABLE_TO_CREATE_NETWORK, errors=[result.message] code=ErrorCode.UNABLE_TO_CREATE_NETWORK, errors=[result.message]
) )
logging.error( logging.error(
"network creation failed: %s- %s", "network creation failed: %s:%s- %s",
self.name,
self.region, self.region,
error, error,
) )
return error return error
return None return None
def delete(self) -> None:
delete_subnet(self.group, self.region)

View File

@ -10,21 +10,23 @@ from typing import Any, Optional, Union, cast
from azure.core.exceptions import ResourceNotFoundError from azure.core.exceptions import ResourceNotFoundError
from msrestazure.azure_exceptions import CloudError from msrestazure.azure_exceptions import CloudError
from onefuzztypes.enums import ErrorCode from onefuzztypes.enums import ErrorCode
from onefuzztypes.models import Error from onefuzztypes.models import Error, NetworkConfig
from onefuzztypes.primitives import Region
from .network_mgmt_client import get_network_client from .network_mgmt_client import get_network_client
def get_subnet_id(resource_group: str, name: str) -> Optional[str]: def get_subnet_id(resource_group: str, name: str, subnet_name: str) -> Optional[str]:
network_client = get_network_client() network_client = get_network_client()
try: try:
subnet = network_client.subnets.get(resource_group, name, name) subnet = network_client.subnets.get(resource_group, name, subnet_name)
return cast(str, subnet.id) return cast(str, subnet.id)
except (CloudError, ResourceNotFoundError): except (CloudError, ResourceNotFoundError):
logging.info( logging.info(
"subnet missing: resource group: %s name: %s", "subnet missing: resource group:%s name:%s subnet_name:%s",
resource_group, resource_group,
name, name,
subnet_name,
) )
return None return None
@ -43,20 +45,23 @@ def delete_subnet(resource_group: str, name: str) -> Union[None, CloudError, Any
def create_virtual_network( def create_virtual_network(
resource_group: str, name: str, location: str resource_group: str,
name: str,
region: Region,
network_config: NetworkConfig,
) -> Optional[Error]: ) -> Optional[Error]:
logging.info( logging.info(
"creating subnet - resource group: %s name: %s location: %s", "creating subnet - resource group:%s name:%s region:%s",
resource_group, resource_group,
name, name,
location, region,
) )
network_client = get_network_client() network_client = get_network_client()
params = { params = {
"location": location, "location": region,
"address_space": {"address_prefixes": ["10.0.0.0/8"]}, "address_space": {"address_prefixes": [network_config.address_space]},
"subnets": [{"name": name, "address_prefix": "10.0.0.0/16"}], "subnets": [{"name": name, "address_prefix": network_config.subnet}],
} }
if "ONEFUZZ_OWNER" in os.environ: if "ONEFUZZ_OWNER" in os.environ:
params["tags"] = {"OWNER": os.environ["ONEFUZZ_OWNER"]} params["tags"] = {"OWNER": os.environ["ONEFUZZ_OWNER"]}

View File

@ -399,11 +399,11 @@ def create_vmss(
@cached(ttl=60) @cached(ttl=60)
@retry_on_auth_failure() @retry_on_auth_failure()
def list_available_skus(location: str) -> List[str]: def list_available_skus(region: Region) -> List[str]:
compute_client = get_compute_client() compute_client = get_compute_client()
skus: List[ResourceSku] = list( skus: List[ResourceSku] = list(
compute_client.resource_skus.list(filter="location eq '%s'" % location) compute_client.resource_skus.list(filter="location eq '%s'" % region)
) )
sku_names: List[str] = [] sku_names: List[str] = []
for sku in skus: for sku in skus:
@ -411,7 +411,7 @@ def list_available_skus(location: str) -> List[str]:
if sku.restrictions is not None: if sku.restrictions is not None:
for restriction in sku.restrictions: for restriction in sku.restrictions:
if restriction.type == ResourceSkuRestrictionsType.location and ( if restriction.type == ResourceSkuRestrictionsType.location and (
location.upper() in [v.upper() for v in restriction.values] region.upper() in [v.upper() for v in restriction.values]
): ):
available = False available = False
break break

View File

@ -92,8 +92,8 @@ def not_ok(
) )
def redirect(location: str) -> HttpResponse: def redirect(url: str) -> HttpResponse:
return HttpResponse(status_code=302, headers={"Location": location}) return HttpResponse(status_code=302, headers={"Location": url})
def convert_error(err: ValidationError) -> Error: def convert_error(err: ValidationError) -> Error:

View File

@ -794,6 +794,11 @@ class Task(BaseModel):
user_info: Optional[UserInfo] user_info: Optional[UserInfo]
class NetworkConfig(BaseModel):
address_space: str = Field(default="10.0.0.0/8")
subnet: str = Field(default="10.0.0.0/16")
class KeyvaultExtensionConfig(BaseModel): class KeyvaultExtensionConfig(BaseModel):
keyvault_name: str keyvault_name: str
cert_name: str cert_name: str
@ -835,9 +840,8 @@ class InstanceConfig(BaseModel):
allow_pool_management: bool = Field(default=True) allow_pool_management: bool = Field(default=True)
allowed_aad_tenants: List[UUID] allowed_aad_tenants: List[UUID]
network_config: NetworkConfig = Field(default_factory=NetworkConfig)
extensions: Optional[AzureVmExtensionConfig] extensions: Optional[AzureVmExtensionConfig]
proxy_vm_sku: str = Field(default="Standard_B2s") proxy_vm_sku: str = Field(default="Standard_B2s")
def update(self, config: "InstanceConfig") -> None: def update(self, config: "InstanceConfig") -> None: