github.com/verrazzano/verrazzano@v1.7.0/authproxy/src/proxy/proxy_test.go (about)

     1  // Copyright (c) 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package proxy
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"net/url"
    11  	"os"
    12  	"reflect"
    13  	"runtime"
    14  	"strings"
    15  	"testing"
    16  
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/verrazzano/verrazzano/authproxy/internal/testutil/file"
    19  	"github.com/verrazzano/verrazzano/authproxy/internal/testutil/testserver"
    20  	"github.com/verrazzano/verrazzano/authproxy/src/auth"
    21  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    22  	"go.uber.org/zap"
    23  	"k8s.io/client-go/rest"
    24  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    25  )
    26  
    27  const (
    28  	apiPath          = "/api/v1/pods"
    29  	testAPIServerURL = "https://api-server.io"
    30  	caCertFile       = "./testdata/test-ca.crt"
    31  )
    32  
    33  var serverURL string
    34  
    35  // TestConfigureKubernetesAPIProxy tests the configuration of the API proxy
    36  // GIVEN an Auth proxy object
    37  // WHEN  the Kubernetes API proxy is configured
    38  // THEN  the handler exists and there is no error
    39  func TestConfigureKubernetesAPIProxy(t *testing.T) {
    40  	c := fake.NewClientBuilder().Build()
    41  	authproxy := InitializeProxy(8777)
    42  	log := zap.S()
    43  
    44  	getConfigFunc = testConfig
    45  	defer func() { getConfigFunc = k8sutil.GetConfigFromController }()
    46  
    47  	err := ConfigureKubernetesAPIProxy(authproxy, c, log)
    48  	assert.NoError(t, err)
    49  	assert.NotNil(t, authproxy.Handler)
    50  }
    51  
    52  // TestLoadCAData tests that the CA data is properly loaded from sources
    53  func TestLoadCAData(t *testing.T) {
    54  	// GIVEN a config with the CA Data populated
    55  	// WHEN  the cert pool is generated
    56  	// THEN  no error is returned
    57  	caData, err := os.ReadFile(caCertFile)
    58  	assert.NoError(t, err)
    59  	config := &rest.Config{
    60  		TLSClientConfig: rest.TLSClientConfig{
    61  			CAData: caData,
    62  		},
    63  	}
    64  	log := zap.S()
    65  	pool, err := loadCAData(config, log)
    66  	assert.NoError(t, err)
    67  	assert.NotEmpty(t, pool)
    68  
    69  	// GIVEN a config with the CA File populated
    70  	// WHEN  the cert pool is generated
    71  	// THEN  no error is returned
    72  	config = &rest.Config{
    73  		TLSClientConfig: rest.TLSClientConfig{
    74  			CAFile: caCertFile,
    75  		},
    76  	}
    77  	pool, err = loadCAData(config, log)
    78  	assert.NoError(t, err)
    79  	assert.NotEmpty(t, pool)
    80  }
    81  
    82  // TestLoadBearerToken tests that the bearer token is properly loaded from the config
    83  func TestLoadBearerToken(t *testing.T) {
    84  	log := zap.S()
    85  
    86  	// GIVEN a config with Bearer Token populated
    87  	// WHEN  the bearer token is loaded
    88  	// THEN  the handler gets the bearer token data
    89  	testToken := "test-token"
    90  	config := &rest.Config{
    91  		BearerToken: testToken,
    92  	}
    93  	bearerToken, err := loadBearerToken(config, log)
    94  	assert.NoError(t, err)
    95  	assert.Equal(t, testToken, bearerToken)
    96  
    97  	// GIVEN a config with the Bearer Token file populated
    98  	// WHEN  the bearer token is loaded
    99  	// THEN  the handler gets the bearer token data
   100  	testTokenFile, err := file.MakeTempFile(testToken)
   101  	if testTokenFile != nil {
   102  		defer os.Remove(testTokenFile.Name())
   103  	}
   104  	assert.NoError(t, err)
   105  	config = &rest.Config{
   106  		BearerTokenFile: testTokenFile.Name(),
   107  	}
   108  	bearerToken, err = loadBearerToken(config, log)
   109  	assert.NoError(t, err)
   110  	assert.Equal(t, testToken, bearerToken)
   111  
   112  	// GIVEN a config with no bearer information
   113  	// WHEN  the bearer token is loaded
   114  	// THEN  the handler gets not bearer token data
   115  	config = &rest.Config{}
   116  	bearerToken, err = loadBearerToken(config, log)
   117  	assert.NoError(t, err)
   118  	assert.Empty(t, bearerToken)
   119  }
   120  
   121  // TestInitializeAuthenticator tests that the authenticator gets initialized if it has not previously
   122  func TestInitializeAuthenticator(t *testing.T) {
   123  	handler := Handler{
   124  		URL:       testAPIServerURL,
   125  		K8sClient: fake.NewClientBuilder().Build(),
   126  		Log:       zap.S(),
   127  	}
   128  
   129  	server := testserver.FakeOIDCProviderServer(t)
   130  	serverURL = server.URL
   131  
   132  	getOIDCConfigFunc = fakeOIDCConfig
   133  	defer func() { getOIDCConfigFunc = getOIDCConfiguration }()
   134  
   135  	// GIVEN a request to initialize the authenticator
   136  	// WHEN the authenticator has already been initialized
   137  	// THEN no error is returned
   138  	handler.AuthInited.Store(true)
   139  	err := handler.initializeAuthenticator()
   140  	assert.NoError(t, err)
   141  
   142  	// GIVEN a request to initialize the authenticator
   143  	// WHEN the authenticator has not been initialized
   144  	// THEN no error is returned
   145  	handler.AuthInited.Store(false)
   146  	err = handler.initializeAuthenticator()
   147  	assert.NoError(t, err)
   148  }
   149  
   150  // TestFindPathHandler tests that the correct handler is returned for a given request
   151  func TestFindPathHandler(t *testing.T) {
   152  	handler := Handler{
   153  		URL:       testAPIServerURL,
   154  		K8sClient: fake.NewClientBuilder().Build(),
   155  		Log:       zap.S(),
   156  	}
   157  
   158  	// GIVEN a request
   159  	// WHEN the url has the callback path
   160  	// THEN the callback function is returned
   161  	callbackURL, err := url.Parse(fmt.Sprintf("%s%s", testAPIServerURL, callbackPath))
   162  	assert.NoError(t, err)
   163  	req := &http.Request{URL: callbackURL}
   164  	handlerfunc := handler.findPathHandler(req)
   165  	handlerName := runtime.FuncForPC(reflect.ValueOf(handlerfunc).Pointer()).Name()
   166  	authCallbackName := runtime.FuncForPC(reflect.ValueOf(handler.handleAuthCallback).Pointer()).Name()
   167  	assert.Equal(t, handlerName, authCallbackName)
   168  
   169  	// GIVEN a request
   170  	// WHEN the url has the logout path
   171  	// THEN the logout function is returned
   172  	logoutURL, err := url.Parse(fmt.Sprintf("%s%s", testAPIServerURL, logoutPath))
   173  	assert.NoError(t, err)
   174  	req = &http.Request{URL: logoutURL}
   175  	handlerfunc = handler.findPathHandler(req)
   176  	handlerName = runtime.FuncForPC(reflect.ValueOf(handlerfunc).Pointer()).Name()
   177  	logoutName := runtime.FuncForPC(reflect.ValueOf(handler.handleLogout).Pointer()).Name()
   178  	assert.Equal(t, handlerName, logoutName)
   179  
   180  	// GIVEN a request
   181  	// WHEN the url has any path
   182  	// THEN the api server function is returned
   183  	apiReqURL, err := url.Parse(testAPIServerURL)
   184  	assert.NoError(t, err)
   185  	req = &http.Request{URL: apiReqURL}
   186  	handlerfunc = handler.findPathHandler(req)
   187  	handlerName = runtime.FuncForPC(reflect.ValueOf(handlerfunc).Pointer()).Name()
   188  	apiReqName := runtime.FuncForPC(reflect.ValueOf(handler.handleAPIRequest).Pointer()).Name()
   189  	assert.Equal(t, handlerName, apiReqName)
   190  
   191  }
   192  
   193  // TestServeHTTP tests that the incoming HTTP requests can be properly handled and forwarded
   194  // GIVEN an HTTP request
   195  // WHEN the request is processed
   196  // THEN no error is returned
   197  func TestServeHTTP(t *testing.T) {
   198  	handler := Handler{
   199  		URL:       testAPIServerURL,
   200  		K8sClient: fake.NewClientBuilder().Build(),
   201  		Log:       zap.S(),
   202  	}
   203  
   204  	server := testserver.FakeOIDCProviderServer(t)
   205  	serverURL = server.URL
   206  
   207  	getOIDCConfigFunc = fakeOIDCConfig
   208  	defer func() { getOIDCConfigFunc = getOIDCConfiguration }()
   209  
   210  	// Sending an option request so the API Server request terminates early
   211  	req := httptest.NewRequest(http.MethodOptions, serverURL, strings.NewReader(""))
   212  	rw := httptest.NewRecorder()
   213  	handler.ServeHTTP(rw, req)
   214  }
   215  
   216  func testConfig() (*rest.Config, error) {
   217  	return &rest.Config{
   218  		Host: "test-host",
   219  		TLSClientConfig: rest.TLSClientConfig{
   220  			CAFile: caCertFile,
   221  		},
   222  	}, nil
   223  }
   224  
   225  func fakeOIDCConfig() auth.OIDCConfiguration {
   226  	return auth.OIDCConfiguration{
   227  		ExternalURL: serverURL,
   228  		ServiceURL:  serverURL,
   229  	}
   230  }