640a15735f7f4fefda2b6a622c1b4cc0a01e926e
[sdc.git] /
1 /*
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2019 Nordix Foundation
4  *  Copyright (C) 2021 Nokia
5  *  ================================================================================
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *        http://www.apache.org/licenses/LICENSE-2.0
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  *  SPDX-License-Identifier: Apache-2.0
18  *  ============LICENSE_END=========================================================
19  */
20 package org.openecomp.sdc.vendorsoftwareproduct.impl.onboarding;
21
22 import static org.openecomp.sdc.common.errors.Messages.COULD_NOT_READ_MANIFEST_FILE;
23 import static org.openecomp.sdc.common.errors.Messages.PACKAGE_EMPTY_ERROR;
24 import static org.openecomp.sdc.common.errors.Messages.PACKAGE_INVALID_ERROR;
25 import static org.openecomp.sdc.common.errors.Messages.PACKAGE_INVALID_EXTENSION;
26 import static org.openecomp.sdc.common.errors.Messages.PACKAGE_MISSING_INTERNAL_PACKAGE;
27 import static org.openecomp.sdc.common.errors.Messages.PACKAGE_PROCESS_ERROR;
28 import static org.openecomp.sdc.common.errors.Messages.PACKAGE_PROCESS_INTERNAL_PACKAGE_ERROR;
29 import static org.openecomp.sdc.vendorsoftwareproduct.security.SecurityManager.ALLOWED_CERTIFICATE_EXTENSIONS;
30 import static org.openecomp.sdc.vendorsoftwareproduct.security.SecurityManager.ALLOWED_SIGNATURE_EXTENSIONS;
31
32 import java.io.ByteArrayInputStream;
33 import java.io.File;
34 import java.io.FileInputStream;
35 import java.io.InputStream;
36 import java.nio.ByteBuffer;
37 import java.nio.charset.StandardCharsets;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.HashSet;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.Objects;
44 import java.util.Optional;
45 import java.util.Set;
46 import org.apache.commons.collections4.CollectionUtils;
47 import org.apache.commons.collections4.MapUtils;
48 import org.apache.commons.io.FilenameUtils;
49 import org.openecomp.core.utilities.file.FileContentHandler;
50 import org.openecomp.core.utilities.json.JsonUtil;
51 import org.openecomp.core.utilities.orchestration.OnboardingTypesEnum;
52 import org.openecomp.sdc.common.utils.CommonUtil;
53 import org.openecomp.sdc.common.utils.SdcCommon;
54 import org.openecomp.sdc.common.zip.exception.ZipException;
55 import org.openecomp.sdc.datatypes.error.ErrorLevel;
56 import org.openecomp.sdc.datatypes.error.ErrorMessage;
57 import org.openecomp.sdc.heat.datatypes.manifest.FileData;
58 import org.openecomp.sdc.heat.datatypes.manifest.ManifestContent;
59 import org.openecomp.sdc.logging.api.Logger;
60 import org.openecomp.sdc.logging.api.LoggerFactory;
61 import org.openecomp.sdc.vendorsoftwareproduct.impl.onboarding.validation.CnfPackageValidator;
62 import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardPackage;
63 import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardPackageInfo;
64 import org.openecomp.sdc.vendorsoftwareproduct.types.OnboardSignedPackage;
65
66 public class OnboardingPackageProcessor {
67
68     private static final Logger LOGGER = LoggerFactory.getLogger(OnboardingPackageProcessor.class);
69     private static final String CSAR_EXTENSION = "csar";
70     private static final String ZIP_EXTENSION = "zip";
71     private final String packageFileName;
72     private final byte[] packageFileContent;
73     private final Set<ErrorMessage> errorMessages = new HashSet<>();
74     private final OnboardPackageInfo onboardPackageInfo;
75     private final CnfPackageValidator cnfPackageValidator;
76     private FileContentHandler packageContent;
77
78     public OnboardingPackageProcessor(final String packageFileName, final byte[] packageFileContent) {
79         this.packageFileName = packageFileName;
80         this.packageFileContent = packageFileContent;
81         this.cnfPackageValidator = new CnfPackageValidator();
82         onboardPackageInfo = processPackage();
83     }
84
85     public Optional<OnboardPackageInfo> getOnboardPackageInfo() {
86         return Optional.ofNullable(onboardPackageInfo);
87     }
88
89     public boolean hasErrors() {
90         return !errorMessages.isEmpty();
91     }
92
93     public boolean hasNoErrors() {
94         return errorMessages.isEmpty();
95     }
96
97     public Set<ErrorMessage> getErrorMessages() {
98         return errorMessages;
99     }
100
101     private OnboardPackageInfo processPackage() {
102         OnboardPackageInfo packageInfo = null;
103         validateFile();
104         if (hasNoErrors()) {
105             final String packageName = FilenameUtils.getBaseName(packageFileName);
106             final String packageExtension = FilenameUtils.getExtension(packageFileName);
107             if (hasSignedPackageStructure()) {
108                 packageInfo = processSignedPackage(packageName, packageExtension);
109             } else {
110                 if (packageExtension.equalsIgnoreCase(CSAR_EXTENSION)) {
111                     packageInfo = processCsarPackage(packageName, packageExtension);
112                 } else if (packageExtension.equalsIgnoreCase(ZIP_EXTENSION)) {
113                     packageInfo = processOnapNativeZipPackage(packageName, packageExtension);
114                 }
115             }
116         }
117         return packageInfo;
118     }
119
120     private void validateFile() {
121         if (!hasValidExtension()) {
122             String message = PACKAGE_INVALID_EXTENSION.formatMessage(packageFileName, String.join(", ", CSAR_EXTENSION, ZIP_EXTENSION));
123             reportError(ErrorLevel.ERROR, message);
124         } else {
125             try {
126                 packageContent = CommonUtil.getZipContent(packageFileContent);
127                 if (isPackageEmpty()) {
128                     String message = PACKAGE_EMPTY_ERROR.formatMessage(packageFileName);
129                     reportError(ErrorLevel.ERROR, message);
130                 }
131             } catch (final ZipException e) {
132                 String message = PACKAGE_PROCESS_ERROR.formatMessage(packageFileName);
133                 reportError(ErrorLevel.ERROR, message);
134                 LOGGER.error(message, e);
135             }
136         }
137     }
138
139     private OnboardPackageInfo processCsarPackage(String packageName, String packageExtension) {
140         OnboardPackage onboardPackage = new OnboardPackage(packageName, packageExtension, ByteBuffer.wrap(packageFileContent),
141             new OnboardingPackageContentHandler(packageContent));
142         return new OnboardPackageInfo(onboardPackage, OnboardingTypesEnum.CSAR);
143     }
144
145     private OnboardPackageInfo processOnapNativeZipPackage(String packageName, String packageExtension) {
146         ManifestContent manifest = getManifest();
147         if (manifest != null) {
148             List<String> errors = validateZipPackage(manifest);
149             if (errors.isEmpty()) {
150                 final OnboardPackage onboardPackage = new OnboardPackage(packageName, packageExtension, ByteBuffer.wrap(packageFileContent),
151                     packageContent);
152                 return new OnboardPackageInfo(onboardPackage, OnboardingTypesEnum.ZIP);
153             } else {
154                 errors.forEach(message -> reportError(ErrorLevel.ERROR, message));
155             }
156         } else {
157             reportError(ErrorLevel.ERROR, COULD_NOT_READ_MANIFEST_FILE.formatMessage(SdcCommon.MANIFEST_NAME, packageFileName));
158         }
159         return null;
160     }
161
162     List<String> validateZipPackage(ManifestContent manifest) {
163         ManifestAnalyzer analyzer = new ManifestAnalyzer(manifest);
164         List<String> errors = Collections.emptyList();
165         if (analyzer.hasHelmEntries()) {
166             if (shouldValidateHelmPackage(analyzer)) {
167                 errors = cnfPackageValidator.validateHelmPackage(analyzer.getHelmEntries());
168             }
169         }
170         addDummyHeat(manifest);
171         return errors;
172     }
173
174     boolean shouldValidateHelmPackage(ManifestAnalyzer analyzer) {
175         return analyzer.hasHelmEntries() && !analyzer.hasHeatEntries();
176     }
177
178     private ManifestContent getManifest() {
179         ManifestContent manifest = null;
180         try (InputStream zipFileManifest = packageContent.getFileContentAsStream(SdcCommon.MANIFEST_NAME)) {
181             manifest = JsonUtil.json2Object(zipFileManifest, ManifestContent.class);
182         } catch (Exception e) {
183             final String message = COULD_NOT_READ_MANIFEST_FILE.formatMessage(SdcCommon.MANIFEST_NAME, packageFileName);
184             LOGGER.error(message, e);
185         }
186         return manifest;
187     }
188
189     private void addDummyHeat(ManifestContent manifestContent) {
190         // temporary fix for adding dummy base
191         List<FileData> newfiledata = new ArrayList<>();
192         try {
193             boolean heatBase = false;
194             for (FileData fileData : manifestContent.getData()) {
195                 if (Objects.nonNull(fileData.getType()) && fileData.getType().equals(FileData.Type.HELM) && fileData.getBase()) {
196                     heatBase = true;
197                     fileData.setBase(false);
198                     FileData dummyHeat = new FileData();
199                     dummyHeat.setBase(true);
200                     dummyHeat.setFile("base_template_dummy_ignore.yaml");
201                     dummyHeat.setType(FileData.Type.HEAT);
202                     FileData dummyEnv = new FileData();
203                     dummyEnv.setBase(false);
204                     dummyEnv.setFile("base_template_dummy_ignore.env");
205                     dummyEnv.setType(FileData.Type.HEAT_ENV);
206                     List<FileData> dataEnvList = new ArrayList<>();
207                     dataEnvList.add(dummyEnv);
208                     dummyHeat.setData(dataEnvList);
209                     newfiledata.add(dummyHeat);
210                     String filePath = new File("").getAbsolutePath() + "/resources";
211                     File envFilePath = new File(filePath + "/base_template.env");
212                     File baseFilePath = new File(filePath + "/base_template.yaml");
213                     try (InputStream envStream = new FileInputStream(envFilePath); InputStream baseStream = new FileInputStream(baseFilePath)) {
214                         packageContent.addFile("base_template_dummy_ignore.env", envStream);
215                         packageContent.addFile("base_template_dummy_ignore.yaml", baseStream);
216                     } catch (Exception e) {
217                         LOGGER.error("Failed creating input stream {}", e);
218                     }
219                 }
220             }
221             if (heatBase) {
222                 manifestContent.getData().addAll(newfiledata);
223                 InputStream manifestContentStream = new ByteArrayInputStream(
224                     (JsonUtil.object2Json(manifestContent)).getBytes(StandardCharsets.UTF_8));
225                 packageContent.remove(SdcCommon.MANIFEST_NAME);
226                 packageContent.addFile(SdcCommon.MANIFEST_NAME, manifestContentStream);
227             }
228         } catch (Exception e) {
229             final String message = PACKAGE_INVALID_ERROR.formatMessage(packageFileName);
230             LOGGER.error(message, e);
231         }
232     }
233
234     private boolean hasValidExtension() {
235         final String packageExtension = FilenameUtils.getExtension(packageFileName);
236         return packageExtension.equalsIgnoreCase(CSAR_EXTENSION) || packageExtension.equalsIgnoreCase(ZIP_EXTENSION);
237     }
238
239     private OnboardPackageInfo processSignedPackage(final String packageName, final String packageExtension) {
240         final String internalPackagePath = findInternalPackagePath().orElse(null);
241         if (internalPackagePath == null) {
242             reportError(ErrorLevel.ERROR, PACKAGE_MISSING_INTERNAL_PACKAGE.getErrorMessage());
243             return null;
244         }
245         final String signatureFilePath = findSignatureFilePath().orElse(null);
246         final String certificateFilePath = findCertificateFilePath().orElse(null);
247         final OnboardSignedPackage onboardSignedPackage = new OnboardSignedPackage(packageName, packageExtension, ByteBuffer.wrap(packageFileContent),
248             packageContent, signatureFilePath, internalPackagePath, certificateFilePath);
249         final String internalPackageName = FilenameUtils.getName(internalPackagePath);
250         final String internalPackageBaseName = FilenameUtils.getBaseName(internalPackagePath);
251         final String internalPackageExtension = FilenameUtils.getExtension(internalPackagePath);
252         final byte[] internalPackageContent = packageContent.getFileContent(internalPackagePath);
253         final OnboardPackage onboardPackage;
254         try {
255             final OnboardingPackageContentHandler fileContentHandler = new OnboardingPackageContentHandler(
256                 CommonUtil.getZipContent(internalPackageContent));
257             onboardPackage = new OnboardPackage(internalPackageBaseName, internalPackageExtension, internalPackageContent, fileContentHandler);
258         } catch (final ZipException e) {
259             final String message = PACKAGE_PROCESS_INTERNAL_PACKAGE_ERROR.formatMessage(internalPackageName);
260             LOGGER.error(message, e);
261             reportError(ErrorLevel.ERROR, message);
262             return null;
263         }
264         return new OnboardPackageInfo(onboardSignedPackage, onboardPackage, OnboardingTypesEnum.SIGNED_CSAR);
265     }
266
267     private void reportError(final ErrorLevel errorLevel, final String message) {
268         errorMessages.add(new ErrorMessage(errorLevel, message));
269     }
270
271     private Optional<String> findInternalPackagePath() {
272         return packageContent.getFileList().stream().filter(filePath -> {
273             final String extension = FilenameUtils.getExtension(filePath);
274             return CSAR_EXTENSION.equalsIgnoreCase(extension) || ZIP_EXTENSION.equalsIgnoreCase(extension);
275         }).findFirst();
276     }
277
278     private boolean isPackageEmpty() {
279         return MapUtils.isEmpty(packageContent.getFiles());
280     }
281
282     private boolean hasSignedPackageStructure() {
283         if (MapUtils.isEmpty(packageContent.getFiles()) || !CollectionUtils.isEmpty(packageContent.getFolderList())) {
284             return false;
285         }
286         final int numberOfFiles = packageContent.getFileList().size();
287         if (numberOfFiles == 2) {
288             return hasOneInternalPackageFile(packageContent) && hasOneSignatureFile(packageContent);
289         }
290         if (numberOfFiles == 3) {
291             return hasOneInternalPackageFile(packageContent) && hasOneSignatureFile(packageContent) && hasOneCertificateFile(packageContent);
292         }
293         return false;
294     }
295
296     private boolean hasOneInternalPackageFile(final FileContentHandler fileContentHandler) {
297         return fileContentHandler.getFileList().parallelStream().map(FilenameUtils::getExtension).map(String::toLowerCase)
298             .filter(file -> file.endsWith(CSAR_EXTENSION)).count() == 1;
299     }
300
301     private boolean hasOneSignatureFile(final FileContentHandler fileContentHandler) {
302         return fileContentHandler.getFileList().parallelStream().map(FilenameUtils::getExtension).map(String::toLowerCase)
303             .filter(ALLOWED_SIGNATURE_EXTENSIONS::contains).count() == 1;
304     }
305
306     private boolean hasOneCertificateFile(final FileContentHandler fileContentHandler) {
307         return fileContentHandler.getFileList().parallelStream().map(FilenameUtils::getExtension).map(String::toLowerCase)
308             .filter(ALLOWED_CERTIFICATE_EXTENSIONS::contains).count() == 1;
309     }
310
311     private Optional<String> findSignatureFilePath() {
312         final Map<String, byte[]> files = packageContent.getFiles();
313         return files.keySet().stream().filter(fileName -> ALLOWED_SIGNATURE_EXTENSIONS.contains(FilenameUtils.getExtension(fileName).toLowerCase()))
314             .findFirst();
315     }
316
317     private Optional<String> findCertificateFilePath() {
318         final Map<String, byte[]> files = packageContent.getFiles();
319         return files.keySet().stream().filter(fileName -> ALLOWED_CERTIFICATE_EXTENSIONS.contains(FilenameUtils.getExtension(fileName).toLowerCase()))
320             .findFirst();
321     }
322 }