Skip to content

Commit 877c8a3

Browse files
authored
Merge pull request #2805 from jonathannewman/PE-34953/main/sign-all-csr
(PE-34953) add sign/all endpoint
2 parents 65b66d1 + ca295e0 commit 877c8a3

File tree

5 files changed

+95
-14
lines changed

5 files changed

+95
-14
lines changed

src/clj/puppetlabs/puppetserver/certificate_authority.clj

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
[slingshot.slingshot :as sling])
1919
(:import (java.io BufferedReader BufferedWriter ByteArrayInputStream ByteArrayOutputStream File FileNotFoundException IOException InputStream Reader StringReader)
2020
(java.nio CharBuffer)
21-
(java.nio.file Files)
21+
(java.nio.file Files Path Paths)
2222
(java.nio.file.attribute FileAttribute PosixFilePermissions)
2323
(java.security PrivateKey PublicKey)
2424
(java.security.cert CRLException CertPathValidatorException X509CRL X509Certificate)
@@ -1530,6 +1530,18 @@
15301530
(when (fs/exists? cert-request-path)
15311531
(slurp cert-request-path))))
15321532

1533+
(schema/defn ^:always-validate
1534+
get-paths-to-all-certificate-requests :- [Path]
1535+
"Given a csr directory, return Path entries to all the files that could be CSRs"
1536+
[csrdir :- schema/Str]
1537+
(let [csr-dir-as-path (Paths/get csrdir (into-array String []))]
1538+
(if (Files/isDirectory csr-dir-as-path ks-file/nofollow-links)
1539+
(with-open [dir-stream (Files/newDirectoryStream csr-dir-as-path "*.pem")]
1540+
(doall (iterator-seq (.iterator dir-stream))))
1541+
(do
1542+
(log/error (i18n/trs "Attempting to use {0} as CSR directory, but it is not a directory." csrdir))
1543+
[]))))
1544+
15331545
(schema/defn ^:always-validate
15341546
autosign-csr? :- schema/Bool
15351547
"Return true if the CSR should be automatically signed given

src/clj/puppetlabs/puppetserver/common.clj

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
(ns puppetlabs.puppetserver.common
2-
(:require [clojure.tools.logging :as log]
3-
[schema.core :as schema]
2+
(:require [clojure.string :as str]
3+
[clojure.tools.logging :as log]
44
[puppetlabs.i18n.core :as i18n]
5+
[schema.core :as schema]
56
[slingshot.slingshot :as sling])
67
(:import (java.util List Map Set)
78
(java.util.concurrent TimeUnit)
@@ -124,3 +125,19 @@
124125
(let [yaml (new Yaml)
125126
data (.load yaml ^String yaml-string)]
126127
(java->clj data)))
128+
129+
(defn extract-file-names-from-paths
130+
"Given a sequence of java.nio.file.Path objects, return a lazy sequence of the file names of the file represented
131+
by those paths. Example ['/foo/bar/baz.tmp'] will result in ['baz.tmp']"
132+
[paths-to-files]
133+
(map #(.toString (.getFileName %)) paths-to-files))
134+
135+
(defn remove-suffix-from-file-names
136+
"Given a suffix, and a sequence of file-names, remove the suffix from the filenames"
137+
[files suffix]
138+
(let [suffix-size (count suffix)]
139+
(map (fn [s]
140+
(if (str/ends-with? s suffix)
141+
(subs s 0 (- (count s) suffix-size))
142+
s))
143+
files)))

src/clj/puppetlabs/services/ca/certificate_authority_core.clj

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -276,11 +276,14 @@
276276
(rr/content-type "application/json")))))
277277

278278
(schema/defn handle-bulk-cert-signing-all
279-
[_request
280-
_ca-settings :- ca/CaSettings]
281-
(-> (rr/response (cheshire/generate-string {}))
282-
(rr/status 200)
283-
(rr/content-type "application/json")))
279+
[ca-settings :- ca/CaSettings report-activity]
280+
(let [csr-files (-> (ca/get-paths-to-all-certificate-requests (:csrdir ca-settings))
281+
(common/extract-file-names-from-paths)
282+
(common/remove-suffix-from-file-names ".pem"))
283+
results (ca/sign-multiple-certificate-signing-requests! csr-files ca-settings report-activity)]
284+
(-> (rr/response (cheshire/generate-string results))
285+
(rr/status 200)
286+
(rr/content-type "application/json"))))
284287

