Skip to content

Commit f5e54ca

Browse files
authored
[MNG-8285] Implement mvnenc CLI tool (#1793)
Implements the `mvnenc` tool that is on par with Maven3 master password encryption functionality wise, but is _really secure_ unlike Maven3 conterpart. On the other hand, _is backward compatible if legacy config is setup_. Implemented goals: `init`, `encrypt`, `decrypt`, `diag`. Also provides one extra "master source" based on Maven infra Prompter: console master password prompt. --- https://issues.apache.org/jira/browse/MNG-8285
1 parent 7df5b16 commit f5e54ca

File tree

25 files changed

+1079
-116
lines changed

25 files changed

+1079
-116
lines changed

apache-maven/src/assembly/component.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ under the License.
8585
<include>mvn</include>
8686
<include>mvnenc</include>
8787
<include>mvnDebug</include>
88+
<include>mvnencDebug</include>
8889
<!-- This is so that CI systems can periodically run the profiler -->
8990
<include>mvnyjp</include>
9091
</includes>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/bin/sh
2+
3+
# Licensed to the Apache Software Foundation (ASF) under one
4+
# or more contributor license agreements. See the NOTICE file
5+
# distributed with this work for additional information
6+
# regarding copyright ownership. The ASF licenses this file
7+
# to you under the Apache License, Version 2.0 (the
8+
# "License"); you may not use this file except in compliance
9+
# with the License. You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing,
14+
# software distributed under the License is distributed on an
15+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
# KIND, either express or implied. See the License for the
17+
# specific language governing permissions and limitations
18+
# under the License.
19+
20+
# -----------------------------------------------------------------------------
21+
# Apache Maven Debug Script
22+
#
23+
# Environment Variable Prerequisites
24+
#
25+
# JAVA_HOME (Optional) Points to a Java installation.
26+
# MAVEN_OPTS (Optional) Java runtime options used when Maven is executed.
27+
# MAVEN_SKIP_RC (Optional) Flag to disable loading of mavenrc files.
28+
# MAVEN_DEBUG_ADDRESS (Optional) Set the debug address. Default value is localhost:8000
29+
# -----------------------------------------------------------------------------
30+
31+
MAVEN_DEBUG_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=${MAVEN_DEBUG_ADDRESS:-localhost:8000}"
32+
33+
echo Preparing to execute Maven in debug mode
34+
35+
env MAVEN_OPTS="$MAVEN_OPTS" MAVEN_DEBUG_OPTS="$MAVEN_DEBUG_OPTS" "`dirname "$0"`/mvnenc" "$@"
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
@REM Licensed to the Apache Software Foundation (ASF) under one
2+
@REM or more contributor license agreements. See the NOTICE file
3+
@REM distributed with this work for additional information
4+
@REM regarding copyright ownership. The ASF licenses this file
5+
@REM to you under the Apache License, Version 2.0 (the
6+
@REM "License"); you may not use this file except in compliance
7+
@REM with the License. You may obtain a copy of the License at
8+
@REM
9+
@REM http://www.apache.org/licenses/LICENSE-2.0
10+
@REM
11+
@REM Unless required by applicable law or agreed to in writing,
12+
@REM software distributed under the License is distributed on an
13+
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
@REM KIND, either express or implied. See the License for the
15+
@REM specific language governing permissions and limitations
16+
@REM under the License.
17+
18+
@REM -----------------------------------------------------------------------------
19+
@REM Apache Maven Debug Script
20+
@REM
21+
@REM Environment Variable Prerequisites
22+
@REM
23+
@REM JAVA_HOME (Optional) Points to a Java installation.
24+
@REM MAVEN_BATCH_ECHO (Optional) Set to 'on' to enable the echoing of the batch commands.
25+
@REM MAVEN_BATCH_PAUSE (Optional) set to 'on' to wait for a key stroke before ending.
26+
@REM MAVEN_OPTS (Optional) Java runtime options used when Maven is executed.
27+
@REM MAVEN_SKIP_RC (Optional) Flag to disable loading of mavenrc files.
28+
@REM MAVEN_DEBUG_ADDRESS (Optional) Set the debug address. Default value is localhost:8000
29+
@REM -----------------------------------------------------------------------------
30+
31+
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
32+
@echo off
33+
@REM set title of command window
34+
title %0
35+
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
36+
@if "%MAVEN_BATCH_ECHO%"=="on" echo %MAVEN_BATCH_ECHO%
37+
38+
@setlocal
39+
40+
if "%MAVEN_DEBUG_ADDRESS%"=="" @set MAVEN_DEBUG_ADDRESS=localhost:8000
41+
42+
@set MAVEN_DEBUG_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=%MAVEN_DEBUG_ADDRESS%
43+
44+
@call "%~dp0"mvnenc.cmd %*

api/maven-api-cli/src/main/java/org/apache/maven/api/cli/mvnenc/EncryptOptions.java

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -36,28 +36,18 @@
3636
@Experimental
3737
public interface EncryptOptions extends Options {
3838
/**
39-
* Returns the cipher that the user wants to use for non-dispatched encryption.
39+
* Should the operation be forced (ie overwrite existing config, if any).
4040
*
41-
* @return an {@link Optional} containing the cipher string, or empty if not specified
41+
* @return an {@link Optional} containing the boolean value {@code true} if specified, or empty
4242
*/
43-
@Nonnull
44-
Optional<String> cipher();
43+
Optional<Boolean> force();
4544

4645
/**
47-
* Returns the master source that the user wants to use for non-dispatched encryption.
46+
* Should imply "yes" to all questions.
4847
*
49-
* @return an {@link Optional} containing the master source string, or empty if not specified
48+
* @return an {@link Optional} containing the boolean value {@code true} if specified, or empty
5049
*/
51-
@Nonnull
52-
Optional<String> masterSource();
53-
54-
/**
55-
* Returns the dispatcher to use for dispatched encryption.
56-
*
57-
* @return an {@link Optional} containing the dispatcher string, or empty if not specified
58-
*/
59-
@Nonnull
60-
Optional<String> dispatcher();
50+
Optional<Boolean> yes();
6151

6252
/**
6353
* Returns the list of encryption goals to be executed.

maven-cli/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@ under the License.
9292

9393
<build>
9494
<plugins>
95+
<plugin>
96+
<groupId>org.eclipse.sisu</groupId>
97+
<artifactId>sisu-maven-plugin</artifactId>
98+
</plugin>
9599
<plugin>
96100
<groupId>org.apache.maven.plugins</groupId>
97101
<artifactId>maven-jar-plugin</artifactId>

maven-cli/src/main/java/org/apache/maven/cling/ClingSupport.java

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import org.apache.maven.api.cli.InvokerRequest;
2626
import org.apache.maven.api.cli.Options;
2727
import org.apache.maven.api.cli.ParserException;
28-
import org.apache.maven.jline.MessageUtils;
2928
import org.codehaus.plexus.classworlds.ClassWorld;
3029

3130
import static java.util.Objects.requireNonNull;
@@ -65,8 +64,6 @@ private ClingSupport(ClassWorld classWorld, boolean classWorldManaged) {
6564
* The main entry point.
6665
*/
6766
public int run(String[] args) throws IOException {
68-
MessageUtils.systemInstall();
69-
MessageUtils.registerShutdownHook();
7067
try (Invoker<R> invoker = createInvoker()) {
7168
return invoker.invoke(parseArguments(args));
7269
} catch (ParserException e) {
@@ -75,12 +72,8 @@ public int run(String[] args) throws IOException {
7572
} catch (InvokerException e) {
7673
return 1;
7774
} finally {
78-
try {
79-
if (classWorldManaged) {
80-
classWorld.close();
81-
}
82-
} finally {
83-
MessageUtils.systemUninstall();
75+
if (classWorldManaged) {
76+
classWorld.close();
8477
}
8578
}
8679
}

maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.apache.maven.cling.invoker.mvn.DefaultMavenParser;
3030
import org.apache.maven.cling.invoker.mvn.local.DefaultLocalMavenInvoker;
3131
import org.apache.maven.jline.JLineMessageBuilderFactory;
32+
import org.apache.maven.jline.MessageUtils;
3233
import org.codehaus.plexus.classworlds.ClassWorld;
3334

3435
/**
@@ -59,6 +60,17 @@ public MavenCling(ClassWorld classWorld) {
5960
super(classWorld);
6061
}
6162

63+
@Override
64+
public int run(String[] args) throws IOException {
65+
MessageUtils.systemInstall();
66+
MessageUtils.registerShutdownHook();
67+
try {
68+
return super.run(args);
69+
} finally {
70+
MessageUtils.systemUninstall();
71+
}
72+
}
73+
6274
@Override
6375
protected Invoker<MavenInvokerRequest<MavenOptions>> createInvoker() {
6476
return new DefaultLocalMavenInvoker(

maven-cli/src/main/java/org/apache/maven/cling/MavenEncCling.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@
3030
import org.apache.maven.cling.invoker.mvnenc.DefaultEncryptInvoker;
3131
import org.apache.maven.cling.invoker.mvnenc.DefaultEncryptParser;
3232
import org.apache.maven.jline.JLineMessageBuilderFactory;
33+
import org.apache.maven.jline.MessageUtils;
3334
import org.codehaus.plexus.classworlds.ClassWorld;
35+
import org.jline.terminal.Terminal;
36+
import org.jline.terminal.TerminalBuilder;
3437

3538
/**
3639
* Maven encrypt CLI "new-gen".
@@ -52,6 +55,8 @@ public static int main(String[] args, ClassWorld world) throws IOException {
5255
return new MavenEncCling(world).run(args);
5356
}
5457

58+
private Terminal terminal;
59+
5560
public MavenEncCling() {
5661
super();
5762
}
@@ -60,10 +65,24 @@ public MavenEncCling(ClassWorld classWorld) {
6065
super(classWorld);
6166
}
6267

68+
@Override
69+
public int run(String[] args) throws IOException {
70+
terminal = TerminalBuilder.builder().build();
71+
MessageUtils.systemInstall(terminal);
72+
MessageUtils.registerShutdownHook();
73+
try {
74+
return super.run(args);
75+
} finally {
76+
MessageUtils.systemUninstall();
77+
}
78+
}
79+
6380
@Override
6481
protected Invoker<EncryptInvokerRequest> createInvoker() {
65-
return new DefaultEncryptInvoker(
66-
ProtoLookup.builder().addMapping(ClassWorld.class, classWorld).build());
82+
return new DefaultEncryptInvoker(ProtoLookup.builder()
83+
.addMapping(ClassWorld.class, classWorld)
84+
.addMapping(Terminal.class, terminal)
85+
.build());
6786
}
6887

6988
@Override

maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/CommonsCliEncryptOptions.java

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -72,25 +72,17 @@ private static CommonsCliEncryptOptions interpolate(
7272
}
7373

7474
@Override
75-
public Optional<String> cipher() {
76-
if (commandLine.hasOption(CLIManager.CIPHER)) {
77-
return Optional.of(commandLine.getOptionValue(CLIManager.CIPHER));
75+
public Optional<Boolean> force() {
76+
if (commandLine.hasOption(CLIManager.FORCE)) {
77+
return Optional.of(Boolean.TRUE);
7878
}
7979
return Optional.empty();
8080
}
8181

8282
@Override
83-
public Optional<String> masterSource() {
84-
if (commandLine.hasOption(CLIManager.MASTER_SOURCE)) {
85-
return Optional.of(commandLine.getOptionValue(CLIManager.MASTER_SOURCE));
86-
}
87-
return Optional.empty();
88-
}
89-
90-
@Override
91-
public Optional<String> dispatcher() {
92-
if (commandLine.hasOption(CLIManager.DISPATCHER)) {
93-
return Optional.of(commandLine.getOptionValue(CLIManager.DISPATCHER));
83+
public Optional<Boolean> yes() {
84+
if (commandLine.hasOption(CLIManager.YES)) {
85+
return Optional.of(Boolean.TRUE);
9486
}
9587
return Optional.empty();
9688
}
@@ -109,24 +101,19 @@ public EncryptOptions interpolate(Collection<Map<String, String>> properties) {
109101
}
110102

111103
protected static class CLIManager extends CommonsCliOptions.CLIManager {
112-
public static final String CIPHER = "c";
113-
public static final String MASTER_SOURCE = "m";
114-
public static final String DISPATCHER = "d";
104+
public static final String FORCE = "f";
105+
public static final String YES = "y";
115106

116107
@Override
117108
protected void prepareOptions(org.apache.commons.cli.Options options) {
118109
super.prepareOptions(options);
119-
options.addOption(Option.builder(CIPHER)
120-
.longOpt("cipher")
121-
.desc("The cipher that user wants to use for non-dispatched encryption")
122-
.build());
123-
options.addOption(Option.builder(MASTER_SOURCE)
124-
.longOpt("master-source")
125-
.desc("The master source that user wants to use for non-dispatched encryption")
110+
options.addOption(Option.builder(FORCE)
111+
.longOpt("force")
112+
.desc("Should overwrite without asking any configuration?")
126113
.build());
127-
options.addOption(Option.builder(DISPATCHER)
128-
.longOpt("dispatcher")
129-
.desc("The dispatcher to use for dispatched encryption")
114+
options.addOption(Option.builder(YES)
115+
.longOpt("yes")
116+
.desc("Should imply user answered \"yes\" to all incoming questions?")
130117
.build());
131118
}
132119
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.cling.invoker.mvnenc;
20+
21+
import javax.inject.Inject;
22+
import javax.inject.Named;
23+
import javax.inject.Singleton;
24+
25+
import java.util.List;
26+
import java.util.Map;
27+
import java.util.Optional;
28+
29+
import org.apache.maven.api.services.Prompter;
30+
import org.apache.maven.api.services.PrompterException;
31+
import org.codehaus.plexus.components.secdispatcher.MasterSource;
32+
import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta;
33+
import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
34+
import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
35+
36+
/**
37+
* Trivial master password source using Maven {@link Prompter} service.
38+
*/
39+
@Singleton
40+
@Named(ConsolePasswordPrompt.NAME)
41+
public class ConsolePasswordPrompt implements MasterSource, MasterSourceMeta {
42+
public static final String NAME = "console-prompt";
43+
44+
private final Prompter prompter;
45+
46+
@Inject
47+
public ConsolePasswordPrompt(Prompter prompter) {
48+
this.prompter = prompter;
49+
}
50+
51+
@Override
52+
public String description() {
53+
return "Secure console password prompt";
54+
}
55+
56+
@Override
57+
public Optional<String> configTemplate() {
58+
return Optional.empty();
59+
}
60+
61+
@Override
62+
public String handle(String config) throws SecDispatcherException {
63+
if (NAME.equals(config)) {
64+
try {
65+
return prompter.promptForPassword("Enter the master password: ");
66+
} catch (PrompterException e) {
67+
throw new SecDispatcherException("Could not collect the password", e);
68+
}
69+
}
70+
return null;
71+
}
72+
73+
@Override
74+
public SecDispatcher.ValidationResponse validateConfiguration(String config) {
75+
if (NAME.equals(config)) {
76+
return new SecDispatcher.ValidationResponse(getClass().getSimpleName(), true, Map.of(), List.of());
77+
}
78+
return null;
79+
}
80+
}

0 commit comments

Comments
 (0)