Move artifact encryption to hawkbit-artifact-api where it does belong (#2540)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-07-10 11:57:44 +03:00
committed by GitHub
parent 22246a57dc
commit edd6dabb90
54 changed files with 243 additions and 227 deletions

View File

@@ -1,64 +0,0 @@
/**
* Copyright (c) 2021 Bosch.IO GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository;
import java.io.InputStream;
import java.util.Map;
import java.util.Set;
import org.eclipse.hawkbit.repository.exception.ArtifactEncryptionFailedException;
/**
* Interface definition for artifact encryption.
*/
public interface ArtifactEncryption {
/**
* Defines the required secret keys for particular encryption algorithm.
*
* @return list of required secret keys
*/
Set<String> requiredSecretKeys();
/**
* Generates required secrets key/value pairs.
*
* @return secrets key/value pairs
* @throws ArtifactEncryptionFailedException thrown in case of an error while generating secrets
*/
Map<String, String> generateSecrets();
/**
* Encrypts artifact stream with provided secrets.
*
* @param secrets secrets key/value pairs to be used for encryption
* @param stream artifact stream to encrypt
* @return encrypted input stream
* @throws ArtifactEncryptionFailedException thrown in case of an error while encrypting the provided stream
*/
InputStream encryptStream(final Map<String, String> secrets, final InputStream stream);
/**
* Decrypts encrypted artifact stream based on provided secrets.
*
* @param secrets secrets key/value pairs to be used for decryption
* @param stream artifact stream to decrypt
* @return decrypted input stream
* @throws ArtifactEncryptionFailedException thrown in case of an error while decrypting the provided stream
*/
InputStream decryptStream(final Map<String, String> secrets, final InputStream stream);
/**
* Size of the underlying encryption algorithm overhead in bytes
*
* @return encryption overhead in byte
*/
int encryptionSizeOverhead();
}

View File

@@ -1,57 +0,0 @@
/**
* Copyright (c) 2021 Bosch.IO GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository;
import java.util.Optional;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
/**
* Interface definition for artifact encryption secrets store.
*/
public interface ArtifactEncryptionSecretsStore {
/**
* Adds secret key/value pair associated with particular
* {@link SoftwareModule} id to the store.
*
* @param softwareModuleId {@link SoftwareModule} id associated with the secret
* @param secretKey key of the secret
* @param secretValue value of the secret
*/
void addSecret(final long softwareModuleId, final String secretKey, final String secretValue);
/**
* Checks if secret is present for particular {@link SoftwareModule} id and
* key in the store.
*
* @param softwareModuleId {@link SoftwareModule} id associated with the secret
* @param secretKey key of the secret
*/
boolean secretExists(final long softwareModuleId, final String secretKey);
/**
* Retrieves secret value associated with particular {@link SoftwareModule}
* id and key from the store.
*
* @param softwareModuleId {@link SoftwareModule} id associated with the secret
* @param secretKey key of the secret
*/
Optional<String> getSecret(final long softwareModuleId, final String secretKey);
/**
* Removes secret key/value pair associated with particular
* {@link SoftwareModule} id from the store.
*
* @param softwareModuleId {@link SoftwareModule} id associated with the secret
* @param secretKey key of the secret
*/
void removeSecret(final long softwareModuleId, final String secretKey);
}

View File

@@ -1,128 +0,0 @@
/**
* Copyright (c) 2021 Bosch.IO GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.exception.ArtifactEncryptionUnsupportedException;
import org.springframework.beans.factory.annotation.Autowired;
/**
* Service responsible for encryption operations.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings("java:S6548") // singleton holder ensures static access to spring resources in some places
public final class ArtifactEncryptionService {
private static final ArtifactEncryptionService SINGLETON = new ArtifactEncryptionService();
private ArtifactEncryption artifactEncryption;
private ArtifactEncryptionSecretsStore artifactEncryptionSecretsStore;
/**
* @return the artifact encryption service singleton instance
*/
public static ArtifactEncryptionService getInstance() {
return SINGLETON;
}
@Autowired(required = false) // spring setter injection
public void setArtifactEncryption(final ArtifactEncryption artifactEncryption) {
this.artifactEncryption = artifactEncryption;
}
@Autowired(required = false) // spring setter injection
public void setArtifactEncryptionSecretsStore(final ArtifactEncryptionSecretsStore artifactEncryptionSecretsStore) {
this.artifactEncryptionSecretsStore = artifactEncryptionSecretsStore;
}
/**
* Checks if required encryption and secrets store beans are present.
*
* @return if encryption is supported
*/
public boolean isEncryptionSupported() {
return artifactEncryption != null && artifactEncryptionSecretsStore != null;
}
/**
* Generates encryption secrets and saves them in secret store by software module id reference.
*
* @param smId software module id
*/
public void addSoftwareModuleEncryptionSecrets(final long smId) {
if (!isEncryptionSupported()) {
throw new ArtifactEncryptionUnsupportedException("Encryption secrets generation is not supported.");
}
final Map<String, String> secrets = artifactEncryption.generateSecrets();
secrets.forEach((key, value) -> artifactEncryptionSecretsStore.addSecret(smId, key, value));
// we want to clear secrets from memory as soon as possible
secrets.clear();
}
/**
* Encrypts artifact stream using the keys retrieved from secrets store by
* software module id reference.
*
* @param smId software module id
* @param artifactStream artifact stream to encrypt
* @return encrypted input stream
*/
public InputStream encryptSoftwareModuleArtifact(final long smId, final InputStream artifactStream) {
if (!isEncryptionSupported()) {
throw new ArtifactEncryptionUnsupportedException("Artifact encryption is not supported.");
}
return artifactEncryption.encryptStream(getSoftwareModuleEncryptionSecrets(smId), artifactStream);
}
/**
* Decrypts artifact stream using the keys retrieved from secrets store by
* software module id reference.
*
* @param smId software module id
* @param encryptedArtifactStream artifact stream to decrypt
* @return decrypted input stream
*/
public InputStream decryptSoftwareModuleArtifact(final long smId, final InputStream encryptedArtifactStream) {
if (!isEncryptionSupported()) {
throw new ArtifactEncryptionUnsupportedException("Artifact decryption is not supported.");
}
return artifactEncryption.decryptStream(getSoftwareModuleEncryptionSecrets(smId), encryptedArtifactStream);
}
/**
* Size of the underlying encryption algorithm overhead in bytes
*
* @return encryption overhead in byte
*/
public int encryptionSizeOverhead() {
return artifactEncryption.encryptionSizeOverhead();
}
private Map<String, String> getSoftwareModuleEncryptionSecrets(final long smId) {
final Set<String> requiredSecretsKeys = artifactEncryption.requiredSecretKeys();
final Map<String, String> requiredSecrets = new HashMap<>();
for (final String requiredSecretsKey : requiredSecretsKeys) {
final Optional<String> requiredSecretsValue = artifactEncryptionSecretsStore.getSecret(smId,
requiredSecretsKey);
requiredSecretsValue.ifPresent(secretValue -> requiredSecrets.put(requiredSecretsKey, secretValue));
}
return requiredSecrets;
}
}

