Blame SOURCES/podman_backend.patch

575137
From 1b4e88f7ce56db4f8eb8e1d0a4272c91b421aefb Mon Sep 17 00:00:00 2001
575137
From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= <jcerny@redhat.com>
575137
Date: Wed, 16 Jan 2019 14:43:19 +0100
575137
Subject: [PATCH 1/7] Update SSG test suite container backend Dockerfiles
575137
575137
- It's more appopriate to describe CentOS container as CentOS-based
575137
  than RHEL based.
575137
- The utility that generates SSH keys is ssh-keygen, not sshd-keygen
575137
  which is a legacy service.
575137
- We remove pam_loginuid.so from SSHD PAM configuration in the container
575137
  as it prevents users to log in via ssh to the container. As our test
575137
  suite doesn't check if oscap scans produce audit messages, we can
575137
  simply remove that.
575137
---
575137
 Dockerfiles/test_suite-centos | 5 +++--
575137
 Dockerfiles/test_suite-fedora | 1 +
575137
 Dockerfiles/test_suite-rhel   | 3 ++-
575137
 3 files changed, 6 insertions(+), 3 deletions(-)
575137
575137
diff --git a/Dockerfiles/test_suite-centos b/Dockerfiles/test_suite-centos
575137
index d653345486..0160917bb9 100644
575137
--- a/Dockerfiles/test_suite-centos
575137
+++ b/Dockerfiles/test_suite-centos
575137
@@ -1,4 +1,4 @@
575137
-# This Dockerfile is a minimal example for a RHEL-based SSG test suite target container.
575137
+# This Dockerfile is a minimal example for a CentOS-based SSG test suite target container.
575137
 FROM centos
575137
 
575137
 ENV AUTH_KEYS=/root/.ssh/authorized_keys
575137
@@ -12,9 +12,10 @@ RUN true \
575137
         && true
575137
 
575137
 RUN true \
575137
-        && for key_type in rsa ecdsa ed25519; do sshd-keygen -N '' -t $key_type -f /etc/ssh/ssh_host_${key_type}_key; done \
575137
+        && for key_type in rsa ecdsa ed25519; do ssh-keygen -N '' -t $key_type -f /etc/ssh/ssh_host_${key_type}_key; done \
575137
         && mkdir -p /root/.ssh \
575137
         && printf "%s\n" "$CLIENT_PUBLIC_KEY" >> "$AUTH_KEYS" \
575137
         && chmod og-rw /root/.ssh "$AUTH_KEYS" \
575137
+        && sed -i '/session\s\+required\s\+pam_loginuid.so/d' /etc/pam.d/sshd \
575137
 && true
575137
 
575137
diff --git a/Dockerfiles/test_suite-fedora b/Dockerfiles/test_suite-fedora
575137
index 250da22e64..3bc4e8f6a7 100644
575137
--- a/Dockerfiles/test_suite-fedora
575137
+++ b/Dockerfiles/test_suite-fedora
575137
@@ -16,5 +16,6 @@ RUN true \
575137
         && mkdir -p /root/.ssh \
575137
         && printf "%s\n" "$CLIENT_PUBLIC_KEY" >> "$AUTH_KEYS" \
575137
         && chmod og-rw /root/.ssh "$AUTH_KEYS" \
575137
+        && sed -i '/session\s\+required\s\+pam_loginuid.so/d' /etc/pam.d/sshd \
575137
 && true
575137
 
575137
diff --git a/Dockerfiles/test_suite-rhel b/Dockerfiles/test_suite-rhel
575137
index ac264da0db..96c3af24b7 100644
575137
--- a/Dockerfiles/test_suite-rhel
575137
+++ b/Dockerfiles/test_suite-rhel
575137
@@ -12,8 +12,9 @@ RUN true \
575137
         && true
575137
 
575137
 RUN true \
575137
-        && for key_type in rsa ecdsa ed25519; do sshd-keygen -N '' -t $key_type -f /etc/ssh/ssh_host_${key_type}_key; done \
575137
+        && for key_type in rsa ecdsa ed25519; do ssh-keygen -N '' -t $key_type -f /etc/ssh/ssh_host_${key_type}_key; done \
575137
         && mkdir -p /root/.ssh \
575137
         && printf "%s\n" "$CLIENT_PUBLIC_KEY" >> "$AUTH_KEYS" \
575137
         && chmod og-rw /root/.ssh "$AUTH_KEYS" \
575137
+        && sed -i '/session\s\+required\s\+pam_loginuid.so/d' /etc/pam.d/sshd \
575137
 && true
