Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 67 additions & 15 deletions server/src/instant/config_edn.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
(:require [clojure.edn :as edn]
[clojure.java.io :as io]
[clojure.spec.alpha :as s]
[clojure.string]
[clojure.walk :as w]
[clojure.tools.logging :as log])
(:import (org.apache.commons.codec.binary Hex)))
Expand Down Expand Up @@ -148,6 +149,53 @@

(def associated-data (.getBytes "config"))

(defn- keys-spec-keys
"Returns the unqualified config keys declared by an `s/keys` spec."
[spec-kw]
(let [opts (apply hash-map (rest (s/form spec-kw)))]
(map (comp keyword name)
(concat (:req-un opts) (:opt-un opts)))))

(def config-keys
"Every config key known to the spec, whether it's required in dev or prod."
(delay (distinct (concat (keys-spec-keys ::config)
(keys-spec-keys ::config-prod)))))

(defn- overridable-key-type
"Classifies a config key as :secret (a `::config-value`, stored obfuscated),
:plain (a bare string), or nil for structured keys we can't represent as a
single env var (keysets, oauth clients, etc.)."
[config-key]
(let [spec-kw (keyword (namespace ::config) (name config-key))]
(when (s/get-spec spec-kw)
(let [form (s/form spec-kw)]
(cond
(= form 'clojure.core/string?) :plain
(= form (s/form ::config-value)) :secret)))))

(defn config-env-var-name
"The env var that overrides `config-key`, e.g. :s3-endpoint ->
INSTANT_CONFIG_S3_ENDPOINT."
[config-key]
(str "INSTANT_CONFIG_"
(-> (name config-key)
(clojure.string/upper-case)
(clojure.string/replace "-" "_"))))

(defn env-overrides
"Reads INSTANT_CONFIG_* env vars for every string-valued config key, so any
such setting in the edn file can be overridden from the environment. Secrets
are obfuscated so they read back like the decrypted config."
[obfuscate]
(reduce (fn [acc k]
(if-let [t (overridable-key-type k)]
(if-let [v (System/getenv (config-env-var-name k))]
(assoc acc k (if (= t :secret) (obfuscate v) v))
acc)
acc))
{}
@config-keys))

(defn decrypted-config
"Given a config edn, decrypts the config and obfsucates the secrets
Takes `obfuscate`, `get-hybrid-decrypt-primitive`, and `hybrid-decrypt`
Expand All @@ -165,18 +213,22 @@
(hybrid-decrypt hybrid
{:ciphertext (Hex/decodeHex ^String hex-string)
:associated-data associated-data}))]
(w/postwalk
(fn [x]
(if-not (and (vector? x)
(keyword? (first x))
(= (namespace (first x))
(namespace ::test)))
x
(case (first x)
::plain (obfuscate (second x))
::encoded (-> ^bytes (decrypt (-> x second :enc))
(String.)
obfuscate)
::encoded-bytes (-> ^bytes (decrypt (-> x second :enc-bytes))
obfuscate))))
(s/conform ::config config-edn))))
(merge
(w/postwalk
(fn [x]
(if-not (and (vector? x)
(keyword? (first x))
(= (namespace (first x))
(namespace ::test)))
x
(case (first x)
::plain (obfuscate (second x))
::encoded (-> ^bytes (decrypt (-> x second :enc))
(String.)
obfuscate)
::encoded-bytes (-> ^bytes (decrypt (-> x second :enc-bytes))
obfuscate))))
(s/conform ::config config-edn))
;; Any string-valued config key can be overridden from the environment
;; via INSTANT_CONFIG_<SNAKE_CASE_KEY>.
(env-overrides obfuscate))))
Comment thread
coderabbitai[bot] marked this conversation as resolved.
16 changes: 16 additions & 0 deletions server/test/instant/config_edn_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@
x)
config)))

(deftest env-override-naming
(is (= "INSTANT_CONFIG_S3_ENDPOINT"
(config-edn/config-env-var-name :s3-endpoint)))
(is (= "INSTANT_CONFIG_POSTMARK_TOKEN"
(config-edn/config-env-var-name :postmark-token))))

(deftest env-overrides-obfuscate-secrets-only
;; With no INSTANT_CONFIG_* env vars set, there's nothing to override.
(is (= {} (config-edn/env-overrides crypt-util/obfuscate)))
;; A string-valued config key is overridable; a structured one is not.
(let [overridable? #(some? (#'config-edn/overridable-key-type %))]
(is (overridable? :s3-endpoint))
(is (overridable? :postmark-token))
(is (not (overridable? :google-oauth-client)))
(is (not (overridable? :aead-keyset)))))

(deftest config-smoketest
(testing "dev config"
;; If this test fails, then there is either something wrong
Expand Down
Loading