View File

@@ -16,7 +16,7 @@ import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.repository.artifact.model.DbArtifact;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.exception.ArtifactDeleteFailedException;
import org.eclipse.hawkbit.repository.exception.ArtifactUploadFailedException;

View File

@@ -1,50 +0,0 @@
/**
* Copyright (c) 2021 Bosch.IO GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.exception;
import java.io.Serial;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.eclipse.hawkbit.exception.AbstractServerRtException;
import org.eclipse.hawkbit.exception.SpServerError;
/**
* Exception being thrown in case of error while generating encryption secrets, encrypting or decrypting artifacts.
*/
@EqualsAndHashCode(callSuper = true)
public final class ArtifactEncryptionFailedException extends AbstractServerRtException {
@Serial
private static final long serialVersionUID = 1L;
@Getter
private final EncryptionOperation encryptionOperation;
public ArtifactEncryptionFailedException(final EncryptionOperation encryptionOperation) {
this(encryptionOperation, null, null);
}
public ArtifactEncryptionFailedException(final EncryptionOperation encryptionOperation, final String message) {
this(encryptionOperation, message, null);
}
public ArtifactEncryptionFailedException(final EncryptionOperation encryptionOperation, final String message, final Throwable cause) {
super(message, SpServerError.SP_ARTIFACT_ENCRYPTION_FAILED, cause);
this.encryptionOperation = encryptionOperation;
}
/**
* Encryption operation that caused the exception.
*/
public enum EncryptionOperation {
GENERATE_SECRETS, ENCRYPT, DECRYPT;
}
}

View File

@@ -1,38 +0,0 @@
/**
* Copyright (c) 2021 Bosch.IO GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.exception;
import java.io.Serial;
import org.eclipse.hawkbit.exception.AbstractServerRtException;
import org.eclipse.hawkbit.exception.SpServerError;
/**
* Exception being thrown when artifact encryption is not supported
*/
public final class ArtifactEncryptionUnsupportedException extends AbstractServerRtException {
@Serial
private static final long serialVersionUID = 1L;
/**
* Constructor.
*/
public ArtifactEncryptionUnsupportedException() {
super(SpServerError.SP_ARTIFACT_ENCRYPTION_NOT_SUPPORTED);
}
/**
* @param message of the error
*/
public ArtifactEncryptionUnsupportedException(final String message) {
super(message, SpServerError.SP_ARTIFACT_ENCRYPTION_NOT_SUPPORTED);
}
}

