Compare commits

...
Author SHA1 Message Date
Bill Oley 3b93f4c743 Add age off iterator for meta table 2019-05-10 16:19:12 -04:00
Bill Oley 3d7d6d0efd Correct typos 2019-04-19 09:06:06 -04:00
Bill Oley dcd23d4fc8 fixes #181 Use netty-tcnative-boringssl-static 2019-04-18 11:56:54 -04:00
Bill Oley cc7326fc19 Tag version 1.2.2 2019-04-12 11:24:53 -04:00
Bill Oley 0d2b6e61c8 Correct typos 2019-04-12 10:55:04 -04:00
Bill Oley b7cd8765c5 Correct typos and minor changed to MetaCache 2019-04-10 15:31:53 -04:00
Bill Oley a9d27d5b1b re #179 Allow AuthCache to periodically refresh credentials, cache meta table up to a configurable number of tags 2019-04-08 10:50:14 -04:00
Bill Oley 7d67eb218d Update netty-tcnative dependency, remove x509 principal extractor from AuthenticationService 2019-04-03 15:52:19 -04:00
Bill Oley 5fb7e3dd2b Bump pom versions to 1.2.2-SNAPSHOT 2019-04-02 12:57:22 -04:00
Bill Oley 5a89861cec Tag version 1.2.1 2019-04-02 12:53:04 -04:00
Bill Oley a3bc9a01ee Update dependency versions
Update dependencies

Update dependency versions
2019-04-02 12:43:36 -04:00
Bill Oley dba494b06d Bump pom versions to 1.2.1-SNAPSHOT 2019-02-25 15:41:31 -05:00
Bill Oley 5826884c3c Tag version 1.2.0 2019-02-25 15:11:57 -05:00
Bill Oley f94c731bbc Remove unused modeule directory introduced in error 2019-02-25 15:11:12 -05:00
Bill Oley 4e042bcb94 Allow all http methods in grafana auth proxy, disable cookies in HttpPools 2019-02-22 16:17:36 -05:00
Bill Oley ab7f499134 closes #180 Allow a configurable default visibility for metrics 2019-02-21 15:33:25 -05:00
Bill Oley c098f9f3e4 re #179 Create an authentication app for grafana that uses Timely's authentication framework and grafana's auth-proxy
re #179 Create authentication proxy for grafana
2019-02-21 13:28:12 -05:00
Bill Oley 94cfcbc0c4 Allow authentication provider to return roles, check users against requiredRoles and requiredAuths for access
index on master: 674e1b0 Improve synchronization in AuthenticationService

Check validated users against requiredRoles and requiredAuths

Conflicts:
	server/conf/security.xml
	server/src/main/java/timely/auth/AuthenticationService.java

More work
2019-02-15 11:24:07 -05:00
Bill Oley 674e1b0ab5 Improve synchronization in AuthenticationService 2019-02-08 16:53:35 -05:00
Bill Oley 4eedb84547 Add synchronization to AuthenticationService to prevent simultaneous authentications of the same user by multiple threads 2019-02-07 17:17:08 -05:00
Bill Oley bef86eac37 Fix websocket proxying with a client certificate, add ability to configure client cert in python websocket client 2019-02-07 17:16:00 -05:00
Bill Oley 27bb16e403 re #178 Allow proxying of http and ws requests
More work

Correct errors
2019-02-05 11:41:35 -05:00
Bill Oley a95d2d7ea1 Bump pom versions to 1.1.1-SNAPSHOT 2019-01-16 13:55:47 -05:00
Bill Oley f751175190 Update version of collectd 2019-01-16 13:53:43 -05:00
Bill Oley 648199f330 Revert "Bump pom versions to 1.1.1-SNAPSHOT"
This reverts commit fcfc3f7bc0.
2019-01-16 13:53:26 -05:00
Bill Oley fcfc3f7bc0 Bump pom versions to 1.1.1-SNAPSHOT 2019-01-15 15:48:43 -05:00
Bill Oley 200bc39902 Tag version 1.1.0
Grafana app changes
2019-01-15 15:47:14 -05:00
Bill Oley f854c9e394 Add build profile for python client package 2019-01-15 15:08:30 -05:00
Bill Oley 73ddae1f8d Correct typo 2019-01-15 14:15:20 -05:00
Bill Oley cb7184503e Allow Websocket requests to take advantage of cached metrics 2019-01-11 16:12:26 -05:00
Christopher Tubbs 3a357b50ef Update jackson-databind dependency
(Also, remove redundant test dependency on timely-common.)
2019-01-10 16:07:25 -05:00
Bill Oley 689aee0678 Fix deadlock in tracking nonCachedMetrics across processes 2019-01-08 12:06:41 -05:00
Bill Oley 32f6b9d4c9 Include the bin/testing directory in assembly 2019-01-04 11:14:11 -05:00
Bill Oley 512f318387 Add distributed tracking of nonCachedMetrics between the timely servers and the balancers
work in progress

