Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ai.triton.server): Add Model Encryption support for Triton Server Service #3986

Merged
merged 91 commits into from
May 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
860ed55
feat(EncryptionUtils): add class and methods skeleton
mattdibi May 10, 2022
dce2ea5
test(EncryptionUtils): add test skeleton
mattdibi May 10, 2022
c2050b5
test(EncryptionUtils): add basic tests for createDecryptionFolder method
mattdibi May 10, 2022
d576c2c
test(EncryptionUtils): refactor to Gherkin style
mattdibi May 10, 2022
507bfda
test(EncryptionUtils): removed comments, renamed method
mattdibi May 10, 2022
1e28418
test(EncryptionUtils): rename method
mattdibi May 10, 2022
b63ca55
feature(EncryptionUtils): add createDecryptionFolder method implement…
mattdibi May 10, 2022
bab58b0
test(EncryptionUtils): improve test with workdir setup/teardown
mattdibi May 10, 2022
417a358
test(EncryptionUtils): renamed method cleanup -> teardown
mattdibi May 10, 2022
9c4973e
test(resources): add encrypted files for testing
mattdibi May 10, 2022
b1e5d85
test(EncryptionUtils): add basic tests for decryptModel method
mattdibi May 10, 2022
fb68e3f
test(EncryptionUtils): simplify teardown method
mattdibi May 10, 2022
1648678
test(EncryptionUtils): move test WORKDIR in java.io.tmpdir
mattdibi May 10, 2022
6fecb20
test(EcryptionUtils): add Gherkin style comments
mattdibi May 10, 2022
eaa23c3
test(EncryptionUtils): add test for ASCII-armored file
mattdibi May 10, 2022
89a13d7
test(EncryptionUtils): stricter checks on exception throw
mattdibi May 10, 2022
aadcb6a
docs: add copyright notice
mattdibi May 11, 2022
d56d796
test: fix expectation param order
mattdibi May 11, 2022
ea2aff3
build: add org.bouncycastle.openpgp as local dependency
mattdibi May 11, 2022
064aa01
feat(EncryptionUtils): add first implementation of decryptModel method
mattdibi May 11, 2022
baf6d06
feat(EncryptionUtils): fix BouncyCastle provider addition
mattdibi May 11, 2022
7c17f30
feat(EncryptionUtils): add preconditions check
mattdibi May 11, 2022
55e91f4
refactor: improve var names
mattdibi May 11, 2022
3799576
refactor: remove unnecessary toString
mattdibi May 11, 2022
1685b8d
feat(EncryptionUtils): move stream instantiation in decryptFile method
mattdibi May 12, 2022
c47e921
refactor: move stream instantiation close to use
mattdibi May 12, 2022
0518c2a
refactor: improve var names
mattdibi May 12, 2022
ffe0507
feat(EncryptionUtils): rethrow PGPException on decryption failure
mattdibi May 12, 2022
658723d
test(resources): rename resourse files
mattdibi May 12, 2022
513c78f
test(EncryptionUtils): add zip file test
mattdibi May 12, 2022
4f870c2
test: refactor to Gerkhin style
mattdibi May 12, 2022
8f995f1
test: remove unnecessary vars
mattdibi May 12, 2022
c8ac003
test: remove unnecessary "this."
mattdibi May 12, 2022
5760a8b
test: renamed method
mattdibi May 12, 2022
c987224
test: renamed method givenAFileExistsAtPath
mattdibi May 12, 2022
9563bec
test: add basic test for deleteModel method
mattdibi May 12, 2022
88f4903
test: add test for missing directory for deleteModel
mattdibi May 12, 2022
f3649d5
test: add check for parent folder in deleteModel test
mattdibi May 12, 2022
45f79a2
feat(EncryptionUtils): add deleteModel method implemention
mattdibi May 12, 2022
fbac91c
test: refactor to Gerkhin style
mattdibi May 12, 2022
9b8be44
refactor: renamed method to thenAFolderExistsAtPath
mattdibi May 12, 2022
148eeb6
test: remove forgotten comment
mattdibi May 12, 2022
71346e6
test: add .zip test file
mattdibi May 12, 2022
ef2cc84
test(EncryptionUtils): add unzipModel tests
mattdibi May 12, 2022
381a078
test: add unzipModel method test for wrong file format
mattdibi May 13, 2022
65e33ed
test: add decryptModel test for wrong file format
mattdibi May 13, 2022
9e5033e
feat(EncryptionUtils): add check for wrong file format
mattdibi May 13, 2022
9a2d9d1
test: improved test naming
mattdibi May 13, 2022
7915652
test(resources): fixed wrong test .zip file
mattdibi May 13, 2022
0260a14
feat(EncryptionUtils): initial implementation of unzipModel method
mattdibi May 13, 2022
f3f51ab
build: add dependency on org.eclipse.kura.core.cloud for unZip class
mattdibi May 13, 2022
960fb41
feat(EncryptionUtils): add ZIP magic number check
mattdibi May 13, 2022
82929c6
test: refactor to Gerkhin style
mattdibi May 13, 2022
c090542
feat(EncryptionUtils): remove unnecessary todo
mattdibi May 13, 2022
2471524
test: improve error handling in given/then methods
mattdibi May 13, 2022
2ec2e20
feat(EncryptionUtils): add getEncryptedModelPath method skeleton
mattdibi May 13, 2022
4805bd1
test: add getEncryptedModelPath tests
mattdibi May 13, 2022
8e8858b
test: add forgotten no exception check
mattdibi May 13, 2022
fc21474
test: fixed typo in WORKDIR path
mattdibi May 13, 2022
da6fba8
test: fix wrong "when" method
mattdibi May 13, 2022
3a4073a
feat(EncryptionUtils): add getEncryptedModelPath method implementation
mattdibi May 13, 2022
7f8d4a6
test: updated test with new behaviour for cleanModelRepository
mattdibi May 13, 2022
e10c06c
feat(EncryptionUtils): updated cleanRepository behaviour
mattdibi May 13, 2022
a781701
build: add bouncycastle lib in build properties
mattdibi May 16, 2022
f5cda5b
build: add necessary dependencies
mattdibi May 16, 2022
cd6a2fa
refactor(EncryptionUtils): do not throw from cleanRepository method
mattdibi May 16, 2022
570c69f
feat(triton): add password field
mattdibi May 16, 2022
7f4e298
feat(triton): add decrypted model folder path
mattdibi May 16, 2022
a90edf7
feat(triton): add encrypted model support
mattdibi May 16, 2022
81aa251
test: temporary path fix
mattdibi May 16, 2022
fa69e22
fix(build): use range for kura.crypto version dependency
mattdibi May 17, 2022
220d904
refactor: use try-with-resource
mattdibi May 17, 2022
d09fbc9
fix: remove debug print
mattdibi May 17, 2022
9d61f24
feat: improve decrypted model directory creation
mattdibi May 18, 2022
45ea9ab
feat(EncryptionUtils): use createTempDirectory for decryption dir
mattdibi May 18, 2022
4a5e12e
feat(EncryptionUtils): dynamic decrypted model folder
mattdibi May 18, 2022
c16cd8b
refactor: create startLocalInstance method
mattdibi May 18, 2022
46b6b9c
refactor: add isModelEncryptionActive method
mattdibi May 18, 2022
1d5a10d
refactor: rename method to modelsAreEcnrypted
mattdibi May 18, 2022
7533539
test: temporary path fix
mattdibi May 18, 2022
378f98a
Merge pull request #1 from mattdibi/fix/path_handling
mattdibi May 18, 2022
0f3ad59
test: fix test work dir path
mattdibi May 18, 2022
350964c
test: add dedicated var for temp dir path
mattdibi May 18, 2022
b217799
feat(ai.triton): handle decrypt folder deletion
mattdibi May 18, 2022
e177d72
test: add decryptModelShouldThrowWithDirectory test
mattdibi May 18, 2022
1cf011e
test: fix wrong path in unzipModel test
mattdibi May 18, 2022
a2b746e
build: use bouncy castle provider from target platform
mattdibi May 18, 2022
99f4529
revert: use bouncy castle provider from target platform
mattdibi May 19, 2022
7084df9
fix: add check for non-existent folder in getEncryptedModelPath
mattdibi May 19, 2022
c090671
build: use bouncy castle provider from target platform
mattdibi May 20, 2022
0e1f4b5
feat(EncryptionUtils): log exception stacktrace on failure
mattdibi May 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions kura/org.eclipse.kura.ai.triton.server/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Bundle-Vendor: Eclipse Kura
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Service-Component: OSGI-INF/*.xml
Bundle-ClassPath: .,
lib/bcpg-jdk15on-1.68.jar,
lib/com.google.protobuf_3.19.3.jar,
lib/grpc-netty-shaded-1.33.1.jar,
lib/grpc-protobuf-1.33.1.jar,
Expand All @@ -22,10 +23,22 @@ Import-Package: com.google.common.base;version="25.0.0",
com.google.common.util.concurrent;version="25.0.0",
com.google.gson;version="2.7.0",
javax.annotation;version="1.2.0",
org.bouncycastle.jcajce;version="1.68.0",
org.bouncycastle.jcajce.util;version="1.68.0",
org.bouncycastle.jcajce.interfaces;version="1.68.0",
org.bouncycastle.jcajce.io;version="1.68.0",
org.bouncycastle.jcajce.spec;version="1.68.0",
org.bouncycastle.jce.provider;version="1.68.0",
org.bouncycastle.util;version="1.68.0",
org.bouncycastle.util.encoders;version="1.68.0",
org.bouncycastle.util.io;version="1.68.0",
org.apache.commons.io;version="2.4.0",
org.apache.commons.io.filefilter;version="2.11.0",
org.eclipse.kura;version="[1.0,2.0)",
org.eclipse.kura.ai.inference;version="[1.0,1.1)",
org.eclipse.kura.cloud.app.command;version="[1.0,2.0)",
org.eclipse.kura.configuration;version="[1.1,2.0)",
org.eclipse.kura.crypto;version="[1.0,2.0)",
org.eclipse.kura.core.linux.executor;version="[1.0,2.0)",
org.eclipse.kura.core.util;version="[1.0,2.0)",
org.eclipse.kura.executor;version="[1.0,2.0)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@
default=""
description="Only for local instance, specify the path on the filesystem where the models are stored.">
</AD>

<AD id="local.model.repository.password"
name="Local model decryption password"
type="Password"
cardinality="0"
required="false"
default=""
description="Only for local instance, specify the password to be used for decrypting models stored in the model repository. If none is specified, models are supposed to be plaintext.">
</AD>

<AD id="local.backends.path"
name="Local backends path"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@
</service>
<property name="service.pid" type="String" value="org.eclipse.kura.ai.triton.server.TritonServerService"/>
<reference bind="setCommandExecutorService" cardinality="1..1" interface="org.eclipse.kura.executor.PrivilegedExecutorService" name="PrivilegedExecutorService" policy="static" />
<reference bind="setCryptoService" cardinality="1..1" interface="org.eclipse.kura.crypto.CryptoService" name="CryptoService" policy="static"/>
</scr:component>
1 change: 1 addition & 0 deletions kura/org.eclipse.kura.ai.triton.server/build.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ bin.includes = .,\
OSGI-INF/,\
about.html,\
about_files/,\
lib/bcpg-jdk15on-1.68.jar/,\
lib/com.google.protobuf_3.19.3.jar,\
lib/grpc-netty-shaded-1.33.1.jar,\
lib/grpc-protobuf-1.33.1.jar,\
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/*******************************************************************************
* Copyright (c) 2022 Eurotech and/or its affiliates and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Eurotech
******************************************************************************/