285288
(schema/defn ^:always-validate
286289
handle-cert-renewal
@@ -568,8 +571,8 @@
568571
(handle-cert-renewal request ca-settings report-activity))
569572
(POST ["/sign"] request
570573
(handle-bulk-cert-signing request ca-settings report-activity))
571-
(POST ["/sign/all"] request
572-
(handle-bulk-cert-signing-all request ca-settings)))
574+
(POST ["/sign/all"] _request
575+
(handle-bulk-cert-signing-all ca-settings report-activity)))
573576
(comidi/not-found "Not Found")))
574577

575578
(schema/defn ^:always-validate

test/integration/puppetlabs/services/certificate_authority/certificate_authority_int_test.clj

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
[me.raynes.fs :as fs]
99
[puppetlabs.http.client.sync :as http-client]
1010
[puppetlabs.kitchensink.core :as ks]
11+
[puppetlabs.kitchensink.file :as ks-file]
1112
[puppetlabs.puppetserver.bootstrap-testutils :as bootstrap]
1213
[puppetlabs.puppetserver.certificate-authority :as ca]
1314
[puppetlabs.puppetserver.testutils :as testutils :refer [http-get]]
@@ -84,6 +85,11 @@
8485
(ssl-utils/obj->pem! csr csr-path)
8586
key-pair))
8687

88+
(defn delete-all-csrs
89+
[]
90+
(let [csr-path (str bootstrap/server-conf-dir "/ca/requests/")]
91+
(ks-file/delete-recursively csr-path)))
92+
8793
(defn generate-and-sign-a-cert!
8894
[certname]
8995
(let [cert-path (str bootstrap/server-conf-dir "/ssl/certs/localhost.pem")
@@ -1207,6 +1213,8 @@
12071213
(is (.contains body error-msg)))))))
12081214

12091215
(deftest ca-bulk-signing-all-endpoint-test
1216+
;; ensure the csr directory is empty as other tests leave cruft behind
1217+
(delete-all-csrs)
12101218
(testing "returns 200 response"
12111219
(bootstrap/with-puppetserver-running-with-mock-jrubies
12121220
"JRuby mocking is safe here because all of the requests are to the CA
@@ -1219,14 +1227,25 @@
12191227
:ssl-key (str bootstrap/server-conf-dir "/ssl/private_keys/localhost.pem")
12201228
:ssl-ca-cert (str bootstrap/server-conf-dir "/ca/ca_crt.pem")
12211229
:ssl-crl-path (str bootstrap/server-conf-dir "/ssl/crl.pem")}}
1222-
(let [response (http-client/post
1230+
(testing "returns 200 with valid payload"
1231+
;; note- more extensive testing of the behavior is done with the testing in sign-multiple-certificate-signing-requests!-test
1232+
(let [certname (ks/rand-str :alpha-lower 16)
1233+
certname-with-bad-extension (ks/rand-str :alpha-lower 16)
1234+
_ (generate-a-csr certname [] [])
1235+
_ (generate-a-csr certname-with-bad-extension [{:oid "1.9.9.9.9.9.0" :value "true" :critical false}] [])
1236+
response (http-client/post
12231237
"https://localhost:8140/puppet-ca/v1/sign/all"
12241238
{:ssl-cert (str bootstrap/server-conf-dir "/ca/ca_crt.pem")
12251239
:ssl-key (str bootstrap/server-conf-dir "/ca/ca_key.pem")
12261240
:ssl-ca-cert (str bootstrap/server-conf-dir "/ca/ca_crt.pem")
12271241
:as :text
12281242
:headers {"Accept" "application/json"}})]
1229-
(is (= 200 (:status response)))))))
1243+
(is (= 200 (:status response)))
1244+
(is (= {:signed [certname]
1245+
;; this would represent any files that are removed between when the set is collected, and when they are processed.
1246+
:no-csr []
1247+
:signing-errors [certname-with-bad-extension]}
1248+
(json/parse-string (:body response) true))))))))
12301249

