diff --git a/README.md b/README.md index 1f83c41..f8680ae 100644 --- a/README.md +++ b/README.md @@ -215,6 +215,8 @@ You will get 7 extra columns: - Idle dialout connections - Active dialout connections +To correlate with application served by each connectors, use the `--showapps`, a list of the application FQDNs as an array in the JSON response. + #### Swapping connectors If you are doing a maintenance on an hypervizor, you may need to swap out 2 connectors. @@ -261,7 +263,7 @@ Use 'akamai eaa cert crt://certificate-UUID status' to monitor the progress. Checking the status of the deployment: ``` -./akamai-eaa cert crt://certificate-UUID status +$ akamai eaa cert crt://certificate-UUID status #App/IdP ID,name,status app://appid-1,Multi-origin Active-Active Demo (US-East),Pending app://appid-2,Multi-origin Active-Active Demo (US-West),Pending diff --git a/VERSION b/VERSION index c8a5397..c0a1ac1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.5 \ No newline at end of file +0.4.6 \ No newline at end of file diff --git a/bin/akamai-eaa b/bin/akamai-eaa index b0548f1..19545e1 100755 --- a/bin/akamai-eaa +++ b/bin/akamai-eaa @@ -196,9 +196,10 @@ if __name__ == "__main__": # Unless the long form "akamai eaa connector list" is used # the ArgumentParser won't have the attribute set json = hasattr(config, 'json') and config.json + show_apps = hasattr(config, 'showapps') and config.showapps tail = hasattr(config, 'tail') and config.tail interval = hasattr(config, 'interval') and config.interval - c.list(perf, json, tail, interval, cli.stop_event) + c.list(perf, json, show_apps, tail, interval, cli.stop_event) elif config.command in ("certificate", "cert"): c = CertificateAPI(config) if config.action is None or config.action == "list" or config.certificate_id is None: diff --git a/bin/config.py b/bin/config.py index 6d3c104..52862b5 100644 --- a/bin/config.py +++ b/bin/config.py @@ -145,6 +145,8 @@ def __init__(self, config_values, configuration, flags=None): list_parser = subsub.add_parser("list", help="List all connectors") list_parser.add_argument('--perf', default=False, action="store_true", help='Show performance metrics') list_parser.add_argument('--json', '-j', default=False, action="store_true", help='View as JSON') + list_parser.add_argument('--showapps', '-a', default=False, action="store_true", + help='Response contains the applications running on the connector (JSON only)') list_parser.add_argument('--tail', '-f', default=False, action="store_true", help='Keep watching, do not exit until Control+C/SIGTERM') list_parser.add_argument('--interval', '-i', default=300, type=float, help='Interval between update (works with --tail only)') # subparsers.required = False diff --git a/cli.json b/cli.json index cde903a..2949bf9 100755 --- a/cli.json +++ b/cli.json @@ -5,7 +5,7 @@ "commands": [ { "name": "eaa", - "version": "0.4.5", + "version": "0.4.6", "description": "Akamai CLI for Enterprise Application Access (EAA)" } ] diff --git a/libeaa/common.py b/libeaa/common.py index c97d2b9..2a4fbaa 100644 --- a/libeaa/common.py +++ b/libeaa/common.py @@ -37,7 +37,7 @@ config = EdgeGridConfig({'verbose': False}, 'default') #: cli-eaa version -__version__ = '0.4.5' +__version__ = '0.4.6' #: HTTP Request Timeout in seconds HTTP_REQ_TIMEOUT = 300 diff --git a/libeaa/connector.py b/libeaa/connector.py index 65782fb..359d5da 100644 --- a/libeaa/connector.py +++ b/libeaa/connector.py @@ -17,6 +17,8 @@ import time import json import signal +from functools import lru_cache + from common import cli, BaseAPI, EAAItem from application import ApplicationAPI @@ -28,6 +30,7 @@ class ConnectorAPI(BaseAPI): """ POOL_SIZE = 6 # When doing sub request, max concurrency of underlying HTTP request LIMIT_SOFT = 256 # Soft limit of maximum of connectors to retreive at once + APP_CACHE_TTL = 300 # How long we consider the app <-> connector mapping accurate enough NODATA = "-" # Output value in the CSV cell if data is not available NODATA_JSON = None # Output value in the CSV cell if data is not available @@ -96,7 +99,7 @@ def perf_apps(self, connector_id): perf_by_host[perf_by_app.get('app_name')] = perf_by_app.get('histogram_data')[-1] return perf_by_host - def list_once(self, perf=False, json_fmt=False): + def list_once(self, perf=False, json_fmt=False, show_apps=False): """ Display the list of EAA connectors as comma separated CSV or JSON TODO: refactor this method, too long @@ -110,8 +113,7 @@ def list_once(self, perf=False, json_fmt=False): if perf: header += ",last_upd,CPU%,Mem%,Disk%,NetworkMbps,do_total,do_idle,do_active" format_line += ",{ts},{cpu},{mem},{disk},{network},{dialout_total},{dialout_idle},{dialout_active}" - - if perf: # Add performance metrics in the report + # Add performance metrics in the report perf_res_list = None signal.signal(signal.SIGTERM, signal.SIG_DFL) with Pool(ConnectorAPI.POOL_SIZE) as p: @@ -151,6 +153,12 @@ def list_once(self, perf=False, json_fmt=False): "dialout_idle": perf_latest.get('dialout_idle') or ConnectorAPI.NODATA_JSON, "dialout_active": perf_latest.get('active_dialout_count') or ConnectorAPI.NODATA_JSON }) + # Help SIEM with the mapping connector <-> apps + if show_apps: + apps = [] + for a in self.findappbyconnector(EAAItem("con://" + c.get('uuid_url'))): + apps.append(str(a[2])) + data.update({"apps": apps}) if not json_fmt: cli.print(format_line.format( scheme=EAAItem.Type.Connector.scheme, @@ -176,7 +184,7 @@ def list_once(self, perf=False, json_fmt=False): if not json_fmt: cli.footer("Total %s connector(s)" % total_con) - def list(self, perf, json_fmt, follow=False, interval=300, stop_event=None): + def list(self, perf, json_fmt, show_apps=False, follow=False, interval=300, stop_event=None): """ List the connector and their attributes and status The default output is CSV @@ -184,6 +192,7 @@ def list(self, perf, json_fmt, follow=False, interval=300, stop_event=None): Args: perf (bool): Add performance data (cpu, mem, disk, dialout) json_fmt (bool): Output as JSON instead of CSV + show_apps (bool): Add an extra field 'apps' as array of application UUID follow (bool): Never stop until Control+C or SIGTERM is received interval (float): Interval in seconds between pulling the API, default is 5 minutes (300s) stop_event (Event): Main program stop event allowing the function @@ -192,7 +201,7 @@ def list(self, perf, json_fmt, follow=False, interval=300, stop_event=None): while True or (stop_event and not stop_event.is_set()): try: start = time.time() - self.list_once(perf, json_fmt) + self.list_once(perf, json_fmt, show_apps) if follow: sleep_time = interval - (time.time() - start) @@ -213,6 +222,24 @@ def list(self, perf, json_fmt, follow=False, interval=300, stop_event=None): else: raise + @lru_cache(maxsize=1) + def all_apps(self, exp): + """ + This method is expensive in time so we use `lru_cache decorator` + with a size of 1, combined with a functiom argument `exp` that will be + used as expiration or cache key. + + Args: + exp (integer): cache key + Returns: + [dict]: JSON dictionnary containing all the applications for this tenant + """ + url_params = {'limit': ApplicationAPI.LIMIT_SOFT, 'expand': 'true'} + search_app = self.get('mgmt-pop/apps', params=url_params) + print(search_app.json()) + return search_app.json() + + def findappbyconnector(self, connector_moniker): """ Find EAA Applications using a particular connector. @@ -221,20 +248,23 @@ def findappbyconnector(self, connector_moniker): connector_moniker (EAAItem): Connector ID. Returns: - Tuple of 3 values: + Tuple of 4 values: - application moniker - application name - - application host (external hostname) + - application host (external hostname - FQDN) - dialout version (1 or 2) 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() + + now = time.time() + exp = now - now % (-1 * ConnectorAPI.APP_CACHE_TTL) + apps = self.all_apps(exp) + logging.debug("Searching app using %s..." % connector_moniker) for app in apps.get('objects', []): # Only tunnel apps are using Dialout Version 2 @@ -242,10 +272,13 @@ def findappbyconnector(self, connector_moniker): for con in app.get('agents', []): app_moniker = EAAItem("app://" + app.get('uuid_url')) con_moniker = EAAItem("con://" + con.get('uuid_url')) + app_host = app.get('host') + if app.get('domain') == 2: + app_host += "." + app.get('domain_suffix') if con_moniker == connector_moniker: yield app_moniker, \ app.get('name'), \ - app.get('host'), \ + app_host, \ dialout_ver def list_apps(self, con_moniker, perf=False):