Commit 4d7b062d authored by jan.koester's avatar jan.koester
Browse files

test

parent a1460a7d
Loading
Loading
Loading
Loading
+3 −1
Changes for admin/static/app.js: 3 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -93,7 +93,7 @@ const ProxyPlusAdmin = (() => {
            ? " (" + r.tls_pinned_fingerprints_sha256.length + " gepinnt)"
            : "")
        : "keine";
      tr.innerHTML = `<td>${r.id}</td><td>${r.domain}</td><td>${r.path_prefix}</td><td>${r.upstream}</td><td>${r.upstream_pool}</td><td>${r.required_permission}</td><td>${tlsInfo}</td><td></td>`;
      tr.innerHTML = `<td>${r.id}</td><td>${r.domain}</td><td>${r.path_prefix}</td><td>${r.priority}</td><td>${r.upstream}</td><td>${r.upstream_pool}</td><td>${r.required_permission}</td><td>${tlsInfo}</td><td></td>`;
      addEditButton(tr, () => editRoute(r));
      addDeleteButton(tr, () => deleteRoute(r.id));
      body.appendChild(tr);
@@ -106,6 +106,7 @@ const ProxyPlusAdmin = (() => {
    document.getElementById("route-id").readOnly = true;
    document.getElementById("route-domain").value = r.domain;
    document.getElementById("route-path").value = r.path_prefix;
    document.getElementById("route-priority").value = r.priority;
    document.getElementById("route-upstream").value = r.upstream;
    document.getElementById("route-upstream-pool").value = r.upstream_pool;
    document.getElementById("route-permission").value = r.required_permission;
@@ -404,6 +405,7 @@ const ProxyPlusAdmin = (() => {
        id: document.getElementById("route-id").value,
        domain: document.getElementById("route-domain").value || "*",
        path_prefix: document.getElementById("route-path").value,
        priority: parseInt(document.getElementById("route-priority").value, 10) || 0,
        upstream: document.getElementById("route-upstream").value,
        upstream_pool: document.getElementById("route-upstream-pool").value,
        required_permission: document.getElementById("route-permission").value,
+2 −1
Changes for admin/static/index.html: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -24,12 +24,13 @@
  <table id="status-table"></table>

  <h2>HTTP-Routen</h2>
  <table id="http-routes"><thead><tr><th>ID</th><th>Domain</th><th>Pfad</th><th>Upstream</th><th>Pool</th><th>Berechtigung</th><th>TLS-Pruefung</th><th>Aktionen</th></tr></thead><tbody></tbody></table>
  <table id="http-routes"><thead><tr><th>ID</th><th>Domain</th><th>Pfad</th><th>Prioritaet</th><th>Upstream</th><th>Pool</th><th>Berechtigung</th><th>TLS-Pruefung</th><th>Aktionen</th></tr></thead><tbody></tbody></table>
  <button onclick="ProxyPlusAdmin.showNewRouteForm()">Neue Route</button>
  <div id="new-route-form" hidden>
    <input id="route-id" placeholder="ID">
    <input id="route-domain" placeholder="Domain, * fuer alle">
    <input id="route-path" placeholder="Pfad-Prefix, z.B. /app">
    <input id="route-priority" type="number" step="1" value="0" placeholder="Prioritaet (kleiner = zuerst geprueft, Default 0)">
    <input id="route-upstream" placeholder="Upstream (direkt), genau eine URL, z.B. http://10.0.0.5:8080">
    <input id="route-upstream-pool" placeholder="oder Pool-ID fuer mehrere Ziele (statt Upstream)">
    <input id="route-permission" placeholder="Berechtigung, z.B. http.proxy.use">
+2 −0
Changes for src/api/JsonApiServer.cpp: 2 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -106,6 +106,7 @@ json_object *httpRouteToJson(const store::HttpRouteRecord &r) {
    json_object_object_add(item, "id", json_object_new_string(r.id.c_str()));
    json_object_object_add(item, "domain", json_object_new_string(r.domain.c_str()));
    json_object_object_add(item, "path_prefix", json_object_new_string(r.pathPrefix.c_str()));
    json_object_object_add(item, "priority", json_object_new_int(r.priority));
    json_object_object_add(item, "upstream", json_object_new_string(r.upstream.c_str()));
    json_object_object_add(item, "upstream_pool", json_object_new_string(r.upstreamPool.c_str()));
    json_object_object_add(item, "required_permission",
@@ -124,6 +125,7 @@ std::string httpRouteFromJson(json_object *body, store::HttpRouteRecord &rec) {
    rec.id = getStringField(body, "id");
    rec.domain = getStringField(body, "domain", "*");
    rec.pathPrefix = getStringField(body, "path_prefix");
    rec.priority = getIntField(body, "priority", 0);
    rec.upstream = getStringField(body, "upstream");
    rec.upstreamPool = getStringField(body, "upstream_pool");
    rec.requiredPermission = getStringField(body, "required_permission");
+11 −27
Changes for src/http/HttpProxyServer.cpp: 11 added lines, 27 removed lines.
Original line number Diff line number Diff line
@@ -293,36 +293,20 @@ void HttpProxyController::RequestEvent(libhttppp::HttpRequest &curreq, const int
            if (auto *v = hostHdr->getfirstValue()) requestHost = normalizeHost(v->getvalue());
        }

        // The longer/more specific pathPrefix wins outright, regardless of domain
        // specificity -- e.g. "*" + "/blog" outranks "example.com" + "/" for a request to
        // example.com/blog, since "/blog" is the more specific match a config author clearly
        // meant to carve out of that domain's catch-all. Domain specificity is only a
        // tiebreak between routes whose pathPrefix ties in length (most commonly two
        // catch-all "/" routes, one for a specific domain and one wildcard -- the specific
        // one should win for its own domain there).
        const store::HttpRouteRecord *best = nullptr;
        bool bestIsDomainSpecific = false;
        auto routes = _configStore.listHttpRoutes();
        for (const auto &route : routes) {
            if (!pathPrefixMatches(url, route.pathPrefix)) continue;
            bool wildcard = route.domain.empty() || route.domain == "*";
            if (!wildcard && normalizeHost(route.domain) != requestHost) continue;
            bool domainSpecific = !wildcard;
            bool better = !best || route.pathPrefix.size() > best->pathPrefix.size() ||
                          (route.pathPrefix.size() == best->pathPrefix.size() &&
                           domainSpecific && !bestIsDomainSpecific);
            if (better) {
                best = &route;
                bestIsDomainSpecific = domainSpecific;
            }
        }
        // routes is a local vector of copies; keep 'best' valid by re-finding after the loop
        // is pointless churn -- instead copy the winning record out before routes goes away.
        // Domain is a hard filter, not a tiebreak: a route whose domain doesn't match this
        // request's Host header is never eligible, regardless of how specific its pathPrefix
        // is. Among the routes left, the first match wins -- ConfigStore::listHttpRoutes()
        // returns them in match-priority order (lowest HttpRouteRecord::priority first, id as
        // a tiebreak), so config-defined priority decides ties, not pathPrefix length.
        store::HttpRouteRecord matched;
        bool haveRoute = false;
        if (best) {
            matched = *best;
        for (const auto &route : _configStore.listHttpRoutes()) {
            bool wildcard = route.domain.empty() || route.domain == "*";
            if (!wildcard && normalizeHost(route.domain) != requestHost) continue;
            if (!pathPrefixMatches(url, route.pathPrefix)) continue;
            matched = route;
            haveRoute = true;
            break;
        }

        if (!haveRoute) {
+56 −1
Changes for src/store/ConfigStore.cpp: 56 added lines, 1 removed line.
Original line number Diff line number Diff line
#include "ConfigStore.h"

#include <algorithm>
#include <cstring>
#include <mutex>
#include <stdexcept>
#include <utility>

#include <fcntl.h>
#include <sys/stat.h>
@@ -90,6 +92,8 @@ private:
    size_t _pos = 0;
};

// Deliberately does not touch r.priority -- see the HttpRoutePriority comment in Records.h for
// why that field is persisted as its own section instead of appended here.
void encodeHttpRoute(ByteWriter &w, const HttpRouteRecord &r) {
    w.str(r.id);
    w.str(r.domain);
@@ -116,6 +120,16 @@ HttpRouteRecord decodeHttpRoute(ByteReader &r) {
    return rec;
}

void encodeHttpRoutePriority(ByteWriter &w, const std::string &routeId, int32_t priority) {
    w.str(routeId);
    w.u32(static_cast<uint32_t>(priority));
}
std::pair<std::string, int32_t> decodeHttpRoutePriority(ByteReader &r) {
    std::string routeId = r.str();
    int32_t priority = static_cast<int32_t>(r.u32());
    return {routeId, priority};
}

void encodeSmtpRule(ByteWriter &w, const SmtpRelayRuleRecord &r) {
    w.str(r.id);
    w.str(r.domain);
@@ -417,6 +431,14 @@ void ConfigStore::applyRecord(RecordTable table, RecordOp op, const std::vector<
                _listenerTlsAssignments.erase(r.str());
            }
            break;
        case RecordTable::HttpRoutePriority:
            if (op == RecordOp::Put) {
                auto [routeId, priority] = decodeHttpRoutePriority(r);
                _httpRoutePriorities[routeId] = priority;
            } else {
                _httpRoutePriorities.erase(r.str());
            }
            break;
    }
    ++_version;
}
@@ -427,6 +449,11 @@ void ConfigStore::putHttpRoute(const HttpRouteRecord &rec) {
    encodeHttpRoute(w, rec);
    appendWal(RecordTable::HttpRoute, RecordOp::Put, w.buffer());
    _httpRoutes[rec.id] = rec;

    ByteWriter pw;
    encodeHttpRoutePriority(pw, rec.id, rec.priority);
    appendWal(RecordTable::HttpRoutePriority, RecordOp::Put, pw.buffer());
    _httpRoutePriorities[rec.id] = rec.priority;
}

void ConfigStore::deleteHttpRoute(const std::string &id) {
@@ -435,12 +462,27 @@ void ConfigStore::deleteHttpRoute(const std::string &id) {
    w.str(id);
    appendWal(RecordTable::HttpRoute, RecordOp::Delete, w.buffer());
    _httpRoutes.erase(id);

    ByteWriter pw;
    pw.str(id);
    appendWal(RecordTable::HttpRoutePriority, RecordOp::Delete, pw.buffer());
    _httpRoutePriorities.erase(id);
}

std::vector<HttpRouteRecord> ConfigStore::listHttpRoutes() const {
    std::shared_lock lk(_mutex);
    std::vector<HttpRouteRecord> out;
    for (const auto &[id, rec] : _httpRoutes) out.push_back(rec);
    for (const auto &[id, rec] : _httpRoutes) {
        out.push_back(rec);
        auto pit = _httpRoutePriorities.find(id);
        out.back().priority = (pit != _httpRoutePriorities.end()) ? pit->second : 0;
    }
    // HttpProxyController::RequestEvent picks the first match off this list -- lowest priority
    // first, id as a stable tiebreak (most commonly every route sitting at the default 0).
    std::sort(out.begin(), out.end(), [](const HttpRouteRecord &a, const HttpRouteRecord &b) {
        if (a.priority != b.priority) return a.priority < b.priority;
        return a.id < b.id;
    });
    return out;
}

@@ -659,6 +701,9 @@ std::vector<uint8_t> ConfigStore::snapshot() const {
    w.u32(static_cast<uint32_t>(_listenerTlsAssignments.size()));
    for (const auto &[key, rec] : _listenerTlsAssignments) encodeListenerTlsAssignment(w, rec);

    w.u32(static_cast<uint32_t>(_httpRoutePriorities.size()));
    for (const auto &[routeId, priority] : _httpRoutePriorities) encodeHttpRoutePriority(w, routeId, priority);

    return w.buffer();
}

@@ -680,6 +725,7 @@ bool ConfigStore::applySnapshotIfNewer(const std::vector<uint8_t> &blob) {
    std::unordered_map<std::string, UpstreamPoolRecord> upstreamPools;
    std::unordered_map<std::string, DkimKeyRecord> dkimKeys;
    std::unordered_map<std::string, ListenerTlsAssignmentRecord> listenerTlsAssignments;
    std::unordered_map<std::string, int32_t> httpRoutePriorities;

    uint32_t n = r.u32();
    for (uint32_t i = 0; i < n; ++i) {
@@ -734,6 +780,14 @@ bool ConfigStore::applySnapshotIfNewer(const std::vector<uint8_t> &blob) {
            listenerTlsAssignments[rec.listenerKey] = rec;
        }
    }
    // HTTP route priorities were added after listener TLS assignments; same guard.
    if (!r.atEnd()) {
        n = r.u32();
        for (uint32_t i = 0; i < n; ++i) {
            auto [routeId, priority] = decodeHttpRoutePriority(r);
            httpRoutePriorities[routeId] = priority;
        }
    }

    _httpRoutes = std::move(httpRoutes);
    _smtpRelayRules = std::move(smtpRules);
@@ -743,6 +797,7 @@ bool ConfigStore::applySnapshotIfNewer(const std::vector<uint8_t> &blob) {
    _upstreamPools = std::move(upstreamPools);
    _dkimKeys = std::move(dkimKeys);
    _listenerTlsAssignments = std::move(listenerTlsAssignments);
    _httpRoutePriorities = std::move(httpRoutePriorities);
    _version = incomingVersion;
    return true;
}
Loading