Skip to content

Commit

Permalink
[WFMP-275] Add GPG checks to the channel provisioning
Browse files Browse the repository at this point in the history
  • Loading branch information
spyrkob committed Sep 16, 2024
1 parent 687d0eb commit 8af12c9
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 10 deletions.
4 changes: 4 additions & 0 deletions plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@
<groupId>org.wildfly.channel</groupId>
<artifactId>maven-resolver</artifactId>
</dependency>
<dependency>
<groupId>org.wildfly.channel</groupId>
<artifactId>gpg-validator</artifactId>
</dependency>
<dependency>
<groupId>org.wildfly.prospero</groupId>
<artifactId>prospero-metadata</artifactId>
Expand Down
9 changes: 9 additions & 0 deletions plugin/src/main/java/org/wildfly/plugin/dev/DevMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.ClosedWatchServiceException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
Expand Down Expand Up @@ -377,6 +378,12 @@ public class DevMojo extends AbstractServerStartMojo {
@Parameter(property = PropertyNames.CHANNELS)
private List<ChannelConfiguration> channels;

@Parameter(alias = "keyserver-urls")
protected List<URL> keyserverUrls = Collections.emptyList();

@Parameter(alias = "trusted-keyring")
protected Path trustedKeyring;

/**
* Specifies the name used for the deployment.
* <p>
Expand Down Expand Up @@ -520,6 +527,8 @@ protected MavenRepoManager createMavenRepoManager() throws MojoExecutionExceptio
try {
return new ChannelMavenArtifactRepositoryManager(channels,
repoSystem, session, repositories,
keyserverUrls,
trustedKeyring,
getLog(), offlineProvisioning);
} catch (MalformedURLException | UnresolvedMavenArtifactException ex) {
throw new MojoExecutionException(ex.getLocalizedMessage(), ex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand Down Expand Up @@ -223,6 +224,12 @@ abstract class AbstractProvisionServerMojo extends AbstractMojo {
@Parameter(alias = "dry-run")
boolean dryRun;

@Parameter(alias = "keyserver-urls")
protected List<URL> keyserverUrls = Collections.emptyList();

@Parameter(alias = "trusted-keyring")
protected File trustedKeyring;

private Path wildflyDir;

protected MavenRepoManager artifactResolver;
Expand Down Expand Up @@ -251,6 +258,8 @@ public void execute() throws MojoExecutionException, MojoFailureException {
try {
artifactResolver = new ChannelMavenArtifactRepositoryManager(channels,
repoSystem, repoSession, repositories,
keyserverUrls,
trustedKeyring == null ? null : trustedKeyring.toPath(),
getLog(), offlineProvisioning);
} catch (MalformedURLException | UnresolvedMavenArtifactException ex) {
throw new MojoExecutionException(ex.getLocalizedMessage(), ex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import org.apache.maven.plugin.MojoExecutionException;
import org.eclipse.aether.repository.RemoteRepository;
import org.wildfly.channel.Channel;
import org.wildfly.channel.ChannelManifestCoordinate;
import org.wildfly.channel.Repository;

/**
* A channel configuration. Contains a {@code manifest} composed of a {@code groupId}, an {@code artifactId}
* an optional {@code version} or a {@code url}.
*
* Optionally can declare if the channel requires GPG signature validation ({@code gpgCheck}) and a list of GPG public
* keys used to verify them ({@code gpgUrls}).
*
* @author jdenise
*/
public class ChannelConfiguration {
Expand All @@ -30,6 +31,10 @@ public class ChannelConfiguration {
private boolean multipleManifest;
private String name;

private boolean gpgCheck;

private List<URL> gpgUrls;

/**
* @return the manifest
*/
Expand Down Expand Up @@ -110,10 +115,18 @@ private void validate() throws MojoExecutionException {

public Channel toChannel(List<RemoteRepository> repositories) throws MojoExecutionException {
validate();
List<Repository> repos = new ArrayList<>();
final Channel.Builder builder = new Channel.Builder()
.setManifestCoordinate(getManifest())
.setGpgCheck(gpgCheck);

if (gpgUrls != null) {
gpgUrls.stream().map(URL::toExternalForm).forEach(builder::addGpgUrl);
}

for (RemoteRepository r : repositories) {
repos.add(new Repository(r.getId(), r.getUrl()));
builder.addRepository(r.getId(), r.getUrl());
}
return new Channel(name, null, null, repos, getManifest(), null, null);

return builder.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,24 @@
import java.io.BufferedReader;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Pattern;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
Expand All @@ -46,7 +50,11 @@
import org.wildfly.channel.Repository;
import org.wildfly.channel.UnresolvedMavenArtifactException;
import org.wildfly.channel.VersionResult;
import org.wildfly.channel.gpg.GpgSignatureValidator;
import org.wildfly.channel.gpg.GpgSignatureValidatorListener;
import org.wildfly.channel.gpg.Keyserver;
import org.wildfly.channel.maven.VersionResolverFactory;
import org.wildfly.channel.spi.ArtifactIdentifier;
import org.wildfly.channel.spi.ChannelResolvable;
import org.wildfly.prospero.metadata.ManifestVersionRecord;
import org.wildfly.prospero.metadata.ManifestVersionResolver;
Expand All @@ -63,18 +71,24 @@ public class ChannelMavenArtifactRepositoryManager implements MavenRepoManager,
private final RepositorySystem system;
private final DefaultRepositorySystemSession session;
private final List<RemoteRepository> repositories;
private final Set<String> artifactSources = new HashSet<>();
private final Path gpgKeyring;

public ChannelMavenArtifactRepositoryManager(List<ChannelConfiguration> channels,
RepositorySystem system,
RepositorySystemSession contextSession,
List<RemoteRepository> repositories, Log log, boolean offline)
List<RemoteRepository> repositories,
List<URL> keystoreUrls,
Path gpgKeyring,
Log log, boolean offline)
throws MalformedURLException, UnresolvedMavenArtifactException, MojoExecutionException {
if (channels.isEmpty()) {
throw new MojoExecutionException("No channel specified.");
}
this.log = log;
session = MavenRepositorySystemUtils.newSession();
this.repositories = repositories;
this.gpgKeyring = gpgKeyring;
session.setLocalRepositoryManager(contextSession.getLocalRepositoryManager());
session.setOffline(offline);
Map<String, RemoteRepository> mapping = new HashMap<>();
Expand All @@ -91,7 +105,20 @@ public ChannelMavenArtifactRepositoryManager(List<ChannelConfiguration> channels
}
return rep;
};
VersionResolverFactory factory = new VersionResolverFactory(system, session, mapper);
final GpgSignatureValidator signatureValidator = new GpgSignatureValidator(new GpgKeyring(gpgKeyring),
new Keyserver(keystoreUrls));
VersionResolverFactory factory = new VersionResolverFactory(system, session, signatureValidator, mapper);
signatureValidator.addListener(new GpgSignatureValidatorListener() {
@Override
public void artifactSignatureCorrect(ArtifactIdentifier artifact, PGPPublicKey publicKey) {
artifactSources.add(GpgKeyring.describeImportedKeys(publicKey));
}

@Override
public void artifactSignatureInvalid(ArtifactIdentifier artifact, PGPPublicKey publicKey) {

}
});
channelSession = new ChannelSession(this.channels, factory);
localCachePath = contextSession.getLocalRepositoryManager().getRepository().getBasedir().toPath();
this.system = system;
Expand Down Expand Up @@ -186,9 +213,17 @@ private void resolveFromChannels(MavenArtifact artifact) throws UnresolvedMavenA

public void done(Path home) throws MavenUniverseException, IOException {
ChannelManifest channelManifest = channelSession.getRecordedChannel();
final ManifestVersionRecord currentVersions = new ManifestVersionResolver(localCachePath, system)
final ManifestVersionRecord currentVersions = new ManifestVersionResolver(localCachePath, system,
new GpgSignatureValidator(new GpgKeyring(gpgKeyring)))
.getCurrentVersions(channels);
ProsperoMetadataUtils.generate(home, channels, channelManifest, currentVersions);

if (!this.artifactSources.isEmpty()) {
log.info("Resolved artifacts were signed by:");
for (String artifactSource : this.artifactSources) {
log.info(" * " + artifactSource);
}
}
}

@Override
Expand Down
84 changes: 84 additions & 0 deletions plugin/src/main/java/org/wildfly/plugin/provision/GpgKeyring.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright The WildFly Authors
* SPDX-License-Identifier: Apache-2.0
*/

package org.wildfly.plugin.provision;

import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.bouncycastle.util.encoders.Hex;
import org.jboss.logging.Logger;
import org.wildfly.channel.gpg.GpgKeystore;

/**
* Read-only keystore used to read keys from a local GPG keyring file.
*/
public class GpgKeyring implements GpgKeystore {

private final Logger log = Logger.getLogger(GpgKeyring.class.getName());

private final PGPPublicKeyRingCollection publicKeyRingCollection;
private Map<String, PGPPublicKey> keyCache = new HashMap<>();

public PGPPublicKey get(String keyID) {
if (publicKeyRingCollection != null) {
final Iterator<PGPPublicKeyRing> keyRings = publicKeyRingCollection.getKeyRings();
while (keyRings.hasNext()) {
final PGPPublicKeyRing keyRing = keyRings.next();
final PGPPublicKey publicKey = keyRing.getPublicKey(new BigInteger(keyID, 16).longValue());
if (publicKey != null) {
return publicKey;
}
}
return null;
} else {
return keyCache.get(keyID);
}
}

public GpgKeyring(Path keyringPath) {
if (keyringPath != null) {
try {
publicKeyRingCollection = new PGPPublicKeyRingCollection(
new ArmoredInputStream(new FileInputStream(keyringPath.toFile())),
new JcaKeyFingerprintCalculator());
} catch (IOException | PGPException e) {
throw new RuntimeException("Unable to access GPG keystore", e);
}
} else {
publicKeyRingCollection = null;
}
}

public boolean add(List<PGPPublicKey> publicKeys) {
for (PGPPublicKey publicKey : publicKeys) {
keyCache.put(Long.toHexString(publicKey.getKeyID()).toUpperCase(Locale.ROOT), publicKey);
}
return true;
}

static String describeImportedKeys(PGPPublicKey pgpPublicKey) {
final StringBuilder sb = new StringBuilder();
final Iterator<String> userIDs = pgpPublicKey.getUserIDs();
while (userIDs.hasNext()) {
sb.append(userIDs.next());
}
sb.append(": ").append(Hex.toHexString(pgpPublicKey.getFingerprint()));
return sb.toString();
}
}
9 changes: 7 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@
<!-- This version property is also retrieved by plugin at runtime to resolve CLI artifact -->
<version.org.wildfly.core>25.0.2.Final</version.org.wildfly.core>
<version.org.wildfly>32.0.1.Final</version.org.wildfly>
<version.org.wildfly.channel>1.1.0.Final</version.org.wildfly.channel>
<version.org.wildfly.prospero>1.2.1.Final</version.org.wildfly.prospero>
<version.org.wildfly.channel>1.1.1.Final-SNAPSHOT</version.org.wildfly.channel>
<version.org.wildfly.prospero>1.2.2.Final-SNAPSHOT</version.org.wildfly.prospero>
<!-- maven dependencies -->
<version.javax.inject.javax.inject>1</version.javax.inject.javax.inject>
<version.org.apache.maven.maven-core>3.9.4</version.org.apache.maven.maven-core>
Expand Down Expand Up @@ -394,6 +394,11 @@
<artifactId>maven-resolver</artifactId>
<version>${version.org.wildfly.channel}</version>
</dependency>
<dependency>
<groupId>org.wildfly.channel</groupId>
<artifactId>gpg-validator</artifactId>
<version>${version.org.wildfly.channel}</version>
</dependency>
<dependency>
<groupId>org.wildfly.checkstyle</groupId>
<artifactId>wildfly-checkstyle-config</artifactId>
Expand Down

0 comments on commit 8af12c9

Please sign in to comment.