Skip to content

Commit

Permalink
feat(container.provider): added container instances enforcement allow…
Browse files Browse the repository at this point in the history
…list (eclipse-kura#5197)

* feat(container.orchestration.provider): added first implementation of enforcement allowlist

Signed-off-by: SimoneFiorani <[email protected]>

* feat(container.orchestration.provider): enforcement allowlist implemented

Signed-off-by: SimoneFiorani <[email protected]>

* feat(container.orchestration.provider): updated copyright and method signature

Signed-off-by: SimoneFiorani <[email protected]>

* feat(container.orchestration.provider): improved implementation, tests added

Signed-off-by: SimoneFiorani <[email protected]>

* feat(container.orchestration.provider): corrected typo in option description

Signed-off-by: SimoneFiorani <[email protected]>

* feat(container.orchestration.provider): fixed indendation

Co-authored-by: Mattia Dal Ben <[email protected]>

* feat(container.orchestration.provider): implemented suggestion and validation of already running containers

Signed-off-by: SimoneFiorani <[email protected]>

* feat(container.orchestration.provider): added tests for monitor-starting phase

Signed-off-by: SimoneFiorani <[email protected]>

* feat(container.orchestration.provider): refactored allowlist monitor class

Signed-off-by: SimoneFiorani <[email protected]>

* fix: typo in log

* style: fix copyright header

* fix: copyright header year

* feat(container.provider): added container instance digest enforcement

Signed-off-by: SimoneFiorani <[email protected]>

* feat: added tests and some updates on orchestration service

Signed-off-by: SimoneFiorani <[email protected]>

* feat: added running containers check after instance digest removing

Signed-off-by: SimoneFiorani <[email protected]>

* feat: running container check after stopping added

Signed-off-by: SimoneFiorani <[email protected]>

* refactor: refactor with suggestions

Signed-off-by: SimoneFiorani <[email protected]>

* refactor: moved enforceAlreadyRunningContainer method

* refactor: refactored some methods

* refactor: refactored hashcode and equals methods

* fix: fixed unit tests after rebase on develop

* Update kura/org.eclipse.kura.container.provider/OSGI-INF/metatype/org.eclipse.kura.container.provider.ContainerInstance.xml

Co-authored-by: Mattia Dal Ben <[email protected]>

* refactor: refactored constant names in Monitor

* Update kura/org.eclipse.kura.container.orchestration.provider/src/main/java/org/eclipse/kura/container/orchestration/provider/impl/enforcement/AllowlistEnforcementMonitor.java

Co-authored-by: Mattia Dal Ben <[email protected]>

---------

Signed-off-by: SimoneFiorani <[email protected]>
Co-authored-by: Mattia Dal Ben <[email protected]>
  • Loading branch information
sfiorani and mattdibi authored Apr 4, 2024
1 parent 7b489bf commit 024e264
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public class ContainerConfiguration {
private Optional<Float> cpus;
private Optional<String> gpus;
private Optional<String> runtime;
private Optional<String> enforcementDigest;

private ContainerConfiguration() {
}
Expand Down Expand Up @@ -303,6 +304,16 @@ public Optional<String> getRuntime() {
return this.runtime;
}

/**
* Return the enforcement digest assigned to the container.
*
* @return the optional runtime string used by the container
* @since 2.7
*/
public Optional<String> getEnforcementDigest() {
return this.enforcementDigest;
}

/**
* Creates a builder for creating a new {@link ContainerConfiguration} instance.
*
Expand All @@ -316,8 +327,9 @@ public static ContainerConfigurationBuilder builder() {
public int hashCode() {
return Objects.hash(this.containerDevices, this.containerEnvVars, this.containerLoggerParameters,
this.containerLoggingType, this.containerName, this.containerPorts, this.containerPrivileged,
this.containerVolumes, this.cpus, this.entryPoint, this.gpus, this.imageConfig, this.isFrameworkManaged,
this.memory, this.networkConfiguration, this.containerRestartOnFailure, this.runtime);
this.containerVolumes, this.cpus, this.enforcementDigest, this.entryPoint, this.gpus, this.imageConfig,
this.isFrameworkManaged, this.memory, this.networkConfiguration, this.containerRestartOnFailure,
this.runtime);
}

@Override
Expand All @@ -337,8 +349,9 @@ public boolean equals(Object obj) {
&& Objects.equals(this.containerPorts, other.containerPorts)
&& Objects.equals(this.containerPrivileged, other.containerPrivileged)
&& Objects.equals(this.containerVolumes, other.containerVolumes)
&& Objects.equals(this.cpus, other.cpus) && Objects.equals(this.entryPoint, other.entryPoint)
&& Objects.equals(this.gpus, other.gpus) && Objects.equals(this.imageConfig, other.imageConfig)
&& Objects.equals(this.cpus, other.cpus) && Objects.equals(enforcementDigest, other.enforcementDigest)
&& Objects.equals(this.entryPoint, other.entryPoint) && Objects.equals(this.gpus, other.gpus)
&& Objects.equals(this.imageConfig, other.imageConfig)
&& Objects.equals(this.isFrameworkManaged, other.isFrameworkManaged)
&& Objects.equals(this.memory, other.memory)
&& Objects.equals(this.networkConfiguration, other.networkConfiguration)
Expand Down Expand Up @@ -367,6 +380,7 @@ public static final class ContainerConfigurationBuilder {
private Optional<Float> cpus = Optional.empty();
private Optional<String> gpus = Optional.empty();
private Optional<String> runtime = Optional.empty();
private Optional<String> enforcementDigest = Optional.empty();

public ContainerConfigurationBuilder setContainerName(String serviceName) {
this.containerName = serviceName;
Expand Down Expand Up @@ -547,6 +561,14 @@ public ContainerConfigurationBuilder setRuntime(Optional<String> runtime) {
return this;
}

/**
* @since 2.7
*/
public ContainerConfigurationBuilder setEnforcementDigest(Optional<String> digest) {
this.enforcementDigest = digest;
return this;
}

public ContainerConfiguration build() {

if (this.containerPorts.isEmpty()) {
Expand Down Expand Up @@ -577,6 +599,7 @@ public ContainerConfiguration build() {
result.cpus = this.cpus;
result.gpus = this.gpus;
result.runtime = this.runtime;
result.enforcementDigest = this.enforcementDigest;

return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
Expand Down Expand Up @@ -91,6 +92,8 @@ public class ContainerOrchestrationServiceImpl implements ConfigurableComponent,
private List<ExposedPort> exposedPorts;
private AllowlistEnforcementMonitor allowlistEnforcementMonitor;

private Map<String, String> containerInstancesDigests = new HashMap<>();

public void setDockerClient(DockerClient dockerClient) {
this.dockerClient = dockerClient;
}
Expand Down Expand Up @@ -374,6 +377,7 @@ public String startContainer(ContainerConfiguration container) throws KuraExcept
logger.info("Creating new container instance");
pullImage(container.getImageConfiguration());
containerId = createContainer(container);
addContainerInstanceDigest(containerId, container.getEnforcementDigest());
startContainer(containerId);
}

Expand All @@ -391,7 +395,13 @@ public String startContainer(ContainerConfiguration container) throws KuraExcept
public void stopContainer(String id) throws KuraException {
checkRequestEnv(id);
try {
this.dockerClient.stopContainerCmd(id).exec();

if (listContainersIds().contains(id)) {
this.dockerClient.stopContainerCmd(id).exec();
}

removeContainerInstanceDigest(id);

} catch (Exception e) {
logger.error("Could not stop container {}. Caused by {}", id, e);
throw new KuraException(KuraErrorCode.OS_COMMAND_ERROR);
Expand All @@ -402,7 +412,11 @@ public void stopContainer(String id) throws KuraException {
public void deleteContainer(String id) throws KuraException {
checkRequestEnv(id);
try {
this.dockerClient.removeContainerCmd(id).exec();

if (listContainersIds().contains(id)) {
this.dockerClient.removeContainerCmd(id).exec();
}

this.frameworkManagedContainers.removeIf(c -> id.equals(c.id));
} catch (Exception e) {
logger.error("Could not remove container {}. Caused by {}", id, e);
Expand Down Expand Up @@ -1004,4 +1018,31 @@ public Set<String> getImageDigestsByContainerId(String containerId) {
return imageDigests;
}

private void addContainerInstanceDigest(String containerId, Optional<String> containerInstanceDigest) {

if (containerInstanceDigest.isPresent()) {
logger.info(
"Container {} presented enforcement digest. Adding it to the digests allowlist: it will be used if the enforcement is enabled.",
containerId);
this.containerInstancesDigests.put(containerId, containerInstanceDigest.get());
} else {
logger.info("Container {} doesn't contain the enforcement digest. "
+ "If enforcement is enabled, be sure that the digest is included in the Orchestration Service allowlist",
containerId);
}

}

private void removeContainerInstanceDigest(String containerId) {
if (this.containerInstancesDigests.containsKey(containerId)) {
this.containerInstancesDigests.remove(containerId);
logger.info("Removed digest of container with ID {} from Container Instances Allowlist", containerId);
enforceAlreadyRunningContainer();
}
}

public Set<String> getContainerInstancesAllowlist() {
return new HashSet<>(this.containerInstancesDigests.values());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@
public class AllowlistEnforcementMonitor extends ResultCallbackTemplate<AllowlistEnforcementMonitor, Event> {

private static final Logger logger = LoggerFactory.getLogger(AllowlistEnforcementMonitor.class);
private static final String ENFORCEMENT_SUCCESS = "Enforcement allowlist contains image digests {}...container {} is starting";
private static final String ENFORCEMENT_FAILURE = "Enforcement allowlist doesn't contain image digests...container {} will be stopped";
private static final String ENFORCEMENT_CHECK_SUCCESS = "Enforcement allowlist contains image digests {}...container {} is starting";
private static final String ENFORCEMENT_CHECK_FAILURE = "Enforcement allowlist doesn't contain image digests...container {} will be stopped";
private final Set<String> enforcementAllowlistContent;
private final ContainerOrchestrationServiceImpl orchestrationServiceImpl;

Expand All @@ -55,11 +55,17 @@ private void enforceAllowlistFor(String containerId) {

Set<String> digestIntersection = this.enforcementAllowlistContent.stream().distinct()
.filter(digestsList::contains).collect(Collectors.toSet());

if (digestIntersection.isEmpty()) {
digestIntersection = this.orchestrationServiceImpl.getContainerInstancesAllowlist().stream().distinct()
.filter(digestsList::contains).collect(Collectors.toSet());
}


if (!digestIntersection.isEmpty()) {
logger.info(ENFORCEMENT_SUCCESS, digestIntersection, containerId);
logger.info(ENFORCEMENT_CHECK_SUCCESS, digestIntersection, containerId);
} else {
logger.error(ENFORCEMENT_FAILURE, containerId);
logger.info(ENFORCEMENT_CHECK_FAILURE, containerId);
stopContainer(containerId);
deleteContainer(containerId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,14 @@
<AD id="container.restart.onfailure" name="Restart Container On Failure" type="Boolean" cardinality="1" required="true" default="false"
description="Automatically restart the container when it has failed.">
</AD>

<AD
id="enforcement.digest"
name="Container Image Enforcement Digest "
description="Digest of the container image allowed to run on the device if the Container Enforcement Monitor is enabled. If not given, it will be computed by the Container Signature Verification service."
type="String" cardinality="1" required="false"
default="" />

</OCD>
<Designate pid="org.eclipse.kura.container.provider.ContainerInstance"
factoryPid="org.eclipse.kura.container.provider.ContainerInstance">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public class ContainerInstanceOptions {
"");
private static final Property<Boolean> SIGNATURE_VERIFY_TLOG = new Property<>(
"container.signature.verify.transparency.log", true);
private static final Property<String> ENFORCEMENT_DIGEST = new Property<>("enforcement.digest", "");

private boolean enabled;
private final String image;
Expand Down Expand Up @@ -104,6 +105,8 @@ public class ContainerInstanceOptions {
private final Optional<String> signatureTrustAnchor;
private final Boolean signatureVerifyTransparencyLog;

private final Optional<String> enforcementDigest;

public ContainerInstanceOptions(final Map<String, Object> properties) {
if (isNull(properties)) {
throw new IllegalArgumentException("Properties cannot be null!");
Expand Down Expand Up @@ -138,6 +141,7 @@ public ContainerInstanceOptions(final Map<String, Object> properties) {
this.containerRuntime = parseOptionalString(CONTAINER_RUNTIME.getOptional(properties));
this.signatureTrustAnchor = parseOptionalString(SIGNATURE_TRUST_ANCHOR.getOptional(properties));
this.signatureVerifyTransparencyLog = SIGNATURE_VERIFY_TLOG.get(properties);
this.enforcementDigest = parseOptionalString(ENFORCEMENT_DIGEST.getOptional(properties));
}

private Map<String, String> parseVolume(String volumeString) {
Expand Down Expand Up @@ -353,6 +357,10 @@ public Boolean getSignatureVerifyTransparencyLog() {
return this.signatureVerifyTransparencyLog;
}

public Optional<String> getEnforcementDigest() {
return this.enforcementDigest;
}

private ImageConfiguration buildImageConfig() {
return new ImageConfiguration.ImageConfigurationBuilder().setImageName(this.image).setImageTag(this.imageTag)
.setImageDownloadTimeoutSeconds(this.imageDownloadTimeout)
Expand Down Expand Up @@ -383,7 +391,7 @@ public ContainerConfiguration getContainerConfiguration() {
.setContainerNetowrkConfiguration(buildContainerNetworkConfig())
.setLoggerParameters(getLoggerParameters()).setEntryPoint(getEntryPoint())
.setRestartOnFailure(getRestartOnFailure()).setMemory(getMemory()).setCpus(getCpus()).setGpus(getGpus())
.setRuntime(getRuntime()).build();
.setRuntime(getRuntime()).setEnforcementDigest(getEnforcementDigest()).build();
}

private List<Integer> parsePortString(String ports) {
Expand Down Expand Up @@ -429,9 +437,10 @@ private List<PortInternetProtocol> parsePortStringProtocol(String ports) {
public int hashCode() {
return Objects.hash(containerCpus, containerDevice, containerEntryPoint, containerEnv, containerGpus,
containerLoggerType, containerLoggingParameters, containerMemory, containerName,
containerNetworkingMode, containerPortProtocol, containerVolumeString, containerVolumes, enabled,
externalPorts, image, imageDownloadTimeout, imageTag, internalPorts, maxDownloadRetries, privilegedMode,
registryPassword, registryURL, registryUsername, restartOnFailure, retryInterval, containerRuntime);
containerNetworkingMode, containerPortProtocol, containerRuntime, containerVolumeString,
containerVolumes, enabled, enforcementDigest, externalPorts, image, imageDownloadTimeout, imageTag,
internalPorts, maxDownloadRetries, privilegedMode, registryPassword, registryURL, registryUsername,
restartOnFailure, retryInterval, signatureTrustAnchor, signatureVerifyTransparencyLog);
}

@Override
Expand Down Expand Up @@ -459,14 +468,17 @@ public boolean equals(Object obj) {
&& Objects.equals(containerPortProtocol, other.containerPortProtocol)
&& Objects.equals(containerVolumeString, other.containerVolumeString)
&& Objects.equals(containerVolumes, other.containerVolumes) && enabled == other.enabled
&& Objects.equals(enforcementDigest, other.enforcementDigest)
&& Objects.equals(externalPorts, other.externalPorts) && Objects.equals(image, other.image)
&& imageDownloadTimeout == other.imageDownloadTimeout && Objects.equals(imageTag, other.imageTag)
&& Objects.equals(internalPorts, other.internalPorts) && maxDownloadRetries == other.maxDownloadRetries
&& privilegedMode == other.privilegedMode && Objects.equals(registryPassword, other.registryPassword)
&& Objects.equals(registryURL, other.registryURL)
&& Objects.equals(registryUsername, other.registryUsername)
&& restartOnFailure == other.restartOnFailure && retryInterval == other.retryInterval
&& Objects.equals(containerRuntime, other.containerRuntime);
&& Objects.equals(containerRuntime, other.containerRuntime)
&& Objects.equals(signatureTrustAnchor, other.signatureTrustAnchor)
&& Objects.equals(signatureVerifyTransparencyLog, other.signatureVerifyTransparencyLog);
}

}
Loading

0 comments on commit 024e264

Please sign in to comment.