diff --git a/src/auth/AuthenticationChannelHandler.java b/src/auth/AuthenticationChannelHandler.java index 67e0d08f14..c1a9d36904 100644 --- a/src/auth/AuthenticationChannelHandler.java +++ b/src/auth/AuthenticationChannelHandler.java @@ -16,20 +16,73 @@ import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelUpstreamHandler; - +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.Arrays; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelEvent; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelStateEvent; +import org.jboss.netty.channel.ExceptionEvent; +import org.jboss.netty.channel.MessageEvent; +import org.jboss.netty.channel.SimpleChannelUpstreamHandler; /** * @since 2.3 */ public class AuthenticationChannelHandler extends SimpleChannelUpstreamHandler { + private static final Logger LOG = LoggerFactory.getLogger(AuthenticationChannelHandler.class); + private TSDB tsdb = null; private AuthenticationPlugin authentication = null; + public AuthenticationChannelHandler(String type, TSDB tsdb) { + LOG.debug("Setting up AuthenticationChannelHandler"); this.authentication = tsdb.getAuth(); } + + @Override + public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e) throws Exception { + if (e instanceof ChannelStateEvent) { + System.err.println(e); + } + super.handleUpstream(ctx, e); + } + + @Override + public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { + // Send greeting for a new connection. + e.getChannel().write("AUTHREQUIRED.\r\n"); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) { + e.getCause().printStackTrace(); + e.getChannel().close(); + } + @Override - public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { - if (authentication.authenticate(e)) { - ctx.getPipeline().remove(this); + public void messageReceived(ChannelHandlerContext ctx, MessageEvent msgevent) { + String response = "AUTHFAIL\r\n"; + try { + final Object message = msgevent.getMessage(); + if (message instanceof String[]) { + LOG.debug("Passing auth command to Authentication Plugin"); + if (authentication.authenticate((String[]) message)) { + LOG.info("Authentication Completed"); + response = "AUTHSUCESS.\r\n"; + ctx.getPipeline().remove(this); + } + } else { + LOG.debug("Unexpected message type " + + message.getClass() + ": " + message); + } + } catch (Exception e) { + LOG.debug("Unexpected exception caught" + + " while serving: " + e); + } finally { + ChannelFuture future = msgevent.getChannel().write(response); } } } diff --git a/src/auth/AuthenticationPlugin.java b/src/auth/AuthenticationPlugin.java index 9c9c45560d..73550fd403 100644 --- a/src/auth/AuthenticationPlugin.java +++ b/src/auth/AuthenticationPlugin.java @@ -13,13 +13,9 @@ package net.opentsdb.auth; import net.opentsdb.core.TSDB; -import net.opentsdb.meta.Annotation; -import net.opentsdb.meta.TSMeta; -import net.opentsdb.meta.UIDMeta; import net.opentsdb.stats.StatsCollector; - import com.stumbleupon.async.Deferred; -import org.jboss.netty.channel.MessageEvent; +import java.util.Map; /** * @since 2.3 @@ -64,8 +60,7 @@ public abstract class AuthenticationPlugin { * @param collector The collector used for emitting statistics */ public abstract void collectStats(final StatsCollector collector); - - public abstract Boolean authenticate(MessageEvent e); - public abstract Boolean authenticate(String digest); - public abstract Boolean authenticate(String username, String password); + public abstract Boolean authenticate(String[] command); + public abstract Boolean authenticate(String access_key, String access_secret_key); + public abstract Boolean authenticate(String access_key, Map fields); } diff --git a/src/auth/EmbeddedAuthenticationPlugin.java b/src/auth/EmbeddedAuthenticationPlugin.java index 03c1063f37..ebef8862f4 100644 --- a/src/auth/EmbeddedAuthenticationPlugin.java +++ b/src/auth/EmbeddedAuthenticationPlugin.java @@ -16,20 +16,78 @@ import com.stumbleupon.async.Deferred; import net.opentsdb.core.TSDB; import net.opentsdb.stats.StatsCollector; -import org.jboss.netty.channel.MessageEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.UnsupportedEncodingException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; + /** * @since 2.3 */ public class EmbeddedAuthenticationPlugin extends AuthenticationPlugin { private static final Logger LOG = LoggerFactory.getLogger(EmbeddedAuthenticationPlugin.class); private TSDB tsdb = null; + private Map authDB = new HashMap(); + + private static String algo = "HmacSHA256"; + + private static String hmacDigest(String access_key, String access_key_secret) { + LOG.trace("Generating HASH for " + access_key + " " + access_key_secret); + LOG.debug("Generating HASH for " + access_key); + String digest = null; + try { + SecretKeySpec key = new SecretKeySpec((access_key_secret).getBytes("UTF-8"), algo); + Mac mac = Mac.getInstance(algo); + mac.init(key); + + byte[] bytes = mac.doFinal(access_key.getBytes("ASCII")); + + StringBuffer hash = new StringBuffer(); + for (int i = 0; i < bytes.length; i++) { + String hex = Integer.toHexString(0xFF & bytes[i]); + if (hex.length() == 1) { + hash.append('0'); + } + hash.append(hex); + } + digest = hash.toString(); + } catch (UnsupportedEncodingException e) { + LOG.debug("UnsupportedEncodingException: " + e); + } catch (InvalidKeyException e) { + LOG.debug("InvalidKeyException: " + e); + } catch (NoSuchAlgorithmException e) { + LOG.debug("NoSuchAlgorithmException: " + e); + } + return digest; + } + + private static String createDigest(String accessKey, String accessKeySecret, Map fields) { + String digest = hmacDigest(accessKey, accessKeySecret + (String) fields.get("date") + (String) fields.get("noonce")); + LOG.trace("got hash: " + digest); + return digest; + } + + private Map createFields(final String input) { + final Map map = new HashMap(); + for (String pair : input.split("&")) { + String[] kv = pair.split("="); + map.put(kv[0], kv[1]); + } + return map; + } @Override public void initialize(TSDB tsdb) { LOG.debug("initialized authentication plugin"); + String correctUsername = tsdb.getConfig().getString("tsd.authentication.username"); + String correctSecret = tsdb.getConfig().getString("tsd.authentication.secret"); + authDB.put(correctUsername, correctSecret); this.tsdb = tsdb; } @@ -49,23 +107,63 @@ public void collectStats(StatsCollector collector) { } @Override - public Boolean authenticate(MessageEvent e) { - String username = tsdb.getConfig().getString("tsd.authentication.username"); - String secret = tsdb.getConfig().getString("tsd.authentication.secret"); - LOG.debug(e.getMessage().toString()); - LOG.debug(username + ":" + secret); - return authenticate(username, secret); + public Boolean authenticate(String[] command) { + Boolean ret = false; + // Command should be 'auth basic access_key access_secret' + if (command.length < 3 || command.length > 4) { + LOG.error("Invalid Authentication Command Length: " + Integer.toString(command.length)); + } else if (command[0].equals("auth")) { + if (command[1].equals(algo.trim().toLowerCase())) { + LOG.debug("Validating Digest"); + Map fields = createFields(command[3]); + ret = authenticate(command[2], fields); + } else if (command[1].equals("basic")) { + LOG.debug("Validating Credentials"); + ret = authenticate(command[2], command[3]); + } else { + LOG.error("Command not understood: " + command[0] + " " + command[1]); + } + } else { + LOG.error("Command is not auth: " + command[0]); + } + return ret; } @Override - public Boolean authenticate(String digest) { - return true; + public Boolean authenticate(String accessKey, Map fields) { + try { + String providedDigest = (String) fields.get("digest"); + LOG.debug("Authenticating " + accessKey + " " + providedDigest); + String date = (String) fields.get("date"); + String noonce = (String) fields.get("noonce"); + String secretKey = (String) authDB.get(accessKey); + String fullSecretKey = secretKey + date + noonce; + String calculatedDigest = hmacDigest(accessKey, fullSecretKey); + LOG.debug("Calc: " + calculatedDigest); + LOG.debug("Prov: " + providedDigest); + if (calculatedDigest.equals(providedDigest)) { + LOG.debug("Authentication Succeeded for: " + accessKey); + return true; + } else { + LOG.debug("Authentication Failed for: " + accessKey); + return false; + } + } catch (Exception e) { + LOG.error("Exception: " + e); + return false; + } } @Override public Boolean authenticate(String providedUsername, String providedSecret) { - String correctUsername = tsdb.getConfig().getString("tsd.authentication.username"); - String correctSecret = tsdb.getConfig().getString("tsd.authentication.secret"); - return (correctUsername == providedUsername && correctSecret == providedSecret); + String correctSecret = (String) authDB.get(providedUsername); + Boolean passwordMatched = correctSecret.equals(providedSecret); + if (passwordMatched) { + LOG.debug("Authentication Succeeded for: " + providedUsername); + return true; + } else { + LOG.debug("Authentication Failed for: " + providedUsername); + return false; + } } } diff --git a/src/logback.xml b/src/logback.xml index ff97a50889..c053292653 100644 --- a/src/logback.xml +++ b/src/logback.xml @@ -63,9 +63,10 @@ - + + - + diff --git a/src/opentsdb.conf b/src/opentsdb.conf index 11d2a911df..c5eaf9cb85 100644 --- a/src/opentsdb.conf +++ b/src/opentsdb.conf @@ -71,3 +71,9 @@ tsd.http.cachedir = # Compaction flush speed multiplier, default 2 # tsd.storage.compaction.flush_speed = 2 + +# --------- PLUGINS --------------------------------- +# Authentication Plugin +tsd.authentication.enable =true +tsd.authentication.plugin = net.opentsdb.auth.EmbeddedAuthenticationPlugin +tsd.startup.enable =false