Commit 9393f3d7 authored by jan.koester's avatar jan.koester
Browse files

provision added

parent 8e379da8
Loading
Loading
Loading
Loading
Loading
+2 −0
Changes for plugin/ad/ad.cpp: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -440,6 +440,8 @@ void ActiveDirectory::loadDomains() {
        d.dir = std::make_unique<dsdb::Directory>(*bck, d.meta);
        try {
            d.dir->provision(_AdminPassword);
            if (!_DcHost.empty())
                d.dir->provisionDc(_DcHost);   /* this DC's own account + SPNs */
        } catch (std::exception &ex) {
            std::cerr << "AD: provision '" << e.name << "' failed: " << ex.what() << std::endl;
        }
+7 −0
Changes for src/dsdb/directory.h: 7 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -91,6 +91,13 @@ namespace fenrirds {
             * Administrator without credentials. */
            void provision(const std::string &adminPassword = "");

            /* create this DC's own computer account (CN=<DC>,OU=Domain
             * Controllers) with the SPNs a client needs (host/ cifs/ ldap/)
             * and a random machine password, so the KDC can issue service
             * tickets for it. dcHost is the DC's FQDN (e.g. dc1.example.com);
             * idempotent. Does nothing if dcHost is not within this domain. */
            void provisionDc(const std::string &dcHost);

            /* all entries at/under base matching the scope (0 base,1 one,2 sub) */
            void search(const Dn &base, int scope, const Filter &filter,
                        const std::function<bool(const Entry &)> &sink);
+80 −0
Changes for src/dsdb/provision.cpp: 80 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -232,5 +232,85 @@ void Directory::provision(const std::string &adminPassword) {
    }
}

void Directory::provisionDc(const std::string &dcHost) {
    std::lock_guard<std::mutex> lk(_Mtx);
    ensureFresh();
    if (dcHost.empty())
        return;
    std::string lowHost = dsdb::lower(dcHost);
    std::string suffix = "." + dsdb::lower(_Meta.dnsName);
    if (lowHost.size() <= suffix.size() ||
        lowHost.compare(lowHost.size() - suffix.size(), suffix.size(), suffix) != 0)
        return;   /* this DC is not in this domain */

    std::string shortName = dcHost.substr(0, dcHost.find('.'));
    std::string sam = dsdb::upper(shortName) + "$";
    Dn dn = _Meta.baseDn.child("OU", "Domain Controllers").child("CN", shortName);
    if (findByDnLocked(dn))
        return;   /* already provisioned */

    const Dn &base = _Meta.baseDn;
    Sid domSid = _Meta.sid;
    if (domSid.empty())
        if (Entry *head = findByDnLocked(base)) domSid = head->objectSid;

    uint32_t rid = uint32_t(intValue(findByDnLocked(base)->attrs, "@nextRid", RID_FIRST_FREE));
    /* bump the pool on the head */
    if (Entry *head = findByDnLocked(base)) {
        AttrMap h = head->attrs;
        setInt(h, "@nextRid", rid + 1);
        storeObject(_Backend, head->objectGuid, head->dn, h, objectSecrets(_Backend, head->objectGuid), false);
        head->attrs = h;
    }

    std::string fqdn = dcHost;
    std::string up = dsdb::upper(shortName);
    AttrMap a;
    a["objectClass"] = {"top", "person", "organizationalPerson", "user", "computer"};
    a["objectCategory"] = {"CN=Computer,CN=Schema,CN=Configuration," + base.str()};
    a["objectSid"] = {domSid.withRid(rid).binary()};
    a["sAMAccountName"] = {sam};
    setInt(a, "sAMAccountType", SAM_MACHINE_ACCOUNT);
    setInt(a, "userAccountControl", UAC_SERVER_TRUST | UAC_TRUSTED_FOR_DELEGATION);
    setInt(a, "primaryGroupID", RID_DOMAIN_CONTROLLERS);
    setInt(a, "accountExpires", NEVER);
    a["dNSHostName"] = {fqdn};
    setInt(a, "msDS-SupportedEncryptionTypes", 0x1F);   /* AES256|AES128|RC4|DES */
    a["servicePrincipalName"] = {
        "HOST/" + fqdn, "HOST/" + up,
        "ldap/" + fqdn, "ldap/" + up,
        "ldap/" + fqdn + "/" + _Meta.dnsName,
        "cifs/" + fqdn, "cifs/" + up,
        "RestrictedKrbHost/" + fqdn, "RestrictedKrbHost/" + up,
        "GC/" + fqdn + "/" + _Meta.dnsName,
    };
    a["memberOf"] = {"CN=Domain Controllers,CN=Users," + base.str()};
    a["isCriticalSystemObject"] = {"TRUE"};

    std::string pw = crypto::base64(crypto::randomBytes(24));
    Secrets sec = deriveSecrets(pw, kerberosSalt(PrincipalKind::Computer, _Meta.realm, sam, _Meta.dnsName), 0);
    setInt(a, "msDS-KeyVersionNumber", sec.kvno);

    /* inline create (we hold the lock) */
    uuid::uuid g;
    g.generate();
    a["name"] = {shortName};
    a["cn"] = {shortName};
    a["distinguishedName"] = {dn.str()};
    setInt(a, "instanceType", 4);
    a["whenCreated"] = {generalizedTime(unixFromFileTime(fileTimeNow()))};
    a["whenChanged"] = a["whenCreated"];
    storeObject(_Backend, g, dn, a, sec.serialize(), true);
    Entry e;
    e.objectGuid = g;
    e.dn = dn;
    e.attrs = a;
    e.objectSid = domSid.withRid(rid);
    size_t idx = _Entries.size();
    _Entries.push_back(e);
    addEntryToIndex(_Entries.back(), idx);
    _Revision = _Backend.getRevesion();
}

}
}
+34 −0
Changes for tests/proto/test_directory.cpp: 34 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -121,4 +121,38 @@ TEST(create_modify_delete) {
    unlink((path + ".lock").c_str());
}

TEST(provision_dc_account) {
    std::string path = "/build/fds-dc-" + std::to_string(getpid()) + ".db";
    unlink(path.c_str()); unlink((path + ".lock").c_str());
    {
        AuthBackend backend(fenrirds::File, path, "example.com");
        Directory dir(backend, meta());
        dir.provision();
        dir.provisionDc("dc1.example.com");

        /* the DC machine account exists with its SPNs and a key */
        auto dc = dir.bySamAccountName("DC1$");
        CHECK(dc.has_value());
        CHECK(dc->isClass("computer"));
        CHECK(dc->dn.under(dnFromDomain("example.com").child("OU","Domain Controllers")));

        /* the KDC resolves these SPNs to the DC account */
        CHECK(dir.bySpn("cifs/dc1.example.com").has_value());
        CHECK(dir.bySpn("ldap/dc1.example.com").has_value());
        CHECK(dir.bySpn("HOST/dc1.example.com").has_value());
        /* and it has Kerberos keys to decrypt service tickets */
        Secrets sec = dir.secretsOf(dc->objectGuid);
        CHECK(!sec.empty());
        CHECK(sec.key(fenrirds::crypto::krb5::AES256_CTS_HMAC_SHA1_96) != nullptr);

        /* idempotent */
        dir.provisionDc("dc1.example.com");
        int n = 0;
        Filter f = Filter::equal("sAMAccountName", "DC1$");
        dir.search(dnFromDomain("example.com"), 2, f, [&](const Entry&){ ++n; return true; });
        CHECK_EQ(n, 1);
    }
    unlink(path.c_str()); unlink((path + ".lock").c_str());
}

CHECK_MAIN