def register(registration_endpoint, worker_group_name, machine_id, cert_path, key_path, is_azure_vm, vm_id, azure_resource_id, test_mode): """Registers the worker through the automation linked account with the Agent Service. Returns: The deserialized response from the Agent Service. """ http_client_factory = httpclientfactory.HttpClientFactory( cert_path, key_path, test_mode) http_client = http_client_factory.create_http_client(sys.version_info) no_proxy_http_client_factory = httpclientfactory.HttpClientFactory( cert_path, key_path, test_mode, force_no_proxy=True) no_proxy_http_client = no_proxy_http_client_factory.create_http_client( sys.version_info) headers, payload = get_headers_and_payload(worker_group_name, is_azure_vm, vm_id, azure_resource_id, cert_path, no_proxy_http_client) url = registration_endpoint + "/HybridV2(MachineId='" + machine_id + "')" response = http_client.put(url, headers=headers, data=payload) if response.status_code != 200: raise Exception("Unable to register [status_code=" + str(response.status_code) + "]") return json.loads(response.raw_data), payload
def deregister(registration_endpoint, worker_group_name, machine_id, cert_path, key_path, test_mode): """Deregisters the worker through the automation linked account with the Agent Service. Note: This method is only present for testing purposes for now. Linked account deregistration is not yet implemented and deregistration need to be made through using the automation account information. Returns: """ headers, payload = get_headers_and_payload(worker_group_name, certificate_path=cert_path) url = registration_endpoint + "/Hybrid(MachineId='" + machine_id + "')" http_client_factory = httpclientfactory.HttpClientFactory(cert_path, key_path, test_mode) http_client = http_client_factory.create_http_client(sys.version_info) response = http_client.delete(url, headers=headers, data=payload) if response.status_code != 200: raise Exception("Unable to deregister [status_code=" + str(response.status_code) + "]")
def deregister(options): registration_endpoint = options.registration_endpoint automation_account_key = options.automation_account_key workspace_id = options.workspace_id # assert workspace exists on the box state_base_path = "/var/opt/microsoft/omsagent/" + workspace_id + "/state/" working_directory_base_path = "/var/opt/microsoft/omsagent/" + workspace_id + "/run/" if os.path.exists(state_base_path) is False or os.path.exists(working_directory_base_path) is False: raise Exception("Invalid workspace id. Is the specified workspace id registered as the OMSAgent " "primary worksapce?") diy_state_base_path = os.path.join(state_base_path, os.path.join("automationworker", "diy")) diy_working_directory_base_path = os.path.join(working_directory_base_path, os.path.join("automationworker", "diy")) worker_conf_path = os.path.join(diy_state_base_path, "worker.conf") certificate_path = os.path.join(diy_state_base_path, "worker_diy.crt") key_path = os.path.join(diy_state_base_path, "worker_diy.key") if os.path.exists(worker_conf_path) is False: raise Exception("Unable to deregister, no worker configuration found on disk.") if os.path.exists(certificate_path) is False or os.path.exists(key_path) is False: raise Exception("Unable to deregister, no worker certificate/key found on disk.") issuer, subject, thumbprint = linuxutil.get_cert_info(certificate_path) if os.path.exists(worker_conf_path) is False: raise Exception("Missing worker configuration.") if os.path.exists(certificate_path) is False: raise Exception("Missing worker certificate.") if os.path.exists(key_path) is False: raise Exception("Missing worker key.") config = ConfigParser.ConfigParser() config.read(worker_conf_path) machine_id = config.get("worker-required", "machine_id") # generate payload for registration request date = datetime.datetime.utcnow().isoformat() + "0-00:00" payload = {"Thumbprint": thumbprint, "Issuer": issuer, "Subject": subject} # the signature generation is based on agent service contract payload_hash = sha256_digest(payload) b64encoded_payload_hash = base64.b64encode(payload_hash) signature = generate_hmac(b64encoded_payload_hash + "\n" + date, automation_account_key) b64encoded_signature = base64.b64encode(signature) headers = {'Authorization': 'Shared ' + b64encoded_signature, 'ProtocolVersion': "2.0", 'x-ms-date': date, "Content-Type": "application/json"} # agent service registration request http_client_factory = httpclientfactory.HttpClientFactory(certificate_path, key_path, options.test) http_client = http_client_factory.create_http_client(sys.version_info) url = registration_endpoint + "/Hybrid(MachineId='" + machine_id + "')" response = http_client.delete(url, headers=headers, data=payload) if response.status_code != 200: raise Exception("Failed to deregister worker. [response_status=" + str(response.status_code) + "]") if response.status_code == 404: raise Exception("Unable to deregister. Worker not found.") print "Successfuly deregistered worker." print "Cleaning up left over directories." try: shutil.rmtree(diy_state_base_path) print "Removed state directory." except: raise Exception("Unable to remove state directory base path.") try: shutil.rmtree(diy_working_directory_base_path) print "Removed working directory." except: raise Exception("Unable to remove working directory base path.")
"VirtualMachineId": vm_id, "Subject": subject} # the signature generation is based on agent service contract payload_hash = sha256_digest(payload) b64encoded_payload_hash = base64.b64encode(payload_hash) signature = generate_hmac(b64encoded_payload_hash + "\n" + date, automation_account_key) b64encoded_signature = base64.b64encode(signature) headers = {'Authorization': 'Shared ' + b64encoded_signature, 'ProtocolVersion': "2.0", 'x-ms-date': date, "Content-Type": "application/json"} # agent service registration request http_client_factory = httpclientfactory.HttpClientFactory(certificate_path, key_path, options.test) http_client = http_client_factory.create_http_client(sys.version_info) url = registration_endpoint + "/HybridV2(MachineId='" + machine_id + "')" response = http_client.put(url, headers=headers, data=payload) if response.status_code != 200: raise Exception("Failed to register worker. [response_status=" + str(response.status_code) + "]") registration_response = json.loads(response.raw_data) account_id = registration_response["AccountId"] create_worker_configuration_file(registration_response["jobRuntimeDataServiceUri"], account_id, hybrid_worker_group_name, machine_id, diy_working_directory_base_path, diy_state_base_path, certificate_path, key_path, registration_endpoint, workspace_id, thumbprint, vm_id, is_azure_vm, options.test) # generate working directory path
def register(options): environment_prerequisite_validation() """Registers the machine against the automation agent service. Args: options : dict, the options dictionary """ registration_endpoint = options.registration_endpoint automation_account_key = options.automation_account_key hybrid_worker_group_name = options.hybrid_worker_group_name workspace_id = options.workspace_id # assert workspace exists on the box state_base_path = "/var/opt/microsoft/omsagent/" + workspace_id + "/state/" working_directory_base_path = "/var/opt/microsoft/omsagent/" + workspace_id + "/run/" if os.path.exists(state_base_path) is False or os.path.exists( working_directory_base_path) is False: raise Exception( "Invalid workspace id. Is the specified workspace id registered as the OMSAgent " "primary worksapce?") diy_account_id = extract_account_id_from_registration_endpoint( registration_endpoint) auto_registered_account_id = get_autoregistered_worker_account_id() if auto_registered_account_id != None and auto_registered_account_id != diy_account_id: raise Exception( "Cannot register, conflicting worker already registered.") worker_conf_path = os.path.join(DIY_STATE_PATH, "worker.conf") if os.path.isfile(worker_conf_path) is True: raise Exception( "Unable to register, an existing worker was found. Please deregister any existing worker and " "try again.") certificate_path = os.path.join(DIY_STATE_PATH, "worker_diy.crt") key_path = os.path.join(DIY_STATE_PATH, "worker_diy.key") machine_id = util.generate_uuid() # generate state path (certs/conf will be dropped in this path) if os.path.isdir(DIY_STATE_PATH) is False: try: os.makedirs(DIY_STATE_PATH) except Exception as ex: print("Registration unsuccessful.") print( "Cannot create directory for certs/conf. Because of the following exception : " + str(ex)) return generate_self_signed_certificate(certificate_path=certificate_path, key_path=key_path) issuer, subject, thumbprint = linuxutil.get_cert_info(certificate_path) # try to extract optional metadata unknown = "Unknown" asset_tag = unknown vm_id = unknown is_azure_vm = False try: dmidecode = invoke_dmidecode() is_azure_vm = linuxutil.is_azure_vm(dmidecode) if is_azure_vm: asset_tag = linuxutil.get_azure_vm_asset_tag() else: asset_tag = False vm_id = linuxutil.get_vm_unique_id_from_dmidecode( sys.byteorder, dmidecode) except Exception as e: print(str(e)) pass # generate payload for registration request date = datetime.datetime.utcnow().isoformat() + "0-00:00" payload = { 'RunbookWorkerGroup': hybrid_worker_group_name, "MachineName": socket.gethostname().split(".")[0], "IpAddress": get_ip_address(), "Thumbprint": thumbprint, "Issuer": issuer, "OperatingSystem": 2, "SMBIOSAssetTag": asset_tag, "VirtualMachineId": vm_id, "Subject": subject } # the signature generation is based on agent service contract payload_hash = sha256_digest(payload) b64encoded_payload_hash = base64.b64encode(payload_hash) signature = generate_hmac( b64encoded_payload_hash.decode("utf-8") + "\n" + date, automation_account_key) b64encoded_signature = base64.b64encode(signature) headers = { 'Authorization': 'Shared ' + b64encoded_signature.decode("utf-8"), 'ProtocolVersion': "2.0", 'x-ms-date': date, "Content-Type": "application/json" } is_conf_file_writable = check_if_conf_file_can_be_written() if is_conf_file_writable: # agent service registration request http_client_factory = httpclientfactory.HttpClientFactory( certificate_path, key_path, options.test) http_client = http_client_factory.create_http_client(sys.version_info) url = registration_endpoint + "/HybridV2(MachineId='" + machine_id + "')" response = http_client.put(url, headers=headers, data=payload) if response.status_code != 200: raise Exception("Failed to register worker. [response_status=" + str(response.status_code) + "]") response.raw_data = response.raw_data.decode() if isinstance( response.raw_data, bytes) else response.raw_data registration_response = json.loads(response.raw_data) account_id = registration_response["AccountId"] create_worker_configuration_file( registration_response["jobRuntimeDataServiceUri"], account_id, hybrid_worker_group_name, machine_id, DIY_WORKING_DIR, DIY_STATE_PATH, certificate_path, key_path, registration_endpoint, workspace_id, thumbprint, vm_id, is_azure_vm, options.gpg_keyring, options.test) # generate working directory path diydirs.create_persistent_diy_dirs() print("Registration successful!") else: print( "Registration cannot be completed because configuration file could not be written. Please check the file permissions for /home/nxautomation folder" )
def main(argv): agent_id = None is_azure_vm = False vm_id = None oms_cert_path = None oms_key_path = None endpoint = None gpg_keyring_path = None operation = None proxy_configuration_path = None test_mode = False state_directory = None working_directory = None workspace_id = None mock_powershelldsc_test = False diy_account_id = None azure_resource_id = None # parse cmd line args try: opts, args = getopt.getopt(argv, "hrdw:a:c:k:e:f:s:p:g:y:i:v:zt", [ "help", "register", "deregister", "workspaceid=", "agentid=", "certpath=", "keypath=", "endpoint=", "workingdirpath=", "statepath=", "proxyconfpath=", "gpgkeyringpath=", "diyaccountid=", "mock_powershelldsc_test=", "vmid=", "azureresourceid=" ]) except getopt.GetoptError: print __file__ + "[--register, --deregister] -w <workspaceid> -a <agentid> -c <certhpath> -k <keypath> " \ "-e <endpoint> -f <workingdirpath> -s <statepath> -p <proxyconfpath> -g <gpgkeyringpath>" \ "-y <diyaccountid> -i <vmid>" sys.exit(2) for opt, arg in opts: if opt == ("-h", "--help"): print __file__ + "[--register, --deregister] -w <workspaceid> -a <agentid> -c <certhpath> -k <keypath> " \ "-e <endpoint> -f <workingdirpath> -s <statepath> -p <proxyconfpath> -g <gpgkeyringpath>" \ "-y <diyaccountid> -i <vmid>" sys.exit() elif opt in ("-r", "--register"): operation = REGISTER elif opt in ("-d", "--deregister"): operation = DEREGISTER elif opt in ("-w", "--workspaceid"): workspace_id = arg.strip() elif opt in ("-a", "--agentid"): agent_id = arg.strip() elif opt in ("-c", "--certpath"): oms_cert_path = arg.strip() elif opt in ("-k", "--keypath"): oms_key_path = arg.strip() elif opt in ("-e", "--endpoint"): endpoint = arg.strip() elif opt in ("-f", "--workingdirpath"): working_directory = arg.strip() elif opt in ("-p", "--proxyconfpath"): proxy_configuration_path = arg.strip() elif opt in ("-s", "--statepath"): state_directory = arg.strip() elif opt in ("-g", "--gpgkeyringpath"): gpg_keyring_path = arg.strip() elif opt in ("-y", "--diyaccountid"): diy_account_id = arg.strip() elif opt in ("-z", "--azurevm"): is_azure_vm = True elif opt in ("-v", "--azureresourceid"): azure_resource_id = arg.strip( ) # Use the Resource ID from DSC resource as a backup. Overwrite it with metadata from IMDS when available elif opt in ("-i", "--vmid"): vm_id = arg.strip( ) # Use the VM ID from DSC resource as a backup. Overwrite it with metadata from IMDS when available elif opt in ("-t", "--test"): test_mode = True elif opt == "--mock_powershelldsc_test": # generate a dummy configuration file # does not do actual registration, just creates the resulting config file mock_powershelldsc_test = True if workspace_id is None or agent_id is None or oms_cert_path is None or oms_key_path is None \ or endpoint is None or gpg_keyring_path is None or proxy_configuration_path is None \ or working_directory is None or state_directory is None or vm_id is None: print "Missing mandatory arguments." print "Use -h or --help for usage." sys.exit(1) else: if mock_powershelldsc_test is True: # Don't validate paths if we want to generate a dummy config file pass else: # validate that the cert and key exists if os.path.isfile(oms_cert_path) is False or os.path.isfile( oms_key_path) is False: raise Exception( "Certificate or key file doesn't exist. Are you using absolute path?" ) configuration.clear_config() configuration.set_config({ configuration.PROXY_CONFIGURATION_PATH: proxy_configuration_path, configuration.WORKER_VERSION: "LinuxAutoRegister", configuration.WORKING_DIRECTORY_PATH: "/var/opt/microsoft/omsagent/tmp" }) # build registration endpoint # example endpoint : agentsvc.azure-automation.net registration_endpoint = "https://" + workspace_id + "." + endpoint + "/accounts/" + workspace_id if "df-agentsvc" in registration_endpoint: registration_endpoint = "https://oaasagentsvcdf.test.azure-automation.net/accounts/" + workspace_id test_mode = True # rename to match oms concepts to automation machine_id = agent_id worker_group_name = get_hybrid_worker_group_name(agent_id=agent_id) # action if operation == REGISTER: if mock_powershelldsc_test is True: # Don't do the actual registration in case we want only a dummy registration file # create a dummy response instead registration_response = \ {'jobRuntimeDataServiceUri': 'https://we-jobruntimedata-prod-su1.azure-automation.net', 'AccountId': '23216587-8f56-428c-9006-4c2f28c036f5'} cert_info = [ '', '', '959GG850526XC5JT35E269CZ69A55E1C7E1256JH' ] else: # Update the metadata if possible platform_update_domain = "" tags = "" try: http_client_factory = httpclientfactory.HttpClientFactory( oms_cert_path, oms_key_path, test_mode) http_client = http_client_factory.create_http_client( sys.version_info) metadata = get_metadata_from_imds(http_client) if metadata is not None: try: vm_id = metadata["compute"]["vmId"] sub_id = metadata["compute"]["subscriptionId"] resource_group = metadata["compute"][ "resourceGroupName"] vm_name = metadata["compute"]["name"] azure_resource_id = "/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Compute/virtualMachines/{2}".format( sub_id, resource_group, vm_name) platform_update_domain = metadata["compute"][ "platformUpdateDomain"] tags = metadata["compute"]["tags"] except KeyError: pass except: pass registration_response = register(registration_endpoint, worker_group_name, machine_id, oms_cert_path, oms_key_path, is_azure_vm, vm_id, azure_resource_id, test_mode, platform_update_domain, tags) cert_info = linuxutil.get_cert_info(oms_cert_path) account_id = registration_response["AccountId"] if test_mode is False and diy_account_id is not None and diy_account_id != account_id: sys.stderr.write( "Unable to create worker configuration. DIY Automation account differs from " "linked account.") sys.exit(-5) create_worker_configuration_file( working_directory, registration_response["jobRuntimeDataServiceUri"], registration_endpoint, workspace_id, account_id, worker_group_name, machine_id, oms_cert_path, oms_key_path, state_directory, gpg_keyring_path, proxy_configuration_path, test_mode, cert_info, is_azure_vm, vm_id) elif operation == DEREGISTER: deregister(registration_endpoint, worker_group_name, machine_id, oms_cert_path, oms_key_path, test_mode) else: raise Exception( "No option specified, specify --register, --deregister or --help." )