diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b1fa21..954214f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ This change log follows the conventions of [keepachangelog.com](http://keepachan ... +## [0.7.1] - 2019-11-20 + +- Bugfix in mock client so that it acts more similarly to http client when creating tokens (create-token!) + [#36](https://github.com/amperity/vault-clj/issues/36) +- Added support for auth mount points to support authentication methods under a custom mount point + [#27](https://github.com/amperity/vault-clj/issues/27) + ## [0.7.0] - 2019-06-20 @@ -226,7 +233,8 @@ With this version, the project has been forked to the Amperity organization. ### Added - Initial library implementation. -[Unreleased]: https://github.com/amperity/vault-clj/compare/0.7.0...HEAD +[Unreleased]: https://github.com/amperity/vault-clj/compare/0.7.1...HEAD +[0.7.1]: https://github.com/amperity/vault-clj/compare/0.7.0...0.7.1 [0.7.0]: https://github.com/amperity/vault-clj/compare/0.6.6...0.7.0 [0.6.6]: https://github.com/amperity/vault-clj/compare/0.6.5...0.6.6 [0.6.5]: https://github.com/amperity/vault-clj/compare/0.6.4...0.6.5 diff --git a/README.md b/README.md index 015be6f..7b554ea 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,20 @@ client and resolve a map of config variables to their secret values. :bar "direct-value} ``` +## Mount Points + +The auth mount point configuration can be used to address any of the + auth methods under a custom mount point. + +```clojure +=> (def client (vault-client (assoc (vault/new-client vault-addr) + :auth-mount-point "auth/mountpath/" + :lease-renewal-window 00 + :lease-check-period 00 + :lease-check-jitter 00)) +``` + + ## License Copyright © 2016 Amperity, Inc diff --git a/project.clj b/project.clj index b39da32..ede06a0 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject amperity/vault-clj "0.7.0" +(defproject amperity/vault-clj "0.7.1" :description "Clojure client for the Vault secret management system." :url "https://github.com/amperity/vault-clj" :license {:name "Apache License" diff --git a/src/vault/client/http.clj b/src/vault/client/http.clj index 3338eb7..7bfbe61 100644 --- a/src/vault/client/http.clj +++ b/src/vault/client/http.clj @@ -182,7 +182,7 @@ (str "user " username) (:auth client) (do-api-request - :post (str (:api-url client) "/v1/auth/userpass/login/" username) + :post (str (:api-url client) "/v1/auth/userpass/" (:auth-mount-point client) "login/" username) (merge (:http-opts client) {:form-params {:password password} @@ -198,7 +198,7 @@ (str "app-id " app) (:auth client) (do-api-request - :post (str (:api-url client) "/v1/auth/app-id/login") + :post (str (:api-url client) "/v1/auth/app-id/" (:auth-mount-point client) "login") (merge (:http-opts client) {:form-params {:app_id app, :user_id user} @@ -214,7 +214,7 @@ (str "role-id sha256:" (sha-256 role-id)) (:auth client) (do-api-request - :post (str (:api-url client) "/v1/auth/approle/login") + :post (str (:api-url client) "/v1/auth/approle/" (:auth-mount-point client) "login") (merge (:http-opts client) {:form-params {:role_id role-id, :secret_id secret-id} @@ -230,7 +230,7 @@ (str "LDAP user " username) (:auth client) (do-api-request - :post (str (:api-url client) "/v1/auth/ldap/login/" username) + :post (str (:api-url client) "/v1/auth/ldap/" (:auth-mount-point client) "login/" username) (merge (:http-opts client) {:form-params {:password password} @@ -242,7 +242,7 @@ (defmethod authenticate* :k8s [client _ credentials] (let [{:keys [api-path jwt role]} credentials - api-path (or api-path "/v1/auth/kubernetes/login")] + api-path (or api-path (str "/v1/auth/kubernetes/" (:auth-mount-point client) "login"))] (when-not jwt (throw (IllegalArgumentException. "Kubernetes auth credentials must include :jwt"))) (when-not role diff --git a/src/vault/client/mock.clj b/src/vault/client/mock.clj index 2852c03..b765826 100644 --- a/src/vault/client/mock.clj +++ b/src/vault/client/mock.clj @@ -59,21 +59,46 @@ (create-token! [this opts] - {:request-id "" - :lease-id "" - :renewable false - :lease-duration 0 - :data nil - :wrap-info - (when (:wrap-ttl opts) - (let [wrap-token (gen-uuid)] - (swap! cubbies assoc wrap-token (mock-token-auth)) - {:token wrap-token - :ttl (:wrap-ttl opts) - :creation-time (gen-date) - :wrapped-accessor (gen-uuid)})) - :warnings nil - :auth (when-not (:wrap-ttl opts) (mock-token-auth))}) + (let [dates {\s 1 \m 60 \h 3600 \d 86400} + + ttl-string->seconds-int + (fn [ttl] + (* (read-string (subs ttl 0 (dec (count ttl)))) + (or (get dates (last ttl)) + (throw (ex-info (str "Mock Client doesn't recognize format of ttl: " ttl) + {:opts opts :client this})))))] + (if (:wrap-ttl opts) + (let [wrap-token (gen-uuid)] + (swap! cubbies assoc wrap-token (mock-token-auth)) + {:token wrap-token + :ttl (ttl-string->seconds-int (:wrap-ttl opts)) + :creation-time (gen-date) + :wrapped-accessor (gen-uuid) + :accessor (gen-uuid) + :creation-path "auth/token/create"}) + + ;; unwrapped + (let [policies (cond + (and (:policies opts) (:no-default-policy opts)) + (get opts :policies ["root"]) + + (contains? opts :policies) + (into ["default"] (:policies opts)) + + :else + ["root"])] + {:policies policies + :renewable (get opts :renewable false) + :entity-id "" + :token-policies policies + :accessor (gen-uuid) + :lease-duration (if (:ttl opts) + (ttl-string->seconds-int (:ttl opts)) + 0) + :token-type "service" + :orphan (get opts :no-parent false) + :client-token (gen-uuid) + :metadata nil})))) (lookup-token @@ -204,7 +229,6 @@ (throw (ex-info "Unknown wrap-token used" {}))))) - ;; ## Constructors ;; Privatize automatic constructors. diff --git a/test/vault/client/mock_test.clj b/test/vault/client/mock_test.clj new file mode 100644 index 0000000..bf76994 --- /dev/null +++ b/test/vault/client/mock_test.clj @@ -0,0 +1,71 @@ +(ns vault.client.mock-test + (:require + [clojure.test :refer :all] + [vault.core :as vault]) + (:import + (clojure.lang + ExceptionInfo))) + + +(defn mock-client-authenticated + "A mock vault client using the secrets found in `resources/secret-fixture.edn`" + [] + (let [client (vault/new-client "mock:amperity/gocd/secret/vault/secret-fixture.edn")] + (vault/authenticate! client :token "fake-token") + client)) + + +(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 (and (string? (:accessor result)) (not (empty? (:accessor result))))) + (is (= 0 (:lease-duration result))) + (is (= "service" (:token-type result))) + (is (= false (:orphan result))) + (is (and (string? (:client-token result)) (not (empty? (: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 (and (string? (:accessor result)) (not (empty? (:accessor result))))) + (is (= 604800 (:lease-duration result))) + (is (= "service" (:token-type result))) + (is (= false (:orphan result))) + (is (and (string? (:client-token result)) (not (empty? (: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 (and (string? (:accessor result)) (not (empty? (:accessor result))))) + (is (= 10 (:lease-duration result))) + (is (= "service" (:token-type result))) + (is (= true (:orphan result))) + (is (and (string? (:client-token result)) (not (empty? (: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 (and (string? (:token result)) (not (empty? (:token result))))) + (is (and (string? (:accessor result)) (not (empty? (:accessor result))))) + (is (= 7200 (:ttl result))) + (is (and (string? (:creation-time result)) (not (empty? (:creation-time result))))) + (is (= "auth/token/create" (:creation-path result))) + (is (and (string? (:wrapped-accessor result)) (not (empty? (:wrapped-accessor result)))))))) diff --git a/test/vault/client/secret-fixture.edn b/test/vault/client/secret-fixture.edn new file mode 100644 index 0000000..e69de29