aboutsummaryrefslogtreecommitdiffstats
path: root/sdv/docker/sdvsecurity/nfvsec/os-checklist
diff options
context:
space:
mode:
Diffstat (limited to 'sdv/docker/sdvsecurity/nfvsec/os-checklist')
-rwxr-xr-xsdv/docker/sdvsecurity/nfvsec/os-checklist848
1 files changed, 848 insertions, 0 deletions
diff --git a/sdv/docker/sdvsecurity/nfvsec/os-checklist b/sdv/docker/sdvsecurity/nfvsec/os-checklist
new file mode 100755
index 0000000..e022ffd
--- /dev/null
+++ b/sdv/docker/sdvsecurity/nfvsec/os-checklist
@@ -0,0 +1,848 @@
+#!/usr/bin/env python3
+
+# Copyright 2021 Spirent Communications.
+#
+# 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.
+"""
+NFV Security Check
+"""
+from __future__ import print_function
+
+import logging
+import os
+import sys
+import argparse
+import time
+import datetime
+import io
+import configparser
+from collections import OrderedDict
+from conf import settings
+from utils import sshclient
+from utils import k8sclient
+
+VERBOSITY_LEVELS = {
+ 'debug': logging.DEBUG,
+ 'info': logging.INFO,
+ 'warning': logging.WARNING,
+ 'error': logging.ERROR,
+ 'critical': logging.CRITICAL
+}
+
+_CURR_DIR = os.path.dirname(os.path.realpath(__file__))
+_LOGGER = logging.getLogger()
+
+# pylint: disable=too-few-public-methods, no-self-use
+class PseudoFile(io.RawIOBase):
+ """
+ Handle ssh command output.
+ """
+ def __init__(self, filename):
+ self.fname = filename
+
+ def write(self, chunk):
+ """
+ Write to file
+ """
+ #if "error" in chunk:
+ # return
+ with open(self.fname, "a+") as fref:
+ fref.write(chunk)
+
+class MultiOrderedDict(OrderedDict):
+ """
+ For Duplicate Keys
+ """
+ def __setitem__(self, key, value):
+ if isinstance(value, list) and key in self:
+ self[key].extend(value)
+ else:
+ super(MultiOrderedDict, self).__setitem__(key, value)
+
+def parse_arguments():
+ """
+ Parse command line arguments.
+ """
+ parser = argparse.ArgumentParser(prog=__file__, formatter_class=
+ argparse.ArgumentDefaultsHelpFormatter)
+ parser.add_argument('--version', action='version', version='%(prog)s 0.1')
+ parser.add_argument('--list-clouds', action='store_true',
+ help='list the supported clouds ')
+ args = vars(parser.parse_args())
+ return args
+
+def configure_logging(level):
+ """Configure logging.
+ """
+ name, ext = os.path.splitext(settings.getValue('LOG_FILE_DEFAULT'))
+ rename_default = "{name}_{uid}{ex}".format(name=name,
+ uid=settings.getValue(
+ 'LOG_TIMESTAMP'),
+ ex=ext)
+ log_file_default = os.path.join(
+ settings.getValue('RESULTS_PATH'), rename_default)
+ _LOGGER.setLevel(logging.DEBUG)
+ stream_logger = logging.StreamHandler(sys.stdout)
+ stream_logger.setLevel(VERBOSITY_LEVELS[level])
+ stream_logger.setFormatter(logging.Formatter(
+ '[%(levelname)-5s] %(asctime)s : (%(name)s) - %(message)s'))
+ _LOGGER.addHandler(stream_logger)
+ file_logger = logging.FileHandler(filename=log_file_default)
+ file_logger.setLevel(logging.DEBUG)
+ file_logger.setFormatter(logging.Formatter(
+ '%(asctime)s : %(message)s'))
+ _LOGGER.addHandler(file_logger)
+
+def handle_list_options(args):
+ """ Process --list cli arguments if needed
+
+ :param args: A dictionary with all CLI arguments
+ """
+ if args['list_clouds']:
+ print("WIP - Clouds")
+ sys.exit(0)
+
+def check_horizon_hardening(deployment, client):
+ '''
+ DISALLOW_IFRAME_EMBED parameter set to True
+ CSRF_COOKIE_SECURE parameter set to True
+ SESSION_COOKIE_SECURE parameter set to True
+ SESSION_COOKIE_HTTPONLY parameter set to True
+ PASSWORD_AUTOCOMPLETE set to False
+ DISABLE_PASSWORD_REVEAL set to True
+ ENFORCE_PASSWORD_CHECK set to True
+ '''
+ result = True
+ horizonkeys = OrderedDict()
+ dictkeys = ['DISALLOW_IFRAME_EMBED', 'CSRF_COOKIE_SECURE', 'SESSION_COOKIE_SECURE',
+ 'SESSION_COOKIE_HTTPONLY', 'PASSWORD_AUTOCOMPLETE', 'DISABLE_PASSWORD_REVEAL',
+ 'ENFORCE_PASSWORD_CHECK', 'PASSWORD_VALIDATOR', 'SECURE_PROXY_SSL_HEADER']
+ for dks in dictkeys:
+ horizonkeys[dks] = 'UNSET - NOT OK'
+
+ actualfile = settings.getValue('HORIZON_LOCAL_SETTINGS')
+ filename = "./horizon-conf.conf"
+ if 'k8s' not in deployment:
+ with PseudoFile(filename) as hor:
+ if 'legacy' in deployment:
+ cmd = 'sudo cat ' + actualfile
+ elif 'docker' in deployment:
+ cmd = 'sudo docker exec horizon cat ' + actualfile
+ client.run(cmd, stdout=hor, timeout=0)
+ else:
+ cmd = ['cat', actualfile]
+ pod = client.get_pod('openstack', 'horizon')
+ resp = client.execute(pod, cmd)
+ if resp:
+ with open(filename, "w+", encoding="utf-8") as fref:
+ fref.write(resp)
+
+ if (not os.path.isfile(filename) or
+ os.stat(filename).st_size == 0):
+ print("Could not read file from the container")
+ return False
+
+ with open(filename, "r") as fil:
+ for lin in fil:
+ if lin.startswith('DISALLOW_IFRAME_EMBED'):
+ if 'True' in lin:
+ horizonkeys['DISALLOW_IFRAME_EMBED'] = "OK"
+ else:
+ horizonkeys['DISALLOW_IFRAME_EMBED'] = "NOT OK"
+ result = False
+ if lin.startswith('CSRF_COOKIE_SECURE'):
+ if 'True' in lin:
+ horizonkeys['CSRF_COOKIE_SECURE'] = "OK"
+ else:
+ horizonkeys['CSRF_COOKIE_SECURE'] = "NOT OK"
+ result = False
+ if lin.startswith('SESSION_COOKIE_SECURE'):
+ if 'True' in lin:
+ horizonkeys['SESSION_COOKIE_SECURE'] = "OK"
+ else:
+ horizonkeys['SESSION_COOKIE_SECURE'] = "NOT OK"
+ result = False
+ if lin.startswith('SESSION_COOKIE_HTTPONLY'):
+ if 'True' in lin:
+ horizonkeys['SESSION_COOKIE_HTTPONLY'] = "OK"
+ else:
+ horizonkeys['SESSION_COOKIE_HTTPONLY'] = "NOT OK"
+ result = False
+ if lin.startswith('PASSWORD_AUTOCOMPLETE'):
+ if 'False' in lin:
+ horizonkeys['PASSWORD_AUTOCOMPLETE'] = "OK"
+ else:
+ horizonkeys['PASSWORD_AUTOCOMPLETE'] = "NOT OK"
+ result = False
+ if lin.startswith('DISABLE_PASSWORD_REVEAL'):
+ if 'True' in lin:
+ horizonkeys['DISABLE_PASSWORD_REVEAL'] = "OK"
+ else:
+ horizonkeys['DISABLE_PASSWORD_REVEAL'] = "NOT OK"
+ result = False
+ if lin.startswith('ENFORCE_PASSWORD_CHECK'):
+ if 'True' in lin:
+ horizonkeys['ENFORCE_PASSWORD_CHECK'] = "OK"
+ else:
+ horizonkeys['ENFORCE_PASSWORD_CHECK'] = "NOT OK"
+ result = False
+ if lin.startswith('PASSWORD_VALIDATOR'):
+ if 'regex' in lin:
+ horizonkeys['PASSWORD_VALIDATOR'] = "OK"
+ else:
+ horizonkeys['PASSWORD_VALIDATOR'] = "NOT OK"
+ result = False
+ if lin.startswith('SECURE_PROXY_SSL_HEADER'):
+ if 'HTTP_X_FORWARDED_PROTO' in lin and 'https' in lin:
+ horizonkeys['SECURE_PROXY_SSL_HEADER'] = "OK"
+ else:
+ horizonkeys['SECURE_PROXY_SSL_HEADER'] = "NOT OK"
+ result = False
+ for key in horizonkeys:
+ print("The KEY: '" + key + "' is " + horizonkeys[key])
+ return result
+
+def check_neutron_hardening(deployment, client):
+ '''
+ Section:Parameter:Expected-Value
+ keystone_authtoken:auth_protocol:https
+ keystone_authtoken:identity_uri:https://....
+ :use_ssl:True
+ :auth_strategy:keystone
+ '''
+ config = configparser.ConfigParser()
+ result = True
+ actualfile = settings.getValue('NEUTRON_CONF_FILE')
+ filename = "./neutron-conf.conf"
+ if 'k8s' not in deployment:
+ with PseudoFile(filename) as hor:
+ if 'legacy' in deployment:
+ cmd = 'sudo cat ' + actualfile
+ elif 'docker' in deployment:
+ cmd = 'sudo docker exec neutron_api cat ' + actualfile
+ client.run(cmd, stdout=hor, timeout=0)
+ else:
+ cmd = ['cat', actualfile]
+ pod = client.get_pod('openstack', 'neutron-server')
+ resp = client.execute(pod, cmd)
+ if resp:
+ with open(filename, "w+", encoding="utf-8") as fref:
+ fref.write(resp)
+
+ if (not os.path.isfile(filename) or
+ os.stat(filename).st_size == 0):
+ print("Could not read file from the container")
+ return False
+
+ config.read(filename)
+
+ if (config.has_option('keystone_authtoken', 'auth_protocol') and
+ config.has_option('keystone_authtoken','identity_uri')):
+ if (config.get('keystone_authtoken', 'auth_protocol') != 'https' and
+ not config.get('keystone_authtoken','identity_uri').startswith('https')):
+ print('Authentication token is not secured ... NOT OK')
+ result = False
+ if config.has_option('DEFAULT','use_ssl'):
+ if not config.get('DEFAULT','use_ssl'):
+ print('SSL is not used ... NOT OK')
+ result = False
+ if config.has_option('DEFAULT','auth_strategy'):
+ if config.get('DEFAULT','auth_strategy') != 'keystone':
+ print('Authentication strategy should be keystone ... NOT OK')
+ result = False
+ return result
+
+
+def check_cinder_hardening(deployment, client):
+ '''
+ Section:Parameter:Expected-Value
+ keystone_authtoken:auth_protocol:https
+ keystone_authtoken:identity_uri:https://....
+ :nova_api_insecure:False
+ :glance_api_insecure:False
+ :nas_secure_file_permissions:auto
+ :nas_secure_file_operations:auto
+ :auth_strategy:keystone
+ :osapi_max_request_body_size:114688 OR
+ oslo_middleware:max_request_body_size:114688
+ '''
+ result = True
+ config = configparser.ConfigParser()
+ actualfile = settings.getValue('CINDER_CONF_FILE')
+ filename = "./cinder-conf.conf"
+ if 'k8s' not in deployment:
+ with PseudoFile(filename) as hor:
+ if 'legacy' in deployment:
+ cmd = 'sudo cat ' + actualfile
+ elif 'docker' in deployment:
+ cmd = 'sudo docker exec cinder_api cat ' + actualfile
+ client.run(cmd, stdout=hor, timeout=0)
+ else:
+ cmd = ['cat', actualfile]
+ pod = client.get_pod('openstack', 'cinder-api')
+ resp = client.execute(pod, cmd)
+ if resp:
+ with open(filename, "w+", encoding="utf-8") as fref:
+ fref.write(resp)
+
+ if (not os.path.isfile(filename) or
+ os.stat(filename).st_size == 0):
+ print("Could not read file from the container")
+ return False
+
+ config.read(filename)
+ if (config.has_option('keystone_authtoken','auth_protocol') and
+ config.has_option('keystone_authtoken','identity_uri')):
+ if (config.get('keystone_authtoken','auth_protocol') != 'https' and
+ config.get('keystone_authtoken','identity_uri').startswith('https')):
+ print('Authentication token is not secured ... NOT OK')
+ result = False
+ if config.has_option('DEFAULT','nova_api_insecure'):
+ if config.get('DEFAULT','nova_api_insecure'):
+ print('Cinder-Nova API is insecure ... NOT OK')
+ result = False
+ if config.has_option('DEFAULT','nas_secure_file_operations'):
+ if config.get('DEFAULT','nas_secure_file_operations') != 'auto':
+ print('NAS Secure File is False ... NOT OK')
+ result = False
+ if config.has_option('DEFAULT','nas_secure_file_permissions'):
+ if config.get('DEFAULT','nas_secure_file_permissions') != 'auto':
+ print('NAS secure file permissions ... NOT OK')
+ result = False
+ if config.has_option('DEFAULT','auth_strategy'):
+ if config.get('DEFAULT','auth_strategy') != 'keystone':
+ print('Authentication strategy should be keystone')
+ result = False
+ if config.has_option('DEFAULT','glance_api_insecure'):
+ if config.get('DEFAULT','glance_api_insecure') != 'False' :
+ print('Cinder-Glance API is insecure ... NOT OK ')
+ result = False
+ if (config.has_option('DEFAULT','osapi_max_request_body_size') and
+ config.has_option('oslo_middleware','max_request_body_size')):
+ if (config.get('DEFAULT','osapi_max_request_body_size') != 114688 or
+ config.get('oslo_middleware','max_request_body_size') != 114688):
+ print('MAX Request Body Size is not 114688 ... NOT OK')
+ result = False
+ return result
+
+
+
+def check_nova_hardening(deployment, client):
+ result = True
+ config = configparser.ConfigParser(allow_no_value=True, interpolation=None, strict=False)
+ #config = configparser.RawConfigParser(dict_type=MultiOrderedDict, strict=False)
+ actualfile = settings.getValue('NOVA_CONF_FILE')
+ filename = "./nova-conf.conf"
+ if 'k8s' not in deployment:
+ with PseudoFile(filename) as hor:
+ if 'legacy' in deployment:
+ cmd = 'sudo cat ' + actualfile
+ elif 'docker' in deployment:
+ cmd = 'sudo docker exec nova_api cat ' + actualfile
+ client.run(cmd, stdout=hor, timeout=0)
+ else:
+ cmd = ['cat', actualfile]
+ pod = client.get_pod('openstack', 'nova-api-osapi')
+ resp = client.execute(pod, cmd)
+ if resp:
+ with open(filename, "w+", encoding="utf-8") as fref:
+ fref.write(resp)
+ if (not os.path.isfile(filename) or
+ os.stat(filename).st_size == 0):
+ print("Could not read file from the container")
+ return False
+
+ config.read([filename])
+ if config.has_option('DEFAULT','auth_strategy'):
+ if config.get('DEFAULT','auth_strategy') != 'keystone':
+ print('Authentication strategy should be keystone ... NOT OK')
+ result = False
+ if (config.has_option('keystone_authtoken','auth_protocol') and
+ config.has_option('keystone_authtoken','identity_uri')):
+ if (config.get('keystone_authtoken','auth_protocol') != 'https' and
+ not config.get('keystone_authtoken','identity_uri').startswith('https')):
+ print('Authentication token is not secured ... NOT OK')
+ result = False
+ if config.has_option('DEFAULT','glance_api_insecure'):
+ if config.get('DEFAULT','glance_api_insecure'):
+ print('Glance-Nova API is insecure ... NOT OK')
+ result = False
+ return result
+
+def check_keystone_hardening(deployment, client):
+ '''
+ https://static.open-scap.org/ssg-guides/ssg-rhosp13-guide-stig.html
+ /etc/keystone/keystone.conf
+ Section:Parameter:Expected-Value
+ token:hash_algorithm:SHA256
+ ssl:enable:True
+ NA:max_request_body_size:default/114688/some-value
+ security_compliance:disable_user_account_days_inactive:some-value
+ security_compliance:lockout_failure_attempts:some-value
+ security_compliance:lockout_duration:some-value
+ DEFAULT:admin_token:disabled
+ *** If lockout_failure_attempts is enabled and lockout_duration is left undefined,
+ users will be locked out indefinitely until the user is explicitly re-enabled ***
+ [/etc/keystone/keystone-paste.ini]
+ filter:admin_token_auth:AdminTokenAuthMiddleware:not-exist
+ '''
+ result = True
+ #config = configparser.ConfigParser()
+ config = configparser.ConfigParser(allow_no_value=True, interpolation=None, strict=False)
+ actualfile = settings.getValue('KEYSTONE_CONF_FILE')
+ filename = "./keystone-conf.conf"
+ if 'k8s' not in deployment:
+ with PseudoFile(filename) as hor:
+ if 'legacy' in deployment:
+ cmd = 'sudo cat ' + actualfile
+ elif 'docker' in deployment:
+ cmd = 'sudo docker exec keystone cat ' + actualfile
+ client.run(cmd, stdout=hor, timeout=0)
+ else:
+ cmd = ['cat', actualfile]
+ pod = client.get_pod('openstack', 'keystone-api')
+ resp = client.execute(pod, cmd)
+ if resp:
+ with open(filename, "w+", encoding="utf-8") as fref:
+ fref.write(resp)
+ if (not os.path.isfile(filename) or
+ os.stat(filename).st_size == 0):
+ print("Could not read file from the container")
+ return False
+
+ config.read(filename)
+ if config.has_option('token','hash_algorithm'):
+ if config.get('token','hash_algorithm') != 'SHA256':
+ print('Hash Algorithm is NOT SHA256 ... NOT OK')
+ result = False
+ if config.has_option('ssl','enable'):
+ if not config.get('ssl','enable'):
+ print('SSL is not enabled ... NOT OK')
+ result = False
+ if config.has_option('DEFAULT','max_request_body_size'):
+ if not config.get('DEFAULT','max_request_body_size'):
+ print('MAX request Body Size is not specified ... NOT OK')
+ result = False
+ if (config.has_option('security_compliance','disable_user_account_days_inactive') and
+ not config.has_option('security_compliance','lockout_failure_attempts') and
+ not config.has_option('security_compliance','lockout_duration')):
+ if (not config.get('security_compliance','disable_user_account_days_inactive') and
+ not config.get('security_compliance','lockout_failure_attempts') and
+ not config.get('security_compliance','lockout_duration')):
+ print("Security Compliance configurations are not correct ... NOT OK")
+ result = False
+ if config.has_option('DEFAULT','admin_token'):
+ if config.get('DEFAULT','admin_token') != 'disabled':
+ print("Admin Token is not disabled ... NOT OK")
+ result = False
+ return result
+
+
+
+def check_sixfourzero(deployment, client):
+ """
+ Check 644
+ """
+ result = True
+ status = -1
+ stderror = "Failed to Stat file"
+ filenames = (settings.getValue('KSP_FILES') +
+ settings.getValue('NOP_FILES') +
+ settings.getValue('NEP_FILES') +
+ settings.getValue('CIP_FILES'))
+ # https://stackoverflow.com/questions/1861836/checking-file-permissions-in-linux-with-python
+ for filename in filenames:
+ if 'cinder' in filename:
+ container = 'cinder_api'
+ podn = 'cinder-api'
+ elif 'neutron' in filename:
+ container = 'neutron_api'
+ podn = 'neutron-server'
+ elif 'nova' in filename:
+ container = 'nova_api'
+ podn = 'nova-api-osapi'
+ elif 'keystone' in filename:
+ container = 'keystone'
+ podn = 'keystone-api'
+ else:
+ print("Bad File Name ")
+ return False
+ if 'legacy' in deployment:
+ cmd = "sudo stat -c '%a' " + filename
+ elif 'docker' in deployment:
+ cmd = "sudo docker exec " + container + " stat -c '%a' " + filename
+ elif 'k8s' in deployment:
+ cmd = ['stat', '-c', '"%a"', filename]
+ pod = client.get_pod('openstack', podn)
+ else:
+ print("Deployment not supported")
+ return False
+ if 'k8s' not in deployment:
+ status, stdout, stderror = client.execute(cmd)
+ else:
+ stdout = client.execute(pod, cmd)
+ if stdout:
+ status = 0
+ if status == 0:
+ if '640' not in stdout:
+ print('The File {} has wrong permission - NOT OK'.format(filename))
+ result = False
+ else:
+ print(stderror)
+ result = False
+ return result
+
+def check_ug_keystone(deployment, client):
+ """
+ UG of Keystone
+ """
+ result = True
+ statusu = statusg = -1
+ stderroru = stderrorg = "Failed to Stat file"
+ filenames = settings.getValue('KSP_FILES')
+ for filename in filenames:
+ if 'legacy' in deployment:
+ cmdu = "sudo stat -c '%U' " + filename
+ cmdg = "sudo stat -c '%G' " + filename
+ elif 'docker' in deployment:
+ cmdu = "sudo docker exec keystone stat -c '%U' " + filename
+ cmdg = "sudo docker exec keystone stat -c '%G' " + filename
+ elif 'k8s' in deployment:
+ pod = client.get_pod('openstack', 'keystone-api')
+ cmdu = ['stat', '-c' '"%U"', filename]
+ cmdg = ['stat', '-c' '"%G"', filename]
+ else:
+ print("Deployment type not supported")
+ return False
+ if 'k8s' not in deployment:
+ statusu, stdoutu, stderroru = client.execute(cmdu)
+ statusg, stdoutg, stderrorg = client.execute(cmdg)
+ else:
+ stdoutu = client.execute(pod, cmdu)
+ stdoutg = client.execute(pod, cmdg)
+ if stdoutu:
+ statusu = 0
+ if stdoutg:
+ statusg = 0
+ if statusu == 0:
+ if ('keystone' not in stdoutu and
+ 'root' not in stdoutu):
+ print('The user of File {} is not keystone ... NOT OK'.format(filename))
+ result = False
+ else:
+ print(stderroru)
+ if statusg == 0:
+ if 'keystone' not in stdoutg:
+ print(filename)
+ print('The group ownership of file {} is not keystone ... NOT OK'.format(filename))
+ result = False
+ else:
+ print(stderrorg)
+ return result
+
+
+def check_ug_root_apache(deployment, client):
+ """
+ UG of Apache
+ """
+ result = True
+ statusu = statusg = -1
+ stderroru = stderrorg = "Failed to Stat file"
+ filenames = settings.getValue('HORIZON_APACHE_FILES')
+ for filename in filenames:
+ if 'legacy' in deployment:
+ cmdu = "sudo stat -c '%U' " + filename
+ cmdg = "sudo stat -c '%G' " + filename
+ elif 'docker' in deployment:
+ cmdu = "sudo docker exec horizon stat -c '%U' " + filename
+ cmdg = "sudo docker exec horizon stat -c '%G' " + filename
+ elif 'k8s' in deployment:
+ pod = client.get_pod('openstack', 'horizon')
+ cmdu = ['stat', '-c' '"%U"', filename]
+ cmdg = ['stat', '-c' '"%G"', filename]
+ else:
+ print("Deployment type not supported")
+ return False
+ if 'k8s' not in deployment:
+ statusu, stdoutu, stderroru = client.execute(cmdu)
+ statusg, stdoutg, stderrorg = client.execute(cmdg)
+ else:
+ stdoutu = client.execute(pod, cmdu)
+ stdoutg = client.execute(pod, cmdg)
+ if stdoutu:
+ statusu = 0
+ if stdoutg:
+ statusg = 0
+
+ if statusu == 0:
+ if 'root' not in stdoutu:
+ print('The user of File {} is not root ... NOT OK'.format(filename))
+ result = False
+ else:
+ print(stderroru)
+ if statusg == 0:
+ if 'apache' not in stdoutg:
+ print(filename)
+ print('The group ownership of file {} is not Apache ... NOT OK'.format(filename))
+ result = False
+ else:
+ print(stderrorg)
+ return result
+
+
+def check_ug_root_nova(deployment, client):
+ """
+ UG of Nova
+ """
+ result = True
+ statusu = statusg = -1
+ stderroru = stderrorg = "Failed to Stat file"
+ filenames = settings.getValue('NOP_FILES')
+ for filename in filenames:
+ if 'legacy' in deployment:
+ cmdu = "sudo stat -c '%U' " + filename
+ cmdg = "sudo stat -c '%G' " + filename
+ elif 'docker' in deployment:
+ cmdu = "sudo docker exec nova_api stat -c '%U' " + filename
+ cmdg = "sudo docker exec nova_api stat -c '%G' " + filename
+ elif 'k8s' in deployment:
+ pod = client.get_pod('openstack', 'nova-api-osapi')
+ cmdu = ['stat', '-c' '"%U"', filename]
+ cmdg = ['stat', '-c' '"%G"', filename]
+ else:
+ print("Deployment type not supported")
+ return False
+ if 'k8s' not in deployment:
+ statusu, stdoutu, stderroru = client.execute(cmdu)
+ statusg, stdoutg, stderrorg = client.execute(cmdg)
+ else:
+ stdoutu = client.execute(pod, cmdu)
+ stdoutg = client.execute(pod, cmdg)
+ if stdoutu:
+ statusu = 0
+ if stdoutg:
+ statusg = 0
+
+ if statusu == 0:
+ if 'root' not in stdoutu:
+ print('The user of File {} is not root ... NOT OK'.format(filename))
+ result = False
+ else:
+ print(stderroru)
+ if statusg == 0:
+ if 'nova' not in stdoutg:
+ print(filename)
+ print('The group ownership of file {} is not nova ... NOT OK'.format(filename))
+ result = False
+ else:
+ print(stderrorg)
+ return result
+
+
+def check_ug_root_neutron(deployment, client):
+ """
+ UG of Neutron
+ """
+ result = True
+ statusu = statusg = -1
+ stderroru = stderrorg = "Failed to Stat file"
+ # https://stackoverflow.com/questions/927866/how-to-get-the-owner-and-group-of-a-folder-with-python-on-a-linux-machine
+ filenames = settings.getValue('NEP_FILES')
+ for filename in filenames:
+ if 'legacy' in deployment:
+ cmdu = "sudo stat -c '%U' " + filename
+ cmdg = "sudo stat -c '%G' " + filename
+ elif 'docker' in deployment:
+ cmdu = "sudo docker exec neutron_api stat -c '%U' " + filename
+ cmdg = "sudo docker exec neutron_api stat -c '%G' " + filename
+ elif 'k8s' in deployment:
+ pod = client.get_pod('openstack', 'neutron-server')
+ cmdu = ['stat', '-c' '"%U"', filename]
+ cmdg = ['stat', '-c' '"%G"', filename]
+ else:
+ print("Deployment type not supported")
+ return False
+ if 'k8s' not in deployment:
+ statusu, stdoutu, stderroru = client.execute(cmdu)
+ statusg, stdoutg, stderrorg = client.execute(cmdg)
+ else:
+ stdoutu = client.execute(pod, cmdu)
+ stdoutg = client.execute(pod, cmdg)
+ if stdoutu:
+ statusu = 0
+ if stdoutg:
+ statusg = 0
+
+ if statusu == 0:
+ if 'root' not in stdoutu:
+ print('The user of File {} is not root ... NOT OK'.format(filename))
+ result = False
+ else:
+ print(stderroru)
+ if statusg == 0:
+ if ('neutron' not in stdoutg and
+ 'root' not in stdoutg):
+ print(filename)
+ print('The group ownership of file {} is not neutron ... NOT OK'.format(filename))
+ result = False
+ else:
+ print(stderrorg)
+ return result
+
+
+def check_ug_root_cinder(deployment, client):
+ """
+ UG of Cinder
+ """
+ result = True
+ statusu = statusg = -1
+ statusg = -1
+ stderroru = "Failed to Stat file"
+ stderrorg = "Failed to Stat file"
+ # https://stackoverflow.com/questions/927866/how-to-get-the-owner-and-group-of-a-folder-with-python-on-a-linux-machine
+ filenames = settings.getValue('CIP_FILES')
+ for filename in filenames:
+ if 'legacy' in deployment:
+ cmdu = "sudo stat -c '%U' " + filename
+ cmdg = "sudo stat -c '%G' " + filename
+ elif 'docker' in deployment:
+ cmdu = "sudo docker exec cinder_api stat -c '%U' " + filename
+ cmdu = "sudo docker exec cinder_api stat -c '%G' " + filename
+ elif 'k8s' in deployment:
+ pod = client.get_pod('openstack', 'cinder-api')
+ cmdu = ['stat', '-c' '"%U"', filename]
+ cmdg = ['stat', '-c' '"%G"', filename]
+ else:
+ print("Deployment type not supported")
+ return False
+ if 'k8s' not in deployment:
+ statusu, stdoutu, stderroru = client.execute(cmdu)
+ statusg, stdoutg, stderrorg = client.execute(cmdg)
+ else:
+ stdoutu = client.execute(pod, cmdu)
+ stdoutg = client.execute(pod, cmdg)
+ if stdoutu:
+ statusu = 0
+ if stdoutg:
+ statusg = 0
+ if statusu == 0:
+ if 'root' not in stdoutu:
+ print('The user of File {} is not root ... NOT OK'.format(filename))
+ result = False
+ else:
+ print(stderroru)
+ if statusg == 0:
+ if 'cinder' not in stdoutg:
+ print(filename)
+ print('The group ownership of file {} is not cinder ... NOT OK'.format(filename))
+ result = False
+ else:
+ print(stderrorg)
+ return result
+
+def testing_k8s(k8s):
+ """
+ Testing Kubernetes
+ """
+ pod = k8s.get_pod('ceph', 'ingress')
+ if pod:
+ print(pod.metadata.name)
+ print(pod.metadata.namespace)
+ response = k8s.execute(pod, 'ls')
+ print(response)
+
+
+def main():
+ """Main function.
+ """
+ client = None
+ args = parse_arguments()
+
+ # define the timestamp to be used by logs and results
+ date = datetime.datetime.fromtimestamp(time.time())
+ timestamp = date.strftime('%Y-%m-%d_%H-%M-%S')
+ settings.setValue('LOG_TIMESTAMP', timestamp)
+
+
+ # configure settings
+ settings.load_from_dir(os.path.join(_CURR_DIR, 'conf'))
+
+ # Read default config file.
+ def_conf_file = '/conf/security.conf'
+ if os.path.exists(def_conf_file):
+ settings.load_from_file(def_conf_file)
+
+ # Read from Environment
+ settings.load_from_env()
+
+
+ # if required, handle list-* operations
+ handle_list_options(args)
+
+ configure_logging(settings.getValue('VERBOSITY'))
+ deployment = settings.getValue('DEPLOYMENT')
+ if ('docker' in deployment or
+ 'podman' in deployment or
+ 'legacy' in deployment):
+ print('Deployment is Docker')
+ if 'key' in settings.getValue('ACCESS_TYPE'):
+ client = sshclient.SSH(host = settings.getValue('HOST'),
+ user = settings.getValue('USER'),
+ key_filename = settings.getValue('PRIVATE_KEY_FILE'))
+ elif 'k8s' in deployment:
+ client = k8sclient.K8sClient()
+
+ # Run checks
+ if check_ug_root_cinder(deployment, client):
+ print('UG-ROOT-CINDER PASSED')
+ else:
+ print('UG-ROOT-CINDER FAILED')
+ if check_ug_root_neutron(deployment, client):
+ print('UG-ROOT-NEUTRON PASSED')
+ else:
+ print('UG-ROOT-NEUTRON FAILED')
+ if check_ug_keystone(deployment, client):
+ print('UG-KEYSTONE-KEYSTONE PASSED')
+ else:
+ print('UG-ROOT-KEYSTONE FAILED')
+ if check_ug_root_nova(deployment, client):
+ print('UG-ROOT-NOVA PASSED')
+ else:
+ print('UG-ROOT-NOVA FAILED')
+ if check_sixfourzero(deployment, client):
+ print('ALL FILE PERMISSIONS ARE STRICT')
+ else:
+ print('SOME FILE PERMISSIONS ARE NOT STRICT')
+ if check_nova_hardening(deployment, client):
+ print('NOVA HARDENING PASSED')
+ else:
+ print('NOVA HARDENING FAILED')
+ if check_cinder_hardening(deployment, client):
+ print('CINDER HARDENING PASSED')
+ else:
+ print('CINDER HARDENING FAILED')
+ if check_neutron_hardening(deployment, client):
+ print('NEUTRON HARDENING PASSED')
+ else:
+ print('NEUTRON HARDENING FAILED')
+ if check_keystone_hardening(deployment, client):
+ print('KEYSTONE HARDENING PASSED')
+ else:
+ print('KEYSTONE HARDENING FAILED')
+ if check_horizon_hardening(deployment, client):
+ print('HORIZON HARDENING PASSED')
+ else:
+ print('HORIZON HARDENING FAILED')
+
+if __name__ == "__main__":
+ main()