Skip to content

Commit

Permalink
Merge pull request #12 from iriusrisk/altered-startup
Browse files Browse the repository at this point in the history
altered how the modules start up, putting the heavy lifting in the ir…
  • Loading branch information
walter-iriusrisk authored Nov 21, 2023
2 parents 6752fb3 + fea9e93 commit 634877a
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 204 deletions.
4 changes: 2 additions & 2 deletions Integrations/ApiShell/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ entries useful for making IriusRisk API calls. Most of the command line
arguments provided can be duplicated in configuration files, thereby allowing
you to provide them in a file once across multiple script calls.

For further information, clone this subdirectory and execute the main file:
For further information, clone this subdirectory and execute the following:

python3 main.py
python3 -c 'import iriusrisk.v1' --help

This describes in greater detail how to call the program shell.

Expand Down
133 changes: 133 additions & 0 deletions Integrations/ApiShell/iriusrisk/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""This module provides helper methods for parsing the command line and
loading configuration files. Call --help on the command line to get detailed
information regarding which arguments are expected on the comand line and in
the configuration files.
Besides initial configuration, this module contains the sub-module "facade,"
which itself provides helper methods for calling the IriusRisk API.
The configuration values are contained in an object named "config," which is
imported by default. The base configuration contains raw command line and
configuration file parameters, plus the attribute "url," which contains the
full URL where the API is being accessed.
"""
import iriusrisk.commandline
import iriusrisk.configfile
import http.client
import json
import logging
import sys

__all__=["config,parse_arguments,get_commandline_parser"]

config = None
parse_args_on_load = True

_log = logging.getLogger('iriusrisk')
_parser = iriusrisk.commandline.get_parser()

"""Call this method before loading any sub-modules of iriusrisk. This then
prevents the automatic parsing of the configuration files and command line,
which is normally done following initialization of the sub-modules.
"""
def suppress_parse_args_on_load():
parse_args_on_load = False

"""This returns the instance of the argparse class used by this method. Use
this to add needed command line parameters prior to calling parse_arguments().
"""
def get_commandline_parser():
return _parser

"""Parse the command line arguments, and load in any config files. By default,
this method is called after any sub-module of iriusrisk is initialized.
"""
def parse_arguments():
global config
config = _parser.parse_args()

if config.verbose:
logging.basicConfig(level=logging.DEBUG)
elif config.quiet:
logging.basicConfig(level=logging.ERROR)

_raw_config = iriusrisk.configfile.parse_config()

if not len(sys.argv) > 1 and not _raw_config:
_parser.print_help()
exit(-1)

if not _raw_config:
_log.info("No configuration file (iriusrisk.ini) found in any of the locations.")
_log.info("See help (--help) for more information.")
_raw_config = { "DEFAULT":{}}
elif "DEFAULT" in _raw_config:
_log.info("Successfully parsed at least one configuration file")
else:
_log.warn("At least one configuration file was read in, but no [DEFAULT] section was found.")
_log.info("All configurations in the file(s) will therefore be ignored. See help (--help)")
_log.info("for further information")

_log.info("Starting configuration initialization")

config.key = _get_item(_raw_config, config.key, "key", None)

if not config.key:
_log.error("No --key has been specified. Any API call will fail. See help (--help) for more information.")

if config.dryrun:
_log.info("Option --dryrun passed on the command line. No HTTP calls will be made.")

config.url = _get_url(_raw_config)
_check_url(config.url)

def _get_url(config_file):
full_url = _get_item(config_file, config.full_url, "full-url", None)
if full_url:
_log.info("Using the --full-url option. No URL will be derived from domain or subdomain.")
return full_url

subdomain = _get_item(config_file, config.subdomain, "subdomain", None)
if subdomain:
_log.info("Using the --subdomain option. URL will be derived by appending .iriusrisk.com (SaaS instance).")
return f"{subdomain}.iriusrisk.com:443"

domain = _get_item(config_file, config.domain, "domain", None)

if domain:
_log.info("Using the --domain option. Protocol assumed to be HTTPS")
return "{domain}:443"

def _get_item(config_file, value, key, default_value):
if value:
_log.debug(f"Found {key} on the command line. Will take precedence over config file")
return value

if key in config_file["DEFAULT"]:
_log.debug(f"Found {key} in a configuration file but not on the command line.")
return config_file["DEFAULT"][key]

_log.debug(f"Item {key} not found in configuration file or on the command line.")
return default_value

def _check_url(url):
if not url:
_log.error("No URL has been specified!")
_log.warn("You must supply one of subdomain, domain or url on the command line or in the ini file")
_log.warn("Get extended help (--help) from the program for more information")

if not config.dryrun:
_log.info("Making a call to the given URL as a fail-fast test")
_log.debug("Note that this does not test the security key's validity, but just whether")
_log.debug("the URL is valid and accepting requests.")
headers = { "accept": "application/json" }
conn = http.client.HTTPSConnection(url)
conn.request("GET", "/health", None, headers)
resp = conn.getresponse()
if resp.status != 200:
_log.error(f"Call to {url} returned status of {resp.status} ({resp.reason}); program will exit")
exit(-1)

data = resp.read().decode("utf-8")
json_obj = json.loads(data)
_log.debug(f"Successfully checked health of {url} (server: {json_obj['company']})")
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ def get_parser():
create more complex scripts that create, call and process API calls.
Using this shell program provides default command-line and configuration-file
entries useful for making IriusRisk API calls. Most of the command line
arguments provided can be duplicated in configuration files, thereby allowing
you to providethem in a file once across multiple script calls.
entries useful for making IriusRisk API calls. Some of the command line
arguments can appear in configuration files, thereby allowing them to be
specified across multiple script calls.
Configuration files are always named "iriusrisk.ini" and are searched for in
the following locations:
Expand All @@ -27,19 +27,15 @@ def get_parser():
* {user data directory}/.iriusrisk/iriusrisk.ini
* {global data directory}/.iriusrisk/iriusrisk.ini
There is a precedence for the ini files; the first one overrides, the next, and
so on. Hence, the same keys may appear in multiple ini files, but the one
"nearest" to the script will be used.
Command line arguments take precedence over ini files. The verbosity parameters
(verbose and quiet) are only available on the command line.
The same keys may appear in multiple ini files, but the one "nearest" to the
script will be used. For instance, if the same key appears in the ini file
in the working directory and the in the user data directory, the one in the
working directory takes precedence.
Command line arguments take precedence over ini files.
"""), epilog=textwrap.dedent(
"""***************************************************
Format and contents of the initialization files
Each ini file contains a default namespace that must be explicitly declared.
Within this namespace are zero or more key/value pairs, corresponding to the
command line arguments. Format is as follows:
Expand All @@ -53,41 +49,11 @@ def get_parser():
[DEFAULT]
key = a123b456-78c9-01d2-e345-f6789ab012c3
A note regarding URLs:
When accessing a HTTP server, Python expects a URL that contains the domain and
port number, but no protocol. For most users, the complexity of this can be
ignored completely. For instance, if you are accessing a IriusRisk instance
provided as SaaS by IriusRisk, you only need to provide the --subdomain of the
instance. For instance, if your IriusRisk instance has a URL as follows:
https://my-company.iriusrisk.com/
you need only provide the subdomain parameter on the command line or in a config
file:
--subdomain my-company
If instead, you have a non-IriusRisk domain hosting your instance, you can
specify this using the domain parameter:
--domain ir-subdomain.example.com
Finally, the url parameter can be used for URLs that don't follow this simple
pattern. Note that when using the url parameter, you may not include a
protocol (e.g., https), but you must supply the port number. For instance:
--url subdomain.example.com:443/iriusrisk
The port parameter can be used when leveraging the domain parameter. It defaults
to 443. Only one of the parameters subdomain, domain and url are recognized.
"""
))
parser.add_argument("-k", "--key", help="API Token to use when accessing the API")
parser.add_argument("-s", "--subdomain", help="Subdomain of a SaaS instance. Will be prepended to .iriusrisk.com")
parser.add_argument("-d", "--domain", help="The entire domain of the target system, without protocol or path.")
parser.add_argument("-p", "--port", help="Defaults to 443.")
parser.add_argument("-u", "--url", help="Enter the entire base URL for the target system. Cannot have protocol, but must have port.")
parser.add_argument("-f", "--full-url", help="The target system's complete URL, port number included, but no protocol.")
parser.add_argument("-v", "--verbose", help="Output extended log information", action='store_true')
parser.add_argument("-q", "--quiet", help="Only output log messages indicating errors", action='store_true')
parser.add_argument("--dryrun", help="Do everything but actual HTTP calls", action='store_true')
Expand Down
152 changes: 8 additions & 144 deletions Integrations/ApiShell/iriusrisk/v1/__init__.py
Original file line number Diff line number Diff line change
@@ -1,148 +1,12 @@
"""This module provides helper methods for accessing IriusRisk API version v1.
It automatically parses the command line and searches for configuration files.
Call --help on the command line to get detailed information regarding which
config attributes are expected on the comand line and in the configuration
files, as well as other details regarding configuration prioritization.
It also adds the --key command line parser, which expects the IriusRisk v1 API
key as it's argument.
Besides initial configuration, this module contains the sub-module "facade,"
which itself provides helper methods for calling the IriusRisk API.
The configuration values are contained in an object named "config," which is
imported by default. All configuration items are attributes of that object
and reflect the name of the attribute as defined. So for instance, typical
attributes are as follows:
config:
url : the URL, calculated or defined, to the IriusRisk instance
key : the API key required to access the instance
dryrun : whether actual remote calls should be made or not
verbose : whether verbose output should be included
quiet : whether output should only be minimal or not
There may be other attributes, as well. For instance, while subdomain is a
valid application parameter, it is not a required one. It will only be an
attribute of config if it is actually passed, either as an applicaiton
parameter or within a configuration file.
Specific methods for calling into the API are provided in the iriusrisk.v1.facade
module.
"""
import logging
import http.client
import json
import sys

