|
@@ -32,137 +32,90 @@ import org.elasticsearch.xpack.security.action.SecurityActionMapper;
|
|
|
import org.elasticsearch.xpack.security.authc.AuthenticationService;
|
|
|
import org.elasticsearch.xpack.security.authz.AuthorizationService;
|
|
|
|
|
|
-import java.io.IOException;
|
|
|
-
|
|
|
-import static org.elasticsearch.xpack.core.security.support.Exceptions.authenticationError;
|
|
|
-
|
|
|
/**
|
|
|
- * This interface allows to intercept messages as they come in and execute logic
|
|
|
- * This is used in x-pack security to execute the authentication/authorization on incoming
|
|
|
- * messages.
|
|
|
- * Note that this filter only applies for nodes, but not for clients.
|
|
|
+ * The server transport filter that should be used in nodes as it ensures that an incoming
|
|
|
+ * request is properly authenticated and authorized
|
|
|
*/
|
|
|
-public interface ServerTransportFilter {
|
|
|
+final class ServerTransportFilter {
|
|
|
+
|
|
|
+ private static final Logger logger = LogManager.getLogger(ServerTransportFilter.class);
|
|
|
+
|
|
|
+ private final AuthenticationService authcService;
|
|
|
+ private final AuthorizationService authzService;
|
|
|
+ private final SecurityActionMapper actionMapper = new SecurityActionMapper();
|
|
|
+ private final ThreadContext threadContext;
|
|
|
+ private final boolean extractClientCert;
|
|
|
+ private final DestructiveOperations destructiveOperations;
|
|
|
+ private final SecurityContext securityContext;
|
|
|
+ private final XPackLicenseState licenseState;
|
|
|
+
|
|
|
+ ServerTransportFilter(AuthenticationService authcService, AuthorizationService authzService,
|
|
|
+ ThreadContext threadContext, boolean extractClientCert, DestructiveOperations destructiveOperations,
|
|
|
+ SecurityContext securityContext, XPackLicenseState licenseState) {
|
|
|
+ this.authcService = authcService;
|
|
|
+ this.authzService = authzService;
|
|
|
+ this.threadContext = threadContext;
|
|
|
+ this.extractClientCert = extractClientCert;
|
|
|
+ this.destructiveOperations = destructiveOperations;
|
|
|
+ this.securityContext = securityContext;
|
|
|
+ this.licenseState = licenseState;
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
* Called just after the given request was received by the transport. Any exception
|
|
|
* thrown by this method will stop the request from being handled and the error will
|
|
|
* be sent back to the sender.
|
|
|
*/
|
|
|
- void inbound(String action, TransportRequest request, TransportChannel transportChannel, ActionListener<Void> listener)
|
|
|
- throws IOException;
|
|
|
-
|
|
|
- /**
|
|
|
- * The server transport filter that should be used in nodes as it ensures that an incoming
|
|
|
- * request is properly authenticated and authorized
|
|
|
- */
|
|
|
- class NodeProfile implements ServerTransportFilter {
|
|
|
- private static final Logger logger = LogManager.getLogger(NodeProfile.class);
|
|
|
-
|
|
|
- private final AuthenticationService authcService;
|
|
|
- private final AuthorizationService authzService;
|
|
|
- private final SecurityActionMapper actionMapper = new SecurityActionMapper();
|
|
|
- private final ThreadContext threadContext;
|
|
|
- private final boolean extractClientCert;
|
|
|
- private final DestructiveOperations destructiveOperations;
|
|
|
- private final boolean reservedRealmEnabled;
|
|
|
- private final SecurityContext securityContext;
|
|
|
- private final XPackLicenseState licenseState;
|
|
|
-
|
|
|
- NodeProfile(AuthenticationService authcService, AuthorizationService authzService,
|
|
|
- ThreadContext threadContext, boolean extractClientCert, DestructiveOperations destructiveOperations,
|
|
|
- boolean reservedRealmEnabled, SecurityContext securityContext, XPackLicenseState licenseState) {
|
|
|
- this.authcService = authcService;
|
|
|
- this.authzService = authzService;
|
|
|
- this.threadContext = threadContext;
|
|
|
- this.extractClientCert = extractClientCert;
|
|
|
- this.destructiveOperations = destructiveOperations;
|
|
|
- this.reservedRealmEnabled = reservedRealmEnabled;
|
|
|
- this.securityContext = securityContext;
|
|
|
- this.licenseState = licenseState;
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void inbound(String action, TransportRequest request, TransportChannel transportChannel, ActionListener<Void> listener)
|
|
|
- throws IOException {
|
|
|
- if (CloseIndexAction.NAME.equals(action) || OpenIndexAction.NAME.equals(action) || DeleteIndexAction.NAME.equals(action)) {
|
|
|
- IndicesRequest indicesRequest = (IndicesRequest) request;
|
|
|
- try {
|
|
|
- destructiveOperations.failDestructive(indicesRequest.indices());
|
|
|
- } catch(IllegalArgumentException e) {
|
|
|
- listener.onFailure(e);
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- /*
|
|
|
- here we don't have a fallback user, as all incoming request are
|
|
|
- expected to have a user attached (either in headers or in context)
|
|
|
- We can make this assumption because in nodes we make sure all outgoing
|
|
|
- requests from all the nodes are attached with a user (either a serialize
|
|
|
- user an authentication token
|
|
|
- */
|
|
|
- String securityAction = actionMapper.action(action, request);
|
|
|
-
|
|
|
- TransportChannel unwrappedChannel = transportChannel;
|
|
|
- if (unwrappedChannel instanceof TaskTransportChannel) {
|
|
|
- unwrappedChannel = ((TaskTransportChannel) unwrappedChannel).getChannel();
|
|
|
+ void inbound(String action, TransportRequest request, TransportChannel transportChannel,ActionListener<Void> listener) {
|
|
|
+ if (CloseIndexAction.NAME.equals(action) || OpenIndexAction.NAME.equals(action) || DeleteIndexAction.NAME.equals(action)) {
|
|
|
+ IndicesRequest indicesRequest = (IndicesRequest) request;
|
|
|
+ try {
|
|
|
+ destructiveOperations.failDestructive(indicesRequest.indices());
|
|
|
+ } catch(IllegalArgumentException e) {
|
|
|
+ listener.onFailure(e);
|
|
|
+ return;
|
|
|
}
|
|
|
+ }
|
|
|
+ /*
|
|
|
+ here we don't have a fallback user, as all incoming request are
|
|
|
+ expected to have a user attached (either in headers or in context)
|
|
|
+ We can make this assumption because in nodes we make sure all outgoing
|
|
|
+ requests from all the nodes are attached with a user (either a serialize
|
|
|
+ user an authentication token
|
|
|
+ */
|
|
|
+ String securityAction = actionMapper.action(action, request);
|
|
|
+
|
|
|
+ TransportChannel unwrappedChannel = transportChannel;
|
|
|
+ if (unwrappedChannel instanceof TaskTransportChannel) {
|
|
|
+ unwrappedChannel = ((TaskTransportChannel) unwrappedChannel).getChannel();
|
|
|
+ }
|
|
|
|
|
|
- if (extractClientCert && (unwrappedChannel instanceof TcpTransportChannel)) {
|
|
|
- TcpChannel tcpChannel = ((TcpTransportChannel) unwrappedChannel).getChannel();
|
|
|
- if (tcpChannel instanceof Netty4TcpChannel || tcpChannel instanceof NioTcpChannel) {
|
|
|
- if (tcpChannel.isOpen()) {
|
|
|
- SSLEngineUtils.extractClientCertificates(logger, threadContext, tcpChannel);
|
|
|
- }
|
|
|
+ if (extractClientCert && (unwrappedChannel instanceof TcpTransportChannel)) {
|
|
|
+ TcpChannel tcpChannel = ((TcpTransportChannel) unwrappedChannel).getChannel();
|
|
|
+ if (tcpChannel instanceof Netty4TcpChannel || tcpChannel instanceof NioTcpChannel) {
|
|
|
+ if (tcpChannel.isOpen()) {
|
|
|
+ SSLEngineUtils.extractClientCertificates(logger, threadContext, tcpChannel);
|
|
|
}
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- final Version version = transportChannel.getVersion();
|
|
|
- authcService.authenticate(securityAction, request, (User)null, ActionListener.wrap((authentication) -> {
|
|
|
- if (authentication != null) {
|
|
|
- if (securityAction.equals(TransportService.HANDSHAKE_ACTION_NAME) &&
|
|
|
- SystemUser.is(authentication.getUser()) == false) {
|
|
|
- securityContext.executeAsUser(SystemUser.INSTANCE, (ctx) -> {
|
|
|
- final Authentication replaced = Authentication.getAuthentication(threadContext);
|
|
|
- authzService.authorize(replaced, securityAction, request, listener);
|
|
|
- }, version);
|
|
|
- } else {
|
|
|
- authzService.authorize(authentication, securityAction, request, listener);
|
|
|
- }
|
|
|
- } else if (licenseState.isAuthAllowed() == false) {
|
|
|
- listener.onResponse(null);
|
|
|
+ final Version version = transportChannel.getVersion();
|
|
|
+ authcService.authenticate(securityAction, request, (User)null, ActionListener.wrap((authentication) -> {
|
|
|
+ if (authentication != null) {
|
|
|
+ if (securityAction.equals(TransportService.HANDSHAKE_ACTION_NAME) &&
|
|
|
+ SystemUser.is(authentication.getUser()) == false) {
|
|
|
+ securityContext.executeAsUser(SystemUser.INSTANCE, (ctx) -> {
|
|
|
+ final Authentication replaced = Authentication.getAuthentication(threadContext);
|
|
|
+ authzService.authorize(replaced, securityAction, request, listener);
|
|
|
+ }, version);
|
|
|
} else {
|
|
|
- listener.onFailure(new IllegalStateException("no authentication present but auth is allowed"));
|
|
|
+ authzService.authorize(authentication, securityAction, request, listener);
|
|
|
}
|
|
|
- }, listener::onFailure));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * A server transport filter rejects internal calls, which should be used on connections
|
|
|
- * where only clients connect to. This ensures that no client can send any internal actions
|
|
|
- * or shard level actions. As it extends the NodeProfile the authentication/authorization is
|
|
|
- * done as well
|
|
|
- */
|
|
|
- class ClientProfile extends NodeProfile {
|
|
|
-
|
|
|
- ClientProfile(AuthenticationService authcService, AuthorizationService authzService,
|
|
|
- ThreadContext threadContext, boolean extractClientCert, DestructiveOperations destructiveOperations,
|
|
|
- boolean reservedRealmEnabled, SecurityContext securityContext, XPackLicenseState licenseState) {
|
|
|
- super(authcService, authzService, threadContext, extractClientCert, destructiveOperations, reservedRealmEnabled,
|
|
|
- securityContext, licenseState);
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public void inbound(String action, TransportRequest request, TransportChannel transportChannel, ActionListener<Void> listener)
|
|
|
- throws IOException {
|
|
|
- // TODO is ']' sufficient to mark as shard action?
|
|
|
- final boolean isInternalOrShardAction = action.startsWith("internal:") || action.endsWith("]");
|
|
|
- if (isInternalOrShardAction && TransportService.HANDSHAKE_ACTION_NAME.equals(action) == false) {
|
|
|
- throw authenticationError("executing internal/shard actions is considered malicious and forbidden");
|
|
|
+ } else if (licenseState.isAuthAllowed() == false) {
|
|
|
+ listener.onResponse(null);
|
|
|
+ } else {
|
|
|
+ listener.onFailure(new IllegalStateException("no authentication present but auth is allowed"));
|
|
|
}
|
|
|
- super.inbound(action, request, transportChannel, listener);
|
|
|
- }
|
|
|
+ }, listener::onFailure));
|
|
|
}
|
|
|
-
|
|
|
}
|