Files
kubesphere/pkg/apiserver/authentication/token/issuer_test.go
2025-04-30 15:53:51 +08:00

335 lines
8.8 KiB
Go

/*
* Copyright 2024 the KubeSphere Authors.
* Please refer to the LICENSE file in the root directory of the project.
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
*/
package token
import (
"encoding/base64"
"reflect"
"testing"
"time"
"github.com/go-jose/go-jose/v4"
"github.com/golang-jwt/jwt/v4"
"github.com/stretchr/testify/assert"
"k8s.io/apiserver/pkg/authentication/user"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
)
const privateKeyData = `
-----BEGIN RSA PRIVATE KEY-----
MIIEoQIBAAKCAQEAnDK2bNmX+tBWY/JHll1T1LF3/6RTbJ2qUsvwZVuVP/XbmWeY
vDZyTR+YL6JaqRC/NibphgCV0p6cKZNuoGCEHpS0Ix9ZZwkA8BhwrFwAU0O1Qmrv
v7It3p0Lc9WKN7PBWDQnUIdeSWnAeSbmWETP8Y2e+vG/iusLojPJenEaiOiwzU8p
3CGSh7IPBXF3aUeB3dgJuaiumDuzlp0Oe/xKvWo0faB2hFXi36KaLMpugNcbejKl
R6w3jH5wJjto5XqTEpW4a77K4rt7CFXVGfcbLo+n/5j3oC0lw4KOy7OX0Qf1jY+x
sa1Q+3UoDC02sQRf77uj3eITol8Spoo7wfJqmwIDAQABAoIBAGEArYIT7+p3j+8p
+4NKGlGwlRFR/+0oTSp2NKj9o0bBbMtsJtJcDcgPoveSIDN2jwkWSVhK7MCMd/bp
9H3s8p/7QZO+WEtAsDBrPS4NRLZxChRhTNsD0LC7Xu1k5B2LqLsaSIAeUVPONRYI
Lm0K7wjYJq85iva+2c610p4Tt6LlxuOu41Zw7RAaW8nBoMdQzi19X+hUloogVo7S
hid8gm2KUPY6xF+RpHGQ5OUND0d+2wBkHxbYNRIfYrxCKt8+dLykLzAmm+ScCfyG
jKcNoRwW5s/3ttR7r7hn3whttydkper5YvxM3+EvL83H7JL11KHcHy/yPYv+2IxQ
psvEtIECgYEAykCm/w58pdifLuWG1mwHCkdQ6wCd9gAHIDfaqbFhTcdwGGYXb1xQ
3CHjkkX6rpB3rJReCxlGq01OemVNlpIGLhdnK87aX5pRVn2fHGaMoC2V5RWv3pyE
3gJ41h9FtPX2juKFG9PNiR7FrtKPzQczfh2L1OMpLOXfPgxvo/fXBQsCgYEAxbTz
mibb4F/TBVXMuSL7Pk9hBPlFgFIEUKbiqt+sKQCqSZPGjV5giDfQDGsJ5EqOkRg0
qlCrKk+DW+d+Pmc4yo58cd2xPnQETholV19+dM3AGiy4BMjeUnJD+Dme7M/fhrlW
IK/1ZErKSZ3nN20qeneIFltm6+4pgQ1HB9KwirECgYAy65wf0xHm32cUc41DJueO
2u2wfPNIIDGrFuTinFoXLwM14V49F0z0X0Pga+X1VUIMHT6gJLj6H/iGMEMciZ8s
s4+yI94u+7dGw1Hv4JG/Mjru9krVDSsWiiDKKA1wxgxRZQ6GNwkkYK78mN7Di/CW
6/Fso9SWDTnrcU4aRifIiQKBgQCQ+kJwVfKCtIIPtX0sfeRzKs5gUVKP6JTVd6tb
1i1u29gDoGPHIt/yw8rCcHOOfsXQzElCY2lA25HeAQFoTVUt5BKJhSIGRBksFKwx
SAt5J6+pAgXnLE0rdDM3gTlzOnQVXS81RRLTeqygEzSMRncR2zll+5ybgcfZpJzj
tbJT4QJ/Y02wfkm1dL/BFg520/otVeuC+Bt+YyWMVs867xLLzFci7tj6ZzlzMorQ
PsSsOHhPx0g+Wl8K2+Edg3FQRZ1m0rQFAZn66jd96u85aA9NH/bw3A3VYUdVJyHh
4ZgZLx9JMCkmRfa7Dp2mzoqGUC1cjNvm722baeMqXpHSXDP2Jg==
-----END RSA PRIVATE KEY-----
`
func TestNewIssuer(t *testing.T) {
signKeyData := base64.StdEncoding.EncodeToString([]byte(privateKeyData))
config := &oauth.IssuerOptions{
URL: "https://ks-console.kubesphere-system.svc",
SignKeyData: signKeyData,
MaximumClockSkew: 10 * time.Second,
JWTSecret: "test-secret",
}
got, err := NewIssuer(config)
if err != nil {
t.Fatal(err)
}
signKey, keyID, err := loadSignKey(config)
if err != nil {
t.Fatal(err)
}
want := &issuer{
url: config.URL,
secret: []byte(config.JWTSecret),
maximumClockSkew: config.MaximumClockSkew,
signKey: &Keys{
SigningKey: &jose.JSONWebKey{
Key: signKey,
KeyID: keyID,
Algorithm: jwt.SigningMethodRS256.Alg(),
Use: "sig",
},
SigningKeyPub: &jose.JSONWebKey{
Key: signKey.Public(),
KeyID: keyID,
Algorithm: jwt.SigningMethodRS256.Alg(),
Use: "sig",
},
},
}
if !reflect.DeepEqual(got, want) {
t.Errorf("NewIssuerOptions() got = %v, want %v", got, want)
return
}
}
func TestNewIssuerGenerateSignKey(t *testing.T) {
config := &oauth.IssuerOptions{
URL: "https://ks-console.kubesphere-system.svc",
MaximumClockSkew: 10 * time.Second,
JWTSecret: "test-secret",
}
got, err := NewIssuer(config)
if err != nil {
t.Fatal(err)
}
iss := got.(*issuer)
assert.NotNil(t, iss.signKey)
assert.NotNil(t, iss.signKey.SigningKey)
assert.NotNil(t, iss.signKey.SigningKeyPub)
assert.NotNil(t, iss.signKey.SigningKey.KeyID)
assert.NotNil(t, iss.signKey.SigningKeyPub.KeyID)
}
func Test_issuer_IssueTo(t *testing.T) {
type fields struct {
url string
secret []byte
maximumClockSkew time.Duration
}
type args struct {
request *IssueRequest
}
tests := []struct {
name string
fields fields
args args
want *VerifiedResponse
wantErr bool
}{
{
name: "token is successfully issued",
fields: fields{
url: "kubesphere",
secret: []byte("kubesphere"),
maximumClockSkew: 0,
},
args: args{request: &IssueRequest{
User: &user.DefaultInfo{
Name: "user1",
},
Claims: Claims{
TokenType: AccessToken,
},
ExpiresIn: 2 * time.Hour},
},
want: &VerifiedResponse{
User: &user.DefaultInfo{
Name: "user1",
},
Claims: Claims{
Username: "user1",
TokenType: AccessToken,
},
},
wantErr: false,
},
{
name: "token is successfully issued",
fields: fields{
url: "kubesphere",
secret: []byte("kubesphere"),
maximumClockSkew: 0,
},
args: args{request: &IssueRequest{
User: &user.DefaultInfo{
Name: "user2",
},
Claims: Claims{
Username: "user2",
TokenType: RefreshToken,
},
ExpiresIn: 0},
},
want: &VerifiedResponse{
User: &user.DefaultInfo{
Name: "user2",
},
Claims: Claims{
Username: "user2",
TokenType: RefreshToken,
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &issuer{
url: tt.fields.url,
secret: tt.fields.secret,
maximumClockSkew: tt.fields.maximumClockSkew,
}
token, err := s.IssueTo(tt.args.request)
if (err != nil) != tt.wantErr {
t.Errorf("IssueTo() error = %v, wantErr %v", err, tt.wantErr)
return
}
got, err := s.Verify(token)
if (err != nil) != tt.wantErr {
t.Errorf("Verify() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got == nil {
return
}
assert.Equal(t, got.TokenType, tt.want.TokenType)
assert.Equal(t, got.Issuer, tt.fields.url)
assert.Equal(t, got.Username, tt.want.Username)
assert.Equal(t, got.Subject, tt.want.User.GetName())
assert.NotZero(t, got.IssuedAt)
})
}
}
func Test_issuer_Verify(t *testing.T) {
type fields struct {
url string
secret []byte
maximumClockSkew time.Duration
}
type args struct {
token string
}
tests := []struct {
name string
fields fields
args args
want *VerifiedResponse
wantErr bool
}{
{
name: "token validation failed",
fields: fields{
url: "kubesphere",
secret: []byte("kubesphere"),
maximumClockSkew: 0,
},
args: args{token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwidG9rZW5fdHlwZSI6ImFjY2Vzc190b2tlbiIsImV4cCI6MTYzMDY0MDMyMywiaWF0IjoxNjMwNjM2NzIzLCJpc3MiOiJrdWJlc3BoZXJlIiwibmJmIjoxNjMwNjM2NzIzfQ.4ENxyPTIe-BoQfuY5F4Mon5tB3KeV06B4i2JITRlPA8"},
wantErr: true,
},
{
name: "token is successfully verified",
fields: fields{
url: "kubesphere",
secret: []byte("kubesphere"),
maximumClockSkew: 0,
},
args: args{token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MzA2MzczOTgsImlzcyI6Imt1YmVzcGhlcmUiLCJzdWIiOiJ1c2VyMiIsInRva2VuX3R5cGUiOiJyZWZyZXNoX3Rva2VuIiwidXNlcm5hbWUiOiJ1c2VyMiJ9.vqPczw4SyytVOQmgaK9ip2dvg2fSQStUUE_Y7Ts45WY"},
want: &VerifiedResponse{
User: &user.DefaultInfo{
Name: "user2",
},
Claims: Claims{
Username: "user2",
TokenType: RefreshToken,
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &issuer{
url: tt.fields.url,
secret: tt.fields.secret,
maximumClockSkew: tt.fields.maximumClockSkew,
}
got, err := s.Verify(tt.args.token)
if (err != nil) != tt.wantErr {
t.Errorf("Verify() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got == nil {
return
}
assert.Equal(t, got.TokenType, tt.want.TokenType)
assert.Equal(t, got.Issuer, tt.fields.url)
assert.Equal(t, got.Username, tt.want.Username)
assert.Equal(t, got.Subject, tt.want.User.GetName())
assert.NotZero(t, got.IssuedAt)
})
}
}
func Test_issuer_keyFunc(t *testing.T) {
type fields struct {
//nolint:unused
name string
secret []byte
//nolint:unused
maximumClockSkew time.Duration
}
type args struct {
token *jwt.Token
}
tests := []struct {
name string
fields fields
args args
}{
{
name: "sign key obtained successfully",
fields: fields{
secret: []byte("kubesphere"),
},
args: args{token: &jwt.Token{
Method: jwt.SigningMethodHS256,
Header: map[string]interface{}{"alg": "HS256"},
}},
},
{
name: "sign key obtained successfully",
fields: fields{},
args: args{token: &jwt.Token{
Method: jwt.SigningMethodRS256,
Header: map[string]interface{}{"alg": "RS256"},
}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, err := NewIssuer(oauth.NewIssuerOptions())
if err != nil {
t.Error(err)
return
}
iss := s.(*issuer)
got, _ := iss.keyFunc(tt.args.token)
assert.NotNil(t, got)
})
}
}