/*
* Copyright 2015 floragunn UG (haftungsbeschränkt)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.floragunn.searchguard.transport;
import java.security.cert.X509Certificate;
import java.util.Objects;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.Provider;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.tasks.Task;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.Transport;
import org.elasticsearch.transport.TransportChannel;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.transport.TransportRequestHandler;
import org.elasticsearch.transport.TransportRequestOptions;
import org.elasticsearch.transport.TransportResponse;
import org.elasticsearch.transport.TransportResponseHandler;
import com.floragunn.searchguard.auditlog.AuditLog;
import com.floragunn.searchguard.auth.BackendRegistry;
import com.floragunn.searchguard.ssl.transport.PrincipalExtractor;
import com.floragunn.searchguard.ssl.transport.SearchGuardSSLTransportService;
import com.floragunn.searchguard.support.Base64Helper;
import com.floragunn.searchguard.support.ConfigConstants;
import com.floragunn.searchguard.support.HeaderHelper;
import com.floragunn.searchguard.support.LogHelper;
import com.floragunn.searchguard.user.User;
import com.google.common.base.Strings;
public class SearchGuardTransportService extends SearchGuardSSLTransportService {
protected final ESLogger log = Loggers.getLogger(this.getClass());
private final Provider<BackendRegistry> backendRegistry;
private final Provider<InterClusterRequestEvaluator> requestEvalProvider;
private final AuditLog auditLog;
@Inject
public SearchGuardTransportService(final Settings settings, final Transport transport, final ThreadPool threadPool,
final Provider<BackendRegistry> backendRegistry, AuditLog auditLog, final PrincipalExtractor principalExtractor,
final Provider<InterClusterRequestEvaluator> requestEvalProvider) {
super(settings, transport, threadPool, principalExtractor);
this.backendRegistry = backendRegistry;
this.auditLog = auditLog;
this.requestEvalProvider = requestEvalProvider;
}
@Override
public <T extends TransportResponse> void sendRequest(final DiscoveryNode node, final String action, final TransportRequest request,
final TransportResponseHandler<T> handler) {
attachHeaders(action, request);
//LogHelper.logUserTrace("<-- Send {} to {} with {}/{}", action, node.getName(), request.getContext(), request.getHeaders());
super.sendRequest(node, action, request, handler);
}
@Override
public <T extends TransportResponse> void sendRequest(final DiscoveryNode node, final String action, final TransportRequest request,
final TransportRequestOptions options, final TransportResponseHandler<T> handler) {
attachHeaders(action, request);
//LogHelper.logUserTrace("<-- Send {} to {} with {}/{}", action, node.getName(), request.getContext(), request.getHeaders());
super.sendRequest(node, action, request, options, handler);
}
private void attachHeaders(final String action, final TransportRequest request) {
// keep original address
final Object remoteAdr = request.getFromContext(ConfigConstants.SG_REMOTE_ADDRESS);
if (remoteAdr != null && remoteAdr instanceof InetSocketTransportAddress) {
request.putHeader(ConfigConstants.SG_REMOTE_ADDRESS_HEADER, Base64Helper.serializeObject(((InetSocketTransportAddress) remoteAdr).address()));
//LogHelper.logUserTrace("<-- Put remote address {} in header (from sg_remote_address ctx)", remoteAdr);
}
/*if(log.isTraceEnabled()) {
log.trace("sendRequest {}", LogHelper.toString(request));
}*/
User user = request.getFromContext(ConfigConstants.SG_USER);
if(user == null /* && action.startsWith("internal:")*/ && request.remoteAddress() == null) {
user = User.SG_INTERNAL;
}
if(user != null) {
request.putHeader(ConfigConstants.SG_USER_HEADER, Base64Helper.serializeObject(user));
} else {
throw new ElasticsearchSecurityException("user must not be null here for " + action + " "
+ LogHelper.toString(request));
}
}
@Override
protected void addAdditionalContextValues(final String action, final TransportRequest request, final X509Certificate[] localCerts, final X509Certificate[] peerCerts, final String principal)
throws Exception {
boolean isInterClusterRequest = requestEvalProvider.get().isInterClusterRequest(request, localCerts, peerCerts, principal);
if (isInterClusterRequest) {
if (log.isTraceEnabled() && !action.startsWith("internal:")) {
log.trace("Is inter cluster request ({}/{}/{})", action, request.getClass(), request.remoteAddress());
}
request.putInContext(ConfigConstants.SG_SSL_TRANSPORT_INTERCLUSTER_REQUEST, Boolean.TRUE);
} else {
if (log.isTraceEnabled()) {
log.trace("Is not an inter cluster request");
}
}
super.addAdditionalContextValues(action, request, localCerts, peerCerts, principal);
}
@Override
protected void messageReceivedDecorate(final TransportRequest request, final TransportRequestHandler handler,
final TransportChannel transportChannel, Task task) throws Exception {
try {
final com.floragunn.searchguard.configuration.RequestHolder context = new com.floragunn.searchguard.configuration.RequestHolder(
request);
com.floragunn.searchguard.configuration.RequestHolder.setCurrent(context);
request.putInContext(ConfigConstants.SG_CHANNEL_TYPE, transportChannel.getChannelType());
//bypass non-netty requests
if(transportChannel.getChannelType().equals("local") || transportChannel.getChannelType().equals("direct")) {
super.messageReceivedDecorate(request, handler, transportChannel, task);
return;
}
//if the incoming request is an internal:* or a shard request allow only if request was sent by a server node
//if transport channel is not a netty channel but a direct or local channel (e.g. send via network) then allow it (regardless of beeing a internal: or shard request)
if (!isInterClusterRequest(request)
&& (transportChannel.action().startsWith("internal:") || transportChannel.action().contains("["))) {
auditLog.logMissingPrivileges(transportChannel.action(), request);
log.error("Internal or shard requests not allowed from a non-server node for transport type "+transportChannel.getChannelType());
transportChannel.sendResponse(new ElasticsearchSecurityException(
"Internal or shard requests not allowed from a non-server node for transport type "+transportChannel.getChannelType()));
return;
}
String principal = null;
//LogHelper.logUserTrace("Received {} from {} via {}", transportChannel.action(), request.remoteAddress(),
// transportChannel.getClass());
//LogHelper.logUserTrace("CTX/H {}/{}", request.getContext(), request.getHeaders());
if ((principal = request.getFromContext(ConfigConstants.SG_SSL_TRANSPORT_PRINCIPAL)) == null) {
Exception ex = new ElasticsearchSecurityException(
"No SSL client certificates found for transport type "+transportChannel.getChannelType()+". Search Guard needs the Search Guard SSL plugin to be installed");
auditLog.logSSLException(request, ex, transportChannel.action());
log.error("No SSL client certificates found for transport type "+transportChannel.getChannelType()+". Search Guard needs the Search Guard SSL plugin to be installed");
transportChannel.sendResponse(ex);
return;
} else {
if(isInterClusterRequest(request)) {
String userHeader = request.getHeader(ConfigConstants.SG_USER_HEADER);
if(Strings.isNullOrEmpty(userHeader)) {
//user can be null when a node client wants connect
request.putInContext(ConfigConstants.SG_USER, User.SG_INTERNAL);
} else {
request.putInContext(ConfigConstants.SG_USER, Objects.requireNonNull((User) Base64Helper.deserializeObject(userHeader)));
}
String originalRemoteAddress = request.getHeader(ConfigConstants.SG_REMOTE_ADDRESS_HEADER);
if(!Strings.isNullOrEmpty(originalRemoteAddress)) {
request.putInContext(ConfigConstants.SG_REMOTE_ADDRESS, Base64Helper.deserializeObject(originalRemoteAddress));
}
} else {
//this is a netty request from a non-server node (maybe also be internal: or a shard request)
//and therefore issued by a transport client
try {
HeaderHelper.checkSGHeader(request);
} catch (Exception e) {
auditLog.logBadHeaders(request);
log.error("Error validating headers "+e, e);
transportChannel.sendResponse(ExceptionsHelper.convertToElastic(e));
return;
}
request.putInContext(ConfigConstants.SG_USER, new User(principal));
try {
if(!backendRegistry.get().authenticate(request, transportChannel)) {
log.error("Cannot authenticate {}", (User) request.getFromContext(ConfigConstants.SG_USER));
transportChannel.sendResponse(new ElasticsearchSecurityException("Cannot authenticate "+request.getFromContext(ConfigConstants.SG_USER)));
return;
}
} catch (Exception e) {
log.error("Error authentication transport user "+e, e);
auditLog.logFailedLogin(principal, request);
transportChannel.sendResponse(ExceptionsHelper.convertToElastic(e));
return;
}
TransportAddress originalRemoteAddress = request.remoteAddress();
if(originalRemoteAddress != null && (originalRemoteAddress instanceof InetSocketTransportAddress)) {
request.putInContext(ConfigConstants.SG_REMOTE_ADDRESS, originalRemoteAddress);
} else {
log.error("Request has no proper remote address {}", originalRemoteAddress);
transportChannel.sendResponse(new ElasticsearchException("Request has no proper remote address"));
return;
}
}
super.messageReceivedDecorate(request, handler, transportChannel, task);
//LogHelper.logUserTrace("--> Put user {} in context (from sg_ssl_transport_principal)", principal);
}
//LogHelper.logUserTrace(">>>> TransportService for {}", transportChannel.action());
} finally {
//LogHelper.logUserTrace("<<<< TransportService {}", transportChannel.action());
com.floragunn.searchguard.configuration.RequestHolder.removeCurrent();
}
}
@Override
protected void errorThrown(Throwable t, final TransportRequest request, String action) {
auditLog.logSSLException(request, t, action);
}
/**
*
* @param request
* @return true if request comes from a node with a server certificate
*/
private static boolean isInterClusterRequest(final TransportRequest request) {
return request.getFromContext(ConfigConstants.SG_SSL_TRANSPORT_INTERCLUSTER_REQUEST) == Boolean.TRUE;
}
}