diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 766b12d..9b1a67b 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -34,22 +34,22 @@ jobs: run: | set -eux mkdir -p dist - BIN="kc-policy-${GOOS}-${GOARCH}" + BIN="mailcloak-${GOOS}-${GOARCH}" go build \ -trimpath \ -ldflags="-s -w -X main.version=${VERSION}" \ - -o "dist/${BIN}" ./cmd/kc-policy + -o "dist/${BIN}" ./cmd/mailcloak tar -C dist -czf "dist/${BIN}.tar.gz" "${BIN}" - cp aliasctl dist/ + cp mailcloakctl dist/ sha256sum "dist/${BIN}.tar.gz" >> dist/checksums.txt - sha256sum "dist/aliasctl" >> dist/checksums.txt + sha256sum "dist/mailcloakctl" >> dist/checksums.txt - name: Upload assets to Gitea release uses: https://gitea.com/actions/gitea-release-action@v1 with: files: | dist/*.tar.gz - dist/aliasctl + dist/mailcloakctl dist/checksums.txt generate_release_notes: true env: diff --git a/Makefile b/Makefile index a5e925e..04908d9 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,14 @@ -BINARY := kc-policy +BINARY := mailcloak BIN_DIR := bin .PHONY: build run test tidy clean install build: - go build -o $(BIN_DIR)/$(BINARY) ./cmd/$(BINARY) + export CGO_ENABLED=0 + go build \ + -trimpath \ + -ldflags="-s -w" \ + -o "$(BIN_DIR)/$(BINARY)" ./cmd/$(BINARY) run: go run ./cmd/$(BINARY) diff --git a/README.md b/README.md index c76aa5b..2ca5142 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# kc-policy +# mailcloak Postfix policy + socketmap daemon that validates recipients/senders against Keycloak and serves a local aliases SQLite database. @@ -9,13 +9,13 @@ Postfix policy + socketmap daemon that validates recipients/senders against Keyc - **Socketmap service**: exposes an `alias` map to Postfix, rewriting alias -> `username@domain`. ## Project layout -- `cmd/kc-policy/` – main package entrypoint -- `internal/kcpolicy/` – daemon sources +- `cmd/mailcloak/` – main package entrypoint +- `internal/mailcloak/` – daemon sources - `go.mod` / `go.sum` – Go module files -- `configs/config.yaml.sample` – sample config to copy to `/etc/kc-policy/config.yaml` -- `configs/openrc-kc-policy` – OpenRC service file +- `configs/config.yaml.sample` – sample config to copy to `/etc/mailcloak/config.yaml` +- `configs/openrc-mailcloak` – OpenRC service file - `db-init.sql` – SQLite schema (also auto-created by the app) -- `aliasctl.py` – CLI helper to manage aliases +- `mailcloakctl` – CLI helper to manage aliases ## Build the binary From the repository root: @@ -40,8 +40,8 @@ make run Copy the sample config and edit it: ```bash -install -d -m 0750 -o root -g postfix /etc/kc-policy -cp configs/config.yaml.sample /etc/kc-policy/config.yaml +install -d -m 0750 -o root -g postfix /etc/mailcloak +cp configs/config.yaml.sample /etc/mailcloak/config.yaml ``` Key settings: @@ -50,12 +50,12 @@ Key settings: - `sqlite.path` is the aliases database path. - `sockets.*` must be under the Postfix chroot (usually `/var/spool/postfix`). -## Alias database +## Mailcloak database You can manage aliases using the helper script: ```bash -./aliasctl.py --db /var/lib/kc-policy/aliases.db add alias@example.com username -./aliasctl.py --db /var/lib/kc-policy/aliases.db list +./mailcloakctl aliases add alias@example.com username +./mailcloakctl aliases list ``` The script creates the schema automatically if missing. @@ -63,21 +63,21 @@ The script creates the schema automatically if missing. ## Postfix integration (example) Policy service (smtpd_recipient_restrictions): ``` -check_policy_service unix:private/kc-policy +check_policy_service unix:private/mailcloak ``` Socketmap (virtual_alias_maps): ``` -socketmap:unix:private/kc-socketmap:alias +socketmap:unix:private/mailcloak-socketmap:alias ``` ## OpenRC Use the provided service file: ```bash -cp configs/openrc-kc-policy /etc/init.d/kc-policy -rc-update add kc-policy default -rc-service kc-policy start +cp configs/openrc-mailcloak /etc/init.d/mailcloak +rc-update add mailcloak default +rc-service mailcloak start ``` ## Notes diff --git a/cmd/kc-policy/main.go b/cmd/mailcloak/main.go similarity index 64% rename from cmd/kc-policy/main.go rename to cmd/mailcloak/main.go index 6f482fe..1a04b74 100644 --- a/cmd/kc-policy/main.go +++ b/cmd/mailcloak/main.go @@ -9,7 +9,7 @@ import ( "syscall" "time" - "kc-policy/internal/kcpolicy" + "mailcloak/internal/mailcloak" ) var version = "dev" @@ -20,43 +20,43 @@ func main() { return } - cfgPath := "/etc/kc-policy/config.yaml" + cfgPath := "/etc/mailcloak/config.yaml" if len(os.Args) >= 2 { cfgPath = os.Args[1] } - cfg, err := kcpolicy.LoadConfig(cfgPath) + cfg, err := mailcloak.LoadConfig(cfgPath) if err != nil { log.Fatalf("config: %v", err) } - db, err := kcpolicy.OpenAliasDB(cfg.SQLite.Path) + db, err := mailcloak.OpenAliasDB(cfg.SQLite.Path) if err != nil { log.Fatalf("sqlite: %v", err) } defer db.Close() - kc := kcpolicy.NewKeycloak(cfg) - cache := kcpolicy.NewCache(time.Duration(cfg.Policy.CacheTTLSeconds) * time.Second) + kc := mailcloak.NewKeycloak(cfg) + cache := mailcloak.NewCache(time.Duration(cfg.Policy.CacheTTLSeconds) * time.Second) ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Start socketmap server go func() { - if err := kcpolicy.RunSocketmap(ctx, cfg, db); err != nil { + if err := mailcloak.RunSocketmap(ctx, cfg, db); err != nil { log.Fatalf("socketmap: %v", err) } }() // Start policy server go func() { - if err := kcpolicy.RunPolicy(ctx, cfg, db, kc, cache); err != nil { + if err := mailcloak.RunPolicy(ctx, cfg, db, kc, cache); err != nil { log.Fatalf("policy: %v", err) } }() - log.Printf("kc-policy started") + log.Printf("mailcloak started") // Handle signals ch := make(chan os.Signal, 2) diff --git a/configs/config.yaml.sample b/configs/config.yaml.sample index 71d2e2d..c2f3286 100644 --- a/configs/config.yaml.sample +++ b/configs/config.yaml.sample @@ -6,7 +6,7 @@ keycloak: # admin API is derived: {base_url}/admin/realms/{realm} sqlite: - path: "/var/lib/kc-policy/aliases.db" + path: "/var/lib/mailcloak/state.db" policy: domain: "" @@ -19,8 +19,8 @@ policy: sockets: # These paths must be inside postfix chroot (/var/spool/postfix) - policy_socket: "/var/spool/postfix/private/kc-policy" - socketmap_socket: "/var/spool/postfix/private/kc-socketmap" + policy_socket: "/var/spool/postfix/private/mailcloak-policy" + socketmap_socket: "/var/spool/postfix/private/mailcloak-socketmap" socket_owner_user: "postfix" socket_owner_group: "postfix" socket_mode: "0660" diff --git a/configs/openrc-kc-policy b/configs/openrc-kc-policy deleted file mode 100644 index 19280ce..0000000 --- a/configs/openrc-kc-policy +++ /dev/null @@ -1,23 +0,0 @@ -#!/sbin/openrc-run - -name="kc-policy" -command="/usr/local/sbin/kc-policy" -command_args="/etc/kc-policy/config.yaml" -command_background="yes" -pidfile="/run/kc-policy.pid" - -depend() { - need net - after postfix -} - -start_pre() { - checkpath -d -m 0750 -o root:postfix /etc/kc-policy - checkpath -d -m 0750 -o root:postfix /var/lib/kc-policy - checkpath -d -m 0755 -o root:root /usr/local/sbin - # sockets dir already exists -} - -stop_post() { - rm -f /var/spool/postfix/private/kc-policy /var/spool/postfix/private/kc-socketmap -} \ No newline at end of file diff --git a/configs/openrc-mailcloak b/configs/openrc-mailcloak new file mode 100644 index 0000000..c7355e8 --- /dev/null +++ b/configs/openrc-mailcloak @@ -0,0 +1,23 @@ +#!/sbin/openrc-run + +name="mailcloak" +command="/usr/local/sbin/mailcloak" +command_args="/etc/mailcloak/config.yaml" +command_background="yes" +pidfile="/run/mailcloak.pid" + +depend() { + need net + after postfix +} + +start_pre() { + checkpath -d -m 0750 -o root:postfix /etc/mailcloak + checkpath -d -m 0750 -o root:postfix /var/lib/mailcloak + checkpath -d -m 0755 -o root:root /usr/local/sbin + # sockets dir already exists +} + +stop_post() { + rm -f /var/spool/postfix/private/mailcloak-policy /var/spool/postfix/private/mailcloak-socketmap +} \ No newline at end of file diff --git a/configs/postfix-main.cf b/configs/postfix-main.cf index fd1c727..5a09085 100644 --- a/configs/postfix-main.cf +++ b/configs/postfix-main.cf @@ -9,7 +9,7 @@ virtual_mailbox_domains = static: virtual_transport = lmtp:unix:private/dovecot-lmtp # Dynamic aliases via socketmap -virtual_alias_maps = socketmap:unix:private/kc-socketmap:alias +virtual_alias_maps = socketmap:unix:private/mailcloak-socketmap:alias # Policy (RCPT existence + sender policy on 587 via master.cf) smtpd_recipient_restrictions = @@ -17,5 +17,5 @@ smtpd_recipient_restrictions = reject_unknown_recipient_domain, permit_sasl_authenticated, reject_unauth_destination, - check_policy_service unix:private/kc-policy, + check_policy_service unix:private/mailcloak-policy, permit diff --git a/configs/postfix-master.cf b/configs/postfix-master.cf index 26b5c2b..baf3e7d 100644 --- a/configs/postfix-master.cf +++ b/configs/postfix-master.cf @@ -2,7 +2,7 @@ # Configuration to add to /etc/postfix/master.cf # --o smtpd_sender_restrictions=check_policy_service unix:private/kc-policy +-o smtpd_sender_restrictions=check_policy_service unix:private/mailcloak-policy -# You can remove `reject_senders_login_mismaych` + `sender_login_maps` -# as this kc-policy will handle it. \ No newline at end of file +# You can remove `reject_senders_login_mismatch` + `sender_login_maps` +# as mailcloak will handle it. \ No newline at end of file diff --git a/db-init.sql b/db-init.sql index c952c6b..fcc5d66 100644 --- a/db-init.sql +++ b/db-init.sql @@ -6,3 +6,18 @@ CREATE TABLE IF NOT EXISTS aliases ( ); CREATE INDEX IF NOT EXISTS idx_aliases_username ON aliases(username); + +CREATE TABLE IF NOT EXISTS apps ( + app_id TEXT PRIMARY KEY, + secret_hash TEXT NOT NULL, + enabled INTEGER NOT NULL DEFAULT 1, + created_at INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS app_from ( + app_id TEXT NOT NULL, + from_addr TEXT NOT NULL, + enabled INTEGER NOT NULL DEFAULT 1, + PRIMARY KEY (app_id, from_addr), + FOREIGN KEY (app_id) REFERENCES apps(app_id) ON DELETE CASCADE +); \ No newline at end of file diff --git a/go.mod b/go.mod index ced8b49..19b9332 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module kc-policy +module mailcloak go 1.22 diff --git a/internal/kcpolicy/config.go b/internal/mailcloak/config.go similarity index 98% rename from internal/kcpolicy/config.go rename to internal/mailcloak/config.go index 2ea98b1..e27dfa7 100644 --- a/internal/kcpolicy/config.go +++ b/internal/mailcloak/config.go @@ -1,4 +1,4 @@ -package kcpolicy +package mailcloak import ( "fmt" diff --git a/internal/kcpolicy/keycloak.go b/internal/mailcloak/keycloak.go similarity index 99% rename from internal/kcpolicy/keycloak.go rename to internal/mailcloak/keycloak.go index c9bc758..dcfb731 100644 --- a/internal/kcpolicy/keycloak.go +++ b/internal/mailcloak/keycloak.go @@ -1,4 +1,4 @@ -package kcpolicy +package mailcloak import ( "context" diff --git a/internal/kcpolicy/policy.go b/internal/mailcloak/policy.go similarity index 99% rename from internal/kcpolicy/policy.go rename to internal/mailcloak/policy.go index 64c14b8..11e0195 100644 --- a/internal/kcpolicy/policy.go +++ b/internal/mailcloak/policy.go @@ -1,4 +1,4 @@ -package kcpolicy +package mailcloak import ( "bufio" diff --git a/internal/kcpolicy/socket_perms.go b/internal/mailcloak/socket_perms.go similarity index 97% rename from internal/kcpolicy/socket_perms.go rename to internal/mailcloak/socket_perms.go index 5deef32..54e9057 100644 --- a/internal/kcpolicy/socket_perms.go +++ b/internal/mailcloak/socket_perms.go @@ -1,4 +1,4 @@ -package kcpolicy +package mailcloak import ( "fmt" diff --git a/internal/kcpolicy/socketmap.go b/internal/mailcloak/socketmap.go similarity index 99% rename from internal/kcpolicy/socketmap.go rename to internal/mailcloak/socketmap.go index 21af2b0..01e0e15 100644 --- a/internal/kcpolicy/socketmap.go +++ b/internal/mailcloak/socketmap.go @@ -1,4 +1,4 @@ -package kcpolicy +package mailcloak import ( "bufio" diff --git a/internal/kcpolicy/sqlite.go b/internal/mailcloak/sqlite.go similarity index 98% rename from internal/kcpolicy/sqlite.go rename to internal/mailcloak/sqlite.go index 26ae180..13e9d70 100644 --- a/internal/kcpolicy/sqlite.go +++ b/internal/mailcloak/sqlite.go @@ -1,4 +1,4 @@ -package kcpolicy +package mailcloak import ( "database/sql" diff --git a/aliasctl b/mailcloakctl similarity index 98% rename from aliasctl rename to mailcloakctl index 8ee8f02..7a06018 100755 --- a/aliasctl +++ b/mailcloakctl @@ -4,7 +4,7 @@ import sqlite3 import time import sys -DEFAULT_DB = "/var/lib/kc-policy/aliases.db" +DEFAULT_DB = "/var/lib/mailcloak/state.db" SCHEMA = """ CREATE TABLE IF NOT EXISTS aliases ( diff --git a/state.db b/state.db new file mode 100644 index 0000000..6438b6f Binary files /dev/null and b/state.db differ