Skip to content

Commit

Permalink
Merge branch 'allowlist_enforcement_adding' into container-instances-…
Browse files Browse the repository at this point in the history
…allowlist
  • Loading branch information
sfiorani authored Mar 28, 2024
2 parents 5e7e511 + ab61d1d commit 90c7c50
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<AD
id="enforcement.allowlist"
name="Container Image Allowlist "
description="List of values containing the image digests. Each entry must be inserted in a new line of the text box|TextArea"
description="List of container image digests. Each entry must be separated by a new line of the text box|TextArea"
type="String"
cardinality="1"
required="false"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,9 @@ public void updated(Map<String, Object> properties) {
startEnforcementMonitor();
enforceAlreadyRunningContainer();
} catch (Exception ex) {
logger.error("Error starting enforcement monitor, disconnecting from docker...", ex);
cleanUpDocker();
logger.error("Error starting enforcement monitor. Due to {}", ex.getMessage());
closeEnforcementMonitor();
logger.error("Disconnected from docker");
logger.warn("Enforcement won't be active.");
}
}
}
Expand Down Expand Up @@ -540,6 +539,7 @@ private String createContainer(ContainerConfiguration containerDescription) thro
if (containerDescription.isContainerPrivileged()) {
configuration = configuration.withPrivileged(containerDescription.isContainerPrivileged());
}

commandBuilder = commandBuilder.withExposedPorts(this.exposedPorts);

return commandBuilder.withHostConfig(configuration).exec().getId();
Expand Down Expand Up @@ -985,14 +985,20 @@ public void deleteImage(String imageId) throws KuraException {
}
}