import iriusrisk.v1.commandline as _commandline
import iriusrisk.v1.configfile as _configfile

# Automatically loaded and executed when this module is loaded.
#
# This script automatically parses the command line and any configuration files found.
# It also determines the ultimate URL that will be used for calling IriusRisk.

__all__=["config"]

class _configuration:
key = None
url = None
verbose = False
quiet = False
dryrun = False

config = _configuration()
_log = logging.getLogger('iriusrisk.v1')
_parser = _commandline.get_parser()
_parsed_args = ()

def _get_item(value, key, default_value=None):
if value:
_log.debug(f"Found {key} on the command line. Will take precedence over config file")
return value

if key in _raw_config["DEFAULT"]:
_log.debug(f"Found {key} in a configuration file but not on the command line.")
return _raw_config["DEFAULT"][key]

_log.debug(f"Item {key} not found in configuration file or on the command line.")
return default_value

def get_url():
port = _get_item(_parsed_args.port, "port", "443")
url = _get_item(_parsed_args.url, "url")
if url:
_log.info("Using the --url option. No URL will be derived from domain or subdomain.")
return url

subdomain = _get_item(_parsed_args.subdomain, "subdomain")
if subdomain:
_log.info("Using the --subdomain option. URL will be derived by appending .iriusrisk.com (SaaS instance).")
return f"{subdomain}.iriusrisk.com:{port}"

