Commit 2219337d authored by Andre Freyssinet's avatar Andre Freyssinet

Adds authentication and filtering based on IP address.

parent 86cf4d06
Pipeline #3178 failed with stages
in 15 seconds
......@@ -47,7 +47,8 @@ import org.osgi.framework.BundleContext;
import fr.dyade.aaa.common.Debug;
public class Helper {
public static Logger logger = Debug.getLogger(Helper.class.getName());
private static final String BYTES_CLASS_NAME = byte[].class.getName();
public static final String BUNDLE_CF_PROP = "rest.jms.connectionFactory";
public static final String BUNDLE_JNDI_FACTORY_INITIAL_PROP = "rest.jndi.factory.initial";
......@@ -56,12 +57,16 @@ public class Helper {
public static final String BUNDLE_IDLE_TIMEOUT_PROP = "rest.idle.timeout";
public static final String BUNDLE_CLEANER_PERIOD_PROP = "rest.cleaner.period";
public static final String BUNDLE_JMS_USER = "rest.jms.user";
public static final String BUNDLE_JMS_PASS = "rest.jms.password";
public static final String BUNDLE_JMS_IP_ALLOWED = "rest.jms.ipallowed";
public static final int DFLT_CLEANER_PERIOD = 15;
public static Logger logger = Debug.getLogger(Helper.class.getName());
private static final AtomicLong counter = new AtomicLong(1);
// Singleton
private static Helper helper = null;
private InitialContext ictx;
private HashMap<String, RestClientContext> restClientCtxs;
private HashMap<String, SessionContext> sessionCtxs;
......@@ -69,6 +74,12 @@ public class Helper {
private long globalIdleTimeout = 0;
private Properties jndiProps;
private String restUser;
private String restPass;
private String IPAllowed;
private IPFilter ipfilter;
private Helper() {
restClientCtxs = new HashMap<String, RestClientContext>();
sessionCtxs = new HashMap<String, SessionContext>();
......@@ -80,7 +91,29 @@ public class Helper {
return helper;
}
/**
* @return the restJmxRoot
*/
public String getRestUser() {
return restUser;
}
/**
* @return the restJmxPass
*/
public String getRestPass() {
return restPass;
}
public void setGlobalProperties(BundleContext bundleContext) throws NamingException {
restUser = bundleContext.getProperty(BUNDLE_JMS_USER);
restPass = bundleContext.getProperty(BUNDLE_JMS_PASS);
IPAllowed = bundleContext.getProperty(BUNDLE_JMS_IP_ALLOWED);
if (logger.isLoggable(BasicLevel.INFO))
logger.log(BasicLevel.INFO, "IPFilter allowedList = " + IPAllowed);
ipfilter = new IPFilter(IPAllowed);
// set the connection factory
setConnectionFactoryName(bundleContext.getProperty(BUNDLE_CF_PROP));
......@@ -119,6 +152,30 @@ public class Helper {
}
}
/**
* @return the IPAllowed
*/
public String getIPAllowed() {
return IPAllowed;
}
/**
* Check if the addr is authorized (all local address is authorized).
*
* @param addr The ip address to check
* @return true if authorized
* @throws UnknownHostException
* @throws SocketException
*/
public boolean checkIPAddress(String addr) {
return ipfilter.checkIpAllowed(addr);
}
public boolean authenticationRequired() {
return restUser != null && !restUser.isEmpty() &&
restPass != null && !restPass.isEmpty();
}
/**
* @return the restClientCtxs
*/
......
/*
* JORAM: Java(TM) Open Reliable Asynchronous Messaging
* Copyright (C) 2018 ScalAgent Distributed Technologies
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*
* Initial developer(s): ScalAgent Distributed Technologies
* Contributor(s):
*/
package org.objectweb.joram.tools.rest.jms;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.StringTokenizer;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import fr.dyade.aaa.common.Debug;
/**
* Class allowing to check if an IP address is in a filter list (white or black list).
* Be careful the current implementation is IPv4 dependent and any IPv6 address is refused.
*/
public class IPFilter {
public static Logger logger = Debug.getLogger(IPFilter.class.getName());
class Authorized {
String addr;
int ip;
int mask;
public Authorized(String addr, int ip, int mask) {
this.addr = addr;
this.ip = ip;
this.mask = mask;
}
public String toString() {
return addr;
}
}
private ArrayList<Authorized> ipAlloweds = null;
/**
* Creates list of IP allowed list from declaration.
*/
public IPFilter(String IPAllowedList) {
if (IPAllowedList == null) return;
IPAllowedList = IPAllowedList.trim();
if (IPAllowedList.length() == 0) return;
ipAlloweds = new ArrayList<Authorized>();
StringTokenizer st = new StringTokenizer(IPAllowedList, ",");
while (st.hasMoreTokens()) {
String addr = st.nextToken().trim();
String[] parts = addr.split("/");
String ip = parts[0];
int prefix;
if (parts.length < 2) {
prefix = 0;
} else {
prefix = Integer.parseInt(parts[1]);
}
Inet4Address a = null;
try {
a = (Inet4Address) InetAddress.getByName(ip);
} catch (UnknownHostException e) { }
byte[] b = a.getAddress();
ipAlloweds.add(new Authorized(addr,
((b[0] & 0xFF) << 24) | ((b[1] & 0xFF) << 16) | ((b[2] & 0xFF) << 8) | ((b[3] & 0xFF) << 0),
~((1 << (32 - prefix)) - 1)));
}
}
/**
* Check if the addr is authorized (all local address is authorized).
*
* @param addr The ip address to check
* @return true if authorized
* @throws UnknownHostException
* @throws SocketException
*/
public boolean checkIpAllowed(String addr) {
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "IPFilter.checkIpAllowed address=" + addr + ", ipAlloweds=" + ipAlloweds);
if (ipAlloweds == null)
return true;
try {
Inet4Address a = (Inet4Address) InetAddress.getByName(addr);
if (a.isAnyLocalAddress() || a.isLoopbackAddress())
return true;
// Check if the address is defined on any interface
if(NetworkInterface.getByInetAddress(a) != null)
return true;
byte[] b = a.getAddress();
int ipInt = ((b[0] & 0xFF) << 24) | ((b[1] & 0xFF) << 16) | ((b[2] & 0xFF) << 8) | ((b[3] & 0xFF) << 0);
for (Authorized authorized : ipAlloweds) {
if ((authorized.ip & authorized.mask) == (ipInt & authorized.mask)) {
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "IPFilter.checkIpAllowed address " + addr + " authorized.");
return true;
}
}
} catch (Exception exc) {
logger.log(BasicLevel.WARN, "IPFilter.checkIpAllowed " + addr, exc);
}
if (logger.isLoggable(BasicLevel.INFO))
logger.log(BasicLevel.INFO, "IPFilter.checkIpAllowed " + addr + " failed.");
return false;
}
}
......@@ -22,8 +22,18 @@
*/
package org.objectweb.joram.tools.rest.jms;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import java.io.IOException;
import java.net.URI;
import java.util.Base64;
import java.util.List;
import java.util.StringTokenizer;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
......@@ -31,10 +41,22 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
@Path("/")
public class RootService {
import javax.servlet.http.HttpServletRequest;
import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;
import fr.dyade.aaa.common.Debug;
@Path("/")
public class RootService implements ContainerRequestFilter {
public static Logger logger = Debug.getLogger(RootService.class.getName());
private static final String AUTHORIZATION_PROPERTY = "Authorization";
private static final String AUTHENTICATION_SCHEME = "Basic";
private final Helper helper = Helper.getInstance();
@GET
@Produces(MediaType.TEXT_HTML)
public String info(@Context UriInfo uriInfo) {
......@@ -49,4 +71,80 @@ public class RootService {
"<br><a href=\""+contextURI+"\">"+contextURI+"</a>"+
"</body></html>";
}
@Context
private HttpServletRequest httpServletRequest;
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
if (!helper.authenticationRequired()) {
if (logger.isLoggable(BasicLevel.INFO))
logger.log(BasicLevel.INFO, "no authentication.");
return;
}
if (httpServletRequest != null) {
// JSR-315/JSR-339 compliant server
String remoteIpAddress = httpServletRequest.getRemoteAddr();
if (remoteIpAddress != null) {
if (! helper.checkIPAddress(remoteIpAddress)) {
Response response = Response.status(Response.Status.UNAUTHORIZED)
.header("WWW-Authenticate", "Basic realm=\"executives\"")
.entity("You cannot access this resource (IP not allowed)").build();
requestContext.abortWith(response);
return;
}
}
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "request from: " + remoteIpAddress);
}
// request headers
final MultivaluedMap<String, String> headers = requestContext.getHeaders();
// authorization header
final List<String> authorization = headers.get(AUTHORIZATION_PROPERTY);
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "authorization = " + authorization);
if (authorization == null) {
Response response = Response.status(Response.Status.UNAUTHORIZED)
.header("WWW-Authenticate", "Basic realm=\"executives\"")
.entity("You cannot access this resource").build();
requestContext.abortWith(response);
return;
}
// get encoded username and password
final String encodedUserPassword = authorization.get(0).replaceFirst(AUTHENTICATION_SCHEME + " ", "");
// decode username and password
String usernameAndPassword = new String(Base64.getDecoder().decode(encodedUserPassword));
final StringTokenizer tokenizer = new StringTokenizer(usernameAndPassword, ":");
String username = null;
String password = null;
if (tokenizer.hasMoreTokens()) {
username = tokenizer.nextToken();
}
if (tokenizer.hasMoreTokens()) {
password = tokenizer.nextToken();
}
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "username = " + username);
// Verifying username and password
if (helper.getRestUser().equals(username) && helper.getRestPass().equals(password)) {
// the valid authentication
return;
}
if (logger.isLoggable(BasicLevel.WARN))
logger.log(BasicLevel.WARN, "Bad authorization: " + username + ":" + password);
Response response = Response.status(Response.Status.UNAUTHORIZED)
.header("WWW-Authenticate", "Basic realm=\"executives\"")
.entity("You cannot access this resource").build();
requestContext.abortWith(response);
return;
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment