Skip to content

Commit 85c759e

Browse files
corporate-gadflyGitHub Copilot
andcommitted
Fix FIPS-safe random string generation in dataset generator
Chunk large RandomStringUtils calls to stay under the BC FIPS DRBG per-request limit (262144 bits / 32768 bytes), which was causing generate tests to fail under the FIPS profile. - add random-ascii-string helper in cli/generate.clj and use it in add-blob - add alphabetic-string helper in random.clj and route random-string/ random-string-alpha through it - use StringBuilder-based chunk assembly to reduce intermediate allocations - add local type hints on interop targets to avoid reflection noise Co-authored-by: GitHub Copilot <copilot@users.noreply.github.com> Signed-off-by: Corporate Gadfly <haroon.rafique@gmail.com>
1 parent 8046580 commit 85c759e

2 files changed

Lines changed: 51 additions & 4 deletions

File tree

src/puppetlabs/puppetdb/cli/generate.clj

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,8 @@
246246
[java.nio.file Files]
247247
[org.apache.commons.lang3 RandomStringUtils]))
248248

249+
(declare random-ascii-string)
250+
249251
(def resource-fact-path "puppetlabs/puppetdb/generate/samples/facts/baseline-agent-node.json")
250252

251253
(defmulti weigh "Loosely weigh up the bytes of a structure." class)
@@ -335,7 +337,7 @@
335337
(recur (assoc parameters
336338
(parameter-name
337339
(rnd/safe-sample-normal 20 5 {:lowerb 5}))
338-
(.nextAscii (RandomStringUtils/secure)
340+
(random-ascii-string
339341
(rnd/safe-sample-normal 50 25 {:upperb (max 50 size)}))))
340342
parameters))))
341343

@@ -461,6 +463,29 @@
461463
:target (select-keys target [:type :title])
462464
:relationship relation})
463465

466+
(defn- random-ascii-string
467+
"Generate a random ASCII string of the given length.
468+
469+
Chunks requests to stay within the BouncyCastle FIPS DRBG per-request limit
470+
of 262144 bits (32768 bytes). Apache Commons Lang3's RandomStringUtils
471+
allocates a CachedRandomBits with size = (count * gapBits + 3) / 5 + 10
472+
bytes. For nextAscii (ASCII range 32-127, gap=95, gapBits=7) the maximum
473+
count that keeps the cache within 32768 bytes is:
474+
(count * 7 + 3) / 5 + 10 <= 32768 => count <= 23398"
475+
[length]
476+
(let [fips-max-chunk 23398
477+
^RandomStringUtils secure-random (RandomStringUtils/secure)]
478+
(if (<= length fips-max-chunk)
479+
(.nextAscii secure-random length)
480+
;; Build incrementally to avoid intermediate vectors/strings for large values.
481+
(let [^StringBuilder builder (StringBuilder. length)]
482+
(loop [remaining length]
483+
(if (<= remaining 0)
484+
(.toString builder)
485+
(let [chunk-size (min remaining fips-max-chunk)]
486+
(.append builder (.nextAscii secure-random chunk-size))
487+
(recur (- remaining chunk-size)))))))))
488+
464489
(defn add-blob
465490
"Add a large parameter string blob to one of the given catalog's resource parameters.
466491
@@ -480,7 +505,7 @@
480505
bsize (rnd/safe-sample-normal avg-blob-size-in-bytes standard-deviation {:lowerb lowerb :upperb upperb})
481506
pname (format "content_blob_%s" (rnd/random-pronouncable-word))]
482507
(update-in catalog [:resources (rand-int (count resources)) :parameters]
483-
#(merge % {pname (.nextAscii (RandomStringUtils/secure) bsize)}))))
508+
#(merge % {pname (random-ascii-string bsize)}))))
484509

485510
(defn system-seconds-str
486511
"Epoch seconds as a string. Used by default as a version string in Puppet

src/puppetlabs/puppetdb/random.clj

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,39 @@
1010
(def ^{:doc "Convenience for java.util.Random"}
1111
random (java.util.Random.))
1212

13+
(defn- alphabetic-string
14+
"Generate a random alphabetic string of the given length.
15+
16+
Chunks requests to stay within the BouncyCastle FIPS DRBG per-request limit
17+
of 262144 bits. Apache Commons Lang3's CachedRandomBits allocates
18+
(count * gapBits + 3) / 5 + 10 bytes per call. For nextAlphabetic
19+
(A-Z + a-z = 52 chars, gapBits = ceil(log2(52)) = 6) the maximum count per call is:
20+
(count * 6 + 3) / 5 + 10 <= 32768 => count <= 27298"
21+
[length]
22+
(let [fips-max-chunk 27298
23+
^RandomStringUtils secure-random (RandomStringUtils/secure)]
24+
(if (<= length fips-max-chunk)
25+
(.nextAlphabetic secure-random length)
26+
;; Build incrementally to avoid intermediate vectors/strings for large values.
27+
(let [^StringBuilder builder (StringBuilder. length)]
28+
(loop [remaining length]
29+
(if (<= remaining 0)
30+
(.toString builder)
31+
(let [chunk-size (min remaining fips-max-chunk)]
32+
(.append builder (.nextAlphabetic secure-random chunk-size))
33+
(recur (- remaining chunk-size)))))))))
34+
1335
(defn random-string
1436
"Generate a random string of optional length"
1537
([] (.nextAlphabetic (RandomStringUtils/secure) (inc (rand-int 10))))
1638
([length]
17-
(.nextAlphabetic (RandomStringUtils/secure) length)))
39+
(alphabetic-string length)))
1840

1941
(defn random-string-alpha
2042
"Generate a random string of optional length, only lower case alphabet chars"
2143
([] (random-string (inc (rand-int 10))))
2244
([length]
23-
(.toLowerCase (.nextAlphabetic (RandomStringUtils/secure) length))))
45+
(.toLowerCase (alphabetic-string length))))
2446

2547
(defn random-bool
2648
"Generate a random boolean"

0 commit comments

Comments
 (0)