{"id":11343,"date":"2024-12-05T13:01:37","date_gmt":"2024-12-05T18:01:37","guid":{"rendered":"https:\/\/www.rushworth.us\/lisa\/?p=11343"},"modified":"2025-01-08T13:05:38","modified_gmt":"2025-01-08T18:05:38","slug":"sumo-logic-running-queries-via-api","status":"publish","type":"post","link":"https:\/\/www.rushworth.us\/lisa\/?p=11343","title":{"rendered":"Sumo Logic: Running Queries via API"},"content":{"rendered":"\n<p>This is my base script for using the Sumo Logic API to query logs and analyze data. This particular script finds hosts sending syslog data successfully through our firewall, looks who owns the netblock (they weren&#8217;t all internal!), and checks our configuration management database (cmdb) to see if we have a host registered with the destination IP address of the syslog traffic. <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nimport requests\nfrom requests.auth import HTTPBasicAuth\nimport time\nfrom collections import defaultdict\nimport cx_Oracle\nimport pandas as pd\nimport ipaddress\nfrom datetime import datetime\nfrom ipwhois import IPWhois\nfrom ipwhois.exceptions import IPDefinedError\n\n# Import credentials from a config file\nfrom config import access_id, access_key, oracle_username, oracle_password\n\n# Initialize Oracle Client\ncx_Oracle.init_oracle_client(lib_dir=r&quot;C:\\Oracle\\instantclient_21_15&quot;)\noracle_dsn = cx_Oracle.makedsn(&#039;cmdb_db.example.com&#039;, 1521, service_name=&#039;cmdb_db.example.com&#039;)\n\n# Function to query Oracle database\ndef query_oracle_cmdb(strIPAddress):\n    with cx_Oracle.connect(user=oracle_username, password=oracle_password, dsn=oracle_dsn) as connection:\n        cursor = connection.cursor()\n        query = &quot;&quot;&quot;\n            SELECT HOSTNAME, FRIENDLYNAME, STATUS, COLLECTIONTIME, RETIREDBYDISPLAYNAME, \n                    RETIREDDATETIME, SERVERAPPSUPPORTTEAM, SERVERENVIRONMENT\n            FROM NBIREPORT.CHERWELL_CMDBDATA_FULL\n            WHERE IPADDRESS = :ipaddy\n        &quot;&quot;&quot;\n        cursor.execute(query, &#x5B;strIPAddress])\n        result = cursor.fetchone()\n        cursor.close()\n        return result if result else (&quot;&quot;,) * 8\n\n# Function to determine IP ownership\ndef get_ip_ownership(ip):\n    # Define internal IP ranges\n    internal_networks = &#x5B;\n        ipaddress.IPv4Network(&quot;10.0.0.0\/8&quot;),\n        ipaddress.IPv4Network(&quot;172.16.0.0\/12&quot;),\n        ipaddress.IPv4Network(&quot;192.168.0.0\/16&quot;)\n    ]\n    \n    # Check if the IP is internal\n    ip_obj = ipaddress.IPv4Address(ip)\n    if any(ip_obj in network for network in internal_networks):\n        return &quot;INTERNAL&quot;\n    \n    # For external IPs, use ipwhois to get ownership info\n    try:\n        obj = IPWhois(ip)\n        result = obj.lookup_rdap(depth=1)\n        ownership = result&#x5B;&#039;network&#039;]&#x5B;&#039;name&#039;]\n    except IPDefinedError:\n        ownership = &quot;Reserved IP&quot;\n    except Exception as e:\n        print(f&quot;Error looking up IP {ip}: {e}&quot;)\n        ownership = &quot;UNKNOWN&quot;\n    \n    return ownership\n\n# Base URL for Sumo Logic API\nbase_url = &#039;https:\/\/api.sumologic.com\/api\/v1&#039;\n\n# Define the search query\nsearch_query = &#039;&#039;&#039;\n(dpt=514)\nAND _sourcecategory = &quot;observe\/perimeter\/firewall\/logs&quot;\n| where !(act = &quot;deny&quot;)\n| where !(act = &quot;timeout&quot;)\n| where !(act = &quot;ip-conn&quot;)\n| where (proto=17 or proto=6)\n| count dst, act\n&#039;&#039;&#039;\n\n# Function to create and manage search jobs\ndef run_search_job(start_time, end_time):\n    search_job_data = {\n        &#039;query&#039;: search_query,\n        &#039;from&#039;: start_time,\n        &#039;to&#039;: end_time,\n        &#039;timeZone&#039;: &#039;UTC&#039;\n    }\n\n    # Create a search job\n    search_job_url = f&#039;{base_url}\/search\/jobs&#039;\n    response = requests.post(\n        search_job_url,\n        auth=HTTPBasicAuth(access_id, access_key),\n        json=search_job_data\n    )\n\n    if response.status_code != 202:\n        print(&#039;Error starting search job:&#039;, response.status_code, response.text)\n        return None\n\n    # Get the search job ID\n    job_id = response.json()&#x5B;&#039;id&#039;]\n    print(&#039;Search Job ID:&#039;, job_id)\n\n    # Poll for the search job status\n    job_status_url = f&#039;{search_job_url}\/{job_id}&#039;\n    while True:\n        response = requests.get(job_status_url, auth=HTTPBasicAuth(access_id, access_key))\n        status = response.json().get(&#039;state&#039;, None)\n        print(&#039;Search Job Status:&#039;, status)\n        if status in &#x5B;&#039;DONE GATHERING RESULTS&#039;, &#039;CANCELLED&#039;, &#039;FAILED&#039;]:\n            break\n        time.sleep(5)  # Reasonable delay to prevent overwhelming the server\n\n    return job_id if status == &#039;DONE GATHERING RESULTS&#039; else None\n\n# Function to retrieve results of a search job\ndef retrieve_results(job_id):\n    dst_counts = defaultdict(int)\n    results_url = f&#039;{base_url}\/search\/jobs\/{job_id}\/messages&#039;\n    offset = 0\n    limit = 1000\n\n    while True:\n        params = {&#039;offset&#039;: offset, &#039;limit&#039;: limit}\n        try:\n            response = requests.get(results_url, auth=HTTPBasicAuth(access_id, access_key), params=params, timeout=30)\n            if response.status_code == 200:\n                results = response.json()\n                messages = results.get(&#039;messages&#039;, &#x5B;])\n                \n                for message in messages:\n                    message_map = message&#x5B;&#039;map&#039;]\n                    dst = message_map.get(&#039;dst&#039;)\n                    if dst:\n                        dst_counts&#x5B;dst] += 1\n                \n                if len(messages) &lt; limit:\n                    break\n\n                offset += limit\n            else:\n                print(&#039;Error retrieving results:&#039;, response.status_code, response.text)\n                break\n        except requests.exceptions.RequestException as e:\n            print(f&#039;Error during request: {e}&#039;)\n            time.sleep(5)\n            continue\n\n    return dst_counts\n\n# Main execution\nif __name__ == &quot;__main__&quot;:\n    # Prompt for the start date\n    start_date_input = input(&quot;Enter the start date (YYYY-MM-DD): &quot;)\n    try:\n        start_time = datetime.strptime(start_date_input, &quot;%Y-%m-%d&quot;).strftime(&quot;%Y-%m-%dT00:00:00&quot;)\n    except ValueError:\n        print(&quot;Invalid date format. Please enter the date in YYYY-MM-DD format.&quot;)\n        exit()\n\n    # Use today&#039;s date as the end date\n    end_time = datetime.now().strftime(&quot;%Y-%m-%dT00:00:00&quot;)\n\n    # Create a search job\n    job_id = run_search_job(start_time, end_time)\n    if job_id:\n        # Retrieve and process results\n        dst_counts = retrieve_results(job_id)\n\n        # Prepare data for Excel\n        data_for_excel = &#x5B;]\n\n        print(&quot;\\nDestination IP Counts and Oracle Data:&quot;)\n        for dst, count in dst_counts.items():\n            oracle_data = query_oracle_cmdb(dst)\n            ownership = get_ip_ownership(dst)\n            # Use only Oracle data columns\n            combined_data = (dst, count, ownership) + oracle_data\n            data_for_excel.append(combined_data)\n            print(combined_data)\n\n        # Create a DataFrame and write to Excel\n        df = pd.DataFrame(data_for_excel, columns=&#x5B;\n            &quot;IP Address&quot;, &quot;Occurrence Count&quot;, &quot;Ownership&quot;,\n            &quot;CMDB_Hostname&quot;, &quot;CMDB_Friendly Name&quot;, &quot;CMDB_Status&quot;, &quot;CMDB_Collection Time&quot;, \n            &quot;CMDB_Retired By&quot;, &quot;CMDB_Retired Date&quot;, &quot;CMDB_Support Team&quot;, &quot;CMDB_Environment&quot;\n        ])\n\n        # Generate the filename with current date and time\n        timestamp = datetime.now().strftime(&quot;%Y%m%d-%H%M&quot;)\n        output_file = f&quot;{timestamp}-sumo_oracle_data.xlsx&quot;\n        df.to_excel(output_file, index=False)\n        print(f&quot;\\nData written to {output_file}&quot;)\n    else:\n        print(&#039;Search job did not complete successfully.&#039;)\n<\/pre><\/div>","protected":false},"excerpt":{"rendered":"<p>This is my base script for using the Sumo Logic API to query logs and analyze data. This particular script finds hosts sending syslog data successfully through our firewall, looks who owns the netblock (they weren&#8217;t all internal!), and checks our configuration management database (cmdb) to see if we have a host registered with the &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2076],"tags":[664,2074,2075],"class_list":["post-11343","post","type-post","status-publish","format-standard","hentry","category-sumo-logic","tag-python","tag-sumo-logic","tag-sumo-logic-api"],"_links":{"self":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/11343","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=11343"}],"version-history":[{"count":1,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/11343\/revisions"}],"predecessor-version":[{"id":11344,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/11343\/revisions\/11344"}],"wp:attachment":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=11343"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=11343"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=11343"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}