8 Commits
Author SHA1 Message Date
jklein ee9ae8a724 feat!: NetworkPolicy in six more charts, on by default
Install-test / Install-test apache-tika (pull_request) Successful in 1m21s
Install-test / Install-test bentopdf (pull_request) Successful in 1m16s
Install-test / Install-test cyberchef (pull_request) Successful in 1m6s
Install-test / Install-test gotenberg (pull_request) Successful in 1m32s
Install-test / Install-test outline (pull_request) Failing after 1m16s
Install-test / Install-test rss-bridge (pull_request) Successful in 1m1s
Install-test / Install-test searxng (pull_request) Successful in 1m13s
Validate Helm Charts / Install-test (pull_request) Failing after 9m57s
Install-test / Install-test ytdlp-web-player (pull_request) Successful in 1m11s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Resource quantities (pull_request) Failing after 18s
Validate Helm Charts / HTTPRoute match (pull_request) Failing after 13s
Validate Helm Charts / Selector match (pull_request) Failing after 17s
Validate Helm Charts / Bump level (pull_request) Successful in 8s
Validate Helm Charts / YAML scalars (pull_request) Failing after 16s
Validate Helm Charts / NetworkPolicy (pull_request) Failing after 9s
Validate Helm Charts / Build apache-tika (pull_request) Successful in 5s
Validate Helm Charts / Trivy config apache-tika (pull_request) Successful in 13s
Validate Helm Charts / Build bentopdf (pull_request) Successful in 5s
Validate Helm Charts / Build cyberchef (pull_request) Successful in 6s
Validate Helm Charts / Build outline (pull_request) Successful in 5s
Validate Helm Charts / Build template-chart (pull_request) Successful in 5s
Validate Helm Charts / Trivy config outline (pull_request) Failing after 12s
Validate Helm Charts / Trivy config searxng (pull_request) Successful in 15s
Validate Helm Charts / Trivy config template-chart (pull_request) Successful in 14s
Validate Helm Charts / Build gotenberg (pull_request) Successful in 5s
Validate Helm Charts / Build rss-bridge (pull_request) Successful in 5s
Validate Helm Charts / Build searxng (pull_request) Successful in 5s
Validate Helm Charts / Build ytdlp-web-player (pull_request) Successful in 5s
Validate Helm Charts / Trivy config bentopdf (pull_request) Successful in 14s
Validate Helm Charts / Trivy config cyberchef (pull_request) Successful in 13s
Validate Helm Charts / Trivy config gotenberg (pull_request) Successful in 13s
Validate Helm Charts / Trivy config rss-bridge (pull_request) Successful in 14s
Validate Helm Charts / Trivy config ytdlp-web-player (pull_request) Successful in 15s
bentopdf, apache-tika, gotenberg, rss-bridge, searxng, ytdlp-web-player. One
major each, networkPolicy.enabled defaults to true.

Three of them differ from the simple case in ways worth naming:

GOTENBERG gets DNS-only egress as a deliberate, documented trade. It drives a
headless Chromium, and 3000 is its probe port - so it is reachable from the
whole cluster by default, and an egress rule for 80/443 would turn a service
anyone can POST to into a way of fetching arbitrary URLs. The price is the
remote-asset half of HTML conversion: inline content converts, images the page
would have fetched silently do not. That degrades a PDF instead of failing a
pod, so nothing alerts on it, which is why it is in the template header, in
values.yaml and in NOTES.txt, with extraPorts: [80, 443] named as the knowing
opt-in.

RSS-BRIDGE, SEARXNG and YTDLP-WEB-PLAYER get the open internet on their web
ports, because reaching other people's servers IS the application and this
object cannot name a host. Said plainly in each, so none of them reads
stricter than it is. What the cap still buys is real: no other outbound port,
so a compromised pod reaches no database, no mail server and no SSH anywhere.

APACHE-TIKA and GOTENBERG render no Ingress - their consumers are other pods,
in other namespaces here - so neither gets an ingressController value and both
say in NOTES.txt that narrowing probeSources means listing the consumers'
namespaces in allowedNamespaces in the same change, or extraction and
conversion stop.