575137
575137
From c9de05e77285597ceb80216ec5e54a5a354d891e Mon Sep 17 00:00:00 2001
575137
From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= <jcerny@redhat.com>
575137
Date: Wed, 16 Jan 2019 14:49:10 +0100
575137
Subject: [PATCH 2/7] Introduce Podman backend to SSG Test Suite harness
575137
575137
On RHEL8, Docker isn't available and it's replaced by Podman. Therefore,
575137
we need to implement a backend for Podman to run container-based tests.
575137
The Podman backend works similar way as the Docker backend. However,
575137
it's implemented by invoking podman CLI calls (subprocess) instead of
575137
using Python 3 bindings, because as of python3-podman version 0.12.1.2
575137
it doesn't allow to run, inspect and commit containers. Users will use
575137
this backend using `--container` cmdline option.
575137
---
575137
 tests/ssg_test_suite/test_env.py | 137 ++++++++++++++++++++++++++++++-
575137
 tests/test_suite.py              |   9 ++
575137
 2 files changed, 145 insertions(+), 1 deletion(-)
575137
575137
diff --git a/tests/ssg_test_suite/test_env.py b/tests/ssg_test_suite/test_env.py
575137
index 3b0d549d42..4c1503542c 100644
575137
--- a/tests/ssg_test_suite/test_env.py
575137
+++ b/tests/ssg_test_suite/test_env.py
575137
@@ -4,6 +4,7 @@
575137
 import sys
575137
 import os
575137
 import time
575137
+import subprocess
575137
 
575137
 import docker
575137
 
575137
@@ -172,7 +173,7 @@ def offline_scan(self, args, verbose_path):
575137
 
575137
 
575137
 class DockerTestEnv(TestEnv):
575137
-    name = "container-based"
575137
+    name = "docker-based"
575137
 
575137
     def __init__(self, mode, image_name):
575137
         super(DockerTestEnv, self).__init__(mode)
575137
@@ -249,6 +250,7 @@ def reset_state_to(self, state_name, new_running_state_name):
575137
         return new_container
575137
 
575137
     def _find_image_by_name(self, image_name):
575137
+        # only in DockerTestEnv
575137
         return self.client.images.get(image_name)
575137
 
575137
     def _new_container_from_image(self, image_name, container_name):
575137
@@ -285,3 +287,136 @@ def offline_scan(self, args, verbose_path):
575137
         command_list = self._local_oscap_check_base_arguments() + args
575137
 
575137
         return common.run_cmd_local(command_list, verbose_path)
