Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmd/generate-config/config/config-openapi-spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,11 @@
"default": "example.com",
"example": "microshift.example.com"
},
"configFile": {
"description": "configFile is the path to a custom CoreDNS Corefile on the host filesystem.\nWhen set, MicroShift uses this file as the Corefile in the dns-default ConfigMap,\nfully replacing the default template-rendered configuration.\nChanges to this file are detected at runtime and applied without restarting MicroShift.\nMutually exclusive with dns.hosts: setting both causes a startup error.",
"type": "string",
"example": "/etc/microshift/dns/Corefile"
},
"hosts": {
"description": "Hosts contains configuration for the hosts file.",
"type": "object",
Expand Down
2 changes: 2 additions & 0 deletions docs/user/howto_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ debugging:
logLevel: ""
dns:
baseDomain: ""
configFile: ""
hosts:
file: ""
status: ""
Expand Down Expand Up @@ -198,6 +199,7 @@ debugging:
logLevel: Normal
dns:
baseDomain: example.com
configFile: ""
hosts:
file: /etc/hosts
status: Disabled
Expand Down
8 changes: 8 additions & 0 deletions packaging/microshift/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ dns:
# example:
# microshift.example.com
baseDomain: example.com
# configFile is the path to a custom CoreDNS Corefile on the host filesystem.
# When set, MicroShift uses this file as the Corefile in the dns-default ConfigMap,
# fully replacing the default template-rendered configuration.
# Changes to this file are detected at runtime and applied without restarting MicroShift.
# Mutually exclusive with dns.hosts: setting both causes a startup error.
# example:
# /etc/microshift/dns/Corefile
configFile: ""
# Hosts contains configuration for the hosts file.
hosts:
# File is the path to the hosts file to monitor.
Expand Down
1 change: 1 addition & 0 deletions pkg/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ func RunMicroshift(cfg *config.Config) error {
util.Must(m.AddService(controllers.NewClusterID(cfg)))
util.Must(m.AddService(controllers.NewTelemetryManager(cfg)))
util.Must(m.AddService(controllers.NewHostsWatcherManager(cfg)))
util.Must(m.AddService(controllers.NewDNSConfigurationWatcherManager(cfg)))
util.Must(m.AddService(gdp.NewGenericDevicePlugin(cfg)))

// Storing and clearing the env, so other components don't send the READY=1 until MicroShift is fully ready
Expand Down
30 changes: 22 additions & 8 deletions pkg/components/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,10 +291,9 @@ func startDNSController(ctx context.Context, cfg *config.Config, kubeconfigPath
"components/openshift-dns/dns/service-account.yaml",
"components/openshift-dns/node-resolver/service-account.yaml",
}
cm = []string{
"components/openshift-dns/dns/configmap.yaml",
}
svc = []string{
cm = "components/openshift-dns/dns/configmap.yaml"
cmList = []string{cm}
svc = []string{
"components/openshift-dns/dns/service.yaml",
}
)
Expand All @@ -303,9 +302,10 @@ func startDNSController(ctx context.Context, cfg *config.Config, kubeconfigPath
return err
}

hostsEnabled := cfg.DNS.Hosts.Status == config.HostsStatusEnabled
extraParams := assets.RenderParams{
"ClusterIP": cfg.Network.DNS,
"HostsEnabled": cfg.DNS.Hosts.Status == config.HostsStatusEnabled,
"HostsEnabled": hostsEnabled,
}

if err := assets.ApplyServices(ctx, svc, renderTemplate, renderParamsFromConfig(cfg, extraParams), kubeconfigPath); err != nil {
Expand Down Expand Up @@ -333,10 +333,24 @@ func startDNSController(ctx context.Context, cfg *config.Config, kubeconfigPath
klog.Warningf("Failed to apply serviceAccount %v %v", sa, err)
return err
}
if err := assets.ApplyConfigMaps(ctx, cm, renderTemplate, renderParamsFromConfig(cfg, extraParams), kubeconfigPath); err != nil {
klog.Warningf("Failed to apply configMap %v %v", cm, err)
return err

if cfg.DNS.ConfigFile != "" {
corefileContent, err := os.ReadFile(cfg.DNS.ConfigFile)
if err != nil {
klog.Warningf("Failed to read custom DNS config file %s: %v", cfg.DNS.ConfigFile, err)
return err
}
if err := assets.ApplyConfigMapWithData(ctx, cm, map[string]string{"Corefile": string(corefileContent)}, kubeconfigPath); err != nil {
klog.Warningf("Failed to apply custom DNS configMap: %v", err)
return err
}
Comment thread
pacevedom marked this conversation as resolved.
} else {
if err := assets.ApplyConfigMaps(ctx, cmList, renderTemplate, renderParamsFromConfig(cfg, extraParams), kubeconfigPath); err != nil {
klog.Warningf("Failed to apply configMap %v %v", cmList, err)
return err
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

if err := assets.ApplyDaemonSets(ctx, apps, renderTemplate, renderParamsFromConfig(cfg, extraParams), kubeconfigPath); err != nil {
klog.Warningf("Failed to apply apps %v %v", apps, err)
return err
Expand Down
3 changes: 3 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ func (c *Config) incorporateUserSettings(u *Config) {
if u.DNS.BaseDomain != "" {
c.DNS.BaseDomain = u.DNS.BaseDomain
}
if u.DNS.ConfigFile != "" {
c.DNS.ConfigFile = u.DNS.ConfigFile
}

if u.Network.CNIPlugin != "" {
c.Network.CNIPlugin = u.Network.CNIPlugin
Expand Down
57 changes: 56 additions & 1 deletion pkg/config/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ type DNS struct {
// +kubebuilder:example=microshift.example.com
BaseDomain string `json:"baseDomain"`

// configFile is the path to a custom CoreDNS Corefile on the host filesystem.
// When set, MicroShift uses this file as the Corefile in the dns-default ConfigMap,
// fully replacing the default template-rendered configuration.
// Changes to this file are detected at runtime and applied without restarting MicroShift.
// Mutually exclusive with dns.hosts: setting both causes a startup error.
// +optional
// +kubebuilder:example="/etc/microshift/dns/Corefile"
ConfigFile string `json:"configFile,omitempty"`

// Hosts contains configuration for the hosts file.
Hosts HostsConfig `json:"hosts,omitempty"`
}
Expand Down Expand Up @@ -59,6 +68,53 @@ func dnsDefaults() DNS {
}

func (t *DNS) validate() error {
if t.ConfigFile != "" && t.Hosts.Status == HostsStatusEnabled {
return fmt.Errorf("dns.configFile and dns.hosts are mutually exclusive")
}

if err := t.validateConfigFile(); err != nil {
return err
}

return t.validateHosts()
}

func (t *DNS) validateConfigFile() error {
if t.ConfigFile == "" {
return nil
}

cleanPath := filepath.Clean(t.ConfigFile)
if !filepath.IsAbs(cleanPath) {
return fmt.Errorf("dns config file path must be absolute: got %s", t.ConfigFile)
}

fi, err := os.Stat(cleanPath)
if os.IsNotExist(err) {
return fmt.Errorf("dns config file %s does not exist", t.ConfigFile)
} else if err != nil {
return fmt.Errorf("error checking dns config file %s: %v", t.ConfigFile, err)
}
if !fi.Mode().IsRegular() {
return fmt.Errorf("dns config file %s must be a regular file", t.ConfigFile)
}

if fi.Size() == 0 {
return fmt.Errorf("dns config file %s is empty", t.ConfigFile)
}

if fi.Size() > 1048576 {
return fmt.Errorf("dns config file %s exceeds 1MiB ConfigMap size limit (got %d bytes)", t.ConfigFile, fi.Size())
}

file, err := os.Open(cleanPath)
if err != nil {
return fmt.Errorf("dns config file %s is not readable: %v", t.ConfigFile, err)
}
return file.Close()
Comment thread
pacevedom marked this conversation as resolved.
}

func (t *DNS) validateHosts() error {
switch t.Hosts.Status {
case HostsStatusEnabled:
if t.Hosts.File == "" {
Expand All @@ -68,7 +124,6 @@ func (t *DNS) validate() error {
cleanPath := filepath.Clean(t.Hosts.File)

fi, err := os.Stat(cleanPath)
// Enforce ConfigMap requirement: the file must not exceed 1MiB, as it will be mounted into a ConfigMap.
if err == nil && fi.Size() > 1048576 {
return fmt.Errorf("hosts file %s exceeds 1MiB ConfigMap (and internal buffer) size limit (got %d bytes)", t.Hosts.File, fi.Size())
}
Expand Down
141 changes: 141 additions & 0 deletions pkg/config/dns_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package config

import (
"fmt"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestDNS_ValidateConfigFile_MutualExclusivity(t *testing.T) {
tmpFile := createTempCorefile(t, ".:5353 { whoami }")

dns := DNS{
ConfigFile: tmpFile,
Hosts: HostsConfig{
Status: HostsStatusEnabled,
File: "/etc/hosts",
},
}
err := dns.validate()
assert.ErrorContains(t, err, "dns.configFile and dns.hosts are mutually exclusive")
}

func TestDNS_ValidateConfigFile_WithHostsDisabled(t *testing.T) {
tmpFile := createTempCorefile(t, ".:5353 { whoami }")

dns := DNS{
ConfigFile: tmpFile,
Hosts: HostsConfig{
Status: HostsStatusDisabled,
},
}
assert.NoError(t, dns.validate())
}

func TestDNS_ValidateConfigFile_EmptyConfigFilePreservesDefault(t *testing.T) {
dns := DNS{
ConfigFile: "",
Hosts: HostsConfig{
Status: HostsStatusDisabled,
},
}
assert.NoError(t, dns.validate())
}

func TestDNS_ValidateConfigFile_NonAbsolutePath(t *testing.T) {
dns := DNS{
ConfigFile: "relative/path/Corefile",
Hosts: HostsConfig{
Status: HostsStatusDisabled,
},
}
err := dns.validate()
assert.ErrorContains(t, err, "dns config file path must be absolute")
}

func TestDNS_ValidateConfigFile_NonExistentFile(t *testing.T) {
dns := DNS{
ConfigFile: "/tmp/nonexistent-corefile-test-12345",
Hosts: HostsConfig{
Status: HostsStatusDisabled,
},
}
err := dns.validate()
assert.ErrorContains(t, err, "does not exist")
}

func TestDNS_ValidateConfigFile_Directory(t *testing.T) {
tmpDir := t.TempDir()

dns := DNS{
ConfigFile: tmpDir,
Hosts: HostsConfig{
Status: HostsStatusDisabled,
},
}
err := dns.validate()
assert.ErrorContains(t, err, "must be a regular file")
}

func TestDNS_ValidateConfigFile_EmptyFile(t *testing.T) {
tmpFile := createTempCorefile(t, "")

dns := DNS{
ConfigFile: tmpFile,
Hosts: HostsConfig{
Status: HostsStatusDisabled,
},
}
err := dns.validate()
assert.ErrorContains(t, err, "is empty")
}

func TestDNS_ValidateConfigFile_ExceedsSizeLimit(t *testing.T) {
tmpDir := t.TempDir()
tmpFile := filepath.Join(tmpDir, "Corefile")
// 1 MiB + 1 byte
data := make([]byte, 1048576+1)
require.NoError(t, os.WriteFile(tmpFile, data, 0644))

dns := DNS{
ConfigFile: tmpFile,
Hosts: HostsConfig{
Status: HostsStatusDisabled,
},
}
err := dns.validate()
assert.ErrorContains(t, err, "exceeds 1MiB ConfigMap size limit")
}

func TestDNS_ValidateConfigFile_ValidFile(t *testing.T) {
tmpFile := createTempCorefile(t, ".:5353 {\n whoami\n reload\n}\n")

dns := DNS{
ConfigFile: tmpFile,
Hosts: HostsConfig{
Status: HostsStatusDisabled,
},
}
assert.NoError(t, dns.validate())
}

func TestDNS_ConfigFile_IncorporatedFromDropIn(t *testing.T) {
tmpFile := createTempCorefile(t, ".:5353 {\n whoami\n reload\n}\n")

yamlConfig := fmt.Sprintf("dns:\n configFile: %s\n", tmpFile)
config, err := getActiveConfigFromYAMLDropins([][]byte{[]byte(yamlConfig)})
require.NoError(t, err)
assert.Equal(t, tmpFile, config.DNS.ConfigFile)
}

func createTempCorefile(t *testing.T, content string) string {
t.Helper()
tmpDir := t.TempDir()
tmpFile := filepath.Join(tmpDir, "Corefile")
require.NoError(t, os.WriteFile(tmpFile, []byte(content), 0644))
return tmpFile
}
Loading