type=int, default=2) parser.add_argument('-c', '--controller', help='controller ip', default='127.0.0.1') parser.add_argument('-u', '--username', help='user name', default='admin') parser.add_argument('-p', '--password', help='password', default='abc') args = parser.parse_args() print('parsed args', args) mq = { 'metric_id': args.vs_metrics, 'serviceengine_uuid': args.se_uuid, 'tenant': (args.tenant if args.tenant else 'admin'), 'step': args.step, 'limit': args.limit, 'aggregate_entity': not args.per_vs_metrics, 'entity_uuid': '*', 'pad_missing_data': False } api_ssn = ApiSession(args.controller, args.username, args.password, args.tenant) api_utils = ApiUtils(api_ssn) rsp = api_utils.get_metrics_collection(tenant=args.tenant, metric_requests=[mq]) print 'metrics query', mq print json.dumps(rsp, indent=2)
def test_reuse_api_session(self): api1 = ApiSession(avi_credentials=api.avi_credentials, verify=False) api2 = ApiSession.get_session(avi_credentials=api.avi_credentials, verify=False) assert api1 == api2
# Get session on the basis of authentication token token = os.environ.get('API_TOKEN') user = os.environ.get('USER') tenant = os.environ.get('TENANT') print "[INFO] token: %s" % token print "[INFO] user: %s" % user print "[INFO] tenant: %s" % tenant ibx_server = '10.56.70.250' ibx_username = '******' ibx_password = '******' ibx_version = '2.0' ibx_dns_view = 'default' ibx_net_view = 'default' with ApiSession("localhost", user, token=token, tenant=tenant) as session: vs_name = ParseAviParams(session, tenant, sys.argv) vs = GetVSObject(session, vs_name, tenant) vs_hostnames = vs['vh_domain_name'] vs_parent_ref = vs['vh_parent_vs_ref'] vs_parent_vip = GetVSParentObject(session, vs_parent_ref) #Open connection to Infoblox ibx = infoblox.Infoblox(ibx_server, ibx_username, ibx_password, ibx_version, ibx_dns_view, ibx_net_view, iba_verify_ssl=False) AddHosts(vs_hostnames, vs_parent_vip, ibx)
def __init__(self, avi_ip, avi_user, avi_pswd, avi_version): self.avi_api = ApiSession(avi_ip, avi_user, avi_pswd, api_version=avi_version)
def test_get_key_token(self): api1 = ApiSession(avi_credentials=api.avi_credentials, verify=False) api2 = ApiSession.get_session(avi_credentials=api.avi_credentials, verify=False) assert api1.keystone_token == api2.keystone_token
def test_tenant(self): api1 = ApiSession(avi_credentials=api.avi_credentials, verify=False) api2 = ApiSession.get_session(avi_credentials=api.avi_credentials, verify=False) assert api1.tenant == api2.tenant
def test_get_controller_ip(self): api1 = ApiSession(avi_credentials=api.avi_credentials, verify=False) api2 = ApiSession.get_session(avi_credentials=api.avi_credentials, verify=False) assert api1.controller_ip == api2.controller_ip
def get_crt(user, password, tenant, api_version, account_key, csr, CA=DEFAULT_CA, disable_check=False, directory_url=DEFAULT_DIRECTORY_URL, contact=None): directory, acct_headers, alg, jwk = None, None, None, None # global variables # helper functions - base64 encode for jose spec def _b64(b): return base64.urlsafe_b64encode(b).decode('utf8').replace("=", "") # helper function - run external commands def _cmd(cmd_list, stdin=None, cmd_input=None, err_msg="Command Line Error"): proc = subprocess.Popen(cmd_list, stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = proc.communicate(cmd_input) if proc.returncode != 0: raise IOError("{0}\n{1}".format(err_msg, err)) return out # helper function - make request and automatically parse json response def _do_request(url, data=None, err_msg="Error", depth=0): try: resp = urlopen( Request(url, data=data, headers={ "Content-Type": "application/jose+json", "User-Agent": "acme-tiny" })) resp_data, code, headers = resp.read().decode( "utf8"), resp.getcode(), resp.headers except IOError as e: resp_data = e.read().decode("utf8") if hasattr(e, "read") else str(e) code, headers = getattr(e, "code", None), {} try: resp_data = json.loads(resp_data) # try to parse json results except ValueError: pass # ignore json parsing errors if depth < 100 and code == 400 and resp_data[ 'type'] == "urn:ietf:params:acme:error:badNonce": raise IndexError(resp_data) # allow 100 retrys for bad nonces if code not in [200, 201, 204]: raise ValueError( "{0}:\nUrl: {1}\nData: {2}\nResponse Code: {3}\nResponse: {4}". format(err_msg, url, data, code, resp_data)) return resp_data, code, headers # helper function - make signed requests def _send_signed_request(url, payload, err_msg, depth=0): payload64 = "" if payload is None else _b64( json.dumps(payload).encode('utf8')) new_nonce = _do_request(directory['newNonce'])[2]['Replay-Nonce'] protected = {"url": url, "alg": alg, "nonce": new_nonce} protected.update({"jwk": jwk} if acct_headers is None else {"kid": acct_headers['Location']}) protected64 = _b64(json.dumps(protected).encode('utf8')) protected_input = "{0}.{1}".format(protected64, payload64).encode('utf8') out = _cmd(["openssl", "dgst", "-sha256", "-sign", account_key], stdin=subprocess.PIPE, cmd_input=protected_input, err_msg="OpenSSL Error") data = json.dumps({ "protected": protected64, "payload": payload64, "signature": _b64(out) }) try: return _do_request(url, data=data.encode('utf8'), err_msg=err_msg, depth=depth) except IndexError: # retry bad nonces (they raise IndexError) return _send_signed_request(url, payload, err_msg, depth=(depth + 1)) # helper function - poll until complete def _poll_until_not(url, pending_statuses, err_msg): result, t0 = None, time.time() while result is None or result['status'] in pending_statuses: assert (time.time() - t0 < 3600), "Polling timeout" # 1 hour timeout time.sleep(0 if result is None else 2) result, _, _ = _send_signed_request(url, None, err_msg) return result session = ApiSession('localhost', user, password, tenant=tenant, api_version=api_version) log.info("Generating account key...") out = _cmd(["openssl", "genrsa", "4096"], err_msg="OpenSSL Error") with open(account_key, 'w') as f: f.write(out.decode("utf-8")) # parse account key to get public key log.info("Parsing account key...") out = _cmd(["openssl", "rsa", "-in", account_key, "-noout", "-text"], err_msg="OpenSSL Error") pub_pattern = r"modulus:[\s]+?00:([a-f0-9\:\s]+?)\npublicExponent: ([0-9]+)" pub_hex, pub_exp = re.search(pub_pattern, out.decode('utf8'), re.MULTILINE | re.DOTALL).groups() pub_exp = "{0:x}".format(int(pub_exp)) pub_exp = "0{0}".format(pub_exp) if len(pub_exp) % 2 else pub_exp alg = "RS256" jwk = { "e": _b64(binascii.unhexlify(pub_exp.encode("utf-8"))), "kty": "RSA", "n": _b64(binascii.unhexlify( re.sub(r"(\s|:)", "", pub_hex).encode("utf-8"))), } accountkey_json = json.dumps(jwk, sort_keys=True, separators=(',', ':')) thumbprint = _b64(hashlib.sha256(accountkey_json.encode('utf8')).digest()) # find domains log.info("Parsing CSR...") out = _cmd(["openssl", "req", "-in", csr, "-noout", "-text"], err_msg="Error loading {0}".format(csr)) domains = set([]) common_name = re.search(r"Subject:.*? CN\s?=\s?([^\s,;/]+)", out.decode('utf8')) if common_name is not None: domains.add(common_name.group(1)) subject_alt_names = re.search( r"X509v3 Subject Alternative Name: (?:critical)?\n +([^\n]+)\n", out.decode('utf8'), re.MULTILINE | re.DOTALL) if subject_alt_names is not None: for san in subject_alt_names.group(1).split(", "): if san.startswith("DNS:"): domains.add(san[4:]) log.info("Found domains: {0}".format(", ".join(domains))) # get the ACME directory of urls log.info("Getting directory...") directory_url = CA + "/directory" if CA != DEFAULT_CA else directory_url # backwards compatibility with deprecated CA kwarg directory, _, _ = _do_request(directory_url, err_msg="Error getting directory") log.info("Directory found!") # create account, update contact details (if any), and set the global key identifier log.info("Registering account...") reg_payload = {"termsOfServiceAgreed": True} account, code, acct_headers = _send_signed_request(directory['newAccount'], reg_payload, "Error registering") log.info("Registered!" if code == 201 else "Already registered!") if contact is not None: account, _, _ = _send_signed_request(acct_headers['Location'], {"contact": contact}, "Error updating contact details") log.info("Updated contact details:\n{0}".format("\n".join( account['contact']))) # create a new order log.info("Creating new order...") order_payload = { "identifiers": [{ "type": "dns", "value": d } for d in domains] } order, _, order_headers = _send_signed_request(directory['newOrder'], order_payload, "Error creating new order") log.info("Order created!") # get the authorizations that need to be completed for auth_url in order['authorizations']: authorization, _, _ = _send_signed_request(auth_url, None, "Error getting challenges") domain = authorization['identifier']['value'] log.info("Verifying {0}...".format(domain)) # find the http-01 challenge and write the challenge file challenge = [ c for c in authorization['challenges'] if c['type'] == "http-01" ][0] token = re.sub(r"[^A-Za-z0-9_\-]", "_", challenge['token']) keyauthorization = "{0}.{1}".format(token, thumbprint) # Update vs rsp = session.get("vsvip/?search=(fqdn,{})".format(domain)).json() if rsp["count"] == 0: raise Exception( "Could not find a VSVIP with fqdn = {}".format(domain)) vsvip_uuid = rsp["results"][0]["uuid"] rsp = session.get( "virtualservice?search=(vsvip_ref,{})".format(vsvip_uuid)).json() if rsp['count'] == 0: raise Exception( "Could not find a VS with common name = {}".format(domain)) vs_uuid = rsp["results"][0]["uuid"] log.info("Found vs {} with fqdn {}".format(vs_uuid, domain)) # Check if the vs is servering on port 80 serving_on_port_80 = False service_on_port_80_data = None for service in rsp["results"][0]["services"]: if service["port"] == "80": serving_on_port_80 = True break # create HTTP policy httppolicy_data = { "name": (domain + "LetsEncryptHTTPpolicy"), "http_security_policy": { "rules": [{ "name": "Rule 1", "index": 1, "enable": True, "match": { "vs_port": { "match_criteria": "IS_IN", "ports": [80] }, "path": { "match_criteria": "CONTAINS", "match_case": "SENSITIVE", "match_str": [".well-known/acme-challenge/{}".format(token)] } }, "action": { "action": "HTTP_SECURITY_ACTION_SEND_RESPONSE", "status_code": "HTTP_LOCAL_RESPONSE_STATUS_CODE_200", "file": { "content_type": "text/plain", "file_content": keyauthorization } } }] }, "is_internal_policy": False } rsp = session.post("httppolicyset", data=httppolicy_data).json() httppolicy_uuid = rsp["uuid"] log.info("Created HTTP policy with uuid {}".format(httppolicy_uuid)) patch_data = { "add": { "http_policies": [{ "http_policy_set_ref": "/api/httppolicyset/{}".format(httppolicy_uuid), "index": 1000001 }] } } if not serving_on_port_80: # Add to port to virtualservice service_on_port_80_data = { "enable_http2": False, "enable_ssl": False, "port": 80, "port_range_end": 80 } patch_data["add"]["services"] = [service_on_port_80_data] rsp = session.patch("virtualservice/{}".format(vs_uuid), patch_data) exception_occured = None try: # check that the file is in place try: wellknown_url = "http://{0}/.well-known/acme-challenge/{1}".format( domain, token) assert (disable_check or _do_request(wellknown_url)[0] == keyauthorization) except (AssertionError, ValueError) as e: raise ValueError( "Wrote file to {0}, but couldn't download {1}: {2}".format( 'wellknown_path', 'wellknown_url', e)) # say the challenge is done _send_signed_request( challenge['url'], {}, "Error submitting challenges: {0}".format(domain)) authorization = _poll_until_not( auth_url, ["pending"], "Error checking challenge status for {0}".format(domain)) if authorization['status'] != "valid": raise ValueError("Challenge did not pass for {0}: {1}".format( domain, authorization)) except Exception as e: exception_occured = str(e) finally: # Update the vs patch_data = { "delete": { "http_policies": [{ "http_policy_set_ref": "/api/httppolicyset/{}".format(httppolicy_uuid), "index": 1000001 }] } } if not serving_on_port_80: patch_data["delete"]["services"] = [service_on_port_80_data] rsp = session.patch("virtualservice/{}".format(vs_uuid), patch_data) rsp = session.delete("httppolicyset/{}".format(httppolicy_uuid)) if exception_occured: log.error(exception_occured) raise Exception(exception_occured) log.info("{0} verified!".format(domain)) # finalize the order with the csr log.info("Signing certificate...") csr_der = _cmd(["openssl", "req", "-in", csr, "-outform", "DER"], err_msg="DER Export Error") _send_signed_request(order['finalize'], {"csr": _b64(csr_der)}, "Error finalizing order") # poll the order to monitor when it's done order = _poll_until_not(order_headers['Location'], ["pending", "processing"], "Error checking order status") if order['status'] != "valid": raise ValueError("Order failed: {0}".format(order)) # download the certificate certificate_pem, _, _ = _send_signed_request( order['certificate'], None, "Certificate download failed") log.info("Certificate signed!") return certificate_pem
Example: python change_password_example.py --controller_ip="10.10.10.10" --user="******" --current_password="******" --new_password="******" """ parser = argparse.ArgumentParser(description=HELP_STR) parser.add_argument('-c', '--controller_ip', help='controller ip') parser.add_argument('-u', '--user', default='admin') parser.add_argument('-p', '--current_password', default='admin') parser.add_argument('-n', '--new_password', default='admin@123') args = parser.parse_args() print("Input args: ", args) api = None try: api = ApiSession(controller_ip=args.controller_ip, username=args.user, password=args.current_password, tenant="admin") except APIError as error: print("Failed to update the password. Error message: " + str(error)) exit(1) change_pwd_example = ChangePasswordExample(api_session=api) print("Get pool before changing password") change_pwd_example.get_pool() change_pwd_example.change_password(args.new_password) api_after_updation = ApiSession(controller_ip=args.controller_ip, username=args.user, password=args.new_password, tenant="admin") updated_password_example = ChangePasswordExample( api_session=api_after_updation) print("Get pool after changing password")
#!/usr/bin/python3 #example PATCH request script to turn off waf policy on vs #requires avisdk packages. install here #requires avisdk packages. install here https://github.com/avinetworks/sdk/tree/master/python/avi/sdk #API token generation needed. for token generation help, see https://avinetworks.com/docs/18.2/saas-rest-api-access/ import json from avi.sdk.avi_api import ApiSession token = "InsertTokenHere" user = "******" tenant = "admin" controller_ip = "InsertControllerIP" vs_name = "InsertVSnameHere" #Desired policy state. Empty string removes WAF policy on VS. # Input name of policy to turn on, ex: {"waf_policy_ref": "My Waf" } vs {"waf_policy_ref": "" } request_body = '{ "replace": {"waf_policy_ref": "" }}' # Get session on the basis of authentication token with ApiSession(controller_ip, user, token=token, tenant=tenant) as session: # Get the virtualservice object by name vs_obj = session.get_object_by_name('virtualservice', vs_name) if vs_obj: # Save the object resp = session.patch('virtualservice/%s' % vs_obj['uuid'], data=request_body, api_version="18.2.7") print(resp.status_code, resp.json())
def get_crt(user, password, tenant, api_version, csr, CA=DEFAULT_CA, disable_check=False, overwrite_vs=None, directory_url=DEFAULT_DIRECTORY_URL, contact=None, debug=False): directory, acct_headers, alg, jwk = None, None, None, None # global variables # helper functions - base64 encode for jose spec def _b64(b): return base64.urlsafe_b64encode(b).decode('utf8').replace("=", "") # helper function - run external commands def _cmd(cmd_list, stdin=None, cmd_input=None, err_msg="Command Line Error"): proc = subprocess.Popen(cmd_list, stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = proc.communicate(cmd_input) if proc.returncode != 0: raise IOError("{0}\n{1}".format(err_msg, err)) return out # helper function - make request and automatically parse json response def _do_request(url, data=None, err_msg="Error", depth=0, verify=True): try: ctx = ssl.create_default_context() if not verify: # disable certificate verify ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE # open request. resp = urlopen(Request(url, data=data, headers={ "Content-Type": "application/jose+json", "User-Agent": "acme-tiny" }), context=ctx) resp_data, code, headers = resp.read().decode( "utf8"), resp.getcode(), resp.headers except IOError as e: resp_data = e.read().decode("utf8") if hasattr(e, "read") else str(e) code, headers = getattr(e, "code", None), {} try: resp_data = json.loads(resp_data) # try to parse json results except ValueError: pass # ignore json parsing errors if depth < 100 and code == 400 and resp_data[ 'type'] == "urn:ietf:params:acme:error:badNonce": raise IndexError(resp_data) # allow 100 retrys for bad nonces if code not in [200, 201, 204]: raise ValueError( "{0}:\nUrl: {1}\nData: {2}\nResponse Code: {3}\nResponse: {4}". format(err_msg, url, data, code, resp_data)) return resp_data, code, headers # helper function - make signed requests def _send_signed_request(url, payload, err_msg, depth=0): payload64 = "" if payload is None else _b64( json.dumps(payload).encode('utf8')) new_nonce = _do_request(directory['newNonce'])[2]['Replay-Nonce'] protected = {"url": url, "alg": alg, "nonce": new_nonce} protected.update({"jwk": jwk} if acct_headers is None else {"kid": acct_headers['Location']}) protected64 = _b64(json.dumps(protected).encode('utf8')) protected_input = "{0}.{1}".format(protected64, payload64).encode('utf8') out = _cmd(["openssl", "dgst", "-sha256", "-sign", ACCOUNT_KEY_PATH], stdin=subprocess.PIPE, cmd_input=protected_input, err_msg="OpenSSL Error") data = json.dumps({ "protected": protected64, "payload": payload64, "signature": _b64(out) }) try: return _do_request(url, data=data.encode('utf8'), err_msg=err_msg, depth=depth) except IndexError: # retry bad nonces (they raise IndexError) return _send_signed_request(url, payload, err_msg, depth=(depth + 1)) # helper function - poll until complete def _poll_until_not(url, pending_statuses, err_msg): result, t0 = None, time.time() while result is None or result['status'] in pending_statuses: assert (time.time() - t0 < 3600), "Polling timeout" # 1 hour timeout time.sleep(0 if result is None else 2) result, _, _ = _send_signed_request(url, None, err_msg) return result session = ApiSession(os.environ.get("DOCKER_GATEWAY", 'localhost'), user, password, tenant=tenant, api_version=api_version) def _do_request_avi(url, method, data=None, error_msg="Error"): rsp = None if method == "GET": rsp = session.get(url) elif method == "POST": rsp = session.post(url, data=data) elif method == "PATCH": rsp = session.patch(url, data) elif method == "PUT": rsp = session.put(url, data=data) elif method == "DELETE": rsp = session.delete(url) else: raise Exception("Unsupported API method") if rsp.status_code >= 300: err = error_msg + " url - {}. Method - {}. Response status - {}. Response - {}".format( url, method, rsp.status_code, rsp.json()) raise Exception(err) return rsp if os.path.exists(ACCOUNT_KEY_PATH): if debug: print("Reusing account key.") else: print("Account key not found. Generating account key...") out = _cmd(["openssl", "genrsa", "4096"], err_msg="OpenSSL Error") with open(ACCOUNT_KEY_PATH, 'w') as f: f.write(out.decode("utf-8")) # Check if we need to overwrite the VS UUID if it was specified # We request the info here once, instead in the loop for each SAN entry below. if overwrite_vs != None: if overwrite_vs.lower().startswith('virtualservice-'): search_term = "uuid={}".format(overwrite_vs.lower()) else: search_term = "name={}".format( urlparse.quote(overwrite_vs, safe='')) overwrite_vs = _do_request_avi( "virtualservice/?{}".format(search_term), "GET").json() if overwrite_vs['count'] == 0: raise Exception("Could not find a VS with {}".format(search_term)) # parse account key to get public key print("Parsing account key...") out = _cmd(["openssl", "rsa", "-in", ACCOUNT_KEY_PATH, "-noout", "-text"], err_msg="OpenSSL Error") pub_pattern = r"modulus:[\s]+?00:([a-f0-9\:\s]+?)\npublicExponent: ([0-9]+)" pub_hex, pub_exp = re.search(pub_pattern, out.decode('utf8'), re.MULTILINE | re.DOTALL).groups() pub_exp = "{0:x}".format(int(pub_exp)) pub_exp = "0{0}".format(pub_exp) if len(pub_exp) % 2 else pub_exp alg = "RS256" jwk = { "e": _b64(binascii.unhexlify(pub_exp.encode("utf-8"))), "kty": "RSA", "n": _b64(binascii.unhexlify( re.sub(r"(\s|:)", "", pub_hex).encode("utf-8"))), } accountkey_json = json.dumps(jwk, sort_keys=True, separators=(',', ':')) thumbprint = _b64(hashlib.sha256(accountkey_json.encode('utf8')).digest()) # find domains print("Parsing CSR...") out = _cmd(["openssl", "req", "-in", csr, "-noout", "-text"], err_msg="Error loading {0}".format(csr)) domains = set([]) common_name = re.search(r"Subject:.*? CN\s?=\s?([^\s,;/]+)", out.decode('utf8')) if common_name is not None: domains.add(common_name.group(1)) subject_alt_names = re.search( r"X509v3 Subject Alternative Name: (?:critical)?\n +([^\n]+)\n", out.decode('utf8'), re.MULTILINE | re.DOTALL) if subject_alt_names is not None: for san in subject_alt_names.group(1).split(", "): if san.startswith("DNS:"): domains.add(san[4:]) print("Found domains: {0}".format(", ".join(domains))) # get the ACME directory of urls print("Getting directory...") directory_url = CA + "/directory" if CA != DEFAULT_CA else directory_url # backwards compatibility with deprecated CA kwarg directory, _, _ = _do_request(directory_url, err_msg="Error getting directory") print("Directory found!") # create account, update contact details (if any), and set the global key identifier print("Registering account...") reg_payload = {"termsOfServiceAgreed": True} account, code, acct_headers = _send_signed_request(directory['newAccount'], reg_payload, "Error registering") print("Registered!" if code == 201 else "Already registered!") if contact is not None: account, _, _ = _send_signed_request(acct_headers['Location'], {"contact": contact}, "Error updating contact details") print("Updated contact details:\n{0}".format("\n".join( account['contact']))) # create a new order print("Creating new order...") order_payload = { "identifiers": [{ "type": "dns", "value": d } for d in domains] } order, _, order_headers = _send_signed_request(directory['newOrder'], order_payload, "Error creating new order") print("Order created!") # get the authorizations that need to be completed for auth_url in order['authorizations']: if debug: print("Authorization URL is: {}".format(auth_url)) authorization, _, _ = _send_signed_request(auth_url, None, "Error getting challenges") domain = authorization['identifier']['value'] print("Verifying {0}...".format(domain)) # find the http-01 challenge and write the challenge file challenge = [ c for c in authorization['challenges'] if c['type'] == "http-01" ][0] token = re.sub(r"[^A-Za-z0-9_\-]", "_", challenge['token']) keyauthorization = "{0}.{1}".format(token, thumbprint) wellknown_url = "http://{0}/.well-known/acme-challenge/{1}".format( domain, token) if debug: print("Validation URL is: {}".format(wellknown_url)) vhMode = False # Check if we need to overwrite VirtualService UUID to something specific if overwrite_vs == None: # Get VSVIPs/VSs, based on FQDN rsp = _do_request_avi("vsvip/?search=(fqdn,{})".format(domain), "GET").json() if debug: print("Found {} matching VSVIP FQDNs".format(rsp["count"])) if rsp["count"] == 0: print("Warning: Could not find a VSVIP with fqdn = {}".format( domain)) # As a fallback we search for VirtualHosting entries with that domain vhMode = True search_term = "vh_domain_name.contains={}".format(domain) else: vsvip_uuid = rsp["results"][0]["uuid"] search_term = "vsvip_ref={}".format(vsvip_uuid) rsp = _do_request_avi("virtualservice/?{}".format(search_term), "GET").json() if debug: print("Found {} matching VSs".format(rsp["count"])) if rsp['count'] == 0: raise Exception( "Could not find a VS with fqdn = {}".format(domain)) vs_uuid = rsp["results"][0]["uuid"] else: # Overwriting VS UUID to what user specified. # ALL SANs of the CSR must be reachable on the specified VS to succeed. rsp = overwrite_vs vs_uuid = rsp["results"][0]["uuid"] print("Note: Overwriting VS UUID to {}".format(vs_uuid)) print("Found VS {} with fqdn {}".format(vs_uuid, domain)) # Let's check if VS is enabled, otherwise challenge can never successfully complete. if not rsp["results"][0]["enabled"]: raise Exception("VS with fqdn {} is not enabled.".format(domain)) # Special handling for virtualHosting: if child, get services from parent. if vhMode and rsp["results"][0]["type"] == "VS_TYPE_VH_CHILD": # vh_parent_vs_ref is schema of https://avi.domain.tld/api/virtualservice/virtualservice-UUID, hence picking the last part vs_uuid_parent = rsp["results"][0]["vh_parent_vs_ref"].split( "/")[-1] vhRsp = _do_request_avi( "virtualservice/?uuid={}".format(vs_uuid_parent), "GET").json() if debug: print( "Parent VS of Child-VS is {} and found {} matches".format( vs_uuid_parent, vhRsp['count'])) if vhRsp['count'] == 0: raise Exception( "Could not find parent VS {} of child VS UUID = {}".format( vs_uuid_parent, vs_uuid)) # we just copy it over. more transparent for further logic. rsp["results"][0]["services"] = vhRsp["results"][0]["services"] # Check if the vs is serving on port 80 serving_on_port_80 = False service_on_port_80_data = None for service in rsp["results"][0]["services"]: if service["port"] == 80 and not service["enable_ssl"]: serving_on_port_80 = True if debug: print("VS serving on port 80") break # Update VS httpPolicyName = (domain + "-LetsEncryptHTTPpolicy") # Check if HTTP policy exists. # Can happen in rare cases, when e.g. script fails or removal failed for whatever reasons. # To prevent this from failing forever, we clean it up at this stage. hpRsp = _do_request_avi( "httppolicyset/?name={}".format(httpPolicyName), "GET").json() if hpRsp['count'] > 0: hp_uuid = hpRsp['results'][0]['uuid'] print( "Stranded httpPolicySet {} found. Deleting...".format(hp_uuid)) _do_request_avi("httppolicyset/{}".format(hp_uuid), "DELETE") # Create HTTP policy httppolicy_data = { "name": httpPolicyName, "http_security_policy": { "rules": [{ "name": "Rule 1", "index": 1, "enable": True, "match": { "path": { "match_criteria": "CONTAINS", "match_case": "SENSITIVE", "match_str": [".well-known/acme-challenge/{}".format(token)] } }, "action": { "action": "HTTP_SECURITY_ACTION_SEND_RESPONSE", "status_code": "HTTP_LOCAL_RESPONSE_STATUS_CODE_200", "file": { "content_type": "text/plain", "file_content": keyauthorization } } }] }, "is_internal_policy": False } httppolicy_uuid = None try: # Create httpPolicySet rsp = _do_request_avi("httppolicyset", "POST", data=httppolicy_data).json() httppolicy_uuid = rsp["uuid"] print("Created httpPolicy with uuid {}".format(httppolicy_uuid)) patch_data = { "add": { "http_policies": [{ "http_policy_set_ref": "/api/httppolicyset/{}".format(httppolicy_uuid), "index": 1000001 }] } } if not serving_on_port_80: # Add port to virtualservice print("Adding port 80 to VS") service_on_port_80_data = { "enable_http2": False, "enable_ssl": False, "port": 80, "port_range_end": 80 } patch_data["add"]["services"] = [service_on_port_80_data] # Adding httpPolicySet to VS if vhMode: # if VH, we set the rule on the parent. Without SNI (so HTTP) it will go to the parent. _do_request_avi("virtualservice/{}".format(vs_uuid_parent), "PATCH", patch_data) print("Added httpPolicySet to parent-VS {}".format( vs_uuid_parent)) else: _do_request_avi("virtualservice/{}".format(vs_uuid), "PATCH", patch_data) print("Added httpPolicySet to VS {}".format(vs_uuid)) # check that the file is in place if not disable_check: print("Validating token from Avi Controller...") try: maxVerifyAttempts = 5 # maximal amount of verification attempts # retrying logic. Otherwise race-condition can occurr between avi controller pushing config and the token validation request for verifyAttempt in range(maxVerifyAttempts): reqToken = _do_request(wellknown_url, verify=False) if reqToken[0] != keyauthorization: print( "Internal token validation failed, {0} of {1} attempts. Retrying in 2 seconds." .format((verifyAttempt + 1), maxVerifyAttempts)) time.sleep(2) else: break else: raise Exception( "All {2} internal token verifications failed. Got '{0}' but expected '{1}'." .format(reqToken[0], keyauthorization, maxVerifyAttempts)) except Exception as e: raise ValueError( "Wrote file, but Avi couldn't verify token at {0}. Exception: {1}" .format(wellknown_url, str(e))) else: print( "Waiting 5 seconds before letting LetsEncrypt validating the challenge as validation disabled. Give controller time to push configs." ) time.sleep( 5 ) # wait 5 secs if not validating, due to above mentioned race condition print("Challenge completed, notifying LetsEncrypt") # say the challenge is done _send_signed_request( challenge['url'], {}, "Error submitting challenges: {0}".format(domain)) authorization = _poll_until_not( auth_url, ["pending"], "Error checking challenge status for {0}".format(domain)) if authorization['status'] != "valid": raise ValueError("Challenge did not pass for {0}: {1}".format( domain, authorization)) print("Challenge passed") finally: print("Cleaning up...") # Update the vs if httppolicy_uuid == None: print("Error: Failed removing httpPolicy as UUID not defined.") else: patch_data = { "delete": { "http_policies": [{ "http_policy_set_ref": "/api/httppolicyset/{}".format(httppolicy_uuid), "index": 1000001 }] } } if not serving_on_port_80: patch_data["delete"]["services"] = [ service_on_port_80_data ] # Remove httpPolicySet from VS if vhMode: # if VH, we set the rule on the parent. Without SNI (so HTTP) it will go to the parent. _do_request_avi("virtualservice/{}".format(vs_uuid_parent), "PATCH", patch_data) print("Removed httpPolicySet from parent-VS {}".format( vs_uuid_parent)) else: _do_request_avi("virtualservice/{}".format(vs_uuid), "PATCH", patch_data) print("Removed httpPolicySet from VS {}".format(vs_uuid)) # Remove httpPolicySet _do_request_avi("httppolicyset/{}".format(httppolicy_uuid), "DELETE") print("Deleted httpPolicySet") print("{0} verified!".format(domain)) # finalize the order with the csr print("Signing certificate...") csr_der = _cmd(["openssl", "req", "-in", csr, "-outform", "DER"], err_msg="DER Export Error") _send_signed_request(order['finalize'], {"csr": _b64(csr_der)}, "Error finalizing order") # poll the order to monitor when it's done order = _poll_until_not(order_headers['Location'], ["pending", "processing"], "Error checking order status") if order['status'] != "valid": raise ValueError("Order failed: {0}".format(order)) # download the certificate certificate_pem, _, _ = _send_signed_request( order['certificate'], None, "Certificate download failed") print("Certificate signed!") return certificate_pem