blob: 708a89e9eace60650b198a6e25e70fc9aa394813 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
2Copyright 2014 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package x509
18
19import (
20 "crypto/x509"
21 "crypto/x509/pkix"
22 "encoding/asn1"
23 "fmt"
24 "net/http"
25 "time"
26
27 "github.com/golang/glog"
28 "github.com/prometheus/client_golang/prometheus"
29
30 utilerrors "k8s.io/apimachinery/pkg/util/errors"
31 "k8s.io/apimachinery/pkg/util/sets"
32 "k8s.io/apiserver/pkg/authentication/authenticator"
33 "k8s.io/apiserver/pkg/authentication/user"
34)
35
36var clientCertificateExpirationHistogram = prometheus.NewHistogram(
37 prometheus.HistogramOpts{
38 Namespace: "apiserver",
39 Subsystem: "client",
40 Name: "certificate_expiration_seconds",
41 Help: "Distribution of the remaining lifetime on the certificate used to authenticate a request.",
42 Buckets: []float64{
43 0,
44 (6 * time.Hour).Seconds(),
45 (12 * time.Hour).Seconds(),
46 (24 * time.Hour).Seconds(),
47 (2 * 24 * time.Hour).Seconds(),
48 (4 * 24 * time.Hour).Seconds(),
49 (7 * 24 * time.Hour).Seconds(),
50 (30 * 24 * time.Hour).Seconds(),
51 (3 * 30 * 24 * time.Hour).Seconds(),
52 (6 * 30 * 24 * time.Hour).Seconds(),
53 (12 * 30 * 24 * time.Hour).Seconds(),
54 },
55 },
56)
57
58func init() {
59 prometheus.MustRegister(clientCertificateExpirationHistogram)
60}
61
62// UserConversion defines an interface for extracting user info from a client certificate chain
63type UserConversion interface {
64 User(chain []*x509.Certificate) (user.Info, bool, error)
65}
66
67// UserConversionFunc is a function that implements the UserConversion interface.
68type UserConversionFunc func(chain []*x509.Certificate) (user.Info, bool, error)
69
70// User implements x509.UserConversion
71func (f UserConversionFunc) User(chain []*x509.Certificate) (user.Info, bool, error) {
72 return f(chain)
73}
74
75// Authenticator implements request.Authenticator by extracting user info from verified client certificates
76type Authenticator struct {
77 opts x509.VerifyOptions
78 user UserConversion
79}
80
81// New returns a request.Authenticator that verifies client certificates using the provided
82// VerifyOptions, and converts valid certificate chains into user.Info using the provided UserConversion
83func New(opts x509.VerifyOptions, user UserConversion) *Authenticator {
84 return &Authenticator{opts, user}
85}
86
87// AuthenticateRequest authenticates the request using presented client certificates
88func (a *Authenticator) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
89 if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
90 return nil, false, nil
91 }
92
93 // Use intermediates, if provided
94 optsCopy := a.opts
95 if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 {
96 optsCopy.Intermediates = x509.NewCertPool()
97 for _, intermediate := range req.TLS.PeerCertificates[1:] {
98 optsCopy.Intermediates.AddCert(intermediate)
99 }
100 }
101
102 remaining := req.TLS.PeerCertificates[0].NotAfter.Sub(time.Now())
103 clientCertificateExpirationHistogram.Observe(remaining.Seconds())
104 chains, err := req.TLS.PeerCertificates[0].Verify(optsCopy)
105 if err != nil {
106 return nil, false, err
107 }
108
109 var errlist []error
110 for _, chain := range chains {
111 user, ok, err := a.user.User(chain)
112 if err != nil {
113 errlist = append(errlist, err)
114 continue
115 }
116
117 if ok {
118 return user, ok, err
119 }
120 }
121 return nil, false, utilerrors.NewAggregate(errlist)
122}
123
124// Verifier implements request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth
125type Verifier struct {
126 opts x509.VerifyOptions
127 auth authenticator.Request
128
129 // allowedCommonNames contains the common names which a verified certificate is allowed to have.
130 // If empty, all verified certificates are allowed.
131 allowedCommonNames sets.String
132}
133
134// NewVerifier create a request.Authenticator by verifying a client cert on the request, then delegating to the wrapped auth
135func NewVerifier(opts x509.VerifyOptions, auth authenticator.Request, allowedCommonNames sets.String) authenticator.Request {
136 return &Verifier{opts, auth, allowedCommonNames}
137}
138
139// AuthenticateRequest verifies the presented client certificate, then delegates to the wrapped auth
140func (a *Verifier) AuthenticateRequest(req *http.Request) (user.Info, bool, error) {
141 if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
142 return nil, false, nil
143 }
144
145 // Use intermediates, if provided
146 optsCopy := a.opts
147 if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 {
148 optsCopy.Intermediates = x509.NewCertPool()
149 for _, intermediate := range req.TLS.PeerCertificates[1:] {
150 optsCopy.Intermediates.AddCert(intermediate)
151 }
152 }
153
154 if _, err := req.TLS.PeerCertificates[0].Verify(optsCopy); err != nil {
155 return nil, false, err
156 }
157 if err := a.verifySubject(req.TLS.PeerCertificates[0].Subject); err != nil {
158 return nil, false, err
159 }
160 return a.auth.AuthenticateRequest(req)
161}
162
163func (a *Verifier) verifySubject(subject pkix.Name) error {
164 // No CN restrictions
165 if len(a.allowedCommonNames) == 0 {
166 return nil
167 }
168 // Enforce CN restrictions
169 if a.allowedCommonNames.Has(subject.CommonName) {
170 return nil
171 }
172 glog.Warningf("x509: subject with cn=%s is not in the allowed list: %v", subject.CommonName, a.allowedCommonNames.List())
173 return fmt.Errorf("x509: subject with cn=%s is not allowed", subject.CommonName)
174}
175
176// DefaultVerifyOptions returns VerifyOptions that use the system root certificates, current time,
177// and requires certificates to be valid for client auth (x509.ExtKeyUsageClientAuth)
178func DefaultVerifyOptions() x509.VerifyOptions {
179 return x509.VerifyOptions{
180 KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
181 }
182}
183
184// CommonNameUserConversion builds user info from a certificate chain using the subject's CommonName
185var CommonNameUserConversion = UserConversionFunc(func(chain []*x509.Certificate) (user.Info, bool, error) {
186 if len(chain[0].Subject.CommonName) == 0 {
187 return nil, false, nil
188 }
189 return &user.DefaultInfo{
190 Name: chain[0].Subject.CommonName,
191 Groups: chain[0].Subject.Organization,
192 }, true, nil
193})
194
195// DNSNameUserConversion builds user info from a certificate chain using the first DNSName on the certificate
196var DNSNameUserConversion = UserConversionFunc(func(chain []*x509.Certificate) (user.Info, bool, error) {
197 if len(chain[0].DNSNames) == 0 {
198 return nil, false, nil
199 }
200 return &user.DefaultInfo{Name: chain[0].DNSNames[0]}, true, nil
201})
202
203// EmailAddressUserConversion builds user info from a certificate chain using the first EmailAddress on the certificate
204var EmailAddressUserConversion = UserConversionFunc(func(chain []*x509.Certificate) (user.Info, bool, error) {
205 var emailAddressOID asn1.ObjectIdentifier = []int{1, 2, 840, 113549, 1, 9, 1}
206 if len(chain[0].EmailAddresses) == 0 {
207 for _, name := range chain[0].Subject.Names {
208 if name.Type.Equal(emailAddressOID) {
209 return &user.DefaultInfo{Name: name.Value.(string)}, true, nil
210 }
211 }
212 return nil, false, nil
213 }
214 return &user.DefaultInfo{Name: chain[0].EmailAddresses[0]}, true, nil
215})