Signed-off-by: hongming <talonwan@yunify.com>
This commit is contained in:
hongming
2020-03-22 04:29:06 +08:00
parent aadb5e91e8
commit b9bdcd824c
19 changed files with 284 additions and 347 deletions

View File

@@ -0,0 +1,74 @@
/*
*
* Copyright 2020 The KubeSphere Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* /
*/
package auth
import (
"fmt"
"github.com/spf13/pflag"
"time"
)
type AuthenticationOptions struct {
// authenticate rate limit will
AuthenticateRateLimiterMaxTries int `json:"authenticateRateLimiterMaxTries" yaml:"authenticateRateLimiterMaxTries"`
AuthenticateRateLimiterDuration time.Duration `json:"authenticationRateLimiterDuration" yaml:"authenticationRateLimiterDuration"`
// maximum retries when authenticate failed
MaxAuthenticateRetries int `json:"maxAuthenticateRetries" yaml:"maxAuthenticateRetries"`
// token validation duration, will refresh token expiration for each user request
// 0 means never expire
TokenExpiration time.Duration `json:"tokenExpiration" yaml:"tokenExpiration"`
// allow multiple users login at the same time
MultipleLogin bool `json:"multipleLogin" yaml:"multipleLogin"`
// secret to signed jwt token
JwtSecret string `json:"jwtSecret" yaml:"jwtSecret"`
}
func NewAuthenticateOptions() *AuthenticationOptions {
return &AuthenticationOptions{
AuthenticateRateLimiterMaxTries: 5,
AuthenticateRateLimiterDuration: time.Minute * 30,
MaxAuthenticateRetries: 0,
TokenExpiration: 0,
MultipleLogin: false,
JwtSecret: "",
}
}
func (options *AuthenticationOptions) Validate() []error {
var errs []error
if len(options.JwtSecret) == 0 {
errs = append(errs, fmt.Errorf("jwt secret is empty"))
}
return errs
}
func (options *AuthenticationOptions) AddFlags(fs *pflag.FlagSet, s *AuthenticationOptions) {
fs.IntVar(&options.AuthenticateRateLimiterMaxTries, "authenticate-rate-limiter-max-retries", s.AuthenticateRateLimiterMaxTries, "")
fs.DurationVar(&options.AuthenticateRateLimiterDuration, "authenticate-rate-limiter-duration", s.AuthenticateRateLimiterDuration, "")
fs.IntVar(&options.MaxAuthenticateRetries, "authenticate-max-retries", s.MaxAuthenticateRetries, "")
fs.DurationVar(&options.TokenExpiration, "token-expiration", s.TokenExpiration, "Token expire duration, for example 30m/2h/1d, 0 means token never expire unless server restart.")
fs.BoolVar(&options.MultipleLogin, "multiple-login", s.MultipleLogin, "Allow multiple login with the same account, disable means only one user can login at the same time.")
fs.StringVar(&options.JwtSecret, "jwt-secret", s.JwtSecret, "Secret to sign jwt token, must not be empty.")
}

View File

@@ -0,0 +1,10 @@
package token
// Issuer issues token to user, tokens are required to perform mutating requests to resources
type Issuer interface {
// IssueTo issues a token a User, return error if issuing process failed
IssueTo(User) (string, error)
// Verify verifies a token, and return a User if it's a valid token, otherwise return error
Verify(string) (User, error)
}

76
pkg/api/auth/token/jwt.go Normal file
View File

@@ -0,0 +1,76 @@
package token
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"kubesphere.io/kubesphere/pkg/server/errors"
"time"
)
const DefaultIssuerName = "kubesphere"
var errInvalidToken = errors.New("invalid token")
type claims struct {
Username string `json:"username"`
UID string `json:"uid"`
Groups []string `json:"groups"`
// Currently, we are not using any field in jwt.StandardClaims
jwt.StandardClaims
}
type jwtTokenIssuer struct {
name string
secret []byte
keyFunc jwt.Keyfunc
}
func (s *jwtTokenIssuer) Verify(tokenString string) (User, error) {
if len(tokenString) == 0 {
return nil, errInvalidToken
}
clm := &claims{}
_, err := jwt.ParseWithClaims(tokenString, clm, s.keyFunc)
if err != nil {
return nil, err
}
return &AuthUser{Name: clm.Username, UID: clm.UID, Groups: clm.Groups}, nil
}
func (s *jwtTokenIssuer) IssueTo(user User) (string, error) {
clm := &claims{
Username: user.GetName(),
UID: user.GetUID(),
Groups: user.GetGroups(),
StandardClaims: jwt.StandardClaims{
IssuedAt: time.Now().Unix(),
Issuer: s.name,
NotBefore: time.Now().Unix(),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, clm)
tokenString, err := token.SignedString(s.secret)
if err != nil {
return "", err
}
return tokenString, nil
}
func NewJwtTokenIssuer(issuerName string, secret []byte) Issuer {
return &jwtTokenIssuer{
name: issuerName,
secret: secret,
keyFunc: func(token *jwt.Token) (i interface{}, err error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok {
return secret, nil
} else {
return nil, fmt.Errorf("expect token signed with HMAC but got %v", token.Header["alg"])
}
},
}
}

View File

@@ -0,0 +1,48 @@
package token
import (
"github.com/google/go-cmp/cmp"
"testing"
)
func TestJwtTokenIssuer(t *testing.T) {
issuer := NewJwtTokenIssuer(DefaultIssuerName, []byte("kubesphere"))
testCases := []struct {
description string
name string
uid string
}{
{
name: "admin",
uid: "b8be6edd-2c92-4535-9b2a-df6326474458",
},
{
name: "bar",
uid: "b8be6edd-2c92-4535-9b2a-df6326474452",
},
}
for _, testCase := range testCases {
user := &AuthUser{
Name: testCase.name,
UID: testCase.uid,
}
t.Run(testCase.description, func(t *testing.T) {
token, err := issuer.IssueTo(user)
if err != nil {
t.Fatal(err)
}
got, err := issuer.Verify(token)
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(user, got); len(diff) != 0 {
t.Errorf("%T differ (-got, +expected), %s", user, diff)
}
})
}
}

View File

@@ -0,0 +1,30 @@
package token
type User interface {
// Name
GetName() string
// UID
GetUID() string
// Groups
GetGroups() []string
}
type AuthUser struct {
Name string
UID string
Groups []string
}
func (a AuthUser) GetName() string {
return a.Name
}
func (a AuthUser) GetUID() string {
return a.UID
}
func (a AuthUser) GetGroups() []string {
return a.Groups
}

48
pkg/api/auth/types.go Normal file
View File

@@ -0,0 +1,48 @@
/*
*
* Copyright 2020 The KubeSphere Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* /
*/
package auth
import "fmt"
const (
KindTokenReview = "TokenReview"
)
type Spec struct {
Token string `json:"token" description:"access token"`
}
type Status struct {
Authenticated bool `json:"authenticated" description:"is authenticated"`
User map[string]interface{} `json:"user,omitempty" description:"user info"`
}
type TokenReview struct {
APIVersion string `json:"apiVersion" description:"Kubernetes API version"`
Kind string `json:"kind" description:"kind of the API object"`
Spec *Spec `json:"spec,omitempty"`
Status *Status `json:"status,omitempty" description:"token review status"`
}
func (request *TokenReview) Validate() error {
if request.Spec == nil || request.Spec.Token == "" {
return fmt.Errorf("token must not be null")
}
return nil
}