mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 02:28:09 +00:00 
			
		
		
		
	cli: Add 'agent generate-config' sub-command (#20530)
This commit is contained in:
		 Anton Averchenkov
					Anton Averchenkov
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							3d7d8f4965
						
					
				
				
					commit
					1a1af69cdd
				
			
							
								
								
									
										274
									
								
								command/agent_generate_config_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										274
									
								
								command/agent_generate_config_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,274 @@ | ||||
| // Copyright (c) HashiCorp, Inc. | ||||
| // SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| package command | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"reflect" | ||||
| 	"regexp" | ||||
| 	"testing" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| // TestConstructTemplates tests the construcTemplates helper function | ||||
| func TestConstructTemplates(t *testing.T) { | ||||
| 	ctx, cancelContextFunc := context.WithTimeout(context.Background(), 5*time.Second) | ||||
| 	defer cancelContextFunc() | ||||
|  | ||||
| 	client, closer := testVaultServerWithSecrets(ctx, t) | ||||
| 	defer closer() | ||||
|  | ||||
| 	cases := map[string]struct { | ||||
| 		paths         []string | ||||
| 		expected      []generatedConfigEnvTemplate | ||||
| 		expectedError bool | ||||
| 	}{ | ||||
| 		"kv-v1-simple": { | ||||
| 			paths: []string{"kv-v1/foo"}, | ||||
| 			expected: []generatedConfigEnvTemplate{ | ||||
| 				{Contents: `{{ with secret "kv-v1/foo" }}{{ .Data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"}, | ||||
| 				{Contents: `{{ with secret "kv-v1/foo" }}{{ .Data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"}, | ||||
| 			}, | ||||
| 			expectedError: false, | ||||
| 		}, | ||||
|  | ||||
| 		"kv-v2-simple": { | ||||
| 			paths: []string{"kv-v2/foo"}, | ||||
| 			expected: []generatedConfigEnvTemplate{ | ||||
| 				{Contents: `{{ with secret "kv-v2/data/foo" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"}, | ||||
| 				{Contents: `{{ with secret "kv-v2/data/foo" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"}, | ||||
| 			}, | ||||
| 			expectedError: false, | ||||
| 		}, | ||||
|  | ||||
| 		"kv-v2-data-in-path": { | ||||
| 			paths: []string{"kv-v2/data/foo"}, | ||||
| 			expected: []generatedConfigEnvTemplate{ | ||||
| 				{Contents: `{{ with secret "kv-v2/data/foo" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"}, | ||||
| 				{Contents: `{{ with secret "kv-v2/data/foo" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"}, | ||||
| 			}, | ||||
| 			expectedError: false, | ||||
| 		}, | ||||
|  | ||||
| 		"kv-v1-nested": { | ||||
| 			paths: []string{"kv-v1/app-1/*"}, | ||||
| 			expected: []generatedConfigEnvTemplate{ | ||||
| 				{Contents: `{{ with secret "kv-v1/app-1/bar" }}{{ .Data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_PASSWORD"}, | ||||
| 				{Contents: `{{ with secret "kv-v1/app-1/bar" }}{{ .Data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_USER"}, | ||||
| 				{Contents: `{{ with secret "kv-v1/app-1/foo" }}{{ .Data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"}, | ||||
| 				{Contents: `{{ with secret "kv-v1/app-1/foo" }}{{ .Data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"}, | ||||
| 				{Contents: `{{ with secret "kv-v1/app-1/nested/baz" }}{{ .Data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAZ_PASSWORD"}, | ||||
| 				{Contents: `{{ with secret "kv-v1/app-1/nested/baz" }}{{ .Data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAZ_USER"}, | ||||
| 			}, | ||||
| 			expectedError: false, | ||||
| 		}, | ||||
|  | ||||
| 		"kv-v2-nested": { | ||||
| 			paths: []string{"kv-v2/app-1/*"}, | ||||
| 			expected: []generatedConfigEnvTemplate{ | ||||
| 				{Contents: `{{ with secret "kv-v2/data/app-1/bar" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_PASSWORD"}, | ||||
| 				{Contents: `{{ with secret "kv-v2/data/app-1/bar" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_USER"}, | ||||
| 				{Contents: `{{ with secret "kv-v2/data/app-1/foo" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"}, | ||||
| 				{Contents: `{{ with secret "kv-v2/data/app-1/foo" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"}, | ||||
| 				{Contents: `{{ with secret "kv-v2/data/app-1/nested/baz" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAZ_PASSWORD"}, | ||||
| 				{Contents: `{{ with secret "kv-v2/data/app-1/nested/baz" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAZ_USER"}, | ||||
| 			}, | ||||
| 			expectedError: false, | ||||
| 		}, | ||||
|  | ||||
| 		"kv-v1-multi-path": { | ||||
| 			paths: []string{"kv-v1/foo", "kv-v1/app-1/bar"}, | ||||
| 			expected: []generatedConfigEnvTemplate{ | ||||
| 				{Contents: `{{ with secret "kv-v1/foo" }}{{ .Data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"}, | ||||
| 				{Contents: `{{ with secret "kv-v1/foo" }}{{ .Data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"}, | ||||
| 				{Contents: `{{ with secret "kv-v1/app-1/bar" }}{{ .Data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_PASSWORD"}, | ||||
| 				{Contents: `{{ with secret "kv-v1/app-1/bar" }}{{ .Data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_USER"}, | ||||
| 			}, | ||||
| 			expectedError: false, | ||||
| 		}, | ||||
|  | ||||
| 		"kv-v2-multi-path": { | ||||
| 			paths: []string{"kv-v2/foo", "kv-v2/app-1/bar"}, | ||||
| 			expected: []generatedConfigEnvTemplate{ | ||||
| 				{Contents: `{{ with secret "kv-v2/data/foo" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_PASSWORD"}, | ||||
| 				{Contents: `{{ with secret "kv-v2/data/foo" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "FOO_USER"}, | ||||
| 				{Contents: `{{ with secret "kv-v2/data/app-1/bar" }}{{ .Data.data.password }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_PASSWORD"}, | ||||
| 				{Contents: `{{ with secret "kv-v2/data/app-1/bar" }}{{ .Data.data.user }}{{ end }}`, ErrorOnMissingKey: true, Name: "BAR_USER"}, | ||||
| 			}, | ||||
| 			expectedError: false, | ||||
| 		}, | ||||
|  | ||||
| 		"kv-v1-path-not-found": { | ||||
| 			paths:         []string{"kv-v1/does/not/exist"}, | ||||
| 			expected:      nil, | ||||
| 			expectedError: true, | ||||
| 		}, | ||||
|  | ||||
| 		"kv-v2-path-not-found": { | ||||
| 			paths:         []string{"kv-v2/does/not/exist"}, | ||||
| 			expected:      nil, | ||||
| 			expectedError: true, | ||||
| 		}, | ||||
|  | ||||
| 		"kv-v1-early-wildcard": { | ||||
| 			paths:         []string{"kv-v1/*/foo"}, | ||||
| 			expected:      nil, | ||||
| 			expectedError: true, | ||||
| 		}, | ||||
|  | ||||
| 		"kv-v2-early-wildcard": { | ||||
| 			paths:         []string{"kv-v2/*/foo"}, | ||||
| 			expected:      nil, | ||||
| 			expectedError: true, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for name, tc := range cases { | ||||
| 		name, tc := name, tc | ||||
|  | ||||
| 		t.Run(name, func(t *testing.T) { | ||||
| 			templates, err := constructTemplates(ctx, client, tc.paths) | ||||
|  | ||||
| 			if tc.expectedError { | ||||
| 				if err == nil { | ||||
| 					t.Fatal("an error was expected but the test succeeded") | ||||
| 				} | ||||
| 			} else { | ||||
| 				if err != nil { | ||||
| 					t.Fatal(err) | ||||
| 				} | ||||
|  | ||||
| 				if !reflect.DeepEqual(tc.expected, templates) { | ||||
| 					t.Fatalf("unexpected output; want: %v, got: %v", tc.expected, templates) | ||||
| 				} | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // TestGenerateConfiguration tests the generateConfiguration helper function | ||||
| func TestGenerateConfiguration(t *testing.T) { | ||||
| 	ctx, cancelContextFunc := context.WithTimeout(context.Background(), 5*time.Second) | ||||
| 	defer cancelContextFunc() | ||||
|  | ||||
| 	client, closer := testVaultServerWithSecrets(ctx, t) | ||||
| 	defer closer() | ||||
|  | ||||
| 	cases := map[string]struct { | ||||
| 		flagExec      string | ||||
| 		flagPaths     []string | ||||
| 		expected      *regexp.Regexp | ||||
| 		expectedError bool | ||||
| 	}{ | ||||
| 		"kv-v1-simple": { | ||||
| 			flagExec:  "./my-app arg1 arg2", | ||||
| 			flagPaths: []string{"kv-v1/foo"}, | ||||
| 			expected: regexp.MustCompile(` | ||||
| auto_auth \{ | ||||
|  | ||||
|   method \{ | ||||
|     type = "token_file" | ||||
|  | ||||
|     config \{ | ||||
|       token_file_path = ".*/.vault-token" | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| template_config \{ | ||||
|   static_secret_render_interval = "5m" | ||||
|   exit_on_retry_failure         = true | ||||
| } | ||||
|  | ||||
| vault \{ | ||||
|   address = "https://127.0.0.1:[0-9]{5}" | ||||
| } | ||||
|  | ||||
| env_template "FOO_PASSWORD" \{ | ||||
|   contents             = "\{\{ with secret \\"kv-v1/foo\\" }}\{\{ .Data.password }}\{\{ end }}" | ||||
|   error_on_missing_key = true | ||||
| } | ||||
| env_template "FOO_USER" \{ | ||||
|   contents             = "\{\{ with secret \\"kv-v1/foo\\" }}\{\{ .Data.user }}\{\{ end }}" | ||||
|   error_on_missing_key = true | ||||
| } | ||||
|  | ||||
| exec \{ | ||||
|   command                   = \["./my-app", "arg1", "arg2"\] | ||||
|   restart_on_secret_changes = "always" | ||||
|   restart_stop_signal       = "SIGTERM" | ||||
| } | ||||
| `), | ||||
| 			expectedError: false, | ||||
| 		}, | ||||
|  | ||||
| 		"kv-v2-default-exec": { | ||||
| 			flagExec:  "", | ||||
| 			flagPaths: []string{"kv-v2/foo"}, | ||||
| 			expected: regexp.MustCompile(` | ||||
| auto_auth \{ | ||||
|  | ||||
|   method \{ | ||||
|     type = "token_file" | ||||
|  | ||||
|     config \{ | ||||
|       token_file_path = ".*/.vault-token" | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| template_config \{ | ||||
|   static_secret_render_interval = "5m" | ||||
|   exit_on_retry_failure         = true | ||||
| } | ||||
|  | ||||
| vault \{ | ||||
|   address = "https://127.0.0.1:[0-9]{5}" | ||||
| } | ||||
|  | ||||
| env_template "FOO_PASSWORD" \{ | ||||
|   contents             = "\{\{ with secret \\"kv-v2/data/foo\\" }}\{\{ .Data.data.password }}\{\{ end }}" | ||||
|   error_on_missing_key = true | ||||
| } | ||||
| env_template "FOO_USER" \{ | ||||
|   contents             = "\{\{ with secret \\"kv-v2/data/foo\\" }}\{\{ .Data.data.user }}\{\{ end }}" | ||||
|   error_on_missing_key = true | ||||
| } | ||||
|  | ||||
| exec \{ | ||||
|   command                   = \["env"\] | ||||
|   restart_on_secret_changes = "always" | ||||
|   restart_stop_signal       = "SIGTERM" | ||||
| } | ||||
| `), | ||||
| 			expectedError: false, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for name, tc := range cases { | ||||
| 		name, tc := name, tc | ||||
|  | ||||
| 		t.Run(name, func(t *testing.T) { | ||||
| 			var config bytes.Buffer | ||||
|  | ||||
| 			c, err := generateConfiguration(ctx, client, tc.flagExec, tc.flagPaths) | ||||
| 			c.WriteTo(&config) | ||||
|  | ||||
| 			if tc.expectedError { | ||||
| 				if err == nil { | ||||
| 					t.Fatal("an error was expected but the test succeeded") | ||||
| 				} | ||||
| 			} else { | ||||
| 				if err != nil { | ||||
| 					t.Fatal(err) | ||||
| 				} | ||||
|  | ||||
| 				if !tc.expected.MatchString(config.String()) { | ||||
| 					t.Fatalf("unexpected output; want: %v, got: %v", tc.expected.String(), config.String()) | ||||
| 				} | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user