Skip to content

Commit 8d6884a

Browse files
committed
test: add a test for inline machine config trusted roots
Run SideroLink API server via TLS with self-signed certificate, inject that certificate into Talos via `talos.config.inline=`. Fix a couple of place where our special TLS root CA provider supporting reloading on the fly was not used. Signed-off-by: Andrey Smirnov <[email protected]>
1 parent d4a6d01 commit 8d6884a

File tree

11 files changed

+171
-20
lines changed

11 files changed

+171
-20
lines changed

.github/workflows/ci.yaml

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
22
#
3-
# Generated on 2024-09-09T13:58:35Z by kres 8be5fa7.
3+
# Generated on 2024-09-12T16:43:46Z by kres 8be5fa7.
44

55
name: default
66
concurrency:
@@ -2385,6 +2385,14 @@ jobs:
23852385
WITH_SIDEROLINK_AGENT: tunnel
23862386
run: |
23872387
sudo -E make e2e-qemu
2388+
- name: e2e-siderolink-tls
2389+
env:
2390+
IMAGE_REGISTRY: registry.dev.siderolabs.io
2391+
SHORT_INTEGRATION_TEST: "yes"
2392+
VIA_MAINTENANCE_MODE: "true"
2393+
WITH_SIDEROLINK_AGENT: wireguard+tls
2394+
run: |
2395+
sudo -E make e2e-qemu
23882396
- name: e2e-apparmor
23892397
env:
23902398
IMAGE_REGISTRY: registry.dev.siderolabs.io

.github/workflows/integration-misc-4-cron.yaml

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
22
#
3-
# Generated on 2024-09-09T13:58:35Z by kres 8be5fa7.
3+
# Generated on 2024-09-12T16:43:46Z by kres 8be5fa7.
44

55
name: integration-misc-4-cron
66
concurrency:
@@ -94,6 +94,14 @@ jobs:
9494
WITH_SIDEROLINK_AGENT: tunnel
9595
run: |
9696
sudo -E make e2e-qemu
97+
- name: e2e-siderolink-tls
98+
env:
99+
IMAGE_REGISTRY: registry.dev.siderolabs.io
100+
SHORT_INTEGRATION_TEST: "yes"
101+
VIA_MAINTENANCE_MODE: "true"
102+
WITH_SIDEROLINK_AGENT: wireguard+tls
103+
run: |
104+
sudo -E make e2e-qemu
97105
- name: e2e-apparmor
98106
env:
99107
IMAGE_REGISTRY: registry.dev.siderolabs.io

.kres.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,14 @@ spec:
926926
WITH_SIDEROLINK_AGENT: tunnel
927927
VIA_MAINTENANCE_MODE: true
928928
IMAGE_REGISTRY: registry.dev.siderolabs.io
929+
- name: e2e-siderolink-tls
930+
command: e2e-qemu
931+
withSudo: true
932+
environment:
933+
SHORT_INTEGRATION_TEST: yes
934+
WITH_SIDEROLINK_AGENT: wireguard+tls
935+
VIA_MAINTENANCE_MODE: true
936+
IMAGE_REGISTRY: registry.dev.siderolabs.io
929937
- name: e2e-apparmor
930938
command: e2e-qemu
931939
withSudo: true

cmd/talosctl/cmd/mgmt/cluster/create.go

+96-13
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
package cluster
66

77
import (
8+
"bytes"
89
"context"
10+
"encoding/base64"
911
"errors"
1012
"fmt"
1113
"math/big"
@@ -23,6 +25,8 @@ import (
2325
"github.com/dustin/go-humanize"
2426
"github.com/google/uuid"
2527
"github.com/hashicorp/go-getter/v2"
28+
"github.com/klauspost/compress/zstd"
29+
"github.com/siderolabs/crypto/x509"
2630
"github.com/siderolabs/gen/maps"
2731
"github.com/siderolabs/go-blockdevice/v2/encryption"
2832
"github.com/siderolabs/go-kubeconfig"
@@ -40,10 +44,12 @@ import (
4044
clientconfig "github.com/siderolabs/talos/pkg/machinery/client/config"
4145
"github.com/siderolabs/talos/pkg/machinery/config"
4246
"github.com/siderolabs/talos/pkg/machinery/config/bundle"
47+
"github.com/siderolabs/talos/pkg/machinery/config/configloader"
4348
"github.com/siderolabs/talos/pkg/machinery/config/configpatcher"
4449
"github.com/siderolabs/talos/pkg/machinery/config/encoder"
4550
"github.com/siderolabs/talos/pkg/machinery/config/generate"
4651
"github.com/siderolabs/talos/pkg/machinery/config/machine"
52+
"github.com/siderolabs/talos/pkg/machinery/config/types/security"
4753
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
4854
"github.com/siderolabs/talos/pkg/machinery/constants"
4955
"github.com/siderolabs/talos/pkg/machinery/nethelpers"
@@ -752,6 +758,24 @@ func create(ctx context.Context) error {
752758
)
753759
}
754760

761+
var slb *siderolinkBuilder
762+
763+
if withSiderolinkAgent.IsEnabled() {
764+
slb, err = newSiderolinkBuilder(gatewayIPs[0].String(), withSiderolinkAgent.IsTLS())
765+
if err != nil {
766+
return err
767+
}
768+
}
769+
770+
if trustedRootsConfig := slb.TrustedRootsConfig(); trustedRootsConfig != nil {
771+
trustedRootsPatch, err := configloader.NewFromBytes(trustedRootsConfig)
772+
if err != nil {
773+
return fmt.Errorf("error loading trusted roots config: %w", err)
774+
}
775+
776+
configBundleOpts = append(configBundleOpts, bundle.WithPatch([]configpatcher.Patch{configpatcher.NewStrategicMergePatch(trustedRootsPatch)}))
777+
}
778+
755779
configBundle, err := bundle.NewBundle(configBundleOpts...)
756780
if err != nil {
757781
return err
@@ -795,15 +819,6 @@ func create(ctx context.Context) error {
795819
extraKernelArgs = procfs.NewCmdline(extraBootKernelArgs)
796820
}
797821

798-
var slb *siderolinkBuilder
799-
800-
if withSiderolinkAgent.IsEnabled() {
801-
slb, err = newSiderolinkBuilder(gatewayIPs[0].String())
802-
if err != nil {
803-
return err
804-
}
805-
}
806-
807822
err = slb.SetKernelArgs(extraKernelArgs, withSiderolinkAgent.IsTunnel())
808823
if err != nil {
809824
return err
@@ -1255,7 +1270,7 @@ func init() {
12551270
Cmd.AddCommand(createCmd)
12561271
}
12571272

1258-
func newSiderolinkBuilder(wgHost string) (*siderolinkBuilder, error) {
1273+
func newSiderolinkBuilder(wgHost string, useTLS bool) (*siderolinkBuilder, error) {
12591274
prefix, err := networkPrefix("")
12601275
if err != nil {
12611276
return nil, err
@@ -1268,6 +1283,16 @@ func newSiderolinkBuilder(wgHost string) (*siderolinkBuilder, error) {
12681283
nodeIPv6Addr: prefix.Addr().Next().String(),
12691284
}
12701285

1286+
if useTLS {
1287+
ca, err := x509.NewSelfSignedCertificateAuthority(x509.ECDSA(true), x509.IPAddresses([]net.IP{net.ParseIP(wgHost)}))
1288+
if err != nil {
1289+
return nil, err
1290+
}
1291+
1292+
result.apiCert = ca.CrtPEM
1293+
result.apiKey = ca.KeyPEM
1294+
}
1295+
12711296
var resultErr error
12721297

12731298
for range 10 {
@@ -1312,6 +1337,9 @@ type siderolinkBuilder struct {
13121337
apiPort int
13131338
sinkPort int
13141339
logPort int
1340+
1341+
apiCert []byte
1342+
apiKey []byte
13151343
}
13161344

13171345
// DefineIPv6ForUUID defines an IPv6 address for a given UUID. It is safe to call this method on a nil pointer.
@@ -1340,6 +1368,8 @@ func (slb *siderolinkBuilder) SiderolinkRequest() provision.SiderolinkRequest {
13401368
return provision.SiderolinkRequest{
13411369
WireguardEndpoint: net.JoinHostPort(slb.wgHost, strconv.Itoa(slb.wgPort)),
13421370
APIEndpoint: ":" + strconv.Itoa(slb.apiPort),
1371+
APICertificate: slb.apiCert,
1372+
APIKey: slb.apiKey,
13431373
SinkEndpoint: ":" + strconv.Itoa(slb.sinkPort),
13441374
LogEndpoint: ":" + strconv.Itoa(slb.logPort),
13451375
SiderolinkBind: maps.ToSlice(slb.binds, func(k uuid.UUID, v netip.Addr) provision.SiderolinkBind {
@@ -1351,6 +1381,24 @@ func (slb *siderolinkBuilder) SiderolinkRequest() provision.SiderolinkRequest {
13511381
}
13521382
}
13531383

1384+
// TrustedRootsConfig returns the trusted roots config for the current builder.
1385+
func (slb *siderolinkBuilder) TrustedRootsConfig() []byte {
1386+
if slb == nil || slb.apiCert == nil {
1387+
return nil
1388+
}
1389+
1390+
trustedRootsConfig := security.NewTrustedRootsConfigV1Alpha1()
1391+
trustedRootsConfig.MetaName = "siderolink-ca"
1392+
trustedRootsConfig.Certificates = string(slb.apiCert)
1393+
1394+
marshaled, err := encoder.NewEncoder(trustedRootsConfig, encoder.WithComments(encoder.CommentsDisabled)).Encode()
1395+
if err != nil {
1396+
panic(fmt.Sprintf("failed to marshal trusted roots config: %s", err))
1397+
}
1398+
1399+
return marshaled
1400+
}
1401+
13541402
// SetKernelArgs sets the kernel arguments for the current builder. It is safe to call this method on a nil pointer.
13551403
func (slb *siderolinkBuilder) SetKernelArgs(extraKernelArgs *procfs.Cmdline, tunnel bool) error {
13561404
switch {
@@ -1361,7 +1409,13 @@ func (slb *siderolinkBuilder) SetKernelArgs(extraKernelArgs *procfs.Cmdline, tun
13611409
extraKernelArgs.Get("talos.logging.kernel") != nil:
13621410
return errors.New("siderolink kernel arguments are already set, cannot run with --with-siderolink")
13631411
default:
1364-
apiLink := "grpc://" + net.JoinHostPort(slb.wgHost, strconv.Itoa(slb.apiPort)) + "?jointoken=foo"
1412+
scheme := "grpc://"
1413+
1414+
if slb.apiCert != nil {
1415+
scheme = "https://"
1416+
}
1417+
1418+
apiLink := scheme + net.JoinHostPort(slb.wgHost, strconv.Itoa(slb.apiPort)) + "?jointoken=foo"
13651419

13661420
if tunnel {
13671421
apiLink += "&grpc_tunnel=true"
@@ -1371,6 +1425,26 @@ func (slb *siderolinkBuilder) SetKernelArgs(extraKernelArgs *procfs.Cmdline, tun
13711425
extraKernelArgs.Append("talos.events.sink", net.JoinHostPort(slb.nodeIPv6Addr, strconv.Itoa(slb.sinkPort)))
13721426
extraKernelArgs.Append("talos.logging.kernel", "tcp://"+net.JoinHostPort(slb.nodeIPv6Addr, strconv.Itoa(slb.logPort)))
13731427

1428+
if trustedRootsConfig := slb.TrustedRootsConfig(); trustedRootsConfig != nil {
1429+
var buf bytes.Buffer
1430+
1431+
zencoder, err := zstd.NewWriter(&buf)
1432+
if err != nil {
1433+
return fmt.Errorf("failed to create zstd encoder: %w", err)
1434+
}
1435+
1436+
_, err = zencoder.Write(trustedRootsConfig)
1437+
if err != nil {
1438+
return fmt.Errorf("failed to write zstd data: %w", err)
1439+
}
1440+
1441+
if err = zencoder.Close(); err != nil {
1442+
return fmt.Errorf("failed to close zstd encoder: %w", err)
1443+
}
1444+
1445+
extraKernelArgs.Append(constants.KernelParamConfigInline, base64.StdEncoding.EncodeToString(buf.Bytes()))
1446+
}
1447+
13741448
return nil
13751449
}
13761450
}
@@ -1444,6 +1518,10 @@ func (a *agentFlag) String() string {
14441518
return "wireguard"
14451519
case 2:
14461520
return "grpc-tunnel"
1521+
case 3:
1522+
return "wireguard+tls"
1523+
case 4:
1524+
return "grpc-tunnel+tls"
14471525
default:
14481526
return "none"
14491527
}
@@ -1455,13 +1533,18 @@ func (a *agentFlag) Set(s string) error {
14551533
*a = 1
14561534
case "tunnel":
14571535
*a = 2
1536+
case "wireguard+tls":
1537+
*a = 3
1538+
case "grpc-tunnel+tls":
1539+
*a = 4
14581540
default:
1459-
return fmt.Errorf("unknown type: %s, possible values: 'true', 'wireguard' for the usual WG; 'tunnel' for WG over GRPC", s)
1541+
return fmt.Errorf("unknown type: %s, possible values: 'true', 'wireguard' for the usual WG; 'tunnel' for WG over GRPC, add '+tls' to enable TLS for API", s)
14601542
}
14611543

14621544
return nil
14631545
}
14641546

14651547
func (a *agentFlag) Type() string { return "agent" }
14661548
func (a *agentFlag) IsEnabled() bool { return *a != 0 }
1467-
func (a *agentFlag) IsTunnel() bool { return *a == 2 }
1549+
func (a *agentFlag) IsTunnel() bool { return *a == 2 || *a == 4 }
1550+
func (a *agentFlag) IsTLS() bool { return *a == 3 || *a == 4 }

cmd/talosctl/cmd/mgmt/siderolink_launch_linux.go

+19
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package mgmt
66

77
import (
88
"context"
9+
"crypto/tls"
910
"fmt"
1011
"os"
1112
"os/signal"
@@ -23,6 +24,8 @@ var siderolinkFlags struct {
2324
wireguardEndpoint string
2425
sinkEndpoint string
2526
apiEndpoint string
27+
apiCertPath string
28+
apiKeyPath string
2629
logEndpoint string
2730
predefinedPairs []string
2831
}
@@ -46,6 +49,8 @@ func init() {
4649
siderolinkCmd.PersistentFlags().StringVar(&siderolinkFlags.wireguardEndpoint, "sidero-link-wireguard-endpoint", "", "advertised Wireguard endpoint")
4750
siderolinkCmd.PersistentFlags().StringVar(&siderolinkFlags.sinkEndpoint, "event-sink-endpoint", "", "gRPC API endpoint for the Event Sink")
4851
siderolinkCmd.PersistentFlags().StringVar(&siderolinkFlags.apiEndpoint, "sidero-link-api-endpoint", "", "gRPC API endpoint for the SideroLink")
52+
siderolinkCmd.PersistentFlags().StringVar(&siderolinkFlags.apiCertPath, "sidero-link-api-cert", "", "path to the API server certificate (optional)")
53+
siderolinkCmd.PersistentFlags().StringVar(&siderolinkFlags.apiKeyPath, "sidero-link-api-key", "", "path to the API server key (optional)")
4954
siderolinkCmd.PersistentFlags().StringVar(&siderolinkFlags.logEndpoint, "log-receiver-endpoint", "", "TCP log receiver endpoint")
5055
siderolinkCmd.PersistentFlags().StringArrayVar(&siderolinkFlags.predefinedPairs, "predefined-pair", nil, "predefined pairs of UUID=IPv6 addrs for the nodes")
5156

@@ -68,11 +73,25 @@ func run(ctx context.Context) error {
6873
logger.Info("starting embedded siderolink agent")
6974
defer logger.Info("stopping embedded siderolink agent")
7075

76+
var apiTLSConfig *tls.Config
77+
78+
if siderolinkFlags.apiCertPath != "" && siderolinkFlags.apiKeyPath != "" {
79+
apiCert, err := tls.LoadX509KeyPair(siderolinkFlags.apiCertPath, siderolinkFlags.apiKeyPath)
80+
if err != nil {
81+
return fmt.Errorf("failed to load API server certificate: %w", err)
82+
}
83+
84+
apiTLSConfig = &tls.Config{
85+
Certificates: []tls.Certificate{apiCert},
86+
}
87+
}
88+
7189
err = agent.Run(
7290
ctx,
7391
agent.Config{
7492
WireguardEndpoint: siderolinkFlags.wireguardEndpoint,
7593
APIEndpoint: siderolinkFlags.apiEndpoint,
94+
APITLSConfig: apiTLSConfig,
7695
JoinToken: siderolinkFlags.joinToken,
7796
SinkEndpoint: siderolinkFlags.sinkEndpoint,
7897
LogEndpoint: siderolinkFlags.logEndpoint,

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ require (
152152
github.com/siderolabs/kms-client v0.1.0
153153
github.com/siderolabs/net v0.4.0
154154
github.com/siderolabs/protoenc v0.2.1
155-
github.com/siderolabs/siderolink v0.3.9
155+
github.com/siderolabs/siderolink v0.3.10
156156
github.com/siderolabs/talos/pkg/machinery v1.8.0-alpha.2
157157
github.com/spf13/cobra v1.8.1
158158
github.com/spf13/pflag v1.0.5

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -629,8 +629,8 @@ github.com/siderolabs/net v0.4.0 h1:1bOgVay/ijPkJz4qct98nHsiB/ysLQU0KLoBC4qLm7I=
629629
github.com/siderolabs/net v0.4.0/go.mod h1:/ibG+Hm9HU27agp5r9Q3eZicEfjquzNzQNux5uEk0kM=
630630
github.com/siderolabs/protoenc v0.2.1 h1:BqxEmeWQeMpNP3R6WrPqDatX8sM/r4t97OP8mFmg6GA=
631631
github.com/siderolabs/protoenc v0.2.1/go.mod h1:StTHxjet1g11GpNAWiATgc8K0HMKiFSEVVFOa/H0otc=
632-
github.com/siderolabs/siderolink v0.3.9 h1:lvHFCu+CdfUyMk90g1Zt5r7n1Dw3jhXMxyzXmQ0776o=
633-
github.com/siderolabs/siderolink v0.3.9/go.mod h1:QbGnXpHI5MDq6qMZkCFnxYOOw5eE+lkLx53L5ZgjLMQ=
632+
github.com/siderolabs/siderolink v0.3.10 h1:M8OrRyfzmyyGksHalOqvRSxvb1Fwi7S3AFQx6ERap44=
633+
github.com/siderolabs/siderolink v0.3.10/go.mod h1:QbGnXpHI5MDq6qMZkCFnxYOOw5eE+lkLx53L5ZgjLMQ=
634634
github.com/siderolabs/tcpproxy v0.1.0 h1:IbkS9vRhjMOscc1US3M5P1RnsGKFgB6U5IzUk+4WkKA=
635635
github.com/siderolabs/tcpproxy v0.1.0/go.mod h1:onn6CPPj/w1UNqQ0U97oRPF0CqbrgEApYCw4P9IiCW8=
636636
github.com/siderolabs/wgctrl-go v0.0.0-20240401105613-579af3342774 h1:wLhs5zMQVjA6LN9WpF2owOdtcoRp40zL8AaQSle+9EE=

internal/app/machined/pkg/controllers/siderolink/manager.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"google.golang.org/grpc/credentials/insecure"
3131

3232
networkutils "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/network/utils"
33+
"github.com/siderolabs/talos/pkg/httpdefaults"
3334
"github.com/siderolabs/talos/pkg/machinery/constants"
3435
"github.com/siderolabs/talos/pkg/machinery/nethelpers"
3536
"github.com/siderolabs/talos/pkg/machinery/resources/config"
@@ -487,7 +488,9 @@ func withTransportCredentials(insec bool) grpc.DialOption {
487488
if insec {
488489
transportCredentials = insecure.NewCredentials()
489490
} else {
490-
transportCredentials = credentials.NewTLS(&tls.Config{})
491+
transportCredentials = credentials.NewTLS(&tls.Config{
492+
RootCAs: httpdefaults.RootCAs(),
493+
})
491494
}
492495

493496
return grpc.WithTransportCredentials(transportCredentials)

internal/pkg/encryption/keys/kms.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323

2424
"github.com/siderolabs/talos/internal/pkg/encryption/helpers"
2525
"github.com/siderolabs/talos/internal/pkg/endpoint"
26+
"github.com/siderolabs/talos/pkg/httpdefaults"
2627
)
2728

2829
// KMSToken is the userdata stored in the partition token metadata.
@@ -130,7 +131,9 @@ func (h *KMSKeyHandler) getConn() (*grpc.ClientConn, error) {
130131
if endpoint.Insecure {
131132
transportCredentials = insecure.NewCredentials()
132133
} else {
133-
transportCredentials = credentials.NewTLS(&tls.Config{})
134+
transportCredentials = credentials.NewTLS(&tls.Config{
135+
RootCAs: httpdefaults.RootCAs(),
136+
})
134137
}
135138

136139
return grpc.NewClient(

0 commit comments

Comments
 (0)