575137
+
575137
+
575137
+class PodmanTestEnv(TestEnv):
575137
+    # TODO: Rework this class using Podman Python bindings (python3-podman)
575137
+    # at the moment when their API will provide methods to run containers,
575137
+    # commit images and inspect containers
575137
+    name = "podman-based"
575137
+
575137
+    def __init__(self, scanning_mode, image_name):
575137
+        super(PodmanTestEnv, self).__init__(scanning_mode)
575137
+        self._name_stem = "ssg_test"
575137
+        self.base_image = image_name
575137
+        self.created_images = []
575137
+        self.containers = []
575137
+        self.domain_ip = None
575137
+
575137
+    def start(self):
575137
+        self.run_container(self.base_image)
575137
+
575137
+    def finalize(self):
575137
+        self._terminate_current_running_container_if_applicable()
575137
+
575137
+    def image_stem2fqn(self, stem):
575137
+        image_name = "{0}_{1}".format(self.base_image, stem)
575137
+        return image_name
575137
+
575137
+    @property
575137
+    def current_container(self):
575137
+        if self.containers:
575137
+            return self.containers[-1]
575137
+        return None
575137
+
575137
+    @property
575137
+    def current_image(self):
575137
+        if self.created_images:
575137
+            return self.created_images[-1]
575137
+        return self.base_image
575137
+
575137
+    def _create_new_image(self, from_container, name):
575137
+        new_image_name = self.image_stem2fqn(name)
575137
+        if not from_container:
575137
+            from_container = self.run_container(self.current_image)
575137
+        podman_cmd = ["podman", "commit", from_container, new_image_name]
575137
+        try:
575137
+            subprocess.check_output(podman_cmd, stderr=subprocess.STDOUT)
575137
+        except subprocess.CalledProcessError as e:
575137
+            msg = "Command '{0}' returned {1}:\n{2}".format(" ".join(e.cmd), e.returncode, e.output.decode("utf-8"))
575137
+            raise RuntimeError(msg)
575137
+        self.created_images.append(new_image_name)
575137
+        return new_image_name
575137
+
575137
+    def _save_state(self, state_name):
575137
+        state = self._create_new_image(self.current_container, state_name)
575137
+        return state
575137
+
575137
+    def run_container(self, image_name, container_name="running"):
575137
+        new_container = self._new_container_from_image(image_name, container_name)
575137
+        self.containers.append(new_container)
575137
+        # Get the container time to fully start its service
575137
+        time.sleep(0.2)
575137
+        self.domain_ip = self._get_container_ip(new_container)
575137
+        return new_container
575137
+
575137
+    def reset_state_to(self, state_name, new_running_state_name):
575137
+        self._terminate_current_running_container_if_applicable()
575137
+        image_name = self.image_stem2fqn(state_name)
575137
+
575137
+        new_container = self.run_container(image_name, new_running_state_name)
575137
+
575137
+        return new_container
575137
+
575137
+    def _new_container_from_image(self, image_name, container_name):
575137
+        long_name = "{0}_{1}".format(self._name_stem, container_name)
575137
+        podman_cmd = ["podman", "run", "--name", long_name,
575137
+                      "--publish", "22", "--detach", image_name,
575137
+                      "/usr/sbin/sshd", "-D"]
575137
+        try:
575137
+            podman_output = subprocess.check_output(podman_cmd, stderr=subprocess.STDOUT)
575137
+        except subprocess.CalledProcessError as e:
575137
+            msg = "Command '{0}' returned {1}:\n{2}".format(" ".join(e.cmd), e.returncode, e.output.decode("utf-8"))
575137
+            raise RuntimeError(msg)
575137
+        container_id = podman_output.decode("utf-8").strip()
575137
+        return container_id
575137
+
575137
+    def _get_container_ip(self, container):
575137
+        # only in PodmanTestEnv
575137
+        podman_cmd = ["podman", "inspect", container, "--format", "{{.NetworkSettings.IPAddress}}"]
575137
+        try:
575137
+            podman_output = subprocess.check_output(podman_cmd, stderr=subprocess.STDOUT)
575137
+        except subprocess.CalledProcessError as e:
575137
+            msg = "Command '{0}' returned {1}:\n{2}".format(" ".join(e.cmd), e.returncode, e.output.decode("utf-8"))
575137
+            raise RuntimeError(msg)
575137
+        ip_address = podman_output.decode("utf-8")
575137
+        return ip_address
575137
+
575137
+    def _terminate_current_running_container_if_applicable(self):
575137
+        if self.containers:
575137
+            running_state = self.containers.pop()
575137
+            podman_cmd = ["podman", "stop", running_state]
575137
+            try:
575137
+                subprocess.check_output(podman_cmd, stderr=subprocess.STDOUT)
575137
+            except subprocess.CalledProcessError as e:
575137
+                msg = "Command '{0}' returned {1}:\n{2}".format(" ".join(e.cmd), e.returncode, e.output.decode("utf-8"))
575137
+                raise RuntimeError(msg)
575137
+            podman_cmd = ["podman", "rm", running_state]
575137
+            try:
575137
+                subprocess.check_output(podman_cmd, stderr=subprocess.STDOUT)
575137
+            except subprocess.CalledProcessError as e:
575137
+                msg = "Command '{0}' returned {1}:\n{2}".format(" ".join(e.cmd), e.returncode, e.output.decode("utf-8"))
575137
+                raise RuntimeError(msg)
575137
+
575137
+    def _delete_saved_state(self, image):
575137
+        self._terminate_current_running_container_if_applicable()
575137
+
575137
+        assert self.created_images
575137
+
575137
+        associated_image = self.created_images.pop()
575137
+        assert associated_image == image
575137
+        podman_cmd = ["podman", "rmi", associated_image]
575137
+        try:
575137
+            subprocess.check_output(podman_cmd, stderr=subprocess.STDOUT)
575137
+        except subprocess.CalledProcessError as e:
575137
+            msg = "Command '{0}' returned {1}:\n{2}".format(" ".join(e.cmd), e.returncode, e.output.decode("utf-8"))
575137
+            raise RuntimeError(msg)
575137
+
575137
+    def discard_running_state(self, state_handle):
575137
+        self._terminate_current_running_container_if_applicable()
575137
+
575137
+    def _local_oscap_check_base_arguments(self):
575137
+        raise NotImplementedError
575137
+
575137
+    def offline_scan(self, args, verbose_path):
575137
+        raise NotImplementedError("OpenSCAP doesn't support offline scanning of Podman Containers")
575137
diff --git a/tests/test_suite.py b/tests/test_suite.py
575137
index 667fdd7296..275a147bdf 100755
575137
--- a/tests/test_suite.py
575137
+++ b/tests/test_suite.py
575137
@@ -30,6 +30,9 @@ def parse_args():
575137
     backends.add_argument(
575137
         "--docker", dest="docker", metavar="BASE_IMAGE",
575137
         help="Use Docker test environment with this base image.")
575137
+    backends.add_argument(
575137
+        "--container", dest="container", metavar="BASE_IMAGE",
575137
+        help="Use container test environment with this base image.")
575137
 