View File

@@ -9,7 +9,7 @@
*/
package org.eclipse.hawkbit.repository.model;
import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact;
import org.eclipse.hawkbit.repository.artifact.model.AbstractDbArtifact;
/**
* Binaries for a {@link SoftwareModule} Note: the decision which artifacts have

View File

@@ -1,42 +0,0 @@
/**
* Copyright (c) 2021 Bosch.IO GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import org.eclipse.hawkbit.repository.exception.ArtifactEncryptionUnsupportedException;
import org.junit.jupiter.api.Test;
/**
* Test class to verify that no {@link ArtifactEncryptionService} required beans
* are loaded and therefore the encryption support is not given.
* <p/>
* Feature: Unit Tests - Repository<br/>
* Story: Artifact Encryption Service
*/
class ArtifactEncryptionServiceTest {
/**
* Verify that no artifact encryption support is given
*/
@Test
void verifyNoArtifactEncryptionSupport() {
final ArtifactEncryptionService artifactEncryptionService = ArtifactEncryptionService.getInstance();
assertThat(artifactEncryptionService.isEncryptionSupported()).isFalse();
assertThatExceptionOfType(ArtifactEncryptionUnsupportedException.class)
.isThrownBy(() -> artifactEncryptionService.addSoftwareModuleEncryptionSecrets(1L));
assertThatExceptionOfType(ArtifactEncryptionUnsupportedException.class)
.isThrownBy(() -> artifactEncryptionService.encryptSoftwareModuleArtifact(1L, null));
assertThatExceptionOfType(ArtifactEncryptionUnsupportedException.class)
.isThrownBy(() -> artifactEncryptionService.decryptSoftwareModuleArtifact(1L, null));
}
}

View File

@@ -12,8 +12,8 @@ package org.eclipse.hawkbit.repository.jpa;
import java.io.InputStream;
import java.util.function.UnaryOperator;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.eclipse.hawkbit.repository.artifact.model.DbArtifact;
import org.eclipse.hawkbit.repository.artifact.model.DbArtifactHash;
/**
* {@link DbArtifact} implementation that decrypts the underlying artifact

View File

@@ -20,11 +20,11 @@ import jakarta.validation.Validation;
import io.micrometer.core.instrument.MeterRegistry;
import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
import org.eclipse.hawkbit.repository.artifact.ArtifactRepository;
import org.eclipse.hawkbit.cache.TenancyCacheManager;
import org.eclipse.hawkbit.repository.ArtifactEncryption;
import org.eclipse.hawkbit.repository.ArtifactEncryptionSecretsStore;
import org.eclipse.hawkbit.repository.ArtifactEncryptionService;
import org.eclipse.hawkbit.repository.artifact.encryption.ArtifactEncryption;
import org.eclipse.hawkbit.repository.artifact.encryption.ArtifactEncryptionSecretsStore;
import org.eclipse.hawkbit.repository.artifact.encryption.ArtifactEncryptionService;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.BaseRepositoryTypeProvider;
import org.eclipse.hawkbit.repository.ConfirmationManagement;
@@ -155,7 +155,6 @@ import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.helper.SystemSecurityContextHolder;
import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagementHolder;
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SystemSecurityContext;
@@ -1010,9 +1009,8 @@ public class RepositoryApplicationConfiguration {
}
/**
* Default artifact encryption service bean that internally uses
* {@link ArtifactEncryption} and {@link ArtifactEncryptionSecretsStore} beans
* for {@link SoftwareModule} artifacts encryption/decryption
* Default artifact encryption service bean that internally uses {@link ArtifactEncryption} and
* {@link ArtifactEncryptionSecretsStore} beans for {@link SoftwareModule} artifacts encryption/decryption
*
* @return a {@link ArtifactEncryptionService} bean
*/

View File