YTDLP-WEB-PLAYER names the gap this convention has: its oauth2-proxy subchart
renders its own Deployment with its own labels and ships no policy, so with the
proxy enabled the pod that actually faces the Ingress is unrestricted in both
directions. Nothing in this chart can change that - an upstream chart is
consumed as it comes - so it is written down instead.

ci/outline/test-values.yaml gains egress.extraPorts: [5556]. The CI dex runs
there rather than on 443, and Outline fetches the discovery document at boot -
so this is the chart's documented escape hatch used for exactly the case it was
written for, and the one place in the repo where the cap's upgrade risk is
actually exercised.

Part of #5
2026-10-14 14:20:39 +00:00
jklein f530f58b1d docs(rss-bridge): #51 was closed, and the cause was never identified (2.1.1)
Validate Helm Charts / Resource quantities (pull_request) Successful in 15s
Validate Helm Charts / Selector match (pull_request) Successful in 14s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 14s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Build rss-bridge (pull_request) Successful in 8s
Validate Helm Charts / Trivy config rss-bridge (pull_request) Successful in 23s
Install-test / Install-test rss-bridge (pull_request) Successful in 1m6s
Validate Helm Charts / Install-test (pull_request) Successful in 1m6s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 12s
Publish RSS-Bridge Helm Chart / Publish RSS-Bridge Helm Chart (push) Successful in 12s
SonarQube Analysis / SonarQube Analysis (push) Successful in 45s
PR #124 merged at 12:25 and the maintainer closed #51 one second later, on
the strength of that round's green install-test. Three statements that
merged with #124 are stale as a result, and they are stale in the
direction that matters - they describe a failing test and an open issue:

  - rss-bridge/templates/rss-bridge.httproute.yaml and values.yaml said
    the install-test "fails on the Gitea runner (#51)", in the present
    tense, and values.yaml added "and that test is still red".
  - ci/httproute/rss-bridge.yaml said "#51 is not closed on the strength
    of a colour", which was true when written and is now the opposite of
    what happened.

Replaced with what is actually known, and the emphasis moved to the part
that outlives the colour: the test went green on a render byte-identical
to main, which rules the chart out and leaves the runner - and THE CAUSE
WAS NEVER IDENTIFIED. The diagnostic programme in #51 (docker info for
userns and the dind configuration, the same image in a local k3d without
remapping) was not executed. So a recurrence is a fresh finding rather
than a known quirk, and the overlay comment now says so where whoever
sees the red will be reading.

Also carries the wording change that was already on this branch when
#124 merged: the reproduced green is phrased as "every run of that branch
since" rather than pinned to a count, because each commit recording the
count invalidated it - a third green arrived with the push after "two".

