On Tue, 1 Aug 2023 at 04:35, Ondřej Kuzník <[email protected]> wrote:
>
> On Tue, Aug 01, 2023 at 09:09:43AM +1000, Sean Gallagher wrote:
> > 3) Finally, if the system admin wants to use the TLS layer authentication
> > state to subtly modify access rights, that is also allowed by the RFCs, BUT
> > NOT BY SLAPD.
> >
> > I find slapd's incapacity in the third case to be a bizarre inconsistency.
>
> The ACL subsystem is extensible well beyond this and I find it bizarre that
> you keep ignoring that.

I created a dynacl a while back that does what I think Sean is looking
for: use the SASL_AUTH_EXTERNAL property to allow auth access to
userPassword. My original use case was to get rid of an IP whitelist
and instead use TLS client auth to control what clients can perform a
simple bind, but it can be used for pretty much any access you'd like.

I've attached a simplified version of that dynacl that does away with
instance-specific checks.
/**
 * Author:  David H. Hawes, Jr.
 * Email:   [email protected]
 *
 * dynacl-clientauth
 *
 * This is an OpenLDAP dynamic ACL that will allow access if TLS client
 * authentication with a valid ED service certificate has been performed. Its
 * original intent was to allow access to userPassword for the purpose of simple
 * binding without having to use an IP whitelist. For this to work, the DN of
 * the cert that authenticated must be in the serviceDN attribute of the
 * service, the service must be ACTIVE, and the attribute name and value must
 * match as well.
 *
 * Usage:
 *
 *      access to <what>
 *          by dynacl/clientauth/<attribute>[.exact]=<value>
 *
 * Only "exact" is supported, and can be omitted since it is the default.
 *
 * Example:
 *
 *      access to attrs=userPassword
 *          by dynacl/clientauth/uusid=middleware +x
 *
 * This will allow simple binds to succeed if TLS client authentication has been
 * performed with an active ED service certificate with the uusid "middleware".
 */

#include <portable.h>

#include <ac/string.h>
#include <slap.h>
#include <lutil.h>

#include <sasl/sasl.h>

static int count_entry_cb( Operation *op, SlapReply *rs);

typedef struct clientauth_t {
    struct berval clientauth_name;
    struct berval clientauth_value;
} clientauth_t;

static int
ca_dynacl_mask(
    void                 *priv,
    struct Operation     *op,
    Entry                *target,
    AttributeDescription *desc,
    struct berval        *val,
    int                  nmatch,
    regmatch_t           *matches,
    slap_access_t        *grant,
    slap_access_t        *deny )
{
    clientauth_t *clientauth = (clientauth_t *)priv;
    sasl_conn_t *ctx = op->o_conn->c_sasl_authctx;
    const char *authid = NULL;

    sasl_getprop(ctx, SASL_AUTH_EXTERNAL, (void *)&authid);

    ACL_INVALIDATE(*deny);

    fprintf(stderr, "dynacl-clientauth: entering ca_dynacl_mask\n");
    fprintf(stderr, "dynacl-clientauth: authid = %s\n", authid);
    fprintf(stderr, "dynacl-clientauth: attribute = %s\n", clientauth->clientauth_name.bv_val);
    fprintf(stderr, "dynacl-clientauth: value = %s\n", clientauth->clientauth_value.bv_val);

    if(authid != NULL) {
        /* whatever you need to do to determine access here */
        ACL_LVL_ASSIGN_WRITE(*grant);
    }

    return 0;
}

static int
ca_dynacl_unparse(
    void *priv,
    struct berval *bv )
{
    clientauth_t *clientauth = (clientauth_t *)priv;
    char *ptr;

    bv->bv_len = STRLENOF(" dynacl/clientauth/.exact=") +
        clientauth->clientauth_name.bv_len +
        clientauth->clientauth_value.bv_len;
    bv->bv_val = ch_malloc(bv->bv_len + 1);

    ptr = lutil_strcopy(bv->bv_val, " dynacl/clientauth/");
    ptr = lutil_strncopy(ptr, clientauth->clientauth_name.bv_val, clientauth->clientauth_name.bv_len);
    ptr = lutil_strcopy(ptr, "=");

    ptr = lutil_strncopy(
        ptr, clientauth->clientauth_value.bv_val, clientauth->clientauth_value.bv_len);
    ptr[0] = '\0';

    bv->bv_len = ptr - bv->bv_val;

    return 0;
}

static int
ca_dynacl_destroy(
    void *priv )
{
    clientauth_t *clientauth = (clientauth_t *)priv;

    if (clientauth != NULL) {
        if (!BER_BVISNULL(&clientauth->clientauth_name)) {
            ber_memfree(clientauth->clientauth_name.bv_val);
        }
        if (!BER_BVISNULL(&clientauth->clientauth_value)) {
            ber_memfree(clientauth->clientauth_value.bv_val);
        }
        ch_free(clientauth);
    }

    return 0;
}

static int
ca_dynacl_parse(
        const char   *fname,
        int          lineno,
        const char   *opts,
        slap_style_t style,
        const char   *pattern,
        void         **privp )
{
    clientauth_t *clientauth;

    clientauth = (clientauth_t *)ch_calloc(1, sizeof(clientauth_t));

    if (opts == NULL || opts[0] == '\0') {
        fprintf(stderr, "%s line %d: dynacl-clientauth: no attribute specified.\n", fname, lineno);
        goto cleanup;
    }

    if (pattern == NULL || pattern[0] == '\0') {
        fprintf(stderr, "%s line %d: dynacl-clientauth: no attribute value specified.\n",
            fname, lineno);
        goto cleanup;
    }

    if (style != ACL_STYLE_BASE) {
        fprintf(stderr, "%s line %d: dynacl-clientauth: only exact style is supported.\n",
            fname, lineno);
        goto cleanup;
    }

    ber_str2bv(opts,    0, 1, &clientauth->clientauth_name);
    ber_str2bv(pattern, 0, 1, &clientauth->clientauth_value);

    *privp = (void *)clientauth;
    return 0;

cleanup:
    (void)ca_dynacl_destroy((void *)clientauth);
    return 1;
}

static struct slap_dynacl_t ca_dynacl = {
    "clientauth",
    ca_dynacl_parse,
    ca_dynacl_unparse,
    ca_dynacl_mask,
    ca_dynacl_destroy
};

int
init_module( int argc, char *argv[] )
{
    return slap_dynacl_register( &ca_dynacl );
}

static int count_entry_cb(Operation *op, SlapReply *rs)
{
    int *count;
    if(!op || !rs) return(0);

    if(rs->sr_entry) {
        count = op->o_callback->sc_private;
        (*count)++;
    }

    return 0;
}

Reply via email to