bx509d: Implement /get-tgt end-point
This commit is contained in:
31
kdc/bx509d.8
31
kdc/bx509d.8
@@ -194,6 +194,37 @@ header is included in the request, then the response will be a
|
||||
redirect to that URI with the Negotiate token in an
|
||||
.Va Authorization
|
||||
header that the user-agent should copy to the redirected request.
|
||||
.Sh TGT HTTP API
|
||||
This service provides an HTTP-based "kinit" service.
|
||||
The protocol consists of a
|
||||
.Ar GET
|
||||
of
|
||||
.Ar /get-tgt
|
||||
with an optional
|
||||
.Ar cname = Ar principal-name
|
||||
query parameter.
|
||||
.Pp
|
||||
In a successful query, the response body will contain a TGT and
|
||||
its session key encoded as a "ccache" file contents.
|
||||
.Pp
|
||||
Authentication is required.
|
||||
Unauthenticated requests will elicit a 401 response.
|
||||
.Pp
|
||||
Supported query parameters (separated by ampersands)
|
||||
.Bl -tag -width Ds -offset indent
|
||||
.It Li cname = Va principal
|
||||
.El
|
||||
.Pp
|
||||
Authentication is required
|
||||
Unauthenticated requests will elicit a 401 response.
|
||||
.Pp
|
||||
Authorization is required, where the authorization check is the
|
||||
same as for
|
||||
.Va /get-cert
|
||||
by the authenticated client principal to get a certificate with
|
||||
a PKINIT SAN for itself or the requested principal if a
|
||||
.Va cname
|
||||
query parameter was included.
|
||||
.Sh ENVIRONMENT
|
||||
.Bl -tag -width Ds
|
||||
.It Ev KRB5_CONFIG
|
||||
|
199
kdc/bx509d.c
199
kdc/bx509d.c
@@ -1062,29 +1062,16 @@ find_ccache(krb5_context context, const char *princ, char **ccname)
|
||||
return ret ? ret : ENOENT;
|
||||
}
|
||||
|
||||
/*
|
||||
* Acquire credentials for `princ' using PKINIT and the PKIX credentials in
|
||||
* `pkix_store', then place the result in the ccache named `ccname' (which will
|
||||
* be in our own private `cache_dir').
|
||||
*
|
||||
* XXX This function could be rewritten using gss_acquire_cred_from() and
|
||||
* gss_store_cred_into() provided we add new generic cred store key/value pairs
|
||||
* for PKINIT.
|
||||
*/
|
||||
enum k5_creds_kind { K5_CREDS_EPHEMERAL, K5_CREDS_CACHED };
|
||||
|
||||
static krb5_error_code
|
||||
do_pkinit(struct bx509_request_desc *r)
|
||||
get_ccache(struct bx509_request_desc *r, krb5_ccache *cc, int *won)
|
||||
{
|
||||
krb5_get_init_creds_opt *opt = NULL;
|
||||
krb5_init_creds_context ctx = NULL;
|
||||
krb5_error_code ret = 0;
|
||||
krb5_ccache temp_cc = NULL;
|
||||
krb5_ccache cc = NULL;
|
||||
krb5_principal p = NULL;
|
||||
struct stat st1, st2;
|
||||
time_t life;
|
||||
const char *crealm;
|
||||
const char *fn = NULL;
|
||||
char *temp_ccname = NULL;
|
||||
const char *fn = NULL;
|
||||
time_t life;
|
||||
int fd = -1;
|
||||
|
||||
/*
|
||||
@@ -1103,6 +1090,8 @@ do_pkinit(struct bx509_request_desc *r)
|
||||
* FILE ccache would take care to mkstemp() and rename() into place.
|
||||
* fcc_open() basically does a similar thing.
|
||||
*/
|
||||
*cc = NULL;
|
||||
*won = -1;
|
||||
if (asprintf(&temp_ccname, "%s.ccnew", r->ccname) == -1 ||
|
||||
temp_ccname == NULL)
|
||||
ret = ENOMEM;
|
||||
@@ -1138,30 +1127,70 @@ do_pkinit(struct bx509_request_desc *r)
|
||||
|
||||
/* Check if we lost any race to acquire Kerberos creds */
|
||||
if (ret == 0)
|
||||
ret = krb5_cc_resolve(r->context, temp_ccname, &temp_cc);
|
||||
if (ret == 0)
|
||||
ret = krb5_cc_get_lifetime(r->context, temp_cc, &life);
|
||||
ret = krb5_cc_resolve(r->context, temp_ccname, cc);
|
||||
if (ret == 0) {
|
||||
ret = krb5_cc_get_lifetime(r->context, *cc, &life);
|
||||
if (ret == 0 && life > 60)
|
||||
goto out; /* We lost the race, but we win: we get to do less work */
|
||||
*won = 0; /* We lost the race, but we win: we get to do less work */
|
||||
*won = 1;
|
||||
ret = 0;
|
||||
}
|
||||
free(temp_ccname);
|
||||
if (fd != -1)
|
||||
(void) close(fd); /* Drops the flock */
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* We won the race. Setup to acquire Kerberos creds with PKINIT.
|
||||
/*
|
||||
* Acquire credentials for `princ' using PKINIT and the PKIX credentials in
|
||||
* `pkix_store', then place the result in the ccache named `ccname' (which will
|
||||
* be in our own private `cache_dir').
|
||||
*
|
||||
* We should really make sure that gss_acquire_cred_from() can do this for
|
||||
* us. We'd add generic cred store key/value pairs for PKIX cred store,
|
||||
* trust anchors, and so on, and acquire that way, then
|
||||
* XXX This function could be rewritten using gss_acquire_cred_from() and
|
||||
* gss_store_cred_into() provided we add new generic cred store key/value pairs
|
||||
* for PKINIT.
|
||||
*/
|
||||
static krb5_error_code
|
||||
do_pkinit(struct bx509_request_desc *r, enum k5_creds_kind kind)
|
||||
{
|
||||
krb5_get_init_creds_opt *opt = NULL;
|
||||
krb5_init_creds_context ctx = NULL;
|
||||
krb5_error_code ret = 0;
|
||||
krb5_ccache temp_cc = NULL;
|
||||
krb5_ccache cc = NULL;
|
||||
krb5_principal p = NULL;
|
||||
const char *crealm;
|
||||
|
||||
if (kind == K5_CREDS_CACHED) {
|
||||
int won = -1;
|
||||
|
||||
ret = get_ccache(r, &temp_cc, &won);
|
||||
if (ret || !won)
|
||||
goto out;
|
||||
/*
|
||||
* We won the race to do PKINIT. Setup to acquire Kerberos creds with
|
||||
* PKINIT.
|
||||
*
|
||||
* We should really make sure that gss_acquire_cred_from() can do this
|
||||
* for us. We'd add generic cred store key/value pairs for PKIX cred
|
||||
* store, trust anchors, and so on, and acquire that way, then
|
||||
* gss_store_cred_into() to save it in a FILE ccache.
|
||||
*/
|
||||
} else {
|
||||
ret = krb5_cc_new_unique(r->context, "FILE", NULL, &temp_cc);
|
||||
}
|
||||
|
||||
ret = krb5_parse_name(r->context, r->cname, &p);
|
||||
if (ret == 0)
|
||||
crealm = krb5_principal_get_realm(r->context, p);
|
||||
if (ret == 0 &&
|
||||
(ret = krb5_get_init_creds_opt_alloc(r->context, &opt)) == 0)
|
||||
if (ret == 0)
|
||||
ret = krb5_get_init_creds_opt_alloc(r->context, &opt);
|
||||
if (ret == 0)
|
||||
krb5_get_init_creds_opt_set_default_flags(r->context, "kinit", crealm,
|
||||
opt);
|
||||
if (ret == 0 &&
|
||||
(ret = krb5_get_init_creds_opt_set_addressless(r->context,
|
||||
opt, 1)) == 0)
|
||||
if (ret == 0)
|
||||
ret = krb5_get_init_creds_opt_set_addressless(r->context, opt, 1);
|
||||
if (ret == 0)
|
||||
ret = krb5_get_init_creds_opt_set_pkinit(r->context, opt, p,
|
||||
r->pkix_store,
|
||||
NULL, /* pkinit_anchor */
|
||||
@@ -1183,12 +1212,20 @@ do_pkinit(struct bx509_request_desc *r)
|
||||
* into temp_cc, and rename into place. Note that krb5_cc_move() closes
|
||||
* the source ccache, so we set temp_cc = NULL if it succeeds.
|
||||
*/
|
||||
if (ret == 0 &&
|
||||
(ret = krb5_init_creds_get(r->context, ctx)) == 0 &&
|
||||
(ret = krb5_init_creds_store(r->context, ctx, temp_cc)) == 0 &&
|
||||
(ret = krb5_cc_resolve(r->context, r->ccname, &cc)) == 0 &&
|
||||
(ret = krb5_cc_move(r->context, temp_cc, cc)) == 0)
|
||||
if (ret == 0)
|
||||
ret = krb5_init_creds_get(r->context, ctx);
|
||||
if (ret == 0)
|
||||
ret = krb5_init_creds_store(r->context, ctx, temp_cc);
|
||||
if (kind == K5_CREDS_CACHED) {
|
||||
if (ret == 0)
|
||||
ret = krb5_cc_resolve(r->context, r->ccname, &cc);
|
||||
if (ret == 0)
|
||||
ret = krb5_cc_move(r->context, temp_cc, cc);
|
||||
if (ret == 0)
|
||||
temp_cc = NULL;
|
||||
} else if (ret == 0 && kind == K5_CREDS_EPHEMERAL) {
|
||||
ret = krb5_cc_get_full_name(r->context, temp_cc, &r->ccname);
|
||||
}
|
||||
|
||||
out:
|
||||
if (ctx)
|
||||
@@ -1197,9 +1234,6 @@ out:
|
||||
krb5_free_principal(r->context, p);
|
||||
krb5_cc_close(r->context, temp_cc);
|
||||
krb5_cc_close(r->context, cc);
|
||||
free(temp_ccname);
|
||||
if (fd != -1)
|
||||
(void) close(fd); /* Drops the flock */
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1230,7 +1264,7 @@ load_priv_key(krb5_context context, const char *fn, hx509_private_key *key)
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
bnegotiate_do_CA(struct bx509_request_desc *r)
|
||||
k5_do_CA(struct bx509_request_desc *r)
|
||||
{
|
||||
SubjectPublicKeyInfo spki;
|
||||
hx509_private_key key = NULL;
|
||||
@@ -1272,7 +1306,7 @@ bnegotiate_do_CA(struct bx509_request_desc *r)
|
||||
|
||||
/* Issue the certificate */
|
||||
if (ret == 0)
|
||||
ret = kdc_issue_certificate(r->context, "bx509", logfac, req, p,
|
||||
ret = kdc_issue_certificate(r->context, "getTGT", logfac, req, p,
|
||||
&r->token_times, 0,
|
||||
1 /* send_chain */, &certs);
|
||||
krb5_free_principal(r->context, p);
|
||||
@@ -1307,22 +1341,23 @@ bnegotiate_do_CA(struct bx509_request_desc *r)
|
||||
|
||||
/* Get impersonated Kerberos credentials for `cprinc' */
|
||||
static krb5_error_code
|
||||
bnegotiate_get_creds(struct bx509_request_desc *r)
|
||||
k5_get_creds(struct bx509_request_desc *r, enum k5_creds_kind kind)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
|
||||
/* If we have a live ccache for `cprinc', we're done */
|
||||
if ((ret = find_ccache(r->context, r->cname, &r->ccname)) == 0)
|
||||
if (kind == K5_CREDS_CACHED &&
|
||||
(ret = find_ccache(r->context, r->cname, &r->ccname)) == 0)
|
||||
return ret; /* Success */
|
||||
|
||||
/*
|
||||
* Else we have to acquire a credential for them using their bearer token
|
||||
* for authentication (and our keytab / initiator credentials perhaps).
|
||||
*/
|
||||
if ((ret = bnegotiate_do_CA(r)))
|
||||
return ret; /* bnegotiate_do_CA() calls bad_req() */
|
||||
if ((ret = k5_do_CA(r)))
|
||||
return ret; /* k5_do_CA() calls bad_req() */
|
||||
|
||||
if (ret == 0 && (ret = do_pkinit(r)))
|
||||
if (ret == 0 && (ret = do_pkinit(r, kind)))
|
||||
ret = bad_403(r, ret,
|
||||
"Could not acquire Kerberos credentials using PKINIT");
|
||||
return ret;
|
||||
@@ -1598,7 +1633,7 @@ bnegotiate(struct bx509_request_desc *r)
|
||||
* Perhaps we could use S4U instead, which would speed up the slow path a
|
||||
* bit.
|
||||
*/
|
||||
ret = bnegotiate_get_creds(r);
|
||||
ret = k5_get_creds(r, K5_CREDS_CACHED);
|
||||
|
||||
/* Acquire the Negotiate token and output it */
|
||||
if (ret == 0 && r->ccname != NULL)
|
||||
@@ -1618,6 +1653,72 @@ bnegotiate(struct bx509_request_desc *r)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
authorize_TGT_REQ(struct bx509_request_desc *r, const char *cname)
|
||||
{
|
||||
krb5_principal p = NULL;
|
||||
krb5_error_code ret;
|
||||
|
||||
ret = krb5_parse_name(r->context, r->cname, &p);
|
||||
ret = hx509_request_init(r->context->hx509ctx, &r->req);
|
||||
if (ret)
|
||||
return bad_500(r, ret, "Out of resources");
|
||||
heim_audit_addkv((heim_svc_req_desc)r, KDC_AUDIT_VIS,
|
||||
"requested_krb5PrincipalName", "%s", cname);
|
||||
ret = hx509_request_add_pkinit(r->context->hx509ctx, r->req, cname);
|
||||
if (ret == 0)
|
||||
ret = kdc_authorize_csr(r->context, "getTGT", r->req, p);
|
||||
krb5_free_principal(r->context, p);
|
||||
hx509_request_free(&r->req);
|
||||
if (ret)
|
||||
return bad_403(r, ret, "Not authorized to requested TGT");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Implements /get-tgt end-point.
|
||||
*
|
||||
* Query parameters (mutually exclusive):
|
||||
*
|
||||
* - cname=<name> (client principal name, if not the same as the authenticated
|
||||
* name, then this will be impersonated if allowed)
|
||||
*/
|
||||
static krb5_error_code
|
||||
get_tgt(struct bx509_request_desc *r)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
size_t bodylen;
|
||||
const char *cname = NULL;
|
||||
const char *fn;
|
||||
void *body;
|
||||
|
||||
cname = MHD_lookup_connection_value(r->connection, MHD_GET_ARGUMENT_KIND,
|
||||
"cname");
|
||||
ret = validate_token(r);
|
||||
if (ret == 0)
|
||||
ret = authorize_TGT_REQ(r, cname ? cname : r->cname);
|
||||
/* validate_token() and authorize_TGT_REQ() call bad_req() */
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = k5_get_creds(r, K5_CREDS_EPHEMERAL);
|
||||
if (ret)
|
||||
return bad_503(r, ret, "Could not get TGT");
|
||||
|
||||
fn = strchr(r->ccname, ':');
|
||||
if (fn == NULL)
|
||||
return bad_500(r, ret, "Impossible error");
|
||||
fn++;
|
||||
if ((errno = rk_undumpdata(fn, &body, &bodylen))) {
|
||||
(void) unlink(fn);
|
||||
return bad_503(r, ret, "Could not get TGT");
|
||||
}
|
||||
|
||||
ret = resp(r, MHD_HTTP_OK, MHD_RESPMEM_MUST_COPY, body, bodylen, NULL);
|
||||
free(body);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static krb5_error_code
|
||||
health(const char *method, struct bx509_request_desc *r)
|
||||
{
|
||||
@@ -1676,6 +1777,8 @@ route(void *cls,
|
||||
else if (strcmp(url, "/get-negotiate-token") == 0 ||
|
||||
strcmp(url, "/bnegotiate") == 0) /* old name */
|
||||
ret = bnegotiate(&r);
|
||||
else if (strcmp(url, "/get-tgt") == 0)
|
||||
ret = get_tgt(&r);
|
||||
else
|
||||
ret = bad_404(&r, url);
|
||||
|
||||
|
@@ -317,6 +317,7 @@ CLEANFILES= \
|
||||
barpassword \
|
||||
ca.crt \
|
||||
cache.krb5 \
|
||||
cache2.krb5 \
|
||||
cdigest-reply \
|
||||
client-cache \
|
||||
curlheaders \
|
||||
@@ -339,8 +340,10 @@ CLEANFILES= \
|
||||
krb5-cc.conf \
|
||||
krb5-cccol.conf \
|
||||
krb5-hdb-mitdb.conf \
|
||||
krb5-master2.conf \
|
||||
krb5-pkinit-win.conf \
|
||||
krb5-pkinit.conf \
|
||||
krb5-pkinit2.conf \
|
||||
krb5-bx509.conf \
|
||||
krb5-httpkadmind.conf \
|
||||
krb5-slave2.conf \
|
||||
@@ -354,12 +357,14 @@ CLEANFILES= \
|
||||
malloc-log \
|
||||
malloc-log-master \
|
||||
malloc-log-slave \
|
||||
messages.log2 \
|
||||
negotiate-token \
|
||||
notfoopassword \
|
||||
o2cache.krb5 \
|
||||
o2digest-reply \
|
||||
ocache.krb5 \
|
||||
out-log \
|
||||
req \
|
||||
response-headers \
|
||||
s2digest-reply \
|
||||
sdb \
|
||||
|
@@ -54,7 +54,10 @@ kdc="${kdc} --addresses=localhost -P $port"
|
||||
|
||||
server=datan.test.h5l.se
|
||||
otherserver=other.test.h5l.se
|
||||
cache="FILE:${objdir}/cache.krb5"
|
||||
cachefile="${objdir}/cache.krb5"
|
||||
cache="FILE:${cachefile}"
|
||||
cachefile2="${objdir}/cache2.krb5"
|
||||
cache2="FILE:${cachefile2}"
|
||||
keyfile="${hx509_data}/key.der"
|
||||
keyfile2="${hx509_data}/key2.der"
|
||||
kt=${objdir}/kt
|
||||
@@ -63,6 +66,7 @@ ukt=${objdir}/ukt
|
||||
ukeytab=FILE:${ukt}
|
||||
|
||||
kinit="${kinit} -c $cache ${afs_no_afslog}"
|
||||
klist2="${klist} --hidden -v -c $cache2"
|
||||
klist="${klist} --hidden -v -c $cache"
|
||||
kgetcred="${kgetcred} -c $cache"
|
||||
kdestroy="${kdestroy} -c $cache ${afs_no_unlog}"
|
||||
@@ -429,6 +433,30 @@ trap "kill -9 ${kdcpid} ${bx509pid}; echo signal killing kdc and bx509d; exit 1;
|
||||
${kinit} -kt $ukeytab foo@${R} || exit 1
|
||||
$klist || { echo "failed to setup kimpersonate credentials"; exit 2; }
|
||||
|
||||
echo "Fetch TGT"
|
||||
(set -vx; csr_grant pkinit foo@${R} foo@${R})
|
||||
token=$(KRB5CCNAME=$cache $gsstoken HTTP@$server)
|
||||
if ! (set -vx;
|
||||
curl -o "${cachefile2}" -Lgsf \
|
||||
--resolve ${server}:${bx509port}:127.0.0.1 \
|
||||
-H "Authorization: Negotiate $token" \
|
||||
"http://${server}:${bx509port}/get-tgt"); then
|
||||
echo "Failed to get a TGT with /get-tgt end-point"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "Fetch TGT (inception)"
|
||||
${kdestroy}
|
||||
token=$(KRB5CCNAME=$cache2 $gsstoken HTTP@$server)
|
||||
if ! (set -vx;
|
||||
curl -o "${cachefile}" -Lgsf \
|
||||
--resolve ${server}:${bx509port}:127.0.0.1 \
|
||||
-H "Authorization: Negotiate $token" \
|
||||
"http://${server}:${bx509port}/get-tgt"); then
|
||||
echo "Failed to get a TGT with /get-tgt end-point"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
echo "Fetch negotiate token (pre-test)"
|
||||
# Do what /bnegotiate does, roughly, prior to testing /bnegotiate
|
||||
$hxtool request-create --subject='' --generate-key=rsa --key-bits=1024 \
|
||||
|
@@ -121,6 +121,45 @@
|
||||
}
|
||||
}
|
||||
|
||||
[getTGT]
|
||||
simple_csr_authorizer_directory = @objdir@/simple_csr_authz
|
||||
realms = {
|
||||
TEST.H5L.SE = {
|
||||
# Default (no cert exts requested)
|
||||
user = {
|
||||
# Use an issuer for user certs:
|
||||
ca = PEM-FILE:@objdir@/user-issuer.pem
|
||||
subject_name = CN=${principal-name-without-realm},DC=test,DC=h5l,DC=se
|
||||
ekus = 1.3.6.1.5.5.7.3.2
|
||||
include_pkinit_san = true
|
||||
}
|
||||
hostbased_service = {
|
||||
# Only for HTTP services
|
||||
HTTP = {
|
||||
# Use an issuer for server certs:
|
||||
ca = PEM-FILE:@objdir@/server-issuer.pem
|
||||
include_dnsname_san = true
|
||||
# Don't bother with a template
|
||||
}
|
||||
}
|
||||
# Non-default certs (extensions requested)
|
||||
#
|
||||
# Use no templates -- get empty subject names,
|
||||
# use SANs.
|
||||
#
|
||||
# Use appropriate issuers.
|
||||
client = {
|
||||
ca = PEM-FILE:@objdir@/user-issuer.pem
|
||||
}
|
||||
server = {
|
||||
ca = PEM-FILE:@objdir@/server-issuer.pem
|
||||
}
|
||||
mixed = {
|
||||
ca = PEM-FILE:@objdir@/mixed-issuer.pem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[logging]
|
||||
kdc = 0-/FILE:@objdir@/messages.log
|
||||
bx509d = 0-/FILE:@objdir@/messages.log
|
||||
|
Reference in New Issue
Block a user