Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Leap:15.5:Update
sops
sops-3.7.3.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File sops-3.7.3.obscpio of Package sops
07070100000000000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001500000000sops-3.7.3/.circleci07070100000001000081A400000000000000000000000162794F9300000EB0000000000000000000000000000000000000002000000000sops-3.7.3/.circleci/config.ymlversion: 2.1 workflows: build-and-deploy: jobs: - build - push: filters: tags: only: /^v.*/ branches: ignore: /.*/ jobs: build: working_directory: /go/src/go.mozilla.org/sops docker: - image: circleci/golang:1.13 resource_class: large steps: - checkout - setup_remote_docker: version: 20.10.11 - run: name: Build containers command: | docker build -t mozilla/sops . docker tag mozilla/sops "mozilla/sops:$CIRCLE_SHA1" - run: name: Build containers (alpine) command: | # Just to ensure the container can be built. docker build -f Dockerfile.alpine -t mozilla/sops:alpine . push: machine: image: ubuntu-2004:202111-02 resource_class: large steps: - checkout - run: name: semver check command: | MAJOR=$(echo ${CIRCLE_TAG#v} | cut -d"." -f1) MINOR=$(echo ${CIRCLE_TAG#v} | cut -d"." -f2) PATCH=$(echo ${CIRCLE_TAG#v} | cut -d"." -f3) echo "export MAJOR=${MAJOR}" >> $BASH_ENV echo "export MINOR=${MINOR}" >> $BASH_ENV echo "export PATCH=${PATCH}" >> $BASH_ENV if [ -z $MAJOR ];then cat \<< EOF Failure Info: This job uses the semver from the git TAG as the public version to publish. - This should only run on workflows triggered by a tag. - The tag name should be a semver like 'v1.2.3' - The version should follow conventions documented at https://github.com/fsaintjacques/semver-tool EOF exit 1 fi - run: name: Build containers command: | docker build -t mozilla/sops . docker build -f Dockerfile.alpine -t mozilla/sops:alpine . - run: name: Tag & Push containers command: | #latest bin/ci/deploy_dockerhub.sh "latest" bin/ci/deploy_dockerhub.sh "alpine" # by sha echo "Tag and push mozilla/sops:$CIRCLE_SHA1" docker tag mozilla/sops "mozilla/sops:$CIRCLE_SHA1" bin/ci/deploy_dockerhub.sh "$CIRCLE_SHA1" # no sha for alpine # by semver # v1.2.3 if [ ! -z $PATCH ];then echo "Tag and Push mozilla/sops:v$MAJOR.$MINOR.$PATCH" docker tag mozilla/sops "mozilla/sops:v$MAJOR.$MINOR.$PATCH" bin/ci/deploy_dockerhub.sh "v$MAJOR.$MINOR.$PATCH" echo "Tag and Push mozilla/sops:v$MAJOR.$MINOR.$PATCH-alpine" docker tag mozilla/sops:alpine "mozilla/sops:v$MAJOR.$MINOR.$PATCH-alpine" bin/ci/deploy_dockerhub.sh "v$MAJOR.$MINOR.$PATCH-alpine" fi # v1.2 if [ ! -z $MINOR ];then echo "Tag and Push mozilla/sops:v$MAJOR.$MINOR" docker tag mozilla/sops "mozilla/sops:v$MAJOR.$MINOR" bin/ci/deploy_dockerhub.sh "v$MAJOR.$MINOR" echo "Tag and Push mozilla/sops:v$MAJOR.$MINOR-alpine" docker tag mozilla/sops:alpine "mozilla/sops:v$MAJOR.$MINOR-alpine" bin/ci/deploy_dockerhub.sh "v$MAJOR.$MINOR-alpine" fi # v1 echo "Tag and Push mozilla/sops:v$MAJOR" docker tag mozilla/sops "mozilla/sops:v$MAJOR" bin/ci/deploy_dockerhub.sh "v$MAJOR" echo "Tag and Push mozilla/sops:v$MAJOR-alpine" docker tag mozilla/sops:alpine "mozilla/sops:v$MAJOR-alpine" bin/ci/deploy_dockerhub.sh "v$MAJOR-alpine" 07070100000002000081A400000000000000000000000162794F9300000025000000000000000000000000000000000000001900000000sops-3.7.3/.dockerignore/.git /Dockerfile /Dockerfile.alpine 07070100000003000041ED00000000000000000000000362794F9300000000000000000000000000000000000000000000001300000000sops-3.7.3/.github07070100000004000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001D00000000sops-3.7.3/.github/workflows07070100000005000081A400000000000000000000000162794F9300000D85000000000000000000000000000000000000002500000000sops-3.7.3/.github/workflows/cli.ymlname: CLI on: push: branches: - develop - master pull_request: branches: - develop - master jobs: build: name: Build and test ${{ matrix.os }} ${{ matrix.arch }} runs-on: ubuntu-latest strategy: matrix: os: [linux, darwin, windows] arch: [amd64, arm64] exclude: - os: windows arch: arm64 env: VAULT_VERSION: "1.1.3" VAULT_TOKEN: "root" VAULT_ADDR: "http://127.0.0.1:8200" steps: - name: Install dependencies run: sudo apt-get update && sudo apt-get install git -y - name: Set up Go 1.17 uses: actions/setup-go@v2 with: go-version: 1.17 id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 - uses: actions/cache@v2 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} restore-keys: | ${{ runner.os }}-go- - name: Build Linux and Darwin if: matrix.os != 'windows' run: GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} go build -o sops-${{ matrix.os }}-${{ matrix.arch }}-${{ github.sha }} -v ./cmd/sops - name: Build Windows if: matrix.os == 'windows' run: GOOS=${{ matrix.os }} go build -o sops-${{ matrix.os }}-${{ github.sha }} -v ./cmd/sops - name: Import test GPG keys run: for i in 1 2 3 4 5; do gpg --import pgp/sops_functional_tests_key.asc && break || sleep 15; done - name: Test run: make test - name: Upload artifact for Linux and Darwin if: matrix.os != 'windows' uses: actions/upload-artifact@v2 with: name: sops-${{ matrix.os }}-${{ matrix.arch }}-${{ github.sha }} path: sops-${{ matrix.os }}-${{ matrix.arch }}-${{ github.sha }} - name: Upload artifact for Windows if: matrix.os == 'windows' uses: actions/upload-artifact@v2 with: name: sops-${{ matrix.os }}-${{ github.sha }} path: sops-${{ matrix.os }}-${{ github.sha }} test: name: Functional tests runs-on: ubuntu-latest needs: [build] env: VAULT_VERSION: "1.1.3" VAULT_TOKEN: "root" VAULT_ADDR: "http://127.0.0.1:8200" steps: - name: Install rustup run: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y --default-toolchain 1.47.0 - name: Check out code uses: actions/checkout@v2 - uses: actions/download-artifact@v2 with: name: sops-linux-amd64-${{ github.sha }} - name: Move SOPS binary run: mv sops-linux-amd64-${{ github.sha }} ./functional-tests/sops - name: Make SOPS binary executable run: chmod +x ./functional-tests/sops - name: Download Vault run: curl -O "https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip" && sudo unzip vault_${VAULT_VERSION}_linux_amd64.zip -d /usr/local/bin/ - name: Start Vault server run: vault server -dev -dev-root-token-id="$VAULT_TOKEN" & - name: Enable Vault KV run: vault secrets enable -version=1 kv - name: Import test GPG keys run: for i in 1 2 3 4 5; do gpg --import pgp/sops_functional_tests_key.asc && break || sleep 15; done - name: Run tests run: cargo test working-directory: ./functional-tests 07070100000006000081A400000000000000000000000162794F9300000C2E000000000000000000000000000000000000002900000000sops-3.7.3/.github/workflows/release.ymlname: Release on: push: tags: - "v*" jobs: tagged-release: name: "Tagged Release" runs-on: ubuntu-latest steps: - name: Install dependencies run: sudo apt-get update && sudo apt-get install git ruby rpm -y - name: Install fpm run: gem install fpm || sudo gem install fpm - name: Set up Go 1.17 uses: actions/setup-go@v2 with: go-version: 1.17 id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 - name: Go vendor run: go mod vendor - name: Make release directory run: mkdir dist - name: Build deb and rpm run: make deb-pkg rpm-pkg - name: Move deb and rpm into release directory run: mv *.deb *.rpm dist/ - name: Set RELEASE_VERSION run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - name: Set RELEASE_NUMBER run: echo "RELEASE_NUMBER=$(echo $RELEASE_VERSION | cut -c2-)" >> $GITHUB_ENV - name: Build linux amd64 binary run: GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -mod vendor -o dist/sops-${{ env.RELEASE_VERSION }}.linux.amd64 go.mozilla.org/sops/v3/cmd/sops && cp dist/sops-${{ env.RELEASE_VERSION }}.linux.amd64 dist/sops-${{ env.RELEASE_VERSION }}.linux - name: Build linux arm64 binary run: GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -mod vendor -o dist/sops-${{ env.RELEASE_VERSION }}.linux.arm64 go.mozilla.org/sops/v3/cmd/sops - name: Build darwin amd64 binary run: GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -mod vendor -o dist/sops-${{ env.RELEASE_VERSION }}.darwin.amd64 go.mozilla.org/sops/v3/cmd/sops - name: Copy darwin amd64 to have a no-architecture labeled version run: cp dist/sops-${{ env.RELEASE_VERSION }}.darwin.amd64 dist/sops-${{ env.RELEASE_VERSION }}.darwin - name: Build darwin arm64 binary run: GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -mod vendor -o dist/sops-${{ env.RELEASE_VERSION }}.darwin.arm64 go.mozilla.org/sops/v3/cmd/sops - name: Build windows binary run: GOOS=windows CGO_ENABLED=0 go build -mod vendor -o dist/sops-${{ env.RELEASE_VERSION }}.exe go.mozilla.org/sops/v3/cmd/sops - name: Create release uses: "mozilla/action-automatic-releases@latest" with: repo_token: "${{ secrets.GITHUB_TOKEN }}" prerelease: true files: | dist/sops-${{ env.RELEASE_VERSION }}.exe dist/sops-${{ env.RELEASE_VERSION }}.darwin.amd64 dist/sops-${{ env.RELEASE_VERSION }}.darwin.arm64 dist/sops-${{ env.RELEASE_VERSION }}.darwin dist/sops-${{ env.RELEASE_VERSION }}.linux.amd64 dist/sops-${{ env.RELEASE_VERSION }}.linux.arm64 dist/sops-${{ env.RELEASE_VERSION }}.linux dist/sops_${{ env.RELEASE_NUMBER }}_amd64.deb dist/sops_${{ env.RELEASE_NUMBER }}_arm64.deb dist/sops-${{ env.RELEASE_NUMBER }}-1.x86_64.rpm dist/sops-${{ env.RELEASE_NUMBER }}-1.aarch64.rpm 07070100000007000081A400000000000000000000000162794F9300000033000000000000000000000000000000000000001600000000sops-3.7.3/.gitignoretarget Cargo.lock vendor/ coverage.txt profile.out 07070100000008000081A400000000000000000000000162794F9300000081000000000000000000000000000000000000001600000000sops-3.7.3/.sops.yamlcreation_rules: - pgp: >- FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4, D7229043384BCC60326C6FB9D8720D957C3D3074 07070100000009000081A400000000000000000000000162794F93000021EE000000000000000000000000000000000000001900000000sops-3.7.3/CHANGELOG.rstChangelog ========= 3.7.3 ----- Changes: * Upgrade dependencies (#1024, #1045) * Build alpine container in CI (#1018, #1032, #1025) * keyservice: accept KeyServiceServer in LocalClient (#1035) * Add support for GCP Service Account within `GOOGLE_CREDENTIALS` (#953) Bug fixes: * Upload the correct binary for the linux amd64 build (#1026) * Fix bug when specifying multiple age recipients (#966) * Allow for empty yaml maps (#908) * Limit AWS role names to 64 characters (#1037) 3.7.2 ----- Changes: * README updates (#861, #860) * Various test fixes (#909, #906, #1008) * Added Linux and Darwin arm64 releases (#911, #891) * Upgrade to go v1.17 (#1012) * Support SOPS_AGE_KEY environment variable (#1006) Bug fixes: * Make sure comments in yaml files are not duplicated (#866) * Make sure configuration file paths work correctly relative to the config file in us (#853) 3.7.1 ----- Changes: * Security fix * Add release workflow (#843) * Fix issue where CI wouldn't run against master (#848) * Trim extra whitespace around age keys (#846) 3.7.0 ----- Features: * Add support for age (#688) * Add filename to exec-file (#761) Changes: * On failed decryption with GPG, return the error returned by GPG to the sops user (#762) * Use yaml.v3 instead of modified yaml.v2 for handling YAML files (#791) * Update aws-sdk-go to version v1.37.18 (#823) Project Changes: * Switch from TravisCI to Github Actions (#792) 3.6.1 ----- Features: * Add support for --unencrypted-regex (#715) Changes: * Use keys.openpgp.org instead of gpg.mozilla.org (#732) * Upgrade AWS SDK version (#714) * Support --input-type for exec-file (#699) Bug fixes: * Fixes broken Vault tests (#731) * Revert "Add standard newline/quoting behavior to dotenv store" (#706) 3.6.0 ----- Features: * Support for encrypting data through the use of Hashicorp Vault (#655) * `sops publish` now supports `--recursive` flag for publishing all files in a directory (#602) * `sops publish` now supports `--omit-extensions` flag for omitting the extension in the destination path (#602) * sops now supports JSON arrays of arrays (#642) Improvements: * Updates and standardization for the dotenv store (#612, #622) * Close temp files after using them for edit command (#685) Bug fixes: * AWS SDK usage now correctly resolves the `~/.aws/config` file (#680) * `sops updatekeys` now correctly matches config rules (#682) * `sops updatekeys` now correctly uses the config path cli flag (#672) * Partially empty sops config files don't break the use of sops anymore (#662) * Fix possible infinite loop in PGP's passphrase prompt call (#690) Project changes: * Dockerfile now based off of golang version 1.14 (#649) * Push alpine version of docker image to Dockerhub (#609) * Push major, major.minor, and major.minor.patch tagged docker images to Dockerhub (#607) * Removed out of date contact information (#668) * Update authors in the cli help text (#645) 3.5.0 ----- Features: * `sops exec-env` and `sops exec-file`, two new commands for utilizing sops secrets within a temporary file or env vars Bug fixes: * Sanitize AWS STS session name, as sops creates it based off of the machines hostname * Fix for `decrypt.Data` to support `.ini` files * Various package fixes related to switching to Go Modules * Fixes for Vault-related tests running locally and in CI. Project changes: * Change to proper use of go modules, changing to primary module name to `go.mozilla.org/sops/v3` * Change tags to requiring a `v` prefix. * Add documentation for `sops updatekeys` command 3.4.0 ----- Features: * `sops publish`, a new command for publishing sops encrypted secrets to S3, GCS, or Hashicorp Vault * Support for multiple Azure authentication mechanisms * Azure Keyvault support to the sops config file * `encrypted_regex` option to the sops config file Bug fixes: * Return non-zero exit code for invalid CLI flags * Broken path handling for sops editing on Windows * `go lint/fmt` violations * Check for pgp fingerprint before slicing it Project changes: * Build container using golang 1.12 * Switch to using go modules * Hashicorp Vault server in Travis CI build * Mozilla Publice License file to repo * Replaced expiring test gpg keys 3.3.1 ----- Bug fixes: * Make sure the pgp key fingerprint is longer than 16 characters before slicing it. (#463) * Allow for `--set` value to be a string. (#461) Project changes: * Using `develop` as a staging branch to create releases off of. What is in `master` is now the current stable release. * Upgrade to using Go 1.12 to build sops * Updated all vendored packages 3.3.0 ----- New features: * Multi-document support for YAML files * Support referencing AWS KMS keys by their alias * Support for INI files * Support for AWS CLI profiles * Comment support in .env files * Added vi to the list of known editors * Added a way to specify the GPG key server to use through the SOPS_GPG_KEYSERVER environment variable Bug fixes: * Now uses $HOME instead of ~ (which didn't work) to find the GPG home * Fix panic when vim was not available as an editor, but other alternative editors were * Fix issue with AWS KMS Encryption Contexts (#445) with more than one context value failing to decrypt intermittently. Includes an automatic fix for old files affected by this issue. Project infrastructure changes: * Added integration tests for AWS KMS * Added Code of Conduct 3.2.0 ----- * Added --output flag to write output a file directly instead of through stdout * Added support for dotenv files 3.1.1 ----- * Fix incorrect version number from previous release 3.1.0 ----- * Add support for Azure Key Service * Fix bug that prevented JSON escapes in input files from working 3.0.5 ----- * Prevent files from being encrypted twice * Fix empty comments not being decrypted correctly * If keyservicecmd returns an error, log it. * Initial sops workspace auditing support (still wip) * Refactor Store interface to reflect operations SOPS performs 3.0.3 ----- * --set now works with nested data structures and not just simple values * Changed default log level to warn instead of info * Avoid creating empty files when using the editor mode to create new files and not making any changes to the example files * Output unformatted strings when using --extract instead of encoding them to yaml * Allow forcing binary input and output types from command line flags * Deprecate filename_regex in favor of path_regex. filename_regex had a bug and matched on the whole file path, when it should have only matched on the file name. path_regex on the other hand is documented to match on the whole file path. * Add an encrypted-suffix option, the exact opposite of unencrypted-suffix * Allow specifying unencrypted_suffix and encrypted_suffix rules in the .sops.yaml configuration file * Introduce key service flag optionally prompting users on encryption/decryption 3.0.1 ----- * Don't consider io.EOF returned by Decoder.Token as error * add IsBinary: true to FileHints when encoding with crypto/openpgp * some improvements to error messages 3.0.0 ----- * Shamir secret sharing scheme support allows SOPS to require multiple master keys to access a data key and decrypt a file. See `sops groups -help` and the documentation in README. * Keyservice to forward access to a local master key on a socket, similar to gpg-agent. See `sops keyservice --help` and the documentation in README. * Encrypt comments by default * Support for Google Compute Platform KMS * Refactor of the store logic to separate the internal representation SOPS has of files from the external representation used in JSON and YAML files * Reencoding of versions as string on sops 1.X files. **WARNING** this change breaks backward compatibility. SOPS shows an error message with instructions on how to solve this if it happens. * Added command to reconfigure the keys used to encrypt/decrypt a file based on the .sops.yaml config file * Retrieve missing PGP keys from gpg.mozilla.org * Improved error messages for errors when decrypting files 2.0.0 ----- * [major] rewrite in Go 1.14 ---- * [medium] Support AWS KMS Encryption Contexts * [minor] Support insertion in encrypted documents via --set * [minor] Read location of gpg binary from SOPS_GPG_EXEC env variables 1.13 ---- * [minor] handle $EDITOR variable with parameters 1.12 ---- * [minor] make sure filename_regex gets applied to file names, not paths * [minor] move check of latest version under the -V flag * [medium] fix handling of binary data to preserve file integrity * [minor] try to use configuration when encrypting existing files 0707010000000A000081A400000000000000000000000162794F93000001EF000000000000000000000000000000000000001E00000000sops-3.7.3/CODE_OF_CONDUCT.md# Community Participation Guidelines This repository is governed by Mozilla's code of conduct and etiquette guidelines. For more details, please read the [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). ## How to Report For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page. 0707010000000B000081A400000000000000000000000162794F9300000830000000000000000000000000000000000000001B00000000sops-3.7.3/CONTRIBUTING.md# Contributing to SOPS Mozilla welcomes contributions from everyone. Here are a few guidelines and instructions if you're thinking of helping with the development of SOPS. # Getting started * Make sure you have Go 1.12 or greater installed. You can find information on how to install Go [here](https://golang.org/dl/) * After following the [Go installation guide](https://golang.org/doc/install), run `go get go.mozilla.org/sops`. This will automatically clone this repository. * Switch into sops's directory, which will be in `$GOPATH/src/go.mozilla.org/sops`. * Run the tests with `make test`. They should all pass. * Fork the project on GitHub. * Add your fork to git's remotes: * If you use SSH authentication: `git remote add <your username> git@github.com:<your username>/sops.git`. * Otherwise: `git remote add <your username> https://github.com/<your username>/sops.git`. * **Switch to the `develop` branch: `git checkout develop`** * Make any changes you want to sops, commit them, and push them to your fork. * **Create a pull request against `develop`**, and a contributor will come by and review your code. They may ask for some changes, and hopefully your contribution will be merged to the `develop` branch! # Guidelines * Unless it's particularly hard, changes that fix a bug should have a regression test to make sure that the bug is not introduced again. * New features and changes to existing features should be documented, and, if possible, tested. # Regenerating mocks If you encounter an error like `kms/mocks/KMSAPI.go:1607: cannot use (*KMSAPI)(nil) (type *KMSAPI) as type kmsiface.KMSAPI in assignment: *KMSAPI does not implement kmsiface.KMSAPI (missing ListResourceTags method)`, you need to regenerate mocks, probably because the interface was changed by a vendoring update. There is a make command to do this for you. Simply run `make mock`, and the new mocks will be automatically generated. # Communication If you need any help contributing to sops, several contributors are on the `#go` channel on [Mozilla's IRC server](https://wiki.mozilla.org/IRC). 0707010000000C000081A400000000000000000000000162794F93000000DF000000000000000000000000000000000000001600000000sops-3.7.3/DockerfileFROM golang:1.17 COPY . /go/src/go.mozilla.org/sops WORKDIR /go/src/go.mozilla.org/sops RUN CGO_ENABLED=1 make install RUN apt-get update RUN apt-get install -y vim python3-pip emacs RUN pip install awscli ENV EDITOR vim 0707010000000D000081A400000000000000000000000162794F9300000156000000000000000000000000000000000000001D00000000sops-3.7.3/Dockerfile.alpineFROM golang:1.17-alpine3.15 AS builder RUN apk --no-cache add make COPY . /go/src/go.mozilla.org/sops WORKDIR /go/src/go.mozilla.org/sops RUN CGO_ENABLED=1 make install FROM alpine:3.15 RUN apk --no-cache add \ vim ca-certificates ENV EDITOR vim COPY --from=builder /go/bin/sops /usr/local/bin/sops ENTRYPOINT ["/usr/local/bin/sops"] 0707010000000E000081A400000000000000000000000162794F9300004155000000000000000000000000000000000000001300000000sops-3.7.3/LICENSEMozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. 0707010000000F000081A400000000000000000000000162794F93000011E0000000000000000000000000000000000000001400000000sops-3.7.3/Makefile# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. PROJECT := go.mozilla.org/sops/v3 GO := GOPROXY=https://proxy.golang.org go GOLINT := golint all: test vet generate install functional-tests origin-build: test vet generate install functional-tests-all install: $(GO) install go.mozilla.org/sops/v3/cmd/sops tag: all git tag -s $(TAGVER) -a -m "$(TAGMSG)" lint: $(GOLINT) $(PROJECT) vendor: $(GO) mod tidy $(GO) mod vendor vet: $(GO) vet $(PROJECT) test: vendor gpg --import pgp/sops_functional_tests_key.asc 2>&1 1>/dev/null || exit 0 ./test.sh showcoverage: test $(GO) tool cover -html=coverage.out generate: keyservice/keyservice.pb.go $(GO) generate %.pb.go: %.proto protoc --go_out=plugins=grpc:. $< functional-tests: $(GO) build -o functional-tests/sops go.mozilla.org/sops/v3/cmd/sops cd functional-tests && cargo test # Ignored tests are ones that require external services (e.g. AWS KMS) # TODO: Once `--include-ignored` lands in rust stable, switch to that. functional-tests-all: $(GO) build -o functional-tests/sops go.mozilla.org/sops/v3/cmd/sops cd functional-tests && cargo test && cargo test -- --ignored # Creates variables during target re-definition. Basically this block allows the particular variables to be used in the final target build-deb-%: OS = $(word 1,$(subst -, ,$*)) build-deb-%: ARCH = $(word 2,$(subst -, ,$*)) build-deb-%: FPM_ARCH = $(word 3,$(subst -, ,$*)) # Poor-mans function with parameters being split out from the variable part of it's name build-deb-%: rm -rf tmppkg mkdir -p tmppkg/usr/local/bin GOOS=$(OS) GOARCH="$(ARCH)" CGO_ENABLED=0 go build -mod vendor -o tmppkg/usr/local/bin/sops go.mozilla.org/sops/v3/cmd/sops fpm -C tmppkg -n sops --license MPL2.0 --vendor mozilla \ --description "Sops is an editor of encrypted files that supports YAML, JSON and BINARY formats and encrypts with AWS KMS and PGP." \ -m "AJ Bahnken <ajvb+sops@mozilla.com>" \ --url https://go.mozilla.org/sops \ --architecture $(FPM_ARCH) \ -v "$$(grep '^const Version' version/version.go |cut -d \" -f 2)" \ -s dir -t deb . # Create .deb packages for multiple architectures deb-pkg: vendor build-deb-linux-amd64-x86_64 build-deb-linux-arm64-arm64 # Creates variables during target re-definition. Basically this block allows the particular variables to be used in the final target build-rpm-%: OS = $(word 1,$(subst -, ,$*)) build-rpm-%: ARCH = $(word 2,$(subst -, ,$*)) build-rpm-%: FPM_ARCH = $(word 3,$(subst -, ,$*)) # Poor-mans function with parameters being split out from the variable part of it's name build-rpm-%: rm -rf tmppkg mkdir -p tmppkg/usr/local/bin GOOS=$(OS) GOARCH="$(ARCH)" CGO_ENABLED=0 go build -mod vendor -o tmppkg/usr/local/bin/sops go.mozilla.org/sops/v3/cmd/sops fpm -C tmppkg -n sops --license MPL2.0 --vendor mozilla \ --description "Sops is an editor of encrypted files that supports YAML, JSON and BINARY formats and encrypts with AWS KMS and PGP." \ -m "AJ Bahnken <ajvb+sops@mozilla.com>" \ --url https://go.mozilla.org/sops \ --architecture $(FPM_ARCH) \ --rpm-os $(OS) \ -v "$$(grep '^const Version' version/version.go |cut -d \" -f 2)" \ -s dir -t rpm . # Create .rpm packages for multiple architectures rpm-pkg: vendor build-rpm-linux-amd64-x86_64 build-rpm-linux-arm64-arm64 dmg-pkg: install ifneq ($(OS),darwin) echo 'you must be on MacOS and set OS=darwin on the make command line to build an OSX package' else rm -rf tmppkg mkdir -p tmppkg/usr/local/bin cp $$GOPATH/bin/sops tmppkg/usr/local/bin/ fpm -C tmppkg -n sops --license MPL2.0 --vendor mozilla \ --description "Sops is an editor of encrypted files that supports YAML, JSON and BINARY formats and encrypts with AWS KMS and PGP." \ -m "Mozilla Security <security@mozilla.org>" \ --url https://go.mozilla.org/sops \ --architecture x86_64 \ -v "$$(grep '^const Version' version/version.go |cut -d \" -f 2)" \ -s dir -t osxpkg \ --osxpkg-identifier-prefix org.mozilla.sops \ -p tmppkg/sops-$$(git describe --abbrev=0 --tags).pkg . hdiutil makehybrid -hfs -hfs-volume-name "Mozilla Sops" \ -o tmppkg/sops-$$(git describe --abbrev=0 --tags).dmg tmpdmg endif download-index: bash make_download_page.sh mock: go install github.com/vektra/mockery/.../ mockery -dir vendor/github.com/aws/aws-sdk-go/service/kms/kmsiface/ -name KMSAPI -output kms/mocks .PHONY: all test generate clean vendor functional-tests mock 07070100000010000081A400000000000000000000000162794F930000FB57000000000000000000000000000000000000001600000000sops-3.7.3/README.rstSOPS: Secrets OPerationS ======================== **sops** is an editor of encrypted files that supports YAML, JSON, ENV, INI and BINARY formats and encrypts with AWS KMS, GCP KMS, Azure Key Vault, age, and PGP. (`demo <https://www.youtube.com/watch?v=YTEVyLXFiq0>`_) .. image:: https://i.imgur.com/X0TM5NI.gif ------------ .. image:: https://pkg.go.dev/badge/go.mozilla.org/sops/v3.svg :target: https://pkg.go.dev/go.mozilla.org/sops/v3 Download -------- Stable release ~~~~~~~~~~~~~~ Binaries and packages of the latest stable release are available at `https://github.com/mozilla/sops/releases <https://github.com/mozilla/sops/releases>`_. Development branch ~~~~~~~~~~~~~~~~~~ For the adventurous, unstable features are available in the `develop` branch, which you can install from source: .. code:: bash $ mkdir -p $GOPATH/src/go.mozilla.org/sops/ $ git clone https://github.com/mozilla/sops.git $GOPATH/src/go.mozilla.org/sops/ $ cd $GOPATH/src/go.mozilla.org/sops/ $ git checkout develop $ make install (requires Go >= 1.17) If you don't have Go installed, set it up with: .. code:: bash $ {apt,yum,brew} install golang $ echo 'export GOPATH=~/go' >> ~/.bashrc $ source ~/.bashrc $ mkdir $GOPATH Or whatever variation of the above fits your system and shell. To use **sops** as a library, take a look at the `decrypt package <https://pkg.go.dev/go.mozilla.org/sops/v3/decrypt>`_. .. sectnum:: .. contents:: Table of Contents Usage ----- For a quick presentation of Sops, check out this Youtube tutorial: .. image:: https://img.youtube.com/vi/V2PRhxphH2w/0.jpg :target: https://www.youtube.com/watch?v=V2PRhxphH2w If you're using AWS KMS, create one or multiple master keys in the IAM console and export them, comma separated, in the **SOPS_KMS_ARN** env variable. It is recommended to use at least two master keys in different regions. .. code:: bash export SOPS_KMS_ARN="arn:aws:kms:us-east-1:656532927350:key/920aff2e-c5f1-4040-943a-047fa387b27e,arn:aws:kms:ap-southeast-1:656532927350:key/9006a8aa-0fa6-4c14-930e-a2dfb916de1d" Your AWS credentials must be present in ``~/.aws/credentials``. sops uses aws-sdk-go. .. code:: $ cat ~/.aws/credentials [default] aws_access_key_id = AKI..... aws_secret_access_key = mw...... If you want to use PGP, export the fingerprints of the public keys, comma separated, in the **SOPS_PGP_FP** env variable. .. code:: bash export SOPS_PGP_FP="85D77543B3D624B63CEA9E6DBC17301B491B3F21,E60892BB9BD89A69F759A1A0A3D652173B763E8F" Note: you can use both PGP and KMS simultaneously. Then simply call ``sops`` with a file path as argument. It will handle the encryption/decryption transparently and open the cleartext file in an editor .. code:: shell $ sops mynewtestfile.yaml mynewtestfile.yaml doesn't exist, creating it. please wait while an encryption key is being generated and stored in a secure fashion file written to mynewtestfile.yaml Editing will happen in whatever ``$EDITOR`` is set to, or, if it's not set, in vim. Keep in mind that sops will wait for the editor to exit, and then try to reencrypt the file. Some GUI editors (atom, sublime) spawn a child process and then exit immediately. They usually have an option to wait for the main editor window to be closed before exiting. See `#127 <https://github.com/mozilla/sops/issues/127>`_ for more information. The resulting encrypted file looks like this: .. code:: yaml myapp1: ENC[AES256_GCM,data:Tr7o=,iv:1=,aad:No=,tag:k=] app2: db: user: ENC[AES256_GCM,data:CwE4O1s=,iv:2k=,aad:o=,tag:w==] password: ENC[AES256_GCM,data:p673w==,iv:YY=,aad:UQ=,tag:A=] # private key for secret operations in app2 key: |- ENC[AES256_GCM,data:Ea3kL5O5U8=,iv:DM=,aad:FKA=,tag:EA==] an_array: - ENC[AES256_GCM,data:v8jQ=,iv:HBE=,aad:21c=,tag:gA==] - ENC[AES256_GCM,data:X10=,iv:o8=,aad:CQ=,tag:Hw==] - ENC[AES256_GCM,data:KN=,iv:160=,aad:fI4=,tag:tNw==] sops: kms: - created_at: 1441570389.775376 enc: CiC....Pm1Hm arn: arn:aws:kms:us-east-1:656532927350:key/920aff2e-c5f1-4040-943a-047fa387b27e - created_at: 1441570391.925734 enc: Ci...awNx arn: arn:aws:kms:ap-southeast-1:656532927350:key/9006a8aa-0fa6-4c14-930e-a2dfb916de1d pgp: - fp: 85D77543B3D624B63CEA9E6DBC17301B491B3F21 created_at: 1441570391.930042 enc: | -----BEGIN PGP MESSAGE----- hQIMA0t4uZHfl9qgAQ//UvGAwGePyHuf2/zayWcloGaDs0MzI+zw6CmXvMRNPUsA ...=oJgS -----END PGP MESSAGE----- A copy of the encryption/decryption key is stored securely in each KMS and PGP block. As long as one of the KMS or PGP method is still usable, you will be able to access your data. To decrypt a file in a ``cat`` fashion, use the ``-d`` flag: .. code:: bash $ sops -d mynewtestfile.yaml ``sops`` encrypted files contain the necessary information to decrypt their content. All a user of ``sops`` needs is valid AWS credentials and the necessary permissions on KMS keys. Given that, the only command a ``sops`` user needs is: .. code:: bash $ sops <file> `<file>` will be opened, decrypted, passed to a text editor (vim by default), encrypted if modified, and saved back to its original location. All of these steps, apart from the actual editing, are transparent to the user. Test with the dev PGP key ~~~~~~~~~~~~~~~~~~~~~~~~~ If you want to test **sops** without having to do a bunch of setup, you can use the example files and pgp key provided with the repository:: $ git clone https://github.com/mozilla/sops.git $ cd sops $ gpg --import pgp/sops_functional_tests_key.asc $ sops example.yaml This last step will decrypt ``example.yaml`` using the test private key. Encrypting using age ~~~~~~~~~~~~~~~~~~~~ `age <https://age-encryption.org/>`_ is a simple, modern, and secure tool for encrypting files. It's recommended to use age over PGP, if possible. You can encrypt a file for one or more age recipients (comma separated) using the ``--age`` option or the **SOPS_AGE_RECIPIENTS** environment variable: .. code:: bash $ sops --encrypt --age age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw test.yaml > test.enc.yaml When decrypting a file with the corresponding identity, sops will look for a text file name ``keys.txt`` located in a ``sops`` subdirectory of your user configuration directory. On Linux, this would be ``$XDG_CONFIG_HOME/sops/age/keys.txt``. On macOS, this would be ``$HOME/Library/Application Support/sops/age/keys.txt``. On Windows, this would be ``%AppData%\sops\age\keys.txt``. You can specify the location of this file manually by setting the environment variable **SOPS_AGE_KEY_FILE**. Alternatively you can provide the the key(s) directly by setting the **SOPS_AGE_KEY** environment variable. The contents of this key file should be a list of age X25519 identities, one per line. Lines beginning with ``#`` are considered comments and ignored. Each identity will be tried in sequence until one is able to decrypt the data. Encrypting with SSH keys via age is not yet supported by sops. Encrypting using GCP KMS ~~~~~~~~~~~~~~~~~~~~~~~~ GCP KMS uses `Application Default Credentials <https://developers.google.com/identity/protocols/application-default-credentials>`_. If you already logged in using .. code:: bash $ gcloud auth login you can enable application default credentials using the sdk:: $ gcloud auth application-default login Encrypting/decrypting with GCP KMS requires a KMS ResourceID. You can use the cloud console the get the ResourceID or you can create one using the gcloud sdk: .. code:: bash $ gcloud kms keyrings create sops --location global $ gcloud kms keys create sops-key --location global --keyring sops --purpose encryption $ gcloud kms keys list --location global --keyring sops # you should see NAME PURPOSE PRIMARY_STATE projects/my-project/locations/global/keyRings/sops/cryptoKeys/sops-key ENCRYPT_DECRYPT ENABLED Now you can encrypt a file using:: $ sops --encrypt --gcp-kms projects/my-project/locations/global/keyRings/sops/cryptoKeys/sops-key test.yaml > test.enc.yaml And decrypt it using:: $ sops --decrypt test.enc.yaml Encrypting using Azure Key Vault ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The Azure Key Vault integration tries several authentication methods, in this order: 1. Client credentials 2. Client Certificate 3. Username Password 4. MSI 5. Azure CLI auth You can force a specific authentication method through the AZURE_AUTH_METHOD environment variable, which may be one of: clientcredentials, clientcertificate, usernamepassword, msi, or cli (default). For example, you can use service principals with the following environment variables: .. code:: bash AZURE_TENANT_ID AZURE_CLIENT_ID AZURE_CLIENT_SECRET You can create a service principal using the cli like this: .. code:: bash $ az ad sp create-for-rbac -n my-keyvault-sp { "appId": "<some-uuid>", "displayName": "my-keyvault-sp", "name": "http://my-keyvault-sp", "password": "<some-uuid>", "tenant": "<tenant-id>" } The appId is the client id, and the password is the client secret. Encrypting/decrypting with Azure Key Vault requires the resource identifier for a key. This has the following form:: https://${VAULT_URL}/keys/${KEY_NAME}/${KEY_VERSION} To create a Key Vault and assign your service principal permissions on it from the commandline: .. code:: bash # Create a resource group if you do not have one: $ az group create --name sops-rg --location westeurope # Key Vault names are globally unique, so generate one: $ keyvault_name=sops-$(uuidgen | tr -d - | head -c 16) # Create a Vault, a key, and give the service principal access: $ az keyvault create --name $keyvault_name --resource-group sops-rg --location westeurope $ az keyvault key create --name sops-key --vault-name $keyvault_name --protection software --ops encrypt decrypt $ az keyvault set-policy --name $keyvault_name --resource-group sops-rg --spn $AZURE_CLIENT_ID \ --key-permissions encrypt decrypt # Read the key id: $ az keyvault key show --name sops-key --vault-name $keyvault_name --query key.kid https://sops.vault.azure.net/keys/sops-key/some-string Now you can encrypt a file using:: $ sops --encrypt --azure-kv https://sops.vault.azure.net/keys/sops-key/some-string test.yaml > test.enc.yaml And decrypt it using:: $ sops --decrypt test.enc.yaml Encrypting using Hashicorp Vault ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ We assume you have an instance (or more) of Vault running and you have privileged access to it. For instructions on how to deploy a secure instance of Vault, refer to Hashicorp's official documentation. To easily deploy Vault locally: (DO NOT DO THIS FOR PRODUCTION!!!) .. code:: bash $ docker run -d -p8200:8200 vault:1.2.0 server -dev -dev-root-token-id=toor .. code:: bash $ # Substitute this with the address Vault is running on $ export VAULT_ADDR=http://127.0.0.1:8200 $ # this may not be necessary in case you previously used `vault login` for production use $ export VAULT_TOKEN=toor $ # to check if Vault started and is configured correctly $ vault status Key Value --- ----- Seal Type shamir Initialized true Sealed false Total Shares 1 Threshold 1 Version 1.2.0 Cluster Name vault-cluster-618cc902 Cluster ID e532e461-e8f0-1352-8a41-fc7c11096908 HA Enabled false $ # It is required to enable a transit engine if not already done (It is suggested to create a transit engine specifically for sops, in which it is possible to have multiple keys with various permission levels) $ vault secrets enable -path=sops transit Success! Enabled the transit secrets engine at: sops/ $ # Then create one or more keys $ vault write sops/keys/firstkey type=rsa-4096 Success! Data written to: sops/keys/firstkey $ vault write sops/keys/secondkey type=rsa-2048 Success! Data written to: sops/keys/secondkey $ vault write sops/keys/thirdkey type=chacha20-poly1305 Success! Data written to: sops/keys/thirdkey $ sops --hc-vault-transit $VAULT_ADDR/v1/sops/keys/firstkey vault_example.yml $ cat <<EOF > .sops.yaml creation_rules: - path_regex: \.dev\.yaml$ hc_vault_transit_uri: "$VAULT_ADDR/v1/sops/keys/secondkey" - path_regex: \.prod\.yaml$ hc_vault_transit_uri: "$VAULT_ADDR/v1/sops/keys/thirdkey" EOF $ sops --verbose -e prod/raw.yaml > prod/encrypted.yaml Adding and removing keys ~~~~~~~~~~~~~~~~~~~~~~~~ When creating new files, ``sops`` uses the PGP, KMS and GCP KMS defined in the command line arguments ``--kms``, ``--pgp``, ``--gcp-kms`` or ``--azure-kv``, or from the environment variables ``SOPS_KMS_ARN``, ``SOPS_PGP_FP``, ``SOPS_GCP_KMS_IDS``, ``SOPS_AZURE_KEYVAULT_URLS``. That information is stored in the file under the ``sops`` section, such that decrypting files does not require providing those parameters again. Master PGP and KMS keys can be added and removed from a ``sops`` file in one of three ways:: 1. By using a .sops.yaml file and the ``updatekeys`` command. 2. By using command line flags. 3. By editing the file directly. The sops team recommends the ``updatekeys`` approach. ``updatekeys`` command ********************** The ``updatekeys`` command uses the `.sops.yaml <#using-sops-yaml-conf-to-select-kms-pgp-for-new-files>`_ configuration file to update (add or remove) the corresponding secrets in the encrypted file. Note that the example below uses the `Block Scalar yaml construct <https://yaml-multiline.info/>`_ to build a space separated list. .. code:: yaml creation_rules: - pgp: >- 85D77543B3D624B63CEA9E6DBC17301B491B3F21, FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4 .. code:: bash $ sops updatekeys test.enc.yaml Sops will prompt you with the changes to be made. This interactivity can be disabled by supplying the ``-y`` flag. Command Line ************ Command line flag ``--add-kms``, ``--add-pgp``, ``--add-gcp-kms``, ``--add-azure-kv``, ``--rm-kms``, ``--rm-pgp``, ``--rm-gcp-kms`` and ``--rm-azure-kv`` can be used to add and remove keys from a file. These flags use the comma separated syntax as the ``--kms``, ``--pgp``, ``--gcp-kms`` and ``--azure-kv`` arguments when creating new files. Note that ``-r`` or ``--rotate`` is mandatory in this mode. Not specifying rotate will ignore the ``--add-*`` options. Use ``updatekeys`` if you want to add a key without rotating the data key. .. code:: bash # add a new pgp key to the file and rotate the data key $ sops -r -i --add-pgp 85D77543B3D624B63CEA9E6DBC17301B491B3F21 example.yaml # remove a pgp key from the file and rotate the data key $ sops -r -i --rm-pgp 85D77543B3D624B63CEA9E6DBC17301B491B3F21 example.yaml Direct Editing ************** Alternatively, invoking ``sops`` with the flag **-s** will display the master keys while editing. This method can be used to add or remove kms or pgp keys under the sops section. Invoking ``sops`` with the **-i** flag will perform an in-place edit instead of redirecting output to ``stdout``. For example, to add a KMS master key to a file, add the following entry while editing: .. code:: yaml sops: kms: - arn: arn:aws:kms:us-east-1:656532927350:key/920aff2e-c5f1-4040-943a-047fa387b27e And, similarly, to add a PGP master key, we add its fingerprint: .. code:: yaml sops: pgp: - fp: 85D77543B3D624B63CEA9E6DBC17301B491B3F21 When the file is saved, ``sops`` will update its metadata and encrypt the data key with the freshly added master keys. The removed entries are simply deleted from the file. When removing keys, it is recommended to rotate the data key using ``-r``, otherwise owners of the removed key may have add access to the data key in the past. KMS AWS Profiles ~~~~~~~~~~~~~~~~ If you want to use a specific profile, you can do so with `aws_profile`: .. code:: yaml sops: kms: - arn: arn:aws:kms:us-east-1:656532927350:key/920aff2e-c5f1-4040-943a-047fa387b27e aws_profile: foo If no AWS profile is set, default credentials will be used. Similarly the `--aws-profile` flag can be set with the command line with any of the KMS commands. Assuming roles and using KMS in various AWS accounts ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SOPS has the ability to use KMS in multiple AWS accounts by assuming roles in each account. Being able to assume roles is a nice feature of AWS that allows administrators to establish trust relationships between accounts, typically from the most secure account to the least secure one. In our use-case, we use roles to indicate that a user of the Master AWS account is allowed to make use of KMS master keys in development and staging AWS accounts. Using roles, a single file can be encrypted with KMS keys in multiple accounts, thus increasing reliability and ease of use. You can use keys in various accounts by tying each KMS master key to a role that the user is allowed to assume in each account. The `IAM roles <http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use.html>`_ documentation has full details on how this needs to be configured on AWS's side. From the point of view of ``sops``, you only need to specify the role a KMS key must assume alongside its ARN, as follows: .. code:: yaml sops: kms: - arn: arn:aws:kms:us-east-1:656532927350:key/920aff2e-c5f1-4040-943a-047fa387b27e role: arn:aws:iam::927034868273:role/sops-dev-xyz The role must have permission to call Encrypt and Decrypt using KMS. An example policy is shown below. .. code:: json { "Sid": "Allow use of the key", "Effect": "Allow", "Action": [ "kms:Encrypt", "kms:Decrypt", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:DescribeKey" ], "Resource": "*", "Principal": { "AWS": [ "arn:aws:iam::927034868273:role/sops-dev-xyz" ] } } You can specify a role in the ``--kms`` flag and ``SOPS_KMS_ARN`` variable by appending it to the ARN of the master key, separated by a **+** sign:: <KMS ARN>+<ROLE ARN> arn:aws:kms:us-west-2:927034868273:key/fe86dd69-4132-404c-ab86-4269956b4500+arn:aws:iam::927034868273:role/sops-dev-xyz AWS KMS Encryption Context ~~~~~~~~~~~~~~~~~~~~~~~~~~ SOPS has the ability to use `AWS KMS key policy and encryption context <http://docs.aws.amazon.com/kms/latest/developerguide/encryption-context.html>`_ to refine the access control of a given KMS master key. When creating a new file, you can specify encryption context in the ``--encryption-context`` flag by comma separated list of key-value pairs: .. code:: bash $ sops --encryption-context Environment:production,Role:web-server test.dev.yaml The format of the Encrypt Context string is ``<EncryptionContext Key>:<EncryptionContext Value>,<EncryptionContext Key>:<EncryptionContext Value>,...`` The encryption context will be stored in the file metadata and does not need to be provided at decryption. Encryption contexts can be used in conjunction with KMS Key Policies to define roles that can only access a given context. An example policy is shown below: .. code:: json { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::111122223333:role/RoleForExampleApp" }, "Action": "kms:Decrypt", "Resource": "*", "Condition": { "StringEquals": { "kms:EncryptionContext:AppName": "ExampleApp", "kms:EncryptionContext:FilePath": "/var/opt/secrets/" } } } Key Rotation ~~~~~~~~~~~~ It is recommended to renew the data key on a regular basis. ``sops`` supports key rotation via the ``-r`` flag. Invoking it on an existing file causes sops to reencrypt the file with a new data key, which is then encrypted with the various KMS and PGP master keys defined in the file. .. code:: bash sops -r example.yaml Using .sops.yaml conf to select KMS/PGP for new files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It is often tedious to specify the ``--kms`` ``--gcp-kms`` and ``--pgp`` parameters for creation of all new files. If your secrets are stored under a specific directory, like a ``git`` repository, you can create a ``.sops.yaml`` configuration file at the root directory to define which keys are used for which filename. Let's take an example: * file named **something.dev.yaml** should use one set of KMS A * file named **something.prod.yaml** should use another set of KMS B * other files use a third set of KMS C * all live under **mysecretrepo/something.{dev,prod,gcp}.yaml** Under those circumstances, a file placed at **mysecretrepo/.sops.yaml** can manage the three sets of configurations for the three types of files: .. code:: yaml # creation rules are evaluated sequentially, the first match wins creation_rules: # upon creation of a file that matches the pattern *.dev.yaml, # KMS set A is used - path_regex: \.dev\.yaml$ kms: 'arn:aws:kms:us-west-2:927034868273:key/fe86dd69-4132-404c-ab86-4269956b4500,arn:aws:kms:us-west-2:361527076523:key/5052f06a-5d3f-489e-b86c-57201e06f31e+arn:aws:iam::361527076523:role/hiera-sops-prod' pgp: 'FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4' # prod files use KMS set B in the PROD IAM - path_regex: \.prod\.yaml$ kms: 'arn:aws:kms:us-west-2:361527076523:key/5052f06a-5d3f-489e-b86c-57201e06f31e+arn:aws:iam::361527076523:role/hiera-sops-prod,arn:aws:kms:eu-central-1:361527076523:key/cb1fab90-8d17-42a1-a9d8-334968904f94+arn:aws:iam::361527076523:role/hiera-sops-prod' pgp: 'FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4' hc_vault_uris: "http://localhost:8200/v1/sops/keys/thirdkey" # gcp files using GCP KMS - path_regex: \.gcp\.yaml$ gcp_kms: projects/mygcproject/locations/global/keyRings/mykeyring/cryptoKeys/thekey # Finally, if the rules above have not matched, this one is a # catchall that will encrypt the file using KMS set C # The absence of a path_regex means it will match everything - kms: 'arn:aws:kms:us-west-2:927034868273:key/fe86dd69-4132-404c-ab86-4269956b4500,arn:aws:kms:us-west-2:142069644989:key/846cfb17-373d-49b9-8baf-f36b04512e47,arn:aws:kms:us-west-2:361527076523:key/5052f06a-5d3f-489e-b86c-57201e06f31e' pgp: 'FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4' When creating any file under **mysecretrepo**, whether at the root or under a subdirectory, sops will recursively look for a ``.sops.yaml`` file. If one is found, the filename of the file being created is compared with the filename regexes of the configuration file. The first regex that matches is selected, and its KMS and PGP keys are used to encrypt the file. It should be noted that the looking up of ``.sops.yaml`` is from the working directory (CWD) instead of the directory of the encrypting file (see `Issue 242 <https://github.com/mozilla/sops/issues/242>`_). The path_regex checks the path of the encrypting file relative to the .sops.yaml config file. Here is another example: * files located under directory **development** should use one set of KMS A * files located under directory **production** should use another set of KMS B * other files use a third set of KMS C .. code:: yaml creation_rules: # upon creation of a file under development, # KMS set A is used - path_regex: .*/development/.* kms: 'arn:aws:kms:us-west-2:927034868273:key/fe86dd69-4132-404c-ab86-4269956b4500,arn:aws:kms:us-west-2:361527076523:key/5052f06a-5d3f-489e-b86c-57201e06f31e+arn:aws:iam::361527076523:role/hiera-sops-prod' pgp: 'FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4' # prod files use KMS set B in the PROD IAM - path_regex: .*/production/.* kms: 'arn:aws:kms:us-west-2:361527076523:key/5052f06a-5d3f-489e-b86c-57201e06f31e+arn:aws:iam::361527076523:role/hiera-sops-prod,arn:aws:kms:eu-central-1:361527076523:key/cb1fab90-8d17-42a1-a9d8-334968904f94+arn:aws:iam::361527076523:role/hiera-sops-prod' pgp: 'FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4' # other files use KMS set C - kms: 'arn:aws:kms:us-west-2:927034868273:key/fe86dd69-4132-404c-ab86-4269956b4500,arn:aws:kms:us-west-2:142069644989:key/846cfb17-373d-49b9-8baf-f36b04512e47,arn:aws:kms:us-west-2:361527076523:key/5052f06a-5d3f-489e-b86c-57201e06f31e' pgp: 'FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4' Creating a new file with the right keys is now as simple as .. code:: bash $ sops <newfile>.prod.yaml Note that the configuration file is ignored when KMS or PGP parameters are passed on the sops command line or in environment variables. Specify a different GPG executable ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``sops`` checks for the ``SOPS_GPG_EXEC`` environment variable. If specified, it will attempt to use the executable set there instead of the default of ``gpg``. Example: place the following in your ``~/.bashrc`` .. code:: bash SOPS_GPG_EXEC = 'your_gpg_client_wrapper' Specify a different GPG key server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By default, ``sops`` uses the key server ``keys.openpgp.org`` to retrieve the GPG keys that are not present in the local keyring. This is no longer configurable. You can learn more about why from this write-up: `SKS Keyserver Network Under Attack <https://gist.github.com/rjhansen/67ab921ffb4084c865b3618d6955275f>`_. Key groups ~~~~~~~~~~ By default, ``sops`` encrypts the data key for a file with each of the master keys, such that if any of the master keys is available, the file can be decrypted. However, it is sometimes desirable to require access to multiple master keys in order to decrypt files. This can be achieved with key groups. When using key groups in sops, data keys are split into parts such that keys from multiple groups are required to decrypt a file. ``sops`` uses Shamir's Secret Sharing to split the data key such that each key group has a fragment, each key in the key group can decrypt that fragment, and a configurable number of fragments (threshold) are needed to decrypt and piece together the complete data key. When decrypting a file using multiple key groups, ``sops`` goes through key groups in order, and in each group, tries to recover the fragment of the data key using a master key from that group. Once the fragment is recovered, ``sops`` moves on to the next group, until enough fragments have been recovered to obtain the complete data key. By default, the threshold is set to the number of key groups. For example, if you have three key groups configured in your SOPS file and you don't override the default threshold, then one master key from each of the three groups will be required to decrypt the file. Management of key groups is done with the ``sops groups`` command. For example, you can add a new key group with 3 PGP keys and 3 KMS keys to the file ``my_file.yaml``: .. code:: bash $ sops groups add --file my_file.yaml --pgp fingerprint1 --pgp fingerprint2 --pgp fingerprint3 --kms arn1 --kms arn2 --kms arn3 Or you can delete the 1st group (group number 0, as groups are zero-indexed) from ``my_file.yaml``: .. code:: bash $ sops groups delete --file my_file.yaml 0 Key groups can also be specified in the ``.sops.yaml`` config file, like so: .. code:: yaml creation_rules: - path_regex: .*keygroups.* key_groups: # First key group - pgp: - fingerprint1 - fingerprint2 kms: - arn: arn1 role: role1 context: foo: bar - arn: arn2 # Second key group - pgp: - fingerprint3 - fingerprint4 kms: - arn: arn3 - arn: arn4 # Third key group - pgp: - fingerprint5 Given this configuration, we can create a new encrypted file like we normally would, and optionally provide the ``--shamir-secret-sharing-threshold`` command line flag if we want to override the default threshold. ``sops`` will then split the data key into three parts (from the number of key groups) and encrypt each fragment with the master keys found in each group. For example: .. code:: bash $ sops --shamir-secret-sharing-threshold 2 example.json Alternatively, you can configure the Shamir threshold for each creation rule in the ``.sops.yaml`` config with ``shamir_threshold``: .. code:: yaml creation_rules: - path_regex: .*keygroups.* shamir_threshold: 2 key_groups: # First key group - pgp: - fingerprint1 - fingerprint2 kms: - arn: arn1 role: role1 context: foo: bar - arn: arn2 # Second key group - pgp: - fingerprint3 - fingerprint4 kms: - arn: arn3 - arn: arn4 # Third key group - pgp: - fingerprint5 And then run ``sops example.json``. The threshold (``shamir_threshold``) is set to 2, so this configuration will require master keys from two of the three different key groups in order to decrypt the file. You can then decrypt the file the same way as with any other SOPS file: .. code:: bash $ sops -d example.json Key service ~~~~~~~~~~~ There are situations where you might want to run ``sops`` on a machine that doesn't have direct access to encryption keys such as PGP keys. The ``sops`` key service allows you to forward a socket so that ``sops`` can access encryption keys stored on a remote machine. This is similar to GPG Agent, but more portable. SOPS uses a client-server approach to encrypting and decrypting the data key. By default, SOPS runs a local key service in-process. SOPS uses a key service client to send an encrypt or decrypt request to a key service, which then performs the operation. The requests are sent using gRPC and Protocol Buffers. The requests contain an identifier for the key they should perform the operation with, and the plaintext or encrypted data key. The requests do not contain any cryptographic keys, public or private. **WARNING: the key service connection currently does not use any sort of authentication or encryption. Therefore, it is recommended that you make sure the connection is authenticated and encrypted in some other way, for example through an SSH tunnel.** Whenever we try to encrypt or decrypt a data key, SOPS will try to do so first with the local key service (unless it's disabled), and if that fails, it will try all other remote key services until one succeeds. You can start a key service server by running ``sops keyservice``. You can specify the key services the ``sops`` binary uses with ``--keyservice``. This flag can be specified more than once, so you can use multiple key services. The local key service can be disabled with ``enable-local-keyservice=false``. For example, to decrypt a file using both the local key service and the key service exposed on the unix socket located in ``/tmp/sops.sock``, you can run: .. code:: bash $ sops --keyservice unix:///tmp/sops.sock -d file.yaml` And if you only want to use the key service exposed on the unix socket located in ``/tmp/sops.sock`` and not the local key service, you can run: .. code:: bash $ sops --enable-local-keyservice=false --keyservice unix:///tmp/sops.sock -d file.yaml Auditing ~~~~~~~~ Sometimes, users want to be able to tell what files were accessed by whom in an environment they control. For this reason, SOPS can generate audit logs to record activity on encrypted files. When enabled, SOPS will write a log entry into a pre-configured PostgreSQL database when a file is decrypted. The log includes a timestamp, the username SOPS is running as, and the file that was decrypted. In order to enable auditing, you must first create the database and credentials using the schema found in ``audit/schema.sql``. This schema defines the tables that store the audit events and a role named ``sops`` that only has permission to add entries to the audit event tables. The default password for the role ``sops`` is ``sops``. You should change this password. Once you have created the database, you have to tell SOPS how to connect to it. Because we don't want users of SOPS to be able to control auditing, the audit configuration file location is not configurable, and must be at ``/etc/sops/audit.yaml``. This file should have strict permissions such that only the root user can modify it. For example, to enable auditing to a PostgreSQL database named ``sops`` running on localhost, using the user ``sops`` and the password ``sops``, ``/etc/sops/audit.yaml`` should have the following contents: .. code:: yaml backends: postgres: - connection_string: "postgres://sops:sops@localhost/sops?sslmode=verify-full" You can find more information on the ``connection_string`` format in the `PostgreSQL docs <https://www.postgresql.org/docs/current/static/libpq-connect.html#libpq-connstring>`_. Under the ``postgres`` map entry in the above YAML is a list, so one can provide more than one backend, and SOPS will log to all of them: .. code:: yaml backends: postgres: - connection_string: "postgres://sops:sops@localhost/sops?sslmode=verify-full" - connection_string: "postgres://sops:sops@remotehost/sops?sslmode=verify-full" Saving Output to a File ~~~~~~~~~~~~~~~~~~~~~~~ By default ``sops`` just dumps all the output to the standard output. We can use the ``--output`` flag followed by a filename to save the output to the file specified. Beware using both ``--in-place`` and ``--output`` flags will result in an error. Passing Secrets to Other Processes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In addition to writing secrets to standard output and to files on disk, ``sops`` has two commands for passing decrypted secrets to a new process: ``exec-env`` and ``exec-file``. These commands will place all output into the environment of a child process and into a temporary file, respectively. For example, if a program looks for credentials in its environment, ``exec-env`` can be used to ensure that the decrypted contents are available only to this process and never written to disk. .. code:: bash # print secrets to stdout to confirm values $ sops -d out.json { "database_password": "jf48t9wfw094gf4nhdf023r", "AWS_ACCESS_KEY_ID": "AKIAIOSFODNN7EXAMPLE", "AWS_SECRET_KEY": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" } # decrypt out.json and run a command # the command prints the environment variable and runs a script that uses it $ sops exec-env out.json 'echo secret: $database_password; ./database-import' secret: jf48t9wfw094gf4nhdf023r # launch a shell with the secrets available in its environment $ sops exec-env out.json 'sh' sh-3.2# echo $database_password jf48t9wfw094gf4nhdf023r # the secret is not accessible anywhere else sh-3.2$ exit $ echo your password: $database_password your password: If the command you want to run only operates on files, you can use ``exec-file`` instead. By default ``sops`` will use a FIFO to pass the contents of the decrypted file to the new program. Using a FIFO, secrets are only passed in memory which has two benefits: the plaintext secrets never touch the disk, and the child process can only read the secrets once. In contexts where this won't work, eg platforms like Windows where FIFOs unavailable or secret files that need to be available to the child process longer term, the ``--no-fifo`` flag can be used to instruct ``sops`` to use a traditional temporary file that will get cleaned up once the process is finished executing. ``exec-file`` behaves similar to ``find(1)`` in that ``{}`` is used as a placeholder in the command which will be substituted with the temporary file path (whether a FIFO or an actual file). .. code:: bash # operating on the same file as before, but as a file this time $ sops exec-file out.json 'echo your temporary file: {}; cat {}' your temporary file: /tmp/.sops894650499/tmp-file { "database_password": "jf48t9wfw094gf4nhdf023r", "AWS_ACCESS_KEY_ID": "AKIAIOSFODNN7EXAMPLE", "AWS_SECRET_KEY": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" } # launch a shell with a variable TMPFILE pointing to the temporary file $ sops exec-file --no-fifo out.json 'TMPFILE={} sh' sh-3.2$ echo $TMPFILE /tmp/.sops506055069/tmp-file291138648 sh-3.2$ cat $TMPFILE { "database_password": "jf48t9wfw094gf4nhdf023r", "AWS_ACCESS_KEY_ID": "AKIAIOSFODNN7EXAMPLE", "AWS_SECRET_KEY": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" } sh-3.2$ ./program --config $TMPFILE sh-3.2$ exit # try to open the temporary file from earlier $ cat /tmp/.sops506055069/tmp-file291138648 cat: /tmp/.sops506055069/tmp-file291138648: No such file or directory Additionally, on unix-like platforms, both ``exec-env`` and ``exec-file`` support dropping privileges before executing the new program via the ``--user <username>`` flag. This is particularly useful in cases where the encrypted file is only readable by root, but the target program does not need root privileges to function. This flag should be used where possible for added security. To overwrite the default file name (``tmp-file``) in ``exec-file`` use the ``--filename <filename>`` parameter. .. code:: bash # the encrypted file can't be read by the current user $ cat out.json cat: out.json: Permission denied # execute sops as root, decrypt secrets, then drop privileges $ sudo sops exec-env --user nobody out.json 'sh' sh-3.2$ echo $database_password jf48t9wfw094gf4nhdf023r # dropped privileges, still can't load the original file sh-3.2$ id uid=4294967294(nobody) gid=4294967294(nobody) groups=4294967294(nobody) sh-3.2$ cat out.json cat: out.json: Permission denied Using the publish command ~~~~~~~~~~~~~~~~~~~~~~~~~ ``sops publish $file`` publishes a file to a pre-configured destination (this lives in the sops config file). Additionally, support re-encryption rules that work just like the creation rules. This command requires a ``.sops.yaml`` configuration file. Below is an example: .. code:: yaml destination_rules: - s3_bucket: "sops-secrets" path_regex: s3/* recreation_rule: pgp: F69E4901EDBAD2D1753F8C67A64535C4163FB307 - gcs_bucket: "sops-secrets" path_regex: gcs/* recreation_rule: pgp: F69E4901EDBAD2D1753F8C67A64535C4163FB307 - vault_path: "sops/" vault_kv_mount_name: "secret/" # default vault_kv_version: 2 # default path_regex: vault/* omit_extensions: true The above configuration will place all files under ``s3/*`` into the S3 bucket ``sops-secrets``, all files under ``gcs/*`` into the GCS bucket ``sops-secrets``, and the contents of all files under ``vault/*`` into Vault's KV store under the path ``secrets/sops/``. For the files that will be published to S3 and GCS, it will decrypt them and re-encrypt them using the ``F69E4901EDBAD2D1753F8C67A64535C4163FB307`` pgp key. You would deploy a file to S3 with a command like: ``sops publish s3/app.yaml`` To publish all files in selected directory recursively, you need to specify ``--recursive`` flag. If you don't want file extension to appear in destination secret path, use ``--omit-extensions`` flag or ``omit_extensions: true`` in the destination rule in ``.sops.yaml``. Publishing to Vault ******************* There are a few settings for Vault that you can place in your destination rules. The first is ``vault_path``, which is required. The others are optional, and they are ``vault_address``, ``vault_kv_mount_name``, ``vault_kv_version``. ``sops`` uses the official Vault API provided by Hashicorp, which makes use of `environment variables <https://www.vaultproject.io/docs/commands/#environment-variables>`_ for configuring the client. ``vault_kv_mount_name`` is used if your Vault KV is mounted somewhere other than ``secret/``. ``vault_kv_version`` supports ``1`` and ``2``, with ``2`` being the default. If destination secret path already exists in Vault and contains same data as the source file, it will be skipped. Below is an example of publishing to Vault (using token auth with a local dev instance of Vault). .. code:: bash $ export VAULT_TOKEN=... $ export VAULT_ADDR='http://127.0.0.1:8200' $ sops -d vault/test.yaml example_string: bar example_number: 42 example_map: key: value $ sops publish vault/test.yaml uploading /home/user/sops_directory/vault/test.yaml to http://127.0.0.1:8200/v1/secret/data/sops/test.yaml ? (y/n): y $ vault kv get secret/sops/test.yaml ====== Metadata ====== Key Value --- ----- created_time 2019-07-11T03:32:17.074792017Z deletion_time n/a destroyed false version 3 ========= Data ========= Key Value --- ----- example_map map[key:value] example_number 42 example_string bar Important information on types ------------------------------ YAML and JSON type extensions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``sops`` uses the file extension to decide which encryption method to use on the file content. ``YAML``, ``JSON``, ``ENV``, and ``INI`` files are treated as trees of data, and key/values are extracted from the files to only encrypt the leaf values. The tree structure is also used to check the integrity of the file. Therefore, if a file is encrypted using a specific format, it need to be decrypted in the same format. The easiest way to achieve this is to conserve the original file extension after encrypting a file. For example: .. code:: bash $ sops -e -i myfile.json $ sops -d myfile.json If you want to change the extension of the file once encrypted, you need to provide sops with the ``--input-type`` flag upon decryption. For example: .. code:: bash $ sops -e myfile.json > myfile.json.enc $ sops -d --input-type json myfile.json.enc When operating on stdin, use the ``--input-type`` and ``--output-type`` flags as follows: .. code:: bash $ cat myfile.json | sops --input-type json --output-type json -d /dev/stdin YAML anchors ~~~~~~~~~~~~ ``sops`` only supports a subset of ``YAML``'s many types. Encrypting YAML files that contain strings, numbers and booleans will work fine, but files that contain anchors will not work, because the anchors redefine the structure of the file at load time. This file will not work in ``sops``: .. code:: yaml bill-to: &id001 street: | 123 Tornado Alley Suite 16 city: East Centerville state: KS ship-to: *id001 ``sops`` uses the path to a value as additional data in the AEAD encryption, and thus dynamic paths generated by anchors break the authentication step. JSON and TEXT file types do not support anchors and thus have no such limitation. YAML Streams ~~~~~~~~~~~~ ``YAML`` supports having more than one "document" in a single file, while formats like ``JSON`` do not. ``sops`` is able to handle both. This means the following multi-document will be encrypted as expected: .. code:: yaml --- data: foo --- data: bar Note that the ``sops`` metadata, i.e. the hash, etc, is computed for the physical file rather than each internal "document". Top-level arrays ~~~~~~~~~~~~~~~~ ``YAML`` and ``JSON`` top-level arrays are not supported, because ``sops`` needs a top-level ``sops`` key to store its metadata. This file will not work in sops: .. code:: yaml --- - some - array - elements But this one will work because the ``sops`` key can be added at the same level as the ``data`` key. .. code:: yaml data: - some - array - elements Similarly, with ``JSON`` arrays, this document will not work: .. code:: json [ "some", "array", "elements" ] But this one will work just fine: .. code:: json { "data": [ "some", "array", "elements" ] } Examples -------- Take a look into the `examples <https://github.com/mozilla/sops/tree/master/examples>`_ folder for detailed use cases of sops in a CI environment. The section below describes specific tips for common use cases. Creating a new file ~~~~~~~~~~~~~~~~~~~ The command below creates a new file with a data key encrypted by KMS and PGP. .. code:: bash $ sops --kms "arn:aws:kms:us-west-2:927034868273:key/fe86dd69-4132-404c-ab86-4269956b4500" --pgp C9CAB0AF1165060DB58D6D6B2653B624D620786D /path/to/new/file.yaml Encrypting an existing file ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Similar to the previous command, we tell sops to use one KMS and one PGP key. The path points to an existing cleartext file, so we give sops flag ``-e`` to encrypt the file, and redirect the output to a destination file. .. code:: bash $ export SOPS_KMS_ARN="arn:aws:kms:us-west-2:927034868273:key/fe86dd69-4132-404c-ab86-4269956b4500" $ export SOPS_PGP_FP="C9CAB0AF1165060DB58D6D6B2653B624D620786D" $ sops -e /path/to/existing/file.yaml > /path/to/new/encrypted/file.yaml Decrypt the file with ``-d``. .. code:: bash $ sops -d /path/to/new/encrypted/file.yaml Encrypt or decrypt a file in place ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Rather than redirecting the output of ``-e`` or ``-d``, sops can replace the original file after encrypting or decrypting it. .. code:: bash # file.yaml is in cleartext $ sops -e -i /path/to/existing/file.yaml # file.yaml is now encrypted $ sops -d -i /path/to/existing/file.yaml # file.yaml is back in cleartext Encrypting binary files ~~~~~~~~~~~~~~~~~~~~~~~ ``sops`` primary use case is encrypting YAML and JSON configuration files, but it also has the ability to manage binary files. When encrypting a binary, sops will read the data as bytes, encrypt it, store the encrypted base64 under ``tree['data']`` and write the result as JSON. Note that the base64 encoding of encrypted data can actually make the encrypted file larger than the cleartext one. In-place encryption/decryption also works on binary files. .. code:: $ dd if=/dev/urandom of=/tmp/somerandom bs=1024 count=512 512+0 records in 512+0 records out 524288 bytes (524 kB) copied, 0.0466158 s, 11.2 MB/s $ sha512sum /tmp/somerandom 9589bb20280e9d381f7a192000498c994e921b3cdb11d2ef5a986578dc2239a340b25ef30691bac72bdb14028270828dad7e8bd31e274af9828c40d216e60cbe /tmp/somerandom $ sops -e -i /tmp/somerandom please wait while a data encryption key is being generated and stored securely $ sops -d -i /tmp/somerandom $ sha512sum /tmp/somerandom 9589bb20280e9d381f7a192000498c994e921b3cdb11d2ef5a986578dc2239a340b25ef30691bac72bdb14028270828dad7e8bd31e274af9828c40d216e60cbe /tmp/somerandom Extract a sub-part of a document tree ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``sops`` can extract a specific part of a YAML or JSON document, by provided the path in the ``--extract`` command line flag. This is useful to extract specific values, like keys, without needing an extra parser. .. code:: bash $ sops -d --extract '["app2"]["key"]' ~/git/svc/sops/example.yaml -----BEGIN RSA PRIVATE KEY----- MIIBPAIBAAJBAPTMNIyHuZtpLYc7VsHQtwOkWYobkUblmHWRmbXzlAX6K8tMf3Wf ImcbNkqAKnELzFAPSBeEMhrBN0PyOC9lYlMCAwEAAQJBALXD4sjuBn1E7Y9aGiMz bJEBuZJ4wbhYxomVoQKfaCu+kH80uLFZKoSz85/ySauWE8LgZcMLIBoiXNhDKfQL vHECIQD6tCG9NMFWor69kgbX8vK5Y+QL+kRq+9HK6yZ9a+hsLQIhAPn4Ie6HGTjw fHSTXWZpGSan7NwTkIu4U5q2SlLjcZh/AiEA78NYRRBwGwAYNUqzutGBqyXKUl4u Erb0xAEyVV7e8J0CIQC8VBY8f8yg+Y7Kxbw4zDYGyb3KkXL10YorpeuZR4LuQQIg bKGPkMM4w5blyE1tqGN0T7sJwEx+EUOgacRNqM2ljVA= -----END RSA PRIVATE KEY----- The tree path syntax uses regular python dictionary syntax, without the variable name. Extract keys by naming them, and array elements by numbering them. .. code:: bash $ sops -d --extract '["an_array"][1]' ~/git/svc/sops/example.yaml secretuser2 Set a sub-part in a document tree ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``sops`` can set a specific part of a YAML or JSON document, by providing the path and value in the ``--set`` command line flag. This is useful to set specific values, like keys, without needing an editor. .. code:: bash $ sops --set '["app2"]["key"] "app2keystringvalue"' ~/git/svc/sops/example.yaml The tree path syntax uses regular python dictionary syntax, without the variable name. Set to keys by naming them, and array elements by numbering them. .. code:: bash $ sops --set '["an_array"][1] "secretuser2"' ~/git/svc/sops/example.yaml The value must be formatted as json. .. code:: bash $ sops --set '["an_array"][1] {"uid1":null,"uid2":1000,"uid3":["bob"]}' ~/git/svc/sops/example.yaml Showing diffs in cleartext in git ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You most likely want to store encrypted files in a version controlled repository. Sops can be used with git to decrypt files when showing diffs between versions. This is very handy for reviewing changes or visualizing history. To configure sops to decrypt files during diff, create a ``.gitattributes`` file at the root of your repository that contains a filter and a command. .. code:: *.yaml diff=sopsdiffer Here we only care about YAML files. ``sopsdiffer`` is an arbitrary name that we map to a sops command in the git configuration file of the repository. .. code:: bash $ git config diff.sopsdiffer.textconv "sops -d" $ grep -A 1 sopsdiffer .git/config [diff "sopsdiffer"] textconv = "sops -d" With this in place, calls to ``git diff`` will decrypt both previous and current versions of the target file prior to displaying the diff. And it even works with git client interfaces, because they call git diff under the hood! Encrypting only parts of a file ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Note: this only works on YAML and JSON files, not on BINARY files. By default, ``sops`` encrypts all the values of a YAML or JSON file and leaves the keys in cleartext. In some instances, you may want to exclude some values from being encrypted. This can be accomplished by adding the suffix **_unencrypted** to any key of a file. When set, all values underneath the key that set the **_unencrypted** suffix will be left in cleartext. Note that, while in cleartext, unencrypted content is still added to the checksum of the file, and thus cannot be modified outside of sops without breaking the file integrity check. The unencrypted suffix can be set to a different value using the ``--unencrypted-suffix`` option. Conversely, you can opt in to only encrypt some values in a YAML or JSON file, by adding a chosen suffix to those keys and passing it to the ``--encrypted-suffix`` option. A third method is to use the ``--encrypted-regex`` which will only encrypt values under keys that match the supplied regular expression. For example, this command: .. code:: bash $ sops --encrypt --encrypted-regex '^(data|stringData)$' k8s-secrets.yaml will encrypt the values under the ``data`` and ``stringData`` keys in a YAML file containing kubernetes secrets. It will not encrypt other values that help you to navigate the file, like ``metadata`` which contains the secrets' names. Conversely, you can opt in to only left certain keys without encrypting by using the ``--unencrypted-regex`` option, which will leave the values unencrypted of those keys that match the supplied regular expression. For example, this command: .. code:: bash $ sops --encrypt --unencrypted-regex '^(description|metadata)$' k8s-secrets.yaml will not encrypt the values under the ``description`` and ``metadata`` keys in a YAML file containing kubernetes secrets, while encrypting everything else. You can also specify these options in the ``.sops.yaml`` config file. Note: these four options ``--unencrypted-suffix``, ``--encrypted-suffix``, ``--encrypted-regex`` and ``--unencrypted-regex`` are mutually exclusive and cannot all be used in the same file. Encryption Protocol ------------------- When sops creates a file, it generates a random 256 bit data key and asks each KMS and PGP master key to encrypt the data key. The encrypted version of the data key is stored in the ``sops`` metadata under ``sops.kms`` and ``sops.pgp``. For KMS: .. code:: yaml sops: kms: - enc: CiC6yCOtzsnFhkfdIslYZ0bAf//gYLYCmIu87B3sy/5yYxKnAQEBAQB4usgjrc7JxYZH3SLJWGdGwH//4GC2ApiLvOwd7Mv+cmMAAAB+MHwGCSqGSIb3DQEHBqBvMG0CAQAwaAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAyGdRODuYMHbA8Ozj8CARCAO7opMolPJUmBXd39Zlp0L2H9fzMKidHm1vvaF6nNFq0ClRY7FlIZmTm4JfnOebPseffiXFn9tG8cq7oi enc_ts: 1439568549.245995 arn: arn:aws:kms:us-east-1:656532927350:key/920aff2e-c5f1-4040-943a-047fa387b27e For PGP: .. code:: yaml sops: pgp: - fp: 85D77543B3D624B63CEA9E6DBC17301B491B3F21 created_at: 1441570391.930042 enc: | -----BEGIN PGP MESSAGE----- Version: GnuPG v1 hQIMA0t4uZHfl9qgAQ//UvGAwGePyHuf2/zayWcloGaDs0MzI+zw6CmXvMRNPUsA pAgRKczJmDu4+XzN+cxX5Iq9xEWIbny9B5rOjwTXT3qcUYZ4Gkzbq4MWkjuPp/Iv qO4MJaYzoH5YxC4YORQ2LvzhA2YGsCzYnljmatGEUNg01yJ6r5mwFwDxl4Nc80Cn RwnHuGExK8j1jYJZu/juK1qRbuBOAuruIPPWVdFB845PA7waacG1IdUW3ZtBkOy3 O0BIfG2ekRg0Nik6sTOhDUA+l2bewCcECI8FYCEjwHm9Sg5cxmP2V5m1mby+uKAm kewaoOyjbmV1Mh3iI1b/AQMr+/6ZE9MT2KnsoWosYamFyjxV5r1ZZM7cWKnOT+tu KOvGhTV1TeOfVpajNTNwtV/Oyh3mMLQ0F0HgCTqomQVqw5+sj7OWAASuD3CU/dyo pcmY5Qe0TNL1JsMNEH8LJDqSh+E0hsUxdY1ouVsg3ysf6mdM8ciWb3WRGxih1Vmf unfLy8Ly3V7ZIC8EHV8aLJqh32jIZV4i2zXIoO4ZBKrudKcECY1C2+zb/TziVAL8 qyPe47q8gi1rIyEv5uirLZjgpP+JkDUgoMnzlX334FZ9pWtQMYW4Y67urAI4xUq6 /q1zBAeHoeeeQK+YKDB7Ak/Y22YsiqQbNp2n4CKSKAE4erZLWVtDvSp+49SWmS/S XgGi+13MaXIp0ecPKyNTBjF+NOw/I3muyKr8EbDHrd2XgIT06QXqjYLsCb1TZ0zm xgXsOTY3b+ONQ2zjhcovanDp7/k77B+gFitLYKg4BLZsl7gJB12T8MQnpfSmRT4= =oJgS -----END PGP MESSAGE----- ``sops`` then opens a text editor on the newly created file. The user adds data to the file and saves it when done. Upon save, sops browses the entire file as a key/value tree. Every time sops encounters a leaf value (a value that does not have children), it encrypts the value with AES256_GCM using the data key and a 256 bit random initialization vector. Each file uses a single data key to encrypt all values of a document, but each value receives a unique initialization vector and has unique authentication data. Additional data is used to guarantee the integrity of the encrypted data and of the tree structure: when encrypting the tree, key names are concatenated into a byte string that is used as AEAD additional data (aad) when encrypting values. We expect that keys do not carry sensitive information, and keeping them in cleartext allows for better diff and overall readability. Any valid KMS or PGP master key can later decrypt the data key and access the data. Multiple master keys allow for sharing encrypted files without sharing master keys, and provide a disaster recovery solution. The recommended way to use sops is to have two KMS master keys in different regions and one PGP public key with the private key stored offline. If, by any chance, both KMS master keys are lost, you can always recover the encrypted data using the PGP private key. Message Authentication Code ~~~~~~~~~~~~~~~~~~~~~~~~~~~ In addition to authenticating branches of the tree using keys as additional data, sops computes a MAC on all the values to ensure that no value has been added or removed fraudulently. The MAC is stored encrypted with AES_GCM and the data key under tree->`sops`->`mac`. Motivation ---------- Automating the distribution of secrets and credentials to components of an infrastructure is a hard problem. We know how to encrypt secrets and share them between humans, but extending that trust to systems is difficult. Particularly when these systems follow devops principles and are created and destroyed without human intervention. The issue boils down to establishing the initial trust of a system that just joined the infrastructure, and providing it access to the secrets it needs to configure itself. The initial trust ~~~~~~~~~~~~~~~~~ In many infrastructures, even highly dynamic ones, the initial trust is established by a human. An example is seen in Puppet by the way certificates are issued: when a new system attempts to join a Puppetmaster, an administrator must, by default, manually approve the issuance of the certificate the system needs. This is cumbersome, and many puppetmasters are configured to auto-sign new certificates to work around that issue. This is obviously not recommended and far from ideal. AWS provides a more flexible approach to trusting new systems. It uses a powerful mechanism of roles and identities. In AWS, it is possible to verify that a new system has been granted a specific role at creation, and it is possible to map that role to specific resources. Instead of trusting new systems directly, the administrator trusts the AWS permission model and its automation infrastructure. As long as AWS keys are safe, and the AWS API is secure, we can assume that trust is maintained and systems are who they say they are. KMS, Trust and secrets distribution ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Using the AWS trust model, we can create fine grained access controls to Amazon's Key Management Service (KMS). KMS is a service that encrypts and decrypts data with AES_GCM, using keys that are never visible to users of the service. Each KMS master key has a set of role-based access controls, and individual roles are permitted to encrypt or decrypt using the master key. KMS helps solve the problem of distributing keys, by shifting it into an access control problem that can be solved using AWS's trust model. Operational requirements ~~~~~~~~~~~~~~~~~~~~~~~~ When Mozilla's Services Operations team started revisiting the issue of distributing secrets to EC2 instances, we set a goal to store these secrets encrypted until the very last moment, when they need to be decrypted on target systems. Not unlike many other organizations that operate sufficiently complex automation, we found this to be a hard problem with a number of prerequisites: 1. Secrets must be stored in YAML files for easy integration into hiera 2. Secrets must be stored in GIT, and when a new CloudFormation stack is built, the current HEAD is pinned to the stack. (This allows secrets to be changed in GIT without impacting the current stack that may autoscale). 3. Entries must be encrypted separately. Encrypting entire files as blobs makes git conflict resolution almost impossible. Encrypting each entry separately is much easier to manage. 4. Secrets must always be encrypted on disk (admin laptop, upstream git repo, jenkins and S3) and only be decrypted on the target systems SOPS can be used to encrypt YAML, JSON and BINARY files. In BINARY mode, the content of the file is treated as a blob, the same way PGP would encrypt an entire file. In YAML and JSON modes, however, the content of the file is manipulated as a tree where keys are stored in cleartext, and values are encrypted. hiera-eyaml does something similar, and over the years we learned to appreciate its benefits, namely: * diffs are meaningful. If a single value of a file is modified, only that value will show up in the diff. The diff is still limited to only showing encrypted data, but that information is already more granular that indicating that an entire file has changed. * conflicts are easier to resolve. If multiple users are working on the same encrypted files, as long as they don't modify the same values, changes are easy to merge. This is an improvement over the PGP encryption approach where unsolvable conflicts often happen when multiple users work on the same file. OpenPGP integration ~~~~~~~~~~~~~~~~~~~ OpenPGP gets a lot of bad press for being an outdated crypto protocol, and while true, what really made us look for alternatives is the difficulty of managing and distributing keys to systems. With KMS, we manage permissions to an API, not keys, and that's a lot easier to do. But PGP is not dead yet, and we still rely on it heavily as a backup solution: all our files are encrypted with KMS and with one PGP public key, with its private key stored securely for emergency decryption in the event that we lose all our KMS master keys. SOPS can be used without KMS entirely, the same way you would use an encrypted PGP file: by referencing the pubkeys of each individual who has access to the file. It can easily be done by providing sops with a comma-separated list of public keys when creating a new file: .. code:: bash $ sops --pgp "E60892BB9BD89A69F759A1A0A3D652173B763E8F,84050F1D61AF7C230A12217687DF65059EF093D3,85D77543B3D624B63CEA9E6DBC17301B491B3F21" mynewfile.yaml Threat Model ------------ The security of the data stored using sops is as strong as the weakest cryptographic mechanism. Values are encrypted using AES256_GCM which is the strongest symmetric encryption algorithm known today. Data keys are encrypted in either KMS, which also uses AES256_GCM, or PGP which uses either RSA or ECDSA keys. Going from the most likely to the least likely, the threats are as follows: Compromised AWS credentials grant access to KMS master key ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ An attacker with access to an AWS console can grant itself access to one of the KMS master keys used to encrypt a sops data key. This threat should be mitigated by protecting AWS accesses with strong controls, such as multi-factor authentication, and also by performing regular audits of permissions granted to AWS users. Compromised PGP key ~~~~~~~~~~~~~~~~~~~ PGP keys are routinely mishandled, either because owners copy them from machine to machine, or because the key is left forgotten on an unused machine an attacker gains access to. When using PGP encryption, sops users should take special care of PGP private keys, and store them on smart cards or offline as often as possible. Factorized RSA key ~~~~~~~~~~~~~~~~~~ sops doesn't apply any restriction on the size or type of PGP keys. A weak PGP keys, for example 512 bits RSA, could be factorized by an attacker to gain access to the private key and decrypt the data key. Users of sops should rely on strong keys, such as 2048+ bits RSA keys, or 256+ bits ECDSA keys. Weak AES cryptography ~~~~~~~~~~~~~~~~~~~~~ A vulnerability in AES256_GCM could potentially leak the data key or the KMS master key used by a sops encrypted file. While no such vulnerability exists today, we recommend that users keep their encrypted files reasonably private. Backward compatibility ---------------------- ``sops`` will remain backward compatible on the major version, meaning that all improvements brought to the 1.X and 2.X branches (current) will maintain the file format introduced in **1.0**. Security -------- Please report security issues to security at mozilla dot org, or by using one of the contact method available here: `https://www.mozilla.org/en-US/security/#For_Developers <https://www.mozilla.org/en-US/security/#For_Developers>`_ License ------- Mozilla Public License Version 2.0 Authors ------- The core team is composed of: * AJ Banhken @ajvb The original authors were: * Adrian Utrilla @autrilla * Julien Vehent @jvehent And a whole bunch of `contributors <https://github.com/mozilla/sops/graphs/contributors>`_ Credits ------- `sops` was inspired by `hiera-eyaml <https://github.com/TomPoulton/hiera-eyaml>`_, `credstash <https://github.com/LuminalOSS/credstash>`_ , `sneaker <https://github.com/codahale/sneaker>`_, `password store <http://www.passwordstore.org/>`_ and too many years managing PGP encrypted files by hand... 07070100000011000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000000F00000000sops-3.7.3/aes07070100000012000081A400000000000000000000000162794F9300001562000000000000000000000000000000000000001900000000sops-3.7.3/aes/cipher.go/* Package aes defines a Cipher that uses 256-bit AES-GCM authenticated encryption to encrypt values the SOPS tree. */ package aes //import "go.mozilla.org/sops/v3/aes" import ( cryptoaes "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/base64" "fmt" "regexp" "strconv" "strings" "github.com/sirupsen/logrus" "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/logging" ) var log *logrus.Logger func init() { log = logging.NewLogger("AES") } type encryptedValue struct { data []byte iv []byte tag []byte datatype string } const nonceSize int = 32 type stashKey struct { additionalData string plaintext interface{} } // Cipher encrypts and decrypts data keys with AES GCM 256 type Cipher struct { // stash is a map that stores IVs for reuse, so that the ciphertext doesn't change when decrypting and reencrypting // the same values. stash map[stashKey][]byte } // NewCipher is the constructor for a new Cipher object func NewCipher() Cipher { return Cipher{ stash: make(map[stashKey][]byte), } } var encre = regexp.MustCompile(`^ENC\[AES256_GCM,data:(.+),iv:(.+),tag:(.+),type:(.+)\]`) func parse(value string) (*encryptedValue, error) { matches := encre.FindStringSubmatch(value) if matches == nil { return nil, fmt.Errorf("Input string %s does not match sops' data format", value) } data, err := base64.StdEncoding.DecodeString(matches[1]) if err != nil { return nil, fmt.Errorf("Error base64-decoding data: %s", err) } iv, err := base64.StdEncoding.DecodeString(matches[2]) if err != nil { return nil, fmt.Errorf("Error base64-decoding iv: %s", err) } tag, err := base64.StdEncoding.DecodeString(matches[3]) if err != nil { return nil, fmt.Errorf("Error base64-decoding tag: %s", err) } datatype := string(matches[4]) return &encryptedValue{data, iv, tag, datatype}, nil } // Decrypt takes a sops-format value string and a key and returns the decrypted value and a stash value func (c Cipher) Decrypt(ciphertext string, key []byte, additionalData string) (plaintext interface{}, err error) { if isEmpty(ciphertext) { return "", nil } encryptedValue, err := parse(ciphertext) if err != nil { return nil, err } aescipher, err := cryptoaes.NewCipher(key) if err != nil { return nil, err } gcm, err := cipher.NewGCMWithNonceSize(aescipher, len(encryptedValue.iv)) if err != nil { return nil, err } data := append(encryptedValue.data, encryptedValue.tag...) decryptedBytes, err := gcm.Open(nil, encryptedValue.iv, data, []byte(additionalData)) if err != nil { return nil, fmt.Errorf("Could not decrypt with AES_GCM: %s", err) } decryptedValue := string(decryptedBytes) switch encryptedValue.datatype { case "str": plaintext = decryptedValue case "int": plaintext, err = strconv.Atoi(decryptedValue) case "float": plaintext, err = strconv.ParseFloat(decryptedValue, 64) case "bytes": plaintext = decryptedBytes case "bool": plaintext, err = strconv.ParseBool(decryptedValue) case "comment": plaintext = sops.Comment{Value: decryptedValue} default: return nil, fmt.Errorf("Unknown datatype: %s", encryptedValue.datatype) } c.stash[stashKey{plaintext: plaintext, additionalData: additionalData}] = encryptedValue.iv return plaintext, err } func isEmpty(value interface{}) bool { switch value := value.(type) { case string: return value == "" case []byte: return len(value) == 0 case sops.Comment: return isEmpty(value.Value) default: return false } } // Encrypt takes one of (string, int, float, bool) and encrypts it with the provided key and additional auth data, returning a sops-format encrypted string. func (c Cipher) Encrypt(plaintext interface{}, key []byte, additionalData string) (ciphertext string, err error) { if isEmpty(plaintext) { return "", nil } aescipher, err := cryptoaes.NewCipher(key) if err != nil { return "", fmt.Errorf("Could not initialize AES GCM encryption cipher: %s", err) } var iv []byte if stash, ok := c.stash[stashKey{plaintext: plaintext, additionalData: additionalData}]; !ok { iv = make([]byte, nonceSize) _, err = rand.Read(iv) if err != nil { return "", fmt.Errorf("Could not generate random bytes for IV: %s", err) } } else { iv = stash } gcm, err := cipher.NewGCMWithNonceSize(aescipher, nonceSize) if err != nil { return "", fmt.Errorf("Could not create GCM: %s", err) } var plainBytes []byte var encryptedType string switch value := plaintext.(type) { case string: encryptedType = "str" plainBytes = []byte(value) case int: encryptedType = "int" plainBytes = []byte(strconv.Itoa(value)) case float64: encryptedType = "float" // The Python version encodes floats without padding 0s after the decimal point. plainBytes = []byte(strconv.FormatFloat(value, 'f', -1, 64)) case bool: encryptedType = "bool" // The Python version encodes booleans with Titlecase plainBytes = []byte(strings.Title(strconv.FormatBool(value))) case sops.Comment: encryptedType = "comment" plainBytes = []byte(value.Value) default: return "", fmt.Errorf("Value to encrypt has unsupported type %T", value) } out := gcm.Seal(nil, iv, plainBytes, []byte(additionalData)) return fmt.Sprintf("ENC[AES256_GCM,data:%s,iv:%s,tag:%s,type:%s]", base64.StdEncoding.EncodeToString(out[:len(out)-cryptoaes.BlockSize]), base64.StdEncoding.EncodeToString(iv), base64.StdEncoding.EncodeToString(out[len(out)-cryptoaes.BlockSize:]), encryptedType), nil } 07070100000013000081A400000000000000000000000162794F9300000AED000000000000000000000000000000000000001E00000000sops-3.7.3/aes/cipher_test.gopackage aes import ( "crypto/rand" "strings" "testing" "testing/quick" "github.com/stretchr/testify/assert" "go.mozilla.org/sops/v3" ) func TestDecrypt(t *testing.T) { expected := "foo" key := []byte(strings.Repeat("f", 32)) message := `ENC[AES256_GCM,data:oYyi,iv:MyIDYbT718JRr11QtBkcj3Dwm4k1aCGZBVeZf0EyV8o=,tag:t5z2Z023Up0kxwCgw1gNxg==,type:str]` decryption, err := NewCipher().Decrypt(message, key, "bar:") if err != nil { t.Errorf("%s", err) } if decryption != expected { t.Errorf("Decrypt(\"%s\", \"%s\") == \"%s\", expected %s", message, key, decryption, expected) } } func TestDecryptInvalidAad(t *testing.T) { message := `ENC[AES256_GCM,data:oYyi,iv:MyIDYbT718JRr11QtBkcj3Dwm4k1aCGZBVeZf0EyV8o=,tag:t5z2Z023Up0kxwCgw1gNxg==,type:str]` _, err := NewCipher().Decrypt(message, []byte(strings.Repeat("f", 32)), "") if err == nil { t.Errorf("Decrypting with an invalid AAC should fail") } } func TestRoundtripString(t *testing.T) { f := func(x, aad string) bool { key := make([]byte, 32) rand.Read(key) s, err := NewCipher().Encrypt(x, key, aad) if err != nil { log.Println(err) return false } d, err := NewCipher().Decrypt(s, key, aad) if err != nil { return false } return x == d } if err := quick.Check(f, nil); err != nil { t.Error(err) } } func TestRoundtripFloat(t *testing.T) { key := []byte(strings.Repeat("f", 32)) f := func(x float64) bool { s, err := NewCipher().Encrypt(x, key, "") if err != nil { log.Println(err) return false } d, err := NewCipher().Decrypt(s, key, "") if err != nil { return false } return x == d } if err := quick.Check(f, nil); err != nil { t.Error(err) } } func TestRoundtripInt(t *testing.T) { key := []byte(strings.Repeat("f", 32)) f := func(x int) bool { s, err := NewCipher().Encrypt(x, key, "") if err != nil { log.Println(err) return false } d, err := NewCipher().Decrypt(s, key, "") if err != nil { return false } return x == d } if err := quick.Check(f, nil); err != nil { t.Error(err) } } func TestRoundtripBool(t *testing.T) { key := []byte(strings.Repeat("f", 32)) f := func(x bool) bool { s, err := NewCipher().Encrypt(x, key, "") if err != nil { log.Println(err) return false } d, err := NewCipher().Decrypt(s, key, "") if err != nil { return false } return x == d } if err := quick.Check(f, nil); err != nil { t.Error(err) } } func TestEncryptEmptyComment(t *testing.T) { key := []byte(strings.Repeat("f", 32)) s, err := NewCipher().Encrypt(sops.Comment{}, key, "") assert.Nil(t, err) assert.Equal(t, "", s) } func TestDecryptEmptyValue(t *testing.T) { key := []byte(strings.Repeat("f", 32)) s, err := NewCipher().Decrypt("", key, "") assert.Nil(t, err) assert.Equal(t, "", s) } 07070100000014000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000000F00000000sops-3.7.3/age07070100000015000081A400000000000000000000000162794F9300000179000000000000000000000000000000000000001800000000sops-3.7.3/age/keys.txt# created: 2020-07-18T03:16:47-07:00 # public key: age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw AGE-SECRET-KEY-1NJT5YCS2LWU4V4QAJQ6R4JNU7LXPDX602DZ9NUFANVU5GDTGUWCQ5T59M6 # created: 2021-12-12T01:39:30+01:00 # public key: age1tmaae3ld5vpevmsh5yacsauzx8jetg300mpvc4ugp5zr5l6ssq9sla97ep AGE-SECRET-KEY-1T0Z66WSXS6RMNCPSL7P2E8N4Q7SUD8VMG9ND27S08JL7Y2XAU9EQECHDS707070100000016000081A400000000000000000000000162794F93000017E3000000000000000000000000000000000000001C00000000sops-3.7.3/age/keysource.gopackage age import ( "bytes" "fmt" "io" "os" "path/filepath" "strings" "filippo.io/age" "filippo.io/age/armor" "github.com/sirupsen/logrus" "go.mozilla.org/sops/v3/logging" ) var log *logrus.Logger func init() { log = logging.NewLogger("AGE") } const SopsAgeKeyEnv = "SOPS_AGE_KEY" const SopsAgeKeyFileEnv = "SOPS_AGE_KEY_FILE" // MasterKey is an age key used to encrypt and decrypt sops' data key. type MasterKey struct { Identity string // a Bech32-encoded private key Recipient string // a Bech32-encoded public key EncryptedKey string // a sops data key encrypted with age parsedRecipient *age.X25519Recipient // a parsed age public key } // Encrypt takes a sops data key, encrypts it with age and stores the result in the EncryptedKey field. func (key *MasterKey) Encrypt(datakey []byte) error { buffer := &bytes.Buffer{} if key.parsedRecipient == nil { parsedRecipient, err := parseRecipient(key.Recipient) if err != nil { log.WithField("recipient", key.parsedRecipient).Error("Encryption failed") return err } key.parsedRecipient = parsedRecipient } aw := armor.NewWriter(buffer) w, err := age.Encrypt(aw, key.parsedRecipient) if err != nil { return fmt.Errorf("failed to open file for encrypting sops data key with age: %w", err) } if _, err := w.Write(datakey); err != nil { log.WithField("recipient", key.parsedRecipient).Error("Encryption failed") return fmt.Errorf("failed to encrypt sops data key with age: %w", err) } if err := w.Close(); err != nil { log.WithField("recipient", key.parsedRecipient).Error("Encryption failed") return fmt.Errorf("failed to close file for encrypting sops data key with age: %w", err) } if err := aw.Close(); err != nil { log.WithField("recipient", key.parsedRecipient).Error("Encryption failed") return fmt.Errorf("failed to close armored writer: %w", err) } key.EncryptedKey = buffer.String() log.WithField("recipient", key.parsedRecipient).Info("Encryption succeeded") return nil } // EncryptIfNeeded encrypts the provided sops' data key and encrypts it if it hasn't been encrypted yet. func (key *MasterKey) EncryptIfNeeded(datakey []byte) error { if key.EncryptedKey == "" { return key.Encrypt(datakey) } return nil } // EncryptedDataKey returns the encrypted data key this master key holds. func (key *MasterKey) EncryptedDataKey() []byte { return []byte(key.EncryptedKey) } // SetEncryptedDataKey sets the encrypted data key for this master key. func (key *MasterKey) SetEncryptedDataKey(enc []byte) { key.EncryptedKey = string(enc) } // Decrypt decrypts the EncryptedKey field with the age identity and returns the result. func (key *MasterKey) Decrypt() ([]byte, error) { var ageKeyReader io.Reader var ageKeyReaderName string if ageKeyReader == nil { ageKey, ok := os.LookupEnv(SopsAgeKeyEnv) if ok { ageKeyReader = strings.NewReader(ageKey) ageKeyReaderName = "environment variable" } } if ageKeyReader == nil { ageKeyFilePath, ok := os.LookupEnv(SopsAgeKeyFileEnv) if ok { ageKeyFile, err := os.Open(ageKeyFilePath) if err != nil { return nil, fmt.Errorf("failed to open file: %w", err) } defer ageKeyFile.Close() ageKeyReader = ageKeyFile ageKeyReaderName = ageKeyFilePath } } if ageKeyReader == nil { userConfigDir, err := os.UserConfigDir() if err != nil { return nil, fmt.Errorf("user config directory could not be determined: %w", err) } ageKeyFilePath := filepath.Join(userConfigDir, "sops", "age", "keys.txt") ageKeyFile, err := os.Open(ageKeyFilePath) if err != nil { return nil, fmt.Errorf("failed to open file: %w", err) } defer ageKeyFile.Close() ageKeyReader = ageKeyFile ageKeyReaderName = ageKeyFilePath } identities, err := age.ParseIdentities(ageKeyReader) if err != nil { return nil, err } src := bytes.NewReader([]byte(key.EncryptedKey)) ar := armor.NewReader(src) r, err := age.Decrypt(ar, identities...) if err != nil { return nil, fmt.Errorf("no age identity found in %q that could decrypt the data", ageKeyReaderName) } var b bytes.Buffer if _, err := io.Copy(&b, r); err != nil { return nil, fmt.Errorf("failed to copy decrypted data into bytes.Buffer: %w", err) } return b.Bytes(), nil } // NeedsRotation returns whether the data key needs to be rotated or not. func (key *MasterKey) NeedsRotation() bool { return false } // ToString converts the key to a string representation. func (key *MasterKey) ToString() string { return key.Recipient } // ToMap converts the MasterKey to a map for serialization purposes. func (key *MasterKey) ToMap() map[string]interface{} { return map[string]interface{}{"recipient": key.Recipient, "enc": key.EncryptedKey} } // MasterKeysFromRecipients takes a comma-separated list of Bech32-encoded public keys and returns a // slice of new MasterKeys. func MasterKeysFromRecipients(commaSeparatedRecipients string) ([]*MasterKey, error) { if commaSeparatedRecipients == "" { // otherwise Split returns [""] and MasterKeyFromRecipient is unhappy return make([]*MasterKey, 0), nil } recipients := strings.Split(commaSeparatedRecipients, ",") var keys []*MasterKey for _, recipient := range recipients { key, err := masterKeyFromRecipient(recipient) if err != nil { return nil, err } keys = append(keys, key) } return keys, nil } // masterKeyFromRecipient takes a Bech32-encoded public key and returns a new MasterKey. func masterKeyFromRecipient(recipient string) (*MasterKey, error) { recipient = strings.TrimSpace(recipient) parsedRecipient, err := parseRecipient(recipient) if err != nil { return nil, err } return &MasterKey{ Recipient: recipient, parsedRecipient: parsedRecipient, }, nil } // parseRecipient attempts to parse a string containing an encoded age public key func parseRecipient(recipient string) (*age.X25519Recipient, error) { parsedRecipient, err := age.ParseX25519Recipient(recipient) if err != nil { return nil, fmt.Errorf("failed to parse input as Bech32-encoded age public key: %w", err) } return parsedRecipient, nil } 07070100000017000081A400000000000000000000000162794F93000014B7000000000000000000000000000000000000002100000000sops-3.7.3/age/keysource_test.gopackage age import ( "io/ioutil" "os" "path" "runtime" "testing" "github.com/stretchr/testify/assert" ) func TestMasterKeysFromRecipientsEmpty(t *testing.T) { assert := assert.New(t) commaSeparatedRecipients := "" recipients, err := MasterKeysFromRecipients(commaSeparatedRecipients) assert.NoError(err) assert.Equal(recipients, make([]*MasterKey, 0)) } func TestMasterKeyFromRecipientWithLeadingAndTrailingSpacesSingle(t *testing.T) { assert := assert.New(t) commaSeparatedRecipients := " age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw " keys, err := MasterKeysFromRecipients(commaSeparatedRecipients) assert.NoError(err) assert.Equal(len(keys), 1) assert.Equal(keys[0].Recipient, "age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw") } func TestMasterKeyFromRecipientWithLeadingAndTrailingSpacesMultiple(t *testing.T) { assert := assert.New(t) commaSeparatedRecipients := " age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw , age1tmaae3ld5vpevmsh5yacsauzx8jetg300mpvc4ugp5zr5l6ssq9sla97ep " keys, err := MasterKeysFromRecipients(commaSeparatedRecipients) assert.NoError(err) assert.Equal(len(keys), 2) assert.Equal(keys[0].Recipient, "age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw") assert.Equal(keys[1].Recipient, "age1tmaae3ld5vpevmsh5yacsauzx8jetg300mpvc4ugp5zr5l6ssq9sla97ep") } func TestMasterKeysFromRecipientsWithSingle(t *testing.T) { assert := assert.New(t) commaSeparatedRecipients := "age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw" keys, err := MasterKeysFromRecipients(commaSeparatedRecipients) assert.NoError(err) assert.Equal(len(keys), 1) assert.Equal(keys[0].Recipient, "age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw") } func TestMasterKeysFromRecipientsWithMultiple(t *testing.T) { assert := assert.New(t) commaSeparatedRecipients := "age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw,age1tmaae3ld5vpevmsh5yacsauzx8jetg300mpvc4ugp5zr5l6ssq9sla97ep" keys, err := MasterKeysFromRecipients(commaSeparatedRecipients) assert.NoError(err) assert.Equal(len(keys), 2) assert.Equal(keys[0].Recipient, "age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw") assert.Equal(keys[1].Recipient, "age1tmaae3ld5vpevmsh5yacsauzx8jetg300mpvc4ugp5zr5l6ssq9sla97ep") } func TestAge(t *testing.T) { assert := assert.New(t) commaSeparatedRecipients := "age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw,age1tmaae3ld5vpevmsh5yacsauzx8jetg300mpvc4ugp5zr5l6ssq9sla97ep" keys, err := MasterKeysFromRecipients(commaSeparatedRecipients) assert.NoError(err) assert.Equal(len(keys), 2) assert.Equal(keys[0].Recipient, "age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw") assert.Equal(keys[1].Recipient, "age1tmaae3ld5vpevmsh5yacsauzx8jetg300mpvc4ugp5zr5l6ssq9sla97ep") dataKey := []byte("abcdefghijklmnopqrstuvwxyz123456") for _, key := range keys { err = key.Encrypt(dataKey) assert.NoError(err) _, filename, _, _ := runtime.Caller(0) err = os.Setenv("SOPS_AGE_KEY_FILE", path.Join(path.Dir(filename), "keys.txt")) assert.NoError(err) decryptedKey, err := key.Decrypt() assert.NoError(err) assert.Equal(dataKey, decryptedKey) } } func TestAgeDotEnv(t *testing.T) { assert := assert.New(t) commaSeparatedRecipients := "age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw,age1tmaae3ld5vpevmsh5yacsauzx8jetg300mpvc4ugp5zr5l6ssq9sla97ep" keys, err := MasterKeysFromRecipients(commaSeparatedRecipients) assert.NoError(err) assert.Equal(len(keys), 2) assert.Equal(keys[0].Recipient, "age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw") assert.Equal(keys[1].Recipient, "age1tmaae3ld5vpevmsh5yacsauzx8jetg300mpvc4ugp5zr5l6ssq9sla97ep") dotenv := `IMAGE_PREFIX=repo/service- APPLICATION_KEY=K6pfAWuUVND9Fz5SC7jmA6pfAWuUVND9Fz5SC7jmA KEY_ID=003683d721f2ae683d721f2a1 DOMAIN=files.127.0.0.1.nip.io` dataKey := []byte(dotenv) err = keys[0].Encrypt(dataKey) assert.NoError(err) _, filename, _, _ := runtime.Caller(0) err = os.Setenv(SopsAgeKeyFileEnv, path.Join(path.Dir(filename), "keys.txt")) defer os.Unsetenv(SopsAgeKeyFileEnv) assert.NoError(err) decryptedKey, err := keys[0].Decrypt() assert.NoError(err) assert.Equal(dataKey, decryptedKey) } func TestAgeEnv(t *testing.T) { assert := assert.New(t) commaSeparatedRecipients := "age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw,age1tmaae3ld5vpevmsh5yacsauzx8jetg300mpvc4ugp5zr5l6ssq9sla97ep" keys, err := MasterKeysFromRecipients(commaSeparatedRecipients) assert.NoError(err) assert.Equal(len(keys), 2) assert.Equal(keys[0].Recipient, "age1yt3tfqlfrwdwx0z0ynwplcr6qxcxfaqycuprpmy89nr83ltx74tqdpszlw") assert.Equal(keys[1].Recipient, "age1tmaae3ld5vpevmsh5yacsauzx8jetg300mpvc4ugp5zr5l6ssq9sla97ep") dataKey := []byte("abcdefghijklmnopqrstuvwxyz123456") err = keys[0].Encrypt(dataKey) assert.NoError(err) _, filename, _, _ := runtime.Caller(0) keysBytes, err := ioutil.ReadFile(path.Join(path.Dir(filename), "keys.txt")) assert.NoError(err) err = os.Setenv(SopsAgeKeyEnv, string(keysBytes)) defer os.Unsetenv(SopsAgeKeyEnv) assert.NoError(err) decryptedKey, err := keys[0].Decrypt() assert.NoError(err) assert.Equal(dataKey, decryptedKey) } 07070100000018000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001100000000sops-3.7.3/audit07070100000019000081A400000000000000000000000162794F9300001352000000000000000000000000000000000000001A00000000sops-3.7.3/audit/audit.gopackage audit import ( "database/sql" "flag" "fmt" "io/ioutil" "os/user" "github.com/pkg/errors" // empty import as per https://godoc.org/github.com/lib/pq _ "github.com/lib/pq" "gopkg.in/yaml.v3" "github.com/sirupsen/logrus" "go.mozilla.org/sops/v3/logging" ) var log *logrus.Logger func init() { log = logging.NewLogger("AUDIT") confBytes, err := ioutil.ReadFile(configFile) if err != nil { log.WithField("error", err).Debugf("Error reading config") return } var conf config err = yaml.Unmarshal(confBytes, &conf) if err != nil { log.WithField("error", err).Panicf("Error unmarshalling config") } // If we are running test, then don't create auditors. // This is pretty hacky, but doing it The Right Way would require // restructuring SOPS to use dependency injection instead of just using // globals everywhere. if flag.Lookup("test.v") != nil { return } var auditErrors []error for _, pgConf := range conf.Backends.Postgres { auditDb, err := NewPostgresAuditor(pgConf.ConnStr) if err != nil { auditErrors = append(auditErrors, errors.Wrap(err, fmt.Sprintf("connectStr: %s, err", pgConf.ConnStr))) } auditors = append(auditors, auditDb) } if len(auditErrors) > 0 { log.Errorf("connecting to audit database, defined in %s", configFile) for _, err := range auditErrors { log.Error(err) } log.Fatal("one or more audit backends reported errors, exiting") } } // TODO: Make platform agnostic const configFile = "/etc/sops/audit.yaml" type config struct { Backends struct { Postgres []struct { ConnStr string `yaml:"connection_string"` } `yaml:"postgres"` } `yaml:"backends"` } var auditors []Auditor // SubmitEvent handles an event for all auditors func SubmitEvent(event interface{}) { for _, auditor := range auditors { auditor.Handle(event) } } // Register registers a new Auditor in the global auditor list func Register(auditor Auditor) { auditors = append(auditors, auditor) } // Auditor is notified when noteworthy events happen, // for example when a file is encrypted or decrypted. type Auditor interface { // Handle() takes an audit event and attempts to persists it; // how it is persisted and how errors are handled is up to the // implementation of this interface. Handle(event interface{}) } // DecryptEvent contains fields relevant to a decryption event type DecryptEvent struct { File string } // EncryptEvent contains fields relevant to an encryption event type EncryptEvent struct { File string } // RotateEvent contains fields relevant to a key rotation event type RotateEvent struct { File string } // PostgresAuditor is a Postgres SQL DB implementation of the Auditor interface. // It persists the audit event by writing a row to the 'audit_event' table. // Errors with writing to the database will output a log message and the // process will exit with status set to 1 type PostgresAuditor struct { DB *sql.DB } // NewPostgresAuditor is the constructor for a new PostgresAuditor struct // initialized with the given db connection string func NewPostgresAuditor(connStr string) (*PostgresAuditor, error) { db, err := sql.Open("postgres", connStr) pg := &PostgresAuditor{DB: db} if err != nil { return pg, err } var result int err = pg.DB.QueryRow("SELECT 1").Scan(&result) if err != nil { return pg, fmt.Errorf("Pinging audit database failed: %s", err) } else if result != 1 { return pg, fmt.Errorf("Database malfunction: SELECT 1 should return 1, but returned %d", result) } return pg, nil } // Handle persists the audit event by writing a row to the // 'audit_event' postgres table func (p *PostgresAuditor) Handle(event interface{}) { u, err := user.Current() if err != nil { log.Fatalf("Error getting current user for auditing: %s", err) } switch event := event.(type) { case DecryptEvent: // Save the event to the database log.WithField("file", event.File). Debug("Saving decrypt event to database") _, err = p.DB.Exec("INSERT INTO audit_event (action, username, file) VALUES ($1, $2, $3)", "decrypt", u.Username, event.File) if err != nil { log.Fatalf("Failed to insert audit record: %s", err) } case EncryptEvent: // Save the event to the database log.WithField("file", event.File). Debug("Saving encrypt event to database") _, err = p.DB.Exec("INSERT INTO audit_event (action, username, file) VALUES ($1, $2, $3)", "encrypt", u.Username, event.File) if err != nil { log.Fatalf("Failed to insert audit record: %s", err) } case RotateEvent: // Save the event to the database log.WithField("file", event.File). Debug("Saving rotate event to database") _, err = p.DB.Exec("INSERT INTO audit_event (action, username, file) VALUES ($1, $2, $3)", "rotate", u.Username, event.File) if err != nil { log.Fatalf("Failed to insert audit record: %s", err) } default: log.WithField("type", fmt.Sprintf("%T", event)). Info("Received unknown event") } } 0707010000001A000081A400000000000000000000000162794F930000013F000000000000000000000000000000000000001C00000000sops-3.7.3/audit/schema.sqlCREATE TABLE audit_event ( id SERIAL PRIMARY KEY, timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, action TEXT, username TEXT, file TEXT ); CREATE ROLE sops WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB LOGIN PASSWORD 'sops'; GRANT INSERT ON audit_event TO sops; GRANT USAGE ON audit_event_id_seq TO sops; 0707010000001B000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001000000000sops-3.7.3/azkv0707010000001C000081A400000000000000000000000162794F93000022C7000000000000000000000000000000000000001D00000000sops-3.7.3/azkv/keysource.go/* Package azkv contains an implementation of the go.mozilla.org/sops/v3/keys.MasterKey interface that encrypts and decrypts the data key using Azure Key Vault with the Azure Go SDK. */ package azkv //import "go.mozilla.org/sops/v3/azkv" import ( "context" "encoding/base64" "errors" "fmt" "os" "regexp" "strings" "time" "go.mozilla.org/sops/v3/logging" "github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault" "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/azure" "github.com/Azure/go-autorest/autorest/azure/auth" "github.com/sirupsen/logrus" ) var log *logrus.Logger func init() { log = logging.NewLogger("AZKV") } // MasterKey is a Azure Key Vault key used to encrypt and decrypt sops' data key. type MasterKey struct { VaultURL string Name string Version string EncryptedKey string CreationDate time.Time } func newKeyVaultClient() (keyvault.BaseClient, error) { var err error c := keyvault.New() c.Authorizer, err = newAuthorizer() if err != nil { log.WithError(err).Error("Failed to create Azure authorizer") return c, err } return c, nil } // newAuthorizer returns the correct authorizer for the given settings and/or based on the value // of the AZURE_AUTH_METHOD environment variable, which may be one of: // clientcredentials, clientcertificate, usernamepassword, msi, or cli (default). func newAuthorizer() (autorest.Authorizer, error) { settings := struct { authMethod string tenantID string clientID string clientSecret string certificatePath string certificatePassword string username string password string envName string resource string environment azure.Environment }{ authMethod: os.Getenv("AZURE_AUTH_METHOD"), tenantID: os.Getenv("AZURE_TENANT_ID"), clientID: os.Getenv("AZURE_CLIENT_ID"), clientSecret: os.Getenv("AZURE_CLIENT_SECRET"), certificatePath: os.Getenv("AZURE_CERTIFICATE_PATH"), certificatePassword: os.Getenv("AZURE_CERTIFICATE_PASSWORD"), username: os.Getenv("AZURE_USERNAME"), password: os.Getenv("AZURE_PASSWORD"), envName: os.Getenv("AZURE_ENVIRONMENT"), resource: os.Getenv("AZURE_AD_RESOURCE"), } settings.environment = azure.PublicCloud if settings.envName != "" { val, err := azure.EnvironmentFromName(settings.envName) if err != nil { return nil, err } settings.environment = val } if settings.resource == "" { settings.resource = strings.TrimSuffix(settings.environment.KeyVaultEndpoint, "/") } if os.Getenv("MSI_ENDPOINT") != "" { settings.authMethod = "msi" } // 1. Client credentials if (settings.clientSecret != "") || settings.authMethod == "clientcredentials" { config := auth.NewClientCredentialsConfig(settings.clientID, settings.clientSecret, settings.tenantID) config.AADEndpoint = settings.environment.ActiveDirectoryEndpoint config.Resource = settings.resource return config.Authorizer() } // 2. Client Certificate if (settings.certificatePath != "") || settings.authMethod == "clientcertificate" { config := auth.NewClientCertificateConfig(settings.certificatePath, settings.certificatePassword, settings.clientID, settings.tenantID) config.AADEndpoint = settings.environment.ActiveDirectoryEndpoint config.Resource = settings.resource return config.Authorizer() } // 3. Username Password if (settings.username != "" && settings.password != "") || settings.authMethod == "usernamepassword" { config := auth.NewUsernamePasswordConfig(settings.username, settings.password, settings.clientID, settings.tenantID) config.AADEndpoint = settings.environment.ActiveDirectoryEndpoint config.Resource = settings.resource return config.Authorizer() } // 4. MSI if settings.authMethod == "msi" { config := auth.NewMSIConfig() config.Resource = settings.resource config.ClientID = settings.clientID return config.Authorizer() } // 5. Device Code if settings.authMethod == "devicecode" { // TODO: Removed until we decide how to handle prompt on stdout, etc. //// TODO: This will be required on every execution. Consider caching. //config := auth.NewDeviceFlowConfig(settings.clientID, settings.tenantID) //return config.Authorizer() return nil, errors.New("device code flow not implemented") } // 6. CLI return auth.NewAuthorizerFromCLIWithResource(settings.resource) } // NewMasterKey creates a new MasterKey from an URL, key name and version, setting the creation date to the current date func NewMasterKey(vaultURL string, keyName string, keyVersion string) *MasterKey { return &MasterKey{ VaultURL: vaultURL, Name: keyName, Version: keyVersion, CreationDate: time.Now().UTC(), } } // MasterKeysFromURLs takes a comma separated list of Azure Key Vault URLs and returns a slice of new MasterKeys for them func MasterKeysFromURLs(urls string) ([]*MasterKey, error) { var keys []*MasterKey if urls == "" { return keys, nil } for _, s := range strings.Split(urls, ",") { k, err := NewMasterKeyFromURL(s) if err != nil { return nil, err } keys = append(keys, k) } return keys, nil } // NewMasterKeyFromURL takes an Azure Key Vault key URL and returns a new MasterKey // URL format is {vaultUrl}/keys/{key-name}/{key-version} func NewMasterKeyFromURL(url string) (*MasterKey, error) { k := &MasterKey{} re := regexp.MustCompile("^(https://[^/]+)/keys/([^/]+)/([^/]+)$") parts := re.FindStringSubmatch(url) if parts == nil || len(parts) < 2 { return nil, fmt.Errorf("Could not parse valid key from %q", url) } k.VaultURL = parts[1] k.Name = parts[2] k.Version = parts[3] k.CreationDate = time.Now().UTC() return k, nil } // EncryptedDataKey returns the encrypted data key this master key holds func (key *MasterKey) EncryptedDataKey() []byte { return []byte(key.EncryptedKey) } // SetEncryptedDataKey sets the encrypted data key for this master key func (key *MasterKey) SetEncryptedDataKey(enc []byte) { key.EncryptedKey = string(enc) } // Encrypt takes a sops data key, encrypts it with Key Vault and stores the result in the EncryptedKey field func (key *MasterKey) Encrypt(dataKey []byte) error { c, err := newKeyVaultClient() if err != nil { return err } data := base64.RawURLEncoding.EncodeToString(dataKey) p := keyvault.KeyOperationsParameters{Value: &data, Algorithm: keyvault.RSAOAEP256} res, err := c.Encrypt(context.Background(), key.VaultURL, key.Name, key.Version, p) if err != nil { log.WithError(err).WithFields(logrus.Fields{ "key": key.Name, "version": key.Version, }).Error("Encryption failed") return fmt.Errorf("Failed to encrypt data: %w", err) } key.EncryptedKey = *res.Result log.WithFields(logrus.Fields{ "key": key.Name, "version": key.Version, }).Info("Encryption succeeded") return nil } // EncryptIfNeeded encrypts the provided sops' data key and encrypts it if it hasn't been encrypted yet func (key *MasterKey) EncryptIfNeeded(dataKey []byte) error { if key.EncryptedKey == "" { return key.Encrypt(dataKey) } return nil } // Decrypt decrypts the EncryptedKey field with Azure Key Vault and returns the result. func (key *MasterKey) Decrypt() ([]byte, error) { c, err := newKeyVaultClient() if err != nil { return nil, err } p := keyvault.KeyOperationsParameters{Value: &key.EncryptedKey, Algorithm: keyvault.RSAOAEP256} res, err := c.Decrypt(context.TODO(), key.VaultURL, key.Name, key.Version, p) if err != nil { log.WithError(err).WithFields(logrus.Fields{ "key": key.Name, "version": key.Version, }).Error("Decryption failed") return nil, fmt.Errorf("Error decrypting key: %w", err) } plaintext, err := base64.RawURLEncoding.DecodeString(*res.Result) if err != nil { log.WithError(err).WithFields(logrus.Fields{ "key": key.Name, "version": key.Version, }).Error("Decryption failed") return nil, err } log.WithFields(logrus.Fields{ "key": key.Name, "version": key.Version, }).Info("Decryption succeeded") return plaintext, nil } // NeedsRotation returns whether the data key needs to be rotated or not. func (key *MasterKey) NeedsRotation() bool { return time.Since(key.CreationDate) > (time.Hour * 24 * 30 * 6) } // ToString converts the key to a string representation func (key *MasterKey) ToString() string { return fmt.Sprintf("%s/keys/%s/%s", key.VaultURL, key.Name, key.Version) } // ToMap converts the MasterKey to a map for serialization purposes func (key MasterKey) ToMap() map[string]interface{} { out := make(map[string]interface{}) out["vaultUrl"] = key.VaultURL out["key"] = key.Name out["version"] = key.Version out["created_at"] = key.CreationDate.UTC().Format(time.RFC3339) out["enc"] = key.EncryptedKey return out } 0707010000001D000081A400000000000000000000000162794F9300000C7D000000000000000000000000000000000000002200000000sops-3.7.3/azkv/keysource_test.gopackage azkv import ( "flag" "testing" "time" "github.com/stretchr/testify/assert" ) func TestAzureKeySourceFromUrl(t *testing.T) { cases := []struct { name string input string expectSuccess bool expectedFoundKeys int expectedKeys []MasterKey }{ { name: "Single url", input: "https://test.vault.azure.net/keys/test-key/a2a690a4fcc04166b739da342a912c90", expectSuccess: true, expectedFoundKeys: 1, expectedKeys: []MasterKey{ { VaultURL: "https://test.vault.azure.net", Name: "test-key", Version: "a2a690a4fcc04166b739da342a912c90", }, }, }, { name: "Multiple url", input: "https://test.vault.azure.net/keys/test-key/a2a690a4fcc04166b739da342a912c90,https://test2.vault.azure.net/keys/another-test-key/cf0021e8b743453bae758e7fbf71b60e", expectSuccess: true, expectedFoundKeys: 2, expectedKeys: []MasterKey{ { VaultURL: "https://test.vault.azure.net", Name: "test-key", Version: "a2a690a4fcc04166b739da342a912c90", }, { VaultURL: "https://test2.vault.azure.net", Name: "another-test-key", Version: "cf0021e8b743453bae758e7fbf71b60e", }, }, }, { name: "Single malformed url", input: "https://test.vault.azure.net/no-keys-here/test-key/a2a690a4fcc04166b739da342a912c90", expectSuccess: false, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { keys, err := MasterKeysFromURLs(c.input) if err != nil && c.expectSuccess { t.Fatalf("Unexpected error %v", err) } else if err == nil && !c.expectSuccess { t.Fatal("Expected error, but no error was returned") } if c.expectedFoundKeys != len(keys) { t.Errorf("Unexpected number of keys returned, expected %d, got %d", c.expectedFoundKeys, len(keys)) } for idx := range keys { assert.Equal(t, c.expectedKeys[idx].VaultURL, keys[idx].VaultURL) assert.Equal(t, c.expectedKeys[idx].Name, keys[idx].Name) assert.Equal(t, c.expectedKeys[idx].Version, keys[idx].Version) } }) } } func TestKeyToMap(t *testing.T) { key := MasterKey{ CreationDate: time.Date(2016, time.October, 31, 10, 0, 0, 0, time.UTC), VaultURL: "https://test.vault.azure.net", Name: "test-key", Version: "1", EncryptedKey: "this is encrypted", } assert.Equal(t, map[string]interface{}{ "vaultUrl": key.VaultURL, "key": key.Name, "version": key.Version, "enc": "this is encrypted", "created_at": "2016-10-31T10:00:00Z", }, key.ToMap()) } var azureKeyAcceptanceTestURL = flag.String("azure-key", "", "URL to Azure Key Vault (note that this can incur real costs!)") func TestRoundtrip(t *testing.T) { if *azureKeyAcceptanceTestURL == "" { t.Skip("Azure URL not provided, skipping acceptance test") } input := []byte("test-string") key, err := NewMasterKeyFromURL(*azureKeyAcceptanceTestURL) if err != nil { t.Fatal(err) } err = key.Encrypt(input) if err != nil { t.Fatal(err) } output, err := key.Decrypt() if err != nil { t.Fatal(err) } assert.Equal(t, input, output) } 0707010000001E000041ED00000000000000000000000362794F9300000000000000000000000000000000000000000000000F00000000sops-3.7.3/bin0707010000001F000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001200000000sops-3.7.3/bin/ci07070100000020000081ED00000000000000000000000162794F9300000324000000000000000000000000000000000000002600000000sops-3.7.3/bin/ci/deploy_dockerhub.sh#!/bin/bash # THIS IS MEANT TO BE RUN BY CI set -e set +x # Usage: retry MAX CMD... # Retry CMD up to MAX times. If it fails MAX times, returns failure. # Example: retry 3 docker push "mozilla/sops:$TAG" function retry() { max=$1 shift count=1 until "$@"; do count=$((count + 1)) if [[ $count -gt $max ]]; then return 1 fi echo "$count / $max" done return 0 } if [[ "$DOCKER_DEPLOY" == "true" ]]; then # configure docker creds retry 3 docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" # docker tag and push git branch to dockerhub if [ -n "$1" ]; then retry 3 docker push "mozilla/sops:$1" || (echo "Couldn't push mozilla/sops:$1" && false) echo "Pushed mozilla/sops:$1" fi fi 07070100000021000041ED00000000000000000000000362794F9300000000000000000000000000000000000000000000000F00000000sops-3.7.3/cmd07070100000022000041ED00000000000000000000000662794F9300000000000000000000000000000000000000000000001400000000sops-3.7.3/cmd/sops07070100000023000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001A00000000sops-3.7.3/cmd/sops/codes07070100000024000081A400000000000000000000000162794F9300000548000000000000000000000000000000000000002300000000sops-3.7.3/cmd/sops/codes/codes.go// Package codes the exit statuses returned by the sops binary package codes // Exit statuses returned by the binary const ( ErrorGeneric int = 1 CouldNotReadInputFile int = 2 CouldNotWriteOutputFile int = 3 ErrorDumpingTree int = 4 ErrorReadingConfig int = 5 ErrorInvalidKMSEncryptionContextFormat int = 6 ErrorInvalidSetFormat int = 7 ErrorConflictingParameters int = 8 ErrorEncryptingMac int = 21 ErrorEncryptingTree int = 23 ErrorDecryptingMac int = 24 ErrorDecryptingTree int = 25 CannotChangeKeysFromNonExistentFile int = 49 MacMismatch int = 51 MacNotFound int = 52 ConfigFileNotFound int = 61 KeyboardInterrupt int = 85 InvalidTreePathFormat int = 91 NoFileSpecified int = 100 CouldNotRetrieveKey int = 128 NoEncryptionKeyFound int = 111 FileHasNotBeenModified int = 200 NoEditorFound int = 201 FailedToCompareVersions int = 202 FileAlreadyEncrypted int = 203 ) 07070100000025000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001B00000000sops-3.7.3/cmd/sops/common07070100000026000081A400000000000000000000000162794F930000345C000000000000000000000000000000000000002500000000sops-3.7.3/cmd/sops/common/common.gopackage common import ( "fmt" "io/ioutil" "os" "path/filepath" "time" "github.com/fatih/color" wordwrap "github.com/mitchellh/go-wordwrap" "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/cmd/sops/codes" . "go.mozilla.org/sops/v3/cmd/sops/formats" "go.mozilla.org/sops/v3/keys" "go.mozilla.org/sops/v3/keyservice" "go.mozilla.org/sops/v3/kms" "go.mozilla.org/sops/v3/stores/dotenv" "go.mozilla.org/sops/v3/stores/ini" "go.mozilla.org/sops/v3/stores/json" "go.mozilla.org/sops/v3/stores/yaml" "go.mozilla.org/sops/v3/version" "golang.org/x/crypto/ssh/terminal" "gopkg.in/urfave/cli.v1" ) // ExampleFileEmitter emits example files. This is used by the `sops` binary // whenever a new file is created, in order to present the user with a non-empty file type ExampleFileEmitter interface { EmitExample() []byte } // Store handles marshaling and unmarshaling from SOPS files type Store interface { sops.Store ExampleFileEmitter } type storeConstructor = func() Store func newBinaryStore() Store { return &json.BinaryStore{} } func newDotenvStore() Store { return &dotenv.Store{} } func newIniStore() Store { return &ini.Store{} } func newJsonStore() Store { return &json.Store{} } func newYamlStore() Store { return &yaml.Store{} } var storeConstructors = map[Format]storeConstructor{ Binary: newBinaryStore, Dotenv: newDotenvStore, Ini: newIniStore, Json: newJsonStore, Yaml: newYamlStore, } // DecryptTreeOpts are the options needed to decrypt a tree type DecryptTreeOpts struct { // Tree is the tree to be decrypted Tree *sops.Tree // KeyServices are the key services to be used for decryption of the data key KeyServices []keyservice.KeyServiceClient // IgnoreMac is whether or not to ignore the Message Authentication Code included in the SOPS tree IgnoreMac bool // Cipher is the cryptographic cipher to use to decrypt the values inside the tree Cipher sops.Cipher } // DecryptTree decrypts the tree passed in through the DecryptTreeOpts and additionally returns the decrypted data key func DecryptTree(opts DecryptTreeOpts) (dataKey []byte, err error) { dataKey, err = opts.Tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices) if err != nil { return nil, NewExitError(err, codes.CouldNotRetrieveKey) } computedMac, err := opts.Tree.Decrypt(dataKey, opts.Cipher) if err != nil { return nil, NewExitError(fmt.Sprintf("Error decrypting tree: %s", err), codes.ErrorDecryptingTree) } fileMac, err := opts.Cipher.Decrypt(opts.Tree.Metadata.MessageAuthenticationCode, dataKey, opts.Tree.Metadata.LastModified.Format(time.RFC3339)) if !opts.IgnoreMac { if fileMac != computedMac { // If the file has an empty MAC, display "no MAC" instead of not displaying anything if fileMac == "" { fileMac = "no MAC" } return nil, NewExitError(fmt.Sprintf("MAC mismatch. File has %s, computed %s", fileMac, computedMac), codes.MacMismatch) } } return dataKey, nil } // EncryptTreeOpts are the options needed to encrypt a tree type EncryptTreeOpts struct { // Tree is the tree to be encrypted Tree *sops.Tree // Cipher is the cryptographic cipher to use to encrypt the values inside the tree Cipher sops.Cipher // DataKey is the key the cipher should use to encrypt the values inside the tree DataKey []byte } // EncryptTree encrypts the tree passed in through the EncryptTreeOpts func EncryptTree(opts EncryptTreeOpts) error { unencryptedMac, err := opts.Tree.Encrypt(opts.DataKey, opts.Cipher) if err != nil { return NewExitError(fmt.Sprintf("Error encrypting tree: %s", err), codes.ErrorEncryptingTree) } opts.Tree.Metadata.LastModified = time.Now().UTC() opts.Tree.Metadata.MessageAuthenticationCode, err = opts.Cipher.Encrypt(unencryptedMac, opts.DataKey, opts.Tree.Metadata.LastModified.Format(time.RFC3339)) if err != nil { return NewExitError(fmt.Sprintf("Could not encrypt MAC: %s", err), codes.ErrorEncryptingMac) } return nil } // LoadEncryptedFile loads an encrypted SOPS file, returning a SOPS tree func LoadEncryptedFile(loader sops.EncryptedFileLoader, inputPath string) (*sops.Tree, error) { fileBytes, err := ioutil.ReadFile(inputPath) if err != nil { return nil, NewExitError(fmt.Sprintf("Error reading file: %s", err), codes.CouldNotReadInputFile) } path, err := filepath.Abs(inputPath) if err != nil { return nil, err } tree, err := loader.LoadEncryptedFile(fileBytes) tree.FilePath = path return &tree, err } // NewExitError returns a cli.ExitError given an error (wrapped in a generic interface{}) // and an exit code to represent the failure func NewExitError(i interface{}, exitCode int) *cli.ExitError { if userErr, ok := i.(sops.UserError); ok { return NewExitError(userErr.UserError(), exitCode) } return cli.NewExitError(i, exitCode) } // StoreForFormat returns the correct format-specific implementation // of the Store interface given the format. func StoreForFormat(format Format) Store { storeConst, found := storeConstructors[format] if !found { storeConst = storeConstructors[Binary] // default } return storeConst() } // DefaultStoreForPath returns the correct format-specific implementation // of the Store interface given the path to a file func DefaultStoreForPath(path string) Store { format := FormatForPath(path) return StoreForFormat(format) } // DefaultStoreForPathOrFormat returns the correct format-specific implementation // of the Store interface given the formatString if specified, or the path to a file. // This is to support the cli, where both are provided. func DefaultStoreForPathOrFormat(path, format string) Store { formatFmt := FormatForPathOrString(path, format) return StoreForFormat(formatFmt) } // KMS_ENC_CTX_BUG_FIXED_VERSION represents the SOPS version in which the // encryption context bug was fixed const KMS_ENC_CTX_BUG_FIXED_VERSION = "3.3.0" // DetectKMSEncryptionContextBug returns true if the encryption context bug is detected // in a given runtime sops.Tree object func DetectKMSEncryptionContextBug(tree *sops.Tree) (bool, error) { versionCheck, err := version.AIsNewerThanB(KMS_ENC_CTX_BUG_FIXED_VERSION, tree.Metadata.Version) if err != nil { return false, err } if versionCheck { _, _, key := GetKMSKeyWithEncryptionCtx(tree) if key != nil { return true, nil } } return false, nil } // GetKMSKeyWithEncryptionCtx returns the first KMS key affected by the encryption context bug as well as its location in the key groups. func GetKMSKeyWithEncryptionCtx(tree *sops.Tree) (keyGroupIndex int, keyIndex int, key *kms.MasterKey) { for i, kg := range tree.Metadata.KeyGroups { for n, k := range kg { kmsKey, ok := k.(*kms.MasterKey) if ok { if kmsKey.EncryptionContext != nil && len(kmsKey.EncryptionContext) >= 2 { duplicateValues := map[string]int{} for _, v := range kmsKey.EncryptionContext { duplicateValues[*v] = duplicateValues[*v] + 1 } if len(duplicateValues) > 1 { return i, n, kmsKey } } } } } return 0, 0, nil } // GenericDecryptOpts represents decryption options and config type GenericDecryptOpts struct { Cipher sops.Cipher InputStore sops.Store InputPath string IgnoreMAC bool KeyServices []keyservice.KeyServiceClient } // LoadEncryptedFileWithBugFixes is a wrapper around LoadEncryptedFile which includes // check for the issue described in https://github.com/mozilla/sops/pull/435 func LoadEncryptedFileWithBugFixes(opts GenericDecryptOpts) (*sops.Tree, error) { tree, err := LoadEncryptedFile(opts.InputStore, opts.InputPath) if err != nil { return nil, err } encCtxBug, err := DetectKMSEncryptionContextBug(tree) if err != nil { return nil, err } if encCtxBug { tree, err = FixAWSKMSEncryptionContextBug(opts, tree) if err != nil { return nil, err } } return tree, nil } // FixAWSKMSEncryptionContextBug is used to fix the issue described in https://github.com/mozilla/sops/pull/435 func FixAWSKMSEncryptionContextBug(opts GenericDecryptOpts, tree *sops.Tree) (*sops.Tree, error) { message := "Up until version 3.3.0 of sops there was a bug surrounding the " + "use of encryption context with AWS KMS." + "\nYou can read the full description of the issue here:" + "\nhttps://github.com/mozilla/sops/pull/435" + "\n\nIf a TTY is detected, sops will ask you if you'd like for this issue to be " + "automatically fixed, which will require re-encrypting the data keys used by " + "each key." + "\n\nIf you are not using a TTY, sops will fix the issue for this run.\n\n" fmt.Println(wordwrap.WrapString(message, 75)) persistFix := false if terminal.IsTerminal(int(os.Stdout.Fd())) { var response string for response != "y" && response != "n" { fmt.Println("Would you like sops to automatically fix this issue? (y/n): ") _, err := fmt.Scanln(&response) if err != nil { return nil, err } } if response == "n" { return nil, fmt.Errorf("Exiting. User responded no") } persistFix = true } // If there is another key, then we should be able to just decrypt // without having to try different variations of the encryption context. dataKey, err := DecryptTree(DecryptTreeOpts{ Cipher: opts.Cipher, IgnoreMac: opts.IgnoreMAC, Tree: tree, KeyServices: opts.KeyServices, }) if err != nil { dataKey = RecoverDataKeyFromBuggyKMS(opts, tree) } if dataKey == nil { return nil, NewExitError(fmt.Sprintf("Failed to decrypt, meaning there is likely another problem from the encryption context bug: %s", err), codes.ErrorDecryptingTree) } errs := tree.Metadata.UpdateMasterKeysWithKeyServices(dataKey, opts.KeyServices) if len(errs) > 0 { err = fmt.Errorf("Could not re-encrypt data key: %s", errs) return nil, err } err = EncryptTree(EncryptTreeOpts{ DataKey: dataKey, Tree: tree, Cipher: opts.Cipher, }) if err != nil { return nil, err } // If we are not going to persist the fix, just return the re-encrypted tree. if !persistFix { return tree, nil } encryptedFile, err := opts.InputStore.EmitEncryptedFile(*tree) if err != nil { return nil, NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree) } file, err := os.Create(opts.InputPath) defer file.Close() if err != nil { return nil, NewExitError(fmt.Sprintf("Could not open file for writing: %s", err), codes.CouldNotWriteOutputFile) } _, err = file.Write(encryptedFile) if err != nil { return nil, err } newTree, err := LoadEncryptedFile(opts.InputStore, opts.InputPath) if err != nil { return nil, err } return newTree, nil } // RecoverDataKeyFromBuggyKMS loops through variations on Encryption Context to // recover the datakey. This is used to fix the issue described in https://github.com/mozilla/sops/pull/435 func RecoverDataKeyFromBuggyKMS(opts GenericDecryptOpts, tree *sops.Tree) []byte { kgndx, kndx, originalKey := GetKMSKeyWithEncryptionCtx(tree) keyToEdit := *originalKey encCtxVals := map[string]interface{}{} for _, v := range keyToEdit.EncryptionContext { encCtxVals[*v] = "" } encCtxVariations := []map[string]*string{} for ctxVal := range encCtxVals { encCtxVariation := map[string]*string{} for key := range keyToEdit.EncryptionContext { val := ctxVal encCtxVariation[key] = &val } encCtxVariations = append(encCtxVariations, encCtxVariation) } for _, encCtxVar := range encCtxVariations { keyToEdit.EncryptionContext = encCtxVar tree.Metadata.KeyGroups[kgndx][kndx] = &keyToEdit dataKey, err := DecryptTree(DecryptTreeOpts{ Cipher: opts.Cipher, IgnoreMac: opts.IgnoreMAC, Tree: tree, KeyServices: opts.KeyServices, }) if err == nil { tree.Metadata.KeyGroups[kgndx][kndx] = originalKey tree.Metadata.Version = version.Version return dataKey } } return nil } // Diff represents a key diff type Diff struct { Common []keys.MasterKey Added []keys.MasterKey Removed []keys.MasterKey } func max(a, b int) int { if a > b { return a } return b } // DiffKeyGroups returns the list of diffs found in two sops.keyGroup slices func DiffKeyGroups(ours, theirs []sops.KeyGroup) []Diff { var diffs []Diff for i := 0; i < max(len(ours), len(theirs)); i++ { var diff Diff var ourGroup, theirGroup sops.KeyGroup if len(ours) > i { ourGroup = ours[i] } if len(theirs) > i { theirGroup = theirs[i] } ourKeys := make(map[string]struct{}) theirKeys := make(map[string]struct{}) for _, key := range ourGroup { ourKeys[key.ToString()] = struct{}{} } for _, key := range theirGroup { if _, ok := ourKeys[key.ToString()]; ok { diff.Common = append(diff.Common, key) } else { diff.Added = append(diff.Added, key) } theirKeys[key.ToString()] = struct{}{} } for _, key := range ourGroup { if _, ok := theirKeys[key.ToString()]; !ok { diff.Removed = append(diff.Removed, key) } } diffs = append(diffs, diff) } return diffs } // PrettyPrintDiffs prints a slice of Diff objects to stdout func PrettyPrintDiffs(diffs []Diff) { for i, diff := range diffs { color.New(color.Underline).Printf("Group %d\n", i+1) for _, c := range diff.Common { fmt.Printf(" %s\n", c.ToString()) } for _, c := range diff.Added { color.New(color.FgGreen).Printf("+++ %s\n", c.ToString()) } for _, c := range diff.Removed { color.New(color.FgRed).Printf("--- %s\n", c.ToString()) } } } 07070100000027000081A400000000000000000000000162794F93000007DC000000000000000000000000000000000000001F00000000sops-3.7.3/cmd/sops/decrypt.gopackage main import ( "fmt" "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/cmd/sops/codes" "go.mozilla.org/sops/v3/cmd/sops/common" "go.mozilla.org/sops/v3/keyservice" ) type decryptOpts struct { Cipher sops.Cipher InputStore sops.Store OutputStore sops.Store InputPath string IgnoreMAC bool Extract []interface{} KeyServices []keyservice.KeyServiceClient } func decrypt(opts decryptOpts) (decryptedFile []byte, err error) { tree, err := common.LoadEncryptedFileWithBugFixes(common.GenericDecryptOpts{ Cipher: opts.Cipher, InputStore: opts.InputStore, InputPath: opts.InputPath, IgnoreMAC: opts.IgnoreMAC, KeyServices: opts.KeyServices, }) if err != nil { return nil, err } _, err = common.DecryptTree(common.DecryptTreeOpts{ Cipher: opts.Cipher, IgnoreMac: opts.IgnoreMAC, Tree: tree, KeyServices: opts.KeyServices, }) if err != nil { return nil, err } if len(opts.Extract) > 0 { return extract(tree, opts.Extract, opts.OutputStore) } decryptedFile, err = opts.OutputStore.EmitPlainFile(tree.Branches) if err != nil { return nil, common.NewExitError(fmt.Sprintf("Error dumping file: %s", err), codes.ErrorDumpingTree) } return decryptedFile, err } func extract(tree *sops.Tree, path []interface{}, outputStore sops.Store) (output []byte, err error) { v, err := tree.Branches[0].Truncate(path) if err != nil { return nil, fmt.Errorf("error truncating tree: %s", err) } if newBranch, ok := v.(sops.TreeBranch); ok { tree.Branches[0] = newBranch decrypted, err := outputStore.EmitPlainFile(tree.Branches) if err != nil { return nil, common.NewExitError(fmt.Sprintf("Error dumping file: %s", err), codes.ErrorDumpingTree) } return decrypted, err } else if str, ok := v.(string); ok { return []byte(str), nil } bytes, err := outputStore.EmitValue(v) if err != nil { return nil, common.NewExitError(fmt.Sprintf("Error dumping tree: %s", err), codes.ErrorDumpingTree) } return bytes, nil } 07070100000028000081A400000000000000000000000162794F9300001FFF000000000000000000000000000000000000001C00000000sops-3.7.3/cmd/sops/edit.gopackage main import ( "fmt" "io/ioutil" "os" "crypto/md5" exec "golang.org/x/sys/execabs" "io" "strings" "bufio" "bytes" "path/filepath" "github.com/google/shlex" "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/cmd/sops/codes" "go.mozilla.org/sops/v3/cmd/sops/common" "go.mozilla.org/sops/v3/keyservice" "go.mozilla.org/sops/v3/version" ) type editOpts struct { Cipher sops.Cipher InputStore common.Store OutputStore common.Store InputPath string IgnoreMAC bool KeyServices []keyservice.KeyServiceClient ShowMasterKeys bool } type editExampleOpts struct { editOpts UnencryptedSuffix string EncryptedSuffix string UnencryptedRegex string EncryptedRegex string KeyGroups []sops.KeyGroup GroupThreshold int } type runEditorUntilOkOpts struct { TmpFile *os.File OriginalHash []byte InputStore sops.Store ShowMasterKeys bool Tree *sops.Tree } func editExample(opts editExampleOpts) ([]byte, error) { fileBytes := opts.InputStore.EmitExample() branches, err := opts.InputStore.LoadPlainFile(fileBytes) if err != nil { return nil, common.NewExitError(fmt.Sprintf("Error unmarshalling file: %s", err), codes.CouldNotReadInputFile) } path, err := filepath.Abs(opts.InputPath) if err != nil { return nil, err } tree := sops.Tree{ Branches: branches, Metadata: sops.Metadata{ KeyGroups: opts.KeyGroups, UnencryptedSuffix: opts.UnencryptedSuffix, EncryptedSuffix: opts.EncryptedSuffix, UnencryptedRegex: opts.UnencryptedRegex, EncryptedRegex: opts.EncryptedRegex, Version: version.Version, ShamirThreshold: opts.GroupThreshold, }, FilePath: path, } // Generate a data key dataKey, errs := tree.GenerateDataKeyWithKeyServices(opts.KeyServices) if len(errs) > 0 { return nil, common.NewExitError(fmt.Sprintf("Error encrypting the data key with one or more master keys: %s", errs), codes.CouldNotRetrieveKey) } return editTree(opts.editOpts, &tree, dataKey) } func edit(opts editOpts) ([]byte, error) { // Load the file tree, err := common.LoadEncryptedFileWithBugFixes(common.GenericDecryptOpts{ Cipher: opts.Cipher, InputStore: opts.InputStore, InputPath: opts.InputPath, IgnoreMAC: opts.IgnoreMAC, KeyServices: opts.KeyServices, }) if err != nil { return nil, err } // Decrypt the file dataKey, err := common.DecryptTree(common.DecryptTreeOpts{ Cipher: opts.Cipher, IgnoreMac: opts.IgnoreMAC, Tree: tree, KeyServices: opts.KeyServices, }) if err != nil { return nil, err } return editTree(opts, tree, dataKey) } func editTree(opts editOpts, tree *sops.Tree, dataKey []byte) ([]byte, error) { // Create temporary file for editing tmpdir, err := ioutil.TempDir("", "") if err != nil { return nil, common.NewExitError(fmt.Sprintf("Could not create temporary directory: %s", err), codes.CouldNotWriteOutputFile) } defer os.RemoveAll(tmpdir) tmpfile, err := os.Create(filepath.Join(tmpdir, filepath.Base(opts.InputPath))) if err != nil { return nil, common.NewExitError(fmt.Sprintf("Could not create temporary file: %s", err), codes.CouldNotWriteOutputFile) } // Write to temporary file var out []byte if opts.ShowMasterKeys { out, err = opts.OutputStore.EmitEncryptedFile(*tree) } else { out, err = opts.OutputStore.EmitPlainFile(tree.Branches) } if err != nil { return nil, common.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree) } _, err = tmpfile.Write(out) if err != nil { return nil, common.NewExitError(fmt.Sprintf("Could not write output file: %s", err), codes.CouldNotWriteOutputFile) } // Close temporary file, since Windows won't delete the file unless it's closed beforehand defer tmpfile.Close() // Compute file hash to detect if the file has been edited origHash, err := hashFile(tmpfile.Name()) if err != nil { return nil, common.NewExitError(fmt.Sprintf("Could not hash file: %s", err), codes.CouldNotReadInputFile) } // Let the user edit the file err = runEditorUntilOk(runEditorUntilOkOpts{ InputStore: opts.InputStore, OriginalHash: origHash, TmpFile: tmpfile, ShowMasterKeys: opts.ShowMasterKeys, Tree: tree}) if err != nil { return nil, err } // Encrypt the file err = common.EncryptTree(common.EncryptTreeOpts{ DataKey: dataKey, Tree: tree, Cipher: opts.Cipher, }) if err != nil { return nil, err } // Output the file encryptedFile, err := opts.OutputStore.EmitEncryptedFile(*tree) if err != nil { return nil, common.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree) } return encryptedFile, nil } func runEditorUntilOk(opts runEditorUntilOkOpts) error { for { err := runEditor(opts.TmpFile.Name()) if err != nil { return common.NewExitError(fmt.Sprintf("Could not run editor: %s", err), codes.NoEditorFound) } newHash, err := hashFile(opts.TmpFile.Name()) if err != nil { return common.NewExitError(fmt.Sprintf("Could not hash file: %s", err), codes.CouldNotReadInputFile) } if bytes.Equal(newHash, opts.OriginalHash) { return common.NewExitError("File has not changed, exiting.", codes.FileHasNotBeenModified) } edited, err := ioutil.ReadFile(opts.TmpFile.Name()) if err != nil { return common.NewExitError(fmt.Sprintf("Could not read edited file: %s", err), codes.CouldNotReadInputFile) } newBranches, err := opts.InputStore.LoadPlainFile(edited) if err != nil { log.WithField( "error", err, ).Errorf("Could not load tree, probably due to invalid " + "syntax. Press a key to return to the editor, or Ctrl+C to " + "exit.") bufio.NewReader(os.Stdin).ReadByte() continue } if opts.ShowMasterKeys { // The file is not actually encrypted, but it contains SOPS // metadata t, err := opts.InputStore.LoadEncryptedFile(edited) if err != nil { log.WithField( "error", err, ).Errorf("SOPS metadata is invalid. Press a key to " + "return to the editor, or Ctrl+C to exit.") bufio.NewReader(os.Stdin).ReadByte() continue } // Replace the whole tree, because otherwise newBranches would // contain the SOPS metadata opts.Tree = &t } opts.Tree.Branches = newBranches needVersionUpdated, err := version.AIsNewerThanB(version.Version, opts.Tree.Metadata.Version) if err != nil { return common.NewExitError(fmt.Sprintf("Failed to compare document version %q with program version %q: %v", opts.Tree.Metadata.Version, version.Version, err), codes.FailedToCompareVersions) } if needVersionUpdated { opts.Tree.Metadata.Version = version.Version } if opts.Tree.Metadata.MasterKeyCount() == 0 { log.Error("No master keys were provided, so sops can't " + "encrypt the file. Press a key to return to the editor, or " + "Ctrl+C to exit.") bufio.NewReader(os.Stdin).ReadByte() continue } break } return nil } func hashFile(filePath string) ([]byte, error) { var result []byte file, err := os.Open(filePath) if err != nil { return result, err } defer file.Close() hash := md5.New() if _, err := io.Copy(hash, file); err != nil { return result, err } return hash.Sum(result), nil } func runEditor(path string) error { editor := os.Getenv("EDITOR") var cmd *exec.Cmd if editor == "" { editor, err := lookupAnyEditor("vim", "nano", "vi") if err != nil { return err } cmd = exec.Command(editor, path) } else { parts, err := shlex.Split(editor) if err != nil { return fmt.Errorf("invalid $EDITOR: %s", editor) } parts = append(parts, path) cmd = exec.Command(parts[0], parts[1:]...) } cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() } func lookupAnyEditor(editorNames ...string) (editorPath string, err error) { for _, editorName := range editorNames { editorPath, err = exec.LookPath(editorName) if err == nil { return editorPath, nil } } return "", fmt.Errorf("no editor available: sops attempts to use the editor defined in the EDITOR environment variable, and if that's not set defaults to any of %s, but none of them could be found", strings.Join(editorNames, ", ")) } 07070100000029000081A400000000000000000000000162794F9300000C76000000000000000000000000000000000000001F00000000sops-3.7.3/cmd/sops/encrypt.gopackage main import ( "io/ioutil" "path/filepath" "fmt" wordwrap "github.com/mitchellh/go-wordwrap" "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/cmd/sops/codes" "go.mozilla.org/sops/v3/cmd/sops/common" "go.mozilla.org/sops/v3/keyservice" "go.mozilla.org/sops/v3/version" ) type encryptOpts struct { Cipher sops.Cipher InputStore sops.Store OutputStore sops.Store InputPath string KeyServices []keyservice.KeyServiceClient UnencryptedSuffix string EncryptedSuffix string UnencryptedRegex string EncryptedRegex string KeyGroups []sops.KeyGroup GroupThreshold int } type fileAlreadyEncryptedError struct{} func (err *fileAlreadyEncryptedError) Error() string { return "File already encrypted" } func (err *fileAlreadyEncryptedError) UserError() string { message := "The file you have provided contains a top-level entry called " + "'sops'. This is generally due to the file already being encrypted. " + "SOPS uses a top-level entry called 'sops' to store the metadata " + "required to decrypt the file. For this reason, SOPS can not " + "encrypt files that already contain such an entry.\n\n" + "If this is an unencrypted file, rename the 'sops' entry.\n\n" + "If this is an encrypted file and you want to edit it, use the " + "editor mode, for example: `sops my_file.yaml`" return wordwrap.WrapString(message, 75) } func ensureNoMetadata(opts encryptOpts, branch sops.TreeBranch) error { for _, b := range branch { if b.Key == "sops" { return &fileAlreadyEncryptedError{} } } return nil } func encrypt(opts encryptOpts) (encryptedFile []byte, err error) { // Load the file fileBytes, err := ioutil.ReadFile(opts.InputPath) if err != nil { return nil, common.NewExitError(fmt.Sprintf("Error reading file: %s", err), codes.CouldNotReadInputFile) } branches, err := opts.InputStore.LoadPlainFile(fileBytes) if err != nil { return nil, common.NewExitError(fmt.Sprintf("Error unmarshalling file: %s", err), codes.CouldNotReadInputFile) } if err := ensureNoMetadata(opts, branches[0]); err != nil { return nil, common.NewExitError(err, codes.FileAlreadyEncrypted) } path, err := filepath.Abs(opts.InputPath) if err != nil { return nil, err } tree := sops.Tree{ Branches: branches, Metadata: sops.Metadata{ KeyGroups: opts.KeyGroups, UnencryptedSuffix: opts.UnencryptedSuffix, EncryptedSuffix: opts.EncryptedSuffix, UnencryptedRegex: opts.UnencryptedRegex, EncryptedRegex: opts.EncryptedRegex, Version: version.Version, ShamirThreshold: opts.GroupThreshold, }, FilePath: path, } dataKey, errs := tree.GenerateDataKeyWithKeyServices(opts.KeyServices) if len(errs) > 0 { err = fmt.Errorf("Could not generate data key: %s", errs) return nil, err } err = common.EncryptTree(common.EncryptTreeOpts{ DataKey: dataKey, Tree: &tree, Cipher: opts.Cipher, }) if err != nil { return nil, err } encryptedFile, err = opts.OutputStore.EmitEncryptedFile(tree) if err != nil { return nil, common.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree) } return } 0707010000002A000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001C00000000sops-3.7.3/cmd/sops/formats0707010000002B000081A400000000000000000000000162794F9300000764000000000000000000000000000000000000002700000000sops-3.7.3/cmd/sops/formats/formats.gopackage formats import "strings" // Format is an enum type type Format int const ( Binary Format = iota Dotenv Ini Json Yaml ) var stringToFormat = map[string]Format{ "binary": Binary, "dotenv": Dotenv, "ini": Ini, "json": Json, "yaml": Yaml, } // FormatFromString returns a Format from a string. // This is used for converting string cli options. func FormatFromString(formatString string) Format { format, found := stringToFormat[formatString] if !found { return Binary } return format } // IsYAMLFile returns true if a given file path corresponds to a YAML file func IsYAMLFile(path string) bool { return strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml") } // IsJSONFile returns true if a given file path corresponds to a JSON file func IsJSONFile(path string) bool { return strings.HasSuffix(path, ".json") } // IsEnvFile returns true if a given file path corresponds to a .env file func IsEnvFile(path string) bool { return strings.HasSuffix(path, ".env") } // IsIniFile returns true if a given file path corresponds to a INI file func IsIniFile(path string) bool { return strings.HasSuffix(path, ".ini") } // FormatForPath returns the correct format given the path to a file func FormatForPath(path string) Format { format := Binary // default if IsYAMLFile(path) { format = Yaml } else if IsJSONFile(path) { format = Json } else if IsEnvFile(path) { format = Dotenv } else if IsIniFile(path) { format = Ini } return format } // FormatForPathOrString returns the correct format-specific implementation // of the Store interface given the formatString if specified, or the path to a file. // This is to support the cli, where both are provided. func FormatForPathOrString(path, format string) Format { formatFmt, found := stringToFormat[format] if !found { formatFmt = FormatForPath(path) } return formatFmt } 0707010000002C000081A400000000000000000000000162794F9300000671000000000000000000000000000000000000002C00000000sops-3.7.3/cmd/sops/formats/formats_test.gopackage formats import ( "testing" "github.com/stretchr/testify/assert" ) func TestFormatFromString(t *testing.T) { assert.Equal(t, Binary, FormatFromString("foobar")) assert.Equal(t, Dotenv, FormatFromString("dotenv")) assert.Equal(t, Ini, FormatFromString("ini")) assert.Equal(t, Yaml, FormatFromString("yaml")) assert.Equal(t, Json, FormatFromString("json")) } func TestFormatForPath(t *testing.T) { assert.Equal(t, Binary, FormatForPath("/path/to/foobar")) assert.Equal(t, Dotenv, FormatForPath("/path/to/foobar.env")) assert.Equal(t, Ini, FormatForPath("/path/to/foobar.ini")) assert.Equal(t, Json, FormatForPath("/path/to/foobar.json")) assert.Equal(t, Yaml, FormatForPath("/path/to/foobar.yml")) assert.Equal(t, Yaml, FormatForPath("/path/to/foobar.yaml")) } func TestFormatForPathOrString(t *testing.T) { assert.Equal(t, Binary, FormatForPathOrString("/path/to/foobar", "")) assert.Equal(t, Dotenv, FormatForPathOrString("/path/to/foobar", "dotenv")) assert.Equal(t, Dotenv, FormatForPathOrString("/path/to/foobar.env", "")) assert.Equal(t, Ini, FormatForPathOrString("/path/to/foobar", "ini")) assert.Equal(t, Ini, FormatForPathOrString("/path/to/foobar.ini", "")) assert.Equal(t, Json, FormatForPathOrString("/path/to/foobar", "json")) assert.Equal(t, Json, FormatForPathOrString("/path/to/foobar.json", "")) assert.Equal(t, Yaml, FormatForPathOrString("/path/to/foobar", "yaml")) assert.Equal(t, Yaml, FormatForPathOrString("/path/to/foobar.yml", "")) assert.Equal(t, Ini, FormatForPathOrString("/path/to/foobar.yml", "ini")) assert.Equal(t, Binary, FormatForPathOrString("/path/to/foobar.yml", "binary")) } 0707010000002D000081A400000000000000000000000162794F9300009F5A000000000000000000000000000000000000001C00000000sops-3.7.3/cmd/sops/main.gopackage main //import "go.mozilla.org/sops/v3/cmd/sops" import ( encodingjson "encoding/json" "fmt" "net" "net/url" "os" osExec "os/exec" "path/filepath" "reflect" "strconv" "strings" "time" "github.com/sirupsen/logrus" "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/aes" "go.mozilla.org/sops/v3/age" _ "go.mozilla.org/sops/v3/audit" "go.mozilla.org/sops/v3/azkv" "go.mozilla.org/sops/v3/cmd/sops/codes" "go.mozilla.org/sops/v3/cmd/sops/common" "go.mozilla.org/sops/v3/cmd/sops/subcommand/exec" "go.mozilla.org/sops/v3/cmd/sops/subcommand/groups" keyservicecmd "go.mozilla.org/sops/v3/cmd/sops/subcommand/keyservice" publishcmd "go.mozilla.org/sops/v3/cmd/sops/subcommand/publish" "go.mozilla.org/sops/v3/cmd/sops/subcommand/updatekeys" "go.mozilla.org/sops/v3/config" "go.mozilla.org/sops/v3/gcpkms" "go.mozilla.org/sops/v3/hcvault" "go.mozilla.org/sops/v3/keys" "go.mozilla.org/sops/v3/keyservice" "go.mozilla.org/sops/v3/kms" "go.mozilla.org/sops/v3/logging" "go.mozilla.org/sops/v3/pgp" "go.mozilla.org/sops/v3/stores/dotenv" "go.mozilla.org/sops/v3/stores/json" "go.mozilla.org/sops/v3/version" "google.golang.org/grpc" "gopkg.in/urfave/cli.v1" ) var log *logrus.Logger func init() { log = logging.NewLogger("CMD") } func main() { cli.VersionPrinter = version.PrintVersion app := cli.NewApp() keyserviceFlags := []cli.Flag{ cli.BoolTFlag{ Name: "enable-local-keyservice", Usage: "use local key service", }, cli.StringSliceFlag{ Name: "keyservice", Usage: "Specify the key services to use in addition to the local one. Can be specified more than once. Syntax: protocol://address. Example: tcp://myserver.com:5000", }, } app.Name = "sops" app.Usage = "sops - encrypted file editor with AWS KMS, GCP KMS, Azure Key Vault, age, and GPG support" app.ArgsUsage = "sops [options] file" app.Version = version.Version app.Authors = []cli.Author{ {Name: "AJ Bahnken", Email: "ajvb@mozilla.com"}, {Name: "Adrian Utrilla", Email: "adrianutrilla@gmail.com"}, {Name: "Julien Vehent", Email: "jvehent@mozilla.com"}, } app.UsageText = `sops is an editor of encrypted files that supports AWS KMS and PGP To encrypt or decrypt a document with AWS KMS, specify the KMS ARN in the -k flag or in the SOPS_KMS_ARN environment variable. (you need valid credentials in ~/.aws/credentials or in your env) To encrypt or decrypt a document with GCP KMS, specify the GCP KMS resource ID in the --gcp-kms flag or in the SOPS_GCP_KMS_IDS environment variable. (you need to setup google application default credentials. See https://developers.google.com/identity/protocols/application-default-credentials) To encrypt or decrypt a document with HashiCorp Vault's Transit Secret Engine, specify the Vault key URI name in the --hc-vault-transit flag or in the SOPS_VAULT_URIS environment variable (eg. https://vault.example.org:8200/v1/transit/keys/dev where 'https://vault.example.org:8200' is the vault server, 'transit' the enginePath, and 'dev' is the name of the key ) environment variable. (you need to enable the Transit Secrets Engine in Vault. See https://www.vaultproject.io/docs/secrets/transit/index.html) To encrypt or decrypt a document with Azure Key Vault, specify the Azure Key Vault key URL in the --azure-kv flag or in the SOPS_AZURE_KEYVAULT_URL environment variable. (authentication is based on environment variables, see https://docs.microsoft.com/en-us/go/azure/azure-sdk-go-authorization#use-environment-based-authentication. The user/sp needs the key/encrypt and key/decrypt permissions) To encrypt or decrypt using age, specify the recipient in the -a flag, or in the SOPS_AGE_RECIPIENTS environment variable. To encrypt or decrypt using PGP, specify the PGP fingerprint in the -p flag or in the SOPS_PGP_FP environment variable. To use multiple KMS or PGP keys, separate them by commas. For example: $ sops -p "10F2...0A, 85D...B3F21" file.yaml The -p, -k, --gcp-kms, --hc-vault-transit and --azure-kv flags are only used to encrypt new documents. Editing or decrypting existing documents can be done with "sops file" or "sops -d file" respectively. The KMS and PGP keys listed in the encrypted documents are used then. To manage master keys in existing documents, use the "add-{kms,pgp,gcp-kms,azure-kv,hc-vault-transit}" and "rm-{kms,pgp,gcp-kms,azure-kv,hc-vault-transit}" flags. To use a different GPG binary than the one in your PATH, set SOPS_GPG_EXEC. To select a different editor than the default (vim), set EDITOR. For more information, see the README at github.com/mozilla/sops` app.EnableBashCompletion = true app.Commands = []cli.Command{ { Name: "exec-env", Usage: "execute a command with decrypted values inserted into the environment", ArgsUsage: "[file to decrypt] [command to run]", Flags: append([]cli.Flag{ cli.BoolFlag{ Name: "background", Usage: "background the process and don't wait for it to complete", }, cli.StringFlag{ Name: "user", Usage: "the user to run the command as", }, }, keyserviceFlags...), Action: func(c *cli.Context) error { if len(c.Args()) != 2 { return common.NewExitError(fmt.Errorf("error: missing file to decrypt"), codes.ErrorGeneric) } fileName := c.Args()[0] command := c.Args()[1] inputStore := inputStore(c, fileName) svcs := keyservices(c) opts := decryptOpts{ OutputStore: &dotenv.Store{}, InputStore: inputStore, InputPath: fileName, Cipher: aes.NewCipher(), KeyServices: svcs, IgnoreMAC: c.Bool("ignore-mac"), } output, err := decrypt(opts) if err != nil { return toExitError(err) } if err := exec.ExecWithEnv(exec.ExecOpts{ Command: command, Plaintext: output, Background: c.Bool("background"), User: c.String("user"), }); err != nil { return toExitError(err) } return nil }, }, { Name: "exec-file", Usage: "execute a command with the decrypted contents as a temporary file", ArgsUsage: "[file to decrypt] [command to run]", Flags: append([]cli.Flag{ cli.BoolFlag{ Name: "background", Usage: "background the process and don't wait for it to complete", }, cli.BoolFlag{ Name: "no-fifo", Usage: "use a regular file instead of a fifo to temporarily hold the decrypted contents", }, cli.StringFlag{ Name: "user", Usage: "the user to run the command as", }, cli.StringFlag{ Name: "input-type", Usage: "currently json, yaml, dotenv and binary are supported. If not set, sops will use the file's extension to determine the type", }, cli.StringFlag{ Name: "output-type", Usage: "currently json, yaml, dotenv and binary are supported. If not set, sops will use the input file's extension to determine the output format", }, cli.StringFlag{ Name: "filename", Usage: "filename for the temporarily file (default: tmp-file)", }, }, keyserviceFlags...), Action: func(c *cli.Context) error { if len(c.Args()) != 2 { return common.NewExitError(fmt.Errorf("error: missing file to decrypt"), codes.ErrorGeneric) } fileName := c.Args()[0] command := c.Args()[1] inputStore := inputStore(c, fileName) outputStore := outputStore(c, fileName) svcs := keyservices(c) opts := decryptOpts{ OutputStore: outputStore, InputStore: inputStore, InputPath: fileName, Cipher: aes.NewCipher(), KeyServices: svcs, IgnoreMAC: c.Bool("ignore-mac"), } output, err := decrypt(opts) if err != nil { return toExitError(err) } filename := c.String("filename") if filename == "" { filename = "tmp-file" } if err := exec.ExecWithFile(exec.ExecOpts{ Command: command, Plaintext: output, Background: c.Bool("background"), Fifo: !c.Bool("no-fifo"), User: c.String("user"), Filename: filename, }); err != nil { return toExitError(err) } return nil }, }, { Name: "publish", Usage: "Publish sops file or directory to a configured destination", ArgsUsage: `file`, Flags: append([]cli.Flag{ cli.BoolFlag{ Name: "yes, y", Usage: `pre-approve all changes and run non-interactively`, }, cli.BoolFlag{ Name: "omit-extensions", Usage: "Omit file extensions in destination path when publishing sops file to configured destinations", }, cli.BoolFlag{ Name: "recursive", Usage: "If the source path is a directory, publish all its content recursively", }, cli.BoolFlag{ Name: "verbose", Usage: "Enable verbose logging output", }, }, keyserviceFlags...), Action: func(c *cli.Context) error { if c.Bool("verbose") || c.GlobalBool("verbose") { logging.SetLevel(logrus.DebugLevel) } configPath, err := config.FindConfigFile(".") if err != nil { return common.NewExitError(err, codes.ErrorGeneric) } if c.NArg() < 1 { return common.NewExitError("Error: no file specified", codes.NoFileSpecified) } path := c.Args()[0] info, err := os.Stat(path) if err != nil { return err } if info.IsDir() && !c.Bool("recursive") { return fmt.Errorf("can't operate on a directory without --recursive flag.") } err = filepath.Walk(path, func(subPath string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() { err = publishcmd.Run(publishcmd.Opts{ ConfigPath: configPath, InputPath: subPath, Cipher: aes.NewCipher(), KeyServices: keyservices(c), InputStore: inputStore(c, subPath), Interactive: !c.Bool("yes"), OmitExtensions: c.Bool("omit-extensions"), Recursive: c.Bool("recursive"), RootPath: path, }) if cliErr, ok := err.(*cli.ExitError); ok && cliErr != nil { return cliErr } else if err != nil { return common.NewExitError(err, codes.ErrorGeneric) } } return nil }) if err != nil { return err } return nil }, }, { Name: "keyservice", Usage: "start a SOPS key service server", Flags: []cli.Flag{ cli.StringFlag{ Name: "network, net", Usage: "network to listen on, e.g. 'tcp' or 'unix'", Value: "tcp", }, cli.StringFlag{ Name: "address, addr", Usage: "address to listen on, e.g. '127.0.0.1:5000' or '/tmp/sops.sock'", Value: "127.0.0.1:5000", }, cli.BoolFlag{ Name: "prompt", Usage: "Prompt user to confirm every incoming request", }, cli.BoolFlag{ Name: "verbose", Usage: "Enable verbose logging output", }, }, Action: func(c *cli.Context) error { if c.Bool("verbose") || c.GlobalBool("verbose") { logging.SetLevel(logrus.DebugLevel) } err := keyservicecmd.Run(keyservicecmd.Opts{ Network: c.String("network"), Address: c.String("address"), Prompt: c.Bool("prompt"), }) if err != nil { log.Errorf("Error running keyservice: %s", err) return err } return nil }, }, { Name: "groups", Usage: "modify the groups on a SOPS file", Subcommands: []cli.Command{ { Name: "add", Usage: "add a new group to a SOPS file", Flags: append([]cli.Flag{ cli.StringFlag{ Name: "file, f", Usage: "the file to add the group to", }, cli.StringSliceFlag{ Name: "pgp", Usage: "the PGP fingerprints the new group should contain. Can be specified more than once", }, cli.StringSliceFlag{ Name: "kms", Usage: "the KMS ARNs the new group should contain. Can be specified more than once", }, cli.StringFlag{ Name: "aws-profile", Usage: "The AWS profile to use for requests to AWS", }, cli.StringSliceFlag{ Name: "gcp-kms", Usage: "the GCP KMS Resource ID the new group should contain. Can be specified more than once", }, cli.StringSliceFlag{ Name: "azure-kv", Usage: "the Azure Key Vault key URL the new group should contain. Can be specified more than once", }, cli.StringSliceFlag{ Name: "hc-vault-transit", Usage: "the full vault path to the key used to encrypt/decrypt. Make you choose and configure a key with encrption/decryption enabled (e.g. 'https://vault.example.org:8200/v1/transit/keys/dev'). Can be specified more than once", }, cli.StringSliceFlag{ Name: "age", Usage: "the age recipient the new group should contain. Can be specified more than once", }, cli.BoolFlag{ Name: "in-place, i", Usage: "write output back to the same file instead of stdout", }, cli.IntFlag{ Name: "shamir-secret-sharing-threshold", Usage: "the number of master keys required to retrieve the data key with shamir", }, cli.StringFlag{ Name: "encryption-context", Usage: "comma separated list of KMS encryption context key:value pairs", }, }, keyserviceFlags...), Action: func(c *cli.Context) error { pgpFps := c.StringSlice("pgp") kmsArns := c.StringSlice("kms") gcpKmses := c.StringSlice("gcp-kms") vaultURIs := c.StringSlice("hc-vault-transit") azkvs := c.StringSlice("azure-kv") ageRecipients := c.StringSlice("age") var group sops.KeyGroup for _, fp := range pgpFps { group = append(group, pgp.NewMasterKeyFromFingerprint(fp)) } for _, arn := range kmsArns { group = append(group, kms.NewMasterKeyFromArn(arn, kms.ParseKMSContext(c.String("encryption-context")), c.String("aws-profile"))) } for _, kms := range gcpKmses { group = append(group, gcpkms.NewMasterKeyFromResourceID(kms)) } for _, uri := range vaultURIs { k, err := hcvault.NewMasterKeyFromURI(uri) if err != nil { log.WithError(err).Error("Failed to add key") continue } group = append(group, k) } for _, url := range azkvs { k, err := azkv.NewMasterKeyFromURL(url) if err != nil { log.WithError(err).Error("Failed to add key") continue } group = append(group, k) } for _, recipient := range ageRecipients { keys, err := age.MasterKeysFromRecipients(recipient) if err != nil { log.WithError(err).Error("Failed to add key") continue } for _, key := range keys { group = append(group, key) } } return groups.Add(groups.AddOpts{ InputPath: c.String("file"), InPlace: c.Bool("in-place"), InputStore: inputStore(c, c.String("file")), OutputStore: outputStore(c, c.String("file")), Group: group, GroupThreshold: c.Int("shamir-secret-sharing-threshold"), KeyServices: keyservices(c), }) }, }, { Name: "delete", Usage: "delete a key group from a SOPS file", Flags: append([]cli.Flag{ cli.StringFlag{ Name: "file, f", Usage: "the file to add the group to", }, cli.BoolFlag{ Name: "in-place, i", Usage: "write output back to the same file instead of stdout", }, cli.IntFlag{ Name: "shamir-secret-sharing-threshold", Usage: "the number of master keys required to retrieve the data key with shamir", }, }, keyserviceFlags...), ArgsUsage: `[index]`, Action: func(c *cli.Context) error { group, err := strconv.ParseUint(c.Args().First(), 10, 32) if err != nil { return fmt.Errorf("failed to parse [index] argument: %s", err) } return groups.Delete(groups.DeleteOpts{ InputPath: c.String("file"), InPlace: c.Bool("in-place"), InputStore: inputStore(c, c.String("file")), OutputStore: outputStore(c, c.String("file")), Group: uint(group), GroupThreshold: c.Int("shamir-secret-sharing-threshold"), KeyServices: keyservices(c), }) }, }, }, }, { Name: "updatekeys", Usage: "update the keys of a SOPS file using the config file", ArgsUsage: `file`, Flags: append([]cli.Flag{ cli.BoolFlag{ Name: "yes, y", Usage: `pre-approve all changes and run non-interactively`, }, }, keyserviceFlags...), Action: func(c *cli.Context) error { var err error var configPath string if c.GlobalString("config") != "" { configPath = c.GlobalString("config") } else { configPath, err = config.FindConfigFile(".") if err != nil { return common.NewExitError(err, codes.ErrorGeneric) } } if c.NArg() < 1 { return common.NewExitError("Error: no file specified", codes.NoFileSpecified) } err = updatekeys.UpdateKeys(updatekeys.Opts{ InputPath: c.Args()[0], GroupQuorum: c.Int("shamir-secret-sharing-quorum"), KeyServices: keyservices(c), Interactive: !c.Bool("yes"), ConfigPath: configPath, }) if cliErr, ok := err.(*cli.ExitError); ok && cliErr != nil { return cliErr } else if err != nil { return common.NewExitError(err, codes.ErrorGeneric) } return nil }, }, } app.Flags = append([]cli.Flag{ cli.BoolFlag{ Name: "decrypt, d", Usage: "decrypt a file and output the result to stdout", }, cli.BoolFlag{ Name: "encrypt, e", Usage: "encrypt a file and output the result to stdout", }, cli.BoolFlag{ Name: "rotate, r", Usage: "generate a new data encryption key and reencrypt all values with the new key", }, cli.StringFlag{ Name: "kms, k", Usage: "comma separated list of KMS ARNs", EnvVar: "SOPS_KMS_ARN", }, cli.StringFlag{ Name: "aws-profile", Usage: "The AWS profile to use for requests to AWS", }, cli.StringFlag{ Name: "gcp-kms", Usage: "comma separated list of GCP KMS resource IDs", EnvVar: "SOPS_GCP_KMS_IDS", }, cli.StringFlag{ Name: "azure-kv", Usage: "comma separated list of Azure Key Vault URLs", EnvVar: "SOPS_AZURE_KEYVAULT_URLS", }, cli.StringFlag{ Name: "hc-vault-transit", Usage: "comma separated list of vault's key URI (e.g. 'https://vault.example.org:8200/v1/transit/keys/dev')", EnvVar: "SOPS_VAULT_URIS", }, cli.StringFlag{ Name: "pgp, p", Usage: "comma separated list of PGP fingerprints", EnvVar: "SOPS_PGP_FP", }, cli.StringFlag{ Name: "age, a", Usage: "comma separated list of age recipients", EnvVar: "SOPS_AGE_RECIPIENTS", }, cli.BoolFlag{ Name: "in-place, i", Usage: "write output back to the same file instead of stdout", }, cli.StringFlag{ Name: "extract", Usage: "extract a specific key or branch from the input document. Decrypt mode only. Example: --extract '[\"somekey\"][0]'", }, cli.StringFlag{ Name: "input-type", Usage: "currently json, yaml, dotenv and binary are supported. If not set, sops will use the file's extension to determine the type", }, cli.StringFlag{ Name: "output-type", Usage: "currently json, yaml, dotenv and binary are supported. If not set, sops will use the input file's extension to determine the output format", }, cli.BoolFlag{ Name: "show-master-keys, s", Usage: "display master encryption keys in the file during editing", }, cli.StringFlag{ Name: "add-gcp-kms", Usage: "add the provided comma-separated list of GCP KMS key resource IDs to the list of master keys on the given file", }, cli.StringFlag{ Name: "rm-gcp-kms", Usage: "remove the provided comma-separated list of GCP KMS key resource IDs from the list of master keys on the given file", }, cli.StringFlag{ Name: "add-azure-kv", Usage: "add the provided comma-separated list of Azure Key Vault key URLs to the list of master keys on the given file", }, cli.StringFlag{ Name: "rm-azure-kv", Usage: "remove the provided comma-separated list of Azure Key Vault key URLs from the list of master keys on the given file", }, cli.StringFlag{ Name: "add-kms", Usage: "add the provided comma-separated list of KMS ARNs to the list of master keys on the given file", }, cli.StringFlag{ Name: "rm-kms", Usage: "remove the provided comma-separated list of KMS ARNs from the list of master keys on the given file", }, cli.StringFlag{ Name: "add-hc-vault-transit", Usage: "add the provided comma-separated list of Vault's URI key to the list of master keys on the given file ( eg. https://vault.example.org:8200/v1/transit/keys/dev)", }, cli.StringFlag{ Name: "rm-hc-vault-transit", Usage: "remove the provided comma-separated list of Vault's URI key from the list of master keys on the given file ( eg. https://vault.example.org:8200/v1/transit/keys/dev)", }, cli.StringFlag{ Name: "add-age", Usage: "add the provided comma-separated list of age recipients fingerprints to the list of master keys on the given file", }, cli.StringFlag{ Name: "rm-age", Usage: "remove the provided comma-separated list of age recipients from the list of master keys on the given file", }, cli.StringFlag{ Name: "add-pgp", Usage: "add the provided comma-separated list of PGP fingerprints to the list of master keys on the given file", }, cli.StringFlag{ Name: "rm-pgp", Usage: "remove the provided comma-separated list of PGP fingerprints from the list of master keys on the given file", }, cli.BoolFlag{ Name: "ignore-mac", Usage: "ignore Message Authentication Code during decryption", }, cli.StringFlag{ Name: "unencrypted-suffix", Usage: "override the unencrypted key suffix.", }, cli.StringFlag{ Name: "encrypted-suffix", Usage: "override the encrypted key suffix. When empty, all keys will be encrypted, unless otherwise marked with unencrypted-suffix.", }, cli.StringFlag{ Name: "unencrypted-regex", Usage: "set the unencrypted key suffix. When specified, only keys matching the regex will be left unencrypted.", }, cli.StringFlag{ Name: "encrypted-regex", Usage: "set the encrypted key suffix. When specified, only keys matching the regex will be encrypted.", }, cli.StringFlag{ Name: "config", Usage: "path to sops' config file. If set, sops will not search for the config file recursively.", }, cli.StringFlag{ Name: "encryption-context", Usage: "comma separated list of KMS encryption context key:value pairs", }, cli.StringFlag{ Name: "set", Usage: `set a specific key or branch in the input document. value must be a json encoded string. (edit mode only). eg. --set '["somekey"][0] {"somevalue":true}'`, }, cli.IntFlag{ Name: "shamir-secret-sharing-threshold", Usage: "the number of master keys required to retrieve the data key with shamir", }, cli.BoolFlag{ Name: "verbose", Usage: "Enable verbose logging output", }, cli.StringFlag{ Name: "output", Usage: "Save the output after encryption or decryption to the file specified", }, }, keyserviceFlags...) app.Action = func(c *cli.Context) error { if c.Bool("verbose") { logging.SetLevel(logrus.DebugLevel) } if c.NArg() < 1 { return common.NewExitError("Error: no file specified", codes.NoFileSpecified) } if c.Bool("in-place") && c.String("output") != "" { return common.NewExitError("Error: cannot operate on both --output and --in-place", codes.ErrorConflictingParameters) } fileName, err := filepath.Abs(c.Args()[0]) if err != nil { return toExitError(err) } if _, err := os.Stat(fileName); os.IsNotExist(err) { if c.String("add-kms") != "" || c.String("add-pgp") != "" || c.String("add-gcp-kms") != "" || c.String("add-hc-vault-transit") != "" || c.String("add-azure-kv") != "" || c.String("add-age") != "" || c.String("rm-kms") != "" || c.String("rm-pgp") != "" || c.String("rm-gcp-kms") != "" || c.String("rm-hc-vault-transit") != "" || c.String("rm-azure-kv") != "" || c.String("rm-age") != "" { return common.NewExitError("Error: cannot add or remove keys on non-existent files, use `--kms` and `--pgp` instead.", codes.CannotChangeKeysFromNonExistentFile) } if c.Bool("encrypt") || c.Bool("decrypt") || c.Bool("rotate") { return common.NewExitError("Error: cannot operate on non-existent file", codes.NoFileSpecified) } } unencryptedSuffix := c.String("unencrypted-suffix") encryptedSuffix := c.String("encrypted-suffix") encryptedRegex := c.String("encrypted-regex") unencryptedRegex := c.String("unencrypted-regex") conf, err := loadConfig(c, fileName, nil) if err != nil { return toExitError(err) } if conf != nil { // command line options have precedence if unencryptedSuffix == "" { unencryptedSuffix = conf.UnencryptedSuffix } if encryptedSuffix == "" { encryptedSuffix = conf.EncryptedSuffix } if encryptedRegex == "" { encryptedRegex = conf.EncryptedRegex } if unencryptedRegex == "" { unencryptedRegex = conf.UnencryptedRegex } } cryptRuleCount := 0 if unencryptedSuffix != "" { cryptRuleCount++ } if encryptedSuffix != "" { cryptRuleCount++ } if encryptedRegex != "" { cryptRuleCount++ } if unencryptedRegex != "" { cryptRuleCount++ } if cryptRuleCount > 1 { return common.NewExitError("Error: cannot use more than one of encrypted_suffix, unencrypted_suffix, encrypted_regex or unencrypted_regex in the same file", codes.ErrorConflictingParameters) } // only supply the default UnencryptedSuffix when EncryptedSuffix and EncryptedRegex are not provided if cryptRuleCount == 0 { unencryptedSuffix = sops.DefaultUnencryptedSuffix } inputStore := inputStore(c, fileName) outputStore := outputStore(c, fileName) svcs := keyservices(c) var output []byte if c.Bool("encrypt") { var groups []sops.KeyGroup groups, err = keyGroups(c, fileName) if err != nil { return toExitError(err) } var threshold int threshold, err = shamirThreshold(c, fileName) if err != nil { return toExitError(err) } output, err = encrypt(encryptOpts{ OutputStore: outputStore, InputStore: inputStore, InputPath: fileName, Cipher: aes.NewCipher(), UnencryptedSuffix: unencryptedSuffix, EncryptedSuffix: encryptedSuffix, UnencryptedRegex: unencryptedRegex, EncryptedRegex: encryptedRegex, KeyServices: svcs, KeyGroups: groups, GroupThreshold: threshold, }) } if c.Bool("decrypt") { var extract []interface{} extract, err = parseTreePath(c.String("extract")) if err != nil { return common.NewExitError(fmt.Errorf("error parsing --extract path: %s", err), codes.InvalidTreePathFormat) } output, err = decrypt(decryptOpts{ OutputStore: outputStore, InputStore: inputStore, InputPath: fileName, Cipher: aes.NewCipher(), Extract: extract, KeyServices: svcs, IgnoreMAC: c.Bool("ignore-mac"), }) } if c.Bool("rotate") { var addMasterKeys []keys.MasterKey kmsEncryptionContext := kms.ParseKMSContext(c.String("encryption-context")) for _, k := range kms.MasterKeysFromArnString(c.String("add-kms"), kmsEncryptionContext, c.String("aws-profile")) { addMasterKeys = append(addMasterKeys, k) } for _, k := range pgp.MasterKeysFromFingerprintString(c.String("add-pgp")) { addMasterKeys = append(addMasterKeys, k) } for _, k := range gcpkms.MasterKeysFromResourceIDString(c.String("add-gcp-kms")) { addMasterKeys = append(addMasterKeys, k) } azureKeys, err := azkv.MasterKeysFromURLs(c.String("add-azure-kv")) if err != nil { return err } for _, k := range azureKeys { addMasterKeys = append(addMasterKeys, k) } hcVaultKeys, err := hcvault.NewMasterKeysFromURIs(c.String("add-hc-vault-transit")) if err != nil { return err } for _, k := range hcVaultKeys { addMasterKeys = append(addMasterKeys, k) } ageKeys, err := age.MasterKeysFromRecipients(c.String("add-age")) if err != nil { return err } for _, k := range ageKeys { addMasterKeys = append(addMasterKeys, k) } var rmMasterKeys []keys.MasterKey for _, k := range kms.MasterKeysFromArnString(c.String("rm-kms"), kmsEncryptionContext, c.String("aws-profile")) { rmMasterKeys = append(rmMasterKeys, k) } for _, k := range pgp.MasterKeysFromFingerprintString(c.String("rm-pgp")) { rmMasterKeys = append(rmMasterKeys, k) } for _, k := range gcpkms.MasterKeysFromResourceIDString(c.String("rm-gcp-kms")) { rmMasterKeys = append(rmMasterKeys, k) } azureKeys, err = azkv.MasterKeysFromURLs(c.String("rm-azure-kv")) if err != nil { return err } for _, k := range azureKeys { rmMasterKeys = append(rmMasterKeys, k) } hcVaultKeys, err = hcvault.NewMasterKeysFromURIs(c.String("rm-hc-vault-transit")) if err != nil { return err } for _, k := range hcVaultKeys { rmMasterKeys = append(rmMasterKeys, k) } ageKeys, err = age.MasterKeysFromRecipients(c.String("rm-age")) if err != nil { return err } for _, k := range ageKeys { rmMasterKeys = append(rmMasterKeys, k) } output, err = rotate(rotateOpts{ OutputStore: outputStore, InputStore: inputStore, InputPath: fileName, Cipher: aes.NewCipher(), KeyServices: svcs, IgnoreMAC: c.Bool("ignore-mac"), AddMasterKeys: addMasterKeys, RemoveMasterKeys: rmMasterKeys, }) } if c.String("set") != "" { var path []interface{} var value interface{} path, value, err = extractSetArguments(c.String("set")) if err != nil { return toExitError(err) } output, err = set(setOpts{ OutputStore: outputStore, InputStore: inputStore, InputPath: fileName, Cipher: aes.NewCipher(), KeyServices: svcs, IgnoreMAC: c.Bool("ignore-mac"), Value: value, TreePath: path, }) } isEditMode := !c.Bool("encrypt") && !c.Bool("decrypt") && !c.Bool("rotate") && c.String("set") == "" if isEditMode { _, statErr := os.Stat(fileName) fileExists := statErr == nil opts := editOpts{ OutputStore: outputStore, InputStore: inputStore, InputPath: fileName, Cipher: aes.NewCipher(), KeyServices: svcs, IgnoreMAC: c.Bool("ignore-mac"), ShowMasterKeys: c.Bool("show-master-keys"), } if fileExists { output, err = edit(opts) } else { // File doesn't exist, edit the example file instead var groups []sops.KeyGroup groups, err = keyGroups(c, fileName) if err != nil { return toExitError(err) } var threshold int threshold, err = shamirThreshold(c, fileName) if err != nil { return toExitError(err) } output, err = editExample(editExampleOpts{ editOpts: opts, UnencryptedSuffix: unencryptedSuffix, EncryptedSuffix: encryptedSuffix, UnencryptedRegex: unencryptedRegex, EncryptedRegex: encryptedRegex, KeyGroups: groups, GroupThreshold: threshold, }) } } if err != nil { return toExitError(err) } // We open the file *after* the operations on the tree have been // executed to avoid truncating it when there's errors if c.Bool("in-place") || isEditMode || c.String("set") != "" { file, err := os.Create(fileName) if err != nil { return common.NewExitError(fmt.Sprintf("Could not open in-place file for writing: %s", err), codes.CouldNotWriteOutputFile) } defer file.Close() _, err = file.Write(output) if err != nil { return toExitError(err) } log.Info("File written successfully") return nil } outputFile := os.Stdout if c.String("output") != "" { file, err := os.Create(c.String("output")) if err != nil { return common.NewExitError(fmt.Sprintf("Could not open output file for writing: %s", err), codes.CouldNotWriteOutputFile) } defer file.Close() outputFile = file } _, err = outputFile.Write(output) return toExitError(err) } err := app.Run(os.Args) if err != nil { log.Fatal(err) } } func toExitError(err error) error { if cliErr, ok := err.(*cli.ExitError); ok && cliErr != nil { return cliErr } else if execErr, ok := err.(*osExec.ExitError); ok && execErr != nil { return cli.NewExitError(err, execErr.ExitCode()) } else if err != nil { return cli.NewExitError(err, codes.ErrorGeneric) } return nil } func keyservices(c *cli.Context) (svcs []keyservice.KeyServiceClient) { if c.Bool("enable-local-keyservice") { svcs = append(svcs, keyservice.NewLocalClient()) } uris := c.StringSlice("keyservice") for _, uri := range uris { url, err := url.Parse(uri) if err != nil { log.WithField("uri", uri). Warnf("Error parsing URI for keyservice, skipping") continue } addr := url.Host if url.Scheme == "unix" { addr = url.Path } opts := []grpc.DialOption{ grpc.WithInsecure(), grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { return net.DialTimeout(url.Scheme, addr, timeout) }), } log.WithField( "address", fmt.Sprintf("%s://%s", url.Scheme, addr), ).Infof("Connecting to key service") conn, err := grpc.Dial(addr, opts...) if err != nil { log.Fatalf("failed to listen: %v", err) } svcs = append(svcs, keyservice.NewKeyServiceClient(conn)) } return } func inputStore(context *cli.Context, path string) common.Store { return common.DefaultStoreForPathOrFormat(path, context.String("input-type")) } func outputStore(context *cli.Context, path string) common.Store { return common.DefaultStoreForPathOrFormat(path, context.String("output-type")) } func parseTreePath(arg string) ([]interface{}, error) { var path []interface{} components := strings.Split(arg, "[") for _, component := range components { if component == "" { continue } if component[len(component)-1] != ']' { return nil, fmt.Errorf("component %s doesn't end with ]", component) } component = component[:len(component)-1] if component[0] == byte('"') || component[0] == byte('\'') { // The component is a string component = component[1 : len(component)-1] path = append(path, component) } else { // The component must be a number i, err := strconv.Atoi(component) if err != nil { return nil, err } path = append(path, i) } } return path, nil } func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) { var kmsKeys []keys.MasterKey var pgpKeys []keys.MasterKey var cloudKmsKeys []keys.MasterKey var azkvKeys []keys.MasterKey var hcVaultMkKeys []keys.MasterKey var ageMasterKeys []keys.MasterKey kmsEncryptionContext := kms.ParseKMSContext(c.String("encryption-context")) if c.String("encryption-context") != "" && kmsEncryptionContext == nil { return nil, common.NewExitError("Invalid KMS encryption context format", codes.ErrorInvalidKMSEncryptionContextFormat) } if c.String("kms") != "" { for _, k := range kms.MasterKeysFromArnString(c.String("kms"), kmsEncryptionContext, c.String("aws-profile")) { kmsKeys = append(kmsKeys, k) } } if c.String("gcp-kms") != "" { for _, k := range gcpkms.MasterKeysFromResourceIDString(c.String("gcp-kms")) { cloudKmsKeys = append(cloudKmsKeys, k) } } if c.String("azure-kv") != "" { azureKeys, err := azkv.MasterKeysFromURLs(c.String("azure-kv")) if err != nil { return nil, err } for _, k := range azureKeys { azkvKeys = append(azkvKeys, k) } } if c.String("hc-vault-transit") != "" { hcVaultKeys, err := hcvault.NewMasterKeysFromURIs(c.String("hc-vault-transit")) if err != nil { return nil, err } for _, k := range hcVaultKeys { hcVaultMkKeys = append(hcVaultMkKeys, k) } } if c.String("pgp") != "" { for _, k := range pgp.MasterKeysFromFingerprintString(c.String("pgp")) { pgpKeys = append(pgpKeys, k) } } if c.String("age") != "" { ageKeys, err := age.MasterKeysFromRecipients(c.String("age")) if err != nil { return nil, err } for _, k := range ageKeys { ageMasterKeys = append(ageMasterKeys, k) } } if c.String("kms") == "" && c.String("pgp") == "" && c.String("gcp-kms") == "" && c.String("azure-kv") == "" && c.String("hc-vault-transit") == "" && c.String("age") == "" { conf, err := loadConfig(c, file, kmsEncryptionContext) // config file might just not be supplied, without any error if conf == nil { errMsg := "config file not found and no keys provided through command line options" if err != nil { errMsg = fmt.Sprintf("%s: %s", errMsg, err) } return nil, fmt.Errorf(errMsg) } return conf.KeyGroups, err } var group sops.KeyGroup group = append(group, kmsKeys...) group = append(group, cloudKmsKeys...) group = append(group, azkvKeys...) group = append(group, pgpKeys...) group = append(group, hcVaultMkKeys...) group = append(group, ageMasterKeys...) log.Debugf("Master keys available: %+v", group) return []sops.KeyGroup{group}, nil } // loadConfig will look for an existing config file, either provided through the command line, or using config.FindConfigFile. // Since a config file is not required, this function does not error when one is not found, and instead returns a nil config pointer func loadConfig(c *cli.Context, file string, kmsEncryptionContext map[string]*string) (*config.Config, error) { var err error var configPath string if c.String("config") != "" { configPath = c.String("config") } else { // Ignore config not found errors returned from FindConfigFile since the config file is not mandatory configPath, err = config.FindConfigFile(".") if err != nil { // If we can't find a config file, but we were not explicitly requested to, assume it does not exist return nil, nil } } conf, err := config.LoadCreationRuleForFile(configPath, file, kmsEncryptionContext) if err != nil { return nil, err } return conf, nil } func shamirThreshold(c *cli.Context, file string) (int, error) { if c.Int("shamir-secret-sharing-threshold") != 0 { return c.Int("shamir-secret-sharing-threshold"), nil } conf, err := loadConfig(c, file, nil) if conf == nil { // This takes care of the following two case: // 1. No config was provided. Err will be nil and ShamirThreshold will be the default value of 0. // 2. We did find a config file, but failed to load it. In that case the calling function will print the error and exit. return 0, err } return conf.ShamirThreshold, nil } func jsonValueToTreeInsertableValue(jsonValue string) (interface{}, error) { var valueToInsert interface{} err := encodingjson.Unmarshal([]byte(jsonValue), &valueToInsert) if err != nil { return nil, common.NewExitError("Value for --set is not valid JSON", codes.ErrorInvalidSetFormat) } // Check if decoding it as json we find a single value // and not a map or slice, in which case we can't marshal // it to a sops.TreeBranch kind := reflect.ValueOf(valueToInsert).Kind() if kind == reflect.Map || kind == reflect.Slice { var err error valueToInsert, err = (&json.Store{}).LoadPlainFile([]byte(jsonValue)) if err != nil { return nil, common.NewExitError("Invalid --set value format", codes.ErrorInvalidSetFormat) } } // Fix for #461 // Attempt conversion to TreeBranches to handle yaml multidoc. If conversion fails it's // most likely a string value, so just return it as-is. values, ok := valueToInsert.(sops.TreeBranches) if !ok { return valueToInsert, nil } return values[0], nil } func extractSetArguments(set string) (path []interface{}, valueToInsert interface{}, err error) { // Set is a string with the format "python-dict-index json-value" // Since python-dict-index has to end with ], we split at "] " to get the two parts pathValuePair := strings.SplitAfterN(set, "] ", 2) if len(pathValuePair) < 2 { return nil, nil, common.NewExitError("Invalid --set format", codes.ErrorInvalidSetFormat) } fullPath := strings.TrimRight(pathValuePair[0], " ") jsonValue := pathValuePair[1] valueToInsert, err = jsonValueToTreeInsertableValue(jsonValue) path, err = parseTreePath(fullPath) if err != nil { return nil, nil, common.NewExitError("Invalid --set format", codes.ErrorInvalidSetFormat) } return path, valueToInsert, nil } 0707010000002E000081A400000000000000000000000162794F9300000897000000000000000000000000000000000000001E00000000sops-3.7.3/cmd/sops/rotate.gopackage main import ( "fmt" "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/audit" "go.mozilla.org/sops/v3/cmd/sops/codes" "go.mozilla.org/sops/v3/cmd/sops/common" "go.mozilla.org/sops/v3/keys" "go.mozilla.org/sops/v3/keyservice" ) type rotateOpts struct { Cipher sops.Cipher InputStore sops.Store OutputStore sops.Store InputPath string IgnoreMAC bool AddMasterKeys []keys.MasterKey RemoveMasterKeys []keys.MasterKey KeyServices []keyservice.KeyServiceClient } func rotate(opts rotateOpts) ([]byte, error) { tree, err := common.LoadEncryptedFileWithBugFixes(common.GenericDecryptOpts{ Cipher: opts.Cipher, InputStore: opts.InputStore, InputPath: opts.InputPath, IgnoreMAC: opts.IgnoreMAC, KeyServices: opts.KeyServices, }) if err != nil { return nil, err } audit.SubmitEvent(audit.RotateEvent{ File: tree.FilePath, }) _, err = common.DecryptTree(common.DecryptTreeOpts{ Cipher: opts.Cipher, IgnoreMac: opts.IgnoreMAC, Tree: tree, KeyServices: opts.KeyServices, }) if err != nil { return nil, err } // Add new master keys for _, key := range opts.AddMasterKeys { tree.Metadata.KeyGroups[0] = append(tree.Metadata.KeyGroups[0], key) } // Remove master keys for _, rmKey := range opts.RemoveMasterKeys { for i := range tree.Metadata.KeyGroups { for j, groupKey := range tree.Metadata.KeyGroups[i] { if rmKey.ToString() == groupKey.ToString() { tree.Metadata.KeyGroups[i] = append(tree.Metadata.KeyGroups[i][:j], tree.Metadata.KeyGroups[i][j+1:]...) } } } } // Create a new data key dataKey, errs := tree.GenerateDataKeyWithKeyServices(opts.KeyServices) if len(errs) > 0 { err = fmt.Errorf("Could not generate data key: %s", errs) return nil, err } // Reencrypt the file with the new key err = common.EncryptTree(common.EncryptTreeOpts{ DataKey: dataKey, Tree: tree, Cipher: opts.Cipher, }) if err != nil { return nil, err } encryptedFile, err := opts.OutputStore.EmitEncryptedFile(*tree) if err != nil { return nil, common.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree) } return encryptedFile, nil } 0707010000002F000081A400000000000000000000000162794F9300000602000000000000000000000000000000000000001B00000000sops-3.7.3/cmd/sops/set.gopackage main import ( "fmt" "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/cmd/sops/codes" "go.mozilla.org/sops/v3/cmd/sops/common" "go.mozilla.org/sops/v3/keyservice" ) type setOpts struct { Cipher sops.Cipher InputStore sops.Store OutputStore sops.Store InputPath string IgnoreMAC bool TreePath []interface{} Value interface{} KeyServices []keyservice.KeyServiceClient } func set(opts setOpts) ([]byte, error) { // Load the file // TODO: Issue #173: if the file does not exist, create it with the contents passed in as opts.Value tree, err := common.LoadEncryptedFileWithBugFixes(common.GenericDecryptOpts{ Cipher: opts.Cipher, InputStore: opts.InputStore, InputPath: opts.InputPath, IgnoreMAC: opts.IgnoreMAC, KeyServices: opts.KeyServices, }) if err != nil { return nil, err } // Decrypt the file dataKey, err := common.DecryptTree(common.DecryptTreeOpts{ Cipher: opts.Cipher, IgnoreMac: opts.IgnoreMAC, Tree: tree, KeyServices: opts.KeyServices, }) if err != nil { return nil, err } // Set the value tree.Branches[0] = tree.Branches[0].Set(opts.TreePath, opts.Value) err = common.EncryptTree(common.EncryptTreeOpts{ DataKey: dataKey, Tree: tree, Cipher: opts.Cipher, }) if err != nil { return nil, err } encryptedFile, err := opts.OutputStore.EmitEncryptedFile(*tree) if err != nil { return nil, common.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree) } return encryptedFile, err } 07070100000030000041ED00000000000000000000000762794F9300000000000000000000000000000000000000000000001F00000000sops-3.7.3/cmd/sops/subcommand07070100000031000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000002400000000sops-3.7.3/cmd/sops/subcommand/exec07070100000032000081A400000000000000000000000162794F9300000795000000000000000000000000000000000000002C00000000sops-3.7.3/cmd/sops/subcommand/exec/exec.gopackage exec import ( "bytes" "io/ioutil" "os" "runtime" "strings" "go.mozilla.org/sops/v3/logging" "github.com/sirupsen/logrus" ) var log *logrus.Logger func init() { log = logging.NewLogger("EXEC") } type ExecOpts struct { Command string Plaintext []byte Background bool Fifo bool User string Filename string } func GetFile(dir, filename string) *os.File { handle, err := ioutil.TempFile(dir, filename) if err != nil { log.Fatal(err) } return handle } func ExecWithFile(opts ExecOpts) error { if opts.User != "" { SwitchUser(opts.User) } if runtime.GOOS == "windows" && opts.Fifo { log.Warn("no fifos on windows, use --no-fifo next time") opts.Fifo = false } dir, err := ioutil.TempDir("", ".sops") if err != nil { log.Fatal(err) } defer os.RemoveAll(dir) var filename string if opts.Fifo { // fifo handling needs to be async, even opening to write // will block if there is no reader present filename = GetPipe(dir, opts.Filename) go WritePipe(filename, opts.Plaintext) } else { handle := GetFile(dir, opts.Filename) handle.Write(opts.Plaintext) handle.Close() filename = handle.Name() } placeholdered := strings.Replace(opts.Command, "{}", filename, -1) cmd := BuildCommand(placeholdered) cmd.Env = os.Environ() if opts.Background { return cmd.Start() } cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() } func ExecWithEnv(opts ExecOpts) error { if opts.User != "" { SwitchUser(opts.User) } env := os.Environ() lines := bytes.Split(opts.Plaintext, []byte("\n")) for _, line := range lines { if len(line) == 0 { continue } if line[0] == '#' { continue } env = append(env, string(line)) } cmd := BuildCommand(opts.Command) cmd.Env = env if opts.Background { return cmd.Start() } cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() } 07070100000033000081A400000000000000000000000162794F93000003EA000000000000000000000000000000000000003100000000sops-3.7.3/cmd/sops/subcommand/exec/exec_unix.go// +build !windows package exec import ( "os" "os/exec" "os/user" "path/filepath" "strconv" "syscall" ) func BuildCommand(command string) *exec.Cmd { return exec.Command("/bin/sh", "-c", command) } func WritePipe(pipe string, contents []byte) { handle, err := os.OpenFile(pipe, os.O_WRONLY, 0600) if err != nil { os.Remove(pipe) log.Fatal(err) } handle.Write(contents) handle.Close() } func GetPipe(dir, filename string) string { tmpfn := filepath.Join(dir, filename) err := syscall.Mkfifo(tmpfn, 0600) if err != nil { log.Fatal(err) } return tmpfn } func SwitchUser(username string) { user, err := user.Lookup(username) if err != nil { log.Fatal(err) } uid, _ := strconv.Atoi(user.Uid) err = syscall.Setgid(uid) if err != nil { log.Fatal(err) } err = syscall.Setuid(uid) if err != nil { log.Fatal(err) } err = syscall.Setreuid(uid, uid) if err != nil { log.Fatal(err) } err = syscall.Setregid(uid, uid) if err != nil { log.Fatal(err) } } 07070100000034000081A400000000000000000000000162794F93000001AE000000000000000000000000000000000000003400000000sops-3.7.3/cmd/sops/subcommand/exec/exec_windows.gopackage exec import ( "os/exec" ) func BuildCommand(command string) *exec.Cmd { return exec.Command("cmd.exe", "/C", command) } func WritePipe(pipe string, contents []byte) { log.Fatal("fifos are not available on windows") } func GetPipe(dir, filename string) string { log.Fatal("fifos are not available on windows") return "" } func SwitchUser(username string) { log.Fatal("user switching not available on windows") } 07070100000035000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000002600000000sops-3.7.3/cmd/sops/subcommand/groups07070100000036000081A400000000000000000000000162794F93000004DF000000000000000000000000000000000000002D00000000sops-3.7.3/cmd/sops/subcommand/groups/add.gopackage groups import ( "os" "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/cmd/sops/common" "go.mozilla.org/sops/v3/keyservice" ) // AddOpts are the options for adding a key group to a SOPS file type AddOpts struct { InputPath string InputStore sops.Store OutputStore sops.Store Group sops.KeyGroup GroupThreshold int InPlace bool KeyServices []keyservice.KeyServiceClient } // Add adds a key group to a SOPS file func Add(opts AddOpts) error { tree, err := common.LoadEncryptedFile(opts.InputStore, opts.InputPath) if err != nil { return err } dataKey, err := tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices) if err != nil { return err } tree.Metadata.KeyGroups = append(tree.Metadata.KeyGroups, opts.Group) if opts.GroupThreshold != 0 { tree.Metadata.ShamirThreshold = opts.GroupThreshold } tree.Metadata.UpdateMasterKeysWithKeyServices(dataKey, opts.KeyServices) output, err := opts.OutputStore.EmitEncryptedFile(*tree) if err != nil { return err } var outputFile = os.Stdout if opts.InPlace { var err error outputFile, err = os.Create(opts.InputPath) if err != nil { return err } defer outputFile.Close() } outputFile.Write(output) return nil } 07070100000037000081A400000000000000000000000162794F930000064D000000000000000000000000000000000000003000000000sops-3.7.3/cmd/sops/subcommand/groups/delete.gopackage groups import ( "os" "fmt" "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/cmd/sops/common" "go.mozilla.org/sops/v3/keyservice" ) // DeleteOpts are the options for deleting a key group from a SOPS file type DeleteOpts struct { InputPath string InputStore sops.Store OutputStore sops.Store Group uint GroupThreshold int InPlace bool KeyServices []keyservice.KeyServiceClient } // Delete deletes a key group from a SOPS file func Delete(opts DeleteOpts) error { tree, err := common.LoadEncryptedFile(opts.InputStore, opts.InputPath) if err != nil { return err } dataKey, err := tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices) if err != nil { return err } tree.Metadata.KeyGroups = append(tree.Metadata.KeyGroups[:opts.Group], tree.Metadata.KeyGroups[opts.Group+1:]...) if opts.GroupThreshold != 0 { tree.Metadata.ShamirThreshold = opts.GroupThreshold } if len(tree.Metadata.KeyGroups) < tree.Metadata.ShamirThreshold { return fmt.Errorf("removing this key group will make the Shamir threshold impossible to satisfy: "+ "Shamir threshold is %d, but we only have %d key groups", tree.Metadata.ShamirThreshold, len(tree.Metadata.KeyGroups)) } tree.Metadata.UpdateMasterKeysWithKeyServices(dataKey, opts.KeyServices) output, err := opts.OutputStore.EmitEncryptedFile(*tree) if err != nil { return err } var outputFile = os.Stdout if opts.InPlace { var err error outputFile, err = os.Create(opts.InputPath) if err != nil { return err } defer outputFile.Close() } outputFile.Write(output) return nil } 07070100000038000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000002A00000000sops-3.7.3/cmd/sops/subcommand/keyservice07070100000039000081A400000000000000000000000162794F930000041E000000000000000000000000000000000000003800000000sops-3.7.3/cmd/sops/subcommand/keyservice/keyservice.gopackage keyservice import ( "net" "os" "os/signal" "syscall" "go.mozilla.org/sops/v3/keyservice" "go.mozilla.org/sops/v3/logging" "github.com/sirupsen/logrus" "google.golang.org/grpc" ) var log *logrus.Logger func init() { log = logging.NewLogger("KEYSERVICE") } // Opts are the options the key service server can take type Opts struct { Network string Address string Prompt bool } // Run runs a SOPS key service server func Run(opts Opts) error { lis, err := net.Listen(opts.Network, opts.Address) if err != nil { return err } defer lis.Close() grpcServer := grpc.NewServer() keyservice.RegisterKeyServiceServer(grpcServer, keyservice.Server{ Prompt: opts.Prompt, }) log.Infof("Listening on %s://%s", opts.Network, opts.Address) // Close socket if we get killed sigc := make(chan os.Signal, 1) signal.Notify(sigc, os.Interrupt, os.Kill, syscall.SIGTERM) go func(c chan os.Signal) { sig := <-c log.Infof("Caught signal %s: shutting down.", sig) lis.Close() os.Exit(0) }(sigc) return grpcServer.Serve(lis) } 0707010000003A000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000002700000000sops-3.7.3/cmd/sops/subcommand/publish0707010000003B000081A400000000000000000000000162794F93000011C5000000000000000000000000000000000000003200000000sops-3.7.3/cmd/sops/subcommand/publish/publish.gopackage publish import ( "errors" "fmt" "io/ioutil" "path/filepath" "strings" "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/cmd/sops/codes" "go.mozilla.org/sops/v3/cmd/sops/common" "go.mozilla.org/sops/v3/config" "go.mozilla.org/sops/v3/keyservice" "go.mozilla.org/sops/v3/logging" "go.mozilla.org/sops/v3/publish" "go.mozilla.org/sops/v3/version" "github.com/sirupsen/logrus" ) var log *logrus.Logger func init() { log = logging.NewLogger("PUBLISH") } // Opts represents publish options and config type Opts struct { Interactive bool Cipher sops.Cipher ConfigPath string InputPath string KeyServices []keyservice.KeyServiceClient InputStore sops.Store OmitExtensions bool Recursive bool RootPath string } // Run publish operation func Run(opts Opts) error { var fileContents []byte path, err := filepath.Abs(opts.InputPath) if err != nil { return err } conf, err := config.LoadDestinationRuleForFile(opts.ConfigPath, opts.InputPath, make(map[string]*string)) if err != nil { return err } if conf.Destination == nil { return errors.New("no destination configured for this file") } var destinationPath string if opts.Recursive { destinationPath, err = filepath.Rel(opts.RootPath, opts.InputPath) if err != nil { return err } } else { _, destinationPath = filepath.Split(path) } if opts.OmitExtensions || conf.OmitExtensions { destinationPath = strings.TrimSuffix(destinationPath, filepath.Ext(path)) } // Check that this is a sops-encrypted file tree, err := common.LoadEncryptedFile(opts.InputStore, opts.InputPath) if err != nil { return err } data := map[string]interface{}{} switch conf.Destination.(type) { case *publish.S3Destination, *publish.GCSDestination: // Re-encrypt if settings exist to do so if len(conf.KeyGroups[0]) != 0 { log.Debug("Re-encrypting tree before publishing") _, err = common.DecryptTree(common.DecryptTreeOpts{ Cipher: opts.Cipher, IgnoreMac: false, Tree: tree, KeyServices: opts.KeyServices, }) if err != nil { return err } diffs := common.DiffKeyGroups(tree.Metadata.KeyGroups, conf.KeyGroups) keysWillChange := false for _, diff := range diffs { if len(diff.Added) > 0 || len(diff.Removed) > 0 { keysWillChange = true } } if keysWillChange { fmt.Printf("The following changes will be made to the file's key groups:\n") common.PrettyPrintDiffs(diffs) } tree.Metadata = sops.Metadata{ KeyGroups: conf.KeyGroups, UnencryptedSuffix: conf.UnencryptedSuffix, EncryptedSuffix: conf.EncryptedSuffix, Version: version.Version, ShamirThreshold: conf.ShamirThreshold, } dataKey, errs := tree.GenerateDataKeyWithKeyServices(opts.KeyServices) if len(errs) > 0 { err = fmt.Errorf("Could not generate data key: %s", errs) return err } err = common.EncryptTree(common.EncryptTreeOpts{ DataKey: dataKey, Tree: tree, Cipher: opts.Cipher, }) if err != nil { return err } fileContents, err = opts.InputStore.EmitEncryptedFile(*tree) if err != nil { return common.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree) } } else { fileContents, err = ioutil.ReadFile(path) if err != nil { return fmt.Errorf("could not read file: %s", err) } } case *publish.VaultDestination: _, err = common.DecryptTree(common.DecryptTreeOpts{ Cipher: opts.Cipher, IgnoreMac: false, Tree: tree, KeyServices: opts.KeyServices, }) if err != nil { return err } data, err = sops.EmitAsMap(tree.Branches) if err != nil { return err } } if opts.Interactive { var response string for response != "y" && response != "n" { fmt.Printf("uploading %s to %s ? (y/n): ", path, conf.Destination.Path(destinationPath)) _, err := fmt.Scanln(&response) if err != nil { return err } } if response == "n" { msg := fmt.Sprintf("Publication of %s canceled", path) if opts.Recursive { fmt.Println(msg) return nil } else { return errors.New(msg) } } } switch dest := conf.Destination.(type) { case *publish.S3Destination, *publish.GCSDestination: err = dest.Upload(fileContents, destinationPath) case *publish.VaultDestination: err = dest.UploadUnencrypted(data, destinationPath) } if err != nil { return err } return nil } func min(a, b int) int { if a < b { return a } return b } 0707010000003C000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000002A00000000sops-3.7.3/cmd/sops/subcommand/updatekeys0707010000003D000081A400000000000000000000000162794F9300000B9C000000000000000000000000000000000000003800000000sops-3.7.3/cmd/sops/subcommand/updatekeys/updatekeys.gopackage updatekeys import ( "fmt" "log" "os" "path/filepath" "go.mozilla.org/sops/v3/cmd/sops/codes" "go.mozilla.org/sops/v3/cmd/sops/common" "go.mozilla.org/sops/v3/config" "go.mozilla.org/sops/v3/keyservice" ) // Opts represents key operation options and config type Opts struct { InputPath string GroupQuorum int KeyServices []keyservice.KeyServiceClient Interactive bool ConfigPath string } // UpdateKeys update the keys for a given file func UpdateKeys(opts Opts) error { path, err := filepath.Abs(opts.InputPath) if err != nil { return err } info, err := os.Stat(path) if err != nil { return err } if info.IsDir() { return fmt.Errorf("can't operate on a directory") } opts.InputPath = path return updateFile(opts) } func updateFile(opts Opts) error { store := common.DefaultStoreForPath(opts.InputPath) log.Printf("Syncing keys for file %s", opts.InputPath) tree, err := common.LoadEncryptedFile(store, opts.InputPath) if err != nil { return err } conf, err := config.LoadCreationRuleForFile(opts.ConfigPath, opts.InputPath, make(map[string]*string)) if err != nil { return err } diffs := common.DiffKeyGroups(tree.Metadata.KeyGroups, conf.KeyGroups) keysWillChange := false for _, diff := range diffs { if len(diff.Added) > 0 || len(diff.Removed) > 0 { keysWillChange = true } } if !keysWillChange { log.Printf("File %s already up to date", opts.InputPath) return nil } fmt.Printf("The following changes will be made to the file's groups:\n") common.PrettyPrintDiffs(diffs) if opts.Interactive { var response string for response != "y" && response != "n" { fmt.Printf("Is this okay? (y/n):") _, err = fmt.Scanln(&response) if err != nil { return err } } if response == "n" { log.Printf("File %s left unchanged", opts.InputPath) return nil } } key, err := tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices) if err != nil { return common.NewExitError(err, codes.CouldNotRetrieveKey) } tree.Metadata.KeyGroups = conf.KeyGroups if opts.GroupQuorum != 0 { tree.Metadata.ShamirThreshold = opts.GroupQuorum } tree.Metadata.ShamirThreshold = min(tree.Metadata.ShamirThreshold, len(tree.Metadata.KeyGroups)) errs := tree.Metadata.UpdateMasterKeysWithKeyServices(key, opts.KeyServices) if len(errs) > 0 { return fmt.Errorf("error updating one or more master keys: %s", errs) } output, err := store.EmitEncryptedFile(*tree) if err != nil { return common.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree) } outputFile, err := os.Create(opts.InputPath) if err != nil { return fmt.Errorf("could not open file for writing: %s", err) } defer outputFile.Close() _, err = outputFile.Write(output) if err != nil { return fmt.Errorf("error writing to file: %s", err) } log.Printf("File %s synced with new keys", opts.InputPath) return nil } func min(a, b int) int { if a < b { return a } return b } 0707010000003E000041ED00000000000000000000000362794F9300000000000000000000000000000000000000000000001200000000sops-3.7.3/config0707010000003F000081A400000000000000000000000162794F9300002B3E000000000000000000000000000000000000001C00000000sops-3.7.3/config/config.go/* Package config provides a way to find and load SOPS configuration files */ package config //import "go.mozilla.org/sops/v3/config" import ( "fmt" "io/ioutil" "os" "path" "path/filepath" "regexp" "strings" "github.com/sirupsen/logrus" "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/age" "go.mozilla.org/sops/v3/azkv" "go.mozilla.org/sops/v3/gcpkms" "go.mozilla.org/sops/v3/hcvault" "go.mozilla.org/sops/v3/kms" "go.mozilla.org/sops/v3/logging" "go.mozilla.org/sops/v3/pgp" "go.mozilla.org/sops/v3/publish" "gopkg.in/yaml.v3" ) var log *logrus.Logger func init() { log = logging.NewLogger("CONFIG") } type fileSystem interface { Stat(name string) (os.FileInfo, error) } type osFS struct { stat func(string) (os.FileInfo, error) } func (fs osFS) Stat(name string) (os.FileInfo, error) { return fs.stat(name) } var fs fileSystem = osFS{stat: os.Stat} const ( maxDepth = 100 configFileName = ".sops.yaml" ) // FindConfigFile looks for a sops config file in the current working directory and on parent directories, up to the limit defined by the maxDepth constant. func FindConfigFile(start string) (string, error) { filepath := path.Dir(start) for i := 0; i < maxDepth; i++ { _, err := fs.Stat(path.Join(filepath, configFileName)) if err != nil { filepath = path.Join(filepath, "..") } else { return path.Join(filepath, configFileName), nil } } return "", fmt.Errorf("Config file not found") } type configFile struct { CreationRules []creationRule `yaml:"creation_rules"` DestinationRules []destinationRule `yaml:"destination_rules"` } type keyGroup struct { KMS []kmsKey GCPKMS []gcpKmsKey `yaml:"gcp_kms"` AzureKV []azureKVKey `yaml:"azure_keyvault"` Vault []string `yaml:"hc_vault"` Age []string `yaml:"age"` PGP []string } type gcpKmsKey struct { ResourceID string `yaml:"resource_id"` } type kmsKey struct { Arn string `yaml:"arn"` Role string `yaml:"role,omitempty"` Context map[string]*string `yaml:"context"` AwsProfile string `yaml:"aws_profile"` } type azureKVKey struct { VaultURL string `yaml:"vaultUrl"` Key string `yaml:"key"` Version string `yaml:"version"` } type destinationRule struct { PathRegex string `yaml:"path_regex"` S3Bucket string `yaml:"s3_bucket"` S3Prefix string `yaml:"s3_prefix"` GCSBucket string `yaml:"gcs_bucket"` GCSPrefix string `yaml:"gcs_prefix"` VaultPath string `yaml:"vault_path"` VaultAddress string `yaml:"vault_address"` VaultKVMountName string `yaml:"vault_kv_mount_name"` VaultKVVersion int `yaml:"vault_kv_version"` RecreationRule creationRule `yaml:"recreation_rule,omitempty"` OmitExtensions bool `yaml:"omit_extensions"` } type creationRule struct { PathRegex string `yaml:"path_regex"` KMS string AwsProfile string `yaml:"aws_profile"` Age string `yaml:"age"` PGP string GCPKMS string `yaml:"gcp_kms"` AzureKeyVault string `yaml:"azure_keyvault"` VaultURI string `yaml:"hc_vault_transit_uri"` KeyGroups []keyGroup `yaml:"key_groups"` ShamirThreshold int `yaml:"shamir_threshold"` UnencryptedSuffix string `yaml:"unencrypted_suffix"` EncryptedSuffix string `yaml:"encrypted_suffix"` UnencryptedRegex string `yaml:"unencrypted_regex"` EncryptedRegex string `yaml:"encrypted_regex"` } // Load loads a sops config file into a temporary struct func (f *configFile) load(bytes []byte) error { err := yaml.Unmarshal(bytes, f) if err != nil { return fmt.Errorf("Could not unmarshal config file: %s", err) } return nil } // Config is the configuration for a given SOPS file type Config struct { KeyGroups []sops.KeyGroup ShamirThreshold int UnencryptedSuffix string EncryptedSuffix string UnencryptedRegex string EncryptedRegex string Destination publish.Destination OmitExtensions bool } func getKeyGroupsFromCreationRule(cRule *creationRule, kmsEncryptionContext map[string]*string) ([]sops.KeyGroup, error) { var groups []sops.KeyGroup if len(cRule.KeyGroups) > 0 { for _, group := range cRule.KeyGroups { var keyGroup sops.KeyGroup for _, k := range group.Age { keys, err := age.MasterKeysFromRecipients(k) if err != nil { return nil, err } for _, key := range keys { keyGroup = append(keyGroup, key) } } for _, k := range group.PGP { keyGroup = append(keyGroup, pgp.NewMasterKeyFromFingerprint(k)) } for _, k := range group.KMS { keyGroup = append(keyGroup, kms.NewMasterKey(k.Arn, k.Role, k.Context)) } for _, k := range group.GCPKMS { keyGroup = append(keyGroup, gcpkms.NewMasterKeyFromResourceID(k.ResourceID)) } for _, k := range group.AzureKV { keyGroup = append(keyGroup, azkv.NewMasterKey(k.VaultURL, k.Key, k.Version)) } for _, k := range group.Vault { if masterKey, err := hcvault.NewMasterKeyFromURI(k); err == nil { keyGroup = append(keyGroup, masterKey) } else { return nil, err } } groups = append(groups, keyGroup) } } else { var keyGroup sops.KeyGroup if cRule.Age != "" { ageKeys, err := age.MasterKeysFromRecipients(cRule.Age) if err != nil { return nil, err } else { for _, ak := range ageKeys { keyGroup = append(keyGroup, ak) } } } for _, k := range pgp.MasterKeysFromFingerprintString(cRule.PGP) { keyGroup = append(keyGroup, k) } for _, k := range kms.MasterKeysFromArnString(cRule.KMS, kmsEncryptionContext, cRule.AwsProfile) { keyGroup = append(keyGroup, k) } for _, k := range gcpkms.MasterKeysFromResourceIDString(cRule.GCPKMS) { keyGroup = append(keyGroup, k) } azureKeys, err := azkv.MasterKeysFromURLs(cRule.AzureKeyVault) if err != nil { return nil, err } for _, k := range azureKeys { keyGroup = append(keyGroup, k) } vaultKeys, err := hcvault.NewMasterKeysFromURIs(cRule.VaultURI) if err != nil { return nil, err } for _, k := range vaultKeys { keyGroup = append(keyGroup, k) } groups = append(groups, keyGroup) } return groups, nil } func loadConfigFile(confPath string) (*configFile, error) { confBytes, err := ioutil.ReadFile(confPath) if err != nil { return nil, fmt.Errorf("could not read config file: %s", err) } conf := &configFile{} err = conf.load(confBytes) if err != nil { return nil, fmt.Errorf("error loading config: %s", err) } return conf, nil } func configFromRule(rule *creationRule, kmsEncryptionContext map[string]*string) (*Config, error) { cryptRuleCount := 0 if rule.UnencryptedSuffix != "" { cryptRuleCount++ } if rule.EncryptedSuffix != "" { cryptRuleCount++ } if rule.EncryptedRegex != "" { cryptRuleCount++ } if cryptRuleCount > 1 { return nil, fmt.Errorf("error loading config: cannot use more than one of encrypted_suffix, unencrypted_suffix, or encrypted_regex for the same rule") } groups, err := getKeyGroupsFromCreationRule(rule, kmsEncryptionContext) if err != nil { return nil, err } return &Config{ KeyGroups: groups, ShamirThreshold: rule.ShamirThreshold, UnencryptedSuffix: rule.UnencryptedSuffix, EncryptedSuffix: rule.EncryptedSuffix, UnencryptedRegex: rule.UnencryptedRegex, EncryptedRegex: rule.EncryptedRegex, }, nil } func parseDestinationRuleForFile(conf *configFile, filePath string, kmsEncryptionContext map[string]*string) (*Config, error) { var rule *creationRule var dRule *destinationRule if len(conf.DestinationRules) > 0 { for _, r := range conf.DestinationRules { if r.PathRegex == "" { dRule = &r rule = &dRule.RecreationRule break } if r.PathRegex != "" { if match, _ := regexp.MatchString(r.PathRegex, filePath); match { dRule = &r rule = &dRule.RecreationRule break } } } } if dRule == nil { return nil, fmt.Errorf("error loading config: no matching destination found in config") } var dest publish.Destination if dRule != nil { if dRule.S3Bucket != "" && dRule.GCSBucket != "" && dRule.VaultPath != "" { return nil, fmt.Errorf("error loading config: more than one destinations were found in a single destination rule, you can only use one per rule") } if dRule.S3Bucket != "" { dest = publish.NewS3Destination(dRule.S3Bucket, dRule.S3Prefix) } if dRule.GCSBucket != "" { dest = publish.NewGCSDestination(dRule.GCSBucket, dRule.GCSPrefix) } if dRule.VaultPath != "" { dest = publish.NewVaultDestination(dRule.VaultAddress, dRule.VaultPath, dRule.VaultKVMountName, dRule.VaultKVVersion) } } config, err := configFromRule(rule, kmsEncryptionContext) if err != nil { return nil, err } config.Destination = dest config.OmitExtensions = dRule.OmitExtensions return config, nil } func parseCreationRuleForFile(conf *configFile, confPath, filePath string, kmsEncryptionContext map[string]*string) (*Config, error) { // If config file doesn't contain CreationRules (it's empty or only contains DestionationRules), assume it does not exist if conf.CreationRules == nil { return nil, nil } configDir, err := filepath.Abs(filepath.Dir(confPath)) if err != nil { return nil, err } // compare file path relative to path of config file filePath = strings.TrimPrefix(filePath, configDir + string(filepath.Separator)) var rule *creationRule for _, r := range conf.CreationRules { if r.PathRegex == "" { rule = &r break } reg, err := regexp.Compile(r.PathRegex) if err != nil { return nil, fmt.Errorf("can not compile regexp: %w", err) } if reg.MatchString(filePath) { rule = &r break } } if rule == nil { return nil, fmt.Errorf("error loading config: no matching creation rules found") } config, err := configFromRule(rule, kmsEncryptionContext) if err != nil { return nil, err } return config, nil } // LoadCreationRuleForFile load the configuration for a given SOPS file from the config file at confPath. A kmsEncryptionContext // should be provided for configurations that do not contain key groups, as there's no way to specify context inside // a SOPS config file outside of key groups. func LoadCreationRuleForFile(confPath string, filePath string, kmsEncryptionContext map[string]*string) (*Config, error) { conf, err := loadConfigFile(confPath) if err != nil { return nil, err } return parseCreationRuleForFile(conf, confPath, filePath, kmsEncryptionContext) } // LoadDestinationRuleForFile works the same as LoadCreationRuleForFile, but gets the "creation_rule" from the matching destination_rule's // "recreation_rule". func LoadDestinationRuleForFile(confPath string, filePath string, kmsEncryptionContext map[string]*string) (*Config, error) { conf, err := loadConfigFile(confPath) if err != nil { return nil, err } return parseDestinationRuleForFile(conf, filePath, kmsEncryptionContext) } 07070100000040000081A400000000000000000000000162794F9300003113000000000000000000000000000000000000002100000000sops-3.7.3/config/config_test.gopackage config import ( "os" "path" "testing" "github.com/stretchr/testify/assert" ) type mockFS struct { stat func(string) (os.FileInfo, error) } func (fs mockFS) Stat(name string) (os.FileInfo, error) { return fs.stat(name) } func TestFindConfigFileRecursive(t *testing.T) { expectedPath := path.Clean("./../../.sops.yaml") fs = mockFS{stat: func(name string) (os.FileInfo, error) { if name == expectedPath { return nil, nil } return nil, &os.PathError{} }} filepath, err := FindConfigFile(".") assert.Nil(t, err) assert.Equal(t, expectedPath, filepath) } func TestFindConfigFileCurrentDir(t *testing.T) { expectedPath := path.Clean(".sops.yaml") fs = mockFS{stat: func(name string) (os.FileInfo, error) { if name == expectedPath { return nil, nil } return nil, &os.PathError{} }} filepath, err := FindConfigFile(".") assert.Nil(t, err) assert.Equal(t, expectedPath, filepath) } var sampleConfig = []byte(` creation_rules: - path_regex: foobar* kms: "1" pgp: "2" gcp_kms: "3" hc_vault_transit_uri: http://4:8200/v1/4/keys/4 - path_regex: "" kms: foo pgp: bar gcp_kms: baz hc_vault_transit_uri: http://127.0.1.1/v1/baz/keys/baz `) var sampleConfigWithPath = []byte(` creation_rules: - path_regex: foo/bar* kms: "1" pgp: "2" gcp_kms: "3" hc_vault_uris: http://4:8200/v1/4/keys/4 - path_regex: somefilename.yml kms: bilbo pgp: baggins gcp_kms: precious hc_vault_uris: https://pluto/v1/pluto/keys/pluto - path_regex: "" kms: foo pgp: bar gcp_kms: baz hc_vault_uris: https://foz:443/v1/foz/keys/foz `) var sampleConfigWithAmbiguousPath = []byte(` creation_rules: - path_regex: foo/* kms: "1" pgp: "2" gcp_kms: "3" hc_vault_uris: http://4:8200/v1/4/keys/4 `) var sampleConfigWithGroups = []byte(` creation_rules: - path_regex: foobar* kms: "1" pgp: "2" - path_regex: "" key_groups: - kms: - arn: foo pgp: - bar gcp_kms: - resource_id: foo azure_keyvault: - vaultUrl: https://foo.vault.azure.net key: foo-key version: fooversion hc_vault: - 'https://foo.vault:8200/v1/foo/keys/foo-key' - kms: - arn: baz pgp: - qux gcp_kms: - resource_id: bar - resource_id: baz azure_keyvault: - vaultUrl: https://bar.vault.azure.net key: bar-key version: barversion hc_vault: - 'https://baz.vault:8200/v1/baz/keys/baz-key' `) var sampleConfigWithSuffixParameters = []byte(` creation_rules: - path_regex: foobar* kms: "1" pgp: "2" unencrypted_suffix: _unencrypted - path_regex: bar?foo$ encrypted_suffix: _enc key_groups: - kms: - arn: baz pgp: - qux gcp_kms: - resource_id: bar - resource_id: baz azure_keyvault: - vaultUrl: https://foo.vault.azure.net key: foo-key version: fooversion `) var sampleConfigWithRegexParameters = []byte(` creation_rules: - path_regex: barbar* kms: "1" pgp: "2" encrypted_regex: "^enc:" unencrypted_regex: "^dec:" `) var sampleConfigWithInvalidParameters = []byte(` creation_rules: - path_regex: foobar* kms: "1" pgp: "2" hc_vault_uris: "https://vault.com/v1/bug/keys/pr" unencrypted_suffix: _unencrypted encrypted_suffix: _enc `) var sampleConfigWithNoMatchingRules = []byte(` creation_rules: - path_regex: notexisting pgp: bar `) var sampleEmptyConfig = []byte(``) var sampleConfigWithEmptyCreationRules = []byte(` creation_rules: `) var sampleConfigWithOnlyDestinationRules = []byte(` destination_rules: - path_regex: "" s3_bucket: "foobar" s3_prefix: "test/" recreation_rule: pgp: newpgp `) var sampleConfigWithDestinationRule = []byte(` creation_rules: - path_regex: foobar* kms: "1" pgp: "2" gcp_kms: "3" - path_regex: "" kms: foo pgp: bar gcp_kms: baz destination_rules: - path_regex: "" s3_bucket: "foobar" s3_prefix: "test/" recreation_rule: pgp: newpgp `) var sampleConfigWithVaultDestinationRules = []byte(` creation_rules: - path_regex: foobar* kms: "1" pgp: "2" gcp_kms: "3" - path_regex: "" kms: foo pgp: bar gcp_kms: baz destination_rules: - vault_path: "foobar/" path_regex: "vault-v2/*" - vault_path: "barfoo/" vault_kv_mount_name: "kv/" vault_kv_version: 1 path_regex: "vault-v1/*" `) var sampleConfigWithInvalidComplicatedRegexp = []byte(` creation_rules: - path_regex: "[ ]\\K(?<!\\d )(?=" kms: default `) var sampleConfigWithComplicatedRegexp = []byte(` creation_rules: - path_regex: "stage/dev/feature-.*" kms: dev-feature - path_regex: "stage/dev/.*" kms: dev - path_regex: "stage/staging/.*" kms: staging - path_regex: "stage/.*/.*" kms: default `) func parseConfigFile(confBytes []byte, t *testing.T) *configFile { conf := &configFile{} err := conf.load(confBytes) assert.Nil(t, err) return conf } func TestLoadConfigFile(t *testing.T) { expected := configFile{ CreationRules: []creationRule{ { PathRegex: "foobar*", KMS: "1", PGP: "2", GCPKMS: "3", VaultURI: "http://4:8200/v1/4/keys/4", }, { PathRegex: "", KMS: "foo", PGP: "bar", GCPKMS: "baz", VaultURI: "http://127.0.1.1/v1/baz/keys/baz", }, }, } conf := configFile{} err := conf.load(sampleConfig) assert.Nil(t, err) assert.Equal(t, expected, conf) } func TestLoadConfigFileWithGroups(t *testing.T) { expected := configFile{ CreationRules: []creationRule{ { PathRegex: "foobar*", KMS: "1", PGP: "2", }, { PathRegex: "", KeyGroups: []keyGroup{ { KMS: []kmsKey{{Arn: "foo"}}, PGP: []string{"bar"}, GCPKMS: []gcpKmsKey{{ResourceID: "foo"}}, AzureKV: []azureKVKey{{VaultURL: "https://foo.vault.azure.net", Key: "foo-key", Version: "fooversion"}}, Vault: []string{"https://foo.vault:8200/v1/foo/keys/foo-key"}, }, { KMS: []kmsKey{{Arn: "baz"}}, PGP: []string{"qux"}, GCPKMS: []gcpKmsKey{ {ResourceID: "bar"}, {ResourceID: "baz"}, }, AzureKV: []azureKVKey{{VaultURL: "https://bar.vault.azure.net", Key: "bar-key", Version: "barversion"}}, Vault: []string{"https://baz.vault:8200/v1/baz/keys/baz-key"}, }, }, }, }, } conf := configFile{} err := conf.load(sampleConfigWithGroups) assert.Nil(t, err) assert.Equal(t, expected, conf) } func TestLoadConfigFileWithNoMatchingRules(t *testing.T) { _, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithNoMatchingRules, t), "/conf/path", "foobar2000", nil) assert.NotNil(t, err) } func TestLoadConfigFileWithInvalidComplicatedRegexp(t *testing.T) { conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithInvalidComplicatedRegexp, t), "/conf/path", "stage/prod/api.yml", nil) assert.Equal(t, "can not compile regexp: error parsing regexp: invalid escape sequence: `\\K`", err.Error()) assert.Nil(t, conf) } func TestLoadConfigFileWithComplicatedRegexp(t *testing.T) { for filePath, k := range map[string]string{ "stage/prod/api.yml": "default", "stage/dev/feature-foo.yml": "dev-feature", "stage/dev/api.yml": "dev", } { conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithComplicatedRegexp, t), "/conf/path", filePath, nil) assert.Nil(t, err) assert.Equal(t, k, conf.KeyGroups[0][0].ToString()) } } func TestLoadEmptyConfigFile(t *testing.T) { conf, err := parseCreationRuleForFile(parseConfigFile(sampleEmptyConfig, t), "/conf/path", "foobar2000", nil) assert.Nil(t, conf) assert.Nil(t, err) } func TestLoadConfigFileWithEmptyCreationRules(t *testing.T) { conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithEmptyCreationRules, t), "/conf/path", "foobar2000", nil) assert.Nil(t, conf) assert.Nil(t, err) } func TestLoadConfigFileWithOnlyDestinationRules(t *testing.T) { conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithOnlyDestinationRules, t), "/conf/path", "foobar2000", nil) assert.Nil(t, conf) assert.Nil(t, err) } func TestKeyGroupsForFile(t *testing.T) { conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfig, t), "/conf/path", "foobar2000", nil) assert.Nil(t, err) assert.Equal(t, "2", conf.KeyGroups[0][0].ToString()) assert.Equal(t, "1", conf.KeyGroups[0][1].ToString()) conf, err = parseCreationRuleForFile(parseConfigFile(sampleConfig, t), "/conf/path", "whatever", nil) assert.Nil(t, err) assert.Equal(t, "bar", conf.KeyGroups[0][0].ToString()) assert.Equal(t, "foo", conf.KeyGroups[0][1].ToString()) } func TestKeyGroupsForFileWithPath(t *testing.T) { conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithPath, t), "/conf/path", "foo/bar2000", nil) assert.Nil(t, err) assert.Equal(t, "2", conf.KeyGroups[0][0].ToString()) assert.Equal(t, "1", conf.KeyGroups[0][1].ToString()) conf, err = parseCreationRuleForFile(parseConfigFile(sampleConfigWithPath, t), "/conf/path", "somefilename.yml", nil) assert.Nil(t, err) assert.Equal(t, "baggins", conf.KeyGroups[0][0].ToString()) assert.Equal(t, "bilbo", conf.KeyGroups[0][1].ToString()) conf, err = parseCreationRuleForFile(parseConfigFile(sampleConfig, t), "/conf/path", "whatever", nil) assert.Nil(t, err) assert.Equal(t, "bar", conf.KeyGroups[0][0].ToString()) assert.Equal(t, "foo", conf.KeyGroups[0][1].ToString()) } func TestKeyGroupsForFileWithGroups(t *testing.T) { conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithGroups, t), "/conf/path", "whatever", nil) assert.Nil(t, err) assert.Equal(t, "bar", conf.KeyGroups[0][0].ToString()) assert.Equal(t, "foo", conf.KeyGroups[0][1].ToString()) assert.Equal(t, "qux", conf.KeyGroups[1][0].ToString()) assert.Equal(t, "baz", conf.KeyGroups[1][1].ToString()) } func TestLoadConfigFileWithUnencryptedSuffix(t *testing.T) { conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithSuffixParameters, t), "/conf/path", "foobar", nil) assert.Nil(t, err) assert.Equal(t, "_unencrypted", conf.UnencryptedSuffix) } func TestLoadConfigFileWithEncryptedSuffix(t *testing.T) { conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithSuffixParameters, t), "/conf/path", "barfoo", nil) assert.Nil(t, err) assert.Equal(t, "_enc", conf.EncryptedSuffix) } func TestLoadConfigFileWithUnencryptedRegex(t *testing.T) { conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithRegexParameters, t), "/conf/path", "barbar", nil) assert.Equal(t, nil, err) assert.Equal(t, "^dec:", conf.UnencryptedRegex) } func TestLoadConfigFileWithEncryptedRegex(t *testing.T) { conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithRegexParameters, t), "/conf/path", "barbar", nil) assert.Equal(t, nil, err) assert.Equal(t, "^enc:", conf.EncryptedRegex) } func TestLoadConfigFileWithInvalidParameters(t *testing.T) { _, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithInvalidParameters, t), "/conf/path", "foobar", nil) assert.NotNil(t, err) } func TestLoadConfigFileWithAmbiguousPath(t *testing.T) { config := parseConfigFile(sampleConfigWithAmbiguousPath, t) _, err := parseCreationRuleForFile(config, "/foo/config", "/foo/foo/bar", nil) assert.Nil(t, err) _, err = parseCreationRuleForFile(config, "/foo/config", "/foo/fuu/bar", nil) assert.NotNil(t, err) } func TestLoadConfigFileWithDestinationRule(t *testing.T) { conf, err := parseDestinationRuleForFile(parseConfigFile(sampleConfigWithDestinationRule, t), "barfoo", nil) assert.Nil(t, err) assert.Equal(t, "newpgp", conf.KeyGroups[0][0].ToString()) assert.NotNil(t, conf.Destination) assert.Equal(t, "s3://foobar/test/barfoo", conf.Destination.Path("barfoo")) } func TestLoadConfigFileWithVaultDestinationRules(t *testing.T) { conf, err := parseDestinationRuleForFile(parseConfigFile(sampleConfigWithVaultDestinationRules, t), "vault-v2/barfoo", nil) assert.Nil(t, err) assert.NotNil(t, conf.Destination) assert.Contains(t, conf.Destination.Path("barfoo"), "/v1/secret/data/foobar/barfoo") conf, err = parseDestinationRuleForFile(parseConfigFile(sampleConfigWithVaultDestinationRules, t), "vault-v1/barfoo", nil) assert.Nil(t, err) assert.NotNil(t, conf.Destination) assert.Contains(t, conf.Destination.Path("barfoo"), "/v1/kv/barfoo/barfoo") } 07070100000041000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000002100000000sops-3.7.3/config/test_resources07070100000042000081A400000000000000000000000162794F9300000B59000000000000000000000000000000000000002E00000000sops-3.7.3/config/test_resources/example.yamlexample_key: ENC[AES256_GCM,data:mOtEkS9nmmncpxRBYA==,iv:ecPCSny/r62lJr2DuwvucwK/SGiAWLSbariWob7r4Ek=,tag:3m8Dul5yxqEmunMJom+Cag==,type:str] example_array: - ENC[AES256_GCM,data:vYoTCRm2gudQ/XFkIm8=,iv:DOM9XZRFFdty4kjMWvqT4UCiwHr5sd7Z3CiS6Xg4q8Q=,tag:07gBVF4mSeG/7Xs+qBeMew==,type:str] - ENC[AES256_GCM,data:Wf3LVG1677UaZSzzIYc=,iv:8sQd09YT7BkJSntvKUudfXtjPyqzsPqayu0Qo8v+SHI=,tag:zVz8aWLVox40iEKOSUGW+Q==,type:str] example_multiline: |- ENC[AES256_GCM,data:Jw34myS8s3NPrOnPnBdJ67bVyNxKmq7pmp0=,iv:8pjw5k5XpV2x4bM2jpISMHy97b6ffkCFvbtKOiZ22VU=,tag:V3NFD/WtgBDXujNTxst1HQ==,type:str] example_number: ENC[AES256_GCM,data:/KVkicl+xk6x,iv:M+qJNRywTlnkk28nLV3aO6pszNoLcNlrSs05aPIwgwE=,tag:Zku+tQMIeF/Sqnuy8QW4zg==,type:float] example: nested: values: ENC[AES256_GCM,data:y/Gbd4dFl8X4,iv:zOKYeVFBLEoliQ/WuxgNwui+9Vy1CcR+Eg9Zrs9d2qs=,tag:UUC/23KmH4ZGPT+dhHBYGg==,type:str] example_booleans: - ENC[AES256_GCM,data:RYzb/A==,iv:7cQpPBZ/0Woa5X3kdlx+4pOW15EmJMXopelmOGfYd1w=,tag:RT0L/KZJTrHQ9E3fgFfqhA==,type:bool] - ENC[AES256_GCM,data:H/wjpX4=,iv:EDRADHc4Z65yJzXHpVsWgaqcNPhwEdmSNeRtmi14p0g=,tag:ED3o+9Yv4NkoMg2BeS03Cw==,type:bool] sops: lastmodified: '2016-08-02T18:34:06Z' attention: This section contains key material that should only be modified with extra care. See `sops -h`. unencrypted_suffix: _unencrypted mac: ENC[AES256_GCM,data:n6bRU4NrPaMvQj0nI7j7x+Ihkdvr61SDYKw7atVeTDGj+puvd+/0DuFyeWJADNtlAZq7pasSobUMuA66MrNpavMLc5/tBX+NZbT1cbGD0nBt4y7ztog1MFSay03CDrhbepmWNSs8ihgYtHfxfGiqzTU5sRTM+iEHAiwJ1h7eU4k=,iv:oqO1KMNkWUC+aAxgNV4Kng4CBWzO7wIIswDwbRvlv8g=,tag:uUmgKKFeNGvAzZZLI0RViA==,type:str] version: '1.13' pgp: - fp: E5297818703249D0C60E19E6824612478D1A4CCD created_at: '2016-08-02T18:34:06Z' enc: | -----BEGIN PGP MESSAGE----- Version: GnuPG v1 hQIMA2X8rvoeiASBAQ/+MYitjFEMfLz5N6JeZ0IoYAbpJZPWkUavhXF9H4SU9mYi 8Tp4KgJVRhv7IOsqmuMrxIWZL9++HqEpe+HWWy6luzPrWU7HIPpwGcYRCyWrdt2u UjBVYMVgLwGMfrWe6sr0yWrVXrLQ2ltPBn8J5vyHDZUYXY33B1vj7F2BuF2oL32J f9ZTAikiHJI/4Q62P5x7RQrqHqV/Ncczf+w/ni+ZnOwH5kay306DKNwTX64AWoc6 iW/nPCRzIfhWx89BPu+SwGaEwQbHvS7zMLZ/UtarNWAFQ5QmlBW6dyH9Edj5bA7u BuhxjBT/6ZaXX6iSAl98021eplf6s945vB7gPuzrgW1LM4t6X8M9P0KMMrDujz/S cm7Vrhm//syT2IJqFR9x/PylbpHhVTwTWZIFvLRRJlM66NYsvxMvJdknovN/889D I+mQkA8oxtjyof8fgIOvU7ph6yBzRbb838a0Fyu5U5wwrNzBc5N23WNjzNcT1XRy kl7W7MElHnjzq2jNJIB+XipaV6uoMbaoiYZNi+bpHj6pASCaBko7c8aPYVLruPbZ G3fCMY1mxwRBYyn6d1sTqzVGR4WBJLc+4ikm+H4O4aa4sv137/cnRRbVNhpgefob H9SSjSr2gr9YiHgMsG0kdilyTnccYaeljBqOJKUKiWOPk8OBiycNtU+rESagw5rS XgHpMul9uyxksAQf7VpXJJQYYgk+DlZhkkd/hVu59vxJo2oxxp5aGyBijGSy7mcI 3FajmsEFKdPaHpbsSDP0/83RHIwVwbQK5N+IcNGHk/rnh7XEJ73vtrsA9muAmoo= =OxaE -----END PGP MESSAGE----- 07070100000043000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001300000000sops-3.7.3/decrypt07070100000044000081A400000000000000000000000162794F930000090C000000000000000000000000000000000000001E00000000sops-3.7.3/decrypt/decrypt.go/* Package decrypt is the external API other Go programs can use to decrypt SOPS files. It is the only package in SOPS with a stable API. */ package decrypt // import "go.mozilla.org/sops/v3/decrypt" import ( "fmt" "io/ioutil" "time" "go.mozilla.org/sops/v3/aes" "go.mozilla.org/sops/v3/cmd/sops/common" . "go.mozilla.org/sops/v3/cmd/sops/formats" // Re-export ) // File is a wrapper around Data that reads a local encrypted // file and returns its cleartext data in an []byte func File(path, format string) (cleartext []byte, err error) { // Read the file into an []byte encryptedData, err := ioutil.ReadFile(path) if err != nil { return nil, fmt.Errorf("Failed to read %q: %w", path, err) } // uses same logic as cli. formatFmt := FormatForPathOrString(path, format) return DataWithFormat(encryptedData, formatFmt) } // DataWithFormat is a helper that takes encrypted data, and a format enum value, // decrypts the data and returns its cleartext in an []byte. func DataWithFormat(data []byte, format Format) (cleartext []byte, err error) { store := common.StoreForFormat(format) // Load SOPS file and access the data key tree, err := store.LoadEncryptedFile(data) if err != nil { return nil, err } key, err := tree.Metadata.GetDataKey() if err != nil { return nil, err } // Decrypt the tree cipher := aes.NewCipher() mac, err := tree.Decrypt(key, cipher) if err != nil { return nil, err } // Compute the hash of the cleartext tree and compare it with // the one that was stored in the document. If they match, // integrity was preserved originalMac, err := cipher.Decrypt( tree.Metadata.MessageAuthenticationCode, key, tree.Metadata.LastModified.Format(time.RFC3339), ) if originalMac != mac { return nil, fmt.Errorf("Failed to verify data integrity. expected mac %q, got %q", originalMac, mac) } return store.EmitPlainFile(tree.Branches) } // Data is a helper that takes encrypted data and a format string, // decrypts the data and returns its cleartext in an []byte. // The format string can be `json`, `yaml`, `ini`, `dotenv` or `binary`. // If the format string is empty, binary format is assumed. func Data(data []byte, format string) (cleartext []byte, err error) { formatFmt := FormatFromString(format) return DataWithFormat(data, formatFmt) } 07070100000045000081A400000000000000000000000162794F9300000522000000000000000000000000000000000000002300000000sops-3.7.3/decrypt/example_test.gopackage decrypt import ( "encoding/json" "go.mozilla.org/sops/v3/logging" "github.com/sirupsen/logrus" ) var log *logrus.Logger func init() { log = logging.NewLogger("DECRYPT") } type configuration struct { FirstName string `json:"firstName"` LastName string `json:"lastName"` Age float64 `json:"age"` Address struct { City string `json:"city"` PostalCode string `json:"postalCode"` State string `json:"state"` StreetAddress string `json:"streetAddress"` } `json:"address"` PhoneNumbers []struct { Number string `json:"number"` Type string `json:"type"` } `json:"phoneNumbers"` AnEmptyValue string `json:"anEmptyValue"` } func ExampleDecryptFile() { var ( confPath string = "./example.json" cfg configuration err error ) confData, err := File(confPath, "json") if err != nil { log.Fatalf("cleartext configuration marshalling failed with error: %v", err) } err = json.Unmarshal(confData, &cfg) if err != nil { log.Fatalf("cleartext configuration unmarshalling failed with error: %v", err) } if cfg.FirstName != "John" || cfg.LastName != "Smith" || cfg.Age != 25.4 || cfg.PhoneNumbers[1].Number != "646 555-4567" { log.Fatalf("configuration does not contain expected values: %+v", cfg) } log.Printf("%+v", cfg) } 07070100000046000081A400000000000000000000000162794F9300000C64000000000000000000000000000000000000001700000000sops-3.7.3/example.ini; ENC[AES256_GCM,data:EexYkXJHDv1E9WwU8wUrBakxAZcnlIkAJQgRFts3alF+5w==,iv:/wd7bCxSkJ85eG69NMq2RH70Dlw5q/diL42GcdTbRYs=,tag:8W4B3JjLYAtJgG3Zi6Hu+A==,type:comment] [name] firstName = ENC[AES256_GCM,data:7cWtsg==,iv:rh/1l0yiOE3JmmSJB9CXp4Ctb5en0FcNUZZkLyWsX3c=,tag:d7We6RtSs3AYhA6jan/CoA==,type:str] lastName = ENC[AES256_GCM,data:Iq50v00=,iv:o+bfCsNBsv/+XdXS4M7TCOTPzG38ft14/Q9/vZvnNvM=,tag:cRivFOZyIpMDXdKA9R+6mA==,type:str] age = ENC[AES256_GCM,data:aSt9Fg==,iv:z1NjXdRRFs8+dAFkrngS7PyhUSCOTzAmxtzCcfafkAM=,tag:6kpo65TxMu1oKF9d7L1wJg==,type:str] [address] city = ENC[AES256_GCM,data:3hQ6RXAc8ik=,iv:ajAjhQIJiNWy9PGj8ZCI5k+3uw7igweqZ+eiZ9tuvG0=,tag:bGX7PK+/hTQE/1uYmXexyA==,type:str] postalCode = ENC[AES256_GCM,data:1Ha0avZTe6IflQ==,iv:l8rbQbhSwxr3AJzqe/H8ALrHcXsntcBfkyUGt9/6k/U=,tag:f6s6ObY5QaqmG4CUpV+UKg==,type:str] state = ENC[AES256_GCM,data:ba0=,iv:P/VsGYbhT8ihRvluWc8HcUDPPigh/IcHXBnj0Xb86AE=,tag:JN7Y77eOaF1+tJjN2cxtnQ==,type:str] streetAddress = ENC[AES256_GCM,data:LYYluDyJzryoYT2Nkg==,iv:KTmXPVV5tSY8/piTb/uPcfJL2mUqkS1aUI9pukQm1Dc=,tag:AzdSsRjEWx8kzclSZIdIcA==,type:str] [phoneNumbers] home = ENC[AES256_GCM,data:wv3pG8J7wmiotVY6,iv:YdX9RKlN9t0PohEu+Dxws0POUf8hjDMi4PJre+4/lsg=,tag:2XadWng1DxnfMituylFC5g==,type:str] office = ENC[AES256_GCM,data:Hu3X626TUgV3Ix7i,iv:P1SBjvZPogJih4KLFLcbxeWeZT2aTNXPBtwlC9yZZ70=,tag:4SOkQZw4ynKfrFwrmre6Lw==,type:str] [not private] notsecret_unencrypted = hi there! [sops] pgp__list_0__map_fp = FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4 pgp__list_1__map_created_at = 2019-12-10T22:45:52Z pgp__list_1__map_fp = D7229043384BCC60326C6FB9D8720D957C3D3074 lastmodified = 2019-12-10T22:45:52Z pgp__list_0__map_created_at = 2019-12-10T22:45:52Z pgp__list_0__map_enc = -----BEGIN PGP MESSAGE-----\n\nwcBMAyUpShfNkFB/AQgAGb6tDB1gDtwLcVuENgd/AU1ZjxDbUY46OJzi1fkQ4jyo\nvxfCviqfslYyncVrKab0S9U+alIu8Po8BrHCTwmDkcnPG7HWMcqRo+Nq3JShir3y\ntVMbHuRGykPmGjs7GOUn1WibOisiJjHzLdFOsyNexveBV9AVln7kZazCRlXwbLqc\nyJ9IQsWi1ET6DjtViejiBKblDzZ87WgvL2Z0E8QfYXmrB3GpFa+K4Aqgb3pHkQQo\nGQArNAe8BmrhEXotj0IS/K3v2wjX5Sqli+0dh4qhaXiIeYb16nfc4M1TUo4Vvj7Q\nyWdjufTi+2RIxxUEkIdRg6lDVabm98hLn6/jsQ5c5tLgAeT99N9te4j1eeO62xwy\nz4S+4Xok4Cng9OFHFODz4hh6ah3gYuXRf4qFh++GUaxGwG/iGNSiY7A9eWg1gM+N\nPB6MtiOnhuB15N5mbD2V1xrzlF0fu0K98Vviv75h4eGzYQA=\n=RaGg\n-----END PGP MESSAGE----- mac = ENC[AES256_GCM,data:5NouZe0uY8581eqXLe31TWYqThPlfePdpIyYDBgj3FwGvPfhiWB+dn/diSt4NpZ9zUNI4VhF2cmNesk8NgZdUK9/N9WbGgmJjjdBXvo0xhvr8RR22AiVCB9ipDlSFqnOMyJzo+CAvpSDbwF1bMU0qxwKW+zpDZV2uLX+px7b2qM=,iv:em3/lIV7eYA46u2kM0bR1/3O6Ga2vRfS5/r2qh0jges=,tag:ow9277u7mFNcs4IdsU4YUQ==,type:str] pgp__list_1__map_enc = -----BEGIN PGP MESSAGE-----\n\nwYwDXFUltYFwV4MBBAAQYkLcQ1MORGYYprtFwUZPO2J3CBOU4u76qJQod5JXZuAa\n3aCynkyrOHIpgh2cKoyUle4u/FK68mFl9+TxixlFDRxt1CsvMR8dHP0EFJOMSq5U\nNwpcnEh20++3DNiq7bCtS2W68FRh8bVxmhEEXPxW4HLJV4WZSE4pu9B7w9kd5NLg\nAeQyj0ugpILq3IonJdLsgLkg4RGV4GDgFOEqxOCT4uKeSX/gdOXvr5WJzlWJRLjT\nA51CtgnUVXlNO5zfCiFRMXOFyE1FCeBm5Ivkgh4KxkQvwI6jSZpU1rjiz4QYTOGO\n4gA=\n=XdTd\n-----END PGP MESSAGE----- unencrypted_suffix = _unencrypted version = 3.5.0 07070100000047000081A400000000000000000000000162794F9300000CBB000000000000000000000000000000000000001800000000sops-3.7.3/example.json{ "firstName": "ENC[AES256_GCM,data:f8++3g==,iv:rYuVzzb+C40QlYgO4Dl2V7atZUx0ITBcyb5fUsftKMo=,tag:krquPqa1HQltZqidzNamrA==,type:str]", "lastName": "ENC[AES256_GCM,data:94a2Q8c=,iv:c3NC7L80UTtbz7gdvPV5oSUwg30lC3Kg82uvRVs5CZw=,tag:kUXRNerUWmSe44mwD4w5uA==,type:str]", "age": "ENC[AES256_GCM,data:gjwWkw==,iv:XEWFpsyvEsPwr3qqsOJlfZ+vSZdiA+D6DAc6aoq/BS0=,tag:pcnUyMtYFa9v5DB6sNV15w==,type:float]", "address": { "city": "ENC[AES256_GCM,data:vSeyQwN1Z9k=,iv:DBmuX4w6w14Z/1b820OE3SM3MPx3oLGAeSoR4CWxdhg=,tag:ClpJZLb4ObIOdDD441clrw==,type:str]", "postalCode": "ENC[AES256_GCM,data:SZadC4tZh106eg==,iv:z/yWCZTd19j+3cFY5mwVkxY8a7i6veTBnwh4fsw5Kbw=,tag:iel9Pqh0jS0KhjxklXeqIg==,type:str]", "state": "ENC[AES256_GCM,data:b0Y=,iv:7Ar/Tb7XCDo5ABZNdSNBqGquaGEQF7dNxd1VvW7Nwak=,tag:uEjmOKwPlkYSq4IV1tQjwQ==,type:str]", "streetAddress": "ENC[AES256_GCM,data:dVFPTRPKOFSJ1plV9w==,iv:08Ks4C1FzFozezKBBYSPEAIkC5DkthDFmMW0R3zVbkI=,tag:waInfXMCAcx5C7avXHahOw==,type:str]" }, "phoneNumbers": [ { "number": "ENC[AES256_GCM,data:lkUEC7s3qU9AY6W+,iv:KjF9i0K9u7THbb3Bn1adQrIKpv1ZqA3PiJkctgFm3Bw=,tag:LBSqqr+gn15x0Pz5JKMJJQ==,type:str]", "type": "ENC[AES256_GCM,data:aXBHGg==,iv:ulcjNwVfGFvUtVN8q0h1LMYM5zRDmOsqtoFC1JOHREY=,tag:7vdMoCOD+7IeUHWQqQJ8XA==,type:str]" }, { "number": "ENC[AES256_GCM,data:z9Ujp3n2yXBqPNM1,iv:1nbZrIKozuS2p2AgD5/gHgjMN/VSd8SFeCbWdjy9Cf0=,tag:tfwaYihQDMAsmy/yt6ScLg==,type:str]", "type": "ENC[AES256_GCM,data:ddkB7Iu6,iv:4t31C5r1zhCpLQ64idoJ8OBC7ocME15zCUXCmgf2ItY=,tag:RmBsmbhQW10oOTDuzfxEaA==,type:str]" } ], "anEmptyValue": "", "sops": { "kms": null, "gcp_kms": null, "azure_kv": null, "lastmodified": "2019-12-10T22:45:55Z", "mac": "ENC[AES256_GCM,data:VoXDgYpIYCvFSLyKGx4c8yk56Mk5GkeIwM8IyUi4RgkKBY/xEIzNUOuMqBzWEOvTTsivF/JtUOrBIsDRxGY0u0qNJK1R5WFuSYr3TA5sdu3ytMcu+mKY4THSJN8uuri/tXVcoF+ywLOS6NRFbHDJPtJGcy1XQwJJAgvdw+sIvQA=,iv:dvk3FYXHr2N6gYIw2OqbYiHn6FfXzuyHqZvJIo6IVGM=,tag:rjnNkJSGpXJ762EyjDltXg==,type:str]", "pgp": [ { "created_at": "2019-12-10T22:45:55Z", "enc": "-----BEGIN PGP MESSAGE-----\n\nwcBMAyUpShfNkFB/AQgAMSeWf3F8kIm8EFiVgGVQgWGIHUVoolToi8d8lAC8/UdK\ncx9dIqlR43IFvvmCKyNZ6Q+/a1ERc07xLpVp3wmN80sE4NZCGZioThZjp2qNS42e\n/HtLfDu+Rie1eKcXEik40rMDn7d8gaFVOpD3FbzoZUFVm8hN5ChzqQqL1nLy9ZgY\nbthH3Rzt58Z1/sxARLNF2/yUqAEX/YEoL0MxM68Z55kwiMqSZ1rdmLKKfXdJbdoL\nSRrFyi+XaAwr1bTD+BnqHqgmYEEWEfHPDW7e1St/4IS4PU98kKuVLjhBKbfTRUpF\nkzxt+XQV6uDfPzdeOzf+JrFMRaoxTRMpcUi4Jn0vstLgAeSAjSzqkUr6DEVsuS8V\nAqRD4Z9I4HHgy+GYruDQ4kDtTvPgbeUWFma5yz25JOoORZIHaGiEB3T8ZhrFD8VI\ndFLxtxLtCeCg5BhRcrxFgWPQCMK/uCt/GFniNf4Y2OGOHQA=\n=r/lV\n-----END PGP MESSAGE-----", "fp": "FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4" }, { "created_at": "2019-12-10T22:45:55Z", "enc": "-----BEGIN PGP MESSAGE-----\n\nwYwDXFUltYFwV4MBBABs0ZEghsU+mdsRFROvlT8ACqk4Ru0Bssw3N+lkSrpR+QyY\nuRgAFNmZgPKz4DhZLHbhOD0zAAHuMCGriXVqtxMYMveX1nbXom+ayBmU0jja+6X2\njwk0MghHS1bgIsGrrhPoD7c3iirdaXSgHKAxwl4bpw5OxW6t91moOtJ4DAyGXdLg\nAeTkQxUyLFEeUbx4xX7kNCm+4Yho4CngDuH4oOD44gSWJsjgU+XiRBuJa50tiloC\nA10yQkWZtuyPOWppO5qOdXqV3XDIeOBl5JSNFuv5B80w8UIYhPPXWpriV1nBveE/\nwAA=\n=Vg44\n-----END PGP MESSAGE-----", "fp": "D7229043384BCC60326C6FB9D8720D957C3D3074" } ], "unencrypted_suffix": "_unencrypted", "version": "3.5.0" } }07070100000048000081A400000000000000000000000162794F93000007E9000000000000000000000000000000000000001700000000sops-3.7.3/example.txt{ "data": "ENC[AES256_GCM,data:p6kOd9e7KOYw47VlNlKa52wPFfbY3xaJYQrO5QDT1LyNvUIVBSRTrJxvn5MCC7vdnTOkcBzWmlr6Z/Q23/sx22++3Y7nXTSgFPQxPVIA8X33OoIsCamNHS8+8JWOReALCf2Cd3rzedu0GWR+/f2YBSHNA3C4nffEDbWbXRyAvcvCv3G4umH+Jh9auWUlfbk3Bx/8LvX6DodcxhQ=,iv:ESrDyOG6qetEWGBNHWRpT6ra1NhpaFH3SnjBSdMj2r0=,tag:aP5vOboB64cJDUls9WKsTA==,type:str]", "sops": { "kms": null, "gcp_kms": null, "azure_kv": null, "lastmodified": "2019-12-10T22:44:49Z", "mac": "ENC[AES256_GCM,data:wN+npCzfJVz6nwZQ40FTPD23Ly1CEiU1N6aDua+Mgj9cH7NwJOklW8QKTs3+q3f4HEkbeuFE6VQN+Jm05Zsj1inGjAdG2MfDurspJl6Jpe5DBKgk3zudAcc66gm4T4Dn3h7zFvNovOl+VEa4+ntaxIoVNugVDq3ZLTj/wMd3XwU=,iv:RadNg2jPeQEkE1F/GzrdcPIZHbxXoZpo+iOHpRGlLhc=,tag:ID8N4xhN7p3N5EYGTkYKxg==,type:str]", "pgp": [ { "created_at": "2019-12-10T22:44:49Z", "enc": "-----BEGIN PGP MESSAGE-----\n\nwcBMAyUpShfNkFB/AQgANOTnicDHAmqwi76yIm2eAgzm32k34hsPS40vKeCKtbIP\niR91/hDmklYXgR9yL9xgBI0SRTMGySSk9YJ9daZd61JVh1IVuxr93Y8GSxhDldAn\n1Wc2dXJ24x7zxfUs4sfZYCtzXZBUb/eAPLDIkeKPzkVKN4kLdVdccOig/2lOuuVo\nw3Xy+m7cx0VPdsFFzVWok15oHj8n0+J8v6Vnyiyx7yI7xgsynNwpZDUN+K15NyGs\nkaO21AeQnxDWmwo4H93+r10esFYns0kyLOCNwN5/XLskT31f9MCo8H4bBDyeO1lE\nrfLKAn0mh81qKedQLTssjElCLBgY4CpcL9B688P/otLgAeSR+v/JrgslAw+QhiBC\nPxqj4ZUC4KbgFeERieC34sjLWuPgxOUoC769iqiM3ArscWLYG6jYb9Acigwtf5/r\nNkFoXHoZPOD15Ne/ElmCDPowh0aAFCwVp6/ipRc0teELTQA=\n=FyYT\n-----END PGP MESSAGE-----", "fp": "FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4" }, { "created_at": "2019-12-10T22:44:49Z", "enc": "-----BEGIN PGP MESSAGE-----\n\nwYwDXFUltYFwV4MBBACebuDSGC4caG1iSviC+IQKXw/mQXmWDDWLA1RDH7/ZEa/2\nqsA0Eb5bsd4Hf5pW6UpK/TDZpFn3eKeCn8wP2G792Ez19UmhwHB+0Zid9Zq76UAZ\n1bcwX2YJeCgd8/OgIxfh7a6MDDz6TDNGL916BIE6kFJwT3Vvm9EzF7vDglE5mtLg\nAeTK87HB76QLJNDI03Q8JYrN4e1S4KzgxOFhkeAv4lmhR+zgCeU3u0ripltx+Hys\ngtiGJSnuoJrYrwhSIO8JOoc2iR0bkuCn5Gf4VirHEUwrIUqgQVsmLQnick1iv+Hk\n3QA=\n=exjM\n-----END PGP MESSAGE-----", "fp": "D7229043384BCC60326C6FB9D8720D957C3D3074" } ], "unencrypted_suffix": "_unencrypted", "version": "3.5.0" } }07070100000049000081A400000000000000000000000162794F930000140F000000000000000000000000000000000000001800000000sops-3.7.3/example.yamlmyapp1: ENC[AES256_GCM,data:zlGNmhTYX5xol4ZZFsiaoGkD73nn,iv:ql9mkhoU1I64E/FJi3iA0HaAe2U3kQVFee2ZLwPnBik=,tag:SqVSfu/JkRrwqidAT/i0pg==,type:str] app2: db: user: ENC[AES256_GCM,data:tQ1l,iv:o9MiMveNYO7T82yDab+4pAt17DO6B4wl8yGy3oFbDb8=,tag:0RjvUUtSQwaUc9SF3RSTZQ==,type:str] password: ENC[AES256_GCM,data:8Ll+TDCgzQ==,iv:aao0OSVdFwaB9EGZ0O+Wn5bRJ6do7hgiQioqiwDi1w0=,tag:WKj0nuSSPkm2dBOBwinYvg==,type:str] #ENC[AES256_GCM,data:uPuUQAahmq1xL5L2B0+a2gHSl7zXucjqa4Kr0AgNKTpgHw3IPx2lCoK+,iv:qYxjRmWMir47YKhmrrHwV0atmGjcJ3Dts+xvQ+6skHQ=,tag:dsj+0DsfiDyJNGUmvZwnHg==,type:comment] key: ENC[AES256_GCM,data:xRjmLiX4BCoSUElToUs5twDq1WNWQNvNMi8yitXly43iGQiwltIs0FsY5u+7fzLepS66oLTGdL0NfWwCGxksYYzUKz5OXRLEatacZP40D71861zu+njmGdXepY0q0q5VOG7ObgIAMMMElVKRIFdjpVgmgUa+/h6R77mEbDztk6lqb28r15XyR6GmdubierTE7aialFzNoC+XO/yk7bnMi0XA/aomj1H5RdZ37LBR2k+rhqXmhkPdGTdJ39t4Ou1Q2Oc4RHRGvgs/EeFqQHcq0AilqXsFIv/PE0bQP564LaOcXK33B+PnoV1D+lXZ7mLOjKlq04c+UojwXjpZeXBr6Ip4H3dAGkPAoQKMtyGwHYzuLCNesxwPv2tPqbxkbVev0AKezGhnPjvCNvRN3S1Y0LPf1atfwzOCBQBhdUTpmXtxCdTNG0cUUZjeIKAyJXDWJooYHlstzDti/dSGMEedOnKq6648Yp7tLNDjAg5CGbDEWjTWehtqgvixUoRdYc2/r/ie3t09XB9h70BzLFDnbNdTNHhg0/aivHeDf8LJ2Co8YvIjLTwlm7GV0mSwzJIoY/2YxXFtw+XFqt+BCt1G8wq3R6OdXZK/+6fUKH/D4EVym5nrGkZIuOCiTB+wpzy09QmZ80Fo+ba0x/1g9ZTMKHk=,iv:ZtzvrO7QSHEOCnKCrIYcaesKnyScV8KaHZr22tUMLlU=,tag:2A3nJBIPF2Q3FlwMYLvG2w==,type:str] number: ENC[AES256_GCM,data:DX0qiTOWhQvG/w==,iv:ouWsby8JoFwCRj/mLVCnNcYhP2sdyf4h6nwZuGksE7Q=,tag:lPU6AId2JrlquHnYRw+E8Q==,type:float] an_array: - ENC[AES256_GCM,data:vyczE8EQr9qHkaM=,iv:sT5jKk3LZ61Zq/neTli5tcnDFxCxY5RuGr2k5oGQWJQ=,tag:1HgaHWfyh6EJLkI1V2kOrw==,type:str] - ENC[AES256_GCM,data:XtBinnYXR7bx1GY=,iv:KvT9smKVmgMNrab+RzfuWscyvJav2r8j1P08ucNmhgQ=,tag:IllAEvIPPeKOfqi1XbmT8w==,type:str] - ENC[AES256_GCM,data:gpZ7nwWTGaI+Ti+lk+CPQOoM0ypwK7UMMBUiZAniQHDNJelipqc8hyhNeV+tpJLNaRt74OHs04EX8g==,iv:mKcwVelqLvwVDPjR8NeyMZ7AhsjRgmnYmyEuwPNPrQ8=,tag:vrkoccUfJs105yLCmXYYCw==,type:str] - ENC[AES256_GCM,data:L9jPh+7+XsdqEpUnFcD4nA==,iv:xyfKjOXVrBDCIQG5786pSu5yvHdl/PK8eVxkIUWoCIw=,tag:Q0wlTV2e2vZeU6eTF5Oacg==,type:str] somebooleans: - ENC[AES256_GCM,data:ExiXxg==,iv:K7FUwomqdA7o9lzvNoAMH/wbXs08FextTGGeJKnaatU=,tag:A9UntgvPIcappmeM3jsbdA==,type:bool] - ENC[AES256_GCM,data:3I0AVdM=,iv:q4YKnRIKufREPmwT4sz8plcsOD6iem/tY3NMUV0STBE=,tag:0w4OMKClWTzjKhqsJZT8JA==,type:bool] this: is: a: nested: value: ENC[AES256_GCM,data:oFn5fJS5+slb2sCdLY5SxZ+iWeowWtf4wn9g,iv:MZ7i4tZnfCQhQRUwXV2fYQPIJ0tTUFLiD9xuB+765e8=,tag:ZEjE5jvxE5HkN3mma84pKw==,type:str] #ENC[AES256_GCM,data:WwWiKtMsD1shPe5kPHOh2bJqQPGHwxa6GYrR1y14wiid,iv:AZPaRyVDOl100PvBPMeq0lt6/O5ZUhzWX5UmWNABWvM=,tag:PBReK6Ap/VHxncn0G4qtEA==,type:comment] #ENC[AES256_GCM,data:eYRaxgs3vGeS96+ZDV8GYrwbvsrMtnWHOtsT2045tD2mlfOD,iv:/RVNEWuBlxhhY8OlJPbS/81QJukXZu1EWnPUQwrcin4=,tag:DybgrXKGWxoRyIQOlc+UMA==,type:comment] #ENC[AES256_GCM,data:JXKEWGBg4eeCdeQ=,iv:K5keuEjyekf7a3q7WBOKwljsHGXRdQteJcXeeKvHo28=,tag:60VtIdsy13qSKPIEWHUUNg==,type:comment] somelist_unencrypted: - all elements of this list - remain in clear text - because of the _unencrypted suffix in the key nested_unencrypted: this: is: all: going to remain in clear text sops: kms: [] gcp_kms: [] azure_kv: [] lastmodified: '2019-12-10T22:45:53Z' mac: ENC[AES256_GCM,data:WDjMv0eWcyPQzZlr3MppeAMQavN88xv5LzI/9wOlg+WPhRoTdrvgFpWowyWvTdUC/i0ybRQRg2u/Wam0kaqzMDpl/E806Gp9hgJcSneqydDJqPiMh+HpXkXWpc70xbYg8/gc1l7eIfSG7rS1dC2t2je60OAIfC/5zAXrL9KH4Ho=,iv:h0hWhb+46upix6K7hZfNNQoiX7WCapiMTv5I/keZsm4=,tag:v7mVbTN3HWmekol5iaO8FA==,type:str] pgp: - created_at: '2019-12-10T22:45:53Z' enc: |- -----BEGIN PGP MESSAGE----- wcBMAyUpShfNkFB/AQgAE0MaWAQGbTKY7Xg3fDNtzlvnVBkkQRHsLt5kUTu2nAy4 sPX0NRXPVF/tAMxr9mI2fRjKnNBXKpOAecNis85D/QEkfflG8/syGkqiJqy9Nqon WSm1bNriPfD4PL850688EJe49Xrsz6rVbW5FYZCHMbnPxvmoheMJRxLonW3/eWPy IjJ9i6Z7W175mv1y7FELOimdQeelynp4r8bOuuq1BhePB4+wJXihw9n0ovuLklpl Kr2iCmIUibSywlEO/LGQT/VXo6R7xgSN2Xg0RwWfflajYHNhVkHlnNYzkACjvdhj ph1M5fLGDqPu+ySSe93EyahNhdwKgQ7R9yF8/13+QdLgAeRmrBgh0N54mQQAQyL7 mH/i4SWi4BjgmeHgUeBL4qcm2avg6OUasVJQHlTeA8D+c3TwKSTRVDijN4GBadYJ Z5/vXKOseeBk5JWCnIHC/MtjOkuPt53nvGzi3lYvW+F0FQA= =RDyn -----END PGP MESSAGE----- fp: FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4 - created_at: '2019-12-10T22:45:53Z' enc: |- -----BEGIN PGP MESSAGE----- wYwDXFUltYFwV4MBBABpm+tFhFhv3A7A/L/p6nL3HXKKhONrgguYgXA/hhSg4/bD 1Po5pQhCM4yb3gqWewxgVpGNKFr/Gl+kN9eZ3LXp5nEdhei/aQn7BbWkhph5PKt6 faiEZAL5PNHvktvEQwPsfNJvxe8QT2Z9oFmlueP0n3mCZz3UV9LZHNwOP7XfzdLg AeRzUGrZK43KavmIjdgPXcd/4ZW14NPgNeGm8+BO4nD26Fvgh+UiCb0TpBiI7WsX HQGUlFjuR6Vd7Q+vg8B/1Ovm3fUw7uCG5Gc2WeB0M+pXaLYS9bCDL9jisGOkD+Ef ZAA= =mqGc -----END PGP MESSAGE----- fp: D7229043384BCC60326C6FB9D8720D957C3D3074 unencrypted_suffix: _unencrypted version: 3.5.0 0707010000004A000041ED00000000000000000000000462794F9300000000000000000000000000000000000000000000001400000000sops-3.7.3/examples0707010000004B000041ED00000000000000000000000462794F9300000000000000000000000000000000000000000000001F00000000sops-3.7.3/examples/all_in_one0707010000004C000081A400000000000000000000000162794F9300000013000000000000000000000000000000000000002A00000000sops-3.7.3/examples/all_in_one/.gitignoreconfig/secret.json 0707010000004D000081A400000000000000000000000162794F9300000C7D000000000000000000000000000000000000002A00000000sops-3.7.3/examples/all_in_one/README.rstAll-in-one example ================== This directory is an example configuration for SOPS inside of a project. We will cover the files used and relevant scripts for developers. This example is optimized for saving developer time by storing all secrets in a single file (e.g. ``secret.enc.json``). One downside is any configurations which should be stored side by side might not be. Getting started --------------- To use this example, run the following: .. code:: bash # From the `sops` root directory # Import the test key gpg --import tests/sops_functional_tests_key.asc # Navigate to our example directory cd examples/all_in_one # Decrypt our secrets bin/decrypt-config.sh # Optionally edit a secret # bin/edit-secret.sh config/secret.enc.json # Run a script that uses our decrypted secrets python main.py Storage ------- In both development and production, we will be storing the secrets file unencrypted on disk. This is for a few reasons: - Can't store file in an encrypted manner because we would need to know the secret to decode it - Loading it into memory at boot is impractical - Requires reimplementing SOPS' decryption logic to multiple languages which increases chance of human error which is bad for security - If someone uses an automatic process reloader during development, then it could get expensive with AWS - We could cache the results from AWS but those secrets would wind up being stored on disk As peace of mind, think about this: - Unencrypted on disk is fine because if the attacker ever gains access to the server, then they can run ``sops --decrypt`` as well. Files ----- - ``bin/decrypt-config.sh`` - Script to decrypt secret file - ``bin/edit-config-file.sh`` - Script to edit a secret file and then decrypt it - ``config/secret.enc.json`` - Catch-all file containing our secrets - ``config/secret.json`` - Decrypted catch-all secrets file - ``config/static.py`` - Configuration file which imports secrets - ``.gitignore`` - Ignore file for decrypted secret file - ``main.py`` - Example script Usage ----- Development ~~~~~~~~~~~ For development, each developer must have access to the PGP/KMS keys. This means: - If we are using PGP, then each developer must have the private key installed on their local machine - If we are using KMS, then each developer must have AWS access to the appropriate key Testing ~~~~~~~ For testing in a public CI, we can copy ``secret.enc.json`` to ``secret.json``. This will represent the same structure as ``secret.enc.json`` with an additional ``sops`` key but not reveal any secret information. .. For convenience, we can run ``CONFIG_COPY_ONLY=TRUE bin/decrypt-config.sh`` which will use ``cp`` rather than ``sops --decrypt``. For testing in a private CI where we need private information, see the `Production instructions <#production>`_. Production ~~~~~~~~~~ For production, we have a few options: - Build an archive (e.g. ``.tar.gz``) in a private CI which contains the secrets and deploy our service via the archive - Install PGP private key/KMS credentials on production machine, decrypt secrets during deployment process on production machine 0707010000004E000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000002300000000sops-3.7.3/examples/all_in_one/bin0707010000004F000081ED00000000000000000000000162794F9300000261000000000000000000000000000000000000003500000000sops-3.7.3/examples/all_in_one/bin/decrypt-config.sh#!/usr/bin/env bash # Exit on first error set -e # Define our secret files secret_files="secret.enc.json" # For each of our files in our encrypted config for file in $secret_files; do # Determine src and target for our file src_file="config/$file" target_file="$(echo "config/$file" | sed -E "s/.enc.json/.json/")" # If we only want to copy, then perform a copy # DEV: We allow `CONFIG_COPY_ONLY` to handle tests in Travis CI if test "$CONFIG_COPY_ONLY" = "TRUE"; then cp "$src_file" "$target_file" # Otherwise, decrypt it else sops --decrypt "$src_file" > "$target_file" fi done 07070100000050000081ED00000000000000000000000162794F930000033F000000000000000000000000000000000000003700000000sops-3.7.3/examples/all_in_one/bin/edit-config-file.sh#!/usr/bin/env bash # Exit on first error set -e # Define our secret files secret_files="secret.enc.json" # Look up our file filepath="$1" if test "$filepath" = ""; then echo "Expected \`filepath\` but received nothing" 1>&2 echo "Usage: $0 <filepath>" 1>&2 exit 1 fi # If our file is a secret filename="$(basename "$filepath")" if echo "$secret_files" | grep "$filename"; then # Load it into SOPS and run our sync script sops "$filepath" bin/decrypt-config.sh # Otherwise (it's a normal file) else # Resolve our editor via `sops` logic editor="$EDITOR" if test "$editor" = ""; then editor="$(which vim nano | head -n 1)" fi if test "$editor" = ""; then echo "Expected \`EDITOR\` environment variable to be defined but it was not" 1>&2 exit 1 fi # Edit our file "$editor" "$filepath" fi 07070100000051000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000002600000000sops-3.7.3/examples/all_in_one/config07070100000052000081A400000000000000000000000162794F9300000000000000000000000000000000000000000000003200000000sops-3.7.3/examples/all_in_one/config/__init__.py07070100000053000081A400000000000000000000000162794F930000073B000000000000000000000000000000000000003600000000sops-3.7.3/examples/all_in_one/config/secret.enc.json{ "github_oauth_token": "ENC[AES256_GCM,data:lVe1fLoILE8e/jiah90=,iv:6g350p9Id8+ssrV5lVEF4TWEnR+JfY84iUsh92/9XIQ=,tag:2qojKddpx5lO1iR2dqLZJg==,type:str]", "sops": { "kms": null, "gcp_kms": null, "azure_kv": null, "lastmodified": "2019-12-10T22:46:40Z", "mac": "ENC[AES256_GCM,data:XUVZm8UI26QEbhlIkQDfEWcyhooh/bGQ2eHOTu/ojbE4SBLRpcLtKHt2Bn8Z/72pLV77FmmSXAIwy8+xvzeTmEAZNRMfjrm0ZulBY1VgJLTrvA4Hh9F7H+Pb95xtc8B9SbFAo65L+xBJJ/e6t8QsV0N4crOoq0Su3OuvqXIVa90=,iv:1Vnq304ic+vPZ0as0xLpaO8dMduMrF3Xh9aAHT+VLiA=,tag:37+ami7Z3tQd1EwZKC7sPg==,type:str]", "pgp": [ { "created_at": "2019-12-10T22:46:40Z", "enc": "-----BEGIN PGP MESSAGE-----\n\nwcBMAyUpShfNkFB/AQgATTwZYaZdIVFnJrzujQvk5x/SqVc5VY367UtStph9pxn5\nkSh2lvPrYHtkbpPkvv5Ug5CUwUbofq4rj3YSO3kVSDu5Z3IrAT+EtnK5PECg2JDT\nIXskkjFegQYp4YtC5UUhjIVIALti/9b3g2skVES/IHHlL+BE9AWj93yF24weu54D\nbwF6PQyGRE7HhZ8uUWEKIGTbJbpDzySk06LJSF33uNbKUViRtHuLu4JNFb22sjzy\nMKzITct/4KVlHAkC4ZHpDlsXmtooBJ8Yk7DK2vEM1POk2pE6ZBQbEgtY0hAOMFGB\nfg+j6cvNsLYWyQrjaB9Vsf4+QPDeTWuv5+cJjvgildLgAeQssqFSnZXumtz/VgB1\nm7Ma4Rov4DLgAeHkV+DJ4vTaGavgHuVj+a53Oa9HuX4p1rNV3ZLUyJi6HmNOWjvw\np8QFzrcRyOCN5N3VkrUyEK9l03NczoI9nt7i6r8jBeHWTQA=\n=Uxc1\n-----END PGP MESSAGE-----", "fp": "FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4" }, { "created_at": "2019-12-10T22:46:40Z", "enc": "-----BEGIN PGP MESSAGE-----\n\nwYwDXFUltYFwV4MBBAA5g2pnnh43zql0b1bOa6zw0S6uMSLioFy5Or4Z+aEhywpe\n94unRNqABQdqCtowoUpjJ4vEHxQ+dkHPXpYJejolBKPaKpHJHLrdImkC9nb62jJ8\nthWFitj8ELQnHdmSk+t5kvaNj4nWXSuTTiXd2UodXZcHxH4UI4P7A4aZXKTYytLg\nAeRHhnSBOBYD0rMvFIWKLcR94SZE4LrgluHLYODT4l6Ln4fg5uX2CzKsP/KD4+jI\nPDvITr4Sdnh6m6XEctb4whn0OOKXo+Ca5OJ75/+Kb9SzSbA24uXJ1qriELpTt+Hk\nawA=\n=SIw4\n-----END PGP MESSAGE-----", "fp": "D7229043384BCC60326C6FB9D8720D957C3D3074" } ], "unencrypted_suffix": "_unencrypted", "version": "3.5.0" } }07070100000054000081A400000000000000000000000162794F9300000104000000000000000000000000000000000000003000000000sops-3.7.3/examples/all_in_one/config/static.py# Load in our dependencies import json # Load in our secrets with open('config/secret.json', 'r') as file: secret = json.loads(file.read()) # Define our configuration common = { 'github_oauth_token': secret['github_oauth_token'], 'port': 8080, } 07070100000055000081A400000000000000000000000162794F93000001CA000000000000000000000000000000000000002700000000sops-3.7.3/examples/all_in_one/main.py# Load in our dependencies from __future__ import absolute_import from config.static import common # Define our main function def main(): # Output our configuration print('Configuration') print('-------------') for key in common: # Example: `port: "8080"` print('{key}: "{val}"'.format(key=key, val=common[key])) # If this script is being invoked directly, then run our main function if __name__ == '__main__': main() 07070100000056000041ED00000000000000000000000462794F9300000000000000000000000000000000000000000000001D00000000sops-3.7.3/examples/per_file07070100000057000081A400000000000000000000000162794F9300000012000000000000000000000000000000000000002800000000sops-3.7.3/examples/per_file/.gitignoreconfig config.bak 07070100000058000081A400000000000000000000000162794F9300000D83000000000000000000000000000000000000002800000000sops-3.7.3/examples/per_file/README.rstPer-file example ================ This directory is an example configuration for SOPS inside of a project. We will cover the files used and relevant scripts for developers. This example is optimized for storing sensitive information next to related non-sensitive information (e.g. password next to username). The downsides include: - Slowing down developers by requiring usage of SOPS for non-sensitive information - Losing dynamic configurations that rely on reusing variables (e.g. ``test = {'foo': {'bar': common['foo']['bar'], 'baz': false}}``) - There might be work arounds via YAML Getting started --------------- To use this example, run the following .. code:: bash # From the `sops` root directory # Import the test key gpg --import pgp/sops_functional_tests_key.asc # Navigate to our example directory cd examples/per_file # Decrypt our secrets bin/decrypt-config.sh # Optionally edit a secret # bin/edit-secret.sh config.enc/static_github.json # Run our script python main.py Storage ------- In both development and production, we will be storing the secrets file unencrypted on disk. This is for a few reasons: - Can't store file in an encrypted manner because we would need to know the secret to decode it - Loading it into memory at boot is impractical - Requires reimplementing SOPS' decryption logic to multiple languages which increases chance of human error which is bad for security - If someone uses an automatic process reloader during development, then it could get expensive with AWS - We could cache the results from AWS but those secrets would wind up being stored on disk As peace of mind, think about this: - Unencrypted on disk is fine because if the attacker ever gains access to the server, then they can run ``sops --decrypt`` as well. Files ----- - ``bin/decrypt-config.sh`` - Script to decrypt secret file - ``bin/edit-config-file.sh`` - Script to edit a secret file and then decrypt it - ``config`` - Directory containing decrypted secrets - ``config.bak`` - Backup of ``config`` to prevent accidental data loss - ``config.enc`` - Directory containing encrypted secrets - ``static.py`` - Python script to merge together secrets - ``static_github.json`` - File containing secrets - ``.gitignore`` - Ignore file for ``config`` and ``config.bak`` - ``main.py`` - Example script Usage ----- Development ~~~~~~~~~~~ For development, each developer must have access to the PGP/KMS keys. This means: - If we are using PGP, then each developer must have the private key installed on their local machine - If we are using KMS, then each developer must have AWS access to the appropriate key Testing ~~~~~~~ For testing in a public CI, we can copy ``config.enc`` to ``config``. The secret files will have structure with an additional ``sops`` key but not reveal any secret information. .. For convenience, we can run ``CONFIG_COPY_ONLY=TRUE bin/decrypt-config.sh`` which will use ``ln -s`` rather than ``sops --decrypt``. For testing in a private CI where we need private information, see the `Production instructions <#production>`_. Production ~~~~~~~~~~ For production, we have a few options: - Build an archive (e.g. ``.tar.gz``) in a private CI which contains the secrets and deploy our service via the archive - Install PGP private key/KMS credentials on production machine, decrypt secrets during deployment process on production machine 07070100000059000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000002100000000sops-3.7.3/examples/per_file/bin0707010000005A000081ED00000000000000000000000162794F930000032A000000000000000000000000000000000000003300000000sops-3.7.3/examples/per_file/bin/decrypt-config.sh#!/usr/bin/env bash # Exit on first error set -e # Define our secret extenssion secret_ext=".json" # If there is a config directory, then move it to a backup if test -d config; then if test -d config.bak; then rm -r config.bak fi mv config/ config.bak/ fi # Create our new config directory mkdir config # For each of our files in our encrypted config for src_file in config.enc/*; do # Determine target for our file src_filename="$(basename "$src_file")" target_file="config/$src_filename" # If the file is our secret, then decrypt it if echo "$src_filename" | grep -E "${secret_ext}$" && test "$CONFIG_COPY_ONLY" != "TRUE"; then sops --decrypt "$src_file" > "$target_file" # Otherwise, symlink to the original file else ln -s "../$src_file" "$target_file" fi done 0707010000005B000081ED00000000000000000000000162794F9300000139000000000000000000000000000000000000003500000000sops-3.7.3/examples/per_file/bin/edit-config-file.sh#!/usr/bin/env bash # Exit on first error set -e # Localize our filepath filepath="$1" if test "$filepath" = ""; then echo "Expected \`filepath\` but received nothing" 1>&2 echo "Usage: $0 <filepath>" 1>&2 exit 1 fi # Load our file into SOPS and run our sync script sops "$filepath" bin/decrypt-config.sh 0707010000005C000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000002800000000sops-3.7.3/examples/per_file/config.enc0707010000005D000081A400000000000000000000000162794F9300000000000000000000000000000000000000000000003400000000sops-3.7.3/examples/per_file/config.enc/__init__.py0707010000005E000081A400000000000000000000000162794F93000007DF000000000000000000000000000000000000003200000000sops-3.7.3/examples/per_file/config.enc/static.py# Load in our dependencies import json # Define our configuration # DEV: THE FOLLOWING CONFIGURATIONS SHOULD NOT CONTAIN ANY SECRETS # THIS FILE IS NOT ENCRYPTED!! common = { 'port': 8080, } development = { } test = { } production = { } config = { 'common': common, 'development': development, 'test': test, 'production': production, } def walk(item, fn): """Traverse dicts and lists to update keys via `fn`""" # If we are looking at a dict, then traverse each of its branches if isinstance(item, dict): for key in item: # Walk our value walk(item[key], fn) # If we are changing our key, then update it new_key = fn(key) if new_key != key: item[new_key] = item[key] del item[key] # Otherwise, if we are looking at a list, walk each of its items elif isinstance(item, list): for val in item: walk(val, fn) # Merge all of our static secrets onto our config # For each of our secrets secret_files = [ 'config/static_github.json', ] for secret_file in secret_files: with open(secret_file, 'r') as file: # Load and parse our JSON data = json.loads(file.read()) # Strip off `_unencrypted` from all keys walk(data, lambda key: key.replace('_unencrypted', '')) # For each of the environments for env_key in data: # Load in the respective source and target env_src = data[env_key] env_target = config[env_key] # Merge info between configs for key in env_src: if key in env_target: raise AssertionError( 'Expected "{env_key}.{key}" to not be defined already ' 'but it was. ' 'Please verify no configs are using the same key' .format(env_key=env_key, key=key)) env_target.update(env_src) 0707010000005F000081A400000000000000000000000162794F930000093F000000000000000000000000000000000000003B00000000sops-3.7.3/examples/per_file/config.enc/static_github.json{ "common": { "github_username_unencrypted": "twolfson-dev", "github_password": "ENC[AES256_GCM,data:wtyRyrcYh1AaoA==,iv:lJKpAdaRjC4jAl+wIT6ozSZanSIEJwEyJkLi0aFHlGg=,tag:NrrARDWsoWqOyQ+oyALHAA==,type:str]" }, "development": {}, "test": { "github_username_unencrypted": "test-user", "github_password": "ENC[AES256_GCM,data:To6eP3uCm+IQo6KCBalF2Q+7zc4=,iv:GZz08Kz9fjCsIk1edHVZRYgSzfF9smzGbllOU6op+z8=,tag:Zrp5RNck7DXCVffX/zmWIQ==,type:str]" }, "production": { "github_username_unencrypted": "twolfson", "github_password": "ENC[AES256_GCM,data:AZ+Yef+hheVBQWtQ6Ys=,iv:RMd4iwMCiNkt2zPyLBakdA9+HaJA9hh2qUbbdNcemLo=,tag:ejPV/E3dwubZl6s1NyZnSA==,type:str]" }, "sops": { "kms": null, "gcp_kms": null, "azure_kv": null, "lastmodified": "2019-12-10T22:47:04Z", "mac": "ENC[AES256_GCM,data:rBKvMg8FroPuKB03JEzRvlnONsca7ByBqoTB18kjXHwr7mmnBYLQP4mWiYPRRRaBDaGtDXx3FH+Igyo84vPwNO2eEN94gv1NpeGqPI1vRUwAkCB3UE3hIQN22zXBEwWO4sUPKzERuG8t5dFF7xO4EyzweZlC+nwtyQUQX78ysGo=,iv:vymJO0wSb+/W4LUiZi9+ov9YVy3la+BopXiqd2X/mbA=,tag:Pm7NolfYB7ELKjHaGdbauQ==,type:str]", "pgp": [ { "created_at": "2019-12-10T22:47:04Z", "enc": "-----BEGIN PGP MESSAGE-----\n\nwcBMAyUpShfNkFB/AQgAJAeWCsS3nqQ+uDy5gj7Fm7evXn/gY5DW7AWJjkXmHqwx\n+IHVERj9JsfFApoUCPu4Om/KB67uegLWk/GFmAtHcwV2GrsGt1AAIURdNSwYf8P7\nLix9fpRwAUlamXS0wDbGQQyNX3X1rIMu7PnL3KLzvfBQUHaVs3+4GpNXjzUVRGgb\nmjtCTsH3u1VYiHrRz0uoWisPDlvn60fPv0mcYdu17sVpgPnC0qmHh6UJd85dGzfy\nSOisauKYkirOfLSJyVCItFD0GwPVd3mbEcxmIokHmaIznck/Tto3sXcQO4Amc54+\nCjXhk0f/KJbKQQg4mbZMaEMISE+/s3H4mBmk2VADJNLgAeS5csP9GjYrFOAHsgGR\nrDIC4SMd4DTgmuEYSOCh4qLAvJfgmOXA0EoByddCKyrCIHDdyq+lFOQq1Hk+kfl/\nruUZSgCy9OCo5FerieGdW/fWfWsKx2CGKczi0Q/ivuFb2QA=\n=rw7+\n-----END PGP MESSAGE-----", "fp": "FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4" }, { "created_at": "2019-12-10T22:47:04Z", "enc": "-----BEGIN PGP MESSAGE-----\n\nwYwDXFUltYFwV4MBBACeF7HYThUNm0osc9SEASAnYLB0/qp6IdA6dc0aNHBZxM3Z\nxiOmbGOfImgwWMDGFP3GYFe3NNt/imzy0V+umVWMYxLYb9Hm3yCdVSb6ks9xJwkp\nXWD7vLXXEuVRsLp+IsmMXSRRJ/shBLIORiOMIAiSv92yluNT8PZF3Nv1hA56Y9Lg\nAeR2GEqpdbw6/tIUPMJUHWQf4XZ14DHgkOF9beAN4qqnf1zgsuVA+YmakiS7L5iY\nA/4OMVnCDJBKTD+l91ID5d4k2bQoU+Dm5AmJqm+I6sX6nViQUgyEyjvi+aeOuuG0\nDQA=\n=0KIM\n-----END PGP MESSAGE-----", "fp": "D7229043384BCC60326C6FB9D8720D957C3D3074" } ], "unencrypted_suffix": "_unencrypted", "version": "3.5.0" } }07070100000060000081A400000000000000000000000162794F9300000377000000000000000000000000000000000000002500000000sops-3.7.3/examples/per_file/main.py# Load in our dependencies from __future__ import absolute_import from config.static import config # Define our main function def main(): # Output our configuration # DEV: We use custom keys for a custom sort print('Configuration') print('=============') for env_key in ('common', 'development', 'test', 'production'): # Example: `Environment: common` # Example: `-------------------` env_str = 'Environment: {env_key}'.format(env_key=env_key) print(env_str) print(''.join(['-' for char in env_str])) env_config = config[env_key] for key in sorted(env_config.keys()): # Example: `port: "8080"` print('{key}: "{val}"'.format(key=key, val=env_config[key])) print('') # If this script is being invoked directly, then run our main function if __name__ == '__main__': main() 07070100000061000041ED00000000000000000000000562794F9300000000000000000000000000000000000000000000001C00000000sops-3.7.3/functional-tests07070100000062000081A400000000000000000000000162794F9300000401000000000000000000000000000000000000002700000000sops-3.7.3/functional-tests/.sops.yamlcreation_rules: - path_regex: test_roundtrip_keygroups.yaml key_groups: - pgp: - FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4 - pgp: - D7229043384BCC60326C6FB9D8720D957C3D3074 - path_regex: test_roundtrip_keygroups_missing_decryption_key.yaml key_groups: - pgp: - FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4 - pgp: - B611A2F9F11D0FF82568805119F9B5DAEA91FF86 - pgp: FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4 destination_rules: - s3_bucket: "sops-publish-functional-tests" s3_prefix: "functional-test/" path_regex: test_encrypt_publish_s3.json reencryption_rule: pgp: B611A2F9F11D0FF82568805119F9B5DAEA91FF86 - vault_path: "functional-test/" vault_kv_mount_name: "secret/" vault_kv_version: 2 path_regex: test_encrypt_publish_vault.json - vault_path: "functional-test-version-1/" vault_kv_mount_name: "kv/" vault_kv_version: 1 path_regex: test_encrypt_publish_vault_version_1.json 07070100000063000081A400000000000000000000000162794F93000000EE000000000000000000000000000000000000002700000000sops-3.7.3/functional-tests/Cargo.toml[package] name = "functional-tests" version = "0.1.0" authors = ["Adrian Utrilla <adrianutrilla@gmail.com>"] [dependencies] tempdir = "0.3.5" serde = "1.0" serde_json = "0.8" serde_yaml = "0.5" serde_derive = "1.0" lazy_static = "0.1.*" 07070100000064000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000002000000000sops-3.7.3/functional-tests/bin07070100000065000081A400000000000000000000000162794F9300000000000000000000000000000000000000000000002A00000000sops-3.7.3/functional-tests/bin/editor.rs07070100000066000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000002000000000sops-3.7.3/functional-tests/res07070100000067000081A400000000000000000000000162794F930000070B000000000000000000000000000000000000003200000000sops-3.7.3/functional-tests/res/comments.enc.yaml#ENC[AES256_GCM,data:IYA+b4ORDq8u9CBQolipWD4HRqoZyA==,iv:F8ldQqGng+WptHuBkFtjrGM+7sRZCsvd0FHq98lrpAE=,tag:ZHbLU9+CELinf5PhhuIzSQ==,type:comment] lorem: ENC[AES256_GCM,data:PhmSdTs=,iv:J5ugEWq6RfyNx+5zDXvcTdoQ18YYZkqesDED7LNzou4=,tag:0Qrom6J6aUnZMZzGz5XCxw==,type:str] #ENC[AES256_GCM,data:HiHCasVRzWUiFxKb3X/AcEeM,iv:bmNg+T91dqGk/CEtVH+FDC53osDCEPmWmJKpLyAU5OM=,tag:bTLDYxQSAfYDCBYccoUokQ==,type:comment] dolor: ENC[AES256_GCM,data:IgvT,iv:wtPNYbDTARFE810PH6ldOLzCDcAjkB/dzPsZjpgHcko=,tag:zwE8P+AwO1hrHkgF6pTbZw==,type:str] sops: kms: [] gcp_kms: [] azure_kv: [] hc_vault: [] age: [] lastmodified: '2020-10-07T15:49:13Z' mac: ENC[AES256_GCM,data:2dhyKdHYSynjXPwYrn9356wA7vRKw+T5qwBenI2vZrgthpQBOCQG4M6f7eeH3VLTxB4mN4CAchb25dsNRoGr6A38VruaSSAhPco3Rh4AlvKSvXuhgRnzZvNxE/bnHX1D4K5cdTb4FsJg/Ue1l7UcWrlrv1s3H3SwLHP/nf+suD0=,iv:6xBYURjjaQzlUOKOrs2NWOChiNFZVAGPJZQZ59MwX3o=,tag:uXD5VYme+c8eHcCc5TD2YA==,type:str] pgp: - created_at: '2019-08-29T21:52:32Z' enc: | -----BEGIN PGP MESSAGE----- hQEMAyUpShfNkFB/AQgAlvpTj0NYqF4mQyIeM7wX2SHLb4U07/flpqDpp2W/30Pz AHA7sYrgP0l8BrjT2kwtgCN0cdfoIHJudezrNjANp2P5TbP2b9kYYNxpehzB9PFj FixnCS7Zp8WIt1yXr1TX+ANZoXLopVcRbMaQ5OdH7CN1pNQtMR+R3FR3X/IqKxiU Do1YLaooRJICUC8LJw2Tb4K+lYnTSqd/HalLGym++ivFvdDB1Ya1GhT1FswXidXK IRjsOVbxV0q5VeNOR0zxsheOvuHyCje16c7NXJtATJVWtTFABJB8u7CY5HhZSgq+ rXJHyLHqVLzJ8E4WqHQkMNUlVcrqAz7glZ6xbAhfI9JeAYk5SuBOQOQ4yvASqH4K b0N3+/abluBY7YPqKuRZBiEtmcYlZ+zIHuOTP1rD/7L5VY8CwE5U8SFlEqwM7nQJ 6/vtl6qngOFjwt34WrhZzUfLPB/wRV/m1Qv2kr0RNA== =Ykiw -----END PGP MESSAGE----- fp: FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4 unencrypted_suffix: _unencrypted version: 3.6.1 07070100000068000081A400000000000000000000000162794F9300000043000000000000000000000000000000000000002E00000000sops-3.7.3/functional-tests/res/comments.yaml# first comment in file lorem: ipsum # this-is-a-comment dolor: sit07070100000069000081A400000000000000000000000162794F9300000025000000000000000000000000000000000000003300000000sops-3.7.3/functional-tests/res/comments_list.yamllorem: - foo #this-is-a-comment - bar0707010000006A000081A400000000000000000000000162794F9300000605000000000000000000000000000000000000004300000000sops-3.7.3/functional-tests/res/comments_unencrypted_comments.yaml# first comment in file lorem: ENC[AES256_GCM,data:qVz4paM=,iv:0oGsaw71i3wZKmlyDl8uDhQT9XLvJt3oIyx514X44K8=,tag:acbMS613StWo1IVnKK+5uQ==,type:str] # this-is-a-comment dolor: ENC[AES256_GCM,data:21fI,iv:01LXdHZYwLTeyUB1YWIAM6KF8cPPVsw/RuQO+Ab4pgM=,tag:o1xnCIIoccWzdWxB2kZYKg==,type:str] sops: kms: [] gcp_kms: [] azure_kv: [] lastmodified: '2019-08-29T22:42:03Z' mac: ENC[AES256_GCM,data:xKkcsqrAHxyqwgv+IVqx52AmrJdC607Dc/Ughna2e2UnnHteXTw7LGt4d0sSlw8LgjaXpa+T6lQ0MgnMPjgTEa20lbtVtauCDdRCnR7Z/Vdk7t7uLl94+STD7C1H6obnOe4fG6c2cUfDNHoeABLetti2ZBZOSZDkWQeCucLys/s=,iv:k9ODJLNYsBedQKMHcgn0KUXPlunOq5jDFH9BeJOyYRE=,tag:d+Ga71V+gijiWLFZ7BhQgg==,type:str] pgp: - created_at: '2019-08-29T22:42:03Z' enc: |- -----BEGIN PGP MESSAGE----- wcBMAyUpShfNkFB/AQgAMgUtep3vurVXoOI1h4Ovr3YbYz+gRHAPGMAUdXQdX7az fmh7Eq+6Eye2wpnAaogJ2RtIoYO1F/jkoQO74mgaLXNu/gtr8b6Ejc61sQSyjnjg N9I50+Bh075TqzToZTo7gOwOlltMjA/UVGD0z+gPHP1MpcVpUvm7C0Ol5L0Co37O UvLrQJjw1x6ktAawWokw910iX9usXiTj87fYvaqutNKRBfh1LI0Os2H8C9xpJu7q I60NcqF7JQbAaaumRroQpF2K7RI1nt+qTshPqWzDLfjzlrCVPnJlxludqvHUKzR3 TnJdb4Dsx3o/XEQkZxpl7RZa9SZZuKg3EejTxOj0ZtLgAeRLzLl0gSeMGvkntASq NQkM4e1p4AjgJOEqrOCw4h0xtdrg9+UczwVpI4rnibRqJZtEhFHRaupX6leAEi1l FMikSjpGSOB55PVMt6+e4ruokoulnq32VRTi07691uEeowA= =ft04 -----END PGP MESSAGE----- fp: FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4 unencrypted_suffix: _unencrypted version: 3.3.1 0707010000006B000081A400000000000000000000000162794F93000007B5000000000000000000000000000000000000003300000000sops-3.7.3/functional-tests/res/multiple_keys.yamlmessage: ENC[AES256_GCM,data:4soxy1Q=,iv:GGOFFLu8aqMEyoOBdMGdevug7E0R8H0iDyXIKQ5ufzM=,tag:954eipkbSu2U64tx2usiBA==,type:str] sops: kms: [] gcp_kms: [] azure_kv: [] lastmodified: '2019-08-29T22:22:04Z' mac: ENC[AES256_GCM,data:Rcyh6DKgpd8WNvLqnPhMRYDGztK/WjNJ+SisCD4qL50wxiYUgwRGs5S9I8kOFm4+8pruOcQyCVIDzyjQHZfM1bKs+CnMJTrp1HDz2vhIKkE8O6EdE5+JbNxwc6ZFQE6YThmx6rz93gMHnDOcrO0XG2guGxTumM5xPkZsEBfpIyQ=,iv:YI0Yc2khjy+q1BxiEBDejgymyAQmBkdyl3fQUm76/1Y=,tag:pf4ZbMy9TrpTA/B2qVEkmw==,type:str] pgp: - created_at: '2019-08-29T22:22:04Z' enc: |- -----BEGIN PGP MESSAGE----- wcBMAyUpShfNkFB/AQgAQH8KFrXipAGaHsKjQecpZ4R5PsWg4nqetgsy6FTcAI3j KwvBoXapI+2m5aXjAdO4k76ZxGyyXXdwe6ebbuikxCuDl28m86o9xNxbBv5SxbaO gTxtUR+swcnYRyfVmkd07HhATXQw7b2q4ERNJAVH9zxJcmzNzP6q9eRjePRWXSjn xxzpnS9kQkevuzJXg5dhVZ9HnsuVEthK88c9H3ZZQt/kg3GLaj2XDhFUt9Xy3POl 3Q2dE8xzVXzg5rF+0n8H0WpiRJ7hbvW6kvAanKNUDeNLY2shmkKiIzh4W0f2Vl5Q W1nw23Tga6IQ6mSqLItZB5bYX/C9K33kceV1DYyW+tLgAeTofEdQuueh2myAYmmV Wjap4eAq4F/gi+EpuOAS4jDaEhPgY+UkojNLl9ow2EkyrIMMJCKDSzuZcmZhwTYl 7/RSqGHDHuDX5GvXVgnRiqnoDiPsImCDN5/iNmoSVuG9cgA= =wVy4 -----END PGP MESSAGE----- fp: FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4 - created_at: '2019-08-29T22:22:04Z' enc: | -----BEGIN PGP MESSAGE----- hIwDXFUltYFwV4MBA/9/qqKDEbT9oZJqB7Z7R1RAz+TStVUzlE9+M0ScFuguBUpd 7doLhhgBYa9ygti5pnAN4E1SI97xPhflGZLTp4OtdTg5UGOUwh6BM0BZI1ai6dlK u4ra0BKOmDzv9SkbTngStXfPPLC9msc6sZe9TER88puqNAZ4DBrTyTtEAmg5wtJe AecV7hxDzAGleAdmaTC+WHkvg/EgwY/9PaU1IMf6tCx8O/bS8JQhxmptO1aAm5Vr dO/SV2m9QStMafO6OG1aGpy3zkVytlxGavkUK+dPp5fYbRYGQVfUiGpgRRZEZg== =NlDf -----END PGP MESSAGE----- fp: D7229043384BCC60326C6FB9D8720D957C3D3074 unencrypted_suffix: _unencrypted version: 3.3.1 0707010000006C000081A400000000000000000000000162794F9300000456000000000000000000000000000000000000002C00000000sops-3.7.3/functional-tests/res/no_mac.yamlmyapp1: ENC[AES256_GCM,data:NjYyOQ1GyFUhy25XX7cz7CZo4tLt,iv:WeWmnZCG7/pm71VMd37ua3LsZ6NnqQzLAyN8ylfYHb8=,tag:zh2hOpxHZ8ON+sJgB+OnTg==,type:str] sops: kms: [] gcp_kms: [] azure_kv: [] lastmodified: '2019-08-29T22:23:01Z' pgp: - created_at: '2019-08-29T22:23:00Z' enc: |- -----BEGIN PGP MESSAGE----- wcBMAyUpShfNkFB/AQgABuzcPMJSK+DvnPKZVyYlmDLCM9BobLz/7/F8zyX4O00T UN7/0Lc+MwZWUOe7lmTVgorDLcfxLONbLg9nxXO+apbb2gRABRYdggosMFaRUn3+ m+maKQ4yDTIjIvblVa/olN8reD4Bt037mT52IxTZBLDAnyVw45dRTe9mzJ73eTi7 1Mk5s6s1ZH52SQHtbY4TSqCnOKodn3UkTjnFmDXMSAzCwBDmK/wv+fDufXSkLpeR AGufxo697as2bQ8mpqgPPTvB04MgXwnTngUQ8BfagyOndgTCrU9YMA1gewJnJJj4 azRP9SOukqtn0c/LpewiOHHk/w00dMLEk0E8DpxMItLgAeQQGaX95PJfl3vuPeRT 2CA24Qbb4DbgseGimeBG4oS0oCLg4OVDd1tvVtu4+6VGKJr8YvgDk7WpDAK33LoO QBWfQ5mPIeAN5GVWJ2Mk1qcSQTlO9LmM1vPiWojLjOEvZAA= =5Gax -----END PGP MESSAGE----- fp: FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4 unencrypted_suffix: _unencrypted version: 3.3.1 0707010000006D000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000002000000000sops-3.7.3/functional-tests/src0707010000006E000081A400000000000000000000000162794F9300005CEC000000000000000000000000000000000000002700000000sops-3.7.3/functional-tests/src/lib.rsextern crate tempdir; extern crate serde; extern crate serde_json; extern crate serde_yaml; #[macro_use] extern crate lazy_static; #[macro_use] extern crate serde_derive; #[cfg(test)] mod tests { extern crate serde; extern crate serde_json; extern crate serde_yaml; use std::env; use std::fs::File; use std::io::{Write, Read}; use tempdir::TempDir; use std::process::Command; use serde_yaml::Value; use std::path::Path; const SOPS_BINARY_PATH: &'static str = "./sops"; const KMS_KEY: &'static str = "FUNCTIONAL_TEST_KMS_ARN"; macro_rules! assert_encrypted { ($object:expr, $key:expr) => { assert!($object.get(&$key).is_some()); match *$object.get(&$key).unwrap() { Value::String(ref s) => { assert!(s.starts_with("ENC["), "Value is not encrypted"); } _ => panic!("Value under key was not a string"), } } } lazy_static! { static ref TMP_DIR: TempDir = TempDir::new("sops-functional-tests") .expect("Unable to create temporary directory"); } fn prepare_temp_file(name: &str, contents: &[u8]) -> String { let file_path = TMP_DIR.path().join(name); let mut tmp_file = File::create(file_path.clone()).expect("Unable to create temporary file"); tmp_file.write_all(&contents) .expect("Error writing to temporary file"); file_path.to_string_lossy().into_owned() } #[test] fn encrypt_json_file() { let file_path = prepare_temp_file("test_encrypt.json", b"{ \"foo\": 2, \"bar\": \"baz\" }"); let output = Command::new(SOPS_BINARY_PATH) .arg("-e") .arg(file_path.clone()) .output() .expect("Error running sops"); assert!(output.status.success(), "sops didn't exit successfully"); let json = &String::from_utf8_lossy(&output.stdout); let data: Value = serde_json::from_str(json).expect("Error parsing sops's JSON output"); match data.into() { Value::Mapping(m) => { assert!(m.get(&Value::String("sops".to_owned())).is_some(), "sops metadata branch not found"); assert_encrypted!(&m, Value::String("foo".to_owned())); assert_encrypted!(&m, Value::String("bar".to_owned())); } _ => panic!("sops's JSON output is not an object"), } } #[test] #[ignore] fn publish_json_file_s3() { let file_path = prepare_temp_file("test_encrypt_publish_s3.json", b"{ \"foo\": 2, \"bar\": \"baz\" }"); assert!(Command::new(SOPS_BINARY_PATH) .arg("-e") .arg("-i") .arg(file_path.clone()) .output() .expect("Error running sops") .status .success(), "SOPS failed to encrypt a file"); assert!(Command::new(SOPS_BINARY_PATH) .arg("publish") .arg("--yes") .arg(file_path.clone()) .output() .expect("Error running sops") .status .success(), "sops failed to publish a file to S3"); //TODO: Check that file exists in S3 Bucket } #[test] fn publish_json_file_vault() { let file_path = prepare_temp_file("test_encrypt_publish_vault.json", b"{ \"foo\": 2, \"bar\": \"baz\" }"); assert!(Command::new(SOPS_BINARY_PATH) .arg("-e") .arg("-i") .arg(file_path.clone()) .output() .expect("Error running sops") .status .success(), "SOPS failed to encrypt a file"); assert!(Command::new(SOPS_BINARY_PATH) .arg("publish") .arg("--yes") .arg(file_path.clone()) .output() .expect("Error running sops") .status .success(), "sops failed to publish a file to Vault"); //TODO: Check that file exists in Vault } #[test] fn publish_json_file_vault_version_1() { let file_path = prepare_temp_file("test_encrypt_publish_vault_version_1.json", b"{ \"foo\": 2, \"bar\": \"baz\" }"); assert!(Command::new(SOPS_BINARY_PATH) .arg("-e") .arg("-i") .arg(file_path.clone()) .output() .expect("Error running sops") .status .success(), "SOPS failed to encrypt a file"); assert!(Command::new(SOPS_BINARY_PATH) .arg("publish") .arg("--yes") .arg(file_path.clone()) .output() .expect("Error running sops") .status .success(), "sops failed to publish a file to Vault"); //TODO: Check that file exists in Vault } #[test] #[ignore] fn encrypt_json_file_kms() { let kms_arn = env::var(KMS_KEY).expect("Expected $FUNCTIONAL_TEST_KMS_ARN env var to be set"); let file_path = prepare_temp_file("test_encrypt_kms.json", b"{ \"foo\": 2, \"bar\": \"baz\" }"); let output = Command::new(SOPS_BINARY_PATH) .arg("--kms") .arg(kms_arn) .arg("-e") .arg(file_path.clone()) .output() .expect("Error running sops"); assert!(output.status.success(), "sops didn't exit successfully"); let json = &String::from_utf8_lossy(&output.stdout); let data: Value = serde_json::from_str(json).expect("Error parsing sops's JSON output"); match data.into() { Value::Mapping(m) => { assert!(m.get(&Value::String("sops".to_owned())).is_some(), "sops metadata branch not found"); assert_encrypted!(&m, Value::String("foo".to_owned())); assert_encrypted!(&m, Value::String("bar".to_owned())); } _ => panic!("sops's JSON output is not an object"), } } #[test] fn encrypt_yaml_file() { let file_path = prepare_temp_file("test_encrypt.yaml", b"foo: 2 bar: baz"); let output = Command::new(SOPS_BINARY_PATH) .arg("-e") .arg(file_path.clone()) .output() .expect("Error running sops"); assert!(output.status.success(), "sops didn't exit successfully"); let json = &String::from_utf8_lossy(&output.stdout); let data: Value = serde_yaml::from_str(&json).expect("Error parsing sops's JSON output"); match data.into() { Value::Mapping(m) => { assert!(m.get(&Value::String("sops".to_owned())).is_some(), "sops metadata branch not found"); assert_encrypted!(&m, Value::String("foo".to_owned())); assert_encrypted!(&m, Value::String("bar".to_owned())); } _ => panic!("sops's YAML output is not a mapping"), } } #[test] fn set_json_file_update() { let file_path = prepare_temp_file("test_set_update.json", r#"{"a": 2, "b": "ba"}"#.as_bytes()); Command::new(SOPS_BINARY_PATH) .arg("-e") .arg("-i") .arg(file_path.clone()) .output() .expect("Error running sops"); let output = Command::new(SOPS_BINARY_PATH) .arg("--set") .arg(r#"["a"] {"aa": "aaa"}"#) .arg(file_path.clone()) .output() .expect("Error running sops"); println!("stdout: {}, stderr: {}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr)); let mut s = String::new(); File::open(file_path).unwrap().read_to_string(&mut s).unwrap(); let data: Value = serde_json::from_str(&s).expect("Error parsing sops's JSON output"); if let Value::Mapping(data) = data { let a = data.get(&Value::String("a".to_owned())).unwrap(); if let &Value::Mapping(ref a) = a { assert_encrypted!(&a, Value::String("aa".to_owned())); return; } } panic!("Output JSON does not have the expected structure"); } #[test] fn set_json_file_insert() { let file_path = prepare_temp_file("test_set_insert.json", r#"{"a": 2, "b": "ba"}"#.as_bytes()); Command::new(SOPS_BINARY_PATH) .arg("-e") .arg("-i") .arg(file_path.clone()) .output() .expect("Error running sops"); let output = Command::new(SOPS_BINARY_PATH) .arg("--set") .arg(r#"["c"] {"cc": "ccc"}"#) .arg(file_path.clone()) .output() .expect("Error running sops"); println!("stdout: {}, stderr: {}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr)); let mut s = String::new(); File::open(file_path).unwrap().read_to_string(&mut s).unwrap(); let data: Value = serde_json::from_str(&s).expect("Error parsing sops's JSON output"); if let Value::Mapping(data) = data { let a = data.get(&Value::String("c".to_owned())).unwrap(); if let &Value::Mapping(ref a) = a { assert_encrypted!(&a, Value::String("cc".to_owned())); return; } } panic!("Output JSON does not have the expected structure"); } #[test] fn set_yaml_file_update() { let file_path = prepare_temp_file("test_set_update.yaml", r#"a: 2 b: ba"# .as_bytes()); Command::new(SOPS_BINARY_PATH) .arg("-e") .arg("-i") .arg(file_path.clone()) .output() .expect("Error running sops"); let output = Command::new(SOPS_BINARY_PATH) .arg("--set") .arg(r#"["a"] {"aa": "aaa"}"#) .arg(file_path.clone()) .output() .expect("Error running sops"); println!("stdout: {}, stderr: {}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr)); let mut s = String::new(); File::open(file_path).unwrap().read_to_string(&mut s).unwrap(); let data: Value = serde_yaml::from_str(&s).expect("Error parsing sops's JSON output"); if let Value::Mapping(data) = data { let a = data.get(&Value::String("a".to_owned())).unwrap(); if let &Value::Mapping(ref a) = a { assert_encrypted!(&a, Value::String("aa".to_owned())); return; } } panic!("Output JSON does not have the expected structure"); } #[test] fn set_yaml_file_insert() { let file_path = prepare_temp_file("test_set_insert.yaml", r#"a: 2 b: ba"# .as_bytes()); Command::new(SOPS_BINARY_PATH) .arg("-e") .arg("-i") .arg(file_path.clone()) .output() .expect("Error running sops"); let output = Command::new(SOPS_BINARY_PATH) .arg("--set") .arg(r#"["c"] {"cc": "ccc"}"#) .arg(file_path.clone()) .output() .expect("Error running sops"); println!("stdout: {}, stderr: {}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr)); let mut s = String::new(); File::open(file_path).unwrap().read_to_string(&mut s).unwrap(); let data: Value = serde_yaml::from_str(&s).expect("Error parsing sops's JSON output"); if let Value::Mapping(data) = data { let a = data.get(&Value::String("c".to_owned())).unwrap(); if let &Value::Mapping(ref a) = a { assert_encrypted!(&a, Value::String("cc".to_owned())); return; } } panic!("Output YAML does not have the expected structure"); } #[test] fn set_yaml_file_string() { let file_path = prepare_temp_file("test_set_string.yaml", r#"a: 2 b: ba"# .as_bytes()); Command::new(SOPS_BINARY_PATH) .arg("-e") .arg("-i") .arg(file_path.clone()) .output() .expect("Error running sops"); Command::new(SOPS_BINARY_PATH) .arg("-e") .arg("-i") .arg("--set") .arg(r#"["a"] "aaa""#) .arg(file_path.clone()) .output() .expect("Error running sops"); let output = Command::new(SOPS_BINARY_PATH) .arg("-d") .arg("-i") .arg(file_path.clone()) .output() .expect("Error running sops"); println!("stdout: {}, stderr: {}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr)); let mut s = String::new(); File::open(file_path).unwrap().read_to_string(&mut s).unwrap(); let data: Value = serde_yaml::from_str(&s).expect("Error parsing sops's YAML output"); if let Value::Mapping(data) = data { let a = data.get(&Value::String("a".to_owned())).unwrap(); assert_eq!(a, &Value::String("aaa".to_owned())); } else { panic!("Output JSON does not have the expected structure"); } } #[test] fn decrypt_file_no_mac() { let file_path = prepare_temp_file("test_decrypt_no_mac.yaml", include_bytes!("../res/no_mac.yaml")); assert!(!Command::new(SOPS_BINARY_PATH) .arg("-d") .arg(file_path.clone()) .output() .expect("Error running sops") .status .success(), "SOPS allowed decrypting a file with no MAC without --ignore-mac"); assert!(Command::new(SOPS_BINARY_PATH) .arg("-d") .arg("--ignore-mac") .arg(file_path.clone()) .output() .expect("Error running sops") .status .success(), "SOPS failed to decrypt a file with no MAC with --ignore-mac passed in"); } #[test] fn encrypt_comments() { let file_path = "res/comments.yaml"; let output = Command::new(SOPS_BINARY_PATH) .arg("-e") .arg(file_path.clone()) .output() .expect("Error running sops"); assert!(output.status.success(), "SOPS didn't return successfully"); assert!(!String::from_utf8_lossy(&output.stdout).contains("first comment in file"), "Comment was not encrypted"); assert!(!String::from_utf8_lossy(&output.stdout).contains("this-is-a-comment"), "Comment was not encrypted"); } #[test] fn encrypt_comments_list() { let file_path = "res/comments_list.yaml"; let output = Command::new(SOPS_BINARY_PATH) .arg("-e") .arg(file_path.clone()) .output() .expect("Error running sops"); assert!(output.status.success(), "SOPS didn't return successfully"); assert!(!String::from_utf8_lossy(&output.stdout).contains("this-is-a-comment"), "Comment was not encrypted"); assert!(!String::from_utf8_lossy(&output.stdout).contains("this-is-a-comment"), "Comment was not encrypted"); } #[test] fn decrypt_comments() { let file_path = "res/comments.enc.yaml"; let output = Command::new(SOPS_BINARY_PATH) .arg("-d") .arg(file_path.clone()) .output() .expect("Error running sops"); assert!(output.status.success(), "SOPS didn't return successfully"); assert!(String::from_utf8_lossy(&output.stdout).contains("first comment in file"), "Comment was not decrypted"); assert!(String::from_utf8_lossy(&output.stdout).contains("this-is-a-comment"), "Comment was not decrypted"); } #[test] fn decrypt_comments_unencrypted_comments() { let file_path = "res/comments_unencrypted_comments.yaml"; let output = Command::new(SOPS_BINARY_PATH) .arg("-d") .arg(file_path.clone()) .output() .expect("Error running sops"); assert!(output.status.success(), "SOPS didn't return successfully"); assert!(String::from_utf8_lossy(&output.stdout).contains("first comment in file"), "Comment was not decrypted"); assert!(String::from_utf8_lossy(&output.stdout).contains("this-is-a-comment"), "Comment was not decrypted"); } #[test] fn roundtrip_shamir() { // The .sops.yaml file ensures this file is encrypted with two key groups, each with one GPG key let file_path = prepare_temp_file("test_roundtrip_keygroups.yaml", "a: secret".as_bytes()); let output = Command::new(SOPS_BINARY_PATH) .arg("-i") .arg("-e") .arg(file_path.clone()) .output() .expect("Error running sops"); assert!(output.status.success(), "SOPS failed to encrypt a file with Shamir Secret Sharing"); let output = Command::new(SOPS_BINARY_PATH) .arg("-d") .arg(file_path.clone()) .output() .expect("Error running sops"); assert!(output.status .success(), "SOPS failed to decrypt a file with Shamir Secret Sharing"); assert!(String::from_utf8_lossy(&output.stdout).contains("secret")); } #[test] fn roundtrip_shamir_missing_decryption_key() { // The .sops.yaml file ensures this file is encrypted with two key groups, each with one GPG key, // but we don't have one of the private keys let file_path = prepare_temp_file("test_roundtrip_keygroups_missing_decryption_key.yaml", "a: secret".as_bytes()); let output = Command::new(SOPS_BINARY_PATH) .arg("-i") .arg("-e") .arg(file_path.clone()) .output() .expect("Error running sops"); assert!(output.status.success(), "SOPS failed to encrypt a file with Shamir Secret Sharing"); let output = Command::new(SOPS_BINARY_PATH) .arg("-d") .arg(file_path.clone()) .output() .expect("Error running sops"); assert!(!output.status .success(), "SOPS succeeded decrypting a file with a missing decrytion key"); } #[test] fn test_decrypt_file_multiple_keys() { let file_path = prepare_temp_file("test_decrypt_file_multiple_keys.yaml", include_bytes!("../res/multiple_keys.yaml")); let output = Command::new(SOPS_BINARY_PATH) .arg("-d") .arg(file_path.clone()) .output() .expect("Error running sops"); assert!(output.status .success(), "SOPS failed to decrypt a file that uses multiple keys"); } #[test] fn extract_string() { let file_path = prepare_temp_file("test_extract_string.yaml", "multiline: |\n multi\n line".as_bytes()); let output = Command::new(SOPS_BINARY_PATH) .arg("-i") .arg("-e") .arg(file_path.clone()) .output() .expect("Error running sops"); assert!(output.status.success(), "SOPS failed to encrypt a file"); let output = Command::new(SOPS_BINARY_PATH) .arg("--extract") .arg("[\"multiline\"]") .arg("-d") .arg(file_path.clone()) .output() .expect("Error running sops"); assert!(output.status .success(), "SOPS failed to extract"); assert_eq!(output.stdout, b"multi\nline"); } #[test] fn roundtrip_binary() { let data = b"\"\"{}this_is_binary_data"; let file_path = prepare_temp_file("test.binary", data); let output = Command::new(SOPS_BINARY_PATH) .arg("-i") .arg("-e") .arg(file_path.clone()) .output() .expect("Error running sops"); assert!(output.status.success(), "SOPS failed to encrypt a binary file"); let output = Command::new(SOPS_BINARY_PATH) .arg("-d") .arg(file_path.clone()) .output() .expect("Error running sops"); assert!(output.status .success(), "SOPS failed to decrypt a binary file"); assert_eq!(output.stdout, data); } #[test] #[ignore] fn roundtrip_kms_encryption_context() { let kms_arn = env::var(KMS_KEY).expect("Expected $FUNCTIONAL_TEST_KMS_ARN env var to be set"); let file_path = prepare_temp_file("test_roundtrip_kms_encryption_context.json", b"{ \"foo\": 2, \"bar\": \"baz\" }"); let output = Command::new(SOPS_BINARY_PATH) .arg("--kms") .arg(kms_arn) .arg("--encryption-context") .arg("foo:bar,one:two") .arg("-i") .arg("-e") .arg(file_path.clone()) .output() .expect("Error running sops"); assert!(output.status.success(), "sops didn't exit successfully"); let output = Command::new(SOPS_BINARY_PATH) .arg("-d") .arg(file_path.clone()) .output() .expect("Error running sops"); assert!(output.status .success(), "SOPS failed to decrypt a file with KMS Encryption Context"); assert!(String::from_utf8_lossy(&output.stdout).contains("foo")); assert!(String::from_utf8_lossy(&output.stdout).contains("baz")); } #[test] fn output_flag() { let input_path = prepare_temp_file("test_output_flag.binary", b"foo"); let output_path = Path::join(TMP_DIR.path(), "output_flag.txt"); let output = Command::new(SOPS_BINARY_PATH) .arg("--output") .arg(&output_path) .arg("-e") .arg(input_path.clone()) .output() .expect("Error running sops"); assert!(output.status .success(), "SOPS failed to decrypt a binary file"); assert_eq!(output.stdout, &[]); let mut f = File::open(&output_path).expect("output file not found"); let mut contents = String::new(); f.read_to_string(&mut contents) .expect("couldn't read output file contents"); assert_ne!(contents, "", "Output file is empty"); } } 0707010000006F000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001200000000sops-3.7.3/gcpkms07070100000070000081A400000000000000000000000162794F9300001551000000000000000000000000000000000000001F00000000sops-3.7.3/gcpkms/keysource.gopackage gcpkms //import "go.mozilla.org/sops/v3/gcpkms" import ( "encoding/base64" "fmt" "google.golang.org/api/option" "os" "regexp" "strings" "time" "go.mozilla.org/sops/v3/logging" "github.com/sirupsen/logrus" "golang.org/x/net/context" cloudkms "google.golang.org/api/cloudkms/v1" ) var log *logrus.Logger func init() { log = logging.NewLogger("GCPKMS") } // MasterKey is a GCP KMS key used to encrypt and decrypt sops' data key. type MasterKey struct { ResourceID string EncryptedKey string CreationDate time.Time } // EncryptedDataKey returns the encrypted data key this master key holds func (key *MasterKey) EncryptedDataKey() []byte { return []byte(key.EncryptedKey) } // SetEncryptedDataKey sets the encrypted data key for this master key func (key *MasterKey) SetEncryptedDataKey(enc []byte) { key.EncryptedKey = string(enc) } // Encrypt takes a sops data key, encrypts it with GCP KMS and stores the result in the EncryptedKey field func (key *MasterKey) Encrypt(dataKey []byte) error { cloudkmsService, err := key.createCloudKMSService() if err != nil { log.WithField("resourceID", key.ResourceID).Info("Encryption failed") return fmt.Errorf("Cannot create GCP KMS service: %w", err) } req := &cloudkms.EncryptRequest{ Plaintext: base64.StdEncoding.EncodeToString(dataKey), } resp, err := cloudkmsService.Projects.Locations.KeyRings.CryptoKeys.Encrypt(key.ResourceID, req).Do() if err != nil { log.WithField("resourceID", key.ResourceID).Info("Encryption failed") return fmt.Errorf("Failed to call GCP KMS encryption service: %w", err) } log.WithField("resourceID", key.ResourceID).Info("Encryption succeeded") key.EncryptedKey = resp.Ciphertext return nil } // EncryptIfNeeded encrypts the provided sops' data key and encrypts it if it hasn't been encrypted yet func (key *MasterKey) EncryptIfNeeded(dataKey []byte) error { if key.EncryptedKey == "" { return key.Encrypt(dataKey) } return nil } // Decrypt decrypts the EncryptedKey field with CGP KMS and returns the result. func (key *MasterKey) Decrypt() ([]byte, error) { cloudkmsService, err := key.createCloudKMSService() if err != nil { log.WithField("resourceID", key.ResourceID).Info("Decryption failed") return nil, fmt.Errorf("Cannot create GCP KMS service: %w", err) } req := &cloudkms.DecryptRequest{ Ciphertext: key.EncryptedKey, } resp, err := cloudkmsService.Projects.Locations.KeyRings.CryptoKeys.Decrypt(key.ResourceID, req).Do() if err != nil { log.WithField("resourceID", key.ResourceID).Info("Decryption failed") return nil, fmt.Errorf("Error decrypting key: %w", err) } encryptedKey, err := base64.StdEncoding.DecodeString(resp.Plaintext) if err != nil { log.WithField("resourceID", key.ResourceID).Info("Decryption failed") return nil, err } log.WithField("resourceID", key.ResourceID).Info("Decryption succeeded") return encryptedKey, nil } // NeedsRotation returns whether the data key needs to be rotated or not. func (key *MasterKey) NeedsRotation() bool { return time.Since(key.CreationDate) > (time.Hour * 24 * 30 * 6) } // ToString converts the key to a string representation func (key *MasterKey) ToString() string { return key.ResourceID } // NewMasterKeyFromResourceID takes a GCP KMS resource ID string and returns a new MasterKey for that func NewMasterKeyFromResourceID(resourceID string) *MasterKey { k := &MasterKey{} resourceID = strings.Replace(resourceID, " ", "", -1) k.ResourceID = resourceID k.CreationDate = time.Now().UTC() return k } // MasterKeysFromResourceIDString takes a comma separated list of GCP KMS resource IDs and returns a slice of new MasterKeys for them func MasterKeysFromResourceIDString(resourceID string) []*MasterKey { var keys []*MasterKey if resourceID == "" { return keys } for _, s := range strings.Split(resourceID, ",") { keys = append(keys, NewMasterKeyFromResourceID(s)) } return keys } func (key MasterKey) createCloudKMSService() (*cloudkms.Service, error) { re := regexp.MustCompile(`^projects/[^/]+/locations/[^/]+/keyRings/[^/]+/cryptoKeys/[^/]+$`) matches := re.FindStringSubmatch(key.ResourceID) if matches == nil { return nil, fmt.Errorf("No valid resourceId found in %q", key.ResourceID) } ctx := context.Background() var options []option.ClientOption if credentials, err := getGoogleCredentials(); err != nil { return nil, err } else if len(credentials) > 0 { options = append(options, option.WithCredentialsJSON(credentials)) } cloudkmsService, err := cloudkms.NewService(ctx, options...) if err != nil { return nil, err } return cloudkmsService, nil } // ToMap converts the MasterKey to a map for serialization purposes func (key MasterKey) ToMap() map[string]interface{} { out := make(map[string]interface{}) out["resource_id"] = key.ResourceID out["enc"] = key.EncryptedKey out["created_at"] = key.CreationDate.UTC().Format(time.RFC3339) return out } // getGoogleCredentials looks for a GCP Service Account in the environment // variable: GOOGLE_CREDENTIALS, set as either a path to a credentials file or directly as the // variable's value in JSON format. // // If not set, will default to use GOOGLE_APPLICATION_CREDENTIALS func getGoogleCredentials() ([]byte, error) { defaultCredentials := os.Getenv("GOOGLE_CREDENTIALS") if _, err := os.Stat(defaultCredentials); err == nil { return os.ReadFile(defaultCredentials) } return []byte(defaultCredentials), nil } 07070100000071000081A400000000000000000000000162794F930000049F000000000000000000000000000000000000002400000000sops-3.7.3/gcpkms/keysource_test.gopackage gcpkms import ( "testing" "time" "github.com/stretchr/testify/assert" ) func TestGCPKMSKeySourceFromString(t *testing.T) { s := "projects/sops-testing1/locations/global/keyRings/creds/cryptoKeys/key1, projects/sops-testing2/locations/global/keyRings/creds/cryptoKeys/key2" ks := MasterKeysFromResourceIDString(s) k1 := ks[0] k2 := ks[1] expectedResourceID1 := "projects/sops-testing1/locations/global/keyRings/creds/cryptoKeys/key1" expectedResourceID2 := "projects/sops-testing2/locations/global/keyRings/creds/cryptoKeys/key2" if k1.ResourceID != expectedResourceID1 { t.Errorf("ResourceID mismatch. Expected %s, found %s", expectedResourceID1, k1.ResourceID) } if k2.ResourceID != expectedResourceID2 { t.Errorf("ResourceID mismatch. Expected %s, found %s", expectedResourceID2, k2.ResourceID) } } func TestKeyToMap(t *testing.T) { key := MasterKey{ CreationDate: time.Date(2016, time.October, 31, 10, 0, 0, 0, time.UTC), ResourceID: "foo", EncryptedKey: "this is encrypted", } assert.Equal(t, map[string]interface{}{ "resource_id": "foo", "enc": "this is encrypted", "created_at": "2016-10-31T10:00:00Z", }, key.ToMap()) } 07070100000072000081A400000000000000000000000162794F930000158C000000000000000000000000000000000000001200000000sops-3.7.3/go.modmodule go.mozilla.org/sops/v3 go 1.17 require ( cloud.google.com/go/storage v1.22.0 filippo.io/age v1.0.0 github.com/Azure/azure-sdk-for-go v63.3.0+incompatible github.com/Azure/go-autorest/autorest v0.11.26 github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 github.com/aws/aws-sdk-go v1.43.43 github.com/blang/semver v3.5.1+incompatible github.com/fatih/color v1.13.0 github.com/golang/protobuf v1.5.2 github.com/google/go-cmp v0.5.7 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/goware/prefixer v0.0.0-20160118172347-395022866408 github.com/hashicorp/vault/api v1.5.0 github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef github.com/lib/pq v1.10.5 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-wordwrap v1.0.1 github.com/ory/dockertest v3.3.5+incompatible github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.1 go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 golang.org/x/net v0.0.0-20220420153159-1850ba15e1be golang.org/x/sys v0.0.0-20220412211240-33da011f77ad google.golang.org/api v0.74.0 google.golang.org/grpc v1.45.0 google.golang.org/protobuf v1.28.0 gopkg.in/ini.v1 v1.66.4 gopkg.in/urfave/cli.v1 v1.20.0 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) require ( cloud.google.com/go v0.100.2 // indirect cloud.google.com/go/compute v1.5.0 // indirect cloud.google.com/go/iam v0.3.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/armon/go-metrics v0.3.10 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect github.com/containerd/continuity v0.2.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dimchansky/utfbom v1.1.1 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect github.com/golang-jwt/jwt/v4 v4.3.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect github.com/googleapis/gax-go/v2 v2.2.0 // indirect github.com/googleapis/go-type-adapters v1.0.0 // indirect github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.2.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.4.3 // indirect github.com/hashicorp/go-retryablehttp v0.7.0 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/mlock v0.1.2 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.1.3 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/go-uuid v1.0.2 // indirect github.com/hashicorp/go-version v1.4.0 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/vault/sdk v0.4.1 // indirect github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/run v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/opencontainers/runc v1.1.0 // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/stretchr/objx v0.3.0 // indirect go.opencensus.io v0.23.0 // indirect go.uber.org/atomic v1.9.0 // indirect golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gotest.tools v2.2.0+incompatible // indirect ) 07070100000073000081A400000000000000000000000162794F9300018C60000000000000000000000000000000000000001200000000sops-3.7.3/go.sumbazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= cloud.google.com/go/compute v1.5.0 h1:b1zWmYuuHz7gO9kDcM/EpHGr06UgsYNRpNJzI2kFiLM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.22.0 h1:NUV0NNp9nkBuW66BFRLuMgldN60C57ET3dhbwLIYio8= cloud.google.com/go/storage v1.22.0/go.mod h1:GbaLEoMqbVm6sx3Z0R++gSiBlgMv6yUi2q1DeGFKQgE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/age v1.0.0 h1:V6q14n0mqYU3qKFkZ6oOaF9oXneOviS3ubXsSVBRSzc= filippo.io/age v1.0.0/go.mod h1:PaX+Si/Sd5G8LgfCwldsSba3H1DDQZhIhFGkhbHaBq8= filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= github.com/Azure/azure-sdk-for-go v63.3.0+incompatible h1:INepVujzUrmArRZjDLHbtER+FkvCoEwyRCXGqOlmDII= github.com/Azure/azure-sdk-for-go v63.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= github.com/Azure/go-autorest/autorest v0.11.26 h1:W/MzvoAiFfL5h4nq81wm7axvITgbnOoifXXGkFrgF1g= github.com/Azure/go-autorest/autorest v0.11.26/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 h1:P6bYXFoao05z5uhOQzbC3Qd8JqF3jUoocoTeIxkp2cA= github.com/Azure/go-autorest/autorest/azure/auth v0.5.11/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg= github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 h1:0W/yGmFdTIT77fvdlGZ0LMISoLHFJ7Tx4U0yeB+uFs4= github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 h1:cSHEbLj0GZeHM1mWG84qEnGFojNEQ83W7cwaPRjcwXU= github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo= github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.43.43 h1:1L06qzQvl4aC3Skfh5rV7xVhGHjIZoHcqy16NoyQ1o4= github.com/aws/aws-sdk-go v1.43.43/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/continuity v0.2.2 h1:QSqfxcn8c+12slxwu00AtzXrsami0MJb/MQs9lOLHLA= github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch/v5 v5.5.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog= github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gax-go/v2 v2.2.0 h1:s7jOdKSaksJVOxE0Y/S32otcfiP+UQ0cL8/GTKaONwE= github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= github.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/goware/prefixer v0.0.0-20160118172347-395022866408 h1:Y9iQJfEqnN3/Nce9cOegemcy/9Ai5k3huT6E80F3zaw= github.com/goware/prefixer v0.0.0-20160118172347-395022866408/go.mod h1:PE1ycukgRPJ7bJ9a1fdfQ9j8i/cEcRAoLZzbxYpNB/s= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4= github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw= github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= github.com/hashicorp/go-secure-stdlib/mlock v0.1.2 h1:p4AKXPPS24tO8Wc8i1gLvSKdmkiSY5xuju57czJ/IJQ= github.com/hashicorp/go-secure-stdlib/mlock v0.1.2/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.3 h1:geBw3SBrxQq+buvbf4K+Qltv1gjaXJxy8asD4CjGYow= github.com/hashicorp/go-secure-stdlib/parseutil v0.1.3/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= github.com/hashicorp/go-secure-stdlib/password v0.1.1/go.mod h1:9hH302QllNwu1o2TGYtSk8I8kTAN0ca1EHpwhm5Mmzo= github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.1/go.mod h1:l8slYwnJA26yBz+ErHpp2IRCLr0vuOMGBORIz4rRiAs= github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4= github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/vault/api v1.5.0 h1:Bp6yc2bn7CWkOrVIzFT/Qurzx528bdavF3nz590eu28= github.com/hashicorp/vault/api v1.5.0/go.mod h1:LkMdrZnWNrFaQyYYazWVn7KshilfDidgVBq6YiTq/bM= github.com/hashicorp/vault/sdk v0.4.1 h1:3SaHOJY687jY1fnB61PtL0cOkKItphrbLmux7T92HBo= github.com/hashicorp/vault/sdk v0.4.1/go.mod h1:aZ3fNuL5VNydQk8GcLJ2TV8YCRVvyaakYkhZRoVuhj0= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I= github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ= github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v1.1.0 h1:O9+X96OcDjkmmZyfaG996kV7yq8HsoU2h1XRRQcefG8= github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As= github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a h1:N7VD+PwpJME2ZfQT8+ejxwA4Ow10IkGbU0MGf94ll8k= go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a/go.mod h1:YDKUvO0b//78PaaEro6CAPH6NqohCmL2Cwju5XI2HoE= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220420153159-1850ba15e1be h1:yx80W7nvY5ySWpaU8UWaj5o9e23YgO9BRhQol7Lc+JI= golang.org/x/net v0.0.0-20220420153159-1850ba15e1be/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs= golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= google.golang.org/api v0.74.0 h1:ExR2D+5TYIrMphWgs5JCgwRhEDlPDXXrLwHHMgPHTXE= google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf h1:JTjwKJX9erVpsw17w+OIPP7iAgEkN/r8urhWSunEDTs= google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 07070100000074000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001300000000sops-3.7.3/hcvault07070100000075000081A400000000000000000000000162794F9300001F32000000000000000000000000000000000000002000000000sops-3.7.3/hcvault/keysource.gopackage hcvault import ( "bytes" "encoding/base64" "fmt" "io" "net/url" "os" "path" "path/filepath" "regexp" "strings" "time" "github.com/hashicorp/vault/api" homedir "github.com/mitchellh/go-homedir" "github.com/sirupsen/logrus" "go.mozilla.org/sops/v3/logging" ) var log *logrus.Logger func init() { log = logging.NewLogger("VAULT_TRANSIT") } // MasterKey is a Vault Transit backend path used to encrypt and decrypt sops' data key. type MasterKey struct { EncryptedKey string KeyName string EnginePath string VaultAddress string CreationDate time.Time } // NewMasterKeysFromURIs gets lots of keys from lots of URIs func NewMasterKeysFromURIs(uris string) ([]*MasterKey, error) { var keys []*MasterKey if uris == "" { return keys, nil } uriList := strings.Split(uris, ",") for _, uri := range uriList { if uri == "" { continue } key, err := NewMasterKeyFromURI(uri) if err != nil { return nil, err } keys = append(keys, key) } return keys, nil } // NewMasterKeyFromURI obtains the vaultAddress the transit backend path and the key name from the full URI of the key func NewMasterKeyFromURI(uri string) (*MasterKey, error) { log.Debugln("Called NewMasterKeyFromURI with uri: ", uri) var key *MasterKey if uri == "" { return key, nil } u, err := url.Parse(uri) if err != nil { return nil, err } if u.Scheme == "" { return nil, fmt.Errorf("missing scheme in vault URL (should be like this: https://vault.example.com:8200/v1/transit/keys/keyName), got: %v", uri) } enginePath, keyName, err := getBackendAndKeyFromPath(u.RequestURI()) if err != nil { return nil, err } u.Path = "" return NewMasterKey(u.String(), enginePath, keyName), nil } func getBackendAndKeyFromPath(fullPath string) (enginePath, keyName string, err error) { // Running vault behind a reverse proxy with longer urls seems not to be supported // by the vault client api so we have a separate Error for that here. if re := regexp.MustCompile(`/[^/]+/v[\d]+/[^/]+/[^/]+/[^/]+`); re.Match([]byte(fullPath)) { return "", "", fmt.Errorf("running Vault with a prefixed url is not supported! (Format has to be like https://vault.example.com:8200/v1/transit/keys/keyName)") } else if re := regexp.MustCompile(`/v[\d]+/[^/]+/[^/]+/[^/]+`); re.Match([]byte(fullPath)) == false { return "", "", fmt.Errorf("vault path does not seem to be formatted correctly: (eg. https://vault.example.com:8200/v1/transit/keys/keyName)") } fullPath = strings.TrimPrefix(fullPath, "/") fullPath = strings.TrimSuffix(fullPath, "/") dirs := strings.Split(fullPath, "/") keyName = dirs[len(dirs)-1] enginePath = path.Join(dirs[1 : len(dirs)-2]...) err = nil return } // NewMasterKey creates a new MasterKey from a vault address, transit backend path and a key name and setting the creation date to the current date func NewMasterKey(addess, enginePath, keyName string) *MasterKey { mk := &MasterKey{ VaultAddress: addess, EnginePath: enginePath, KeyName: keyName, CreationDate: time.Now().UTC(), } log.Debugln("Created Vault Master Key: ", mk) return mk } // EncryptedDataKey returns the encrypted data key this master key holds func (key *MasterKey) EncryptedDataKey() []byte { return []byte(key.EncryptedKey) } // SetEncryptedDataKey sets the encrypted data key for this master key func (key *MasterKey) SetEncryptedDataKey(enc []byte) { key.EncryptedKey = string(enc) } func vaultClient(address string) (*api.Client, error) { cfg := api.DefaultConfig() cfg.Address = address cli, err := api.NewClient(cfg) if err != nil { return nil, fmt.Errorf("Cannot create Vault Client: %w", err) } if cli.Token() != "" { return cli, nil } homePath, err := homedir.Dir() if err != nil { panic(fmt.Sprintf("error getting user's home directory: %v", err)) } tokenPath := filepath.Join(homePath, ".vault-token") f, err := os.Open(tokenPath) if os.IsNotExist(err) { return cli, nil } if err != nil { return nil, err } defer f.Close() buf := bytes.NewBuffer(nil) if _, err := io.Copy(buf, f); err != nil { return nil, err } cli.SetToken(strings.TrimSpace(buf.String())) return cli, nil } // Encrypt takes a sops data key, encrypts it with Vault Transit and stores the result in the EncryptedKey field func (key *MasterKey) Encrypt(dataKey []byte) error { fullPath := path.Join(key.EnginePath, "encrypt", key.KeyName) cli, err := vaultClient(key.VaultAddress) if err != nil { return err } encoded := base64.StdEncoding.EncodeToString(dataKey) payload := make(map[string]interface{}) payload["plaintext"] = encoded raw, err := cli.Logical().Write(fullPath, payload) if err != nil { log.WithField("Path", fullPath).Info("Encryption failed") return err } if raw == nil || raw.Data == nil { return fmt.Errorf("The transit backend %s is empty", fullPath) } encrypted, ok := raw.Data["ciphertext"] if !ok { return fmt.Errorf("there's not encrypted data") } encryptedKey, ok := encrypted.(string) if !ok { return fmt.Errorf("the ciphertext cannot be casted to string") } key.EncryptedKey = encryptedKey return nil } // EncryptIfNeeded encrypts the provided sops' data key and encrypts it if it hasn't been encrypted yet func (key *MasterKey) EncryptIfNeeded(dataKey []byte) error { if key.EncryptedKey == "" { return key.Encrypt(dataKey) } return nil } // Decrypt decrypts the EncryptedKey field with Vault Transit and returns the result. func (key *MasterKey) Decrypt() ([]byte, error) { fullPath := path.Join(key.EnginePath, "decrypt", key.KeyName) cli, err := vaultClient(key.VaultAddress) if err != nil { return nil, err } payload := make(map[string]interface{}) payload["ciphertext"] = key.EncryptedKey raw, err := cli.Logical().Write(fullPath, payload) if err != nil { log.WithField("Path", fullPath).Info("Encryption failed") return nil, err } if raw == nil || raw.Data == nil { return nil, fmt.Errorf("The transit backend %s is empty", fullPath) } decrypted, ok := raw.Data["plaintext"] if ok != true { return nil, fmt.Errorf("there's no decrypted data") } dataKey, ok := decrypted.(string) if ok != true { return nil, fmt.Errorf("the plaintest cannot be casted to string") } result, err := base64.StdEncoding.DecodeString(dataKey) if err != nil { return nil, fmt.Errorf("Couldn't decode base64 plaintext") } return result, nil } // NeedsRotation returns whether the data key needs to be rotated or not. // This is simply copied from GCPKMS // TODO: handle key rotation on vault side func (key *MasterKey) NeedsRotation() bool { //TODO: manage rewrapping https://www.vaultproject.io/api/secret/transit/index.html#rewrap-data return time.Since(key.CreationDate) > (time.Hour * 24 * 30 * 6) } // ToString converts the key to a string representation func (key *MasterKey) ToString() string { return fmt.Sprintf("%s/v1/%s/keys/%s", key.VaultAddress, key.EnginePath, key.KeyName) } func (key *MasterKey) createVaultTransitAndKey() error { cli, err := vaultClient(key.VaultAddress) if err != nil { return err } if err != nil { return fmt.Errorf("Cannot create Vault Client: %w", err) } err = cli.Sys().Mount(key.EnginePath, &api.MountInput{ Type: "transit", Description: "backend transit used by SOPS", }) if err != nil { return err } path := path.Join(key.EnginePath, "keys", key.KeyName) payload := make(map[string]interface{}) payload["type"] = "rsa-4096" _, err = cli.Logical().Write(path, payload) if err != nil { return err } _, err = cli.Logical().Read(path) if err != nil { return err } return nil } // ToMap converts the MasterKey to a map for serialization purposes func (key MasterKey) ToMap() map[string]interface{} { out := make(map[string]interface{}) out["vault_address"] = key.VaultAddress out["key_name"] = key.KeyName out["engine_path"] = key.EnginePath out["enc"] = key.EncryptedKey out["created_at"] = key.CreationDate.UTC().Format(time.RFC3339) return out } 07070100000076000081A400000000000000000000000162794F9300000FD2000000000000000000000000000000000000002500000000sops-3.7.3/hcvault/keysource_test.gopackage hcvault import ( "fmt" logger "log" "os" "testing" "time" "github.com/hashicorp/vault/api" "github.com/ory/dockertest" "github.com/stretchr/testify/assert" ) func TestMain(m *testing.M) { // uses a sensible default on windows (tcp/http) and linux/osx (socket) pool, err := dockertest.NewPool("") if err != nil { logger.Fatalf("Could not connect to docker: %s", err) } // pulls an image, creates a container based on it and runs it resource, err := pool.Run("vault", "1.2.2", []string{"VAULT_DEV_ROOT_TOKEN_ID=secret"}) if err != nil { logger.Fatalf("Could not start resource: %s", err) } vaultAddr := fmt.Sprintf("http://%s", resource.GetHostPort("8200/tcp")) os.Setenv("VAULT_ADDR", vaultAddr) os.Setenv("VAULT_TOKEN", "secret") // exponential backoff-retry, because the application in the container might not be ready to accept connections yet if err := pool.Retry(func() error { cli, err := api.NewClient(api.DefaultConfig()) if err != nil { return fmt.Errorf("Cannot create Vault Client: %w", err) } status, err := cli.Sys().InitStatus() if err != nil { return err } if status != true { return fmt.Errorf("Vault not ready yet") } return nil }); err != nil { logger.Fatalf("Could not connect to docker: %s", err) } key := NewMasterKey(vaultAddr, "sops", "main") err = key.createVaultTransitAndKey() if err != nil { logger.Fatal(err) } code := 0 if err == nil { code = m.Run() } // You can't defer this because os.Exit doesn't care for defer if err := pool.Purge(resource); err != nil { logger.Fatalf("Could not purge resource: %s", err) } os.Exit(code) } func TestKeyToMap(t *testing.T) { key := MasterKey{ CreationDate: time.Date(2016, time.October, 31, 10, 0, 0, 0, time.UTC), VaultAddress: "http://127.0.0.1:8200", EnginePath: "foo", KeyName: "bar", EncryptedKey: "this is encrypted", } assert.Equal(t, map[string]interface{}{ "vault_address": "http://127.0.0.1:8200", "engine_path": "foo", "key_name": "bar", "enc": "this is encrypted", "created_at": "2016-10-31T10:00:00Z", }, key.ToMap()) } func TestEncryptionDecryption(t *testing.T) { dataKey := []byte("super very Secret Key!!!") key := MasterKey{ VaultAddress: os.Getenv("VAULT_ADDR"), EnginePath: "sops", KeyName: "main", } err := key.Encrypt(dataKey) if err != nil { fmt.Println(err) t.Fail() return } decrypted, err := key.Decrypt() if err != nil { fmt.Println(err) t.Fail() return } assert.Equal(t, dataKey, decrypted) } func TestNewMasterKeyFromURI(t *testing.T) { uri1 := "https://vault.example.com:8200/v1/transit/keys/keyName" uri2 := "https://vault.me.com/v1/super42/bestmarket/keys/slig" uri3 := "http://127.0.0.1:12121/v1/transit/keys/dev" mk1 := &MasterKey{ VaultAddress: "https://vault.example.com:8200", EnginePath: "transit", KeyName: "keyName", } mk2 := &MasterKey{ VaultAddress: "https://vault.me.com", EnginePath: "super42/bestmarket", KeyName: "slig", } mk3 := &MasterKey{ VaultAddress: "http://127.0.0.1:12121", EnginePath: "transit", KeyName: "dev", } genMk1, err := NewMasterKeyFromURI(uri1) if err != nil { log.Errorln(err) t.Fail() } genMk2, err := NewMasterKeyFromURI(uri2) if err != nil { log.Errorln(err) t.Fail() } genMk3, err := NewMasterKeyFromURI(uri3) if err != nil { log.Errorln(err) t.Fail() } if assert.NotNil(t, genMk1) { mk1.CreationDate = genMk1.CreationDate assert.Equal(t, mk1, genMk1) } if assert.NotNil(t, genMk2) { mk2.CreationDate = genMk2.CreationDate assert.Equal(t, mk2, genMk2) } if assert.NotNil(t, genMk3) { mk3.CreationDate = genMk3.CreationDate assert.Equal(t, mk3, genMk3) } badURIs := []string{ "vault.me/keys/dev/mykey", "http://127.0.0.1:12121/v1/keys/dev", "tcp://127.0.0.1:12121/v1/keys/dev", } for _, uri := range badURIs { if _, err = NewMasterKeyFromURI(uri); err == nil { log.Errorf("Should be a invalid uri: %s", uri) t.Fail() } } } 07070100000077000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001000000000sops-3.7.3/keys07070100000078000081A400000000000000000000000162794F930000017D000000000000000000000000000000000000001800000000sops-3.7.3/keys/keys.gopackage keys // MasterKey provides a way of securing the key used to encrypt the Tree by encrypting and decrypting said key. type MasterKey interface { Encrypt(dataKey []byte) error EncryptIfNeeded(dataKey []byte) error EncryptedDataKey() []byte SetEncryptedDataKey([]byte) Decrypt() ([]byte, error) NeedsRotation() bool ToString() string ToMap() map[string]interface{} } 07070100000079000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001600000000sops-3.7.3/keyservice0707010000007A000081A400000000000000000000000162794F9300000422000000000000000000000000000000000000002000000000sops-3.7.3/keyservice/client.gopackage keyservice import ( "golang.org/x/net/context" "google.golang.org/grpc" ) // LocalClient is a key service client that performs all operations locally type LocalClient struct { Server KeyServiceServer } // NewLocalClient creates a new local client func NewLocalClient() LocalClient { return LocalClient{Server{}} } // NewCustomLocalClient creates a new local client with a non-default backing // KeyServiceServer implementation func NewCustomLocalClient(server KeyServiceServer) LocalClient { return LocalClient{Server: server} } // Decrypt processes a decrypt request locally // See keyservice/server.go for more details func (c LocalClient) Decrypt(ctx context.Context, req *DecryptRequest, opts ...grpc.CallOption) (*DecryptResponse, error) { return c.Server.Decrypt(ctx, req) } // Encrypt processes an encrypt request locally // See keyservice/server.go for more details func (c LocalClient) Encrypt(ctx context.Context, req *EncryptRequest, opts ...grpc.CallOption) (*EncryptResponse, error) { return c.Server.Encrypt(ctx, req) } 0707010000007B000081A400000000000000000000000162794F930000070F000000000000000000000000000000000000002400000000sops-3.7.3/keyservice/keyservice.go/* Package keyservice implements a gRPC API that can be used by SOPS to encrypt and decrypt the data key using remote master keys. */ package keyservice import ( "fmt" "go.mozilla.org/sops/v3/age" "go.mozilla.org/sops/v3/azkv" "go.mozilla.org/sops/v3/gcpkms" "go.mozilla.org/sops/v3/hcvault" "go.mozilla.org/sops/v3/keys" "go.mozilla.org/sops/v3/kms" "go.mozilla.org/sops/v3/pgp" ) // KeyFromMasterKey converts a SOPS internal MasterKey to an RPC Key that can be serialized with Protocol Buffers func KeyFromMasterKey(mk keys.MasterKey) Key { switch mk := mk.(type) { case *pgp.MasterKey: return Key{ KeyType: &Key_PgpKey{ PgpKey: &PgpKey{ Fingerprint: mk.Fingerprint, }, }, } case *gcpkms.MasterKey: return Key{ KeyType: &Key_GcpKmsKey{ GcpKmsKey: &GcpKmsKey{ ResourceId: mk.ResourceID, }, }, } case *hcvault.MasterKey: return Key{ KeyType: &Key_VaultKey{ VaultKey: &VaultKey{ VaultAddress: mk.VaultAddress, EnginePath: mk.EnginePath, KeyName: mk.KeyName, }, }, } case *kms.MasterKey: ctx := make(map[string]string) for k, v := range mk.EncryptionContext { ctx[k] = *v } return Key{ KeyType: &Key_KmsKey{ KmsKey: &KmsKey{ Arn: mk.Arn, Role: mk.Role, Context: ctx, AwsProfile: mk.AwsProfile, }, }, } case *azkv.MasterKey: return Key{ KeyType: &Key_AzureKeyvaultKey{ AzureKeyvaultKey: &AzureKeyVaultKey{ VaultUrl: mk.VaultURL, Name: mk.Name, Version: mk.Version, }, }, } case *age.MasterKey: return Key{ KeyType: &Key_AgeKey{ AgeKey: &AgeKey{ Recipient: mk.Recipient, }, }, } default: panic(fmt.Sprintf("Tried to convert unknown MasterKey type %T to keyservice.Key", mk)) } } 0707010000007C000081A400000000000000000000000162794F93000089E3000000000000000000000000000000000000002700000000sops-3.7.3/keyservice/keyservice.pb.go// Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.23.0 // protoc v3.13.0 // source: keyservice/keyservice.proto package keyservice import ( context "context" proto "github.com/golang/protobuf/proto" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) // This is a compile-time assertion that a sufficiently up-to-date version // of the legacy proto package is being used. const _ = proto.ProtoPackageIsVersion4 type Key struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // Types that are assignable to KeyType: // *Key_KmsKey // *Key_PgpKey // *Key_GcpKmsKey // *Key_AzureKeyvaultKey // *Key_VaultKey // *Key_AgeKey KeyType isKey_KeyType `protobuf_oneof:"key_type"` } func (x *Key) Reset() { *x = Key{} if protoimpl.UnsafeEnabled { mi := &file_keyservice_keyservice_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *Key) String() string { return protoimpl.X.MessageStringOf(x) } func (*Key) ProtoMessage() {} func (x *Key) ProtoReflect() protoreflect.Message { mi := &file_keyservice_keyservice_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use Key.ProtoReflect.Descriptor instead. func (*Key) Descriptor() ([]byte, []int) { return file_keyservice_keyservice_proto_rawDescGZIP(), []int{0} } func (m *Key) GetKeyType() isKey_KeyType { if m != nil { return m.KeyType } return nil } func (x *Key) GetKmsKey() *KmsKey { if x, ok := x.GetKeyType().(*Key_KmsKey); ok { return x.KmsKey } return nil } func (x *Key) GetPgpKey() *PgpKey { if x, ok := x.GetKeyType().(*Key_PgpKey); ok { return x.PgpKey } return nil } func (x *Key) GetGcpKmsKey() *GcpKmsKey { if x, ok := x.GetKeyType().(*Key_GcpKmsKey); ok { return x.GcpKmsKey } return nil } func (x *Key) GetAzureKeyvaultKey() *AzureKeyVaultKey { if x, ok := x.GetKeyType().(*Key_AzureKeyvaultKey); ok { return x.AzureKeyvaultKey } return nil } func (x *Key) GetVaultKey() *VaultKey { if x, ok := x.GetKeyType().(*Key_VaultKey); ok { return x.VaultKey } return nil } func (x *Key) GetAgeKey() *AgeKey { if x, ok := x.GetKeyType().(*Key_AgeKey); ok { return x.AgeKey } return nil } type isKey_KeyType interface { isKey_KeyType() } type Key_KmsKey struct { KmsKey *KmsKey `protobuf:"bytes,1,opt,name=kms_key,json=kmsKey,proto3,oneof"` } type Key_PgpKey struct { PgpKey *PgpKey `protobuf:"bytes,2,opt,name=pgp_key,json=pgpKey,proto3,oneof"` } type Key_GcpKmsKey struct { GcpKmsKey *GcpKmsKey `protobuf:"bytes,3,opt,name=gcp_kms_key,json=gcpKmsKey,proto3,oneof"` } type Key_AzureKeyvaultKey struct { AzureKeyvaultKey *AzureKeyVaultKey `protobuf:"bytes,4,opt,name=azure_keyvault_key,json=azureKeyvaultKey,proto3,oneof"` } type Key_VaultKey struct { VaultKey *VaultKey `protobuf:"bytes,5,opt,name=vault_key,json=vaultKey,proto3,oneof"` } type Key_AgeKey struct { AgeKey *AgeKey `protobuf:"bytes,6,opt,name=age_key,json=ageKey,proto3,oneof"` } func (*Key_KmsKey) isKey_KeyType() {} func (*Key_PgpKey) isKey_KeyType() {} func (*Key_GcpKmsKey) isKey_KeyType() {} func (*Key_AzureKeyvaultKey) isKey_KeyType() {} func (*Key_VaultKey) isKey_KeyType() {} func (*Key_AgeKey) isKey_KeyType() {} type PgpKey struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Fingerprint string `protobuf:"bytes,1,opt,name=fingerprint,proto3" json:"fingerprint,omitempty"` } func (x *PgpKey) Reset() { *x = PgpKey{} if protoimpl.UnsafeEnabled { mi := &file_keyservice_keyservice_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *PgpKey) String() string { return protoimpl.X.MessageStringOf(x) } func (*PgpKey) ProtoMessage() {} func (x *PgpKey) ProtoReflect() protoreflect.Message { mi := &file_keyservice_keyservice_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use PgpKey.ProtoReflect.Descriptor instead. func (*PgpKey) Descriptor() ([]byte, []int) { return file_keyservice_keyservice_proto_rawDescGZIP(), []int{1} } func (x *PgpKey) GetFingerprint() string { if x != nil { return x.Fingerprint } return "" } type KmsKey struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Arn string `protobuf:"bytes,1,opt,name=arn,proto3" json:"arn,omitempty"` Role string `protobuf:"bytes,2,opt,name=role,proto3" json:"role,omitempty"` Context map[string]string `protobuf:"bytes,3,rep,name=context,proto3" json:"context,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` AwsProfile string `protobuf:"bytes,4,opt,name=aws_profile,json=awsProfile,proto3" json:"aws_profile,omitempty"` } func (x *KmsKey) Reset() { *x = KmsKey{} if protoimpl.UnsafeEnabled { mi := &file_keyservice_keyservice_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *KmsKey) String() string { return protoimpl.X.MessageStringOf(x) } func (*KmsKey) ProtoMessage() {} func (x *KmsKey) ProtoReflect() protoreflect.Message { mi := &file_keyservice_keyservice_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use KmsKey.ProtoReflect.Descriptor instead. func (*KmsKey) Descriptor() ([]byte, []int) { return file_keyservice_keyservice_proto_rawDescGZIP(), []int{2} } func (x *KmsKey) GetArn() string { if x != nil { return x.Arn } return "" } func (x *KmsKey) GetRole() string { if x != nil { return x.Role } return "" } func (x *KmsKey) GetContext() map[string]string { if x != nil { return x.Context } return nil } func (x *KmsKey) GetAwsProfile() string { if x != nil { return x.AwsProfile } return "" } type GcpKmsKey struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields ResourceId string `protobuf:"bytes,1,opt,name=resource_id,json=resourceId,proto3" json:"resource_id,omitempty"` } func (x *GcpKmsKey) Reset() { *x = GcpKmsKey{} if protoimpl.UnsafeEnabled { mi := &file_keyservice_keyservice_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *GcpKmsKey) String() string { return protoimpl.X.MessageStringOf(x) } func (*GcpKmsKey) ProtoMessage() {} func (x *GcpKmsKey) ProtoReflect() protoreflect.Message { mi := &file_keyservice_keyservice_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use GcpKmsKey.ProtoReflect.Descriptor instead. func (*GcpKmsKey) Descriptor() ([]byte, []int) { return file_keyservice_keyservice_proto_rawDescGZIP(), []int{3} } func (x *GcpKmsKey) GetResourceId() string { if x != nil { return x.ResourceId } return "" } type VaultKey struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields VaultAddress string `protobuf:"bytes,1,opt,name=vault_address,json=vaultAddress,proto3" json:"vault_address,omitempty"` EnginePath string `protobuf:"bytes,2,opt,name=engine_path,json=enginePath,proto3" json:"engine_path,omitempty"` KeyName string `protobuf:"bytes,3,opt,name=key_name,json=keyName,proto3" json:"key_name,omitempty"` } func (x *VaultKey) Reset() { *x = VaultKey{} if protoimpl.UnsafeEnabled { mi := &file_keyservice_keyservice_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *VaultKey) String() string { return protoimpl.X.MessageStringOf(x) } func (*VaultKey) ProtoMessage() {} func (x *VaultKey) ProtoReflect() protoreflect.Message { mi := &file_keyservice_keyservice_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use VaultKey.ProtoReflect.Descriptor instead. func (*VaultKey) Descriptor() ([]byte, []int) { return file_keyservice_keyservice_proto_rawDescGZIP(), []int{4} } func (x *VaultKey) GetVaultAddress() string { if x != nil { return x.VaultAddress } return "" } func (x *VaultKey) GetEnginePath() string { if x != nil { return x.EnginePath } return "" } func (x *VaultKey) GetKeyName() string { if x != nil { return x.KeyName } return "" } type AzureKeyVaultKey struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields VaultUrl string `protobuf:"bytes,1,opt,name=vault_url,json=vaultUrl,proto3" json:"vault_url,omitempty"` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` } func (x *AzureKeyVaultKey) Reset() { *x = AzureKeyVaultKey{} if protoimpl.UnsafeEnabled { mi := &file_keyservice_keyservice_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AzureKeyVaultKey) String() string { return protoimpl.X.MessageStringOf(x) } func (*AzureKeyVaultKey) ProtoMessage() {} func (x *AzureKeyVaultKey) ProtoReflect() protoreflect.Message { mi := &file_keyservice_keyservice_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AzureKeyVaultKey.ProtoReflect.Descriptor instead. func (*AzureKeyVaultKey) Descriptor() ([]byte, []int) { return file_keyservice_keyservice_proto_rawDescGZIP(), []int{5} } func (x *AzureKeyVaultKey) GetVaultUrl() string { if x != nil { return x.VaultUrl } return "" } func (x *AzureKeyVaultKey) GetName() string { if x != nil { return x.Name } return "" } func (x *AzureKeyVaultKey) GetVersion() string { if x != nil { return x.Version } return "" } type AgeKey struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Recipient string `protobuf:"bytes,1,opt,name=recipient,proto3" json:"recipient,omitempty"` } func (x *AgeKey) Reset() { *x = AgeKey{} if protoimpl.UnsafeEnabled { mi := &file_keyservice_keyservice_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *AgeKey) String() string { return protoimpl.X.MessageStringOf(x) } func (*AgeKey) ProtoMessage() {} func (x *AgeKey) ProtoReflect() protoreflect.Message { mi := &file_keyservice_keyservice_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use AgeKey.ProtoReflect.Descriptor instead. func (*AgeKey) Descriptor() ([]byte, []int) { return file_keyservice_keyservice_proto_rawDescGZIP(), []int{6} } func (x *AgeKey) GetRecipient() string { if x != nil { return x.Recipient } return "" } type EncryptRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Key *Key `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` Plaintext []byte `protobuf:"bytes,2,opt,name=plaintext,proto3" json:"plaintext,omitempty"` } func (x *EncryptRequest) Reset() { *x = EncryptRequest{} if protoimpl.UnsafeEnabled { mi := &file_keyservice_keyservice_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *EncryptRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*EncryptRequest) ProtoMessage() {} func (x *EncryptRequest) ProtoReflect() protoreflect.Message { mi := &file_keyservice_keyservice_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use EncryptRequest.ProtoReflect.Descriptor instead. func (*EncryptRequest) Descriptor() ([]byte, []int) { return file_keyservice_keyservice_proto_rawDescGZIP(), []int{7} } func (x *EncryptRequest) GetKey() *Key { if x != nil { return x.Key } return nil } func (x *EncryptRequest) GetPlaintext() []byte { if x != nil { return x.Plaintext } return nil } type EncryptResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Ciphertext []byte `protobuf:"bytes,1,opt,name=ciphertext,proto3" json:"ciphertext,omitempty"` } func (x *EncryptResponse) Reset() { *x = EncryptResponse{} if protoimpl.UnsafeEnabled { mi := &file_keyservice_keyservice_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *EncryptResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*EncryptResponse) ProtoMessage() {} func (x *EncryptResponse) ProtoReflect() protoreflect.Message { mi := &file_keyservice_keyservice_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use EncryptResponse.ProtoReflect.Descriptor instead. func (*EncryptResponse) Descriptor() ([]byte, []int) { return file_keyservice_keyservice_proto_rawDescGZIP(), []int{8} } func (x *EncryptResponse) GetCiphertext() []byte { if x != nil { return x.Ciphertext } return nil } type DecryptRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Key *Key `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` Ciphertext []byte `protobuf:"bytes,2,opt,name=ciphertext,proto3" json:"ciphertext,omitempty"` } func (x *DecryptRequest) Reset() { *x = DecryptRequest{} if protoimpl.UnsafeEnabled { mi := &file_keyservice_keyservice_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DecryptRequest) String() string { return protoimpl.X.MessageStringOf(x) } func (*DecryptRequest) ProtoMessage() {} func (x *DecryptRequest) ProtoReflect() protoreflect.Message { mi := &file_keyservice_keyservice_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DecryptRequest.ProtoReflect.Descriptor instead. func (*DecryptRequest) Descriptor() ([]byte, []int) { return file_keyservice_keyservice_proto_rawDescGZIP(), []int{9} } func (x *DecryptRequest) GetKey() *Key { if x != nil { return x.Key } return nil } func (x *DecryptRequest) GetCiphertext() []byte { if x != nil { return x.Ciphertext } return nil } type DecryptResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Plaintext []byte `protobuf:"bytes,1,opt,name=plaintext,proto3" json:"plaintext,omitempty"` } func (x *DecryptResponse) Reset() { *x = DecryptResponse{} if protoimpl.UnsafeEnabled { mi := &file_keyservice_keyservice_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *DecryptResponse) String() string { return protoimpl.X.MessageStringOf(x) } func (*DecryptResponse) ProtoMessage() {} func (x *DecryptResponse) ProtoReflect() protoreflect.Message { mi := &file_keyservice_keyservice_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use DecryptResponse.ProtoReflect.Descriptor instead. func (*DecryptResponse) Descriptor() ([]byte, []int) { return file_keyservice_keyservice_proto_rawDescGZIP(), []int{10} } func (x *DecryptResponse) GetPlaintext() []byte { if x != nil { return x.Plaintext } return nil } var File_keyservice_keyservice_proto protoreflect.FileDescriptor var file_keyservice_keyservice_proto_rawDesc = []byte{ 0x0a, 0x1b, 0x6b, 0x65, 0x79, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x6b, 0x65, 0x79, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x98, 0x02, 0x0a, 0x03, 0x4b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x07, 0x6b, 0x6d, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x4b, 0x6d, 0x73, 0x4b, 0x65, 0x79, 0x48, 0x00, 0x52, 0x06, 0x6b, 0x6d, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x07, 0x70, 0x67, 0x70, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x50, 0x67, 0x70, 0x4b, 0x65, 0x79, 0x48, 0x00, 0x52, 0x06, 0x70, 0x67, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x0b, 0x67, 0x63, 0x70, 0x5f, 0x6b, 0x6d, 0x73, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x47, 0x63, 0x70, 0x4b, 0x6d, 0x73, 0x4b, 0x65, 0x79, 0x48, 0x00, 0x52, 0x09, 0x67, 0x63, 0x70, 0x4b, 0x6d, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x41, 0x0a, 0x12, 0x61, 0x7a, 0x75, 0x72, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x4b, 0x65, 0x79, 0x48, 0x00, 0x52, 0x10, 0x61, 0x7a, 0x75, 0x72, 0x65, 0x4b, 0x65, 0x79, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x09, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x4b, 0x65, 0x79, 0x48, 0x00, 0x52, 0x08, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x07, 0x61, 0x67, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x41, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x48, 0x00, 0x52, 0x06, 0x61, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x42, 0x0a, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2a, 0x0a, 0x06, 0x50, 0x67, 0x70, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x22, 0xbb, 0x01, 0x0a, 0x06, 0x4b, 0x6d, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x72, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x61, 0x72, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x6f, 0x6c, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x4b, 0x6d, 0x73, 0x4b, 0x65, 0x79, 0x2e, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x77, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x77, 0x73, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x1a, 0x3a, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x2c, 0x0a, 0x09, 0x47, 0x63, 0x70, 0x4b, 0x6d, 0x73, 0x4b, 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x22, 0x6b, 0x0a, 0x08, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x6b, 0x65, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6b, 0x65, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x5d, 0x0a, 0x10, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x4b, 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x55, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x26, 0x0a, 0x06, 0x41, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x63, 0x69, 0x70, 0x69, 0x65, 0x6e, 0x74, 0x22, 0x46, 0x0a, 0x0e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x31, 0x0a, 0x0f, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x22, 0x48, 0x0a, 0x0e, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x04, 0x2e, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x74, 0x65, 0x78, 0x74, 0x22, 0x2f, 0x0a, 0x0f, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x6c, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x32, 0x6c, 0x0a, 0x0a, 0x4b, 0x65, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x12, 0x0f, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x07, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x12, 0x0f, 0x2e, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x44, 0x65, 0x63, 0x72, 0x79, 0x70, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_keyservice_keyservice_proto_rawDescOnce sync.Once file_keyservice_keyservice_proto_rawDescData = file_keyservice_keyservice_proto_rawDesc ) func file_keyservice_keyservice_proto_rawDescGZIP() []byte { file_keyservice_keyservice_proto_rawDescOnce.Do(func() { file_keyservice_keyservice_proto_rawDescData = protoimpl.X.CompressGZIP(file_keyservice_keyservice_proto_rawDescData) }) return file_keyservice_keyservice_proto_rawDescData } var file_keyservice_keyservice_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_keyservice_keyservice_proto_goTypes = []interface{}{ (*Key)(nil), // 0: Key (*PgpKey)(nil), // 1: PgpKey (*KmsKey)(nil), // 2: KmsKey (*GcpKmsKey)(nil), // 3: GcpKmsKey (*VaultKey)(nil), // 4: VaultKey (*AzureKeyVaultKey)(nil), // 5: AzureKeyVaultKey (*AgeKey)(nil), // 6: AgeKey (*EncryptRequest)(nil), // 7: EncryptRequest (*EncryptResponse)(nil), // 8: EncryptResponse (*DecryptRequest)(nil), // 9: DecryptRequest (*DecryptResponse)(nil), // 10: DecryptResponse nil, // 11: KmsKey.ContextEntry } var file_keyservice_keyservice_proto_depIdxs = []int32{ 2, // 0: Key.kms_key:type_name -> KmsKey 1, // 1: Key.pgp_key:type_name -> PgpKey 3, // 2: Key.gcp_kms_key:type_name -> GcpKmsKey 5, // 3: Key.azure_keyvault_key:type_name -> AzureKeyVaultKey 4, // 4: Key.vault_key:type_name -> VaultKey 6, // 5: Key.age_key:type_name -> AgeKey 11, // 6: KmsKey.context:type_name -> KmsKey.ContextEntry 0, // 7: EncryptRequest.key:type_name -> Key 0, // 8: DecryptRequest.key:type_name -> Key 7, // 9: KeyService.Encrypt:input_type -> EncryptRequest 9, // 10: KeyService.Decrypt:input_type -> DecryptRequest 8, // 11: KeyService.Encrypt:output_type -> EncryptResponse 10, // 12: KeyService.Decrypt:output_type -> DecryptResponse 11, // [11:13] is the sub-list for method output_type 9, // [9:11] is the sub-list for method input_type 9, // [9:9] is the sub-list for extension type_name 9, // [9:9] is the sub-list for extension extendee 0, // [0:9] is the sub-list for field type_name } func init() { file_keyservice_keyservice_proto_init() } func file_keyservice_keyservice_proto_init() { if File_keyservice_keyservice_proto != nil { return } if !protoimpl.UnsafeEnabled { file_keyservice_keyservice_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Key); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_keyservice_keyservice_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PgpKey); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_keyservice_keyservice_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*KmsKey); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_keyservice_keyservice_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GcpKmsKey); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_keyservice_keyservice_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*VaultKey); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_keyservice_keyservice_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AzureKeyVaultKey); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_keyservice_keyservice_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AgeKey); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_keyservice_keyservice_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*EncryptRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_keyservice_keyservice_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*EncryptResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_keyservice_keyservice_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DecryptRequest); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_keyservice_keyservice_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*DecryptResponse); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } file_keyservice_keyservice_proto_msgTypes[0].OneofWrappers = []interface{}{ (*Key_KmsKey)(nil), (*Key_PgpKey)(nil), (*Key_GcpKmsKey)(nil), (*Key_AzureKeyvaultKey)(nil), (*Key_VaultKey)(nil), (*Key_AgeKey)(nil), } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_keyservice_keyservice_proto_rawDesc, NumEnums: 0, NumMessages: 12, NumExtensions: 0, NumServices: 1, }, GoTypes: file_keyservice_keyservice_proto_goTypes, DependencyIndexes: file_keyservice_keyservice_proto_depIdxs, MessageInfos: file_keyservice_keyservice_proto_msgTypes, }.Build() File_keyservice_keyservice_proto = out.File file_keyservice_keyservice_proto_rawDesc = nil file_keyservice_keyservice_proto_goTypes = nil file_keyservice_keyservice_proto_depIdxs = nil } // Reference imports to suppress errors if they are not otherwise used. var _ context.Context var _ grpc.ClientConnInterface // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. const _ = grpc.SupportPackageIsVersion6 // KeyServiceClient is the client API for KeyService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type KeyServiceClient interface { Encrypt(ctx context.Context, in *EncryptRequest, opts ...grpc.CallOption) (*EncryptResponse, error) Decrypt(ctx context.Context, in *DecryptRequest, opts ...grpc.CallOption) (*DecryptResponse, error) } type keyServiceClient struct { cc grpc.ClientConnInterface } func NewKeyServiceClient(cc grpc.ClientConnInterface) KeyServiceClient { return &keyServiceClient{cc} } func (c *keyServiceClient) Encrypt(ctx context.Context, in *EncryptRequest, opts ...grpc.CallOption) (*EncryptResponse, error) { out := new(EncryptResponse) err := c.cc.Invoke(ctx, "/KeyService/Encrypt", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *keyServiceClient) Decrypt(ctx context.Context, in *DecryptRequest, opts ...grpc.CallOption) (*DecryptResponse, error) { out := new(DecryptResponse) err := c.cc.Invoke(ctx, "/KeyService/Decrypt", in, out, opts...) if err != nil { return nil, err } return out, nil } // KeyServiceServer is the server API for KeyService service. type KeyServiceServer interface { Encrypt(context.Context, *EncryptRequest) (*EncryptResponse, error) Decrypt(context.Context, *DecryptRequest) (*DecryptResponse, error) } // UnimplementedKeyServiceServer can be embedded to have forward compatible implementations. type UnimplementedKeyServiceServer struct { } func (*UnimplementedKeyServiceServer) Encrypt(context.Context, *EncryptRequest) (*EncryptResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Encrypt not implemented") } func (*UnimplementedKeyServiceServer) Decrypt(context.Context, *DecryptRequest) (*DecryptResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Decrypt not implemented") } func RegisterKeyServiceServer(s *grpc.Server, srv KeyServiceServer) { s.RegisterService(&_KeyService_serviceDesc, srv) } func _KeyService_Encrypt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(EncryptRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(KeyServiceServer).Encrypt(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/KeyService/Encrypt", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(KeyServiceServer).Encrypt(ctx, req.(*EncryptRequest)) } return interceptor(ctx, in, info, handler) } func _KeyService_Decrypt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DecryptRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(KeyServiceServer).Decrypt(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/KeyService/Decrypt", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(KeyServiceServer).Decrypt(ctx, req.(*DecryptRequest)) } return interceptor(ctx, in, info, handler) } var _KeyService_serviceDesc = grpc.ServiceDesc{ ServiceName: "KeyService", HandlerType: (*KeyServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Encrypt", Handler: _KeyService_Encrypt_Handler, }, { MethodName: "Decrypt", Handler: _KeyService_Decrypt_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "keyservice/keyservice.proto", } 0707010000007D000081A400000000000000000000000162794F9300000409000000000000000000000000000000000000002700000000sops-3.7.3/keyservice/keyservice.protosyntax = "proto3"; message Key { oneof key_type { KmsKey kms_key = 1; PgpKey pgp_key = 2; GcpKmsKey gcp_kms_key = 3; AzureKeyVaultKey azure_keyvault_key = 4; VaultKey vault_key = 5; AgeKey age_key = 6; } } message PgpKey { string fingerprint = 1; } message KmsKey { string arn = 1; string role = 2; map<string, string> context = 3; string aws_profile = 4; } message GcpKmsKey { string resource_id = 1; } message VaultKey { string vault_address = 1; string engine_path = 2; string key_name = 3; } message AzureKeyVaultKey { string vault_url = 1; string name = 2; string version = 3; } message AgeKey { string recipient = 1; } message EncryptRequest { Key key = 1; bytes plaintext = 2; } message EncryptResponse { bytes ciphertext = 1; } message DecryptRequest { Key key = 1; bytes ciphertext = 2; } message DecryptResponse { bytes plaintext = 1; } service KeyService { rpc Encrypt (EncryptRequest) returns (EncryptResponse) {} rpc Decrypt (DecryptRequest) returns (DecryptResponse) {} } 0707010000007E000081A400000000000000000000000162794F93000022F9000000000000000000000000000000000000002000000000sops-3.7.3/keyservice/server.gopackage keyservice import ( "fmt" "go.mozilla.org/sops/v3/age" "go.mozilla.org/sops/v3/azkv" "go.mozilla.org/sops/v3/gcpkms" "go.mozilla.org/sops/v3/hcvault" "go.mozilla.org/sops/v3/kms" "go.mozilla.org/sops/v3/pgp" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // Server is a key service server that uses SOPS MasterKeys to fulfill requests type Server struct { // Prompt indicates whether the server should prompt before decrypting or encrypting data Prompt bool } func (ks *Server) encryptWithPgp(key *PgpKey, plaintext []byte) ([]byte, error) { pgpKey := pgp.NewMasterKeyFromFingerprint(key.Fingerprint) err := pgpKey.Encrypt(plaintext) if err != nil { return nil, err } return []byte(pgpKey.EncryptedKey), nil } func (ks *Server) encryptWithKms(key *KmsKey, plaintext []byte) ([]byte, error) { kmsKey := kmsKeyToMasterKey(key) err := kmsKey.Encrypt(plaintext) if err != nil { return nil, err } return []byte(kmsKey.EncryptedKey), nil } func (ks *Server) encryptWithGcpKms(key *GcpKmsKey, plaintext []byte) ([]byte, error) { gcpKmsKey := gcpkms.MasterKey{ ResourceID: key.ResourceId, } err := gcpKmsKey.Encrypt(plaintext) if err != nil { return nil, err } return []byte(gcpKmsKey.EncryptedKey), nil } func (ks *Server) encryptWithAzureKeyVault(key *AzureKeyVaultKey, plaintext []byte) ([]byte, error) { azkvKey := azkv.MasterKey{ VaultURL: key.VaultUrl, Name: key.Name, Version: key.Version, } err := azkvKey.Encrypt(plaintext) if err != nil { return nil, err } return []byte(azkvKey.EncryptedKey), nil } func (ks *Server) encryptWithVault(key *VaultKey, plaintext []byte) ([]byte, error) { vaultKey := hcvault.MasterKey{ VaultAddress: key.VaultAddress, EnginePath: key.EnginePath, KeyName: key.KeyName, } err := vaultKey.Encrypt(plaintext) if err != nil { return nil, err } return []byte(vaultKey.EncryptedKey), nil } func (ks *Server) encryptWithAge(key *AgeKey, plaintext []byte) ([]byte, error) { ageKey := age.MasterKey{ Recipient: key.Recipient, } if err := ageKey.Encrypt(plaintext); err != nil { return nil, err } return []byte(ageKey.EncryptedKey), nil } func (ks *Server) decryptWithPgp(key *PgpKey, ciphertext []byte) ([]byte, error) { pgpKey := pgp.NewMasterKeyFromFingerprint(key.Fingerprint) pgpKey.EncryptedKey = string(ciphertext) plaintext, err := pgpKey.Decrypt() return []byte(plaintext), err } func (ks *Server) decryptWithKms(key *KmsKey, ciphertext []byte) ([]byte, error) { kmsKey := kmsKeyToMasterKey(key) kmsKey.EncryptedKey = string(ciphertext) plaintext, err := kmsKey.Decrypt() return []byte(plaintext), err } func (ks *Server) decryptWithGcpKms(key *GcpKmsKey, ciphertext []byte) ([]byte, error) { gcpKmsKey := gcpkms.MasterKey{ ResourceID: key.ResourceId, } gcpKmsKey.EncryptedKey = string(ciphertext) plaintext, err := gcpKmsKey.Decrypt() return []byte(plaintext), err } func (ks *Server) decryptWithAzureKeyVault(key *AzureKeyVaultKey, ciphertext []byte) ([]byte, error) { azkvKey := azkv.MasterKey{ VaultURL: key.VaultUrl, Name: key.Name, Version: key.Version, } azkvKey.EncryptedKey = string(ciphertext) plaintext, err := azkvKey.Decrypt() return []byte(plaintext), err } func (ks *Server) decryptWithVault(key *VaultKey, ciphertext []byte) ([]byte, error) { vaultKey := hcvault.MasterKey{ VaultAddress: key.VaultAddress, EnginePath: key.EnginePath, KeyName: key.KeyName, } vaultKey.EncryptedKey = string(ciphertext) plaintext, err := vaultKey.Decrypt() return []byte(plaintext), err } func (ks *Server) decryptWithAge(key *AgeKey, ciphertext []byte) ([]byte, error) { ageKey := age.MasterKey{ Recipient: key.Recipient, } ageKey.EncryptedKey = string(ciphertext) plaintext, err := ageKey.Decrypt() return []byte(plaintext), err } // Encrypt takes an encrypt request and encrypts the provided plaintext with the provided key, returning the encrypted // result func (ks Server) Encrypt(ctx context.Context, req *EncryptRequest) (*EncryptResponse, error) { key := *req.Key var response *EncryptResponse switch k := key.KeyType.(type) { case *Key_PgpKey: ciphertext, err := ks.encryptWithPgp(k.PgpKey, req.Plaintext) if err != nil { return nil, err } response = &EncryptResponse{ Ciphertext: ciphertext, } case *Key_KmsKey: ciphertext, err := ks.encryptWithKms(k.KmsKey, req.Plaintext) if err != nil { return nil, err } response = &EncryptResponse{ Ciphertext: ciphertext, } case *Key_GcpKmsKey: ciphertext, err := ks.encryptWithGcpKms(k.GcpKmsKey, req.Plaintext) if err != nil { return nil, err } response = &EncryptResponse{ Ciphertext: ciphertext, } case *Key_AzureKeyvaultKey: ciphertext, err := ks.encryptWithAzureKeyVault(k.AzureKeyvaultKey, req.Plaintext) if err != nil { return nil, err } response = &EncryptResponse{ Ciphertext: ciphertext, } case *Key_VaultKey: ciphertext, err := ks.encryptWithVault(k.VaultKey, req.Plaintext) if err != nil { return nil, err } response = &EncryptResponse{ Ciphertext: ciphertext, } case *Key_AgeKey: ciphertext, err := ks.encryptWithAge(k.AgeKey, req.Plaintext) if err != nil { return nil, err } response = &EncryptResponse{ Ciphertext: ciphertext, } case nil: return nil, status.Errorf(codes.NotFound, "Must provide a key") default: return nil, status.Errorf(codes.NotFound, "Unknown key type") } if ks.Prompt { err := ks.prompt(key, "encrypt") if err != nil { return nil, err } } return response, nil } func keyToString(key Key) string { switch k := key.KeyType.(type) { case *Key_PgpKey: return fmt.Sprintf("PGP key with fingerprint %s", k.PgpKey.Fingerprint) case *Key_KmsKey: return fmt.Sprintf("AWS KMS key with ARN %s", k.KmsKey.Arn) case *Key_GcpKmsKey: return fmt.Sprintf("GCP KMS key with resource ID %s", k.GcpKmsKey.ResourceId) case *Key_AzureKeyvaultKey: return fmt.Sprintf("Azure Key Vault key with URL %s/keys/%s/%s", k.AzureKeyvaultKey.VaultUrl, k.AzureKeyvaultKey.Name, k.AzureKeyvaultKey.Version) case *Key_VaultKey: return fmt.Sprintf("Hashicorp Vault key with URI %s/v1/%s/keys/%s", k.VaultKey.VaultAddress, k.VaultKey.EnginePath, k.VaultKey.KeyName) default: return fmt.Sprintf("Unknown key type") } } func (ks Server) prompt(key Key, requestType string) error { keyString := keyToString(key) var response string for response != "y" && response != "n" { fmt.Printf("\nReceived %s request using %s. Respond to request? (y/n): ", requestType, keyString) _, err := fmt.Scanln(&response) if err != nil { return err } } if response == "n" { return grpc.Errorf(codes.PermissionDenied, "Request rejected by user") } return nil } // Decrypt takes a decrypt request and decrypts the provided ciphertext with the provided key, returning the decrypted // result func (ks Server) Decrypt(ctx context.Context, req *DecryptRequest) (*DecryptResponse, error) { key := *req.Key var response *DecryptResponse switch k := key.KeyType.(type) { case *Key_PgpKey: plaintext, err := ks.decryptWithPgp(k.PgpKey, req.Ciphertext) if err != nil { return nil, err } response = &DecryptResponse{ Plaintext: plaintext, } case *Key_KmsKey: plaintext, err := ks.decryptWithKms(k.KmsKey, req.Ciphertext) if err != nil { return nil, err } response = &DecryptResponse{ Plaintext: plaintext, } case *Key_GcpKmsKey: plaintext, err := ks.decryptWithGcpKms(k.GcpKmsKey, req.Ciphertext) if err != nil { return nil, err } response = &DecryptResponse{ Plaintext: plaintext, } case *Key_AzureKeyvaultKey: plaintext, err := ks.decryptWithAzureKeyVault(k.AzureKeyvaultKey, req.Ciphertext) if err != nil { return nil, err } response = &DecryptResponse{ Plaintext: plaintext, } case *Key_VaultKey: plaintext, err := ks.decryptWithVault(k.VaultKey, req.Ciphertext) if err != nil { return nil, err } response = &DecryptResponse{ Plaintext: plaintext, } case *Key_AgeKey: plaintext, err := ks.decryptWithAge(k.AgeKey, req.Ciphertext) if err != nil { return nil, err } response = &DecryptResponse{ Plaintext: plaintext, } case nil: return nil, grpc.Errorf(codes.NotFound, "Must provide a key") default: return nil, grpc.Errorf(codes.NotFound, "Unknown key type") } if ks.Prompt { err := ks.prompt(key, "decrypt") if err != nil { return nil, err } } return response, nil } func kmsKeyToMasterKey(key *KmsKey) kms.MasterKey { ctx := make(map[string]*string) for k, v := range key.Context { value := v // Allocate a new string to prevent the pointer below from referring to only the last iteration value ctx[k] = &value } return kms.MasterKey{ Arn: key.Arn, Role: key.Role, EncryptionContext: ctx, AwsProfile: key.AwsProfile, } } 0707010000007F000081A400000000000000000000000162794F9300000A08000000000000000000000000000000000000002500000000sops-3.7.3/keyservice/server_test.gopackage keyservice import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "testing" ) func TestKmsKeyToMasterKey(t *testing.T) { cases := []struct { description string expectedArn string expectedRole string expectedCtx map[string]string expectedAwsProfile string }{ { description: "empty context", expectedArn: "arn:aws:kms:eu-west-1:123456789012:key/d5c90a06-f824-4628-922b-12424571ed4d", expectedRole: "ExampleRole", expectedCtx: map[string]string{}, expectedAwsProfile: "", }, { description: "context with one key-value pair", expectedArn: "arn:aws:kms:eu-west-1:123456789012:key/d5c90a06-f824-4628-922b-12424571ed4d", expectedRole: "", expectedCtx: map[string]string{ "firstKey": "first value", }, expectedAwsProfile: "ExampleProfile", }, { description: "context with three key-value pairs", expectedArn: "arn:aws:kms:eu-west-1:123456789012:key/d5c90a06-f824-4628-922b-12424571ed4d", expectedRole: "", expectedCtx: map[string]string{ "firstKey": "first value", "secondKey": "second value", "thirdKey": "third value", }, expectedAwsProfile: "", }, } for _, c := range cases { t.Run(c.description, func(t *testing.T) { inputCtx := make(map[string]string) for k, v := range c.expectedCtx { inputCtx[k] = v } key := &KmsKey{ Arn: c.expectedArn, Role: c.expectedRole, Context: inputCtx, AwsProfile: c.expectedAwsProfile, } masterKey := kmsKeyToMasterKey(key) foundCtx := masterKey.EncryptionContext for k := range c.expectedCtx { require.Containsf(t, foundCtx, k, "Context does not contain expected key '%s'", k) } for k := range foundCtx { require.Containsf(t, c.expectedCtx, k, "Context contains an unexpected key '%s' which cannot be found from expected map", k) } for k, expected := range c.expectedCtx { foundVal := *foundCtx[k] assert.Equalf(t, expected, foundVal, "Context key '%s' value '%s' does not match expected value '%s'", k, foundVal, expected) } assert.Equalf(t, c.expectedArn, masterKey.Arn, "Expected ARN to be '%s', but found '%s'", c.expectedArn, masterKey.Arn) assert.Equalf(t, c.expectedRole, masterKey.Role, "Expected Role to be '%s', but found '%s'", c.expectedRole, masterKey.Role) assert.Equalf(t, c.expectedAwsProfile, masterKey.AwsProfile, "Expected AWS profile to be '%s', but found '%s'", c.expectedAwsProfile, masterKey.AwsProfile) }) } } 07070100000080000041ED00000000000000000000000362794F9300000000000000000000000000000000000000000000000F00000000sops-3.7.3/kms07070100000081000081A400000000000000000000000162794F930000229C000000000000000000000000000000000000001C00000000sops-3.7.3/kms/keysource.go/* Package kms contains an implementation of the go.mozilla.org/sops/v3.MasterKey interface that encrypts and decrypts the data key using AWS KMS with the AWS Go SDK. */ package kms //import "go.mozilla.org/sops/v3/kms" import ( "encoding/base64" "fmt" "os" "regexp" "strings" "time" "go.mozilla.org/sops/v3/logging" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials/stscreds" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/kms" "github.com/aws/aws-sdk-go/service/kms/kmsiface" "github.com/aws/aws-sdk-go/service/sts" "github.com/sirupsen/logrus" ) var log *logrus.Logger func init() { log = logging.NewLogger("AWSKMS") } // this needs to be a global var for unit tests to work (mockKMS redefines // it in keysource_test.go) var kmsSvc kmsiface.KMSAPI var isMocked bool // MasterKey is a AWS KMS key used to encrypt and decrypt sops' data key. type MasterKey struct { Arn string Role string EncryptedKey string CreationDate time.Time EncryptionContext map[string]*string AwsProfile string } // EncryptedDataKey returns the encrypted data key this master key holds func (key *MasterKey) EncryptedDataKey() []byte { return []byte(key.EncryptedKey) } // SetEncryptedDataKey sets the encrypted data key for this master key func (key *MasterKey) SetEncryptedDataKey(enc []byte) { key.EncryptedKey = string(enc) } // Encrypt takes a sops data key, encrypts it with KMS and stores the result in the EncryptedKey field func (key *MasterKey) Encrypt(dataKey []byte) error { // isMocked is set by unit test to indicate that the KMS service // has already been initialized. it's ugly, but it works. if kmsSvc == nil || !isMocked { sess, err := key.createSession() if err != nil { log.WithField("arn", key.Arn).Info("Encryption failed") return fmt.Errorf("Failed to create session: %w", err) } kmsSvc = kms.New(sess) } out, err := kmsSvc.Encrypt(&kms.EncryptInput{Plaintext: dataKey, KeyId: &key.Arn, EncryptionContext: key.EncryptionContext}) if err != nil { log.WithField("arn", key.Arn).Info("Encryption failed") return fmt.Errorf("Failed to call KMS encryption service: %w", err) } key.EncryptedKey = base64.StdEncoding.EncodeToString(out.CiphertextBlob) log.WithField("arn", key.Arn).Info("Encryption succeeded") return nil } // EncryptIfNeeded encrypts the provided sops' data key and encrypts it if it hasn't been encrypted yet func (key *MasterKey) EncryptIfNeeded(dataKey []byte) error { if key.EncryptedKey == "" { return key.Encrypt(dataKey) } return nil } // Decrypt decrypts the EncryptedKey field with AWS KMS and returns the result. func (key *MasterKey) Decrypt() ([]byte, error) { k, err := base64.StdEncoding.DecodeString(key.EncryptedKey) if err != nil { log.WithField("arn", key.Arn).Info("Decryption failed") return nil, fmt.Errorf("Error base64-decoding encrypted data key: %s", err) } // isMocked is set by unit test to indicate that the KMS service // has already been initialized. it's ugly, but it works. if kmsSvc == nil || !isMocked { sess, err := key.createSession() if err != nil { log.WithField("arn", key.Arn).Info("Decryption failed") return nil, fmt.Errorf("Error creating AWS session: %w", err) } kmsSvc = kms.New(sess) } decrypted, err := kmsSvc.Decrypt(&kms.DecryptInput{CiphertextBlob: k, EncryptionContext: key.EncryptionContext}) if err != nil { log.WithField("arn", key.Arn).Info("Decryption failed") return nil, fmt.Errorf("Error decrypting key: %w", err) } log.WithField("arn", key.Arn).Info("Decryption succeeded") return decrypted.Plaintext, nil } // NeedsRotation returns whether the data key needs to be rotated or not. func (key *MasterKey) NeedsRotation() bool { return time.Since(key.CreationDate) > (time.Hour * 24 * 30 * 6) } // ToString converts the key to a string representation func (key *MasterKey) ToString() string { return key.Arn } // NewMasterKey creates a new MasterKey from an ARN, role and context, setting the creation date to the current date func NewMasterKey(arn string, role string, context map[string]*string) *MasterKey { return &MasterKey{ Arn: arn, Role: role, EncryptionContext: context, CreationDate: time.Now().UTC(), } } // NewMasterKeyFromArn takes an ARN string and returns a new MasterKey for that ARN func NewMasterKeyFromArn(arn string, context map[string]*string, awsProfile string) *MasterKey { k := &MasterKey{} arn = strings.Replace(arn, " ", "", -1) roleIndex := strings.Index(arn, "+arn:aws:iam::") if roleIndex > 0 { k.Arn = arn[:roleIndex] k.Role = arn[roleIndex+1:] } else { k.Arn = arn } k.EncryptionContext = context k.CreationDate = time.Now().UTC() k.AwsProfile = awsProfile return k } // MasterKeysFromArnString takes a comma separated list of AWS KMS ARNs and returns a slice of new MasterKeys for those ARNs func MasterKeysFromArnString(arn string, context map[string]*string, awsProfile string) []*MasterKey { var keys []*MasterKey if arn == "" { return keys } for _, s := range strings.Split(arn, ",") { keys = append(keys, NewMasterKeyFromArn(s, context, awsProfile)) } return keys } func (key MasterKey) createStsSession(config aws.Config, sess *session.Session) (*session.Session, error) { hostname, err := os.Hostname() if err != nil { return nil, err } stsRoleSessionNameRe, err := regexp.Compile("[^a-zA-Z0-9=,.@-]+") if err != nil { return nil, fmt.Errorf("Failed to compile STS role session name regex: %w", err) } sanitizedHostname := stsRoleSessionNameRe.ReplaceAllString(hostname, "") stsService := sts.New(sess) name := "sops@" + sanitizedHostname // Make sure the name is no longer than 64 characters (role session name length limit from AWS) roleSessionNameLengthLimit := 64 if len(name) >= roleSessionNameLengthLimit { name = name[:roleSessionNameLengthLimit] } out, err := stsService.AssumeRole(&sts.AssumeRoleInput{ RoleArn: &key.Role, RoleSessionName: &name}) if err != nil { return nil, fmt.Errorf("Failed to assume role %q: %w", key.Role, err) } config.Credentials = credentials.NewStaticCredentials(*out.Credentials.AccessKeyId, *out.Credentials.SecretAccessKey, *out.Credentials.SessionToken) sess, err = session.NewSession(&config) if err != nil { return nil, fmt.Errorf("Failed to create new aws session: %w", err) } return sess, nil } func (key MasterKey) createSession() (*session.Session, error) { re := regexp.MustCompile(`^arn:aws[\w-]*:kms:(.+):[0-9]+:(key|alias)/.+$`) matches := re.FindStringSubmatch(key.Arn) if matches == nil { return nil, fmt.Errorf("No valid ARN found in %q", key.Arn) } config := aws.Config{Region: aws.String(matches[1])} opts := session.Options{ Profile: key.AwsProfile, Config: config, AssumeRoleTokenProvider: stscreds.StdinTokenProvider, SharedConfigState: session.SharedConfigEnable, } sess, err := session.NewSessionWithOptions(opts) if err != nil { return nil, err } if key.Role != "" { return key.createStsSession(config, sess) } return sess, nil } // ToMap converts the MasterKey to a map for serialization purposes func (key MasterKey) ToMap() map[string]interface{} { out := make(map[string]interface{}) out["arn"] = key.Arn if key.Role != "" { out["role"] = key.Role } out["created_at"] = key.CreationDate.UTC().Format(time.RFC3339) out["enc"] = key.EncryptedKey if key.EncryptionContext != nil { outcontext := make(map[string]string) for k, v := range key.EncryptionContext { outcontext[k] = *v } out["context"] = outcontext } return out } // ParseKMSContext takes either a KMS context map or a comma-separated list of KMS context key:value pairs and returns a map func ParseKMSContext(in interface{}) map[string]*string { nonStringValueWarning := "Encryption context contains a non-string value, context will not be used" out := make(map[string]*string) switch in := in.(type) { case map[string]interface{}: if len(in) == 0 { return nil } for k, v := range in { value, ok := v.(string) if !ok { log.Warn(nonStringValueWarning) return nil } out[k] = &value } case map[interface{}]interface{}: if len(in) == 0 { return nil } for k, v := range in { key, ok := k.(string) if !ok { log.Warn(nonStringValueWarning) return nil } value, ok := v.(string) if !ok { log.Warn(nonStringValueWarning) return nil } out[key] = &value } case string: if in == "" { return nil } for _, kv := range strings.Split(in, ",") { kv := strings.Split(kv, ":") if len(kv) != 2 { log.Warn(nonStringValueWarning) return nil } out[kv[0]] = &kv[1] } } return out } 07070100000082000081A400000000000000000000000162794F9300001042000000000000000000000000000000000000002100000000sops-3.7.3/kms/keysource_test.gopackage kms import ( "bytes" "testing" "testing/quick" "time" "github.com/aws/aws-sdk-go/service/kms" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "go.mozilla.org/sops/v3/kms/mocks" ) func TestKMS(t *testing.T) { mockKMS := &mocks.KMSAPI{} defer mockKMS.AssertExpectations(t) kmsSvc = mockKMS isMocked = true encryptOutput := &kms.EncryptOutput{} decryptOutput := &kms.DecryptOutput{} mockKMS.On("Encrypt", mock.AnythingOfType("*kms.EncryptInput")).Return(encryptOutput, nil).Run(func(args mock.Arguments) { encryptOutput.CiphertextBlob = args.Get(0).(*kms.EncryptInput).Plaintext }) mockKMS.On("Decrypt", mock.AnythingOfType("*kms.DecryptInput")).Return(decryptOutput, nil).Run(func(args mock.Arguments) { decryptOutput.Plaintext = args.Get(0).(*kms.DecryptInput).CiphertextBlob }) k := MasterKey{Arn: "arn:aws:kms:us-east-1:927034868273:key/e9fc75db-05e9-44c1-9c35-633922bac347", Role: "", EncryptedKey: ""} f := func(x []byte) bool { err := k.Encrypt(x) if err != nil { log.Println(err) } v, err := k.Decrypt() if err != nil { log.Println(err) } return bytes.Equal(v, x) } config := quick.Config{} if testing.Short() { config.MaxCount = 10 } if err := quick.Check(f, &config); err != nil { t.Error(err) } } func TestKMSKeySourceFromString(t *testing.T) { s := "arn:aws:kms:us-east-1:656532927350:key/920aff2e-c5f1-4040-943a-047fa387b27e+arn:aws:iam::927034868273:role/sops-dev, arn:aws:kms:ap-southeast-1:656532927350:key/9006a8aa-0fa6-4c14-930e-a2dfb916de1d" ks := MasterKeysFromArnString(s, nil, "foo") k1 := ks[0] k2 := ks[1] expectedArn1 := "arn:aws:kms:us-east-1:656532927350:key/920aff2e-c5f1-4040-943a-047fa387b27e" expectedRole1 := "arn:aws:iam::927034868273:role/sops-dev" if k1.Arn != expectedArn1 { t.Errorf("ARN mismatch. Expected %s, found %s", expectedArn1, k1.Arn) } if k1.Role != expectedRole1 { t.Errorf("Role mismatch. Expected %s, found %s", expectedRole1, k1.Role) } expectedArn2 := "arn:aws:kms:ap-southeast-1:656532927350:key/9006a8aa-0fa6-4c14-930e-a2dfb916de1d" expectedRole2 := "" if k2.Arn != expectedArn2 { t.Errorf("ARN mismatch. Expected %s, found %s", expectedArn2, k2.Arn) } if k2.Role != expectedRole2 { t.Errorf("Role mismatch. Expected empty role, found %s.", k2.Role) } } func TestParseEncryptionContext(t *testing.T) { value1 := "value1" value2 := "value2" // map from YAML var yamlmap = map[interface{}]interface{}{ "key1": value1, "key2": value2, } assert.Equal(t, ParseKMSContext(yamlmap), map[string]*string{ "key1": &value1, "key2": &value2, }) assert.Nil(t, ParseKMSContext(map[interface{}]interface{}{})) assert.Nil(t, ParseKMSContext(map[interface{}]interface{}{ "key1": 1, })) assert.Nil(t, ParseKMSContext(map[interface{}]interface{}{ 1: "value", })) // map from JSON var jsonmap = map[string]interface{}{ "key1": value1, "key2": value2, } assert.Equal(t, ParseKMSContext(jsonmap), map[string]*string{ "key1": &value1, "key2": &value2, }) assert.Nil(t, ParseKMSContext(map[string]interface{}{})) assert.Nil(t, ParseKMSContext(map[string]interface{}{ "key1": 1, })) // sops 2.0.x formatted encryption context as a comma-separated list of key:value pairs assert.Equal(t, ParseKMSContext("key1:value1,key2:value2"), map[string]*string{ "key1": &value1, "key2": &value2, }) assert.Equal(t, ParseKMSContext("key1:value1"), map[string]*string{ "key1": &value1, }) assert.Nil(t, ParseKMSContext("key1,key2:value2")) assert.Nil(t, ParseKMSContext("key1")) } func TestKeyToMap(t *testing.T) { value1 := "value1" value2 := "value2" key := MasterKey{ CreationDate: time.Date(2016, time.October, 31, 10, 0, 0, 0, time.UTC), Arn: "foo", Role: "bar", EncryptedKey: "this is encrypted", EncryptionContext: map[string]*string{ "key1": &value1, "key2": &value2, }, } assert.Equal(t, map[string]interface{}{ "arn": "foo", "role": "bar", "enc": "this is encrypted", "created_at": "2016-10-31T10:00:00Z", "context": map[string]string{ "key1": value1, "key2": value2, }, }, key.ToMap()) } 07070100000083000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001500000000sops-3.7.3/kms/mocks07070100000084000081A400000000000000000000000162794F930001B9D8000000000000000000000000000000000000001F00000000sops-3.7.3/kms/mocks/KMSAPI.go// Code generated by mockery v1.0.0. DO NOT EDIT. package mocks import ( context "context" kms "github.com/aws/aws-sdk-go/service/kms" mock "github.com/stretchr/testify/mock" request "github.com/aws/aws-sdk-go/aws/request" ) // KMSAPI is an autogenerated mock type for the KMSAPI type type KMSAPI struct { mock.Mock } // CancelKeyDeletion provides a mock function with given fields: _a0 func (_m *KMSAPI) CancelKeyDeletion(_a0 *kms.CancelKeyDeletionInput) (*kms.CancelKeyDeletionOutput, error) { ret := _m.Called(_a0) var r0 *kms.CancelKeyDeletionOutput if rf, ok := ret.Get(0).(func(*kms.CancelKeyDeletionInput) *kms.CancelKeyDeletionOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.CancelKeyDeletionOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.CancelKeyDeletionInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // CancelKeyDeletionRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) CancelKeyDeletionRequest(_a0 *kms.CancelKeyDeletionInput) (*request.Request, *kms.CancelKeyDeletionOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.CancelKeyDeletionInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.CancelKeyDeletionOutput if rf, ok := ret.Get(1).(func(*kms.CancelKeyDeletionInput) *kms.CancelKeyDeletionOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.CancelKeyDeletionOutput) } } return r0, r1 } // CancelKeyDeletionWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) CancelKeyDeletionWithContext(_a0 context.Context, _a1 *kms.CancelKeyDeletionInput, _a2 ...request.Option) (*kms.CancelKeyDeletionOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.CancelKeyDeletionOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.CancelKeyDeletionInput, ...request.Option) *kms.CancelKeyDeletionOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.CancelKeyDeletionOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.CancelKeyDeletionInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // ConnectCustomKeyStore provides a mock function with given fields: _a0 func (_m *KMSAPI) ConnectCustomKeyStore(_a0 *kms.ConnectCustomKeyStoreInput) (*kms.ConnectCustomKeyStoreOutput, error) { ret := _m.Called(_a0) var r0 *kms.ConnectCustomKeyStoreOutput if rf, ok := ret.Get(0).(func(*kms.ConnectCustomKeyStoreInput) *kms.ConnectCustomKeyStoreOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ConnectCustomKeyStoreOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.ConnectCustomKeyStoreInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // ConnectCustomKeyStoreRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) ConnectCustomKeyStoreRequest(_a0 *kms.ConnectCustomKeyStoreInput) (*request.Request, *kms.ConnectCustomKeyStoreOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.ConnectCustomKeyStoreInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.ConnectCustomKeyStoreOutput if rf, ok := ret.Get(1).(func(*kms.ConnectCustomKeyStoreInput) *kms.ConnectCustomKeyStoreOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.ConnectCustomKeyStoreOutput) } } return r0, r1 } // ConnectCustomKeyStoreWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) ConnectCustomKeyStoreWithContext(_a0 context.Context, _a1 *kms.ConnectCustomKeyStoreInput, _a2 ...request.Option) (*kms.ConnectCustomKeyStoreOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.ConnectCustomKeyStoreOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.ConnectCustomKeyStoreInput, ...request.Option) *kms.ConnectCustomKeyStoreOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ConnectCustomKeyStoreOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.ConnectCustomKeyStoreInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // CreateAlias provides a mock function with given fields: _a0 func (_m *KMSAPI) CreateAlias(_a0 *kms.CreateAliasInput) (*kms.CreateAliasOutput, error) { ret := _m.Called(_a0) var r0 *kms.CreateAliasOutput if rf, ok := ret.Get(0).(func(*kms.CreateAliasInput) *kms.CreateAliasOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.CreateAliasOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.CreateAliasInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // CreateAliasRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) CreateAliasRequest(_a0 *kms.CreateAliasInput) (*request.Request, *kms.CreateAliasOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.CreateAliasInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.CreateAliasOutput if rf, ok := ret.Get(1).(func(*kms.CreateAliasInput) *kms.CreateAliasOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.CreateAliasOutput) } } return r0, r1 } // CreateAliasWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) CreateAliasWithContext(_a0 context.Context, _a1 *kms.CreateAliasInput, _a2 ...request.Option) (*kms.CreateAliasOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.CreateAliasOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.CreateAliasInput, ...request.Option) *kms.CreateAliasOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.CreateAliasOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.CreateAliasInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // CreateCustomKeyStore provides a mock function with given fields: _a0 func (_m *KMSAPI) CreateCustomKeyStore(_a0 *kms.CreateCustomKeyStoreInput) (*kms.CreateCustomKeyStoreOutput, error) { ret := _m.Called(_a0) var r0 *kms.CreateCustomKeyStoreOutput if rf, ok := ret.Get(0).(func(*kms.CreateCustomKeyStoreInput) *kms.CreateCustomKeyStoreOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.CreateCustomKeyStoreOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.CreateCustomKeyStoreInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // CreateCustomKeyStoreRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) CreateCustomKeyStoreRequest(_a0 *kms.CreateCustomKeyStoreInput) (*request.Request, *kms.CreateCustomKeyStoreOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.CreateCustomKeyStoreInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.CreateCustomKeyStoreOutput if rf, ok := ret.Get(1).(func(*kms.CreateCustomKeyStoreInput) *kms.CreateCustomKeyStoreOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.CreateCustomKeyStoreOutput) } } return r0, r1 } // CreateCustomKeyStoreWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) CreateCustomKeyStoreWithContext(_a0 context.Context, _a1 *kms.CreateCustomKeyStoreInput, _a2 ...request.Option) (*kms.CreateCustomKeyStoreOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.CreateCustomKeyStoreOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.CreateCustomKeyStoreInput, ...request.Option) *kms.CreateCustomKeyStoreOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.CreateCustomKeyStoreOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.CreateCustomKeyStoreInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // CreateGrant provides a mock function with given fields: _a0 func (_m *KMSAPI) CreateGrant(_a0 *kms.CreateGrantInput) (*kms.CreateGrantOutput, error) { ret := _m.Called(_a0) var r0 *kms.CreateGrantOutput if rf, ok := ret.Get(0).(func(*kms.CreateGrantInput) *kms.CreateGrantOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.CreateGrantOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.CreateGrantInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // CreateGrantRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) CreateGrantRequest(_a0 *kms.CreateGrantInput) (*request.Request, *kms.CreateGrantOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.CreateGrantInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.CreateGrantOutput if rf, ok := ret.Get(1).(func(*kms.CreateGrantInput) *kms.CreateGrantOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.CreateGrantOutput) } } return r0, r1 } // CreateGrantWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) CreateGrantWithContext(_a0 context.Context, _a1 *kms.CreateGrantInput, _a2 ...request.Option) (*kms.CreateGrantOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.CreateGrantOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.CreateGrantInput, ...request.Option) *kms.CreateGrantOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.CreateGrantOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.CreateGrantInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // CreateKey provides a mock function with given fields: _a0 func (_m *KMSAPI) CreateKey(_a0 *kms.CreateKeyInput) (*kms.CreateKeyOutput, error) { ret := _m.Called(_a0) var r0 *kms.CreateKeyOutput if rf, ok := ret.Get(0).(func(*kms.CreateKeyInput) *kms.CreateKeyOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.CreateKeyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.CreateKeyInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // CreateKeyRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) CreateKeyRequest(_a0 *kms.CreateKeyInput) (*request.Request, *kms.CreateKeyOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.CreateKeyInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.CreateKeyOutput if rf, ok := ret.Get(1).(func(*kms.CreateKeyInput) *kms.CreateKeyOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.CreateKeyOutput) } } return r0, r1 } // CreateKeyWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) CreateKeyWithContext(_a0 context.Context, _a1 *kms.CreateKeyInput, _a2 ...request.Option) (*kms.CreateKeyOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.CreateKeyOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.CreateKeyInput, ...request.Option) *kms.CreateKeyOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.CreateKeyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.CreateKeyInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // Decrypt provides a mock function with given fields: _a0 func (_m *KMSAPI) Decrypt(_a0 *kms.DecryptInput) (*kms.DecryptOutput, error) { ret := _m.Called(_a0) var r0 *kms.DecryptOutput if rf, ok := ret.Get(0).(func(*kms.DecryptInput) *kms.DecryptOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.DecryptOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.DecryptInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // DecryptRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) DecryptRequest(_a0 *kms.DecryptInput) (*request.Request, *kms.DecryptOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.DecryptInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.DecryptOutput if rf, ok := ret.Get(1).(func(*kms.DecryptInput) *kms.DecryptOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.DecryptOutput) } } return r0, r1 } // DecryptWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) DecryptWithContext(_a0 context.Context, _a1 *kms.DecryptInput, _a2 ...request.Option) (*kms.DecryptOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.DecryptOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.DecryptInput, ...request.Option) *kms.DecryptOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.DecryptOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.DecryptInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // DeleteAlias provides a mock function with given fields: _a0 func (_m *KMSAPI) DeleteAlias(_a0 *kms.DeleteAliasInput) (*kms.DeleteAliasOutput, error) { ret := _m.Called(_a0) var r0 *kms.DeleteAliasOutput if rf, ok := ret.Get(0).(func(*kms.DeleteAliasInput) *kms.DeleteAliasOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.DeleteAliasOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.DeleteAliasInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // DeleteAliasRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) DeleteAliasRequest(_a0 *kms.DeleteAliasInput) (*request.Request, *kms.DeleteAliasOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.DeleteAliasInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.DeleteAliasOutput if rf, ok := ret.Get(1).(func(*kms.DeleteAliasInput) *kms.DeleteAliasOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.DeleteAliasOutput) } } return r0, r1 } // DeleteAliasWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) DeleteAliasWithContext(_a0 context.Context, _a1 *kms.DeleteAliasInput, _a2 ...request.Option) (*kms.DeleteAliasOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.DeleteAliasOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.DeleteAliasInput, ...request.Option) *kms.DeleteAliasOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.DeleteAliasOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.DeleteAliasInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // DeleteCustomKeyStore provides a mock function with given fields: _a0 func (_m *KMSAPI) DeleteCustomKeyStore(_a0 *kms.DeleteCustomKeyStoreInput) (*kms.DeleteCustomKeyStoreOutput, error) { ret := _m.Called(_a0) var r0 *kms.DeleteCustomKeyStoreOutput if rf, ok := ret.Get(0).(func(*kms.DeleteCustomKeyStoreInput) *kms.DeleteCustomKeyStoreOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.DeleteCustomKeyStoreOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.DeleteCustomKeyStoreInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // DeleteCustomKeyStoreRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) DeleteCustomKeyStoreRequest(_a0 *kms.DeleteCustomKeyStoreInput) (*request.Request, *kms.DeleteCustomKeyStoreOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.DeleteCustomKeyStoreInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.DeleteCustomKeyStoreOutput if rf, ok := ret.Get(1).(func(*kms.DeleteCustomKeyStoreInput) *kms.DeleteCustomKeyStoreOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.DeleteCustomKeyStoreOutput) } } return r0, r1 } // DeleteCustomKeyStoreWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) DeleteCustomKeyStoreWithContext(_a0 context.Context, _a1 *kms.DeleteCustomKeyStoreInput, _a2 ...request.Option) (*kms.DeleteCustomKeyStoreOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.DeleteCustomKeyStoreOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.DeleteCustomKeyStoreInput, ...request.Option) *kms.DeleteCustomKeyStoreOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.DeleteCustomKeyStoreOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.DeleteCustomKeyStoreInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // DeleteImportedKeyMaterial provides a mock function with given fields: _a0 func (_m *KMSAPI) DeleteImportedKeyMaterial(_a0 *kms.DeleteImportedKeyMaterialInput) (*kms.DeleteImportedKeyMaterialOutput, error) { ret := _m.Called(_a0) var r0 *kms.DeleteImportedKeyMaterialOutput if rf, ok := ret.Get(0).(func(*kms.DeleteImportedKeyMaterialInput) *kms.DeleteImportedKeyMaterialOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.DeleteImportedKeyMaterialOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.DeleteImportedKeyMaterialInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // DeleteImportedKeyMaterialRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) DeleteImportedKeyMaterialRequest(_a0 *kms.DeleteImportedKeyMaterialInput) (*request.Request, *kms.DeleteImportedKeyMaterialOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.DeleteImportedKeyMaterialInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.DeleteImportedKeyMaterialOutput if rf, ok := ret.Get(1).(func(*kms.DeleteImportedKeyMaterialInput) *kms.DeleteImportedKeyMaterialOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.DeleteImportedKeyMaterialOutput) } } return r0, r1 } // DeleteImportedKeyMaterialWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) DeleteImportedKeyMaterialWithContext(_a0 context.Context, _a1 *kms.DeleteImportedKeyMaterialInput, _a2 ...request.Option) (*kms.DeleteImportedKeyMaterialOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.DeleteImportedKeyMaterialOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.DeleteImportedKeyMaterialInput, ...request.Option) *kms.DeleteImportedKeyMaterialOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.DeleteImportedKeyMaterialOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.DeleteImportedKeyMaterialInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // DescribeCustomKeyStores provides a mock function with given fields: _a0 func (_m *KMSAPI) DescribeCustomKeyStores(_a0 *kms.DescribeCustomKeyStoresInput) (*kms.DescribeCustomKeyStoresOutput, error) { ret := _m.Called(_a0) var r0 *kms.DescribeCustomKeyStoresOutput if rf, ok := ret.Get(0).(func(*kms.DescribeCustomKeyStoresInput) *kms.DescribeCustomKeyStoresOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.DescribeCustomKeyStoresOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.DescribeCustomKeyStoresInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // DescribeCustomKeyStoresRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) DescribeCustomKeyStoresRequest(_a0 *kms.DescribeCustomKeyStoresInput) (*request.Request, *kms.DescribeCustomKeyStoresOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.DescribeCustomKeyStoresInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.DescribeCustomKeyStoresOutput if rf, ok := ret.Get(1).(func(*kms.DescribeCustomKeyStoresInput) *kms.DescribeCustomKeyStoresOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.DescribeCustomKeyStoresOutput) } } return r0, r1 } // DescribeCustomKeyStoresWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) DescribeCustomKeyStoresWithContext(_a0 context.Context, _a1 *kms.DescribeCustomKeyStoresInput, _a2 ...request.Option) (*kms.DescribeCustomKeyStoresOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.DescribeCustomKeyStoresOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.DescribeCustomKeyStoresInput, ...request.Option) *kms.DescribeCustomKeyStoresOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.DescribeCustomKeyStoresOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.DescribeCustomKeyStoresInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // DescribeKey provides a mock function with given fields: _a0 func (_m *KMSAPI) DescribeKey(_a0 *kms.DescribeKeyInput) (*kms.DescribeKeyOutput, error) { ret := _m.Called(_a0) var r0 *kms.DescribeKeyOutput if rf, ok := ret.Get(0).(func(*kms.DescribeKeyInput) *kms.DescribeKeyOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.DescribeKeyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.DescribeKeyInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // DescribeKeyRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) DescribeKeyRequest(_a0 *kms.DescribeKeyInput) (*request.Request, *kms.DescribeKeyOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.DescribeKeyInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.DescribeKeyOutput if rf, ok := ret.Get(1).(func(*kms.DescribeKeyInput) *kms.DescribeKeyOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.DescribeKeyOutput) } } return r0, r1 } // DescribeKeyWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) DescribeKeyWithContext(_a0 context.Context, _a1 *kms.DescribeKeyInput, _a2 ...request.Option) (*kms.DescribeKeyOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.DescribeKeyOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.DescribeKeyInput, ...request.Option) *kms.DescribeKeyOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.DescribeKeyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.DescribeKeyInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // DisableKey provides a mock function with given fields: _a0 func (_m *KMSAPI) DisableKey(_a0 *kms.DisableKeyInput) (*kms.DisableKeyOutput, error) { ret := _m.Called(_a0) var r0 *kms.DisableKeyOutput if rf, ok := ret.Get(0).(func(*kms.DisableKeyInput) *kms.DisableKeyOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.DisableKeyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.DisableKeyInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // DisableKeyRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) DisableKeyRequest(_a0 *kms.DisableKeyInput) (*request.Request, *kms.DisableKeyOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.DisableKeyInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.DisableKeyOutput if rf, ok := ret.Get(1).(func(*kms.DisableKeyInput) *kms.DisableKeyOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.DisableKeyOutput) } } return r0, r1 } // DisableKeyRotation provides a mock function with given fields: _a0 func (_m *KMSAPI) DisableKeyRotation(_a0 *kms.DisableKeyRotationInput) (*kms.DisableKeyRotationOutput, error) { ret := _m.Called(_a0) var r0 *kms.DisableKeyRotationOutput if rf, ok := ret.Get(0).(func(*kms.DisableKeyRotationInput) *kms.DisableKeyRotationOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.DisableKeyRotationOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.DisableKeyRotationInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // DisableKeyRotationRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) DisableKeyRotationRequest(_a0 *kms.DisableKeyRotationInput) (*request.Request, *kms.DisableKeyRotationOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.DisableKeyRotationInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.DisableKeyRotationOutput if rf, ok := ret.Get(1).(func(*kms.DisableKeyRotationInput) *kms.DisableKeyRotationOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.DisableKeyRotationOutput) } } return r0, r1 } // DisableKeyRotationWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) DisableKeyRotationWithContext(_a0 context.Context, _a1 *kms.DisableKeyRotationInput, _a2 ...request.Option) (*kms.DisableKeyRotationOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.DisableKeyRotationOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.DisableKeyRotationInput, ...request.Option) *kms.DisableKeyRotationOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.DisableKeyRotationOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.DisableKeyRotationInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // DisableKeyWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) DisableKeyWithContext(_a0 context.Context, _a1 *kms.DisableKeyInput, _a2 ...request.Option) (*kms.DisableKeyOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.DisableKeyOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.DisableKeyInput, ...request.Option) *kms.DisableKeyOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.DisableKeyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.DisableKeyInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // DisconnectCustomKeyStore provides a mock function with given fields: _a0 func (_m *KMSAPI) DisconnectCustomKeyStore(_a0 *kms.DisconnectCustomKeyStoreInput) (*kms.DisconnectCustomKeyStoreOutput, error) { ret := _m.Called(_a0) var r0 *kms.DisconnectCustomKeyStoreOutput if rf, ok := ret.Get(0).(func(*kms.DisconnectCustomKeyStoreInput) *kms.DisconnectCustomKeyStoreOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.DisconnectCustomKeyStoreOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.DisconnectCustomKeyStoreInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // DisconnectCustomKeyStoreRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) DisconnectCustomKeyStoreRequest(_a0 *kms.DisconnectCustomKeyStoreInput) (*request.Request, *kms.DisconnectCustomKeyStoreOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.DisconnectCustomKeyStoreInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.DisconnectCustomKeyStoreOutput if rf, ok := ret.Get(1).(func(*kms.DisconnectCustomKeyStoreInput) *kms.DisconnectCustomKeyStoreOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.DisconnectCustomKeyStoreOutput) } } return r0, r1 } // DisconnectCustomKeyStoreWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) DisconnectCustomKeyStoreWithContext(_a0 context.Context, _a1 *kms.DisconnectCustomKeyStoreInput, _a2 ...request.Option) (*kms.DisconnectCustomKeyStoreOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.DisconnectCustomKeyStoreOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.DisconnectCustomKeyStoreInput, ...request.Option) *kms.DisconnectCustomKeyStoreOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.DisconnectCustomKeyStoreOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.DisconnectCustomKeyStoreInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // EnableKey provides a mock function with given fields: _a0 func (_m *KMSAPI) EnableKey(_a0 *kms.EnableKeyInput) (*kms.EnableKeyOutput, error) { ret := _m.Called(_a0) var r0 *kms.EnableKeyOutput if rf, ok := ret.Get(0).(func(*kms.EnableKeyInput) *kms.EnableKeyOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.EnableKeyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.EnableKeyInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // EnableKeyRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) EnableKeyRequest(_a0 *kms.EnableKeyInput) (*request.Request, *kms.EnableKeyOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.EnableKeyInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.EnableKeyOutput if rf, ok := ret.Get(1).(func(*kms.EnableKeyInput) *kms.EnableKeyOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.EnableKeyOutput) } } return r0, r1 } // EnableKeyRotation provides a mock function with given fields: _a0 func (_m *KMSAPI) EnableKeyRotation(_a0 *kms.EnableKeyRotationInput) (*kms.EnableKeyRotationOutput, error) { ret := _m.Called(_a0) var r0 *kms.EnableKeyRotationOutput if rf, ok := ret.Get(0).(func(*kms.EnableKeyRotationInput) *kms.EnableKeyRotationOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.EnableKeyRotationOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.EnableKeyRotationInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // EnableKeyRotationRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) EnableKeyRotationRequest(_a0 *kms.EnableKeyRotationInput) (*request.Request, *kms.EnableKeyRotationOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.EnableKeyRotationInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.EnableKeyRotationOutput if rf, ok := ret.Get(1).(func(*kms.EnableKeyRotationInput) *kms.EnableKeyRotationOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.EnableKeyRotationOutput) } } return r0, r1 } // EnableKeyRotationWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) EnableKeyRotationWithContext(_a0 context.Context, _a1 *kms.EnableKeyRotationInput, _a2 ...request.Option) (*kms.EnableKeyRotationOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.EnableKeyRotationOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.EnableKeyRotationInput, ...request.Option) *kms.EnableKeyRotationOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.EnableKeyRotationOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.EnableKeyRotationInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // EnableKeyWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) EnableKeyWithContext(_a0 context.Context, _a1 *kms.EnableKeyInput, _a2 ...request.Option) (*kms.EnableKeyOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.EnableKeyOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.EnableKeyInput, ...request.Option) *kms.EnableKeyOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.EnableKeyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.EnableKeyInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // Encrypt provides a mock function with given fields: _a0 func (_m *KMSAPI) Encrypt(_a0 *kms.EncryptInput) (*kms.EncryptOutput, error) { ret := _m.Called(_a0) var r0 *kms.EncryptOutput if rf, ok := ret.Get(0).(func(*kms.EncryptInput) *kms.EncryptOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.EncryptOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.EncryptInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // EncryptRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) EncryptRequest(_a0 *kms.EncryptInput) (*request.Request, *kms.EncryptOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.EncryptInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.EncryptOutput if rf, ok := ret.Get(1).(func(*kms.EncryptInput) *kms.EncryptOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.EncryptOutput) } } return r0, r1 } // EncryptWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) EncryptWithContext(_a0 context.Context, _a1 *kms.EncryptInput, _a2 ...request.Option) (*kms.EncryptOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.EncryptOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.EncryptInput, ...request.Option) *kms.EncryptOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.EncryptOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.EncryptInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // GenerateDataKey provides a mock function with given fields: _a0 func (_m *KMSAPI) GenerateDataKey(_a0 *kms.GenerateDataKeyInput) (*kms.GenerateDataKeyOutput, error) { ret := _m.Called(_a0) var r0 *kms.GenerateDataKeyOutput if rf, ok := ret.Get(0).(func(*kms.GenerateDataKeyInput) *kms.GenerateDataKeyOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GenerateDataKeyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.GenerateDataKeyInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // GenerateDataKeyPair provides a mock function with given fields: _a0 func (_m *KMSAPI) GenerateDataKeyPair(_a0 *kms.GenerateDataKeyPairInput) (*kms.GenerateDataKeyPairOutput, error) { ret := _m.Called(_a0) var r0 *kms.GenerateDataKeyPairOutput if rf, ok := ret.Get(0).(func(*kms.GenerateDataKeyPairInput) *kms.GenerateDataKeyPairOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GenerateDataKeyPairOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.GenerateDataKeyPairInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // GenerateDataKeyPairRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) GenerateDataKeyPairRequest(_a0 *kms.GenerateDataKeyPairInput) (*request.Request, *kms.GenerateDataKeyPairOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.GenerateDataKeyPairInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.GenerateDataKeyPairOutput if rf, ok := ret.Get(1).(func(*kms.GenerateDataKeyPairInput) *kms.GenerateDataKeyPairOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.GenerateDataKeyPairOutput) } } return r0, r1 } // GenerateDataKeyPairWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) GenerateDataKeyPairWithContext(_a0 context.Context, _a1 *kms.GenerateDataKeyPairInput, _a2 ...request.Option) (*kms.GenerateDataKeyPairOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.GenerateDataKeyPairOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.GenerateDataKeyPairInput, ...request.Option) *kms.GenerateDataKeyPairOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GenerateDataKeyPairOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.GenerateDataKeyPairInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // GenerateDataKeyPairWithoutPlaintext provides a mock function with given fields: _a0 func (_m *KMSAPI) GenerateDataKeyPairWithoutPlaintext(_a0 *kms.GenerateDataKeyPairWithoutPlaintextInput) (*kms.GenerateDataKeyPairWithoutPlaintextOutput, error) { ret := _m.Called(_a0) var r0 *kms.GenerateDataKeyPairWithoutPlaintextOutput if rf, ok := ret.Get(0).(func(*kms.GenerateDataKeyPairWithoutPlaintextInput) *kms.GenerateDataKeyPairWithoutPlaintextOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GenerateDataKeyPairWithoutPlaintextOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.GenerateDataKeyPairWithoutPlaintextInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // GenerateDataKeyPairWithoutPlaintextRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) GenerateDataKeyPairWithoutPlaintextRequest(_a0 *kms.GenerateDataKeyPairWithoutPlaintextInput) (*request.Request, *kms.GenerateDataKeyPairWithoutPlaintextOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.GenerateDataKeyPairWithoutPlaintextInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.GenerateDataKeyPairWithoutPlaintextOutput if rf, ok := ret.Get(1).(func(*kms.GenerateDataKeyPairWithoutPlaintextInput) *kms.GenerateDataKeyPairWithoutPlaintextOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.GenerateDataKeyPairWithoutPlaintextOutput) } } return r0, r1 } // GenerateDataKeyPairWithoutPlaintextWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) GenerateDataKeyPairWithoutPlaintextWithContext(_a0 context.Context, _a1 *kms.GenerateDataKeyPairWithoutPlaintextInput, _a2 ...request.Option) (*kms.GenerateDataKeyPairWithoutPlaintextOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.GenerateDataKeyPairWithoutPlaintextOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.GenerateDataKeyPairWithoutPlaintextInput, ...request.Option) *kms.GenerateDataKeyPairWithoutPlaintextOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GenerateDataKeyPairWithoutPlaintextOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.GenerateDataKeyPairWithoutPlaintextInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // GenerateDataKeyRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) GenerateDataKeyRequest(_a0 *kms.GenerateDataKeyInput) (*request.Request, *kms.GenerateDataKeyOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.GenerateDataKeyInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.GenerateDataKeyOutput if rf, ok := ret.Get(1).(func(*kms.GenerateDataKeyInput) *kms.GenerateDataKeyOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.GenerateDataKeyOutput) } } return r0, r1 } // GenerateDataKeyWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) GenerateDataKeyWithContext(_a0 context.Context, _a1 *kms.GenerateDataKeyInput, _a2 ...request.Option) (*kms.GenerateDataKeyOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.GenerateDataKeyOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.GenerateDataKeyInput, ...request.Option) *kms.GenerateDataKeyOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GenerateDataKeyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.GenerateDataKeyInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // GenerateDataKeyWithoutPlaintext provides a mock function with given fields: _a0 func (_m *KMSAPI) GenerateDataKeyWithoutPlaintext(_a0 *kms.GenerateDataKeyWithoutPlaintextInput) (*kms.GenerateDataKeyWithoutPlaintextOutput, error) { ret := _m.Called(_a0) var r0 *kms.GenerateDataKeyWithoutPlaintextOutput if rf, ok := ret.Get(0).(func(*kms.GenerateDataKeyWithoutPlaintextInput) *kms.GenerateDataKeyWithoutPlaintextOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GenerateDataKeyWithoutPlaintextOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.GenerateDataKeyWithoutPlaintextInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // GenerateDataKeyWithoutPlaintextRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) GenerateDataKeyWithoutPlaintextRequest(_a0 *kms.GenerateDataKeyWithoutPlaintextInput) (*request.Request, *kms.GenerateDataKeyWithoutPlaintextOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.GenerateDataKeyWithoutPlaintextInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.GenerateDataKeyWithoutPlaintextOutput if rf, ok := ret.Get(1).(func(*kms.GenerateDataKeyWithoutPlaintextInput) *kms.GenerateDataKeyWithoutPlaintextOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.GenerateDataKeyWithoutPlaintextOutput) } } return r0, r1 } // GenerateDataKeyWithoutPlaintextWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) GenerateDataKeyWithoutPlaintextWithContext(_a0 context.Context, _a1 *kms.GenerateDataKeyWithoutPlaintextInput, _a2 ...request.Option) (*kms.GenerateDataKeyWithoutPlaintextOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.GenerateDataKeyWithoutPlaintextOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.GenerateDataKeyWithoutPlaintextInput, ...request.Option) *kms.GenerateDataKeyWithoutPlaintextOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GenerateDataKeyWithoutPlaintextOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.GenerateDataKeyWithoutPlaintextInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // GenerateMac provides a mock function with given fields: _a0 func (_m *KMSAPI) GenerateMac(_a0 *kms.GenerateMacInput) (*kms.GenerateMacOutput, error) { ret := _m.Called(_a0) var r0 *kms.GenerateMacOutput if rf, ok := ret.Get(0).(func(*kms.GenerateMacInput) *kms.GenerateMacOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GenerateMacOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.GenerateMacInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // GenerateMacRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) GenerateMacRequest(_a0 *kms.GenerateMacInput) (*request.Request, *kms.GenerateMacOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.GenerateMacInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.GenerateMacOutput if rf, ok := ret.Get(1).(func(*kms.GenerateMacInput) *kms.GenerateMacOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.GenerateMacOutput) } } return r0, r1 } // GenerateMacWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) GenerateMacWithContext(_a0 context.Context, _a1 *kms.GenerateMacInput, _a2 ...request.Option) (*kms.GenerateMacOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.GenerateMacOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.GenerateMacInput, ...request.Option) *kms.GenerateMacOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GenerateMacOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.GenerateMacInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // GenerateRandom provides a mock function with given fields: _a0 func (_m *KMSAPI) GenerateRandom(_a0 *kms.GenerateRandomInput) (*kms.GenerateRandomOutput, error) { ret := _m.Called(_a0) var r0 *kms.GenerateRandomOutput if rf, ok := ret.Get(0).(func(*kms.GenerateRandomInput) *kms.GenerateRandomOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GenerateRandomOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.GenerateRandomInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // GenerateRandomRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) GenerateRandomRequest(_a0 *kms.GenerateRandomInput) (*request.Request, *kms.GenerateRandomOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.GenerateRandomInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.GenerateRandomOutput if rf, ok := ret.Get(1).(func(*kms.GenerateRandomInput) *kms.GenerateRandomOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.GenerateRandomOutput) } } return r0, r1 } // GenerateRandomWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) GenerateRandomWithContext(_a0 context.Context, _a1 *kms.GenerateRandomInput, _a2 ...request.Option) (*kms.GenerateRandomOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.GenerateRandomOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.GenerateRandomInput, ...request.Option) *kms.GenerateRandomOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GenerateRandomOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.GenerateRandomInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // GetKeyPolicy provides a mock function with given fields: _a0 func (_m *KMSAPI) GetKeyPolicy(_a0 *kms.GetKeyPolicyInput) (*kms.GetKeyPolicyOutput, error) { ret := _m.Called(_a0) var r0 *kms.GetKeyPolicyOutput if rf, ok := ret.Get(0).(func(*kms.GetKeyPolicyInput) *kms.GetKeyPolicyOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GetKeyPolicyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.GetKeyPolicyInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // GetKeyPolicyRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) GetKeyPolicyRequest(_a0 *kms.GetKeyPolicyInput) (*request.Request, *kms.GetKeyPolicyOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.GetKeyPolicyInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.GetKeyPolicyOutput if rf, ok := ret.Get(1).(func(*kms.GetKeyPolicyInput) *kms.GetKeyPolicyOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.GetKeyPolicyOutput) } } return r0, r1 } // GetKeyPolicyWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) GetKeyPolicyWithContext(_a0 context.Context, _a1 *kms.GetKeyPolicyInput, _a2 ...request.Option) (*kms.GetKeyPolicyOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.GetKeyPolicyOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.GetKeyPolicyInput, ...request.Option) *kms.GetKeyPolicyOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GetKeyPolicyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.GetKeyPolicyInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // GetKeyRotationStatus provides a mock function with given fields: _a0 func (_m *KMSAPI) GetKeyRotationStatus(_a0 *kms.GetKeyRotationStatusInput) (*kms.GetKeyRotationStatusOutput, error) { ret := _m.Called(_a0) var r0 *kms.GetKeyRotationStatusOutput if rf, ok := ret.Get(0).(func(*kms.GetKeyRotationStatusInput) *kms.GetKeyRotationStatusOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GetKeyRotationStatusOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.GetKeyRotationStatusInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // GetKeyRotationStatusRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) GetKeyRotationStatusRequest(_a0 *kms.GetKeyRotationStatusInput) (*request.Request, *kms.GetKeyRotationStatusOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.GetKeyRotationStatusInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.GetKeyRotationStatusOutput if rf, ok := ret.Get(1).(func(*kms.GetKeyRotationStatusInput) *kms.GetKeyRotationStatusOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.GetKeyRotationStatusOutput) } } return r0, r1 } // GetKeyRotationStatusWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) GetKeyRotationStatusWithContext(_a0 context.Context, _a1 *kms.GetKeyRotationStatusInput, _a2 ...request.Option) (*kms.GetKeyRotationStatusOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.GetKeyRotationStatusOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.GetKeyRotationStatusInput, ...request.Option) *kms.GetKeyRotationStatusOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GetKeyRotationStatusOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.GetKeyRotationStatusInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // GetParametersForImport provides a mock function with given fields: _a0 func (_m *KMSAPI) GetParametersForImport(_a0 *kms.GetParametersForImportInput) (*kms.GetParametersForImportOutput, error) { ret := _m.Called(_a0) var r0 *kms.GetParametersForImportOutput if rf, ok := ret.Get(0).(func(*kms.GetParametersForImportInput) *kms.GetParametersForImportOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GetParametersForImportOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.GetParametersForImportInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // GetParametersForImportRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) GetParametersForImportRequest(_a0 *kms.GetParametersForImportInput) (*request.Request, *kms.GetParametersForImportOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.GetParametersForImportInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.GetParametersForImportOutput if rf, ok := ret.Get(1).(func(*kms.GetParametersForImportInput) *kms.GetParametersForImportOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.GetParametersForImportOutput) } } return r0, r1 } // GetParametersForImportWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) GetParametersForImportWithContext(_a0 context.Context, _a1 *kms.GetParametersForImportInput, _a2 ...request.Option) (*kms.GetParametersForImportOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.GetParametersForImportOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.GetParametersForImportInput, ...request.Option) *kms.GetParametersForImportOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GetParametersForImportOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.GetParametersForImportInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // GetPublicKey provides a mock function with given fields: _a0 func (_m *KMSAPI) GetPublicKey(_a0 *kms.GetPublicKeyInput) (*kms.GetPublicKeyOutput, error) { ret := _m.Called(_a0) var r0 *kms.GetPublicKeyOutput if rf, ok := ret.Get(0).(func(*kms.GetPublicKeyInput) *kms.GetPublicKeyOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GetPublicKeyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.GetPublicKeyInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // GetPublicKeyRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) GetPublicKeyRequest(_a0 *kms.GetPublicKeyInput) (*request.Request, *kms.GetPublicKeyOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.GetPublicKeyInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.GetPublicKeyOutput if rf, ok := ret.Get(1).(func(*kms.GetPublicKeyInput) *kms.GetPublicKeyOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.GetPublicKeyOutput) } } return r0, r1 } // GetPublicKeyWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) GetPublicKeyWithContext(_a0 context.Context, _a1 *kms.GetPublicKeyInput, _a2 ...request.Option) (*kms.GetPublicKeyOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.GetPublicKeyOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.GetPublicKeyInput, ...request.Option) *kms.GetPublicKeyOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.GetPublicKeyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.GetPublicKeyInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // ImportKeyMaterial provides a mock function with given fields: _a0 func (_m *KMSAPI) ImportKeyMaterial(_a0 *kms.ImportKeyMaterialInput) (*kms.ImportKeyMaterialOutput, error) { ret := _m.Called(_a0) var r0 *kms.ImportKeyMaterialOutput if rf, ok := ret.Get(0).(func(*kms.ImportKeyMaterialInput) *kms.ImportKeyMaterialOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ImportKeyMaterialOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.ImportKeyMaterialInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // ImportKeyMaterialRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) ImportKeyMaterialRequest(_a0 *kms.ImportKeyMaterialInput) (*request.Request, *kms.ImportKeyMaterialOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.ImportKeyMaterialInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.ImportKeyMaterialOutput if rf, ok := ret.Get(1).(func(*kms.ImportKeyMaterialInput) *kms.ImportKeyMaterialOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.ImportKeyMaterialOutput) } } return r0, r1 } // ImportKeyMaterialWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) ImportKeyMaterialWithContext(_a0 context.Context, _a1 *kms.ImportKeyMaterialInput, _a2 ...request.Option) (*kms.ImportKeyMaterialOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.ImportKeyMaterialOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.ImportKeyMaterialInput, ...request.Option) *kms.ImportKeyMaterialOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ImportKeyMaterialOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.ImportKeyMaterialInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // ListAliases provides a mock function with given fields: _a0 func (_m *KMSAPI) ListAliases(_a0 *kms.ListAliasesInput) (*kms.ListAliasesOutput, error) { ret := _m.Called(_a0) var r0 *kms.ListAliasesOutput if rf, ok := ret.Get(0).(func(*kms.ListAliasesInput) *kms.ListAliasesOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ListAliasesOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.ListAliasesInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // ListAliasesPages provides a mock function with given fields: _a0, _a1 func (_m *KMSAPI) ListAliasesPages(_a0 *kms.ListAliasesInput, _a1 func(*kms.ListAliasesOutput, bool) bool) error { ret := _m.Called(_a0, _a1) var r0 error if rf, ok := ret.Get(0).(func(*kms.ListAliasesInput, func(*kms.ListAliasesOutput, bool) bool) error); ok { r0 = rf(_a0, _a1) } else { r0 = ret.Error(0) } return r0 } // ListAliasesPagesWithContext provides a mock function with given fields: _a0, _a1, _a2, _a3 func (_m *KMSAPI) ListAliasesPagesWithContext(_a0 context.Context, _a1 *kms.ListAliasesInput, _a2 func(*kms.ListAliasesOutput, bool) bool, _a3 ...request.Option) error { _va := make([]interface{}, len(_a3)) for _i := range _a3 { _va[_i] = _a3[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1, _a2) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 error if rf, ok := ret.Get(0).(func(context.Context, *kms.ListAliasesInput, func(*kms.ListAliasesOutput, bool) bool, ...request.Option) error); ok { r0 = rf(_a0, _a1, _a2, _a3...) } else { r0 = ret.Error(0) } return r0 } // ListAliasesRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) ListAliasesRequest(_a0 *kms.ListAliasesInput) (*request.Request, *kms.ListAliasesOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.ListAliasesInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.ListAliasesOutput if rf, ok := ret.Get(1).(func(*kms.ListAliasesInput) *kms.ListAliasesOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.ListAliasesOutput) } } return r0, r1 } // ListAliasesWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) ListAliasesWithContext(_a0 context.Context, _a1 *kms.ListAliasesInput, _a2 ...request.Option) (*kms.ListAliasesOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.ListAliasesOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.ListAliasesInput, ...request.Option) *kms.ListAliasesOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ListAliasesOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.ListAliasesInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // ListGrants provides a mock function with given fields: _a0 func (_m *KMSAPI) ListGrants(_a0 *kms.ListGrantsInput) (*kms.ListGrantsResponse, error) { ret := _m.Called(_a0) var r0 *kms.ListGrantsResponse if rf, ok := ret.Get(0).(func(*kms.ListGrantsInput) *kms.ListGrantsResponse); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ListGrantsResponse) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.ListGrantsInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // ListGrantsPages provides a mock function with given fields: _a0, _a1 func (_m *KMSAPI) ListGrantsPages(_a0 *kms.ListGrantsInput, _a1 func(*kms.ListGrantsResponse, bool) bool) error { ret := _m.Called(_a0, _a1) var r0 error if rf, ok := ret.Get(0).(func(*kms.ListGrantsInput, func(*kms.ListGrantsResponse, bool) bool) error); ok { r0 = rf(_a0, _a1) } else { r0 = ret.Error(0) } return r0 } // ListGrantsPagesWithContext provides a mock function with given fields: _a0, _a1, _a2, _a3 func (_m *KMSAPI) ListGrantsPagesWithContext(_a0 context.Context, _a1 *kms.ListGrantsInput, _a2 func(*kms.ListGrantsResponse, bool) bool, _a3 ...request.Option) error { _va := make([]interface{}, len(_a3)) for _i := range _a3 { _va[_i] = _a3[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1, _a2) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 error if rf, ok := ret.Get(0).(func(context.Context, *kms.ListGrantsInput, func(*kms.ListGrantsResponse, bool) bool, ...request.Option) error); ok { r0 = rf(_a0, _a1, _a2, _a3...) } else { r0 = ret.Error(0) } return r0 } // ListGrantsRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) ListGrantsRequest(_a0 *kms.ListGrantsInput) (*request.Request, *kms.ListGrantsResponse) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.ListGrantsInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.ListGrantsResponse if rf, ok := ret.Get(1).(func(*kms.ListGrantsInput) *kms.ListGrantsResponse); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.ListGrantsResponse) } } return r0, r1 } // ListGrantsWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) ListGrantsWithContext(_a0 context.Context, _a1 *kms.ListGrantsInput, _a2 ...request.Option) (*kms.ListGrantsResponse, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.ListGrantsResponse if rf, ok := ret.Get(0).(func(context.Context, *kms.ListGrantsInput, ...request.Option) *kms.ListGrantsResponse); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ListGrantsResponse) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.ListGrantsInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // ListKeyPolicies provides a mock function with given fields: _a0 func (_m *KMSAPI) ListKeyPolicies(_a0 *kms.ListKeyPoliciesInput) (*kms.ListKeyPoliciesOutput, error) { ret := _m.Called(_a0) var r0 *kms.ListKeyPoliciesOutput if rf, ok := ret.Get(0).(func(*kms.ListKeyPoliciesInput) *kms.ListKeyPoliciesOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ListKeyPoliciesOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.ListKeyPoliciesInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // ListKeyPoliciesPages provides a mock function with given fields: _a0, _a1 func (_m *KMSAPI) ListKeyPoliciesPages(_a0 *kms.ListKeyPoliciesInput, _a1 func(*kms.ListKeyPoliciesOutput, bool) bool) error { ret := _m.Called(_a0, _a1) var r0 error if rf, ok := ret.Get(0).(func(*kms.ListKeyPoliciesInput, func(*kms.ListKeyPoliciesOutput, bool) bool) error); ok { r0 = rf(_a0, _a1) } else { r0 = ret.Error(0) } return r0 } // ListKeyPoliciesPagesWithContext provides a mock function with given fields: _a0, _a1, _a2, _a3 func (_m *KMSAPI) ListKeyPoliciesPagesWithContext(_a0 context.Context, _a1 *kms.ListKeyPoliciesInput, _a2 func(*kms.ListKeyPoliciesOutput, bool) bool, _a3 ...request.Option) error { _va := make([]interface{}, len(_a3)) for _i := range _a3 { _va[_i] = _a3[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1, _a2) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 error if rf, ok := ret.Get(0).(func(context.Context, *kms.ListKeyPoliciesInput, func(*kms.ListKeyPoliciesOutput, bool) bool, ...request.Option) error); ok { r0 = rf(_a0, _a1, _a2, _a3...) } else { r0 = ret.Error(0) } return r0 } // ListKeyPoliciesRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) ListKeyPoliciesRequest(_a0 *kms.ListKeyPoliciesInput) (*request.Request, *kms.ListKeyPoliciesOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.ListKeyPoliciesInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.ListKeyPoliciesOutput if rf, ok := ret.Get(1).(func(*kms.ListKeyPoliciesInput) *kms.ListKeyPoliciesOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.ListKeyPoliciesOutput) } } return r0, r1 } // ListKeyPoliciesWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) ListKeyPoliciesWithContext(_a0 context.Context, _a1 *kms.ListKeyPoliciesInput, _a2 ...request.Option) (*kms.ListKeyPoliciesOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.ListKeyPoliciesOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.ListKeyPoliciesInput, ...request.Option) *kms.ListKeyPoliciesOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ListKeyPoliciesOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.ListKeyPoliciesInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // ListKeys provides a mock function with given fields: _a0 func (_m *KMSAPI) ListKeys(_a0 *kms.ListKeysInput) (*kms.ListKeysOutput, error) { ret := _m.Called(_a0) var r0 *kms.ListKeysOutput if rf, ok := ret.Get(0).(func(*kms.ListKeysInput) *kms.ListKeysOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ListKeysOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.ListKeysInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // ListKeysPages provides a mock function with given fields: _a0, _a1 func (_m *KMSAPI) ListKeysPages(_a0 *kms.ListKeysInput, _a1 func(*kms.ListKeysOutput, bool) bool) error { ret := _m.Called(_a0, _a1) var r0 error if rf, ok := ret.Get(0).(func(*kms.ListKeysInput, func(*kms.ListKeysOutput, bool) bool) error); ok { r0 = rf(_a0, _a1) } else { r0 = ret.Error(0) } return r0 } // ListKeysPagesWithContext provides a mock function with given fields: _a0, _a1, _a2, _a3 func (_m *KMSAPI) ListKeysPagesWithContext(_a0 context.Context, _a1 *kms.ListKeysInput, _a2 func(*kms.ListKeysOutput, bool) bool, _a3 ...request.Option) error { _va := make([]interface{}, len(_a3)) for _i := range _a3 { _va[_i] = _a3[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1, _a2) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 error if rf, ok := ret.Get(0).(func(context.Context, *kms.ListKeysInput, func(*kms.ListKeysOutput, bool) bool, ...request.Option) error); ok { r0 = rf(_a0, _a1, _a2, _a3...) } else { r0 = ret.Error(0) } return r0 } // ListKeysRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) ListKeysRequest(_a0 *kms.ListKeysInput) (*request.Request, *kms.ListKeysOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.ListKeysInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.ListKeysOutput if rf, ok := ret.Get(1).(func(*kms.ListKeysInput) *kms.ListKeysOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.ListKeysOutput) } } return r0, r1 } // ListKeysWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) ListKeysWithContext(_a0 context.Context, _a1 *kms.ListKeysInput, _a2 ...request.Option) (*kms.ListKeysOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.ListKeysOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.ListKeysInput, ...request.Option) *kms.ListKeysOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ListKeysOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.ListKeysInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // ListResourceTags provides a mock function with given fields: _a0 func (_m *KMSAPI) ListResourceTags(_a0 *kms.ListResourceTagsInput) (*kms.ListResourceTagsOutput, error) { ret := _m.Called(_a0) var r0 *kms.ListResourceTagsOutput if rf, ok := ret.Get(0).(func(*kms.ListResourceTagsInput) *kms.ListResourceTagsOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ListResourceTagsOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.ListResourceTagsInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // ListResourceTagsRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) ListResourceTagsRequest(_a0 *kms.ListResourceTagsInput) (*request.Request, *kms.ListResourceTagsOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.ListResourceTagsInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.ListResourceTagsOutput if rf, ok := ret.Get(1).(func(*kms.ListResourceTagsInput) *kms.ListResourceTagsOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.ListResourceTagsOutput) } } return r0, r1 } // ListResourceTagsWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) ListResourceTagsWithContext(_a0 context.Context, _a1 *kms.ListResourceTagsInput, _a2 ...request.Option) (*kms.ListResourceTagsOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.ListResourceTagsOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.ListResourceTagsInput, ...request.Option) *kms.ListResourceTagsOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ListResourceTagsOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.ListResourceTagsInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // ListRetirableGrants provides a mock function with given fields: _a0 func (_m *KMSAPI) ListRetirableGrants(_a0 *kms.ListRetirableGrantsInput) (*kms.ListGrantsResponse, error) { ret := _m.Called(_a0) var r0 *kms.ListGrantsResponse if rf, ok := ret.Get(0).(func(*kms.ListRetirableGrantsInput) *kms.ListGrantsResponse); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ListGrantsResponse) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.ListRetirableGrantsInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // ListRetirableGrantsRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) ListRetirableGrantsRequest(_a0 *kms.ListRetirableGrantsInput) (*request.Request, *kms.ListGrantsResponse) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.ListRetirableGrantsInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.ListGrantsResponse if rf, ok := ret.Get(1).(func(*kms.ListRetirableGrantsInput) *kms.ListGrantsResponse); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.ListGrantsResponse) } } return r0, r1 } // ListRetirableGrantsWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) ListRetirableGrantsWithContext(_a0 context.Context, _a1 *kms.ListRetirableGrantsInput, _a2 ...request.Option) (*kms.ListGrantsResponse, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.ListGrantsResponse if rf, ok := ret.Get(0).(func(context.Context, *kms.ListRetirableGrantsInput, ...request.Option) *kms.ListGrantsResponse); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ListGrantsResponse) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.ListRetirableGrantsInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // PutKeyPolicy provides a mock function with given fields: _a0 func (_m *KMSAPI) PutKeyPolicy(_a0 *kms.PutKeyPolicyInput) (*kms.PutKeyPolicyOutput, error) { ret := _m.Called(_a0) var r0 *kms.PutKeyPolicyOutput if rf, ok := ret.Get(0).(func(*kms.PutKeyPolicyInput) *kms.PutKeyPolicyOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.PutKeyPolicyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.PutKeyPolicyInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // PutKeyPolicyRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) PutKeyPolicyRequest(_a0 *kms.PutKeyPolicyInput) (*request.Request, *kms.PutKeyPolicyOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.PutKeyPolicyInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.PutKeyPolicyOutput if rf, ok := ret.Get(1).(func(*kms.PutKeyPolicyInput) *kms.PutKeyPolicyOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.PutKeyPolicyOutput) } } return r0, r1 } // PutKeyPolicyWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) PutKeyPolicyWithContext(_a0 context.Context, _a1 *kms.PutKeyPolicyInput, _a2 ...request.Option) (*kms.PutKeyPolicyOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.PutKeyPolicyOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.PutKeyPolicyInput, ...request.Option) *kms.PutKeyPolicyOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.PutKeyPolicyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.PutKeyPolicyInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // ReEncrypt provides a mock function with given fields: _a0 func (_m *KMSAPI) ReEncrypt(_a0 *kms.ReEncryptInput) (*kms.ReEncryptOutput, error) { ret := _m.Called(_a0) var r0 *kms.ReEncryptOutput if rf, ok := ret.Get(0).(func(*kms.ReEncryptInput) *kms.ReEncryptOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ReEncryptOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.ReEncryptInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // ReEncryptRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) ReEncryptRequest(_a0 *kms.ReEncryptInput) (*request.Request, *kms.ReEncryptOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.ReEncryptInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.ReEncryptOutput if rf, ok := ret.Get(1).(func(*kms.ReEncryptInput) *kms.ReEncryptOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.ReEncryptOutput) } } return r0, r1 } // ReEncryptWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) ReEncryptWithContext(_a0 context.Context, _a1 *kms.ReEncryptInput, _a2 ...request.Option) (*kms.ReEncryptOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.ReEncryptOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.ReEncryptInput, ...request.Option) *kms.ReEncryptOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ReEncryptOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.ReEncryptInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // ReplicateKey provides a mock function with given fields: _a0 func (_m *KMSAPI) ReplicateKey(_a0 *kms.ReplicateKeyInput) (*kms.ReplicateKeyOutput, error) { ret := _m.Called(_a0) var r0 *kms.ReplicateKeyOutput if rf, ok := ret.Get(0).(func(*kms.ReplicateKeyInput) *kms.ReplicateKeyOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ReplicateKeyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.ReplicateKeyInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // ReplicateKeyRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) ReplicateKeyRequest(_a0 *kms.ReplicateKeyInput) (*request.Request, *kms.ReplicateKeyOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.ReplicateKeyInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.ReplicateKeyOutput if rf, ok := ret.Get(1).(func(*kms.ReplicateKeyInput) *kms.ReplicateKeyOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.ReplicateKeyOutput) } } return r0, r1 } // ReplicateKeyWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) ReplicateKeyWithContext(_a0 context.Context, _a1 *kms.ReplicateKeyInput, _a2 ...request.Option) (*kms.ReplicateKeyOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.ReplicateKeyOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.ReplicateKeyInput, ...request.Option) *kms.ReplicateKeyOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ReplicateKeyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.ReplicateKeyInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // RetireGrant provides a mock function with given fields: _a0 func (_m *KMSAPI) RetireGrant(_a0 *kms.RetireGrantInput) (*kms.RetireGrantOutput, error) { ret := _m.Called(_a0) var r0 *kms.RetireGrantOutput if rf, ok := ret.Get(0).(func(*kms.RetireGrantInput) *kms.RetireGrantOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.RetireGrantOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.RetireGrantInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // RetireGrantRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) RetireGrantRequest(_a0 *kms.RetireGrantInput) (*request.Request, *kms.RetireGrantOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.RetireGrantInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.RetireGrantOutput if rf, ok := ret.Get(1).(func(*kms.RetireGrantInput) *kms.RetireGrantOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.RetireGrantOutput) } } return r0, r1 } // RetireGrantWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) RetireGrantWithContext(_a0 context.Context, _a1 *kms.RetireGrantInput, _a2 ...request.Option) (*kms.RetireGrantOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.RetireGrantOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.RetireGrantInput, ...request.Option) *kms.RetireGrantOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.RetireGrantOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.RetireGrantInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // RevokeGrant provides a mock function with given fields: _a0 func (_m *KMSAPI) RevokeGrant(_a0 *kms.RevokeGrantInput) (*kms.RevokeGrantOutput, error) { ret := _m.Called(_a0) var r0 *kms.RevokeGrantOutput if rf, ok := ret.Get(0).(func(*kms.RevokeGrantInput) *kms.RevokeGrantOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.RevokeGrantOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.RevokeGrantInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // RevokeGrantRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) RevokeGrantRequest(_a0 *kms.RevokeGrantInput) (*request.Request, *kms.RevokeGrantOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.RevokeGrantInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.RevokeGrantOutput if rf, ok := ret.Get(1).(func(*kms.RevokeGrantInput) *kms.RevokeGrantOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.RevokeGrantOutput) } } return r0, r1 } // RevokeGrantWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) RevokeGrantWithContext(_a0 context.Context, _a1 *kms.RevokeGrantInput, _a2 ...request.Option) (*kms.RevokeGrantOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.RevokeGrantOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.RevokeGrantInput, ...request.Option) *kms.RevokeGrantOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.RevokeGrantOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.RevokeGrantInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // ScheduleKeyDeletion provides a mock function with given fields: _a0 func (_m *KMSAPI) ScheduleKeyDeletion(_a0 *kms.ScheduleKeyDeletionInput) (*kms.ScheduleKeyDeletionOutput, error) { ret := _m.Called(_a0) var r0 *kms.ScheduleKeyDeletionOutput if rf, ok := ret.Get(0).(func(*kms.ScheduleKeyDeletionInput) *kms.ScheduleKeyDeletionOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ScheduleKeyDeletionOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.ScheduleKeyDeletionInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // ScheduleKeyDeletionRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) ScheduleKeyDeletionRequest(_a0 *kms.ScheduleKeyDeletionInput) (*request.Request, *kms.ScheduleKeyDeletionOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.ScheduleKeyDeletionInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.ScheduleKeyDeletionOutput if rf, ok := ret.Get(1).(func(*kms.ScheduleKeyDeletionInput) *kms.ScheduleKeyDeletionOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.ScheduleKeyDeletionOutput) } } return r0, r1 } // ScheduleKeyDeletionWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) ScheduleKeyDeletionWithContext(_a0 context.Context, _a1 *kms.ScheduleKeyDeletionInput, _a2 ...request.Option) (*kms.ScheduleKeyDeletionOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.ScheduleKeyDeletionOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.ScheduleKeyDeletionInput, ...request.Option) *kms.ScheduleKeyDeletionOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.ScheduleKeyDeletionOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.ScheduleKeyDeletionInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // Sign provides a mock function with given fields: _a0 func (_m *KMSAPI) Sign(_a0 *kms.SignInput) (*kms.SignOutput, error) { ret := _m.Called(_a0) var r0 *kms.SignOutput if rf, ok := ret.Get(0).(func(*kms.SignInput) *kms.SignOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.SignOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.SignInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // SignRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) SignRequest(_a0 *kms.SignInput) (*request.Request, *kms.SignOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.SignInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.SignOutput if rf, ok := ret.Get(1).(func(*kms.SignInput) *kms.SignOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.SignOutput) } } return r0, r1 } // SignWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) SignWithContext(_a0 context.Context, _a1 *kms.SignInput, _a2 ...request.Option) (*kms.SignOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.SignOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.SignInput, ...request.Option) *kms.SignOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.SignOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.SignInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // TagResource provides a mock function with given fields: _a0 func (_m *KMSAPI) TagResource(_a0 *kms.TagResourceInput) (*kms.TagResourceOutput, error) { ret := _m.Called(_a0) var r0 *kms.TagResourceOutput if rf, ok := ret.Get(0).(func(*kms.TagResourceInput) *kms.TagResourceOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.TagResourceOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.TagResourceInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // TagResourceRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) TagResourceRequest(_a0 *kms.TagResourceInput) (*request.Request, *kms.TagResourceOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.TagResourceInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.TagResourceOutput if rf, ok := ret.Get(1).(func(*kms.TagResourceInput) *kms.TagResourceOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.TagResourceOutput) } } return r0, r1 } // TagResourceWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) TagResourceWithContext(_a0 context.Context, _a1 *kms.TagResourceInput, _a2 ...request.Option) (*kms.TagResourceOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.TagResourceOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.TagResourceInput, ...request.Option) *kms.TagResourceOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.TagResourceOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.TagResourceInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // UntagResource provides a mock function with given fields: _a0 func (_m *KMSAPI) UntagResource(_a0 *kms.UntagResourceInput) (*kms.UntagResourceOutput, error) { ret := _m.Called(_a0) var r0 *kms.UntagResourceOutput if rf, ok := ret.Get(0).(func(*kms.UntagResourceInput) *kms.UntagResourceOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.UntagResourceOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.UntagResourceInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // UntagResourceRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) UntagResourceRequest(_a0 *kms.UntagResourceInput) (*request.Request, *kms.UntagResourceOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.UntagResourceInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.UntagResourceOutput if rf, ok := ret.Get(1).(func(*kms.UntagResourceInput) *kms.UntagResourceOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.UntagResourceOutput) } } return r0, r1 } // UntagResourceWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) UntagResourceWithContext(_a0 context.Context, _a1 *kms.UntagResourceInput, _a2 ...request.Option) (*kms.UntagResourceOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.UntagResourceOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.UntagResourceInput, ...request.Option) *kms.UntagResourceOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.UntagResourceOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.UntagResourceInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // UpdateAlias provides a mock function with given fields: _a0 func (_m *KMSAPI) UpdateAlias(_a0 *kms.UpdateAliasInput) (*kms.UpdateAliasOutput, error) { ret := _m.Called(_a0) var r0 *kms.UpdateAliasOutput if rf, ok := ret.Get(0).(func(*kms.UpdateAliasInput) *kms.UpdateAliasOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.UpdateAliasOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.UpdateAliasInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // UpdateAliasRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) UpdateAliasRequest(_a0 *kms.UpdateAliasInput) (*request.Request, *kms.UpdateAliasOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.UpdateAliasInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.UpdateAliasOutput if rf, ok := ret.Get(1).(func(*kms.UpdateAliasInput) *kms.UpdateAliasOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.UpdateAliasOutput) } } return r0, r1 } // UpdateAliasWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) UpdateAliasWithContext(_a0 context.Context, _a1 *kms.UpdateAliasInput, _a2 ...request.Option) (*kms.UpdateAliasOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.UpdateAliasOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.UpdateAliasInput, ...request.Option) *kms.UpdateAliasOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.UpdateAliasOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.UpdateAliasInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // UpdateCustomKeyStore provides a mock function with given fields: _a0 func (_m *KMSAPI) UpdateCustomKeyStore(_a0 *kms.UpdateCustomKeyStoreInput) (*kms.UpdateCustomKeyStoreOutput, error) { ret := _m.Called(_a0) var r0 *kms.UpdateCustomKeyStoreOutput if rf, ok := ret.Get(0).(func(*kms.UpdateCustomKeyStoreInput) *kms.UpdateCustomKeyStoreOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.UpdateCustomKeyStoreOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.UpdateCustomKeyStoreInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // UpdateCustomKeyStoreRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) UpdateCustomKeyStoreRequest(_a0 *kms.UpdateCustomKeyStoreInput) (*request.Request, *kms.UpdateCustomKeyStoreOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.UpdateCustomKeyStoreInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.UpdateCustomKeyStoreOutput if rf, ok := ret.Get(1).(func(*kms.UpdateCustomKeyStoreInput) *kms.UpdateCustomKeyStoreOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.UpdateCustomKeyStoreOutput) } } return r0, r1 } // UpdateCustomKeyStoreWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) UpdateCustomKeyStoreWithContext(_a0 context.Context, _a1 *kms.UpdateCustomKeyStoreInput, _a2 ...request.Option) (*kms.UpdateCustomKeyStoreOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.UpdateCustomKeyStoreOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.UpdateCustomKeyStoreInput, ...request.Option) *kms.UpdateCustomKeyStoreOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.UpdateCustomKeyStoreOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.UpdateCustomKeyStoreInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // UpdateKeyDescription provides a mock function with given fields: _a0 func (_m *KMSAPI) UpdateKeyDescription(_a0 *kms.UpdateKeyDescriptionInput) (*kms.UpdateKeyDescriptionOutput, error) { ret := _m.Called(_a0) var r0 *kms.UpdateKeyDescriptionOutput if rf, ok := ret.Get(0).(func(*kms.UpdateKeyDescriptionInput) *kms.UpdateKeyDescriptionOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.UpdateKeyDescriptionOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.UpdateKeyDescriptionInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // UpdateKeyDescriptionRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) UpdateKeyDescriptionRequest(_a0 *kms.UpdateKeyDescriptionInput) (*request.Request, *kms.UpdateKeyDescriptionOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.UpdateKeyDescriptionInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.UpdateKeyDescriptionOutput if rf, ok := ret.Get(1).(func(*kms.UpdateKeyDescriptionInput) *kms.UpdateKeyDescriptionOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.UpdateKeyDescriptionOutput) } } return r0, r1 } // UpdateKeyDescriptionWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) UpdateKeyDescriptionWithContext(_a0 context.Context, _a1 *kms.UpdateKeyDescriptionInput, _a2 ...request.Option) (*kms.UpdateKeyDescriptionOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.UpdateKeyDescriptionOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.UpdateKeyDescriptionInput, ...request.Option) *kms.UpdateKeyDescriptionOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.UpdateKeyDescriptionOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.UpdateKeyDescriptionInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // UpdatePrimaryRegion provides a mock function with given fields: _a0 func (_m *KMSAPI) UpdatePrimaryRegion(_a0 *kms.UpdatePrimaryRegionInput) (*kms.UpdatePrimaryRegionOutput, error) { ret := _m.Called(_a0) var r0 *kms.UpdatePrimaryRegionOutput if rf, ok := ret.Get(0).(func(*kms.UpdatePrimaryRegionInput) *kms.UpdatePrimaryRegionOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.UpdatePrimaryRegionOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.UpdatePrimaryRegionInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // UpdatePrimaryRegionRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) UpdatePrimaryRegionRequest(_a0 *kms.UpdatePrimaryRegionInput) (*request.Request, *kms.UpdatePrimaryRegionOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.UpdatePrimaryRegionInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.UpdatePrimaryRegionOutput if rf, ok := ret.Get(1).(func(*kms.UpdatePrimaryRegionInput) *kms.UpdatePrimaryRegionOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.UpdatePrimaryRegionOutput) } } return r0, r1 } // UpdatePrimaryRegionWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) UpdatePrimaryRegionWithContext(_a0 context.Context, _a1 *kms.UpdatePrimaryRegionInput, _a2 ...request.Option) (*kms.UpdatePrimaryRegionOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.UpdatePrimaryRegionOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.UpdatePrimaryRegionInput, ...request.Option) *kms.UpdatePrimaryRegionOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.UpdatePrimaryRegionOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.UpdatePrimaryRegionInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // Verify provides a mock function with given fields: _a0 func (_m *KMSAPI) Verify(_a0 *kms.VerifyInput) (*kms.VerifyOutput, error) { ret := _m.Called(_a0) var r0 *kms.VerifyOutput if rf, ok := ret.Get(0).(func(*kms.VerifyInput) *kms.VerifyOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.VerifyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.VerifyInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // VerifyMac provides a mock function with given fields: _a0 func (_m *KMSAPI) VerifyMac(_a0 *kms.VerifyMacInput) (*kms.VerifyMacOutput, error) { ret := _m.Called(_a0) var r0 *kms.VerifyMacOutput if rf, ok := ret.Get(0).(func(*kms.VerifyMacInput) *kms.VerifyMacOutput); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.VerifyMacOutput) } } var r1 error if rf, ok := ret.Get(1).(func(*kms.VerifyMacInput) error); ok { r1 = rf(_a0) } else { r1 = ret.Error(1) } return r0, r1 } // VerifyMacRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) VerifyMacRequest(_a0 *kms.VerifyMacInput) (*request.Request, *kms.VerifyMacOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.VerifyMacInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.VerifyMacOutput if rf, ok := ret.Get(1).(func(*kms.VerifyMacInput) *kms.VerifyMacOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.VerifyMacOutput) } } return r0, r1 } // VerifyMacWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) VerifyMacWithContext(_a0 context.Context, _a1 *kms.VerifyMacInput, _a2 ...request.Option) (*kms.VerifyMacOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.VerifyMacOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.VerifyMacInput, ...request.Option) *kms.VerifyMacOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.VerifyMacOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.VerifyMacInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } // VerifyRequest provides a mock function with given fields: _a0 func (_m *KMSAPI) VerifyRequest(_a0 *kms.VerifyInput) (*request.Request, *kms.VerifyOutput) { ret := _m.Called(_a0) var r0 *request.Request if rf, ok := ret.Get(0).(func(*kms.VerifyInput) *request.Request); ok { r0 = rf(_a0) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*request.Request) } } var r1 *kms.VerifyOutput if rf, ok := ret.Get(1).(func(*kms.VerifyInput) *kms.VerifyOutput); ok { r1 = rf(_a0) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*kms.VerifyOutput) } } return r0, r1 } // VerifyWithContext provides a mock function with given fields: _a0, _a1, _a2 func (_m *KMSAPI) VerifyWithContext(_a0 context.Context, _a1 *kms.VerifyInput, _a2 ...request.Option) (*kms.VerifyOutput, error) { _va := make([]interface{}, len(_a2)) for _i := range _a2 { _va[_i] = _a2[_i] } var _ca []interface{} _ca = append(_ca, _a0, _a1) _ca = append(_ca, _va...) ret := _m.Called(_ca...) var r0 *kms.VerifyOutput if rf, ok := ret.Get(0).(func(context.Context, *kms.VerifyInput, ...request.Option) *kms.VerifyOutput); ok { r0 = rf(_a0, _a1, _a2...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*kms.VerifyOutput) } } var r1 error if rf, ok := ret.Get(1).(func(context.Context, *kms.VerifyInput, ...request.Option) error); ok { r1 = rf(_a0, _a1, _a2...) } else { r1 = ret.Error(1) } return r0, r1 } 07070100000085000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001300000000sops-3.7.3/logging07070100000086000081A400000000000000000000000162794F9300000467000000000000000000000000000000000000001E00000000sops-3.7.3/logging/logging.gopackage logging import ( "fmt" "github.com/fatih/color" "github.com/sirupsen/logrus" ) func init() { Loggers = make(map[string]*logrus.Logger) } // TextFormatter extends the standard logrus TextFormatter and adds a field to specify the logger's name type TextFormatter struct { LoggerName string logrus.TextFormatter } // Format formats a log entry onto bytes func (f *TextFormatter) Format(entry *logrus.Entry) ([]byte, error) { bytes, err := f.TextFormatter.Format(entry) name := color.New(color.Bold).Sprintf("[%s]", f.LoggerName) return []byte(fmt.Sprintf("%s\t %s", name, bytes)), err } // NewLogger is the constructor for a new Logger object with the given name func NewLogger(name string) *logrus.Logger { log := logrus.New() log.SetLevel(logrus.WarnLevel) log.Formatter = &TextFormatter{ LoggerName: name, } Loggers[name] = log return log } // SetLevel sets the given level for all current Loggers func SetLevel(level logrus.Level) { for k := range Loggers { Loggers[k].SetLevel(level) } } // Loggers is the runtime map of logger name to logger object var Loggers map[string]*logrus.Logger 07070100000087000081A400000000000000000000000162794F9300000270000000000000000000000000000000000000002100000000sops-3.7.3/make_download_page.sh#!/usr/bin/env bash [ ! -d dist ] && mkdir dist echo -e "<html>\n<head><title>Sops download page></title>\n<body>\n<h1>Sops download page</h1>\n<h2><a href="https://go.mozilla.org/sops/">go.mozilla.org/sops</a></h2>\n<table>" > index.html IFS=$'\n' for dist in $(aws s3 ls s3://go.mozilla.org/sops/dist/ | grep -P "deb|rpm"); do ts=$(echo $dist|awk '{print $1,$2}') size=$(echo $dist|awk '{print $3}') pkg=$(echo $dist|awk '{print $4}') echo -e "<tr><td>$ts</td><td>$size</td><td><a href=\"https://go.mozilla.org/sops/dist/$pkg\">$pkg</a></td></tr>" >> index.html done echo -e "</table>\n</body>\n</html>" >> index.html 07070100000088000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000000F00000000sops-3.7.3/pgp07070100000089000081A400000000000000000000000162794F930000296E000000000000000000000000000000000000001C00000000sops-3.7.3/pgp/keysource.go/* Package pgp contains an implementation of the go.mozilla.org/sops/v3.MasterKey interface that encrypts and decrypts the data key by first trying with the github.com/ProtonMail/go-crypto/openpgp package and if that fails, by calling the "gpg" binary. */ package pgp //import "go.mozilla.org/sops/v3/pgp" import ( "bytes" "encoding/hex" "fmt" "io/ioutil" "net/http" "os" "os/exec" "os/user" "path" "strings" "time" "github.com/ProtonMail/go-crypto/openpgp" "github.com/ProtonMail/go-crypto/openpgp/armor" "github.com/howeyc/gopass" "github.com/sirupsen/logrus" gpgagent "go.mozilla.org/gopgagent" "go.mozilla.org/sops/v3/logging" ) var log *logrus.Logger func init() { log = logging.NewLogger("PGP") } // MasterKey is a PGP key used to securely store sops' data key by encrypting it and decrypting it type MasterKey struct { Fingerprint string EncryptedKey string CreationDate time.Time } // EncryptedDataKey returns the encrypted data key this master key holds func (key *MasterKey) EncryptedDataKey() []byte { return []byte(key.EncryptedKey) } // SetEncryptedDataKey sets the encrypted data key for this master key func (key *MasterKey) SetEncryptedDataKey(enc []byte) { key.EncryptedKey = string(enc) } func gpgBinary() string { binary := "gpg" if envBinary := os.Getenv("SOPS_GPG_EXEC"); envBinary != "" { binary = envBinary } return binary } func (key *MasterKey) encryptWithGPGBinary(dataKey []byte) error { fingerprint := key.Fingerprint if offset := len(fingerprint) - 16; offset > 0 { fingerprint = fingerprint[offset:] } args := []string{ "--no-default-recipient", "--yes", "--encrypt", "-a", "-r", key.Fingerprint, "--trusted-key", fingerprint, "--no-encrypt-to", } cmd := exec.Command(gpgBinary(), args...) var stdout, stderr bytes.Buffer cmd.Stdin = bytes.NewReader(dataKey) cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() if err != nil { return fmt.Errorf("gpg binary failed with error: %s, %s", err, stderr.String()) } key.EncryptedKey = stdout.String() return nil } func getKeyFromKeyServer(fingerprint string) (openpgp.Entity, error) { log.Warn("Deprecation Warning: GPG key fetching from a keyserver within sops will be removed in a future version of sops. See https://github.com/mozilla/sops/issues/727 for more information.") url := fmt.Sprintf("https://keys.openpgp.org/vks/v1/by-fingerprint/%s", fingerprint) resp, err := http.Get(url) if err != nil { return openpgp.Entity{}, fmt.Errorf("error getting key from keyserver: %s", err) } defer resp.Body.Close() if resp.StatusCode != 200 { return openpgp.Entity{}, fmt.Errorf("keyserver returned non-200 status code %s", resp.Status) } ents, err := openpgp.ReadArmoredKeyRing(resp.Body) if err != nil { return openpgp.Entity{}, fmt.Errorf("could not read entities: %s", err) } return *ents[0], nil } func (key *MasterKey) getPubKey() (openpgp.Entity, error) { ring, err := key.pubRing() if err == nil { fingerprints := key.fingerprintMap(ring) entity, ok := fingerprints[key.Fingerprint] if ok { return entity, nil } } entity, err := getKeyFromKeyServer(key.Fingerprint) if err != nil { return openpgp.Entity{}, fmt.Errorf("key with fingerprint %s is not available "+ "in keyring and could not be retrieved from keyserver", key.Fingerprint) } return entity, nil } func (key *MasterKey) encryptWithCryptoOpenPGP(dataKey []byte) error { entity, err := key.getPubKey() if err != nil { return err } encbuf := new(bytes.Buffer) armorbuf, err := armor.Encode(encbuf, "PGP MESSAGE", nil) if err != nil { return err } plaintextbuf, err := openpgp.Encrypt(armorbuf, []*openpgp.Entity{&entity}, nil, &openpgp.FileHints{IsBinary: true}, nil) if err != nil { return err } _, err = plaintextbuf.Write(dataKey) if err != nil { return err } err = plaintextbuf.Close() if err != nil { return err } err = armorbuf.Close() if err != nil { return err } bytes, err := ioutil.ReadAll(encbuf) if err != nil { return err } key.EncryptedKey = string(bytes) return nil } // Encrypt encrypts the data key with the PGP key with the same fingerprint as the MasterKey. It looks for PGP public keys in $PGPHOME/pubring.gpg. func (key *MasterKey) Encrypt(dataKey []byte) error { openpgpErr := key.encryptWithCryptoOpenPGP(dataKey) if openpgpErr == nil { log.WithField("fingerprint", key.Fingerprint).Info("Encryption succeeded") return nil } binaryErr := key.encryptWithGPGBinary(dataKey) if binaryErr == nil { log.WithField("fingerprint", key.Fingerprint).Info("Encryption succeeded") return nil } log.WithField("fingerprint", key.Fingerprint).Info("Encryption failed") return fmt.Errorf( `could not encrypt data key with PGP key: github.com/ProtonMail/go-crypto/openpgp error: %v; GPG binary error: %v`, openpgpErr, binaryErr) } // EncryptIfNeeded encrypts the data key with PGP only if it's needed, that is, if it hasn't been encrypted already func (key *MasterKey) EncryptIfNeeded(dataKey []byte) error { if key.EncryptedKey == "" { return key.Encrypt(dataKey) } return nil } func (key *MasterKey) decryptWithGPGBinary() ([]byte, error) { args := []string{ "--use-agent", "-d", } cmd := exec.Command(gpgBinary(), args...) var stdout, stderr bytes.Buffer cmd.Stdin = strings.NewReader(key.EncryptedKey) cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() if err != nil { return nil, err } return stdout.Bytes(), nil } func (key *MasterKey) decryptWithCryptoOpenpgp() ([]byte, error) { ring, err := key.secRing() if err != nil { return nil, fmt.Errorf("Could not load secring: %s", err) } block, err := armor.Decode(strings.NewReader(key.EncryptedKey)) if err != nil { return nil, fmt.Errorf("Armor decoding failed: %s", err) } md, err := openpgp.ReadMessage(block.Body, ring, key.passphrasePrompt(), nil) if err != nil { return nil, fmt.Errorf("Reading PGP message failed: %s", err) } if b, err := ioutil.ReadAll(md.UnverifiedBody); err == nil { return b, nil } return nil, fmt.Errorf("The key could not be decrypted with any of the PGP entries") } // Decrypt uses PGP to obtain the data key from the EncryptedKey store in the MasterKey and returns it func (key *MasterKey) Decrypt() ([]byte, error) { dataKey, openpgpErr := key.decryptWithCryptoOpenpgp() if openpgpErr == nil { log.WithField("fingerprint", key.Fingerprint).Info("Decryption succeeded") return dataKey, nil } dataKey, binaryErr := key.decryptWithGPGBinary() if binaryErr == nil { log.WithField("fingerprint", key.Fingerprint).Info("Decryption succeeded") return dataKey, nil } log.WithField("fingerprint", key.Fingerprint).Info("Decryption failed") return nil, fmt.Errorf( `could not decrypt data key with PGP key: github.com/ProtonMail/go-crypto/openpgp error: %v; GPG binary error: %v`, openpgpErr, binaryErr) } // NeedsRotation returns whether the data key needs to be rotated or not func (key *MasterKey) NeedsRotation() bool { return time.Since(key.CreationDate).Hours() > 24*30*6 } // ToString returns the string representation of the key, i.e. its fingerprint func (key *MasterKey) ToString() string { return key.Fingerprint } func (key *MasterKey) gpgHome() string { dir := os.Getenv("GNUPGHOME") if dir == "" { usr, err := user.Current() if err != nil { return path.Join(os.Getenv("HOME"), "/.gnupg") } return path.Join(usr.HomeDir, ".gnupg") } return dir } // NewMasterKeyFromFingerprint takes a PGP fingerprint and returns a new MasterKey with that fingerprint func NewMasterKeyFromFingerprint(fingerprint string) *MasterKey { return &MasterKey{ Fingerprint: strings.Replace(fingerprint, " ", "", -1), CreationDate: time.Now().UTC(), } } // MasterKeysFromFingerprintString takes a comma separated list of PGP fingerprints and returns a slice of new MasterKeys with those fingerprints func MasterKeysFromFingerprintString(fingerprint string) []*MasterKey { var keys []*MasterKey if fingerprint == "" { return keys } for _, s := range strings.Split(fingerprint, ",") { keys = append(keys, NewMasterKeyFromFingerprint(s)) } return keys } func (key *MasterKey) loadRing(path string) (openpgp.EntityList, error) { f, err := os.Open(path) if err != nil { return openpgp.EntityList{}, err } defer f.Close() keyring, err := openpgp.ReadKeyRing(f) if err != nil { return keyring, err } return keyring, nil } func (key *MasterKey) secRing() (openpgp.EntityList, error) { return key.loadRing(key.gpgHome() + "/secring.gpg") } func (key *MasterKey) pubRing() (openpgp.EntityList, error) { return key.loadRing(key.gpgHome() + "/pubring.gpg") } func (key *MasterKey) fingerprintMap(ring openpgp.EntityList) map[string]openpgp.Entity { fps := make(map[string]openpgp.Entity) for _, entity := range ring { fp := strings.ToUpper(hex.EncodeToString(entity.PrimaryKey.Fingerprint[:])) if entity != nil { fps[fp] = *entity } } return fps } func (key *MasterKey) passphrasePrompt() func(keys []openpgp.Key, symmetric bool) ([]byte, error) { callCounter := 0 maxCalls := 3 return func(keys []openpgp.Key, symmetric bool) ([]byte, error) { if callCounter >= maxCalls { return nil, fmt.Errorf("function passphrasePrompt called too many times") } callCounter++ conn, err := gpgagent.NewConn() if err == gpgagent.ErrNoAgent { log.Infof("gpg-agent not found, continuing with manual passphrase " + "input...") fmt.Print("Enter PGP key passphrase: ") pass, err := gopass.GetPasswd() if err != nil { return nil, err } for _, k := range keys { k.PrivateKey.Decrypt(pass) } return pass, err } if err != nil { return nil, fmt.Errorf("Could not establish connection with gpg-agent: %s", err) } defer conn.Close() for _, k := range keys { req := gpgagent.PassphraseRequest{ CacheKey: k.PublicKey.KeyIdShortString(), Prompt: "Passphrase", Desc: fmt.Sprintf("Unlock key %s to decrypt sops's key", k.PublicKey.KeyIdShortString()), } pass, err := conn.GetPassphrase(&req) if err != nil { return nil, fmt.Errorf("gpg-agent passphrase request errored: %s", err) } k.PrivateKey.Decrypt([]byte(pass)) return []byte(pass), nil } return nil, fmt.Errorf("No key to unlock") } } // ToMap converts the MasterKey into a map for serialization purposes func (key MasterKey) ToMap() map[string]interface{} { out := make(map[string]interface{}) out["fp"] = key.Fingerprint out["created_at"] = key.CreationDate.UTC().Format(time.RFC3339) out["enc"] = key.EncryptedKey return out } 0707010000008A000081A400000000000000000000000162794F93000005F2000000000000000000000000000000000000002100000000sops-3.7.3/pgp/keysource_test.gopackage pgp import ( "bytes" "testing" "testing/quick" "github.com/stretchr/testify/assert" ) func TestPGP(t *testing.T) { key := NewMasterKeyFromFingerprint("FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4") f := func(x []byte) bool { if x == nil || len(x) == 0 { return true } if err := key.Encrypt(x); err != nil { t.Errorf("Failed to encrypt: %#v err: %w", x, err) return false } k, err := key.Decrypt() if err != nil { t.Errorf("Failed to decrypt: %#v err: %w", x, err) return false } return bytes.Equal(x, k) } if err := quick.Check(f, nil); err != nil { t.Error(err) } } func TestPGPKeySourceFromString(t *testing.T) { s := "C8C5 2C0A B2A4 8174 01E8 12C8 F3CC 3233 3FAD 9F1E, C8C5 2C0A B2A4 8174 01E8 12C8 F3CC 3233 3FAD 9F1E" ks := MasterKeysFromFingerprintString(s) expected := "C8C52C0AB2A4817401E812C8F3CC32333FAD9F1E" if ks[0].Fingerprint != expected { t.Errorf("Fingerprint does not match. Got %s, expected %s", ks[0].Fingerprint, expected) } if ks[1].Fingerprint != expected { t.Error("Fingerprint does not match") } } func TestRetrievePGPKey(t *testing.T) { // Requires a key available in https://keys.openpgp.org/ *with identity information* (that is, an email address). // See https://keys.openpgp.org/about/faq#verify-multiple for details about identity information. // We use the key of release@mozilla.com for here. fingerprint := "14F26682D0916CDD81E37B6D61B7B526D98F0353" _, err := getKeyFromKeyServer(fingerprint) assert.NoError(t, err) } 0707010000008B000081A400000000000000000000000162794F9300002861000000000000000000000000000000000000002D00000000sops-3.7.3/pgp/sops_functional_tests_key.asc-----BEGIN PGP PRIVATE KEY BLOCK----- lQOYBF1oQV0BCAC1iFfE7H3uu0hbWbRYVMoz5zZ91ACHETCOMVxN8GOG4SV0l8aQ wmK9QWkYxhi52LnicVD3D7Uy75+J3zkvEDQ15C0AZ8UHXp4JlSQuXpFhrOhfYUF/ 6pr/QexT+hQjOacvY4qfnj4xKa/AGdv5vPIygtQumE6r3GhEVAxQ1GSwtCWSU3Zl 3Uqf7S8kDvJTemtR2UkVfpXcMd4AmMKgt7fVhPO8eFotqTLPvz/iClzER+q61fLA d1rP9YlmY46MJp/PffPicWdJiKv2i6ynKcIwkrQyP6V2ZzYi/gAhNJst3ZlMfsiN ekCtcow9Bn44uxW3U8W02FNQSNyn6V6QPDIXABEBAAEAB/0Z8kQSlkzE97QhXm0g /PQuaVCdY9UJeSMBXTvDZhBhAcLf6yZLStq1uz4sIiWm6+ZcX8mXQ9b90fMceoaK sVxiYYaEcCXgu5zcuMTu8xRWK30bzjkARrDjEByZFNLrr/yzO3KKWvdVAToou77N xLxct4df+46vEMs/DOulDUkxBOjlkprlq8xSG/6vuo7rJKUylsS4s5+y+EJCfm0m 8C94IIOt42ANObDUziUHCFNhCKSUs92rL7HXfcMG6L16UrSpJ3yLNvTI34PgRydv ppu6DAFNeqsJ6oINSWXEqjfMHK7Ly9oyF2bkB2VKoapAdz6YGJydrODhFrThcuJk +pY9BADKnXtYvDRPoTsfRYgZewtBxf3ccGUjoS9YCC3salWuPEWnal2yI0YRwZNE iirOFGKH6jh/fxtFZNPXuYb7MJzFqVOcARz6USCvR1va2kMZzQEOKwxOXqIYYMVh Uwz9++QugqcBLHw9YUFmH/DsRaL4zP4H8cX5O1TALFo3aC/EHQQA5VzUDupcpRLP gF6dCgT2GyajgRoUFU7Brq82+HJDBDhHMB+3VWJhsC9DkTMh/RtPOuLb41K0OZ// acoXo0QjsLsBx+hNqWC0oosqaoXiUyhbmEukvlURm5uHThX9n5BZIKhiCft/NYNO yb+OBgYFHN11BMUVyhMR7be2mlJ4EMMD/jd9WQIoHQQ6BKMNOlc6BGu4KsMv/+fF KV4xnJKrWjJxwri0FsOYLS2qkgbSAXjxLqZWx4UylmJh1HSAyjTghY0zQEf2oDKd 0DKN8Y42aawh1AolIfDbYOampw5tBzI2/WYOksGRFCwjCidL3pNd03W9dBmNbBRc tVKLG/kt4JwCL0y0U1NPUFMgRnVuY3Rpb25hbCBUZXN0cyBLZXkgMSAoaHR0cHM6 Ly9naXRodWIuY29tL21vemlsbGEvc29wcy8pIDxzZWNvcHNAbW96aWxsYS5jb20+ iQFOBBMBCAA4FiEE+8e54qT5KJrAwdSEPRbO5KJzgbQFAl1oQV0CGwMFCwkIBwIG FQoJCAsCBBYCAwECHgECF4AACgkQPRbO5KJzgbTDcQf7Bp7e2zY9pBBXTgDASQl3 1SSHp9WkRUV5iqPVC9iPCELggteBGMwIpbDlobc6O8/06foxWctTUaaciPBo2+je WFTO+DNvB7oXIArqr5673QHLh6jEABBjyt91rvta2wYF1XJBgxpui9aLICsCptFN IRvHeKUrXBI4fG5z3CDs/EOoY8K/AAYJUF+ERtmvmisiE/m20UpbYRmkBJy25c89 Wcn12I1SUJA3H3hGwvZCYp8hY1HPxxQUtU+DZBIpryi0xQqExGAlYqck7G03F+AD 7/csaT1LEdCtWRLNwE8UkvfUF6liF0SgzxFo1pp3gBU4swds9yO9wNe12JY/M5A/ BJ0DmARdaEFdAQgAtun8JhSpNAKvOXwWX2nFhnMXTJp4viMhlAZEdmMXEi27B2DM /nRzldjxGZoNUBSVbJNj2kx5ZUDl0o6eOpChvRaGuCOpYqOuSQvD8FnX0NgQULwu TZ+MawsaezktJEjDSBM1R6uASeJwDZj4hcUnPgyAIESajPdowEkEjdYt261fGOLL cVoVdtqzOMBkLVdrK/FD1kGR9jnSlKEYDV9DveBUBQGdqkgWXjS5BKcae07viC6x Ma9AJS4pizyDALB2k0HQOelZNihOGXYUuvkcs2Fivl0Tk3OCfH9XDvFehbYRHmkR DoMuKUDSzdy6tFBAkL0CPlXAWI6kQklaBEp19QARAQABAAf7BX7YLYi3YLGn9BEv VuSFo7l3fLyzXfsOOjVJ/0iQ2+H12Y3l+ssi4eCntb40IjDMIHv5JwjfKNSfUwkn 5diMk3LGz2d64lTKmrU4yNLaMhMbwmE0/u4JOPoXbJZWLd3lyBeTpTiY3R9pgG8V IGfA+xNDEjUdc5jHU+edtGk37X6l6uL3OANS/MyTRdVNr28Gv/upXmJs/NbvTost 1hsU89gaDjkfsWhdhiuCHR9bqoyot/Vgvpt1NxzfV4SQGVFeph8yCGvSRBS8zXuZ FtmzACs0j2aOMSucAGogEoD158OpXSNfdmZ1nCswlo1yqP6+ir8mr2DTRgMtxPQa N49b4QQAxVTwRZ6+qiSCz/GJPq7qASGG4RIr87gPzxaHmznQhKIx6LEMjX/+NU6c 94A8aZY/oN7f3rr8apIA+cAHbAwFGpbc7ke1Cgy/m/eJZNUxWPT/YBjZ4V+41Uat viGrbmS9B4QulOpF2Ng6LcOc4dggxTPAW/CYd5T2FImr1qYjjWkEAO1Lss00LY5o 5I4QqgM0OeeBEOO8LiSDmjKgOvtsmJ6+dA4x3rYgI8smFMsvtyrcb75k6EdZazgN YSI4sU3WceWbrtdVr1glP38CBMupnFvg8KwbjSFV8vNqVBHCXShUxnHmlOW+UVqy CxjJf0RTOhLEY5DIRwQB0H8P30dYOfatBADaGIbs/6+1RulKpHwW/c3+XOlaTZrT UhNjuccj7Y9IspYD+6crNkQvAri60AoDfIiO5aTk8rSYpGwB1vEmnUVmNPvRF958 GV3pyCOv/pkmnpS+4w+akiJsSHX3jqqp5fb/xd6ukUX95VgSymuJ+ya49G8B0jj6 bw7B4S2M39+Xdkg2iQE2BBgBCAAgFiEE+8e54qT5KJrAwdSEPRbO5KJzgbQFAl1o QV0CGwwACgkQPRbO5KJzgbS7zwgAndbf532OXo9HwPH+yQQmzQCLDFL6P4V7LcFr rydYItTEhxqI3tbb96MKXRAt+G5Mw6JjRkWhwzbU3jE7D7XBMHw7GriTTU9QltNH g7VUpSSaiTfVcSNErzsaqbjbA7jMs7VWzOq4LZo6Efy8UDKg5qcqLFaTQrzQZYNH NfM+kLAiUPU8m7vwmz6oJWsjHkQKUhKhHptlpwMwdHkoacqDO0x2H6H91l/PnDm4 ZG6FybJtcjr98i+p52/XOo81nLgX7tcFS3nrN9HNdgKg1ZW3yrzg8NOaFCVA8qLD gLk//M3qDixOxiurECkFrMvt/bDxEGpN5GVy550MmyUZQrkuqg== =VjGL -----END PGP PRIVATE KEY BLOCK----- -----BEGIN PGP PRIVATE KEY BLOCK----- lQHYBF1oQYgBBADPuVP6Jdk/J/TbNa9dXirp/zzwK18ZqNudNqQGN3H+2aSgxXwL wlRfzy7rB3CU6Ewjzk9EVYeYztTIkGHL0JZ1CCTiBJArlHO0bHQQ7CPeKPkhIhkj eA8yu9dcU77oYC2xbwgf43KYzfMKSGEybg+sBO+bH+Y6paJK54V2cuS3GwARAQAB AAP+Jjf5BXtVP1OAr5xvCYS77JWzhpTUSIpS7dgR0br91GAC9DmhmyBEGeSqwz95 LUyYRbY9y1rZOfpEGCrIc5GLPOQytO9XMIzaS3dpzfGhla/spaKN4vJDvIOl+ruT bInDdCRSmqXCfm2478OhOquc0H0a46eSmoaYeKdE3E8QZiECANxUL/dFk5j8NyPo ZcwXw9Mv0A8UrynRcqht3Scti9k7dbsHylcObM305LFdcoNnSfNAIJhxfjbiXyGW vwT2/qMCAPFatq3gvVjy6wKKylioi5cVwbLv9L+OaRXdR/Dy2bh/t3ujnsliV4+R f7k3rHOQeaMLTnyfcz8AenL5IOe8RSkCANFpBgyzxCcV48Mm+FWDxjrSJ4/msRnN gxqAPRrdpm7e1uebtBkPh4ch4oCW5/lLsRN23LUVIXYJRwyFfRjehCio0rRTU09Q UyBGdW5jdGlvbmFsIFRlc3RzIEtleSAyIChodHRwczovL2dpdGh1Yi5jb20vbW96 aWxsYS9zb3BzLykgPHNlY29wc0Btb3ppbGxhLmNvbT6IzgQTAQgAOBYhBNcikEM4 S8xgMmxvudhyDZV8PTB0BQJdaEGIAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheA AAoJENhyDZV8PTB0R2cD/2YwaJ43iGueaAzByFnl+mUEBQJ4HhH4p7BIdx6B9AjE 3yLe8I4dqqYXxyZzaJ9d+KiqxJBT0l1GXt3H5M32yDJZqzXB9PTWP3yx8+Q1CuCs 7EL/bhJD1/sLdumVc77bmQtcI9NSiYyPzN/2ZqtV5RU14Loh24VFEjuHGvO0jI3+ nQHYBF1oQYgBBAChXi00fmpEs0Jiq0zOyYm9i749VoOsNReoB/5ix1QCimwVZKe1 D37IP5Qqysxy+LIQc4lJ+Q8foNOx1Aev5+TDyv+iU82D9xr9uPLLbA82k3AZ04Or BjrZ/Yt1NZhuaHzciZCPpmqzF9kqVqAZc+vMiKZL1WZjS7O1FwaidY1vXwARAQAB AAP+L0wUQeOfsD0+gv8khyPJTJZOD1pxQ6NYKLcXF8rG0+vQnECha098YKNKAXTp kfVU8795iQYIKcQQ6Hl2O1fj1AxJE/iZYrqfm7UZz3bQ7ROSsAEPZ5GDOjKfbwsz E6bWVH+PhS1azlvtTs9JezUtK0Wl9s+81FOrZtnUUskmWtECAMNNs9ujUt6GHv/J NXVaSmk1z8QXitPHbAJLDMj4xVDysJWZV95eplC+RUSiLz5HeP2AQgh1D9Rv2bA5 c7OcJ3kCANOEkA0hVpXCI0FKrsihOf0NUOaAtS6CQNFlaIkrLwssJQY8pGYbRfRa 3krNJPyOlXmezV2/CsX3EqA9KXXen5cB/iSmMJO4WndGJTe7YzUEnnY/P2TKg1fN s6v5Lf39j5Ll8V5rVDT7ApAw0IKS8fzpbdHP0HcizutlF6l44YaAXMGfhoi2BBgB CAAgFiEE1yKQQzhLzGAybG+52HINlXw9MHQFAl1oQYgCGwwACgkQ2HINlXw9MHTD HwQAv+ui718AT2hw2pK9JaNuTxjllrH+KPMlrov0P8oXHPCohC5cxM5sJ6tCQ0qH XyeWoDE8V31btqFVAQyrr0wy0gntl1L/trnwMHoP8a/xa0RHNk5C7hmcuhTHbQey JNbiRJZpCIZ1OyrF17+q6u9YBPjwqp8KrJ/0ryy2kyb7ZRM= =+tJ6 -----END PGP PRIVATE KEY BLOCK----- -----BEGIN PGP PUBLIC KEY BLOCK----- mQENBF1oQV0BCAC1iFfE7H3uu0hbWbRYVMoz5zZ91ACHETCOMVxN8GOG4SV0l8aQ wmK9QWkYxhi52LnicVD3D7Uy75+J3zkvEDQ15C0AZ8UHXp4JlSQuXpFhrOhfYUF/ 6pr/QexT+hQjOacvY4qfnj4xKa/AGdv5vPIygtQumE6r3GhEVAxQ1GSwtCWSU3Zl 3Uqf7S8kDvJTemtR2UkVfpXcMd4AmMKgt7fVhPO8eFotqTLPvz/iClzER+q61fLA d1rP9YlmY46MJp/PffPicWdJiKv2i6ynKcIwkrQyP6V2ZzYi/gAhNJst3ZlMfsiN ekCtcow9Bn44uxW3U8W02FNQSNyn6V6QPDIXABEBAAG0U1NPUFMgRnVuY3Rpb25h bCBUZXN0cyBLZXkgMSAoaHR0cHM6Ly9naXRodWIuY29tL21vemlsbGEvc29wcy8p IDxzZWNvcHNAbW96aWxsYS5jb20+iQFOBBMBCAA4FiEE+8e54qT5KJrAwdSEPRbO 5KJzgbQFAl1oQV0CGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQPRbO5KJz gbTDcQf7Bp7e2zY9pBBXTgDASQl31SSHp9WkRUV5iqPVC9iPCELggteBGMwIpbDl obc6O8/06foxWctTUaaciPBo2+jeWFTO+DNvB7oXIArqr5673QHLh6jEABBjyt91 rvta2wYF1XJBgxpui9aLICsCptFNIRvHeKUrXBI4fG5z3CDs/EOoY8K/AAYJUF+E RtmvmisiE/m20UpbYRmkBJy25c89Wcn12I1SUJA3H3hGwvZCYp8hY1HPxxQUtU+D ZBIpryi0xQqExGAlYqck7G03F+AD7/csaT1LEdCtWRLNwE8UkvfUF6liF0SgzxFo 1pp3gBU4swds9yO9wNe12JY/M5A/BLkBDQRdaEFdAQgAtun8JhSpNAKvOXwWX2nF hnMXTJp4viMhlAZEdmMXEi27B2DM/nRzldjxGZoNUBSVbJNj2kx5ZUDl0o6eOpCh vRaGuCOpYqOuSQvD8FnX0NgQULwuTZ+MawsaezktJEjDSBM1R6uASeJwDZj4hcUn PgyAIESajPdowEkEjdYt261fGOLLcVoVdtqzOMBkLVdrK/FD1kGR9jnSlKEYDV9D veBUBQGdqkgWXjS5BKcae07viC6xMa9AJS4pizyDALB2k0HQOelZNihOGXYUuvkc s2Fivl0Tk3OCfH9XDvFehbYRHmkRDoMuKUDSzdy6tFBAkL0CPlXAWI6kQklaBEp1 9QARAQABiQE2BBgBCAAgFiEE+8e54qT5KJrAwdSEPRbO5KJzgbQFAl1oQV0CGwwA CgkQPRbO5KJzgbS7zwgAndbf532OXo9HwPH+yQQmzQCLDFL6P4V7LcFrrydYItTE hxqI3tbb96MKXRAt+G5Mw6JjRkWhwzbU3jE7D7XBMHw7GriTTU9QltNHg7VUpSSa iTfVcSNErzsaqbjbA7jMs7VWzOq4LZo6Efy8UDKg5qcqLFaTQrzQZYNHNfM+kLAi UPU8m7vwmz6oJWsjHkQKUhKhHptlpwMwdHkoacqDO0x2H6H91l/PnDm4ZG6FybJt cjr98i+p52/XOo81nLgX7tcFS3nrN9HNdgKg1ZW3yrzg8NOaFCVA8qLDgLk//M3q DixOxiurECkFrMvt/bDxEGpN5GVy550MmyUZQrkuqg== =Zs2s -----END PGP PUBLIC KEY BLOCK----- -----BEGIN PGP PUBLIC KEY BLOCK----- mI0EXWhBiAEEAM+5U/ol2T8n9Ns1r11eKun/PPArXxmo2502pAY3cf7ZpKDFfAvC VF/PLusHcJToTCPOT0RVh5jO1MiQYcvQlnUIJOIEkCuUc7RsdBDsI94o+SEiGSN4 DzK711xTvuhgLbFvCB/jcpjN8wpIYTJuD6wE75sf5jqlokrnhXZy5LcbABEBAAG0 U1NPUFMgRnVuY3Rpb25hbCBUZXN0cyBLZXkgMiAoaHR0cHM6Ly9naXRodWIuY29t L21vemlsbGEvc29wcy8pIDxzZWNvcHNAbW96aWxsYS5jb20+iM4EEwEIADgWIQTX IpBDOEvMYDJsb7nYcg2VfD0wdAUCXWhBiAIbAwULCQgHAgYVCgkICwIEFgIDAQIe AQIXgAAKCRDYcg2VfD0wdEdnA/9mMGieN4hrnmgMwchZ5fplBAUCeB4R+KewSHce gfQIxN8i3vCOHaqmF8cmc2ifXfioqsSQU9JdRl7dx+TN9sgyWas1wfT01j98sfPk NQrgrOxC/24SQ9f7C3bplXO+25kLXCPTUomMj8zf9marVeUVNeC6IduFRRI7hxrz tIyN/riNBF1oQYgBBAChXi00fmpEs0Jiq0zOyYm9i749VoOsNReoB/5ix1QCimwV ZKe1D37IP5Qqysxy+LIQc4lJ+Q8foNOx1Aev5+TDyv+iU82D9xr9uPLLbA82k3AZ 04OrBjrZ/Yt1NZhuaHzciZCPpmqzF9kqVqAZc+vMiKZL1WZjS7O1FwaidY1vXwAR AQABiLYEGAEIACAWIQTXIpBDOEvMYDJsb7nYcg2VfD0wdAUCXWhBiAIbDAAKCRDY cg2VfD0wdMMfBAC/66LvXwBPaHDakr0lo25PGOWWsf4o8yWui/Q/yhcc8KiELlzE zmwnq0JDSodfJ5agMTxXfVu2oVUBDKuvTDLSCe2XUv+2ufAweg/xr/FrREc2TkLu GZy6FMdtB7Ik1uJElmkIhnU7KsXXv6rq71gE+PCqnwqsn/SvLLaTJvtlEw== =PafV -----END PGP PUBLIC KEY BLOCK----- -----BEGIN PGP PUBLIC KEY BLOCK----- mQENBF1oQaYBCADsCw223WDj2ISnkZQJ07NS7ER/ft2Tz6FPzsMqz5JmGlwQantH MzjqNGE5du1TBK+yCIuzn+P/iokmOdFjkH20OHHCEmgBQPQ2WxpR4Bc/jDvswoL2 2amknIYStf+SgCwtNPT51RS7/5brN5pVn4GjDYg+IRLk33bxz1kN3933olkMFHJF hrr1rcE1uxt3j2CEPzfKAUyviKXkSl41IAopE2e69zsVg0YaensTnE/hY45r1q0W ABXoJLg4H6UtHZWWGg7vmtQYCpiiUIaWZ3YB449Ur4jxXeIZPUqhliqOoUk8a7En 1ZC1hGLw0jaENeaHywVGi9ZqABmOVYmvItI3ABEBAAG0U1NPUFMgRnVuY3Rpb25h bCBUZXN0cyBLZXkgMyAoaHR0cHM6Ly9naXRodWIuY29tL21vemlsbGEvc29wcy8p IDxzZWNvcHNAbW96aWxsYS5jb20+iQFOBBMBCAA4FiEEthGi+fEdD/glaIBRGfm1 2uqR/4YFAl1oQaYCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQGfm12uqR /4Yc3QgA2WcMlCCUB4uyyvKq1a8ZTBli0YzIxT+KslL+PbOxfhoxglxPRClB0/l6 stG3RD0UXKq9cTe9f5nI96XJRqUzePfKtixSieirNTZswiHIYifYfg460gXYAIsV deztRGxEaMUVq6vajkA69yGbi2nExaQrPQAgcOoyEcGhsQVi5M3rbIGIJtK/K777 lAvWskMWjEL/VIQ3qHJapBwhtyK8pKiQ1U2ssvXSP15RouUU/8PdD6d82dLlLfBm eiyQCeJYQyR/geJAvhyBO+Jy+SS2RNCE6FGirACzRh3SSLpKAvieoMHqYzav8kMh zSMa5230SKpartbsgFu7t39zsc3t6LkBDQRdaEGmAQgA60kJghTuxvZaNjXCZBnj 0+z+NSnfhdPaGgdUStXDCbzsO2l/6kvlWMwwS3VuDwGjx1vLWt9KWxmL85+qLE2H FuRpH4sJFa+0HraY9SP1UxgO5ydommRKdLDw5iQIaJACMSsMAHHOQCJ9VuNE1gt8 gOb5J902gRahgIKpGrrmtI6x3Zmu1ryKrBH9Ln60kYvrU14ANPYtIvOwqCNxeU4C 9q1q4/b73RXg2Gri8alGb2HMIaeK6u7i5MlEOqkQof2FkmGg3nEw/6Cvcnf4jesJ ibKa69vC6n39ZJ0eRhmhr3tw3MP76Laayhqq8T+Ffog68A3Oe+i6+f7PyLe0oaHA uQARAQABiQE2BBgBCAAgFiEEthGi+fEdD/glaIBRGfm12uqR/4YFAl1oQaYCGwwA CgkQGfm12uqR/4b5lwgAmVCa0XYeco6Ec+Iz0CLBvNXDFH/KsP/ypWK5duzZRKeb D30cDwQHUFWH5WivGZ5nJ+Rs9zkD7a07omMTRmCsrjD4I3xDGMTU23l+gBSC5+9R B6bOi81ngH3OLaSbeh2t21PDEf57M94WFNlw2LVgMvZ6S4rs7I4FZgm75h4EGGuY It1l8SqNWcKDm9Kz/qG0lqeSGGFnQqmSBFH0Vb0hus/XErU2r3fQr1lDj0VKpOIO J0Ys9rmI6yEPTi+GhFr1bHKwZMinz5lcHnOl8xye48tsrOtHMGN17/B6hUUGzd+W TphrOfnfTO1YCkg1nEB5E2Raj/KV+ohqPvjE+KhE7Q== =gqhE -----END PGP PUBLIC KEY BLOCK----- 0707010000008C000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001300000000sops-3.7.3/publish0707010000008D000081A400000000000000000000000162794F93000004EC000000000000000000000000000000000000001A00000000sops-3.7.3/publish/gcs.gopackage publish import ( "context" "fmt" "cloud.google.com/go/storage" ) // GCSDestination represents the Google Cloud Storage destination type GCSDestination struct { gcsBucket string gcsPrefix string } // NewGCSDestination is the constructor for a Google Cloud Storage destination func NewGCSDestination(gcsBucket, gcsPrefix string) *GCSDestination { return &GCSDestination{gcsBucket, gcsPrefix} } // Path returns a the GCS path for a file within this GCS Destination func (gcsd *GCSDestination) Path(fileName string) string { return fmt.Sprintf("gcs://%s/%s%s", gcsd.gcsBucket, gcsd.gcsPrefix, fileName) } // Upload uploads contents to a file in GCS func (gcsd *GCSDestination) Upload(fileContents []byte, fileName string) error { ctx := context.Background() client, err := storage.NewClient(ctx) if err != nil { return err } wc := client.Bucket(gcsd.gcsBucket).Object(gcsd.gcsPrefix + fileName).NewWriter(ctx) defer wc.Close() _, err = wc.Write(fileContents) if err != nil { return err } return nil } // Returns NotImplementedError func (gcsd *GCSDestination) UploadUnencrypted(data map[string]interface{}, fileName string) error { return &NotImplementedError{"GCS does not support uploading the unencrypted file contents."} } 0707010000008E000081A400000000000000000000000162794F93000001E4000000000000000000000000000000000000001E00000000sops-3.7.3/publish/publish.gopackage publish import "fmt" // Destination represents actions which all destination types // must implement in order to be used by SOPS type Destination interface { Upload(fileContents []byte, fileName string) error UploadUnencrypted(data map[string]interface{}, fileName string) error Path(fileName string) string } type NotImplementedError struct { message string } func (e *NotImplementedError) Error() string { return fmt.Sprintf("NotImplementedError: %s", e.message) } 0707010000008F000081A400000000000000000000000162794F9300000557000000000000000000000000000000000000001900000000sops-3.7.3/publish/s3.gopackage publish import ( "bytes" "fmt" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" ) // S3Destination is the AWS S3 implementation of the Destination interface type S3Destination struct { s3Bucket string s3Prefix string } // NewS3Destination is the constructor for an S3 Destination func NewS3Destination(s3Bucket, s3Prefix string) *S3Destination { return &S3Destination{s3Bucket, s3Prefix} } // Path returns the S3 path of a file in an S3 Destination (bucket) func (s3d *S3Destination) Path(fileName string) string { return fmt.Sprintf("s3://%s/%s%s", s3d.s3Bucket, s3d.s3Prefix, fileName) } // Upload uploads contents to a file in an S3 Destination (bucket) func (s3d *S3Destination) Upload(fileContents []byte, fileName string) error { sess := session.Must(session.NewSession()) svc := s3.New(sess) input := &s3.PutObjectInput{ Body: aws.ReadSeekCloser(bytes.NewReader(fileContents)), Bucket: aws.String(s3d.s3Bucket), Key: aws.String(s3d.s3Prefix + fileName), } _, err := svc.PutObject(input) if err != nil { return err } return nil } // Returns NotImplementedError func (s3d *S3Destination) UploadUnencrypted(data map[string]interface{}, fileName string) error { return &NotImplementedError{"S3 does not support uploading the unencrypted file contents."} } 07070100000090000081A400000000000000000000000162794F9300000A34000000000000000000000000000000000000001C00000000sops-3.7.3/publish/vault.gopackage publish import ( "fmt" "strings" "github.com/google/go-cmp/cmp" vault "github.com/hashicorp/vault/api" "go.mozilla.org/sops/v3/logging" "github.com/sirupsen/logrus" ) var log *logrus.Logger func init() { log = logging.NewLogger("PUBLISH") } type VaultDestination struct { vaultAddress string vaultPath string kvMountName string kvVersion int } func NewVaultDestination(vaultAddress, vaultPath, kvMountName string, kvVersion int) *VaultDestination { if !strings.HasSuffix(vaultPath, "/") { vaultPath = vaultPath + "/" } if kvMountName == "" { kvMountName = "secret/" } if !strings.HasSuffix(kvMountName, "/") { kvMountName = kvMountName + "/" } if kvVersion != 1 && kvVersion != 2 { kvVersion = 2 } return &VaultDestination{vaultAddress, vaultPath, kvMountName, kvVersion} } func (vaultd *VaultDestination) getAddress() string { if vaultd.vaultAddress != "" { return vaultd.vaultAddress } return vault.DefaultConfig().Address } func (vaultd *VaultDestination) Path(fileName string) string { return fmt.Sprintf("%s/v1/%s", vaultd.getAddress(), vaultd.secretsPath(fileName)) } func (vaultd *VaultDestination) secretsPath(fileName string) string { if vaultd.kvVersion == 1 { return fmt.Sprintf("%s%s%s", vaultd.kvMountName, vaultd.vaultPath, fileName) } return fmt.Sprintf("%sdata/%s%s", vaultd.kvMountName, vaultd.vaultPath, fileName) } // Returns NotImplementedError func (vaultd *VaultDestination) Upload(fileContents []byte, fileName string) error { return &NotImplementedError{"Vault does not support uploading encrypted sops files directly."} } func (vaultd *VaultDestination) UploadUnencrypted(data map[string]interface{}, fileName string) error { client, err := vault.NewClient(nil) if err != nil { return err } if vaultd.vaultAddress != "" { err = client.SetAddress(vaultd.vaultAddress) if err != nil { return err } } secretsPath := vaultd.secretsPath(fileName) existingSecret, err := client.Logical().Read(secretsPath) if err != nil { log.Warnf("Cannot check if destination secret already exists in %s. New version will be created even if the data has not been changed.", secretsPath) } if existingSecret != nil && cmp.Equal(data, existingSecret.Data["data"]) { log.Infof("Secret in %s is already up-to-date.\n", secretsPath) return nil } secretsData := make(map[string]interface{}) if vaultd.kvVersion == 1 { secretsData = data } else if vaultd.kvVersion == 2 { secretsData["data"] = data } _, err = client.Logical().Write(secretsPath, secretsData) if err != nil { return err } return nil } 07070100000091000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001200000000sops-3.7.3/shamir07070100000092000081A400000000000000000000000162794F9300003E31000000000000000000000000000000000000001A00000000sops-3.7.3/shamir/LICENSEMozilla Public License, version 2.0 1. Definitions 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means a. that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or b. that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: a. any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or b. any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: a. under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and b. under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: a. for any code that a Contributor has removed from Covered Software; or b. for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or c. under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: a. such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and b. You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. 6. Disclaimer of Warranty Covered Software is provided under this License on an "as is" basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the Covered Software is free of defects, merchantable, fit for a particular purpose or non-infringing. The entire risk as to the quality and performance of the Covered Software is with You. Should any Covered Software prove defective in any respect, You (not any Contributor) assume the cost of any necessary servicing, repair, or correction. This disclaimer of warranty constitutes an essential part of this License. No use of any Covered Software is authorized under this License except under this disclaimer. 7. Limitation of Liability Under no circumstances and under no legal theory, whether tort (including negligence), contract, or otherwise, shall any Contributor, or anyone who distributes Covered Software as permitted above, be liable to You for any direct, indirect, special, incidental, or consequential damages of any character including, without limitation, damages for lost profits, loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses, even if such party shall have been informed of the possibility of such damages. This limitation of liability shall not apply to liability for death or personal injury resulting from such party's negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You. 8. Litigation Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. 07070100000093000081A400000000000000000000000162794F9300001685000000000000000000000000000000000000001C00000000sops-3.7.3/shamir/README.mdForked from [Vault](https://github.com/hashicorp/vault/tree/master/shamir) ## How it works We want to split a secret into parts. Any two points on the cartesian plane define a line. Three points define a parabola. Four points define a cubic curve, and so on. In general, `n` points define an function of degree `(n - 1)`. If our secret was somehow an function of degree `(n - 1)`, we could just compute `n` different points of that function and give `n` different people one point each. In order to recover the secret, then we'd need all the `n` points. If we wanted to, we could compute more than n points, but even then still only `n` points out of our whole set of computed points would be required to recover the function. A concrete example: our secret is the function `y = 2x + 1`. This function is of degree 1, so we need at least 2 points to define it. For example, let's set `x = 1`, `x = 2` and `x = 3`. From this follows that `y = 3`, `y = 5` and `y = 7`, respectively. Now, with the information that our secret is of degree 1, we can use any 2 of the 3 points we computed to recover our original function. For example, let's use the points `x = 1; y = 3` and `x = 2; y = 5`. We know that first degree functions are lines, defined by their slope and their intersection point with the y axis. We can easily compute the slope given our two points: it's the change in `y` divided by the change in `x`: `(5 - 3)/(2 - 1) = 2`. Now, knowing the slope we can compute the intersection point with the `y` axis by "working our way back". We know that at `x = 1`, `y` equals `3`, so naturally because the slope is `2`, at `x = 0`, `y` must be `1`. ## Lagrange interpolation The method we've used for this isn't very general: it only works for polynomials of degree 1. Lagrange interpolation is a more general way that lets us obtain the function of degree `(n - 1)` that passes through `n` arbitrary points. Understanding how to perform Lagrange interpolation isn't really necessary to understand Shamir's Secret Sharing: it's enough to know that there's only one function of degree `(n - 1)` that passes through `n` given points and that computing this function given the points is computationally efficient. But for those interested, here's an explanation: Let's say our points are `(x_0, y_0),...,(x_j, y_j),...,(x_(n-1), y_(n-1))`. Then, the Lagrange polynomial `L(x)`, the polynomial we're looking for, is defined as follows: `L(x) = sum from j=0 to j=(n-1) of {y_j * l_j(x)}` and `l_j(x) = product from m=0 to m=(n-1) except when m=j of {(x - x_m)/(x_j - x_m)}` A concrete example, with 3 points: ``` x_0 = 1 y_0 = 1 x_1 = 2 y_1 = 4 x_2 = 3 y_2 = 9 ``` Let's apply the formula: ``` L(x) = y_0 * l_0(x) + y_1 * l_1(x) + y_2 * l_2(x) ``` Substitute `y_j` for the actual value: ``` L(x) = 1 * l_0(x) + 4 * l_1(x) + 9 * l_2(x) ``` Replace `l_j(x)`: ``` l_0(x) = (x - 2)/(1 - 2) * (x - 3)/(1 - 3) = 0.5x^2 - 2.5x + 3 l_1(x) = (x - 1)/(2 - 1) * (x - 3)/(2 - 3) = - x^2 + 4x - 3 l_2(x) = (x - 1)/(3 - 1) * (x - 2)/(3 - 2) = 0.5x^2 - 1.5x + 1 ``` ``` L(x) = 1 * ( 0.5x^2 - 2.5x + 3) + 4 * ( -x^2 + 4x - 3) + 9 * ( 0.5x^2 - 1.5x + 1) ``` ``` L(x) = ( 0.5x^2 - 2.5x + 3) + ( -4x^2 + 16x - 12) + ( 4.5x^2 - 13.5x + 9) = x^2 + 0x + 0 = x^2 ``` So the polynomial we were looking for is `y = x^2`. ## Splitting a secret So we have the ability of splitting a function into parts, but in the context of computing we generally want to split a number, not a function. For this, let's define a function of degree `threshold`. `threshold` is the amount of parts we want to require in order to recover the secret. Let's set the parameter of degree zero to our secret `S` and make the rest of the parameters random: `y = ax^(threshold) + bx^(threshold-1) + ... + zx^1 + S` With `a, b, ...` random. Then, we want to generate our parts. For this, we evaluate our function at as many points as we want parts. For example, say our secret is 123, we want 5 parts and a threshold of 2. Because the threshold is 2, we're going to need a polynomial of degree 2: `y = ax^2 + bx + 123` We randomly set `a = 7` and `b = 1`: `y = 7x^2 + x + 123` Because we want 5 parts, we need to compute 5 points: ``` x = 0 -> y = 123 # woops! This is the secret itself. Let's not use that one. x = 1 -> y = 131 x = 2 -> y = 153 x = 3 -> y = 189 x = 4 -> y = 239 x = 5 -> y = 303 ``` And that's it. Each of the computed points is one part of the secret. ## Combining a secret Now that we have our parts, we have to define a way to recover them. Using the example from the previous section, we only need any two points out of the five we created to recover the secret, because we set the threshold to two. So with any two of the five points we created, we can recover the original polynomial, and because the secret is the free term in the polynomial, we can recover the secret. ## Finite fields In the previous examples we've only used integers, and this unfortunately has a flaw. First of all, it's impossible to uniformly sample integers to get random coefficients for our generated polynomial. Additionally, if we don't operate in a finite field, information about the secret is leaked for every part someone recovers. For these reasons, Vault's implementation of Shamir's Secret Sharing uses finite field arithmetic, specifically in GF(2^8), with 229 as the generator. GF(2^8) has 256 elements, so using this we can only split one byte at a time. This is not a problem, though, as we can just split each byte in our secret independently. This implementation uses tables to speed up the execution of finite field arithmetic. 07070100000094000081A400000000000000000000000162794F93000020C6000000000000000000000000000000000000001C00000000sops-3.7.3/shamir/shamir.gopackage shamir // Some comments in this file were written by @autrilla // The code was written by HashiCorp as part of Vault. // This implementation of Shamir's Secret Sharing matches the definition // of the scheme. Other tools used, such as GF(2^8) arithmetic, Lagrange // interpolation and Horner's method also match their definitions and should // therefore be correct. // More information about Shamir's Secret Sharing and Lagrange interpolation // can be found in README.md import ( "crypto/rand" "crypto/subtle" "fmt" mathrand "math/rand" "time" ) const ( // ShareOverhead is the byte size overhead of each share // when using Split on a secret. This is caused by appending // a one byte tag to the share. ShareOverhead = 1 ) // polynomial represents a polynomial of arbitrary degree type polynomial struct { coefficients []uint8 } // makePolynomial constructs a random polynomial of the given // degree but with the provided intercept value. func makePolynomial(intercept, degree uint8) (polynomial, error) { // Create a wrapper p := polynomial{ coefficients: make([]byte, degree+1), } // Ensure the intercept is set p.coefficients[0] = intercept // Assign random co-efficients to the polynomial if _, err := rand.Read(p.coefficients[1:]); err != nil { return p, err } return p, nil } // evaluate returns the value of the polynomial for the given x // Uses Horner's method <https://en.wikipedia.org/wiki/Horner%27s_method> to // evaluate the polynomial at point x func (p *polynomial) evaluate(x uint8) uint8 { // Special case the origin if x == 0 { return p.coefficients[0] } // Compute the polynomial value using Horner's method. degree := len(p.coefficients) - 1 out := p.coefficients[degree] for i := degree - 1; i >= 0; i-- { coeff := p.coefficients[i] out = add(mult(out, x), coeff) } return out } // interpolatePolynomial takes N sample points and returns // the value at a given x using a lagrange interpolation. // An implementation of Lagrange interpolation // <https://en.wikipedia.org/wiki/Lagrange_polynomial> // For this particular implementation, x is always 0 func interpolatePolynomial(xSamples, ySamples []uint8, x uint8) uint8 { limit := len(xSamples) var result, basis uint8 for i := 0; i < limit; i++ { basis = 1 for j := 0; j < limit; j++ { if i == j { continue } num := add(x, xSamples[j]) denom := add(xSamples[i], xSamples[j]) term := div(num, denom) basis = mult(basis, term) } group := mult(ySamples[i], basis) result = add(result, group) } return result } // div divides two numbers in GF(2^8) // GF(2^8) division using log/exp tables func div(a, b uint8) uint8 { if b == 0 { // leaks some timing information but we don't care anyways as this // should never happen, hence the panic panic("divide by zero") } var goodVal, zero uint8 logA := logTable[a] logB := logTable[b] diff := (int(logA) - int(logB)) % 255 if diff < 0 { diff += 255 } ret := expTable[diff] // Ensure we return zero if a is zero but aren't subject to timing attacks goodVal = ret if subtle.ConstantTimeByteEq(a, 0) == 1 { ret = zero } else { ret = goodVal } return ret } // mult multiplies two numbers in GF(2^8) // GF(2^8) multiplication using log/exp tables func mult(a, b uint8) (out uint8) { var goodVal, zero uint8 log_a := logTable[a] log_b := logTable[b] sum := (int(log_a) + int(log_b)) % 255 ret := expTable[sum] // Ensure we return zero if either a or b are zero but aren't subject to // timing attacks goodVal = ret if subtle.ConstantTimeByteEq(a, 0) == 1 { ret = zero } else { ret = goodVal } if subtle.ConstantTimeByteEq(b, 0) == 1 { ret = zero } else { // This operation does not do anything logically useful. It // only ensures a constant number of assignments to thwart // timing attacks. goodVal = zero } return ret } // add combines two numbers in GF(2^8) // This can also be used for subtraction since it is symmetric. func add(a, b uint8) uint8 { return a ^ b } // Split takes an arbitrarily long secret and generates a `parts` // number of shares, `threshold` of which are required to reconstruct // the secret. The parts and threshold must be at least 2, and less // than 256. The returned shares are each one byte longer than the secret // as they attach a tag used to reconstruct the secret. func Split(secret []byte, parts, threshold int) ([][]byte, error) { // Sanity check the input if parts < threshold { return nil, fmt.Errorf("parts cannot be less than threshold") } if parts > 255 { return nil, fmt.Errorf("parts cannot exceed 255") } if threshold < 2 { return nil, fmt.Errorf("threshold must be at least 2") } if threshold > 255 { return nil, fmt.Errorf("threshold cannot exceed 255") } if len(secret) == 0 { return nil, fmt.Errorf("cannot split an empty secret") } // Generate random x coordinates for computing points. I don't know // why random x coordinates are used, and I also don't know why // a non-cryptographically secure source of randomness is used. // As far as I know the x coordinates do not need to be random. mathrand.Seed(time.Now().UnixNano()) xCoordinates := mathrand.Perm(255) // Allocate the output array, initialize the final byte // of the output with the offset. The representation of each // output is {y1, y2, .., yN, x}. out := make([][]byte, parts) for idx := range out { // Store the x coordinate for each part as its last byte // Add 1 to the xCoordinate because if the x coordinate is 0, // then the result of evaluating the polynomial at that point // will be our secret out[idx] = make([]byte, len(secret)+1) out[idx][len(secret)] = uint8(xCoordinates[idx]) + 1 } // Construct a random polynomial for each byte of the secret. // Because we are using a field of size 256, we can only represent // a single byte as the intercept of the polynomial, so we must // use a new polynomial for each byte. for idx, val := range secret { // Create a random polynomial for each point. // This polynomial crosses the y axis at `val`. p, err := makePolynomial(val, uint8(threshold-1)) if err != nil { return nil, fmt.Errorf("failed to generate polynomial: %w", err) } // Generate a `parts` number of (x,y) pairs // We cheat by encoding the x value once as the final index, // so that it only needs to be stored once. for i := 0; i < parts; i++ { // Add 1 to the xCoordinate because if it's 0, // then the result of p.evaluate(x) will be our secret x := uint8(xCoordinates[i]) + 1 // Evaluate the polynomial at x y := p.evaluate(x) out[i][idx] = y } } // Return the encoded secrets return out, nil } // Combine is used to reverse a Split and reconstruct a secret // once a `threshold` number of parts are available. func Combine(parts [][]byte) ([]byte, error) { // Verify enough parts provided if len(parts) < 2 { return nil, fmt.Errorf("less than two parts cannot be used to reconstruct the secret") } // Verify the parts are all the same length firstPartLen := len(parts[0]) if firstPartLen < 2 { return nil, fmt.Errorf("parts must be at least two bytes") } for i := 1; i < len(parts); i++ { if len(parts[i]) != firstPartLen { return nil, fmt.Errorf("all parts must be the same length") } } // Create a buffer to store the reconstructed secret secret := make([]byte, firstPartLen-1) // Buffer to store the samples xSamples := make([]uint8, len(parts)) ySamples := make([]uint8, len(parts)) // Set the x value for each sample and ensure no x_sample values are the same, // otherwise div() can be unhappy // Check that we don't have any duplicate parts, that is, two or // more parts with the same x coordinate. checkMap := map[byte]bool{} for i, part := range parts { samp := part[firstPartLen-1] if exists := checkMap[samp]; exists { return nil, fmt.Errorf("duplicate part detected") } checkMap[samp] = true xSamples[i] = samp } // Reconstruct each byte for idx := range secret { // Set the y value for each sample for i, part := range parts { ySamples[i] = part[idx] } // Use Lagrange interpolation to retrieve the free term // of the original polynomial val := interpolatePolynomial(xSamples, ySamples, 0) // Evaluate the 0th value to get the intercept secret[idx] = val } return secret, nil } 07070100000095000081A400000000000000000000000162794F9300000E01000000000000000000000000000000000000002100000000sops-3.7.3/shamir/shamir_test.gopackage shamir import ( "bytes" "testing" ) func TestSplit_invalid(t *testing.T) { secret := []byte("test") if _, err := Split(secret, 0, 0); err == nil { t.Fatalf("expect error") } if _, err := Split(secret, 2, 3); err == nil { t.Fatalf("expect error") } if _, err := Split(secret, 1000, 3); err == nil { t.Fatalf("expect error") } if _, err := Split(secret, 10, 1); err == nil { t.Fatalf("expect error") } if _, err := Split(nil, 3, 2); err == nil { t.Fatalf("expect error") } } func TestSplit(t *testing.T) { secret := []byte("test") out, err := Split(secret, 5, 3) if err != nil { t.Fatalf("err: %v", err) } if len(out) != 5 { t.Fatalf("bad: %v", out) } for _, share := range out { if len(share) != len(secret)+1 { t.Fatalf("bad: %v", out) } } } func TestCombine_invalid(t *testing.T) { // Not enough parts if _, err := Combine(nil); err == nil { t.Fatalf("should err") } // Mis-match in length parts := [][]byte{ []byte("foo"), []byte("ba"), } if _, err := Combine(parts); err == nil { t.Fatalf("should err") } //Too short parts = [][]byte{ []byte("f"), []byte("b"), } if _, err := Combine(parts); err == nil { t.Fatalf("should err") } parts = [][]byte{ []byte("foo"), []byte("foo"), } if _, err := Combine(parts); err == nil { t.Fatalf("should err") } } func TestCombine(t *testing.T) { secret := []byte("test") out, err := Split(secret, 5, 3) if err != nil { t.Fatalf("err: %v", err) } // There is 5*4*3 possible choices, // we will just brute force try them all for i := 0; i < 5; i++ { for j := 0; j < 5; j++ { if j == i { continue } for k := 0; k < 5; k++ { if k == i || k == j { continue } parts := [][]byte{out[i], out[j], out[k]} recomb, err := Combine(parts) if err != nil { t.Fatalf("err: %v", err) } if !bytes.Equal(recomb, secret) { t.Errorf("parts: (i:%d, j:%d, k:%d) %v", i, j, k, parts) t.Fatalf("bad: %v %v", recomb, secret) } } } } } func TestField_Add(t *testing.T) { if out := add(16, 16); out != 0 { t.Fatalf("Bad: %v 16", out) } if out := add(3, 4); out != 7 { t.Fatalf("Bad: %v 7", out) } } func TestField_Mult(t *testing.T) { if out := mult(3, 7); out != 9 { t.Fatalf("Bad: %v 9", out) } if out := mult(3, 0); out != 0 { t.Fatalf("Bad: %v 0", out) } if out := mult(0, 3); out != 0 { t.Fatalf("Bad: %v 0", out) } } func TestField_Divide(t *testing.T) { if out := div(0, 7); out != 0 { t.Fatalf("Bad: %v 0", out) } if out := div(3, 3); out != 1 { t.Fatalf("Bad: %v 1", out) } if out := div(6, 3); out != 2 { t.Fatalf("Bad: %v 2", out) } } func TestPolynomial_Random(t *testing.T) { p, err := makePolynomial(42, 2) if err != nil { t.Fatalf("err: %v", err) } if p.coefficients[0] != 42 { t.Fatalf("bad: %v", p.coefficients) } } func TestPolynomial_Eval(t *testing.T) { p, err := makePolynomial(42, 1) if err != nil { t.Fatalf("err: %v", err) } if out := p.evaluate(0); out != 42 { t.Fatalf("bad: %v", out) } out := p.evaluate(1) exp := add(42, mult(1, p.coefficients[1])) if out != exp { t.Fatalf("bad: %v %v %v", out, exp, p.coefficients) } } func TestInterpolate_Rand(t *testing.T) { for i := 0; i < 256; i++ { p, err := makePolynomial(uint8(i), 2) if err != nil { t.Fatalf("err: %v", err) } xVals := []uint8{1, 2, 3} yVals := []uint8{p.evaluate(1), p.evaluate(2), p.evaluate(3)} out := interpolatePolynomial(xVals, yVals, 0) if out != uint8(i) { t.Fatalf("Bad: %v %d", out, i) } } } 07070100000096000081A400000000000000000000000162794F9300000DB9000000000000000000000000000000000000001C00000000sops-3.7.3/shamir/tables.gopackage shamir // Tables taken from http://www.samiam.org/galois.html // They use 0xe5 (229) as the generator var ( // logTable provides the log(X)/log(g) at each index X logTable = [256]uint8{ 0x00, 0xff, 0xc8, 0x08, 0x91, 0x10, 0xd0, 0x36, 0x5a, 0x3e, 0xd8, 0x43, 0x99, 0x77, 0xfe, 0x18, 0x23, 0x20, 0x07, 0x70, 0xa1, 0x6c, 0x0c, 0x7f, 0x62, 0x8b, 0x40, 0x46, 0xc7, 0x4b, 0xe0, 0x0e, 0xeb, 0x16, 0xe8, 0xad, 0xcf, 0xcd, 0x39, 0x53, 0x6a, 0x27, 0x35, 0x93, 0xd4, 0x4e, 0x48, 0xc3, 0x2b, 0x79, 0x54, 0x28, 0x09, 0x78, 0x0f, 0x21, 0x90, 0x87, 0x14, 0x2a, 0xa9, 0x9c, 0xd6, 0x74, 0xb4, 0x7c, 0xde, 0xed, 0xb1, 0x86, 0x76, 0xa4, 0x98, 0xe2, 0x96, 0x8f, 0x02, 0x32, 0x1c, 0xc1, 0x33, 0xee, 0xef, 0x81, 0xfd, 0x30, 0x5c, 0x13, 0x9d, 0x29, 0x17, 0xc4, 0x11, 0x44, 0x8c, 0x80, 0xf3, 0x73, 0x42, 0x1e, 0x1d, 0xb5, 0xf0, 0x12, 0xd1, 0x5b, 0x41, 0xa2, 0xd7, 0x2c, 0xe9, 0xd5, 0x59, 0xcb, 0x50, 0xa8, 0xdc, 0xfc, 0xf2, 0x56, 0x72, 0xa6, 0x65, 0x2f, 0x9f, 0x9b, 0x3d, 0xba, 0x7d, 0xc2, 0x45, 0x82, 0xa7, 0x57, 0xb6, 0xa3, 0x7a, 0x75, 0x4f, 0xae, 0x3f, 0x37, 0x6d, 0x47, 0x61, 0xbe, 0xab, 0xd3, 0x5f, 0xb0, 0x58, 0xaf, 0xca, 0x5e, 0xfa, 0x85, 0xe4, 0x4d, 0x8a, 0x05, 0xfb, 0x60, 0xb7, 0x7b, 0xb8, 0x26, 0x4a, 0x67, 0xc6, 0x1a, 0xf8, 0x69, 0x25, 0xb3, 0xdb, 0xbd, 0x66, 0xdd, 0xf1, 0xd2, 0xdf, 0x03, 0x8d, 0x34, 0xd9, 0x92, 0x0d, 0x63, 0x55, 0xaa, 0x49, 0xec, 0xbc, 0x95, 0x3c, 0x84, 0x0b, 0xf5, 0xe6, 0xe7, 0xe5, 0xac, 0x7e, 0x6e, 0xb9, 0xf9, 0xda, 0x8e, 0x9a, 0xc9, 0x24, 0xe1, 0x0a, 0x15, 0x6b, 0x3a, 0xa0, 0x51, 0xf4, 0xea, 0xb2, 0x97, 0x9e, 0x5d, 0x22, 0x88, 0x94, 0xce, 0x19, 0x01, 0x71, 0x4c, 0xa5, 0xe3, 0xc5, 0x31, 0xbb, 0xcc, 0x1f, 0x2d, 0x3b, 0x52, 0x6f, 0xf6, 0x2e, 0x89, 0xf7, 0xc0, 0x68, 0x1b, 0x64, 0x04, 0x06, 0xbf, 0x83, 0x38} // expTable provides the anti-log or exponentiation value // for the equivalent index expTable = [256]uint8{ 0x01, 0xe5, 0x4c, 0xb5, 0xfb, 0x9f, 0xfc, 0x12, 0x03, 0x34, 0xd4, 0xc4, 0x16, 0xba, 0x1f, 0x36, 0x05, 0x5c, 0x67, 0x57, 0x3a, 0xd5, 0x21, 0x5a, 0x0f, 0xe4, 0xa9, 0xf9, 0x4e, 0x64, 0x63, 0xee, 0x11, 0x37, 0xe0, 0x10, 0xd2, 0xac, 0xa5, 0x29, 0x33, 0x59, 0x3b, 0x30, 0x6d, 0xef, 0xf4, 0x7b, 0x55, 0xeb, 0x4d, 0x50, 0xb7, 0x2a, 0x07, 0x8d, 0xff, 0x26, 0xd7, 0xf0, 0xc2, 0x7e, 0x09, 0x8c, 0x1a, 0x6a, 0x62, 0x0b, 0x5d, 0x82, 0x1b, 0x8f, 0x2e, 0xbe, 0xa6, 0x1d, 0xe7, 0x9d, 0x2d, 0x8a, 0x72, 0xd9, 0xf1, 0x27, 0x32, 0xbc, 0x77, 0x85, 0x96, 0x70, 0x08, 0x69, 0x56, 0xdf, 0x99, 0x94, 0xa1, 0x90, 0x18, 0xbb, 0xfa, 0x7a, 0xb0, 0xa7, 0xf8, 0xab, 0x28, 0xd6, 0x15, 0x8e, 0xcb, 0xf2, 0x13, 0xe6, 0x78, 0x61, 0x3f, 0x89, 0x46, 0x0d, 0x35, 0x31, 0x88, 0xa3, 0x41, 0x80, 0xca, 0x17, 0x5f, 0x53, 0x83, 0xfe, 0xc3, 0x9b, 0x45, 0x39, 0xe1, 0xf5, 0x9e, 0x19, 0x5e, 0xb6, 0xcf, 0x4b, 0x38, 0x04, 0xb9, 0x2b, 0xe2, 0xc1, 0x4a, 0xdd, 0x48, 0x0c, 0xd0, 0x7d, 0x3d, 0x58, 0xde, 0x7c, 0xd8, 0x14, 0x6b, 0x87, 0x47, 0xe8, 0x79, 0x84, 0x73, 0x3c, 0xbd, 0x92, 0xc9, 0x23, 0x8b, 0x97, 0x95, 0x44, 0xdc, 0xad, 0x40, 0x65, 0x86, 0xa2, 0xa4, 0xcc, 0x7f, 0xec, 0xc0, 0xaf, 0x91, 0xfd, 0xf7, 0x4f, 0x81, 0x2f, 0x5b, 0xea, 0xa8, 0x1c, 0x02, 0xd1, 0x98, 0x71, 0xed, 0x25, 0xe3, 0x24, 0x06, 0x68, 0xb3, 0x93, 0x2c, 0x6f, 0x3e, 0x6c, 0x0a, 0xb8, 0xce, 0xae, 0x74, 0xb1, 0x42, 0xb4, 0x1e, 0xd3, 0x49, 0xe9, 0x9c, 0xc8, 0xc6, 0xc7, 0x22, 0x6e, 0xdb, 0x20, 0xbf, 0x43, 0x51, 0x52, 0x66, 0xb2, 0x76, 0x60, 0xda, 0xc5, 0xf3, 0xf6, 0xaa, 0xcd, 0x9a, 0xa0, 0x75, 0x54, 0x0e, 0x01} ) 07070100000097000081A400000000000000000000000162794F93000000E4000000000000000000000000000000000000002100000000sops-3.7.3/shamir/tables_test.gopackage shamir import "testing" func TestTables(t *testing.T) { for i := 1; i < 256; i++ { logV := logTable[i] expV := expTable[logV] if expV != uint8(i) { t.Fatalf("bad: %d log: %d exp: %d", i, logV, expV) } } } 07070100000098000081A400000000000000000000000162794F9300005E78000000000000000000000000000000000000001300000000sops-3.7.3/sops.go/* Package sops manages JSON, YAML and BINARY documents to be encrypted or decrypted. This package should not be used directly. Instead, Sops users should install the command line client via `go get -u go.mozilla.org/sops/v3/cmd/sops`, or use the decryption helper provided at `go.mozilla.org/sops/v3/decrypt`. We do not guarantee API stability for any package other than `go.mozilla.org/sops/v3/decrypt`. A Sops document is a Tree composed of a data branch with arbitrary key/value pairs and a metadata branch with encryption and integrity information. In JSON and YAML formats, the structure of the cleartext tree is preserved, keys are stored in cleartext and only values are encrypted. Keeping the values in cleartext provides better readability when storing Sops documents in version controls, and allows for merging competing changes on documents. This is a major difference between Sops and other encryption tools that store documents as encrypted blobs. In BINARY format, the cleartext data is treated as a single blob and the encrypted document is in JSON format with a single `data` key and a single encrypted value. Sops allows operators to encrypt their documents with multiple master keys. Each of the master key defined in the document is able to decrypt it, allowing users to share documents amongst themselves without sharing keys, or using a PGP key as a backup for KMS. In practice, this is achieved by generating a data key for each document that is used to encrypt all values, and encrypting the data with each master key defined. Being able to decrypt the data key gives access to the document. The integrity of each document is guaranteed by calculating a Message Authentication Code (MAC) that is stored encrypted by the data key. When decrypting a document, the MAC should be recalculated and compared with the MAC stored in the document to verify that no fraudulent changes have been applied. The MAC covers keys and values as well as their ordering. */ package sops //import "go.mozilla.org/sops/v3" import ( "crypto/rand" "crypto/sha512" "fmt" "reflect" "regexp" "strconv" "strings" "time" "github.com/sirupsen/logrus" "go.mozilla.org/sops/v3/audit" "go.mozilla.org/sops/v3/keys" "go.mozilla.org/sops/v3/keyservice" "go.mozilla.org/sops/v3/logging" "go.mozilla.org/sops/v3/shamir" "golang.org/x/net/context" ) // DefaultUnencryptedSuffix is the default suffix a TreeItem key has to end with for sops to leave its Value unencrypted const DefaultUnencryptedSuffix = "_unencrypted" type sopsError string func (e sopsError) Error() string { return string(e) } // MacMismatch occurs when the computed MAC does not match the expected ones const MacMismatch = sopsError("MAC mismatch") // MetadataNotFound occurs when the input file is malformed and doesn't have sops metadata in it const MetadataNotFound = sopsError("sops metadata not found") var log *logrus.Logger func init() { log = logging.NewLogger("SOPS") } // Cipher provides a way to encrypt and decrypt the data key used to encrypt and decrypt sops files, so that the // data key can be stored alongside the encrypted content. A Cipher must be able to decrypt the values it encrypts. type Cipher interface { // Encrypt takes a plaintext, a key and additional data and returns the plaintext encrypted with the key, using the // additional data for authentication Encrypt(plaintext interface{}, key []byte, additionalData string) (ciphertext string, err error) // Encrypt takes a ciphertext, a key and additional data and returns the ciphertext encrypted with the key, using // the additional data for authentication Decrypt(ciphertext string, key []byte, additionalData string) (plaintext interface{}, err error) } // Comment represents a comment in the sops tree for the file formats that actually support them. type Comment struct { Value string } // TreeItem is an item inside sops's tree type TreeItem struct { Key interface{} Value interface{} } // TreeBranch is a branch inside sops's tree. It is a slice of TreeItems and is therefore ordered type TreeBranch []TreeItem // TreeBranches is a collection of TreeBranch // Trees usually have more than one branch type TreeBranches []TreeBranch func valueFromPathAndLeaf(path []interface{}, leaf interface{}) interface{} { switch component := path[0].(type) { case int: if len(path) == 1 { return []interface{}{ leaf, } } return []interface{}{ valueFromPathAndLeaf(path[1:], leaf), } default: if len(path) == 1 { return TreeBranch{ TreeItem{ Key: component, Value: leaf, }, } } return TreeBranch{ TreeItem{ Key: component, Value: valueFromPathAndLeaf(path[1:], leaf), }, } } } func set(branch interface{}, path []interface{}, value interface{}) interface{} { switch branch := branch.(type) { case TreeBranch: for i, item := range branch { if item.Key == path[0] { if len(path) == 1 { branch[i].Value = value } else { branch[i].Value = set(item.Value, path[1:], value) } return branch } } // Not found, need to add the next path entry to the branch if len(path) == 1 { return append(branch, TreeItem{Key: path[0], Value: value}) } return valueFromPathAndLeaf(path, value) case []interface{}: position := path[0].(int) if len(path) == 1 { if position >= len(branch) { return append(branch, value) } branch[position] = value } else { if position >= len(branch) { branch = append(branch, valueFromPathAndLeaf(path[1:], value)) } branch[position] = set(branch[position], path[1:], value) } return branch default: return valueFromPathAndLeaf(path, value) } } // Set sets a value on a given tree for the specified path func (branch TreeBranch) Set(path []interface{}, value interface{}) TreeBranch { return set(branch, path, value).(TreeBranch) } // Tree is the data structure used by sops to represent documents internally type Tree struct { Metadata Metadata Branches TreeBranches // FilePath is the path of the file this struct represents FilePath string } // Truncate truncates the tree to the path specified func (branch TreeBranch) Truncate(path []interface{}) (interface{}, error) { log.WithField("path", path).Info("Truncating tree") var current interface{} = branch for _, component := range path { switch component := component.(type) { case string: found := false for _, item := range current.(TreeBranch) { if item.Key == component { current = item.Value found = true break } } if !found { return nil, fmt.Errorf("component ['%s'] not found", component) } case int: if reflect.ValueOf(current).Kind() != reflect.Slice { return nil, fmt.Errorf("component [%d] is integer, but tree part is not a slice", component) } if reflect.ValueOf(current).Len() <= component { return nil, fmt.Errorf("component [%d] accesses out of bounds", component) } current = reflect.ValueOf(current).Index(component).Interface() } } return current, nil } func (branch TreeBranch) walkValue(in interface{}, path []string, onLeaves func(in interface{}, path []string) (interface{}, error)) (interface{}, error) { switch in := in.(type) { case string: return onLeaves(in, path) case []byte: return onLeaves(string(in), path) case int: return onLeaves(in, path) case bool: return onLeaves(in, path) case float64: return onLeaves(in, path) case Comment: return onLeaves(in, path) case TreeBranch: return branch.walkBranch(in, path, onLeaves) case []interface{}: return branch.walkSlice(in, path, onLeaves) case nil: // the value returned remains the same since it doesn't make // sense to encrypt or decrypt a nil value return nil, nil default: return nil, fmt.Errorf("Cannot walk value, unknown type: %T", in) } } func (branch TreeBranch) walkSlice(in []interface{}, path []string, onLeaves func(in interface{}, path []string) (interface{}, error)) ([]interface{}, error) { for i, v := range in { newV, err := branch.walkValue(v, path, onLeaves) if err != nil { return nil, err } in[i] = newV } return in, nil } func (branch TreeBranch) walkBranch(in TreeBranch, path []string, onLeaves func(in interface{}, path []string) (interface{}, error)) (TreeBranch, error) { for i, item := range in { if _, ok := item.Key.(Comment); ok { enc, err := branch.walkValue(item.Key, path, onLeaves) if err != nil { return nil, err } if encComment, ok := enc.(Comment); ok { in[i].Key = encComment continue } else if comment, ok := enc.(string); ok { in[i].Key = Comment{Value: comment} continue } else { return nil, fmt.Errorf("walkValue of Comment should be either Comment or string, was %T", enc) } } key, ok := item.Key.(string) if !ok { return nil, fmt.Errorf("Tree contains a non-string key (type %T): %s. Only string keys are"+ "supported", item.Key, item.Key) } newV, err := branch.walkValue(item.Value, append(path, key), onLeaves) if err != nil { return nil, err } in[i].Value = newV } return in, nil } // Encrypt walks over the tree and encrypts all values with the provided cipher, // except those whose key ends with the UnencryptedSuffix specified on the // Metadata struct, those not ending with EncryptedSuffix, if EncryptedSuffix // is provided (by default it is not), those not matching EncryptedRegex, // if EncryptedRegex is provided (by default it is not) or those matching // UnencryptedRegex, if UnencryptedRegex is provided (by default it is not). // If encryption is successful, it returns the MAC for the encrypted tree. func (tree Tree) Encrypt(key []byte, cipher Cipher) (string, error) { audit.SubmitEvent(audit.EncryptEvent{ File: tree.FilePath, }) hash := sha512.New() walk := func(branch TreeBranch) error { _, err := branch.walkBranch(branch, make([]string, 0), func(in interface{}, path []string) (interface{}, error) { // Only add to MAC if not a comment if _, ok := in.(Comment); !ok { bytes, err := ToBytes(in) if err != nil { return nil, fmt.Errorf("Could not convert %s to bytes: %s", in, err) } hash.Write(bytes) } encrypted := true if tree.Metadata.UnencryptedSuffix != "" { for _, v := range path { if strings.HasSuffix(v, tree.Metadata.UnencryptedSuffix) { encrypted = false break } } } if tree.Metadata.EncryptedSuffix != "" { encrypted = false for _, v := range path { if strings.HasSuffix(v, tree.Metadata.EncryptedSuffix) { encrypted = true break } } } if tree.Metadata.UnencryptedRegex != "" { for _, p := range path { matched, _ := regexp.Match(tree.Metadata.UnencryptedRegex, []byte(p)) if matched { encrypted = false break } } } if tree.Metadata.EncryptedRegex != "" { encrypted = false for _, p := range path { matched, _ := regexp.Match(tree.Metadata.EncryptedRegex, []byte(p)) if matched { encrypted = true break } } } if encrypted { var err error pathString := strings.Join(path, ":") + ":" in, err = cipher.Encrypt(in, key, pathString) if err != nil { return nil, fmt.Errorf("Could not encrypt value: %s", err) } } return in, nil }) return err } for _, branch := range tree.Branches { err := walk(branch) if err != nil { return "", fmt.Errorf("Error walking tree: %s", err) } } return fmt.Sprintf("%X", hash.Sum(nil)), nil } // Decrypt walks over the tree and decrypts all values with the provided cipher, // except those whose key ends with the UnencryptedSuffix specified on the Metadata struct, // those not ending with EncryptedSuffix, if EncryptedSuffix is provided (by default it is not), // those not matching EncryptedRegex, if EncryptedRegex is provided (by default it is not), // or those matching UnencryptedRegex, if UnencryptedRegex is provided (by default it is not). // If decryption is successful, it returns the MAC for the decrypted tree. func (tree Tree) Decrypt(key []byte, cipher Cipher) (string, error) { log.Debug("Decrypting tree") audit.SubmitEvent(audit.DecryptEvent{ File: tree.FilePath, }) hash := sha512.New() walk := func(branch TreeBranch) error { _, err := branch.walkBranch(branch, make([]string, 0), func(in interface{}, path []string) (interface{}, error) { encrypted := true if tree.Metadata.UnencryptedSuffix != "" { for _, p := range path { if strings.HasSuffix(p, tree.Metadata.UnencryptedSuffix) { encrypted = false break } } } if tree.Metadata.EncryptedSuffix != "" { encrypted = false for _, p := range path { if strings.HasSuffix(p, tree.Metadata.EncryptedSuffix) { encrypted = true break } } } if tree.Metadata.UnencryptedRegex != "" { for _, p := range path { matched, _ := regexp.Match(tree.Metadata.UnencryptedRegex, []byte(p)) if matched { encrypted = false break } } } if tree.Metadata.EncryptedRegex != "" { encrypted = false for _, p := range path { matched, _ := regexp.Match(tree.Metadata.EncryptedRegex, []byte(p)) if matched { encrypted = true break } } } var v interface{} if encrypted { var err error pathString := strings.Join(path, ":") + ":" if c, ok := in.(Comment); ok { v, err = cipher.Decrypt(c.Value, key, pathString) if err != nil { // Assume the comment was not encrypted in the first place log.WithField("comment", c.Value). Warn("Found possibly unencrypted comment in file. " + "This is to be expected if the file being " + "decrypted was created with an older version of " + "SOPS.") v = c } } else { v, err = cipher.Decrypt(in.(string), key, pathString) if err != nil { return nil, fmt.Errorf("Could not decrypt value: %s", err) } } } else { v = in } // Only add to MAC if not a comment if _, ok := v.(Comment); !ok { bytes, err := ToBytes(v) if err != nil { return nil, fmt.Errorf("Could not convert %s to bytes: %s", in, err) } hash.Write(bytes) } return v, nil }) return err } for _, branch := range tree.Branches { err := walk(branch) if err != nil { return "", fmt.Errorf("Error walking tree: %s", err) } } return fmt.Sprintf("%X", hash.Sum(nil)), nil } // GenerateDataKey generates a new random data key and encrypts it with all MasterKeys. func (tree Tree) GenerateDataKey() ([]byte, []error) { newKey := make([]byte, 32) _, err := rand.Read(newKey) if err != nil { return nil, []error{fmt.Errorf("Could not generate random key: %s", err)} } return newKey, tree.Metadata.UpdateMasterKeys(newKey) } // GenerateDataKeyWithKeyServices generates a new random data key and encrypts it with all MasterKeys. func (tree *Tree) GenerateDataKeyWithKeyServices(svcs []keyservice.KeyServiceClient) ([]byte, []error) { newKey := make([]byte, 32) _, err := rand.Read(newKey) if err != nil { return nil, []error{fmt.Errorf("Could not generate random key: %s", err)} } return newKey, tree.Metadata.UpdateMasterKeysWithKeyServices(newKey, svcs) } // Metadata holds information about a file encrypted by sops type Metadata struct { LastModified time.Time UnencryptedSuffix string EncryptedSuffix string UnencryptedRegex string EncryptedRegex string MessageAuthenticationCode string Version string KeyGroups []KeyGroup // ShamirThreshold is the number of key groups required to recover the // original data key ShamirThreshold int // DataKey caches the decrypted data key so it doesn't have to be decrypted with a master key every time it's needed DataKey []byte } // KeyGroup is a slice of SOPS MasterKeys that all encrypt the same part of the data key type KeyGroup []keys.MasterKey // EncryptedFileLoader is the interface for loading of encrypted files. It provides a // way to load encrypted SOPS files into the internal SOPS representation. Because it // loads encrypted files, the returned data structure already contains all SOPS // metadata. type EncryptedFileLoader interface { LoadEncryptedFile(in []byte) (Tree, error) } // PlainFileLoader is the interface for loading of plain text files. It provides a // way to load unencrypted files into SOPS. Because the files it loads are // unencrypted, the returned data structure does not contain any metadata. type PlainFileLoader interface { LoadPlainFile(in []byte) (TreeBranches, error) } // EncryptedFileEmitter is the interface for emitting encrypting files. It provides a // way to emit encrypted files from the internal SOPS representation. type EncryptedFileEmitter interface { EmitEncryptedFile(Tree) ([]byte, error) } // PlainFileEmitter is the interface for emitting plain text files. It provides a way // to emit plain text files from the internal SOPS representation so that they can be // shown type PlainFileEmitter interface { EmitPlainFile(TreeBranches) ([]byte, error) } // ValueEmitter is the interface for emitting a value. It provides a way to emit // values from the internal SOPS representation so that they can be shown type ValueEmitter interface { EmitValue(interface{}) ([]byte, error) } // Store is used to interact with files, both encrypted and unencrypted. type Store interface { EncryptedFileLoader PlainFileLoader EncryptedFileEmitter PlainFileEmitter ValueEmitter } // MasterKeyCount returns the number of master keys available func (m *Metadata) MasterKeyCount() int { count := 0 for _, group := range m.KeyGroups { count += len(group) } return count } // UpdateMasterKeysWithKeyServices encrypts the data key with all master keys using the provided key services func (m *Metadata) UpdateMasterKeysWithKeyServices(dataKey []byte, svcs []keyservice.KeyServiceClient) (errs []error) { if len(svcs) == 0 { return []error{ fmt.Errorf("no key services provided, cannot update master keys"), } } var parts [][]byte if len(m.KeyGroups) == 1 { // If there's only one key group, we can't do Shamir. All keys // in the group encrypt the whole data key. parts = append(parts, dataKey) } else { var err error if m.ShamirThreshold == 0 { m.ShamirThreshold = len(m.KeyGroups) } log.WithFields(logrus.Fields{ "quorum": m.ShamirThreshold, "parts": len(m.KeyGroups), }).Info("Splitting data key with Shamir Secret Sharing") parts, err = shamir.Split(dataKey, len(m.KeyGroups), int(m.ShamirThreshold)) if err != nil { errs = append(errs, fmt.Errorf("could not split data key into parts for Shamir: %s", err)) return } if len(parts) != len(m.KeyGroups) { errs = append(errs, fmt.Errorf("not enough parts obtained from Shamir: need %d, got %d", len(m.KeyGroups), len(parts))) return } } for i, group := range m.KeyGroups { part := parts[i] for _, key := range group { svcKey := keyservice.KeyFromMasterKey(key) var keyErrs []error encrypted := false for _, svc := range svcs { rsp, err := svc.Encrypt(context.Background(), &keyservice.EncryptRequest{ Key: &svcKey, Plaintext: part, }) if err != nil { keyErrs = append(keyErrs, fmt.Errorf("failed to encrypt new data key with master key %q: %w", key.ToString(), err)) continue } key.SetEncryptedDataKey(rsp.Ciphertext) encrypted = true // Only need to encrypt the key successfully with one service break } if !encrypted { errs = append(errs, keyErrs...) } } } m.DataKey = dataKey return } // UpdateMasterKeys encrypts the data key with all master keys func (m *Metadata) UpdateMasterKeys(dataKey []byte) (errs []error) { return m.UpdateMasterKeysWithKeyServices(dataKey, []keyservice.KeyServiceClient{ keyservice.NewLocalClient(), }) } // GetDataKeyWithKeyServices retrieves the data key, asking KeyServices to decrypt it with each // MasterKey in the Metadata's KeySources until one of them succeeds. func (m Metadata) GetDataKeyWithKeyServices(svcs []keyservice.KeyServiceClient) ([]byte, error) { if m.DataKey != nil { return m.DataKey, nil } getDataKeyErr := getDataKeyError{ RequiredSuccessfulKeyGroups: m.ShamirThreshold, GroupResults: make([]error, len(m.KeyGroups)), } var parts [][]byte for i, group := range m.KeyGroups { part, err := decryptKeyGroup(group, svcs) if err == nil { parts = append(parts, part) } getDataKeyErr.GroupResults[i] = err } var dataKey []byte if len(m.KeyGroups) > 1 { if len(parts) < m.ShamirThreshold { return nil, &getDataKeyErr } var err error dataKey, err = shamir.Combine(parts) if err != nil { return nil, fmt.Errorf("could not get data key from shamir parts: %s", err) } } else { if len(parts) != 1 { return nil, &getDataKeyErr } dataKey = parts[0] } log.Info("Data key recovered successfully") m.DataKey = dataKey return dataKey, nil } // decryptKeyGroup tries to decrypt the contents of the provided KeyGroup with // any of the MasterKeys in the KeyGroup with any of the provided key services, // returning as soon as one key service succeeds. func decryptKeyGroup(group KeyGroup, svcs []keyservice.KeyServiceClient) ([]byte, error) { var keyErrs []error for _, key := range group { part, err := decryptKey(key, svcs) if err != nil { keyErrs = append(keyErrs, err) } else { return part, nil } } return nil, decryptKeyErrors(keyErrs) } // decryptKey tries to decrypt the contents of the provided MasterKey with any // of the key services, returning as soon as one key service succeeds. func decryptKey(key keys.MasterKey, svcs []keyservice.KeyServiceClient) ([]byte, error) { svcKey := keyservice.KeyFromMasterKey(key) var part []byte decryptErr := decryptKeyError{ keyName: key.ToString(), } for _, svc := range svcs { // All keys in a key group encrypt the same part, so as soon // as we decrypt it successfully with one key, we need to // proceed with the next group var err error if part == nil { var rsp *keyservice.DecryptResponse rsp, err = svc.Decrypt( context.Background(), &keyservice.DecryptRequest{ Ciphertext: key.EncryptedDataKey(), Key: &svcKey, }) if err == nil { part = rsp.Plaintext } } decryptErr.errs = append(decryptErr.errs, err) } if part != nil { return part, nil } return nil, &decryptErr } // GetDataKey retrieves the data key from the first MasterKey in the Metadata's KeySources that's able to return it, // using the local KeyService func (m Metadata) GetDataKey() ([]byte, error) { return m.GetDataKeyWithKeyServices([]keyservice.KeyServiceClient{ keyservice.NewLocalClient(), }) } // ToBytes converts a string, int, float or bool to a byte representation. func ToBytes(in interface{}) ([]byte, error) { switch in := in.(type) { case string: return []byte(in), nil case int: return []byte(strconv.Itoa(in)), nil case float64: return []byte(strconv.FormatFloat(in, 'f', -1, 64)), nil case bool: return []byte(strings.Title(strconv.FormatBool(in))), nil case []byte: return in, nil case Comment: return ToBytes(in.Value) default: return nil, fmt.Errorf("Could not convert unknown type %T to bytes", in) } } // EmitAsMap will emit the tree branches as a map. This is used by the publish // command for writing decrypted trees to various destinations. Should only be // used for outputting to data structures in code. func EmitAsMap(in TreeBranches) (map[string]interface{}, error) { data := map[string]interface{}{} for _, branch := range in { for _, item := range branch { if _, ok := item.Key.(Comment); ok { continue } val, err := encodeValueForMap(item.Value) if err != nil { return nil, err } data[item.Key.(string)] = val } } return data, nil } func encodeValueForMap(v interface{}) (interface{}, error) { switch v := v.(type) { case TreeBranch: return EmitAsMap([]TreeBranch{v}) default: return v, nil } } 07070100000099000081A400000000000000000000000162794F930000377F000000000000000000000000000000000000001800000000sops-3.7.3/sops_test.gopackage sops import ( "bytes" "fmt" "reflect" "testing" "github.com/stretchr/testify/assert" ) type reverseCipher struct{} // reverse returns its argument string reversed rune-wise left to right. func reverse(s string) string { r := []rune(s) for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { r[i], r[j] = r[j], r[i] } return string(r) } func (c reverseCipher) Encrypt(value interface{}, key []byte, path string) (string, error) { b, err := ToBytes(value) if err != nil { return "", err } return reverse(string(b)), nil } func (c reverseCipher) Decrypt(value string, key []byte, path string) (plaintext interface{}, err error) { if value == "error" { return nil, fmt.Errorf("Error") } return reverse(value), nil } func TestUnencryptedSuffix(t *testing.T) { branches := TreeBranches{ TreeBranch{ TreeItem{ Key: "foo_unencrypted", Value: "bar", }, TreeItem{ Key: "bar_unencrypted", Value: TreeBranch{ TreeItem{ Key: "foo", Value: "bar", }, }, }, }, } tree := Tree{Branches: branches, Metadata: Metadata{UnencryptedSuffix: "_unencrypted"}} expected := TreeBranch{ TreeItem{ Key: "foo_unencrypted", Value: "bar", }, TreeItem{ Key: "bar_unencrypted", Value: TreeBranch{ TreeItem{ Key: "foo", Value: "bar", }, }, }, } cipher := reverseCipher{} _, err := tree.Encrypt(bytes.Repeat([]byte("f"), 32), cipher) if err != nil { t.Errorf("Encrypting the tree failed: %s", err) } if !reflect.DeepEqual(tree.Branches[0], expected) { t.Errorf("Trees don't match: \ngot \t\t%+v,\n expected \t\t%+v", tree.Branches[0], expected) } _, err = tree.Decrypt(bytes.Repeat([]byte("f"), 32), cipher) if err != nil { t.Errorf("Decrypting the tree failed: %s", err) } if !reflect.DeepEqual(tree.Branches[0], expected) { t.Errorf("Trees don't match: \ngot\t\t\t%+v,\nexpected\t\t%+v", tree.Branches[0], expected) } } func TestEncryptedSuffix(t *testing.T) { branches := TreeBranches{ TreeBranch{ TreeItem{ Key: "foo_encrypted", Value: "bar", }, TreeItem{ Key: "bar", Value: TreeBranch{ TreeItem{ Key: "foo", Value: "bar", }, }, }, }, } tree := Tree{Branches: branches, Metadata: Metadata{EncryptedSuffix: "_encrypted"}} expected := TreeBranch{ TreeItem{ Key: "foo_encrypted", Value: "rab", }, TreeItem{ Key: "bar", Value: TreeBranch{ TreeItem{ Key: "foo", Value: "bar", }, }, }, } cipher := reverseCipher{} _, err := tree.Encrypt(bytes.Repeat([]byte("f"), 32), cipher) if err != nil { t.Errorf("Encrypting the tree failed: %s", err) } if !reflect.DeepEqual(tree.Branches[0], expected) { t.Errorf("Trees don't match: \ngot \t\t%+v,\n expected \t\t%+v", tree.Branches[0], expected) } _, err = tree.Decrypt(bytes.Repeat([]byte("f"), 32), cipher) if err != nil { t.Errorf("Decrypting the tree failed: %s", err) } expected[0].Value = "bar" if !reflect.DeepEqual(tree.Branches[0], expected) { t.Errorf("Trees don't match: \ngot\t\t\t%+v,\nexpected\t\t%+v", tree.Branches[0], expected) } } func TestEncryptedRegex(t *testing.T) { branches := TreeBranches{ TreeBranch{ TreeItem{ Key: "enc:foo", Value: "bar", }, TreeItem{ Key: "bar", Value: TreeBranch{ TreeItem{ Key: "foo", Value: "bar", }, }, }, }, } tree := Tree{Branches: branches, Metadata: Metadata{EncryptedRegex: "^enc:"}} expected := TreeBranch{ TreeItem{ Key: "enc:foo", Value: "rab", }, TreeItem{ Key: "bar", Value: TreeBranch{ TreeItem{ Key: "foo", Value: "bar", }, }, }, } cipher := reverseCipher{} _, err := tree.Encrypt(bytes.Repeat([]byte("f"), 32), cipher) if err != nil { t.Errorf("Encrypting the tree failed: %s", err) } if !reflect.DeepEqual(tree.Branches[0], expected) { t.Errorf("Trees don't match: \ngot \t\t%+v,\n expected \t\t%+v", tree.Branches[0], expected) } _, err = tree.Decrypt(bytes.Repeat([]byte("f"), 32), cipher) if err != nil { t.Errorf("Decrypting the tree failed: %s", err) } expected[0].Value = "bar" if !reflect.DeepEqual(tree.Branches[0], expected) { t.Errorf("Trees don't match: \ngot\t\t\t%+v,\nexpected\t\t%+v", tree.Branches[0], expected) } } func TestUnencryptedRegex(t *testing.T) { branches := TreeBranches{ TreeBranch{ TreeItem{ Key: "dec:foo", Value: "bar", }, TreeItem{ Key: "dec:bar", Value: TreeBranch{ TreeItem{ Key: "foo", Value: "bar", }, }, }, }, } tree := Tree{Branches: branches, Metadata: Metadata{UnencryptedRegex: "^dec:"}} expected := TreeBranch{ TreeItem{ Key: "dec:foo", Value: "bar", }, TreeItem{ Key: "dec:bar", Value: TreeBranch{ TreeItem{ Key: "foo", Value: "bar", }, }, }, } cipher := reverseCipher{} _, err := tree.Encrypt(bytes.Repeat([]byte("f"), 32), cipher) if err != nil { t.Errorf("Encrypting the tree failed: %s", err) } // expected[1].Value[] = "bar" if !reflect.DeepEqual(tree.Branches[0], expected) { t.Errorf("Trees don't match: \ngot \t\t%+v,\n expected \t\t%+v", tree.Branches[0], expected) } _, err = tree.Decrypt(bytes.Repeat([]byte("f"), 32), cipher) if err != nil { t.Errorf("Decrypting the tree failed: %s", err) } if !reflect.DeepEqual(tree.Branches[0], expected) { t.Errorf("Trees don't match: \ngot\t\t\t%+v,\nexpected\t\t%+v", tree.Branches[0], expected) } } type MockCipher struct{} func (m MockCipher) Encrypt(value interface{}, key []byte, path string) (string, error) { return "a", nil } func (m MockCipher) Decrypt(value string, key []byte, path string) (interface{}, error) { return "a", nil } func TestEncrypt(t *testing.T) { branches := TreeBranches{ TreeBranch{ TreeItem{ Key: "foo", Value: "bar", }, TreeItem{ Key: "baz", Value: TreeBranch{ TreeItem{ Key: "bar", Value: 5, }, }, }, TreeItem{ Key: "bar", Value: false, }, TreeItem{ Key: "foobar", Value: 2.12, }, TreeItem{ Key: "barfoo", Value: nil, }, }, TreeBranch{ TreeItem{ Key: "foo2", Value: "bar", }, }, TreeBranch{ TreeItem{ Key: "foo3", Value: "bar", }, }, } expected := TreeBranches{ TreeBranch{ TreeItem{ Key: "foo", Value: "a", }, TreeItem{ Key: "baz", Value: TreeBranch{ TreeItem{ Key: "bar", Value: "a", }, }, }, TreeItem{ Key: "bar", Value: "a", }, TreeItem{ Key: "foobar", Value: "a", }, TreeItem{ Key: "barfoo", Value: nil, }, }, TreeBranch{ TreeItem{ Key: "foo2", Value: "a", }, }, TreeBranch{ TreeItem{ Key: "foo3", Value: "a", }, }, } tree := Tree{Branches: branches, Metadata: Metadata{UnencryptedSuffix: DefaultUnencryptedSuffix}} tree.Encrypt(bytes.Repeat([]byte{'f'}, 32), MockCipher{}) if !reflect.DeepEqual(tree.Branches, expected) { t.Errorf("%s does not equal expected tree: %s", tree.Branches, expected) } } func TestDecrypt(t *testing.T) { branches := TreeBranches{ TreeBranch{ TreeItem{ Key: "foo", Value: "bar", }, TreeItem{ Key: "baz", Value: TreeBranch{ TreeItem{ Key: "bar", Value: "5", }, }, }, TreeItem{ Key: "bar", Value: "false", }, TreeItem{ Key: "foobar", Value: "2.12", }, TreeItem{ Key: "barfoo", Value: nil, }, }, TreeBranch{ TreeItem{ Key: "foo", Value: "bar", }, TreeItem{ Key: "baz", Value: TreeBranch{ TreeItem{ Key: "bar", Value: "6", }, }, }, }, TreeBranch{ TreeItem{ Key: "foo3", Value: "bar", }, }, } expected := TreeBranches{ TreeBranch{ TreeItem{ Key: "foo", Value: "a", }, TreeItem{ Key: "baz", Value: TreeBranch{ TreeItem{ Key: "bar", Value: "a", }, }, }, TreeItem{ Key: "bar", Value: "a", }, TreeItem{ Key: "foobar", Value: "a", }, TreeItem{ Key: "barfoo", Value: nil, }, }, TreeBranch{ TreeItem{ Key: "foo", Value: "a", }, TreeItem{ Key: "baz", Value: TreeBranch{ TreeItem{ Key: "bar", Value: "a", }, }, }, }, TreeBranch{ TreeItem{ Key: "foo3", Value: "a", }, }, } tree := Tree{Branches: branches, Metadata: Metadata{UnencryptedSuffix: DefaultUnencryptedSuffix}} tree.Decrypt(bytes.Repeat([]byte{'f'}, 32), MockCipher{}) if !reflect.DeepEqual(tree.Branches, expected) { t.Errorf("%s does not equal expected tree: %s", tree.Branches[0], expected) } } func TestTruncateTree(t *testing.T) { tree := TreeBranch{ TreeItem{ Key: "foo", Value: 2, }, TreeItem{ Key: "bar", Value: TreeBranch{ TreeItem{ Key: "foobar", Value: []int{ 1, 2, 3, 4, }, }, }, }, } expected := 3 result, err := tree.Truncate([]interface{}{ "bar", "foobar", 2, }) assert.Equal(t, nil, err) assert.Equal(t, expected, result) } func TestEncryptComments(t *testing.T) { tree := Tree{ Branches: TreeBranches{ TreeBranch{ TreeItem{ Key: Comment{"foo"}, Value: nil, }, TreeItem{ Key: "list", Value: []interface{}{ "1", Comment{"bar"}, "2", }, }, }, }, Metadata: Metadata{ UnencryptedSuffix: DefaultUnencryptedSuffix, }, } tree.Encrypt(bytes.Repeat([]byte{'f'}, 32), reverseCipher{}) assert.Equal(t, "oof", tree.Branches[0][0].Key.(Comment).Value) assert.Equal(t, "rab", tree.Branches[0][1].Value.([]interface{})[1]) } func TestDecryptComments(t *testing.T) { tree := Tree{ Branches: TreeBranches{ TreeBranch{ TreeItem{ Key: Comment{"oof"}, Value: nil, }, TreeItem{ Key: "list", Value: []interface{}{ "1", Comment{"rab"}, "2", }, }, TreeItem{ Key: "list", Value: nil, }, }, }, Metadata: Metadata{ UnencryptedSuffix: DefaultUnencryptedSuffix, }, } tree.Decrypt(bytes.Repeat([]byte{'f'}, 32), reverseCipher{}) assert.Equal(t, "foo", tree.Branches[0][0].Key.(Comment).Value) assert.Equal(t, "bar", tree.Branches[0][1].Value.([]interface{})[1]) } func TestDecryptUnencryptedComments(t *testing.T) { tree := Tree{ Branches: TreeBranches{ TreeBranch{ TreeItem{ // We use `error` to simulate an error decrypting, the fake cipher will error in this case Key: Comment{"error"}, Value: nil, }, }, }, Metadata: Metadata{}, } tree.Decrypt(bytes.Repeat([]byte{'f'}, 32), reverseCipher{}) assert.Equal(t, "error", tree.Branches[0][0].Key.(Comment).Value) } func TestSetNewKey(t *testing.T) { branch := TreeBranch{ TreeItem{ Key: "foo", Value: TreeBranch{ TreeItem{ Key: "bar", Value: TreeBranch{ TreeItem{ Key: "baz", Value: "foobar", }, }, }, }, }, } set := branch.Set([]interface{}{"foo", "bar", "foo"}, "hello") assert.Equal(t, "hello", set[0].Value.(TreeBranch)[0].Value.(TreeBranch)[1].Value) } func TestSetArrayDeepNew(t *testing.T) { branch := TreeBranch{ TreeItem{ Key: "foo", Value: []interface{}{ "one", "two", }, }, } set := branch.Set([]interface{}{"foo", 2, "bar"}, "hello") assert.Equal(t, "hello", set[0].Value.([]interface{})[2].(TreeBranch)[0].Value) } func TestSetNewKeyDeep(t *testing.T) { branch := TreeBranch{ TreeItem{ Key: "foo", Value: "bar", }, } set := branch.Set([]interface{}{"foo", "bar", "baz"}, "hello") assert.Equal(t, "hello", set[0].Value.(TreeBranch)[0].Value.(TreeBranch)[0].Value) } func TestSetNewKeyOnEmptyBranch(t *testing.T) { branch := TreeBranch{} set := branch.Set([]interface{}{"foo", "bar", "baz"}, "hello") assert.Equal(t, "hello", set[0].Value.(TreeBranch)[0].Value.(TreeBranch)[0].Value) } func TestSetArray(t *testing.T) { branch := TreeBranch{ TreeItem{ Key: "foo", Value: []interface{}{ "one", "two", "three", }, }, } set := branch.Set([]interface{}{"foo", 0}, "uno") assert.Equal(t, "uno", set[0].Value.([]interface{})[0]) } func TestSetArrayNew(t *testing.T) { branch := TreeBranch{} set := branch.Set([]interface{}{"foo", 0, 0}, "uno") assert.Equal(t, "uno", set[0].Value.([]interface{})[0].([]interface{})[0]) } func TestSetExisting(t *testing.T) { branch := TreeBranch{ TreeItem{ Key: "foo", Value: "foobar", }, } set := branch.Set([]interface{}{"foo"}, "bar") assert.Equal(t, "bar", set[0].Value) } func TestSetArrayLeafNewItem(t *testing.T) { branch := TreeBranch{ TreeItem{ Key: "array", Value: []interface{}{}, }, } set := branch.Set([]interface{}{"array", 2}, "hello") assert.Equal(t, TreeBranch{ TreeItem{ Key: "array", Value: []interface{}{ "hello", }, }, }, set) } func TestSetArrayNonLeaf(t *testing.T) { branch := TreeBranch{ TreeItem{ Key: "array", Value: []interface{}{ 1, }, }, } set := branch.Set([]interface{}{"array", 0, "hello"}, "hello") assert.Equal(t, TreeBranch{ TreeItem{ Key: "array", Value: []interface{}{ TreeBranch{ TreeItem{ Key: "hello", Value: "hello", }, }, }, }, }, set) } func TestEmitAsMap(t *testing.T) { expected := map[string]interface{}{ "foobar": "barfoo", "number": 42, "foo": map[string]interface{}{ "bar": map[string]interface{}{ "baz": "foobar", }, }, } branches := TreeBranches{ TreeBranch{ TreeItem{ Key: "foobar", Value: "barfoo", }, TreeItem{ Key: "number", Value: 42, }, }, TreeBranch{ TreeItem{ Key: "foo", Value: TreeBranch{ TreeItem{ Key: "bar", Value: TreeBranch{ TreeItem{ Key: "baz", Value: "foobar", }, }, }, }, }, }, } data, err := EmitAsMap(branches) if assert.NoError(t, err) { assert.Equal(t, expected, data) } } 0707010000009A000041ED00000000000000000000000662794F9300000000000000000000000000000000000000000000001200000000sops-3.7.3/stores0707010000009B000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001900000000sops-3.7.3/stores/dotenv0707010000009C000081A400000000000000000000000162794F9300001278000000000000000000000000000000000000002200000000sops-3.7.3/stores/dotenv/store.gopackage dotenv //import "go.mozilla.org/sops/v3/stores/dotenv" import ( "bytes" "encoding/json" "fmt" "strings" "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/stores" ) // SopsPrefix is the prefix for all metadatada entry keys const SopsPrefix = "sops_" // Store handles storage of dotenv data type Store struct { } // LoadEncryptedFile loads an encrypted file's bytes onto a sops.Tree runtime object func (store *Store) LoadEncryptedFile(in []byte) (sops.Tree, error) { branches, err := store.LoadPlainFile(in) if err != nil { return sops.Tree{}, err } var resultBranch sops.TreeBranch mdMap := make(map[string]interface{}) for _, item := range branches[0] { switch key := item.Key.(type) { case string: if strings.HasPrefix(key, SopsPrefix) { key = key[len(SopsPrefix):] mdMap[key] = item.Value } else { resultBranch = append(resultBranch, item) } case sops.Comment: resultBranch = append(resultBranch, item) default: panic(fmt.Sprintf("Unexpected type: %T (value %#v)", key, key)) } } metadata, err := mapToMetadata(mdMap) if err != nil { return sops.Tree{}, err } internalMetadata, err := metadata.ToInternal() if err != nil { return sops.Tree{}, err } return sops.Tree{ Branches: sops.TreeBranches{ resultBranch, }, Metadata: internalMetadata, }, nil } // LoadPlainFile returns the contents of a plaintext file loaded onto a // sops runtime object func (store *Store) LoadPlainFile(in []byte) (sops.TreeBranches, error) { var branches sops.TreeBranches var branch sops.TreeBranch for _, line := range bytes.Split(in, []byte("\n")) { if len(line) == 0 { continue } if line[0] == '#' { branch = append(branch, sops.TreeItem{ Key: sops.Comment{string(line[1:])}, Value: nil, }) } else { pos := bytes.Index(line, []byte("=")) if pos == -1 { return nil, fmt.Errorf("invalid dotenv input line: %s", line) } branch = append(branch, sops.TreeItem{ Key: string(line[:pos]), Value: strings.Replace(string(line[pos+1:]), "\\n", "\n", -1), }) } } branches = append(branches, branch) return branches, nil } // EmitEncryptedFile returns the encrypted file's bytes corresponding to a sops // runtime object func (store *Store) EmitEncryptedFile(in sops.Tree) ([]byte, error) { metadata := stores.MetadataFromInternal(in.Metadata) mdItems, err := metadataToMap(metadata) if err != nil { return nil, err } for key, value := range mdItems { if value == nil { continue } in.Branches[0] = append(in.Branches[0], sops.TreeItem{Key: SopsPrefix + key, Value: value}) } return store.EmitPlainFile(in.Branches) } // EmitPlainFile returns the plaintext file's bytes corresponding to a sops // runtime object func (store *Store) EmitPlainFile(in sops.TreeBranches) ([]byte, error) { buffer := bytes.Buffer{} for _, item := range in[0] { if isComplexValue(item.Value) { return nil, fmt.Errorf("cannot use complex value in dotenv file: %s", item.Value) } var line string if comment, ok := item.Key.(sops.Comment); ok { line = fmt.Sprintf("#%s\n", comment.Value) } else { value := strings.Replace(item.Value.(string), "\n", "\\n", -1) line = fmt.Sprintf("%s=%s\n", item.Key, value) } buffer.WriteString(line) } return buffer.Bytes(), nil } // EmitValue returns a single value as bytes func (Store) EmitValue(v interface{}) ([]byte, error) { if s, ok := v.(string); ok { return []byte(s), nil } return nil, fmt.Errorf("the dotenv store only supports emitting strings, got %T", v) } // EmitExample returns the bytes corresponding to an example Flat Tree runtime object func (store *Store) EmitExample() []byte { bytes, err := store.EmitPlainFile(stores.ExampleFlatTree.Branches) if err != nil { panic(err) } return bytes } func metadataToMap(md stores.Metadata) (map[string]interface{}, error) { var mdMap map[string]interface{} inrec, err := json.Marshal(md) if err != nil { return nil, err } err = json.Unmarshal(inrec, &mdMap) if err != nil { return nil, err } flat := stores.Flatten(mdMap) for k, v := range flat { if s, ok := v.(string); ok { flat[k] = strings.Replace(s, "\n", "\\n", -1) } } return flat, nil } func mapToMetadata(m map[string]interface{}) (stores.Metadata, error) { for k, v := range m { if s, ok := v.(string); ok { m[k] = strings.Replace(s, "\\n", "\n", -1) } } m = stores.Unflatten(m) var md stores.Metadata inrec, err := json.Marshal(m) if err != nil { return md, err } err = json.Unmarshal(inrec, &md) return md, err } func isComplexValue(v interface{}) bool { switch v.(type) { case []interface{}: return true case sops.TreeBranch: return true } return false } 0707010000009D000081A400000000000000000000000162794F930000048A000000000000000000000000000000000000002700000000sops-3.7.3/stores/dotenv/store_test.gopackage dotenv import ( "strings" "testing" "github.com/stretchr/testify/assert" "go.mozilla.org/sops/v3" ) var PLAIN = []byte(strings.TrimLeft(` VAR1=val1 VAR2=val2 #comment VAR3_unencrypted=val3 VAR4=val4\nval4 `, "\n")) var BRANCH = sops.TreeBranch{ sops.TreeItem{ Key: "VAR1", Value: "val1", }, sops.TreeItem{ Key: "VAR2", Value: "val2", }, sops.TreeItem{ Key: sops.Comment{"comment"}, Value: nil, }, sops.TreeItem{ Key: "VAR3_unencrypted", Value: "val3", }, sops.TreeItem{ Key: "VAR4", Value: "val4\nval4", }, } func TestLoadPlainFile(t *testing.T) { branches, err := (&Store{}).LoadPlainFile(PLAIN) assert.Nil(t, err) assert.Equal(t, BRANCH, branches[0]) } func TestEmitPlainFile(t *testing.T) { branches := sops.TreeBranches{ BRANCH, } bytes, err := (&Store{}).EmitPlainFile(branches) assert.Nil(t, err) assert.Equal(t, PLAIN, bytes) } func TestEmitValueString(t *testing.T) { bytes, err := (&Store{}).EmitValue("hello") assert.Nil(t, err) assert.Equal(t, []byte("hello"), bytes) } func TestEmitValueNonstring(t *testing.T) { _, err := (&Store{}).EmitValue(BRANCH) assert.NotNil(t, err) } 0707010000009E000081A400000000000000000000000162794F93000013C4000000000000000000000000000000000000001D00000000sops-3.7.3/stores/flatten.gopackage stores import ( "fmt" "strconv" "strings" ) const mapSeparator = "__map_" const listSeparator = "__list_" // flattenAndMerge flattens the provided value and merges into the // into map using prefix func flattenAndMerge(into map[string]interface{}, prefix string, value interface{}) { flattenedValue := flattenValue(value) if flattenedValue, ok := flattenedValue.(map[string]interface{}); ok { for flatK, flatV := range flattenedValue { into[prefix+flatK] = flatV } } else { into[prefix] = value } } func flattenValue(value interface{}) interface{} { var output interface{} switch value := value.(type) { case map[string]interface{}: newMap := make(map[string]interface{}) for k, v := range value { flattenAndMerge(newMap, mapSeparator+k, v) } output = newMap case []interface{}: newMap := make(map[string]interface{}) for i, v := range value { flattenAndMerge(newMap, listSeparator+fmt.Sprintf("%d", i), v) } output = newMap default: output = value } return output } // Flatten flattens a map with potentially nested maps into a flat // map. Only string keys are allowed on both the top-level map and // child maps. func Flatten(in map[string]interface{}) map[string]interface{} { newMap := make(map[string]interface{}) for k, v := range in { if flat, ok := flattenValue(v).(map[string]interface{}); ok { for flatK, flatV := range flat { newMap[k+flatK] = flatV } } else { newMap[k] = v } } return newMap } type token interface{} type mapToken struct { key string } type listToken struct { position int } // tokenize converts a path generated by Flatten to be used as a key // in the flattened map, and converts it to a slice of tokens func tokenize(path string) []token { const ( StateNormal = 0 StateMap = iota StateList = iota ) var tokens []token state := StateNormal lastTokenEnd := 0 i := 0 finishPrevToken := func() { var t token switch state { case StateNormal: t = mapToken{path[lastTokenEnd:i]} case StateMap: t = mapToken{path[lastTokenEnd+len(mapSeparator) : i]} case StateList: pos, _ := strconv.Atoi(path[lastTokenEnd+len(listSeparator) : i]) t = listToken{pos} } lastTokenEnd = i tokens = append(tokens, t) } for i < len(path) { if strings.HasPrefix(path[i:], mapSeparator) { finishPrevToken() state = StateMap i += len(mapSeparator) } else if strings.HasPrefix(path[i:], listSeparator) { finishPrevToken() state = StateList i += len(listSeparator) } else { i++ } } finishPrevToken() return tokens } // unflatten takes the currentNode, currentToken, nextToken and value // and populates currentNode such that currentToken can be considered // processed. It inspects nextToken to decide what type to allocate // and assign under currentNode. func unflatten(currentNode interface{}, currentToken, nextToken token, value interface{}) interface{} { switch currentToken := currentToken.(type) { case mapToken: currentNode := currentNode.(map[string]interface{}) switch nextToken := nextToken.(type) { case mapToken: if _, ok := currentNode[currentToken.key]; !ok { currentNode[currentToken.key] = make(map[string]interface{}) } next := currentNode[currentToken.key].(map[string]interface{}) return next case listToken: if _, ok := currentNode[currentToken.key]; !ok { currentNode[currentToken.key] = make([]interface{}, nextToken.position+1) } next := currentNode[currentToken.key].([]interface{}) if nextToken.position >= len(next) { // Grow the slice and reassign it newNext := make([]interface{}, nextToken.position+1) copy(newNext, next) next = newNext currentNode[currentToken.key] = next } return next default: currentNode[currentToken.key] = value } case listToken: currentNode := currentNode.([]interface{}) switch nextToken := nextToken.(type) { case mapToken: if currentNode[currentToken.position] == nil { currentNode[currentToken.position] = make(map[string]interface{}) } next := currentNode[currentToken.position].(map[string]interface{}) return next case listToken: if currentNode[currentToken.position] == nil { currentNode[currentToken.position] = make([]interface{}, nextToken.position+1) } next := currentNode[currentToken.position].([]interface{}) if nextToken.position >= len(next) { // Grow the slice and reassign it newNext := make([]interface{}, nextToken.position+1) copy(newNext, next) next = newNext currentNode[currentToken.position] = next } return next default: currentNode[currentToken.position] = value } } return nil } // Unflatten unflattens a map flattened by Flatten func Unflatten(in map[string]interface{}) map[string]interface{} { newMap := make(map[string]interface{}) for k, v := range in { var current interface{} = newMap tokens := append(tokenize(k), nil) for i := 0; i < len(tokens)-1; i++ { current = unflatten(current, tokens[i], tokens[i+1], v) } } return newMap } 0707010000009F000081A400000000000000000000000162794F9300000D66000000000000000000000000000000000000002200000000sops-3.7.3/stores/flatten_test.gopackage stores import ( "testing" "github.com/stretchr/testify/assert" ) func TestFlat(t *testing.T) { input := map[string]interface{}{ "foo": "bar", } expected := map[string]interface{}{ "foo": "bar", } flattened := Flatten(input) assert.Equal(t, expected, flattened) unflattened := Unflatten(flattened) assert.Equal(t, input, unflattened) } func TestMap(t *testing.T) { input := map[string]interface{}{ "foo": map[string]interface{}{ "bar": 0, "baz": 0, }, } expected := map[string]interface{}{ "foo" + mapSeparator + "bar": 0, "foo" + mapSeparator + "baz": 0, } flattened := Flatten(input) assert.Equal(t, expected, flattened) unflattened := Unflatten(flattened) assert.Equal(t, input, unflattened) } func TestFlattenMapMoreNesting(t *testing.T) { input := map[string]interface{}{ "foo": map[string]interface{}{ "bar": map[string]interface{}{ "baz": 0, }, }, } expected := map[string]interface{}{ "foo" + mapSeparator + "bar" + mapSeparator + "baz": 0, } flattened := Flatten(input) assert.Equal(t, expected, flattened) unflattened := Unflatten(flattened) assert.Equal(t, input, unflattened) } func TestFlattenList(t *testing.T) { input := map[string]interface{}{ "foo": []interface{}{ 0, }, } expected := map[string]interface{}{ "foo" + listSeparator + "0": 0, } flattened := Flatten(input) assert.Equal(t, expected, flattened) unflattened := Unflatten(flattened) assert.Equal(t, input, unflattened) } func TestFlattenListWithMap(t *testing.T) { input := map[string]interface{}{ "foo": []interface{}{ map[string]interface{}{ "bar": 0, }, }, } expected := map[string]interface{}{ "foo" + listSeparator + "0" + mapSeparator + "bar": 0, } flattened := Flatten(input) assert.Equal(t, expected, flattened) unflattened := Unflatten(flattened) assert.Equal(t, input, unflattened) } func TestFlatten(t *testing.T) { input := map[string]interface{}{ "foo": "bar", "baz": map[string]interface{}{ "foo": 2, "bar": map[string]interface{}{ "foo": 2, }, }, "qux": []interface{}{ "hello", 1, 2, }, } expected := map[string]interface{}{ "foo": "bar", "baz" + mapSeparator + "foo": 2, "baz" + mapSeparator + "bar" + mapSeparator + "foo": 2, "qux" + listSeparator + "0": "hello", "qux" + listSeparator + "1": 1, "qux" + listSeparator + "2": 2, } flattened := Flatten(input) assert.Equal(t, expected, flattened) unflattened := Unflatten(flattened) assert.Equal(t, input, unflattened) } func TestTokenizeFlat(t *testing.T) { input := "bar" expected := []token{mapToken{"bar"}} tokenized := tokenize(input) assert.Equal(t, expected, tokenized) } func TestTokenizeMap(t *testing.T) { input := "bar" + mapSeparator + "foo" expected := []token{mapToken{"bar"}, mapToken{"foo"}} tokenized := tokenize(input) assert.Equal(t, expected, tokenized) } func TestTokenizeList(t *testing.T) { input := "bar" + listSeparator + "10" expected := []token{mapToken{"bar"}, listToken{10}} tokenized := tokenize(input) assert.Equal(t, expected, tokenized) } func TestTokenizeNested(t *testing.T) { input := "bar" + listSeparator + "10" + mapSeparator + "baz" expected := []token{mapToken{"bar"}, listToken{10}, mapToken{"baz"}} tokenized := tokenize(input) assert.Equal(t, expected, tokenized) } 070701000000A0000041ED00000000000000000000000362794F9300000000000000000000000000000000000000000000001600000000sops-3.7.3/stores/ini070701000000A1000081A400000000000000000000000162794F9300001C7E000000000000000000000000000000000000001F00000000sops-3.7.3/stores/ini/store.gopackage ini //import "go.mozilla.org/sops/v3/stores/ini" import ( "bytes" "encoding/json" "fmt" "strconv" "strings" "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/stores" "gopkg.in/ini.v1" ) // Store handles storage of ini data. type Store struct { } func (store Store) encodeTree(branches sops.TreeBranches) ([]byte, error) { iniFile := ini.Empty() for _, branch := range branches { for _, item := range branch { if _, ok := item.Key.(sops.Comment); ok { continue } section, err := iniFile.NewSection(item.Key.(string)) if err != nil { return nil, fmt.Errorf("Error encoding section %s: %s", item.Key, err) } itemTree, ok := item.Value.(sops.TreeBranch) if !ok { return nil, fmt.Errorf("Error encoding section: Section values should always be TreeBranches") } first := 0 if len(itemTree) > 0 { if sectionComment, ok := itemTree[0].Key.(sops.Comment); ok { section.Comment = sectionComment.Value first = 1 } } var lastItem *ini.Key for i := first; i < len(itemTree); i++ { keyVal := itemTree[i] if comment, ok := keyVal.Key.(sops.Comment); ok { if lastItem != nil { lastItem.Comment = comment.Value } } else { lastItem, err = section.NewKey(keyVal.Key.(string), store.valToString(keyVal.Value)) if err != nil { return nil, fmt.Errorf("Error encoding key: %s", err) } } } } } var buffer bytes.Buffer iniFile.WriteTo(&buffer) return buffer.Bytes(), nil } func (store Store) stripCommentChar(comment string) string { if strings.HasPrefix(comment, ";") { comment = strings.TrimLeft(comment, "; ") } else if strings.HasPrefix(comment, "#") { comment = strings.TrimLeft(comment, "# ") } return comment } func (store Store) valToString(v interface{}) string { switch v := v.(type) { case fmt.Stringer: return v.String() case float64: return strconv.FormatFloat(v, 'f', 6, 64) case bool: return strconv.FormatBool(v) default: return fmt.Sprintf("%s", v) } } func (store Store) iniFromTreeBranches(branches sops.TreeBranches) ([]byte, error) { return store.encodeTree(branches) } func (store Store) treeBranchesFromIni(in []byte) (sops.TreeBranches, error) { iniFile, err := ini.Load(in) if err != nil { return nil, err } var branch sops.TreeBranch for _, section := range iniFile.Sections() { item, err := store.treeItemFromSection(section) if err != nil { return sops.TreeBranches{branch}, err } branch = append(branch, item) } return sops.TreeBranches{branch}, nil } func (store Store) treeItemFromSection(section *ini.Section) (sops.TreeItem, error) { var sectionItem sops.TreeItem sectionItem.Key = section.Name() var items sops.TreeBranch if section.Comment != "" { items = append(items, sops.TreeItem{ Key: sops.Comment{ Value: store.stripCommentChar(section.Comment), }, Value: nil, }) } for _, key := range section.Keys() { item := sops.TreeItem{Key: key.Name(), Value: key.Value()} items = append(items, item) if key.Comment != "" { items = append(items, sops.TreeItem{ Key: sops.Comment{ Value: store.stripCommentChar(key.Comment), }, Value: nil, }) } } sectionItem.Value = items return sectionItem, nil } // LoadEncryptedFile loads encrypted INI file's bytes onto a sops.Tree runtime object func (store *Store) LoadEncryptedFile(in []byte) (sops.Tree, error) { iniFileOuter, err := ini.Load(in) if err != nil { return sops.Tree{}, err } sopsSection, err := iniFileOuter.GetSection("sops") if err != nil { return sops.Tree{}, sops.MetadataNotFound } metadataHolder, err := store.iniSectionToMetadata(sopsSection) if err != nil { return sops.Tree{}, err } metadata, err := metadataHolder.ToInternal() if err != nil { return sops.Tree{}, err } // After that, we load the whole file into a map. branches, err := store.treeBranchesFromIni(in) if err != nil { return sops.Tree{}, fmt.Errorf("Could not unmarshal input data: %s", err) } // Discard metadata, as we already loaded it. for bi, branch := range branches { for s, sectionBranch := range branch { if sectionBranch.Key == "sops" { branch = append(branch[:s], branch[s+1:]...) branches[bi] = branch } } } return sops.Tree{ Branches: branches, Metadata: metadata, }, nil } func (store *Store) iniSectionToMetadata(sopsSection *ini.Section) (stores.Metadata, error) { metadataHash := make(map[string]interface{}) for k, v := range sopsSection.KeysHash() { metadataHash[k] = strings.Replace(v, "\\n", "\n", -1) } m := stores.Unflatten(metadataHash) var md stores.Metadata inrec, err := json.Marshal(m) if err != nil { return md, err } err = json.Unmarshal(inrec, &md) return md, err } // LoadPlainFile loads a plaintext INI file's bytes onto a sops.TreeBranches runtime object func (store *Store) LoadPlainFile(in []byte) (sops.TreeBranches, error) { branches, err := store.treeBranchesFromIni(in) if err != nil { return branches, fmt.Errorf("Could not unmarshal input data: %s", err) } return branches, nil } // EmitEncryptedFile returns encrypted INI file bytes corresponding to a sops.Tree // runtime object func (store *Store) EmitEncryptedFile(in sops.Tree) ([]byte, error) { metadata := stores.MetadataFromInternal(in.Metadata) newBranch, err := store.encodeMetadataToIniBranch(metadata) if err != nil { return nil, err } sectionItem := sops.TreeItem{Key: "sops", Value: newBranch} branch := sops.TreeBranch{sectionItem} in.Branches = append(in.Branches, branch) out, err := store.iniFromTreeBranches(in.Branches) if err != nil { return nil, fmt.Errorf("Error marshaling to ini: %s", err) } return out, nil } func (store *Store) encodeMetadataToIniBranch(md stores.Metadata) (sops.TreeBranch, error) { var mdMap map[string]interface{} inrec, err := json.Marshal(md) if err != nil { return nil, err } err = json.Unmarshal(inrec, &mdMap) if err != nil { return nil, err } flat := stores.Flatten(mdMap) for k, v := range flat { if s, ok := v.(string); ok { flat[k] = strings.Replace(s, "\n", "\\n", -1) } } if err != nil { return nil, err } branch := sops.TreeBranch{} for key, value := range flat { if value == nil { continue } branch = append(branch, sops.TreeItem{Key: key, Value: value}) } return branch, nil } // EmitPlainFile returns the plaintext INI file bytes corresponding to a sops.TreeBranches object func (store *Store) EmitPlainFile(in sops.TreeBranches) ([]byte, error) { out, err := store.iniFromTreeBranches(in) if err != nil { return nil, fmt.Errorf("Error marshaling to ini: %s", err) } return out, nil } func (store Store) encodeValue(v interface{}) ([]byte, error) { switch v := v.(type) { case sops.TreeBranches: return store.encodeTree(v) default: return json.Marshal(v) } } // EmitValue returns a single value encoded in a generic interface{} as bytes func (store *Store) EmitValue(v interface{}) ([]byte, error) { return store.encodeValue(v) } // EmitExample returns the plaintext INI file bytes corresponding to the SimpleTree example func (store *Store) EmitExample() []byte { bytes, err := store.EmitPlainFile(stores.ExampleSimpleTree.Branches) if err != nil { panic(err) } return bytes } 070701000000A2000081A400000000000000000000000162794F9300000ABD000000000000000000000000000000000000002400000000sops-3.7.3/stores/ini/store_test.gopackage ini import ( "testing" "github.com/stretchr/testify/assert" "go.mozilla.org/sops/v3" ) func TestDecodeIni(t *testing.T) { in := ` ; last modified 1 April 2001 by John Doe [owner] name=John Doe organization=Acme Widgets Inc. [database] ; use IP address in case network name resolution is not working server=192.0.2.62 port=143 file="payroll.dat" ` expected := sops.TreeBranches{ sops.TreeBranch{ sops.TreeItem{ Key: "DEFAULT", Value: sops.TreeBranch(nil), }, sops.TreeItem{ Key: "owner", Value: sops.TreeBranch{ sops.TreeItem{ Key: sops.Comment{Value: "last modified 1 April 2001 by John Doe"}, Value: nil, }, sops.TreeItem{ Key: "name", Value: "John Doe", }, sops.TreeItem{ Key: "organization", Value: "Acme Widgets Inc.", }, }, }, sops.TreeItem{ Key: "database", Value: sops.TreeBranch{ sops.TreeItem{ Key: "server", Value: "192.0.2.62", }, sops.TreeItem{ Key: sops.Comment{Value: "use IP address in case network name resolution is not working"}, Value: nil, }, sops.TreeItem{ Key: "port", Value: "143", }, sops.TreeItem{ Key: "file", Value: "payroll.dat", }, }, }, }, } branch, err := Store{}.treeBranchesFromIni([]byte(in)) assert.Nil(t, err) assert.Equal(t, expected, branch) } func TestEncodeSimpleIni(t *testing.T) { branches := sops.TreeBranches{ sops.TreeBranch{ sops.TreeItem{ Key: "DEFAULT", Value: sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: "bar", }, sops.TreeItem{ Key: "baz", Value: "3.0", }, sops.TreeItem{ Key: "qux", Value: "false", }, }, }, }, } out, err := Store{}.iniFromTreeBranches(branches) assert.Nil(t, err) expected, _ := Store{}.treeBranchesFromIni(out) assert.Equal(t, expected, branches) } func TestEncodeIniWithEscaping(t *testing.T) { branches := sops.TreeBranches{ sops.TreeBranch{ sops.TreeItem{ Key: "DEFAULT", Value: sops.TreeBranch{ sops.TreeItem{ Key: "foo\\bar", Value: "value", }, sops.TreeItem{ Key: "a_key_with\"quotes\"", Value: "4.0", }, sops.TreeItem{ Key: "baz\\\\foo", Value: "2.0", }, }, }, }, } out, err := Store{}.iniFromTreeBranches(branches) assert.Nil(t, err) expected, _ := Store{}.treeBranchesFromIni(out) assert.Equal(t, expected, branches) } func TestUnmarshalMetadataFromNonSOPSFile(t *testing.T) { data := []byte(`hello=2`) store := Store{} _, err := store.LoadEncryptedFile(data) assert.Equal(t, sops.MetadataNotFound, err) } 070701000000A3000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000002500000000sops-3.7.3/stores/ini/test_resources070701000000A4000081A400000000000000000000000162794F9300000C03000000000000000000000000000000000000003200000000sops-3.7.3/stores/ini/test_resources/example.json{ "example_key": "ENC[AES256_GCM,data:Xjen3YMQYCBfTU8VjA==,iv:1NUKversqeQiuTmAkZuyd6UY2AWiBS4owa4QHnwKOBM=,tag:ZO+Aln5DQ5Qm/QV2uBdahA==,type:str]", "example_array": [ "ENC[AES256_GCM,data:XJR0qvZuifm8j1TIQB8=,iv:QUkwy1dp0RU0PKEAw/VxVe1ZsQ972c8gPMJoVKgMfuw=,tag:LGdwC5nTua4rSe0dLbbA1Q==,type:str]", "ENC[AES256_GCM,data:7bhghzi5GN/mMqh1vHU=,iv:X5vrd9X7ItIG/RVCn0T7RFhUrTb2YItr3i97EVk9nOY=,tag:vrM058PPOGmWOGPThOP5Ew==,type:str]" ], "example_number": "ENC[AES256_GCM,data:w9etQN5r8iCz,iv:YF+1uUlMa4I1C7A0ELpVuMa1yK042uEMhp8y6HiCTDE=,tag:Dmh+AV9sh+ir0M1Txe+v2A==,type:float]", "example_booleans": [ "ENC[AES256_GCM,data:/hltsg==,iv:pbAtZ9i8rxFpaFlbwE1KOA+k/TVx5dm0tDtH94GCEVc=,tag:yz3d1pQu9zy5Ra9z2kDcoA==,type:bool]", "ENC[AES256_GCM,data:HEei0+s=,iv:hgKT5eiYdHn5AqWdNji7vRKfabln90VbLmJqt7A480E=,tag:6pfezzTX/eULebF+32Z2+w==,type:bool]" ], "sops": { "lastmodified": "2016-08-04T23:30:35Z", "attention": "This section contains key material that should only be modified with extra care. See `sops -h`.", "unencrypted_suffix": "_unencrypted", "mac": "ENC[AES256_GCM,data:EK1LkVgW5CBEsGgGc7RkfZlzqWrP2fZe3kG7HbkJ5JFd591oUkbQ6I2uPImkcxf7HjiEHzKPPF5QvNg3+rUxgw6S8pQtumhDbFrfDi8GDS2VVvPR+0fnc2fR5PMGm36bOaQFDNSmgyJzKhMmNL+MtRhH+fMUnHhrnxuN3wfLr4w=,iv:xbNK6wRDVT4xhrP+vP2RIy+uNjZSSzqEJZPOdShn96o=,tag:vT4akR5X6qx5/wJ4dncxtg==,type:str]", "version": "1.13", "kms": [ { "created_at": "2016-08-04T23:30:35Z", "enc": "AQECAHgFEiO2dNygC3Rz8PhERCc8Sfhak4g81FUPqQJ0OBcAKgAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDPKe5R67LMN3+xAkygIBEIA7F8noZukawV3VLQ/yH3Ep7Ptx8weLFUgVf/ZI6xqSMNvEHIr4+vf2xjBiAyrEF8u/n9nm9PWAdKHszFM=", "arn": "arn:aws:kms:us-east-1:927034868273:key/e9fc75db-05e9-44c1-9c35-633922bac347" } ], "pgp": [ { "fp": "E5297818703249D0C60E19E6824612478D1A4CCD", "created_at": "2016-08-04T23:30:35Z", "enc": "-----BEGIN PGP MESSAGE-----\nVersion: GnuPG v1\n\nhQIMA2X8rvoeiASBARAAurTEVS82kqadk68f5ZlwR176S148WYTYxFp5oMC7cVD7\n42+Eo9RzaxbHeO5n7XKX0SDOUUeCucFl8fwuDUV1iDIx4/u5HgWXxDuvWoNe5cAL\n4LBS1Er2ZBVAdU0WHZ/8USuZLhSu7ucAHOvqNpHzPT6gkuBUYLQKOu0c+onWHqVO\n1DhfkTtvphotZ0ZBBR099t5N8ofD0W2+SM268A9/bB5yQcK9Ig/KxZBrfmMQm7zx\n9hLVQhcBmj0OQG37K4/SXGwjrQFarh6lm+FuZM0Q+GI+OARoAKdpZOnPEhXKE6un\nSEa69rh5FKVM/XRp2/QVZEakzRtq3gi9CtYL2sNr7KEnCvxt/v2pEc6evfIvxWTc\nT8MWdk48FkVjdsJ34sNiIM8msstnYorse8RZny9gcLE+A5lsRavo2QPL4GADyHF8\n7kwijSVDd08nByTBMMEPpMozUFhzF8QuVZPD+siuUvi+Bned9MmqgGMfvhS0Kf38\nMZFy5C6e38VGEX3IrWChvzbBm/M3fjs1fPVDShHfk1MYsCU9sXNQMQVewWE0s/em\nklycIL3hywd4N9z1MVW2hBpRrC247PtGQRKGoB9qbKtSgjTtgM7bo1vYekeY1tjr\nBGTHNFV+FBqFih16u/rGVzIaBsf5lLL/RtpaFZx1OWHMd9XjQpRrHhjOMpQ8tjvS\nXgGH59vv/9GNZ+Rix1QF+iMD84sfkyyguGKwg+TC3m275v+HIO1NvNdU6oS3O/Xq\nBCBV3yYAUwcrWUPWCuSUHJbuHKJEI1ymXUu8+RUElPyi/5JEhW+J1WlVPvnG1Xk=\n=Q/XA\n-----END PGP MESSAGE-----\n" } ] } }070701000000A5000041ED00000000000000000000000362794F9300000000000000000000000000000000000000000000001700000000sops-3.7.3/stores/json070701000000A6000081A400000000000000000000000162794F93000022AF000000000000000000000000000000000000002000000000sops-3.7.3/stores/json/store.gopackage json //import "go.mozilla.org/sops/v3/stores/json" import ( "bytes" "encoding/json" "fmt" "io" "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/stores" ) // Store handles storage of JSON data. type Store struct { } // BinaryStore handles storage of binary data in a JSON envelope. type BinaryStore struct { store Store } // LoadEncryptedFile loads an encrypted json file onto a sops.Tree object func (store BinaryStore) LoadEncryptedFile(in []byte) (sops.Tree, error) { return store.store.LoadEncryptedFile(in) } // LoadPlainFile loads a plaintext json file onto a sops.Tree encapsulated // within a sops.TreeBranches object func (store BinaryStore) LoadPlainFile(in []byte) (sops.TreeBranches, error) { return sops.TreeBranches{ sops.TreeBranch{ sops.TreeItem{ Key: "data", Value: string(in), }, }, }, nil } // EmitEncryptedFile produces an encrypted json file's bytes from its corresponding sops.Tree object func (store BinaryStore) EmitEncryptedFile(in sops.Tree) ([]byte, error) { return store.store.EmitEncryptedFile(in) } // EmitPlainFile produces plaintext json file's bytes from its corresponding sops.TreeBranches object func (store BinaryStore) EmitPlainFile(in sops.TreeBranches) ([]byte, error) { // JSON stores a single object per file for _, item := range in[0] { if item.Key == "data" { return []byte(item.Value.(string)), nil } } return nil, fmt.Errorf("No binary data found in tree") } // EmitValue extracts a value from a generic interface{} object representing a structured set // of binary files func (store BinaryStore) EmitValue(v interface{}) ([]byte, error) { return nil, fmt.Errorf("Binary files are not structured and extracting a single value is not possible") } // EmitExample returns the example's plaintext json file bytes func (store BinaryStore) EmitExample() []byte { return []byte("Welcome to SOPS! Edit this file as you please!") } func (store Store) sliceFromJSONDecoder(dec *json.Decoder) ([]interface{}, error) { var slice []interface{} for { t, err := dec.Token() if err != nil { return slice, err } if delim, ok := t.(json.Delim); ok && delim.String() == "]" { return slice, nil } else if ok && delim.String() == "{" { item, err := store.treeBranchFromJSONDecoder(dec) if err != nil { return slice, err } slice = append(slice, item) } else if ok && delim.String() == "[" { item, err := store.sliceFromJSONDecoder(dec) if err != nil { return slice, err } slice = append(slice, item) } else { slice = append(slice, t) } } } var errEndOfObject = fmt.Errorf("End of object") func (store Store) treeItemFromJSONDecoder(dec *json.Decoder) (sops.TreeItem, error) { var item sops.TreeItem key, err := dec.Token() if err != nil && err != io.EOF { return item, err } if k, ok := key.(string); ok { item.Key = k } else if d, ok := key.(json.Delim); ok && d.String() == "}" { return item, errEndOfObject } else { return item, fmt.Errorf("Expected JSON object key, got %s of type %T instead", key, key) } value, err := dec.Token() if err != nil { return item, err } if delim, ok := value.(json.Delim); ok { if delim.String() == "[" { v, err := store.sliceFromJSONDecoder(dec) if err != nil { return item, err } item.Value = v } if delim.String() == "{" { v, err := store.treeBranchFromJSONDecoder(dec) if err != nil { return item, err } item.Value = v } } else { item.Value = value } return item, nil } func (store Store) treeBranchFromJSONDecoder(dec *json.Decoder) (sops.TreeBranch, error) { var tree sops.TreeBranch for { item, err := store.treeItemFromJSONDecoder(dec) if err == io.EOF { return tree, nil } if err == errEndOfObject { return tree, nil } if err != nil { return tree, err } tree = append(tree, item) } } func (store Store) encodeValue(v interface{}) ([]byte, error) { switch v := v.(type) { case sops.TreeBranch: return store.encodeTree(v) case []interface{}: return store.encodeArray(v) default: return json.Marshal(v) } } func (store Store) encodeArray(array []interface{}) ([]byte, error) { out := "[" for i, item := range array { if _, ok := item.(sops.Comment); ok { continue } v, err := store.encodeValue(item) if err != nil { return nil, err } out += string(v) if i != len(array)-1 { out += "," } } out += "]" return []byte(out), nil } func (store Store) encodeTree(tree sops.TreeBranch) ([]byte, error) { out := "{" for i, item := range tree { if _, ok := item.Key.(sops.Comment); ok { continue } v, err := store.encodeValue(item.Value) if err != nil { return nil, fmt.Errorf("Error encoding value %s: %s", v, err) } k, err := json.Marshal(item.Key.(string)) if err != nil { return nil, fmt.Errorf("Error encoding key %s: %s", k, err) } out += string(k) + `: ` + string(v) if i != len(tree)-1 { out += "," } } return []byte(out + "}"), nil } func (store Store) jsonFromTreeBranch(branch sops.TreeBranch) ([]byte, error) { out, err := store.encodeTree(branch) if err != nil { return nil, err } return store.reindentJSON(out) } func (store Store) treeBranchFromJSON(in []byte) (sops.TreeBranch, error) { dec := json.NewDecoder(bytes.NewReader(in)) dec.Token() return store.treeBranchFromJSONDecoder(dec) } func (store Store) reindentJSON(in []byte) ([]byte, error) { var out bytes.Buffer err := json.Indent(&out, in, "", "\t") return out.Bytes(), err } // LoadEncryptedFile loads an encrypted secrets file onto a sops.Tree object func (store *Store) LoadEncryptedFile(in []byte) (sops.Tree, error) { // Because we don't know what fields the input file will have, we have to // load the file in two steps. // First, we load the file's metadata, the structure of which is known. metadataHolder := stores.SopsFile{} err := json.Unmarshal(in, &metadataHolder) if err != nil { if err, ok := err.(*json.UnmarshalTypeError); ok { if err.Value == "number" && err.Struct == "Metadata" && err.Field == "version" { return sops.Tree{}, fmt.Errorf("SOPS versions higher than 2.0.10 can not automatically decrypt JSON files " + "created with SOPS 1.x. In order to be able to decrypt this file, you can either edit it " + "manually and make sure the JSON value under `sops -> version` is a string and not a " + "number, or you can rotate the file's key with any version of SOPS between 2.0 and 2.0.10 " + "using `sops -r your_file.json`") } } return sops.Tree{}, fmt.Errorf("Error unmarshalling input json: %s", err) } if metadataHolder.Metadata == nil { return sops.Tree{}, sops.MetadataNotFound } metadata, err := metadataHolder.Metadata.ToInternal() if err != nil { return sops.Tree{}, err } // After that, we load the whole file into a map. branch, err := store.treeBranchFromJSON(in) if err != nil { return sops.Tree{}, fmt.Errorf("Could not unmarshal input data: %s", err) } // Discard metadata, as we already loaded it. for i, item := range branch { if item.Key == "sops" { branch = append(branch[:i], branch[i+1:]...) } } return sops.Tree{ Branches: sops.TreeBranches{ branch, }, Metadata: metadata, }, nil } // LoadPlainFile loads plaintext json file bytes onto a sops.TreeBranches object func (store *Store) LoadPlainFile(in []byte) (sops.TreeBranches, error) { branch, err := store.treeBranchFromJSON(in) if err != nil { return nil, fmt.Errorf("Could not unmarshal input data: %s", err) } return sops.TreeBranches{ branch, }, nil } // EmitEncryptedFile returns the encrypted bytes of the json file corresponding to a // sops.Tree runtime object func (store *Store) EmitEncryptedFile(in sops.Tree) ([]byte, error) { tree := append(in.Branches[0], sops.TreeItem{Key: "sops", Value: stores.MetadataFromInternal(in.Metadata)}) out, err := store.jsonFromTreeBranch(tree) if err != nil { return nil, fmt.Errorf("Error marshaling to json: %s", err) } return out, nil } // EmitPlainFile returns the plaintext bytes of the json file corresponding to a // sops.TreeBranches runtime object func (store *Store) EmitPlainFile(in sops.TreeBranches) ([]byte, error) { out, err := store.jsonFromTreeBranch(in[0]) if err != nil { return nil, fmt.Errorf("Error marshaling to json: %s", err) } return out, nil } // EmitValue returns bytes corresponding to a single encoded value // in a generic interface{} object func (store *Store) EmitValue(v interface{}) ([]byte, error) { s, err := store.encodeValue(v) if err != nil { return nil, err } return store.reindentJSON(s) } // EmitExample returns the bytes corresponding to an example complex tree func (store *Store) EmitExample() []byte { bytes, err := store.EmitPlainFile(stores.ExampleComplexTree.Branches) if err != nil { panic(err) } return bytes } 070701000000A7000081A400000000000000000000000162794F9300001B0E000000000000000000000000000000000000002500000000sops-3.7.3/stores/json/store_test.gopackage json import ( "testing" "github.com/stretchr/testify/assert" "go.mozilla.org/sops/v3" ) func TestDecodeJSON(t *testing.T) { in := ` { "glossary":{ "title":"example glossary", "GlossDiv":{ "title":"S", "GlossList":{ "GlossEntry":{ "ID":"SGML", "SortAs":"SGML", "GlossTerm":"Standard Generalized Markup Language", "Acronym":"SGML", "Abbrev":"ISO 8879:1986", "GlossDef":{ "para":"A meta-markup language, used to create markup languages such as DocBook.", "GlossSeeAlso":[ "GML", "XML" ] }, "GlossSee":"markup" } } } } }` expected := sops.TreeBranch{ sops.TreeItem{ Key: "glossary", Value: sops.TreeBranch{ sops.TreeItem{ Key: "title", Value: "example glossary", }, sops.TreeItem{ Key: "GlossDiv", Value: sops.TreeBranch{ sops.TreeItem{ Key: "title", Value: "S", }, sops.TreeItem{ Key: "GlossList", Value: sops.TreeBranch{ sops.TreeItem{ Key: "GlossEntry", Value: sops.TreeBranch{ sops.TreeItem{ Key: "ID", Value: "SGML", }, sops.TreeItem{ Key: "SortAs", Value: "SGML", }, sops.TreeItem{ Key: "GlossTerm", Value: "Standard Generalized Markup Language", }, sops.TreeItem{ Key: "Acronym", Value: "SGML", }, sops.TreeItem{ Key: "Abbrev", Value: "ISO 8879:1986", }, sops.TreeItem{ Key: "GlossDef", Value: sops.TreeBranch{ sops.TreeItem{ Key: "para", Value: "A meta-markup language, used to create markup languages such as DocBook.", }, sops.TreeItem{ Key: "GlossSeeAlso", Value: []interface{}{ "GML", "XML", }, }, }, }, sops.TreeItem{ Key: "GlossSee", Value: "markup", }, }, }, }, }, }, }, }, }, } branch, err := Store{}.treeBranchFromJSON([]byte(in)) assert.Nil(t, err) assert.Equal(t, expected, branch) } func TestDecodeSimpleJSONObject(t *testing.T) { in := `{"foo": "bar", "baz": 2}` expected := sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: "bar", }, sops.TreeItem{ Key: "baz", Value: 2.0, }, } branch, err := Store{}.treeBranchFromJSON([]byte(in)) assert.Nil(t, err) assert.Equal(t, expected, branch) } func TestDecodeNumber(t *testing.T) { in := `42` _, err := Store{}.treeBranchFromJSON([]byte(in)) assert.NotNil(t, err) } func TestDecodeNestedJSONObject(t *testing.T) { in := `{"foo": {"foo": "bar"}}` expected := sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: "bar", }, }, }, } branch, err := Store{}.treeBranchFromJSON([]byte(in)) assert.Nil(t, err) assert.Equal(t, expected, branch) } func TestDecodeJSONWithArray(t *testing.T) { in := `{"foo": {"foo": [1, 2, 3]}, "bar": "baz"}` expected := sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: []interface{}{1.0, 2.0, 3.0}, }, }, }, sops.TreeItem{ Key: "bar", Value: "baz", }, } branch, err := Store{}.treeBranchFromJSON([]byte(in)) assert.Nil(t, err) assert.Equal(t, expected, branch) } func TestDecodeJSONArrayOfObjects(t *testing.T) { in := `{"foo": [{"bar": "foo"}, {"foo": "bar"}]}` expected := sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: []interface{}{ sops.TreeBranch{ sops.TreeItem{ Key: "bar", Value: "foo", }, }, sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: "bar", }, }, }, }, } branch, err := Store{}.treeBranchFromJSON([]byte(in)) assert.Nil(t, err) assert.Equal(t, expected, branch) } func TestDecodeJSONArrayOfArrays(t *testing.T) { in := `{"foo": [[["foo", {"bar": "foo"}]]]}` expected := sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: []interface{}{ []interface{}{ []interface{}{ "foo", sops.TreeBranch{ sops.TreeItem{ Key: "bar", Value: "foo", }, }, }, }, }, }, } branch, err := Store{}.treeBranchFromJSON([]byte(in)) assert.Nil(t, err) assert.Equal(t, expected, branch) } func TestEncodeSimpleJSON(t *testing.T) { branch := sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: "bar", }, sops.TreeItem{ Key: "foo", Value: 3.0, }, sops.TreeItem{ Key: "bar", Value: false, }, } out, err := Store{}.jsonFromTreeBranch(branch) assert.Nil(t, err) expected, _ := Store{}.treeBranchFromJSON(out) assert.Equal(t, expected, branch) } func TestEncodeJSONWithEscaping(t *testing.T) { branch := sops.TreeBranch{ sops.TreeItem{ Key: "foo\\bar", Value: "value", }, sops.TreeItem{ Key: "a_key_with\"quotes\"", Value: 4.0, }, sops.TreeItem{ Key: "baz\\\\foo", Value: 2.0, }, } out, err := Store{}.jsonFromTreeBranch(branch) assert.Nil(t, err) expected, _ := Store{}.treeBranchFromJSON(out) assert.Equal(t, expected, branch) } func TestEncodeJSONArrayOfObjects(t *testing.T) { tree := sops.Tree{ Branches: sops.TreeBranches{ sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: []interface{}{ sops.TreeBranch{ sops.TreeItem{ Key: "foo", Value: 3, }, sops.TreeItem{ Key: "bar", Value: false, }, }, 2, }, }, }, }, } expected := `{ "foo": [ { "foo": 3, "bar": false }, 2 ] }` store := Store{} out, err := store.EmitPlainFile(tree.Branches) assert.Nil(t, err) assert.Equal(t, expected, string(out)) } func TestUnmarshalMetadataFromNonSOPSFile(t *testing.T) { data := []byte(`{"hello": 2}`) store := Store{} _, err := store.LoadEncryptedFile(data) assert.Equal(t, sops.MetadataNotFound, err) } func TestLoadJSONFormattedBinaryFile(t *testing.T) { // This is JSON data, but we want SOPS to interpret it as binary, // e.g. because the --input-type binary flag was provided. data := []byte(`{"hello": 2}`) store := BinaryStore{} branches, err := store.LoadPlainFile(data) assert.Nil(t, err) assert.Equal(t, "data", branches[0][0].Key) } func TestEmitValueString(t *testing.T) { bytes, err := (&Store{}).EmitValue("hello") assert.Nil(t, err) assert.Equal(t, []byte("\"hello\""), bytes) } 070701000000A8000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000002600000000sops-3.7.3/stores/json/test_resources070701000000A9000081A400000000000000000000000162794F9300000C03000000000000000000000000000000000000003300000000sops-3.7.3/stores/json/test_resources/example.json{ "example_key": "ENC[AES256_GCM,data:Xjen3YMQYCBfTU8VjA==,iv:1NUKversqeQiuTmAkZuyd6UY2AWiBS4owa4QHnwKOBM=,tag:ZO+Aln5DQ5Qm/QV2uBdahA==,type:str]", "example_array": [ "ENC[AES256_GCM,data:XJR0qvZuifm8j1TIQB8=,iv:QUkwy1dp0RU0PKEAw/VxVe1ZsQ972c8gPMJoVKgMfuw=,tag:LGdwC5nTua4rSe0dLbbA1Q==,type:str]", "ENC[AES256_GCM,data:7bhghzi5GN/mMqh1vHU=,iv:X5vrd9X7ItIG/RVCn0T7RFhUrTb2YItr3i97EVk9nOY=,tag:vrM058PPOGmWOGPThOP5Ew==,type:str]" ], "example_number": "ENC[AES256_GCM,data:w9etQN5r8iCz,iv:YF+1uUlMa4I1C7A0ELpVuMa1yK042uEMhp8y6HiCTDE=,tag:Dmh+AV9sh+ir0M1Txe+v2A==,type:float]", "example_booleans": [ "ENC[AES256_GCM,data:/hltsg==,iv:pbAtZ9i8rxFpaFlbwE1KOA+k/TVx5dm0tDtH94GCEVc=,tag:yz3d1pQu9zy5Ra9z2kDcoA==,type:bool]", "ENC[AES256_GCM,data:HEei0+s=,iv:hgKT5eiYdHn5AqWdNji7vRKfabln90VbLmJqt7A480E=,tag:6pfezzTX/eULebF+32Z2+w==,type:bool]" ], "sops": { "lastmodified": "2016-08-04T23:30:35Z", "attention": "This section contains key material that should only be modified with extra care. See `sops -h`.", "unencrypted_suffix": "_unencrypted", "mac": "ENC[AES256_GCM,data:EK1LkVgW5CBEsGgGc7RkfZlzqWrP2fZe3kG7HbkJ5JFd591oUkbQ6I2uPImkcxf7HjiEHzKPPF5QvNg3+rUxgw6S8pQtumhDbFrfDi8GDS2VVvPR+0fnc2fR5PMGm36bOaQFDNSmgyJzKhMmNL+MtRhH+fMUnHhrnxuN3wfLr4w=,iv:xbNK6wRDVT4xhrP+vP2RIy+uNjZSSzqEJZPOdShn96o=,tag:vT4akR5X6qx5/wJ4dncxtg==,type:str]", "version": "1.13", "kms": [ { "created_at": "2016-08-04T23:30:35Z", "enc": "AQECAHgFEiO2dNygC3Rz8PhERCc8Sfhak4g81FUPqQJ0OBcAKgAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDPKe5R67LMN3+xAkygIBEIA7F8noZukawV3VLQ/yH3Ep7Ptx8weLFUgVf/ZI6xqSMNvEHIr4+vf2xjBiAyrEF8u/n9nm9PWAdKHszFM=", "arn": "arn:aws:kms:us-east-1:927034868273:key/e9fc75db-05e9-44c1-9c35-633922bac347" } ], "pgp": [ { "fp": "E5297818703249D0C60E19E6824612478D1A4CCD", "created_at": "2016-08-04T23:30:35Z", "enc": "-----BEGIN PGP MESSAGE-----\nVersion: GnuPG v1\n\nhQIMA2X8rvoeiASBARAAurTEVS82kqadk68f5ZlwR176S148WYTYxFp5oMC7cVD7\n42+Eo9RzaxbHeO5n7XKX0SDOUUeCucFl8fwuDUV1iDIx4/u5HgWXxDuvWoNe5cAL\n4LBS1Er2ZBVAdU0WHZ/8USuZLhSu7ucAHOvqNpHzPT6gkuBUYLQKOu0c+onWHqVO\n1DhfkTtvphotZ0ZBBR099t5N8ofD0W2+SM268A9/bB5yQcK9Ig/KxZBrfmMQm7zx\n9hLVQhcBmj0OQG37K4/SXGwjrQFarh6lm+FuZM0Q+GI+OARoAKdpZOnPEhXKE6un\nSEa69rh5FKVM/XRp2/QVZEakzRtq3gi9CtYL2sNr7KEnCvxt/v2pEc6evfIvxWTc\nT8MWdk48FkVjdsJ34sNiIM8msstnYorse8RZny9gcLE+A5lsRavo2QPL4GADyHF8\n7kwijSVDd08nByTBMMEPpMozUFhzF8QuVZPD+siuUvi+Bned9MmqgGMfvhS0Kf38\nMZFy5C6e38VGEX3IrWChvzbBm/M3fjs1fPVDShHfk1MYsCU9sXNQMQVewWE0s/em\nklycIL3hywd4N9z1MVW2hBpRrC247PtGQRKGoB9qbKtSgjTtgM7bo1vYekeY1tjr\nBGTHNFV+FBqFih16u/rGVzIaBsf5lLL/RtpaFZx1OWHMd9XjQpRrHhjOMpQ8tjvS\nXgGH59vv/9GNZ+Rix1QF+iMD84sfkyyguGKwg+TC3m275v+HIO1NvNdU6oS3O/Xq\nBCBV3yYAUwcrWUPWCuSUHJbuHKJEI1ymXUu8+RUElPyi/5JEhW+J1WlVPvnG1Xk=\n=Q/XA\n-----END PGP MESSAGE-----\n" } ] } }070701000000AA000081A400000000000000000000000162794F9300003E2A000000000000000000000000000000000000001C00000000sops-3.7.3/stores/stores.go/* Package stores acts as a layer between the internal representation of encrypted files and the encrypted files themselves. Subpackages implement serialization and deserialization to multiple formats. This package defines the structure SOPS files should have and conversions to and from the internal representation. Part of the purpose of this package is to make it easy to change the SOPS file format while remaining backwards-compatible. */ package stores import ( "time" "fmt" "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/age" "go.mozilla.org/sops/v3/azkv" "go.mozilla.org/sops/v3/gcpkms" "go.mozilla.org/sops/v3/hcvault" "go.mozilla.org/sops/v3/kms" "go.mozilla.org/sops/v3/pgp" ) // SopsFile is a struct used by the stores as a helper to unmarshal the SOPS metadata type SopsFile struct { // Metadata is a pointer so we can easily tell when the field is not present // in the SOPS file by checking for nil. This way we can show the user a // helpful error message indicating that the metadata wasn't found, instead // of showing a cryptic parsing error Metadata *Metadata `yaml:"sops" json:"sops" ini:"sops"` } // Metadata is stored in SOPS encrypted files, and it contains the information necessary to decrypt the file. // This struct is just used for serialization, and SOPS uses another struct internally, sops.Metadata. It exists // in order to allow the binary format to stay backwards compatible over time, but at the same time allow the internal // representation SOPS uses to change over time. type Metadata struct { ShamirThreshold int `yaml:"shamir_threshold,omitempty" json:"shamir_threshold,omitempty"` KeyGroups []keygroup `yaml:"key_groups,omitempty" json:"key_groups,omitempty"` KMSKeys []kmskey `yaml:"kms" json:"kms"` GCPKMSKeys []gcpkmskey `yaml:"gcp_kms" json:"gcp_kms"` AzureKeyVaultKeys []azkvkey `yaml:"azure_kv" json:"azure_kv"` VaultKeys []vaultkey `yaml:"hc_vault" json:"hc_vault"` AgeKeys []agekey `yaml:"age" json:"age"` LastModified string `yaml:"lastmodified" json:"lastmodified"` MessageAuthenticationCode string `yaml:"mac" json:"mac"` PGPKeys []pgpkey `yaml:"pgp" json:"pgp"` UnencryptedSuffix string `yaml:"unencrypted_suffix,omitempty" json:"unencrypted_suffix,omitempty"` EncryptedSuffix string `yaml:"encrypted_suffix,omitempty" json:"encrypted_suffix,omitempty"` UnencryptedRegex string `yaml:"unencrypted_regex,omitempty" json:"unencrypted_regex,omitempty"` EncryptedRegex string `yaml:"encrypted_regex,omitempty" json:"encrypted_regex,omitempty"` Version string `yaml:"version" json:"version"` } type keygroup struct { PGPKeys []pgpkey `yaml:"pgp,omitempty" json:"pgp,omitempty"` KMSKeys []kmskey `yaml:"kms,omitempty" json:"kms,omitempty"` GCPKMSKeys []gcpkmskey `yaml:"gcp_kms,omitempty" json:"gcp_kms,omitempty"` AzureKeyVaultKeys []azkvkey `yaml:"azure_kv,omitempty" json:"azure_kv,omitempty"` VaultKeys []vaultkey `yaml:"hc_vault" json:"hc_vault"` AgeKeys []agekey `yaml:"age" json:"age"` } type pgpkey struct { CreatedAt string `yaml:"created_at" json:"created_at"` EncryptedDataKey string `yaml:"enc" json:"enc"` Fingerprint string `yaml:"fp" json:"fp"` } type kmskey struct { Arn string `yaml:"arn" json:"arn"` Role string `yaml:"role,omitempty" json:"role,omitempty"` Context map[string]*string `yaml:"context,omitempty" json:"context,omitempty"` CreatedAt string `yaml:"created_at" json:"created_at"` EncryptedDataKey string `yaml:"enc" json:"enc"` AwsProfile string `yaml:"aws_profile" json:"aws_profile"` } type gcpkmskey struct { ResourceID string `yaml:"resource_id" json:"resource_id"` CreatedAt string `yaml:"created_at" json:"created_at"` EncryptedDataKey string `yaml:"enc" json:"enc"` } type vaultkey struct { VaultAddress string `yaml:"vault_address" json:"vault_address"` EnginePath string `yaml:"engine_path" json:"engine_path"` KeyName string `yaml:"key_name" json:"key_name"` CreatedAt string `yaml:"created_at" json:"created_at"` EncryptedDataKey string `yaml:"enc" json:"enc"` } type azkvkey struct { VaultURL string `yaml:"vault_url" json:"vault_url"` Name string `yaml:"name" json:"name"` Version string `yaml:"version" json:"version"` CreatedAt string `yaml:"created_at" json:"created_at"` EncryptedDataKey string `yaml:"enc" json:"enc"` } type agekey struct { Recipient string `yaml:"recipient" json:"recipient"` EncryptedDataKey string `yaml:"enc" json:"enc"` } // MetadataFromInternal converts an internal SOPS metadata representation to a representation appropriate for storage func MetadataFromInternal(sopsMetadata sops.Metadata) Metadata { var m Metadata m.LastModified = sopsMetadata.LastModified.Format(time.RFC3339) m.UnencryptedSuffix = sopsMetadata.UnencryptedSuffix m.EncryptedSuffix = sopsMetadata.EncryptedSuffix m.UnencryptedRegex = sopsMetadata.UnencryptedRegex m.EncryptedRegex = sopsMetadata.EncryptedRegex m.MessageAuthenticationCode = sopsMetadata.MessageAuthenticationCode m.Version = sopsMetadata.Version m.ShamirThreshold = sopsMetadata.ShamirThreshold if len(sopsMetadata.KeyGroups) == 1 { group := sopsMetadata.KeyGroups[0] m.PGPKeys = pgpKeysFromGroup(group) m.KMSKeys = kmsKeysFromGroup(group) m.GCPKMSKeys = gcpkmsKeysFromGroup(group) m.VaultKeys = vaultKeysFromGroup(group) m.AzureKeyVaultKeys = azkvKeysFromGroup(group) m.AgeKeys = ageKeysFromGroup(group) } else { for _, group := range sopsMetadata.KeyGroups { m.KeyGroups = append(m.KeyGroups, keygroup{ KMSKeys: kmsKeysFromGroup(group), PGPKeys: pgpKeysFromGroup(group), GCPKMSKeys: gcpkmsKeysFromGroup(group), VaultKeys: vaultKeysFromGroup(group), AzureKeyVaultKeys: azkvKeysFromGroup(group), AgeKeys: ageKeysFromGroup(group), }) } } return m } func pgpKeysFromGroup(group sops.KeyGroup) (keys []pgpkey) { for _, key := range group { switch key := key.(type) { case *pgp.MasterKey: keys = append(keys, pgpkey{ Fingerprint: key.Fingerprint, EncryptedDataKey: key.EncryptedKey, CreatedAt: key.CreationDate.Format(time.RFC3339), }) } } return } func kmsKeysFromGroup(group sops.KeyGroup) (keys []kmskey) { for _, key := range group { switch key := key.(type) { case *kms.MasterKey: keys = append(keys, kmskey{ Arn: key.Arn, CreatedAt: key.CreationDate.Format(time.RFC3339), EncryptedDataKey: key.EncryptedKey, Context: key.EncryptionContext, Role: key.Role, AwsProfile: key.AwsProfile, }) } } return } func gcpkmsKeysFromGroup(group sops.KeyGroup) (keys []gcpkmskey) { for _, key := range group { switch key := key.(type) { case *gcpkms.MasterKey: keys = append(keys, gcpkmskey{ ResourceID: key.ResourceID, CreatedAt: key.CreationDate.Format(time.RFC3339), EncryptedDataKey: key.EncryptedKey, }) } } return } func vaultKeysFromGroup(group sops.KeyGroup) (keys []vaultkey) { for _, key := range group { switch key := key.(type) { case *hcvault.MasterKey: keys = append(keys, vaultkey{ VaultAddress: key.VaultAddress, EnginePath: key.EnginePath, KeyName: key.KeyName, CreatedAt: key.CreationDate.Format(time.RFC3339), EncryptedDataKey: key.EncryptedKey, }) } } return } func azkvKeysFromGroup(group sops.KeyGroup) (keys []azkvkey) { for _, key := range group { switch key := key.(type) { case *azkv.MasterKey: keys = append(keys, azkvkey{ VaultURL: key.VaultURL, Name: key.Name, Version: key.Version, CreatedAt: key.CreationDate.Format(time.RFC3339), EncryptedDataKey: key.EncryptedKey, }) } } return } func ageKeysFromGroup(group sops.KeyGroup) (keys []agekey) { for _, key := range group { switch key := key.(type) { case *age.MasterKey: keys = append(keys, agekey{ Recipient: key.Recipient, EncryptedDataKey: key.EncryptedKey, }) } } return } // ToInternal converts a storage-appropriate Metadata struct to a SOPS internal representation func (m *Metadata) ToInternal() (sops.Metadata, error) { lastModified, err := time.Parse(time.RFC3339, m.LastModified) if err != nil { return sops.Metadata{}, err } groups, err := m.internalKeygroups() if err != nil { return sops.Metadata{}, err } cryptRuleCount := 0 if m.UnencryptedSuffix != "" { cryptRuleCount++ } if m.EncryptedSuffix != "" { cryptRuleCount++ } if m.UnencryptedRegex != "" { cryptRuleCount++ } if m.EncryptedRegex != "" { cryptRuleCount++ } if cryptRuleCount > 1 { return sops.Metadata{}, fmt.Errorf("Cannot use more than one of encrypted_suffix, unencrypted_suffix, encrypted_regex or unencrypted_regex in the same file") } if cryptRuleCount == 0 { m.UnencryptedSuffix = sops.DefaultUnencryptedSuffix } return sops.Metadata{ KeyGroups: groups, ShamirThreshold: m.ShamirThreshold, Version: m.Version, MessageAuthenticationCode: m.MessageAuthenticationCode, UnencryptedSuffix: m.UnencryptedSuffix, EncryptedSuffix: m.EncryptedSuffix, UnencryptedRegex: m.UnencryptedRegex, EncryptedRegex: m.EncryptedRegex, LastModified: lastModified, }, nil } func internalGroupFrom(kmsKeys []kmskey, pgpKeys []pgpkey, gcpKmsKeys []gcpkmskey, azkvKeys []azkvkey, vaultKeys []vaultkey, ageKeys []agekey) (sops.KeyGroup, error) { var internalGroup sops.KeyGroup for _, kmsKey := range kmsKeys { k, err := kmsKey.toInternal() if err != nil { return nil, err } internalGroup = append(internalGroup, k) } for _, gcpKmsKey := range gcpKmsKeys { k, err := gcpKmsKey.toInternal() if err != nil { return nil, err } internalGroup = append(internalGroup, k) } for _, azkvKey := range azkvKeys { k, err := azkvKey.toInternal() if err != nil { return nil, err } internalGroup = append(internalGroup, k) } for _, vaultKey := range vaultKeys { k, err := vaultKey.toInternal() if err != nil { return nil, err } internalGroup = append(internalGroup, k) } for _, pgpKey := range pgpKeys { k, err := pgpKey.toInternal() if err != nil { return nil, err } internalGroup = append(internalGroup, k) } for _, ageKey := range ageKeys { k, err := ageKey.toInternal() if err != nil { return nil, err } internalGroup = append(internalGroup, k) } return internalGroup, nil } func (m *Metadata) internalKeygroups() ([]sops.KeyGroup, error) { var internalGroups []sops.KeyGroup if len(m.PGPKeys) > 0 || len(m.KMSKeys) > 0 || len(m.GCPKMSKeys) > 0 || len(m.AzureKeyVaultKeys) > 0 || len(m.VaultKeys) > 0 || len(m.AgeKeys) > 0 { internalGroup, err := internalGroupFrom(m.KMSKeys, m.PGPKeys, m.GCPKMSKeys, m.AzureKeyVaultKeys, m.VaultKeys, m.AgeKeys) if err != nil { return nil, err } internalGroups = append(internalGroups, internalGroup) return internalGroups, nil } else if len(m.KeyGroups) > 0 { for _, group := range m.KeyGroups { internalGroup, err := internalGroupFrom(group.KMSKeys, group.PGPKeys, group.GCPKMSKeys, group.AzureKeyVaultKeys, group.VaultKeys, group.AgeKeys) if err != nil { return nil, err } internalGroups = append(internalGroups, internalGroup) } return internalGroups, nil } else { return nil, fmt.Errorf("No keys found in file") } } func (kmsKey *kmskey) toInternal() (*kms.MasterKey, error) { creationDate, err := time.Parse(time.RFC3339, kmsKey.CreatedAt) if err != nil { return nil, err } return &kms.MasterKey{ Role: kmsKey.Role, EncryptionContext: kmsKey.Context, EncryptedKey: kmsKey.EncryptedDataKey, CreationDate: creationDate, Arn: kmsKey.Arn, AwsProfile: kmsKey.AwsProfile, }, nil } func (gcpKmsKey *gcpkmskey) toInternal() (*gcpkms.MasterKey, error) { creationDate, err := time.Parse(time.RFC3339, gcpKmsKey.CreatedAt) if err != nil { return nil, err } return &gcpkms.MasterKey{ ResourceID: gcpKmsKey.ResourceID, EncryptedKey: gcpKmsKey.EncryptedDataKey, CreationDate: creationDate, }, nil } func (azkvKey *azkvkey) toInternal() (*azkv.MasterKey, error) { creationDate, err := time.Parse(time.RFC3339, azkvKey.CreatedAt) if err != nil { return nil, err } return &azkv.MasterKey{ VaultURL: azkvKey.VaultURL, Name: azkvKey.Name, Version: azkvKey.Version, EncryptedKey: azkvKey.EncryptedDataKey, CreationDate: creationDate, }, nil } func (vaultKey *vaultkey) toInternal() (*hcvault.MasterKey, error) { creationDate, err := time.Parse(time.RFC3339, vaultKey.CreatedAt) if err != nil { return nil, err } return &hcvault.MasterKey{ VaultAddress: vaultKey.VaultAddress, EnginePath: vaultKey.EnginePath, KeyName: vaultKey.KeyName, CreationDate: creationDate, EncryptedKey: vaultKey.EncryptedDataKey, }, nil } func (pgpKey *pgpkey) toInternal() (*pgp.MasterKey, error) { creationDate, err := time.Parse(time.RFC3339, pgpKey.CreatedAt) if err != nil { return nil, err } return &pgp.MasterKey{ EncryptedKey: pgpKey.EncryptedDataKey, CreationDate: creationDate, Fingerprint: pgpKey.Fingerprint, }, nil } func (ageKey *agekey) toInternal() (*age.MasterKey, error) { return &age.MasterKey{ EncryptedKey: ageKey.EncryptedDataKey, Recipient: ageKey.Recipient, }, nil } // ExampleComplexTree is an example sops.Tree object exhibiting complex relationships var ExampleComplexTree = sops.Tree{ Branches: sops.TreeBranches{ sops.TreeBranch{ sops.TreeItem{ Key: "hello", Value: `Welcome to SOPS! Edit this file as you please!`, }, sops.TreeItem{ Key: "example_key", Value: "example_value", }, sops.TreeItem{ Key: sops.Comment{Value: " Example comment"}, Value: nil, }, sops.TreeItem{ Key: "example_array", Value: []interface{}{ "example_value1", "example_value2", }, }, sops.TreeItem{ Key: "example_number", Value: 1234.56789, }, sops.TreeItem{ Key: "example_booleans", Value: []interface{}{true, false}, }, }, }, } // ExampleSimpleTree is an example sops.Tree object exhibiting only simple relationships // with only one nested branch and only simple string values var ExampleSimpleTree = sops.Tree{ Branches: sops.TreeBranches{ sops.TreeBranch{ sops.TreeItem{ Key: "Welcome!", Value: sops.TreeBranch{ sops.TreeItem{ Key: sops.Comment{Value: " This is an example file."}, Value: nil, }, sops.TreeItem{ Key: "hello", Value: "Welcome to SOPS! Edit this file as you please!", }, sops.TreeItem{ Key: "example_key", Value: "example_value", }, }, }, }, }, } // ExampleFlatTree is an example sops.Tree object exhibiting only simple relationships // with no nested branches and only simple string values var ExampleFlatTree = sops.Tree{ Branches: sops.TreeBranches{ sops.TreeBranch{ sops.TreeItem{ Key: sops.Comment{Value: " This is an example file."}, Value: nil, }, sops.TreeItem{ Key: "hello", Value: "Welcome to SOPS! Edit this file as you please!", }, sops.TreeItem{ Key: "example_key", Value: "example_value", }, sops.TreeItem{ Key: "example_multiline", Value: "foo\nbar\nbaz", }, }, }, } 070701000000AB000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001700000000sops-3.7.3/stores/yaml070701000000AC000081A400000000000000000000000162794F9300002C07000000000000000000000000000000000000002000000000sops-3.7.3/stores/yaml/store.gopackage yaml //import "go.mozilla.org/sops/v3/stores/yaml" import ( "bytes" "fmt" "io" "strings" "gopkg.in/yaml.v3" "go.mozilla.org/sops/v3" "go.mozilla.org/sops/v3/stores" ) // Store handles storage of YAML data type Store struct { } func (store Store) appendCommentToList(comment string, list []interface{}) []interface{} { if comment != "" { for _, commentLine := range strings.Split(comment, "\n") { if commentLine != "" { list = append(list, sops.Comment{ Value: commentLine[1:], }) } } } return list } func (store Store) appendCommentToMap(comment string, branch sops.TreeBranch) sops.TreeBranch { if comment != "" { for _, commentLine := range strings.Split(comment, "\n") { if commentLine != "" { branch = append(branch, sops.TreeItem{ Key: sops.Comment{ Value: commentLine[1:], }, Value: nil, }) } } } return branch } func (store Store) nodeToTreeValue(node *yaml.Node, commentsWereHandled bool) (interface{}, error) { switch node.Kind { case yaml.DocumentNode: panic("documents should never be passed here") case yaml.SequenceNode: var result []interface{} if !commentsWereHandled { result = store.appendCommentToList(node.HeadComment, result) result = store.appendCommentToList(node.LineComment, result) } for _, item := range node.Content { result = store.appendCommentToList(item.HeadComment, result) result = store.appendCommentToList(item.LineComment, result) val, err := store.nodeToTreeValue(item, true) if err != nil { return nil, err } result = append(result, val) result = store.appendCommentToList(item.FootComment, result) } if !commentsWereHandled { result = store.appendCommentToList(node.FootComment, result) } return result, nil case yaml.MappingNode: branch := make(sops.TreeBranch, 0) return store.appendYamlNodeToTreeBranch(node, branch, commentsWereHandled) case yaml.ScalarNode: var result interface{} node.Decode(&result) return result, nil case yaml.AliasNode: return store.nodeToTreeValue(node.Alias, false); } return nil, nil } func (store Store) appendYamlNodeToTreeBranch(node *yaml.Node, branch sops.TreeBranch, commentsWereHandled bool) (sops.TreeBranch, error) { var err error if !commentsWereHandled { branch = store.appendCommentToMap(node.HeadComment, branch) branch = store.appendCommentToMap(node.LineComment, branch) } switch node.Kind { case yaml.DocumentNode: for _, item := range node.Content { branch, err = store.appendYamlNodeToTreeBranch(item, branch, false) if err != nil { return nil, err } } case yaml.SequenceNode: return nil, fmt.Errorf("YAML documents that are sequences are not supported") case yaml.MappingNode: for i := 0; i < len(node.Content); i += 2 { key := node.Content[i] value := node.Content[i + 1] branch = store.appendCommentToMap(key.HeadComment, branch) branch = store.appendCommentToMap(key.LineComment, branch) handleValueComments := value.Kind == yaml.ScalarNode || value.Kind == yaml.AliasNode if handleValueComments { branch = store.appendCommentToMap(value.HeadComment, branch) branch = store.appendCommentToMap(value.LineComment, branch) } var keyValue interface{} key.Decode(&keyValue) valueTV, err := store.nodeToTreeValue(value, handleValueComments) if err != nil { return nil, err } branch = append(branch, sops.TreeItem{ Key: keyValue, Value: valueTV, }) if handleValueComments { branch = store.appendCommentToMap(value.FootComment, branch) } branch = store.appendCommentToMap(key.FootComment, branch) } case yaml.ScalarNode: // A empty document with a document start marker without comments results in null if node.ShortTag() == "!!null" { return branch, nil } return nil, fmt.Errorf("YAML documents that are values are not supported") case yaml.AliasNode: branch, err = store.appendYamlNodeToTreeBranch(node.Alias, branch, false) } if !commentsWereHandled { branch = store.appendCommentToMap(node.FootComment, branch) } return branch, nil } func (store Store) yamlDocumentNodeToTreeBranch(in yaml.Node) (sops.TreeBranch, error) { branch := make(sops.TreeBranch, 0) return store.appendYamlNodeToTreeBranch(&in, branch, false) } func (store *Store) addCommentsHead(node *yaml.Node, comments []string) []string { if len(comments) > 0 { comment := "#" + strings.Join(comments, "\n#") if len(node.HeadComment) > 0 { node.HeadComment = comment + "\n" + node.HeadComment } else { node.HeadComment = comment } } return nil } func (store *Store) addCommentsFoot(node *yaml.Node, comments []string) []string { if len(comments) > 0 { comment := "#" + strings.Join(comments, "\n#") if len(node.FootComment) > 0 { node.FootComment += "\n" + comment } else { node.FootComment = comment } } return nil } func (store *Store) treeValueToNode(in interface{}) *yaml.Node { switch in := in.(type) { case sops.TreeBranch: var mapping = &yaml.Node{} mapping.Kind = yaml.MappingNode store.appendTreeBranch(in, mapping) return mapping case []interface{}: var sequence = &yaml.Node{} sequence.Kind = yaml.SequenceNode store.appendSequence(in, sequence) return sequence default: var valueNode = &yaml.Node{} valueNode.Encode(in) return valueNode } } func (store *Store) appendSequence(in []interface{}, sequence *yaml.Node) { var comments []string var beginning bool = true for _, item := range in { if comment, ok := item.(sops.Comment); ok { comments = append(comments, comment.Value) } else { if beginning { comments = store.addCommentsHead(sequence, comments) beginning = false } itemNode := store.treeValueToNode(item) comments = store.addCommentsHead(itemNode, comments) sequence.Content = append(sequence.Content, itemNode) } } if len(comments) > 0 { if beginning { comments = store.addCommentsHead(sequence, comments) } else { comments = store.addCommentsFoot(sequence.Content[len(sequence.Content) - 1], comments) } } } func (store *Store) appendTreeBranch(branch sops.TreeBranch, mapping *yaml.Node) { var comments []string var beginning bool = true for _, item := range branch { if comment, ok := item.Key.(sops.Comment); ok { comments = append(comments, comment.Value) } else { if beginning { comments = store.addCommentsHead(mapping, comments) beginning = false } var keyNode = &yaml.Node{} keyNode.Encode(item.Key) comments = store.addCommentsHead(keyNode, comments) valueNode := store.treeValueToNode(item.Value) mapping.Content = append(mapping.Content, keyNode, valueNode) } } if len(comments) > 0 { if beginning { comments = store.addCommentsHead(mapping, comments) } else { comments = store.addCommentsFoot(mapping.Content[len(mapping.Content) - 1], comments) } } } // LoadEncryptedFile loads the contents of an encrypted yaml file onto a // sops.Tree runtime object func (store *Store) LoadEncryptedFile(in []byte) (sops.Tree, error) { // Because we don't know what fields the input file will have, we have to // load the file in two steps. // First, we load the file's metadata, the structure of which is known. metadataHolder := stores.SopsFile{} err := yaml.Unmarshal(in, &metadataHolder) if err != nil { return sops.Tree{}, fmt.Errorf("Error unmarshalling input yaml: %s", err) } if metadataHolder.Metadata == nil { return sops.Tree{}, sops.MetadataNotFound } metadata, err := metadataHolder.Metadata.ToInternal() if err != nil { return sops.Tree{}, err } var data yaml.Node if err := yaml.Unmarshal(in, &data); err != nil { return sops.Tree{}, fmt.Errorf("Error unmarshaling input YAML: %s", err) } var branches sops.TreeBranches d := yaml.NewDecoder(bytes.NewReader(in)) for true { var data yaml.Node err := d.Decode(&data) if err == io.EOF { break } if err != nil { return sops.Tree{}, fmt.Errorf("Error unmarshaling input YAML: %s", err) } branch, err := store.yamlDocumentNodeToTreeBranch(data) if err != nil { return sops.Tree{}, fmt.Errorf("Error unmarshaling input YAML: %s", err) } for i, elt := range branch { if elt.Key == "sops" { // Erase branch = append(branch[:i], branch[i+1:]...) } } branches = append(branches, branch) } return sops.Tree{ Branches: branches, Metadata: metadata, }, nil } // LoadPlainFile loads the contents of a plaintext yaml file onto a // sops.Tree runtime object func (store *Store) LoadPlainFile(in []byte) (sops.TreeBranches, error) { var branches sops.TreeBranches d := yaml.NewDecoder(bytes.NewReader(in)) for true { var data yaml.Node err := d.Decode(&data) if err == io.EOF { break } if err != nil { return nil, fmt.Errorf("Error unmarshaling input YAML: %s", err) } branch, err := store.yamlDocumentNodeToTreeBranch(data) if err != nil { return nil, fmt.Errorf("Error unmarshaling input YAML: %s", err) } branches = append(branches, branch) } return branches, nil } // EmitEncryptedFile returns the encrypted bytes of the yaml file corresponding to a // sops.Tree runtime object func (store *Store) EmitEncryptedFile(in sops.Tree) ([]byte, error) { var b bytes.Buffer e := yaml.NewEncoder(io.Writer(&b)) e.SetIndent(4) for _, branch := range in.Branches { // Document root var doc = yaml.Node{} doc.Kind = yaml.DocumentNode // Add global mapping var mapping = yaml.Node{} mapping.Kind = yaml.MappingNode doc.Content = append(doc.Content, &mapping) // Create copy of branch with metadata appended branch = append(sops.TreeBranch(nil), branch...) branch = append(branch, sops.TreeItem{ Key: "sops", Value: stores.MetadataFromInternal(in.Metadata), }) // Marshal branch to global mapping node store.appendTreeBranch(branch, &mapping) // Encode YAML err := e.Encode(&doc) if err != nil { return nil, fmt.Errorf("Error marshaling to yaml: %s", err) } } e.Close() return b.Bytes(), nil } // EmitPlainFile returns the plaintext bytes of the yaml file corresponding to a // sops.TreeBranches runtime object func (store *Store) EmitPlainFile(branches sops.TreeBranches) ([]byte, error) { var b bytes.Buffer e := yaml.NewEncoder(io.Writer(&b)) e.SetIndent(4) for _, branch := range branches { // Document root var doc = yaml.Node{} doc.Kind = yaml.DocumentNode // Add global mapping var mapping = yaml.Node{} mapping.Kind = yaml.MappingNode // Marshal branch to global mapping node store.appendTreeBranch(branch, &mapping) doc.Content = append(doc.Content, &mapping) // Encode YAML err := e.Encode(&doc) if err != nil { return nil, fmt.Errorf("Error marshaling to yaml: %s", err) } } e.Close() return b.Bytes(), nil } // EmitValue returns bytes corresponding to a single encoded value // in a generic interface{} object func (store *Store) EmitValue(v interface{}) ([]byte, error) { n := store.treeValueToNode(v) return yaml.Marshal(n) } // EmitExample returns the bytes corresponding to an example complex tree func (store *Store) EmitExample() []byte { bytes, err := store.EmitPlainFile(stores.ExampleComplexTree.Branches) if err != nil { panic(err) } return bytes } 070701000000AD000081A400000000000000000000000162794F930000170C000000000000000000000000000000000000002500000000sops-3.7.3/stores/yaml/store_test.gopackage yaml import ( "testing" "github.com/stretchr/testify/assert" "go.mozilla.org/sops/v3" ) var PLAIN = []byte(`--- # comment 0 key1: value key1_a: value # ^ comment 1 --- key2: value2`) var PLAIN_0 = []byte(`# comment 0 key1: value key1_a: value # ^ comment 1 `) var BRANCHES = sops.TreeBranches{ sops.TreeBranch{ sops.TreeItem{ Key: sops.Comment{" comment 0"}, Value: nil, }, sops.TreeItem{ Key: "key1", Value: "value", }, sops.TreeItem{ Key: "key1_a", Value: "value", }, sops.TreeItem{ Key: sops.Comment{" ^ comment 1"}, Value: nil, }, }, sops.TreeBranch{ sops.TreeItem{ Key: "key2", Value: "value2", }, }, } var COMMENT_1 = []byte(`# test a: b: null # foo `) var COMMENT_2 = []byte(`a: # foo b: null `) var COMMENT_3_IN = []byte(`## Configuration for prometheus-node-exporter subchart ## prometheus-node-exporter: podLabels: ## Add the 'node-exporter' label to be used by serviceMonitor to match standard common usage in rules and grafana dashboards ## jobLabel: node-exporter extraArgs: - --collector.filesystem.ignored-mount-points=^/(dev|proc|sys|var/lib/docker/.+)($|/) - --collector.filesystem.ignored-fs-types=^(autofs|binfmt_misc|cgroup|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|mqueue|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|sysfs|tracefs)$ `) var COMMENT_3_OUT = []byte(`## Configuration for prometheus-node-exporter subchart ## prometheus-node-exporter: podLabels: ## Add the 'node-exporter' label to be used by serviceMonitor to match standard common usage in rules and grafana dashboards ## jobLabel: node-exporter extraArgs: - --collector.filesystem.ignored-mount-points=^/(dev|proc|sys|var/lib/docker/.+)($|/) - --collector.filesystem.ignored-fs-types=^(autofs|binfmt_misc|cgroup|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|mqueue|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|sysfs|tracefs)$ `) var COMMENT_4 = []byte(`# foo `) var COMMENT_5 = []byte(`# foo --- key: value `) // The following is a regression test for https://github.com/mozilla/sops/issues/865 var COMMENT_6 = []byte(`a: - a # I no longer get duplicated - {} `) var COMMENT_6_BRANCHES = sops.TreeBranches{ sops.TreeBranch{ sops.TreeItem{ Key: "a", Value: []interface{}{ "a", sops.Comment{" I no longer get duplicated"}, sops.TreeBranch{}, }, }, }, } func TestUnmarshalMetadataFromNonSOPSFile(t *testing.T) { data := []byte(`hello: 2`) _, err := (&Store{}).LoadEncryptedFile(data) assert.Equal(t, sops.MetadataNotFound, err) } func TestLoadPlainFile(t *testing.T) { branches, err := (&Store{}).LoadPlainFile(PLAIN) assert.Nil(t, err) assert.Equal(t, BRANCHES, branches) } func TestComment1(t *testing.T) { // First iteration: load and store branches, err := (&Store{}).LoadPlainFile(COMMENT_1) assert.Nil(t, err) bytes, err := (&Store{}).EmitPlainFile(branches) assert.Nil(t, err) assert.Equal(t, string(COMMENT_1), string(bytes)) assert.Equal(t, COMMENT_1, bytes) } func TestComment2(t *testing.T) { // First iteration: load and store branches, err := (&Store{}).LoadPlainFile(COMMENT_2) assert.Nil(t, err) bytes, err := (&Store{}).EmitPlainFile(branches) assert.Nil(t, err) assert.Equal(t, string(COMMENT_2), string(bytes)) assert.Equal(t, COMMENT_2, bytes) } func TestComment3(t *testing.T) { // First iteration: load and store branches, err := (&Store{}).LoadPlainFile(COMMENT_3_IN) assert.Nil(t, err) bytes, err := (&Store{}).EmitPlainFile(branches) assert.Nil(t, err) assert.Equal(t, string(COMMENT_3_OUT), string(bytes)) assert.Equal(t, COMMENT_3_OUT, bytes) } /* TODO: re-enable once https://github.com/go-yaml/yaml/pull/690 is merged func TestComment4(t *testing.T) { // First iteration: load and store branches, err := (&Store{}).LoadPlainFile(COMMENT_4) assert.Nil(t, err) bytes, err := (&Store{}).EmitPlainFile(branches) assert.Nil(t, err) assert.Equal(t, string(COMMENT_4), string(bytes)) assert.Equal(t, COMMENT_4, bytes) } func TestComment5(t *testing.T) { // First iteration: load and store branches, err := (&Store{}).LoadPlainFile(COMMENT_5) assert.Nil(t, err) bytes, err := (&Store{}).EmitPlainFile(branches) assert.Nil(t, err) assert.Equal(t, string(COMMENT_5), string(bytes)) assert.Equal(t, COMMENT_5, bytes) } */ func TestEmpty(t *testing.T) { // First iteration: load and store branches, err := (&Store{}).LoadPlainFile([]byte(``)) assert.Nil(t, err) assert.Equal(t, len(branches), 0) bytes, err := (&Store{}).EmitPlainFile(branches) assert.Nil(t, err) assert.Equal(t, ``, string(bytes)) } /* TODO: re-enable once https://github.com/go-yaml/yaml/pull/690 is merged func TestEmpty2(t *testing.T) { // First iteration: load and store branches, err := (&Store{}).LoadPlainFile([]byte(`---`)) assert.Nil(t, err) assert.Equal(t, len(branches), 1) assert.Equal(t, len(branches[0]), 0) bytes, err := (&Store{}).EmitPlainFile(branches) assert.Nil(t, err) assert.Equal(t, ``, string(bytes)) } */ func TestEmpty3(t *testing.T) { branches, err := (&Store{}).LoadPlainFile([]byte("{}\n")) assert.Nil(t, err) assert.Equal(t, len(branches), 1) bytes, err := (&Store{}).EmitPlainFile(branches) assert.Nil(t, err) assert.Equal(t, "{}\n", string(bytes)) } func TestComment6(t *testing.T) { branches, err := (&Store{}).LoadPlainFile(COMMENT_6) assert.Nil(t, err) assert.Equal(t, COMMENT_6_BRANCHES, branches) bytes, err := (&Store{}).EmitPlainFile(branches) assert.Nil(t, err) assert.Equal(t, string(COMMENT_6), string(bytes)) assert.Equal(t, COMMENT_6, bytes) } func TestEmitValue(t *testing.T) { // First iteration: load and store bytes, err := (&Store{}).EmitValue(BRANCHES[0]) assert.Nil(t, err) assert.Equal(t, string(PLAIN_0), string(bytes)) assert.Equal(t, PLAIN_0, bytes) } 070701000000AE000081ED00000000000000000000000162794F930000016D000000000000000000000000000000000000001300000000sops-3.7.3/test.sh#!/usr/bin/env bash set -e echo "" > coverage.txt failed=0 for d in $(go list ./... | grep -v vendor); do go test -race -coverprofile=profile.out -covermode=atomic $d && true rc=$? if [ $rc != 0 ]; then failed=$rc fi if [ -f profile.out ]; then cat profile.out >> coverage.txt rm profile.out fi done exit ${failed} 070701000000AF000081A400000000000000000000000162794F930000115E000000000000000000000000000000000000001900000000sops-3.7.3/usererrors.gopackage sops import ( "fmt" "io/ioutil" "strings" "github.com/fatih/color" "github.com/goware/prefixer" wordwrap "github.com/mitchellh/go-wordwrap" ) // UserError is a well-formatted error for the purpose of being displayed to // the end user. type UserError interface { error UserError() string } var statusSuccess = color.New(color.FgGreen).Sprint("SUCCESS") var statusFailed = color.New(color.FgRed).Sprint("FAILED") type getDataKeyError struct { RequiredSuccessfulKeyGroups int GroupResults []error } func (err *getDataKeyError) successfulKeyGroups() int { n := 0 for _, r := range err.GroupResults { if r == nil { n++ } } return n } func (err *getDataKeyError) Error() string { return fmt.Sprintf("Error getting data key: %d successful groups "+ "required, got %d", err.RequiredSuccessfulKeyGroups, err.successfulKeyGroups()) } func (err *getDataKeyError) UserError() string { var groupErrs []string for i, res := range err.GroupResults { groupErr := decryptGroupError{ err: res, groupName: fmt.Sprintf("%d", i), } groupErrs = append(groupErrs, groupErr.UserError()) } var trailer string if err.RequiredSuccessfulKeyGroups == 0 { trailer = "Recovery failed because no master key was able to decrypt " + "the file. In order for SOPS to recover the file, at least one key " + "has to be successful, but none were." } else { trailer = fmt.Sprintf("Recovery failed because the file was "+ "encrypted with a Shamir threshold of %d, but only %d part(s) "+ "were successfully recovered, one for each successful key group. "+ "In order for SOPS to recover the file, at least %d groups have "+ "to be successful. In order for a group to be successful, "+ "decryption has to succeed with any of the keys in that key group.", err.RequiredSuccessfulKeyGroups, err.successfulKeyGroups(), err.RequiredSuccessfulKeyGroups) } trailer = wordwrap.WrapString(trailer, 75) return fmt.Sprintf("Failed to get the data key required to "+ "decrypt the SOPS file.\n\n%s\n\n%s", strings.Join(groupErrs, "\n\n"), trailer) } type decryptGroupError struct { groupName string err error } func (r *decryptGroupError) Error() string { return fmt.Sprintf("could not decrypt group %s: %s", r.groupName, r.err) } func (r *decryptGroupError) UserError() string { var status string if r.err == nil { status = statusSuccess } else { status = statusFailed } header := fmt.Sprintf(`Group %s: %s`, r.groupName, status) if r.err == nil { return header } message := r.err.Error() if userError, ok := r.err.(UserError); ok { message = userError.UserError() } reader := prefixer.New(strings.NewReader(message), " ") // Safe to ignore this error, as reading from a strings.Reader can't fail errMsg, _ := ioutil.ReadAll(reader) return fmt.Sprintf("%s\n%s", header, string(errMsg)) } type decryptKeyErrors []error func (e decryptKeyErrors) Error() string { return fmt.Sprintf("error decrypting key: %s", []error(e)) } func (e decryptKeyErrors) UserError() string { var errStrs []string for _, err := range []error(e) { if userErr, ok := err.(UserError); ok { errStrs = append(errStrs, userErr.UserError()) } else { errStrs = append(errStrs, err.Error()) } } return strings.Join(errStrs, "\n\n") } type decryptKeyError struct { keyName string errs []error } func (e *decryptKeyError) isSuccessful() bool { for _, err := range e.errs { if err == nil { return true } } return false } func (e *decryptKeyError) Error() string { return fmt.Sprintf("error decrypting key %s: %s", e.keyName, e.errs) } func (e *decryptKeyError) UserError() string { var status string if e.isSuccessful() { status = statusSuccess } else { status = statusFailed } header := fmt.Sprintf("%s: %s", e.keyName, status) if e.isSuccessful() { return header } var errMessages []string for _, err := range e.errs { wrappedErr := wordwrap.WrapString(err.Error(), 60) reader := prefixer.New(strings.NewReader(wrappedErr), " | ") // Safe to ignore this error, as reading from a strings.Reader can't fail errMsg, _ := ioutil.ReadAll(reader) errMsg[0] = '-' errMessages = append(errMessages, string(errMsg)) } joinedMsgs := strings.Join(errMessages, "\n\n") reader := prefixer.New(strings.NewReader(joinedMsgs), " ") errMsg, _ := ioutil.ReadAll(reader) return fmt.Sprintf("%s\n%s", header, string(errMsg)) } 070701000000B0000041ED00000000000000000000000362794F9300000000000000000000000000000000000000000000001600000000sops-3.7.3/validation070701000000B1000081A400000000000000000000000162794F93000000A0000000000000000000000000000000000000002100000000sops-3.7.3/validation/Cargo.toml[package] name = "validation" version = "0.1.0" authors = ["Adrian Utrilla <adrianutrilla@gmail.com>"] [dependencies] serde_json = "0.8.0" yaml-rust = "0.3.3" 070701000000B2000081A400000000000000000000000162794F9300000314000000000000000000000000000000000000002300000000sops-3.7.3/validation/example.json{ "glossary": { "title": "example glossary", "GlossDiv": { "title": "S", "GlossList": { "GlossEntry": { "ID": "SGML", "SortAs": "SGML", "GlossTerm": "Standard Generalized Markup Language", "Acronym": "SGML", "Abbrev": "ISO 8879:1986", "GlossDef": { "para": "A meta-markup language, used to create markup languages such as DocBook.", "GlossSeeAlso": [ "GML", "XML" ] }, "GlossSee": "markup" } } } } } 070701000000B3000081A400000000000000000000000162794F93000000B7000000000000000000000000000000000000002300000000sops-3.7.3/validation/example.yamlname: Martin job: Developer skill: Elite foods: - Apple - Orange - Strawberry - Mango languages: perl: Elite python: Elite pascal: Lame foo: | bar baz 070701000000B4000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001A00000000sops-3.7.3/validation/src070701000000B5000081A400000000000000000000000162794F9300000E76000000000000000000000000000000000000002200000000sops-3.7.3/validation/src/main.rsuse std::rc::Rc; use std::process::{Stdio, Command}; use std::io::{Write, Read}; use std::fs::{OpenOptions, File}; use std::str; use std::env; extern crate serde_json; use serde_json::Value; extern crate yaml_rust; use yaml_rust::{YamlLoader, YamlEmitter}; fn main() {} fn run_sops_and_return_output(command: &mut Command, filename: &str) -> String { let mut child = command.stdout(Stdio::piped()) .arg(filename) .spawn() .expect("Could not start sops python process"); let output = child.wait_with_output().expect("Could not retrieve sops's output"); if !output.status.success() { panic!("sops did not exit successfully!"); } return String::from_utf8(output.stdout).expect("Could not decode sops's output as utf-8"); } fn get_sops_python() -> Command { let sops_python_path = env::var("SOPS_PYTHON_PATH") .expect("SOPS_PYTHON_PATH environment variable missing"); let mut cmd = Command::new("python"); cmd.arg(sops_python_path); cmd } fn encrypt_with_sops_python(plaintext: &str) -> String { let mut child = get_sops_python(); let child = child.arg("-e"); return run_sops_and_return_output(child, plaintext); } fn decrypt_with_sops_python(ciphertext: &str) -> String { let mut child = get_sops_python(); let child = child.arg("-d"); return run_sops_and_return_output(child, ciphertext); } fn validate_json_file(input_file_name: &str, encrypt: fn(&str) -> String, decrypt: fn(&str) -> String) { let output_file_name = "temp.json"; let mut input = String::new(); File::open(input_file_name).unwrap().read_to_string(&mut input); let input_value: Value = serde_json::from_str(&input).expect("Could not decode input json"); let encrypted_output = encrypt(input_file_name); let mut output_file = OpenOptions::new() .write(true) .create(true) .open(output_file_name) .expect("Could not open output file"); output_file.write_all(encrypted_output.as_bytes()).expect("Could not write to output file"); let decryption = decrypt(output_file_name); let output_value: Value = serde_json::from_str(&decryption).unwrap(); std::fs::remove_file(output_file_name).expect("Could not remove output file"); assert_eq!(input_value, output_value); } fn validate_yaml_file(input_file_name: &str, encrypt: fn(&str) -> String, decrypt: fn(&str) -> String) { let output_file_name = "temp.yaml"; let mut input = String::new(); File::open(input_file_name).unwrap().read_to_string(&mut input); let input_value = YamlLoader::load_from_str(&input).expect("Could not decode input yaml"); let encrypted_output = encrypt(input_file_name); let mut output_file = OpenOptions::new() .write(true) .create(true) .open(output_file_name) .expect("Could not open output file"); output_file.write_all(encrypted_output.as_bytes()).expect("Could not write to output file"); let decryption = decrypt(output_file_name); let output_value = YamlLoader::load_from_str(&decryption) .expect("Could not decode output yaml"); std::fs::remove_file(output_file_name).expect("Could not remove output file"); assert_eq!(input_value, output_value); } #[test] fn validate_python_json() { validate_json_file("example.json", encrypt_with_sops_python, decrypt_with_sops_python); } #[test] fn validate_python_yaml() { validate_yaml_file("example.yaml", encrypt_with_sops_python, decrypt_with_sops_python); } 070701000000B6000041ED00000000000000000000000262794F9300000000000000000000000000000000000000000000001300000000sops-3.7.3/version070701000000B7000081A400000000000000000000000162794F93000009AF000000000000000000000000000000000000001E00000000sops-3.7.3/version/version.gopackage version import ( "bufio" "fmt" "net/http" "strings" "github.com/blang/semver" "gopkg.in/urfave/cli.v1" ) // Version represents the value of the current semantic version const Version = "3.7.3" // PrintVersion handles the version command for sops func PrintVersion(c *cli.Context) { out := fmt.Sprintf("%s %s", c.App.Name, c.App.Version) upstreamVersion, err := RetrieveLatestVersionFromUpstream() if err != nil { out += fmt.Sprintf("\n[warning] failed to retrieve latest version from upstream: %v\n", err) } outdated, err := AIsNewerThanB(upstreamVersion, Version) if err != nil { out += fmt.Sprintf("\n[warning] failed to compare current version with latest: %v\n", err) } if outdated { out += fmt.Sprintf("\n[info] sops %s is available, update with `go get -u go.mozilla.org/sops/v3/cmd/sops`\n", upstreamVersion) } else { out += " (latest)\n" } fmt.Fprintf(c.App.Writer, "%s", out) } // AIsNewerThanB takes 2 semver strings are returns true // is the A is newer than B, false otherwise func AIsNewerThanB(A, B string) (bool, error) { if strings.HasPrefix(B, "1.") { // sops 1.0 doesn't use the semver format, which will // fail the call to `make` below. Since we now we're // more recent than 1.X anyway, return true right away return true, nil } vA, err := semver.Make(A) if err != nil { return false, err } vB, err := semver.Make(B) if err != nil { return false, err } if vA.Compare(vB) > 0 { // vA is newer than vB return true, nil } return false, nil } // RetrieveLatestVersionFromUpstream gets the latest version from the source code at Github func RetrieveLatestVersionFromUpstream() (string, error) { resp, err := http.Get("https://raw.githubusercontent.com/mozilla/sops/master/version/version.go") if err != nil { return "", err } defer resp.Body.Close() scanner := bufio.NewScanner(resp.Body) for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, `const Version = "`) { comps := strings.Split(line, `"`) if len(comps) < 2 { return "", fmt.Errorf("Failed to parse version from upstream source") } // try to parse the version as semver _, err := semver.Make(comps[1]) if err != nil { return "", fmt.Errorf("Retrieved version %q does not match semver format: %w", comps[1], err) } return comps[1], nil } } if err := scanner.Err(); err != nil { return "", err } return "", fmt.Errorf("Version information not found in upstream file") } 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!1674 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor