public class InterNodeCommunicationAuthenticationService extends Object implements ApplicationEventPublisherAware, SmartInitializingSingleton
Authentication services for the inter-node communication.
On startup, this service reads a shared secret from the Cassandra database.
If the shared secret is not found, it generates a new one and stores it in
the database for use by other servers. When this initialization has finished,
an InterNodeCommunicationAuthenticationServiceInitializedEvent
is
published through the application event publisher. The initialization status
can also be queried with the isInitialized()
method.
When the getUsernameAndPassword(Role)
method is called, this service
uses the shared secret to calculate an HMAC on the current time. This HMAC
and the current time are combined and returned as a password. This password
can then be used for authentication with other nodes in the cluster.
When a server in the cluster receives a request from another server, it can
verify the credentials provided by that server using the
authenticate(String, String)
method. This method verifies, that the
HMAC presented as part of the password was actually generated using the same
shared secret. It also checks that the provided time-stamp is not too far in
the past or the future. This way, even if an attacker gets knowledge of the
password, it is only useful for a limited amount of time. The shared secret
itself is never transported over the network (except for communication with
the Cassandra database which has to be protected separately).
The authentication mechanism implemented by this class is not secure if used over an untrusted network because it does not enforce any authentication on the content of a request and - in addition to that - is vulnerable to replay attacks. The authentication mechanism is not designed to provide protection from an attacker that acts as a man-in-the-middle. However, it can provide sufficient protection against accidental connections from other servers that do not belong to the same cluster but have been misconfigured to contact servers that are not part of their cluster.
Modifier and Type | Class and Description |
---|---|
static class |
InterNodeCommunicationAuthenticationService.Role
Role for which a client authenticated.
|
Modifier and Type | Field and Description |
---|---|
protected org.apache.commons.logging.Log |
log
Logger for this class.
|
Constructor and Description |
---|
InterNodeCommunicationAuthenticationService() |
Modifier and Type | Method and Description |
---|---|
void |
afterSingletonsInstantiated() |
InterNodeCommunicationAuthenticationService.Role |
authenticate(String username,
String password)
Tries to authenticate a request with the specified username and password.
|
Pair<String,String> |
getUsernameAndPassword(InterNodeCommunicationAuthenticationService.Role role)
Returns a username / password pair that is suitable for authentication
with this service.
|
boolean |
isInitialized()
Tells whether this authentication service has initialized completely and
is ready for operation.
|
void |
onGenericDataStoreDAOInitializedEvent(GenericDataStoreDAOInitializedEvent event)
Handles
GenericDataStoreDAOInitializedEvent s. |
void |
setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) |
void |
setGenericDataStoreDAO(GenericDataStoreDAO genericDataStoreDAO)
Sets the DAO for reading and writing generic pieces of configuration
data.
|
public InterNodeCommunicationAuthenticationService()
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher)
setApplicationEventPublisher
in interface ApplicationEventPublisherAware
@Autowired public void setGenericDataStoreDAO(GenericDataStoreDAO genericDataStoreDAO)
genericDataStoreDAO
- generic data-store DAO to be used by this service.@EventListener public void onGenericDataStoreDAOInitializedEvent(GenericDataStoreDAOInitializedEvent event)
GenericDataStoreDAOInitializedEvent
s. This event is used
when the GenericDataStoreDAO
set using
setGenericDataStoreDAO(GenericDataStoreDAO)
is not ready yet
when this object is initialized. In this case, initialization code that
depends on the generic data-store DAO is delayed until the DAO has
finished initialization.event
- initialization event sent by the GenericDataStoreDAO
.public void afterSingletonsInstantiated()
afterSingletonsInstantiated
in interface SmartInitializingSingleton
public InterNodeCommunicationAuthenticationService.Role authenticate(String username, String password)
Tries to authenticate a request with the specified username and password.
The username needs to be the same username that is returned by
getUsernameAndPassword(Role)
. The password is validated
according to the following rules:
The password is decoded into a time stamp and a corresponding MAC. If the total number of the decoded bytes is not equal to the number of expected bytes, the authentication fails.
The MAC of the time stamp specified as part of the password is calculated using the internal shared secret. If the calculated MAC does not match the MAC provided as part of the password, the authentication fails.
Finally, the provided time-stamp is checked. If it is too far in the past or future (more than five seconds in each direction), the authentication fails. Otherwise, the authentication is succeeds.
In general, the authentication should succeed if the supplied username /
password pair has been recently generated using the
getUsernameAndPassword(Role)
method of an instance internally
using the same shared secret. However, if the generating instance runs on
a different host than the validating one, the clocks need to be
synchronized within a few seconds so that the time-stamp presented as
part of the password is considered valid.
For authentication with the InterNodeCommunicationAuthenticationService.Role.UNPRIVILEGED
role, the clock
skew does not matter.
username
- username provided by the client.password
- password provided by the client.InterNodeCommunicationAuthenticationService.Role.PRIVILEGED
if the client was authenticated for
privileged access, InterNodeCommunicationAuthenticationService.Role.UNPRIVILEGED
if the client was
authenticated for unprivileged access and
InterNodeCommunicationAuthenticationService.Role.NOT_AUTHENTICATED
if the client was not
authenticated at all.IllegalStateException
- if the authentication cannot complete because this service
has not initialized completely yet. Typically, this happens
if this method is called before the connection with the
Cassandra database has been established.NullPointerException
- if username
or password
are
null
.getUsernameAndPassword(Role)
public Pair<String,String> getUsernameAndPassword(InterNodeCommunicationAuthenticationService.Role role)
Returns a username / password pair that is suitable for authentication
with this service. In general, if the username / password pair returned
by this method is passed to authenticate(String, String)
, that
method will return true
.
This will work across different instances of this class (even across
different JVMs or hosts) if the shared secret used by this service is the
same. For the privileged role, the password returned by this method is
only valid for a limited amount of time. Therefore, authentication can
fail if too much time passed between calling this method and
authenticate(String, String)
or if
authenticate(String, String)
is called on a different host with
a clock that is not sufficiently synchronized with the clock on this
host.
role
- role for which the credentials shall be provided. When the
returned credentials are passed to the
authenticate(String, String)
method, it will return
the specified role. Must not be InterNodeCommunicationAuthenticationService.Role.NOT_AUTHENTICATED
.authenticate(...)
method.IllegalArgumentException
- if role
is InterNodeCommunicationAuthenticationService.Role.NOT_AUTHENTICATED
.IllegalStateException
- if the password cannot be generated because this service has
not initialized completely yet. Typically, this happens if
this method is called before the connection with the
Cassandra database has been established.NullPointerException
- if role
is null
.authenticate(String, String)
public boolean isInitialized()
Tells whether this authentication service has initialized completely and is ready for operation. The authentication service is ready as soon as it has retrieved the shared secret used for inter-node authentication from the database or has generated a shared secret and successfully stored it in the database. The latter case only happens if there is no shared secret in the database yet.
When this service changes to the initialized state, it publishes an
InterNodeCommunicationAuthenticationServiceInitializedEvent
to
the application context.
true
if initialization has completed and this
authentication service can be used, false
otherwise.Copyright © 2011–2017 aquenos GmbH. All rights reserved.