- Java 73.1%
- CSS 9.9%
- JavaScript 6.6%
- HTML 4.3%
- PLpgSQL 4.3%
- Other 1.8%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Actually checked "does this work with mvn quarkus:dev" by installing JDK 21 + Maven standalone (no root, no system changes) and, further, a throwaway local PostgreSQL 18 instance to get a full boot rather than stopping at "no database available". Full details and what was and wasn't covered are in the README's "Kvar innan produktion" section. Three real bugs found and fixed, all pre-existing (not introduced by last round's additions, though three new files had copied the same wrong import): - @Authenticated was imported from jakarta.annotation.security, which doesn't have that annotation at all. The real one is io.quarkus.security.Authenticated. The project would never have compiled before this fix. - CSRF protection was configured in application.properties but quarkus-csrf-reactive (now quarkus-rest-csrf) was never added to pom.xml, so none of that configuration was doing anything. - CurrentUser directly injected the OIDC JsonWebToken, which doesn't exist as a bean when OIDC is disabled (%dev.quarkus.oidc.enabled=false) -- a CDI startup failure in dev mode specifically, unrelated to the database. Changed to Instance<JsonWebToken>, resolved lazily. Also fixed two Panache generics bugs in last round's own new code (MembershipService.invite(), PortalResource.server()): chaining .stream() straight off Membership.list(...) / AuditEvent.find(...) without an explicitly-typed intermediate variable left the generic type defaulting to PanacheEntityBase instead of the real entity type. With a live database, the app now boots fully, Flyway migrates cleanly, the new scheduled jobs run without error, and the Qute templates were confirmed to render correctly in both languages against real view-model data (checked via a temporary local-only test route, never committed). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JWeXEhdkA4nj8BRdjKjMwh |
||
| docs | ||
| src/main | ||
| .gitignore | ||
| compose.yml | ||
| Dockerfile | ||
| pom.xml | ||
| proxmox-setup.sh | ||
| README.md | ||
| setup.sh | ||
ABON-panel — backend v1.0
Kundportal för ABON:s VPS-kunder. Proxmox bakom ett broker-lager, Pocket ID för inloggning.
Stack: Java · Quarkus · PostgreSQL · Flyway (migrationer) · JWT
(via quarkus-oidc mot Pocket ID).
Version 1.0 gör: logga in, se sina VPS:er, start/stop/reboot, brandväggsregler. Inget mer.
Kom igång
# På panel-VPS:en
git clone <repo> /opt/docker/abon-panel && cd /opt/docker/abon-panel
./setup.sh # genererar secrets, skapar .env
vim .env # fyll i OIDC_CLIENT_ID, PROXMOX_*, NEWT_*
docker compose up -d
# På en Proxmox-nod
./proxmox-setup.sh # skapar panel@pve + två tokens
Lokalt: mvn quarkus:dev (OIDC avstängt i dev-profilen).
Var säkerheten sitter
| Fil | Vad den skyddar mot |
|---|---|
auth/AuthorizationService.java |
IDOR — enda vägen till en Service |
proxmox/ProxmoxEndpointGuard.java |
Att VM.Config.Network används till NIC-flytt |
proxmox/ProxmoxBroker.java |
Att vmid kommer från klienten; brandväggslåsningar |
job/JobWorker.java |
Att ett gammalt jobb körs efter indragen behörighet |
audit/AuditService.java |
Ändrad historik — hash-kedja per kund |
V1__init.sql |
UPDATE/DELETE på audit_event, via triggers |
application.properties |
Spoofad X-Forwarded-For, CSRF, tokenlivslängd |
job/JobService.java |
Rate limit; en client som spammar start/stop |
membership/MembershipService.java |
En kund utan ägare; en obekräftad inbjudan som ger åtkomst |
firewall/FirewallReconciliationJob.java |
Att brandväggsdrift går obemärkt förbi |
audit/AuditVerificationJob.java |
Att en bruten audit-kedja går obemärkt förbi |
Fyra beslut värda att förstå innan du ändrar
Proxmox äger tillstånd, panelen äger relationer. VM-status hämtas live
vid varje visning. Går Proxmox inte att nå visas reachable: false — aldrig
gammal data som om den vore aktuell.
Klienten ser aldrig vmid. Den skickar service.id (UUID). vmid slås upp
i databasen efter att ägarskapet är verifierat. En manipulerad request kan
därför inte peka på någon annans VM.
Panelen äger hela brandväggen på VM-nivå. Varje ändring skriver om
<vmid>.fw helt. enable, ipfilter, macfilter och policy_in: DROP
skrivs varje gång och verifieras efteråt. "Av" i kundens gränssnitt betyder
tillåt-allt-regler, inte att anti-spoofing försvinner.
JWT kompletteras med DB-kontroll. Access token lever 5 minuter. Varje destruktiv operation kontrollerar dessutom medlemskap och tjänstestatus mot databasen, både när jobbet köas och i workern innan Proxmox anropas. Läsning kan släpa 5 minuter; att göra något kan den inte.
Vad som medvetet inte finns än
Uppgradering, tillval, backup, Invoice Ninja, webbhotell, mail, Foto Cloud.
Strukturen bär dem — service heter inte server, customer har
invoice_ninja_client_id, statusfältet har SUSPENDED och PENDING_UPGRADE
— men inget av det är byggt.
Suspend sätts för hand i 1.0:
UPDATE service SET status='SUSPENDED', suspended_at=now() WHERE id='...';
Broker vägrar då starta tjänsten, så det räcker som spärr.
Kvar innan produktion
- Rate limiting på strömoperationer (max N per minut per tjänst) —
job/JobService.java, configjob.rate-limit.max-per-minute(default 6/min). - Qute-vyer —
web/PortalResource.java+templates/PortalResource/*.html, static assets underMETA-INF/resources/static/. Read-only rendering; all mutations still go through the existing JSON API from browser-side JS (static/app.js). - Inbjudningsflöde —
membership/MembershipService.java+api/MembershipResource.java(POST /api/customers/{id}/invitations,POST /api/invitations/{token}/accept). No outbound email yet: the invite endpoint returns the token/link for an owner to relay manually. - Medlemshantering (lägg till/ta bort/ändra roll, med minst-en-ägare-regeln) —
same
MembershipResource/MembershipService, enforcesMembershipService.LastOwnerException(409) on demoting/removing the last owner. - Periodisk avstämning brandvägg: panelens DB mot Proxmox —
firewall/FirewallReconciliationJob.java, configfirewall.reconciliation.interval(default 5m). Read-only: logs drift at ERROR level, never auto-corrects. - Schemalagd verifiering av audit-kedjan —
audit/AuditVerificationJob.java, configaudit.verification.interval(default 1h). - PUB/DPA och villkorstext för supportåtkomst — draft in
docs/pub-dpa-support-access.md. Not legal advice, not reviewed by a lawyer — must not be published as-is.
Update: verified end-to-end, including a live boot against a real database. JDK 21 + Maven 3.9.9 and a throwaway PostgreSQL 18 instance were installed standalone for this check (all as the regular user, no root, no system changes, all removed afterwards). Results:
mvn packagebuilds clean.mvn quarkus:dev, pointed at the local Postgres viaDB_HOST, starts fully: Flyway appliesV1__init.sqlto a fresh database with no errors, the app reachesListening on: http://localhost:8080, andGET /q/healthreports the database connection asUP.- The new scheduled jobs ran on their own on startup and completed
without error:
AuditVerificationJob("0 customer chain(s) OK") andFirewallReconciliationJob("0 active services checked, no drift"). - Static assets serve correctly:
/static/app.js,/static/style.css,/static/fonts/fonts.css,/static/img/logotyp.svgall returned 200 with the expected byte sizes. - Protected routes correctly reject unauthenticated requests (
/,/servers,/api/servicesall returned 403, as expected with OIDC disabled in dev and no way to complete a real login there). - The Qute templates render correctly with real data, in both
languages. Using a temporary, local-only, unauthenticated resource
(never committed) that called
PortalResource.Templates.servers(...)and.server(...)directly with fake view-model data: all three renders (server list in English, server list in Swedish, server detail) returned 200 with no exceptions in the log, and the output HTML was inspected directly — headings, the "N servers/server in drift" count line, the language toggle button (correctly offering the other language), the customer switcher dropdown, all four power buttons with correct per-statusdisabledattributes, and the firewall table headers all rendered exactly as intended in the correct language.
Three real, pre-existing bugs were found and fixed along the way — none of them related to this round's additions specifically, they were latent in the codebase and only surfaced once it was actually built:
@Authenticatedwas imported from the wrong package (jakarta.annotation.security.Authenticated, which doesn't exist — the real annotation isio.quarkus.security.Authenticated) acrossAuditResource,MeResource,ServiceResource,SupportResource(pre-existing, from before this round) andMembershipResource,PortalResource,RootResource(new this round, copied the same wrong import). The project would never have compiled before this fix.- CSRF protection was configured but not installed:
application.propertiessetsquarkus.csrf-reactive.*, butquarkus-csrf-reactive(now namedquarkus-rest-csrf) was never added topom.xml, so every one of those settings was silently ignored — confirmed bymvn quarkus:devlogging them as unrecognized. Added the dependency;rest-csrfnow shows up in the "Installed features" list at boot. CurrentUsercould never start in dev mode: it directly@Injects the OIDCJsonWebToken, but%dev.quarkus.oidc.enabled=falsemeans no such bean exists in dev — a CDI validation failure at startup, independent of the database. Changed the injection toInstance<JsonWebToken>, resolved lazily instead of required at boot.
Two Panache generics bugs were also found and fixed in this round's own
new code specifically (MembershipService.invite(),
PortalResource.server()): chaining .stream() straight off
Membership.list(...) / AuditEvent.find(...).list() without first
assigning to an explicitly-typed List<Membership> / List<AuditEvent>
variable left Java unable to infer the generic type, defaulting it to
PanacheEntityBase instead. Fixed by introducing the typed intermediate
variable, matching the pattern already used elsewhere in this codebase
(e.g. AuditResource.list()).
What this check did NOT cover: the actual Proxmox integration (no
real Proxmox cluster reachable from this environment — ProxmoxBroker
calls were never exercised), the actual OIDC login flow against Pocket
ID (dev mode had OIDC disabled; a real login was never completed against
a real IdP), and the CSRF protection's actual behavior against a real
form submission or fetch call. Those still need to be checked against
the real staging/production environment.
Follow-up items these additions surfaced, not yet done
- No outbound email for invitations — the accept link/token is only returned in the API response right now.
- Audit log timestamps in the new UI are rendered via
Instant's defaulttoString()(ISO-8601), not a human-friendly local format. FirewallReconciliationJobandAuditVerificationJobonly log on drift — no alerting integration (email/Slack/etc.) exists yet.