Browse Source

feat(generators): add hex generator (#5314)

* add hex generator

Signed-off-by: unique-jakub <jakub@unique.ch>

* Revert "add hex generator"

This reverts commit 1809fe56edca8367cf68743a0b940832e544c527.

Signed-off-by: unique-jakub <jakub@unique.ch>

* add encoding option to password generator

Signed-off-by: unique-jakub <jakub@unique.ch>

* make reviewable

Signed-off-by: unique-jakub <jakub@unique.ch>

* run make check-diff

Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>

---------

Signed-off-by: unique-jakub <jakub@unique.ch>
Signed-off-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
Co-authored-by: Gergely Brautigam <skarlso777@gmail.com>
Co-authored-by: Gergely Brautigam <182850+Skarlso@users.noreply.github.com>
unique-jakub 5 months ago
parent
commit
3b222ff4a0

+ 11 - 0
apis/generators/v1alpha1/types_password.go

@@ -46,6 +46,17 @@ type PasswordSpec struct {
 	// set AllowRepeat to true to allow repeating characters.
 	// +kubebuilder:default=false
 	AllowRepeat bool `json:"allowRepeat"`
+
+	// Encoding specifies the encoding of the generated password.
+	// Valid values are:
+	// - "raw" (default): no encoding
+	// - "base64": standard base64 encoding
+	// - "base64url": base64url encoding
+	// - "base32": base32 encoding
+	// - "hex": hexadecimal encoding
+	// +kubebuilder:default="raw"
+	// +kubebuilder:validation:Enum=base64;base64url;base32;hex;raw
+	Encoding *string `json:"encoding,omitempty"`
 }
 
 // Password generates a random password based on the

+ 5 - 0
apis/generators/v1alpha1/zz_generated.deepcopy.go

@@ -1423,6 +1423,11 @@ func (in *PasswordSpec) DeepCopyInto(out *PasswordSpec) {
 		*out = new(string)
 		**out = **in
 	}
+	if in.Encoding != nil {
+		in, out := &in.Encoding, &out.Encoding
+		*out = new(string)
+		**out = **in
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PasswordSpec.

+ 17 - 0
config/crds/bases/generators.external-secrets.io_clustergenerators.yaml

@@ -853,6 +853,23 @@ spec:
                           Digits specifies the number of digits in the generated
                           password. If omitted it defaults to 25% of the length of the password
                         type: integer
+                      encoding:
+                        default: raw
+                        description: |-
+                          Encoding specifies the encoding of the generated password.
+                          Valid values are:
+                          - "raw" (default): no encoding
+                          - "base64": standard base64 encoding
+                          - "base64url": base64url encoding
+                          - "base32": base32 encoding
+                          - "hex": hexadecimal encoding
+                        enum:
+                        - base64
+                        - base64url
+                        - base32
+                        - hex
+                        - raw
+                        type: string
                       length:
                         default: 24
                         description: |-

+ 17 - 0
config/crds/bases/generators.external-secrets.io_passwords.yaml

@@ -55,6 +55,23 @@ spec:
                   Digits specifies the number of digits in the generated
                   password. If omitted it defaults to 25% of the length of the password
                 type: integer
+              encoding:
+                default: raw
+                description: |-
+                  Encoding specifies the encoding of the generated password.
+                  Valid values are:
+                  - "raw" (default): no encoding
+                  - "base64": standard base64 encoding
+                  - "base64url": base64url encoding
+                  - "base32": base32 encoding
+                  - "hex": hexadecimal encoding
+                enum:
+                - base64
+                - base64url
+                - base32
+                - hex
+                - raw
+                type: string
               length:
                 default: 24
                 description: |-

+ 34 - 0
deploy/crds/bundle.yaml

@@ -23795,6 +23795,23 @@ spec:
                             Digits specifies the number of digits in the generated
                             password. If omitted it defaults to 25% of the length of the password
                           type: integer
+                        encoding:
+                          default: raw
+                          description: |-
+                            Encoding specifies the encoding of the generated password.
+                            Valid values are:
+                            - "raw" (default): no encoding
+                            - "base64": standard base64 encoding
+                            - "base64url": base64url encoding
+                            - "base32": base32 encoding
+                            - "hex": hexadecimal encoding
+                          enum:
+                            - base64
+                            - base64url
+                            - base32
+                            - hex
+                            - raw
+                          type: string
                         length:
                           default: 24
                           description: |-
@@ -26066,6 +26083,23 @@ spec:
                     Digits specifies the number of digits in the generated
                     password. If omitted it defaults to 25% of the length of the password
                   type: integer
+                encoding:
+                  default: raw
+                  description: |-
+                    Encoding specifies the encoding of the generated password.
+                    Valid values are:
+                    - "raw" (default): no encoding
+                    - "base64": standard base64 encoding
+                    - "base64url": base64url encoding
+                    - "base32": base32 encoding
+                    - "hex": hexadecimal encoding
+                  enum:
+                    - base64
+                    - base64url
+                    - base32
+                    - hex
+                    - raw
+                  type: string
                 length:
                   default: 24
                   description: |-

+ 25 - 0
docs/api/generator/password.md

@@ -21,6 +21,7 @@ You can influence the behavior of the generator by providing the following args
 | symbolCharacters | ~!@#$%^&\*()\_+`-={}\|[]\\:"<>?,./ | Specify the character set that should be used when generating the password. |
 | noUpper          | false                              | disable uppercase characters.                                               |
 | allowRepeat      | false                              | allow repeating characters.                                                 |
+| encoding         | raw                                | Encoding format for the generated password. Valid values: `raw`, `base64`, `base64url`, `base32`, `hex`. |
 
 ## Example Manifest
 
@@ -49,3 +50,27 @@ With default values you would get something like:
 ZRv-k!y6x/V"29:43aErSf$1
 Vk9*mwXE30Q+>H?lY$5I64_q
 ```
+
+## Encoding Examples
+
+The password generator supports different encoding formats for the output:
+
+```yaml
+{% include 'generator-password-encoding-examples.yaml' %}
+```
+
+### Encoding Output Examples
+
+For the same password `Test>>Pass??word`, the different encodings would produce:
+
+- **raw** (default): `Test>>Pass??word` (original password string)
+- **base64**: `VGVzdD4+UGFzcz8/d29yZA==` (standard base64)
+- **base64url**: `VGVzdD4-UGFzcz8_d29yZA==` (URL-safe base64)
+- **base32**: `ORSXG5BRGIYTEMJQGQYQ====` (base32 encoding)
+- **hex**: `546573743e3e506173733f3f776f7264` (hexadecimal encoding)
+
+Key differences between `base64` and `base64url`:
+
+- **base64**: `VGVzdD4+UGFzcz8/d29yZA==` uses `+`, `/`, and `=` for padding
+
+- **base64url**: `VGVzdD4-UGFzcz8_d29yZA==` uses `-`, `_`, and no padding (URL-safe)

+ 35 - 0
docs/snippets/generator-password-encoding-examples.yaml

@@ -0,0 +1,35 @@
+# Example with hex encoding
+apiVersion: generators.external-secrets.io/v1alpha1
+kind: Password
+metadata:
+  name: password-hex
+spec:
+  length: 16
+  encoding: "hex"
+---
+# Example with base32 encoding
+apiVersion: generators.external-secrets.io/v1alpha1
+kind: Password
+metadata:
+  name: password-base32
+spec:
+  length: 20
+  encoding: "base32"
+---
+# Example with raw encoding (no encoding)
+apiVersion: generators.external-secrets.io/v1alpha1
+kind: Password
+metadata:
+  name: password-raw
+spec:
+  length: 12
+  encoding: "raw"
+---
+# Example with base64url encoding
+apiVersion: generators.external-secrets.io/v1alpha1
+kind: Password
+metadata:
+  name: password-base64url
+spec:
+  length: 24
+  encoding: "base64url"

+ 32 - 1
pkg/generator/password/password.go

@@ -19,6 +19,9 @@ package password
 
 import (
 	"context"
+	"encoding/base32"
+	"encoding/base64"
+	"encoding/hex"
 	"errors"
 	"fmt"
 
@@ -101,8 +104,17 @@ func (g *Generator) generate(jsonSpec *apiextensions.JSON, passGen generateFunc)
 	if err != nil {
 		return nil, nil, err
 	}
+
+	// Apply encoding
+	encoding := "raw"
+	if res.Spec.Encoding != nil {
+		encoding = *res.Spec.Encoding
+	}
+
+	encodedPass := encodePassword([]byte(pass), encoding)
+
 	return map[string][]byte{
-		"password": []byte(pass),
+		"password": encodedPass,
 	}, nil, nil
 }
 
@@ -129,6 +141,25 @@ func generateSafePassword(
 	)
 }
 
+func encodePassword(b []byte, encoding string) []byte {
+	var encodedString string
+	switch encoding {
+	case "base64url":
+		encodedString = base64.URLEncoding.EncodeToString(b)
+	case "raw":
+		return b
+	case "base32":
+		encodedString = base32.StdEncoding.EncodeToString(b)
+	case "hex":
+		encodedString = hex.EncodeToString(b)
+	case "base64":
+		encodedString = base64.StdEncoding.EncodeToString(b)
+	default:
+		return b
+	}
+	return []byte(encodedString)
+}
+
 func parseSpec(data []byte) (*genv1alpha1.Password, error) {
 	var spec genv1alpha1.Password
 	err := yaml.Unmarshal(data, &spec)

+ 50 - 0
pkg/generator/password/password_test.go

@@ -17,6 +17,8 @@ limitations under the License.
 package password
 
 import (
+	"encoding/base64"
+	"encoding/hex"
 	"errors"
 	"reflect"
 	"testing"
@@ -110,6 +112,54 @@ func TestGenerate(t *testing.T) {
 			},
 			wantErr: true,
 		},
+		{
+			name: "spec with hex encoding should encode password as hex",
+			args: args{
+				jsonSpec: &apiextensions.JSON{
+					Raw: []byte(`{"spec":{"encoding":"hex"}}`),
+				},
+				passGen: func(len int, symbols int, symbolCharacters string, digits int, noUpper bool, allowRepeat bool,
+				) (string, error) {
+					return "test_hex", nil
+				},
+			},
+			want: map[string][]byte{
+				"password": []byte(hex.EncodeToString([]byte("test_hex"))),
+			},
+			wantErr: false,
+		},
+		{
+			name: "spec with raw encoding should return raw password",
+			args: args{
+				jsonSpec: &apiextensions.JSON{
+					Raw: []byte(`{"spec":{"encoding":"raw"}}`),
+				},
+				passGen: func(len int, symbols int, symbolCharacters string, digits int, noUpper bool, allowRepeat bool,
+				) (string, error) {
+					return "test_raw", nil
+				},
+			},
+			want: map[string][]byte{
+				"password": []byte(`test_raw`),
+			},
+			wantErr: false,
+		},
+		{
+			name: "spec with base64 encoding should encode password as base64",
+			args: args{
+				jsonSpec: &apiextensions.JSON{
+					Raw: []byte(`{"spec":{"encoding":"base64"}}`),
+				},
+				passGen: func(len int, symbols int, symbolCharacters string, digits int, noUpper bool, allowRepeat bool,
+				) (string, error) {
+					return "test_base64", nil
+				},
+			},
+			want: map[string][]byte{
+				"password": []byte(base64.StdEncoding.EncodeToString([]byte("test_base64"))),
+			},
+			wantErr: false,
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {

+ 1 - 0
tests/__snapshot__/clustergenerator-v1alpha1.yaml

@@ -130,6 +130,7 @@ spec:
     passwordSpec:
       allowRepeat: false
       digits: 1
+      encoding: "raw"
       length: 24
       noUpper: false
       symbolCharacters: string

+ 1 - 0
tests/__snapshot__/password-v1alpha1.yaml

@@ -4,6 +4,7 @@ metadata: {}
 spec:
   allowRepeat: false
   digits: 1
+  encoding: "raw"
   length: 24
   noUpper: false
   symbolCharacters: string