def _check_fortify(path): """Fortify Source - This introduces support for detecting buffer overflows in various functions that perform operations on memory and strings. The indicator for this is symbols such as __sprintf_chk rather then __sprintf. To compile an executable with fortify source enabled: $ gcc foo.c -D_FORTIFY_SOURCE=2 -O2 """ libc = _find_used_libc(path) if not libc: logger.debug('Unable to determine location of libc') return (False, Result.CONF_GUESS) fortified = set([]) for addr, sym, name in _symbols_in_dynsym(libc): if sym in (b'T', b'i') and name.endswith(b'_chk'): fortified.add(name) plain = set(name[2:-4] for name in fortified) symbols = set([name for addr, sym, name in _symbols_in_dynsym(path)]) if len(symbols.intersection(fortified)) > 0: return (True, Result.CONF_SURE) # if there are no functions to fortify, treat it the same as fortified if len(symbols.intersection(plain)) == 0: return (True, Result.CONF_SURE) # there may be a situation where a function is used on a buffer of unknown # size and cannot be fortified - or it may be just not fortified return (False, Result.CONF_GUESS)
def test_IPC_host(): logger.debug("Testing if the container is running in user namespace.") notes = "No Docker containers found or docker is not running." results = GroupTestResult() containers = _get_docker_container() testcmd = '{{ .Id }}: IpcMode={{ .HostConfig.Devices }}' if not containers: return TestResult(Result.SKIP, notes) for container_id in containers: if container_id == '': pass else: check = "Checking container: " + str(container_id) test = subprocess.check_output( ['docker', 'inspect', '--format', testcmd, container_id]) if 'host' in test: notes = ("Container " + str(container_id) + " is " "sharing IPC namespace with the container. ") result = TestResult(Result.FAIL, notes) else: result = TestResult(Result.PASS) results.add_result(check, result) return results
def _extract_symbols(cmd): """Helper function to reduce code duplication. Only difference in output of commands comes from the way the 'nm' command is run. :param cmd: The way to invoke 'nm' command. :returns: Generated symbols resulting from nm invocation. """ try: entries = _safe_exec(cmd) for entry in entries.split(b'\n'): try: values = entry.strip().split(b' ') # handle case: # U __sprintf_chk@@GLIBC_2.3.4 if len(values) == 2: sym_addr = None sym_type, sym_name = values # otherwise expect: # 00000000004004b0 T main else: sym_addr, sym_type, sym_name = entry.split(b' ') yield (sym_addr, sym_type, sym_name.split(b'@@')[0]) except ValueError as err: logger.debug('Unexpected output [ %s ]', entry.strip()) except subprocess.CalledProcessError as err: logger.debug(err)
def test_ulimit_default_override(): logger.debug("Testing if the container is running in user namespace.") notes = "No Docker containers found or docker is not running." results = GroupTestResult() containers = _get_docker_container() testcmd = '{{ .Id }}: Ulimits={{ .HostConfig.Ulimits }}' if not containers: return TestResult(Result.SKIP, notes) for container_id in containers: if container_id == '': pass else: check = "Checking container: " + str(container_id) test = subprocess.check_output( ['docker', 'inspect', '--format', testcmd, container_id]) if '<no value>' in test: result = TestResult(Result.PASS) else: notes = ("Container " + str(container_id) + " is " "running with default ulimits in place. ") result = TestResult(Result.FAIL, notes) results.add_result(check, result) return results
def test_read_only_root_fs(): logger.debug("Testing if the container is running in user namespace.") notes = "No Docker containers found or docker is not running." results = GroupTestResult() containers = _get_docker_container() testcmd = '{{ .Id }}: ReadonlyRootfs={{ .HostConfig.ReadonlyRootfs }}' if not containers: return TestResult(Result.SKIP, notes) for container_id in containers: if container_id == '': pass else: check = "Checking container: " + str(container_id) test = subprocess.check_output(['docker', 'inspect', '--format', testcmd, container_id]) if 'false' in test: result = TestResult(Result.PASS) else: notes = ("Container " + str(container_id) + " has a file " "system with permissions that are not read only.") result = TestResult(Result.FAIL, notes) results.add_result(check, result) return results
def test_read_only_root_fs(): logger.debug("Testing if the container is running in user namespace.") notes = "No Docker containers found or docker is not running." results = GroupTestResult() containers = _get_docker_container() testcmd = '{{ .Id }}: ReadonlyRootfs={{ .HostConfig.ReadonlyRootfs }}' if not containers: return TestResult(Result.SKIP, notes) for container_id in containers: if container_id == '': pass else: check = "Checking container: " + str(container_id) test = subprocess.check_output( ['docker', 'inspect', '--format', testcmd, container_id]) if 'false' in test: result = TestResult(Result.PASS) else: notes = ("Container " + str(container_id) + " has a file " "system with permissions that are not read only.") result = TestResult(Result.FAIL, notes) results.add_result(check, result) return results
def test_docker_pid_mode(): logger.debug("Testing if the container is running in user namespace.") notes = "No Docker containers found or docker is not running." results = GroupTestResult() containers = _get_docker_container() testcmd = '{{ .Id }}: PidMode={{ .HostConfig.PidMode }}' if not containers: return TestResult(Result.SKIP, notes) for container_id in containers: if container_id == '': pass else: check = "Checking container: " + str(container_id) test = subprocess.check_output(['docker', 'inspect', '--format', testcmd, container_id]) if 'host' in test: notes = ("Container " + str(container_id) + " is sharing " "host process namespaces.") result = TestResult(Result.FAIL, notes) else: result = TestResult(Result.PASS) results.add_result(check, result) return results
def test_docker_privilege(): logger.debug("Testing if the container is running in user namespace.") notes = "No Docker containers found or docker is not running." results = GroupTestResult() containers = _get_docker_container() testcmd = '{{ .Id }}: {{.HostConfig.Privileged }}' if not containers: return TestResult(Result.SKIP, notes) for container_id in containers: if container_id == '': pass else: check = "Checking container: " + str(container_id) test = subprocess.check_output(['docker', 'inspect', '--format', testcmd, container_id]) entry = test.split(':') if 'false' in entry: result = TestResult(Result.PASS) else: notes = ("Container " + str(container_id) + " is running with " "privileged flags set to true.") result = TestResult(Result.FAIL, notes) results.add_result(check, result) return results
def test_mount_sensitive_directories(): logger.debug("Testing if the container is running in user namespace.") notes = "No Docker containers found or docker is not running." results = GroupTestResult() containers = _get_docker_container() testcmd = '{{ .Id }}: Volumes={{ .Volumes }} VolumesRW={{ .VolumesRW }}' if not containers: return TestResult(Result.SKIP, notes) for container_id in containers: if container_id == '': pass else: check = "Checking container: " + str(container_id) test = subprocess.check_output(['docker', 'inspect', '--format', testcmd, container_id]) if ':true' in test: notes = ("Container " + str(container_id) + " has " "sensitive host system directories " + "mounted.") result = TestResult(Result.FAIL, notes) else: result = TestResult(Result.PASS) results.add_result(check, result) return results
def test_docker_privilege(): logger.debug("Testing if the container is running in user namespace.") notes = "No Docker containers found or docker is not running." results = GroupTestResult() containers = _get_docker_container() testcmd = '{{ .Id }}: {{.HostConfig.Privileged }}' if not containers: return TestResult(Result.SKIP, notes) for container_id in containers: if container_id == '': pass else: check = "Checking container: " + str(container_id) test = subprocess.check_output( ['docker', 'inspect', '--format', testcmd, container_id]) entry = test.split(':') if 'false' in entry: result = TestResult(Result.PASS) else: notes = ("Container " + str(container_id) + " is running with " "privileged flags set to true.") result = TestResult(Result.FAIL, notes) results.add_result(check, result) return results
def test_log_level(): logger.debug("Checking the Docker log level.") reason = "No Docker containers found." result = None # if --log-level is set in ps, else find config file and check there docker_ps = _get_docker_processes() if docker_ps is None: return TestResult(Result.SKIP, "Docker is not running.") for entry in docker_ps: if '--log-level=info' in entry: result = TestResult(Result.PASS) elif '--log-level=debug' in entry: reason = ("It is not recommended to run Docker in production " "in debug mode.") result = TestResult(Result.FAIL, reason) else: notes = "Recommended Docker log level is 'info'." result = TestResult(Result.PASS, notes) return result
def _extract_symbols(cmd): """Helper function to reduce code duplication. Only difference in output of commands comes from the way the 'nm' command is run. :param cmd: The way to invoke 'nm' command. :returns: Generated symbols resulting from nm invocation. """ try: entries = _safe_exec(cmd) for entry in entries.split(b'\n'): try: values = entry.strip().split(b' ') # handle case: # U __sprintf_chk@@GLIBC_2.3.4 if len(values) == 2: sym_addr = None sym_type, sym_name = values # otherwise expect: # 00000000004004b0 T main else: sym_addr, sym_type, sym_name = entry.split(b' ') yield (sym_addr, sym_type, sym_name.split(b'@@')[0]) except ValueError as err: logger.debug('Unexpected output [ %s ]', entry.strip()) except subprocess.CalledProcessError as err: logger.debug(err)
def test_list_installed_packages(): logger.debug("Listing installed packages.") notes = "" containers = _get_docker_ps() if not containers: reason = "No Docker containers found." return TestResult(Result.SKIP, reason) for line in containers: container_id = line.split(' ') for instance in container_id[0]: flavor = subprocess.check_output(['docker', 'exec', instance, 'cat', '/etc/issue']) if 'RH' in flavor: notes = subprocess.check_output(['docker', 'exec', instance, 'rpm', '-qa']).split('\n') elif 'DEB' in flavor: notes = subprocess.check_output(['docker', 'exec', instance, 'dpkg', '-l']).split('\n') else: reason = "Host is not RedHat or Debian family." return TestResult(Result.SKIP, reason) return TestResult(Result.PASS, notes)
def test_ulimit_default_override(): logger.debug("Testing if the container is running in user namespace.") notes = "No Docker containers found or docker is not running." results = GroupTestResult() containers = _get_docker_container() testcmd = '{{ .Id }}: Ulimits={{ .HostConfig.Ulimits }}' if not containers: return TestResult(Result.SKIP, notes) for container_id in containers: if container_id == '': pass else: check = "Checking container: " + str(container_id) test = subprocess.check_output(['docker', 'inspect', '--format', testcmd, container_id]) if '<no value>' in test: result = TestResult(Result.PASS) else: notes = ("Container " + str(container_id) + " is " "running with default ulimits in place. ") result = TestResult(Result.FAIL, notes) results.add_result(check, result) return results
def test_log_level(): logger.debug("Checking the Docker log level.") reason = "No Docker containers found." result = None # if --log-level is set in ps, else find config file and check there docker_ps = _get_docker_processes() if docker_ps is None: return TestResult(Result.SKIP, "Docker is not running.") for entry in docker_ps: if '--log-level=info' in entry: result = TestResult(Result.PASS) elif '--log-level=debug' in entry: reason = ("It is not recommended to run Docker in production " "in debug mode.") result = TestResult(Result.FAIL, reason) else: notes = "Recommended Docker log level is 'info'." result = TestResult(Result.PASS, notes) return result
def test_mount_sensitive_directories(): logger.debug("Testing if the container is running in user namespace.") notes = "No Docker containers found or docker is not running." results = GroupTestResult() containers = _get_docker_container() testcmd = '{{ .Id }}: Volumes={{ .Volumes }} VolumesRW={{ .VolumesRW }}' if not containers: return TestResult(Result.SKIP, notes) for container_id in containers: if container_id == '': pass else: check = "Checking container: " + str(container_id) test = subprocess.check_output( ['docker', 'inspect', '--format', testcmd, container_id]) if ':true' in test: notes = ("Container " + str(container_id) + " has " "sensitive host system directories " + "mounted.") result = TestResult(Result.FAIL, notes) else: result = TestResult(Result.PASS) results.add_result(check, result) return results
def test_list_installed_packages(): logger.debug("Listing installed packages.") notes = "" containers = _get_docker_ps() if not containers: reason = "No Docker containers found." return TestResult(Result.SKIP, reason) for line in containers: container_id = line.split(' ') for instance in container_id[0]: flavor = subprocess.check_output( ['docker', 'exec', instance, 'cat', '/etc/issue']) if 'RH' in flavor: notes = subprocess.check_output( ['docker', 'exec', instance, 'rpm', '-qa']).split('\n') elif 'DEB' in flavor: notes = subprocess.check_output( ['docker', 'exec', instance, 'dpkg', '-l']).split('\n') else: reason = "Host is not RedHat or Debian family." return TestResult(Result.SKIP, reason) return TestResult(Result.PASS, notes)
def _check_fortify(path): """Fortify Source - This introduces support for detecting buffer overflows in various functions that perform operations on memory and strings. The indicator for this is symbols such as __sprintf_chk rather then __sprintf. To compile an executable with fortify source enabled: $ gcc foo.c -D_FORTIFY_SOURCE=2 -O2 """ libc = _find_used_libc(path) if not libc: logger.debug('Unable to determine location of libc') return (False, Result.CONF_GUESS) fortified = set([]) for addr, sym, name in _symbols_in_dynsym(libc): if sym in (b'T', b'i') and name.endswith(b'_chk'): fortified.add(name) plain = set(name[2:-4] for name in fortified) symbols = set([name for addr, sym, name in _symbols_in_dynsym(path)]) if len(symbols.intersection(fortified)) > 0: return (True, Result.CONF_SURE) # if there are no functions to fortify, treat it the same as fortified if len(symbols.intersection(plain)) == 0: return (True, Result.CONF_SURE) # there may be a situation where a function is used on a buffer of unknown # size and cannot be fortified - or it may be just not fortified return (False, Result.CONF_GUESS)
def test_restart_policy(): logger.debug("Testing if the container is running in user namespace.") notes = "No Docker containers found or docker is not running." results = GroupTestResult() containers = _get_docker_container() testcmd = '''{{ .Id }}: RestartPolicyName={{ .HostConfig.RestartPolicy.Name }} MaximumRetryCount={{ .HostConfig.RestartPolicy.MaximumRetryCount }}''' if not containers: return TestResult(Result.SKIP, notes) for container_id in containers: if container_id == '': pass else: check = "Checking container: " + str(container_id) test = subprocess.check_output(['docker', 'inspect', '--format', testcmd, container_id]) try: entry = test.split(':') r = entry[1].split('=') restart_policy = r[1].split(" ") max_retry = r[2] policy = str(restart_policy[0]) except IndexError: notes = ("Container: " + str(container_id) + "returns " "a malformed restart policy value.") result = TestResult(Result.SKIP, notes) else: if 'no' in policy or policy == " ": result = TestResult(Result.PASS) elif policy is None: result = TestResult(Result.PASS) elif policy == 'always': notes = ("Container " + str(container_id) + " will always " "restart regardless of max retry count. This is " " not recommended.") result = TestResult(Result.FAIL, notes) elif policy == 'on-failure': if int(max_retry) <= 5: result = TestResult(Result.PASS) else: notes = ("Container " + str(container_id) + " max " "retry count set to a non-compliant level.") result = TestResult(Result.FAIL, notes) else: notes = ("Cannot test. Container " + str(container_id) + " settings not returning an expected value.") result = TestResult(Result.SKIP, notes) results.add_result(check, result) return results
def test_restart_policy(): logger.debug("Testing if the container is running in user namespace.") notes = "No Docker containers found or docker is not running." results = GroupTestResult() containers = _get_docker_container() testcmd = '''{{ .Id }}: RestartPolicyName={{ .HostConfig.RestartPolicy.Name }} MaximumRetryCount={{ .HostConfig.RestartPolicy.MaximumRetryCount }}''' if not containers: return TestResult(Result.SKIP, notes) for container_id in containers: if container_id == '': pass else: check = "Checking container: " + str(container_id) test = subprocess.check_output( ['docker', 'inspect', '--format', testcmd, container_id]) try: entry = test.split(':') r = entry[1].split('=') restart_policy = r[1].split(" ") max_retry = r[2] policy = str(restart_policy[0]) except IndexError: notes = ("Container: " + str(container_id) + "returns " "a malformed restart policy value.") result = TestResult(Result.SKIP, notes) else: if 'no' in policy or policy == " ": result = TestResult(Result.PASS) elif policy is None: result = TestResult(Result.PASS) elif policy == 'always': notes = ("Container " + str(container_id) + " will always " "restart regardless of max retry count. This is " " not recommended.") result = TestResult(Result.FAIL, notes) elif policy == 'on-failure': if int(max_retry) <= 5: result = TestResult(Result.PASS) else: notes = ("Container " + str(container_id) + " max " "retry count set to a non-compliant level.") result = TestResult(Result.FAIL, notes) else: notes = ("Cannot test. Container " + str(container_id) + " settings not returning an expected value.") result = TestResult(Result.SKIP, notes) results.add_result(check, result) return results
def test_devmem(): # initial configurations reason = " " logger.debug("Attempting to validate /dev/mem protection.") result = Result.FAIL # set fail by default? # check kernel config - CONFIG_STRICT_DEVMEM=y try: devmem_val = utils.kconfig_option('CONFIG_STRICT_DEVMEM') if devmem_val == 'y': reason = "/dev/mem protection is enabled." logger.debug(reason) result = Result.PASS elif devmem_val == 'n': reason = "/dev/mem protection is not enabled." logger.debug(reason) result = Result.FAIL else: result = Result.SKIP reason = "Cannot find the kernel config or option" except IOError as e: reason = "Error opening /proc/config.gz." logger.debug("Unable to open /proc/config.gz.\n" " Exception information: [ {} ]".format(e)) result = Result.SKIP return TestResult(result, reason)
def test_storage_driver(): logger.debug("Checking storage driver.") notes = "No Docker containers found." driver = _parse_colon_delim(list, key='Storage Driver') if driver: if 'aufs' in driver: notes = "Storage driver set to insecure aufs." return TestResult(Result.FAIL, notes) else: return TestResult(Result.PASS) else: # empty driver, odd failure return TestResult(Result.SKIP, notes)
def test_storage_driver(): logger.debug("Checking storage driver.") notes = "No Docker containers found." driver = _parse_colon_delim(list, key='Storage Driver') if driver: if 'aufs' in driver: notes = "Storage driver set to insecure aufs." return TestResult(Result.FAIL, notes) else: return TestResult(Result.PASS) else: # empty driver, odd failure return TestResult(Result.SKIP, notes)
def test_traffic(): logger.debug("Testing for restricted traffic between containers.") dockers = _get_main_docker_processes() if not dockers: return TestResult(Result.SKIP, "Docker is not running.") results = GroupTestResult() for pid, cmdline in dockers: if '--icc=false' in cmdline: results.add_result("pid %s" % pid, TestResult(Result.PASS)) else: reason = "Direct communication between containers is enabled." results.add_result("pid %s" % pid, TestResult(Result.FAIL, reason)) return results
def test_cpu_priority(): logger.debug("Testing if the container has memory limitations.") notes = "No Docker containers found or docker is not running." results = GroupTestResult() containers = _get_docker_container() testcmd = '{{ .Id }}: CpuShares={{ .Config.CpuShares }}' if not containers: return TestResult(Result.SKIP, notes) for container_id in containers: if container_id == '': pass else: check = "Checking container: " + str(container_id) test = subprocess.check_output(['docker', 'inspect', '--format', testcmd, container_id]) cpu_test = test.split(':') try: cpu_return = cpu_test[1].strip('\n') except IndexError: notes = ("Container: " + str(container_id) + "returns " "a malformed CPU share value.") result = TestResult(Result.SKIP, notes) else: if '<no value>' not in cpu_return: notes = ("Container " + str(container_id) + " is running " "with no value given for CPU share limitations.") result = TestResult(Result.FAIL, notes) elif not cpu_return: notes = ("Container " + str(container_id) + " is running " "with no value given for CPU share limitations.") result = TestResult(Result.SKIP, notes) elif int(cpu_return) == 0 or int(cpu_return) == 1024: notes = ("Container " + str(container_id) + " do not have " "CPU shares in place.") result = TestResult(Result.FAIL, notes) else: result = TestResult(Result.PASS) results.add_result(check, result) return results
def test_storage_driver(): logger.debug("Checking storage driver.") info = _get_docker_info() if not info: return TestResult(Result.SKIP, "Cannot get docker info.") driver = [l for l in info.splitlines() if l.startswith(b'Storage Driver:')] if driver: if b'aufs' in driver[0]: notes = "Storage driver set to insecure aufs." return TestResult(Result.FAIL, notes) else: return TestResult(Result.PASS) else: # empty driver, odd failure return TestResult(Result.SKIP, "Cannot find storage driver")
def test_iptables(): logger.debug("Checking the firewall settings.") dockers = _get_main_docker_processes() if not dockers: return TestResult(Result.SKIP, "Docker is not running.") results = GroupTestResult() for pid, cmdline in dockers: if '--iptables=false' in cmdline: reason = ("The iptables firewall is enabled on a per-container " "basis and will need to be maintained by the user.") results.add_result("pid %s" % pid, TestResult(Result.FAIL, reason)) else: results.add_result("pid %s" % pid, TestResult(Result.PASS)) return results
def test_insecure_registries(): logger.debug("Testing for insecure registries.") dockers = _get_main_docker_processes() if not dockers: return TestResult(Result.SKIP, "Docker is not running.") results = GroupTestResult() for pid, cmdline in dockers: if '--insecure-registry' in cmdline: reason = ("A registry was specified with the --insecure-registry " "flag.") results.add_result("pid %s" % pid, TestResult(Result.FAIL, reason)) else: results.add_result("pid %s" % pid, TestResult(Result.PASS)) return results
def test_memory_limit(): logger.debug("Testing if the container has memory limitations.") notes = "No Docker containers found or docker is not running." results = GroupTestResult() containers = _get_docker_container() if not containers: return TestResult(Result.SKIP, notes) for container_id in containers: if container_id == '': pass else: check = "Checking container: " + str(container_id) test = subprocess.check_output(['docker', 'inspect', '--format', '{{.ID}}:{{.Config.Memory}}', container_id]) mem_test = test.split(':') try: memory = mem_test[1].strip('\n') except IndexError: notes = ("Container: " + str(container_id) + "returns " "a malformed memory value.") result = TestResult(Result.SKIP, notes) else: if memory == '<no value>': notes = ("Container " + str(container_id) + " is running " "with no value given for memory limitations.") result = TestResult(Result.FAIL, notes) elif memory is None: notes = ("Container " + str(container_id) + " is running " "without memory limitations.") result = TestResult(Result.FAIL, notes) elif int(memory) <= 0: notes = ("Container " + str(container_id) + " is running " "without memory limitations.") result = TestResult(Result.FAIL, notes) else: result = TestResult(Result.PASS) results.add_result(check, result) return results
def test_iptables(): logger.debug("Checking the firewall settings.") reason = "No Docker containers found." result = None docker_ps = _get_docker_processes() if docker_ps is None: return TestResult(Result.SKIP, "Docker is not running.") for entry in docker_ps: if '--iptables=false' in entry: result = TestResult(Result.PASS) else: reason = ("The iptables firewall is enabled on a per-container " "basis and will need to be maintained by the user.") result = TestResult(Result.FAIL, reason) return result
def test_traffic(): logger.debug("Testing for restricted traffic between containers.") reason = "No Docker containers found." result = None docker_ps = _get_docker_processes() if docker_ps is None: return TestResult(Result.SKIP, "Docker is not running.") for entry in docker_ps: if '--icc=false' in entry: result = TestResult(Result.PASS) else: reason = "Direct communication between containers is enabled." result = TestResult(Result.FAIL, reason) return result
def test_no_lxc(): logger.debug("Testing if the container is running in LXC memory.") reason = "No Docker containers found." result = None docker_ps = _get_docker_processes() if docker_ps is None: return TestResult(Result.SKIP, "Docker is not running.") for entry in docker_ps: if 'lxc' in entry: reason = "LXC in ps output." result = TestResult(Result.FAIL, reason) else: result = TestResult(Result.PASS) return result
def test_no_lxc(): logger.debug("Testing if the container is running in LXC memory.") reason = "No Docker containers found." docker_ps = _get_main_docker_processes() if docker_ps is None: return TestResult(Result.SKIP, "Docker is not running.") results = GroupTestResult() for pid, cmdline in docker_ps: if b'lxc' in cmdline: reason = "LXC in ps output." results.add_result("pid %s" % pid, TestResult(Result.FAIL, reason)) else: results.add_result("pid %s" % pid, TestResult(Result.PASS)) return results
def test_traffic(): logger.debug("Testing for restricted traffic between containers.") reason = "No Docker containers found." result = None docker_ps = _get_docker_processes() if docker_ps is None: return TestResult(Result.SKIP, "Docker is not running.") for entry in docker_ps: if '--icc=false' in entry: result = TestResult(Result.PASS) else: reason = "Direct communication between containers is enabled." result = TestResult(Result.FAIL, reason) return result
def test_iptables(): logger.debug("Checking the firewall settings.") reason = "No Docker containers found." result = None docker_ps = _get_docker_processes() if docker_ps is None: return TestResult(Result.SKIP, "Docker is not running.") for entry in docker_ps: if '--iptables=false' in entry: result = TestResult(Result.PASS) else: reason = ("The iptables firewall is enabled on a per-container " "basis and will need to be maintained by the user.") result = TestResult(Result.FAIL, reason) return result
def test_no_lxc(): logger.debug("Testing if the container is running in LXC memory.") reason = "No Docker containers found." result = None docker_ps = _get_docker_processes() if docker_ps is None: return TestResult(Result.SKIP, "Docker is not running.") for entry in docker_ps: if 'lxc' in entry: reason = "LXC in ps output." result = TestResult(Result.FAIL, reason) else: result = TestResult(Result.PASS) return result
def test_privilege_port_mapping(): logger.debug("Testing if the container has memory limitations.") notes = "No Docker containers found or docker is not running." results = GroupTestResult() containers = _get_docker_container() if not containers: return TestResult(Result.SKIP, notes) for container_id in containers: if container_id == '': pass else: check = "Checking container: " + str(container_id) test = subprocess.check_output(['docker', 'port', container_id]) pn = test.split(':') try: port_number = str(pn[1]) except IndexError: notes = ("Container: " + str(container_id) + "returns " "a malformed port number value.") result = TestResult(Result.SKIP, notes) else: if int(port_number) <= 1024: notes = ("Container " + str(container_id) + " is running " "privileged port number - " + str(port_number) + ".") result = TestResult(Result.FAIL, notes) elif port_number == '': notes = ("Container " + str(container_id) + " does not" "have a port number assigned.") result = TestResult(Result.FAIL, notes) elif port_number is None: notes = ("Container " + str(container_id) + " does not" "have a port number assigned.") result = TestResult(Result.FAIL, notes) else: result = TestResult(Result.PASS) results.add_result(check, result) return results
def test_cpu_priority(): logger.debug("Testing if the container has memory limitations.") notes = "No Docker containers found or docker is not running." results = GroupTestResult() containers = _get_docker_container() testcmd = '{{ .Id }}: CpuShares={{ .Config.CpuShares }}' if not containers: return TestResult(Result.SKIP, notes) for container_id in containers: if container_id == '': pass else: check = "Checking container: " + str(container_id) test = subprocess.check_output( ['docker', 'inspect', '--format', testcmd, container_id]) cpu_test = test.split(':') try: cpu_return = cpu_test[1].strip('\n') except IndexError: notes = ("Container: " + str(container_id) + "returns " "a malformed CPU share value.") result = TestResult(Result.SKIP, notes) else: if '<no value>' not in cpu_return: notes = ("Container " + str(container_id) + " is running " "with no value given for CPU share limitations.") result = TestResult(Result.FAIL, notes) elif not cpu_return: notes = ("Container " + str(container_id) + " is running " "with no value given for CPU share limitations.") result = TestResult(Result.SKIP, notes) elif int(cpu_return) == 0 or int(cpu_return) == 1024: notes = ("Container " + str(container_id) + " do not have " "CPU shares in place.") result = TestResult(Result.FAIL, notes) else: result = TestResult(Result.PASS) results.add_result(check, result) return results
def test_memory_limit(): logger.debug("Testing if the container has memory limitations.") notes = "No Docker containers found or docker is not running." results = GroupTestResult() containers = _get_docker_container() if not containers: return TestResult(Result.SKIP, notes) for container_id in containers: if container_id == '': pass else: check = "Checking container: " + str(container_id) test = subprocess.check_output([ 'docker', 'inspect', '--format', '{{.ID}}:{{.Config.Memory}}', container_id ]) mem_test = test.split(':') try: memory = mem_test[1].strip('\n') except IndexError: notes = ("Container: " + str(container_id) + "returns " "a malformed memory value.") result = TestResult(Result.SKIP, notes) else: if memory == '<no value>': notes = ("Container " + str(container_id) + " is running " "with no value given for memory limitations.") result = TestResult(Result.FAIL, notes) elif memory is None: notes = ("Container " + str(container_id) + " is running " "without memory limitations.") result = TestResult(Result.FAIL, notes) elif int(memory) <= 0: notes = ("Container " + str(container_id) + " is running " "without memory limitations.") result = TestResult(Result.FAIL, notes) else: result = TestResult(Result.PASS) results.add_result(check, result) return results
def test_port_binding(): logger.debug("Testing for insecure registries.") reason = "No Docker containers found." result = None docker_ps = _get_docker_processes() if docker_ps is None: return TestResult(Result.SKIP, "Docker is not running.") for entry in docker_ps: if '-H' in entry: reason = ("A container is binding to a specific interface " "or port.") result = TestResult(Result.FAIL, reason) else: result = TestResult(Result.PASS) return result
def test_secure_communication(): # TODO: check if there is a 'well known' HDP container that either # acts as an intermediate CA (for --tlscacert option), or a server # that clients connect to securely (for --tlscert and tlskey), and # if so, break them into separate tests for better profile coverage logger.debug("Testing for insecure registries.") reason = "No Docker containers found." docker_ps = _get_docker_processes() if docker_ps is None: return TestResult(Result.SKIP, "Docker is not running.") for entry in docker_ps: if '--tlsverify' in entry: if '--tlscert' in entry: if '--tlskey' in entry: if '--tlscacert' in entry: reason = ("Container set to validate certificates, " "has both certificate and key in place, " "and can act as an intermediate CA.") logger.info(reason) else: reason = ("No CA certificate, container cannot act " "as intermediate CA.") logger.info(reason) else: reason = ("A public Certificate exists, but key does not." " Communciation unable to be decrypted.") logger.info(reason) else: reason = ("No certificate available, container will only be" " able to act as client and accept server cert.") else: reason = "Docker is not configured to validate certificates." result = TestResult(Result.FAIL, reason) return result result = TestResult(Result.PASS) return result
def test_insecure_registries(): logger.debug("Testing for insecure registries.") reason = "No Docker containers found." result = None docker_ps = _get_docker_processes() if docker_ps is None: return TestResult(Result.SKIP, "Docker is not running.") for entry in docker_ps: if '--insecure-registry' in entry: reason = ("A registry was specified with the --insecure-registry " "flag.") result = TestResult(Result.FAIL, reason) else: result = TestResult(Result.PASS) return result
def test_insecure_registries(): logger.debug("Testing for insecure registries.") reason = "No Docker containers found." result = None docker_ps = _get_docker_processes() if docker_ps is None: return TestResult(Result.SKIP, "Docker is not running.") for entry in docker_ps: if '--insecure-registry' in entry: reason = ("A registry was specified with the --insecure-registry " "flag.") result = TestResult(Result.FAIL, reason) else: result = TestResult(Result.PASS) return result
def test_port_binding(): logger.debug("Testing for insecure registries.") reason = "No Docker containers found." result = None docker_ps = _get_docker_processes() if docker_ps is None: return TestResult(Result.SKIP, "Docker is not running.") for entry in docker_ps: if '-H' in entry: reason = ("A container is binding to a specific interface " "or port.") result = TestResult(Result.FAIL, reason) else: result = TestResult(Result.PASS) return result
def test_secure_communication(): # TODO: check if there is a 'well known' HDP container that either # acts as an intermediate CA (for --tlscacert option), or a server # that clients connect to securely (for --tlscert and tlskey), and # if so, break them into separate tests for better profile coverage logger.debug("Testing for insecure registries.") reason = "No Docker containers found." docker_ps = _get_docker_processes() if docker_ps is None: return TestResult(Result.SKIP, "Docker is not running.") for entry in docker_ps: if '--tlsverify' in entry: if '--tlscert' in entry: if '--tlskey' in entry: if '--tlscacert' in entry: reason = ("Container set to validate certificates, " "has both certificate and key in place, " "and can act as an intermediate CA.") logger.info(reason) else: reason = ("No CA certificate, container cannot act " "as intermediate CA.") logger.info(reason) else: reason = ("A public Certificate exists, but key does not." " Communciation unable to be decrypted.") logger.info(reason) else: reason = ("No certificate available, container will only be" " able to act as client and accept server cert.") else: reason = "Docker is not configured to validate certificates." result = TestResult(Result.FAIL, reason) return result result = TestResult(Result.PASS) return result
def test_privilege_port_mapping(): logger.debug("Testing if the container has memory limitations.") notes = "No Docker containers found or docker is not running." results = GroupTestResult() containers = _get_docker_container() if not containers: return TestResult(Result.SKIP, notes) for container_id in containers: if container_id == '': pass else: check = "Checking container: " + str(container_id) test = subprocess.check_output(['docker', 'port', container_id]) pn = test.split(':') try: port_number = str(pn[1]) except IndexError: notes = ("Container: " + str(container_id) + "returns " "a malformed port number value.") result = TestResult(Result.SKIP, notes) else: if int(port_number) <= 1024: notes = ("Container " + str(container_id) + " is running " "privileged port number - " + str(port_number) + ".") result = TestResult(Result.FAIL, notes) elif port_number == '': notes = ("Container " + str(container_id) + " does not" "have a port number assigned.") result = TestResult(Result.FAIL, notes) elif port_number is None: notes = ("Container " + str(container_id) + " does not" "have a port number assigned.") result = TestResult(Result.FAIL, notes) else: result = TestResult(Result.PASS) results.add_result(check, result) return results
def test_docker_daemon(): logger.debug("Checking auditing on the Docker daemon.") note = "Test is invalid for newer kernels." kernel = tuple(int(x) for x in os.uname()[2].split('.')[:2]) if kernel >= (3, 12): return TestResult(Result.SKIP, note) try: subprocess.check_output(['which', 'auditctl']) except subprocess.CalledProcessError: note = "The auditctl command is not installed." return TestResult(Result.SKIP, note) audit = subprocess.check_output(['auditctl', '-l']) if b'/usr/bin/docker' in audit: return TestResult(Result.PASS) else: note = "/usr/bin/docker is not being tracked in auditctl." return TestResult(Result.FAIL, note)
def test_shellshock(config): logger.debug("Testing shell for 'shellshock/bashbug' vulnerability.") try: cmd = config['exploit_command'] except KeyError: logger.error("Can't find exploit command for shellshock test") else: p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True) stdout, stderr = p.communicate() if b'vulnerable' in stdout: reason = "System is vulnerable to Shellshock/Bashbug." logger.info(reason) result = Result.FAIL else: reason = "System is not vulnerable to Shellshock/Bashbug." logger.info(reason) result = Result.PASS return TestResult(result, reason)
def test_shellshock(config): logger.debug("Testing shell for 'shellshock/bashbug' vulnerability.") try: cmd = config['exploit_command'] except KeyError: logger.error("Can't find exploit command for shellshock test") else: p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True) stdout, stderr = p.communicate() if b'vulnerable' in stdout: reason = "System is vulnerable to Shellshock/Bashbug." logger.info(reason) result = Result.FAIL else: reason = "System is not vulnerable to Shellshock/Bashbug." logger.info(reason) result = Result.PASS return TestResult(result, reason)
def test_certs(): logger.debug("Testing bundled certificate validity & expiration.") certList = [] certStore = '/etc/ssl/certs' result = None notes = "" # use utils to get list of certificates certList = utils.get_files_list_from_dir(certStore) if certList is None: notes = "/etc/ssl/certs is empty, please check on-system certificates." logger.debug(notes) result = Result.SKIP return TestResult(result, notes) for cert in certList: try: p = Popen(['openssl', 'verify', cert], stdout=PIPE, shell=False) stdout = p.communicate() if b"OK" in stdout[0]: logger.debug("Certificate verification success for: %s", cert) if result is None: result = Result.PASS else: result = Result.FAIL if notes is "": notes += "Error validating certificate: " + cert else: notes += ", " + cert logger.debug("Certificate verification failure for: %s", cert) except ValueError: logger.exception("Error running 'openssl verify %s'", cert) result = Result.SKIP logger.debug("Completed on-system certificate validation tests.") return TestResult(result, notes)
def test_certs(): logger.debug("Testing bundled certificate validity & expiration.") certList = [] certStore = '/etc/ssl/certs' result = None notes = "" # use utils to get list of certificates certList = utils.get_files_list_from_dir(certStore) if certList is None: notes = "/etc/ssl/certs is empty, please check on-system certificates." logger.debug(notes) result = Result.SKIP return TestResult(result, notes) for cert in certList: try: p = Popen(['openssl', 'verify', cert], stdout=PIPE, shell=False) stdout = p.communicate() if b"OK" in stdout[0]: logger.debug("Certificate verification success for: %s", cert) if result is None: result = Result.PASS else: result = Result.FAIL if notes is "": notes += "Error validating certificate: " + cert else: notes += ", " + cert logger.debug("Certificate verification failure for: %s", cert) except ValueError: logger.exception("Error running 'openssl verify %s'", cert) result = Result.SKIP logger.debug("Completed on-system certificate validation tests.") return TestResult(result, notes)
def test_docker_daemon(): logger.debug("Checking auditing on the Docker daemon.") note = "Test is invalid for newer kernels." kernel = os.uname()[2].split('.') major_version = kernel[0] minor_version = int(kernel[1]) if "3" in major_version: if minor_version >= 12: return TestResult(Result.SKIP, note) try: subprocess.check_output(['which', 'auditctl']) except OSError: note = "The auditctl command is not installed." return TestResult(Result.SKIP, note) audit = subprocess.check_output(['auditctl', '-l']) if '/usr/bin/docker' in audit: return TestResult(Result.PASS) else: note = "/usr/bin/docker is not being tracked in auditctl." return TestResult(Result.FAIL, note)
def test_log_level(): logger.debug("Checking the Docker log level.") # if --log-level is set in ps, else find config file and check there dockers = _get_main_docker_processes() if not dockers: return TestResult(Result.SKIP, "Docker is not running.") results = GroupTestResult() for pid, cmdline in dockers: if '--log-level=info' in cmdline: results.add_result("pid %s" % pid, TestResult(Result.PASS)) elif '--log-level=debug' in cmdline: reason = ("It is not recommended to run Docker in production " "in debug mode.") results.add_result("pid %s" % pid, TestResult(Result.FAIL, reason)) else: notes = "Recommended Docker log level is 'info'." results.add_result("pid %s" % pid, TestResult(Result.PASS, notes)) return results
def test_user_owned(): logger.debug("Testing if the container is running in user namespace.") reason = "No Docker containers found." containers = _get_docker_ps() if not containers: return TestResult(Result.SKIP, reason) for line in containers: container_id = line.split(' ') for instance in container_id[0]: results = subprocess.check_output(['docker', 'inspect', '--format', '{{.ID}}:{{.Config.User}}', instance]) container_id = results.split(':') if container_id[1] is None: reason = ("Container " + str(container_id[0]) + " is running in " "root namespace.") return TestResult(Result.FAIL, reason) else: return TestResult(Result.PASS)
def test_apparmor(): """Uses return from apparmor_status to check installation and level at which AppArmor is monitoring. :returns: A TestResult object containing the result and notes explaining why it did not pass. """ # initial configurations return_result = None logger.debug("Attempting to validate AppArmor is installed.") # check try: cmd = 'apparmor_status' p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True) stdout, stderr = p.communicate() if b'apparmor_status: command not found' in stderr: reason = "AppArmor is not installed." logger.debug(reason) return_result = TestResult(Result.FAIL, notes=reason) # enforcing check, no /'s = no directories elif b"//" not in stdout: reason = "AppArmor has no modules loaded." logger.info(reason) return_result = TestResult(Result.FAIL, notes=reason) elif b"//" in stdout: reason = "AppArmor is installed and policy is loaded." logger.info(reason) return_result = TestResult(Result.PASS) else: # wth? logger.debug("Unexpected error while looking for AppArmor: " " Standard Output from sestatus command: [%s]" " Standard Error from sestatus command: [%s]", stdout, stderr) return_result = TestResult(Result.SKIP, notes="Unexpected error.") except EnvironmentError as e: logger.debug("Unexpected error running apparmor_status: [%s]", e) return_result = TestResult(Result.SKIP, notes="Unexpected error.") return return_result
def _get_login_defs_config(): config = {} try: with open('/etc/login.defs', 'r') as f: for line in f: line = line.strip() if not line: continue if line.startswith('#'): continue try: key, val = line.split() config[key] = val except ValueError: logger.debug("could not parse '%s'", line) continue except EnvironmentError: logger.warning("cannot read the login.defs config") return None return config
def test_selinux(): """Uses return from sestatus command to ensure SELinux is installed :returns: A TestResult object containing the result and, on failure, notes explaining why it did not pass. """ # logger return_result = None logger.debug("Attempting to validate SELinux is installed.") # check try: # if sestatus_return is stdout: cmd = 'sestatus' p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True) stdout, stderr = p.communicate() if b'sestatus: not found' in stderr: reason = "SELinux is not installed." logger.info(reason) return_result = TestResult(Result.FAIL, notes=reason) elif b'disabled' in stdout: reason = "SELinux is disabled." logger.info(reason) return_result = TestResult(Result.FAIL, notes=reason) elif b'permissive' in stdout: reason = "SELinux is permissive (disabled but logging)." logger.info(reason) return_result = TestResult(Result.FAIL, notes=reason) elif b'enforcing' in stdout: reason = "SELinux is installed and enforcing." logger.info(reason) return_result = TestResult(Result.PASS) else: # wth? logger.debug("Unexpected error while looking for SELinux: " " Standard Output from sestatus command: [%s]" " Standard Error from sestatus command: [%s]", stdout, stderr) return_result = TestResult(Result.SKIP, notes="Unexpected error.") except EnvironmentError as e: # log no selinux logger.debug("Unexpected error running sestatus: [{}]".format(e)) return_result = TestResult(Result.SKIP, notes="Unexpected error.") return return_result