auth_gcp_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package vault
  14. import (
  15. "context"
  16. "os"
  17. "testing"
  18. "github.com/go-logr/logr"
  19. corev1 "k8s.io/api/core/v1"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
  22. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  23. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  24. )
  25. func TestSetEnvVar(t *testing.T) {
  26. c := &client{
  27. log: logr.Discard(),
  28. }
  29. tests := []struct {
  30. name string
  31. key string
  32. value string
  33. wantError bool
  34. }{
  35. {
  36. name: "valid environment variable",
  37. key: "TEST_VAR",
  38. value: "test_value",
  39. wantError: false,
  40. },
  41. {
  42. name: "empty value should error",
  43. key: "TEST_VAR",
  44. value: "",
  45. wantError: true,
  46. },
  47. }
  48. for _, tt := range tests {
  49. t.Run(tt.name, func(t *testing.T) {
  50. // Clean up environment variable after test
  51. defer func() {
  52. if tt.key != "" {
  53. os.Unsetenv(tt.key)
  54. }
  55. }()
  56. err := c.setEnvVar(tt.key, tt.value)
  57. if tt.wantError && err == nil {
  58. t.Errorf("setEnvVar() expected error, got nil")
  59. }
  60. if !tt.wantError && err != nil {
  61. t.Errorf("setEnvVar() unexpected error: %v", err)
  62. }
  63. // If successful, verify the environment variable was actually set
  64. if !tt.wantError && err == nil {
  65. actualValue := os.Getenv(tt.key)
  66. if actualValue != tt.value {
  67. t.Errorf("setEnvVar() environment variable not set correctly, got %v, want %v", actualValue, tt.value)
  68. }
  69. }
  70. })
  71. }
  72. }
  73. func TestSetGCPEnvironment(t *testing.T) {
  74. c := &client{
  75. log: logr.Discard(),
  76. }
  77. tests := []struct {
  78. name string
  79. accessToken string
  80. wantError bool
  81. }{
  82. {
  83. name: "valid access token",
  84. accessToken: "ya29.test-token",
  85. wantError: false,
  86. },
  87. {
  88. name: "empty access token",
  89. accessToken: "",
  90. wantError: true,
  91. },
  92. }
  93. for _, tt := range tests {
  94. t.Run(tt.name, func(t *testing.T) {
  95. // Clean up environment variable after test
  96. defer os.Unsetenv(googleOAuthAccessTokenKey)
  97. err := c.setGCPEnvironment(tt.accessToken)
  98. if tt.wantError && err == nil {
  99. t.Errorf("setGCPEnvironment() expected error, got nil")
  100. }
  101. if !tt.wantError && err != nil {
  102. t.Errorf("setGCPEnvironment() unexpected error: %v", err)
  103. }
  104. // If successful, verify the GOOGLE_OAUTH_ACCESS_TOKEN was set
  105. if !tt.wantError && err == nil {
  106. actualValue := os.Getenv(googleOAuthAccessTokenKey)
  107. if actualValue != tt.accessToken {
  108. t.Errorf("setGCPEnvironment() %s not set correctly, got %v, want %v", googleOAuthAccessTokenKey, actualValue, tt.accessToken)
  109. }
  110. }
  111. })
  112. }
  113. }
  114. func TestSetupDefaultGCPAuth(t *testing.T) {
  115. c := &client{
  116. log: logr.Discard(),
  117. }
  118. err := c.setupDefaultGCPAuth()
  119. // In test environments, ADC might not be available, so we expect this to potentially fail.
  120. // This is expected behavior - the function should fail gracefully if ADC is not configured.
  121. // The test verifies that the function behaves correctly in both scenarios:
  122. // 1. ADC available: function succeeds
  123. // 2. ADC unavailable: function fails with meaningful error message
  124. if err != nil {
  125. t.Logf("setupDefaultGCPAuth() failed as expected in test environment without ADC: %v", err)
  126. // Verify the error message indicates ADC unavailability
  127. if err.Error() == "" {
  128. t.Errorf("setupDefaultGCPAuth() should provide meaningful error message when ADC is unavailable")
  129. }
  130. } else {
  131. t.Logf("setupDefaultGCPAuth() succeeded - ADC is configured and available in test environment")
  132. }
  133. }
  134. func TestSetupGCPAuthPriority(t *testing.T) {
  135. c := &client{
  136. log: logr.Discard(),
  137. kube: clientfake.NewClientBuilder().Build(),
  138. namespace: "default",
  139. storeKind: "SecretStore",
  140. }
  141. tests := []struct {
  142. name string
  143. gcpAuth *esv1.VaultGCPAuth
  144. expectError bool
  145. description string
  146. }{
  147. {
  148. name: "SecretRef has priority",
  149. gcpAuth: &esv1.VaultGCPAuth{
  150. Role: "test-role",
  151. ProjectID: "test-project",
  152. SecretRef: &esv1.GCPSMAuthSecretRef{
  153. SecretAccessKey: esmeta.SecretKeySelector{
  154. Name: "gcp-secret",
  155. Key: "credentials.json",
  156. },
  157. },
  158. WorkloadIdentity: &esv1.GCPWorkloadIdentity{
  159. ServiceAccountRef: esmeta.ServiceAccountSelector{
  160. Name: "test-sa",
  161. },
  162. },
  163. },
  164. expectError: true, // Will fail because secret doesn't exist in fake client
  165. description: "SecretRef should be tried first",
  166. },
  167. {
  168. name: "WorkloadIdentity second priority",
  169. gcpAuth: &esv1.VaultGCPAuth{
  170. Role: "test-role",
  171. ProjectID: "test-project",
  172. WorkloadIdentity: &esv1.GCPWorkloadIdentity{
  173. ServiceAccountRef: esmeta.ServiceAccountSelector{
  174. Name: "test-sa",
  175. },
  176. },
  177. ServiceAccountRef: &esmeta.ServiceAccountSelector{
  178. Name: "test-sa",
  179. },
  180. },
  181. expectError: true, // Will fail because workload identity setup will fail
  182. description: "WorkloadIdentity should be tried when SecretRef is nil",
  183. },
  184. {
  185. name: "ServiceAccountRef third priority",
  186. gcpAuth: &esv1.VaultGCPAuth{
  187. Role: "test-role",
  188. ServiceAccountRef: &esmeta.ServiceAccountSelector{
  189. Name: "test-sa",
  190. },
  191. },
  192. expectError: false, // Should fall back to default auth
  193. description: "ServiceAccountRef should fall back to default auth",
  194. },
  195. {
  196. name: "Default auth last resort",
  197. gcpAuth: &esv1.VaultGCPAuth{
  198. Role: "test-role",
  199. },
  200. expectError: false, // Test handles both ADC available and unavailable scenarios
  201. description: "Should attempt default ADC when no other auth method is specified",
  202. },
  203. }
  204. for _, tt := range tests {
  205. t.Run(tt.name, func(t *testing.T) {
  206. err := c.setupGCPAuth(context.Background(), tt.gcpAuth)
  207. // For default auth test, ADC availability depends on the test environment.
  208. // We accept both success (ADC configured) and failure (ADC not available)
  209. // as valid outcomes, since this test should pass in any environment.
  210. if tt.name == "Default auth last resort" {
  211. if err != nil {
  212. t.Logf("setupGCPAuth() with default ADC: %v (expected behavior in environments without ADC configured)", err)
  213. } else {
  214. t.Logf("setupGCPAuth() with default ADC succeeded (ADC is properly configured in this environment)")
  215. }
  216. return
  217. }
  218. if tt.expectError && err == nil {
  219. t.Errorf("setupGCPAuth() expected error for %s, got nil", tt.description)
  220. }
  221. if !tt.expectError && err != nil {
  222. t.Errorf("setupGCPAuth() unexpected error for %s: %v", tt.description, err)
  223. }
  224. })
  225. }
  226. }
  227. func TestGCPAuthMethodSelection(t *testing.T) {
  228. tests := []struct {
  229. name string
  230. setupClient func() *client
  231. gcpAuth *esv1.VaultGCPAuth
  232. expectError bool
  233. description string
  234. }{
  235. {
  236. name: "SecretRef method selected",
  237. setupClient: func() *client {
  238. // Create a secret with invalid credentials to trigger expected error
  239. secret := &corev1.Secret{
  240. ObjectMeta: metav1.ObjectMeta{
  241. Name: "gcp-secret",
  242. Namespace: "default",
  243. },
  244. Data: map[string][]byte{
  245. "credentials.json": []byte(`{"type": "service_account", "project_id": "test"}`),
  246. },
  247. }
  248. kube := clientfake.NewClientBuilder().WithObjects(secret).Build()
  249. return &client{
  250. log: logr.Discard(),
  251. kube: kube,
  252. namespace: "default",
  253. storeKind: "SecretStore",
  254. }
  255. },
  256. gcpAuth: &esv1.VaultGCPAuth{
  257. Role: "test-role",
  258. ProjectID: "test-project",
  259. SecretRef: &esv1.GCPSMAuthSecretRef{
  260. SecretAccessKey: esmeta.SecretKeySelector{
  261. Name: "gcp-secret",
  262. Key: "credentials.json",
  263. },
  264. },
  265. },
  266. expectError: true, // Expected to fail in test environment
  267. description: "Should attempt to use SecretRef method",
  268. },
  269. {
  270. name: "WorkloadIdentity method selected",
  271. setupClient: func() *client {
  272. return &client{
  273. log: logr.Discard(),
  274. kube: clientfake.NewClientBuilder().Build(),
  275. namespace: "default",
  276. storeKind: "SecretStore",
  277. }
  278. },
  279. gcpAuth: &esv1.VaultGCPAuth{
  280. Role: "test-role",
  281. ProjectID: "test-project",
  282. WorkloadIdentity: &esv1.GCPWorkloadIdentity{
  283. ServiceAccountRef: esmeta.ServiceAccountSelector{
  284. Name: "test-sa",
  285. },
  286. },
  287. },
  288. expectError: true, // Expected to fail in test environment
  289. description: "Should attempt to use WorkloadIdentity method",
  290. },
  291. {
  292. name: "Default ADC method selected",
  293. setupClient: func() *client {
  294. return &client{
  295. log: logr.Discard(),
  296. kube: clientfake.NewClientBuilder().Build(),
  297. namespace: "default",
  298. storeKind: "SecretStore",
  299. }
  300. },
  301. gcpAuth: &esv1.VaultGCPAuth{
  302. Role: "test-role",
  303. },
  304. expectError: false, // Test handles both ADC available and unavailable scenarios
  305. description: "Should attempt to use default ADC method when no explicit auth is configured",
  306. },
  307. }
  308. for _, tt := range tests {
  309. t.Run(tt.name, func(t *testing.T) {
  310. c := tt.setupClient()
  311. err := c.setupGCPAuth(context.Background(), tt.gcpAuth)
  312. // For default ADC test, availability depends on the test environment.
  313. // We accept both success (ADC configured) and failure (ADC not available)
  314. // as valid outcomes to ensure tests pass in all environments.
  315. if tt.name == "Default ADC method selected" {
  316. if err != nil {
  317. t.Logf("%s: %v (expected behavior in environments without ADC configured)", tt.description, err)
  318. } else {
  319. t.Logf("%s: succeeded (ADC is properly configured in this environment)", tt.description)
  320. }
  321. t.Logf("%s: test completed successfully", tt.description)
  322. return
  323. }
  324. if tt.expectError && err == nil {
  325. t.Errorf("%s: expected error but got none", tt.description)
  326. }
  327. if !tt.expectError && err != nil {
  328. t.Errorf("%s: unexpected error: %v", tt.description, err)
  329. }
  330. // All tests should at least not panic and follow the correct code path
  331. t.Logf("%s: test completed successfully", tt.description)
  332. })
  333. }
  334. }