1
0
mirror of https://github.com/getsops/sops.git synced 2026-02-05 12:45:21 +01:00

Merge branch 'main' into cg/minimum-sops-config

This commit is contained in:
Charlie Getzen
2025-03-27 23:04:20 -07:00
committed by GitHub
63 changed files with 4372 additions and 1487 deletions

View File

@@ -2,7 +2,7 @@ version: 2
updates:
- package-ecosystem: "docker"
directory: "/"
directory: "/.release"
labels: ["dependencies"]
schedule:
# By default, this will be on a Monday.
@@ -39,3 +39,16 @@ updates:
go:
patterns:
- "*"
- package-ecosystem: "cargo"
directory: "/functional-tests"
labels: ["area/CI", "dependencies"]
schedule:
# By default, this will be on a Monday.
interval: "weekly"
groups:
# Group all updates together, so that they are all applied in a single PR.
# xref: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#groups
rust:
patterns:
- "*"

77
.github/utils/patch-go.mod.py vendored Normal file
View File

@@ -0,0 +1,77 @@
"""
Patch go.mod so that the lines 'go xxx' to 'toolchain xxx' are as in git's
HEAD.
This is necessary since newer 'go mod tidy' versions tend to modify these
lines. Since we check in CI that 'go mod tidy' does not change go.mod, this
causes CI to fail.
"""
import subprocess
def split_go_mod(contents: str) -> tuple[list[str], list[str], list[str]]:
"""
Given the contents of go.mod, splits it into three lists of lines
(with endings):
1. The lines before 'go';
2. The lines starting with 'go' and ending with 'toolchain';
3. The lines after 'toolchain'.
"""
parts: tuple[list[str], list[str], list[str]] = ([], [], [])
index = 0
for line in contents.splitlines(keepends=True):
next_index = index
if line.startswith('go '):
index = next_index = 1
if line.startswith('toolchain '):
next_index = 2
parts[index].append(line)
index = next_index
return parts
def get_file_contents_from_git_revision(filename: str, revision: str) -> str:
"""
Get the file contents of ``filename`` from Git revision ``revision``.
"""
p = subprocess.run(
['git', 'show', f'{revision}:{filename}'],
stdout=subprocess.PIPE,
check=True,
encoding='utf-8',
)
return p.stdout
def read_file(filename: str) -> str:
"""
Read the file's contents.
"""
with open(filename, 'r', encoding='utf-8') as f:
return f.read()
def write_file(filename: str, contents: str) -> None:
"""
Write the file's contents.
"""
with open(filename, 'w', encoding='utf-8') as f:
f.write(contents)
def main():
"""
Patches go.mod.
"""
filename = 'go.mod'
_, go_versions, __ = split_go_mod(
get_file_contents_from_git_revision(filename, 'HEAD')
)
head, _, tail = split_go_mod(read_file(filename))
lines = head + go_versions + tail
write_file(filename, ''.join(lines))
if __name__ == '__main__':
main()

View File

