Skip to content

Commit

Permalink
Merge pull request #65 from wklken/ft_use_auth
Browse files Browse the repository at this point in the history
feat(support/bkauth): add bkauth support, check app_code/app_secret
  • Loading branch information
wklken committed Jan 12, 2022
2 parents 80c86e0 + afaab8a commit 2b02b54
Show file tree
Hide file tree
Showing 15 changed files with 293 additions and 34 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.9.3
1.9.4
26 changes: 23 additions & 3 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,19 @@ func initDatabase() {
panic("database bk-iam should be configured")
}

if globalConfig.EnableBkAuth {
database.InitDBClients(&defaultDBConfig, nil)
log.Info("init Database success")
return
}

// TODO: 不应该成为强依赖
bkPaaSDBConfig, ok := globalConfig.DatabaseMap["open_paas"]
if !ok {
panic("database open_paas should be configured")
panic("bkauth is not enabled, so database open_paas should be configured")
}

database.InitDBClients(&defaultDBConfig, &bkPaaSDBConfig)

log.Info("init Database success")
}

Expand Down Expand Up @@ -142,7 +148,21 @@ func initSupportShieldFeatures() {
}

func initComponents() {
component.InitComponentClients()
component.InitBkRemoteResourceClient()

if globalConfig.EnableBkAuth {
bkAuthHost, ok := globalConfig.HostMap["bkauth"]
if !ok {
panic("bkauth is enabled, so host bkauth should be configured")
}

if globalConfig.BkAppCode == "" || globalConfig.BkAppSecret == "" {
panic("bkauth is enabled, but iam's bkAppCode and bkAppSecret is not configured")
}

component.InitBkAuthClient(bkAuthHost.Addr, globalConfig.BkAppCode, globalConfig.BkAppSecret)
log.Infof("init bkauth client success, host = %s", bkAuthHost.Addr)
}
}

