diff --git a/README.md b/README.md index ffad41c..60d86b4 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,9 @@ - [Add items to a list](#add-items-to-a-list) - [Deploy list changes](#deploy-list-changes) - [Query Akamai IOC inteligence database](#query-akamai-ioc-inteligence-database) +- [Reporting](#reporting) + - [List of sub-tenants](#list-of-sub-tenants) + - [Active ETP Client by sub-tenant](#active-etp-client-by-sub-tenant) - [Troubleshooting](#troubleshooting) - [ERROR: Exiting with code 404, reason: Code-130009](#error-exiting-with-code-404-reason-code-130009) @@ -202,6 +205,41 @@ $ akamai etp ioc changes akamaietpmalwaretest.com $ akamai etp ioc timeseries akamaietpmalwaretest.com ``` +## Reporting + +### List of sub-tenants + +List all the sub tenants in the account, with their respective ETP Config ID and name +``` +$ akamai etp tenant list +# Command: akamai etp tenant list +# config_id, tenant_name +12345,Tenant A +23456,Tenant B +34567,Tenant C +45678,Tenant D +56789,Tenant E +``` + +### Active ETP Client by sub-tenant + +Report number of active ETP clients for the last 30 days by sub-tenant. +You can specify the time interval passing EPOCH integer as `--start` and `--end`. +CSV output headers are sent to *stderr*, body with the rows are sent to *stdout* to allow to pipe commands. + +``` +$ akamai etp tenant clients +# Command: akamai etp tenant clients +# Report start: 1640721997 +# Report end: 1643313997 +# config_id, tenant_name, active_client_count +12345,Tenant A,10 +23456,Tenant B,20 +34567,Tenant C,30 +45678,Tenant D,40 +56789,Tenant E,50 +``` + ## Troubleshooting ### ERROR: Exiting with code 404, reason: Code-130009 diff --git a/bin/akamai-etp b/bin/akamai-etp index ed72f13..d93a422 100755 --- a/bin/akamai-etp +++ b/bin/akamai-etp @@ -27,6 +27,7 @@ from threading import Event from enum import Enum import logging from urllib.parse import parse_qs +import csv # 3rd party modules @@ -35,7 +36,7 @@ from requests.compat import urljoin from akamai.edgegrid import EdgeGridAuth, EdgeRc from config import EdgeGridConfig -__version__ = "0.3.6" +__version__ = "0.3.7" #: Window span in ad-hoc mode, default is 15 min span_duration_min = 15 @@ -150,6 +151,33 @@ def build_params(params=None): return final_params +class cli: + """ + Various shared functions for CLI + """ + @staticmethod + def write(s): + print(s) + + @staticmethod + def write_header(s): + sys.stderr.write(s) + sys.stderr.write("\n") + + @staticmethod + def write_footer(s): + cli.write_header(s) + + @staticmethod + def write_columns(row): + writer = csv.writer(sys.stdout) + writer.writerow(row) + + @staticmethod + def current_command(): + return "akamai etp " + " ".join(sys.argv[1:]) + + def input2feed(event_type): api_eventtype = None if event_type == "threat": @@ -347,6 +375,59 @@ class ioc: print(resp.text) +class report: + + @staticmethod + def active_clients(start, end, configId=None): + path = '/etp-config/v3/configs/{configId}/client/status'.format(configId = configId or config.etp_config_id) + params = {'startTimeSec': start, 'endTimeSec': end} + resp = session.get(urljoin(baseurl, path), params=build_params(params)) + grand_total = 0 + for os in resp.json().get('installed', []): + grand_total += os.get('total', 0) + return grand_total + + +class tenant: + """ + Operate on sub-tenants, ETP feature introduce late 2021. + """ + @staticmethod + def _get_all(): + path = '/etp-config/v3/configs/{configId}/tenants'.format(configId = config.etp_config_id) + resp = session.get(urljoin(baseurl, path)) + if resp.status_code != 200: + sys.stderr.write(f"Error fetching tenants:\n{resp.text}\n") + sys.exit(2) + return resp.json() + + @staticmethod + def list(): + tenants = tenant._get_all() + cli.write_header('# Command: {0}'.format(cli.current_command())) + cli.write_header("# ConfigID, name") + for t in tenants: + cli.write_columns((t.get("id"), t.get("name"))) + + @staticmethod + def report_active_clients(): + + tenants = tenant._get_all() + + # Date/time boundaries + end = config.end or int(time.time()) + start = config.start or (end - (30 * 24 * 60 * 60)) + + # CSV Headers + cli.write_header('# Command: {0}'.format(cli.current_command())) + cli.write_header(f'# Report start: {start}') + cli.write_header(f'# Report end: {end}') + cli.write_header("# config_id, tenant_name, active_client_count") + + for t in tenants: + cli.write_columns((t.get('id'), t.get('name'), report.active_clients(start, end, t.get('id')))) + + def log_level(): if config.debug: return logging.DEBUG @@ -456,6 +537,13 @@ def main(): ioc.timeseries(config.domain) elif config.ioc_action == "changes": ioc.changes(config.domain) + elif config.command == "tenant": + if config.operation == "list": + tenant.list() + elif config.operation == "clients": + tenant.report_active_clients() + else: + print("Not supported") if __name__ == '__main__': main() \ No newline at end of file diff --git a/bin/config.py b/bin/config.py index 6cbb5c8..185e377 100644 --- a/bin/config.py +++ b/bin/config.py @@ -39,6 +39,7 @@ def __init__(self, config_values, configuration, flags=None): parser = self.parser subparsers = parser.add_subparsers(dest="command", help='ETP object to manipulate') + # Security Events event_parser = subparsers.add_parser("event", help="Fetch last events (from 1h15 ago to 1 hour ago)", epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) event_parser.add_argument('event_type', nargs='?', default="threat", @@ -55,6 +56,7 @@ def __init__(self, config_values, configuration, flags=None): event_parser.add_argument('--limit', type=int, default=3*60*60, help="Stop the most recent fetch to now minus specified seconds, default is 3 hours. Applicable to --tail") + # ETP Lists list_parser = subparsers.add_parser("list", help="Manage ETP security list", epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) subsub = list_parser.add_subparsers(dest="list_action", help='List action') @@ -78,6 +80,7 @@ def __init__(self, config_values, configuration, flags=None): epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) listdeploy.add_argument('listid', type=int, metavar='listid', help='ETP list ID') + # IOC ioc_parser = subparsers.add_parser("ioc", help="Manage Indicator of Compromise (IOC) feed intelligence", epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) iocsubsub = ioc_parser.add_subparsers(dest="ioc_action", help='List action') @@ -88,6 +91,18 @@ def __init__(self, config_values, configuration, flags=None): ioc_changes = iocsubsub.add_parser("changes", help="Information on a particular internet domain") ioc_changes.add_argument('domain', type=str, metavar='domain', help='Internet domain (eg. example.com)') + # Sub-tenants + tenant_parser = subparsers.add_parser("tenant", help="Manage ETP Account sub-tenants", + epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + tenant_operation = tenant_parser.add_subparsers(dest="operation", help='Sub-tenant operation') + tenant_list = tenant_operation.add_parser("list", help="List all tenants in the account", + epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + tenant_reportclient = tenant_operation.add_parser("clients", help="Active ETP Client for the last 30 days per tenant", + epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) + tenant_reportclient.add_argument('--start', '-s', type=int, help="Start datetime (EPOCH),\nDefault is 1h ago") + tenant_reportclient.add_argument('--end', '-e', type=int, help="End datetime (EPOCH),\nDefault is now") + + # General options subparsers.add_parser("version", help="Display CLI ETP module version", epilog=epilog, formatter_class=argparse.RawTextHelpFormatter) diff --git a/cli.json b/cli.json index 6da3381..503c5df 100755 --- a/cli.json +++ b/cli.json @@ -5,7 +5,7 @@ "commands": [ { "name": "etp", - "version": "0.3.6", + "version": "0.3.7", "description": "Akamai CLI for Enterprise Threat Protector" } ]