Skip to content

[Draft] Feature/secrets with complexity #1511

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions pkg/splunk/common/names.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ const (
// SecretBytes used to generate Splunk secrets
SecretBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

// SecretBytes with complexity used to generate Splunk secrets with complexity
SecretBytesLower = "abcdefghijklmnopqrstuvwxyz"
SecretBytesUpper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
SecretBytesDecimal = "0123456789"
// we dont use $ here to prevent secret starting with this as this is used by Splunk to identify obfuscated one
SecretBytesSpecial = "-*&%#@,.;:/?[]{}+=-_<>"
// version with all possible characters use to complete after we have match minimal complexity
SecretBytesComplete = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-*&%#@,.;:/?[]{}+=-_<>"
// version when complexity disabled
SecretBytesSimple = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

// HexBytes used to generate random hexadecimal strings (e.g. HEC tokens)
HexBytes = "ABCDEF01234567890"

Expand Down
101 changes: 100 additions & 1 deletion pkg/splunk/common/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,110 @@ func GetServiceFQDN(namespace string, name string) string {
)
}

// SecretBytes is the string with characters allowed to
// m is number of characters to generate
// b is they byte array to modify (which already exist)
func GenerateSecretPartWithComplexity(SecretBytes string, m int, b []byte) error {
j := 0
k := 0
length := len(b)
if m > 0 {
brokeEarly := false
for i := 1; i < m+1; i++ {
maxtry := 100
for j = 1; j < maxtry; j++ {
// we try a random position from 0 to length-1 where length is secret size
// Use crypto/rand to get a secure random index
var indexByte [1]byte
_, err := rand.Read(indexByte[0:1]) // 0:1 turn array into slice to be used with Read and the function will put the random value in indexByte[0]
if err != nil {
// note : we may lack entropy and be running out of randomness
return err
}
// compute the random position number
k = int(indexByte[0]) % length
if b[k] == 0 {
_, err = rand.Read(indexByte[0:1]) // 0:1 turn array into slice to be used with Read and the function will put the random value in indexByte[0]
if err != nil {
// note : we may lack entropy and be running out of randomness
return err
}
// this was not yet assigned a value
b[k] = SecretBytes[int(indexByte[0])%len(SecretBytes)]
brokeEarly = true
break
} else {
//fmt.Printf("position k %d already used will try another position\n ", k)
}
}
}
if brokeEarly {
//fmt.Printf("generation ended succesfully \n")
} else {
return fmt.Errorf("generation was not completed after %d maxtry, something is wrong\n", m)
}
} else if m == 0 {
//fmt.Println("no complexity requirement for this type")
} else {
return fmt.Errorf("incorrect value for minimal complexity, ignoring")
}
return nil
}

func GenerateSecretWithComplexity(n int, minlower int, minupper int, mindecimal int, minspecial int) ([]byte, error) {
b := make([]byte, n)
if n < minlower+minupper+mindecimal+minspecial {
fmt.Printf("password length and complexity requirements are incompatible length=%d, minlower=%d , minupper=%d, mindecimal=%d, minspecial=%d\n", n, minlower, minupper, mindecimal, minspecial)
// b is empty here , we return error and expect caller to check for it
return b, fmt.Errorf("password length and complexity requirements are incompatible length=%d, minlower=%d , minupper=%d, mindecimal=%d, minspecial=%d\n", n, minlower, minupper, mindecimal, minspecial)
} else if minlower+minupper+mindecimal+minspecial == 0 {
// disable complexity , we also use SecretBytesNormal instead of SecretBytesComplete
for i := range b {
// Use crypto/rand to get a secure random index
var indexByte [1]byte
_, err := rand.Read(indexByte[0:1]) // 0:1 turn array into slice to be used with Read and the function will put the random value in indexByte[0]
if err != nil {
return b, err
}

b[i] = SecretBytesSimple[int(indexByte[0])%len(SecretBytesSimple)]
}
} else {
//fmt.Printf("password length and complexity requirements are OK length=%d, minlower=%d , minupper=%d, mindecimal=%d, minspecial=%d\n", n, minlower, minupper, mindecimal, minspecial)

GenerateSecretPartWithComplexity(SecretBytesLower, minlower, b)
GenerateSecretPartWithComplexity(SecretBytesUpper, minupper, b)
GenerateSecretPartWithComplexity(SecretBytesDecimal, mindecimal, b)
GenerateSecretPartWithComplexity(SecretBytesSpecial, minspecial, b)
// complete gaps
for i := range b {
if b[i] == 0 {
// we try a random position from 0 to length-1 where length is secret size
// Use crypto/rand to get a secure random index
var indexByte [1]byte
_, err := rand.Read(indexByte[0:1]) // 0:1 turn array into slice to be used with Read and the function will put the random value in indexByte[0]
if err != nil {
return b, err
}

b[i] = SecretBytesComplete[int(indexByte[0])%len(SecretBytesComplete)]
}
}
}
return b, nil
}