575137
     backends.add_argument(
575137
         "--libvirt", dest="libvirt", metavar="HYPERVISOR DOMAIN", nargs=2,
575137
@@ -163,6 +166,12 @@ def normalize_passed_arguments(options):
575137
         logging.info(
575137
             "The base image option has been specified, "
575137
             "choosing Docker-based test environment.")
575137
+    elif options.container:
575137
+        options.test_env = ssg_test_suite.test_env.PodmanTestEnv(
575137
+            options.scanning_mode, options.container)
575137
+        logging.info(
575137
+            "The base image option has been specified, "
575137
+            "choosing Podman-based test environment.")
575137
     else:
575137
         hypervisor, domain_name = options.libvirt
575137
         options.test_env = ssg_test_suite.test_env.VMTestEnv(
575137
575137
From 05bf3c7cfcc25a2d7b4970712df8b2c67336ba23 Mon Sep 17 00:00:00 2001
575137
From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= <jcerny@redhat.com>
575137
Date: Wed, 16 Jan 2019 14:56:45 +0100
575137
Subject: [PATCH 3/7] Update tests/README.md with the new Podman backend
575137
575137
---
575137
 tests/README.md | 70 ++++++++++++++++++++++++++++++++++++-------------
575137
 1 file changed, 52 insertions(+), 18 deletions(-)
575137
575137
diff --git a/tests/README.md b/tests/README.md
575137
index 02ddc2921e..517f754c8c 100644
575137
--- a/tests/README.md
575137
+++ b/tests/README.md
575137
@@ -10,7 +10,7 @@ remediation works.
575137
 
575137
 ## Prerequisites
575137
 
575137
-You can use the more powerful VM-based tests, or more lightweight Docker-based tests.
575137
+You can use the more powerful VM-based tests, or more lightweight container-based tests.
575137
 
575137
 For the Test Suite to work, you need to have libvirt domains prepared for
575137
 testing.
575137
@@ -69,9 +69,10 @@ VM-based tests:
575137
   - `hypervisor`: Typically, you will use the `qemu:///system` value.
575137
   - `domain`: `libvirt` domain, which is basically name of the virtual machine.
575137
 
575137
-docker-based tests:
575137
+Container-based tests:
575137
 
575137
-- `--docker`: Accepts the base image name.
575137
+- `--docker`: Uses Docker as container engine. Accepts the base image name.
575137
+- `--container`: Uses Podman as container engine. Accepts the base image name.
575137
 
575137
 ### Profile-based testing
575137
 
575137
@@ -187,41 +188,74 @@ Now, you can perform validation check with command
575137
 ./test_suite.py rule --libvirt qemu:///system ssg-test-suite-centos --datastream ../build/ssg-centos7-ds.xml --xccdf-id scap_org.open-scap_cref_ssg-rhel7-xccdf-1.2.xml rule_sshd_disable_kerb_auth
575137
 ```
575137
 
575137
-## Docker backend
575137
+## Container backends
575137
 
575137
-You can also use the docker-based for running tests, just use the script with the `--docker` argument.
575137
-You need to provide `--docker <base image name>` option on the command-line.
575137
+You can also run the tests in a container. There are 2 container backends, Podman and Docker, supported.
575137
+
575137
+To use container backends, use the following options on the command line:
575137
+
575137
+- Podman - `--container <base image name>`
575137
+- Docker - `--docker <base image name>`
575137
 
575137
 To obtain the base image, you can use `test_suite-*` Dockerfiles in the `Dockerfiles` directory to build it.
575137
 We recommend to use RHEL-based containers, as the test suite is optimized for testing the RHEL content.
575137
 
575137
-Build the image using this command:
575137
+To use Podman backend, you need to have:
575137
 
575137
-```
575137
-public_key="ssh-rsa AAAAB3NzaC1y...rJSs4BL me@localhost"
575137
-docker build --build-arg CLIENT_PUBLIC_KEY="$public_key" -t ssg_test_suite -f test_suite-rhel .
575137
-```
575137
-
575137
-Run the test suite using this command:
575137
+- `podman` package installed, and
575137
+- rights that allow you to start/stop containers and to create images.
575137
 
575137
-```
575137
-./test_suite.py rule --docker ssg_test_suite --datastream ../build/ssg-centos7-ds.xml --xccdf-id scap_org.open-scap_cref_ssg-rhel7-xccdf-1.2.xml rule_sshd_disable_kerb_auth
575137
-```
575137
+To use Docker backend, you need to have:
575137
 
575137
-On your side, you need to have
575137
 - the [docker](https://pypi.org/project/docker/) Python module installed. You may have to use `pip` to install it on older distributions s.a. RHEL 7, running `pip install --user docker` as `root` will do the trick of installing it only for the `root` user.
575137
 - the Docker service running, and
575137
 - rights that allow you to start/stop containers and to create images.
575137
   This level of rights is considered to be insecure, so it is recommended to run the test suite in a VM.
575137
   You can accomplish this by creating a `docker` group, then add yourself in it and restart `docker`.
575137
 
575137
-The Docker image you want to use with the tests needs to be prepared, so it can scan itself, and that it can accept connections and data.
575137
+
575137
+### Building the base image
575137
+
575137
+The container image you want to use with the tests needs to be prepared, so it can scan itself, and that it can accept connections and data.
575137
 Following services need to be supported:
575137
 
575137
 - `sshd` (`openssh-server` needs to be installed, server host keys have to be in place, root's `.ssh/authorized_keys` are set up with correct permissions)
575137
 - `scp` (`openssh-clients` need to be installed - `scp` requires more than a ssh server on the server-side)
575137
 - `oscap` (`openscap-scanner` - the container has to be able to scan itself)
575137
 - You may want to include another packages, as base images tend to be bare-bone and tests may require more packages to be present.
575137
+
575137
+Using Podman:
575137
+
575137
+NOTE: With Podman, you have to run all the operations as root. Podman supports rootless containers, but the test suite internally uses a container exposing a TCP port. As of Podman version 0.12.1.2, port bindings are not yet supported by rootless containers.
575137
+
575137
+```
575137
+# public_key="ssh-rsa AAAAB3NzaC1y...rJSs4BL root@localhost"
575137
+# podman build --build-arg CLIENT_PUBLIC_KEY="$public_key" -t ssg_test_suite -f test_suite-rhel .
575137
+```
575137
+
575137
+Using Docker:
575137
+
575137
+```
575137
+public_key="ssh-rsa AAAAB3NzaC1y...rJSs4BL me@localhost"
575137
+docker build --build-arg CLIENT_PUBLIC_KEY="$public_key" -t ssg_test_suite -f test_suite-rhel .
575137
+```
575137
+
575137
+### Running the tests
575137
+
575137
+This is an example to run test scenarios for rule `rule_sshd_disable_kerb_auth`:
575137
+
575137
+Using Podman:
575137
+
575137
+```
575137
+./test_suite.py rule --container ssg_test_suite --datastream ../build/ssg-centos7-ds.xml --xccdf-id scap_org.open-scap_cref_ssg-rhel7-xccdf-1.2.xml rule_sshd_disable_kerb_auth
575137
+```
575137
+
575137
+Using Docker:
575137
+
575137
+```
575137
+./test_suite.py rule --docker ssg_test_suite --datastream ../build/ssg-centos7-ds.xml --xccdf-id scap_org.open-scap_cref_ssg-rhel7-xccdf-1.2.xml rule_sshd_disable_kerb_auth
575137
+```
575137
+
575137
 Also, as containers may get any IP address, a conflict may appear in your local client's `known_hosts` file.
575137
 You might have a version of `oscap-ssh` that doesn't support ssh connection customization at the client-side, so it may be a good idea to disable known hosts checks for all hosts if you are testing on a VM or under a separate user.
575137
 You can do that by putting following lines in your `$HOME/.ssh/config` file:
575137
575137
From e27f410f20dd1fb7fb719f5d4874774ef43316ff Mon Sep 17 00:00:00 2001
575137
From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= <jcerny@redhat.com>
575137
Date: Wed, 16 Jan 2019 15:47:44 +0100
575137
Subject: [PATCH 4/7] Refactor: Extract common code to a parent class
575137
575137
Classes DockerTestEnv and PodmanTestEnv contain a lot of duplicate code.
575137
They're very similar because both of them are a specific implementation
575137
of a generic container backend.  We introduce a new parent class
575137
ContainerTestEnv that contains common code. Then, DockerTestEnv and
575137
PodmanTestEnv will inherit from ContainerTestEnv.
575137
---
575137
 tests/ssg_test_suite/test_env.py | 188 ++++++++++++-------------------
575137
 1 file changed, 74 insertions(+), 114 deletions(-)
575137
575137
diff --git a/tests/ssg_test_suite/test_env.py b/tests/ssg_test_suite/test_env.py
575137
index 4c1503542c..1b11f4888b 100644
575137
--- a/tests/ssg_test_suite/test_env.py
575137
+++ b/tests/ssg_test_suite/test_env.py
575137
@@ -172,28 +172,14 @@ def offline_scan(self, args, verbose_path):
575137
         return common.run_cmd_local(command_list, verbose_path)
575137
 
575137
 
575137
-class DockerTestEnv(TestEnv):
575137
-    name = "docker-based"
575137
-
575137
-    def __init__(self, mode, image_name):
575137
-        super(DockerTestEnv, self).__init__(mode)
575137
-
575137
+class ContainerTestEnv(TestEnv):
575137
+    def __init__(self, scanning_mode, image_name):
575137
+        super(ContainerTestEnv, self).__init__(scanning_mode)
575137
         self._name_stem = "ssg_test"
575137
-
575137
-        try:
575137
-            self.client = docker.from_env(version="auto")
575137
-            self.client.ping()
575137
-        except Exception as exc:
575137
-            msg = (
575137
-                "Unable to start the Docker test environment, "
575137
-                "is the Docker service started "
575137
-                "and do you have rights to access it?"
575137
-                .format(str(exc)))
575137
-            raise RuntimeError(msg)
575137
-
575137
         self.base_image = image_name
575137
         self.created_images = []
575137
         self.containers = []
575137
+        self.domain_ip = None
575137
 
575137
     def start(self):
575137
         self.run_container(self.base_image)
575137
@@ -221,7 +207,7 @@ def _create_new_image(self, from_container, name):
575137
         new_image_name = self.image_stem2fqn(name)
575137
         if not from_container:
575137
             from_container = self.run_container(self.current_image)
575137
-        from_container.commit(repository=new_image_name)
575137
+        self._commit(from_container, new_image_name)
575137
         self.created_images.append(new_image_name)
575137
         return new_image_name
575137
 
575137
@@ -232,13 +218,10 @@ def _save_state(self, state_name):
575137
     def run_container(self, image_name, container_name="running"):
575137
         new_container = self._new_container_from_image(image_name, container_name)
575137
         self.containers.append(new_container)
575137
-
575137
         # Get the container time to fully start its service
575137
         time.sleep(0.2)
575137
 
575137
-        new_container.reload()
575137
-        self.domain_ip = new_container.attrs["NetworkSettings"]["Networks"]["bridge"]["IPAddress"]
575137
-
575137
+        self.domain_ip = self._get_container_ip(new_container)
575137
         return new_container
575137
 
575137
     def reset_state_to(self, state_name, new_running_state_name):
575137
@@ -249,114 +232,104 @@ def reset_state_to(self, state_name, new_running_state_name):
575137
 
575137
         return new_container
575137
 
575137
-    def _find_image_by_name(self, image_name):
575137
-        # only in DockerTestEnv
575137
-        return self.client.images.get(image_name)
575137
+    def _delete_saved_state(self, image):
575137
+        self._terminate_current_running_container_if_applicable()
575137
+
575137
+        assert self.created_images
575137
+
575137
+        associated_image = self.created_images.pop()
575137
+        assert associated_image == image
575137
+        self._remove_image(associated_image)
575137
+
575137
+    def discard_running_state(self, state_handle):
575137
+        self._terminate_current_running_container_if_applicable()
575137
+
575137
+    def offline_scan(self, args, verbose_path):
575137
+        command_list = self._local_oscap_check_base_arguments() + args
575137
+
575137
+        return common.run_cmd_local(command_list, verbose_path)
575137
+
575137
+    def _commit(self, container, image):
575137
+        pass
575137
 
575137
     def _new_container_from_image(self, image_name, container_name):
575137
-        img = self._find_image_by_name(image_name)
575137
+        pass
575137
+
575137
+    def _get_container_ip(self, container):
575137
+        pass
575137
+
575137
+    def _terminate_current_running_container_if_applicable(self):
575137
+        pass
575137
+
575137
+    def _remove_image(self, image):
575137
+        pass
575137
+
575137
+    def _local_oscap_check_base_arguments(self):
575137
+        pass
575137
+
575137
+
575137
+class DockerTestEnv(ContainerTestEnv):
575137
+    name = "docker-based"
575137
+
575137
+    def __init__(self, mode, image_name):
575137
+        super(DockerTestEnv, self).__init__(mode, image_name)
575137
+        try:
575137
+            self.client = docker.from_env(version="auto")
575137
+            self.client.ping()
575137
+        except Exception as exc:
575137
+            msg = (
575137
+                "Unable to start the Docker test environment, "
575137
+                "is the Docker service started "
575137
+                "and do you have rights to access it?"
575137
+                .format(str(exc)))
575137
+            raise RuntimeError(msg)
575137
+
575137
+    def _commit(self, container, image):
575137
+        container.commit(repository=image)
575137
+
575137
+    def _new_container_from_image(self, image_name, container_name):
575137
+        img = self.client.images.get(image_name)
575137
         result = self.client.containers.run(
575137
             img, "/usr/sbin/sshd -D",
575137
             name="{0}_{1}".format(self._name_stem, container_name), ports={"22": None},
575137
             detach=True)
575137
         return result
575137
 
575137
+    def _get_container_ip(self, container):
575137
+        container.reload()
575137
+        container_ip = container.attrs["NetworkSettings"]["Networks"]["bridge"]["IPAddress"]
575137
+        return container_ip
575137
+
575137
     def _terminate_current_running_container_if_applicable(self):
575137
         if self.containers:
575137
             running_state = self.containers.pop()
575137
             running_state.stop()
575137
             running_state.remove()
575137
 
575137
-    def _delete_saved_state(self, image):
575137
-        self._terminate_current_running_container_if_applicable()
575137
-
575137
-        assert self.created_images
575137
-
575137
-        associated_image = self.created_images.pop()
575137
-        assert associated_image == image
575137
-        self.client.images.remove(associated_image)
575137
-
575137
-    def discard_running_state(self, state_handle):
575137
-        self._terminate_current_running_container_if_applicable()
575137
+    def _remove_image(self, image):
575137
+        self.client.images.remove(image)
575137
 
575137
     def _local_oscap_check_base_arguments(self):
575137
         return ['oscap-docker', "container", self.current_container.id,
575137
                                                             'xccdf', 'eval']
575137
 
575137
-    def offline_scan(self, args, verbose_path):
575137
-        command_list = self._local_oscap_check_base_arguments() + args
575137
-
575137
-        return common.run_cmd_local(command_list, verbose_path)
575137
-
575137
 
575137
-class PodmanTestEnv(TestEnv):
575137
+class PodmanTestEnv(ContainerTestEnv):
575137
     # TODO: Rework this class using Podman Python bindings (python3-podman)
575137
     # at the moment when their API will provide methods to run containers,
575137
     # commit images and inspect containers
575137
     name = "podman-based"
575137
 
575137
     def __init__(self, scanning_mode, image_name):
575137
-        super(PodmanTestEnv, self).__init__(scanning_mode)
575137
-        self._name_stem = "ssg_test"
575137
-        self.base_image = image_name
575137
-        self.created_images = []
575137
-        self.containers = []
575137
-        self.domain_ip = None
575137
-
575137
-    def start(self):
575137
-        self.run_container(self.base_image)
575137
-
575137
-    def finalize(self):
575137
-        self._terminate_current_running_container_if_applicable()
575137
-
575137
-    def image_stem2fqn(self, stem):
575137
-        image_name = "{0}_{1}".format(self.base_image, stem)
575137
-        return image_name
575137
+        super(PodmanTestEnv, self).__init__(scanning_mode, image_name)
575137
 
575137
-    @property
575137
-    def current_container(self):
575137
-        if self.containers:
575137
-            return self.containers[-1]
575137
-        return None
575137
-
575137
-    @property
575137
-    def current_image(self):
575137
-        if self.created_images:
575137
-            return self.created_images[-1]
575137
-        return self.base_image
575137
-
575137
-    def _create_new_image(self, from_container, name):
575137
-        new_image_name = self.image_stem2fqn(name)
575137
-        if not from_container:
575137
-            from_container = self.run_container(self.current_image)
575137
-        podman_cmd = ["podman", "commit", from_container, new_image_name]
575137
+    def _commit(self, container, image):
575137
+        podman_cmd = ["podman", "commit", container, image]
575137
         try:
575137
             subprocess.check_output(podman_cmd, stderr=subprocess.STDOUT)
575137
         except subprocess.CalledProcessError as e:
575137
             msg = "Command '{0}' returned {1}:\n{2}".format(" ".join(e.cmd), e.returncode, e.output.decode("utf-8"))
575137
             raise RuntimeError(msg)
575137
-        self.created_images.append(new_image_name)
575137
-        return new_image_name
575137
-
575137
-    def _save_state(self, state_name):
575137
-        state = self._create_new_image(self.current_container, state_name)
575137
-        return state
575137
-
575137
-    def run_container(self, image_name, container_name="running"):
575137
-        new_container = self._new_container_from_image(image_name, container_name)
575137
-        self.containers.append(new_container)
575137
-        # Get the container time to fully start its service
575137
-        time.sleep(0.2)
575137
-        self.domain_ip = self._get_container_ip(new_container)
575137
-        return new_container
575137
-
575137
-    def reset_state_to(self, state_name, new_running_state_name):
575137
-        self._terminate_current_running_container_if_applicable()
575137
-        image_name = self.image_stem2fqn(state_name)
575137
-
575137
-        new_container = self.run_container(image_name, new_running_state_name)
575137
-
575137
-        return new_container
575137
 
575137
     def _new_container_from_image(self, image_name, container_name):
575137
         long_name = "{0}_{1}".format(self._name_stem, container_name)
575137
@@ -372,7 +345,6 @@ def _new_container_from_image(self, image_name, container_name):
575137
         return container_id
575137
 
575137
     def _get_container_ip(self, container):
575137
-        # only in PodmanTestEnv
575137
         podman_cmd = ["podman", "inspect", container, "--format", "{{.NetworkSettings.IPAddress}}"]
575137
         try:
575137
             podman_output = subprocess.check_output(podman_cmd, stderr=subprocess.STDOUT)
575137
@@ -398,25 +370,13 @@ def _terminate_current_running_container_if_applicable(self):
575137
                 msg = "Command '{0}' returned {1}:\n{2}".format(" ".join(e.cmd), e.returncode, e.output.decode("utf-8"))
575137
                 raise RuntimeError(msg)
575137
 
575137
-    def _delete_saved_state(self, image):
575137
-        self._terminate_current_running_container_if_applicable()
575137
-
575137
-        assert self.created_images
575137
-
575137
-        associated_image = self.created_images.pop()
575137
-        assert associated_image == image
575137
-        podman_cmd = ["podman", "rmi", associated_image]
575137
+    def _remove_image(self, image):
575137
+        podman_cmd = ["podman", "rmi", image]
575137
         try:
575137
             subprocess.check_output(podman_cmd, stderr=subprocess.STDOUT)
575137
         except subprocess.CalledProcessError as e:
575137
             msg = "Command '{0}' returned {1}:\n{2}".format(" ".join(e.cmd), e.returncode, e.output.decode("utf-8"))
575137
             raise RuntimeError(msg)
575137
 
575137
-    def discard_running_state(self, state_handle):
575137
-        self._terminate_current_running_container_if_applicable()
575137
-
575137
     def _local_oscap_check_base_arguments(self):
575137
-        raise NotImplementedError
575137
-
575137
-    def offline_scan(self, args, verbose_path):
575137
         raise NotImplementedError("OpenSCAP doesn't support offline scanning of Podman Containers")
575137
575137
From cbc93c8cf4320f81e00061492e5e2d76cc733c6f Mon Sep 17 00:00:00 2001
575137
From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= <jcerny@redhat.com>
575137
Date: Wed, 16 Jan 2019 16:28:43 +0100
575137
Subject: [PATCH 5/7] Import docker only if needed
575137
575137
If people use only other backends than Docker they don't need to
575137
install docker Python module.
575137
---
575137
 tests/ssg_test_suite/test_env.py | 6 ++++--
575137
 1 file changed, 4 insertions(+), 2 deletions(-)
575137
575137
diff --git a/tests/ssg_test_suite/test_env.py b/tests/ssg_test_suite/test_env.py
575137
index 1b11f4888b..1bbc955f7d 100644
575137
--- a/tests/ssg_test_suite/test_env.py
575137
+++ b/tests/ssg_test_suite/test_env.py
575137
@@ -6,8 +6,6 @@
575137
 import time
575137
 import subprocess
575137
 
575137
-import docker
575137
-
575137
 import ssg_test_suite
575137
 from ssg_test_suite.virt import SnapshotStack
575137
 from ssg_test_suite import common
575137
@@ -273,6 +271,10 @@ class DockerTestEnv(ContainerTestEnv):
575137
 
575137
     def __init__(self, mode, image_name):
575137
         super(DockerTestEnv, self).__init__(mode, image_name)
575137
+        try:
575137
+            import docker
575137
+        except ImportError:
575137
+            raise RuntimeError("Can't import Docker, Docker backend will not work.")
575137
         try:
575137
             self.client = docker.from_env(version="auto")
575137
             self.client.ping()
575137
575137
From 5e76fd5ec49eabc7ef378d63fd86caeb2afb3b48 Mon Sep 17 00:00:00 2001
575137
From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= <jcerny@redhat.com>
575137
Date: Thu, 17 Jan 2019 14:12:14 +0100
575137
Subject: [PATCH 6/7] Remove unused method discard_running_state
575137
575137
---
575137
 tests/ssg_test_suite/test_env.py | 6 ------
575137
 1 file changed, 6 deletions(-)
575137
575137
diff --git a/tests/ssg_test_suite/test_env.py b/tests/ssg_test_suite/test_env.py
575137
index 1bbc955f7d..2590102217 100644
575137
--- a/tests/ssg_test_suite/test_env.py
575137
+++ b/tests/ssg_test_suite/test_env.py
575137
@@ -151,9 +151,6 @@ def reset_state_to(self, state_name, new_running_state_name):
575137
         state = self.snapshot_stack.revert(delete=False)
575137
         return state
575137
 
575137
-    def discard_running_state(self, state_handle):
575137
-        pass
575137
-
575137
     def _save_state(self, state_name):
575137
         state = self.snapshot_stack.create(state_name)
575137
         return state
575137
@@ -239,9 +236,6 @@ def _delete_saved_state(self, image):
575137
         assert associated_image == image
575137
         self._remove_image(associated_image)
575137
 
575137
-    def discard_running_state(self, state_handle):
575137
-        self._terminate_current_running_container_if_applicable()
575137
-
575137
     def offline_scan(self, args, verbose_path):
575137
         command_list = self._local_oscap_check_base_arguments() + args
575137
 
575137
575137
From 50bfbaacb237e7f04a7f0d58a1468d4663889d25 Mon Sep 17 00:00:00 2001
575137
From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?= <jcerny@redhat.com>
575137
Date: Fri, 18 Jan 2019 15:07:11 +0100
575137
Subject: [PATCH 7/7] Raise NotImplementedError in ContainerTestEnv
575137
575137
---
575137
 tests/ssg_test_suite/test_env.py | 12 ++++++------
575137
 1 file changed, 6 insertions(+), 6 deletions(-)
575137
575137
diff --git a/tests/ssg_test_suite/test_env.py b/tests/ssg_test_suite/test_env.py
575137
index 2590102217..34da74b0a5 100644
575137
--- a/tests/ssg_test_suite/test_env.py
575137
+++ b/tests/ssg_test_suite/test_env.py
575137
@@ -242,22 +242,22 @@ def offline_scan(self, args, verbose_path):
575137
         return common.run_cmd_local(command_list, verbose_path)
575137
 
575137
     def _commit(self, container, image):
575137
-        pass
575137
+        raise NotImplementedError
575137
 
575137
     def _new_container_from_image(self, image_name, container_name):
575137
-        pass
575137
+        raise NotImplementedError
575137
 
575137
     def _get_container_ip(self, container):
575137
-        pass
575137
+        raise NotImplementedError
575137
 
575137
     def _terminate_current_running_container_if_applicable(self):
575137
-        pass
575137
+        raise NotImplementedError
575137
 
575137
     def _remove_image(self, image):
575137
-        pass
575137
+        raise NotImplementedError
575137
 
575137
     def _local_oscap_check_base_arguments(self):
575137
-        pass
575137
+        raise NotImplementedError
575137
 
575137
 
575137
 class DockerTestEnv(ContainerTestEnv):