webhook_auth_test.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. // /*
  2. // Licensed under the Apache License, Version 2.0 (the "License");
  3. // you may not use this file except in compliance with the License.
  4. // You may obtain a copy of the License at
  5. //
  6. // http://www.apache.org/licenses/LICENSE-2.0
  7. //
  8. // Unless required by applicable law or agreed to in writing, software
  9. // distributed under the License is distributed on an "AS IS" BASIS,
  10. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. // See the License for the specific language governing permissions and
  12. // limitations under the License.
  13. // */
  14. package webhook
  15. import (
  16. "context"
  17. b64 "encoding/base64"
  18. "net/http"
  19. "net/http/httptest"
  20. "testing"
  21. corev1 "k8s.io/api/core/v1"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "sigs.k8s.io/controller-runtime/pkg/client"
  24. "sigs.k8s.io/controller-runtime/pkg/client/fake"
  25. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  26. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  27. )
  28. type mockAuthTestPackage struct {
  29. Creds mockCreds
  30. MockServer mockAuthTestServer
  31. Request mockAuthRequest
  32. Expect string
  33. }
  34. type mockCreds struct {
  35. UserName string
  36. Password string
  37. }
  38. type mockAuthTestServer func(
  39. serverCreds mockCreds,
  40. t *testing.T) *httptest.Server
  41. type mockAuthRequest func(
  42. url string,
  43. creds mockCreds,
  44. t *testing.T) string
  45. func TestWebhookAuth(t *testing.T) {
  46. // define test cases
  47. creds := mockCreds{"correctuser123", "correctpassword123"}
  48. basicAuthExpect := "Basic " + b64.StdEncoding.EncodeToString([]byte(creds.UserName+":"+creds.Password))
  49. ntlmExpect := "NTLM TlRMTVNTUAABAAAAAQCIoAAAAAAoAAAAAAAAACgAAAAGAbEdAAAADw=="
  50. negotiateExpect := "Negotiate TlRMTVNTUAABAAAAAQCIoAAAAAAoAAAAAAAAACgAAAAGAbEdAAAADw=="
  51. // due to integrated nature of GetSecret(), we use a mock server
  52. // to return relevant parts of a request, in this case, the auth header.
  53. testAuthHeaders := map[string]mockAuthTestPackage{
  54. "BasicAuth": {creds, basicAuthRequestEcho, basicAuthRequest, basicAuthExpect},
  55. "NTLM": {creds, ntlmRequestEcho, ntlmRequest, ntlmExpect},
  56. "Negotiate": {creds, negotiateRequestEcho, ntlmRequest, negotiateExpect},
  57. }
  58. // execute test cases
  59. for _, p := range testAuthHeaders {
  60. server := p.MockServer(p.Creds, t)
  61. result := p.Request(server.URL, creds, t)
  62. server.Close()
  63. expect := p.Expect
  64. if result != expect {
  65. t.Errorf("Test failed. Result: '%s' / Expected: '%s'", result, expect)
  66. }
  67. }
  68. }
  69. func ntlmRequestEcho(creds mockCreds, t *testing.T) *httptest.Server {
  70. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  71. reqAuthString := r.Header.Get("Authorization")
  72. if reqAuthString == "" {
  73. // go-ntlmssp first sends anonymous request, respond with 401
  74. w.Header().Add("WWW-Authenticate", "NTLM")
  75. w.WriteHeader(401)
  76. } else {
  77. w.Write([]byte(reqAuthString))
  78. }
  79. }))
  80. return server
  81. }
  82. func negotiateRequestEcho(creds mockCreds, t *testing.T) *httptest.Server {
  83. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  84. reqAuthString := r.Header.Get("Authorization")
  85. if reqAuthString == "" {
  86. // go-ntlmssp first sends anonymous request, respond with 401
  87. w.Header().Add("WWW-Authenticate", "Negotiate")
  88. w.WriteHeader(401)
  89. } else {
  90. w.Write([]byte(reqAuthString))
  91. }
  92. }))
  93. return server
  94. }
  95. func ntlmRequest(url string, creds mockCreds, t *testing.T) string {
  96. secretName := "ntlmTestAuthSecret"
  97. secretNamespace := "default"
  98. // ntlm clustersecretstore takes credentials from a secret,
  99. // so we need to mock k8s-client retrieval of secret.
  100. mockClient := fake.NewClientBuilder().WithObjects(&corev1.Secret{
  101. ObjectMeta: metav1.ObjectMeta{
  102. Namespace: secretNamespace,
  103. Name: secretName,
  104. Labels: map[string]string{
  105. "external-secrets.io/type": "webhook",
  106. },
  107. },
  108. Data: map[string][]byte{
  109. "userName": []byte(creds.UserName),
  110. "password": []byte(creds.Password),
  111. },
  112. }).Build()
  113. // create clusteSecretStore
  114. ntlmAuthStore := &esv1.ClusterSecretStore{
  115. TypeMeta: metav1.TypeMeta{
  116. Kind: "ClusterSecretStore",
  117. },
  118. ObjectMeta: metav1.ObjectMeta{
  119. Name: "webhook-store",
  120. Namespace: secretNamespace,
  121. },
  122. Spec: esv1.SecretStoreSpec{
  123. Provider: &esv1.SecretStoreProvider{
  124. Webhook: &esv1.WebhookProvider{
  125. URL: url,
  126. Auth: &esv1.AuthorizationProtocol{
  127. NTLM: &esv1.NTLMProtocol{
  128. UserName: esmeta.SecretKeySelector{
  129. Name: secretName,
  130. Namespace: &secretNamespace,
  131. Key: "userName",
  132. },
  133. Password: esmeta.SecretKeySelector{
  134. Name: secretName,
  135. Namespace: &secretNamespace,
  136. Key: "password",
  137. },
  138. },
  139. },
  140. },
  141. },
  142. },
  143. }
  144. result := exerciseGetSecret(ntlmAuthStore, mockClient, t)
  145. return result
  146. }
  147. func basicAuthRequestEcho(creds mockCreds, t *testing.T) *httptest.Server {
  148. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  149. reqAuthString := r.Header.Get("Authorization")
  150. if reqAuthString == "" {
  151. w.Write([]byte("Empty Authorization header"))
  152. } else {
  153. w.Write([]byte(reqAuthString))
  154. }
  155. }))
  156. return server
  157. }
  158. func basicAuthRequest(url string, creds mockCreds, t *testing.T) string {
  159. reqAuthString := "Basic " + b64.StdEncoding.EncodeToString([]byte(creds.UserName+":"+creds.Password))
  160. // create ClusterSecretStore
  161. basicAuthStore := &esv1.ClusterSecretStore{
  162. TypeMeta: metav1.TypeMeta{
  163. Kind: "ClusterSecretStore",
  164. },
  165. ObjectMeta: metav1.ObjectMeta{
  166. Name: "webhook-store",
  167. Namespace: "default",
  168. },
  169. Spec: esv1.SecretStoreSpec{
  170. Provider: &esv1.SecretStoreProvider{
  171. Webhook: &esv1.WebhookProvider{
  172. URL: url,
  173. Headers: map[string]string{
  174. "Authorization": reqAuthString,
  175. },
  176. },
  177. },
  178. },
  179. }
  180. result := exerciseGetSecret(basicAuthStore, nil, t)
  181. return result
  182. }
  183. func exerciseGetSecret(mockStore esv1.GenericStore, mockKubeClient client.Client, t *testing.T) string {
  184. mockProvider := &Provider{}
  185. client, err := mockProvider.NewClient(context.Background(), mockStore, mockKubeClient, "default")
  186. if err != nil {
  187. t.Errorf("Error creating client: %q", err)
  188. return "error"
  189. }
  190. // perform request, exercising GetSecret
  191. resp, err := client.GetSecret(context.Background(), esv1.ExternalSecretDataRemoteRef{Key: "dummy"})
  192. if err != nil {
  193. t.Errorf("Error retrieving secret:%s", err)
  194. }
  195. return string(resp)
  196. }