Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 21 additions & 14 deletions pkg/microservice/aslan/core/common/service/nacos.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,29 @@ import (
"github.com/koderover/zadig/v2/pkg/types"
)

func logNacosError(log *zap.SugaredLogger, err error) {
if err == nil {
return
}

cause := errors.Cause(err)
if cause != nil && cause != err {
log.Errorf("%v, raw error: %v", err, cause)
return
}

log.Error(err)
}

func ListNacosNamespace(nacosID string, log *zap.SugaredLogger) ([]*types.NacosNamespace, error) {
client, err := GetNacosClient(nacosID)
if err != nil {
err = errors.Wrap(err, "fail to get nacos client")
log.Error(err)
logNacosError(log, err)
return []*types.NacosNamespace{}, err
}
resp, err := client.ListNamespaces()
if err != nil {
err = errors.Wrap(err, "fail to list nacos namespace")
log.Error(err)
logNacosError(log, err)
return []*types.NacosNamespace{}, err
}
return resp, nil
Expand All @@ -46,14 +58,12 @@ func ListNacosNamespace(nacosID string, log *zap.SugaredLogger) ([]*types.NacosN
func ListNacosConfig(nacosID, namespaceID, groupName string, log *zap.SugaredLogger) ([]*types.NacosConfig, error) {
client, err := GetNacosClient(nacosID)
if err != nil {
err = errors.Wrap(err, "fail to get nacos client")
log.Error(err)
logNacosError(log, err)
return []*types.NacosConfig{}, err
}
namespaces, err := client.ListNamespaces()
if err != nil {
err = errors.Wrap(err, "fail to list nacos namespaces")
log.Error(err)
logNacosError(log, err)
return nil, err
}

Expand All @@ -67,8 +77,7 @@ func ListNacosConfig(nacosID, namespaceID, groupName string, log *zap.SugaredLog

resp, err := client.ListConfigs(namespaceID, groupName)
if err != nil {
err = errors.Wrap(err, "fail to list nacos config")
log.Error(err)
logNacosError(log, err)
return []*types.NacosConfig{}, err
}
for _, item := range resp {
Expand All @@ -82,15 +91,13 @@ func ListNacosConfig(nacosID, namespaceID, groupName string, log *zap.SugaredLog
func ListNacosGroup(nacosID, namespaceID, keyword string, log *zap.SugaredLogger) ([]*types.NacosDataID, error) {
client, err := GetNacosClient(nacosID)
if err != nil {
err = errors.Wrap(err, "fail to get nacos client")
log.Error(err)
logNacosError(log, err)
return []*types.NacosDataID{}, err
}

resp, err := client.ListGroups(namespaceID, keyword)
if err != nil {
err = errors.Wrap(err, "fail to list nacos config")
log.Error(err)
logNacosError(log, err)
return []*types.NacosDataID{}, err
}

Expand Down
9 changes: 4 additions & 5 deletions pkg/microservice/aslan/core/system/handler/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,11 @@ func ListProxies(c *gin.Context) {
return
}

// TODO: Authroization leaks
// authorization checks

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不要改这里,可能会有问题,造成普通用户无法使用代理,真的要改的话也要跟前端确认没问题后再改

//if !ctx.Resources.IsSystemAdmin {
// ctx.UnAuthorized = true
// return
//}
if !ctx.Resources.IsSystemAdmin {
ctx.UnAuthorized = true
return
}

ctx.Resp, ctx.RespErr = service.ListProxies(ctx.Logger)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,12 @@ func (j NacosJobController) Update(useUserInput bool, ticket *commonmodels.Appro

nacosConfigs, err := commonservice.ListNacosConfig(j.jobSpec.NacosID, j.jobSpec.NamespaceID, j.jobSpec.GroupName, log.SugaredLogger())
if err != nil {
return fmt.Errorf("fail to list nacos config: %w", err)
return err

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

去掉描述是为什么?

}

namespaces, err := commonservice.ListNacosNamespace(j.jobSpec.NacosID, log.SugaredLogger())
if err != nil {
return fmt.Errorf("failed to list nacos namespace")
return err
}

namespaceName := ""
Expand Down Expand Up @@ -169,12 +169,12 @@ func (j NacosJobController) Update(useUserInput bool, ticket *commonmodels.Appro
func (j NacosJobController) SetOptions(ticket *commonmodels.ApprovalTicket) error {
nacosConfigs, err := commonservice.ListNacosConfig(j.jobSpec.NacosID, j.jobSpec.NamespaceID, j.jobSpec.GroupName, log.SugaredLogger())
if err != nil {
return fmt.Errorf("fail to list nacos config: %w", err)
return err
}

namespaces, err := commonservice.ListNacosNamespace(j.jobSpec.NacosID, log.SugaredLogger())
if err != nil {
return fmt.Errorf("failed to list nacos namespace")
return err
}

namespaceName := ""
Expand Down Expand Up @@ -226,7 +226,7 @@ func (j NacosJobController) ToTask(taskID int64) ([]*commonmodels.JobTask, error
}
client, err := commonservice.GetNacosClient(j.jobSpec.NacosID)
if err != nil {
return nil, fmt.Errorf("get nacos client error: %v", err)
return nil, err
}
namespaces, err := client.ListNamespaces()
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions pkg/tool/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ func ErrorMessage(err error) (code int, message map[string]interface{}) {
"type": "error",
"message": v.Message(),
"code": v.Code(),
"description": v.Desc(),
"description": sanitizeSensitiveInfo(v.Desc()),
"extra": v.Extra(),
}
}
Expand All @@ -146,6 +146,6 @@ func ErrorMessage(err error) (code int, message map[string]interface{}) {
return internalErr.Code(), map[string]interface{}{
"message": internalErr.Error(),
"code": internalErr.Code(),
"description": err.Error(),
"description": sanitizeSensitiveInfo(err.Error()),
}
}
20 changes: 18 additions & 2 deletions pkg/tool/errors/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ func TestErrors(t *testing.T) {

httpErr := NewHTTPError(400, "testErr", "error description")
assert.Equal(400, httpErr.Code())
assert.Equal("testErr", httpErr.Error())
assert.Equal("testErr: error description", httpErr.Error())
assert.Equal("error description", httpErr.Desc())

httpErr.AddDesc("error description updated")
assert.Equal("testErr: error description updated", httpErr.Error())
assert.Equal("error description updated", httpErr.Desc())

err2 := NewWithDesc(httpErr, "new error with desc")
Expand All @@ -50,6 +51,21 @@ func TestErrors(t *testing.T) {
code, message := ErrorMessage(httpErr)
assert.Equal(400, code)
assert.Equal(400, message["code"])
assert.Equal(httpErr.Error(), message["message"])
assert.Equal("error", message["type"])
assert.Equal(httpErr.Message(), message["message"])
assert.Equal(httpErr.Desc(), message["description"])
}

func TestSanitizeSensitiveInfoKeepsWrappedURLDelimiters(t *testing.T) {
assert := assert.New(t)

input := `(https://example.com?token=secret)`
assert.Equal(`(https://example.com?token=***)`, sanitizeSensitiveInfo(input))
}

func TestSanitizeSensitiveInfoRedactsEncodedSensitiveQueryKeys(t *testing.T) {
assert := assert.New(t)

input := `https://example.com?%70%61%73%73%77%6f%72%64=secret&username=demo`
assert.Equal(`https://example.com?%70%61%73%73%77%6f%72%64=***&username=***`, sanitizeSensitiveInfo(input))
}
106 changes: 106 additions & 0 deletions pkg/tool/errors/sanitize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package errors

import (
"net/url"
"regexp"
"strings"
)

var (
urlCandidatePattern = regexp.MustCompile(`https?://[^\s",<>()\[\]{}]+`)
authorizationHeaderPattern = regexp.MustCompile(`(?i)(authorization[:=]\s*(?:basic|bearer)\s+)[^,\s"]+`)
sensitiveQueryKeys = map[string]struct{}{
"username": {},
"user_name": {},
"password": {},
"passwd": {},
"pwd": {},
"token": {},
"access_token": {},
"refresh_token": {},
"access_key": {},
"access_key_id": {},
"access_key_secret": {},
"secret": {},
"client_secret": {},
"private_access_token": {},
}
)

func sanitizeSensitiveInfo(text string) string {
if text == "" {
return text
}

text = sanitizeURLsInText(text)
text = authorizationHeaderPattern.ReplaceAllString(text, `${1}***`)

return text
}

func sanitizeURLsInText(text string) string {
return urlCandidatePattern.ReplaceAllStringFunc(text, sanitizeURLString)
}

func sanitizeURLString(raw string) string {
raw = sanitizeURLUserInfo(raw)
raw = sanitizeURLQuery(raw)
return raw
}

func sanitizeURLUserInfo(raw string) string {
parsed, err := url.Parse(raw)
if err != nil || parsed.User == nil {
return raw
}

schemeIdx := strings.Index(raw, "://")
if schemeIdx < 0 {
return raw
}

userInfoStart := schemeIdx + 3
atOffset := strings.Index(raw[userInfoStart:], "@")
if atOffset < 0 {
return raw
}

replacement := "***"
if _, hasPassword := parsed.User.Password(); hasPassword {
replacement = "***:***"
}

userInfoEnd := userInfoStart + atOffset
return raw[:userInfoStart] + replacement + raw[userInfoEnd:]
}

func sanitizeURLQuery(raw string) string {
queryStart := strings.Index(raw, "?")
if queryStart < 0 {
return raw
}

queryEnd := len(raw)
if fragmentStart := strings.Index(raw[queryStart+1:], "#"); fragmentStart >= 0 {
queryEnd = queryStart + 1 + fragmentStart
}

queryParts := strings.Split(raw[queryStart+1:queryEnd], "&")
for i, part := range queryParts {
key, _, found := strings.Cut(part, "=")
decodedKey, err := url.QueryUnescape(key)
if err != nil {
decodedKey = key
}
if _, ok := sensitiveQueryKeys[strings.ToLower(decodedKey)]; !ok {
continue
}
if found {
queryParts[i] = key + "=***"
} else {
queryParts[i] = key
}
}

return raw[:queryStart+1] + strings.Join(queryParts, "&") + raw[queryEnd:]
}
101 changes: 101 additions & 0 deletions pkg/tool/nacos/error_humanizer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package nacos

import (
"fmt"
"net/url"
"regexp"
"strings"
)

var authStatusPattern = regexp.MustCompile(`(^|[^0-9])(401|403)([^0-9]|$)`)

type HumanizedError struct {
message string
cause error
}

func (e *HumanizedError) Error() string {
return e.message
}

func (e *HumanizedError) Unwrap() error {
return e.cause
}

func (e *HumanizedError) Cause() error {
return e.cause
}

func humanizeNacosError(operation, serverAddr string, err error) error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

日志里最好打印出来真实报错,否则debug难度增大

if err == nil {
return nil
}

raw := strings.ToLower(err.Error())
addr := displayNacosAddress(serverAddr)
message := fmt.Sprintf("%s失败:请检查 Nacos 地址、账号密码和服务状态", operation)

switch {
case strings.Contains(raw, "parse nacos server address failed"),
strings.Contains(raw, "missing protocol scheme"),
strings.Contains(raw, "invalid uri"):
message = fmt.Sprintf("%s失败:Nacos 地址格式不正确,请检查地址配置", operation)
case strings.Contains(raw, "unmarshal nacos"),
strings.Contains(raw, "unmarshal task error"),
strings.Contains(raw, "cannot unmarshal"),
strings.Contains(raw, "invalid character"):
message = fmt.Sprintf("%s失败:Nacos 返回的数据格式异常,请检查服务版本或响应内容", operation)
case strings.Contains(raw, "no such host"):
message = fmt.Sprintf("%s失败:无法解析 Nacos 地址 %s,请检查地址是否填写正确", operation, addr)
case strings.Contains(raw, "certificate signed by unknown authority"):
message = fmt.Sprintf("%s失败:HTTPS 证书校验失败,请检查 Nacos 服务证书是否受信任", operation)
case strings.Contains(raw, "x509:"):
message = fmt.Sprintf("%s失败:HTTPS 证书校验失败,请检查 Nacos 服务证书配置是否正确", operation)
case strings.Contains(raw, "connection refused"):
message = fmt.Sprintf("%s失败:连接被拒绝,请检查服务地址、端口或 Nacos 服务状态", operation)
case strings.Contains(raw, "i/o timeout"),
strings.Contains(raw, "context deadline exceeded"),
strings.Contains(raw, "client.timeout exceeded"):
message = fmt.Sprintf("%s失败:连接超时,请检查网络连通性或 Nacos 服务状态", operation)
case containsNacosAuthError(raw):
message = fmt.Sprintf("%s失败:用户名或密码错误,或当前账号无权限访问 Nacos", operation)
}

return &HumanizedError{
message: message,
cause: err,
}
}

func containsNacosAuthError(raw string) bool {
if authStatusPattern.MatchString(raw) {
return true
}

for _, keyword := range []string{
"unauthorized",
"forbidden",
"unknown user",
"user not found",
"invalid password",
"password error",
"access denied",
"permission denied",
"login failed",
} {
if strings.Contains(raw, keyword) {
return true
}
}

return false
}

func displayNacosAddress(serverAddr string) string {
parsed, err := url.Parse(serverAddr)
if err == nil && parsed.Host != "" {
return parsed.Host
}

return serverAddr
}
Loading
Loading