|
| 1 | +#!/usr/bin/env python3 |
| 2 | +### |
| 3 | +# CLOUDERA CDP Control (cdpctl) |
| 4 | +# |
| 5 | +# (C) Cloudera, Inc. 2021-2021 |
| 6 | +# All rights reserved. |
| 7 | +# |
| 8 | +# Applicable Open Source License: GNU AFFERO GENERAL PUBLIC LICENSE |
| 9 | +# |
| 10 | +# NOTE: Cloudera open source products are modular software products |
| 11 | +# made up of hundreds of individual components, each of which was |
| 12 | +# individually copyrighted. Each Cloudera open source product is a |
| 13 | +# collective work under U.S. Copyright Law. Your license to use the |
| 14 | +# collective work is as provided in your written agreement with |
| 15 | +# Cloudera. Used apart from the collective work, this file is |
| 16 | +# licensed for your use pursuant to the open source license |
| 17 | +# identified above. |
| 18 | +# |
| 19 | +# This code is provided to you pursuant a written agreement with |
| 20 | +# (i) Cloudera, Inc. or (ii) a third-party authorized to distribute |
| 21 | +# this code. If you do not have a written agreement with Cloudera nor |
| 22 | +# with an authorized and properly licensed third party, you do not |
| 23 | +# have any rights to access nor to use this code. |
| 24 | +# |
| 25 | +# Absent a written agreement with Cloudera, Inc. (“Cloudera”) to the |
| 26 | +# contrary, A) CLOUDERA PROVIDES THIS CODE TO YOU WITHOUT WARRANTIES OF ANY |
| 27 | +# KIND; (B) CLOUDERA DISCLAIMS ANY AND ALL EXPRESS AND IMPLIED |
| 28 | +# WARRANTIES WITH RESPECT TO THIS CODE, INCLUDING BUT NOT LIMITED TO |
| 29 | +# IMPLIED WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND |
| 30 | +# FITNESS FOR A PARTICULAR PURPOSE; (C) CLOUDERA IS NOT LIABLE TO YOU, |
| 31 | +# AND WILL NOT DEFEND, INDEMNIFY, NOR HOLD YOU HARMLESS FOR ANY CLAIMS |
| 32 | +# ARISING FROM OR RELATED TO THE CODE; AND (D)WITH RESPECT TO YOUR EXERCISE |
| 33 | +# OF ANY RIGHTS GRANTED TO YOU FOR THE CODE, CLOUDERA IS NOT LIABLE FOR ANY |
| 34 | +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, PUNITIVE OR |
| 35 | +# CONSEQUENTIAL DAMAGES INCLUDING, BUT NOT LIMITED TO, DAMAGES |
| 36 | +# RELATED TO LOST REVENUE, LOST PROFITS, LOSS OF INCOME, LOSS OF |
| 37 | +# BUSINESS ADVANTAGE OR UNAVAILABILITY, OR LOSS OR CORRUPTION OF |
| 38 | +# DATA. |
| 39 | +# |
| 40 | +# Source File Name: validate_azure_cross_account_identity.py |
| 41 | +### |
| 42 | +"""Validation of Azure Cross Account Identity.""" |
| 43 | +# "Cross Account Identity" is equivalent to "Cloudera Management Console Operator Identity" # noqa: D401,E501 |
| 44 | +# Customer facing text changed to "Cloudera Management Console Operator Identity" to |
| 45 | +# match the documentation |
| 46 | + |
| 47 | +from typing import Any, Dict, List |
| 48 | + |
| 49 | +import pytest |
| 50 | +from azure.mgmt.authorization import AuthorizationManagementClient |
| 51 | +from azure.mgmt.resource import ResourceManagementClient |
| 52 | + |
| 53 | +from cdpctl.validation import fail, get_config_value |
| 54 | +from cdpctl.validation.azure_utils import ( |
| 55 | + check_for_actions, |
| 56 | + get_client, |
| 57 | + get_resource_group_scope, |
| 58 | + get_role_assignments, |
| 59 | +) |
| 60 | +from cdpctl.validation.infra.issues import ( |
| 61 | + AZURE_IDENTITY_MISSING_ACTIONS_FOR_LOCATION, |
| 62 | + AZURE_IDENTITY_MISSING_DATA_ACTIONS_FOR_LOCATION, |
| 63 | +) |
| 64 | + |
| 65 | +_cross_account_info = {} |
| 66 | + |
| 67 | + |
| 68 | +@pytest.fixture(name="cross_account_info") |
| 69 | +def cross_account_info_fixture(): |
| 70 | + """Get the info for the Cross Account Identity.""" |
| 71 | + return _cross_account_info |
| 72 | + |
| 73 | + |
| 74 | +@pytest.fixture(autouse=True, name="resource_client") |
| 75 | +def resource_client_fixture(config: Dict[str, Any]) -> ResourceManagementClient: |
| 76 | + """Return an Azure Resource Client.""" |
| 77 | + return get_client("resource", config) |
| 78 | + |
| 79 | + |
| 80 | +@pytest.fixture(autouse=True, name="auth_client") |
| 81 | +def auth_client_fixture(config: Dict[str, Any]) -> AuthorizationManagementClient: |
| 82 | + """Return an Azure Auth Client.""" |
| 83 | + return get_client("auth", config) |
| 84 | + |
| 85 | + |
| 86 | +@pytest.mark.azure |
| 87 | +@pytest.mark.infra |
| 88 | +def azure_cross_account_identity_exists_validation( |
| 89 | + config: Dict[str, Any], |
| 90 | + auth_client: AuthorizationManagementClient, |
| 91 | + resource_client: ResourceManagementClient, |
| 92 | +) -> None: # pragma: no cover |
| 93 | + """Azure Cloudera Management Console Operator Identity exists.""" |
| 94 | + |
| 95 | + _cross_account_info["sub_id"] = get_config_value( |
| 96 | + config=config, key="infra:azure:subscription_id" |
| 97 | + ) |
| 98 | + _cross_account_info["rg_name"] = get_config_value( |
| 99 | + config=config, key="infra:azure:metagroup:name" |
| 100 | + ) |
| 101 | + _cross_account_info["name"] = get_config_value( |
| 102 | + config=config, key="env:azure:role:name:cross_account" |
| 103 | + ) |
| 104 | + |
| 105 | + _cross_account_info["assignments"] = get_role_assignments( |
| 106 | + auth_client=auth_client, |
| 107 | + resource_client=resource_client, |
| 108 | + identity_name=_cross_account_info["name"], |
| 109 | + subscription_id=_cross_account_info["sub_id"], |
| 110 | + resource_group=_cross_account_info["rg_name"], |
| 111 | + ) |
| 112 | + |
| 113 | + |
| 114 | +@pytest.mark.azure |
| 115 | +@pytest.mark.infra |
| 116 | +@pytest.mark.dependency(depends=["azure_cross_account_identity_exists_validation"]) |
| 117 | +def azure_cross_account_rg_actions_validation( |
| 118 | + auth_client: AuthorizationManagementClient, |
| 119 | + azure_cross_account_required_resource_group_actions: List[str], |
| 120 | + cross_account_info, |
| 121 | +) -> None: # pragma: no cover |
| 122 | + """Cloudera Management Console Operator Identity has the necessary actions on the resource group.""" # noqa: D401,E501 |
| 123 | + |
| 124 | + proper_scope = get_resource_group_scope( |
| 125 | + subscription_id=cross_account_info["sub_id"], |
| 126 | + resource_group=cross_account_info["rg_name"], |
| 127 | + ) |
| 128 | + |
| 129 | + missing_actions, _ = check_for_actions( |
| 130 | + auth_client=auth_client, |
| 131 | + role_assigments=cross_account_info["assignments"], |
| 132 | + proper_scope=proper_scope, |
| 133 | + required_actions=azure_cross_account_required_resource_group_actions, |
| 134 | + required_data_actions=[], |
| 135 | + ) |
| 136 | + |
| 137 | + if missing_actions: |
| 138 | + fail( |
| 139 | + AZURE_IDENTITY_MISSING_ACTIONS_FOR_LOCATION, |
| 140 | + subjects=[ |
| 141 | + cross_account_info["name"], |
| 142 | + proper_scope, # noqa: E501 |
| 143 | + ], |
| 144 | + resources=missing_actions, |
| 145 | + ) |
| 146 | + |
| 147 | + |
| 148 | +@pytest.mark.azure |
| 149 | +@pytest.mark.infra |
| 150 | +@pytest.mark.dependency(depends=["azure_cross_account_identity_exists_validation"]) |
| 151 | +def azure_cross_account_rg_data_actions_validation( |
| 152 | + auth_client: AuthorizationManagementClient, |
| 153 | + azure_cross_account_required_resource_group_data_actions: List[str], |
| 154 | + cross_account_info, |
| 155 | +) -> None: # pragma: no cover |
| 156 | + """Cloudera Management Console Operator Identity has the necessary data actions on the resource group.""" # noqa: D401,E501 |
| 157 | + |
| 158 | + proper_scope = get_resource_group_scope( |
| 159 | + subscription_id=cross_account_info["sub_id"], |
| 160 | + resource_group=cross_account_info["rg_name"], |
| 161 | + ) |
| 162 | + |
| 163 | + _, missing_data_actions = check_for_actions( |
| 164 | + auth_client=auth_client, |
| 165 | + role_assigments=cross_account_info["assignments"], |
| 166 | + proper_scope=proper_scope, |
| 167 | + required_actions=[], |
| 168 | + required_data_actions=azure_cross_account_required_resource_group_data_actions, |
| 169 | + ) |
| 170 | + if missing_data_actions: |
| 171 | + fail( |
| 172 | + AZURE_IDENTITY_MISSING_DATA_ACTIONS_FOR_LOCATION, |
| 173 | + subjects=[ |
| 174 | + cross_account_info["name"], |
| 175 | + proper_scope, # noqa: E501 |
| 176 | + ], |
| 177 | + resources=missing_data_actions, |
| 178 | + ) |
0 commit comments