Restore implementations for policies and management group creation

These were accidentally stripped out during a rebase.
This commit is contained in:
tomdds 2020-01-22 14:39:30 -05:00
parent f5e4b603cb
commit 1b1a20cf52
3 changed files with 153 additions and 67 deletions

View File

@ -5,9 +5,11 @@ from uuid import uuid4
from pydantic import BaseModel, validator from pydantic import BaseModel, validator
from atst.models.user import User from atst.models.user import User
from atst.models.application import Application
from atst.models.environment import Environment from atst.models.environment import Environment
from atst.models.environment_role import EnvironmentRole from atst.models.environment_role import EnvironmentRole
from atst.utils import snake_to_camel from atst.utils import snake_to_camel
from .policy import AzurePolicyManager
class GeneralCSPException(Exception): class GeneralCSPException(Exception):
@ -718,11 +720,12 @@ SUBSCRIPTION_ID_REGEX = re.compile(
# This needs to be a fully pathed role definition identifier, not just a UUID # This needs to be a fully pathed role definition identifier, not just a UUID
REMOTE_ROOT_ROLE_DEF_ID = "/providers/Microsoft.Authorization/roleDefinitions/00000000-0000-4000-8000-000000000000" REMOTE_ROOT_ROLE_DEF_ID = "/providers/Microsoft.Authorization/roleDefinitions/00000000-0000-4000-8000-000000000000"
AZURE_MANAGEMENT_API = "https://management.azure.com"
class AzureSDKProvider(object): class AzureSDKProvider(object):
def __init__(self): def __init__(self):
from azure.mgmt import subscription, authorization from azure.mgmt import subscription, authorization, managementgroups
from azure.mgmt.resource import policy
import azure.graphrbac as graphrbac import azure.graphrbac as graphrbac
import azure.common.credentials as credentials import azure.common.credentials as credentials
import azure.identity as identity import azure.identity as identity
@ -733,6 +736,8 @@ class AzureSDKProvider(object):
import requests import requests
self.subscription = subscription self.subscription = subscription
self.policy = policy
self.managementgroups = managementgroups
self.authorization = authorization self.authorization = authorization
self.adal = adal self.adal = adal
self.graphrbac = graphrbac self.graphrbac = graphrbac
@ -758,6 +763,8 @@ class AzureCloudProvider(CloudProviderInterface):
else: else:
self.sdk = azure_sdk_provider self.sdk = azure_sdk_provider
self.policy_manager = AzurePolicyManager(config["AZURE_POLICY_LOCATION"])
def set_secret(secret_key, secret_value): def set_secret(secret_key, secret_value):
credential = self._get_client_secret_credential_obj() credential = self._get_client_secret_credential_obj()
secret_client = self.secrets.SecretClient( secret_client = self.secrets.SecretClient(
@ -777,42 +784,23 @@ class AzureCloudProvider(CloudProviderInterface):
def create_environment( def create_environment(
self, auth_credentials: Dict, user: User, environment: Environment self, auth_credentials: Dict, user: User, environment: Environment
): ):
# since this operation would only occur within a tenant, should we source the tenant
# via lookup from environment once we've created the portfolio csp data schema
# something like this:
# environment_tenant = environment.application.portfolio.csp_data.get('tenant_id', None)
# though we'd probably source the whole credentials for these calls from the portfolio csp
# data, as it would have to be where we store the creds for the at-at user within the portfolio tenant
# credentials = self._get_credential_obj(environment.application.portfolio.csp_data.get_creds())
credentials = self._get_credential_obj(self._root_creds) credentials = self._get_credential_obj(self._root_creds)
sub_client = self.sdk.subscription.SubscriptionClient(credentials)
display_name = f"{environment.application.name}_{environment.name}_{environment.id}" # proposed format display_name = f"{environment.application.name}_{environment.name}_{environment.id}" # proposed format
management_group_id = "?" # management group id chained from environment
parent_id = "?" # from environment.application
billing_profile_id = "?" # something chained from environment? management_group = self._create_management_group(
sku_id = AZURE_SKU_ID credentials, management_group_id, display_name, parent_id,
# we want to set AT-AT as an owner here
# we could potentially associate subscriptions with "management groups" per DOD component
body = self.sdk.subscription.models.ModernSubscriptionCreationParameters(
display_name,
billing_profile_id,
sku_id,
# owner=<AdPrincipal: for AT-AT user>
) )
# These 2 seem like something that might be worthwhile to allow tiebacks to return management_group
# TOs filed for the environment
billing_account_name = "?"
invoice_section_name = "?"
# We may also want to create billing sections in the enrollment account
sub_creation_operation = sub_client.subscription_factory.create_subscription(
billing_account_name, invoice_section_name, body
)
# the resulting object from this process is a link to the new subscription
# not a subscription model, so we'll have to unpack the ID
new_sub = sub_creation_operation.result()
subscription_id = self._extract_subscription_id(new_sub.subscription_link)
if subscription_id:
return subscription_id
else:
# troublesome error, subscription should exist at this point
# but we just don't have a valid ID
pass
def create_atat_admin_user( def create_atat_admin_user(
self, auth_credentials: Dict, csp_environment_id: str self, auth_credentials: Dict, csp_environment_id: str
@ -851,6 +839,125 @@ class AzureCloudProvider(CloudProviderInterface):
"role_name": role_assignment_id, "role_name": role_assignment_id,
} }
def _create_application(self, auth_credentials: Dict, application: Application):
management_group_name = str(uuid4()) # can be anything, not just uuid
display_name = application.name # Does this need to be unique?
credentials = self._get_credential_obj(auth_credentials)
parent_id = "?" # application.portfolio.csp_details.management_group_id
return self._create_management_group(
credentials, management_group_name, display_name, parent_id,
)
def _create_management_group(
self, credentials, management_group_id, display_name, parent_id=None,
):
mgmgt_group_client = self.sdk.managementgroups.ManagementGroupsAPI(credentials)
create_parent_grp_info = self.sdk.managementgroups.models.CreateParentGroupInfo(
id=parent_id
)
create_mgmt_grp_details = self.sdk.managementgroups.models.CreateManagementGroupDetails(
parent=create_parent_grp_info
)
mgmt_grp_create = self.sdk.managementgroups.models.CreateManagementGroupRequest(
name=management_group_id,
display_name=display_name,
details=create_mgmt_grp_details,
)
create_request = mgmgt_group_client.management_groups.create_or_update(
management_group_id, mgmt_grp_create
)
# result is a synchronous wait, might need to do a poll instead to handle first mgmt group create
# since we were told it could take 10+ minutes to complete, unless this handles that polling internally
return create_request.result()
def _create_subscription(
self,
credentials,
display_name,
billing_profile_id,
sku_id,
management_group_id,
billing_account_name,
invoice_section_name,
):
sub_client = self.sdk.subscription.SubscriptionClient(credentials)
billing_profile_id = "?" # where do we source this?
sku_id = AZURE_SKU_ID
# These 2 seem like something that might be worthwhile to allow tiebacks to
# TOs filed for the environment
billing_account_name = "?" # from TO?
invoice_section_name = "?" # from TO?
body = self.sdk.subscription.models.ModernSubscriptionCreationParameters(
display_name=display_name,
billing_profile_id=billing_profile_id,
sku_id=sku_id,
management_group_id=management_group_id,
)
# We may also want to create billing sections in the enrollment account
sub_creation_operation = sub_client.subscription_factory.create_subscription(
billing_account_name, invoice_section_name, body
)
# the resulting object from this process is a link to the new subscription
# not a subscription model, so we'll have to unpack the ID
new_sub = sub_creation_operation.result()
subscription_id = self._extract_subscription_id(new_sub.subscription_link)
if subscription_id:
return subscription_id
else:
# troublesome error, subscription should exist at this point
# but we just don't have a valid ID
pass
def _create_policy_definition(
self, credentials, subscription_id, management_group_id, properties,
):
"""
Requires credentials that have AZURE_MANAGEMENT_API
specified as the resource. The Service Principal
specified in the credentials must have the "Resource
Policy Contributor" role assigned with a scope at least
as high as the management group specified by
management_group_id.
Arguments:
credentials -- ServicePrincipalCredentials
subscription_id -- str, ID of the subscription (just the UUID, not the path)
management_group_id -- str, ID of the management group (just the UUID, not the path)
properties -- dictionary, the "properties" section of a valid Azure policy definition document
Returns:
azure.mgmt.resource.policy.[api version].models.PolicyDefinition: the PolicyDefinition object provided to Azure
Raises:
TBD
"""
# TODO: which subscription would this be?
client = self.sdk.policy.PolicyClient(credentials, subscription_id)
definition = client.policy_definitions.models.PolicyDefinition(
policy_type=properties.get("policyType"),
mode=properties.get("mode"),
display_name=properties.get("displayName"),
description=properties.get("description"),
policy_rule=properties.get("policyRule"),
parameters=properties.get("parameters"),
)
name = properties.get("displayName")
return client.policy_definitions.create_or_update_at_management_group(
policy_definition_name=name,
parameters=definition,
management_group_id=management_group_id,
)
def create_tenant(self, payload: TenantCSPPayload): def create_tenant(self, payload: TenantCSPPayload):
sp_token = self._get_sp_token(payload.creds) sp_token = self._get_sp_token(payload.creds)
if sp_token is None: if sp_token is None:
@ -1112,9 +1219,7 @@ class AzureCloudProvider(CloudProviderInterface):
return sub_id_match.group(1) return sub_id_match.group(1)
def _get_sp_token(self, creds): def _get_sp_token(self, creds):
home_tenant_id = creds.get( home_tenant_id = creds.get("home_tenant_id")
"home_tenant_id"
)
client_id = creds.get("client_id") client_id = creds.get("client_id")
secret_key = creds.get("secret_key") secret_key = creds.get("secret_key")
@ -1141,7 +1246,8 @@ class AzureCloudProvider(CloudProviderInterface):
resource=resource, resource=resource,
cloud_environment=self.sdk.cloud, cloud_environment=self.sdk.cloud,
) )
def _get_client_secret_credential_obj():
def _get_client_secret_credential_obj(self, creds):
return self.sdk.identity.ClientSecretCredential( return self.sdk.identity.ClientSecretCredential(
tenant_id=creds.get("tenant_id"), tenant_id=creds.get("tenant_id"),
client_id =creds.get("client_id"), client_id =creds.get("client_id"),

View File

@ -26,14 +26,13 @@ from tests.factories import EnvironmentFactory, ApplicationFactory
creds = { creds = {
"home_tenant_id": "", "home_tenant_id": "tenant_id",
"client_id": "", "client_id": "client_id",
"secret_key": "", "secret_key": "secret_key",
} }
BILLING_ACCOUNT_NAME = "52865e4c-52e8-5a6c-da6b-c58f0814f06f:7ea5de9d-b8ce-4901-b1c5-d864320c7b03_2019-05-31" BILLING_ACCOUNT_NAME = "52865e4c-52e8-5a6c-da6b-c58f0814f06f:7ea5de9d-b8ce-4901-b1c5-d864320c7b03_2019-05-31"
@pytest.mark.skip("Skipping legacy azure integration tests")
def test_create_subscription_succeeds(mock_azure: AzureCloudProvider): def test_create_subscription_succeeds(mock_azure: AzureCloudProvider):
environment = EnvironmentFactory.create() environment = EnvironmentFactory.create()
@ -74,7 +73,6 @@ def mock_management_group_create(mock_azure, spec_dict):
) )
@pytest.mark.skip("Skipping legacy azure integration tests")
def test_create_environment_succeeds(mock_azure: AzureCloudProvider): def test_create_environment_succeeds(mock_azure: AzureCloudProvider):
environment = EnvironmentFactory.create() environment = EnvironmentFactory.create()
@ -87,7 +85,6 @@ def test_create_environment_succeeds(mock_azure: AzureCloudProvider):
assert result.id == "Test Id" assert result.id == "Test Id"
@pytest.mark.skip("Skipping legacy azure integration tests")
def test_create_application_succeeds(mock_azure: AzureCloudProvider): def test_create_application_succeeds(mock_azure: AzureCloudProvider):
application = ApplicationFactory.create() application = ApplicationFactory.create()
@ -98,7 +95,6 @@ def test_create_application_succeeds(mock_azure: AzureCloudProvider):
assert result.id == "Test Id" assert result.id == "Test Id"
@pytest.mark.skip("Skipping legacy azure integration tests")
def test_create_atat_admin_user_succeeds(mock_azure: AzureCloudProvider): def test_create_atat_admin_user_succeeds(mock_azure: AzureCloudProvider):
environment_id = str(uuid4()) environment_id = str(uuid4())
@ -113,7 +109,6 @@ def test_create_atat_admin_user_succeeds(mock_azure: AzureCloudProvider):
assert result.get("csp_user_id") == csp_user_id assert result.get("csp_user_id") == csp_user_id
@pytest.mark.skip("Skipping legacy azure integration tests")
def test_create_policy_definition_succeeds(mock_azure: AzureCloudProvider): def test_create_policy_definition_succeeds(mock_azure: AzureCloudProvider):
subscription_id = str(uuid4()) subscription_id = str(uuid4())
management_group_id = str(uuid4()) management_group_id = str(uuid4())
@ -191,7 +186,7 @@ def test_create_billing_profile_creation(mock_azure: AzureCloudProvider):
country="US", country="US",
postal_code="19109", postal_code="19109",
), ),
creds={"username": "mock-cloud", "password": "shh"}, creds=creds,
tenant_id="60ff9d34-82bf-4f21-b565-308ef0533435", tenant_id="60ff9d34-82bf-4f21-b565-308ef0533435",
billing_profile_display_name="Test Billing Profile", billing_profile_display_name="Test Billing Profile",
billing_account_name=BILLING_ACCOUNT_NAME, billing_account_name=BILLING_ACCOUNT_NAME,
@ -242,11 +237,7 @@ def test_validate_billing_profile_creation(mock_azure: AzureCloudProvider):
payload = BillingProfileVerificationCSPPayload( payload = BillingProfileVerificationCSPPayload(
**dict( **dict(
creds={ creds=creds,
"username": "username",
"password": "password",
"tenant_id": "tenant_id",
},
billing_profile_verify_url="https://management.azure.com/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/operationResults/createBillingProfile_478d5706-71f9-4a8b-8d4e-2cbaca27a668?api-version=2019-10-01-preview", billing_profile_verify_url="https://management.azure.com/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/operationResults/createBillingProfile_478d5706-71f9-4a8b-8d4e-2cbaca27a668?api-version=2019-10-01-preview",
) )
) )
@ -285,11 +276,7 @@ def test_create_billing_profile_tenant_access(mock_azure: AzureCloudProvider):
payload = BillingProfileTenantAccessCSPPayload( payload = BillingProfileTenantAccessCSPPayload(
**dict( **dict(
creds={ creds=creds,
"username": "username",
"password": "password",
"tenant_id": "tenant_id",
},
tenant_id="60ff9d34-82bf-4f21-b565-308ef0533435", tenant_id="60ff9d34-82bf-4f21-b565-308ef0533435",
user_object_id="0a5f4926-e3ee-4f47-a6e3-8b0a30a40e3d", user_object_id="0a5f4926-e3ee-4f47-a6e3-8b0a30a40e3d",
billing_account_name="7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31", billing_account_name="7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31",
@ -321,11 +308,7 @@ def test_create_task_order_billing_creation(mock_azure: AzureCloudProvider):
payload = TaskOrderBillingCreationCSPPayload( payload = TaskOrderBillingCreationCSPPayload(
**dict( **dict(
creds={ creds=creds,
"username": "username",
"password": "password",
"tenant_id": "tenant_id",
},
billing_account_name="7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31", billing_account_name="7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31",
billing_profile_name="KQWI-W2SU-BG7-TGB", billing_profile_name="KQWI-W2SU-BG7-TGB",
) )
@ -385,11 +368,7 @@ def test_create_task_order_billing_verification(mock_azure):
payload = TaskOrderBillingVerificationCSPPayload( payload = TaskOrderBillingVerificationCSPPayload(
**dict( **dict(
creds={ creds=creds,
"username": "username",
"password": "password",
"tenant_id": "tenant_id",
},
task_order_billing_verify_url="https://management.azure.com/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/operationResults/createBillingProfile_478d5706-71f9-4a8b-8d4e-2cbaca27a668?api-version=2019-10-01-preview", task_order_billing_verify_url="https://management.azure.com/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/operationResults/createBillingProfile_478d5706-71f9-4a8b-8d4e-2cbaca27a668?api-version=2019-10-01-preview",
) )
) )
@ -424,7 +403,7 @@ def test_create_billing_instruction(mock_azure: AzureCloudProvider):
payload = BillingInstructionCSPPayload( payload = BillingInstructionCSPPayload(
**dict( **dict(
creds={}, creds=creds,
amount=1000.00, amount=1000.00,
start_date="2020/1/1", start_date="2020/1/1",
end_date="2020/3/1", end_date="2020/3/1",

View File

@ -72,6 +72,7 @@ class MockAzureSDK(object):
self.subscription = mock_subscription() self.subscription = mock_subscription()
self.authorization = mock_authorization() self.authorization = mock_authorization()
self.policy = mock_policy()
self.adal = mock_adal() self.adal = mock_adal()
self.managementgroups = mock_managementgroups() self.managementgroups = mock_managementgroups()
self.graphrbac = mock_graphrbac() self.graphrbac = mock_graphrbac()