diff --git a/kura/org.eclipse.kura.container.orchestration.provider/OSGI-INF/metatype/org.eclipse.kura.container.orchestration.provider.ContainerOrchestrationService.xml b/kura/org.eclipse.kura.container.orchestration.provider/OSGI-INF/metatype/org.eclipse.kura.container.orchestration.provider.ContainerOrchestrationService.xml index 77ae89886e4..8e9912689de 100644 --- a/kura/org.eclipse.kura.container.orchestration.provider/OSGI-INF/metatype/org.eclipse.kura.container.orchestration.provider.ContainerOrchestrationService.xml +++ b/kura/org.eclipse.kura.container.orchestration.provider/OSGI-INF/metatype/org.eclipse.kura.container.orchestration.provider.ContainerOrchestrationService.xml @@ -33,7 +33,7 @@ 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."); } } } @@ -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(); @@ -985,14 +985,20 @@ public void deleteImage(String imageId) throws KuraException { } } - public Set getImageDigestsByContainerName(String containerName) { + public Set getImageDigestsByContainerId(String containerId) { Set imageDigests = new HashSet<>(); - dockerClient.listImagesCmd().withImageNameFilter(containerName).exec().stream().forEach(image -> { - List 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 digests = Arrays.asList(image.getRepoDigests()); + digests.stream().forEach(digest -> imageDigests.add(digest.split("@")[1])); + }); + } return imageDigests; } diff --git a/kura/org.eclipse.kura.container.orchestration.provider/src/main/java/org/eclipse/kura/container/orchestration/provider/impl/enforcement/AllowlistEnforcementMonitor.java b/kura/org.eclipse.kura.container.orchestration.provider/src/main/java/org/eclipse/kura/container/orchestration/provider/impl/enforcement/AllowlistEnforcementMonitor.java index bb43a583264..de95cfbaa1c 100644 --- a/kura/org.eclipse.kura.container.orchestration.provider/src/main/java/org/eclipse/kura/container/orchestration/provider/impl/enforcement/AllowlistEnforcementMonitor.java +++ b/kura/org.eclipse.kura.container.orchestration.provider/src/main/java/org/eclipse/kura/container/orchestration/provider/impl/enforcement/AllowlistEnforcementMonitor.java @@ -51,12 +51,11 @@ public void onNext(Event item) { private void enforceAllowlistFor(String containerId) { - Set digestsList = this.orchestrationServiceImpl - .getImageDigestsByContainerName(getContainerNameById(containerId)); + Set digestsList = this.orchestrationServiceImpl.getImageDigestsByContainerId(containerId); Set 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()); @@ -78,12 +77,6 @@ public void enforceAllowlistFor(List 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() diff --git a/kura/test/org.eclipse.kura.container.orchestration.provider.test/src/test/java/org/eclipse/kura/container/orchestration/provider/ContainerOrchestrationServiceImplTest.java b/kura/test/org.eclipse.kura.container.orchestration.provider.test/src/test/java/org/eclipse/kura/container/orchestration/provider/ContainerOrchestrationServiceImplTest.java index d5fd1c29d05..f73c93b050b 100644 --- a/kura/test/org.eclipse.kura.container.orchestration.provider.test/src/test/java/org/eclipse/kura/container/orchestration/provider/ContainerOrchestrationServiceImplTest.java +++ b/kura/test/org.eclipse.kura.container.orchestration.provider.test/src/test/java/org/eclipse/kura/container/orchestration/provider/ContainerOrchestrationServiceImplTest.java @@ -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); @@ -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); } /** @@ -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() { @@ -697,9 +697,7 @@ private void thenContainerInstanceDigestIsNotInAllowlist() { } private void thenContainerInstanceDigestIsExpectedOne(String expected) { - List actualDigests = new ArrayList<>(this.dockerService.getContainerInstancesAllowlist()); assertEquals(actualDigests.get(0), expected); - } } diff --git a/kura/test/org.eclipse.kura.container.orchestration.provider.test/src/test/java/org/eclipse/kura/container/orchestration/provider/EnforcementSecurityTest.java b/kura/test/org.eclipse.kura.container.orchestration.provider.test/src/test/java/org/eclipse/kura/container/orchestration/provider/EnforcementSecurityTest.java index 6fe4f825861..bd291cda2b8 100644 --- a/kura/test/org.eclipse.kura.container.orchestration.provider.test/src/test/java/org/eclipse/kura/container/orchestration/provider/EnforcementSecurityTest.java +++ b/kura/test/org.eclipse.kura.container.orchestration.provider.test/src/test/java/org/eclipse/kura/container/orchestration/provider/EnforcementSecurityTest.java @@ -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; @@ -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 { @@ -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 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); } /* @@ -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 containerDescriptors = new ArrayList<>(); containerDescriptors.add(containerInstanceDescriptor); @@ -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 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); } /* @@ -236,19 +220,19 @@ private void whenVerifyAlreadyRunningContainersDigests(List