diff --git a/controller/src/main/java/org/jboss/as/controller/descriptions/ModelDescriptionConstants.java b/controller/src/main/java/org/jboss/as/controller/descriptions/ModelDescriptionConstants.java
index 04f524ffb84..ba209e5746c 100644
--- a/controller/src/main/java/org/jboss/as/controller/descriptions/ModelDescriptionConstants.java
+++ b/controller/src/main/java/org/jboss/as/controller/descriptions/ModelDescriptionConstants.java
@@ -84,6 +84,7 @@ public class ModelDescriptionConstants {
public static final String CAPABILITY_REFERENCE = "capability-reference";
public static final String CAPABILITY_REFERENCE_PATTERN_ELEMENTS = "capability-reference-pattern-elements";
public static final String CAPABILITY_REGISTRY = "capability-registry";
+ public static final String CERTIFICATE_INFO = "certificate-info";
public static final String CHILD_TYPE = "child-type";
public static final String CHILDREN = "children";
public static final String CLASSIFICATION = "classification";
diff --git a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/wildfly/installation-manager/main/module.xml b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/wildfly/installation-manager/main/module.xml
index 920f26e0fe4..61d41c6b4f3 100644
--- a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/wildfly/installation-manager/main/module.xml
+++ b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/wildfly/installation-manager/main/module.xml
@@ -25,5 +25,6 @@
+
diff --git a/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrCertificateImportHandler.java b/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrCertificateImportHandler.java
new file mode 100644
index 00000000000..67aaff0e50e
--- /dev/null
+++ b/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrCertificateImportHandler.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright The WildFly Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.wildfly.core.instmgr;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ATTACHED_STREAMS;
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FILESYSTEM_PATH;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERTIFICATE_CONTENT;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationDefinition;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.as.controller.OperationStepHandler;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.SimpleOperationDefinitionBuilder;
+import org.jboss.as.controller.registry.OperationEntry;
+import org.jboss.dmr.ModelNode;
+import org.jboss.dmr.ModelType;
+import org.wildfly.core.instmgr.logging.InstMgrLogger;
+import org.wildfly.installationmanager.MavenOptions;
+import org.wildfly.installationmanager.spi.InstallationManager;
+import org.wildfly.installationmanager.spi.InstallationManagerFactory;
+
+/**
+ * Operation handler to import a certificate used to validate components installed during updates.
+ * The certificate has to be an ASCII-armored PGP public certificate.
+ */
+public class InstMgrCertificateImportHandler extends InstMgrOperationStepHandler {
+ public static final String OPERATION_NAME = "certificate-import";
+
+ protected static final AttributeDefinition CERT_FILE = SimpleAttributeDefinitionBuilder.create(InstMgrConstants.CERT_FILE, ModelType.INT)
+ .setStorageRuntime()
+ .setRequired(false)
+ .addArbitraryDescriptor(FILESYSTEM_PATH, ModelNode.TRUE)
+ .addArbitraryDescriptor(ATTACHED_STREAMS, ModelNode.TRUE)
+ .build();
+ protected static final AttributeDefinition CERT_CONTENT = SimpleAttributeDefinitionBuilder.create(CERTIFICATE_CONTENT, ModelType.STRING)
+ .setStorageRuntime()
+ .setRequired(false)
+ .build();
+
+ public static final OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(OPERATION_NAME, InstMgrResolver.RESOLVER)
+ .withFlags(OperationEntry.Flag.HOST_CONTROLLER_ONLY)
+ .setReplyType(ModelType.OBJECT)
+ .setRuntimeOnly()
+ .setReplyValueType(ModelType.OBJECT)
+ .addParameter(CERT_FILE)
+ .build();
+
+ InstMgrCertificateImportHandler(InstMgrService imService, InstallationManagerFactory imf) {
+ super(imService, imf);
+ }
+
+ @Override
+ public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
+ context.addStep(new OperationStepHandler() {
+ @Override
+ public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
+ try {
+ Path serverHome = imService.getHomeDir();
+ MavenOptions mavenOptions = new MavenOptions(null, false);
+ InstallationManager installationManager = imf.create(serverHome, mavenOptions);
+
+ final Integer streamIndex = CERT_FILE.resolveModelAttribute(context, operation).asIntOrNull();
+ final String certificateContent = CERT_CONTENT.resolveModelAttribute(context, operation).asStringOrNull();
+
+ if (streamIndex != null && certificateContent != null) {
+ throw InstMgrLogger.ROOT_LOGGER.mutuallyExclusiveOptions(CERT_FILE.getName(), CERT_CONTENT.getName());
+ }
+
+ if (streamIndex != null) {
+ try (InputStream is = context.getAttachmentStream(streamIndex)) {
+ installationManager.acceptTrustedCertificates(is);
+ }
+ } else if (certificateContent != null) {
+ try (InputStream is = new ByteArrayInputStream(certificateContent.getBytes(StandardCharsets.UTF_8))) {
+ installationManager.acceptTrustedCertificates(is);
+ }
+ } else {
+ // throw exception
+ }
+ } catch (OperationFailedException | RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }, OperationContext.Stage.RUNTIME);
+ }
+}
diff --git a/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrCertificateParseHandler.java b/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrCertificateParseHandler.java
new file mode 100644
index 00000000000..73dfe3a0e52
--- /dev/null
+++ b/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrCertificateParseHandler.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright The WildFly Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.wildfly.core.instmgr;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ATTACHED_STREAMS;
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FILESYSTEM_PATH;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERT_DESCRIPTION;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERT_FINGERPRINT;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERT_KEY_ID;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERT_STATUS;
+
+import java.io.InputStream;
+import java.nio.file.Path;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationDefinition;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.as.controller.OperationStepHandler;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.SimpleOperationDefinitionBuilder;
+import org.jboss.as.controller.registry.OperationEntry;
+import org.jboss.dmr.ModelNode;
+import org.jboss.dmr.ModelType;
+import org.wildfly.installationmanager.MavenOptions;
+import org.wildfly.installationmanager.TrustCertificate;
+import org.wildfly.installationmanager.spi.InstallationManager;
+import org.wildfly.installationmanager.spi.InstallationManagerFactory;
+
+/**
+ * Operation handler to parse the certificate file and read the certificate information. Expects am ASCII-armored PGP public key.
+ */
+public class InstMgrCertificateParseHandler extends InstMgrOperationStepHandler {
+ public static final String OPERATION_NAME = "certificate-parse";
+
+ protected static final AttributeDefinition CERT_FILE = SimpleAttributeDefinitionBuilder.create(InstMgrConstants.CERT_FILE, ModelType.INT)
+ .setStorageRuntime()
+ .setRequired(true)
+ .addArbitraryDescriptor(FILESYSTEM_PATH, ModelNode.TRUE)
+ .addArbitraryDescriptor(ATTACHED_STREAMS, ModelNode.TRUE)
+ .build();
+
+ public static final OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(OPERATION_NAME, InstMgrResolver.RESOLVER)
+ .withFlags(OperationEntry.Flag.HOST_CONTROLLER_ONLY)
+ .setReplyType(ModelType.OBJECT)
+ .setRuntimeOnly()
+ .setReplyValueType(ModelType.OBJECT)
+ .addParameter(CERT_FILE)
+ .build();
+
+ InstMgrCertificateParseHandler(InstMgrService imService, InstallationManagerFactory imf) {
+ super(imService, imf);
+ }
+
+ @Override
+ public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
+ context.addStep(new OperationStepHandler() {
+ @Override
+ public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
+ try {
+ Path serverHome = imService.getHomeDir();
+ MavenOptions mavenOptions = new MavenOptions(null, false);
+ InstallationManager installationManager = imf.create(serverHome, mavenOptions);
+
+ try (InputStream is = context.getAttachmentStream(CERT_FILE.resolveModelAttribute(context, operation).asInt())) {
+ final TrustCertificate tc = installationManager.parseCertificate(is);
+
+ final ModelNode entry = new ModelNode();
+ entry.get(CERT_KEY_ID).set(tc.getKeyID());
+ entry.get(CERT_FINGERPRINT).set(tc.getFingerprint());
+ entry.get(CERT_DESCRIPTION).set(tc.getDescription());
+ entry.get(CERT_STATUS).set(tc.getStatus());
+ context.getResult().set(entry);
+ }
+ } catch (OperationFailedException | RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }, OperationContext.Stage.RUNTIME);
+ }
+}
diff --git a/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrCertificateRemoveHandler.java b/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrCertificateRemoveHandler.java
new file mode 100644
index 00000000000..e8d6ae2105d
--- /dev/null
+++ b/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrCertificateRemoveHandler.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright The WildFly Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.wildfly.core.instmgr;
+
+import static org.wildfly.core.instmgr.InstMgrConstants.CERT_KEY_ID;
+
+import java.nio.file.Path;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationDefinition;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.as.controller.OperationStepHandler;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.SimpleOperationDefinitionBuilder;
+import org.jboss.as.controller.registry.OperationEntry;
+import org.jboss.dmr.ModelNode;
+import org.jboss.dmr.ModelType;
+import org.wildfly.installationmanager.MavenOptions;
+import org.wildfly.installationmanager.spi.InstallationManager;
+import org.wildfly.installationmanager.spi.InstallationManagerFactory;
+
+/**
+ * Operation handler to remove one of imported component certificates. The certificate is identified by it's ID (in hex format).
+ * Once the certificate is removed, the user will have to re-import it to apply and updates including a component
+ * signed by that certificate.
+ */
+public class InstMgrCertificateRemoveHandler extends InstMgrOperationStepHandler {
+ public static final String OPERATION_NAME = "certificate-remove";
+
+ protected static final AttributeDefinition KEY_ID = SimpleAttributeDefinitionBuilder.create(CERT_KEY_ID, ModelType.STRING)
+ .setStorageRuntime()
+ .setRequired(true)
+ .build();
+
+ public static final OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(OPERATION_NAME, InstMgrResolver.RESOLVER)
+ .withFlags(OperationEntry.Flag.HOST_CONTROLLER_ONLY)
+ .setReplyType(ModelType.OBJECT)
+ .setRuntimeOnly()
+ .setReplyValueType(ModelType.OBJECT)
+ .addParameter(KEY_ID)
+ .build();
+
+ InstMgrCertificateRemoveHandler(InstMgrService imService, InstallationManagerFactory imf) {
+ super(imService, imf);
+ }
+
+ @Override
+ public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
+ final String keyId = KEY_ID.resolveModelAttribute(context, operation).asString();
+ context.addStep(new OperationStepHandler() {
+ @Override
+ public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
+ try {
+ Path serverHome = imService.getHomeDir();
+ MavenOptions mavenOptions = new MavenOptions(null, false);
+ InstallationManager installationManager = imf.create(serverHome, mavenOptions);
+
+ installationManager.revokeTrustedCertificate(keyId);
+ } catch (OperationFailedException | RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }, OperationContext.Stage.RUNTIME);
+ }
+}
diff --git a/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrConstants.java b/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrConstants.java
index 2abebd93f1f..dccd9f4a058 100644
--- a/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrConstants.java
+++ b/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrConstants.java
@@ -15,6 +15,14 @@ public interface InstMgrConstants {
Path PREPARED_SERVER_SUBPATH = Paths.get("installation-manager")
.resolve("prepared-server");
+ String CERT_DESCRIPTION = "description";
+ String CERT_FINGERPRINT = "fingerprint";
+ String CERT_KEY_ID = "key-id";
+ String CERT_STATUS = "status";
+ String CERT_FILE = "cert-file";
+ String CERTIFICATE = "certificate";
+ String CERTIFICATE_CONTENT = "certificate-content";
+ String CERTIFICATES = "certificates";
String CHANNEL = "channel";
String CHANNELS = "channels";
String CHANNEL_NAME = "name";
diff --git a/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrListUpdatesHandler.java b/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrListUpdatesHandler.java
index efaf649265b..9eb5da7d3ad 100644
--- a/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrListUpdatesHandler.java
+++ b/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrListUpdatesHandler.java
@@ -27,6 +27,7 @@
import org.wildfly.core.instmgr.logging.InstMgrLogger;
import org.wildfly.installationmanager.ArtifactChange;
import org.wildfly.installationmanager.MavenOptions;
+import org.wildfly.installationmanager.MissingSignatureException;
import org.wildfly.installationmanager.Repository;
import org.wildfly.installationmanager.spi.InstallationManager;
import org.wildfly.installationmanager.spi.InstallationManagerFactory;
@@ -177,6 +178,8 @@ public void handleResult(OperationContext.ResultAction resultAction, OperationCo
throw new OperationFailedException(e.getLocalizedMessage());
} catch (OperationFailedException | RuntimeException e) {
throw e;
+ } catch (MissingSignatureException e) {
+ throw InstMgrLogger.ROOT_LOGGER.componentSignedWithUnknownCertificate(e.getDescription(), e);
} catch (Exception e) {
throw new RuntimeException(e);
}
diff --git a/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrPrepareRevertHandler.java b/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrPrepareRevertHandler.java
index eaffed1b9bc..05b89fb3fea 100644
--- a/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrPrepareRevertHandler.java
+++ b/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrPrepareRevertHandler.java
@@ -26,6 +26,7 @@
import org.jboss.dmr.ModelType;
import org.wildfly.core.instmgr.logging.InstMgrLogger;
import org.wildfly.installationmanager.MavenOptions;
+import org.wildfly.installationmanager.MissingSignatureException;
import org.wildfly.installationmanager.Repository;
import org.wildfly.installationmanager.spi.InstallationManager;
import org.wildfly.installationmanager.spi.InstallationManagerFactory;
@@ -147,6 +148,8 @@ public void execute(OperationContext context, ModelNode operation) throws Operat
throw new OperationFailedException(e.getLocalizedMessage());
} catch (OperationFailedException | RuntimeException e) {
throw e;
+ } catch (MissingSignatureException e) {
+ throw InstMgrLogger.ROOT_LOGGER.componentSignedWithUnknownCertificate(e.getDescription(), e);
} catch (Exception e) {
throw new RuntimeException(e);
}
diff --git a/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrPrepareUpdateHandler.java b/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrPrepareUpdateHandler.java
index ee1aad22d4f..a3cd98db673 100644
--- a/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrPrepareUpdateHandler.java
+++ b/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrPrepareUpdateHandler.java
@@ -29,6 +29,7 @@
import org.jboss.dmr.ModelType;
import org.wildfly.core.instmgr.logging.InstMgrLogger;
import org.wildfly.installationmanager.MavenOptions;
+import org.wildfly.installationmanager.MissingSignatureException;
import org.wildfly.installationmanager.Repository;
import org.wildfly.installationmanager.spi.InstallationManager;
import org.wildfly.installationmanager.spi.InstallationManagerFactory;
@@ -189,6 +190,8 @@ public void execute(OperationContext context, ModelNode operation) throws Operat
throw new OperationFailedException(e.getLocalizedMessage());
} catch (OperationFailedException | RuntimeException e) {
throw e;
+ } catch (MissingSignatureException e) {
+ throw InstMgrLogger.ROOT_LOGGER.componentSignedWithUnknownCertificate(e.getDescription(), e);
} catch (Exception e) {
throw new RuntimeException(e);
}
diff --git a/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrResourceDefinition.java b/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrResourceDefinition.java
index 890cb8c088a..66fdfc562f0 100644
--- a/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrResourceDefinition.java
+++ b/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrResourceDefinition.java
@@ -39,6 +39,7 @@
import org.wildfly.installationmanager.Channel;
import org.wildfly.installationmanager.MavenOptions;
import org.wildfly.installationmanager.Repository;
+import org.wildfly.installationmanager.TrustCertificate;
import org.wildfly.installationmanager.spi.InstallationManager;
import org.wildfly.installationmanager.spi.InstallationManagerFactory;
@@ -86,6 +87,12 @@ class InstMgrResourceDefinition extends SimpleResourceDefinition {
.setRequired(true)
.build();
+ private static final AttributeDefinition KEY_ID = new SimpleAttributeDefinitionBuilder(InstMgrConstants.CERT_KEY_ID, ModelType.STRING)
+ .setStorageRuntime()
+ .setRuntimeServiceNotRequired()
+ .setRequired(true)
+ .build();
+
private static final AttributeDefinition MANIFEST_GAV = new SimpleAttributeDefinitionBuilder(InstMgrConstants.MANIFEST_GAV, ModelType.STRING)
.setAlternatives(InstMgrConstants.MANIFEST_URL)
.setStorageRuntime()
@@ -115,11 +122,22 @@ class InstMgrResourceDefinition extends SimpleResourceDefinition {
.setSuffix("channel")
.build();
+ private static final ObjectTypeAttributeDefinition CERTIFICATE = ObjectTypeAttributeDefinition.create(InstMgrConstants.CERTIFICATE, KEY_ID)
+ .setStorageRuntime()
+ .setRuntimeServiceNotRequired()
+ .setRequired(true)
+ .setSuffix("certificate")
+ .build();
+
private static final AttributeDefinition CHANNELS = ObjectListAttributeDefinition.Builder.of(InstMgrConstants.CHANNELS, CHANNEL)
.setStorageRuntime()
.setRuntimeServiceNotRequired()
.build();
+ private static final AttributeDefinition CERTIFICATES = ObjectListAttributeDefinition.Builder.of("certificates", CERTIFICATE)
+ .setStorageRuntime()
+ .setRuntimeServiceNotRequired()
+ .build();
public static PathElement getPath(String name) {
return PathElement.pathElement(CORE_SERVICE, name);
}
@@ -167,11 +185,24 @@ public void registerOperations(ManagementResourceRegistration resourceRegistrati
InstMgrCustomPatchRemoveHandler customPatchRemoveHandler = new InstMgrCustomPatchRemoveHandler(imService, imf);
resourceRegistration.registerOperationHandler(customPatchRemoveHandler.DEFINITION, customPatchRemoveHandler);
+
+ InstMgrCertificateParseHandler certificateParseHandler = new InstMgrCertificateParseHandler(imService, imf);
+ resourceRegistration.registerOperationHandler(InstMgrCertificateParseHandler.DEFINITION, certificateParseHandler);
+
+ InstMgrCertificateImportHandler certificateImportHandler = new InstMgrCertificateImportHandler(imService, imf);
+ resourceRegistration.registerOperationHandler(InstMgrCertificateImportHandler.DEFINITION, certificateImportHandler);
+
+ InstMgrCertificateRemoveHandler certificateRemoveHandler = new InstMgrCertificateRemoveHandler(imService, imf);
+ resourceRegistration.registerOperationHandler(InstMgrCertificateRemoveHandler.DEFINITION, certificateRemoveHandler);
+
+ InstMgrUnacceptedCertificateHandler unacceptedcertificateHandler = new InstMgrUnacceptedCertificateHandler(imService, imf);
+ resourceRegistration.registerOperationHandler(InstMgrUnacceptedCertificateHandler.DEFINITION, unacceptedcertificateHandler);
}
@Override
public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
resourceRegistration.registerReadWriteAttribute(CHANNELS, new ReadHandler(), new WriteHandler());
+ resourceRegistration.registerReadOnlyAttribute(CERTIFICATES, new CertReadHandler());
}
private class WriteHandler implements OperationStepHandler {
@@ -364,4 +395,38 @@ public void validateParameter(String parameterName, ModelNode value) throws Oper
}
}
}
+
+ private class CertReadHandler implements OperationStepHandler {
+ @Override
+ public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
+ context.addStep(new OperationStepHandler() {
+ @Override
+ public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
+ try {
+ final ModelNode result = context.getResult();
+ Path serverHome = imService.getHomeDir();
+
+ MavenOptions mavenOptions = new MavenOptions(null, false);
+ InstallationManager installationManager = imf.create(serverHome, mavenOptions);
+
+ ModelNode mCertificates = new ModelNode().addEmptyList();
+ Collection trustedCertificates = installationManager.listTrustedCertificates();
+ for (TrustCertificate tc : trustedCertificates) {
+ ModelNode entry = new ModelNode();
+ entry.get(InstMgrConstants.CERT_KEY_ID).set(tc.getKeyID());
+ entry.get(InstMgrConstants.CERT_FINGERPRINT).set(tc.getFingerprint());
+ entry.get(InstMgrConstants.CERT_DESCRIPTION).set(tc.getDescription());
+ entry.get(InstMgrConstants.CERT_STATUS).set(tc.getStatus());
+ mCertificates.add(entry);
+ }
+ result.set(mCertificates);
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }, OperationContext.Stage.RUNTIME);
+ }
+ }
}
diff --git a/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrUnacceptedCertificateHandler.java b/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrUnacceptedCertificateHandler.java
new file mode 100644
index 00000000000..b1d4e70b8e3
--- /dev/null
+++ b/installation-manager/src/main/java/org/wildfly/core/instmgr/InstMgrUnacceptedCertificateHandler.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright The WildFly Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.wildfly.core.instmgr;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CERTIFICATE_INFO;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERTIFICATE_CONTENT;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERT_DESCRIPTION;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERT_FINGERPRINT;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERT_KEY_ID;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERT_STATUS;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.Collection;
+
+import org.jboss.as.controller.AttributeDefinition;
+import org.jboss.as.controller.OperationContext;
+import org.jboss.as.controller.OperationDefinition;
+import org.jboss.as.controller.OperationFailedException;
+import org.jboss.as.controller.OperationStepHandler;
+import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
+import org.jboss.as.controller.SimpleOperationDefinitionBuilder;
+import org.jboss.as.controller.registry.OperationEntry;
+import org.jboss.dmr.ModelNode;
+import org.jboss.dmr.ModelType;
+import org.wildfly.installationmanager.MavenOptions;
+import org.wildfly.installationmanager.TrustCertificate;
+import org.wildfly.installationmanager.spi.InstallationManager;
+import org.wildfly.installationmanager.spi.InstallationManagerFactory;
+
+/**
+ * Operation handler to get the certificates potentially required by the update or revert operation. Those certificates
+ * are listed by the channel and may be used to sign the components included in the channel.
+ */
+public class InstMgrUnacceptedCertificateHandler extends InstMgrOperationStepHandler {
+ public static final String OPERATION_NAME = "unaccepted-certificates";
+
+ protected static final AttributeDefinition OFFLINE = SimpleAttributeDefinitionBuilder.create(InstMgrConstants.OFFLINE, ModelType.BOOLEAN)
+ .setStorageRuntime()
+ .setDefaultValue(ModelNode.FALSE)
+ .setRequired(false)
+ .build();
+
+ public static final OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(OPERATION_NAME, InstMgrResolver.RESOLVER)
+ .withFlags(OperationEntry.Flag.HOST_CONTROLLER_ONLY)
+ .setReplyType(ModelType.OBJECT)
+ .setRuntimeOnly()
+ .setReplyValueType(ModelType.OBJECT)
+ .addParameter(OFFLINE)
+ .build();
+
+ InstMgrUnacceptedCertificateHandler(InstMgrService imService, InstallationManagerFactory imf) {
+ super(imService, imf);
+ }
+
+ @Override
+ public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
+ context.addStep(new OperationStepHandler() {
+ @Override
+ public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
+ try {
+ final Path serverHome = imService.getHomeDir();
+ final boolean offline = OFFLINE.resolveModelAttribute(context, operation).asBoolean();
+ final MavenOptions mavenOptions = new MavenOptions(null, offline);
+ final InstallationManager installationManager = imf.create(serverHome, mavenOptions);
+
+ final Collection downloadedCerts = installationManager.downloadRequiredCertificates();
+
+ final ModelNode mCertificates = new ModelNode().addEmptyList();
+ for (InputStream is : downloadedCerts) {
+ final ModelNode mCert = new ModelNode();
+ try (BufferedInputStream bis = new BufferedInputStream(is)) {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ for (int result = bis.read(); result != -1; result = bis.read()) {
+ buf.write((byte) result);
+ }
+
+ final String certContent = buf.toString(StandardCharsets.UTF_8);
+ mCert.get(CERTIFICATE_CONTENT).set(certContent);
+ try (final ByteArrayInputStream bais = new ByteArrayInputStream(certContent.getBytes(StandardCharsets.UTF_8))) {
+ final TrustCertificate tc = installationManager.parseCertificate(bais);
+ final ModelNode info = new ModelNode();
+ info.get(CERT_KEY_ID).set(tc.getKeyID());
+ info.get(CERT_FINGERPRINT).set(tc.getFingerprint());
+ info.get(CERT_DESCRIPTION).set(tc.getDescription());
+ info.get(CERT_STATUS).set(tc.getStatus());
+ context.getResult().set(info);
+
+ mCert.get(CERTIFICATE_INFO).set(info);
+ }
+ }
+
+
+ mCertificates.add(mCert);
+ }
+
+ final ModelNode result = context.getResult();
+ result.set(mCertificates);
+ } catch (OperationFailedException | RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }, OperationContext.Stage.RUNTIME);
+ }
+}
diff --git a/installation-manager/src/main/java/org/wildfly/core/instmgr/cli/AbstractInstMgrCommand.java b/installation-manager/src/main/java/org/wildfly/core/instmgr/cli/AbstractInstMgrCommand.java
index 578f77b9aeb..85c6d3cdad2 100644
--- a/installation-manager/src/main/java/org/wildfly/core/instmgr/cli/AbstractInstMgrCommand.java
+++ b/installation-manager/src/main/java/org/wildfly/core/instmgr/cli/AbstractInstMgrCommand.java
@@ -61,6 +61,10 @@ public abstract class AbstractInstMgrCommand implements Command {
public static final String COMMAND_NAME = "installer";
diff --git a/installation-manager/src/main/java/org/wildfly/core/instmgr/cli/ListCertificatesCommand.java b/installation-manager/src/main/java/org/wildfly/core/instmgr/cli/ListCertificatesCommand.java
new file mode 100644
index 00000000000..c2873e67ffe
--- /dev/null
+++ b/installation-manager/src/main/java/org/wildfly/core/instmgr/cli/ListCertificatesCommand.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright The WildFly Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.wildfly.core.instmgr.cli;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAME;
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERT_DESCRIPTION;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERT_FINGERPRINT;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERT_KEY_ID;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERT_STATUS;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.aesh.command.CommandDefinition;
+import org.aesh.command.CommandException;
+import org.aesh.command.CommandResult;
+import org.jboss.as.cli.CommandContext;
+import org.jboss.as.cli.Util;
+import org.jboss.as.controller.client.ModelControllerClient;
+import org.jboss.as.controller.client.Operation;
+import org.jboss.as.controller.client.OperationBuilder;
+import org.jboss.dmr.ModelNode;
+import org.wildfly.core.cli.command.aesh.CLICommandInvocation;
+import org.wildfly.core.instmgr.InstMgrConstants;
+
+@CommandDefinition(name = "certificates-list", description = "List trusted component certificates.", activator = InstMgrActivator.class)
+public class ListCertificatesCommand extends AbstractInstMgrCommand {
+
+ @Override
+ protected Operation buildOperation() {
+ final ModelNode op = new ModelNode();
+ op.get(Util.OPERATION).set(Util.READ_RESOURCE);
+ op.get(Util.INCLUDE_RUNTIME).set(true);
+
+ return OperationBuilder.create(op, true).build();
+ }
+
+ @Override
+ public CommandResult execute(CLICommandInvocation commandInvocation) throws CommandException, InterruptedException {
+ final CommandContext ctx = commandInvocation.getCommandContext();
+ final ModelControllerClient client = ctx.getModelControllerClient();
+ if (client == null) {
+ ctx.printLine("You are disconnected at the moment. Type 'connect' to connect to the server or 'help' for the list of supported commands.");
+ return CommandResult.FAILURE;
+ }
+
+ ModelNode response = this.executeOp(commandInvocation.getCommandContext(), this.host);
+ ModelNode result = response.get(Util.RESULT);
+ List certificatesMn = result.get(InstMgrConstants.CERTIFICATES).asListOrEmpty();
+
+ if (certificatesMn.isEmpty()) {
+ ctx.printLine("No component certificates have been trusted for this installation.");
+
+ return CommandResult.SUCCESS;
+ }
+
+ ctx.printLine("-------");
+ for (ModelNode certificate : certificatesMn) {
+ ctx.printLine("key ID " + certificate.get(CERT_KEY_ID).asString());
+ ctx.printLine("fingerprint " + certificate.get(CERT_FINGERPRINT).asString());
+ ctx.printLine("description " + certificate.get(CERT_DESCRIPTION).asString());
+ ctx.printLine("status " + certificate.get(CERT_STATUS).asString());
+ ctx.printLine("-------");
+ }
+ return CommandResult.SUCCESS;
+ }
+
+ /**
+ * Returns a Set with the current channel names available on the server installation
+ *
+ * @param ctx
+ * @param host
+ * @return
+ * @throws CommandException
+ */
+ Set getAllChannelNames(CommandContext ctx, String host) throws CommandException {
+ final Set existingChannelNames = new HashSet<>();
+ ModelNode listCmdResponse = this.executeOp(ctx, host);
+ if (listCmdResponse.hasDefined(RESULT)) {
+ ModelNode result = listCmdResponse.get(RESULT);
+ List channels = result.get(InstMgrConstants.CHANNELS).asListOrEmpty();
+ for (ModelNode channel : channels) {
+ existingChannelNames.add(channel.get(NAME).asString());
+ }
+ }
+ return existingChannelNames;
+ }
+}
diff --git a/installation-manager/src/main/java/org/wildfly/core/instmgr/cli/RemoveCertificatesCommand.java b/installation-manager/src/main/java/org/wildfly/core/instmgr/cli/RemoveCertificatesCommand.java
new file mode 100644
index 00000000000..e33ccaa8460
--- /dev/null
+++ b/installation-manager/src/main/java/org/wildfly/core/instmgr/cli/RemoveCertificatesCommand.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright The WildFly Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.wildfly.core.instmgr.cli;
+
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERT_KEY_ID;
+
+import org.aesh.command.CommandDefinition;
+import org.aesh.command.CommandException;
+import org.aesh.command.CommandResult;
+import org.aesh.command.option.Option;
+import org.jboss.as.cli.CommandContext;
+import org.jboss.as.controller.client.Operation;
+import org.jboss.as.controller.client.OperationBuilder;
+import org.jboss.dmr.ModelNode;
+import org.wildfly.core.cli.command.aesh.CLICommandInvocation;
+import org.wildfly.core.instmgr.InstMgrCertificateRemoveHandler;
+
+@CommandDefinition(name = "certificates-remove", description = "Removes a trusted component certificate.", activator = InstMgrActivator.class)
+public class RemoveCertificatesCommand extends AbstractInstMgrCommand {
+
+ @Option(name = CERT_KEY_ID, required = true)
+ private String keyId;
+ @Override
+ protected Operation buildOperation() {
+ final ModelNode op = new ModelNode();
+
+ op.get(OP).set(InstMgrCertificateRemoveHandler.DEFINITION.getName());
+ op.get(CERT_KEY_ID).set(keyId);
+
+ return OperationBuilder.create(op).build();
+ }
+
+ @Override
+ public CommandResult execute(CLICommandInvocation commandInvocation) throws CommandException, InterruptedException {
+ final CommandContext ctx = commandInvocation.getCommandContext();
+
+ this.executeOp(ctx, this.host);
+
+ return CommandResult.SUCCESS;
+ }
+}
diff --git a/installation-manager/src/main/java/org/wildfly/core/instmgr/cli/UpdateCommand.java b/installation-manager/src/main/java/org/wildfly/core/instmgr/cli/UpdateCommand.java
index 52ebf8993db..0707526283a 100644
--- a/installation-manager/src/main/java/org/wildfly/core/instmgr/cli/UpdateCommand.java
+++ b/installation-manager/src/main/java/org/wildfly/core/instmgr/cli/UpdateCommand.java
@@ -5,9 +5,17 @@
package org.wildfly.core.instmgr.cli;
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CERTIFICATE_INFO;
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERTIFICATE_CONTENT;
+import static org.wildfly.core.instmgr.InstMgrConstants.OFFLINE;
+
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import org.aesh.command.CommandDefinition;
@@ -23,9 +31,12 @@
import org.jboss.as.cli.operation.ParsedCommandLine;
import org.jboss.as.controller.client.ModelControllerClient;
import org.jboss.as.controller.client.Operation;
+import org.jboss.as.controller.client.OperationBuilder;
import org.jboss.dmr.ModelNode;
import org.wildfly.core.cli.command.aesh.CLICommandInvocation;
+import org.wildfly.core.instmgr.InstMgrCertificateImportHandler;
import org.wildfly.core.instmgr.InstMgrConstants;
+import org.wildfly.core.instmgr.InstMgrUnacceptedCertificateHandler;
@CommandDefinition(name = "update", description = "Apply the latest available patches on a server instance.", activator = InstMgrActivator.class)
public class UpdateCommand extends AbstractInstMgrCommand {
@@ -68,6 +79,13 @@ public CommandResult execute(CLICommandInvocation commandInvocation) throws Comm
final Boolean optNoResolveLocalCache = cmdParser.hasProperty("--" + NO_RESOLVE_LOCAL_CACHE_OPTION) ? noResolveLocalCache : null;
final Boolean optUseDefaultLocalCache = cmdParser.hasProperty("--" + USE_DEFAULT_LOCAL_CACHE_OPTION) ? useDefaultLocalCache : null;
+ // call the download handler
+ Collection pendingCertificates = getPendingCertificates(ctx);
+ // call the import handler
+ if (!pendingCertificates.isEmpty() && !importPendingCertificates(pendingCertificates, ctx, commandInvocation)) {
+ return CommandResult.SUCCESS;
+ }
+
ListUpdatesAction.Builder listUpdatesCmdBuilder = new ListUpdatesAction.Builder()
.setNoResolveLocalCache(optNoResolveLocalCache)
.setUseDefaultLocalCache(optUseDefaultLocalCache)
@@ -146,6 +164,61 @@ public CommandResult execute(CLICommandInvocation commandInvocation) throws Comm
return CommandResult.SUCCESS;
}
+ private boolean importPendingCertificates(Collection pendingCertificates, CommandContext ctx, CLICommandInvocation commandInvocation) throws CommandException, InterruptedException {
+ commandInvocation.println("The update is configured to verify the integrity of updated components, but following certificates need to be trusted:");
+
+ for (ModelNode pendingCertificate : pendingCertificates) {
+ final ModelNode certificateInfo = pendingCertificate.get(CERTIFICATE_INFO);
+
+ commandInvocation.println("key-id: " + certificateInfo.get(InstMgrConstants.CERT_KEY_ID));
+ commandInvocation.println("fingerprint: " + certificateInfo.get(InstMgrConstants.CERT_FINGERPRINT));
+ commandInvocation.println("description: " + certificateInfo.get(InstMgrConstants.CERT_DESCRIPTION));
+ commandInvocation.println("");
+
+ }
+ final String input = commandInvocation.inputLine(new Prompt("Import these certificates y/N "));
+ if ("y".equals(input)) {
+ commandInvocation.print("Importing a trusted certificate");
+
+ for (ModelNode pendingCertificate : pendingCertificates) {
+ this.executeOp(buildImportOperation(pendingCertificate.get(CERTIFICATE_CONTENT).asString()), ctx, host);
+ }
+
+ return true;
+ } else {
+ commandInvocation.print("Importing canceled.");
+ return false;
+ }
+ }
+
+ protected Operation buildImportOperation(String certificateContent) {
+ final ModelNode op = new ModelNode();
+ final OperationBuilder operationBuilder = OperationBuilder.create(op);
+
+ op.get(OP).set(InstMgrCertificateImportHandler.DEFINITION.getName());
+ op.get(CERTIFICATE_CONTENT).set(certificateContent);
+
+ return operationBuilder.build();
+ }
+
+ private Collection getPendingCertificates(CommandContext ctx) throws CommandException {
+ if (confirm || dryRun) {
+ // skip the check in non-interactive runs because the certificate cannot be accepted either way
+ // the update will fail if certificate is required and will print error message
+ return Collections.emptyList();
+ }
+
+ final ModelNode op = new ModelNode();
+
+ op.get(OP).set(InstMgrUnacceptedCertificateHandler.DEFINITION.getName());
+ op.get(OFFLINE).set(offline);
+
+ final ModelNode modelNode = executeOp(OperationBuilder.create(op).build(), ctx, this.host);
+
+ // certificate-content & certificate-info
+ return modelNode.get(RESULT).asListOrEmpty();
+ }
+
private void printListUpdatesResult(CLICommandInvocation commandInvocation, List changesMn) {
if (changesMn.isEmpty()) {
commandInvocation.println("No updates found");
diff --git a/installation-manager/src/main/java/org/wildfly/core/instmgr/logging/InstMgrLogger.java b/installation-manager/src/main/java/org/wildfly/core/instmgr/logging/InstMgrLogger.java
index fa9d6639bc7..6e0a8a23442 100644
--- a/installation-manager/src/main/java/org/wildfly/core/instmgr/logging/InstMgrLogger.java
+++ b/installation-manager/src/main/java/org/wildfly/core/instmgr/logging/InstMgrLogger.java
@@ -13,6 +13,7 @@
import org.jboss.as.controller.OperationFailedException;
import org.jboss.logging.BasicLogger;
import org.jboss.logging.Logger;
+import org.jboss.logging.annotations.Cause;
import org.jboss.logging.annotations.LogMessage;
import org.jboss.logging.annotations.Message;
import org.jboss.logging.annotations.MessageLogger;
@@ -105,6 +106,11 @@ public interface InstMgrLogger extends BasicLogger {
@Message(id = 25, value = "Cannot report installation channels: '%s'")
void failedToFindInstallationChannels(Exception failure);
+ @Message(id = 26, value = "One of the signatures in the update is signed by an unknown public key '%s'. Please import the key using import operation and try again.")
+ OperationFailedException componentSignedWithUnknownCertificate(String keyID, @Cause Throwable cause);
+
+ @Message(id = 27, value = "You cannot use the '%s' option with the '%s' option because they are mutually exclusive.")
+ OperationFailedException mutuallyExclusiveOptions(String optionName1, String optionName2);
////////////////////////////////////////////////
// Messages without IDs
diff --git a/installation-manager/src/main/resources/org/wildfly/core/instmgr/LocalDescriptions.properties b/installation-manager/src/main/resources/org/wildfly/core/instmgr/LocalDescriptions.properties
index 4b6c3af1692..142353efdbf 100644
--- a/installation-manager/src/main/resources/org/wildfly/core/instmgr/LocalDescriptions.properties
+++ b/installation-manager/src/main/resources/org/wildfly/core/instmgr/LocalDescriptions.properties
@@ -82,3 +82,14 @@ installation-manager.custom-patch.upload-custom-patch.custom-patch-file=A custom
installation-manager.custom-patch.remove-custom-patch=Removes a custom patch from the base server. It also removes the channel that provides this custom patch and unsubscribes the installation from this channel.
installation-manager.custom-patch.remove-custom-patch.manifest=Channel Manifest maven coordinates associated with the custom patch. Expected format is Maven GA (GroupId:ArtifactId).
+installation-manager.certificate-list=Lists certificates used to verify components.
+installation-manager.certificate-parse=Parses a certificate
+installation-manager.certificate-parse.cert-file=A certificate file
+installation-manager.certificate-import=Add a certificate to verify installation components.
+installation-manager.certificate-import.cert-file=Path to the certificate that will be used to verify updated components.
+installation-manager.certificate-remove=Remove a certificate to verify installation components.
+installation-manager.certificate-remove.key-id=The ID of a certificate to be removed from the list of certificates trusted to verify updated components.
+installation-manager.certificates=Certificates used to verify components.
+installation-manager.certificates.certificate.key-id=The ID of a certificate used to verify components.
+installation-manager.unaccepted-certificates=Downloads certificates listed in the server channels, but not trusted yet.
+installation-manager.unaccepted-certificates.offline=Only resolve certificates available locally.
diff --git a/installation-manager/src/main/resources/org/wildfly/core/instmgr/cli/command_resources.properties b/installation-manager/src/main/resources/org/wildfly/core/instmgr/cli/command_resources.properties
index f339861ac3f..f513af56f35 100644
--- a/installation-manager/src/main/resources/org/wildfly/core/instmgr/cli/command_resources.properties
+++ b/installation-manager/src/main/resources/org/wildfly/core/instmgr/cli/command_resources.properties
@@ -194,4 +194,28 @@ installer.remove-custom-patch.description=\
installer.remove-custom-patch.option.manifest.description=\
Location of the channel manifest artifact associated with custom patch that will be removed. A manifest defines the versions and artifacts that will be available from the channel that consumes this custom patch. \
- Specify the location as a Maven GA coordinate (groupId:artifactId).
\ No newline at end of file
+ Specify the location as a Maven GA coordinate (groupId:artifactId).
+
+# LIST COMPONENT CERTIFICATES
+installer.certificates-list.description=\
+ Lists all public keys imported into the server. The public keys are used to verify components installed during update and revert operation.
+
+# ADD COMPONENT CERTIFICATES
+installer.certificates-add.description=\
+ Imports a new public key to be used to verify components during update and revert operations. Once imported, as long as the key is not \
+ expired or revoked, any artifact signed by it, will be treated as a trusted component.
+
+installer.certificates-add.option.cert-file.description=\
+ Path to the file containing an ASCII-armored GPG public key to be imported.
+
+installer.certificates-add.option.non-interactive.description=\
+ Switches off a user prompt asking to confirm the public key validity. Note, the user needs to be sure the file contains a valid key when using this option.
+
+# REMOVE COMPONENT CERTIFICATES
+installer.certificates-remove.description=\
+ Removes one of the public keys used to verify components during update and revert operations.
+
+installer.certificates-remove.option.key-id.description=\
+ The ID in a hexadecimal form of a public key to be removed from the list of trusted component certificates. \
+ Once the key is removed, if an update or revert operation tries to use component signed by this key, the user will be warned \
+ and the update will be rejected unless the uses re-imports the key.
\ No newline at end of file
diff --git a/installation-manager/src/test/java/org/wildfly/core/instmgr/InstMgrResourceTestCase.java b/installation-manager/src/test/java/org/wildfly/core/instmgr/InstMgrResourceTestCase.java
index 7e65046d51d..7963fe56d2b 100644
--- a/installation-manager/src/test/java/org/wildfly/core/instmgr/InstMgrResourceTestCase.java
+++ b/installation-manager/src/test/java/org/wildfly/core/instmgr/InstMgrResourceTestCase.java
@@ -11,12 +11,21 @@
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.INCLUDE_RUNTIME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATIONS;
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_RESOURCE_DESCRIPTION_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_RESOURCE_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RECURSIVE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESPONSE_HEADERS;
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT;
+import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUCCESS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.VALUE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.WRITE_ATTRIBUTE_OPERATION;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERTIFICATE_CONTENT;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERT_DESCRIPTION;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERT_FILE;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERT_FINGERPRINT;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERT_KEY_ID;
+import static org.wildfly.core.instmgr.InstMgrConstants.CERT_STATUS;
import java.io.File;
import java.io.FileInputStream;
@@ -65,7 +74,9 @@
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import org.wildfly.installationmanager.ArtifactChange;
import org.wildfly.installationmanager.Channel;
import org.wildfly.installationmanager.ChannelChange;
@@ -86,6 +97,9 @@ public class InstMgrResourceTestCase extends AbstractControllerTestBase {
static final Path JBOSS_CONTROLLER_TEMP_DIR = JBOSS_HOME.resolve("temp");
static final Path INSTALLATION_MANAGER_PROPERTIES = JBOSS_HOME.resolve("bin").resolve("installation-manager.properties");
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
@Before
public void setupController() throws InterruptedException, IOException {
TestInstallationManager.initialized = false;
@@ -357,6 +371,111 @@ public void testRemoveChannels() throws Exception {
);
}
+ @Test
+ public void testReadCertificates() throws Exception {
+ PathAddress pathElements = PathAddress.pathAddress(CORE_SERVICE, InstMgrConstants.TOOL_NAME);
+ ModelNode op = Util.createEmptyOperation(READ_RESOURCE_OPERATION, pathElements);
+ op.get(INCLUDE_RUNTIME).set(true);
+
+ ModelNode result = executeForResult(op);
+ Assert.assertTrue(result.isDefined());
+
+ // validate the result:
+ List certs = result.get(InstMgrConstants.CERTIFICATES).asListOrEmpty();
+ Assert.assertEquals(1, certs.size());
+
+ // First Channel
+ ModelNode cert = certs.get(0);
+ Assert.assertEquals("abcd", cert.get(CERT_KEY_ID).asString());
+ Assert.assertEquals("abcd1234", cert.get(CERT_FINGERPRINT).asString());
+ Assert.assertEquals("Test Cert", cert.get(CERT_DESCRIPTION).asString());
+ Assert.assertEquals("TRUSTED", cert.get(CERT_STATUS).asString());
+ }
+
+ @Test
+ public void testRemoveCertificate() throws Exception {
+ PathAddress pathElements = PathAddress.pathAddress(CORE_SERVICE, InstMgrConstants.TOOL_NAME);
+ ModelNode op = Util.createEmptyOperation(InstMgrCertificateRemoveHandler.OPERATION_NAME, pathElements);
+ op.get(INCLUDE_RUNTIME).set(true);
+ op.get(CERT_KEY_ID).set("abcd");
+
+ executeForResult(op);
+
+ // verify there are no certificates now
+ pathElements = PathAddress.pathAddress(CORE_SERVICE, InstMgrConstants.TOOL_NAME);
+ ModelNode listOp = Util.createEmptyOperation(READ_RESOURCE_OPERATION, pathElements);
+ listOp.get(INCLUDE_RUNTIME).set(true);
+
+ ModelNode result = executeForResult(listOp);
+ Assert.assertTrue(result.isDefined());
+
+ // validate the result is an empty list:
+ List certs = result.get(InstMgrConstants.CERTIFICATES).asListOrEmpty();
+ Assert.assertEquals(0, certs.size());
+ }
+
+ @Test
+ public void testAddCertificate() throws Exception {
+ PathAddress pathElements = PathAddress.pathAddress(CORE_SERVICE, InstMgrConstants.TOOL_NAME);
+ ModelNode op = Util.createEmptyOperation(InstMgrCertificateImportHandler.OPERATION_NAME, pathElements);
+ op.get(INCLUDE_RUNTIME).set(true);
+ op.get(CERT_FILE).set(0);
+ final OperationBuilder builder = OperationBuilder.create(op);
+ final File file = temp.newFile("test.crt");
+ Files.writeString(file.toPath(), "key-id:efgh\nfingerprint:efgh5678\ndescription:Another Cert");
+ builder.addFileAsAttachment(file);
+
+ executeForResult(builder.build());
+
+ // verify there are two certificates now
+ pathElements = PathAddress.pathAddress(CORE_SERVICE, InstMgrConstants.TOOL_NAME);
+ ModelNode listOp = Util.createEmptyOperation(READ_RESOURCE_OPERATION, pathElements);
+ listOp.get(INCLUDE_RUNTIME).set(true);
+
+ ModelNode result = executeForResult(listOp);
+ Assert.assertTrue(result.isDefined());
+
+ // validate the result has two elements:
+ List certs = result.get(InstMgrConstants.CERTIFICATES).asListOrEmpty();
+ Assert.assertEquals(2, certs.size());
+
+ // First Channel
+ ModelNode cert = certs.get(0);
+ Assert.assertEquals("abcd", cert.get(CERT_KEY_ID).asString());
+ Assert.assertEquals("abcd1234", cert.get(CERT_FINGERPRINT).asString());
+ Assert.assertEquals("Test Cert", cert.get(CERT_DESCRIPTION).asString());
+ Assert.assertEquals("TRUSTED", cert.get(CERT_STATUS).asString());
+
+ // Second Channel
+ cert = certs.get(1);
+ Assert.assertEquals("efgh", cert.get(CERT_KEY_ID).asString());
+ Assert.assertEquals("efgh5678", cert.get(CERT_FINGERPRINT).asString());
+ Assert.assertEquals("Another Cert", cert.get(CERT_DESCRIPTION).asString());
+ Assert.assertEquals("TRUSTED", cert.get(CERT_STATUS).asString());
+ }
+
+ @Test
+ public void testParseCertificate() throws Exception {
+ PathAddress pathElements = PathAddress.pathAddress(CORE_SERVICE, InstMgrConstants.TOOL_NAME);
+ ModelNode op = Util.createEmptyOperation(InstMgrCertificateParseHandler.OPERATION_NAME, pathElements);
+ op.get(INCLUDE_RUNTIME).set(true);
+ op.get(CERT_FILE).set(0);
+ final OperationBuilder builder = OperationBuilder.create(op);
+ final File file = temp.newFile("test.crt");
+ Files.writeString(file.toPath(), "key-id:efgh\nfingerprint:efgh5678\ndescription:Another Cert");
+ builder.addFileAsAttachment(file);
+
+ ModelNode result = executeForResult(builder.build());
+ Assert.assertTrue(result.isDefined());
+
+ // validate the result is a new cert:
+ ModelNode cert = result.asObject();
+ Assert.assertEquals("efgh", cert.get(CERT_KEY_ID).asString());
+ Assert.assertEquals("efgh5678", cert.get(CERT_FINGERPRINT).asString());
+ Assert.assertEquals("Another Cert", cert.get(CERT_DESCRIPTION).asString());
+ Assert.assertEquals("TRUSTED", cert.get(CERT_STATUS).asString());
+ }
+
@Test
public void testHistoryChannels() throws Exception {
PathAddress pathElements = PathAddress.pathAddress(CORE_SERVICE, InstMgrConstants.TOOL_NAME);
@@ -1430,6 +1549,23 @@ public void removeNonExistentCustomPatch() throws IOException {
);
}
+ @Test
+ public void downloadUnacceptedCertificates() throws IOException {
+ TestInstallationManager.initialize();
+
+ PathAddress pathElements = PathAddress.pathAddress(CORE_SERVICE, InstMgrConstants.TOOL_NAME);
+ ModelNode op = Util.createEmptyOperation(InstMgrUnacceptedCertificateHandler.OPERATION_NAME, pathElements);
+
+ ModelNode rsp = getController().execute(op, null, null, null);
+ Assert.assertEquals(SUCCESS, rsp.get(OUTCOME).asString());
+
+ final String certText = rsp.get(RESULT).asList().get(0).get(CERTIFICATE_CONTENT).asString();
+
+ Assert.assertEquals("key-id:abcd\n" +
+ "fingerprint:abcd1234\n" +
+ "description:Missing Cert", certText);
+ }
+
/**
* Creates and upload a custom patch associated to the customPatchManifest passed as argument.
* It will use as Zip content the content available in the "test-repo-one" resource directory.
diff --git a/pom.xml b/pom.xml
index a4371fa2245..16cd5b30174 100644
--- a/pom.xml
+++ b/pom.xml
@@ -240,7 +240,7 @@
1.0.1.Final
1.7.0.Final
1.3.0.Final
- 1.0.3.Final
+ 2.0.0.Beta2-SNAPSHOT
8.0.2.Final
2.2.5.Final
2.2.2.Final
diff --git a/testsuite/shared/src/main/java/org/wildfly/test/installationmanager/TestInstallationManager.java b/testsuite/shared/src/main/java/org/wildfly/test/installationmanager/TestInstallationManager.java
index 91b5a3928e4..3226d076074 100644
--- a/testsuite/shared/src/main/java/org/wildfly/test/installationmanager/TestInstallationManager.java
+++ b/testsuite/shared/src/main/java/org/wildfly/test/installationmanager/TestInstallationManager.java
@@ -5,31 +5,41 @@
package org.wildfly.test.installationmanager;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Optional;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.wildfly.installationmanager.ArtifactChange;
+import org.wildfly.installationmanager.CandidateType;
import org.wildfly.installationmanager.Channel;
import org.wildfly.installationmanager.ChannelChange;
+import org.wildfly.installationmanager.FileConflict;
import org.wildfly.installationmanager.HistoryResult;
import org.wildfly.installationmanager.InstallationChanges;
import org.wildfly.installationmanager.ManifestVersion;
import org.wildfly.installationmanager.MavenOptions;
import org.wildfly.installationmanager.OperationNotAvailableException;
import org.wildfly.installationmanager.Repository;
+import org.wildfly.installationmanager.TrustCertificate;
import org.wildfly.installationmanager.spi.InstallationManager;
import org.wildfly.installationmanager.spi.OsShell;
@@ -44,6 +54,7 @@ public class TestInstallationManager implements InstallationManager {
public static List findUpdatesRepositories;
public static List findUpdatesChanges;
public static List prepareUpdatesRepositories;
+ private static List lstTrustCertificates;
public static Path prepareUpdatesTargetDir;
public static List prepareRevertRepositories;
@@ -119,10 +130,10 @@ public static void initialize() throws IOException {
// History sample data
history = new HashMap<>();
- history.put("update", new HistoryResult("update", Instant.now(), "update", "update description"));
- history.put("install", new HistoryResult("install", Instant.now(), "install", "install description"));
- history.put("rollback", new HistoryResult("rollback", Instant.now(), "rollback", "rollback description"));
- history.put("config_change", new HistoryResult("config_change", Instant.now(), "config_change", "config_change description"));
+ history.put("update", new HistoryResult("update", Instant.now(), "update", "update description", Collections.emptyList()));
+ history.put("install", new HistoryResult("install", Instant.now(), "install", "install description", Collections.emptyList()));
+ history.put("rollback", new HistoryResult("rollback", Instant.now(), "rollback", "rollback description", Collections.emptyList()));
+ history.put("config_change", new HistoryResult("config_change", Instant.now(), "config_change", "config_change description", Collections.emptyList()));
// List Updates sample Data
@@ -157,6 +168,10 @@ public static void initialize() throws IOException {
}
}
+ // certificates sample data
+ lstTrustCertificates = new ArrayList<>();
+ lstTrustCertificates.add(new TrustCertificate("abcd", "abcd1234", "Test Cert", "TRUSTED"));
+
initialized = true;
}
}
@@ -266,11 +281,81 @@ public String generateApplyRevertCommand(Path scriptHome, Path candidatePath, Os
return scriptHome + APPLY_REVERT_BASE_GENERATED_COMMAND + candidatePath.toString();
}
+ @Override
+ public String generateApplyUpdateCommand(Path scriptHome, Path candidatePath, OsShell shell, boolean noConflictsOnly) throws OperationNotAvailableException {
+ return scriptHome + APPLY_UPDATE_BASE_GENERATED_COMMAND + candidatePath.toString();
+ }
+
+ @Override
+ public String generateApplyRevertCommand(Path scriptHome, Path candidatePath, OsShell shell, boolean noConflictsOnly) throws OperationNotAvailableException {
+ return scriptHome + APPLY_REVERT_BASE_GENERATED_COMMAND + candidatePath.toString();
+ }
+
@Override
public Collection getInstalledVersions() throws Exception {
return installedVersions;
}
+ @Override
+ public Collection verifyCandidate(Path candidatePath, CandidateType candidateType) throws Exception {
+ return Collections.emptySet();
+ }
+
+ @Override
+ public void acceptTrustedCertificates(InputStream certificate) throws Exception {
+ final TrustCertificate trustCertificate = parseCertificate(certificate);
+
+ lstTrustCertificates.add(trustCertificate);
+ }
+
+ @Override
+ public void revokeTrustedCertificate(String keyID) throws Exception {
+ final Optional cert = lstTrustCertificates.stream()
+ .filter(c -> c.getKeyID().equals(keyID))
+ .findFirst();
+
+ cert.ifPresent(trustCertificate -> lstTrustCertificates.remove(trustCertificate));
+ }
+
+ @Override
+ public Collection listTrustedCertificates() throws Exception {
+ return Collections.unmodifiableCollection(lstTrustCertificates);
+ }
+
+ @Override
+ public TrustCertificate parseCertificate(InputStream certificate) throws Exception {
+ String keyId = null;
+ String fingerprint = null;
+ String description = null;
+ try(BufferedReader reader = new BufferedReader(new InputStreamReader(certificate))) {
+ while (reader.ready()) {
+ final String[] line = reader.readLine().split(":");
+ switch (line[0]) {
+ case "key-id":
+ keyId = line[1];
+ break;
+ case "fingerprint":
+ fingerprint = line[1];
+ break;
+ case "description":
+ description = line[1];
+ break;
+ default:
+ throw new RuntimeException("Unknown line: " + line[0]);
+ }
+ }
+ }
+
+ return new TrustCertificate(keyId, fingerprint, description, "TRUSTED");
+ }
+
+ @Override
+ public Collection downloadRequiredCertificates() throws Exception {
+ final String cert = "key-id:abcd\nfingerprint:abcd1234\ndescription:Missing Cert";
+ final ByteArrayInputStream bais = new ByteArrayInputStream(cert.getBytes(StandardCharsets.UTF_8));
+ return List.of(bais);
+ }
+
public static void zipDir(Path inputFile, Path target) throws IOException {
try (FileOutputStream fos = new FileOutputStream(target.toFile()); ZipOutputStream zos = new ZipOutputStream(fos)) {
ZipEntry entry = new ZipEntry(inputFile.getFileName().toString());