Skip to content

Commit

Permalink
feat: add support for custom search domains
Browse files Browse the repository at this point in the history
New config field `machine.network.searchDomains` supports specifying custom search domains.

For the node it will look something like this:
```
nameserver 127.0.0.53

search my-custom-search-name.com my-custom-search-name2.com
```

For the pods it will look something like this:

```
search default.svc.cluster.local svc.cluster.local cluster.local my-custom-search-name.com my-custom-search-name2.com
nameserver 10.96.0.10
options ndots:5
```

Signed-off-by: Dmitriy Matrenichev <[email protected]>
  • Loading branch information
DmitriyMV committed Nov 29, 2024
1 parent 95c6958 commit 60e4561
Show file tree
Hide file tree
Showing 24 changed files with 510 additions and 243 deletions.
2 changes: 2 additions & 0 deletions api/resource/definitions/network/network.proto
Original file line number Diff line number Diff line change
Expand Up @@ -316,11 +316,13 @@ message ProbeStatusSpec {
message ResolverSpecSpec {
repeated common.NetIP dns_servers = 1;
talos.resource.definitions.enums.NetworkConfigLayer config_layer = 2;
repeated string search_domains = 3;
}

// ResolverStatusSpec describes DNS resolvers.
message ResolverStatusSpec {
repeated common.NetIP dns_servers = 1;
repeated string search_domains = 2;
}

// RouteSpecSpec describes the route.
Expand Down
24 changes: 24 additions & 0 deletions hack/release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,30 @@ The new command `talosctl wipe disk` allows to wipe a disk or a partition which
title = "OCI Base Runtime Spec"
description = """\
Talos now allows to [modify the OCI base runtime spec for the container runtime](https://www.talos.dev/v1.9/advanced/oci-base-spec/).
"""

[notes.custom-search-domains]
title = "Custom search domains for Talos nodes"
description = """\
Talos now allows to supports specifying custom search domains for Talos nodes using
new config field `machine.network.searchDomains`
For the host it will look something like this:
```
nameserver 127.0.0.53
search my-custom-search-name.com my-custom-search-name2.com
```
For the pods it will look something like this:
```
search default.svc.cluster.local svc.cluster.local cluster.local my-custom-search-name.com my-custom-search-name2.com
nameserver 10.96.0.10
options ndots:5
```
"""

[make_deps]
Expand Down
23 changes: 8 additions & 15 deletions internal/app/machined/pkg/controllers/network/etcfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,14 @@ func (ctrl *EtcFileController) Run(ctx context.Context, r controller.Runtime, _
}
}

var hostnameStatusSpec *network.HostnameStatusSpec
if hostnameStatus != nil {
hostnameStatusSpec = hostnameStatus.TypedSpec()
}

