mirror of
				https://github.com/minio/minio.git
				synced 2025-11-04 02:01:05 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			303 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			303 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) 2015-2022 MinIO, Inc.
 | 
						|
//
 | 
						|
// This file is part of MinIO Object Storage stack
 | 
						|
//
 | 
						|
// This program is free software: you can redistribute it and/or modify
 | 
						|
// it under the terms of the GNU Affero General Public License as published by
 | 
						|
// the Free Software Foundation, either version 3 of the License, or
 | 
						|
// (at your option) any later version.
 | 
						|
//
 | 
						|
// This program is distributed in the hope that it will be useful
 | 
						|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
// GNU Affero General Public License for more details.
 | 
						|
//
 | 
						|
// You should have received a copy of the GNU Affero General Public License
 | 
						|
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
package openid
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"context"
 | 
						|
	"encoding/base64"
 | 
						|
	"encoding/json"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"net/http"
 | 
						|
	"net/http/httptest"
 | 
						|
	"net/url"
 | 
						|
	"sync"
 | 
						|
	"testing"
 | 
						|
	"time"
 | 
						|
 | 
						|
	jwtgo "github.com/golang-jwt/jwt/v4"
 | 
						|
	"github.com/minio/minio/internal/arn"
 | 
						|
	"github.com/minio/minio/internal/config"
 | 
						|
	jwtm "github.com/minio/minio/internal/jwt"
 | 
						|
	xnet "github.com/minio/pkg/v2/net"
 | 
						|
)
 | 
						|
 | 
						|
