mirror of
https://github.com/NationalSecurityAgency/timely.git
synced 2026-11-10 03:41:25 -10:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ecbba6daf | ||
|
|
dcf6e604a1 | ||
|
|
bfbbad3b2d |
No files matched your search
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>timely</groupId>
|
<groupId>timely</groupId>
|
||||||
<artifactId>timely</artifactId>
|
<artifactId>timely</artifactId>
|
||||||
<version>1.1.1-SNAPSHOT</version>
|
<version>1.0.1-SNAPSHOT</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.isClientAuth(), jp.isDoLogin(), jp.getTimelyUsername(), jp.getTimelyPassword(), jp.getKeyStoreFile(),
|
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(String.class, new MessageHandler.Whole<String>() {
|
session.addMessageHandler(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,7 +18,6 @@ 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;
|
||||||
@@ -41,7 +40,6 @@ 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);
|
||||||
@@ -71,7 +69,6 @@ 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);
|
||||||
@@ -115,10 +112,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,27 +15,15 @@
|
|||||||
|
|
||||||
<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:list>
|
<util:map key-type="java.lang.String">
|
||||||
<beans:bean class="timely.auth.TimelyUser">
|
<beans:entry key="example.com">
|
||||||
<beans:constructor-arg name="subjectDn">
|
|
||||||
<beans:value>CN=example.com</beans:value>
|
|
||||||
</beans:constructor-arg>
|
|
||||||
<beans:constructor-arg name="auths">
|
|
||||||
<util:list>
|
<util:list>
|
||||||
<beans:value>D</beans:value>
|
<beans:value>D</beans:value>
|
||||||
<beans:value>E</beans:value>
|
<beans:value>E</beans:value>
|
||||||
<beans:value>F</beans:value>
|
<beans:value>F</beans:value>
|
||||||
</util:list>
|
</util:list>
|
||||||
</beans:constructor-arg>
|
</beans:entry>
|
||||||
<beans:constructor-arg name="roles">
|
</util:map>
|
||||||
<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>
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ timely-balancer:
|
|||||||
assignmentFile: /timely/balancer/assignments.csv
|
assignmentFile: /timely/balancer/assignments.csv
|
||||||
security:
|
security:
|
||||||
session-max-age: 86400
|
session-max-age: 86400
|
||||||
allow-anonymous-http-access: true
|
allow-anonymous-access: true
|
||||||
allow-anonymous-ws-access: true
|
|
||||||
serverSsl:
|
serverSsl:
|
||||||
certificate-file:
|
certificate-file:
|
||||||
key-file:
|
key-file:
|
||||||
@@ -32,7 +31,7 @@ timely-balancer:
|
|||||||
nonCachedMetrics:
|
nonCachedMetrics:
|
||||||
server:
|
server:
|
||||||
ip: 127.0.0.1
|
ip: 127.0.0.1
|
||||||
tcpPort: 4242
|
tcp-port: 4242
|
||||||
numTcpPools: 1
|
numTcpPools: 1
|
||||||
tcpBufferSize: 100
|
tcpBufferSize: 100
|
||||||
tcpClientPool:
|
tcpClientPool:
|
||||||
@@ -52,7 +51,7 @@ timely-balancer:
|
|||||||
jmxEnabled: true
|
jmxEnabled: true
|
||||||
testOnBorrow: false
|
testOnBorrow: false
|
||||||
testOnReturn: false
|
testOnReturn: false
|
||||||
udpPort: 4245
|
udp-port: 4245
|
||||||
udpClientPool:
|
udpClientPool:
|
||||||
minIdlePerKey: 0
|
minIdlePerKey: 0
|
||||||
maxIdlePerKey: 40
|
maxIdlePerKey: 40
|
||||||
|
|||||||
+6
-1
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>timely</groupId>
|
<groupId>timely</groupId>
|
||||||
<artifactId>timely</artifactId>
|
<artifactId>timely</artifactId>
|
||||||
<version>1.1.1-SNAPSHOT</version>
|
<version>1.0.1-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>timely-balancer</artifactId>
|
<artifactId>timely-balancer</artifactId>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -201,6 +201,11 @@
|
|||||||
<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>
|
||||||
|
|||||||
Vendored
+1
-1
@@ -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>
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
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;
|
||||||
@@ -92,7 +91,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.WebSocketFullRequestHandler;
|
import timely.netty.websocket.WebSocketHttpCookieHandler;
|
||||||
import timely.netty.websocket.WebSocketRequestDecoder;
|
import timely.netty.websocket.WebSocketRequestDecoder;
|
||||||
import timely.netty.websocket.subscription.WSTimelyExceptionHandler;
|
import timely.netty.websocket.subscription.WSTimelyExceptionHandler;
|
||||||
|
|
||||||
@@ -363,7 +362,6 @@ public class Balancer {
|
|||||||
curatorFramework.start();
|
curatorFramework.start();
|
||||||
ensureZkPaths(curatorFramework, zkPaths);
|
ensureZkPaths(curatorFramework, zkPaths);
|
||||||
|
|
||||||
AuthCache.setSessionMaxAge(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;
|
||||||
@@ -561,7 +559,7 @@ public class Balancer {
|
|||||||
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(8192));
|
||||||
ch.pipeline().addLast("sessionExtractor", new WebSocketFullRequestHandler());
|
ch.pipeline().addLast("sessionExtractor", new WebSocketHttpCookieHandler(balancerConfig.getSecurity()));
|
||||||
|
|
||||||
ch.pipeline().addLast("idle-handler",
|
ch.pipeline().addLast("idle-handler",
|
||||||
new IdleStateHandler(balancerConfig.getWebsocket().getTimeout(), 0, 0));
|
new IdleStateHandler(balancerConfig.getWebsocket().getTimeout(), 0, 0));
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ 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;
|
||||||
@@ -57,21 +55,6 @@ 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,25 +7,20 @@ 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.BasicCookieStore;
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
import timely.balancer.configuration.BalancerConfiguration;
|
|
||||||
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 BalancerConfiguration balancerConfig;
|
|
||||||
|
|
||||||
public HttpClientFactory(BalancerConfiguration balancerConfig, SSLContext sslContext) {
|
public HttpClientFactory(SSLContext sslContext) {
|
||||||
this.balancerConfig = balancerConfig;
|
|
||||||
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();
|
BasicCookieStore cookieJar = new BasicCookieStore();
|
||||||
return new DefaultPooledObject<>(timely.client.http.HttpClient.get(this.sslContext, cookieJar,
|
return new DefaultPooledObject<>(timely.client.http.HttpClient.get(this.sslContext, cookieJar, false));
|
||||||
balancerConfig.getSecurity().getClientSsl().isHostVerificationEnabled(),
|
|
||||||
balancerConfig.getSecurity().getClientSsl().isUseClientCert()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public class HttpClientPool extends GenericKeyedObjectPool<TimelyBalancedHost, C
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(HttpClientPool.class);
|
private static final Logger LOG = LoggerFactory.getLogger(HttpClientPool.class);
|
||||||
|
|
||||||
public HttpClientPool(BalancerConfiguration balancerConfig, SSLContext sslContext) {
|
public HttpClientPool(BalancerConfiguration config, SSLContext sslContext) {
|
||||||
super(new HttpClientFactory(balancerConfig, sslContext), balancerConfig.getHttp().getHttpClientPool());
|
super(new HttpClientFactory(sslContext), config.getHttp().getHttpClientPool());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,11 +12,13 @@ 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.balancerConfig = balancerConfig;
|
|
||||||
this.sslContext = sslContext;
|
this.sslContext = sslContext;
|
||||||
|
this.doLogin = balancerConfig.isLoginRequired();
|
||||||
|
this.balancerConfig = balancerConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -24,9 +26,7 @@ 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(), balancerConfig.getSecurity().getClientSsl().isUseClientCert(),
|
k.getWsPort(), doLogin, "", "", false, bufferSize);
|
||||||
balancerConfig.isLoginRequired(), "", "",
|
|
||||||
balancerConfig.getSecurity().getClientSsl().isHostVerificationEnabled(), bufferSize);
|
|
||||||
return new DefaultPooledObject<>(client);
|
return new DefaultPooledObject<>(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ 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;
|
||||||
@@ -34,19 +33,15 @@ 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.auth.util.HttpHeaderUtils;
|
|
||||||
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.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 {
|
||||||
|
|
||||||
@@ -94,19 +89,10 @@ public class HttpRelayHandler extends SimpleChannelInboundHandler<HttpRequest> i
|
|||||||
k = metricResolver.getHostPortKey(metric);
|
k = metricResolver.getHostPortKey(metric);
|
||||||
client = httpClientPool.borrowObject(k);
|
client = httpClientPool.borrowObject(k);
|
||||||
|
|
||||||
Multimap<String, String> headers = HttpHeaderUtils.toMultimap(request.headers());
|
List<Map.Entry<String, String>> headerList = request.headers().entries();
|
||||||
if (msg instanceof AuthenticatedRequest) {
|
|
||||||
AuthenticatedRequest authenticatedRequest = (AuthenticatedRequest) msg;
|
|
||||||
TimelyAuthenticationToken token = authenticatedRequest.getToken();
|
|
||||||
if (token != null) {
|
|
||||||
if (token.getClientCert() != null) {
|
|
||||||
ProxiedEntityUtils.addProxyHeaders(headers, token.getClientCert());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Header> relayedHeaderList = new ArrayList<>();
|
List<Header> relayedHeaderList = new ArrayList<>();
|
||||||
for (Map.Entry<String, String> h : headers.entries()) {
|
|
||||||
|
for (Map.Entry<String, String> h : headerList) {
|
||||||
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()));
|
||||||
}
|
}
|
||||||
@@ -157,6 +143,7 @@ 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);
|
||||||
|
|
||||||
@@ -166,6 +153,8 @@ 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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
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;
|
||||||
@@ -19,48 +15,25 @@ 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, TimelyAuthenticationToken token, int pingRate) {
|
public WsClientHandler(ChannelHandlerContext ctx, 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(String.class, message -> {
|
session.addMessageHandler((MessageHandler.Whole<String>) 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,7 +9,6 @@ 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;
|
||||||
@@ -25,7 +24,6 @@ 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;
|
||||||
@@ -66,10 +64,6 @@ 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();
|
||||||
@@ -124,8 +118,7 @@ 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, add.getToken(),
|
client.open(new WsClientHandler(origContext, (balancerConfig.getWebsocket().getTimeout() / 2)));
|
||||||
(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();
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ 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("ArrivalRateTimerResolver", true);
|
private Timer arrivalRateTimer = new Timer("AriivalRateTimerResolver", true);
|
||||||
private int roundRobinCounter = 0;
|
private int roundRobinCounter = 0;
|
||||||
private Set<String> nonCachedMetrics = new HashSet<>();
|
private Set<String> nonCachedMetrics = new HashSet<>();
|
||||||
private DistributedAtomicValue nonCachedMetricsIP;
|
private DistributedAtomicValue nonCachedMetricsIP;
|
||||||
@@ -105,17 +105,17 @@ public class BalancedMetricResolver implements MetricResolver {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent event) throws Exception {
|
public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent event) throws Exception {
|
||||||
LOG.debug("Handling assignments event {}. assignmentsLastUpdatedInHdfs:{}", event.getType().toString(),
|
LOG.info("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.debug("Reading assignments from hdfs lastHdfsUpdate ({}) > lastLocalUpdate ({})",
|
LOG.info("Reading assignments from hdfs lastHdfsUpdate ({}) > lastLocalUpdate ({})",
|
||||||
new Date(lastHdfsUpdate), new Date(lastLocalUpdate));
|
new Date(lastHdfsUpdate), new Date(lastLocalUpdate));
|
||||||
readAssignmentsFromHdfs();
|
readAssignmentsFromHdfs();
|
||||||
} else {
|
} else {
|
||||||
LOG.debug("Not reading assignments from hdfs lastHdfsUpdate ({}) <= lastLocalUpdate ({})",
|
LOG.info("Not reading assignments from hdfs lastHdfsUpdate ({}) <= lastLocalUpdate ({})",
|
||||||
new Date(lastHdfsUpdate), new Date(lastLocalUpdate));
|
new Date(lastHdfsUpdate), new Date(lastLocalUpdate));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,7 +138,7 @@ public class BalancedMetricResolver implements MetricResolver {
|
|||||||
@Override
|
@Override
|
||||||
public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent event) throws Exception {
|
public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent event) throws Exception {
|
||||||
if (event.getType().equals(TreeCacheEvent.Type.NODE_UPDATED)) {
|
if (event.getType().equals(TreeCacheEvent.Type.NODE_UPDATED)) {
|
||||||
LOG.debug("Handling nonCachedMetricsIP event {}", event.getType().toString());
|
LOG.info("Handling nonCachedMetricsIP event {}", event.getType().toString());
|
||||||
readNonCachedMetricsIP();
|
readNonCachedMetricsIP();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,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.debug("Writing assignments to hdfs lastLocalUpdate ({}) > lastHdfsUpdate ({})",
|
LOG.info("Writing assignments to hdfs lastLocalUpdate ({}) > lastHdfsUpdate ({})",
|
||||||
new Date(lastLocalUpdate), new Date(lastHdfsUpdate));
|
new Date(lastLocalUpdate), new Date(lastHdfsUpdate));
|
||||||
writeAssigmentsToHdfs();
|
writeAssigmentsToHdfs();
|
||||||
}
|
}
|
||||||
@@ -198,12 +198,15 @@ public class BalancedMetricResolver implements MetricResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void readNonCachedMetricsIP() {
|
private void readNonCachedMetricsIP() {
|
||||||
|
try {
|
||||||
Set<String> currentNonCachedMetricsDistributed = new TreeSet<>();
|
nonCachedMetricsLocalLock.readLock().lock();
|
||||||
|
Set<String> currentNonCachedMetricsDistributed;
|
||||||
try {
|
try {
|
||||||
nonCachedMetricsIPRWLock.readLock().acquire();
|
nonCachedMetricsIPRWLock.readLock().acquire();
|
||||||
byte[] currentNonCachedMetricsDistributedBytes = nonCachedMetricsIP.get().postValue();
|
byte[] currentNonCachedMetricsDistributedBytes = nonCachedMetricsIP.get().postValue();
|
||||||
if (currentNonCachedMetricsDistributedBytes != null) {
|
if (currentNonCachedMetricsDistributedBytes == null) {
|
||||||
|
currentNonCachedMetricsDistributed = new TreeSet<>();
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
currentNonCachedMetricsDistributed = SerializationUtils
|
currentNonCachedMetricsDistributed = SerializationUtils
|
||||||
.deserialize(currentNonCachedMetricsDistributedBytes);
|
.deserialize(currentNonCachedMetricsDistributedBytes);
|
||||||
@@ -212,29 +215,35 @@ public class BalancedMetricResolver implements MetricResolver {
|
|||||||
currentNonCachedMetricsDistributed = new TreeSet<>();
|
currentNonCachedMetricsDistributed = new TreeSet<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error(e.getMessage(), e);
|
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
|
||||||
nonCachedMetricsIPRWLock.readLock().release();
|
nonCachedMetricsIPRWLock.readLock().release();
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nonCachedMetricsLocalLock.writeLock().lock();
|
|
||||||
try {
|
|
||||||
if (nonCachedMetrics.containsAll(currentNonCachedMetricsDistributed)) {
|
if (nonCachedMetrics.containsAll(currentNonCachedMetricsDistributed)) {
|
||||||
LOG.debug("local nonCachedMetrics already contains {}", currentNonCachedMetricsDistributed);
|
LOG.info("local nonCachedMetrics already contains {}", currentNonCachedMetricsDistributed);
|
||||||
} else {
|
} else {
|
||||||
|
nonCachedMetricsLocalLock.readLock().unlock();
|
||||||
|
nonCachedMetricsLocalLock.writeLock().lock();
|
||||||
currentNonCachedMetricsDistributed.removeAll(nonCachedMetrics);
|
currentNonCachedMetricsDistributed.removeAll(nonCachedMetrics);
|
||||||
LOG.debug("Adding {} to local nonCachedMetrics", currentNonCachedMetricsDistributed);
|
LOG.info("Adding {} to local nonCachedMetrics", currentNonCachedMetricsDistributed);
|
||||||
nonCachedMetrics.addAll(currentNonCachedMetricsDistributed);
|
nonCachedMetrics.addAll(currentNonCachedMetricsDistributed);
|
||||||
|
balancerLock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
for (String m : currentNonCachedMetricsDistributed) {
|
||||||
|
metricToHostMap.remove(m);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
balancerLock.writeLock().unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LOG.error(e.getMessage(), e);
|
LOG.error(e.getMessage(), e);
|
||||||
} finally {
|
} finally {
|
||||||
|
if (nonCachedMetricsLocalLock.isWriteLockedByCurrentThread()) {
|
||||||
nonCachedMetricsLocalLock.writeLock().unlock();
|
nonCachedMetricsLocalLock.writeLock().unlock();
|
||||||
|
} else {
|
||||||
|
nonCachedMetricsLocalLock.readLock().unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,21 +514,21 @@ public class BalancedMetricResolver implements MetricResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (highHosts.isEmpty() && lowHosts.isEmpty()) {
|
if (highHosts.isEmpty() && lowHosts.isEmpty()) {
|
||||||
LOG.debug("Host's arrival rates are within {} of the average", controlBandPercentage);
|
LOG.info("Host's arrival rates are within {} of the average", controlBandPercentage);
|
||||||
} else if (!highHosts.isEmpty() && !lowHosts.isEmpty()) {
|
} else if (!highHosts.isEmpty() && !lowHosts.isEmpty()) {
|
||||||
LOG.debug("begin rebalancing {}", BalanceType.HIGH_LOW);
|
LOG.info("begin rebalancing {}", BalanceType.HIGH_LOW);
|
||||||
numReassigned = rebalance(highHosts, lowHosts, calculatedRates, averageArrivalRate,
|
numReassigned = rebalance(highHosts, lowHosts, calculatedRates, averageArrivalRate,
|
||||||
BalanceType.HIGH_LOW);
|
BalanceType.HIGH_LOW);
|
||||||
LOG.debug("end rebalancing {} - reassigned {}", BalanceType.HIGH_LOW, numReassigned);
|
LOG.info("end rebalancing {} - reassigned {}", BalanceType.HIGH_LOW, numReassigned);
|
||||||
} else if (lowHosts.isEmpty()) {
|
} else if (lowHosts.isEmpty()) {
|
||||||
LOG.debug("begin rebalancing {}", BalanceType.HIGH_AVG);
|
LOG.info("begin rebalancing {}", BalanceType.HIGH_AVG);
|
||||||
numReassigned = rebalance(highHosts, avgHosts, calculatedRates, averageArrivalRate,
|
numReassigned = rebalance(highHosts, avgHosts, calculatedRates, averageArrivalRate,
|
||||||
BalanceType.HIGH_AVG);
|
BalanceType.HIGH_AVG);
|
||||||
LOG.debug("end rebalancing {} - reassigned {}", BalanceType.HIGH_AVG, numReassigned);
|
LOG.info("end rebalancing {} - reassigned {}", BalanceType.HIGH_AVG, numReassigned);
|
||||||
} else {
|
} else {
|
||||||
LOG.debug("begin rebalancing {}", BalanceType.AVG_LOW);
|
LOG.info("begin rebalancing {}", BalanceType.AVG_LOW);
|
||||||
numReassigned = rebalance(avgHosts, lowHosts, calculatedRates, averageArrivalRate, BalanceType.AVG_LOW);
|
numReassigned = rebalance(avgHosts, lowHosts, calculatedRates, averageArrivalRate, BalanceType.AVG_LOW);
|
||||||
LOG.debug("end rebalancing {} - reassigned {}", BalanceType.AVG_LOW, numReassigned);
|
LOG.info("end rebalancing {} - reassigned {}", BalanceType.AVG_LOW, numReassigned);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return numReassigned;
|
return numReassigned;
|
||||||
@@ -633,13 +642,21 @@ public class BalancedMetricResolver implements MetricResolver {
|
|||||||
private void addNonCachedMetrics(Collection<String> nonCachedMetricsUpdate) {
|
private void addNonCachedMetrics(Collection<String> nonCachedMetricsUpdate) {
|
||||||
if (!nonCachedMetricsUpdate.isEmpty()) {
|
if (!nonCachedMetricsUpdate.isEmpty()) {
|
||||||
try {
|
try {
|
||||||
LOG.debug("Adding {} to local nonCachedMetrics", nonCachedMetricsUpdate);
|
LOG.info("Adding {} to local nonCachedMetrics", nonCachedMetricsUpdate);
|
||||||
nonCachedMetricsLocalLock.writeLock().lock();
|
nonCachedMetricsLocalLock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
nonCachedMetrics.addAll(nonCachedMetricsUpdate);
|
nonCachedMetrics.addAll(nonCachedMetricsUpdate);
|
||||||
} finally {
|
} finally {
|
||||||
nonCachedMetricsLocalLock.writeLock().unlock();
|
nonCachedMetricsLocalLock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
|
balancerLock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
for (String m : nonCachedMetricsUpdate) {
|
||||||
|
metricToHostMap.remove(m);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
balancerLock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
nonCachedMetricsIPRWLock.writeLock().acquire();
|
nonCachedMetricsIPRWLock.writeLock().acquire();
|
||||||
@@ -652,10 +669,10 @@ public class BalancedMetricResolver implements MetricResolver {
|
|||||||
.deserialize(currentNonCachedMetricsDistributedBytes);
|
.deserialize(currentNonCachedMetricsDistributedBytes);
|
||||||
}
|
}
|
||||||
if (currentNonCachedMetricsIP.containsAll(nonCachedMetricsUpdate)) {
|
if (currentNonCachedMetricsIP.containsAll(nonCachedMetricsUpdate)) {
|
||||||
LOG.debug("nonCachedMetricsIP already contains {}", nonCachedMetricsUpdate);
|
LOG.info("nonCachedMetricsIP already contains {}", nonCachedMetricsUpdate);
|
||||||
} else {
|
} else {
|
||||||
nonCachedMetricsUpdate.removeAll(currentNonCachedMetricsIP);
|
nonCachedMetricsUpdate.removeAll(currentNonCachedMetricsIP);
|
||||||
LOG.debug("Adding {} to nonCachedMetricsIP", nonCachedMetricsUpdate);
|
LOG.info("Adding {} to nonCachedMetricsIP", nonCachedMetricsUpdate);
|
||||||
TreeSet<String> updateSet = new TreeSet<>();
|
TreeSet<String> updateSet = new TreeSet<>();
|
||||||
updateSet.addAll(currentNonCachedMetricsIP);
|
updateSet.addAll(currentNonCachedMetricsIP);
|
||||||
updateSet.addAll(nonCachedMetricsUpdate);
|
updateSet.addAll(nonCachedMetricsUpdate);
|
||||||
@@ -720,7 +737,7 @@ public class BalancedMetricResolver implements MetricResolver {
|
|||||||
try {
|
try {
|
||||||
for (String r : nonCachedMetrics) {
|
for (String r : nonCachedMetrics) {
|
||||||
if (metricName.matches(r)) {
|
if (metricName.matches(r)) {
|
||||||
LOG.debug("Adding {} to list of non-cached metrics", metricName);
|
LOG.info("Adding {} to list of non-cached metrics", metricName);
|
||||||
addNonCachedMetrics(Collections.singleton(metricName));
|
addNonCachedMetrics(Collections.singleton(metricName));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -769,7 +786,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.debug("rebalancing from host that is down: reassigning metric {} from server {}:{} to {}:{}",
|
LOG.info("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();
|
||||||
@@ -982,7 +999,7 @@ public class BalancedMetricResolver implements MetricResolver {
|
|||||||
if (!assignmentsLastUpdatedInHdfs.get().succeeded()) {
|
if (!assignmentsLastUpdatedInHdfs.get().succeeded()) {
|
||||||
assignmentsLastUpdatedInHdfs.forceSet(now);
|
assignmentsLastUpdatedInHdfs.forceSet(now);
|
||||||
}
|
}
|
||||||
LOG.debug("Wrote {} assignments to hdfs lastHdfsUpdate = lastLocalUpdate ({})",
|
LOG.info("Wrote {} assignments to hdfs lastHdfsUpdate = lastLocalUpdate ({})",
|
||||||
metricToHostMap.size(), new Date(assignmentsLastUpdatedLocal.get()));
|
metricToHostMap.size(), new Date(assignmentsLastUpdatedLocal.get()));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
+2
-41
@@ -4,12 +4,12 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>timely</groupId>
|
<groupId>timely</groupId>
|
||||||
<artifactId>timely</artifactId>
|
<artifactId>timely</artifactId>
|
||||||
<version>1.1.1-SNAPSHOT</version>
|
<version>1.0.1-SNAPSHOT</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>1.1.1.dev0</python.client.version>
|
<python.client.version>0.0.7.dev0</python.client.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -107,43 +107,4 @@
|
|||||||
</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,13 +1,12 @@
|
|||||||
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;
|
||||||
@@ -41,25 +40,14 @@ public class HttpClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static CloseableHttpClient get(SSLContext ssl, CookieStore cookieStore, boolean hostVerificationEnabled) {
|
public static CloseableHttpClient get(SSLContext ssl, CookieStore cookieStore, boolean hostVerificationEnabled) {
|
||||||
return get(ssl, cookieStore, hostVerificationEnabled, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CloseableHttpClient get(SSLContext ssl, CookieStore cookieStore, boolean hostVerificationEnabled,
|
|
||||||
boolean clientAuth) {
|
|
||||||
RequestConfig defaultRequestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build();
|
RequestConfig defaultRequestConfig = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build();
|
||||||
HostnameVerifier hostnameVerifier;
|
|
||||||
if (hostVerificationEnabled) {
|
|
||||||
hostnameVerifier = SSLConnectionSocketFactory.getDefaultHostnameVerifier();
|
|
||||||
} else {
|
|
||||||
hostnameVerifier = new NoopHostnameVerifier();
|
|
||||||
}
|
|
||||||
HttpClientBuilder builder = HttpClients.custom().setSSLContext(ssl).setDefaultCookieStore(cookieStore)
|
|
||||||
.setDefaultRequestConfig(defaultRequestConfig).setSSLHostnameVerifier(hostnameVerifier);
|
|
||||||
|
|
||||||
if (clientAuth) {
|
HttpClientBuilder builder = HttpClients.custom().setSSLContext(ssl).setDefaultCookieStore(cookieStore)
|
||||||
SSLConnectionSocketFactory sslCSF = new SSLConnectionSocketFactory(ssl,
|
.setDefaultRequestConfig(defaultRequestConfig);
|
||||||
new String[] { "TLSv1.1", "TLSv1.2" }, null, hostnameVerifier);
|
if (hostVerificationEnabled) {
|
||||||
builder.setSSLSocketFactory(sslCSF);
|
builder.setSSLHostnameVerifier(new DefaultHostnameVerifier());
|
||||||
|
} else {
|
||||||
|
builder.setSSLHostnameVerifier(new NoopHostnameVerifier());
|
||||||
}
|
}
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
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;
|
||||||
@@ -38,6 +35,4 @@ 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,15 +14,13 @@ 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(ClientHandler clientEndpoint, Cookie sessionCookie) {
|
public TimelyEndpointConfig(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
|
||||||
@@ -60,7 +58,6 @@ 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,7 +40,6 @@ 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;
|
||||||
@@ -52,14 +51,13 @@ 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 clientAuth, boolean doLogin, String timelyUsername, String timelyPassword,
|
boolean doLogin, String timelyUsername, String timelyPassword, boolean hostVerificationEnabled,
|
||||||
boolean hostVerificationEnabled, int bufferSize) {
|
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;
|
||||||
@@ -69,28 +67,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 && !clientAuth) {
|
if (doLogin && ((StringUtils.isEmpty(timelyUsername) && !StringUtils.isEmpty(timelyPassword)
|
||||||
if ((StringUtils.isEmpty(timelyUsername) && !StringUtils.isEmpty(timelyPassword)
|
|| (!StringUtils.isEmpty(timelyUsername) && StringUtils.isEmpty(timelyPassword))))) {
|
||||||
|| (!StringUtils.isEmpty(timelyUsername) && StringUtils.isEmpty(timelyPassword)))) {
|
|
||||||
throw new IllegalArgumentException("Both Timely username and password must be empty or non-empty");
|
throw new IllegalArgumentException("Both Timely username and password must be empty or non-empty");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected WebSocketClient(String timelyHostname, int timelyHttpsPort, int timelyWssPort, boolean clientAuth,
|
protected WebSocketClient(String timelyHostname, int timelyHttpsPort, int timelyWssPort, boolean doLogin,
|
||||||
boolean doLogin, String timelyUsername, String timelyPassword, String keyStoreFile, String keyStoreType,
|
String timelyUsername, String timelyPassword, String keyStoreFile, String keyStoreType, String keyStorePass,
|
||||||
String keyStorePass, String trustStoreFile, String trustStoreType, String trustStorePass,
|
String trustStoreFile, String trustStoreType, String trustStorePass, boolean hostVerificationEnabled,
|
||||||
boolean hostVerificationEnabled, int bufferSize) {
|
int bufferSize) {
|
||||||
this(HttpClient.getSSLContext(trustStoreFile, trustStoreType, trustStorePass, keyStoreFile, keyStoreType,
|
this(HttpClient.getSSLContext(trustStoreFile, trustStoreType, trustStorePass, keyStoreFile, keyStoreType,
|
||||||
keyStorePass), timelyHostname, timelyHttpsPort, timelyWssPort, clientAuth, doLogin, timelyUsername,
|
keyStorePass), timelyHostname, timelyHttpsPort, timelyWssPort, doLogin, timelyUsername, timelyPassword,
|
||||||
timelyPassword, hostVerificationEnabled, bufferSize);
|
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, clientAuth)) {
|
try (CloseableHttpClient client = HttpClient.get(ssl, cookieJar, hostVerificationEnabled)) {
|
||||||
|
|
||||||
String target = "https://" + timelyHostname + ":" + timelyHttpsPort + "/login";
|
String target = "https://" + timelyHostname + ":" + timelyHttpsPort + "/login";
|
||||||
|
|
||||||
@@ -142,8 +140,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,
|
session = webSocketClient.connectToServer(clientEndpoint, new TimelyEndpointConfig(sessionCookie),
|
||||||
new TimelyEndpointConfig(clientEndpoint, sessionCookie), new URI(wssPath));
|
new URI(wssPath));
|
||||||
|
|
||||||
final ByteBuffer pingData = ByteBuffer.allocate(0);
|
final ByteBuffer pingData = ByteBuffer.allocate(0);
|
||||||
webSocketClient.getScheduledExecutorService().scheduleAtFixedRate(() -> {
|
webSocketClient.getScheduledExecutorService().scheduleAtFixedRate(() -> {
|
||||||
|
|||||||
+10
-10
@@ -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 clientAuth, boolean doLogin, String timelyUsername, String timelyPassword,
|
boolean doLogin, String timelyUsername, String timelyPassword, boolean hostVerificationEnabled,
|
||||||
boolean hostVerificationEnabled, int bufferSize) {
|
int bufferSize) {
|
||||||
super(ssl, timelyHostname, timelyHttpsPort, timelyWssPort, clientAuth, doLogin, timelyUsername, timelyPassword,
|
super(ssl, timelyHostname, timelyHttpsPort, timelyWssPort, 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,
|
public WebSocketSubscriptionClient(String timelyHostname, int timelyHttpsPort, int timelyWssPort, boolean doLogin,
|
||||||
boolean clientAuth, boolean doLogin, String timelyUsername, String timelyPassword, String keyStoreFile,
|
String timelyUsername, String timelyPassword, String keyStoreFile, String keyStoreType, String keyStorePass,
|
||||||
String keyStoreType, String keyStorePass, String trustStoreFile, String trustStoreType,
|
String trustStoreFile, String trustStoreType, String trustStorePass, boolean hostVerificationEnabled,
|
||||||
String trustStorePass, boolean hostVerificationEnabled, int bufferSize) {
|
int bufferSize) {
|
||||||
super(timelyHostname, timelyHttpsPort, timelyWssPort, clientAuth, doLogin, timelyUsername, timelyPassword,
|
super(timelyHostname, timelyHttpsPort, timelyWssPort, doLogin, timelyUsername, timelyPassword, keyStoreFile,
|
||||||
keyStoreFile, keyStoreType, keyStorePass, trustStoreFile, trustStoreType, trustStorePass,
|
keyStoreType, keyStorePass, trustStoreFile, trustStoreType, trustStorePass, hostVerificationEnabled,
|
||||||
hostVerificationEnabled, bufferSize);
|
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', interpolate=True, fill=None):
|
def resample(df, sample, how='mean'):
|
||||||
|
|
||||||
dataFrame = pandas.DataFrame(df, copy=True)
|
dataFrame = pandas.DataFrame(df, copy=True)
|
||||||
if dataFrame is not None:
|
if dataFrame is not None:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ 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,15 +16,14 @@ 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, client_cert=None, client_key=None, ca_certs=None, validate_cert=False):
|
request_timeout=WebSocketClient.DEFAULT_REQUEST_TIMEOUT):
|
||||||
|
|
||||||
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
|
||||||
@@ -73,8 +72,7 @@ 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
|
||||||
@@ -84,8 +82,7 @@ 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,14 +15,10 @@ 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, client_cert=None, client_key=None, ca_certs=None, validate_cert=False):
|
request_timeout=DEFAULT_REQUEST_TIMEOUT):
|
||||||
|
|
||||||
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('/')):
|
||||||
@@ -43,8 +39,7 @@ 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=self.validate_cert,
|
request = websocket.httpclient.HTTPRequest(self.url, connect_timeout=self.connect_timeout, request_timeout=self.request_timeout, validate_cert=False)
|
||||||
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,9 +5,7 @@ import os
|
|||||||
from timely.TimelyAnalyticConfiguration import TimelyAnalyticConfiguration
|
from timely.TimelyAnalyticConfiguration import TimelyAnalyticConfiguration
|
||||||
|
|
||||||
hostport = "127.0.0.1:5244"
|
hostport = "127.0.0.1:5244"
|
||||||
client_cert = None
|
timelyMetric = timely.TimelyMetric.TimelyMetric(hostport, 'timely.keys.metric.inserted', None, None, None, '24 hours', None).fetch()
|
||||||
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()
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>timely</groupId>
|
<groupId>timely</groupId>
|
||||||
<artifactId>timely</artifactId>
|
<artifactId>timely</artifactId>
|
||||||
<version>1.1.1-SNAPSHOT</version>
|
<version>1.0.1-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>collectd-nsq-plugin</artifactId>
|
<artifactId>collectd-nsq-plugin</artifactId>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>timely</groupId>
|
<groupId>timely</groupId>
|
||||||
<artifactId>timely</artifactId>
|
<artifactId>timely</artifactId>
|
||||||
<version>1.1.1-SNAPSHOT</version>
|
<version>1.0.1-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>collectd-plugin-parent</artifactId>
|
<artifactId>collectd-plugin-parent</artifactId>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>timely</groupId>
|
<groupId>timely</groupId>
|
||||||
<artifactId>timely</artifactId>
|
<artifactId>timely</artifactId>
|
||||||
<version>1.1.1-SNAPSHOT</version>
|
<version>1.0.1-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>collectd-timely-plugin</artifactId>
|
<artifactId>collectd-timely-plugin</artifactId>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>timely</groupId>
|
<groupId>timely</groupId>
|
||||||
<artifactId>timely</artifactId>
|
<artifactId>timely</artifactId>
|
||||||
<version>1.1.1-SNAPSHOT</version>
|
<version>1.0.1-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>timely-common</artifactId>
|
<artifactId>timely-common</artifactId>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>timely</groupId>
|
<groupId>timely</groupId>
|
||||||
<artifactId>timely</artifactId>
|
<artifactId>timely</artifactId>
|
||||||
<version>1.1.1-SNAPSHOT</version>
|
<version>1.0.1-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>timely-app</artifactId>
|
<artifactId>timely-app</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|||||||
Vendored
+2
-2
@@ -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": "1.1.0",
|
"version": "0.0.6-SNAPSHOT",
|
||||||
"updated": "2019-01-15"
|
"updated": "2017-08-11"
|
||||||
},
|
},
|
||||||
|
|
||||||
"includes": [
|
"includes": [
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "timely-app",
|
"name": "timely-app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.1.0",
|
"version": "0.0.2",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -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": "1.1.0",
|
"version": "0.0.6-SNAPSHOT",
|
||||||
"updated": "2019-01-15"
|
"updated": "2017-08-11"
|
||||||
},
|
},
|
||||||
|
|
||||||
"includes": [
|
"includes": [
|
||||||
|
|||||||
@@ -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.1.1-SNAPSHOT</version>
|
<version>1.0.1-SNAPSHOT</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>
|
||||||
@@ -40,10 +40,10 @@
|
|||||||
<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.9.2</version.accumulo>
|
<version.accumulo>1.7.4</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.3.3</version.caffeine>
|
||||||
<version.collectd>5.7.1</version.collectd>
|
<version.collectd>5.5.1</version.collectd>
|
||||||
<version.commons-collections4>4.1</version.commons-collections4>
|
<version.commons-collections4>4.1</version.commons-collections4>
|
||||||
<version.commons-configuration>1.10</version.commons-configuration>
|
<version.commons-configuration>1.10</version.commons-configuration>
|
||||||
<version.commons-io>2.6</version.commons-io>
|
<version.commons-io>2.6</version.commons-io>
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
<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.2</version.httpcomponents>
|
||||||
<version.impsort>1.2.0</version.impsort>
|
<version.impsort>1.2.0</version.impsort>
|
||||||
<version.jackson>2.9.8</version.jackson>
|
<version.jackson>2.9.7</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.21.0-GA</version.javassist>
|
||||||
@@ -102,11 +102,6 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -15,27 +15,15 @@
|
|||||||
|
|
||||||
<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:list>
|
<util:map key-type="java.lang.String">
|
||||||
<beans:bean class="timely.auth.TimelyUser">
|
<beans:entry key="example.com">
|
||||||
<beans:constructor-arg name="subjectDn">
|
|
||||||
<beans:value>CN=example.com</beans:value>
|
|
||||||
</beans:constructor-arg>
|
|
||||||
<beans:constructor-arg name="auths">
|
|
||||||
<util:list>
|
<util:list>
|
||||||
<beans:value>D</beans:value>
|
<beans:value>D</beans:value>
|
||||||
<beans:value>E</beans:value>
|
<beans:value>E</beans:value>
|
||||||
<beans:value>F</beans:value>
|
<beans:value>F</beans:value>
|
||||||
</util:list>
|
</util:list>
|
||||||
</beans:constructor-arg>
|
</beans:entry>
|
||||||
<beans:constructor-arg name="roles">
|
</util:map>
|
||||||
<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>
|
||||||
|
|
||||||
@@ -58,8 +46,4 @@
|
|||||||
<authentication-provider ref="UserNamePasswordAuthenticationProvider" />
|
<authentication-provider ref="UserNamePasswordAuthenticationProvider" />
|
||||||
</authentication-manager>
|
</authentication-manager>
|
||||||
|
|
||||||
<beans:bean id="requiredRoles" class="Array">
|
|
||||||
|
|
||||||
</beans:bean>
|
|
||||||
|
|
||||||
</beans:beans>
|
</beans:beans>
|
||||||
@@ -18,8 +18,7 @@ timely:
|
|||||||
max-downsample-memory: 10000000
|
max-downsample-memory: 10000000
|
||||||
security:
|
security:
|
||||||
session-max-age: 86400
|
session-max-age: 86400
|
||||||
allow-anonymous-http-access: true
|
allow-anonymous-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
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ timely:
|
|||||||
max-downsample-memory: 10000000
|
max-downsample-memory: 10000000
|
||||||
security:
|
security:
|
||||||
session-max-age: 86400
|
session-max-age: 86400
|
||||||
allow-anonymous-http-access: true
|
allow-anonymous-access: true
|
||||||
allow-anonymous-ws-access: true
|
|
||||||
ssl:
|
ssl:
|
||||||
certificate-file:
|
certificate-file:
|
||||||
key-file:
|
key-file:
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ timely:
|
|||||||
max-downsample-memory: 10000000
|
max-downsample-memory: 10000000
|
||||||
security:
|
security:
|
||||||
session-max-age: 86400
|
session-max-age: 86400
|
||||||
allow-anonymous-http-access: true
|
allow-anonymous-access: true
|
||||||
allow-anonymous-ws-access: true
|
|
||||||
serverSsl:
|
serverSsl:
|
||||||
certificate-file:
|
certificate-file:
|
||||||
key-file:
|
key-file:
|
||||||
|
|||||||
+1
-5
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>timely</groupId>
|
<groupId>timely</groupId>
|
||||||
<artifactId>timely</artifactId>
|
<artifactId>timely</artifactId>
|
||||||
<version>1.1.1-SNAPSHOT</version>
|
<version>1.0.1-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>timely-server</artifactId>
|
<artifactId>timely-server</artifactId>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -20,10 +20,6 @@
|
|||||||
<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>
|
||||||
|
|||||||
Vendored
+1
-1
@@ -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>
|
||||||
|
|||||||
@@ -101,7 +101,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.WebSocketFullRequestHandler;
|
import timely.netty.websocket.WebSocketHttpCookieHandler;
|
||||||
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;
|
||||||
@@ -430,7 +430,7 @@ public class Server {
|
|||||||
// Initialize the MetaCache
|
// Initialize the MetaCache
|
||||||
MetaCacheFactory.getCache(config);
|
MetaCacheFactory.getCache(config);
|
||||||
// initialize the auth cache
|
// initialize the auth cache
|
||||||
AuthCache.setSessionMaxAge(config.getSecurity());
|
AuthCache.setSessionMaxAge(config);
|
||||||
// Initialize the VisibilityCache
|
// Initialize the VisibilityCache
|
||||||
VisibilityCache.init(config);
|
VisibilityCache.init(config);
|
||||||
final boolean useEpoll = useEpoll();
|
final boolean useEpoll = useEpoll();
|
||||||
@@ -664,7 +664,7 @@ public class Server {
|
|||||||
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(8192));
|
||||||
ch.pipeline().addLast("sessionExtractor", new WebSocketFullRequestHandler());
|
ch.pipeline().addLast("sessionExtractor", new WebSocketHttpCookieHandler(config.getSecurity()));
|
||||||
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));
|
||||||
@@ -676,8 +676,7 @@ 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",
|
ch.pipeline().addLast("create", new WSCreateSubscriptionRequestHandler(dataStore, config));
|
||||||
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());
|
||||||
|
|||||||
@@ -1,22 +1,18 @@
|
|||||||
package timely.api.request;
|
package timely.api.request;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import java.util.Collections;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import java.util.HashMap;
|
||||||
import com.google.common.collect.HashMultimap;
|
import java.util.List;
|
||||||
import com.google.common.collect.Multimap;
|
import java.util.Map;
|
||||||
import timely.auth.TimelyUser;
|
import java.util.Map.Entry;
|
||||||
import timely.auth.util.HttpHeaderUtils;
|
|
||||||
import timely.netty.http.auth.TimelyAuthenticationToken;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for requests that require authentication
|
* Base class for requests that require authentication
|
||||||
*/
|
*/
|
||||||
@JsonIgnoreProperties({ "userName", "requestHeader" })
|
|
||||||
public class AuthenticatedRequest implements Request {
|
public class AuthenticatedRequest implements Request {
|
||||||
|
|
||||||
private String sessionId = null;
|
private String sessionId = null;
|
||||||
private TimelyAuthenticationToken token = null;
|
private Map<String, String> requestHeaders = new HashMap<>();
|
||||||
private Multimap<String, String> requestHeaders = HashMultimap.create();
|
|
||||||
|
|
||||||
public String getSessionId() {
|
public String getSessionId() {
|
||||||
return sessionId;
|
return sessionId;
|
||||||
@@ -26,17 +22,12 @@ public class AuthenticatedRequest implements Request {
|
|||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addHeaders(Multimap<String, String> headers) {
|
public void addHeaders(List<Entry<String, String>> headers) {
|
||||||
requestHeaders.putAll(headers);
|
headers.forEach(l -> requestHeaders.put(l.getKey(), l.getValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Multimap<String, String> getRequestHeaders() {
|
public Map<String, String> getRequestHeaders() {
|
||||||
return requestHeaders;
|
return Collections.unmodifiableMap(requestHeaders);
|
||||||
}
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
public String getRequestHeader(String name) {
|
|
||||||
return HttpHeaderUtils.getSingleHeader(requestHeaders, name, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -44,32 +35,17 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setToken(TimelyAuthenticationToken token) {
|
@Override
|
||||||
this.token = token;
|
public void validate() {
|
||||||
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,25 @@
|
|||||||
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.slf4j.Logger;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import timely.api.request.AuthenticatedRequest;
|
import timely.api.request.AuthenticatedRequest;
|
||||||
import timely.api.request.AuthenticatedWebSocketRequest;
|
import timely.api.request.Request;
|
||||||
import timely.auth.util.AuthorizationsUtil;
|
import timely.api.response.TimelyException;
|
||||||
|
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 final Logger LOG = LoggerFactory.getLogger(AuthCache.class);
|
private static Cache<String, Authentication> CACHE = null;
|
||||||
private static Cache<String, TimelyPrincipal> CACHE = null;
|
|
||||||
|
|
||||||
private static int sessionMaxAge = -1;
|
private static int sessionMaxAge = -1;
|
||||||
|
|
||||||
@@ -32,14 +30,14 @@ public class AuthCache {
|
|||||||
sessionMaxAge = -1;
|
sessionMaxAge = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setSessionMaxAge(Security security) {
|
public static void setSessionMaxAge(Configuration config) {
|
||||||
if (-1 != sessionMaxAge) {
|
if (-1 != sessionMaxAge) {
|
||||||
throw new IllegalStateException("Cache session max age already configured.");
|
throw new IllegalStateException("Cache session max age already configured.");
|
||||||
}
|
}
|
||||||
sessionMaxAge = security.getSessionMaxAge();
|
sessionMaxAge = config.getSecurity().getSessionMaxAge();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Cache<String, TimelyPrincipal> getCache() {
|
public static Cache<String, Authentication> getCache() {
|
||||||
if (-1 == sessionMaxAge) {
|
if (-1 == sessionMaxAge) {
|
||||||
throw new IllegalStateException("Cache session max age not configured.");
|
throw new IllegalStateException("Cache session max age not configured.");
|
||||||
}
|
}
|
||||||
@@ -49,67 +47,35 @@ public class AuthCache {
|
|||||||
return CACHE;
|
return CACHE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean containsKey(String key) {
|
public static Authorizations getAuthorizations(String sessionId) {
|
||||||
return getCache().getIfPresent(key) != null;
|
if (!StringUtils.isEmpty(sessionId)) {
|
||||||
}
|
Authentication auth = CACHE.asMap().get(sessionId);
|
||||||
|
if (null != auth) {
|
||||||
public static TimelyPrincipal get(String key) {
|
Collection<? extends GrantedAuthority> authorities = CACHE.asMap().get(sessionId).getAuthorities();
|
||||||
return getCache().getIfPresent(key);
|
String[] auths = new String[authorities.size()];
|
||||||
}
|
final AtomicInteger i = new AtomicInteger(0);
|
||||||
|
authorities.forEach(a -> auths[i.getAndIncrement()] = a.getAuthority());
|
||||||
public static void put(String key, TimelyPrincipal principal) {
|
return new Authorizations(auths);
|
||||||
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 Collections.singletonList(Authorizations.EMPTY);
|
return null;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("entityName can not be null");
|
throw new IllegalArgumentException("session id cannot be null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Collection<Authorizations> getAuthorizations(AuthenticatedRequest request, Security security) {
|
public static void enforceAccess(Security security, Request r) throws Exception {
|
||||||
Collection<Authorizations> auths;
|
if (!security.isAllowAnonymousAccess() && (r instanceof AuthenticatedRequest)) {
|
||||||
boolean anonAccessAllowed;
|
AuthenticatedRequest ar = (AuthenticatedRequest) r;
|
||||||
if (request instanceof AuthenticatedWebSocketRequest) {
|
if (StringUtils.isEmpty(ar.getSessionId())) {
|
||||||
anonAccessAllowed = security.isAllowAnonymousWsAccess();
|
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "User must log in",
|
||||||
} else {
|
"Anonymous access is disabled, log in first");
|
||||||
anonAccessAllowed = security.isAllowAnonymousHttpAccess();
|
|
||||||
}
|
}
|
||||||
TimelyAuthenticationToken token = request.getToken();
|
if (!AuthCache.getCache().asMap().containsKey(ar.getSessionId())) {
|
||||||
String sessionId = request.getSessionId();
|
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "User must log in",
|
||||||
if (StringUtils.isBlank(sessionId) && token.getClientCert() == null) {
|
"Unknown session id was submitted, log in again");
|
||||||
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,235 +1,37 @@
|
|||||||
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.core.Authentication;
|
|
||||||
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
|
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
|
||||||
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 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 = springContext.getBean("authenticationManager", AuthenticationManager.class);
|
authManager = (AuthenticationManager) springContext.getBean("authenticationManager");
|
||||||
x509 = springContext.getBean("x509PrincipalExtractor", SubjectDnX509PrincipalExtractor.class);
|
x509 = (SubjectDnX509PrincipalExtractor) springContext.getBean("x509PrincipalExtractor");
|
||||||
requiredRoles = springContext.getBean("requiredRoles", List.class);
|
|
||||||
requiredAuths = springContext.getBean("requiredAuths", List.class);
|
|
||||||
} 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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AuthenticationManager getAuthenticationManager() {
|
public static AuthenticationManager getAuthenticationManager() {
|
||||||
return authManager;
|
return authManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TimelyPrincipal authenticate(Authentication authentication, String cacheName) {
|
public static String extractDN(X509Certificate clientCert) throws Exception {
|
||||||
return authenticate(authentication, SubjectIssuerDNPair.of(cacheName), cacheName);
|
return (String) x509.extractPrincipal(clientCert);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TimelyPrincipal authenticate(Authentication authentication, SubjectIssuerDNPair pair, String entity) {
|
|
||||||
|
|
||||||
// only one thread should call the authenticationManager for a given entity
|
|
||||||
TimelyPrincipal 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
|
|
||||||
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);
|
|
||||||
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();
|
|
||||||
if (!AuthCache.containsKey(requestPrincipal.getName())) {
|
|
||||||
Object newObj = new Object();
|
|
||||||
Object o = fetchingPrincipal.getOrDefault(entity, newObj);
|
|
||||||
if (o == null) {
|
|
||||||
o = newObj;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
synchronized (o) {
|
|
||||||
// check again if in the AuthCache
|
|
||||||
if (!AuthCache.containsKey(entity)) {
|
|
||||||
// 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());
|
|
||||||
}
|
|
||||||
TimelyPrincipal principal = new TimelyPrincipal(authenticatedTimelyUsers);
|
|
||||||
AuthCache.put(principal.getName(), principal);
|
|
||||||
LOG.debug("Authenticated user {} for request {} with authorizations {}",
|
|
||||||
principal.getName(), request.toString(), principal.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);
|
|
||||||
}
|
|
||||||
|
|
||||||
TimelyUser primaryUser = verifiedPrincipal.getPrimaryUser();
|
|
||||||
if (requiredRoles != null) {
|
|
||||||
if (!primaryUser.getRoles().containsAll(requiredRoles)) {
|
|
||||||
Set<String> missingRoles = new TreeSet<>();
|
|
||||||
missingRoles.addAll(requiredRoles);
|
|
||||||
missingRoles.removeAll(primaryUser.getRoles());
|
|
||||||
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "Access denied",
|
|
||||||
"User:" + primaryUser.getCommonName() + " is missing auth(s):" + missingRoles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (requiredAuths != null) {
|
|
||||||
if (!primaryUser.getAuths().containsAll(requiredAuths)) {
|
|
||||||
Set<String> missingAuths = new TreeSet<>();
|
|
||||||
missingAuths.addAll(requiredRoles);
|
|
||||||
missingAuths.removeAll(primaryUser.getAuths());
|
|
||||||
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "Access denied",
|
|
||||||
"User:" + primaryUser.getCommonName() + " is missing role(s):" + missingAuths);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error(e.getMessage(), e);
|
|
||||||
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "Access denied", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(X509Certificate 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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
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(" -> ", "[", "]"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
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,6 +3,7 @@ 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;
|
||||||
|
|
||||||
@@ -10,20 +11,18 @@ 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, TimelyUser> users;
|
private HashMap<String, List<String>> users;
|
||||||
|
|
||||||
public Map<String, TimelyUser> getUsers() {
|
public Map<String, List<String>> getUsers() {
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUsers(List<TimelyUser> users) {
|
public void setUsers(LinkedHashMap<String, List<String>> users) {
|
||||||
this.users = new HashMap<>();
|
this.users = new HashMap<>(users);
|
||||||
users.forEach(u -> this.users.put(u.getName(), u));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -31,32 +30,25 @@ 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();
|
String username = token.getName().toString();
|
||||||
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 TimelyUserDetails() {
|
return new UserDetails() {
|
||||||
|
|
||||||
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).getAuths().forEach(a -> {
|
users.get(username).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 "";
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
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 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,27 +6,18 @@ import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
|||||||
|
|
||||||
public class Security {
|
public class Security {
|
||||||
|
|
||||||
private boolean allowAnonymousHttpAccess = false;
|
private boolean allowAnonymousAccess = false;
|
||||||
private boolean allowAnonymousWsAccess = false;
|
|
||||||
private int sessionMaxAge = 86400;
|
private int sessionMaxAge = 86400;
|
||||||
@Valid
|
@Valid
|
||||||
@NestedConfigurationProperty
|
@NestedConfigurationProperty
|
||||||
private ServerSsl serverSsl = new ServerSsl();
|
private ServerSsl serverSsl = new ServerSsl();
|
||||||
|
|
||||||
public boolean isAllowAnonymousHttpAccess() {
|
public boolean isAllowAnonymousAccess() {
|
||||||
return allowAnonymousHttpAccess;
|
return allowAnonymousAccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAllowAnonymousHttpAccess(boolean allowAnonymousHttpAccess) {
|
public void setAllowAnonymousAccess(boolean allowAnonymousAccess) {
|
||||||
this.allowAnonymousHttpAccess = allowAnonymousHttpAccess;
|
this.allowAnonymousAccess = allowAnonymousAccess;
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAllowAnonymousWsAccess() {
|
|
||||||
return allowAnonymousWsAccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAllowAnonymousWsAccess(boolean allowAnonymousWsAccess) {
|
|
||||||
this.allowAnonymousWsAccess = allowAnonymousWsAccess;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSessionMaxAge() {
|
public int getSessionMaxAge() {
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
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;
|
||||||
@@ -15,7 +12,6 @@ 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;
|
||||||
@@ -26,20 +22,16 @@ 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;
|
||||||
@@ -50,11 +42,9 @@ public class HttpRequestDecoder extends MessageToMessageDecoder<FullHttpRequest>
|
|||||||
this.nonSecureRedirectAddress = http.getRedirectPath();
|
this.nonSecureRedirectAddress = http.getRedirectPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getSessionId(FullHttpRequest msg) {
|
public static String getSessionId(FullHttpRequest msg, boolean anonymousAccessAllowed) {
|
||||||
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();
|
||||||
cookies.forEach(h -> {
|
msg.headers().getAll(Names.COOKIE).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) {
|
||||||
@@ -63,11 +53,13 @@ public class HttpRequestDecoder extends MessageToMessageDecoder<FullHttpRequest>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (buf.length() == 0) {
|
String sessionId = buf.toString();
|
||||||
return null;
|
if (sessionId.length() == 0 && anonymousAccessAllowed) {
|
||||||
} else {
|
sessionId = NO_AUTHORIZATIONS;
|
||||||
return buf.toString();
|
} else if (sessionId.length() == 0) {
|
||||||
|
sessionId = null;
|
||||||
}
|
}
|
||||||
|
return sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -82,7 +74,7 @@ public class HttpRequestDecoder extends MessageToMessageDecoder<FullHttpRequest>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String sessionId = getSessionId(msg);
|
final String sessionId = getSessionId(msg, this.security.isAllowAnonymousAccess());
|
||||||
LOG.trace("SessionID: " + sessionId);
|
LOG.trace("SessionID: " + sessionId);
|
||||||
|
|
||||||
HttpRequest request;
|
HttpRequest request;
|
||||||
@@ -105,28 +97,9 @@ 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) {
|
if (request instanceof AuthenticatedRequest && sessionId != null) {
|
||||||
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);
|
((AuthenticatedRequest) request).setSessionId(sessionId);
|
||||||
if (AuthCache.containsKey(sessionId)) {
|
((AuthenticatedRequest) request).addHeaders(msg.headers().entries());
|
||||||
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);
|
||||||
@@ -140,15 +113,7 @@ public class HttpRequestDecoder extends MessageToMessageDecoder<FullHttpRequest>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (request instanceof AuthenticatedRequest) {
|
AuthCache.enforceAccess(security, request);
|
||||||
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,35 +1,27 @@
|
|||||||
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 TimelyPrincipal authenticate(ChannelHandlerContext ctx, BasicAuthLoginRequest msg, String sessionId)
|
protected Authentication authenticate(ChannelHandlerContext ctx, BasicAuthLoginRequest msg)
|
||||||
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());
|
||||||
TimelyPrincipal principal = AuthenticationService.authenticate(token, sessionId);
|
return AuthenticationService.getAuthenticationManager().authenticate(token);
|
||||||
|
|
||||||
LOG.debug("Authenticated user {} with authorizations {}", principal.getPrimaryUser().getDn().subjectDN(),
|
|
||||||
principal.getAuthorizationsString());
|
|
||||||
return principal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
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;
|
|
||||||
private TimelyPrincipal timelyPrincipal;
|
|
||||||
|
|
||||||
public TimelyAuthenticationToken(String subjectDn, X509Certificate clientCert,
|
|
||||||
Multimap<String, String> httpHeaders) {
|
|
||||||
super(subjectDn, clientCert);
|
|
||||||
if (httpHeaders == null) {
|
|
||||||
this.httpHeaders = HashMultimap.create();
|
|
||||||
} else {
|
|
||||||
this.httpHeaders = httpHeaders;
|
|
||||||
}
|
|
||||||
this.clientCert = clientCert;
|
|
||||||
String issuerDn = clientCert == null ? null : 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, X509Certificate clientCert) {
|
|
||||||
super(subjectDn, clientCert);
|
|
||||||
this.clientCert = 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,7 +15,8 @@ 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 timely.auth.TimelyPrincipal;
|
import org.springframework.security.core.Authentication;
|
||||||
|
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;
|
||||||
@@ -37,15 +38,14 @@ 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);
|
||||||
String sessionId = UUID.randomUUID().toString();
|
Authentication auth = authenticate(ctx, loginRequest);
|
||||||
TimelyPrincipal principal = authenticate(ctx, loginRequest, sessionId);
|
LOG.trace("Authenticated {}", auth);
|
||||||
LOG.trace("Authenticated new sessionId {} for user {}", sessionId,
|
String sessionId = URLEncoder.encode(UUID.randomUUID().toString(), StandardCharsets.UTF_8.name());
|
||||||
principal.getPrimaryUser().getDn().subjectDN());
|
AuthCache.getCache().put(sessionId, auth);
|
||||||
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, sessionIdEncoded);
|
DefaultCookie cookie = new DefaultCookie(Constants.COOKIE_NAME, sessionId);
|
||||||
cookie.setDomain(domain);
|
cookie.setDomain(domain);
|
||||||
cookie.setMaxAge(maxAge);
|
cookie.setMaxAge(maxAge);
|
||||||
cookie.setPath("/");
|
cookie.setPath("/");
|
||||||
@@ -64,6 +64,5 @@ public abstract class TimelyLoginRequestHandler<T> extends SimpleChannelInboundH
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract TimelyPrincipal authenticate(ChannelHandlerContext ctx, T loginRequest, String entity)
|
protected abstract Authentication authenticate(ChannelHandlerContext ctx, T loginRequest) throws Exception;
|
||||||
throws Exception;
|
|
||||||
}
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
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,50 +3,35 @@ 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.HttpResponseStatus;
|
import io.netty.handler.codec.http.HttpHeaders;
|
||||||
import org.slf4j.Logger;
|
import io.netty.handler.ssl.SslHandler;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.springframework.security.core.Authentication;
|
||||||
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 TimelyPrincipal authenticate(ChannelHandlerContext ctx, X509LoginRequest loginRequest, String sessionId)
|
protected Authentication authenticate(ChannelHandlerContext ctx, X509LoginRequest loginRequest) throws Exception {
|
||||||
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.
|
||||||
TimelyPrincipal principal = null;
|
SslHandler sslHandler = (SslHandler) ctx.channel().pipeline().get("ssl");
|
||||||
try {
|
if (null != sslHandler) {
|
||||||
X509Certificate clientCert = AuthenticationService.getClientCertificate(ctx);
|
X509Certificate clientCert = (X509Certificate) sslHandler.engine().getSession().getPeerCertificates()[0];
|
||||||
if (clientCert == null) {
|
String subjectDN = AuthenticationService.extractDN(clientCert);
|
||||||
throw new IllegalArgumentException("No client certificate found");
|
HttpHeaders httpHeaders = loginRequest.getHttpRequest().headers();
|
||||||
|
TimelyPreAuthenticatedAuthenticationToken token = new TimelyPreAuthenticatedAuthenticationToken(subjectDN,
|
||||||
|
clientCert, httpHeaders);
|
||||||
|
return AuthenticationService.getAuthenticationManager().authenticate(token);
|
||||||
} else {
|
} else {
|
||||||
// login endpoint is for direct certificate only, not for proxying
|
throw new IllegalStateException("The expected SSL handler is not in the pipeline.");
|
||||||
TimelyAuthenticationToken token = AuthenticationService.getAuthenticationToken(clientCert,
|
|
||||||
clientCert.getSubjectDN().getName(), clientCert.getIssuerDN().getName());
|
|
||||||
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.getRequestHeader(Names.ACCEPT);
|
String acceptHeader = msg.getRequestHeaders().get(Names.ACCEPT);
|
||||||
sendResponse(ctx, r.toHttpResponse(acceptHeader));
|
sendResponse(ctx, r.toHttpResponse(acceptHeader));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
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,19 +1,15 @@
|
|||||||
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;
|
||||||
@@ -21,19 +17,12 @@ 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;
|
||||||
@@ -50,32 +39,14 @@ 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();
|
||||||
Multimap<String, String> headers = ctx.channel().attr(HTTP_HEADERS_ATTR).get();
|
if (request instanceof AuthenticatedRequest && !StringUtils.isEmpty(sessionId)) {
|
||||||
X509Certificate clientCert = ctx.channel().attr(CLIENT_CERT_ATTR).get();
|
LOG.info("Found session id in WebSocket channel, setting sessionId {} on request", sessionId);
|
||||||
if (request instanceof AuthenticatedRequest) {
|
AuthenticatedRequest ar = (AuthenticatedRequest) request;
|
||||||
if (headers != null) {
|
ar.setSessionId(sessionId);
|
||||||
((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) {
|
||||||
@@ -84,16 +55,8 @@ public class WebSocketRequestDecoder extends MessageToMessageDecoder<WebSocketFr
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (request instanceof AuthenticatedRequest) {
|
AuthCache.enforceAccess(security, request);
|
||||||
try {
|
|
||||||
AuthenticationService.enforceAccess((AuthenticatedRequest) request);
|
|
||||||
} catch (TimelyException e) {
|
} 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()));
|
||||||
@@ -136,11 +99,10 @@ public class WebSocketRequestDecoder extends MessageToMessageDecoder<WebSocketFr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (evt instanceof SslCompletionEvent) {
|
} else if (evt instanceof SslCompletionEvent) {
|
||||||
LOG.debug("{}", ((SslCompletionEvent) evt).getClass().getSimpleName());
|
SslCompletionEvent ssl = (SslCompletionEvent) evt;
|
||||||
} else if (evt instanceof WebSocketServerProtocolHandler.ServerHandshakeStateEvent) {
|
if (!ssl.isSuccess()) {
|
||||||
// The handshake completed succesfully and the channel was upgraded to
|
LOG.error("SSL error: {}", ssl.getClass().getSimpleName(), ssl.cause());
|
||||||
// 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);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -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(add, metric, tags, startTime, endTime, delayTime);
|
s.addMetric(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"));
|
||||||
|
|||||||
+4
-7
@@ -9,7 +9,6 @@ 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;
|
||||||
|
|
||||||
@@ -17,12 +16,10 @@ 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, DataStoreCache cache, Configuration conf) {
|
public WSCreateSubscriptionRequestHandler(DataStore store, Configuration conf) {
|
||||||
this.store = store;
|
this.store = store;
|
||||||
this.cache = cache;
|
|
||||||
this.conf = conf;
|
this.conf = conf;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,11 +27,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, cache, ctx, this.conf));
|
new Subscription(subscriptionId, create.getSessionId(), store, 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() {
|
||||||
|
|
||||||
@@ -42,7 +39,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", subscriptionId);
|
LOG.info("Channel closed, closing subscriptions for subscriptionId: " + subscriptionId);
|
||||||
s.close();
|
s.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ 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;
|
||||||
@@ -37,8 +36,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(AuthenticatedRequest request, String metric, Map<String, String> tags,
|
Scanner createScannerForMetric(String sessionId, String metric, Map<String, String> tags, long startTime,
|
||||||
int scannerBatchSize, int scannerReadAhead) throws TimelyException;
|
long endTime, int lag, int scannerBatchSize, int scannerReadAhead) throws TimelyException;
|
||||||
|
|
||||||
void setCache(DataStoreCache cache);
|
void setCache(DataStoreCache cache);
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ 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.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.hadoop.io.Text;
|
import org.apache.hadoop.io.Text;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -70,10 +71,8 @@ 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;
|
||||||
@@ -119,14 +118,14 @@ 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("InternalMetricsTimer", true);
|
private final Timer internalMetricsTimer = new Timer("InternalMatricsTimer", 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 final Security security;
|
private boolean anonAccessAllowed = false;
|
||||||
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;
|
||||||
@@ -148,7 +147,7 @@ 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();
|
||||||
security = conf.getSecurity();
|
anonAccessAllowed = conf.getSecurity().isAllowAnonymousAccess();
|
||||||
|
|
||||||
metricsTable = conf.getMetricsTable();
|
metricsTable = conf.getMetricsTable();
|
||||||
if (metricsTable.contains(".")) {
|
if (metricsTable.contains(".")) {
|
||||||
@@ -506,7 +505,8 @@ 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,9 +549,8 @@ public class DataStoreImpl implements DataStore {
|
|||||||
if (percentServedFromCache > 100) {
|
if (percentServedFromCache > 100) {
|
||||||
percentServedFromCache = 100;
|
percentServedFromCache = 100;
|
||||||
}
|
}
|
||||||
LOG.debug(
|
LOG.debug("Cache query time:{} duration (min):{} metrics:{} results:{} percentFromCache:{}",
|
||||||
"Cache query for [{}] time:{} duration (min):{} metrics:{} results:{} percentFromCache:{}",
|
(System.currentTimeMillis() - now),
|
||||||
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);
|
||||||
@@ -582,8 +581,7 @@ public class DataStoreImpl implements DataStore {
|
|||||||
if (endOfLastPeriod > startOfFirstPeriod) {
|
if (endOfLastPeriod > startOfFirstPeriod) {
|
||||||
BatchScanner scanner = null;
|
BatchScanner scanner = null;
|
||||||
try {
|
try {
|
||||||
Collection<Authorizations> authorizations = getSessionAuthorizations(msg);
|
scanner = connector.createBatchScanner(metricsTable, 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());
|
||||||
@@ -660,9 +658,8 @@ 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 for [{}] time:{} duration:{} metrics:{} results:{}", msg.getUserName(),
|
LOG.debug("Query time:{} duration:{} metrics:{} results:{}", (System.currentTimeMillis() - now),
|
||||||
(System.currentTimeMillis() - now), ((requestedEndTs - requestedStartTs) / (1000 * 60)),
|
((requestedEndTs - requestedStartTs) / (1000 * 60)), metricList.toString(), numResults);
|
||||||
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) {
|
||||||
@@ -948,8 +945,32 @@ public class DataStoreImpl implements DataStore {
|
|||||||
return Aggregator.getAggregator(query.getAggregator());
|
return Aggregator.getAggregator(query.getAggregator());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<Authorizations> getSessionAuthorizations(AuthenticatedRequest request) {
|
private Authorizations getSessionAuthorizations(AuthenticatedRequest request) {
|
||||||
return AuthCache.getAuthorizations(request, security);
|
return getSessionAuthorizations(request.getSessionId());
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
@@ -961,15 +982,27 @@ public class DataStoreImpl implements DataStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Scanner createScannerForMetric(AuthenticatedRequest request, String metric, Map<String, String> tags,
|
public Scanner createScannerForMetric(String sessionId, String metric, Map<String, String> tags, long startTime,
|
||||||
int scannerBatchSize, int scannerReadAhead) throws TimelyException {
|
long endTime, int lag, 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<>();
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-55
@@ -25,6 +25,7 @@ import org.apache.accumulo.core.data.Value;
|
|||||||
import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
|
import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
|
||||||
import org.apache.accumulo.core.security.Authorizations;
|
import org.apache.accumulo.core.security.Authorizations;
|
||||||
import org.apache.commons.lang3.SerializationUtils;
|
import org.apache.commons.lang3.SerializationUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.curator.framework.CuratorFramework;
|
import org.apache.curator.framework.CuratorFramework;
|
||||||
import org.apache.curator.framework.recipes.atomic.DistributedAtomicValue;
|
import org.apache.curator.framework.recipes.atomic.DistributedAtomicValue;
|
||||||
import org.apache.curator.framework.recipes.cache.TreeCache;
|
import org.apache.curator.framework.recipes.cache.TreeCache;
|
||||||
@@ -35,17 +36,13 @@ import org.apache.curator.retry.RetryForever;
|
|||||||
import org.apache.zookeeper.CreateMode;
|
import org.apache.zookeeper.CreateMode;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import timely.adapter.accumulo.MetricAdapter;
|
|
||||||
import timely.api.request.AuthenticatedRequest;
|
import timely.api.request.AuthenticatedRequest;
|
||||||
import timely.api.request.timeseries.QueryRequest;
|
import timely.api.request.timeseries.QueryRequest;
|
||||||
import timely.api.response.CacheResponse;
|
import timely.api.response.CacheResponse;
|
||||||
import timely.api.response.MetricResponse;
|
|
||||||
import timely.api.response.TimelyException;
|
import timely.api.response.TimelyException;
|
||||||
import timely.api.response.timeseries.QueryResponse;
|
import timely.api.response.timeseries.QueryResponse;
|
||||||
import timely.auth.AuthCache;
|
import timely.auth.AuthCache;
|
||||||
import timely.auth.util.AuthorizationsUtil;
|
|
||||||
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;
|
||||||
@@ -72,7 +69,7 @@ public class DataStoreCache {
|
|||||||
private DistributedAtomicValue nonCachedMetricsIP = null;
|
private DistributedAtomicValue nonCachedMetricsIP = null;
|
||||||
private InterProcessReadWriteLock nonCachedMetricsIPRWLock;
|
private InterProcessReadWriteLock nonCachedMetricsIPRWLock;
|
||||||
private long maxUniqueTagSets;
|
private long maxUniqueTagSets;
|
||||||
private final Security security;
|
private boolean anonAccessAllowed;
|
||||||
private Map<String, Long> minimumAgeOff;
|
private Map<String, Long> minimumAgeOff;
|
||||||
private Map<String, String> minimumAgeOffForIterator;
|
private Map<String, String> minimumAgeOffForIterator;
|
||||||
private int flushBatch = 0;
|
private int flushBatch = 0;
|
||||||
@@ -89,7 +86,7 @@ public class DataStoreCache {
|
|||||||
LOG.info("Reading initial values from nonCachedMetricsIP");
|
LOG.info("Reading initial values from nonCachedMetricsIP");
|
||||||
readNonCachedMetricsIP();
|
readNonCachedMetricsIP();
|
||||||
maxUniqueTagSets = conf.getCache().getMaxUniqueTagSets();
|
maxUniqueTagSets = conf.getCache().getMaxUniqueTagSets();
|
||||||
security = conf.getSecurity();
|
anonAccessAllowed = conf.getSecurity().isAllowAnonymousAccess();
|
||||||
Map<String, Integer> cacheAgeOff = conf.getCache().getMetricAgeOffHours();
|
Map<String, Integer> cacheAgeOff = conf.getCache().getMetricAgeOffHours();
|
||||||
Map<String, Integer> accumuloAgeOff = conf.getMetricAgeOffDays();
|
Map<String, Integer> accumuloAgeOff = conf.getMetricAgeOffDays();
|
||||||
LOG.info("cacheAgeOff:{} accumuloAgeOff:{}", cacheAgeOff, accumuloAgeOff);
|
LOG.info("cacheAgeOff:{} accumuloAgeOff:{}", cacheAgeOff, accumuloAgeOff);
|
||||||
@@ -562,6 +559,7 @@ public class DataStoreCache {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -603,8 +601,7 @@ public class DataStoreCache {
|
|||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
try {
|
try {
|
||||||
SortedKeyValueIterator<org.apache.accumulo.core.data.Key, org.apache.accumulo.core.data.Value> itr = null;
|
SortedKeyValueIterator<org.apache.accumulo.core.data.Key, org.apache.accumulo.core.data.Value> itr = null;
|
||||||
Collection<Authorizations> authorizations = getSessionAuthorizations(msg);
|
itr = setupIterator(msg, query, getSessionAuthorizations(msg), getAgeOffForMetric(query.getMetric()));
|
||||||
itr = setupIterator(msg, query, authorizations, getAgeOffForMetric(query.getMetric()));
|
|
||||||
Map<Set<Tag>, Set<Tag>> matchingTagCache = new HashMap<>();
|
Map<Set<Tag>, Set<Tag>> matchingTagCache = new HashMap<>();
|
||||||
while (itr.hasTop()) {
|
while (itr.hasTop()) {
|
||||||
Map<Set<Tag>, Aggregation> samples = AggregationIterator.decodeValue(itr.getTopValue());
|
Map<Set<Tag>, Aggregation> samples = AggregationIterator.decodeValue(itr.getTopValue());
|
||||||
@@ -635,46 +632,8 @@ public class DataStoreCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<MetricResponse> getMetricsFromCache(AuthenticatedRequest request, String metric,
|
|
||||||
Map<String, String> tags, long begin, long end) {
|
|
||||||
|
|
||||||
List<MetricResponse> metricResponses = new ArrayList<>();
|
|
||||||
QueryRequest.SubQuery subQuery = new QueryRequest.SubQuery();
|
|
||||||
subQuery.setMetric(metric);
|
|
||||||
if (tags != null) {
|
|
||||||
for (Map.Entry<String, String> t : tags.entrySet()) {
|
|
||||||
subQuery.addTag(t.getKey(), t.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Collection<Authorizations> authorizations = getSessionAuthorizations(request);
|
|
||||||
Collection<Authorizations> minimizedAuths = AuthorizationsUtil.minimize(authorizations);
|
|
||||||
Collection<VisibilityFilter> visibilityFilters = new ArrayList<>();
|
|
||||||
for (Authorizations a : minimizedAuths) {
|
|
||||||
visibilityFilters.add(new VisibilityFilter(a));
|
|
||||||
}
|
|
||||||
DataStoreCacheIterator itr = new DataStoreCacheIterator(this, visibilityFilters, subQuery, begin, end);
|
|
||||||
try {
|
|
||||||
itr.seek(new Range(subQuery.getMetric()), null, true);
|
|
||||||
while (itr.hasTop()) {
|
|
||||||
Key k = itr.getTopKey();
|
|
||||||
Value v = itr.getTopValue();
|
|
||||||
Metric m = MetricAdapter.parse(k, v);
|
|
||||||
MetricResponse mr = new MetricResponse();
|
|
||||||
mr.setMetric(metric);
|
|
||||||
mr.setTags(m.getTags());
|
|
||||||
mr.setTimestamp(m.getValue().getTimestamp());
|
|
||||||
mr.setValue(m.getValue().getMeasure());
|
|
||||||
metricResponses.add(mr);
|
|
||||||
itr.next();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.error(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
return metricResponses;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SortedKeyValueIterator<Key, Value> setupIterator(QueryRequest query, QueryRequest.SubQuery subQuery,
|
protected SortedKeyValueIterator<Key, Value> setupIterator(QueryRequest query, QueryRequest.SubQuery subQuery,
|
||||||
Collection<Authorizations> authorizations, long ageOffForMetric) throws TimelyException {
|
Authorizations authorizations, long ageOffForMetric) throws TimelyException {
|
||||||
|
|
||||||
SortedKeyValueIterator<org.apache.accumulo.core.data.Key, org.apache.accumulo.core.data.Value> itr = null;
|
SortedKeyValueIterator<org.apache.accumulo.core.data.Key, org.apache.accumulo.core.data.Value> itr = null;
|
||||||
|
|
||||||
@@ -693,12 +652,9 @@ public class DataStoreCache {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// create DataStoreCacheIterator which is the base iterator of the stack
|
// create DataStoreCacheIterator which is the base iterator of the stack
|
||||||
Collection<Authorizations> minimizedAuths = AuthorizationsUtil.minimize(authorizations);
|
VisibilityFilter visFilter = new VisibilityFilter(authorizations);
|
||||||
Collection<VisibilityFilter> visibilityFilters = new ArrayList<>();
|
|
||||||
for (Authorizations a : minimizedAuths) {
|
itr = new DataStoreCacheIterator(this, visFilter, subQuery, startOfFirstPeriod, endOfLastPeriod);
|
||||||
((ArrayList<VisibilityFilter>) visibilityFilters).add(new VisibilityFilter(a));
|
|
||||||
}
|
|
||||||
itr = new DataStoreCacheIterator(this, visibilityFilters, subQuery, startOfFirstPeriod, endOfLastPeriod);
|
|
||||||
|
|
||||||
// create RateIterator if necessary
|
// create RateIterator if necessary
|
||||||
if (subQuery.isRate()) {
|
if (subQuery.isRate()) {
|
||||||
@@ -747,8 +703,32 @@ public class DataStoreCache {
|
|||||||
return itr;
|
return itr;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Collection<Authorizations> getSessionAuthorizations(AuthenticatedRequest request) {
|
private Authorizations getSessionAuthorizations(AuthenticatedRequest request) {
|
||||||
return AuthCache.getAuthorizations(request, security);
|
return getSessionAuthorizations(request.getSessionId());
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private QueryResponse convertToQueryResponse(QueryRequest.SubQuery query, Set<Tag> tags,
|
private QueryResponse convertToQueryResponse(QueryRequest.SubQuery query, Set<Tag> tags,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public class DataStoreCacheIterator implements SortedKeyValueIterator<Key, Value
|
|||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(DataStoreCacheIterator.class);
|
private static final Logger LOG = LoggerFactory.getLogger(DataStoreCacheIterator.class);
|
||||||
private DataStoreCache store;
|
private DataStoreCache store;
|
||||||
private Collection<VisibilityFilter> visibilityFilters;
|
private VisibilityFilter visibilityFilter;
|
||||||
private QueryRequest.SubQuery query;
|
private QueryRequest.SubQuery query;
|
||||||
private long startTs;
|
private long startTs;
|
||||||
private long endTs;
|
private long endTs;
|
||||||
@@ -38,11 +38,11 @@ public class DataStoreCacheIterator implements SortedKeyValueIterator<Key, Value
|
|||||||
private Queue<KeyValue> kvQueue = new LinkedList<>();
|
private Queue<KeyValue> kvQueue = new LinkedList<>();
|
||||||
private Set<TaggedMetric> matchingTaggedMetrics = new HashSet<>();
|
private Set<TaggedMetric> matchingTaggedMetrics = new HashSet<>();
|
||||||
|
|
||||||
public DataStoreCacheIterator(DataStoreCache store, Collection<VisibilityFilter> visibilityFilters,
|
public DataStoreCacheIterator(DataStoreCache store, VisibilityFilter visibilityFilter, QueryRequest.SubQuery query,
|
||||||
QueryRequest.SubQuery query, long startTs, long endTs) {
|
long startTs, long endTs) {
|
||||||
|
|
||||||
this.store = store;
|
this.store = store;
|
||||||
this.visibilityFilters = visibilityFilters;
|
this.visibilityFilter = visibilityFilter;
|
||||||
this.query = query;
|
this.query = query;
|
||||||
this.startTs = startTs;
|
this.startTs = startTs;
|
||||||
this.endTs = endTs;
|
this.endTs = endTs;
|
||||||
@@ -72,22 +72,11 @@ public class DataStoreCacheIterator implements SortedKeyValueIterator<Key, Value
|
|||||||
if (matchingTaggedMetrics.contains(currentTaggedMetric)) {
|
if (matchingTaggedMetrics.contains(currentTaggedMetric)) {
|
||||||
addCurrent = true;
|
addCurrent = true;
|
||||||
} else {
|
} else {
|
||||||
if (currentTaggedMetric.matches(requestedTags)) {
|
if (currentTaggedMetric.matches(requestedTags) && entry.getKey().isVisible(visibilityFilter)) {
|
||||||
boolean isVisible = false;
|
|
||||||
for (VisibilityFilter v : visibilityFilters) {
|
|
||||||
if (entry.getKey().isVisible(v)) {
|
|
||||||
isVisible = true;
|
|
||||||
} else {
|
|
||||||
isVisible = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isVisible) {
|
|
||||||
addCurrent = true;
|
addCurrent = true;
|
||||||
matchingTaggedMetrics.add(currentTaggedMetric);
|
matchingTaggedMetrics.add(currentTaggedMetric);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (addCurrent) {
|
if (addCurrent) {
|
||||||
List<WrappedGorillaDecompressor> listDecompressors = entry.getValue().getDecompressors(startTs, endTs);
|
List<WrappedGorillaDecompressor> listDecompressors = entry.getValue().getDecompressors(startTs, endTs);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package timely.subscription;
|
|||||||
import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly;
|
import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly;
|
||||||
|
|
||||||
import java.lang.Thread.UncaughtExceptionHandler;
|
import java.lang.Thread.UncaughtExceptionHandler;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -23,18 +22,15 @@ import org.apache.accumulo.core.data.Key;
|
|||||||
import org.apache.accumulo.core.data.Range;
|
import org.apache.accumulo.core.data.Range;
|
||||||
import org.apache.accumulo.core.data.Value;
|
import org.apache.accumulo.core.data.Value;
|
||||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import timely.adapter.accumulo.MetricAdapter;
|
import timely.adapter.accumulo.MetricAdapter;
|
||||||
import timely.api.request.AuthenticatedRequest;
|
|
||||||
import timely.api.response.MetricResponse;
|
import timely.api.response.MetricResponse;
|
||||||
import timely.api.response.MetricResponses;
|
import timely.api.response.MetricResponses;
|
||||||
import timely.api.response.TimelyException;
|
import timely.api.response.TimelyException;
|
||||||
import timely.model.Metric;
|
import timely.model.Metric;
|
||||||
import timely.model.Tag;
|
import timely.model.Tag;
|
||||||
import timely.store.DataStore;
|
import timely.store.DataStore;
|
||||||
import timely.store.cache.DataStoreCache;
|
|
||||||
import timely.util.JsonUtil;
|
import timely.util.JsonUtil;
|
||||||
|
|
||||||
public class MetricScanner extends Thread implements UncaughtExceptionHandler {
|
public class MetricScanner extends Thread implements UncaughtExceptionHandler {
|
||||||
@@ -45,147 +41,81 @@ public class MetricScanner extends Thread implements UncaughtExceptionHandler {
|
|||||||
private final Scanner scanner;
|
private final Scanner scanner;
|
||||||
private Iterator<Entry<Key, Value>> iter = null;
|
private Iterator<Entry<Key, Value>> iter = null;
|
||||||
private final ChannelHandlerContext ctx;
|
private final ChannelHandlerContext ctx;
|
||||||
private final AuthenticatedRequest request;
|
|
||||||
private volatile boolean closed = false;
|
private volatile boolean closed = false;
|
||||||
private final long delay;
|
private final long delay;
|
||||||
private String scannerInfo;
|
private final String name;
|
||||||
private final int lag;
|
private final int lag;
|
||||||
private final String subscriptionId;
|
private final String subscriptionId;
|
||||||
private final String metric;
|
private final String metric;
|
||||||
private long beginTime;
|
private long startTime;
|
||||||
private long endTime;
|
private long endTime;
|
||||||
private final Subscription subscription;
|
private final Subscription subscription;
|
||||||
private final int subscriptionBatchSize;
|
|
||||||
private MetricResponses responses = new MetricResponses();
|
private MetricResponses responses = new MetricResponses();
|
||||||
private ScheduledFuture<?> flusher = null;
|
private final ScheduledFuture<?> flusher;
|
||||||
|
private final int subscriptionBatchSize;
|
||||||
private List<Range> ranges = null;
|
private List<Range> ranges = null;
|
||||||
private Iterator<Range> rangeItr = null;
|
private Iterator<Range> rangeItr = null;
|
||||||
private DataStore store = null;
|
private DataStore store = null;
|
||||||
private DataStoreCache cache = null;
|
|
||||||
private Set<Tag> colFamValues = null;
|
private Set<Tag> colFamValues = null;
|
||||||
private boolean completedResponseSent = false;
|
private boolean completedResponseSent = false;
|
||||||
private boolean done = false;
|
|
||||||
|
|
||||||
public MetricScanner(Subscription sub, String subscriptionId, String sessionId, DataStore store,
|
public MetricScanner(Subscription sub, String subscriptionId, String sessionId, DataStore store, String metric,
|
||||||
DataStoreCache cache, String metric, Map<String, String> tags, long beginTime, long endTime, long delay,
|
Map<String, String> tags, long startTime, long endTime, long delay, int lag, ChannelHandlerContext ctx,
|
||||||
int lag, AuthenticatedRequest request, ChannelHandlerContext ctx, int scannerBatchSize,
|
int scannerBatchSize, int flushIntervalSeconds, int scannerReadAhead, int subscriptionBatchSize)
|
||||||
int flushIntervalSeconds, int scannerReadAhead, int subscriptionBatchSize) throws TimelyException {
|
throws TimelyException {
|
||||||
this.setDaemon(true);
|
this.setDaemon(true);
|
||||||
this.setUncaughtExceptionHandler(this);
|
this.setUncaughtExceptionHandler(this);
|
||||||
this.store = store;
|
this.store = store;
|
||||||
this.cache = cache;
|
|
||||||
this.subscription = sub;
|
this.subscription = sub;
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.request = request;
|
|
||||||
this.lag = lag;
|
this.lag = lag;
|
||||||
this.metric = metric;
|
this.metric = metric;
|
||||||
this.beginTime = beginTime;
|
this.startTime = startTime;
|
||||||
this.endTime = endTime;
|
this.endTime = endTime;
|
||||||
this.delay = delay;
|
this.scanner = this.store.createScannerForMetric(sessionId, metric, tags, startTime, endTime, lag,
|
||||||
this.subscriptionId = subscriptionId;
|
scannerBatchSize, scannerReadAhead);
|
||||||
this.subscriptionBatchSize = subscriptionBatchSize;
|
|
||||||
|
|
||||||
ToStringBuilder buf = new ToStringBuilder(this, ToStringStyle.JSON_STYLE);
|
if (0 == startTime) {
|
||||||
buf.append("sessionId", sessionId);
|
this.startTime = (System.currentTimeMillis() - this.store.getAgeOffForMetric(metric) - 1000);
|
||||||
buf.append("subscriptionId", subscriptionId);
|
LOG.debug("Overriding zero start time to {} due to age off configuration", startTime);
|
||||||
buf.append("metric", metric);
|
|
||||||
buf.append("startTime", beginTime);
|
|
||||||
buf.append("endTime", endTime);
|
|
||||||
buf.append("delayTime", delay);
|
|
||||||
if (null != tags) {
|
|
||||||
buf.append("tags", tags.toString());
|
|
||||||
}
|
|
||||||
scannerInfo = buf.toString();
|
|
||||||
LOG.info("[{}] Setting up MetricScanner: {}", subscriptionId, scannerInfo);
|
|
||||||
|
|
||||||
if (0 == beginTime) {
|
|
||||||
this.beginTime = (System.currentTimeMillis() - this.store.getAgeOffForMetric(metric) - 1000);
|
|
||||||
LOG.debug("[{}] Overriding zero start time to {} due to age off configuration", subscriptionId, beginTime);
|
|
||||||
}
|
}
|
||||||
long endTimeStamp = (endTime == 0) ? (System.currentTimeMillis() - (lag * 1000)) : endTime;
|
long endTimeStamp = (endTime == 0) ? (System.currentTimeMillis() - (lag * 1000)) : endTime;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.colFamValues = this.store.getColumnFamilies(metric, tags);
|
this.colFamValues = this.store.getColumnFamilies(metric, tags);
|
||||||
this.ranges = new ArrayList<>();
|
ranges = this.store.getQueryRanges(metric, this.startTime, endTimeStamp, colFamValues);
|
||||||
if (this.cache == null) {
|
|
||||||
LOG.debug("[{}] Cache not enabled, adding complete range", subscriptionId);
|
|
||||||
ranges.addAll(this.store.getQueryRanges(metric, this.beginTime, endTimeStamp, colFamValues));
|
|
||||||
} else {
|
|
||||||
long oldestTsForMetric = this.cache.getOldestTimestamp(metric);
|
|
||||||
// metric is cached on this server and requested range ends somewhere in the
|
|
||||||
// cache
|
|
||||||
if (oldestTsForMetric < Long.MAX_VALUE && endTimeStamp > oldestTsForMetric) {
|
|
||||||
List<MetricResponse> metricsFromCache;
|
|
||||||
if (this.beginTime < oldestTsForMetric) {
|
|
||||||
// add range this.beginTime to oldestTsForMetric - 1 because this range is not
|
|
||||||
// cached
|
|
||||||
ranges.addAll(
|
|
||||||
this.store.getQueryRanges(metric, this.beginTime, oldestTsForMetric - 1, colFamValues));
|
|
||||||
metricsFromCache = cache.getMetricsFromCache(this.request, metric, tags, oldestTsForMetric,
|
|
||||||
endTimeStamp);
|
|
||||||
LOG.debug("[{}] MetricScanner request partially fulfilled from cache: {} metrics",
|
|
||||||
subscriptionId, metricsFromCache.size());
|
|
||||||
} else {
|
|
||||||
metricsFromCache = cache.getMetricsFromCache(this.request, metric, tags, this.beginTime,
|
|
||||||
endTimeStamp);
|
|
||||||
LOG.debug("[{}] Websocket request completely fulfilled from cache: {} metrics [{}]",
|
|
||||||
subscriptionId, metricsFromCache.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (metricsFromCache.isEmpty()) {
|
|
||||||
// no metrics from cache, clear ranges and add whole range
|
|
||||||
ranges.clear();
|
|
||||||
ranges.addAll(this.store.getQueryRanges(metric, this.beginTime, endTimeStamp, colFamValues));
|
|
||||||
LOG.debug("[{}] MetricScanner request got no metrics from cache, re-adding complete range",
|
|
||||||
subscriptionId);
|
|
||||||
} else {
|
|
||||||
for (MetricResponse r : metricsFromCache) {
|
|
||||||
responses.addResponse(r);
|
|
||||||
}
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
LOG.debug("[{}] MetricScanner request range not cached, adding complete range", subscriptionId);
|
|
||||||
ranges.addAll(this.store.getQueryRanges(metric, this.beginTime, endTimeStamp, colFamValues));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ranges.isEmpty() && endTime == 0) {
|
|
||||||
// request fulfilled from cache up until current, but user asked for continuous
|
|
||||||
// data
|
|
||||||
// must add a small range otherwise we will be scanning all data regardless of
|
|
||||||
// metric, tag
|
|
||||||
ranges.addAll(this.store.getQueryRanges(metric, endTimeStamp, endTimeStamp + 1, colFamValues));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ranges.isEmpty()) {
|
|
||||||
this.scanner = null;
|
|
||||||
this.done = true;
|
|
||||||
} else {
|
|
||||||
this.scanner = this.store.createScannerForMetric(this.request, metric, tags, scannerBatchSize,
|
|
||||||
scannerReadAhead);
|
|
||||||
rangeItr = ranges.iterator();
|
rangeItr = ranges.iterator();
|
||||||
if (rangeItr.hasNext()) {
|
if (rangeItr.hasNext()) {
|
||||||
Range r = rangeItr.next();
|
Range r = rangeItr.next();
|
||||||
this.scanner.setRange(r);
|
this.scanner.setRange(r);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (TableNotFoundException e) {
|
} catch (TableNotFoundException e) {
|
||||||
throw new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(),
|
throw new TimelyException(HttpResponseStatus.INTERNAL_SERVER_ERROR.code(), "Error in MetricScanner",
|
||||||
"[" + subscriptionId + "] Error in MetricScanner", e.getMessage(), e);
|
e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!done) {
|
|
||||||
this.iter = scanner.iterator();
|
this.iter = scanner.iterator();
|
||||||
this.setName("Scan:" + subscriptionId);
|
this.delay = delay;
|
||||||
|
this.subscriptionId = subscriptionId;
|
||||||
|
this.subscriptionBatchSize = subscriptionBatchSize;
|
||||||
|
ToStringBuilder buf = new ToStringBuilder(this);
|
||||||
|
buf.append("sessionId", sessionId);
|
||||||
|
buf.append("metric", metric);
|
||||||
|
buf.append("startTime", startTime);
|
||||||
|
buf.append("endTime", endTime);
|
||||||
|
buf.append("delayTime", delay);
|
||||||
|
if (null != tags) {
|
||||||
|
buf.append("tags", tags.toString());
|
||||||
|
}
|
||||||
|
name = buf.toString();
|
||||||
|
this.setName("Metric Scanner " + name);
|
||||||
this.flusher = this.ctx.executor().scheduleAtFixedRate(() -> {
|
this.flusher = this.ctx.executor().scheduleAtFixedRate(() -> {
|
||||||
flush();
|
flush();
|
||||||
}, flushIntervalSeconds, flushIntervalSeconds, TimeUnit.SECONDS);
|
}, flushIntervalSeconds, flushIntervalSeconds, TimeUnit.SECONDS);
|
||||||
}
|
LOG.trace("Created MetricScanner: {}", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void flush() {
|
public synchronized void flush() {
|
||||||
LOG.debug("[{}] Flush called", subscriptionId);
|
LOG.info("Flush called");
|
||||||
synchronized (responses) {
|
synchronized (responses) {
|
||||||
if (responses.size() > 0) {
|
if (responses.size() > 0) {
|
||||||
try {
|
try {
|
||||||
@@ -193,7 +123,7 @@ public class MetricScanner extends Thread implements UncaughtExceptionHandler {
|
|||||||
this.ctx.writeAndFlush(new TextWebSocketFrame(json));
|
this.ctx.writeAndFlush(new TextWebSocketFrame(json));
|
||||||
responses.clear();
|
responses.clear();
|
||||||
} catch (JsonProcessingException e) {
|
} catch (JsonProcessingException e) {
|
||||||
LOG.error("[" + subscriptionId + "] Error serializing metrics: " + responses, e);
|
LOG.error("Error serializing metrics: " + responses, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,6 +133,7 @@ public class MetricScanner extends Thread implements UncaughtExceptionHandler {
|
|||||||
public void run() {
|
public void run() {
|
||||||
Metric m = null;
|
Metric m = null;
|
||||||
try {
|
try {
|
||||||
|
boolean done = false;
|
||||||
while (!done && !closed) {
|
while (!done && !closed) {
|
||||||
|
|
||||||
if (this.iter.hasNext()) {
|
if (this.iter.hasNext()) {
|
||||||
@@ -214,8 +145,7 @@ public class MetricScanner extends Thread implements UncaughtExceptionHandler {
|
|||||||
}
|
}
|
||||||
this.responses.addResponse(MetricResponse.fromMetric(m, this.subscriptionId));
|
this.responses.addResponse(MetricResponse.fromMetric(m, this.subscriptionId));
|
||||||
} catch (Exception e1) {
|
} catch (Exception e1) {
|
||||||
LOG.error("[{}] Error {} parsing metric at key: {}", subscriptionId, e1.getMessage(),
|
LOG.error("Error {} parsing metric at key: {}", e1.getMessage(), e.getKey().toString());
|
||||||
e.getKey().toString());
|
|
||||||
}
|
}
|
||||||
} else if (rangeItr.hasNext()) {
|
} else if (rangeItr.hasNext()) {
|
||||||
// set next range on the scanner
|
// set next range on the scanner
|
||||||
@@ -228,10 +158,9 @@ public class MetricScanner extends Thread implements UncaughtExceptionHandler {
|
|||||||
long endTimeStamp = (System.currentTimeMillis() - (lag * 1000));
|
long endTimeStamp = (System.currentTimeMillis() - (lag * 1000));
|
||||||
this.scanner.close();
|
this.scanner.close();
|
||||||
if (null == m) {
|
if (null == m) {
|
||||||
LOG.debug("[{}] No results found, waiting {}ms to retry with new end time {}. [{}]",
|
LOG.debug("No results found, waiting {}ms to retry with new end time {}.", delay, endTimeStamp);
|
||||||
subscriptionId, delay, endTimeStamp);
|
|
||||||
sleepUninterruptibly(delay, TimeUnit.MILLISECONDS);
|
sleepUninterruptibly(delay, TimeUnit.MILLISECONDS);
|
||||||
ranges = this.store.getQueryRanges(metric, beginTime, endTimeStamp, colFamValues);
|
ranges = this.store.getQueryRanges(metric, startTime, endTimeStamp, colFamValues);
|
||||||
rangeItr = ranges.iterator();
|
rangeItr = ranges.iterator();
|
||||||
if (rangeItr.hasNext()) {
|
if (rangeItr.hasNext()) {
|
||||||
Range r = rangeItr.next();
|
Range r = rangeItr.next();
|
||||||
@@ -240,9 +169,8 @@ public class MetricScanner extends Thread implements UncaughtExceptionHandler {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Reset the starting range to the last key returned
|
// Reset the starting range to the last key returned
|
||||||
LOG.debug("[{}] Exhausted scanner, last metric returned was {} [{}]", subscriptionId, m);
|
LOG.debug("Exhausted scanner, last metric returned was {}", m);
|
||||||
LOG.debug("[{}] Waiting {}ms to retry with new end time {}. [{}]", subscriptionId, delay,
|
LOG.debug("Waiting {}ms to retry with new end time {}.", delay, endTimeStamp);
|
||||||
endTimeStamp);
|
|
||||||
sleepUninterruptibly(delay, TimeUnit.MILLISECONDS);
|
sleepUninterruptibly(delay, TimeUnit.MILLISECONDS);
|
||||||
ranges = this.store.getQueryRanges(metric, m.getValue().getTimestamp() + 1, endTimeStamp,
|
ranges = this.store.getQueryRanges(metric, m.getValue().getTimestamp() + 1, endTimeStamp,
|
||||||
colFamValues);
|
colFamValues);
|
||||||
@@ -259,24 +187,22 @@ public class MetricScanner extends Thread implements UncaughtExceptionHandler {
|
|||||||
done = true;
|
done = true;
|
||||||
}
|
}
|
||||||
if (done) {
|
if (done) {
|
||||||
LOG.debug("[{}] Exhausted scanner", subscriptionId);
|
LOG.debug("Exhausted scanner for subscription {}", this.subscriptionId);
|
||||||
sendCompletedResponse();
|
sendCompletedResponse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
LOG.error("[" + subscriptionId + "] Error in metric scanner, closing.", e);
|
LOG.error("Error in metric scanner, closing.", e);
|
||||||
} finally {
|
} finally {
|
||||||
close();
|
close();
|
||||||
if (this.scanner != null) {
|
|
||||||
this.scanner.close();
|
this.scanner.close();
|
||||||
}
|
|
||||||
subscription.scannerComplete(metric);
|
subscription.scannerComplete(metric);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized private void sendCompletedResponse() {
|
synchronized private void sendCompletedResponse() {
|
||||||
if (!completedResponseSent) {
|
if (!completedResponseSent) {
|
||||||
LOG.info("[{}] Sending completed response", subscriptionId);
|
LOG.debug("Sending completed response for subscription {}", this.subscriptionId);
|
||||||
final MetricResponse completedResponse = new MetricResponse();
|
final MetricResponse completedResponse = new MetricResponse();
|
||||||
completedResponse.setSubscriptionId(this.subscriptionId);
|
completedResponse.setSubscriptionId(this.subscriptionId);
|
||||||
completedResponse.setComplete(true);
|
completedResponse.setComplete(true);
|
||||||
@@ -288,18 +214,16 @@ public class MetricScanner extends Thread implements UncaughtExceptionHandler {
|
|||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
if (!closed) {
|
if (!closed) {
|
||||||
LOG.debug("[{}] Marking metric scanner closed", subscriptionId);
|
LOG.info("Marking metric scanner closed: {}", name);
|
||||||
sendCompletedResponse();
|
sendCompletedResponse();
|
||||||
if (this.flusher != null) {
|
|
||||||
this.flusher.cancel(false);
|
this.flusher.cancel(false);
|
||||||
}
|
|
||||||
this.closed = true;
|
this.closed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void uncaughtException(Thread t, Throwable e) {
|
public void uncaughtException(Thread t, Throwable e) {
|
||||||
LOG.error("[" + subscriptionId + "] Error during metric scanner", e);
|
LOG.error("Error during metric scanner " + name, e);
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,11 +10,9 @@ import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
|
|||||||
import io.netty.util.concurrent.ScheduledFuture;
|
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.request.AuthenticatedRequest;
|
|
||||||
import timely.api.response.TimelyException;
|
import timely.api.response.TimelyException;
|
||||||
import timely.configuration.Configuration;
|
import timely.configuration.Configuration;
|
||||||
import timely.store.DataStore;
|
import timely.store.DataStore;
|
||||||
import timely.store.cache.DataStoreCache;
|
|
||||||
|
|
||||||
public class Subscription {
|
public class Subscription {
|
||||||
|
|
||||||
@@ -23,7 +21,6 @@ public class Subscription {
|
|||||||
|
|
||||||
private final String sessionId;
|
private final String sessionId;
|
||||||
private final DataStore store;
|
private final DataStore store;
|
||||||
private final DataStoreCache cache;
|
|
||||||
private final ChannelHandlerContext ctx;
|
private final ChannelHandlerContext ctx;
|
||||||
private final ScheduledFuture<?> ping;
|
private final ScheduledFuture<?> ping;
|
||||||
private final int lag;
|
private final int lag;
|
||||||
@@ -34,12 +31,11 @@ public class Subscription {
|
|||||||
private final String subscriptionId;
|
private final String subscriptionId;
|
||||||
private LinkedList<String> deadMetricScannerQueue = new LinkedList<>();
|
private LinkedList<String> deadMetricScannerQueue = new LinkedList<>();
|
||||||
|
|
||||||
public Subscription(String subscriptionId, String sessionId, DataStore store, DataStoreCache cache,
|
public Subscription(String subscriptionId, String sessionId, DataStore store, ChannelHandlerContext ctx,
|
||||||
ChannelHandlerContext ctx, Configuration conf) {
|
Configuration conf) {
|
||||||
this.subscriptionId = subscriptionId;
|
this.subscriptionId = subscriptionId;
|
||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
this.store = store;
|
this.store = store;
|
||||||
this.cache = cache;
|
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.lag = conf.getWebsocket().getSubscriptionLag();
|
this.lag = conf.getWebsocket().getSubscriptionLag();
|
||||||
this.scannerBatchSize = conf.getWebsocket().getScannerBatchSize();
|
this.scannerBatchSize = conf.getWebsocket().getScannerBatchSize();
|
||||||
@@ -49,17 +45,17 @@ public class Subscription {
|
|||||||
// send a websocket ping at half the timeout interval.
|
// send a websocket ping at half the timeout interval.
|
||||||
int rate = conf.getWebsocket().getTimeout() / 2;
|
int rate = conf.getWebsocket().getTimeout() / 2;
|
||||||
this.ping = this.ctx.executor().scheduleAtFixedRate(() -> {
|
this.ping = this.ctx.executor().scheduleAtFixedRate(() -> {
|
||||||
LOG.trace("[{}] Sending ping on channel {}", subscriptionId, ctx.channel());
|
LOG.trace("Sending ping on channel {}", ctx.channel());
|
||||||
ctx.writeAndFlush(new PingWebSocketFrame());
|
ctx.writeAndFlush(new PingWebSocketFrame());
|
||||||
cleanupCompletedMetrics();
|
cleanupCompletedMetrics();
|
||||||
}, rate, rate, TimeUnit.SECONDS);
|
}, rate, rate, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addMetric(AuthenticatedRequest request, String metric, Map<String, String> tags, long startTime,
|
public void addMetric(String metric, Map<String, String> tags, long startTime, long endTime, long delay)
|
||||||
long endTime, long delay) throws TimelyException {
|
throws TimelyException {
|
||||||
LOG.debug("[{}] Adding metric scanner", this.subscriptionId);
|
LOG.debug("Adding metric scanner for subscription {}", this.subscriptionId);
|
||||||
MetricScanner m = new MetricScanner(this, this.subscriptionId, this.sessionId, store, cache, metric, tags,
|
MetricScanner m = new MetricScanner(this, this.subscriptionId, this.sessionId, store, metric, tags, startTime,
|
||||||
startTime, endTime, delay, lag, request, ctx, scannerBatchSize, flushIntervalSeconds, scannerReadAhead,
|
endTime, delay, lag, ctx, scannerBatchSize, flushIntervalSeconds, scannerReadAhead,
|
||||||
subscriptionBatchSize);
|
subscriptionBatchSize);
|
||||||
METRICS.put(metric, m);
|
METRICS.put(metric, m);
|
||||||
m.start();
|
m.start();
|
||||||
@@ -84,20 +80,20 @@ public class Subscription {
|
|||||||
try {
|
try {
|
||||||
m.join();
|
m.join();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOG.error("[" + this.subscriptionId + "] Error closing MetricScanner", e);
|
LOG.error("Error closing MetricScanner", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
LOG.info("[{}] Closing subscriptions", this.subscriptionId);
|
LOG.info("Closing subscriptions for {}", this.subscriptionId);
|
||||||
this.ping.cancel(false);
|
this.ping.cancel(false);
|
||||||
METRICS.forEach((k, v) -> {
|
METRICS.forEach((k, v) -> {
|
||||||
v.close();
|
v.close();
|
||||||
try {
|
try {
|
||||||
v.join();
|
v.join();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
LOG.error("[" + this.subscriptionId + "] Error closing MetricScanner", e);
|
LOG.error("Error closing MetricScanner", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
METRICS.clear();
|
METRICS.clear();
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.jsontype.NamedType;
|
import com.fasterxml.jackson.databind.jsontype.NamedType;
|
||||||
import com.fasterxml.jackson.databind.jsontype.SubtypeResolver;
|
import com.fasterxml.jackson.databind.jsontype.SubtypeResolver;
|
||||||
import com.fasterxml.jackson.datatype.guava.GuavaModule;
|
|
||||||
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
|
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -19,11 +18,18 @@ public class JsonUtil {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
mapper.registerModule(new Jdk8Module());
|
mapper.registerModule(new Jdk8Module());
|
||||||
mapper.registerModule(new GuavaModule());
|
|
||||||
mapper.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true);
|
mapper.configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true);
|
||||||
mapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
|
mapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
|
||||||
// mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
|
// mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
|
||||||
mapper.configure(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS, true);
|
mapper.configure(JsonParser.Feature.ALLOW_NON_NUMERIC_NUMBERS, true);
|
||||||
|
// Register the subtypes dynamically based on the WebSocket annotation
|
||||||
|
// instead of having
|
||||||
|
// to register them statically on this class using:
|
||||||
|
// @JsonSubTypes({ @Type(name = "create", value =
|
||||||
|
// CreateSubscription.class),
|
||||||
|
// @Type(name = "add", value = AddSubscription.class),
|
||||||
|
// @Type(name = "remove", value = RemoveSubscription.class),
|
||||||
|
// @Type(name = "close", value = CloseSubscription.class) })
|
||||||
SubtypeResolver resolver = mapper.getSubtypeResolver();
|
SubtypeResolver resolver = mapper.getSubtypeResolver();
|
||||||
AnnotationResolver.getWebSocketClasses().forEach(c -> {
|
AnnotationResolver.getWebSocketClasses().forEach(c -> {
|
||||||
String name = c.getAnnotation(WebSocket.class).operation();
|
String name = c.getAnnotation(WebSocket.class).operation();
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
npe.ou.entries=iamnotaperson,npe,stillnotaperson
|
|
||||||
subject.dn.pattern=(?:^|,)\\s*OU\\s*=\\s*My Department\\s*(?:,|$)
|
|
||||||
@@ -2,17 +2,16 @@ package timely.auth;
|
|||||||
|
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.apache.accumulo.core.security.Authorizations;
|
import org.apache.accumulo.core.security.Authorizations;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.AfterClass;
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
import timely.configuration.Configuration;
|
import timely.configuration.Configuration;
|
||||||
import timely.test.TestConfiguration;
|
import timely.test.TestConfiguration;
|
||||||
|
|
||||||
@@ -28,16 +27,12 @@ public class AuthCacheTest {
|
|||||||
cookie = URLEncoder.encode(UUID.randomUUID().toString(), StandardCharsets.UTF_8.name());
|
cookie = URLEncoder.encode(UUID.randomUUID().toString(), StandardCharsets.UTF_8.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
|
||||||
public static void after() {
|
|
||||||
AuthCache.resetSessionMaxAge();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() throws Exception {
|
public void setup() throws Exception {
|
||||||
AuthCache.setSessionMaxAge(config.getSecurity());
|
AuthCache.setSessionMaxAge(config);
|
||||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("test", "test1");
|
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("test", "test1");
|
||||||
AuthenticationService.authenticate(token, cookie);
|
Authentication auth = AuthenticationService.getAuthenticationManager().authenticate(token);
|
||||||
|
AuthCache.getCache().put(cookie, auth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -52,9 +47,7 @@ public class AuthCacheTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetAuths() throws Exception {
|
public void testGetAuths() throws Exception {
|
||||||
Collection<Authorizations> auths = AuthCache.getAuthorizations(cookie);
|
Authorizations a = AuthCache.getAuthorizations(cookie);
|
||||||
Assert.assertEquals(1, auths.size());
|
|
||||||
Authorizations a = auths.iterator().next();
|
|
||||||
Assert.assertEquals("A,B,C", a.toString());
|
Assert.assertEquals("A,B,C", a.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,76 +2,55 @@ package timely.auth;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import org.junit.AfterClass;
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.security.authentication.BadCredentialsException;
|
import org.springframework.security.authentication.BadCredentialsException;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
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.configuration.Configuration;
|
|
||||||
import timely.test.TestConfiguration;
|
|
||||||
|
|
||||||
public class AuthenticationServiceTest {
|
public class AuthenticationServiceTest {
|
||||||
|
|
||||||
private static Configuration config;
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void before() {
|
|
||||||
config = TestConfiguration.createMinimalConfigurationForTest();
|
|
||||||
config.getSecurity().setSessionMaxAge(Integer.MAX_VALUE);
|
|
||||||
AuthCache.resetSessionMaxAge();
|
|
||||||
AuthCache.setSessionMaxAge(config.getSecurity());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
|
|
||||||
AuthCache.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterClass
|
|
||||||
public static void after() {
|
|
||||||
AuthCache.resetSessionMaxAge();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = BadCredentialsException.class)
|
@Test(expected = BadCredentialsException.class)
|
||||||
public void testBasicAuthenticationFailure() {
|
public void testBasicAuthenticationFailure() {
|
||||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("test", "test2");
|
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("test", "test2");
|
||||||
AuthenticationService.authenticate(token, "test");
|
AuthenticationService.getAuthenticationManager().authenticate(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBasicAuthenticationLogin() {
|
public void testBasicAuthenticationLogin() {
|
||||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("test", "test1");
|
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("test", "test1");
|
||||||
TimelyPrincipal principal = AuthenticationService.authenticate(token, "test");
|
Authentication auth = AuthenticationService.getAuthenticationManager().authenticate(token);
|
||||||
Collection<? extends Collection<String>> authorizations = principal.getAuthorizations();
|
Collection<? extends GrantedAuthority> authorizations = auth.getAuthorities();
|
||||||
authorizations.forEach(a -> {
|
authorizations.forEach(a -> {
|
||||||
Assert.assertTrue(a.contains("A") || a.contains("B") || a.contains("C"));
|
Assert.assertTrue(
|
||||||
|
a.getAuthority().equals("A") || a.getAuthority().equals("B") || a.getAuthority().equals("C"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testX509AuthenticationLogin() {
|
public void testX509AuthenticationLogin() {
|
||||||
PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken("CN=example.com",
|
PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken("example.com",
|
||||||
"doesn't matter what I put here");
|
"doesn't matter what I put here");
|
||||||
TimelyPrincipal principal = AuthenticationService.authenticate(token, "test");
|
Authentication auth = AuthenticationService.getAuthenticationManager().authenticate(token);
|
||||||
Collection<? extends Collection<String>> authorizations = principal.getAuthorizations();
|
Collection<? extends GrantedAuthority> authorizations = auth.getAuthorities();
|
||||||
authorizations.forEach(a -> {
|
authorizations.forEach(a -> {
|
||||||
Assert.assertTrue(a.contains("D") || a.contains("E") || a.contains("F"));
|
Assert.assertTrue(
|
||||||
|
a.getAuthority().equals("D") || a.getAuthority().equals("E") || a.getAuthority().equals("F"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = UsernameNotFoundException.class)
|
@Test(expected = UsernameNotFoundException.class)
|
||||||
public void testX509AuthenticationLoginFailed() {
|
public void testX509AuthenticationLoginFailed() {
|
||||||
PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken("CN=bad.example.com",
|
PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken("bad.example.com",
|
||||||
"doesn't matter what I put here");
|
"doesn't matter what I put here");
|
||||||
TimelyPrincipal principal = AuthenticationService.authenticate(token, "test");
|
Authentication auth = AuthenticationService.getAuthenticationManager().authenticate(token);
|
||||||
Collection<? extends Collection<String>> authorizations = principal.getAuthorizations();
|
Collection<? extends GrantedAuthority> authorizations = auth.getAuthorities();
|
||||||
authorizations.forEach(a -> {
|
authorizations.forEach(a -> {
|
||||||
Assert.assertTrue(a.contains("D") || a.contains("E") || a.contains("F"));
|
Assert.assertTrue(
|
||||||
|
a.getAuthority().equals("D") || a.getAuthority().equals("E") || a.getAuthority().equals("F"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
package timely.auth;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.ServiceConfigurationError;
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.springframework.beans.BeansException;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
|
||||||
|
|
||||||
public class UserDetailsServiceTest {
|
|
||||||
|
|
||||||
private static ApplicationContext springContext = null;
|
|
||||||
private static UserDetailsService userDetailsService = null;
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void setupClass() {
|
|
||||||
try {
|
|
||||||
springContext = new ClassPathXmlApplicationContext("security.xml");
|
|
||||||
userDetailsService = (UserDetailsService) springContext.getBean("timelyUserDetailsService");
|
|
||||||
} catch (BeansException e) {
|
|
||||||
throw new ServiceConfigurationError("Error initializing from spring: " + e.getMessage(), e.getRootCause());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAuths() {
|
|
||||||
TimelyUser u = userDetailsService.getUsers().get("CN=example.com");
|
|
||||||
Assert.assertNotNull("TimelyUser not found", u);
|
|
||||||
Assert.assertEquals("3 auths expected", 3, u.getAuths().size());
|
|
||||||
Assert.assertTrue("Unexpected auths found " + u.getAuths(),
|
|
||||||
u.getAuths().containsAll(Arrays.asList("D", "E", "F")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRoles() {
|
|
||||||
TimelyUser u = userDetailsService.getUsers().get("CN=example.com");
|
|
||||||
Assert.assertNotNull("TimelyUser not found", u);
|
|
||||||
Assert.assertEquals("3 roles expected", 3, u.getRoles().size());
|
|
||||||
Assert.assertTrue("Unexpected roles found " + u.getRoles(),
|
|
||||||
u.getRoles().containsAll(Arrays.asList("G", "H", "I")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -31,9 +31,7 @@ import timely.api.request.timeseries.QueryRequest.RateOption;
|
|||||||
import timely.api.request.timeseries.QueryRequest.SubQuery;
|
import timely.api.request.timeseries.QueryRequest.SubQuery;
|
||||||
import timely.api.request.timeseries.SearchLookupRequest;
|
import timely.api.request.timeseries.SearchLookupRequest;
|
||||||
import timely.api.request.timeseries.SuggestRequest;
|
import timely.api.request.timeseries.SuggestRequest;
|
||||||
import timely.api.response.TimelyException;
|
|
||||||
import timely.auth.AuthCache;
|
import timely.auth.AuthCache;
|
||||||
import timely.auth.AuthenticationService;
|
|
||||||
import timely.configuration.Configuration;
|
import timely.configuration.Configuration;
|
||||||
import timely.model.Metric;
|
import timely.model.Metric;
|
||||||
import timely.model.Tag;
|
import timely.model.Tag;
|
||||||
@@ -68,11 +66,10 @@ public class HttpRequestDecoderTest {
|
|||||||
public static void before() throws Exception {
|
public static void before() throws Exception {
|
||||||
config = TestConfiguration.createMinimalConfigurationForTest();
|
config = TestConfiguration.createMinimalConfigurationForTest();
|
||||||
anonConfig = TestConfiguration.createMinimalConfigurationForTest();
|
anonConfig = TestConfiguration.createMinimalConfigurationForTest();
|
||||||
anonConfig.getSecurity().setAllowAnonymousHttpAccess(true);
|
anonConfig.getSecurity().setAllowAnonymousAccess(true);
|
||||||
cookie = URLEncoder.encode(UUID.randomUUID().toString(), StandardCharsets.UTF_8.name());
|
cookie = URLEncoder.encode(UUID.randomUUID().toString(), StandardCharsets.UTF_8.name());
|
||||||
AuthCache.setSessionMaxAge(config.getSecurity());
|
AuthCache.setSessionMaxAge(config);
|
||||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("test", "test1");
|
AuthCache.getCache().put(cookie, new UsernamePasswordAuthenticationToken("test", "test1"));
|
||||||
AuthenticationService.authenticate(token, cookie);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
@@ -99,7 +96,7 @@ public class HttpRequestDecoderTest {
|
|||||||
Assert.assertEquals(request, results.get(0));
|
Assert.assertEquals(request, results.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = TimelyException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testAggregatorsURINoSession() throws Exception {
|
public void testAggregatorsURINoSession() throws Exception {
|
||||||
decoder = new TestHttpQueryDecoder(config);
|
decoder = new TestHttpQueryDecoder(config);
|
||||||
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
|
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
|
||||||
@@ -130,7 +127,7 @@ public class HttpRequestDecoderTest {
|
|||||||
Assert.assertEquals(AggregatorsRequest.class, results.iterator().next().getClass());
|
Assert.assertEquals(AggregatorsRequest.class, results.iterator().next().getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = TimelyException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testAggregatorsPostNoSession() throws Exception {
|
public void testAggregatorsPostNoSession() throws Exception {
|
||||||
decoder = new TestHttpQueryDecoder(config);
|
decoder = new TestHttpQueryDecoder(config);
|
||||||
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST,
|
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST,
|
||||||
@@ -378,7 +375,7 @@ public class HttpRequestDecoderTest {
|
|||||||
suggest.validate();
|
suggest.validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = TimelyException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testSuggestURIWithValidTypeNoSession() throws Exception {
|
public void testSuggestURIWithValidTypeNoSession() throws Exception {
|
||||||
decoder = new TestHttpQueryDecoder(config);
|
decoder = new TestHttpQueryDecoder(config);
|
||||||
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
|
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
|
||||||
@@ -513,7 +510,7 @@ public class HttpRequestDecoderTest {
|
|||||||
suggest.validate();
|
suggest.validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = TimelyException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testQueryWithNoSession() throws Exception {
|
public void testQueryWithNoSession() throws Exception {
|
||||||
decoder = new TestHttpQueryDecoder(config);
|
decoder = new TestHttpQueryDecoder(config);
|
||||||
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
|
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public class BasicAuthLoginRequestHandlerTest {
|
|||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void before() throws Exception {
|
public static void before() throws Exception {
|
||||||
AuthCache.setSessionMaxAge(TestConfiguration.createMinimalConfigurationForTest().getSecurity());
|
AuthCache.setSessionMaxAge(TestConfiguration.createMinimalConfigurationForTest());
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
@@ -57,7 +57,6 @@ public class BasicAuthLoginRequestHandlerTest {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() throws Exception {
|
public void setup() throws Exception {
|
||||||
AuthCache.clear();
|
|
||||||
results.clear();
|
results.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,6 @@ 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;
|
||||||
import timely.auth.AuthCache;
|
import timely.auth.AuthCache;
|
||||||
import timely.auth.TimelyPrincipal;
|
|
||||||
import timely.auth.TimelyUser;
|
|
||||||
import timely.configuration.Configuration;
|
import timely.configuration.Configuration;
|
||||||
import timely.model.Metric;
|
import timely.model.Metric;
|
||||||
import timely.subscription.SubscriptionRegistry;
|
import timely.subscription.SubscriptionRegistry;
|
||||||
@@ -62,11 +60,10 @@ public class WebSocketRequestDecoderTest {
|
|||||||
public static void before() throws Exception {
|
public static void before() throws Exception {
|
||||||
config = TestConfiguration.createMinimalConfigurationForTest();
|
config = TestConfiguration.createMinimalConfigurationForTest();
|
||||||
anonConfig = TestConfiguration.createMinimalConfigurationForTest();
|
anonConfig = TestConfiguration.createMinimalConfigurationForTest();
|
||||||
anonConfig.getSecurity().setAllowAnonymousWsAccess(true);
|
anonConfig.getSecurity().setAllowAnonymousAccess(true);
|
||||||
cookie = URLEncoder.encode(UUID.randomUUID().toString(), StandardCharsets.UTF_8.name());
|
cookie = URLEncoder.encode(UUID.randomUUID().toString(), StandardCharsets.UTF_8.name());
|
||||||
AuthCache.setSessionMaxAge(config.getSecurity());
|
AuthCache.setSessionMaxAge(config);
|
||||||
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("test", "test1");
|
AuthCache.getCache().put(cookie, new UsernamePasswordAuthenticationToken("test", "test1"));
|
||||||
AuthCache.put(cookie, new TimelyPrincipal(TimelyUser.ANONYMOUS_USER));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class TestDataStoreCacheIterator {
|
|||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void setup() {
|
public static void setup() {
|
||||||
configuration = new Configuration();
|
configuration = new Configuration();
|
||||||
configuration.getSecurity().setAllowAnonymousHttpAccess(true);
|
configuration.getSecurity().setAllowAnonymousAccess(true);
|
||||||
HashMap<String, Integer> ageoffs = new HashMap<>();
|
HashMap<String, Integer> ageoffs = new HashMap<>();
|
||||||
ageoffs.put(DataStoreCache.DEFAULT_AGEOFF_KEY, Integer.MAX_VALUE);
|
ageoffs.put(DataStoreCache.DEFAULT_AGEOFF_KEY, Integer.MAX_VALUE);
|
||||||
configuration.getCache().setMetricAgeOffHours(ageoffs);
|
configuration.getCache().setMetricAgeOffHours(ageoffs);
|
||||||
@@ -120,8 +120,7 @@ public class TestDataStoreCacheIterator {
|
|||||||
long firstTimestamp = -1;
|
long firstTimestamp = -1;
|
||||||
long lastTimestamp = -1;
|
long lastTimestamp = -1;
|
||||||
int numSamples = 0;
|
int numSamples = 0;
|
||||||
itr = mmStore.setupIterator(query, subQuery, Collections.singletonList(Authorizations.EMPTY),
|
itr = mmStore.setupIterator(query, subQuery, new Authorizations(), Long.MAX_VALUE);
|
||||||
Long.MAX_VALUE);
|
|
||||||
while (itr.hasTop()) {
|
while (itr.hasTop()) {
|
||||||
itr.next();
|
itr.next();
|
||||||
Map<Set<Tag>, Aggregation> aggregations = AggregationIterator.decodeValue(itr.getTopValue());
|
Map<Set<Tag>, Aggregation> aggregations = AggregationIterator.decodeValue(itr.getTopValue());
|
||||||
@@ -172,8 +171,7 @@ public class TestDataStoreCacheIterator {
|
|||||||
long firstTimestamp = -1;
|
long firstTimestamp = -1;
|
||||||
long lastTimestamp = -1;
|
long lastTimestamp = -1;
|
||||||
int numSamples = 0;
|
int numSamples = 0;
|
||||||
itr = mmStore.setupIterator(query, subQuery, Collections.singletonList(Authorizations.EMPTY),
|
itr = mmStore.setupIterator(query, subQuery, new Authorizations(), Long.MAX_VALUE);
|
||||||
Long.MAX_VALUE);
|
|
||||||
while (itr.hasTop()) {
|
while (itr.hasTop()) {
|
||||||
itr.next();
|
itr.next();
|
||||||
Map<Set<Tag>, Aggregation> aggregations = AggregationIterator.decodeValue(itr.getTopValue());
|
Map<Set<Tag>, Aggregation> aggregations = AggregationIterator.decodeValue(itr.getTopValue());
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public class TestGorillaStore {
|
|||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void setup() {
|
public static void setup() {
|
||||||
configuration = new Configuration();
|
configuration = new Configuration();
|
||||||
configuration.getSecurity().setAllowAnonymousHttpAccess(true);
|
configuration.getSecurity().setAllowAnonymousAccess(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class TestMemoryDataStore {
|
|||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void setup() {
|
public static void setup() {
|
||||||
configuration = new Configuration();
|
configuration = new Configuration();
|
||||||
configuration.getSecurity().setAllowAnonymousHttpAccess(true);
|
configuration.getSecurity().setAllowAnonymousAccess(true);
|
||||||
configuration.getCache().setDefaultAgeOffHours(24);
|
configuration.getCache().setDefaultAgeOffHours(24);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import java.io.IOException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import org.apache.accumulo.core.client.sample.SamplerConfiguration;
|
|
||||||
import org.apache.accumulo.core.conf.AccumuloConfiguration;
|
import org.apache.accumulo.core.conf.AccumuloConfiguration;
|
||||||
import org.apache.accumulo.core.data.ByteSequence;
|
import org.apache.accumulo.core.data.ByteSequence;
|
||||||
import org.apache.accumulo.core.data.Key;
|
import org.apache.accumulo.core.data.Key;
|
||||||
@@ -64,20 +63,6 @@ public class IteratorTestBase {
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public IteratorEnvironment cloneWithSamplingEnabled() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SamplerConfiguration getSamplerConfiguration() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSamplingEnabled() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class CombinerIteratorEnvironment extends DefaultIteratorEnvironment {
|
protected static class CombinerIteratorEnvironment extends DefaultIteratorEnvironment {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import org.junit.Before;
|
|||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import timely.auth.AuthCache;
|
|
||||||
import timely.configuration.Configuration;
|
import timely.configuration.Configuration;
|
||||||
import timely.store.MetricAgeOffIterator;
|
import timely.store.MetricAgeOffIterator;
|
||||||
import timely.test.TestConfiguration;
|
import timely.test.TestConfiguration;
|
||||||
@@ -84,8 +83,4 @@ public class MacITBase {
|
|||||||
conf.setMetricAgeOffDays(ageOffSettings);
|
conf.setMetricAgeOffDays(ageOffSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
|
||||||
public void initializeAuthCache() {
|
|
||||||
AuthCache.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -42,7 +42,7 @@ public class OneWaySSLBase extends QueryBase {
|
|||||||
config.getSecurity().getServerSsl().setKeyFile(serverCert.privateKey().getAbsolutePath());
|
config.getSecurity().getServerSsl().setKeyFile(serverCert.privateKey().getAbsolutePath());
|
||||||
config.getSecurity().getServerSsl().setUseOpenssl(false);
|
config.getSecurity().getServerSsl().setUseOpenssl(false);
|
||||||
config.getSecurity().getServerSsl().setUseGeneratedKeypair(false);
|
config.getSecurity().getServerSsl().setUseGeneratedKeypair(false);
|
||||||
config.getSecurity().setAllowAnonymousHttpAccess(true);
|
config.getSecurity().setAllowAnonymousAccess(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ public class OneWaySSLBasicAuthAccessIT extends OneWaySSLBase {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() throws Exception {
|
public void setup() throws Exception {
|
||||||
AuthCache.clear();
|
|
||||||
Connector con = mac.getConnector("root", "secret");
|
Connector con = mac.getConnector("root", "secret");
|
||||||
con.securityOperations().changeUserAuthorizations("root", new Authorizations("A", "B", "C", "D", "E", "F"));
|
con.securityOperations().changeUserAuthorizations("root", new Authorizations("A", "B", "C", "D", "E", "F"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public class TwoWaySSLFailureIT extends QueryBase {
|
|||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
// This fqdn does not match what is in security.xml
|
// This fqdn does not match what is in security.xml
|
||||||
serverCert = new SelfSignedCertificate("CN=bad.example.com");
|
serverCert = new SelfSignedCertificate("bad.example.com");
|
||||||
clientTrustStoreFile = serverCert.certificate().getAbsoluteFile();
|
clientTrustStoreFile = serverCert.certificate().getAbsoluteFile();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Error creating self signed certificate", e);
|
throw new RuntimeException("Error creating self signed certificate", e);
|
||||||
@@ -80,7 +80,7 @@ public class TwoWaySSLFailureIT extends QueryBase {
|
|||||||
config.getSecurity().getServerSsl().setTrustStoreFile(serverCert.certificate().getAbsolutePath());
|
config.getSecurity().getServerSsl().setTrustStoreFile(serverCert.certificate().getAbsolutePath());
|
||||||
config.getSecurity().getServerSsl().setUseOpenssl(false);
|
config.getSecurity().getServerSsl().setUseOpenssl(false);
|
||||||
config.getSecurity().getServerSsl().setUseGeneratedKeypair(false);
|
config.getSecurity().getServerSsl().setUseGeneratedKeypair(false);
|
||||||
config.getSecurity().setAllowAnonymousHttpAccess(false);
|
config.getSecurity().setAllowAnonymousAccess(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HttpsURLConnection getUrlConnection(URL url) throws Exception {
|
protected HttpsURLConnection getUrlConnection(URL url) throws Exception {
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ public class TwoWaySSLIT extends QueryBase {
|
|||||||
config.getSecurity().getServerSsl().setTrustStoreFile(serverCert.certificate().getAbsolutePath());
|
config.getSecurity().getServerSsl().setTrustStoreFile(serverCert.certificate().getAbsolutePath());
|
||||||
config.getSecurity().getServerSsl().setUseOpenssl(false);
|
config.getSecurity().getServerSsl().setUseOpenssl(false);
|
||||||
config.getSecurity().getServerSsl().setUseGeneratedKeypair(false);
|
config.getSecurity().getServerSsl().setUseGeneratedKeypair(false);
|
||||||
config.getSecurity().setAllowAnonymousHttpAccess(false);
|
config.getSecurity().setAllowAnonymousAccess(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HttpsURLConnection getUrlConnection(URL url) throws Exception {
|
protected HttpsURLConnection getUrlConnection(URL url) throws Exception {
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ public class TwoWaySSLOpenSSLIT extends QueryBase {
|
|||||||
config.getSecurity().getServerSsl().setTrustStoreFile(serverCert.certificate().getAbsolutePath());
|
config.getSecurity().getServerSsl().setTrustStoreFile(serverCert.certificate().getAbsolutePath());
|
||||||
config.getSecurity().getServerSsl().setUseOpenssl(false);
|
config.getSecurity().getServerSsl().setUseOpenssl(false);
|
||||||
config.getSecurity().getServerSsl().setUseGeneratedKeypair(false);
|
config.getSecurity().getServerSsl().setUseGeneratedKeypair(false);
|
||||||
config.getSecurity().setAllowAnonymousHttpAccess(false);
|
config.getSecurity().setAllowAnonymousAccess(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ public class WebSocketClientIT extends OneWaySSLBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onOpen(Session session, EndpointConfig config) {
|
public void onOpen(Session session, EndpointConfig config) {
|
||||||
session.addMessageHandler(String.class, new MessageHandler.Whole<String>() {
|
session.addMessageHandler(new MessageHandler.Whole<String>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessage(String message) {
|
public void onMessage(String message) {
|
||||||
@@ -147,33 +147,28 @@ public class WebSocketClientIT extends OneWaySSLBase {
|
|||||||
@Test
|
@Test
|
||||||
public void testClientAnonymousAccess() throws Exception {
|
public void testClientAnonymousAccess() throws Exception {
|
||||||
WebSocketSubscriptionClient client = new WebSocketSubscriptionClient(sslCtx, "localhost", 54322, 54323, false,
|
WebSocketSubscriptionClient client = new WebSocketSubscriptionClient(sslCtx, "localhost", 54322, 54323, false,
|
||||||
false, null, null, false, 65536);
|
null, null, false, 65536);
|
||||||
try {
|
|
||||||
conf.getSecurity().setAllowAnonymousWsAccess(true);
|
|
||||||
testWorkflow(client);
|
testWorkflow(client);
|
||||||
} finally {
|
|
||||||
conf.getSecurity().setAllowAnonymousWsAccess(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testClientBasicAuthAccess() throws Exception {
|
public void testClientBasicAuthAccess() throws Exception {
|
||||||
WebSocketSubscriptionClient client = new WebSocketSubscriptionClient(sslCtx, "localhost", 54322, 54323, false,
|
WebSocketSubscriptionClient client = new WebSocketSubscriptionClient(sslCtx, "localhost", 54322, 54323, true,
|
||||||
true, "test", "test1", false, 65536);
|
"test", "test1", false, 65536);
|
||||||
testWorkflow(client);
|
testWorkflow(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = HttpResponseException.class)
|
@Test(expected = HttpResponseException.class)
|
||||||
public void testClientBasicAuthAccessFailure() throws Exception {
|
public void testClientBasicAuthAccessFailure() throws Exception {
|
||||||
WebSocketSubscriptionClient client = new WebSocketSubscriptionClient(sslCtx, "localhost", 54322, 54323, false,
|
WebSocketSubscriptionClient client = new WebSocketSubscriptionClient(sslCtx, "localhost", 54322, 54323, true,
|
||||||
true, "test", "test2", false, 65536);
|
"test", "test2", false, 65536);
|
||||||
testWorkflow(client);
|
testWorkflow(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testClientBasicAuthParameterMismatch() throws Exception {
|
public void testClientBasicAuthParameterMismatch() throws Exception {
|
||||||
WebSocketSubscriptionClient client = new WebSocketSubscriptionClient(sslCtx, "localhost", 54322, 54323, false,
|
WebSocketSubscriptionClient client = new WebSocketSubscriptionClient(sslCtx, "localhost", 54322, 54323, true,
|
||||||
true, "test", null, false, 65536);
|
"test", null, false, 65536);
|
||||||
testWorkflow(client);
|
testWorkflow(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -517,7 +517,7 @@ public class HttpApiIT extends OneWaySSLBase {
|
|||||||
AtomicInteger rack1Count = new AtomicInteger(0);
|
AtomicInteger rack1Count = new AtomicInteger(0);
|
||||||
AtomicInteger rack2Count = new AtomicInteger(0);
|
AtomicInteger rack2Count = new AtomicInteger(0);
|
||||||
for(QueryResponse response: responses){
|
for(QueryResponse response: responses){
|
||||||
assertEquals("Found incorrect metric", "sys.cpu.idle", response.getMetric());
|
assertTrue("Found incorrect metric", "sys.cpu.idle".equals(response.getMetric()));
|
||||||
response.getTags().forEach((tagk, tagv) -> {
|
response.getTags().forEach((tagk, tagv) -> {
|
||||||
switch (tagk){
|
switch (tagk){
|
||||||
case "rack":
|
case "rack":
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import org.junit.experimental.categories.Category;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
import timely.Server;
|
import timely.Server;
|
||||||
import timely.api.request.VersionRequest;
|
import timely.api.request.VersionRequest;
|
||||||
import timely.api.request.subscription.AddSubscription;
|
import timely.api.request.subscription.AddSubscription;
|
||||||
@@ -67,7 +68,6 @@ 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.AuthenticationService;
|
import timely.auth.AuthenticationService;
|
||||||
import timely.auth.TimelyPrincipal;
|
|
||||||
import timely.model.Metric;
|
import timely.model.Metric;
|
||||||
import timely.model.Tag;
|
import timely.model.Tag;
|
||||||
import timely.netty.Constants;
|
import timely.netty.Constants;
|
||||||
@@ -195,7 +195,7 @@ public class WebSocketIT extends OneWaySSLBase {
|
|||||||
con.securityOperations().changeUserAuthorizations("root", new Authorizations("A", "B", "C", "D", "E", "F"));
|
con.securityOperations().changeUserAuthorizations("root", new Authorizations("A", "B", "C", "D", "E", "F"));
|
||||||
|
|
||||||
this.sessionId = UUID.randomUUID().toString();
|
this.sessionId = UUID.randomUUID().toString();
|
||||||
AuthCache.put(sessionId, TimelyPrincipal.anonymousPrincipal());
|
AuthCache.getCache().put(sessionId, token);
|
||||||
group = new NioEventLoopGroup();
|
group = new NioEventLoopGroup();
|
||||||
SslContext ssl = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
|
SslContext ssl = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
|
||||||
|
|
||||||
@@ -504,8 +504,8 @@ public class WebSocketIT extends OneWaySSLBase {
|
|||||||
@Test
|
@Test
|
||||||
public void testSubscriptionWorkflowWithEndTimeAndVisibilities() throws Exception {
|
public void testSubscriptionWorkflowWithEndTimeAndVisibilities() throws Exception {
|
||||||
|
|
||||||
AuthCache.remove(sessionId);
|
Authentication auth = AuthenticationService.getAuthenticationManager().authenticate(token);
|
||||||
AuthenticationService.authenticate(token, sessionId);
|
AuthCache.getCache().put(sessionId, auth);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final String subscriptionId = "1234";
|
final String subscriptionId = "1234";
|
||||||
@@ -583,7 +583,7 @@ public class WebSocketIT extends OneWaySSLBase {
|
|||||||
close.setSubscriptionId(subscriptionId);
|
close.setSubscriptionId(subscriptionId);
|
||||||
ch.writeAndFlush(new TextWebSocketFrame(JsonUtil.getObjectMapper().writeValueAsString(close)));
|
ch.writeAndFlush(new TextWebSocketFrame(JsonUtil.getObjectMapper().writeValueAsString(close)));
|
||||||
} finally {
|
} finally {
|
||||||
AuthCache.remove(sessionId);
|
AuthCache.getCache().asMap().remove(sessionId, auth);
|
||||||
ch.close().sync();
|
ch.close().sync();
|
||||||
s.shutdown();
|
s.shutdown();
|
||||||
group.shutdownGracefully();
|
group.shutdownGracefully();
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
npe.ou.entries=iamnotaperson,npe,stillnotaperson
|
|
||||||
subject.dn.pattern=(?:^|,)\\s*OU\\s*=\\s*My Department\\s*(?:,|$)
|
|
||||||
Loaded 100 of 101 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user