认证方式

证书认证

Posted by Boenn on August 11, 2021

证书认证

使用k8s进行操作时,最常用的认证方式是证书认证token认证,其中证书认证主要依赖于 go 中 x509 包内的相关验证方法。这里分析证书认证。后续会增加token认证的分析

证书

  • 证书池 CertPool:证书的集合。其中 byName 的 key 是 RawSubject ,也就是对DER编码证书的 subject 字段进行加密;value 是 lazyCerts 的索引。
  type CertPool struct {
  	byName map[string][]int 
  
  	// lazyCerts contains funcs that return a certificate,
  	// lazily parsing/decompressing it as needed.
    // 对证书进行解析操作
  	lazyCerts []lazyCert
  
  	// haveSum maps from sum224(cert.Raw) to true. It's used only
  	// for AddCert duplicate detection, to avoid CertPool.contains
  	// calls in the AddCert path (because the contains method can
  	// call getCert and otherwise negate savings from lazy getCert
  	// funcs).
    // 只用于 AddCert 重复检测
  	haveSum map[sum224]bool
  }
  • lazyCert 是关于 Cert 和 func 的最小数据。
type lazyCert struct {
	// rawSubject is the Certificate.RawSubject value.
	// It's the same as the CertPool.byName key, but in []byte
	// form to make CertPool.Subjects (as used by crypto/tls) do
	// fewer allocations.
  // 使 CertPool.Subjects(由 crypto/tls 使用)做更少的分配。
	rawSubject []byte

	// getCert returns the certificate.
	//
	// It is not meant to do network operations or anything else
	// where a failure is likely; the func is meant to lazily
	// parse/decompress data that is already known to be good. The
	// error in the signature primarily is meant for use in the
	// case where a cert file existed on local disk when the program
	// started up is deleted later before it's read.
  // 这里的 func 意在延迟解析/解压缩已知良好的数据
	getCert func() (*Certificate, error)
}

主要结构

// Authenticator 从经过验证的客户端证书中提取用户信息
type Authenticator struct {
   verifyOptionsFn VerifyOptionFunc
   user            UserConversion
}

// 给 Authenticator 提供了一个 VerifyOptions 的浅拷贝
type VerifyOptionFunc func() (x509.VerifyOptions, bool)

// 从客户端的证书链中提取用户信息
type UserConversion interface {
	 User(chain []*x509.Certificate) (*authenticator.Response, bool, error)
}

认证逻辑

创建

New 返回一个 request.Authenticator,它使用传入的 x509.VerifyOptions 验证客户端证书,并使用提供的 UserConversion 将有效的证书链转换为 user.Info

func New(opts x509.VerifyOptions, user UserConversion) *Authenticator {
   return NewDynamic(StaticVerifierFn(opts), user)
}

// 对于VerifyOptions中无法更改的选项进行浅拷贝并直接返回,
func StaticVerifierFn(opts x509.VerifyOptions) VerifyOptionFunc {
	 return func() (x509.VerifyOptions, bool) {
		  return opts, true
	 }
}

func NewDynamic(verifyOptionsFn VerifyOptionFunc, user UserConversion) *Authenticator {
	 return &Authenticator{verifyOptionsFn, user}
}

验证

对请求的身份进行验证。

PeerCertificates 是 peer 发送的解析证书。 第一个元素是验证连接所依据的 leaf certificate。对于客户端来说,它不能为空。 在服务器端,如果 Config.ClientAuth 不是 RequireAnyClientCertRequireAndVerifyClientCert,它可以为空。

func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
   if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
      return nil, false, nil
   }

   // Use intermediates, if provided
   // 获取浅拷贝
   optsCopy, ok := a.verifyOptionsFn()
   // if there are intentionally no verify options, then we cannot authenticate this request
   if !ok {
      return nil, false, nil
   }
   // 这里的Intermediates是验证x509时的字段,属于证书的概念,一般来说颁发证书时,是一条证书链,其中包括root cert-intermediate cert-end user cert。intermediate 用于保证 end user 证书未被篡改。
   // 如果 Intermediates 为空,但存在 PeerCertificates,则新建一个 CertPool ,并将每一个 PeerCertificates 添加进去
   if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 {
      optsCopy.Intermediates = x509.NewCertPool()
      for _, intermediate := range req.TLS.PeerCertificates[1:] {
         optsCopy.Intermediates.AddCert(intermediate)
      }
   }

   // 更新监控的 Histogram 指标
   remaining := req.TLS.PeerCertificates[0].NotAfter.Sub(time.Now())
   clientCertificateExpirationHistogram.WithContext(req.Context()).Observe(remaining.Seconds())
   // 进行验证,这里的验证在go源码中。
   // 主要是验证签发者和DNS是否合理
   chains, err := req.TLS.PeerCertificates[0].Verify(optsCopy)
   if err != nil {
      return nil, false, fmt.Errorf(
         "verifying certificate %s failed: %w",
         certificateIdentifier(req.TLS.PeerCertificates[0]),
         err,
      )
   }

   var errlist []error
   for _, chain := range chains {
      user, ok, err := a.user.User(chain)
      if err != nil {
         errlist = append(errlist, err)
         continue
      }

      if ok {
         return user, ok, err
      }
   }
   return nil, false, utilerrors.NewAggregate(errlist)
}

Verifier

Verifier 通过验证客户端证书,实现 request.Authenticator,然后委托给封装的 auth

type Verifier struct {
   verifyOptionsFn VerifyOptionFunc
   auth            authenticator.Request

   // allowedCommonNames contains the common names which a verified certificate is allowed to have.
   // If empty, all verified certificates are allowed.
   // 为空,表示允许所有经过验证的证书。
   allowedCommonNames StringSliceProvider
}

// 从请求中提取身份验证信息
type Request interface {
	AuthenticateRequest(req *http.Request) (*Response, bool, error)
}

type StringSliceProvider interface {
	// Value returns the current string slice.  Callers should never mutate the returned value.
	Value() []string
}

VerifierAuthenticateRequest 与上述 Authenticator 的逻辑类似。只是最后用户可以自己写验证方式进行验证。

func (a *Verifier) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
   if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
      return nil, false, nil
   }

   // Use intermediates, if provided
   optsCopy, ok := a.verifyOptionsFn()
   // if there are intentionally no verify options, then we cannot authenticate this request
   if !ok {
      return nil, false, nil
   }
   if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 {
      optsCopy.Intermediates = x509.NewCertPool()
      for _, intermediate := range req.TLS.PeerCertificates[1:] {
         optsCopy.Intermediates.AddCert(intermediate)
      }
   }

   if _, err := req.TLS.PeerCertificates[0].Verify(optsCopy); err != nil {
      return nil, false, err
   }
   if err := a.verifySubject(req.TLS.PeerCertificates[0].Subject); err != nil {
      return nil, false, err
   }
   // 可以自定义 auth 的 func 进行验证
   return a.auth.AuthenticateRequest(req)
}

附录

参考代码路径:

kubernetes:staging/src/k8s.io/apiserver/pkg/authentication/request/x509

go:src/crypto/x509 #Verify