func TestUpdateClaimsExpiry(t *testing.T) {
 | 
						|
	testCases := []struct {
 | 
						|
		exp             interface{}
 | 
						|
		dsecs           string
 | 
						|
		expectedFailure bool
 | 
						|
	}{
 | 
						|
		{"", "", true},
 | 
						|
		{"-1", "0", true},
 | 
						|
		{"-1", "900", true},
 | 
						|
		{"1574812326", "900", false},
 | 
						|
		{1574812326, "900", false},
 | 
						|
		{int64(1574812326), "900", false},
 | 
						|
		{int(1574812326), "900", false},
 | 
						|
		{uint(1574812326), "900", false},
 | 
						|
		{uint64(1574812326), "900", false},
 | 
						|
		{json.Number("1574812326"), "900", false},
 | 
						|
		{1574812326.000, "900", false},
 | 
						|
		{time.Duration(3) * time.Minute, "900", false},
 | 
						|
	}
 | 
						|
 | 
						|
	for _, testCase := range testCases {
 | 
						|
		testCase := testCase
 | 
						|
		t.Run("", func(t *testing.T) {
 | 
						|
			claims := map[string]interface{}{}
 | 
						|
			claims["exp"] = testCase.exp
 | 
						|
			err := updateClaimsExpiry(testCase.dsecs, claims)
 | 
						|
			if err != nil && !testCase.expectedFailure {
 | 
						|
				t.Errorf("Expected success, got failure %s", err)
 | 
						|
			}
 | 
						|
			if err == nil && testCase.expectedFailure {
 | 
						|
				t.Error("Expected failure, got success")
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func initJWKSServer() *httptest.Server {
 | 
						|
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
						|
		const jsonkey = `{"keys":
 | 
						|
       [
 | 
						|
         {"kty":"RSA",
 | 
						|
          "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
 | 
						|
          "e":"AQAB",
 | 
						|
          "alg":"RS256",
 | 
						|
          "kid":"2011-04-29"}
 | 
						|
       ]
 | 
						|
     }`
 | 
						|
		w.Write([]byte(jsonkey))
 | 
						|
	}))
 | 
						|
	return server
 | 
						|
}
 | 
						|
 | 
						|
func TestJWTHMACType(t *testing.T) {
 | 
						|
	server := initJWKSServer()
 | 
						|
	defer server.Close()
 | 
						|
 | 
						|
	jwt := &jwtgo.Token{
 | 
						|
		Method: jwtgo.SigningMethodHS256,
 | 
						|
		Claims: jwtgo.StandardClaims{
 | 
						|
			ExpiresAt: 253428928061,
 | 
						|
			Audience:  "76b95ae5-33ef-4283-97b7-d2a85dc2d8f4",
 | 
						|
		},
 | 
						|
		Header: map[string]interface{}{
 | 
						|
			"typ": "JWT",
 | 
						|
			"alg": jwtgo.SigningMethodHS256.Alg(),
 | 
						|
			"kid": "76b95ae5-33ef-4283-97b7-d2a85dc2d8f4",
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	token, err := jwt.SignedString([]byte("WNGvKVyyNmXq0TraSvjaDN9CtpFgx35IXtGEffMCPR0"))
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	fmt.Println(token)
 | 
						|
 | 
						|
	u1, err := xnet.ParseHTTPURL(server.URL)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	pubKeys := publicKeys{
 | 
						|
		RWMutex: &sync.RWMutex{},
 | 
						|
		pkMap:   map[string]interface{}{},
 | 
						|
	}
 | 
						|
	pubKeys.add("76b95ae5-33ef-4283-97b7-d2a85dc2d8f4", []byte("WNGvKVyyNmXq0TraSvjaDN9CtpFgx35IXtGEffMCPR0"))
 | 
						|
 | 
						|
	if len(pubKeys.pkMap) != 1 {
 | 
						|
		t.Fatalf("Expected 1 keys, got %d", len(pubKeys.pkMap))
 | 
						|
	}
 | 
						|
 | 
						|
	provider := providerCfg{
 | 
						|
		ClientID:     "76b95ae5-33ef-4283-97b7-d2a85dc2d8f4",
 | 
						|
		ClientSecret: "WNGvKVyyNmXq0TraSvjaDN9CtpFgx35IXtGEffMCPR0",
 | 
						|
	}
 | 
						|
	provider.JWKS.URL = u1
 | 
						|
	cfg := Config{
 | 
						|
		Enabled: true,
 | 
						|
		pubKeys: pubKeys,
 | 
						|
		arnProviderCfgsMap: map[arn.ARN]*providerCfg{
 | 
						|
			DummyRoleARN: &provider,
 | 
						|
		},
 | 
						|
		ProviderCfgs: map[string]*providerCfg{
 | 
						|
			"1": &provider,
 | 
						|
		},
 | 
						|
		closeRespFn: func(rc io.ReadCloser) {
 | 
						|
			rc.Close()
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	var claims jwtgo.MapClaims
 | 
						|
	if err = cfg.Validate(context.Background(), DummyRoleARN, token, "", "", claims); err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestJWT(t *testing.T) {
 | 
						|
	const jsonkey = `{"keys":
 | 
						|
       [
 | 
						|
         {"kty":"RSA",
 | 
						|
          "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
 | 
						|
          "e":"AQAB",
 | 
						|
          "alg":"RS256",
 | 
						|
          "kid":"2011-04-29"}
 | 
						|
       ]
 | 
						|
     }`
 | 
						|
 | 
						|
	pubKeys := publicKeys{
 | 
						|
		RWMutex: &sync.RWMutex{},
 | 
						|
		pkMap:   map[string]interface{}{},
 | 
						|
	}
 | 
						|
	err := pubKeys.parseAndAdd(bytes.NewBuffer([]byte(jsonkey)))
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal("Error loading pubkeys:", err)
 | 
						|
	}
 | 
						|
	if len(pubKeys.pkMap) != 1 {
 | 
						|
		t.Fatalf("Expected 1 keys, got %d", len(pubKeys.pkMap))
 | 
						|
	}
 | 
						|
 | 
						|
	u1, err := xnet.ParseHTTPURL("http://127.0.0.1:8443")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	provider := providerCfg{}
 | 
						|
	provider.JWKS.URL = u1
 | 
						|
	cfg := Config{
 | 
						|
		Enabled: true,
 | 
						|
		pubKeys: pubKeys,
 | 
						|
		arnProviderCfgsMap: map[arn.ARN]*providerCfg{
 | 
						|
			DummyRoleARN: &provider,
 | 
						|
		},
 | 
						|
		ProviderCfgs: map[string]*providerCfg{
 | 
						|
			"1": &provider,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	u, err := url.Parse("http://127.0.0.1:8443/?Token=invalid")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	var claims jwtgo.MapClaims
 | 
						|
	if err = cfg.Validate(context.Background(), DummyRoleARN, u.Query().Get("Token"), "", "", claims); err == nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestDefaultExpiryDuration(t *testing.T) {
 | 
						|
	testCases := []struct {
 | 
						|
		reqURL    string
 | 
						|
		duration  time.Duration
 | 
						|
		expectErr bool
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			reqURL:   "http://127.0.0.1:8443/?Token=xxxxx",
 | 
						|
			duration: time.Duration(60) * time.Minute,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			reqURL:    "http://127.0.0.1:8443/?DurationSeconds=9s",
 | 
						|
			expectErr: true,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			reqURL:    "http://127.0.0.1:8443/?DurationSeconds=31536001",
 | 
						|
			expectErr: true,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			reqURL:    "http://127.0.0.1:8443/?DurationSeconds=800",
 | 
						|
			expectErr: true,
 | 
						|
		},
 | 
						|
		{
 | 
						|
			reqURL:   "http://127.0.0.1:8443/?DurationSeconds=901",
 | 
						|
			duration: time.Duration(901) * time.Second,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	for i, testCase := range testCases {
 | 
						|
		u, err := url.Parse(testCase.reqURL)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatal(err)
 | 
						|
		}
 | 
						|
		d, err := GetDefaultExpiration(u.Query().Get("DurationSeconds"))
 | 
						|
		gotErr := (err != nil)
 | 
						|
		if testCase.expectErr != gotErr {
 | 
						|
			t.Errorf("Test %d: Expected %v, got %v with error %s", i+1, testCase.expectErr, gotErr, err)
 | 
						|
		}
 | 
						|
		if d != testCase.duration {
 | 
						|
			t.Errorf("Test %d: Expected duration %d, got %d", i+1, testCase.duration, d)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestExpCorrect(t *testing.T) {
 | 
						|
	signKey, _ := base64.StdEncoding.DecodeString("NTNv7j0TuYARvmNMmWXo6fKvM4o6nv/aUi9ryX38ZH+L1bkrnD1ObOQ8JAUmHCBq7Iy7otZcyAagBLHVKvvYaIpmMuxmARQ97jUVG16Jkpkp1wXOPsrF9zwew6TpczyHkHgX5EuLg2MeBuiT/qJACs1J0apruOOJCg/gOtkjB4c=")
 | 
						|
 | 
						|
	claimsMap := jwtm.NewMapClaims()
 | 
						|
	claimsMap.SetExpiry(time.Now().Add(time.Minute))
 | 
						|
	claimsMap.SetAccessKey("test-access")
 | 
						|
	if err := updateClaimsExpiry("3600", claimsMap.MapClaims); err != nil {
 | 
						|
		t.Error(err)
 | 
						|
	}
 | 
						|
	// Build simple token with updated expiration claim
 | 
						|
	token := jwtgo.NewWithClaims(jwtgo.SigningMethodHS256, claimsMap)
 | 
						|
	tokenString, err := token.SignedString(signKey)
 | 
						|
	if err != nil {
 | 
						|
		t.Error(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Parse token to be sure it is valid
 | 
						|
	err = jwtm.ParseWithClaims(tokenString, claimsMap, func(*jwtm.MapClaims) ([]byte, error) {
 | 
						|
		return signKey, nil
 | 
						|
	})
 | 
						|
	if err != nil {
 | 
						|
		t.Error(err)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestKeycloakProviderInitialization(t *testing.T) {
 | 
						|
	testConfig := providerCfg{
 | 
						|
		DiscoveryDoc: DiscoveryDoc{
 | 
						|
			TokenEndpoint: "http://keycloak.test/token/endpoint",
 | 
						|
		},
 | 
						|
	}
 | 
						|
	testKvs := config.KVS{}
 | 
						|
	testKvs.Set(Vendor, "keycloak")
 | 
						|
	testKvs.Set(KeyCloakRealm, "TestRealm")
 | 
						|
	testKvs.Set(KeyCloakAdminURL, "http://keycloak.test/auth/admin")
 | 
						|
	cfgGet := func(param string) string {
 | 
						|
		return testKvs.Get(param)
 | 
						|
	}
 | 
						|
 | 
						|
	if testConfig.provider != nil {
 | 
						|
		t.Errorf("Empty config cannot have any provider!")
 | 
						|
	}
 | 
						|
 | 
						|
	if err := testConfig.initializeProvider(cfgGet, http.DefaultTransport); err != nil {
 | 
						|
		t.Error(err)
 | 
						|
	}
 | 
						|
 | 
						|
	if testConfig.provider == nil {
 | 
						|
		t.Errorf("keycloak provider must be initialized!")
 | 
						|
	}
 | 
						|
}
 |