348 lines
12 KiB
Python
348 lines
12 KiB
Python
# Copyright 2016 Rackspace
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
import datetime
|
|
import logging
|
|
import os
|
|
import pwd
|
|
import shutil
|
|
import sys
|
|
|
|
from oslo_config import cfg
|
|
import requests
|
|
from six.moves import input
|
|
|
|
import syntribos
|
|
from syntribos._i18n import _
|
|
from syntribos.utils import remotes
|
|
|
|
FOLDER = ".syntribos"
|
|
FILE = "syntribos.conf"
|
|
CONF = cfg.CONF
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def expand_path(path):
|
|
if not path:
|
|
return ""
|
|
elif "~" in path:
|
|
path = os.path.expanduser(path)
|
|
return os.path.abspath(path)
|
|
|
|
|
|
def get_user_home_root():
|
|
global FOLDER
|
|
user = os.environ.get("SUDO_USER")
|
|
if not user:
|
|
try:
|
|
user = os.environ.get("USER") or os.getlogin()
|
|
except OSError as e:
|
|
# Refer https://mail.python.org/pipermail/python-bugs-list/
|
|
# 2002-July/012691.html
|
|
LOG.error("Exception thrown in : %s", e)
|
|
user = pwd.getpwuid(os.getuid())[0]
|
|
home_path = "~{0}/{1}".format(user, FOLDER)
|
|
return expand_path(home_path)
|
|
|
|
|
|
def is_venv():
|
|
# Virtualenv sets "sys.real_prefix" and replaces "sys.prefix"
|
|
return hasattr(sys, "real_prefix")
|
|
|
|
|
|
def get_venv_root():
|
|
# Virtualenv detection
|
|
path = ""
|
|
if is_venv():
|
|
path = os.path.abspath(os.path.join(sys.prefix, FOLDER))
|
|
return path
|
|
|
|
|
|
def get_syntribos_root():
|
|
"""This determines the proper path to use as syntribos' root directory."""
|
|
path = ""
|
|
try:
|
|
custom_root = CONF.syntribos.custom_root
|
|
if custom_root:
|
|
return expand_path(custom_root)
|
|
except Exception:
|
|
pass
|
|
|
|
home_root = get_user_home_root()
|
|
|
|
# Virtualenv detection
|
|
if get_venv_root():
|
|
path = get_venv_root()
|
|
|
|
# Use home dir if syntribos folder already exists, or no virtualenv found
|
|
if os.path.exists(home_root) or not path:
|
|
path = home_root
|
|
|
|
return path
|
|
|
|
|
|
def get_syntribos_path(*args):
|
|
return os.path.abspath(os.path.join(get_syntribos_root(), *args))
|
|
|
|
|
|
def get_default_conf_file():
|
|
global FILE
|
|
return get_syntribos_path(FILE)
|
|
|
|
|
|
def get_log_dir_name(log_path=""):
|
|
"""Returns the directory where log files would be saved."""
|
|
log_dir = CONF.logging.log_dir or log_path
|
|
time_str = datetime.datetime.now().strftime("%Y-%m-%d_%X.%f")
|
|
log_path = "{time}".format(time=time_str.split(".")[0])
|
|
log_path = os.path.join(log_dir, log_path)
|
|
return log_path
|
|
|
|
|
|
def safe_makedirs(path, force=False):
|
|
if not os.path.exists(path):
|
|
try:
|
|
os.makedirs(path)
|
|
except (OSError, IOError):
|
|
LOG.exception(_("Error creating folder (%s).") % path)
|
|
elif os.path.exists(path) and force:
|
|
try:
|
|
shutil.rmtree(path)
|
|
os.makedirs(path)
|
|
except (OSError, IOError):
|
|
LOG.exception(
|
|
_("Error overwriting existing folder (%s).") % path)
|
|
else:
|
|
LOG.warning("Folder was already found (%s). Skipping.", path)
|
|
|
|
|
|
def create_env_dirs(root_dir, force=False):
|
|
# Create syntribos environment folder
|
|
safe_makedirs(root_dir, force)
|
|
|
|
# Create payloads folder
|
|
payloads = os.path.join(root_dir, "payloads")
|
|
safe_makedirs(payloads, force)
|
|
|
|
# Create templates folder
|
|
templates = os.path.join(root_dir, "templates")
|
|
safe_makedirs(templates, force)
|
|
|
|
# Create logs folder
|
|
log_dir = os.path.join(root_dir, "logs")
|
|
safe_makedirs(log_dir, force)
|
|
|
|
return (root_dir, payloads, templates, log_dir)
|
|
|
|
|
|
def create_conf_file(created_folders=None, remote_path=None):
|
|
global FILE
|
|
root, payloads, templates, logs = created_folders
|
|
conf_file = os.path.join(root, FILE)
|
|
# Create default configuration file
|
|
with open(conf_file, "w") as f:
|
|
custom_root = CONF.sub_command.custom_install_root or ""
|
|
if custom_root:
|
|
custom_root = "custom_root={0}".format(custom_root)
|
|
template = (
|
|
"# syntribos barebones configuration file\n"
|
|
"# You should update this with your desired options!\n"
|
|
"[syntribos]\n"
|
|
"endpoint=http://127.0.0.1:8080\n"
|
|
"payloads={payloads}\n"
|
|
"templates={templates}\n"
|
|
"{custom_root}\n\n"
|
|
"[logging]\n"
|
|
"log_dir={logs}\n"
|
|
).format(
|
|
payloads=remote_path if remote_path else payloads,
|
|
templates=templates, custom_root=custom_root, logs=logs)
|
|
f.write(template)
|
|
return conf_file
|
|
|
|
|
|
def initialize_syntribos_env():
|
|
"""Sets up payloads, config, etc. for syntribos after installation."""
|
|
|
|
def prompt_yes(prompt):
|
|
answer = input(prompt).lower()
|
|
return answer == "yes" or answer == "y"
|
|
|
|
def prompt_yes_or_quit(prompt):
|
|
prompt = ("{0}\n\tType 'yes' or 'y' to continue, anything else "
|
|
"to quit: ").format(prompt)
|
|
if not prompt_yes(prompt):
|
|
print("Aborting syntribos initialization.")
|
|
exit(0)
|
|
return True
|
|
|
|
def prompt_yes_or_continue(prompt):
|
|
prompt = ("{0}\n\tType 'yes' or 'y' to continue, anything else "
|
|
"for more options: ").format(prompt)
|
|
return prompt_yes(prompt)
|
|
|
|
global FILE
|
|
logging.basicConfig(level=logging.DEBUG)
|
|
root_dir = get_venv_root() if is_venv() else get_user_home_root()
|
|
|
|
force = CONF.sub_command.force
|
|
custom_root = CONF.sub_command.custom_install_root or ""
|
|
if custom_root:
|
|
root_dir = custom_root
|
|
elif CONF.sub_command.force:
|
|
pass
|
|
else:
|
|
# Check if we've already initalized env so we don't overwrite anything
|
|
if is_syntribos_initialized():
|
|
prompt = ("It seems syntribos has already been initialized.")
|
|
prompt_yes_or_quit(prompt)
|
|
|
|
else:
|
|
if not CONF.sub_command.no_downloads:
|
|
prompt = ("Syntribos has not been initialized. By default, "
|
|
"this process will create a '.syntribos' folder\n "
|
|
"with a barebones configuration file, and "
|
|
"sub-folders for templates, debug logs, and\n "
|
|
"payloads. Syntribos will also attempt to download "
|
|
"payload files, which are necessary for fuzz\n "
|
|
"tests to run. To avoid this behavior, run this "
|
|
"command again with the --no_downloads flag")
|
|
else:
|
|
prompt = ("Syntribos has not been initialized. By default, "
|
|
"this process will create a '.syntribos' folder\n "
|
|
"with a barebones configuration file, and "
|
|
"sub-folders for templates, debug logs, and\n "
|
|
"payloads. Syntribos will not attempt to download "
|
|
"any files during the initialization process.")
|
|
prompt_yes_or_quit(prompt)
|
|
|
|
if is_venv():
|
|
prompt = "Virtual environment detected. Install to {0}?".format(
|
|
get_venv_root())
|
|
if prompt_yes_or_continue(prompt):
|
|
root_dir = get_venv_root()
|
|
else:
|
|
prompt = ("Install to your home directory ({0})?").format(
|
|
get_user_home_root())
|
|
if prompt_yes_or_quit(prompt):
|
|
root_dir = get_user_home_root()
|
|
|
|
folders_created = create_env_dirs(root_dir, force=force)
|
|
|
|
# Grab payloads
|
|
logging.disable(logging.ERROR) # Don't want to log to console here...
|
|
|
|
payloads_dir = folders_created[1]
|
|
if not CONF.sub_command.no_downloads:
|
|
print(
|
|
_("\nDownloading payload files to %s...") % payloads_dir)
|
|
try:
|
|
remote_path = remotes.get(CONF.remote.payloads_uri, payloads_dir)
|
|
conf_file = create_conf_file(folders_created, remote_path)
|
|
print(_("Download successful!"))
|
|
except (requests.ConnectionError, IOError):
|
|
print(_("Download failed. If you would still like to download"
|
|
" payload files, please consult our documentation"
|
|
" about the 'syntribos download' command or do so"
|
|
" manually."))
|
|
conf_file = create_conf_file(folders_created)
|
|
else:
|
|
conf_file = create_conf_file(folders_created)
|
|
|
|
logging.disable(logging.NOTSET)
|
|
|
|
print(_("\nSyntribos has been initialized!"))
|
|
print(
|
|
_("Folders created:\n\t%s") % "\n\t".join(folders_created))
|
|
print(_("Configuration file:\n\t%s") % conf_file)
|
|
print(_(
|
|
"\nYou'll need to edit your configuration file to specify the "
|
|
"endpoint to test and any other configuration options you want."))
|
|
print(_(
|
|
"\nBy default, syntribos does not ship with any template files, "
|
|
"which are required for syntribos to run. However, we provide a\n "
|
|
"'syntribos download' command to fetch template files remotely. "
|
|
"Please see our documentation for this subcommand, or run\n "
|
|
"'syntribos download --templates' to download our default set of "
|
|
"OpenStack templates."))
|
|
print(syntribos.SEP)
|
|
|
|
|
|
def is_syntribos_initialized():
|
|
"""Determine whether syntribos has been set up properly.
|
|
|
|
For testing whether or not the user has e.g. run ```syntribos init``` or
|
|
otherwise created the necessary folders/files to run syntribos
|
|
"""
|
|
if not os.path.exists(get_syntribos_root()):
|
|
return False
|
|
flat_list = []
|
|
for ele in [get_default_conf_file(), CONF.config_file, CONF.config_dir]:
|
|
if ele and isinstance(ele, str):
|
|
flat_list.append(ele)
|
|
elif ele and isinstance(ele, list):
|
|
for s in ele:
|
|
flat_list.append(s)
|
|
|
|
if any([os.path.exists(conf_file) for conf_file in flat_list]):
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def download_wrapper():
|
|
"""Provides wrapper method to use in 'syntribos download' subcommand."""
|
|
templates_uri = CONF.remote.templates_uri
|
|
payloads_uri = CONF.remote.payloads_uri
|
|
|
|
templates_dir = (CONF.remote.cache_dir or
|
|
os.path.join(get_syntribos_root(), "templates"))
|
|
payloads_dir = (CONF.remote.cache_dir or
|
|
os.path.join(get_syntribos_root(), "payloads"))
|
|
|
|
if not CONF.sub_command.templates and not CONF.sub_command.payloads:
|
|
print(
|
|
_(
|
|
"Please specify the --templates flag and/or the --payloads"
|
|
"flag to this command.\nNo files have been downloaded.\n"))
|
|
|
|
if CONF.sub_command.templates:
|
|
print(_(
|
|
"Downloading template files from %(uri)s to %(dir)s..."
|
|
) % {"uri": templates_uri, "dir": templates_dir})
|
|
try:
|
|
remotes.get(templates_uri, templates_dir)
|
|
print(_(
|
|
"Download successful! To use these templates, edit your "
|
|
"config file to update the location of templates."))
|
|
except Exception:
|
|
print(_(
|
|
"Template download failed. Our documentation contains "
|
|
"instructions to provide templates manually."))
|
|
exit(1)
|
|
|
|
if CONF.sub_command.payloads:
|
|
print(_(
|
|
"Downloading payload files from %(uri)s to %(dir)s...\n") % {
|
|
"uri": payloads_uri, "dir": payloads_dir})
|
|
try:
|
|
remotes.get(payloads_uri, payloads_dir)
|
|
print(_(
|
|
"Download successful! To use these payloads, edit your "
|
|
"config file to update the location of payloads."))
|
|
except Exception:
|
|
print(_(
|
|
"Payload download failed. Our documentation contains "
|
|
"instructions to provide payloads manually."))
|
|
exit(1)
|