0446103491b35b9a0f82d78a5170b6268afcd72f
[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         final CnfPackageValidator cnfPackageValidator) {
80         this.packageFileName = packageFileName;
81         this.packageFileContent = packageFileContent;
82         this.cnfPackageValidator = cnfPackageValidator;
83         onboardPackageInfo = processPackage();
84     }
85
86     public Optional<OnboardPackageInfo> getOnboardPackageInfo() {
87         return Optional.ofNullable(onboardPackageInfo);
88     }
89
90     public boolean hasErrors() {
91         return errorMessages.stream()
92             .anyMatch(error -> error.getLevel() == ErrorLevel.ERROR);
93     }
94
95     public boolean hasNoErrors() {
96         return errorMessages.stream()
97             .noneMatch(error -> error.getLevel() == ErrorLevel.ERROR);
98     }
99
100     public Set<ErrorMessage> getErrorMessages() {
101         return errorMessages;
102     }
103
104     private OnboardPackageInfo processPackage() {
105         OnboardPackageInfo packageInfo = null;
106         validateFile();
107         if (hasNoErrors()) {
108             final String packageName = FilenameUtils.getBaseName(packageFileName);
109             final String packageExtension = FilenameUtils.getExtension(packageFileName);
110             if (hasSignedPackageStructure()) {
111                 packageInfo = processSignedPackage(packageName, packageExtension);
112             } else {
113                 if (packageExtension.equalsIgnoreCase(CSAR_EXTENSION)) {
114                     packageInfo = processCsarPackage(packageName, packageExtension);
115                 } else if (packageExtension.equalsIgnoreCase(ZIP_EXTENSION)) {
116                     packageInfo = processOnapNativeZipPackage(packageName, packageExtension);
117                 }
118             }
119         }
120         return packageInfo;
121     }
122
123     private void validateFile() {
124         if (!hasValidExtension()) {
125             String message = PACKAGE_INVALID_EXTENSION.formatMessage(packageFileName, String.join(", ", CSAR_EXTENSION, ZIP_EXTENSION));
126             reportError(ErrorLevel.ERROR, message);
127         } else {
128             try {
129                 packageContent = CommonUtil.getZipContent(packageFileContent);
130                 if (isPackageEmpty()) {
131                     String message = PACKAGE_EMPTY_ERROR.formatMessage(packageFileName);
132                     reportError(ErrorLevel.ERROR, message);
133                 }
134             } catch (final ZipException e) {
135                 String message = PACKAGE_PROCESS_ERROR.formatMessage(packageFileName);
136                 reportError(ErrorLevel.ERROR, message);
137                 LOGGER.error(message, e);
138             }
139         }
140     }
141
142     private OnboardPackageInfo processCsarPackage(String packageName, String packageExtension) {
143         OnboardPackage onboardPackage = new OnboardPackage(packageName, packageExtension, ByteBuffer.wrap(packageFileContent),
144             new OnboardingPackageContentHandler(packageContent));
145         return new OnboardPackageInfo(onboardPackage, OnboardingTypesEnum.CSAR);
146     }
147
148     private OnboardPackageInfo processOnapNativeZipPackage(String packageName, String packageExtension) {
149         ManifestContent manifest = getManifest();
150         if (manifest != null) {
151             List<String> errors = validateZipPackage(manifest);
152             if (errors.isEmpty()) {
153                 final OnboardPackage onboardPackage = new OnboardPackage(packageName, packageExtension, ByteBuffer.wrap(packageFileContent),
154                     packageContent);
155                 return new OnboardPackageInfo(onboardPackage, OnboardingTypesEnum.ZIP);
156             } else {
157                 errors.forEach(message -> reportError(ErrorLevel.ERROR, message));
158             }
159         } else {
160             reportError(ErrorLevel.ERROR, COULD_NOT_READ_MANIFEST_FILE.formatMessage(SdcCommon.MANIFEST_NAME, packageFileName));
161         }
162         return null;
163     }
164
165     List<String> validateZipPackage(ManifestContent manifest) {
166         ManifestAnalyzer analyzer = new ManifestAnalyzer(manifest);
167         List<String> errors = Collections.emptyList();
168         if (analyzer.hasHelmEntries()) {
169             if (shouldValidateHelmPackage(analyzer)) {
170                 errors = cnfPackageValidator.validateHelmPackage(analyzer.getHelmEntries());
171             }
172         }
173         addDummyHeat(manifest);
174         return errors;
175     }
176
177     boolean shouldValidateHelmPackage(ManifestAnalyzer analyzer) {
178         return analyzer.hasHelmEntries() && !analyzer.hasHeatEntries();
179     }
180
181     private ManifestContent getManifest() {
182         ManifestContent manifest = null;
183         try (InputStream zipFileManifest = packageContent.getFileContentAsStream(SdcCommon.MANIFEST_NAME)) {
184             manifest = JsonUtil.json2Object(zipFileManifest, ManifestContent.class);
185         } catch (Exception e) {
186             final String message = COULD_NOT_READ_MANIFEST_FILE.formatMessage(SdcCommon.MANIFEST_NAME, packageFileName);
187             LOGGER.error(message, e);
188         }
189         return manifest;
190     }
191
192     private void addDummyHeat(ManifestContent manifestContent) {
193         // temporary fix for adding dummy base
194         List<FileData> newfiledata = new ArrayList<>();
195         try {
196             boolean heatBase = false;
197             for (FileData fileData : manifestContent.getData()) {
198                 if (Objects.nonNull(fileData.getType()) && fileData.getType().equals(FileData.Type.HELM) && fileData.getBase()) {
199                     heatBase = true;
200                     fileData.setBase(false);
201                     FileData dummyHeat = new FileData();
202                     dummyHeat.setBase(true);
203                     dummyHeat.setFile("base_template_dummy_ignore.yaml");
204                     dummyHeat.setType(FileData.Type.HEAT);
205                     FileData dummyEnv = new FileData();
206                     dummyEnv.setBase(false);
207                     dummyEnv.setFile("base_template_dummy_ignore.env");
208                     dummyEnv.setType(FileData.Type.HEAT_ENV);
209                     List<FileData> dataEnvList = new ArrayList<>();
210                     dataEnvList.add(dummyEnv);
211                     dummyHeat.setData(dataEnvList);
212                     newfiledata.add(dummyHeat);
213                     String filePath = new File("").getAbsolutePath() + "/resources";
214                     File envFilePath = new File(filePath + "/base_template.env");
215                     File baseFilePath = new File(filePath + "/base_template.yaml");
216                     try (InputStream envStream = new FileInputStream(envFilePath); InputStream baseStream = new FileInputStream(baseFilePath)) {
217                         packageContent.addFile("base_template_dummy_ignore.env", envStream);
218                         packageContent.addFile("base_template_dummy_ignore.yaml", baseStream);
219                     } catch (Exception e) {
220                         LOGGER.error("Failed creating input stream {}", e);
221                     }
222                 }
223             }
224             if (heatBase) {
225                 manifestContent.getData().addAll(newfiledata);
226                 InputStream manifestContentStream = new ByteArrayInputStream(
227                     (JsonUtil.object2Json(manifestContent)).getBytes(StandardCharsets.UTF_8));
228                 packageContent.remove(SdcCommon.MANIFEST_NAME);
229                 packageContent.addFile(SdcCommon.MANIFEST_NAME, manifestContentStream);
230             }
231         } catch (Exception e) {
232             final String message = PACKAGE_INVALID_ERROR.formatMessage(packageFileName);
233             LOGGER.error(message, e);
234         }
235     }
236
237     private boolean hasValidExtension() {
238         final String packageExtension = FilenameUtils.getExtension(packageFileName);
239         return packageExtension.equalsIgnoreCase(CSAR_EXTENSION) || packageExtension.equalsIgnoreCase(ZIP_EXTENSION);
240     }
241
242     private OnboardPackageInfo processSignedPackage(final String packageName, final String packageExtension) {
243         final String internalPackagePath = findInternalPackagePath().orElse(null);
244         if (internalPackagePath == null) {
245             reportError(ErrorLevel.ERROR, PACKAGE_MISSING_INTERNAL_PACKAGE.getErrorMessage());
246             return null;
247         }
248         final String signatureFilePath = findSignatureFilePath().orElse(null);
249         final String certificateFilePath = findCertificateFilePath().orElse(null);
250         final OnboardSignedPackage onboardSignedPackage = new OnboardSignedPackage(packageName, packageExtension, ByteBuffer.wrap(packageFileContent),
251             packageContent, signatureFilePath, internalPackagePath, certificateFilePath);
252         final String internalPackageName = FilenameUtils.getName(internalPackagePath);
253         final String internalPackageBaseName = FilenameUtils.getBaseName(internalPackagePath);
254         final String internalPackageExtension = FilenameUtils.getExtension(internalPackagePath);
255         final byte[] internalPackageContent = packageContent.getFileContent(internalPackagePath);
256         final OnboardPackage onboardPackage;
257         try {
258             final OnboardingPackageContentHandler fileContentHandler = new OnboardingPackageContentHandler(
259                 CommonUtil.getZipContent(internalPackageContent));
260             onboardPackage = new OnboardPackage(internalPackageBaseName, internalPackageExtension, internalPackageContent, fileContentHandler);
261         } catch (final ZipException e) {
262             final String message = PACKAGE_PROCESS_INTERNAL_PACKAGE_ERROR.formatMessage(internalPackageName);
263             LOGGER.error(message, e);
264             reportError(ErrorLevel.ERROR, message);
265             return null;
266         }
267         return new OnboardPackageInfo(onboardSignedPackage, onboardPackage, OnboardingTypesEnum.SIGNED_CSAR);
268     }
269
270     private void reportError(final ErrorLevel errorLevel, final String message) {
271         errorMessages.add(new ErrorMessage(errorLevel, message));
272     }
273
274     private Optional<String> findInternalPackagePath() {
275         return packageContent.getFileList().stream().filter(filePath -> {
276             final String extension = FilenameUtils.getExtension(filePath);
277             return CSAR_EXTENSION.equalsIgnoreCase(extension) || ZIP_EXTENSION.equalsIgnoreCase(extension);
278         }).findFirst();
279     }
280
281     private boolean isPackageEmpty() {
282         return MapUtils.isEmpty(packageContent.getFiles());
283     }
284
285     private boolean hasSignedPackageStructure() {
286         if (MapUtils.isEmpty(packageContent.getFiles()) || !CollectionUtils.isEmpty(packageContent.getFolderList())) {
287             return false;
288         }
289         final int numberOfFiles = packageContent.getFileList().size();
290         if (numberOfFiles == 2) {
291             return hasOneInternalPackageFile(packageContent) && hasOneSignatureFile(packageContent);
292         }
293         if (numberOfFiles == 3) {
294             return hasOneInternalPackageFile(packageContent) && hasOneSignatureFile(packageContent) && hasOneCertificateFile(packageContent);
295         }
296         return false;
297     }
298
299     private boolean hasOneInternalPackageFile(final FileContentHandler fileContentHandler) {
300         return fileContentHandler.getFileList().parallelStream().map(FilenameUtils::getExtension).map(String::toLowerCase)
301             .filter(file -> file.endsWith(CSAR_EXTENSION)).count() == 1;
302     }
303
304     private boolean hasOneSignatureFile(final FileContentHandler fileContentHandler) {
305         return fileContentHandler.getFileList().parallelStream().map(FilenameUtils::getExtension).map(String::toLowerCase)
306             .filter(ALLOWED_SIGNATURE_EXTENSIONS::contains).count() == 1;
307     }
308
309     private boolean hasOneCertificateFile(final FileContentHandler fileContentHandler) {
310         return fileContentHandler.getFileList().parallelStream().map(FilenameUtils::getExtension).map(String::toLowerCase)
311             .filter(ALLOWED_CERTIFICATE_EXTENSIONS::contains).count() == 1;
312     }
313
314     private Optional<String> findSignatureFilePath() {
315         final Map<String, byte[]> files = packageContent.getFiles();
316         return files.keySet().stream().filter(fileName -> ALLOWED_SIGNATURE_EXTENSIONS.contains(FilenameUtils.getExtension(fileName).toLowerCase()))
317             .findFirst();
318     }
319
320     private Optional<String> findCertificateFilePath() {
321         final Map<String, byte[]> files = packageContent.getFiles();
322         return files.keySet().stream().filter(fileName -> ALLOWED_CERTIFICATE_EXTENSIONS.contains(FilenameUtils.getExtension(fileName).toLowerCase()))
323             .findFirst();
324     }
325 }