mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-12 02:15:01 +00:00
Export-Source-Commit: 01e89c89ecc67620917c3ac4e38455ca405fa884
This commit is contained in:
+95
-26
@@ -36,6 +36,15 @@ type platformOSConfigState struct {
|
||||
configuredIPv6 bool
|
||||
configuredIPv6Route bool
|
||||
configuredCidrRanges []string
|
||||
configuredDNS *appliedDNSConfig
|
||||
}
|
||||
|
||||
// appliedDNSConfig records what configureDNS last wrote.
|
||||
type appliedDNSConfig struct {
|
||||
// fileContents is the content written to every managed resolver file.
|
||||
fileContents []byte
|
||||
// files holds the sorted absolute paths of the managed resolver files.
|
||||
files []string
|
||||
}
|
||||
|
||||
// platformConfigureOS reconciles host OS state for cfg. It is safe
|
||||
@@ -45,7 +54,7 @@ func platformConfigureOS(ctx context.Context, cfg *osConfig, state *platformOSCo
|
||||
// IPs and routes are cleaned up when the TUN is deleted on exit,
|
||||
// so no removal is needed.
|
||||
if cfg.tunName == "" {
|
||||
if err := configureDNS(ctx, cfg.dnsAddrs, cfg.dnsZones); err != nil {
|
||||
if err := configureDNS(ctx, state, cfg.dnsAddrs, cfg.dnsZones); err != nil {
|
||||
return trace.Wrap(err, "configuring DNS")
|
||||
}
|
||||
*state = platformOSConfigState{}
|
||||
@@ -100,7 +109,7 @@ func platformConfigureOS(ctx context.Context, cfg *osConfig, state *platformOSCo
|
||||
state.configuredIPv6Route = true
|
||||
}
|
||||
|
||||
if err := configureDNS(ctx, cfg.dnsAddrs, cfg.dnsZones); err != nil {
|
||||
if err := configureDNS(ctx, state, cfg.dnsAddrs, cfg.dnsZones); err != nil {
|
||||
return trace.Wrap(err, "configuring DNS")
|
||||
}
|
||||
|
||||
@@ -117,19 +126,38 @@ const resolverFileComment = "# automatically installed by Teleport VNet"
|
||||
|
||||
var resolverPath = filepath.Join("/", "etc", "resolver")
|
||||
|
||||
func configureDNS(ctx context.Context, nameservers []string, zones []string) error {
|
||||
// configureDNS writes resolver files for the zones to /etc/resolver, skipping
|
||||
// the write when nothing changed. A change in nameserver order triggers a rewrite.
|
||||
func configureDNS(ctx context.Context, state *platformOSConfigState, nameservers []string, zones []string) error {
|
||||
if len(nameservers) == 0 {
|
||||
// There are no nameservers so VNet can't handle any DNS zones. Continue
|
||||
// so that any VNet-managed resolver files can be deleted.
|
||||
zones = nil
|
||||
}
|
||||
|
||||
desiredContents := desiredResolverFileContents(nameservers)
|
||||
files := make([]string, 0, len(zones))
|
||||
for _, zone := range zones {
|
||||
files = append(files, filepath.Join(resolverPath, zone))
|
||||
}
|
||||
slices.Sort(files)
|
||||
|
||||
if state.configuredDNS != nil &&
|
||||
bytes.Equal(state.configuredDNS.fileContents, desiredContents) &&
|
||||
slices.Equal(state.configuredDNS.files, files) {
|
||||
// Read the managed files to catch external drift
|
||||
if resolverFilesMatch(ctx, files, desiredContents) {
|
||||
return nil
|
||||
}
|
||||
log.InfoContext(ctx, "Resolver files no longer match the applied DNS configuration, re-applying.")
|
||||
}
|
||||
|
||||
log.DebugContext(ctx, "Configuring DNS.", "nameservers", nameservers, "zones", zones)
|
||||
if err := os.MkdirAll(resolverPath, os.FileMode(0755)); err != nil {
|
||||
return trace.Wrap(err, "creating %s", resolverPath)
|
||||
}
|
||||
|
||||
managedFiles, err := vnetManagedResolverFiles()
|
||||
managedFiles, err := vnetManagedResolverFiles(ctx)
|
||||
if err != nil {
|
||||
return trace.Wrap(err, "finding VNet managed files in /etc/resolver")
|
||||
}
|
||||
@@ -138,18 +166,8 @@ func configureDNS(ctx context.Context, nameservers []string, zones []string) err
|
||||
// errors with one or more of them.
|
||||
var allErrors []error
|
||||
|
||||
var fileContents bytes.Buffer
|
||||
fileContents.WriteString(resolverFileComment)
|
||||
fileContents.WriteByte('\n')
|
||||
for _, nameserver := range nameservers {
|
||||
fileContents.WriteString("nameserver ")
|
||||
fileContents.WriteString(nameserver)
|
||||
fileContents.WriteByte('\n')
|
||||
}
|
||||
|
||||
for _, zone := range zones {
|
||||
fileName := filepath.Join(resolverPath, zone)
|
||||
if err := renameio.WriteFile(fileName, fileContents.Bytes(), 0644); err != nil {
|
||||
for _, fileName := range files {
|
||||
if err := renameio.WriteFile(fileName, desiredContents, 0644); err != nil {
|
||||
allErrors = append(allErrors, trace.Wrap(err, "writing DNS configuration file %s", fileName))
|
||||
} else {
|
||||
// Successfully wrote this file, don't clean it up below.
|
||||
@@ -164,10 +182,45 @@ func configureDNS(ctx context.Context, nameservers []string, zones []string) err
|
||||
}
|
||||
}
|
||||
|
||||
if len(allErrors) == 0 {
|
||||
state.configuredDNS = &appliedDNSConfig{
|
||||
fileContents: desiredContents,
|
||||
files: files,
|
||||
}
|
||||
}
|
||||
return trace.NewAggregate(allErrors...)
|
||||
}
|
||||
|
||||
func vnetManagedResolverFiles() (map[string]struct{}, error) {
|
||||
func desiredResolverFileContents(nameservers []string) []byte {
|
||||
var fileContents bytes.Buffer
|
||||
fileContents.WriteString(resolverFileComment)
|
||||
fileContents.WriteByte('\n')
|
||||
for _, nameserver := range nameservers {
|
||||
fileContents.WriteString("nameserver ")
|
||||
fileContents.WriteString(nameserver)
|
||||
fileContents.WriteByte('\n')
|
||||
}
|
||||
return fileContents.Bytes()
|
||||
}
|
||||
|
||||
// resolverFilesMatch reports whether every managed resolver file still has
|
||||
// the expected contents.
|
||||
func resolverFilesMatch(ctx context.Context, files []string, wantContents []byte) bool {
|
||||
for _, f := range files {
|
||||
contents, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
log.DebugContext(ctx, "Failed to read resolver file.", "file", f, "error", err)
|
||||
return false
|
||||
}
|
||||
if !bytes.Equal(contents, wantContents) {
|
||||
log.DebugContext(ctx, "Resolver file does not have the expected contents.", "file", f)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func vnetManagedResolverFiles(ctx context.Context) (map[string]struct{}, error) {
|
||||
entries, err := os.ReadDir(resolverPath)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "reading %s", resolverPath)
|
||||
@@ -179,18 +232,34 @@ func vnetManagedResolverFiles() (map[string]struct{}, error) {
|
||||
continue
|
||||
}
|
||||
filePath := filepath.Join(resolverPath, entry.Name())
|
||||
file, err := os.Open(filePath)
|
||||
matches, err := fileStartsWithVNetComment(ctx, filePath)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "opening %s", filePath)
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
if scanner.Scan() {
|
||||
if resolverFileComment == scanner.Text() {
|
||||
matchingFiles[filePath] = struct{}{}
|
||||
}
|
||||
if matches {
|
||||
matchingFiles[filePath] = struct{}{}
|
||||
}
|
||||
}
|
||||
return matchingFiles, nil
|
||||
}
|
||||
|
||||
// fileStartsWithVNetComment reports whether the first line of the file at
|
||||
// path is the comment marking a VNet managed resolver file. Files whose
|
||||
// first line cannot be read are treated as not managed by VNet.
|
||||
func fileStartsWithVNetComment(ctx context.Context, path string) (bool, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return false, trace.Wrap(err, "opening %s", path)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
if !scanner.Scan() {
|
||||
if err := scanner.Err(); err != nil {
|
||||
log.DebugContext(ctx, "Failed to read first line of resolver file, treating it as not managed by VNet.",
|
||||
"file", path, "error", err)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return scanner.Text() == resolverFileComment, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user