blob: 1b3e6a2ab794a673aec889484549ff61547bea25 [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.ignite.console.agent;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.ProtectionDomain;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsonorg.JsonOrgModule;
import io.socket.client.Ack;
import okhttp3.ConnectionSpec;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.ssl.SSLContextWrapper;
import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONObject;
/**
* Utility methods.
*/
public class AgentUtils {
/** */
private static final Logger log = Logger.getLogger(AgentUtils.class.getName());
/** */
private static final char[] EMPTY_PWD = new char[0];
/** JSON object mapper. */
private static final ObjectMapper MAPPER = new ObjectMapper();
static {
// Register special module with basic serializers.
MAPPER.registerModule(new JsonOrgModule());
}
/** */
private static final Ack NOOP_CB = args -> {
if (args != null && args.length > 0 && args[0] instanceof Throwable)
log.error("Failed to execute request on agent.", (Throwable)args[0]);
else
log.info("Request on agent successfully executed " + Arrays.toString(args));
};
/**
* Default constructor.
*/
private AgentUtils() {
// No-op.
}
/**
* @param path Path to normalize.
* @return Normalized file path.
*/
public static String normalizePath(String path) {
return path != null ? path.replace('\\', '/') : null;
}
/**
* @return App folder.
*/
public static File getAgentHome() {
try {
ProtectionDomain domain = AgentLauncher.class.getProtectionDomain();
// Should not happen, but to make sure our code is not broken.
if (domain == null || domain.getCodeSource() == null || domain.getCodeSource().getLocation() == null) {
log.warn("Failed to resolve agent jar location!");
return null;
}
// Resolve path to class-file.
URI classesUri = domain.getCodeSource().getLocation().toURI();
boolean win = System.getProperty("os.name").toLowerCase().contains("win");
// Overcome UNC path problem on Windows (http://www.tomergabel.com/JavaMishandlesUNCPathsOnWindows.aspx)
if (win && classesUri.getAuthority() != null)
classesUri = new URI(classesUri.toString().replace("file://", "file:/"));
return new File(classesUri).getParentFile();
}
catch (URISyntaxException | SecurityException ignored) {
log.warn("Failed to resolve agent jar location!");
return null;
}
}
/**
* Gets file associated with path.
* <p>
* First check if path is relative to agent home.
* If not, check if path is absolute.
* If all checks fail, then {@code null} is returned.
* <p>
*
* @param path Path to resolve.
* @return Resolved path as file, or {@code null} if path cannot be resolved.
*/
public static File resolvePath(String path) {
assert path != null;
File home = getAgentHome();
if (home != null) {
File file = new File(home, normalizePath(path));
if (file.exists())
return file;
}
// 2. Check given path as absolute.
File file = new File(path);
if (file.exists())
return file;
return null;
}
/**
* Get callback from handler arguments.
*
* @param args Arguments.
* @return Callback or noop callback.
*/
public static Ack safeCallback(Object[] args) {
boolean hasCb = args != null && args.length > 0 && args[args.length - 1] instanceof Ack;
return hasCb ? (Ack)args[args.length - 1] : NOOP_CB;
}
/**
* Remove callback from handler arguments.
*
* @param args Arguments.
* @return Arguments without callback.
*/
public static Object[] removeCallback(Object[] args) {
boolean hasCb = args != null && args.length > 0 && args[args.length - 1] instanceof Ack;
return hasCb ? Arrays.copyOf(args, args.length - 1) : args;
}
/**
* Map java object to JSON object.
*
* @param obj Java object.
* @return {@link JSONObject} or {@link JSONArray}.
* @throws IllegalArgumentException If conversion fails due to incompatible type.
*/
public static Object toJSON(Object obj) {
if (obj instanceof Iterable)
return MAPPER.convertValue(obj, JSONArray.class);
return MAPPER.convertValue(obj, JSONObject.class);
}
/**
* Map JSON object to java object.
*
* @param obj {@link JSONObject} or {@link JSONArray}.
* @param toValType Expected value type.
* @return Mapped object type of {@link T}.
* @throws IllegalArgumentException If conversion fails due to incompatible type.
*/
public static <T> T fromJSON(Object obj, Class<T> toValType) throws IllegalArgumentException {
return MAPPER.convertValue(obj, toValType);
}
/**
* @param pathToJks Path to java key store file.
* @param pwd Key store password.
* @return Key store.
* @throws GeneralSecurityException If failed to load key store.
* @throws IOException If failed to load key store file content.
*/
private static KeyStore keyStore(String pathToJks, char[] pwd) throws GeneralSecurityException, IOException {
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream(pathToJks), pwd);
return keyStore;
}
/**
* @param keyStorePath Path to key store.
* @param keyStorePwd Key store password.
* @return Key managers.
* @throws GeneralSecurityException If failed to load key store.
* @throws IOException If failed to load key store file content.
*/
private static KeyManager[] keyManagers(String keyStorePath, String keyStorePwd)
throws GeneralSecurityException, IOException {
if (keyStorePath == null)
return null;
char[] keyPwd = keyStorePwd != null ? keyStorePwd.toCharArray() : EMPTY_PWD;
KeyStore keyStore = keyStore(keyStorePath, keyPwd);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keyPwd);
return kmf.getKeyManagers();
}
/**
* @param trustAll {@code true} If we trust to self-signed sertificates.
* @param trustStorePath Path to trust store file.
* @param trustStorePwd Trust store password.
* @return Trust manager
* @throws GeneralSecurityException If failed to load trust store.
* @throws IOException If failed to load trust store file content.
*/
public static X509TrustManager trustManager(boolean trustAll, String trustStorePath, String trustStorePwd)
throws GeneralSecurityException, IOException {
if (trustAll)
return disabledTrustManager();
if (trustStorePath == null)
return null;
char[] trustPwd = trustStorePwd != null ? trustStorePwd.toCharArray() : EMPTY_PWD;
KeyStore trustKeyStore = keyStore(trustStorePath, trustPwd);
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(trustKeyStore);
TrustManager[] trustMgrs = tmf.getTrustManagers();
return (X509TrustManager)Arrays.stream(trustMgrs)
.filter(tm -> tm instanceof X509TrustManager)
.findFirst()
.orElseThrow(() -> new IllegalStateException("X509TrustManager manager not found"));
}
/**
* Create SSL socket factory.
*
* @param keyStorePath Path to key store.
* @param keyStorePwd Key store password.
* @param trustMgr Trust manager.
* @param cipherSuites Optional cipher suites.
* @throws GeneralSecurityException If failed to load trust store.
* @throws IOException If failed to load store file content.
*/
public static SSLSocketFactory sslSocketFactory(
String keyStorePath, String keyStorePwd,
X509TrustManager trustMgr,
List<String> cipherSuites
) throws GeneralSecurityException, IOException {
KeyManager[] keyMgrs = keyManagers(keyStorePath, keyStorePwd);
if (keyMgrs == null && trustMgr == null)
return null;
SSLContext ctx = SSLContext.getInstance("TLS");
if (!F.isEmpty(cipherSuites))
ctx = new SSLContextWrapper(ctx, new SSLParameters(cipherSuites.toArray(new String[0])));
ctx.init(keyMgrs, new TrustManager[] {trustMgr}, null);
return ctx.getSocketFactory();
}
/**
* Create SSL configuration.
*
* @param cipherSuites SSL cipher suites.
*/
public static List<ConnectionSpec> sslConnectionSpec(List<String> cipherSuites) {
return Collections.singletonList(new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.cipherSuites(cipherSuites.toArray(new String[0]))
.build());
}
/**
* Create a trust manager that trusts all certificates.
*/
private static X509TrustManager disabledTrustManager() {
return new X509TrustManager() {
/** {@inheritDoc} */
@Override public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
/** {@inheritDoc} */
@Override public void checkClientTrusted(X509Certificate[] certs, String authType) {
// No-op.
}
/** {@inheritDoc} */
@Override public void checkServerTrusted(X509Certificate[] certs, String authType) {
// No-op.
}
};
}
}