Skip to content

Commit 1091813

Browse files
committed
fix: lock down etcd listen address to IPv4 localhost
Use literal IP address instead of `localhost` to make `kube-apiserver` connect to etcd member instead of relying on IPv4/IPv6 resolving of `localhost`. Simplify configuration for listening on 127.0.0.1 only, generate cert SANs uncoditionally for etcd loopback IPs. Fixes #12542 Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com> (cherry picked from commit 35fc520)
1 parent 9f8d938 commit 1091813

File tree

5 files changed

+72
-110
lines changed

5 files changed

+72
-110
lines changed

internal/app/machined/pkg/controllers/etcd/spec.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -155,18 +155,9 @@ func (ctrl *SpecController) Run(ctx context.Context, r controller.Runtime, _ *za
155155
xslices.Map(etcdConfig.TypedSpec().ListenExcludeSubnets, func(cidr string) string { return "!" + cidr }),
156156
)
157157

158-
defaultListenAddress := netip.AddrFrom4([4]byte{0, 0, 0, 0})
158+
defaultListenAddress := netip.IPv4Unspecified()
159159
loopbackAddress := netip.AddrFrom4([4]byte{127, 0, 0, 1})
160160

161-
for _, ip := range routedAddrs {
162-
if ip.Is6() {
163-
defaultListenAddress = netip.IPv6Unspecified()
164-
loopbackAddress = netip.MustParseAddr("::1")
165-
166-
break
167-
}
168-
}
169-
170161
var (
171162
advertisedIPs []netip.Addr
172163
listenPeerIPs []netip.Addr

internal/app/machined/pkg/controllers/etcd/spec_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,10 @@ func (suite *SpecSuite) TestReconcile() {
9292
netip.MustParseAddr("10.0.0.5"),
9393
},
9494
ListenPeerAddresses: []netip.Addr{
95-
netip.IPv6Unspecified(),
95+
netip.IPv4Unspecified(),
9696
},
9797
ListenClientAddresses: []netip.Addr{
98-
netip.IPv6Unspecified(),
98+
netip.IPv4Unspecified(),
9999
},
100100
},
101101
},
@@ -114,10 +114,10 @@ func (suite *SpecSuite) TestReconcile() {
114114
netip.MustParseAddr("192.168.1.1"),
115115
},
116116
ListenPeerAddresses: []netip.Addr{
117-
netip.IPv6Unspecified(),
117+
netip.IPv4Unspecified(),
118118
},
119119
ListenClientAddresses: []netip.Addr{
120-
netip.IPv6Unspecified(),
120+
netip.IPv4Unspecified(),
121121
},
122122
},
123123
},
@@ -139,10 +139,10 @@ func (suite *SpecSuite) TestReconcile() {
139139
netip.MustParseAddr("1.3.5.7"),
140140
},
141141
ListenPeerAddresses: []netip.Addr{
142-
netip.IPv6Unspecified(),
142+
netip.IPv4Unspecified(),
143143
},
144144
ListenClientAddresses: []netip.Addr{
145-
netip.IPv6Unspecified(),
145+
netip.IPv4Unspecified(),
146146
},
147147
},
148148
},
@@ -165,10 +165,10 @@ func (suite *SpecSuite) TestReconcile() {
165165
netip.MustParseAddr("192.168.1.1"),
166166
},
167167
ListenPeerAddresses: []netip.Addr{
168-
netip.IPv6Unspecified(),
168+
netip.IPv4Unspecified(),
169169
},
170170
ListenClientAddresses: []netip.Addr{
171-
netip.IPv6Unspecified(),
171+
netip.IPv4Unspecified(),
172172
},
173173
},
174174
},
@@ -197,7 +197,7 @@ func (suite *SpecSuite) TestReconcile() {
197197
netip.MustParseAddr("192.168.1.50"),
198198
},
199199
ListenClientAddresses: []netip.Addr{
200-
netip.MustParseAddr("::1"),
200+
netip.MustParseAddr("127.0.0.1"),
201201
netip.MustParseAddr("192.168.1.1"),
202202
netip.MustParseAddr("192.168.1.50"),
203203
},

