mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-06-23 00:19:36 +08:00
394 lines
11 KiB
Go
394 lines
11 KiB
Go
// Copyright 2019 Yunion
|
|
//
|
|
// 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 (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"yunion.io/x/jsonutils"
|
|
"yunion.io/x/log"
|
|
"yunion.io/x/pkg/util/cache"
|
|
|
|
"yunion.io/x/onecloud/pkg/mcclient"
|
|
)
|
|
|
|
var (
|
|
manager *authManager
|
|
defaultTimeout int = 600 // maybe time.Duration better
|
|
defaultCacheCount int64 = 100000
|
|
initCh chan bool = make(chan bool)
|
|
globalEndpointType string
|
|
)
|
|
|
|
const (
|
|
PublicEndpointType string = "public"
|
|
InternalEndpointType string = "internal"
|
|
)
|
|
|
|
type AuthInfo struct {
|
|
AuthUrl string
|
|
// Domain not need when v2 auth
|
|
Domain string
|
|
Username string
|
|
Passwd string
|
|
// Project is tenant when v2 auth
|
|
Project string
|
|
ProjectDomain string
|
|
}
|
|
|
|
func SetTimeout(t time.Duration) {
|
|
defaultTimeout = int(t)
|
|
}
|
|
|
|
func SetEndpointType(epType string) {
|
|
globalEndpointType = epType
|
|
}
|
|
|
|
func NewV2AuthInfo(authUrl, user, passwd, tenant string) *AuthInfo {
|
|
return NewAuthInfo(authUrl, "", user, passwd, tenant, "")
|
|
}
|
|
|
|
func NewAuthInfo(authUrl, domain, user, passwd, project, projectDomain string) *AuthInfo {
|
|
return &AuthInfo{
|
|
AuthUrl: authUrl,
|
|
Domain: domain,
|
|
Username: user,
|
|
Passwd: passwd,
|
|
Project: project,
|
|
ProjectDomain: projectDomain,
|
|
}
|
|
}
|
|
|
|
type cacheItem struct {
|
|
credential mcclient.TokenCredential
|
|
}
|
|
|
|
func (item *cacheItem) Size() int {
|
|
return 1
|
|
}
|
|
|
|
type TokenCacheVerify struct {
|
|
*cache.LRUCache
|
|
}
|
|
|
|
func NewTokenCacheVerify() *TokenCacheVerify {
|
|
return &TokenCacheVerify{
|
|
LRUCache: cache.NewLRUCache(defaultCacheCount),
|
|
}
|
|
}
|
|
|
|
func (c *TokenCacheVerify) AddToken(cred mcclient.TokenCredential) error {
|
|
item := &cacheItem{cred}
|
|
c.Set(cred.GetTokenString(), item)
|
|
return nil
|
|
}
|
|
|
|
func (c *TokenCacheVerify) GetToken(token string) (mcclient.TokenCredential, bool) {
|
|
item, found := c.Get(token)
|
|
|
|
if !found {
|
|
return nil, false
|
|
}
|
|
|
|
return item.(*cacheItem).credential, true
|
|
}
|
|
|
|
func (c *TokenCacheVerify) DeleteToken(token string) bool {
|
|
return c.Delete(token)
|
|
}
|
|
|
|
func (c *TokenCacheVerify) Verify(cli *mcclient.Client, adminToken, token string) (mcclient.TokenCredential, error) {
|
|
cred, found := c.GetToken(token)
|
|
if found {
|
|
if cred.IsValid() {
|
|
return cred, nil
|
|
} else {
|
|
c.DeleteToken(token)
|
|
log.Infof("Remove expired cache token: %s", token)
|
|
}
|
|
}
|
|
|
|
cred, err := cli.Verify(adminToken, token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cred = mcclient.SimplifyToken(cred)
|
|
err = c.AddToken(cred)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Add %s credential to cache: %#v", cred.GetTokenString(), err)
|
|
}
|
|
callbackAuthhooks(cred)
|
|
// log.Debugf("Add token: %s", cred)
|
|
return cred, nil
|
|
}
|
|
|
|
type authManager struct {
|
|
client *mcclient.Client
|
|
info *AuthInfo
|
|
adminCredential mcclient.TokenCredential
|
|
tokenCacheVerify *TokenCacheVerify
|
|
accessKeyCache *sAccessKeyCache
|
|
}
|
|
|
|
func newAuthManager(cli *mcclient.Client, info *AuthInfo) *authManager {
|
|
return &authManager{
|
|
client: cli,
|
|
info: info,
|
|
tokenCacheVerify: NewTokenCacheVerify(),
|
|
accessKeyCache: newAccessKeyCache(),
|
|
}
|
|
}
|
|
|
|
func (a *authManager) verifyRequest(req http.Request, virtualHost bool) (mcclient.TokenCredential, error) {
|
|
if a.adminCredential == nil {
|
|
return nil, fmt.Errorf("No valid admin token credential")
|
|
}
|
|
cred, err := a.accessKeyCache.Verify(a.client, req, virtualHost)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return cred, nil
|
|
}
|
|
|
|
func (a *authManager) verify(token string) (mcclient.TokenCredential, error) {
|
|
if a.adminCredential == nil {
|
|
return nil, fmt.Errorf("No valid admin token credential")
|
|
}
|
|
cred, err := a.tokenCacheVerify.Verify(a.client, a.adminCredential.GetTokenString(), token)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return cred, nil
|
|
}
|
|
|
|
func (a *authManager) authAdmin() error {
|
|
var token mcclient.TokenCredential
|
|
var err error
|
|
token, err = a.client.AuthenticateWithSource(
|
|
a.info.Username, a.info.Passwd, a.info.Domain,
|
|
a.info.Project, a.info.ProjectDomain, mcclient.AuthSourceSrv)
|
|
if err != nil {
|
|
log.Errorf("Admin auth failed: %s", err)
|
|
return err
|
|
}
|
|
if token != nil {
|
|
a.adminCredential = token
|
|
return nil
|
|
} else {
|
|
return fmt.Errorf("Auth token is nil")
|
|
}
|
|
}
|
|
|
|
func (a *authManager) reAuth() {
|
|
redoSleepTime := 60 * time.Second
|
|
for {
|
|
err := a.authAdmin()
|
|
if err == nil {
|
|
break
|
|
}
|
|
log.Errorf("Reauth failed: %s, try it again after %v", err, redoSleepTime)
|
|
time.Sleep(redoSleepTime)
|
|
}
|
|
expire := a.adminCredential.GetExpires()
|
|
duration := expire.Sub(time.Now())
|
|
time.AfterFunc(time.Duration(duration.Nanoseconds()/2), a.reAuth)
|
|
}
|
|
|
|
func (a *authManager) GetServiceURL(service, region, zone, endpointType string) (string, error) {
|
|
if endpointType == "" && globalEndpointType != "" {
|
|
endpointType = globalEndpointType
|
|
}
|
|
return a.adminCredential.GetServiceURL(service, region, zone, endpointType)
|
|
}
|
|
|
|
func (a *authManager) GetServiceURLs(service, region, zone, endpointType string) ([]string, error) {
|
|
if endpointType == "" && globalEndpointType != "" {
|
|
endpointType = globalEndpointType
|
|
}
|
|
return a.adminCredential.GetServiceURLs(service, region, zone, endpointType)
|
|
}
|
|
|
|
func (a *authManager) getTokenString() string {
|
|
return a.adminCredential.GetTokenString()
|
|
}
|
|
|
|
func (a *authManager) isExpired() bool {
|
|
return time.Now().After(a.adminCredential.GetExpires())
|
|
}
|
|
|
|
func (a *authManager) isAuthed() bool {
|
|
if a == nil {
|
|
return false
|
|
}
|
|
if a.adminCredential == nil || a.isExpired() {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (a *authManager) init() error {
|
|
if err := a.authAdmin(); err != nil {
|
|
initCh <- false
|
|
log.Fatalf("Auth manager init err: %v", err)
|
|
}
|
|
log.Infof("Get token: %v", a.getTokenString())
|
|
expire := a.adminCredential.GetExpires()
|
|
duration := expire.Sub(time.Now())
|
|
time.AfterFunc(time.Duration(duration.Nanoseconds()/2), a.reAuth)
|
|
initCh <- true
|
|
return nil
|
|
}
|
|
|
|
func GetCatalogData(serviceTypes []string, region string) jsonutils.JSONObject {
|
|
return manager.adminCredential.GetCatalogData(serviceTypes, region)
|
|
}
|
|
|
|
func Verify(tokenId string) (mcclient.TokenCredential, error) {
|
|
return manager.verify(tokenId)
|
|
}
|
|
|
|
func VerifyRequest(req http.Request, virtualHost bool) (mcclient.TokenCredential, error) {
|
|
return manager.verifyRequest(req, virtualHost)
|
|
}
|
|
|
|
func GetServiceURL(service, region, zone, endpointType string) (string, error) {
|
|
return manager.GetServiceURL(service, region, zone, endpointType)
|
|
}
|
|
|
|
func GetPublicServiceURL(service, region, zone string) (string, error) {
|
|
return manager.GetServiceURL(service, region, zone, PublicEndpointType)
|
|
}
|
|
|
|
func GetServiceURLs(service, region, zone, endpointType string) ([]string, error) {
|
|
return manager.GetServiceURLs(service, region, zone, endpointType)
|
|
}
|
|
|
|
func GetTokenString() string {
|
|
return manager.getTokenString()
|
|
}
|
|
|
|
func IsAuthed() bool {
|
|
return manager.isAuthed()
|
|
}
|
|
|
|
func Client() *mcclient.Client {
|
|
return manager.client
|
|
}
|
|
|
|
func AdminCredential() mcclient.TokenCredential {
|
|
return manager.adminCredential
|
|
}
|
|
|
|
func AdminSession(ctx context.Context, region, zone, endpointType, apiVersion string) *mcclient.ClientSession {
|
|
cli := Client()
|
|
if cli == nil {
|
|
return nil
|
|
}
|
|
if endpointType == "" && globalEndpointType != "" {
|
|
endpointType = globalEndpointType
|
|
}
|
|
return cli.NewSession(ctx, region, zone, endpointType, AdminCredential(), apiVersion)
|
|
}
|
|
|
|
func AdminSessionWithInternal(ctx context.Context, region, zone, apiVersion string) *mcclient.ClientSession {
|
|
return AdminSession(ctx, region, zone, "internal", apiVersion)
|
|
}
|
|
|
|
func AdminSessionWithPublic(ctx context.Context, region, zone, apiVersion string) *mcclient.ClientSession {
|
|
return AdminSession(ctx, region, zone, "public", apiVersion)
|
|
}
|
|
|
|
type AuthCompletedCallback func()
|
|
|
|
func (callback *AuthCompletedCallback) Run() {
|
|
f := *callback
|
|
for {
|
|
initOk := <-initCh
|
|
if initOk && manager.isAuthed() {
|
|
log.V(10).Infof("Auth completed, run callback: %v", *callback)
|
|
f()
|
|
return
|
|
}
|
|
log.Warningf("Auth manager not ready, check it again...")
|
|
}
|
|
}
|
|
|
|
func AsyncInit(info *AuthInfo, debug, insecure bool, certFile, keyFile string, callback AuthCompletedCallback) {
|
|
cli := mcclient.NewClient(info.AuthUrl, defaultTimeout, debug, insecure, certFile, keyFile)
|
|
manager = newAuthManager(cli, info)
|
|
go manager.init()
|
|
if callback != nil {
|
|
go callback.Run()
|
|
}
|
|
}
|
|
|
|
func Init(info *AuthInfo, debug, insecure bool, certFile, keyFile string) {
|
|
done := make(chan bool, 1)
|
|
f := func() {
|
|
done <- true
|
|
}
|
|
AsyncInit(info, debug, insecure, certFile, keyFile, f)
|
|
<-done
|
|
}
|
|
|
|
func GetAdminSession(ctx context.Context, region string,
|
|
apiVersion string) *mcclient.ClientSession {
|
|
return GetSession(ctx, manager.adminCredential, region, apiVersion)
|
|
}
|
|
|
|
func GetAdminSessionWithPublic(ctx context.Context, region string,
|
|
apiVersion string) *mcclient.ClientSession {
|
|
return GetSessionWithPublic(ctx, manager.adminCredential, region, apiVersion)
|
|
}
|
|
|
|
func GetAdminSessionWithInternal(
|
|
ctx context.Context, region string, apiVersion string) *mcclient.ClientSession {
|
|
return GetSessionWithInternal(ctx, manager.adminCredential, region, apiVersion)
|
|
}
|
|
|
|
func GetSession(ctx context.Context, token mcclient.TokenCredential, region string, apiVersion string) *mcclient.ClientSession {
|
|
if len(globalEndpointType) != 0 {
|
|
return getSessionByType(ctx, token, region, apiVersion, globalEndpointType)
|
|
}
|
|
return GetSessionWithInternal(ctx, token, region, apiVersion)
|
|
}
|
|
|
|
func GetSessionWithInternal(ctx context.Context, token mcclient.TokenCredential, region string, apiVersion string) *mcclient.ClientSession {
|
|
return getSessionByType(ctx, token, region, apiVersion, InternalEndpointType)
|
|
}
|
|
|
|
func GetSessionWithPublic(ctx context.Context, token mcclient.TokenCredential, region string, apiVersion string) *mcclient.ClientSession {
|
|
return getSessionByType(ctx, token, region, apiVersion, PublicEndpointType)
|
|
}
|
|
|
|
func getSessionByType(ctx context.Context, token mcclient.TokenCredential, region string, apiVersion string, epType string) *mcclient.ClientSession {
|
|
return manager.client.NewSession(ctx, region, "", epType, token, apiVersion)
|
|
}
|
|
|
|
// use for climc test only
|
|
func InitFromClientSession(session *mcclient.ClientSession) {
|
|
cli := session.GetClient()
|
|
token := session.GetToken()
|
|
info := &AuthInfo{}
|
|
manager = &authManager{
|
|
client: cli,
|
|
info: info,
|
|
adminCredential: token,
|
|
}
|
|
}
|