证书认证
使用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 不是 RequireAnyClientCert 或 RequireAndVerifyClientCert,它可以为空。
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
}
Verifier 的 AuthenticateRequest 与上述 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