We are using Apache-Mina SSHD 1.7 to expose a SFTP server that uses a custom file-system implementation which creates a file system per company. So users of the same company (or more precisely for the same connector) will access the same file system while a users of an other company will access a filesystem unique to their company. The file-system is moreover just a view on a MySQL database and will write uploaded files after some conversions directly into the DB and read files on download from the DB.
The setup of the server looks like the excerpt below
void init() {
server = MessageSftpServer.setUpDefaultServer();
server.setPort(port);
LOG.debug("Server is configured for port {}", port);
File pemFile = new File(pemLocation);
FileKeyPairProvider provider = new FileKeyPairProvider(pemFile.toPath());
validateKeyPairProvider(provider.loadKeys(), publicKeyList);
server.setKeyPairProvider(provider);
server.setCommandFactory(new ScpCommandFactory());
server.setPasswordAuthenticator(
(String username, String password, ServerSession session) -> {
...
});
PropertyResolverUtils.updateProperty(server, ServerAuthenticationManager.MAX_AUTH_REQUESTS, 3);
SftpSubsystemFactory sftpFactory = new SftpSubsystemFactory.Builder()
.withShutdownOnExit(false)
.withUnsupportedAttributePolicy(UnsupportedAttributePolicy.Warn)
.build();
server.setSubsystemFactories(Collections.singletonList(sftpFactory));
// add our custom virtual file system to trick the user into believing she is operating against
// a true file system instead of just operating against a backing database
server.setFileSystemFactory(
new DBFileSystemFactory(connectorService, companyService, mmService, template));
// filter connection attempts based on remote IPs defined in connectors
server.addSessionListener(whitelistSessionListener);
}
Within the file system factory we basically just create the URI for the file system provider and pass it to the respective method of it
#Override
public FileSystem createFileSystem(Session session) throws IOException {
SFTPServerConnectorEntity connector =
connectorService.getSFTPServerConnectorForUser(session.getUsername());
if (null == connector) {
throw new IOException("No SFTP Server connector found for user " + session.getUsername());
}
String ip = CommonUtils.getIPforSession(session);
URI fsUri = URI.create("dbfs://" + session.getUsername() + "#" + ip + "/" + connector.getUuid());
LOG.debug("Checking whether to create file system for user {} connected via IP {}",
session.getUsername(), ip);
Map<String, Object> env = new HashMap<>();
env.put("UserAgent", session.getClientVersion());
try {
return fileSystemProvider.newFileSystem(fsUri, env);
} catch (FileSystemAlreadyExistsException fsaeEx) {
LOG.debug("Reusing existing filesystem for connector {}", connector.getUuid());
return fileSystemProvider.getFileSystem(fsUri);
}
}
and within the provider we simply parse the values from the provided URI and environment variables to create the final filesystem if none was yet available within the cache
#Override
public DBFileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
LOG.trace("newFileSystem({}, {}))", uri, env);
ConnectionInfo ci = ConnectionInfo.fromSchemeSpecificPart(uri.getSchemeSpecificPart());
String cacheKey = generateCacheKey(ci);
synchronized (fileSystems) {
if (fileSystems.containsKey(cacheKey)) {
throw new FileSystemAlreadyExistsException(
"A filesystem for connector " + ci.getConnectorUuid()
+ " connected from IP " + ci.getIp() + " already exists");
}
}
SFTPServerConnectorEntity connector =
connectorService.get(SFTPServerConnectorEntity.class, ci.getConnectorUuid());
List<CompanyEntity> companies = companyService.getCompaniesForConnector(connector);
if (companies.size() < 1) {
throw new IOException("No company for connector " + connector.getUuid() + " found");
}
DBFileSystem fileSystem = null;
synchronized (fileSystems) {
if (!fileSystems.containsKey(cacheKey)) {
LOG.info("Created new filesystem for connector {} (Remote IP: {}, User: {}, UserAgent: {})",
ci.getConnectorUuid(), ci.getIp(), ci.getUser(), env.get("UserAgent"));
fileSystem = new DBFileSystem(this, connector.getUsername(), companies, connector,
template, ci.getIp(), (String) env.get("UserAgent"));
Pair<DBFileSystem, AtomicInteger> sessions = Pair.of(fileSystem, new AtomicInteger(1));
fileSystems.put(cacheKey, sessions);
}
}
if (null == fileSystem) {
throw new FileSystemAlreadyExistsException(
"A filesystem for connector " + ci.getConnectorUuid()
+ " connected from IP " + ci.getIp() + " already exists");
}
return fileSystem;
}
#Override
public DBFileSystem getFileSystem(URI uri) {
LOG.trace("getFileSystem({}))", uri);
String schemeSpecificPart = uri.getSchemeSpecificPart();
if (!schemeSpecificPart.startsWith("//")) {
throw new IllegalArgumentException(
"Invalid URI provided. URI must have a form of 'dbfs://ip:port/connector-uuid' where "
+ "'ip' is the IP address of the connected user, 'port' is the remote port of the user and "
+ "'connector-uuid' is a UUID string identifying the connector the filesystem was created for");
}
ConnectionInfo ci = ConnectionInfo.fromSchemeSpecificPart(schemeSpecificPart);
String cacheKey = generateCacheKey(ci);
if (!fileSystems.containsKey(cacheKey)) {
throw new FileSystemNotFoundException(
"No filesystem found for connector " + ci.getConnectorUuid() + " with connection from IP "
+ ci.getIp());
}
Pair<DBFileSystem, AtomicInteger> sessions = fileSystems.get(cacheKey);
if (!sessions.getKey().isOpen()) {
throw new FileSystemNotFoundException(
"Filesystem for connector " + ci.getConnectorUuid() + " with connection from IP " + ci
.getIp() + " was closed already");
}
int curSessions = sessions.getValue().incrementAndGet();
LOG.info("Added further session to filesystem for connector {}. Current connected sessions: {} (Remote IP: {}, User: {})",
ci.getConnectorUuid(), curSessions, ci.getIp(), ci.getUser());
return sessions.getKey();
}
private String generateCacheKey(String user, String ip, String connectorUuid) {
return connectorUuid + "_" + ip + "_" + user;
}
private String generateCacheKey(ConnectionInfo ci) {
return generateCacheKey(ci.getUser(), ci.getIp(), ci.getConnectorUuid());
}
This works out really well, however, as more and more users get added to the SFTP server the monitoring of the performed actions is suffering a bit due to the lack of propper MDC logging. Simply adding MDC logging isn't working cleanly as Mina or SSHD in particular share the threads among connected users which lead to the MDC context printing the wrong information at times which further lead to confusion on analyzing the log. As a temporary solution we removed it currently from the project.
We also tried to customize Nio2Session (and a couple of other classes) in order to intervene into the threading creation, though this classes were obviously not designed for inheritance which later lead to problems down the road.
Is there a better strategy to include propper MDC logging in our particular scenario where not one file system is used but a filesystem per company approach?
Related
I am trying to do a request on a REST service on two different web servers. On both servers it is necessary to present a keyStore, obviously they are different keyStores. Running the code below I am having successful only in the first request (for the test environment), but when I do the second request (for the staging environment), the request presents the first keyStore, in this case testKeyStore.jks.
I tried to clear the keyStore property of the System class and set a new value. In println it is displayed as if the property was changed, but when I see the log on the staging server, the testKeyStore was presented, not the stagingKeyStore.
If I change de order, first STAGING and then TEST, in doStaff() method, the second request fail because the stagingKeyStore is present in the test-server.
Is there some solution to solve this problem?
public void doStaff() {
callServer("TEST");
callServer("STAGING");
}
private void callServer(String enviroment) {
String url = "";
if ("TEST".equalsIgnoreCase(enviroment))
url = "https://test-server/dostaff";
else if("STAGING".equalsIgnoreCase(enviroment))
url = "https://staging-server/dostaff";
try {
System.clearProperty("javax.net.ssl.keyStore");
System.out.println(enviroment + " -> " + System.getProperty("javax.net.ssl.keyStore"));
if ("TEST".equalsIgnoreCase(enviroment)) {
System.setProperty("javax.net.ssl.keyStore", "C:\\temp\\testKeyStore.jks");
} else if("STAGING".equalsIgnoreCase(enviroment)) {
System.setProperty("javax.net.ssl.keyStore", "C:\\temp\\stagingKeyStore.jks");
}
System.out.println(enviroment + " -> " + System.getProperty("javax.net.ssl.keyStore"));
//...
} catch (Exception e) {
e.printStackTrace();
}
}
The application is packaged as executable jar, but it can't find the webapp directory that is included with the fat jar. so I must include the web app in the same directory as the fat jar.
I suspect the issue has to do with this code:
public void init(String host, int port) throws Exception {
logger.info("Starting Server bound to '" + host + ":" + port + "'");
String memory = Configurations.get("refine.memory");
if (memory != null) {
logger.info("refine.memory size: " + memory + " JVM Max heap: " + Runtime.getRuntime().maxMemory());
}
int maxThreads = Configurations.getInteger("refine.queue.size", 30);
int maxQueue = Configurations.getInteger("refine.queue.max_size", 300);
long keepAliveTime = Configurations.getInteger("refine.queue.idle_time", 60);
LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(maxQueue);
threadPool = new ThreadPoolExecutor(maxThreads, maxQueue, keepAliveTime, TimeUnit.SECONDS, queue);
this.setThreadPool(new ThreadPoolExecutorAdapter(threadPool));
Connector connector = new SocketConnector();
connector.setPort(port);
connector.setHost(host);
connector.setMaxIdleTime(Configurations.getInteger("refine.connection.max_idle_time",60000));
connector.setStatsOn(false);
this.addConnector(connector);
File webapp = new File("webapp");
final String contextPath = Configurations.get("refine.context_path","/");
final int maxFormContentSize = Configurations.getInteger("refine.max_form_content_size", 1048576);
logger.info("Initializing context: '" + contextPath + "' from '" + webapp.getAbsolutePath() + "'");
WebAppContext context = new WebAppContext();
URL webRootLocation = this.getClass().getResource("/webapp");
if (webRootLocation == null)
{
throw new IllegalStateException("Unable to determine webroot URL location");
}
URI webRootUri = URI.create(webRootLocation.toURI().toASCIIString());
System.err.printf("Web Root location: %s%n",webRootLocation);
System.err.printf("Web Root URI: %s%n",webRootUri);
context.setContextPath(webRootLocation.toString());
context.setBaseResource(Resource.newResource(webRootLocation));
context.setMaxFormContentSize(maxFormContentSize);
this.setHandler(context);
this.setHandler(context);
this.setStopAtShutdown(true);
this.setSendServerVersion(true);
// Enable context autoreloading
if (Configurations.getBoolean("refine.autoreload",false)) {
scanForUpdates(webapp, context);
}
// start the server
try {
this.start();
} catch (BindException e) {
logger.error("Failed to start server - is there another copy running already on this port/address?");
throw e;
}
configure(context);
}
I found what seems to be a possible solution is this SO,
am I on the right path ? how would I fix this ?
Edit
I modified the code following advice given here and web searches, but now I get the following errors:
#-ThinkPad-T450s:~/projects/github/OpenRefine/fatjar$ java -jar openrefinefat.jar
10:54:47.405 [ refine_server] Starting Server bound to '127.0.0.1:3333' (0ms)
10:54:47.416 [ refine_server] Initializing context: '/' from '/home/me/projects/github/OpenRefine/fatjar/webapp' (11ms)
Web Root location: jar:file:/home/me/projects/github/OpenRefine/fatjar/openrefinefat.jar!/webapp
Web Root URI: jar:file:/home/me/projects/github/OpenRefine/fatjar/openrefinefat.jar!/webapp
10:54:48.506 [ refine] Starting OpenRefine trunk [TRUNK]... (1090ms)
10:54:48.537 [..enrefinefat.jar!/webapp] unavailable (31ms)
java.lang.NullPointerException
at java.io.File.<init>(File.java:277)
at edu.mit.simile.butterfly.Butterfly.init(Butterfly.java:191)
at org.mortbay.jetty.servlet.ServletHolder.initServlet(ServletHolder.java:440)
at org.mortbay.jetty.servlet.ServletHolder.doStart(ServletHolder.java:263)
at com.google.refine.RefineServer.configure(Refine.java:328)
at com.google.refine.RefineServer.init(Refine.java:242)
at com.google.refine.Refine.init(Refine.java:117)
at com.google.refine.Refine.main(Refine.java:111)
10:54:48.543 [ org.mortbay.log] Nested in javax.servlet.ServletException: java.lang.NullPointerException: (6ms)
java.lang.NullPointerException
at java.io.File.<init>(File.java:277)
at edu.mit.simile.butterfly.Butterfly.init(Butterfly.java:191)
at org.mortbay.jetty.servlet.ServletHolder.initServlet(ServletHolder.java:440)
at org.mortbay.jetty.servlet.ServletHolder.doStart(ServletHolder.java:263)
at com.google.refine.RefineServer.configure(Refine.java:328)
at com.google.refine.RefineServer.init(Refine.java:242)
at com.google.refine.Refine.init(Refine.java:117)
at com.google.refine.Refine.main(Refine.java:11
Created new window in existing browser session.
You are missing the WebAppContext.setBaseResource(Resource) call.
This is how the WebAppContext, and the ServletContext finds its static resources, as well as any configuration resources that are specific to that context.
I have some code connecting to JMX and getting mBean by name. Now I'm writing tests with JUnit for it. I have already done some tests without authentication using something like this:
private static void startJmxServer() throws Exception {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
LocateRegistry.createRegistry(PORT);
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + HOST + ':' + PORT + "/jmxrmi");
JMXConnectorServer connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, null, mbs);
Example exampleMBean = new Example();
ObjectName exampleName = new ObjectName(MBEAN_NAME);
mbs.registerMBean(exampleMBean, exampleName);
connectorServer.start();
}
Now I want to do some test with authentication. So I need to specify next JVM properies:
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=1234
-Dcom.sun.management.jmxremote.authenticate=true
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.access.file=/somepath/jmxremote.access
-Dcom.sun.management.jmxremote.password.file=/somepath/jmxremote.password
I've already tried passing this properties in JMXConnectorServer environment variable. Also I've tried System.setProperty. But have failed, as connection was available without any credentials.
The only way, that makes it work is:
private static void startJmxServer() throws Exception {
String name = ManagementFactory.getRuntimeMXBean().getName();
VirtualMachine vm = VirtualMachine.attach(name.substring(0, name.indexOf('#')));
String lca = vm.getAgentProperties().getProperty("com.sun.management.jmxremote.localConnectorAddress");
if (lca == null) {
Path p = Paths.get(System.getProperty("java.home")).normalize();
if (!"jre".equals(p.getName(p.getNameCount() - 1).toString()
.toLowerCase())) {
p = p.resolve("jre");
}
File f = p.resolve("lib").resolve("management-agent.jar").toFile();
if (!f.exists()) {
throw new IOException("Management agent not found");
}
String options = String.format("com.sun.management.jmxremote.port=%d, " +
"com.sun.management.jmxremote.authenticate=true, " +
"com.sun.management.jmxremote.ssl=false, " +
"com.sun.management.jmxremote.access.file=/somepath/jmxremote.access, " +
"com.sun.management.jmxremote.password.file=/somepath/jmxremote.password", PORT);
vm.loadAgent(f.getCanonicalPath(), options);
}
vm.detach();
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
Example exampleMBean = new Example();
ObjectName exampleName = new ObjectName(MBEAN_NAME);
mbs.registerMBean(exampleMBean, exampleName);
}
But as agent was loaded I can not change VM properties to run test without authentication.Also I'm want to avoid such sort of thing, because of need in manual defining tools.jar and want to use common JMX tools. Any idea how to manage this?
Authentication configuration is passed in environment - the second argument to JMXConnectorServerFactory.newJMXConnectorServer.
HashMap<String, Object> env = new HashMap<>();
env.put("jmx.remote.x.password.file", "/somepath/jmxremote.password");
env.put("jmx.remote.x.access.file", "/somepath/jmxremote.access");
JMXConnectorServer connectorServer =
JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);
Note that the attribute names here differ from property names.
Consult ConnectorBootstrap.java from JDK sources to see how the default JMXConnectorServer is initialized.
I'm writting a Java (1.7) application to be running on Windows. The application is accessing additional services running on the same host and other ones running in the Internet. The application can be run in two environments where in one, proxy settings must be specified (there is proxy when accessing the Internet); while in the other environment, the proxy settings must not be specified (there is no proxy).
I want the application to be simple and don't want its users bother with specification of the proxy settings on cmd-line (-Dhttp.proxyHost, etc.) - the application should learn the proxy settings from Windows system settings (IE / Tools / Internet Properties / Connections / LAN Settings).
I have written a piece of code that is supposed to learn that settings, see below. The trouble is that this piece of code does not identify localhost, 127.0.0.1 and my-computer-name (where my-computer-name is the name of my computer) as URLs where proxy should be by-passed when being accessed (yes, I do have 'Bypass proxy server for local addresses' checked). As a result, the application tries to access local services through the proxy which is wrong.
So far I've found out that one way to teach JVM not to use proxy for 'local addresses' is to list the strings (localhost, 127.0.0.1, my-computer-name) in Proxy Settings / Exceptions (Do not use proxy server for addresses beginning with). Obviously, this is not a good solution as usually no one is listing these strings there (the first check-box is enough for non-Java applications).
Second (trivial) solution would be just to count with these strings in my piece of code and do not use proxy settings for them even when JVM thinks otherwise. I don't think this is a good solution and if this is the only solution, IMHO, there is a defect in JVM.
I've found many resources in the Internet how to learn System proxy settings. But how to learn the non-proxy settings?
Thanks,
PP
public static final String HTTP_PROXY_HOST_KEY = "http.proxyHost";
public static final String HTTPS_PROXY_HOST_KEY = "https.proxyHost";
public static final String HTTP_PROXY_PORT_KEY = "http.proxyPort";
public static final String HTTPS_PROXY_PORT_KEY = "https.proxyPort";
public static final String NO_PROXY_HOSTS_KEY = "http.nonProxyHosts";
// provide list of urls which are to be accessed by this application and return proxy and non-proxy settings
private Properties getSystemProxyConfiguration(String[] urls) {
log.debug("Getting system proxy");
Properties properties = new Properties();
SortedSet<String> nonProxyHosts = new TreeSet<>();
for (String url : urls) {
URI uri;
try {
uri = new URI(url);
} catch (URISyntaxException e) {
throw new IllegalStateException(e);
}
InetSocketAddress address = getSystemProxy(uri);
if (address != null) {
if (url.toLowerCase().startsWith("https")) {
properties.put(HTTPS_PROXY_HOST_KEY, address.getHostString());
properties.put(HTTPS_PROXY_PORT_KEY, ""+address.getPort());
//todo verify that all previous URLs in this array are using the same proxy
log.debug("HTTPS proxy: " + address.getHostString() + ":" + address.getPort());
} else {
properties.put(HTTP_PROXY_HOST_KEY, address.getHostString());
properties.put(HTTP_PROXY_PORT_KEY, ""+address.getPort());
//todo verify that all previous URLs in this array are using the same proxy
log.debug("HTTP proxy: " + address.getHostString() + ":" + address.getPort());
}
} else { //todo DEFECT -> this does not find the non-proxy hosts (even though specified in IE Internet settings)
nonProxyHosts.add(uri.getHost());
}
}
if (nonProxyHosts.size() > 0) {
String nonProxyHostsString = nonProxyHosts.first();
nonProxyHosts.remove(nonProxyHostsString);
for (String nonProxyHost : nonProxyHosts) {
nonProxyHostsString = nonProxyHostsString + "|" + nonProxyHost;
}
properties.put(NO_PROXY_HOSTS_KEY, nonProxyHostsString);
log.debug("Non HTTP(S) proxy hosts: "+nonProxyHostsString);
} else {
log.debug("No non HTTP(S) proxy hosts set");
}
return properties;
}
private InetSocketAddress getSystemProxy(URI uri) {
List<Proxy> proxyList;
proxyList = ProxySelector.getDefault().select(uri);
if (proxyList != null && proxyList.size() > 0) { //todo DEFECT - this never returns DIRECT proxy for localhost, 127.0.0.1, my-computer-name strings
Proxy proxy = proxyList.get(0);
if (proxyList.size() > 1) {
log.warn("There is more " + proxy.type() + " proxies available. Use "+PROXY_PROPERTIES_FILE_NAME+" to set the right one.");
}
InetSocketAddress address = (InetSocketAddress) proxy.address();
return address;
}
return null;
}
I need to connect a java aplication to an MBean server, but I'm having trouble getting JMXConnector to work.
I am getting a NoInitialContextException when I try to run this:
try
{
if(user != null)
{
HashMap environment = new HashMap();
environment.put(JMXConnector.CREDENTIALS, new String[] {user, password});
connector = JMXConnectorFactory.connect(location, environment);
}
else
{
connector = JMXConnectorFactory.connect(location, null);
}
beanServer = connector.getMBeanServerConnection();
}
catch(Exception e)
{
throw new ConnectException("Failed to connect to " + location + ": " + e.getMessage());
}
It happens when I use an username and password, although I cannot test without one because the test server I have has to be authenticated.
Edit: I am using java6 SE. No related jars added.
Any ideas on what I'm doing wrong here? Any help is much appreciated.
You didn't specify which server you are trying to connect, but here is an example for weblogic . I suspect, your JMXServiceURL is not correct, it changes based on Mbeanserver you are trying to connect.
As thinksteep said, you can try that:
Map<String, Object> env = new HashMap<String, Object>();
env.put(JMXConnectorServerFactory.PROTOCOL_PROVIDER_PACKAGES, "com.sun.jmx.remote.protocol");
And later you can connect with:
jmxc = JMXConnectorFactory.connect(new JMXServiceURL(address), env);