Convert license verifier tool to jopt-simple

Original commit: elastic/x-pack-elasticsearch@56ecc6333c
This commit is contained in:
Ryan Ernst 2016-03-01 17:13:17 -08:00
parent bd64bd6ff3
commit 626bdbe7bf
4 changed files with 130 additions and 225 deletions

View File

@ -5,54 +5,46 @@
*/
package org.elasticsearch.license.licensor.tools;
import java.nio.file.Files;
import java.nio.file.Path;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.apache.commons.cli.CommandLine;
import org.apache.lucene.index.Term;
import org.elasticsearch.cli.Command;
import org.elasticsearch.cli.ExitCodes;
import org.elasticsearch.cli.UserError;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolConfig;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.licensor.LicenseSigner;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.option;
import static org.elasticsearch.common.cli.CliToolConfig.config;
public class LicenseGeneratorTool extends Command {
private final OptionSpec<File> publicKeyPathOption;
private final OptionSpec<File> privateKeyPathOption;
private final OptionSpec<String> publicKeyPathOption;
private final OptionSpec<String> privateKeyPathOption;
private final OptionSpec<String> licenseOption;
private final OptionSpec<File> licenseFileOption;
private final OptionSpec<String> licenseFileOption;
public LicenseGeneratorTool() {
super("Generates signed elasticsearch license(s) for a given license spec(s)");
publicKeyPathOption = parser.accepts("publicKeyPath", "path to public key file")
.withRequiredArg().ofType(File.class).required();
.withRequiredArg().required();
privateKeyPathOption = parser.accepts("privateKeyPath", "path to private key file")
.withRequiredArg().ofType(File.class).required();
.withRequiredArg().required();
// TODO: with jopt-simple 5.0, we can make these requiredUnless each other
// which is effectively "one must be present"
licenseOption = parser.accepts("license", "license json spec")
.withRequiredArg();
licenseFileOption = parser.accepts("licenseFile", "license json spec file")
.withRequiredArg().ofType(File.class);
.withRequiredArg();
}
public static void main(String[] args) throws Exception {
exit(new LicenseGeneratorTool().main(args, Terminal.DEFAULT));
}
@Override
@ -66,15 +58,15 @@ public class LicenseGeneratorTool extends Command {
@Override
protected int execute(Terminal terminal, OptionSet options) throws Exception {
Path publicKeyPath = publicKeyPathOption.value(options).toPath();
Path privateKeyPath = privateKeyPathOption.value(options).toPath();
Path publicKeyPath = PathUtils.get(publicKeyPathOption.value(options));
Path privateKeyPath = PathUtils.get(privateKeyPathOption.value(options));
String licenseSpecString = null;
if (options.has(licenseOption)) {
licenseSpecString = licenseOption.value(options);
}
Path licenseSpecPath = null;
if (options.has(licenseFileOption)) {
licenseSpecPath = licenseFileOption.value(options).toPath();
licenseSpecPath = PathUtils.get(licenseFileOption.value(options));
}
execute(terminal, publicKeyPath, privateKeyPath, licenseSpecString, licenseSpecPath);
return ExitCodes.OK;

View File

@ -5,11 +5,17 @@
*/
package org.elasticsearch.license.licensor.tools;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.apache.commons.cli.CommandLine;
import org.elasticsearch.cli.Command;
import org.elasticsearch.cli.ExitCodes;
import org.elasticsearch.cli.UserError;
import org.elasticsearch.common.SuppressForbidden;
import org.elasticsearch.common.cli.CliTool;
import org.elasticsearch.common.cli.CliToolConfig;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
@ -17,7 +23,9 @@ import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.core.LicenseVerifier;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@ -26,92 +34,73 @@ import static org.elasticsearch.common.cli.CliToolConfig.Builder.cmd;
import static org.elasticsearch.common.cli.CliToolConfig.Builder.option;
import static org.elasticsearch.common.cli.CliToolConfig.config;
public class LicenseVerificationTool extends CliTool {
public static final String NAME = "verify-license";
public class LicenseVerificationTool extends Command {
private static final CliToolConfig CONFIG = config("licensor", LicenseVerificationTool.class)
.cmds(LicenseVerifier.CMD)
.build();
private final OptionSpec<String> publicKeyPathOption;
private final OptionSpec<String> licenseOption;
private final OptionSpec<String> licenseFileOption;
public LicenseVerificationTool() {
super(CONFIG);
}
@Override
protected Command parse(String s, CommandLine commandLine) throws Exception {
return LicenseVerifier.parse(terminal, commandLine, env);
}
public static class LicenseVerifier extends Command {
private static final CliToolConfig.Cmd CMD = cmd(NAME, LicenseVerifier.class)
.options(
option("pub", "publicKeyPath").required(true).hasArg(true),
option("l", "license").required(false).hasArg(true),
option("lf", "licenseFile").required(false).hasArg(true)
).build();
public final License license;
public final Path publicKeyPath;
public LicenseVerifier(Terminal terminal, License license, Path publicKeyPath) {
super(terminal);
this.license = license;
this.publicKeyPath = publicKeyPath;
}
public static Command parse(Terminal terminal, CommandLine commandLine, Environment environment) throws IOException {
String publicKeyPathString = commandLine.getOptionValue("publicKeyPath");
String licenseSource = commandLine.getOptionValue("license");
String licenseSourceFile = commandLine.getOptionValue("licenseFile");
License license = null;
if (licenseSource != null) {
license = License.fromSource(licenseSource);
} else if (licenseSourceFile != null) {
Path licenseSpecPath = environment.binFile().getParent().resolve(licenseSourceFile);
if (!Files.exists(licenseSpecPath)) {
return exitCmd(ExitStatus.USAGE, terminal, licenseSourceFile + " does not exist");
}
license = License.fromSource(Files.readAllBytes(licenseSpecPath));
}
if (license == null) {
return exitCmd(ExitStatus.USAGE, terminal, "no license spec provided");
}
Path publicKeyPath = environment.binFile().getParent().resolve(publicKeyPathString);
if (!Files.exists(publicKeyPath)) {
return exitCmd(ExitStatus.USAGE, terminal, publicKeyPath + " does not exist");
}
return new LicenseVerifier(terminal, license, publicKeyPath);
}
@Override
public ExitStatus execute(Settings settings, Environment env) throws Exception {
// verify
if (!org.elasticsearch.license.core.LicenseVerifier.verifyLicense(license, Files.readAllBytes(publicKeyPath))) {
terminal.println("Invalid License!");
return ExitStatus.DATA_ERROR;
}
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
builder.startObject();
builder.startObject("license");
license.toInnerXContent(builder, ToXContent.EMPTY_PARAMS);
builder.endObject();
builder.endObject();
builder.flush();
terminal.println(builder.string());
return ExitStatus.OK;
}
super("Generates signed elasticsearch license(s) for a given license spec(s)");
publicKeyPathOption = parser.accepts("publicKeyPath", "path to public key file")
.withRequiredArg().required();
// TODO: with jopt-simple 5.0, we can make these requiredUnless each other
// which is effectively "one must be present"
licenseOption = parser.accepts("license", "license json spec")
.withRequiredArg();
licenseFileOption = parser.accepts("licenseFile", "license json spec file")
.withRequiredArg();
}
public static void main(String[] args) throws Exception {
ExitStatus exitStatus = new LicenseVerificationTool().execute(args);
exit(exitStatus.status());
exit(new LicenseVerificationTool().main(args, Terminal.DEFAULT));
}
@SuppressForbidden(reason = "Allowed to exit explicitly from #main()")
private static void exit(int status) {
System.exit(status);
@Override
protected int execute(Terminal terminal, OptionSet options) throws Exception {
Path publicKeyPath = PathUtils.get(publicKeyPathOption.value(options));
String licenseSpecString = null;
if (options.has(licenseOption)) {
licenseSpecString = licenseOption.value(options);
}
Path licenseSpecPath = null;
if (options.has(licenseFileOption)) {
licenseSpecPath = PathUtils.get(licenseFileOption.value(options));
}
execute(terminal, publicKeyPath, licenseSpecString, licenseSpecPath);
return ExitCodes.OK;
}
// pkg private for tests
void execute(Terminal terminal, Path publicKeyPath,
String licenseSpecString, Path licenseSpecPath) throws Exception {
if (Files.exists(publicKeyPath) == false) {
throw new UserError(ExitCodes.USAGE, publicKeyPath + " does not exist");
}
final License licenseSpec;
if (licenseSpecString != null) {
licenseSpec = License.fromSource(licenseSpecString);
} else if (licenseSpecPath != null) {
if (Files.exists(licenseSpecPath) == false) {
throw new UserError(ExitCodes.USAGE, licenseSpecPath + " does not exist");
}
licenseSpec = License.fromSource(Files.readAllBytes(licenseSpecPath));
} else {
throw new UserError(ExitCodes.USAGE, "Must specify either --license or --licenseFile");
}
// verify
if (LicenseVerifier.verifyLicense(licenseSpec, Files.readAllBytes(publicKeyPath)) == false) {
throw new UserError(ExitCodes.DATA_ERROR, "Invalid License!");
}
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
builder.startObject();
builder.startObject("license");
licenseSpec.toInnerXContent(builder, ToXContent.EMPTY_PARAMS);
builder.endObject();
builder.endObject();
builder.flush();
terminal.println(builder.string());
}
}

View File

@ -31,19 +31,19 @@ public class LicenseGenerationToolTests extends ESTestCase {
public void testMissingKeyPaths() throws Exception {
LicenseGeneratorTool licenseGeneratorTool = new LicenseGeneratorTool();
Path pub = createTempDir().resolve("pub");
Path priv = createTempDir().resolve("pri");
Path pri = createTempDir().resolve("pri");
UserError e = expectThrows(UserError.class, () -> {
licenseGeneratorTool.execute(Terminal.DEFAULT, pub, priv, null, null);
});
assertTrue(e.getMessage(), e.getMessage().contains("pub does not exist"));
assertEquals(ExitCodes.USAGE, e.exitCode);
Files.createFile(pub);
e = expectThrows(UserError.class, () -> {
licenseGeneratorTool.execute(Terminal.DEFAULT, pub, priv, null, null);
licenseGeneratorTool.execute(Terminal.DEFAULT, pub, pri, null, null);
});
assertTrue(e.getMessage(), e.getMessage().contains("pri does not exist"));
assertEquals(ExitCodes.USAGE, e.exitCode);
Files.createFile(pri);
e = expectThrows(UserError.class, () -> {
licenseGeneratorTool.execute(Terminal.DEFAULT, pub, pri, null, null);
});
assertTrue(e.getMessage(), e.getMessage().contains("pub does not exist"));
assertEquals(ExitCodes.USAGE, e.exitCode);
}
public void testMissingLicenseSpec() throws Exception {

View File

@ -8,27 +8,17 @@ package org.elasticsearch.license.licensor.tools;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.elasticsearch.common.cli.CliTool.Command;
import org.elasticsearch.common.cli.CliTool.ExitStatus;
import org.elasticsearch.common.cli.CliToolTestCase;
import org.elasticsearch.cli.ExitCodes;
import org.elasticsearch.cli.UserError;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.cli.Terminal;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.env.Environment;
import org.elasticsearch.license.core.License;
import org.elasticsearch.license.licensor.TestUtils;
import org.elasticsearch.license.licensor.tools.LicenseVerificationTool.LicenseVerifier;
import org.hamcrest.CoreMatchers;
import org.elasticsearch.test.ESTestCase;
import org.junit.Before;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.core.IsEqual.equalTo;
public class LicenseVerificationToolTests extends CliToolTestCase {
public class LicenseVerificationToolTests extends ESTestCase {
protected Path pubKeyPath = null;
protected Path priKeyPath = null;
@ -39,115 +29,49 @@ public class LicenseVerificationToolTests extends CliToolTestCase {
priKeyPath = getDataPath(TestUtils.PRIVATE_KEY_RESOURCE);
}
public void testParsingMissingLicense() throws Exception {
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
Path path = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME, new String[] { "--publicKeyPath", path.toString() });
assertThat(command, instanceOf(Command.Exit.class));
Command.Exit exitCommand = (Command.Exit) command;
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
}
public void testParsingMissingPublicKeyPath() throws Exception {
License inputLicense = TestUtils.generateSignedLicense(TimeValue.timeValueHours(1), pubKeyPath, priKeyPath);
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
public void testMissingKeyPath() throws Exception {
LicenseVerificationTool tool = new LicenseVerificationTool();
Path pub = createTempDir().resolve("pub");
UserError e = expectThrows(UserError.class, () -> {
licenseVerificationTool.parse(LicenseVerificationTool.NAME,
new String[] { "--license", TestUtils.dumpLicense(inputLicense) });
tool.execute(Terminal.DEFAULT, pub, null, null);
});
assertThat(e.getMessage(), containsString("pub"));
assertTrue(e.getMessage(), e.getMessage().contains("pub does not exist"));
assertEquals(ExitCodes.USAGE, e.exitCode);
}
public void testParsingNonExistentPublicKeyPath() throws Exception {
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
Path path = getDataPath(TestUtils.PUBLIC_KEY_RESOURCE);
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME,
new String[] { "--publicKeyPath", path.toString().concat(".invalid") });
assertThat(command, instanceOf(Command.Exit.class));
Command.Exit exitCommand = (Command.Exit) command;
assertThat(exitCommand.status(), equalTo(ExitStatus.USAGE));
public void testMissingLicenseSpec() throws Exception {
LicenseVerificationTool tool = new LicenseVerificationTool();
UserError e = expectThrows(UserError.class, () -> {
tool.execute(Terminal.DEFAULT, pubKeyPath, null, null);
});
assertTrue(e.getMessage(), e.getMessage().contains("Must specify either --license or --licenseFile"));
assertEquals(ExitCodes.USAGE, e.exitCode);
}
public void testParsingSimple() throws Exception {
License inputLicense = TestUtils.generateSignedLicense(TimeValue.timeValueHours(1), pubKeyPath, priKeyPath);
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME,
new String[] { "--license", TestUtils.dumpLicense(inputLicense),
"--publicKeyPath", getDataPath(TestUtils.PUBLIC_KEY_RESOURCE).toString() });
assertThat(command, instanceOf(LicenseVerifier.class));
LicenseVerifier licenseVerifier = (LicenseVerifier) command;
assertThat(inputLicense, equalTo(licenseVerifier.license));
}
public void testParsingLicenseFile() throws Exception {
License inputLicense = TestUtils.generateSignedLicense(TimeValue.timeValueHours(1), pubKeyPath, priKeyPath);
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME,
new String[]{"--licenseFile", dumpLicenseAsFile(inputLicense),
"--publicKeyPath", getDataPath(TestUtils.PUBLIC_KEY_RESOURCE).toString()});
assertThat(command, instanceOf(LicenseVerifier.class));
LicenseVerifier licenseVerifier = (LicenseVerifier) command;
assertThat(inputLicense, equalTo(licenseVerifier.license));
}
public void testParsingMultipleLicense() throws Exception {
License license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(1), pubKeyPath, priKeyPath);
List<String> arguments = new ArrayList<>();
arguments.add("--license");
arguments.add(TestUtils.dumpLicense(license));
arguments.add("--publicKeyPath");
arguments.add(getDataPath(TestUtils.PUBLIC_KEY_RESOURCE).toString());
LicenseVerificationTool licenseVerificationTool = new LicenseVerificationTool();
Command command = licenseVerificationTool.parse(LicenseVerificationTool.NAME, arguments.toArray(new String[arguments.size()]));
assertThat(command, instanceOf(LicenseVerifier.class));
LicenseVerifier licenseVerifier = (LicenseVerifier) command;
assertThat(licenseVerifier.license, equalTo(license));
}
public void testToolSimple() throws Exception {
License license = TestUtils.generateSignedLicense(TimeValue.timeValueHours(1), pubKeyPath, priKeyPath);
String output = runLicenseVerificationTool(license, getDataPath(TestUtils.PUBLIC_KEY_RESOURCE), ExitStatus.OK);
License outputLicense = License.fromSource(output.getBytes(StandardCharsets.UTF_8));
assertThat(outputLicense, CoreMatchers.equalTo(license));
}
public void testToolInvalidLicense() throws Exception {
public void testBrokenLicense() throws Exception {
License signedLicense = TestUtils.generateSignedLicense(TimeValue.timeValueHours(1), pubKeyPath, priKeyPath);
License tamperedLicense = License.builder()
.fromLicenseSpec(signedLicense, signedLicense.signature())
.expiryDate(signedLicense.expiryDate() + randomIntBetween(1, 1000)).build();
runLicenseVerificationTool(tamperedLicense, getDataPath(TestUtils.PUBLIC_KEY_RESOURCE), ExitStatus.DATA_ERROR);
.fromLicenseSpec(signedLicense, signedLicense.signature())
.expiryDate(signedLicense.expiryDate() + randomIntBetween(1, 1000)).build();
LicenseVerificationTool tool = new LicenseVerificationTool();
UserError e = expectThrows(UserError.class, () -> {
tool.execute(Terminal.DEFAULT, pubKeyPath, TestUtils.dumpLicense(tamperedLicense), null);
});
assertEquals("Invalid License!", e.getMessage());
assertEquals(ExitCodes.DATA_ERROR, e.exitCode);
}
private String dumpLicenseAsFile(License license) throws Exception {
Path tempFile = createTempFile();
Files.write(tempFile, TestUtils.dumpLicense(license).getBytes(StandardCharsets.UTF_8));
return tempFile.toAbsolutePath().toString();
public void testLicenseSpecString() throws Exception {
License signedLicense = TestUtils.generateSignedLicense(TimeValue.timeValueHours(1), pubKeyPath, priKeyPath);
LicenseVerificationTool tool = new LicenseVerificationTool();
tool.execute(Terminal.DEFAULT, pubKeyPath, TestUtils.dumpLicense(signedLicense), null);
}
private String runLicenseVerificationTool(License license, Path publicKeyPath, ExitStatus expectedExitStatus) throws Exception {
CaptureOutputTerminal outputTerminal = new CaptureOutputTerminal();
Settings settings = Settings.builder().put("path.home", createTempDir("LicenseVerificationToolTests")).build();
LicenseVerifier licenseVerifier = new LicenseVerifier(outputTerminal, license, publicKeyPath);
assertThat(execute(licenseVerifier, settings), equalTo(expectedExitStatus));
if (expectedExitStatus == ExitStatus.OK) {
assertThat(outputTerminal.getTerminalOutput().size(), equalTo(1));
return outputTerminal.getTerminalOutput().get(0);
} else {
return null;
}
}
private ExitStatus execute(Command cmd, Settings settings) throws Exception {
Environment env = new Environment(settings);
return cmd.execute(settings, env);
public void testLicenseSpecFile() throws Exception {
License signedLicense = TestUtils.generateSignedLicense(TimeValue.timeValueHours(1), pubKeyPath, priKeyPath);
Path licenseSpecFile = createTempFile();
Files.write(licenseSpecFile, TestUtils.dumpLicense(signedLicense).getBytes(StandardCharsets.UTF_8));
LicenseVerificationTool tool = new LicenseVerificationTool();
tool.execute(Terminal.DEFAULT, pubKeyPath, null, licenseSpecFile);
}
}