Skip to content

Commit 96e6048

Browse files
committed
fix: add hostname to endpoints
Populate endpoint coming from the Kubernetes controlplane endpoint with the hostname (if the endpoint is a hostname). This should improve cases when hostname is used for the endpoint in terms of SNI, proper resolving of DNS if it's dynamic. See #12556 (comment) Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
1 parent 7033275 commit 96e6048

File tree

12 files changed

+184
-20
lines changed

12 files changed

+184
-20
lines changed

api/resource/definitions/k8s/k8s.proto

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,10 @@ message ControllerManagerConfigSpec {
9393
Resources resources = 9;
9494
}
9595

96-
// EndpointSpec describes status of rendered secrets.
96+
// EndpointSpec describes a list of endpoints to connect to.
9797
message EndpointSpec {
9898
repeated common.NetIP addresses = 1;
99+
repeated string hosts = 2;
99100
}
100101

101102
// ExtraManifest defines a single extra manifest to download.

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ func (ctrl *StaticEndpointController) Run(ctx context.Context, r controller.Runt
7575
var (
7676
resolver net.Resolver
7777
addrs []netip.Addr
78+
hosts []string
7879
)
7980

8081
addrs, err = resolver.LookupNetIP(ctx, "ip", cpHostname)
@@ -84,8 +85,13 @@ func (ctrl *StaticEndpointController) Run(ctx context.Context, r controller.Runt
8485

8586
addrs = xslices.Map(addrs, netip.Addr.Unmap)
8687

88+
if len(addrs) != 1 || addrs[0].String() != cpHostname {
89+
hosts = []string{cpHostname}
90+
}
91+
8792
if err = safe.WriterModify(ctx, r, k8s.NewEndpoint(k8s.ControlPlaneNamespaceName, k8s.ControlPlaneKubernetesEndpointsID), func(endpoint *k8s.Endpoint) error {
8893
endpoint.TypedSpec().Addresses = addrs
94+
endpoint.TypedSpec().Hosts = hosts
8995

9096
return nil
9197
}); err != nil {

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,44 @@ func (suite *StaticEndpointControllerSuite) TestReconcile() {
5151
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.ControlPlaneKubernetesEndpointsID},
5252
func(endpoint *k8s.Endpoint, assert *assert.Assertions) {
5353
assert.Equal([]netip.Addr{netip.MustParseAddr("2001:db8::1")}, endpoint.TypedSpec().Addresses)
54+
assert.Empty(endpoint.TypedSpec().Hosts)
5455
})
5556

5657
suite.Require().NoError(suite.State().Destroy(suite.Ctx(), cfg.Metadata()))
5758

5859
rtestutils.AssertNoResource[*k8s.Endpoint](suite.Ctx(), suite.T(), suite.State(), k8s.ControlPlaneKubernetesEndpointsID)
5960
}
6061

62+
func (suite *StaticEndpointControllerSuite) TestReconcileHostname() {
63+
u, err := url.Parse("https://localhost:6443/")
64+
suite.Require().NoError(err)
65+
66+
cfg := config.NewMachineConfig(
67+
container.NewV1Alpha1(
68+
&v1alpha1.Config{
69+
ConfigVersion: "v1alpha1",
70+
MachineConfig: &v1alpha1.MachineConfig{},
71+
ClusterConfig: &v1alpha1.ClusterConfig{
72+
ControlPlane: &v1alpha1.ControlPlaneConfig{
73+
Endpoint: &v1alpha1.Endpoint{
74+
URL: u,
75+
},
76+
},
77+
},
78+
},
79+
),
80+
)
81+
82+
suite.Require().NoError(suite.State().Create(suite.Ctx(), cfg))
83+
84+
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.ControlPlaneKubernetesEndpointsID},
85+
func(endpoint *k8s.Endpoint, assert *assert.Assertions) {
86+
// localhost might resolve to ::1 as well, check only for 127.0.0.1
87+
assert.Contains(endpoint.TypedSpec().Addresses, netip.MustParseAddr("127.0.0.1"))
88+
assert.Equal([]string{"localhost"}, endpoint.TypedSpec().Hosts)
89+
})
90+
}
91+
6192
func TestStaticEndpointControllerSuite(t *testing.T) {
6293
t.Parallel()
6394

internal/app/machined/pkg/controllers/kubeaccess/endpoint.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func (ctrl *EndpointController) Run(ctx context.Context, r controller.Runtime, l
108108
endpointAddrs = endpointAddrs.Merge(endpointResource)
109109
}
110110

111-
if len(endpointAddrs) == 0 {
111+
if endpointAddrs.IsEmpty() {
112112
continue
113113
}
114114

@@ -212,20 +212,20 @@ func (ctrl *EndpointController) ensureTalosEndpointSlices(ctx context.Context, l
212212
addrsIPv6 k8s.EndpointList
213213
)
214214

215-
for _, addr := range endpointAddrs {
215+
for _, addr := range endpointAddrs.Addresses {
216216
switch {
217217
case addr.Is4():
218-
addrsIPv4 = append(addrsIPv4, addr)
218+
addrsIPv4.Addresses = append(addrsIPv4.Addresses, addr)
219219

220220
case addr.Is6():
221-
addrsIPv6 = append(addrsIPv6, addr)
221+
addrsIPv6.Addresses = append(addrsIPv6.Addresses, addr)
222222

223223
default:
224224
// ignore other address types
225225
}
226226
}
227227

228-
if len(addrsIPv4) == 0 {
228+
if len(addrsIPv4.Addresses) == 0 {
229229
if err := ctrl.deleteTalosEndpointSlicesTyped(ctx, logger, client, discoveryv1.AddressTypeIPv4); err != nil {
230230
return fmt.Errorf("error deleting Talos API endpoint slices for IPv4: %w", err)
231231
}
@@ -235,7 +235,7 @@ func (ctrl *EndpointController) ensureTalosEndpointSlices(ctx context.Context, l
235235
}
236236
}
237237

238-
if len(addrsIPv6) == 0 {
238+
if len(addrsIPv6.Addresses) == 0 {
239239
if err := ctrl.deleteTalosEndpointSlicesTyped(ctx, logger, client, discoveryv1.AddressTypeIPv6); err != nil {
240240
return fmt.Errorf("error deleting Talos API endpoint slices for IPv6: %w", err)
241241
}
@@ -306,7 +306,7 @@ func (ctrl *EndpointController) ensureTalosEndpointSlicesTyped(
306306
},
307307
}
308308

309-
for _, addr := range endpointAddrs {
309+
for _, addr := range endpointAddrs.Addresses {
310310
newEndpointSlice.Endpoints = append(
311311
newEndpointSlice.Endpoints,
312312
discoveryv1.Endpoint{

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ func (ctrl *APIController) reconcile(ctx context.Context, r controller.Runtime,
257257
endpointAddrs = endpointAddrs.Merge(res.(*k8s.Endpoint))
258258
}
259259

260-
if len(endpointAddrs) == 0 {
260+
if endpointAddrs.IsEmpty() {
261261
continue
262262
}
263263

internal/pkg/etcd/endpoints.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func GetEndpoints(ctx context.Context, resources state.State) ([]string, error)
3333
endpointAddrs = endpointAddrs.Merge(res)
3434
}
3535

36-
if len(endpointAddrs) == 0 {
36+
if endpointAddrs.IsEmpty() {
3737
return nil, errors.New("no controlplane endpoints discovered yet")
3838
}
3939

pkg/machinery/api/resource/definitions/k8s/k8s.pb.go

Lines changed: 12 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/machinery/api/resource/definitions/k8s/k8s_vtproto.pb.go

Lines changed: 47 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/machinery/resources/k8s/deep_copy.generated.go

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/machinery/resources/k8s/endpoint.go

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ const ControlPlaneKubernetesEndpointsID = resource.ID("controlplane")
3232
// Endpoint resource holds definition of rendered secrets.
3333
type Endpoint = typed.Resource[EndpointSpec, EndpointExtension]
3434

35-
// EndpointSpec describes status of rendered secrets.
35+
// EndpointSpec describes a list of endpoints to connect to.
3636
//
3737
//gotagsrewrite:gen
3838
type EndpointSpec struct {
3939
Addresses []netip.Addr `yaml:"addresses" protobuf:"1"`
40+
Hosts []string `yaml:"hosts" protobuf:"2"`
4041
}
4142

4243
// NewEndpoint initializes the Endpoint resource.
@@ -61,32 +62,56 @@ func (EndpointExtension) ResourceDefinition() meta.ResourceDefinitionSpec {
6162
Name: "Addresses",
6263
JSONPath: "{.addresses}",
6364
},
65+
{
66+
Name: "Hosts",
67+
JSONPath: "{.hosts}",
68+
},
6469
},
6570
}
6671
}
6772

6873
// EndpointList is a flattened list of endpoints.
69-
type EndpointList []netip.Addr
74+
type EndpointList struct {
75+
Addresses []netip.Addr
76+
Hosts []string
77+
}
7078

7179
// Merge endpoints from multiple Endpoint resources into a single list.
7280
func (l EndpointList) Merge(endpoint *Endpoint) EndpointList {
7381
for _, ip := range endpoint.TypedSpec().Addresses {
74-
idx, _ := slices.BinarySearchFunc(l, ip, func(a netip.Addr, target netip.Addr) int {
82+
idx, _ := slices.BinarySearchFunc(l.Addresses, ip, func(a netip.Addr, target netip.Addr) int {
7583
return a.Compare(target)
7684
})
77-
if idx < len(l) && l[idx].Compare(ip) == 0 {
85+
if idx < len(l.Addresses) && l.Addresses[idx].Compare(ip) == 0 {
7886
continue
7987
}
8088

81-
l = slices.Insert(l, idx, ip)
89+
l.Addresses = slices.Insert(l.Addresses, idx, ip)
90+
}
91+
92+
for _, host := range endpoint.TypedSpec().Hosts {
93+
idx, _ := slices.BinarySearch(l.Hosts, host)
94+
if idx < len(l.Hosts) && l.Hosts[idx] == host {
95+
continue
96+
}
97+
98+
l.Hosts = slices.Insert(l.Hosts, idx, host)
8299
}
83100

84101
return l
85102
}
86103

104+
// IsEmpty checks if the EndpointList is empty.
105+
func (l EndpointList) IsEmpty() bool {
106+
return len(l.Addresses) == 0 && len(l.Hosts) == 0
107+
}
108+
87109
// Strings returns a slice of formatted endpoints to string.
88110
func (l EndpointList) Strings() []string {
89-
return xslices.Map(l, netip.Addr.String)
111+
return slices.Concat(
112+
xslices.Map(l.Addresses, netip.Addr.String),
113+
l.Hosts,
114+
)
90115
}
91116

92117
func init() {

0 commit comments

Comments
 (0)