Signed-off-by: hongming <talonwan@yunify.com>
This commit is contained in:
hongming
2020-03-22 23:17:43 +08:00
parent cae7843832
commit aa05c2baf4
29 changed files with 626 additions and 367 deletions

View File

@@ -0,0 +1,58 @@
/*
*
* 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 basic
import (
"context"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
"kubesphere.io/kubesphere/pkg/models/iam/im"
)
// TokenAuthenticator implements kubernetes token authenticate interface with our custom logic.
// TokenAuthenticator will retrieve user info from cache by given token. If empty or invalid token
// was given, authenticator will still give passed response at the condition user will be user.Anonymous
// and group from user.AllUnauthenticated. This helps requests be passed along the handler chain,
// because some resources are public accessible.
type basicAuthenticator struct {
im im.IdentityManagementInterface
}
func NewBasicAuthenticator(im im.IdentityManagementInterface) authenticator.Password {
return &basicAuthenticator{
im: im,
}
}
func (t *basicAuthenticator) AuthenticatePassword(ctx context.Context, username, password string) (*authenticator.Response, bool, error) {
providedUser, err := t.im.Authenticate(username, password)
if err != nil {
return nil, false, err
}
return &authenticator.Response{
User: &user.DefaultInfo{
Name: providedUser.GetName(),
UID: providedUser.GetUID(),
Groups: []string{user.AllAuthenticated},
},
}, true, nil
}

View File

@@ -4,44 +4,30 @@ import (
"context"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
"kubesphere.io/kubesphere/pkg/api/auth/token"
"kubesphere.io/kubesphere/pkg/server/errors"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
token2 "kubesphere.io/kubesphere/pkg/apiserver/authentication/token"
)
var errTokenExpired = errors.New("expired token")
// TokenAuthenticator implements kubernetes token authenticate interface with our custom logic.
// TokenAuthenticator will retrieve user info from cache by given token. If empty or invalid token
// was given, authenticator will still give passed response at the condition user will be user.Anonymous
// and group from user.AllUnauthenticated. This helps requests be passed along the handler chain,
// because some resources are public accessible.
type tokenAuthenticator struct {
cacheClient cache.Interface
jwtTokenIssuer token.Issuer
jwtTokenIssuer token2.Issuer
}
func NewTokenAuthenticator(cacheClient cache.Interface, jwtSecret string) authenticator.Token {
func NewTokenAuthenticator(issuer token2.Issuer) authenticator.Token {
return &tokenAuthenticator{
cacheClient: cacheClient,
jwtTokenIssuer: token.NewJwtTokenIssuer(token.DefaultIssuerName, []byte(jwtSecret)),
jwtTokenIssuer: issuer,
}
}
func (t *tokenAuthenticator) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
providedUser, err := t.jwtTokenIssuer.Verify(token)
providedUser, _, err := t.jwtTokenIssuer.Verify(token)
if err != nil {
return nil, false, err
}
// TODO implement token cache
//_, err = t.cacheClient.Get(tokenKeyForUsername(providedUser.Name(), token))
//if err != nil {
// return nil, false, errTokenExpired
//}
// Should we need to refresh token?
return &authenticator.Response{
User: &user.DefaultInfo{
Name: providedUser.GetName(),

View File

@@ -0,0 +1,30 @@
/*
*
* 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 oauth
import (
"errors"
"golang.org/x/oauth2"
)
var ConfigNotFound = errors.New("config not found")
type Configuration interface {
Load(clientId string) (*oauth2.Config, error)
}

View File

@@ -0,0 +1,37 @@
/*
*
* 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 oauth
import "golang.org/x/oauth2"
type SimpleConfigManager struct {
}
func (s *SimpleConfigManager) Load(clientId string) (*oauth2.Config, error) {
if clientId == "kubesphere-console-client" {
return &oauth2.Config{
ClientID: "8b21fef43889a28f2bd6",
ClientSecret: "xb21fef43889a28f2bd6",
Endpoint: oauth2.Endpoint{AuthURL: "http://ks-apiserver.kubesphere-system.svc/oauth/authorize", TokenURL: "http://ks-apiserver.kubesphere.io/oauth/token"},
RedirectURL: "http://ks-console.kubesphere-system.svc/oauth/token/implicit",
Scopes: nil,
}, nil
}
return nil, ConfigNotFound
}

View File

@@ -1,24 +0,0 @@
package request
import (
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
"net/http"
"strings"
)
type AnonymousAuthenticator struct{}
func (a *AnonymousAuthenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
auth := strings.TrimSpace(req.Header.Get("Authorization"))
if auth == "" {
return &authenticator.Response{
User: &user.DefaultInfo{
Name: user.Anonymous,
UID: "",
Groups: []string{user.AllUnauthenticated},
},
}, true, nil
}
return nil, false, nil
}

View File

@@ -0,0 +1,46 @@
/*
*
* 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 anonymous
import (
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
"net/http"
"strings"
)
type Authenticator struct{}
func NewAuthenticator() authenticator.Request {
return &Authenticator{}
}
func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
auth := strings.TrimSpace(req.Header.Get("Authorization"))
if auth == "" {
return &authenticator.Response{
User: &user.DefaultInfo{
Name: user.Anonymous,
UID: "",
Groups: []string{user.AllUnauthenticated},
},
}, true, nil
}
return nil, false, nil
}

View File

@@ -0,0 +1,56 @@
/*
Copyright 2014 The Kubernetes 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 basictoken
import (
"errors"
"k8s.io/apiserver/pkg/authentication/authenticator"
"net/http"
)
type Authenticator struct {
auth authenticator.Password
}
func New(auth authenticator.Password) *Authenticator {
return &Authenticator{auth}
}
var invalidToken = errors.New("invalid basic token")
func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
username, password, ok := req.BasicAuth()
if !ok {
return nil, false, nil
}
resp, ok, err := a.auth.AuthenticatePassword(req.Context(), username, password)
// if we authenticated successfully, go ahead and remove the bearer token so that no one
// is ever tempted to use it inside of the API server
if ok {
req.Header.Del("Authorization")
}
// If the token authenticator didn't error, provide a default error
if !ok && err == nil {
err = invalidToken
}
return resp, ok, err
}

View File

@@ -0,0 +1,31 @@
/*
*
* 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 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, *Claims, error)
// Verify verifies a token, and return a User if it's a valid token, otherwise return error
Verify(string) (User, *Claims, error)
// Revoke a token,
Revoke(token string) error
}

View File

@@ -0,0 +1,124 @@
/*
*
* 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 token
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"kubesphere.io/kubesphere/pkg/api/auth"
"kubesphere.io/kubesphere/pkg/api/iam"
"kubesphere.io/kubesphere/pkg/server/errors"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
"time"
)
const DefaultIssuerName = "kubesphere"
var (
errInvalidToken = errors.New("invalid token")
errTokenExpired = errors.New("expired token")
)
type Claims struct {
Username string `json:"username"`
UID string `json:"uid"`
// Currently, we are not using any field in jwt.StandardClaims
jwt.StandardClaims
}
type jwtTokenIssuer struct {
name string
options *auth.AuthenticationOptions
cache cache.Interface
keyFunc jwt.Keyfunc
}
func (s *jwtTokenIssuer) Verify(tokenString string) (User, *Claims, error) {
if len(tokenString) == 0 {
return nil, nil, errInvalidToken
}
_, err := s.cache.Get(tokenCacheKey(tokenString))
if err != nil {
if err == cache.ErrNoSuchKey {
return nil, nil, errTokenExpired
}
return nil, nil, err
}
clm := &Claims{}
_, err = jwt.ParseWithClaims(tokenString, clm, s.keyFunc)
if err != nil {
return nil, nil, err
}
return &iam.User{Name: clm.Username, UID: clm.UID}, clm, nil
}
func (s *jwtTokenIssuer) IssueTo(user User) (string, *Claims, error) {
clm := &Claims{
Username: user.GetName(),
UID: user.GetUID(),
StandardClaims: jwt.StandardClaims{
IssuedAt: time.Now().Unix(),
Issuer: s.name,
NotBefore: time.Now().Unix(),
},
}
if s.options.TokenExpiration > 0 {
clm.ExpiresAt = clm.IssuedAt + int64(s.options.TokenExpiration.Seconds())
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, clm)
tokenString, err := token.SignedString([]byte(s.options.JwtSecret))
if err != nil {
return "", nil, err
}
s.cache.Set(tokenCacheKey(tokenString), tokenString, s.options.TokenExpiration)
return tokenString, clm, nil
}
func (s *jwtTokenIssuer) Revoke(token string) error {
return s.cache.Del(tokenCacheKey(token))
}
func NewJwtTokenIssuer(issuerName string, options *auth.AuthenticationOptions, cache cache.Interface) Issuer {
return &jwtTokenIssuer{
name: issuerName,
options: options,
cache: cache,
keyFunc: func(token *jwt.Token) (i interface{}, err error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok {
return []byte(options.JwtSecret), nil
} else {
return nil, fmt.Errorf("expect token signed with HMAC but got %v", token.Header["alg"])
}
},
}
}
func tokenCacheKey(token string) string {
return fmt.Sprintf("kubesphere:tokens:%s", token)
}

View File

@@ -0,0 +1,72 @@
/*
*
* 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 token
import (
"github.com/google/go-cmp/cmp"
"kubesphere.io/kubesphere/pkg/api/auth"
"kubesphere.io/kubesphere/pkg/api/iam"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
"testing"
)
func TestJwtTokenIssuer(t *testing.T) {
options := auth.NewAuthenticateOptions()
options.JwtSecret = "kubesphere"
issuer := NewJwtTokenIssuer(DefaultIssuerName, options, cache.NewSimpleCache())
testCases := []struct {
description string
name string
uid string
email string
}{
{
name: "admin",
uid: "b8be6edd-2c92-4535-9b2a-df6326474458",
},
{
name: "bar",
uid: "b8be6edd-2c92-4535-9b2a-df6326474452",
},
}
for _, testCase := range testCases {
user := &iam.User{
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

@@ -1 +0,0 @@
package token

View File

@@ -0,0 +1,27 @@
/*
*
* 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 token
type User interface {
// Name
GetName() string
// UID
GetUID() string
}