package org.eclipse.kura.ai.triton.server;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.security.Security;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPPBEEncryptedData;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
import org.bouncycastle.util.io.Streams;
import org.eclipse.kura.KuraIOException;
import org.eclipse.kura.cloud.app.command.UnZip;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TritonServerEncryptionUtils {

private static final Logger logger = LoggerFactory.getLogger(TritonServerEncryptionUtils.class);

private TritonServerEncryptionUtils() {
}

protected static String getEncryptedModelPath(String modelName, String folderPath) throws KuraIOException {
if (!Files.isDirectory(Paths.get(folderPath))) {
throw new KuraIOException("Model repository folder " + folderPath + " does not exist/is not a folder");
}

File dir = new File(folderPath);
FileFilter fileFilter = new WildcardFileFilter(modelName + ".*");
File[] files = dir.listFiles(fileFilter);

if (files.length != 1) {
throw new KuraIOException("No good match found in folder path");
}

return files[0].toString();
}

protected static String createDecryptionFolder(String prefix) throws IOException {
Set<PosixFilePermission> permissions = new HashSet<>(Arrays.asList(PosixFilePermission.OWNER_READ,
PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE));
Path tempFolderPath = Files.createTempDirectory(prefix, PosixFilePermissions.asFileAttribute(permissions));

logger.debug("Created temporary directory at path {}", tempFolderPath);

return tempFolderPath.toString();
}

protected static void decryptModel(String password, String inputFilePath, String outputFilePath)
throws IOException, KuraIOException {
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}

if (!Files.isRegularFile(Paths.get(inputFilePath))) {
throw new IOException("Input file " + inputFilePath + " does not exist/is not a file");
}

if (Files.exists(Paths.get(outputFilePath))) {
throw new IOException("Output file " + outputFilePath + " already exists");
}

try {
decryptFile(password, inputFilePath, outputFilePath);
} catch (PGPException e) {
throw new KuraIOException(e, "File decryption failed.");
}
}

