335 lines
8.8 KiB
Go
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)
|
|
})
|
|
}
|
|
}
|