github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/config/identity/openid/jwt_test.go (about)

     1  // Copyright (c) 2015-2022 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package openid
    19  
    20  import (
    21  	"bytes"
    22  	"context"
    23  	"encoding/base64"
    24  	"encoding/json"
    25  	"fmt"
    26  	"io"
    27  	"net/http"
    28  	"net/http/httptest"
    29  	"net/url"
    30  	"sync"
    31  	"testing"
    32  	"time"
    33  
    34  	jwtgo "github.com/golang-jwt/jwt/v4"
    35  	"github.com/minio/minio/internal/arn"
    36  	"github.com/minio/minio/internal/config"
    37  	jwtm "github.com/minio/minio/internal/jwt"
    38  	xnet "github.com/minio/pkg/v2/net"
    39  )
    40  
    41  func TestUpdateClaimsExpiry(t *testing.T) {
    42  	testCases := []struct {
    43  		exp             interface{}
    44  		dsecs           string
    45  		expectedFailure bool
    46  	}{
    47  		{"", "", true},
    48  		{"-1", "0", true},
    49  		{"-1", "900", true},
    50  		{"1574812326", "900", false},
    51  		{1574812326, "900", false},
    52  		{int64(1574812326), "900", false},
    53  		{int(1574812326), "900", false},
    54  		{uint(1574812326), "900", false},
    55  		{uint64(1574812326), "900", false},
    56  		{json.Number("1574812326"), "900", false},
    57  		{1574812326.000, "900", false},
    58  		{time.Duration(3) * time.Minute, "900", false},
    59  	}
    60  
    61  	for _, testCase := range testCases {
    62  		testCase := testCase
    63  		t.Run("", func(t *testing.T) {
    64  			claims := map[string]interface{}{}
    65  			claims["exp"] = testCase.exp
    66  			err := updateClaimsExpiry(testCase.dsecs, claims)
    67  			if err != nil && !testCase.expectedFailure {
    68  				t.Errorf("Expected success, got failure %s", err)
    69  			}
    70  			if err == nil && testCase.expectedFailure {
    71  				t.Error("Expected failure, got success")
    72  			}
    73  		})
    74  	}
    75  }
    76  
    77  func initJWKSServer() *httptest.Server {
    78  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    79  		const jsonkey = `{"keys":
    80         [
    81           {"kty":"RSA",
    82            "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
    83            "e":"AQAB",
    84            "alg":"RS256",
    85            "kid":"2011-04-29"}
    86         ]
    87       }`
    88  		w.Write([]byte(jsonkey))
    89  	}))
    90  	return server
    91  }
    92  
    93  func TestJWTHMACType(t *testing.T) {
    94  	server := initJWKSServer()
    95  	defer server.Close()
    96  
    97  	jwt := &jwtgo.Token{
    98  		Method: jwtgo.SigningMethodHS256,
    99  		Claims: jwtgo.StandardClaims{
   100  			ExpiresAt: 253428928061,
   101  			Audience:  "76b95ae5-33ef-4283-97b7-d2a85dc2d8f4",
   102  		},
   103  		Header: map[string]interface{}{
   104  			"typ": "JWT",
   105  			"alg": jwtgo.SigningMethodHS256.Alg(),
   106  			"kid": "76b95ae5-33ef-4283-97b7-d2a85dc2d8f4",
   107  		},
   108  	}
   109  
   110  	token, err := jwt.SignedString([]byte("WNGvKVyyNmXq0TraSvjaDN9CtpFgx35IXtGEffMCPR0"))
   111  	if err != nil {
   112  		t.Fatal(err)
   113  	}
   114  	fmt.Println(token)
   115  
   116  	u1, err := xnet.ParseHTTPURL(server.URL)
   117  	if err != nil {
   118  		t.Fatal(err)
   119  	}
   120  
   121  	pubKeys := publicKeys{
   122  		RWMutex: &sync.RWMutex{},
   123  		pkMap:   map[string]interface{}{},
   124  	}
   125  	pubKeys.add("76b95ae5-33ef-4283-97b7-d2a85dc2d8f4", []byte("WNGvKVyyNmXq0TraSvjaDN9CtpFgx35IXtGEffMCPR0"))
   126  
   127  	if len(pubKeys.pkMap) != 1 {
   128  		t.Fatalf("Expected 1 keys, got %d", len(pubKeys.pkMap))
   129  	}
   130  
   131  	provider := providerCfg{
   132  		ClientID:     "76b95ae5-33ef-4283-97b7-d2a85dc2d8f4",
   133  		ClientSecret: "WNGvKVyyNmXq0TraSvjaDN9CtpFgx35IXtGEffMCPR0",
   134  	}
   135  	provider.JWKS.URL = u1
   136  	cfg := Config{
   137  		Enabled: true,
   138  		pubKeys: pubKeys,
   139  		arnProviderCfgsMap: map[arn.ARN]*providerCfg{
   140  			DummyRoleARN: &provider,
   141  		},
   142  		ProviderCfgs: map[string]*providerCfg{
   143  			"1": &provider,
   144  		},
   145  		closeRespFn: func(rc io.ReadCloser) {
   146  			rc.Close()
   147  		},
   148  	}
   149  
   150  	var claims jwtgo.MapClaims
   151  	if err = cfg.Validate(context.Background(), DummyRoleARN, token, "", "", claims); err != nil {
   152  		t.Fatal(err)
   153  	}
   154  }
   155  
   156  func TestJWT(t *testing.T) {
   157  	const jsonkey = `{"keys":
   158         [
   159           {"kty":"RSA",
   160            "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
   161            "e":"AQAB",
   162            "alg":"RS256",
   163            "kid":"2011-04-29"}
   164         ]
   165       }`
   166  
   167  	pubKeys := publicKeys{
   168  		RWMutex: &sync.RWMutex{},
   169  		pkMap:   map[string]interface{}{},
   170  	}
   171  	err := pubKeys.parseAndAdd(bytes.NewBuffer([]byte(jsonkey)))
   172  	if err != nil {
   173  		t.Fatal("Error loading pubkeys:", err)
   174  	}
   175  	if len(pubKeys.pkMap) != 1 {
   176  		t.Fatalf("Expected 1 keys, got %d", len(pubKeys.pkMap))
   177  	}
   178  
   179  	u1, err := xnet.ParseHTTPURL("http://127.0.0.1:8443")
   180  	if err != nil {
   181  		t.Fatal(err)
   182  	}
   183  
   184  	provider := providerCfg{}
   185  	provider.JWKS.URL = u1
   186  	cfg := Config{
   187  		Enabled: true,
   188  		pubKeys: pubKeys,
   189  		arnProviderCfgsMap: map[arn.ARN]*providerCfg{
   190  			DummyRoleARN: &provider,
   191  		},
   192  		ProviderCfgs: map[string]*providerCfg{
   193  			"1": &provider,
   194  		},
   195  	}
   196  
   197  	u, err := url.Parse("http://127.0.0.1:8443/?Token=invalid")
   198  	if err != nil {
   199  		t.Fatal(err)
   200  	}
   201  
   202  	var claims jwtgo.MapClaims
   203  	if err = cfg.Validate(context.Background(), DummyRoleARN, u.Query().Get("Token"), "", "", claims); err == nil {
   204  		t.Fatal(err)
   205  	}
   206  }
   207  
   208  func TestDefaultExpiryDuration(t *testing.T) {
   209  	testCases := []struct {
   210  		reqURL    string
   211  		duration  time.Duration
   212  		expectErr bool
   213  	}{
   214  		{
   215  			reqURL:   "http://127.0.0.1:8443/?Token=xxxxx",
   216  			duration: time.Duration(60) * time.Minute,
   217  		},
   218  		{
   219  			reqURL:    "http://127.0.0.1:8443/?DurationSeconds=9s",
   220  			expectErr: true,
   221  		},
   222  		{
   223  			reqURL:    "http://127.0.0.1:8443/?DurationSeconds=31536001",
   224  			expectErr: true,
   225  		},
   226  		{
   227  			reqURL:    "http://127.0.0.1:8443/?DurationSeconds=800",
   228  			expectErr: true,
   229  		},
   230  		{
   231  			reqURL:   "http://127.0.0.1:8443/?DurationSeconds=901",
   232  			duration: time.Duration(901) * time.Second,
   233  		},
   234  	}
   235  
   236  	for i, testCase := range testCases {
   237  		u, err := url.Parse(testCase.reqURL)
   238  		if err != nil {
   239  			t.Fatal(err)
   240  		}
   241  		d, err := GetDefaultExpiration(u.Query().Get("DurationSeconds"))
   242  		gotErr := (err != nil)
   243  		if testCase.expectErr != gotErr {
   244  			t.Errorf("Test %d: Expected %v, got %v with error %s", i+1, testCase.expectErr, gotErr, err)
   245  		}
   246  		if d != testCase.duration {
   247  			t.Errorf("Test %d: Expected duration %d, got %d", i+1, testCase.duration, d)
   248  		}
   249  	}
   250  }
   251  
   252  func TestExpCorrect(t *testing.T) {
   253  	signKey, _ := base64.StdEncoding.DecodeString("NTNv7j0TuYARvmNMmWXo6fKvM4o6nv/aUi9ryX38ZH+L1bkrnD1ObOQ8JAUmHCBq7Iy7otZcyAagBLHVKvvYaIpmMuxmARQ97jUVG16Jkpkp1wXOPsrF9zwew6TpczyHkHgX5EuLg2MeBuiT/qJACs1J0apruOOJCg/gOtkjB4c=")
   254  
   255  	claimsMap := jwtm.NewMapClaims()
   256  	claimsMap.SetExpiry(time.Now().Add(time.Minute))
   257  	claimsMap.SetAccessKey("test-access")
   258  	if err := updateClaimsExpiry("3600", claimsMap.MapClaims); err != nil {
   259  		t.Error(err)
   260  	}
   261  	// Build simple token with updated expiration claim
   262  	token := jwtgo.NewWithClaims(jwtgo.SigningMethodHS256, claimsMap)
   263  	tokenString, err := token.SignedString(signKey)
   264  	if err != nil {
   265  		t.Error(err)
   266  	}
   267  
   268  	// Parse token to be sure it is valid
   269  	err = jwtm.ParseWithClaims(tokenString, claimsMap, func(*jwtm.MapClaims) ([]byte, error) {
   270  		return signKey, nil
   271  	})
   272  	if err != nil {
   273  		t.Error(err)
   274  	}
   275  }
   276  
   277  func TestKeycloakProviderInitialization(t *testing.T) {
   278  	testConfig := providerCfg{
   279  		DiscoveryDoc: DiscoveryDoc{
   280  			TokenEndpoint: "http://keycloak.test/token/endpoint",
   281  		},
   282  	}
   283  	testKvs := config.KVS{}
   284  	testKvs.Set(Vendor, "keycloak")
   285  	testKvs.Set(KeyCloakRealm, "TestRealm")
   286  	testKvs.Set(KeyCloakAdminURL, "http://keycloak.test/auth/admin")
   287  	cfgGet := func(param string) string {
   288  		return testKvs.Get(param)
   289  	}
   290  
   291  	if testConfig.provider != nil {
   292  		t.Errorf("Empty config cannot have any provider!")
   293  	}
   294  
   295  	if err := testConfig.initializeProvider(cfgGet, http.DefaultTransport); err != nil {
   296  		t.Error(err)
   297  	}
   298  
   299  	if testConfig.provider == nil {
   300  		t.Errorf("keycloak provider must be initialized!")
   301  	}
   302  }