From 4fddf3d98635d695fafef3aea7688c7dcd1758e5 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 18 Nov 2025 15:06:58 +0100 Subject: [PATCH] :recycle: Make management key derivable from secret key Still preserves the ability to set management --- backend/scripts/_env | 3 +-- backend/src/app/config.clj | 3 +-- backend/src/app/http/management.clj | 34 +++++++++++++++------------ backend/src/app/http/middleware.clj | 15 +++++++----- backend/src/app/rpc.clj | 10 ++++---- backend/src/app/rpc/commands/demo.clj | 2 +- backend/src/app/rpc/cond.clj | 5 ++-- backend/src/app/setup.clj | 8 +++---- backend/src/app/setup/keys.clj | 4 ++-- 9 files changed, 44 insertions(+), 40 deletions(-) diff --git a/backend/scripts/_env b/backend/scripts/_env index 2709853db6..1e4408efe8 100644 --- a/backend/scripts/_env +++ b/backend/scripts/_env @@ -1,7 +1,6 @@ #!/usr/bin/env bash - -export PENPOT_MANAGEMENT_API_SHARED_KEY=super-secret-management-api-key +export PENPOT_MANAGEMENT_API_KEY=super-secret-management-api-key export PENPOT_SECRET_KEY=super-secret-devenv-key export PENPOT_HOST=devenv diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index 1fc3b0539d..de030f2e11 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -5,7 +5,6 @@ ;; Copyright (c) KALEIDOS INC (ns app.config - "A configuration management." (:refer-clojure :exclude [get]) (:require [app.common.data :as d] @@ -103,7 +102,7 @@ [:http-server-io-threads {:optional true} ::sm/int] [:http-server-max-worker-threads {:optional true} ::sm/int] - [:management-api-shared-key {:optional true} :string] + [:management-api-key {:optional true} :string] [:telemetry-uri {:optional true} :string] [:telemetry-with-taiga {:optional true} ::sm/boolean] ;; DELETE diff --git a/backend/src/app/http/management.clj b/backend/src/app/http/management.clj index 756ca7450c..b32b7f4077 100644 --- a/backend/src/app/http/management.clj +++ b/backend/src/app/http/management.clj @@ -50,23 +50,27 @@ (db/tx-run! cfg handler request)))))}) (defmethod ig/init-key ::routes - [_ cfg] - ["" {:middleware [[mw/shared-key-auth (cf/get :management-api-shared-key)] - [default-system cfg] - [transaction]]} - ["/authenticate" - {:handler authenticate - :allowed-methods #{:post}}] + [_ {:keys [::setup/props] :as cfg}] - ["/get-customer" - {:handler get-customer - :transaction true - :allowed-methods #{:post}}] + (let [management-key (or (cf/get :management-api-key) + (get props :management-key))] - ["/update-customer" - {:handler update-customer - :allowed-methods #{:post} - :transaction true}]]) + ["" {:middleware [[mw/shared-key-auth management-key] + [default-system cfg] + [transaction]]} + ["/authenticate" + {:handler authenticate + :allowed-methods #{:post}}] + + ["/get-customer" + {:handler get-customer + :transaction true + :allowed-methods #{:post}}] + + ["/update-customer" + {:handler update-customer + :allowed-methods #{:post} + :transaction true}]])) ;; ---- HELPERS diff --git a/backend/src/app/http/middleware.clj b/backend/src/app/http/middleware.clj index 1e8ac15039..e7b4b5c953 100644 --- a/backend/src/app/http/middleware.clj +++ b/backend/src/app/http/middleware.clj @@ -16,6 +16,7 @@ [app.http.errors :as errors] [app.tokens :as tokens] [app.util.pointer-map :as pmap] + [buddy.core.codecs :as bc] [cuerdas.core :as str] [yetti.adapter :as yt] [yetti.middleware :as ymw] @@ -243,7 +244,6 @@ (handler request) {::yres/status 405}))))))}) - (defn- wrap-auth [handler decoders] (let [token-re @@ -303,11 +303,14 @@ (defn- wrap-shared-key-auth [handler shared-key] (if shared-key - (fn [request] - (let [key (yreq/get-header request "x-shared-key")] - (if (= key shared-key) - (handler request) - {::yres/status 403}))) + (let [shared-key (if (string? shared-key) + shared-key + (bc/bytes->b64-str shared-key true))] + (fn [request] + (let [key (yreq/get-header request "x-shared-key")] + (if (= key shared-key) + (handler request) + {::yres/status 403})))) (fn [_ _] {::yres/status 403}))) diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 5b7d4a0edd..bd9b8c8af9 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -346,14 +346,16 @@ (assert (valid-methods? (::management-methods params)) "expect valid methods map")) (defmethod ig/init-key ::routes - [_ {:keys [::methods ::management-methods] :as cfg}] + [_ {:keys [::methods ::management-methods ::setup/props] :as cfg}] + + (let [public-uri (cf/get :public-uri) + management-key (or (cf/get :management-api-key) + (get props :management-key))] - (let [public-uri (cf/get :public-uri)] ["/api" - ["/management" ["/methods/:type" - {:middleware [[mw/shared-key-auth (cf/get :management-api-shared-key)] + {:middleware [[mw/shared-key-auth management-key] [session/authz cfg]] :handler (make-rpc-handler management-methods)}] diff --git a/backend/src/app/rpc/commands/demo.clj b/backend/src/app/rpc/commands/demo.clj index e609d7ec79..d4f46e750b 100644 --- a/backend/src/app/rpc/commands/demo.clj +++ b/backend/src/app/rpc/commands/demo.clj @@ -39,7 +39,7 @@ fullname (str "Demo User " sem) password (-> (bn/random-bytes 16) - (bc/bytes->b64u) + (bc/bytes->b64 true) (bc/bytes->str)) params {:email email diff --git a/backend/src/app/rpc/cond.clj b/backend/src/app/rpc/cond.clj index 168b7f0c8a..aeb7f7d99d 100644 --- a/backend/src/app/rpc/cond.clj +++ b/backend/src/app/rpc/cond.clj @@ -39,9 +39,8 @@ (defn- encode [s] (-> s - bh/blake2b-256 - bc/bytes->b64u - bc/bytes->str)) + (bh/blake2b-256) + (bc/bytes->b64-str true))) (defn- fmt-key [s] diff --git a/backend/src/app/setup.clj b/backend/src/app/setup.clj index 4debccdad6..03bed90182 100644 --- a/backend/src/app/setup.clj +++ b/backend/src/app/setup.clj @@ -22,8 +22,7 @@ (defn- generate-random-key [] (-> (bn/random-bytes 64) - (bc/bytes->b64u) - (bc/bytes->str))) + (bc/bytes->b64-str true))) (defn- get-all-props [conn] @@ -85,12 +84,11 @@ (l/warn :hint (str "using autogenerated secret-key, it will change on each restart and will invalidate " "all sessions on each restart, it is highly recommended setting up the " "PENPOT_SECRET_KEY environment variable"))) - (let [secret (or key (generate-random-key))] (-> (get-all-props conn) (assoc :secret-key secret) (assoc :tokens-key (keys/derive secret :salt "tokens")) + (assoc :management-key (keys/derive secret :salt "management")) (update :instance-id handle-instance-id conn (db/read-only? pool))))))) -;; FIXME -(sm/register! ::props :any) +(sm/register! ::props [:map-of :keyword ::sm/any]) diff --git a/backend/src/app/setup/keys.clj b/backend/src/app/setup/keys.clj index bdc5ce45e3..125e1a59bc 100644 --- a/backend/src/app/setup/keys.clj +++ b/backend/src/app/setup/keys.clj @@ -8,13 +8,13 @@ "Keys derivation service." (:refer-clojure :exclude [derive]) (:require - [app.common.spec :as us] [buddy.core.kdf :as bk])) (defn derive "Derive a key from secret-key" [secret-key & {:keys [salt size] :or {size 32}}] - (us/assert! ::us/not-empty-string secret-key) + (assert (string? secret-key) "expect string") + (assert (seq secret-key) "expect string") (let [engine (bk/engine {:key secret-key :salt salt :alg :hkdf