Draft implementations of remote admin creation and root management group ownership.
This commit is contained in:
parent
7bf6b9addc
commit
144312863c
@ -1,4 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
|
import time
|
||||||
from secrets import token_urlsafe
|
from secrets import token_urlsafe
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
@ -76,6 +77,8 @@ class AzureCloudProvider(CloudProviderInterface):
|
|||||||
self.secret_key = config["AZURE_SECRET_KEY"]
|
self.secret_key = config["AZURE_SECRET_KEY"]
|
||||||
self.tenant_id = config["AZURE_TENANT_ID"]
|
self.tenant_id = config["AZURE_TENANT_ID"]
|
||||||
self.vault_url = config["AZURE_VAULT_URL"]
|
self.vault_url = config["AZURE_VAULT_URL"]
|
||||||
|
# self.ps_client_id = config["POWERSHELL_CLIENT_ID"]
|
||||||
|
self.ps_client_id = "1950a258-227b-4e31-a9cf-717495945fc2"
|
||||||
|
|
||||||
if azure_sdk_provider is None:
|
if azure_sdk_provider is None:
|
||||||
self.sdk = AzureSDKProvider()
|
self.sdk = AzureSDKProvider()
|
||||||
@ -479,21 +482,188 @@ class AzureCloudProvider(CloudProviderInterface):
|
|||||||
else:
|
else:
|
||||||
return self._error(result.json())
|
return self._error(result.json())
|
||||||
|
|
||||||
def create_remote_admin(self, creds, tenant_details):
|
def assign_root_mg_ownership(self, payload):
|
||||||
# create app/service principal within tenant, with name constructed from tenant details
|
import ipdb; ipdb.set_trace()
|
||||||
# assign principal global admin
|
# elevate
|
||||||
|
mgmt_token = self.get_tenant_admin_token(
|
||||||
# needs to call out to CLI with tenant owner username/password, prototyping for that underway
|
payload.tenant_id, self.sdk.cloud.endpoints.resource_manager
|
||||||
|
|
||||||
# return identifier and creds to consumer for storage
|
|
||||||
response = {"clientId": "string", "secretKey": "string", "tenantId": "string"}
|
|
||||||
return self._ok(
|
|
||||||
{
|
|
||||||
"client_id": response["clientId"],
|
|
||||||
"secret_key": response["secret_key"],
|
|
||||||
"tenant_id": response["tenantId"],
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
if mgmt_token is None:
|
||||||
|
raise AuthenticationException(
|
||||||
|
"Could not resolve management token for tenant admin"
|
||||||
|
)
|
||||||
|
|
||||||
|
auth_header = {
|
||||||
|
"Authorization": f"Bearer {mgmt_token}",
|
||||||
|
}
|
||||||
|
url = f"{self.sdk.cloud.endpoints.resource_manager}/providers/Microsoft.Authorization/elevateAccess?api-version=2016-07-01"
|
||||||
|
result = self.sdk.requests.post(url, headers=auth_header)
|
||||||
|
|
||||||
|
if not result.ok:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# ----------- NEXT STEP: Root MGMT Group Ownership (tenant admin) -------------
|
||||||
|
time.sleep(20)
|
||||||
|
# HARD CODED, MOVE TO CONFIG
|
||||||
|
ownerRoleId = '8e3af657-a8ff-443c-a75c-2fe8c4bcb635'
|
||||||
|
|
||||||
|
role_definition_id = f"/providers/Microsoft.Management/managementGroups/{payload.tenant_id}/providers/Microsoft.Authorization/roleDefinitions/{ownerRoleId}"
|
||||||
|
|
||||||
|
request_body = {
|
||||||
|
"properties": {
|
||||||
|
"roleDefinitionId": role_definition_id,
|
||||||
|
"principalId": payload.user_object_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auth_header = {
|
||||||
|
"Authorization": f"Bearer {mgmt_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.put(url, headers=auth_header, json=request_body)
|
||||||
|
|
||||||
|
if not response.ok:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# ----------- NEXT STEP: Root MGMT Group Ownership (remote admin SP) -------------
|
||||||
|
time.sleep(20)
|
||||||
|
# HARD CODED, MOVE TO CONFIG
|
||||||
|
ownerRoleId = '8e3af657-a8ff-443c-a75c-2fe8c4bcb635'
|
||||||
|
|
||||||
|
# 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/{ownerRoleId}"
|
||||||
|
|
||||||
|
request_body = {
|
||||||
|
"properties": {
|
||||||
|
"roleDefinitionId": role_definition_id,
|
||||||
|
"principalId": payload.admin_principal_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auth_header = {
|
||||||
|
"Authorization": f"Bearer {mgmt_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.put(url, headers=auth_header, json=request_body)
|
||||||
|
|
||||||
|
if not response.ok:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def create_remote_admin(self, payload):
|
||||||
|
import ipdb; ipdb.set_trace()
|
||||||
|
GRAPH_RESOURCE = "https://graph.microsoft.com"
|
||||||
|
graph_token = self.get_tenant_admin_token(payload.tenant_id, GRAPH_RESOURCE)
|
||||||
|
if graph_token is None:
|
||||||
|
raise AuthenticationException(
|
||||||
|
"Could not resolve graph token for tenant admin"
|
||||||
|
)
|
||||||
|
|
||||||
|
request_body = {"displayName": "ATAT Remote Admin"}
|
||||||
|
|
||||||
|
auth_header = {
|
||||||
|
"Authorization": f"Bearer {graph_token}",
|
||||||
|
}
|
||||||
|
|
||||||
|
url = f"{GRAPH_RESOURCE}/v1.0/applications"
|
||||||
|
|
||||||
|
response = self.sdk.requests.post(url, json=request_body, headers=auth_header)
|
||||||
|
|
||||||
|
res = response.json()
|
||||||
|
result1 = {}
|
||||||
|
if response.ok:
|
||||||
|
result1 = {"app_id": res.get("appId"), "object_id": res.get("id")}
|
||||||
|
|
||||||
|
# ---- SEPARATE STEP (Create associated Service Principal) ----------
|
||||||
|
time.sleep(20)
|
||||||
|
|
||||||
|
request_body = {"appId": result1.get("app_id")}
|
||||||
|
|
||||||
|
auth_header = {
|
||||||
|
"Authorization": f"Bearer {graph_token}",
|
||||||
|
}
|
||||||
|
|
||||||
|
url = f"{GRAPH_RESOURCE}/beta/servicePrincipals"
|
||||||
|
|
||||||
|
response = self.sdk.requests.post(url, json=request_body, headers=auth_header)
|
||||||
|
|
||||||
|
res = response.json()
|
||||||
|
result2 = {}
|
||||||
|
if response.ok:
|
||||||
|
result2 = {"sp_id": res.get("id")}
|
||||||
|
|
||||||
|
# ---- SEPARATE STEP - Generate Creds (Client Secret)----------
|
||||||
|
time.sleep(20)
|
||||||
|
|
||||||
|
request_body = {
|
||||||
|
"passwordCredentials": [{"displayName": "ATAT Generated Password"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
auth_header = {
|
||||||
|
"Authorization": f"Bearer {graph_token}",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Uses OBJECT_ID of App Registration
|
||||||
|
url = (
|
||||||
|
f"{GRAPH_RESOURCE}/v1.0/applications/{result1.get('object_id')}/addPassword"
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.sdk.requests.post(url, json=request_body, headers=auth_header)
|
||||||
|
result3 = {}
|
||||||
|
res = response.json()
|
||||||
|
if response.ok:
|
||||||
|
result3 = {"client_secret": res.get("secretText")}
|
||||||
|
|
||||||
|
# ---- SEPARATE STEP - Source Global Admin Role----------
|
||||||
|
|
||||||
|
auth_header = {
|
||||||
|
"Authorization": f"Bearer {graph_token}",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Uses OBJECT_ID of App Registration
|
||||||
|
url = f"{GRAPH_RESOURCE}/beta/roleManagement/directory/roleDefinitions"
|
||||||
|
|
||||||
|
response = self.sdk.requests.get(url, headers=auth_header)
|
||||||
|
|
||||||
|
result = response.json()
|
||||||
|
roleList = result.get("value")
|
||||||
|
|
||||||
|
admin_role_id = "794bb258-3e31-42ff-9ee4-731a72f62851" # May be hard coded? use for fall back
|
||||||
|
for role in roleList:
|
||||||
|
if role.get("displayName") == "Company Administrator":
|
||||||
|
admin_role_id = role.get("id")
|
||||||
|
break
|
||||||
|
|
||||||
|
# ---- SEPARATE STEP - Source Global Admin Role----------
|
||||||
|
time.sleep(20)
|
||||||
|
|
||||||
|
request_body = {
|
||||||
|
"principalId": result2.get("sp_id"),
|
||||||
|
"roleDefinitionId": admin_role_id,
|
||||||
|
"resourceScope": "/",
|
||||||
|
}
|
||||||
|
|
||||||
|
auth_header = {
|
||||||
|
"Authorization": f"Bearer {graph_token}",
|
||||||
|
}
|
||||||
|
|
||||||
|
url = f"{GRAPH_RESOURCE}/beta/roleManagement/directory/roleAssignments"
|
||||||
|
|
||||||
|
response = self.sdk.requests.post(url, headers=auth_header, json=request_body)
|
||||||
|
|
||||||
|
if response.ok:
|
||||||
|
return (result1, result2, result3)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def force_tenant_admin_pw_update(self, creds, tenant_owner_id):
|
def force_tenant_admin_pw_update(self, creds, tenant_owner_id):
|
||||||
# use creds to update to force password recovery?
|
# use creds to update to force password recovery?
|
||||||
@ -563,9 +733,21 @@ class AzureCloudProvider(CloudProviderInterface):
|
|||||||
if sub_id_match:
|
if sub_id_match:
|
||||||
return sub_id_match.group(1)
|
return sub_id_match.group(1)
|
||||||
|
|
||||||
def get_tenant_principal_token(self, tenant_id):
|
def get_tenant_admin_token(self, tenant_id, resource):
|
||||||
creds = self.get_secret(tenant_id)
|
creds = self.get_secret(tenant_id)
|
||||||
return self._get_sp_token(creds)
|
return self._get_up_token_for_resource(
|
||||||
|
creds.get("admin_username"), creds.get("admin_password"), tenant_id, resource
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_tenant_principal_token(self, tenant_id, resource):
|
||||||
|
# creds = self.get_secret(tenant_id)
|
||||||
|
# return self._get_up_token_for_resource(
|
||||||
|
# creds.get("admin_username"),
|
||||||
|
# creds.get("admin_password"),
|
||||||
|
# tenat_id,
|
||||||
|
# resource
|
||||||
|
# )
|
||||||
|
pass
|
||||||
|
|
||||||
def get_root_provisioning_token(self):
|
def get_root_provisioning_token(self):
|
||||||
return self._get_sp_token(self._root_creds)
|
return self._get_sp_token(self._root_creds)
|
||||||
@ -586,6 +768,19 @@ class AzureCloudProvider(CloudProviderInterface):
|
|||||||
|
|
||||||
return token_response.get("accessToken", None)
|
return token_response.get("accessToken", None)
|
||||||
|
|
||||||
|
def _get_up_token_for_resource(self, username, password, tenant_id, resource):
|
||||||
|
|
||||||
|
context = self.sdk.adal.AuthenticationContext(
|
||||||
|
f"{self.sdk.cloud.endpoints.active_directory}/{tenant_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO: handle failure states here
|
||||||
|
token_response = context.acquire_token_with_username_password(
|
||||||
|
resource, username, password, self.ps_client_id
|
||||||
|
)
|
||||||
|
|
||||||
|
return token_response.get("accessToken", None)
|
||||||
|
|
||||||
def _get_credential_obj(self, creds, resource=None):
|
def _get_credential_obj(self, creds, resource=None):
|
||||||
return self.sdk.credentials.ServicePrincipalCredentials(
|
return self.sdk.credentials.ServicePrincipalCredentials(
|
||||||
client_id=creds.get("client_id"),
|
client_id=creds.get("client_id"),
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from unittest.mock import Mock
|
from unittest.mock import Mock, patch
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from tests.factories import ApplicationFactory, EnvironmentFactory
|
from tests.factories import ApplicationFactory, EnvironmentFactory
|
||||||
@ -6,6 +6,7 @@ from tests.mock_azure import AUTH_CREDENTIALS, mock_azure
|
|||||||
|
|
||||||
from atst.domain.csp.cloud import AzureCloudProvider
|
from atst.domain.csp.cloud import AzureCloudProvider
|
||||||
from atst.domain.csp.cloud.models import (
|
from atst.domain.csp.cloud.models import (
|
||||||
|
BaseCSPPayload,
|
||||||
BillingInstructionCSPPayload,
|
BillingInstructionCSPPayload,
|
||||||
BillingInstructionCSPResult,
|
BillingInstructionCSPResult,
|
||||||
BillingProfileCreationCSPPayload,
|
BillingProfileCreationCSPPayload,
|
||||||
@ -407,3 +408,47 @@ def test_create_billing_instruction(mock_azure: AzureCloudProvider):
|
|||||||
body: BillingInstructionCSPResult = result.get("body")
|
body: BillingInstructionCSPResult = result.get("body")
|
||||||
assert body.reported_clin_name == "TO1:CLIN001"
|
assert body.reported_clin_name == "TO1:CLIN001"
|
||||||
|
|
||||||
|
|
||||||
|
def test_admin_principal_creation(mock_azure: AzureCloudProvider):
|
||||||
|
# Auth As Tenant Admin
|
||||||
|
# Create App Registration
|
||||||
|
# Create Service Principal
|
||||||
|
# Create App Registration Password Credential
|
||||||
|
# Lookup global admin role
|
||||||
|
# Assign global admin role to Service Principal
|
||||||
|
with patch.object(
|
||||||
|
AzureCloudProvider, "get_secret", wraps=mock_azure.get_secret
|
||||||
|
) as mock_get_secret:
|
||||||
|
mock_get_secret.return_value = {
|
||||||
|
"admin_username": "",
|
||||||
|
"admin_password": "",
|
||||||
|
}
|
||||||
|
payload = BaseCSPPayload(
|
||||||
|
**{"tenant_id": "6d2d2d6c-a6d6-41e1-8bb1-73d11475f8f4"}
|
||||||
|
)
|
||||||
|
|
||||||
|
result = mock_azure.create_remote_admin(payload)
|
||||||
|
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
|
||||||
|
def test_admin_mg_ownership(mock_azure: AzureCloudProvider):
|
||||||
|
with patch.object(
|
||||||
|
AzureCloudProvider, "get_secret", wraps=mock_azure.get_secret
|
||||||
|
) as mock_get_secret:
|
||||||
|
mock_get_secret.return_value = {
|
||||||
|
"admin_username": "",
|
||||||
|
"admin_password": "",
|
||||||
|
}
|
||||||
|
payload = TenantCSPResult(
|
||||||
|
**{
|
||||||
|
"user_id": "blach",
|
||||||
|
"tenant_id": "6d2d2d6c-a6d6-41e1-8bb1-73d11475f8f4",
|
||||||
|
"user_object_id": "971efe4d-1e80-4e39-b3b9-4e5c63ad446d",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
result = mock_azure.assign_root_mg_ownership(payload)
|
||||||
|
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
@ -63,13 +63,15 @@ def mock_policy():
|
|||||||
def mock_adal():
|
def mock_adal():
|
||||||
import adal
|
import adal
|
||||||
|
|
||||||
return Mock(spec=adal)
|
return adal
|
||||||
|
# return Mock(spec=adal)
|
||||||
|
|
||||||
|
|
||||||
def mock_requests():
|
def mock_requests():
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
return Mock(spec=requests)
|
# return Mock(spec=requests)
|
||||||
|
return requests
|
||||||
|
|
||||||
|
|
||||||
def mock_secrets():
|
def mock_secrets():
|
||||||
|
Loading…
x
Reference in New Issue
Block a user