From 663349ec351360feb3b0d6505d92778765d43081 Mon Sep 17 00:00:00 2001 From: Rohan Prabhu Date: Sun, 13 Apr 2025 13:32:18 +0530 Subject: [PATCH 1/5] feat: add ability to disable sudo and containers --- agent.go | 14 ++++++++ config.go | 51 ++++++++++++++------------- sudo.go | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 24 deletions(-) diff --git a/agent.go b/agent.go index 79c15dd..d18a240 100644 --- a/agent.go +++ b/agent.go @@ -95,6 +95,7 @@ func Run(ctx context.Context, configFilePath string, hostDNSServer DNSServer, if !isActive { config.EgressPolicy = EgressPolicyAudit config.DisableSudo = false + config.DisableSudoAndContainers = false apiclient.DisableTelemetry = true config.DisableFileMonitoring = true WriteAnnotation("StepSecurity Harden Runner is disabled. A subscription is required for private repositories. Please start a free trial at https://www.stepsecurity.io") @@ -247,6 +248,15 @@ func Run(ctx context.Context, configFilePath string, hostDNSServer DNSServer, } } + if config.DisableSudoAndContainers { + err := sudo.disableSudoAndContainers(tempDir) + if err != nil { + WriteAnnotation(fmt.Sprintf("%s Unable to disable sudo and docker %v", StepSecurityAnnotationPrefix, err)) + } else { + WriteLog("disabled sudo and docker") + } + } + if config.DisableSudo { err := sudo.disableSudo(tempDir) if err != nil { @@ -383,6 +393,10 @@ func RevertChanges(iptables *Firewall, nflog AgentNflogger, if err != nil { WriteLog(fmt.Sprintf("Error in reverting sudo changes %v", err)) } + err = sudo.revertDisableSudoAndContainers() + if err != nil { + WriteLog(fmt.Sprintf("Error in reverting sudo and containers changes %v", err)) + } WriteLog("Reverted changes") } diff --git a/config.go b/config.go index f927477..d63f1b3 100644 --- a/config.go +++ b/config.go @@ -11,18 +11,19 @@ import ( ) type config struct { - Repo string - CorrelationId string - RunId string - WorkingDirectory string - APIURL string - OneTimeKey string - Endpoints map[string][]Endpoint - EgressPolicy string - DisableTelemetry bool - DisableSudo bool - DisableFileMonitoring bool - Private bool + Repo string + CorrelationId string + RunId string + WorkingDirectory string + APIURL string + OneTimeKey string + Endpoints map[string][]Endpoint + EgressPolicy string + DisableTelemetry bool + DisableSudo bool + DisableSudoAndContainers bool + DisableFileMonitoring bool + Private bool } type Endpoint struct { @@ -31,18 +32,19 @@ type Endpoint struct { } type configFile struct { - Repo string `json:"repo"` - CorrelationId string `json:"correlation_id"` - RunId string `json:"run_id"` - WorkingDirectory string `json:"working_directory"` - APIURL string `json:"api_url"` - OneTimeKey string `json:"one_time_key"` - AllowedEndpoints string `json:"allowed_endpoints"` - EgressPolicy string `json:"egress_policy"` - DisableTelemetry bool `json:"disable_telemetry"` - DisableSudo bool `json:"disable_sudo"` - DisableFileMonitoring bool `json:"disable_file_monitoring"` - Private bool `json:"private"` + Repo string `json:"repo"` + CorrelationId string `json:"correlation_id"` + RunId string `json:"run_id"` + WorkingDirectory string `json:"working_directory"` + APIURL string `json:"api_url"` + OneTimeKey string `json:"one_time_key"` + AllowedEndpoints string `json:"allowed_endpoints"` + EgressPolicy string `json:"egress_policy"` + DisableTelemetry bool `json:"disable_telemetry"` + DisableSudo bool `json:"disable_sudo"` + DisableSudoAndContainers bool `json:"disable_sudo_and_containers"` + DisableFileMonitoring bool `json:"disable_file_monitoring"` + Private bool `json:"private"` } // init reads the config file for the agent and initializes config settings @@ -67,6 +69,7 @@ func (c *config) init(configFilePath string) error { c.EgressPolicy = configFile.EgressPolicy c.DisableTelemetry = configFile.DisableTelemetry c.DisableSudo = configFile.DisableSudo + c.DisableSudoAndContainers = configFile.DisableSudoAndContainers c.DisableFileMonitoring = configFile.DisableFileMonitoring c.Private = configFile.Private c.OneTimeKey = configFile.OneTimeKey diff --git a/sudo.go b/sudo.go index 4be2e63..c976af6 100644 --- a/sudo.go +++ b/sudo.go @@ -3,6 +3,8 @@ package main import ( "fmt" "os" + "os/exec" + "os/user" "path" ) @@ -40,3 +42,103 @@ func (s *Sudo) revertDisableSudo() error { return nil } + +func (s *Sudo) disableSudoAndContainers(tempDir string) error { + + // Remove socket permissions if they exist + s.removeSocketPermissions() + + // Remove user from docker group + if err := s.removeUserFromDockerGroup(); err != nil { + return err + } + + err := s.disableSudo(tempDir) + if err != nil { + return err + } + + return nil +} + +// removeSocketPermissions removes permissions from Docker and containerd sockets if they exist +func (s *Sudo) removeSocketPermissions() { + // Check and remove docker.sock permissions if it exists + if _, err := os.Stat("/var/run/docker.sock"); err == nil { + cmd := exec.Command("sudo", "chmod", "000", "/var/run/docker.sock") + cmd.Run() + } + + // Check and remove containerd.sock permissions if it exists + if _, err := os.Stat("/run/containerd/containerd.sock"); err == nil { + cmd := exec.Command("sudo", "chmod", "000", "/run/containerd/containerd.sock") + cmd.Run() + } +} + +// removeUserFromDockerGroup removes the current user from the docker group +func (s *Sudo) removeUserFromDockerGroup() error { + currentUser, err := user.Current() + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } + + cmd := exec.Command("sudo", "gpasswd", "-d", currentUser.Username, "docker") + output, err := cmd.CombinedOutput() + if err != nil { + // It's okay if the user is not in the docker group + if len(output) > 0 { + // Check if the error is because the user is not a member + if fmt.Sprintf("%s", output) != fmt.Sprintf("gpasswd: user '%s' is not a member of 'docker'\n", currentUser.Username) { + return fmt.Errorf("error removing user from docker group: %v, output: %s", err, output) + } + } + } + return nil +} + +// revertDisableSudoAndContainers reverts the changes made by disableSudoAndContainers +func (s *Sudo) revertDisableSudoAndContainers() error { + // Step 1: Restore the sudoers file from backup + s.revertDisableSudo() + + // Step 2: Restore socket permissions + s.restoreSocketPermissions() + + // Step 3: Add user back to docker group + if err := s.addUserToDockerGroup(); err != nil { + return fmt.Errorf("error adding user back to docker group: %v", err) + } + + return nil +} + +// restoreSocketPermissions restores permissions to Docker and containerd sockets +func (s *Sudo) restoreSocketPermissions() { + // Check if docker socket exists before restoring + if _, err := os.Stat("/var/run/docker.sock"); err == nil { + cmd := exec.Command("sudo", "chmod", "660", "/var/run/docker.sock") + cmd.Run() + } + + // Check if containerd socket exists before restoring + if _, err := os.Stat("/run/containerd/containerd.sock"); err == nil { + cmd := exec.Command("sudo", "chmod", "660", "/run/containerd/containerd.sock") + cmd.Run() + } +} + +// addUserToDockerGroup adds the current user back to the docker group +func (s *Sudo) addUserToDockerGroup() error { + currentUser, err := user.Current() + if err != nil { + return fmt.Errorf("error getting current user: %v", err) + } + + cmd := exec.Command("sudo", "gpasswd", "-a", currentUser.Username, "docker") + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("error adding user back to docker group: %v, output: %s", err, output) + } + return nil +} From 35ee5f0efbde1a3fce4e25afd6d4ca006e68bdcb Mon Sep 17 00:00:00 2001 From: Rohan Prabhu Date: Sun, 13 Apr 2025 23:10:03 +0530 Subject: [PATCH 2/5] fix: update user for docker group fix: function sequencing --- sudo.go | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/sudo.go b/sudo.go index c976af6..92e7ec7 100644 --- a/sudo.go +++ b/sudo.go @@ -6,6 +6,7 @@ import ( "os/exec" "os/user" "path" + "strings" ) type Sudo struct { @@ -14,6 +15,7 @@ type Sudo struct { const ( sudoersFile = "/etc/sudoers.d/runner" + runnerUser = "runner" ) func (s *Sudo) disableSudo(tempDir string) error { @@ -47,17 +49,24 @@ func (s *Sudo) disableSudoAndContainers(tempDir string) error { // Remove socket permissions if they exist s.removeSocketPermissions() + var errstrings []string // Remove user from docker group if err := s.removeUserFromDockerGroup(); err != nil { - return err + WriteLog(fmt.Sprintf("error removing user from docker group: %v", err)) + errstrings = append(errstrings, err.Error()) } err := s.disableSudo(tempDir) if err != nil { - return err + WriteLog(fmt.Sprintf("error disabling sudo: %v", err)) + errstrings = append(errstrings, err.Error()) } + //flatten errs + if len(errstrings) > 0 { + return fmt.Errorf("error disabling sudo and containers: %s", strings.Join(errstrings, "\n")) + } return nil } @@ -66,30 +75,34 @@ func (s *Sudo) removeSocketPermissions() { // Check and remove docker.sock permissions if it exists if _, err := os.Stat("/var/run/docker.sock"); err == nil { cmd := exec.Command("sudo", "chmod", "000", "/var/run/docker.sock") - cmd.Run() + err := cmd.Run() + if err != nil { + WriteLog(fmt.Sprintf("error removing docker.sock permissions: %v", err)) + } } // Check and remove containerd.sock permissions if it exists if _, err := os.Stat("/run/containerd/containerd.sock"); err == nil { cmd := exec.Command("sudo", "chmod", "000", "/run/containerd/containerd.sock") - cmd.Run() + err := cmd.Run() + if err != nil { + WriteLog(fmt.Sprintf("error removing containerd.sock permissions: %v", err)) + } } } // removeUserFromDockerGroup removes the current user from the docker group func (s *Sudo) removeUserFromDockerGroup() error { - currentUser, err := user.Current() - if err != nil { - return fmt.Errorf("error getting current user: %v", err) - } - cmd := exec.Command("sudo", "gpasswd", "-d", currentUser.Username, "docker") + cmd := exec.Command("sudo", "gpasswd", "-d", runnerUser, "docker") output, err := cmd.CombinedOutput() if err != nil { // It's okay if the user is not in the docker group + WriteLog(fmt.Sprintf("error removing user from docker group: %v", err)) if len(output) > 0 { // Check if the error is because the user is not a member - if fmt.Sprintf("%s", output) != fmt.Sprintf("gpasswd: user '%s' is not a member of 'docker'\n", currentUser.Username) { + WriteLog(fmt.Sprintf("error removing user from docker group: output: %s", output)) + if fmt.Sprintf("%s", output) != fmt.Sprintf("gpasswd: user '%s' is not a member of 'docker'\n", runnerUser) { return fmt.Errorf("error removing user from docker group: %v, output: %s", err, output) } } From 6e91234d002f1f6d23a5fcd1608902044d40e00b Mon Sep 17 00:00:00 2001 From: Rohan Prabhu Date: Mon, 14 Apr 2025 09:00:43 +0530 Subject: [PATCH 3/5] feat: uninstall docker --- agent.go | 4 ++++ sudo.go | 73 +++++++++++++++++++++++++++++++++++++------------------- 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/agent.go b/agent.go index d18a240..4196721 100644 --- a/agent.go +++ b/agent.go @@ -134,6 +134,10 @@ func Run(ctx context.Context, configFilePath string, hostDNSServer DNSServer, sudo := Sudo{} var ipAddressEndpoints []ipAddressEndpoint + if config.DisableSudoAndContainers { + go sudo.uninstallDocker() + } + // hydrate dns cache if config.EgressPolicy == EgressPolicyBlock { for domainName, endpoints := range allowedEndpoints { diff --git a/sudo.go b/sudo.go index 92e7ec7..7a4b446 100644 --- a/sudo.go +++ b/sudo.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "fmt" "os" "os/exec" @@ -47,16 +48,12 @@ func (s *Sudo) revertDisableSudo() error { func (s *Sudo) disableSudoAndContainers(tempDir string) error { + s.removeDockerDirectoriesAndFiles() + // Remove socket permissions if they exist s.removeSocketPermissions() var errstrings []string - // Remove user from docker group - if err := s.removeUserFromDockerGroup(); err != nil { - WriteLog(fmt.Sprintf("error removing user from docker group: %v", err)) - errstrings = append(errstrings, err.Error()) - } - err := s.disableSudo(tempDir) if err != nil { WriteLog(fmt.Sprintf("error disabling sudo: %v", err)) @@ -91,25 +88,6 @@ func (s *Sudo) removeSocketPermissions() { } } -// removeUserFromDockerGroup removes the current user from the docker group -func (s *Sudo) removeUserFromDockerGroup() error { - - cmd := exec.Command("sudo", "gpasswd", "-d", runnerUser, "docker") - output, err := cmd.CombinedOutput() - if err != nil { - // It's okay if the user is not in the docker group - WriteLog(fmt.Sprintf("error removing user from docker group: %v", err)) - if len(output) > 0 { - // Check if the error is because the user is not a member - WriteLog(fmt.Sprintf("error removing user from docker group: output: %s", output)) - if fmt.Sprintf("%s", output) != fmt.Sprintf("gpasswd: user '%s' is not a member of 'docker'\n", runnerUser) { - return fmt.Errorf("error removing user from docker group: %v, output: %s", err, output) - } - } - } - return nil -} - // revertDisableSudoAndContainers reverts the changes made by disableSudoAndContainers func (s *Sudo) revertDisableSudoAndContainers() error { // Step 1: Restore the sudoers file from backup @@ -155,3 +133,48 @@ func (s *Sudo) addUserToDockerGroup() error { } return nil } + +func run(cmd string, args ...string) { + WriteLog(fmt.Sprintf("Running: %s %v", cmd, args)) + c := exec.Command(cmd, args...) + + stdout, _ := c.StdoutPipe() + stderr, _ := c.StderrPipe() + + if err := c.Start(); err != nil { + WriteLog(fmt.Sprintf("Failed to start command: %s", err)) + } + + go func() { + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + WriteLog(scanner.Text()) + } + }() + + // Stream stderr + go func() { + scanner := bufio.NewScanner(stderr) + for scanner.Scan() { + WriteLog(scanner.Text()) + } + }() + + if err := c.Wait(); err != nil { + WriteLog(fmt.Sprintf("Command failed: %v", err)) + } +} + +func (s *Sudo) uninstallDocker() error { + run("sudo", "apt-get", "purge", "-y", + "docker-ce", "docker-ce-cli", "containerd.io") + return nil +} + +func (s *Sudo) removeDockerDirectoriesAndFiles() error { + run("sudo", "rm", "-rf", "/var/lib/docker") + run("sudo", "rm", "-rf", "/var/lib/containerd") + run("sudo", "rm", "-f", "/etc/apt/sources.list.d/docker.list") + run("sudo", "rm", "-f", "/etc/apt/keyrings/docker.asc") + return nil +} From 731bcf010343c7ea09b36f77ba886bceb080db5f Mon Sep 17 00:00:00 2001 From: Rohan Prabhu Date: Mon, 14 Apr 2025 09:58:10 +0530 Subject: [PATCH 4/5] feat: remove revert --- agent.go | 4 ---- sudo.go | 47 ----------------------------------------------- 2 files changed, 51 deletions(-) diff --git a/agent.go b/agent.go index 4196721..69ee51b 100644 --- a/agent.go +++ b/agent.go @@ -397,10 +397,6 @@ func RevertChanges(iptables *Firewall, nflog AgentNflogger, if err != nil { WriteLog(fmt.Sprintf("Error in reverting sudo changes %v", err)) } - err = sudo.revertDisableSudoAndContainers() - if err != nil { - WriteLog(fmt.Sprintf("Error in reverting sudo and containers changes %v", err)) - } WriteLog("Reverted changes") } diff --git a/sudo.go b/sudo.go index 7a4b446..31f29f0 100644 --- a/sudo.go +++ b/sudo.go @@ -5,7 +5,6 @@ import ( "fmt" "os" "os/exec" - "os/user" "path" "strings" ) @@ -88,52 +87,6 @@ func (s *Sudo) removeSocketPermissions() { } } -// revertDisableSudoAndContainers reverts the changes made by disableSudoAndContainers -func (s *Sudo) revertDisableSudoAndContainers() error { - // Step 1: Restore the sudoers file from backup - s.revertDisableSudo() - - // Step 2: Restore socket permissions - s.restoreSocketPermissions() - - // Step 3: Add user back to docker group - if err := s.addUserToDockerGroup(); err != nil { - return fmt.Errorf("error adding user back to docker group: %v", err) - } - - return nil -} - -// restoreSocketPermissions restores permissions to Docker and containerd sockets -func (s *Sudo) restoreSocketPermissions() { - // Check if docker socket exists before restoring - if _, err := os.Stat("/var/run/docker.sock"); err == nil { - cmd := exec.Command("sudo", "chmod", "660", "/var/run/docker.sock") - cmd.Run() - } - - // Check if containerd socket exists before restoring - if _, err := os.Stat("/run/containerd/containerd.sock"); err == nil { - cmd := exec.Command("sudo", "chmod", "660", "/run/containerd/containerd.sock") - cmd.Run() - } -} - -// addUserToDockerGroup adds the current user back to the docker group -func (s *Sudo) addUserToDockerGroup() error { - currentUser, err := user.Current() - if err != nil { - return fmt.Errorf("error getting current user: %v", err) - } - - cmd := exec.Command("sudo", "gpasswd", "-a", currentUser.Username, "docker") - output, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("error adding user back to docker group: %v, output: %s", err, output) - } - return nil -} - func run(cmd string, args ...string) { WriteLog(fmt.Sprintf("Running: %s %v", cmd, args)) c := exec.Command(cmd, args...) From 93890c017a84e217d3bd2edd5f024f0afd55a835 Mon Sep 17 00:00:00 2001 From: Rohan Prabhu Date: Mon, 14 Apr 2025 09:59:03 +0530 Subject: [PATCH 5/5] fix: change to WriteLog --- agent.go | 2 +- sudo.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/agent.go b/agent.go index 69ee51b..a52b9d4 100644 --- a/agent.go +++ b/agent.go @@ -255,7 +255,7 @@ func Run(ctx context.Context, configFilePath string, hostDNSServer DNSServer, if config.DisableSudoAndContainers { err := sudo.disableSudoAndContainers(tempDir) if err != nil { - WriteAnnotation(fmt.Sprintf("%s Unable to disable sudo and docker %v", StepSecurityAnnotationPrefix, err)) + WriteLog(fmt.Sprintf("%s Unable to disable sudo and docker %v", StepSecurityAnnotationPrefix, err)) } else { WriteLog("disabled sudo and docker") } diff --git a/sudo.go b/sudo.go index 31f29f0..5a4c226 100644 --- a/sudo.go +++ b/sudo.go @@ -119,6 +119,7 @@ func run(cmd string, args ...string) { } func (s *Sudo) uninstallDocker() error { + WriteLog("Uninstalling docker") run("sudo", "apt-get", "purge", "-y", "docker-ce", "docker-ce-cli", "containerd.io") return nil