if resolverStatus != nil && hostDNSCfg != nil && !ctrl.V1Alpha1Mode.InContainer() {
// in container mode, keep the original resolv.conf to use the resolvers supplied by the container runtime
if err = safe.WriterModify(ctx, r, files.NewEtcFileSpec(files.NamespaceName, "resolv.conf"),
func(r *files.EtcFileSpec) error {
r.TypedSpec().Contents = renderResolvConf(pickNameservers(hostDNSCfg, resolverStatus), hostnameStatusSpec, cfgProvider)
r.TypedSpec().Contents = renderResolvConf(
pickNameservers(hostDNSCfg, resolverStatus),
resolverStatus.TypedSpec().SearchDomains,
)
r.TypedSpec().Mode = 0o644
r.TypedSpec().SelinuxLabel = constants.EtcSelinuxLabel

Expand All @@ -169,7 +167,7 @@ func (ctrl *EtcFileController) Run(ctx context.Context, r controller.Runtime, _
dnsServers = resolverStatus.TypedSpec().DNSServers
}

conf := renderResolvConf(slices.All(dnsServers), hostnameStatusSpec, cfgProvider)
conf := renderResolvConf(slices.All(dnsServers), resolverStatus.TypedSpec().SearchDomains)

if err = os.MkdirAll(filepath.Dir(ctrl.PodResolvConfPath), 0o755); err != nil {
return fmt.Errorf("error creating pod resolv.conf dir: %w", err)
Expand Down Expand Up @@ -209,7 +207,7 @@ func pickNameservers(hostDNSCfg *network.HostDNSConfig, resolverStatus *network.
return slices.All(resolverStatus.TypedSpec().DNSServers)
}

func renderResolvConf(nameservers iter.Seq2[int, netip.Addr], hostnameStatus *network.HostnameStatusSpec, cfgProvider talosconfig.Config) []byte {
func renderResolvConf(nameservers iter.Seq2[int, netip.Addr], searchDomains []string) []byte {
var buf bytes.Buffer

for i, ns := range nameservers {
Expand All @@ -221,13 +219,8 @@ func renderResolvConf(nameservers iter.Seq2[int, netip.Addr], hostnameStatus *ne
fmt.Fprintf(&buf, "nameserver %s\n", ns)
}

var disableSearchDomain bool
if cfgProvider != nil && cfgProvider.Machine() != nil {
disableSearchDomain = cfgProvider.Machine().Network().DisableSearchDomain()
}

if !disableSearchDomain && hostnameStatus != nil && hostnameStatus.Domainname != "" {
fmt.Fprintf(&buf, "\nsearch %s\n", hostnameStatus.Domainname)
if len(searchDomains) > 0 {
fmt.Fprintf(&buf, "\nsearch %s\n", strings.Join(searchDomains, " "))
}

return buf.Bytes()
Expand Down
12 changes: 8 additions & 4 deletions internal/app/machined/pkg/controllers/network/etcfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,23 +223,27 @@ func (suite *EtcFileConfigSuite) testFiles(resources []resource.Resource, conten
}

func (suite *EtcFileConfigSuite) TestComplete() {
suite.resolverStatus.TypedSpec().SearchDomains = []string{"foo.example.com"}

suite.testFiles(
[]resource.Resource{suite.cfg, suite.defaultAddress, suite.hostnameStatus, suite.resolverStatus, suite.hostDNSConfig},
etcFileContents{
hosts: "127.0.0.1 localhost\n33.11.22.44 foo.example.com foo\n::1 localhost ip6-localhost ip6-loopback\nff02::1 ip6-allnodes\nff02::2 ip6-allrouters\n10.0.0.1 a b\n10.0.0.2 c d\n", //nolint:lll
resolvConf: "nameserver 127.0.0.53\n\nsearch example.com\n",
resolvGlobalConf: "nameserver 169.254.116.108\n\nsearch example.com\n",
resolvConf: "nameserver 127.0.0.53\n\nsearch foo.example.com\n",
resolvGlobalConf: "nameserver 169.254.116.108\n\nsearch foo.example.com\n",
},
)
}

func (suite *EtcFileConfigSuite) TestNoExtraHosts() {
suite.resolverStatus.TypedSpec().SearchDomains = []string{"foo.example.com"}

suite.testFiles(
[]resource.Resource{suite.defaultAddress, suite.hostnameStatus, suite.resolverStatus, suite.hostDNSConfig},
etcFileContents{
hosts: "127.0.0.1 localhost\n33.11.22.44 foo.example.com foo\n::1 localhost ip6-localhost ip6-loopback\nff02::1 ip6-allnodes\nff02::2 ip6-allrouters\n",
resolvConf: "nameserver 127.0.0.53\n\nsearch example.com\n",
resolvGlobalConf: "nameserver 169.254.116.108\n\nsearch example.com\n",
resolvConf: "nameserver 127.0.0.53\n\nsearch foo.example.com\n",
resolvGlobalConf: "nameserver 169.254.116.108\n\nsearch foo.example.com\n",
},
)
}
Expand Down
55 changes: 43 additions & 12 deletions internal/app/machined/pkg/controllers/network/resolver_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"context"
"fmt"
"net/netip"
"slices"

"github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/resource"
Expand Down Expand Up @@ -42,6 +43,12 @@ func (ctrl *ResolverConfigController) Inputs() []controller.Input {
ID: optional.Some(config.V1Alpha1ID),
Kind: controller.InputWeak,
},
{
Namespace: network.NamespaceName,
Type: network.HostnameStatusType,
ID: optional.Some(network.HostnameID),
Kind: controller.InputWeak,
},
}
}

Expand Down Expand Up @@ -81,8 +88,20 @@ func (ctrl *ResolverConfigController) Run(ctx context.Context, r controller.Runt

var specs []network.ResolverSpecSpec

hostnameStatus, err := safe.ReaderGetByID[*network.HostnameStatus](ctx, r, network.HostnameID)
if err != nil {
if !state.IsNotFoundError(err) {
return fmt.Errorf("error getting hostname status: %w", err)
}
}

var hostnameStatusSpec *network.HostnameStatusSpec
if hostnameStatus != nil {
hostnameStatusSpec = hostnameStatus.TypedSpec()
}

// defaults
specs = append(specs, ctrl.getDefault())
specs = append(specs, ctrl.getDefault(cfgProvider, hostnameStatusSpec))

// parse kernel cmdline for the default gateway
cmdlineServers := ctrl.parseCmdline(logger)
Expand All @@ -92,9 +111,7 @@ func (ctrl *ResolverConfigController) Run(ctx context.Context, r controller.Runt

// parse machine configuration for specs
if cfgProvider != nil {
configServers := ctrl.parseMachineConfiguration(logger, cfgProvider)

if configServers.DNSServers != nil {
if configServers, ok := ctrl.parseMachineConfiguration(logger, cfgProvider); ok {
specs = append(specs, configServers)
}
}
Expand Down Expand Up @@ -159,10 +176,20 @@ func (ctrl *ResolverConfigController) apply(ctx context.Context, r controller.Ru
return ids, nil
}

func (ctrl *ResolverConfigController) getDefault() (spec network.ResolverSpecSpec) {
func (ctrl *ResolverConfigController) getDefault(cfg talosconfig.Config, hostnameStatus *network.HostnameStatusSpec) (spec network.ResolverSpecSpec) {
spec.DNSServers = []netip.Addr{netip.MustParseAddr(constants.DefaultPrimaryResolver), netip.MustParseAddr(constants.DefaultSecondaryResolver)}
spec.ConfigLayer = network.ConfigDefault

if cfg == nil ||
cfg.Machine() == nil ||
cfg.Machine().Network().DisableSearchDomain() ||
hostnameStatus == nil ||
hostnameStatus.Domainname == "" {
return spec
}

spec.SearchDomains = []string{hostnameStatus.FQDN()}

return spec
}

Expand All @@ -188,25 +215,29 @@ func (ctrl *ResolverConfigController) parseCmdline(logger *zap.Logger) (spec net
return spec
}

func (ctrl *ResolverConfigController) parseMachineConfiguration(logger *zap.Logger, cfgProvider talosconfig.Config) (spec network.ResolverSpecSpec) {
func (ctrl *ResolverConfigController) parseMachineConfiguration(logger *zap.Logger, cfgProvider talosconfig.Config) (network.ResolverSpecSpec, bool) {
var spec network.ResolverSpecSpec

resolvers := cfgProvider.Machine().Network().Resolvers()
searchDomains := cfgProvider.Machine().Network().SearchDomains()

if len(resolvers) == 0 {
return
if len(resolvers) == 0 && len(searchDomains) == 0 {
return spec, false
}

for i := range resolvers {
server, err := netip.ParseAddr(resolvers[i])
for _, resolver := range resolvers {
server, err := netip.ParseAddr(resolver)
if err != nil {
logger.Warn("failed to parse DNS server", zap.String("server", resolvers[i]), zap.Error(err))
logger.Warn("failed to parse DNS server", zap.String("server", resolver), zap.Error(err))

continue
}

spec.DNSServers = append(spec.DNSServers, server)
}

spec.SearchDomains = slices.Clone(searchDomains)
spec.ConfigLayer = network.ConfigMachineConfiguration

return spec
return spec, true
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ func (suite *ResolverConfigSuite) TestMachineConfiguration() {
MachineConfig: &v1alpha1.MachineConfig{
MachineNetwork: &v1alpha1.NetworkConfig{
NameServers: []string{"2.2.2.2", "3.3.3.3"},
Searches: []string{"example.com", "example.org"},
},
},
ClusterConfig: &v1alpha1.ClusterConfig{
Expand All @@ -177,11 +178,17 @@ func (suite *ResolverConfigSuite) TestMachineConfiguration() {
netip.MustParseAddr("3.3.3.3"),
}, r.TypedSpec().DNSServers,
)

asrt.Equal(
[]string{"example.com", "example.org"},
r.TypedSpec().SearchDomains,
)
},
)

ctest.UpdateWithConflicts(suite, cfg, func(r *config.MachineConfig) error {
r.Container().RawV1Alpha1().MachineConfig.MachineNetwork.NameServers = nil
r.Container().RawV1Alpha1().MachineConfig.MachineNetwork.Searches = nil

return nil
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ func (ctrl *ResolverMergeController) Run(ctx context.Context, r controller.Runti
for res := range list.All() {
spec := res.TypedSpec()

final.SearchDomains = slices.Insert(final.SearchDomains, 0, spec.SearchDomains...)

if spec.ConfigLayer == final.ConfigLayer {
// simply append server lists on the same layer
final.DNSServers = append(final.DNSServers, spec.DNSServers...)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,9 @@ func (suite *ResolverMergeSuite) TestMerge() {

static := network.NewResolverSpec(network.ConfigNamespaceName, "configuration/resolvers")
*static.TypedSpec() = network.ResolverSpecSpec{
DNSServers: []netip.Addr{netip.MustParseAddr("2.2.2.2")},
ConfigLayer: network.ConfigMachineConfiguration,
DNSServers: []netip.Addr{netip.MustParseAddr("2.2.2.2")},
SearchDomains: []string{"example.com", "example.org", "example.net"},
ConfigLayer: network.ConfigMachineConfiguration,
}

for _, res := range []resource.Resource{def, dhcp1, dhcp2, static} {
Expand All @@ -105,6 +106,7 @@ func (suite *ResolverMergeSuite) TestMerge() {
"resolvers",
}, func(r *network.ResolverSpec, asrt *assert.Assertions) {
asrt.Equal(*static.TypedSpec(), *r.TypedSpec())
asrt.Equal([]string{"example.com", "example.org", "example.net"}, r.TypedSpec().SearchDomains)
},
)

Expand All @@ -114,7 +116,7 @@ func (suite *ResolverMergeSuite) TestMerge() {
[]string{
"resolvers",
}, func(r *network.ResolverSpec, asrt *assert.Assertions) {
asrt.Equal(r.TypedSpec().DNSServers, []netip.Addr{netip.MustParseAddr("1.1.2.0"), netip.MustParseAddr("1.1.2.1")})
asrt.Equal([]netip.Addr{netip.MustParseAddr("1.1.2.0"), netip.MustParseAddr("1.1.2.1")}, r.TypedSpec().DNSServers)
},
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,15 @@ func (ctrl *ResolverSpecController) Run(ctx context.Context, r controller.Runtim
return fmt.Errorf("error removing finalizer: %w", err)
}
case resource.PhaseRunning:
logger.Info("setting resolvers", zap.Stringers("resolvers", spec.TypedSpec().DNSServers))
logger.Info(
"setting resolvers",
zap.Stringers("resolvers", spec.TypedSpec().DNSServers),
zap.Strings("searchDomains", spec.TypedSpec().SearchDomains),
)

if err = safe.WriterModify(ctx, r, network.NewResolverStatus(network.NamespaceName, spec.Metadata().ID()), func(r *network.ResolverStatus) error {
r.TypedSpec().DNSServers = spec.TypedSpec().DNSServers
r.TypedSpec().SearchDomains = spec.TypedSpec().SearchDomains

return nil
}); err != nil {
Expand Down
Loading

0 comments on commit 60e4561

Please sign in to comment.