├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .goreleaser.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── create.go ├── decrypt.go ├── encrypt.go ├── keys.go ├── root.go ├── rotate.go └── update.go ├── config_example.yaml ├── ebos.pub ├── go.mod ├── go.sum ├── main.go ├── main_test.go ├── pki └── pki.go ├── sls └── sls.go ├── testdata ├── decrypt-file.golden ├── decrypt-path.golden ├── decrypt-recurse.golden ├── empty │ └── .gitignore ├── encrypt-file.golden ├── encrypt-recurse.golden ├── gnupg │ └── .gitkeep ├── gpginit.txt ├── inc.sls ├── keys-count.golden ├── keys-file.golden ├── keys-path.golden ├── keys-recurse-bad.golden ├── keys-recurse.golden ├── new.sls ├── no-args.golden ├── test.sls ├── test │ ├── bar.sls │ ├── baz.sls │ ├── foo.sls │ └── simple.sls └── testkeys.sh └── utils └── utils.go /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [main] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [main] 14 | schedule: 15 | - cron: '0 3 * * 0' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | # Override automatic language detection by changing the below list 26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 27 | language: ['go'] 28 | # Learn more... 29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v2 34 | with: 35 | # We must fetch at least the immediate parents so that if this is 36 | # a pull request then we can checkout the head. 37 | fetch-depth: 2 38 | 39 | # If this run was triggered by a pull request event, then checkout 40 | # the head of the pull request instead of the merge commit. 41 | - run: git checkout HEAD^2 42 | if: ${{ github.event_name == 'pull_request' }} 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | generate-secure-pillar 2 | .vs* 3 | bin/* 4 | Gopkg.lock 5 | vendor/* 6 | _vendor* 7 | .DS_Store 8 | *~ 9 | debug.test 10 | bar.sls 11 | bar2.sls 12 | blah2.sls 13 | dec.sls 14 | enc.sls 15 | foo.sls 16 | foo.enc 17 | foo/ 18 | foo2.sls 19 | new.sls 20 | packages/* 21 | .idea 22 | testdata/gnupg/* 23 | num.sls 24 | num.yaml 25 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | env_files: 4 | # use only one or release will fail! 5 | github_token: ~/.config/goreleaser/github_token 6 | 7 | dist: packages 8 | 9 | builds: 10 | - binary: generate-secure-pillar 11 | targets: 12 | - windows_amd64 13 | - windows_arm64 14 | - darwin_amd64 15 | - darwin_arm64 16 | - linux_amd64 17 | - linux_arm64 18 | - openbsd_amd64 19 | - openbsd_arm64 20 | - netbsd_amd64 21 | - netbsd_arm64 22 | - freebsd_amd64 23 | - freebsd_arm64 24 | 25 | archives: 26 | - id: main 27 | name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" 28 | 29 | universal_binaries: 30 | - replace: true 31 | 32 | snapshot: 33 | version_template: SNAPSHOT-{{.Commit}} 34 | 35 | changelog: 36 | sort: asc 37 | filters: 38 | exclude: 39 | - "^docs:" 40 | - "^test:" 41 | 42 | brews: 43 | - # Repository to push the tap to. 44 | repository: 45 | owner: esilva-everbridge 46 | name: homebrew-generate-secure-pillar 47 | 48 | skip_upload: false 49 | 50 | dependencies: 51 | - gpg1 52 | 53 | # Git author used to commit to the repository. 54 | # Defaults are shown. 55 | commit_author: 56 | name: Ed Silva 57 | email: ed.silva@everbridge.com 58 | 59 | # Folder inside the repository to put the formula. 60 | # Default is the root folder. 61 | directory: Formula 62 | 63 | # Your app's homepage. 64 | # Default is empty. 65 | homepage: https://github.com/Everbridge/generate-secure-pillar 66 | 67 | # Your app's description. 68 | # Default is empty. 69 | description: "Create and update encrypted content or decrypt encrypted content in YAML files" 70 | 71 | nfpms: 72 | - # Your app's vendor. 73 | # Default is empty. 74 | vendor: Everbridge, Inc. 75 | # Your app's homepage. 76 | # Default is empty. 77 | homepage: https://github.com/Everbridge/generate-secure-pillar 78 | 79 | # Your app's maintainer (probably you). 80 | # Default is empty. 81 | maintainer: "Ed Silva " 82 | 83 | # Your app's description. 84 | # Default is empty. 85 | description: "Create and update encrypted content or decrypt encrypted content in YAML files" 86 | 87 | # Your app's license. 88 | # Default is empty. 89 | license: MIT 90 | 91 | # Formats to be generated. 92 | formats: 93 | - deb 94 | - rpm 95 | 96 | signs: 97 | - cmd: gpg 98 | artifacts: checksum 99 | args: 100 | [ 101 | "-u", 102 | "Everbridge Open Source", 103 | "--output", 104 | "${signature}", 105 | "--detach-sign", 106 | "${artifact}", 107 | ] 108 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at ed.silva@everbridge.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Everbridge, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /usr/bin/env bash 2 | 3 | PATH := $(PATH):/usr/local/bin 4 | 5 | # The name of the executable (default is current directory name) 6 | TARGET := $(shell echo $${PWD\#\#*/}) 7 | .DEFAULT_GOAL: $(TARGET) 8 | 9 | # These will be provided to the target 10 | BUILD := `git rev-parse HEAD` 11 | # always add one to the commit number to fix an off by one bug 12 | # as the release makes a commit prior to publishing 13 | COMMIT := $(shell git rev-list HEAD | wc -l | sed 's/^ *//g' | awk '{print $$1 + 1}') 14 | VERSION := 1.0.$(COMMIT) 15 | 16 | # Use linker flags to provide version/build settings to the target 17 | LDFLAGS=-ldflags "-X=main.Version=$(VERSION) -X=main.Build=$(BUILD) -s -w" 18 | 19 | # go source files, ignore vendor directory 20 | SRC = $(shell find . -type f -name '*.go' -not -path "./vendor/*") 21 | 22 | RELEASER := $(shell command -v goreleaser 2> /dev/null) 23 | REVIVE := $(shell command -v revive 2> /dev/null) 24 | FPM := $(shell command -v fpm 2> /dev/null) 25 | 26 | BRANCH := `git rev-parse --abbrev-ref HEAD` 27 | 28 | GOROOT := `go env GOROOT` 29 | 30 | .PHONY: all build clean install uninstall fmt simplify check run 31 | 32 | all: build install 33 | 34 | $(TARGET): $(SRC) 35 | @go build $(LDFLAGS) -o $(TARGET) 36 | 37 | build: $(TARGET) deps check test 38 | @go build $(LDFLAGS) 39 | 40 | release: build 41 | @sed -i .bak 's/\"1.0.*\"/\"1.0.'$(COMMIT)'\"/' cmd/root.go 42 | @grep $(COMMIT) cmd/root.go 2> /dev/null && rm cmd/root.go.bak 43 | @sed -i .bak 's/VERSION 1.0.*/VERSION 1.0.'$(COMMIT)'/' README.md 44 | @grep $(COMMIT) README.md 2> /dev/null && rm README.md.bak 45 | @git commit -am "new $(BRANCH) build: $(VERSION)" 46 | @git tag -a v$(VERSION) -m "new $(BRANCH) build: $(VERSION)" 47 | @echo pushing to branch $(BRANCH) 48 | @git push origin v$(VERSION) 49 | @git push origin $(BRANCH) 50 | ifndef RELEASER 51 | @echo "cannot build release (missing goreleaser)" 52 | else 53 | @echo "creating a new release" 54 | GITHUB_TOKEN=`cat ~/.config/goreleaser/github_token` goreleaser --clean 55 | endif 56 | @true 57 | 58 | clean: 59 | @rm -f $(TARGET) 60 | $(shell find ./bin -type f -perm +111 -delete) 61 | 62 | install: 63 | @go install $(LDFLAGS) 64 | 65 | uninstall: clean 66 | @rm -f $$(which ${TARGET}) 67 | 68 | fmt: 69 | @gofmt -l -w $(SRC) 70 | 71 | simplify: 72 | @gofmt -s -l -w $(SRC) 73 | 74 | check: 75 | @test -z $(shell gofmt -l main.go | tee /dev/stderr) || echo "[WARN] Fix formatting issues with 'make fmt'" 76 | ifndef REVIVE 77 | @echo "running 'staticcheck .'" 78 | @staticcheck . 79 | else 80 | @echo "running 'revive ./...'" 81 | @revive --formatter friendly ./... 82 | endif 83 | 84 | run: install 85 | @$(TARGET) 86 | 87 | test: $(TARGET) 88 | @go test -v 89 | 90 | deps: 91 | GO111MODULE="on" go mod init | true 92 | GO111MODULE="on" go mod tidy 93 | GO111MODULE="on" go mod verify 94 | 95 | mac: GOOS = darwin 96 | mac: GOARCH = amd64 97 | mac: 98 | @echo "building for $(GOOS)/$(GOARCH)" 99 | @mkdir -p bin/$(GOARCH)/$(GOOS)/ && GOOS=$(GOOS) GOARCH=$(GOARCH) go build && mv $(TARGET) bin/$(GOARCH)/$(GOOS)/ 100 | 101 | ubuntu: GOOS = linux 102 | ubuntu: GOARCH = amd64 103 | ubuntu: 104 | @echo "building for $(GOOS)/$(GOARCH)" 105 | @mkdir -p bin/$(GOARCH)/$(GOOS)/ && GOOS=$(GOOS) GOARCH=$(GOARCH) go build && mv $(TARGET) bin/$(GOARCH)/$(GOOS)/ 106 | 107 | packages: deb pkg 108 | 109 | deb: GOOS = linux 110 | deb: GOARCH = amd64 111 | deb: ubuntu 112 | ifndef FPM 113 | @echo "'fpm' is not installed, cannot make packages" 114 | else 115 | @fpm -n $(TARGET) -s dir -t deb -a $(GOARCH) -p $(TARGET)_$(VERSION)_$(GOARCH).deb --deb-no-default-config-files ./bin/$(GOARCH)/$(GOOS)/$(TARGET)=/usr/local/bin/$(TARGET) 116 | @mv $(TARGET)*.deb ./packages 117 | endif 118 | 119 | pkg: GOOS = darwin 120 | pkg: GOARCH = amd64 121 | pkg: mac 122 | ifndef FPM 123 | @echo "'fpm' is not installed, cannot make packages" 124 | else 125 | @fpm -n $(TARGET) -s dir -t osxpkg -a $(GOARCH) -p $(TARGET)-$(VERSION)-$(GOARCH).pkg ./bin/$(GOARCH)/$(GOOS)/$(TARGET)=/usr/local/bin/$(TARGET) 126 | @mv $(TARGET)*.pkg ./packages 127 | endif 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generate-secure-pillar 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/Everbridge/generate-secure-pillar)](https://goreportcard.com/report/github.com/Everbridge/generate-secure-pillar) 4 | 5 | ## Create and update encrypted content or decrypt encrypted content in YAML files 6 | 7 | 8 | 9 | ## USAGE 10 | 11 | generate-secure-pillar [global options] command [command options] [arguments...] 12 | 13 | ## VERSION 1.0.634 14 | 15 | ## AUTHOR 16 | 17 | Ed Silva 18 | 19 | ## HOMEBREW INSTALL 20 | 21 | ``` shell 22 | brew tap esilva-everbridge/homebrew-generate-secure-pillar 23 | brew install generate-secure-pillar 24 | ``` 25 | 26 | ## CONFIG FILE USAGE 27 | 28 | A config file can be used to set default values, and an example file is created if there isn't one already, with commented out values. The file location defaults to `~/.config/generate-secure-pillar/config.yaml`. 29 | Profiles can be specified and selected via a command line option. 30 | 31 | ``` shell 32 | profiles: 33 | - name: dev 34 | default: true 35 | default_key: Dev Salt Master 36 | gnupg_home: ~/.gnupg 37 | default_pub_ring: ~/.gnupg/pubring.gpg 38 | default_sec_ring: ~/.gnupg/secring.gpg 39 | - name: prod 40 | default: false 41 | default_key: Prod Salt Master 42 | gnupg_home: ~/.gnupg 43 | default_pub_ring: ~/.gnupg/pubring.gpg 44 | default_sec_ring: ~/.gnupg/secring.gpg 45 | ... 46 | ``` 47 | 48 | ## ABOUT PGP KEYS 49 | 50 | The PGP keys you import for use with this tool need to be 'trusted' keys. 51 | An easy way to do this is, after importing a key, run the following commands: 52 | 53 | ``` shell 54 | expect -c "spawn gpg --edit-key '' trust quit; send \"5\ry\r\"; expect eof" 55 | ``` 56 | 57 | (found here: 58 | 59 | ## COMMANDS 60 | 61 | ```text 62 | create, c create a new sls file 63 | update, u update the value of the given key in the given file 64 | encrypt, e perform encryption operations 65 | decrypt, d perform decryption operations 66 | rotate, r decrypt existing files and re-encrypt with a new key 67 | keys, k show PGP key IDs used 68 | help, h Shows a list of commands or help for one command 69 | ``` 70 | 71 | ## GLOBAL OPTIONS 72 | 73 | - --profile value default profile to use in the config file 74 | - --pubring value PGP public keyring (default: "~/.gnupg/pubring.gpg" or "$GNUPGHOME/pubring.gpg") 75 | - --secring value PGP private keyring (default: "~/.gnupg/secring.gpg" or "$GNUPGHOME/secring.gpg") 76 | - --pgp_key value, -k value PGP key name, email, or ID to use for encryption 77 | - --debug adds line number info to log output 78 | - --element value, -e value Name of the top level element under which encrypted key/value pairs are kept 79 | - --help, -h show help 80 | - --version, -v print the version 81 | 82 | ## COPYRIGHT 83 | 84 | (c) 2018 Everbridge, Inc. 85 | 86 | **CAVEAT: YAML files with include statements are not handled properly, so we skip them.** 87 | 88 | ## EXAMPLES 89 | 90 | ### specify a config profile and create a new file 91 | 92 | ```$ generate-secure-pillar --profile dev create --name secret_name1 --value secret_value1 --name secret_name2 --value secret_value2 --outfile new.sls``` 93 | 94 | ### create a new sls file 95 | 96 | ```$ generate-secure-pillar -k "Salt Master" create --name secret_name1 --value secret_value1 --name secret_name2 --value secret_value2 --outfile new.sls``` 97 | 98 | ### add to the new file 99 | 100 | ```$ generate-secure-pillar -k "Salt Master" update --name new_secret_name --value new_secret_value --file new.sls``` 101 | 102 | ### update an existing value 103 | 104 | ```$ generate-secure-pillar -k "Salt Master" update --name secret_name --value secret_value3 --file new.sls``` 105 | 106 | ### encrypt all plain text values in a file 107 | 108 | ```$ generate-secure-pillar -k "Salt Master" encrypt all --file us1.sls --outfile us1.sls``` 109 | 110 | ### or use --update flag 111 | 112 | ```$ generate-secure-pillar -k "Salt Master" encrypt all --file us1.sls --update``` 113 | 114 | ### encrypt all plain text values in a file under the element 'secret_stuff' 115 | 116 | ```$ generate-secure-pillar -k "Salt Master" --element secret_stuff encrypt all --file us1.sls --outfile us1.sls``` 117 | 118 | ### recurse through all sls files, encrypting all values 119 | 120 | ```$ generate-secure-pillar -k "Salt Master" encrypt recurse -d /path/to/pillar/secure/stuff``` 121 | 122 | ### recurse through all sls files, decrypting all values (requires imported private key) 123 | 124 | ```$ generate-secure-pillar decrypt recurse -d /path/to/pillar/secure/stuff``` 125 | 126 | ### decrypt a specific existing value (requires imported private key) 127 | 128 | ```$ generate-secure-pillar decrypt path --path "some:yaml:path" --file new.sls``` 129 | 130 | ### decrypt all files and re-encrypt with given key (requires imported private key) 131 | 132 | ```$ generate-secure-pillar -k "New Salt Master Key" rotate -d /path/to/pillar/secure/stuff``` 133 | 134 | ### show all PGP key IDs used in a file 135 | 136 | ```$ generate-secure-pillar keys all --file us1.sls``` 137 | 138 | ### show all keys used in all files in a given directory 139 | 140 | ```$ generate-secure-pillar keys recurse -d /path/to/pillar/secure/stuff``` 141 | 142 | ### show the PGP key ID used for an element at a path in a file 143 | 144 | ```$ generate-secure-pillar keys path --path "some:yaml:path" --file new.sls``` 145 | -------------------------------------------------------------------------------- /cmd/create.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Everbridge, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // Package cmd/create handles the creation of new secure pillar files 22 | package cmd 23 | 24 | import ( 25 | "os" 26 | "path/filepath" 27 | "strings" 28 | 29 | "github.com/Everbridge/generate-secure-pillar/sls" 30 | "github.com/rs/zerolog" 31 | "github.com/rs/zerolog/log" 32 | "github.com/spf13/cobra" 33 | ) 34 | 35 | // createCmd represents the create command 36 | var createCmd = &cobra.Command{ 37 | Use: "create", 38 | Short: "create a new sls file", 39 | Run: func(cmd *cobra.Command, args []string) { 40 | outputFilePath, err := filepath.Abs(outputFilePath) 41 | if err != nil { 42 | logger.Fatal().Err(err) 43 | } 44 | secretNames := strings.Split(strings.Trim(cmd.Flag("name").Value.String(), "[]"), ",") 45 | secretValues := strings.Split(strings.Trim(cmd.Flag("value").Value.String(), "[]"), ",") 46 | pk := getPki() 47 | s := sls.New(outputFilePath, pk, topLevelElement) 48 | err = s.ProcessYaml(secretNames, secretValues) 49 | if err != nil { 50 | logger.Fatal().Err(err).Msg("create") 51 | } 52 | buffer, err := s.FormatBuffer("") 53 | if err != nil { 54 | logger.Fatal().Err(err).Msg("create") 55 | } 56 | _, err = sls.WriteSlsFile(buffer, outputFilePath) 57 | if err != nil { 58 | logger.Fatal().Err(err).Msg("create") 59 | } 60 | }, 61 | } 62 | 63 | func init() { 64 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) 65 | rootCmd.AddCommand(createCmd) 66 | createCmd.PersistentFlags().StringVarP(&outputFilePath, "outfile", "o", os.Stdout.Name(), "output file (defaults to STDOUT)") 67 | createCmd.PersistentFlags().StringArrayP("name", "n", nil, "secret name(s)") 68 | createCmd.PersistentFlags().StringArrayP("value", "s", nil, "secret value(s)") 69 | } 70 | -------------------------------------------------------------------------------- /cmd/decrypt.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Everbridge, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // Package cmd/decrypt handles the decryption of secure pillar files 22 | package cmd 23 | 24 | import ( 25 | "os" 26 | "path/filepath" 27 | 28 | "github.com/Everbridge/generate-secure-pillar/sls" 29 | "github.com/Everbridge/generate-secure-pillar/utils" 30 | "github.com/rs/zerolog" 31 | "github.com/rs/zerolog/log" 32 | "github.com/spf13/cobra" 33 | ) 34 | 35 | // decryptCmd represents the decrypt command 36 | var decryptCmd = &cobra.Command{ 37 | Use: "decrypt", 38 | Short: "perform decryption operations", 39 | PreRunE: func(cmd *cobra.Command, args []string) error { 40 | if len(args) == 0 { 41 | err := cmd.Help() 42 | if err != nil { 43 | logger.Fatal().Err(err) 44 | } 45 | os.Exit(0) 46 | } 47 | return nil 48 | }, 49 | Run: func(cmd *cobra.Command, args []string) { 50 | pk := getPki() 51 | outputFilePath, err := filepath.Abs(outputFilePath) 52 | if err != nil { 53 | logger.Fatal().Err(err) 54 | } 55 | inputFilePath, err := filepath.Abs(inputFilePath) 56 | if err != nil { 57 | logger.Fatal().Err(err) 58 | } 59 | 60 | // process args 61 | switch args[0] { 62 | case all: 63 | if inputFilePath == os.Stdin.Name() && !stdinIsPiped() { 64 | logger.Info().Msgf("reading from %s", os.Stdin.Name()) 65 | } 66 | s := sls.New(inputFilePath, pk, topLevelElement) 67 | if inputFilePath != os.Stdin.Name() && updateInPlace { 68 | outputFilePath = inputFilePath 69 | } 70 | buffer, err := s.PerformAction("decrypt") 71 | utils.SafeWrite(buffer, outputFilePath, err) 72 | case recurse: 73 | err = utils.ProcessDir(recurseDir, ".sls", "decrypt", outputFilePath, topLevelElement, pk) 74 | if err != nil { 75 | logger.Warn().Err(err).Msg("decrypt") 76 | } 77 | case path: 78 | s := sls.New(inputFilePath, pk, topLevelElement) 79 | utils.PathAction(&s, yamlPath, "decrypt") 80 | default: 81 | err = cmd.Help() 82 | if err != nil { 83 | logger.Fatal().Err(err) 84 | } 85 | } 86 | }, 87 | } 88 | 89 | func init() { 90 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) 91 | rootCmd.AddCommand(decryptCmd) 92 | decryptCmd.PersistentFlags().StringVarP(&yamlPath, "path", "p", "", "YAML path to decrypt") 93 | decryptCmd.PersistentFlags().StringVarP(&recurseDir, "dir", "d", "", "recurse over all .sls files in the given directory") 94 | decryptCmd.PersistentFlags().StringVarP(&inputFilePath, "file", "f", os.Stdin.Name(), "input file (defaults to STDIN)") 95 | decryptCmd.PersistentFlags().StringVarP(&outputFilePath, "outfile", "o", os.Stdout.Name(), "output file (defaults to STDOUT)") 96 | decryptCmd.PersistentFlags().BoolVarP(&updateInPlace, "update", "u", false, "update the input file") 97 | } 98 | -------------------------------------------------------------------------------- /cmd/encrypt.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Everbridge, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // Package cmd/encrypt handles the encryption of secure pillar files 22 | package cmd 23 | 24 | import ( 25 | "os" 26 | "path/filepath" 27 | 28 | "github.com/Everbridge/generate-secure-pillar/sls" 29 | "github.com/Everbridge/generate-secure-pillar/utils" 30 | "github.com/rs/zerolog" 31 | "github.com/rs/zerolog/log" 32 | "github.com/spf13/cobra" 33 | ) 34 | 35 | // encryptCmd represents the encrypt command 36 | var encryptCmd = &cobra.Command{ 37 | Use: "encrypt", 38 | Short: "perform encryption operations", 39 | PreRunE: func(cmd *cobra.Command, args []string) error { 40 | if len(args) == 0 { 41 | err := cmd.Help() 42 | if err != nil { 43 | logger.Fatal().Err(err) 44 | } 45 | os.Exit(0) 46 | } 47 | return nil 48 | }, 49 | Run: func(cmd *cobra.Command, args []string) { 50 | pk := getPki() 51 | outputFilePath, err := filepath.Abs(outputFilePath) 52 | if err != nil { 53 | logger.Fatal().Err(err) 54 | } 55 | inputFilePath, err := filepath.Abs(inputFilePath) 56 | if err != nil { 57 | logger.Fatal().Err(err) 58 | } 59 | 60 | // process args 61 | switch args[0] { 62 | case all: 63 | if inputFilePath == os.Stdin.Name() && !stdinIsPiped() { 64 | logger.Info().Msgf("reading from %s", os.Stdin.Name()) 65 | } 66 | s := sls.New(inputFilePath, pk, topLevelElement) 67 | if inputFilePath != os.Stdin.Name() && updateInPlace { 68 | outputFilePath = inputFilePath 69 | } 70 | buffer, err := s.PerformAction("encrypt") 71 | utils.SafeWrite(buffer, outputFilePath, err) 72 | case recurse: 73 | err := utils.ProcessDir(recurseDir, ".sls", "encrypt", outputFilePath, topLevelElement, pk) 74 | if err != nil { 75 | logger.Warn().Err(err).Msg("encrypt") 76 | } 77 | case path: 78 | s := sls.New(inputFilePath, pk, topLevelElement) 79 | utils.PathAction(&s, yamlPath, "encrypt") 80 | default: 81 | err = cmd.Help() 82 | if err != nil { 83 | logger.Fatal().Err(err) 84 | } 85 | } 86 | }, 87 | } 88 | 89 | func init() { 90 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) 91 | rootCmd.AddCommand(encryptCmd) 92 | encryptCmd.PersistentFlags().StringVarP(&yamlPath, "path", "p", "", "YAML path to encrypt") 93 | encryptCmd.PersistentFlags().StringVarP(&recurseDir, "dir", "d", "", "recurse over all .sls files in the given directory") 94 | encryptCmd.PersistentFlags().StringVarP(&inputFilePath, "file", "f", os.Stdin.Name(), "input file (defaults to STDIN)") 95 | encryptCmd.PersistentFlags().StringVarP(&outputFilePath, "outfile", "o", os.Stdout.Name(), "output file (defaults to STDOUT)") 96 | encryptCmd.PersistentFlags().BoolVarP(&updateInPlace, "update", "u", false, "update the input file") 97 | } 98 | -------------------------------------------------------------------------------- /cmd/keys.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Everbridge, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // Package cmd/keys is for showing what PGP keys were used to encrypt data in secure pillar files 22 | package cmd 23 | 24 | import ( 25 | "fmt" 26 | "os" 27 | "path/filepath" 28 | 29 | "github.com/Everbridge/generate-secure-pillar/sls" 30 | "github.com/Everbridge/generate-secure-pillar/utils" 31 | "github.com/rs/zerolog" 32 | "github.com/rs/zerolog/log" 33 | "github.com/spf13/cobra" 34 | ) 35 | 36 | const count = "count" 37 | 38 | var verbose bool 39 | 40 | // keysCmd represents the keys command 41 | var keysCmd = &cobra.Command{ 42 | Use: "keys", 43 | Short: "show PGP key IDs used", 44 | PreRunE: func(cmd *cobra.Command, args []string) error { 45 | if len(args) == 0 { 46 | err := cmd.Help() 47 | if err != nil { 48 | logger.Fatal().Err(err) 49 | } 50 | os.Exit(0) 51 | } 52 | return nil 53 | }, 54 | Run: func(cmd *cobra.Command, args []string) { 55 | pk := getPki() 56 | outputFilePath = os.Stdout.Name() 57 | inputFilePath, err := filepath.Abs(inputFilePath) 58 | if err != nil { 59 | logger.Fatal().Err(err) 60 | } 61 | 62 | // process args 63 | switch args[0] { 64 | case all: 65 | if inputFilePath == os.Stdin.Name() && !stdinIsPiped() { 66 | logger.Info().Msgf("reading from %s", os.Stdin.Name()) 67 | } 68 | s := sls.New(inputFilePath, pk, topLevelElement) 69 | buffer, err := s.PerformAction("validate") 70 | if err != nil { 71 | logger.Fatal().Err(err) 72 | } 73 | fmt.Printf("%s\n", buffer.String()) 74 | case recurse: 75 | err := utils.ProcessDir(recurseDir, ".sls", "validate", outputFilePath, topLevelElement, pk) 76 | if err != nil { 77 | logger.Warn().Err(err).Msg("keys") 78 | } 79 | case path: 80 | s := sls.New(inputFilePath, pk, topLevelElement) 81 | utils.PathAction(&s, yamlPath, "validate") 82 | case count: 83 | s := sls.New(inputFilePath, pk, topLevelElement) 84 | _, err := s.PerformAction("validate") 85 | if err != nil { 86 | logger.Fatal().Err(err) 87 | } 88 | if verbose { 89 | fmt.Println(s.KeyMeta) 90 | } 91 | if s.KeyCount > 1 { 92 | os.Exit(s.KeyCount) 93 | } 94 | default: 95 | logger.Fatal().Msgf("unknown argument: '%s'", args[0]) 96 | } 97 | }, 98 | } 99 | 100 | func init() { 101 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) 102 | rootCmd.AddCommand(keysCmd) 103 | keysCmd.PersistentFlags().StringVarP(&yamlPath, "path", "p", "", "YAML path to examine") 104 | keysCmd.PersistentFlags().StringVarP(&recurseDir, "dir", "d", "", "recurse over all .sls files in the given directory") 105 | keysCmd.PersistentFlags().StringVarP(&inputFilePath, "file", "f", os.Stdin.Name(), "input file (defaults to STDIN)") 106 | keysCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output") 107 | } 108 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | // Package cmd/root handles the base command line arguments 2 | package cmd 3 | 4 | // Copyright © 2018 Everbridge, Inc. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to deal 8 | // in the Software without restriction, including without limitation the rights 9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | // copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | // THE SOFTWARE. 23 | 24 | import ( 25 | "fmt" 26 | "os" 27 | "path/filepath" 28 | 29 | "github.com/Everbridge/generate-secure-pillar/pki" 30 | homedir "github.com/mitchellh/go-homedir" 31 | "github.com/rs/zerolog" 32 | "github.com/rs/zerolog/log" 33 | 34 | "github.com/spf13/cobra" 35 | "github.com/spf13/viper" 36 | tilde "gopkg.in/mattes/go-expand-tilde.v1" 37 | ) 38 | 39 | var logger = zerolog.New(os.Stdout) 40 | var inputFilePath string 41 | var outputFilePath = os.Stdout.Name() 42 | var cfgFile string 43 | var profile string 44 | var pgpKeyName string 45 | var publicKeyRing = "~/.gnupg/pubring.gpg" 46 | var privateKeyRing = "~/.gnupg/secring.gpg" 47 | var updateInPlace bool 48 | var topLevelElement string 49 | var recurseDir string 50 | var yamlPath string 51 | 52 | // rootCmd represents the base command when called without any subcommands 53 | var rootCmd = &cobra.Command{ 54 | Use: "generate-secure-pillar", 55 | Short: "Create and update encrypted content or decrypt encrypted content.", 56 | Example: ` 57 | # specify a config profile and create a new file 58 | $ generate-secure-pillar --profile dev create --name secret_name1 --value secret_value1 --name secret_name2 --value secret_value2 --outfile new.sls 59 | 60 | # create a new sls file 61 | $ generate-secure-pillar -k "Salt Master" create --name secret_name1 --value secret_value1 --name secret_name2 --value secret_value2 --outfile new.sls 62 | 63 | # add to the new file 64 | $ generate-secure-pillar -k "Salt Master" update --name new_secret_name --value new_secret_value --file new.sls 65 | 66 | # update an existing value 67 | $ generate-secure-pillar -k "Salt Master" update --name secret_name --value secret_value3 --file new.sls 68 | 69 | # encrypt all plain text values in a file 70 | $ generate-secure-pillar -k "Salt Master" encrypt all --file us1.sls --outfile us1.sls 71 | # or use --update flag 72 | $ generate-secure-pillar -k "Salt Master" encrypt all --file us1.sls --update 73 | 74 | # encrypt all plain text values in a file under the element 'secret_stuff' 75 | $ generate-secure-pillar -k "Salt Master" --element secret_stuff encrypt all --file us1.sls --outfile us1.sls 76 | 77 | # recurse through all sls files, encrypting all values 78 | $ generate-secure-pillar -k "Salt Master" encrypt recurse -d /path/to/pillar/secure/stuff 79 | 80 | # recurse through all sls files, decrypting all values (requires imported private key) 81 | $ generate-secure-pillar decrypt recurse -d /path/to/pillar/secure/stuff 82 | 83 | # decrypt a specific existing value (requires imported private key) 84 | $ generate-secure-pillar decrypt path --path "some:yaml:path" --file new.sls 85 | 86 | # decrypt all files and re-encrypt with given key (requires imported private key) 87 | $ generate-secure-pillar -k "New Salt Master Key" rotate -d /path/to/pillar/secure/stuff 88 | 89 | # show all PGP key IDs used in a file 90 | $ generate-secure-pillar keys all --file us1.sls 91 | 92 | # show all keys used in all files in a given directory 93 | $ generate-secure-pillar keys recurse -d /path/to/pillar/secure/stuff 94 | 95 | # show the PGP Key ID used for an element at a path in a file 96 | $ generate-secure-pillar keys path --path "some:yaml:path" --file new.sls 97 | `, 98 | Version: "1.0.634", 99 | } 100 | 101 | const all = "all" 102 | const recurse = "recurse" 103 | const path = "path" 104 | 105 | // Execute adds all child commands to the root command and sets flags appropriately. 106 | // This is called by main.main(). It only needs to happen once to the rootCmd. 107 | func Execute() { 108 | if err := rootCmd.Execute(); err != nil { 109 | fmt.Println(err) 110 | os.Exit(1) 111 | } 112 | } 113 | 114 | func init() { 115 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) 116 | cobra.OnInitialize(initConfig) 117 | 118 | // respect the env var if set 119 | gpgHome := os.Getenv("GNUPGHOME") 120 | if gpgHome != "" { 121 | publicKeyRing = fmt.Sprintf("%s/pubring.gpg", gpgHome) 122 | privateKeyRing = fmt.Sprintf("%s/secring.gpg", gpgHome) 123 | } 124 | 125 | // check for GNUPG1 pubring file 126 | filePath, err := tilde.Expand(publicKeyRing) 127 | if err != nil { 128 | logger.Fatal().Err(err).Msg("Error with GNUPG pubring path") 129 | } 130 | if _, err = os.Stat(filepath.Clean(filePath)); os.IsNotExist(err) { 131 | if err != nil { 132 | logger.Fatal().Err(err).Msg("Error finding GNUPG pubring file") 133 | } 134 | } 135 | 136 | rootCmd.PersistentFlags().Bool("version", false, "print the version") 137 | rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.config/generate-secure-pillar/config.yaml)") 138 | rootCmd.PersistentFlags().StringVar(&profile, "profile", "", "profile name from profile specified in the config file") 139 | rootCmd.PersistentFlags().StringVarP(&pgpKeyName, "pgp_key", "k", pgpKeyName, "PGP key name, email, or ID to use for encryption") 140 | rootCmd.PersistentFlags().StringVar(&publicKeyRing, "pubring", publicKeyRing, "PGP public keyring (default is $HOME/.gnupg/pubring.gpg)") 141 | rootCmd.PersistentFlags().StringVar(&privateKeyRing, "secring", privateKeyRing, "PGP private keyring (default is $HOME/.gnupg/secring.gpg)") 142 | rootCmd.PersistentFlags().StringVarP(&topLevelElement, "element", "e", "", "Name of the top level element under which encrypted key/value pairs are kept") 143 | } 144 | 145 | // initConfig reads in config file and ENV variables if set. 146 | func initConfig() { 147 | if cfgFile != "" { 148 | // Use config file from the flag. 149 | viper.SetConfigFile(cfgFile) 150 | } else { 151 | // Find home directory. 152 | home, err := homedir.Dir() 153 | if err != nil { 154 | fmt.Println(err) 155 | os.Exit(1) 156 | } 157 | 158 | configPath := fmt.Sprintf("%s/.config/generate-secure-pillar/", home) 159 | dir := filepath.Clean(configPath) 160 | err = os.MkdirAll(dir, 0700) 161 | if err != nil { 162 | logger.Fatal().Err(err).Msg("error creating config file path") 163 | } 164 | _, err = os.OpenFile(dir+"/config.yaml", os.O_RDONLY|os.O_CREATE, 0660) 165 | if err != nil { 166 | logger.Fatal().Err(err).Msg("Error creating config file") 167 | } 168 | 169 | // set config in "~/.config/generate-secure-pillar/config.yaml". 170 | viper.AddConfigPath(configPath) 171 | viper.SetConfigName("config") 172 | viper.SetConfigType("yaml") 173 | } 174 | 175 | viper.AutomaticEnv() // read in environment variables that match 176 | 177 | // If a config file is found, read it in. 178 | err := viper.ReadInConfig() // Find and read the config file 179 | if err != nil { // Handle errors reading the config file 180 | logger.Fatal().Err(err).Msg("Fatal error config file") 181 | } 182 | readProfile() 183 | } 184 | 185 | func getPki() pki.Pki { 186 | return pki.New(pgpKeyName, publicKeyRing, privateKeyRing) 187 | } 188 | 189 | func readProfile() { 190 | if viper.IsSet("profiles") { 191 | profiles := viper.Get("profiles") 192 | profName := "" 193 | if rootCmd.Flag("profile") != nil && rootCmd.Flag("profile").Value != nil { 194 | profName = rootCmd.Flag("profile").Value.String() 195 | } 196 | 197 | if profName != "" || pgpKeyName == "" { 198 | for _, prof := range profiles.([]interface{}) { 199 | p := prof.(map[string]interface{}) 200 | if p["default"] == true || profName == p["name"] { 201 | gpgHome := p["gnupg_home"].(string) 202 | if gpgHome != "" { 203 | publicKeyRing = fmt.Sprintf("%s/pubring.gpg", gpgHome) 204 | privateKeyRing = fmt.Sprintf("%s/secring.gpg", gpgHome) 205 | } 206 | if p["default_key"] != nil { 207 | pgpKeyName = p["default_key"].(string) 208 | } 209 | } 210 | } 211 | } 212 | } 213 | } 214 | 215 | // if we are getting stdin from a pipe we don't want 216 | // to output log info about it that could mess up parsing 217 | func stdinIsPiped() bool { 218 | fi, err := os.Stdin.Stat() 219 | if err != nil { 220 | logger.Fatal().Err(err).Msgf("Fatal error: %s", err) 221 | } 222 | if fi != nil { 223 | return ((fi.Mode() & os.ModeCharDevice) == 0) 224 | } 225 | 226 | // if something goes wrong assume we are piped 227 | return true 228 | } 229 | -------------------------------------------------------------------------------- /cmd/rotate.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Everbridge, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // Package cmd/rotate handles the rotation of PGP keys by decrypting and re-encrypting data in secure pillar files 22 | package cmd 23 | 24 | import ( 25 | "os" 26 | 27 | "github.com/Everbridge/generate-secure-pillar/sls" 28 | "github.com/Everbridge/generate-secure-pillar/utils" 29 | "github.com/rs/zerolog" 30 | "github.com/rs/zerolog/log" 31 | "github.com/spf13/cobra" 32 | ) 33 | 34 | // rotateCmd represents the rotate command 35 | var rotateCmd = &cobra.Command{ 36 | Use: "rotate", 37 | Short: "decrypt existing files and re-encrypt with a new key", 38 | Run: func(cmd *cobra.Command, args []string) { 39 | pk := getPki() 40 | 41 | if recurseDir != "" { 42 | err := utils.ProcessDir(recurseDir, ".sls", "rotate", outputFilePath, topLevelElement, pk) 43 | if err != nil { 44 | logger.Warn().Err(err).Msg("rotate") 45 | } 46 | } else if inputFilePath != "" { 47 | s := sls.New(inputFilePath, pk, topLevelElement) 48 | buf, err := s.PerformAction("rotate") 49 | utils.SafeWrite(buf, outputFilePath, err) 50 | } else { 51 | err := cmd.Help() 52 | if err != nil { 53 | logger.Fatal().Err(err) 54 | } 55 | } 56 | }, 57 | } 58 | 59 | func init() { 60 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) 61 | rootCmd.AddCommand(rotateCmd) 62 | rotateCmd.PersistentFlags().StringVarP(&recurseDir, "dir", "d", "", "recurse over all .sls files in the given directory") 63 | rotateCmd.PersistentFlags().StringVarP(&inputFilePath, "file", "f", "", "input file (defaults to STDIN)") 64 | } 65 | -------------------------------------------------------------------------------- /cmd/update.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Everbridge, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // Package cmd/update handles the updating of existing secure pillar files 22 | package cmd 23 | 24 | import ( 25 | "os" 26 | "path/filepath" 27 | "strings" 28 | 29 | "github.com/Everbridge/generate-secure-pillar/sls" 30 | "github.com/rs/zerolog" 31 | "github.com/rs/zerolog/log" 32 | "github.com/spf13/cobra" 33 | ) 34 | 35 | // updateCmd represents the update command 36 | var updateCmd = &cobra.Command{ 37 | Use: "update", 38 | Short: "update the value of the given key in the given file", 39 | Run: func(cmd *cobra.Command, args []string) { 40 | inputFilePath, err := filepath.Abs(inputFilePath) 41 | if err != nil { 42 | logger.Fatal().Err(err) 43 | } 44 | if inputFilePath != os.Stdin.Name() { 45 | outputFilePath = inputFilePath 46 | } 47 | 48 | secretNames := strings.Split(strings.Trim(cmd.Flag("name").Value.String(), "[]"), ",") 49 | secretValues := strings.Split(strings.Trim(cmd.Flag("value").Value.String(), "[]"), ",") 50 | pk := getPki() 51 | s := sls.New(inputFilePath, pk, topLevelElement) 52 | err = s.ProcessYaml(secretNames, secretValues) 53 | if err != nil { 54 | logger.Fatal().Err(err) 55 | } 56 | buffer, err := s.FormatBuffer("") 57 | if err != nil { 58 | logger.Fatal().Err(err) 59 | } 60 | _, err = sls.WriteSlsFile(buffer, outputFilePath) 61 | if err != nil { 62 | logger.Fatal().Err(err) 63 | } 64 | }, 65 | } 66 | 67 | func init() { 68 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) 69 | rootCmd.AddCommand(updateCmd) 70 | updateCmd.PersistentFlags().StringVarP(&inputFilePath, "file", "f", os.Stdin.Name(), "input file (defaults to STDIN)") 71 | updateCmd.PersistentFlags().StringArrayP("name", "n", nil, "secret name(s)") 72 | updateCmd.PersistentFlags().StringArrayP("value", "s", nil, "secret value(s)") 73 | } 74 | -------------------------------------------------------------------------------- /config_example.yaml: -------------------------------------------------------------------------------- 1 | profiles: 2 | - name: dev 3 | default: true 4 | default_key: Dev Salt Master 5 | gnupg_home: ~/.gnupg 6 | default_pub_ring: ~/.gnupg/pubring.gpg 7 | default_sec_ring: ~/.gnupg/secring.gpg 8 | 9 | - name: stage 10 | default: false 11 | default_key: Stage Salt Master 12 | gnupg_home: ~/.gnupg 13 | default_pub_ring: ~/.gnupg/pubring.gpg 14 | default_sec_ring: ~/.gnupg/secring.gpg 15 | 16 | - name: prod 17 | default: false 18 | default_key: Prod Salt Master 19 | gnupg_home: ~/.gnupg 20 | default_pub_ring: ~/.gnupg/pubring.gpg 21 | default_sec_ring: ~/.gnupg/secring.gpg 22 | -------------------------------------------------------------------------------- /ebos.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQINBFrDk88BEADMcZzClUTMPfiSYQNP2e8VzPVtsJh2iIQe2kp9W/M/LtWJRfQu 4 | 2uBYHUB5SmREg6qElCHpWCFMYJzuk8I4Cof4lSV9RpcMzgwlyQ2DSAh4qNbSv1No 5 | Y2tFGbfLqFZ+39PimOkKD7N0+lP9gVjA+YCbne+Ymklw7nOBY2W31+1gfJKrQnD4 6 | bBConESHiBcrhz7RM/Muqgw8jwCgr6CiGtbTfWFaFFPcGqHoValOHvHWu65lZutG 7 | s20nVnvOkcZdbmB4buWhsnfNoupIP/0Ef1fUI+fk6mYSCd+TowoB2UqyoeF1ayJV 8 | ctqvMotkyr5dQ1WrQHvdPHIPqLYyaI6SL4a468FVn/oWfn6yba0th6oZ8lRiWX+g 9 | h8S3m9m8+paxWtOjRjm2wxYYtQu33sua+rZrwEdMGeIeOlGNH8Rqlg5bSG1SJVYI 10 | ItKAoifBSYmrXpnSmG5rLI//NZODbDHKLBVDQ+rDmTttACBL946q1G55POzvNGEl 11 | cozvEzkFGXnwL5moaD/lJgnFyp5yvzTbyJ+A4+rWlQ8erJHWvX7L83mWTxJAEfHn 12 | FeOIh8vrNeXoAEr2WPawve59OcaAGxhw43DqvBWrdHnbml3+0kzJmKSbxpmO6OFN 13 | OT4QU4EAUJLW6BXPg1nemMUL8vDENLHM+bFnClsi5UDfa+bjRsvT79NqKwARAQAB 14 | tBZFdmVyYnJpZGdlIE9wZW4gU291cmNliQI4BBMBAgAiBQJaw5PPAhsDBgsJCAcD 15 | AgYVCAIJCgsEFgIDAQIeAQIXgAAKCRDxBAwFplFdhVF2EACFEDJCPRKVYPGjiJXD 16 | QT5ooLbY1PBf07r4nnBtqFTRLl/wTKpe7IkYd+mGSqRhZlBjW1JDFr2zW4P0jCNY 17 | XbbcOAEQU8JAO2IVgp+SXmmOucwHYqgg9PC/nqrax+XAsfnDAiqhbSpgKuDQQk6r 18 | clon8B6oevDKX9AuPto6ocss9nNSR+1JgLDfeAtSQardhXBsgUE+XjVA1MiEdB0e 19 | Q0YiOKWfJVztipA9oThj+t8wRR3zDuqsCEYNAYDcczfS5lJXWxpviAz/YFoc9x5q 20 | mZXRTFeergZFC7lkbmVWlaGDolP+ySXjReNAUZyC9aUJOlI3hlNu1A3jSTCs/8b9 21 | o739ftJ0Yuep6ZvMlk1f6oUMalyMLhMi/LqLtz2PUFrR67qj0QvOIHdIdeI/NZ+g 22 | GmmDu1biWpTcb5IYecmqaRyFsBcKmpZzE2q6wFGK6+OpBVUGK21g5lW2x91dwFpO 23 | AKRrx1mWG46DCk6Uoa65W9u1OGOsrtBGkouk1tUWSiDC8du+cCA33Vz6EGs2QYTD 24 | BXe7rlh5DFxruBsXilLSkUR13TSlDJkMjLH5hI/bc8DiTILxouglInCpGw1fL5qE 25 | TEhnuQMU4JyF1XbRKuaM50ZrUORVc527V9CvrQa6/ipWcsG3J7dj7bEaD6qyWpyt 26 | b7FeK0+/kLvFCZWXjBMOu25ClbkCDQRaw5PPARAAvz1NZFeEkXSXxFJXWTsZvHWf 27 | YaiMsHncfYekMrswolpJAop72+AM/YGVlR4MH1zCodU0nC+swoCTvqiXySH6ReVn 28 | FrQE+aPTmmBGVg4q2x1uZ5QEZ8wUVQ+uU91A0N9H1J62EipBj499zBf7E1BApca7 29 | G+tWEgwBj59GHUc/OQ8kt2TtYCt55qLhneJJzeo4wnBooZPCNzpYmWrAdwTWajRX 30 | yQsMMcaJUP8NeD+HcbM8wQS79gwvEiG98hyzeKMKUiKCxOmoUZmp/vbkwzYB2dqG 31 | iYDb4HG0VwHxFQQerx7C6A1MXT0LF3+a+9D9XsgQa6s8f6zJTZemUl1W3MeGyvcs 32 | MMAMLruHO7MKXL6FpoYTWwXuVfGHYZnDMgKMU3Qkxqk+0NrkoNeQf8gDR9aETqDs 33 | 1fANmGt1ncHKqvcglz21FX8zYcrj+79J4CXNliIiDT3IINsBI8kRt9/UY6y3XBD5 34 | Sx2cEO6K7fas1Bd2ZR5NU1tlXbL186QicYyGaj1DZDm3RtoOaoaHuvnxfxhyYGQt 35 | RCCwOx8LyXIV0mCgHfN+/e1JVP2sql/4jOwLC0VHHgBFj/YSQSBAOuTPIycYTXab 36 | 1eKvyHsBXsdS4JHWfSS77PbMiY8MwD2ksb0CA41MJnmsnEBFDg6upb+lKWzEdYyU 37 | QA+4yz1BxXvAlIXFkOsAEQEAAYkCHwQYAQIACQUCWsOTzwIbDAAKCRDxBAwFplFd 38 | hfoaD/9ifTWRGJh1p1x9IX81SqBCrFdudgyuvLzezp+vnkapMvMnaKxb1bfwrPb/ 39 | 3KKbkMol6zQrjA4PAQjGuDd/cu6IcMjUEiAuWUL4ZpBJz03ox//gJ+mw7tgqg3w3 40 | 9AJIumYK8dug8ug3I5OXWaV0WgjBpPyO0ZTwWcR2JOf0bF1xCpvy74eGYbrT+GeR 41 | e2BVoAYJqCqhZAMw7M4U74DkV7qie5p8l0YksIf3duX3GKzZBSr6SZ0tODyLTuPN 42 | eyXoq9YhrL1FRd8CO7YcXAlWrwqzBEAI4GY1xjMIG5vjpm0G3KdriUhQ56xMxcFE 43 | McsXRFRSHVnc+3ax3ANUblY7WYc+xFygDRKhX2goMPJAXqP8eHQmY+mnoXKC09Wk 44 | b8qN4GGGYDDUycNaDoXSt83thWC3J0moSc7ZHTmi24Bqorb5LRgLihhInFuHGNqH 45 | NGR+QPWbsMC3HxpFMb8Zt/54OER0s2AATxWzAOBFq3jAh3pRzxToovCfwQAvZyLj 46 | ygOXbsfKZfPxwJdHnUOC5xQrhEwQCjl7nVeIlrxuP5BEq4rssBR5UDBW1XKeND4p 47 | ztVezL2PcbRmRUqxA+kuIKzDwKunpaLO3UTiw0gz3e+VDI5gZ/45mo55iKCayvRi 48 | IaQcY0hSzYGIa1QAAixjBL+fwHS99UHZw/WAjuW/ApyJPMwSybkCDQRaw5ToARAA 49 | 0Q+qUIgAldRXRbb5XoBVJb57gW6zYzSRaSvoaHCSWSFikQN61p2rM2LWfKcrmG5m 50 | jHozVuucZHWEF126LEvowDSCsYtb+eaY4UMyIJMjvV97oNdmA/GptybuRGwBGNyN 51 | IOiHptOjfb4fqUCv5RKWr7nBrdeeNeksKb+KgwHn6pSc3VZ8ePTTfRPhv/gRK9/+ 52 | Dhx/+lDQNduIdFw/bF4W6HX/K92OR3WeXzEOjpL9rxI1QHB1nkKjNTqVIt2Rf4X+ 53 | SJyUblsYyOk7KixJILv2XQZXksuo8W2oYjQcnI2+ErJVOIIIZWj9g6CwtIq+zCxU 54 | hTjzbXneQbWbCkCn/UAg400UxHdJscZeLebO7oa61ZriSCYhnTOkeJ0U0YmHaeR0 55 | XeF/H9revi2v0byUkjFewLWfKk4apfPv1xPtWyzf43pfMAU4GDkOp+wv5sGnlugv 56 | 0wkXHvvB+7LAV3hbOkVZNXE9rt0/Qq6r8TiKdLAtJ+A2rWzyXT55D6nybua1ZpmS 57 | uGIDe+5F2g6iNZShYgoEEVRBUd8VXJ7mOYCbEUVAIcgYSTnehSgGiHlHQ3333bm9 58 | qfABW+Y5S+d8U5RmXWwLiDivaAu5GYqcB9JCpRE7do7F4Y6sm60kjUZJurkbM19h 59 | Ik314rvwT2EgLgAnzEDjK2Xs8obK/azBuT10aJN9QA0AEQEAAYkEPgQYAQIACQUC 60 | WsOU6AIbAgIpCRDxBAwFplFdhcFdIAQZAQIABgUCWsOU6AAKCRAtm6aa1PK9xT/k 61 | D/426X9DK+mlLurFOrpqUBmRuqL3FaKnkLq/icU5Ehc1yHLB+H2hc+UAnqdeW9k8 62 | XsVIA1i48oJbp76ObFm3Jbf+VWPXLGMvTqyZxOC71No8wiUvedUq22ufbOzKBJe+ 63 | fHxU8psfgzIi9SD29HLjx0lebvX8Eqr9nPKOkPuMZ9X7cEu0N3m/7HpD3G4KQ3zs 64 | xUXxepstlXyEx7WeQzET0RlLQYW9xZQKcsxmsX8lwAnvzQTxgDDjiXYBrec8k0LC 65 | 3yxil0TQVxNeXQaqL1qlRqgPghd+nJrliH0Hz60gJJD84e7KzArv7qNB+vw1wg8j 66 | HJNIopIuK5xdDT4IuXfN7POePn7rGj/ddx4cKgTjO5FOHqUqz6zDA3EZBMi9XJN1 67 | JFz8aNvP/7pS2o0QBAWznBorI7YrU+tEOdg9e8Xq2TjPM6r5AaQlQHyDyppUD5fV 68 | PtYzXl5gOygk1+WNPIOHBoMSp7/q7f8DiCfIddKw2R5Vn2IJk91ktGtORswWi4RP 69 | 5pdf5QNgBYR6EzeWOH3Ofdt1LYyqEvuv3xXun1sYE+WidNEdoqqmXmgtOUVXK6yy 70 | KKXdYp+zaIIG/w6uu/4Uxh9WaeU0hA1zzFwnhnIEKNgYQ0bIoF3GCyi63t/u0FLj 71 | FiZW2U5mmuI+yhrYGymg+NAyfqwThYz748zzzMjx/3fTxM7tD/wKTgHISroGop2J 72 | 2rcJd7TYCvZbtbSr7E98JsCqMzf8vqp9vsv/3LzYCDAftZ43dT4foIezjqHQLsAf 73 | mSIYnr1VOM18NY2+cxYs16Mbzktz6PdAvsk84fGBmhelaeyKu4HmQiXOJ95JYaey 74 | Jt0dRhXhsBkfrHSJe9UdpLYhkxGocOsyYsNDFDrsRHgK2f+0eyk+nPaOIgSxQwrk 75 | XhZ8k0T105Oq4ySHPD96UIcRu2G7xzodAv5Es++N+2CjFxHvAwXFixT6Uni74nyQ 76 | jYKs07nSnk1gqRfJ1gislXReylE0jvXU1oyReTrZ97HAsdw4MbiNtSHgqY2HQMh/ 77 | 2/PvCm0YIw18yXshS0nhMwLatpa3tUBIegv5+Nbe3apoZtAoapzTPhPfqnPt5Ohd 78 | niIOtckpGs5O/vlqtJLjkJcMEz4dWxy01rQbRyGy1OeTbcpjN0bAGtD7O5LwpE7F 79 | FtrhH82wCha6NasgtYIaJp1YqpndIgl8IVs9E1BiyoO04eDSCxyMaomizQ5DHJKi 80 | nocXEIxrwLxvZHbWWTDknIirpkVCtqDb6O5jlnU/7CTpEOPvJQhosBJkBRXXKsy9 81 | dVjPU5Tlqnp7E+nu3BysBNX16rAlPlv5OaXesQi6Ot5zyFzI3xxK5o1QD+oH4Bbn 82 | 5RZCqgBlxLEq4PdrOcQn2dv8QjUDUrkCDQRaw5UOARAAyoW3tJE/o8APwECW7Up3 83 | FkDI96OLLsacx21tfXBYmWf3phv8U8KYMXqOUf9nKTGQVyTJPhnNIPyY3jvgM5PV 84 | yJXZQwayTwV6RWoydPvCVD758QjcvpNNPK4gCUOKLiezd/tU7W0T+AwS8LN2Fvme 85 | ucwa8l3Ygg85QX4gsBvihkcDCpTx1YNTZZ7dKRS8EFLoXtrlFE/XWfypMae4fgbp 86 | 0yQBK3dSrsFRakGEaJP1peCiqAq0swtW3RFEHG/XdcvGdOG/edfVgPjU5f/fUxDe 87 | J6ea72HYfgwOaiCn6JbOw/iw+KLny0Jkui5rgPZDNw3ayd69GccI1orNt51kJ3NO 88 | wuIa+JiaGtjC2fdDHlsIc4VsPvRoLTxieK9/5wlo31YlAqPfHsm80oJZRqJOmucy 89 | q2rCfD3KFTaEPGC9IpYQjHqlCkDd7uRlh97hFl+muI8uiGnAszjxQn2KnolBM1YW 90 | ZFbrbNTMFrl7Bz2WSE4WgP69k91qVQ+VZK0+pkwsnm0IU2hXbQwRP5Btt8+YwiXR 91 | r4QuWEwu83J17S+FpuN5q6V+FQ0QHa67juDs2r8ritAhqkKfjCbJA0Yz9/Fi+x7r 92 | pFuZconL9PxHIboBiYSs3ZEEfhdHzBJcW61wGiixOCaCbtMRyBU12pfNWSzOfGIZ 93 | Y6oey/uhCr0ioVu2Ra9leqMAEQEAAYkCHwQYAQIACQUCWsOVDgIbDAAKCRDxBAwF 94 | plFdhSfREACSD4lbfJTMX3BQq31SlKvS6cSIrTgelwwEBlLWwkLt8dE1wjsyPadB 95 | dSS9RZXqBccuu/LEhJWdblRllyeaF1CjcyWgfm3dKEsQnXC0pRdQea5z6jFlZAu3 96 | 9REDx5dAuwQLshfAdvtZ0I3XXCLHmd34tga9rUBiTSB/CW2lD+n9nUtkHeDc036K 97 | Y0JeoQQHFTxu918x2LQv4TCqLttlYGls5TkFHeNyJoA9O3KoxJxp/KfqgXhvVEbY 98 | 9zxfLmCrNQfTSdtzGoAx+vCDqFeJ50M+vmovIFQkqWdrUX1S7HLCvVlvw9Kd7nIe 99 | kFNdBIGErR49wdzN90ce9Gok3Lg6OPFGNIscHSZGHmCmuIb3i03/ZUeo1VN/1f9e 100 | Z85uvN78eCVq2ZzSm3Xfly3YD5G5abHkVexo3KGIHA3hhVy67STbKA0oyw7CX/pG 101 | DF/y3sWYy7r8IG3IQVNx5MhJRA4JVbtXjf5QaYsB/h2L6yi8HLRN9uIvvY+DSiHL 102 | rCRdgghYPFS1nKEPYohTPxN075s6+hwbG43GiL0EO//76eNBRshxMD1CQLKDUaor 103 | QtflqFt7ZIBNa43PM26zYeoEMN1nrTww9ySIYFTIsAMNdsvsDsJoBuNmFk573xk1 104 | 2Zti5EkctDmcLOl5+Q8JzXAZQx5H0Qxx+cpcqoQppBY+wuOooEe/aA== 105 | =OMN+ 106 | -----END PGP PUBLIC KEY BLOCK----- 107 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Everbridge/generate-secure-pillar 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 7 | github.com/edlitmus/ezyaml v0.0.0-20231025150529-6f3a38cc5cf6 8 | github.com/esilva-everbridge/yaml v0.0.0-20230222145725-586d68d00607 9 | github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 10 | github.com/mitchellh/go-homedir v1.1.0 11 | github.com/rs/zerolog v1.31.0 12 | github.com/ryboe/q v1.0.19 13 | github.com/spf13/cobra v1.6.1 14 | github.com/spf13/viper v1.15.0 15 | gopkg.in/mattes/go-expand-tilde.v1 v1.0.0-20150330173918-cb884138e64c 16 | gopkg.in/yaml.v3 v3.0.1 17 | ) 18 | 19 | require ( 20 | github.com/edlitmus/dig v0.0.0-20231025150220-13b7e66ce5ac // indirect 21 | github.com/edlitmus/to v0.0.0-20231025141937-dd8488388a59 // indirect 22 | github.com/esilva-everbridge/dig v0.0.0-20230222145646-42ad4ced5ae3 // indirect 23 | github.com/esilva-everbridge/to v0.0.0-20220524174750-493ecaf861c8 // indirect 24 | github.com/fsnotify/fsnotify v1.6.0 // indirect 25 | github.com/hashicorp/hcl v1.0.0 // indirect 26 | github.com/inconshreveable/mousetrap v1.0.1 // indirect 27 | github.com/kr/pretty v0.3.1 // indirect 28 | github.com/kr/text v0.2.0 // indirect 29 | github.com/magiconair/properties v1.8.7 // indirect 30 | github.com/mattn/go-colorable v0.1.13 // indirect 31 | github.com/mattn/go-isatty v0.0.19 // indirect 32 | github.com/mitchellh/mapstructure v1.5.0 // indirect 33 | github.com/mozilla-services/yaml v0.0.0-20201007153854-c369669a6625 // indirect 34 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect 35 | github.com/rogpeppe/go-internal v1.9.0 // indirect 36 | github.com/sergi/go-diff v1.3.1 // indirect 37 | github.com/spf13/afero v1.9.3 // indirect 38 | github.com/spf13/cast v1.5.0 // indirect 39 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 40 | github.com/spf13/pflag v1.0.5 // indirect 41 | github.com/subosito/gotenv v1.4.2 // indirect 42 | golang.org/x/sys v0.12.0 // indirect 43 | golang.org/x/text v0.5.0 // indirect 44 | gopkg.in/ini.v1 v1.67.0 // indirect 45 | ) 46 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 7 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 8 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 9 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 10 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 11 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 12 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 13 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 14 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 15 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 16 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 17 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= 18 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= 19 | cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= 20 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 21 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 22 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 23 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 24 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 25 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 26 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 27 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 28 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 29 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 30 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 31 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 32 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 33 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 34 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 35 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 36 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 37 | cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= 38 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 39 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 40 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 41 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= 42 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= 43 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 44 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 45 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 46 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 47 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 48 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 49 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 50 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 51 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 52 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 53 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 54 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 55 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 56 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 57 | github.com/edlitmus/dig v0.0.0-20231025150220-13b7e66ce5ac h1:RKRHNxeWKsDLBskJO3RP2iNJYPu+6r9lOp2Q/2/u2Bw= 58 | github.com/edlitmus/dig v0.0.0-20231025150220-13b7e66ce5ac/go.mod h1:qI0S3QmXvueEML9VYzbdxrc11Sshlue7P2WMseqpwew= 59 | github.com/edlitmus/ezyaml v0.0.0-20231025150529-6f3a38cc5cf6 h1:q6v8sBlpnCGJPXLLAedkxFXRfcxsNiMeUdgn09k4in8= 60 | github.com/edlitmus/ezyaml v0.0.0-20231025150529-6f3a38cc5cf6/go.mod h1:jRVmFKOBEiBDHHvCpfrOHS8cCtOMuxDDLddys3HJUhw= 61 | github.com/edlitmus/to v0.0.0-20231025141937-dd8488388a59 h1:UFWUbAi6CJX4GCDMwM7X0fcnAwL0nMwydTqlJDEZEb4= 62 | github.com/edlitmus/to v0.0.0-20231025141937-dd8488388a59/go.mod h1:MVh3dcMcrpKKzrsZ7TA78B9d+GgfoqVaTo+vDQxKcGk= 63 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 64 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 65 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 66 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 67 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 68 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 69 | github.com/esilva-everbridge/dig v0.0.0-20230222145646-42ad4ced5ae3 h1:fl47Gz55o+rUs7OkMwGQZN3Abs5efRKMCcDBWOOwhfk= 70 | github.com/esilva-everbridge/dig v0.0.0-20230222145646-42ad4ced5ae3/go.mod h1:9gvntW38KlY7PWDZIkjBxPhuOT+ykm3LgPxxDwLGjXc= 71 | github.com/esilva-everbridge/to v0.0.0-20220524174750-493ecaf861c8 h1:dLx7TI6NatfvOT4ILPnIE1W0NTI7XAPHXKVePRPJUZ0= 72 | github.com/esilva-everbridge/to v0.0.0-20220524174750-493ecaf861c8/go.mod h1:4TQUsQ+fnGppSGWJvgY2d2wxBth2F5ym2F/myZPU+PY= 73 | github.com/esilva-everbridge/yaml v0.0.0-20230222145725-586d68d00607 h1:Xdcs05/N3W4FHbrA96iwlZAImiW7Ezx+omPfPWHm1Mg= 74 | github.com/esilva-everbridge/yaml v0.0.0-20230222145725-586d68d00607/go.mod h1:zQveO9otHmbxtT5gXhdnjYaV66dyLs4CY/wgPqT6tpI= 75 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= 76 | github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= 77 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 78 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 79 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 80 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 81 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 82 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 83 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 84 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 85 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 86 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 87 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 88 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 89 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 90 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 91 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 92 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 93 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 94 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 95 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 96 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 97 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 98 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 99 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 100 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 101 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 102 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 103 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 104 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 105 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 106 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 107 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 108 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 109 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 110 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 111 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 112 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 113 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 114 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 115 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 116 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 117 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 118 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 119 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 120 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 121 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 122 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 123 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 124 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 125 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 126 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 127 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 128 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 129 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 130 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 131 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 132 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 133 | github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 134 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 135 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 136 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 137 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 138 | github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= 139 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 140 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 141 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 142 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 143 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 144 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 145 | github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= 146 | github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 147 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 148 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 149 | github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 h1:cTxwSmnaqLoo+4tLukHoB9iqHOu3LmLhRmgUxZo6Vp4= 150 | github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= 151 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 152 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 153 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 154 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 155 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 156 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 157 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 158 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 159 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 160 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 161 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 162 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 163 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 164 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 165 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 166 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 167 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 168 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 169 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 170 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 171 | github.com/mozilla-services/yaml v0.0.0-20201007153854-c369669a6625 h1:5IeGQzguDQ+EsTR5HE7tMYkZe09mqQ9cDypdKQEB5Kg= 172 | github.com/mozilla-services/yaml v0.0.0-20201007153854-c369669a6625/go.mod h1:Is/Ucts/yU/mWyGR8yELRoO46mejouKsJfQLAIfTR18= 173 | github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= 174 | github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= 175 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 176 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 177 | github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 178 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 179 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 180 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 181 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 182 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 183 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 184 | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 185 | github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= 186 | github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= 187 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 188 | github.com/ryboe/q v1.0.19 h1:1dO1anK4gorZRpXBD/edBZkMxIC1tFIwN03nfyOV13A= 189 | github.com/ryboe/q v1.0.19/go.mod h1:IoEB3Q2/p6n1qbhIQVuNyakxtnV4rNJ/XJPK+jsEa0M= 190 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 191 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 192 | github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= 193 | github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= 194 | github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= 195 | github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= 196 | github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= 197 | github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= 198 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= 199 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 200 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 201 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 202 | github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= 203 | github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= 204 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 205 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 206 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 207 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 208 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 209 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 210 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 211 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 212 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 213 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 214 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 215 | github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= 216 | github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= 217 | github.com/xiam/to v0.0.0-20200126224905-d60d31e03561 h1:SVoNK97S6JlaYlHcaC+79tg3JUlQABcc0dH2VQ4Y+9s= 218 | github.com/xiam/to v0.0.0-20200126224905-d60d31e03561/go.mod h1:cqbG7phSzrbdg3aj+Kn63bpVruzwDZi58CpxlZkjwzw= 219 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 220 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 221 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 222 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 223 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 224 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 225 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 226 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 227 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 228 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 229 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 230 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 231 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 232 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 233 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 234 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 235 | golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 236 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 237 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 238 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 239 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 240 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 241 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 242 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 243 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 244 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 245 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 246 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 247 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 248 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 249 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 250 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 251 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 252 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 253 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 254 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 255 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 256 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 257 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 258 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 259 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 260 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 261 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 262 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 263 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 264 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 265 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 266 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 267 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 268 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 269 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 270 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 271 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 272 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 273 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 274 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 275 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 276 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 277 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 278 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 279 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 280 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 281 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 282 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 283 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 284 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 285 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 286 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 287 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 288 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 289 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 290 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 291 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 292 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 293 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 294 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 295 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 296 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 297 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 298 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 299 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 300 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 301 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 302 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 303 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 304 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 305 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 306 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 307 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 308 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 309 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 310 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 311 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 312 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 313 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 314 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 315 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 316 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 317 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 318 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 319 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 320 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 321 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 322 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 323 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 324 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 325 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 326 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 327 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 328 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 329 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 330 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 331 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 332 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 333 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 334 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 335 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 336 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 337 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 338 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 339 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 340 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 341 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 342 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 343 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 344 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 345 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 346 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 347 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 348 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 349 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 350 | golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 351 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 352 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 353 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 354 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 355 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 356 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 357 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 358 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 359 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 360 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 361 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 362 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 363 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 364 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 365 | golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= 366 | golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 367 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 368 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 369 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 370 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 371 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 372 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 373 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 374 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 375 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 376 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 377 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 378 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 379 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 380 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 381 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 382 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 383 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 384 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 385 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 386 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 387 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 388 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 389 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 390 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 391 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 392 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 393 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 394 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 395 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 396 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 397 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 398 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 399 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 400 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 401 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 402 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 403 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 404 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 405 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 406 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 407 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 408 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 409 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 410 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 411 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 412 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 413 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 414 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 415 | golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 416 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 417 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 418 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 419 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 420 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 421 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 422 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 423 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 424 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 425 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 426 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 427 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 428 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 429 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 430 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 431 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 432 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 433 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 434 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 435 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 436 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 437 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 438 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= 439 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 440 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 441 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 442 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 443 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 444 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 445 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 446 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 447 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 448 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 449 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 450 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 451 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 452 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 453 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 454 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 455 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 456 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 457 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 458 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 459 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 460 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 461 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 462 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 463 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 464 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 465 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 466 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 467 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 468 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 469 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 470 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 471 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 472 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 473 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 474 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 475 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 476 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 477 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 478 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 479 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 480 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 481 | google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 482 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 483 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 484 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 485 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 486 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 487 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 488 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 489 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 490 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 491 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 492 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 493 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 494 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 495 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 496 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 497 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= 498 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 499 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 500 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 501 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 502 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 503 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 504 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 505 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 506 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 507 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 508 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 509 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 510 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 511 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 512 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 513 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 514 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 515 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 516 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 517 | gopkg.in/mattes/go-expand-tilde.v1 v1.0.0-20150330173918-cb884138e64c h1:/Onz8dZtKBCmB8P0JU7+WSCfMekXry7BflVO0SQQrCU= 518 | gopkg.in/mattes/go-expand-tilde.v1 v1.0.0-20150330173918-cb884138e64c/go.mod h1:j6QavCO5cYWud1+2/PFTXL1y6tjjkhSs+qcWgibOIc0= 519 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 520 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 521 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 522 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 523 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 524 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 525 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 526 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 527 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 528 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 529 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 530 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 531 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 532 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 533 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 534 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Everbridge, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // Package main 22 | package main 23 | 24 | import ( 25 | "github.com/Everbridge/generate-secure-pillar/cmd" 26 | ) 27 | 28 | func main() { 29 | cmd.Execute() 30 | } 31 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Everbridge, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package main 22 | 23 | import ( 24 | "bufio" 25 | "encoding/json" 26 | "flag" 27 | "fmt" 28 | "os" 29 | "os/exec" 30 | "path" 31 | "path/filepath" 32 | "reflect" 33 | "regexp" 34 | "runtime" 35 | "sort" 36 | "strings" 37 | "testing" 38 | 39 | "github.com/Everbridge/generate-secure-pillar/pki" 40 | "github.com/Everbridge/generate-secure-pillar/sls" 41 | "github.com/Everbridge/generate-secure-pillar/utils" 42 | "github.com/andreyvit/diff" 43 | yaml "github.com/edlitmus/ezyaml" 44 | ) 45 | 46 | var pgpKeyName string 47 | var publicKeyRing string 48 | var secretKeyRing string 49 | var topLevelElement string 50 | var update = flag.Bool("update", false, "update golden files") 51 | var dirPath string 52 | 53 | type CLITest struct { 54 | name string 55 | args []string 56 | fixture string 57 | count int 58 | } 59 | 60 | func TestMain(m *testing.M) { 61 | initGPGDir() 62 | defer teardownGPGDir() 63 | m.Run() 64 | } 65 | 66 | func TestCliArgs(t *testing.T) { 67 | pgpKeyName, publicKeyRing, secretKeyRing = getTestKeyRings() 68 | topLevelElement = "" 69 | binaryName := "generate-secure-pillar" 70 | 71 | // set up: encrypt the test sls files 72 | _, slsCount := utils.FindFilesByExt(dirPath, ".sls") 73 | Equals(t, 7, slsCount) 74 | pk := pki.New(pgpKeyName, publicKeyRing, secretKeyRing) 75 | defer func() { 76 | _ = utils.ProcessDir(dirPath, ".sls", sls.Decrypt, "", topLevelElement, pk) 77 | }() 78 | 79 | tests := []CLITest{ 80 | {"no arguments", []string{}, "testdata/no-args.golden", 0}, 81 | {"encrypt recurse", []string{"-k", "Test Salt Master", "encrypt", "recurse", "-d", dirPath}, "testdata/encrypt-recurse.golden", 0}, 82 | {"keys recurse", []string{"-k", "Test Salt Master", "keys", "recurse", "-d", dirPath}, "testdata/keys-recurse.golden", 26}, 83 | {"keys recurse bad", []string{"-k", "Test Salt Master", "keys", "recurse", "-f", dirPath}, "testdata/keys-recurse-bad.golden", 0}, 84 | {"decrypt recurse", []string{"-k", "Test Salt Master", "decrypt", "recurse", "-d", dirPath}, "testdata/decrypt-recurse.golden", 0}, 85 | {"encrypt file", []string{"-k", "Test Salt Master", "encrypt", "all", "-f", dirPath + "/test.sls", "-u"}, "testdata/encrypt-file.golden", 0}, 86 | {"keys file", []string{"-k", "Test Salt Master", "keys", "all", "-f", dirPath + "/test.sls"}, "testdata/keys-file.golden", 12}, 87 | {"keys path", []string{"-k", "Test Salt Master", "keys", "path", "-f", dirPath + "/test.sls", "-p", "key"}, "testdata/keys-path.golden", 1}, 88 | {"keys count", []string{"-k", "Test Salt Master", "keys", "count", "-v", "-f", dirPath + "/test.sls"}, "testdata/keys-count.golden", 1}, 89 | {"decrypt path", []string{"-k", "Test Salt Master", "decrypt", "path", "-f", dirPath + "/test.sls", "-p", "key", "-u"}, "testdata/decrypt-path.golden", 0}, 90 | {"decrypt file", []string{"-k", "Test Salt Master", "decrypt", "all", "-f", dirPath + "/test.sls", "-u"}, "testdata/decrypt-file.golden", 0}, 91 | } 92 | 93 | err := os.Setenv("GNUPGHOME", dirPath+"/gnupg") 94 | if err != nil { 95 | t.Fatal(err) 96 | } 97 | 98 | for _, tt := range tests { 99 | args := tt.args 100 | count := tt.count 101 | fixture := tt.fixture 102 | name := tt.name 103 | 104 | t.Run(name, func(t *testing.T) { 105 | dir, err := os.Getwd() 106 | if err != nil { 107 | t.Fatal(err) 108 | } 109 | 110 | cmd := exec.Command(path.Join(dir, binaryName), args...) 111 | output, err := cmd.CombinedOutput() 112 | if err != nil { 113 | t.Fatalf("%s:\n%s%s", err, output, args) 114 | } 115 | ex := cmd.ProcessState.ExitCode() 116 | if ex != 0 { 117 | t.Errorf("Key command error, expected 0 got %d", ex) 118 | } 119 | 120 | actual := getActual(output) 121 | if *update { 122 | writeFixture(t, fixture, []byte(actual)) 123 | } 124 | 125 | // due to the way the output is generated we normalize the recursive output 126 | switch name { 127 | case "keys file": 128 | case "keys path": 129 | case "keys recurse": 130 | actualCount := keyNameCount(actual, "Test Salt Master") 131 | if actualCount != count { 132 | t.Errorf("Key name count error, expected %d got %d", count, actualCount) 133 | } 134 | 135 | case "keys count": 136 | actualCount := keyNameCount(actual, "1 keys found:") 137 | if actualCount != count { 138 | t.Errorf("Key count error, expected %d got %d", count, actualCount) 139 | } 140 | 141 | case "no arguments": 142 | actual := strings.TrimSpace(actual) 143 | expected := strings.TrimSpace(getExpected(t, fixture)) 144 | 145 | scanner := bufio.NewScanner(strings.NewReader(actual)) 146 | for scanner.Scan() { 147 | line := scanner.Text() 148 | if !strings.Contains(line, expected) { 149 | t.Errorf("Output error:\n%v", diff.LineDiff(expected, actual)) 150 | } 151 | break 152 | } 153 | 154 | case "decrypt path": 155 | actual := strings.TrimSpace(actual) 156 | expected := strings.TrimSpace(getExpected(t, fixture)) 157 | if !strings.Contains(expected, actual) { 158 | t.Errorf("Output error:\n%v", diff.LineDiff(expected, actual)) 159 | } 160 | 161 | default: 162 | if *update { 163 | writeFixture(t, fixture, []byte(actual)) 164 | } 165 | 166 | expected := strings.TrimSpace(getExpected(t, fixture)) 167 | actual := strings.TrimSpace(actual) 168 | 169 | exp, err := getLinesAsJSON(expected) 170 | if err != nil { 171 | t.Fatal(err) 172 | } 173 | act, err := getLinesAsJSON(actual) 174 | if err != nil { 175 | t.Fatal(err) 176 | } 177 | 178 | ActLoop: 179 | for _, a := range act { 180 | ExpLoop: 181 | for _, e := range exp { 182 | if !strings.Contains(a["message"], e["message"]) { 183 | t.Errorf("Output error:\n%v", diff.LineDiff(e["message"], a["message"])) 184 | break ExpLoop 185 | } else { 186 | break ActLoop 187 | } 188 | } 189 | } 190 | } 191 | }) 192 | } 193 | } 194 | 195 | func getLinesAsJSON(lines string) ([]map[string]string, error) { 196 | var jsonArr []map[string]string 197 | 198 | // iterate over lines in string 199 | scanner := bufio.NewScanner(strings.NewReader(lines)) 200 | for scanner.Scan() { 201 | line := scanner.Text() 202 | var e map[string]string 203 | err := json.Unmarshal([]byte(line), &e) 204 | if err != nil { 205 | return nil, err 206 | } 207 | jsonArr = append(jsonArr, e) 208 | } 209 | return jsonArr, nil 210 | } 211 | 212 | func getActual(output []byte) string { 213 | return cleanAndSort(string(output)) 214 | } 215 | 216 | func getExpected(t *testing.T, fixture string) string { 217 | return cleanAndSort(loadFixture(t, fixture)) 218 | } 219 | 220 | func cleanAndSort(str string) string { 221 | // need to remove timestamps 222 | reg := regexp.MustCompile(`(?m)time=\".*?\"\s`) 223 | str = reg.ReplaceAllString(str, "") 224 | lines := strings.Split(str, "\n") 225 | sort.Strings(lines) 226 | return strings.Join(lines, "\n") 227 | } 228 | 229 | func keyNameCount(str string, needle string) int { 230 | lines := strings.Split(str, "\n") 231 | count := 0 232 | for _, line := range lines { 233 | ok := strings.Contains(line, needle) 234 | if ok { 235 | count++ 236 | } 237 | } 238 | return count 239 | } 240 | 241 | func TestWriteSlsFile(t *testing.T) { 242 | pgpKeyName, publicKeyRing, secretKeyRing = getTestKeyRings() 243 | slsFile := "./testdata/foo/foo.sls" 244 | 245 | p := pki.New(pgpKeyName, publicKeyRing, secretKeyRing) 246 | s := sls.New(slsFile, p, topLevelElement) 247 | 248 | secText := "secret" 249 | valType := "text" 250 | _ = s.SetValueFromPath(secText, valType) 251 | 252 | buffer, err := s.FormatBuffer("") 253 | Ok(t, err) 254 | _, _ = sls.WriteSlsFile(buffer, slsFile) 255 | 256 | if _, err = os.Stat(slsFile); os.IsNotExist(err) { 257 | t.Errorf("%s file was not written", slsFile) 258 | } 259 | yamlObj, err := yaml.Open(slsFile) 260 | Ok(t, err) 261 | 262 | if yamlObj.Get(secText) == nil { 263 | t.Errorf("YAML content is incorrect, missing key") 264 | } else if yamlObj.Get(secText) != valType { 265 | t.Errorf("YAML content is incorrect, got: %s, want: %s.", 266 | yamlObj.Get(secText), valType) 267 | } 268 | os.Remove(slsFile) 269 | os.Remove("./testdata/foo/") 270 | } 271 | 272 | func TestReadSlsFile(t *testing.T) { 273 | pgpKeyName, publicKeyRing, secretKeyRing = getTestKeyRings() 274 | topLevelElement = "secure_vars" 275 | yamlObj, err := yaml.Open("./testdata/new.sls") 276 | Ok(t, err) 277 | 278 | length := len(yamlObj.Get(topLevelElement).(map[interface{}]interface{})) 279 | Assert(t, length == 3, fmt.Sprintf("YAML content length is incorrect, got: %d, want: %d.", length, 3), 3) 280 | } 281 | 282 | func TestReadIncludeFile(t *testing.T) { 283 | pgpKeyName, publicKeyRing, secretKeyRing = getTestKeyRings() 284 | slsFile := "./testdata/inc.sls" 285 | p := pki.New(pgpKeyName, publicKeyRing, secretKeyRing) 286 | s := sls.New(slsFile, p, topLevelElement) 287 | Assert(t, s.IsInclude, "failed to detect include file", s.IsInclude) 288 | slsFile = "./testdata/new.sls" 289 | s = sls.New(slsFile, p, topLevelElement) 290 | Assert(t, !s.IsInclude, "bad status for non-include file", s.IsInclude) 291 | } 292 | 293 | func TestReadBadFile(t *testing.T) { 294 | pgpKeyName, publicKeyRing, secretKeyRing = getTestKeyRings() 295 | topLevelElement = "secure_vars" 296 | yamlObj, err := yaml.Open("/dev/null") 297 | Ok(t, err) 298 | Assert(t, yamlObj.Get(topLevelElement) == nil, "got YAML from /dev/null???", yamlObj.Get(topLevelElement)) 299 | } 300 | 301 | func TestEncryptSecret(t *testing.T) { 302 | pgpKeyName, publicKeyRing, secretKeyRing = getTestKeyRings() 303 | topLevelElement = "secure_vars" 304 | p := pki.New(pgpKeyName, publicKeyRing, secretKeyRing) 305 | 306 | yamlObj, err := yaml.Open("./testdata/new.sls") 307 | Ok(t, err) 308 | 309 | length := len(yamlObj.Get(topLevelElement).(map[interface{}]interface{})) 310 | Assert(t, length == 3, fmt.Sprintf("YAML content length is incorrect, got: %d, want: %d.", length, 3), 3) 311 | 312 | secureVars := yamlObj.Get(topLevelElement) 313 | for _, v := range secureVars.(map[interface{}]interface{}) { 314 | if strings.Contains(v.(string), pki.PGPHeader) { 315 | t.Errorf("YAML content is already encrypted.") 316 | } else { 317 | cipherText, err := p.EncryptSecret(v.(string)) 318 | Ok(t, err) 319 | Assert(t, strings.Contains(cipherText, pki.PGPHeader), "YAML content was not encrypted.", strings.Contains(cipherText, pki.PGPHeader)) 320 | } 321 | } 322 | } 323 | 324 | func TestGetPath(t *testing.T) { 325 | pgpKeyName, publicKeyRing, secretKeyRing = getTestKeyRings() 326 | topLevelElement = "secure_vars" 327 | 328 | file := "./testdata/test/bar.sls" 329 | p := pki.New(pgpKeyName, publicKeyRing, secretKeyRing) 330 | s := sls.New(file, p, topLevelElement) 331 | 332 | buffer, err := s.PerformAction("encrypt") 333 | Ok(t, err) 334 | if err == nil { 335 | _, _ = sls.WriteSlsFile(buffer, file) 336 | } 337 | 338 | if s.GetValueFromPath(topLevelElement) == nil { 339 | t.Errorf("YAML content is incorrect, got: %v.", 340 | s.GetValueFromPath(topLevelElement)) 341 | } 342 | secureVars := s.GetValueFromPath(topLevelElement) 343 | for _, v := range secureVars.(map[string]interface{}) { 344 | Assert(t, strings.Contains(v.(string), pki.PGPHeader), "YAML content was not encrypted.", strings.Contains(v.(string), pki.PGPHeader)) 345 | } 346 | 347 | buffer, err = s.PerformAction("decrypt") 348 | Ok(t, err) 349 | if err == nil { 350 | _, _ = sls.WriteSlsFile(buffer, file) 351 | } 352 | } 353 | 354 | func TestDecryptSecret(t *testing.T) { 355 | pgpKeyName, publicKeyRing, secretKeyRing = getTestKeyRings() 356 | topLevelElement = "secure_vars" 357 | p := pki.New(pgpKeyName, publicKeyRing, secretKeyRing) 358 | 359 | yamlObj, err := yaml.Open("./testdata/new.sls") 360 | Ok(t, err) 361 | 362 | length := len(yamlObj.Get(topLevelElement).(map[interface{}]interface{})) 363 | Assert(t, length == 3, fmt.Sprintf("YAML content length is incorrect, got: %d, want: %d.", length, 3), 3) 364 | for _, v := range yamlObj.Get(topLevelElement).(map[interface{}]interface{}) { 365 | cipherText, err := p.EncryptSecret(v.(string)) 366 | Ok(t, err) 367 | 368 | plainText, err := p.DecryptSecret(cipherText) 369 | Ok(t, err) 370 | 371 | Assert(t, !strings.Contains(plainText, pki.PGPHeader), "YAML content was not decrypted.", strings.Contains(plainText, pki.PGPHeader)) 372 | Assert(t, plainText != "", "decrypted content is empty", plainText) 373 | } 374 | } 375 | 376 | func TestGetValueFromPath(t *testing.T) { 377 | pgpKeyName, publicKeyRing, secretKeyRing = getTestKeyRings() 378 | 379 | filePath := "./testdata/new.sls" 380 | p := pki.New(pgpKeyName, publicKeyRing, secretKeyRing) 381 | s := sls.New(filePath, p, topLevelElement) 382 | val := s.GetValueFromPath("bar:baz") 383 | Equals(t, "qux", val.(string)) 384 | } 385 | 386 | func TestNestedAndMultiLineFile(t *testing.T) { 387 | pgpKeyName, publicKeyRing, secretKeyRing = getTestKeyRings() 388 | 389 | filePath := "./testdata/test.sls" 390 | p := pki.New(pgpKeyName, publicKeyRing, secretKeyRing) 391 | s := sls.New(filePath, p, topLevelElement) 392 | 393 | buffer, err := s.PerformAction("encrypt") 394 | Ok(t, err) 395 | if err == nil { 396 | _, _ = sls.WriteSlsFile(buffer, filePath) 397 | } 398 | 399 | err = scanString(buffer.String(), 2, pki.PGPHeader) 400 | Ok(t, err) 401 | 402 | filePath = "./testdata/test.sls" 403 | p = pki.New(pgpKeyName, publicKeyRing, secretKeyRing) 404 | s = sls.New(filePath, p, topLevelElement) 405 | 406 | buffer, err = s.PerformAction("decrypt") 407 | Ok(t, err) 408 | if err == nil { 409 | _, _ = sls.WriteSlsFile(buffer, filePath) 410 | } 411 | 412 | err = scanString(buffer.String(), 0, pki.PGPHeader) 413 | Ok(t, err) 414 | } 415 | 416 | func TestSetValueFromPath(t *testing.T) { 417 | pgpKeyName, publicKeyRing, secretKeyRing = getTestKeyRings() 418 | 419 | filePath := "./testdata/new.sls" 420 | p := pki.New(pgpKeyName, publicKeyRing, secretKeyRing) 421 | s := sls.New(filePath, p, topLevelElement) 422 | 423 | err := s.SetValueFromPath("bar:baz", "foo") 424 | Ok(t, err) 425 | 426 | val := s.GetValueFromPath("bar:baz") 427 | Equals(t, "foo", val.(string)) 428 | } 429 | 430 | func TestRotateFile(t *testing.T) { 431 | pgpKeyName, publicKeyRing, secretKeyRing = getTestKeyRings() 432 | topLevelElement = "" 433 | 434 | filePath := "./testdata/new.sls" 435 | p := pki.New(pgpKeyName, publicKeyRing, secretKeyRing) 436 | s := sls.New(filePath, p, topLevelElement) 437 | 438 | buffer, err := s.PerformAction("encrypt") 439 | Ok(t, err) 440 | if err == nil { 441 | _, _ = sls.WriteSlsFile(buffer, filePath) 442 | } 443 | 444 | buffer, err = s.PerformAction("rotate") 445 | Ok(t, err) 446 | if err == nil { 447 | _, _ = sls.WriteSlsFile(buffer, filePath) 448 | } 449 | 450 | val := s.GetValueFromPath("bar:baz") 451 | Assert(t, strings.Contains(val.(string), pki.PGPHeader), "YAML content was not encrypted.", strings.Contains(val.(string), pki.PGPHeader)) 452 | buffer, err = s.PerformAction("decrypt") 453 | Ok(t, err) 454 | if err == nil { 455 | _, _ = sls.WriteSlsFile(buffer, filePath) 456 | } 457 | } 458 | 459 | func TestKeyInfo(t *testing.T) { 460 | pgpKeyName, publicKeyRing, secretKeyRing = getTestKeyRings() 461 | topLevelElement = "" 462 | 463 | filePath := "./testdata/new.sls" 464 | p := pki.New(pgpKeyName, publicKeyRing, secretKeyRing) 465 | s := sls.New(filePath, p, topLevelElement) 466 | 467 | buffer, err := s.PerformAction("encrypt") 468 | Ok(t, err) 469 | if err == nil { 470 | _, _ = sls.WriteSlsFile(buffer, filePath) 471 | } 472 | 473 | buffer, err = s.PerformAction("validate") 474 | Ok(t, err) 475 | 476 | if err = scanString(buffer.String(), 0, pki.PGPHeader); err != nil { 477 | t.Errorf("Found PGP data in buffer: %s", err) 478 | } 479 | if err = scanString(buffer.String(), 5, pgpKeyName); err != nil { 480 | t.Errorf("Key name count in buffer: %s", err) 481 | } 482 | 483 | buffer, err = s.PerformAction("decrypt") 484 | Ok(t, err) 485 | if err == nil { 486 | _, _ = sls.WriteSlsFile(buffer, filePath) 487 | } 488 | } 489 | 490 | func TestEncryptProcessDir(t *testing.T) { 491 | pgpKeyName, publicKeyRing, secretKeyRing = getTestKeyRings() 492 | topLevelElement = "" 493 | 494 | dirPath := "./testdata" 495 | slsFiles, slsCount := utils.FindFilesByExt(dirPath, ".sls") 496 | Equals(t, 7, slsCount) 497 | 498 | pk := pki.New(pgpKeyName, publicKeyRing, secretKeyRing) 499 | err := utils.ProcessDir(dirPath, ".sls", sls.Encrypt, "", topLevelElement, pk) 500 | Ok(t, err) 501 | 502 | for n := 0; n < slsCount; n++ { 503 | s := sls.New(slsFiles[n], pk, topLevelElement) 504 | if s.IsInclude { 505 | continue 506 | } 507 | var buf []byte 508 | buf, err = os.ReadFile(slsFiles[n]) 509 | Ok(t, err) 510 | 511 | reader := strings.NewReader(string(buf)) 512 | scanner := bufio.NewScanner(reader) 513 | 514 | found := hasPgpHeader(*scanner) 515 | err = scanner.Err() 516 | Ok(t, err) 517 | 518 | Assert(t, found, fmt.Sprintf("%s does not contain PGP header", slsFiles[n]), slsFiles[n]) 519 | } 520 | } 521 | 522 | func TestDecryptProcessDir(t *testing.T) { 523 | pgpKeyName, publicKeyRing, secretKeyRing = getTestKeyRings() 524 | topLevelElement = "" 525 | 526 | dirPath := "./testdata" 527 | slsFiles, slsCount := utils.FindFilesByExt(dirPath, ".sls") 528 | Equals(t, 7, slsCount) 529 | 530 | pk := pki.New(pgpKeyName, publicKeyRing, secretKeyRing) 531 | err := utils.ProcessDir(dirPath, ".sls", sls.Decrypt, "", topLevelElement, pk) 532 | Ok(t, err) 533 | 534 | for n := 0; n < slsCount; n++ { 535 | s := sls.New(slsFiles[n], pk, topLevelElement) 536 | if s.IsInclude { 537 | continue 538 | } 539 | var buf []byte 540 | buf, err = os.ReadFile(slsFiles[n]) 541 | Ok(t, err) 542 | 543 | reader := strings.NewReader(string(buf)) 544 | scanner := bufio.NewScanner(reader) 545 | 546 | found := hasPgpHeader(*scanner) 547 | err = scanner.Err() 548 | Ok(t, err) 549 | 550 | Assert(t, !found, fmt.Sprintf("%s contains PGP header", slsFiles[n]), slsFiles[n]) 551 | } 552 | } 553 | 554 | func hasPgpHeader(scanner bufio.Scanner) bool { 555 | found := false 556 | for scanner.Scan() { 557 | txt := scanner.Text() 558 | if strings.Contains(txt, pki.PGPHeader) { 559 | found = true 560 | continue 561 | } 562 | } 563 | return found 564 | } 565 | 566 | func scanString(buffer string, wantedCount int, term string) error { 567 | var err error 568 | encCount := 0 569 | scanner := bufio.NewScanner(strings.NewReader(buffer)) 570 | 571 | for scanner.Scan() { 572 | text := scanner.Text() 573 | if strings.Contains(text, term) { 574 | encCount++ 575 | } 576 | } 577 | if err = scanner.Err(); err != nil { 578 | return fmt.Errorf("%s", err) 579 | } 580 | if encCount != wantedCount { 581 | return fmt.Errorf("count is wrong, wanted %d, got %d", wantedCount, encCount) 582 | } 583 | 584 | return err 585 | } 586 | 587 | // Assert fails the test if the provided condition is false 588 | func Assert(tb testing.TB, condition bool, msg string, v ...interface{}) { 589 | if !condition { 590 | _, file, line, _ := runtime.Caller(1) 591 | fmt.Printf("\033[31m%s:%d: "+msg+"\033[39m\n\n", append([]interface{}{filepath.Base(file), line}, v...)...) 592 | tb.FailNow() 593 | } 594 | } 595 | 596 | // Ok fails the test if the `err` is not nil 597 | func Ok(tb testing.TB, err error) { 598 | if err != nil { 599 | _, file, line, _ := runtime.Caller(1) 600 | fmt.Printf("\033[31m%s:%d: Unexpected error: %s\033[39m\n\n", filepath.Base(file), line, err.Error()) 601 | tb.FailNow() 602 | } 603 | } 604 | 605 | // Equals fails the test if exp is not equal to act 606 | func Equals(tb testing.TB, exp, act interface{}) { 607 | if !reflect.DeepEqual(exp, act) { 608 | _, file, line, _ := runtime.Caller(1) 609 | fmt.Printf("\033[31m%s:%d:\n\n\tExpected: %#v\n\n\tGot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act) 610 | tb.FailNow() 611 | } 612 | } 613 | 614 | func initGPGDir() { 615 | teardownGPGDir() 616 | dirPath, _ = filepath.Abs("./testdata") 617 | os.Setenv("GNUPGHOME", dirPath+"/gnupg") 618 | cmd := exec.Command("./testdata/testkeys.sh") 619 | out, _ := cmd.CombinedOutput() 620 | fmt.Printf("%s", string(out)) 621 | } 622 | 623 | func teardownGPGDir() { 624 | _ = os.Remove("./testdata/gnupg/pubring.gpg") 625 | _ = os.Remove("./testdata/gnupg/pubring.gpg~") 626 | _ = os.Remove("./testdata/gnupg/random_seed") 627 | _ = os.Remove("./testdata/gnupg/secring.gpg") 628 | _ = os.Remove("./testdata/gnupg/trustdb.gpg") 629 | } 630 | 631 | func getTestKeyRings() (pgpKeyName string, publicKeyRing string, secretKeyRing string) { 632 | pgpKeyName = "Test Salt Master" 633 | if os.Getenv("SALT_SEC_KEYRING") != "" { 634 | publicKeyRing, _ = filepath.Abs(os.Getenv("SALT_PUB_KEYRING")) 635 | } else { 636 | publicKeyRing = "./testdata/gnupg/pubring.gpg" 637 | } 638 | 639 | if os.Getenv("SALT_SEC_KEYRING") != "" { 640 | secretKeyRing, _ = filepath.Abs(os.Getenv("SALT_SEC_KEYRING")) 641 | } else { 642 | secretKeyRing = "./testdata/gnupg/secring.gpg" 643 | } 644 | 645 | return pgpKeyName, publicKeyRing, secretKeyRing 646 | } 647 | 648 | func fixturePath(t *testing.T, fixture string) string { 649 | _, filename, _, ok := runtime.Caller(0) 650 | if !ok { 651 | t.Fatalf("problems recovering caller information") 652 | } 653 | 654 | return filepath.Join(filepath.Dir(filename), fixture) 655 | } 656 | 657 | func writeFixture(t *testing.T, fixture string, content []byte) { 658 | str := getActual(content) 659 | err := os.WriteFile(fixturePath(t, fixture), []byte(str), 0644) 660 | if err != nil { 661 | t.Fatal(err) 662 | } 663 | } 664 | 665 | func loadFixture(t *testing.T, fixture string) string { 666 | content, err := os.ReadFile(fixturePath(t, fixture)) 667 | if err != nil { 668 | t.Fatal(err) 669 | } 670 | return string(content) 671 | } 672 | -------------------------------------------------------------------------------- /pki/pki.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Everbridge, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // Package pki handles PGP for pillar content 22 | package pki 23 | 24 | import ( 25 | "bufio" 26 | "bytes" 27 | "fmt" 28 | "io" 29 | "os" 30 | "os/user" 31 | "path/filepath" 32 | "time" 33 | 34 | "github.com/keybase/go-crypto/openpgp" 35 | "github.com/keybase/go-crypto/openpgp/armor" 36 | "github.com/rs/zerolog" 37 | "github.com/rs/zerolog/log" 38 | "github.com/ryboe/q" 39 | ) 40 | 41 | var logger = zerolog.New(os.Stdout) 42 | var debug = false 43 | 44 | // PGPHeader header const 45 | const PGPHeader string = "-----BEGIN PGP MESSAGE-----" 46 | 47 | // Pki pki info 48 | type Pki struct { 49 | PublicKey *openpgp.Entity 50 | SecretKey *openpgp.Entity 51 | PubRing *openpgp.EntityList 52 | SecRing *openpgp.EntityList 53 | PublicKeyRing string 54 | SecretKeyRing string 55 | PgpKeyName string 56 | } 57 | 58 | // if debug==true this can be used to dump values from the var(s) passed in 59 | func dbg() func(thing ...interface{}) { 60 | return func(thing ...interface{}) { 61 | if debug { 62 | q.Q(thing) 63 | } 64 | } 65 | } 66 | 67 | var dumper = dbg() 68 | 69 | // New returns a pki object 70 | func New(pgpKeyName string, publicKeyRing string, secretKeyRing string) Pki { 71 | if os.Getenv("GSPPKI_DEBUG") != "" { 72 | debug = true 73 | } 74 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) 75 | var err error 76 | 77 | p := Pki{nil, nil, nil, nil, publicKeyRing, secretKeyRing, pgpKeyName} 78 | publicKeyRing, err = p.ExpandTilde(p.PublicKeyRing) 79 | if err != nil { 80 | logger.Fatal().Err(err).Msg("cannot expand public key ring path") 81 | } 82 | p.PublicKeyRing = publicKeyRing 83 | p.PubRing, err = p.setKeyRing(p.PublicKeyRing) 84 | if err != nil { 85 | logger.Fatal().Err(err).Msg("Pki") 86 | } 87 | 88 | secKeyRing, err := p.ExpandTilde(p.SecretKeyRing) 89 | if err != nil { 90 | logger.Fatal().Err(err).Msg("cannot expand secret key ring path") 91 | } 92 | p.SecretKeyRing = secKeyRing 93 | p.SecRing, err = p.setKeyRing(p.SecretKeyRing) 94 | if err != nil { 95 | logger.Warn().Err(err).Msg("Pki") 96 | } 97 | 98 | // TODO: Something is goofy here sometimes when getting a key to decrypt 99 | if p.SecRing != nil { 100 | p.SecretKey = p.GetKeyByID(p.SecRing, p.PgpKeyName) 101 | } 102 | p.PublicKey = p.GetKeyByID(p.PubRing, p.PgpKeyName) 103 | if p.PublicKey == nil { 104 | logger.Fatal().Err(err).Msg(fmt.Sprintf("unable to find key '%s' in %s", p.PgpKeyName, p.PublicKeyRing)) 105 | } 106 | 107 | dumper(p) 108 | 109 | return p 110 | } 111 | 112 | func (p *Pki) setKeyRing(keyRingPath string) (*openpgp.EntityList, error) { 113 | keyRing, err := p.ExpandTilde(keyRingPath) 114 | if err != nil { 115 | return nil, fmt.Errorf("error reading secring: %s", err) 116 | } 117 | keyRingFile, err := os.Open(filepath.Clean(keyRing)) 118 | if err != nil { 119 | return nil, fmt.Errorf("unable to open key ring: %s", err) 120 | } 121 | ring, err := openpgp.ReadKeyRing(keyRingFile) 122 | if err != nil { 123 | return nil, fmt.Errorf("cannot read private keys: %s", err) 124 | } else if ring == nil { 125 | return nil, fmt.Errorf("%s is empty", p.SecretKeyRing) 126 | } 127 | if err = keyRingFile.Close(); err != nil { 128 | return &ring, fmt.Errorf("error closing secring: %s", err) 129 | } 130 | 131 | return &ring, nil 132 | } 133 | 134 | // EncryptSecret returns encrypted plainText 135 | func (p *Pki) EncryptSecret(plainText string) (string, error) { 136 | var memBuffer bytes.Buffer 137 | 138 | hints := openpgp.FileHints{IsBinary: false, ModTime: time.Time{}} 139 | writer := bufio.NewWriter(&memBuffer) 140 | w, err := armor.Encode(writer, "PGP MESSAGE", nil) 141 | if err != nil { 142 | return plainText, fmt.Errorf("encode error: %s", err) 143 | } 144 | 145 | plainFile, err := openpgp.Encrypt(w, []*openpgp.Entity{p.PublicKey}, nil, &hints, nil) 146 | if err != nil { 147 | return plainText, fmt.Errorf("encryption error: %s", err) 148 | } 149 | if plainFile == nil { 150 | return plainText, fmt.Errorf("encryption error: plainFile is nil") 151 | } 152 | 153 | if _, err = fmt.Fprintf(plainFile, "%s", plainText); err != nil { 154 | return plainText, fmt.Errorf("encryption error: %s", err) 155 | } 156 | 157 | if err = plainFile.Close(); err != nil { 158 | return plainText, fmt.Errorf("encryption error: %s", err) 159 | } 160 | 161 | if err = w.Close(); err != nil { 162 | return plainText, fmt.Errorf("encryption error: %s", err) 163 | } 164 | if err = writer.Flush(); err != nil { 165 | return plainText, fmt.Errorf("encryption error: %s", err) 166 | } 167 | 168 | return memBuffer.String(), nil 169 | } 170 | 171 | // DecryptSecret returns decrypted cipherText 172 | func (p *Pki) DecryptSecret(cipherText string) (plainText string, err error) { 173 | if p.SecRing == nil { 174 | return cipherText, fmt.Errorf("no secring set") 175 | } 176 | if p.SecretKey == nil { 177 | return cipherText, fmt.Errorf("unable to load PGP secret key for '%s'", p.PgpKeyName) 178 | } 179 | 180 | decbuf := bytes.NewBuffer([]byte(cipherText)) 181 | block, err := armor.Decode(decbuf) 182 | if err != nil { 183 | return cipherText, fmt.Errorf("Decode error: %s", err) 184 | } 185 | if block.Type != "PGP MESSAGE" { 186 | return cipherText, fmt.Errorf("block type is not PGP MESSAGE: %s", err) 187 | } 188 | 189 | md, err := openpgp.ReadMessage(block.Body, p.SecRing, nil, nil) 190 | if err != nil { 191 | return cipherText, fmt.Errorf("unable to read PGP message: %s", err) 192 | } 193 | if md == nil { 194 | return cipherText, fmt.Errorf("unable to read PGP message: md is nil") 195 | } 196 | 197 | body, err := io.ReadAll(md.UnverifiedBody) 198 | if err != nil { 199 | return cipherText, fmt.Errorf("unable to read message body: %s", err) 200 | } 201 | 202 | return string(body), err 203 | } 204 | 205 | // GetKeyByID returns a keyring by the given ID 206 | func (p *Pki) GetKeyByID(keyring *openpgp.EntityList, id interface{}) *openpgp.Entity { 207 | if keyring == nil { 208 | return nil 209 | } 210 | 211 | for _, entity := range *keyring { 212 | if entity.PrivateKey != nil && entity.PrivateKey.KeyIdString() == id.(string) { 213 | return entity 214 | } 215 | if entity.PrimaryKey != nil && entity.PrimaryKey.KeyIdString() == id.(string) { 216 | return entity 217 | } 218 | 219 | if checkIdentities(id.(string), entity) { 220 | return entity 221 | } 222 | } 223 | 224 | return nil 225 | } 226 | 227 | func checkIdentities(id string, entity *openpgp.Entity) bool { 228 | for _, ident := range entity.Identities { 229 | if id == ident.Name { 230 | return true 231 | } 232 | if id == ident.UserId.Email { 233 | return true 234 | } 235 | if id == ident.UserId.Name { 236 | return true 237 | } 238 | if id == ident.UserId.Id { 239 | return true 240 | } 241 | } 242 | 243 | return false 244 | } 245 | 246 | // ExpandTilde does exactly what it says on the tin 247 | func (p *Pki) ExpandTilde(path string) (string, error) { 248 | if len(path) == 0 || path[0] != '~' { 249 | return path, nil 250 | } 251 | 252 | usr, err := user.Current() 253 | if err != nil { 254 | return "", err 255 | } 256 | return filepath.Join(usr.HomeDir, path[1:]), nil 257 | } 258 | 259 | // KeyUsedForEncryptedFile gets the key used to encrypt a file 260 | func (p *Pki) KeyUsedForEncryptedFile(file string) (string, error) { 261 | filePath, err := filepath.Abs(file) 262 | if err != nil { 263 | return "", err 264 | } 265 | 266 | in, err := os.Open(filepath.Clean(filePath)) 267 | if err != nil { 268 | return "", err 269 | } 270 | 271 | block, err := armor.Decode(in) 272 | if err != nil { 273 | return "", err 274 | } 275 | 276 | if block.Type != "PGP MESSAGE" { 277 | return "", fmt.Errorf("error decoding private key") 278 | } 279 | md, err := openpgp.ReadMessage(block.Body, p.SecRing, nil, nil) 280 | if err != nil { 281 | return "", fmt.Errorf("unable to read PGP message: %s", err) 282 | } 283 | if md == nil { 284 | return "", fmt.Errorf("unable to read PGP message: md is nil") 285 | } 286 | 287 | for index := 0; index < len(md.EncryptedToKeyIds); index++ { 288 | id := md.EncryptedToKeyIds[index] 289 | keyStr := p.keyStringForID(id) 290 | if keyStr != "" { 291 | return keyStr, nil 292 | } 293 | } 294 | 295 | return "", fmt.Errorf("unable to find key for ids used") 296 | } 297 | 298 | func (p *Pki) keyStringForID(id uint64) string { 299 | if p.SecRing == nil { 300 | return "" 301 | } 302 | keys := p.SecRing.KeysById(id, nil) 303 | if len(keys) > 0 { 304 | for n := 0; n < len(keys); n++ { 305 | key := keys[n] 306 | if key.Entity != nil { 307 | for k := range key.Entity.Identities { 308 | // return the first valid key 309 | return fmt.Sprintf("%X: %s\n", id, k) 310 | } 311 | } 312 | } 313 | } 314 | return "" 315 | } 316 | -------------------------------------------------------------------------------- /sls/sls.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Everbridge, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // Package sls handles parsing of Salt Pillar files 22 | package sls 23 | 24 | import ( 25 | "bufio" 26 | "bytes" 27 | "fmt" 28 | "io" 29 | "os" 30 | "path" 31 | "path/filepath" 32 | "reflect" 33 | "strings" 34 | 35 | "github.com/Everbridge/generate-secure-pillar/pki" 36 | yaml "github.com/esilva-everbridge/yaml" 37 | "github.com/rs/zerolog" 38 | "github.com/rs/zerolog/log" 39 | yamlv3 "gopkg.in/yaml.v3" 40 | ) 41 | 42 | // Encrypt action 43 | const Encrypt = "encrypt" 44 | 45 | // Decrypt action 46 | const Decrypt = "decrypt" 47 | 48 | // Validate action (keys) 49 | const Validate = "validate" 50 | 51 | // Rotate action 52 | const Rotate = "rotate" 53 | 54 | var logger = zerolog.New(os.Stdout) 55 | 56 | // Sls sls data 57 | type Sls struct { 58 | Error error 59 | Yaml *yaml.Yaml 60 | Pki *pki.Pki 61 | KeyMap map[string]interface{} 62 | FilePath string 63 | EncryptionPath string 64 | KeyMeta string 65 | KeyCount int 66 | IsInclude bool 67 | } 68 | 69 | // New returns a Sls object 70 | func New(filePath string, p pki.Pki, encPath string) Sls { 71 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) 72 | s := Sls{nil, yaml.New(), &p, map[string]interface{}{}, filePath, encPath, "", 0, false} 73 | if len(filePath) > 0 { 74 | err := s.ReadSlsFile() 75 | if err != nil { 76 | logger.Error().Err(err).Msgf("init error for %s", s.FilePath) 77 | s.Error = err 78 | } 79 | } 80 | 81 | return s 82 | } 83 | 84 | // ReadBytes loads YAML from a []byte 85 | func (s *Sls) ReadBytes(buf []byte) error { 86 | reader := strings.NewReader(string(buf)) 87 | 88 | err := s.ScanForIncludes(reader) 89 | if err != nil { 90 | s.IsInclude = true 91 | return err 92 | } 93 | 94 | return yamlv3.Unmarshal(buf, &s.Yaml.Values) 95 | } 96 | 97 | // ScanForIncludes looks for include statements in the given io.Reader 98 | func (s *Sls) ScanForIncludes(reader io.Reader) error { 99 | // Splits on newlines by default. 100 | scanner := bufio.NewScanner(reader) 101 | 102 | // https://golang.org/pkg/bufio/#Scanner.Scan 103 | for scanner.Scan() { 104 | txt := scanner.Text() 105 | if strings.Contains(txt, "include:") { 106 | return fmt.Errorf("%s contains include directives", shortFileName(s.FilePath)) 107 | } 108 | } 109 | return scanner.Err() 110 | } 111 | 112 | // ReadSlsFile open and read a yaml file, if the file has include statements 113 | // we throw an error as the YAML parser will try to act on the include directives 114 | func (s *Sls) ReadSlsFile() error { 115 | if len(s.FilePath) == 0 { 116 | return fmt.Errorf("no file path given") 117 | } 118 | 119 | if s.FilePath == os.Stdout.Name() { 120 | return nil 121 | } 122 | 123 | // this could be called when creating a new file, so check the path 124 | if _, statErr := os.Stat(s.FilePath); statErr == nil { 125 | dir := filepath.Dir(s.FilePath) 126 | err := os.MkdirAll(dir, 0700) 127 | if err != nil { 128 | return err 129 | } 130 | _, err = os.OpenFile(s.FilePath, os.O_RDONLY|os.O_CREATE, 0600) 131 | if err != nil { 132 | return err 133 | } 134 | } 135 | 136 | fullPath, err := filepath.Abs(s.FilePath) 137 | if err != nil { 138 | return err 139 | } 140 | 141 | var buf []byte 142 | buf, err = os.ReadFile(filepath.Clean(fullPath)) 143 | if err != nil { 144 | return err 145 | } 146 | 147 | return s.ReadBytes(buf) 148 | } 149 | 150 | // WriteSlsFile writes a buffer to the specified file 151 | // If the outFilePath is not stdout an INFO string will be printed to stdout 152 | func WriteSlsFile(buffer bytes.Buffer, outFilePath string) (int, error) { 153 | fullPath, err := filepath.Abs(outFilePath) 154 | if err != nil { 155 | fullPath = outFilePath 156 | } 157 | 158 | stdOut := false 159 | if fullPath == os.Stdout.Name() { 160 | stdOut = true 161 | } 162 | 163 | // check that the path exists, create it if not 164 | if !stdOut { 165 | dir := filepath.Dir(fullPath) 166 | err = os.MkdirAll(dir, 0700) 167 | if err != nil { 168 | return buffer.Len(), fmt.Errorf("error creating sls path: %s", err) 169 | } 170 | } 171 | 172 | var byteCount int 173 | if stdOut { 174 | byteCount, err = os.Stdout.Write(buffer.Bytes()) 175 | } else { 176 | byteCount, err = atomicWrite(fullPath, buffer) 177 | } 178 | 179 | if !stdOut && err == nil { 180 | shortFile := shortFileName(outFilePath) 181 | logger.Info().Msgf("wrote out to file: '%s'", shortFile) 182 | } 183 | 184 | return byteCount, err 185 | } 186 | 187 | func atomicWrite(fullPath string, buffer bytes.Buffer) (int, error) { 188 | _, name := path.Split(fullPath) 189 | f, err := os.CreateTemp("", fmt.Sprintf("gsp-%s", name)) 190 | if err != nil { 191 | return 0, err 192 | } 193 | byteCount, err := f.Write(buffer.Bytes()) 194 | if err == nil { 195 | err = f.Sync() 196 | } 197 | if closeErr := f.Close(); err == nil { 198 | err = closeErr 199 | } 200 | if permErr := os.Chmod(f.Name(), 0600); err == nil { 201 | err = permErr 202 | } 203 | if err == nil { 204 | err = copyFile(f.Name(), fullPath) 205 | } 206 | if err != nil { 207 | return byteCount, err 208 | } 209 | 210 | if _, statErr := os.Stat(f.Name()); !os.IsNotExist(statErr) { 211 | err = os.Remove(f.Name()) 212 | } 213 | 214 | return byteCount, err 215 | } 216 | 217 | func copyFile(src string, dst string) error { 218 | srcStat, err := os.Stat(src) 219 | if err != nil { 220 | return err 221 | } 222 | 223 | fsrc, err := os.Open(filepath.Clean(src)) 224 | if err != nil { 225 | return err 226 | } 227 | 228 | fdst, err := os.Create(dst) 229 | if err != nil { 230 | return err 231 | } 232 | 233 | size, err := io.Copy(fdst, fsrc) 234 | if err != nil { 235 | return err 236 | } 237 | if size != srcStat.Size() { 238 | return fmt.Errorf("%s: %d/%d copied", src, size, srcStat.Size()) 239 | } 240 | 241 | err = fsrc.Close() 242 | if err != nil { 243 | return fdst.Close() 244 | } 245 | return err 246 | } 247 | 248 | // FormatBuffer returns a formatted .sls buffer with the gpg renderer line 249 | func (s *Sls) FormatBuffer(action string) (bytes.Buffer, error) { 250 | var buffer bytes.Buffer 251 | var out []byte 252 | var err error 253 | var data map[string]interface{} 254 | 255 | if action != Validate { 256 | data = s.Yaml.Values 257 | } else { 258 | data = s.KeyMap 259 | } 260 | 261 | if len(data) == 0 { 262 | return buffer, fmt.Errorf("%s has no values to format", s.FilePath) 263 | } 264 | 265 | out, err = yamlv3.Marshal(data) 266 | if err != nil { 267 | return buffer, fmt.Errorf("%s format error: %s", s.FilePath, err) 268 | } 269 | 270 | if action != Validate { 271 | _, err = buffer.WriteString("#!yaml|gpg\n\n") 272 | if err != nil { 273 | return buffer, fmt.Errorf("%s format error: %s", s.FilePath, err) 274 | } 275 | } 276 | _, err = buffer.WriteString(string(out)) 277 | 278 | return buffer, err 279 | } 280 | 281 | // ProcessYaml encrypts elements matching keys specified on the command line 282 | func (s *Sls) ProcessYaml(secretNames []string, secretValues []string) error { 283 | var err error 284 | 285 | for index := 0; index < len(secretNames); index++ { 286 | cipherText := "" 287 | if index >= 0 && index < len(secretValues) { 288 | cipherText, err = s.Pki.EncryptSecret(secretValues[index]) 289 | if err != nil { 290 | return err 291 | } 292 | } 293 | err = s.SetValueFromPath(secretNames[index], cipherText) 294 | if err != nil { 295 | return err 296 | } 297 | } 298 | 299 | return err 300 | } 301 | 302 | // GetValueFromPath returns the value from a path string 303 | func (s *Sls) GetValueFromPath(path string) interface{} { 304 | parts := strings.Split(path, ":") 305 | 306 | args := make([]interface{}, len(parts)) 307 | for i := 0; i < len(parts); i++ { 308 | args[i] = parts[i] 309 | } 310 | if s.Yaml != nil { 311 | return s.Yaml.Get(args...) 312 | } 313 | return nil 314 | } 315 | 316 | // SetValueFromPath returns the value from a path string 317 | func (s *Sls) SetValueFromPath(path string, value string) error { 318 | parts := strings.Split(path, ":") 319 | 320 | // construct the args list 321 | args := make([]interface{}, len(parts)+1) 322 | for i := 0; i < len(parts); i++ { 323 | args[i] = parts[i] 324 | } 325 | args[len(args)-1] = value 326 | err := s.Yaml.Set(args...) 327 | if err == nil { 328 | return nil 329 | } 330 | return fmt.Errorf("%s", err) 331 | } 332 | 333 | // PerformAction takes an action string (encrypt or decrypt) 334 | // and applies that action on all items 335 | func (s *Sls) PerformAction(action string) (bytes.Buffer, error) { 336 | var buf bytes.Buffer 337 | 338 | if validAction(action) { 339 | var stuff = make(map[string]interface{}) 340 | 341 | for key := range s.Yaml.Values { 342 | if s.EncryptionPath != "" { 343 | vals := s.GetValueFromPath(key) 344 | if vals != nil { 345 | if s.EncryptionPath == key { 346 | processed, err := s.ProcessValues(vals, action) 347 | if err != nil { 348 | return buf, err 349 | } 350 | if processed != nil { 351 | stuff[key] = processed 352 | } 353 | } else { 354 | stuff[key] = vals 355 | } 356 | } 357 | } else { 358 | vals := s.GetValueFromPath(key) 359 | if vals != nil { 360 | processed, err := s.ProcessValues(vals, action) 361 | if err != nil { 362 | return buf, err 363 | } 364 | if processed != nil { 365 | stuff[key] = processed 366 | } 367 | } 368 | } 369 | } 370 | if action != Validate { 371 | // replace the values in the Yaml object 372 | s.Yaml.Values = stuff 373 | } else { 374 | s.KeyMap = stuff 375 | var vals []string 376 | for _, v := range s.KeyMap { 377 | if v != nil { 378 | node := getNode(v.(interface{})) 379 | if node != nil { 380 | vals = append(vals, node.(string)) 381 | } 382 | } 383 | } 384 | unique := removeDuplicates(vals) 385 | buf := bytes.Buffer{} 386 | buf.WriteString(fmt.Sprintf("%d keys found:\n", len(unique))) 387 | for i := range unique { 388 | buf.WriteString(fmt.Sprintf(" %s", unique[i])) 389 | } 390 | s.KeyMeta = buf.String() 391 | s.KeyCount = len(unique) 392 | } 393 | } 394 | 395 | return s.FormatBuffer(action) 396 | } 397 | 398 | // ProcessValues will encrypt or decrypt given values 399 | func (s *Sls) ProcessValues(vals interface{}, action string) (interface{}, error) { 400 | var res interface{} 401 | 402 | if vals == nil { 403 | return res, nil 404 | } 405 | vtype := reflect.TypeOf(vals).Kind() 406 | switch vtype { 407 | case reflect.Slice: 408 | return s.doSlice(vals, action) 409 | case reflect.Map: 410 | return s.doMap(vals.(map[string]interface{}), action) 411 | default: 412 | return s.doString(vals, action) 413 | } 414 | } 415 | 416 | func (s *Sls) doSlice(vals interface{}, action string) (interface{}, error) { 417 | var things []interface{} 418 | 419 | if vals == nil { 420 | return things, nil 421 | } 422 | 423 | for _, item := range vals.([]interface{}) { 424 | var thing interface{} 425 | vtype := reflect.TypeOf(item).Kind() 426 | 427 | switch vtype { 428 | case reflect.Slice: 429 | sliceStuff, err := s.doSlice(item, action) 430 | if err != nil { 431 | return vals, err 432 | } 433 | things = append(things, sliceStuff) 434 | case reflect.Map: 435 | thing = item 436 | mapStuff, err := s.doMap(thing.(map[string]interface{}), action) 437 | if err != nil { 438 | return vals, err 439 | } 440 | things = append(things, mapStuff) 441 | default: 442 | thing, err := s.doString(item, action) 443 | if err != nil { 444 | return vals, err 445 | } 446 | things = append(things, thing) 447 | } 448 | } 449 | 450 | return things, nil 451 | } 452 | 453 | func (s *Sls) doMap(vals map[string]interface{}, action string) (map[string]interface{}, error) { 454 | var ret = make(map[string]interface{}) 455 | var err error 456 | 457 | for key, val := range vals { 458 | if val == nil { 459 | return ret, err 460 | } 461 | 462 | vtype := reflect.TypeOf(val).Kind() 463 | switch vtype { 464 | case reflect.Slice: 465 | var slice interface{} 466 | slice, err = s.doSlice(val, action) 467 | if slice != nil { 468 | ret[key] = slice 469 | } 470 | case reflect.Map: 471 | var slice interface{} 472 | slice, err = s.doMap(val.(map[string]interface{}), action) 473 | if slice.(map[string]interface{}) != nil { 474 | ret[key] = slice 475 | } 476 | default: 477 | var slice interface{} 478 | slice, err = s.doString(val, action) 479 | if len(slice.(string)) != 0 { 480 | ret[key] = slice 481 | } 482 | } 483 | } 484 | 485 | return ret, err 486 | } 487 | 488 | func (s *Sls) doString(val interface{}, action string) (string, error) { 489 | var err error 490 | 491 | // %v is a 'cheat' in that it will convert any type 492 | // and allow it to be used as a string output with sprintf 493 | strVal := fmt.Sprintf("%v", val) 494 | 495 | switch action { 496 | case Decrypt: 497 | strVal, err = s.decryptVal(strVal) 498 | if err != nil { 499 | return strVal, err 500 | } 501 | case Encrypt: 502 | if !isEncrypted(strVal) { 503 | strVal, err = s.Pki.EncryptSecret(strVal) 504 | if err != nil { 505 | return strVal, err 506 | } 507 | } 508 | case Validate: 509 | strVal, err = s.keyInfo(strVal) 510 | if err != nil { 511 | return strVal, err 512 | } 513 | case Rotate: 514 | strVal, err = s.rotateVal(strVal) 515 | if err != nil { 516 | return strVal, err 517 | } 518 | } 519 | 520 | return strVal, err 521 | } 522 | 523 | func (s *Sls) rotateVal(strVal string) (string, error) { 524 | strVal, err := s.decryptVal(strVal) 525 | if err != nil { 526 | return strVal, err 527 | } 528 | return s.Pki.EncryptSecret(strVal) 529 | } 530 | 531 | func isEncrypted(str string) bool { 532 | return strings.Contains(str, pki.PGPHeader) 533 | } 534 | 535 | func (s *Sls) keyInfo(val string) (string, error) { 536 | if !isEncrypted(val) { 537 | return val, fmt.Errorf("value is not encrypted") 538 | } 539 | 540 | tmpfile, err := os.CreateTemp("", "gsp-") 541 | if err != nil { 542 | return val, fmt.Errorf("keyInfo: %s", err) 543 | } 544 | 545 | if _, err = tmpfile.Write([]byte(val)); err != nil { 546 | return val, fmt.Errorf("keyInfo: %s", err) 547 | } 548 | 549 | keyInfo, err := s.Pki.KeyUsedForEncryptedFile(tmpfile.Name()) 550 | if err != nil { 551 | return val, fmt.Errorf("keyInfo: %s", err) 552 | } 553 | 554 | if err = tmpfile.Close(); err != nil { 555 | return val, fmt.Errorf("keyInfo: %s", err) 556 | } 557 | if err = os.Remove(tmpfile.Name()); err != nil { 558 | return val, fmt.Errorf("keyInfo: %s", err) 559 | } 560 | 561 | return keyInfo, nil 562 | } 563 | 564 | func (s *Sls) decryptVal(strVal string) (string, error) { 565 | var plainText string 566 | 567 | if isEncrypted(strVal) { 568 | var err error 569 | plainText, err = s.Pki.DecryptSecret(strVal) 570 | if err != nil { 571 | return strVal, fmt.Errorf("error decrypting value: %s", err) 572 | } 573 | } else { 574 | return strVal, nil 575 | } 576 | 577 | return plainText, nil 578 | } 579 | 580 | func validAction(action string) bool { 581 | return action == Encrypt || action == Decrypt || action == Validate || action == Rotate 582 | } 583 | 584 | func shortFileName(file string) string { 585 | pwd, err := os.Getwd() 586 | if err != nil { 587 | logger.Warn().Err(err) 588 | return file 589 | } 590 | return strings.Replace(file, pwd+"/", "", 1) 591 | } 592 | 593 | func getNode(v interface{}) interface{} { 594 | var node interface{} 595 | vtype := reflect.TypeOf(v) 596 | kind := vtype.Kind() 597 | 598 | switch kind { 599 | case reflect.Slice: 600 | case reflect.Map: 601 | for _, v2 := range v.(map[string]interface{}) { 602 | node = getNode(v2.(interface{})) 603 | } 604 | default: 605 | node = fmt.Sprintf("%v", v) 606 | } 607 | return node 608 | } 609 | 610 | func removeDuplicates(elements []string) []string { 611 | seen := make(map[string]bool) 612 | var result []string 613 | 614 | for v := range elements { 615 | if seen[elements[v]] { 616 | // skip it 617 | } else { 618 | seen[elements[v]] = true 619 | result = append(result, elements[v]) 620 | } 621 | } 622 | 623 | return result 624 | } 625 | -------------------------------------------------------------------------------- /testdata/decrypt-file.golden: -------------------------------------------------------------------------------- 1 | 2 | {"level":"info","message":"wrote out to file: 'testdata/test.sls'"} -------------------------------------------------------------------------------- /testdata/decrypt-path.golden: -------------------------------------------------------------------------------- 1 | key: value -------------------------------------------------------------------------------- /testdata/decrypt-recurse.golden: -------------------------------------------------------------------------------- 1 | {"level":"error","error":"testdata/inc.sls contains include directives","message":"init error for"} 2 | {"level":"info","message":"wrote out to file: 'testdata/new.sls'"} 3 | {"level":"info","message":"wrote out to file: 'testdata/test.sls'"} 4 | {"level":"info","message":"wrote out to file: 'testdata/test/bar.sls'"} 5 | {"level":"info","message":"wrote out to file: 'testdata/test/baz.sls'"} 6 | {"level":"info","message":"wrote out to file: 'testdata/test/foo.sls'"} 7 | {"level":"info","message":"wrote out to file: 'testdata/test/simple.sls'"} -------------------------------------------------------------------------------- /testdata/empty/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Everbridge/generate-secure-pillar/628854297b93431723fd454a851f74127cbfc659/testdata/empty/.gitignore -------------------------------------------------------------------------------- /testdata/encrypt-file.golden: -------------------------------------------------------------------------------- 1 | {"level":"info","message":"wrote out to file: 'testdata/test.sls'"} -------------------------------------------------------------------------------- /testdata/encrypt-recurse.golden: -------------------------------------------------------------------------------- 1 | {"level":"error","error":"testdata/inc.sls contains include directives","message":"init error for"} 2 | {"level":"info","message":"wrote out to file: 'testdata/new.sls'"} 3 | {"level":"info","message":"wrote out to file: 'testdata/test.sls'"} 4 | {"level":"info","message":"wrote out to file: 'testdata/test/bar.sls'"} 5 | {"level":"info","message":"wrote out to file: 'testdata/test/baz.sls'"} 6 | {"level":"info","message":"wrote out to file: 'testdata/test/foo.sls'"} 7 | {"level":"info","message":"wrote out to file: 'testdata/test/simple.sls'"} -------------------------------------------------------------------------------- /testdata/gnupg/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Everbridge/generate-secure-pillar/628854297b93431723fd454a851f74127cbfc659/testdata/gnupg/.gitkeep -------------------------------------------------------------------------------- /testdata/gpginit.txt: -------------------------------------------------------------------------------- 1 | %echo Generating a default key 2 | Key-Type: 1 3 | Key-Length: 2048 4 | Subkey-Type: 1 5 | Subkey-Length: 2048 6 | Name-Real: Test Salt Master 7 | Name-Comment: test key 8 | Expire-Date: 0 9 | %commit 10 | %echo done 11 | -------------------------------------------------------------------------------- /testdata/inc.sls: -------------------------------------------------------------------------------- 1 | include: 2 | - new 3 | - test.foo 4 | - test.bar 5 | - test.baz 6 | -------------------------------------------------------------------------------- /testdata/keys-count.golden: -------------------------------------------------------------------------------- 1 | 2 | 1EADCB5C042E5B66: Test Salt Master (test key) 3 | 1 keys found: 4 | -------------------------------------------------------------------------------- /testdata/keys-file.golden: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1EADCB5C042E5B66: Test Salt Master (test key) 4 | 1EADCB5C042E5B66: Test Salt Master (test key) 5 | 1EADCB5C042E5B66: Test Salt Master (test key) 6 | 1EADCB5C042E5B66: Test Salt Master (test key) 7 | 1EADCB5C042E5B66: Test Salt Master (test key) 8 | 1EADCB5C042E5B66: Test Salt Master (test key) 9 | 1EADCB5C042E5B66: Test Salt Master (test key) 10 | 1EADCB5C042E5B66: Test Salt Master (test key) 11 | a.multi-line.example: | 12 | barney: | 13 | fred: | 14 | - | 15 | - | 16 | - | 17 | 1EADCB5C042E5B66: Test Salt Master (test key) 18 | 1EADCB5C042E5B66: Test Salt Master (test key) 19 | 1EADCB5C042E5B66: Test Salt Master (test key) 20 | 1EADCB5C042E5B66: Test Salt Master (test key) 21 | api_key: | 22 | flinstones: 23 | is: 24 | list: 25 | password: | 26 | - | 27 | - | 28 | - | 29 | key: | 30 | list: 31 | secure_vars: 32 | stuff: 33 | sub: 34 | this: -------------------------------------------------------------------------------- /testdata/keys-path.golden: -------------------------------------------------------------------------------- 1 | key: 1EADCB5C042E5B66: Test Salt Master (test key) -------------------------------------------------------------------------------- /testdata/keys-recurse-bad.golden: -------------------------------------------------------------------------------- 1 | {"level":"warn","error":"search directory not specified","message":"keys"} -------------------------------------------------------------------------------- /testdata/keys-recurse.golden: -------------------------------------------------------------------------------- 1 | 1EADCB5C042E5B66: Test Salt Master (test key) 2 | 1EADCB5C042E5B66: Test Salt Master (test key) 3 | 1EADCB5C042E5B66: Test Salt Master (test key) 4 | 1EADCB5C042E5B66: Test Salt Master (test key) 5 | 1EADCB5C042E5B66: Test Salt Master (test key) 6 | 1EADCB5C042E5B66: Test Salt Master (test key) 7 | 1EADCB5C042E5B66: Test Salt Master (test key) 8 | 1EADCB5C042E5B66: Test Salt Master (test key) 9 | 1EADCB5C042E5B66: Test Salt Master (test key) 10 | 1EADCB5C042E5B66: Test Salt Master (test key) 11 | 1EADCB5C042E5B66: Test Salt Master (test key) 12 | 1EADCB5C042E5B66: Test Salt Master (test key) 13 | 1EADCB5C042E5B66: Test Salt Master (test key) 14 | 1EADCB5C042E5B66: Test Salt Master (test key) 15 | 1EADCB5C042E5B66: Test Salt Master (test key) 16 | 1EADCB5C042E5B66: Test Salt Master (test key) 17 | 1EADCB5C042E5B66: Test Salt Master (test key) 18 | 1EADCB5C042E5B66: Test Salt Master (test key) 19 | 1EADCB5C042E5B66: Test Salt Master (test key) 20 | a.multi-line.example: | 21 | barney: | 22 | fred: | 23 | - | 24 | - | 25 | - | 26 | 1EADCB5C042E5B66: Test Salt Master (test key) 27 | 1EADCB5C042E5B66: Test Salt Master (test key) 28 | 1EADCB5C042E5B66: Test Salt Master (test key) 29 | 1EADCB5C042E5B66: Test Salt Master (test key) 30 | 1EADCB5C042E5B66: Test Salt Master (test key) 31 | 1EADCB5C042E5B66: Test Salt Master (test key) 32 | 1EADCB5C042E5B66: Test Salt Master (test key) 33 | aaa: | 34 | api_key: | 35 | bar: | 36 | bar: | 37 | bar: | 38 | baz: | 39 | baz: | 40 | bbb: | 41 | flinstones: 42 | foo: | 43 | foo: | 44 | foo: | 45 | is: 46 | list: 47 | password: | 48 | zzz: | 49 | - | 50 | - | 51 | - | 52 | /Users/ed.silva/gocode/src/github.com/Everbridge/generate-secure-pillar/testdata/new.sls: 53 | /Users/ed.silva/gocode/src/github.com/Everbridge/generate-secure-pillar/testdata/test.sls: 54 | /Users/ed.silva/gocode/src/github.com/Everbridge/generate-secure-pillar/testdata/test/bar.sls: 55 | /Users/ed.silva/gocode/src/github.com/Everbridge/generate-secure-pillar/testdata/test/baz.sls: 56 | /Users/ed.silva/gocode/src/github.com/Everbridge/generate-secure-pillar/testdata/test/foo.sls: 57 | /Users/ed.silva/gocode/src/github.com/Everbridge/generate-secure-pillar/testdata/test/simple.sls: 58 | bar: 59 | foo: | 60 | key: | 61 | level=warning msg="testdata/inc.sls contains include directives" 62 | list: 63 | secure_vars: 64 | secure_vars: 65 | secure_vars: 66 | secure_vars: 67 | secure_vars: 68 | stuff: 69 | stuff: | 70 | sub: 71 | this: 72 | username: | -------------------------------------------------------------------------------- /testdata/new.sls: -------------------------------------------------------------------------------- 1 | #!yaml|gpg 2 | 3 | bar: 4 | baz: qux 5 | foo: bar 6 | secure_vars: 7 | aaa: bbb 8 | bbb: foo 9 | zzz: xxx 10 | -------------------------------------------------------------------------------- /testdata/no-args.golden: -------------------------------------------------------------------------------- 1 | --config string config file (default is $HOME/.config/generate-secure-pillar/config.yaml) -------------------------------------------------------------------------------- /testdata/test.sls: -------------------------------------------------------------------------------- 1 | #!yaml|gpg 2 | 3 | key: value 4 | list: 5 | - one 6 | - two 7 | - three 8 | secure_vars: 9 | api_key: key_value 10 | password: foo 11 | stuff: 12 | flinstones: 13 | barney: betty 14 | fred: wilma 15 | sub: 16 | list: 17 | - one 18 | - two 19 | - three 20 | this: 21 | is: 22 | a.multi-line.example: | 23 | This is a multi 24 | line example. 25 | It could be a private key 26 | or something similar. 27 | -------------------------------------------------------------------------------- /testdata/test/bar.sls: -------------------------------------------------------------------------------- 1 | #!yaml|gpg 2 | 3 | secure_vars: 4 | bar: baz 5 | foo: bar 6 | -------------------------------------------------------------------------------- /testdata/test/baz.sls: -------------------------------------------------------------------------------- 1 | #!yaml|gpg 2 | 3 | secure_vars: 4 | bar: baz 5 | foo: bar 6 | -------------------------------------------------------------------------------- /testdata/test/foo.sls: -------------------------------------------------------------------------------- 1 | #!yaml|gpg 2 | 3 | secure_vars: 4 | bar: baz 5 | baz: "2592000" 6 | foo: bar 7 | -------------------------------------------------------------------------------- /testdata/test/simple.sls: -------------------------------------------------------------------------------- 1 | #!yaml|gpg 2 | 3 | stuff: "1.2" 4 | username: "123456" 5 | -------------------------------------------------------------------------------- /testdata/testkeys.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | # set -x 5 | 6 | PATH=$PATH:/usr/local/bin 7 | 8 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 9 | echo $DIR 10 | 11 | # find the gpg binary 12 | if command -v gpg1 &> /dev/null; then 13 | GPG=gpg1 14 | else 15 | if command -v gpg &> /dev/null; then 16 | GPG=gpg 17 | else 18 | echo "cannot find gpg binary" 19 | exit 1 20 | fi 21 | fi 22 | 23 | # test the gpg version (only works with gpg1) 24 | GPG_MAJOR_VERSION=`$GPG --version | head -1 | cut -d ' ' -f 3 | cut -d '.' -f 1` 25 | if [[ $GPG_MAJOR_VERSION != "1" ]]; then 26 | echo "GNUPGv1 required for tests" 27 | exit -1; 28 | fi 29 | 30 | mkdir -p $DIR/gnupg 31 | chmod 700 $DIR/gnupg 32 | $GPG --homedir $DIR/gnupg/ --gen-key --batch < $DIR/gpginit.txt 33 | $GPG --homedir $DIR/gnupg/ --expert --armor --export | $GPG --homedir $DIR/gnupg/ --import 34 | $GPG --homedir $DIR/gnupg/ --expert --armor --export-secret-key | $GPG --homedir $DIR/gnupg/ --import 35 | SECKEY=`$GPG --homedir $DIR/gnupg --list-secret-keys | grep 'sec ' | cut -d '/' -f 2 | cut -d ' ' -f 1` 36 | expect -c "spawn $GPG --homedir $DIR/gnupg --edit-key $SECKEY trust quit; send \"5\ry\r\"; expect eof" 37 | 38 | exit 0 39 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Everbridge, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | // Package utils is for general utility functions 22 | package utils 23 | 24 | import ( 25 | "bytes" 26 | "fmt" 27 | "os" 28 | "path/filepath" 29 | 30 | "github.com/Everbridge/generate-secure-pillar/pki" 31 | "github.com/Everbridge/generate-secure-pillar/sls" 32 | "github.com/rs/zerolog" 33 | "github.com/rs/zerolog/log" 34 | ) 35 | 36 | var logger = zerolog.New(os.Stdout) 37 | 38 | func init() { 39 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout}) 40 | } 41 | 42 | // SafeWrite checks that there is no error prior to trying to write a file 43 | func SafeWrite(buffer bytes.Buffer, outputFilePath string, err error) { 44 | if err != nil { 45 | logger.Fatal().Err(err) 46 | } else { 47 | _, err = sls.WriteSlsFile(buffer, outputFilePath) 48 | if err != nil { 49 | logger.Fatal().Err(err) 50 | } 51 | } 52 | } 53 | 54 | // PathAction applies an action to a YAML path 55 | func PathAction(s *sls.Sls, path string, action string) { 56 | vals := s.GetValueFromPath(path) 57 | if vals != nil { 58 | processedVals, err := s.ProcessValues(vals, action) 59 | if err != nil { 60 | logger.Fatal().Err(err).Msg("path action failed") 61 | } 62 | fmt.Printf("%s: %s\n", path, processedVals) 63 | } else { 64 | logger.Warn().Msgf("unable to find path: '%s'", path) 65 | } 66 | } 67 | 68 | // ProcessDir applies an action concurrently to a directory of files 69 | func ProcessDir(searchDir string, fileExt string, action string, outputFilePath string, topLevelElement string, pk pki.Pki) error { 70 | if len(searchDir) == 0 { 71 | return fmt.Errorf("search directory not specified") 72 | } 73 | 74 | // get a list of sls files along with the count 75 | files, count := FindFilesByExt(searchDir, fileExt) 76 | 77 | // copy files to a channel then close the 78 | // channel so that workers stop when done 79 | filesChan := make(chan string, count) 80 | for _, file := range files { 81 | filesChan <- file 82 | } 83 | close(filesChan) 84 | 85 | errChan := make(chan error, count) 86 | resChan := make(chan int, count) 87 | remaining := count 88 | 89 | // run workers 90 | for i := 0; i < count; i++ { 91 | go func() { 92 | for file := range filesChan { 93 | resChan <- applyActionAndWrite(file, action, &pk, topLevelElement, errChan) 94 | } 95 | }() 96 | } 97 | 98 | // collect results 99 | for i := 0; i < count; i++ { 100 | select { 101 | case byteCount := <-resChan: 102 | if action != sls.Validate && outputFilePath != os.Stdout.Name() { 103 | logger.Info().Msgf("%d bytes written", byteCount) 104 | logger.Info().Msgf("Finished processing %d of %d files\n", count-remaining+1, count) 105 | } 106 | remaining-- 107 | case err := <-errChan: 108 | return err 109 | } 110 | if remaining == 0 { 111 | break 112 | } 113 | } 114 | return nil 115 | } 116 | 117 | func applyActionAndWrite(file string, action string, pk *pki.Pki, topLevelElement string, errChan chan error) int { 118 | byteCount := 0 119 | s := sls.New(file, *pk, topLevelElement) 120 | if s.IsInclude || s.Error != nil { 121 | if s.Error != nil { 122 | logger.Warn().Err(s.Error) 123 | } 124 | return 0 125 | } 126 | 127 | buf, err := s.PerformAction(action) 128 | if buf.Len() > 0 && err != nil && action != sls.Validate { 129 | logger.Warn().Err(err) 130 | } else if err != nil && action == sls.Validate { 131 | logger.Warn().Err(err) 132 | } else if action == sls.Validate { 133 | fmt.Printf("%s:\nkey count: %d\n%s\n", s.FilePath, s.KeyCount, buf.String()) 134 | return byteCount 135 | } else if buf.Len() == 0 { 136 | err = fmt.Errorf("zero length buffer produced by '%s' for file '%s'", action, file) 137 | handleErr(err, errChan) 138 | return byteCount 139 | } 140 | 141 | if action != sls.Validate { 142 | byteCount, err = sls.WriteSlsFile(buf, file) 143 | } else { 144 | byteCount, err = os.Stdout.Write(buf.Bytes()) 145 | } 146 | 147 | if err != nil { 148 | handleErr(err, errChan) 149 | } 150 | 151 | return byteCount 152 | } 153 | 154 | func handleErr(err error, errChan chan error) { 155 | if err != nil { 156 | select { 157 | case errChan <- err: 158 | // will break parent goroutine out of loop 159 | default: 160 | // don't care, first error wins 161 | } 162 | return 163 | } 164 | } 165 | 166 | // FindFilesByExt recurses through the given searchDir returning a list of files with a given extension and it's length 167 | func FindFilesByExt(searchDir string, ext string) ([]string, int) { 168 | fileList := []string{} 169 | searchDir, err := filepath.Abs(searchDir) 170 | if err != nil { 171 | logger.Error().Err(err) 172 | return fileList, 0 173 | } 174 | err = checkForDir(searchDir) 175 | if err != nil { 176 | logger.Error().Err(err) 177 | return fileList, 0 178 | } 179 | 180 | err = filepath.Walk(searchDir, func(path string, f os.FileInfo, err error) error { 181 | if !f.IsDir() && filepath.Ext(f.Name()) == ext { 182 | fileList = append(fileList, path) 183 | } 184 | return nil 185 | }) 186 | if err != nil { 187 | logger.Fatal().Err(err).Msg("error walking file path") 188 | } 189 | 190 | return fileList, len(fileList) 191 | } 192 | 193 | // checkForDir does exactly what it says on the tin 194 | func checkForDir(filePath string) error { 195 | fi, err := os.Stat(filePath) 196 | if err != nil { 197 | return fmt.Errorf("cannot stat %s: %s", filePath, err) 198 | } 199 | switch mode := fi.Mode(); { 200 | case mode.IsRegular(): 201 | return fmt.Errorf("%s is a file", filePath) 202 | case mode.IsDir(): 203 | return nil 204 | } 205 | 206 | return err 207 | } 208 | --------------------------------------------------------------------------------