public Set<String> getImageDigestsByContainerName(String containerName) {
public Set<String> getImageDigestsByContainerId(String containerId) {

Set<String> imageDigests = new HashSet<>();

dockerClient.listImagesCmd().withImageNameFilter(containerName).exec().stream().forEach(image -> {
List<String> digests = Arrays.asList(image.getRepoDigests());
digests.stream().forEach(digest -> imageDigests.add(digest.split("@")[1]));
});
String containerName = listContainerDescriptors().stream()
.filter(container -> container.getContainerId().equals(containerId)).findFirst()
.map(container -> container.getContainerName()).orElse(null);

if (containerName != null) {
dockerClient.listImagesCmd().withImageNameFilter(containerName).exec().stream().forEach(image -> {
List<String> digests = Arrays.asList(image.getRepoDigests());
digests.stream().forEach(digest -> imageDigests.add(digest.split("@")[1]));
});
}

return imageDigests;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,11 @@ public void onNext(Event item) {

private void enforceAllowlistFor(String containerId) {

Set<String> digestsList = this.orchestrationServiceImpl
.getImageDigestsByContainerName(getContainerNameById(containerId));
Set<String> digestsList = this.orchestrationServiceImpl.getImageDigestsByContainerId(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());
Expand All @@ -78,12 +77,6 @@ public void enforceAllowlistFor(List<ContainerInstanceDescriptor> containerDescr
}
}

private String getContainerNameById(String containerId) {
return this.orchestrationServiceImpl.listContainerDescriptors().stream()
.filter(container -> container.getContainerId().equals(containerId)).findFirst()
.map(container -> container.getContainerName()).orElse(null);
}

private void stopContainer(String containerId) {

this.orchestrationServiceImpl.listContainerDescriptors().stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,8 +324,8 @@ public void testImageDigestsByContainerName() throws KuraException, InterruptedE
givenDockerClient();

whenMockForImageDigestsListing();

whenGetImageDigestsByContainerName(IMAGE_NAME_NGINX);
whenDockerClientMockSomeContainers();
whenGetImageDigestsByContainerId(CONTAINER_ID_1);

thenDigestsListEqualsExpectedOne(EXPECTED_DIGESTS_ARRAY);

Expand Down Expand Up @@ -630,8 +630,8 @@ private void whenImagesAreListed() {
this.dockerService.listImageInstanceDescriptors();
}

private void whenGetImageDigestsByContainerName(String containerName) {
this.digestsList = this.dockerService.getImageDigestsByContainerName(containerName);
private void whenGetImageDigestsByContainerId(String containerName) {
this.digestsList = this.dockerService.getImageDigestsByContainerId(containerName);
}

/**
Expand Down Expand Up @@ -684,7 +684,7 @@ private void thenCheckIfImagesWereListed() {
}

private void thenDigestsListEqualsExpectedOne(String[] digestsArray) {
assertEquals(this.digestsList, new HashSet<>(Arrays.asList(digestsArray)));
assertEquals(new HashSet<>(Arrays.asList(digestsArray)), this.digestsList);
}

private void thenContainerInstanceDigestIsAddedToAllowlist() {
Expand All @@ -697,9 +697,7 @@ private void thenContainerInstanceDigestIsNotInAllowlist() {
}

private void thenContainerInstanceDigestIsExpectedOne(String expected) {

List<String> actualDigests = new ArrayList<>(this.dockerService.getContainerInstancesAllowlist());
assertEquals(actualDigests.get(0), expected);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,21 @@

package org.eclipse.kura.container.orchestration.provider;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand All @@ -44,11 +45,7 @@
import org.mockito.Mockito;

import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.ListImagesCmd;
import com.github.dockerjava.api.command.RemoveContainerCmd;
import com.github.dockerjava.api.command.StopContainerCmd;
import com.github.dockerjava.api.model.Event;
import com.github.dockerjava.api.model.Image;

public class EnforcementSecurityTest {

Expand All @@ -64,93 +61,102 @@ public class EnforcementSecurityTest {
private static final String FILLED_ALLOWLIST_CONTENT_NO_SPACE = "sha256:f9d633ff6640178c2d0525017174a688e2c1aef28f0a0130b26bd5554491f0da\nsha256:c26ae7472d624ba1fafd296e73cecc4f93f853088e6a9c13c0d52f6ca5865107";
private static final String FILLED_ALLOWLIST_CONTENT_WITH_SPACES = " sha256:f9d633ff6640178c2d0525017174a688e2c1aef28f0a0130b26bd5554491f0da \n sha256:c26ae7472d624ba1fafd296e73cecc4f93f853088e6a9c13c0d52f6ca5865107";

private static final String CORRECT_DIGEST = "ubuntu@sha256:c26ae7472d624ba1fafd296e73cecc4f93f853088e6a9c13c0d52f6ca5865107";
private static final String WRONG_DIGEST = "ubuntu@sha256:0000000000000000000000000000000000000000000000000000000000000000";
private static final String CORRECT_DIGEST = "sha256:c26ae7472d624ba1fafd296e73cecc4f93f853088e6a9c13c0d52f6ca5865107";
private static final String WRONG_DIGEST = "sha256:0000000000000000000000000000000000000000000000000000000000000000";

private AllowlistEnforcementMonitor allowlistEnforcementMonitor;
private ContainerOrchestrationServiceImpl mockedContainerOrchImpl;
private ContainerOrchestrationServiceImpl mockedContainerOrchestrationImpl;

ContainerConfiguration containerConfig;

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

private ContainerState stoppingResult;

public EnforcementSecurityTest() {
this.properties.clear();
this.stoppingResult = null;
}

@Test
public void shouldAllowStartingWithCorrectAllowlistContent() throws KuraException, InterruptedException {

givenMockedContainerOrchestrationServiceWith(CONTAINER_ID, CONTAINER_NAME, IMAGE_NAME, ContainerState.ACTIVE);
givenMockedDockerClient(new String[] { CORRECT_DIGEST }, CONTAINER_ID, CONTAINER_NAME, IMAGE_NAME);
givenMockedContainerOrchestrationServiceWith(CONTAINER_ID, CONTAINER_NAME, IMAGE_NAME, ContainerState.ACTIVE,
CORRECT_DIGEST);
givenMockedDockerClient();
givenAllowlistEnforcement(FILLED_ALLOWLIST_CONTENT_NO_SPACE);

whenOnNext(CONTAINER_ID);

thenContainerDigestIsVerified();
thenStopContainerWasNeverCalled();
thenDeleteContainerWasNeverCalled();
}

@Test
public void shouldAllowStartingWithCorrectAllowlistContentWithSpaces() throws KuraException, InterruptedException {

givenMockedContainerOrchestrationServiceWith(CONTAINER_ID, CONTAINER_NAME, IMAGE_NAME, ContainerState.ACTIVE);
givenMockedDockerClient(new String[] { CORRECT_DIGEST }, CONTAINER_ID, CONTAINER_NAME, IMAGE_NAME);
givenMockedContainerOrchestrationServiceWith(CONTAINER_ID, CONTAINER_NAME, IMAGE_NAME, ContainerState.ACTIVE,
CORRECT_DIGEST);
givenMockedDockerClient();
givenAllowlistEnforcement(FILLED_ALLOWLIST_CONTENT_WITH_SPACES);

whenOnNext(CONTAINER_ID);

thenContainerDigestIsVerified();
thenStopContainerWasNeverCalled();
thenDeleteContainerWasNeverCalled();
}

@Test
public void shouldNotAllowStartingWithEmptyAllowlistContent() throws KuraException, InterruptedException {

givenMockedContainerOrchestrationServiceWith(CONTAINER_ID, CONTAINER_NAME, IMAGE_NAME, ContainerState.ACTIVE);
givenMockedDockerClient(new String[] { CORRECT_DIGEST }, CONTAINER_ID, CONTAINER_NAME, IMAGE_NAME);
givenMockedContainerOrchestrationServiceWith(CONTAINER_ID, CONTAINER_NAME, IMAGE_NAME, ContainerState.ACTIVE,
CORRECT_DIGEST);
givenMockedDockerClient();
givenAllowlistEnforcement(EMPTY_ALLOWLIST_CONTENT);

whenOnNext(CONTAINER_ID);

thenContainerDigestIsNotValidAndStoppedAndDeleted();
thenStopContainerWasCalledFor(CONTAINER_ID);
thenDeleteContainerWasCalledFor(CONTAINER_ID);
}

@Test
public void shouldNotAllowStartingWithWrongContainerDigest() throws KuraException, InterruptedException {

givenMockedContainerOrchestrationServiceWith(CONTAINER_ID, CONTAINER_NAME, IMAGE_NAME, ContainerState.ACTIVE);
givenMockedDockerClient(new String[] { WRONG_DIGEST }, CONTAINER_ID, CONTAINER_NAME, IMAGE_NAME);
givenMockedContainerOrchestrationServiceWith(CONTAINER_ID, CONTAINER_NAME, IMAGE_NAME, ContainerState.ACTIVE,
WRONG_DIGEST);
givenMockedDockerClient();
givenAllowlistEnforcement(FILLED_ALLOWLIST_CONTENT_NO_SPACE);

whenOnNext(CONTAINER_ID);

thenContainerDigestIsNotValidAndStoppedAndDeleted();
thenStopContainerWasCalledFor(CONTAINER_ID);
thenDeleteContainerWasCalledFor(CONTAINER_ID);
}

@Test
public void shouldStopAndDeleteContainerWithWrongDigestAndActiveState() throws KuraException, InterruptedException {

givenMockedContainerOrchestrationServiceWith(CONTAINER_ID, CONTAINER_NAME, IMAGE_NAME, ContainerState.ACTIVE);
givenMockedDockerClient(new String[] { CORRECT_DIGEST }, CONTAINER_ID, CONTAINER_NAME, IMAGE_NAME);
givenMockedContainerOrchestrationServiceWith(CONTAINER_ID, CONTAINER_NAME, IMAGE_NAME, ContainerState.ACTIVE,
CORRECT_DIGEST);
givenMockedDockerClient();
givenAllowlistEnforcement(EMPTY_ALLOWLIST_CONTENT);

whenVerifyAlreadyRunningContainersDigests(this.mockedContainerOrchImpl.listContainerDescriptors());
whenVerifyAlreadyRunningContainersDigests(this.mockedContainerOrchestrationImpl.listContainerDescriptors());

thenContainerDigestIsNotValidAndStoppedAndDeleted();
thenStopContainerWasCalledFor(CONTAINER_ID);
thenDeleteContainerWasCalledFor(CONTAINER_ID);
}

@Test
public void shouldOnlyDeleteContainerWithWrongDigestAndFailedState() throws KuraException, InterruptedException {

givenMockedContainerOrchestrationServiceWith(CONTAINER_ID, CONTAINER_NAME, IMAGE_NAME, ContainerState.FAILED);
givenMockedDockerClient(new String[] { CORRECT_DIGEST }, CONTAINER_ID, CONTAINER_NAME, IMAGE_NAME);
givenMockedContainerOrchestrationServiceWith(CONTAINER_ID, CONTAINER_NAME, IMAGE_NAME, ContainerState.FAILED,
CORRECT_DIGEST);
givenMockedDockerClient();
givenAllowlistEnforcement(EMPTY_ALLOWLIST_CONTENT);

whenVerifyAlreadyRunningContainersDigests(this.mockedContainerOrchImpl.listContainerDescriptors());
whenVerifyAlreadyRunningContainersDigests(this.mockedContainerOrchestrationImpl.listContainerDescriptors());

thenContainerDigestIsNotValidAndOnlyDeleted();
thenStopContainerWasNeverCalled();
thenDeleteContainerWasCalledFor(CONTAINER_ID);
}

/*
Expand All @@ -160,10 +166,11 @@ public void shouldOnlyDeleteContainerWithWrongDigestAndFailedState() throws Kura
ContainerInstanceDescriptor containerInstanceDescriptor;

private void givenMockedContainerOrchestrationServiceWith(String containerId, String containerName,
String imageName, ContainerState containerState) throws KuraException, InterruptedException {
this.mockedContainerOrchImpl = spy(new ContainerOrchestrationServiceImpl());
String imageName, ContainerState containerState, String digest) throws KuraException, InterruptedException {

this.mockedContainerOrchestrationImpl = spy(new ContainerOrchestrationServiceImpl());

containerInstanceDescriptor = ContainerInstanceDescriptor.builder().setContainerID(containerId)
this.containerInstanceDescriptor = ContainerInstanceDescriptor.builder().setContainerID(containerId)
.setContainerName(containerName).setContainerImage(imageName).setContainerState(containerState).build();
List<ContainerInstanceDescriptor> containerDescriptors = new ArrayList<>();
containerDescriptors.add(containerInstanceDescriptor);
Expand All @@ -179,45 +186,22 @@ REGISTRY_USERNAME, new Password(REGISTRY_PASSWORD))))
.setDeviceList(Arrays.asList("/dev/gpio1", "/dev/gpio2"))
.setEnvVars(Arrays.asList("test=test", "test2=test2")).build();

doReturn(containerDescriptors).when(this.mockedContainerOrchImpl).listContainerDescriptors();

doNothing().when(this.mockedContainerOrchImpl).pullImage(any(ImageConfiguration.class));
doReturn(containerDescriptors).when(this.mockedContainerOrchestrationImpl).listContainerDescriptors();
doNothing().when(this.mockedContainerOrchestrationImpl).pullImage(any(ImageConfiguration.class));
doNothing().when(this.mockedContainerOrchestrationImpl).stopContainer(anyString());
doNothing().when(this.mockedContainerOrchestrationImpl).deleteContainer(anyString());
doReturn(new HashSet<>(Arrays.asList(digest))).when(this.mockedContainerOrchestrationImpl)
.getImageDigestsByContainerId(containerId);
}

private void givenMockedDockerClient(String[] digestsList, String containerId, String containerName,
String imageName) {
private void givenMockedDockerClient() {
DockerClient mockedDockerClient = mock(DockerClient.class, Mockito.RETURNS_DEEP_STUBS);
List<Image> images = new LinkedList<>();
Image mockImage = mock(Image.class);

when(mockImage.getRepoTags()).thenReturn(new String[] { imageName, "latest", "nginx:latest" });
when(mockImage.getRepoDigests()).thenReturn(digestsList);
when(mockImage.getId()).thenReturn(imageName);
images.add(mockImage);

when(mockedDockerClient.listImagesCmd()).thenReturn(mock(ListImagesCmd.class));
when(mockedDockerClient.listImagesCmd().withImageNameFilter(anyString())).thenReturn(mock(ListImagesCmd.class));
when(mockedDockerClient.listImagesCmd().withImageNameFilter(anyString()).exec()).thenReturn(images);
when(mockedDockerClient.stopContainerCmd(anyString())).thenReturn(mock(StopContainerCmd.class));
when(mockedDockerClient.stopContainerCmd(anyString()).exec()).thenAnswer(answer -> {
this.stoppingResult = ContainerState.STOPPING;
return null;
});

when(mockedDockerClient.removeContainerCmd(anyString())).thenReturn(mock(RemoveContainerCmd.class));
when(mockedDockerClient.removeContainerCmd(anyString()).exec()).thenAnswer(answer -> {
this.containerInstanceDescriptor = ContainerInstanceDescriptor.builder().setContainerID(containerId)
.setContainerName(containerName).setContainerImage(imageName)
.setContainerState(ContainerState.FAILED).build();
return null;
});

this.mockedContainerOrchImpl.setDockerClient(mockedDockerClient);
this.mockedContainerOrchestrationImpl.setDockerClient(mockedDockerClient);
}

private void givenAllowlistEnforcement(String rawAllowlistContent) {
this.allowlistEnforcementMonitor = new AllowlistEnforcementMonitor(rawAllowlistContent,
this.mockedContainerOrchImpl);
this.mockedContainerOrchestrationImpl);
}

/*
Expand All @@ -236,19 +220,19 @@ private void whenVerifyAlreadyRunningContainersDigests(List<ContainerInstanceDes
* Then
*/

private void thenContainerDigestIsVerified() {

assertEquals(ContainerState.ACTIVE, this.containerInstanceDescriptor.getContainerState());
private void thenStopContainerWasNeverCalled() throws KuraException {
verify(this.mockedContainerOrchestrationImpl, never()).stopContainer(any(String.class));
}

private void thenStopContainerWasCalledFor(String containerId) throws KuraException {
verify(this.mockedContainerOrchestrationImpl, times(1)).stopContainer(containerId);
}

private void thenContainerDigestIsNotValidAndStoppedAndDeleted() {
assertEquals(ContainerState.STOPPING, this.stoppingResult);
assertEquals(ContainerState.FAILED, this.containerInstanceDescriptor.getContainerState());
private void thenDeleteContainerWasNeverCalled() throws KuraException {
verify(this.mockedContainerOrchestrationImpl, never()).deleteContainer(any(String.class));
}

private void thenContainerDigestIsNotValidAndOnlyDeleted() {
assertEquals(null, this.stoppingResult);
assertEquals(ContainerState.FAILED, this.containerInstanceDescriptor.getContainerState());
private void thenDeleteContainerWasCalledFor(String containerId) throws KuraException {
verify(this.mockedContainerOrchestrationImpl, times(1)).deleteContainer(containerId);
}
}

0 comments on commit 90c7c50

Please sign in to comment.