internal/app/machined/pkg/controllers/k8s/control_plane.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ func NewControlPlaneAPIServerController() *ControlPlaneAPIServerController {
192192
Image: cfgProvider.Cluster().APIServer().Image(),
193193
CloudProvider: cloudProvider,
194194
ControlPlaneEndpoint: cfgProvider.Cluster().Endpoint().String(),
195-
EtcdServers: []string{fmt.Sprintf("https://%s", nethelpers.JoinHostPort("localhost", constants.EtcdClientPort))},
195+
EtcdServers: []string{fmt.Sprintf("https://%s", nethelpers.JoinHostPort("127.0.0.1", constants.EtcdClientPort))},
196196
LocalPort: cfgProvider.Cluster().LocalAPIServerPort(),
197197
ServiceCIDRs: cfgProvider.Cluster().Network().ServiceCIDRs(),
198198
ExtraArgs: cfgProvider.Cluster().APIServer().ExtraArgs(),

internal/app/machined/pkg/controllers/secrets/etcd_test.go

Lines changed: 57 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,8 @@ import (
1010
"testing"
1111
"time"
1212

13-
"github.com/cosi-project/runtime/pkg/resource"
14-
"github.com/cosi-project/runtime/pkg/state"
1513
"github.com/siderolabs/crypto/x509"
1614
"github.com/stretchr/testify/assert"
17-
"github.com/stretchr/testify/require"
1815
"github.com/stretchr/testify/suite"
1916

2017
"github.com/siderolabs/talos/internal/app/machined/pkg/controllers/ctest"
@@ -26,8 +23,11 @@ import (
2623
)
2724

2825
func TestEtcdSuite(t *testing.T) {
26+
t.Parallel()
27+
2928
suite.Run(t, &EtcdSuite{
3029
DefaultSuite: ctest.DefaultSuite{
30+
Timeout: 5 * time.Second,
3131
AfterSetup: func(suite *ctest.DefaultSuite) {
3232
suite.Require().NoError(suite.Runtime().RegisterController(&secretsctrl.EtcdController{}))
3333
},
@@ -52,124 +52,100 @@ func (suite *EtcdSuite) TestReconcile() {
5252
Crt: etcdCA.CrtPEM,
5353
Key: etcdCA.KeyPEM,
5454
}
55-
suite.Require().NoError(suite.State().Create(suite.Ctx(), rootSecrets))
55+
suite.Create(rootSecrets)
5656

5757
networkStatus := network.NewStatus(network.NamespaceName, network.StatusID)
5858
networkStatus.TypedSpec().AddressReady = true
5959
networkStatus.TypedSpec().HostnameReady = true
60-
suite.Require().NoError(suite.State().Create(suite.Ctx(), networkStatus))
60+
suite.Create(networkStatus)
6161

6262
hostnameStatus := network.NewHostnameStatus(network.NamespaceName, network.HostnameID)
6363
hostnameStatus.TypedSpec().Hostname = "host"
6464
hostnameStatus.TypedSpec().Domainname = "domain"
65-
suite.Require().NoError(suite.State().Create(suite.Ctx(), hostnameStatus))
65+
suite.Create(hostnameStatus)
6666

6767
nodeAddresses := network.NewNodeAddress(network.NamespaceName, network.FilteredNodeAddressID(network.NodeAddressAccumulativeID, k8s.NodeAddressFilterNoK8s))
6868
nodeAddresses.TypedSpec().Addresses = []netip.Prefix{
6969
netip.MustParsePrefix("10.3.4.5/24"),
7070
netip.MustParsePrefix("2001:db8::1eaf/64"),
7171
}
72-
suite.Require().NoError(suite.State().Create(suite.Ctx(), nodeAddresses))
72+
suite.Create(nodeAddresses)
7373

7474
timeSync := timeres.NewStatus()
7575
timeSync.TypedSpec().Synced = true
76-
suite.Require().NoError(suite.State().Create(suite.Ctx(), timeSync))
77-
78-
suite.AssertWithin(3*time.Second, 100*time.Millisecond,
79-
ctest.WrapRetry(func(assert *assert.Assertions, require *require.Assertions) {
80-
certs, err := ctest.Get[*secrets.Etcd](
81-
suite,
82-
resource.NewMetadata(
83-
secrets.NamespaceName,
84-
secrets.EtcdType,
85-
secrets.EtcdID,
86-
resource.VersionUndefined,
87-
),
88-
)
89-
if err != nil {
90-
if state.IsNotFoundError(err) {
91-
assert.NoError(err)
92-
} else {
93-
require.NoError(err)
94-
}
76+
suite.Create(timeSync)
9577

96-
return
97-
}
78+
ctest.AssertResource(suite, secrets.EtcdID, func(certs *secrets.Etcd, asrt *assert.Assertions) {
79+
etcdCerts := certs.TypedSpec()
9880

99-
etcdCerts := certs.TypedSpec()
81+
serverCert, err := etcdCerts.Etcd.GetCert()
82+
if !asrt.NoError(err) {
83+
return
84+
}
10085

101-
serverCert, err := etcdCerts.Etcd.GetCert()
102-
require.NoError(err)
86+
asrt.Equal([]string{"host", "host.domain", "localhost"}, serverCert.DNSNames)
87+
asrt.Equal("[10.3.4.5 2001:db8::1eaf 127.0.0.1 ::1]", fmt.Sprintf("%v", serverCert.IPAddresses))
10388

104-
assert.Equal([]string{"host", "host.domain", "localhost"}, serverCert.DNSNames)
105-
assert.Equal("[10.3.4.5 2001:db8::1eaf 127.0.0.1 ::1]", fmt.Sprintf("%v", serverCert.IPAddresses))
89+
asrt.Equal("host", serverCert.Subject.CommonName)
10690

107-
assert.Equal("host", serverCert.Subject.CommonName)
91+
peerCert, err := etcdCerts.EtcdPeer.GetCert()
92+
if !asrt.NoError(err) {
93+
return
94+
}
10895

109-
peerCert, err := etcdCerts.EtcdPeer.GetCert()
110-
require.NoError(err)
96+
asrt.Equal([]string{"host", "host.domain"}, peerCert.DNSNames)
97+
asrt.Equal("[10.3.4.5 2001:db8::1eaf]", fmt.Sprintf("%v", peerCert.IPAddresses))
11198

112-
assert.Equal([]string{"host", "host.domain"}, peerCert.DNSNames)
113-
assert.Equal("[10.3.4.5 2001:db8::1eaf]", fmt.Sprintf("%v", peerCert.IPAddresses))
99+
asrt.Equal("host", peerCert.Subject.CommonName)
114100

115-
assert.Equal("host", peerCert.Subject.CommonName)
101+
adminCert, err := etcdCerts.EtcdAdmin.GetCert()
102+
if !asrt.NoError(err) {
103+
return
104+
}
116105

117-
adminCert, err := etcdCerts.EtcdAdmin.GetCert()
118-
require.NoError(err)
106+
asrt.Empty(adminCert.DNSNames)
107+
asrt.Empty(adminCert.IPAddresses)
119108

120-
assert.Empty(adminCert.DNSNames)
121-
assert.Empty(adminCert.IPAddresses)
109+
asrt.Equal("talos", adminCert.Subject.CommonName)
122110

123-
assert.Equal("talos", adminCert.Subject.CommonName)
111+
kubeAPICert, err := etcdCerts.EtcdAPIServer.GetCert()
112+
if !asrt.NoError(err) {
113+
return
114+
}
124115

125-
kubeAPICert, err := etcdCerts.EtcdAPIServer.GetCert()
126-
require.NoError(err)
116+
asrt.Empty(kubeAPICert.DNSNames)
117+
asrt.Empty(kubeAPICert.IPAddresses)
127118

128-
assert.Empty(kubeAPICert.DNSNames)
129-
assert.Empty(kubeAPICert.IPAddresses)
130-
131-
assert.Equal("kube-apiserver", kubeAPICert.Subject.CommonName)
132-
}))
119+
asrt.Equal("kube-apiserver", kubeAPICert.Subject.CommonName)
120+
})
133121

134122
// update node addresses, certs should be updated
135123
nodeAddresses.TypedSpec().Addresses = []netip.Prefix{
136124
netip.MustParsePrefix("10.3.4.5/24"),
137125
}
138-
suite.Require().NoError(suite.State().Update(suite.Ctx(), nodeAddresses))
126+
suite.Update(nodeAddresses)
139127

140-
suite.AssertWithin(3*time.Second, 100*time.Millisecond,
141-
ctest.WrapRetry(func(assert *assert.Assertions, require *require.Assertions) {
142-
certs, err := ctest.Get[*secrets.Etcd](
143-
suite,
144-
resource.NewMetadata(
145-
secrets.NamespaceName,
146-
secrets.EtcdType,
147-
secrets.EtcdID,
148-
resource.VersionUndefined,
149-
),
150-
)
151-
if err != nil {
152-
require.NoError(err)
128+
ctest.AssertResource(suite, secrets.EtcdID, func(certs *secrets.Etcd, asrt *assert.Assertions) {
129+
etcdCerts := certs.TypedSpec()
153130

154-
return
155-
}
131+
serverCert, err := etcdCerts.Etcd.GetCert()
132+
if !asrt.NoError(err) {
133+
return
134+
}
156135

157-
etcdCerts := certs.TypedSpec()
136+
asrt.Equal([]string{"host", "host.domain", "localhost"}, serverCert.DNSNames)
137+
asrt.Equal("[10.3.4.5 127.0.0.1 ::1]", fmt.Sprintf("%v", serverCert.IPAddresses))
158138

159-
serverCert, err := etcdCerts.Etcd.GetCert()
160-
require.NoError(err)
139+
asrt.Equal("host", serverCert.Subject.CommonName)
161140

162-
assert.Equal([]string{"host", "host.domain", "localhost"}, serverCert.DNSNames)
163-
assert.Equal("[10.3.4.5 127.0.0.1]", fmt.Sprintf("%v", serverCert.IPAddresses))
141+
peerCert, err := etcdCerts.EtcdPeer.GetCert()
142+
if !asrt.NoError(err) {
143+
return
144+
}
164145

165-
assert.Equal("host", serverCert.Subject.CommonName)
146+
asrt.Equal([]string{"host", "host.domain"}, peerCert.DNSNames)
147+
asrt.Equal("[10.3.4.5]", fmt.Sprintf("%v", peerCert.IPAddresses))
166148

167-
peerCert, err := etcdCerts.EtcdPeer.GetCert()
168-
require.NoError(err)
169-
170-
assert.Equal([]string{"host", "host.domain"}, peerCert.DNSNames)
171-
assert.Equal("[10.3.4.5]", fmt.Sprintf("%v", peerCert.IPAddresses))
172-
173-
assert.Equal("host", peerCert.Subject.CommonName)
174-
}))
149+
asrt.Equal("host", peerCert.Subject.CommonName)
150+
})
175151
}

internal/pkg/etcd/certs.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,10 @@ func (gen *CertificateGenerator) buildOptions(autoSANs, includeLocalhost bool) [
3030
addresses := gen.NodeAddresses.TypedSpec().IPs()
3131

3232
if includeLocalhost {
33-
addresses = append(addresses, netip.MustParseAddr("127.0.0.1"))
34-
35-
for _, addr := range addresses {
36-
if addr.Is6() {
37-
addresses = append(addresses, netip.MustParseAddr("::1"))
38-
39-
break
40-
}
41-
}
33+
addresses = append(addresses,
34+
netip.MustParseAddr("127.0.0.1"),
35+
netip.MustParseAddr("::1"),
36+
)
4237
}
4338

4439
hostname := gen.HostnameStatus.TypedSpec().Hostname

0 commit comments

Comments
 (0)