protected static void unzipModel(String inputFilePath, String outputFolder) throws IOException {
if (!Files.isRegularFile(Paths.get(inputFilePath))) {
throw new IOException("Input file " + inputFilePath + " does not exist/is not a file");
}

if (!isZipCompressed(inputFilePath)) {
throw new IOException("ZIP magic number check failed. Wrong file format");
}

if (!Files.isDirectory(Paths.get(outputFolder))) {
throw new IOException("Output folder " + outputFolder + " does not exist/is not a folder");
}

UnZip.unZipFile(inputFilePath, outputFolder);
}

private static boolean isZipCompressed(String filePath) throws IOException {
byte b1 = 0;
byte b2 = 0;

try (InputStream is = new FileInputStream(filePath)) {
b1 = (byte) is.read();
b2 = (byte) is.read();
} catch (IOException e) {
throw new IOException(e);
}

return b1 == 0x50 && b2 == 0x4B;
}

protected static void cleanRepository(String modelRootPath) {
if (!Files.isDirectory(Paths.get(modelRootPath))) {
logger.warn("Model root folder {} does not exist", modelRootPath);
return;
}

try {
FileUtils.cleanDirectory(new File(modelRootPath));
} catch (IOException e) {
logger.warn("Cannot clean directory at path {}", modelRootPath, e);
}
}