Add distributed tracking of noncachedMetrics
2019-01-04 11:04:24 -05:00
137 changed files with 5665 additions and 990 deletions

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>timely</groupId> <groupId>timely</groupId>
<artifactId>timely</artifactId> <artifactId>timely</artifactId>
<version>1.0.1-SNAPSHOT</version> <version>1.2.3</version>
</parent> </parent>
<artifactId>timely-analytics</artifactId> <artifactId>timely-analytics</artifactId>
<!-- <!--
@@ -66,7 +66,7 @@ public class SubscriptionSource extends RichSourceFunction<MetricResponse> imple
LOG.info("Opening summarization job."); LOG.info("Opening summarization job.");
super.open(parameters); super.open(parameters);
client = new WebSocketSubscriptionClient(jp.getTimelyHostname(), jp.getTimelyHttpsPort(), jp.getTimelyWssPort(), client = new WebSocketSubscriptionClient(jp.getTimelyHostname(), jp.getTimelyHttpsPort(), jp.getTimelyWssPort(),
jp.isDoLogin(), jp.getTimelyUsername(), jp.getTimelyPassword(), jp.getKeyStoreFile(), jp.isClientAuth(), jp.isDoLogin(), jp.getTimelyUsername(), jp.getTimelyPassword(), jp.getKeyStoreFile(),
jp.getKeyStoreType(), jp.getKeyStorePass(), jp.getTrustStoreFile(), jp.getTrustStoreType(), jp.getKeyStoreType(), jp.getKeyStorePass(), jp.getTrustStoreFile(), jp.getTrustStoreType(),
jp.getTrustStorePass(), jp.isHostVerificationEnabled(), jp.getBufferSize()); jp.getTrustStorePass(), jp.isHostVerificationEnabled(), jp.getBufferSize());
} }
@@ -96,7 +96,7 @@ public class SubscriptionSource extends RichSourceFunction<MetricResponse> imple
@Override @Override
public void onOpen(Session session, EndpointConfig config) { public void onOpen(Session session, EndpointConfig config) {
session.addMessageHandler(new MessageHandler.Whole<String>() { session.addMessageHandler(String.class, new MessageHandler.Whole<String>() {
private final SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd-HH"); private final SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd-HH");
private long lastWatermarkTime = 0L; private long lastWatermarkTime = 0L;
@@ -18,6 +18,7 @@ public class SummarizationJobParameters extends GlobalJobParameters implements S
private final int timelyTcpPort; private final int timelyTcpPort;
private final int timelyHttpsPort; private final int timelyHttpsPort;
private final int timelyWssPort; private final int timelyWssPort;
private final boolean clientAuth;
private final boolean doLogin; private final boolean doLogin;
private final String timelyUsername; private final String timelyUsername;
private final String timelyPassword; private final String timelyPassword;
@@ -40,6 +41,7 @@ public class SummarizationJobParameters extends GlobalJobParameters implements S
timelyTcpPort = params.getInt("timelyTcpPort", 4241); timelyTcpPort = params.getInt("timelyTcpPort", 4241);
timelyHttpsPort = params.getInt("timelyHttpsPort", 4242); timelyHttpsPort = params.getInt("timelyHttpsPort", 4242);
timelyWssPort = params.getInt("timelyWssPort", 4243); timelyWssPort = params.getInt("timelyWssPort", 4243);
clientAuth = params.getBoolean("clientAuth", false);
doLogin = params.getBoolean("doLogin", false); doLogin = params.getBoolean("doLogin", false);
timelyUsername = params.get("timelyUsername", null); timelyUsername = params.get("timelyUsername", null);
timelyPassword = params.get("timelyPassword", null); timelyPassword = params.get("timelyPassword", null);
@@ -69,6 +71,7 @@ public class SummarizationJobParameters extends GlobalJobParameters implements S
timelyTcpPort = conf.getInteger("timelyTcpPort", 4241); timelyTcpPort = conf.getInteger("timelyTcpPort", 4241);
timelyHttpsPort = conf.getInteger("timelyHttpsPort", 4242); timelyHttpsPort = conf.getInteger("timelyHttpsPort", 4242);
timelyWssPort = conf.getInteger("timelyWssPort", 4243); timelyWssPort = conf.getInteger("timelyWssPort", 4243);
clientAuth = conf.getBoolean("clientAuth", false);
doLogin = conf.getBoolean("doLogin", false); doLogin = conf.getBoolean("doLogin", false);
timelyUsername = conf.getString("timelyUsername", null); timelyUsername = conf.getString("timelyUsername", null);
timelyPassword = conf.getString("timelyPassword", null); timelyPassword = conf.getString("timelyPassword", null);
@@ -112,6 +115,10 @@ public class SummarizationJobParameters extends GlobalJobParameters implements S
return timelyWssPort; return timelyWssPort;
} }
public boolean isClientAuth() {
return clientAuth;
}
public boolean isDoLogin() { public boolean isDoLogin() {
return doLogin; return doLogin;
} }
+6 -10
View File
@@ -1,4 +1,4 @@
#!/bin/bash -vx #!/bin/bash
if [[ `uname` == "Darwin" ]]; then if [[ `uname` == "Darwin" ]]; then
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0` THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
@@ -8,11 +8,6 @@ else
TCNATIVE_SUFFIX="so" TCNATIVE_SUFFIX="so"
fi fi
# netty tcnative file reference
#netty-tcnative-1.1.33.Fork18-linux-x86_64-fedora.jar -> libnetty-tcnative.so
#netty-tcnative-1.1.33.Fork18-linux-x86_64.jar -> libnetty-tcnative.so
#netty-tcnative-1.1.33.Fork18-osx-x86_64.jar -> libnetty-tcnative.jnilib
THIS_DIR="${THIS_SCRIPT%/*}" THIS_DIR="${THIS_SCRIPT%/*}"
NATIVE_DIR="${THIS_DIR}/META-INF/native" NATIVE_DIR="${THIS_DIR}/META-INF/native"
BASE_DIR=${THIS_DIR}/.. BASE_DIR=${THIS_DIR}/..
@@ -32,14 +27,15 @@ fi
mkdir -p ${NATIVE_DIR} mkdir -p ${NATIVE_DIR}
pushd ${BASE_DIR}/bin pushd ${BASE_DIR}/bin
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative*.jar META-INF/native/libnetty_tcnative.${NATIVE_SUFFIX} $JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative-boringssl-static*.jar META-INF/native/libnetty_tcnative_linux_x86_64.${TCNATIVE_SUFFIX}
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-transport-native-epoll*.jar META-INF/native/libnetty_transport_native_epoll_x86_64.${NATIVE_SUFFIX} $JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-all*.jar META-INF/native/libnetty_transport_native_epoll_x86_64.${TCNATIVE_SUFFIX}
popd popd
export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*" export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*:${HADOOP_CONF_DIR}"
JVM_ARGS="-Xmx1G -Xms1G -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS}" JVM_ARGS="-Xmx1G -Xms1G -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS} -Dlog4j.configurationFile=${CONF_DIR}/log4j2-spring.xml"
JVM_ARGS="${JVM_ARGS} -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector" JVM_ARGS="${JVM_ARGS} -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
JVM_ARGS="${JVM_ARGS} -Djava.library.path=${NATIVE_DIR}" JVM_ARGS="${JVM_ARGS} -Djava.library.path=${NATIVE_DIR}"
#JVM_ARGS="${JVM_ARGS} -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005" #JVM_ARGS="${JVM_ARGS} -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"
echo "$JAVA_HOME/bin/java ${JVM_ARGS} timely.balancer.Balancer --spring.config.name=timely --spring.profiles.active=balancer" echo "$JAVA_HOME/bin/java ${JVM_ARGS} timely.balancer.Balancer --spring.config.name=timely --spring.profiles.active=balancer"
+3
View File
@@ -22,5 +22,8 @@
<AppenderRef ref="Console" level="warn" /> <AppenderRef ref="Console" level="warn" />
<AppenderRef ref="Rolling" level="trace" /> <AppenderRef ref="Rolling" level="trace" />
</Root> </Root>
<Logger name="timely" additivity="false">
<AppenderRef ref="Console" level="debug" />
</Logger>
</Loggers> </Loggers>
</Configuration> </Configuration>
+36 -14
View File
@@ -13,19 +13,31 @@
<beans:property name="subjectDnRegex" value="CN=(.*?)(?:,|$)"/> <beans:property name="subjectDnRegex" value="CN=(.*?)(?:,|$)"/>
</beans:bean> </beans:bean>
<beans:bean id="timelyUserDetailsService" class="timely.auth.UserDetailsService"> <beans:bean id="timelyUserDetailsService" class="timely.auth.UserDetailsService">
<beans:property name="users"> <beans:property name="users">
<util:map key-type="java.lang.String"> <util:list>
<beans:entry key="example.com"> <beans:bean class="timely.auth.TimelyUser">
<util:list> <beans:constructor-arg name="subjectDn">
<beans:value>D</beans:value> <beans:value>CN=example.com</beans:value>
<beans:value>E</beans:value> </beans:constructor-arg>
<beans:value>F</beans:value> <beans:constructor-arg name="auths">
</util:list> <util:list>
</beans:entry> <beans:value>D</beans:value>
</util:map> <beans:value>E</beans:value>
</beans:property> <beans:value>F</beans:value>
</beans:bean> </util:list>
</beans:constructor-arg>
<beans:constructor-arg name="roles">
<util:list>
<beans:value>G</beans:value>
<beans:value>H</beans:value>
<beans:value>I</beans:value>
</util:list>
</beans:constructor-arg>
</beans:bean>
</util:list>
</beans:property>
</beans:bean>
<beans:bean id="x509AuthorizationProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider"> <beans:bean id="x509AuthorizationProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<beans:property name="preAuthenticatedUserDetailsService" ref="timelyUserDetailsService" /> <beans:property name="preAuthenticatedUserDetailsService" ref="timelyUserDetailsService" />
@@ -35,8 +47,11 @@
<user name="test" password="test1" authorities="A,B,C" /> <user name="test" password="test1" authorities="A,B,C" />
</user-service> </user-service>
<beans:bean id="UserNamePasswordAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> <beans:bean id="noOpPasswordEncoder" class="org.springframework.security.crypto.password.NoOpPasswordEncoder"/>
<beans:bean id="UserNamePasswordAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="usernamePasswordService"/> <beans:property name="userDetailsService" ref="usernamePasswordService"/>
<beans:property name="passwordEncoder" ref="noOpPasswordEncoder"/>
</beans:bean> </beans:bean>
<authentication-manager alias="authenticationManager"> <authentication-manager alias="authenticationManager">
@@ -46,4 +61,11 @@
<authentication-provider ref="UserNamePasswordAuthenticationProvider" /> <authentication-provider ref="UserNamePasswordAuthenticationProvider" />
</authentication-manager> </authentication-manager>
<util:list id="requiredRoles" value-type="java.lang.String">
<beans:value></beans:value>
</util:list>
<util:list id="requiredAuths" value-type="java.lang.String">
<beans:value></beans:value>
</util:list>
</beans:beans> </beans:beans>
+7 -4
View File
@@ -8,12 +8,15 @@ timely-balancer:
assignmentFile: /timely/balancer/assignments.csv assignmentFile: /timely/balancer/assignments.csv
security: security:
session-max-age: 86400 session-max-age: 86400
allow-anonymous-access: true cache-expiration-minutes: 60
cache-refresh-minutes: 5
allow-anonymous-http-access: true
allow-anonymous-ws-access: true
serverSsl: serverSsl:
certificate-file: certificate-file:
key-file: key-file:
#key-password: #key-password:
use-generated-keypair: false use-generated-keypair: true
trust-store-file: trust-store-file:
use-openssl: true use-openssl: true
#use-ciphers: #use-ciphers:
@@ -31,7 +34,7 @@ timely-balancer:
nonCachedMetrics: nonCachedMetrics:
server: server:
ip: 127.0.0.1 ip: 127.0.0.1
tcp-port: 4242 tcpPort: 4242
numTcpPools: 1 numTcpPools: 1
tcpBufferSize: 100 tcpBufferSize: 100
tcpClientPool: tcpClientPool:
@@ -51,7 +54,7 @@ timely-balancer:
jmxEnabled: true jmxEnabled: true
testOnBorrow: false testOnBorrow: false
testOnReturn: false testOnReturn: false
udp-port: 4245 udpPort: 4245
udpClientPool: udpClientPool:
minIdlePerKey: 0 minIdlePerKey: 0
maxIdlePerKey: 40 maxIdlePerKey: 40
+3 -43
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>timely</groupId> <groupId>timely</groupId>
<artifactId>timely</artifactId> <artifactId>timely</artifactId>
<version>1.0.1-SNAPSHOT</version> <version>1.2.3</version>
</parent> </parent>
<artifactId>timely-balancer</artifactId> <artifactId>timely-balancer</artifactId>
<dependencies> <dependencies>
@@ -161,9 +161,8 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.netty</groupId> <groupId>io.netty</groupId>
<artifactId>netty-tcnative</artifactId> <artifactId>netty-tcnative-boringssl-static</artifactId>
<version>${version.netty-tcnative}</version> <version>${version.netty-tcnative}</version>
<classifier>${os.detected.classifier}</classifier>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -201,11 +200,6 @@
<artifactId>spring-boot-test-autoconfigure</artifactId> <artifactId>spring-boot-test-autoconfigure</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>timely-common</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
@@ -252,9 +246,8 @@
<artifactItems> <artifactItems>
<artifactItem> <artifactItem>
<groupId>io.netty</groupId> <groupId>io.netty</groupId>
<artifactId>netty-tcnative</artifactId> <artifactId>netty-tcnative-boringssl-static</artifactId>
<version>${version.netty-tcnative}</version> <version>${version.netty-tcnative}</version>
<classifier>${os.detected.classifier}</classifier>
<type>jar</type> <type>jar</type>
<overWrite>false</overWrite> <overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/native</outputDirectory> <outputDirectory>${project.build.directory}/native</outputDirectory>
@@ -296,37 +289,4 @@
</extension> </extension>
</extensions> </extensions>
</build> </build>
<profiles>
<profile>
<id>native-epoll-linux</id>
<activation>
<os>
<family>!mac</family>
</os>
</activation>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<version>${version.netty}</version>
<classifier>linux-x86_64</classifier>
</dependency>
</dependencies>
</profile>
<profile>
<id>native-epoll</id>
<activation>
<os>
<family>mac</family>
</os>
</activation>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<version>${version.netty}</version>
</dependency>
</dependencies>
</profile>
</profiles>
</project> </project>
+1 -1
View File
@@ -33,7 +33,7 @@
<directory>${project.basedir}/bin</directory> <directory>${project.basedir}/bin</directory>
<outputDirectory>bin</outputDirectory> <outputDirectory>bin</outputDirectory>
<includes> <includes>
<include>*</include> <include>**/*</include>
</includes> </includes>
</fileSet> </fileSet>
<fileSet> <fileSet>
@@ -1,5 +1,9 @@
package timely.balancer; package timely.balancer;
import static timely.Server.SERVICE_DISCOVERY_PATH;
import static timely.store.cache.DataStoreCache.NON_CACHED_METRICS;
import static timely.store.cache.DataStoreCache.NON_CACHED_METRICS_LOCK_PATH;
import java.io.File; import java.io.File;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@@ -52,10 +56,18 @@ import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler; import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.NettyRuntime; import io.netty.util.NettyRuntime;
import io.netty.util.internal.SystemPropertyUtil; import io.netty.util.internal.SystemPropertyUtil;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryForever;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import timely.auth.AuthCache;
import timely.balancer.configuration.BalancerConfiguration; import timely.balancer.configuration.BalancerConfiguration;
import timely.balancer.configuration.ClientSsl; import timely.balancer.configuration.ClientSsl;
import timely.balancer.configuration.SpringBootstrap; import timely.balancer.configuration.SpringBootstrap;
@@ -81,7 +93,7 @@ import timely.netty.tcp.TcpDecoder;
import timely.netty.tcp.TcpVersionHandler; import timely.netty.tcp.TcpVersionHandler;
import timely.netty.udp.UdpDecoder; import timely.netty.udp.UdpDecoder;
import timely.netty.udp.UdpPacketToByteBuf; import timely.netty.udp.UdpPacketToByteBuf;
import timely.netty.websocket.WebSocketHttpCookieHandler; import timely.netty.websocket.WebSocketFullRequestHandler;
import timely.netty.websocket.WebSocketRequestDecoder; import timely.netty.websocket.WebSocketRequestDecoder;
import timely.netty.websocket.subscription.WSTimelyExceptionHandler; import timely.netty.websocket.subscription.WSTimelyExceptionHandler;
@@ -115,9 +127,13 @@ public class Balancer {
protected Channel httpChannelHandle = null; protected Channel httpChannelHandle = null;
protected Channel wsChannelHandle = null; protected Channel wsChannelHandle = null;
protected List<Channel> udpChannelHandleList = new ArrayList<>(); protected List<Channel> udpChannelHandleList = new ArrayList<>();
private CuratorFramework curatorFramework;
protected volatile boolean shutdown = false; protected volatile boolean shutdown = false;
private static final int DEFAULT_EVENT_LOOP_THREADS; private static final int DEFAULT_EVENT_LOOP_THREADS;
private String[] zkPaths = new String[] { LEADER_LATCH_PATH, ASSIGNMENTS_LAST_UPDATED_PATH, ASSIGNMENTS_LOCK_PATH,
SERVICE_DISCOVERY_PATH, NON_CACHED_METRICS, NON_CACHED_METRICS_LOCK_PATH };
static { static {
DEFAULT_EVENT_LOOP_THREADS = Math.max(1, DEFAULT_EVENT_LOOP_THREADS = Math.max(1,
SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2)); SystemPropertyUtil.getInt("io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));
@@ -128,7 +144,7 @@ public class Balancer {
} }
protected static ConfigurableApplicationContext initializeConfiguration(String[] args) { protected static ConfigurableApplicationContext initializeConfiguration(String[] args) {
return new SpringApplicationBuilder(SpringBootstrap.class).web(false).run(args); return new SpringApplicationBuilder(SpringBootstrap.class).web(WebApplicationType.NONE).run(args);
} }
private void shutdownHook() { private void shutdownHook() {
@@ -239,6 +255,10 @@ public class Balancer {
LOG.info("Closing applicationContext"); LOG.info("Closing applicationContext");
applicationContext.close(); applicationContext.close();
} }
if (curatorFramework != null) {
curatorFramework.close();
}
this.shutdown = true; this.shutdown = true;
LOG.info("Server shut down."); LOG.info("Server shut down.");
} }
@@ -320,8 +340,31 @@ public class Balancer {
ssl.getKeyFile(), ssl.getKeyType(), ssl.getKeyPassword()); ssl.getKeyFile(), ssl.getKeyType(), ssl.getKeyPassword());
} }
private void ensureZkPaths(CuratorFramework curatorFramework, String[] paths) {
for (String s : paths) {
try {
Stat stat = curatorFramework.checkExists().forPath(s);
if (stat == null) {
curatorFramework.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT)
.forPath(s);
}
} catch (Exception e) {
LOG.info(e.getMessage());
}
}
}
public void run() throws Exception { public void run() throws Exception {
// start curator framework
RetryPolicy retryPolicy = new RetryForever(1000);
curatorFramework = CuratorFrameworkFactory.newClient(balancerConfig.getZooKeeper().getServers(), 30000, 1000,
retryPolicy);
curatorFramework.start();
ensureZkPaths(curatorFramework, zkPaths);
AuthCache.configure(balancerConfig.getSecurity());
final boolean useEpoll = useEpoll(); final boolean useEpoll = useEpoll();
Class<? extends ServerSocketChannel> channelClass; Class<? extends ServerSocketChannel> channelClass;
Class<? extends Channel> datagramChannelClass; Class<? extends Channel> datagramChannelClass;
@@ -355,7 +398,7 @@ public class Balancer {
tcpClientPools.add(new TcpClientPool(this.balancerConfig)); tcpClientPools.add(new TcpClientPool(this.balancerConfig));
} }
HealthChecker healthChecker = new HealthChecker(this.balancerConfig, tcpClientPools.get(0)); HealthChecker healthChecker = new HealthChecker(this.balancerConfig, tcpClientPools.get(0));
this.metricResolver = new BalancedMetricResolver(this.balancerConfig, healthChecker); this.metricResolver = new BalancedMetricResolver(curatorFramework, this.balancerConfig, healthChecker);
final ServerBootstrap tcpServer = new ServerBootstrap(); final ServerBootstrap tcpServer = new ServerBootstrap();
tcpServer.group(tcpBossGroup, tcpWorkerGroup); tcpServer.group(tcpBossGroup, tcpWorkerGroup);
@@ -387,7 +430,8 @@ public class Balancer {
httpServer.group(httpBossGroup, httpWorkerGroup); httpServer.group(httpBossGroup, httpWorkerGroup);
httpServer.channel(channelClass); httpServer.channel(channelClass);
httpServer.handler(new LoggingHandler()); httpServer.handler(new LoggingHandler());
HttpClientPool httpClientPool = new HttpClientPool(balancerConfig, clientSSLContext); HttpClientPool httpClientPool = new HttpClientPool(balancerConfig.getSecurity(), balancerConfig.getHttp(),
clientSSLContext);
httpServer.childHandler(setupHttpChannel(balancerConfig, sslCtx, metricResolver, httpClientPool)); httpServer.childHandler(setupHttpChannel(balancerConfig, sslCtx, metricResolver, httpClientPool));
httpServer.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); httpServer.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
httpServer.option(ChannelOption.SO_BACKLOG, 128); httpServer.option(ChannelOption.SO_BACKLOG, 128);
@@ -467,15 +511,14 @@ public class Balancer {
ch.pipeline().addLast("decoder", new HttpRequestDecoder()); ch.pipeline().addLast("decoder", new HttpRequestDecoder());
ch.pipeline().addLast("compressor", new HttpContentCompressor()); ch.pipeline().addLast("compressor", new HttpContentCompressor());
ch.pipeline().addLast("decompressor", new HttpContentDecompressor()); ch.pipeline().addLast("decompressor", new HttpContentDecompressor());
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(8192)); ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
ch.pipeline().addLast("chunker", new ChunkedWriteHandler()); ch.pipeline().addLast("chunker", new ChunkedWriteHandler());
ch.pipeline().addLast("queryDecoder", new timely.netty.http.HttpRequestDecoder( ch.pipeline().addLast("queryDecoder", new timely.netty.http.HttpRequestDecoder(
balancerConfig.getSecurity(), balancerConfig.getHttp())); balancerConfig.getSecurity(), balancerConfig.getHttp()));
ch.pipeline().addLast("fileServer", new HttpStaticFileServerHandler()); ch.pipeline().addLast("fileServer", new HttpStaticFileServerHandler());
ch.pipeline().addLast("login", ch.pipeline().addLast("login",
new X509LoginRequestHandler(balancerConfig.getSecurity(), balancerConfig.getHttp())); new X509LoginRequestHandler(balancerConfig.getSecurity(), balancerConfig.getHttp()));
ch.pipeline().addLast("httpRelay", ch.pipeline().addLast("httpRelay", new HttpRelayHandler(metricResolver, httpClientPool));
new HttpRelayHandler(balancerConfig, metricResolver, httpClientPool));
ch.pipeline().addLast("error", new TimelyExceptionHandler()); ch.pipeline().addLast("error", new TimelyExceptionHandler());
} }
}; };
@@ -489,7 +532,7 @@ public class Balancer {
ch.pipeline().addLast("logger", new LoggingHandler()); ch.pipeline().addLast("logger", new LoggingHandler());
ch.pipeline().addLast("packetDecoder", new UdpPacketToByteBuf()); ch.pipeline().addLast("packetDecoder", new UdpPacketToByteBuf());
ch.pipeline().addLast("buffer", new MetricsBufferDecoder()); ch.pipeline().addLast("buffer", new MetricsBufferDecoder());
ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(8192, true, Delimiters.lineDelimiter())); ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(65536, true, Delimiters.lineDelimiter()));
ch.pipeline().addLast("putDecoder", new UdpDecoder()); ch.pipeline().addLast("putDecoder", new UdpDecoder());
ch.pipeline().addLast("udpRelayHandler", new UdpRelayHandler(metricResolver, udpClientPool)); ch.pipeline().addLast("udpRelayHandler", new UdpRelayHandler(metricResolver, udpClientPool));
} }
@@ -502,7 +545,7 @@ public class Balancer {
@Override @Override
protected void initChannel(SocketChannel ch) throws Exception { protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("buffer", new MetricsBufferDecoder()); ch.pipeline().addLast("buffer", new MetricsBufferDecoder());
ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(8192, true, Delimiters.lineDelimiter())); ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(65536, true, Delimiters.lineDelimiter()));
ch.pipeline().addLast("putDecoder", new TcpDecoder()); ch.pipeline().addLast("putDecoder", new TcpDecoder());
ch.pipeline().addLast("tcpRelayHandler", new TcpRelayHandler(metricResolver, tcpClientPools)); ch.pipeline().addLast("tcpRelayHandler", new TcpRelayHandler(metricResolver, tcpClientPools));
ch.pipeline().addLast("versionHandler", new TcpVersionHandler()); ch.pipeline().addLast("versionHandler", new TcpVersionHandler());
@@ -518,8 +561,8 @@ public class Balancer {
protected void initChannel(SocketChannel ch) throws Exception { protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("ssl", sslCtx.newHandler(ch.alloc())); ch.pipeline().addLast("ssl", sslCtx.newHandler(ch.alloc()));
ch.pipeline().addLast("httpServer", new HttpServerCodec()); ch.pipeline().addLast("httpServer", new HttpServerCodec());
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(8192)); ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
ch.pipeline().addLast("sessionExtractor", new WebSocketHttpCookieHandler(balancerConfig.getSecurity())); ch.pipeline().addLast("sessionExtractor", new WebSocketFullRequestHandler());
ch.pipeline().addLast("idle-handler", ch.pipeline().addLast("idle-handler",
new IdleStateHandler(balancerConfig.getWebsocket().getTimeout(), 0, 0)); new IdleStateHandler(balancerConfig.getWebsocket().getTimeout(), 0, 0));
@@ -5,8 +5,10 @@ import javax.validation.Valid;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import timely.configuration.Cache; import timely.configuration.Cache;
@Validated
@Component @Component
@ConfigurationProperties(prefix = "timely-balancer") @ConfigurationProperties(prefix = "timely-balancer")
public class BalancerConfiguration { public class BalancerConfiguration {
@@ -8,6 +8,8 @@ public class ClientSsl {
private String trustStoreFile; private String trustStoreFile;
private String trustStoreType; private String trustStoreType;
private String trustStorePassword; private String trustStorePassword;
private boolean useClientCert = true;
private boolean hostVerificationEnabled = true;
public String getKeyFile() { public String getKeyFile() {
return keyFile; return keyFile;
@@ -55,6 +57,21 @@ public class ClientSsl {
public void setTrustStorePassword(String trustStorePassword) { public void setTrustStorePassword(String trustStorePassword) {
this.trustStorePassword = trustStorePassword; this.trustStorePassword = trustStorePassword;
}
public void setUseClientCert(boolean useClientCert) {
this.useClientCert = useClientCert;
}
public boolean isUseClientCert() {
return useClientCert;
}
public void setHostVerificationEnabled(boolean hostVerificationEnabled) {
this.hostVerificationEnabled = hostVerificationEnabled;
}
public boolean isHostVerificationEnabled() {
return hostVerificationEnabled;
} }
} }
@@ -1,5 +1,7 @@
package timely.balancer.connection; package timely.balancer.connection;
import java.util.Timer;
import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringBuilder;
@@ -8,8 +10,6 @@ import org.slf4j.LoggerFactory;
import timely.balancer.ArrivalRate; import timely.balancer.ArrivalRate;
import timely.balancer.configuration.BalancerConfiguration; import timely.balancer.configuration.BalancerConfiguration;
import java.util.Timer;
public class TimelyBalancedHost { public class TimelyBalancedHost {
private static final Logger LOG = LoggerFactory.getLogger(TimelyBalancedHost.class); private static final Logger LOG = LoggerFactory.getLogger(TimelyBalancedHost.class);
@@ -5,22 +5,25 @@ import javax.net.ssl.SSLContext;
import org.apache.commons.pool2.KeyedPooledObjectFactory; import org.apache.commons.pool2.KeyedPooledObjectFactory;
import org.apache.commons.pool2.PooledObject; import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject; import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import timely.balancer.configuration.BalancerSecurity;
import timely.balancer.connection.TimelyBalancedHost; import timely.balancer.connection.TimelyBalancedHost;
public class HttpClientFactory implements KeyedPooledObjectFactory<TimelyBalancedHost, CloseableHttpClient> { public class HttpClientFactory implements KeyedPooledObjectFactory<TimelyBalancedHost, CloseableHttpClient> {
private final SSLContext sslContext; private final SSLContext sslContext;
private final BalancerSecurity security;
public HttpClientFactory(SSLContext sslContext) { public HttpClientFactory(BalancerSecurity security, SSLContext sslContext) {
this.security = security;
this.sslContext = sslContext; this.sslContext = sslContext;
} }
@Override @Override
public PooledObject<CloseableHttpClient> makeObject(TimelyBalancedHost k) throws Exception { public PooledObject<CloseableHttpClient> makeObject(TimelyBalancedHost k) throws Exception {
BasicCookieStore cookieJar = new BasicCookieStore(); // disable cookie management because we are sharing connections
return new DefaultPooledObject<>(timely.client.http.HttpClient.get(this.sslContext, cookieJar, false)); return new DefaultPooledObject<>(timely.client.http.HttpClient.get(this.sslContext, null,
security.getClientSsl().isHostVerificationEnabled(), security.getClientSsl().isUseClientCert()));
} }
@Override @Override
@@ -6,14 +6,15 @@ import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import timely.balancer.configuration.BalancerConfiguration; import timely.balancer.configuration.BalancerHttp;
import timely.balancer.configuration.BalancerSecurity;
import timely.balancer.connection.TimelyBalancedHost; import timely.balancer.connection.TimelyBalancedHost;
public class HttpClientPool extends GenericKeyedObjectPool<TimelyBalancedHost, CloseableHttpClient> { public class HttpClientPool extends GenericKeyedObjectPool<TimelyBalancedHost, CloseableHttpClient> {
private static final Logger LOG = LoggerFactory.getLogger(HttpClientPool.class); private static final Logger LOG = LoggerFactory.getLogger(HttpClientPool.class);
public HttpClientPool(BalancerConfiguration config, SSLContext sslContext) { public HttpClientPool(BalancerSecurity balancerSecurity, BalancerHttp http, SSLContext sslContext) {
super(new HttpClientFactory(sslContext), config.getHttp().getHttpClientPool()); super(new HttpClientFactory(balancerSecurity, sslContext), http.getHttpClientPool());
} }
} }
@@ -12,13 +12,11 @@ import timely.client.websocket.subscription.WebSocketSubscriptionClient;
public class WsClientFactory implements KeyedPooledObjectFactory<TimelyBalancedHost, WebSocketSubscriptionClient> { public class WsClientFactory implements KeyedPooledObjectFactory<TimelyBalancedHost, WebSocketSubscriptionClient> {
private final SSLContext sslContext; private final SSLContext sslContext;
private final boolean doLogin;
private BalancerConfiguration balancerConfig; private BalancerConfiguration balancerConfig;
public WsClientFactory(BalancerConfiguration balancerConfig, SSLContext sslContext) { public WsClientFactory(BalancerConfiguration balancerConfig, SSLContext sslContext) {
this.sslContext = sslContext;
this.doLogin = balancerConfig.isLoginRequired();
this.balancerConfig = balancerConfig; this.balancerConfig = balancerConfig;
this.sslContext = sslContext;
} }
@Override @Override
@@ -26,7 +24,9 @@ public class WsClientFactory implements KeyedPooledObjectFactory<TimelyBalancedH
int bufferSize = balancerConfig.getWebsocket().getSubscriptionBatchSize() * 500; int bufferSize = balancerConfig.getWebsocket().getSubscriptionBatchSize() * 500;
WebSocketSubscriptionClient client = new WebSocketSubscriptionClient(sslContext, k.getHost(), k.getHttpPort(), WebSocketSubscriptionClient client = new WebSocketSubscriptionClient(sslContext, k.getHost(), k.getHttpPort(),
k.getWsPort(), doLogin, "", "", false, bufferSize); k.getWsPort(), balancerConfig.getSecurity().getClientSsl().isUseClientCert(),
balancerConfig.isLoginRequired(), "", "",
balancerConfig.getSecurity().getClientSsl().isHostVerificationEnabled(), bufferSize);
return new DefaultPooledObject<>(client); return new DefaultPooledObject<>(client);
} }
@@ -3,7 +3,6 @@ package timely.balancer.netty.http;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH; import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
@@ -12,6 +11,7 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.google.common.collect.Multimap;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
@@ -33,26 +33,26 @@ import org.apache.http.message.BasicHeader;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.util.StreamUtils; import org.springframework.util.StreamUtils;
import timely.api.request.AuthenticatedRequest;
import timely.api.request.MetricRequest; import timely.api.request.MetricRequest;
import timely.api.request.timeseries.HttpRequest; import timely.api.request.timeseries.HttpRequest;
import timely.api.request.timeseries.QueryRequest; import timely.api.request.timeseries.QueryRequest;
import timely.api.response.TimelyException; import timely.api.response.TimelyException;
import timely.balancer.configuration.BalancerConfiguration; import timely.auth.util.HttpHeaderUtils;
import timely.auth.util.ProxiedEntityUtils;
import timely.balancer.connection.TimelyBalancedHost; import timely.balancer.connection.TimelyBalancedHost;
import timely.balancer.connection.http.HttpClientPool; import timely.balancer.connection.http.HttpClientPool;
import timely.balancer.resolver.MetricResolver; import timely.balancer.resolver.MetricResolver;
import timely.netty.http.TimelyHttpHandler; import timely.netty.http.TimelyHttpHandler;
import timely.netty.http.auth.TimelyAuthenticationToken;
public class HttpRelayHandler extends SimpleChannelInboundHandler<HttpRequest> implements TimelyHttpHandler { public class HttpRelayHandler extends SimpleChannelInboundHandler<HttpRequest> implements TimelyHttpHandler {
private static final Logger LOG = LoggerFactory.getLogger(HttpRelayHandler.class); private static final Logger LOG = LoggerFactory.getLogger(HttpRelayHandler.class);
private final BalancerConfiguration balancerConf;
private final HttpClientPool httpClientPool; private final HttpClientPool httpClientPool;
private MetricResolver metricResolver; private MetricResolver metricResolver;
public HttpRelayHandler(BalancerConfiguration balancerConf, MetricResolver metricResolver, public HttpRelayHandler(MetricResolver metricResolver, HttpClientPool httpClientPool) {
HttpClientPool httpClientPool) {
this.balancerConf = balancerConf;
this.metricResolver = metricResolver; this.metricResolver = metricResolver;
this.httpClientPool = httpClientPool; this.httpClientPool = httpClientPool;
} }
@@ -86,13 +86,21 @@ public class HttpRelayHandler extends SimpleChannelInboundHandler<HttpRequest> i
} else { } else {
k = metricResolver.getHostPortKey(null); k = metricResolver.getHostPortKey(null);
} }
k = metricResolver.getHostPortKey(metric);
client = httpClientPool.borrowObject(k); client = httpClientPool.borrowObject(k);
List<Map.Entry<String, String>> headerList = request.headers().entries(); Multimap<String, String> headers = HttpHeaderUtils.toMultimap(request.headers());
List<Header> relayedHeaderList = new ArrayList<>(); if (msg instanceof AuthenticatedRequest) {
AuthenticatedRequest authenticatedRequest = (AuthenticatedRequest) msg;
TimelyAuthenticationToken token = authenticatedRequest.getToken();
if (token != null) {
if (token.getClientCert() != null) {
ProxiedEntityUtils.addProxyHeaders(headers, token.getClientCert());
}
}
}
for (Map.Entry<String, String> h : headerList) { List<Header> relayedHeaderList = new ArrayList<>();
for (Map.Entry<String, String> h : headers.entries()) {
if (!h.getKey().equals(CONTENT_LENGTH)) { if (!h.getKey().equals(CONTENT_LENGTH)) {
relayedHeaderList.add(new BasicHeader(h.getKey(), h.getValue())); relayedHeaderList.add(new BasicHeader(h.getKey(), h.getValue()));
} }
@@ -143,7 +151,6 @@ public class HttpRelayHandler extends SimpleChannelInboundHandler<HttpRequest> i
this.sendHttpError(ctx, new TimelyException(HttpResponseStatus.METHOD_NOT_ALLOWED.code(), this.sendHttpError(ctx, new TimelyException(HttpResponseStatus.METHOD_NOT_ALLOWED.code(),
"Method not allowed", "Method not allowed")); "Method not allowed", "Method not allowed"));
} else { } else {
// buf = JsonUtil.getObjectMapper().writeValueAsBytes(msg);
ByteArrayOutputStream baos = new MyByteArrayOutputStream(); ByteArrayOutputStream baos = new MyByteArrayOutputStream();
StreamUtils.copy(relayedResponse.getEntity().getContent(), baos); StreamUtils.copy(relayedResponse.getEntity().getContent(), baos);
@@ -153,8 +160,6 @@ public class HttpRelayHandler extends SimpleChannelInboundHandler<HttpRequest> i
for (Header h : relayedResponse.getAllHeaders()) { for (Header h : relayedResponse.getAllHeaders()) {
response.headers().add(h.getName(), h.getValue()); response.headers().add(h.getName(), h.getValue());
} }
// response.headers().set(HttpHeaders.Names.CONTENT_TYPE,
// Constants.JSON_TYPE);
response.headers().set(CONTENT_LENGTH, response.content().readableBytes()); response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
sendResponse(ctx, response); sendResponse(ctx, response);
} }
@@ -197,13 +202,4 @@ public class HttpRelayHandler extends SimpleChannelInboundHandler<HttpRequest> i
} }
return s; return s;
} }
public static void main(String[] args) {
String s = "https://127.0.0.1:4243/api/query?start=1356998400000&end=1528398304000&m=sum:rate{false,100,0}:sys.thermal.gauge{class=aws}";
URI uri = URI.create(encodeURI(s));
System.out.println(uri.toString());
}
} }
@@ -1,12 +1,16 @@
package timely.balancer.netty.ws; package timely.balancer.netty.ws;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.websocket.CloseReason; import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig; import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session; import javax.websocket.Session;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
@@ -15,25 +19,48 @@ import io.netty.util.concurrent.ScheduledFuture;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import timely.api.response.TimelyException; import timely.api.response.TimelyException;
import timely.auth.util.ProxiedEntityUtils;
import timely.client.websocket.ClientHandler; import timely.client.websocket.ClientHandler;
import timely.netty.http.auth.TimelyAuthenticationToken;
public class WsClientHandler extends ClientHandler { public class WsClientHandler extends ClientHandler {
private static final Logger LOG = LoggerFactory.getLogger(WsClientHandler.class); private static final Logger LOG = LoggerFactory.getLogger(WsClientHandler.class);
private final ChannelHandlerContext ctx; private final ChannelHandlerContext ctx;
private final TimelyAuthenticationToken token;
private ScheduledFuture<?> ping; private ScheduledFuture<?> ping;
public WsClientHandler(ChannelHandlerContext ctx, int pingRate) { public WsClientHandler(ChannelHandlerContext ctx, TimelyAuthenticationToken token, int pingRate) {
this.ctx = ctx; this.ctx = ctx;
this.token = token;
this.ping = this.ctx.executor().scheduleAtFixedRate(() -> { this.ping = this.ctx.executor().scheduleAtFixedRate(() -> {
LOG.trace("Sending ping on channel {}", ctx.channel()); LOG.trace("Sending ping on channel {}", ctx.channel());
ctx.writeAndFlush(new PingWebSocketFrame()); ctx.writeAndFlush(new PingWebSocketFrame());
}, pingRate, pingRate, TimeUnit.SECONDS); }, pingRate, pingRate, TimeUnit.SECONDS);
} }
@Override
public void beforeRequest(Map<String, List<String>> headers) {
if (token.getClientCert() != null) {
Multimap<String, String> proxyRequestHeaders = HashMultimap.create();
ProxiedEntityUtils.addProxyHeaders(proxyRequestHeaders, token.getClientCert());
for (String s : proxyRequestHeaders.keySet()) {
headers.put(s, new ArrayList(proxyRequestHeaders.get(s)));
}
}
Multimap<String, String> originalRequestHeaders = token.getHttpHeaders();
for (String s : originalRequestHeaders.keySet()) {
if (!headers.containsKey(s)) {
// add pre-existing values if key does not exist in proxyRequest
headers.put(s, new ArrayList(originalRequestHeaders.get(s)));
}
}
}
@Override @Override
public void onOpen(Session session, EndpointConfig config) { public void onOpen(Session session, EndpointConfig config) {
session.addMessageHandler((MessageHandler.Whole<String>) message -> { session.addMessageHandler(String.class, message -> {
ctx.writeAndFlush(new TextWebSocketFrame(message)); ctx.writeAndFlush(new TextWebSocketFrame(message));
LOG.debug("Message received on Websocket session {}: {}", session.getId(), message); LOG.debug("Message received on Websocket session {}: {}", session.getId(), message);
}); });
@@ -9,6 +9,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.google.common.collect.Multimap;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
@@ -24,6 +25,7 @@ import timely.api.request.subscription.CreateSubscription;
import timely.api.request.subscription.RemoveSubscription; import timely.api.request.subscription.RemoveSubscription;
import timely.api.request.subscription.SubscriptionRequest; import timely.api.request.subscription.SubscriptionRequest;
import timely.api.response.TimelyException; import timely.api.response.TimelyException;
import timely.auth.util.ProxiedEntityUtils;
import timely.balancer.configuration.BalancerConfiguration; import timely.balancer.configuration.BalancerConfiguration;
import timely.balancer.connection.TimelyBalancedHost; import timely.balancer.connection.TimelyBalancedHost;
import timely.balancer.connection.ws.WsClientPool; import timely.balancer.connection.ws.WsClientPool;
@@ -64,6 +66,10 @@ public class WsRelayHandler extends SimpleChannelInboundHandler<SubscriptionRequ
String subscriptionId; String subscriptionId;
try { try {
String metric = null; String metric = null;
Multimap<String, String> headers = msg.getRequestHeaders();
ProxiedEntityUtils.addProxyHeaders(headers, msg.getToken().getClientCert());
if (msg instanceof CreateSubscription) { if (msg instanceof CreateSubscription) {
CreateSubscription create = (CreateSubscription) msg; CreateSubscription create = (CreateSubscription) msg;
final String currentSubscriptionId = create.getSubscriptionId(); final String currentSubscriptionId = create.getSubscriptionId();
@@ -118,7 +124,8 @@ public class WsRelayHandler extends SimpleChannelInboundHandler<SubscriptionRequ
if (hostClientHolder == null) { if (hostClientHolder == null) {
k = metricResolver.getHostPortKey(metric); k = metricResolver.getHostPortKey(metric);
client = wsClientPool.borrowObject(k); client = wsClientPool.borrowObject(k);
client.open(new WsClientHandler(origContext, (balancerConfig.getWebsocket().getTimeout() / 2))); client.open(new WsClientHandler(origContext, add.getToken(),
(balancerConfig.getWebsocket().getTimeout() / 2)));
metricToClientMap.put(metric, new WsClientHolder(k, client)); metricToClientMap.put(metric, new WsClientHolder(k, client));
} else { } else {
client = hostClientHolder.getClient(); client = hostClientHolder.getClient();
@@ -4,6 +4,8 @@ import static timely.Server.SERVICE_DISCOVERY_PATH;
import static timely.balancer.Balancer.ASSIGNMENTS_LAST_UPDATED_PATH; import static timely.balancer.Balancer.ASSIGNMENTS_LAST_UPDATED_PATH;
import static timely.balancer.Balancer.ASSIGNMENTS_LOCK_PATH; import static timely.balancer.Balancer.ASSIGNMENTS_LOCK_PATH;
import static timely.balancer.Balancer.LEADER_LATCH_PATH; import static timely.balancer.Balancer.LEADER_LATCH_PATH;
import static timely.store.cache.DataStoreCache.NON_CACHED_METRICS;
import static timely.store.cache.DataStoreCache.NON_CACHED_METRICS_LOCK_PATH;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
@@ -20,6 +22,8 @@ import java.util.Set;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -27,10 +31,10 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.csvreader.CsvReader; import com.csvreader.CsvReader;
import com.csvreader.CsvWriter; import com.csvreader.CsvWriter;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.curator.RetryPolicy; import org.apache.commons.lang3.SerializationUtils;
import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.atomic.DistributedAtomicLong; import org.apache.curator.framework.recipes.atomic.DistributedAtomicLong;
import org.apache.curator.framework.recipes.atomic.DistributedAtomicValue;
import org.apache.curator.framework.recipes.cache.TreeCache; import org.apache.curator.framework.recipes.cache.TreeCache;
import org.apache.curator.framework.recipes.cache.TreeCacheEvent; import org.apache.curator.framework.recipes.cache.TreeCacheEvent;
import org.apache.curator.framework.recipes.cache.TreeCacheListener; import org.apache.curator.framework.recipes.cache.TreeCacheListener;
@@ -38,7 +42,7 @@ import org.apache.curator.framework.recipes.leader.LeaderLatch;
import org.apache.curator.framework.recipes.leader.LeaderLatchListener; import org.apache.curator.framework.recipes.leader.LeaderLatchListener;
import org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock; import org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock;
import org.apache.curator.framework.state.ConnectionState; import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.retry.RetryUntilElapsed; import org.apache.curator.retry.RetryForever;
import org.apache.curator.x.discovery.ServiceCache; import org.apache.curator.x.discovery.ServiceCache;
import org.apache.curator.x.discovery.ServiceDiscovery; import org.apache.curator.x.discovery.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceDiscoveryBuilder; import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;
@@ -49,7 +53,7 @@ import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.Path;
import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.CreateMode;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import timely.ServerDetails; import timely.ServerDetails;
@@ -68,56 +72,50 @@ public class BalancedMetricResolver implements MetricResolver {
private Random r = new Random(); private Random r = new Random();
final private HealthChecker healthChecker; final private HealthChecker healthChecker;
private Timer timer = new Timer("RebalanceTimer", true); private Timer timer = new Timer("RebalanceTimer", true);
private Timer arrivalRateTimer = new Timer("AriivalRateTimerResolver", true); private Timer arrivalRateTimer = new Timer("ArrivalRateTimerResolver", true);
private int roundRobinCounter = 0; private int roundRobinCounter = 0;
private Set<String> nonCachedMetrics = new HashSet<>(); private Set<String> nonCachedMetrics = new HashSet<>();
private ReentrantReadWriteLock nonCachedMetricsLock = new ReentrantReadWriteLock(); private DistributedAtomicValue nonCachedMetricsIP;
private ReentrantReadWriteLock nonCachedMetricsLocalLock = new ReentrantReadWriteLock();
private InterProcessReadWriteLock nonCachedMetricsIPRWLock;
private BalancerConfiguration balancerConfig; private BalancerConfiguration balancerConfig;
private CuratorFramework curatorFramework;
private RetryPolicy retryPolicy = new RetryUntilElapsed(60000, 1000);
private LeaderLatch leaderLatch; private LeaderLatch leaderLatch;
private AtomicBoolean isLeader = new AtomicBoolean(false); private AtomicBoolean isLeader = new AtomicBoolean(false);
private InterProcessReadWriteLock assignmentsLock; private InterProcessReadWriteLock assignmentsIPRWLock;
private DistributedAtomicLong assignmentsLastUpdatedInHdfs; private DistributedAtomicLong assignmentsLastUpdatedInHdfs;
private AtomicLong assignmentsLastUpdatedLocal = new AtomicLong(0); private AtomicLong assignmentsLastUpdatedLocal = new AtomicLong(0);
private String[] zkPaths = new String[] { LEADER_LATCH_PATH, ASSIGNMENTS_LAST_UPDATED_PATH, ASSIGNMENTS_LOCK_PATH,
SERVICE_DISCOVERY_PATH };
private enum BalanceType { private enum BalanceType {
HIGH_LOW, HIGH_AVG, AVG_LOW; HIGH_LOW, HIGH_AVG, AVG_LOW
} }
public BalancedMetricResolver(BalancerConfiguration balancerConfig, HealthChecker healthChecker) { public BalancedMetricResolver(CuratorFramework curatorFramework, BalancerConfiguration balancerConfig,
HealthChecker healthChecker) {
this.balancerConfig = balancerConfig; this.balancerConfig = balancerConfig;
this.healthChecker = healthChecker; this.healthChecker = healthChecker;
// start curator framework assignmentsIPRWLock = new InterProcessReadWriteLock(curatorFramework, ASSIGNMENTS_LOCK_PATH);
curatorFramework = CuratorFrameworkFactory.newClient(balancerConfig.getZooKeeper().getServers(), 30000, 1000, testIPRWLock(curatorFramework, assignmentsIPRWLock, ASSIGNMENTS_LOCK_PATH);
retryPolicy);
curatorFramework.start();
ensureZkPaths(curatorFramework, zkPaths);
assignmentsLock = new InterProcessReadWriteLock(curatorFramework, ASSIGNMENTS_LOCK_PATH);
startLeaderLatch(curatorFramework); startLeaderLatch(curatorFramework);
startServiceListener(curatorFramework); startServiceListener(curatorFramework);
assignmentsLastUpdatedInHdfs = new DistributedAtomicLong(curatorFramework, ASSIGNMENTS_LAST_UPDATED_PATH, assignmentsLastUpdatedInHdfs = new DistributedAtomicLong(curatorFramework, ASSIGNMENTS_LAST_UPDATED_PATH,
retryPolicy); new RetryForever(1000));
TreeCacheListener listener = new TreeCacheListener() { TreeCacheListener assignmentListener = new TreeCacheListener() {
@Override @Override
public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent event) throws Exception { public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent event) throws Exception {
LOG.info("Handling event {}. assignmentsLastUpdatedInHdfs:{}", event.getType().toString(), LOG.debug("Handling assignments event {}. assignmentsLastUpdatedInHdfs:{}", event.getType().toString(),
new Date(assignmentsLastUpdatedInHdfs.get().postValue())); new Date(assignmentsLastUpdatedInHdfs.get().postValue()));
if (event.getType().equals(TreeCacheEvent.Type.NODE_UPDATED)) { if (event.getType().equals(TreeCacheEvent.Type.NODE_UPDATED)) {
long lastLocalUpdate = assignmentsLastUpdatedLocal.get(); long lastLocalUpdate = assignmentsLastUpdatedLocal.get();
long lastHdfsUpdate = assignmentsLastUpdatedInHdfs.get().postValue(); long lastHdfsUpdate = assignmentsLastUpdatedInHdfs.get().postValue();
if (lastHdfsUpdate > lastLocalUpdate) { if (lastHdfsUpdate > lastLocalUpdate) {
LOG.info("Reading assignments from hdfs lastHdfsUpdate ({}) > lastLocalUpdate ({})", LOG.debug("Reading assignments from hdfs lastHdfsUpdate ({}) > lastLocalUpdate ({})",
new Date(lastHdfsUpdate), new Date(lastLocalUpdate)); new Date(lastHdfsUpdate), new Date(lastLocalUpdate));
readAssignmentsFromHdfs(); readAssignmentsFromHdfs();
} else { } else {
LOG.info("Not reading assignments from hdfs lastHdfsUpdate ({}) <= lastLocalUpdate ({})", LOG.debug("Not reading assignments from hdfs lastHdfsUpdate ({}) <= lastLocalUpdate ({})",
new Date(lastHdfsUpdate), new Date(lastLocalUpdate)); new Date(lastHdfsUpdate), new Date(lastLocalUpdate));
} }
} }
@@ -125,20 +123,43 @@ public class BalancedMetricResolver implements MetricResolver {
}; };
try { try {
TreeCache treeCache = new TreeCache(curatorFramework, ASSIGNMENTS_LAST_UPDATED_PATH); TreeCache assignmentTreeCache = new TreeCache(curatorFramework, ASSIGNMENTS_LAST_UPDATED_PATH);
treeCache.getListenable().addListener(listener); assignmentTreeCache.getListenable().addListener(assignmentListener);
treeCache.start(); assignmentTreeCache.start();
} catch (Exception e) { } catch (Exception e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
} }
nonCachedMetricsLock.writeLock().lock(); nonCachedMetricsIPRWLock = new InterProcessReadWriteLock(curatorFramework, NON_CACHED_METRICS_LOCK_PATH);
testIPRWLock(curatorFramework, nonCachedMetricsIPRWLock, NON_CACHED_METRICS_LOCK_PATH);
nonCachedMetricsIP = new DistributedAtomicValue(curatorFramework, NON_CACHED_METRICS, new RetryForever(1000));
TreeCacheListener nonCachedMetricsListener = new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent event) throws Exception {
if (event.getType().equals(TreeCacheEvent.Type.NODE_UPDATED)) {
LOG.debug("Handling nonCachedMetricsIP event {}", event.getType().toString());
readNonCachedMetricsIP();
}
}
};
try { try {
nonCachedMetrics.addAll(balancerConfig.getCache().getNonCachedMetrics()); TreeCache nonCachedMetricsTreeCache = new TreeCache(curatorFramework, NON_CACHED_METRICS);
} finally { nonCachedMetricsTreeCache.getListenable().addListener(nonCachedMetricsListener);
nonCachedMetricsLock.writeLock().unlock(); nonCachedMetricsTreeCache.start();
} catch (Exception e) {
LOG.error(e.getMessage(), e);
} }
nonCachedMetricsLocalLock.writeLock().lock();
try {
addNonCachedMetrics(balancerConfig.getCache().getNonCachedMetrics());
} finally {
nonCachedMetricsLocalLock.writeLock().unlock();
}
readNonCachedMetricsIP();
readAssignmentsFromHdfs(); readAssignmentsFromHdfs();
timer.schedule(new TimerTask() { timer.schedule(new TimerTask() {
@@ -164,7 +185,7 @@ public class BalancedMetricResolver implements MetricResolver {
long lastLocalUpdate = assignmentsLastUpdatedLocal.get(); long lastLocalUpdate = assignmentsLastUpdatedLocal.get();
long lastHdfsUpdate = assignmentsLastUpdatedInHdfs.get().postValue(); long lastHdfsUpdate = assignmentsLastUpdatedInHdfs.get().postValue();
if (lastLocalUpdate > lastHdfsUpdate) { if (lastLocalUpdate > lastHdfsUpdate) {
LOG.info("Writing assignments to hdfs lastLocalUpdate ({}) > lastHdfsUpdate ({})", LOG.debug("Writing assignments to hdfs lastLocalUpdate ({}) > lastHdfsUpdate ({})",
new Date(lastLocalUpdate), new Date(lastHdfsUpdate)); new Date(lastLocalUpdate), new Date(lastHdfsUpdate));
writeAssigmentsToHdfs(); writeAssigmentsToHdfs();
} }
@@ -176,17 +197,44 @@ public class BalancedMetricResolver implements MetricResolver {
}, 10000, 60000); }, 10000, 60000);
} }
private void ensureZkPaths(CuratorFramework curatorFramework, String[] paths) { private void readNonCachedMetricsIP() {
for (String s : paths) {
try {
Stat stat = curatorFramework.checkExists().forPath(s);
if (stat == null) {
curatorFramework.create().creatingParentContainersIfNeeded().forPath(s);
}
} catch (Exception e) {
LOG.info(e.getMessage());
}
Set<String> currentNonCachedMetricsDistributed = new TreeSet<>();
try {
nonCachedMetricsIPRWLock.readLock().acquire();
byte[] currentNonCachedMetricsDistributedBytes = nonCachedMetricsIP.get().postValue();
if (currentNonCachedMetricsDistributedBytes != null) {
try {
currentNonCachedMetricsDistributed = SerializationUtils
.deserialize(currentNonCachedMetricsDistributedBytes);
} catch (Exception e) {
LOG.error(e.getMessage());
currentNonCachedMetricsDistributed = new TreeSet<>();
}
}
} catch (Exception e) {
LOG.error(e.getMessage(), e);
} finally {
try {
nonCachedMetricsIPRWLock.readLock().release();
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
}
nonCachedMetricsLocalLock.writeLock().lock();
try {
if (nonCachedMetrics.containsAll(currentNonCachedMetricsDistributed)) {
LOG.debug("local nonCachedMetrics already contains {}", currentNonCachedMetricsDistributed);
} else {
currentNonCachedMetricsDistributed.removeAll(nonCachedMetrics);
LOG.debug("Adding {} to local nonCachedMetrics", currentNonCachedMetricsDistributed);
nonCachedMetrics.addAll(currentNonCachedMetricsDistributed);
}
} catch (Exception e) {
LOG.error(e.getMessage(), e);
} finally {
nonCachedMetricsLocalLock.writeLock().unlock();
} }
} }
@@ -457,21 +505,21 @@ public class BalancedMetricResolver implements MetricResolver {
} }
if (highHosts.isEmpty() && lowHosts.isEmpty()) { if (highHosts.isEmpty() && lowHosts.isEmpty()) {
LOG.info("Host's arrival rates are within {} of the average", controlBandPercentage); LOG.debug("Host's arrival rates are within {} of the average", controlBandPercentage);
} else if (!highHosts.isEmpty() && !lowHosts.isEmpty()) { } else if (!highHosts.isEmpty() && !lowHosts.isEmpty()) {
LOG.info("begin rebalancing {}", BalanceType.HIGH_LOW); LOG.debug("begin rebalancing {}", BalanceType.HIGH_LOW);
numReassigned = rebalance(highHosts, lowHosts, calculatedRates, averageArrivalRate, numReassigned = rebalance(highHosts, lowHosts, calculatedRates, averageArrivalRate,
BalanceType.HIGH_LOW); BalanceType.HIGH_LOW);
LOG.info("end rebalancing {} - reassigned {}", BalanceType.HIGH_LOW, numReassigned); LOG.debug("end rebalancing {} - reassigned {}", BalanceType.HIGH_LOW, numReassigned);
} else if (lowHosts.isEmpty()) { } else if (lowHosts.isEmpty()) {
LOG.info("begin rebalancing {}", BalanceType.HIGH_AVG); LOG.debug("begin rebalancing {}", BalanceType.HIGH_AVG);
numReassigned = rebalance(highHosts, avgHosts, calculatedRates, averageArrivalRate, numReassigned = rebalance(highHosts, avgHosts, calculatedRates, averageArrivalRate,
BalanceType.HIGH_AVG); BalanceType.HIGH_AVG);
LOG.info("end rebalancing {} - reassigned {}", BalanceType.HIGH_AVG, numReassigned); LOG.debug("end rebalancing {} - reassigned {}", BalanceType.HIGH_AVG, numReassigned);
} else { } else {
LOG.info("begin rebalancing {}", BalanceType.AVG_LOW); LOG.debug("begin rebalancing {}", BalanceType.AVG_LOW);
numReassigned = rebalance(avgHosts, lowHosts, calculatedRates, averageArrivalRate, BalanceType.AVG_LOW); numReassigned = rebalance(avgHosts, lowHosts, calculatedRates, averageArrivalRate, BalanceType.AVG_LOW);
LOG.info("end rebalancing {} - reassigned {}", BalanceType.AVG_LOW, numReassigned); LOG.debug("end rebalancing {} - reassigned {}", BalanceType.AVG_LOW, numReassigned);
} }
} }
return numReassigned; return numReassigned;
@@ -582,6 +630,70 @@ public class BalancedMetricResolver implements MetricResolver {
return numReassigned; return numReassigned;
} }
private void addNonCachedMetrics(Collection<String> nonCachedMetricsUpdate) {
if (!nonCachedMetricsUpdate.isEmpty()) {
try {
LOG.debug("Adding {} to local nonCachedMetrics", nonCachedMetricsUpdate);
nonCachedMetricsLocalLock.writeLock().lock();
try {
nonCachedMetrics.addAll(nonCachedMetricsUpdate);
} finally {
nonCachedMetricsLocalLock.writeLock().unlock();
}
try {
nonCachedMetricsIPRWLock.writeLock().acquire();
byte[] currentNonCachedMetricsDistributedBytes = nonCachedMetricsIP.get().postValue();
Set<String> currentNonCachedMetricsIP;
if (currentNonCachedMetricsDistributedBytes == null) {
currentNonCachedMetricsIP = new TreeSet<>();
} else {
currentNonCachedMetricsIP = SerializationUtils
.deserialize(currentNonCachedMetricsDistributedBytes);
}
if (currentNonCachedMetricsIP.containsAll(nonCachedMetricsUpdate)) {
LOG.debug("nonCachedMetricsIP already contains {}", nonCachedMetricsUpdate);
} else {
nonCachedMetricsUpdate.removeAll(currentNonCachedMetricsIP);
LOG.debug("Adding {} to nonCachedMetricsIP", nonCachedMetricsUpdate);
TreeSet<String> updateSet = new TreeSet<>();
updateSet.addAll(currentNonCachedMetricsIP);
updateSet.addAll(nonCachedMetricsUpdate);
byte[] updateValue = SerializationUtils.serialize(updateSet);
nonCachedMetricsIP.trySet(updateValue);
if (!nonCachedMetricsIP.get().succeeded()) {
nonCachedMetricsIP.forceSet(updateValue);
}
}
} finally {
nonCachedMetricsIPRWLock.writeLock().release();
}
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
}
}
private void testIPRWLock(CuratorFramework curatorFramework, InterProcessReadWriteLock lock, String path) {
try {
lock.writeLock().acquire(10, TimeUnit.SECONDS);
} catch (Exception e1) {
try {
curatorFramework.delete().deletingChildrenIfNeeded().forPath(path);
curatorFramework.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT)
.forPath(path);
} catch (Exception e2) {
LOG.info(e2.getMessage(), e2);
}
} finally {
try {
lock.writeLock().release();
} catch (Exception e3) {
LOG.error(e3.getMessage());
}
}
}
private boolean shouldCache(String metricName) { private boolean shouldCache(String metricName) {
if (StringUtils.isBlank(metricName)) { if (StringUtils.isBlank(metricName)) {
@@ -589,6 +701,14 @@ public class BalancedMetricResolver implements MetricResolver {
} else { } else {
balancerLock.readLock().lock(); balancerLock.readLock().lock();
try { try {
nonCachedMetricsLocalLock.readLock().lock();
try {
if (nonCachedMetrics.contains(metricName)) {
return false;
}
} finally {
nonCachedMetricsLocalLock.readLock().unlock();
}
if (metricToHostMap.containsKey(metricName)) { if (metricToHostMap.containsKey(metricName)) {
return true; return true;
} }
@@ -596,27 +716,17 @@ public class BalancedMetricResolver implements MetricResolver {
balancerLock.readLock().unlock(); balancerLock.readLock().unlock();
} }
nonCachedMetricsLock.readLock().lock(); nonCachedMetricsLocalLock.writeLock().lock();
try { try {
if (nonCachedMetrics.contains(metricName)) {
return false;
}
for (String r : nonCachedMetrics) { for (String r : nonCachedMetrics) {
if (metricName.matches(r)) { if (metricName.matches(r)) {
LOG.info("Adding {} to list of non-cached metrics", metricName); LOG.debug("Adding {} to list of non-cached metrics", metricName);
nonCachedMetricsLock.readLock().unlock(); addNonCachedMetrics(Collections.singleton(metricName));
nonCachedMetricsLock.writeLock().lock();
nonCachedMetrics.add(metricName);
return false; return false;
} }
} }
} finally { } finally {
if (nonCachedMetricsLock.isWriteLockedByCurrentThread()) { nonCachedMetricsLocalLock.writeLock().unlock();
nonCachedMetricsLock.writeLock().unlock();
} else {
nonCachedMetricsLock.readLock().unlock();
}
} }
return true; return true;
} }
@@ -659,7 +769,7 @@ public class BalancedMetricResolver implements MetricResolver {
} else if (!tbh.isUp()) { } else if (!tbh.isUp()) {
TimelyBalancedHost oldTbh = tbh; TimelyBalancedHost oldTbh = tbh;
tbh = getLeastUsedHost(); tbh = getLeastUsedHost();
LOG.info("rebalancing from host that is down: reassigning metric {} from server {}:{} to {}:{}", LOG.debug("rebalancing from host that is down: reassigning metric {} from server {}:{} to {}:{}",
metric, oldTbh.getHost(), oldTbh.getTcpPort(), tbh.getHost(), tbh.getTcpPort()); metric, oldTbh.getHost(), oldTbh.getTcpPort(), tbh.getHost(), tbh.getTcpPort());
if (!balancerLock.isWriteLockedByCurrentThread()) { if (!balancerLock.isWriteLockedByCurrentThread()) {
balancerLock.readLock().unlock(); balancerLock.readLock().unlock();
@@ -768,7 +878,8 @@ public class BalancedMetricResolver implements MetricResolver {
Map<String, TimelyBalancedHost> assignedMetricToHostMap = new TreeMap<>(); Map<String, TimelyBalancedHost> assignedMetricToHostMap = new TreeMap<>();
try { try {
assignmentsLock.readLock().acquire(); assignmentsIPRWLock.readLock().acquire();
balancerLock.writeLock().lock();
Configuration configuration = new Configuration(); Configuration configuration = new Configuration();
FileSystem fs = FileSystem.get(configuration); FileSystem fs = FileSystem.get(configuration);
Path assignmentFile = new Path(balancerConfig.getAssignmentFile()); Path assignmentFile = new Path(balancerConfig.getAssignmentFile());
@@ -798,16 +909,24 @@ public class BalancedMetricResolver implements MetricResolver {
} }
} }
balancerLock.writeLock().lock(); metricToHostMap.clear();
for (Map.Entry<String, TimelyBalancedHost> e : assignedMetricToHostMap.entrySet()) {
if (shouldCache(e.getKey())) {
metricToHostMap.put(e.getKey(), e.getValue());
}
}
nonCachedMetricsLocalLock.readLock().lock();
try { try {
metricToHostMap.clear(); Iterator<Map.Entry<String, TimelyBalancedHost>> itr = metricToHostMap.entrySet().iterator();
for (Map.Entry<String, TimelyBalancedHost> e : assignedMetricToHostMap.entrySet()) { while (itr.hasNext()) {
if (shouldCache(e.getKey())) { Map.Entry<String, TimelyBalancedHost> e = itr.next();
metricToHostMap.put(e.getKey(), e.getValue()); if (nonCachedMetrics.contains(e.getKey())) {
itr.remove();
} }
} }
} finally { } finally {
balancerLock.writeLock().unlock(); nonCachedMetricsLocalLock.readLock().unlock();
} }
assignmentsLastUpdatedLocal.set(assignmentsLastUpdatedInHdfs.get().postValue()); assignmentsLastUpdatedLocal.set(assignmentsLastUpdatedInHdfs.get().postValue());
LOG.info("Read {} assignments from hdfs lastHdfsUpdate = lastLocalUpdate ({})", metricToHostMap.size(), LOG.info("Read {} assignments from hdfs lastHdfsUpdate = lastLocalUpdate ({})", metricToHostMap.size(),
@@ -815,8 +934,9 @@ public class BalancedMetricResolver implements MetricResolver {
} catch (Exception e) { } catch (Exception e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
} finally { } finally {
balancerLock.writeLock().unlock();
try { try {
assignmentsLock.readLock().release(); assignmentsIPRWLock.readLock().release();
} catch (Exception e) { } catch (Exception e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
} }
@@ -827,8 +947,9 @@ public class BalancedMetricResolver implements MetricResolver {
CsvWriter writer = null; CsvWriter writer = null;
try { try {
assignmentsLock.writeLock().acquire(); assignmentsIPRWLock.writeLock().acquire();
balancerLock.readLock().lock(); balancerLock.readLock().lock();
nonCachedMetricsLocalLock.readLock().lock();
try { try {
if (!metricToHostMap.isEmpty()) { if (!metricToHostMap.isEmpty()) {
Configuration configuration = new Configuration(); Configuration configuration = new Configuration();
@@ -845,12 +966,14 @@ public class BalancedMetricResolver implements MetricResolver {
writer.write("tcpPort"); writer.write("tcpPort");
writer.endRecord(); writer.endRecord();
for (Map.Entry<String, TimelyBalancedHost> e : metricToHostMap.entrySet()) { for (Map.Entry<String, TimelyBalancedHost> e : metricToHostMap.entrySet()) {
writer.write(e.getKey()); if (!nonCachedMetrics.contains(e.getKey())) {
writer.write(e.getValue().getHost()); writer.write(e.getKey());
writer.write(Integer.toString(e.getValue().getTcpPort())); writer.write(e.getValue().getHost());
writer.endRecord(); writer.write(Integer.toString(e.getValue().getTcpPort()));
LOG.trace("Saving assigment: {} to {}:{}", e.getKey(), e.getValue().getHost(), writer.endRecord();
e.getValue().getTcpPort()); LOG.trace("Saving assigment: {} to {}:{}", e.getKey(), e.getValue().getHost(),
e.getValue().getTcpPort());
}
} }
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
@@ -859,10 +982,11 @@ public class BalancedMetricResolver implements MetricResolver {
if (!assignmentsLastUpdatedInHdfs.get().succeeded()) { if (!assignmentsLastUpdatedInHdfs.get().succeeded()) {
assignmentsLastUpdatedInHdfs.forceSet(now); assignmentsLastUpdatedInHdfs.forceSet(now);
} }
LOG.info("Wrote {} assignments to hdfs lastHdfsUpdate = lastLocalUpdate ({})", LOG.debug("Wrote {} assignments to hdfs lastHdfsUpdate = lastLocalUpdate ({})",
metricToHostMap.size(), new Date(assignmentsLastUpdatedLocal.get())); metricToHostMap.size(), new Date(assignmentsLastUpdatedLocal.get()));
} }
} finally { } finally {
nonCachedMetricsLocalLock.readLock().unlock();
balancerLock.readLock().unlock(); balancerLock.readLock().unlock();
} }
} catch (Exception e) { } catch (Exception e) {
@@ -872,7 +996,7 @@ public class BalancedMetricResolver implements MetricResolver {
writer.close(); writer.close();
} }
try { try {
assignmentsLock.writeLock().release(); assignmentsIPRWLock.writeLock().release();
} catch (Exception e) { } catch (Exception e) {
LOG.error(e.getMessage(), e); LOG.error(e.getMessage(), e);
} }
+41 -2
View File
@@ -4,12 +4,12 @@
<parent> <parent>
<groupId>timely</groupId> <groupId>timely</groupId>
<artifactId>timely</artifactId> <artifactId>timely</artifactId>
<version>1.0.1-SNAPSHOT</version> <version>1.2.3</version>
</parent> </parent>
<artifactId>timely-client</artifactId> <artifactId>timely-client</artifactId>
<properties> <properties>
<!-- PEP 440 compliant version gets substituted into src/main/python/template/setup.py --> <!-- PEP 440 compliant version gets substituted into src/main/python/template/setup.py -->
<python.client.version>0.0.7.dev0</python.client.version> <python.client.version>1.2.3</python.client.version>
</properties> </properties>
<dependencies> <dependencies>
<dependency> <dependency>
@@ -107,4 +107,43 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
<profiles>
<profile>
<id>python</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>python-dist</id>
<goals>
<goal>exec</goal>
</goals>
<phase>package</phase>
<configuration>
<executable>python</executable>
<workingDirectory>${project.basedir}/src/main/python</workingDirectory>
<commandlineArgs>setup.py sdist --dist-dir ${project.build.directory}</commandlineArgs>
</configuration>
</execution>
<execution>
<id>python-clean</id>
<goals>
<goal>exec</goal>
</goals>
<phase>package</phase>
<configuration>
<executable>rm</executable>
<workingDirectory>${project.basedir}/src/main/python</workingDirectory>
<commandlineArgs>-rf timely.egg-info setup.py</commandlineArgs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project> </project>
@@ -1,12 +1,13 @@
package timely.client.http; package timely.client.http;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import org.apache.http.client.CookieStore; import org.apache.http.client.CookieStore;
import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig; import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.HttpClients;
@@ -40,14 +41,31 @@ public class HttpClient {
} }
public static CloseableHttpClient get(SSLContext ssl, CookieStore cookieStore, boolean hostVerificationEnabled) { public static CloseableHttpClient get(SSLContext ssl, CookieStore cookieStore, boolean hostVerificationEnabled) {
RequestConfig defaultRequestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build(); return get(ssl, cookieStore, hostVerificationEnabled, false);
}
HttpClientBuilder builder = HttpClients.custom().setSSLContext(ssl).setDefaultCookieStore(cookieStore) public static CloseableHttpClient get(SSLContext ssl, CookieStore cookieStore, boolean hostVerificationEnabled,
.setDefaultRequestConfig(defaultRequestConfig); boolean clientAuth) {
HostnameVerifier hostnameVerifier;
if (hostVerificationEnabled) { if (hostVerificationEnabled) {
builder.setSSLHostnameVerifier(new DefaultHostnameVerifier()); hostnameVerifier = SSLConnectionSocketFactory.getDefaultHostnameVerifier();
} else { } else {
builder.setSSLHostnameVerifier(new NoopHostnameVerifier()); hostnameVerifier = new NoopHostnameVerifier();
}
RequestConfig defaultRequestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build();
HttpClientBuilder builder;
if (cookieStore == null) {
builder = HttpClients.custom().setSSLContext(ssl).disableCookieManagement()
.setDefaultRequestConfig(defaultRequestConfig).setSSLHostnameVerifier(hostnameVerifier);
} else {
builder = HttpClients.custom().setSSLContext(ssl).setDefaultCookieStore(cookieStore)
.setDefaultRequestConfig(defaultRequestConfig).setSSLHostnameVerifier(hostnameVerifier);
}
if (clientAuth) {
SSLConnectionSocketFactory sslCSF = new SSLConnectionSocketFactory(ssl,
new String[] { "TLSv1.1", "TLSv1.2" }, null, hostnameVerifier);
builder.setSSLSocketFactory(sslCSF);
} }
return builder.build(); return builder.build();
} }
@@ -1,5 +1,8 @@
package timely.client.websocket; package timely.client.websocket;
import java.util.List;
import java.util.Map;
import javax.websocket.CloseReason; import javax.websocket.CloseReason;
import javax.websocket.Endpoint; import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig; import javax.websocket.EndpointConfig;
@@ -35,4 +38,6 @@ public abstract class ClientHandler extends Endpoint {
LOG.error("Error occurred on Websocket session" + session.getId(), error); LOG.error("Error occurred on Websocket session" + session.getId(), error);
} }
public void beforeRequest(Map<String, List<String>> headers) {
}
} }
@@ -14,13 +14,15 @@ import org.apache.http.cookie.Cookie;
public class TimelyEndpointConfig implements ClientEndpointConfig { public class TimelyEndpointConfig implements ClientEndpointConfig {
private String sessionCookie = null; private String sessionCookie = null;
private final ClientHandler clientEndpoint;
private static final String FORMAT = "%s=%s;"; private static final String FORMAT = "%s=%s;";
public TimelyEndpointConfig(Cookie sessionCookie) { public TimelyEndpointConfig(ClientHandler clientEndpoint, Cookie sessionCookie) {
if (null != sessionCookie) { if (null != sessionCookie) {
this.sessionCookie = String.format(FORMAT, sessionCookie.getName(), sessionCookie.getValue()); this.sessionCookie = String.format(FORMAT, sessionCookie.getName(), sessionCookie.getValue());
} }
this.clientEndpoint = clientEndpoint;
} }
@Override @Override
@@ -58,6 +60,7 @@ public class TimelyEndpointConfig implements ClientEndpointConfig {
if (null != sessionCookie) { if (null != sessionCookie) {
headers.put("Cookie", Collections.singletonList(sessionCookie)); headers.put("Cookie", Collections.singletonList(sessionCookie));
} }
TimelyEndpointConfig.this.clientEndpoint.beforeRequest(headers);
} }
}; };
} }
@@ -40,6 +40,7 @@ public class WebSocketClient implements AutoCloseable {
private final int timelyHttpsPort; private final int timelyHttpsPort;
private final int timelyWssPort; private final int timelyWssPort;
private final boolean doLogin; private final boolean doLogin;
private final boolean clientAuth;
private final String timelyUsername; private final String timelyUsername;
private final String timelyPassword; private final String timelyPassword;
private final boolean hostVerificationEnabled; private final boolean hostVerificationEnabled;
@@ -51,13 +52,14 @@ public class WebSocketClient implements AutoCloseable {
protected volatile boolean closed = true; protected volatile boolean closed = true;
protected WebSocketClient(SSLContext ssl, String timelyHostname, int timelyHttpsPort, int timelyWssPort, protected WebSocketClient(SSLContext ssl, String timelyHostname, int timelyHttpsPort, int timelyWssPort,
boolean doLogin, String timelyUsername, String timelyPassword, boolean hostVerificationEnabled, boolean clientAuth, boolean doLogin, String timelyUsername, String timelyPassword,
int bufferSize) { boolean hostVerificationEnabled, int bufferSize) {
this.ssl = ssl; this.ssl = ssl;
this.timelyHostname = timelyHostname; this.timelyHostname = timelyHostname;
this.timelyHttpsPort = timelyHttpsPort; this.timelyHttpsPort = timelyHttpsPort;
this.timelyWssPort = timelyWssPort; this.timelyWssPort = timelyWssPort;
this.doLogin = doLogin; this.doLogin = doLogin;
this.clientAuth = clientAuth;
this.timelyUsername = timelyUsername; this.timelyUsername = timelyUsername;
this.timelyPassword = timelyPassword; this.timelyPassword = timelyPassword;
this.hostVerificationEnabled = hostVerificationEnabled; this.hostVerificationEnabled = hostVerificationEnabled;
@@ -67,28 +69,28 @@ public class WebSocketClient implements AutoCloseable {
Preconditions.checkNotNull(timelyHttpsPort, "%s must be supplied", "Timely HTTPS port"); Preconditions.checkNotNull(timelyHttpsPort, "%s must be supplied", "Timely HTTPS port");
Preconditions.checkNotNull(timelyWssPort, "%s must be supplied", "Timely WSS port"); Preconditions.checkNotNull(timelyWssPort, "%s must be supplied", "Timely WSS port");
if (doLogin && ((StringUtils.isEmpty(timelyUsername) && !StringUtils.isEmpty(timelyPassword) if (doLogin && !clientAuth) {
|| (!StringUtils.isEmpty(timelyUsername) && StringUtils.isEmpty(timelyPassword))))) { if ((StringUtils.isEmpty(timelyUsername) && !StringUtils.isEmpty(timelyPassword)
throw new IllegalArgumentException("Both Timely username and password must be empty or non-empty"); || (!StringUtils.isEmpty(timelyUsername) && StringUtils.isEmpty(timelyPassword)))) {
throw new IllegalArgumentException("Both Timely username and password must be empty or non-empty");
}
} }
} }
protected WebSocketClient(String timelyHostname, int timelyHttpsPort, int timelyWssPort, boolean doLogin, protected WebSocketClient(String timelyHostname, int timelyHttpsPort, int timelyWssPort, boolean clientAuth,
String timelyUsername, String timelyPassword, String keyStoreFile, String keyStoreType, String keyStorePass, boolean doLogin, String timelyUsername, String timelyPassword, String keyStoreFile, String keyStoreType,
String trustStoreFile, String trustStoreType, String trustStorePass, boolean hostVerificationEnabled, String keyStorePass, String trustStoreFile, String trustStoreType, String trustStorePass,
int bufferSize) { boolean hostVerificationEnabled, int bufferSize) {
this(HttpClient.getSSLContext(trustStoreFile, trustStoreType, trustStorePass, keyStoreFile, keyStoreType, this(HttpClient.getSSLContext(trustStoreFile, trustStoreType, trustStorePass, keyStoreFile, keyStoreType,
keyStorePass), timelyHostname, timelyHttpsPort, timelyWssPort, doLogin, timelyUsername, timelyPassword, keyStorePass), timelyHostname, timelyHttpsPort, timelyWssPort, clientAuth, doLogin, timelyUsername,
hostVerificationEnabled, bufferSize); timelyPassword, hostVerificationEnabled, bufferSize);
} }
public void open(ClientHandler clientEndpoint) throws IOException, DeploymentException, URISyntaxException { public void open(ClientHandler clientEndpoint) throws IOException, DeploymentException, URISyntaxException {
Cookie sessionCookie = null; Cookie sessionCookie = null;
if (doLogin) { if (doLogin) {
BasicCookieStore cookieJar = new BasicCookieStore(); BasicCookieStore cookieJar = new BasicCookieStore();
try (CloseableHttpClient client = HttpClient.get(ssl, cookieJar, hostVerificationEnabled)) { try (CloseableHttpClient client = HttpClient.get(ssl, cookieJar, hostVerificationEnabled, clientAuth)) {
String target = "https://" + timelyHostname + ":" + timelyHttpsPort + "/login"; String target = "https://" + timelyHostname + ":" + timelyHttpsPort + "/login";
@@ -140,8 +142,8 @@ public class WebSocketClient implements AutoCloseable {
webSocketClient.getProperties().put(ClientProperties.SSL_ENGINE_CONFIGURATOR, sslEngine); webSocketClient.getProperties().put(ClientProperties.SSL_ENGINE_CONFIGURATOR, sslEngine);
webSocketClient.getProperties().put(ClientProperties.INCOMING_BUFFER_SIZE, bufferSize); webSocketClient.getProperties().put(ClientProperties.INCOMING_BUFFER_SIZE, bufferSize);
String wssPath = "wss://" + timelyHostname + ":" + timelyWssPort + "/websocket"; String wssPath = "wss://" + timelyHostname + ":" + timelyWssPort + "/websocket";
session = webSocketClient.connectToServer(clientEndpoint, new TimelyEndpointConfig(sessionCookie), session = webSocketClient.connectToServer(clientEndpoint,
new URI(wssPath)); new TimelyEndpointConfig(clientEndpoint, sessionCookie), new URI(wssPath));
final ByteBuffer pingData = ByteBuffer.allocate(0); final ByteBuffer pingData = ByteBuffer.allocate(0);
webSocketClient.getScheduledExecutorService().scheduleAtFixedRate(() -> { webSocketClient.getScheduledExecutorService().scheduleAtFixedRate(() -> {
@@ -27,21 +27,21 @@ public class WebSocketSubscriptionClient extends WebSocketClient {
private final String subscriptionId; private final String subscriptionId;
public WebSocketSubscriptionClient(SSLContext ssl, String timelyHostname, int timelyHttpsPort, int timelyWssPort, public WebSocketSubscriptionClient(SSLContext ssl, String timelyHostname, int timelyHttpsPort, int timelyWssPort,
boolean doLogin, String timelyUsername, String timelyPassword, boolean hostVerificationEnabled, boolean clientAuth, boolean doLogin, String timelyUsername, String timelyPassword,
int bufferSize) { boolean hostVerificationEnabled, int bufferSize) {
super(ssl, timelyHostname, timelyHttpsPort, timelyWssPort, doLogin, timelyUsername, timelyPassword, super(ssl, timelyHostname, timelyHttpsPort, timelyWssPort, clientAuth, doLogin, timelyUsername, timelyPassword,
hostVerificationEnabled, bufferSize); hostVerificationEnabled, bufferSize);
subscriptionId = UUID.randomUUID().toString(); subscriptionId = UUID.randomUUID().toString();
LOG.trace("Created WebSocketClient with subscriptionId {}", this.subscriptionId); LOG.trace("Created WebSocketClient with subscriptionId {}", this.subscriptionId);
} }
public WebSocketSubscriptionClient(String timelyHostname, int timelyHttpsPort, int timelyWssPort, boolean doLogin, public WebSocketSubscriptionClient(String timelyHostname, int timelyHttpsPort, int timelyWssPort,
String timelyUsername, String timelyPassword, String keyStoreFile, String keyStoreType, String keyStorePass, boolean clientAuth, boolean doLogin, String timelyUsername, String timelyPassword, String keyStoreFile,
String trustStoreFile, String trustStoreType, String trustStorePass, boolean hostVerificationEnabled, String keyStoreType, String keyStorePass, String trustStoreFile, String trustStoreType,
int bufferSize) { String trustStorePass, boolean hostVerificationEnabled, int bufferSize) {
super(timelyHostname, timelyHttpsPort, timelyWssPort, doLogin, timelyUsername, timelyPassword, keyStoreFile, super(timelyHostname, timelyHttpsPort, timelyWssPort, clientAuth, doLogin, timelyUsername, timelyPassword,
keyStoreType, keyStorePass, trustStoreFile, trustStoreType, trustStorePass, hostVerificationEnabled, keyStoreFile, keyStoreType, keyStorePass, trustStoreFile, trustStoreType, trustStorePass,
bufferSize); hostVerificationEnabled, bufferSize);
subscriptionId = UUID.randomUUID().toString(); subscriptionId = UUID.randomUUID().toString();
LOG.trace("Created WebSocketClient with subscriptionId {}", this.subscriptionId); LOG.trace("Created WebSocketClient with subscriptionId {}", this.subscriptionId);
} }
@@ -23,7 +23,7 @@ def pivot(df, metric, groupByColumn=None):
return dataFrame return dataFrame
def resample(df, sample, how='mean'): def resample(df, sample, how='mean', interpolate=True, fill=None):
dataFrame = pandas.DataFrame(df, copy=True) dataFrame = pandas.DataFrame(df, copy=True)
if dataFrame is not None: if dataFrame is not None:
@@ -2,7 +2,6 @@ import time
from datetime import datetime from datetime import datetime
from datetime import timedelta from datetime import timedelta
from datetime import tzinfo from datetime import tzinfo
from pandas.tslib import Timedelta
import pandas import pandas
class TimeDateError(ValueError): class TimeDateError(ValueError):
@@ -16,14 +16,15 @@ utc = UTC()
class TimelyWebSocketClient(WebSocketClient): class TimelyWebSocketClient(WebSocketClient):
def __init__(self, hostport, metric, tags, startTime, endTime, connect_timeout=WebSocketClient.DEFAULT_CONNECT_TIMEOUT, def __init__(self, hostport, metric, tags, startTime, endTime, connect_timeout=WebSocketClient.DEFAULT_CONNECT_TIMEOUT,
request_timeout=WebSocketClient.DEFAULT_REQUEST_TIMEOUT): request_timeout=WebSocketClient.DEFAULT_REQUEST_TIMEOUT, client_cert=None, client_key=None, ca_certs=None, validate_cert=False):
self.metric = metric self.metric = metric
self.tags = tags self.tags = tags
self.startTime = startTime self.startTime = startTime
self.endTime = endTime self.endTime = endTime
self.subscriptionId = random.randint(1000000000, 9999999999) self.subscriptionId = random.randint(1000000000, 9999999999)
WebSocketClient.__init__(self, hostport, '/websocket', {'metric' : metric}, connect_timeout, request_timeout) WebSocketClient.__init__(self, hostport, '/websocket', {'metric' : metric}, connect_timeout, request_timeout,
client_cert=client_cert, client_key=client_key, ca_certs=ca_certs, validate_cert=validate_cert)
def _on_message(self, msg): def _on_message(self, msg):
# implement in subclass # implement in subclass
@@ -72,7 +73,8 @@ class TimelyMetric(TimelyWebSocketClient):
client = None client = None
endtime = None endtime = None
def __init__(self, hostport, metric, tags, beginStr, endStr, period, sample): def __init__(self, hostport, metric, tags, beginStr, endStr, period, sample,
client_cert=None, client_key=None, ca_certs=None, validate_cert=False):
self.series = pandas.Series() self.series = pandas.Series()
self.metric = metric self.metric = metric
self.tags = tags self.tags = tags
@@ -82,7 +84,8 @@ class TimelyMetric(TimelyWebSocketClient):
self.data = [] self.data = []
self.timeDateRange = TimeDateRange(beginStr, endStr, period) self.timeDateRange = TimeDateRange(beginStr, endStr, period)
TimelyWebSocketClient.__init__(self, hostport, metric, tags, self.timeDateRange.getBeginMs(), self.timeDateRange.getEndMs()) TimelyWebSocketClient.__init__(self, hostport, metric, tags, self.timeDateRange.getBeginMs(), self.timeDateRange.getEndMs(),
client_cert=client_cert, client_key=client_key, ca_certs=ca_certs, validate_cert=validate_cert)
def debugOn(self): def debugOn(self):
self.debug = True self.debug = True
@@ -15,10 +15,14 @@ class WebSocketClient():
APPLICATION_JSON = 'application/json' APPLICATION_JSON = 'application/json'
def __init__(self, hostport, path, params, connect_timeout=DEFAULT_CONNECT_TIMEOUT, def __init__(self, hostport, path, params, connect_timeout=DEFAULT_CONNECT_TIMEOUT,
request_timeout=DEFAULT_REQUEST_TIMEOUT): request_timeout=DEFAULT_REQUEST_TIMEOUT, client_cert=None, client_key=None, ca_certs=None, validate_cert=False):
self.connect_timeout = connect_timeout self.connect_timeout = connect_timeout
self.request_timeout = request_timeout self.request_timeout = request_timeout
self.client_cert=client_cert
self.client_key=client_key
self.ca_certs=ca_certs
self.validate_cert=validate_cert
self.url = 'wss://' + hostport self.url = 'wss://' + hostport
if (path is not None and len(path) > 0): if (path is not None and len(path) > 0):
if (path.startswith('/')): if (path.startswith('/')):
@@ -39,7 +43,8 @@ class WebSocketClient():
"""Connect to the server. """Connect to the server.
:param str url: server URL. :param str url: server URL.
""" """
request = websocket.httpclient.HTTPRequest(self.url, connect_timeout=self.connect_timeout, request_timeout=self.request_timeout, validate_cert=False) request = websocket.httpclient.HTTPRequest(self.url, connect_timeout=self.connect_timeout, request_timeout=self.request_timeout, validate_cert=self.validate_cert,
client_cert=self.client_cert, client_key=self.client_key, ca_certs=self.ca_certs)
websocket.websocket_connect(request, io_loop=ioloop, callback=self._connect_callback) websocket.websocket_connect(request, io_loop=ioloop, callback=self._connect_callback)
def send(self, data): def send(self, data):
@@ -5,7 +5,9 @@ import os
from timely.TimelyAnalyticConfiguration import TimelyAnalyticConfiguration from timely.TimelyAnalyticConfiguration import TimelyAnalyticConfiguration
hostport = "127.0.0.1:5244" hostport = "127.0.0.1:5244"
timelyMetric = timely.TimelyMetric.TimelyMetric(hostport, 'timely.keys.metric.inserted', None, None, None, '24 hours', None).fetch() client_cert = None
client_key = None
timelyMetric = timely.TimelyMetric.TimelyMetric(hostport, 'timely.keys.metric.inserted', None, None, None, '24 hours', None, client_cert=client_cert, client_key=client_key).fetch()
print(timelyMetric.getDataFrame()) print(timelyMetric.getDataFrame())
exit() exit()
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>timely</groupId> <groupId>timely</groupId>
<artifactId>timely</artifactId> <artifactId>timely</artifactId>
<version>1.0.1-SNAPSHOT</version> <version>1.2.3</version>
</parent> </parent>
<artifactId>collectd-nsq-plugin</artifactId> <artifactId>collectd-nsq-plugin</artifactId>
<dependencies> <dependencies>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>timely</groupId> <groupId>timely</groupId>
<artifactId>timely</artifactId> <artifactId>timely</artifactId>
<version>1.0.1-SNAPSHOT</version> <version>1.2.3</version>
</parent> </parent>
<artifactId>collectd-plugin-parent</artifactId> <artifactId>collectd-plugin-parent</artifactId>
<dependencies> <dependencies>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>timely</groupId> <groupId>timely</groupId>
<artifactId>timely</artifactId> <artifactId>timely</artifactId>
<version>1.0.1-SNAPSHOT</version> <version>1.2.3</version>
</parent> </parent>
<artifactId>collectd-timely-plugin</artifactId> <artifactId>collectd-timely-plugin</artifactId>
<dependencies> <dependencies>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>timely</groupId> <groupId>timely</groupId>
<artifactId>timely</artifactId> <artifactId>timely</artifactId>
<version>1.0.1-SNAPSHOT</version> <version>1.2.3</version>
</parent> </parent>
<artifactId>timely-common</artifactId> <artifactId>timely-common</artifactId>
<dependencies> <dependencies>
+42
View File
@@ -0,0 +1,42 @@
#!/bin/bash
if [[ `uname` == "Darwin" ]]; then
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
TCNATIVE_SUFFIX="jnilib"
else
THIS_SCRIPT=`readlink -f $0`
TCNATIVE_SUFFIX="so"
fi
THIS_DIR="${THIS_SCRIPT%/*}"
NATIVE_DIR="${THIS_DIR}/META-INF/native"
BASE_DIR=${THIS_DIR}/..
TMP_DIR="${BASE_DIR}/tmp"
CONF_DIR="${BASE_DIR}/conf"
LIB_DIR="${BASE_DIR}/lib"
NUM_SERVER_THREADS=4
if [[ -e ${TMP_DIR} ]]; then
rm -rf ${TMP_DIR}
fi
mkdir ${TMP_DIR}
if [[ -e ${NATIVE_DIR} ]]; then
rm -rf ${NATIVE_DIR}
fi
mkdir -p ${NATIVE_DIR}
pushd ${BASE_DIR}/bin
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative-boringssl-static*.jar META-INF/native/libnetty_tcnative_linux_x86_64.${TCNATIVE_SUFFIX}
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-all*.jar META-INF/native/libnetty_transport_native_epoll_x86_64.${TCNATIVE_SUFFIX}
popd
export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*:${HADOOP_CONF_DIR}"
JVM_ARGS="-Xmx1G -Xms1G -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS} Dlog4j.configurationFile=${CONF_DIR}/log4j2-spring.xml"
JVM_ARGS="${JVM_ARGS} -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
JVM_ARGS="${JVM_ARGS} -Djava.library.path=${NATIVE_DIR}"
#JVM_ARGS="${JVM_ARGS} -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"
echo "$JAVA_HOME/bin/java ${JVM_ARGS} timely.grafana.auth.GrafanaAuth --spring.config.name=grafana-auth"
$JAVA_HOME/bin/java ${JVM_ARGS} timely.grafana.auth.GrafanaAuth --spring.config.name=grafana-auth
+55
View File
@@ -0,0 +1,55 @@
grafana-auth:
grafana:
host: localhost
port: 3001
security:
session-max-age: 86400
cache-expiration-minutes: 60
cache-refresh-minutes: 5
allow-anonymous-http-access: true
allow-anonymous-ws-access: true
serverSsl:
certificate-file:
key-file:
#key-password:
use-generated-keypair: true
trust-store-file:
use-openssl: true
#use-ciphers:
clientSsl:
keyFile:
keyType: pkcs12
#keyPassword:
trustStoreFile:
trustStoreType: jks
#trustStorePassword:
http:
host: localhost
ip: 127.0.0.1
port: 3000
redirect-path: /secure-me
strict-transport-max-age: 608400
cors:
allow-any-origin: false
allow-null-origin: false
allow-credentials: true
allowed-origins:
allowed-methods: DELETE,GET,HEAD,OPTIONS,PUT,POST
allowed-headers: content-type
httpClientPool:
minIdlePerKey: 0
maxIdlePerKey: 40
maxTotalPerKey: 40
maxTotal: -1
lifo: true
fairness: false
maxWaitMillis: -1
minEvictableIdleTimeMillis: 1800000
evictorShutdownTimeoutMillis: 10000
softMinEvictableIdleTimeMillis: -1
numTestsPerEvictionRun: 3
timeBetweenEvictionRunsMillis: -1
blockWhenExhausted: true
jmxEnabled: true
testOnBorrow: false
testOnReturn: false
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" monitorInterval="60">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="error" includeLocation="false">
<AppenderRef ref="Console" level="error" />
</Root>
</Loggers>
</Configuration>
+29
View File
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" monitorInterval="60">
<Properties>
<Property name="PID">????</Property>
<Property name="LOG_PATTERN">%clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{%5p} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n%wEx</Property>
</Properties>
<Appenders>
<RollingRandomAccessFile name="Rolling" fileName="/srv/logs/timely/grafana-auth.log"
filePattern="/srv/logs/timely/$${date:yyyy-MM}/grafana-auth-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<SizeBasedTriggeringPolicy size="1024 MB" />
</Policies>
<DefaultRolloverStrategy max="10" />
</RollingRandomAccessFile>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${LOG_PATTERN}"/>
</Console>
</Appenders>
<Loggers>
<Root level="trace" includeLocation="false">
<AppenderRef ref="Console" level="warn" />
<AppenderRef ref="Rolling" level="trace" />
</Root>
<Logger name="timely" additivity="false">
<AppenderRef ref="Console" level="debug" />
</Logger>
</Loggers>
</Configuration>
+71
View File
@@ -0,0 +1,71 @@
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<beans:bean id="x509PrincipalExtractor" class="org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor">
<beans:property name="subjectDnRegex" value="CN=(.*?)(?:,|$)"/>
</beans:bean>
<beans:bean id="timelyUserDetailsService" class="timely.auth.UserDetailsService">
<beans:property name="users">
<util:list>
<beans:bean class="timely.auth.TimelyUser">
<beans:constructor-arg name="subjectDn">
<beans:value>CN=example.com</beans:value>
</beans:constructor-arg>
<beans:constructor-arg name="auths">
<util:list>
<beans:value>D</beans:value>
<beans:value>E</beans:value>
<beans:value>F</beans:value>
</util:list>
</beans:constructor-arg>
<beans:constructor-arg name="roles">
<util:list>
<beans:value>G</beans:value>
<beans:value>H</beans:value>
<beans:value>I</beans:value>
</util:list>
</beans:constructor-arg>
</beans:bean>
</util:list>
</beans:property>
</beans:bean>
<beans:bean id="x509AuthorizationProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<beans:property name="preAuthenticatedUserDetailsService" ref="timelyUserDetailsService" />
</beans:bean>
<user-service id="usernamePasswordService">
<user name="test" password="test1" authorities="A,B,C" />
</user-service>
<beans:bean id="noOpPasswordEncoder" class="org.springframework.security.crypto.password.NoOpPasswordEncoder"/>
<beans:bean id="UserNamePasswordAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="usernamePasswordService"/>
<beans:property name="passwordEncoder" ref="noOpPasswordEncoder"/>
</beans:bean>
<authentication-manager alias="authenticationManager">
<!-- X509 PreAuthenticated authorization provider -->
<authentication-provider ref="x509AuthorizationProvider"/>
<!-- Basic authorization provider, validates provided username and password -->
<authentication-provider ref="UserNamePasswordAuthenticationProvider" />
</authentication-manager>
<util:list id="requiredRoles" value-type="java.lang.String">
<beans:value></beans:value>
</util:list>
<util:list id="requiredAuths" value-type="java.lang.String">
<beans:value></beans:value>
</util:list>
</beans:beans>
+296
View File
@@ -0,0 +1,296 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>timely</groupId>
<artifactId>timely</artifactId>
<version>1.2.3</version>
</parent>
<artifactId>grafana-auth</artifactId>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>com.google.flatbuffers</groupId>
<artifactId>flatbuffers-java</artifactId>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</dependency>
<dependency>
<groupId>io.github.lukehutch</groupId>
<artifactId>fast-classpath-scanner</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>net.sourceforge.javacsv</groupId>
<artifactId>javacsv</artifactId>
</dependency>
<dependency>
<groupId>org.apache.accumulo</groupId>
<artifactId>accumulo-core</artifactId>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.accumulo</groupId>
<artifactId>accumulo-minicluster</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-x-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<exclusions>
<exclusion>
<groupId>jdk.tools</groupId>
<artifactId>jdk.tools</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-minicluster</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>timely-balancer</artifactId>
</dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>timely-client</artifactId>
</dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>timely-common</artifactId>
</dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>timely-server</artifactId>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative-boringssl-static</artifactId>
<version>${version.netty-tcnative}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test-autoconfigure</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>assemble</id>
<goals>
<goal>single</goal>
</goals>
<phase>package</phase>
<configuration>
<descriptors>
<descriptor>src/main/dist/assembly.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<goals>
<goal>copy-dependencies</goal>
</goals>
<phase>prepare-package</phase>
<configuration>
<excludeArtifactIds>junit</excludeArtifactIds>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
<execution>
<id>unpack-native</id>
<goals>
<goal>unpack</goal>
</goals>
<phase>process-test-classes</phase>
<configuration>
<artifactItems>
<artifactItem>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative-boringssl-static</artifactId>
<version>${version.netty-tcnative}</version>
<type>jar</type>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/native</outputDirectory>
<includes>**/*.so</includes>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-source</id>
<goals>
<goal>add-source</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<sources>
<source>src/main/generated-java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<extensions>
<!-- Use os-maven-plugin to initialize the "os.detected" properties -->
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
</extension>
</extensions>
</build>
</project>
+55
View File
@@ -0,0 +1,55 @@
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>dist</id>
<formats>
<format>tar.gz</format>
</formats>
<fileSets>
<fileSet>
<directory>${project.basedir}</directory>
<outputDirectory></outputDirectory>
<includes>
<include>README*</include>
<include>LICENSE*</include>
<include>NOTICE*</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory>lib</outputDirectory>
<includes>
<include>timely*.jar</include>
<include>grafana*.jar</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.build.directory}/lib</directory>
<outputDirectory>lib</outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.basedir}/bin</directory>
<outputDirectory>bin</outputDirectory>
<includes>
<include>**/*</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.basedir}/conf</directory>
<outputDirectory>conf</outputDirectory>
<includes>
<include>*</include>
</includes>
</fileSet>
<fileSet>
<directory>${project.basedir}/src/main/resources/webapp</directory>
<outputDirectory>bin/webapp</outputDirectory>
<includes>
<include>*</include>
</includes>
</fileSet>
</fileSets>
</assembly>
@@ -0,0 +1,321 @@
package timely.grafana.auth;
import java.io.File;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpContentDecompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.OpenSslServerContext;
import io.netty.handler.ssl.OpenSslServerSessionContext;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslProvider;
import io.netty.handler.ssl.util.SelfSignedCertificate;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.internal.SystemPropertyUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import timely.auth.AuthCache;
import timely.balancer.configuration.ClientSsl;
import timely.balancer.connection.http.HttpClientPool;
import timely.client.http.HttpClient;
import timely.configuration.ServerSsl;
import timely.grafana.auth.configuration.GrafanaAuthConfiguration;
import timely.grafana.auth.configuration.SpringBootstrap;
import timely.grafana.auth.netty.http.GrafanaRelayHandler;
import timely.grafana.auth.netty.http.GrafanaRequestDecoder;
import timely.netty.http.HttpStaticFileServerHandler;
import timely.netty.http.NonSslRedirectHandler;
import timely.netty.http.TimelyExceptionHandler;
import timely.netty.http.auth.X509LoginRequestHandler;
public class GrafanaAuth {
private static final Logger LOG = LoggerFactory.getLogger(GrafanaAuth.class);
private static final int EPOLL_MIN_MAJOR_VERSION = 2;
private static final int EPOLL_MIN_MINOR_VERSION = 6;
private static final int EPOLL_MIN_PATCH_VERSION = 32;
private static final String OS_NAME = "os.name";
private static final String OS_VERSION = "os.version";
protected static final CountDownLatch LATCH = new CountDownLatch(1);
static ConfigurableApplicationContext applicationContext;
private final GrafanaAuthConfiguration grafanaAuthConfig;
private EventLoopGroup httpWorkerGroup = null;
private EventLoopGroup httpBossGroup = null;
protected Channel httpChannelHandle = null;
protected volatile boolean shutdown = false;
public GrafanaAuth(GrafanaAuthConfiguration grafanaAuthConfig) throws Exception {
this.grafanaAuthConfig = grafanaAuthConfig;
}
protected static ConfigurableApplicationContext initializeConfiguration(String[] args) {
return new SpringApplicationBuilder(SpringBootstrap.class).web(WebApplicationType.NONE).run(args);
}
private void shutdownHook() {
final Runnable shutdownRunner = () -> {
if (!shutdown) {
shutdown();
}
};
final Thread hook = new Thread(shutdownRunner, "shutdown-hook-thread");
Runtime.getRuntime().addShutdownHook(hook);
}
public void shutdown() {
List<ChannelFuture> channelFutures = new ArrayList<>();
if (httpChannelHandle != null) {
LOG.info("Closing httpChannelHandle");
channelFutures.add(httpChannelHandle.close());
}
// wait for the channels to shutdown
channelFutures.forEach(f -> {
try {
f.get();
} catch (final Exception e) {
LOG.error("Error while shutting down channel: {}", f.channel().config());
LOG.error("{}", e.getMessage());
e.printStackTrace();
}
});
Integer quietPeriod = grafanaAuthConfig.getShutdownQuietPeriod();
List<Future<?>> groupFutures = new ArrayList<>();
if (httpBossGroup != null) {
LOG.info("Shutting down httpBossGroup");
groupFutures.add(httpBossGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
}
if (httpWorkerGroup != null) {
LOG.info("Shutting down httpWorkerGroup");
groupFutures.add(httpWorkerGroup.shutdownGracefully(quietPeriod, 10, TimeUnit.SECONDS));
}
groupFutures.parallelStream().forEach(f -> {
try {
f.get();
} catch (final Exception e) {
LOG.error("Error while shutting down group: {}", f.toString());
LOG.error("{}", e.getMessage());
e.printStackTrace();
}
});
if (applicationContext != null) {
LOG.info("Closing applicationContext");
applicationContext.close();
}
this.shutdown = true;
LOG.info("Server shut down.");
}
private static boolean useEpoll() {
// Should we just return true if this is Linux and if we get an error
// during Epoll
// setup handle it there?
final String os = SystemPropertyUtil.get(OS_NAME).toLowerCase().trim();
final String[] version = SystemPropertyUtil.get(OS_VERSION).toLowerCase().trim().split("\\.");
if (os.startsWith("linux") && version.length >= 3) {
final int major = Integer.parseInt(version[0]);
if (major > EPOLL_MIN_MAJOR_VERSION) {
return true;
} else if (major == EPOLL_MIN_MAJOR_VERSION) {
final int minor = Integer.parseInt(version[1]);
if (minor > EPOLL_MIN_MINOR_VERSION) {
return true;
} else if (minor == EPOLL_MIN_MINOR_VERSION) {
final int patch = Integer.parseInt(version[2].substring(0, 2));
return patch >= EPOLL_MIN_PATCH_VERSION;
} else {
return false;
}
} else {
return false;
}
} else {
return false;
}
}
protected SslContext createSSLContext(GrafanaAuthConfiguration config) throws Exception {
ServerSsl sslCfg = config.getSecurity().getServerSsl();
Boolean generate = sslCfg.isUseGeneratedKeypair();
SslContextBuilder ssl;
if (generate) {
LOG.warn("Using generated self signed server certificate");
Date begin = new Date();
Date end = new Date(begin.getTime() + 86400000);
SelfSignedCertificate ssc = new SelfSignedCertificate("localhost", begin, end);
ssl = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey());
} else {
String cert = sslCfg.getCertificateFile();
String key = sslCfg.getKeyFile();
String keyPass = sslCfg.getKeyPassword();
if (null == cert || null == key) {
throw new IllegalArgumentException("Check your SSL properties, something is wrong.");
}
ssl = SslContextBuilder.forServer(new File(cert), new File(key), keyPass);
}
ssl.ciphers(sslCfg.getUseCiphers());
// Can't set to REQUIRE because the CORS pre-flight requests will fail.
ssl.clientAuth(ClientAuth.OPTIONAL);
Boolean useOpenSSL = sslCfg.isUseOpenssl();
if (useOpenSSL) {
ssl.sslProvider(SslProvider.OPENSSL);
} else {
ssl.sslProvider(SslProvider.JDK);
}
String trustStore = sslCfg.getTrustStoreFile();
if (null != trustStore) {
if (!trustStore.isEmpty()) {
ssl.trustManager(new File(trustStore));
}
}
return ssl.build();
}
protected SSLContext createSSLClientContext(GrafanaAuthConfiguration config) throws Exception {
ClientSsl ssl = config.getSecurity().getClientSsl();
return HttpClient.getSSLContext(ssl.getTrustStoreFile(), ssl.getTrustStoreType(), ssl.getTrustStorePassword(),
ssl.getKeyFile(), ssl.getKeyType(), ssl.getKeyPassword());
}
public void run() throws Exception {
AuthCache.configure(grafanaAuthConfig.getSecurity());
final boolean useEpoll = useEpoll();
Class<? extends ServerSocketChannel> channelClass;
if (useEpoll) {
httpWorkerGroup = new EpollEventLoopGroup();
httpBossGroup = new EpollEventLoopGroup();
channelClass = EpollServerSocketChannel.class;
} else {
httpWorkerGroup = new NioEventLoopGroup();
httpBossGroup = new NioEventLoopGroup();
channelClass = NioServerSocketChannel.class;
}
LOG.info("Using channel class {}", channelClass.getSimpleName());
final int httpPort = grafanaAuthConfig.getHttp().getPort();
final String httpIp = grafanaAuthConfig.getHttp().getIp();
SslContext sslCtx = createSSLContext(grafanaAuthConfig);
if (sslCtx instanceof OpenSslServerContext) {
OpenSslServerContext openssl = (OpenSslServerContext) sslCtx;
String application = "GrafanaAuth_" + httpPort;
OpenSslServerSessionContext opensslCtx = openssl.sessionContext();
opensslCtx.setSessionCacheEnabled(true);
opensslCtx.setSessionCacheSize(128);
opensslCtx.setSessionIdContext(application.getBytes(StandardCharsets.UTF_8));
opensslCtx.setSessionTimeout(grafanaAuthConfig.getSecurity().getSessionMaxAge());
}
SSLContext clientSSLContext = createSSLClientContext(grafanaAuthConfig);
final ServerBootstrap httpServer = new ServerBootstrap();
httpServer.group(httpBossGroup, httpWorkerGroup);
httpServer.channel(channelClass);
httpServer.handler(new LoggingHandler());
HttpClientPool httpClientPool = new HttpClientPool(grafanaAuthConfig.getSecurity(), grafanaAuthConfig.getHttp(),
clientSSLContext);
httpServer.childHandler(setupHttpChannel(grafanaAuthConfig, sslCtx, httpClientPool));
httpServer.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
httpServer.option(ChannelOption.SO_BACKLOG, 128);
httpServer.option(ChannelOption.SO_KEEPALIVE, true);
httpChannelHandle = httpServer.bind(httpIp, httpPort).sync().channel();
final String httpAddress = ((InetSocketAddress) httpChannelHandle.localAddress()).getAddress().getHostAddress();
shutdownHook();
LOG.info("Server started. Listening on {}:{} for HTTP traffic", httpAddress, httpPort);
}
public static void main(String[] args) throws Exception {
GrafanaAuth.applicationContext = GrafanaAuth.initializeConfiguration(args);
GrafanaAuthConfiguration balancerConf = applicationContext.getBean(GrafanaAuthConfiguration.class);
GrafanaAuth b = new GrafanaAuth(balancerConf);
try {
b.run();
LATCH.await();
} catch (final InterruptedException e) {
LOG.info("Server shutting down.");
} catch (Exception e) {
LOG.error("Error running server.", e);
} finally {
try {
b.shutdown();
} catch (Exception e) {
System.exit(1);
}
}
}
protected ChannelHandler setupHttpChannel(GrafanaAuthConfiguration config, SslContext sslCtx,
HttpClientPool httpClientPool) {
return new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("ssl", new NonSslRedirectHandler(config.getHttp(), sslCtx));
ch.pipeline().addLast("encoder", new HttpResponseEncoder());
ch.pipeline().addLast("decoder", new HttpRequestDecoder());
ch.pipeline().addLast("compressor", new HttpContentCompressor());
ch.pipeline().addLast("decompressor", new HttpContentDecompressor());
// high maximum contentLength so that grafana snapshots can be delivered
// might not be necessary if inbound chunking (while proxying) is handled
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(2097152));
ch.pipeline().addLast("chunker", new ChunkedWriteHandler());
ch.pipeline().addLast("grafanaDecoder",
new GrafanaRequestDecoder(config.getSecurity(), config.getHttp()));
ch.pipeline().addLast("fileServer", new HttpStaticFileServerHandler());
ch.pipeline().addLast("login", new X509LoginRequestHandler(config.getSecurity(), config.getHttp()));
ch.pipeline().addLast("httpRelay", new GrafanaRelayHandler(config, httpClientPool));
ch.pipeline().addLast("error", new TimelyExceptionHandler());
}
};
}
}
@@ -0,0 +1,32 @@
package timely.grafana.auth.configuration;
public class Grafana {
private String host = "localhost";
private Integer port = 3000;
private String protocol = "http";
public void setHost(String host) {
this.host = host;
}
public String getHost() {
return host;
}
public void setPort(Integer port) {
this.port = port;
}
public Integer getPort() {
return port;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public String getProtocol() {
return protocol;
}
}
@@ -0,0 +1,56 @@
package timely.grafana.auth.configuration;
import javax.validation.Valid;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import timely.balancer.configuration.BalancerHttp;
import timely.balancer.configuration.BalancerSecurity;
@Validated
@Component
@ConfigurationProperties(prefix = "grafana-auth")
public class GrafanaAuthConfiguration {
@Valid
@NestedConfigurationProperty
private BalancerHttp http = new BalancerHttp();
@Valid
@NestedConfigurationProperty
private BalancerSecurity security = new BalancerSecurity();
@Valid
@NestedConfigurationProperty
private Grafana grafana = new Grafana();
private Integer shutdownQuietPeriod = 5;
public BalancerSecurity getSecurity() {
return security;
}
public BalancerHttp getHttp() {
return http;
}
public Grafana getGrafana() {
return grafana;
}
/**
* Time to wait (in seconds) for connections to finish and to make sure no new
* connections happen before shutting down Netty event loop groups.
*
* @return
*/
public int getShutdownQuietPeriod() {
return this.shutdownQuietPeriod;
}
public void setShutdownQuietPeriod(Integer quietPeriod) {
this.shutdownQuietPeriod = quietPeriod;
}
}
@@ -0,0 +1,7 @@
package timely.grafana.auth.configuration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackageClasses = { SpringBootstrap.class })
public class SpringBootstrap {
}
@@ -0,0 +1,234 @@
package timely.grafana.auth.netty.http;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
import java.io.ByteArrayOutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Multimap;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.ReferenceCountUtil;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StreamUtils;
import timely.api.request.timeseries.HttpRequest;
import timely.api.response.TimelyException;
import timely.auth.TimelyPrincipal;
import timely.auth.TimelyUser;
import timely.auth.util.DnUtils;
import timely.auth.util.HttpHeaderUtils;
import timely.balancer.connection.TimelyBalancedHost;
import timely.balancer.connection.http.HttpClientPool;
import timely.grafana.auth.configuration.GrafanaAuthConfiguration;
import timely.grafana.request.GrafanaRequest;
import timely.netty.http.TimelyHttpHandler;
import timely.netty.http.auth.TimelyAuthenticationToken;
public class GrafanaRelayHandler extends SimpleChannelInboundHandler<HttpRequest> implements TimelyHttpHandler {
private static final Logger LOG = LoggerFactory.getLogger(GrafanaRelayHandler.class);
private final HttpClientPool httpClientPool;
private GrafanaAuthConfiguration config;
public GrafanaRelayHandler(GrafanaAuthConfiguration config, HttpClientPool httpClientPool) {
this.config = config;
this.httpClientPool = httpClientPool;
}
@Override
public void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) throws Exception {
CloseableHttpClient client = null;
CloseableHttpResponse relayedResponse = null;
FullHttpRequest request = null;
GrafanaRequest grafanaRequest;
TimelyBalancedHost k = null;
String relayURI = null;
try {
try {
request = msg.getHttpRequest();
String originalURI = request.getUri();
originalURI = encodeURI(originalURI);
if (msg instanceof GrafanaRequest) {
grafanaRequest = (GrafanaRequest) msg;
} else {
throw new UnsupportedOperationException("Request not supported");
}
TimelyAuthenticationToken token = grafanaRequest.getToken();
if (token == null) {
throw new IllegalStateException("No token on grafana request");
}
String protocol = config.getGrafana().getProtocol();
String host = config.getGrafana().getHost();
Integer port = config.getGrafana().getPort();
k = TimelyBalancedHost.of(host, -1, port, -1, -1);
client = httpClientPool.borrowObject(k);
Multimap<String, String> headers = HttpHeaderUtils.toMultimap(request.headers());
// If incoming user sets these headers, do not relay them
headers.removeAll("X-WEBAUTH-USER");
headers.removeAll("X-WEBAUTH-NAME");
TimelyPrincipal principal = token.getTimelyPrincipal();
TimelyUser user = principal.getPrimaryUser();
String subjectDn = user.getDn().subjectDN();
String userName = DnUtils.getCommonName(subjectDn);
String loginName = DnUtils.getShortName(subjectDn);
headers.put("X-WEBAUTH-USER", loginName);
headers.put("X-WEBAUTH-NAME", userName);
List<Header> relayedHeaderList = new ArrayList<>();
for (Map.Entry<String, String> h : headers.entries()) {
if (!h.getKey().equals(CONTENT_LENGTH)) {
relayedHeaderList.add(new BasicHeader(h.getKey(), h.getValue()));
}
}
Header[] headerArray = new Header[relayedHeaderList.size()];
relayedHeaderList.toArray(headerArray);
relayURI = protocol + "://" + host + ":" + port + originalURI;
if (request.getMethod().equals(HttpMethod.HEAD)) {
HttpHead relayedRequest = new HttpHead(relayURI);
if (headerArray.length > 0) {
relayedRequest.setHeaders(headerArray);
}
relayedResponse = client.execute(relayedRequest);
} else if (request.getMethod().equals(HttpMethod.GET)) {
HttpGet relayedRequest = new HttpGet(relayURI);
if (headerArray.length > 0) {
relayedRequest.setHeaders(headerArray);
}
relayedResponse = client.execute(relayedRequest);
} else if (request.getMethod().equals(HttpMethod.POST)) {
HttpPost relayedRequest = new HttpPost(relayURI);
String content = request.content().toString(StandardCharsets.UTF_8);
StringEntity entity = new StringEntity(content);
relayedRequest.setEntity(entity);
if (headerArray.length > 0) {
relayedRequest.setHeaders(headerArray);
}
relayedResponse = client.execute(relayedRequest);
} else if (request.getMethod().equals(HttpMethod.PUT)) {
HttpPut relayedRequest = new HttpPut(relayURI);
String content = request.content().toString(StandardCharsets.UTF_8);
StringEntity entity = new StringEntity(content);
relayedRequest.setEntity(entity);
if (headerArray.length > 0) {
relayedRequest.setHeaders(headerArray);
}
relayedResponse = client.execute(relayedRequest);
} else if (request.getMethod().equals(HttpMethod.DELETE)) {
HttpDelete relayedRequest = new HttpDelete(relayURI);
if (headerArray.length > 0) {
relayedRequest.setHeaders(headerArray);
}
relayedResponse = client.execute(relayedRequest);
} else if (request.getMethod().equals(HttpMethod.OPTIONS)) {
HttpOptions relayedRequest = new HttpOptions(relayURI);
if (headerArray.length > 0) {
relayedRequest.setHeaders(headerArray);
}
relayedResponse = client.execute(relayedRequest);
}
} catch (Exception e) {
String message = e.getMessage();
if (message == null) {
LOG.error("", e);
} else {
LOG.error(message, e);
}
this.sendHttpError(ctx, new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(),
e.getMessage(), e.getLocalizedMessage(), e));
return;
} finally {
if (request != null && request instanceof AutoCloseable) {
ReferenceCountUtil.release(request);
}
}
FullHttpResponse response = null;
if (relayedResponse == null) {
this.sendHttpError(ctx, new TimelyException(HttpResponseStatus.METHOD_NOT_ALLOWED.code(),
"Method not allowed", "Method not allowed"));
} else {
LOG.debug("RequestURL:{} ResponseCode:{}", relayURI, relayedResponse.getStatusLine().getStatusCode());
HttpEntity httpEntity = relayedResponse.getEntity();
if (httpEntity == null) {
response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.valueOf(relayedResponse.getStatusLine().getStatusCode()));
} else {
ByteArrayOutputStream baos = new MyByteArrayOutputStream();
StreamUtils.copy(httpEntity.getContent(), baos);
response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.valueOf(relayedResponse.getStatusLine().getStatusCode()),
Unpooled.copiedBuffer(baos.toByteArray()));
response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
}
for (Header h : relayedResponse.getAllHeaders()) {
response.headers().add(h.getName(), h.getValue());
}
sendResponse(ctx, response);
}
} finally {
if (relayedResponse != null) {
relayedResponse.close();
}
if (client != null && k != null) {
httpClientPool.returnObject(k, client);
} else {
LOG.error("NOT RETURNING CONNECTION! " + msg.getHttpRequest().getUri());
}
}
}
static public class MyByteArrayOutputStream extends ByteArrayOutputStream {
public MyByteArrayOutputStream() {
}
public MyByteArrayOutputStream(int size) {
super(size);
}
public int getCount() {
return count;
}
public byte[] getBuf() {
return buf;
}
}
static public String encodeURI(String s) {
try {
s = s.replaceAll("\\{", URLEncoder.encode("{", "UTF-8"));
s = s.replaceAll("\\}", URLEncoder.encode("}", "UTF-8"));
} catch (Exception e) {
}
return s;
}
}
@@ -0,0 +1,133 @@
package timely.grafana.auth.netty.http;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.List;
import com.google.common.collect.Multimap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders.Names;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.api.request.AuthenticatedRequest;
import timely.api.request.timeseries.HttpRequest;
import timely.api.response.StrictTransportResponse;
import timely.api.response.TimelyException;
import timely.auth.AuthCache;
import timely.auth.AuthenticationService;
import timely.auth.SubjectIssuerDNPair;
import timely.auth.TimelyPrincipal;
import timely.auth.util.HttpHeaderUtils;
import timely.configuration.Http;
import timely.configuration.Security;
import timely.grafana.request.GrafanaRequest;
import timely.netty.Constants;
import timely.netty.http.TimelyHttpHandler;
import timely.netty.http.auth.TimelyAuthenticationToken;
public class GrafanaRequestDecoder extends MessageToMessageDecoder<FullHttpRequest> implements TimelyHttpHandler {
private static final Logger LOG = LoggerFactory.getLogger(GrafanaRequestDecoder.class);
private static final String LOG_RECEIVED_REQUEST = "Received HTTP request {}";
private static final String LOG_PARSED_REQUEST = "Parsed request {}";
private final Security security;
private final String nonSecureRedirectAddress;
public GrafanaRequestDecoder(Security security, Http http) {
this.security = security;
this.nonSecureRedirectAddress = http.getRedirectPath();
}
public static String getSessionId(FullHttpRequest msg) {
Multimap<String, String> headers = HttpHeaderUtils.toMultimap(msg.headers());
Collection<String> cookies = headers.get(Names.COOKIE);
final StringBuilder buf = new StringBuilder();
cookies.forEach(h -> {
ServerCookieDecoder.STRICT.decode(h).forEach(c -> {
if (c.name().equals(Constants.COOKIE_NAME)) {
if (buf.length() == 0) {
buf.append(c.value());
}
}
});
});
if (buf.length() == 0) {
return null;
} else {
return buf.toString();
}
}
@Override
protected void decode(ChannelHandlerContext ctx, FullHttpRequest msg, List<Object> out) throws Exception {
LOG.trace(LOG_RECEIVED_REQUEST, msg);
final String uri = msg.getUri();
final QueryStringDecoder decoder = new QueryStringDecoder(uri);
if (decoder.path().equals(nonSecureRedirectAddress)) {
out.add(new StrictTransportResponse());
return;
}
final String sessionId = getSessionId(msg);
LOG.trace("SessionID: " + sessionId);
HttpRequest request;
try {
request = new GrafanaRequest();
Multimap<String, String> headers = HttpHeaderUtils.toMultimap(msg.headers());
((AuthenticatedRequest) request).addHeaders(headers);
X509Certificate clientCert = AuthenticationService.getClientCertificate(ctx);
TimelyAuthenticationToken token;
if (StringUtils.isNotBlank(sessionId)) {
TimelyPrincipal principal;
((AuthenticatedRequest) request).setSessionId(sessionId);
if (AuthCache.containsKey(sessionId)) {
principal = AuthCache.get(sessionId);
} else {
principal = TimelyPrincipal.anonymousPrincipal();
}
SubjectIssuerDNPair dn = principal.getPrimaryUser().getDn();
token = AuthenticationService.getAuthenticationToken(null, dn.subjectDN(), dn.issuerDN());
} else if (clientCert != null) {
token = AuthenticationService.getAuthenticationToken(clientCert, headers);
} else {
SubjectIssuerDNPair dn = TimelyPrincipal.anonymousPrincipal().getPrimaryUser().getDn();
token = AuthenticationService.getAuthenticationToken(null, dn.subjectDN(), dn.issuerDN());
}
((AuthenticatedRequest) request).setToken(token);
request.setHttpRequest(msg.copy());
LOG.trace(LOG_PARSED_REQUEST, request);
request.validate();
out.add(request);
} catch (UnsupportedOperationException | NullPointerException e) {
// Return the original http request to route to the static file
// server
msg.retain();
out.add(msg);
return;
}
try {
try {
AuthenticationService.enforceAccess((AuthenticatedRequest) request);
} catch (TimelyException e) {
if (!security.isAllowAnonymousHttpAccess()) {
throw e;
}
}
} catch (Exception e) {
out.clear();
throw e;
}
}
}
@@ -0,0 +1,32 @@
package timely.grafana.request;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.QueryStringDecoder;
import timely.api.annotation.Http;
import timely.api.request.AuthenticatedRequest;
import timely.api.request.HttpGetRequest;
import timely.api.request.HttpPostRequest;
@Http(path = "/")
public class GrafanaRequest extends AuthenticatedRequest implements HttpGetRequest, HttpPostRequest {
private FullHttpRequest httpRequest = null;
@Override
public HttpGetRequest parseQueryParameters(QueryStringDecoder decoder) throws Exception {
return new GrafanaRequest();
}
@Override
public HttpPostRequest parseBody(String content) throws Exception {
return new GrafanaRequest();
}
public void setHttpRequest(FullHttpRequest httpRequest) {
this.httpRequest = httpRequest;
}
public FullHttpRequest getHttpRequest() {
return httpRequest;
}
}
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<FindBugsFilter>
<Match>
<Class name="timely.grafana.auth.netty.http.GrafanaRelayHandler$MyByteArrayOutputStream" />
<Method name="getBuf" />
<Bug pattern="EI_EXPOSE_REP" />
</Match>
</FindBugsFilter>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>timely</groupId> <groupId>timely</groupId>
<artifactId>timely</artifactId> <artifactId>timely</artifactId>
<version>1.0.1-SNAPSHOT</version> <version>1.2.3</version>
</parent> </parent>
<artifactId>timely-app</artifactId> <artifactId>timely-app</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
+2 -2
View File
@@ -23,8 +23,8 @@
{"name": "Project site", "url": "https://github.com/NationalSecurityAgency/timely"}, {"name": "Project site", "url": "https://github.com/NationalSecurityAgency/timely"},
{"name": "License & Terms", "url": "https://raw.githubusercontent.com/NationalSecurityAgency/timely/master/LICENSE"} {"name": "License & Terms", "url": "https://raw.githubusercontent.com/NationalSecurityAgency/timely/master/LICENSE"}
], ],
"version": "0.0.6-SNAPSHOT", "version": "1.2.3",
"updated": "2017-08-11" "updated": "2019-04-18"
}, },
"includes": [ "includes": [
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "timely-app", "name": "timely-app",
"private": true, "private": true,
"version": "0.0.2", "version": "1.2.3",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
+2 -2
View File
@@ -23,8 +23,8 @@
{"name": "Project site", "url": "https://github.com/NationalSecurityAgency/timely"}, {"name": "Project site", "url": "https://github.com/NationalSecurityAgency/timely"},
{"name": "License & Terms", "url": "https://raw.githubusercontent.com/NationalSecurityAgency/timely/master/LICENSE"} {"name": "License & Terms", "url": "https://raw.githubusercontent.com/NationalSecurityAgency/timely/master/LICENSE"}
], ],
"version": "0.0.6-SNAPSHOT", "version": "1.2.3",
"updated": "2017-08-11" "updated": "2019-04-18"
}, },
"includes": [ "includes": [
+41 -26
View File
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>timely</groupId> <groupId>timely</groupId>
<artifactId>timely</artifactId> <artifactId>timely</artifactId>
<version>1.0.1-SNAPSHOT</version> <version>1.2.3</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>Timely</name> <name>Timely</name>
<description>A secure and scalable time series database based on Apache Accumulo</description> <description>A secure and scalable time series database based on Apache Accumulo</description>
@@ -20,6 +20,7 @@
<module>grafana</module> <module>grafana</module>
<module>analytics</module> <module>analytics</module>
<module>balancer</module> <module>balancer</module>
<module>grafana-auth</module>
</modules> </modules>
<scm> <scm>
<connection>scm:git:https://github.com/NationalSecurityAgency/timely.git</connection> <connection>scm:git:https://github.com/NationalSecurityAgency/timely.git</connection>
@@ -40,33 +41,31 @@
<os.detection.classifierWithLikes>fedora</os.detection.classifierWithLikes> <os.detection.classifierWithLikes>fedora</os.detection.classifierWithLikes>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<version.accumulo>1.7.4</version.accumulo> <version.accumulo>1.9.2</version.accumulo>
<version.buildnumber-maven>1.4</version.buildnumber-maven> <version.buildnumber-maven>1.4</version.buildnumber-maven>
<version.caffeine>2.3.3</version.caffeine> <version.caffeine>2.6.2</version.caffeine>
<version.collectd>5.5.1</version.collectd> <version.collectd>5.7.1</version.collectd>
<version.commons-collections4>4.1</version.commons-collections4> <version.commons-collections4>4.3</version.commons-collections4>
<version.commons-configuration>1.10</version.commons-configuration> <version.commons-lang3>3.8.1</version.commons-lang3>
<version.commons-io>2.6</version.commons-io> <version.commons-pool2>2.6.1</version.commons-pool2>
<version.commons-lang3>3.5</version.commons-lang3> <version.curator>4.1.0</version.curator>
<version.commons-pool2>2.6.0</version.commons-pool2> <version.disruptor>3.4.2</version.disruptor>
<version.curator>2.11.1</version.curator>
<version.disruptor>3.3.6</version.disruptor>
<version.eclipse-lifecycle>1.0.0</version.eclipse-lifecycle> <version.eclipse-lifecycle>1.0.0</version.eclipse-lifecycle>
<version.fast-classpath-scanner>2.0.5</version.fast-classpath-scanner> <version.fast-classpath-scanner>3.1.13</version.fast-classpath-scanner>
<version.flink>1.1.4</version.flink> <version.flink>1.1.4</version.flink>
<version.google-flatbuffers>1.10.0</version.google-flatbuffers> <version.google-flatbuffers>1.10.0</version.google-flatbuffers>
<version.guava>19.0</version.guava> <version.guava>27.1-jre</version.guava>
<version.hadoop>2.7.3</version.hadoop> <version.hadoop>2.7.3</version.hadoop>
<version.httpcomponents>4.5.2</version.httpcomponents> <version.httpcomponents>4.5.6</version.httpcomponents>
<version.impsort>1.2.0</version.impsort> <version.impsort>1.2.0</version.impsort>
<version.jackson>2.9.7</version.jackson> <version.jackson>2.9.8</version.jackson>
<version.jacoco>0.7.6.201602180812</version.jacoco> <version.jacoco>0.7.6.201602180812</version.jacoco>
<version.javacsv>2.0</version.javacsv> <version.javacsv>2.0</version.javacsv>
<version.javassist>3.21.0-GA</version.javassist> <version.javassist>3.24.1-GA</version.javassist>
<version.jcip-annotations>1.0</version.jcip-annotations> <version.jcip-annotations>1.0</version.jcip-annotations>
<version.jsoup>1.9.2</version.jsoup> <version.jsoup>1.11.3</version.jsoup>
<version.junit>4.12</version.junit> <version.junit>4.12</version.junit>
<version.log4j>2.6.2</version.log4j> <version.log4j>2.11.2</version.log4j>
<version.maven-assembly>2.6</version.maven-assembly> <version.maven-assembly>2.6</version.maven-assembly>
<version.maven-build-helper>1.12</version.maven-build-helper> <version.maven-build-helper>1.12</version.maven-build-helper>
<version.maven-compiler>3.3</version.maven-compiler> <version.maven-compiler>3.3</version.maven-compiler>
@@ -76,14 +75,14 @@
<version.maven-rpm>2.1.4</version.maven-rpm> <version.maven-rpm>2.1.4</version.maven-rpm>
<version.maven-shade>2.4.2</version.maven-shade> <version.maven-shade>2.4.2</version.maven-shade>
<version.maven-surefire>2.19.1</version.maven-surefire> <version.maven-surefire>2.19.1</version.maven-surefire>
<version.netty>4.0.56.Final</version.netty> <version.netty>4.1.35.Final</version.netty>
<version.netty-tcnative>2.0.7.Final</version.netty-tcnative> <version.netty-tcnative>2.0.25.Final</version.netty-tcnative>
<version.os-maven>1.5.0.Final</version.os-maven> <version.os-maven>1.5.0.Final</version.os-maven>
<version.powermock>1.6.4</version.powermock> <version.powermock>1.6.4</version.powermock>
<version.slf4j>1.7.21</version.slf4j> <version.slf4j>1.7.26</version.slf4j>
<version.spotbugs-maven>3.1.7</version.spotbugs-maven> <version.spotbugs-maven>3.1.7</version.spotbugs-maven>
<version.spring-boot>1.4.1.RELEASE</version.spring-boot> <version.spring-boot>2.1.3.RELEASE</version.spring-boot>
<version.tyrus>1.13</version.tyrus> <version.tyrus>1.15</version.tyrus>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
@@ -102,6 +101,11 @@
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
<version>${version.jackson}</version> <version>${version.jackson}</version>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-guava</artifactId>
<version>${version.jackson}</version>
</dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.datatype</groupId> <groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId> <artifactId>jackson-datatype-jdk8</artifactId>
@@ -135,7 +139,7 @@
<dependency> <dependency>
<groupId>io.github.lukehutch</groupId> <groupId>io.github.lukehutch</groupId>
<artifactId>fast-classpath-scanner</artifactId> <artifactId>fast-classpath-scanner</artifactId>
<version>2.0.5</version> <version>${version.fast-classpath-scanner}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.netty</groupId> <groupId>io.netty</groupId>
@@ -151,6 +155,12 @@
<groupId>org.apache.accumulo</groupId> <groupId>org.apache.accumulo</groupId>
<artifactId>accumulo-core</artifactId> <artifactId>accumulo-core</artifactId>
<version>${version.accumulo}</version> <version>${version.accumulo}</version>
<exclusions>
<exclusion>
<groupId>io.netty</groupId>
<artifactId>netty</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.accumulo</groupId> <groupId>org.apache.accumulo</groupId>
@@ -304,6 +314,11 @@
<artifactId>collectd-plugin-parent</artifactId> <artifactId>collectd-plugin-parent</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>timely</groupId>
<artifactId>timely-balancer</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>timely</groupId> <groupId>timely</groupId>
<artifactId>timely-client</artifactId> <artifactId>timely-client</artifactId>
@@ -441,7 +456,7 @@
<artifactId>maven-failsafe-plugin</artifactId> <artifactId>maven-failsafe-plugin</artifactId>
<version>${version.maven-surefire}</version> <version>${version.maven-surefire}</version>
<configuration> <configuration>
<argLine>${failsafeArgLine} -Djava.library.path=${project.build.directory}/native/META-INF/native/libnetty-tcnative.so -Dzookeeper.sasl.client=false</argLine> <argLine>${failsafeArgLine} -Djava.library.path=${project.build.directory}/native/META-INF/native/libnetty_tcnative_linux_x86_64.so -Dzookeeper.sasl.client=false</argLine>
<includes> <includes>
<include>**/*.java</include> <include>**/*.java</include>
</includes> </includes>
@@ -484,7 +499,7 @@
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>${version.maven-surefire}</version> <version>${version.maven-surefire}</version>
<configuration> <configuration>
<argLine>${sureFireArgLine} -Djava.library.path=${project.build.directory}/native/META-INF/native/libnetty-tcnative.so -Dzookeeper.sasl.client=false</argLine> <argLine>${sureFireArgLine} -Djava.library.path=${project.build.directory}/native/META-INF/native/libnetty-tcnative_linux_x86_64.so -Dzookeeper.sasl.client=false</argLine>
<excludedGroups>timely.test.IntegrationTest</excludedGroups> <excludedGroups>timely.test.IntegrationTest</excludedGroups>
<redirectTestOutputToFile>true</redirectTestOutputToFile> <redirectTestOutputToFile>true</redirectTestOutputToFile>
</configuration> </configuration>
+4 -9
View File
@@ -8,11 +8,6 @@ else
TCNATIVE_SUFFIX="so" TCNATIVE_SUFFIX="so"
fi fi
# netty tcnative file reference
#netty-tcnative-1.1.33.Fork18-linux-x86_64-fedora.jar -> libnetty-tcnative.so
#netty-tcnative-1.1.33.Fork18-linux-x86_64.jar -> libnetty-tcnative.so
#netty-tcnative-1.1.33.Fork18-osx-x86_64.jar -> libnetty-tcnative.jnilib
THIS_DIR="${THIS_SCRIPT%/*}" THIS_DIR="${THIS_SCRIPT%/*}"
NATIVE_DIR="${THIS_DIR}/META-INF/native" NATIVE_DIR="${THIS_DIR}/META-INF/native"
BASE_DIR=${THIS_DIR}/.. BASE_DIR=${THIS_DIR}/..
@@ -32,12 +27,12 @@ fi
mkdir -p ${NATIVE_DIR} mkdir -p ${NATIVE_DIR}
pushd ${BASE_DIR}/bin pushd ${BASE_DIR}/bin
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative*.jar META-INF/native/libnetty_tcnative.${NATIVE_SUFFIX} $JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative-boringssl-static*.jar META-INF/native/libnetty_tcnative_linux_x86_64.${TCNATIVE_SUFFIX}
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-transport-native-epoll*.jar META-INF/native/libnetty_transport_native_epoll_x86_64.${NATIVE_SUFFIX} $JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-all*.jar META-INF/native/libnetty_transport_native_epoll_x86_64.${TCNATIVE_SUFFIX}
popd popd
export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*" export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*:${HADOOP_CONF_DIR}"
JVM_ARGS="-Xmx256m -Xms256m -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS}" JVM_ARGS="-Xmx256m -Xms256m -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS} -Dlog4j.configurationFile=${CONF_DIR}/log4j2-spring.xml"
JVM_ARGS="${JVM_ARGS} -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector" JVM_ARGS="${JVM_ARGS} -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
JVM_ARGS="${JVM_ARGS} -Djava.library.path=${NATIVE_DIR}" JVM_ARGS="${JVM_ARGS} -Djava.library.path=${NATIVE_DIR}"
+6 -10
View File
@@ -2,17 +2,12 @@
if [[ `uname` == "Darwin" ]]; then if [[ `uname` == "Darwin" ]]; then
THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0` THIS_SCRIPT=`python -c 'import os,sys; print os.path.realpath(sys.argv[1])' $0`
TCNATIVE_SUFFIX="tcnative.jnilib" TCNATIVE_SUFFIX="jnilib"
else else
THIS_SCRIPT=`readlink -f $0` THIS_SCRIPT=`readlink -f $0`
TCNATIVE_SUFFIX="tcnative.so" TCNATIVE_SUFFIX="so"
fi fi
# netty tcnative file reference
#netty-tcnative-1.1.33.Fork18-linux-x86_64-fedora.jar -> libnetty-tcnative.so
#netty-tcnative-1.1.33.Fork18-linux-x86_64.jar -> libnetty-tcnative.so
#netty-tcnative-1.1.33.Fork18-osx-x86_64.jar -> libnetty-tcnative.jnilib
THIS_DIR="${THIS_SCRIPT%/*}" THIS_DIR="${THIS_SCRIPT%/*}"
NATIVE_DIR="${THIS_DIR}/META-INF/native" NATIVE_DIR="${THIS_DIR}/META-INF/native"
BASE_DIR=${THIS_DIR}/.. BASE_DIR=${THIS_DIR}/..
@@ -32,11 +27,12 @@ fi
mkdir -p ${NATIVE_DIR} mkdir -p ${NATIVE_DIR}
pushd ${BASE_DIR}/bin pushd ${BASE_DIR}/bin
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative*.jar META-INF/native/libnetty-${TCNATIVE_SUFFIX} $JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-tcnative-boringssl-static*.jar META-INF/native/libnetty_tcnative_linux_x86_64.${TCNATIVE_SUFFIX}
$JAVA_HOME/bin/jar xf ${LIB_DIR}/netty-all*.jar META-INF/native/libnetty_transport_native_epoll_x86_64.${TCNATIVE_SUFFIX}
popd popd
export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*" export CLASSPATH="${CONF_DIR}:${LIB_DIR}/*:${HADOOP_CONF_DIR}"
JVM_ARGS="-Xmx256m -Xms256m -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS}" JVM_ARGS="-Xmx256m -Xms256m -Dio.netty.eventLoopThreads=${NUM_SERVER_THREADS} -Dlog4j.configurationFile=${CONF_DIR}/log4j2-spring.xml"
JVM_ARGS="${JVM_ARGS} -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector" JVM_ARGS="${JVM_ARGS} -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"
JVM_ARGS="${JVM_ARGS} -Djava.library.path=${NATIVE_DIR}" JVM_ARGS="${JVM_ARGS} -Djava.library.path=${NATIVE_DIR}"
# for debugging # for debugging
+5 -5
View File
@@ -26,12 +26,12 @@
</Console> </Console>
</Appenders> </Appenders>
<Loggers> <Loggers>
<Logger name="timely.subscription" level="trace" additivity="false"> <Root level="debug" includeLocation="false">
<AppenderRef ref="Subscription" />
</Logger>
<Root level="trace" includeLocation="false">
<AppenderRef ref="Console" level="warn" /> <AppenderRef ref="Console" level="warn" />
<AppenderRef ref="Rolling" level="trace" /> <AppenderRef ref="Rolling" level="debug" />
</Root> </Root>
<Logger name="timely" additivity="false">
<AppenderRef ref="Console" level="debug" />
</Logger>
</Loggers> </Loggers>
</Configuration> </Configuration>
+32 -10
View File
@@ -15,15 +15,27 @@
<beans:bean id="timelyUserDetailsService" class="timely.auth.UserDetailsService"> <beans:bean id="timelyUserDetailsService" class="timely.auth.UserDetailsService">
<beans:property name="users"> <beans:property name="users">
<util:map key-type="java.lang.String"> <util:list>
<beans:entry key="example.com"> <beans:bean class="timely.auth.TimelyUser">
<util:list> <beans:constructor-arg name="subjectDn">
<beans:value>D</beans:value> <beans:value>CN=example.com</beans:value>
<beans:value>E</beans:value> </beans:constructor-arg>
<beans:value>F</beans:value> <beans:constructor-arg name="auths">
</util:list> <util:list>
</beans:entry> <beans:value>D</beans:value>
</util:map> <beans:value>E</beans:value>
<beans:value>F</beans:value>
</util:list>
</beans:constructor-arg>
<beans:constructor-arg name="roles">
<util:list>
<beans:value>G</beans:value>
<beans:value>H</beans:value>
<beans:value>I</beans:value>
</util:list>
</beans:constructor-arg>
</beans:bean>
</util:list>
</beans:property> </beans:property>
</beans:bean> </beans:bean>
@@ -35,8 +47,11 @@
<user name="test" password="test1" authorities="A,B,C" /> <user name="test" password="test1" authorities="A,B,C" />
</user-service> </user-service>
<beans:bean id="UserNamePasswordAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> <beans:bean id="noOpPasswordEncoder" class="org.springframework.security.crypto.password.NoOpPasswordEncoder"/>
<beans:bean id="UserNamePasswordAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="usernamePasswordService"/> <beans:property name="userDetailsService" ref="usernamePasswordService"/>
<beans:property name="passwordEncoder" ref="noOpPasswordEncoder"/>
</beans:bean> </beans:bean>
<authentication-manager alias="authenticationManager"> <authentication-manager alias="authenticationManager">
@@ -46,4 +61,11 @@
<authentication-provider ref="UserNamePasswordAuthenticationProvider" /> <authentication-provider ref="UserNamePasswordAuthenticationProvider" />
</authentication-manager> </authentication-manager>
<util:list id="requiredRoles" value-type="java.lang.String">
<beans:value></beans:value>
</util:list>
<util:list id="requiredAuths" value-type="java.lang.String">
<beans:value></beans:value>
</util:list>
</beans:beans> </beans:beans>
+7 -4
View File
@@ -18,7 +18,10 @@ timely:
max-downsample-memory: 10000000 max-downsample-memory: 10000000
security: security:
session-max-age: 86400 session-max-age: 86400
allow-anonymous-access: true cache-expiration-minutes: 60
cache-refresh-minutes: 5
allow-anonymous-http-access: true
allow-anonymous-ws-access: true
ssl: ssl:
certificate-file: /timely-server-src/server/pki/timely.csr certificate-file: /timely-server-src/server/pki/timely.csr
key-file: /timely-server-src/server/pki/timely-pkcs8.key key-file: /timely-server-src/server/pki/timely-pkcs8.key
@@ -49,9 +52,9 @@ timely:
timeout: 60 timeout: 60
subscription-lag: 120 subscription-lag: 120
meta-cache: meta-cache:
expiration-minutes: 60 expiration-minutes: 1440
initial-capacity: 2000 cache-refresh-minutes: 60
max-capacity: 10000 max-tag-values: 100
visibility-cache: visibility-cache:
expiration-minutes: 60 expiration-minutes: 60
initial-capacity: 2000 initial-capacity: 2000
+7 -4
View File
@@ -18,7 +18,10 @@ timely:
max-downsample-memory: 10000000 max-downsample-memory: 10000000
security: security:
session-max-age: 86400 session-max-age: 86400
allow-anonymous-access: true cache-expiration-minutes: 60
cache-refresh-minutes: 5
allow-anonymous-http-access: true
allow-anonymous-ws-access: true
ssl: ssl:
certificate-file: certificate-file:
key-file: key-file:
@@ -53,9 +56,9 @@ timely:
scanner-read-ahead: 1 scanner-read-ahead: 1
subscription-batch-size: 5000 subscription-batch-size: 5000
meta-cache: meta-cache:
expiration-minutes: 60 expiration-minutes: 1440
initial-capacity: 2000 cache-refresh-minutes: 60
max-capacity: 10000 max-tag-values: 100
visibility-cache: visibility-cache:
expiration-minutes: 60 expiration-minutes: 60
initial-capacity: 2000 initial-capacity: 2000
+24 -21
View File
@@ -4,14 +4,15 @@ timely:
metric-age-off-days: metric-age-off-days:
default: 7 default: 7
cache: cache:
enabled: enabled: true
age-off-hours: age-off-hours:
default: 30 default: 26
metrics-report-ignored-tags: metrics-report-ignored-tags:
defaultVisibility:
accumulo: accumulo:
instance-name: instance-name: accumulo
zookeepers: zookeepers: localhost:2181
username: username: root
password: password:
write: write:
latency: 5s latency: 5s
@@ -22,23 +23,26 @@ timely:
max-downsample-memory: 10000000 max-downsample-memory: 10000000
security: security:
session-max-age: 86400 session-max-age: 86400
allow-anonymous-access: false cache-expiration-minutes: 60
ssl: cache-refresh-minutes: 5
allow-anonymous-http-access: true
allow-anonymous-ws-access: true
serverSsl:
certificate-file: certificate-file:
key-file: key-file:
key-password: key-password:
use-generated-keypair: false use-generated-keypair: true
trust-store-file: trust-store-file:
use-openssl: true use-openssl: true
#use-ciphers: #use-ciphers:
server: server:
ip: ip: 0.0.0.0
tcp-port: tcp-port: 4242
udp-port: udp-port: 4542
http: http:
host: host: localhost
ip: ip: 0.0.0.0
port: port: 4243
redirect-path: /secure-me redirect-path: /secure-me
strict-transport-max-age: 608400 strict-transport-max-age: 608400
cors: cors:
@@ -49,8 +53,8 @@ timely:
allowed-methods: DELETE,GET,HEAD,OPTIONS,PUT,POST allowed-methods: DELETE,GET,HEAD,OPTIONS,PUT,POST
allowed-headers: content-type allowed-headers: content-type
websocket: websocket:
ip: ip: 0.0.0.0
port: port: 4244
timeout: 60 timeout: 60
subscription-lag: 120 subscription-lag: 120
scanner-batch-size: 10000 scanner-batch-size: 10000
@@ -58,10 +62,9 @@ timely:
scanner-read-ahead: 1 scanner-read-ahead: 1
subscription-batch-size: 5000 subscription-batch-size: 5000
meta-cache: meta-cache:
expiration-minutes: 60 expiration-minutes: 1440
initial-capacity: 2000 cache-refresh-minutes: 60
max-capacity: 10000 max-tag-values: 100
visibility-cache: visibility-cache:
expiration-minutes: 60 expiration-minutes: 60
initial-capacity: 2000
max-capacity: 10000
+7 -38
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>timely</groupId> <groupId>timely</groupId>
<artifactId>timely</artifactId> <artifactId>timely</artifactId>
<version>1.0.1-SNAPSHOT</version> <version>1.2.3</version>
</parent> </parent>
<artifactId>timely-server</artifactId> <artifactId>timely-server</artifactId>
<dependencies> <dependencies>
@@ -20,6 +20,10 @@
<groupId>com.fasterxml.jackson.core</groupId> <groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId> <artifactId>jackson-databind</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-guava</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.datatype</groupId> <groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId> <artifactId>jackson-datatype-jdk8</artifactId>
@@ -145,9 +149,8 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>io.netty</groupId> <groupId>io.netty</groupId>
<artifactId>netty-tcnative</artifactId> <artifactId>netty-tcnative-boringssl-static</artifactId>
<version>${version.netty-tcnative}</version> <version>${version.netty-tcnative}</version>
<classifier>${os.detected.classifier}</classifier>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency> <dependency>
@@ -236,9 +239,8 @@
<artifactItems> <artifactItems>
<artifactItem> <artifactItem>
<groupId>io.netty</groupId> <groupId>io.netty</groupId>
<artifactId>netty-tcnative</artifactId> <artifactId>netty-tcnative-boringssl-static</artifactId>
<version>${version.netty-tcnative}</version> <version>${version.netty-tcnative}</version>
<classifier>${os.detected.classifier}</classifier>
<type>jar</type> <type>jar</type>
<overWrite>false</overWrite> <overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/native</outputDirectory> <outputDirectory>${project.build.directory}/native</outputDirectory>
@@ -280,37 +282,4 @@
</extension> </extension>
</extensions> </extensions>
</build> </build>
<profiles>
<profile>
<id>native-epoll-linux</id>
<activation>
<os>
<family>!mac</family>
</os>
</activation>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<version>${version.netty}</version>
<classifier>linux-x86_64</classifier>
</dependency>
</dependencies>
</profile>
<profile>
<id>native-epoll</id>
<activation>
<os>
<family>mac</family>
</os>
</activation>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<version>${version.netty}</version>
</dependency>
</dependencies>
</profile>
</profiles>
</project> </project>
+1 -1
View File
@@ -33,7 +33,7 @@
<directory>${project.basedir}/bin</directory> <directory>${project.basedir}/bin</directory>
<outputDirectory>bin</outputDirectory> <outputDirectory>bin</outputDirectory>
<includes> <includes>
<include>*</include> <include>**/*</include>
</includes> </includes>
</fileSet> </fileSet>
<fileSet> <fileSet>
+46 -17
View File
@@ -1,5 +1,8 @@
package timely; package timely;
import static timely.store.cache.DataStoreCache.NON_CACHED_METRICS;
import static timely.store.cache.DataStoreCache.NON_CACHED_METRICS_LOCK_PATH;
import java.io.File; import java.io.File;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
@@ -63,9 +66,11 @@ import org.apache.curator.x.discovery.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceDiscoveryBuilder; import org.apache.curator.x.discovery.ServiceDiscoveryBuilder;
import org.apache.curator.x.discovery.ServiceInstance; import org.apache.curator.x.discovery.ServiceInstance;
import org.apache.curator.x.discovery.ServiceInstanceBuilder; import org.apache.curator.x.discovery.ServiceInstanceBuilder;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import timely.api.response.TimelyException; import timely.api.response.TimelyException;
@@ -97,7 +102,7 @@ import timely.netty.udp.UdpDecoder;
import timely.netty.udp.UdpPacketToByteBuf; import timely.netty.udp.UdpPacketToByteBuf;
import timely.netty.websocket.WSMetricPutHandler; import timely.netty.websocket.WSMetricPutHandler;
import timely.netty.websocket.WSVersionRequestHandler; import timely.netty.websocket.WSVersionRequestHandler;
import timely.netty.websocket.WebSocketHttpCookieHandler; import timely.netty.websocket.WebSocketFullRequestHandler;
import timely.netty.websocket.WebSocketRequestDecoder; import timely.netty.websocket.WebSocketRequestDecoder;
import timely.netty.websocket.subscription.WSAddSubscriptionRequestHandler; import timely.netty.websocket.subscription.WSAddSubscriptionRequestHandler;
import timely.netty.websocket.subscription.WSCloseSubscriptionRequestHandler; import timely.netty.websocket.subscription.WSCloseSubscriptionRequestHandler;
@@ -125,6 +130,7 @@ public class Server {
private static final String OS_VERSION = "os.version"; private static final String OS_VERSION = "os.version";
private static final String WS_PATH = "/websocket"; private static final String WS_PATH = "/websocket";
private CuratorFramework curatorFramework;
protected static final CountDownLatch LATCH = new CountDownLatch(1); protected static final CountDownLatch LATCH = new CountDownLatch(1);
static ConfigurableApplicationContext applicationContext; static ConfigurableApplicationContext applicationContext;
@@ -146,6 +152,9 @@ public class Server {
protected volatile boolean shutdown = false; protected volatile boolean shutdown = false;
private final int DEFAULT_EVENT_LOOP_THREADS; private final int DEFAULT_EVENT_LOOP_THREADS;
private String[] zkPaths = new String[] { SERVICE_DISCOVERY_PATH, NON_CACHED_METRICS,
NON_CACHED_METRICS_LOCK_PATH };
private static boolean useEpoll() { private static boolean useEpoll() {
// Should we just return true if this is Linux and if we get an error // Should we just return true if this is Linux and if we get an error
@@ -175,13 +184,8 @@ public class Server {
} }
} }
public void registerService() { public void registerService(CuratorFramework curatorFramework) {
try { try {
RetryPolicy retryPolicy = new RetryForever(1000);
CuratorFramework curatorFramework = CuratorFrameworkFactory.newClient(config.getAccumulo().getZookeepers(),
30000, 10000, retryPolicy);
curatorFramework.start();
try { try {
Stat stat = curatorFramework.checkExists().forPath(SERVICE_DISCOVERY_PATH); Stat stat = curatorFramework.checkExists().forPath(SERVICE_DISCOVERY_PATH);
if (stat == null) { if (stat == null) {
@@ -247,7 +251,7 @@ public class Server {
} }
protected static Configuration initializeConfiguration(String[] args) { protected static Configuration initializeConfiguration(String[] args) {
applicationContext = new SpringApplicationBuilder(SpringBootstrap.class).web(false).run(args); applicationContext = new SpringApplicationBuilder(SpringBootstrap.class).web(WebApplicationType.NONE).run(args);
return applicationContext.getBean(Configuration.class); return applicationContext.getBean(Configuration.class);
} }
@@ -369,6 +373,10 @@ public class Server {
LOG.error("Error closing WebSocketRequestDecoder during shutdown", e); LOG.error("Error closing WebSocketRequestDecoder during shutdown", e);
} }
if (curatorFramework != null) {
curatorFramework.close();
}
if (applicationContext != null) { if (applicationContext != null) {
LOG.info("Closing applicationContext"); LOG.info("Closing applicationContext");
applicationContext.close(); applicationContext.close();
@@ -390,20 +398,40 @@ public class Server {
this.config = conf; this.config = conf;
} }
private void ensureZkPaths(CuratorFramework curatorFramework, String[] paths) {
for (String s : paths) {
try {
Stat stat = curatorFramework.checkExists().forPath(s);
if (stat == null) {
curatorFramework.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT)
.forPath(s);
}
} catch (Exception e) {
LOG.info(e.getMessage());
}
}
}
public void run() throws Exception { public void run() throws Exception {
RetryPolicy retryPolicy = new RetryForever(1000);
curatorFramework = CuratorFrameworkFactory.newClient(config.getAccumulo().getZookeepers(), 30000, 10000,
retryPolicy);
curatorFramework.start();
ensureZkPaths(curatorFramework, zkPaths);
int nettyThreads = Math.max(1, int nettyThreads = Math.max(1,
SystemPropertyUtil.getInt("io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2)); SystemPropertyUtil.getInt("io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));
dataStore = DataStoreFactory.create(config, nettyThreads); dataStore = DataStoreFactory.create(config, nettyThreads);
if (config.getCache().isEnabled()) { if (config.getCache().isEnabled()) {
dataStoreCache = new DataStoreCache(config); dataStoreCache = new DataStoreCache(curatorFramework, config);
dataStoreCache.setInternalMetrics(dataStore.getInternalMetrics()); dataStoreCache.setInternalMetrics(dataStore.getInternalMetrics());
dataStore.setCache(dataStoreCache); dataStore.setCache(dataStoreCache);
} }
// Initialize the MetaCache // Initialize the MetaCache
MetaCacheFactory.getCache(config); MetaCacheFactory.getCache(config);
// initialize the auth cache // initialize the auth cache
AuthCache.setSessionMaxAge(config); AuthCache.configure(config.getSecurity());
// Initialize the VisibilityCache // Initialize the VisibilityCache
VisibilityCache.init(config); VisibilityCache.init(config);
final boolean useEpoll = useEpoll(); final boolean useEpoll = useEpoll();
@@ -500,7 +528,7 @@ public class Server {
udpServer.option(EpollChannelOption.SO_REUSEPORT, true); udpServer.option(EpollChannelOption.SO_REUSEPORT, true);
udpChannelHandleList.add(udpServer.bind(udpIp, udpPort).sync().channel()); udpChannelHandleList.add(udpServer.bind(udpIp, udpPort).sync().channel());
} }
registerService(); registerService(curatorFramework);
shutdownHook(); shutdownHook();
LOG.info( LOG.info(
"Server started. Listening on {}:{} for TCP traffic, {}:{} for HTTP traffic, {}:{} for WebSocket traffic, and {}:{} for UDP traffic", "Server started. Listening on {}:{} for TCP traffic, {}:{} for HTTP traffic, {}:{} for WebSocket traffic, and {}:{} for UDP traffic",
@@ -560,7 +588,7 @@ public class Server {
ch.pipeline().addLast("decoder", new HttpRequestDecoder()); ch.pipeline().addLast("decoder", new HttpRequestDecoder());
ch.pipeline().addLast("compressor", new HttpContentCompressor()); ch.pipeline().addLast("compressor", new HttpContentCompressor());
ch.pipeline().addLast("decompressor", new HttpContentDecompressor()); ch.pipeline().addLast("decompressor", new HttpContentDecompressor());
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(8192)); ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
ch.pipeline().addLast("chunker", new ChunkedWriteHandler()); ch.pipeline().addLast("chunker", new ChunkedWriteHandler());
final Cors corsCfg = config.getHttp().getCors(); final Cors corsCfg = config.getHttp().getCors();
final CorsConfig.Builder ccb; final CorsConfig.Builder ccb;
@@ -608,7 +636,7 @@ public class Server {
ch.pipeline().addLast("logger", new LoggingHandler()); ch.pipeline().addLast("logger", new LoggingHandler());
ch.pipeline().addLast("packetDecoder", new UdpPacketToByteBuf()); ch.pipeline().addLast("packetDecoder", new UdpPacketToByteBuf());
ch.pipeline().addLast("buffer", new MetricsBufferDecoder()); ch.pipeline().addLast("buffer", new MetricsBufferDecoder());
ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(8192, true, Delimiters.lineDelimiter())); ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(65536, true, Delimiters.lineDelimiter()));
ch.pipeline().addLast("putDecoder", new UdpDecoder()); ch.pipeline().addLast("putDecoder", new UdpDecoder());
ch.pipeline().addLast("putHandler", new TcpPutHandler(dataStore)); ch.pipeline().addLast("putHandler", new TcpPutHandler(dataStore));
} }
@@ -621,7 +649,7 @@ public class Server {
@Override @Override
protected void initChannel(SocketChannel ch) throws Exception { protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("buffer", new MetricsBufferDecoder()); ch.pipeline().addLast("buffer", new MetricsBufferDecoder());
ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(8192, true, Delimiters.lineDelimiter())); ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(65536, true, Delimiters.lineDelimiter()));
ch.pipeline().addLast("putDecoder", new TcpDecoder()); ch.pipeline().addLast("putDecoder", new TcpDecoder());
ch.pipeline().addLast("putHandler", new TcpPutHandler(dataStore)); ch.pipeline().addLast("putHandler", new TcpPutHandler(dataStore));
ch.pipeline().addLast("versionHandler", new TcpVersionHandler()); ch.pipeline().addLast("versionHandler", new TcpVersionHandler());
@@ -636,8 +664,8 @@ public class Server {
protected void initChannel(SocketChannel ch) throws Exception { protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("ssl", sslCtx.newHandler(ch.alloc())); ch.pipeline().addLast("ssl", sslCtx.newHandler(ch.alloc()));
ch.pipeline().addLast("httpServer", new HttpServerCodec()); ch.pipeline().addLast("httpServer", new HttpServerCodec());
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(8192)); ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
ch.pipeline().addLast("sessionExtractor", new WebSocketHttpCookieHandler(config.getSecurity())); ch.pipeline().addLast("sessionExtractor", new WebSocketFullRequestHandler());
ch.pipeline().addLast("idle-handler", new IdleStateHandler(conf.getWebsocket().getTimeout(), 0, 0)); ch.pipeline().addLast("idle-handler", new IdleStateHandler(conf.getWebsocket().getTimeout(), 0, 0));
ch.pipeline().addLast("ws-protocol", ch.pipeline().addLast("ws-protocol",
new WebSocketServerProtocolHandler(WS_PATH, null, true, 65536, true)); new WebSocketServerProtocolHandler(WS_PATH, null, true, 65536, true));
@@ -649,7 +677,8 @@ public class Server {
ch.pipeline().addLast("suggest", new WSSuggestRequestHandler(dataStore)); ch.pipeline().addLast("suggest", new WSSuggestRequestHandler(dataStore));
ch.pipeline().addLast("version", new WSVersionRequestHandler()); ch.pipeline().addLast("version", new WSVersionRequestHandler());
ch.pipeline().addLast("put", new WSMetricPutHandler(dataStore)); ch.pipeline().addLast("put", new WSMetricPutHandler(dataStore));
ch.pipeline().addLast("create", new WSCreateSubscriptionRequestHandler(dataStore, config)); ch.pipeline().addLast("create",
new WSCreateSubscriptionRequestHandler(dataStore, dataStoreCache, config));
ch.pipeline().addLast("add", new WSAddSubscriptionRequestHandler()); ch.pipeline().addLast("add", new WSAddSubscriptionRequestHandler());
ch.pipeline().addLast("remove", new WSRemoveSubscriptionRequestHandler()); ch.pipeline().addLast("remove", new WSRemoveSubscriptionRequestHandler());
ch.pipeline().addLast("close", new WSCloseSubscriptionRequestHandler()); ch.pipeline().addLast("close", new WSCloseSubscriptionRequestHandler());
@@ -0,0 +1,53 @@
package timely.adapter.accumulo;
import org.apache.accumulo.core.client.lexicoder.LongLexicoder;
import org.apache.accumulo.core.client.lexicoder.PairLexicoder;
import org.apache.accumulo.core.client.lexicoder.StringLexicoder;
import org.apache.accumulo.core.data.Key;
import timely.api.model.Meta;
import timely.model.Metric;
import timely.model.parse.TagListParser;
import timely.model.parse.TagParser;
/**
* Creates Accumulo data structures from {@link Metric}
*/
public class MetaAdapter {
private static final PairLexicoder<String, Long> rowCoder = new PairLexicoder<>(new StringLexicoder(),
new LongLexicoder());
private static final PairLexicoder<Long, String> colQualCoder = new PairLexicoder<>(new LongLexicoder(),
new StringLexicoder());
private static final TagParser tagParser = new TagParser();
private static final TagListParser tagListParser = new TagListParser();
public static Key createMetricKey(String metricName, Long timestamp) {
return new Key(Meta.METRIC_PREFIX + metricName, "", "", timestamp);
}
public static Key createTagKey(String metricName, String tagName, Long timestamp) {
return new Key(Meta.TAG_PREFIX + metricName, tagName, "", timestamp);
}
public static Key createValueKey(String metricName, String tagName, String tagValue, Long timestamp) {
return new Key(Meta.VALUE_PREFIX + metricName, tagName, tagValue, timestamp);
}
public static Meta parse(Key key) {
String row = key.getRow().toString();
if (row.startsWith(Meta.METRIC_PREFIX)) {
return Meta.parse(key, null, Meta.METRIC_PREFIX);
} else if (row.startsWith(Meta.TAG_PREFIX)) {
return Meta.parse(key, null, Meta.TAG_PREFIX);
} else if (row.startsWith(Meta.VALUE_PREFIX)) {
return Meta.parse(key, null, Meta.VALUE_PREFIX);
}
throw new IllegalStateException("Invalid key in meta " + key.toString());
}
public static String decodeRowKey(Key key) {
return parse(key).getMetric();
}
}
@@ -55,16 +55,43 @@ public class Meta implements Comparable<Meta> {
return keys; return keys;
} }
public static Meta parse(Key k, Value v) { public static String prefix(Key key) {
String row = key.getRow().toString();
if (row.startsWith(Meta.METRIC_PREFIX)) {
return Meta.METRIC_PREFIX;
} else if (row.startsWith(Meta.TAG_PREFIX)) {
return Meta.TAG_PREFIX;
} else if (row.startsWith(Meta.VALUE_PREFIX)) {
return Meta.VALUE_PREFIX;
}
throw new IllegalStateException("Invalid key in meta " + key.toString());
}
public static Meta parse(Key key) {
return parse(key, null);
}
public static Meta parse(Key key, Value value) {
String row = key.getRow().toString();
if (row.startsWith(Meta.METRIC_PREFIX)) {
return Meta.parse(key, value, Meta.METRIC_PREFIX);
} else if (row.startsWith(Meta.TAG_PREFIX)) {
return Meta.parse(key, value, Meta.TAG_PREFIX);
} else if (row.startsWith(Meta.VALUE_PREFIX)) {
return Meta.parse(key, value, Meta.VALUE_PREFIX);
}
throw new IllegalStateException("Invalid key in meta " + key.toString());
}
public static Meta parse(Key k, Value v, String prefix) {
if (k.getColumnQualifier().getLength() > 0) { if (k.getColumnQualifier().getLength() > 0) {
return new Meta(k.getRow().toString().substring(METRIC_PREFIX.length()), k.getColumnFamily().toString(), return new Meta(k.getRow().toString().substring(prefix.length()), k.getColumnFamily().toString(),
k.getColumnQualifier().toString()); k.getColumnQualifier().toString());
} else if (k.getColumnFamily().getLength() > 0) { } else if (k.getColumnFamily().getLength() > 0) {
return new Meta(k.getRow().toString().substring(METRIC_PREFIX.length()), k.getColumnFamily().toString(), return new Meta(k.getRow().toString().substring(prefix.length()), k.getColumnFamily().toString(), null);
null);
} else { } else {
return new Meta(k.getRow().toString().substring(METRIC_PREFIX.length()), null, null); return new Meta(k.getRow().toString().substring(prefix.length()), null, null);
} }
} }
@@ -1,18 +1,22 @@
package timely.api.request; package timely.api.request;
import java.util.Collections; import com.fasterxml.jackson.annotation.JsonIgnore;
import java.util.HashMap; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.util.List; import com.google.common.collect.HashMultimap;
import java.util.Map; import com.google.common.collect.Multimap;
import java.util.Map.Entry; import timely.auth.TimelyUser;
import timely.auth.util.HttpHeaderUtils;
import timely.netty.http.auth.TimelyAuthenticationToken;
/** /**
* Base class for requests that require authentication * Base class for requests that require auth
*/ */
@JsonIgnoreProperties({ "userName", "requestHeader" })
public class AuthenticatedRequest implements Request { public class AuthenticatedRequest implements Request {
private String sessionId = null; private String sessionId = null;
private Map<String, String> requestHeaders = new HashMap<>(); private TimelyAuthenticationToken token = null;
private Multimap<String, String> requestHeaders = HashMultimap.create();
public String getSessionId() { public String getSessionId() {
return sessionId; return sessionId;
@@ -22,12 +26,17 @@ public class AuthenticatedRequest implements Request {
this.sessionId = sessionId; this.sessionId = sessionId;
} }
public void addHeaders(List<Entry<String, String>> headers) { public void addHeaders(Multimap<String, String> headers) {
headers.forEach(l -> requestHeaders.put(l.getKey(), l.getValue())); requestHeaders.putAll(headers);
} }
public Map<String, String> getRequestHeaders() { public Multimap<String, String> getRequestHeaders() {
return Collections.unmodifiableMap(requestHeaders); return requestHeaders;
}
@JsonIgnore
public String getRequestHeader(String name) {
return HttpHeaderUtils.getSingleHeader(requestHeaders, name, false);
} }
@Override @Override
@@ -35,17 +44,32 @@ public class AuthenticatedRequest implements Request {
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();
buf.append("{"); buf.append("{");
buf.append("Session ID: ").append(sessionId); buf.append("Session ID: ").append(sessionId);
buf.append("Token: ").append(token == null ? null : token.getTimelyPrincipal().getName());
buf.append(", Request Headers: ").append(requestHeaders.toString()); buf.append(", Request Headers: ").append(requestHeaders.toString());
buf.append("}"); buf.append("}");
return buf.toString(); return buf.toString();
} }
@Override public void setToken(TimelyAuthenticationToken token) {
public void validate() { this.token = token;
Request.super.validate();
if (null == sessionId) {
throw new IllegalArgumentException("SessionID is null, must log in first");
}
} }
public TimelyAuthenticationToken getToken() {
return token;
}
@JsonIgnore
public String getUserName() {
String name;
try {
TimelyUser user = token.getTimelyPrincipal().getPrimaryUser();
name = user.getCommonName();
if (name == null) {
name = user.getName();
}
} catch (Exception e) {
name = "UNKNOWN";
}
return name;
}
} }
+110 -41
View File
@@ -1,81 +1,150 @@
package timely.auth; package timely.auth;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Caffeine;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.apache.accumulo.core.security.Authorizations; import org.apache.accumulo.core.security.Authorizations;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.Authentication; import org.slf4j.Logger;
import org.springframework.security.core.GrantedAuthority; import org.slf4j.LoggerFactory;
import timely.api.request.AuthenticatedRequest; import timely.api.request.AuthenticatedRequest;
import timely.api.request.Request; import timely.api.request.AuthenticatedWebSocketRequest;
import timely.api.response.TimelyException; import timely.auth.util.AuthorizationsUtil;
import timely.configuration.Configuration;
import timely.configuration.Security; import timely.configuration.Security;
import timely.netty.http.auth.TimelyAuthenticationToken;
public class AuthCache { public class AuthCache {
private static Cache<String, Authentication> CACHE = null; private static final Logger LOG = LoggerFactory.getLogger(AuthCache.class);
private static Cache<String, TimelyPrincipal> CACHE = null;
private static int sessionMaxAge = -1; private static int cacheExpirationMinutes = -1;
private static int cacheRefreshMinutes = -1;
/** /**
* For tests only * For tests only
*/ */
public static void resetSessionMaxAge() { public static void resetConfiguration() {
sessionMaxAge = -1; cacheExpirationMinutes = -1;
cacheRefreshMinutes = -1;
} }
public static void setSessionMaxAge(Configuration config) { public static void configure(Security security) {
if (-1 != sessionMaxAge) { if (-1 != cacheExpirationMinutes || -1 != cacheRefreshMinutes) {
throw new IllegalStateException("Cache session max age already configured."); throw new IllegalStateException("Cache already configured.");
} }
sessionMaxAge = config.getSecurity().getSessionMaxAge(); cacheExpirationMinutes = security.getCacheExpirationMinutes();
cacheRefreshMinutes = security.getCacheRefreshMinutes();
} }
public static Cache<String, Authentication> getCache() { private static Cache<String, TimelyPrincipal> getCache() {
if (-1 == sessionMaxAge) { if (-1 == cacheExpirationMinutes) {
throw new IllegalStateException("Cache session max age not configured."); throw new IllegalStateException("Cache session max age not configured.");
} }
if (null == CACHE) { if (null == CACHE) {
CACHE = Caffeine.newBuilder().expireAfterAccess(sessionMaxAge, TimeUnit.SECONDS).build(); Caffeine<Object, Object> caffeine = Caffeine.newBuilder();
caffeine.expireAfterWrite(cacheExpirationMinutes, TimeUnit.MINUTES);
if (cacheRefreshMinutes > 0) {
caffeine.refreshAfterWrite(cacheRefreshMinutes, TimeUnit.MINUTES);
CACHE = caffeine.build(key -> getTimelyPrincipal(key));
} else {
CACHE = caffeine.build();
}
} }
return CACHE; return CACHE;
} }
public static Authorizations getAuthorizations(String sessionId) { private static TimelyPrincipal getTimelyPrincipal(String key) {
if (!StringUtils.isEmpty(sessionId)) { LOG.debug("Refreshing TimelyPrincipal {}", key);
Authentication auth = CACHE.asMap().get(sessionId); TimelyPrincipal timelyPrincipal = null;
if (null != auth) { String[] dnArray = key.split(" -> ");
Collection<? extends GrantedAuthority> authorities = CACHE.asMap().get(sessionId).getAuthorities(); if (dnArray.length == 1) {
String[] auths = new String[authorities.size()]; SubjectIssuerDNPair pair = SubjectIssuerDNPair.parse(dnArray[0]);
final AtomicInteger i = new AtomicInteger(0); TimelyAuthenticationToken token = AuthenticationService.getAuthenticationToken(new Object(),
authorities.forEach(a -> auths[i.getAndIncrement()] = a.getAuthority()); pair.subjectDN(), pair.issuerDN());
return new Authorizations(auths); timelyPrincipal = AuthenticationService.authenticate(token, pair, token.getTimelyPrincipal().getName(),
false);
} else if (dnArray.length > 1) {
Collection<TimelyUser> timelyUsers = new ArrayList<>();
for (String s : dnArray) {
SubjectIssuerDNPair pair = SubjectIssuerDNPair.parse(s);
TimelyAuthenticationToken token = AuthenticationService.getAuthenticationToken(new Object(),
pair.subjectDN(), pair.issuerDN());
TimelyPrincipal p = AuthenticationService.authenticate(token, pair,
token.getTimelyPrincipal().getName(), false);
timelyUsers.addAll(p.getProxiedUsers());
}
timelyPrincipal = new TimelyPrincipal(timelyUsers);
}
return timelyPrincipal;
}
public static boolean containsKey(String key) {
return getCache().getIfPresent(key) != null;
}
public static TimelyPrincipal get(String key) {
return getCache().getIfPresent(key);
}
public static void put(String key, TimelyPrincipal principal) {
getCache().put(key, principal);
}
public static void remove(String key) {
getCache().invalidate(key);
}
public static void clear() {
if (CACHE != null) {
CACHE.invalidateAll();
}
}
protected static Collection<Authorizations> getAuthorizations(String entityName) {
if (!StringUtils.isEmpty(entityName)) {
List<Authorizations> authorizationsList = new ArrayList<>();
TimelyPrincipal principal = AuthCache.get(entityName);
if (principal != null) {
for (Collection<String> authCollection : principal.getAuthorizations()) {
authorizationsList.add(AuthorizationsUtil.toAuthorizations(authCollection));
}
LOG.debug("Authorizations for user {} {}", entityName, principal.getAuthorizationsString());
return authorizationsList;
} else { } else {
return null; return Collections.singletonList(Authorizations.EMPTY);
} }
} else { } else {
throw new IllegalArgumentException("session id cannot be null"); throw new IllegalArgumentException("entityName can not be null");
} }
} }
public static void enforceAccess(Security security, Request r) throws Exception { public static Collection<Authorizations> getAuthorizations(AuthenticatedRequest request, Security security) {
if (!security.isAllowAnonymousAccess() && (r instanceof AuthenticatedRequest)) { Collection<Authorizations> auths;
AuthenticatedRequest ar = (AuthenticatedRequest) r; boolean anonAccessAllowed;
if (StringUtils.isEmpty(ar.getSessionId())) { if (request instanceof AuthenticatedWebSocketRequest) {
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "User must log in", anonAccessAllowed = security.isAllowAnonymousWsAccess();
"Anonymous access is disabled, log in first"); } else {
} anonAccessAllowed = security.isAllowAnonymousHttpAccess();
if (!AuthCache.getCache().asMap().containsKey(ar.getSessionId())) {
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "User must log in",
"Unknown session id was submitted, log in again");
}
} }
TimelyAuthenticationToken token = request.getToken();
String sessionId = request.getSessionId();
if (StringUtils.isBlank(sessionId) && token.getClientCert() == null) {
if (anonAccessAllowed) {
auths = Collections.singletonList(Authorizations.EMPTY);
} else {
throw new IllegalArgumentException("User must provide either a sessionId or a client certificate");
}
} else if (StringUtils.isNotBlank(sessionId)) {
auths = getAuthorizations(sessionId);
} else {
auths = getAuthorizations(token.getTimelyPrincipal().getName());
}
return auths;
} }
} }
@@ -1,37 +1,271 @@
package timely.auth; package timely.auth;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.ServiceConfigurationError; import java.util.ServiceConfigurationError;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import com.google.common.collect.Multimap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.ssl.SslHandler;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor; import org.springframework.security.core.Authentication;
import timely.api.request.AuthenticatedRequest;
import timely.api.response.TimelyException;
import timely.auth.util.DnUtils;
import timely.netty.http.auth.TimelyAuthenticationToken;
import timely.netty.http.auth.TimelyUserDetails;
public class AuthenticationService { public class AuthenticationService {
private static final Logger LOG = LoggerFactory.getLogger(AuthenticationService.class);
private static ApplicationContext springContext = null; private static ApplicationContext springContext = null;
private static AuthenticationManager authManager = null; private static AuthenticationManager authManager = null;
private static SubjectDnX509PrincipalExtractor x509 = null; private static ConcurrentHashMap<String, Object> fetchingEntity = new ConcurrentHashMap<>();
private static ConcurrentHashMap<String, Object> fetchingPrincipal = new ConcurrentHashMap<>();
private static Collection<String> requiredRoles;
private static Collection<String> requiredAuths;
static { static {
try { try {
springContext = new ClassPathXmlApplicationContext("security.xml"); springContext = new ClassPathXmlApplicationContext("security.xml");
authManager = (AuthenticationManager) springContext.getBean("authenticationManager"); authManager = springContext.getBean("authenticationManager", AuthenticationManager.class);
x509 = (SubjectDnX509PrincipalExtractor) springContext.getBean("x509PrincipalExtractor"); requiredRoles = springContext.getBean("requiredRoles", List.class);
requiredRoles.removeIf(String::isEmpty);
requiredAuths = springContext.getBean("requiredAuths", List.class);
requiredAuths.removeIf(String::isEmpty);
} catch (BeansException e) { } catch (BeansException e) {
throw new ServiceConfigurationError("Error setting up Authentication objects: " + e.getMessage(), throw new ServiceConfigurationError("Error setting up Authentication objects: " + e.getMessage(),
e.getRootCause()); e.getRootCause());
} }
} }
public static AuthenticationManager getAuthenticationManager() { private static AuthenticationManager getAuthenticationManager() {
return authManager; return authManager;
} }
public static String extractDN(X509Certificate clientCert) throws Exception { public static TimelyPrincipal authenticate(Authentication authentication, String cacheName) {
return (String) x509.extractPrincipal(clientCert); return authenticate(authentication, SubjectIssuerDNPair.of(cacheName), cacheName);
} }
public static TimelyPrincipal authenticate(Authentication authentication, SubjectIssuerDNPair pair, String entity) {
return authenticate(authentication, pair, entity, true);
}
public static TimelyPrincipal authenticate(Authentication authentication, SubjectIssuerDNPair pair, String entity,
boolean useCache) {
// only one thread should call the authenticationManager for a given entity
TimelyPrincipal principal = null;
if (useCache) {
principal = AuthCache.get(entity);
}
// once an entity is in the AuthCache, we can bypass all of this
if (principal == null) {
Object newObj = new Object();
Object o = fetchingEntity.getOrDefault(entity, newObj);
if (o == null) {
o = newObj;
}
try {
synchronized (o) {
// if multiple threads are waiting on this synchronized, then the 2nd and all
// others will get the principal here
if (useCache) {
principal = AuthCache.get(entity);
}
if (principal == null) {
Authentication token = getAuthenticationManager().authenticate(authentication);
principal = getTimelyPrincipal(token, pair);
LOG.trace("Got entity principal for {} from AuthenticationManager", entity);
if (useCache) {
AuthCache.put(entity, principal);
}
} else {
LOG.trace("Got entity principal for {} from AuthCache on 2nd attempt", entity);
}
}
} finally {
fetchingEntity.remove(entity);
}
} else {
LOG.trace("Got entity principal for {} from AuthCache on 1st attempt", entity);
}
return principal;
}
public static void enforceAccess(AuthenticatedRequest request) throws Exception {
String sessionId = request.getSessionId();
X509Certificate clientCert = request.getToken().getClientCert();
TimelyPrincipal verifiedPrincipal = null;
if (StringUtils.isBlank(sessionId) && clientCert == null) {
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "User must log in",
"Anonymous access is disabled. User must either send a client certificate or log in");
} else if (StringUtils.isNotBlank(sessionId)) {
verifiedPrincipal = AuthCache.get(sessionId);
if (verifiedPrincipal == null) {
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "User must log in",
"Unknown session id was submitted, log in again");
}
} else {
try {
TimelyAuthenticationToken token = request.getToken();
TimelyPrincipal requestPrincipal = token.getTimelyPrincipal();
String entity = requestPrincipal.getName();
verifiedPrincipal = AuthCache.get(entity);
if (verifiedPrincipal == null) {
Object newObj = new Object();
Object o = fetchingPrincipal.getOrDefault(entity, newObj);
if (o == null) {
o = newObj;
}
try {
synchronized (o) {
// check again if in the AuthCache
verifiedPrincipal = AuthCache.get(entity);
if (verifiedPrincipal == null) {
// first thread here, so authenticate all entities in requestPrincipal
List<TimelyUser> authenticatedTimelyUsers = new ArrayList<>();
for (TimelyUser user : requestPrincipal.getProxiedUsers()) {
SubjectIssuerDNPair p = user.getDn();
TimelyAuthenticationToken entityToken = getAuthenticationToken(clientCert,
p.subjectDN(), p.issuerDN());
TimelyPrincipal entityPrincipal = authenticate(entityToken, p,
entityToken.getTimelyPrincipal().getName());
authenticatedTimelyUsers.addAll(entityPrincipal.getProxiedUsers());
}
verifiedPrincipal = new TimelyPrincipal(authenticatedTimelyUsers);
AuthCache.put(verifiedPrincipal.getName(), verifiedPrincipal);
LOG.debug("Authenticated user {} for request {} with authorizations {}",
verifiedPrincipal.getName(), request.toString(),
verifiedPrincipal.getAuthorizationsString());
} else {
LOG.trace("Verified principal {} in AuthCache on 2nd attempt", entity);
}
}
} finally {
fetchingPrincipal.remove(entity);
}
} else {
LOG.trace("Verified principal {} in AuthCache on 1st attempt", entity);
}
} catch (RuntimeException e) {
LOG.error(e.getMessage(), e);
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "Access denied", e.getMessage());
}
}
try {
checkAllowedAccess(verifiedPrincipal.getPrimaryUser());
} catch (TimelyException e) {
// validation based on roles and/or auths failed
// remove combined principal and primary user from cache
// i.e. don't cache a failure
AuthCache.remove(verifiedPrincipal.getName());
AuthCache.remove(verifiedPrincipal.getPrimaryUser().getName());
throw e;
}
}
private static void checkAllowedAccess(TimelyUser primaryUser) throws TimelyException {
Set<String> missingRoles = new TreeSet<>();
Set<String> missingAuths = new TreeSet<>();
if (requiredRoles != null) {
if (!primaryUser.getRoles().containsAll(requiredRoles)) {
missingRoles.addAll(requiredRoles);
missingRoles.removeAll(primaryUser.getRoles());
}
}
if (requiredAuths != null) {
if (!primaryUser.getAuths().containsAll(requiredAuths)) {
missingAuths.addAll(requiredRoles);
missingAuths.removeAll(primaryUser.getAuths());
}
}
if (!missingRoles.isEmpty() || !missingAuths.isEmpty()) {
String message = "";
if (!missingRoles.isEmpty() && !missingAuths.isEmpty()) {
message = "User:" + primaryUser.getName() + " is missing role(s):" + missingRoles + " and auth(s):"
+ missingAuths;
} else if (!missingRoles.isEmpty()) {
message = "User:" + primaryUser.getName() + " is missing role(s):" + missingRoles;
} else if (missingAuths.isEmpty()) {
message = "User:" + primaryUser.getName() + " is missing auths(s):" + missingAuths;
}
LOG.debug(message);
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "Access denied", message);
}
}
public static X509Certificate getClientCertificate(ChannelHandlerContext ctx) {
X509Certificate clientCert = null;
if (ctx != null) {
SslHandler sslHandler = (SslHandler) ctx.channel().pipeline().get("ssl");
if (null != sslHandler) {
try {
clientCert = (X509Certificate) sslHandler.engine().getSession().getPeerCertificates()[0];
} catch (Exception e) {
}
} else {
throw new IllegalStateException("The expected SSL handler is not in the pipeline.");
}
}
return clientCert;
}
public static TimelyAuthenticationToken getAuthenticationToken(X509Certificate clientCert,
Multimap<String, String> headers) {
TimelyAuthenticationToken authenticationToken = null;
try {
authenticationToken = new TimelyAuthenticationToken(clientCert.getSubjectDN().getName(), clientCert,
headers);
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
return authenticationToken;
}
public static TimelyAuthenticationToken getAuthenticationToken(Object clientCert, String subjectDn,
String issuerDn) {
TimelyAuthenticationToken authenticationToken = null;
try {
authenticationToken = new TimelyAuthenticationToken(subjectDn, issuerDn, clientCert);
} catch (Exception e) {
LOG.error(e.getMessage(), e);
}
return authenticationToken;
}
private static TimelyPrincipal getTimelyPrincipal(Authentication authentication,
SubjectIssuerDNPair subjectIssuerDNPair) {
String subjectDn = subjectIssuerDNPair.subjectDN();
TimelyUser.UserType userType = DnUtils.isServerDN(subjectDn) ? TimelyUser.UserType.SERVER
: TimelyUser.UserType.USER;
Collection<String> auths = authentication.getAuthorities().stream().map(a -> a.getAuthority())
.collect(Collectors.toList());
Object o = authentication.getPrincipal();
Collection<String> roles = null;
if (o instanceof TimelyUserDetails) {
roles = ((TimelyUserDetails) o).getRoles();
}
TimelyUser timelyUser = new TimelyUser(subjectIssuerDNPair, userType, auths, roles, null,
System.currentTimeMillis());
return new TimelyPrincipal(Arrays.asList(timelyUser));
}
} }
@@ -0,0 +1,93 @@
package timely.auth;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.accumulo.core.security.Authorizations;
/**
* @see AuthorizationsMinimizer#minimize(Collection)
*/
public class AuthorizationsMinimizer {
/**
* Given a collection of Accumulo {@link Authorizations} objects, this method
* attempts to compute a "minimum" set that can be used for a multi-entity scan
* of data from Accumulo. The idea is that there is a call chain with multiple
* entities (e.g., a middleware broker acting on behalf of a GUI that is in turn
* acting on behalf of a user). In order to ensure that only data visible to all
* of the entities is returned, we must add a visibility filter for each entity
* in the chain to ensure that each key/value pair returned by Accumulo can be
* seen by all of the entities in the chain. Since we may be scanning a very
* large number of keys, it would be ideal if we could reduce the number of
* visibility filters to a small set if that can be done safely.
* <p>
* The crux of the problem, and the reason we need multiple filters in the first
* place (as opposed to just intersecting the authorizations for all all
* entities and using that set), is the fact that Accumulo visibility
* expressions may include disjunctions. Consider the following visibility
* expression.
* <p>
* {@code (A & B & ORG1) | (A & C & ORG2)}
* <p>
* If the incoming user has the Accumulo authorizations A, B, and ORG1 and the
* user is coming through a server with the Accumulo authorizations A, C, and
* ORG2, then the server is allowed to see data protected with this expression
* and the user is also allowed, so the data should be returned with those two
* calling entities. If we simply computed an intersection of the authorizations
* that set would be A. That set of authorizations does not match the visibility
* expression and data that should be returned would not be.
* <p>
* Given that we must apply multiple visibility filters in order to properly
* return data for a calling entity chain, it is desirable to minimize the
* number of visibility filters used, if possible. If a set of authorizations is
* a superset of any other set of authorizations, then it does not need to be
* tested. As long as we are not changing any of the authorizations sets, we
* know we will never allow any data to be returned that would otherwise not be.
* By removing authorizations sets, we might only prevent the return of data
* that should otherwise be returned. Consider a given set of authorizations
* that is a subset of another. If the subset passes a visibility expression,
* then the superset will too since it contains at least the same
* authorizations, so there is no need to test against it. If the subset does
* not pass, then the data is not visible to the chain anyway, so there is no
* point in testing the superset.
*
* @param authorizations
* the list of Authorizations for each entity in the call chain
* @return a minimized set of Authorizations that allows visibility of exactly
* the same data as {@code authorizations}
*/
public static Collection<Authorizations> minimize(Collection<Authorizations> authorizations) {
if (authorizations.size() > 1) {
// Convert collection of Authorizations into a collection of String sets (the
// individual authorizations).
// Since we are adding to a LinkedHashSet, this will de-dupe any duplicate
// authorization sets.
final LinkedHashSet<Set<String>> allAuths = authorizations.stream().map(
a -> a.getAuthorizations().stream().map(String::new).collect(Collectors.toCollection(HashSet::new)))
.collect(Collectors.toCollection(LinkedHashSet::new));
// Go through the authorizations sets and remove any that are supersets of any
// other.
for (Iterator<Set<String>> it = allAuths.iterator(); it.hasNext(); /* empty */) {
Set<String> currentSet = it.next();
if (allAuths.stream().filter(a -> a != currentSet && a.size() <= currentSet.size())
.anyMatch(currentSet::containsAll))
it.remove();
}
// If we removed any sets of authorizations, then we need to convert the reduced
// set from
// TreeSet<String> objects back into Authorizations objects.
if (allAuths.size() < authorizations.size()) {
authorizations = allAuths.stream().map(a -> new Authorizations(a.toArray(new String[0])))
.collect(Collectors.toCollection(LinkedHashSet::new));
}
}
return authorizations;
}
}
@@ -0,0 +1,76 @@
package timely.auth;
import java.io.IOException;
import java.util.Map;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.iterators.IteratorEnvironment;
import org.apache.accumulo.core.iterators.OptionDescriber;
import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
import org.apache.accumulo.core.iterators.WrappingIterator;
import org.apache.accumulo.core.iterators.system.VisibilityFilter;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.log4j.Logger;
public class ConfigurableVisibilityFilter extends WrappingIterator implements OptionDescriber {
public static final String AUTHORIZATIONS_OPT = "authorizations";
private SortedKeyValueIterator<Key, Value> delegate;
private static final Logger log = Logger.getLogger(ConfigurableVisibilityFilter.class);
public ConfigurableVisibilityFilter() {
}
public ConfigurableVisibilityFilter(SortedKeyValueIterator<Key, Value> iterator, Authorizations authorizations,
byte[] defaultVisibility) {
setSource(iterator);
}
public ConfigurableVisibilityFilter(ConfigurableVisibilityFilter other, IteratorEnvironment env) {
this.delegate = other.delegate.deepCopy(env);
this.setSource(delegate);
}
@Override
public void init(SortedKeyValueIterator<Key, Value> source, Map<String, String> options, IteratorEnvironment env)
throws IOException {
Authorizations auths = Authorizations.EMPTY;
if (options.containsKey(AUTHORIZATIONS_OPT))
auths = new Authorizations(options.get(AUTHORIZATIONS_OPT).split(","));
log.debug("Using authorizations: " + auths);
delegate = VisibilityFilter.wrap(source, auths, new byte[0]);
super.init(delegate, options, env);
}
@Override
public SortedKeyValueIterator<Key, Value> deepCopy(IteratorEnvironment env) {
return new ConfigurableVisibilityFilter(this, env);
}
@Override
public IteratorOptions describeOptions() {
IteratorOptions opts = new IteratorOptions(getClass().getSimpleName(),
"Filters keys based to return only those whose visibility tests positive against the supplied authorizations",
null, null);
opts.addNamedOption(AUTHORIZATIONS_OPT, "Comma delimited list of scan authorizations");
return opts;
}
@Override
public boolean validateOptions(Map<String, String> options) {
boolean valid = false;
String auths = options.get(AUTHORIZATIONS_OPT);
if (auths != null) {
try {
new Authorizations(auths.split(","));
valid = true;
} catch (Exception e) {
// ignore
}
}
return valid;
}
}
@@ -0,0 +1,257 @@
package timely.auth;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import com.google.common.collect.Lists;
import org.apache.accumulo.core.client.IteratorSetting;
import org.apache.accumulo.core.client.IteratorSetting.Column;
import org.apache.accumulo.core.client.ScannerBase;
import org.apache.accumulo.core.client.impl.ScannerOptions;
import org.apache.accumulo.core.client.sample.SamplerConfiguration;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.data.thrift.IterInfo;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.hadoop.io.Text;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A simple wrapper around a {@link ScannerBase} that overrides the methods that
* configure iterators.
*
* @see ScannerOptionsHelper
*/
public class ScannerBaseDelegate implements ScannerBase {
private static final Logger log = LoggerFactory.getLogger(ScannerBaseDelegate.class);
private static final String SYSTEM_ITERATOR_NAME_PREFIX = "sys_";
protected final ScannerBase delegate;
public ScannerBaseDelegate(ScannerBase delegate) {
this.delegate = delegate;
}
@Override
public void addScanIterator(IteratorSetting cfg) {
if (cfg.getName().startsWith(SYSTEM_ITERATOR_NAME_PREFIX)) {
throw new IllegalArgumentException(
"Non-system iterators' names cannot start with " + SYSTEM_ITERATOR_NAME_PREFIX);
} else {
delegate.addScanIterator(cfg);
}
}
/**
* Adds a "system" scan iterator. The iterator name is automatically prefixed
* with {@link #SYSTEM_ITERATOR_NAME_PREFIX}. A "system" scan iterator can only
* be modified or removed by calling
* {@link #updateSystemScanIteratorOption(String, String, String)},
* {@link #removeSystemScanIterator(String)}, or
* {@link #clearSystemScanIterators()}. Updates the iterator configuration for
* {@code iteratorName}. The iterator name is automatically prefixed with
* {@link #SYSTEM_ITERATOR_NAME_PREFIX}.
*
* @param cfg
* the configuration of the iterator to add
*/
public void addSystemScanIterator(IteratorSetting cfg) {
if (!cfg.getName().startsWith(SYSTEM_ITERATOR_NAME_PREFIX)) {
cfg.setName(SYSTEM_ITERATOR_NAME_PREFIX + cfg.getName());
}
delegate.addScanIterator(cfg);
}
@Override
public void removeScanIterator(String iteratorName) {
if (iteratorName.startsWith(SYSTEM_ITERATOR_NAME_PREFIX)) {
throw new IllegalArgumentException("DATAWAVE system iterator " + iteratorName + " cannot be removed");
} else {
delegate.removeScanIterator(iteratorName);
}
}
/**
* Removes a "system" scan iterator. The iterator name is automatically prefixed
* with {@link #SYSTEM_ITERATOR_NAME_PREFIX}.
*
* @param iteratorName
* the name of the system iterator to remove
*/
public void removeSystemScanIterator(String iteratorName) {
if (!iteratorName.startsWith(SYSTEM_ITERATOR_NAME_PREFIX)) {
iteratorName = SYSTEM_ITERATOR_NAME_PREFIX + iteratorName;
}
delegate.removeScanIterator(iteratorName);
}
@Override
public void updateScanIteratorOption(String iteratorName, String key, String value) {
if (iteratorName.startsWith(SYSTEM_ITERATOR_NAME_PREFIX)) {
throw new IllegalArgumentException("DATAWAVE system iterator " + iteratorName + " cannot be updated");
} else {
delegate.updateScanIteratorOption(iteratorName, key, value);
}
}
/**
* Updates the iterator configuration for {@code iteratorName}. The iterator
* name is automatically prefixed with {@link #SYSTEM_ITERATOR_NAME_PREFIX}.
*
* @param iteratorName
* the name of the system iterator to modify
* @param key
* the name of the iterator option to modify
* @param value
* the new value for the iterator option named in {@code key}
*/
public void updateSystemScanIteratorOption(String iteratorName, String key, String value) {
if (!iteratorName.startsWith(SYSTEM_ITERATOR_NAME_PREFIX)) {
iteratorName = SYSTEM_ITERATOR_NAME_PREFIX + iteratorName;
}
delegate.updateScanIteratorOption(iteratorName, key, value);
}
@Override
public void fetchColumnFamily(Text col) {
delegate.fetchColumnFamily(col);
}
@Override
public void fetchColumn(Text colFam, Text colQual) {
delegate.fetchColumn(colFam, colQual);
}
@Override
public void fetchColumn(Column column) {
delegate.fetchColumn(column);
}
@Override
public void clearColumns() {
delegate.clearColumns();
}
@Override
public void clearScanIterators() {
if (delegate instanceof ScannerOptions) {
ScannerOptionsHelper opts = new ScannerOptionsHelper((ScannerOptions) delegate);
for (IteratorSetting iteratorSetting : opts.getIterators()) {
if (!iteratorSetting.getName().startsWith(SYSTEM_ITERATOR_NAME_PREFIX)) {
delegate.removeScanIterator(iteratorSetting.getName());
}
}
} else {
throw new UnsupportedOperationException(
"Cannot clear scan iterators on a non-ScannerOptions class! (" + delegate.getClass() + ")");
}
}
/**
* Clears all iterators (including system iterators).
*/
public void clearSystemScanIterators() {
delegate.clearScanIterators();
}
@Override
public Iterator<Map.Entry<Key, Value>> iterator() {
return delegate.iterator();
}
@Override
public void setTimeout(long timeOut, TimeUnit timeUnit) {
delegate.setTimeout(timeOut, timeUnit);
}
@Override
public long getTimeout(TimeUnit timeUnit) {
return delegate.getTimeout(timeUnit);
}
@Override
public void close() {
delegate.close();
}
@Override
public Authorizations getAuthorizations() {
return delegate.getAuthorizations();
}
@Override
public void setSamplerConfiguration(SamplerConfiguration samplerConfiguration) {
delegate.setSamplerConfiguration(samplerConfiguration);
}
@Override
public SamplerConfiguration getSamplerConfiguration() {
return delegate.getSamplerConfiguration();
}
@Override
public void clearSamplerConfiguration() {
delegate.clearSamplerConfiguration();
}
@Override
public void setBatchTimeout(long l, TimeUnit timeUnit) {
delegate.setBatchTimeout(l, timeUnit);
}
@Override
public long getBatchTimeout(TimeUnit timeUnit) {
return delegate.getBatchTimeout(timeUnit);
}
@Override
public void setClassLoaderContext(String s) {
delegate.setClassLoaderContext(s);
}
@Override
public void clearClassLoaderContext() {
delegate.clearClassLoaderContext();
}
@Override
public String getClassLoaderContext() {
return delegate.getClassLoaderContext();
}
public void setContext(String context) {
delegate.setClassLoaderContext(context);
}
public void clearContext() {
delegate.clearClassLoaderContext();
}
public String getContext() {
return delegate.getClassLoaderContext();
}
private static class ScannerOptionsHelper extends ScannerOptions {
public ScannerOptionsHelper(ScannerOptions other) {
super(other);
}
public Collection<IteratorSetting> getIterators() {
Collection<IteratorSetting> settings = Lists.newArrayList();
for (IterInfo iter : serverSideIteratorList) {
IteratorSetting setting = new IteratorSetting(iter.getPriority(), iter.getIterName(),
iter.getClassName());
setting.addOptions(serverSideIteratorOptions.get(iter.getIterName()));
settings.add(setting);
}
return settings;
}
}
}
@@ -0,0 +1,80 @@
package timely.auth;
import java.io.Serializable;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import timely.auth.util.ProxiedEntityUtils;
/**
* A simple pair containing a subject and (optional) issuer DN. The supplied DN
* values are normalized into a lower-case form with the CN portion first.
*/
public class SubjectIssuerDNPair implements Serializable {
private final String subjectDN;
private final String issuerDN;
public static SubjectIssuerDNPair of(String subjectDN) {
return new SubjectIssuerDNPair(subjectDN, null);
}
@JsonCreator
public static SubjectIssuerDNPair of(@JsonProperty("subjectDN") String subjectDN,
@JsonProperty("issuerDN") String issuerDN) {
return new SubjectIssuerDNPair(subjectDN, issuerDN);
}
public static SubjectIssuerDNPair parse(String dn) {
String[] dns = ProxiedEntityUtils.splitProxiedSubjectIssuerDNs(dn);
if (dns.length != 2)
throw new IllegalArgumentException(dn + " must contain a single subject and issuer DN");
return new SubjectIssuerDNPair(dns[0], dns[1]);
}
protected SubjectIssuerDNPair(String subjectDN, String issuerDN) {
this.subjectDN = ProxiedEntityUtils.normalizeDN(subjectDN);
if (issuerDN != null) {
this.issuerDN = ProxiedEntityUtils.normalizeDN(issuerDN);
} else {
this.issuerDN = null;
}
}
@JsonGetter
public String subjectDN() {
return subjectDN;
}
@JsonGetter
public String issuerDN() {
return issuerDN;
}
@Override
public String toString() {
return issuerDN == null ? subjectDN + "<>" : ProxiedEntityUtils.buildProxiedDN(subjectDN, issuerDN);
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
SubjectIssuerDNPair that = (SubjectIssuerDNPair) o;
if (!subjectDN.equals(that.subjectDN))
return false;
return issuerDN != null ? issuerDN.equals(that.issuerDN) : that.issuerDN == null;
}
@Override
public int hashCode() {
int result = subjectDN.hashCode();
result = 31 * result + (issuerDN != null ? issuerDN.hashCode() : 0);
return result;
}
}
@@ -0,0 +1,150 @@
package timely.auth;
import static timely.auth.TimelyUser.ANONYMOUS_USER;
import java.io.Serializable;
import java.security.Principal;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import timely.auth.util.DnUtils;
/**
* A {@link Principal} that represents a set of proxied {@link TimelyUser}s. For
* example, this proxied user could represent a GUI server acting on behalf of a
* user. The GUI server user represents the entity that made the call to us and
* the other proxied user would be the actual end user.
*/
@XmlRootElement
@XmlType(factoryMethod = "anonymousPrincipal", propOrder = { "name", "proxiedUsers", "creationTime" })
@XmlAccessorType(XmlAccessType.NONE)
public class TimelyPrincipal implements Principal, Serializable {
private final String username;
private final TimelyUser primaryUser;
@XmlElement
private final Set<TimelyUser> proxiedUsers = new LinkedHashSet<>();
@XmlElement
private final long creationTime;
/**
* This constructor should not be used. It is here to allow JAX-B mapping and
* CDI proxying of this class.
*/
public TimelyPrincipal() {
this(ANONYMOUS_USER);
}
public TimelyPrincipal(TimelyUser timelyUser) {
this.creationTime = System.currentTimeMillis();
this.primaryUser = timelyUser;
this.proxiedUsers.add(timelyUser);
this.username = timelyUser.getName();
}
public TimelyPrincipal(Collection<TimelyUser> proxiedUsers) {
this(proxiedUsers, System.currentTimeMillis());
}
public TimelyPrincipal(Collection<TimelyUser> proxiedUsers, long creationTime) {
this.creationTime = creationTime;
TimelyUser first = proxiedUsers.stream().findFirst().orElse(null);
this.primaryUser = proxiedUsers.stream().filter(u -> u.getUserType() == TimelyUser.UserType.USER).findFirst()
.orElse(first);
this.proxiedUsers.add(primaryUser);
this.proxiedUsers
.addAll(proxiedUsers.stream().filter(u -> !u.equals(primaryUser)).collect(Collectors.toList()));
this.username = this.proxiedUsers.stream().map(TimelyUser::getName).collect(Collectors.joining(" -> "));
}
public Set<TimelyUser> getProxiedUsers() {
return Collections.unmodifiableSet(proxiedUsers);
}
public TimelyUser getPrimaryUser() {
return primaryUser;
}
public Collection<? extends Collection<String>> getAuthorizations() {
return Collections
.unmodifiableCollection(proxiedUsers.stream().map(TimelyUser::getAuths).collect(Collectors.toList()));
}
public String[] getDNs() {
return proxiedUsers.stream().map(TimelyUser::getDn).map(SubjectIssuerDNPair::subjectDN).toArray(String[]::new);
}
public long getCreationTime() {
return creationTime;
}
@Override
@XmlElement
public String getName() {
return username;
}
public String getShortName() {
return DnUtils.getShortName(getPrimaryUser().getName());
}
public SubjectIssuerDNPair getUserDN() {
return getPrimaryUser().getDn();
}
public Set<String> getProxyServers() {
// @formatter:off
Set<String> proxyServers = getProxiedUsers().stream()
.filter(u -> u.getUserType() == TimelyUser.UserType.SERVER)
.map(TimelyUser::getDn)
.map(SubjectIssuerDNPair::subjectDN)
.collect(Collectors.toSet());
// @formatter:on
return proxyServers.isEmpty() ? null : proxyServers;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
TimelyPrincipal that = (TimelyPrincipal) o;
if (!username.equals(that.username))
return false;
return proxiedUsers.equals(that.proxiedUsers);
}
@Override
public int hashCode() {
int result = username.hashCode();
result = 31 * result + proxiedUsers.hashCode();
return result;
}
@Override
public String toString() {
return "TimelyPrincipal{" + "name='" + username + "'" + ", proxiedUsers=" + proxiedUsers + "}";
}
public static TimelyPrincipal anonymousPrincipal() {
return new TimelyPrincipal(ANONYMOUS_USER);
}
public String getAuthorizationsString() {
return proxiedUsers.stream().map(TimelyUser::getAuths).map(a -> a.toString())
.collect(Collectors.joining(" -> ", "[", "]"));
}
}
@@ -0,0 +1,156 @@
package timely.auth;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import timely.auth.util.DnUtils;
/**
* A user of a TIMELY service. Typically, one or more of these users (a chain
* where a user called an intermediate service which in turn called us) is
* represented with a TimelyPrincipal.
*/
public class TimelyUser implements Serializable {
public enum UserType {
USER, SERVER
}
public static final TimelyUser ANONYMOUS_USER = new TimelyUser(SubjectIssuerDNPair.of("ANONYMOUS"), UserType.USER,
null, null, null, -1L);
private final String name;
private final String commonName;
private final SubjectIssuerDNPair dn;
private final UserType userType;
private final Collection<String> auths;
private final Collection<String> unmodifiableAuths;
private final Collection<String> roles;
private final Collection<String> unmodifiableRoles;
private final Multimap<String, String> roleToAuthMapping;
private final long creationTime;
private final long expirationTime;
/*
* For Spring configuration
*/
public TimelyUser(String subjectDn, Collection<String> auths, Collection<String> roles) {
this.name = subjectDn;
this.commonName = DnUtils.getCommonName(subjectDn);
this.dn = SubjectIssuerDNPair.of(subjectDn);
this.userType = DnUtils.isServerDN(subjectDn) ? UserType.SERVER : UserType.USER;
this.auths = auths == null ? Collections.emptyList() : new LinkedHashSet<>(auths);
this.unmodifiableAuths = Collections.unmodifiableCollection(this.auths);
this.roles = roles == null ? Collections.emptyList() : new LinkedHashSet<>(roles);
this.unmodifiableRoles = Collections.unmodifiableCollection(this.roles);
this.roleToAuthMapping = null;
this.creationTime = System.currentTimeMillis();
this.expirationTime = -1L;
}
/*
* For testing
*/
public TimelyUser(String subjectDn, Collection<String> auths) {
this(SubjectIssuerDNPair.of(subjectDn), UserType.USER, auths, null, null, System.currentTimeMillis(), -1L);
}
public TimelyUser(SubjectIssuerDNPair dn, UserType userType, Collection<String> auths, Collection<String> roles,
Multimap<String, String> roleToAuthMapping, long creationTime) {
this(dn, userType, auths, roles, roleToAuthMapping, creationTime, -1L);
}
@JsonCreator
public TimelyUser(@JsonProperty(value = "dn", required = true) SubjectIssuerDNPair dn,
@JsonProperty(value = "userType", required = true) UserType userType,
@JsonProperty("auths") Collection<String> auths, @JsonProperty("roles") Collection<String> roles,
@JsonProperty("roleToAuthMapping") Multimap<String, String> roleToAuthMapping,
@JsonProperty(value = "creationTime", defaultValue = "-1L") long creationTime,
@JsonProperty(value = "expirationTime", defaultValue = "-1L") long expirationTime) {
this.name = dn.toString();
this.commonName = DnUtils.getCommonName(dn.subjectDN());
this.dn = dn;
this.userType = userType;
this.auths = auths == null ? Collections.emptyList() : new LinkedHashSet<>(auths);
this.unmodifiableAuths = Collections.unmodifiableCollection(this.auths);
this.roles = roles == null ? Collections.emptyList() : new LinkedHashSet<>(roles);
this.unmodifiableRoles = Collections.unmodifiableCollection(this.roles);
this.roleToAuthMapping = roleToAuthMapping == null ? LinkedHashMultimap.create()
: Multimaps.unmodifiableMultimap(LinkedHashMultimap.create(roleToAuthMapping));
this.creationTime = creationTime;
this.expirationTime = expirationTime;
}
public String getName() {
return name;
}
@JsonIgnore
public String getCommonName() {
return commonName;
}
public SubjectIssuerDNPair getDn() {
return dn;
}
public UserType getUserType() {
return userType;
}
public Collection<String> getAuths() {
return unmodifiableAuths;
}
public Collection<String> getRoles() {
return unmodifiableRoles;
}
public Multimap<String, String> getRoleToAuthMapping() {
return roleToAuthMapping;
}
public long getCreationTime() {
return creationTime;
}
public long getExpirationTime() {
return expirationTime;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
TimelyUser that = (TimelyUser) o;
return creationTime == that.creationTime && dn.equals(that.dn) && userType == that.userType
&& auths.equals(that.auths) && roles.equals(that.roles);
}
@Override
public int hashCode() {
int result = dn.hashCode();
result = 31 * result + userType.hashCode();
result = 31 * result + auths.hashCode();
result = 31 * result + roles.hashCode();
result = 31 * result + (int) (creationTime ^ (creationTime >>> 32));
return result;
}
@Override
public String toString() {
return "TimelyUser{" + "name='" + getName() + "'" + ", userType=" + getUserType() + ", auths=" + getAuths()
+ ", roles=" + getRoles() + ", creationTime=" + getCreationTime() + "}";
}
}
@@ -3,7 +3,6 @@ package timely.auth;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -11,18 +10,20 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import timely.netty.http.auth.TimelyUserDetails;
public class UserDetailsService implements public class UserDetailsService implements
org.springframework.security.core.userdetails.AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> { org.springframework.security.core.userdetails.AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {
private HashMap<String, List<String>> users; private HashMap<String, TimelyUser> users;
public Map<String, List<String>> getUsers() { public Map<String, TimelyUser> getUsers() {
return users; return users;
} }
public void setUsers(LinkedHashMap<String, List<String>> users) { public void setUsers(List<TimelyUser> users) {
this.users = new HashMap<>(users); this.users = new HashMap<>();
users.forEach(u -> this.users.put(u.getName(), u));
} }
@Override @Override
@@ -30,25 +31,32 @@ public class UserDetailsService implements
throws UsernameNotFoundException { throws UsernameNotFoundException {
// Determine if the user is allowed to access the system, if not throw // Determine if the user is allowed to access the system, if not throw
// UsernameNotFoundException // UsernameNotFoundException
String username = token.getName().toString(); String username = token.getName();
if (!users.containsKey(username)) { if (!users.containsKey(username)) {
throw new UsernameNotFoundException(username + " not configured."); throw new UsernameNotFoundException(username + " not configured.");
} }
// If allowed, populate the user details object with the authorities for // If allowed, populate the user details object with the authorities for
// the user. // the user.
return new UserDetails() { return new TimelyUserDetails() {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Override @Override
public Collection<SimpleGrantedAuthority> getAuthorities() { public Collection<SimpleGrantedAuthority> getAuthorities() {
final Collection<SimpleGrantedAuthority> auths = new ArrayList<>(); final Collection<SimpleGrantedAuthority> auths = new ArrayList<>();
users.get(username).forEach(a -> { users.get(username).getAuths().forEach(a -> {
auths.add(new SimpleGrantedAuthority(a)); auths.add(new SimpleGrantedAuthority(a));
}); });
return auths; return auths;
} }
@Override
public Collection<String> getRoles() {
final Collection<String> roles = new ArrayList<>();
roles.addAll(users.get(username).getRoles());
return roles;
}
@Override @Override
public String getPassword() { public String getPassword() {
return ""; return "";
@@ -0,0 +1,61 @@
package timely.auth.util;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.apache.accumulo.core.security.Authorizations;
import timely.auth.AuthorizationsMinimizer;
public class AuthorizationsUtil {
public static Authorizations union(Iterable<byte[]> authorizations1, Iterable<byte[]> authorizations2) {
LinkedList<byte[]> aggregatedAuthorizations = Lists.newLinkedList();
addTo(aggregatedAuthorizations, authorizations1);
addTo(aggregatedAuthorizations, authorizations2);
return new Authorizations(aggregatedAuthorizations);
}
private static void addTo(LinkedList<byte[]> aggregatedAuthorizations, Iterable<byte[]> authsToAdd) {
for (byte[] auth : authsToAdd) {
aggregatedAuthorizations.add(auth);
}
}
public static Authorizations toAuthorizations(Collection<String> auths) {
return new Authorizations(auths.stream().map(String::trim).map(s -> s.getBytes(Charset.forName("UTF-8")))
.collect(Collectors.toList()));
}
public static List<String> splitAuths(String requestedAuths) {
return Arrays.asList(Iterables.toArray(Splitter.on(',').omitEmptyStrings().trimResults().split(requestedAuths),
String.class));
}
public static Set<Authorizations> buildAuthorizations(Collection<? extends Collection<String>> userAuths) {
if (null == userAuths) {
return Collections.singleton(new Authorizations());
}
HashSet<Authorizations> auths = Sets.newHashSet();
for (Collection<String> userAuth : userAuths) {
auths.add(new Authorizations(userAuth.toArray(new String[userAuth.size()])));
}
return auths;
}
public static Collection<Authorizations> minimize(Collection<Authorizations> authorizations) {
return AuthorizationsMinimizer.minimize(authorizations);
}
}
@@ -0,0 +1,212 @@
package timely.auth.util;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.regex.Pattern;
import io.netty.handler.codec.http.QueryStringDecoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DnUtils {
/** Config for injecting NPE OU identifiers */
public static final String PROPS_RESOURCE = "dnutils.properties";
private static final Properties PROPS = new Properties();
public static final String SUBJECT_DN_PATTERN_PROPERTY = "subject.dn.pattern";
private static final Pattern SUBJECT_DN_PATTERN;
private static final Logger log = LoggerFactory.getLogger(DnUtils.class);
static {
InputStream in = null;
try {
in = DnUtils.class.getClassLoader().getResourceAsStream(PROPS_RESOURCE);
PROPS.load(in);
} catch (Throwable t) {
log.error(PROPS_RESOURCE + " could not be loaded!", t);
throw new RuntimeException(t);
} finally {
if (null != in) {
try {
in.close();
} catch (IOException e) {
log.warn("Failed to close input stream", e);
}
}
}
String subjectDnPattern = System.getProperty(SUBJECT_DN_PATTERN_PROPERTY,
PROPS.getProperty(SUBJECT_DN_PATTERN_PROPERTY));
try {
if (null == subjectDnPattern || subjectDnPattern.isEmpty()) {
throw new IllegalStateException(SUBJECT_DN_PATTERN_PROPERTY + " property value cannot be null");
}
SUBJECT_DN_PATTERN = Pattern.compile(subjectDnPattern, Pattern.CASE_INSENSITIVE);
} catch (Throwable t) {
log.error(SUBJECT_DN_PATTERN_PROPERTY + " = '" + subjectDnPattern + "' could not be compiled", t);
throw new RuntimeException(t);
}
}
public static String[] splitProxiedDNs(String proxiedDNs, boolean allowDups) {
return ProxiedEntityUtils.splitProxiedDNs(proxiedDNs, allowDups);
}
public static String[] splitProxiedSubjectIssuerDNs(String proxiedDNs) {
return ProxiedEntityUtils.splitProxiedSubjectIssuerDNs(proxiedDNs);
}
public static String buildProxiedDN(String... dns) {
return ProxiedEntityUtils.buildProxiedDN(dns);
}
public static Collection<String> buildNormalizedDNList(String subjectDN, String issuerDN, String proxiedSubjectDNs,
String proxiedIssuerDNs) {
subjectDN = normalizeDN(subjectDN);
issuerDN = normalizeDN(issuerDN);
List<String> dnList = new ArrayList<>();
HashSet<String> subjects = new HashSet<>();
String subject = subjectDN.replaceAll("(?<!\\\\)([<>])", "\\\\$1");
dnList.add(subject);
subjects.add(subject);
dnList.add(issuerDN.replaceAll("(?<!\\\\)([<>])", "\\\\$1"));
if (proxiedSubjectDNs != null) {
if (proxiedIssuerDNs == null)
throw new IllegalArgumentException(
"If proxied subject DNs are supplied, then issuer DNs must be supplied as well.");
String[] subjectDNarray = splitProxiedDNs(proxiedSubjectDNs, true);
String[] issuerDNarray = splitProxiedDNs(proxiedIssuerDNs, true);
if (subjectDNarray.length != issuerDNarray.length)
throw new IllegalArgumentException(
"Subject and issuer DN lists do not have the same number of entries: "
+ Arrays.toString(subjectDNarray) + " vs " + Arrays.toString(issuerDNarray));
for (int i = 0; i < subjectDNarray.length; ++i) {
subjectDNarray[i] = normalizeDN(subjectDNarray[i]);
if (!subjects.contains(subjectDNarray[i])) {
issuerDNarray[i] = normalizeDN(issuerDNarray[i]);
subjects.add(subjectDNarray[i]);
dnList.add(subjectDNarray[i]);
dnList.add(issuerDNarray[i]);
if (issuerDNarray[i].equalsIgnoreCase(subjectDNarray[i]))
throw new IllegalArgumentException(
"Subject DN " + issuerDNarray[i] + " was passed as an issuer DN.");
if (SUBJECT_DN_PATTERN.matcher(issuerDNarray[i]).find())
throw new IllegalArgumentException(
"It appears that a subject DN (" + issuerDNarray[i] + ") was passed as an issuer DN.");
}
}
}
return dnList;
}
public static String buildNormalizedProxyDN(String subjectDN, String issuerDN, String proxiedSubjectDNs,
String proxiedIssuerDNs) {
StringBuilder sb = new StringBuilder();
for (String escapedDN : buildNormalizedDNList(subjectDN, issuerDN, proxiedSubjectDNs, proxiedIssuerDNs)) {
if (sb.length() == 0)
sb.append(escapedDN);
else
sb.append('<').append(escapedDN).append('>');
}
return sb.toString();
}
public static String getCommonName(String dn) {
return ProxiedEntityUtils.getCommonName(dn);
}
public static String[] getOrganizationalUnits(String dn) {
return ProxiedEntityUtils.getOrganizationalUnits(dn);
}
public static String getShortName(String dn) {
return ProxiedEntityUtils.getShortName(dn);
}
public static boolean isServerDN(String dn) {
return NpeUtils.isNPE(dn);
}
public static String getUserDN(String[] dns) {
return getUserDN(dns, false);
}
public static String getUserDN(String[] dns, boolean issuerDNs) {
if (issuerDNs && (dns.length % 2) != 0)
throw new IllegalArgumentException("DNs array is not a subject/issuer DN list: " + Arrays.toString(dns));
for (int i = 0; i < dns.length; i += (issuerDNs) ? 2 : 1) {
String dn = dns[i];
if (!isServerDN(dn))
return dn;
}
return null;
}
public static String[] getComponents(String dn, String componentName) {
return ProxiedEntityUtils.getComponents(dn, componentName);
}
public static String normalizeDN(String userName) {
return ProxiedEntityUtils.normalizeDN(userName);
}
/**
* Encapsulates the known/valid NPE OU list, and whatever else is needed for NPE
* handling. Static inner-class, so that injection of the configured OUs is only
* performed on-demand/if needed
*/
public static class NpeUtils {
/** Property containing a comma-delimited list of OUs */
public static final String NPE_OU_PROPERTY = "npe.ou.entries";
/** Parsed NPE OU identifiers */
static final List<String> NPE_OU_LIST;
private NpeUtils() {
}
static {
List<String> npeOUs = new ArrayList<>();
String ouString = System.getProperty(NPE_OU_PROPERTY, PROPS.getProperty(NPE_OU_PROPERTY));
if (null == ouString || ouString.isEmpty()) {
throw new IllegalStateException("No '" + NPE_OU_PROPERTY + "' value has been configured");
}
// Normalize and load...
String[] ouArray = ouString.split(",");
for (String ou : ouArray) {
npeOUs.add(ou.trim().toUpperCase());
}
NPE_OU_LIST = Collections.unmodifiableList(npeOUs);
}
static boolean isNPE(String dn) {
String[] ouList = ProxiedEntityUtils.getOrganizationalUnits(dn);
for (String ou : ouList) {
if (NPE_OU_LIST.contains(ou.toUpperCase())) {
return true;
}
}
return false;
}
}
public static void main(String[] args) {
QueryStringDecoder decoder = new QueryStringDecoder("https://my.server:3000");
System.out.println(decoder.path());
}
}
@@ -0,0 +1,34 @@
package timely.auth.util;
import java.util.Collection;
import java.util.Map;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import io.netty.handler.codec.http.HttpHeaders;
public class HttpHeaderUtils {
public static Multimap<String, String> toMultimap(HttpHeaders headers) {
Multimap<String, String> headerMultimap = HashMultimap.create();
if (headers != null) {
for (Map.Entry<String, String> e : headers.entries()) {
headerMultimap.put(e.getKey(), e.getValue());
}
}
return headerMultimap;
}
public static String getSingleHeader(Multimap<String, String> headers, String headerName, boolean enforceOneValue)
throws IllegalArgumentException {
String value = null;
Collection<String> values = (headers == null) ? null : headers.get(headerName);
if (values != null && !values.isEmpty()) {
if (values.size() > 1 && enforceOneValue) {
throw new IllegalArgumentException(headerName + " was specified multiple times, which is not allowed");
}
value = values.stream().findFirst().orElse(null);
}
return value;
}
}
@@ -0,0 +1,188 @@
package timely.auth.util;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.regex.Pattern;
import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import com.google.common.collect.Multimap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.netty.http.auth.TimelyAuthenticationToken;
public class ProxiedEntityUtils {
private static final Logger log = LoggerFactory.getLogger(ProxiedEntityUtils.class);
public static String[] splitProxiedDNs(String proxiedDNs, boolean allowDups) {
String[] dns;
if (proxiedDNs.indexOf('<') < 0) {
dns = new String[] { proxiedDNs };
} else {
Collection<String> dnCollection = allowDups ? new ArrayList<>() : new LinkedHashSet<>();
String[] pieces = proxiedDNs.split("(?<!\\\\)<|(?<!\\\\)>");
for (String piece : pieces) {
if (piece.trim().length() > 0)
dnCollection.add(piece);
}
dns = dnCollection.toArray(new String[0]);
}
return dns;
}
public static String[] splitProxiedSubjectIssuerDNs(String proxiedDNs) {
String[] dns;
if (proxiedDNs.indexOf('<') < 0) {
dns = new String[] { proxiedDNs };
} else {
HashSet<String> subjects = new HashSet<>();
List<String> dnList = new ArrayList<>();
String[] pieces = proxiedDNs.split("(?<!\\\\)<|(?<!\\\\)>");
ArrayList<String> trimmedPieces = new ArrayList<>(pieces.length);
for (String piece : pieces) {
piece = piece.trim();
if (piece.length() > 0)
trimmedPieces.add(piece);
}
if ((trimmedPieces.size() % 2) != 0)
throw new IllegalArgumentException("Invalid proxied DNs list does not have entries in pairs.");
for (int i = 0; i < trimmedPieces.size(); i += 2) {
String subject = trimmedPieces.get(i);
String issuer = trimmedPieces.get(i + 1);
if (subject.length() > 0 && !subjects.contains(subject)) {
subjects.add(subject);
dnList.add(subject);
dnList.add(issuer);
}
}
dns = dnList.toArray(new String[0]);
}
return dns;
}
public static String buildProxiedDN(String... dns) {
StringBuilder sb = new StringBuilder();
for (String dn : dns) {
String escapedDN = dn.replaceAll("(?<!\\\\)(<|>)", "\\\\$1");
if (sb.length() == 0)
sb.append(escapedDN);
else
sb.append('<').append(escapedDN).append('>');
}
return sb.toString();
}
public static String getCommonName(String dn) {
String[] comps = getComponents(dn, "CN");
return comps.length >= 1 ? comps[0] : null;
}
public static String[] getOrganizationalUnits(String dn) {
return getComponents(dn, "OU");
}
public static String getShortName(String dn) {
String cn = getCommonName(dn);
if (cn == null)
cn = dn;
String sid = cn;
int idx = cn.lastIndexOf(' ');
if (idx >= 0)
sid = cn.substring(idx + 1);
return sid;
}
public static String[] getComponents(String dn, String componentName) {
componentName = componentName.toUpperCase();
ArrayList<String> components = new ArrayList<>();
try {
LdapName name = new LdapName(dn);
for (Rdn rdn : name.getRdns()) {
if (componentName.equals(rdn.getType().toUpperCase())) {
components.add(String.valueOf(rdn.getValue()));
}
}
} catch (InvalidNameException e) {
// ignore -- invalid name, so can't find components
}
return components.toArray(new String[0]);
}
/**
* Attempts to normalize a DN by taking it and reversing the components if it
* doesn't start with CN. Some systems requires the DN components be in a
* specific order, or that order reversed. We cannot arbitrarily reorder the
* components however, e.g., sorting them.
*/
public static String normalizeDN(String userName) {
String normalizedUserName = userName.trim().toLowerCase();
try {
if (!normalizedUserName.startsWith("cn") || Pattern.compile(",[^ ]").matcher(normalizedUserName).find()) {
LdapName name = new LdapName(userName);
StringBuilder sb = new StringBuilder();
ArrayList<Rdn> rdns = new ArrayList<>(name.getRdns());
if (rdns.size() > 0 && !rdns.get(0).toString().toLowerCase().startsWith("cn"))
Collections.reverse(rdns);
for (Rdn rdn : rdns) {
if (sb.length() > 0)
sb.append(", ");
sb.append(rdn.toString());
}
normalizedUserName = sb.toString().toLowerCase();
}
} catch (InvalidNameException e) {
// ignore -- might be a sid rather than a DN
}
log.trace("Normalized [" + userName + "] into [" + normalizedUserName + "]");
return normalizedUserName;
}
public static void addProxyHeaders(Multimap<String, String> headers, X509Certificate clientCert) {
if (clientCert != null) {
List<String> proxiedEntitiesList = new ArrayList<>();
if (headers.containsKey(TimelyAuthenticationToken.PROXIED_ENTITIES_HEADER)) {
String proxiedEntities = HttpHeaderUtils.getSingleHeader(headers,
TimelyAuthenticationToken.PROXIED_ENTITIES_HEADER, false);
headers.removeAll(TimelyAuthenticationToken.PROXIED_ENTITIES_HEADER);
String[] preExistingProxiedEntities = ProxiedEntityUtils.splitProxiedDNs(proxiedEntities, false);
if (preExistingProxiedEntities.length > 0) {
proxiedEntitiesList.addAll(Arrays.asList(preExistingProxiedEntities));
}
}
String subjectDN = DnUtils.normalizeDN(clientCert.getSubjectDN().getName());
proxiedEntitiesList.add(subjectDN);
String[] newProxiedEntities = new String[proxiedEntitiesList.size()];
proxiedEntitiesList.toArray(newProxiedEntities);
headers.put(TimelyAuthenticationToken.PROXIED_ENTITIES_HEADER,
ProxiedEntityUtils.buildProxiedDN(newProxiedEntities));
List<String> proxiedIssuersList = new ArrayList<>();
if (headers.containsKey(TimelyAuthenticationToken.PROXIED_ISSUERS_HEADER)) {
String proxiedIssuers = HttpHeaderUtils.getSingleHeader(headers,
TimelyAuthenticationToken.PROXIED_ISSUERS_HEADER, false);
headers.removeAll(TimelyAuthenticationToken.PROXIED_ISSUERS_HEADER);
String[] preExistingProxiedIssuers = ProxiedEntityUtils.splitProxiedDNs(proxiedIssuers, false);
if (preExistingProxiedIssuers.length > 0) {
proxiedEntitiesList.addAll(Arrays.asList(preExistingProxiedIssuers));
}
}
String issuerDN = DnUtils.normalizeDN(clientCert.getIssuerDN().getName());
proxiedIssuersList.add(issuerDN);
String[] newProxiedIssuers = new String[proxiedIssuersList.size()];
proxiedIssuersList.toArray(newProxiedIssuers);
headers.put(TimelyAuthenticationToken.PROXIED_ISSUERS_HEADER,
ProxiedEntityUtils.buildProxiedDN(newProxiedIssuers));
}
}
}
@@ -0,0 +1,81 @@
package timely.auth.util;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.TimeUnit;
import org.apache.accumulo.core.client.BatchDeleter;
import org.apache.accumulo.core.client.BatchScanner;
import org.apache.accumulo.core.client.BatchWriterConfig;
import org.apache.accumulo.core.client.Connector;
import org.apache.accumulo.core.client.IteratorSetting;
import org.apache.accumulo.core.client.Scanner;
import org.apache.accumulo.core.client.ScannerBase;
import org.apache.accumulo.core.client.TableNotFoundException;
import org.apache.accumulo.core.security.Authorizations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.auth.AuthorizationsMinimizer;
import timely.auth.ConfigurableVisibilityFilter;
import timely.auth.ScannerBaseDelegate;
public class ScannerHelper {
private static final Logger logger = LoggerFactory.getLogger(ScannerHelper.class);
public static Scanner createScanner(Connector connector, String tableName,
Collection<Authorizations> authorizations) throws TableNotFoundException {
if (authorizations == null || authorizations.isEmpty())
throw new IllegalArgumentException("Authorizations must not be empty.");
Iterator<Authorizations> iter = AuthorizationsMinimizer.minimize(authorizations).iterator();
Scanner scanner = connector.createScanner(tableName, iter.next());
addVisibilityFilters(iter, scanner);
return scanner;
}
public static BatchScanner createBatchScanner(Connector connector, String tableName,
Collection<Authorizations> authorizations, int numQueryThreads) throws TableNotFoundException {
if (authorizations == null || authorizations.isEmpty())
throw new IllegalArgumentException("Authorizations must not be empty.");
Iterator<Authorizations> iter = AuthorizationsMinimizer.minimize(authorizations).iterator();
BatchScanner batchScanner = connector.createBatchScanner(tableName, iter.next(), numQueryThreads);
addVisibilityFilters(iter, batchScanner);
return batchScanner;
}
public static BatchDeleter createBatchDeleter(Connector connector, String tableName,
Collection<Authorizations> authorizations, int numQueryThreads, long maxMemory, long maxLatency,
int maxWriteThreads) throws TableNotFoundException {
if (authorizations == null || authorizations.isEmpty())
throw new IllegalArgumentException("Authorizations must not be empty.");
Iterator<Authorizations> iter = AuthorizationsMinimizer.minimize(authorizations).iterator();
BatchWriterConfig bwCfg = new BatchWriterConfig().setMaxLatency(maxLatency, TimeUnit.MILLISECONDS)
.setMaxMemory(maxMemory).setMaxWriteThreads(maxWriteThreads);
BatchDeleter batchDeleter = connector.createBatchDeleter(tableName, iter.next(), numQueryThreads, bwCfg);
addVisibilityFilters(iter, batchDeleter);
return batchDeleter;
}
protected static void addVisibilityFilters(Iterator<Authorizations> iter, ScannerBase scanner) {
for (int priority = 10; iter.hasNext(); priority++) {
IteratorSetting cfg = new IteratorSetting(priority, ConfigurableVisibilityFilter.class);
cfg.setName("visibilityFilter" + priority);
cfg.addOption(ConfigurableVisibilityFilter.AUTHORIZATIONS_OPT, iter.next().toString());
// Set the visibility filter as a "system" iterator, which means that normal
// modify, remove, clear operations performed
// on the scanner will not modify/remove/clear this iterator. This way, if a
// query logic attempts to reconfigure the
// scanner's iterators, then this iterator will remain intact.
if (scanner instanceof ScannerBaseDelegate) {
((ScannerBaseDelegate) scanner).addSystemScanIterator(cfg);
} else {
logger.warn("Adding system visibility filter to non-wrapped scanner {}.", scanner.getClass(),
new Exception());
scanner.addScanIterator(cfg);
}
}
}
}
@@ -9,7 +9,9 @@ import javax.validation.Valid;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
@Validated
@Component @Component
@ConfigurationProperties(prefix = "timely") @ConfigurationProperties(prefix = "timely")
public class Configuration { public class Configuration {
@@ -19,6 +21,7 @@ public class Configuration {
private HashMap<String, Integer> metricAgeOffDays = new HashMap<>(); private HashMap<String, Integer> metricAgeOffDays = new HashMap<>();
private List<String> metricsReportIgnoredTags = new ArrayList<>(); private List<String> metricsReportIgnoredTags = new ArrayList<>();
private String instance = null; private String instance = null;
private String defaultVisibility = null;
@Valid @Valid
@NestedConfigurationProperty @NestedConfigurationProperty
@@ -71,6 +74,14 @@ public class Configuration {
return instance; return instance;
} }
public String getDefaultVisibility() {
return defaultVisibility;
}
public void setDefaultVisibility(String defaultVisibility) {
this.defaultVisibility = defaultVisibility;
}
public HashMap<String, Integer> getMetricAgeOffDays() { public HashMap<String, Integer> getMetricAgeOffDays() {
return metricAgeOffDays; return metricAgeOffDays;
} }
@@ -2,9 +2,9 @@ package timely.configuration;
public class MetaCache { public class MetaCache {
private long expirationMinutes = 60; private long expirationMinutes = 1440;
private int initialCapacity = 2000; private int cacheRefreshMinutes = -1;
private long maxCapacity = 10000; private long maxTagValues = 100;
public long getExpirationMinutes() { public long getExpirationMinutes() {
return expirationMinutes; return expirationMinutes;
@@ -14,19 +14,19 @@ public class MetaCache {
this.expirationMinutes = expirationMinutes; this.expirationMinutes = expirationMinutes;
} }
public int getInitialCapacity() { public void setCacheRefreshMinutes(int cacheRefreshMinutes) {
return initialCapacity; this.cacheRefreshMinutes = cacheRefreshMinutes;
} }
public void setInitialCapacity(int initialCapacity) { public int getCacheRefreshMinutes() {
this.initialCapacity = initialCapacity; return cacheRefreshMinutes;
} }
public long getMaxCapacity() { public long getMaxTagValues() {
return maxCapacity; return maxTagValues;
} }
public void setMaxCapacity(long maxCapacity) { public void setMaxTagValues(long maxTagValues) {
this.maxCapacity = maxCapacity; this.maxTagValues = maxTagValues;
} }
} }
@@ -6,18 +6,29 @@ import org.springframework.boot.context.properties.NestedConfigurationProperty;
public class Security { public class Security {
private boolean allowAnonymousAccess = false; private boolean allowAnonymousHttpAccess = false;
private boolean allowAnonymousWsAccess = false;
private int sessionMaxAge = 86400; private int sessionMaxAge = 86400;
private int cacheExpirationMinutes = 60;
private int cacheRefreshMinutes = 5;
@Valid @Valid
@NestedConfigurationProperty @NestedConfigurationProperty
private ServerSsl serverSsl = new ServerSsl(); private ServerSsl serverSsl = new ServerSsl();
public boolean isAllowAnonymousAccess() { public boolean isAllowAnonymousHttpAccess() {
return allowAnonymousAccess; return allowAnonymousHttpAccess;
} }
public void setAllowAnonymousAccess(boolean allowAnonymousAccess) { public void setAllowAnonymousHttpAccess(boolean allowAnonymousHttpAccess) {
this.allowAnonymousAccess = allowAnonymousAccess; this.allowAnonymousHttpAccess = allowAnonymousHttpAccess;
}
public boolean isAllowAnonymousWsAccess() {
return allowAnonymousWsAccess;
}
public void setAllowAnonymousWsAccess(boolean allowAnonymousWsAccess) {
this.allowAnonymousWsAccess = allowAnonymousWsAccess;
} }
public int getSessionMaxAge() { public int getSessionMaxAge() {
@@ -28,6 +39,22 @@ public class Security {
this.sessionMaxAge = sessionMaxAge; this.sessionMaxAge = sessionMaxAge;
} }
public void setCacheExpirationMinutes(int cacheExpirationMinutes) {
this.cacheExpirationMinutes = cacheExpirationMinutes;
}
public int getCacheExpirationMinutes() {
return cacheExpirationMinutes;
}
public void setCacheRefreshMinutes(int cacheRefreshMinutes) {
this.cacheRefreshMinutes = cacheRefreshMinutes;
}
public int getCacheRefreshMinutes() {
return cacheRefreshMinutes;
}
public ServerSsl getServerSsl() { public ServerSsl getServerSsl() {
return serverSsl; return serverSsl;
} }
@@ -1,8 +1,11 @@
package timely.netty.http; package timely.netty.http;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.List; import java.util.List;
import com.google.common.collect.Multimap;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder; import io.netty.handler.codec.MessageToMessageDecoder;
@@ -12,6 +15,7 @@ import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.QueryStringDecoder; import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder; import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import timely.api.annotation.AnnotationResolver; import timely.api.annotation.AnnotationResolver;
@@ -22,16 +26,20 @@ import timely.api.request.timeseries.HttpRequest;
import timely.api.response.StrictTransportResponse; import timely.api.response.StrictTransportResponse;
import timely.api.response.TimelyException; import timely.api.response.TimelyException;
import timely.auth.AuthCache; import timely.auth.AuthCache;
import timely.auth.AuthenticationService;
import timely.auth.SubjectIssuerDNPair;
import timely.auth.TimelyPrincipal;
import timely.auth.util.HttpHeaderUtils;
import timely.configuration.Http; import timely.configuration.Http;
import timely.configuration.Security; import timely.configuration.Security;
import timely.netty.Constants; import timely.netty.Constants;
import timely.netty.http.auth.TimelyAuthenticationToken;
public class HttpRequestDecoder extends MessageToMessageDecoder<FullHttpRequest> implements TimelyHttpHandler { public class HttpRequestDecoder extends MessageToMessageDecoder<FullHttpRequest> implements TimelyHttpHandler {
private static final Logger LOG = LoggerFactory.getLogger(HttpRequestDecoder.class); private static final Logger LOG = LoggerFactory.getLogger(HttpRequestDecoder.class);
private static final String LOG_RECEIVED_REQUEST = "Received HTTP request {}"; private static final String LOG_RECEIVED_REQUEST = "Received HTTP request {}";
private static final String LOG_PARSED_REQUEST = "Parsed request {}"; private static final String LOG_PARSED_REQUEST = "Parsed request {}";
private static final String NO_AUTHORIZATIONS = "";
private final Security security; private final Security security;
private final String nonSecureRedirectAddress; private final String nonSecureRedirectAddress;
@@ -42,9 +50,11 @@ public class HttpRequestDecoder extends MessageToMessageDecoder<FullHttpRequest>
this.nonSecureRedirectAddress = http.getRedirectPath(); this.nonSecureRedirectAddress = http.getRedirectPath();
} }
public static String getSessionId(FullHttpRequest msg, boolean anonymousAccessAllowed) { public static String getSessionId(FullHttpRequest msg) {
Multimap<String, String> headers = HttpHeaderUtils.toMultimap(msg.headers());
Collection<String> cookies = headers.get(Names.COOKIE);
final StringBuilder buf = new StringBuilder(); final StringBuilder buf = new StringBuilder();
msg.headers().getAll(Names.COOKIE).forEach(h -> { cookies.forEach(h -> {
ServerCookieDecoder.STRICT.decode(h).forEach(c -> { ServerCookieDecoder.STRICT.decode(h).forEach(c -> {
if (c.name().equals(Constants.COOKIE_NAME)) { if (c.name().equals(Constants.COOKIE_NAME)) {
if (buf.length() == 0) { if (buf.length() == 0) {
@@ -53,13 +63,11 @@ public class HttpRequestDecoder extends MessageToMessageDecoder<FullHttpRequest>
} }
}); });
}); });
String sessionId = buf.toString(); if (buf.length() == 0) {
if (sessionId.length() == 0 && anonymousAccessAllowed) { return null;
sessionId = NO_AUTHORIZATIONS; } else {
} else if (sessionId.length() == 0) { return buf.toString();
sessionId = null;
} }
return sessionId;
} }
@Override @Override
@@ -74,7 +82,7 @@ public class HttpRequestDecoder extends MessageToMessageDecoder<FullHttpRequest>
return; return;
} }
final String sessionId = getSessionId(msg, this.security.isAllowAnonymousAccess()); final String sessionId = getSessionId(msg);
LOG.trace("SessionID: " + sessionId); LOG.trace("SessionID: " + sessionId);
HttpRequest request; HttpRequest request;
@@ -97,9 +105,28 @@ public class HttpRequestDecoder extends MessageToMessageDecoder<FullHttpRequest>
LOG.warn("Unhandled HTTP request type {}", msg.getMethod()); LOG.warn("Unhandled HTTP request type {}", msg.getMethod());
throw e; throw e;
} }
if (request instanceof AuthenticatedRequest && sessionId != null) { if (request instanceof AuthenticatedRequest) {
((AuthenticatedRequest) request).setSessionId(sessionId); Multimap<String, String> headers = HttpHeaderUtils.toMultimap(msg.headers());
((AuthenticatedRequest) request).addHeaders(msg.headers().entries()); ((AuthenticatedRequest) request).addHeaders(headers);
X509Certificate clientCert = AuthenticationService.getClientCertificate(ctx);
TimelyAuthenticationToken token;
if (StringUtils.isNotBlank(sessionId)) {
TimelyPrincipal principal;
((AuthenticatedRequest) request).setSessionId(sessionId);
if (AuthCache.containsKey(sessionId)) {
principal = AuthCache.get(sessionId);
} else {
principal = TimelyPrincipal.anonymousPrincipal();
}
SubjectIssuerDNPair dn = principal.getPrimaryUser().getDn();
token = AuthenticationService.getAuthenticationToken(null, dn.subjectDN(), dn.issuerDN());
} else if (clientCert != null) {
token = AuthenticationService.getAuthenticationToken(clientCert, headers);
} else {
SubjectIssuerDNPair dn = TimelyPrincipal.anonymousPrincipal().getPrimaryUser().getDn();
token = AuthenticationService.getAuthenticationToken(null, dn.subjectDN(), dn.issuerDN());
}
((AuthenticatedRequest) request).setToken(token);
} }
request.setHttpRequest(msg.copy()); request.setHttpRequest(msg.copy());
LOG.trace(LOG_PARSED_REQUEST, request); LOG.trace(LOG_PARSED_REQUEST, request);
@@ -113,7 +140,15 @@ public class HttpRequestDecoder extends MessageToMessageDecoder<FullHttpRequest>
return; return;
} }
try { try {
AuthCache.enforceAccess(security, request); if (request instanceof AuthenticatedRequest) {
try {
AuthenticationService.enforceAccess((AuthenticatedRequest) request);
} catch (TimelyException e) {
if (!security.isAllowAnonymousHttpAccess()) {
throw e;
}
}
}
} catch (Exception e) { } catch (Exception e) {
out.clear(); out.clear();
throw e; throw e;
@@ -1,27 +1,35 @@
package timely.netty.http.auth; package timely.netty.http.auth;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import timely.api.request.auth.BasicAuthLoginRequest; import timely.api.request.auth.BasicAuthLoginRequest;
import timely.auth.AuthenticationService; import timely.auth.AuthenticationService;
import timely.auth.TimelyPrincipal;
import timely.configuration.Http; import timely.configuration.Http;
import timely.configuration.Security; import timely.configuration.Security;
public class BasicAuthLoginRequestHandler extends TimelyLoginRequestHandler<BasicAuthLoginRequest> { public class BasicAuthLoginRequestHandler extends TimelyLoginRequestHandler<BasicAuthLoginRequest> {
private static final Logger LOG = LoggerFactory.getLogger(BasicAuthLoginRequestHandler.class);
public BasicAuthLoginRequestHandler(Security security, Http http) { public BasicAuthLoginRequestHandler(Security security, Http http) {
super(security, http); super(security, http);
} }
@Override @Override
protected Authentication authenticate(ChannelHandlerContext ctx, BasicAuthLoginRequest msg) protected TimelyPrincipal authenticate(ChannelHandlerContext ctx, BasicAuthLoginRequest msg, String sessionId)
throws AuthenticationException { throws AuthenticationException {
// Perform the login process using username/password // Perform the login process using username/password
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(msg.getUsername(), UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(msg.getUsername(),
msg.getPassword()); msg.getPassword());
return AuthenticationService.getAuthenticationManager().authenticate(token); TimelyPrincipal principal = AuthenticationService.authenticate(token, sessionId);
LOG.debug("Authenticated user {} with authorizations {}", principal.getPrimaryUser().getDn().subjectDN(),
principal.getAuthorizationsString());
return principal;
} }
} }
@@ -0,0 +1,115 @@
package timely.netty.http.auth;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import timely.auth.SubjectIssuerDNPair;
import timely.auth.TimelyPrincipal;
import timely.auth.TimelyUser;
import timely.auth.util.DnUtils;
import timely.auth.util.HttpHeaderUtils;
public class TimelyAuthenticationToken extends PreAuthenticatedAuthenticationToken {
private static final Logger LOG = LoggerFactory.getLogger(TimelyAuthenticationToken.class);
public static final String PROXIED_ENTITIES_HEADER = "X-ProxiedEntitiesChain";
public static final String PROXIED_ISSUERS_HEADER = "X-ProxiedIssuersChain";
private Multimap<String, String> httpHeaders = null;
private X509Certificate clientCert = null;
private TimelyPrincipal timelyPrincipal;
public TimelyAuthenticationToken(String subjectDn, Object clientCert, Multimap<String, String> httpHeaders) {
super(subjectDn, clientCert);
if (httpHeaders == null) {
this.httpHeaders = HashMultimap.create();
} else {
this.httpHeaders = httpHeaders;
}
if (clientCert instanceof X509Certificate) {
this.clientCert = (X509Certificate) clientCert;
}
String issuerDn = clientCert == null ? null : this.clientCert.getIssuerDN().getName();
String proxiedEntities;
String proxiedIssuers;
try {
proxiedEntities = HttpHeaderUtils.getSingleHeader(httpHeaders, PROXIED_ENTITIES_HEADER, true);
proxiedIssuers = HttpHeaderUtils.getSingleHeader(httpHeaders, PROXIED_ISSUERS_HEADER, true);
} catch (IllegalArgumentException e) {
LOG.error(e.getMessage(), e);
throw e;
}
if (proxiedEntities != null && proxiedIssuers == null) {
LOG.error(PROXIED_ENTITIES_HEADER + " supplied, but missing " + PROXIED_ISSUERS_HEADER);
throw new IllegalArgumentException(
PROXIED_ENTITIES_HEADER + " supplied, but missing " + PROXIED_ISSUERS_HEADER);
}
List<SubjectIssuerDNPair> entities = extractEntities(subjectDn, issuerDn, proxiedEntities, proxiedIssuers);
long now = System.currentTimeMillis();
List<TimelyUser> timelyUsers = new ArrayList<>();
for (SubjectIssuerDNPair ent : entities) {
TimelyUser.UserType userType = DnUtils.isServerDN(ent.subjectDN()) ? TimelyUser.UserType.SERVER
: TimelyUser.UserType.USER;
timelyUsers.add(new TimelyUser(ent, userType, null, null, null, now));
}
timelyPrincipal = new TimelyPrincipal(timelyUsers);
LOG.trace("Created TimelyAuthenticationToken: {}", timelyPrincipal.getName());
}
public TimelyAuthenticationToken(String subjectDn, String issuerDn, Object clientCert) {
super(subjectDn, clientCert);
if (clientCert instanceof X509Certificate) {
this.clientCert = (X509Certificate) clientCert;
}
TimelyUser.UserType userType = DnUtils.isServerDN(subjectDn) ? TimelyUser.UserType.SERVER
: TimelyUser.UserType.USER;
TimelyUser timelyUser = new TimelyUser(SubjectIssuerDNPair.of(subjectDn, issuerDn), userType, null, null, null,
System.currentTimeMillis());
timelyPrincipal = new TimelyPrincipal(timelyUser);
LOG.trace("Created TimelyAuthenticationToken: {}", timelyPrincipal.getName());
}
private List<SubjectIssuerDNPair> extractEntities(String subjectDn, String issuerDn, String proxiedEntities,
String proxiedIssuers) {
List<SubjectIssuerDNPair> entities = new ArrayList<>();
entities.add(SubjectIssuerDNPair.of(subjectDn, issuerDn));
if (proxiedEntities != null) {
String[] subjects = DnUtils.splitProxiedDNs(proxiedEntities, true);
if (proxiedIssuers == null) {
throw new IllegalArgumentException("Proxied issuers must be supplied if proxied subjects are supplied");
}
String[] issuers = DnUtils.splitProxiedDNs(proxiedIssuers, true);
if (subjects.length != issuers.length) {
throw new IllegalArgumentException("Proxied subjects and issuers don't match up. Subjects="
+ proxiedEntities + " , Issuers=" + proxiedIssuers);
}
for (int i = 0; i < subjects.length; ++i) {
entities.add(SubjectIssuerDNPair.of(subjects[i], issuers[i]));
}
}
return entities;
}
public Multimap<String, String> getHttpHeaders() {
return httpHeaders;
}
public void setTimelyPrincipal(TimelyPrincipal timelyPrincipal) {
this.timelyPrincipal = timelyPrincipal;
}
public TimelyPrincipal getTimelyPrincipal() {
return timelyPrincipal;
}
public X509Certificate getClientCert() {
return clientCert;
}
}
@@ -15,8 +15,7 @@ import io.netty.handler.codec.http.cookie.DefaultCookie;
import io.netty.handler.codec.http.cookie.ServerCookieEncoder; import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication; import timely.auth.TimelyPrincipal;
import timely.auth.AuthCache;
import timely.configuration.Http; import timely.configuration.Http;
import timely.configuration.Security; import timely.configuration.Security;
import timely.netty.Constants; import timely.netty.Constants;
@@ -38,14 +37,15 @@ public abstract class TimelyLoginRequestHandler<T> extends SimpleChannelInboundH
protected final void channelRead0(ChannelHandlerContext ctx, T loginRequest) throws Exception { protected final void channelRead0(ChannelHandlerContext ctx, T loginRequest) throws Exception {
try { try {
LOG.trace("Authenticating {}", loginRequest); LOG.trace("Authenticating {}", loginRequest);
Authentication auth = authenticate(ctx, loginRequest); String sessionId = UUID.randomUUID().toString();
LOG.trace("Authenticated {}", auth); TimelyPrincipal principal = authenticate(ctx, loginRequest, sessionId);
String sessionId = URLEncoder.encode(UUID.randomUUID().toString(), StandardCharsets.UTF_8.name()); LOG.trace("Authenticated new sessionId {} for user {}", sessionId,
AuthCache.getCache().put(sessionId, auth); principal.getPrimaryUser().getDn().subjectDN());
String sessionIdEncoded = URLEncoder.encode(sessionId, StandardCharsets.UTF_8.name());
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
response.headers().set(Names.CONTENT_TYPE, Constants.JSON_TYPE); response.headers().set(Names.CONTENT_TYPE, Constants.JSON_TYPE);
response.headers().set(Names.CONTENT_LENGTH, response.content().readableBytes()); response.headers().set(Names.CONTENT_LENGTH, response.content().readableBytes());
DefaultCookie cookie = new DefaultCookie(Constants.COOKIE_NAME, sessionId); DefaultCookie cookie = new DefaultCookie(Constants.COOKIE_NAME, sessionIdEncoded);
cookie.setDomain(domain); cookie.setDomain(domain);
cookie.setMaxAge(maxAge); cookie.setMaxAge(maxAge);
cookie.setPath("/"); cookie.setPath("/");
@@ -64,5 +64,6 @@ public abstract class TimelyLoginRequestHandler<T> extends SimpleChannelInboundH
} }
protected abstract Authentication authenticate(ChannelHandlerContext ctx, T loginRequest) throws Exception; protected abstract TimelyPrincipal authenticate(ChannelHandlerContext ctx, T loginRequest, String entity)
throws Exception;
} }
@@ -1,21 +0,0 @@
package timely.netty.http.auth;
import java.security.cert.X509Certificate;
import io.netty.handler.codec.http.HttpHeaders;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
public class TimelyPreAuthenticatedAuthenticationToken extends PreAuthenticatedAuthenticationToken {
private HttpHeaders httpHeaders = null;
public TimelyPreAuthenticatedAuthenticationToken(String subjectDN, X509Certificate clientCert,
HttpHeaders httpHeaders) {
super(subjectDN, clientCert);
this.httpHeaders = httpHeaders;
}
public HttpHeaders getHttpHeaders() {
return httpHeaders;
}
}
@@ -0,0 +1,10 @@
package timely.netty.http.auth;
import java.util.Collection;
import org.springframework.security.core.userdetails.UserDetails;
public interface TimelyUserDetails extends UserDetails {
Collection<String> getRoles();
}
@@ -3,35 +3,50 @@ package timely.netty.http.auth;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.ssl.SslHandler; import org.slf4j.Logger;
import org.springframework.security.core.Authentication; import org.slf4j.LoggerFactory;
import timely.api.request.auth.X509LoginRequest; import timely.api.request.auth.X509LoginRequest;
import timely.api.response.TimelyException;
import timely.auth.AuthenticationService; import timely.auth.AuthenticationService;
import timely.auth.SubjectIssuerDNPair;
import timely.auth.TimelyPrincipal;
import timely.configuration.Http; import timely.configuration.Http;
import timely.configuration.Security; import timely.configuration.Security;
public class X509LoginRequestHandler extends TimelyLoginRequestHandler<X509LoginRequest> { public class X509LoginRequestHandler extends TimelyLoginRequestHandler<X509LoginRequest> {
private static final Logger LOG = LoggerFactory.getLogger(X509LoginRequestHandler.class);
public X509LoginRequestHandler(Security security, Http http) { public X509LoginRequestHandler(Security security, Http http) {
super(security, http); super(security, http);
} }
@Override @Override
protected Authentication authenticate(ChannelHandlerContext ctx, X509LoginRequest loginRequest) throws Exception { protected TimelyPrincipal authenticate(ChannelHandlerContext ctx, X509LoginRequest loginRequest, String sessionId)
throws Exception {
// If we are operating in 2 way SSL, then get the subjectDN from the // If we are operating in 2 way SSL, then get the subjectDN from the
// client certificate and perform the login process. // client certificate and perform the login process.
SslHandler sslHandler = (SslHandler) ctx.channel().pipeline().get("ssl"); TimelyPrincipal principal = null;
if (null != sslHandler) { try {
X509Certificate clientCert = (X509Certificate) sslHandler.engine().getSession().getPeerCertificates()[0]; X509Certificate clientCert = AuthenticationService.getClientCertificate(ctx);
String subjectDN = AuthenticationService.extractDN(clientCert); if (clientCert == null) {
HttpHeaders httpHeaders = loginRequest.getHttpRequest().headers(); throw new IllegalArgumentException("No client certificate found");
TimelyPreAuthenticatedAuthenticationToken token = new TimelyPreAuthenticatedAuthenticationToken(subjectDN, } else {
clientCert, httpHeaders); // login endpoint is for direct certificate only, not for proxying
return AuthenticationService.getAuthenticationManager().authenticate(token); TimelyAuthenticationToken token = AuthenticationService.getAuthenticationToken(clientCert,
} else { clientCert.getSubjectDN().getName(), clientCert.getIssuerDN().getName());
throw new IllegalStateException("The expected SSL handler is not in the pipeline."); SubjectIssuerDNPair pair = SubjectIssuerDNPair.of(clientCert.getSubjectDN().getName(),
clientCert.getIssuerDN().getName());
principal = AuthenticationService.authenticate(token, pair, sessionId);
LOG.debug("Authenticated user {} with authorizations {}",
principal.getPrimaryUser().getDn().subjectDN(), principal.getAuthorizationsString());
}
} catch (Exception e) {
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "Error performing login", e.getMessage(),
e);
} }
return principal;
} }
} }
@@ -20,7 +20,7 @@ public class HttpMetricsRequestHandler extends SimpleChannelInboundHandler<Metri
@Override @Override
protected void channelRead0(ChannelHandlerContext ctx, MetricsRequest msg) throws Exception { protected void channelRead0(ChannelHandlerContext ctx, MetricsRequest msg) throws Exception {
MetricsResponse r = new MetricsResponse(conf); MetricsResponse r = new MetricsResponse(conf);
String acceptHeader = msg.getRequestHeaders().get(Names.ACCEPT); String acceptHeader = msg.getRequestHeader(Names.ACCEPT);
sendResponse(ctx, r.toHttpResponse(acceptHeader)); sendResponse(ctx, r.toHttpResponse(acceptHeader));
} }
@@ -0,0 +1,51 @@
package timely.netty.websocket;
import java.security.cert.X509Certificate;
import java.util.List;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import io.netty.handler.codec.http.FullHttpRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.auth.AuthenticationService;
import timely.auth.util.HttpHeaderUtils;
import timely.netty.http.HttpRequestDecoder;
import timely.subscription.SubscriptionRegistry;
public class WebSocketFullRequestHandler extends MessageToMessageCodec<FullHttpRequest, FullHttpRequest> {
private static final Logger LOG = LoggerFactory.getLogger(WebSocketFullRequestHandler.class);
public WebSocketFullRequestHandler() {
super();
}
@Override
protected void decode(ChannelHandlerContext ctx, FullHttpRequest msg, List<Object> out) throws Exception {
msg.retain();
out.add(msg);
// If the session cookie exists, set its value on the ctx.
final String sessionId = HttpRequestDecoder.getSessionId(msg);
if (sessionId != null) {
ctx.channel().attr(SubscriptionRegistry.SESSION_ID_ATTR).set(sessionId);
LOG.trace("Found sessionId in WebSocket channel, setting sessionId {} on context", sessionId);
}
if (msg.headers() != null) {
ctx.channel().attr(WebSocketRequestDecoder.HTTP_HEADERS_ATTR)
.set(HttpHeaderUtils.toMultimap(msg.headers()));
}
X509Certificate clientCert = AuthenticationService.getClientCertificate(ctx);
if (clientCert != null) {
ctx.channel().attr(WebSocketRequestDecoder.CLIENT_CERT_ATTR).set(clientCert);
}
}
@Override
protected void encode(ChannelHandlerContext ctx, FullHttpRequest msg, List<Object> out) throws Exception {
msg.retain();
out.add(msg);
}
}
@@ -1,40 +0,0 @@
package timely.netty.websocket;
import java.util.List;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import io.netty.handler.codec.http.FullHttpRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import timely.configuration.Security;
import timely.netty.http.HttpRequestDecoder;
import timely.subscription.SubscriptionRegistry;
public class WebSocketHttpCookieHandler extends MessageToMessageCodec<FullHttpRequest, FullHttpRequest> {
private static final Logger LOG = LoggerFactory.getLogger(WebSocketHttpCookieHandler.class);
private boolean anonymousAccessAllowed;
public WebSocketHttpCookieHandler(Security security) {
super();
this.anonymousAccessAllowed = security.isAllowAnonymousAccess();
}
@Override
protected void decode(ChannelHandlerContext ctx, FullHttpRequest msg, List<Object> out) throws Exception {
msg.retain();
out.add(msg);
// If the session cookie exists, set its value on the ctx.
final String sessionId = HttpRequestDecoder.getSessionId(msg, this.anonymousAccessAllowed);
ctx.channel().attr(SubscriptionRegistry.SESSION_ID_ATTR).set(sessionId);
LOG.info("Found session id in WebSocket channel, setting sessionId {} on context", sessionId);
}
@Override
protected void encode(ChannelHandlerContext ctx, FullHttpRequest msg, List<Object> out) throws Exception {
msg.retain();
out.add(msg);
}
}
@@ -1,15 +1,19 @@
package timely.netty.websocket; package timely.netty.websocket;
import java.security.cert.X509Certificate;
import java.util.List; import java.util.List;
import com.google.common.collect.Multimap;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder; import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.ssl.SslCompletionEvent; import io.netty.handler.ssl.SslCompletionEvent;
import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent; import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.AttributeKey;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -17,12 +21,19 @@ import timely.api.request.AuthenticatedRequest;
import timely.api.request.WebSocketRequest; import timely.api.request.WebSocketRequest;
import timely.api.response.TimelyException; import timely.api.response.TimelyException;
import timely.auth.AuthCache; import timely.auth.AuthCache;
import timely.auth.AuthenticationService;
import timely.auth.SubjectIssuerDNPair;
import timely.auth.TimelyPrincipal;
import timely.configuration.Security; import timely.configuration.Security;
import timely.netty.http.auth.TimelyAuthenticationToken;
import timely.subscription.SubscriptionRegistry; import timely.subscription.SubscriptionRegistry;
import timely.util.JsonUtil; import timely.util.JsonUtil;
public class WebSocketRequestDecoder extends MessageToMessageDecoder<WebSocketFrame> { public class WebSocketRequestDecoder extends MessageToMessageDecoder<WebSocketFrame> {
public static final AttributeKey<Multimap<String, String>> HTTP_HEADERS_ATTR = AttributeKey.newInstance("headers");
public static final AttributeKey<X509Certificate> CLIENT_CERT_ATTR = AttributeKey.newInstance("clientCert");
private static final Logger LOG = LoggerFactory.getLogger(WebSocketRequestDecoder.class); private static final Logger LOG = LoggerFactory.getLogger(WebSocketRequestDecoder.class);
private Security security; private Security security;
@@ -39,14 +50,32 @@ public class WebSocketRequestDecoder extends MessageToMessageDecoder<WebSocketFr
String content = frame.text(); String content = frame.text();
WebSocketRequest request = JsonUtil.getObjectMapper().readValue(content, WebSocketRequest.class); WebSocketRequest request = JsonUtil.getObjectMapper().readValue(content, WebSocketRequest.class);
LOG.trace("Received WS request {}", content); LOG.trace("Received WS request {}", content);
String sessionId = ctx.channel().attr(SubscriptionRegistry.SESSION_ID_ATTR).get(); String sessionId = ctx.channel().attr(SubscriptionRegistry.SESSION_ID_ATTR).get();
if (request instanceof AuthenticatedRequest && !StringUtils.isEmpty(sessionId)) { Multimap<String, String> headers = ctx.channel().attr(HTTP_HEADERS_ATTR).get();
LOG.info("Found session id in WebSocket channel, setting sessionId {} on request", sessionId); X509Certificate clientCert = ctx.channel().attr(CLIENT_CERT_ATTR).get();
AuthenticatedRequest ar = (AuthenticatedRequest) request; if (request instanceof AuthenticatedRequest) {
ar.setSessionId(sessionId); if (headers != null) {
((AuthenticatedRequest) request).addHeaders(headers);
}
TimelyAuthenticationToken token;
if (StringUtils.isNotBlank(sessionId)) {
TimelyPrincipal principal;
((AuthenticatedRequest) request).setSessionId(sessionId);
if (AuthCache.containsKey(sessionId)) {
principal = AuthCache.get(sessionId);
} else {
principal = TimelyPrincipal.anonymousPrincipal();
}
SubjectIssuerDNPair dn = principal.getPrimaryUser().getDn();
token = AuthenticationService.getAuthenticationToken(null, dn.subjectDN(), dn.issuerDN());
} else if (clientCert != null) {
token = AuthenticationService.getAuthenticationToken(clientCert, headers);
} else {
SubjectIssuerDNPair dn = TimelyPrincipal.anonymousPrincipal().getPrimaryUser().getDn();
token = AuthenticationService.getAuthenticationToken(null, dn.subjectDN(), dn.issuerDN());
}
((AuthenticatedRequest) request).setToken(token);
} }
try { try {
request.validate(); request.validate();
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@@ -55,8 +84,16 @@ public class WebSocketRequestDecoder extends MessageToMessageDecoder<WebSocketFr
return; return;
} }
try { try {
AuthCache.enforceAccess(security, request); if (request instanceof AuthenticatedRequest) {
} catch (TimelyException e) { try {
AuthenticationService.enforceAccess((AuthenticatedRequest) request);
} catch (TimelyException e) {
if (!security.isAllowAnonymousWsAccess()) {
throw e;
}
}
}
} catch (Exception e) {
out.clear(); out.clear();
LOG.error("Error during access enforcment: " + e.getMessage()); LOG.error("Error during access enforcment: " + e.getMessage());
ctx.writeAndFlush(new CloseWebSocketFrame(1008, e.getMessage())); ctx.writeAndFlush(new CloseWebSocketFrame(1008, e.getMessage()));
@@ -99,10 +136,11 @@ public class WebSocketRequestDecoder extends MessageToMessageDecoder<WebSocketFr
} }
} }
} else if (evt instanceof SslCompletionEvent) { } else if (evt instanceof SslCompletionEvent) {
SslCompletionEvent ssl = (SslCompletionEvent) evt; LOG.debug("{}", ((SslCompletionEvent) evt).getClass().getSimpleName());
if (!ssl.isSuccess()) { } else if (evt instanceof WebSocketServerProtocolHandler.ServerHandshakeStateEvent) {
LOG.error("SSL error: {}", ssl.getClass().getSimpleName(), ssl.cause()); // The handshake completed succesfully and the channel was upgraded to
} // websockets
LOG.trace("SSL handshake completed successfully, upgraded channel to websockets");
} else { } else {
LOG.warn("Received unhandled user event {}", evt); LOG.warn("Received unhandled user event {}", evt);
} }
@@ -40,7 +40,7 @@ public class WSAddSubscriptionRequestHandler extends SimpleChannelInboundHandler
if (add.getDelayTime().isPresent()) { if (add.getDelayTime().isPresent()) {
delayTime = add.getDelayTime().get(); delayTime = add.getDelayTime().get();
} }
s.addMetric(metric, tags, startTime, endTime, delayTime); s.addMetric(add, metric, tags, startTime, endTime, delayTime);
} else { } else {
LOG.error("Unknown subscription id, create subscription first"); LOG.error("Unknown subscription id, create subscription first");
ctx.writeAndFlush(new CloseWebSocketFrame(1003, "Unknown subscription id, create subscription first")); ctx.writeAndFlush(new CloseWebSocketFrame(1003, "Unknown subscription id, create subscription first"));
@@ -9,6 +9,7 @@ import org.slf4j.LoggerFactory;
import timely.api.request.subscription.CreateSubscription; import timely.api.request.subscription.CreateSubscription;
import timely.configuration.Configuration; import timely.configuration.Configuration;
import timely.store.DataStore; import timely.store.DataStore;
import timely.store.cache.DataStoreCache;
import timely.subscription.Subscription; import timely.subscription.Subscription;
import timely.subscription.SubscriptionRegistry; import timely.subscription.SubscriptionRegistry;
@@ -16,10 +17,12 @@ public class WSCreateSubscriptionRequestHandler extends SimpleChannelInboundHand
private static final Logger LOG = LoggerFactory.getLogger(WSCreateSubscriptionRequestHandler.class); private static final Logger LOG = LoggerFactory.getLogger(WSCreateSubscriptionRequestHandler.class);
private final DataStore store; private final DataStore store;
private final DataStoreCache cache;
private final Configuration conf; private final Configuration conf;
public WSCreateSubscriptionRequestHandler(DataStore store, Configuration conf) { public WSCreateSubscriptionRequestHandler(DataStore store, DataStoreCache cache, Configuration conf) {
this.store = store; this.store = store;
this.cache = cache;
this.conf = conf; this.conf = conf;
} }
@@ -27,11 +30,11 @@ public class WSCreateSubscriptionRequestHandler extends SimpleChannelInboundHand
protected void channelRead0(ChannelHandlerContext ctx, CreateSubscription create) throws Exception { protected void channelRead0(ChannelHandlerContext ctx, CreateSubscription create) throws Exception {
final String subscriptionId = create.getSubscriptionId(); final String subscriptionId = create.getSubscriptionId();
SubscriptionRegistry.get().put(subscriptionId, SubscriptionRegistry.get().put(subscriptionId,
new Subscription(subscriptionId, create.getSessionId(), store, ctx, this.conf)); new Subscription(subscriptionId, create.getSessionId(), store, cache, ctx, this.conf));
// Store the session id as an attribute on the context. // Store the session id as an attribute on the context.
ctx.channel().attr(SubscriptionRegistry.SUBSCRIPTION_ID_ATTR).set(subscriptionId); ctx.channel().attr(SubscriptionRegistry.SUBSCRIPTION_ID_ATTR).set(subscriptionId);
LOG.info("Created subscription {} on channel {}", subscriptionId, ctx); LOG.info("[{}] Created subscription on channel {}", subscriptionId, ctx);
ctx.channel().closeFuture().addListener(new ChannelFutureListener() { ctx.channel().closeFuture().addListener(new ChannelFutureListener() {
@@ -39,7 +42,7 @@ public class WSCreateSubscriptionRequestHandler extends SimpleChannelInboundHand
public void operationComplete(ChannelFuture future) throws Exception { public void operationComplete(ChannelFuture future) throws Exception {
Subscription s = SubscriptionRegistry.get().remove(subscriptionId); Subscription s = SubscriptionRegistry.get().remove(subscriptionId);
if (null != s) { if (null != s) {
LOG.info("Channel closed, closing subscriptions for subscriptionId: " + subscriptionId); LOG.info("[{}] Channel closed, closing subscriptions", subscriptionId);
s.close(); s.close();
} }
} }
@@ -7,6 +7,7 @@ import java.util.Set;
import org.apache.accumulo.core.client.Scanner; import org.apache.accumulo.core.client.Scanner;
import org.apache.accumulo.core.client.TableNotFoundException; import org.apache.accumulo.core.client.TableNotFoundException;
import org.apache.accumulo.core.data.Range; import org.apache.accumulo.core.data.Range;
import timely.api.request.AuthenticatedRequest;
import timely.api.request.timeseries.QueryRequest; import timely.api.request.timeseries.QueryRequest;
import timely.api.request.timeseries.SearchLookupRequest; import timely.api.request.timeseries.SearchLookupRequest;
import timely.api.request.timeseries.SuggestRequest; import timely.api.request.timeseries.SuggestRequest;
@@ -36,8 +37,8 @@ public interface DataStore {
Set<Tag> getColumnFamilies(String metric, Map<String, String> tags) throws TableNotFoundException; Set<Tag> getColumnFamilies(String metric, Map<String, String> tags) throws TableNotFoundException;
Scanner createScannerForMetric(String sessionId, String metric, Map<String, String> tags, long startTime, Scanner createScannerForMetric(AuthenticatedRequest request, String metric, Map<String, String> tags,
long endTime, int lag, int scannerBatchSize, int scannerReadAhead) throws TimelyException; int scannerBatchSize, int scannerReadAhead) throws TimelyException;
void setCache(DataStoreCache cache); void setCache(DataStoreCache cache);
@@ -2,6 +2,7 @@ package timely.store;
import static org.apache.accumulo.core.conf.AccumuloConfiguration.getMemoryInBytes; import static org.apache.accumulo.core.conf.AccumuloConfiguration.getMemoryInBytes;
import static org.apache.accumulo.core.conf.AccumuloConfiguration.getTimeInMillis; import static org.apache.accumulo.core.conf.AccumuloConfiguration.getTimeInMillis;
import static timely.adapter.accumulo.MetricAdapter.VISIBILITY_TAG;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@@ -18,6 +19,7 @@ import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.SortedMap; import java.util.SortedMap;
import java.util.Timer; import java.util.Timer;
@@ -50,6 +52,8 @@ import org.apache.accumulo.core.data.*;
import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope; import org.apache.accumulo.core.iterators.IteratorUtil.IteratorScope;
import org.apache.accumulo.core.iterators.user.RegExFilter; import org.apache.accumulo.core.iterators.user.RegExFilter;
import org.apache.accumulo.core.security.Authorizations; import org.apache.accumulo.core.security.Authorizations;
import org.apache.accumulo.core.security.ColumnVisibility;
import org.apache.accumulo.core.security.VisibilityEvaluator;
import org.apache.accumulo.core.util.Pair; import org.apache.accumulo.core.util.Pair;
import org.apache.commons.configuration.BaseConfiguration; import org.apache.commons.configuration.BaseConfiguration;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -71,8 +75,10 @@ import timely.api.response.timeseries.SearchLookupResponse;
import timely.api.response.timeseries.SearchLookupResponse.Result; import timely.api.response.timeseries.SearchLookupResponse.Result;
import timely.api.response.timeseries.SuggestResponse; import timely.api.response.timeseries.SuggestResponse;
import timely.auth.AuthCache; import timely.auth.AuthCache;
import timely.auth.util.ScannerHelper;
import timely.configuration.Accumulo; import timely.configuration.Accumulo;
import timely.configuration.Configuration; import timely.configuration.Configuration;
import timely.configuration.Security;
import timely.model.Metric; import timely.model.Metric;
import timely.model.Tag; import timely.model.Tag;
import timely.sample.Aggregation; import timely.sample.Aggregation;
@@ -118,17 +124,18 @@ public class DataStoreImpl implements DataStore {
private final String metricsTable; private final String metricsTable;
private final String metaTable; private final String metaTable;
private final InternalMetrics internalMetrics; private final InternalMetrics internalMetrics;
private final Timer internalMetricsTimer = new Timer("InternalMatricsTimer", true); private final Timer internalMetricsTimer = new Timer("InternalMetricsTimer", true);
private final int scannerThreads; private final int scannerThreads;
private final long maxDownsampleMemory; private final long maxDownsampleMemory;
private final BatchWriterConfig bwConfig; private final BatchWriterConfig bwConfig;
private final List<BatchWriter> writers = new ArrayList<>(); private final List<BatchWriter> writers = new ArrayList<>();
private final ThreadLocal<BatchWriter> metaWriter = new ThreadLocal<>(); private final ThreadLocal<BatchWriter> metaWriter = new ThreadLocal<>();
private final ThreadLocal<BatchWriter> batchWriter = new ThreadLocal<>(); private final ThreadLocal<BatchWriter> batchWriter = new ThreadLocal<>();
private boolean anonAccessAllowed = false; private final Security security;
private final Map<String, String> ageOff; private final Map<String, String> ageOff;
private final long defaultAgeOffMilliSec; private final long defaultAgeOffMilliSec;
private DataStoreCache cache = null; private DataStoreCache cache = null;
private final String defaultVisibility;
public DataStoreImpl(Configuration conf, int numWriteThreads) throws TimelyException { public DataStoreImpl(Configuration conf, int numWriteThreads) throws TimelyException {
@@ -147,7 +154,32 @@ public class DataStoreImpl implements DataStore {
bwConfig.setMaxWriteThreads(accumuloConf.getWrite().getThreads()); bwConfig.setMaxWriteThreads(accumuloConf.getWrite().getThreads());
scannerThreads = accumuloConf.getScan().getThreads(); scannerThreads = accumuloConf.getScan().getThreads();
maxDownsampleMemory = accumuloConf.getScan().getMaxDownsampleMemory(); maxDownsampleMemory = accumuloConf.getScan().getMaxDownsampleMemory();
anonAccessAllowed = conf.getSecurity().isAllowAnonymousAccess(); security = conf.getSecurity();
defaultVisibility = conf.getDefaultVisibility();
if (StringUtils.isNotBlank(defaultVisibility)) {
ColumnVisibility vis;
try {
// validate visibility
vis = new ColumnVisibility(defaultVisibility);
LOG.info("Using defaultVisibility: {}", vis.toString());
} catch (RuntimeException e) {
LOG.error("Error validating defaultVisibility " + defaultVisibility + " " + e.getMessage(), e);
throw e;
}
try {
String whoami = connector.whoami();
Authorizations auths = connector.securityOperations().getUserAuthorizations(whoami);
VisibilityEvaluator eval = new VisibilityEvaluator(auths);
if (!eval.evaluate(vis)) {
throw new IllegalArgumentException("Accumulo user " + whoami + " with authorization "
+ auths.toString() + " can not see data with defaultVisibility " + vis.toString());
}
} catch (RuntimeException e) {
LOG.error(e.getMessage(), e);
throw e;
}
}
metricsTable = conf.getMetricsTable(); metricsTable = conf.getMetricsTable();
if (metricsTable.contains(".")) { if (metricsTable.contains(".")) {
@@ -278,6 +310,17 @@ public class DataStoreImpl implements DataStore {
public void store(Metric metric, boolean cacheEnabled) { public void store(Metric metric, boolean cacheEnabled) {
LOG.trace("Received Store Request for: {}", metric); LOG.trace("Received Store Request for: {}", metric);
// if default visibility is configured and current metric does not contain a
// visibility
// add it here, so that the cache and accumulo stores both have the correct
// visibility
if (StringUtils.isNotBlank(defaultVisibility)) {
Optional<Tag> visTag = metric.getTags().stream().filter(t -> t.getKey().equals(VISIBILITY_TAG)).findFirst();
if (!visTag.isPresent()) {
metric.addTag(new Tag(VISIBILITY_TAG, defaultVisibility));
}
}
if (cache != null && cacheEnabled) { if (cache != null && cacheEnabled) {
cache.store(metric); cache.store(metric);
} }
@@ -395,6 +438,7 @@ public class DataStoreImpl implements DataStore {
@Override @Override
public SuggestResponse suggest(SuggestRequest request) throws TimelyException { public SuggestResponse suggest(SuggestRequest request) throws TimelyException {
SuggestResponse result = new SuggestResponse(); SuggestResponse result = new SuggestResponse();
Scanner scanner = null;
try { try {
if (request.getType().equals("metrics")) { if (request.getType().equals("metrics")) {
Range range; Range range;
@@ -414,7 +458,7 @@ public class DataStoreImpl implements DataStore {
end.append(lastBytes, 0, lastBytes.length); end.append(lastBytes, 0, lastBytes.length);
range = new Range(start, end); range = new Range(start, end);
} }
Scanner scanner = connector.createScanner(metaTable, Authorizations.EMPTY); scanner = connector.createScanner(metaTable, Authorizations.EMPTY);
scanner.setRange(range); scanner.setRange(range);
List<String> metrics = new ArrayList<>(); List<String> metrics = new ArrayList<>();
for (Entry<Key, Value> metric : scanner) { for (Entry<Key, Value> metric : scanner) {
@@ -429,6 +473,10 @@ public class DataStoreImpl implements DataStore {
LOG.error("Error during suggest: " + ex.getMessage(), ex); LOG.error("Error during suggest: " + ex.getMessage(), ex);
throw new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), throw new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(),
"Error during suggest: " + ex.getMessage(), ex.getMessage(), ex); "Error during suggest: " + ex.getMessage(), ex.getMessage(), ex);
} finally {
if (scanner != null) {
scanner.close();
}
} }
return result; return result;
} }
@@ -463,28 +511,34 @@ public class DataStoreImpl implements DataStore {
tagPatterns.put(k, Pattern.compile(v)); tagPatterns.put(k, Pattern.compile(v));
}); });
try { try {
List<Result> resultField = new ArrayList<>(); final Scanner scanner = connector.createScanner(metaTable, Authorizations.EMPTY);
Scanner scanner = connector.createScanner(metaTable, Authorizations.EMPTY); try {
Key start = new Key(Meta.VALUE_PREFIX + msg.getQuery()); List<Result> resultField = new ArrayList<>();
Key end = start.followingKey(PartialKey.ROW); Key start = new Key(Meta.VALUE_PREFIX + msg.getQuery());
Range range = new Range(start, end); Key end = start.followingKey(PartialKey.ROW);
scanner.setRange(range); Range range = new Range(start, end);
tags.keySet().forEach(k -> scanner.fetchColumnFamily(new Text(k))); scanner.setRange(range);
int total = 0; tags.keySet().forEach(k -> scanner.fetchColumnFamily(new Text(k)));
for (Entry<Key, Value> entry : scanner) { int total = 0;
Meta metaEntry = Meta.parse(entry.getKey(), entry.getValue()); for (Entry<Key, Value> entry : scanner) {
if (matches(metaEntry.getTagKey(), metaEntry.getTagValue(), tagPatterns)) { Meta metaEntry = Meta.parse(entry.getKey(), entry.getValue());
if (resultField.size() < msg.getLimit()) { if (matches(metaEntry.getTagKey(), metaEntry.getTagValue(), tagPatterns)) {
Result r = new Result(); if (resultField.size() < msg.getLimit()) {
r.putTag(metaEntry.getTagKey(), metaEntry.getTagValue()); Result r = new Result();
resultField.add(r); r.putTag(metaEntry.getTagKey(), metaEntry.getTagValue());
resultField.add(r);
}
total++;
} }
total++; }
result.setResults(resultField);
result.setTotalResults(total);
result.setTime((int) (System.currentTimeMillis() - startMillis));
} finally {
if (scanner != null) {
scanner.close();
} }
} }
result.setResults(resultField);
result.setTotalResults(total);
result.setTime((int) (System.currentTimeMillis() - startMillis));
} catch (Exception ex) { } catch (Exception ex) {
LOG.error("Error during lookup: " + ex.getMessage(), ex); LOG.error("Error during lookup: " + ex.getMessage(), ex);
throw new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), throw new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(),
@@ -505,8 +559,7 @@ public class DataStoreImpl implements DataStore {
@Override @Override
public List<QueryResponse> query(QueryRequest msg) throws TimelyException { public List<QueryResponse> query(QueryRequest msg) throws TimelyException {
List<QueryResponse> result = new ArrayList<>(); List<QueryResponse> result = new ArrayList<>();
LOG.debug("Query for [{}] [{}]", msg.getUserName(), msg);
LOG.debug("Query request {}", msg);
long requestedStartTs = msg.getStart(); long requestedStartTs = msg.getStart();
long requestedEndTs = msg.getEnd(); long requestedEndTs = msg.getEnd();
StringBuilder metricList = new StringBuilder(); StringBuilder metricList = new StringBuilder();
@@ -549,8 +602,9 @@ public class DataStoreImpl implements DataStore {
if (percentServedFromCache > 100) { if (percentServedFromCache > 100) {
percentServedFromCache = 100; percentServedFromCache = 100;
} }
LOG.debug("Cache query time:{} duration (min):{} metrics:{} results:{} percentFromCache:{}", LOG.debug(
(System.currentTimeMillis() - now), "Cache query for [{}] time:{} duration (min):{} metrics:{} results:{} percentFromCache:{}",
msg.getUserName(), (System.currentTimeMillis() - now),
((requestedEndTs - requestedStartTs) / (1000 * 60)), metricList.toString(), z, ((requestedEndTs - requestedStartTs) / (1000 * 60)), metricList.toString(), z,
percentServedFromCache); percentServedFromCache);
allSeries.putAll(cachedMetrics); allSeries.putAll(cachedMetrics);
@@ -581,7 +635,8 @@ public class DataStoreImpl implements DataStore {
if (endOfLastPeriod > startOfFirstPeriod) { if (endOfLastPeriod > startOfFirstPeriod) {
BatchScanner scanner = null; BatchScanner scanner = null;
try { try {
scanner = connector.createBatchScanner(metricsTable, getSessionAuthorizations(msg), Collection<Authorizations> authorizations = getSessionAuthorizations(msg);
scanner = ScannerHelper.createBatchScanner(connector, metricsTable, authorizations,
scannerThreads); scannerThreads);
List<String> tagOrder = prioritizeTags(query.getMetric(), query.getTags()); List<String> tagOrder = prioritizeTags(query.getMetric(), query.getTags());
Map<String, String> orderedTags = orderTags(tagOrder, query.getTags()); Map<String, String> orderedTags = orderTags(tagOrder, query.getTags());
@@ -658,8 +713,9 @@ public class DataStoreImpl implements DataStore {
result.add(convertToQueryResponse(query, entry.getKey(), entry.getValue(), tsDivisor)); result.add(convertToQueryResponse(query, entry.getKey(), entry.getValue(), tsDivisor));
} }
} }
LOG.debug("Query time:{} duration:{} metrics:{} results:{}", (System.currentTimeMillis() - now), LOG.debug("Query for [{}] time:{} duration:{} metrics:{} results:{}", msg.getUserName(),
((requestedEndTs - requestedStartTs) / (1000 * 60)), metricList.toString(), numResults); (System.currentTimeMillis() - now), ((requestedEndTs - requestedStartTs) / (1000 * 60)),
metricList.toString(), numResults);
internalMetrics.addQueryResponse(result.size(), (System.currentTimeMillis() - now)); internalMetrics.addQueryResponse(result.size(), (System.currentTimeMillis() - now));
return result; return result;
} catch (ClassNotFoundException | IOException | TableNotFoundException ex) { } catch (ClassNotFoundException | IOException | TableNotFoundException ex) {
@@ -753,70 +809,77 @@ public class DataStoreImpl implements DataStore {
public Set<Tag> getColumnFamilies(String metric, Map<String, String> requestedTags) throws TableNotFoundException { public Set<Tag> getColumnFamilies(String metric, Map<String, String> requestedTags) throws TableNotFoundException {
Map<String, String> tags = (requestedTags == null) ? new LinkedHashMap<>() : requestedTags; Scanner meta = null;
LOG.trace("Looking for requested tags: {}", tags); try {
Scanner meta = connector.createScanner(metaTable, Authorizations.EMPTY); Map<String, String> tags = (requestedTags == null) ? new LinkedHashMap<>() : requestedTags;
Text start = new Text(Meta.VALUE_PREFIX + metric); LOG.trace("Looking for requested tags: {}", tags);
Text end = new Text(Meta.VALUE_PREFIX + metric + "\\x0000"); meta = connector.createScanner(metaTable, Authorizations.EMPTY);
end.append(new byte[] { (byte) 0xff }, 0, 1); Text start = new Text(Meta.VALUE_PREFIX + metric);
meta.setRange(new Range(start, end)); Text end = new Text(Meta.VALUE_PREFIX + metric + "\\x0000");
// Only look for the meta entries that match our tags, if any end.append(new byte[] { (byte) 0xff }, 0, 1);
boolean onlyFirstRow = false; meta.setRange(new Range(start, end));
Entry<String, String> first = null; // Only look for the meta entries that match our tags, if any
// Set the columns on the meta scanner based on the first tag boolean onlyFirstRow = false;
// in the set of tags passed in the query. If no tags are present Entry<String, String> first = null;
// then we are only going to return the first tag name present in the // Set the columns on the meta scanner based on the first tag
// meta table. // in the set of tags passed in the query. If no tags are present
Iterator<Entry<String, String>> tagIter = tags.entrySet().iterator(); // then we are only going to return the first tag name present in the
if (tagIter.hasNext()) { // meta table.
first = tagIter.next(); Iterator<Entry<String, String>> tagIter = tags.entrySet().iterator();
if (isTagValueRegex(first.getValue())) { if (tagIter.hasNext()) {
meta.fetchColumnFamily(new Text(first.getKey())); first = tagIter.next();
} else { if (isTagValueRegex(first.getValue())) {
meta.fetchColumn(new Text(first.getKey()), new Text(first.getValue())); meta.fetchColumnFamily(new Text(first.getKey()));
} } else {
} else { meta.fetchColumn(new Text(first.getKey()), new Text(first.getValue()));
// grab all of the values found for the first tag for the metric
onlyFirstRow = true;
}
final boolean ONLY_RETURN_FIRST_TAG = onlyFirstRow;
Iterator<Entry<Key, Value>> iter = meta.iterator();
Iterator<Pair<String, String>> knownKeyValues = new Iterator<Pair<String, String>>() {
Text firstTag = null;
Text tagName = null;
Text tagValue = null;
@Override
public boolean hasNext() {
if (iter.hasNext()) {
Entry<Key, Value> metaEntry = iter.next();
if (null == firstTag) {
firstTag = metaEntry.getKey().getColumnFamily();
}
tagName = metaEntry.getKey().getColumnFamily();
tagValue = metaEntry.getKey().getColumnQualifier();
LOG.trace("Found tag entry {}={}", tagName, tagValue);
if (ONLY_RETURN_FIRST_TAG && !tagName.equals(firstTag)) {
return false;
}
return true;
} }
return false; } else {
// grab all of the values found for the first tag for the metric
onlyFirstRow = true;
} }
final boolean ONLY_RETURN_FIRST_TAG = onlyFirstRow;
Iterator<Entry<Key, Value>> iter = meta.iterator();
Iterator<Pair<String, String>> knownKeyValues = new Iterator<Pair<String, String>>() {
@Override Text firstTag = null;
public Pair<String, String> next() { Text tagName = null;
LOG.trace("Returning tag {}={}", tagName, tagValue); Text tagValue = null;
return new Pair<>(tagName.toString(), tagValue.toString());
@Override
public boolean hasNext() {
if (iter.hasNext()) {
Entry<Key, Value> metaEntry = iter.next();
if (null == firstTag) {
firstTag = metaEntry.getKey().getColumnFamily();
}
tagName = metaEntry.getKey().getColumnFamily();
tagValue = metaEntry.getKey().getColumnQualifier();
LOG.trace("Found tag entry {}={}", tagName, tagValue);
if (ONLY_RETURN_FIRST_TAG && !tagName.equals(firstTag)) {
return false;
}
return true;
}
return false;
}
@Override
public Pair<String, String> next() {
LOG.trace("Returning tag {}={}", tagName, tagValue);
return new Pair<>(tagName.toString(), tagValue.toString());
}
};
// Expand the list of tags in the meta table for this metric that
// matches
// the pattern of the first tag in the query. The resulting set of tags
// will be used to fetch specific columns from the metric table.
return expandTagValues(first, knownKeyValues);
} finally {
if (meta != null) {
meta.close();
} }
}; }
// Expand the list of tags in the meta table for this metric that
// matches
// the pattern of the first tag in the query. The resulting set of tags
// will be used to fetch specific columns from the metric table.
return expandTagValues(first, knownKeyValues);
} }
private void setQueryColumns(ScannerBase scanner, String metric, Map<String, String> tags, Set<Tag> colFamValues) private void setQueryColumns(ScannerBase scanner, String metric, Map<String, String> tags, Set<Tag> colFamValues)
@@ -945,32 +1008,8 @@ public class DataStoreImpl implements DataStore {
return Aggregator.getAggregator(query.getAggregator()); return Aggregator.getAggregator(query.getAggregator());
} }
private Authorizations getSessionAuthorizations(AuthenticatedRequest request) { private Collection<Authorizations> getSessionAuthorizations(AuthenticatedRequest request) {
return getSessionAuthorizations(request.getSessionId()); return AuthCache.getAuthorizations(request, security);
}
private Authorizations getSessionAuthorizations(String sessionId) {
if (anonAccessAllowed) {
if (StringUtils.isEmpty(sessionId)) {
return Authorizations.EMPTY;
} else {
Authorizations auths = AuthCache.getAuthorizations(sessionId);
if (null == auths) {
auths = Authorizations.EMPTY;
}
return auths;
}
} else {
if (StringUtils.isEmpty(sessionId)) {
throw new IllegalArgumentException("session id cannot be null");
} else {
Authorizations auths = AuthCache.getAuthorizations(sessionId);
if (null == auths) {
throw new IllegalStateException("No auths found for sessionId: " + sessionId);
}
return auths;
}
}
} }
public long getAgeOffForMetric(String metricName) { public long getAgeOffForMetric(String metricName) {
@@ -982,27 +1021,15 @@ public class DataStoreImpl implements DataStore {
} }
} }
public Scanner createScannerForMetric(String sessionId, String metric, Map<String, String> tags, long startTime, public Scanner createScannerForMetric(AuthenticatedRequest request, String metric, Map<String, String> tags,
long endTime, int lag, int scannerBatchSize, int scannerReadAhead) throws TimelyException { int scannerBatchSize, int scannerReadAhead) throws TimelyException {
try { try {
Authorizations auths = null;
try {
auths = getSessionAuthorizations(sessionId);
} catch (NullPointerException npe) {
// Session id being used for metric scanner, but session Id does
// not exist in Auth Cache. Use Empty auths if anonymous access
// allowed
if (anonAccessAllowed) {
auths = Authorizations.EMPTY;
} else {
throw npe;
}
}
LOG.debug("Creating metric scanner for session: {} with auths: {}", sessionId, auths);
Scanner s = connector.createScanner(this.metricsTable, auths);
if (null == metric) { if (null == metric) {
throw new IllegalArgumentException("metric name must be specified"); throw new IllegalArgumentException("metric name must be specified");
} }
Collection<Authorizations> auths = getSessionAuthorizations(request);
LOG.debug("Creating metric scanner for [{}] with auths: {}", request.getUserName(), auths);
Scanner s = ScannerHelper.createScanner(connector, this.metricsTable, auths);
if (tags == null) { if (tags == null) {
tags = new LinkedHashMap<>(); tags = new LinkedHashMap<>();
} }
Loaded 100 of 137 files, more files were not shown because too many files have changed in this diff. Show more