webhook_auth_test.go 7.0 KB

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