| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- /*
- Copyright © The ESO Authors
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- https://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- // /*
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // https://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- // */
- package webhook
- import (
- "context"
- b64 "encoding/base64"
- "net/http"
- "net/http/httptest"
- "testing"
- corev1 "k8s.io/api/core/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "sigs.k8s.io/controller-runtime/pkg/client"
- "sigs.k8s.io/controller-runtime/pkg/client/fake"
- esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
- esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
- )
- type mockAuthTestPackage struct {
- Creds mockCreds
- MockServer mockAuthTestServer
- Request mockAuthRequest
- Expect string
- }
- type mockCreds struct {
- UserName string
- Password string
- }
- type mockAuthTestServer func(
- serverCreds mockCreds,
- t *testing.T) *httptest.Server
- type mockAuthRequest func(
- url string,
- creds mockCreds,
- t *testing.T) string
- func TestWebhookAuth(t *testing.T) {
- // define test cases
- creds := mockCreds{"correctuser123", "correctpassword123"}
- basicAuthExpect := "Basic " + b64.StdEncoding.EncodeToString([]byte(creds.UserName+":"+creds.Password))
- ntlmExpect := "NTLM TlRMTVNTUAABAAAAAQCIoAAAAAAoAAAAAAAAACgAAAAGAbEdAAAADw=="
- negotiateExpect := "Negotiate TlRMTVNTUAABAAAAAQCIoAAAAAAoAAAAAAAAACgAAAAGAbEdAAAADw=="
- // due to integrated nature of GetSecret(), we use a mock server
- // to return relevant parts of a request, in this case, the auth header.
- testAuthHeaders := map[string]mockAuthTestPackage{
- "BasicAuth": {creds, basicAuthRequestEcho, basicAuthRequest, basicAuthExpect},
- "NTLM": {creds, ntlmRequestEcho, ntlmRequest, ntlmExpect},
- "Negotiate": {creds, negotiateRequestEcho, ntlmRequest, negotiateExpect},
- }
- // execute test cases
- for _, p := range testAuthHeaders {
- server := p.MockServer(p.Creds, t)
- result := p.Request(server.URL, creds, t)
- server.Close()
- expect := p.Expect
- if result != expect {
- t.Errorf("Test failed. Result: '%s' / Expected: '%s'", result, expect)
- }
- }
- }
- func ntlmRequestEcho(mockCreds, *testing.T) *httptest.Server {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- reqAuthString := r.Header.Get("Authorization")
- if reqAuthString == "" {
- // go-ntlmssp first sends anonymous request, respond with 401
- w.Header().Add("WWW-Authenticate", "NTLM")
- w.WriteHeader(401)
- } else {
- w.Write([]byte(reqAuthString))
- }
- }))
- return server
- }
- func negotiateRequestEcho(mockCreds, *testing.T) *httptest.Server {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- reqAuthString := r.Header.Get("Authorization")
- if reqAuthString == "" {
- // go-ntlmssp first sends anonymous request, respond with 401
- w.Header().Add("WWW-Authenticate", "Negotiate")
- w.WriteHeader(401)
- } else {
- w.Write([]byte(reqAuthString))
- }
- }))
- return server
- }
- func ntlmRequest(url string, creds mockCreds, t *testing.T) string {
- secretName := "ntlmTestAuthSecret"
- secretNamespace := "default"
- // ntlm clustersecretstore takes credentials from a secret,
- // so we need to mock k8s-client retrieval of secret.
- mockClient := fake.NewClientBuilder().WithObjects(&corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Namespace: secretNamespace,
- Name: secretName,
- Labels: map[string]string{
- "external-secrets.io/type": "webhook",
- },
- },
- Data: map[string][]byte{
- "userName": []byte(creds.UserName),
- "password": []byte(creds.Password),
- },
- }).Build()
- // create clusteSecretStore
- ntlmAuthStore := &esv1.ClusterSecretStore{
- TypeMeta: metav1.TypeMeta{
- Kind: "ClusterSecretStore",
- },
- ObjectMeta: metav1.ObjectMeta{
- Name: "webhook-store",
- Namespace: secretNamespace,
- },
- Spec: esv1.SecretStoreSpec{
- Provider: &esv1.SecretStoreProvider{
- Webhook: &esv1.WebhookProvider{
- URL: url,
- Auth: &esv1.AuthorizationProtocol{
- NTLM: &esv1.NTLMProtocol{
- UserName: esmeta.SecretKeySelector{
- Name: secretName,
- Namespace: &secretNamespace,
- Key: "userName",
- },
- Password: esmeta.SecretKeySelector{
- Name: secretName,
- Namespace: &secretNamespace,
- Key: "password",
- },
- },
- },
- },
- },
- },
- }
- result := exerciseGetSecret(ntlmAuthStore, mockClient, t)
- return result
- }
- func basicAuthRequestEcho(mockCreds, *testing.T) *httptest.Server {
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- reqAuthString := r.Header.Get("Authorization")
- if reqAuthString == "" {
- w.Write([]byte("Empty Authorization header"))
- } else {
- w.Write([]byte(reqAuthString))
- }
- }))
- return server
- }
- func basicAuthRequest(url string, creds mockCreds, t *testing.T) string {
- reqAuthString := "Basic " + b64.StdEncoding.EncodeToString([]byte(creds.UserName+":"+creds.Password))
- // create ClusterSecretStore
- basicAuthStore := &esv1.ClusterSecretStore{
- TypeMeta: metav1.TypeMeta{
- Kind: "ClusterSecretStore",
- },
- ObjectMeta: metav1.ObjectMeta{
- Name: "webhook-store",
- Namespace: "default",
- },
- Spec: esv1.SecretStoreSpec{
- Provider: &esv1.SecretStoreProvider{
- Webhook: &esv1.WebhookProvider{
- URL: url,
- Headers: map[string]string{
- "Authorization": reqAuthString,
- },
- },
- },
- },
- }
- result := exerciseGetSecret(basicAuthStore, nil, t)
- return result
- }
- func exerciseGetSecret(mockStore esv1.GenericStore, mockKubeClient client.Client, t *testing.T) string {
- mockProvider := &Provider{}
- client, err := mockProvider.NewClient(context.Background(), mockStore, mockKubeClient, "default")
- if err != nil {
- t.Errorf("Error creating client: %q", err)
- return "error"
- }
- // perform request, exercising GetSecret
- resp, err := client.GetSecret(context.Background(), esv1.ExternalSecretDataRemoteRef{Key: "dummy"})
- if err != nil {
- t.Errorf("Error retrieving secret:%s", err)
- }
- return string(resp)
- }
|