Patch bump, per the rule #40 was argued on: a comment in a chart
directory is still a change to that chart, so it is a patch plus a
publish. No template output changes - the render for the fixture values
is byte-identical to the pre-#124 main, which the tests re-confirm.
2026-10-13 10:28:09 +00:00
jklein 7d365c912c docs(rss-bridge): the install-test went green, and the chart is ruled out as the cause
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Resource quantities (pull_request) Successful in 15s
Validate Helm Charts / Selector match (pull_request) Successful in 15s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 16s
Validate Helm Charts / Build rss-bridge (pull_request) Successful in 8s
Validate Helm Charts / Trivy config rss-bridge (pull_request) Successful in 23s
Install-test / Install-test rss-bridge (pull_request) Successful in 1m14s
Validate Helm Charts / Install-test (pull_request) Successful in 1m14s
This chart's install-test had failed on the Gitea runner since the move
(#51): php-fpm as uid 33 unable to read /app/index.php, the startup probe
403ing through its whole 150s budget, the pod never Ready. The chart was
deliberately kept OUT of ci/excluded-charts.txt so that a pass would be
noticed rather than silenced.

It passed, in this PR. Measured from the run: k3d cluster up, helm install
in Ingress mode, pod 1/1 Ready in 22s with 0 restarts, no "Permission
denied" anywhere in the job, and all eight check contexts green.

The chart is ruled out as the cause by a diff rather than by an argument:
the rendered output for the fixture values is BYTE-IDENTICAL to
origin/main - proven in the previous commit to justify the Minor bump - so
the pod spec that succeeded is, line for line, the pod spec that had been
failing. The implicated layer is the runner, which is where #51 had
already placed the burden of proof. The control case arrived for free,
from evidence collected for an unrelated reason.

What this does NOT establish, and the docs say so: that it is repaired.
It is one run, nobody has identified what changed on the runner, and an
intermittent effect needs several runs per side. #51 therefore stays open
and is to be closed on a cause, not on a colour - which was the standing
instruction there before the green arrived.

Corrected accordingly, because shipping documentation that says the test
fails while it demonstrably passed is the half-finished state this repo
tries not to leave behind:

  - README: the httproute-test paragraph, guideline 4's closing passage on
    the last chart of the sweep, and the chart table row.
  - rss-bridge/templates/NOTES.txt: the Gateway branch no longer claims
    the chart has no successful install-test. What remains true, and is
    what it now says, is that the install-test installs the INGRESS mode,
    so the Gateway mode is render-only - the same position as all 21 other
    charts of the sweep rather than a worse one.
  - ci/httproute/rss-bridge.yaml: same correction, plus why the overlay
    stays load-bearing either way.

The wiki carries the same correction in the same round (rss-bridge, CI-CD)
plus a Lessons-Learned entry on the decision that produced the signal: an
excluded test is not a quiet test but a blind one, because you also lose
the moment the fault stops existing.
2026-10-13 10:10:28 +00:00
jklein 45b2d2ea4e feat(rss-bridge): Gateway-API as an alternative to the Ingress (last chart)
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Resource quantities (pull_request) Successful in 17s
Validate Helm Charts / Selector match (pull_request) Successful in 14s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 15s
Validate Helm Charts / Build rss-bridge (pull_request) Successful in 8s
Validate Helm Charts / Trivy config rss-bridge (pull_request) Successful in 24s
Install-test / Install-test rss-bridge (pull_request) Successful in 1m10s
Validate Helm Charts / Install-test (pull_request) Successful in 1m10s
rss-bridge was the twenty-second and last chart of the Gateway-API sweep:
22 charts carried an Ingress template, 21 an HTTPRoute template, and the
difference was exactly this one. It had been held back as frozen by #51 -
a runner defect, not a property of the chart - and that freeze is lifted.

One HTTPRoute carrying ALL THREE hostnames, because nothing differs per
host: every name serves the same Service at "/", measured on the live
Ingress. spec.hostnames is a list, so one object is the whole answer and a
second would repeat one rule. (Unlike homeassistant, whose paths differ per
host, or matrix-synapse, whose two routes want different backends.)

httpRoute.hostnames is its own MANDATORY value, which is a third shape for
this repository and follows from the values rather than from taste. Most
charts fall back to ingress.domain; freesailarr and homeassistant reject a
hostnames key outright, because a chart-global list could not say which app
or host entry it belonged to. This chart has no ingress.domain either, but
its names are unambiguous - and the ingress.hosts list it would otherwise
reuse carries per-host tlsSecretName values that address nothing once TLS
sits on the Gateway's listener. Reusing it would drag a structure whose
other half is meaningless into the new mode.

rss-bridge.tlsGroups is therefore INERT in Gateway mode, and that is named
as a simplification rather than quietly translated: the helper exists only
to group hosts by TLS secret name, and there is nothing left to group. The
required() on ingress.hosts needed no change to become conditional - it
already sits inside the Ingress template behind ingress.enabled - so what
is new is that values.yaml and the schema now say so.

The ingress.enabled-derived-value screen that caught sure, freesailarr and
ytdlp-web-player was run here and came back CLEAN: outside the Ingress
template the only readers of an ingress value are NOTES.txt, which knows
its own mode, and tlsGroups, called only from behind the switch. The app
takes no base URL in its environment, so no "published" helper is needed.

First chart of the sweep whose Gateway branch has NO loss to explain, and
it was measured rather than assumed (read-only kubectl against the live
object): the running Ingress carries the cert-manager annotation plus Helm
and Rancher/Fleet bookkeeping, and not one nginx.ingress.kubernetes.io
annotation. The group-3 screen came back empty, so NOTES.txt says so
positively instead of copying a loss paragraph from another chart.

Also corrects the last occurrence in the repository of the outdated
sentence "charts in this repo deliberately have no dependencies"
(rss-bridge/templates/_helpers.tpl) - dependencies are admissible but
expensive since #214, and ytdlp-web-player, stalwart and paperless-ngx use
subcharts. The reason the helper is duplicated is unchanged, only its
content: sharing it would mean a library chart, a second artefact to
publish, version and pull for about one change a year.

Minor, not major: additive, defaults unchanged, and relaxing a required is
not a tightening. Proven with an EMPTY helm template diff against
origin/main for the defaults (229 lines, byte-identical); the Gateway mode
with its overlay renders the HTTPRoute in place of the Ingress and nothing
else changes. appVersion untouched - 2026-09-30 is the newest date tag the
registry has, everything newer is sha-* and latest.

ci/run-httproute-match-test.sh goes from 56 assertions over 21 charts to
58 over 22. The overlay at ci/httproute/rss-bridge.yaml has to SUPPLY
hostnames, the second in the repository that does (homeassistant's was the
first, for a different reason), because this chart has no fallback.

NOT PROVEN, and stated in the PR as well: that traffic flows. No Gateway
controller runs in CI and no HTTPRoute reaches "Accepted: True" there. For
this chart there is additionally NO successful install-test at all - it
fails on the Gitea runner in either mode (#51, reproduced on two images
while the same chart and image run healthy in the cluster). The chart is
deliberately NOT added to ci/excluded-charts.txt: an exclusion would
silence the signal, and the day the runner is fixed the test goes green by
itself. So the only red contexts expected here are
"Install-test / Install-test rss-bridge" and the collective
"Validate Helm Charts / Install-test".

Fixes #114
Fixes #40
2026-10-13 09:58:11 +00:00
jklein e666cfed6c rss-bridge: render byte counts as plain decimals (1.0.2+up2025.08.05)
Teil von #157.

The null-safe fallback helpers from #99 pass values through fromYaml, which
goes over JSON and therefore parses every number as float64. Go's "%v"
switches float64 to exponential notation from about 1e6 upwards, so a bare
byte count like 1048576 turned into "1.048576e+06" - not a valid Kubernetes
quantity. The suffix check in multiplyQuantity then aborted the render with
  unsupported resource quantity suffix "e+06" in "1.048576e+06"

The second "%v" - the one printing a non-integer product - never reaches that
check and failed silently instead: with a fractional limitFactor it rendered
"1.4999985e+06" into the manifest, which only the API server rejects on apply.

Both call sites now go through a new rss-bridge.formatNumber helper that
forces decimal notation via printf "%.10f" and trims the padding zeros again;
the leading dot in the regex protects "100.0000000000" from collapsing to
"1". Strings such as "512Mi" pass through untouched.

This is the canonical fix from #157, applied verbatim with this chart's
prefix; the round-trip itself is not touched.

Note on this chart specifically: rss-bridge sets explicit memory and
ephemeral-storage limits as chart defaults, and an explicit limit always
wins, so multiplyQuantity is not reachable through this chart's values at
all - the bug was reproduced and the fix verified on a throwaway copy with
those default limits removed. The rendered output for the chart defaults and
for ci/rss-bridge/test-values.yaml is byte-identical to main.

Patch bump: no behavior and no values-schema change.
2026-08-29 01:13:57 +02:00
jklein 7e6b3beed5 rss-bridge: pin the unprivileged-port sysctl, quote env values
Follow-up to the template-chart hardening fixes in #133, which landed
while this branch was open.

The env block now quotes names, values and keyRef fields. Without it a
value like "5" renders as a YAML number and the API server rejects the
whole manifest. RSS-Bridge takes numeric settings that way
(RSSBRIDGE_http_timeout), so this was reachable with the first env entry
anyone adds.

The port-80 question is settled by measurement rather than by the
"it works in k3d" reasoning #133 warns about. Same pod spec in k3d
(k3s v1.35.5), only ip_unprivileged_port_start changed:

  1024, capabilities.drop [ALL]      nginx: bind() to 0.0.0.0:80 failed
                                     (13: Permission denied)
  1024, plus NET_BIND_SERVICE        identical failure - for a non-root
                                     process the capability lands in the
                                     permitted but not the effective
                                     set, so it buys nothing
  0                                  binds, HTTP 200

So the chart cannot buy safety with a capability the way samba does, and
port 80 is not negotiable either: nginx is configured for it inside the
image and the entrypoint's only lever is a sed against a read-only
/etc/nginx. What it can do is stop depending on the node default - the
pod securityContext now pins the sysctl to 0, so the bind no longer
relies on cluster configuration. Verified: the chart-installed pod
reports 0 and answers 200 through the Service.

Worth recording how that failure looks without probes: the entrypoint
backgrounds nginx and then execs php-fpm, so a failed bind leaves a
container reporting Running with no web server at all. Both test pods
above were Running. The probes on /?action=health are what turn it into
a pod that never becomes Ready.

trivy config still reports 0 to act on; the KSV-0117 statement is
updated to the measured reasoning.
2026-08-23 23:39:49 +02:00
jklein e016650f91 rss-bridge: qualify the image registry, except the port-80 finding
Two findings from the Trivy config scan that landed on main in #129.

AVD-KSV-0125 is fixed rather than excepted: writing the image as
docker.io/rssbridge/rss-bridge puts a real registry in the registry
position instead of "rssbridge", which is what ci/trivy/data/ksv0125.yaml
asks for. Verified in k3d that the qualified reference resolves to the
same digest (sha256:0eca99b2...) and still runs.

AVD-KSV-0117 (containerPort below 1024) becomes a scoped, documented
exception. nginx listens on 80 inside the image, and the entrypoint's
only way to change that is a sed against /etc/nginx/sites-enabled/default
which a read-only root filesystem rejects - so the alternative to port 80
is giving up readOnlyRootFilesystem. The premise of the check does not
hold here anyway: the container binds 80 as uid 33 with all capabilities
dropped and no NET_BIND_SERVICE.

trivy config now reports 0 to act on for this chart.
2026-08-23 23:33:23 +02:00
jklein 3fa67d55ef rss-bridge: new chart
Takes over the four loose manifests in cluster-config/rss-bridge/ as a
proper chart, on the current template-chart standard: strict values
schema, null-safe helpers, computed limits, replicas/scaleDown,
checksum/config, NOTES.txt.

Two things deviate from the repo standard on purpose:

- The Ingress models host AND TLS secret name per entry instead of the
  single conventional tls-<release>-<chart>-ingress. The installation
  this replaces has grown three separate certificates, one per host.
  Renaming them would make cert-manager issue three new ones and leave
  each host unreachable until it did - a self-inflicted outage on a
  deployment that has been up for over a year. Entries sharing a secret
  name are grouped into one tls entry, and omitting the name falls back
  to the convention, so consolidating later is a values change.

- Requests and limits are decoupled. The old manifest requested 2Gi of
  ephemeral storage, identical to its limit, reserving two gigabytes for
  a workload measured at 2.2 MB after fetching all five whitelisted
  bridges. The request drops to 64Mi; the limit stays at 2Gi.

Hardening was verified rather than assumed. The image ships no USER and
runs as root by default, but runs fully hardened once its writable paths
are provided: uid/gid 33, readOnlyRootFilesystem, all capabilities
dropped, seccompProfile RuntimeDefault. The writable paths were found by
running the image with a writable rootfs, exercising every bridge and
reading its diff layer - /app/cache, /run, /run/php, /var/lib/nginx.
/tmp did not show up that way, because PHP's opcache creates its lock
file there and unlinks it immediately; it only surfaced when the
hardened container died at startup. Stopping at the first writable path
would have shipped a chart that cannot start.

Probes use the app's own /?action=health, which renders fixed JSON
without touching the cache or any remote site, so neither a scraped site
going down nor an unwritable cache can restart the pod.

Fixes #128
2026-08-23 23:33:23 +02:00