♻️ Make management key derivable from secret key

Still preserves the ability to set management
This commit is contained in:
Andrey Antukh
2025-11-18 15:06:58 +01:00
parent 57aa9a585b
commit 4fddf3d986
9 changed files with 44 additions and 40 deletions

View File

@@ -1,7 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
export PENPOT_MANAGEMENT_API_KEY=super-secret-management-api-key
export PENPOT_MANAGEMENT_API_SHARED_KEY=super-secret-management-api-key
export PENPOT_SECRET_KEY=super-secret-devenv-key export PENPOT_SECRET_KEY=super-secret-devenv-key
export PENPOT_HOST=devenv export PENPOT_HOST=devenv

View File

@@ -5,7 +5,6 @@
;; Copyright (c) KALEIDOS INC ;; Copyright (c) KALEIDOS INC
(ns app.config (ns app.config
"A configuration management."
(:refer-clojure :exclude [get]) (:refer-clojure :exclude [get])
(:require (:require
[app.common.data :as d] [app.common.data :as d]
@@ -103,7 +102,7 @@
[:http-server-io-threads {:optional true} ::sm/int] [:http-server-io-threads {:optional true} ::sm/int]
[:http-server-max-worker-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-uri {:optional true} :string]
[:telemetry-with-taiga {:optional true} ::sm/boolean] ;; DELETE [:telemetry-with-taiga {:optional true} ::sm/boolean] ;; DELETE

View File

@@ -50,8 +50,12 @@
(db/tx-run! cfg handler request)))))}) (db/tx-run! cfg handler request)))))})
(defmethod ig/init-key ::routes (defmethod ig/init-key ::routes
[_ cfg] [_ {:keys [::setup/props] :as cfg}]
["" {:middleware [[mw/shared-key-auth (cf/get :management-api-shared-key)]
(let [management-key (or (cf/get :management-api-key)
(get props :management-key))]
["" {:middleware [[mw/shared-key-auth management-key]
[default-system cfg] [default-system cfg]
[transaction]]} [transaction]]}
["/authenticate" ["/authenticate"
@@ -66,7 +70,7 @@
["/update-customer" ["/update-customer"
{:handler update-customer {:handler update-customer
:allowed-methods #{:post} :allowed-methods #{:post}
:transaction true}]]) :transaction true}]]))
;; ---- HELPERS ;; ---- HELPERS

View File

@@ -16,6 +16,7 @@
[app.http.errors :as errors] [app.http.errors :as errors]
[app.tokens :as tokens] [app.tokens :as tokens]
[app.util.pointer-map :as pmap] [app.util.pointer-map :as pmap]
[buddy.core.codecs :as bc]
[cuerdas.core :as str] [cuerdas.core :as str]
[yetti.adapter :as yt] [yetti.adapter :as yt]
[yetti.middleware :as ymw] [yetti.middleware :as ymw]
@@ -243,7 +244,6 @@
(handler request) (handler request)
{::yres/status 405}))))))}) {::yres/status 405}))))))})
(defn- wrap-auth (defn- wrap-auth
[handler decoders] [handler decoders]
(let [token-re (let [token-re
@@ -303,11 +303,14 @@
(defn- wrap-shared-key-auth (defn- wrap-shared-key-auth
[handler shared-key] [handler shared-key]
(if shared-key (if shared-key
(let [shared-key (if (string? shared-key)
shared-key
(bc/bytes->b64-str shared-key true))]
(fn [request] (fn [request]
(let [key (yreq/get-header request "x-shared-key")] (let [key (yreq/get-header request "x-shared-key")]
(if (= key shared-key) (if (= key shared-key)
(handler request) (handler request)
{::yres/status 403}))) {::yres/status 403}))))
(fn [_ _] (fn [_ _]
{::yres/status 403}))) {::yres/status 403})))

View File

@@ -346,14 +346,16 @@
(assert (valid-methods? (::management-methods params)) "expect valid methods map")) (assert (valid-methods? (::management-methods params)) "expect valid methods map"))
(defmethod ig/init-key ::routes (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" ["/api"
["/management" ["/management"
["/methods/:type" ["/methods/:type"
{:middleware [[mw/shared-key-auth (cf/get :management-api-shared-key)] {:middleware [[mw/shared-key-auth management-key]
[session/authz cfg]] [session/authz cfg]]
:handler (make-rpc-handler management-methods)}] :handler (make-rpc-handler management-methods)}]

View File

@@ -39,7 +39,7 @@
fullname (str "Demo User " sem) fullname (str "Demo User " sem)
password (-> (bn/random-bytes 16) password (-> (bn/random-bytes 16)
(bc/bytes->b64u) (bc/bytes->b64 true)
(bc/bytes->str)) (bc/bytes->str))
params {:email email params {:email email

View File

@@ -39,9 +39,8 @@
(defn- encode (defn- encode
[s] [s]
(-> s (-> s
bh/blake2b-256 (bh/blake2b-256)
bc/bytes->b64u (bc/bytes->b64-str true)))
bc/bytes->str))
(defn- fmt-key (defn- fmt-key
[s] [s]

View File

@@ -22,8 +22,7 @@
(defn- generate-random-key (defn- generate-random-key
[] []
(-> (bn/random-bytes 64) (-> (bn/random-bytes 64)
(bc/bytes->b64u) (bc/bytes->b64-str true)))
(bc/bytes->str)))
(defn- get-all-props (defn- get-all-props
[conn] [conn]
@@ -85,12 +84,11 @@
(l/warn :hint (str "using autogenerated secret-key, it will change on each restart and will invalidate " (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 " "all sessions on each restart, it is highly recommended setting up the "
"PENPOT_SECRET_KEY environment variable"))) "PENPOT_SECRET_KEY environment variable")))
(let [secret (or key (generate-random-key))] (let [secret (or key (generate-random-key))]
(-> (get-all-props conn) (-> (get-all-props conn)
(assoc :secret-key secret) (assoc :secret-key secret)
(assoc :tokens-key (keys/derive secret :salt "tokens")) (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))))))) (update :instance-id handle-instance-id conn (db/read-only? pool)))))))
;; FIXME (sm/register! ::props [:map-of :keyword ::sm/any])
(sm/register! ::props :any)

View File

@@ -8,13 +8,13 @@
"Keys derivation service." "Keys derivation service."
(:refer-clojure :exclude [derive]) (:refer-clojure :exclude [derive])
(:require (:require
[app.common.spec :as us]
[buddy.core.kdf :as bk])) [buddy.core.kdf :as bk]))
(defn derive (defn derive
"Derive a key from secret-key" "Derive a key from secret-key"
[secret-key & {:keys [salt size] :or {size 32}}] [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 (let [engine (bk/engine {:key secret-key
:salt salt :salt salt
:alg :hkdf :alg :hkdf