All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.rub.nds.tls.subject.docker.DockerTlsInstance Maven / Gradle / Ivy

Go to download

Bill of Materials POM for the entire protocol attacker ecosystem used to keep dependencies in sync.

The newest version!
/*
 * TLS-Docker-Library - A collection of open source TLS clients and servers
 *
 * Copyright 2017-2022 Ruhr University Bochum, Paderborn University, and Hackmanit GmbH
 *
 * Licensed under Apache License, Version 2.0
 * http://www.apache.org/licenses/LICENSE-2.0.txt
 */
package de.rub.nds.tls.subject.docker;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.CreateContainerCmd;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.InspectContainerResponse.ContainerState;
import com.github.dockerjava.api.command.InspectVolumeResponse;
import com.github.dockerjava.api.exception.DockerException;
import com.github.dockerjava.api.model.*;
import de.rub.nds.tls.subject.ConnectionRole;
import de.rub.nds.tls.subject.docker.build.DockerBuilder;
import de.rub.nds.tls.subject.exceptions.TlsVersionNotFoundException;
import de.rub.nds.tls.subject.params.ParameterProfile;
import de.rub.nds.tls.subject.properties.ImageProperties;
import java.util.*;
import java.util.function.UnaryOperator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public abstract class DockerTlsInstance {
    protected static final DockerClient DOCKER = DockerClientManager.getDockerClient();
    private static final Logger LOGGER = LogManager.getLogger();

    private final String containerName;
    private String containerId;
    protected Image image;
    private Optional exitCode = Optional.empty();
    private boolean autoRemove;
    private int logReadOffset = 0;
    protected final ParameterProfile parameterProfile;
    protected final ImageProperties imageProperties;
    protected List childExecs = new LinkedList<>();
    private final UnaryOperator hostConfigHook;
    private final String[] cmd;

    private final List containerExposedPorts;

    public DockerTlsInstance(
            Image image,
            String containerName,
            ParameterProfile profile,
            ImageProperties imageProperties,
            String version,
            String additionalBuildFlags,
            ConnectionRole role,
            boolean autoRemove,
            UnaryOperator hostConfigHook,
            String[] cmd,
            List exposedPorts) {
        if (profile == null) {
            throw new NullPointerException("profile may not be null");
        }
        if (imageProperties == null) {
            throw new NullPointerException("imageProperties may not be null");
        }
        this.autoRemove = autoRemove;
        this.parameterProfile = profile;
        this.imageProperties = imageProperties;
        this.hostConfigHook = hostConfigHook;
        this.containerName = containerName;
        this.cmd = cmd;
        this.containerExposedPorts = exposedPorts;
        Map labels =
                DockerBuilder.getImageLabels(
                        profile.getType(), version, role, additionalBuildFlags);
        if (image == null) {
            this.image = DockerBuilder.getImageWithLabels(labels, true);
            if (this.image == null) {
                throw new TlsVersionNotFoundException();
            }
        } else {
            this.image = image;
        }
    }

    protected HostConfig prepareHostConfig(HostConfig cfg) {
        // Check if volume exists; Without this check, the container would be started
        // without any problems, swallowing the error and making it harder to identify
        InspectVolumeResponse vol = DockerBuilder.getCertDataVolumeInfo();

        // hook is handled in prepareCreateContainerCmd; this ensures it is called last
        return cfg.withBinds(
                new Bind(
                        vol.getName(),
                        new Volume("/cert/"),
                        AccessMode.ro,
                        SELContext.DEFAULT,
                        true));
    }

    protected CreateContainerCmd prepareCreateContainerCmd(CreateContainerCmd createContainerCmd) {
        HostConfig hcfg = prepareHostConfig(HostConfig.newHostConfig());
        if (hostConfigHook != null) {
            hcfg = hostConfigHook.apply(hcfg);
        }
        if (containerName != null) {
            createContainerCmd.withName(containerName);
        }
        if (cmd != null) {
            createContainerCmd.withCmd(cmd);
        }
        if (containerExposedPorts != null) {
            createContainerCmd.withExposedPorts(containerExposedPorts);
        }
        return createContainerCmd
                .withAttachStderr(true)
                .withAttachStdout(true)
                .withAttachStdin(true)
                .withTty(true)
                .withStdInOnce(true)
                .withStdinOpen(true)
                .withHostConfig(hcfg);
    }

    protected String createContainer() {
        if (this.image == null) {
            throw new IllegalStateException("Container could not be created, image is missing");
        }
        @SuppressWarnings("squid:S2095") // sonarlint: Resources should be closed
        // Create container does not need to be closed
        CreateContainerCmd containerCmd = DOCKER.createContainerCmd(image.getId());

        containerCmd = prepareCreateContainerCmd(containerCmd);
        CreateContainerResponse container = containerCmd.exec();
        String[] warnings = container.getWarnings();
        if (warnings != null && warnings.length != 0 && LOGGER.isWarnEnabled()) {
            LOGGER.warn("During container creation the following warnings were raised:");
            for (String warning : warnings) {
                LOGGER.warn(warning);
            }
        }
        return container.getId();
    }

    public void ensureContainerExists() {
        // TODO check if container already exists
        if (containerId != null) {
            // check if still exists
            // TODO
        }
        if (containerId == null) {
            // create new container
            containerId = createContainer();
        }
    }

    public void start() {
        ensureContainerExists();
        DOCKER.startContainerCmd(getId()).exec();
    }

    public void remove() {
        String id = getId();
        if (id != null) {
            DOCKER.removeContainerCmd(id).exec();
        }
        closeChildren();
        containerId = null;
    }

    private void autoRemove() {
        if (autoRemove) {
            remove();
        }
    }

    private void storeExitCode() {
        this.exitCode =
                Optional.of(
                        DOCKER.inspectContainerCmd(getId()).exec().getState().getExitCodeLong());
    }

    private void closeChildren() {
        for (DockerExecInstance exec : childExecs) {
            try {
                exec.close();
            } catch (Exception e) {
                LOGGER.warn("Error while closing exec instance", e);
            }
        }
        childExecs.clear();
    }

    public void stop(int secondsToWaitBeforeKilling) {
        DOCKER.stopContainerCmd(getId()).withTimeout(secondsToWaitBeforeKilling).exec();
        closeChildren();
        storeExitCode();
        autoRemove();
    }

    public void stop() {
        stop(2);
    }

    public void kill() {
        DOCKER.killContainerCmd(getId()).exec();
        closeChildren();
        storeExitCode();
        autoRemove();
    }

    public Image getImage() {
        return image;
    }

    @SuppressWarnings("squid:S2142") // sonarlint: "InterruptedException" should not be ignored
    // we rethrow the interrupted exception a bit later
    public void close() {
        closeChildren();
        if (autoRemove) {
            try {
                String id = getId();
                if (id != null) {
                    DOCKER.killContainerCmd(id).exec();
                }
            } catch (DockerException e) {
                LOGGER.warn("Failed to kill container on close()");
            }
            try {
                remove();
            } catch (DockerException e) {
                // we did our best
                LOGGER.warn("Failed to remove container on close()", e);
            }
        }
    }

    public void restart() {
        DOCKER.restartContainerCmd(getId()).exec();
    }

    public String getId() {
        return containerId;
    }

    public String getLogs() throws InterruptedException {
        FrameHandler fh = new FrameHandler();
        DOCKER.logContainerCmd(getId()).withStdOut(true).withStdErr(true).exec(fh);
        fh.awaitCompletion();
        String[] lines = fh.getLines();
        // TODO optimize the following into the frame handler itself
        String logs =
                Arrays.stream(lines)
                        .skip(logReadOffset)
                        .map(s -> s.concat("\n"))
                        .reduce(String::concat)
                        .orElse("-");
        logReadOffset = lines.length;
        return logs;
    }

    @SuppressWarnings(
            "squid:S3655") // sonarlint: Optional value should only be accessed after calling
    // isPresent()
    // this is fixed as if there is no value we either throw an exception or store a
    // new value
    public long getExitCode() {
        if (!exitCode.isPresent()) {
            // check if still running
            ContainerState state = DOCKER.inspectContainerCmd(getId()).exec().getState();
            if (Boolean.TRUE.equals(state.getRunning())) {
                throw new IllegalStateException("Container is still running");
            } else {
                storeExitCode();
                autoRemove();
            }
        }
        return exitCode.get();
    }

    public String getContainerName() {
        return containerName;
    }

    public List getContainerExposedPorts() {
        return containerExposedPorts;
    }

    public String[] getCmd() {
        return cmd;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy