talos

Форк
0
215 строк · 5.4 Кб
1
// This Source Code Form is subject to the terms of the Mozilla Public
2
// License, v. 2.0. If a copy of the MPL was not distributed with this
3
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4

5
// Package provider provides TLS config for client & server.
6
package provider
7

8
import (
9
	"bytes"
10
	"context"
11
	stdlibtls "crypto/tls"
12
	stdx509 "crypto/x509"
13
	"fmt"
14
	"sync"
15

16
	"github.com/cosi-project/runtime/pkg/resource"
17
	"github.com/cosi-project/runtime/pkg/state"
18
	"github.com/siderolabs/crypto/tls"
19
	"github.com/siderolabs/crypto/x509"
20
	"github.com/siderolabs/gen/xslices"
21

22
	"github.com/siderolabs/talos/pkg/machinery/resources/secrets"
23
)
24

25
// TLSConfig provides client & server TLS configs for apid.
26
type TLSConfig struct {
27
	certificateProvider *certificateProvider
28
	watchCh             <-chan state.Event
29
}
30

31
// NewTLSConfig builds provider from configuration and endpoints.
32
func NewTLSConfig(ctx context.Context, resources state.State) (*TLSConfig, error) {
33
	watchCh := make(chan state.Event)
34

35
	if err := resources.Watch(ctx, resource.NewMetadata(secrets.NamespaceName, secrets.APIType, secrets.APIID, resource.VersionUndefined), watchCh); err != nil {
36
		return nil, fmt.Errorf("error setting up watch: %w", err)
37
	}
38

39
	// wait for the first event to set up certificate provider
40
	provider := &certificateProvider{}
41

42
	for {
43
		var event state.Event
44

45
		select {
46
		case <-ctx.Done():
47
			return nil, ctx.Err()
48
		case event = <-watchCh:
49
		}
50

51
		switch event.Type {
52
		case state.Created, state.Updated:
53
			// expected
54
		case state.Destroyed, state.Bootstrapped:
55
			// ignore, we'll get another event
56
			continue
57
		case state.Errored:
58
			return nil, fmt.Errorf("error watching for API certificates: %w", event.Error)
59
		}
60

61
		apiCerts := event.Resource.(*secrets.API) //nolint:errcheck,forcetypeassert
62

63
		if err := provider.Update(apiCerts); err != nil {
64
			return nil, err
65
		}
66

67
		return &TLSConfig{
68
			certificateProvider: provider,
69
			watchCh:             watchCh,
70
		}, nil
71
	}
72
}
73

74
// Watch for changes in API certificates and updates the TLSConfig.
75
func (tlsConfig *TLSConfig) Watch(ctx context.Context, onUpdate func()) error {
76
	for {
77
		var event state.Event
78

79
		select {
80
		case <-ctx.Done():
81
			return nil
82
		case event = <-tlsConfig.watchCh:
83
		}
84

85
		switch event.Type {
86
		case state.Created, state.Updated:
87
			// expected
88
		case state.Destroyed, state.Bootstrapped:
89
			// ignore, we'll get another event
90
			continue
91
		case state.Errored:
92
			return fmt.Errorf("error watching API certificates: %w", event.Error)
93
		}
94

95
		apiCerts := event.Resource.(*secrets.API) //nolint:errcheck,forcetypeassert
96

97
		if err := tlsConfig.certificateProvider.Update(apiCerts); err != nil {
98
			return fmt.Errorf("failed updating cert: %v", err)
99
		}
100

101
		if onUpdate != nil {
102
			onUpdate()
103
		}
104
	}
105
}
106

107
// ServerConfig generates server-side tls.Config.
108
func (tlsConfig *TLSConfig) ServerConfig() (*stdlibtls.Config, error) {
109
	return tls.New(
110
		tls.WithClientAuthType(tls.Mutual),
111
		tls.WithDynamicClientCA(tlsConfig.certificateProvider),
112
		tls.WithServerCertificateProvider(tlsConfig.certificateProvider),
113
	)
114
}
115

116
// ClientConfig generates client-side tls.Config.
117
func (tlsConfig *TLSConfig) ClientConfig() (*stdlibtls.Config, error) {
118
	if !tlsConfig.certificateProvider.HasClientCertificate() {
119
		return nil, nil
120
	}
121

122
	ca, err := tlsConfig.certificateProvider.GetCA()
123
	if err != nil {
124
		return nil, fmt.Errorf("failed to get root CA: %w", err)
125
	}
126

127
	return tls.New(
128
		tls.WithClientAuthType(tls.Mutual),
129
		tls.WithCACertPEM(ca),
130
		tls.WithClientCertificateProvider(tlsConfig.certificateProvider),
131
	)
132
}
133

134
type certificateProvider struct {
135
	mu sync.Mutex
136

137
	ca                     []byte
138
	caCertPool             *stdx509.CertPool
139
	clientCert, serverCert *stdlibtls.Certificate
140
}
141

142
func (p *certificateProvider) Update(apiCerts *secrets.API) error {
143
	p.mu.Lock()
144
	defer p.mu.Unlock()
145

146
	serverCert, err := stdlibtls.X509KeyPair(apiCerts.TypedSpec().Server.Crt, apiCerts.TypedSpec().Server.Key)
147
	if err != nil {
148
		return fmt.Errorf("failed to parse server cert and key into a TLS Certificate: %w", err)
149
	}
150

151
	p.serverCert = &serverCert
152

153
	p.ca = bytes.Join(
154
		xslices.Map(
155
			apiCerts.TypedSpec().AcceptedCAs,
156
			func(cert *x509.PEMEncodedCertificate) []byte {
157
				return cert.Crt
158
			},
159
		),
160
		nil,
161
	)
162

163
	p.caCertPool = stdx509.NewCertPool()
164
	if !p.caCertPool.AppendCertsFromPEM(p.ca) {
165
		return fmt.Errorf("failed to parse CA certs into a CertPool")
166
	}
167

168
	if apiCerts.TypedSpec().Client != nil {
169
		clientCert, err := stdlibtls.X509KeyPair(apiCerts.TypedSpec().Client.Crt, apiCerts.TypedSpec().Client.Key)
170
		if err != nil {
171
			return fmt.Errorf("failed to parse client cert and key into a TLS Certificate: %w", err)
172
		}
173

174
		p.clientCert = &clientCert
175
	} else {
176
		p.clientCert = nil
177
	}
178

179
	return nil
180
}
181

182
func (p *certificateProvider) GetCA() ([]byte, error) {
183
	p.mu.Lock()
184
	defer p.mu.Unlock()
185

186
	return p.ca, nil
187
}
188

189
func (p *certificateProvider) GetCACertPool() (*stdx509.CertPool, error) {
190
	p.mu.Lock()
191
	defer p.mu.Unlock()
192

193
	return p.caCertPool, nil
194
}
195

196
func (p *certificateProvider) GetCertificate(h *stdlibtls.ClientHelloInfo) (*stdlibtls.Certificate, error) {
197
	p.mu.Lock()
198
	defer p.mu.Unlock()
199

200
	return p.serverCert, nil
201
}
202

203
func (p *certificateProvider) HasClientCertificate() bool {
204
	p.mu.Lock()
205
	defer p.mu.Unlock()
206

207
	return p.clientCert != nil
208
}
209

210
func (p *certificateProvider) GetClientCertificate(*stdlibtls.CertificateRequestInfo) (*stdlibtls.Certificate, error) {
211
	p.mu.Lock()
212
	defer p.mu.Unlock()
213

214
	return p.clientCert, nil
215
}
216

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.