diff options
author | Simo Sorce <simo@redhat.com> | 2014-04-21 16:36:56 -0400 |
---|---|---|
committer | Simo Sorce <simo@redhat.com> | 2014-07-09 05:10:27 -0400 |
commit | 285432d9f918c45a1746d0f569eee727aaa9673b (patch) | |
tree | 6ee66e5586d422158d19221ed6fcde9742a20adf /src/mod_auth_gssapi.c | |
parent | 197cf29a4ed19e6ec3a3e73e4798d4c114b428b0 (diff) | |
download | mod_auth_gssapi-285432d9f918c45a1746d0f569eee727aaa9673b.tar.gz mod_auth_gssapi-285432d9f918c45a1746d0f569eee727aaa9673b.tar.xz mod_auth_gssapi-285432d9f918c45a1746d0f569eee727aaa9673b.zip |
Add mod_session support
By setting GssapiUseSessions we enable the module to store a bearer
token with the user and gss names in the client, this way we can allow
clients to perform authentication once but then remain authenticaed
for the duration of the session or until the original credentials expire.
The Secure cookie used to store the token is encrypted using a randomly
generated AES key at process startup. This means multiple apache servers
will not be able to use the same cookie, however the client will reauth
transparently if the cookie cannot be read.
Diffstat (limited to 'src/mod_auth_gssapi.c')
-rw-r--r-- | src/mod_auth_gssapi.c | 247 |
1 files changed, 232 insertions, 15 deletions
diff --git a/src/mod_auth_gssapi.c b/src/mod_auth_gssapi.c index 7f4077b..240d47a 100644 --- a/src/mod_auth_gssapi.c +++ b/src/mod_auth_gssapi.c @@ -24,16 +24,23 @@ #include <stdbool.h> #include <stdint.h> +#include <time.h> #include <gssapi/gssapi.h> #include <gssapi/gssapi_ext.h> +#include <apr_strings.h> +#include <apr_base64.h> +#define APR_WANT_STRFUNC +#include "apr_want.h" + #include <httpd.h> #include <http_core.h> #include <http_connection.h> #include <http_log.h> #include <http_request.h> -#include <apr_strings.h> -#include <apr_base64.h> +#include <mod_session.h> +#include "crypto.h" + module AP_MODULE_DECLARE_DATA auth_gssapi_module; @@ -43,7 +50,9 @@ struct mag_config { bool ssl_only; bool map_to_local; bool gss_conn_ctx; + bool use_sessions; gss_key_value_set_desc cred_store; + struct seal_key *mag_skey; }; static char *mag_status(request_rec *req, int type, uint32_t err) @@ -88,23 +97,56 @@ static char *mag_error(request_rec *req, const char *msg, } static APR_OPTIONAL_FN_TYPE(ssl_is_https) *mag_is_https = NULL; +static APR_OPTIONAL_FN_TYPE(ap_session_load) *mag_sess_load_fn = NULL; +static APR_OPTIONAL_FN_TYPE(ap_session_get) *mag_sess_get_fn = NULL; +static APR_OPTIONAL_FN_TYPE(ap_session_set) *mag_sess_set_fn = NULL; -static int mag_post_config(apr_pool_t *cfg, apr_pool_t *log, +static int mag_post_config(apr_pool_t *cfgpool, apr_pool_t *log, apr_pool_t *temp, server_rec *s) { /* FIXME: create mutex to deal with connections and contexts ? */ mag_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); + mag_sess_load_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_load); + mag_sess_get_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_get); + mag_sess_set_fn = APR_RETRIEVE_OPTIONAL_FN(ap_session_set); return OK; } +static apr_status_t mag_session_load(request_rec *req, session_rec **sess) +{ + if (mag_sess_load_fn) { + return mag_sess_load_fn(req, sess); + } + return DECLINED; +} + +static apr_status_t mag_session_get(request_rec *req, session_rec *sess, + const char *key, const char **value) +{ + if (mag_sess_get_fn) { + return mag_sess_get_fn(req, sess, key, value); + } + return DECLINED; +} + +static apr_status_t mag_session_set(request_rec *req, session_rec *sess, + const char *key, const char *value) +{ + if (mag_sess_set_fn) { + return mag_sess_set_fn(req, sess, key, value); + } + return DECLINED; +} + struct mag_conn { apr_pool_t *parent; gss_ctx_id_t ctx; bool established; - char *user_name; - char *gss_name; + const char *user_name; + const char *gss_name; + time_t expiration; }; static int mag_pre_connection(conn_rec *c, void *csd) @@ -131,6 +173,150 @@ static apr_status_t mag_conn_destroy(void *ptr) return APR_SUCCESS; } +#define MIN_EXPIRATION_TIME 300 /* 5 minutes validity minimum */ + +#define MAG_BEARER_KEY "MagBearerToken" + +static void mag_check_session(request_rec *req, + struct mag_config *cfg, struct mag_conn **conn) +{ + struct mag_conn *mc; + apr_status_t rc; + session_rec *sess = NULL; + const char *sessval = NULL; + int declen; + struct databuf ctxbuf = { 0 }; + struct databuf cipherbuf = { 0 }; + char *next, *last; + time_t expiration; + + rc = mag_session_load(req, &sess); + if (rc != OK || sess == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, 0, req, + "Sessions not available, no cookies!"); + return; + } + + mc = *conn; + if (!mc) { + mc = apr_pcalloc(req->pool, sizeof(struct mag_conn)); + if (!mc) return; + + mc->parent = req->pool; + *conn = mc; + } + + rc = mag_session_get(req, sess, MAG_BEARER_KEY, &sessval); + if (rc != OK) { + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, + "Failed to get session data!"); + return; + } + if (!sessval) { + /* no session established, just return */ + return; + } + + if (!cfg->mag_skey) { + /* we do not have a key, just return */ + return; + } + + /* decode it */ + declen = apr_base64_decode_len(sessval); + cipherbuf.value = apr_palloc(req->pool, declen); + if (!cipherbuf.value) return; + cipherbuf.length = (int)apr_base64_decode((char *)cipherbuf.value, sessval); + + rc = UNSEAL_BUFFER(req->pool, cfg->mag_skey, &cipherbuf, &ctxbuf); + if (rc != OK) { + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, + "Failed to unseal session data!"); + return; + } + + /* get time */ + next = apr_strtok((char *)ctxbuf.value, ":", &last); + expiration = (time_t)apr_atoi64(next); + if (expiration < time(NULL)) { + /* credentials fully expired, return nothing */ + return; + } + + /* user name is next */ + next = apr_strtok(NULL, ":", &last); + mc->user_name = apr_pstrdup(mc->parent, next); + if (!mc->user_name) return; + + /* gssapi name (often a principal) is last. + * (because it may contain the separator as a valid char we + * just read last as is, without further tokenizing */ + mc->gss_name = apr_pstrdup(mc->parent, last); + if (!mc->gss_name) return; + + /* OK we have a valid token */ + mc->established = true; +} + +static void mag_attempt_session(request_rec *req, + struct mag_config *cfg, struct mag_conn *mc) +{ + session_rec *sess = NULL; + char *sessval = NULL; + struct databuf plainbuf = { 0 }; + struct databuf cipherbuf = { 0 }; + struct databuf ctxbuf = { 0 }; + apr_status_t rc; + + /* we save the session only if the authentication is established */ + + if (!mc->established) return; + rc = mag_session_load(req, &sess); + if (rc != OK || sess == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, 0, req, + "Sessions not available, can't send cookies!"); + return; + } + + if (!cfg->mag_skey) { + rc = SEAL_KEY_CREATE(&cfg->mag_skey); + if (rc != OK) { + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, + "Failed to create sealing key!"); + return; + } + } + + sessval = apr_psprintf(req->pool, "%ld:%s:%s", + (long)mc->expiration, mc->user_name, mc->gss_name); + if (!sessval) return; + + plainbuf.length = strlen(sessval) + 1; + plainbuf.value = (unsigned char *)sessval; + + rc = SEAL_BUFFER(req->pool, cfg->mag_skey, &plainbuf, &cipherbuf); + if (rc != OK) { + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, + "Failed to seal session data!"); + return; + } + + ctxbuf.length = apr_base64_encode_len(cipherbuf.length); + ctxbuf.value = apr_pcalloc(req->pool, ctxbuf.length); + if (!ctxbuf.value) return; + + ctxbuf.length = apr_base64_encode((char *)ctxbuf.value, + (char *)cipherbuf.value, + cipherbuf.length); + + rc = mag_session_set(req, sess, MAG_BEARER_KEY, (char *)ctxbuf.value); + if (rc != OK) { + ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, + "Failed to set session data!"); + return; + } +} + static bool mag_conn_is_https(conn_rec *c) { if (mag_is_https) { @@ -156,6 +342,7 @@ static int mag_auth(request_rec *req) gss_name_t client = GSS_C_NO_NAME; gss_cred_id_t delegated_cred = GSS_C_NO_CREDENTIAL; uint32_t flags; + uint32_t vtime; uint32_t maj, min; char *reply; size_t replen; @@ -169,6 +356,11 @@ static int mag_auth(request_rec *req) return DECLINED; } + /* ignore auth for subrequests */ + if (!ap_is_initial_req(req)) { + return OK; + } + cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module); if (cfg->ssl_only) { @@ -184,11 +376,24 @@ static int mag_auth(request_rec *req) req->connection->conn_config, &auth_gssapi_module); if (!mc) { - return DECLINED; + ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req, + "Failed to retrieve connection context!"); + goto done; } + } + + /* if available, session always supersedes connection bound data */ + mag_check_session(req, cfg, &mc); + + if (mc) { + /* register the context in the memory pool, so it can be freed + * when the connection/request is terminated */ + apr_pool_userdata_set(mc, "mag_conn_ptr", + mag_conn_destroy, mc->parent); + if (mc->established) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, 0, req, - "Connection bound pre-authentication found."); + "Already established context found!"); apr_table_set(req->subprocess_env, "GSS_NAME", mc->gss_name); req->ap_auth_type = apr_pstrdup(req->pool, "Negotiate"); req->user = apr_pstrdup(req->pool, mc->user_name); @@ -217,7 +422,7 @@ static int mag_auth(request_rec *req) maj = gss_accept_sec_context(&min, pctx, GSS_C_NO_CREDENTIAL, &input, GSS_C_NO_CHANNEL_BINDINGS, - &client, &mech_type, &output, &flags, NULL, + &client, &mech_type, &output, &flags, &vtime, &delegated_cred); if (GSS_ERROR(maj)) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, @@ -226,19 +431,17 @@ static int mag_auth(request_rec *req) goto done; } - /* register the context in the connection pool, so it can be freed - * when the connection is terminated */ - apr_pool_userdata_set(mc, "mag_conn_ptr", mag_conn_destroy, mc->parent); - if (maj == GSS_S_CONTINUE_NEEDED) { - if (!cfg->gss_conn_ctx) { + if (!mc) { ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, req, - "Mechanism needs continuation but " - "GssapiConnectionBound is off."); + "Mechanism needs continuation but neither " + "GssapiConnectionBound nor " + "GssapiUseSessions are available"); gss_delete_sec_context(&min, pctx, GSS_C_NO_BUFFER); gss_release_buffer(&min, &output); output.length = 0; } + /* auth not complete send token and wait next packet */ goto done; } @@ -281,6 +484,11 @@ static int mag_auth(request_rec *req) mc->user_name = apr_pstrdup(mc->parent, req->user); mc->gss_name = apr_pstrdup(mc->parent, clientname); mc->established = true; + if (vtime == GSS_C_INDEFINITE || vtime < MIN_EXPIRATION_TIME) { + vtime = MIN_EXPIRATION_TIME; + } + mc->expiration = time(NULL) + vtime; + mag_attempt_session(req, cfg, mc); } ret = OK; @@ -341,6 +549,13 @@ static const char *mag_conn_ctx(cmd_parms *parms, void *mconfig, int on) return NULL; } +static const char *mag_use_sess(cmd_parms *parms, void *mconfig, int on) +{ + struct mag_config *cfg = (struct mag_config *)mconfig; + cfg->use_sessions = on ? true : false; + return NULL; +} + static const char *mag_cred_store(cmd_parms *parms, void *mconfig, const char *w) { @@ -394,6 +609,8 @@ static const command_rec mag_commands[] = { "Work only if connection is SSL Secured"), AP_INIT_FLAG("GssapiConnectionBound", mag_conn_ctx, NULL, OR_AUTHCFG, "Authentication is bound to the TCP connection"), + AP_INIT_FLAG("GssapiUseSessions", mag_use_sess, NULL, OR_AUTHCFG, + "Authentication uses mod_sessions to hold status"), AP_INIT_ITERATE("GssapiCredStore", mag_cred_store, NULL, OR_AUTHCFG, "Credential Store"), { NULL } |