// GenerateSecret returns a randomly generated sequence of text that is n bytes in length.
func GenerateSecret(SecretBytes string, n int) []byte {
b := make([]byte, n)
for i := range b {
b[i] = SecretBytes[rand.Int63()%int64(len(SecretBytes))]
// Use crypto/rand to get a secure random index
var indexByte [1]byte
_, err := rand.Read(indexByte[0:1]) // 0:1 turn array into slice to be used with Read and the function will put the random value in indexByte[0]
if err != nil {
return nil
}
b[i] = SecretBytes[int(indexByte[0])%len(SecretBytes)]
}
return b
}
Expand Down
61 changes: 61 additions & 0 deletions pkg/splunk/common/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,67 @@ func TestGenerateSecret(t *testing.T) {
test("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 10)
}

func TestGenerateSecretWithComplexity(t *testing.T) {
test := func(n int, minlower int, minupper int, mindecimal int, minspecial int) {
var err error
results := [][]byte{}
b := make([]byte, n)
// get 100 results
for i := 0; i < 100; i++ {
b, err = GenerateSecretWithComplexity(n, minlower, minupper, mindecimal, minspecial)
if err != nil {
fmt.Printf("GenerateSecretWithComplexity(%d,%d,%d,%d,%d) returned error %s", n, minlower, minupper, mindecimal, minspecial, err)
break
} else {

results = append(results, b)

// ensure its length is correct
if len(results[i]) != n {
fmt.Printf("GenerateSecretWithComplexity(%d,%d,%d,%d,%d) len = %d; want %d", n, minlower, minupper, mindecimal, minspecial, len(results[i]), n)
}

// ensure it only includes allowed bytes
for _, c := range results[i] {
if bytes.IndexByte([]byte(SecretBytesComplete), c) == -1 {
fmt.Printf("GenerateSecretWithComplexity(%d,%d,%d,%d,%d) returned invalid byte: %c", n, minlower, minupper, mindecimal, minspecial, c)
}
}

// ensure each result is unique
for x := i; x > 0; x-- {
if bytes.Equal(results[x-1], results[i]) {
fmt.Printf("GenerateSecretWithComplexity(%d,%d,%d,%d,%d) returned two identical values: %s", n, minlower, minupper, mindecimal, minspecial, string(results[i]))
}
}
}
}
}
// simple one with no complexity constraint
test(10, 0, 0, 0, 0)
// test with complexity and different lengths
test(10, 1, 1, 1, 1)
test(24, 1, 1, 1, 1)
// test complexity that match exactly length
test(4, 1, 1, 1, 1)
test(8, 2, 2, 2, 2)
}

func TestErrorGenerateSecretWithTooMuchComplexity(t *testing.T) {
testneg := func(n int, minlower int, minupper int, mindecimal int, minspecial int) {
var err error
_, err = GenerateSecretWithComplexity(n, minlower, minupper, mindecimal, minspecial)
fmt.Printf("err=%s ", err)
if err == nil {
fmt.Printf("GenerateSecretWithComplexity(%d,%d,%d,%d,%d) returned success when expected error about impossible complexity, err=%s", n, minlower, minupper, mindecimal, minspecial, err)
}
}
// test impossible combination ie complexity over length
testneg(4, 1, 1, 2, 1)
testneg(8, 2, 5, 1, 1)
testneg(24, 24, 1, 1, 1)
}

func TestSortContainerPorts(t *testing.T) {
var ports []corev1.ContainerPort
var want []corev1.ContainerPort
Expand Down
23 changes: 21 additions & 2 deletions pkg/splunk/util/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,8 +459,18 @@ func ApplyNamespaceScopedSecretObject(ctx context.Context, client splcommon.Cont
// Value for token not found, generate
if tokenType == "hec_token" {
current.Data[tokenType] = generateHECToken()
} else if tokenType == "password" {
// use complexity for password
current.Data[tokenType], err = splcommon.GenerateSecretWithComplexity(24, 1, 1, 1, 1)
if err != nil {
return nil, err
}
} else {
current.Data[tokenType] = splcommon.GenerateSecret(splcommon.SecretBytes, 24)
// disable complexity for secrets
current.Data[tokenType], err = splcommon.GenerateSecretWithComplexity(24, 0, 0, 0, 0)
if err != nil {
return nil, err
}
}
updateNeeded = true
}
Expand Down Expand Up @@ -488,8 +498,17 @@ func ApplyNamespaceScopedSecretObject(ctx context.Context, client splcommon.Cont
for _, tokenType := range splcommon.GetSplunkSecretTokenTypes() {
if tokenType == "hec_token" {
current.Data[tokenType] = generateHECToken()
} else if tokenType == "password" {
// use complexity for password
current.Data[tokenType], err = splcommon.GenerateSecretWithComplexity(24, 1, 1, 1, 1)
if err != nil {
return nil, err
}
} else {
current.Data[tokenType] = splcommon.GenerateSecret(splcommon.SecretBytes, 24)
current.Data[tokenType], err = splcommon.GenerateSecretWithComplexity(24, 0, 0, 0, 0)
if err != nil {
return nil, err
}
}
}

Expand Down
54 changes: 45 additions & 9 deletions pkg/splunk/util/secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,10 @@ func TestGetLatestVersionedSecret(t *testing.T) {
}

// Update namespace scoped secret with new admin password
namespacescopedsecret.Data["password"] = splcommon.GenerateSecret(splcommon.SecretBytes, 24)
namespacescopedsecret.Data["password"], err = splcommon.GenerateSecretWithComplexity(24, 1, 1, 1, 1)
if err != nil {
t.Errorf(err.Error())
}
err = UpdateResource(context.TODO(), c, namespacescopedsecret)
if err != nil {
t.Errorf(err.Error())
Expand Down Expand Up @@ -798,7 +801,10 @@ func TestGetSplunkReadableNamespaceScopedSecretData(t *testing.T) {
secretData := make(map[string][]byte)
for _, tokenType := range splcommon.GetSplunkSecretTokenTypes() {
if tokenType != "hec_token" {
secretData[tokenType] = splcommon.GenerateSecret(splcommon.SecretBytes, 24)
secretData[tokenType], err = splcommon.GenerateSecretWithComplexity(24, 1, 1, 1, 1)
if err != nil {
t.Errorf(err.Error())
}
}
}

Expand Down Expand Up @@ -943,22 +949,52 @@ func TestApplyNamespaceScopedSecretObject(t *testing.T) {
// Partially baked "splunk-secrets" object(applies to empty as well)
createCalls = map[string][]spltest.MockFuncCall{"Get": funcCalls, "Update": funcCalls}
updateCalls = map[string][]spltest.MockFuncCall{"Get": funcCalls}
password, err := splcommon.GenerateSecretWithComplexity(24, 1, 1, 1, 1)
if err != nil {
t.Errorf("Error Generating Password With Complexity")
// FIXME : should we return here ?
}
pass4, err := splcommon.GenerateSecretWithComplexity(24, 1, 1, 1, 1)
if err != nil {
t.Errorf("Error Generating Password With Complexity")
// FIXME : should we return here ?
}

secret := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: splcommon.GetNamespaceScopedSecretName("test"),
Namespace: "test",
},
Data: map[string][]byte{
"password": splcommon.GenerateSecret(splcommon.SecretBytes, 24),
"pass4Symmkey": splcommon.GenerateSecret(splcommon.SecretBytes, 24),
"password": password,
"pass4Symmkey": pass4,
},
}
spltest.ReconcileTester(t, "TestApplyNamespaceScopedSecretObject", "test", "test", createCalls, updateCalls, reconcile, false, &secret)

// Fully baked splunk-secrets object
createCalls = map[string][]spltest.MockFuncCall{"Get": funcCalls}
updateCalls = map[string][]spltest.MockFuncCall{"Get": funcCalls}
password, err = splcommon.GenerateSecretWithComplexity(24, 1, 1, 1, 1)
if err != nil {
t.Errorf("Error Generating Password With Complexity")
// FIXME : should we return here ?
}
pass4, err = splcommon.GenerateSecretWithComplexity(24, 1, 1, 1, 1)
if err != nil {
t.Errorf("Error Generating Password With Complexity")
// FIXME : should we return here ?
}
idxc_secret, err := splcommon.GenerateSecretWithComplexity(24, 1, 1, 1, 1)
if err != nil {
t.Errorf("Error Generating Password With Complexity")
// FIXME : should we return here ?
}
shc_secret, err := splcommon.GenerateSecretWithComplexity(24, 1, 1, 1, 1)
if err != nil {
t.Errorf("Error Generating Password With Complexity")
// FIXME : should we return here ?
}

secret = corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -967,10 +1003,10 @@ func TestApplyNamespaceScopedSecretObject(t *testing.T) {
},
Data: map[string][]byte{
"hec_token": generateHECToken(),
"password": splcommon.GenerateSecret(splcommon.SecretBytes, 24),
"pass4SymmKey": splcommon.GenerateSecret(splcommon.SecretBytes, 24),
"idxc_secret": splcommon.GenerateSecret(splcommon.SecretBytes, 24),
"shc_secret": splcommon.GenerateSecret(splcommon.SecretBytes, 24),
"password": password,
"pass4SymmKey": pass4,
"idxc_secret": idxc_secret,
"shc_secret": shc_secret,
},
}
spltest.ReconcileTester(t, "TestApplyNamespaceScopedSecretObject", "test", "test", createCalls, updateCalls, reconcile, false, &secret)
Expand All @@ -985,7 +1021,7 @@ func TestApplyNamespaceScopedSecretObject(t *testing.T) {
c.Create(ctx, &negSecret)
rerr := errors.New(splcommon.Rerr)
c.InduceErrorKind[splcommon.MockClientInduceErrorUpdate] = rerr
_, err := ApplyNamespaceScopedSecretObject(ctx, c, negSecret.GetNamespace())
_, err = ApplyNamespaceScopedSecretObject(ctx, c, negSecret.GetNamespace())
if err == nil {
t.Errorf("Expected error")
}
Expand Down
Loading