A Kubernetes operator that bridges Hardware Security Module (HSM) data storage with Kubernetes Secrets, providing true secret portability th
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

merge keys on create by default, add --replace flag

+56 -8
+29 -1
kubectl-hsm/pkg/client/client.go
··· 44 44 } 45 45 } 46 46 47 - // CreateSecret creates a new secret in the HSM 47 + // CreateSecret creates a new secret in the HSM, merging with existing data if present 48 48 func (c *Client) CreateSecret(ctx context.Context, name string, data map[string]any) error { 49 + return c.CreateSecretWithOptions(ctx, name, data, false) 50 + } 51 + 52 + // CreateSecretWithOptions creates a new secret in the HSM with replace option 53 + func (c *Client) CreateSecretWithOptions(ctx context.Context, name string, data map[string]any, replace bool) error { 54 + // Only merge if replace is false 55 + if !replace { 56 + // Try to read existing secret first for merge behavior 57 + existing, err := c.GetSecret(ctx, name) 58 + if err == nil && existing != nil { 59 + // Merge existing data with new data (new data takes precedence) 60 + mergedData := make(map[string]any) 61 + 62 + // Start with existing data 63 + for k, v := range existing.Data { 64 + mergedData[k] = v 65 + } 66 + 67 + // Override/add with new data 68 + for k, v := range data { 69 + mergedData[k] = v 70 + } 71 + 72 + data = mergedData 73 + } 74 + // If error reading existing secret, continue with original data (new secret) 75 + } 76 + 49 77 req := CreateSecretRequest{ 50 78 Data: data, 51 79 }
+27 -7
kubectl-hsm/pkg/commands/create.go
··· 31 31 FromFile []string 32 32 FromJsonFile string 33 33 Interactive bool 34 + Replace bool 34 35 } 35 36 36 37 // NewCreateCmd creates the create command ··· 39 40 40 41 cmd := &cobra.Command{ 41 42 Use: "create SECRET_NAME [flags]", 42 - Short: "Create a new HSM secret", 43 - Long: `Create a new secret in the HSM. 43 + Short: "Create or update an HSM secret", 44 + Long: `Create a new secret in the HSM, or add keys to an existing secret. 45 + 46 + By default, if the secret already exists, new keys will be added and existing keys with the same name will be updated. 47 + Existing keys not specified in the command will be preserved. Use --replace to completely replace the secret instead. 44 48 45 49 The secret data can be provided in several ways: 46 50 - --from-literal key=value: Specify key-value pairs directly ··· 51 55 # Create secret with literal values 52 56 kubectl hsm create database-creds --from-literal username=admin --from-literal password=secret123 53 57 58 + # Add a new key to existing secret (preserves username and password) 59 + kubectl hsm create database-creds --from-literal connection_string="postgres://..." 60 + 61 + # Replace entire secret (removes username and password, only keeps api_key) 62 + kubectl hsm create database-creds --from-literal api_key=xyz123 --replace 63 + 54 64 # Load values from files 55 65 kubectl hsm create tls-cert --from-file cert=server.crt --from-file key=server.key 56 66 ··· 66 76 cmd.Flags().StringArrayVar(&opts.FromFile, "from-file", nil, "Load secret data from files. Use 'key=file' or just 'file' (uses filename without extension as key)") 67 77 cmd.Flags().StringVar(&opts.FromJsonFile, "from-json-file", "", "Load secret data from a JSON file with structure {\"name\":\"secret-name\",\"secrets\":[{\"key\":\"k\",\"value\":\"v\"}]}") 68 78 cmd.Flags().BoolVar(&opts.Interactive, "interactive", false, "Prompt for secret values interactively") 79 + cmd.Flags().BoolVar(&opts.Replace, "replace", false, "Replace the entire secret instead of merging with existing data") 69 80 cmd.Flags().StringVarP(&opts.Namespace, "namespace", "n", "", "Override the default namespace") 70 81 cmd.Flags().StringVarP(&opts.Output, "output", "o", "text", "Output format (text, json, yaml)") 71 82 cmd.Flags().BoolVarP(&opts.Verbose, "verbose", "v", false, "Show verbose output including port forward details") ··· 168 179 return err 169 180 } 170 181 171 - // Create the secret 172 - fmt.Printf("Creating secret '%s' in namespace '%s'...\n", secretName, cm.GetCurrentNamespace()) 173 - if err := hsmClient.CreateSecret(ctx, secretName, secretData); err != nil { 174 - return fmt.Errorf("failed to create secret: %w", err) 182 + // Create the secret (with merge or replace behavior) 183 + if opts.Replace { 184 + fmt.Printf("Replacing secret '%s' in namespace '%s'...\n", secretName, cm.GetCurrentNamespace()) 185 + } else { 186 + fmt.Printf("Creating/updating secret '%s' in namespace '%s'...\n", secretName, cm.GetCurrentNamespace()) 187 + } 188 + 189 + if err := hsmClient.CreateSecretWithOptions(ctx, secretName, secretData, opts.Replace); err != nil { 190 + return fmt.Errorf("failed to create/update secret: %w", err) 175 191 } 176 192 177 - fmt.Printf("Secret '%s' created successfully.\n", secretName) 193 + if opts.Replace { 194 + fmt.Printf("Secret '%s' replaced successfully.\n", secretName) 195 + } else { 196 + fmt.Printf("Secret '%s' created/updated successfully.\n", secretName) 197 + } 178 198 179 199 // Show how to retrieve the secret 180 200 fmt.Printf("\nTo view the secret:\n")