parent
9c338c9b7f
commit
7fac0f1eb9
12 changed files with 1153 additions and 43 deletions
3
deps.edn
3
deps.edn
|
|
@ -86,7 +86,8 @@
|
|||
io.replikativ/hasch {:mvn/version "0.3.7"}
|
||||
com.grammarly/omniconf {:mvn/version "0.4.3"}
|
||||
crispin/crispin {:mvn/version "0.3.8"}
|
||||
org.clojure/data.json {:mvn/version "2.4.0"}}
|
||||
org.clojure/data.json {:mvn/version "2.4.0"}
|
||||
amperity/vault-clj {:mvn/version "1.0.4"}}
|
||||
:classpath-overrides {org.clojure/clojure nil
|
||||
org.clojure/spec.alpha nil
|
||||
org.clojure/core.specs.alpha nil}}
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@
|
|||
java.lang.StringBuilder
|
||||
java.lang.System
|
||||
java.lang.Throwable
|
||||
;; java.lang.UnsupportedOperationException
|
||||
java.math.BigDecimal
|
||||
java.math.BigInteger
|
||||
java.math.MathContext
|
||||
|
|
@ -206,6 +207,8 @@
|
|||
java.security.SecureRandom
|
||||
java.sql.Date
|
||||
java.text.ParseException
|
||||
;; adds about 200kb, same functionality provided by java.time:
|
||||
;; java.text.SimpleDateFormat
|
||||
~@(when features/java-time?
|
||||
`[java.time.format.DateTimeFormatter
|
||||
java.time.Clock
|
||||
|
|
@ -396,6 +399,49 @@
|
|||
|
||||
(def class-map (gen-class-map))
|
||||
|
||||
(def imports
|
||||
'{Appendable java.lang.Appendable
|
||||
ArithmeticException java.lang.ArithmeticException
|
||||
AssertionError java.lang.AssertionError
|
||||
BigDecimal java.math.BigDecimal
|
||||
BigInteger java.math.BigInteger
|
||||
Boolean java.lang.Boolean
|
||||
Byte java.lang.Byte
|
||||
Character java.lang.Character
|
||||
CharSequence java.lang.CharSequence
|
||||
Class java.lang.Class
|
||||
ClassNotFoundException java.lang.ClassNotFoundException
|
||||
Comparable java.lang.Comparable
|
||||
Double java.lang.Double
|
||||
Exception java.lang.Exception
|
||||
IndexOutOfBoundsException java.lang.IndexOutOfBoundsException
|
||||
IllegalArgumentException java.lang.IllegalArgumentException
|
||||
IllegalStateException java.lang.IllegalStateException
|
||||
Integer java.lang.Integer
|
||||
InterruptedException java.lang.InterruptedException
|
||||
Iterable java.lang.Iterable
|
||||
File java.io.File
|
||||
Float java.lang.Float
|
||||
Long java.lang.Long
|
||||
Math java.lang.Math
|
||||
NullPointerException java.lang.NullPointerException
|
||||
Number java.lang.Number
|
||||
NumberFormatException java.lang.NumberFormatException
|
||||
Object java.lang.Object
|
||||
Runtime java.lang.Runtime
|
||||
RuntimeException java.lang.RuntimeException
|
||||
Process java.lang.Process
|
||||
ProcessBuilder java.lang.ProcessBuilder
|
||||
Short java.lang.Short
|
||||
StackTraceElement java.lang.StackTraceElement
|
||||
String java.lang.String
|
||||
StringBuilder java.lang.StringBuilder
|
||||
System java.lang.System
|
||||
Thread java.lang.Thread
|
||||
Throwable java.lang.Throwable
|
||||
;; UnsupportedOperationException java.lang.UnsupportedOperationException
|
||||
})
|
||||
|
||||
(defn reflection-file-entries []
|
||||
(let [entries (vec (for [c (sort (:all classes))
|
||||
:let [class-name (str c)]]
|
||||
|
|
|
|||
|
|
@ -415,47 +415,6 @@ Use bb run --help to show this help output.
|
|||
'selmer.validator
|
||||
@(resolve 'babashka.impl.selmer/selmer-validator-namespace))))
|
||||
|
||||
(def imports
|
||||
'{Appendable java.lang.Appendable
|
||||
ArithmeticException java.lang.ArithmeticException
|
||||
AssertionError java.lang.AssertionError
|
||||
BigDecimal java.math.BigDecimal
|
||||
BigInteger java.math.BigInteger
|
||||
Boolean java.lang.Boolean
|
||||
Byte java.lang.Byte
|
||||
Character java.lang.Character
|
||||
CharSequence java.lang.CharSequence
|
||||
Class java.lang.Class
|
||||
ClassNotFoundException java.lang.ClassNotFoundException
|
||||
Comparable java.lang.Comparable
|
||||
Double java.lang.Double
|
||||
Exception java.lang.Exception
|
||||
IndexOutOfBoundsException java.lang.IndexOutOfBoundsException
|
||||
IllegalArgumentException java.lang.IllegalArgumentException
|
||||
IllegalStateException java.lang.IllegalStateException
|
||||
Integer java.lang.Integer
|
||||
InterruptedException java.lang.InterruptedException
|
||||
Iterable java.lang.Iterable
|
||||
File java.io.File
|
||||
Float java.lang.Float
|
||||
Long java.lang.Long
|
||||
Math java.lang.Math
|
||||
NullPointerException java.lang.NullPointerException
|
||||
Number java.lang.Number
|
||||
NumberFormatException java.lang.NumberFormatException
|
||||
Object java.lang.Object
|
||||
Runtime java.lang.Runtime
|
||||
RuntimeException java.lang.RuntimeException
|
||||
Process java.lang.Process
|
||||
ProcessBuilder java.lang.ProcessBuilder
|
||||
Short java.lang.Short
|
||||
StackTraceElement java.lang.StackTraceElement
|
||||
String java.lang.String
|
||||
StringBuilder java.lang.StringBuilder
|
||||
System java.lang.System
|
||||
Thread java.lang.Thread
|
||||
Throwable java.lang.Throwable})
|
||||
|
||||
(def edn-readers (cond-> {}
|
||||
features/yaml?
|
||||
(assoc 'ordered/map @(resolve 'flatland.ordered.map/ordered-map))))
|
||||
|
|
@ -746,7 +705,7 @@ Use bb run --help to show this help output.
|
|||
:env env
|
||||
:features #{:bb :clj}
|
||||
:classes classes/class-map
|
||||
:imports imports
|
||||
:imports classes/imports
|
||||
:load-fn load-fn
|
||||
:uberscript uberscript
|
||||
:readers core/data-readers
|
||||
|
|
|
|||
|
|
@ -237,6 +237,17 @@
|
|||
(test-namespaces 'clojure.data.json-test
|
||||
'clojure.data.json-test-suite-test)
|
||||
|
||||
(test-namespaces
|
||||
;; TODO: env tests don't work because envoy lib isn't compatible with bb
|
||||
#_'vault.env-test
|
||||
'vault.lease-test
|
||||
'vault.client.http-test
|
||||
;; TODO:
|
||||
;; failing tests in the following namespaces:
|
||||
#_'vault.client.mock-test
|
||||
#_'vault.secrets.kvv1-test
|
||||
#_'vault.secrets.kvv2-test)
|
||||
|
||||
;;;; final exit code
|
||||
|
||||
(let [{:keys [:test :fail :error] :as m} @status]
|
||||
|
|
|
|||
198
test-resources/lib_tests/vault/client/http_test.clj
Normal file
198
test-resources/lib_tests/vault/client/http_test.clj
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
(ns vault.client.http-test
|
||||
(:require
|
||||
[clojure.test :refer [deftest testing is]]
|
||||
[vault.authenticate :as authenticate]
|
||||
[vault.client.api-util :as api-util]
|
||||
[vault.client.http :refer [http-client]]
|
||||
[vault.core :as vault]
|
||||
[vault.secrets.kvv1 :as vault-kvv1]))
|
||||
|
||||
|
||||
(def example-url "https://vault.example.com")
|
||||
|
||||
|
||||
(deftest http-client-instantiation
|
||||
(is (thrown? IllegalArgumentException
|
||||
(http-client nil)))
|
||||
(is (thrown? IllegalArgumentException
|
||||
(http-client :foo)))
|
||||
(is (instance? vault.client.http.HTTPClient (http-client example-url))))
|
||||
|
||||
|
||||
(deftest http-read-checks
|
||||
(let [client (http-client example-url)]
|
||||
(is (thrown? IllegalArgumentException
|
||||
(vault-kvv1/read-secret client nil))
|
||||
"should throw an exception on non-string path")
|
||||
(is (thrown? RuntimeException
|
||||
(vault-kvv1/read-secret client "secret/foo/bar"))
|
||||
"should throw an exception on unauthenticated client")))
|
||||
|
||||
|
||||
(deftest app-role
|
||||
(let [api-endpoint (str example-url "/v1/auth/approle/login")
|
||||
client (http-client example-url)
|
||||
connection-attempt (atom nil)]
|
||||
(with-redefs [api-util/do-api-request (fn [_method url _req]
|
||||
(reset! connection-attempt url))
|
||||
authenticate/api-auth! (constantly nil)]
|
||||
(vault/authenticate! client :app-role {:secret-id "secret"
|
||||
:role-id "role-id"})
|
||||
(is (= @connection-attempt api-endpoint)
|
||||
(str "should attempt to auth with: " api-endpoint)))))
|
||||
|
||||
|
||||
(deftest authenticate-via-k8s
|
||||
(testing "When a token file is available"
|
||||
(let [client (http-client example-url)
|
||||
api-requests (atom [])
|
||||
api-auths (atom [])]
|
||||
(with-redefs [api-util/do-api-request (fn [& args]
|
||||
(swap! api-requests conj args)
|
||||
:do-api-request-response)
|
||||
authenticate/api-auth! (fn [& args]
|
||||
(swap! api-auths conj args)
|
||||
:api-auth!-response)]
|
||||
(vault/authenticate! client :k8s {:jwt "fake-jwt-goes-here"
|
||||
:role "my-role"})
|
||||
(is (= [[:post
|
||||
(str example-url "/v1/auth/kubernetes/login")
|
||||
{:form-params {:jwt "fake-jwt-goes-here" :role "my-role"}
|
||||
:content-type :json
|
||||
:accept :json}]]
|
||||
@api-requests))
|
||||
(is (= [[(str "Kubernetes auth role=my-role")
|
||||
(:auth client)
|
||||
:do-api-request-response]]
|
||||
@api-auths)))))
|
||||
(testing "When no jwt is specified"
|
||||
(let [client (http-client example-url)
|
||||
api-requests (atom [])
|
||||
api-auths (atom [])]
|
||||
(with-redefs [api-util/do-api-request (fn [& args]
|
||||
(swap! api-requests conj args))
|
||||
authenticate/api-auth! (fn [& args]
|
||||
(swap! api-auths conj args))]
|
||||
(is (thrown? IllegalArgumentException
|
||||
(vault/authenticate! client :k8s {:role "my-role"})))
|
||||
(is (empty? @api-requests))
|
||||
(is (empty? @api-auths)))))
|
||||
(testing "When no role is specified"
|
||||
(let [client (http-client example-url)
|
||||
api-requests (atom [])
|
||||
api-auths (atom [])]
|
||||
(with-redefs [api-util/do-api-request (fn [& args]
|
||||
(swap! api-requests conj args))
|
||||
authenticate/api-auth! (fn [& args]
|
||||
(swap! api-auths conj args))]
|
||||
(is (thrown? IllegalArgumentException
|
||||
(vault/authenticate! client :k8s {:jwt "fake-jwt-goes-here"})))
|
||||
(is (empty? @api-requests))
|
||||
(is (empty? @api-auths))))))
|
||||
|
||||
|
||||
(deftest authenticate-via-aws
|
||||
(testing "When all parameters are specified"
|
||||
(let [client (http-client example-url)
|
||||
api-requests (atom [])
|
||||
api-auths (atom [])]
|
||||
(with-redefs [api-util/do-api-request (fn [& args]
|
||||
(swap! api-requests conj args)
|
||||
:do-api-request-response)
|
||||
authenticate/api-auth! (fn [& args]
|
||||
(swap! api-auths conj args)
|
||||
:api-auth!-response)]
|
||||
(vault/authenticate! client :aws-iam {:role "my-role"
|
||||
:http-request-method "POST"
|
||||
:request-url "fake.sts.com"
|
||||
:request-body "FakeAction&Version=1"
|
||||
:request-headers "{'foo':'bar'}"})
|
||||
(is (= [[:post
|
||||
(str example-url "/v1/auth/aws/login")
|
||||
{:form-params {:iam_http_request_method "POST"
|
||||
:iam_request_url "fake.sts.com"
|
||||
:iam_request_body "FakeAction&Version=1"
|
||||
:iam_request_headers "{'foo':'bar'}"
|
||||
:role "my-role"}
|
||||
:content-type :json
|
||||
:accept :json}]]
|
||||
@api-requests))
|
||||
(is (= [["AWS auth role=my-role"
|
||||
(:auth client)
|
||||
:do-api-request-response]]
|
||||
@api-auths)))))
|
||||
(testing "When no http-request-method is specified"
|
||||
(let [client (http-client example-url)
|
||||
api-requests (atom [])
|
||||
api-auths (atom [])]
|
||||
(with-redefs [api-util/do-api-request (fn [& args]
|
||||
(swap! api-requests conj args))
|
||||
authenticate/api-auth! (fn [& args]
|
||||
(swap! api-auths conj args))]
|
||||
(is (thrown? IllegalArgumentException
|
||||
(vault/authenticate! client :aws-iam {:role "my-role"
|
||||
:request-url "fake.sts.com"
|
||||
:request-body "FakeAction&Version=1"
|
||||
:request-headers "{'foo':'bar'}"})))
|
||||
(is (empty? @api-requests))
|
||||
(is (empty? @api-auths)))))
|
||||
(testing "When no request-url is specified"
|
||||
(let [client (http-client example-url)
|
||||
api-requests (atom [])
|
||||
api-auths (atom [])]
|
||||
(with-redefs [api-util/do-api-request (fn [& args]
|
||||
(swap! api-requests conj args))
|
||||
authenticate/api-auth! (fn [& args]
|
||||
(swap! api-auths conj args))]
|
||||
(is (thrown? IllegalArgumentException
|
||||
(vault/authenticate! client :aws-iam {:role "my-role"
|
||||
:http-request-method "POST"
|
||||
:request-body "FakeAction&Version=1"
|
||||
:request-headers "{'foo':'bar'}"})))
|
||||
(is (empty? @api-requests))
|
||||
(is (empty? @api-auths)))))
|
||||
(testing "When no request-body is specified"
|
||||
(let [client (http-client example-url)
|
||||
api-requests (atom [])
|
||||
api-auths (atom [])]
|
||||
(with-redefs [api-util/do-api-request (fn [& args]
|
||||
(swap! api-requests conj args))
|
||||
authenticate/api-auth! (fn [& args]
|
||||
(swap! api-auths conj args))]
|
||||
(is (thrown? IllegalArgumentException
|
||||
(vault/authenticate! client :aws-iam {:role "my-role"
|
||||
:http-request-method "POST"
|
||||
:request-url "fake.sts.com"
|
||||
:request-headers "{'foo':'bar'}"})))
|
||||
(is (empty? @api-requests))
|
||||
(is (empty? @api-auths)))))
|
||||
(testing "When no request-headers is specified"
|
||||
(let [client (http-client example-url)
|
||||
api-requests (atom [])
|
||||
api-auths (atom [])]
|
||||
(with-redefs [api-util/do-api-request (fn [& args]
|
||||
(swap! api-requests conj args))
|
||||
authenticate/api-auth! (fn [& args]
|
||||
(swap! api-auths conj args))]
|
||||
(is (thrown? IllegalArgumentException
|
||||
(vault/authenticate! client :aws-iam {:role "my-role"
|
||||
:http-request-method "POST"
|
||||
:request-url "fake.sts.com"
|
||||
:request-body "FakeAction&Version=1"})))
|
||||
(is (empty? @api-requests))
|
||||
(is (empty? @api-auths)))))
|
||||
(testing "When no role is specified"
|
||||
(let [client (http-client example-url)
|
||||
api-requests (atom [])
|
||||
api-auths (atom [])]
|
||||
(with-redefs [api-util/do-api-request (fn [& args]
|
||||
(swap! api-requests conj args))
|
||||
authenticate/api-auth! (fn [& args]
|
||||
(swap! api-auths conj args))]
|
||||
(is (thrown? IllegalArgumentException
|
||||
(vault/authenticate! client :aws-iam {:http-request-method "POST"
|
||||
:request-url "fake.sts.com"
|
||||
:request-body "FakeAction&Version=1"
|
||||
:request-headers "{'foo':'bar'}"})))
|
||||
(is (empty? @api-requests))
|
||||
(is (empty? @api-auths))))))
|
||||
74
test-resources/lib_tests/vault/client/mock_test.clj
Normal file
74
test-resources/lib_tests/vault/client/mock_test.clj
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
(ns vault.client.mock-test
|
||||
(:require
|
||||
[clojure.string :as str]
|
||||
[clojure.test :refer [deftest testing is]]
|
||||
[vault.core :as vault])
|
||||
(:import
|
||||
(clojure.lang
|
||||
ExceptionInfo)))
|
||||
|
||||
|
||||
(defn mock-client-authenticated
|
||||
"A mock vault client using the secrets found in the given path, defaults to `vault/client/secret-fixture-logical.edn`"
|
||||
([path]
|
||||
(let [client (vault/new-client (str "mock:" path))]
|
||||
(vault/authenticate! client :token "fake-token")
|
||||
client))
|
||||
([]
|
||||
(mock-client-authenticated "vault/client/secret-fixture-logical.edn")))
|
||||
|
||||
|
||||
(deftest create-token!-test
|
||||
(testing "The return value of create-token is correct when not wrapped"
|
||||
(let [result (vault/create-token! (mock-client-authenticated) {:no-default-policy true})]
|
||||
(is (= ["root"] (:policies result)))
|
||||
(is (= false (:renewable result)))
|
||||
(is (= "" (:entity-id result)))
|
||||
(is (= ["root"] (:token-policies result)))
|
||||
(is (not (str/blank? (:accessor result))))
|
||||
(is (= 0 (:lease-duration result)))
|
||||
(is (= "service" (:token-type result)))
|
||||
(is (= false (:orphan result)))
|
||||
(is (not (str/blank? (:client-token result))))
|
||||
(is (contains? result :metadata))))
|
||||
(testing "The return value of create-token is correct when not wrapped and some options are specified"
|
||||
(let [result (vault/create-token! (mock-client-authenticated) {:policies ["hello" "goodbye"]
|
||||
:ttl "7d"})]
|
||||
(is (= ["default" "hello" "goodbye"] (:policies result)))
|
||||
(is (= false (:renewable result)))
|
||||
(is (= "" (:entity-id result)))
|
||||
(is (= ["default" "hello" "goodbye"] (:token-policies result)))
|
||||
(is (not (str/blank? (:accessor result))))
|
||||
(is (= 604800 (:lease-duration result)))
|
||||
(is (= "service" (:token-type result)))
|
||||
(is (= false (:orphan result)))
|
||||
(is (not (str/blank? (:client-token result))))
|
||||
(is (contains? result :metadata))))
|
||||
(testing "The client throws a helpful error for debugging if ttl is incorrectly formatted"
|
||||
(is (thrown-with-msg? ExceptionInfo
|
||||
#"Mock Client doesn't recognize format of ttl"
|
||||
(vault/create-token! (mock-client-authenticated) {:ttl "BLT"}))))
|
||||
(testing "The return value of create-token is correct when not wrapped and some less common options are specified"
|
||||
(let [result (vault/create-token! (mock-client-authenticated) {:policies ["hello" "goodbye"]
|
||||
:ttl "10s"
|
||||
:no-parent true
|
||||
:no-default-policy true
|
||||
:renewable true})]
|
||||
(is (= ["hello" "goodbye"] (:policies result)))
|
||||
(is (= true (:renewable result)))
|
||||
(is (= "" (:entity-id result)))
|
||||
(is (= ["hello" "goodbye"] (:token-policies result)))
|
||||
(is (not (str/blank? (:accessor result))))
|
||||
(is (= 10 (:lease-duration result)))
|
||||
(is (= "service" (:token-type result)))
|
||||
(is (= true (:orphan result)))
|
||||
(is (not (str/blank? (:client-token result))))
|
||||
(is (contains? result :metadata))))
|
||||
(testing "The return value of create-token is correct when wrapped"
|
||||
(let [result (vault/create-token! (mock-client-authenticated) {:wrap-ttl "2h"})]
|
||||
(is (not (str/blank? (:token result))))
|
||||
(is (not (str/blank? (:accessor result))))
|
||||
(is (= 7200 (:ttl result)))
|
||||
(is (not (str/blank? (:creation-time result))))
|
||||
(is (= "auth/token/create" (:creation-path result)))
|
||||
(is (not (str/blank? (:wrapped-accessor result)))))))
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
{"identities" {:batman "Bruce Wayne"
|
||||
:captain-marvel "Carol Danvers"}}
|
||||
33
test-resources/lib_tests/vault/env_test.clj
Normal file
33
test-resources/lib_tests/vault/env_test.clj
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
(ns vault.env-test
|
||||
(:require
|
||||
[clojure.test :refer [deftest is]]
|
||||
[vault.client.mock :refer [mock-client]]
|
||||
[vault.env :as venv]))
|
||||
|
||||
|
||||
(deftest uri-resolution
|
||||
(let [client (mock-client {"some/path" {:id "foo"}})]
|
||||
(is (thrown? Exception (venv/resolve-uri nil "vault:some/path#id"))
|
||||
"resolution without client should throw")
|
||||
(is (thrown? Exception (venv/resolve-uri client "vault:some/path#nope"))
|
||||
"resoultion of nil secret should throw")
|
||||
(is (= "foo" (venv/resolve-uri client "vault:some/path#id")))))
|
||||
|
||||
|
||||
(deftest env-loading
|
||||
(let [client (mock-client {"secret/foo" {:thing 123}
|
||||
"secret/bar" {:id "abc"}})
|
||||
env {:a "12345"
|
||||
:b "vault:secret/foo#thing"
|
||||
:c "vault:secret/bar#id"}]
|
||||
(is (identical? env (venv/load! client env nil))
|
||||
"resolution without whitelisted secrets returns env unchanged")
|
||||
(is (= {:a "12345", :b 123, :c "abc"}
|
||||
(venv/load! client env #{:a :b :c}))
|
||||
"resolution allows direct passthrough")
|
||||
(is (= {:a "12345", :b 123, :c "vault:secret/bar#id"}
|
||||
(venv/load! client env #{:b}))
|
||||
"resolution does not touch non-whitelisted vars")
|
||||
(is (= {:a "12345", :b 123, :c "abc"}
|
||||
(venv/load! client env #{:b :c :d}))
|
||||
"resolution ignores missing whitelisted vars")))
|
||||
98
test-resources/lib_tests/vault/lease_test.clj
Normal file
98
test-resources/lib_tests/vault/lease_test.clj
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
(ns vault.lease-test
|
||||
(:require
|
||||
[clojure.test :refer [deftest is]]
|
||||
[vault.lease :as lease])
|
||||
(:import
|
||||
java.time.Instant))
|
||||
|
||||
|
||||
(defmacro with-time
|
||||
"Evaluates the given body of forms with `vault.lease/now` rebound to always
|
||||
give the result `t`."
|
||||
[t & body]
|
||||
`(with-redefs [vault.lease/now (constantly (Instant/ofEpochMilli ~t))]
|
||||
~@body))
|
||||
|
||||
|
||||
(deftest missing-info
|
||||
(let [c (lease/new-store)]
|
||||
(is (nil? (lease/lookup c :foo))
|
||||
"lookup of unstored key should return nil")
|
||||
(is (nil? (lease/update! c nil))
|
||||
"storing nil should return nil")
|
||||
(is (nil? (lease/lookup c :foo))
|
||||
"lookup of nil store should return nil")))
|
||||
|
||||
|
||||
(deftest secret-expiry
|
||||
(let [c (lease/new-store)]
|
||||
(with-time 1000
|
||||
(is (= {:path "foo/bar"
|
||||
:data {:bar "baz"}
|
||||
:lease-id "foo/bar/12345"
|
||||
:lease-duration 100
|
||||
:renewable true
|
||||
:vault.lease/issued (Instant/ofEpochMilli 1000)
|
||||
:vault.lease/expiry (Instant/ofEpochMilli 101000)}
|
||||
(lease/update! c {:path "foo/bar"
|
||||
:lease-id "foo/bar/12345"
|
||||
:lease-duration 100
|
||||
:renewable true
|
||||
:data {:bar "baz"}}))
|
||||
"storing secret info should return data structure"))
|
||||
(with-time 50000
|
||||
(is (= {:path "foo/bar"
|
||||
:data {:bar "baz"}
|
||||
:lease-id "foo/bar/12345"
|
||||
:lease-duration 100
|
||||
:renewable true
|
||||
:vault.lease/issued (Instant/ofEpochMilli 1000)
|
||||
:vault.lease/expiry (Instant/ofEpochMilli 101000)}
|
||||
(lease/lookup c "foo/bar"))
|
||||
"lookup of stored secret within expiry should return data structure"))
|
||||
(with-time 101001
|
||||
(is (lease/expired? (lease/lookup c "foo/bar"))
|
||||
"lookup of stored secret after expiry should return nil"))))
|
||||
|
||||
|
||||
(deftest lease-filtering
|
||||
(let [c (lease/new-store)
|
||||
the-lease {:path "foo/bar"
|
||||
:lease-id "foo/bar/12345"
|
||||
:lease-duration 100
|
||||
:renewable true
|
||||
:vault.lease/renew true
|
||||
:vault.lease/rotate true
|
||||
:vault.lease/issued (Instant/ofEpochMilli 1000)
|
||||
:vault.lease/expiry (Instant/ofEpochMilli 101000)}]
|
||||
(with-time 1000
|
||||
(lease/update! c {:path "foo/bar"
|
||||
:data {:bar "baz"}
|
||||
:lease-id "foo/bar/12345"
|
||||
:lease-duration 100
|
||||
:renewable true
|
||||
:renew true
|
||||
:rotate true}))
|
||||
(with-time 101001
|
||||
(is (= [the-lease] (lease/list-leases c))
|
||||
"Basic lease listing should work, and the data should match.")
|
||||
(is (= [the-lease] (lease/rotatable-leases c 0))
|
||||
"Expired but rotatable lease should be considered rotatable"))
|
||||
(with-time 100000
|
||||
(is (= [the-lease] (lease/renewable-leases c 2)),
|
||||
"Renewable leases should be listed when not expired yet.")
|
||||
(is (empty? (lease/renewable-leases c 1)),
|
||||
"Renewable leases should not be listed when outside the given window.")
|
||||
(is (empty? (lease/rotatable-leases c 0))
|
||||
"Non-expired, renewable leases should not be considered for rotation."))))
|
||||
|
||||
|
||||
(deftest secret-invalidation
|
||||
(let [c (lease/new-store)]
|
||||
(is (some? (lease/update! c {:path "foo/bar"
|
||||
:data {:baz "qux"}
|
||||
:lease-id "foo/bar/12345"})))
|
||||
(is (some? (lease/lookup c "foo/bar")))
|
||||
(is (nil? (lease/remove-path! c "foo/bar")))
|
||||
(is (nil? (lease/lookup c "foo/bar"))
|
||||
"lookup of invalidated secret should return nil")))
|
||||
183
test-resources/lib_tests/vault/secrets/kvv1_test.clj
Normal file
183
test-resources/lib_tests/vault/secrets/kvv1_test.clj
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
(ns vault.secrets.kvv1-test
|
||||
(:require
|
||||
[cheshire.core :as json]
|
||||
[clojure.test :refer [is testing deftest]]
|
||||
[org.httpkit.client :as http]
|
||||
[vault.client.http :as http-client]
|
||||
[vault.client.mock-test :as mock-test]
|
||||
[vault.core :as vault]
|
||||
[vault.secrets.kvv1 :as vault-kvv1])
|
||||
(:import
|
||||
(clojure.lang
|
||||
ExceptionInfo)))
|
||||
|
||||
|
||||
;; -------- HTTP Client -------------------------------------------------------
|
||||
|
||||
(deftest list-secrets-test
|
||||
(let [path "path/passed/in"
|
||||
token-passed-in "fake-token"
|
||||
vault-url "https://vault.example.amperity.com"
|
||||
client (http-client/http-client vault-url)
|
||||
response {:auth nil
|
||||
:data {:keys ["foo" "foo/"]}
|
||||
:lease_duration 2764800
|
||||
:lease-id ""
|
||||
:renewable false}]
|
||||
(vault/authenticate! client :token token-passed-in)
|
||||
(testing "List secrets has correct response and sends correct request"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :get (:method req)))
|
||||
(is (= (str vault-url "/v1/" path) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(is (true? (-> req :query-params :list)))
|
||||
(atom {:body response}))]
|
||||
(is (= ["foo" "foo/"]
|
||||
(vault-kvv1/list-secrets client path)))))))
|
||||
|
||||
|
||||
(deftest read-secret-test
|
||||
(let [lookup-response-valid-path (json/generate-string {:auth nil
|
||||
:data {:foo "bar"
|
||||
:ttl "1h"}
|
||||
:lease_duration 3600
|
||||
:lease_id ""
|
||||
:renewable false})
|
||||
path-passed-in "path/passed/in"
|
||||
path-passed-in2 "path/passed/in2"
|
||||
token-passed-in "fake-token"
|
||||
vault-url "https://vault.example.amperity.com"
|
||||
client (http-client/http-client vault-url)]
|
||||
(vault/authenticate! client :token token-passed-in)
|
||||
(testing "Read secrets sends correct request and responds correctly if secret is successfully located"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :get (:method req)))
|
||||
(is (= (str vault-url "/v1/" path-passed-in) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(atom {:body lookup-response-valid-path}))]
|
||||
(is (= {:foo "bar" :ttl "1h"} (vault-kvv1/read-secret client path-passed-in)))))
|
||||
(testing "Read secrets sends correct request and responds correctly if no secret is found"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= (str vault-url "/v1/" path-passed-in2) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(throw (ex-info "not found" {:error [] :status 404})))]
|
||||
(try
|
||||
(vault-kvv1/read-secret client path-passed-in2)
|
||||
(catch ExceptionInfo e
|
||||
(is (= {:errors nil
|
||||
:status 404
|
||||
:type :vault.client.api-util/api-error}
|
||||
(ex-data e)))))))))
|
||||
|
||||
|
||||
(deftest write-secret-test
|
||||
(let [create-success (json/generate-string {:data {:created_time "2018-03-22T02:24:06.945319214Z"
|
||||
:deletion_time ""
|
||||
:destroyed false
|
||||
:version 1}})
|
||||
write-data {:foo "bar"
|
||||
:zip "zap"}
|
||||
path-passed-in "path/passed/in"
|
||||
token-passed-in "fake-token"
|
||||
vault-url "https://vault.example.amperity.com"
|
||||
client (http-client/http-client vault-url)]
|
||||
(vault/authenticate! client :token token-passed-in)
|
||||
(testing "Write secrets sends correct request and returns true upon success"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :post (:method req)))
|
||||
(is (= (str vault-url "/v1/" path-passed-in) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(is (= write-data (:form-params req)))
|
||||
(atom {:body create-success
|
||||
:status 204}))]
|
||||
(is (true? (vault-kvv1/write-secret! client path-passed-in write-data)))))
|
||||
(testing "Write secrets sends correct request and returns false upon failure"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :post (:method req)))
|
||||
(is (= (str vault-url "/v1/" path-passed-in) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(is (= write-data
|
||||
(:form-params req)))
|
||||
(atom {:errors []
|
||||
:status 400}))]
|
||||
(is (false? (vault-kvv1/write-secret! client path-passed-in write-data)))))))
|
||||
|
||||
|
||||
(deftest delete-secret-test
|
||||
(let [path-passed-in "path/passed/in"
|
||||
token-passed-in "fake-token"
|
||||
vault-url "https://vault.example.amperity.com"
|
||||
client (http-client/http-client vault-url)]
|
||||
(vault/authenticate! client :token "fake-token")
|
||||
(testing "Delete secret returns correctly upon success, and sends correct request"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :delete (:method req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(is (= (str vault-url "/v1/" path-passed-in) (:url req)))
|
||||
(atom {:status 204}))]
|
||||
(is (true? (vault/delete-secret! client path-passed-in)))))
|
||||
(testing "Delete secret returns correctly upon failure, and sends correct request"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :delete (:method req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(is (= (str vault-url "/v1/" path-passed-in) (:url req)))
|
||||
(atom {:status 404}))]
|
||||
(is (false? (vault/delete-secret! client path-passed-in)))))))
|
||||
|
||||
|
||||
;; -------- Mock Client -------------------------------------------------------
|
||||
|
||||
(deftest mock-client-test
|
||||
(testing "Mock client can correctly read values it was initialized with"
|
||||
(is (= {:batman "Bruce Wayne"
|
||||
:captain-marvel "Carol Danvers"}
|
||||
(vault-kvv1/read-secret (mock-test/mock-client-authenticated) "identities"))))
|
||||
(testing "Mock client correctly responds with a 404 to non-existent paths"
|
||||
(is (thrown-with-msg? ExceptionInfo #"No such secret: hello"
|
||||
(vault-kvv1/read-secret (mock-test/mock-client-authenticated) "hello")))
|
||||
(is (thrown-with-msg? ExceptionInfo #"No such secret: identities"
|
||||
(vault-kvv1/read-secret (vault/new-client "mock:-") "identities"))))
|
||||
(testing "Mock client can write/update and read data"
|
||||
(let [client (mock-test/mock-client-authenticated)]
|
||||
(is (thrown-with-msg? ExceptionInfo #"No such secret: hello"
|
||||
(vault-kvv1/read-secret client "hello")))
|
||||
(is (true? (vault-kvv1/write-secret! client "hello" {:and-i-say "goodbye"})))
|
||||
(is (true? (vault-kvv1/write-secret! client "identities" {:intersect "Chuck"})))
|
||||
(is (= {:and-i-say "goodbye"}
|
||||
(vault-kvv1/read-secret client "hello")))
|
||||
(is (= {:intersect "Chuck"}
|
||||
(vault-kvv1/read-secret client "identities")))))
|
||||
(testing "Mock client can list secrets"
|
||||
(let [client (mock-test/mock-client-authenticated)]
|
||||
(is (empty? (vault-kvv1/list-secrets client "hello")))
|
||||
(is (true? (vault-kvv1/write-secret! client "hello" {:and-i-say "goodbye"})))
|
||||
(is (true? (vault-kvv1/write-secret! client "identities" {:intersect "Chuck"})))
|
||||
(is (= ["identities" "hello"] (into [] (vault-kvv1/list-secrets client ""))))
|
||||
(is (= ["identities"] (into [] (vault-kvv1/list-secrets client "identities"))))))
|
||||
(testing "Mock client can delete secrets"
|
||||
(let [client (mock-test/mock-client-authenticated)]
|
||||
(is (true? (vault-kvv1/write-secret! client "hello" {:and-i-say "goodbye"})))
|
||||
(is (= {:and-i-say "goodbye"}
|
||||
(vault-kvv1/read-secret client "hello")))
|
||||
(is (= {:batman "Bruce Wayne"
|
||||
:captain-marvel "Carol Danvers"}
|
||||
(vault-kvv1/read-secret client "identities")))
|
||||
;; delete them
|
||||
(is (true? (vault-kvv1/delete-secret! client "hello")))
|
||||
(is (true? (vault-kvv1/delete-secret! client "identities")))
|
||||
(is (thrown? ExceptionInfo (vault-kvv1/read-secret client "hello")))
|
||||
(is (thrown? ExceptionInfo (vault-kvv1/read-secret client "identities"))))))
|
||||
493
test-resources/lib_tests/vault/secrets/kvv2_test.clj
Normal file
493
test-resources/lib_tests/vault/secrets/kvv2_test.clj
Normal file
|
|
@ -0,0 +1,493 @@
|
|||
(ns vault.secrets.kvv2-test
|
||||
(:require
|
||||
[cheshire.core :as json]
|
||||
[clojure.test :refer [testing deftest is]]
|
||||
[org.httpkit.client :as http]
|
||||
[vault.client.api-util :as api-util]
|
||||
[vault.client.http :as http-client]
|
||||
[vault.client.mock-test :as mock-test]
|
||||
[vault.core :as vault]
|
||||
[vault.secrets.kvv2 :as vault-kvv2])
|
||||
(:import
|
||||
(clojure.lang
|
||||
ExceptionInfo)))
|
||||
|
||||
|
||||
(deftest list-secrets-test
|
||||
(let [path "path/passed/in"
|
||||
token-passed-in "fake-token"
|
||||
vault-url "https://vault.example.amperity.com"
|
||||
client (http-client/http-client vault-url)
|
||||
response {:auth nil
|
||||
:data {:keys ["foo" "foo/"]}
|
||||
:lease_duration 2764800
|
||||
:lease_id ""
|
||||
:renewable false}]
|
||||
(vault/authenticate! client :token token-passed-in)
|
||||
(testing "List secrets has correct response and sends correct request"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :get (:method req)))
|
||||
(is (= (str vault-url "/v1/listmount/metadata/" path) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(is (true? (-> req :query-params :list)))
|
||||
(atom {:body response}))]
|
||||
(is (= ["foo" "foo/"]
|
||||
(vault-kvv2/list-secrets client "listmount" path)))))))
|
||||
|
||||
|
||||
(deftest write-config!-test
|
||||
(let [mount "mount"
|
||||
token-passed-in "fake-token"
|
||||
vault-url "https://vault.example.amperity.com"
|
||||
client (http-client/http-client vault-url)
|
||||
new-config-kebab {:max-versions 5
|
||||
:cas-required false
|
||||
:delete-version-after "3h25m19s"}
|
||||
new-config-snake {:max_versions 5
|
||||
:cas_required false
|
||||
:delete_version_after "3h25m19s"}]
|
||||
(vault/authenticate! client :token token-passed-in)
|
||||
(testing "Write config sends correct request and returns true on valid call"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :post (:method req)))
|
||||
(is (= (str vault-url "/v1/" mount "/config") (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(is (= new-config-snake (:form-params req)))
|
||||
(atom {:status 204}))]
|
||||
(is (true? (vault-kvv2/write-config! client mount new-config-kebab)))))))
|
||||
|
||||
|
||||
(deftest read-config-test
|
||||
(let [config {:max-versions 5
|
||||
:cas-required false
|
||||
:delete-version-after "3h25m19s"}
|
||||
body-str (json/generate-string {:data (api-util/snakeify-keys config)})
|
||||
mount "mount"
|
||||
token-passed-in "fake-token"
|
||||
vault-url "https://vault.example.amperity.com"
|
||||
client (http-client/http-client vault-url)]
|
||||
(vault/authenticate! client :token token-passed-in)
|
||||
(testing "Read config sends correct request and returns the config with valid call"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :get (:method req)))
|
||||
(is (= (str vault-url "/v1/" mount "/config") (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(atom {:body body-str}))]
|
||||
(is (= config (vault-kvv2/read-config client mount)))))))
|
||||
|
||||
|
||||
(deftest read-test
|
||||
(let [lookup-response-valid-path (json/generate-string {:data {:data {:foo "bar"}
|
||||
:metadata {:created_time "2018-03-22T02:24:06.945319214Z"
|
||||
:deletion_time ""
|
||||
:destroyed false
|
||||
:version 1}}})
|
||||
mount "mount"
|
||||
path-passed-in "path/passed/in"
|
||||
token-passed-in "fake-token"
|
||||
vault-url "https://vault.example.amperity.com"
|
||||
client (http-client/http-client vault-url)]
|
||||
(vault/authenticate! client :token token-passed-in)
|
||||
(testing "Read secrets sends correct request and responds correctly if secret is successfully located"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :get (:method req)))
|
||||
(is (= (str vault-url "/v1/" mount "/data/" path-passed-in) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(atom {:body lookup-response-valid-path}))]
|
||||
(is (= {:foo "bar"} (vault-kvv2/read-secret client mount path-passed-in)))))
|
||||
(testing "Read secrets sends correct request and responds correctly if secret with version is successfully located"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :get (:method req)))
|
||||
(is (= (str vault-url "/v1/" mount "/data/" path-passed-in) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(is (= {"version" 3} (:query-params req)))
|
||||
(atom {:body lookup-response-valid-path}))]
|
||||
(is (= {:foo "bar"} (vault-kvv2/read-secret client mount path-passed-in {:version 3 :force-read true})))))
|
||||
(testing "Read secrets sends correct request and responds correctly if no secret is found"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :get (:method req)))
|
||||
(is (= (str vault-url "/v1/" mount "/data/different/path") (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(throw (ex-info "not found" {:errors [] :status 404 :type :vault.client.api-util/api-error})))]
|
||||
(try
|
||||
(is (= {:default-val :is-here}
|
||||
(vault-kvv2/read-secret
|
||||
client
|
||||
mount
|
||||
"different/path"
|
||||
{:not-found {:default-val :is-here}})))
|
||||
|
||||
(vault-kvv2/read-secret client mount "different/path")
|
||||
(is false)
|
||||
(catch ExceptionInfo e
|
||||
(is (= {:errors nil
|
||||
:status 404
|
||||
:type ::api-util/api-error}
|
||||
(ex-data e)))))))))
|
||||
|
||||
|
||||
(deftest write!-test
|
||||
(let [create-success {:data {:created_time "2018-03-22T02:24:06.945319214Z"
|
||||
:deletion_time ""
|
||||
:destroyed false
|
||||
:version 1}}
|
||||
write-data {:foo "bar"
|
||||
:zip "zap"}
|
||||
mount "mount"
|
||||
path-passed-in "path/passed/in"
|
||||
token-passed-in "fake-token"
|
||||
vault-url "https://vault.example.amperity.com"
|
||||
client (http-client/http-client vault-url)]
|
||||
(vault/authenticate! client :token token-passed-in)
|
||||
(testing "Write secrets sends correct request and returns true upon success"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(is (= :post (:method req)))
|
||||
(if (= (str vault-url "/v1/" mount "/metadata/" path-passed-in) (:url req))
|
||||
(do (is (= {} (:form-params req)))
|
||||
(atom {:errors []
|
||||
:status 200}))
|
||||
(do (is (= (str vault-url "/v1/" mount "/data/" path-passed-in) (:url req)))
|
||||
(is (= {:data write-data}
|
||||
(:form-params req)))
|
||||
(atom {:body create-success
|
||||
:status 200}))))]
|
||||
(is (= (:data create-success) (vault-kvv2/write-secret! client mount path-passed-in write-data)))))
|
||||
(testing "Write secrets sends correct request and returns false upon failure"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(is (= :post (:method req)))
|
||||
(if (= (str vault-url "/v1/" mount "/metadata/other-path") (:url req))
|
||||
(do (is (= {} (:form-params req)))
|
||||
(atom {:errors []
|
||||
:status 200}))
|
||||
(do (is (= (str vault-url "/v1/" mount "/data/other-path") (:url req)))
|
||||
(is (= {:data write-data}
|
||||
(:form-params req)))
|
||||
(atom {:errors []
|
||||
:status 500}))))]
|
||||
(is (false? (vault-kvv2/write-secret! client mount "other-path" write-data)))))))
|
||||
|
||||
|
||||
(deftest delete-test
|
||||
(let [mount "mount"
|
||||
path-passed-in "path/passed/in"
|
||||
token-passed-in "fake-token"
|
||||
vault-url "https://vault.example.amperity.com"
|
||||
client (http-client/http-client vault-url)]
|
||||
(vault/authenticate! client :token token-passed-in)
|
||||
(testing "delete secrets send correct request and returns true upon success when no versions passed in"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :delete (:method req)))
|
||||
(is (= (str vault-url "/v1/" mount "/data/" path-passed-in) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(atom {:status 204}))]
|
||||
(is (true? (vault-kvv2/delete-secret! client mount path-passed-in))
|
||||
(is (true? (vault-kvv2/delete-secret! client mount path-passed-in []))))
|
||||
(testing "delete secrets send correct request and returns false upon failure when no versions passed in"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :delete (:method req)))
|
||||
(is (= (str vault-url "/v1/" mount "/data/" path-passed-in) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(atom {:status 404}))]
|
||||
(is (false? (vault-kvv2/delete-secret! client mount path-passed-in)))))
|
||||
(testing "delete secrets send correct request and returns true upon success when multiple versions passed in"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :post (:method req)))
|
||||
(is (= (str vault-url "/v1/" mount "/delete/" path-passed-in) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(is (= {:versions [12 14 147]} (:form-params req)))
|
||||
(atom {:status 204}))]
|
||||
(is (true? (vault-kvv2/delete-secret! client mount path-passed-in [12 14 147])))))
|
||||
(testing "delete secrets send correct request and returns false upon failure when multiple versions passed in"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :post (:method req)))
|
||||
(is (= (str vault-url "/v1/" mount "/delete/" path-passed-in) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(is (= {:versions [123]} (:form-params req)))
|
||||
(atom {:status 404}))]
|
||||
(is (false? (vault-kvv2/delete-secret! client mount path-passed-in [123])))))))))
|
||||
|
||||
|
||||
(deftest read-metadata-test
|
||||
(let [data (json/generate-string {:data
|
||||
{:created_time "2018-03-22T02:24:06.945319214Z"
|
||||
:current_version 3
|
||||
:max_versions 0
|
||||
:oldest_version 0
|
||||
:updated_time "2018-03-22T02:36:43.986212308Z"
|
||||
:versions {:1 {:created_time "2018-03-22T02:24:06.945319214Z"
|
||||
:deletion_time ""
|
||||
:destroyed false}
|
||||
:2 {:created_time "2018-03-22T02:36:33.954880664Z"
|
||||
:deletion_time ""
|
||||
:destroyed false}
|
||||
:3 {:created_time "2018-03-22T02:36:43.986212308Z"
|
||||
:deletion_time ""
|
||||
:destroyed false}}}})
|
||||
kebab-metadata (json/generate-string {:created-time "2018-03-22T02:24:06.945319214Z"
|
||||
:current-version 3
|
||||
:max-versions 0
|
||||
:oldest-version 0
|
||||
:updated-time "2018-03-22T02:36:43.986212308Z"
|
||||
:versions {:1 {:created-time "2018-03-22T02:24:06.945319214Z"
|
||||
:deletion-time ""
|
||||
:destroyed false}
|
||||
:2 {:created-time "2018-03-22T02:36:33.954880664Z"
|
||||
:deletion-time ""
|
||||
:destroyed false}
|
||||
:3 {:created-time "2018-03-22T02:36:43.986212308Z"
|
||||
:deletion-time ""
|
||||
:destroyed false}}})
|
||||
mount "mount"
|
||||
path-passed-in "path/passed/in"
|
||||
token-passed-in "fake-token"
|
||||
vault-url "https://vault.example.amperity.com"
|
||||
client (http-client/http-client vault-url)]
|
||||
(vault/authenticate! client :token token-passed-in)
|
||||
(testing "Sends correct request and responds correctly upon success"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :get (:method req)))
|
||||
(is (= (str vault-url "/v1/" mount "/metadata/" path-passed-in) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(atom {:body data
|
||||
:status 200}))]
|
||||
(is (= kebab-metadata (json/generate-string (vault-kvv2/read-metadata client mount path-passed-in))))))
|
||||
(testing "Sends correct request and responds correctly when metadata not found"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :get (:method req)))
|
||||
(is (= (str vault-url "/v1/" mount "/metadata/" path-passed-in) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(throw (ex-info "not found" {:errors [] :status 404 :type :vault.client.api-util/api-error})))]
|
||||
(is (thrown? ExceptionInfo (vault-kvv2/read-metadata client mount path-passed-in {:force-read true})))
|
||||
(is (= 3 (vault-kvv2/read-metadata client mount path-passed-in {:not-found 3
|
||||
:force-read true})))))))
|
||||
|
||||
|
||||
(deftest write-metadata-test
|
||||
(let [payload {:max-versions 5,
|
||||
:cas-required false,
|
||||
:delete-version-after "3h25m19s"}
|
||||
mount "mount"
|
||||
path-passed-in "path/passed/in"
|
||||
token-passed-in "fake-token"
|
||||
vault-url "https://vault.example.amperity.com"
|
||||
client (http-client/http-client vault-url)]
|
||||
(vault/authenticate! client :token token-passed-in)
|
||||
(testing "Write metadata sends correct request and responds with true upon success"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :post (:method req)))
|
||||
(is (= (str vault-url "/v1/" mount "/metadata/" path-passed-in) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(is (= (api-util/snakeify-keys payload) (:form-params req)))
|
||||
(atom {:status 204}))]
|
||||
(is (true? (vault-kvv2/write-metadata! client mount path-passed-in payload)))))
|
||||
(testing "Write metadata sends correct request and responds with false upon failure"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :post (:method req)))
|
||||
(is (= (str vault-url "/v1/" mount "/metadata/" path-passed-in) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(is (= (api-util/snakeify-keys payload) (:form-params req)))
|
||||
(atom {:status 500}))]
|
||||
(is (false? (vault-kvv2/write-metadata! client mount path-passed-in payload)))))))
|
||||
|
||||
|
||||
(deftest delete-metadata-test
|
||||
(let [mount "mount"
|
||||
path-passed-in "path/passed/in"
|
||||
token-passed-in "fake-token"
|
||||
vault-url "https://vault.example.amperity.com"
|
||||
client (http-client/http-client vault-url)]
|
||||
(vault/authenticate! client :token token-passed-in)
|
||||
(testing "Sends correct request and responds correctly upon success"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :delete (:method req)))
|
||||
(is (= (str vault-url "/v1/" mount "/metadata/" path-passed-in) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(atom {:status 204}))]
|
||||
(is (true? (vault-kvv2/delete-metadata! client mount path-passed-in)))))
|
||||
(testing "Sends correct request and responds correctly upon failure"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :delete (:method req)))
|
||||
(is (= (str vault-url "/v1/" mount "/metadata/" path-passed-in) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(atom {:status 500}))]
|
||||
(is (false? (vault-kvv2/delete-metadata! client mount path-passed-in)))))))
|
||||
|
||||
|
||||
(deftest destroy!-test
|
||||
(let [mount "mount"
|
||||
path-passed-in "path/passed/in"
|
||||
token-passed-in "fake-token"
|
||||
vault-url "https://vault.example.amperity.com"
|
||||
client (http-client/http-client vault-url)
|
||||
versions [1 2]]
|
||||
(vault/authenticate! client :token token-passed-in)
|
||||
(testing "Destroy secrets sends correct request and returns true upon success"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :post (:method req)))
|
||||
(is (= (str vault-url "/v1/" mount "/destroy/" path-passed-in) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(is (= {:versions versions}
|
||||
(:form-params req)))
|
||||
(atom {:status 204}))]
|
||||
(is (true? (vault-kvv2/destroy-secret! client mount path-passed-in versions)))))
|
||||
(testing "Destroy secrets sends correct request and returns false upon failure"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :post (:method req)))
|
||||
(is (= (str vault-url "/v1/" mount "/destroy/" path-passed-in) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(is (= {:versions [1]}
|
||||
(:form-params req)))
|
||||
(atom {:status 500}))]
|
||||
(is (false? (vault-kvv2/destroy-secret! client mount path-passed-in [1])))))))
|
||||
|
||||
|
||||
(deftest undelete-secret!-test
|
||||
(let [mount "mount"
|
||||
path-passed-in "path/passed/in"
|
||||
token-passed-in "fake-token"
|
||||
vault-url "https://vault.example.amperity.com"
|
||||
client (http-client/http-client vault-url)
|
||||
versions [1 2]]
|
||||
(vault/authenticate! client :token token-passed-in)
|
||||
(testing "Undelete secrets sends correct request and returns true upon success"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :post (:method req)))
|
||||
(is (= (str vault-url "/v1/" mount "/undelete/" path-passed-in) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(is (= {:versions versions}
|
||||
(:form-params req)))
|
||||
(atom {:status 204}))]
|
||||
(is (true? (vault-kvv2/undelete-secret! client mount path-passed-in versions)))))
|
||||
(testing "Undelete secrets sends correct request and returns false upon failure"
|
||||
(with-redefs
|
||||
[http/request
|
||||
(fn [req]
|
||||
(is (= :post (:method req)))
|
||||
(is (= (str vault-url "/v1/" mount "/undelete/" path-passed-in) (:url req)))
|
||||
(is (= token-passed-in (get (:headers req) "X-Vault-Token")))
|
||||
(is (= {:versions [1]}
|
||||
(:form-params req)))
|
||||
(atom {:status 500}))]
|
||||
(is (false? (vault-kvv2/undelete-secret! client mount path-passed-in [1])))))))
|
||||
|
||||
|
||||
;; -------- Mock Client -------------------------------------------------------
|
||||
|
||||
(defn mock-client-kvv2
|
||||
"Creates a mock client with the data in `vault/secrets/secret-fixture-kvv2.edn`"
|
||||
[]
|
||||
(mock-test/mock-client-authenticated "vault/secrets/secret-fixture-kvv2.edn"))
|
||||
|
||||
|
||||
(deftest mock-client-test
|
||||
(testing "Mock client can correctly read values it was initialized with"
|
||||
(is (= {:batman "Bruce Wayne"
|
||||
:captain-marvel "Carol Danvers"}
|
||||
(vault-kvv2/read-secret (mock-client-kvv2) "mount" "identities"))))
|
||||
(testing "Mock client correctly responds with a 404 to reading non-existent paths"
|
||||
(is (thrown-with-msg? ExceptionInfo #"No such secret: mount/data/hello"
|
||||
(vault-kvv2/read-secret (mock-client-kvv2) "mount" "hello")))
|
||||
(is (thrown-with-msg? ExceptionInfo #"No such secret: mount/data/identities"
|
||||
(vault-kvv2/read-secret (vault/new-client "mock:-") "mount" "identities"))))
|
||||
(testing "Mock client can write/update and read data"
|
||||
(let [client (mock-client-kvv2)]
|
||||
(is (thrown-with-msg? ExceptionInfo #"No such secret: mount/data/hello"
|
||||
(vault-kvv2/read-secret client "mount" "hello")))
|
||||
(is (true? (vault-kvv2/write-secret! client "mount" "hello" {:and-i-say "goodbye"})))
|
||||
(is (true? (vault-kvv2/write-secret! client "mount" "identities" {:intersect "Chuck"})))
|
||||
(is (= {:and-i-say "goodbye"}
|
||||
(vault-kvv2/read-secret client "mount" "hello")))
|
||||
(is (= {:intersect "Chuck"}
|
||||
(vault-kvv2/read-secret client "mount" "identities")))))
|
||||
(testing "Mock client can write and read config"
|
||||
(let [client (mock-client-kvv2)
|
||||
config {:max-versions 5
|
||||
:cas-required false
|
||||
:delete-version-after "3h23m19s"}]
|
||||
(is (thrown? ExceptionInfo
|
||||
(vault-kvv2/read-config client "mount")))
|
||||
(is (true? (vault-kvv2/write-config! client "mount" config)))
|
||||
(is (= config (vault-kvv2/read-config client "mount")))))
|
||||
(testing "Mock client can write and read metadata"
|
||||
(let [client (mock-client-kvv2)]
|
||||
(is (thrown? ExceptionInfo
|
||||
(vault-kvv2/read-metadata client "mount" "doesn't exist" {:force-read true})))
|
||||
(is (= {:created-time "2018-03-22T02:24:06.945319214Z"
|
||||
:current-version 1
|
||||
:max-versions 0
|
||||
:oldest-version 0
|
||||
:updated-time "2018-03-22T02:36:43.986212308Z"
|
||||
:versions {:1 {:created-time "2018-03-22T02:24:06.945319214Z"
|
||||
:deletion-time ""
|
||||
:destroyed false}}}
|
||||
(vault-kvv2/read-metadata client "mount" "identities" {:force-read true})))
|
||||
(is (true? (vault-kvv2/delete-metadata! client "mount" "identities")))
|
||||
(is (thrown? ExceptionInfo
|
||||
(vault-kvv2/read-metadata client "mount" "identities" {:force-read true})))
|
||||
(is (true? (vault-kvv2/write-metadata! client "mount" "hello" {:max-versions 3})))
|
||||
(is (= 3 (:max-versions (vault-kvv2/read-metadata client "mount" "hello"))))
|
||||
(is (= 5 (vault-kvv2/read-metadata client "mount" "doesn't exist" {:force-read true
|
||||
:not-found 5})))))
|
||||
(testing "Mock client returns true if path is found on delete for secret, false if not when no versions specified"
|
||||
(let [client (mock-client-kvv2)]
|
||||
(is (true? (vault-kvv2/delete-secret! client "mount" "identities")))
|
||||
(is (false? (vault-kvv2/delete-secret! client "mount" "eggsactly")))))
|
||||
(testing "Mock client always returns true on delete for secret when versions specified"
|
||||
(let [client (mock-client-kvv2)]
|
||||
(is (true? (vault-kvv2/delete-secret! client "mount" "identities" [1])))
|
||||
(is (true? (vault-kvv2/delete-secret! client "mount" "eggsactly" [4 5 6])))))
|
||||
(testing "Mock can list secrets from their associated metadata"
|
||||
(let [client (mock-client-kvv2)]
|
||||
(is (empty? (vault-kvv2/list-secrets client "hello" "yes")))
|
||||
(is (true? (vault-kvv2/write-secret! client "mount" "hello" {:and-i-say "goodbye"})))
|
||||
;; Paths are good enough for mock right now, but be aware they are current
|
||||
(is (= ["identities" "hello"]
|
||||
(into [] (vault-kvv2/list-secrets client "mount" ""))))))
|
||||
(testing "Mock client does not crash upon destroy"
|
||||
(is (true? (vault-kvv2/destroy-secret! (mock-client-kvv2) "mount" "identities" [1]))))
|
||||
(testing "Mock client does not crash upon undelete"
|
||||
(is (true? (vault-kvv2/undelete-secret! (mock-client-kvv2) "mount" "identities" [1])))))
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{"mount/data/identities"
|
||||
{:data {:batman "Bruce Wayne"
|
||||
:captain-marvel "Carol Danvers"}}
|
||||
"mount/metadata/identities"
|
||||
{:created_time "2018-03-22T02:24:06.945319214Z"
|
||||
:current_version 1
|
||||
:max_versions 0
|
||||
:oldest_version 0
|
||||
:updated_time "2018-03-22T02:36:43.986212308Z"
|
||||
:versions {:1 {:created_time "2018-03-22T02:24:06.945319214Z"
|
||||
:deletion_time ""
|
||||
:destroyed false}}}}
|
||||
Loading…
Reference in a new issue