Compare commits

...
1 Commits
Author SHA1 Message Date
Andrey Antukh fa7c8acfa8 🐛 Store audit initiator as plain string for shared-key callers
Shared-key callers (exporter, admin-console) arrive as keywords on auth-key-id, so transit persisted them as ~:exporter while regular traffic stored app. Coerce with d/name at the single origin so every audit and telemetry copy carries a plain string. Adds regression tests for the origin and the push-audit-events path, including caller spoofing precedence. Closes #11628 AI-assisted-by: muse-spark-1.3-contributor
2026-09-10 18:24:03 +00:00
3 changed files with 39 additions and 1 deletions

No files matched your search

+1 -1
View File
@@ -204,7 +204,7 @@
token-id (::actoken/id request)
token-type (::actoken/type request)]
{:external-session-id session-id
:initiator (or key-id "app")
:initiator (or (d/name key-id) "app")
:access-token-id (some-> token-id str)
:access-token-type (some-> token-type str)
:client-event-origin client-event-origin
@@ -35,6 +35,21 @@
"x-forwarded-for" "127.0.0.44"
"x-real-ip" "127.0.0.43"))))
(t/deftest prepare-context-initiator-is-plain-string
;; The initiator must always be a plain string, never a keyword: shared-key
;; authenticated callers (exporter, admin-console) arrive as keywords on
;; :app.http/auth-key-id and transit would persist them as "~:exporter".
(let [base {:headers {"x-forwarded-for" "127.0.0.44"}}]
(t/is (= "app" (:initiator (audit/prepare-context-from-request base))))
(t/is (= "exporter"
(:initiator (audit/prepare-context-from-request
(assoc base :app.http/auth-key-id :exporter)))))
(t/is (= "admin-console"
(:initiator (audit/prepare-context-from-request
(assoc base :app.http/auth-key-id :admin-console)))))
(t/is (string? (:initiator (audit/prepare-context-from-request
(assoc base :app.http/auth-key-id :nexus)))))))
(t/deftest push-events-1
(with-redefs [app.config/flags #{:audit-log}]
(let [prof (th/create-profile* 1 {:is-active true})
@@ -1943,3 +1943,26 @@
(t/is (= "bar" (get-in event [:context :foo])))
(t/is (= (:full cf/version) (get-in event [:context :version])))
(t/is (= "app" (get-in event [:context :initiator]))))))))
(t/deftest push-audit-events-initiator-is-plain-string
;; Shared-key callers (e.g. admin-console) carry :app.http/auth-key-id as a
;; keyword; the stored initiator must be a plain string, and a
;; caller-supplied initiator must never survive (server context wins).
(with-mocks [audit-mock {:target 'app.loggers.audit/submit :return nil}]
(binding [cf/flags #{:audit-log}]
(let [prof (th/create-profile* 1 {:is-active true})
params {::th/type :push-audit-events
:events [{:name "context-test"
:profile-id (:id prof)
:type "action"
:context {:custom-key "custom-val"
:initiator "spoofed"}}]}
params (with-meta params
{::http/request (assoc http-request
::http/auth-key-id :admin-console)})
out (th/management-command! params)]
(t/is (nil? (:error out)))
(let [[_ event] (:call-args @audit-mock)]
(t/is (= "custom-val" (get-in event [:context :custom-key])))
(t/is (= "admin-console" (get-in event [:context :initiator])))
(t/is (string? (get-in event [:context :initiator]))))))))