Commit cbd11b89 authored by jan.koester's avatar jan.koester
Browse files

test

parent a5dfa6fe
Loading
Loading
Loading
Loading
+21 −7
Changes for docs/ROLES.md: 21 added lines, 7 removed lines.
Original line number Diff line number Diff line
@@ -79,10 +79,24 @@ Service Accounts laufen über dieselben GPOs — `GPOcheck()` funktioniert unabh
Session aus `UserAuth()` oder `ClientAuth()` stammt, solange die zugehörige Gruppen-/Connection-ID
Mitglied der GPO ist.

## Bekannte Lücke

`authdb::client` bietet aktuell **keine** Methode, um eine neue Gruppe anzulegen (`addGroup`
existiert nicht, nur `listGroups()` lesend). Bis authdb das nachrüstet, müssen die
`proxyplus.*`-Gruppen einmalig durch einen authdb-Administrator direkt im Backend angelegt
werden; `scripts/authdb-bootstrap.sh` bricht mit einer klaren Fehlermeldung ab und verweist
hierauf, statt das über einen inoffiziellen Weg zu umgehen.
## Gruppenanlage

`authdb::client::Client::addGroup(gid, name)` legt eine Gruppe an (spiegelt `GPOadd`s Vertrag:
der Aufrufer liefert die UUID, siehe `authdb/src/api.cpp`s `addGroup`-Handler und
`authdb/src/group.cpp`s `Group::create()`). Idempotent über den gleichen Id-Match-Fastpath wie
`addGPO` -- ein erneuter Aufruf mit derselben `gid` gibt einfach `true` zurück, statt auf
`Group::create()`s Name-Kollisionsprüfung zu laufen. `tools/proxyplus-bootstrap.cpp` legt beim
Start alle zehn `proxyplus.*`-Standardrollen-Gruppen aus der Tabelle oben mit festen UUIDs an
(`kGroups`), bevor es die GPOs anlegt und deren `allowedGroupNames` per `listGroups()` auf die
echten Gruppen-UUIDs auflöst.

**Frühere Lücke, jetzt geschlossen (2026-08-07):** vorher bot `authdb::client` keine
Gruppenanlage an (nur lesendes `listGroups()`), sodass die `proxyplus.*`-Gruppen einmalig von
einem authdb-Administrator direkt im Backend angelegt werden mussten. Ergänzt um
`Client::addGroup()` + den serverseitigen `groupadd`-API-Befehl.

**Verbleibende Hürde:** `proxyplus` bindet authdb aktuell über das installierte
`libauthorize-dev`-Paket ein (`/usr/include/authdb/client.h`), nicht direkt gegen den
authdb-Quellbaum. Bis dieses Paket neu gebaut und installiert ist, sieht `proxyplus-bootstrap`
weiterhin die alte, `addGroup`-lose Client-API -- dasselbe Muster wie bei anderen Bibliotheken,
die über ein Debian-Paket statt direkt aus dem Quellbaum eingebunden werden.
+0 −3
Changes for test/CMakeLists.txt: 0 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -19,6 +19,3 @@ add_test(NAME test_jose COMMAND test_jose)
add_executable(test_loadbalancer test_loadbalancer.cpp)
target_link_libraries(test_loadbalancer PRIVATE proxyplus_core)
add_test(NAME test_loadbalancer COMMAND test_loadbalancer)

add_executable(manual_verify_spf manual_verify_spf.cpp)
target_link_libraries(manual_verify_spf PRIVATE proxyplus_core)

test/manual_verify_spf.cpp

deleted100644 → 0
+0 −45
Changes for test/manual_verify_spf.cpp: 0 added lines, 45 removed lines.
Original line number Diff line number Diff line
// Ad-hoc manual verification, not part of the checked-in test suite (SPF evaluation needs live
// DNS against real third-party domains, which would make a ctest-registered test flaky against
// this environment's network and against those domains' policies changing over time -- the
// same reason DkimAdapter::verify()/DmarcAdapter::evaluate() have no ctest coverage either).
#include "../src/security/SpfAdapter.h"

#include <iostream>

using proxyplus::security::SpfAdapter;
using proxyplus::security::SpfResult;

const char *name(SpfResult r) {
    switch (r) {
        case SpfResult::None: return "None";
        case SpfResult::Neutral: return "Neutral";
        case SpfResult::Pass: return "Pass";
        case SpfResult::Fail: return "Fail";
        case SpfResult::SoftFail: return "SoftFail";
        case SpfResult::TempError: return "TempError";
        case SpfResult::PermError: return "PermError";
    }
    return "?";
}

