From b754f1384fe7f272997e882702c64619b0ee60fb Mon Sep 17 00:00:00 2001 From: dandds Date: Tue, 4 Feb 2020 16:12:45 -0500 Subject: [PATCH 1/5] Include all Azure config in the INI file. Adds all the new config items to the INI file and adjusts some naming conventions so that these values sort together. Also adds defaults for some values where they're known. --- .secrets.baseline | 4 ++-- atst/domain/csp/cloud/azure_cloud_provider.py | 12 ++++++++---- config/base.ini | 18 ++++++++++++++---- tests/mock_azure.py | 6 ++++-- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index a233e4cf..657683b7 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "^.secrets.baseline$|^.*pgsslrootcert.yml$", "lines": null }, - "generated_at": "2020-01-27T19:24:43Z", + "generated_at": "2020-02-04T21:00:49Z", "plugins_used": [ { "base64_limit": 4.5, @@ -82,7 +82,7 @@ "hashed_secret": "afc848c316af1a89d49826c5ae9d00ed769415f3", "is_secret": false, "is_verified": false, - "line_number": 32, + "line_number": 43, "type": "Secret Keyword" } ], diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index d5ef5204..60b6d9a0 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -97,10 +97,14 @@ class AzureCloudProvider(CloudProviderInterface): self.secret_key = config["AZURE_SECRET_KEY"] self.tenant_id = config["AZURE_TENANT_ID"] self.vault_url = config["AZURE_VAULT_URL"] - self.ps_client_id = config["POWERSHELL_CLIENT_ID"] - self.owner_role_def_id = config["AZURE_OWNER_ROLE_DEF_ID"] + self.ps_client_id = config["AZURE_POWERSHELL_CLIENT_ID"] self.graph_resource = config["AZURE_GRAPH_RESOURCE"] self.default_aadp_qty = config["AZURE_AADP_QTY"] + self.roles = { + "owner": config["AZURE_ROLE_DEF_ID_OWNER"], + "contributor": config["AZURE_ROLE_DEF_ID_CONTRIBUTOR"], + "billing": config["AZURE_ROLE_DEF_ID_BILLING_READER"], + } if azure_sdk_provider is None: self.sdk = AzureSDKProvider() @@ -602,7 +606,7 @@ class AzureCloudProvider(CloudProviderInterface): def create_tenant_admin_ownership(self, payload: TenantAdminOwnershipCSPPayload): mgmt_token = self._get_elevated_management_token(payload.tenant_id) - role_definition_id = f"/providers/Microsoft.Management/managementGroups/{payload.tenant_id}/providers/Microsoft.Authorization/roleDefinitions/{self.owner_role_def_id}" + role_definition_id = f"/providers/Microsoft.Management/managementGroups/{payload.tenant_id}/providers/Microsoft.Authorization/roleDefinitions/{self.roles['owner']}" request_body = { "properties": { @@ -630,7 +634,7 @@ class AzureCloudProvider(CloudProviderInterface): mgmt_token = self._get_elevated_management_token(payload.tenant_id) # NOTE: the tenant_id is also the id of the root management group, once it is created - role_definition_id = f"/providers/Microsoft.Management/managementGroups/{payload.tenant_id}/providers/Microsoft.Authorization/roleDefinitions/{self.owner_role_def_id}" + role_definition_id = f"/providers/Microsoft.Management/managementGroups/{payload.tenant_id}/providers/Microsoft.Authorization/roleDefinitions/{self.roles['owner']}" request_body = { "properties": { diff --git a/config/base.ini b/config/base.ini index 1f4c732a..0105eea5 100644 --- a/config/base.ini +++ b/config/base.ini @@ -1,9 +1,19 @@ [default] ASSETS_URL +AZURE_AADP_QTY=5 AZURE_ACCOUNT_NAME -AZURE_STORAGE_KEY -AZURE_TO_BUCKET_NAME +AZURE_CLIENT_ID +AZURE_GRAPH_RESOURCE="https://graph.microsoft.com/" AZURE_POLICY_LOCATION=policies +AZURE_POWERSHELL_CLIENT_ID +AZURE_ROLE_DEF_ID_BILLING_READER="fa23ad8b-c56e-40d8-ac0c-ce449e1d2c64" +AZURE_ROLE_DEF_ID_CONTRIBUTOR="b24988ac-6180-42a0-ab88-20f7382dd24c" +AZURE_ROLE_DEF_ID_OWNER="8e3af657-a8ff-443c-a75c-2fe8c4bcb635" +AZURE_SECRET_KEY +AZURE_STORAGE_KEY +AZURE_TENANT_ID +AZURE_TO_BUCKET_NAME +AZURE_VAULT_URL BLOB_STORAGE_URL=http://localhost:8000/ CAC_URL = http://localhost:8000/login-redirect CA_CHAIN = ssl/server-certs/ca-chain.pem @@ -42,10 +52,10 @@ REDIS_TLS=False REDIS_USER SECRET_KEY = change_me_into_something_secret SERVER_NAME -SESSION_COOKIE_NAME=atat SESSION_COOKIE_DOMAIN -SESSION_KEY_PREFIX=session: +SESSION_COOKIE_NAME=atat SESSION_COOKIE_SECURE=false +SESSION_KEY_PREFIX=session: SESSION_TYPE = redis SESSION_USE_SIGNER = True SQLALCHEMY_ECHO = False diff --git a/tests/mock_azure.py b/tests/mock_azure.py index ce85a396..0062e386 100644 --- a/tests/mock_azure.py +++ b/tests/mock_azure.py @@ -9,8 +9,10 @@ AZURE_CONFIG = { "AZURE_TENANT_ID": "MOCK", "AZURE_POLICY_LOCATION": "policies", "AZURE_VAULT_URL": "http://vault", - "POWERSHELL_CLIENT_ID": "MOCK", - "AZURE_OWNER_ROLE_DEF_ID": "MOCK", + "AZURE_POWERSHELL_CLIENT_ID": "MOCK", + "AZURE_ROLE_DEF_ID_OWNER": "MOCK", + "AZURE_ROLE_DEF_ID_CONTRIBUTOR": "MOCK", + "AZURE_ROLE_DEF_ID_BILLING_READER": "MOCK", "AZURE_GRAPH_RESOURCE": "MOCK", "AZURE_AADP_QTY": 5, } From cdf6a469ed7d757a4351c6a18899f8ab75ebe893 Mon Sep 17 00:00:00 2001 From: dandds Date: Wed, 5 Feb 2020 09:20:22 -0500 Subject: [PATCH 2/5] WIP: create_user_role --- atst/domain/csp/cloud/azure_cloud_provider.py | 45 +++++++++++++++++++ atst/domain/csp/cloud/models.py | 17 +++++++ 2 files changed, 62 insertions(+) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 60b6d9a0..53e98b2a 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -50,6 +50,8 @@ from .models import ( TenantPrincipalOwnershipCSPResult, UserCSPPayload, UserCSPResult, + UserRoleCSPPayload, + UserRoleCSPResult, ) from .policy import AzurePolicyManager @@ -932,6 +934,49 @@ class AzureCloudProvider(CloudProviderInterface): f"Failed update user email: {response.json()}" ) + def create_user_role(self, payload: UserRoleCSPPayload): + # creds TBD + graph_token = "" + # graph_token = self._get_up_token_for_resource( + # username, + # password, + # payload.tenant_id, + # self.sdk.cloud.endpoints.resource_manager + # ) + # graph_token = self._get_tenant_principal_token( + # payload.tenant_id + # ) + if graph_token is None: + raise AuthenticationException( + "Could not resolve graph token for tenant admin" + ) + + role_definition_id = f"/providers/Microsoft.Management/managementGroups/{payload.management_group_id}/providers/Microsoft.Authorization/roleDefinitions/{self.roles[payload.role]}" + + request_body = { + "properties": { + "roleDefinitionId": role_definition_id, + "principalId": payload.user_object_id, + } + } + + auth_header = { + "Authorization": f"Bearer {graph_token}", + } + + assignment_guid = str(uuid4()) + + url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Management/managementGroups/{payload.tenant_id}/providers/Microsoft.Authorization/roleAssignments/{assignment_guid}?api-version=2015-07-01" + + response = self.sdk.requests.post(url, headers=auth_header, json=request_body) + + if response.ok: + return UserRoleCSPResult(**response.json()) + else: + raise UserProvisioningException( + f"Failed to create user role assignment: {response.json()}" + ) + def _extract_subscription_id(self, subscription_url): sub_id_match = SUBSCRIPTION_ID_REGEX.match(subscription_url) diff --git a/atst/domain/csp/cloud/models.py b/atst/domain/csp/cloud/models.py index 188c2cc7..16065cca 100644 --- a/atst/domain/csp/cloud/models.py +++ b/atst/domain/csp/cloud/models.py @@ -1,3 +1,4 @@ +from enum import Enum from secrets import token_urlsafe from typing import Dict, List, Optional from uuid import uuid4 @@ -499,3 +500,19 @@ class UserCSPPayload(BaseCSPPayload): class UserCSPResult(AliasModel): id: str + + +class UserRoles(str, Enum): + owner = "owner" + contributor = "contributor" + billing = "billing" + + +class UserRoleCSPPayload(BaseCSPPayload): + management_group_id: str + role: UserRoles + user_object_id: str + + +class UserRoleCSPResult(AliasModel): + id: str From 7c7dd088278d10719350616c13be3a67e6c4b223 Mon Sep 17 00:00:00 2001 From: dandds Date: Wed, 5 Feb 2020 13:48:59 -0500 Subject: [PATCH 3/5] Add environment_roles.cloud_id and update query for finding pending roles. --- ...df_change_to_environment_roles_cloud_id.py | 30 +++++++++++++++++ atst/domain/environment_roles.py | 10 ++++-- atst/models/environment_role.py | 2 +- tests/domain/test_environment_roles.py | 33 ++++++++++++++++++- 4 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 alembic/versions/418b52c1cedf_change_to_environment_roles_cloud_id.py diff --git a/alembic/versions/418b52c1cedf_change_to_environment_roles_cloud_id.py b/alembic/versions/418b52c1cedf_change_to_environment_roles_cloud_id.py new file mode 100644 index 00000000..7f92469e --- /dev/null +++ b/alembic/versions/418b52c1cedf_change_to_environment_roles_cloud_id.py @@ -0,0 +1,30 @@ +"""change to environment_roles.cloud_Id + +Revision ID: 418b52c1cedf +Revises: 17da2a475429 +Create Date: 2020-02-05 13:40:37.870183 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '418b52c1cedf' # pragma: allowlist secret +down_revision = '17da2a475429' # pragma: allowlist secret +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('environment_roles', sa.Column('cloud_id', sa.String(), nullable=True)) + op.drop_column('environment_roles', 'csp_user_id') + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('environment_roles', sa.Column('csp_user_id', sa.VARCHAR(), autoincrement=False, nullable=True)) + op.drop_column('environment_roles', 'cloud_id') + # ### end Alembic commands ### diff --git a/atst/domain/environment_roles.py b/atst/domain/environment_roles.py index ef8a4b8e..603c5f29 100644 --- a/atst/domain/environment_roles.py +++ b/atst/domain/environment_roles.py @@ -90,14 +90,18 @@ class EnvironmentRoles(object): ) @classmethod - def get_environment_roles_pending_creation(cls) -> List[UUID]: + def get_pending_creation(cls) -> List[UUID]: results = ( db.session.query(EnvironmentRole.id) .join(Environment) .join(ApplicationRole) .filter(Environment.deleted == False) - .filter(EnvironmentRole.status == EnvironmentRole.Status.PENDING) - .filter(ApplicationRole.status == ApplicationRoleStatus.ACTIVE) + .filter(EnvironmentRole.deleted == False) + .filter(ApplicationRole.deleted == False) + .filter(ApplicationRole.cloud_id != None) + .filter(ApplicationRole.status != ApplicationRoleStatus.DISABLED) + .filter(EnvironmentRole.status != EnvironmentRole.Status.DISABLED) + .filter(EnvironmentRole.cloud_id.is_(None)) .all() ) return [id_ for id_, in results] diff --git a/atst/models/environment_role.py b/atst/models/environment_role.py index 56fe78d4..871f39a1 100644 --- a/atst/models/environment_role.py +++ b/atst/models/environment_role.py @@ -36,7 +36,7 @@ class EnvironmentRole( ) application_role = relationship("ApplicationRole") - csp_user_id = Column(String()) + cloud_id = Column(String()) class Status(Enum): PENDING = "pending" diff --git a/tests/domain/test_environment_roles.py b/tests/domain/test_environment_roles.py index 216f072e..df1e50a9 100644 --- a/tests/domain/test_environment_roles.py +++ b/tests/domain/test_environment_roles.py @@ -1,7 +1,7 @@ import pytest from atst.domain.environment_roles import EnvironmentRoles -from atst.models.environment_role import EnvironmentRole +from atst.models import EnvironmentRole, ApplicationRoleStatus from tests.factories import * @@ -161,3 +161,34 @@ def test_for_user(application_role): assert len(env_roles) == 3 assert env_roles == [env_role_1, env_role_2, env_role_3] assert not rando_env_role in env_roles + + +class TestPendingCreation: + def test_pending_role(self): + appr = ApplicationRoleFactory.create(cloud_id="123") + envr = EnvironmentRoleFactory.create(application_role=appr) + assert EnvironmentRoles.get_pending_creation() == [envr.id] + + def test_deleted_role(self): + appr = ApplicationRoleFactory.create(cloud_id="123") + envr = EnvironmentRoleFactory.create(application_role=appr, deleted=True) + assert EnvironmentRoles.get_pending_creation() == [] + + def test_not_ready_role(self): + appr = ApplicationRoleFactory.create(cloud_id=None) + envr = EnvironmentRoleFactory.create(application_role=appr) + assert EnvironmentRoles.get_pending_creation() == [] + + def test_disabled_app_role(self): + appr = ApplicationRoleFactory.create( + cloud_id="123", status=ApplicationRoleStatus.DISABLED + ) + envr = EnvironmentRoleFactory.create(application_role=appr) + assert EnvironmentRoles.get_pending_creation() == [] + + def test_disabled_env_role(self): + appr = ApplicationRoleFactory.create(cloud_id="123") + envr = EnvironmentRoleFactory.create( + application_role=appr, status=EnvironmentRole.Status.DISABLED + ) + assert EnvironmentRoles.get_pending_creation() == [] From 4da68a1fabef2a44771facd4fef51f23e735d0a6 Mon Sep 17 00:00:00 2001 From: dandds Date: Wed, 5 Feb 2020 15:56:53 -0500 Subject: [PATCH 4/5] WIP: orchestration for env role creation --- atst/domain/csp/cloud/azure_cloud_provider.py | 14 +---- .../csp/cloud/cloud_provider_interface.py | 2 +- atst/domain/csp/cloud/models.py | 13 ++-- atst/domain/environment_roles.py | 4 +- atst/jobs.py | 59 ++++++++++++++++++- atst/queue.py | 4 ++ tests/domain/test_environment_roles.py | 6 +- tests/test_jobs.py | 41 ++++++++++++- 8 files changed, 116 insertions(+), 27 deletions(-) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 53e98b2a..2808dd88 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -935,17 +935,7 @@ class AzureCloudProvider(CloudProviderInterface): ) def create_user_role(self, payload: UserRoleCSPPayload): - # creds TBD - graph_token = "" - # graph_token = self._get_up_token_for_resource( - # username, - # password, - # payload.tenant_id, - # self.sdk.cloud.endpoints.resource_manager - # ) - # graph_token = self._get_tenant_principal_token( - # payload.tenant_id - # ) + graph_token = self._get_tenant_principal_token(payload.tenant_id) if graph_token is None: raise AuthenticationException( "Could not resolve graph token for tenant admin" @@ -968,7 +958,7 @@ class AzureCloudProvider(CloudProviderInterface): url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Management/managementGroups/{payload.tenant_id}/providers/Microsoft.Authorization/roleAssignments/{assignment_guid}?api-version=2015-07-01" - response = self.sdk.requests.post(url, headers=auth_header, json=request_body) + response = self.sdk.requests.put(url, headers=auth_header, json=request_body) if response.ok: return UserRoleCSPResult(**response.json()) diff --git a/atst/domain/csp/cloud/cloud_provider_interface.py b/atst/domain/csp/cloud/cloud_provider_interface.py index d173396a..7c262062 100644 --- a/atst/domain/csp/cloud/cloud_provider_interface.py +++ b/atst/domain/csp/cloud/cloud_provider_interface.py @@ -1,7 +1,7 @@ from typing import Dict -class CloudProviderInterface: +class CloudProviderInterface: # pragma: no cover def set_secret(self, secret_key: str, secret_value: str): raise NotImplementedError() diff --git a/atst/domain/csp/cloud/models.py b/atst/domain/csp/cloud/models.py index 16065cca..ec2a0798 100644 --- a/atst/domain/csp/cloud/models.py +++ b/atst/domain/csp/cloud/models.py @@ -502,15 +502,14 @@ class UserCSPResult(AliasModel): id: str -class UserRoles(str, Enum): - owner = "owner" - contributor = "contributor" - billing = "billing" - - class UserRoleCSPPayload(BaseCSPPayload): + class Roles(str, Enum): + owner = "owner" + contributor = "contributor" + billing = "billing" + management_group_id: str - role: UserRoles + role: Roles user_object_id: str diff --git a/atst/domain/environment_roles.py b/atst/domain/environment_roles.py index 603c5f29..9c503485 100644 --- a/atst/domain/environment_roles.py +++ b/atst/domain/environment_roles.py @@ -110,9 +110,9 @@ class EnvironmentRoles(object): def disable(cls, environment_role_id): environment_role = EnvironmentRoles.get_by_id(environment_role_id) - if environment_role.csp_user_id and not environment_role.environment.is_pending: + if environment_role.cloud_id and not environment_role.environment.is_pending: credentials = environment_role.environment.csp_credentials - app.csp.cloud.disable_user(credentials, environment_role.csp_user_id) + app.csp.cloud.disable_user(credentials, environment_role.cloud_id) environment_role.status = EnvironmentRole.Status.DISABLED db.session.add(environment_role) diff --git a/atst/jobs.py b/atst/jobs.py index 986b2004..988d4acc 100644 --- a/atst/jobs.py +++ b/atst/jobs.py @@ -8,11 +8,17 @@ from atst.domain.csp.cloud.exceptions import GeneralCSPException from atst.domain.csp.cloud import CloudProviderInterface from atst.domain.applications import Applications from atst.domain.environments import Environments +from atst.domain.environment_roles import EnvironmentRoles from atst.domain.portfolios import Portfolios from atst.domain.application_roles import ApplicationRoles from atst.models.utils import claim_for_update, claim_many_for_update +from atst.models import CSPRole from atst.utils.localization import translate -from atst.domain.csp.cloud.models import ApplicationCSPPayload, UserCSPPayload +from atst.domain.csp.cloud.models import ( + ApplicationCSPPayload, + UserCSPPayload, + UserRoleCSPPayload, +) class RecordFailure(celery.Task): @@ -138,6 +144,41 @@ def do_create_environment(csp: CloudProviderInterface, environment_id=None): ) +def do_create_environment_role(csp: CloudProviderInterface, environment_role_id=None): + env_role = EnvironmentRoles.get_by_id(environment_role_id) + + with claim_for_update(env_role) as env_role: + + if env_role.cloud_id is not None: + return + + env = env_role.environment + csp_details = env.application.portfolio.csp_data + app_role = env_role.application_role + + role = None + if env_role.role == CSPRole.ADMIN: + role = UserRoleCSPPayload.Roles.owner + elif env_role.role == CSPRole.BILLING_READ: + role = UserRoleCSPPayload.Roles.billing + elif env_role.role == CSPRole.CONTRIBUTOR: + role = UserRoleCSPPayload.Roles.contributor + + # resolve role + payload = UserRoleCSPPayload( + tenant_id=csp_details.get("tenant_id"), + management_group_id=env.cloud_id, + user_object_id=app_role.cloud_id, + role=role, + ) + result = csp.create_user_role(payload) + + env_role.cloud_id = result.id + db.session.add(env_role) + db.session.commit() + # TODO: should send notification email to the user, maybe with their portal login name + + def do_create_atat_admin_user(csp: CloudProviderInterface, environment_id=None): environment = Environments.get(environment_id) @@ -186,6 +227,16 @@ def create_user(self, application_role_ids=None): ) +@celery.task(bind=True, base=RecordFailure) +def create_environment_role(self, environment_role_id=None): + do_work( + do_create_environment_role, + self, + app.csp.cloud, + environment_role_id=environment_role_id, + ) + + @celery.task(bind=True, base=RecordFailure) def create_environment(self, environment_id=None): do_work(do_create_environment, self, app.csp.cloud, environment_id=environment_id) @@ -219,6 +270,12 @@ def dispatch_create_user(self): create_user.delay(application_role_ids=application_role_ids) +@celery.task(bind=True) +def dispatch_create_environment_role(self): + for environment_role_id in EnvironmentRoles.get_pending_creation(): + create_environment_role.delay(environment_role_id=environment_role_id) + + @celery.task(bind=True) def dispatch_create_environment(self): for environment_id in Environments.get_environments_pending_creation( diff --git a/atst/queue.py b/atst/queue.py index 3f97f88c..6db3f05a 100644 --- a/atst/queue.py +++ b/atst/queue.py @@ -27,6 +27,10 @@ def update_celery(celery, app): "task": "atst.jobs.dispatch_create_user", "schedule": 60, }, + "beat-dispatch_create_environment_role": { + "task": "atst.jobs.dispatch_create_environment_role", + "schedule": 60, + }, } class ContextTask(celery.Task): diff --git a/tests/domain/test_environment_roles.py b/tests/domain/test_environment_roles.py index df1e50a9..50151352 100644 --- a/tests/domain/test_environment_roles.py +++ b/tests/domain/test_environment_roles.py @@ -115,14 +115,14 @@ def test_disable_checks_env_role_provisioning_status(): cloud_id="cloud-id", root_user_info={"credentials": "credentials"} ) env_role1 = EnvironmentRoleFactory.create(environment=environment) - assert not env_role1.csp_user_id + assert not env_role1.cloud_id env_role1 = EnvironmentRoles.disable(env_role1.id) assert env_role1.disabled env_role2 = EnvironmentRoleFactory.create( - environment=environment, csp_user_id="123456" + environment=environment, cloud_id="123456" ) - assert env_role2.csp_user_id + assert env_role2.cloud_id env_role2 = EnvironmentRoles.disable(env_role2.id) assert env_role2.disabled diff --git a/tests/test_jobs.py b/tests/test_jobs.py index 54f4c5dc..3d3826de 100644 --- a/tests/test_jobs.py +++ b/tests/test_jobs.py @@ -1,9 +1,10 @@ import pendulum import pytest from uuid import uuid4 -from unittest.mock import Mock +from unittest.mock import Mock, MagicMock from atst.domain.csp.cloud import MockCloudProvider +from atst.domain.csp.cloud.models import UserRoleCSPResult from atst.domain.portfolios import Portfolios from atst.models import ApplicationRoleStatus @@ -12,12 +13,14 @@ from atst.jobs import ( dispatch_create_environment, dispatch_create_application, dispatch_create_user, + dispatch_create_environment_role, dispatch_create_atat_admin_user, dispatch_provision_portfolio, create_environment, do_create_user, do_provision_portfolio, do_create_environment, + do_create_environment_role, do_create_application, do_create_atat_admin_user, ) @@ -310,3 +313,39 @@ def test_provision_portfolio_create_tenant( # monkeypatch.setattr("atst.jobs.provision_portfolio", mock) # dispatch_provision_portfolio.run() # mock.delay.assert_called_once_with(portfolio_id=portfolio.id) + + +def test_dispatch_create_environment_role(monkeypatch): + portfolio = PortfolioFactory.create(csp_data={"tenant_id": "123"}) + app_role = ApplicationRoleFactory.create( + application=ApplicationFactory.create(portfolio=portfolio), + status=ApplicationRoleStatus.ACTIVE, + cloud_id="123", + ) + env_role = EnvironmentRoleFactory.create(application_role=app_role) + + mock = Mock() + monkeypatch.setattr("atst.jobs.create_environment_role", mock) + + dispatch_create_environment_role.run() + + mock.delay.assert_called_once_with(environment_role_id=env_role.id) + + +def test_create_environment_role(): + portfolio = PortfolioFactory.create(csp_data={"tenant_id": "123"}) + app = ApplicationFactory.create(portfolio=portfolio) + app_role = ApplicationRoleFactory.create( + application=app, status=ApplicationRoleStatus.ACTIVE, cloud_id="123", + ) + env = EnvironmentFactory.create(application=app, cloud_id="123") + env_role = EnvironmentRoleFactory.create( + environment=env, application_role=app_role, cloud_id=None + ) + + csp = Mock() + result = UserRoleCSPResult(id="a-cloud-id") + csp.create_user_role = MagicMock(return_value=result) + do_create_environment_role(csp, environment_role_id=env_role.id) + + assert env_role.cloud_id == "a-cloud-id" From e29ff3f91ba712250601b93732ed5885316e5136 Mon Sep 17 00:00:00 2001 From: dandds Date: Thu, 6 Feb 2020 06:35:12 -0500 Subject: [PATCH 5/5] fix scope for role creation --- atst/domain/csp/cloud/azure_cloud_provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atst/domain/csp/cloud/azure_cloud_provider.py b/atst/domain/csp/cloud/azure_cloud_provider.py index 4d610515..00a107ca 100644 --- a/atst/domain/csp/cloud/azure_cloud_provider.py +++ b/atst/domain/csp/cloud/azure_cloud_provider.py @@ -924,7 +924,7 @@ class AzureCloudProvider(CloudProviderInterface): assignment_guid = str(uuid4()) - url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Management/managementGroups/{payload.tenant_id}/providers/Microsoft.Authorization/roleAssignments/{assignment_guid}?api-version=2015-07-01" + url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Management/managementGroups/{payload.management_group_id}/providers/Microsoft.Authorization/roleAssignments/{assignment_guid}?api-version=2015-07-01" response = self.sdk.requests.put(url, headers=auth_header, json=request_body)