NativeSecureConnection.java
/*
** Module : NativeSecureConnection.java
** Abstract : Helper for connecting to a remote P2J server from native code, via JNI.
**
** Copyright (c) 2014-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------Description---------------------------------
** 001 CA 20140211 Created initial version.
** 002 CA 20140226 Write any errors to STDERR instead of STDOUT, so they end up on the server's
** log.
** 003 MAG 20140718 Implements remote batch clients. Substitute placeholders on remote launch.
** IAS 20210325 Added NIO configuration via BootstrapConfig
** 004 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 005 GBB 20240729 Initialize CentralLogger in try-catch.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.main;
import java.util.*;
import com.goldencode.p2j.cfg.*;
import com.goldencode.p2j.net.*;
import com.goldencode.p2j.security.SecurityManager;
import com.goldencode.p2j.util.logging.*;
/**
* Helper class for connecting to a remote P2J server from native code, via JNI. Used by the
* {@code spawn} tool when no password is specified and authentication needs to be done via P2J.
*/
public class NativeSecureConnection
{
/**
* Establish a secure connection to the target P2J server and retrieve the spawn command
* registered with this request, via the specified UUID.
* <p>
* Authentication will be performed with the temporary credentials set in the environment. If
* the secure connection is not possible, the received certificate can not be validated or
* if the authentication is not possible, it returns {@code null}.
*
* @param port
* The P2J server's secure port.
* @param host
* The P2J server's host.
* @param alias
* The certificate alias.
* @param uuid
* The UUID identifying this request.
* @param misc
* Misc. BootstrapConfig overrides
* @return The spawn command or {@code null}.
*/
public static String[] command(int port, String host, String alias, String uuid, String misc)
{
// establish a secure connection to the P2J server running on the specified port; use the
// certificate targeted by the specified alias for authentication.
Session sess = null;
try
{
CentralLogger.setMode(CentralLogger.Mode.SPAWNER_JVM);
CentralLogger log = CentralLogger.get(NativeSecureConnection.class);
BootstrapConfig cfg = new BootstrapConfig();
cfg.setServer(false);
cfg.setConfigItem("net", "connection", "secure", "true");
cfg.setConfigItem("net", "server", "secure_port", Integer.toString(port));
cfg.setConfigItem("net", "server", "host", host);
cfg.setConfigItem("net", "queue", "conversation", "false");
cfg.setConfigItem("security", "certificate", "validate", "true");
cfg.setConfigItem("security", "truststore", "filename", "srv-certs.store");
cfg.setConfigItem("security", "truststore", "alias", alias);
cfg.setConfigItem("security", "authentication", "type", "program");
// Get user credentials from environment
String subject = System.getenv(TemporaryAccount.P2J_SUBJECT);
String password = System.getenv(TemporaryAccount.P2J_PASSWORD);
cfg.setConfigItem("access", "subject", "id", TemporaryAccount.fromHex(subject));
cfg.setConfigItem("access", "password", "user", TemporaryAccount.fromHex(password));
String[] overrides = misc.split(" ");
for (String override : overrides)
{
String[] args = override.split(":");
if (args.length != 3)
{
log.severe("Invalid override: " + override + ", ignored");
continue;
}
String [] last = args[2].split("=");
if (last.length != 2)
{
log.severe("Invalid override: " + override + ", ignored");
continue;
}
cfg.setConfigItem(args[0], args[1], last[0], last[1]);
}
@SuppressWarnings("unused")
SecurityManager secMgr = SecurityManager.createInstance(cfg);
SessionManager sessMgr = SessionManagerFactory.createLeafNode(cfg);
// establish a secure connection with the remote P2J server
sess = sessMgr.connectDirect(cfg, null, null);
RemoteSpawner remote =
(RemoteSpawner) RemoteObject.obtainNetworkInstance(RemoteSpawner.class, sess);
// get the command in use for this UUID
String[] cmd = remote.getCommand(uuid);
return remote(cmd);
}
catch (Throwable t)
{
final String msg = "Problem retrieving the spawn command, using these settings: " +
"port [%d] host [%s] alias [%s] UUID [%s]";
CentralLogger.get(NativeSecureConnection.class)
.severe(String.format(msg, port, host, alias, uuid), t);
// do not allow exceptions to propagate into native code.
return null;
}
finally
{
if (sess != null)
{
sess.terminate();
}
}
}
/**
* Prepare command substituting placeholder with broker values.
*
* @param args
* Command arguments read from server.
*
* @return Command arguments having placeholders replaced.
*/
private static String[] remote(String[] args)
{
// Get environment variables
Map<String, String> env = System.getenv();
// host
String host = env.get(BrokerCore.BROKER_HOST);
// secure port
String port = env.get(BrokerCore.BROKER_PORT);
// JVM arguments
String jvmargs = env.get(BrokerCore.BROKER_JVMARGS);
// classpath
String classpath = env.get(BrokerCore.BROKER_CLASSPATH);
// Command line arguments
List<String> command = new LinkedList<String>();
// Replace placeholders
for (String cmd : args)
{
// replace host
if (cmd.indexOf(BrokerCore.PARAM_HOST) > -1)
{
if (host == null)
{
throw new NullPointerException("Null value not allowed for host.");
}
// host
command.add(cmd.replace(BrokerCore.PARAM_HOST, host));
}
// replace port
else if (cmd.indexOf(BrokerCore.PARAM_PORT) > -1)
{
if (port == null)
{
throw new NullPointerException("Null value not allowed for secure port.");
}
// secure port
command.add(cmd.replace(BrokerCore.PARAM_PORT, port));
}
else if (BrokerCore.PARAM_JVM.equals(cmd))
{
if (jvmargs == null)
{
throw new NullPointerException("Null value not allowed for JVM arguments.");
}
// add JVM arguments
String[] tokens = jvmargs.split("\\" + BrokerCore.BROKER_JVMARGS_SEPARATOR);
command.addAll(Arrays.asList(tokens));
}
else if (BrokerCore.PARAM_CP.equals(cmd))
{
if (classpath == null)
{
throw new NullPointerException("Null value not allowed for CLASSPATH.");
}
// add CLASSAPTH
command.add("-classpath");
command.add(classpath);
}
else
{
command.add(cmd);
}
}
return command.toArray(new String[0]);
}
}