domain = _get_item(_parsed_args.domain, "domain")

if domain:
_log.info("Using the --domain option. Protocol assumed to be HTTPS")
return "{domain}:{port}"

def check_url(url):
if not url:
_log.error("No URL has been specified!")
_log.warn("You must supply one of subdomain, domain or url on the command line or in the ini file")
_log.warn("Get extended help (--help) from the program for more information")

if not config.dryrun:
_log.info("Making a call to the given URL as a fail-fast test")
_log.debug("Note that this does not test the security key's validity, but just whether")
_log.debug("the URL is valid and accepting requests.")
headers = { "accept": "application/json" }
conn = http.client.HTTPSConnection(url)
conn.request("GET", "/health", None, headers)
resp = conn.getresponse()
if resp.status != 200:
_log.error(f"Call to {url} returned status of {resp.status} ({resp.reason}); program will exit")
exit(-1)

data = resp.read().decode("utf-8")
json_obj = json.loads(data)
_log.debug(f"Successfully checked health of {url} (server: {json_obj['company']})")

### Start reading the config files and command line args
_parser = _commandline.get_parser()

_parsed_args = _parser.parse_args()

if _parsed_args.verbose:
config.verbose = True
logging.basicConfig(level=logging.DEBUG)
elif _parsed_args.quiet:
config.quiet = True
logging.basicConfig(level=logging.ERROR)

_raw_config = _configfile.parse_config()

if not len(sys.argv) > 1 and not _raw_config:
_parser.print_help()
exit(-1)

if not _raw_config:
_log.info("No configuration file (iriusrisk.ini) found in any of the locations.")
_log.info("See help (--help) for more information.")
_raw_config = { "DEFAULT":{}}
elif "DEFAULT" in _raw_config:
_log.info("Successfully parsed at least one configuration file")
else:
_log.warn("At least one configuration file was read in, but no [DEFAULT] section was found.")
_log.info("All configurations in the file(s) will therefore be ignored. See help (--help)")
_log.info("for further information")

_log.info("Starting configuration initialization")

config.key = _get_item(_parsed_args.key, "key")

if not config.key:
_log.error("No --key has been specified. Any API call will fail. See help (--help) for more information.")

config.dryrun = _parsed_args.dryrun

if config.dryrun:
_log.info("Option --dryrun passed on the command line. No HTTP calls will be made.")
import iriusrisk

config.url = get_url()
check_url(config.url)
iriusrisk.get_commandline_parser().add_argument("-k", "--key", help="API Token to use when accessing the API")
if (iriusrisk.parse_args_on_load):
iriusrisk.parse_arguments()
Loading

0 comments on commit 634877a

Please sign in to comment.