From 13e15866c0d8b231649787e5d6f4ec32ccbf9a89 Mon Sep 17 00:00:00 2001 From: Nick Hale <4175918+njhale@users.noreply.github.com> Date: Mon, 16 Oct 2023 15:28:46 -0400 Subject: [PATCH] Add range syntax sugar for secret generation Add the ability to specify ranges "A-Z", "a-z", and "0-9" when providing arguments for basic and token type secrets. Signed-off-by: Nick Hale <4175918+njhale@users.noreply.github.com> --- pkg/secrets/generate.go | 52 ++++++++++++++++++++++ pkg/secrets/generate_test.go | 83 ++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 pkg/secrets/generate_test.go diff --git a/pkg/secrets/generate.go b/pkg/secrets/generate.go index 8dfeae2fa..af10dde99 100644 --- a/pkg/secrets/generate.go +++ b/pkg/secrets/generate.go @@ -3,6 +3,9 @@ package secrets import ( "crypto/rand" "math/big" + "sort" + + "k8s.io/apimachinery/pkg/util/sets" ) const ( @@ -20,6 +23,8 @@ func GenerateRandomSecret(length int, characterSet string) (string, error) { } if characterSet == "" { characterSet = defaultCharacterSet + } else { + characterSet = inflateRanges(characterSet) } // Generate a random secret by randomly selecting characters from the given character set. @@ -34,3 +39,50 @@ func GenerateRandomSecret(length int, characterSet string) (string, error) { return string(secret), nil } + +// inflateRanges inflates a character set by expanding any ranges (e.g. `A-z`) into the full set of characters they represent. +func inflateRanges(characterSet string) string { + var ( + runeSet = []rune(characterSet) + inflated = sets.New[rune]() + ) + for i := 0; i < len(runeSet); i++ { + cur := runeSet[i] + if alphanumeric(cur) { + // Alphanumeric character detected + if i+2 < len(runeSet) && runeSet[i+1] == '-' && alphanumeric(runeSet[i+2]) { + // Range detected, convert to full set of characters + start, end := cur, runeSet[i+2] + if start > end { + // Swap start and end if they're out of order + start, end = end, start + } + + for c := start; c <= end; c++ { + if alphanumeric(c) { + inflated.Insert(c) + } + } + + // Skip the next two characters since we've already processed them + i += 2 + continue + } + } + + inflated.Insert(cur) + } + + sorted := inflated.UnsortedList() + sort.Slice(sorted, func(i, j int) bool { + return sorted[i] < sorted[j] + }) + + return string(sorted) +} + +// alphanumeric returns true IFF the given rune is alphanumeric; e.g. [A-z0-9] . +func alphanumeric(r rune) bool { + cv := int(r) + return (cv >= int('A') && cv <= int('Z')) || (cv >= int('a') && cv <= int('z')) || (cv >= int('0') && cv <= int('9')) +} diff --git a/pkg/secrets/generate_test.go b/pkg/secrets/generate_test.go new file mode 100644 index 000000000..71266b081 --- /dev/null +++ b/pkg/secrets/generate_test.go @@ -0,0 +1,83 @@ +package secrets + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestInflateRanges(t *testing.T) { + for _, tt := range []struct { + name string + want string + }{ + { + name: "", + want: "", + }, + { + name: "-", + want: "-", + }, + { + name: "A-", + want: "-A", + }, + { + name: "A-A", + want: "A", + }, + { + name: "A-Z", + want: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + }, + { + name: "Z-A", + want: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + }, + { + name: "A-z", + want: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", + }, + { + name: "z-A", + want: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", + }, + { + name: "a-z", + want: "abcdefghijklmnopqrstuvwxyz", + }, + { + name: "z-a", + want: "abcdefghijklmnopqrstuvwxyz", + }, + { + name: "0-9", + want: "0123456789", + }, + { + name: "9-0", + want: "0123456789", + }, + { + name: "0-9A-Z", + want: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", + }, + { + name: "A-Z0-9", + want: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", + }, + { + name: "A-Za-z0-9", + want: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", + }, + { + name: "!#$%&*+-0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ^_abcdefghijklmnopqrstuvwxyz", + want: "!#$%&*+-0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ^_abcdefghijklmnopqrstuvwxyz", + }, + } { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, inflateRanges(tt.name)) + }) + } +}