├── requirements.txt ├── .gitignore ├── Dockerfile ├── config.example.json ├── README.md ├── LICENSE └── snort_blocklist_importer.py /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.24.0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | 3 | blocklist.json 4 | config.json 5 | 6 | venv/ 7 | .vscode/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | 3 | ADD config.json /app/ 4 | ADD requirements.txt /app/ 5 | ADD snort_blocklist_importer.py /app/ 6 | 7 | WORKDIR /app 8 | 9 | RUN pip install --no-cache-dir -r requirements.txt 10 | 11 | CMD ["python", "-u", "snort_blocklist_importer.py", "-d"] 12 | -------------------------------------------------------------------------------- /config.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "SNORT_BLOCKLIST_URL": "https://snort.org/downloads/ip-block-list", 3 | "SW_ADDRESS": "", 4 | "SW_USERNAME": "", 5 | "SW_PASSWORD": "", 6 | "SW_TENANT_ID": "", 7 | "SW_TAG_ID": "", 8 | "SW_CREATE_CSE": true, 9 | "SW_CSE_ID": "" 10 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stealthwatch Enterprise: Snort Blocklist Importer 2 | 3 | [![published](https://static.production.devnetcloud.com/codeexchange/assets/images/devnet-published.svg)](https://developer.cisco.com/codeexchange/github/repo/CiscoSE/SnortBlocklistImporter) 4 | 5 | ## Summary 6 | 7 | This is a script to import Snort's Sample IP Blocklist into a Tag (Host Group) within Stealthwatch Enterprise. This will also optionally create a Custom Security Event (CSE) to alert on traffic to the blocklisted IPs. 8 | 9 | You can find more information on Stealthwatch's APIs on [Cisco DevNet](https://developer.cisco.com/docs/stealthwatch/). 10 | 11 | ## Requirements 12 | 13 | 1. Python 3.x 14 | 2. Stealthwatch Enterprise 7.0 or higher 15 | - Update files and documentation can be found in the Network Visibility and Segmentation product category on [software.cisco.com](https://software.cisco.com/download/home/286307082) 16 | 3. Stealthwatch Enterprise user credentials with the "Master Admin" role assigned. 17 | - User roles are configured in the Stealthwatch web interface. Simply navigate to *Global Settings -> User Management*. 18 | 19 | ## Configuration File 20 | 21 | The ***config.json*** file contains the following variables: 22 | 23 | - SNORT_BLOCKLIST_URL: The URL for the Snort IP Blocklist. (String) 24 | - SW_ADDRESS: The IP or FQDN of the Stealthwatch SMC. (String) 25 | - SW_USERNAME: The Username to be used to authenticate to Stealthwatch. (String) 26 | - SW_PASSWORD: The Password to be used to authenticate to Stealthwatch. (String) 27 | - SW_TENANT_ID: The Stealthwatch Tenant (Domain) ID to be used. (Integer) 28 | - SW_TAG_ID: The Tag (Host Group) ID for the blocklist IPs. (Integer) 29 | - SW_CREATE_CSE: Whether a Custom Security Event should be created. (Boolean) 30 | - SW_CSE_ID: The ID of the Custom Security Event. (Integer) 31 | 32 | ## How To Run 33 | 34 | 1. Prior to running the script for the first time, copy the ***config.example.json*** to ***config.json***. 35 | * ```cp config.example.json config.json``` 36 | * **OPTIONAL:** You can manually enter configuration data in the ***config.json*** file if desired. By default, the script will assume it needs to create a Tag (Host Group) and Custom Security Event, unless IDs for each are populated in the ***config.json***. 37 | 2. Install the required packages from the ***requirements.txt*** file. 38 | * ```pip install -r requirements.txt``` 39 | * You'll probably want to set up a virtual environment: [Python 'venv' Tutorial](https://docs.python.org/3/tutorial/venv.html) 40 | * Activate the Python virtual environment, if you created one. 41 | 3. Run the script with ```python snort_blocklist_importer.py``` 42 | 43 | > If you didn't manually enter configuration data, you'll get prompted for the Stealthwatch IP/FQDN, Username, and Password. The script will store these credentials in the ***config.json*** file for future use. **This means you probably want to make the ***config.json*** file read-only. You probably will also want to create unique credentials for scripting/API purposes.** 44 | 45 | The script will automatically try to determine your Stealthwatch Tenant ID, and store that in the ***config.json*** file as well. 46 | 47 | By default, the script will cache downloaded blocklist data from Snort for one hour to prevent creating too many requests. (You'll get greylisted if you make too many requests for the URL) 48 | 49 | ## Docker Container 50 | 51 | This script is Docker friendly, and can be deployed as a container. 52 | 53 | To build the container, run the script once to populate the ***config.json*** file, or manually populate the configuration variables. 54 | 55 | Once the ***config.json*** file is populated, run the following command to build the container: 56 | 57 | - ```docker build -t snort-blocklist-importer .``` 58 | 59 | You can then run the container as a daemon with the following command: 60 | 61 | - ```docker run -d --name snort-blocklist-importer snort-blocklist-importer``` 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CISCO SAMPLE CODE LICENSE 2 | Version 1.0 3 | Copyright (c) 2017 Cisco and/or its affiliates 4 | 5 | These terms govern this Cisco example or demo source code and its 6 | associated documentation (together, the "Sample Code"). By downloading, 7 | copying, modifying, compiling, or redistributing the Sample Code, you 8 | accept and agree to be bound by the following terms and conditions (the 9 | "License"). If you are accepting the License on behalf of an entity, you 10 | represent that you have the authority to do so (either you or the entity, 11 | "you"). Sample Code is not supported by Cisco TAC and is not tested for 12 | quality or performance. This is your only license to the Sample Code and 13 | all rights not expressly granted are reserved. 14 | 15 | 1. LICENSE GRANT: Subject to the terms and conditions of this License, 16 | Cisco hereby grants to you a perpetual, worldwide, non-exclusive, non- 17 | transferable, non-sublicensable, royalty-free license to copy and 18 | modify the Sample Code in source code form, and compile and 19 | redistribute the Sample Code in binary/object code or other executable 20 | forms, in whole or in part, solely for use with Cisco products and 21 | services. For interpreted languages like Java and Python, the 22 | executable form of the software may include source code and 23 | compilation is not required. 24 | 25 | 2. CONDITIONS: You shall not use the Sample Code independent of, or to 26 | replicate or compete with, a Cisco product or service. Cisco products 27 | and services are licensed under their own separate terms and you shall 28 | not use the Sample Code in any way that violates or is inconsistent 29 | with those terms (for more information, please visit: 30 | www.cisco.com/go/terms. 31 | 32 | 3. OWNERSHIP: Cisco retains sole and exclusive ownership of the Sample 33 | Code, including all intellectual property rights therein, except with 34 | respect to any third-party material that may be used in or by the 35 | Sample Code. Any such third-party material is licensed under its own 36 | separate terms (such as an open source license) and all use must be in 37 | full accordance with the applicable license. This License does not 38 | grant you permission to use any trade names, trademarks, service 39 | marks, or product names of Cisco. If you provide any feedback to Cisco 40 | regarding the Sample Code, you agree that Cisco, its partners, and its 41 | customers shall be free to use and incorporate such feedback into the 42 | Sample Code, and Cisco products and services, for any purpose, and 43 | without restriction, payment, or additional consideration of any kind. 44 | If you initiate or participate in any litigation against Cisco, its 45 | partners, or its customers (including cross-claims and counter-claims) 46 | alleging that the Sample Code and/or its use infringe any patent, 47 | copyright, or other intellectual property right, then all rights 48 | granted to you under this License shall terminate immediately without 49 | notice. 50 | 51 | 4. LIMITATION OF LIABILITY: CISCO SHALL HAVE NO LIABILITY IN CONNECTION 52 | WITH OR RELATING TO THIS LICENSE OR USE OF THE SAMPLE CODE, FOR 53 | DAMAGES OF ANY KIND, INCLUDING BUT NOT LIMITED TO DIRECT, INCIDENTAL, 54 | AND CONSEQUENTIAL DAMAGES, OR FOR ANY LOSS OF USE, DATA, INFORMATION, 55 | PROFITS, BUSINESS, OR GOODWILL, HOWEVER CAUSED, EVEN IF ADVISED OF THE 56 | POSSIBILITY OF SUCH DAMAGES. 57 | 58 | 5. DISCLAIMER OF WARRANTY: SAMPLE CODE IS INTENDED FOR EXAMPLE PURPOSES 59 | ONLY AND IS PROVIDED BY CISCO "AS IS" WITH ALL FAULTS AND WITHOUT 60 | WARRANTY OR SUPPORT OF ANY KIND. TO THE MAXIMUM EXTENT PERMITTED BY 61 | LAW, ALL EXPRESS AND IMPLIED CONDITIONS, REPRESENTATIONS, AND 62 | WARRANTIES INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTY OR 63 | CONDITION OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON- 64 | INFRINGEMENT, SATISFACTORY QUALITY, NON-INTERFERENCE, AND ACCURACY, 65 | ARE HEREBY EXCLUDED AND EXPRESSLY DISCLAIMED BY CISCO. CISCO DOES NOT 66 | WARRANT THAT THE SAMPLE CODE IS SUITABLE FOR PRODUCTION OR COMMERCIAL 67 | USE, WILL OPERATE PROPERLY, IS ACCURATE OR COMPLETE, OR IS WITHOUT 68 | ERROR OR DEFECT. 69 | 70 | 6. GENERAL: This License shall be governed by and interpreted in 71 | accordance with the laws of the State of California, excluding its 72 | conflict of laws provisions. You agree to comply with all applicable 73 | United States export laws, rules, and regulations. If any provision of 74 | this License is judged illegal, invalid, or otherwise unenforceable, 75 | that provision shall be severed and the rest of the License shall 76 | remain in full force and effect. No failure by Cisco to enforce any of 77 | its rights related to the Sample Code or to a breach of this License 78 | in a particular situation will act as a waiver of such rights. In the 79 | event of any inconsistencies with any other terms, this License shall 80 | take precedence. 81 | -------------------------------------------------------------------------------- /snort_blocklist_importer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ##################### 5 | # ABOUT THIS SCRIPT # 6 | ##################### 7 | # 8 | # snort_blocklist_importer.py 9 | # ---------------- 10 | # Author: Alan Nix 11 | # Contibutions by: Joel Esler 12 | # Property of: Cisco Systems 13 | # Version: 1.2 14 | # Release Date: 06/26/2019 15 | # 16 | ############################################################ 17 | 18 | import argparse 19 | import getpass 20 | import json 21 | import os 22 | import shutil 23 | import time 24 | 25 | import requests 26 | 27 | from requests.packages import urllib3 28 | from requests.auth import HTTPBasicAuth 29 | 30 | # If receiving SSL Certificate Errors, un-comment the line below 31 | urllib3.disable_warnings() 32 | 33 | # Setup an API session 34 | API_SESSION = requests.Session() 35 | 36 | # Config Paramters 37 | CONFIG_FILE = os.path.join(os.path.dirname(__file__), 'config.json') 38 | CONFIG_FILE_EXAMPLE = os.path.join(os.path.dirname(__file__), 'config.example.json') 39 | CONFIG_DATA = {} 40 | 41 | # Set a wait interval (in seconds) - don't make this too short or you'll get greylisted 42 | INTERVAL = 3600 43 | 44 | # Snort Blocklist Cache 45 | SNORT_DATA_FILE = "blocklist.json" 46 | 47 | #################### 48 | # FUNCTIONS # 49 | #################### 50 | 51 | 52 | def load_config(retry=False): 53 | """Load configuration data from file""" 54 | 55 | print("Loading configuration data...") 56 | 57 | # If we have a stored config file, then use it, otherwise terminate 58 | if os.path.isfile(CONFIG_FILE): 59 | 60 | # Open the CONFIG_FILE and load it 61 | with open(CONFIG_FILE, 'r') as config_file: 62 | CONFIG_DATA = json.loads(config_file.read()) 63 | 64 | print("Configuration data loaded successfully.") 65 | 66 | return CONFIG_DATA 67 | 68 | else: 69 | # Check to see if this is the initial load_config attempt 70 | if not retry: 71 | 72 | # Print that we couldn't find the config file, and attempt to copy the example 73 | print("The configuration file was not found. Copying 'config.example.json' file to '{}', and retrying...".format(CONFIG_FILE)) 74 | shutil.copyfile(CONFIG_FILE_EXAMPLE, CONFIG_FILE) 75 | 76 | # Try to reload the config 77 | return load_config(retry=True) 78 | else: 79 | 80 | # Exit gracefully if we cannot load the config 81 | print("Unable to automatically create config file. Please copy 'config.example.json' to '{}' manually.".format(CONFIG_FILE)) 82 | exit() 83 | 84 | 85 | def save_config(): 86 | """Save configuration data to file""" 87 | 88 | with open(CONFIG_FILE, 'w') as output_file: 89 | json.dump(CONFIG_DATA, output_file, indent=4) 90 | 91 | 92 | def get_blocklist(): 93 | """Check to see if we have a cached blocklist, fetch a new one if needed, then return the results""" 94 | 95 | # Check to see if we have cached data 96 | if os.path.isfile(SNORT_DATA_FILE): 97 | print("Cached blocklist found.") 98 | 99 | # Get the delta of the current time and the file modified time 100 | time_delta = time.time() - os.path.getmtime(SNORT_DATA_FILE) 101 | 102 | # If the file is less than an hour old, use it 103 | if time_delta < INTERVAL: 104 | print("Cached blocklist was less than {} seconds old. Using it.".format(INTERVAL)) 105 | 106 | # Open the CONFIG_FILE and load it 107 | with open(SNORT_DATA_FILE, 'r') as blocklist_file: 108 | ip_list = json.load(blocklist_file) 109 | 110 | else: 111 | print("Cached blocklist was too old, getting a new one.") 112 | 113 | # Get a new blocklist 114 | ip_list = get_new_blocklist() 115 | 116 | else: 117 | print("No cached blocklist was found, getting a new one.") 118 | 119 | # Get a new blocklist 120 | ip_list = get_new_blocklist() 121 | 122 | return ip_list 123 | 124 | 125 | def get_new_blocklist(): 126 | """Retrieve the Snort Blocklist and return a list of IPs""" 127 | 128 | try: 129 | # Get the IP Blocklist data from Snort.org 130 | response = requests.get(CONFIG_DATA["SNORT_BLOCKLIST_URL"], stream=True) 131 | 132 | # If the request was successful 133 | if response.status_code >= 200 or response.status_code < 300: 134 | 135 | # A placeholder list for IPs 136 | ip_list = [] 137 | 138 | # Add each IP address to our ip_list 139 | for line in response.iter_lines(): 140 | 141 | # Decode the line 142 | line = line.decode("utf-8") 143 | 144 | # Make sure we haven't exceeded our requests per minute maximum 145 | if "exceeded" in line: 146 | print("It looks like we've exceeded the request maximum for the blocklist. Terminating.") 147 | exit() 148 | 149 | if "DOCTYPE" in line: 150 | print("Got garbage back from the Snort.org blocklist. Terminating.") 151 | exit() 152 | 153 | if line: 154 | ip_list.append(line) 155 | 156 | # Cache the data from Snort.org 157 | with open(SNORT_DATA_FILE, 'w') as output_file: 158 | json.dump(ip_list, output_file, indent=4) 159 | 160 | return ip_list 161 | 162 | else: 163 | print("Failed to get data from Snort.org. Terminating.") 164 | exit() 165 | 166 | except Exception as err: 167 | print("Unable to get the Snort.org Blocklist - Error: {}".format(err)) 168 | exit() 169 | 170 | 171 | def get_access_token(): 172 | """Get an Access Token from the Stealthwatch API""" 173 | 174 | print("Authenticating to Stealthwatch...") 175 | 176 | # The URL to authenticate to the SMC 177 | url = "https://{}/token/v2/authenticate".format(CONFIG_DATA["SW_ADDRESS"]) 178 | 179 | print("Stealthwatch Authentication URL: {}".format(url)) 180 | 181 | # JSON to hold the authentication credentials 182 | login_credentials = { 183 | "username": CONFIG_DATA["SW_USERNAME"], 184 | "password": CONFIG_DATA["SW_PASSWORD"] 185 | } 186 | 187 | try: 188 | # Make an authentication request to the SMC 189 | response = API_SESSION.post(url, data=login_credentials, verify=False) 190 | 191 | # If the request was successful, then proceed 192 | if response.status_code == 200: 193 | print("Successfully Authenticated.") 194 | 195 | return response.text 196 | 197 | else: 198 | print("SMC Connection Failure - HTTP Return Code: {}\nResponse: {}".format(response.status_code, response.text)) 199 | exit() 200 | 201 | except Exception as err: 202 | print("Unable to post to the SMC - Error: {}".format(err)) 203 | exit() 204 | 205 | 206 | def get_tenants(): 207 | """Get the "tenants" (domains) from Stealthwatch""" 208 | 209 | print("Fetching Stealthwatch Tenants...") 210 | 211 | # The URL to get tenants 212 | url = "https://{}/sw-reporting/v1/tenants/".format(CONFIG_DATA["SW_ADDRESS"]) 213 | 214 | print("Stealthwatch Tenant URL: {}".format(url)) 215 | 216 | try: 217 | # Get the tenants from Stealthwatch 218 | response = API_SESSION.get(url, verify=False) 219 | 220 | # If the request was successful, then proceed, otherwise terminate. 221 | if response.status_code == 200: 222 | 223 | # Parse the response as JSON 224 | tenants = response.json()["data"] 225 | 226 | # Set the Domain ID if theres only one, or prompt the user if there are multiple 227 | if len(tenants) == 1: 228 | selected_tenant_id = tenants[0]["id"] 229 | else: 230 | selected_item = selection_list("Tenants", "displayName", tenants) 231 | selected_tenant_id = selected_item["id"] 232 | 233 | return selected_tenant_id 234 | 235 | else: 236 | print("SMC Connection Failure - HTTP Return Code: {}\nResponse: {}".format(response.status_code, response.text)) 237 | exit() 238 | 239 | except Exception as err: 240 | print("Unable to post to the SMC - Error: {}".format(err)) 241 | exit() 242 | 243 | 244 | def create_update_tag(ip_list): 245 | """Create/Update a Tag (Host Group) in Stealthwatch""" 246 | 247 | # Build the URL to create a Tag 248 | url = "https://{}/smc-configuration/rest/v1/tenants/{}/tags/".format(CONFIG_DATA["SW_ADDRESS"], 249 | CONFIG_DATA["SW_TENANT_ID"]) 250 | 251 | data = [{ 252 | "name": "Snort Blocklist", 253 | "ranges": ip_list, 254 | "hostBaselines": False, 255 | "suppressExcludedServices": True, 256 | "inverseSuppression": False, 257 | "hostTrap": False, 258 | "sendToCta": False, 259 | "location": "OUTSIDE", 260 | "parentId": 0 261 | }] 262 | 263 | try: 264 | # If we already have a Tag ID, just update, otherwise create. 265 | if CONFIG_DATA["SW_TAG_ID"]: 266 | print("Updating Stealthwatch Tag {}...".format(CONFIG_DATA["SW_TAG_ID"])) 267 | 268 | # Add the Tag ID to the data 269 | data[0]["id"] = CONFIG_DATA["SW_TAG_ID"] 270 | 271 | # Send the update request 272 | response = API_SESSION.put(url, json=data, verify=False) 273 | 274 | else: 275 | print("Creating Stealthwatch Tag...") 276 | 277 | # Send the create request 278 | response = API_SESSION.post(url, json=data, verify=False) 279 | 280 | # If the request was successful, then proceed, otherwise terminate. 281 | if response.status_code == 200: 282 | print("Tag Request Successful.") 283 | 284 | # Parse the response as JSON 285 | tag_id = response.json()["data"][0]["id"] 286 | 287 | # Return the Tag ID 288 | return tag_id 289 | 290 | else: 291 | print("SMC Connection Failure - HTTP Return Code: {}\nResponse: {}".format(response.status_code, response.text)) 292 | exit() 293 | 294 | except Exception as err: 295 | print("Unable to post to the SMC - Error: {}".format(err)) 296 | exit() 297 | 298 | 299 | def create_cse(): 300 | """Create a Custom Security Event for bi-directional traffic to/from a Blocklisted IP""" 301 | 302 | print("Creating Custom Security Event...") 303 | 304 | # Build the URL to create a CSE 305 | url = "https://{}/smc-configuration/rest/v1/tenants/{}/policy/customEvents".format(CONFIG_DATA["SW_ADDRESS"], 306 | CONFIG_DATA["SW_TENANT_ID"]) 307 | 308 | data = { 309 | "name": "CSE: Snort Blocklist", 310 | "subject": { 311 | "tags": { 312 | "includes": [1] 313 | }, 314 | "packets": { 315 | "value": 1, 316 | "operator": "GREATER-THAN-OR-EQUAL" 317 | }, 318 | "orientation": "either" 319 | }, 320 | "peer": { 321 | "tags": { 322 | "includes": [CONFIG_DATA['SW_TAG_ID']] 323 | }, 324 | "packets": { 325 | "value": 1, 326 | "operator": "GREATER-THAN-OR-EQUAL" 327 | } 328 | } 329 | } 330 | 331 | try: 332 | # Send the create request 333 | response = API_SESSION.post(url, json=data, verify=False) 334 | 335 | # If the request was successful, then proceed, otherwise terminate. 336 | if response.status_code == 200: 337 | print("Custom Security Event Successfully Created.") 338 | 339 | # Parse the response as JSON 340 | cse_id = response.json()["data"]["customSecurityEvents"]["id"] 341 | 342 | # Return the Tag ID 343 | return cse_id 344 | 345 | else: 346 | print("SMC Connection Failure - HTTP Return Code: {}\nResponse: {}".format(response.status_code, response.text)) 347 | exit() 348 | 349 | except Exception as err: 350 | print("Unable to post to the SMC - Error: {}".format(err)) 351 | exit() 352 | 353 | 354 | def get_cse(): 355 | """Get the Custom Security Event""" 356 | 357 | print("Fetching the Custom Security Event...") 358 | 359 | # Build the URL to create a CSE 360 | url = "https://{}/smc-configuration/rest/v1/tenants/{}/policy/customEvents/{}".format(CONFIG_DATA["SW_ADDRESS"], 361 | CONFIG_DATA["SW_TENANT_ID"], 362 | CONFIG_DATA["SW_CSE_ID"]) 363 | 364 | try: 365 | # Send the get request 366 | response = API_SESSION.get(url, verify=False) 367 | 368 | # If the request was successful, then proceed, otherwise terminate. 369 | if response.status_code == 200: 370 | print("Custom Security Event Successfully Fetched.") 371 | 372 | # Parse the response as JSON 373 | cse_data = response.json()["data"]["customSecurityEvents"] 374 | 375 | # Return the CSE data 376 | return cse_data 377 | 378 | else: 379 | print("SMC Connection Failure - HTTP Return Code: {}\nResponse: {}".format(response.status_code, response.text)) 380 | exit() 381 | 382 | except Exception as err: 383 | print("Unable to post to the SMC - Error: {}".format(err)) 384 | exit() 385 | 386 | 387 | def enable_cse(): 388 | """Enable the Custom Security Event""" 389 | 390 | print("Enabling the Custom Security Event...") 391 | 392 | # Fetch the CSE 393 | cse_data = get_cse() 394 | 395 | # Build the URL to enable the CSE 396 | url = "https://{}/smc-configuration/rest/v1/tenants/{}/policy/customEvents/{}/enable".format(CONFIG_DATA["SW_ADDRESS"], 397 | CONFIG_DATA["SW_TENANT_ID"], 398 | CONFIG_DATA["SW_CSE_ID"]) 399 | 400 | data = { 401 | "timestamp": cse_data["timestamp"] 402 | } 403 | 404 | try: 405 | # Send the get request 406 | response = API_SESSION.put(url, json=data, verify=False) 407 | 408 | # If the request was successful, then proceed, otherwise terminate. 409 | if response.status_code == 200: 410 | print("Custom Security Event Successfully Enabled.") 411 | 412 | return None 413 | 414 | else: 415 | print("SMC Connection Failure - HTTP Return Code: {}\nResponse: {}".format(response.status_code, response.text)) 416 | exit() 417 | 418 | except Exception as err: 419 | print("Unable to post to the SMC - Error: {}".format(err)) 420 | exit() 421 | 422 | 423 | def selection_list(item_name, item_name_key, item_dict): 424 | """This is a function to allow users to select an item from a dictionary""" 425 | 426 | print("\nPlease select one of the following {}:\n".format(item_name)) 427 | 428 | index = 1 429 | 430 | # Print the options that are available 431 | for item in item_dict: 432 | print("\t{}) {}".format(index, item[item_name_key])) 433 | index += 1 434 | 435 | # Prompt the user for the item 436 | selected_item = input("\n{} Selection: ".format(item_name)) 437 | 438 | # Make sure that the selected item was valid 439 | if 0 < int(selected_item) <= len(item_dict): 440 | selected_item = int(selected_item) - 1 441 | else: 442 | print("ERROR: {} selection was not correct.".format(item_name)) 443 | exit() 444 | 445 | return item_dict[selected_item] 446 | 447 | 448 | def main(): 449 | """This is a function to run the main logic of the SnortBlocklistImporter""" 450 | 451 | # Get the IPs in the Snort Blocklist 452 | ip_list = get_blocklist() 453 | 454 | # Authenticate to the Stealthwatch API 455 | get_access_token() 456 | 457 | # If a Domain ID wasn't specified, then get one 458 | if not CONFIG_DATA["SW_TENANT_ID"]: 459 | 460 | # Get Tenants from REST API 461 | CONFIG_DATA["SW_TENANT_ID"] = get_tenants() 462 | 463 | # Save the Tenant/Domain ID 464 | save_config() 465 | 466 | # Send the request to Stealthwatch to Create or Update the Tag (Host Group) 467 | CONFIG_DATA["SW_TAG_ID"] = create_update_tag(ip_list) 468 | save_config() 469 | 470 | # If the configuration dictates that we want to create a CSE 471 | if CONFIG_DATA["SW_CREATE_CSE"]: 472 | 473 | # If we haven't already created a CSE 474 | if not CONFIG_DATA["SW_CSE_ID"]: 475 | 476 | # Create the Custom Security Event 477 | CONFIG_DATA["SW_CSE_ID"] = create_cse() 478 | save_config() 479 | 480 | # Enable the Custom Security Event 481 | enable_cse() 482 | 483 | 484 | #################### 485 | # !!! DO WORK !!! # 486 | #################### 487 | 488 | 489 | if __name__ == "__main__": 490 | 491 | # Set up an argument parser 492 | parser = argparse.ArgumentParser(description="A script to import the Snort Blocklist into Stealthwatch") 493 | parser.add_argument("-d", "--daemon", help="Run the script as a daemon", action="store_true") 494 | args = parser.parse_args() 495 | 496 | # Load configuration data from file 497 | CONFIG_DATA = load_config() 498 | 499 | # If not hard coded, get the SMC Address, Username and Password 500 | if not CONFIG_DATA["SW_ADDRESS"]: 501 | CONFIG_DATA["SW_ADDRESS"] = input("Stealthwatch IP/FQDN Address: ") 502 | save_config() 503 | if not CONFIG_DATA["SW_USERNAME"]: 504 | CONFIG_DATA["SW_USERNAME"] = input("Stealthwatch Username: ") 505 | save_config() 506 | if not CONFIG_DATA["SW_PASSWORD"]: 507 | CONFIG_DATA["SW_PASSWORD"] = getpass.getpass("Stealthwatch Password: ") 508 | save_config() 509 | 510 | if args.daemon: 511 | while True: 512 | main() 513 | print("Waiting {} seconds...".format(INTERVAL)) 514 | time.sleep(INTERVAL) 515 | else: 516 | main() 517 | --------------------------------------------------------------------------------