diff --git a/Pipfile b/Pipfile index f1d852b9..06bd2675 100644 --- a/Pipfile +++ b/Pipfile @@ -35,6 +35,7 @@ azure-mgmt-resource = "*" transitions = "*" azure-mgmt-consumption = "*" adal = "*" +azure-identity = "*" [dev-packages] bandit = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 681fddb1..8a030e11 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "a127b88e6c64842786f1868cb93bb1cdc828aa78040ea8ba4079bb3de0316dab" + "sha256": "3760f0b1df1156211d671afa2eb417b7bf980aa33d2f74d390e8eed6a3ce8c8b" }, "pipfile-spec": 6, "requires": { @@ -45,6 +45,13 @@ ], "version": "==1.1.24" }, + "azure-core": { + "hashes": [ + "sha256:b8ccbd901d085048e4e3e72627b066923c5bd3780e4c43cf9cf9948aee9bdf9e", + "sha256:e2cd99f0c0aef12c168d498cb5bc47a3a45c8ab08112183e3ec97e4dcb33ceb9" + ], + "version": "==1.2.1" + }, "azure-graphrbac": { "hashes": [ "sha256:53e98ae2ca7c19b349e9e9bb1b6a824aeae8dcfcbe17190d20fe69c0f185b2e2", @@ -53,6 +60,14 @@ "index": "pypi", "version": "==0.61.1" }, + "azure-identity": { + "hashes": [ + "sha256:4ce65058461c277991763ed3f121efc6b9eb9c2edefb62c414dfa85c814690d3", + "sha256:b32acd1cdb6202bfe10d9a0858dc463d8960295da70ae18097eb3b85ab12cb91" + ], + "index": "pypi", + "version": "==1.2.0" + }, "azure-mgmt-authorization": { "hashes": [ "sha256:31e875a34ac2c5d6fefe77b4a8079a8b2bdbe9edb957e47e8b44222fb212d6a7", @@ -354,6 +369,20 @@ ], "version": "==8.1.0" }, + "msal": { + "hashes": [ + "sha256:c944b833bf686dfbc973e9affdef94b77e616cb52ab397e76cde82e26b8a3373", + "sha256:ecbe3f5ac77facad16abf08eb9d8562af3bc7184be5d4d90c9ef4db5bde26340" + ], + "version": "==1.0.0" + }, + "msal-extensions": { + "hashes": [ + "sha256:59e171a9a4baacdbf001c66915efeaef372fb424421f1a4397115a3ddd6205dc", + "sha256:c5a32b8e1dce1c67733dcdf8aa8bebcff5ab123e779ef7bc14e416bd0da90037" + ], + "version": "==0.1.3" + }, "msrest": { "hashes": [ "sha256:56b8b5b4556fb2a92cac640df267d560889bdc9e2921187772d4691d97bc4e8d", @@ -388,6 +417,13 @@ "index": "pypi", "version": "==2.0.5" }, + "portalocker": { + "hashes": [ + "sha256:6f57aabb25ba176462dc7c63b86c42ad6a9b5bd3d679a9d776d0536bfb803d54", + "sha256:dac62e53e5670cb40d2ee4cdc785e6b829665932c3ee75307ad677cf5f7d2e9f" + ], + "version": "==1.5.2" + }, "psycopg2-binary": { "hashes": [ "sha256:040234f8a4a8dfd692662a8308d78f63f31a97e1c42d2480e5e6810c48966a29", @@ -453,6 +489,9 @@ "version": "==1.3" }, "pyjwt": { + "extras": [ + "crypto" + ], "hashes": [ "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e", "sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96" @@ -538,10 +577,10 @@ }, "six": { "hashes": [ - "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", - "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" ], - "version": "==1.13.0" + "version": "==1.14.0" }, "sqlalchemy": { "hashes": [ @@ -618,10 +657,10 @@ }, "zipp": { "hashes": [ - "sha256:8dda78f06bd1674bd8720df8a50bb47b6e1233c503a4eed8e7810686bde37656", - "sha256:d38fbe01bbf7a3593a32bc35a9c4453c32bc42b98c377f9bff7e9f8da157786c" + "sha256:57147f6b0403b59f33fd357f169f860e031303415aeb7d04ede4839d23905ab8", + "sha256:7ae5ccaca427bafa9760ac3cd8f8c244bfc259794b5b6bb9db4dda2241575d09" ], - "version": "==1.0.0" + "version": "==2.0.0" } }, "develop": { @@ -632,14 +671,6 @@ ], "version": "==1.4.3" }, - "appnope": { - "hashes": [ - "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", - "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" - ], - "markers": "sys_platform == 'darwin'", - "version": "==0.1.0" - }, "argh": { "hashes": [ "sha256:a9b3aaa1904eeb78e32394cd46c6f37ac0fb4af6dc488daa58971bdc7d7fcaf3", @@ -1210,10 +1241,10 @@ }, "six": { "hashes": [ - "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", - "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" ], - "version": "==1.13.0" + "version": "==1.14.0" }, "smmap2": { "hashes": [ @@ -1328,10 +1359,10 @@ }, "zipp": { "hashes": [ - "sha256:8dda78f06bd1674bd8720df8a50bb47b6e1233c503a4eed8e7810686bde37656", - "sha256:d38fbe01bbf7a3593a32bc35a9c4453c32bc42b98c377f9bff7e9f8da157786c" + "sha256:57147f6b0403b59f33fd357f169f860e031303415aeb7d04ede4839d23905ab8", + "sha256:7ae5ccaca427bafa9760ac3cd8f8c244bfc259794b5b6bb9db4dda2241575d09" ], - "version": "==1.0.0" + "version": "==2.0.0" } } } diff --git a/atst/domain/csp/cloud.py b/atst/domain/csp/cloud.py index 87492f19..f14e468d 100644 --- a/atst/domain/csp/cloud.py +++ b/atst/domain/csp/cloud.py @@ -207,9 +207,10 @@ class TenantCSPResult(AliasModel): return { "tenant_admin_username": self.tenant_admin_username, "tenant_admin_password": self.tenant_admin_password, - "tenant_id": self.tenant_id + "tenant_id": self.tenant_id, } + class BillingProfileAddress(AliasModel): company_name: str address_line_1: str @@ -248,9 +249,7 @@ class BillingProfileCSPPayload(BaseCSPPayload): return v or [] class Config: - fields = { - "billing_profile_display_name": "displayName" - } + fields = {"billing_profile_display_name": "displayName"} class BillingProfileCreateCSPResult(AliasModel): @@ -258,7 +257,10 @@ class BillingProfileCreateCSPResult(AliasModel): retry_after: int class Config: - fields = {"billing_profile_validate_url": "Location", "retry_after": "Retry-After"} + fields = { + "billing_profile_validate_url": "Location", + "retry_after": "Retry-After", + } class BillingProfileVerifyCSPPayload(BaseCSPPayload): @@ -279,9 +281,7 @@ class BillingProfileProperties(AliasModel): invoice_sections: List[BillingInvoiceSection] class Config: - fields = { - "billing_profile_display_name": "displayName" - } + fields = {"billing_profile_display_name": "displayName"} class BillingProfileCSPResult(AliasModel): @@ -314,20 +314,27 @@ class BillingProfileTenantAccessCSPResult(AliasModel): "billing_role_assignment_name": "name", } + class TaskOrderBillingCSPPayload(BaseCSPPayload): billing_account_name: str billing_profile_name: str + class EnableTaskOrderBillingCSPResult(AliasModel): task_order_billing_validation_url: str retry_after: int class Config: - fields = {"task_order_billing_validation_url": "Location", "retry_after": "Retry-After"} + fields = { + "task_order_billing_validation_url": "Location", + "retry_after": "Retry-After", + } + class TaskOrderBillingCSPResult(BaseCSPPayload): task_order_billing_validation_url: str + class BillingProfileEnabledPlanDetails(AliasModel): enabled_azure_plans: List[Dict] @@ -344,6 +351,7 @@ class TaskOrderBillingCSPResult(AliasModel): "billing_profile_enabled_plan_details": "properties", } + class ReportCLINCSPPayload(BaseCSPPayload): amount: float start_date: str @@ -353,6 +361,7 @@ class ReportCLINCSPPayload(BaseCSPPayload): billing_account_name: str billing_profile_name: str + class ReportCLINCSPResult(AliasModel): reported_clin_name: str @@ -361,7 +370,16 @@ class ReportCLINCSPResult(AliasModel): "reported_clin_name": "name", } + class CloudProviderInterface: + + + def set_secret(secret_key: str, secret_value: str): + raise NotImplementedError() + + def get_secret(secret_key: str, secret_value: str): + raise NotImplementedError() + def root_creds(self) -> Dict: raise NotImplementedError() @@ -563,7 +581,7 @@ class MockCloudProvider(CloudProviderInterface): "user_id": response["userId"], "user_object_id": response["objectId"], "tenant_admin_username": "test", - "tenant_admin_password": "test" + "tenant_admin_password": "test", } def create_billing_profile(self, payload): @@ -608,33 +626,33 @@ class MockCloudProvider(CloudProviderInterface): response = {"id": "string"} # return {"billing_profile_id": response["id"]} return { - 'id': '/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/billingProfiles/KQWI-W2SU-BG7-TGB', - 'name': 'KQWI-W2SU-BG7-TGB', - 'properties': { - 'address': { - 'addressLine1': '123 S Broad Street, Suite 2400', - 'city': 'Philadelphia', - 'companyName': 'Promptworks', - 'country': 'US', - 'postalCode': '19109', - 'region': 'PA' + "id": "/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/billingProfiles/KQWI-W2SU-BG7-TGB", + "name": "KQWI-W2SU-BG7-TGB", + "properties": { + "address": { + "addressLine1": "123 S Broad Street, Suite 2400", + "city": "Philadelphia", + "companyName": "Promptworks", + "country": "US", + "postalCode": "19109", + "region": "PA", }, - 'currency': 'USD', - 'displayName': 'Test Billing Profile', - 'enabledAzurePlans': [], - 'hasReadAccess': True, - 'invoiceDay': 5, - 'invoiceEmailOptIn': False, - 'invoiceSections': [{ - 'id': '/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/billingProfiles/KQWI-W2SU-BG7-TGB/invoiceSections/CHCO-BAAR-PJA-TGB', - 'name': 'CHCO-BAAR-PJA-TGB', - 'properties': { - 'displayName': 'Test Billing Profile' - }, - 'type': 'Microsoft.Billing/billingAccounts/billingProfiles/invoiceSections' - }] + "currency": "USD", + "displayName": "Test Billing Profile", + "enabledAzurePlans": [], + "hasReadAccess": True, + "invoiceDay": 5, + "invoiceEmailOptIn": False, + "invoiceSections": [ + { + "id": "/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/billingProfiles/KQWI-W2SU-BG7-TGB/invoiceSections/CHCO-BAAR-PJA-TGB", + "name": "CHCO-BAAR-PJA-TGB", + "properties": {"displayName": "Test Billing Profile"}, + "type": "Microsoft.Billing/billingAccounts/billingProfiles/invoiceSections", + } + ], }, - 'type': 'Microsoft.Billing/billingAccounts/billingProfiles' + "type": "Microsoft.Billing/billingAccounts/billingProfiles", } def create_billing_profile_tenant_access(self, payload): @@ -651,9 +669,9 @@ class MockCloudProvider(CloudProviderInterface): "principalId": "0a5f4926-e3ee-4f47-a6e3-8b0a30a40e3d", "principalTenantId": "60ff9d34-82bf-4f21-b565-308ef0533435", "roleDefinitionId": "/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/billingProfiles/KQWI-W2SU-BG7-TGB/billingRoleDefinitions/40000000-aaaa-bbbb-cccc-100000000000", - "scope": "/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/billingProfiles/KQWI-W2SU-BG7-TGB" + "scope": "/providers/Microsoft.Billing/billingAccounts/7c89b735-b22b-55c0-ab5a-c624843e8bf6:de4416ce-acc6-44b1-8122-c87c4e903c91_2019-05-31/billingProfiles/KQWI-W2SU-BG7-TGB", }, - "type": "Microsoft.Billing/billingRoleAssignments" + "type": "Microsoft.Billing/billingRoleAssignments", } def create_or_update_user(self, auth_credentials, user_info, csp_role_id): @@ -735,6 +753,9 @@ class AzureSDKProvider(object): from azure.mgmt import subscription, authorization import azure.graphrbac as graphrbac import azure.common.credentials as credentials + import azure.identity as identity + from azure.keyvault import secrets import secrets + from msrestazure.azure_cloud import AZURE_PUBLIC_CLOUD import adal import requests @@ -744,6 +765,8 @@ class AzureSDKProvider(object): self.adal = adal self.graphrbac = graphrbac self.credentials = credentials + self.identity = identity + self.secrets = secrets self.requests = requests # may change to a JEDI cloud self.cloud = AZURE_PUBLIC_CLOUD @@ -756,12 +779,29 @@ class AzureCloudProvider(CloudProviderInterface): self.client_id = config["AZURE_CLIENT_ID"] self.secret_key = config["AZURE_SECRET_KEY"] self.tenant_id = config["AZURE_TENANT_ID"] + self.vault_url = config["AZURE_VAULT_URL"] if azure_sdk_provider is None: self.sdk = AzureSDKProvider() else: self.sdk = azure_sdk_provider + def set_secret(secret_key, secret_value): + credential = self._get_client_secret_credential_obj() + secret_client = self.secrets.SecretClient( + vault_url=self.vault_url, + credential=credential, + ) + return secret_client.set_secret(secret_key, secret_value) + + def get_secret(secret_key) + credential = self._get_client_secret_credential_obj() + secret_client = self.secrets.SecretClient( + vault_url=self.vault_url, + credential=credential, + ) + return secret_client.get_secret(secret_key).value + def create_environment( self, auth_credentials: Dict, user: User, environment: Environment ): @@ -856,7 +896,7 @@ class AzureCloudProvider(CloudProviderInterface): headers=create_tenant_headers, ) - print('create tenant result') + print("create tenant result") print(result.json()) if result.status_code == 200: @@ -907,7 +947,9 @@ class AzureCloudProvider(CloudProviderInterface): "Authorization": f"Bearer {sp_token}", } - result = self.sdk.requests.get(payload.billing_profile_validate_url, headers=auth_header) + result = self.sdk.requests.get( + payload.billing_profile_validate_url, headers=auth_header + ) if result.status_code == 202: # 202 has location/retry after headers @@ -917,7 +959,9 @@ class AzureCloudProvider(CloudProviderInterface): else: return self._error(result.json()) - def create_billing_profile_tenant_access(self, payload: BillingProfileTenantAccessCSPPayload): + def create_billing_profile_tenant_access( + self, payload: BillingProfileTenantAccessCSPPayload + ): sp_token = self._get_sp_token(payload.creds) request_body = { "properties": { @@ -945,11 +989,7 @@ class AzureCloudProvider(CloudProviderInterface): { "op": "replace", "path": "/enabledAzurePlans", - "value": [ - { - "skuId": "0001" - } - ] + "value": [{"skuId": "0001"}], } ] @@ -959,7 +999,9 @@ class AzureCloudProvider(CloudProviderInterface): url = f"https://management.azure.com/providers/Microsoft.Billing/billingAccounts/{payload.billing_account_name}/billingProfiles/{payload.billing_profile_name}?api-version=2019-10-01-preview" - result = self.sdk.requests.patch(url, headers=request_headers, json=request_body) + result = self.sdk.requests.patch( + url, headers=request_headers, json=request_body + ) if result.status_code == 202: # 202 has location/retry after headers @@ -969,7 +1011,7 @@ class AzureCloudProvider(CloudProviderInterface): else: return self._error(result.json()) - def validate_task_order_billing_enabled(self, payload: VerifyTaskOrderBillingCSPPayload): + def validate_task_order_billing_enabled(self, payload: TaskOrderBillingCSPPayload): sp_token = self._get_sp_token(payload.creds) if sp_token is None: raise AuthenticationException( @@ -980,7 +1022,9 @@ class AzureCloudProvider(CloudProviderInterface): "Authorization": f"Bearer {sp_token}", } - result = self.sdk.requests.get(payload.task_order_billing_validation_url, headers=auth_header) + result = self.sdk.requests.get( + payload.task_order_billing_validation_url, headers=auth_header + ) if result.status_code == 202: # 202 has location/retry after headers @@ -1001,7 +1045,7 @@ class AzureCloudProvider(CloudProviderInterface): "properties": { "amount": payload.amount, "startDate": payload.start_date, - "endDate": payload.end_date + "endDate": payload.end_date, } } @@ -1125,7 +1169,6 @@ class AzureCloudProvider(CloudProviderInterface): return token_response.get("accessToken", None) def _get_credential_obj(self, creds, resource=None): - return self.sdk.credentials.ServicePrincipalCredentials( client_id=creds.get("client_id"), secret=creds.get("secret_key"), @@ -1133,6 +1176,12 @@ class AzureCloudProvider(CloudProviderInterface): resource=resource, cloud_environment=self.sdk.cloud, ) + def _get_client_secret_credential_obj(): + return self.sdk.identity.ClientSecretCredential( + tenant_id=creds.get("tenant_id"), + client_id =creds.get("client_id"), + client_secret = creds.get("secret_key"), + ) def _make_tenant_admin_cred_obj(self, username, password): return self.sdk.credentials.UserPassCredentials(username, password)