private static void decryptFile(String password, String inputFilePath, String outputFilePath)
throws IOException, PGPException {
InputStream inStream = new BufferedInputStream(new FileInputStream(inputFilePath));

inStream = PGPUtil.getDecoderStream(inStream);

JcaPGPObjectFactory pgpFactory = new JcaPGPObjectFactory(inStream);
Object currentObj = pgpFactory.nextObject();

PGPEncryptedDataList enc;
if (currentObj instanceof PGPEncryptedDataList) {
enc = (PGPEncryptedDataList) currentObj;
} else {
enc = (PGPEncryptedDataList) pgpFactory.nextObject();
}

if (enc == null) {
inStream.close();
throw new IOException("PGP PBE File format read failed");
}

PGPPBEEncryptedData pbe = (PGPPBEEncryptedData) enc.get(0);
InputStream clear = pbe.getDataStream(new JcePBEDataDecryptorFactoryBuilder(
new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build()).setProvider("BC")
.build(password.toCharArray()));

pgpFactory = new JcaPGPObjectFactory(clear);
currentObj = pgpFactory.nextObject();

if (currentObj instanceof PGPCompressedData) {
PGPCompressedData compressedData = (PGPCompressedData) currentObj;
pgpFactory = new JcaPGPObjectFactory(compressedData.getDataStream());
currentObj = pgpFactory.nextObject();
}

PGPLiteralData literalData = (PGPLiteralData) currentObj;
InputStream decryptedStream = literalData.getInputStream();

OutputStream outStream = new FileOutputStream(outputFilePath);
Streams.pipeAll(decryptedStream, outStream);
outStream.close();
inStream.close();

if (pbe.isIntegrityProtected()) {
if (!pbe.verify()) {
logger.error("File failed integrity check");
} else {
logger.info("File integrity check passed");
}
} else {
logger.info("No file integrity check");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class TritonServerLocalManager {
private static final int MONITOR_PERIOD = 30;
private static final String[] TRITONSERVER = new String[] { "tritonserver" };

private final String decryptionFolderPath;
private final CommandExecutorService commandExecutorService;
private final TritonServerServiceOptions options;
private Command serverCommand;
Expand All @@ -44,9 +45,10 @@ public class TritonServerLocalManager {
private ScheduledFuture<?> scheduledFuture;

protected TritonServerLocalManager(TritonServerServiceOptions options,
CommandExecutorService commandExecutorService) {
CommandExecutorService commandExecutorService, String decryptionFolderPath) {
this.options = options;
this.commandExecutorService = commandExecutorService;
this.decryptionFolderPath = decryptionFolderPath;
this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
}

Expand Down Expand Up @@ -137,7 +139,11 @@ protected static void sleepFor(long timeout) {
private Command createServerCommand() {
List<String> commandString = new ArrayList<>();
commandString.add("tritonserver");
commandString.add("--model-repository=" + this.options.getModelRepositoryPath());
if (this.options.modelsAreEncrypted()) {
commandString.add("--model-repository=" + this.decryptionFolderPath);
} else {
commandString.add("--model-repository=" + this.options.getModelRepositoryPath());
}
commandString.add("--backend-directory=" + this.options.getBackendsPath());
if (!this.options.getBackendsConfigs().isEmpty()) {
this.options.getBackendsConfigs().forEach(config -> commandString.add("--backend-config=" + config));
Expand Down
Loading