Configuring the Jar Enforcer
Introduction
The JarEnforcer is a Maven Enforcer that, subject to configuration, performs the following tests on a distribution's externally-provided (dependency) jars.
Tests that they are signed by a key which is in an appropriate keyRing, failing if any signatures are missing or not resolvable.
This is used to ensure that you are not imposing a supply chain issue upon your customers.
Tests that the version is the one specified in the pom file (because maven’s resolution of dependencies is non intuitive). This fails if versions mismatch, or if artifacts are missing.
As a part of this test it can also do a reverse lookup and provide a trace back to which pom-specified artifact caused a particular jar to become a part of the distribution
Finally it can check the signature of every jar in your local maven repository. This can be used to check for supply chain attacks via maven plugins.
Configuration
This is done by adding the following stanza to the pom file for the project distribution.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<dependencies>
<dependency>
<groupId>net.shibboleth.maven.enforcer.rules</groupId>
<artifactId>maven-dist-enforcer</artifactId>
<version>2.1.0</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>idp-enforce</id>
<phase>verify</phase>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<jarEnforcer implementation="net.shibboleth.mvn.enforcer.impl.JarEnforcer">
<enforcerData>${basedir}/src/main/enforcer</enforcerData>
<parentPomDir>${basedir}/../idp-parent</parentPomDir>
<jarDirs>${project.build.directory}/${idp.finalName}/bin/lib ${project.build.directory}/${idp.finalName}/webapp/WEB-INF/lib</jarDirs>
<checkSignatures>true</checkSignatures>
<checkDependencies>true</checkDependencies>
<listJarSources>true</listJarSources>
<artifactMap>${basedir}/src/main/enforcer/artifactMap.properties</artifactMap>
</jarEnforcer>
</rules>
</configuration>
</execution>
</executions>
</plugin>
The supported parameters are:
Element Name | Required? | Default | Function |
---|---|---|---|
parentPomDir | Yes | Â | This is the absolute path to the directory where the parent pom for the project is stored. This is parsed and used to
|
dataGroupId 3.0 | Yes | Â | Maven coordinates of the project which contains the keys (and if required) signatures for jars. See below. Supercedes enforcerData |
dataKeyRing 3.0 | Yes | Â | Absolute path to a keyring with keys which will be used to check the validity of the above specified jar file |
tgzFiles 3.0 | One must be present | Â | Space separated list of tar.gz files to be scanned. Supercedes jarFiles |
zipFiles 3.0 | Â | Space separated list of zip files to be scanned. Supercedes jarFiles | |
checkSignatures | Â | true | Whether to run signature checking on the contents |
sigCheckReportPath 3.0 | Â | ${project.build.directory}\signatureReport.txt | Where to write the report of the signature checking |
checkDependencies | Â | true | Whether to run dependency analysis and report if any versions mismatch |
listJarSources | Â | false | Whether, as part of the dependency check to do a reverse look up of artifact to source (this is a slow operation) |
depCheckReportPath 3.0 | Â | ${project.build.directory}\dependencyReport.txt | Where to write the report of the signature checking |
checkM2 | Â | false | Whether all the non-source, non-test jar files in the users maven repository (~/.m2/repository) will be checked |
versionExtensions 3.2 | Â | -SNAPSHOT -GA -jre -empty-to-avoid-conflict-with-guava | See below |
classifiers 3.2 | Â | <empty string> | See below |
m2ReportPath 3.0 | Â | ${project.build.directory}\m2SignatureReport.txt | Where to write the report of the m2 checking |
Decomposing file names, version extensions, classifiers and group lookups
Versions and Classifiers
When the enforcer is traversing the distribution (the tgz or zip file) as part of the signature check, it needs to infer the maven artifact coordinates from the file name. These coordinates allow the enforcer to download the signature (asc file) for the jar.
Thus, given a name such as bcutil-jdk15on-1.70.jar
listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar
or netty-transport-native-epoll-4.1.75.Final-linux-x86_64.jar
it needs to be able to infer the groupId
, artifactId
, version
and classifier
. Since the enforcer has already read the project pom (and its parents) it can simply do this for artifacts which are explicitly mentioned. However the enforce does not follow dependencies and so it has to infer the coordinates which are in the distribution as a result of a dependency. To do this it treats a jar name as being made up of 4 parts
A non-optional artifactId, followed by
A non-optional version which starts with a '-' followed by
An optional ‘extension garnish' which starts with a '-’ followed by
An optional classifier which starts with a '-'
The maven coordindates are then defined as being
The artifactId is (1)
The version is (2) concatenated to (3) with the initial hyphen removed.
The classifier is (3) with the initial hyphen removed.
Thus in the example above a correct split would be
Jar file name | artifactId | version | classifier |
---|---|---|---|
bcutil-jdk15on-1.70.jar | bcutil-jdk15on | 1.70 | Â |
listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar | listenablefuture | 9999.0-empty-to-avoid-conflict-with-guava | Â |
netty-transport-native-epoll-4.1.75.Final-linux-x86_64.jar | netty-transport-native-epoll | 4.1.75.Final | linux-x86_64 |
Given that all identifiers can contain hyphen tt can easily be seen that, it is impossible to deconstruct the name mechanically and the enforcer requires guidance. This is provided by the <versionExtensions/>
and <classifiers/>
configuration elements. The text content of these elements is a space separated list of names (with the leading hyphen). So, in order to get the decomposition above you would need the following
<versionExtensions>-empty-to-avoid-conflict-with-guava</versionExtensions>
<classifiers>-linux-x86_64</classifiers>
The groupId
There is no mechanical method of determining the groupId from a file name. Instead the enforcer relies on a property file which is distributed in the enforcer data jar at location net/shibboleth/mvn/enforcer/data/artifactMap.properties
. Appropriate additions for the above example would include
listenablefuture=com.google.guava
netty-transport-native-epoll=io.netty
The bcutil-jdk5on
artifactId does not need to be mentioned since it is explicitly defined in the pom files.
See also the troubleshooting section.
The Enforcer Data jar
The jar file referenced by the dataGroupId/dataArtifactId/dataVersion coordinated contains three sets of data used by the enforcer. These are all contained below the net/shibboleth/mvn/enforcer/data/
directory.
A file called
artifactMap.properties
which contains the mapping from artifactid to group. This is used where jar files are encountered which are not explicitly mentioned in the parent pom (see above).A directory called
localsignatures
which contains a series of files whose name is of the formartifactId-version.jar.asc
(e.g.antlr-2.7.7.jar.asc
) these being signatures on the respective jar files. This is a fallback for when jar files have been publish with no signature. Such signatures should only be added if the jar file has had independend verificationA directory called
keyRing
which contains a series off keyrings and which is described below.
Configuring keyRings
Signature checking is done against a series of (GPG) keyrings in the net/shibboleth/mvn/enforcer/data/keyRings
subdirectory of the jar specified by the dataXXX
maven coordinates in V3 and the enforcerData
path in V2.
Each keyring contains all the public keys which are trusted to validate the signatures of all artifacts with that groupId. The name of each key ring is the groupId with suffix .gpg
. As an interim step in keyring deployment it is possible to supply an .asc
file with the name derived soley from the groupId (with no suffix).
These keyrings require maintenance:
If an artifact is added to the distribution and it is not signed with a trusted key
If the version of an artifact changed and the signature on it is not a trusted key.
In both this cases the artifact will not have been added to our repository without a signature check and so it is an absolute assumption that the public key is available.
Adding a new artifact
If an artifact is added implicitly as a dependency then you may need to add the artifactId to groupId mapping to the artifactMap.
If the artifact is in a previously unknown groupId then you must create an empty GPG keyring with the appropriate name (and empty keyring is an empty file with a
.gpg
extension
Adding a new public key to a keyring
Only commit a change to the keyring after discussion with the committers group (either by Slack or by email) to ensure that an appropriate process has been followed for accrediting the signatory.
Assuming that the key is help in a file called pubkey.asc
and the groupId is the.maven.group
the following command will add the key.
Shibboleth Deployments
In the Shibboleth projects the enforcer is used in two distinct types of place:
Dependency & Signature checking when distributables are created.
M2 checking towards the end if the build of every project
Dependency & Signature checking
This is enabled for
The
-dist
part of every pluginThe
idp-dist
project
The enforcer is configured to run during the verify phase and the entirety of the configuration (with the exception of the properties defining versions) is contained within the relevant pom file.
M2 checking
This is usually performed in the last module of every project. For multi module projects this is usually the bom project.
The checking is performed in the verify phase and is enabled via specific profile. This allow (nearly) all the configuration to be contained in the parent pom. M2 checking is enabled for any particular module by creating a file called .check-m2
in the modules root directory. It can then be supressed by the -Dno-check-m2
parameter to command line maven.
Finally the output location for the m2 report can be changed for any project by defining a property maven-dist-enforcer-data.m2ReportPath
in that project’s pom containing the fully qualified name that the output file should have.
Example output
Troubleshooting
Many of the error messages are (supposed to be) self explanatory and people having issues with them should enter a case in JIRA This will allow me to update the documentation or the error messages.
[TBD] Needs worked examples of
wrong versions
wrong classifiers
missing groupId
Â
Â