5. Authenticator Modules¶
What’s An Authenticator Module?¶
Authenticator modules provide credentials for outgoing connections from AMPS. It is not necessary for these credentials to match the account that AMPS runs under: the authenticator module has complete freedom to pass whatever credentials are appropriate. Most often, options provided in the configuration for the module specify how to obtain the credentials for this connection.
When to Implement an Authenticator¶
Implement an authenticator when you need to provide credentials to replication destinations and need to store those credentials outside of the AMPS configuration file. The AMPS default authenticator provides the ability to provide a user name and password.
Authenticator Context¶
AMPS uses authenticator modules when making an outgoing connection that uses authentication. AMPS creates a separate context for each outgoing connection. The same module may have different configuration settings, and provide different credentials, for different outgoing connections. For example, replication to a server at the same site may use different credentials than replication to a server at a different site.
Because the same module may be used with different parameters on different transports, AMPS allows you to save the parameters in an authenticator context.
Your module implements the amps_authenticator_create_context
function
to create an authentication context. The function has this signature:
amps_authenticator_context amps_authenticator_create_context(amps_module_options options);
The authenticator context is a pointer to data that your module defines. The AMPS server does not process or interpret this data. The server receives the pointer from your module when the module is initialized, and then provides the pointer back to your module for each authenticator request.
If your module is unable to successfully create the context, you return
a NULL
context and use the amps_logger
provided to your module to
log information about the problem.
Notice that you cast the pointer to amps_authenticator_context
before you return it, and cast the pointer back to the type that your
application uses after AMPS returns the pointer to you.
AMPS passes configuration options to modules as a pointer to an array of
amps_option
structs. AMPS indicates the end of the array with an
option that has a NULL
key. Your module processes options until it
reaches an option with a NULL
key. If no options are specified in the
configuration file, the first amps_option
in the array will have a
NULL
key.
The following snippet shows one way to process options:
for (; options->key != NULL; ++options) {
// process the key/value pair here
}
When AMPS exits, AMPS calls amps_authenticator_destroy_context
with each
of the authenticator contexts created. This gives your module an opportunity to do any
resource management, cleanup or logging necessary before AMPS shuts down. The function has
this signature:
int amps_authenticator_destroy_context(amps_authenticator_context context);
Providing User Credentials for Logon¶
Authenticator modules implement three functions for providing user credentials.
Function | |
---|---|
amps_authenticator_logon_begin |
Called when AMPS begins the logon process. This function returns the username and password for AMPS to use to log in to the remote instance. |
amps_authenticator_logon_retry |
Called when the remote side issues a challenge that requires a response. AMPS provides the challenge and the module returns the response to the challenge. |
amps_authenticator_logon_complete |
Called when the logon sequence is complete. AMPS provides the final response from the server and a code indicating the reason that the login sequence is complete. |
Table 5.1: Functions for Providing User Credentials
To implement an authenticator, your module implements each of these methods.
When AMPS prepares to create an outgoing connection using your module, AMPS calls the following method:
int amps_authenticator_logon_begin(amps_authenticator_context context,
char** userOut,
size_t* userOutLength,
char** passwdOut,
size_t* passwdOutLength)
The authenticator context provided is the pointer returned by the call
to amps_authenticator_create_context()
for the transport that
received the request. Your module returns two character strings, one for
the user name to provide to the remote instance and one for the password
to provide to the remote instance. AMPS takes ownership of the memory
associated with these strings, and therefore these strings must be
allocated with the amps_allocator provided during module
initialization.
Following is a simple, minimal implementation of
amps_authenticator_logon_begin
:
int amps_authenticator_logon_begin(amps_authenticator_context context,
char** userOut,
size_t* userOutLength,
char** passwdOut,
size_t* passwdOutLength)
{
// for demonstration purposes, use a constant string
const char *userString = "static_username";
const char *passString = "static_password";
size_t userLen = strlen(userString);
size_t passLen = strlen(passString);
// allocate memory for the user name using the allocator provided
// during module initialization
*userOut = amps_allocate(userLen);
// copy the user name to the allocated memory
strncpy(*userOut, userString, userLen);
// allocate memory for the password and copy the password to the
// allocated memory.
*passwdOut = amps_allocate(passLen);
strncpy(*passwdOut, userPass, passLen);
// return success
return AMPS_SUCCESS;
}
This simple implementation uses a static string for username and password, and is intended only to show correct usage of the interface.
The remote server can return a challenge by issuing a retry response to the
login. For this method, the server passes the context and fills in the user,
userLength, passwd, and passwdLength fields. The module is responsible for
responding to the challenge by setting the userOut
, userOutLength
,
passwdOut
, and passwdOutLength
parameters to respond to the challenge. The
values in the out parameters are returned to the remote server as the
response to the challenge.
int amps_authenticator_logon_retry(amps_authenticator_context context,
const char* user,
size_t userLength,
const char* passwd,
size_t passwdLength,
char** userOut,
size_t* userOutLength,
char** passwdOut,
size_t* passwdOutLength);
For sample purposes, we assume a pre-existing function that returns the
appropriate response to the challenge, and implement
amps_authenticator_logon_retry
as follows:
int amps_authenticator_logon_retry(amps_authenticator_context context,
const char* user,
size_t userLength,
const char* passwd,
size_t passwdLength,
char** userOut,
size_t* userOutLength,
char** passwdOut,
size_t* passwdOutLength)
{
char* challengeResponse = NULL;
if (userLength == 0 || passwdLength == 0) { return AMPS_FAILURE; }
// generate_response is assumed to take the provided challenge
// and generate a response which is a NULL-terminated string
challengeResponse = generate_response(user, userLength, passwd, passwdLength);
if (challengeResponse == NULL) { return AMPS_FAILURE; }
*passwdOutLength = strlen(challengeResponse);
// allocate AMPS-managed memory for the response to the challenge
*passwdOut = amps_allocate(*passwdOutLength);
if (*passwdOut == NULL) {
*passwdOutLength = 0;
free(challengeResponse);
return AMPS_FAILURE;
}
memcpy(*passwdOut, challengeResponse, *passwdOutLength);
free(challengeResponse);
*userOut = amps_allocate(userLength);
if (*userOut == NULL) { return AMPS_FAILURE; }
*userOutLength = userLength;
memcpy(*userOut, user, userLength);
return AMPS_SUCCESS;
}
The authenticator module interface also provides a function that AMPS calls when the logon process is complete. This gives the module the opportunity to do any cleanup that may be associated with the login process, and to log the reason code from the server if necessary. This function has the following signature:
int amps_authenticator_logon_completed(amps_authenticator_context context,
char* user,
size_t userLength,
char* passwd,
size_t passwdLength,
char* reason,
size_t reasonLength);
For example, the following implementation of this function logs the
results of the login process, using the amps_logger
stored during
module initialization:
int amps_authenticator_logon_completed(amps_authenticator_context context,
char* user,
size_t userLength,
char* passwd,
size_t passwdLength,
char* reason,
size_t reasonLength)
{
char buffer[1024];
snprintf(buffer, sizeof(buffer),
"AMPS user '%.*s' completed replication logon for reason '%.*s'",
(int)userLength, user, (int)reasonLength, reason);
LOGGER(amps_module_log_level_trace, buffer);
return AMPS_SUCCESS;
}