|
| 1 | +""" |
| 2 | +Use the Nutanix v4 API SDKs to setup API key authentication |
| 3 | +Requires Prism Central 2024.3 or later and AOS 7.0 or later |
| 4 | +""" |
| 5 | + |
| 6 | +import getpass |
| 7 | +import argparse |
| 8 | +import sys |
| 9 | +import urllib3 |
| 10 | +import json |
| 11 | + |
| 12 | +import ntnx_vmm_py_client |
| 13 | +from ntnx_vmm_py_client import Configuration as VMMConfiguration |
| 14 | +from ntnx_vmm_py_client import ApiClient as VMMClient |
| 15 | + |
| 16 | +import ntnx_iam_py_client |
| 17 | +from ntnx_iam_py_client import Configuration as IAMConfiguration |
| 18 | +from ntnx_iam_py_client import ApiClient as IAMClient |
| 19 | +from ntnx_iam_py_client.rest import ApiException as IAMException |
| 20 | + |
| 21 | +from ntnx_iam_py_client import UsersApi, AuthorizationPoliciesApi |
| 22 | +from ntnx_iam_py_client import User, UserType, CreationType, UserStatusType |
| 23 | +from ntnx_iam_py_client import Key, KeyKind |
| 24 | +from ntnx_iam_py_client import AuthorizationPolicy, AuthorizationPolicyType |
| 25 | +from ntnx_iam_py_client import EntityFilter, IdentityFilter |
| 26 | + |
| 27 | +from tme.utils import Utils |
| 28 | + |
| 29 | + |
| 30 | +def main(): |
| 31 | + """ |
| 32 | + suppress warnings about insecure connections |
| 33 | + please consider the security implications before |
| 34 | + doing this in a production environment |
| 35 | + """ |
| 36 | + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) |
| 37 | + |
| 38 | + """ |
| 39 | + setup the command line parameters |
| 40 | + for this example only two parameters are required |
| 41 | + - the Prism Central IP address or FQDN |
| 42 | + - the Prism Central username; the script will prompt for the user's password |
| 43 | + so that it never needs to be stored in plain text |
| 44 | + """ |
| 45 | + parser = argparse.ArgumentParser() |
| 46 | + parser.add_argument("pc_ip", help="Prism Central IP address or FQDN") |
| 47 | + parser.add_argument("username", help="Prism Central username") |
| 48 | + parser.add_argument( |
| 49 | + "-p", "--poll", help="Time between task polling, in seconds", default=1 |
| 50 | + ) |
| 51 | + args = parser.parse_args() |
| 52 | + |
| 53 | + # get the cluster password |
| 54 | + cluster_password = getpass.getpass( |
| 55 | + prompt="Enter your Prism Central \ |
| 56 | +password: ", |
| 57 | + stream=None, |
| 58 | + ) |
| 59 | + |
| 60 | + pc_ip = args.pc_ip |
| 61 | + username = args.username |
| 62 | + |
| 63 | + # make sure the user enters a password |
| 64 | + if not cluster_password: |
| 65 | + while not cluster_password: |
| 66 | + print( |
| 67 | + "Password cannot be empty. \ |
| 68 | + Enter a password or Ctrl-C/Ctrl-D to exit." |
| 69 | + ) |
| 70 | + cluster_password = getpass.getpass( |
| 71 | + prompt="Enter your Prism Central password: ", stream=None |
| 72 | + ) |
| 73 | + |
| 74 | + try: |
| 75 | + # create utils instance for re-use later |
| 76 | + utils = Utils(pc_ip=pc_ip, username=username, password=cluster_password) |
| 77 | + |
| 78 | + vmm_config = VMMConfiguration() |
| 79 | + iam_config = IAMConfiguration() |
| 80 | + for config in [iam_config]: |
| 81 | + # create the configuration instances |
| 82 | + config.host = pc_ip |
| 83 | + config.username = username |
| 84 | + config.password = cluster_password |
| 85 | + config.verify_ssl = False |
| 86 | + config.debug = False |
| 87 | + |
| 88 | + # setup VMM configuration |
| 89 | + # note we are NOT setting the username and password at this time |
| 90 | + # later we will list VMs using API key authentication |
| 91 | + vmm_config.host = pc_ip |
| 92 | + vmm_config.port = "9440" |
| 93 | + vmm_config.verify_ssl = False |
| 94 | + vmm_config.debug = False |
| 95 | + |
| 96 | + vmm_config.logger_file = "./vmm.log" |
| 97 | + iam_config.logger_file = "./iam.log" |
| 98 | + |
| 99 | + # before configuring API key auth, we need to get |
| 100 | + # the extId of an existing role |
| 101 | + # for this demo, we will use the built-in 'Super Admin' role |
| 102 | + iam_client = IAMClient(configuration=iam_config) |
| 103 | + iam_instance = ntnx_iam_py_client.api.RolesApi(api_client=iam_client) |
| 104 | + print("Retrieving filtered role list ...") |
| 105 | + role_list = iam_instance.list_roles( |
| 106 | + async_req=False, _filter="contains(displayName, 'Super')" |
| 107 | + ) |
| 108 | + if len(role_list.data) > 0: |
| 109 | + super_admin_ext_id = role_list.data[0].ext_id |
| 110 | + print(f"Super Admin role extId: {super_admin_ext_id}") |
| 111 | + else: |
| 112 | + print("No role found containing the word \"Super\". Exiting ...") |
| 113 | + sys.exit() |
| 114 | + |
| 115 | + vmm_client = VMMClient(configuration=vmm_config) |
| 116 | + vmm_instance = ntnx_vmm_py_client.api.VmApi(api_client=vmm_client) |
| 117 | + |
| 118 | + sa_username = "api_key_service_account" |
| 119 | + sa_email = "<email_address_here>" |
| 120 | + sa_display_name = "API key service account" |
| 121 | + sa_description = "Service account for API key authentication" |
| 122 | + key_name = "service_account_api_key" |
| 123 | + acp_display_name = "API Key Auth Policy" |
| 124 | + |
| 125 | + print("\nThe following configuration will be used for API key authentication.") |
| 126 | + print(f" Username: {sa_username}") |
| 127 | + print(f" Description: {sa_description}") |
| 128 | + print(f" Email: {sa_email}") |
| 129 | + print(f" Display name: {sa_display_name}") |
| 130 | + print(f" Key name: {key_name}") |
| 131 | + print(f" Authorization policy display name: {acp_display_name}\n") |
| 132 | + |
| 133 | + confirm_continue = utils.confirm("Continue API key configuration?") |
| 134 | + |
| 135 | + if confirm_continue: |
| 136 | + # create service account |
| 137 | + service_account = User( |
| 138 | + username=sa_username, |
| 139 | + email=sa_email, |
| 140 | + display_name=sa_display_name, |
| 141 | + description=sa_description, |
| 142 | + creation_type=CreationType.USERDEFINED, |
| 143 | + status=UserStatusType.ACTIVE, |
| 144 | + user_type=UserType.SERVICE_ACCOUNT, |
| 145 | + ) |
| 146 | + |
| 147 | + iam_instance = UsersApi(api_client=iam_client) |
| 148 | + create_sa = iam_instance.create_user(async_req=False, body=service_account) |
| 149 | + |
| 150 | + if create_sa: |
| 151 | + print("Service account created successfully.") |
| 152 | + else: |
| 153 | + print("Service account creation failed. Check iam.log for details.") |
| 154 | + sys.exit() |
| 155 | + |
| 156 | + # get the new service account user's ext_id |
| 157 | + sa_ext_id = create_sa.data.ext_id |
| 158 | + |
| 159 | + # create API key |
| 160 | + api_key = Key(name=key_name, key_type=KeyKind.API_KEY) |
| 161 | + |
| 162 | + create_key = iam_instance.create_user_key( |
| 163 | + async_req=False, userExtId=sa_ext_id, body=api_key |
| 164 | + ) |
| 165 | + if create_key: |
| 166 | + print("API key created successfully.") |
| 167 | + print( |
| 168 | + f"The API key will only be shown ONCE: {create_key.data.key_details.api_key}" |
| 169 | + ) |
| 170 | + api_key_value = create_key.data.key_details.api_key |
| 171 | + |
| 172 | + entities = [EntityFilter({"*": {"*": {"eq": "*"}}})] |
| 173 | + |
| 174 | + identities = [IdentityFilter({"user": {"uuid": {"anyof": [sa_ext_id]}}})] |
| 175 | + |
| 176 | + # create authorization policy for new service account |
| 177 | + iam_instance = AuthorizationPoliciesApi(api_client=iam_client) |
| 178 | + auth_policy = AuthorizationPolicy( |
| 179 | + display_name=acp_display_name, |
| 180 | + description="Authorization policy for use with API key service accounts", |
| 181 | + authorization_policy_type=AuthorizationPolicyType.USER_DEFINED, |
| 182 | + entities=entities, |
| 183 | + identities=identities, |
| 184 | + role=super_admin_ext_id, |
| 185 | + ) |
| 186 | + |
| 187 | + create_acp = iam_instance.create_authorization_policy( |
| 188 | + async_req=False, body=auth_policy |
| 189 | + ) |
| 190 | + if create_acp: |
| 191 | + print("Authorization policy created successfully.") |
| 192 | + else: |
| 193 | + print( |
| 194 | + "Authorization policy creation failed. Check iam.log for details." |
| 195 | + ) |
| 196 | + |
| 197 | + auth_with_key = utils.confirm( |
| 198 | + "\nAll configuration completed successfully. Attempt to list VMs with API authentication?" |
| 199 | + ) |
| 200 | + if auth_with_key: |
| 201 | + print( |
| 202 | + "Attempting to list Prism Central VMs using API key authentication ..." |
| 203 | + ) |
| 204 | + vmm_client.add_default_header( |
| 205 | + header_name="X-Ntnx-Api-Key", header_value=api_key_value |
| 206 | + ) |
| 207 | + vm_list = vmm_instance.list_vms(async_req=False) |
| 208 | + if vm_list: |
| 209 | + print( |
| 210 | + f"{len(vm_list.data)} VMs found. API key authentication successful." |
| 211 | + ) |
| 212 | + else: |
| 213 | + print("VM list operation failed. Check vmm.log for details.") |
| 214 | + else: |
| 215 | + print("API key authentication cancelled.") |
| 216 | + else: |
| 217 | + print("API key configuration cancelled.") |
| 218 | + sys.exit() |
| 219 | + |
| 220 | + except IAMException as iam_exception: |
| 221 | + print( |
| 222 | + f"Error sending request. Exception details:\n {json.loads(iam_exception.body)['data']['error'][0]['message']}" |
| 223 | + ) |
| 224 | + |
| 225 | + |
| 226 | +if __name__ == "__main__": |
| 227 | + main() |
0 commit comments