def findappbyconnector(self, connector_moniker): """ Find EAA Applications using a particular connector. Args: connector_moniker (EAAItem): Connector ID. Returns: Tuple of 3 values: - application moniker - application name - application host (external hostname) Raises: TypeError: If the argument is wrong type. """ if not isinstance(connector_moniker, EAAItem): raise TypeError("EAAItem expected.") url_params = {'limit': ApplicationAPI.LIMIT_SOFT, 'expand': 'true'} search_app = self.get('mgmt-pop/apps', params=url_params) apps = search_app.json() logging.debug("Searching app using %s..." % connector_moniker) for app in apps.get('objects', []): for con in app.get('agents', []): app_moniker = EAAItem("app://" + app.get('uuid_url')) con_moniker = EAAItem("con://" + con.get('uuid_url')) if con_moniker == connector_moniker: yield app_moniker, app.get('name'), app.get('host')
def swap(self, old_con_id, new_con_id, dryrun=False): """ Replace an EAA connector with another in: - application - directory Args: old_con_id (EAAItem): Existing connector to be replaced new_con_id (EAAItem): New connector to attach on the applications and directories dryrun (bool, optional): Enable dry run. Defaults to False. """ infos_by_conid = {} old_con = EAAItem(old_con_id) new_con = EAAItem(new_con_id) for c in [old_con, new_con]: connector_info = self.load(c) if not connector_info: cli.print_error("EAA connector %s not found." % c) cli.print_error("Please check with command 'akamai eaa connector'.") cli.exit(2) # Save the details for better infos_by_conid[c] = connector_info app_api = ApplicationAPI(self._config) app_processed = 0 cli.header("#Operation,connector-id,connector-name,app-id,app-name") for app_using_old_con, app_name, app_host in self.findappbyconnector(old_con): if dryrun: cli.print("DRYRUN +,%s,%s,%s,%s" % ( new_con, infos_by_conid[new_con].get('name'), app_using_old_con, app_name)) cli.print("DRYRUN -,%s,%s,%s,%s" % ( old_con, infos_by_conid[old_con].get('name'), app_using_old_con, app_name)) else: app_api.attach_connectors(app_using_old_con, [{'uuid_url': new_con.uuid}]) cli.print("+,%s,%s,%s,%s" % ( new_con, infos_by_conid[new_con].get('name'), app_using_old_con, app_name)) app_api.detach_connectors(app_using_old_con, [{'uuid_url': old_con.uuid}]) cli.print("-,%s,%s,%s,%s" % ( old_con, infos_by_conid[old_con].get('name'), app_using_old_con, app_name)) app_processed += 1 if app_processed == 0: cli.footer("Connector %s is not used by any application." % old_con_id) cli.footer("Check with command 'akamai eaa connector %s apps'" % old_con_id) else: cli.footer("Connector swapped in %s application(s)." % app_processed) cli.footer("Updated application(s) is/are marked as ready to deploy")
def __init__(self, configuration, directory_moniker=None): super(DirectoryAPI, self).__init__(configuration, BaseAPI.API_Version.OpenAPI) self._directory = None self._directory_id = None if directory_moniker: self._directory = EAAItem(directory_moniker) self._directory_id = self._directory.uuid
def findidpbycert(self, certid): """Find IdP using certificate identified by `certid`""" url_params = {'limit': 10000} search_idp = self.get('mgmt-pop/idp', params=url_params) idps = search_idp.json() for i in idps.get('objects', []): if i.get('cert') == certid: yield (EAAItem("idp://%s" % i.get('uuid_url')), i.get('name'))
def findappsbycert(self, certid): """Find application using certificate identified by `certid`""" url_params = {'limit': 10000} search_app = self.get('mgmt-pop/apps', params=url_params) apps = search_app.json() for a in apps.get('objects', []): if a.get('cert') == certid: yield (EAAItem("app://%s" % a.get('uuid_url')), a.get('name'))
def create(self, raw_app_config): """ Create a new EAA application configuration. :param app_config: configuration as JSON string Note: the portal use the POST to create a new app with a minimal payload: {"app_profile":1,"app_type":1,"client_app_mode":1,"app_profile_id":"Fp3RYok1EeSE6AIy9YR0Dw", "name":"tes","description":"test"} We should do the same here """ app_config = json.loads(self.parse_template(raw_app_config)) logging.debug("Post Jinja parsing:\n%s" % json.dumps(app_config)) app_config_create = { "app_profile": app_config.get('app_profile'), "app_type": app_config.get('app_type', ApplicationAPI.Type.Hosted.value), "name": app_config.get('name'), "description": app_config.get('description') } newapp = self.post('mgmt-pop/apps', json=app_config_create) logging.info("Create app core: %s %s" % (newapp.status_code, newapp.text)) if newapp.status_code != 200: cli.exit(2) newapp_config = newapp.json() logging.info("New app JSON:\n%s" % newapp.text) app_moniker = EAAItem("app://{}".format(newapp_config.get('uuid_url'))) logging.info("UUID of the newapp: %s" % app_moniker) # Now we push everything else as a PUT self.put('mgmt-pop/apps/{applicationId}'.format( applicationId=app_moniker.uuid), json=app_config) # Sub-components of the application configuration definition # --- Connectors if app_config.get('agents'): self.attach_connectors(app_moniker, app_config.get('agents', [])) # IdP, Directories, Groups self.create_auth(app_moniker, app_config) # --- Access Control rules self.create_acl(app_moniker, app_config) # --- Other services # TODO: implement # URL based policies self.create_urlbasedpolicies(app_moniker, app_config) # At the end we reload the app entirely cli.print(json.dumps(self.load(app_moniker)))
def rotate(self): """ Update an existing certificate. """ cert_moniker = EAAItem(self._config.certificate_id) cli.print("Rotating certificate %s..." % cert_moniker.uuid) api_url = 'mgmt-pop/certificates/{certificate_id}'.format( certificate_id=cert_moniker.uuid) get_resp = self.get(api_url) current_cert = get_resp.json() # cli.print(json.dumps(current_cert, sort_keys=True, indent=4)) payload = {} payload['name'] = current_cert.get('name') cli.print("Certificate CN: %s (%s)" % (current_cert.get('cn'), payload['name'])) payload['cert_type'] = current_cert.get('cert_type') with self._config.cert as f: payload['cert'] = f.read() with self._config.key as f: payload['private_key'] = f.read() if self._config.passphrase: payload['password'] = self._config.passphrase put_resp = self.put(api_url, json=payload, params={ 'expand': 'true', 'limit': 0 }) if put_resp.status_code == 200: new_cert = put_resp.json() cli.footer(("Certificate %s updated, %s application/IdP(s) " "have been marked ready for deployment.") % (cert_moniker.uuid, new_cert.get('app_count'))) if self._config.deployafter: self.deployafter(cert_moniker.uuid) else: cli.footer("Please deploy at your convience.") else: cli.print_error("Error rotating certificate, see response below:") cli.print_error(put_resp.text) cli.exit(2)
def status(self): """ Display status for a particular certificate. """ cert_moniker = EAAItem(self._config.certificate_id) cli.header("#App/IdP ID,name,status") app_api = ApplicationAPI(config) for app_id, app_name in self.findappsbycert(cert_moniker.uuid): # We don't need much info so expand=False to keep it quick app_config = app_api.load(app_id, expand=False) # cli.print(app_config) app_status = ApplicationAPI.Status(app_config.get('app_status')) cli.print("%s,%s,%s" % (app_id, app_name, app_status.name)) idp_api = IdentityProviderAPI(config) for idp_id, idp_name in self.findidpbycert(cert_moniker.uuid): idp_config = idp_api.load(idp_id) idp_status = ApplicationAPI.Status(idp_config.get('idp_status')) cli.print("%s,%s,%s" % (idp_id, idp_name, idp_status.name))
def synchronize_group(self, group_uuid): """ Synchronize a group within the directory Args: group_uuid (EAAItem): Group UUID e.g. grp://abcdef """ """ API call POST https://control.akamai.com/crux/v1/mgmt-pop/groups/16nUm7dCSyiihfCvMiHrMg/sync Payload: {} Response: {"response": "Syncing Group Sales Department"} """ group = EAAItem(group_uuid) retry_remaining = self._config.retry + 1 while retry_remaining > 0: retry_remaining -= 1 cli.print("Synchronizing %s [retry=%s]..." % (group_uuid, retry_remaining)) resp = self.get( 'mgmt-pop/directories/{dir_uuid}/groups/{group_uuid}'.format( dir_uuid=self._directory_id, group_uuid=group.uuid)) if resp.status_code != 200: logging.error("Error retrieve group info (%s)" % resp.status_code) cli.exit(2) group_info = resp.json() if group_info.get('last_sync_time'): last_sync = datetime.datetime.fromisoformat( group_info.get('last_sync_time')) delta = datetime.datetime.utcnow() - last_sync cli.print( "Last sync of group %s was @ %s UTC (%d seconds ago)" % (group_info.get('name'), last_sync, delta.total_seconds())) if delta.total_seconds() > self._config.mininterval: sync_resp = self.post( 'mgmt-pop/groups/{group_uuid}/sync'.format( group_uuid=group.uuid)) if sync_resp.status_code != 200: cli.print_error( "Fail to synchronize group (API response code %s)" % sync_resp.status_code) cli.exit(3) else: cli.print( "Synchronization of group %s (%s) successfully requested." % (group_info.get('name'), group)) break else: cli.print_error( "Last group sync is too recent, sync aborted. %s seconds interval is required." % self._config.mininterval) if retry_remaining == 0: cli.exit(2) else: sleep_time = last_sync + datetime.timedelta(seconds=self._config.mininterval) - \ datetime.datetime.utcnow() cli.print( "Sleeping for %s, press Control-Break to interrupt" % sleep_time) time.sleep(sleep_time.total_seconds())
def process_command(self): """ Process command passed from the CLI. """ applications = list() appgroups = list() if self._config.application_id == '-': # nested if below because we don't do anything on create in this section if self._config.action != 'create': for line in sys.stdin: scanned_items = line.split(',') if len(scanned_items) == 0: logging.warning("Cannot parse line: %s" % line) continue try: scanned_obj = EAAItem(scanned_items[0]) if scanned_obj.objtype == EAAItem.Type.Application: applications.append(scanned_obj) elif scanned_obj.objtype == EAAItem.Type.ApplicationGroupAssociation: appgroups.append(scanned_obj) except EAAInvalidMoniker: logging.warning("Invalid application moniker: %s" % scanned_items[0]) else: logging.info("Single app %s" % config.application_id) applications.append(EAAItem(config.application_id)) logging.info("%s" % EAAItem(config.application_id)) if config.action == "deploy": for a in applications: self.deploy(a) cli.print("Application %s deployment requested, it may take a few minutes before it gets live." % a) elif config.action == "create": # new_config = json.load(sys.stdin) new_config = sys.stdin.read() self.create(new_config) elif config.action == "update": if len(applications) > 1: raise Exception("Batch operation not supported") app = applications[0] new_config = json.load(sys.stdin) self.update(app, new_config) cli.print("Configuration for application %s has been updated." % app) elif config.action == "delete": for a in applications: self.delete_app(a) elif config.action == "add_dnsexception": for a in applications: self.add_dnsexception(a) elif config.action == "del_dnsexception": for a in applications: self.del_dnsexception(a) elif config.action == 'viewgroups': for a in applications: self.loadgroups(a) elif config.action == 'delgroup': for ag in appgroups: self.delgroup(ag) elif config.action in ('attach', 'detach'): for a in applications: connectors = [] for c in set(config.connector_id): con_moniker = EAAItem(c) if con_moniker.objtype is not EAAItem.Type.Connector: raise TypeError("Invalid type of connector: %s" % c) connectors.append({"uuid_url": con_moniker.uuid}) if config.action == 'attach': self.attach_connectors(a, connectors) elif config.action == 'detach': self.detach_connectors(a, connectors) else: # view by default for a in applications: app_config = self.load(a) print(json.dumps(app_config))