12311250
(deftest ca-certificate-renew-endpoint-test
12321251
(testing "with the feature enabled"

test/unit/puppetlabs/puppetserver/certificate_authority_test.clj

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
[puppetlabs.kitchensink.core :as ks]
1010
[puppetlabs.kitchensink.file :as ks-file]
1111
[puppetlabs.puppetserver.certificate-authority :as ca]
12+
[puppetlabs.puppetserver.common :as common]
1213
[puppetlabs.services.ca.ca-testutils :as testutils]
1314
[puppetlabs.services.jruby.jruby-puppet-testutils :as jruby-testutils]
1415
[puppetlabs.ssl-utils.core :as utils]
@@ -19,8 +20,10 @@
1920
(:import (com.puppetlabs.ssl_utils SSLUtils)
2021
(java.io ByteArrayInputStream
2122
ByteArrayOutputStream
22-
StringReader
23+
File StringReader
2324
StringWriter)
25+
(java.nio.file Files Path)
26+
(java.nio.file.attribute FileAttribute)
2427
(java.security MessageDigest PublicKey)
2528
(java.security.cert X509CRL X509Certificate)
2629
(java.time LocalDateTime ZoneOffset)
@@ -2384,4 +2387,31 @@
23842387
(testing "all of the unauthorized names should be in the not-signed"
23852388
(is (= unauthorized-set (clojure.set/intersection unsigned-set unauthorized-set))))
23862389
(testing "all of the unapproved names should be in the not-signed"
2387-
(is (= unapproved-extensions-set (clojure.set/intersection unsigned-set unapproved-extensions-set)))))))))
2390+
(is (= unapproved-extensions-set (clojure.set/intersection unsigned-set unapproved-extensions-set)))))))))
2391+
2392+
(def default-permissions
2393+
(into-array FileAttribute [(ks-file/perms->attribute "rw-------")]))
2394+
2395+
(deftest get-paths-to-all-certificate-requests-test
2396+
(testing "finds all files in directory ending with the pem suffix"
2397+
(let [^File temp-directory (ks/temp-dir)
2398+
path-to-file (.toPath temp-directory)
2399+
a-pem-file-names (set (for [i (range 0 100)]
2400+
(format "a-%d.pem" i)))
2401+
b-pem-file-names (set (for [i (range 0 100)]
2402+
(format "b-%d.pem" i)))
2403+
a-foo-file-names (set (for [i (range 0 100)]
2404+
(format "a-%d.foo" i)))
2405+
all-pem-file-names (clojure.set/union a-pem-file-names b-pem-file-names)]
2406+
2407+
;; create a lot of files that match that end with pem
2408+
(doall
2409+
(for [^String i all-pem-file-names]
2410+
(Files/createFile (.resolve ^Path path-to-file i) default-permissions)))
2411+
;; create a lot of files that don't end that start with pem
2412+
(doall
2413+
(for [^String i a-foo-file-names]
2414+
(Files/createFile (.resolve path-to-file i) default-permissions)))
2415+
(let [result (ca/get-paths-to-all-certificate-requests (.toString temp-directory))
2416+
file-names (set (common/extract-file-names-from-paths result))]
2417+
(is (= (set file-names) all-pem-file-names))))))

0 commit comments

Comments
 (0)