From 707fb6d83819058d5736b2dc38bea3c2d9e07a2d Mon Sep 17 00:00:00 2001 From: vasraz Date: Fri, 8 Oct 2021 14:48:08 +0100 Subject: [PATCH] Large csar handling - object store Change-Id: I4e88bd7bfcc1fdbc93d67da2682f2e873ba243c6 Signed-off-by: Vasyl Razinkov Issue-ID: SDC-3754 --- .../sdc/be/csar/storage/ArtifactInfo.java | 4 +- .../sdc/be/csar/storage/ArtifactStorageConfig.java | 1 + .../be/csar/storage/ArtifactStorageManager.java | 12 +- common-be/pom.xml | 478 +++++++++++---------- ...ageArtifactInfo.java => MinIoArtifactInfo.java} | 13 +- ...java => MinIoStorageArtifactStorageConfig.java} | 30 +- .../MinIoStorageArtifactStorageManager.java | 247 +++++++++++ ...ducer.java => MinIoStorageCsarSizeReducer.java} | 6 +- .../sdc/be/csar/storage/NoneStorageManager.java | 54 +++ .../PersistentVolumeArtifactStorageManager.java | 161 ------- .../sdc/be/csar/storage/StorageFactory.java | 91 ++++ ...xception.java => ArtifactStorageException.java} | 6 +- .../MinIoStorageArtifactStorageManagerTest.java | 120 ++++++ ...t.java => MinIoStorageCsarSizeReducerTest.java} | 12 +- .../be/csar/storage/NoneStorageManagerTest.java | 78 ++++ ...PersistentVolumeArtifactStorageManagerTest.java | 118 ----- .../csarSizeReducer/dummyToReduce-2-files.zip | Bin 0 -> 3154 bytes ...dummyToReduce.zip => dummyToReduce-3-files.zip} | Bin .../s3StoreArtifactStorageManager/dummy.csar | Bin 0 -> 25876 bytes .../OrchestrationTemplateCandidateImpl.java | 87 ++-- .../OrchestrationTemplateCandidateImplTest.java | 36 +- .../csar/validation/CsarSecurityValidator.java | 2 +- .../security/SecurityManager.java | 84 ++-- .../csar/validation/CsarSecurityValidatorTest.java | 104 ++++- .../security/SecurityManagerTest.java | 154 +++++-- .../templates/default/configuration.yaml.erb | 71 +-- .../org/openecomp/sdc/common/errors/Messages.java | 5 +- ...hestrationTemplateCandidateDaoZusammenImpl.java | 57 +-- 28 files changed, 1280 insertions(+), 751 deletions(-) rename common-be/src/main/java/org/openecomp/sdc/be/csar/storage/{PersistentStorageArtifactInfo.java => MinIoArtifactInfo.java} (78%) rename common-be/src/main/java/org/openecomp/sdc/be/csar/storage/{PersistentVolumeArtifactStorageConfig.java => MinIoStorageArtifactStorageConfig.java} (61%) create mode 100644 common-be/src/main/java/org/openecomp/sdc/be/csar/storage/MinIoStorageArtifactStorageManager.java rename common-be/src/main/java/org/openecomp/sdc/be/csar/storage/{CsarSizeReducer.java => MinIoStorageCsarSizeReducer.java} (98%) create mode 100644 common-be/src/main/java/org/openecomp/sdc/be/csar/storage/NoneStorageManager.java delete mode 100644 common-be/src/main/java/org/openecomp/sdc/be/csar/storage/PersistentVolumeArtifactStorageManager.java create mode 100644 common-be/src/main/java/org/openecomp/sdc/be/csar/storage/StorageFactory.java rename common-be/src/main/java/org/openecomp/sdc/be/csar/storage/exception/{PersistentVolumeArtifactStorageException.java => ArtifactStorageException.java} (80%) create mode 100644 common-be/src/test/java/org/openecomp/sdc/be/csar/storage/MinIoStorageArtifactStorageManagerTest.java rename common-be/src/test/java/org/openecomp/sdc/be/csar/storage/{CsarSizeReducerTest.java => MinIoStorageCsarSizeReducerTest.java} (93%) create mode 100644 common-be/src/test/java/org/openecomp/sdc/be/csar/storage/NoneStorageManagerTest.java delete mode 100644 common-be/src/test/java/org/openecomp/sdc/be/csar/storage/PersistentVolumeArtifactStorageManagerTest.java create mode 100644 common-be/src/test/resources/csarSizeReducer/dummyToReduce-2-files.zip rename common-be/src/test/resources/csarSizeReducer/{dummyToReduce.zip => dummyToReduce-3-files.zip} (100%) create mode 100644 common-be/src/test/resources/s3StoreArtifactStorageManager/dummy.csar diff --git a/common-app-api/src/main/java/org/openecomp/sdc/be/csar/storage/ArtifactInfo.java b/common-app-api/src/main/java/org/openecomp/sdc/be/csar/storage/ArtifactInfo.java index 75847704c8..91426537ef 100644 --- a/common-app-api/src/main/java/org/openecomp/sdc/be/csar/storage/ArtifactInfo.java +++ b/common-app-api/src/main/java/org/openecomp/sdc/be/csar/storage/ArtifactInfo.java @@ -20,13 +20,11 @@ package org.openecomp.sdc.be.csar.storage; -import java.nio.file.Path; - /** * Represents the stored artifact */ public interface ArtifactInfo { - Path getPath(); + String getInfo(); } diff --git a/common-app-api/src/main/java/org/openecomp/sdc/be/csar/storage/ArtifactStorageConfig.java b/common-app-api/src/main/java/org/openecomp/sdc/be/csar/storage/ArtifactStorageConfig.java index edac694933..0ad73c6590 100644 --- a/common-app-api/src/main/java/org/openecomp/sdc/be/csar/storage/ArtifactStorageConfig.java +++ b/common-app-api/src/main/java/org/openecomp/sdc/be/csar/storage/ArtifactStorageConfig.java @@ -25,4 +25,5 @@ package org.openecomp.sdc.be.csar.storage; */ public interface ArtifactStorageConfig { + String getTempPath(); } diff --git a/common-app-api/src/main/java/org/openecomp/sdc/be/csar/storage/ArtifactStorageManager.java b/common-app-api/src/main/java/org/openecomp/sdc/be/csar/storage/ArtifactStorageManager.java index da06db0e68..0a4f355642 100644 --- a/common-app-api/src/main/java/org/openecomp/sdc/be/csar/storage/ArtifactStorageManager.java +++ b/common-app-api/src/main/java/org/openecomp/sdc/be/csar/storage/ArtifactStorageManager.java @@ -52,6 +52,16 @@ public interface ArtifactStorageManager { * * @return {@code true} if enable, {@code false} otherwise */ - boolean isEnabled(); + default boolean isEnabled() { + return false; + } + /** + * @return Storage Configuration + */ + ArtifactStorageConfig getStorageConfiguration(); + + InputStream get(final ArtifactInfo artifactInfo); + + void delete(ArtifactInfo artifactInfo); } diff --git a/common-be/pom.xml b/common-be/pom.xml index af93628eb5..d84eec5c0c 100644 --- a/common-be/pom.xml +++ b/common-be/pom.xml @@ -1,156 +1,161 @@ - 4.0.0 - - org.openecomp.sdc.be - common-be - - - org.openecomp.sdc - sdc-main - 1.10.0-SNAPSHOT - - - - - - com.fasterxml.jackson.core - jackson-core - ${jackson.version} - - - - - org.openecomp.sdc - common-app-api - ${project.version} - - - com.fasterxml.jackson.core - jackson-core - - - commons-codec - commons-codec - - - - - - org.apache.commons - commons-lang3 - ${lang3.version} - provided - - - - ch.qos.logback - logback-classic - ${logback.version} - provided - - - - com.google.guava - guava - ${guava.version} - provided - - - org.functionaljava - functionaljava - ${functionaljava.version} - provided - - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - provided - - - com.fasterxml.jackson.core - jackson-core - - - - - - org.bouncycastle - bcpkix-jdk15on - ${bouncycastle.version} - compile - - - - org.hamcrest - hamcrest - ${hamcrest.version} - test - - - org.hamcrest - hamcrest-library - ${hamcrest.version} - test - - - org.junit.jupiter - junit-jupiter - ${junitJupiter.version} - test - - - org.mockito - mockito-junit-jupiter - ${mockitoJupiter.version} - test - - - org.onap.sdc.common - onap-tosca-datatype - ${tosca.datatype.version} - - - com.fasterxml.jackson.core - jackson-core - - - - - org.projectlombok - lombok - ${lombok.version} - - - com.google.code.bean-matchers - bean-matchers - ${bean-matchers.version} - test - - - - - org.togglz - togglz-core - ${togglz.version} - - - - - org.togglz - togglz-servlet - ${togglz.version} - - - - - org.togglz - togglz-console - ${togglz.version} - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + org.openecomp.sdc.be + common-be + + + org.openecomp.sdc + sdc-main + 1.10.0-SNAPSHOT + + + + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + io.minio + minio + 8.3.0 + + + + + org.openecomp.sdc + common-app-api + ${project.version} + + + com.fasterxml.jackson.core + jackson-core + + + commons-codec + commons-codec + + + + + + org.apache.commons + commons-lang3 + ${lang3.version} + provided + + + + ch.qos.logback + logback-classic + ${logback.version} + provided + + + + com.google.guava + guava + ${guava.version} + provided + + + org.functionaljava + functionaljava + ${functionaljava.version} + provided + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + provided + + + com.fasterxml.jackson.core + jackson-core + + + + + + org.bouncycastle + bcpkix-jdk15on + ${bouncycastle.version} + compile + + + + org.hamcrest + hamcrest + ${hamcrest.version} + test + + + org.hamcrest + hamcrest-library + ${hamcrest.version} + test + + + org.junit.jupiter + junit-jupiter + ${junitJupiter.version} + test + + + org.mockito + mockito-junit-jupiter + ${mockitoJupiter.version} + test + + + org.onap.sdc.common + onap-tosca-datatype + ${tosca.datatype.version} + + + com.fasterxml.jackson.core + jackson-core + + + + + org.projectlombok + lombok + ${lombok.version} + + + com.google.code.bean-matchers + bean-matchers + ${bean-matchers.version} + test + + + + + org.togglz + togglz-core + ${togglz.version} + + + + + org.togglz + togglz-servlet + ${togglz.version} + + + + + org.togglz + togglz-console + ${togglz.version} + @@ -159,87 +164,92 @@ ${togglz.version} test - - org.springframework - spring-context - ${spring.version} - compile - - - org.springframework - spring-expression - - - org.springframework - spring-core - - - - - org.springframework - spring-core - ${spring.version} - - - org.apache.cxf - cxf-rt-frontend-jaxrs - ${cxf.version} - - - org.jboss.spec.javax.rmi - jboss-rmi-api_1.0_spec - - - - - - - - - maven-jar-plugin - ${maven-jar-plugin.version} - - - default-jar - package - - jar - test-jar - - - - - - com.github.sylvainlaurent.maven - yaml-json-validator-maven-plugin - - - validate - validate - - validate - - - - - - src/main/resources/**/*.y*ml - src/test/resources/**/*.y*ml - - - - - src/main/resources/**/*.json - src/test/resources/**/*.json - - - - ${skipYamlJsonValidator} - - - - - - + + org.springframework + spring-context + ${spring.version} + compile + + + org.springframework + spring-expression + + + org.springframework + spring-core + + + + + org.springframework + spring-core + ${spring.version} + + + org.apache.cxf + cxf-rt-frontend-jaxrs + ${cxf.version} + + + org.jboss.spec.javax.rmi + jboss-rmi-api_1.0_spec + + + + + org.openecomp.sdc.core + openecomp-common-lib + ${project.version} + + + + + + + maven-jar-plugin + ${maven-jar-plugin.version} + + + default-jar + package + + jar + test-jar + + + + + + com.github.sylvainlaurent.maven + yaml-json-validator-maven-plugin + + + validate + validate + + validate + + + + + + src/main/resources/**/*.y*ml + src/test/resources/**/*.y*ml + + + + + src/main/resources/**/*.json + src/test/resources/**/*.json + + + + ${skipYamlJsonValidator} + + + + + + diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/PersistentStorageArtifactInfo.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/MinIoArtifactInfo.java similarity index 78% rename from common-be/src/main/java/org/openecomp/sdc/be/csar/storage/PersistentStorageArtifactInfo.java rename to common-be/src/main/java/org/openecomp/sdc/be/csar/storage/MinIoArtifactInfo.java index 0472661fd9..a193cdd6db 100644 --- a/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/PersistentStorageArtifactInfo.java +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/MinIoArtifactInfo.java @@ -20,14 +20,19 @@ package org.openecomp.sdc.be.csar.storage; -import java.nio.file.Path; import lombok.AllArgsConstructor; import lombok.Getter; @AllArgsConstructor -public class PersistentStorageArtifactInfo implements ArtifactInfo { +@Getter +public class MinIoArtifactInfo implements ArtifactInfo { - @Getter - private final Path path; + private final String bucket; + private final String objectName; + @Override + public String getInfo() { + return String.format("bucket: %s\n" + + "object: %s", bucket, objectName); + } } diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/PersistentVolumeArtifactStorageConfig.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/MinIoStorageArtifactStorageConfig.java similarity index 61% rename from common-be/src/main/java/org/openecomp/sdc/be/csar/storage/PersistentVolumeArtifactStorageConfig.java rename to common-be/src/main/java/org/openecomp/sdc/be/csar/storage/MinIoStorageArtifactStorageConfig.java index d3cd6fb302..6f6778f2b0 100644 --- a/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/PersistentVolumeArtifactStorageConfig.java +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/MinIoStorageArtifactStorageConfig.java @@ -20,13 +20,33 @@ package org.openecomp.sdc.be.csar.storage; -import java.nio.file.Path; -import lombok.Data; +import lombok.AllArgsConstructor; +import lombok.Getter; -@Data -public class PersistentVolumeArtifactStorageConfig implements ArtifactStorageConfig { +@AllArgsConstructor +@Getter +public class MinIoStorageArtifactStorageConfig implements ArtifactStorageConfig { private final boolean isEnabled; - private final Path storagePath; + private final EndPoint endPoint; + private final Credentials credentials; + private final String tempPath; + + @AllArgsConstructor + @Getter + public static class EndPoint { + + private final String host; + private final int port; + private final boolean secure; + } + + @AllArgsConstructor + @Getter + public static class Credentials { + + private final String accessKey; + private final String secretKey; + } } diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/MinIoStorageArtifactStorageManager.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/MinIoStorageArtifactStorageManager.java new file mode 100644 index 0000000000..0a48c2233c --- /dev/null +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/MinIoStorageArtifactStorageManager.java @@ -0,0 +1,247 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.storage; + +import static org.openecomp.sdc.common.errors.Messages.EXTERNAL_CSAR_STORE_CONFIGURATION_FAILURE_MISSING; + +import io.minio.BucketExistsArgs; +import io.minio.CopyObjectArgs; +import io.minio.CopySource; +import io.minio.GetObjectArgs; +import io.minio.MakeBucketArgs; +import io.minio.MinioClient; +import io.minio.MinioClient.Builder; +import io.minio.PutObjectArgs; +import io.minio.RemoveObjectArgs; +import java.io.InputStream; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import lombok.Getter; +import org.openecomp.sdc.be.csar.storage.MinIoStorageArtifactStorageConfig.Credentials; +import org.openecomp.sdc.be.csar.storage.MinIoStorageArtifactStorageConfig.EndPoint; +import org.openecomp.sdc.be.csar.storage.exception.ArtifactStorageException; +import org.openecomp.sdc.common.CommonConfigurationManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MinIoStorageArtifactStorageManager implements ArtifactStorageManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(MinIoStorageArtifactStorageManager.class); + private static final String EXTERNAL_CSAR_STORE = "externalCsarStore"; + + @Getter + private final MinIoStorageArtifactStorageConfig storageConfiguration; + private final MinioClient minioClient; + + public MinIoStorageArtifactStorageManager() { + this.storageConfiguration = readMinIoStorageArtifactStorageConfig(); + minioClient = initMinioClient(); + } + + //for testing only + MinIoStorageArtifactStorageManager(final ArtifactStorageConfig storageConfiguration) { + this.storageConfiguration = (MinIoStorageArtifactStorageConfig) storageConfiguration; + minioClient = initMinioClient(); + } + + @Override + public ArtifactInfo persist(final String vspId, final String versionId, final ArtifactInfo uploadedArtifactInfo) { + final MinIoArtifactInfo minioObjectTemp = (MinIoArtifactInfo) uploadedArtifactInfo; + try { + minioClient.getObject( + GetObjectArgs.builder() + .bucket(minioObjectTemp.getBucket()) + .object(minioObjectTemp.getObjectName()) + .build() + ); + } catch (final Exception e) { + throw new ArtifactStorageException( + String.format("Failed to retrieve uploaded artifact with bucket '%s' and name '%s' while persisting", + minioObjectTemp.getBucket(), minioObjectTemp.getObjectName()), e); + } + + final var backupPath = backupPreviousVersion(vspId, versionId).orElse(null); + try { + moveFile(minioObjectTemp, vspId, versionId); + } catch (final Exception e) { + rollback(minioObjectTemp, vspId, versionId); + final var errorMsg = String.format("Could not persist artifact for VSP '%s', version '%s'", vspId, versionId); + throw new ArtifactStorageException(errorMsg, e); + } + + removePreviousVersion(backupPath); + + return new MinIoArtifactInfo(vspId, versionId); + } + + @Override + public ArtifactInfo upload(final String vspId, final String versionId, final InputStream fileToUpload) { + + final String name = versionId + "--" + UUID.randomUUID(); + try { + // Make bucket if not exist. + final boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(vspId).build()); + + if (!found) { + // Make a new bucket ${vspId} . + minioClient.makeBucket(MakeBucketArgs.builder().bucket(vspId).build()); + } else { + LOGGER.info("Bucket '{}' already exists.", vspId); + } + + minioClient.putObject( + PutObjectArgs.builder() + .bucket(vspId) + .object(name) + .stream(fileToUpload, fileToUpload.available(), -1) + .build() + ); + + } catch (final Exception e) { + throw new ArtifactStorageException("Failed to upload artifact", e); + } + + return new MinIoArtifactInfo(vspId, name); + } + + @Override + public boolean isEnabled() { + return storageConfiguration != null && storageConfiguration.isEnabled(); + } + + @Override + public InputStream get(final ArtifactInfo artifactInfo) { + final MinIoArtifactInfo minioObject = (MinIoArtifactInfo) artifactInfo; + try { + return minioClient.getObject(GetObjectArgs.builder() + .bucket(minioObject.getBucket()) + .object(minioObject.getObjectName()) + .build()); + } catch (final Exception e) { + throw new ArtifactStorageException("Failed to get Object", e); + } + } + + @Override + public void delete(final ArtifactInfo artifactInfo) { + final MinIoArtifactInfo minioObject = (MinIoArtifactInfo) artifactInfo; + try { + minioClient.removeObject(RemoveObjectArgs.builder() + .bucket(minioObject.getBucket()) + .object(minioObject.getObjectName()) + .bypassGovernanceMode(true) + .build()); + } catch (final Exception e) { + throw new ArtifactStorageException(String.format("Failed to delete '%s'", minioObject.getObjectName()), e); + } + + } + + private Optional backupPreviousVersion(final String vspId, final String versionId) { + + final String tempName = versionId + "--" + UUID.randomUUID().toString(); + try { + copy(vspId, tempName, versionId); + } catch (final Exception e) { + return Optional.empty(); + } + + return Optional.of(new MinIoArtifactInfo(vspId, tempName)); + } + + private void rollback(final MinIoArtifactInfo minioObject, final String vspId, final String versionId) { + try { + moveFile(minioObject, vspId, versionId); + } catch (final Exception ex) { + LOGGER.warn("Could not rollback the backup '{}' to the original '{}'", versionId, minioObject.getObjectName(), ex); + } + } + + private void removePreviousVersion(final MinIoArtifactInfo minioObject) { + if (minioObject == null) { + return; + } + delete(minioObject); + } + + private void moveFile(final MinIoArtifactInfo minioObject, final String vspId, final String versionId) { + try { + copy(vspId, versionId, minioObject.getObjectName()); + } catch (final Exception e) { + throw new ArtifactStorageException("Failed to move", e); + } + delete(minioObject); + } + + private void copy(final String vspId, final String versionId, final String objectName) throws Exception { + minioClient.copyObject( + CopyObjectArgs.builder() + .bucket(vspId) + .object(versionId) + .source(CopySource.builder() + .bucket(vspId) + .object(objectName) + .build()) + .build()); + } + + private MinIoStorageArtifactStorageConfig readMinIoStorageArtifactStorageConfig() { + final var commonConfigurationManager = CommonConfigurationManager.getInstance(); + + final Map endpoint = commonConfigurationManager.getConfigValue(EXTERNAL_CSAR_STORE, "endpoint", null); + final Map credentials = commonConfigurationManager.getConfigValue(EXTERNAL_CSAR_STORE, "credentials", null); + final String tempPath = commonConfigurationManager.getConfigValue(EXTERNAL_CSAR_STORE, "tempPath", null); + LOGGER.info("ArtifactConfig.endpoint: '{}'", endpoint); + LOGGER.info("ArtifactConfig.credentials: '{}'", credentials); + LOGGER.info("ArtifactConfig.tempPath: '{}'", tempPath); + + if (endpoint == null) { + throw new RuntimeException(EXTERNAL_CSAR_STORE_CONFIGURATION_FAILURE_MISSING.formatMessage("endpoint")); + } + if (credentials == null) { + throw new RuntimeException(EXTERNAL_CSAR_STORE_CONFIGURATION_FAILURE_MISSING.formatMessage("credentials")); + } + if (tempPath == null) { + throw new RuntimeException(EXTERNAL_CSAR_STORE_CONFIGURATION_FAILURE_MISSING.formatMessage("tempPath")); + } + final String host = (String) endpoint.getOrDefault("host", null); + final int port = (int) endpoint.getOrDefault("port", 0); + final boolean secure = (boolean) endpoint.getOrDefault("secure", false); + + final String accessKey = (String) credentials.getOrDefault("accessKey", null); + final String secretKey = (String) credentials.getOrDefault("secretKey", null); + + return new MinIoStorageArtifactStorageConfig(true, new EndPoint(host, port, secure), new Credentials(accessKey, secretKey), tempPath); + } + + private MinioClient initMinioClient() { + final EndPoint endPoint = storageConfiguration.getEndPoint(); + final Credentials credentials = storageConfiguration.getCredentials(); + + final Builder builder = MinioClient.builder(); + return builder + .endpoint(endPoint.getHost(), endPoint.getPort(), endPoint.isSecure()) + .credentials(credentials.getAccessKey(), credentials.getSecretKey()) + .build(); + } + +} diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducer.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/MinIoStorageCsarSizeReducer.java similarity index 98% rename from common-be/src/main/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducer.java rename to common-be/src/main/java/org/openecomp/sdc/be/csar/storage/MinIoStorageCsarSizeReducer.java index 822acc0766..3181b088c0 100644 --- a/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducer.java +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/MinIoStorageCsarSizeReducer.java @@ -43,9 +43,9 @@ import org.openecomp.sdc.be.csar.storage.exception.CsarSizeReducerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class CsarSizeReducer implements PackageSizeReducer { +public class MinIoStorageCsarSizeReducer implements PackageSizeReducer { - private static final Logger LOGGER = LoggerFactory.getLogger(CsarSizeReducer.class); + private static final Logger LOGGER = LoggerFactory.getLogger(MinIoStorageCsarSizeReducer.class); private static final Set ALLOWED_SIGNATURE_EXTENSIONS = Set.of("cms"); private static final Set ALLOWED_CERTIFICATE_EXTENSIONS = Set.of("cert", "crt"); private static final String CSAR_EXTENSION = "csar"; @@ -55,7 +55,7 @@ public class CsarSizeReducer implements PackageSizeReducer { private final CsarPackageReducerConfiguration configuration; - public CsarSizeReducer(final CsarPackageReducerConfiguration configuration) { + public MinIoStorageCsarSizeReducer(final CsarPackageReducerConfiguration configuration) { this.configuration = configuration; } diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/NoneStorageManager.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/NoneStorageManager.java new file mode 100644 index 0000000000..3fa22d41be --- /dev/null +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/NoneStorageManager.java @@ -0,0 +1,54 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.storage; + +import java.io.InputStream; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class NoneStorageManager implements ArtifactStorageManager { + + @Override + public ArtifactInfo persist(final String vspId, final String versionId, final ArtifactInfo uploadedArtifactInfo) { + throw new UnsupportedOperationException(); + } + + @Override + public ArtifactInfo upload(final String vspId, final String versionId, final InputStream fileToUpload) { + throw new UnsupportedOperationException(); + } + + @Override + public ArtifactStorageConfig getStorageConfiguration() { + throw new UnsupportedOperationException(); + } + + @Override + public InputStream get(final ArtifactInfo artifactInfo) { + throw new UnsupportedOperationException(); + } + + @Override + public void delete(final ArtifactInfo artifactInfo) { + throw new UnsupportedOperationException(); + } + +} diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/PersistentVolumeArtifactStorageManager.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/PersistentVolumeArtifactStorageManager.java deleted file mode 100644 index 10629b3edb..0000000000 --- a/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/PersistentVolumeArtifactStorageManager.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.openecomp.sdc.be.csar.storage; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.util.Optional; -import java.util.UUID; -import org.openecomp.sdc.be.csar.storage.exception.PersistentVolumeArtifactStorageException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class PersistentVolumeArtifactStorageManager implements ArtifactStorageManager { - - private static final Logger LOGGER = LoggerFactory.getLogger(PersistentVolumeArtifactStorageManager.class); - - private final PersistentVolumeArtifactStorageConfig storageConfiguration; - - public PersistentVolumeArtifactStorageManager(final ArtifactStorageConfig storageConfiguration) { - this.storageConfiguration = (PersistentVolumeArtifactStorageConfig) storageConfiguration; - } - - @Override - public ArtifactInfo persist(final String vspId, final String versionId, final ArtifactInfo uploadedArtifactInfo) { - final var temporaryPath = uploadedArtifactInfo.getPath(); - if (!Files.exists(temporaryPath)) { - throw new PersistentVolumeArtifactStorageException(String.format("Given artifact does not exist '%s'", uploadedArtifactInfo.getPath())); - } - - final var filePath = buildFilePath(vspId, versionId); - final var backupPath = backupPreviousVersion(filePath).orElse(null); - try { - moveFile(temporaryPath, filePath); - } catch (final Exception e) { - rollback(backupPath, filePath); - final var errorMsg = String.format("Could not persist artifact for VSP '%s', version '%s'", vspId, versionId); - throw new PersistentVolumeArtifactStorageException(errorMsg, e); - } - - removePreviousVersion(backupPath); - - return new PersistentStorageArtifactInfo(filePath); - } - - @Override - public ArtifactInfo upload(final String vspId, final String versionId, final InputStream artifactInputStream) { - final var destinationFolder = buildDestinationFolder(vspId, versionId); - try { - Files.createDirectories(destinationFolder); - } catch (final IOException e) { - throw new PersistentVolumeArtifactStorageException(String.format("Could not create directory '%s'", destinationFolder), e); - } - - final var filePath = createTempFilePath(destinationFolder); - try { - persist(artifactInputStream, filePath); - } catch (final IOException e) { - throw new PersistentVolumeArtifactStorageException(String.format("Could not persist artifact '%s'", filePath), e); - } - - return new PersistentStorageArtifactInfo(filePath); - } - - private Path buildFilePath(final String vspId, final String versionId) { - return buildDestinationFolder(vspId, versionId).resolve(versionId); - } - - @Override - public boolean isEnabled() { - return storageConfiguration != null && storageConfiguration.isEnabled(); - } - - private Optional backupPreviousVersion(final Path filePath) { - if (!Files.exists(filePath)) { - return Optional.empty(); - } - - final var backupPath = Path.of(filePath + UUID.randomUUID().toString()); - moveFile(filePath, backupPath); - return Optional.ofNullable(backupPath); - } - - private void rollback(final Path backupPath, final Path filePath) { - try { - moveFile(backupPath, filePath); - } catch (final Exception ex) { - LOGGER.warn("Could not rollback the backup file '{}' to the original '{}'", backupPath, filePath, ex); - } - } - - private void removePreviousVersion(final Path filePath) { - if (filePath == null || !Files.exists(filePath)) { - return; - } - - try { - Files.delete(filePath); - } catch (final IOException e) { - throw new PersistentVolumeArtifactStorageException(String.format("Could not delete previous version '%s'", filePath), e); - } - } - - private Path createTempFilePath(final Path destinationFolder) { - final var retries = 10; - return createTempFilePath(destinationFolder, retries).orElseThrow(() -> { - throw new PersistentVolumeArtifactStorageException(String.format("Could not generate upload file path after '%s' retries", retries)); - }); - } - - private Optional createTempFilePath(final Path destinationFolder, int retries) { - for (var i = 0; i < retries; i++) { - final var filePath = destinationFolder.resolve(UUID.randomUUID().toString()); - if (Files.notExists(filePath)) { - return Optional.of(filePath); - } - } - return Optional.empty(); - } - - private Path buildDestinationFolder(final String vspId, final String versionId) { - return storageConfiguration.getStoragePath().resolve(vspId).resolve(versionId); - } - - private void persist(final InputStream artifactInputStream, final Path filePath) throws IOException { - try (final var inputStream = artifactInputStream; - final var fileOutputStream = new FileOutputStream(filePath.toFile());) { - inputStream.transferTo(fileOutputStream); - } - } - - private void moveFile(final Path from, final Path to) { - try { - Files.move(from, to, StandardCopyOption.REPLACE_EXISTING); - } catch (final IOException e) { - throw new PersistentVolumeArtifactStorageException(String.format("Could not move file '%s' to '%s'", from, to), e); - } - } - -} diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/StorageFactory.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/StorageFactory.java new file mode 100644 index 0000000000..d120b3af8d --- /dev/null +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/StorageFactory.java @@ -0,0 +1,91 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.storage; + +import static org.openecomp.sdc.be.csar.storage.StorageFactory.StorageType.NONE; +import static org.openecomp.sdc.be.csar.storage.StorageFactory.StorageType.findByName; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.NoArgsConstructor; +import org.openecomp.sdc.common.CommonConfigurationManager; +import org.openecomp.sdc.logging.api.Logger; +import org.openecomp.sdc.logging.api.LoggerFactory; + +@NoArgsConstructor +public class StorageFactory { + + private static final Logger LOGGER = LoggerFactory.getLogger(StorageFactory.class); + private static final String EXTERNAL_CSAR_STORE = "externalCsarStore"; + + public ArtifactStorageManager createArtifactStorageManager() { + switch (getConfiguredArtifactStorageType()) { + case MINIO: // MinIoStorage enabled + return new MinIoStorageArtifactStorageManager(); + default:// all configured, nothing enabled + return new NoneStorageManager(); + } + } + + public Optional createPackageSizeReducer() { + switch (getConfiguredArtifactStorageType()) { + case MINIO: // MinIoStorage enabled + return Optional.of(new MinIoStorageCsarSizeReducer(readPackageReducerConfiguration())); + default:// all configured, nothing enabled + return Optional.empty(); + } + } + + private StorageType getConfiguredArtifactStorageType() { + final var commonConfigurationManager = CommonConfigurationManager.getInstance(); + final String storageType = commonConfigurationManager.getConfigValue(EXTERNAL_CSAR_STORE, "storageType", NONE.name()); + LOGGER.info("ArtifactConfig.storageType: '{}'", storageType); + return findByName(storageType); + } + + private CsarPackageReducerConfiguration readPackageReducerConfiguration() { + final var commonConfigurationManager = CommonConfigurationManager.getInstance(); + final List foldersToStrip = commonConfigurationManager.getConfigValue(EXTERNAL_CSAR_STORE, "foldersToStrip", new ArrayList<>()); + final int sizeLimit = commonConfigurationManager.getConfigValue(EXTERNAL_CSAR_STORE, "sizeLimit", 1000000); + final int thresholdEntries = commonConfigurationManager.getConfigValue(EXTERNAL_CSAR_STORE, "thresholdEntries", 10000); + LOGGER.info("Folders to strip: '{}'", String.join(", ", foldersToStrip)); + final Set foldersToStripPathSet = foldersToStrip.stream().map(Path::of).collect(Collectors.toSet()); + return new CsarPackageReducerConfiguration(foldersToStripPathSet, sizeLimit, thresholdEntries); + } + + public enum StorageType { + NONE, + MINIO; + + public static StorageType findByName(String name) { + for (StorageType curr : StorageType.values()) { + if (curr.name().equals(name)) { + return curr; + } + } + return null; + } + } +} diff --git a/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/exception/PersistentVolumeArtifactStorageException.java b/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/exception/ArtifactStorageException.java similarity index 80% rename from common-be/src/main/java/org/openecomp/sdc/be/csar/storage/exception/PersistentVolumeArtifactStorageException.java rename to common-be/src/main/java/org/openecomp/sdc/be/csar/storage/exception/ArtifactStorageException.java index 28fff65bb6..aa621611df 100644 --- a/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/exception/PersistentVolumeArtifactStorageException.java +++ b/common-be/src/main/java/org/openecomp/sdc/be/csar/storage/exception/ArtifactStorageException.java @@ -22,13 +22,13 @@ package org.openecomp.sdc.be.csar.storage.exception; import org.openecomp.sdc.be.exception.BusinessException; -public class PersistentVolumeArtifactStorageException extends BusinessException { +public class ArtifactStorageException extends BusinessException { - public PersistentVolumeArtifactStorageException(final String message, final Throwable cause) { + public ArtifactStorageException(final String message, final Throwable cause) { super(message, cause); } - public PersistentVolumeArtifactStorageException(final String message) { + public ArtifactStorageException(final String message) { super(message); } } diff --git a/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/MinIoStorageArtifactStorageManagerTest.java b/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/MinIoStorageArtifactStorageManagerTest.java new file mode 100644 index 0000000000..41eed0cebb --- /dev/null +++ b/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/MinIoStorageArtifactStorageManagerTest.java @@ -0,0 +1,120 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.storage; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +import io.minio.BucketExistsArgs; +import io.minio.MinioClient; +import java.io.IOException; +import java.io.InputStream; +import javax.activation.DataHandler; +import org.apache.cxf.jaxrs.ext.multipart.Attachment; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openecomp.sdc.be.csar.storage.MinIoStorageArtifactStorageConfig.Credentials; +import org.openecomp.sdc.be.csar.storage.MinIoStorageArtifactStorageConfig.EndPoint; + +@ExtendWith(MockitoExtension.class) +class MinIoStorageArtifactStorageManagerTest { + + public static final String VSP_ID = "vsp-id"; + public static final String VERSION_ID = "version-id"; + private MinIoStorageArtifactStorageManager testSubject; + @Mock + private MinioClient minioClient; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private MinioClient.Builder builderMinio; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private BucketExistsArgs.Builder builderBucketExistsArgs; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + + try (MockedStatic utilities = Mockito.mockStatic(MinioClient.class)) { + utilities.when(MinioClient::builder).thenReturn(builderMinio); + when(builderMinio + .endpoint(anyString(), anyInt(), anyBoolean()) + .credentials(anyString(), anyString()) + .build() + ).thenReturn(minioClient); + + testSubject = new MinIoStorageArtifactStorageManager( + new MinIoStorageArtifactStorageConfig(true, new EndPoint("host", 9000, false), new Credentials("accessKey", "secretKey"), "")); + } + } + + @Test + void testUpload() throws Exception { + + when(builderBucketExistsArgs + .bucket(anyString()) + .build() + ).thenReturn(new BucketExistsArgs()); + when(minioClient.bucketExists(any(BucketExistsArgs.class))).thenReturn(true); + + final Attachment attachment = mockAttachment(); + final ArtifactInfo result = testSubject.upload(VSP_ID, VERSION_ID, attachment.getDataHandler().getInputStream()); + Assertions.assertNotNull(result); + Assertions.assertTrue(result instanceof MinIoArtifactInfo); + Assertions.assertEquals(VSP_ID, ((MinIoArtifactInfo) result).getBucket()); + Assertions.assertTrue(((MinIoArtifactInfo) result).getObjectName().startsWith(VERSION_ID + "--")); + } + + @Test + void testPersist() { + final ArtifactInfo result = testSubject.persist(VSP_ID, VERSION_ID, new MinIoArtifactInfo(VSP_ID, VERSION_ID)); + Assertions.assertNotNull(result); + Assertions.assertTrue(result instanceof MinIoArtifactInfo); + Assertions.assertEquals(VSP_ID, ((MinIoArtifactInfo) result).getBucket()); + Assertions.assertEquals(VERSION_ID, ((MinIoArtifactInfo) result).getObjectName()); + } + + @Test + void testIsEnabled() { + Assertions.assertTrue(testSubject.isEnabled()); + } + + private Attachment mockAttachment() throws IOException { + final Attachment attachment = Mockito.mock(Attachment.class); + final DataHandler dataHandler = Mockito.mock(DataHandler.class); + final InputStream inputStream = Mockito.mock(InputStream.class); + when(dataHandler.getInputStream()).thenReturn(inputStream); + when(attachment.getDataHandler()).thenReturn(dataHandler); + return attachment; + } + +} diff --git a/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducerTest.java b/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/MinIoStorageCsarSizeReducerTest.java similarity index 93% rename from common-be/src/test/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducerTest.java rename to common-be/src/test/java/org/openecomp/sdc/be/csar/storage/MinIoStorageCsarSizeReducerTest.java index e9748f0a16..6515c6fb1e 100644 --- a/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/CsarSizeReducerTest.java +++ b/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/MinIoStorageCsarSizeReducerTest.java @@ -42,12 +42,12 @@ import org.mockito.MockitoAnnotations; import org.openecomp.sdc.common.zip.ZipUtils; import org.openecomp.sdc.common.zip.exception.ZipException; -class CsarSizeReducerTest { +class MinIoStorageCsarSizeReducerTest { @Mock private CsarPackageReducerConfiguration csarPackageReducerConfiguration; @InjectMocks - private CsarSizeReducer csarSizeReducer; + private MinIoStorageCsarSizeReducer minIoStorageCsarSizeReducer; @BeforeEach void setUp() { @@ -55,7 +55,7 @@ class CsarSizeReducerTest { } @ParameterizedTest - @ValueSource(strings = {"dummyToReduce.zip", "dummyToReduce.csar", "dummyToNotReduce.csar"}) + @ValueSource(strings = {"dummyToReduce-3-files.zip", "dummyToReduce.csar", "dummyToNotReduce.csar", "dummyToReduce-2-files.zip"}) void reduceByPathAndSizeTest(String fileName) throws ZipException { final var pathToReduce1 = Path.of("Files/images"); final var pathToReduce2 = Path.of("Files/Scripts/my_script.sh"); @@ -68,7 +68,7 @@ class CsarSizeReducerTest { final Map originalCsar = ZipUtils.readZip(csarPath.toFile(), false); - final byte[] reduce = csarSizeReducer.reduce(csarPath); + final byte[] reduce = minIoStorageCsarSizeReducer.reduce(csarPath); final Map reducedCsar = ZipUtils.readZip(reduce, false); @@ -113,7 +113,7 @@ class CsarSizeReducerTest { assertArrayEquals("".getBytes(StandardCharsets.UTF_8), reducedCsar.get(originalFilePath), String.format("File '%s' expected to be reduced to empty string", originalFilePath)); } else { - if (originalFilePath.endsWith(".csar") && csarSizeReducer.getReduced().get()) { + if (originalFilePath.endsWith(".csar") && minIoStorageCsarSizeReducer.getReduced().get()) { assertNotEquals(originalBytes.length, reducedCsar.get(originalFilePath).length, String.format("File '%s' expected to be NOT equal", originalFilePath)); } else { @@ -122,4 +122,4 @@ class CsarSizeReducerTest { } } } -} +} \ No newline at end of file diff --git a/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/NoneStorageManagerTest.java b/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/NoneStorageManagerTest.java new file mode 100644 index 0000000000..58394b9b76 --- /dev/null +++ b/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/NoneStorageManagerTest.java @@ -0,0 +1,78 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.openecomp.sdc.be.csar.storage; + +import java.io.ByteArrayInputStream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class NoneStorageManagerTest { + + public static final MinIoArtifactInfo UPLOADED_ARTIFACT_INFO = new MinIoArtifactInfo("bucket", "object"); + private NoneStorageManager testSubject; + + @BeforeEach + void setUp() { + testSubject = new NoneStorageManager(); + } + + @Test + void testCtor() { + Assertions.assertTrue(testSubject instanceof NoneStorageManager); + } + + @Test + void testPersist() { + Assertions.assertThrows(UnsupportedOperationException.class, + () -> testSubject.persist("vspId", "versionId", UPLOADED_ARTIFACT_INFO)); + } + + @Test + void testUpload() { + Assertions.assertThrows(UnsupportedOperationException.class, + () -> testSubject.upload("vspId", "versionId", new ByteArrayInputStream(new byte[0]))); + } + + @Test + void testGetStorageConfiguration() { + Assertions.assertThrows(UnsupportedOperationException.class, () -> testSubject.getStorageConfiguration()); + } + + @Test + void testGet() { + Assertions.assertThrows(UnsupportedOperationException.class, () -> testSubject.get(UPLOADED_ARTIFACT_INFO)); + } + + @Test + void testDelete() { + Assertions.assertThrows(UnsupportedOperationException.class, () -> testSubject.delete(UPLOADED_ARTIFACT_INFO)); + } + + @Test + void testIsEnabled() { + Assertions.assertFalse(testSubject.isEnabled()); + } + +} diff --git a/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/PersistentVolumeArtifactStorageManagerTest.java b/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/PersistentVolumeArtifactStorageManagerTest.java deleted file mode 100644 index ab8c11c7c1..0000000000 --- a/common-be/src/test/java/org/openecomp/sdc/be/csar/storage/PersistentVolumeArtifactStorageManagerTest.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021 Nordix Foundation - * ================================================================================ - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - * ============LICENSE_END========================================================= - */ - -package org.openecomp.sdc.be.csar.storage; - -import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.Mockito.when; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Objects; -import javax.activation.DataHandler; -import org.apache.commons.io.IOUtils; -import org.apache.cxf.jaxrs.ext.multipart.Attachment; -import org.apache.cxf.jaxrs.ext.multipart.ContentDisposition; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.mockito.ArgumentMatchers; -import org.mockito.Mockito; - -@TestMethodOrder(OrderAnnotation.class) -class PersistentVolumeArtifactStorageManagerTest { - - private static final String SRC_TEST_RESOURCES = "src/test/resources/"; - - private PersistentVolumeArtifactStorageManager testSubject; - - @BeforeEach - void setUp() { - testSubject = new PersistentVolumeArtifactStorageManager(new PersistentVolumeArtifactStorageConfig(true, Path.of(SRC_TEST_RESOURCES))); - } - - @AfterAll - static void tearDown() throws IOException { - Files.move(Path.of(SRC_TEST_RESOURCES + "vspId/versionId/versionId"), - Path.of(SRC_TEST_RESOURCES + "persistentVolumeArtifactStorageManager/dummy.csar")); - Files.list(Path.of("src/test/resources/vspId/versionId/")).forEach(path -> { - try { - Files.deleteIfExists(path); - } catch (IOException e) { - e.printStackTrace(); - } - }); - Files.deleteIfExists(Path.of(SRC_TEST_RESOURCES + "vspId/versionId/")); - Files.deleteIfExists(Path.of(SRC_TEST_RESOURCES + "vspId/")); - } - - @Test - @Order(1) - void testUpload() throws IOException { - final Attachment attachment = mockAttachment("dummy.csar", this.getClass().getResource("/persistentVolumeArtifactStorageManager/dummy.csar")); - final ArtifactInfo result = testSubject.upload("vspId", "versionId", attachment.getDataHandler().getInputStream()); - Assertions.assertNotNull(result); - Assertions.assertNotNull(result.getPath()); - Assertions.assertTrue(result.getPath().startsWith(Path.of(SRC_TEST_RESOURCES + "vspId/versionId/"))); - } - - @Test - @Order(2) - void testPersist() { - final ArtifactInfo result = testSubject.persist("vspId", "versionId", - new PersistentStorageArtifactInfo(Path.of(SRC_TEST_RESOURCES + "persistentVolumeArtifactStorageManager/dummy.csar"))); - Assertions.assertNotNull(result); - Assertions.assertNotNull(result.getPath()); - Assertions.assertTrue(result.getPath().startsWith(Path.of(SRC_TEST_RESOURCES + "vspId/versionId/"))); - } - - @Test - void testIsEnabled() { - Assertions.assertTrue(testSubject.isEnabled()); - } - - private Attachment mockAttachment(final String fileName, final URL fileToUpload) throws IOException { - final Attachment attachment = Mockito.mock(Attachment.class); - when(attachment.getContentDisposition()).thenReturn(new ContentDisposition("test")); - final DataHandler dataHandler = Mockito.mock(DataHandler.class); - when(dataHandler.getName()).thenReturn(fileName); - final InputStream inputStream = Mockito.mock(InputStream.class); - when(dataHandler.getInputStream()).thenReturn(inputStream); - when(attachment.getDataHandler()).thenReturn(dataHandler); - byte[] bytes = "upload package Test".getBytes(); - if (Objects.nonNull(fileToUpload)) { - try { - bytes = IOUtils.toByteArray(fileToUpload); - } catch (final IOException e) { - fail("Not able to convert file to byte array"); - } - } - when(attachment.getObject(ArgumentMatchers.any())).thenReturn(bytes); - return attachment; - } - -} diff --git a/common-be/src/test/resources/csarSizeReducer/dummyToReduce-2-files.zip b/common-be/src/test/resources/csarSizeReducer/dummyToReduce-2-files.zip new file mode 100644 index 0000000000000000000000000000000000000000..be48e8a6743b34adfa82718bb67ee464ceda8b86 GIT binary patch literal 3154 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 literal 0 HcmV?d00001 diff --git a/common-be/src/test/resources/csarSizeReducer/dummyToReduce.zip b/common-be/src/test/resources/csarSizeReducer/dummyToReduce-3-files.zip similarity index 100% rename from common-be/src/test/resources/csarSizeReducer/dummyToReduce.zip rename to common-be/src/test/resources/csarSizeReducer/dummyToReduce-3-files.zip diff --git a/common-be/src/test/resources/s3StoreArtifactStorageManager/dummy.csar b/common-be/src/test/resources/s3StoreArtifactStorageManager/dummy.csar new file mode 100644 index 0000000000000000000000000000000000000000..73b28f52fd5c2a7100afc8869d583d01231cfafe GIT binary patch literal 25876 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 e3=z}@{FiK7=?2P|t)0uzb;=wD06?S*^ZFm foldersToStrip = commonConfigurationManager.getConfigValue(EXTERNAL_CSAR_STORE, "foldersToStrip", new ArrayList<>()); - final int sizeLimit = commonConfigurationManager.getConfigValue(EXTERNAL_CSAR_STORE, "sizeLimit", 1000000); - final int thresholdEntries = commonConfigurationManager.getConfigValue(EXTERNAL_CSAR_STORE, "thresholdEntries", 10000); - LOGGER.info("Folders to strip: '{}'", String.join(", ", foldersToStrip)); - final Set foldersToStripPathSet = foldersToStrip.stream().map(Path::of).collect(Collectors.toSet()); - return new CsarPackageReducerConfiguration(foldersToStripPathSet, sizeLimit, thresholdEntries); - } - - private ArtifactStorageConfig readArtifactStorageConfiguration() { - final var commonConfigurationManager = CommonConfigurationManager.getInstance(); - final boolean isEnabled = commonConfigurationManager.getConfigValue(EXTERNAL_CSAR_STORE, "storeCsarsExternally", false); - LOGGER.info("ArtifactConfig.isEnabled: '{}'", isEnabled); - final String storagePathString = commonConfigurationManager.getConfigValue(EXTERNAL_CSAR_STORE, "fullPath", null); - LOGGER.info("ArtifactConfig.storagePath: '{}'", storagePathString); - if (isEnabled && storagePathString == null) { - throw new OrchestrationTemplateCandidateException(EXTERNAL_CSAR_STORE_CONFIGURATION_FAILURE_MISSING_FULL_PATH.getErrorMessage()); - } - final var storagePath = storagePathString == null ? null : Path.of(storagePathString); - return new PersistentVolumeArtifactStorageConfig(isEnabled, storagePath); - } - @Override public Response upload(String vspId, String versionId, final Attachment fileToUpload, final String user) { vspId = ValidationUtils.sanitizeInputString(vspId); versionId = ValidationUtils.sanitizeInputString(versionId); final byte[] fileToUploadBytes; - final var filename = ValidationUtils.sanitizeInputString(fileToUpload.getDataHandler().getName()); + final DataHandler dataHandler = fileToUpload.getDataHandler(); + final var filename = ValidationUtils.sanitizeInputString(dataHandler.getName()); ArtifactInfo artifactInfo = null; if (artifactStorageManager.isEnabled()) { - final InputStream packageInputStream; + final Path tempArtifactPath; try { - packageInputStream = fileToUpload.getDataHandler().getInputStream(); - } catch (final IOException e) { + final ArtifactStorageConfig storageConfiguration = artifactStorageManager.getStorageConfiguration(); + + final Path folder = Path.of(storageConfiguration.getTempPath()).resolve(vspId).resolve(versionId); + tempArtifactPath = folder.resolve(UUID.randomUUID().toString()); + Files.createDirectories(folder); + try (final InputStream packageInputStream = dataHandler.getInputStream(); + final var fileOutputStream = new FileOutputStream(tempArtifactPath.toFile())) { + packageInputStream.transferTo(fileOutputStream); + } + } catch (final Exception e) { return Response.status(INTERNAL_SERVER_ERROR).entity(buildUploadResponseWithError( new ErrorMessage(ErrorLevel.ERROR, UNEXPECTED_PROBLEM_HAPPENED_WHILE_GETTING.formatMessage(filename)))).build(); } - try { - artifactInfo = artifactStorageManager.upload(vspId, versionId, packageInputStream); - } catch (final BusinessException e) { + try (final InputStream inputStream = Files.newInputStream(tempArtifactPath)) { + artifactInfo = artifactStorageManager.upload(vspId, versionId, inputStream); + } catch (final Exception e) { + LOGGER.error("Package Size Reducer not configured", e); return Response.status(INTERNAL_SERVER_ERROR).entity(buildUploadResponseWithError( new ErrorMessage(ErrorLevel.ERROR, ERROR_HAS_OCCURRED_WHILE_PERSISTING_THE_ARTIFACT.formatMessage(filename)))).build(); } try { - fileToUploadBytes = packageSizeReducer.reduce(artifactInfo.getPath()); - } catch (final BusinessException e) { + fileToUploadBytes = packageSizeReducer.reduce(tempArtifactPath); + Files.delete(tempArtifactPath); + } catch (final Exception e) { + LOGGER.error("Package Size Reducer not configured", e); return Response.status(INTERNAL_SERVER_ERROR).entity(buildUploadResponseWithError( - new ErrorMessage(ErrorLevel.ERROR, ERROR_HAS_OCCURRED_WHILE_REDUCING_THE_ARTIFACT_SIZE.formatMessage(artifactInfo.getPath())))) - .build(); + new ErrorMessage(ErrorLevel.ERROR, + ERROR_HAS_OCCURRED_WHILE_REDUCING_THE_ARTIFACT_SIZE.formatMessage(tempArtifactPath.toString())))).build(); } } else { fileToUploadBytes = fileToUpload.getObject(byte[].class); @@ -192,12 +177,16 @@ public class OrchestrationTemplateCandidateImpl implements OrchestrationTemplate if (onboardPackageInfo == null) { final UploadFileResponseDto uploadFileResponseDto = buildUploadResponseWithError( new ErrorMessage(ErrorLevel.ERROR, PACKAGE_PROCESS_ERROR.formatMessage(filename))); - return Response.ok(uploadFileResponseDto) - .build(); + return Response.ok(uploadFileResponseDto).build(); } final var version = new Version(versionId); final var vspDetails = vendorSoftwareProductManager.getVsp(vspId, version); - return processOnboardPackage(onboardPackageInfo, vspDetails, errorMessages); + final Response response = processOnboardPackage(onboardPackageInfo, vspDetails, errorMessages); + final UploadFileResponseDto entity = (UploadFileResponseDto) response.getEntity(); + if (artifactStorageManager.isEnabled() && !entity.getErrors().isEmpty()) { + artifactStorageManager.delete(artifactInfo); + } + return response; } private Response processOnboardPackage(final OnboardPackageInfo onboardPackageInfo, final VspDetails vspDetails, diff --git a/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vendor-software-products-rest-services/src/test/java/org/openecomp/sdcrests/vsp/rest/services/OrchestrationTemplateCandidateImplTest.java b/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vendor-software-products-rest-services/src/test/java/org/openecomp/sdcrests/vsp/rest/services/OrchestrationTemplateCandidateImplTest.java index edf29b75c5..2d2c30865a 100644 --- a/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vendor-software-products-rest-services/src/test/java/org/openecomp/sdcrests/vsp/rest/services/OrchestrationTemplateCandidateImplTest.java +++ b/openecomp-be/api/openecomp-sdc-rest-webapp/vendor-software-products-rest/vendor-software-products-rest-services/src/test/java/org/openecomp/sdcrests/vsp/rest/services/OrchestrationTemplateCandidateImplTest.java @@ -32,6 +32,8 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.when; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; @@ -49,14 +51,18 @@ import org.apache.cxf.jaxrs.ext.multipart.ContentDisposition; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatchers; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.openecomp.core.utilities.orchestration.OnboardingTypesEnum; import org.openecomp.sdc.activitylog.ActivityLogManager; import org.openecomp.sdc.be.csar.storage.ArtifactStorageManager; +import org.openecomp.sdc.be.csar.storage.MinIoArtifactInfo; +import org.openecomp.sdc.be.csar.storage.MinIoStorageArtifactStorageConfig; +import org.openecomp.sdc.be.csar.storage.MinIoStorageArtifactStorageConfig.Credentials; +import org.openecomp.sdc.be.csar.storage.MinIoStorageArtifactStorageConfig.EndPoint; import org.openecomp.sdc.be.csar.storage.PackageSizeReducer; -import org.openecomp.sdc.be.csar.storage.PersistentStorageArtifactInfo; import org.openecomp.sdc.logging.api.Logger; import org.openecomp.sdc.logging.api.LoggerFactory; import org.openecomp.sdc.vendorsoftwareproduct.OrchestrationTemplateCandidateManager; @@ -88,6 +94,7 @@ class OrchestrationTemplateCandidateImplTest { private ArtifactStorageManager artifactStorageManager; @Mock private PackageSizeReducer packageSizeReducer; + @InjectMocks private OrchestrationTemplateCandidateImpl orchestrationTemplateCandidate; @BeforeEach @@ -132,20 +139,15 @@ class OrchestrationTemplateCandidateImplTest { ArgumentMatchers.eq(candidateId), ArgumentMatchers.any())).thenReturn(Optional.of(fds)); - orchestrationTemplateCandidate = - new OrchestrationTemplateCandidateImpl(candidateManager, vendorSoftwareProductManager, activityLogManager, - artifactStorageManager, packageSizeReducer); - } catch (Exception e) { logger.error(e.getMessage(), e); } } @Test - void uploadSignedTest() { + void uploadSignedTest() throws IOException { Response response = orchestrationTemplateCandidate - .upload("1", "1", mockAttachment("filename.zip", this.getClass().getResource("/files/sample-signed.zip")), - "1"); + .upload("1", "1", mockAttachment("filename.zip", this.getClass().getResource("/files/sample-signed.zip")), user); assertEquals(Status.OK.getStatusCode(), response.getStatus()); assertTrue(((UploadFileResponseDto) response.getEntity()).getErrors().isEmpty()); } @@ -153,7 +155,7 @@ class OrchestrationTemplateCandidateImplTest { @Test void uploadNotSignedTest() throws IOException { Response response = orchestrationTemplateCandidate.upload("1", "1", - mockAttachment("filename.csar", this.getClass().getResource("/files/sample-not-signed.csar")), "1"); + mockAttachment("filename.csar", this.getClass().getResource("/files/sample-not-signed.csar")), user); assertEquals(Status.OK.getStatusCode(), response.getStatus()); assertTrue(((UploadFileResponseDto) response.getEntity()).getErrors().isEmpty()); } @@ -161,23 +163,29 @@ class OrchestrationTemplateCandidateImplTest { @Test void uploadNotSignedArtifactStorageManagerIsEnabledTest() throws IOException { when(artifactStorageManager.isEnabled()).thenReturn(true); + when(artifactStorageManager.getStorageConfiguration()).thenReturn( + new MinIoStorageArtifactStorageConfig(true, new EndPoint("host", 9000, false), new Credentials("accessKey", "secretKey"), "tempPath")); + final Path path = Path.of("src/test/resources/files/sample-not-signed.csar"); - when(artifactStorageManager.upload(anyString(), anyString(), any())).thenReturn(new PersistentStorageArtifactInfo(path)); + when(artifactStorageManager.upload(anyString(), anyString(), any())).thenReturn(new MinIoArtifactInfo("vspId", "name")); final byte[] bytes = Files.readAllBytes(path); when(packageSizeReducer.reduce(any())).thenReturn(bytes); Response response = orchestrationTemplateCandidate.upload("1", "1", - mockAttachment("filename.csar", this.getClass().getResource("/files/sample-not-signed.csar")), "1"); + mockAttachment("filename.csar", this.getClass().getResource("/files/sample-not-signed.csar")), user); assertEquals(Status.OK.getStatusCode(), response.getStatus()); assertTrue(((UploadFileResponseDto) response.getEntity()).getErrors().isEmpty()); } - private Attachment mockAttachment(final String fileName, final URL fileToUpload) { + private Attachment mockAttachment(final String fileName, final URL fileToUpload) throws IOException { final Attachment attachment = Mockito.mock(Attachment.class); + final InputStream inputStream = Mockito.mock(InputStream.class); when(attachment.getContentDisposition()).thenReturn(new ContentDisposition("test")); final DataHandler dataHandler = Mockito.mock(DataHandler.class); when(dataHandler.getName()).thenReturn(fileName); when(attachment.getDataHandler()).thenReturn(dataHandler); + when(dataHandler.getInputStream()).thenReturn(inputStream); + when(inputStream.transferTo(any(OutputStream.class))).thenReturn(0L); byte[] bytes = "upload package Test".getBytes(); if (Objects.nonNull(fileToUpload)) { try { @@ -192,9 +200,9 @@ class OrchestrationTemplateCandidateImplTest { } @Test - void uploadSignNotValidTest() { + void uploadSignNotValidTest() throws IOException { Response response = orchestrationTemplateCandidate - .upload("1", "1", mockAttachment("filename.zip", null), "1"); + .upload("1", "1", mockAttachment("filename.zip", null), user); assertEquals(Status.NOT_ACCEPTABLE.getStatusCode(), response.getStatus()); assertFalse(((UploadFileResponseDto) response.getEntity()).getErrors().isEmpty()); } diff --git a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidator.java b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidator.java index bf5abe3737..781b4a6e2c 100644 --- a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidator.java +++ b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidator.java @@ -61,7 +61,7 @@ public class CsarSecurityValidator { } private boolean isArtifactInfoPresent(final ArtifactInfo artifactInfo) { - return artifactInfo != null && artifactInfo.getPath() != null; + return artifactInfo != null; } } diff --git a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManager.java b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManager.java index fec15b5fcc..53728c0489 100644 --- a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManager.java +++ b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManager.java @@ -19,12 +19,12 @@ */ package org.openecomp.sdc.vendorsoftwareproduct.security; -import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; - import com.google.common.collect.ImmutableSet; +import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -56,7 +56,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; -import java.util.zip.ZipFile; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; import org.bouncycastle.asn1.cms.ContentInfo; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cms.CMSException; @@ -69,7 +70,9 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.operator.OperatorCreationException; import org.openecomp.sdc.be.csar.storage.ArtifactInfo; -import org.openecomp.sdc.common.errors.SdcRuntimeException; +import org.openecomp.sdc.be.csar.storage.ArtifactStorageConfig; +import org.openecomp.sdc.be.csar.storage.ArtifactStorageManager; +import org.openecomp.sdc.be.csar.storage.StorageFactory; import org.openecomp.sdc.logging.api.Logger; import org.openecomp.sdc.logging.api.LoggerFactory; import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardSignedPackage; @@ -83,9 +86,10 @@ public class SecurityManager { public static final Set ALLOWED_SIGNATURE_EXTENSIONS = Set.of("cms"); public static final Set ALLOWED_CERTIFICATE_EXTENSIONS = Set.of("cert", "crt"); private static final String CERTIFICATE_DEFAULT_LOCATION = "cert"; - private static final Logger logger = LoggerFactory.getLogger(SecurityManager.class); + private static final Logger LOGGER = LoggerFactory.getLogger(SecurityManager.class); private static final String UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION = "Unexpected error occurred during signature validation!"; private static final String COULD_NOT_VERIFY_SIGNATURE = "Could not verify signature!"; + private static final String EXTERNAL_CSAR_STORE = "externalCsarStore"; static { if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { @@ -120,7 +124,7 @@ public class SecurityManager { //if file number in certificate directory changed reload certs String[] certFiles = certificateDirectory.list(); if (certFiles == null) { - logger.error("Certificate directory is empty!"); + LOGGER.error("Certificate directory is empty!"); return ImmutableSet.copyOf(new HashSet<>()); } if (trustedCertificates.size() != certFiles.length) { @@ -160,7 +164,7 @@ public class SecurityManager { } return verify(packageCert, new CMSSignedData(new CMSProcessableByteArray(innerPackageFile), ContentInfo.getInstance(parsedObject))); } catch (final IOException | CMSException e) { - logger.error(e.getMessage(), e); + LOGGER.error(e.getMessage(), e); throw new SecurityManagerException(UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION, e); } } @@ -168,14 +172,27 @@ public class SecurityManager { public boolean verifyPackageSignedData(final OnboardSignedPackage signedPackage, final ArtifactInfo artifactInfo) throws SecurityManagerException { boolean fail = false; + + final StorageFactory storageFactory = new StorageFactory(); + final ArtifactStorageManager artifactStorageManager = storageFactory.createArtifactStorageManager(); + final ArtifactStorageConfig storageConfiguration = artifactStorageManager.getStorageConfiguration(); + final var fileContentHandler = signedPackage.getFileContentHandler(); byte[] packageCert = null; final Optional certificateFilePath = signedPackage.getCertificateFilePath(); if (certificateFilePath.isPresent()) { packageCert = fileContentHandler.getFileContent(certificateFilePath.get()); } - final var path = artifactInfo.getPath(); - final var target = Path.of(path.toString() + "." + UUID.randomUUID()); + + final Path folder = Path.of(storageConfiguration.getTempPath()); + try { + Files.createDirectories(folder); + } catch (final IOException e) { + fail = true; + throw new SecurityManagerException(String.format("Failed to create directory '%s'", folder), e); + } + + final var target = folder.resolve(UUID.randomUUID().toString()); try (final var signatureStream = new ByteArrayInputStream(fileContentHandler.getFileContent(signedPackage.getSignatureFilePath())); final var pemParser = new PEMParser(new InputStreamReader(signatureStream))) { @@ -185,16 +202,18 @@ public class SecurityManager { throw new SecurityManagerException("Signature is not recognized"); } - if (!findCSARandExtract(path, target)) { - fail = true; - return false; + try (final InputStream inputStream = artifactStorageManager.get(artifactInfo)) { + if (!findCSARandExtract(inputStream, target)) { + fail = true; + return false; + } } final var verify = verify(packageCert, new CMSSignedData(new CMSProcessableFile(target.toFile()), ContentInfo.getInstance(parsedObject))); fail = !verify; return verify; } catch (final IOException e) { fail = true; - logger.error(e.getMessage(), e); + LOGGER.error(e.getMessage(), e); throw new SecurityManagerException(UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION, e); } catch (final CMSException e) { fail = true; @@ -205,7 +224,7 @@ public class SecurityManager { } finally { deleteFile(target); if (fail) { - deleteFile(path); + artifactStorageManager.delete(artifactInfo); } } } @@ -214,7 +233,7 @@ public class SecurityManager { try { Files.delete(filePath); } catch (final IOException e) { - logger.warn("Failed to delete '{}' after verifying package signed data", filePath, e); + LOGGER.warn("Failed to delete '{}' after verifying package signed data", filePath, e); } } @@ -246,20 +265,25 @@ public class SecurityManager { } } - private boolean findCSARandExtract(final Path path, final Path target) throws IOException { + private boolean findCSARandExtract(final InputStream inputStream, final Path target) throws IOException { final AtomicBoolean found = new AtomicBoolean(false); - try (final var zf = new ZipFile(path.toString())) { - zf.entries().asIterator().forEachRemaining(entry -> { - final var entryName = entry.getName(); - if (!entry.isDirectory() && entryName.toLowerCase().endsWith(".csar")) { - try { - Files.copy(zf.getInputStream(entry), target, REPLACE_EXISTING); - } catch (final IOException e) { - throw new SdcRuntimeException(UNEXPECTED_ERROR_OCCURRED_DURING_SIGNATURE_VALIDATION, e); + + final var zipInputStream = new ZipInputStream(inputStream); + ZipEntry zipEntry; + byte[] buffer = new byte[2048]; + while ((zipEntry = zipInputStream.getNextEntry()) != null) { + final var entryName = zipEntry.getName(); + if (!zipEntry.isDirectory() && entryName.toLowerCase().endsWith(".csar")) { + try (final FileOutputStream fos = new FileOutputStream(target.toFile()); + final BufferedOutputStream bos = new BufferedOutputStream(fos, buffer.length)) { + + int len; + while ((len = zipInputStream.read(buffer)) > 0) { + bos.write(buffer, 0, len); } - found.set(true); } - }); + found.set(true); + } } return found.get(); } @@ -289,12 +313,12 @@ public class SecurityManager { private void processCertificateDir() throws SecurityManagerException { if (!certificateDirectory.exists() || !certificateDirectory.isDirectory()) { - logger.error("Issue with certificate directory, check if exists!"); + LOGGER.error("Issue with certificate directory, check if exists!"); return; } File[] files = certificateDirectory.listFiles(); if (files == null) { - logger.error("Certificate directory is empty!"); + LOGGER.error("Certificate directory is empty!"); return; } for (File f : files) { @@ -399,10 +423,10 @@ public class SecurityManager { try { cert.checkValidity(); } catch (CertificateExpiredException e) { - logger.error(e.getMessage(), e); + LOGGER.error(e.getMessage(), e); return true; } catch (CertificateNotYetValidException e) { - logger.error(e.getMessage(), e); + LOGGER.error(e.getMessage(), e); return false; } return false; diff --git a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidatorTest.java b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidatorTest.java index 96d11eb148..5f880701f3 100644 --- a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidatorTest.java +++ b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/impl/orchestration/csar/validation/CsarSecurityValidatorTest.java @@ -24,25 +24,43 @@ import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.initMocks; - +import static org.mockito.MockitoAnnotations.openMocks; +import static org.openecomp.sdc.be.csar.storage.StorageFactory.StorageType.MINIO; + +import io.minio.GetObjectArgs; +import io.minio.GetObjectResponse; +import io.minio.MinioClient; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Answers; import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; import org.openecomp.sdc.be.csar.storage.ArtifactInfo; -import org.openecomp.sdc.be.csar.storage.PersistentStorageArtifactInfo; +import org.openecomp.sdc.be.csar.storage.MinIoArtifactInfo; +import org.openecomp.sdc.common.CommonConfigurationManager; import org.openecomp.sdc.vendorsoftwareproduct.impl.onboarding.OnboardingPackageProcessor; import org.openecomp.sdc.vendorsoftwareproduct.impl.onboarding.validation.CnfPackageValidator; import org.openecomp.sdc.vendorsoftwareproduct.security.SecurityManager; @@ -50,6 +68,7 @@ import org.openecomp.sdc.vendorsoftwareproduct.security.SecurityManagerException import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardPackageInfo; import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardSignedPackage; +@ExtendWith(MockitoExtension.class) class CsarSecurityValidatorTest { private static final String BASE_DIR = "/vspmanager.csar/signing/"; @@ -57,6 +76,16 @@ class CsarSecurityValidatorTest { private CsarSecurityValidator csarSecurityValidator; @Mock private SecurityManager securityManager; + @Mock + private CommonConfigurationManager commonConfigurationManager; + @Mock + private MinioClient minioClient; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private MinioClient.Builder builderMinio; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private GetObjectArgs.Builder getObjectArgsBuilder; + @Mock + private GetObjectArgs getObjectArgs; @AfterEach void tearDown() throws Exception { @@ -74,7 +103,7 @@ class CsarSecurityValidatorTest { @BeforeEach public void setUp() throws Exception { - initMocks(this); + openMocks(this); csarSecurityValidator = new CsarSecurityValidator(securityManager); backup(); } @@ -88,9 +117,9 @@ class CsarSecurityValidatorTest { } @Test - void isSignatureValidTestCorrectStructureAndValidSignatureExists() throws SecurityManagerException, IOException { + void isSignatureValidTestCorrectStructureAndValidSignatureExists() throws SecurityManagerException { final byte[] packageBytes = getFileBytesOrFail("signed-package.zip"); - final OnboardPackageInfo onboardPackageInfo = loadSignedPackageWithArtifactInfo("signed-package.zip", packageBytes, null); + final OnboardPackageInfo onboardPackageInfo = loadSignedPackageWithArtifactInfoS3Store("signed-package.zip", packageBytes, null); when(securityManager.verifyPackageSignedData(any(OnboardSignedPackage.class), any(ArtifactInfo.class))).thenReturn(true); final boolean isSignatureValid = csarSecurityValidator .verifyPackageSignature((OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(), onboardPackageInfo.getArtifactInfo()); @@ -98,15 +127,53 @@ class CsarSecurityValidatorTest { } @Test - void isSignatureValidTestCorrectStructureAndNotValidSignatureExists() throws SecurityManagerException { - final byte[] packageBytes = getFileBytesOrFail("signed-package-tampered-data.zip"); - final OnboardPackageInfo onboardPackageInfo = loadSignedPackageWithArtifactInfo("signed-package-tampered-data.zip", packageBytes, null); - //no mocked securityManager - csarSecurityValidator = new CsarSecurityValidator(); - Assertions.assertThrows(SecurityManagerException.class, () -> { - csarSecurityValidator - .verifyPackageSignature((OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(), onboardPackageInfo.getArtifactInfo()); - }); + void isSignatureValidTestCorrectStructureAndNotValidSignatureExists() throws Exception { + + final Map endpoint = new HashMap<>(); + endpoint.put("host", "localhost"); + endpoint.put("port", 9000); + final Map credentials = new HashMap<>(); + credentials.put("accessKey", "login"); + credentials.put("secretKey", "password"); + + try (MockedStatic utilities = Mockito.mockStatic(CommonConfigurationManager.class)) { + utilities.when(CommonConfigurationManager::getInstance).thenReturn(commonConfigurationManager); + try (MockedStatic minioUtilities = Mockito.mockStatic(MinioClient.class)) { + minioUtilities.when(MinioClient::builder).thenReturn(builderMinio); + when(builderMinio + .endpoint(anyString(), anyInt(), anyBoolean()) + .credentials(anyString(), anyString()) + .build() + ).thenReturn(minioClient); + + when(commonConfigurationManager.getConfigValue("externalCsarStore", "endpoint", null)).thenReturn(endpoint); + when(commonConfigurationManager.getConfigValue("externalCsarStore", "credentials", null)).thenReturn(credentials); + when(commonConfigurationManager.getConfigValue("externalCsarStore", "tempPath", null)).thenReturn("cert/2-file-signed-package"); + when(commonConfigurationManager.getConfigValue(eq("externalCsarStore"), eq("storageType"), any())).thenReturn(MINIO.name()); + + final byte[] packageBytes = getFileBytesOrFail("signed-package-tampered-data.zip"); + + when(getObjectArgsBuilder + .bucket(anyString()) + .object(anyString()) + .build() + ).thenReturn(getObjectArgs); + + when(minioClient.getObject(any(GetObjectArgs.class))) + .thenReturn(new GetObjectResponse(null, "bucket", "", "objectName", + new BufferedInputStream(new ByteArrayInputStream(packageBytes)))); + + final OnboardPackageInfo onboardPackageInfo = loadSignedPackageWithArtifactInfoS3Store("signed-package-tampered-data.zip", + packageBytes, + null); + //no mocked securityManager + csarSecurityValidator = new CsarSecurityValidator(); + Assertions.assertThrows(SecurityManagerException.class, () -> { + csarSecurityValidator.verifyPackageSignature((OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(), + onboardPackageInfo.getArtifactInfo()); + }); + } + } } @Test @@ -148,11 +215,10 @@ class CsarSecurityValidatorTest { CsarSecurityValidatorTest.class.getResource(BASE_DIR + path).toURI())); } - private OnboardPackageInfo loadSignedPackageWithArtifactInfo(final String packageName, final byte[] packageBytes, - final CnfPackageValidator cnfPackageValidator) { + private OnboardPackageInfo loadSignedPackageWithArtifactInfoS3Store(final String packageName, final byte[] packageBytes, + final CnfPackageValidator cnfPackageValidator) { final OnboardingPackageProcessor onboardingPackageProcessor = - new OnboardingPackageProcessor(packageName, packageBytes, cnfPackageValidator, - new PersistentStorageArtifactInfo(Path.of("src/test/resources/vspmanager.csar/signing/signed-package.zip"))); + new OnboardingPackageProcessor(packageName, packageBytes, cnfPackageValidator, new MinIoArtifactInfo("bucket", "object")); final OnboardPackageInfo onboardPackageInfo = onboardingPackageProcessor.getOnboardPackageInfo().orElse(null); if (onboardPackageInfo == null) { fail("Unexpected error. Could not load original package"); diff --git a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManagerTest.java b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManagerTest.java index 6dc5517c45..afc43967c9 100644 --- a/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManagerTest.java +++ b/openecomp-be/backend/openecomp-sdc-vendor-software-product-manager/src/test/java/org/openecomp/sdc/vendorsoftwareproduct/security/SecurityManagerTest.java @@ -22,29 +22,61 @@ package org.openecomp.sdc.vendorsoftwareproduct.security; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertTrue; - +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.openMocks; +import static org.openecomp.sdc.be.csar.storage.StorageFactory.StorageType.MINIO; + +import io.minio.GetObjectArgs; +import io.minio.GetObjectResponse; +import io.minio.MinioClient; +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.openecomp.sdc.be.csar.storage.PersistentStorageArtifactInfo; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openecomp.sdc.be.csar.storage.MinIoArtifactInfo; +import org.openecomp.sdc.common.CommonConfigurationManager; import org.openecomp.sdc.vendorsoftwareproduct.impl.onboarding.OnboardingPackageProcessor; import org.openecomp.sdc.vendorsoftwareproduct.impl.onboarding.validation.CnfPackageValidator; import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardPackageInfo; import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardSignedPackage; +@ExtendWith(MockitoExtension.class) class SecurityManagerTest { private File certDir; private String cerDirPath = "/tmp/cert/"; private SecurityManager securityManager; + @Mock + private CommonConfigurationManager commonConfigurationManager; + @Mock + private MinioClient minioClient; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private MinioClient.Builder builderMinio; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private GetObjectArgs.Builder getObjectArgsBuilder; + @Mock + private GetObjectArgs getObjectArgs; private File prepareCertFiles(String origFilePath, String newFilePath) throws IOException, URISyntaxException { File origFile = new File(getClass().getResource(origFilePath).toURI()); @@ -60,12 +92,14 @@ class SecurityManagerTest { @BeforeEach public void setUp() throws IOException { + openMocks(this); certDir = new File(cerDirPath); if (certDir.exists()) { tearDown(); } certDir.mkdirs(); securityManager = new SecurityManager(certDir.getPath()); + } @AfterEach @@ -123,18 +157,51 @@ class SecurityManagerTest { } @Test - void verifySignedDataTestCertIncludedIntoSignatureArtifactStorageManagerIsEnabled() - throws IOException, URISyntaxException, SecurityManagerException { - prepareCertFiles("/cert/rootCA.cert", cerDirPath + "root.cert"); - byte[] fileToUploadBytes = readAllBytes("/cert/2-file-signed-package/2-file-signed-package.zip"); - - final var onboardingPackageProcessor = new OnboardingPackageProcessor("2-file-signed-package.zip", fileToUploadBytes, - new CnfPackageValidator(), - new PersistentStorageArtifactInfo(Path.of("src/test/resources/cert/2-file-signed-package/2-file-signed-package.zip"))); - final OnboardPackageInfo onboardPackageInfo = onboardingPackageProcessor.getOnboardPackageInfo().orElse(null); - - assertTrue(securityManager - .verifyPackageSignedData((OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(), onboardPackageInfo.getArtifactInfo())); + void verifySignedDataTestCertIncludedIntoSignatureArtifactStorageManagerIsEnabled() throws Exception { + + final Map endpoint = new HashMap<>(); + endpoint.put("host", "localhost"); + endpoint.put("port", 9000); + final Map credentials = new HashMap<>(); + credentials.put("accessKey", "login"); + credentials.put("secretKey", "password"); + + try (MockedStatic utilities = Mockito.mockStatic(CommonConfigurationManager.class)) { + utilities.when(CommonConfigurationManager::getInstance).thenReturn(commonConfigurationManager); + try (MockedStatic minioUtilities = Mockito.mockStatic(MinioClient.class)) { + minioUtilities.when(MinioClient::builder).thenReturn(builderMinio); + when(builderMinio + .endpoint(anyString(), anyInt(), anyBoolean()) + .credentials(anyString(), anyString()) + .build() + ).thenReturn(minioClient); + + when(commonConfigurationManager.getConfigValue("externalCsarStore", "endpoint", null)).thenReturn(endpoint); + when(commonConfigurationManager.getConfigValue("externalCsarStore", "credentials", null)).thenReturn(credentials); + when(commonConfigurationManager.getConfigValue("externalCsarStore", "tempPath", null)).thenReturn("cert/2-file-signed-package"); + when(commonConfigurationManager.getConfigValue(eq("externalCsarStore"), eq("storageType"), any())).thenReturn(MINIO.name()); + + prepareCertFiles("/cert/rootCA.cert", cerDirPath + "root.cert"); + byte[] fileToUploadBytes = readAllBytes("/cert/2-file-signed-package/2-file-signed-package.zip"); + when(getObjectArgsBuilder + .bucket(anyString()) + .object(anyString()) + .build() + ).thenReturn(getObjectArgs); + + when(minioClient.getObject(any(GetObjectArgs.class))) + .thenReturn(new GetObjectResponse(null, "bucket", "", "objectName", + new BufferedInputStream(new ByteArrayInputStream(fileToUploadBytes)))); + + final var onboardingPackageProcessor = new OnboardingPackageProcessor("2-file-signed-package.zip", fileToUploadBytes, + new CnfPackageValidator(), new MinIoArtifactInfo("bucket", "objectName")); + final OnboardPackageInfo onboardPackageInfo = onboardingPackageProcessor.getOnboardPackageInfo().orElse(null); + + assertTrue(securityManager + .verifyPackageSignedData((OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(), + onboardPackageInfo.getArtifactInfo())); + } + } } @Test @@ -158,18 +225,51 @@ class SecurityManagerTest { } @Test - void verifySignedDataTestCertNotIncludedIntoSignatureArtifactStorageManagerIsEnabled() - throws IOException, URISyntaxException, SecurityManagerException { - prepareCertFiles("/cert/rootCA.cert", cerDirPath + "root.cert"); - byte[] fileToUploadBytes = readAllBytes("/cert/3-file-signed-package/3-file-signed-package.zip"); - - final var onboardingPackageProcessor = new OnboardingPackageProcessor("3-file-signed-package.zip", fileToUploadBytes, - new CnfPackageValidator(), - new PersistentStorageArtifactInfo(Path.of("src/test/resources/cert/3-file-signed-package/3-file-signed-package.zip"))); - final OnboardPackageInfo onboardPackageInfo = onboardingPackageProcessor.getOnboardPackageInfo().orElse(null); - - assertTrue(securityManager - .verifyPackageSignedData((OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(), onboardPackageInfo.getArtifactInfo())); + void verifySignedDataTestCertNotIncludedIntoSignatureArtifactStorageManagerIsEnabled() throws Exception { + + final Map endpoint = new HashMap<>(); + endpoint.put("host", "localhost"); + endpoint.put("port", 9000); + final Map credentials = new HashMap<>(); + credentials.put("accessKey", "login"); + credentials.put("secretKey", "password"); + + try (MockedStatic utilities = Mockito.mockStatic(CommonConfigurationManager.class)) { + utilities.when(CommonConfigurationManager::getInstance).thenReturn(commonConfigurationManager); + try (MockedStatic minioUtilities = Mockito.mockStatic(MinioClient.class)) { + minioUtilities.when(MinioClient::builder).thenReturn(builderMinio); + when(builderMinio + .endpoint(anyString(), anyInt(), anyBoolean()) + .credentials(anyString(), anyString()) + .build() + ).thenReturn(minioClient); + + when(commonConfigurationManager.getConfigValue("externalCsarStore", "endpoint", null)).thenReturn(endpoint); + when(commonConfigurationManager.getConfigValue("externalCsarStore", "credentials", null)).thenReturn(credentials); + when(commonConfigurationManager.getConfigValue("externalCsarStore", "tempPath", null)).thenReturn("tempPath"); + when(commonConfigurationManager.getConfigValue(eq("externalCsarStore"), eq("storageType"), any())).thenReturn(MINIO.name()); + + prepareCertFiles("/cert/rootCA.cert", cerDirPath + "root.cert"); + byte[] fileToUploadBytes = readAllBytes("/cert/3-file-signed-package/3-file-signed-package.zip"); + when(getObjectArgsBuilder + .bucket(anyString()) + .object(anyString()) + .build() + ).thenReturn(getObjectArgs); + + when(minioClient.getObject(any(GetObjectArgs.class))) + .thenReturn(new GetObjectResponse(null, "bucket", "", "objectName", + new BufferedInputStream(new ByteArrayInputStream(fileToUploadBytes)))); + + final var onboardingPackageProcessor = new OnboardingPackageProcessor("3-file-signed-package.zip", fileToUploadBytes, + new CnfPackageValidator(), new MinIoArtifactInfo("bucket", "objectName")); + final OnboardPackageInfo onboardPackageInfo = onboardingPackageProcessor.getOnboardPackageInfo().orElse(null); + + assertTrue(securityManager + .verifyPackageSignedData((OnboardSignedPackage) onboardPackageInfo.getOriginalOnboardPackage(), + onboardPackageInfo.getArtifactInfo())); + } + } } @Test diff --git a/openecomp-be/dist/sdc-onboard-backend-docker/artifacts/chef-repo/cookbooks/sdc-onboard-backend/templates/default/configuration.yaml.erb b/openecomp-be/dist/sdc-onboard-backend-docker/artifacts/chef-repo/cookbooks/sdc-onboard-backend/templates/default/configuration.yaml.erb index d2c3d10805..3b02114334 100644 --- a/openecomp-be/dist/sdc-onboard-backend-docker/artifacts/chef-repo/cookbooks/sdc-onboard-backend/templates/default/configuration.yaml.erb +++ b/openecomp-be/dist/sdc-onboard-backend-docker/artifacts/chef-repo/cookbooks/sdc-onboard-backend/templates/default/configuration.yaml.erb @@ -1,35 +1,35 @@ catalogNotificationsConfig: - # catalog backend protocol - <% if node[:disableHttp] -%> - catalogBeProtocol: https - <% else %> - catalogBeProtocol: http - <% end -%> - catalogBeHttpPort: <%= @catalog_be_http_port %> - catalogBeSslPort: <%= @catalog_be_ssl_port %> - catalogBeFqdn: <%= @catalog_be_fqdn %> - # do not remove the "" from catalog_notification_url. it is escaping % characters coming from AUTO.json - catalogNotificationUrl: "<%= @catalog_notification_url %>" + # catalog backend protocol + <% if node[:disableHttp] -%> + catalogBeProtocol: https + <% else %> + catalogBeProtocol: http + <% end -%> + catalogBeHttpPort: <%= @catalog_be_http_port %> + catalogBeSslPort: <%= @catalog_be_ssl_port %> + catalogBeFqdn: <%= @catalog_be_fqdn %> + # do not remove the "" from catalog_notification_url. it is escaping % characters coming from AUTO.json + catalogNotificationUrl: "<%= @catalog_notification_url %>" notifications: - pollingIntervalMsec: 2000 - selectionSize: 100 - beHost: <%= @onboard_ip %> - beHttpPort: <%= @onboard_port %> + pollingIntervalMsec: 2000 + selectionSize: 100 + beHost: <%= @onboard_ip %> + beHttpPort: <%= @onboard_port %> cassandraConfig: - cassandraHosts: [<%= @cassandra_ip %>] - cassandraPort: <%= @cassandra_port %> - localDataCenter: <%= @DC_NAME %> - reconnectTimeout : 30000 - socketReadTimeout: <%= @socket_read_timeout %> - socketConnectTimeout: <%= @socket_connect_timeout %> - authenticate: true - username: <%= @cassandra_usr %> - password: <%= @cassandra_pwd %> - ssl: <%= @cassandra_ssl_enabled %> - truststorePath: <%= node['jetty']['truststore_path'] %> - truststorePassword: <%= @cassandra_truststore_password %> + cassandraHosts: [ <%= @cassandra_ip %> ] + cassandraPort: <%= @cassandra_port %> + localDataCenter: <%= @DC_NAME %> + reconnectTimeout: 30000 + socketReadTimeout: <%= @socket_read_timeout %> + socketConnectTimeout: <%= @socket_connect_timeout %> + authenticate: true + username: <%= @cassandra_usr %> + password: <%= @cassandra_pwd %> + ssl: <%= @cassandra_ssl_enabled %> + truststorePath: <%= node['jetty']['truststore_path'] %> + truststorePassword: <%= @cassandra_truststore_password %> # access restriction authCookie: @@ -42,8 +42,8 @@ authCookie: isHttpOnly: true # redirect variable name from portal.properties file redirectURL: "redirect_url" - excludedUrls: ['/.*'] - onboardingExcludedUrls: ['/.*'] + excludedUrls: [ '/.*' ] + onboardingExcludedUrls: [ '/.*' ] basicAuth: enabled: <%= @basic_auth_enabled %> @@ -55,9 +55,16 @@ zipValidation: ignoreManifest: false externalCsarStore: - storeCsarsExternally: false - fullPath: "/home/onap/temp/" + storageType: NONE # NONE, MINIO + endpoint: + host: 127.0.0.1 + port: 9000 + secure: false + credentials: + accessKey: "login" + secretKey: "password" foldersToStrip: - Files/images sizeLimit: 10000000 - thresholdEntries: 10000 \ No newline at end of file + thresholdEntries: 10000 + tempPath: "/home/onap/temp/" diff --git a/openecomp-be/lib/openecomp-common-lib/src/main/java/org/openecomp/sdc/common/errors/Messages.java b/openecomp-be/lib/openecomp-common-lib/src/main/java/org/openecomp/sdc/common/errors/Messages.java index 28ce5d2a62..21ad7a60a5 100644 --- a/openecomp-be/lib/openecomp-common-lib/src/main/java/org/openecomp/sdc/common/errors/Messages.java +++ b/openecomp-be/lib/openecomp-common-lib/src/main/java/org/openecomp/sdc/common/errors/Messages.java @@ -202,11 +202,10 @@ public enum Messages { FAILED_TO_MARK_NOTIFICATION_AS_READ("Failed to mark notifications as read"), FAILED_TO_UPDATE_LAST_SEEN_NOTIFICATION("Failed to update last seen notification for user %s"), FAILED_TO_VERIFY_SIGNATURE("Could not verify signature of signed package."), - EXTERNAL_CSAR_STORE_CONFIGURATION_FAILURE_MISSING_FULL_PATH("externalCsarStore configuration failure, missing 'fullPath'"), + EXTERNAL_CSAR_STORE_CONFIGURATION_FAILURE_MISSING("externalCsarStore configuration failure, missing '%s'"), ERROR_HAS_OCCURRED_WHILE_PERSISTING_THE_ARTIFACT("An error has occurred while persisting the artifact: %s"), ERROR_HAS_OCCURRED_WHILE_REDUCING_THE_ARTIFACT_SIZE("An error has occurred while reducing the artifact's size: %s"), - UNEXPECTED_PROBLEM_HAPPENED_WHILE_GETTING("An unexpected problem happened while getting '%s'"), - PERSISTENCE_STORE_IS_DOWN_OR_NOT_AVAILABLE("Persistence store is down or not available. Error: '%s'"); + UNEXPECTED_PROBLEM_HAPPENED_WHILE_GETTING("An unexpected problem happened while getting '%s'"); // @formatter:on private String errorMessage; diff --git a/openecomp-be/lib/openecomp-sdc-vendor-software-product-lib/openecomp-sdc-vendor-software-product-core/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/dao/impl/zusammen/OrchestrationTemplateCandidateDaoZusammenImpl.java b/openecomp-be/lib/openecomp-sdc-vendor-software-product-lib/openecomp-sdc-vendor-software-product-core/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/dao/impl/zusammen/OrchestrationTemplateCandidateDaoZusammenImpl.java index e5c59968fa..fbb25de0da 100644 --- a/openecomp-be/lib/openecomp-sdc-vendor-software-product-lib/openecomp-sdc-vendor-software-product-core/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/dao/impl/zusammen/OrchestrationTemplateCandidateDaoZusammenImpl.java +++ b/openecomp-be/lib/openecomp-sdc-vendor-software-product-lib/openecomp-sdc-vendor-software-product-core/src/main/java/org/openecomp/sdc/vendorsoftwareproduct/dao/impl/zusammen/OrchestrationTemplateCandidateDaoZusammenImpl.java @@ -31,7 +31,6 @@ import com.amdocs.zusammen.utils.fileutils.FileUtils; import java.io.ByteArrayInputStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import java.nio.file.Path; import java.util.Optional; import lombok.AllArgsConstructor; import lombok.Getter; @@ -39,12 +38,8 @@ import org.openecomp.core.utilities.json.JsonUtil; import org.openecomp.core.utilities.orchestration.OnboardingTypesEnum; import org.openecomp.core.zusammen.api.ZusammenAdaptor; import org.openecomp.sdc.be.csar.storage.ArtifactInfo; -import org.openecomp.sdc.be.csar.storage.ArtifactStorageConfig; import org.openecomp.sdc.be.csar.storage.ArtifactStorageManager; -import org.openecomp.sdc.be.csar.storage.PersistentVolumeArtifactStorageConfig; -import org.openecomp.sdc.be.csar.storage.PersistentVolumeArtifactStorageManager; -import org.openecomp.sdc.common.CommonConfigurationManager; -import org.openecomp.sdc.common.errors.Messages; +import org.openecomp.sdc.be.csar.storage.StorageFactory; import org.openecomp.sdc.datatypes.model.ElementType; import org.openecomp.sdc.heat.datatypes.structure.ValidationStructureList; import org.openecomp.sdc.logging.api.Logger; @@ -56,15 +51,15 @@ import org.openecomp.sdc.versioning.dao.types.Version; public class OrchestrationTemplateCandidateDaoZusammenImpl implements OrchestrationTemplateCandidateDao { - private static final Logger logger = LoggerFactory.getLogger(OrchestrationTemplateCandidateDaoZusammenImpl.class); + private static final Logger LOGGER = LoggerFactory.getLogger(OrchestrationTemplateCandidateDaoZusammenImpl.class); private static final String EMPTY_DATA = "{}"; - private static final String EXTERNAL_CSAR_STORE = "externalCsarStore"; private final ZusammenAdaptor zusammenAdaptor; private final ArtifactStorageManager artifactStorageManager; public OrchestrationTemplateCandidateDaoZusammenImpl(final ZusammenAdaptor zusammenAdaptor) { this.zusammenAdaptor = zusammenAdaptor; - this.artifactStorageManager = new PersistentVolumeArtifactStorageManager(readArtifactStorageConfiguration()); + LOGGER.info("Instantiating artifactStorageManager"); + this.artifactStorageManager = new StorageFactory().createArtifactStorageManager(); } @Override @@ -74,14 +69,14 @@ public class OrchestrationTemplateCandidateDaoZusammenImpl implements Orchestrat @Override public Optional get(String vspId, Version version) { - logger.info("Getting orchestration template for vsp id {}", vspId); + LOGGER.info("Getting orchestration template for vsp id {}", vspId); SessionContext context = createSessionContext(); ElementContext elementContext = new ElementContext(vspId, version.getId()); Optional candidateElement = zusammenAdaptor .getElementByName(context, elementContext, null, ElementType.OrchestrationTemplateCandidate.name()); if (!candidateElement.isPresent() || VspZusammenUtil.hasEmptyData(candidateElement.get().getData()) || candidateElement.get().getSubElements() .isEmpty()) { - logger.info("Orchestration template for vsp id {} does not exist / has empty data", vspId); + LOGGER.info("Orchestration template for vsp id {} does not exist / has empty data", vspId); return Optional.empty(); } OrchestrationTemplateCandidateData candidate = new OrchestrationTemplateCandidateData(); @@ -89,26 +84,26 @@ public class OrchestrationTemplateCandidateDaoZusammenImpl implements Orchestrat candidateElement.get().getSubElements().stream() .map(element -> zusammenAdaptor.getElement(context, elementContext, element.getElementId().toString())) .forEach(element -> element.ifPresent(candidateInfoElement -> populateCandidate(candidate, candidateInfoElement, true))); - logger.info("Finished getting orchestration template for vsp id {}", vspId); + LOGGER.info("Finished getting orchestration template for vsp id {}", vspId); return candidate.getFileSuffix() == null ? Optional.empty() : Optional.of(candidate); } @Override public Optional getInfo(String vspId, Version version) { - logger.info("Getting orchestration template info for vsp id {}", vspId); + LOGGER.info("Getting orchestration template info for vsp id {}", vspId); SessionContext context = createSessionContext(); ElementContext elementContext = new ElementContext(vspId, version.getId()); Optional candidateElement = zusammenAdaptor .getElementInfoByName(context, elementContext, null, ElementType.OrchestrationTemplateCandidate.name()); if (!candidateElement.isPresent() || candidateElement.get().getSubElements().isEmpty()) { - logger.info("Orchestration template info for vsp id {} does not exist", vspId); + LOGGER.info("Orchestration template info for vsp id {} does not exist", vspId); return Optional.empty(); } OrchestrationTemplateCandidateData candidate = new OrchestrationTemplateCandidateData(); candidateElement.get().getSubElements().stream() .map(elementInfo -> zusammenAdaptor.getElement(context, elementContext, elementInfo.getId().toString())) .forEach(element -> element.ifPresent(candidateInfoElement -> populateCandidate(candidate, candidateInfoElement, false))); - logger.info("Finished getting orchestration template info for vsp id {}", vspId); + LOGGER.info("Finished getting orchestration template info for vsp id {}", vspId); return candidate.getFileSuffix() == null ? Optional.empty() : Optional.of(candidate); } @@ -149,7 +144,7 @@ public class OrchestrationTemplateCandidateDaoZusammenImpl implements Orchestrat @Override public void update(final String vspId, final Version version, final OrchestrationTemplateCandidateData candidateData) { - logger.info("Uploading candidate data entity for vsp id {}", vspId); + LOGGER.info("Uploading candidate data entity for vsp id {}", vspId); final ZusammenElement candidateElement = buildStructuralElement(ElementType.OrchestrationTemplateCandidate, Action.UPDATE); candidateElement.setData(new ByteArrayInputStream(candidateData.getFilesDataStructure().getBytes())); final ZusammenElement candidateContentElement = buildStructuralElement(ElementType.OrchestrationTemplateCandidateContent, Action.UPDATE); @@ -170,7 +165,7 @@ public class OrchestrationTemplateCandidateDaoZusammenImpl implements Orchestrat throw new OrchestrationTemplateCandidateDaoZusammenException("No artifact info provided"); } final ArtifactInfo artifactInfo = artifactStorageManager.persist(vspId, versionId, candidateArtifactInfo); - originalPackageElement.setData(new ByteArrayInputStream(artifactInfo.getPath().toString().getBytes(StandardCharsets.UTF_8))); + originalPackageElement.setData(new ByteArrayInputStream(artifactInfo.getInfo().getBytes(StandardCharsets.UTF_8))); } else { originalPackageElement.setData(new ByteArrayInputStream(candidateData.getOriginalFileContentData().array())); } @@ -185,12 +180,12 @@ public class OrchestrationTemplateCandidateDaoZusammenImpl implements Orchestrat final var context = createSessionContext(); final var elementContext = new ElementContext(vspId, versionId); zusammenAdaptor.saveElement(context, elementContext, candidateElement, "Update Orchestration Template Candidate"); - logger.info("Finished uploading candidate data entity for vsp id {}", vspId); + LOGGER.info("Finished uploading candidate data entity for vsp id {}", vspId); } @Override public void updateValidationData(String vspId, Version version, ValidationStructureList validationData) { - logger.info("Updating validation data of orchestration template candidate for VSP id {} ", vspId); + LOGGER.info("Updating validation data of orchestration template candidate for VSP id {} ", vspId); ZusammenElement validationDataElement = buildStructuralElement(ElementType.OrchestrationTemplateCandidateValidationData, Action.UPDATE); validationDataElement.setData(validationData == null ? new ByteArrayInputStream(EMPTY_DATA.getBytes()) : new ByteArrayInputStream(JsonUtil.object2Json(validationData).getBytes())); @@ -199,23 +194,23 @@ public class OrchestrationTemplateCandidateDaoZusammenImpl implements Orchestrat SessionContext context = createSessionContext(); ElementContext elementContext = new ElementContext(vspId, version.getId()); zusammenAdaptor.saveElement(context, elementContext, candidateElement, "Update Orchestration Template Candidate validation data"); - logger.info("Finished updating validation data of orchestration template candidate for VSP id {}", vspId); + LOGGER.info("Finished updating validation data of orchestration template candidate for VSP id {}", vspId); } @Override public void updateStructure(String vspId, Version version, FilesDataStructure fileDataStructure) { - logger.info("Updating orchestration template for VSP id {}", vspId); + LOGGER.info("Updating orchestration template for VSP id {}", vspId); ZusammenElement candidateElement = buildStructuralElement(ElementType.OrchestrationTemplateCandidate, Action.UPDATE); candidateElement.setData(new ByteArrayInputStream(JsonUtil.object2Json(fileDataStructure).getBytes())); SessionContext context = createSessionContext(); ElementContext elementContext = new ElementContext(vspId, version.getId()); zusammenAdaptor.saveElement(context, elementContext, candidateElement, "Update Orchestration Template Candidate structure"); - logger.info("Finished uploading candidate data entity for vsp id {}", vspId); + LOGGER.info("Finished uploading candidate data entity for vsp id {}", vspId); } @Override public Optional getStructure(String vspId, Version version) { - logger.info("Getting orchestration template candidate structure for vsp id {}", vspId); + LOGGER.info("Getting orchestration template candidate structure for vsp id {}", vspId); SessionContext context = createSessionContext(); ElementContext elementContext = new ElementContext(vspId, version.getId()); Optional element = zusammenAdaptor @@ -223,24 +218,10 @@ public class OrchestrationTemplateCandidateDaoZusammenImpl implements Orchestrat if (element.isPresent() && !VspZusammenUtil.hasEmptyData(element.get().getData())) { return Optional.of(new String(FileUtils.toByteArray(element.get().getData()))); } - logger.info("Finished getting orchestration template candidate structure for vsp id {}", vspId); + LOGGER.info("Finished getting orchestration template candidate structure for vsp id {}", vspId); return Optional.empty(); } - private ArtifactStorageConfig readArtifactStorageConfiguration() { - final var commonConfigurationManager = CommonConfigurationManager.getInstance(); - final boolean isEnabled = commonConfigurationManager.getConfigValue(EXTERNAL_CSAR_STORE, "storeCsarsExternally", false); - logger.info("ArtifactConfig.isEnabled: '{}'", isEnabled); - final String storagePathString = commonConfigurationManager.getConfigValue(EXTERNAL_CSAR_STORE, "fullPath", null); - logger.info("ArtifactConfig.storagePath: '{}'", storagePathString); - if (isEnabled && storagePathString == null) { - throw new OrchestrationTemplateCandidateDaoZusammenException( - Messages.EXTERNAL_CSAR_STORE_CONFIGURATION_FAILURE_MISSING_FULL_PATH.getErrorMessage()); - } - final var storagePath = storagePathString == null ? null : Path.of(storagePathString); - return new PersistentVolumeArtifactStorageConfig(isEnabled, storagePath); - } - @Getter @AllArgsConstructor private enum InfoPropertyName { -- 2.16.6