github.com/kiali/kiali@v1.84.0/business/authentication/token_auth_controller_test.go (about)

     1  package authentication
     2  
     3  import (
     4  	"errors"
     5  	"net/http"
     6  	"net/http/httptest"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/assert"
    12  	v1 "k8s.io/api/core/v1"
    13  	k8s_errors "k8s.io/apimachinery/pkg/api/errors"
    14  	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    15  	"k8s.io/apimachinery/pkg/runtime/schema"
    16  
    17  	"github.com/kiali/kiali/config"
    18  	"github.com/kiali/kiali/kubernetes"
    19  	"github.com/kiali/kiali/kubernetes/cache"
    20  	"github.com/kiali/kiali/kubernetes/kubetest"
    21  	"github.com/kiali/kiali/util"
    22  )
    23  
    24  // Token built with the debugger at jwt.io. Subject is system:serviceaccount:k8s_user
    25  const testToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6azhzX3VzZXIifQ.PYnWgochOQsfMInTpJPul7zkDSyMmJwfvJ6nXowITZk"
    26  
    27  func TestTokenAuthControllerAuthenticatesCorrectly(t *testing.T) {
    28  	rr, sData, _, _ := createValidSession(t)
    29  
    30  	expectedExpiration := time.Date(2021, 12, 1, 0, 0, 1, 0, time.UTC)
    31  
    32  	assert.NotNil(t, sData)
    33  	assert.Equal(t, "k8s_user", sData.Username)
    34  	assert.Equal(t, testToken, sData.AuthInfo.Token)
    35  	assert.Equal(t, expectedExpiration, sData.ExpiresOn)
    36  
    37  	// Simply check that some cookie is set and has the right expiration. Testing cookie content is left to the session_persistor_test.go
    38  	response := rr.Result()
    39  	assert.NotEmpty(t, response.Cookies())
    40  	assert.Equal(t, expectedExpiration, response.Cookies()[0].Expires)
    41  }
    42  
    43  func TestTokenAuthControllerRejectsUserWithoutPrivilegesInAnyNamespace(t *testing.T) {
    44  	clockTime := time.Date(2021, 12, 1, 0, 0, 0, 0, time.UTC)
    45  	util.Clock = util.ClockMock{Time: clockTime}
    46  
    47  	conf := config.NewConfig()
    48  	conf.LoginToken.SigningKey = "kiali67890123456"
    49  	config.Set(conf)
    50  
    51  	// Returning no namespace when a cluster API call is made should have the result of
    52  	// a rejected authentication.
    53  	k8s := kubetest.NewFakeK8sClient()
    54  
    55  	requestBody := strings.NewReader("token=Foo")
    56  	request := httptest.NewRequest(http.MethodPost, "/api/authenticate", requestBody)
    57  	request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
    58  
    59  	rr := httptest.NewRecorder()
    60  	mockClientFactory := kubetest.NewK8SClientFactoryMock(k8s)
    61  	cache := cache.NewTestingCacheWithFactory(t, mockClientFactory, *conf)
    62  	controller := NewTokenAuthController(NewCookieSessionPersistor(conf), mockClientFactory, cache, conf)
    63  	sData, err := controller.Authenticate(request, rr)
    64  
    65  	assert.Nil(t, sData)
    66  	assert.IsType(t, &AuthenticationFailureError{}, err)
    67  	assert.Contains(t, err.Error(), "privileges")
    68  
    69  	// Check no cookies are set
    70  	response := rr.Result()
    71  	assert.Empty(t, response.Cookies())
    72  }
    73  
    74  func TestTokenAuthControllerRejectsInvalidToken(t *testing.T) {
    75  	clockTime := time.Date(2021, 12, 1, 0, 0, 0, 0, time.UTC)
    76  	util.Clock = util.ClockMock{Time: clockTime}
    77  
    78  	conf := config.NewConfig()
    79  	conf.LoginToken.SigningKey = "kiali67890123456"
    80  	config.Set(conf)
    81  
    82  	// Returning a forbidden error when a cluster API call is made should have the result of
    83  	// a rejected authentication.
    84  	k8s := kubetest.NewFakeK8sClient(&v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "Foo"}})
    85  	mockClientFactory := kubetest.NewK8SClientFactoryMock(forbiddenClient{k8s})
    86  	cache := cache.NewTestingCacheWithFactory(t, mockClientFactory, *conf)
    87  	controller := NewTokenAuthController(NewCookieSessionPersistor(conf), mockClientFactory, cache, conf)
    88  
    89  	requestBody := strings.NewReader("token=Foo")
    90  	request := httptest.NewRequest(http.MethodPost, "/api/authenticate", requestBody)
    91  	request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
    92  
    93  	rr := httptest.NewRecorder()
    94  	sData, err := controller.Authenticate(request, rr)
    95  
    96  	assert.Nil(t, sData)
    97  	assert.IsType(t, &AuthenticationFailureError{}, err)
    98  	assert.Contains(t, err.Error(), "token")
    99  
   100  	// Check no cookies are set
   101  	response := rr.Result()
   102  	assert.Empty(t, response.Cookies())
   103  }
   104  
   105  func TestTokenAuthControllerRejectsEmptyToken(t *testing.T) {
   106  	requestBody := strings.NewReader("token=")
   107  	request := httptest.NewRequest(http.MethodPost, "/api/authenticate", requestBody)
   108  	request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
   109  
   110  	conf := config.NewConfig()
   111  	k8s := kubetest.NewFakeK8sClient()
   112  	mockClientFactory := kubetest.NewK8SClientFactoryMock(k8s)
   113  	cache := cache.NewTestingCacheWithFactory(t, mockClientFactory, *conf)
   114  	controller := NewTokenAuthController(NewCookieSessionPersistor(conf), mockClientFactory, cache, conf)
   115  
   116  	rr := httptest.NewRecorder()
   117  	sData, err := controller.Authenticate(request, rr)
   118  
   119  	assert.Nil(t, sData)
   120  	assert.Error(t, err)
   121  	assert.Contains(t, err.Error(), "empty")
   122  
   123  	// Check no cookies are set
   124  	response := rr.Result()
   125  	assert.Empty(t, response.Cookies())
   126  }
   127  
   128  func TestTokenAuthControllerValidatesSessionCorrectly(t *testing.T) {
   129  	rr, _, _, controller := createValidSession(t)
   130  	response := rr.Result()
   131  
   132  	request := httptest.NewRequest(http.MethodGet, "/api/get", nil)
   133  	for _, c := range response.Cookies() {
   134  		request.AddCookie(c)
   135  	}
   136  
   137  	rr = httptest.NewRecorder()
   138  	sData, err := controller.ValidateSession(request, rr)
   139  
   140  	assert.Nil(t, err)
   141  	assert.NotNil(t, sData)
   142  	assert.Equal(t, testToken, sData.AuthInfo.Token)
   143  	assert.Equal(t, "k8s_user", sData.Username)
   144  	assert.Equal(t, time.Date(2021, 12, 1, 0, 0, 1, 0, time.UTC), sData.ExpiresOn)
   145  }
   146  
   147  func TestTokenAuthControllerValidatesSessionWithoutActiveSession(t *testing.T) {
   148  	conf := config.NewConfig()
   149  	config.Set(conf)
   150  	request := httptest.NewRequest(http.MethodGet, "/api/get", nil)
   151  
   152  	k8s := kubetest.NewFakeK8sClient()
   153  
   154  	rr := httptest.NewRecorder()
   155  	mockClientFactory := kubetest.NewK8SClientFactoryMock(k8s)
   156  	cache := cache.NewTestingCacheWithFactory(t, mockClientFactory, *conf)
   157  
   158  	controller := NewTokenAuthController(NewCookieSessionPersistor(conf), mockClientFactory, cache, conf)
   159  	sData, err := controller.ValidateSession(request, rr)
   160  
   161  	assert.Nil(t, err)
   162  	assert.Nil(t, sData)
   163  }
   164  
   165  type forbiddenClient struct {
   166  	kubernetes.ClientInterface
   167  }
   168  
   169  func (f forbiddenClient) GetNamespaces(labelSelector string) ([]v1.Namespace, error) {
   170  	return nil, k8s_errors.NewForbidden(schema.GroupResource{Group: "v1", Resource: "namespaces"}, "", errors.New("err"))
   171  }
   172  
   173  func TestTokenAuthControllerValidatesSessionForUserWithMissingPrivileges(t *testing.T) {
   174  	rr, _, _, controller := createValidSession(t)
   175  	response := rr.Result()
   176  
   177  	request := httptest.NewRequest(http.MethodGet, "/api/get", nil)
   178  	for _, c := range response.Cookies() {
   179  		request.AddCookie(c)
   180  	}
   181  
   182  	// Empty cache that will miss.
   183  	forbiddenClient := &forbiddenClient{kubetest.NewFakeK8sClient()}
   184  
   185  	mockClientFactory := kubetest.NewK8SClientFactoryMock(forbiddenClient)
   186  	cache := cache.NewTestingCacheWithFactory(t, mockClientFactory, *config.Get())
   187  	controller.clientFactory = mockClientFactory
   188  	controller.kialiCache = cache
   189  
   190  	rr = httptest.NewRecorder()
   191  	sData, err := controller.ValidateSession(request, rr)
   192  
   193  	assert.Nil(t, err)
   194  	assert.Nil(t, sData)
   195  }
   196  
   197  func createValidSession(t *testing.T) (*httptest.ResponseRecorder, *UserSessionData, *kubetest.FakeK8sClient, *tokenAuthController) {
   198  	t.Helper()
   199  
   200  	clockTime := time.Date(2021, 12, 1, 0, 0, 0, 0, time.UTC)
   201  	util.Clock = util.ClockMock{Time: clockTime}
   202  
   203  	conf := config.NewConfig()
   204  	conf.LoginToken.SigningKey = "kiali67890123456"
   205  	conf.LoginToken.ExpirationSeconds = 1
   206  	config.Set(conf)
   207  
   208  	// Returning some namespace when a cluster API call is made should have the result of
   209  	// a successful authentication.
   210  	k8s := kubetest.NewFakeK8sClient(&v1.Namespace{ObjectMeta: meta_v1.ObjectMeta{Name: "Foo"}})
   211  
   212  	requestBody := strings.NewReader("token=" + testToken)
   213  	request := httptest.NewRequest(http.MethodPost, "/api/authenticate", requestBody)
   214  	request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
   215  
   216  	rr := httptest.NewRecorder()
   217  	mockClientFactory := kubetest.NewK8SClientFactoryMock(k8s)
   218  	cache := cache.NewTestingCacheWithFactory(t, mockClientFactory, *conf)
   219  
   220  	controller := NewTokenAuthController(NewCookieSessionPersistor(conf), mockClientFactory, cache, conf)
   221  
   222  	sData, err := controller.Authenticate(request, rr)
   223  	if err != nil {
   224  		t.Fatal(err)
   225  	}
   226  	return rr, sData, k8s, controller
   227  }