int main() {
    SpfAdapter spf;

    // google.com's record is "v=spf1 include:_spf.google.com ~all" -- an IP with no business
    // being in any of Google's ranges must fail (through the ~all catch-all), and a domain
    // with no SPF record at all must be None.
    std::cout << "google.com vs 203.0.113.1 (documentation range, TEST-NET-3, RFC 5737): "
              << name(spf.check("google.com", "203.0.113.1")) << " (expect SoftFail)\n";

    std::cout << "example.com vs 203.0.113.1 (no SPF record at all): "
              << name(spf.check("example.com", "203.0.113.1")) << " (expect None)\n";

    // A known Google Workspace outbound source range from _spf.google.com's own published
    // "include:_netblocks.google.com" (ip4:172.217.0.0/16 has stayed stable across Google's
    // ranges for years, since it's the core Google IPv4 block) -- exercises ip4 CIDR matching
    // through a real "include:" chain.
    std::cout << "google.com vs 172.217.1.1 (inside Google's own 172.217.0.0/16): "
              << name(spf.check("google.com", "172.217.1.1")) << " (expect Pass)\n";

    return 0;
}
+45 −12
Changes for tools/proxyplus-bootstrap.cpp: 45 added lines, 12 removed lines.
Original line number Diff line number Diff line
// One-shot ProxyPlus/authdb bootstrap helper. Real, compiled tool -- not a shell script
// guessing at JSON payloads -- because authdb's CLI (authdb/cli/authcli.cpp) exposes
// addclient/remclient/editclient/gpoexists but has no "add a GPO" subcommand; only
// authdb::client::Client::GPOadd() (a C++ library call) can create one. See
// docs/ROLES.md's "Bekannte Luecke" for the matching authdb-side gap (no addGroup either --
// the proxyplus.* groups this tool references as GPO members must already exist, created
// by an authdb administrator through whatever means authdb currently offers for that).
// addclient/remclient/editclient/gpoexists but has no "add a GPO"/"add a group" subcommand;
// only authdb::client::Client::GPOadd()/addGroup() (C++ library calls) can create those.
// See docs/ROLES.md's "Gruppenanlage" section for how group/GPO ids stay static across
// reruns (idempotent id-match fast-path) instead of colliding on name.
//
// What this tool does, in order:
//   1. Logs into authdb as an existing admin user (username/password given on argv).
//   2. Creates ProxyPlus's own service Client-Credential (authdb's API-key equivalent,
//      see docs/ARCHITECTURE.md §2) and writes "<data-dir>/service-credential".
//   3. Creates one GPO per permission in docs/ROLES.md's list, with the standard
//      proxyplus.* groups as allowed members, and records permission->GPO-id into
//   3. Creates the standard-role proxyplus.* groups (docs/ROLES.md's role table).
//   4. Creates one GPO per permission in docs/ROLES.md's list, with those groups
//      resolved to their real ids as allowed members, and records permission->GPO-id into
//      ProxyPlus's own binary ConfigStore at <data-dir> (never into authdb itself).
//
// Usage:
@@ -30,6 +30,27 @@

namespace {

struct GroupSpec {
    std::string name;
    // Static id, same reasoning as PermissionSpec::gpoId below.
    std::string groupId;
};

// The standard-role groups from docs/ROLES.md's "Rolle -> authdb-Gruppe" table. Service
// Account has no group (client-credential only, see docs/ARCHITECTURE.md Sz2).
const std::vector<GroupSpec> kGroups = {
    {"proxyplus.super_admin", "9e971dbf-4194-4633-a4b2-62aa1440bead"},
    {"proxyplus.cluster_admin", "9f8a9656-cd77-449e-b7a7-4dbe0e183d2d"},
    {"proxyplus.http_admin", "9a55e711-6b13-4802-b25b-7fca12407489"},
    {"proxyplus.smtp_admin", "b4fceb46-3248-41cc-adc6-1d8fa1d38bcb"},
    {"proxyplus.security_admin", "50f5a682-1c86-48fd-8c1b-c33b219ae5b6"},
    {"proxyplus.user_admin", "77cab5f0-b3a5-4285-9843-051e903e504a"},
    {"proxyplus.auditor", "1f0cc72c-2146-450f-af6a-ba094e9c4f5b"},
    {"proxyplus.readonly_operator", "b5546181-8b5e-48ac-b6e7-3f79fc566e9e"},
    {"proxyplus.http_proxy_user", "b11222ab-7ab8-46fe-920c-9dc8485af97f"},
    {"proxyplus.smtp_relay_user", "1395d6f6-5cf3-4025-bee5-5aeca660f9c4"},
};

struct PermissionSpec {
    std::string permission;
    // Static per-permission GPO id, in the same spirit as authdb's own src/gpo_default.h:
@@ -121,10 +142,23 @@ int main(int argc, char **argv) {
    std::cerr << "created service client '" << serviceClientName << "' (id=" << clientId << ")"
              << std::endl;

    // Create the standard-role groups (idempotent: addGroup's id-match fast-path in
    // authdb's api.cpp returns success if a group with this id already exists).
    for (const auto &g : kGroups) {
        uuid::uuid groupId(g.groupId.c_str());
        try {
            admin.addGroup(groupId, g.name);
        } catch (const std::exception &e) {
            std::cerr << "addGroup(" << g.name << ") failed: " << e.what() << std::endl;
            continue;
        }
        std::cerr << "ensured group '" << g.name << "' (id=" << g.groupId << ")" << std::endl;
    }

    // authdb's wire format takes GPO members as group UUIDs, not names (api.cpp parses each
    // gpomembers entry as uuid::uuid(str)) -- resolve docs/ROLES.md's proxyplus.* group names
    // to their real ids here. Groups not yet created by an authdb admin (see "Bekannte Luecke"
    // above) are dropped from that GPO's members with a warning rather than sent as garbage.
    // to their real ids here. Any group that still failed to create above is dropped from
    // that GPO's members with a warning rather than sent as garbage.
    std::map<std::string, std::string> groupNameToId;
    for (const auto &g : admin.listGroups()) {
        groupNameToId[g.name] = g.gid;
@@ -136,9 +170,8 @@ int main(int argc, char **argv) {
        for (const auto &groupName : spec.allowedGroups) {
            auto it = groupNameToId.find(groupName);
            if (it == groupNameToId.end()) {
                std::cerr << "warning: group '" << groupName << "' does not exist yet in authdb"
                          << " -- omitted from " << spec.permission
                          << "'s GPO members until an authdb admin creates it" << std::endl;
                std::cerr << "warning: group '" << groupName << "' does not exist in authdb"
                          << " -- omitted from " << spec.permission << "'s GPO members" << std::endl;
                continue;
            }
            memberIds.push_back(it->second);