Artifact Encryption plug point (#1202)

* added ArtifactEncryption interface, injected it into SM creation UI module, added encryption metadata key generation upon SM creation, used encryptor during file upload

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* add default artifact encryption implementation based on gcm aes algorithm

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* changed ArtifactEncryptor interface to manage encryption secrets by itself

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* cleaned up stale code, fixed sonar

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* fixed software module encryption within transaction

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* added artifact encryption secrets store

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* extended ArtifactEncryption interface to allow decryption, secrets store provides removeSecret, added missing javadocs

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* intriduced DbArtifact interface, use EncryptionAwareDbArtifact for artifact decryption during download

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* introduced ArtifactEncryptionService to minimize duplications and unneccessary dependency injections

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* declared ArtifactEncryptionService as a bean

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* added persistant encryption flag to software module

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* further adptations for encryption flag persistence

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* added ArtifactEncryptionException, fixed encryption check in UI

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* added encryption error handling

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* added encrypted flag to DDI/DMF, adapted exception handling

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* adapted rest docs

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* Add test to verify artifact encryption is not given by default

Signed-off-by: Florian Ruschbaschan <Florian.Ruschbaschan@bosch.io>

* Add isEncrypted() to toString() of JpaSoftwareModule, fix typos

Signed-off-by: Florian Ruschbaschan <Florian.Ruschbaschan@bosch.io>

* Fix sql migration scripts

Signed-off-by: Florian Ruschbaschan <Florian.Ruschbaschan@bosch.io>

* Calculate encrypted artifact size by subtract encryption size overhead

Signed-off-by: Florian Ruschbaschan <Florian.Ruschbaschan@bosch.io>

* publish upload failed without waiting for interuption during UI file upload

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

* upgraded cron utils to 9.1.6

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch.io>

Co-authored-by: Florian Ruschbaschan <Florian.Ruschbaschan@bosch.io>
This commit is contained in:
Bondar Bogdan
2021-11-18 09:07:05 +01:00
committed by GitHub
parent 7e28fba104
commit 146735012a
74 changed files with 1214 additions and 324 deletions

View File

@@ -26,7 +26,7 @@ import javax.validation.ConstraintViolationException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.ArtifactManagement;
@@ -77,7 +77,8 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
.isFalse();
assertThat(artifactManagement.findFirstBySHA1(NOT_EXIST_ID)).isNotPresent();
assertThat(artifactManagement.loadArtifactBinary(NOT_EXIST_ID)).isNotPresent();
assertThat(artifactManagement.loadArtifactBinary(NOT_EXIST_ID, module.getId(), module.isEncrypted()))
.isNotPresent();
}
@Test
@@ -116,10 +117,10 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
assertThat(artifactRepository.findAll()).hasSize(0);
final JpaSoftwareModule sm = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 1", "version 1", null, null));
.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
final JpaSoftwareModule sm2 = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 2", "version 2", null, null));
softwareModuleRepository.save(new JpaSoftwareModule(osType, "name 3", "version 3", null, null));
.save(new JpaSoftwareModule(osType, "name 2", "version 2"));
softwareModuleRepository.save(new JpaSoftwareModule(osType, "name 3", "version 3"));
final int artifactSize = 5 * 1024;
final byte[] randomBytes = randomBytes(artifactSize);
@@ -165,8 +166,8 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
final String artifactData = "test";
final int artifactSize = artifactData.length();
final long smID = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "smIllegalFilenameTest", "1.0", null, null)).getId();
final long smID = softwareModuleRepository.save(new JpaSoftwareModule(osType, "smIllegalFilenameTest", "1.0"))
.getId();
assertThatExceptionOfType(ConstraintViolationException.class).isThrownBy(
() -> artifactManagement.create(new ArtifactUpload(IOUtils.toInputStream(artifactData, "UTF-8"), smID,
illegalFilename, false, artifactSize)));
@@ -178,8 +179,7 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
public void createArtifactsUntilQuotaIsExceeded() throws IOException {
// create a software module
final long smId = softwareModuleRepository.save(new JpaSoftwareModule(osType, "sm1", "1.0", null, null))
.getId();
final long smId = softwareModuleRepository.save(new JpaSoftwareModule(osType, "sm1", "1.0")).getId();
// now create artifacts for this module until the quota is exceeded
final long maxArtifacts = quotaManagement.getMaxArtifactsPerSoftwareModule();
@@ -217,14 +217,13 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
final int artifactSize = Math.toIntExact(quotaManagement.getMaxArtifactSize() / 10);
final int numArtifacts = Math.toIntExact(maxBytes / artifactSize);
for (int i = 0; i < numArtifacts; ++i) {
final JpaSoftwareModule sm = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "smd" + i, "1.0", null, null));
final JpaSoftwareModule sm = softwareModuleRepository.save(new JpaSoftwareModule(osType, "smd" + i, "1.0"));
artifactIds.add(createArtifactForSoftwareModule("file" + i, sm.getId(), artifactSize).getId());
}
// upload one more artifact to trigger the quota exceeded error
final JpaSoftwareModule sm = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "smd" + numArtifacts, "1.0", null, null));
.save(new JpaSoftwareModule(osType, "smd" + numArtifacts, "1.0"));
assertThatExceptionOfType(StorageQuotaExceededException.class)
.isThrownBy(() -> createArtifactForSoftwareModule("file" + numArtifacts, sm.getId(), artifactSize));
@@ -240,8 +239,7 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
public void createArtifactFailsIfTooLarge() {
// create a software module
final JpaSoftwareModule sm1 = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "sm1", "1.0", null, null));
final JpaSoftwareModule sm1 = softwareModuleRepository.save(new JpaSoftwareModule(osType, "sm1", "1.0"));
// create an artifact that exceeds the configured quota
final long maxSize = quotaManagement.getMaxArtifactSize();
@@ -254,7 +252,7 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
public void hardDeleteSoftwareModule() throws IOException {
final JpaSoftwareModule sm = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 1", "version 1", null, null));
.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
createArtifactForSoftwareModule("file1", sm.getId(), 5 * 1024);
assertThat(artifactRepository.findAll()).hasSize(1);
@@ -273,9 +271,9 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
@Description("Tests the deletion of a local artifact including metadata.")
public void deleteArtifact() throws IOException {
final JpaSoftwareModule sm = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 1", "version 1", null, null));
.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
final JpaSoftwareModule sm2 = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 2", "version 2", null, null));
.save(new JpaSoftwareModule(osType, "name 2", "version 2"));
assertThat(artifactRepository.findAll()).isEmpty();
@@ -326,9 +324,9 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
public void deleteDuplicateArtifacts() throws IOException {
final JpaSoftwareModule sm = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 1", "version 1", null, null));
.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
final JpaSoftwareModule sm2 = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 2", "version 2", null, null));
.save(new JpaSoftwareModule(osType, "name 2", "version 2"));
final int artifactSize = 5 * 1024;
final byte[] randomBytes = randomBytes(artifactSize);
@@ -368,9 +366,9 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
public void deleteArtifactWithSameHashAndSoftwareModuleIsNotDeletedInSameTenants() throws IOException {
final JpaSoftwareModule sm = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 1", "version 1", null, null));
.save(new JpaSoftwareModule(osType, "name 1", "version 1"));
final JpaSoftwareModule sm2 = softwareModuleRepository
.save(new JpaSoftwareModule(osType, "name 2", "version 2", null, null));
.save(new JpaSoftwareModule(osType, "name 2", "version 2"));
final int artifactSize = 5 * 1024;
final byte[] randomBytes = randomBytes(artifactSize);
@@ -382,7 +380,9 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
final Artifact artifact2 = createArtifactForSoftwareModule("file2", sm2.getId(), artifactSize,
inputStream2);
assertEqualFileContents(artifactManagement.loadArtifactBinary(artifact2.getSha1Hash()), randomBytes);
assertEqualFileContents(
artifactManagement.loadArtifactBinary(artifact2.getSha1Hash(), sm2.getId(), sm2.isEncrypted()),
randomBytes);
assertThat(artifactRepository.findAll()).hasSize(2);
@@ -447,9 +447,11 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
final int artifactSize = 5 * 1024;
final byte[] randomBytes = randomBytes(artifactSize);
try (final InputStream input = new ByteArrayInputStream(randomBytes)) {
final Artifact artifact = createArtifactForSoftwareModule("file1",
testdataFactory.createSoftwareModuleOs().getId(), artifactSize, input);
assertEqualFileContents(artifactManagement.loadArtifactBinary(artifact.getSha1Hash()), randomBytes);
final SoftwareModule smOs = testdataFactory.createSoftwareModuleOs();
final Artifact artifact = createArtifactForSoftwareModule("file1", smOs.getId(), artifactSize, input);
assertEqualFileContents(
artifactManagement.loadArtifactBinary(artifact.getSha1Hash(), smOs.getId(), smOs.isEncrypted()),
randomBytes);
}
}
@@ -459,7 +461,7 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
public void loadArtifactBinaryWithoutDownloadArtifactThrowsPermissionDenied() {
assertThatExceptionOfType(InsufficientPermissionException.class)
.as("Should not have worked with missing permission.")
.isThrownBy(() -> artifactManagement.loadArtifactBinary("123"));
.isThrownBy(() -> artifactManagement.loadArtifactBinary("123", 1, false));
}
@Test
@@ -541,7 +543,8 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
assertThat(createdArtifact.getSha256Hash()).isEqualTo(artifactHashes.getSha256());
}
final Optional<AbstractDbArtifact> dbArtifact = artifactManagement.loadArtifactBinary(artifactHashes.getSha1());
final Optional<DbArtifact> dbArtifact = artifactManagement.loadArtifactBinary(artifactHashes.getSha1(),
sm.getId(), sm.isEncrypted());
assertThat(dbArtifact).isPresent();
}
@@ -561,7 +564,8 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
final Artifact artifact = artifactManagement.create(artifactUpload);
assertThat(artifact).isNotNull();
}
final Optional<AbstractDbArtifact> dbArtifact = artifactManagement.loadArtifactBinary(artifactHashes.getSha1());
final Optional<DbArtifact> dbArtifact = artifactManagement.loadArtifactBinary(artifactHashes.getSha1(),
smOs.getId(), smOs.isEncrypted());
assertThat(dbArtifact).isPresent();
try (final InputStream inputStream = new ByteArrayInputStream(testData)) {
@@ -622,10 +626,9 @@ public class ArtifactManagementTest extends AbstractJpaIntegrationTest {
assertThat(runAsTenant(tenant, () -> artifactRepository.findAll())).hasSize(count);
}
private void assertEqualFileContents(final Optional<AbstractDbArtifact> artifact, final byte[] randomBytes)
private void assertEqualFileContents(final Optional<DbArtifact> artifact, final byte[] randomBytes)
throws IOException {
try (final InputStream inputStream = artifactManagement.loadArtifactBinary(artifact.get().getHashes().getSha1())
.get().getFileInputStream()) {
try (final InputStream inputStream = artifact.get().getFileInputStream()) {
assertTrue(IOUtils.contentEquals(new ByteArrayInputStream(randomBytes), inputStream),
"The stored binary matches the given binary");
}

View File

@@ -520,13 +520,13 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
Arrays.asList(testType.getId()));
// found in test
final SoftwareModule unassigned = testdataFactory.createSoftwareModule("thetype", "unassignedfound");
final SoftwareModule one = testdataFactory.createSoftwareModule("thetype", "bfound");
final SoftwareModule two = testdataFactory.createSoftwareModule("thetype", "cfound");
final SoftwareModule differentName = testdataFactory.createSoftwareModule("thetype", "a");
final SoftwareModule unassigned = testdataFactory.createSoftwareModule("thetype", "unassignedfound", false);
final SoftwareModule one = testdataFactory.createSoftwareModule("thetype", "bfound", false);
final SoftwareModule two = testdataFactory.createSoftwareModule("thetype", "cfound", false);
final SoftwareModule differentName = testdataFactory.createSoftwareModule("thetype", "a", false);
// ignored
final SoftwareModule deleted = testdataFactory.createSoftwareModule("thetype", "deleted");
final SoftwareModule deleted = testdataFactory.createSoftwareModule("thetype", "deleted", false);
final SoftwareModule four = testdataFactory.createSoftwareModuleOs("e");
final DistributionSet set = distributionSetManagement
@@ -572,13 +572,13 @@ public class SoftwareModuleManagementTest extends AbstractJpaIntegrationTest {
Arrays.asList(testType.getId()));
// found in test
testdataFactory.createSoftwareModule("thetype", "unassignedfound");
final SoftwareModule one = testdataFactory.createSoftwareModule("thetype", "bfound");
final SoftwareModule two = testdataFactory.createSoftwareModule("thetype", "cfound");
final SoftwareModule differentName = testdataFactory.createSoftwareModule("thetype", "d");
testdataFactory.createSoftwareModule("thetype", "unassignedfound", false);
final SoftwareModule one = testdataFactory.createSoftwareModule("thetype", "bfound", false);
final SoftwareModule two = testdataFactory.createSoftwareModule("thetype", "cfound", false);
final SoftwareModule differentName = testdataFactory.createSoftwareModule("thetype", "d", false);
// ignored
final SoftwareModule deleted = testdataFactory.createSoftwareModule("thetype", "deleted");
final SoftwareModule deleted = testdataFactory.createSoftwareModule("thetype", "deleted", false);
final SoftwareModule four = testdataFactory.createSoftwareModuleOs("e");
distributionSetManagement