sentry库的identify实现
identify
1 - identify.go
identify结构体定义
结构体定义
// Bundle 包含了足以以识别一个跨信任域和命名空间的工作负载的所有的元素:
type Bundle struct {
ID string
Namespace string
TrustDomain string
}
其实就三个元素: ID / Namespace 以及 TrustDomain
NewBundle() 方法
NewBundle() 方法返回一个新的 identity Bundle。
func NewBundle(id, namespace, trustDomain string) *Bundle {
// Empty namespace and trust domain result in an empty bundle
// 如果 namespace 或者 trust domain 为空,则返回空的 bundle(nil)
if namespace == "" || trustDomain == "" {
return nil
}
// 否则指示简单的赋值三个属性
return &Bundle{
ID: id,
Namespace: namespace,
TrustDomain: trustDomain,
}
}
namespace和trustDomain是可选参数。当为空时,将返回一个 nil 值。
2 - validator.go
validator 接口定义
接口定义
Validator 通过使用 ID 和 token 来验证证书请求的身份
type Validator interface {
Validate(id, token, namespace string) error
}
3 - spiff.go
创建 spiff ID
CreateSPIFFEID 方法
CreateSPIFFEID() 方法从给定的 trustDomain, namespace, appID 创建符合 SPIFFE 标准的唯一ID:
func CreateSPIFFEID(trustDomain, namespace, appID string) (string, error) {
// trustDomain, namespace, appID 三者都不能为空
if trustDomain == "" {
return "", errors.New("can't create spiffe id: trust domain is empty")
}
if namespace == "" {
return "", errors.New("can't create spiffe id: namespace is empty")
}
if appID == "" {
return "", errors.New("can't create spiffe id: app id is empty")
}
// 根据 SPIFFE 规范进行验证
// Validate according to the SPIFFE spec
if strings.ContainsRune(trustDomain, ':') {
// trustDomain不能带":"
return "", errors.New("trust domain cannot contain the ':' character")
}
// trustDomain 的长度不能大于255个 byte
if len([]byte(trustDomain)) > 255 {
return "", errors.New("trust domain cannot exceed 255 bytes")
}
// 拼接出 SPIFFE ID
id := fmt.Sprintf("spiffe://%s/ns/%s/%s", trustDomain, namespace, appID)
if len([]byte(id)) > 2048 {
// 验证 SPIFFE ID 长度不大于 2048
return "", errors.New("spiffe id cannot exceed 2048 bytes")
}
return id, nil
}
4 - kubernetes下的validator.go
kubernetes下的validator实现
准备工作
结构体定义
validator 结构体定义:
type validator struct {
client k8s.Interface
auth kauth.AuthenticationV1Interface
audiences []string
}
创建validator的方法
NewValidator() 方法创建新的 validator 结构体:
func NewValidator(client k8s.Interface, audiences []string) identity.Validator {
return &validator{
client: client,
auth: client.AuthenticationV1(),
audiences: audiences,
}
}
实现
Validate() 实现通过使用 ID 和 token 来验证证书请求的身份:
func (v *validator) Validate(id, token, namespace string) error {
// id 和 token 不能为空
if id == "" {
return fmt.Errorf("%s: id field in request must not be empty", errPrefix)
}
if token == "" {
return fmt.Errorf("%s: token field in request must not be empty", errPrefix)
}
// TODO: Remove in Dapr 1.12 to enforce setting an explicit audience
var canTryWithNilAudience, showDefaultTokenAudienceWarning bool
audiences := v.audiences
if len(audiences) == 0 {
// 处理用户没有显式设置 audience 的特殊情况
// 此时采用默认是 sentryConsts.ServiceAccountTokenAudience "dapr.io/sentry"
audiences = []string{sentryConsts.ServiceAccountTokenAudience}
// TODO: Remove in Dapr 1.12 to enforce setting an explicit audience
// Because the user did not specify an explicit audience and is instead relying on the default, if the authentication fails we can retry with nil audience
// 并记录下来这是特殊情况,如果认证失败则应该尝试 audience 为 nil 的情况
canTryWithNilAudience = true
}
tokenReview := &kauthapi.TokenReview{
Spec: kauthapi.TokenReviewSpec{
Token: token,
Audiences: audiences,
},
}
tr: // TODO: Remove in Dapr 1.12 to enforce setting an explicit audience
prts, err := v.executeTokenReview(tokenReview)
if err != nil {
// TODO: Remove in Dapr 1.12 to enforce setting an explicit audience
if canTryWithNilAudience {
// Retry with a nil audience, which means the default audience for the K8s API server
tokenReview.Spec.Audiences = nil
showDefaultTokenAudienceWarning = true
canTryWithNilAudience = false
goto tr
}
return err
}
// TODO: Remove in Dapr 1.12 to enforce setting an explicit audience
if showDefaultTokenAudienceWarning {
log.Warn("WARNING: Sentry accepted a token with the audience for the Kubernetes API server. This is deprecated and only supported to ensure a smooth upgrade from Dapr pre-1.10.")
}
if len(prts) != 4 || prts[0] != "system" {
return fmt.Errorf("%s: provided token is not a properly structured service account token", errPrefix)
}
podSa := prts[3]
podNs := prts[2]
// 检验 namespace
if namespace != "" {
if podNs != namespace {
return fmt.Errorf("%s: namespace mismatch. received namespace: %s", errPrefix, namespace)
}
}
// 检验 id
if id != podNs+":"+podSa {
return fmt.Errorf("%s: token/id mismatch. received id: %s", errPrefix, id)
}
return nil
}
executeTokenReview() 方法执行 tokenReview,如果 token 无效或者失败则返回错误:
func (v *validator) executeTokenReview(tokenReview *kauthapi.TokenReview) ([]string, error) {
review, err := v.auth.TokenReviews().Create(context.TODO(), tokenReview, v1.CreateOptions{})
if err != nil {
return nil, fmt.Errorf("%s: token review failed: %w", errPrefix, err)
}
if review.Status.Error != "" {
return nil, fmt.Errorf("%s: invalid token: %s", errPrefix, review.Status.Error)
}
if !review.Status.Authenticated {
return nil, fmt.Errorf("%s: authentication failed", errPrefix)
}
return strings.Split(review.Status.User.Username, ":"), nil
}
5 - selfhosted下的validator.go
selfhosted下的validator实现
selfhosted 下实际没有做验证:
func NewValidator() identity.Validator {
return &validator{}
}
type validator struct{}
func (v *validator) Validate(id, token, namespace string) error {
// no validation for self hosted.
return nil
}
只是保留了一套代码框架,以满足 Validator 接口的要求。
这意味着在 selfhosted 下是不会进行身份验证的。