@@ -17,13 +17,13 @@ import jakarta.annotation.Nullable;
import jakarta.persistence.EntityManager;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
import org.eclipse.hawkbit.artifact.repository.ArtifactStoreException;
import org.eclipse.hawkbit.artifact.repository.HashNotMatchException;
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.repository.ArtifactEncryptionService;
import org.eclipse.hawkbit.repository.artifact.ArtifactRepository;
import org.eclipse.hawkbit.repository.artifact.exception.ArtifactStoreException;
import org.eclipse.hawkbit.repository.artifact.exception.HashNotMatchException;
import org.eclipse.hawkbit.repository.artifact.model.AbstractDbArtifact;
import org.eclipse.hawkbit.repository.artifact.model.DbArtifact;
import org.eclipse.hawkbit.repository.artifact.model.DbArtifactHash;
import org.eclipse.hawkbit.repository.artifact.encryption.ArtifactEncryptionService;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.exception.ArtifactDeleteFailedException;
@@ -275,7 +275,7 @@ public class JpaArtifactManagement implements ArtifactManagement {
}
private InputStream wrapInEncryptionStream(final long smId, final InputStream stream) {
return ArtifactEncryptionService.getInstance().encryptSoftwareModuleArtifact(smId, stream);
return ArtifactEncryptionService.getInstance().encryptArtifact(smId, stream);
}
private void assertArtifactQuota(final long moduleId, final int requested) {
@@ -303,7 +303,7 @@ public class JpaArtifactManagement implements ArtifactManagement {
}
final ArtifactEncryptionService encryptionService = ArtifactEncryptionService.getInstance();
return new EncryptionAwareDbArtifact(dbArtifact,
stream -> encryptionService.decryptSoftwareModuleArtifact(softwareModuleId, stream),
stream -> encryptionService.decryptArtifact(softwareModuleId, stream),
encryptionService.encryptionSizeOverhead());
}

View File

@@ -21,7 +21,7 @@ import java.util.stream.Collectors;
import jakarta.persistence.EntityManager;
import org.eclipse.hawkbit.repository.ArtifactEncryptionService;
import org.eclipse.hawkbit.repository.artifact.encryption.ArtifactEncryptionService;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
@@ -120,7 +120,7 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
entityManager.flush();
createdModules.stream().filter(SoftwareModule::isEncrypted).map(SoftwareModule::getId)
.forEach(encryptedModuleId -> ArtifactEncryptionService.getInstance()
.addSoftwareModuleEncryptionSecrets(encryptedModuleId));
.addEncryptionSecrets(encryptedModuleId));
}
return createdModules;
}
@@ -136,7 +136,7 @@ public class JpaSoftwareModuleManagement implements SoftwareModuleManagement {
if (create.isEncrypted()) {
// flush sm creation in order to get an Id
entityManager.flush();
ArtifactEncryptionService.getInstance().addSoftwareModuleEncryptionSecrets(sm.getId());
ArtifactEncryptionService.getInstance().addEncryptionSecrets(sm.getId());
}
return sm;

View File

@@ -14,7 +14,7 @@ import java.util.function.Consumer;
import jakarta.persistence.EntityManager;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
import org.eclipse.hawkbit.repository.artifact.ArtifactRepository;
import org.eclipse.hawkbit.cache.TenancyCacheManager;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.RolloutStatusCache;

View File

@@ -28,7 +28,7 @@ import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.eclipse.hawkbit.artifact.repository.model.AbstractDbArtifact;
import org.eclipse.hawkbit.repository.artifact.model.AbstractDbArtifact;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.SoftwareModule;

View File

@@ -27,8 +27,8 @@ import java.util.concurrent.Callable;
import jakarta.validation.ConstraintViolationException;
import org.apache.commons.io.IOUtils;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifact;
import org.eclipse.hawkbit.artifact.repository.model.DbArtifactHash;
import org.eclipse.hawkbit.repository.artifact.model.DbArtifact;
import org.eclipse.hawkbit.repository.artifact.model.DbArtifactHash;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent;

View File

@@ -17,11 +17,11 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.artifact.repository.ArtifactFilesystemProperties;
import org.eclipse.hawkbit.artifact.repository.ArtifactFilesystemRepository;
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
import org.eclipse.hawkbit.artifact.repository.urlhandler.ArtifactUrlHandlerProperties;
import org.eclipse.hawkbit.artifact.repository.urlhandler.PropertyBasedArtifactUrlHandler;
import org.eclipse.hawkbit.repository.artifact.ArtifactFilesystemProperties;
import org.eclipse.hawkbit.repository.artifact.ArtifactFilesystemRepository;
import org.eclipse.hawkbit.repository.artifact.ArtifactRepository;
import org.eclipse.hawkbit.repository.artifact.urlhandler.ArtifactUrlHandlerProperties;
import org.eclipse.hawkbit.repository.artifact.urlhandler.PropertyBasedArtifactUrlHandler;
import org.eclipse.hawkbit.cache.TenantAwareCacheManager;
import org.eclipse.hawkbit.event.BusProtoStuffMessageConverter;
import org.eclipse.hawkbit.im.authentication.SpRole;

View File

@@ -28,8 +28,8 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.awaitility.Awaitility;
import org.awaitility.core.ConditionFactory;
import org.eclipse.hawkbit.artifact.repository.ArtifactRepository;
import org.eclipse.hawkbit.artifact.repository.ArtifactStoreException;
import org.eclipse.hawkbit.repository.artifact.ArtifactRepository;
import org.eclipse.hawkbit.repository.artifact.exception.ArtifactStoreException;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.ConfirmationManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;