@@ -13,30 +13,33 @@ permissions:
jobs:
build:
name: Build and test ${{ matrix.os }} ${{ matrix.arch }}
name: Build and test ${{ matrix.os }} ${{ matrix.arch }} ${{ matrix.go-version }}
runs-on: ubuntu-latest
strategy:
matrix:
os: [linux, darwin, windows]
arch: [amd64, arm64]
go-version: ['1.22', '1.23']
exclude:
- os: windows
arch: arm64
env:
VAULT_VERSION: "1.1.3"
VAULT_VERSION: "1.14.0"
VAULT_TOKEN: "root"
VAULT_ADDR: "http://127.0.0.1:8200"
steps:
- name: Set up Go 1.21
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
with:
go-version: '1.21'
go-version: ${{ matrix.go-version }}
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
@@ -46,16 +49,19 @@ jobs:
- name: Vendor Go Modules
run: make vendor
- name: Restore go/toolchain lines of go.mod
run: python3 .github/utils/patch-go.mod.py
- name: Ensure clean working tree
run: git diff --exit-code
- name: Build Linux and Darwin
- name: Build ${{ matrix.os }}
if: matrix.os != 'windows'
run: GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} go build -o sops-${{ matrix.os }}-${{ matrix.arch }}-${{ github.sha }} -v ./cmd/sops
run: GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} go build -o sops-${{ matrix.go-version }}-${{ matrix.os }}-${{ matrix.arch }}-${{ github.sha }} -v ./cmd/sops
- name: Build Windows
- name: Build ${{ matrix.os }}
if: matrix.os == 'windows'
run: GOOS=${{ matrix.os }} go build -o sops-${{ matrix.os }}-${{ github.sha }} -v ./cmd/sops
run: GOOS=${{ matrix.os }} go build -o sops-${{ matrix.go-version }}-${{ 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
@@ -63,40 +69,51 @@ jobs:
- name: Test
run: make test
- name: Upload artifact for Linux and Darwin
- name: Upload artifact for ${{ matrix.os }}
if: matrix.os != 'windows'
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: sops-${{ matrix.os }}-${{ matrix.arch }}-${{ github.sha }}
path: sops-${{ matrix.os }}-${{ matrix.arch }}-${{ github.sha }}
name: sops-${{ matrix.go-version }}-${{ matrix.os }}-${{ matrix.arch }}-${{ github.sha }}
path: sops-${{ matrix.go-version }}-${{ matrix.os }}-${{ matrix.arch }}-${{ github.sha }}
- name: Upload artifact for Windows
- name: Upload artifact for ${{ matrix.os }}
if: matrix.os == 'windows'
uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: sops-${{ matrix.os }}-${{ github.sha }}
path: sops-${{ matrix.os }}-${{ github.sha }}
name: sops-${{ matrix.go-version }}-${{ matrix.os }}-${{ github.sha }}
path: sops-${{ matrix.go-version }}-${{ matrix.os }}-${{ github.sha }}
test:
name: Functional tests
runs-on: ubuntu-latest
needs: [build]
strategy:
matrix:
go-version: ['1.22']
env:
VAULT_VERSION: "1.1.3"
VAULT_VERSION: "1.14.0"
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.70.0
- name: Check out code
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
name: sops-linux-amd64-${{ github.sha }}
persist-credentials: false
# Rustup will detect toolchain version and profile from rust-toolchain.toml
# It will download and install the toolchain and components automatically
# and make them available for subsequent commands
- name: Install Rust toolchain
run: rustup show
- name: Show Rust version
run: cargo --version
- uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1
with:
name: sops-${{ matrix.go-version }}-linux-amd64-${{ github.sha }}
- name: Move SOPS binary
run: mv sops-linux-amd64-${{ github.sha }} ./functional-tests/sops
run: mv sops-${{ matrix.go-version }}-linux-amd64-${{ github.sha }} ./functional-tests/sops
- name: Make SOPS binary executable
run: chmod +x ./functional-tests/sops
@@ -116,3 +133,22 @@ jobs:
- name: Run tests
run: cargo test
working-directory: ./functional-tests
# The 'check' job should depend on all other jobs so it's possible to configure branch protection only for 'check'
# instead of having to explicitly list all individual jobs that need to pass.
check:
if: always()
needs:
- build
- test
runs-on: ubuntu-latest
steps:
- name: Decide whether the needed jobs succeeded or failed
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2
with:
allowed-failures: docs, linters
allowed-skips: non-voting-flaky-job
jobs: ${{ toJSON(needs) }}

View File

@@ -29,11 +29,13 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15
uses: github/codeql-action/init@5f8171a638ada777af81d42b55959a643bb29017 # v3.28.12
with:
languages: go
# xref: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
@@ -45,9 +47,11 @@ jobs:
# target, which includes a lot more than just the Go files we want to
# scan.
- name: Build
run: make install
run: |
make vendor
make install
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@afb54ba388a7dca6ecae48f608c4ff05ff4cc77a # v3.25.15
uses: github/codeql-action/analyze@5f8171a638ada777af81d42b55959a643bb29017 # v3.28.12
with:
category: "/language:go"

View File

@@ -23,7 +23,9 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.0.0
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Install rstcheck and markdownlint
run: |

39
.github/workflows/linters.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: Linters
on:
push:
branches:
- main
pull_request:
branches:
- main
# Only run when Rust version or linted files change
paths:
- 'rust-toolchain.toml'
- 'functional-tests/**/*.rs'
permissions:
contents: read
jobs:
lint:
name: Lint Rust source files
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
# Rustup will detect toolchain version and profile from rust-toolchain.toml
# It will download and install the toolchain and components automatically
# and make them available for subsequent commands
- name: Install Rust toolchain and additional components
run: rustup component add rustfmt
- name: Show Rust version
run: cargo --version
- name: Run Formatting Check
run: cargo fmt --check
working-directory: ./functional-tests

View File

@@ -25,37 +25,38 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Go
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v4.0.1
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v4.0.1
with:
go-version: 1.21.x
go-version-file: go.mod
cache: false
- name: Setup Syft
uses: anchore/sbom-action/download-syft@d94f46e13c6c62f59525ac9a1e147a99dc0b9bf5 # v0.17.0
uses: anchore/sbom-action/download-syft@f325610c9f50a54015d37c8d16cb3b0e2c8f4de0 # v0.18.0
- name: Setup Cosign
uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0
uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a # v3.8.1
- name: Setup QEMU
uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@aa33708b10e362ff993539393ff100fa93ed6a27 # v3.5.0
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Login to GitHub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Quay.io
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
registry: quay.io
username: ${{ secrets.QUAY_BOT_USERNAME }}
@@ -63,7 +64,7 @@ jobs:
- name: Run GoReleaser
id: goreleaser
uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0
uses: goreleaser/goreleaser-action@90a3faa9d0182683851fbfa97ca1a2cb983bfca3 # v6.2.1
with:
# Note that the following is the version of goreleaser, and NOT a Go version!
# When bumping it, make sure to check out goreleaser's changelog first!
@@ -168,7 +169,7 @@ jobs:
id-token: write # For creating OIDC tokens for signing.
contents: write # For adding assets to a release.
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
with:
base64-subjects: "${{ needs.combine-subjects.outputs.all-subjects }}"
upload-assets: true
@@ -185,7 +186,7 @@ jobs:
strategy:
matrix: ${{ fromJSON(needs.release.outputs.container-subjects) }}
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
with:
image: ghcr.io/${{ matrix.image }}
digest: ${{ matrix.digest }}
@@ -204,7 +205,7 @@ jobs:
strategy:
matrix: ${{ fromJSON(needs.release.outputs.container-subjects) }}
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
with:
image: quay.io/${{ matrix.image }}
digest: ${{ matrix.digest }}

1
.gitignore vendored
View File

@@ -1,6 +1,5 @@
bin/
dist/
functional-tests/sops
Cargo.lock
vendor/
profile.out

View File

@@ -69,6 +69,7 @@ builds:
- windows
goarch:
- amd64
- arm64
# Modified timestamp on the binary, set to ensure reproducible builds.
mod_timestamp: "{{ .CommitTimestamp }}"

View File

@@ -1,4 +1,4 @@
FROM alpine:3.18
FROM alpine:3.21
RUN apk --no-cache add \
ca-certificates \

620
CHANGELOG.md Normal file
View File

@@ -0,0 +1,620 @@
# Changelog
## 3.9.4
Improvements:
* Dependency updates ([#1727](https://github.com/getsops/sops/pull/1727), [#1732](https://github.com/getsops/sops/pull/1732),
[#1734](https://github.com/getsops/sops/pull/1734), [#1739](https://github.com/getsops/sops/pull/1739)).
Bugfixes:
* Prevent key deduplication to identify different AWS KMS keys that only differ by
role, context, or profile ([#1733](https://github.com/getsops/sops/pull/1733)).
* Update part of Azure SDK which prevented decryption in some cases ([#1695](https://github.com/getsops/sops/issue/1695),
[#1734](https://github.com/getsops/sops/pull/1734)).
Project changes:
* CI dependency updates ([#1730](https://github.com/getsops/sops/pull/1730), [#1738](https://github.com/getsops/sops/pull/1738)).
* Rust dependency updates ([#1728](https://github.com/getsops/sops/pull/1728), [#1731](https://github.com/getsops/sops/pull/1731),
[#1735](https://github.com/getsops/sops/pull/1735)).
## 3.9.3
Improvements:
* Dependency updates ([#1699](https://github.com/getsops/sops/pull/1699), [#1703](https://github.com/getsops/sops/pull/1703),
[#1710](https://github.com/getsops/sops/pull/1710), [#1714](https://github.com/getsops/sops/pull/1714),
[#1715](https://github.com/getsops/sops/pull/1715), [#1723](https://github.com/getsops/sops/pull/1723)).
* Add `persist-credentials: false` to checkouts in GitHub workflows ([#1704](https://github.com/getsops/sops/pull/1704)).
* Tests: use container images from
[https://github.com/getsops/ci-container-images](https://github.com/getsops/ci-container-images)
([#1722](https://github.com/getsops/sops/pull/1722)).
Bugfixes:
* GnuPG: do not incorrectly trim fingerprint in presence of exclamation
marks for specfic subkey selection ([#1720](https://github.com/getsops/sops/pull/1720)).
* `updatekeys` subcommand: fix `--input-type` CLI flag being ignored ([#1721](https://github.com/getsops/sops/pull/1721)).
Project changes:
* CI dependency updates ([#1698](https://github.com/getsops/sops/pull/1698), [#1708](https://github.com/getsops/sops/pull/1708),
[#1717](https://github.com/getsops/sops/pull/1717)).
* Rust dependency updates ([#1707](https://github.com/getsops/sops/pull/1707), [#1716](https://github.com/getsops/sops/pull/1716),
[#1725](https://github.com/getsops/sops/pull/1725)).
## 3.9.2
Improvements:
* Dependency updates ([#1645](https://github.com/getsops/sops/pull/1645), [#1649](https://github.com/getsops/sops/pull/1649),
[#1653](https://github.com/getsops/sops/pull/1653), [#1662](https://github.com/getsops/sops/pull/1662),
[#1686](https://github.com/getsops/sops/pull/1686), [#1693](https://github.com/getsops/sops/pull/1693)).
* Update compiled Protobuf definitions ([#1688](https://github.com/getsops/sops/pull/1688)).
* Remove unused variables and simplify conditional (##1687).
Bugfixes:
* Handle whitespace in Azure Key Vault URLs ([#1652](https://github.com/getsops/sops/pull/1652)).
* Correctly handle comments during JSON serialization ([#1647](https://github.com/getsops/sops/pull/1647)).
Project changes:
* CI dependency updates ([#1644](https://github.com/getsops/sops/pull/1644), [#1648](https://github.com/getsops/sops/pull/1648),
[#1654](https://github.com/getsops/sops/pull/1654), [#1664](https://github.com/getsops/sops/pull/1664),
[#1673](https://github.com/getsops/sops/pull/1673), [#1677](https://github.com/getsops/sops/pull/1677),
[#1685](https://github.com/getsops/sops/pull/1685)).
* Rust dependency updates ([#1655](https://github.com/getsops/sops/pull/1655), [#1663](https://github.com/getsops/sops/pull/1663),
[#1670](https://github.com/getsops/sops/pull/1670), [#1676](https://github.com/getsops/sops/pull/1676),
[#1689](https://github.com/getsops/sops/pull/1689)).
* Update and improve Protobuf code generation ([#1688](https://github.com/getsops/sops/pull/1688)).
## 3.9.1
Improvements:
* Dependency updates ([#1550](https://github.com/getsops/sops/pull/1550), [#1554](https://github.com/getsops/sops/pull/1554),
[#1558](https://github.com/getsops/sops/pull/1558), [#1562](https://github.com/getsops/sops/pull/1562),
[#1565](https://github.com/getsops/sops/pull/1565), [#1568](https://github.com/getsops/sops/pull/1568),
[#1575](https://github.com/getsops/sops/pull/1575), [#1581](https://github.com/getsops/sops/pull/1581),
[#1589](https://github.com/getsops/sops/pull/1589), [#1593](https://github.com/getsops/sops/pull/1593),
[#1602](https://github.com/getsops/sops/pull/1602), [#1603](https://github.com/getsops/sops/pull/1603),
[#1618](https://github.com/getsops/sops/pull/1618), [#1629](https://github.com/getsops/sops/pull/1629),
[#1635](https://github.com/getsops/sops/pull/1635), [#1639](https://github.com/getsops/sops/pull/1639),
[#1640](https://github.com/getsops/sops/pull/1640)).
* Clarify naming of the configuration file in the documentation ([#1569](https://github.com/getsops/sops/pull/1569)).
* Build with Go 1.22 ([#1589](https://github.com/getsops/sops/pull/1589)).
* Specify filename of missing file in error messages ([#1625](https://github.com/getsops/sops/pull/1625)).
* `updatekeys` subcommand: show changes in `shamir_threshold` ([#1609](https://github.com/getsops/sops/pull/1609)).
Bugfixes:
* Fix the URL used for determining the latest SOPS version ([#1553](https://github.com/getsops/sops/pull/1553)).
* `updatekeys` subcommand: actually use option
`--shamir-secret-sharing-threshold` ([#1608](https://github.com/getsops/sops/pull/1608)).
* Fix `--config` being ignored in subcommands by `loadConfig` ([#1613](https://github.com/getsops/sops/pull/1613)).
* Allow `edit` subcommand to create files ([#1596](https://github.com/getsops/sops/pull/1596)).
* Do not encrypt if a key group is empty, or there are no key groups ([#1600](https://github.com/getsops/sops/pull/1600)).
* Do not ignore config errors when trying to parse a config file ([#1614](https://github.com/getsops/sops/pull/1614)).
Project changes:
* CI dependency updates ([#1551](https://github.com/getsops/sops/pull/1551), [#1555](https://github.com/getsops/sops/pull/1555),
[#1559](https://github.com/getsops/sops/pull/1559), [#1564](https://github.com/getsops/sops/pull/1564),
[#1566](https://github.com/getsops/sops/pull/1566), [#1574](https://github.com/getsops/sops/pull/1574),
[#1584](https://github.com/getsops/sops/pull/1584), [#1586](https://github.com/getsops/sops/pull/1586),
[#1590](https://github.com/getsops/sops/pull/1590), [#1592](https://github.com/getsops/sops/pull/1592),
[#1619](https://github.com/getsops/sops/pull/1619), [#1628](https://github.com/getsops/sops/pull/1628),
[#1634](https://github.com/getsops/sops/pull/1634)).
* Improve CI workflows ([#1548](https://github.com/getsops/sops/pull/1548), [#1630](https://github.com/getsops/sops/pull/1630)).
* Ignore user-set environment variable `SOPS_AGE_KEY_FILE` in tests ([#1595](https://github.com/getsops/sops/pull/1595)).
* Add example of using Age recipients in `.sops.yaml` ([#1607](https://github.com/getsops/sops/pull/1607)).
* Add linting check for Rust code formatting ([#1604](https://github.com/getsops/sops/pull/1604)).
* Set Rust version globally via `rust-toolchain.toml` for functional tests ([#1612](https://github.com/getsops/sops/pull/1612)).
* Improve test coverage ([#1617](https://github.com/getsops/sops/pull/1617)).
* Improve tests ([#1622](https://github.com/getsops/sops/pull/1622), [#1624](https://github.com/getsops/sops/pull/1624)).
* Simplify branch rules to check DCO and `check` task instead of an explicit
list of tasks in the CLI workflow ([#1621](https://github.com/getsops/sops/pull/1621)).
* Build with Go 1.22 and 1.23 in CI and update Vault to 1.14 ([#1531](https://github.com/getsops/sops/pull/1531)).
* Build release with Go 1.22 ([#1615](https://github.com/getsops/sops/pull/1615)).
* Fix Dependabot config for Docker; add Dependabot config for Rust ([#1632](https://github.com/getsops/sops/pull/1632)).
* Lock Rust package versions for functional tests for improved
reproducibility ([#1637](https://github.com/getsops/sops/pull/1637)).
* Rust dependency updates ([#1638](https://github.com/getsops/sops/pull/1638)).
## 3.9.0
Features:
* Add `--mac-only-encrypted` to compute MAC only over values which
end up encrypted ([#973](https://github.com/getsops/sops/pull/973))
* Allow configuration of indentation for YAML and JSON stores ([#1273](https://github.com/getsops/sops/pull/1273),
[#1372](https://github.com/getsops/sops/pull/1372))
* Introduce a `--pristine` flag to `sops exec-env` ([#912](https://github.com/getsops/sops/pull/912))
* Allow to pass multiple paths to `sops updatekeys` ([#1274](https://github.com/getsops/sops/pull/1274))
* Allow to override `fileName` with different value ([#1332](https://github.com/getsops/sops/pull/1332))
* Sort masterkeys according to `--decryption-order` ([#1345](https://github.com/getsops/sops/pull/1345))
* Add separate subcommands for encryption, decryption, rotating, editing,
and setting values ([#1391](https://github.com/getsops/sops/pull/1391))
* Add `filestatus` command ([#545](https://github.com/getsops/sops/pull/545))
* Add command `unset` ([#1475](https://github.com/getsops/sops/pull/1475))
* Merge key for key groups and make keys unique ([#1493](https://github.com/getsops/sops/pull/1493))
* Support using comments to select parts to encrypt ([#974](https://github.com/getsops/sops/pull/974),
[#1392](https://github.com/getsops/sops/pull/1392))
Deprecations:
* Deprecate the `--background` option to `exec-env` and `exec-file` ([#1379](https://github.com/getsops/sops/pull/1379))
Improvements:
* Warn/fail if the wrong number of arguments is provided ([#1342](https://github.com/getsops/sops/pull/1342))
* Warn if more than one command is used ([#1388](https://github.com/getsops/sops/pull/1388))
* Dependency updates ([#1327](https://github.com/getsops/sops/pull/1327),
[#1328](https://github.com/getsops/sops/pull/1328), [#1330](https://github.com/getsops/sops/pull/1330),
[#1336](https://github.com/getsops/sops/pull/1336), [#1334](https://github.com/getsops/sops/pull/1334),
[#1344](https://github.com/getsops/sops/pull/1344), [#1348](https://github.com/getsops/sops/pull/1348),
[#1354](https://github.com/getsops/sops/pull/1354), [#1357](https://github.com/getsops/sops/pull/1357),
[#1360](https://github.com/getsops/sops/pull/1360), [#1373](https://github.com/getsops/sops/pull/1373),
[#1381](https://github.com/getsops/sops/pull/1381), [#1383](https://github.com/getsops/sops/pull/1383),
[#1385](https://github.com/getsops/sops/pull/1385), [#1408](https://github.com/getsops/sops/pull/1408),
[#1428](https://github.com/getsops/sops/pull/1428), [#1429](https://github.com/getsops/sops/pull/1429),
[#1427](https://github.com/getsops/sops/pull/1427), [#1439](https://github.com/getsops/sops/pull/1439),
[#1454](https://github.com/getsops/sops/pull/1454), [#1460](https://github.com/getsops/sops/pull/1460),
[#1466](https://github.com/getsops/sops/pull/1466), [#1489](https://github.com/getsops/sops/pull/1489),
[#1519](https://github.com/getsops/sops/pull/1519), [#1525](https://github.com/getsops/sops/pull/1525),
[#1528](https://github.com/getsops/sops/pull/1528), [#1540](https://github.com/getsops/sops/pull/1540),
[#1543](https://github.com/getsops/sops/pull/1543), [#1545](https://github.com/getsops/sops/pull/1545))
* Build with Go 1.21 ([#1427](https://github.com/getsops/sops/pull/1427))
* Improve README.rst ([#1339](https://github.com/getsops/sops/pull/1339),
[#1399](https://github.com/getsops/sops/pull/1399), [#1350](https://github.com/getsops/sops/pull/1350))
* Fix typos ([#1337](https://github.com/getsops/sops/pull/1337), [#1477](https://github.com/getsops/sops/pull/1477),
[#1484](https://github.com/getsops/sops/pull/1484))
* Polish the `sops help` output a bit ([#1341](https://github.com/getsops/sops/pull/1341),
[#1544](https://github.com/getsops/sops/pull/1544))
* Improve and fix tests ([#1346](https://github.com/getsops/sops/pull/1346),
[#1349](https://github.com/getsops/sops/pull/1349), [#1370](https://github.com/getsops/sops/pull/1370),
[#1390](https://github.com/getsops/sops/pull/1390), [#1396](https://github.com/getsops/sops/pull/1396),
[#1492](https://github.com/getsops/sops/pull/1492))
* Create a constant for the `sops` metadata key ([#1398](https://github.com/getsops/sops/pull/1398))
* Refactoring: move extraction of encryption and rotation options to
separate functions ([#1389](https://github.com/getsops/sops/pull/1389))
Bug fixes:
* Respect `aws_profile` from keygroup config ([#1049](https://github.com/getsops/sops/pull/1049))
* Fix a bug where not having a config results in a panic ([#1371](https://github.com/getsops/sops/pull/1371))
* Consolidate Flatten/Unflatten pre/post processing ([#1356](https://github.com/getsops/sops/pull/1356))
* INI and DotEnv stores: `shamir_threshold` is an integer ([#1394](https://github.com/getsops/sops/pull/1394))
* Make check whether file contains invalid keys for encryption dependent
on output store ([#1393](https://github.com/getsops/sops/pull/1393))
* Do not panic if `updatekeys` is used with a config that has no creation
rules defined ([#1506](https://github.com/getsops/sops/pull/1506))
* `exec-file`: if `--filename` is used, use the provided filename without
random suffix ([#1474](https://github.com/getsops/sops/pull/1474))
* Do not use DotEnv store for `exec-env`, but specialized environment
serializing code ([#1436](https://github.com/getsops/sops/pull/1436))
* Decryption: do not fail if no matching `creation_rule` is present in
config file ([#1434](https://github.com/getsops/sops/pull/1434))
Project changes:
* CI dependency updates ([#1347](https://github.com/getsops/sops/pull/1347),
[#1359](https://github.com/getsops/sops/pull/1359), [#1376](https://github.com/getsops/sops/pull/1376),
[#1382](https://github.com/getsops/sops/pull/1382), [#1386](https://github.com/getsops/sops/pull/1386),
[#1425](https://github.com/getsops/sops/pull/1425), [#1432](https://github.com/getsops/sops/pull/1432),
[#1498](https://github.com/getsops/sops/pull/1498), [#1503](https://github.com/getsops/sops/pull/1503),
[#1508](https://github.com/getsops/sops/pull/1508), [#1510](https://github.com/getsops/sops/pull/1510),
[#1516](https://github.com/getsops/sops/pull/1516), [#1521](https://github.com/getsops/sops/pull/1521),
[#1492](https://github.com/getsops/sops/pull/1492), [#1534](https://github.com/getsops/sops/pull/1534))
* Adjust Makefile to new goreleaser 6.0.0 release ([#1526](https://github.com/getsops/sops/pull/1526))
## 3.8.1
Improvements:
* Improve handling of errors when binary store handles bad data ([#1289](https://github.com/getsops/sops/pull/1289))
* On macOS, prefer `XDG_CONFIG_HOME` over os.UserConfigDir() ([#1291](https://github.com/getsops/sops/pull/1291))
* Dependency updates ([#1306](https://github.com/getsops/sops/pull/1306),
[#1319](https://github.com/getsops/sops/pull/1319), [#1325](https://github.com/getsops/sops/pull/1325))
* pgp: better error reporting for missing GPG binary during import of keys ([#1286](https://github.com/getsops/sops/pull/1286))
* Fix descriptions of `unencrypted-regex` and `encrypted-regex` flags, and
ensure `unencrypted_regex` is considered in config validation ([#1300](https://github.com/getsops/sops/pull/1300))
* stores/json: improve error messages when parsing invalid JSON ([#1307](https://github.com/getsops/sops/pull/1307))
Bug fixes:
* pgp: improve handling of GnuPG home dir ([#1298](https://github.com/getsops/sops/pull/1298))
* Do not crash if an empty YAML file is encrypted ([#1290](https://github.com/getsops/sops/pull/1290))
* Handling of various ignored errors ([#1304](https://github.com/getsops/sops/pull/1304),
[#1311](https://github.com/getsops/sops/pull/1311))
* pgp: do not require abs path for `SOPS_GPG_EXEC` ([#1309](https://github.com/getsops/sops/pull/1309))
* Report key rotation errors ([#1317](https://github.com/getsops/sops/pull/1317))
* Ensure wrapping of errors in main package ([#1318](https://github.com/getsops/sops/pull/1318))
Project changes:
* Enrich AWS authentication documentation ([#1272](https://github.com/getsops/sops/pull/1272))
* Add linting for RST and MD files ([#1287](https://github.com/getsops/sops/pull/1287))
* Delete SOPS encrypted file we don't have keys for ([#1288](https://github.com/getsops/sops/pull/1288))
* CI dependency updates ([#1295](https://github.com/getsops/sops/pull/1295), [#1301](https://github.com/getsops/sops/pull/1301))
* pgp: make error the last return value ([#1310](https://github.com/getsops/sops/pull/1310))
* Improve documentation files ([#1320](https://github.com/getsops/sops/pull/1320))
## 3.8.0
Features:
* Support `--version` without network requests using `--disable-version-check` ([#1115](https://github.com/getsops/sops/pull/1115))
* Support `--input-type` for updatekeys command ([#1116](https://github.com/getsops/sops/pull/1116))
Improvements:
* pgp: modernize and improve, and add tests ([#1054](https://github.com/getsops/sops/pull/1054),
[#1282](https://github.com/getsops/sops/pull/1282))
* azkv: update SDK to latest, add tests, tidy ([#1067](https://github.com/getsops/sops/pull/1067),
[#1092](https://github.com/getsops/sops/pull/1092), [#1256](https://github.com/getsops/sops/pull/1256))
* age: improve identity loading, add tests, tidy ([#1064](https://github.com/getsops/sops/pull/1064))
* kms: AWS SDK V2, allow creds config, add tests ([#1065](https://github.com/getsops/sops/pull/1065),
[#1257](https://github.com/getsops/sops/pull/1257))
* gcpkms: update SDK to latest, add tests, tidy ([#1072](https://github.com/getsops/sops/pull/1072),
[#1255](https://github.com/getsops/sops/pull/1255))
* hcvault: update API, add tests, tidy ([#1085](https://github.com/getsops/sops/pull/1085))
* Do not report version when upstream `--version` check fails ([#1124](https://github.com/getsops/sops/pull/1124))
* Use GitHub endpoints in `--version` command ([#1261](https://github.com/getsops/sops/pull/1261))
* Close temporary file before invoking editor to widen support on Windows ([#1265](https://github.com/getsops/sops/pull/1265))
* Update dependencies ([#1063](https://github.com/getsops/sops/pull/1063),
[#1091](https://github.com/getsops/sops/pull/1091), [#1147](https://github.com/getsops/sops/pull/1147),
[#1242](https://github.com/getsops/sops/pull/1242), [#1260](https://github.com/getsops/sops/pull/1260),
[#1264](https://github.com/getsops/sops/pull/1264), [#1275](https://github.com/getsops/sops/pull/1275),
[#1280](https://github.com/getsops/sops/pull/1280), [#1283](https://github.com/getsops/sops/pull/1283))
* Deal with various deprecations of dependencies ([#1113](https://github.com/getsops/sops/pull/1113),
[#1262](https://github.com/getsops/sops/pull/1262))
Bug fixes:
* Ensure YAML comments are not displaced ([#1069](https://github.com/getsops/sops/pull/1069))
* Ensure default Google credentials can be used again after introduction
of `GOOGLE_CREDENTIALS` ([#1249](https://github.com/getsops/sops/pull/1249))
* Avoid duplicate logging of errors in some key sources ([#1146](https://github.com/getsops/sops/pull/1146),
[#1281](https://github.com/getsops/sops/pull/1281))
* Using `--set` on a root level key does no longer truncate existing values ([#899](https://github.com/getsops/sops/pull/899))
* Ensure stable order of SOPS parameters in dotenv file ([#1101](https://github.com/getsops/sops/pull/1101))
Project changes:
* Update Go to 1.20 ([#1148](https://github.com/getsops/sops/pull/1148))
* Update rustc functional tests to v1.70.0 ([#1234](https://github.com/getsops/sops/pull/1234))
* Remove remaining CircleCI workflow ([#1237](https://github.com/getsops/sops/pull/1237))
* Run CLI workflow on main ([#1243](https://github.com/getsops/sops/pull/1243))
* Delete obsolete `validation/` artifact ([#1248](https://github.com/getsops/sops/pull/1248))
* Rename Go module to `github.com/getsops/sops/v3` ([#1247](https://github.com/getsops/sops/pull/1247))
* Revamp release automation, including (Cosign) signed container images
and checksums file, SLSA3 provenance and SBOMs ([#1250](https://github.com/getsops/sops/pull/1250))
* Update various bits of documentation ([#1244](https://github.com/getsops/sops/pull/1244))
* Add missing `--encrypt` flag from Vault example ([#1060](https://github.com/getsops/sops/pull/1060))
* Add documentation on how to use age in `.sops.yaml` ([#1192](https://github.com/getsops/sops/pull/1192))
* Improve Make targets and address various issues ([#1258](https://github.com/getsops/sops/pull/1258))
* Ensure clean working tree in CI ([#1267](https://github.com/getsops/sops/pull/1267))
* Fix CHANGELOG.rst formatting ([#1269](https://github.com/getsops/sops/pull/1269))
* Pin GitHub Actions to full length commit SHA and add CodeQL ([#1276](https://github.com/getsops/sops/pull/1276))
* Enable Dependabot for Docker, GitHub Actions and Go Mod ([#1277](https://github.com/getsops/sops/pull/1277))
* Generate versioned `.intoto.jsonl` ([#1278](https://github.com/getsops/sops/pull/1278))
* Update CI dependencies ([#1279](https://github.com/getsops/sops/pull/1279))
## 3.7.3
Changes:
* Upgrade dependencies ([#1024](https://github.com/getsops/sops/pull/1024), [#1045](https://github.com/getsops/sops/pull/1045))
* Build alpine container in CI ([#1018](https://github.com/getsops/sops/pull/1018),
[#1032](https://github.com/getsops/sops/pull/1032), [#1025](https://github.com/getsops/sops/pull/1025))
* keyservice: accept KeyServiceServer in LocalClient ([#1035](https://github.com/getsops/sops/pull/1035))
* Add support for GCP Service Account within `GOOGLE_CREDENTIALS` ([#953](https://github.com/getsops/sops/pull/953))
Bug fixes:
* Upload the correct binary for the linux amd64 build ([#1026](https://github.com/getsops/sops/pull/1026))
* Fix bug when specifying multiple age recipients ([#966](https://github.com/getsops/sops/pull/966))
* Allow for empty yaml maps ([#908](https://github.com/getsops/sops/pull/908))
* Limit AWS role names to 64 characters ([#1037](https://github.com/getsops/sops/pull/1037))
## 3.7.2
Changes:
* README updates ([#861](https://github.com/getsops/sops/pull/861), [#860](https://github.com/getsops/sops/pull/860))
* Various test fixes ([#909](https://github.com/getsops/sops/pull/909),
[#906](https://github.com/getsops/sops/pull/906), [#1008](https://github.com/getsops/sops/pull/1008))
* Added Linux and Darwin arm64 releases ([#911](https://github.com/getsops/sops/pull/911),
[#891](https://github.com/getsops/sops/pull/891))
* Upgrade to go v1.17 ([#1012](https://github.com/getsops/sops/pull/1012))
* Support SOPS_AGE_KEY environment variable ([#1006](https://github.com/getsops/sops/pull/1006))
Bug fixes:
* Make sure comments in yaml files are not duplicated ([#866](https://github.com/getsops/sops/pull/866))
* Make sure configuration file paths work correctly relative to the
config file in us ([#853](https://github.com/getsops/sops/pull/853))
## 3.7.1
Changes:
* Security fix
* Add release workflow ([#843](https://github.com/getsops/sops/pull/843))
* Fix issue where CI wouldn't run against master ([#848](https://github.com/getsops/sops/pull/848))
* Trim extra whitespace around age keys ([#846](https://github.com/getsops/sops/pull/846))
## 3.7.0
Features:
* Add support for age ([#688](https://github.com/getsops/sops/pull/688))
* Add filename to exec-file ([#761](https://github.com/getsops/sops/pull/761))
Changes:
* On failed decryption with GPG, return the error returned by GPG to the
sops user ([#762](https://github.com/getsops/sops/pull/762))
* Use yaml.v3 instead of modified yaml.v2 for handling YAML files ([#791](https://github.com/getsops/sops/pull/791))
* Update aws-sdk-go to version v1.37.18 ([#823](https://github.com/getsops/sops/pull/823))
Project Changes:
* Switch from TravisCI to Github Actions ([#792](https://github.com/getsops/sops/pull/792))
## 3.6.1
Features:
* Add support for --unencrypted-regex ([#715](https://github.com/getsops/sops/pull/715))
Changes:
* Use keys.openpgp.org instead of gpg.mozilla.org ([#732](https://github.com/getsops/sops/pull/732))
* Upgrade AWS SDK version ([#714](https://github.com/getsops/sops/pull/714))
* Support --input-type for exec-file ([#699](https://github.com/getsops/sops/pull/699))
Bug fixes:
* Fixes broken Vault tests ([#731](https://github.com/getsops/sops/pull/731))
* Revert "Add standard newline/quoting behavior to dotenv store" ([#706](https://github.com/getsops/sops/pull/706))
## 3.6.0
Features:
* Support for encrypting data through the use of Hashicorp Vault ([#655](https://github.com/getsops/sops/pull/655))
* `sops publish` now supports `--recursive` flag for publishing all files
in a directory ([#602](https://github.com/getsops/sops/pull/602))
* `sops publish` now supports `--omit-extensions` flag for omitting the
extension in the destination path ([#602](https://github.com/getsops/sops/pull/602))
* sops now supports JSON arrays of arrays ([#642](https://github.com/getsops/sops/pull/642))
Improvements:
* Updates and standardization for the dotenv store ([#612](https://github.com/getsops/sops/pull/612),
[#622](https://github.com/getsops/sops/pull/622))
* Close temp files after using them for edit command ([#685](https://github.com/getsops/sops/pull/685))
Bug fixes:
* AWS SDK usage now correctly resolves the `~/.aws/config` file ([#680](https://github.com/getsops/sops/pull/680))
* `sops updatekeys` now correctly matches config rules ([#682](https://github.com/getsops/sops/pull/682))
* `sops updatekeys` now correctly uses the config path cli flag ([#672](https://github.com/getsops/sops/pull/672))
* Partially empty sops config files don't break the use of sops anymore ([#662](https://github.com/getsops/sops/pull/662))
* Fix possible infinite loop in PGP's passphrase prompt call ([#690](https://github.com/getsops/sops/pull/690))
Project changes:
* Dockerfile now based off of golang version 1.14 ([#649](https://github.com/getsops/sops/pull/649))
* Push alpine version of docker image to Dockerhub ([#609](https://github.com/getsops/sops/pull/609))
* Push major, major.minor, and major.minor.patch tagged docker images to
Dockerhub ([#607](https://github.com/getsops/sops/pull/607))
* Removed out of date contact information ([#668](https://github.com/getsops/sops/pull/668))
* Update authors in the cli help text ([#645](https://github.com/getsops/sops/pull/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](https://github.com/getsops/sops/pull/463))
* Allow for `--set` value to be a string. ([#461](https://github.com/getsops/sops/pull/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](https://github.com/getsops/sops/pull/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

View File

@@ -1,449 +1,4 @@
Changelog
=========
3.9.0
-----
Features:
* Add ``--mac-only-encrypted`` to compute MAC only over values which end up encrypted (#973)
* Allow configuration of indentation for YAML and JSON stores (#1273, #1372)
* Introduce a ``--pristine`` flag to ``sops exec-env`` (#912)
* Allow to pass multiple paths to ``sops updatekeys`` (#1274)
* Allow to override ``fileName`` with different value (#1332)
* Sort masterkeys according to ``--decryption-order`` (#1345)
* Add separate subcommands for encryption, decryption, rotating, editing, and setting values (#1391)
* Add ``filestatus`` command (#545)
* Add command ``unset`` (#1475)
* Merge key for key groups and make keys unique (#1493)
* Support using comments to select parts to encrypt (#974, #1392)
Deprecations:
* Deprecate the ``--background`` option to ``exec-env`` and ``exec-file`` (#1379)
Improvements:
* Warn/fail if the wrong number of arguments is provided (#1342)
* Warn if more than one command is used (#1388)
* Dependency updates (#1327, #1328, #1330, #1336, #1334, #1344, #1348, #1354, #1357, #1360, #1373, #1381, #1383, #1385, #1408, #1428, #1429, #1427, #1439, #1454, #1460, #1466, #1489, #1519, #1525, #1528, #1540, #1543, #1545)
* Build with Go 1.21 (#1427)
* Improve README.rst (#1339, #1399, #1350)
* Fix typos (#1337, #1477, #1484)
* Polish the ``sops help`` output a bit (#1341, #1544)
* Improve and fix tests (#1346, #1349, #1370, #1390, #1396, #1492)
* Create a constant for the ``sops`` metadata key (#1398)
* Refactoring: move extraction of encryption and rotation options to separate functions (#1389)
Bug fixes:
* Respect ``aws_profile`` from keygroup config (#1049)
* Fix a bug where not having a config results in a panic (#1371)
* Consolidate Flatten/Unflatten pre/post processing (#1356)
* INI and DotEnv stores: ``shamir_threshold`` is an integer (#1394)
* Make check whether file contains invalid keys for encryption dependent on output store (#1393)
* Do not panic if ``updatekeys`` is used with a config that has no creation rules defined (#1506)
* ``exec-file``: if ``--filename`` is used, use the provided filename without random suffix (#1474)
* Do not use DotEnv store for ``exec-env``, but specialized environment serializing code (#1436)
* Decryption: do not fail if no matching ``creation_rule`` is present in config file (#1434)
Project changes:
* CI dependency updates (#1347, #1359, #1376, #1382, #1386, #1425, #1432, #1498, #1503, #1508, #1510, #1516, #1521, #1492, #1534)
* Adjust Makefile to new goreleaser 6.0.0 release (#1526)
3.8.1
-----
Improvements:
* Improve handling of errors when binary store handles bad data (#1289)
* On macOS, prefer ``XDG_CONFIG_HOME`` over os.UserConfigDir() (#1291)
* Dependency updates (#1306, #1319, #1325)
* pgp: better error reporting for missing GPG binary during import of keys (#1286)
* Fix descriptions of unencrypted-regex and encrypted-regex flags, and ensure unencrypted_regex is considered in config validation (#1300)
* stores/json: improve error messages when parsing invalid JSON (#1307)
Bug fixes:
* pgp: improve handling of GnuPG home dir (#1298)
* Do not crash if an empty YAML file is encrypted (#1290)
* Handling of various ignored errors (#1304, #1311)
* pgp: do not require abs path for ``SOPS_GPG_EXEC`` (#1309)
* Report key rotation errors (#1317)
* Ensure wrapping of errors in main package (#1318)
Project changes:
* Enrich AWS authentication documentation (#1272)
* Add linting for RST and MD files (#1287)
* Delete SOPS encrypted file we don't have keys for (#1288)
* CI dependency updates (#1295, #1301)
* pgp: make error the last return value (#1310)
* Improve documentation files (#1320)
3.8.0
-----
Features:
* Support ``--version`` without network requests using ``--disable-version-check`` (#1115)
* Support ``--input-type`` for updatekeys command (#1116)
Improvements:
* pgp: modernize and improve, and add tests (#1054, #1282)
* azkv: update SDK to latest, add tests, tidy (#1067, #1092, #1256)
* age: improve identity loading, add tests, tidy (#1064)
* kms: AWS SDK V2, allow creds config, add tests (#1065, #1257)
* gcpkms: update SDK to latest, add tests, tidy (#1072, #1255)
* hcvault: update API, add tests, tidy (#1085)
* Do not report version when upstream ``--version`` check fails (#1124)
* Use GitHub endpoints in ``--version`` command (#1261)
* Close temporary file before invoking editor to widen support on Windows (#1265)
* Update dependencies (#1063, #1091, #1147, #1242, #1260, #1264, #1275, #1280, #1283)
* Deal with various deprecations of dependencies (#1113, #1262)
Bug fixes:
* Ensure YAML comments are not displaced (#1069)
* Ensure default Google credentials can be used again after introduction of ``GOOGLE_CREDENTIALS`` (#1249)
* Avoid duplicate logging of errors in some key sources (#1146, #1281)
* Using ``--set`` on a root level key does no longer truncate existing values (#899)
* Ensure stable order of SOPS parameters in dotenv file (#1101)
Project changes:
* Update Go to 1.20 (#1148)
* Update rustc functional tests to v1.70.0 (#1234)
* Remove remaining CircleCI workflow (#1237)
* Run CLI workflow on main (#1243)
* Delete obsolete ``validation/`` artifact (#1248)
* Rename Go module to ``github.com/getsops/sops/v3`` (#1247)
* Revamp release automation, including (Cosign) signed container images and checksums file, SLSA3 provenance and SBOMs (#1250)
* Update various bits of documentation (#1244)
* Add missing ``--encrypt`` flag from Vault example (#1060)
* Add documentation on how to use age in ``.sops.yaml`` (#1192)
* Improve Make targets and address various issues (#1258)
* Ensure clean working tree in CI (#1267)
* Fix CHANGELOG.rst formatting (#1269)
* Pin GitHub Actions to full length commit SHA and add CodeQL (#1276)
* Enable Dependabot for Docker, GitHub Actions and Go Mod (#1277)
* Generate versioned ``.intoto.jsonl`` (#1278)
* Update CI dependencies (#1279)
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
The changelog can be found in `CHANGELOG.md <CHANGELOG.md>`_.

View File

@@ -20,6 +20,12 @@ SYFT_VERSION ?= v0.87.0
GORELEASER := $(BIN_DIR)/goreleaser
GORELEASER_VERSION ?= v1.20.0
PROTOC_GO := $(BIN_DIR)/protoc-gen-go
PROTOC_GO_VERSION ?= v1.35.2
PROTOC_GO_GRPC := $(BIN_DIR)/protoc-gen-go-grpc
PROTOC_GO_GRPC_VERSION ?= v1.5.1
RSTCHECK := $(shell command -v rstcheck)
MARKDOWNLINT := $(shell command -v mdl)
@@ -67,18 +73,18 @@ checkmd: $(MD_FILES)
.PHONY: test
test: vendor
gpg --import pgp/sops_functional_tests_key.asc 2>&1 1>/dev/null || exit 0
LANG=en_US.UTF-8 $(GO) test $(GO_TEST_FLAGS) ./...
unset SOPS_AGE_KEY_FILE; unset SOPS_AGE_KEY_CMD; LANG=en_US.UTF-8 $(GO) test $(GO_TEST_FLAGS) ./...
.PHONY: showcoverage
showcoverage: test
$(GO) tool cover -html=profile.out
.PHONY: generate
generate: keyservice/keyservice.pb.go
generate: install-protoc-go install-protoc-go-grpc keyservice/keyservice.pb.go
$(GO) generate
%.pb.go: %.proto
protoc --go_out=plugins=grpc:. $<
protoc --plugin gen-go=$(PROTOC_GO) --plugin gen-go-grpc=$(PLUGIN_GO_GRPC) --go-grpc_opt=require_unimplemented_servers=false --go-grpc_out=. --go_out=. $<
.PHONY: functional-tests
functional-tests:
@@ -112,6 +118,14 @@ install-goreleaser:
install-syft:
$(call go-install-tool,$(SYFT),github.com/anchore/syft/cmd/syft@$(SYFT_VERSION),$(SYFT_VERSION))
.PHONY: install-protoc-go
install-protoc-go:
$(call go-install-tool,$(PROTOC_GO),google.golang.org/protobuf/cmd/protoc-gen-go@$(PROTOC_GO_VERSION),$(PROTOC_GO_VERSION))
.PHONY: install-protoc-go-grpc
install-protoc-go-grpc:
$(call go-install-tool,$(PROTOC_GO_GRPC),google.golang.org/grpc/cmd/protoc-gen-go-grpc@$(PROTOC_GO_GRPC_VERSION),$(PROTOC_GO_GRPC_VERSION))
# go-install-tool will 'go install' any package $2 and install it to $1.
define go-install-tool
@[ -f $(1)-$(3) ] || { \

View File

@@ -106,7 +106,8 @@ encryption/decryption transparently and open the cleartext file in an editor
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.
Editing will happen in whatever ``$SOPS_EDITOR`` or ``$EDITOR`` is set to, or, if it's
not set, in vim, nano, or vi.
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
@@ -188,6 +189,22 @@ the example files and pgp key provided with the repository::
This last step will decrypt ``example.yaml`` using the test private key.
Encrypting with GnuPG subkeys
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want to encrypt with specific GnuPG subkeys, it does not suffice to provide the
exact key ID of the subkey to SOPS, since GnuPG might use *another* subkey instead
to encrypt the file key with. To force GnuPG to use a specific subkey, you need to
append ``!`` to the key's fingerprint.
.. code:: yaml
creation_rules:
- pgp: >-
85D77543B3D624B63CEA9E6DBC17301B491B3F21!,
E60892BB9BD89A69F759A1A0A3D652173B763E8F!
Please note that this is only passed on correctly to GnuPG since SOPS 3.9.3.
Encrypting using age
~~~~~~~~~~~~~~~~~~~~
@@ -210,15 +227,43 @@ On macOS, this would be ``$HOME/Library/Application Support/sops/age/keys.txt``.
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 key(s) directly by setting the **SOPS_AGE_KEY**
environment variable.
environment variable. Alternatively, you can provide a command to output the age keys
by setting the **SOPS_AGE_KEY_CMD** 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 with SSH keys via age is also supported by SOPS. You can use SSH public keys
("ssh-ed25519 AAAA...", "ssh-rsa AAAA...") as age recipients when encrypting a file.
When decrypting a file, SOPS will look for ``~/.ssh/id_ed25519`` and falls back to
``~/.ssh/id_rsa``. You can specify the location of the private key manually by setting
the environment variableuse **SOPS_AGE_SSH_PRIVATE_KEY_FILE**.
Note that only ``ssh-rsa`` and ``ssh-ed25519`` are supported.
A list of age recipients can be added to the ``.sops.yaml``:
.. code:: yaml
creation_rules:
- age: >-
age1s3cqcks5genc6ru8chl0hkkd04zmxvczsvdxq99ekffe4gmvjpzsedk23c,
age1qe5lxzzeppw5k79vxn3872272sgy224g2nzqlzy3uljs84say3yqgvd0sw
It is also possible to use ``updatekeys``, when adding or removing age recipients. For example:
.. code:: sh
$ sops updatekeys secret.enc.yaml
2022/02/09 16:32:02 Syncing keys for file /iac/solution1/secret.enc.yaml
The following changes will be made to the file's groups:
Group 1
age1s3cqcks5genc6ru8chl0hkkd04zmxvczsvdxq99ekffe4gmvjpzsedk23c
+++ age1qe5lxzzeppw5k79vxn3872272sgy224g2nzqlzy3uljs84say3yqgvd0sw
Is this okay? (y/n):y
2022/02/09 16:32:04 File /iac/solution1/secret.enc.yaml synced with new keys
Encrypting using GCP KMS
~~~~~~~~~~~~~~~~~~~~~~~~
GCP KMS uses `Application Default Credentials
@@ -338,33 +383,38 @@ Encrypting and decrypting from other programs
When using ``sops`` in scripts or from other programs, there are often situations where you do not want to write
encrypted or decrypted data to disk. The best way to avoid this is to pass data to SOPS via stdin, and to let
SOPS write data to stdout. By default, the encrypt and decrypt operations write data to stdout already. To pass
data via stdin, you need to pass ``/dev/stdin`` as the input filename. Please note that this only works on
Unix-like operating systems such as macOS and Linux. On Windows, you have to use named pipes.
data via stdin, you need to not provide an input filename. For encryption, you also must provide the
``--filename-override`` option with the file's filename. The filename will be used to determine the input and output
types, and to select the correct creation rule.
To decrypt data, you can simply do:
The simplest way to decrypt data from stdin is as follows:
.. code:: sh
$ cat encrypted-data | sops decrypt /dev/stdin > decrypted-data
$ cat encrypted-data | sops decrypt > decrypted-data
To control the input and output format, pass ``--input-type`` and ``--output-type`` as appropriate. By default,
``sops`` determines the input and output format from the provided filename, which is ``/dev/stdin`` here, and
thus will use the binary store which expects JSON input and outputs binary data on decryption.
By default, ``sops`` determines the input and output format from the provided filename. Since in this case,
no filename is provided, ``sops`` will use the binary store which expects JSON input and outputs binary data
on decryption. This is often not what you want.
For example, to decrypt YAML data and obtain the decrypted result as YAML, use:
To avoid this, you can either provide a filename with ``--filename-override``, or explicitly control
the input and output formats by passing ``--input-type`` and ``--output-type`` as appropriate:
.. code:: sh
$ cat encrypted-data | sops decrypt --input-type yaml --output-type yaml /dev/stdin > decrypted-data
$ cat encrypted-data | sops decrypt --filename-override filename.yaml > decrypted-data
$ cat encrypted-data | sops decrypt --input-type yaml --output-type yaml > decrypted-data
In both cases, ``sops`` will assume that the data you provide is in YAML format, and will encode the decrypted
data in YAML as well. The second form allows to use different formats for input and output.
To encrypt, it is important to note that SOPS also uses the filename to look up the correct creation rule from
``.sops.yaml``. Likely ``/dev/stdin`` will not match a creation rule, or only match the fallback rule without
``path_regex``, which is usually not what you want. For that, ``sops`` provides the ``--filename-override``
parameter which allows you to tell SOPS which filename to use to match creation rules:
``.sops.yaml``. Therefore, you must provide the ``--filename-override`` parameter which allows you to tell
SOPS which filename to use to match creation rules:
.. code:: sh
$ echo 'foo: bar' | sops encrypt --filename-override path/filename.sops.yaml /dev/stdin > encrypted-data
$ echo 'foo: bar' | sops encrypt --filename-override path/filename.sops.yaml > encrypted-data
SOPS will find a matching creation rule for ``path/filename.sops.yaml`` in ``.sops.yaml`` and use that one to
encrypt the data from stdin. This filename will also be used to determine the input and output store. As always,
@@ -373,7 +423,7 @@ the input store type can be adjusted by passing ``--input-type``, and the output
.. code:: sh
$ echo foo=bar | sops encrypt --filename-override path/filename.sops.yaml --input-type dotenv /dev/stdin > encrypted-data
$ echo foo=bar | sops encrypt --filename-override path/filename.sops.yaml --input-type dotenv > encrypted-data
Encrypting using Hashicorp Vault
@@ -485,7 +535,7 @@ disabled by supplying the ``-y`` flag.
******************
The ``rotate`` command generates a new data encryption key and reencrypt all values
with the new key. At te same time, the command line flag ``--add-kms``, ``--add-pgp``,
with the new key. At the same time, the 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``
@@ -669,6 +719,11 @@ 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.
.. note::
The file needs to be named ``.sops.yaml``. Other names (i.e. ``.sops.yml``) won't be automatically
discovered by SOPS. You'll need to pass the ``--config .sops.yml`` option for it to be picked up.
Let's take an example:
* file named **something.dev.yaml** should use one set of KMS A, PGP and age
@@ -1026,6 +1081,11 @@ written to disk.
$ echo your password: $database_password
your password:
If you want process signals to be sent to the command, for example if you are
running ``exec-env`` to launch a server and your server handles SIGTERM, then the
``--same-process`` flag can be used to instruct ``sops`` to start your command in
the same process instead of a child process. This uses the ``execve`` system call
and is supported on Unix-like systems.
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
@@ -1211,7 +1271,7 @@ When operating on stdin, use the ``--input-type`` and ``--output-type`` flags as
.. code:: sh
$ cat myfile.json | sops decrypt --input-type json --output-type json /dev/stdin
$ cat myfile.json | sops decrypt --input-type json --output-type json
JSON and JSON_binary indentation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -11,6 +11,7 @@ import (
"fmt"
"regexp"
"strconv"
"time"
"github.com/getsops/sops/v3"
"github.com/getsops/sops/v3/logging"
@@ -110,6 +111,10 @@ func (c Cipher) Decrypt(ciphertext string, key []byte, additionalData string) (p
plaintext = decryptedBytes
case "bool":
plaintext, err = strconv.ParseBool(decryptedValue)
case "time":
var value time.Time
err = value.UnmarshalText(decryptedBytes)
plaintext = value
case "comment":
plaintext = sops.Comment{Value: decryptedValue}
default:
@@ -176,6 +181,12 @@ func (c Cipher) Encrypt(plaintext interface{}, key []byte, additionalData string
} else {
plainBytes = []byte("False")
}
case time.Time:
encryptedType = "time"
plainBytes, err = value.MarshalText()
if err != nil {
return "", fmt.Errorf("Error marshaling timestamp %q: %w", value, err)
}
case sops.Comment:
encryptedType = "comment"
plainBytes = []byte(value.Value)

View File

@@ -1,13 +1,16 @@
package aes
import (
"bytes"
"crypto/rand"
"reflect"
"strings"
"testing"
"testing/quick"
"time"
"github.com/stretchr/testify/assert"
"github.com/getsops/sops/v3"
"github.com/stretchr/testify/assert"
)
func TestDecrypt(t *testing.T) {
@@ -108,6 +111,36 @@ func TestRoundtripBool(t *testing.T) {
}
}
func TestRoundtripTime(t *testing.T) {
key := []byte(strings.Repeat("f", 32))
parsedTime, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05+07:00")
assert.Nil(t, err)
loc := time.FixedZone("", 12300) // offset must be divisible by 60, otherwise won't survive a round-trip
values := []time.Time{
time.UnixMilli(0).In(time.UTC),
time.UnixMilli(123456).In(time.UTC),
time.UnixMilli(123456).In(loc),
time.UnixMilli(123456789).In(time.UTC),
time.UnixMilli(123456789).In(loc),
time.UnixMilli(1234567890).In(time.UTC),
time.UnixMilli(1234567890).In(loc),
parsedTime,
}
for _, value := range values {
s, err := NewCipher().Encrypt(value, key, "foo")
assert.Nil(t, err)
if err != nil {
continue
}
d, err := NewCipher().Decrypt(s, key, "foo")
assert.Nil(t, err)
if err != nil {
continue
}
assert.Equal(t, value, d)
}
}
func TestEncryptEmptyComment(t *testing.T) {
key := []byte(strings.Repeat("f", 32))
s, err := NewCipher().Encrypt(sops.Comment{}, key, "")
@@ -121,3 +154,61 @@ func TestDecryptEmptyValue(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, "", s)
}
// This test would belong more in sops_test.go, but from there we cannot access
// the aes package to get a cipher which can actually handle time.Time objects.
func TestTimestamps(t *testing.T) {
unixTime := time.UnixMilli(123456789).In(time.UTC)
parsedTime, err := time.Parse(time.RFC3339, "2006-01-02T15:04:05+07:00")
assert.Nil(t, err)
branches := sops.TreeBranches{
sops.TreeBranch{
sops.TreeItem{
Key: "foo",
Value: unixTime,
},
sops.TreeItem{
Key: "bar",
Value: sops.TreeBranch{
sops.TreeItem{
Key: "foo",
Value: parsedTime,
},
},
},
},
}
tree := sops.Tree{Branches: branches, Metadata: sops.Metadata{UnencryptedSuffix: "_unencrypted"}}
expected := sops.TreeBranch{
sops.TreeItem{
Key: "foo",
Value: unixTime,
},
sops.TreeItem{
Key: "bar",
Value: sops.TreeBranch{
sops.TreeItem{
Key: "foo",
Value: parsedTime,
},
},
},
}
cipher := NewCipher()
_, 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 do match: \ngot \t\t%+v,\n not 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)
}
assert.Equal(t, tree.Branches[0][0].Value, unixTime)
assert.Equal(t, tree.Branches[0], expected)
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)
}
}

190
age/encrypted_keys.go Normal file
View File

@@ -0,0 +1,190 @@
// These functions have been copied from the age project
// https://github.com/FiloSottile/age/blob/101cc8676386b0503571a929a88618cae2f0b1cd/cmd/age/encrypted_keys.go
// https://github.com/FiloSottile/age/blob/101cc8676386b0503571a929a88618cae2f0b1cd/cmd/age/parse.go
//
// Copyright 2021 The age Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in age's LICENSE file at
// https://github.com/FiloSottile/age/blob/v1.0.0/LICENSE
//
// SPDX-License-Identifier: BSD-3-Clause
package age
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"filippo.io/age"
"filippo.io/age/armor"
gpgagent "github.com/getsops/gopgagent"
)
type EncryptedIdentity struct {
Contents []byte
Passphrase func() (string, error)
NoMatchWarning func()
IncorrectPassphrase func()
identities []age.Identity
}
var _ age.Identity = &EncryptedIdentity{}
func (i *EncryptedIdentity) Unwrap(stanzas []*age.Stanza) (fileKey []byte, err error) {
if i.identities == nil {
if err := i.decrypt(); err != nil {
return nil, err
}
}
for _, id := range i.identities {
fileKey, err = id.Unwrap(stanzas)
if errors.Is(err, age.ErrIncorrectIdentity) {
continue
}
if err != nil {
return nil, err
}
return fileKey, nil
}
i.NoMatchWarning()
return nil, age.ErrIncorrectIdentity
}
func (i *EncryptedIdentity) decrypt() error {
d, err := age.Decrypt(bytes.NewReader(i.Contents), &LazyScryptIdentity{i.Passphrase})
if e := new(age.NoIdentityMatchError); errors.As(err, &e) {
// ScryptIdentity returns ErrIncorrectIdentity for an incorrect
// passphrase, which would lead Decrypt to returning "no identity
// matched any recipient". That makes sense in the API, where there
// might be multiple configured ScryptIdentity. Since in cmd/age there
// can be only one, return a better error message.
i.IncorrectPassphrase()
return fmt.Errorf("incorrect passphrase")
}
if err != nil {
return fmt.Errorf("failed to decrypt identity file: %v", err)
}
i.identities, err = age.ParseIdentities(d)
return err
}
// LazyScryptIdentity is an age.Identity that requests a passphrase only if it
// encounters an scrypt stanza. After obtaining a passphrase, it delegates to
// ScryptIdentity.
type LazyScryptIdentity struct {
Passphrase func() (string, error)
}
var _ age.Identity = &LazyScryptIdentity{}
func (i *LazyScryptIdentity) Unwrap(stanzas []*age.Stanza) (fileKey []byte, err error) {
for _, s := range stanzas {
if s.Type == "scrypt" && len(stanzas) != 1 {
return nil, errors.New("an scrypt recipient must be the only one")
}
}
if len(stanzas) != 1 || stanzas[0].Type != "scrypt" {
return nil, age.ErrIncorrectIdentity
}
pass, err := i.Passphrase()
if err != nil {
return nil, fmt.Errorf("could not read passphrase: %v", err)
}
ii, err := age.NewScryptIdentity(pass)
if err != nil {
return nil, err
}
fileKey, err = ii.Unwrap(stanzas)
return fileKey, err
}
func unwrapIdentities(key string, reader io.Reader) (ParsedIdentities, error) {
b := bufio.NewReader(reader)
p, _ := b.Peek(14) // length of "age-encryption" and "-----BEGIN AGE"
peeked := string(p)
switch {
// An age encrypted file, plain or armored.
case peeked == "age-encryption" || peeked == "-----BEGIN AGE":
var r io.Reader = b
if peeked == "-----BEGIN AGE" {
r = armor.NewReader(r)
}
const privateKeySizeLimit = 1 << 24 // 16 MiB
contents, err := io.ReadAll(io.LimitReader(r, privateKeySizeLimit))
if err != nil {
return nil, fmt.Errorf("failed to read '%s': %w", key, err)
}
if len(contents) == privateKeySizeLimit {
return nil, fmt.Errorf("failed to read '%s': file too long", key)
}
IncorrectPassphrase := func() {
conn, err := gpgagent.NewConn()
if err != nil {
return
}
defer func(conn *gpgagent.Conn) {
if err := conn.Close(); err != nil {
log.Errorf("failed to close connection with gpg-agent: %s", err)
}
}(conn)
err = conn.RemoveFromCache(key)
if err != nil {
log.Warnf("gpg-agent remove cache request errored: %s", err)
return
}
}
ids := []age.Identity{&EncryptedIdentity{
Contents: contents,
Passphrase: func() (string, error) {
conn, err := gpgagent.NewConn()
if err != nil {
passphrase, err := readSecret("Enter passphrase for identity " + key + ":")
if err != nil {
return "", err
}
return string(passphrase), nil
}
defer func(conn *gpgagent.Conn) {
if err := conn.Close(); err != nil {
log.Errorf("failed to close connection with gpg-agent: %s", err)
}
}(conn)
req := gpgagent.PassphraseRequest{
// TODO is the cachekey good enough?
CacheKey: key,
Prompt: "Passphrase",
Desc: fmt.Sprintf("Enter passphrase for identity '%s':", key),
}
pass, err := conn.GetPassphrase(&req)
if err != nil {
return "", fmt.Errorf("gpg-agent passphrase request errored: %s", err)
}
//make sure that we won't store empty pass
if len(pass) == 0 {
IncorrectPassphrase()
}
return pass, nil
},
IncorrectPassphrase: IncorrectPassphrase,
NoMatchWarning: func() {
log.Warnf("encrypted identity '%s' didn't match file's recipients", key)
},
}}
return ids, nil
// An unencrypted age identity file.
default:
ids, err := parseIdentities(b)
if err != nil {
return nil, fmt.Errorf("failed to parse '%s' age identities: %w", key, err)
}
return ids, nil
}
}

View File

@@ -1,20 +1,25 @@
package age
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"filippo.io/age"
"filippo.io/age/agessh"
"filippo.io/age/armor"
"filippo.io/age/plugin"
"github.com/sirupsen/logrus"
"github.com/getsops/sops/v3/logging"
"github.com/google/shlex"
)
const (
@@ -24,6 +29,12 @@ const (
// SopsAgeKeyFileEnv can be set as an environment variable pointing to an
// age keys file.
SopsAgeKeyFileEnv = "SOPS_AGE_KEY_FILE"
// SopsAgeKeyCmdEnv can be set as an environment variable with a command
// to execute that returns the age keys.
SopsAgeKeyCmdEnv = "SOPS_AGE_KEY_CMD"
// SopsAgeSshPrivateKeyFileEnv can be set as an environment variable pointing to
// a private SSH key file.
SopsAgeSshPrivateKeyFileEnv = "SOPS_AGE_SSH_PRIVATE_KEY_FILE"
// SopsAgeKeyUserConfigPath is the default age keys file path in
// getUserConfigDir().
SopsAgeKeyUserConfigPath = "sops/age/keys.txt"
@@ -60,7 +71,7 @@ type MasterKey struct {
parsedIdentities []age.Identity
// parsedRecipient contains a parsed age public key.
// It is used to lazy-load the Recipient at-most once.
parsedRecipient *age.X25519Recipient
parsedRecipient age.Recipient
}
// MasterKeysFromRecipients takes a comma-separated list of Bech32-encoded
@@ -111,7 +122,10 @@ type ParsedIdentities []age.Identity
// parsing (using age.ParseIdentities) and appending to the slice yourself, in
// combination with e.g. a sync.Mutex.
func (i *ParsedIdentities) Import(identity ...string) error {
identities, err := parseIdentities(identity...)
// one identity per line
r := strings.NewReader(strings.Join(identity, "\n"))
identities, err := parseIdentities(r)
if err != nil {
return fmt.Errorf("failed to parse and add to age identities: %w", err)
}
@@ -233,6 +247,35 @@ func (key *MasterKey) TypeToIdentifier() string {
return KeyTypeIdentifier
}
// loadAgeSSHIdentity attempts to load the age SSH identity based on an SSH
// private key from the SopsAgeSshPrivateKeyFileEnv environment variable. If the
// environment variable is not present, it will fall back to `~/.ssh/id_ed25519`
// or `~/.ssh/id_rsa`. If no age SSH identity is found, it will return nil.
func loadAgeSSHIdentity() (age.Identity, error) {
sshKeyFilePath, ok := os.LookupEnv(SopsAgeSshPrivateKeyFileEnv)
if ok {
return parseSSHIdentityFromPrivateKeyFile(sshKeyFilePath)
}
userHomeDir, err := os.UserHomeDir()
if err != nil || userHomeDir == "" {
log.Warnf("could not determine the user home directory: %v", err)
return nil, nil
}
sshEd25519PrivateKeyPath := filepath.Join(userHomeDir, ".ssh", "id_ed25519")
if _, err := os.Stat(sshEd25519PrivateKeyPath); err == nil {
return parseSSHIdentityFromPrivateKeyFile(sshEd25519PrivateKeyPath)
}
sshRsaPrivateKeyPath := filepath.Join(userHomeDir, ".ssh", "id_rsa")
if _, err := os.Stat(sshRsaPrivateKeyPath); err == nil {
return parseSSHIdentityFromPrivateKeyFile(sshRsaPrivateKeyPath)
}
return nil, nil
}
func getUserConfigDir() (string, error) {
if runtime.GOOS == "darwin" {
if userConfigDir, ok := os.LookupEnv(xdgConfigHome); ok && userConfigDir != "" {
@@ -244,9 +287,19 @@ func getUserConfigDir() (string, error) {
// loadIdentities attempts to load the age identities based on runtime
// environment configurations (e.g. SopsAgeKeyEnv, SopsAgeKeyFileEnv,
// SopsAgeKeyUserConfigPath). It will load all found references, and expects
// at least one configuration to be present.
// SopsAgeSshPrivateKeyFileEnv, SopsAgeKeyUserConfigPath). It will load all
// found references, and expects at least one configuration to be present.
func (key *MasterKey) loadIdentities() (ParsedIdentities, error) {
var identities ParsedIdentities
sshIdentity, err := loadAgeSSHIdentity()
if err != nil {
return nil, fmt.Errorf("failed to get SSH identity: %w", err)
}
if sshIdentity != nil {
identities = append(identities, sshIdentity)
}
var readers = make(map[string]io.Reader, 0)
if ageKey, ok := os.LookupEnv(SopsAgeKeyEnv); ok {
@@ -262,8 +315,20 @@ func (key *MasterKey) loadIdentities() (ParsedIdentities, error) {
readers[SopsAgeKeyFileEnv] = f
}
if ageKeyCmd, ok := os.LookupEnv(SopsAgeKeyCmdEnv); ok {
args, err := shlex.Split(ageKeyCmd)
if err != nil {
return nil, fmt.Errorf("failed to parse command %s from %s: %w", ageKeyCmd, SopsAgeKeyCmdEnv, err)
}
out, err := exec.Command(args[0], args[1:]...).Output()
if err != nil {
return nil, fmt.Errorf("failed to execute command %s from %s: %w", ageKeyCmd, SopsAgeKeyCmdEnv, err)
}
readers[SopsAgeKeyCmdEnv] = bytes.NewReader(out)
}
userConfigDir, err := getUserConfigDir()
if err != nil && len(readers) == 0 {
if err != nil && len(readers) == 0 && len(identities) == 0 {
return nil, fmt.Errorf("user config directory could not be determined: %w", err)
}
if userConfigDir != "" {
@@ -272,7 +337,7 @@ func (key *MasterKey) loadIdentities() (ParsedIdentities, error) {
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, fmt.Errorf("failed to open file: %w", err)
}
if errors.Is(err, os.ErrNotExist) && len(readers) == 0 {
if errors.Is(err, os.ErrNotExist) && len(readers) == 0 && len(identities) == 0 {
// If we have no other readers, presence of the file is required.
return nil, fmt.Errorf("failed to open file: %w", err)
}
@@ -282,11 +347,10 @@ func (key *MasterKey) loadIdentities() (ParsedIdentities, error) {
}
}
var identities ParsedIdentities
for n, r := range readers {
ids, err := age.ParseIdentities(r)
ids, err := unwrapIdentities(n, r)
if err != nil {
return nil, fmt.Errorf("failed to parse '%s' age identities: %w", n, err)
return nil, err
}
identities = append(identities, ids...)
}
@@ -294,26 +358,66 @@ func (key *MasterKey) loadIdentities() (ParsedIdentities, error) {
}
// 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)
// key or a public ssh key.
func parseRecipient(recipient string) (age.Recipient, error) {
switch {
case strings.HasPrefix(recipient, "age1") && strings.Count(recipient, "1") > 1:
parsedRecipient, err := plugin.NewRecipient(recipient, pluginTerminalUI)
if err != nil {
return nil, fmt.Errorf("failed to parse input as age key from age plugin: %w", err)
}
return parsedRecipient, nil
case strings.HasPrefix(recipient, "age1"):
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
case strings.HasPrefix(recipient, "ssh-"):
parsedRecipient, err := agessh.ParseRecipient(recipient)
if err != nil {
return nil, fmt.Errorf("failed to parse input as age-ssh public key: %w", err)
}
return parsedRecipient, nil
}
return parsedRecipient, nil
return nil, fmt.Errorf("failed to parse input, unknown recipient type: %q", recipient)
}
// parseIdentities attempts to parse the string set of encoded age identities.
// A single identity argument is allowed to be a multiline string containing
// multiple identities. Empty lines and lines starting with "#" are ignored.
func parseIdentities(identity ...string) (ParsedIdentities, error) {
var identities []age.Identity
for _, i := range identity {
parsed, err := age.ParseIdentities(strings.NewReader(i))
// parseIdentities attempts to parse one or more age identities from the provided reader.
// One identity per line.
// Empty lines and lines starting with "#" are ignored.
func parseIdentities(r io.Reader) (ParsedIdentities, error) {
var identities ParsedIdentities
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
if line == "" || strings.HasPrefix(line, "#") {
continue
}
parsed, err := parseIdentity(line)
if err != nil {
return nil, err
}
identities = append(identities, parsed...)
identities = append(identities, parsed)
}
return identities, nil
}
func parseIdentity(s string) (age.Identity, error) {
switch {
case strings.HasPrefix(s, "AGE-PLUGIN-"):
return plugin.NewIdentity(s, pluginTerminalUI)
case strings.HasPrefix(s, "AGE-SECRET-KEY-1"):
return age.ParseX25519Identity(s)
default:
return nil, fmt.Errorf("unknown identity type")
}
}

View File

@@ -28,6 +28,35 @@ EylloI7MNGbadPGb
-----END AGE ENCRYPTED FILE-----`
// mockEncryptedKeyPlain is the plain value of mockEncryptedKey.
mockEncryptedKeyPlain string = "data"
// passphrase used to encrypt age identity.
mockIdentityPassphrase string = "passphrase"
mockEncryptedIdentity string = `-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNjcnlwdCBMN2FXZW9xSFViYjdNeW5D
dy9iSHFnIDE4Ck9zV0ZoNldmci9rL3VXd3BtZmQvK3VZWEpBQjdhZ0UrcmhqR2lF
YThFMzAKLS0tIGVEQ0xwODI1TlNYeHNHaHZKWHoyLzYwMTMvTGhaZG1oa203cSs0
VUpBL1kKsaTnt+H/z8mkL21UYKIt3YMpWSV/oYqTm1cSSUnF9InZEYU9HndK9rc8
ni+MTJCmYf4mgvvGPMf7oIQvs6ijaTdlQb+zeQsL4eif20w+CWgvPNrS6iXUIs8W
w5/fHsxwmrkG96nDkMErJKhmjmLpC+YdbiMe6P/KIpas09m08RTIqcz7ua0Xm3ey
ndU+8ILJOhcnWV55W43nTw/UUFse7f+qY61n7kcd1sGd7ZfSEdEIqS3K2vEtA3ER
fn0s3cyXVEBxL9OZqcAk45bCFVOl13Fp/DBfquHEjvAyeg0=
-----END AGE ENCRYPTED FILE-----`
// mockSshRecipient is a mock age ssh recipient, it matches mockSshIdentity
mockSshRecipient string = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID+Wi8WZw2bXfBpcs/WECttCzP39OkenS6pHWHWGFJvN Test"
// mockSshIdentity is a mock age identity based on an OpenSSH private key (ed25519)
mockSshIdentity string = `-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACA/lovFmcNm13waXLP1hArbQsz9/TpHp0uqR1h1hhSbzQAAAIgCXDMIAlwz
CAAAAAtzc2gtZWQyNTUxOQAAACA/lovFmcNm13waXLP1hArbQsz9/TpHp0uqR1h1hhSbzQ
AAAEBJdWTJ8dC0OnMcwy4gQ96sp6KG8GE9EiyhFGhKldKiST+Wi8WZw2bXfBpcs/WECttC
zP39OkenS6pHWHWGFJvNAAAABFRlc3QB
-----END OPENSSH PRIVATE KEY-----`
mockEncryptedSshKey string = `-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IDJjd0R4dyB2R3Ns
VUNHaXBiTEJaNU5BMFFQZUpCYWJqODFyTTZ4WWZoRVpUd2M2aTBFCkduUFJHb1U2
K3RqWVQrLzE4anZKZ3h2T3c2MFpZTHlGaHprcElXenByWTAKLS0tIG56MHFSZERl
em9PWmRMMTY4aytYTnVZN04yeER5Z2E3TWxWT3JTZWR2ekUKp/HZLy4MzQqoszGk
+P0hSPPNhOhvFwv4AqCw1+A+WyeHGQPq
-----END AGE ENCRYPTED FILE-----`
)
func TestMasterKeysFromRecipients(t *testing.T) {
@@ -41,22 +70,32 @@ func TestMasterKeysFromRecipients(t *testing.T) {
assert.Equal(t, got[0].Recipient, mockRecipient)
})
t.Run("recipients", func(t *testing.T) {
got, err := MasterKeysFromRecipients(mockRecipient + "," + otherRecipient)
t.Run("recipient-ssh", func(t *testing.T) {
got, err := MasterKeysFromRecipients(mockSshRecipient)
assert.NoError(t, err)
assert.Len(t, got, 2)
assert.Len(t, got, 1)
assert.Equal(t, got[0].Recipient, mockSshRecipient)
})
t.Run("recipients", func(t *testing.T) {
got, err := MasterKeysFromRecipients(mockRecipient + "," + otherRecipient + "," + mockSshRecipient)
assert.NoError(t, err)
assert.Len(t, got, 3)
assert.Equal(t, got[0].Recipient, mockRecipient)
assert.Equal(t, got[1].Recipient, otherRecipient)
assert.Equal(t, got[2].Recipient, mockSshRecipient)
})
t.Run("leading and trailing spaces", func(t *testing.T) {
got, err := MasterKeysFromRecipients(" " + mockRecipient + " , " + otherRecipient + " ")
got, err := MasterKeysFromRecipients(" " + mockRecipient + " , " + otherRecipient + " , " + mockSshRecipient + " ")
assert.NoError(t, err)
assert.Len(t, got, 2)
assert.Len(t, got, 3)
assert.Equal(t, got[0].Recipient, mockRecipient)
assert.Equal(t, got[1].Recipient, otherRecipient)
assert.Equal(t, got[2].Recipient, mockSshRecipient)
})
t.Run("empty", func(t *testing.T) {
@@ -75,6 +114,14 @@ func TestMasterKeyFromRecipient(t *testing.T) {
assert.Nil(t, got.parsedIdentities)
})
t.Run("recipient-ssh", func(t *testing.T) {
got, err := MasterKeyFromRecipient(mockSshRecipient)
assert.NoError(t, err)
assert.EqualValues(t, mockSshRecipient, got.Recipient)
assert.NotNil(t, got.parsedRecipient)
assert.Nil(t, got.parsedIdentities)
})
t.Run("leading and trailing spaces", func(t *testing.T) {
got, err := MasterKeyFromRecipient(" " + mockRecipient + " ")
assert.NoError(t, err)
@@ -83,6 +130,14 @@ func TestMasterKeyFromRecipient(t *testing.T) {
assert.Nil(t, got.parsedIdentities)
})
t.Run("leading and trailing spaces - ssh", func(t *testing.T) {
got, err := MasterKeyFromRecipient(" " + mockSshRecipient + " ")
assert.NoError(t, err)
assert.EqualValues(t, mockSshRecipient, got.Recipient)
assert.NotNil(t, got.parsedRecipient)
assert.Nil(t, got.parsedIdentities)
})
t.Run("invalid recipient", func(t *testing.T) {
got, err := MasterKeyFromRecipient("invalid")
assert.Error(t, err)
@@ -111,6 +166,8 @@ func TestParsedIdentities_ApplyToMasterKey(t *testing.T) {
func TestMasterKey_Encrypt(t *testing.T) {
mockParsedRecipient, err := parseRecipient(mockRecipient)
assert.NoError(t, err)
mockSshParsedRecipient, err := parseRecipient(mockSshRecipient)
assert.NoError(t, err)
t.Run("recipient", func(t *testing.T) {
key := &MasterKey{
@@ -120,6 +177,14 @@ func TestMasterKey_Encrypt(t *testing.T) {
assert.NotEmpty(t, key.EncryptedKey)
})
t.Run("recipient ssh", func(t *testing.T) {
key := &MasterKey{
Recipient: mockSshRecipient,
}
assert.NoError(t, key.Encrypt([]byte(mockEncryptedKeyPlain)))
assert.NotEmpty(t, key.EncryptedKey)
})
t.Run("parsed recipient", func(t *testing.T) {
key := &MasterKey{
parsedRecipient: mockParsedRecipient,
@@ -128,13 +193,21 @@ func TestMasterKey_Encrypt(t *testing.T) {
assert.NotEmpty(t, key.EncryptedKey)
})
t.Run("parsed recipient ssh", func(t *testing.T) {
key := &MasterKey{
parsedRecipient: mockSshParsedRecipient,
}
assert.NoError(t, key.Encrypt([]byte(mockEncryptedKeyPlain)))
assert.NotEmpty(t, key.EncryptedKey)
})
t.Run("invalid recipient", func(t *testing.T) {
key := &MasterKey{
Recipient: "invalid",
}
err := key.Encrypt([]byte(mockEncryptedKeyPlain))
assert.Error(t, err)
assert.ErrorContains(t, err, "failed to parse input as Bech32-encoded age public key")
assert.ErrorContains(t, err, "failed to parse input, unknown recipient type:")
assert.Empty(t, key.EncryptedKey)
})
@@ -180,6 +253,7 @@ func TestMasterKey_Decrypt(t *testing.T) {
})
t.Run("loaded identities", func(t *testing.T) {
overwriteUserConfigDir(t, t.TempDir())
key := &MasterKey{EncryptedKey: mockEncryptedKey}
t.Setenv(SopsAgeKeyEnv, mockIdentity)
@@ -188,6 +262,25 @@ func TestMasterKey_Decrypt(t *testing.T) {
assert.EqualValues(t, mockEncryptedKeyPlain, got)
})
t.Run("loaded identities ssh", func(t *testing.T) {
key := &MasterKey{EncryptedKey: mockEncryptedSshKey}
tmp := t.TempDir()
overwriteUserConfigDir(t, tmp)
homeDir, err := os.UserHomeDir()
assert.NoError(t, err)
keyPath := filepath.Join(homeDir, ".ssh/id_25519")
assert.True(t, strings.HasPrefix(keyPath, homeDir))
assert.NoError(t, os.MkdirAll(filepath.Dir(keyPath), 0o700))
assert.NoError(t, os.WriteFile(keyPath, []byte(mockSshIdentity), 0o644))
t.Setenv(SopsAgeSshPrivateKeyFileEnv, keyPath)
got, err := key.Decrypt()
assert.NoError(t, err)
assert.EqualValues(t, mockEncryptedKeyPlain, got)
})
t.Run("no identities", func(t *testing.T) {
tmpDir := t.TempDir()
overwriteUserConfigDir(t, tmpDir)
@@ -215,6 +308,7 @@ func TestMasterKey_Decrypt(t *testing.T) {
})
t.Run("invalid encrypted key", func(t *testing.T) {
overwriteUserConfigDir(t, t.TempDir())
key := &MasterKey{EncryptedKey: "invalid"}
t.Setenv(SopsAgeKeyEnv, mockIdentity)
@@ -327,6 +421,25 @@ func TestMasterKey_loadIdentities(t *testing.T) {
assert.Len(t, got, 1)
})
t.Run(SopsAgeSshPrivateKeyFileEnv, func(t *testing.T) {
tmpDir := t.TempDir()
overwriteUserConfigDir(t, tmpDir)
homeDir, err := os.UserHomeDir()
assert.NoError(t, err)
keyPath := filepath.Join(homeDir, ".ssh/id_25519")
assert.True(t, strings.HasPrefix(keyPath, homeDir))
assert.NoError(t, os.MkdirAll(filepath.Dir(keyPath), 0o700))
assert.NoError(t, os.WriteFile(keyPath, []byte(mockSshIdentity), 0o644))
t.Setenv(SopsAgeSshPrivateKeyFileEnv, keyPath)
key := &MasterKey{}
got, err := key.loadIdentities()
assert.NoError(t, err)
assert.Len(t, got, 1)
})
t.Run("no identity", func(t *testing.T) {
tmpDir := t.TempDir()
overwriteUserConfigDir(t, tmpDir)
@@ -372,10 +485,37 @@ func TestMasterKey_loadIdentities(t *testing.T) {
assert.ErrorContains(t, err, fmt.Sprintf("failed to parse '%s' age identities", SopsAgeKeyEnv))
assert.Nil(t, got)
})
t.Run(SopsAgeKeyCmdEnv, func(t *testing.T) {
tmpDir := t.TempDir()
// Overwrite to ensure local config is not picked up by tests
overwriteUserConfigDir(t, tmpDir)
t.Setenv(SopsAgeKeyCmdEnv, "echo '"+mockIdentity+"'")
key := &MasterKey{}
got, err := key.loadIdentities()
assert.NoError(t, err)
assert.Len(t, got, 1)
})
t.Run("cmd error", func(t *testing.T) {
tmpDir := t.TempDir()
// Overwrite to ensure local config is not picked up by tests
overwriteUserConfigDir(t, tmpDir)
t.Setenv(SopsAgeKeyCmdEnv, "meow")
key := &MasterKey{}
got, err := key.loadIdentities()
assert.Error(t, err)
assert.ErrorContains(t, err, "failed to execute command meow")
assert.Nil(t, got)
})
}
// overwriteUserConfigDir sets the user config directory based on the
// os.UserConfigDir logic.
// overwriteUserConfigDir sets the user config directory and the user home directory
// based on the os.UserConfigDir logic.
func overwriteUserConfigDir(t *testing.T, path string) {
switch runtime.GOOS {
case "windows":
@@ -384,6 +524,7 @@ func overwriteUserConfigDir(t *testing.T, path string) {
t.Setenv("home", path)
default: // Unix
t.Setenv("XDG_CONFIG_HOME", path)
t.Setenv("HOME", path)
}
}
@@ -400,3 +541,54 @@ func TestUserConfigDir(t *testing.T) {
assert.Equal(t, home, dir)
}
}
func TestMasterKey_Identities_Passphrase(t *testing.T) {
t.Run(SopsAgeKeyEnv, func(t *testing.T) {
key := &MasterKey{EncryptedKey: mockEncryptedKey}
t.Setenv(SopsAgeKeyEnv, mockEncryptedIdentity)
//blocks calling gpg-agent
os.Unsetenv("XDG_RUNTIME_DIR")
testOnlyAgePassword = mockIdentityPassphrase
got, err := key.Decrypt()
testOnlyAgePassword = ""
assert.NoError(t, err)
assert.EqualValues(t, mockEncryptedKeyPlain, got)
})
t.Run(SopsAgeKeyFileEnv, func(t *testing.T) {
tmpDir := t.TempDir()
// Overwrite to ensure local config is not picked up by tests
overwriteUserConfigDir(t, tmpDir)
keyPath := filepath.Join(tmpDir, "keys.txt")
assert.NoError(t, os.WriteFile(keyPath, []byte(mockEncryptedIdentity), 0o644))
key := &MasterKey{EncryptedKey: mockEncryptedKey}
t.Setenv(SopsAgeKeyFileEnv, keyPath)
//blocks calling gpg-agent
os.Unsetenv("XDG_RUNTIME_DIR")
testOnlyAgePassword = mockIdentityPassphrase
got, err := key.Decrypt()
testOnlyAgePassword = ""
assert.NoError(t, err)
assert.EqualValues(t, mockEncryptedKeyPlain, got)
})
t.Run("invalid encrypted key", func(t *testing.T) {
key := &MasterKey{EncryptedKey: "invalid"}
t.Setenv(SopsAgeKeyEnv, mockEncryptedIdentity)
//blocks calling gpg-agent
os.Unsetenv("XDG_RUNTIME_DIR")
testOnlyAgePassword = mockIdentityPassphrase
got, err := key.Decrypt()
testOnlyAgePassword = ""
assert.Error(t, err)
assert.ErrorContains(t, err, "failed to create reader for decrypting sops data key with age")
assert.Nil(t, got)
})
}

84
age/ssh_parse.go Normal file
View File

@@ -0,0 +1,84 @@
// These functions are similar to those in the age project
// https://github.com/FiloSottile/age/blob/v1.0.0/cmd/age/parse.go
//
// Copyright 2021 The age Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in age's LICENSE file at
// https://github.com/FiloSottile/age/blob/v1.0.0/LICENSE
//
// SPDX-License-Identifier: BSD-3-Clause
package age
import (
"fmt"
"io"
"os"
"filippo.io/age"
"filippo.io/age/agessh"
"golang.org/x/crypto/ssh"
)
// readPublicKeyFile attempts to read a public key based on the given private
// key path. It assumes the public key is in the same directory, with the same
// name, but with a ".pub" extension. If the public key cannot be read, an
// error is returned.
func readPublicKeyFile(privateKeyPath string) (ssh.PublicKey, error) {
publicKeyPath := privateKeyPath + ".pub"
f, err := os.Open(publicKeyPath)
if err != nil {
return nil, fmt.Errorf("failed to obtain public %q key for %q SSH key: %w", publicKeyPath, privateKeyPath, err)
}
defer f.Close()
contents, err := io.ReadAll(f)
if err != nil {
return nil, fmt.Errorf("failed to read %q: %w", publicKeyPath, err)
}
pubKey, _, _, _, err := ssh.ParseAuthorizedKey(contents)
if err != nil {
return nil, fmt.Errorf("failed to parse %q: %w", publicKeyPath, err)
}
return pubKey, nil
}
// parseSSHIdentityFromPrivateKeyFile returns an age.Identity from the given
// private key file. If the private key file is encrypted, it will configure
// the identity to prompt for a passphrase.
func parseSSHIdentityFromPrivateKeyFile(keyPath string) (age.Identity, error) {
keyFile, err := os.Open(keyPath)
if err != nil {
return nil, fmt.Errorf("failed to open file: %w", err)
}
defer keyFile.Close()
contents, err := io.ReadAll(keyFile)
if err != nil {
return nil, fmt.Errorf("failed to read file: %w", err)
}
id, err := agessh.ParseIdentity(contents)
if sshErr, ok := err.(*ssh.PassphraseMissingError); ok {
pubKey := sshErr.PublicKey
if pubKey == nil {
pubKey, err = readPublicKeyFile(keyPath)
if err != nil {
return nil, err
}
}
passphrasePrompt := func() ([]byte, error) {
pass, err := readSecret(fmt.Sprintf("Enter passphrase for %q:", keyPath))
if err != nil {
return nil, fmt.Errorf("could not read passphrase for %q: %v", keyPath, err)
}
return pass, nil
}
i, err := agessh.NewEncryptedSSHIdentity(pubKey, contents, passphrasePrompt)
if err != nil {
return nil, fmt.Errorf("could not create encrypted SSH identity: %w", err)
}
return i, nil
}
if err != nil {
return nil, fmt.Errorf("malformed SSH identity in %q: %w", keyPath, err)
}
return id, nil
}

173
age/tui.go Normal file
View File

@@ -0,0 +1,173 @@
// These functions have been copied from the age project
// https://github.com/FiloSottile/age/blob/3d91014ea095e8d70f7c6c4833f89b53a96e0832/cmd/age/tui.go
//
// Copyright 2021 The age Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in age's LICENSE file at
// https://github.com/FiloSottile/age/blob/v1.0.0/LICENSE
//
// SPDX-License-Identifier: BSD-3-Clause
package age
import (
"errors"
"filippo.io/age/plugin"
"fmt"
"io"
"os"
"runtime"
"testing"
"golang.org/x/term"
)
var testOnlyAgePassword string
func printf(format string, v ...interface{}) {
log.Printf("age: "+format, v...)
}
func warningf(format string, v ...interface{}) {
log.Printf("age: warning: "+format, v...)
}
// clearLine clears the current line on the terminal, or opens a new line if
// terminal escape codes don't work.
func clearLine(out io.Writer) {
const (
CUI = "\033[" // Control Sequence Introducer
CPL = CUI + "F" // Cursor Previous Line
EL = CUI + "K" // Erase in Line
)
// First, open a new line, which is guaranteed to work everywhere. Then, try
// to erase the line above with escape codes.
//
// (We use CRLF instead of LF to work around an apparent bug in WSL2's
// handling of CONOUT$. Only when running a Windows binary from WSL2, the
// cursor would not go back to the start of the line with a simple LF.
// Honestly, it's impressive CONIN$ and CONOUT$ work at all inside WSL2.)
fmt.Fprintf(out, "\r\n"+CPL+EL)
}
// withTerminal runs f with the terminal input and output files, if available.
// withTerminal does not open a non-terminal stdin, so the caller does not need
// to check stdinInUse.
func withTerminal(f func(in, out *os.File) error) error {
if runtime.GOOS == "windows" {
in, err := os.OpenFile("CONIN$", os.O_RDWR, 0)
if err != nil {
return err
}
defer in.Close()
out, err := os.OpenFile("CONOUT$", os.O_WRONLY, 0)
if err != nil {
return err
}
defer out.Close()
return f(in, out)
} else if tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0); err == nil {
defer tty.Close()
return f(tty, tty)
} else if term.IsTerminal(int(os.Stdin.Fd())) {
return f(os.Stdin, os.Stdin)
} else {
return fmt.Errorf("standard input is not a terminal, and /dev/tty is not available: %v", err)
}
}
// readSecret reads a value from the terminal with no echo. The prompt is ephemeral.
func readSecret(prompt string) (s []byte, err error) {
if testing.Testing() {
if testOnlyAgePassword != "" {
return []byte(testOnlyAgePassword), nil
}
}
err = withTerminal(func(in, out *os.File) error {
fmt.Fprintf(out, "%s ", prompt)
defer clearLine(out)
s, err = term.ReadPassword(int(in.Fd()))
return err
})
return
}
// readCharacter reads a single character from the terminal with no echo. The
// prompt is ephemeral.
func readCharacter(prompt string) (c byte, err error) {
err = withTerminal(func(in, out *os.File) error {
fmt.Fprintf(out, "%s ", prompt)
defer clearLine(out)
oldState, err := term.MakeRaw(int(in.Fd()))
if err != nil {
return err
}
defer term.Restore(int(in.Fd()), oldState)
b := make([]byte, 1)
if _, err := in.Read(b); err != nil {
return err
}
c = b[0]
return nil
})
return
}
var pluginTerminalUI = &plugin.ClientUI{
DisplayMessage: func(name, message string) error {
printf("%s plugin: %s", name, message)
return nil
},
RequestValue: func(name, message string, _ bool) (s string, err error) {
defer func() {
if err != nil {
warningf("could not read value for age-plugin-%s: %v", name, err)
}
}()
secret, err := readSecret(message)
if err != nil {
return "", err
}
return string(secret), nil
},
Confirm: func(name, message, yes, no string) (choseYes bool, err error) {
defer func() {
if err != nil {
warningf("could not read value for age-plugin-%s: %v", name, err)
}
}()
if no == "" {
message += fmt.Sprintf(" (press enter for %q)", yes)
_, err := readSecret(message)
if err != nil {
return false, err
}
return true, nil
}
message += fmt.Sprintf(" (press [1] for %q or [2] for %q)", yes, no)
for {
selection, err := readCharacter(message)
if err != nil {
return false, err
}
switch selection {
case '1':
return true, nil
case '2':
return false, nil
case '\x03': // CTRL-C
return false, errors.New("user cancelled prompt")
default:
warningf("reading value for age-plugin-%s: invalid selection %q", name, selection)
}
}
},
WaitTimer: func(name string) {
printf("waiting on %s plugin...", name)
},
}

View File

@@ -76,9 +76,10 @@ func NewMasterKey(vaultURL string, keyName string, keyVersion string) *MasterKey
// NewMasterKeyFromURL takes an Azure Key Vault key URL, and returns a new
// MasterKey. The URL format is {vaultUrl}/keys/{keyName}/{keyVersion}.
func NewMasterKeyFromURL(url string) (*MasterKey, error) {
url = strings.TrimSpace(url)
re := regexp.MustCompile("^(https://[^/]+)/keys/([^/]+)/([^/]+)$")
parts := re.FindStringSubmatch(url)
if parts == nil || len(parts) < 3 {
if len(parts) < 3 {
return nil, fmt.Errorf("could not parse %q into a valid Azure Key Vault MasterKey", url)
}
return NewMasterKey(parts[1], parts[2], parts[3]), nil

View File

@@ -88,6 +88,23 @@ func TestMasterKeysFromURLs(t *testing.T) {
},
},
},
{
name: "multiple URLs with leading and trailing spaces",
urls: " https://test.vault.azure.net/keys/test-key/a2a690a4fcc04166b739da342a912c90 , https://test2.vault.azure.net/keys/another-test-key/cf0021e8b743453bae758e7fbf71b60e ",
expectKeyCount: 2,
expectKeys: []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: "multiple URLs, one malformed",
urls: "https://test.vault.azure.net/keys/test-key/a2a690a4fcc04166b739da342a912c90,https://test.vault.azure.net/no-keys-here/test-key/a2a690a4fcc04166b739da342a912c90",

View File

@@ -2,6 +2,7 @@ package common
import (
"fmt"
"io"
"os"
"path/filepath"
"time"
@@ -130,11 +131,20 @@ func EncryptTree(opts EncryptTreeOpts) error {
return nil
}
// LoadEncryptedFile loads an encrypted SOPS file, returning a SOPS tree
func LoadEncryptedFile(loader sops.EncryptedFileLoader, inputPath string) (*sops.Tree, error) {
fileBytes, err := os.ReadFile(inputPath)
if err != nil {
return nil, NewExitError(fmt.Sprintf("Error reading file: %s", err), codes.CouldNotReadInputFile)
// LoadEncryptedFileEx loads an encrypted SOPS file from a file or stdin, returning a SOPS tree
func LoadEncryptedFileEx(loader sops.EncryptedFileLoader, inputPath string, readFromStdin bool) (*sops.Tree, error) {
var fileBytes []byte
var err error
if readFromStdin {
fileBytes, err = io.ReadAll(os.Stdin)
if err != nil {
return nil, NewExitError(fmt.Sprintf("Error reading from stdin: %s", err), codes.CouldNotReadInputFile)
}
} else {
fileBytes, err = os.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 {
@@ -145,6 +155,11 @@ func LoadEncryptedFile(loader sops.EncryptedFileLoader, inputPath string) (*sops
return &tree, err
}
// LoadEncryptedFile loads an encrypted SOPS file, returning a SOPS tree
func LoadEncryptedFile(loader sops.EncryptedFileLoader, inputPath string) (*sops.Tree, error) {
return LoadEncryptedFileEx(loader, inputPath, false)
}
// 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 {
@@ -207,7 +222,7 @@ func GetKMSKeyWithEncryptionCtx(tree *sops.Tree) (keyGroupIndex int, keyIndex in
for n, k := range kg {
kmsKey, ok := k.(*kms.MasterKey)
if ok {
if kmsKey.EncryptionContext != nil && len(kmsKey.EncryptionContext) >= 2 {
if len(kmsKey.EncryptionContext) >= 2 {
duplicateValues := map[string]int{}
for _, v := range kmsKey.EncryptionContext {
duplicateValues[*v] = duplicateValues[*v] + 1
@@ -227,6 +242,7 @@ type GenericDecryptOpts struct {
Cipher sops.Cipher
InputStore sops.Store
InputPath string
ReadFromStdin bool
IgnoreMAC bool
KeyServices []keyservice.KeyServiceClient
DecryptionOrder []string
@@ -235,7 +251,7 @@ type GenericDecryptOpts struct {
// 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)
tree, err := LoadEncryptedFileEx(opts.InputStore, opts.InputPath, opts.ReadFromStdin)
if err != nil {
return nil, err
}
@@ -447,3 +463,17 @@ func PrettyPrintDiffs(diffs []Diff) {
}
}
}
// PrettyPrintShamirDiff prints changes in shamir_threshold to stdout
func PrettyPrintShamirDiff(oldValue, newValue int) {
if oldValue > 0 && oldValue == newValue {
fmt.Printf("shamir_threshold: %d\n", newValue)
} else {
if newValue > 0 {
color.New(color.FgGreen).Printf("+++ shamir_threshold: %d\n", newValue)
}
if oldValue > 0 {
color.New(color.FgRed).Printf("--- shamir_threshold: %d\n", oldValue)
}
}
}

View File

@@ -19,6 +19,7 @@ type decryptOpts struct {
InputStore sops.Store
OutputStore sops.Store
InputPath string
ReadFromStdin bool
IgnoreMAC bool
Extract []interface{}
KeyServices []keyservice.KeyServiceClient
@@ -27,11 +28,12 @@ type decryptOpts struct {
func decryptTree(opts decryptOpts) (tree *sops.Tree, err error) {
tree, err = common.LoadEncryptedFileWithBugFixes(common.GenericDecryptOpts{
Cipher: opts.Cipher,
InputStore: opts.InputStore,
InputPath: opts.InputPath,
IgnoreMAC: opts.IgnoreMAC,
KeyServices: opts.KeyServices,
Cipher: opts.Cipher,
InputStore: opts.InputStore,
InputPath: opts.InputPath,
ReadFromStdin: opts.ReadFromStdin,
IgnoreMAC: opts.IgnoreMAC,
KeyServices: opts.KeyServices,
})
if err != nil {
return nil, err

View File

@@ -245,7 +245,12 @@ func hashFile(filePath string) ([]byte, error) {
}
func runEditor(path string) error {
editor := os.Getenv("EDITOR")
envVar := "SOPS_EDITOR"
editor := os.Getenv(envVar)
if editor == "" {
envVar = "EDITOR"
editor = os.Getenv(envVar)
}
var cmd *exec.Cmd
if editor == "" {
editor, err := lookupAnyEditor("vim", "nano", "vi")
@@ -256,7 +261,7 @@ func runEditor(path string) error {
} else {
parts, err := shlex.Split(editor)
if err != nil {
return fmt.Errorf("invalid $EDITOR: %s", editor)
return fmt.Errorf("invalid $%s: %s", envVar, editor)
}
parts = append(parts, path)
cmd = exec.Command(parts[0], parts[1:]...)
@@ -275,5 +280,5 @@ func lookupAnyEditor(editorNames ...string) (editorPath string, err error) {
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, ", "))
return "", fmt.Errorf("no editor available: sops attempts to use the editor defined in the SOPS_EDITOR or EDITOR environment variables, and if that's not set defaults to any of %s, but none of them could be found", strings.Join(editorNames, ", "))
}

View File

@@ -2,6 +2,7 @@ package main
import (
"fmt"
"io"
"os"
"path/filepath"
@@ -27,11 +28,12 @@ type encryptConfig struct {
}
type encryptOpts struct {
Cipher sops.Cipher
InputStore sops.Store
OutputStore sops.Store
InputPath string
KeyServices []keyservice.KeyServiceClient
Cipher sops.Cipher
InputStore sops.Store
OutputStore sops.Store
InputPath string
ReadFromStdin bool
KeyServices []keyservice.KeyServiceClient
encryptConfig
}
@@ -78,9 +80,17 @@ func metadataFromEncryptionConfig(config encryptConfig) sops.Metadata {
func encrypt(opts encryptOpts) (encryptedFile []byte, err error) {
// Load the file
fileBytes, err := os.ReadFile(opts.InputPath)
if err != nil {
return nil, common.NewExitError(fmt.Sprintf("Error reading file: %s", err), codes.CouldNotReadInputFile)
var fileBytes []byte
if opts.ReadFromStdin {
fileBytes, err = io.ReadAll(os.Stdin)
if err != nil {
return nil, common.NewExitError(fmt.Sprintf("Error reading from stdin: %s", err), codes.CouldNotReadInputFile)
}
} else {
fileBytes, err = os.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 {

View File

@@ -137,7 +137,8 @@ func main() {
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.
To select a different editor than the default (vim), set SOPS_EDITOR or
EDITOR.
Note that flags must always be provided before the filename to operate on.
Otherwise, they will be ignored.
@@ -162,6 +163,10 @@ func main() {
Name: "user",
Usage: "the user to run the command as",
},
cli.BoolFlag{
Name: "same-process",
Usage: "run command in the current process instead of in a child process",
},
}, keyserviceFlags...),
Action: func(c *cli.Context) error {
if c.NArg() != 2 {
@@ -171,7 +176,10 @@ func main() {
fileName := c.Args()[0]
command := c.Args()[1]
inputStore := inputStore(c, fileName)
inputStore, err := inputStore(c, fileName)
if err != nil {
return toExitError(err)
}
svcs := keyservices(c)
@@ -191,6 +199,10 @@ func main() {
if c.Bool("background") {
log.Warn("exec-env's --background option is deprecated and will be removed in a future version of sops")
if c.Bool("same-process") {
return common.NewExitError("Error: The --same-process flag cannot be used with --background", codes.ErrorConflictingParameters)
}
}
tree, err := decryptTree(opts)
@@ -221,12 +233,13 @@ func main() {
}
if err := exec.ExecWithEnv(exec.ExecOpts{
Command: command,
Plaintext: []byte{},
Background: c.Bool("background"),
Pristine: c.Bool("pristine"),
User: c.String("user"),
Env: env,
Command: command,
Plaintext: []byte{},
Background: c.Bool("background"),
Pristine: c.Bool("pristine"),
User: c.String("user"),
SameProcess: c.Bool("same-process"),
Env: env,
}); err != nil {
return toExitError(err)
}
@@ -272,8 +285,14 @@ func main() {
fileName := c.Args()[0]
command := c.Args()[1]
inputStore := inputStore(c, fileName)
outputStore := outputStore(c, fileName)
inputStore, err := inputStore(c, fileName)
if err != nil {
return toExitError(err)
}
outputStore, err := outputStore(c, fileName)
if err != nil {
return toExitError(err)
}
svcs := keyservices(c)
@@ -340,9 +359,15 @@ func main() {
if c.Bool("verbose") || c.GlobalBool("verbose") {
logging.SetLevel(logrus.DebugLevel)
}
configPath, err := config.FindConfigFile(".")
if err != nil {
return common.NewExitError(err, codes.ErrorGeneric)
var configPath string
var err error
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)
@@ -365,13 +390,17 @@ func main() {
return toExitError(err)
}
if !info.IsDir() {
inputStore, err := inputStore(c, subPath)
if err != nil {
return toExitError(err)
}
err = publishcmd.Run(publishcmd.Opts{
ConfigPath: configPath,
InputPath: subPath,
Cipher: aes.NewCipher(),
KeyServices: keyservices(c),
DecryptionOrder: order,
InputStore: inputStore(c, subPath),
InputStore: inputStore,
Interactive: !c.Bool("yes"),
OmitExtensions: c.Bool("omit-extensions"),
Recursive: c.Bool("recursive"),
@@ -433,14 +462,22 @@ func main() {
Name: "filestatus",
Usage: "check the status of the file, returning encryption status",
ArgsUsage: `file`,
Flags: []cli.Flag{},
Flags: []cli.Flag{
cli.StringFlag{
Name: "input-type",
Usage: "currently ini, json, yaml, dotenv and binary are supported. If not set, sops will use the file's extension to determine the type",
},
},
Action: func(c *cli.Context) error {
if c.NArg() < 1 {
return common.NewExitError("Error: no file specified", codes.NoFileSpecified)
}
fileName := c.Args()[0]
inputStore := inputStore(c, fileName)
inputStore, err := inputStore(c, fileName)
if err != nil {
return toExitError(err)
}
opts := filestatuscmd.Opts{
InputStore: inputStore,
InputPath: fileName,
@@ -560,11 +597,19 @@ func main() {
group = append(group, key)
}
}
inputStore, err := inputStore(c, c.String("file"))
if err != nil {
return toExitError(err)
}
outputStore, err := outputStore(c, c.String("file"))
if err != nil {
return toExitError(err)
}
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")),
InputStore: inputStore,
OutputStore: outputStore,
Group: group,
GroupThreshold: c.Int("shamir-secret-sharing-threshold"),
KeyServices: keyservices(c),
@@ -599,11 +644,19 @@ func main() {
return fmt.Errorf("failed to parse [index] argument: %s", err)
}
inputStore, err := inputStore(c, c.String("file"))
if err != nil {
return toExitError(err)
}
outputStore, err := outputStore(c, c.String("file"))
if err != nil {
return toExitError(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")),
InputStore: inputStore,
OutputStore: outputStore,
Group: uint(group),
GroupThreshold: c.Int("shamir-secret-sharing-threshold"),
KeyServices: keyservices(c),
@@ -643,12 +696,12 @@ func main() {
failedCounter := 0
for _, path := range c.Args() {
err := updatekeys.UpdateKeys(updatekeys.Opts{
InputPath: path,
GroupQuorum: c.Int("shamir-secret-sharing-quorum"),
KeyServices: keyservices(c),
Interactive: !c.Bool("yes"),
ConfigPath: configPath,
InputType: c.String("input-type"),
InputPath: path,
ShamirThreshold: c.Int("shamir-secret-sharing-threshold"),
KeyServices: keyservices(c),
Interactive: !c.Bool("yes"),
ConfigPath: configPath,
InputType: c.String("input-type"),
})
if c.NArg() == 1 {
@@ -675,8 +728,8 @@ func main() {
},
{
Name: "decrypt",
Usage: "decrypt a file, and output the results to stdout",
ArgsUsage: `file`,
Usage: "decrypt a file, and output the results to stdout. If no filename is provided, stdin will be used.",
ArgsUsage: `[file]`,
Flags: append([]cli.Flag{
cli.BoolFlag{
Name: "in-place, i",
@@ -704,7 +757,7 @@ func main() {
},
cli.StringFlag{
Name: "filename-override",
Usage: "Use this filename instead of the provided argument for loading configuration, and for determining input type and output type",
Usage: "Use this filename instead of the provided argument for loading configuration, and for determining input type and output type. Should be provided when reading from stdin.",
},
cli.StringFlag{
Name: "decryption-order",
@@ -716,27 +769,43 @@ func main() {
if c.Bool("verbose") {
logging.SetLevel(logrus.DebugLevel)
}
if c.NArg() < 1 {
return common.NewExitError("Error: no file specified", codes.NoFileSpecified)
readFromStdin := c.NArg() == 0
if readFromStdin && c.Bool("in-place") {
return common.NewExitError("Error: cannot use --in-place when reading from stdin", codes.ErrorConflictingParameters)
}
warnMoreThanOnePositionalArgument(c)
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) {
return common.NewExitError("Error: cannot operate on non-existent file", codes.NoFileSpecified)
var fileName string
var err error
if !readFromStdin {
fileName, err = filepath.Abs(c.Args()[0])
if err != nil {
return toExitError(err)
}
if _, err := os.Stat(fileName); os.IsNotExist(err) {
return common.NewExitError(fmt.Sprintf("Error: cannot operate on non-existent file %q", fileName), codes.NoFileSpecified)
}
}
fileNameOverride := c.String("filename-override")
if fileNameOverride == "" {
fileNameOverride = fileName
} else {
fileNameOverride, err = filepath.Abs(fileNameOverride)
if err != nil {
return toExitError(err)
}
}
inputStore := inputStore(c, fileNameOverride)
outputStore := outputStore(c, fileNameOverride)
inputStore, err := inputStore(c, fileNameOverride)
if err != nil {
return toExitError(err)
}
outputStore, err := outputStore(c, fileNameOverride)
if err != nil {
return toExitError(err)
}
svcs := keyservices(c)
order, err := decryptionOrder(c.String("decryption-order"))
@@ -753,6 +822,7 @@ func main() {
OutputStore: outputStore,
InputStore: inputStore,
InputPath: fileName,
ReadFromStdin: readFromStdin,
Cipher: aes.NewCipher(),
Extract: extract,
KeyServices: svcs,
@@ -794,8 +864,8 @@ func main() {
},
{
Name: "encrypt",
Usage: "encrypt a file, and output the results to stdout",
ArgsUsage: `file`,
Usage: "encrypt a file, and output the results to stdout. If no filename is provided, stdin will be used.",
ArgsUsage: `[file]`,
Flags: append([]cli.Flag{
cli.BoolFlag{
Name: "in-place, i",
@@ -873,34 +943,55 @@ func main() {
},
cli.StringFlag{
Name: "filename-override",
Usage: "Use this filename instead of the provided argument for loading configuration, and for determining input type and output type",
Usage: "Use this filename instead of the provided argument for loading configuration, and for determining input type and output type. Required when reading from stdin.",
},
}, keyserviceFlags...),
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)
readFromStdin := c.NArg() == 0
if readFromStdin {
if c.Bool("in-place") {
return common.NewExitError("Error: cannot use --in-place when reading from stdin", codes.ErrorConflictingParameters)
}
if c.String("filename-override") == "" {
return common.NewExitError("Error: must specify --filename-override when reading from stdin", codes.ErrorConflictingParameters)
}
}
warnMoreThanOnePositionalArgument(c)
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) {
return common.NewExitError("Error: cannot operate on non-existent file", codes.NoFileSpecified)
var fileName string
var err error
if !readFromStdin {
fileName, err = filepath.Abs(c.Args()[0])
if err != nil {
return toExitError(err)
}
if _, err := os.Stat(fileName); os.IsNotExist(err) {
return common.NewExitError(fmt.Sprintf("Error: cannot operate on non-existent file %q", fileName), codes.NoFileSpecified)
}
}
fileNameOverride := c.String("filename-override")
if fileNameOverride == "" {
fileNameOverride = fileName
} else {
fileNameOverride, err = filepath.Abs(fileNameOverride)
if err != nil {
return toExitError(err)
}
}
inputStore := inputStore(c, fileNameOverride)
outputStore := outputStore(c, fileNameOverride)
inputStore, err := inputStore(c, fileNameOverride)
if err != nil {
return toExitError(err)
}
outputStore, err := outputStore(c, fileNameOverride)
if err != nil {
return toExitError(err)
}
svcs := keyservices(c)
encConfig, err := getEncryptConfig(c, fileNameOverride)
@@ -911,6 +1002,7 @@ func main() {
OutputStore: outputStore,
InputStore: inputStore,
InputPath: fileName,
ReadFromStdin: readFromStdin,
Cipher: aes.NewCipher(),
KeyServices: svcs,
encryptConfig: encConfig,
@@ -1050,16 +1142,27 @@ func main() {
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 the `edit` subcommand instead.", codes.CannotChangeKeysFromNonExistentFile)
return common.NewExitError(fmt.Sprintf("Error: cannot add or remove keys on non-existent file %q, use the `edit` subcommand instead.", fileName), codes.CannotChangeKeysFromNonExistentFile)
}
}
fileNameOverride := c.String("filename-override")
if fileNameOverride == "" {
fileNameOverride = fileName
} else {
fileNameOverride, err = filepath.Abs(fileNameOverride)
if err != nil {
return toExitError(err)
}
}
inputStore := inputStore(c, fileNameOverride)
outputStore := outputStore(c, fileNameOverride)
inputStore, err := inputStore(c, fileNameOverride)
if err != nil {
return toExitError(err)
}
outputStore, err := outputStore(c, fileNameOverride)
if err != nil {
return toExitError(err)
}
svcs := keyservices(c)
order, err := decryptionOrder(c.String("decryption-order"))
@@ -1202,12 +1305,15 @@ func main() {
if err != nil {
return toExitError(err)
}
if _, err := os.Stat(fileName); os.IsNotExist(err) {
return common.NewExitError("Error: cannot operate on non-existent file", codes.NoFileSpecified)
}
inputStore := inputStore(c, fileName)
outputStore := outputStore(c, fileName)
inputStore, err := inputStore(c, fileName)
if err != nil {
return toExitError(err)
}
outputStore, err := outputStore(c, fileName)
if err != nil {
return toExitError(err)
}
svcs := keyservices(c)
order, err := decryptionOrder(c.String("decryption-order"))
@@ -1288,6 +1394,10 @@ func main() {
Usage: "comma separated list of decryption key types",
EnvVar: "SOPS_DECRYPTION_ORDER",
},
cli.BoolFlag{
Name: "idempotent",
Usage: "do nothing if the given index already has the given value",
},
}, keyserviceFlags...),
Action: func(c *cli.Context) error {
if c.Bool("verbose") {
@@ -1301,8 +1411,14 @@ func main() {
return toExitError(err)
}
inputStore := inputStore(c, fileName)
outputStore := outputStore(c, fileName)
inputStore, err := inputStore(c, fileName)
if err != nil {
return toExitError(err)
}
outputStore, err := outputStore(c, fileName)
if err != nil {
return toExitError(err)
}
svcs := keyservices(c)
path, err := parseTreePath(c.Args()[1])
@@ -1319,7 +1435,7 @@ func main() {
if err != nil {
return toExitError(err)
}
output, err := set(setOpts{
output, changed, err := set(setOpts{
OutputStore: outputStore,
InputStore: inputStore,
InputPath: fileName,
@@ -1334,6 +1450,11 @@ func main() {
return toExitError(err)
}
if !changed && c.Bool("idempotent") {
log.Info("File not written due to no change")
return nil
}
// We open the file *after* the operations on the tree have been
// executed to avoid truncating it when there's errors
file, err := os.Create(fileName)
@@ -1376,8 +1497,8 @@ func main() {
EnvVar: "SOPS_DECRYPTION_ORDER",
},
cli.BoolFlag{
Name: "idempotent",
Usage: "do nothing if the given index does not exist",
Name: "idempotent",
Usage: "do nothing if the given index does not exist",
},
}, keyserviceFlags...),
Action: func(c *cli.Context) error {
@@ -1392,8 +1513,14 @@ func main() {
return toExitError(err)
}
inputStore := inputStore(c, fileName)
outputStore := outputStore(c, fileName)
inputStore, err := inputStore(c, fileName)
if err != nil {
return toExitError(err)
}
outputStore, err := outputStore(c, fileName)
if err != nil {
return toExitError(err)
}
svcs := keyservices(c)
path, err := parseTreePath(c.Args()[1])
@@ -1452,8 +1579,9 @@ func main() {
Usage: "generate a new data encryption key and reencrypt all values with the new key",
},
cli.BoolFlag{
Name: "disable-version-check",
Usage: "do not check whether the current version is latest during --version",
Name: "disable-version-check",
Usage: "do not check whether the current version is latest during --version",
EnvVar: "SOPS_DISABLE_VERSION_CHECK",
},
cli.StringFlag{
Name: "kms, k",
@@ -1592,6 +1720,7 @@ func main() {
cli.StringFlag{
Name: "config",
Usage: "path to sops' config file. If set, sops will not search for the config file recursively.",
EnvVar: "SOPS_CONFIG",
},
cli.StringFlag{
Name: "encryption-context",
@@ -1652,15 +1781,20 @@ func main() {
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)
return common.NewExitError(fmt.Sprintf("Error: cannot add or remove keys on non-existent file %q, use `--kms` and `--pgp` instead.", fileName), codes.CannotChangeKeysFromNonExistentFile)
}
if isEncryptMode || isDecryptMode || isRotateMode {
return common.NewExitError("Error: cannot operate on non-existent file", codes.NoFileSpecified)
return common.NewExitError(fmt.Sprintf("Error: cannot operate on non-existent file %q", fileName), codes.NoFileSpecified)
}
}
fileNameOverride := c.String("filename-override")
if fileNameOverride == "" {
fileNameOverride = fileName
} else {
fileNameOverride, err = filepath.Abs(fileNameOverride)
if err != nil {
return toExitError(err)
}
}
commandCount := 0
@@ -1690,8 +1824,14 @@ func main() {
}
}
inputStore := inputStore(c, fileNameOverride)
outputStore := outputStore(c, fileNameOverride)
inputStore, err := inputStore(c, fileNameOverride)
if err != nil {
return toExitError(err)
}
outputStore, err := outputStore(c, fileNameOverride)
if err != nil {
return toExitError(err)
}
svcs := keyservices(c)
order, err := decryptionOrder(c.String("decryption-order"))
@@ -1758,7 +1898,7 @@ func main() {
if err != nil {
return toExitError(err)
}
output, err = set(setOpts{
output, _, err = set(setOpts{
OutputStore: outputStore,
InputStore: inputStore,
InputPath: fileName,
@@ -2030,7 +2170,7 @@ func keyservices(c *cli.Context) (svcs []keyservice.KeyServiceClient) {
"address",
fmt.Sprintf("%s://%s", url.Scheme, addr),
).Infof("Connecting to key service")
conn, err := grpc.Dial(addr, opts...)
conn, err := grpc.NewClient(addr, opts...)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
@@ -2040,10 +2180,8 @@ func keyservices(c *cli.Context) (svcs []keyservice.KeyServiceClient) {
}
func loadStoresConfig(context *cli.Context, path string) (*config.StoresConfig, error) {
var configPath string
if context.String("config") != "" {
configPath = context.String("config")
} else {
configPath := context.GlobalString("config")
if configPath == "" {
// Ignore config not found errors returned from FindConfigFile since the config file is not mandatory
foundPath, err := config.FindConfigFile(".")
if err != nil {
@@ -2054,13 +2192,19 @@ func loadStoresConfig(context *cli.Context, path string) (*config.StoresConfig,
return config.LoadStoresConfig(configPath)
}
func inputStore(context *cli.Context, path string) common.Store {
storesConf, _ := loadStoresConfig(context, path)
return common.DefaultStoreForPathOrFormat(storesConf, path, context.String("input-type"))
func inputStore(context *cli.Context, path string) (common.Store, error) {
storesConf, err := loadStoresConfig(context, path)
if err != nil {
return nil, err
}
return common.DefaultStoreForPathOrFormat(storesConf, path, context.String("input-type")), nil
}
func outputStore(context *cli.Context, path string) common.Store {
storesConf, _ := loadStoresConfig(context, path)
func outputStore(context *cli.Context, path string) (common.Store, error) {
storesConf, err := loadStoresConfig(context, path)
if err != nil {
return nil, err
}
if context.IsSet("indent") {
indent := context.Int("indent")
storesConf.YAML.Indent = indent
@@ -2068,7 +2212,7 @@ func outputStore(context *cli.Context, path string) common.Store {
storesConf.JSONBinary.Indent = indent
}
return common.DefaultStoreForPathOrFormat(storesConf, path, context.String("output-type"))
return common.DefaultStoreForPathOrFormat(storesConf, path, context.String("output-type")), nil
}
func parseTreePath(arg string) ([]interface{}, error) {
@@ -2159,7 +2303,7 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) {
if err != nil {
errMsg = fmt.Sprintf("%s: %s", errMsg, err)
}
return nil, fmt.Errorf(errMsg)
return nil, fmt.Errorf("%s", errMsg)
}
return conf.KeyGroups, err
}
@@ -2178,10 +2322,8 @@ func keyGroups(c *cli.Context, file string) ([]sops.KeyGroup, error) {
// 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 {
configPath := c.GlobalString("config")
if configPath == "" {
// Ignore config not found errors returned from FindConfigFile since the config file is not mandatory
configPath, err = config.FindConfigFile(".")
if err != nil {

View File

@@ -21,7 +21,7 @@ type setOpts struct {
DecryptionOrder []string
}
func set(opts setOpts) ([]byte, error) {
func set(opts setOpts) ([]byte, bool, 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{
@@ -32,7 +32,7 @@ func set(opts setOpts) ([]byte, error) {
KeyServices: opts.KeyServices,
})
if err != nil {
return nil, err
return nil, false, err
}
// Decrypt the file
@@ -44,22 +44,23 @@ func set(opts setOpts) ([]byte, error) {
DecryptionOrder: opts.DecryptionOrder,
})
if err != nil {
return nil, err
return nil, false, err
}
// Set the value
tree.Branches[0] = tree.Branches[0].Set(opts.TreePath, opts.Value)
var changed bool
tree.Branches[0], changed = 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
return nil, false, 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 nil, false, common.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree)
}
return encryptedFile, err
return encryptedFile, changed, err
}

View File

@@ -2,6 +2,7 @@ package exec
import (
"bytes"
"fmt"
"os"
"path/filepath"
"runtime"
@@ -23,14 +24,15 @@ func init() {
}
type ExecOpts struct {
Command string
Plaintext []byte
Background bool
Pristine bool
Fifo bool
User string
Filename string
Env []string
Command string
Plaintext []byte
Background bool
SameProcess bool
Pristine bool
Fifo bool
User string
Filename string
Env []string
}
func GetFile(dir, filename string) *os.File {
@@ -115,6 +117,10 @@ func ExecWithEnv(opts ExecOpts) error {
SwitchUser(opts.User)
}
if runtime.GOOS == "windows" && opts.SameProcess {
return fmt.Errorf("The --same-process flag is not supported on Windows")
}
var env []string
if !opts.Pristine {
@@ -134,6 +140,15 @@ func ExecWithEnv(opts ExecOpts) error {
env = append(env, opts.Env...)
if opts.SameProcess {
if opts.Background {
log.Fatal("background is not supported for same-process")
}
// Note that the call does NOT return, unless an error happens.
return ExecSyscall(opts.Command, env)
}
cmd := BuildCommand(opts.Command)
cmd.Env = env

View File

@@ -11,6 +11,10 @@ import (
"syscall"
)
func ExecSyscall(command string, env []string) error {
return syscall.Exec("/bin/sh", []string{"/bin/sh", "-c", command}, env)
}
func BuildCommand(command string) *exec.Cmd {
return exec.Command("/bin/sh", "-c", command)
}

View File

@@ -4,6 +4,11 @@ import (
"os/exec"
)
func ExecSyscall(command string, env []string) error {
log.Fatal("same-process not available on windows")
return nil
}
func BuildCommand(command string) *exec.Cmd {
return exec.Command("cmd.exe", "/C", command)
}

View File

@@ -15,7 +15,7 @@ import (
// Opts represents key operation options and config
type Opts struct {
InputPath string
GroupQuorum int
ShamirThreshold int
KeyServices []keyservice.KeyServiceClient
DecryptionOrder []string
Interactive bool
@@ -45,7 +45,7 @@ func updateFile(opts Opts) error {
if err != nil {
return err
}
store := common.DefaultStoreForPath(sc, opts.InputPath)
store := common.DefaultStoreForPathOrFormat(sc, opts.InputPath, opts.InputType)
log.Printf("Syncing keys for file %s", opts.InputPath)
tree, err := common.LoadEncryptedFile(store, opts.InputPath)
if err != nil {
@@ -66,11 +66,22 @@ func updateFile(opts Opts) error {
keysWillChange = true
}
}
if !keysWillChange {
// TODO: use conf.ShamirThreshold instead of tree.Metadata.ShamirThreshold in the next line?
// Or make this configurable?
var shamirThreshold = tree.Metadata.ShamirThreshold
if opts.ShamirThreshold != 0 {
shamirThreshold = opts.ShamirThreshold
}
shamirThreshold = min(shamirThreshold, len(conf.KeyGroups))
var shamirThresholdWillChange = tree.Metadata.ShamirThreshold != shamirThreshold
if !keysWillChange && !shamirThresholdWillChange {
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.PrettyPrintShamirDiff(tree.Metadata.ShamirThreshold, shamirThreshold)
common.PrettyPrintDiffs(diffs)
if opts.Interactive {
@@ -92,10 +103,7 @@ func updateFile(opts Opts) error {
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))
tree.Metadata.ShamirThreshold = shamirThreshold
errs := tree.Metadata.UpdateMasterKeysWithKeyServices(key, opts.KeyServices)
if len(errs) > 0 {
return fmt.Errorf("error updating one or more master keys: %s", errs)

View File

@@ -17,19 +17,11 @@ import (
"github.com/getsops/sops/v3/gcpkms"
"github.com/getsops/sops/v3/hcvault"
"github.com/getsops/sops/v3/kms"
"github.com/getsops/sops/v3/logging"
"github.com/getsops/sops/v3/pgp"
"github.com/getsops/sops/v3/publish"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)
var log *logrus.Logger
func init() {
log = logging.NewLogger("CONFIG")
}
type fileSystem interface {
Stat(name string) (os.FileInfo, error)
}
@@ -377,19 +369,17 @@ func parseDestinationRuleForFile(conf *configFile, filePath string, kmsEncryptio
}
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)
}
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)

View File

@@ -1,10 +1,12 @@
package config
import (
"fmt"
"os"
"path"
"testing"
"github.com/getsops/sops/v3/keys"
"github.com/stretchr/testify/assert"
)
@@ -94,6 +96,20 @@ creation_rules:
- kms:
- arn: foo
aws_profile: bar
- arn: foo
context:
baz: bam
- arn: foo
aws_profile: bar
context:
baz: bam
- arn: foo
role: '123'
- arn: foo
aws_profile: bar
context:
baz: bam
role: '123'
pgp:
- bar
gcp_kms:
@@ -129,113 +145,124 @@ creation_rules:
- 'https://foo.vault:8200/v1/foo/keys/foo-key'
- merge:
- merge:
- kms:
- pgp:
# key01
- foo
kms:
# key02
- arn: foo
aws_profile: foo
pgp:
# key02
- foo
gcp_kms:
# key03
- arn: foo
aws_profile: bar
context:
baz: bam
role: '123'
gcp_kms:
# key04
- resource_id: foo
azure_keyvault:
# key04
# key05
- vaultUrl: https://foo.vault.azure.net
key: foo-key
version: fooversion
hc_vault:
# key05
- 'https://bar.vault:8200/v1/bar/keys/bar-key'
- kms:
# key06
- arn: bar
aws_profile: bar
pgp:
- 'https://bar.vault:8200/v1/bar/keys/bar-key'
- pgp:
# key07
- bar
gcp_kms:
kms:
# key08
- resource_id: bar
- arn: bar
aws_profile: bar
gcp_kms:
# key09
- resource_id: bar
# key10
- resource_id: baz
azure_keyvault:
# key10
# key11
- vaultUrl: https://bar.vault.azure.net
key: bar-key
version: barversion
hc_vault:
# key01 - duplicate#1
# key12
- 'https://baz.vault:8200/v1/baz/keys/baz-key'
pgp:
# key13
- baz
kms:
# key11
# key14
- arn: baz
aws_profile: baz
pgp:
# key12
- baz
gcp_kms:
# key03 - duplicate#2
# --> should be removed when loading config
# duplicate of key09
- resource_id: bar
azure_keyvault:
# key04 - duplicate#3
# duplicate of key05
- vaultUrl: https://foo.vault.azure.net
key: foo-key
version: fooversion
hc_vault:
# key13 - duplicate#4 - but from different key_group
# --> should stay
# key15 (duplicate of key00, but that's in a different key_group)
- 'https://foo.vault:8200/v1/foo/keys/foo-key'
- kms:
# key14
- pgp:
# key16
- qux
kms:
# key17
- arn: qux
aws_profile: qux
# key14 - duplicate#5
# key18
- arn: baz
aws_profile: bar
pgp:
# key15
- qux
# key19
- arn: baz
role: '123'
gcp_kms:
# key16
# key20
- resource_id: qux
# key17
# key21
- resource_id: fnord
azure_keyvault:
# key18
# key22
- vaultUrl: https://baz.vault.azure.net
key: baz-key
version: bazversion
hc_vault:
# key19
# key23
- 'https://qux.vault:8200/v1/qux/keys/qux-key'
# everything below this should be loaded,
# since it is not in a merge block
pgp:
# duplicate of key07
- bar
kms:
# duplicated key06
# duplicate of key08
- arn: bar
aws_profile: bar
# key20
# key24
- arn: fnord
aws_profile: fnord
pgp:
# duplicated key07
- bar
# duplicate of key03
- arn: foo
aws_profile: bar
context:
baz: bam
role: '123'
gcp_kms:
# duplicated key08
# duplicate of key09
- resource_id: bar
# key21
# duplicate of key21
- resource_id: fnord
azure_keyvault:
# duplicated key10
# duplicate of key11
- vaultUrl: https://bar.vault.azure.net
key: bar-key
version: barversion
hc_vault:
# duplicated 'key01 - duplicate#2'
# duplicate of key12
- 'https://baz.vault:8200/v1/baz/keys/baz-key'
# key22
# key25
- 'https://fnord.vault:8200/v1/fnord/keys/fnord-key'
`)
@@ -421,6 +448,7 @@ func TestLoadConfigFile(t *testing.T) {
}
func TestLoadConfigFileWithGroups(t *testing.T) {
bam := "bam"
expected := configFile{
CreationRules: []creationRule{
{
@@ -432,7 +460,37 @@ func TestLoadConfigFileWithGroups(t *testing.T) {
PathRegex: "",
KeyGroups: []keyGroup{
{
KMS: []kmsKey{{Arn: "foo", AwsProfile: "bar"}},
KMS: []kmsKey{
{
Arn: "foo",
AwsProfile: "bar",
},
{
Arn: "foo",
Context: map[string]*string{
"baz": &bam,
},
},
{
Arn: "foo",
AwsProfile: "bar",
Context: map[string]*string{
"baz": &bam,
},
},
{
Arn: "foo",
Role: "123",
},
{
Arn: "foo",
AwsProfile: "bar",
Context: map[string]*string{
"baz": &bam,
},
Role: "123",
},
},
PGP: []string{"bar"},
GCPKMS: []gcpKmsKey{{ResourceID: "foo"}},
AzureKV: []azureKVKey{{VaultURL: "https://foo.vault.azure.net", Key: "foo-key", Version: "fooversion"}},
@@ -459,12 +517,52 @@ func TestLoadConfigFileWithGroups(t *testing.T) {
assert.Equal(t, expected, conf)
}
func id(key keys.MasterKey) string {
return fmt.Sprintf("%s: %s", key.TypeToIdentifier(), key.ToString())
}
func ids(keys []keys.MasterKey) []string {
result := make([]string, 0, len(keys))
for _, key := range keys {
result = append(result, id(key))
}
return result
}
func TestLoadConfigFileWithMerge(t *testing.T) {
conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithMergeType, t), "/conf/path", "whatever", nil)
assert.Nil(t, err)
assert.Equal(t, 2, len(conf.KeyGroups))
assert.Equal(t, 1, len(conf.KeyGroups[0]))
assert.Equal(t, 22, len(conf.KeyGroups[1]))
assert.Equal(t, []string{
"hc_vault: https://foo.vault:8200/v1/foo/keys/foo-key",
}, ids(conf.KeyGroups[0]))
assert.Equal(t, []string{
"pgp: foo", // key01
"kms: foo||foo", //key02
"kms: foo+123|baz:bam|bar", //key03
"gcp_kms: foo", //key04
"azure_kv: https://foo.vault.azure.net/keys/foo-key/fooversion", //key05
"hc_vault: https://bar.vault:8200/v1/bar/keys/bar-key", //key06
"pgp: bar", //key07
"kms: bar||bar", //key08
"gcp_kms: bar", //key09
"gcp_kms: baz", //key10
"azure_kv: https://bar.vault.azure.net/keys/bar-key/barversion", //key11
"hc_vault: https://baz.vault:8200/v1/baz/keys/baz-key", //key12
"pgp: baz", //key13
"kms: baz||baz", //key14
"hc_vault: https://foo.vault:8200/v1/foo/keys/foo-key", //key15
"pgp: qux", //key16
"kms: qux||qux", //key17
"kms: baz||bar", //key18
"kms: baz+123", //key19
"gcp_kms: qux", //key20
"gcp_kms: fnord", //key21
"azure_kv: https://baz.vault.azure.net/keys/baz-key/bazversion", //key22
"hc_vault: https://qux.vault:8200/v1/qux/keys/qux-key", //key23
"kms: fnord||fnord", //key24
"hc_vault: https://fnord.vault:8200/v1/fnord/keys/fnord-key", //key25
}, ids(conf.KeyGroups[1]))
}
func TestLoadConfigFileWithNoMatchingRules(t *testing.T) {
@@ -538,9 +636,13 @@ 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, "foo||bar", conf.KeyGroups[0][1].ToString())
assert.Equal(t, "foo|baz:bam", conf.KeyGroups[0][2].ToString())
assert.Equal(t, "foo|baz:bam|bar", conf.KeyGroups[0][3].ToString())
assert.Equal(t, "foo+123", conf.KeyGroups[0][4].ToString())
assert.Equal(t, "foo+123|baz:bam|bar", conf.KeyGroups[0][5].ToString())
assert.Equal(t, "qux", conf.KeyGroups[1][0].ToString())
assert.Equal(t, "baz", conf.KeyGroups[1][1].ToString())
assert.Equal(t, "baz||foo", conf.KeyGroups[1][1].ToString())
}
func TestLoadConfigFileWithUnencryptedSuffix(t *testing.T) {

View File

@@ -37,7 +37,7 @@ This configuration is quite sophisticated, and ensures at least the following:
`main` branch. At present, this means that all pull requests attached to the
milestone for the release are merged. If there are any pull requests that
should not be included in the release, move them to a different milestone.
- [ ] Create a pull request to update the [`CHANGELOG.rst`](../CHANGELOG.rst)
- [ ] Create a pull request to update the [`CHANGELOG.md`](../CHANGELOG.md)
file. This should include a summary of all changes since the last release,
including references to any relevant pull requests.
- [ ] In this same pull request, update the version number in `version/version.go`

View File

@@ -11,6 +11,12 @@ creation_rules:
- FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4
- pgp:
- B611A2F9F11D0FF82568805119F9B5DAEA91FF86
- path_regex: test_no_keygroups.yaml
- path_regex: test_zero_keygroups.yaml
key_groups: []
- path_regex: test_empty_keygroup.yaml
key_groups:
- {}
- pgp: FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4
destination_rules:
- s3_bucket: "sops-publish-functional-tests"

322
functional-tests/Cargo.lock generated Normal file
View File

@@ -0,0 +1,322 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "functional-tests"
version = "0.1.0"
dependencies = [
"lazy_static",
"serde",
"serde_derive",
"serde_json",
"serde_yaml",
"tempfile",
]
[[package]]
name = "getrandom"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
dependencies = [
"cfg-if",
"libc",
"wasi",
"windows-targets",
]
[[package]]
name = "hashbrown"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "indexmap"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "itoa"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.168"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
[[package]]
name = "linux-raw-sys"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "proc-macro2"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustix"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "ryu"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "syn"
version = "2.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
dependencies = [
"fastrand",
"getrandom",
"once_cell",
"rustix",
"windows-sys",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "wasi"
version = "0.13.3+wasi-0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
dependencies = [
"wit-bindgen-rt",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [
"bitflags",
]

View File

@@ -5,9 +5,9 @@ edition = "2021"
authors = ["Adrian Utrilla <adrianutrilla@gmail.com>"]
[dependencies]
tempdir = "0.3.5"
tempfile = "3.19.1"
serde = "1.0"
serde_json = "1.0.99"
serde_yaml = "0.9.22"
serde_json = "1.0.140"
serde_yaml = "0.9.34"
serde_derive = "1.0"
lazy_static = "1.4.0"
lazy_static = "1.5.0"

View File

@@ -1,11 +1,9 @@
#[cfg_attr(test, macro_use)]
extern crate lazy_static;
extern crate serde;
extern crate serde_json;
extern crate serde_yaml;
extern crate tempdir;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate serde_derive;
extern crate tempfile;
#[cfg(test)]
mod tests {
@@ -16,10 +14,11 @@ mod tests {
use serde_yaml::Value;
use std::env;
use std::fs::File;
use std::io::{Read, Write};
use std::io::{BufWriter, Read, Write};
use std::path::Path;
use std::process::Command;
use tempdir::TempDir;
use std::process::{Child, Command, Stdio};
use tempfile::Builder;
use tempfile::TempDir;
const SOPS_BINARY_PATH: &'static str = "./sops";
const KMS_KEY: &'static str = "FUNCTIONAL_TEST_KMS_ARN";
@@ -36,8 +35,10 @@ mod tests {
}
lazy_static! {
static ref TMP_DIR: TempDir =
TempDir::new("sops-functional-tests").expect("Unable to create temporary directory");
static ref TMP_DIR: TempDir = Builder::new()
.prefix("sops-functional-tests")
.tempdir()
.expect("Unable to create temporary directory");
}
fn prepare_temp_file(name: &str, contents: &[u8]) -> String {
@@ -80,6 +81,47 @@ mod tests {
}
}
fn write_to_stdin(process: &Child, content: &[u8]) {
let mut outstdin = process.stdin.as_ref().unwrap();
let mut writer = BufWriter::new(&mut outstdin);
writer.write_all(content).expect("Cannot write to stdin");
}
#[test]
fn encrypt_from_stdin() {
let process = Command::new(SOPS_BINARY_PATH)
.arg("encrypt")
.arg("--filename-override")
.arg("test_encrypt.yaml")
.arg("--output-type")
.arg("json")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Error running sops");
write_to_stdin(
&process,
b"foo: 2
bar: baz
",
);
let output = process.wait_with_output().expect("Failed to wait on 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() {
@@ -295,6 +337,115 @@ bar: baz",
panic!("Output JSON does not have the expected structure");
}
#[test]
fn set_json_file_update_idempotent_write() {
let file_path = prepare_temp_file(
"test_set_update_idempotent_write.json",
r#"{"a": 2, "b": "ba"}"#.as_bytes(),
);
assert!(
Command::new(SOPS_BINARY_PATH)
.arg("encrypt")
.arg("-i")
.arg(file_path.clone())
.output()
.expect("Error running sops")
.status
.success(),
"sops didn't exit successfully"
);
let mut before = String::new();
File::open(file_path.clone())
.unwrap()
.read_to_string(&mut before)
.unwrap();
let output = Command::new(SOPS_BINARY_PATH)
.arg("set")
.arg("--output-type")
.arg("yaml")
.arg(file_path.clone())
.arg(r#"["b"]"#)
.arg(r#""ba""#)
.output()
.expect("Error running sops");
println!(
"stdout: {}, stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(output.status.success(), "sops didn't exit successfully");
let mut after = String::new();
File::open(file_path.clone())
.unwrap()
.read_to_string(&mut after)
.unwrap();
assert!(before != after);
assert!(after.starts_with("a: "));
let output = Command::new(SOPS_BINARY_PATH)
.arg("decrypt")
.arg("--input-type")
.arg("yaml")
.arg("--output-type")
.arg("yaml")
.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 data = &String::from_utf8_lossy(&output.stdout);
assert!(data == "a: 2\nb: ba\n");
}
#[test]
fn set_json_file_update_idempotent_nowrite() {
let file_path = prepare_temp_file(
"test_set_update_idempotent_nowrite.json",
r#"{"a": 2, "b": "ba"}"#.as_bytes(),
);
assert!(
Command::new(SOPS_BINARY_PATH)
.arg("encrypt")
.arg("-i")
.arg(file_path.clone())
.output()
.expect("Error running sops")
.status
.success(),
"sops didn't exit successfully"
);
let mut before = String::new();
File::open(file_path.clone())
.unwrap()
.read_to_string(&mut before)
.unwrap();
let output = Command::new(SOPS_BINARY_PATH)
.arg("set")
.arg("--output-type")
.arg("yaml")
.arg("--idempotent")
.arg(file_path.clone())
.arg(r#"["b"]"#)
.arg(r#""ba""#)
.output()
.expect("Error running sops");
println!(
"stdout: {}, stderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(output.status.success(), "sops didn't exit successfully");
let mut after = String::new();
File::open(file_path.clone())
.unwrap()
.read_to_string(&mut after)
.unwrap();
println!("before: {}\nafter: {}", &before, &after,);
assert!(before == after);
}
#[test]
fn set_json_file_insert() {
let file_path =
@@ -549,11 +700,57 @@ b: ba"#
}
}
#[test]
fn test_yaml_time() {
let file_path = prepare_temp_file(
"test_time.yaml",
r#"a: 2024-01-01
b: 2006-01-02T15:04:05+07:06"#
.as_bytes(),
);
assert!(
Command::new(SOPS_BINARY_PATH)
.arg("encrypt")
.arg("-i")
.arg(file_path.clone())
.output()
.expect("Error running sops")
.status
.success(),
"sops didn't exit successfully"
);
let output = Command::new(SOPS_BINARY_PATH)
.arg("decrypt")
.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)
);
assert!(output.status.success(), "sops didn't exit successfully");
let mut s = String::new();
File::open(file_path)
.unwrap()
.read_to_string(&mut s)
.unwrap();
assert_eq!(
s,
r#"a: 2024-01-01T00:00:00Z
b: 2006-01-02T15:04:05+07:06
"#
);
}
#[test]
fn unset_json_file() {
// Test removal of tree branch
let file_path =
prepare_temp_file("test_unset.json", r#"{"a": 2, "b": "ba", "c": [1,2]}"#.as_bytes());
let file_path = prepare_temp_file(
"test_unset.json",
r#"{"a": 2, "b": "ba", "c": [1,2]}"#.as_bytes(),
);
assert!(
Command::new(SOPS_BINARY_PATH)
.arg("encrypt")
@@ -661,8 +858,10 @@ b: ba"#
#[test]
fn unset_yaml_file() {
// Test removal of tree branch
let file_path =
prepare_temp_file("test_unset.yaml", r#"{"a": 2, "b": "ba", "c": [1,2]}"#.as_bytes());
let file_path = prepare_temp_file(
"test_unset.yaml",
r#"{"a": 2, "b": "ba", "c": [1,2]}"#.as_bytes(),
);
assert!(
Command::new(SOPS_BINARY_PATH)
.arg("encrypt")
@@ -797,12 +996,42 @@ b: ba"#
);
}
#[test]
fn decrypt_from_stdin() {
let process = Command::new(SOPS_BINARY_PATH)
.arg("decrypt")
.arg("--input-type")
.arg("yaml")
.arg("--output-type")
.arg("yaml")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Error running sops");
write_to_stdin(&process, include_bytes!("../res/comments.enc.yaml"));
let output = process.wait_with_output().expect("Failed to wait on sops");
assert!(output.status.success(), "sops didn't exit successfully");
let yaml = &String::from_utf8_lossy(&output.stdout);
let data: Value = serde_yaml::from_str(&yaml).expect("Error parsing sops's YAML output");
match data.into() {
Value::Mapping(m) => {
assert!(
m.get(&Value::String("sops".to_owned())).is_none(),
"sops metadata branch found"
);
assert_eq!(m["lorem"], Value::String("ipsum".to_owned()));
assert_eq!(m["dolor"], Value::String("sit".to_owned()));
}
_ => panic!("sops's JSON output is not an object"),
}
}
#[test]
fn encrypt_comments() {
let file_path = "res/comments.yaml";
let output = Command::new(SOPS_BINARY_PATH)
.arg("encrypt")
.arg(file_path.clone())
.arg(file_path)
.output()
.expect("Error running sops");
assert!(output.status.success(), "SOPS didn't return successfully");
@@ -821,7 +1050,7 @@ b: ba"#
let file_path = "res/comments_list.yaml";
let output = Command::new(SOPS_BINARY_PATH)
.arg("encrypt")
.arg(file_path.clone())
.arg(file_path)
.output()
.expect("Error running sops");
assert!(output.status.success(), "SOPS didn't return successfully");
@@ -840,7 +1069,7 @@ b: ba"#
let file_path = "res/comments.enc.yaml";
let output = Command::new(SOPS_BINARY_PATH)
.arg("decrypt")
.arg(file_path.clone())
.arg(file_path)
.output()
.expect("Error running sops");
assert!(output.status.success(), "SOPS didn't return successfully");
@@ -859,7 +1088,7 @@ b: ba"#
let file_path = "res/comments_unencrypted_comments.yaml";
let output = Command::new(SOPS_BINARY_PATH)
.arg("decrypt")
.arg(file_path.clone())
.arg(file_path)
.output()
.expect("Error running sops");
assert!(output.status.success(), "SOPS didn't return successfully");
@@ -945,6 +1174,66 @@ b: ba"#
);
}
#[test]
fn test_no_keygroups() {
// The .sops.yaml file ensures this file is encrypted by zero keygroups
let file_path = prepare_temp_file("test_no_keygroups.yaml", "a: secret".as_bytes());
let output = Command::new(SOPS_BINARY_PATH)
.arg("encrypt")
.arg("-i")
.arg(file_path.clone())
.output()
.expect("Error running sops");
assert!(
!output.status.success(),
"SOPS succeeded encrypting a file without a key group"
);
assert_eq!(
std::str::from_utf8(&output.stderr).unwrap(),
"Could not generate data key: [empty key group provided]\n"
);
}
#[test]
fn test_zero_keygroups() {
// The .sops.yaml file ensures this file is encrypted by zero keygroups
let file_path = prepare_temp_file("test_zero_keygroups.yaml", "a: secret".as_bytes());
let output = Command::new(SOPS_BINARY_PATH)
.arg("encrypt")
.arg("-i")
.arg(file_path.clone())
.output()
.expect("Error running sops");
assert!(
!output.status.success(),
"SOPS succeeded encrypting a file without a key group"
);
assert_eq!(
std::str::from_utf8(&output.stderr).unwrap(),
"Could not generate data key: [empty key group provided]\n"
);
}
#[test]
fn test_empty_keygroup() {
// The .sops.yaml file ensures this file is encrypted by zero keygroups
let file_path = prepare_temp_file("test_empty_keygroup.yaml", "a: secret".as_bytes());
let output = Command::new(SOPS_BINARY_PATH)
.arg("encrypt")
.arg("-i")
.arg(file_path.clone())
.output()
.expect("Error running sops");
assert!(
!output.status.success(),
"SOPS succeeded encrypting a file without a key group"
);
assert_eq!(
std::str::from_utf8(&output.stderr).unwrap(),
"Could not generate data key: [empty key group provided]\n"
);
}
#[test]
fn extract_string() {
let file_path = prepare_temp_file(
@@ -1164,7 +1453,8 @@ bar: |-
r#"{
"foo": "bar",
"bar": "baz\nbam"
}"#
}
"#
);
}

View File

@@ -12,6 +12,7 @@ import (
kms "cloud.google.com/go/kms/apiv1"
"cloud.google.com/go/kms/apiv1/kmspb"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"google.golang.org/api/option"
"google.golang.org/grpc"
@@ -50,6 +51,11 @@ type MasterKey struct {
// for NeedsRotation.
CreationDate time.Time
// tokenSource contains the oauth2.TokenSource used by the GCP client.
// It can be injected by a (local) keyservice.KeyServiceServer using
// TokenSource.ApplyToMasterKey.
// If nil, the remaining authentication methods are attempted.
tokenSource oauth2.TokenSource
// credentialJSON is the Service Account credentials JSON used for
// authenticating towards the GCP KMS service.
credentialJSON []byte
@@ -82,6 +88,22 @@ func MasterKeysFromResourceIDString(resourceID string) []*MasterKey {
return keys
}
// TokenSource is an oauth2.TokenSource used for authenticating towards the
// GCP KMS service.
type TokenSource struct {
source oauth2.TokenSource
}
// NewTokenSource creates a new TokenSource from the provided oauth2.TokenSource.
func NewTokenSource(source oauth2.TokenSource) TokenSource {
return TokenSource{source: source}
}
// ApplyToMasterKey configures the TokenSource on the provided key.
func (t TokenSource) ApplyToMasterKey(key *MasterKey) {
key.tokenSource = t.source
}
// CredentialJSON is the Service Account credentials JSON used for authenticating
// towards the GCP KMS service.
type CredentialJSON []byte
@@ -203,8 +225,8 @@ func (key *MasterKey) TypeToIdentifier() string {
return KeyTypeIdentifier
}
// newKMSClient returns a GCP KMS client configured with the credentialJSON
// and/or grpcConn, falling back to environmental defaults.
// newKMSClient returns a GCP KMS client configured with the tokenSource
// or credentialJSON, and/or grpcConn, falling back to environmental defaults.
// It returns an error if the ResourceID is invalid, or if the setup of the
// client fails.
func (key *MasterKey) newKMSClient() (*kms.KeyManagementClient, error) {
@@ -216,6 +238,8 @@ func (key *MasterKey) newKMSClient() (*kms.KeyManagementClient, error) {
var opts []option.ClientOption
switch {
case key.tokenSource != nil:
opts = append(opts, option.WithTokenSource(key.tokenSource))
case key.credentialJSON != nil:
opts = append(opts, option.WithCredentialsJSON(key.credentialJSON))
default:

View File

@@ -9,6 +9,7 @@ import (
"cloud.google.com/go/kms/apiv1/kmspb"
"github.com/stretchr/testify/assert"
"golang.org/x/oauth2"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
@@ -38,6 +39,13 @@ func TestMasterKeysFromResourceIDString(t *testing.T) {
}
}
func TestTokenSource_ApplyToMasterKey(t *testing.T) {
src := NewTokenSource(oauth2.StaticTokenSource(&oauth2.Token{AccessToken: "some-token"}))
key := &MasterKey{}
src.ApplyToMasterKey(key)
assert.Equal(t, src.source, key.tokenSource)
}
func TestCredentialJSON_ApplyToMasterKey(t *testing.T) {
key := &MasterKey{}
credential := CredentialJSON("mock")
@@ -159,7 +167,7 @@ func newGRPCServer(port string) *grpc.ClientConn {
}
go serv.Serve(lis)
conn, err := grpc.Dial(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
conn, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatal(err)
}

173
go.mod
View File

@@ -1,135 +1,148 @@
module github.com/getsops/sops/v3
go 1.21
go 1.22
toolchain go1.24.1
require (
cloud.google.com/go/kms v1.18.4
cloud.google.com/go/storage v1.43.0
filippo.io/age v1.2.0
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0
github.com/ProtonMail/go-crypto v1.1.0-alpha.5-proton
github.com/aws/aws-sdk-go-v2 v1.30.3
github.com/aws/aws-sdk-go-v2/config v1.27.27
github.com/aws/aws-sdk-go-v2/credentials v1.17.27
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.9
github.com/aws/aws-sdk-go-v2/service/kms v1.35.3
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3
cloud.google.com/go/kms v1.21.1
cloud.google.com/go/storage v1.51.0
filippo.io/age v1.2.1
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1
github.com/ProtonMail/go-crypto v1.1.6
github.com/aws/aws-sdk-go-v2 v1.36.3
github.com/aws/aws-sdk-go-v2/config v1.29.9
github.com/aws/aws-sdk-go-v2/credentials v1.17.62
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.66
github.com/aws/aws-sdk-go-v2/service/kms v1.38.1
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17
github.com/blang/semver v3.5.1+incompatible
github.com/fatih/color v1.17.0
github.com/getsops/gopgagent v0.0.0-20240527072608-0c14999532fe
github.com/golang/protobuf v1.5.4
github.com/google/go-cmp v0.6.0
github.com/fatih/color v1.18.0
github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e
github.com/google/go-cmp v0.7.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/goware/prefixer v0.0.0-20160118172347-395022866408
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/vault/api v1.14.0
github.com/hashicorp/vault/api v1.16.0
github.com/lib/pq v1.10.9
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/go-wordwrap v1.0.1
github.com/ory/dockertest/v3 v3.10.0
github.com/ory/dockertest/v3 v3.11.0
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.9.0
github.com/urfave/cli v1.22.15
golang.org/x/net v0.27.0
golang.org/x/sys v0.22.0
golang.org/x/term v0.22.0
google.golang.org/api v0.189.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade
google.golang.org/grpc v1.65.0
google.golang.org/protobuf v1.34.2
github.com/stretchr/testify v1.10.0
github.com/urfave/cli v1.22.16
golang.org/x/crypto v0.36.0
golang.org/x/net v0.37.0
golang.org/x/oauth2 v0.28.0
golang.org/x/sys v0.31.0
golang.org/x/term v0.30.0
google.golang.org/api v0.227.0
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4
google.golang.org/grpc v1.71.0
google.golang.org/protobuf v1.36.5
gopkg.in/ini.v1 v1.67.0
gopkg.in/yaml.v3 v3.0.1
)
require (
cloud.google.com/go v0.115.0 // indirect
cloud.google.com/go/auth v0.7.2 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect
cloud.google.com/go/compute/metadata v0.5.0 // indirect
cloud.google.com/go/iam v1.1.10 // indirect
cloud.google.com/go/longrunning v0.5.9 // indirect
dario.cat/mergo v1.0.0 // indirect
cel.dev/expr v0.19.2 // indirect
cloud.google.com/go v0.118.3 // indirect
cloud.google.com/go/auth v0.15.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/iam v1.4.1 // indirect
cloud.google.com/go/longrunning v0.6.5 // indirect
cloud.google.com/go/monitoring v1.24.0 // indirect
dario.cat/mergo v1.0.1 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect
github.com/aws/smithy-go v1.20.3 // indirect
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 // indirect
github.com/aws/smithy-go v1.22.2 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cloudflare/circl v1.3.9 // indirect
github.com/containerd/continuity v0.4.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudflare/circl v1.5.0 // indirect
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
github.com/containerd/continuity v0.4.5 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v27.0.1+incompatible // indirect
github.com/docker/docker v27.1.0+incompatible // indirect
github.com/docker/cli v27.4.1+incompatible // indirect
github.com/docker/docker v27.4.1+incompatible // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.0.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.6 // indirect
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/sys/user v0.3.0 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/opencontainers/runc v1.1.13 // indirect
github.com/opencontainers/runc v1.2.3 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect
go.opentelemetry.io/otel v1.27.0 // indirect
go.opentelemetry.io/otel/metric v1.27.0 // indirect
go.opentelemetry.io/otel/trace v1.27.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/genproto v0.0.0-20240722135656-d784300faade // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240722135656-d784300faade // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
go.opentelemetry.io/otel v1.34.0 // indirect
go.opentelemetry.io/otel/metric v1.34.0 // indirect
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.34.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/time v0.11.0 // indirect
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)

444
go.sum
View File

@@ -1,180 +1,185 @@
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0=
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
cloud.google.com/go/auth v0.7.2 h1:uiha352VrCDMXg+yoBtaD0tUF4Kv9vrtrWPYXwutnDE=
cloud.google.com/go/auth v0.7.2/go.mod h1:VEc4p5NNxycWQTMQEDQF0bd6aTMb6VgYDXEwiJJQAbs=
cloud.google.com/go/auth/oauth2adapt v0.2.3 h1:MlxF+Pd3OmSudg/b1yZ5lJwoXCEaeedAguodky1PcKI=
cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I=
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
cloud.google.com/go/iam v1.1.10 h1:ZSAr64oEhQSClwBL670MsJAW5/RLiC6kfw3Bqmd5ZDI=
cloud.google.com/go/iam v1.1.10/go.mod h1:iEgMq62sg8zx446GCaijmA2Miwg5o3UbO+nI47WHJps=
cloud.google.com/go/kms v1.18.4 h1:dYN3OCsQ6wJLLtOnI8DGUwQ5shMusXsWCCC+s09ATsk=
cloud.google.com/go/kms v1.18.4/go.mod h1:SG1bgQ3UWW6/KdPo9uuJnzELXY5YTTMJtDYvajiQ22g=
cloud.google.com/go/longrunning v0.5.9 h1:haH9pAuXdPAMqHvzX0zlWQigXT7B0+CL4/2nXXdBo5k=
cloud.google.com/go/longrunning v0.5.9/go.mod h1:HD+0l9/OOW0za6UWdKJtXoFAX/BGg/3Wj8p10NeWF7c=
cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs=
cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
filippo.io/age v1.2.0 h1:vRDp7pUMaAJzXNIWJVAZnEf/Dyi4Vu4wI8S1LBzufhE=
filippo.io/age v1.2.0/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 h1:GJHeeA2N7xrG3q30L2UXDyuWRzDM900/65j70wcM4Ww=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
cel.dev/expr v0.19.2 h1:V354PbqIXr9IQdwy4SYA4xa0HXaWq1BUPAGzugBY5V4=
cel.dev/expr v0.19.2/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
cloud.google.com/go v0.118.3 h1:jsypSnrE/w4mJysioGdMBg4MiW/hHx/sArFpaBWHdME=
cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc=
cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps=
cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8=
cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M=
cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
cloud.google.com/go/iam v1.4.1 h1:cFC25Nv+u5BkTR/BT1tXdoF2daiVbZ1RLx2eqfQ9RMM=
cloud.google.com/go/iam v1.4.1/go.mod h1:2vUEJpUG3Q9p2UdsyksaKpDzlwOrnMzS30isdReIcLM=
cloud.google.com/go/kms v1.21.1 h1:r1Auo+jlfJSf8B7mUnVw5K0fI7jWyoUy65bV53VjKyk=
cloud.google.com/go/kms v1.21.1/go.mod h1:s0wCyByc9LjTdCjG88toVs70U9W+cc6RKFc8zAqX7nE=
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
cloud.google.com/go/longrunning v0.6.5 h1:sD+t8DO8j4HKW4QfouCklg7ZC1qC4uzVZt8iz3uTW+Q=
cloud.google.com/go/longrunning v0.6.5/go.mod h1:Et04XK+0TTLKa5IPYryKf5DkpwImy6TluQ1QTLwlKmY=
cloud.google.com/go/monitoring v1.24.0 h1:csSKiCJ+WVRgNkRzzz3BPoGjFhjPY23ZTcaenToJxMM=
cloud.google.com/go/monitoring v1.24.0/go.mod h1:Bd1PRK5bmQBQNnuGwHBfUamAV1ys9049oEPHnn4pcsc=
cloud.google.com/go/storage v1.51.0 h1:ZVZ11zCiD7b3k+cH5lQs/qcNaoSz3U9I0jgwVzqDlCw=
cloud.google.com/go/storage v1.51.0/go.mod h1:YEJfu/Ki3i5oHC/7jyTgsGZwdQ8P9hqMqvpi5kRKGgc=
cloud.google.com/go/trace v1.11.3 h1:c+I4YFjxRQjvAhRmSsmjpASUKq88chOX854ied0K/pE=
cloud.google.com/go/trace v1.11.3/go.mod h1:pt7zCYiDSQjC9Y2oqCsh9jF4GStB/hmjrYLsxRR27q8=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
filippo.io/age v1.2.1 h1:X0TZjehAZylOIj4DubWYU1vWQxv9bJpo+Uu2/LGhi1o=
filippo.io/age v1.2.1/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zidoYqnYYa6c1MTzDKzKkTo=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0 h1:DRiANoJTiW6obBQe3SqZizkuV1PEgfiiGivmVocDy64=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.1.0/go.mod h1:qLIye2hwb/ZouqhpSD9Zn3SJipvpEnz1Ywl3VUk9Y0s=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.1 h1:9fXQS/0TtQmKXp8SureKouF+idbQvp7cPUxykiohnBs=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.1/go.mod h1:f+OaoSg0VQYPMqB0Jp2D54j1VHzITYcJaCNwV+k00ts=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1 h1:Wgf5rZba3YZqeTNJPtvqZoBu1sBN/L4sry+u2U3Y75w=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.1/go.mod h1:xxCBG/f/4Vbmh2XQJBsOmNdxWUY5j/s27jujKPbQf14=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 h1:bFWuoEKg+gImo7pvkiQEFAc8ocibADgXeiLAxWhWmkI=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1/go.mod h1:Vih/3yc6yac2JzU4hzpaDupBJP0Flaia9rXXrU8xyww=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4=
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI=
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
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/ProtonMail/go-crypto v1.1.0-alpha.5-proton h1:KVBEgU3CJpmzLChnLiSuEyCuhGhcMt3eOST+7A+ckto=
github.com/ProtonMail/go-crypto v1.1.0-alpha.5-proton/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM=
github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90=
github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg=
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI=
github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.9 h1:TC2vjvaAv1VNl9A0rm+SeuBjrzXnrlwk6Yop+gKRi38=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.9/go.mod h1:WPv2FRnkIOoDv/8j2gSUsI4qDc7392w5anFB/I89GZ8=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 h1:Z5r7SycxmSllHYmaAZPpmN8GviDrSGhMS6bldqtXZPw=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15/go.mod h1:CetW7bDE00QoGEmPUoZuRog07SGVAUVW6LFpNP0YfIg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 h1:YPYe6ZmvUfDDDELqEKtAd6bo8zxhkm+XEFEzQisqUIE=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17/go.mod h1:oBtcnYua/CgzCWYN7NZ5j7PotFDaFSUjCYVTtfyn7vw=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 h1:246A4lSTXWJw/rmlQI+TT2OcqeDMKBdyjEQrafMaQdA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15/go.mod h1:haVfg3761/WF7YPuJOER2MP0k4UAXyHaLclKXB6usDg=
github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 h1:UPTdlTOwWUX49fVi7cymEN6hDqCwe3LNv1vi7TXUutk=
github.com/aws/aws-sdk-go-v2/service/kms v1.35.3/go.mod h1:gjDP16zn+WWalyaUqwCCioQ8gU8lzttCCc9jYsiQI/8=
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2 h1:sZXIzO38GZOU+O0C+INqbH7C2yALwfMWpd64tONS/NE=
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM=
github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE=
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ=
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14=
github.com/aws/aws-sdk-go-v2/config v1.29.9 h1:Kg+fAYNaJeGXp1vmjtidss8O2uXIsXwaRqsQJKXVr+0=
github.com/aws/aws-sdk-go-v2/config v1.29.9/go.mod h1:oU3jj2O53kgOU4TXq/yipt6ryiooYjlkqqVaZk7gY/U=
github.com/aws/aws-sdk-go-v2/credentials v1.17.62 h1:fvtQY3zFzYJ9CfixuAQ96IxDrBajbBWGqjNTCa79ocU=
github.com/aws/aws-sdk-go-v2/credentials v1.17.62/go.mod h1:ElETBxIQqcxej++Cs8GyPBbgMys5DgQPTwo7cUPDKt8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.66 h1:MTLivtC3s89de7Fe3P8rzML/8XPNRfuyJhlRTsCEt0k=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.66/go.mod h1:NAuQ2s6gaFEsuTIb2+P5t6amB1w5MhvJFxppoezGWH0=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 h1:lguz0bmOoGzozP9XfRJR1QIayEYo+2vP/No3OfLF0pU=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA=
github.com/aws/aws-sdk-go-v2/service/kms v1.38.1 h1:tecq7+mAav5byF+Mr+iONJnCBf4B4gon8RSp4BrweSc=
github.com/aws/aws-sdk-go-v2/service/kms v1.38.1/go.mod h1:cQn6tAF77Di6m4huxovNM7NVAozWTZLsDRp9t8Z/WYk=
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 h1:jIiopHEV22b4yQP2q36Y0OmwLbsxNWdWwfZRR5QRRO4=
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2/go.mod h1:U5SNqwhXB3Xe6F47kXvWihPl/ilGaEDe8HD/50Z9wxc=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 h1:8JdC7Gr9NROg1Rusk25IcZeTO59zLxsKgE0gkh5O6h0=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 h1:KwuLovgQPcdjNMfFt9OhUd9a2OwcOKhxfvF4glTzLuA=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 h1:PZV5W8yk4OtH1JAuhV2PXwwO9v5G5Aoj+eMCn4T+1Kc=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
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/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.9 h1:QFrlgFYf2Qpi8bSpVPK1HBvWpx16v/1TZivyo7pGuBE=
github.com/cloudflare/circl v1.3.9/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/containerd/continuity v0.4.3 h1:6HVkalIp+2u1ZLH1J/pYX2oBVXlJZvh1X1A7bEZ9Su8=
github.com/containerd/continuity v0.4.3/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk=
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4=
github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
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/docker/cli v27.0.1+incompatible h1:d/OrlblkOTkhJ1IaAGD1bLgUBtFQC/oP0VjkFMIN+B0=
github.com/docker/cli v27.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v27.1.0+incompatible h1:rEHVQc4GZ0MIQKifQPHSFGV/dVgaZafgRf8fCPtDYBs=
github.com/docker/docker v27.1.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/docker/cli v27.4.1+incompatible h1:VzPiUlRJ/xh+otB75gva3r05isHMo5wXDfPRi5/b4hI=
github.com/docker/cli v27.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/docker v27.4.1+incompatible h1:ZJvcY7gfwHn1JF48PfbyXg7Jyt9ZCWDW+GGXOIxEwp4=
github.com/docker/docker v27.4.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/getsops/gopgagent v0.0.0-20240527072608-0c14999532fe h1:QKe/kmAYbndxwu91TcjHERsnMh5SgOB1x/qicvOdUJ8=
github.com/getsops/gopgagent v0.0.0-20240527072608-0c14999532fe/go.mod h1:awFzISqLJoZLm+i9QQ4SgMNHDqljH6jWV0B36V5MrUM=
github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk=
github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e h1:y/1nzrdF+RPds4lfoEpNhjfmzlgZtPqyO3jMzrqDQws=
github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e/go.mod h1:awFzISqLJoZLm+i9QQ4SgMNHDqljH6jWV0B36V5MrUM=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
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/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc=
github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
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.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
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.5.0/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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
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/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -194,12 +199,14 @@ github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSY
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0=
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-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I=
github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI=
github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
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.14.0 h1:Ah3CFLixD5jmjusOgm8grfN9M0d+Y8fVR2SW0K6pJLU=
github.com/hashicorp/vault/api v1.14.0/go.mod h1:pV9YLxBGSz+cItFDd8Ii4G17waWOQ32zVjMWHe/cOqk=
github.com/hashicorp/vault/api v1.16.0 h1:nbEYGJiAPGzT9U4oWgaaB0g+Rj8E59QuHKyA5LhwQN4=
github.com/hashicorp/vault/api v1.16.0/go.mod h1:KhuUhzOD8lDSk29AtzNjgAu2kxRA9jL9NAbkFlqvkBA=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -223,25 +230,30 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
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.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/opencontainers/runc v1.1.13 h1:98S2srgG9vw0zWcDpFMn5TRrh8kLxa/5OFUstuUhmRs=
github.com/opencontainers/runc v1.1.13/go.mod h1:R016aXacfp/gwQBYw2FDGa9m+n6atbLWrYY8hNMT/sA=
github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4=
github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg=
github.com/opencontainers/runc v1.2.3 h1:fxE7amCzfZflJO2lHXf4y/y8M1BoAqp+FVmG19oYB80=
github.com/opencontainers/runc v1.2.3/go.mod h1:nSxcWUydXrsBZVYNSkTjoQ/N6rcyTtn+1SD5D4+kRIM=
github.com/ory/dockertest/v3 v3.11.0 h1:OiHcxKAvSDUwsEVh2BjxQQc/5EHz9n0va9awCtNGuyA=
github.com/ory/dockertest/v3 v3.11.0/go.mod h1:VIPxS1gwT9NpPOrfD3rACs8Y9Z7yhzO4SB194iUDnUI=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
@@ -256,12 +268,12 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli v1.22.15 h1:nuqt+pdC/KqswQKhETJjo7pvn/k4xMUxgW6liI7XpnM=
github.com/urfave/cli v1.22.15/go.mod h1:wSan1hmo5zeyLGBjRJbzRTNk8gwoYa2B9n4q9dmRIc0=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ=
github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
@@ -271,53 +283,46 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 h1:vS1Ao/R55RNV4O7TA2Qopok8yN+X0LIP6RVWLFkprck=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0/go.mod h1:BMsdeOxN04K0L5FNUBfjFdvwWGNe/rkmSwH4Aelu/X0=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0=
go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ=
go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik=
go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw=
go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao=
go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
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.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
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/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-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-20190620200207-3b0461eec859/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
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/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -326,21 +331,17 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@@ -348,37 +349,18 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.189.0 h1:equMo30LypAkdkLMBqfeIqtyAnlyig1JSZArl4XPwdI=
google.golang.org/api v0.189.0/go.mod h1:FLWGJKb0hb+pU2j+rJqwbnsF+ym+fQs73rbJ+KAUgy8=
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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20240722135656-d784300faade h1:lKFsS7wpngDgSCeFn7MoLy+wBDQZ1UQIJD4UNM1Qvkg=
google.golang.org/genproto v0.0.0-20240722135656-d784300faade/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY=
google.golang.org/genproto/googleapis/api v0.0.0-20240722135656-d784300faade h1:WxZOF2yayUHpHSbUE6NMzumUzBxYc3YGwo0YHnbzsJY=
google.golang.org/genproto/googleapis/api v0.0.0-20240722135656-d784300faade/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade h1:oCRSWfwGXQsqlVdErcyTt4A93Y8fo0/9D4b1gnI++qo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
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.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
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.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
google.golang.org/api v0.227.0 h1:QvIHF9IuyG6d6ReE+BNd11kIB8hZvjN8Z5xY5t21zYc=
google.golang.org/api v0.227.0/go.mod h1:EIpaG6MbTgQarWF5xJvX0eOJPK9n/5D4Bynb9j2HXvQ=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE=
google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -389,7 +371,5 @@ 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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=

View File

@@ -41,7 +41,7 @@ func TestMain(m *testing.M) {
}
// Pull the image, create a container based on it, and run it
resource, err := pool.Run("vault", testVaultVersion, []string{"VAULT_DEV_ROOT_TOKEN_ID=" + testVaultToken})
resource, err := pool.Run("ghcr.io/getsops/ci-container-images/vault", testVaultVersion, []string{"VAULT_DEV_ROOT_TOKEN_ID=" + testVaultToken})
if err != nil {
logger.Fatalf("could not start resource: %s", err)
}

View File

@@ -1,17 +1,12 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.23.0
// protoc v3.13.0
// protoc-gen-go v1.35.2
// protoc v5.28.3
// 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"
@@ -25,16 +20,13 @@ const (
_ = 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
@@ -46,11 +38,9 @@ type Key struct {
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)
}
mi := &file_keyservice_keyservice_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Key) String() string {
@@ -61,7 +51,7 @@ func (*Key) ProtoMessage() {}
func (x *Key) ProtoReflect() protoreflect.Message {
mi := &file_keyservice_keyservice_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -175,11 +165,9 @@ type PgpKey struct {
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)
}
mi := &file_keyservice_keyservice_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PgpKey) String() string {
@@ -190,7 +178,7 @@ func (*PgpKey) ProtoMessage() {}
func (x *PgpKey) ProtoReflect() protoreflect.Message {
mi := &file_keyservice_keyservice_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -225,11 +213,9 @@ type KmsKey struct {
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)
}
mi := &file_keyservice_keyservice_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *KmsKey) String() string {
@@ -240,7 +226,7 @@ func (*KmsKey) ProtoMessage() {}
func (x *KmsKey) ProtoReflect() protoreflect.Message {
mi := &file_keyservice_keyservice_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -293,11 +279,9 @@ type GcpKmsKey struct {
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)
}
mi := &file_keyservice_keyservice_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GcpKmsKey) String() string {
@@ -308,7 +292,7 @@ func (*GcpKmsKey) ProtoMessage() {}
func (x *GcpKmsKey) ProtoReflect() protoreflect.Message {
mi := &file_keyservice_keyservice_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -342,11 +326,9 @@ type VaultKey struct {
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)
}
mi := &file_keyservice_keyservice_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *VaultKey) String() string {
@@ -357,7 +339,7 @@ func (*VaultKey) ProtoMessage() {}
func (x *VaultKey) ProtoReflect() protoreflect.Message {
mi := &file_keyservice_keyservice_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -405,11 +387,9 @@ type AzureKeyVaultKey struct {
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)
}
mi := &file_keyservice_keyservice_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AzureKeyVaultKey) String() string {
@@ -420,7 +400,7 @@ func (*AzureKeyVaultKey) ProtoMessage() {}
func (x *AzureKeyVaultKey) ProtoReflect() protoreflect.Message {
mi := &file_keyservice_keyservice_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -466,11 +446,9 @@ type AgeKey struct {
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)
}
mi := &file_keyservice_keyservice_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AgeKey) String() string {
@@ -481,7 +459,7 @@ func (*AgeKey) ProtoMessage() {}
func (x *AgeKey) ProtoReflect() protoreflect.Message {
mi := &file_keyservice_keyservice_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -514,11 +492,9 @@ type EncryptRequest struct {
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)
}
mi := &file_keyservice_keyservice_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *EncryptRequest) String() string {
@@ -529,7 +505,7 @@ func (*EncryptRequest) ProtoMessage() {}
func (x *EncryptRequest) ProtoReflect() protoreflect.Message {
mi := &file_keyservice_keyservice_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -568,11 +544,9 @@ type EncryptResponse struct {
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)
}
mi := &file_keyservice_keyservice_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *EncryptResponse) String() string {
@@ -583,7 +557,7 @@ func (*EncryptResponse) ProtoMessage() {}
func (x *EncryptResponse) ProtoReflect() protoreflect.Message {
mi := &file_keyservice_keyservice_proto_msgTypes[8]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -616,11 +590,9 @@ type DecryptRequest struct {
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)
}
mi := &file_keyservice_keyservice_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *DecryptRequest) String() string {
@@ -631,7 +603,7 @@ func (*DecryptRequest) ProtoMessage() {}
func (x *DecryptRequest) ProtoReflect() protoreflect.Message {
mi := &file_keyservice_keyservice_proto_msgTypes[9]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -670,11 +642,9 @@ type DecryptResponse struct {
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)
}
mi := &file_keyservice_keyservice_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *DecryptResponse) String() string {
@@ -685,7 +655,7 @@ func (*DecryptResponse) ProtoMessage() {}
func (x *DecryptResponse) ProtoReflect() protoreflect.Message {
mi := &file_keyservice_keyservice_proto_msgTypes[10]
if protoimpl.UnsafeEnabled && x != nil {
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
@@ -784,7 +754,8 @@ var file_keyservice_keyservice_proto_rawDesc = []byte{
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,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x0e, 0x5a, 0x0c, 0x2e, 0x2f, 0x6b, 0x65,
0x79, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@@ -800,7 +771,7 @@ func file_keyservice_keyservice_proto_rawDescGZIP() []byte {
}
var file_keyservice_keyservice_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
var file_keyservice_keyservice_proto_goTypes = []interface{}{
var file_keyservice_keyservice_proto_goTypes = []any{
(*Key)(nil), // 0: Key
(*PgpKey)(nil), // 1: PgpKey
(*KmsKey)(nil), // 2: KmsKey
@@ -840,141 +811,7 @@ 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{}{
file_keyservice_keyservice_proto_msgTypes[0].OneofWrappers = []any{
(*Key_KmsKey)(nil),
(*Key_PgpKey)(nil),
(*Key_GcpKmsKey)(nil),
@@ -1001,119 +838,3 @@ func file_keyservice_keyservice_proto_init() {
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",
}

View File

@@ -1,5 +1,7 @@
syntax = "proto3";
option go_package = "./keyservice";
message Key {
oneof key_type {
KmsKey kms_key = 1;

View File

@@ -0,0 +1,157 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.28.3
// source: keyservice/keyservice.proto
package keyservice
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
KeyService_Encrypt_FullMethodName = "/KeyService/Encrypt"
KeyService_Decrypt_FullMethodName = "/KeyService/Decrypt"
)
// KeyServiceClient is the client API for KeyService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#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) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(EncryptResponse)
err := c.cc.Invoke(ctx, KeyService_Encrypt_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *keyServiceClient) Decrypt(ctx context.Context, in *DecryptRequest, opts ...grpc.CallOption) (*DecryptResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(DecryptResponse)
err := c.cc.Invoke(ctx, KeyService_Decrypt_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// KeyServiceServer is the server API for KeyService service.
// All implementations should embed UnimplementedKeyServiceServer
// for forward compatibility.
type KeyServiceServer interface {
Encrypt(context.Context, *EncryptRequest) (*EncryptResponse, error)
Decrypt(context.Context, *DecryptRequest) (*DecryptResponse, error)
}
// UnimplementedKeyServiceServer should be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
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 (UnimplementedKeyServiceServer) testEmbeddedByValue() {}
// UnsafeKeyServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to KeyServiceServer will
// result in compilation errors.
type UnsafeKeyServiceServer interface {
mustEmbedUnimplementedKeyServiceServer()
}
func RegisterKeyServiceServer(s grpc.ServiceRegistrar, srv KeyServiceServer) {
// If the following call pancis, it indicates UnimplementedKeyServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
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_FullMethodName,
}
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_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(KeyServiceServer).Decrypt(ctx, req.(*DecryptRequest))
}
return interceptor(ctx, in, info, handler)
}
// KeyService_ServiceDesc is the grpc.ServiceDesc for KeyService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
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",
}

View File

@@ -11,6 +11,7 @@ import (
"fmt"
"os"
"regexp"
"sort"
"strings"
"time"
@@ -181,6 +182,38 @@ func ParseKMSContext(in interface{}) map[string]*string {
return out
}
// kmsContextToString converts a dictionary into a string that can be parsed
// again with ParseKMSContext().
func kmsContextToString(in map[string]*string) string {
if len(in) == 0 {
return ""
}
// Collect the keys in a slice and compute the expected length
keys := make([]string, 0, len(in))
length := 0
for key := range in {
keys = append(keys, key)
length += len(key) + len(*in[key]) + 2
}
// Sort the keys
sort.Strings(keys)
// Compose a comma-separated string of key-vale pairs
var builder strings.Builder
builder.Grow(length)
for index, key := range keys {
if index > 0 {
builder.WriteString(",")
}
builder.WriteString(key)
builder.WriteByte(':')
builder.WriteString(*in[key])
}
return builder.String()
}
// CredentialsProvider is a wrapper around aws.CredentialsProvider used for
// authentication towards AWS KMS.
type CredentialsProvider struct {
@@ -278,7 +311,18 @@ func (key *MasterKey) NeedsRotation() bool {
// ToString converts the key to a string representation.
func (key *MasterKey) ToString() string {
return key.Arn
arnRole := key.Arn
if key.Role != "" {
arnRole = fmt.Sprintf("%s+%s", key.Arn, key.Role)
}
context := kmsContextToString(key.EncryptionContext)
if key.AwsProfile != "" {
return fmt.Sprintf("%s|%s|%s", arnRole, context, key.AwsProfile)
}
if context != "" {
return fmt.Sprintf("%s|%s", arnRole, context)
}
return arnRole
}
// ToMap converts the MasterKey to a map for serialization purposes.

View File

@@ -33,7 +33,7 @@ const (
// testLocalKMSImage is a container image repository reference to a mock
// version of AWS' Key Management Service.
// Ref: https://github.com/nsmithuk/local-kms
testLocalKMSImage = "docker.io/nsmithuk/local-kms"
testLocalKMSImage = "ghcr.io/getsops/ci-container-images/local-kms"
// testLocalKMSImage is the container image tag to use.
testLocalKMSTag = "3.11.1"
)
@@ -367,8 +367,38 @@ func TestMasterKey_NeedsRotation(t *testing.T) {
}
func TestMasterKey_ToString(t *testing.T) {
dummyARNWithRole := fmt.Sprintf("%s+arn:aws:iam::my-role", dummyARN)
bar := "bar"
bam := "bam"
context := map[string]*string{
"foo": &bar,
"baz": &bam,
}
key := NewMasterKeyFromArn(dummyARN, nil, "")
assert.Equal(t, dummyARN, key.ToString())
key = NewMasterKeyFromArn(dummyARNWithRole, nil, "")
assert.Equal(t, dummyARNWithRole, key.ToString())
key = NewMasterKeyFromArn(dummyARN, nil, "profile")
assert.Equal(t, fmt.Sprintf("%s||profile", dummyARN), key.ToString())
key = NewMasterKeyFromArn(dummyARNWithRole, nil, "profile")
assert.Equal(t, fmt.Sprintf("%s||profile", dummyARNWithRole), key.ToString())
key = NewMasterKeyFromArn(dummyARN, context, "")
assert.Equal(t, fmt.Sprintf("%s|baz:bam,foo:bar", dummyARN), key.ToString())
key = NewMasterKeyFromArn(dummyARNWithRole, context, "")
assert.Equal(t, fmt.Sprintf("%s|baz:bam,foo:bar", dummyARNWithRole), key.ToString())
key = NewMasterKeyFromArn(dummyARN, context, "profile")
assert.Equal(t, fmt.Sprintf("%s|baz:bam,foo:bar|profile", dummyARN), key.ToString())
key = NewMasterKeyFromArn(dummyARNWithRole, context, "profile")
assert.Equal(t, fmt.Sprintf("%s|baz:bam,foo:bar|profile", dummyARNWithRole), key.ToString())
}
func TestMasterKey_ToMap(t *testing.T) {

View File

@@ -428,7 +428,17 @@ func (key *MasterKey) decryptWithGnuPG() ([]byte, error) {
return nil, fmt.Errorf("failed to decrypt sops data key with pgp: %s",
strings.TrimSpace(stderr.String()))
}
return stdout.Bytes(), nil
result := stdout.Bytes()
if len(result) == 0 {
// This can happen if an older GnuPG version is used to decrypt a key encrypted with a
// newer GnuPG version that used an AEAD cipher, which the old version does not support.
// Apparently some GnuPG versions drop the unspuported packets, which results in a decrypted
// data of 0 bytes, and returns nothing with exit code 0.
//
// (See https://github.com/getsops/sops/issues/896#issuecomment-2688079300 for more infos.)
return nil, fmt.Errorf("failed to decrypt sops data key with pgp: zero bytes returned")
}
return result, nil
}
// NeedsRotation returns whether the data key needs to be rotated
@@ -634,7 +644,13 @@ func gnuPGHome(customPath string) string {
// This is mostly used for compatibility reasons, as older versions of GnuPG
// do not always like long IDs.
func shortenFingerprint(fingerprint string) string {
if offset := len(fingerprint) - 16; offset > 0 {
offset := len(fingerprint) - 16
// If the fingerprint ends with '!', we must include '!' in the ID *and* the
// 16 hex digits before it. See https://github.com/getsops/sops/issues/1365.
if strings.HasSuffix(fingerprint, "!") {
offset -= 1
}
if offset > 0 {
fingerprint = fingerprint[offset:]
}
return fingerprint

View File

@@ -697,10 +697,23 @@ func Test_gnuPGHome(t *testing.T) {
}
func Test_shortenFingerprint(t *testing.T) {
// Test with regular fingerprint
shortId := shortenFingerprint(mockFingerprint)
assert.Equal(t, "9732075EA221A7EA", shortId)
assert.Equal(t, shortId, shortenFingerprint(shortId))
// Test with forced subkey
shortId = shortenFingerprint(mockFingerprint + "!")
assert.Equal(t, "9732075EA221A7EA!", shortId)
assert.Equal(t, shortId, shortenFingerprint(shortId))
// Make sure that too short IDs are kept
for _, tooShort := range []string{"012345679abcdef", "012345679abcdef!", "123", "123!"} {
shortId = shortenFingerprint(tooShort)
assert.Equal(t, tooShort, shortId)
}
}
// TODO(hidde): previous tests kept around for now.

3
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,3 @@
[toolchain]
channel = "1.85.0"
profile = "minimal"

View File

@@ -15,7 +15,6 @@ import (
"crypto/subtle"
"fmt"
mathrand "math/rand"
"time"
)
const (
@@ -190,7 +189,6 @@ func Split(secret []byte, parts, threshold int) ([][]byte, error) {
// 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

88
sops.go
View File

@@ -127,6 +127,48 @@ type TreeBranch []TreeItem
// Trees usually have more than one branch
type TreeBranches []TreeBranch
func equals(oneBranch interface{}, otherBranch interface{}) bool {
switch oneBranch := oneBranch.(type) {
case TreeBranch:
otherBranch, ok := otherBranch.(TreeBranch)
if !ok || len(oneBranch) != len(otherBranch) {
return false
}
for i, item := range oneBranch {
otherItem := otherBranch[i]
if !equals(item.Key, otherItem.Key) || !equals(item.Value, otherItem.Value) {
return false
}
}
return true
case []interface{}:
otherBranch, ok := otherBranch.([]interface{})
if !ok || len(oneBranch) != len(otherBranch) {
return false
}
for i, item := range oneBranch {
if !equals(item, otherBranch[i]) {
return false
}
}
return true
case Comment:
otherBranch, ok := otherBranch.(Comment)
if !ok {
return false
}
return oneBranch.Value == otherBranch.Value
default:
// Unexpected type
return oneBranch == otherBranch
}
}
// Compare a branch with another one
func (branch TreeBranch) Equals(other TreeBranch) bool {
return equals(branch, other)
}
func valueFromPathAndLeaf(path []interface{}, leaf interface{}) interface{} {
switch component := path[0].(type) {
case int:
@@ -156,47 +198,55 @@ func valueFromPathAndLeaf(path []interface{}, leaf interface{}) interface{} {
}
}
func set(branch interface{}, path []interface{}, value interface{}) interface{} {
func set(branch interface{}, path []interface{}, value interface{}) (interface{}, bool) {
switch branch := branch.(type) {
case TreeBranch:
for i, item := range branch {
if item.Key == path[0] {
var changed bool
if len(path) == 1 {
changed = !equals(branch[i].Value, value)
branch[i].Value = value
} else {
branch[i].Value = set(item.Value, path[1:], value)
branch[i].Value, changed = set(item.Value, path[1:], value)
}
return branch
return branch, changed
}
}
// Not found, need to add the next path entry to the branch
value := valueFromPathAndLeaf(path, value)
if newBranch, ok := value.(TreeBranch); ok && len(newBranch) > 0 {
return append(branch, newBranch[0])
return append(branch, newBranch[0]), true
}
return branch
return branch, true
case []interface{}:
position := path[0].(int)
var changed bool
if len(path) == 1 {
if position >= len(branch) {
return append(branch, value)
return append(branch, value), true
}
changed = !equals(branch[position], value)
branch[position] = value
} else {
if position >= len(branch) {
branch = append(branch, valueFromPathAndLeaf(path[1:], value))
changed = true
} else {
branch[position], changed = set(branch[position], path[1:], value)
}
branch[position] = set(branch[position], path[1:], value)
}
return branch
return branch, changed
default:
return valueFromPathAndLeaf(path, value)
newValue := valueFromPathAndLeaf(path, value)
return newValue, !equals(branch, newValue)
}
}
// 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)
func (branch TreeBranch) Set(path []interface{}, value interface{}) (TreeBranch, bool) {
v, changed := set(branch, path, value)
return v.(TreeBranch), changed
}
func unset(branch interface{}, path []interface{}) (interface{}, error) {
@@ -297,6 +347,8 @@ func (branch TreeBranch) walkValue(in interface{}, path []string, commentsStack
return onLeaves(in, path, commentsStack)
case float64:
return onLeaves(in, path, commentsStack)
case time.Time:
return onLeaves(in, path, commentsStack)
case Comment:
return onLeaves(in, path, commentsStack)
case TreeBranch:
@@ -700,6 +752,11 @@ func (m *Metadata) UpdateMasterKeysWithKeyServices(dataKey []byte, svcs []keyser
fmt.Errorf("no key services provided, cannot update master keys"),
}
}
if len(m.KeyGroups) == 0 {
return []error{
fmt.Errorf("no key groups provided"),
}
}
var parts [][]byte
if len(m.KeyGroups) == 1 {
// If there's only one key group, we can't do Shamir. All keys
@@ -726,6 +783,11 @@ func (m *Metadata) UpdateMasterKeysWithKeyServices(dataKey []byte, svcs []keyser
}
for i, group := range m.KeyGroups {
part := parts[i]
if len(group) == 0 {
return []error{
fmt.Errorf("empty key group provided"),
}
}
for _, key := range group {
svcKey := keyservice.KeyFromMasterKey(key)
var keyErrs []error
@@ -762,7 +824,7 @@ func (m *Metadata) UpdateMasterKeys(dataKey []byte) (errs []error) {
// 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, decryptionOrder []string) ([]byte, error) {
func (m *Metadata) GetDataKeyWithKeyServices(svcs []keyservice.KeyServiceClient, decryptionOrder []string) ([]byte, error) {
if m.DataKey != nil {
return m.DataKey, nil
}
@@ -908,6 +970,8 @@ func ToBytes(in interface{}) ([]byte, error) {
return boolB, nil
case []byte:
return in, nil
case time.Time:
return in.MarshalText()
case Comment:
return ToBytes(in.Value)
default:

View File

@@ -633,8 +633,7 @@ func TestUnencryptedCommentRegexFail(t *testing.T) {
tree := Tree{Branches: branches, Metadata: Metadata{UnencryptedCommentRegex: "ENC"}}
cipher := encPrefixCipher{}
_, err := tree.Encrypt(bytes.Repeat([]byte("f"), 32), cipher)
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "Encrypted comment \"ENC:sops:noenc\" matches UnencryptedCommentRegex!")
assert.ErrorContains(t, err, "Encrypted comment \"ENC:sops:noenc\" matches UnencryptedCommentRegex!")
}
type MockCipher struct{}
@@ -845,6 +844,23 @@ func TestDecrypt(t *testing.T) {
}
}
type WrongType struct{}
func TestEncryptWrongType(t *testing.T) {
branches := TreeBranches{
TreeBranch{
TreeItem{
Key: "foo",
Value: WrongType{},
},
},
}
tree := Tree{Branches: branches, Metadata: Metadata{UnencryptedSuffix: DefaultUnencryptedSuffix}}
result, err := tree.Encrypt(bytes.Repeat([]byte{'f'}, 32), MockCipher{})
assert.Equal(t, "", result)
assert.ErrorContains(t, err, "unknown type: sops.WrongType")
}
func TestTruncateTree(t *testing.T) {
tree := TreeBranch{
TreeItem{
@@ -872,10 +888,49 @@ func TestTruncateTree(t *testing.T) {
"foobar",
2,
})
assert.Equal(t, nil, err)
assert.NoError(t, err)
assert.Equal(t, expected, result)
}
func TestTruncateTreeNotFound(t *testing.T) {
tree := TreeBranch{
TreeItem{
Key: "foo",
Value: 2,
},
}
result, err := tree.Truncate([]interface{}{"baz"})
assert.ErrorContains(t, err, "baz")
assert.Nil(t, result, "Truncate result was not nil upon %s", err)
}
func TestTruncateTreeNotArray(t *testing.T) {
tree := TreeBranch{
TreeItem{
Key: "foo",
Value: 2,
},
}
result, err := tree.Truncate([]interface{}{"foo", 99})
assert.ErrorContains(t, err, "99")
assert.Nil(t, result, "Truncate result was not nil upon %s", err)
}
func TestTruncateTreeArrayOutOfBounds(t *testing.T) {
tree := TreeBranch{
TreeItem{
Key: "foo",
Value: []interface{}{
"one",
"two",
},
},
}
result, err := tree.Truncate([]interface{}{"foo", 99})
assert.ErrorContains(t, err, "99")
assert.Nil(t, result, "Truncate result was not nil upon %s", err)
}
func TestEncryptComments(t *testing.T) {
tree := Tree{
Branches: TreeBranches{
@@ -968,10 +1023,33 @@ func TestSetNewKey(t *testing.T) {
},
},
}
set := branch.Set([]interface{}{"foo", "bar", "foo"}, "hello")
set, changed := branch.Set([]interface{}{"foo", "bar", "foo"}, "hello")
assert.Equal(t, true, changed)
assert.Equal(t, "hello", set[0].Value.(TreeBranch)[0].Value.(TreeBranch)[1].Value)
}
func TestSetNewKeyUnchanged(t *testing.T) {
branch := TreeBranch{
TreeItem{
Key: "foo",
Value: TreeBranch{
TreeItem{
Key: "bar",
Value: TreeBranch{
TreeItem{
Key: "baz",
Value: "foobar",
},
},
},
},
},
}
set, changed := branch.Set([]interface{}{"foo", "bar", "baz"}, "foobar")
assert.Equal(t, false, changed)
assert.Equal(t, "foobar", set[0].Value.(TreeBranch)[0].Value.(TreeBranch)[0].Value)
}
func TestSetNewBranch(t *testing.T) {
branch := TreeBranch{
TreeItem{
@@ -979,7 +1057,8 @@ func TestSetNewBranch(t *testing.T) {
Value: "value",
},
}
set := branch.Set([]interface{}{"foo", "bar", "baz"}, "hello")
set, changed := branch.Set([]interface{}{"foo", "bar", "baz"}, "hello")
assert.Equal(t, true, changed)
assert.Equal(t, TreeBranch{
TreeItem{
Key: "key",
@@ -1012,7 +1091,8 @@ func TestSetArrayDeepNew(t *testing.T) {
},
},
}
set := branch.Set([]interface{}{"foo", 2, "bar"}, "hello")
set, changed := branch.Set([]interface{}{"foo", 2, "bar"}, "hello")
assert.Equal(t, true, changed)
assert.Equal(t, "hello", set[0].Value.([]interface{})[2].(TreeBranch)[0].Value)
}
@@ -1023,13 +1103,15 @@ func TestSetNewKeyDeep(t *testing.T) {
Value: "bar",
},
}
set := branch.Set([]interface{}{"foo", "bar", "baz"}, "hello")
set, changed := branch.Set([]interface{}{"foo", "bar", "baz"}, "hello")
assert.Equal(t, true, changed)
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")
set, changed := branch.Set([]interface{}{"foo", "bar", "baz"}, "hello")
assert.Equal(t, true, changed)
assert.Equal(t, "hello", set[0].Value.(TreeBranch)[0].Value.(TreeBranch)[0].Value)
}
@@ -1044,13 +1126,15 @@ func TestSetArray(t *testing.T) {
},
},
}
set := branch.Set([]interface{}{"foo", 0}, "uno")
set, changed := branch.Set([]interface{}{"foo", 0}, "uno")
assert.Equal(t, true, changed)
assert.Equal(t, "uno", set[0].Value.([]interface{})[0])
}
func TestSetArrayNew(t *testing.T) {
branch := TreeBranch{}
set := branch.Set([]interface{}{"foo", 0, 0}, "uno")
set, changed := branch.Set([]interface{}{"foo", 0, 0}, "uno")
assert.Equal(t, true, changed)
assert.Equal(t, "uno", set[0].Value.([]interface{})[0].([]interface{})[0])
}
@@ -1061,7 +1145,8 @@ func TestSetExisting(t *testing.T) {
Value: "foobar",
},
}
set := branch.Set([]interface{}{"foo"}, "bar")
set, changed := branch.Set([]interface{}{"foo"}, "bar")
assert.Equal(t, true, changed)
assert.Equal(t, "bar", set[0].Value)
}
@@ -1072,7 +1157,8 @@ func TestSetArrayLeafNewItem(t *testing.T) {
Value: []interface{}{},
},
}
set := branch.Set([]interface{}{"array", 2}, "hello")
set, changed := branch.Set([]interface{}{"array", 2}, "hello")
assert.Equal(t, true, changed)
assert.Equal(t, TreeBranch{
TreeItem{
Key: "array",
@@ -1092,7 +1178,8 @@ func TestSetArrayNonLeaf(t *testing.T) {
},
},
}
set := branch.Set([]interface{}{"array", 0, "hello"}, "hello")
set, changed := branch.Set([]interface{}{"array", 0, "hello"}, "hello")
assert.Equal(t, true, changed)
assert.Equal(t, TreeBranch{
TreeItem{
Key: "array",
@@ -1108,6 +1195,315 @@ func TestSetArrayNonLeaf(t *testing.T) {
}, set)
}
func TestUnsetKeyRootLeaf(t *testing.T) {
branch := TreeBranch{
TreeItem{
Key: "foo",
Value: "foo",
},
TreeItem{
Key: "foofoo",
Value: "foofoo",
},
}
unset, err := branch.Unset([]interface{}{"foofoo"})
assert.NoError(t, err)
assert.Equal(t, TreeBranch{
TreeItem{
Key: "foo",
Value: "foo",
},
}, unset)
}
func TestUnsetKeyBranchLeaf(t *testing.T) {
branch := TreeBranch{
TreeItem{
Key: "foo",
Value: TreeBranch{
TreeItem{
Key: "bar",
Value: "bar",
},
TreeItem{
Key: "barbar",
Value: "barbar",
},
},
},
}
unset, err := branch.Unset([]interface{}{"foo", "barbar"})
assert.NoError(t, err)
assert.Equal(t, TreeBranch{
TreeItem{
Key: "foo",
Value: TreeBranch{
TreeItem{
Key: "bar",
Value: "bar",
},
},
},
}, unset)
}
func TestUnsetKeyBranch(t *testing.T) {
branch := TreeBranch{
TreeItem{
Key: "foo",
Value: "foo",
},
TreeItem{
Key: "foofoo",
Value: TreeBranch{
TreeItem{
Key: "bar",
Value: "bar",
},
},
},
}
unset, err := branch.Unset([]interface{}{"foofoo"})
assert.NoError(t, err)
assert.Equal(t, TreeBranch{
TreeItem{
Key: "foo",
Value: "foo",
},
}, unset)
}
func TestUnsetKeyRootLastLeaf(t *testing.T) {
branch := TreeBranch{
TreeItem{
Key: "foo",
Value: "foo",
},
}
unset, err := branch.Unset([]interface{}{"foo"})
assert.NoError(t, err)
assert.Equal(t, TreeBranch{
}, unset)
}
func TestUnsetKeyBranchLastLeaf(t *testing.T) {
branch := TreeBranch{
TreeItem{
Key: "foo",
Value: TreeBranch{
TreeItem{
Key: "bar",
Value: "bar",
},
},
},
}
unset, err := branch.Unset([]interface{}{"foo", "bar"})
assert.NoError(t, err)
assert.Equal(t, TreeBranch{
TreeItem{
Key: "foo",
Value: TreeBranch{
},
},
}, unset)
}
func TestUnsetKeyArray(t *testing.T) {
branch := TreeBranch{
TreeItem{
Key: "foo",
Value: TreeBranch{
TreeItem{
Key: "bar",
Value: []interface{}{
TreeBranch{
TreeItem{
Key: "baz",
Value: "baz",
},
},
},
},
},
},
}
unset, err := branch.Unset([]interface{}{"foo", "bar"})
assert.NoError(t, err)
assert.Equal(t, TreeBranch{
TreeItem{
Key: "foo",
Value: TreeBranch{
},
},
}, unset)
}
func TestUnsetArrayItem(t *testing.T) {
branch := TreeBranch{
TreeItem{
Key: "foo",
Value: []interface{}{
TreeBranch{
TreeItem{
Key: "bar",
Value: "bar",
},
},
TreeBranch{
TreeItem{
Key: "barbar",
Value: "barbar",
},
},
},
},
}
unset, err := branch.Unset([]interface{}{"foo", 1})
assert.NoError(t, err)
assert.Equal(t, TreeBranch{
TreeItem{
Key: "foo",
Value: []interface{}{
TreeBranch{
TreeItem{
Key: "bar",
Value: "bar",
},
},
},
},
}, unset)
}
func TestUnsetKeyInArrayItem(t *testing.T) {
branch := TreeBranch{
TreeItem{
Key: "foo",
Value: []interface{}{
TreeBranch{
TreeItem{
Key: "bar",
Value: "bar",
},
TreeItem{
Key: "barbar",
Value: "barbar",
},
},
},
},
}
unset, err := branch.Unset([]interface{}{"foo", 0, "barbar"})
assert.NoError(t, err)
assert.Equal(t, TreeBranch{
TreeItem{
Key: "foo",
Value: []interface{}{
TreeBranch{
TreeItem{
Key: "bar",
Value: "bar",
},
},
},
},
}, unset)
}
func TestUnsetArrayLastItem(t *testing.T) {
branch := TreeBranch{
TreeItem{
Key: "foo",
Value: []interface{}{
TreeBranch{
TreeItem{
Key: "bar",
Value: "bar",
},
},
},
},
}
unset, err := branch.Unset([]interface{}{"foo", 0})
assert.NoError(t, err)
assert.Equal(t, TreeBranch{
TreeItem{
Key: "foo",
Value: []interface{}{
},
},
}, unset)
}
func TestUnsetKeyNotFound(t *testing.T) {
branch := TreeBranch{
TreeItem{
Key: "foo",
Value: TreeBranch{
TreeItem{
Key: "bar",
Value: "bar",
},
},
},
}
unset, err := branch.Unset([]interface{}{"foo", "unknown-value"})
assert.Equal(t, err.(*SopsKeyNotFound).Key, "unknown-value")
assert.ErrorContains(t, err, "unknown-value")
assert.Nil(t, unset, "Unset result was not nil upon %s", err)
}
func TestUnsetKeyInArrayNotFound(t *testing.T) {
branch := TreeBranch{
TreeItem{
Key: "foo",
Value: []interface{}{
TreeBranch{
TreeItem{
Key: "bar",
Value: "bar",
},
},
},
},
}
unset, err := branch.Unset([]interface{}{"foo", 0, "unknown"})
assert.Equal(t, err.(*SopsKeyNotFound).Key, "unknown")
assert.Nil(t, unset, "Unset result was not nil upon %s", err)
}
func TestUnsetArrayItemOutOfBounds(t *testing.T) {
branch := TreeBranch{
TreeItem{
Key: "foo",
Value: []interface{}{
TreeBranch{
TreeItem{
Key: "bar",
Value: "bar",
},
},
},
},
}
unset, err := branch.Unset([]interface{}{"foo", 99})
assert.Equal(t, err.(*SopsKeyNotFound).Key, 99)
assert.Nil(t, unset, "Unset result was not nil upon %s", err)
}
func TestUnsetKeyNotABranch(t *testing.T) {
branch := TreeBranch{
TreeItem{
Key: "foo",
Value: 99,
},
}
unset, err := branch.Unset([]interface{}{"foo", "bar"})
assert.ErrorContains(t, err, "Unsupported type")
assert.Nil(t, unset, "Unset result was not nil upon %s", err)
}
func TestEmitAsMap(t *testing.T) {
expected := map[string]interface{}{
"foobar": "barfoo",
@@ -1128,6 +1524,10 @@ func TestEmitAsMap(t *testing.T) {
Key: "number",
Value: 42,
},
TreeItem{
Key: Comment{"comment"},
Value: nil,
},
},
TreeBranch{
TreeItem{
@@ -1149,9 +1549,8 @@ func TestEmitAsMap(t *testing.T) {
data, err := EmitAsMap(branches)
if assert.NoError(t, err) {
assert.Equal(t, expected, data)
}
assert.NoError(t, err)
assert.Equal(t, expected, data)
}
func TestSortKeyGroupIndices(t *testing.T) {

View File

@@ -24,7 +24,8 @@ func NewStore(c *config.INIStoreConfig) *Store {
}
func (store Store) encodeTree(branches sops.TreeBranches) ([]byte, error) {
iniFile := ini.Empty()
iniFile := ini.Empty(ini.LoadOptions{AllowNonUniqueSections: true})
iniFile.DeleteSection(ini.DefaultSection)
for _, branch := range branches {
for _, item := range branch {
if _, ok := item.Key.(sops.Comment); ok {
@@ -95,7 +96,7 @@ func (store Store) iniFromTreeBranches(branches sops.TreeBranches) ([]byte, erro
}
func (store Store) treeBranchesFromIni(in []byte) (sops.TreeBranches, error) {
iniFile, err := ini.Load(in)
iniFile, err := ini.LoadSources(ini.LoadOptions{AllowNonUniqueSections: true}, in)
if err != nil {
return nil, err
}
@@ -143,7 +144,7 @@ func (store Store) treeItemFromSection(section *ini.Section) (sops.TreeItem, err
// 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)
iniFileOuter, err := ini.LoadSources(ini.LoadOptions{AllowNonUniqueSections: true}, in)
if err != nil {
return sops.Tree{}, err
}

View File

@@ -3,8 +3,8 @@ package ini
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/getsops/sops/v3"
"github.com/stretchr/testify/assert"
)
func TestDecodeIni(t *testing.T) {
@@ -127,6 +127,55 @@ func TestEncodeIniWithEscaping(t *testing.T) {
assert.Equal(t, expected, branches)
}
func TestEncodeIniWithDuplicateSections(t *testing.T) {
branches := sops.TreeBranches{
sops.TreeBranch{
sops.TreeItem{
Key: "DEFAULT",
Value: interface{}(sops.TreeBranch(nil)),
},
sops.TreeItem{
Key: "foo",
Value: sops.TreeBranch{
sops.TreeItem{
Key: "foo",
Value: "bar",
},
sops.TreeItem{
Key: "baz",
Value: "3.0",
},
sops.TreeItem{
Key: "qux",
Value: "false",
},
},
},
sops.TreeItem{
Key: "foo",
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 TestUnmarshalMetadataFromNonSOPSFile(t *testing.T) {
data := []byte(`hello=2`)
store := Store{}

View File

@@ -186,18 +186,20 @@ func (store Store) encodeValue(v interface{}) ([]byte, error) {
func (store Store) encodeArray(array []interface{}) ([]byte, error) {
out := "["
for i, item := range array {
empty := true
for _, item := range array {
if _, ok := item.(sops.Comment); ok {
continue
}
if !empty {
out += ","
}
v, err := store.encodeValue(item)
if err != nil {
return nil, err
}
out += string(v)
if i != len(array)-1 {
out += ","
}
empty = false
}
out += "]"
return []byte(out), nil
@@ -205,10 +207,14 @@ func (store Store) encodeArray(array []interface{}) ([]byte, error) {
func (store Store) encodeTree(tree sops.TreeBranch) ([]byte, error) {
out := "{"
for i, item := range tree {
empty := true
for _, item := range tree {
if _, ok := item.Key.(sops.Comment); ok {
continue
}
if !empty {
out += ","
}
v, err := store.encodeValue(item.Value)
if err != nil {
return nil, fmt.Errorf("Error encoding value %s: %s", v, err)
@@ -218,9 +224,7 @@ func (store Store) encodeTree(tree sops.TreeBranch) ([]byte, error) {
return nil, fmt.Errorf("Error encoding key %s: %s", k, err)
}
out += string(k) + `: ` + string(v)
if i != len(tree)-1 {
out += ","
}
empty = false
}
return []byte(out + "}"), nil
}
@@ -326,6 +330,7 @@ func (store *Store) EmitEncryptedFile(in sops.Tree) ([]byte, error) {
if err != nil {
return nil, fmt.Errorf("Error marshaling to json: %s", err)
}
out = append(out, '\n')
return out, nil
}
@@ -336,6 +341,7 @@ func (store *Store) EmitPlainFile(in sops.TreeBranches) ([]byte, error) {
if err != nil {
return nil, fmt.Errorf("Error marshaling to json: %s", err)
}
out = append(out, '\n')
return out, nil
}

View File

@@ -34,7 +34,8 @@ func TestDecodeJSON(t *testing.T) {
}
}
}
}`
}
`
expected := sops.TreeBranch{
sops.TreeItem{
Key: "glossary",
@@ -312,7 +313,8 @@ func TestEncodeJSONArrayOfObjects(t *testing.T) {
},
2
]
}`
}
`
store := Store{
config: config.JSONStoreConfig{
Indent: -1,
@@ -446,7 +448,8 @@ func TestIndentTwoSpaces(t *testing.T) {
},
2
]
}`
}
`
store := Store{
config: config.JSONStoreConfig{
Indent: 2,
@@ -488,7 +491,8 @@ func TestIndentDefault(t *testing.T) {
},
2
]
}`
}
`
store := Store{
config: config.JSONStoreConfig{
Indent: -1,
@@ -530,7 +534,8 @@ func TestNoIndent(t *testing.T) {
},
2
]
}`
}
`
store := Store{
config: config.JSONStoreConfig{
Indent: 0,
@@ -539,4 +544,94 @@ func TestNoIndent(t *testing.T) {
out, err := store.EmitPlainFile(tree.Branches)
assert.Nil(t, err)
assert.Equal(t, expected, string(out))
}
func TestConflictingAttributes(t *testing.T) {
// See https://stackoverflow.com/a/23195243
// Duplicate keys in json is technically valid, but discouraged.
// Implementations may handle them differently. ECMA-262 says
//
// > In the case where there are duplicate name Strings within an object,
// > lexically preceding values for the same key shall be overwritten.
data := `
{
"hello": "Sops config file",
"hello": "Doubles are ok",
"hello": ["repeatedly"],
"hello": 3.14
}
`
s := new(Store)
_, err := s.LoadPlainFile([]byte(data))
assert.Nil(t, err)
}
func TestComments(t *testing.T) {
tree := sops.Tree{
Branches: sops.TreeBranches{
sops.TreeBranch{
sops.TreeItem{
Key: "foo",
Value: []interface{}{
sops.Comment{Value: " comment 0"},
sops.TreeBranch{
sops.TreeItem{
Key: sops.Comment{Value: " comment 1"},
Value: nil,
},
sops.TreeItem{
Key: "foo",
Value: 3,
},
sops.TreeItem{
Key: sops.Comment{Value: " comment 2"},
Value: nil,
},
sops.TreeItem{
Key: sops.Comment{Value: " comment 3"},
Value: nil,
},
sops.TreeItem{
Key: "bar",
Value: false,
},
sops.TreeItem{
Key: sops.Comment{Value: " comment 4"},
Value: nil,
},
sops.TreeItem{
Key: sops.Comment{Value: " comment 5"},
Value: nil,
},
},
sops.Comment{Value: " comment 6"},
sops.Comment{Value: " comment 7"},
2,
sops.Comment{Value: " comment 8"},
sops.Comment{Value: " comment 9"},
},
},
},
},
}
expected := `{
"foo": [
{
"foo": 3,
"bar": false
},
2
]
}
`
store := Store{
config: config.JSONStoreConfig{
Indent: 2,
},
}
out, err := store.EmitPlainFile(tree.Branches)
assert.Nil(t, err)
assert.Equal(t, expected, string(out))
}

View File

@@ -307,6 +307,13 @@ func (store *Store) LoadEncryptedFile(in []byte) (sops.Tree, error) {
// sops.Tree runtime object
func (store *Store) LoadPlainFile(in []byte) (sops.TreeBranches, error) {
var branches sops.TreeBranches
if len(in) > 0 {
// This is needed to make the yaml-decoder check for uniqueness of keys
// Can probably be removed when https://github.com/go-yaml/yaml/issues/814 is merged.
if err := yaml.NewDecoder(bytes.NewReader(in)).Decode(make(map[string]interface{})); err != nil {
return nil, err
}
}
d := yaml.NewDecoder(bytes.NewReader(in))
for {
var data yaml.Node
@@ -322,6 +329,12 @@ func (store *Store) LoadPlainFile(in []byte) (sops.TreeBranches, error) {
if err != nil {
return nil, fmt.Errorf("Error unmarshaling input YAML: %s", err)
}
// Prevent use of reserved keywords
for _, item := range branch {
if item.Key == stores.SopsMetadataKey {
return nil, fmt.Errorf("YAML doc used reserved word '%v'", item.Key)
}
}
branches = append(branches, branch)
}
return branches, nil

View File

@@ -62,19 +62,19 @@ key4: *bar
var ALIASES_BRANCHES = sops.TreeBranches{
sops.TreeBranch{
sops.TreeItem{
Key: "key1",
Key: "key1",
Value: []interface{}{
"foo",
},
},
sops.TreeItem{
Key: "key2",
Key: "key2",
Value: []interface{}{
"foo",
},
},
sops.TreeItem{
Key: "key3",
Key: "key3",
Value: sops.TreeBranch{
sops.TreeItem{
Key: "foo",
@@ -87,7 +87,7 @@ var ALIASES_BRANCHES = sops.TreeBranches{
},
},
sops.TreeItem{
Key: "key4",
Key: "key4",
Value: sops.TreeBranch{
sops.TreeItem{
Key: "foo",
@@ -237,7 +237,6 @@ prometheus-node-exporter:
- --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)$
`)
func TestUnmarshalMetadataFromNonSOPSFile(t *testing.T) {
data := []byte(`hello: 2`)
_, err := (&Store{}).LoadEncryptedFile(data)
@@ -398,3 +397,35 @@ func TestHasSopsTopLevelKey(t *testing.T) {
})
assert.Equal(t, ok, false)
}
func TestDuplicateAttributes(t *testing.T) {
// Duplicate keys are _not_ valid yaml.
//
// See https://yaml.org/spec/1.2.2/#mapping
// > The content of a mapping node is an unordered set of key/value node pairs,
// > with the restriction that each of the keys is unique.
//
data := `
hello: Sops config file
hello: Duplicates are not ok
rootunique:
key2: "value"
key2: "foo"
`
s := new(Store)
_, err := s.LoadPlainFile([]byte(data))
assert.NotNil(t, err)
assert.Equal(t, `yaml: unmarshal errors:
line 3: mapping key "hello" already defined at line 2`, err.Error())
}
func TestReservedAttributes(t *testing.T) {
data := `
hello: Sops config file
sops: The attribute 'sops' must be rejected, otherwise the file cannot be opened later on
`
s := new(Store)
_, err := s.LoadPlainFile([]byte(data))
assert.NotNil(t, err)
assert.Equal(t, `YAML doc used reserved word 'sops'`, err.Error())
}

View File

@@ -12,11 +12,13 @@ import (
)
// Version represents the value of the current semantic version.
var Version = "3.9.0"
var Version = "3.9.4"
// PrintVersion prints the current version of sops. If the flag
// `--disable-version-check` is set, the function will not attempt
// to retrieve the latest version from the GitHub API.
// `--disable-version-check` is set or if the environment variable
// SOPS_DISABLE_VERSION_CHECK is set to a value that is considered
// true by https://pkg.go.dev/strconv#ParseBool, the function will
// not attempt to retrieve the latest version from the GitHub API.
//
// If the flag is not set, the function will attempt to retrieve
// the latest version from the GitHub API and compare it to the