Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Special ccache handling for {HOSTNAME} acceptor #238

Merged
merged 2 commits into from
Oct 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 55 additions & 7 deletions src/mod_auth_gssapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,9 @@ static bool mag_conn_is_https(conn_rec *c)
return false;
}

static char *get_ccache_name(request_rec *req, char *dir, const char *name,
bool use_unique, apr_pool_t *pool);

static bool mag_acquire_creds(request_rec *req,
struct mag_config *cfg,
gss_OID_set desired_mechs,
Expand Down Expand Up @@ -226,7 +229,52 @@ static bool mag_acquire_creds(request_rec *req,
}

#ifdef HAVE_CRED_STORE
gss_const_key_value_set_t store = cfg->cred_store;
gss_const_key_value_set_t store = NULL;

/* When using multiple names, we need to use individual separate ccaches
* for each principal or gss_acquire_cred() on the default ccache will
* fail when names don't match. This is needed only for the s4u2proxy
* case, where we try to acquire proxy credentials. The lucky thing is
* that in this case we require the use of a delegated creedntials
* directory, so we just use this directory to also hold permanent ccaches
* for individual acceptor names. */
if (cfg->acceptor_name_from_req && cfg->use_s4u2proxy &&
cfg->deleg_ccache_dir) {

gss_key_value_set_desc *s;
bool add = true;
char *ccname;
char *special_name;

special_name = apr_psprintf(req->pool, "acceptor_%s", req->hostname);
ccname = get_ccache_name(req, cfg->deleg_ccache_dir, special_name,
false, req->pool);

s = apr_pcalloc(req->pool, sizeof(gss_key_value_set_desc));
s->count = cfg->cred_store->count;
s->elements = apr_pcalloc(req->pool,
(s->count + 1) *
sizeof(gss_key_value_element_desc));
for (size_t i = 0; i < s->count; i++) {
gss_key_value_element_desc *el = &cfg->cred_store->elements[i];
s->elements[i].key = el->key;
if (strcmp(el->key, "ccache") == 0) {
s->elements[i].value = ccname;
add = false;
} else {
s->elements[i].value = el->value;
}
}
if (add) {
s->elements[s->count].key = "ccache";
s->elements[s->count].value = ccname;
s->count++;
}

store = s;
} else {
store = cfg->cred_store;
}

maj = gss_acquire_cred_from(&min, acceptor_name, GSS_C_INDEFINITE,
desired_mechs, cred_usage, store, creds,
Expand Down Expand Up @@ -287,8 +335,8 @@ static char *escape(apr_pool_t *pool, const char *name,
return escaped;
}

static char *get_ccache_name(request_rec *req, char *dir, const char *gss_name,
bool use_unique, struct mag_conn *mc)
static char *get_ccache_name(request_rec *req, char *dir, const char *name,
bool use_unique, apr_pool_t *pool)
{
char *ccname, *escaped;
int ccachefd;
Expand All @@ -297,15 +345,15 @@ static char *get_ccache_name(request_rec *req, char *dir, const char *gss_name,
/* We need to escape away '/', we can't have path separators in
* a ccache file name */
/* first double escape the esacping char (~) if any */
escaped = escape(req->pool, gss_name, '~', "~~");
escaped = escape(req->pool, name, '~', "~~");
/* then escape away the separator (/) if any */
escaped = escape(req->pool, escaped, '/', "~");

if (use_unique == false) {
return apr_psprintf(mc->pool, "%s/%s", dir, escaped);
return apr_psprintf(pool, "%s/%s", dir, escaped);
}

ccname = apr_psprintf(mc->pool, "%s/%s-XXXXXX", dir, escaped);
ccname = apr_psprintf(pool, "%s/%s-XXXXXX", dir, escaped);

umask_save = umask(0177);
ccachefd = mkstemp(ccname);
Expand Down Expand Up @@ -1297,7 +1345,7 @@ static int mag_complete(struct mag_req_cfg *req_cfg, struct mag_conn *mc,
"requester: %s", mc->gss_name);

ccache_path = get_ccache_name(req, cfg->deleg_ccache_dir, mc->gss_name,
cfg->deleg_ccache_unique, mc);
cfg->deleg_ccache_unique, mc->pool);
if (ccache_path == NULL) {
goto done;
}
Expand Down
15 changes: 15 additions & 0 deletions tests/httpd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,21 @@ CoreDumpDirectory "{HTTPROOT}"
Require valid-user
</Location>

<Location /hostname_proxy>
AuthType GSSAPI
AuthName "Login"
GssapiSSLonly Off
GssapiCredStore ccache:{HTTPROOT}/httpd_krb5_ccache
GssapiCredStore client_keytab:{HTTPROOT}/http.keytab
GssapiCredStore keytab:{HTTPROOT}/http.keytab
GssapiBasicAuth Off
GssapiAllowedMech krb5
GssapiAcceptorName {{HOSTNAME}}
GssapiUseS4U2Proxy On
GssapiDelegCcacheDir {HTTPROOT}/delegccachedir
Require valid-user
</Location>

<Location /required_name_attr1>
AuthType GSSAPI
AuthName "Required Name Attributes"
Expand Down
36 changes: 21 additions & 15 deletions tests/magtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -691,26 +691,32 @@ def test_no_negotiate(testdir, testenv, logfile):


def test_hostname_acceptor(testdir, testenv, logfile):
hdir = os.path.join(testdir, 'httpd', 'html', 'hostname_acceptor')
plain_test_name = 'hostname_acceptor'
hdir = os.path.join(testdir, 'httpd', 'html', plain_test_name)
os.mkdir(hdir)
shutil.copy('tests/index.html', hdir)

proxy_test_name = 'hostname_proxy'
hdir = os.path.join(testdir, 'httpd', 'html', proxy_test_name)
os.mkdir(hdir)
shutil.copy('tests/index.html', hdir)
ddir = os.path.join(testdir, 'httpd', 'delegccachedir')
os.mkdir(ddir)

failed = False
for (name, fail) in [(WRAP_HOSTNAME, False),
(WRAP_ALIASNAME, False),
(WRAP_FAILNAME, True)]:
res = subprocess.Popen(["tests/t_hostname_acceptor.py", name],
stdout=logfile, stderr=logfile,
env=testenv, preexec_fn=os.setsid)
res.wait()
if fail:
if res.returncode == 0:
failed = True
else:
if res.returncode != 0:
for test_name in [plain_test_name, proxy_test_name]:
for (name, fail) in [(WRAP_HOSTNAME, False),
(WRAP_ALIASNAME, False),
(WRAP_FAILNAME, True)]:
res = subprocess.Popen(["tests/t_hostname_acceptor.py",
name, test_name],
stdout=logfile, stderr=logfile,
env=testenv, preexec_fn=os.setsid)
res.wait()
if (fail and res.returncode == 0) or \
(not fail and res.returncode != 0):
failed = True
if failed:
break
break

if failed:
sys.stderr.write('HOSTNAME ACCEPTOR: FAILED\n')
Expand Down
4 changes: 2 additions & 2 deletions tests/t_hostname_acceptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

if __name__ == '__main__':
sess = requests.Session()
url = 'http://%s/hostname_acceptor/' % sys.argv[1]
url = 'http://{}/{}/'.format(sys.argv[1], sys.argv[2])
r = sess.get(url, auth=HTTPKerberosAuth(delegate=True))
if r.status_code != 200:
raise ValueError('Hostname-based acceptor failed')
raise ValueError('Hostname acceptor ({}) failed'.format(sys.argv[2]))