func initQuota() {
Expand Down
4 changes: 2 additions & 2 deletions pkg/api/model/handler/system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func TestCreateSystem(t *testing.T) {

// init the router
r := util.SetupRouter()
r.Use(middleware.ClientAuthMiddleware([]byte("")))
r.Use(middleware.ClientAuthMiddleware([]byte(""), false))
url := "/api/v1/systems"
r.POST(url, CreateSystem)

Expand Down Expand Up @@ -287,7 +287,7 @@ func TestUpdateSystem(t *testing.T) {

// init the router
r := util.SetupRouter()
r.Use(middleware.ClientAuthMiddleware([]byte("")))
r.Use(middleware.ClientAuthMiddleware([]byte(""), false))
url := "/api/v1/systems/test"
r.POST(url, UpdateSystem)

Expand Down
4 changes: 4 additions & 0 deletions pkg/cacheimpls/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const CacheLayer = "Cache"
// LocalAppCodeAppSecretCache ...
var (
LocalAppCodeAppSecretCache memory.Cache
LocalAuthAppAccessKeyCache *gocache.Cache
LocalSubjectCache memory.Cache
LocalSubjectRoleCache memory.Cache
LocalSystemClientsCache memory.Cache
Expand Down Expand Up @@ -81,6 +82,9 @@ func InitCaches(disabled bool) {
nil,
)

// auth app_code/app_secret cache
LocalAuthAppAccessKeyCache = gocache.New(12*time.Hour, 5*time.Minute)

// 影响: engine增量同步

LocalSubjectCache = memory.NewCache(
Expand Down
39 changes: 38 additions & 1 deletion pkg/cacheimpls/local_app_code_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@
package cacheimpls

import (
"time"

"github.com/TencentBlueKing/gopkg/cache"
"github.com/TencentBlueKing/gopkg/stringx"
gocache "github.com/patrickmn/go-cache"
log "github.com/sirupsen/logrus"

"iam/pkg/component"
"iam/pkg/database/edao"
)

Expand Down Expand Up @@ -43,8 +48,40 @@ func VerifyAppCodeAppSecret(appCode, appSecret string) bool {
}
exists, err := LocalAppCodeAppSecretCache.GetBool(key)
if err != nil {
log.Errorf("get app_code_app_secret from memory cache fail, key=%s, err=%s", key.Key(), err)
log.Errorf("get app_code_app_secret from memory cache fail, app_code=%s, app_secret=%s, err=%s",
appCode,
stringx.Truncate(appSecret, 6)+"******",
err)
return false
}
return exists
}

func VerifyAppCodeAppSecretFromAuth(appCode, appSecret string) bool {
// 1. get from cache
key := appCode + ":" + appSecret

value, found := LocalAuthAppAccessKeyCache.Get(key)
if found {
return value.(bool)
}

// 2. get from auth
valid, err := component.BkAuth.Verify(appCode, appSecret)
if err != nil {
log.Errorf("verify app_code_app_secret from auth fail, app_code=%s, app_secret=%s, err=%s",
appCode,
stringx.Truncate(appSecret, 6)+"******",
err)
return false
}

// 3. set to cache, default 12 hours, if not valid, only keep in cache for 1 minutes
// in case of auth server down, we can still get the valid matched accessKeys from cache
ttl := gocache.DefaultExpiration
if !valid {
ttl = 1 * time.Minute
}
LocalAuthAppAccessKeyCache.Set(key, valid, ttl)
return valid
}
4 changes: 2 additions & 2 deletions pkg/cacheimpls/local_remote_resource_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ func listRemoteResources(systemID, _type string, ids []string, fields []string)
return nil, err
}

resources, err := component.BKRemoteResource.GetResources(req, systemID, _type, ids, fields)
resources, err := component.BkRemoteResource.GetResources(req, systemID, _type, ids, fields)
if err != nil {
err = errorWrapf(
err, "BKRemoteResource.GetResource systemID=`%s`, resourceTypeID=`%s`, ids length=`%d`, fields=`%s` fail",
err, "BkRemoteResource.GetResource systemID=`%s`, resourceTypeID=`%s`, ids length=`%d`, fields=`%s` fail",
systemID, _type, len(ids), fields)
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cacheimpls/remote_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func TestGetCMDBResource(t *testing.T) {
"id": "checklist",
}}, nil).AnyTimes()

component.BKRemoteResource = mockService
component.BkRemoteResource = mockService

mockCache := redis.NewMockCache("mockCache", expiration)

Expand Down
163 changes: 163 additions & 0 deletions pkg/component/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-权限中心(BlueKing-IAM) available.
* Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://opensource.org/licenses/MIT
* 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 component

import (
"errors"
"fmt"
"net/http"
"strings"
"time"

"github.com/TencentBlueKing/gopkg/conv"
"github.com/TencentBlueKing/gopkg/errorx"
"github.com/parnurzeal/gorequest"

"iam/pkg/logging"
)

// AuthResponse is the struct of iam backend response
type AuthResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data map[string]interface{} `json:"data"`
}

// Error will check if the response with error
func (r *AuthResponse) Error() error {
if r.Code == 0 {
return nil
}

return fmt.Errorf("response error[code=`%d`, message=`%s`]", r.Code, r.Message)
}

// String will return the detail text of the response
func (r *AuthResponse) String() string {
return fmt.Sprintf("response[code=`%d`, message=`%s`, data=`%v`]", r.Code, r.Message, r.Data)
}

// AuthClient is the interface of auth client
type AuthClient interface {
Verify(appCode, appSecret string) (bool, error)
}

type authClient struct {
Host string

// iam's app_code/app_secret, credentials for bkauth
bkAppCode string
bkAppSecret string
}

// NewAuthClient will create a auth client
func NewAuthClient(host string, bkAppCode string, bkAppSecret string) AuthClient {
host = strings.TrimRight(host, "/")
return &authClient{
Host: host,
bkAppCode: bkAppCode,
bkAppSecret: bkAppSecret,
}
}

func (c *authClient) call(
method Method,
path string,
data interface{},
timeout int64,
) (map[string]interface{}, error) {
errorWrapf := errorx.NewLayerFunctionErrorWrapf("component", "authClient.call")

callTimeout := time.Duration(timeout) * time.Second
if timeout == 0 {
callTimeout = defaultTimeout
}

url := fmt.Sprintf("%s%s", c.Host, path)
result := AuthResponse{}
start := time.Now()
callbackFunc := NewMetricCallback("Auth", start)

request := gorequest.New()
switch method {
case POST:
request = request.Post(url)
case GET:
request = request.Get(url)
}
request = request.Timeout(callTimeout).Type("json")

// set headers
request.Header.Set("X-BK-APP-CODE", c.bkAppCode)
request.Header.Set("X-BK-APP-SECRET", c.bkAppSecret)

// do request
resp, respBody, errs := request.
Send(data).
EndStruct(&result, callbackFunc)

// NOTE: it's a sensitive api, so, no log request detail!
// logFailHTTPRequest(start, request, resp, respBody, errs, &result)
logger := logging.GetComponentLogger()

var err error
if len(errs) != 0 {
// 敏感信息泄漏 ip+端口号, 替换为 *.*.*.*
errsMessage := fmt.Sprintf("gorequest errorx=`%s`", errs)
errsMessage = ipRegex.ReplaceAllString(errsMessage, replaceToIP)
err = errors.New(errsMessage)

err = errorWrapf(err, "errsCount=`%d`", len(errs))
logger.Errorf("call auth api %s fail, err=%s", path, err.Error())
return nil, err
}
if resp.StatusCode != http.StatusOK {
err = fmt.Errorf("gorequest statusCode is %d not 200, respBody=%s",
resp.StatusCode, conv.BytesToString(respBody))
logger.Errorf("call auth api %s fail , err=%s", path, err.Error())
return nil, errorWrapf(err, "status=%d", resp.StatusCode)
}
if result.Code != 0 {
err = errors.New(result.Message)
err = errorWrapf(err, "result.Code=%d", result.Code)
logger.Errorf("call auth api %s ok but code in response is not 0, respBody=%s, err=%s",
path, conv.BytesToString(respBody), err.Error())
return nil, err
}

return result.Data, nil
}

// Verify will check bkAppCode, bkAppSecret is valid
func (c *authClient) Verify(appCode, appSecret string) (bool, error) {
errorWrapf := errorx.NewLayerFunctionErrorWrapf("component", "authClient.Verify")

path := fmt.Sprintf("/api/v1/apps/%s/access-keys/verify", appCode)

data, err := c.call(POST, path, map[string]interface{}{
"bk_app_secret": appSecret,
}, 5)
if err != nil {
err = errorWrapf(err, "verify app_code=`%s` fail", appCode)

return false, err
}
matchI, ok := data["is_match"]
if !ok {
return false, errors.New("no is_match in response body")
}

match, ok := matchI.(bool)
if !ok {
return false, errors.New("is_match is not a valid bool")
}
return match, nil
}
14 changes: 9 additions & 5 deletions pkg/component/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,18 @@ const (
maxResponseBodyLength = 10240
)

// BKRemoteResource ...
var (
BKRemoteResource RemoteResourceClient
BkRemoteResource RemoteResourceClient
BkAuth AuthClient
)

// InitComponentClients ...
func InitComponentClients() {
BKRemoteResource = NewRemoteResourceClient()
// InitBkRemoteResourceClient ...
func InitBkRemoteResourceClient() {
BkRemoteResource = NewRemoteResourceClient()
}

func InitBkAuthClient(bkAuthHost, bkAppCode, bkAppSecret string) {
BkAuth = NewAuthClient(bkAuthHost, bkAppCode, bkAppSecret)
}

// CallbackFunc ...
Expand Down
16 changes: 16 additions & 0 deletions pkg/component/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@

package component

import "time"

const (
defaultTimeout = 5 * time.Second
)

// Method is the type of http method
type Method string

var (
// POST http post
POST Method = "POST"
// GET http get
GET Method = "GET"
)

type responseStruct interface {
Error() error
}
Loading

0 comments on commit 2b02b54

Please sign in to comment.