diff --git a/.github/workflows/int.yml b/.github/workflows/int.yml index c33cf43..a700d8a 100644 --- a/.github/workflows/int.yml +++ b/.github/workflows/int.yml @@ -23,7 +23,7 @@ jobs: - name: Set up Go uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 with: - go-version: 1.26.2 + go-version: 1.27.3 - name: Configure .netrc run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 902ab60..0df8365 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: - name: Set up Go uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 with: - go-version: 1.26.2 + go-version: 1.27.3 - name: Configure .netrc run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6dd66b5..22b99bb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,7 +21,7 @@ jobs: - name: Set up Go uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 with: - go-version: 1.26.2 + go-version: 1.27.3 - name: Configure .netrc run: | diff --git a/agent.go b/agent.go index 152417a..9ae1ef9 100644 --- a/agent.go +++ b/agent.go @@ -82,6 +82,10 @@ func Run(ctx context.Context, configFilePath string, hostDNSServer DNSServer, WriteLog("\n") globalFlags := GetGlobalFeatureFlags() + if globalFlags.RevertGithubDNSExperiment { + RevertGithubDNSExperiment() + } + globalBlocklistResponse, err := apiclient.getGlobalBlocklist() globalBlocklist := NewGlobalBlocklist(globalBlocklistResponse) if err != nil { diff --git a/github_dns_experiment.go b/github_dns_experiment.go new file mode 100644 index 0000000..fe755a1 --- /dev/null +++ b/github_dns_experiment.go @@ -0,0 +1,112 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "strings" +) + +// GitHub is experimenting with a DNS resolver and an outbound proxy on hosted runners, +// which bypasses the agent's DNS server and results in allowed connections being dropped. +// ref: https://github.com/step-security/harden-runner/issues/708 +const ( + githubDNSResolverAddress = "127.0.0.100" + etcResolvConfPath = "/etc/resolv.conf" + stubResolvConfPath = "/run/systemd/resolve/stub-resolv.conf" +) + +var githubDNSExperimentServices = []string{"egress-dns-resolver.service", "egress-outbound-proxy.service"} + +// systemd-resolved reads drop-ins from both /etc and /run +var githubDNSDropInPaths = []string{"/etc/systemd/resolved.conf.d/90-egress-proxy.conf", "/run/systemd/resolved.conf.d/90-egress-proxy.conf"} + +// RevertGithubDNSExperiment reverts github's dns experiment, if it is present on the runner +func RevertGithubDNSExperiment() { + if !isGithubDNSExperimentPresent() { + WriteLog("[github-dns-experiment] not detected") + return + } + + WriteLog("[github-dns-experiment] detected, reverting") + if err := revertGithubDNSExperiment(); err != nil { + WriteLog(fmt.Sprintf("[github-dns-experiment] error reverting: %v", err)) + return + } + WriteLog("[github-dns-experiment] reverted") +} + +func isGithubDNSExperimentPresent() bool { + for _, dropInPath := range githubDNSDropInPaths { + if _, err := os.Stat(dropInPath); err == nil { + return true + } + } + + for _, service := range githubDNSExperimentServices { + if isServiceActive(service) { + return true + } + } + + resolvConf, err := os.ReadFile(etcResolvConfPath) + return err == nil && strings.Contains(string(resolvConf), githubDNSResolverAddress) +} + +func revertGithubDNSExperiment() error { + // repoint dns away from github's resolver before stopping it, so a failure at any step leaves dns working + for _, dropInPath := range githubDNSDropInPaths { + if err := os.Remove(dropInPath); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("error removing %s: %v", dropInPath, err) + } + } + + if err := exec.Command("sudo", "systemctl", "restart", "systemd-resolved").Run(); err != nil { + return fmt.Errorf("error restarting systemd-resolved: %v", err) + } + + if err := linkResolvConfToStub(); err != nil { + return err + } + + // github's services are no longer in the dns path, so failing to stop them is not fatal + for _, service := range githubDNSExperimentServices { + if !isServiceActive(service) { + continue + } + if err := exec.Command("sudo", "systemctl", "stop", service).Run(); err != nil { + WriteLog(fmt.Sprintf("[github-dns-experiment] error stopping %s: %v", service, err)) + } + } + + if err := exec.Command("sudo", "resolvectl", "flush-caches").Run(); err != nil { + WriteLog(fmt.Sprintf("[github-dns-experiment] error flushing dns caches: %v", err)) + } + + return nil +} + +// linkResolvConfToStub atomically points /etc/resolv.conf to systemd-resolved's stub resolver +func linkResolvConfToStub() error { + if _, err := os.Stat(stubResolvConfPath); err != nil { + return fmt.Errorf("error finding %s: %v", stubResolvConfPath, err) + } + + tmpPath := etcResolvConfPath + ".tmp" + os.Remove(tmpPath) + if err := os.Symlink(stubResolvConfPath, tmpPath); err != nil { + return fmt.Errorf("error creating %s: %v", tmpPath, err) + } + + if err := os.Rename(tmpPath, etcResolvConfPath); err != nil { + os.Remove(tmpPath) + return fmt.Errorf("error linking %s to %s: %v", etcResolvConfPath, stubResolvConfPath, err) + } + + return nil +} + +func isServiceActive(service string) bool { + output, _ := exec.Command("systemctl", "is-active", service).Output() + return strings.TrimSpace(string(output)) == "active" +} diff --git a/global_feature_flags.go b/global_feature_flags.go index 81201aa..eca7ce5 100644 --- a/global_feature_flags.go +++ b/global_feature_flags.go @@ -15,6 +15,7 @@ type GlobalFeatureFlags struct { EnableArmour bool `json:"enable_armour"` EnableCustomDetectionRules bool `json:"enable_custom_detection_rules"` DisableKillEnforcement bool `json:"disable_kill_enforcement,omitempty"` + RevertGithubDNSExperiment bool `json:"revert_github_dns_experiment,omitempty"` } // GlobalFeatureFlagManager manages fetching and caching of global feature flags. diff --git a/go.mod b/go.mod index 267f570..613714b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/step-security/agent -go 1.26.2 +go 1.27.3 require ( github.com/coreos/go-iptables v0.6.0 diff --git a/release-monitor.yml b/release-monitor.yml index dbaaf08..84d4614 100644 --- a/release-monitor.yml +++ b/release-monitor.yml @@ -7,7 +7,7 @@ release-process: - artifact: agent_{{.Version}}_linux_amd64.tar.gz binary: agent build-command: go build -trimpath -ldflags="-s -w -X main.version={{.Version}} -X main.commit={{.FullCommit}} -X main.date=123 -X main.ReleaseTag={{.Tag}}" - go-version: 1.19.8 + go-version: 1.27.3 pipeline: github-action: repo: step-security/agent