forked from k3s-io/k3s
/
cert.go
213 lines (184 loc) · 6.52 KB
/
cert.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
package server
import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"io/fs"
"net/http"
"os"
"reflect"
"strconv"
"strings"
"github.com/k3s-io/k3s/pkg/bootstrap"
"github.com/k3s-io/k3s/pkg/cluster"
"github.com/k3s-io/k3s/pkg/daemons/config"
"github.com/k3s-io/k3s/pkg/daemons/control/deps"
"github.com/k3s-io/k3s/pkg/version"
"github.com/pkg/errors"
certutil "github.com/rancher/dynamiclistener/cert"
"github.com/rancher/wrangler/pkg/merr"
"github.com/sirupsen/logrus"
"k8s.io/client-go/util/keyutil"
)
func caCertReplaceHandler(server *config.Control) http.HandlerFunc {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
if req.TLS == nil || req.Method != http.MethodPut {
resp.WriteHeader(http.StatusNotFound)
return
}
force, _ := strconv.ParseBool(req.FormValue("force"))
if err := caCertReplace(server, req.Body, force); err != nil {
genErrorMessage(resp, http.StatusInternalServerError, err, "certificate")
return
}
logrus.Infof("certificate: Cluster Certificate Authority data has been updated, %s must be restarted.", version.Program)
resp.WriteHeader(http.StatusNoContent)
})
}
// caCertReplace stores new CA Certificate data from the client. The data is temporarily written out to disk,
// validated to confirm that the new certs share a common root with the existing certs, and if so are saved to
// the datastore. If the functions succeeds, servers should be restarted immediately to load the new certs
// from the bootstrap data.
func caCertReplace(server *config.Control, buf io.ReadCloser, force bool) error {
tmpdir, err := os.MkdirTemp("", "cacerts")
if err != nil {
return err
}
defer os.RemoveAll(tmpdir)
runtime := config.NewRuntime(nil)
runtime.EtcdConfig = server.Runtime.EtcdConfig
runtime.ServerToken = server.Runtime.ServerToken
tmpServer := &config.Control{
Runtime: runtime,
Token: server.Token,
DataDir: tmpdir,
}
deps.CreateRuntimeCertFiles(tmpServer)
bootstrapData := bootstrap.PathsDataformat{}
if err := json.NewDecoder(buf).Decode(&bootstrapData); err != nil {
return err
}
if err := bootstrap.WriteToDiskFromStorage(bootstrapData, &tmpServer.Runtime.ControlRuntimeBootstrap); err != nil {
return err
}
if err := validateBootstrap(server, tmpServer); err != nil {
if !force {
return errors.Wrap(err, "failed to validate new CA certificates and keys")
}
logrus.Warnf("Save of CA certificates and keys forced, ignoring validation errors: %v", err)
}
return cluster.Save(context.TODO(), tmpServer, true)
}
// validateBootstrap checks the new certs and keys to ensure that the cluster would function properly were they to be used.
// - The new leaf CA certificates must be verifiable using the same root and intermediate certs as the current leaf CA certificates.
// - The new service account signing key bundle must include the currently active signing key.
func validateBootstrap(oldServer, newServer *config.Control) error {
errs := []error{}
// Use reflection to iterate over all of the bootstrap fields, checking files at each of the new paths.
oldMeta := reflect.ValueOf(&oldServer.Runtime.ControlRuntimeBootstrap).Elem()
newMeta := reflect.ValueOf(&newServer.Runtime.ControlRuntimeBootstrap).Elem()
for _, field := range reflect.VisibleFields(oldMeta.Type()) {
oldVal := oldMeta.FieldByName(field.Name)
newVal := newMeta.FieldByName(field.Name)
info, err := os.Stat(newVal.String())
if err != nil && !errors.Is(err, fs.ErrNotExist) {
errs = append(errs, errors.Wrap(err, field.Name))
continue
}
if info == nil || info.Size() == 0 {
if newVal.CanSet() {
logrus.Infof("certificate: %s not provided; using current value", field.Name)
newVal.Set(oldVal)
} else {
errs = append(errs, fmt.Errorf("cannot use current data for %s; field is not settable", field.Name))
}
}
// Check CA chain consistency and cert/key agreement
if strings.HasSuffix(field.Name, "CA") {
if err := validateCA(oldVal.String(), newVal.String()); err != nil {
errs = append(errs, errors.Wrap(err, field.Name))
}
newKeyVal := newMeta.FieldByName(field.Name + "Key")
if err := validateCAKey(newVal.String(), newKeyVal.String()); err != nil {
errs = append(errs, errors.Wrap(err, field.Name+"Key"))
}
}
// Check signing key rotation
if field.Name == "ServiceKey" {
if err := validateServiceKey(oldVal.String(), newVal.String()); err != nil {
errs = append(errs, errors.Wrap(err, field.Name))
}
}
}
if len(errs) > 0 {
return merr.NewErrors(errs...)
}
return nil
}
func validateCA(oldCAPath, newCAPath string) error {
oldCerts, err := certutil.CertsFromFile(oldCAPath)
if err != nil {
return err
}
newCerts, err := certutil.CertsFromFile(newCAPath)
if err != nil {
return err
}
if len(newCerts) == 1 {
return errors.New("new CA is self-signed")
}
roots := x509.NewCertPool()
intermediates := x509.NewCertPool()
// Load all certs from the old bundle
for _, cert := range oldCerts {
if len(cert.AuthorityKeyId) == 0 || bytes.Equal(cert.AuthorityKeyId, cert.SubjectKeyId) {
roots.AddCert(cert)
} else {
intermediates.AddCert(cert)
}
}
// Include any intermediates from the new bundle, in case they're cross-signed by a cert in the old bundle
for i, cert := range newCerts {
if i > 0 {
if len(cert.AuthorityKeyId) > 0 {
intermediates.AddCert(cert)
}
}
}
// Verify the first cert in the bundle, using the combined roots and intermediates
_, err = newCerts[0].Verify(x509.VerifyOptions{Roots: roots, Intermediates: intermediates})
if err != nil {
err = errors.Wrap(err, "new CA cert cannot be verified using old CA chain")
}
return err
}
// validateCAKey confirms that the private key is valid for the certificate
func validateCAKey(newCAPath, newCAKeyPath string) error {
_, err := tls.LoadX509KeyPair(newCAPath, newCAKeyPath)
if err != nil {
err = errors.Wrap(err, "new CA cert and key cannot be loaded as X590KeyPair")
}
return err
}
// validateServiceKey ensures that the first key from the old serviceaccount signing key list
// is also present in the new key list, to ensure that old signatures can still be validated.
func validateServiceKey(oldKeyPath, newKeyPath string) error {
oldKeys, err := keyutil.PublicKeysFromFile(oldKeyPath)
if err != nil {
return err
}
newKeys, err := keyutil.PublicKeysFromFile(newKeyPath)
if err != nil {
return err
}
for _, key := range newKeys {
if reflect.DeepEqual(oldKeys[0], key) {
return nil
}
}
return errors.New("old ServiceAccount signing key not in new ServiceAccount key list")
}