blob: 190db99d6f0e515be1b04875d4777f64f9c2e3d0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.knox.gateway.shell;
import org.apache.knox.gateway.shell.knox.token.Get;
import org.apache.knox.gateway.shell.knox.token.Token;
import org.apache.knox.gateway.shell.util.ClientTrustStoreHelper;
import org.apache.knox.gateway.util.JsonUtils;
import org.apache.knox.gateway.util.X509CertificateUtil;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermission;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
public class KnoxSh {
private static final String USAGE_PREFIX = "KnoxSh {cmd} [options]";
private static final String COMMANDS =
" [--help]\n" +
" [" + KnoxBuildTrustStore.USAGE + "]\n" +
" [" + KnoxInit.USAGE + "]\n" +
" [" + KnoxDestroy.USAGE + "]\n" +
" [" + KnoxList.USAGE + "]\n";
/** allows stdout to be captured if necessary */
PrintStream out = System.out;
/** allows stderr to be captured if necessary */
PrintStream err = System.err;
private Command command;
private String gateway;
public int run(String[] args) throws Exception {
int exitCode;
try {
exitCode = init(args);
if (exitCode != 0) {
return exitCode;
}
if (command != null && command.validate()) {
command.execute();
} else {
out.println("ERROR: Invalid Command" + "\n" + "Unrecognized option:" +
args[0] + "\n" +
"A fatal exception has occurred. Program will exit.");
exitCode = -2;
}
} catch (Exception e) {
e.printStackTrace( err );
err.flush();
return -3;
}
return exitCode;
}
/**
* Parse the command line arguments and initialize the data
* <pre>
* % knoxcli version
* % knoxcli service-test [--u user] [--p password] [--cluster clustername] [--hostname name] [--port port]
*
* </pre>
* @param args command line arguments
* @return exit code
* @throws IOException exception on init
*/
private int init(String[] args) throws IOException {
if (args.length == 0) {
printKnoxShellUsage();
return -1;
}
for (int i = 0; i < args.length; i++) { // parse command line
if ( args[i].equals("destroy") ) {
command = new KnoxDestroy();
} else if ( args[i].equals("buildTrustStore") ) {
command = new KnoxBuildTrustStore();
} else if ( args[i].equals("init") ) {
command = new KnoxInit();
} else if ( args[i].equals("list") ) {
command = new KnoxList();
} else if (args[i].equals("--gateway")) {
if( i+1 >= args.length || args[i+1].startsWith( "-" ) ) {
printKnoxShellUsage();
return -1;
}
this.gateway = args[++i];
} else if (args[i].equals("--help")) {
printKnoxShellUsage();
return -1;
} else {
printKnoxShellUsage();
//ToolRunner.printGenericCommandUsage(System.err);
return -1;
}
}
return 0;
}
private void printKnoxShellUsage() {
out.println( USAGE_PREFIX + "\n" + COMMANDS );
if ( command != null ) {
out.println(command.getUsage());
} else {
char[] chars = new char[79];
Arrays.fill( chars, '=' );
String div = new String( chars );
out.println( div );
out.println( KnoxInit.USAGE + "\n\n" + KnoxInit.DESC );
out.println();
out.println( div );
out.println(KnoxDestroy.USAGE + "\n\n" + KnoxDestroy.DESC);
out.println();
out.println( div );
out.println(KnoxList.USAGE + "\n\n" + KnoxList.DESC);
out.println();
out.println( div );
}
}
private abstract class Command {
public boolean validate() {
return true;
}
public abstract void execute() throws Exception;
public abstract String getUsage();
}
class KnoxBuildTrustStore extends Command {
private static final String USAGE = "buildTrustStore --gateway server-url";
private static final String DESC = "Downloads the gateway server's public certificate and builds a trust store.";
private static final String GATEWAY_CERT_NOT_EXPORTED = "Finished work without building truststore";
private static final String GATEWAY_CERT_EXPORTED_MESSAGE_PREFIX = "Gateway server's certificate is exported into ";
@Override
public void execute() throws Exception {
String result = GATEWAY_CERT_NOT_EXPORTED;
try {
final X509Certificate[] gatewayServerPublicCerts = fetchPublicCertsFromGatewayServer();
if (gatewayServerPublicCerts != null) {
final File trustStoreFile = ClientTrustStoreHelper.getClientTrustStoreFile();
final String trustStorePassword = ClientTrustStoreHelper.getClientTrustStoreFilePassword();
X509CertificateUtil.writeCertificatesToJks(gatewayServerPublicCerts, trustStoreFile, trustStorePassword);
result = GATEWAY_CERT_EXPORTED_MESSAGE_PREFIX + trustStoreFile.getAbsolutePath();
}
} catch(Exception e) {
throw new KnoxShellException("Error while building trust store", e);
}
out.println(result);
}
private X509Certificate[] fetchPublicCertsFromGatewayServer() throws Exception {
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
final X509TrustManager defaultTrustManager = (X509TrustManager) trustManagerFactory.getTrustManagers()[0];
final CertificateChainAwareTrustManager trustManagerWithCertificateChain = new CertificateChainAwareTrustManager(defaultTrustManager);
final SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { trustManagerWithCertificateChain }, null);
final URL url = new URL(gateway);
final int port = url.getPort() == -1 ? url.getDefaultPort() : url.getPort();
out.println("Opening connection to " + url.getHost() + ":" + port + "...");
try (Socket socket = sslContext.getSocketFactory().createSocket(url.getHost(), port)) {
socket.setSoTimeout(10000);
out.println("Starting SSL handshake...");
((SSLSocket) socket).startHandshake();
out.println("No errors, certificate is already trusted");
return null; //we already trust the given site's certs; it does not make sense to build a new truststore
} catch (SSLException e) {
// NOP; this is expected in case the gateway server's certificate is not in the
// trust store the JVM uses
}
return trustManagerWithCertificateChain.serverCertificateChain;
}
@Override
public String getUsage() {
return USAGE + ":\n\n" + DESC;
}
private class CertificateChainAwareTrustManager implements X509TrustManager {
private final X509TrustManager defaultTrustManager;
private X509Certificate[] serverCertificateChain;
CertificateChainAwareTrustManager(X509TrustManager tm) {
this.defaultTrustManager = tm;
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return defaultTrustManager.getAcceptedIssuers();
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
defaultTrustManager.checkClientTrusted(chain, authType);
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
this.serverCertificateChain = chain;
defaultTrustManager.checkServerTrusted(chain, authType);
}
}
}
private class KnoxInit extends Command {
public static final String USAGE = "init --gateway topology-url";
public static final String DESC = "Initializes a Knox token session.";
@Override
public void execute() throws Exception {
Credentials credentials = new Credentials();
credentials.add("ClearInput", "Enter username: ", "user")
.add("HiddenInput", "Enter password: ", "pass");
credentials.collect();
String username = credentials.get("user").string();
String pass = credentials.get("pass").string();
KnoxSession session = null;
Get.Response response;
try {
session = KnoxSession.login(gateway, username, pass);
response = Token.get( session ).now();
String text = response.getString();
Map<String, String> json = JsonUtils.getMapFromJsonString(text);
//println "Access Token: " + json.access_token
System.out.println("knoxinit successful!");
displayTokenDetails(json);
File tokenfile = new File(System.getProperty("user.home"), ".knoxtokencache");
try( OutputStream fos = Files.newOutputStream(tokenfile.toPath()) ) {
fos.write(text.getBytes(StandardCharsets.UTF_8));
Set<PosixFilePermission> perms = new HashSet<>();
//add owners permission only
perms.add(PosixFilePermission.OWNER_READ);
perms.add(PosixFilePermission.OWNER_WRITE);
Files.setPosixFilePermissions(Paths.get(System.getProperty("user.home") + "/.knoxtokencache"), perms);
}
} catch(KnoxShellException he) {
System.out.println("Failure to acquire token. Please verify your credentials and Knox URL and try again.");
}
if ( session != null ) {
session.shutdown();
}
}
@Override
public String getUsage() {
return USAGE + ":\n\n" + DESC;
}
}
private class KnoxDestroy extends Command {
public static final String USAGE = "destroy";
public static final String DESC = "Destroys an Knox token session.";
@Override
public void execute() throws Exception {
File tokenfile = new File(System.getProperty("user.home"), ".knoxtokencache");
tokenfile.delete();
}
@Override
public String getUsage() {
return USAGE + ":\n\n" + DESC;
}
}
private class KnoxList extends Command {
public static final String USAGE = "list";
public static final String DESC = "Displays Knox token details.";
@Override
public void execute() throws Exception {
String tokenFilePath = System.getProperty("user.home") +
File.separator + ".knoxtokencache";
if (new File(tokenFilePath).exists()) {
String tokenfile = readFile(tokenFilePath);
if (tokenfile != null) {
Map<String, String> json = JsonUtils.getMapFromJsonString(tokenfile);
displayTokenDetails(json);
}
}
else {
System.out.println("Knox token cache does not exist. Please login with init.");
}
}
@Override
public String getUsage() {
return USAGE + ":\n\n" + DESC;
}
}
private void displayTokenDetails(Map<String, String> json) {
System.out.println("Token Type: " + json.get("token_type"));
DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy hh:mm:ss", Locale.getDefault());
long milliSeconds= Long.parseLong(json.get("expires_in"));
Calendar calendar = Calendar.getInstance(TimeZone.getDefault(), Locale.getDefault());
calendar.setTimeInMillis(milliSeconds);
System.out.println("Expires On: " + formatter.format(calendar.getTime()));
String targetUrl = json.get("target_url");
if (targetUrl != null) {
System.out.println("Target URL: " + json.get("target_url"));
} else {
System.out.println("No specific target URL configured.");
}
}
private String readFile(String file) throws IOException {
try (InputStream inputStream = Files.newInputStream(Paths.get(file));
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
BufferedReader reader = new BufferedReader(inputStreamReader)) {
String line;
StringBuilder stringBuilder = new StringBuilder();
String ls = System.getProperty("line.separator");
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
stringBuilder.append(ls);
}
return stringBuilder.toString();
}
}
/**
* @param args command line arguments
* @throws Exception thrown if there is an issue
*/
public static void main(String[] args) throws Exception {
KnoxSh sh = new KnoxSh();
int res = sh.run(args);
System.exit(res);
}
}