def create_auth(self, app_moniker, app_config): """ Create application authentication configuration IdP, Directories, Groups """ # IdP # The "view" operation gives us the IdP in idp -> idp_id scanned_idp_uuid = app_config.get('idp', {}).get('idp_id') if scanned_idp_uuid: idp_app_payload = {"app": app_moniker.uuid, "idp": scanned_idp_uuid} idp_app_resp = self.post('mgmt-pop/appidp', json=idp_app_payload) logging.info("IdP-app association response: %s %s" % (idp_app_resp.status_code, idp_app_resp.text)) # Directory # The view operation gives us the directories in directories[] -> uuid_url scanned_directories = app_config.get('directories', []) app_directories_payload = {"data": [{"apps": [app_moniker.uuid], "directories": scanned_directories}]} app_directories_resp = self.post('mgmt-pop/appdirectories', json=app_directories_payload) logging.info( "App directories association response: %s %s" % (app_directories_resp.status_code, app_directories_resp.text) ) if app_directories_resp.status_code != 200: cli.exit(2) # Groups if len(app_config.get('groups', [])) > 0: app_groups_payload = {'data': [{'apps': [app_moniker.uuid], 'groups': app_config.get('groups', [])}]} app_groups_resp = self.post('mgmt-pop/appgroups', json=app_groups_payload) if app_groups_resp.status_code != 200: cli.exit(2) else: logging.debug("No group set")
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 update(self, app_moniker, app_config): """ Update an existing EAA application configuration. """ update = self.put('mgmt-pop/apps/{applicationId}'.format( applicationId=app_moniker.uuid), json=app_config) logging.info("Update app response: %s" % update.status_code) logging.info("Update app response: %s" % update.text) if update.status_code != 200: cli.exit(2)
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 detach_connectors(self, app_moniker, connectors): """ Detach connector/s to an application. Payload is different from attach above: {"agents":["cht3_GEjQWyMW9LEk7KQfg"]} """ logging.info("Detaching {} connectors...".format(len(connectors))) api_resp = self.post( 'mgmt-pop/apps/{applicationId}/agents'.format(applicationId=app_moniker.uuid), params={'method': 'delete'}, json={'agents': [c.get('uuid_url') for c in connectors]} ) logging.info("Detach connector response: %s" % api_resp.status_code) logging.info("Detach connector app response: %s" % api_resp.text) if api_resp.status_code not in (200, 204): cli.print_error("Connector(s) %s were not detached from application %s [HTTP %s]" % (','.join([c.get('uuid_url') for c in connectors]), app_moniker, api_resp.status_code)) cli.print_error("use 'akamai eaa -v ...' for more info") cli.exit(2)
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 attach_connectors(self, app_moniker, connectors): """ Attach connector/s to an application. :param EAAItem app_moniker: Application Moniker (prefix + UUID) :param list connectors: Connector list, expected list item format is dict {'uuid_url': '<UUID>'} """ # POST on mgmt-pop/apps/DBMcU6FwSjKa7c9sny4RLg/agents # Body # {"agents":[{"uuid_url":"cht3_GEjQWyMW9LEk7KQfg"}]} logging.info("Attaching {} connectors...".format(len(connectors))) api_resp = self.post( 'mgmt-pop/apps/{applicationId}/agents'.format(applicationId=app_moniker.uuid), json={'agents': connectors} ) logging.info("Attach connector response: %s" % api_resp.status_code) logging.info("Attach connector app response: %s" % api_resp.text) if api_resp.status_code not in (200, 201): cli.print_error("Connector(s) %s were not attached to application %s [HTTP %s]" % (','.join([c.get('uuid_url') for c in connectors]), app_moniker, api_resp.status_code)) cli.print_error("use 'akamai eaa -v ...' for more info") cli.exit(2)
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())