From dba484903b5ae8bd10020df7fea1faa0c651386a Mon Sep 17 00:00:00 2001 From: Varun Sharma Date: Sun, 23 Oct 2022 19:53:32 -0700 Subject: [PATCH] Release v0.12.0 (#318) Add support for disabling sudo Update file monitoring logging Add subscription check for private repos Fix bugs related to dns config --- agent.go | 47 ++++++++--- agent_test.go | 12 +++ apiclient.go | 29 ++++--- config.go | 41 ++++++---- dnsconfig.go | 40 ++++++---- eventhandler.go | 104 ++++++++++++++----------- procmon.go | 17 ++-- procmon_linux.go | 27 ++++--- sudo.go | 42 ++++++++++ testfiles/agent-allowed-endpoints.json | 3 +- testfiles/agent-disable-sudo.json | 11 +++ testfiles/agent-private-repo.json | 12 +++ 12 files changed, 265 insertions(+), 120 deletions(-) create mode 100644 sudo.go create mode 100644 testfiles/agent-disable-sudo.json create mode 100644 testfiles/agent-private-repo.json diff --git a/agent.go b/agent.go index 73498db..ba083f9 100644 --- a/agent.go +++ b/agent.go @@ -65,15 +65,27 @@ func Run(ctx context.Context, configFilePath string, hostDNSServer DNSServer, return err } - apiclient := &ApiClient{Client: &http.Client{}, APIURL: config.APIURL, DisableTelemetry: config.DisableTelemetry, EgressPolicy: config.EgressPolicy} + apiclient := &ApiClient{Client: &http.Client{Timeout: 3 * time.Second}, APIURL: config.APIURL, DisableTelemetry: config.DisableTelemetry, EgressPolicy: config.EgressPolicy} // TODO: pass in an iowriter/ use log library - WriteLog(fmt.Sprintf("read config \n %v", config)) + WriteLog(fmt.Sprintf("read config \n %+v", config)) WriteLog("\n") WriteLog(fmt.Sprintf("%s %s", StepSecurityLogCorrelationPrefix, config.CorrelationId)) WriteLog("\n") + // if this is a private repo + if config.Private { + isActive := apiclient.getSubscriptionStatus(config.Repo) + if !isActive { + config.EgressPolicy = EgressPolicyAudit + config.DisableSudo = false + apiclient.DisableTelemetry = true + config.DisableFileMonitoring = true + WriteAnnotation("StepSecurity Harden Runner disabled. A subscription is required for private repositories. Please start a free trial at https://stepsecurity.io") + } + } + Cache := InitCache(config.EgressPolicy) allowedEndpoints := addImplicitEndpoints(config.Endpoints, config.DisableTelemetry) @@ -95,13 +107,13 @@ func Run(ctx context.Context, configFilePath string, hostDNSServer DNSServer, // start proc mon if cmd == nil { procMon := &ProcessMonitor{CorrelationId: config.CorrelationId, Repo: config.Repo, - ApiClient: apiclient, WorkingDirectory: config.WorkingDirectory, DNSProxy: &dnsProxy} + ApiClient: apiclient, WorkingDirectory: config.WorkingDirectory, DisableFileMonitoring: config.DisableFileMonitoring, DNSProxy: &dnsProxy} go procMon.MonitorProcesses(errc) WriteLog("started process monitor") } dnsConfig := DnsConfig{} - + sudo := Sudo{} var ipAddressEndpoints []ipAddressEndpoint // hydrate dns cache @@ -112,7 +124,7 @@ func Run(ctx context.Context, configFilePath string, hostDNSServer DNSServer, if err != nil { WriteLog(fmt.Sprintf("Error resolving allowed domain %v", err)) WriteAnnotation(fmt.Sprintf("%s Reverting agent since allowed endpoint %s could not be resolved", StepSecurityAnnotationPrefix, strings.Trim(domainName, "."))) - RevertChanges(iptables, nflog, cmd, resolvdConfigPath, dockerDaemonConfigPath, dnsConfig) + RevertChanges(iptables, nflog, cmd, resolvdConfigPath, dockerDaemonConfigPath, dnsConfig, sudo) return err } for _, endpoint := range endpoints { @@ -126,7 +138,7 @@ func Run(ctx context.Context, configFilePath string, hostDNSServer DNSServer, // Change DNS config on host, causes processes to use agent's DNS proxy if err := dnsConfig.SetDNSServer(cmd, resolvdConfigPath, tempDir); err != nil { WriteLog(fmt.Sprintf("Error setting DNS server %v", err)) - RevertChanges(iptables, nflog, cmd, resolvdConfigPath, dockerDaemonConfigPath, dnsConfig) + RevertChanges(iptables, nflog, cmd, resolvdConfigPath, dockerDaemonConfigPath, dnsConfig, sudo) return err } @@ -136,7 +148,7 @@ func Run(ctx context.Context, configFilePath string, hostDNSServer DNSServer, // Change DNS for docker, causes process in containers to use agent's DNS proxy if err := dnsConfig.SetDockerDNSServer(cmd, dockerDaemonConfigPath, tempDir); err != nil { WriteLog(fmt.Sprintf("Error setting DNS server for docker %v", err)) - RevertChanges(iptables, nflog, cmd, resolvdConfigPath, dockerDaemonConfigPath, dnsConfig) + RevertChanges(iptables, nflog, cmd, resolvdConfigPath, dockerDaemonConfigPath, dnsConfig, sudo) return err } @@ -159,7 +171,7 @@ func Run(ctx context.Context, configFilePath string, hostDNSServer DNSServer, // Add logging to firewall, including NFLOG rules if err := AddAuditRules(iptables); err != nil { WriteLog(fmt.Sprintf("Error adding firewall rules %v", err)) - RevertChanges(iptables, nflog, cmd, resolvdConfigPath, dockerDaemonConfigPath, dnsConfig) + RevertChanges(iptables, nflog, cmd, resolvdConfigPath, dockerDaemonConfigPath, dnsConfig, sudo) return err } @@ -182,13 +194,22 @@ func Run(ctx context.Context, configFilePath string, hostDNSServer DNSServer, if err := addBlockRulesForGitHubHostedRunner(iptables, ipAddressEndpoints); err != nil { WriteLog(fmt.Sprintf("Error setting firewall for allowed domains %v", err)) - RevertChanges(iptables, nflog, cmd, resolvdConfigPath, dockerDaemonConfigPath, dnsConfig) + RevertChanges(iptables, nflog, cmd, resolvdConfigPath, dockerDaemonConfigPath, dnsConfig, sudo) return err } go refreshDNSEntries(ctx, iptables, allowedEndpoints, &dnsProxy) } + if config.DisableSudo { + err := sudo.disableSudo(tempDir) + if err != nil { + WriteAnnotation(fmt.Sprintf("%s Unable to disable sudo %v", StepSecurityAnnotationPrefix, err)) + } else { + WriteLog("disabled sudo") + } + } + WriteLog("done") // Write the status file @@ -200,7 +221,7 @@ func Run(ctx context.Context, configFilePath string, hostDNSServer DNSServer, return nil case e := <-errc: WriteLog(fmt.Sprintf("Error in Initialization %v", e)) - RevertChanges(iptables, nflog, cmd, resolvdConfigPath, dockerDaemonConfigPath, dnsConfig) + RevertChanges(iptables, nflog, cmd, resolvdConfigPath, dockerDaemonConfigPath, dnsConfig, sudo) return e } @@ -284,7 +305,7 @@ func addImplicitEndpoints(endpoints map[string][]Endpoint, disableTelemetry bool } func RevertChanges(iptables *Firewall, nflog AgentNflogger, - cmd Command, resolvdConfigPath, dockerDaemonConfigPath string, dnsConfig DnsConfig) { + cmd Command, resolvdConfigPath, dockerDaemonConfigPath string, dnsConfig DnsConfig, sudo Sudo) { err := RevertFirewallChanges(iptables) if err != nil { WriteLog(fmt.Sprintf("Error in RevertChanges %v", err)) @@ -297,6 +318,10 @@ func RevertChanges(iptables *Firewall, nflog AgentNflogger, if err != nil { WriteLog(fmt.Sprintf("Error in reverting docker DNS server changes %v", err)) } + err = sudo.revertDisableSudo() + if err != nil { + WriteLog(fmt.Sprintf("Error in reverting sudo changes %v", err)) + } WriteLog("Reverted changes") } diff --git a/agent_test.go b/agent_test.go index df50115..ffc0b66 100644 --- a/agent_test.go +++ b/agent_test.go @@ -138,6 +138,9 @@ func TestRun(t *testing.T) { httpmock.RegisterResponder("GET", "https://dns.google/resolve", // no query params to match all other requests httpmock.NewStringResponder(200, `{"Status":0,"TC":false,"RD":true,"RA":true,"AD":false,"CD":false,"Question":[{"name":"requesteddomain.com.","type":1}],"Answer":[{"name":"requesteddomain.com.","type":1,"TTL":300,"data":"69.69.69.69"}]}`)) + httpmock.RegisterResponder("GET", "https://apiurl/v1/github/owner/repo/actions/subscription", + httpmock.NewStringResponder(403, "")) + tests := []struct { name string args args @@ -185,6 +188,15 @@ func TestRun(t *testing.T) { hostDNSServer: &mockDNSServer{}, dockerDNSServer: &mockDNSServer{}, iptables: nil, nflog: &MockAgentNflogger{}, cmd: &MockCommand{}, resolvdConfigPath: createTempFileWithContents(""), dockerDaemonConfigPath: createTempFileWithContents("{}"), ciTestOnly: true}, wantErr: false}, + + {name: "success disable sudo", args: args{ctxCancelDuration: 35, configFilePath: "./testfiles/agent-disable-sudo.json", + hostDNSServer: &mockDNSServer{}, dockerDNSServer: &mockDNSServer{}, + iptables: &Firewall{&MockIPTables{}}, nflog: &MockAgentNflogger{}, cmd: &MockCommand{}, resolvdConfigPath: createTempFileWithContents(""), + dockerDaemonConfigPath: createTempFileWithContents("{}"), ciTestOnly: true}, wantErr: false}, + + {name: "private repo no subscription", args: args{ctxCancelDuration: 2, configFilePath: "./testfiles/agent-private-repo.json", hostDNSServer: &mockDNSServer{}, dockerDNSServer: &mockDNSServer{}, + iptables: &Firewall{&MockIPTables{}}, nflog: &MockAgentNflogger{}, cmd: &MockCommand{}, resolvdConfigPath: createTempFileWithContents(""), + dockerDaemonConfigPath: createTempFileWithContents("{}")}, wantErr: false}, } _, ciTest := os.LookupEnv("CI") fmt.Printf("ci-test: %t\n", ciTest) diff --git a/apiclient.go b/apiclient.go index c9fe396..2ed19eb 100644 --- a/apiclient.go +++ b/apiclient.go @@ -80,29 +80,28 @@ func (apiclient *ApiClient) sendNetConnection(correlationId, repo, ipAddress, po } -func (apiclient *ApiClient) sendFileEvent(correlationId, repo, fileType string, timestamp time.Time, tool Tool) error { +func (apiclient *ApiClient) getSubscriptionStatus(repo string) bool { - if !apiclient.DisableTelemetry || apiclient.EgressPolicy == EgressPolicyAudit { - fileEvent := &FileEvent{} - - fileEvent.FileType = fileType - fileEvent.TimeStamp = timestamp - fileEvent.Tool = tool + url := fmt.Sprintf("%s/github/%s/actions/subscription", apiclient.APIURL, repo) - url := fmt.Sprintf("%s/github/%s/actions/jobs/%s/fileevent", apiclient.APIURL, repo, correlationId) + req, err := http.NewRequest("GET", url, nil) - return apiclient.sendApiRequest("POST", url, fileEvent) + if err != nil { + return true } - return nil -} + resp, err := apiclient.Client.Do(req) -/*func (apiclient *ApiClient) sendArtifact(correlationId, repo string, artifact artifact.Artifact) error { + if err != nil { + return true + } - url := fmt.Sprintf("%s/github/%s/actions/jobs/%s/artifact", apiclient.APIURL, repo, correlationId) + if resp.StatusCode == 403 { + return false + } - return apiclient.sendApiRequest("POST", url, artifact) -}*/ + return true +} func (apiclient *ApiClient) sendApiRequest(method, url string, body interface{}) error { diff --git a/config.go b/config.go index 2f84455..47f2c90 100644 --- a/config.go +++ b/config.go @@ -11,14 +11,17 @@ import ( ) type config struct { - Repo string - CorrelationId string - RunId string - WorkingDirectory string - APIURL string - Endpoints map[string][]Endpoint - EgressPolicy string - DisableTelemetry bool + Repo string + CorrelationId string + RunId string + WorkingDirectory string + APIURL string + Endpoints map[string][]Endpoint + EgressPolicy string + DisableTelemetry bool + DisableSudo bool + DisableFileMonitoring bool + Private bool } type Endpoint struct { @@ -27,14 +30,17 @@ 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"` - AllowedEndpoints string `json:"allowed_endpoints"` - EgressPolicy string `json:"egress_policy"` - DisableTelemetry bool `json:"disable_telemetry"` + Repo string `json:"repo"` + CorrelationId string `json:"correlation_id"` + RunId string `json:"run_id"` + WorkingDirectory string `json:"working_directory"` + APIURL string `json:"api_url"` + 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"` } // init reads the config file for the agent and initializes config settings @@ -58,6 +64,9 @@ func (c *config) init(configFilePath string) error { c.Endpoints = parseEndpoints(configFile.AllowedEndpoints) c.EgressPolicy = configFile.EgressPolicy c.DisableTelemetry = configFile.DisableTelemetry + c.DisableSudo = configFile.DisableSudo + c.DisableFileMonitoring = configFile.DisableFileMonitoring + c.Private = configFile.Private return nil } diff --git a/dnsconfig.go b/dnsconfig.go index 1455003..9edd854 100644 --- a/dnsconfig.go +++ b/dnsconfig.go @@ -13,8 +13,9 @@ import ( ) type DnsConfig struct { - ResolveConfigBackUpPath string - DockerConfigBackUpPath string + ResolveConfigBackUpPath string + DockerConfigBackUpPath string + ShouldDeleteDockerConfig bool } const ( @@ -129,7 +130,7 @@ func (d *DnsConfig) SetDNSServer(cmd Command, resolvdConfigPath, tempDir string) err = cmd.Run() if err != nil { - return fmt.Errorf(fmt.Sprintf("error flushing cache: %v", err)) + WriteLog(fmt.Sprintf("error flushing cache: %v", err)) } return nil @@ -158,13 +159,18 @@ func copy(src, dst string) error { } func (d *DnsConfig) SetDockerDNSServer(cmd Command, configPath, tempDir string) error { - d.DockerConfigBackUpPath = path.Join(tempDir, "daemon.json") - err := copy(configPath, d.DockerConfigBackUpPath) - if err != nil { - return fmt.Errorf(fmt.Sprintf("error backing up docker config: %v", err)) + if _, err := os.Stat(configPath); err == nil { + d.DockerConfigBackUpPath = path.Join(tempDir, "daemon.json") + err := copy(configPath, d.DockerConfigBackUpPath) + if err != nil { + return fmt.Errorf(fmt.Sprintf("error backing up docker config: %v", err)) + } + } else { + d.ShouldDeleteDockerConfig = true } + mock := cmd != nil - err = updateDockerConfig(configPath) + err := updateDockerConfig(configPath) if err != nil { return fmt.Errorf(fmt.Sprintf("error updating to docker daemon config: %v", err)) } @@ -192,18 +198,24 @@ func (d *DnsConfig) SetDockerDNSServer(cmd Command, configPath, tempDir string) } func (d *DnsConfig) RevertDockerDNSServer(cmd Command, configPath string) error { - if len(d.DockerConfigBackUpPath) > 0 { - - err := copy(d.DockerConfigBackUpPath, configPath) - if err != nil { - return fmt.Errorf(fmt.Sprintf("error recovering docker config: %v", err)) + if len(d.DockerConfigBackUpPath) > 0 || d.ShouldDeleteDockerConfig { + if len(d.DockerConfigBackUpPath) > 0 { + err := copy(d.DockerConfigBackUpPath, configPath) + if err != nil { + return fmt.Errorf(fmt.Sprintf("error recovering docker config: %v", err)) + } + } else if d.ShouldDeleteDockerConfig { + err := os.Remove(configPath) + if err != nil { + return fmt.Errorf(fmt.Sprintf("error deleting docker config: %v", err)) + } } if cmd == nil { cmd = exec.Command("/bin/sh", "-c", "sudo systemctl daemon-reload && sudo systemctl restart docker") } - err = cmd.Run() + err := cmd.Run() if err != nil { return fmt.Errorf(fmt.Sprintf("error restarting docker: %v", err)) } diff --git a/eventhandler.go b/eventhandler.go index a2a52e8..de05393 100644 --- a/eventhandler.go +++ b/eventhandler.go @@ -60,26 +60,8 @@ func (eventHandler *EventHandler) handleFileEvent(event *Event) { // Uncomment to log file writes (only uncomment in INT env) // WriteLog(fmt.Sprintf("file write %s, syscall %s", event.FileName, event.Syscall)) - _, found := eventHandler.ProcessFileMap[event.Pid] - fileType := "" - if !found { - // TODO: Improve this logic to monitor dependencies across languages - if strings.Contains(event.FileName, "/node_modules/") && strings.HasSuffix(event.FileName, ".js") { - fileType = "Dependencies" - - } else if strings.Contains(event.FileName, ".git/objects") { - fileType = "Source Code" - } - - if fileType != "" { - tool := *eventHandler.GetToolChain(event.PPid, event.Exe) - eventHandler.ApiClient.sendFileEvent(eventHandler.CorrelationId, eventHandler.Repo, fileType, event.Timestamp, tool) - eventHandler.ProcessFileMap[event.Pid] = true - } - } - if isSourceCodeFile(event.FileName) { - _, found = eventHandler.SourceCodeMap[event.FileName] + _, found := eventHandler.SourceCodeMap[event.FileName] if !found { eventHandler.SourceCodeMap[event.FileName] = append(eventHandler.SourceCodeMap[event.FileName], event) } @@ -93,17 +75,15 @@ func (eventHandler *EventHandler) handleFileEvent(event *Event) { if isFromDifferentProcess { eventHandler.SourceCodeMap[event.FileName] = append(eventHandler.SourceCodeMap[event.FileName], event) - if !strings.Contains(event.FileName, "node_modules/") { // node_modules folder has overwrites by design, even has .cs files in some cases. Need a better way to handle that - counter, found := eventHandler.FileOverwriteCounterMap[event.Exe] - if !found || counter < 3 { - checksum, err := getProgramChecksum(event.Exe) - if err == nil { - WriteLog(fmt.Sprintf("[Source code overwritten] file: %s syscall: %s by exe: %s [%s] Timestamp: %s", event.FileName, event.Syscall, event.Exe, checksum, event.Timestamp.Format("2006-01-02T15:04:05.999999999Z"))) - WriteAnnotation(fmt.Sprintf("StepSecurity Harden Runner: Source code overwritten file: %s syscall: %s by exe: %s", event.FileName, event.Syscall, event.Exe)) - } - - eventHandler.FileOverwriteCounterMap[event.Exe]++ + counter, found := eventHandler.FileOverwriteCounterMap[event.Exe] + if !found || counter < 3 { + checksum, err := getProgramChecksum(event.Exe) + if err == nil { + WriteLog(fmt.Sprintf("[Source code overwritten] file: %s syscall: %s by exe: %s [%s] Timestamp: %s", event.FileName, event.Syscall, event.Exe, checksum, event.Timestamp.Format("2006-01-02T15:04:05.999999999Z"))) + // WriteAnnotation(fmt.Sprintf("StepSecurity Harden Runner: Source code overwritten file: %s syscall: %s by exe: %s", event.FileName, event.Syscall, event.Exe)) } + + eventHandler.FileOverwriteCounterMap[event.Exe]++ } } } @@ -113,15 +93,9 @@ func (eventHandler *EventHandler) handleFileEvent(event *Event) { } func isSourceCodeFile(fileName string) bool { - ext := path.Ext(fileName) - // https://docs.github.com/en/get-started/learning-about-github/github-language-support - // TODO: Add js & ts back. node makes change to js files as part of downloading/ setting up dependencies - // TODO: Add more extensions - sourceCodeExtensions := []string{".c", ".cpp", ".cs", ".go", ".java"} - for _, extension := range sourceCodeExtensions { - if ext == extension { - return true - } + // If it has an extension or might be a Dockerfile + if strings.Contains(fileName, ".") || strings.Contains(fileName, "Dockerfile") { + return true } return false @@ -134,9 +108,21 @@ func (eventHandler *EventHandler) handleProcessEvent(event *Event) { if !found { eventHandler.ProcessMap[event.Pid] = &Process{PID: event.Pid, PPid: event.PPid, Exe: event.Exe, Arguments: event.ProcessArguments} - } + eventHandler.procMutex.Unlock() - eventHandler.procMutex.Unlock() + if event.Euid == "0" { + image := eventHandler.GetContainerByPid(event.Pid) + if image == "" { + if event.Exe != "" { + if eventHandler.IsStartedByRunner(event.PPid, event.Exe) { + WriteLog(fmt.Sprintf("sudo process started: Exe: %s, Arguments: %v", event.Exe, event.ProcessArguments)) + } + } + } + } + } else { + eventHandler.procMutex.Unlock() + } } /* @@ -224,12 +210,10 @@ func (eventHandler *EventHandler) HandleEvent(event *Event) { func GetContainerIdByPid(cgroupPath string) string { content, err := ioutil.ReadFile(cgroupPath) if err != nil { - WriteLog(fmt.Sprintf("error reading cgrouppath: %s : %v", cgroupPath, err)) + // WriteLog(fmt.Sprintf("error reading cgrouppath: %s : %v", cgroupPath, err)) return "" } - //WriteLog(fmt.Sprintf("content for cgrouppath: %s : %s", cgroupPath, content)) - for _, line := range strings.Split(string(content), "\n") { parts := strings.Split(line, ":") if len(parts) > 2 && parts[1] == "memory" { @@ -280,8 +264,6 @@ func (eventHandler *EventHandler) GetContainerByPid(pid string) string { containerId := GetContainerIdByPid(cgroupPath) if containerId == "" { return "" - } else { - WriteLog(fmt.Sprintf("Found containerid: %s for pid: %s", containerId, pid)) } // docker prints first 12 characters in the log @@ -306,7 +288,7 @@ func (eventHandler *EventHandler) GetContainerByPid(pid string) string { if strings.Compare(pid, fmt.Sprintf("%d", json.State.Pid)) == 0 { procContainer = container.Image } else if containerId == container.ID { - WriteLog(fmt.Sprintf("Found containerid: %s for pid: %s", container.ID, pid)) + // WriteLog(fmt.Sprintf("Found containerid: %s for pid: %s", container.ID, pid)) procContainer = container.Image } } @@ -331,6 +313,36 @@ func getProgramChecksum(path string) (string, error) { return fmt.Sprintf("%x", h.Sum(nil)), nil } +func (eventHandler *EventHandler) IsStartedByRunner(ppid, exe string) bool { + + if strings.Contains(exe, "Runner.Worker") { + return true + } + + // In some cases the process has already exited, so get from map first + eventHandler.procMutex.Lock() + parentProcess, found := eventHandler.ProcessMap[ppid] + eventHandler.procMutex.Unlock() + + if found { + return eventHandler.IsStartedByRunner(parentProcess.PPid, parentProcess.Exe) + } + + // If not in map, may be long running, so get from OS + parentProcessId, err := getParentProcessId(ppid) + if err != nil { + return false + } + + path, err := getProcessExe(ppid) + if err != nil { + return false + } + + return eventHandler.IsStartedByRunner(fmt.Sprintf("%d", parentProcessId), path) + +} + func (eventHandler *EventHandler) GetToolChain(ppid, exe string) *Tool { checksum, _ := getProgramChecksum(exe) tool := Tool{Name: filepath.Base(exe), SHA256: checksum} diff --git a/procmon.go b/procmon.go index 6f33d04..e84c5a2 100644 --- a/procmon.go +++ b/procmon.go @@ -14,13 +14,14 @@ const ( ) type ProcessMonitor struct { - CorrelationId string - Repo string - ApiClient *ApiClient - DNSProxy *DNSProxy - WorkingDirectory string - Events map[int]*Event - mutex sync.RWMutex + CorrelationId string + Repo string + ApiClient *ApiClient + DNSProxy *DNSProxy + WorkingDirectory string + DisableFileMonitoring bool + Events map[int]*Event + mutex sync.RWMutex } type Process struct { @@ -44,6 +45,7 @@ type Event struct { Pid string ProcessArguments []string PPid string + Euid string Timestamp time.Time EventType string Status string @@ -68,6 +70,7 @@ func (p *ProcessMonitor) PrepareEvent(sequence int, eventMap map[string]interfac p.Events[sequence].Exe = getValue("exe", eventMap) p.Events[sequence].Pid = getValue("pid", eventMap) p.Events[sequence].PPid = getValue("ppid", eventMap) + p.Events[sequence].Euid = getValue("euid", eventMap) timestamp, err := time.Parse("2006-01-02 15:04:05.999999999 +0000 UTC", getValue("@timestamp", eventMap)) if err != nil { timestamp = time.Now().UTC() diff --git a/procmon_linux.go b/procmon_linux.go index 5f2be85..297782b 100644 --- a/procmon_linux.go +++ b/procmon_linux.go @@ -48,20 +48,27 @@ func (p *ProcessMonitor) MonitorProcesses(errc chan error) { WriteLog("Rules deleted") - // files modified in working directory - r, _ := flags.Parse(fmt.Sprintf("-a exit,always -F dir=%s -F perm=wa -S open -S openat -S rename -S renameat -k %s", "/home/runner", fileMonitorTag)) + if !p.DisableFileMonitoring { - actualBytes, _ := rule.Build(r) + // files modified in working directory + workingDirectory := p.WorkingDirectory + if len(workingDirectory) == 0 { + workingDirectory = "/home/runner" + } + r, _ := flags.Parse(fmt.Sprintf("-a exit,always -F dir=%s -F perm=wa -S open -S openat -S rename -S renameat -k %s", workingDirectory, fileMonitorTag)) - if err = client.AddRule(actualBytes); err != nil { - WriteLog(fmt.Sprintf("failed to add audit rule %v", err)) - errc <- errors.Wrap(err, "failed to add audit rule") - } + actualBytes, _ := rule.Build(r) - WriteLog("File monitor added") + if err = client.AddRule(actualBytes); err != nil { + WriteLog(fmt.Sprintf("failed to add audit rule %v", err)) + errc <- errors.Wrap(err, "failed to add audit rule") + } - r, _ = flags.Parse(fmt.Sprintf("-w %s -p w -k %s", "/home/agent", fileMonitorTag)) - actualBytes, _ = rule.Build(r) + WriteLog(fmt.Sprintf("File monitor added for %s", workingDirectory)) + } + + r, _ := flags.Parse(fmt.Sprintf("-w %s -p w -k %s", "/home/agent", fileMonitorTag)) + actualBytes, _ := rule.Build(r) if err = client.AddRule(actualBytes); err != nil { WriteLog(fmt.Sprintf("failed to add audit rule %v", err)) diff --git a/sudo.go b/sudo.go new file mode 100644 index 0000000..55fc705 --- /dev/null +++ b/sudo.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" + "os" + "path" +) + +type Sudo struct { + SudoersBackUpPath string +} + +const ( + sudoersFile = "/etc/sudoers.d/runner" +) + +func (s *Sudo) disableSudo(tempDir string) error { + s.SudoersBackUpPath = path.Join(tempDir, "runner") + err := copy(sudoersFile, s.SudoersBackUpPath) + + if err != nil { + return fmt.Errorf(fmt.Sprintf("error backing up sudoers file: %v", err)) + } + err = os.Remove(sudoersFile) + if err != nil { + return fmt.Errorf(fmt.Sprintf("unable to delete sudoers file at %s: %v", sudoersFile, err)) + } + + return nil +} + +func (s *Sudo) revertDisableSudo() error { + if len(s.SudoersBackUpPath) > 0 { + err := copy(s.SudoersBackUpPath, sudoersFile) + + if err != nil { + return fmt.Errorf(fmt.Sprintf("error reverting sudoers file: %v", err)) + } + } + + return nil +} diff --git a/testfiles/agent-allowed-endpoints.json b/testfiles/agent-allowed-endpoints.json index b48db76..3ff6c7b 100644 --- a/testfiles/agent-allowed-endpoints.json +++ b/testfiles/agent-allowed-endpoints.json @@ -5,5 +5,6 @@ "api_url": "https://apiurl/v1", "allowed_endpoints": "domain1.com:443 domain2.com:443", "egress_policy": "block", - "disable_telemetry": true + "disable_telemetry": true, + "disable_sudo": false } diff --git a/testfiles/agent-disable-sudo.json b/testfiles/agent-disable-sudo.json new file mode 100644 index 0000000..04781ba --- /dev/null +++ b/testfiles/agent-disable-sudo.json @@ -0,0 +1,11 @@ +{ + "repo": "owner/repo", + "run_id": "1287185438", + "correlation_id": "d942cc6c-d349-49da-ad54-a1bf92538567", + "api_url": "https://apiurl/v1", + "allowed_endpoints": "domain1.com:443 domain2.com:443", + "egress_policy": "audit", + "disable_telemetry": true, + "disable_sudo": true + } + \ No newline at end of file diff --git a/testfiles/agent-private-repo.json b/testfiles/agent-private-repo.json new file mode 100644 index 0000000..edddc05 --- /dev/null +++ b/testfiles/agent-private-repo.json @@ -0,0 +1,12 @@ +{ + "repo": "owner/repo", + "run_id": "1287185438", + "correlation_id": "d942cc6c-d349-49da-ad54-a1bf92538567", + "api_url": "https://apiurl/v1", + "allowed_endpoints": "domain1.com:443 domain2.com:443", + "egress_policy": "block", + "disable_telemetry": true, + "disable_sudo": true, + "private": true + } + \ No newline at end of file