├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── release.yml │ └── validate.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── api ├── client.go ├── client_test.go ├── doc.go ├── error.go ├── error_test.go ├── folder_copy.go ├── folder_copy_test.go ├── folder_delete.go ├── folder_delete_meta.go ├── folder_delete_meta_test.go ├── folder_delete_test.go ├── folder_destroy.go ├── folder_destroy_test.go ├── folder_list.go ├── folder_list_test.go ├── folder_move.go ├── folder_move_test.go ├── folder_read.go ├── folder_read_test.go ├── folder_search.go ├── folder_search_test.go ├── folder_write.go ├── folder_write_test.go ├── helpers.go ├── helpers_test.go ├── main_test.go ├── mount_provider.go ├── mounts.go ├── mounts_test.go ├── path_copy.go ├── path_copy_test.go ├── path_delete.go ├── path_delete_meta.go ├── path_delete_meta_test.go ├── path_delete_test.go ├── path_destroy.go ├── path_destroy_test.go ├── path_list.go ├── path_list_test.go ├── path_move.go ├── path_move_test.go ├── path_read.go ├── path_read_test.go ├── path_search.go ├── path_search_test.go ├── path_update.go ├── path_update_test.go ├── path_write.go ├── path_write_test.go ├── version.go └── version_test.go ├── cmd ├── cli.go ├── cli_test.go ├── doc.go ├── docs.go ├── docs_test.go ├── flags.go ├── flags_test.go ├── folder.go ├── folder_copy.go ├── folder_copy_test.go ├── folder_delete.go ├── folder_delete_meta.go ├── folder_delete_meta_test.go ├── folder_delete_test.go ├── folder_destroy.go ├── folder_list.go ├── folder_list_test.go ├── folder_move.go ├── folder_move_test.go ├── folder_read.go ├── folder_read_test.go ├── folder_search.go ├── folder_search_test.go ├── folder_test.go ├── folder_write.go ├── folder_write_test.go ├── helpers.go ├── helpers_test.go ├── main_test.go ├── path.go ├── path_copy.go ├── path_copy_test.go ├── path_delete.go ├── path_delete_meta.go ├── path_delete_meta_test.go ├── path_delete_test.go ├── path_destroy.go ├── path_list.go ├── path_list_test.go ├── path_move.go ├── path_move_test.go ├── path_read.go ├── path_read_test.go ├── path_search.go ├── path_search_test.go ├── path_test.go ├── path_update.go ├── path_write.go ├── vaku.go ├── vaku_test.go ├── version.go └── version_test.go ├── docs └── cli │ ├── vaku.md │ ├── vaku_completion.md │ ├── vaku_completion_bash.md │ ├── vaku_completion_fish.md │ ├── vaku_completion_powershell.md │ ├── vaku_completion_zsh.md │ ├── vaku_folder.md │ ├── vaku_folder_copy.md │ ├── vaku_folder_delete-meta.md │ ├── vaku_folder_delete.md │ ├── vaku_folder_list.md │ ├── vaku_folder_move.md │ ├── vaku_folder_read.md │ ├── vaku_folder_search.md │ ├── vaku_folder_write.md │ ├── vaku_path.md │ ├── vaku_path_copy.md │ ├── vaku_path_delete-meta.md │ ├── vaku_path_delete.md │ ├── vaku_path_list.md │ ├── vaku_path_move.md │ ├── vaku_path_read.md │ ├── vaku_path_search.md │ ├── vaku_path_write.md │ └── vaku_version.md ├── go.mod ├── go.sum ├── main.go ├── main_test.go └── www ├── assets ├── css │ ├── style.css │ └── water │ │ ├── dark.min.css │ │ └── dark.min.css.map ├── icons │ ├── favicon-16x16.png │ ├── favicon-180x180.png │ ├── favicon-192x192.png │ ├── favicon-32x32.png │ ├── favicon-512x512.png │ └── favicon.ico ├── images │ ├── logo-vaku-sm.png │ ├── logo-vaku-sm.webp │ ├── logo-vaku.png │ └── logo-vaku.webp └── site │ └── site.webmanifest ├── fetch-assets.sh ├── index.html └── robots.txt /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @lingrino 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | open-pull-requests-limit: 100 5 | directory: / 6 | schedule: 7 | time: "08:00" 8 | timezone: "America/Los_Angeles" 9 | interval: weekly 10 | allow: 11 | - dependency-type: direct 12 | groups: 13 | all: 14 | patterns: 15 | - "*" 16 | reviewers: 17 | - lingrino 18 | 19 | - package-ecosystem: docker 20 | open-pull-requests-limit: 100 21 | directory: / 22 | schedule: 23 | time: "08:00" 24 | timezone: "America/Los_Angeles" 25 | interval: weekly 26 | allow: 27 | - dependency-type: direct 28 | groups: 29 | all: 30 | patterns: 31 | - "*" 32 | reviewers: 33 | - lingrino 34 | 35 | - package-ecosystem: gomod 36 | open-pull-requests-limit: 100 37 | directory: / 38 | schedule: 39 | time: "08:00" 40 | timezone: "America/Los_Angeles" 41 | interval: weekly 42 | allow: 43 | - dependency-type: direct 44 | groups: 45 | all: 46 | patterns: 47 | - "*" 48 | reviewers: 49 | - lingrino 50 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: read 13 | packages: write 14 | attestations: write 15 | id-token: write 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 21 | - name: Install Go 22 | uses: actions/setup-go@v5 23 | with: 24 | go-version: stable # https://golang.org/dl/ 25 | - name: Check API Version 26 | run: | 27 | version="$(git describe --tags)" 28 | version="${version/v}" 29 | grep -q "${version}" api/version.go 30 | shell: bash 31 | - name: Docker Login 32 | uses: docker/login-action@v3.3.0 33 | with: 34 | registry: ghcr.io 35 | username: ${{ github.repository_owner }} 36 | password: ${{ secrets.GITHUB_TOKEN }} 37 | - name: Release 38 | uses: goreleaser/goreleaser-action@v6.2.1 39 | with: 40 | args: release --clean 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GORELEASER_GITHUB_TOKEN }} 43 | -------------------------------------------------------------------------------- /.github/workflows/validate.yml: -------------------------------------------------------------------------------- 1 | name: Validate 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | 7 | jobs: 8 | docs: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | - name: Install Go 14 | uses: actions/setup-go@v5 15 | with: 16 | go-version: stable 17 | - name: Docs Check 18 | run: | 19 | before=$(cat docs/cli/*.md | md5sum) 20 | go run main.go docs docs/cli 21 | after=$(cat docs/cli/*.md | md5sum) 22 | if [ "$before" != "$after" ]; then exit 1; fi 23 | shell: bash 24 | golangci: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v4 29 | - name: Install Go 30 | uses: actions/setup-go@v5 31 | with: 32 | go-version: stable 33 | - name: Lint 34 | uses: golangci/golangci-lint-action@v6 35 | with: 36 | args: --timeout=5m 37 | gomod: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Checkout 41 | uses: actions/checkout@v4 42 | - name: Install Go 43 | uses: actions/setup-go@v5 44 | with: 45 | go-version: stable 46 | - name: Go Mod Tidy 47 | run: test -z $(go mod tidy) 48 | govulncheck: 49 | runs-on: ubuntu-latest 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v4 53 | - name: Install Go 54 | uses: actions/setup-go@v5 55 | with: 56 | go-version: stable 57 | - name: Install govulncheck 58 | run: go install golang.org/x/vuln/cmd/govulncheck@latest 59 | - name: Run govulncheck 60 | run: govulncheck ./... 61 | goreleaser: 62 | runs-on: ubuntu-latest 63 | steps: 64 | - name: Checkout 65 | uses: actions/checkout@v4 66 | - name: Install Go 67 | uses: actions/setup-go@v5 68 | with: 69 | go-version: stable 70 | - name: Goreleaser Check 71 | uses: goreleaser/goreleaser-action@v6.2.1 72 | with: 73 | args: check 74 | test: 75 | runs-on: ubuntu-latest 76 | steps: 77 | - name: Checkout 78 | uses: actions/checkout@v4 79 | - name: Install Go 80 | uses: actions/setup-go@v5 81 | with: 82 | go-version: stable 83 | - name: Cache Modules 84 | uses: actions/cache@v4 85 | with: 86 | path: ~/go/pkg/mod 87 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 88 | restore-keys: | 89 | ${{ runner.os }}-go- 90 | - name: Test 91 | run: go test -cover -coverprofile=c.out -covermode=atomic -race -v ./... 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries 2 | dist/ 3 | *.test 4 | 5 | # Coverage 6 | *.out 7 | coverage.txt 8 | 9 | # Benchmark 10 | bench.txt 11 | 12 | # IDE settings 13 | .idea/ 14 | .vscode/ 15 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 5m 3 | 4 | linters: 5 | enable: 6 | - bodyclose 7 | - copyloopvar 8 | - dogsled 9 | - dupl 10 | - errcheck 11 | - errorlint 12 | - forcetypeassert 13 | - funlen 14 | - gochecknoglobals 15 | - gochecknoinits 16 | - gocognit 17 | - goconst 18 | - gocritic 19 | - gocyclo 20 | - godot 21 | - godox 22 | - gofmt 23 | - goimports 24 | - gomoddirectives 25 | - goprintffuncname 26 | - gosec 27 | - gosimple 28 | - govet 29 | - ineffassign 30 | - lll 31 | - misspell 32 | - mnd 33 | - nakedret 34 | - nestif 35 | - prealloc 36 | - rowserrcheck 37 | - staticcheck 38 | - stylecheck 39 | - thelper 40 | - tparallel 41 | - typecheck 42 | - unconvert 43 | - unparam 44 | - unused 45 | - whitespace 46 | - wrapcheck 47 | 48 | linters-settings: 49 | dupl: 50 | threshold: 100 51 | errcheck: 52 | check-blank: true 53 | gocognit: 54 | min-complexity: 15 55 | gocyclo: 56 | min-complexity: 10 57 | nakedret: 58 | max-func-lines: 0 59 | goconst: 60 | min-occurrences: 3 61 | 62 | issues: 63 | exclude-rules: 64 | - path: main.go 65 | linters: 66 | - gochecknoglobals # global in main.go needed for testing 67 | - path: cmd/ 68 | linters: 69 | - dupl # CLIs are a lot of similar-looking code! 70 | - wrapcheck # errors don't need to be wrapped in thin CLIs 71 | - path: _test.go 72 | linters: 73 | - dupl # many functions in tests look like duplicates 74 | - funlen # test function can be very long due to test cases 75 | - gochecknoglobals # globals in tests are fine 76 | - gocognit # test functions can be long/complex 77 | - goconst # there are many magic numbers in tests 78 | - gomnd # there are many magic numbers in tests 79 | - wrapcheck # errors don't need to be wrapped in tests 80 | - path: example_*_test.go 81 | linters: 82 | - errcheck # not required to check errors in examples 83 | - ineffassign # not required to check errors in examples 84 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | project_name: vaku 4 | before: 5 | hooks: 6 | - go mod download 7 | builds: 8 | - id: vaku 9 | binary: vaku 10 | goos: 11 | - darwin 12 | - linux 13 | - windows 14 | goarch: 15 | - 386 16 | - amd64 17 | - arm 18 | - arm64 19 | ignore: 20 | - goos: darwin 21 | goarch: 386 22 | env: 23 | - CGO_ENABLED=0 24 | release: 25 | prerelease: auto 26 | changelog: 27 | sort: asc 28 | filters: 29 | exclude: 30 | - Merge pull request 31 | - Merge branch 32 | archives: 33 | - id: vaku 34 | formats: ["zip"] 35 | brews: 36 | - name: vaku 37 | description: CLI that extends the official Vault client. 38 | homepage: https://vaku.dev/ 39 | skip_upload: auto 40 | repository: 41 | owner: lingrino 42 | name: homebrew-tap 43 | directory: Formula 44 | commit_author: 45 | name: Sean Lingren 46 | email: sean@lingren.com 47 | test: | 48 | system "#{bin}/vaku version" 49 | nfpms: 50 | - id: vaku 51 | description: CLI that extends the official Vault client. 52 | homepage: https://vaku.dev/ 53 | maintainer: "Sean Lingren " 54 | license: MIT 55 | formats: 56 | - deb 57 | - rpm 58 | dockers: 59 | - dockerfile: Dockerfile 60 | image_templates: 61 | - ghcr.io/lingrino/vaku:latest 62 | - ghcr.io/lingrino/vaku:{{ .Version }} 63 | - ghcr.io/lingrino/vaku:{{ .Major }} 64 | - ghcr.io/lingrino/vaku:{{ .Major }}.{{ .Minor }} 65 | build_flag_templates: 66 | - --label=org.opencontainers.image.created={{.Date}} 67 | - --label=org.opencontainers.image.revision={{.FullCommit}} 68 | - --label=org.opencontainers.image.version={{.Version}} 69 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | 3 | # https://github.com/opencontainers/image-spec/blob/master/annotations.md 4 | LABEL org.opencontainers.image.ref.name="vaku" \ 5 | org.opencontainers.image.ref.title="vaku" \ 6 | org.opencontainers.image.description="A CLI to extend the official Vault client" \ 7 | org.opencontainers.image.licenses="MIT" \ 8 | org.opencontainers.image.authors="sean@lingren.com" \ 9 | org.opencontainers.image.url="https://vaku.dev" \ 10 | org.opencontainers.image.documentation="https://vaku.dev" \ 11 | org.opencontainers.image.source="https://github.com/lingrino/vaku" 12 | 13 | COPY vaku / 14 | ENTRYPOINT ["/vaku"] 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sean Lingren 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vaku 2 | 3 | [![Vaku](www/assets/images/logo-vaku-sm.png?raw=true)](www/assets/logo-vaku-sm.png "Vaku") 4 | 5 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/lingrino/vaku/v2/api)](https://pkg.go.dev/github.com/lingrino/vaku/v2/api) 6 | [![goreportcard](https://goreportcard.com/badge/github.com/lingrino/vaku)](https://goreportcard.com/report/github.com/lingrino/vaku) 7 | 8 | Vaku is a CLI and API for running path- and folder-based operations on the Vault Key/Value secrets engine. Vaku extends the existing Vault CLI and API by allowing you to run the same path-based list/read/write/delete functions on folders as well. Vaku also lets you search, copy, and move both secrets and folders. 9 | 10 | ## Installation 11 | 12 | ### Homebrew 13 | 14 | ```shell 15 | brew install lingrino/tap/vaku 16 | ``` 17 | 18 | ### Scoop 19 | 20 | ```shell 21 | scoop bucket add vaku https://github.com/lingrino/scoop-vaku.git 22 | scoop install vaku 23 | ``` 24 | 25 | ### Docker 26 | 27 | ```shell 28 | docker run ghcr.io/lingrino/vaku --help 29 | ``` 30 | 31 | ### Binary 32 | 33 | Download the latest binary or deb/rpm for your os/arch from the [releases page](https://github.com/lingrino/vaku/releases). 34 | 35 | ## Usage 36 | 37 | Vaku CLI documentation can be found on the command line using either `vaku help [cmd]` or `vaku [cmd] --help`. The same documentation is also available in markdown form in the [docs/cli](docs/cli/vaku.md) folder. 38 | 39 | ## API 40 | 41 | Documentation for the Vaku API is on [pkg.go.dev](https://pkg.go.dev/github.com/lingrino/vaku/v2/api). 42 | 43 | ## Contributing 44 | 45 | Suggestions and contributions of all kinds are welcome! If there is functionality you would like to see in Vaku please open an Issue or Pull Request and I will be sure to address it. 46 | 47 | ## Tests 48 | 49 | Vaku is well tested and uses only the standard go testing tools. 50 | 51 | ```shell 52 | $ go test -cover -race ./... 53 | ok github.com/lingrino/vaku/v2 0.095s coverage: 100.0% of statements 54 | ok github.com/lingrino/vaku/v2/api 12.065s coverage: 100.0% of statements 55 | ok github.com/lingrino/vaku/v2/cmd 0.168s coverage: 100.0% of statements 56 | ``` 57 | -------------------------------------------------------------------------------- /api/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package vaku provides an API for interacting with the Vault Key/Value secrets engine. Vaku works 3 | indentically on V1 and V2 K/V mount versions except in cases where the command is only supported on 4 | the V2 engine. Vaku supports many useful commands like copy, move, and search on Vault paths and 5 | folders alike. 6 | */ 7 | package vaku 8 | -------------------------------------------------------------------------------- /api/error.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | // Errors that are not specific to one file/function. 9 | var ( 10 | // ErrContext when ctx.Err() is not nil. 11 | ErrContext = errors.New("context") 12 | // ErrDecodeSecret when secret data cannot be extracted from a vault secret. 13 | ErrDecodeSecret = errors.New("decode secret") 14 | // ErrJSONMarshal when secret data cannot be marshaled into json. 15 | ErrJSONMarshal = errors.New("json marshal") 16 | // ErrNilData when passed data is nil. 17 | ErrNilData = errors.New("nil data") 18 | // ErrUnknownError when returning an error with no data. 19 | ErrUnknownError = errors.New("unknown error") 20 | ) 21 | 22 | // wrapErr is a struct that implements the error interface and provides Is() and Unwrap() methods 23 | // that allow go 1.13+ error features. The fmt.Errorf function does something similar but does not 24 | // provide an Is() function which means you cannot use sentinel errors with added context and also 25 | // wrap the returned error. Context - https://golang.org/src/fmt/errors.go. 26 | type wrapErr struct { 27 | msg string 28 | is error 29 | wraps error 30 | } 31 | 32 | // verify compliance with error interface. 33 | var _ error = (*wrapErr)(nil) //nolint:errcheck 34 | 35 | // newWrapErr returns a wrapErr that merges defaults and input. 36 | func newWrapErr(msg string, is, wraps error) *wrapErr { 37 | switch { 38 | case msg == "" && is == nil: 39 | is = ErrUnknownError 40 | case is == nil: 41 | is = errors.New(msg) 42 | } 43 | 44 | switch { 45 | case msg == "" && wraps == nil || msg == is.Error(): 46 | msg = is.Error() 47 | case msg == "": 48 | msg = fmt.Sprintf("%v: %v", is, wraps) 49 | case wraps == nil: 50 | msg = fmt.Sprintf("%v: %v", msg, is) 51 | default: 52 | msg = fmt.Sprintf("%v: %v: %v", msg, is, wraps) 53 | } 54 | 55 | return &wrapErr{ 56 | msg: msg, 57 | is: is, 58 | wraps: wraps, 59 | } 60 | } 61 | 62 | // Is compares an error to e.is. 63 | func (e *wrapErr) Is(target error) bool { 64 | return target == e.is //nolint:errorlint 65 | } 66 | 67 | // Error() returns the error message. 68 | func (e *wrapErr) Error() string { 69 | return e.msg 70 | } 71 | 72 | // Unwrap returns the wrapped error. 73 | func (e *wrapErr) Unwrap() error { 74 | return e.wraps 75 | } 76 | 77 | // ctxErr returns a wrapped ErrContext if err != nil. 78 | func ctxErr(err error) error { 79 | if err == nil { 80 | return nil 81 | } 82 | 83 | return newWrapErr("", ErrContext, err) 84 | } 85 | -------------------------------------------------------------------------------- /api/error_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | var ( 12 | // errInject is used when injecting errors in tests. 13 | errInject = errors.New("injected error") 14 | ) 15 | 16 | func TestNewWrapErr(t *testing.T) { 17 | t.Parallel() 18 | 19 | tests := []struct { 20 | name string 21 | giveMsg string 22 | giveIs error 23 | giveWrap error 24 | wantErr *wrapErr 25 | }{ 26 | { 27 | name: "nil all", 28 | giveMsg: "", 29 | giveIs: nil, 30 | giveWrap: nil, 31 | wantErr: &wrapErr{ 32 | msg: "unknown error", 33 | is: ErrUnknownError, 34 | wraps: nil, 35 | }, 36 | }, 37 | { 38 | name: "nil msg and is", 39 | giveMsg: "", 40 | giveIs: nil, 41 | giveWrap: errInject, 42 | wantErr: &wrapErr{ 43 | msg: fmt.Sprintf("%v: %v", ErrUnknownError, errInject), 44 | is: ErrUnknownError, 45 | wraps: errInject, 46 | }, 47 | }, 48 | { 49 | name: "nil is and wrap", 50 | giveMsg: "random error", 51 | giveIs: nil, 52 | giveWrap: nil, 53 | wantErr: &wrapErr{ 54 | msg: "random error", 55 | is: errors.New("random error"), 56 | wraps: nil, 57 | }, 58 | }, 59 | { 60 | name: "nil msg and wrap", 61 | giveMsg: "", 62 | giveIs: errInject, 63 | giveWrap: nil, 64 | wantErr: &wrapErr{ 65 | msg: errInject.Error(), 66 | is: errInject, 67 | wraps: nil, 68 | }, 69 | }, 70 | { 71 | name: "msg and nil wrap", 72 | giveMsg: "random error", 73 | giveIs: errInject, 74 | giveWrap: nil, 75 | wantErr: &wrapErr{ 76 | msg: fmt.Sprintf("%v: %v", "random error", errInject), 77 | is: errInject, 78 | wraps: nil, 79 | }, 80 | }, 81 | { 82 | name: "standard error", 83 | giveMsg: "context here", 84 | giveIs: errors.New("standard error"), 85 | giveWrap: errInject, 86 | wantErr: &wrapErr{ 87 | msg: fmt.Sprintf("%v: %v: %v", "context here", errors.New("standard error"), errInject), 88 | is: errors.New("standard error"), 89 | wraps: errInject, 90 | }, 91 | }, 92 | } 93 | 94 | for _, tt := range tests { 95 | t.Run(tt.name, func(t *testing.T) { 96 | t.Parallel() 97 | 98 | err := newWrapErr(tt.giveMsg, tt.giveIs, tt.giveWrap) 99 | assert.Equal(t, tt.wantErr, err) 100 | }) 101 | } 102 | } 103 | 104 | func TestCtxErr(t *testing.T) { 105 | t.Parallel() 106 | 107 | tests := []struct { 108 | name string 109 | give error 110 | want []error 111 | }{ 112 | { 113 | name: "nil error", 114 | give: nil, 115 | want: nil, 116 | }, 117 | { 118 | name: "error", 119 | give: errInject, 120 | want: []error{ErrContext, errInject}, 121 | }, 122 | } 123 | 124 | for _, tt := range tests { 125 | t.Run(tt.name, func(t *testing.T) { 126 | t.Parallel() 127 | 128 | compareErrors(t, ctxErr(tt.give), tt.want) 129 | }) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /api/folder_copy.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | ) 7 | 8 | var ( 9 | // ErrFolderCopy when FolderCopy fails. 10 | ErrFolderCopy = errors.New("folder copy") 11 | ) 12 | 13 | // FolderCopy copies data at a source folder to a destination folder.. 14 | func (c *Client) FolderCopy(ctx context.Context, src, dst string) error { 15 | read, err := c.FolderRead(ctx, src) 16 | if err != nil { 17 | return newWrapErr("read from "+src, ErrFolderCopy, err) 18 | } 19 | 20 | // Switch the key prefixes from src to dst 21 | c.swapPaths(read, src, dst) 22 | 23 | err = c.dc.FolderWrite(ctx, read) 24 | if err != nil { 25 | return newWrapErr("write to "+dst, ErrFolderCopy, err) 26 | } 27 | 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /api/folder_copy_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestFolderCopy(t *testing.T) { 11 | t.Parallel() 12 | 13 | tests := []struct { 14 | giveSrc string 15 | giveDst string 16 | wantErr []error 17 | wantNilDst bool 18 | }{ 19 | { 20 | giveSrc: "0/1", 21 | giveDst: "copy/0/1", 22 | wantErr: nil, 23 | }, 24 | { 25 | giveSrc: "0", 26 | giveDst: "copy/0", 27 | wantErr: nil, 28 | }, 29 | { 30 | giveSrc: "0/4/13/24/25/26/error/read/inject", 31 | giveDst: "copy/0/4/13/24/25/26", 32 | wantErr: []error{ErrFolderCopy, ErrFolderRead, ErrFolderReadChan, ErrPathRead, ErrVaultRead}, 33 | wantNilDst: true, 34 | }, 35 | { 36 | giveSrc: "0/4/13/24/25/26", 37 | giveDst: "copy/0/4/13/24/25/26/error/write/inject", 38 | wantErr: []error{ErrFolderCopy, ErrFolderWrite, ErrPathWrite, ErrVaultWrite}, 39 | wantNilDst: true, 40 | }, 41 | } 42 | 43 | for _, tt := range tests { 44 | t.Run(testName(tt.giveSrc, tt.giveDst), func(t *testing.T) { 45 | t.Parallel() 46 | for _, prefixPair := range seededPrefixProduct(t) { 47 | t.Run(testName(prefixPair[0], prefixPair[1]), func(t *testing.T) { 48 | t.Parallel() 49 | 50 | pathSrc := PathJoin(prefixPair[0], tt.giveSrc) 51 | pathDst := PathJoin(prefixPair[1], tt.giveDst) 52 | 53 | err := sharedVaku.FolderCopy(context.Background(), pathSrc, pathDst) 54 | compareErrors(t, err, tt.wantErr) 55 | 56 | readSrc, errSrc := sharedVakuClean.FolderRead(context.Background(), pathSrc) 57 | readDst, errDst := sharedVakuClean.dc.FolderRead(context.Background(), pathDst) 58 | assert.NoError(t, errSrc) 59 | assert.NoError(t, errDst) 60 | 61 | if tt.wantNilDst { 62 | assert.Nil(t, readDst) 63 | } else { 64 | TrimPrefixMap(readSrc, prefixPair[0]) 65 | TrimPrefixMap(readDst, prefixPair[1]) 66 | assert.Equal(t, readSrc, readDst) 67 | } 68 | }) 69 | } 70 | }) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /api/folder_delete.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "golang.org/x/sync/errgroup" 8 | ) 9 | 10 | var ( 11 | // ErrFolderDelete when FolderDelete fails. 12 | ErrFolderDelete = errors.New("folder delete") 13 | ) 14 | 15 | // FolderDelete recursively deletes the provided path and all subpaths. 16 | func (c *Client) FolderDelete(ctx context.Context, p string) error { 17 | err := c.folderDeleteWithFunc(ctx, p, c.PathDelete) 18 | if err != nil { 19 | return newWrapErr(p, ErrFolderDelete, err) 20 | } 21 | 22 | return nil 23 | } 24 | 25 | func (c *Client) folderDeleteWithFunc(ctx context.Context, p string, deleteF func(string) error) error { 26 | // eg manages workers reading from the paths channel 27 | eg, ctx := errgroup.WithContext(ctx) 28 | 29 | // list the path 30 | pathC, errC := c.FolderListChan(ctx, p) 31 | eg.Go(func() error { 32 | err := <-errC 33 | if err != nil { 34 | return err 35 | } 36 | return nil 37 | }) 38 | 39 | // fan out and process paths 40 | for i := 0; i < c.workers; i++ { 41 | eg.Go(func() error { 42 | return c.folderDeleteWork(&folderDeleteWorkInput{ 43 | ctx: ctx, 44 | root: p, 45 | pathC: pathC, 46 | deleteF: deleteF, 47 | }) 48 | }) 49 | } 50 | 51 | return eg.Wait() //nolint:wrapcheck 52 | } 53 | 54 | // folderDeleteWorkInput is the piecces needed to list a folder. 55 | type folderDeleteWorkInput struct { 56 | ctx context.Context 57 | root string 58 | pathC <-chan string 59 | deleteF func(string) error 60 | } 61 | 62 | // folderDeleteWork takes input from pathC, lists the path, adds listed folders back into pathC, and 63 | // adds non-folders into results. 64 | func (c *Client) folderDeleteWork(i *folderDeleteWorkInput) error { 65 | for { 66 | select { 67 | case <-i.ctx.Done(): 68 | return ctxErr(i.ctx.Err()) 69 | case path, ok := <-i.pathC: 70 | if !ok { 71 | return nil 72 | } 73 | path = c.inputPath(path, i.root) 74 | err := i.deleteF(path) 75 | if err != nil { 76 | return err 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /api/folder_delete_meta.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | ) 7 | 8 | var ( 9 | // ErrFolderDeleteMeta when FolderDeleteMeta fails. 10 | ErrFolderDeleteMeta = errors.New("folder delete meta") 11 | ) 12 | 13 | // FolderDeleteMeta deletes all secret metadata and versions for all secrets in a folder. Only works 14 | // on v2 kv engines. 15 | func (c *Client) FolderDeleteMeta(ctx context.Context, p string) error { 16 | err := c.folderDeleteWithFunc(ctx, p, c.PathDeleteMeta) 17 | if err != nil { 18 | return newWrapErr(p, ErrFolderDeleteMeta, err) 19 | } 20 | 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /api/folder_delete_meta_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestFolderDeleteMeta(t *testing.T) { 12 | t.Parallel() 13 | 14 | tests := []struct { 15 | give string 16 | wantReadBack map[string]map[string]any 17 | wantErrKV1 []error 18 | wantErrKV2 []error 19 | }{ 20 | { 21 | give: "0", 22 | wantReadBack: nil, 23 | wantErrKV1: []error{ErrFolderDeleteMeta, ErrPathDeleteMeta, ErrMountVersion}, 24 | wantErrKV2: nil, 25 | }, 26 | { 27 | give: "0/1", 28 | wantReadBack: nil, 29 | wantErrKV1: nil, 30 | wantErrKV2: nil, 31 | }, 32 | { 33 | give: "fake", 34 | wantReadBack: nil, 35 | wantErrKV1: nil, 36 | wantErrKV2: nil, 37 | }, 38 | { 39 | give: "0/4/13/24/25/error/list/inject", 40 | wantReadBack: map[string]map[string]any{ 41 | "26/27": { 42 | "28": "29", 43 | }, 44 | }, 45 | wantErrKV1: []error{ErrFolderDeleteMeta, ErrFolderListChan, ErrPathList, ErrVaultList}, 46 | wantErrKV2: []error{ErrFolderDeleteMeta, ErrFolderListChan, ErrPathList, ErrVaultList}, 47 | }, 48 | { 49 | give: "0/4/13/24/25/26/error/delete/inject", 50 | wantReadBack: map[string]map[string]any{ 51 | "27": { 52 | "28": "29", 53 | }, 54 | }, 55 | wantErrKV1: []error{ErrFolderDeleteMeta, ErrPathDeleteMeta, ErrMountVersion}, 56 | wantErrKV2: []error{ErrFolderDeleteMeta, ErrPathDeleteMeta, ErrVaultDelete}, 57 | }, 58 | } 59 | 60 | for _, tt := range tests { 61 | t.Run(testName(tt.give), func(t *testing.T) { 62 | t.Parallel() 63 | for _, prefix := range seededPrefixes(t, tt.give) { 64 | if strings.HasPrefix(prefix, "kv1") { 65 | t.Run(testName(prefix), func(t *testing.T) { 66 | t.Parallel() 67 | 68 | err := sharedVaku.FolderDeleteMeta(context.Background(), PathJoin(prefix, tt.give)) 69 | compareErrors(t, err, tt.wantErrKV1) 70 | }) 71 | } 72 | if strings.HasPrefix(prefix, "kv2") { 73 | t.Run(testName(prefix), func(t *testing.T) { 74 | t.Parallel() 75 | 76 | err := sharedVaku.FolderDeleteMeta(context.Background(), PathJoin(prefix, tt.give)) 77 | compareErrors(t, err, tt.wantErrKV2) 78 | 79 | readBack, err := sharedVakuClean.FolderRead(context.Background(), PathJoin(prefix, tt.give)) 80 | assert.NoError(t, err) 81 | assert.Equal(t, tt.wantReadBack, readBack) 82 | }) 83 | } 84 | } 85 | }) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /api/folder_delete_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestFolderDelete(t *testing.T) { 11 | t.Parallel() 12 | 13 | tests := []struct { 14 | give string 15 | wantReadBack map[string]map[string]any 16 | wantErr []error 17 | }{ 18 | { 19 | give: "0/1", 20 | wantReadBack: nil, 21 | wantErr: nil, 22 | }, 23 | { 24 | give: "0/4/13", 25 | wantReadBack: nil, 26 | wantErr: nil, 27 | }, 28 | { 29 | give: "empty/path", 30 | wantReadBack: nil, 31 | wantErr: nil, 32 | }, 33 | { 34 | give: "0/4/13/24/25/error/list/inject", 35 | wantReadBack: map[string]map[string]any{ 36 | "26/27": { 37 | "28": "29", 38 | }, 39 | }, 40 | wantErr: []error{ErrFolderDelete, ErrFolderListChan, ErrPathList, ErrVaultList}, 41 | }, 42 | { 43 | give: "0/4/13/24/25/26/error/delete/inject", 44 | wantReadBack: map[string]map[string]any{ 45 | "27": { 46 | "28": "29", 47 | }, 48 | }, 49 | wantErr: []error{ErrFolderDelete, ErrPathDelete, ErrVaultDelete}, 50 | }, 51 | } 52 | 53 | for _, tt := range tests { 54 | t.Run(testName(tt.give), func(t *testing.T) { 55 | t.Parallel() 56 | for _, prefix := range seededPrefixes(t, tt.give) { 57 | t.Run(testName(prefix), func(t *testing.T) { 58 | t.Parallel() 59 | 60 | err := sharedVaku.FolderDelete(context.Background(), PathJoin(prefix, tt.give)) 61 | compareErrors(t, err, tt.wantErr) 62 | 63 | readBack, err := sharedVakuClean.FolderRead(context.Background(), PathJoin(prefix, tt.give)) 64 | assert.NoError(t, err) 65 | assert.Equal(t, tt.wantReadBack, readBack) 66 | }) 67 | } 68 | }) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /api/folder_destroy.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "golang.org/x/sync/errgroup" 8 | ) 9 | 10 | var ( 11 | // ErrFolderDestroy when FolderDestroy fails. 12 | ErrFolderDestroy = errors.New("folder destroy") 13 | ) 14 | 15 | // FolderDestroy destroys versions of all secrets in a folder. Only works on v2 kv engines. 16 | func (c *Client) FolderDestroy(ctx context.Context, p string, versions []int) error { 17 | // eg manages workers reading from the paths channel 18 | eg, ctx := errgroup.WithContext(ctx) 19 | 20 | // list the path 21 | pathC, errC := c.FolderListChan(ctx, p) 22 | eg.Go(func() error { 23 | return <-errC 24 | }) 25 | 26 | // fan out and process paths 27 | for i := 0; i < c.workers; i++ { 28 | eg.Go(func() error { 29 | return c.folderDestroyWork(&folderDestroyWorkInput{ 30 | ctx: ctx, 31 | root: p, 32 | versions: versions, 33 | pathC: pathC, 34 | }) 35 | }) 36 | } 37 | 38 | err := eg.Wait() 39 | if err != nil { 40 | return newWrapErr(p, ErrFolderDestroy, err) 41 | } 42 | return nil 43 | } 44 | 45 | // folderDestroyWorkInput is the piecces needed to destroy a folder. 46 | type folderDestroyWorkInput struct { 47 | ctx context.Context 48 | root string 49 | versions []int 50 | pathC <-chan string 51 | } 52 | 53 | // folderDestroyWork takes input from pathC, lists the path, adds listed folders back into pathC, and 54 | // adds non-folders into results. 55 | func (c *Client) folderDestroyWork(i *folderDestroyWorkInput) error { 56 | for { 57 | select { 58 | case <-i.ctx.Done(): 59 | return ctxErr(i.ctx.Err()) 60 | case path, ok := <-i.pathC: 61 | if !ok { 62 | return nil 63 | } 64 | path = c.inputPath(path, i.root) 65 | err := c.PathDestroy(path, i.versions) 66 | if err != nil { 67 | return err 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /api/folder_destroy_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestFolderDestroy(t *testing.T) { 12 | t.Parallel() 13 | 14 | tests := []struct { 15 | give string 16 | wantReadBack map[string]map[string]any 17 | giveVersions []int 18 | wantErrKV1 []error 19 | wantErrKV2 []error 20 | }{ 21 | { 22 | give: "0", 23 | giveVersions: []int{1, 2, 3}, 24 | wantReadBack: nil, 25 | wantErrKV1: []error{ErrFolderDestroy, ErrPathDestroy, ErrMountVersion}, 26 | wantErrKV2: nil, 27 | }, 28 | { 29 | give: "0/1", 30 | giveVersions: []int{3}, 31 | wantReadBack: nil, 32 | wantErrKV1: nil, 33 | wantErrKV2: nil, 34 | }, 35 | { 36 | give: "0/4/13/24/25/error/list/inject", 37 | giveVersions: []int{1, 2}, 38 | wantReadBack: map[string]map[string]any{ 39 | "26/27": { 40 | "28": "29", 41 | }, 42 | }, 43 | wantErrKV1: []error{ErrFolderDestroy, ErrFolderListChan, ErrPathList, ErrVaultList}, 44 | wantErrKV2: []error{ErrFolderDestroy, ErrFolderListChan, ErrPathList, ErrVaultList}, 45 | }, 46 | { 47 | give: "0/4/13/24/25/26/error/write/inject", 48 | giveVersions: []int{1, 2}, 49 | wantReadBack: map[string]map[string]any{ 50 | "27": { 51 | "28": "29", 52 | }, 53 | }, 54 | wantErrKV1: []error{ErrFolderDestroy, ErrPathDestroy, ErrMountVersion}, 55 | wantErrKV2: []error{ErrFolderDestroy, ErrPathDestroy, ErrVaultWrite}, 56 | }, 57 | } 58 | 59 | for _, tt := range tests { 60 | t.Run(testName(tt.give), func(t *testing.T) { 61 | t.Parallel() 62 | for _, prefix := range seededPrefixes(t, tt.give) { 63 | if strings.HasPrefix(prefix, "kv1") { 64 | t.Run(testName(prefix), func(t *testing.T) { 65 | t.Parallel() 66 | 67 | err := sharedVaku.FolderDestroy(context.Background(), PathJoin(prefix, tt.give), tt.giveVersions) 68 | compareErrors(t, err, tt.wantErrKV1) 69 | }) 70 | } 71 | if strings.HasPrefix(prefix, "kv2") { 72 | t.Run(testName(prefix), func(t *testing.T) { 73 | t.Parallel() 74 | 75 | err := sharedVaku.FolderDestroy(context.Background(), PathJoin(prefix, tt.give), tt.giveVersions) 76 | compareErrors(t, err, tt.wantErrKV2) 77 | 78 | readBack, err := sharedVakuClean.FolderRead(context.Background(), PathJoin(prefix, tt.give)) 79 | assert.NoError(t, err) 80 | assert.Equal(t, tt.wantReadBack, readBack) 81 | }) 82 | } 83 | } 84 | }) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /api/folder_list.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "sync" 7 | 8 | "golang.org/x/sync/errgroup" 9 | ) 10 | 11 | var ( 12 | // ErrFolderList when FolderList fails. 13 | ErrFolderList = errors.New("folder list") 14 | // ErrFolderListChan when FolderListChan fails. 15 | ErrFolderListChan = errors.New("folder list chan") 16 | ) 17 | 18 | // FolderList recursively lists the provided path and all subpaths. 19 | func (c *Client) FolderList(ctx context.Context, p string) ([]string, error) { 20 | resC, errC := c.FolderListChan(ctx, p) 21 | 22 | // read results and errors. send on errC signifies done (can be nil). 23 | var output []string 24 | for { 25 | select { 26 | case res, ok := <-resC: 27 | if !ok { 28 | return output, nil 29 | } 30 | output = append(output, res) 31 | case err := <-errC: 32 | if err != nil { 33 | return nil, newWrapErr(p, ErrFolderList, err) 34 | } 35 | } 36 | } 37 | } 38 | 39 | // FolderListChan recursively lists the provided path and all subpaths. Returns an unbuffered 40 | // channel that can be read until close and an error channel that sends either the first error or 41 | // nil when the work is done. 42 | func (c *Client) FolderListChan(ctx context.Context, p string) (<-chan string, <-chan error) { 43 | // input must be a folder (end in "/") 44 | root := EnsureFolder(p) 45 | 46 | // eg manages workers reading from the paths channel 47 | eg, ctx := errgroup.WithContext(ctx) 48 | // wg tracks when to close the paths channel 49 | var wg sync.WaitGroup 50 | 51 | // pathC is paths to be processed 52 | pathC := make(chan string) 53 | // resC is processed paths 54 | resC := make(chan string) 55 | // errC for the first error seen 56 | errC := make(chan error) 57 | 58 | // add root path to paths 59 | wg.Add(1) 60 | go func(p string) { pathC <- p }(root) 61 | 62 | // fan out and process paths 63 | for i := 0; i < c.workers; i++ { 64 | eg.Go(func() error { 65 | return c.folderListWork(&folderListWorkInput{ 66 | ctx: ctx, 67 | root: root, 68 | wg: &wg, 69 | pathC: pathC, 70 | resC: resC, 71 | }) 72 | }) 73 | } 74 | 75 | // Wait until finished (success or not) and clean up 76 | go func() { 77 | // Close pathC after all paths added 78 | wg.Wait() 79 | close(pathC) 80 | 81 | // Wait for all paths to process 82 | err := eg.Wait() 83 | 84 | // Report the error (or nil) to errC 85 | errC <- err 86 | 87 | // Clean up 88 | close(resC) 89 | close(errC) 90 | }() 91 | 92 | return resC, errC 93 | } 94 | 95 | // folderListWorkInput is the pieces needed to list a folder. 96 | type folderListWorkInput struct { 97 | ctx context.Context 98 | root string 99 | wg *sync.WaitGroup 100 | pathC chan string 101 | resC chan<- string 102 | } 103 | 104 | // folderListWork takes input from pathC, lists the path, adds listed folders back into pathC, and 105 | // adds non-folders into results. 106 | func (c *Client) folderListWork(i *folderListWorkInput) error { 107 | for { 108 | select { 109 | case <-i.ctx.Done(): 110 | return ctxErr(i.ctx.Err()) 111 | case path, ok := <-i.pathC: 112 | if !ok { 113 | return nil 114 | } 115 | err := c.pathListWork(path, i) 116 | if err != nil { 117 | return err 118 | } 119 | } 120 | } 121 | } 122 | 123 | // pathListWork takes a path and either adds it back to the pathC (if folder) or processes it and 124 | // adds it to the resC. 125 | func (c *Client) pathListWork(path string, i *folderListWorkInput) error { 126 | defer i.wg.Done() 127 | 128 | if IsFolder(path) { 129 | list, err := c.PathList(path) 130 | if err != nil { 131 | return newWrapErr(i.root, ErrFolderListChan, err) 132 | } 133 | for _, item := range list { 134 | i.wg.Add(1) 135 | go func(item string) { 136 | select { 137 | case i.pathC <- c.inputPath(item, path): 138 | case <-i.ctx.Done(): 139 | i.wg.Done() 140 | } 141 | }(item) 142 | } 143 | } else { 144 | select { 145 | case i.resC <- c.outputPath(path, i.root): 146 | case <-i.ctx.Done(): 147 | return ctxErr(i.ctx.Err()) 148 | } 149 | } 150 | return nil 151 | } 152 | -------------------------------------------------------------------------------- /api/folder_list_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestFolderList(t *testing.T) { 11 | t.Parallel() 12 | 13 | tests := []struct { 14 | give string 15 | want []string 16 | wantErr []error 17 | }{ 18 | { 19 | give: "0/1", 20 | want: nil, 21 | wantErr: nil, 22 | }, 23 | { 24 | give: "0/4/", 25 | want: []string{ 26 | "5", 27 | "8", 28 | "13/14", 29 | "13/17", 30 | "13/24/25/26/27", 31 | }, 32 | wantErr: nil, 33 | }, 34 | { 35 | give: "error/list/inject", 36 | want: []string{}, 37 | wantErr: []error{ErrFolderList, ErrFolderListChan, ErrPathList, ErrVaultList}, 38 | }, 39 | } 40 | 41 | for _, tt := range tests { 42 | t.Run(testName(tt.give), func(t *testing.T) { 43 | t.Parallel() 44 | for _, prefix := range seededPrefixes(t, tt.give) { 45 | t.Run(testName(prefix), func(t *testing.T) { 46 | t.Parallel() 47 | 48 | list, err := sharedVaku.FolderList(context.Background(), PathJoin(prefix, tt.give)) 49 | compareErrors(t, err, tt.wantErr) 50 | 51 | TrimPrefixList(list, prefix) 52 | assert.ElementsMatch(t, tt.want, list) 53 | }) 54 | } 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /api/folder_move.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | ) 7 | 8 | var ( 9 | // ErrFolderMove when FolderMove fails. 10 | ErrFolderMove = errors.New("folder move") 11 | ) 12 | 13 | // FolderMove moves data at a source folder to a destination folder. Source is deleted after copy. 14 | func (c *Client) FolderMove(ctx context.Context, src, dst string) error { 15 | err := c.FolderCopy(ctx, src, dst) 16 | if err != nil { 17 | return newWrapErr("", ErrFolderMove, err) 18 | } 19 | 20 | err = c.FolderDelete(ctx, src) 21 | if err != nil { 22 | return newWrapErr("delete "+src, ErrFolderMove, err) 23 | } 24 | 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /api/folder_move_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestFolderMove(t *testing.T) { 11 | t.Parallel() 12 | 13 | tests := []struct { 14 | giveSrc string 15 | giveDst string 16 | wantErr []error 17 | wantNilSrc bool 18 | wantNilDst bool 19 | }{ 20 | { 21 | giveSrc: "0/1", 22 | giveDst: "move/0/1", 23 | wantErr: nil, 24 | wantNilSrc: true, 25 | }, 26 | { 27 | giveSrc: "0", 28 | giveDst: "move/0", 29 | wantErr: nil, 30 | wantNilSrc: true, 31 | }, 32 | { 33 | giveSrc: "0/4/13/24/25/26/error/read/inject", 34 | giveDst: "move/0/4/13/24/25/26", 35 | wantErr: []error{ErrFolderMove, ErrFolderCopy, ErrFolderRead, ErrFolderReadChan, ErrPathRead, ErrVaultRead}, 36 | wantNilDst: true, 37 | }, 38 | { 39 | giveSrc: "0/4/13/24/25/26/error/delete/inject", 40 | giveDst: "move/0/4/13/24/25/26", 41 | wantErr: []error{ErrFolderMove, ErrFolderDelete, ErrPathDelete, ErrVaultDelete}, 42 | }, 43 | } 44 | 45 | for _, tt := range tests { 46 | t.Run(testName(tt.giveSrc, tt.giveDst), func(t *testing.T) { 47 | t.Parallel() 48 | for _, prefixPair := range seededPrefixProduct(t) { 49 | t.Run(testName(prefixPair[0], prefixPair[1]), func(t *testing.T) { 50 | t.Parallel() 51 | 52 | pathSrc := PathJoin(prefixPair[0], tt.giveSrc) 53 | pathDst := PathJoin(prefixPair[1], tt.giveDst) 54 | 55 | origSrc, err := sharedVakuClean.FolderRead(context.Background(), pathSrc) 56 | assert.NoError(t, err) 57 | TrimPrefixMap(origSrc, pathSrc) 58 | 59 | err = sharedVaku.FolderMove(context.Background(), pathSrc, pathDst) 60 | compareErrors(t, err, tt.wantErr) 61 | 62 | readSrc, errSrc := sharedVakuClean.FolderRead(context.Background(), pathSrc) 63 | readDst, errDst := sharedVakuClean.dc.FolderRead(context.Background(), pathDst) 64 | assert.NoError(t, errSrc) 65 | assert.NoError(t, errDst) 66 | TrimPrefixMap(readSrc, pathSrc) 67 | TrimPrefixMap(readDst, pathDst) 68 | 69 | if tt.wantNilSrc { 70 | assert.Nil(t, readSrc) 71 | } else { 72 | assert.Equal(t, origSrc, readSrc) 73 | } 74 | if tt.wantNilDst { 75 | assert.Nil(t, readDst) 76 | } else { 77 | assert.Equal(t, origSrc, readDst) 78 | } 79 | }) 80 | } 81 | }) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /api/folder_read.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "golang.org/x/sync/errgroup" 8 | ) 9 | 10 | var ( 11 | // ErrFolderRead when FolderRead fails. 12 | ErrFolderRead = errors.New("folder read") 13 | // ErrFolderReadChan when FolderReadChan fails. 14 | ErrFolderReadChan = errors.New("folder read chan") 15 | ) 16 | 17 | // FolderRead recursively reads the provided path and all subpaths. 18 | func (c *Client) FolderRead(ctx context.Context, p string) (map[string]map[string]any, error) { 19 | resC, errC := c.FolderReadChan(ctx, p) 20 | 21 | // read results and errors. send on errC signifies done (can be nil). 22 | out := make(map[string]map[string]any) 23 | for { 24 | select { 25 | case res := <-resC: 26 | mergeMaps(out, res) 27 | case err := <-errC: 28 | if err != nil { 29 | return nil, newWrapErr(p, ErrFolderRead, err) 30 | } 31 | if len(out) == 0 { 32 | return nil, nil 33 | } 34 | return out, nil 35 | } 36 | } 37 | } 38 | 39 | // FolderReadChan recursively reads the provided path and all subpaths. Returns an unbuffered 40 | // channel that can be read until close and an error channel that sends either the first error or 41 | // nil when the work is done. 42 | func (c *Client) FolderReadChan(ctx context.Context, p string) (<-chan map[string]map[string]any, <-chan error) { //nolint:lll 43 | // eg manages workers reading from the paths channel 44 | eg, ctx := errgroup.WithContext(ctx) 45 | 46 | // resC is processed paths 47 | resC := make(chan map[string]map[string]any) 48 | 49 | // list the path 50 | pathC, errC := c.FolderListChan(ctx, p) 51 | eg.Go(func() error { 52 | err := <-errC 53 | if err != nil { 54 | return newWrapErr(p, ErrFolderReadChan, err) 55 | } 56 | return nil 57 | }) 58 | 59 | // fan out and process paths 60 | for i := 0; i < c.workers; i++ { 61 | eg.Go(func() error { 62 | return c.folderReadWork(&folderReadWorkInput{ 63 | ctx: ctx, 64 | root: p, 65 | pathC: pathC, 66 | resC: resC, 67 | }) 68 | }) 69 | } 70 | 71 | return resC, errFuncOnChan(eg.Wait) 72 | } 73 | 74 | // folderReadWorkInput is the piecces needed to list a folder. 75 | type folderReadWorkInput struct { 76 | ctx context.Context 77 | root string 78 | pathC <-chan string 79 | resC chan<- map[string]map[string]any 80 | } 81 | 82 | // folderReadWork takes input from pathC, lists the path, adds listed folders back into pathC, and 83 | // adds non-folders into results. 84 | func (c *Client) folderReadWork(i *folderReadWorkInput) error { 85 | for { 86 | select { 87 | case <-i.ctx.Done(): 88 | return ctxErr(i.ctx.Err()) 89 | case path, ok := <-i.pathC: 90 | if !ok { 91 | return nil 92 | } 93 | err := c.pathReadWork(path, i) 94 | if err != nil { 95 | return err 96 | } 97 | } 98 | } 99 | } 100 | 101 | // pathReadWork reads the path and adds results to the channel. 102 | func (c *Client) pathReadWork(path string, i *folderReadWorkInput) error { 103 | path = c.inputPath(path, i.root) 104 | 105 | read, err := c.PathRead(path) 106 | if err != nil { 107 | return newWrapErr(i.root, ErrFolderReadChan, err) 108 | } 109 | 110 | // Don't add nil reads to results. These show up in list but are actually deleted secrets. 111 | if read != nil { 112 | res := make(map[string]map[string]any, 1) 113 | res[c.outputPath(path, i.root)] = read 114 | 115 | i.resC <- res 116 | } 117 | 118 | return nil 119 | } 120 | -------------------------------------------------------------------------------- /api/folder_read_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestFolderRead(t *testing.T) { 11 | t.Parallel() 12 | 13 | tests := []struct { 14 | give string 15 | want map[string]map[string]any 16 | wantErr []error 17 | }{ 18 | { 19 | give: "0/1", 20 | want: nil, 21 | wantErr: nil, 22 | }, 23 | { 24 | give: "0/4/13/24/25", 25 | want: map[string]map[string]any{ 26 | "26/27": { 27 | "28": "29", 28 | }, 29 | }, 30 | wantErr: nil, 31 | }, 32 | { 33 | give: "0/4/13", 34 | want: map[string]map[string]any{ 35 | "14": { 36 | "15": "16", 37 | }, 38 | "17": { 39 | "18": "19", 40 | "20": "21", 41 | "22": "23", 42 | }, 43 | "24/25/26/27": { 44 | "28": "29", 45 | }, 46 | }, 47 | wantErr: nil, 48 | }, 49 | { 50 | give: "error/list/inject", 51 | want: nil, 52 | wantErr: []error{ErrFolderRead, ErrFolderReadChan, ErrFolderListChan, ErrPathList, ErrVaultList}, 53 | }, 54 | { 55 | give: "0/4/13/24/25/26/error/read/inject", 56 | want: nil, 57 | wantErr: []error{ErrFolderRead, ErrFolderReadChan, ErrPathRead, ErrVaultRead}, 58 | }, 59 | } 60 | 61 | for _, tt := range tests { 62 | t.Run(testName(tt.give), func(t *testing.T) { 63 | t.Parallel() 64 | for _, prefix := range seededPrefixes(t, tt.give) { 65 | t.Run(testName(prefix), func(t *testing.T) { 66 | t.Parallel() 67 | 68 | read, err := sharedVaku.FolderRead(context.Background(), PathJoin(prefix, tt.give)) 69 | compareErrors(t, err, tt.wantErr) 70 | 71 | TrimPrefixMap(read, prefix) 72 | assert.Equal(t, tt.want, read) 73 | }) 74 | } 75 | }) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /api/folder_search.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | ) 7 | 8 | var ( 9 | // ErrFolderSearch when FolderSearch fails. 10 | ErrFolderSearch = errors.New("folder search") 11 | ) 12 | 13 | // FolderSearch searches the provided path and all subpaths. Returns a list of 14 | // paths in which the string was found. 15 | func (c *Client) FolderSearch(ctx context.Context, path, search string) ([]string, error) { 16 | read, err := c.FolderRead(ctx, path) 17 | if err != nil { 18 | return nil, newWrapErr(path, ErrFolderSearch, err) 19 | } 20 | 21 | var matches []string 22 | for pth, sec := range read { 23 | found, err := searchSecret(sec, search) 24 | if err != nil { 25 | return nil, newWrapErr(path, ErrFolderSearch, err) 26 | } 27 | if found { 28 | matches = append(matches, pth) 29 | } 30 | } 31 | 32 | return matches, nil 33 | } 34 | -------------------------------------------------------------------------------- /api/folder_search_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestFolderSearch(t *testing.T) { 11 | t.Parallel() 12 | 13 | tests := []struct { 14 | name string 15 | give string 16 | giveSearch string 17 | want []string 18 | wantErr []error 19 | }{ 20 | { 21 | give: "0", 22 | giveSearch: "notfound", 23 | want: nil, 24 | wantErr: nil, 25 | }, 26 | { 27 | give: "0/4/13/24", 28 | giveSearch: "7", 29 | want: nil, 30 | wantErr: nil, 31 | }, 32 | { 33 | give: "0/4/13", 34 | giveSearch: "3", 35 | want: []string{"17"}, 36 | wantErr: nil, 37 | }, 38 | { 39 | give: "0/4", 40 | giveSearch: "2", 41 | want: []string{"8", "13/17", "13/24/25/26/27"}, 42 | wantErr: nil, 43 | }, 44 | { 45 | give: "0/4/error/read/inject", 46 | giveSearch: "aaaaaaaaa", 47 | want: nil, 48 | wantErr: []error{ErrFolderSearch, ErrFolderRead, ErrFolderReadChan, ErrPathRead, ErrVaultRead}, 49 | }, 50 | { 51 | give: "0/4/funcdata/read/inject", 52 | giveSearch: "aaaaaaaaa", 53 | want: nil, 54 | wantErr: []error{ErrFolderSearch, ErrJSONMarshal}, 55 | }, 56 | } 57 | 58 | for _, tt := range tests { 59 | t.Run(testName(tt.give), func(t *testing.T) { 60 | t.Parallel() 61 | for _, prefix := range seededPrefixes(t, tt.give) { 62 | t.Run(testName(prefix), func(t *testing.T) { 63 | t.Parallel() 64 | 65 | matches, err := sharedVaku.FolderSearch(context.Background(), PathJoin(prefix, tt.give), tt.giveSearch) 66 | compareErrors(t, err, tt.wantErr) 67 | 68 | TrimPrefixList(matches, prefix) 69 | assert.ElementsMatch(t, tt.want, matches) 70 | }) 71 | } 72 | }) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /api/folder_write.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "golang.org/x/sync/errgroup" 8 | ) 9 | 10 | var ( 11 | // ErrFolderWrite when FolderWrite fails. 12 | ErrFolderWrite = errors.New("folder write") 13 | ) 14 | 15 | // FolderWrite writes data to a path. Multiple paths can be written to at once. 16 | func (c *Client) FolderWrite(ctx context.Context, d map[string]map[string]any) error { 17 | // eg manages workers reading from the paths channel 18 | eg, ctx := errgroup.WithContext(ctx) 19 | 20 | // add paths to be processed by our workers 21 | pathC := make(chan string) 22 | go func() { 23 | for path := range d { 24 | pathC <- path 25 | } 26 | close(pathC) 27 | }() 28 | 29 | // fan out and process paths 30 | for i := 0; i < c.workers; i++ { 31 | eg.Go(func() error { 32 | return c.folderWriteWork(&folderWriteWorkInput{ 33 | ctx: ctx, 34 | pathC: pathC, 35 | data: d, 36 | }) 37 | }) 38 | } 39 | 40 | err := eg.Wait() 41 | if err != nil { 42 | return newWrapErr("", ErrFolderWrite, err) 43 | } 44 | return nil 45 | } 46 | 47 | // folderWriteWorkInput is the piecces needed to list a folder. 48 | type folderWriteWorkInput struct { 49 | ctx context.Context 50 | pathC <-chan string 51 | data map[string]map[string]any 52 | } 53 | 54 | // folderWriteWork takes input from pathC, lists the path, adds listed folders back into pathC, and 55 | // adds non-folders into results. 56 | func (c *Client) folderWriteWork(i *folderWriteWorkInput) error { 57 | for { 58 | select { 59 | case <-i.ctx.Done(): 60 | return ctxErr(i.ctx.Err()) 61 | case path, ok := <-i.pathC: 62 | if !ok { 63 | return nil 64 | } 65 | err := c.PathWrite(path, i.data[path]) 66 | if err != nil { 67 | return err 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /api/folder_write_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestFolderWrite(t *testing.T) { 11 | t.Parallel() 12 | 13 | tests := []struct { 14 | name string 15 | give map[string]map[string]any 16 | wantReadBack map[string]map[string]any 17 | wantErr []error 18 | }{ 19 | { 20 | name: "nil", 21 | give: nil, 22 | wantReadBack: nil, 23 | wantErr: nil, 24 | }, 25 | { 26 | name: "empty data", 27 | give: map[string]map[string]any{ 28 | "000/001": nil, 29 | }, 30 | wantReadBack: nil, 31 | wantErr: []error{ErrFolderWrite, ErrPathWrite, ErrNilData}, 32 | }, 33 | { 34 | name: "overwrite", 35 | give: map[string]map[string]any{ 36 | "0/1": { 37 | "0001": "0002", 38 | }, 39 | }, 40 | wantReadBack: map[string]map[string]any{ 41 | "0/1": { 42 | "0001": "0002", 43 | }, 44 | }, 45 | wantErr: nil, 46 | }, 47 | { 48 | name: "two new paths", 49 | give: map[string]map[string]any{ 50 | "000/001": { 51 | "0001": "0002", 52 | }, 53 | "000/001/002": { 54 | "0003": "0004", 55 | "0005": "0006", 56 | }, 57 | }, 58 | wantReadBack: map[string]map[string]any{ 59 | "000/001": { 60 | "0001": "0002", 61 | }, 62 | "000/001/002": { 63 | "0003": "0004", 64 | "0005": "0006", 65 | }, 66 | }, 67 | wantErr: nil, 68 | }, 69 | { 70 | name: "two different paths", 71 | give: map[string]map[string]any{ 72 | "000/001": { 73 | "0001": "0002", 74 | }, 75 | "111/001/002": { 76 | "0003": "0004", 77 | "0005": "0006", 78 | }, 79 | }, 80 | wantReadBack: map[string]map[string]any{ 81 | "000/001": { 82 | "0001": "0002", 83 | }, 84 | "111/001/002": { 85 | "0003": "0004", 86 | "0005": "0006", 87 | }, 88 | }, 89 | wantErr: nil, 90 | }, 91 | { 92 | name: "path write fail", 93 | give: map[string]map[string]any{ 94 | "failonwrite/error/write/inject": { 95 | "01": "02", 96 | }, 97 | }, 98 | wantReadBack: nil, 99 | wantErr: []error{ErrFolderWrite, ErrPathWrite, ErrVaultWrite}, 100 | }, 101 | } 102 | 103 | for _, tt := range tests { 104 | t.Run(tt.name, func(t *testing.T) { 105 | t.Parallel() 106 | for _, prefix := range seededPrefixes(t, "") { 107 | t.Run(testName(prefix), func(t *testing.T) { 108 | t.Parallel() 109 | 110 | writeMap := make(map[string]map[string]any, len(tt.give)) 111 | for path, data := range tt.give { 112 | writeMap[PathJoin(prefix, path)] = data 113 | } 114 | 115 | err := sharedVaku.FolderWrite(context.Background(), writeMap) 116 | compareErrors(t, err, tt.wantErr) 117 | 118 | for path, data := range tt.wantReadBack { 119 | readBack, err := sharedVakuClean.PathRead(PathJoin(prefix, path)) 120 | assert.NoError(t, err) 121 | assert.Equal(t, data, readBack) 122 | } 123 | }) 124 | } 125 | }) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /api/helpers.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "path" 5 | "strings" 6 | ) 7 | 8 | // PathJoin combines multiple paths into one. 9 | func PathJoin(p ...string) string { 10 | if strings.HasSuffix(p[len(p)-1], "/") { 11 | return strings.TrimPrefix(path.Join(p...)+"/", "/") 12 | } 13 | return strings.TrimPrefix(path.Join(p...), "/") 14 | } 15 | 16 | // IsFolder if path is a folder (ends in "/"). 17 | func IsFolder(p string) bool { 18 | return strings.HasSuffix(p, "/") 19 | } 20 | 21 | // EnsureFolder ensures a path is a folder (adds a trailing "/"). 22 | func EnsureFolder(p string) string { 23 | return PathJoin(p, "/") 24 | } 25 | 26 | // AddPrefix adds a prefix to a path. 27 | func AddPrefix(p, pfx string) string { 28 | return PathJoin(pfx, p) 29 | } 30 | 31 | // EnsurePrefix adds a prefix to a path if it doesn't already have it. 32 | func EnsurePrefix(p, pfx string) string { 33 | if strings.HasPrefix(p, pfx) { 34 | return p 35 | } 36 | return PathJoin(pfx, p) 37 | } 38 | 39 | // AddPrefixList adds a prefix to every item in a list. 40 | func AddPrefixList(l []string, pfx string) { 41 | for i, v := range l { 42 | l[i] = PathJoin(pfx, v) 43 | } 44 | } 45 | 46 | // EnsurePrefixList adds a prefix to every item in a list if it doesn't already have it. 47 | func EnsurePrefixList(l []string, pfx string) { 48 | for i, v := range l { 49 | if !strings.HasPrefix(v, pfx) { 50 | l[i] = PathJoin(pfx, v) 51 | } 52 | } 53 | } 54 | 55 | // TrimPrefixList removes a prefix from every item in a list. 56 | func TrimPrefixList(l []string, pfx string) { 57 | for i, v := range l { 58 | l[i] = PathJoin(strings.TrimPrefix(v, pfx)) 59 | } 60 | } 61 | 62 | // EnsurePrefixMap ensures a prefix for every key in a map. 63 | func EnsurePrefixMap(m map[string]map[string]any, pfx string) { 64 | for k, v := range m { 65 | delete(m, k) 66 | m[EnsurePrefix(k, pfx)] = v 67 | } 68 | } 69 | 70 | // TrimPrefixMap removes a prefix from every key in a map. 71 | func TrimPrefixMap(m map[string]map[string]any, pfx string) { 72 | for k, v := range m { 73 | delete(m, k) 74 | m[PathJoin(strings.TrimPrefix(k, pfx))] = v 75 | } 76 | } 77 | 78 | // InsertIntoPath adds 'insert' into 'path' after 'after' and returns the new path. 79 | func InsertIntoPath(path, after, insert string) string { 80 | return PathJoin(after, insert, strings.TrimPrefix(path, after)) 81 | } 82 | 83 | // errFuncOnChan takes a function like errgroup.Wait() and provides a channel that can be read for 84 | // the err value that the function returns. Makes it easy to wait inside of a select statement. 85 | func errFuncOnChan(errFunc func() error) <-chan error { 86 | errC := make(chan error) 87 | go func() { 88 | errC <- errFunc() 89 | close(errC) 90 | }() 91 | return errC 92 | } 93 | 94 | // mergeMaps merges m2 into m1, preferring data from m2. 95 | func mergeMaps(m1, m2 map[string]map[string]any) { 96 | for k, v := range m2 { 97 | m1[k] = v 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /api/mount_provider.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | // Mount is a high level representation of selected fields of a 4 | // vault mount that are relevant to vaku. 5 | type Mount struct { 6 | Path string 7 | Type string 8 | Version string 9 | } 10 | 11 | // mountProvider is used to get a list of all mounts that the user has access to. 12 | type mountProvider interface { 13 | ListMounts() ([]Mount, error) 14 | } 15 | 16 | // defaultMountProvider is used if no other mountProvider is supplied. 17 | type defaultMountProvider struct { 18 | client *Client 19 | } 20 | 21 | // ListMounts lists mounts using the sys/mounts endpoint. 22 | func (p defaultMountProvider) ListMounts() ([]Mount, error) { 23 | mounts, err := p.client.vc.Sys().ListMounts() 24 | if err != nil { 25 | return nil, newWrapErr("", ErrMountInfo, newWrapErr(err.Error(), ErrListMounts, nil)) 26 | } 27 | 28 | result := make([]Mount, 0) 29 | for mountPath, data := range mounts { 30 | mount := Mount{ 31 | Path: mountPath, 32 | Type: data.Type, 33 | Version: data.Options["version"], 34 | } 35 | result = append(result, mount) 36 | } 37 | return result, nil 38 | } 39 | -------------------------------------------------------------------------------- /api/mounts.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | var ( 10 | // ErrMountInfo when failing to get mount information about a path. 11 | ErrMountInfo = errors.New("mount info") 12 | // ErrListMounts when failing to list vault mounts. 13 | ErrListMounts = errors.New("list mounts") 14 | // ErrNoMount when path cannot be matched to a mount. 15 | ErrNoMount = errors.New("no matching mount") 16 | // ErrRewritePath when failing to rewrite the path with mount data. 17 | ErrRewritePath = errors.New("rewriting path") 18 | // ErrMountVersion when an operation is not supported on the mount version. 19 | ErrMountVersion = errors.New("mount version does not support operation") 20 | ) 21 | 22 | // mountVersion represents possible vault kv mount versions. 23 | type mountVersion int 24 | 25 | const ( 26 | mv0 mountVersion = iota 27 | mv1 28 | mv2 29 | ) 30 | 31 | // vaultOperation represents a call made to vault (read, write, delete, etc...). 32 | type vaultOperation int 33 | 34 | // operations that are used in vaku. 35 | const ( 36 | vaultList vaultOperation = iota + 1 37 | vaultRead 38 | vaultWrite 39 | vaultDelete 40 | vaultDestroy 41 | vaultDeleteMeta 42 | ) 43 | 44 | // mountInfo takes a path and returns the mount path and version. 45 | func (c *Client) mountInfo(p string) (string, mountVersion, error) { 46 | mounts, err := c.mountProvider.ListMounts() 47 | if err != nil { 48 | return "", mv0, newWrapErr(p, ErrMountInfo, newWrapErr(err.Error(), ErrListMounts, nil)) 49 | } 50 | 51 | for _, mount := range mounts { 52 | // Ensure '/' so that no match on foo/bar/ when actual path is foo/barbar/ 53 | mount.Path = EnsureFolder(mount.Path) 54 | if strings.HasPrefix(p, mount.Path) { 55 | if mount.Version == "" { 56 | return mount.Path, mv0, nil 57 | } 58 | 59 | return mount.Path, mountStringToVersion(mount.Version), nil 60 | } 61 | } 62 | 63 | return "", mv0, newWrapErr(p, ErrMountInfo, newWrapErr(p, ErrNoMount, nil)) 64 | } 65 | 66 | // mountStringToVersion converts a mount version string from vault to a MountVersion. 67 | func mountStringToVersion(v string) mountVersion { 68 | version, err := strconv.Atoi(v) 69 | if err != nil { 70 | return mv0 71 | } 72 | 73 | return mountVersion(version) 74 | } 75 | 76 | // rewritePath rewrites a vault input path based on the mount version and operation. 77 | func (c *Client) rewritePath(p string, op vaultOperation) (string, mountVersion, error) { 78 | mount, version, err := c.mountInfo(p) 79 | if err != nil { 80 | return "", mv0, newWrapErr(p, ErrRewritePath, err) 81 | } 82 | 83 | // check if the operation is supported 84 | if !mountSupportsOperation(op, version) { 85 | return "", version, newWrapErr(p, ErrMountVersion, nil) 86 | } 87 | 88 | // only rewrite mv2 mounts 89 | if version != mv2 { 90 | return p, version, nil 91 | } 92 | 93 | switch op { 94 | case vaultList, vaultDeleteMeta: 95 | p = InsertIntoPath(p, mount, "metadata") 96 | case vaultRead, vaultWrite, vaultDelete: 97 | p = InsertIntoPath(p, mount, "data") 98 | case vaultDestroy: 99 | p = InsertIntoPath(p, mount, "destroy") 100 | } 101 | 102 | return p, version, nil 103 | } 104 | 105 | func mountSupportsOperation(op vaultOperation, v mountVersion) bool { 106 | // v2 mounts support all operations 107 | if v == mv2 { 108 | return true 109 | } 110 | 111 | // v1 mounts don't support these 112 | switch op { 113 | case vaultDestroy, vaultDeleteMeta: 114 | return false 115 | } 116 | 117 | // default to supported 118 | return true 119 | } 120 | -------------------------------------------------------------------------------- /api/path_copy.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | // ErrPathCopy when PathCopy fails. 9 | ErrPathCopy = errors.New("path copy") 10 | ) 11 | 12 | // PathCopy copies data at a source path to a destination path. 13 | func (c *Client) PathCopy(src, dst string) error { 14 | secret, err := c.PathRead(src) 15 | if err != nil { 16 | return newWrapErr(src, ErrPathCopy, err) 17 | } 18 | 19 | err = c.dc.PathWrite(dst, secret) 20 | if err != nil { 21 | return newWrapErr(dst, ErrPathCopy, err) 22 | } 23 | 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /api/path_copy_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPathCopy(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | giveSrc string 14 | giveDst string 15 | wantErr []error 16 | wantNilDst bool 17 | }{ 18 | { 19 | giveSrc: "0/1", 20 | giveDst: "copy/0/1", 21 | wantErr: nil, 22 | }, 23 | { 24 | giveSrc: "0/1", 25 | giveDst: "0/4/5", 26 | wantErr: nil, 27 | }, 28 | { 29 | giveSrc: "0/4/8/error/read/inject", 30 | giveDst: "copy/readerror", 31 | wantErr: []error{ErrPathCopy, ErrPathRead, ErrVaultRead}, 32 | wantNilDst: true, 33 | }, 34 | { 35 | giveSrc: "0/4/8", 36 | giveDst: "copy/writeerror/error/write/inject", 37 | wantErr: []error{ErrPathCopy, ErrPathWrite, ErrVaultWrite}, 38 | wantNilDst: true, 39 | }, 40 | } 41 | 42 | for _, tt := range tests { 43 | t.Run(testName(tt.giveSrc, tt.giveDst), func(t *testing.T) { 44 | t.Parallel() 45 | for _, prefixPair := range seededPrefixProduct(t) { 46 | t.Run(testName(prefixPair[0], prefixPair[1]), func(t *testing.T) { 47 | t.Parallel() 48 | 49 | err := sharedVaku.PathCopy(PathJoin(prefixPair[0], tt.giveSrc), PathJoin(prefixPair[1], tt.giveDst)) 50 | compareErrors(t, err, tt.wantErr) 51 | 52 | readSrc, errSrc := sharedVakuClean.PathRead(PathJoin(prefixPair[0], tt.giveSrc)) 53 | readDst, errDst := sharedVakuClean.dc.PathRead(PathJoin(prefixPair[1], tt.giveDst)) 54 | assert.NoError(t, errSrc) 55 | assert.NoError(t, errDst) 56 | 57 | if tt.wantNilDst { 58 | assert.Nil(t, readDst) 59 | } else { 60 | assert.Equal(t, readSrc, readDst) 61 | } 62 | }) 63 | } 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /api/path_delete.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | // ErrPathDelete when PathDelete fails. 9 | ErrPathDelete = errors.New("path delete") 10 | // ErrVaultDelete when the underlying Vault API delete fails. 11 | ErrVaultDelete = errors.New("vault delete") 12 | ) 13 | 14 | // PathDelete deletes data at a path. 15 | func (c *Client) PathDelete(p string) error { 16 | err := c.pathDeleteWithOp(p, vaultDelete) 17 | if err != nil { 18 | return newWrapErr(p, ErrPathDelete, err) 19 | } 20 | 21 | return err 22 | } 23 | 24 | func (c *Client) pathDeleteWithOp(p string, op vaultOperation) error { 25 | vaultPath, _, err := c.rewritePath(p, op) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | _, err = c.vl.Delete(vaultPath) 31 | if err != nil { 32 | return newWrapErr(err.Error(), ErrVaultDelete, nil) 33 | } 34 | 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /api/path_delete_meta.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | // ErrPathDeleteMeta when PathDeleteMeta fails. 9 | ErrPathDeleteMeta = errors.New("path delete meta") 10 | ) 11 | 12 | // PathDeleteMeta deletes all secret metadata and versions. Only works on v2 kv engines. 13 | func (c *Client) PathDeleteMeta(p string) error { 14 | err := c.pathDeleteWithOp(p, vaultDeleteMeta) 15 | if err != nil { 16 | return newWrapErr(p, ErrPathDeleteMeta, err) 17 | } 18 | 19 | return err 20 | } 21 | -------------------------------------------------------------------------------- /api/path_delete_meta_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestPathDeleteMeta(t *testing.T) { 11 | t.Parallel() 12 | 13 | tests := []struct { 14 | give string 15 | wantErr []error 16 | wantNoReadback bool 17 | }{ 18 | { 19 | give: "0/1", 20 | wantErr: nil, 21 | }, 22 | { 23 | give: "fake", 24 | wantErr: nil, 25 | }, 26 | { 27 | give: "error/delete/inject", 28 | wantErr: []error{ErrPathDeleteMeta, ErrVaultDelete}, 29 | wantNoReadback: true, 30 | }, 31 | } 32 | 33 | for _, tt := range tests { 34 | t.Run(testName(tt.give), func(t *testing.T) { 35 | t.Parallel() 36 | for _, prefix := range seededPrefixes(t, tt.give) { 37 | if strings.HasPrefix(prefix, "kv1") { 38 | t.Run(testName(prefix), func(t *testing.T) { 39 | t.Parallel() 40 | 41 | err := sharedVaku.PathDeleteMeta(PathJoin(prefix, tt.give)) 42 | compareErrors(t, err, []error{ErrPathDeleteMeta, ErrMountVersion}) 43 | }) 44 | } 45 | if strings.HasPrefix(prefix, "kv2") { 46 | t.Run(testName(prefix), func(t *testing.T) { 47 | t.Parallel() 48 | 49 | err := sharedVaku.PathDeleteMeta(PathJoin(prefix, tt.give)) 50 | compareErrors(t, err, tt.wantErr) 51 | 52 | if !tt.wantNoReadback { 53 | readBack, err := sharedVakuClean.PathRead(PathJoin(prefix, tt.give)) 54 | assert.NoError(t, err) 55 | assert.Nil(t, readBack) 56 | } 57 | }) 58 | } 59 | } 60 | }) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /api/path_delete_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPathDelete(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | give string 14 | wantErr []error 15 | wantNoReadback bool 16 | }{ 17 | { 18 | give: "0/1", 19 | wantErr: nil, 20 | }, 21 | { 22 | give: "fake", 23 | wantErr: nil, 24 | }, 25 | { 26 | give: mountless, 27 | wantErr: []error{ErrPathDelete, ErrRewritePath, ErrMountInfo, ErrNoMount}, 28 | wantNoReadback: true, 29 | }, 30 | { 31 | give: "error/delete/inject", 32 | wantErr: []error{ErrPathDelete, ErrVaultDelete}, 33 | wantNoReadback: true, 34 | }, 35 | } 36 | 37 | for _, tt := range tests { 38 | t.Run(testName(tt.give), func(t *testing.T) { 39 | t.Parallel() 40 | for _, prefix := range seededPrefixes(t, tt.give) { 41 | t.Run(testName(prefix), func(t *testing.T) { 42 | t.Parallel() 43 | 44 | err := sharedVaku.PathDelete(PathJoin(prefix, tt.give)) 45 | compareErrors(t, err, tt.wantErr) 46 | 47 | if !tt.wantNoReadback { 48 | readBack, err := sharedVakuClean.PathRead(PathJoin(prefix, tt.give)) 49 | assert.NoError(t, err) 50 | assert.Nil(t, readBack) 51 | } 52 | }) 53 | } 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /api/path_destroy.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | // ErrPathDestroy when PathDestroy fails. 9 | ErrPathDestroy = errors.New("path destroy") 10 | ) 11 | 12 | // PathDestroy destroys versions of a secret at a path. Only works on v2 kv engines. 13 | func (c *Client) PathDestroy(p string, versions []int) error { 14 | if len(versions) == 0 { 15 | return newWrapErr("no versions provided", ErrPathDestroy, nil) 16 | } 17 | 18 | vaultPath, _, err := c.rewritePath(p, vaultDestroy) 19 | if err != nil { 20 | return newWrapErr(p, ErrPathDestroy, err) 21 | } 22 | 23 | data := map[string]any{ 24 | "versions": versions, 25 | } 26 | 27 | _, err = c.vl.Write(vaultPath, data) 28 | if err != nil { 29 | return newWrapErr(p, ErrPathDestroy, newWrapErr(err.Error(), ErrVaultWrite, nil)) 30 | } 31 | 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /api/path_destroy_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestPathDestroy(t *testing.T) { 11 | t.Parallel() 12 | 13 | tests := []struct { 14 | give string 15 | giveVersions []int 16 | wantErr []error 17 | wantNilReadback bool 18 | }{ 19 | { 20 | give: "0/1", 21 | giveVersions: nil, 22 | wantErr: []error{ErrPathDestroy}, 23 | }, 24 | { 25 | give: "0/1", 26 | giveVersions: []int{}, 27 | wantErr: []error{ErrPathDestroy}, 28 | }, 29 | { 30 | give: "0/1", 31 | giveVersions: []int{1}, 32 | }, 33 | { 34 | give: "0/1", 35 | giveVersions: []int{2}, 36 | wantNilReadback: true, 37 | }, 38 | { 39 | give: "fake", 40 | wantErr: nil, 41 | giveVersions: []int{1, 2, 3, 4, 5, 6, 7}, 42 | wantNilReadback: true, 43 | }, 44 | { 45 | give: "error/write/inject", 46 | giveVersions: []int{1}, 47 | wantErr: []error{ErrPathDestroy, ErrVaultWrite}, 48 | }, 49 | } 50 | 51 | for _, tt := range tests { 52 | t.Run(testName(tt.give), func(t *testing.T) { 53 | t.Parallel() 54 | for _, prefix := range seededPrefixes(t, tt.give) { 55 | if strings.HasPrefix(prefix, "kv1") { 56 | t.Run(testName(prefix), func(t *testing.T) { 57 | t.Parallel() 58 | 59 | err := sharedVaku.PathDestroy(PathJoin(prefix, tt.give), []int{1}) 60 | compareErrors(t, err, []error{ErrPathDestroy, ErrMountVersion}) 61 | }) 62 | } 63 | if strings.HasPrefix(prefix, "kv2") { 64 | t.Run(testName(prefix), func(t *testing.T) { 65 | t.Parallel() 66 | 67 | // overwrite all paths to create a new version 68 | overwrite := map[string]any{"foo": "bar"} 69 | err := sharedVakuClean.PathWrite(PathJoin(prefix, tt.give), overwrite) 70 | assert.NoError(t, err) 71 | 72 | err = sharedVaku.PathDestroy(PathJoin(prefix, tt.give), tt.giveVersions) 73 | compareErrors(t, err, tt.wantErr) 74 | 75 | readBack, err := sharedVakuClean.PathRead(PathJoin(prefix, tt.give)) 76 | assert.NoError(t, err) 77 | if tt.wantNilReadback { 78 | assert.Nil(t, readBack) 79 | } else { 80 | assert.Equal(t, overwrite, readBack) 81 | } 82 | }) 83 | } 84 | } 85 | }) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /api/path_list.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "errors" 5 | 6 | vault "github.com/hashicorp/vault/api" 7 | ) 8 | 9 | var ( 10 | // ErrPathList when PathList fails. 11 | ErrPathList = errors.New("path list") 12 | // ErrVaultList when the underlying Vault API list fails. 13 | ErrVaultList = errors.New("vault list") 14 | ) 15 | 16 | // PathList lists paths at a path. 17 | func (c *Client) PathList(p string) ([]string, error) { 18 | vaultPath, _, err := c.rewritePath(p, vaultList) 19 | if err != nil { 20 | return nil, newWrapErr(p, ErrPathList, err) 21 | } 22 | 23 | secret, err := c.vl.List(vaultPath) 24 | if err != nil { 25 | if c.ignoreAccessErrors { 26 | return nil, nil 27 | } 28 | return nil, newWrapErr(p, ErrPathList, newWrapErr(err.Error(), ErrVaultList, nil)) 29 | } 30 | 31 | list, err := decodeSecret(secret) 32 | if err != nil { 33 | return nil, newWrapErr(p, ErrPathList, err) 34 | } 35 | 36 | c.outputPaths(list, p) 37 | 38 | return list, nil 39 | } 40 | 41 | func decodeSecret(secret *vault.Secret) ([]string, error) { 42 | if secret == nil || secret.Data == nil { 43 | return nil, nil 44 | } 45 | 46 | data, ok := secret.Data["keys"] 47 | if !ok || data == nil { 48 | return nil, newWrapErr("", ErrDecodeSecret, nil) 49 | } 50 | keys, ok := data.([]any) 51 | if !ok { 52 | return nil, newWrapErr("", ErrDecodeSecret, nil) 53 | } 54 | 55 | return decodeKeys(keys) 56 | } 57 | 58 | func decodeKeys(keys []any) ([]string, error) { 59 | output := make([]string, len(keys)) 60 | for i, k := range keys { 61 | key, ok := k.(string) 62 | if !ok { 63 | return nil, newWrapErr("", ErrDecodeSecret, nil) 64 | } 65 | output[i] = key 66 | } 67 | 68 | return output, nil 69 | } 70 | -------------------------------------------------------------------------------- /api/path_list_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPathList(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | give string 14 | want []string 15 | wantErr []error 16 | }{ 17 | { 18 | give: "0", 19 | want: []string{"1", "4/"}, 20 | wantErr: nil, 21 | }, 22 | { 23 | give: "0/4/13/24", 24 | want: []string{"25/"}, 25 | wantErr: nil, 26 | }, 27 | { 28 | give: "emptypath", 29 | want: nil, 30 | wantErr: nil, 31 | }, 32 | { 33 | give: mountless, 34 | want: nil, 35 | wantErr: []error{ErrPathList, ErrRewritePath, ErrMountInfo, ErrNoMount}, 36 | }, 37 | { 38 | give: "error/list/inject", 39 | want: nil, 40 | wantErr: []error{ErrPathList, ErrVaultList}, 41 | }, 42 | { 43 | give: "nildata/list/inject", 44 | want: nil, 45 | wantErr: nil, 46 | }, 47 | { 48 | give: "nilkeys/list/inject", 49 | want: nil, 50 | wantErr: []error{ErrPathList, ErrDecodeSecret}, 51 | }, 52 | { 53 | give: "intkeys/list/inject", 54 | want: nil, 55 | wantErr: []error{ErrPathList, ErrDecodeSecret}, 56 | }, 57 | { 58 | give: "listintkeys/list/inject", 59 | want: nil, 60 | wantErr: []error{ErrPathList, ErrDecodeSecret}, 61 | }, 62 | } 63 | 64 | for _, tt := range tests { 65 | t.Run(testName(tt.give), func(t *testing.T) { 66 | t.Parallel() 67 | for _, prefix := range seededPrefixes(t, tt.give) { 68 | t.Run(testName(prefix), func(t *testing.T) { 69 | t.Parallel() 70 | 71 | list, err := sharedVaku.PathList(PathJoin(prefix, tt.give)) 72 | TrimPrefixList(list, prefix) 73 | 74 | compareErrors(t, err, tt.wantErr) 75 | assert.Equal(t, tt.want, list) 76 | }) 77 | } 78 | }) 79 | } 80 | } 81 | 82 | func TestPathListIgnoreErrors(t *testing.T) { 83 | t.Parallel() 84 | 85 | tests := []struct { 86 | give string 87 | want []string 88 | wantErr []error 89 | }{ 90 | { 91 | give: "error/list/inject", 92 | want: nil, 93 | wantErr: nil, 94 | }, 95 | } 96 | 97 | for _, tt := range tests { 98 | t.Run(testName(tt.give), func(t *testing.T) { 99 | t.Parallel() 100 | for _, prefix := range seededPrefixes(t, tt.give) { 101 | t.Run(testName(prefix), func(t *testing.T) { 102 | t.Parallel() 103 | 104 | client, err := NewClient( 105 | WithVaultSrcClient(testServer(t)), 106 | WithIgnoreAccessErrors(true), 107 | ) 108 | assert.NoError(t, err) 109 | client.vl = &logicalInjector{realL: client.vl, t: t} 110 | 111 | read, err := client.PathList(PathJoin(prefix, tt.give)) 112 | 113 | compareErrors(t, err, tt.wantErr) 114 | assert.Equal(t, tt.want, read) 115 | }) 116 | } 117 | }) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /api/path_move.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | // ErrPathMove when PathMove fails. 9 | ErrPathMove = errors.New("path move") 10 | ) 11 | 12 | // PathMove moves data at a source path to a destination path (copy + delete). 13 | func (c *Client) PathMove(src, dst string) error { 14 | err := c.PathCopy(src, dst) 15 | if err != nil { 16 | return newWrapErr("", ErrPathMove, err) 17 | } 18 | 19 | err = c.PathDelete(src) 20 | if err != nil { 21 | return newWrapErr(dst, ErrPathMove, err) 22 | } 23 | 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /api/path_move_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPathMove(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | giveSrc string 14 | giveDst string 15 | wantErr []error 16 | wantNilSrc bool 17 | wantNilDst bool 18 | }{ 19 | { 20 | giveSrc: "0/1", 21 | giveDst: "move/0/1", 22 | wantErr: nil, 23 | wantNilSrc: true, 24 | }, 25 | { 26 | giveSrc: "0/4/8", 27 | giveDst: "0/4/5", 28 | wantErr: nil, 29 | wantNilSrc: true, 30 | }, 31 | { 32 | giveSrc: "error/read/inject", 33 | giveDst: "move/readerror", 34 | wantErr: []error{ErrPathMove, ErrPathCopy, ErrPathRead, ErrVaultRead}, 35 | wantNilSrc: true, 36 | wantNilDst: true, 37 | }, 38 | { 39 | giveSrc: "0/4/13/14/error/delete/inject", 40 | giveDst: "move/deleteerror", 41 | wantErr: []error{ErrPathMove, ErrPathDelete, ErrVaultDelete}, 42 | }, 43 | } 44 | 45 | for _, tt := range tests { 46 | t.Run(testName(tt.giveSrc, tt.giveDst), func(t *testing.T) { 47 | t.Parallel() 48 | for _, prefixPair := range seededPrefixProduct(t) { 49 | t.Run(testName(prefixPair[0], prefixPair[1]), func(t *testing.T) { 50 | t.Parallel() 51 | 52 | origSrc, err := sharedVakuClean.PathRead(PathJoin(prefixPair[0], tt.giveSrc)) 53 | assert.NoError(t, err) 54 | 55 | err = sharedVaku.PathMove(PathJoin(prefixPair[0], tt.giveSrc), PathJoin(prefixPair[1], tt.giveDst)) 56 | compareErrors(t, err, tt.wantErr) 57 | 58 | readSrc, errSrc := sharedVakuClean.PathRead(PathJoin(prefixPair[0], tt.giveSrc)) 59 | readDst, errDst := sharedVakuClean.dc.PathRead(PathJoin(prefixPair[1], tt.giveDst)) 60 | assert.NoError(t, errSrc) 61 | assert.NoError(t, errDst) 62 | 63 | if tt.wantNilSrc { 64 | assert.Nil(t, readSrc) 65 | } else { 66 | assert.Equal(t, origSrc, readSrc) 67 | } 68 | if tt.wantNilDst { 69 | assert.Nil(t, readDst) 70 | } else { 71 | assert.Equal(t, origSrc, readDst) 72 | } 73 | }) 74 | } 75 | }) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /api/path_read.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | // ErrPathRead when PathRead fails. 9 | ErrPathRead = errors.New("path read") 10 | // ErrVaultRead when the underlying Vault API read fails. 11 | ErrVaultRead = errors.New("vault read") 12 | ) 13 | 14 | // PathRead reads data at a path. 15 | func (c *Client) PathRead(p string) (map[string]any, error) { 16 | vaultPath, mv, err := c.rewritePath(p, vaultRead) 17 | if err != nil { 18 | return nil, newWrapErr(p, ErrPathRead, err) 19 | } 20 | 21 | secret, err := c.vl.Read(vaultPath) 22 | if err != nil { 23 | if c.ignoreAccessErrors { 24 | return nil, nil 25 | } 26 | return nil, newWrapErr(p, ErrPathRead, newWrapErr(err.Error(), ErrVaultRead, nil)) 27 | } 28 | 29 | if secret == nil || secret.Data == nil { 30 | return nil, nil 31 | } 32 | 33 | data := secret.Data 34 | if mv == mv2 { 35 | data = extractV2Read(data) 36 | } 37 | 38 | return data, nil 39 | } 40 | 41 | // extractV2Read returns data["data"] if the secret is not deleted or destroyed. 42 | func extractV2Read(data map[string]any) map[string]any { 43 | if data == nil { 44 | return nil 45 | } 46 | 47 | if isDeleted(data) { 48 | return nil 49 | } 50 | 51 | dd := data["data"] 52 | if dd == nil { 53 | return nil 54 | } 55 | 56 | dm, ok := dd.(map[string]any) 57 | if !ok { 58 | return nil 59 | } 60 | 61 | return dm 62 | } 63 | 64 | // isDeleted checks if the secret has been deleted or destroyed. 65 | func isDeleted(data map[string]any) bool { 66 | metadata, ok := data["metadata"].(map[string]any) 67 | if !ok { 68 | return true 69 | } 70 | deletionTime, ok := metadata["deletion_time"].(string) 71 | if !ok || deletionTime != "" { 72 | return true 73 | } 74 | destroyed, ok := metadata["destroyed"].(bool) 75 | if !ok || destroyed { 76 | return true 77 | } 78 | 79 | return false 80 | } 81 | -------------------------------------------------------------------------------- /api/path_read_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPathRead(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | give string 14 | want map[string]any 15 | wantErr []error 16 | }{ 17 | { 18 | give: "0/1", 19 | want: map[string]any{ 20 | "2": "3", 21 | }, 22 | wantErr: nil, 23 | }, 24 | { 25 | give: "0/4/13/17", 26 | want: map[string]any{ 27 | "18": "19", 28 | "20": "21", 29 | "22": "23", 30 | }, 31 | wantErr: nil, 32 | }, 33 | { 34 | give: "fake", 35 | want: nil, 36 | wantErr: nil, 37 | }, 38 | { 39 | give: mountless, 40 | want: nil, 41 | wantErr: []error{ErrPathRead, ErrRewritePath, ErrMountInfo, ErrNoMount}, 42 | }, 43 | { 44 | give: "error/read/inject", 45 | want: nil, 46 | wantErr: []error{ErrPathRead, ErrVaultRead}, 47 | }, 48 | } 49 | 50 | for _, tt := range tests { 51 | t.Run(testName(tt.give), func(t *testing.T) { 52 | t.Parallel() 53 | for _, prefix := range seededPrefixes(t, tt.give) { 54 | t.Run(testName(prefix), func(t *testing.T) { 55 | t.Parallel() 56 | 57 | read, err := sharedVaku.PathRead(PathJoin(prefix, tt.give)) 58 | 59 | compareErrors(t, err, tt.wantErr) 60 | assert.Equal(t, tt.want, read) 61 | }) 62 | } 63 | }) 64 | } 65 | } 66 | 67 | func TestExtractV2Read(t *testing.T) { 68 | t.Parallel() 69 | 70 | tests := []struct { 71 | name string 72 | give map[string]any 73 | want map[string]any 74 | }{ 75 | { 76 | give: nil, 77 | want: nil, 78 | }, 79 | { 80 | give: map[string]any{"foo": "bar"}, 81 | want: nil, 82 | }, 83 | { 84 | give: map[string]any{"metadata": map[string]any{"foo": "bar"}}, 85 | want: nil, 86 | }, 87 | { 88 | give: map[string]any{"metadata": map[string]any{"deletion_time": ""}}, 89 | want: nil, 90 | }, 91 | { 92 | give: map[string]any{ 93 | "metadata": map[string]any{ 94 | "deletion_time": "", 95 | "destroyed": false, 96 | }, 97 | }, 98 | want: nil, 99 | }, 100 | { 101 | give: map[string]any{ 102 | "metadata": map[string]any{ 103 | "deletion_time": "", 104 | "destroyed": false, 105 | }, 106 | "data": map[string]any{ 107 | "foo": "bar", 108 | }, 109 | }, 110 | want: map[string]any{ 111 | "foo": "bar", 112 | }, 113 | }, 114 | } 115 | 116 | for _, tt := range tests { 117 | t.Run(tt.name, func(t *testing.T) { 118 | t.Parallel() 119 | 120 | result := extractV2Read(tt.give) 121 | assert.Equal(t, tt.want, result) 122 | }) 123 | } 124 | } 125 | 126 | func TestPathReadIgnoreErrors(t *testing.T) { 127 | t.Parallel() 128 | 129 | tests := []struct { 130 | give string 131 | want map[string]any 132 | wantErr []error 133 | }{ 134 | { 135 | give: "error/read/inject", 136 | want: nil, 137 | wantErr: nil, 138 | }, 139 | } 140 | 141 | for _, tt := range tests { 142 | t.Run(testName(tt.give), func(t *testing.T) { 143 | t.Parallel() 144 | for _, prefix := range seededPrefixes(t, tt.give) { 145 | t.Run(testName(prefix), func(t *testing.T) { 146 | t.Parallel() 147 | 148 | client, err := NewClient( 149 | WithVaultSrcClient(testServer(t)), 150 | WithIgnoreAccessErrors(true), 151 | ) 152 | assert.NoError(t, err) 153 | client.vl = &logicalInjector{realL: client.vl, t: t} 154 | 155 | read, err := client.PathRead(PathJoin(prefix, tt.give)) 156 | 157 | compareErrors(t, err, tt.wantErr) 158 | assert.Equal(t, tt.want, read) 159 | }) 160 | } 161 | }) 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /api/path_search.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "strings" 7 | ) 8 | 9 | var ( 10 | // ErrPathSearch when PathSearch fails. 11 | ErrPathSearch = errors.New("path search") 12 | ) 13 | 14 | // PathSearch searches for a string at a path. 15 | func (c *Client) PathSearch(p, s string) (bool, error) { 16 | read, err := c.PathRead(p) 17 | if err != nil { 18 | return false, newWrapErr(p, ErrPathSearch, err) 19 | } 20 | 21 | match, err := searchSecret(read, s) 22 | if err != nil { 23 | return false, newWrapErr(p, ErrPathSearch, err) 24 | } 25 | 26 | return match, nil 27 | } 28 | 29 | // searchSecret searches a secret for a string. 30 | func searchSecret(secret map[string]any, search string) (bool, error) { 31 | for k, v := range secret { 32 | if strings.Contains(k, search) { 33 | return true, nil 34 | } 35 | vjson, err := json.Marshal(v) 36 | if err != nil { 37 | return false, newWrapErr("", ErrJSONMarshal, nil) 38 | } 39 | vstr := string(vjson) 40 | if strings.Contains(vstr, search) { 41 | return true, nil 42 | } 43 | } 44 | 45 | return false, nil 46 | } 47 | -------------------------------------------------------------------------------- /api/path_search_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPathSearch(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | give string 14 | giveSearch string 15 | wantSuccess bool 16 | wantErr []error 17 | }{ 18 | { 19 | give: "0/1", 20 | giveSearch: "2", 21 | wantSuccess: true, 22 | wantErr: nil, 23 | }, 24 | { 25 | give: "0/4/5", 26 | giveSearch: "7", 27 | wantSuccess: true, 28 | wantErr: nil, 29 | }, 30 | { 31 | give: "0/4/8", 32 | giveSearch: "13", 33 | wantSuccess: false, 34 | wantErr: nil, 35 | }, 36 | { 37 | give: "0/4/13/17", 38 | giveSearch: "9", 39 | wantSuccess: true, 40 | wantErr: nil, 41 | }, 42 | { 43 | give: "fake", 44 | giveSearch: "searchstring", 45 | wantSuccess: false, 46 | wantErr: nil, 47 | }, 48 | { 49 | give: "fakeempty", 50 | giveSearch: "", 51 | wantSuccess: false, 52 | wantErr: nil, 53 | }, 54 | { 55 | give: mountless, 56 | giveSearch: "searchstring", 57 | wantSuccess: false, 58 | wantErr: []error{ErrPathSearch, ErrPathRead, ErrRewritePath, ErrMountInfo, ErrNoMount}, 59 | }, 60 | { 61 | give: "error/read/inject", 62 | giveSearch: "searchstring", 63 | wantSuccess: false, 64 | wantErr: []error{ErrPathSearch, ErrPathRead, ErrVaultRead}, 65 | }, 66 | { 67 | give: "funcdata/read/inject", 68 | giveSearch: "searchstring", 69 | wantSuccess: false, 70 | wantErr: []error{ErrPathSearch, ErrJSONMarshal}, 71 | }, 72 | } 73 | 74 | for _, tt := range tests { 75 | t.Run(testName(tt.give), func(t *testing.T) { 76 | t.Parallel() 77 | for _, prefix := range seededPrefixes(t, tt.give) { 78 | t.Run(testName(prefix), func(t *testing.T) { 79 | t.Parallel() 80 | 81 | success, err := sharedVaku.PathSearch(PathJoin(prefix, tt.give), tt.giveSearch) 82 | 83 | compareErrors(t, err, tt.wantErr) 84 | assert.Equal(t, tt.wantSuccess, success) 85 | }) 86 | } 87 | }) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /api/path_update.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | // ErrPathUpdate when PathUpdate fails. 9 | ErrPathUpdate = errors.New("path update") 10 | ) 11 | 12 | // PathUpdate updates a path with data. New data (precedence) is merged with existing data. 13 | func (c *Client) PathUpdate(p string, d map[string]any) error { 14 | if d == nil { 15 | return newWrapErr(p, ErrPathUpdate, ErrNilData) 16 | } 17 | 18 | read, err := c.PathRead(p) 19 | if err != nil { 20 | return newWrapErr(p, ErrPathUpdate, err) 21 | } 22 | if read == nil { 23 | read = make(map[string]any, len(d)) 24 | } 25 | 26 | for k, v := range d { 27 | read[k] = v 28 | } 29 | 30 | err = c.PathWrite(p, read) 31 | if err != nil { 32 | return newWrapErr(p, ErrPathUpdate, err) 33 | } 34 | 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /api/path_update_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPathUpate(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | give string 14 | giveData map[string]any 15 | wantData map[string]any 16 | wantErr []error 17 | wantNoReadback bool 18 | }{ 19 | { 20 | give: "newpath", 21 | giveData: map[string]any{"0": "1"}, 22 | wantData: map[string]any{"0": "1"}, 23 | wantErr: nil, 24 | }, 25 | { 26 | give: "0/1", 27 | giveData: map[string]any{ 28 | "100": "101", 29 | }, 30 | wantData: map[string]any{ 31 | "2": "3", 32 | "100": "101", 33 | }, 34 | wantErr: nil, 35 | }, 36 | { 37 | give: "nildata", 38 | giveData: nil, 39 | wantErr: []error{ErrPathUpdate, ErrNilData}, 40 | }, 41 | { 42 | give: "0/4/5", 43 | giveData: nil, 44 | wantData: map[string]any{ 45 | "6": "7", 46 | }, 47 | wantErr: []error{ErrPathUpdate, ErrNilData}, 48 | }, 49 | { 50 | give: mountless, 51 | giveData: map[string]any{ 52 | "0": "1", 53 | }, 54 | wantErr: []error{ErrPathUpdate, ErrPathRead, ErrRewritePath, ErrMountInfo, ErrNoMount}, 55 | wantNoReadback: true, 56 | }, 57 | { 58 | give: "error/write/inject", 59 | giveData: map[string]any{ 60 | "0": "1", 61 | }, 62 | wantErr: []error{ErrPathUpdate, ErrPathWrite, ErrVaultWrite}, 63 | wantNoReadback: true, 64 | }, 65 | } 66 | 67 | for _, tt := range tests { 68 | t.Run(testName(tt.give), func(t *testing.T) { 69 | t.Parallel() 70 | for _, prefix := range seededPrefixes(t, tt.give) { 71 | t.Run(testName(prefix), func(t *testing.T) { 72 | t.Parallel() 73 | 74 | err := sharedVaku.PathUpdate(PathJoin(prefix, tt.give), tt.giveData) 75 | compareErrors(t, err, tt.wantErr) 76 | 77 | if !tt.wantNoReadback { 78 | readBack, err := sharedVakuClean.PathRead(PathJoin(prefix, tt.give)) 79 | assert.NoError(t, err) 80 | assert.Equal(t, tt.wantData, readBack) 81 | } 82 | }) 83 | } 84 | }) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /api/path_write.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | // ErrPathWrite when PathWrite errors. 9 | ErrPathWrite = errors.New("path write") 10 | // ErrVaultWrite when the underlying Vault API write fails. 11 | ErrVaultWrite = errors.New("vault write") 12 | ) 13 | 14 | // PathWrite writes data to a path. 15 | func (c *Client) PathWrite(p string, d map[string]any) error { 16 | if d == nil { 17 | return newWrapErr(p, ErrPathWrite, ErrNilData) 18 | } 19 | 20 | vaultPath, mv, err := c.rewritePath(p, vaultWrite) 21 | if err != nil { 22 | return newWrapErr(p, ErrPathWrite, err) 23 | } 24 | 25 | if mv == mv2 { 26 | d = map[string]any{ 27 | "data": d, 28 | } 29 | } 30 | 31 | _, err = c.vl.Write(vaultPath, d) 32 | if err != nil { 33 | return newWrapErr(p, ErrPathWrite, newWrapErr(err.Error(), ErrVaultWrite, nil)) 34 | } 35 | 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /api/path_write_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPathWrite(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | name string 14 | give string 15 | giveData map[string]any 16 | wantErr []error 17 | wantNoReadback bool 18 | }{ 19 | { 20 | give: "newpath", 21 | giveData: map[string]any{ 22 | "0": "1", 23 | "2": "3", 24 | "4": "5", 25 | }, 26 | wantErr: nil, 27 | }, 28 | { 29 | give: "0/1", 30 | giveData: map[string]any{ 31 | "100": "200", 32 | }, 33 | wantErr: nil, 34 | }, 35 | { 36 | give: "nildata", 37 | giveData: nil, 38 | wantErr: []error{ErrPathWrite, ErrNilData}, 39 | }, 40 | { 41 | give: "error/write/inject", 42 | giveData: map[string]any{"0": "1"}, 43 | wantErr: []error{ErrPathWrite, ErrVaultWrite}, 44 | wantNoReadback: true, 45 | }, 46 | { 47 | give: mountless, 48 | giveData: map[string]any{"0": "1"}, 49 | wantErr: []error{ErrPathWrite, ErrRewritePath, ErrMountInfo, ErrNoMount}, 50 | wantNoReadback: true, 51 | }, 52 | } 53 | 54 | for _, tt := range tests { 55 | t.Run(testName(tt.give), func(t *testing.T) { 56 | t.Parallel() 57 | for _, prefix := range seededPrefixes(t, tt.give) { 58 | t.Run(testName(prefix), func(t *testing.T) { 59 | t.Parallel() 60 | 61 | err := sharedVaku.PathWrite(PathJoin(prefix, tt.give), tt.giveData) 62 | compareErrors(t, err, tt.wantErr) 63 | 64 | if !tt.wantNoReadback { 65 | readBack, err := sharedVakuClean.PathRead(PathJoin(prefix, tt.give)) 66 | assert.NoError(t, err) 67 | assert.Equal(t, tt.giveData, readBack) 68 | } 69 | }) 70 | } 71 | }) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /api/version.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | // Version gives the current Vaku API version. 4 | func Version() string { 5 | return "2.8.3" 6 | } 7 | -------------------------------------------------------------------------------- /api/version_test.go: -------------------------------------------------------------------------------- 1 | package vaku 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestVersion(t *testing.T) { 10 | t.Parallel() 11 | assert.Equal(t, "2.8.3", Version()) 12 | } 13 | -------------------------------------------------------------------------------- /cmd/cli_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "testing" 7 | 8 | "github.com/spf13/cobra" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestCLI(t *testing.T) { 13 | t.Parallel() 14 | 15 | cli := newCLI() 16 | assert.Nil(t, cli.vc) 17 | assert.NotNil(t, cli.cmd) 18 | assert.Equal(t, "", cli.version) 19 | 20 | cli.setVersion("1.0.0") 21 | assert.Equal(t, "1.0.0", cli.version) 22 | } 23 | 24 | func TestInitVakuClient(t *testing.T) { 25 | t.Parallel() 26 | 27 | tests := []struct { 28 | name string 29 | giveFail string 30 | giveDstAddr string 31 | giveWorkers int 32 | wantErr string 33 | }{ 34 | { 35 | name: "valid", 36 | giveDstAddr: "foo:8200", 37 | giveWorkers: 1, 38 | wantErr: "", 39 | }, 40 | { 41 | name: "bad addr", 42 | giveDstAddr: "\n", 43 | giveWorkers: 1, 44 | wantErr: "initializing vaku client\nsetting vault address\nfailed to set address: parse \"\\n\": net/url: invalid control character in URL", //nolint:lll 45 | }, 46 | { 47 | name: "bad workers", 48 | giveWorkers: 0, 49 | wantErr: "initializing vaku client\napplying options: workers must 1 or greater: 0: invalid workers", 50 | }, 51 | { 52 | name: "fail vault.NewClient", 53 | giveFail: "vault.NewClient", 54 | giveWorkers: 1, 55 | wantErr: "initializing vaku client\ncreating new vault client", 56 | }, 57 | { 58 | name: "fail config.DefaultTokenHelper", 59 | giveFail: "config.DefaultTokenHelper", 60 | giveWorkers: 1, 61 | wantErr: "initializing vaku client\nsetting vault token\ngetting default token helper", 62 | }, 63 | { 64 | name: "fail helper.Get", 65 | giveFail: "helper.Get", 66 | giveWorkers: 1, 67 | wantErr: "initializing vaku client\nsetting vault token\nusing helper to get vault token", 68 | }, 69 | } 70 | 71 | for _, tt := range tests { 72 | t.Run(tt.name, func(t *testing.T) { 73 | t.Parallel() 74 | 75 | cli, _, _ := newTestCLI(t, nil) 76 | cli.flagDstToken = "token" 77 | cli.flagDstNspc = "foo" 78 | 79 | cli.fail = tt.giveFail 80 | cli.flagDstAddr = tt.giveDstAddr 81 | cli.flagWorkers = tt.giveWorkers 82 | 83 | err := cli.initVakuClient(cli.cmd, nil) 84 | 85 | errStr := "" 86 | if err != nil { 87 | errStr = err.Error() 88 | } 89 | 90 | assert.Equal(t, tt.wantErr, errStr) 91 | }) 92 | } 93 | } 94 | 95 | func TestExecute(t *testing.T) { 96 | t.Parallel() 97 | 98 | var outW, errW bytes.Buffer 99 | 100 | code := Execute("dev", os.Args[1:], &outW, &errW) 101 | assert.Equal(t, exitSuccess, code) 102 | 103 | code = Execute("dev", []string{"INVALID"}, &outW, &errW) 104 | assert.Equal(t, exitFailure, code) 105 | } 106 | 107 | // TestHasExample tests that every command has an example. 108 | func TestHasExample(t *testing.T) { 109 | t.Parallel() 110 | 111 | cli, _, _ := newTestCLI(t, nil) 112 | assert.True(t, allHasExample(cli.cmd)) 113 | } 114 | 115 | // allHasExample recursively checks a command and it's children for example functions. 116 | func allHasExample(cmds ...*cobra.Command) bool { 117 | res := true 118 | for _, cmd := range cmds { 119 | res = res && (cmd.HasExample() || cmd.Hidden) && allHasExample(cmd.Commands()...) 120 | } 121 | return res 122 | } 123 | -------------------------------------------------------------------------------- /cmd/doc.go: -------------------------------------------------------------------------------- 1 | // Package cmd provides a CLI for interacting with the Vaku API. 2 | package cmd 3 | -------------------------------------------------------------------------------- /cmd/docs.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/spf13/cobra/doc" 8 | ) 9 | 10 | const ( 11 | docsArgs = 1 12 | docsUse = "docs " 13 | docsShort = "Generate markdown docs at a path" 14 | docsExample = "vaku docs ." 15 | ) 16 | 17 | var ( 18 | errDocGenMarkdown = errors.New("failed to generate markdown docs") 19 | ) 20 | 21 | func (c *cli) newDocsCmd() *cobra.Command { 22 | cmd := &cobra.Command{ 23 | Hidden: true, 24 | 25 | Use: docsUse, 26 | Short: docsShort, 27 | Example: docsExample, 28 | 29 | Args: cobra.ExactArgs(docsArgs), 30 | 31 | DisableFlagsInUseLine: true, 32 | 33 | RunE: c.runDocs, 34 | } 35 | 36 | return cmd 37 | } 38 | 39 | func (c *cli) runDocs(cmd *cobra.Command, args []string) error { 40 | err := doc.GenMarkdownTree(cmd.Root(), args[0]) 41 | if err != nil { 42 | err = errDocGenMarkdown 43 | } 44 | return err 45 | } 46 | -------------------------------------------------------------------------------- /cmd/docs_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestDocs(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | name string 14 | giveArgs []string 15 | wantOut string 16 | wantErr string 17 | }{ 18 | { 19 | name: "success", 20 | giveArgs: []string{"/tmp"}, 21 | }, 22 | { 23 | name: "failure", 24 | giveArgs: []string{"//\\#\\--%@&*/"}, 25 | wantErr: "ERROR: failed to generate markdown docs\n", 26 | }, 27 | { 28 | name: "extra args", 29 | giveArgs: []string{"///", "foo", "bar"}, 30 | wantErr: "ERROR: accepts 1 arg(s), received 3\n", 31 | }, 32 | } 33 | 34 | for _, tt := range tests { 35 | t.Run(tt.name, func(t *testing.T) { 36 | t.Parallel() 37 | 38 | args := append([]string{"docs"}, tt.giveArgs...) 39 | cli, outW, errW := newTestCLI(t, args) 40 | 41 | ec := cli.execute() 42 | assert.Equal(t, ec*len(errW.String()), len(errW.String()), "unexpected exit code") 43 | 44 | assert.Equal(t, tt.wantOut, outW.String()) 45 | assert.Equal(t, tt.wantErr, errW.String()) 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cmd/flags_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestValidateVakuFlags(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | name string 14 | give *cli 15 | want error 16 | }{ 17 | { 18 | name: "valid", 19 | give: &cli{ 20 | flagAbsPath: true, 21 | flagNoAccessErr: true, 22 | flagFormat: "text", 23 | flagIndent: "----", 24 | flagSort: true, 25 | flagWorkers: 100, 26 | }, 27 | want: nil, 28 | }, 29 | { 30 | name: "invalid format", 31 | give: &cli{ 32 | flagAbsPath: true, 33 | flagNoAccessErr: true, 34 | flagFormat: "invalid", 35 | flagIndent: "----", 36 | flagSort: true, 37 | flagWorkers: 100, 38 | }, 39 | want: errFlagInvalidFormat, 40 | }, 41 | { 42 | name: "invalid workers", 43 | give: &cli{ 44 | flagAbsPath: true, 45 | flagNoAccessErr: true, 46 | flagFormat: "text", 47 | flagIndent: "----", 48 | flagSort: true, 49 | flagWorkers: 0, 50 | }, 51 | want: errFlagInvalidWorkers, 52 | }, 53 | } 54 | 55 | for _, tt := range tests { 56 | t.Run(tt.name, func(t *testing.T) { 57 | t.Parallel() 58 | 59 | err := tt.give.validateVakuFlags(nil, nil) 60 | if err != nil { 61 | assert.Equal(t, tt.want, err) 62 | } 63 | }) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /cmd/folder.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | const ( 8 | folderUse = "folder " 9 | folderShort = "Commands that act on Vault folders" 10 | folderExample = "vaku folder list secret/foo" 11 | folderLong = `Commands that act on Vault folders 12 | 13 | Commands under the folder subcommand act on Vault folders. Folders 14 | are designated by paths that end in a '/' such as 'secret/foo/'. Vaku 15 | can list, copy, move, search, etc.. on Vault folders.` 16 | ) 17 | 18 | func (c *cli) newFolderCmd() *cobra.Command { 19 | cmd := &cobra.Command{ 20 | Use: folderUse, 21 | Short: folderShort, 22 | Long: folderLong, 23 | Example: folderExample, 24 | 25 | PersistentPreRunE: c.initVakuClient, 26 | } 27 | 28 | c.addPathFolderFlags(cmd) 29 | 30 | cmd.AddCommand( 31 | c.newFolderListCmd(), 32 | c.newFolderReadCmd(), 33 | c.newFolderWriteCmd(), 34 | c.newFolderDeleteCmd(), 35 | c.newFolderDeleteMetaCmd(), 36 | c.newFolderDestroyCmd(), 37 | c.newFolderSearchCmd(), 38 | c.newFolderCopyCmd(), 39 | c.newFolderMoveCmd(), 40 | ) 41 | 42 | return cmd 43 | } 44 | -------------------------------------------------------------------------------- /cmd/folder_copy.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | const ( 10 | folderCopyArgs = 2 11 | folderCopyUse = "copy " 12 | folderCopyShort = "Recursively copy all secrets in source folder to destination folder" 13 | folderCopyLong = "Recursively copy all secrets in source folder to destination folder" 14 | folderCopyExample = "vaku folder copy secret/foo secret/bar" 15 | ) 16 | 17 | func (c *cli) newFolderCopyCmd() *cobra.Command { 18 | cmd := &cobra.Command{ 19 | Use: folderCopyUse, 20 | Short: folderCopyShort, 21 | Long: folderCopyLong, 22 | Example: folderCopyExample, 23 | 24 | Args: cobra.ExactArgs(folderCopyArgs), 25 | 26 | RunE: c.runfolderCopy, 27 | } 28 | 29 | return cmd 30 | } 31 | 32 | func (c *cli) runfolderCopy(cmd *cobra.Command, args []string) error { 33 | return c.vc.FolderCopy(context.Background(), args[0], args[1]) 34 | } 35 | -------------------------------------------------------------------------------- /cmd/folder_copy_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestFolderCopy(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | name string 14 | giveArgs []string 15 | wantOut string 16 | wantErr string 17 | }{ 18 | { 19 | name: "foo", 20 | giveArgs: []string{"foo", "bar"}, 21 | wantOut: "", 22 | wantErr: "", 23 | }, 24 | } 25 | 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | t.Parallel() 29 | 30 | args := append([]string{"folder", "copy"}, tt.giveArgs...) 31 | cli, outW, errW := newTestCLIWithAPI(t, args) 32 | 33 | ec := cli.execute() 34 | assert.Equal(t, ec*len(errW.String()), len(errW.String()), "unexpected exit code") 35 | 36 | assert.Equal(t, tt.wantOut, outW.String()) 37 | assert.Equal(t, tt.wantErr, errW.String()) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmd/folder_delete.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | const ( 10 | folderDeleteArgs = 1 11 | folderDeleteUse = "delete " 12 | folderDeleteShort = "Recursively delete all secrets in a folder" 13 | folderDeleteLong = "Recursively delete all secrets in a folder" 14 | folderDeleteExample = "vaku folder delete secret/foo" 15 | ) 16 | 17 | func (c *cli) newFolderDeleteCmd() *cobra.Command { 18 | cmd := &cobra.Command{ 19 | Use: folderDeleteUse, 20 | Short: folderDeleteShort, 21 | Long: folderDeleteLong, 22 | Example: folderDeleteExample, 23 | 24 | Args: cobra.ExactArgs(folderDeleteArgs), 25 | 26 | RunE: c.runfolderDelete, 27 | } 28 | 29 | return cmd 30 | } 31 | 32 | func (c *cli) runfolderDelete(cmd *cobra.Command, args []string) error { 33 | return c.vc.FolderDelete(context.Background(), args[0]) 34 | } 35 | -------------------------------------------------------------------------------- /cmd/folder_delete_meta.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | const ( 10 | folderDeleteMetaArgs = 1 11 | folderDeleteMetaUse = "delete-meta " 12 | folderDeleteMetaShort = "Recursively delete all secrets metadata and versions in a folder. V2 engines only." 13 | folderDeleteMetaLong = "Recursively delete all secrets metadata and versions in a folder. V2 engines only." 14 | folderDeleteMetaExample = "vaku folder delete-meta secret/foo" 15 | ) 16 | 17 | func (c *cli) newFolderDeleteMetaCmd() *cobra.Command { 18 | cmd := &cobra.Command{ 19 | Use: folderDeleteMetaUse, 20 | Short: folderDeleteMetaShort, 21 | Long: folderDeleteMetaLong, 22 | Example: folderDeleteMetaExample, 23 | 24 | Args: cobra.ExactArgs(folderDeleteMetaArgs), 25 | 26 | RunE: c.runfolderDeleteMeta, 27 | } 28 | 29 | return cmd 30 | } 31 | 32 | func (c *cli) runfolderDeleteMeta(cmd *cobra.Command, args []string) error { 33 | return c.vc.FolderDeleteMeta(context.Background(), args[0]) 34 | } 35 | -------------------------------------------------------------------------------- /cmd/folder_delete_meta_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestFolderDeleteMeta(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | name string 14 | giveArgs []string 15 | wantOut string 16 | wantErr string 17 | }{ 18 | { 19 | name: "foo", 20 | giveArgs: []string{"foo"}, 21 | wantOut: "", 22 | wantErr: "", 23 | }, 24 | } 25 | 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | t.Parallel() 29 | 30 | args := append([]string{"folder", "delete-meta"}, tt.giveArgs...) 31 | cli, outW, errW := newTestCLIWithAPI(t, args) 32 | 33 | ec := cli.execute() 34 | assert.Equal(t, ec*len(errW.String()), len(errW.String()), "unexpected exit code") 35 | 36 | assert.Equal(t, tt.wantOut, outW.String()) 37 | assert.Equal(t, tt.wantErr, errW.String()) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmd/folder_delete_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestFolderDelete(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | name string 14 | giveArgs []string 15 | wantOut string 16 | wantErr string 17 | }{ 18 | { 19 | name: "foo", 20 | giveArgs: []string{"foo"}, 21 | wantOut: "", 22 | wantErr: "", 23 | }, 24 | } 25 | 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | t.Parallel() 29 | 30 | args := append([]string{"folder", "delete"}, tt.giveArgs...) 31 | cli, outW, errW := newTestCLIWithAPI(t, args) 32 | 33 | ec := cli.execute() 34 | assert.Equal(t, ec*len(errW.String()), len(errW.String()), "unexpected exit code") 35 | 36 | assert.Equal(t, tt.wantOut, outW.String()) 37 | assert.Equal(t, tt.wantErr, errW.String()) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmd/folder_destroy.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | const ( 8 | folderDestroyUse = "destroy" 9 | folderDestroyShort = "Vaku CLI does not yet support folder destroy. Use the vaku API or native Vault CLI" 10 | folderDestroyLong = "Vaku CLI does not yet support folder destroy. Use the vaku API or native Vault CLI" 11 | ) 12 | 13 | func (c *cli) newFolderDestroyCmd() *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: folderDestroyUse, 16 | Short: folderDestroyShort, 17 | Long: folderDestroyLong, 18 | 19 | // disable all discovery 20 | Hidden: true, 21 | DisableSuggestions: true, 22 | DisableFlagsInUseLine: true, 23 | PersistentPreRun: nil, 24 | Args: cobra.ArbitraryArgs, 25 | } 26 | 27 | return cmd 28 | } 29 | -------------------------------------------------------------------------------- /cmd/folder_list.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | const ( 10 | folderListArgs = 1 11 | folderListUse = "list " 12 | folderListShort = "Recursively list all paths in a folder" 13 | folderListLong = "Recursively list all paths in a folder" 14 | folderListExample = "vaku folder list secret/foo" 15 | ) 16 | 17 | func (c *cli) newFolderListCmd() *cobra.Command { 18 | cmd := &cobra.Command{ 19 | Use: folderListUse, 20 | Short: folderListShort, 21 | Long: folderListLong, 22 | Example: folderListExample, 23 | 24 | Args: cobra.ExactArgs(folderListArgs), 25 | 26 | RunE: c.runfolderList, 27 | } 28 | 29 | return cmd 30 | } 31 | 32 | func (c *cli) runfolderList(cmd *cobra.Command, args []string) error { 33 | list, err := c.vc.FolderList(context.Background(), args[0]) 34 | c.output(list) 35 | return err 36 | } 37 | -------------------------------------------------------------------------------- /cmd/folder_list_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestFolderList(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | name string 14 | giveArgs []string 15 | wantOut string 16 | wantErr string 17 | }{ 18 | { 19 | name: "foo", 20 | giveArgs: []string{"foo"}, 21 | wantOut: "bim/bom\nfoo/bar\nfoo/baz\n", 22 | wantErr: "", 23 | }, 24 | } 25 | 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | t.Parallel() 29 | 30 | args := append([]string{"folder", "list"}, tt.giveArgs...) 31 | cli, outW, errW := newTestCLIWithAPI(t, args) 32 | 33 | ec := cli.execute() 34 | assert.Equal(t, ec*len(errW.String()), len(errW.String()), "unexpected exit code") 35 | 36 | assert.Equal(t, tt.wantOut, outW.String()) 37 | assert.Equal(t, tt.wantErr, errW.String()) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmd/folder_move.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | const ( 10 | folderMoveArgs = 2 11 | folderMoveUse = "move " 12 | folderMoveShort = "Recursively move all secrets in source folder to destination folder" 13 | folderMoveLong = "Recursively move all secrets in source folder to destination folder" 14 | folderMoveExample = "vaku folder move secret/foo secret/bar" 15 | ) 16 | 17 | func (c *cli) newFolderMoveCmd() *cobra.Command { 18 | cmd := &cobra.Command{ 19 | Use: folderMoveUse, 20 | Short: folderMoveShort, 21 | Long: folderMoveLong, 22 | Example: folderMoveExample, 23 | 24 | Args: cobra.ExactArgs(folderMoveArgs), 25 | 26 | RunE: c.runfolderMove, 27 | } 28 | 29 | return cmd 30 | } 31 | 32 | func (c *cli) runfolderMove(cmd *cobra.Command, args []string) error { 33 | return c.vc.FolderMove(context.Background(), args[0], args[1]) 34 | } 35 | -------------------------------------------------------------------------------- /cmd/folder_move_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestFolderMove(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | name string 14 | giveArgs []string 15 | wantOut string 16 | wantErr string 17 | }{ 18 | { 19 | name: "foo", 20 | giveArgs: []string{"foo", "bar"}, 21 | wantOut: "", 22 | wantErr: "", 23 | }, 24 | } 25 | 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | t.Parallel() 29 | 30 | args := append([]string{"folder", "move"}, tt.giveArgs...) 31 | cli, outW, errW := newTestCLIWithAPI(t, args) 32 | 33 | ec := cli.execute() 34 | assert.Equal(t, ec*len(errW.String()), len(errW.String()), "unexpected exit code") 35 | 36 | assert.Equal(t, tt.wantOut, outW.String()) 37 | assert.Equal(t, tt.wantErr, errW.String()) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmd/folder_read.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | const ( 10 | folderReadArgs = 1 11 | folderReadUse = "read " 12 | folderReadShort = "Recursively read all secrets in a folder" 13 | folderReadLong = "Recursively read all secrets in a folder" 14 | folderReadExample = "vaku folder read secret/foo" 15 | ) 16 | 17 | func (c *cli) newFolderReadCmd() *cobra.Command { 18 | cmd := &cobra.Command{ 19 | Use: folderReadUse, 20 | Short: folderReadShort, 21 | Long: folderReadLong, 22 | Example: folderReadExample, 23 | 24 | Args: cobra.ExactArgs(folderReadArgs), 25 | 26 | RunE: c.runfolderRead, 27 | } 28 | 29 | return cmd 30 | } 31 | 32 | func (c *cli) runfolderRead(cmd *cobra.Command, args []string) error { 33 | read, err := c.vc.FolderRead(context.Background(), args[0]) 34 | c.output(read) 35 | return err 36 | } 37 | -------------------------------------------------------------------------------- /cmd/folder_read_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestFolderRead(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | name string 14 | giveArgs []string 15 | wantOut string 16 | wantErr string 17 | }{ 18 | { 19 | name: "foo", 20 | giveArgs: []string{"foo"}, 21 | wantOut: "bar\nhoo: boo\nfoo\nbim: bom\nbiz: baz\n", 22 | wantErr: "", 23 | }, 24 | } 25 | 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | t.Parallel() 29 | 30 | args := append([]string{"folder", "read"}, tt.giveArgs...) 31 | cli, outW, errW := newTestCLIWithAPI(t, args) 32 | 33 | ec := cli.execute() 34 | assert.Equal(t, ec*len(errW.String()), len(errW.String()), "unexpected exit code") 35 | 36 | assert.Equal(t, tt.wantOut, outW.String()) 37 | assert.Equal(t, tt.wantErr, errW.String()) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmd/folder_search.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | const ( 10 | folderSearchArgs = 2 11 | folderSearchUse = "search " 12 | folderSearchShort = "Recursively search all secrets in a folder for a search string" 13 | folderSearchLong = "Recursively search all secrets in a folder for a search string" 14 | folderSearchExample = "vaku folder search secret/foo bar" 15 | ) 16 | 17 | func (c *cli) newFolderSearchCmd() *cobra.Command { 18 | cmd := &cobra.Command{ 19 | Use: folderSearchUse, 20 | Short: folderSearchShort, 21 | Long: folderSearchLong, 22 | Example: folderSearchExample, 23 | 24 | Args: cobra.ExactArgs(folderSearchArgs), 25 | 26 | RunE: c.runfolderSearch, 27 | } 28 | 29 | return cmd 30 | } 31 | 32 | func (c *cli) runfolderSearch(cmd *cobra.Command, args []string) error { 33 | search, err := c.vc.FolderSearch(context.Background(), args[0], args[1]) 34 | c.output(search) 35 | return err 36 | } 37 | -------------------------------------------------------------------------------- /cmd/folder_search_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestFolderSearch(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | name string 14 | giveArgs []string 15 | wantOut string 16 | wantErr string 17 | }{ 18 | { 19 | name: "foo", 20 | giveArgs: []string{"foo", "bar"}, 21 | wantOut: "bim/bom\nfoo/bar\n", 22 | wantErr: "", 23 | }, 24 | } 25 | 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | t.Parallel() 29 | 30 | args := append([]string{"folder", "search"}, tt.giveArgs...) 31 | cli, outW, errW := newTestCLIWithAPI(t, args) 32 | 33 | ec := cli.execute() 34 | assert.Equal(t, ec*len(errW.String()), len(errW.String()), "unexpected exit code") 35 | 36 | assert.Equal(t, tt.wantOut, outW.String()) 37 | assert.Equal(t, tt.wantErr, errW.String()) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmd/folder_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestFolder(t *testing.T) { 10 | t.Parallel() 11 | 12 | cli, outW, errW := newTestCLI(t, []string{"folder"}) 13 | assert.Equal(t, "", errW.String()) 14 | 15 | err := cli.cmd.Execute() 16 | 17 | assert.NoError(t, err) 18 | assert.Contains(t, outW.String(), folderShort) 19 | assert.Contains(t, outW.String(), folderLong) 20 | } 21 | -------------------------------------------------------------------------------- /cmd/folder_write.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | const ( 11 | folderWriteArgs = 1 12 | folderWriteUse = "write " 13 | folderWriteShort = "write a folder of secrets. WARNING: command expects a very specific json input" 14 | folderWriteLong = "write a folder of secrets. WARNING: command expects a very specific json input" 15 | folderWriteExample = "vaku folder write '{\"a/b/c\": {\"foo\": \"bar\"}}'" 16 | ) 17 | 18 | func (c *cli) newFolderWriteCmd() *cobra.Command { 19 | cmd := &cobra.Command{ 20 | Use: folderWriteUse, 21 | Short: folderWriteShort, 22 | Long: folderWriteLong, 23 | Example: folderWriteExample, 24 | 25 | Args: cobra.ExactArgs(folderWriteArgs), 26 | 27 | RunE: c.runfolderWrite, 28 | } 29 | 30 | return cmd 31 | } 32 | 33 | func (c *cli) runfolderWrite(cmd *cobra.Command, args []string) error { 34 | var input map[string]map[string]any 35 | err := json.Unmarshal([]byte(args[0]), &input) 36 | if err != nil { 37 | return c.combineErr(errJSONUnmarshal, err) 38 | } 39 | 40 | return c.vc.FolderWrite(context.Background(), input) 41 | } 42 | -------------------------------------------------------------------------------- /cmd/folder_write_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestFolderWrite(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | name string 14 | giveArgs []string 15 | wantRead string 16 | wantOut string 17 | wantErr string 18 | }{ 19 | { 20 | name: "empty", 21 | giveArgs: []string{"{}"}, 22 | wantOut: "", 23 | wantErr: "", 24 | }, 25 | { 26 | name: "abcxyz", 27 | giveArgs: []string{"{\"a/b/c\": {\"foo\": \"bar\"}, \"x/y/z\": {\"bar\": \"foo\"}}"}, 28 | wantOut: "", 29 | wantErr: "", 30 | }, 31 | { 32 | name: "error", 33 | giveArgs: []string{"error"}, 34 | wantOut: "", 35 | wantErr: "ERROR: json unmarshal\ninvalid character 'e' looking for beginning of value\n", 36 | }, 37 | } 38 | 39 | for _, tt := range tests { 40 | t.Run(tt.name, func(t *testing.T) { 41 | t.Parallel() 42 | 43 | args := append([]string{"folder", "write"}, tt.giveArgs...) 44 | cli, outW, errW := newTestCLIWithAPI(t, args) 45 | 46 | ec := cli.execute() 47 | assert.Equal(t, ec*len(errW.String()), len(errW.String()), "unexpected exit code") 48 | 49 | assert.Equal(t, tt.wantOut, outW.String()) 50 | assert.Equal(t, tt.wantErr, errW.String()) 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /cmd/helpers.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "sort" 9 | "strings" 10 | ) 11 | 12 | var ( 13 | errOutputFormat = errors.New("unsupported output format") 14 | errOutputType = errors.New("unsupported output type") 15 | errJSONMarshal = errors.New("json marshal") 16 | errJSONUnmarshal = errors.New("json unmarshal") 17 | ) 18 | 19 | // combineErr combines two errors to be output later. 20 | func (c *cli) combineErr(e1, e2 error) error { 21 | if e1 == nil && e2 == nil { 22 | return nil 23 | } 24 | if e2 == nil { 25 | return e1 26 | } 27 | if e1 == nil { 28 | return e2 29 | } 30 | return fmt.Errorf("%s\n%s%s", e1, c.flagIndent, e2) //nolint:errorlint 31 | } 32 | 33 | // output handles outputting all of our messages (regular output or errors). 34 | func (c *cli) output(out any) { 35 | outW := c.cmd.OutOrStdout() 36 | errW := c.cmd.ErrOrStderr() 37 | 38 | if out == nil { 39 | return 40 | } 41 | 42 | switch c.flagFormat { 43 | case "json": 44 | c.outputJSON(outW, out) 45 | case "text": 46 | c.outputText(outW, out) 47 | default: 48 | c.outputText(errW, errOutputFormat) 49 | } 50 | } 51 | 52 | // outputJSON handles output when flagFormat == json. 53 | func (c *cli) outputJSON(w io.Writer, out any) { 54 | var jsonOut any 55 | 56 | switch out := out.(type) { 57 | case error: 58 | w = c.cmd.ErrOrStderr() 59 | jsonOut = map[string]string{ 60 | "error": out.Error(), 61 | } 62 | default: 63 | jsonOut = out 64 | } 65 | 66 | json, err := json.MarshalIndent(jsonOut, "", c.flagIndent) 67 | if err != nil { 68 | c.outputText(c.cmd.ErrOrStderr(), errJSONMarshal) 69 | return 70 | } 71 | 72 | fmt.Fprintf(w, "%s\n", json) 73 | } 74 | 75 | // outputJSON handles output when flagFormat == text. 76 | func (c *cli) outputText(w io.Writer, out any) { 77 | switch out := out.(type) { 78 | case error: 79 | w = c.cmd.ErrOrStderr() 80 | c.outputTextError(w, out) 81 | case string: 82 | c.outputTextString(w, out) 83 | case []string: 84 | c.outputTextList(w, out) 85 | case map[string]any: 86 | c.outputTextMap(w, 0, out) 87 | case map[string]map[string]any: 88 | c.outputTextNestedMap(w, out) 89 | default: 90 | c.outputTextError(c.cmd.ErrOrStderr(), errOutputType) 91 | } 92 | } 93 | 94 | // outputTextError outputs errors. 95 | func (c *cli) outputTextError(w io.Writer, e error) { 96 | fmt.Fprintf(w, "ERROR: %s\n", e) 97 | } 98 | 99 | // outputTextString outputs strings. 100 | func (c *cli) outputTextString(w io.Writer, s string) { 101 | fmt.Fprintf(w, "%s\n", s) 102 | } 103 | 104 | // outputTextList outputs lists of strings. 105 | func (c *cli) outputTextList(w io.Writer, l []string) { 106 | if c.flagSort { 107 | sort.Strings(l) 108 | } 109 | 110 | for _, s := range l { 111 | c.outputTextString(w, s) 112 | } 113 | } 114 | 115 | // outputTextMap outputs maps of strings to interfaces. 116 | func (c *cli) outputTextMap(w io.Writer, indentTimes int, m map[string]any) { 117 | indent := strings.Repeat(c.flagIndent, indentTimes) 118 | 119 | keys := c.mapKeys(m) 120 | for _, k := range keys { 121 | fmt.Fprintf(w, "%s%s: %+v\n", indent, k, m[k]) 122 | } 123 | } 124 | 125 | // outputTextNestedMap outputs nested maps of maps of strings to interfaces. 126 | func (c *cli) outputTextNestedMap(w io.Writer, m map[string]map[string]any) { 127 | keys := c.nestedMapKeys(m) 128 | for _, k := range keys { 129 | fmt.Fprintf(w, "%+v\n", k) 130 | c.outputTextMap(w, 1, m[k]) 131 | } 132 | } 133 | 134 | // mapKeys gets a list of (optionally sorted) keys from a map. 135 | func (c *cli) mapKeys(m map[string]any) []string { 136 | keys := make([]string, len(m)) 137 | i := 0 138 | for k := range m { 139 | keys[i] = k 140 | i++ 141 | } 142 | 143 | if c.flagSort { 144 | sort.Strings(keys) 145 | } 146 | return keys 147 | } 148 | 149 | // nestedMapKeys gets a list of (optionally sorted) keys from a nested map. 150 | func (c *cli) nestedMapKeys(m map[string]map[string]any) []string { 151 | keys := make([]string, len(m)) 152 | 153 | i := 0 154 | for k := range m { 155 | keys[i] = k 156 | i++ 157 | } 158 | 159 | if c.flagSort { 160 | sort.Strings(keys) 161 | } 162 | return keys 163 | } 164 | -------------------------------------------------------------------------------- /cmd/helpers_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestWrapErr(t *testing.T) { 11 | t.Parallel() 12 | 13 | tests := []struct { 14 | name string 15 | giveE1 error 16 | giveE2 error 17 | wantErr error 18 | }{ 19 | { 20 | name: "nil", 21 | giveE1: nil, 22 | giveE2: nil, 23 | wantErr: nil, 24 | }, 25 | { 26 | name: "e1 nil", 27 | giveE1: nil, 28 | giveE2: errors.New("bar"), 29 | wantErr: errors.New("bar"), 30 | }, 31 | { 32 | name: "e2 nil", 33 | giveE1: errors.New("foo"), 34 | giveE2: nil, 35 | wantErr: errors.New("foo"), 36 | }, 37 | { 38 | name: "both", 39 | giveE1: errors.New("foo"), 40 | giveE2: errors.New("bar"), 41 | wantErr: errors.New("foo\nbar"), 42 | }, 43 | } 44 | 45 | for _, tt := range tests { 46 | t.Run(tt.name, func(t *testing.T) { 47 | t.Parallel() 48 | 49 | cli, _, _ := newTestCLI(t, nil) 50 | 51 | err := cli.combineErr(tt.giveE1, tt.giveE2) 52 | assert.Equal(t, tt.wantErr, err) 53 | }) 54 | } 55 | } 56 | 57 | func TestOutput(t *testing.T) { 58 | t.Parallel() 59 | 60 | tests := []struct { 61 | name string 62 | give any 63 | giveErr error 64 | giveFormat string 65 | wantOut string 66 | wantErr string 67 | }{ 68 | { 69 | name: "nil", 70 | give: nil, 71 | }, 72 | { 73 | name: "text string", 74 | give: "foo", 75 | giveFormat: "text", 76 | wantOut: "foo\n", 77 | }, 78 | { 79 | name: "text list", 80 | give: []string{"foo", "bar"}, 81 | giveFormat: "text", 82 | wantOut: "bar\nfoo\n", 83 | }, 84 | { 85 | name: "text map", 86 | give: map[string]any{ 87 | "foo": "fooValue", 88 | "bar": 100, 89 | }, 90 | giveFormat: "text", 91 | wantOut: "bar: 100\nfoo: fooValue\n", 92 | }, 93 | { 94 | name: "text nested map", 95 | give: map[string]map[string]any{ 96 | "foo": { 97 | "infoo": "fooValue", 98 | "inbar": 100, 99 | }, 100 | "bar": { 101 | "hello": "world", 102 | }, 103 | }, 104 | giveFormat: "text", 105 | wantOut: "bar\nhello: world\nfoo\ninbar: 100\ninfoo: fooValue\n", 106 | }, 107 | { 108 | name: "json string", 109 | give: "foo", 110 | giveFormat: "json", 111 | wantOut: "\"foo\"\n", 112 | }, 113 | { 114 | name: "json list", 115 | give: []string{"foo", "bar"}, 116 | giveFormat: "json", 117 | wantOut: "[\n\"foo\",\n\"bar\"\n]\n", 118 | }, 119 | { 120 | name: "json map", 121 | give: map[string]any{ 122 | "foo": "fooValue", 123 | "bar": 100, 124 | }, 125 | giveFormat: "json", 126 | wantOut: "{\n\"bar\": 100,\n\"foo\": \"fooValue\"\n}\n", 127 | }, 128 | { 129 | name: "json nested map", 130 | give: map[string]map[string]any{ 131 | "foo": { 132 | "infoo": "fooValue", 133 | "inbar": 100, 134 | }, 135 | "bar": { 136 | "hello": "world", 137 | }, 138 | }, 139 | giveFormat: "json", 140 | wantOut: "{\n\"bar\": {\n\"hello\": \"world\"\n},\n\"foo\": {\n\"inbar\": 100,\n\"infoo\": \"fooValue\"\n}\n}\n", 141 | }, 142 | { 143 | name: "error text", 144 | give: errors.New("test error"), 145 | giveFormat: "text", 146 | wantErr: "ERROR: test error\n", 147 | }, 148 | { 149 | name: "error json", 150 | give: errors.New("test error"), 151 | giveFormat: "json", 152 | wantErr: "{\n\"error\": \"test error\"\n}\n", 153 | }, 154 | { 155 | name: "bad format", 156 | give: "", 157 | giveFormat: "invalid", 158 | wantErr: "ERROR: " + errOutputFormat.Error() + "\n", 159 | }, 160 | { 161 | name: "bad type", 162 | give: 5, 163 | giveFormat: "text", 164 | wantErr: "ERROR: " + errOutputType.Error() + "\n", 165 | }, 166 | { 167 | name: "bad json", 168 | give: func() {}, 169 | giveFormat: "json", 170 | wantErr: "ERROR: " + errJSONMarshal.Error() + "\n", 171 | }, 172 | } 173 | 174 | for _, tt := range tests { 175 | t.Run(tt.name, func(t *testing.T) { 176 | t.Parallel() 177 | 178 | cli, outW, errW := newTestCLI(t, nil) 179 | cli.flagFormat = tt.giveFormat 180 | 181 | cli.output(tt.give) 182 | assert.Equal(t, tt.wantOut, outW.String()) 183 | assert.Equal(t, tt.wantErr, errW.String()) 184 | }) 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /cmd/main_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "testing" 7 | 8 | vaku "github.com/lingrino/vaku/v2/api" 9 | ) 10 | 11 | // newTestCLI returns a CLI ready for running tests. 12 | func newTestCLI(t *testing.T, args []string) (*cli, *bytes.Buffer, *bytes.Buffer) { 13 | t.Helper() 14 | 15 | cli := newCLI() 16 | cli.flagIndent = "" 17 | 18 | var outW, errW bytes.Buffer 19 | cli.cmd.SetOut(&outW) 20 | cli.cmd.SetErr(&errW) 21 | 22 | cli.cmd.SetArgs(args) 23 | 24 | return cli, &outW, &errW 25 | } 26 | 27 | // newTestCLIWithAPI returns a CLI with an initialized API ready for running tests. 28 | func newTestCLIWithAPI(t *testing.T, args []string) (*cli, *bytes.Buffer, *bytes.Buffer) { 29 | t.Helper() 30 | 31 | cli, outW, errW := newTestCLI(t, args) 32 | cli.vc = &testVakuClient{} 33 | return cli, outW, errW 34 | } 35 | 36 | // testVakuClient implements vaku.ClientInterface and just returns very basic values. We don't need 37 | // to test the vaku client again, it is already well tested. 38 | type testVakuClient struct{} 39 | 40 | // Verify Client compliance with the interface. 41 | var _ vaku.ClientInterface = (*testVakuClient)(nil) 42 | 43 | func (c *testVakuClient) PathList(p string) ([]string, error) { 44 | return []string{"foo", "moo"}, nil 45 | } 46 | func (c *testVakuClient) PathRead(p string) (map[string]any, error) { 47 | return map[string]any{"biz": "baz", "foo": "bar"}, nil 48 | } 49 | func (c *testVakuClient) PathWrite(p string, d map[string]any) error { 50 | return nil 51 | } 52 | func (c *testVakuClient) PathDelete(p string) error { 53 | return nil 54 | } 55 | func (c *testVakuClient) PathDestroy(p string, v []int) error { 56 | return nil 57 | } 58 | func (c *testVakuClient) PathDeleteMeta(p string) error { 59 | return nil 60 | } 61 | func (c *testVakuClient) PathUpdate(p string, d map[string]any) error { 62 | return nil 63 | } 64 | func (c *testVakuClient) PathSearch(p, s string) (bool, error) { 65 | return true, nil 66 | } 67 | func (c *testVakuClient) PathCopy(src, dst string) error { 68 | return nil 69 | } 70 | func (c *testVakuClient) PathMove(src, dst string) error { 71 | return nil 72 | } 73 | func (c *testVakuClient) FolderList(ctx context.Context, p string) ([]string, error) { 74 | return []string{"foo/bar", "foo/baz", "bim/bom"}, nil 75 | } 76 | func (c *testVakuClient) FolderListChan(ctx context.Context, p string) (<-chan string, <-chan error) { 77 | return nil, nil 78 | } 79 | func (c *testVakuClient) FolderRead(ctx context.Context, p string) (map[string]map[string]any, error) { 80 | return map[string]map[string]any{ 81 | "foo": { 82 | "bim": "bom", 83 | "biz": "baz", 84 | }, 85 | "bar": { 86 | "hoo": "boo", 87 | }, 88 | }, nil 89 | } 90 | func (c *testVakuClient) FolderReadChan(ctx context.Context, p string) (<-chan map[string]map[string]any, <-chan error) { //nolint:lll 91 | return nil, nil 92 | } 93 | func (c *testVakuClient) FolderWrite(ctx context.Context, d map[string]map[string]any) error { 94 | return nil 95 | } 96 | func (c *testVakuClient) FolderDelete(ctx context.Context, p string) error { 97 | return nil 98 | } 99 | func (c *testVakuClient) FolderDeleteMeta(context.Context, string) error { 100 | return nil 101 | } 102 | func (c *testVakuClient) FolderDestroy(context.Context, string, []int) error { 103 | return nil 104 | } 105 | func (c *testVakuClient) FolderSearch(ctx context.Context, path, search string) ([]string, error) { 106 | return []string{"foo/bar", "bim/bom"}, nil 107 | } 108 | func (c *testVakuClient) FolderCopy(ctx context.Context, src, dst string) error { 109 | return nil 110 | } 111 | func (c *testVakuClient) FolderMove(ctx context.Context, src, dst string) error { 112 | return nil 113 | } 114 | -------------------------------------------------------------------------------- /cmd/path.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | const ( 8 | pathUse = "path " 9 | pathShort = "Commands that act on Vault paths" 10 | pathExample = "vaku path list secret/foo" 11 | pathLong = `Commands that act on Vault paths 12 | 13 | Commands under the path subcommand act on Vault paths. Vaku can list, 14 | copy, move, search, etc.. on Vault paths.` 15 | ) 16 | 17 | func (c *cli) newPathCmd() *cobra.Command { 18 | cmd := &cobra.Command{ 19 | Use: pathUse, 20 | Short: pathShort, 21 | Long: pathLong, 22 | Example: pathExample, 23 | 24 | PersistentPreRunE: c.initVakuClient, 25 | } 26 | 27 | c.addPathFolderFlags(cmd) 28 | 29 | cmd.AddCommand( 30 | c.newPathListCmd(), 31 | c.newPathReadCmd(), 32 | c.newPathWriteCmd(), 33 | c.newPathDeleteCmd(), 34 | c.newPathDeleteMetaCmd(), 35 | c.newPathDestroyCmd(), 36 | c.newPathUpdateCmd(), 37 | c.newPathSearchCmd(), 38 | c.newPathCopyCmd(), 39 | c.newPathMoveCmd(), 40 | ) 41 | 42 | return cmd 43 | } 44 | -------------------------------------------------------------------------------- /cmd/path_copy.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | const ( 8 | pathCopyArgs = 2 9 | pathCopyUse = "copy " 10 | pathCopyShort = "Copy a secret from a source path to a destination path" 11 | pathCopyLong = "Copy a secret from a source path to a destination path" 12 | pathCopyExample = "vaku path copy secret/foo secret/bar" 13 | ) 14 | 15 | func (c *cli) newPathCopyCmd() *cobra.Command { 16 | cmd := &cobra.Command{ 17 | Use: pathCopyUse, 18 | Short: pathCopyShort, 19 | Long: pathCopyLong, 20 | Example: pathCopyExample, 21 | 22 | Args: cobra.ExactArgs(pathCopyArgs), 23 | 24 | RunE: c.runPathCopy, 25 | } 26 | 27 | return cmd 28 | } 29 | 30 | func (c *cli) runPathCopy(cmd *cobra.Command, args []string) error { 31 | return c.vc.PathCopy(args[0], args[1]) 32 | } 33 | -------------------------------------------------------------------------------- /cmd/path_copy_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPathCopy(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | name string 14 | giveArgs []string 15 | wantOut string 16 | wantErr string 17 | }{ 18 | { 19 | name: "foo", 20 | giveArgs: []string{"foo", "bar"}, 21 | wantOut: "", 22 | wantErr: "", 23 | }, 24 | } 25 | 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | t.Parallel() 29 | 30 | args := append([]string{"path", "copy"}, tt.giveArgs...) 31 | cli, outW, errW := newTestCLIWithAPI(t, args) 32 | 33 | ec := cli.execute() 34 | assert.Equal(t, ec*len(errW.String()), len(errW.String()), "unexpected exit code") 35 | 36 | assert.Equal(t, tt.wantOut, outW.String()) 37 | assert.Equal(t, tt.wantErr, errW.String()) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmd/path_delete.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | const ( 8 | pathDeleteArgs = 1 9 | pathDeleteUse = "delete " 10 | pathDeleteShort = "Delete a secret at a path" 11 | pathDeleteLong = "Delete a secret at a path" 12 | pathDeleteExample = "vaku path delete secret/foo" 13 | ) 14 | 15 | func (c *cli) newPathDeleteCmd() *cobra.Command { 16 | cmd := &cobra.Command{ 17 | Use: pathDeleteUse, 18 | Short: pathDeleteShort, 19 | Long: pathDeleteLong, 20 | Example: pathDeleteExample, 21 | 22 | Args: cobra.ExactArgs(pathDeleteArgs), 23 | 24 | RunE: c.runPathDelete, 25 | } 26 | 27 | return cmd 28 | } 29 | 30 | func (c *cli) runPathDelete(cmd *cobra.Command, args []string) error { 31 | return c.vc.PathDelete(args[0]) 32 | } 33 | -------------------------------------------------------------------------------- /cmd/path_delete_meta.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | const ( 8 | pathDeleteMetaArgs = 1 9 | pathDeleteMetaUse = "delete-meta " 10 | pathDeleteMetaShort = "Delete all secret metadata and versions at a path. V2 engines only." 11 | pathDeleteMetaLong = "Delete all secret metadata and versions at a path. V2 engines only." 12 | pathDeleteMetaExample = "vaku path delete-meta secret/foo" 13 | ) 14 | 15 | func (c *cli) newPathDeleteMetaCmd() *cobra.Command { 16 | cmd := &cobra.Command{ 17 | Use: pathDeleteMetaUse, 18 | Short: pathDeleteMetaShort, 19 | Long: pathDeleteMetaLong, 20 | Example: pathDeleteMetaExample, 21 | 22 | Args: cobra.ExactArgs(pathDeleteMetaArgs), 23 | 24 | RunE: c.runPathDeleteMeta, 25 | } 26 | 27 | return cmd 28 | } 29 | 30 | func (c *cli) runPathDeleteMeta(cmd *cobra.Command, args []string) error { 31 | return c.vc.PathDeleteMeta(args[0]) 32 | } 33 | -------------------------------------------------------------------------------- /cmd/path_delete_meta_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPathDeleteMeta(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | name string 14 | giveArgs []string 15 | wantOut string 16 | wantErr string 17 | }{ 18 | { 19 | name: "foo", 20 | giveArgs: []string{"foo"}, 21 | wantOut: "", 22 | wantErr: "", 23 | }, 24 | } 25 | 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | t.Parallel() 29 | 30 | args := append([]string{"path", "delete-meta"}, tt.giveArgs...) 31 | cli, outW, errW := newTestCLIWithAPI(t, args) 32 | 33 | ec := cli.execute() 34 | assert.Equal(t, ec*len(errW.String()), len(errW.String()), "unexpected exit code") 35 | 36 | assert.Equal(t, tt.wantOut, outW.String()) 37 | assert.Equal(t, tt.wantErr, errW.String()) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmd/path_delete_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPathDelete(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | name string 14 | giveArgs []string 15 | wantOut string 16 | wantErr string 17 | }{ 18 | { 19 | name: "foo", 20 | giveArgs: []string{"foo"}, 21 | wantOut: "", 22 | wantErr: "", 23 | }, 24 | } 25 | 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | t.Parallel() 29 | 30 | args := append([]string{"path", "delete"}, tt.giveArgs...) 31 | cli, outW, errW := newTestCLIWithAPI(t, args) 32 | 33 | ec := cli.execute() 34 | assert.Equal(t, ec*len(errW.String()), len(errW.String()), "unexpected exit code") 35 | 36 | assert.Equal(t, tt.wantOut, outW.String()) 37 | assert.Equal(t, tt.wantErr, errW.String()) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmd/path_destroy.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | const ( 8 | pathDestroyUse = "destroy" 9 | pathDestroyShort = "Vaku CLI does not yet support path destroy. Use the vaku API or native Vault CLI" 10 | pathDestroyLong = "Vaku CLI does not yet support path destroy. Use the vaku API or native Vault CLI" 11 | ) 12 | 13 | func (c *cli) newPathDestroyCmd() *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: pathDestroyUse, 16 | Short: pathDestroyShort, 17 | Long: pathDestroyLong, 18 | 19 | // disable all discovery 20 | Hidden: true, 21 | DisableSuggestions: true, 22 | DisableFlagsInUseLine: true, 23 | PersistentPreRun: nil, 24 | Args: cobra.ArbitraryArgs, 25 | } 26 | 27 | return cmd 28 | } 29 | -------------------------------------------------------------------------------- /cmd/path_list.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | const ( 8 | pathListArgs = 1 9 | pathListUse = "list " 10 | pathListShort = "List all paths at a path" 11 | pathListLong = "List all paths at a path" 12 | pathListExample = "vaku path list secret/foo" 13 | ) 14 | 15 | func (c *cli) newPathListCmd() *cobra.Command { 16 | cmd := &cobra.Command{ 17 | Use: pathListUse, 18 | Short: pathListShort, 19 | Long: pathListLong, 20 | Example: pathListExample, 21 | 22 | Args: cobra.ExactArgs(pathListArgs), 23 | 24 | RunE: c.runPathList, 25 | } 26 | 27 | return cmd 28 | } 29 | 30 | func (c *cli) runPathList(cmd *cobra.Command, args []string) error { 31 | list, err := c.vc.PathList(args[0]) 32 | c.output(list) 33 | return err 34 | } 35 | -------------------------------------------------------------------------------- /cmd/path_list_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPathList(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | name string 14 | giveArgs []string 15 | wantOut string 16 | wantErr string 17 | }{ 18 | { 19 | name: "foo", 20 | giveArgs: []string{"foo"}, 21 | wantOut: "foo\nmoo\n", 22 | wantErr: "", 23 | }, 24 | } 25 | 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | t.Parallel() 29 | 30 | args := append([]string{"path", "list"}, tt.giveArgs...) 31 | cli, outW, errW := newTestCLIWithAPI(t, args) 32 | 33 | ec := cli.execute() 34 | assert.Equal(t, ec*len(errW.String()), len(errW.String()), "unexpected exit code") 35 | 36 | assert.Equal(t, tt.wantOut, outW.String()) 37 | assert.Equal(t, tt.wantErr, errW.String()) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmd/path_move.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | const ( 8 | pathMoveArgs = 2 9 | pathMoveUse = "move " 10 | pathMoveShort = "Move a secret from a source path to a destination path" 11 | pathMoveLong = "Move a secret from a source path to a destination path" 12 | pathMoveExample = "vaku path move secret/foo secret/bar" 13 | ) 14 | 15 | func (c *cli) newPathMoveCmd() *cobra.Command { 16 | cmd := &cobra.Command{ 17 | Use: pathMoveUse, 18 | Short: pathMoveShort, 19 | Long: pathMoveLong, 20 | Example: pathMoveExample, 21 | 22 | Args: cobra.ExactArgs(pathMoveArgs), 23 | 24 | RunE: c.runPathMove, 25 | } 26 | 27 | return cmd 28 | } 29 | 30 | func (c *cli) runPathMove(cmd *cobra.Command, args []string) error { 31 | return c.vc.PathMove(args[0], args[1]) 32 | } 33 | -------------------------------------------------------------------------------- /cmd/path_move_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPathMove(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | name string 14 | giveArgs []string 15 | wantOut string 16 | wantErr string 17 | }{ 18 | { 19 | name: "foo", 20 | giveArgs: []string{"foo", "bar"}, 21 | wantOut: "", 22 | wantErr: "", 23 | }, 24 | } 25 | 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | t.Parallel() 29 | 30 | args := append([]string{"path", "move"}, tt.giveArgs...) 31 | cli, outW, errW := newTestCLIWithAPI(t, args) 32 | 33 | ec := cli.execute() 34 | assert.Equal(t, ec*len(errW.String()), len(errW.String()), "unexpected exit code") 35 | 36 | assert.Equal(t, tt.wantOut, outW.String()) 37 | assert.Equal(t, tt.wantErr, errW.String()) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmd/path_read.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | const ( 8 | pathReadArgs = 1 9 | pathReadUse = "read " 10 | pathReadShort = "Read a secret at a path" 11 | pathReadLong = "Read a secret at a path" 12 | pathReadExample = "vaku path read secret/foo" 13 | ) 14 | 15 | func (c *cli) newPathReadCmd() *cobra.Command { 16 | cmd := &cobra.Command{ 17 | Use: pathReadUse, 18 | Short: pathReadShort, 19 | Long: pathReadLong, 20 | Example: pathReadExample, 21 | 22 | Args: cobra.ExactArgs(pathReadArgs), 23 | 24 | RunE: c.runPathRead, 25 | } 26 | 27 | return cmd 28 | } 29 | 30 | func (c *cli) runPathRead(cmd *cobra.Command, args []string) error { 31 | read, err := c.vc.PathRead(args[0]) 32 | c.output(read) 33 | return err 34 | } 35 | -------------------------------------------------------------------------------- /cmd/path_read_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPathRead(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | name string 14 | giveArgs []string 15 | wantOut string 16 | wantErr string 17 | }{ 18 | { 19 | name: "foo", 20 | giveArgs: []string{"foo"}, 21 | wantOut: "biz: baz\nfoo: bar\n", 22 | wantErr: "", 23 | }, 24 | } 25 | 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | t.Parallel() 29 | 30 | args := append([]string{"path", "read"}, tt.giveArgs...) 31 | cli, outW, errW := newTestCLIWithAPI(t, args) 32 | 33 | ec := cli.execute() 34 | assert.Equal(t, ec*len(errW.String()), len(errW.String()), "unexpected exit code") 35 | 36 | assert.Equal(t, tt.wantOut, outW.String()) 37 | assert.Equal(t, tt.wantErr, errW.String()) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmd/path_search.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/spf13/cobra" 7 | ) 8 | 9 | const ( 10 | pathSearchArgs = 2 11 | pathSearchUse = "search " 12 | pathSearchShort = "Search a secret for a search string" 13 | pathSearchLong = "Search a secret for a search string" 14 | pathSearchExample = "vaku path search secret/foo bar" 15 | ) 16 | 17 | func (c *cli) newPathSearchCmd() *cobra.Command { 18 | cmd := &cobra.Command{ 19 | Use: pathSearchUse, 20 | Short: pathSearchShort, 21 | Long: pathSearchLong, 22 | Example: pathSearchExample, 23 | 24 | Args: cobra.ExactArgs(pathSearchArgs), 25 | 26 | RunE: c.runPathSearch, 27 | } 28 | 29 | return cmd 30 | } 31 | 32 | func (c *cli) runPathSearch(cmd *cobra.Command, args []string) error { 33 | search, err := c.vc.PathSearch(args[0], args[1]) 34 | c.output(strconv.FormatBool(search)) 35 | return err 36 | } 37 | -------------------------------------------------------------------------------- /cmd/path_search_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPathSearch(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | name string 14 | giveArgs []string 15 | wantOut string 16 | wantErr string 17 | }{ 18 | { 19 | name: "foo", 20 | giveArgs: []string{"foo", "bar"}, 21 | wantOut: "true\n", 22 | wantErr: "", 23 | }, 24 | } 25 | 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | t.Parallel() 29 | 30 | args := append([]string{"path", "search"}, tt.giveArgs...) 31 | cli, outW, errW := newTestCLIWithAPI(t, args) 32 | 33 | ec := cli.execute() 34 | assert.Equal(t, ec*len(errW.String()), len(errW.String()), "unexpected exit code") 35 | 36 | assert.Equal(t, tt.wantOut, outW.String()) 37 | assert.Equal(t, tt.wantErr, errW.String()) 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cmd/path_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestPath(t *testing.T) { 10 | t.Parallel() 11 | 12 | cli, outW, errW := newTestCLI(t, []string{"path"}) 13 | assert.Equal(t, "", errW.String()) 14 | 15 | err := cli.cmd.Execute() 16 | 17 | assert.NoError(t, err) 18 | assert.Contains(t, outW.String(), pathShort) 19 | assert.Contains(t, outW.String(), pathLong) 20 | } 21 | -------------------------------------------------------------------------------- /cmd/path_update.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | const ( 8 | pathUpdateUse = "update" 9 | pathUpdateShort = "Vaku CLI does not yet support path update. Use the vaku API or native Vault CLI" 10 | pathUpdateLong = "Vaku CLI does not yet support path update. Use the vaku API or native Vault CLI" 11 | ) 12 | 13 | func (c *cli) newPathUpdateCmd() *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: pathUpdateUse, 16 | Short: pathUpdateShort, 17 | Long: pathUpdateLong, 18 | 19 | // disable all discovery 20 | Hidden: true, 21 | DisableSuggestions: true, 22 | DisableFlagsInUseLine: true, 23 | PersistentPreRun: nil, 24 | Args: cobra.ArbitraryArgs, 25 | } 26 | 27 | return cmd 28 | } 29 | -------------------------------------------------------------------------------- /cmd/path_write.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | const ( 8 | pathWriteUse = "write" 9 | pathWriteShort = "Vaku CLI does not yet support path write. Use the vaku API or native Vault CLI" 10 | pathWriteLong = "Vaku CLI does not yet support path write. Use the vaku API or native Vault CLI" 11 | ) 12 | 13 | func (c *cli) newPathWriteCmd() *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: pathWriteUse, 16 | Short: pathWriteShort, 17 | Long: pathWriteLong, 18 | 19 | // disable all discovery 20 | Hidden: true, 21 | DisableSuggestions: true, 22 | DisableFlagsInUseLine: true, 23 | PersistentPreRun: nil, 24 | Args: cobra.ArbitraryArgs, 25 | } 26 | 27 | return cmd 28 | } 29 | -------------------------------------------------------------------------------- /cmd/vaku.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | const ( 8 | vakuUse = "vaku " 9 | vakuShort = "Vaku is a CLI for working with large Vault k/v secret engines" 10 | vakuExample = "vaku folder list secret/foo" 11 | vakuLong = `Vaku is a CLI for working with large Vault k/v secret engines 12 | 13 | The Vaku CLI provides path- and folder-based commands that work on 14 | both Version 1 and Version 2 K/V secret engines. Vaku can help manage 15 | large amounts of Vault data by updating secrets in place, moving 16 | paths or folders, searching secrets, and more. 17 | 18 | Vaku is not a replacement for the Vault CLI and requires that you are 19 | already authenticated to Vault before running any commands. Vaku 20 | commands should not be run on non-K/V engines. 21 | 22 | CLI documentation - 'vaku help [cmd]' 23 | API documentation - https://pkg.go.dev/github.com/lingrino/vaku/v2/api 24 | Built by Sean Lingren ` 25 | ) 26 | 27 | // newVakuCmd sets flags/subcommands and returns the base vaku command. 28 | func (c *cli) newVakuCmd() *cobra.Command { 29 | // base command 30 | cmd := &cobra.Command{ 31 | Use: vakuUse, 32 | Short: vakuShort, 33 | Long: vakuLong, 34 | Example: vakuExample, 35 | 36 | PersistentPreRunE: c.validateVakuFlags, 37 | 38 | // https://github.com/spf13/cobra/issues/914#issuecomment-548411337 39 | SilenceErrors: true, 40 | SilenceUsage: true, 41 | 42 | // prevents docs from adding promotional message footer 43 | DisableAutoGenTag: true, 44 | } 45 | 46 | // add base/persistent flags 47 | c.addVakuFlags(cmd) 48 | 49 | // add subcommands 50 | cmd.AddCommand( 51 | c.newDocsCmd(), 52 | c.newFolderCmd(), 53 | c.newPathCmd(), 54 | c.newVersionCmd(), 55 | ) 56 | 57 | return cmd 58 | } 59 | -------------------------------------------------------------------------------- /cmd/vaku_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestVaku(t *testing.T) { 10 | t.Parallel() 11 | 12 | cli, outW, errW := newTestCLI(t, nil) 13 | assert.Equal(t, "", errW.String()) 14 | 15 | err := cli.cmd.Execute() 16 | 17 | assert.NoError(t, err) 18 | assert.Contains(t, outW.String(), vakuShort) 19 | assert.Contains(t, outW.String(), vakuLong) 20 | assert.Contains(t, outW.String(), vakuExample) 21 | } 22 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | 6 | vaku "github.com/lingrino/vaku/v2/api" 7 | ) 8 | 9 | const ( 10 | versionUse = "version" 11 | versionShort = "Print vaku version" 12 | versionExample = "vaku version" 13 | ) 14 | 15 | func (c *cli) newVersionCmd() *cobra.Command { 16 | cmd := &cobra.Command{ 17 | Use: versionUse, 18 | Short: versionShort, 19 | Example: versionExample, 20 | 21 | Args: cobra.NoArgs, 22 | 23 | DisableFlagsInUseLine: true, 24 | 25 | RunE: c.runVersion, 26 | } 27 | 28 | return cmd 29 | } 30 | 31 | func (c *cli) runVersion(cmd *cobra.Command, args []string) error { 32 | output := map[string]any{ 33 | "CLI": c.version, 34 | "API": vaku.Version(), 35 | } 36 | c.output(output) 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /cmd/version_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestVersion(t *testing.T) { 10 | t.Parallel() 11 | 12 | tests := []struct { 13 | name string 14 | giveVersion string 15 | giveArgs []string 16 | wantOut string 17 | wantErr string 18 | }{ 19 | { 20 | name: "version test", 21 | giveVersion: "test", 22 | wantOut: "API: 2.8.3\nCLI: test\n", 23 | }, 24 | { 25 | name: "version version", 26 | giveVersion: "version", 27 | wantOut: "API: 2.8.3\nCLI: version\n", 28 | }, 29 | { 30 | name: "args", 31 | giveVersion: "version", 32 | giveArgs: []string{"arg1", "arg2"}, 33 | wantOut: "", 34 | wantErr: "unknown command", 35 | }, 36 | } 37 | 38 | for _, tt := range tests { 39 | t.Run(tt.name, func(t *testing.T) { 40 | t.Parallel() 41 | 42 | args := append([]string{"version"}, tt.giveArgs...) 43 | cli, outW, errW := newTestCLI(t, args) 44 | cli.setVersion(tt.giveVersion) 45 | 46 | ec := cli.execute() 47 | assert.Equal(t, ec*len(errW.String()), len(errW.String()), "unexpected exit code") 48 | 49 | assert.Equal(t, tt.wantOut, outW.String()) 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /docs/cli/vaku.md: -------------------------------------------------------------------------------- 1 | ## vaku 2 | 3 | Vaku is a CLI for working with large Vault k/v secret engines 4 | 5 | ### Synopsis 6 | 7 | Vaku is a CLI for working with large Vault k/v secret engines 8 | 9 | The Vaku CLI provides path- and folder-based commands that work on 10 | both Version 1 and Version 2 K/V secret engines. Vaku can help manage 11 | large amounts of Vault data by updating secrets in place, moving 12 | paths or folders, searching secrets, and more. 13 | 14 | Vaku is not a replacement for the Vault CLI and requires that you are 15 | already authenticated to Vault before running any commands. Vaku 16 | commands should not be run on non-K/V engines. 17 | 18 | CLI documentation - 'vaku help [cmd]' 19 | API documentation - https://pkg.go.dev/github.com/lingrino/vaku/v2/api 20 | Built by Sean Lingren 21 | 22 | ### Examples 23 | 24 | ``` 25 | vaku folder list secret/foo 26 | ``` 27 | 28 | ### Options 29 | 30 | ``` 31 | --format string output format: text|json (default "text") 32 | -h, --help help for vaku 33 | -i, --indent-char string string used for indents (default " ") 34 | -s, --sort sort output text (default true) 35 | ``` 36 | 37 | ### SEE ALSO 38 | 39 | * [vaku completion](vaku_completion.md) - Generate the autocompletion script for the specified shell 40 | * [vaku folder](vaku_folder.md) - Commands that act on Vault folders 41 | * [vaku path](vaku_path.md) - Commands that act on Vault paths 42 | * [vaku version](vaku_version.md) - Print vaku version 43 | 44 | -------------------------------------------------------------------------------- /docs/cli/vaku_completion.md: -------------------------------------------------------------------------------- 1 | ## vaku completion 2 | 3 | Generate the autocompletion script for the specified shell 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for vaku for the specified shell. 8 | See each sub-command's help for details on how to use the generated script. 9 | 10 | 11 | ### Options 12 | 13 | ``` 14 | -h, --help help for completion 15 | ``` 16 | 17 | ### Options inherited from parent commands 18 | 19 | ``` 20 | --format string output format: text|json (default "text") 21 | -i, --indent-char string string used for indents (default " ") 22 | -s, --sort sort output text (default true) 23 | ``` 24 | 25 | ### SEE ALSO 26 | 27 | * [vaku](vaku.md) - Vaku is a CLI for working with large Vault k/v secret engines 28 | * [vaku completion bash](vaku_completion_bash.md) - Generate the autocompletion script for bash 29 | * [vaku completion fish](vaku_completion_fish.md) - Generate the autocompletion script for fish 30 | * [vaku completion powershell](vaku_completion_powershell.md) - Generate the autocompletion script for powershell 31 | * [vaku completion zsh](vaku_completion_zsh.md) - Generate the autocompletion script for zsh 32 | 33 | -------------------------------------------------------------------------------- /docs/cli/vaku_completion_bash.md: -------------------------------------------------------------------------------- 1 | ## vaku completion bash 2 | 3 | Generate the autocompletion script for bash 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for the bash shell. 8 | 9 | This script depends on the 'bash-completion' package. 10 | If it is not installed already, you can install it via your OS's package manager. 11 | 12 | To load completions in your current shell session: 13 | 14 | source <(vaku completion bash) 15 | 16 | To load completions for every new session, execute once: 17 | 18 | #### Linux: 19 | 20 | vaku completion bash > /etc/bash_completion.d/vaku 21 | 22 | #### macOS: 23 | 24 | vaku completion bash > $(brew --prefix)/etc/bash_completion.d/vaku 25 | 26 | You will need to start a new shell for this setup to take effect. 27 | 28 | 29 | ``` 30 | vaku completion bash 31 | ``` 32 | 33 | ### Options 34 | 35 | ``` 36 | -h, --help help for bash 37 | --no-descriptions disable completion descriptions 38 | ``` 39 | 40 | ### Options inherited from parent commands 41 | 42 | ``` 43 | --format string output format: text|json (default "text") 44 | -i, --indent-char string string used for indents (default " ") 45 | -s, --sort sort output text (default true) 46 | ``` 47 | 48 | ### SEE ALSO 49 | 50 | * [vaku completion](vaku_completion.md) - Generate the autocompletion script for the specified shell 51 | 52 | -------------------------------------------------------------------------------- /docs/cli/vaku_completion_fish.md: -------------------------------------------------------------------------------- 1 | ## vaku completion fish 2 | 3 | Generate the autocompletion script for fish 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for the fish shell. 8 | 9 | To load completions in your current shell session: 10 | 11 | vaku completion fish | source 12 | 13 | To load completions for every new session, execute once: 14 | 15 | vaku completion fish > ~/.config/fish/completions/vaku.fish 16 | 17 | You will need to start a new shell for this setup to take effect. 18 | 19 | 20 | ``` 21 | vaku completion fish [flags] 22 | ``` 23 | 24 | ### Options 25 | 26 | ``` 27 | -h, --help help for fish 28 | --no-descriptions disable completion descriptions 29 | ``` 30 | 31 | ### Options inherited from parent commands 32 | 33 | ``` 34 | --format string output format: text|json (default "text") 35 | -i, --indent-char string string used for indents (default " ") 36 | -s, --sort sort output text (default true) 37 | ``` 38 | 39 | ### SEE ALSO 40 | 41 | * [vaku completion](vaku_completion.md) - Generate the autocompletion script for the specified shell 42 | 43 | -------------------------------------------------------------------------------- /docs/cli/vaku_completion_powershell.md: -------------------------------------------------------------------------------- 1 | ## vaku completion powershell 2 | 3 | Generate the autocompletion script for powershell 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for powershell. 8 | 9 | To load completions in your current shell session: 10 | 11 | vaku completion powershell | Out-String | Invoke-Expression 12 | 13 | To load completions for every new session, add the output of the above command 14 | to your powershell profile. 15 | 16 | 17 | ``` 18 | vaku completion powershell [flags] 19 | ``` 20 | 21 | ### Options 22 | 23 | ``` 24 | -h, --help help for powershell 25 | --no-descriptions disable completion descriptions 26 | ``` 27 | 28 | ### Options inherited from parent commands 29 | 30 | ``` 31 | --format string output format: text|json (default "text") 32 | -i, --indent-char string string used for indents (default " ") 33 | -s, --sort sort output text (default true) 34 | ``` 35 | 36 | ### SEE ALSO 37 | 38 | * [vaku completion](vaku_completion.md) - Generate the autocompletion script for the specified shell 39 | 40 | -------------------------------------------------------------------------------- /docs/cli/vaku_completion_zsh.md: -------------------------------------------------------------------------------- 1 | ## vaku completion zsh 2 | 3 | Generate the autocompletion script for zsh 4 | 5 | ### Synopsis 6 | 7 | Generate the autocompletion script for the zsh shell. 8 | 9 | If shell completion is not already enabled in your environment you will need 10 | to enable it. You can execute the following once: 11 | 12 | echo "autoload -U compinit; compinit" >> ~/.zshrc 13 | 14 | To load completions in your current shell session: 15 | 16 | source <(vaku completion zsh) 17 | 18 | To load completions for every new session, execute once: 19 | 20 | #### Linux: 21 | 22 | vaku completion zsh > "${fpath[1]}/_vaku" 23 | 24 | #### macOS: 25 | 26 | vaku completion zsh > $(brew --prefix)/share/zsh/site-functions/_vaku 27 | 28 | You will need to start a new shell for this setup to take effect. 29 | 30 | 31 | ``` 32 | vaku completion zsh [flags] 33 | ``` 34 | 35 | ### Options 36 | 37 | ``` 38 | -h, --help help for zsh 39 | --no-descriptions disable completion descriptions 40 | ``` 41 | 42 | ### Options inherited from parent commands 43 | 44 | ``` 45 | --format string output format: text|json (default "text") 46 | -i, --indent-char string string used for indents (default " ") 47 | -s, --sort sort output text (default true) 48 | ``` 49 | 50 | ### SEE ALSO 51 | 52 | * [vaku completion](vaku_completion.md) - Generate the autocompletion script for the specified shell 53 | 54 | -------------------------------------------------------------------------------- /docs/cli/vaku_folder.md: -------------------------------------------------------------------------------- 1 | ## vaku folder 2 | 3 | Commands that act on Vault folders 4 | 5 | ### Synopsis 6 | 7 | Commands that act on Vault folders 8 | 9 | Commands under the folder subcommand act on Vault folders. Folders 10 | are designated by paths that end in a '/' such as 'secret/foo/'. Vaku 11 | can list, copy, move, search, etc.. on Vault folders. 12 | 13 | ### Examples 14 | 15 | ``` 16 | vaku folder list secret/foo 17 | ``` 18 | 19 | ### Options 20 | 21 | ``` 22 | -p, --absolute-path show absolute path in output 23 | -a, --address string address of the Vault server 24 | --destination-address string address of the destination Vault server 25 | --destination-namespace string name of the vault namespace to use in the destination client 26 | --destination-token string token for the destination vault server (alias for --token) 27 | -h, --help help for folder 28 | --ignore-read-errors ignore path read errors and continue 29 | -n, --namespace string name of the vault namespace to use in the source client 30 | --source-address string address of the source Vault server (alias for --address) 31 | --source-namespace string name of the vault namespace to use in the source client (alias for --namespace) 32 | --source-token string token for the source vault server (alias for --token) 33 | -t, --token string token for the vault server 34 | -w, --workers int number of concurrent workers (default 10) 35 | ``` 36 | 37 | ### Options inherited from parent commands 38 | 39 | ``` 40 | --format string output format: text|json (default "text") 41 | -i, --indent-char string string used for indents (default " ") 42 | -s, --sort sort output text (default true) 43 | ``` 44 | 45 | ### SEE ALSO 46 | 47 | * [vaku](vaku.md) - Vaku is a CLI for working with large Vault k/v secret engines 48 | * [vaku folder copy](vaku_folder_copy.md) - Recursively copy all secrets in source folder to destination folder 49 | * [vaku folder delete](vaku_folder_delete.md) - Recursively delete all secrets in a folder 50 | * [vaku folder delete-meta](vaku_folder_delete-meta.md) - Recursively delete all secrets metadata and versions in a folder. V2 engines only. 51 | * [vaku folder list](vaku_folder_list.md) - Recursively list all paths in a folder 52 | * [vaku folder move](vaku_folder_move.md) - Recursively move all secrets in source folder to destination folder 53 | * [vaku folder read](vaku_folder_read.md) - Recursively read all secrets in a folder 54 | * [vaku folder search](vaku_folder_search.md) - Recursively search all secrets in a folder for a search string 55 | * [vaku folder write](vaku_folder_write.md) - write a folder of secrets. WARNING: command expects a very specific json input 56 | 57 | -------------------------------------------------------------------------------- /docs/cli/vaku_folder_copy.md: -------------------------------------------------------------------------------- 1 | ## vaku folder copy 2 | 3 | Recursively copy all secrets in source folder to destination folder 4 | 5 | ### Synopsis 6 | 7 | Recursively copy all secrets in source folder to destination folder 8 | 9 | ``` 10 | vaku folder copy [flags] 11 | ``` 12 | 13 | ### Examples 14 | 15 | ``` 16 | vaku folder copy secret/foo secret/bar 17 | ``` 18 | 19 | ### Options 20 | 21 | ``` 22 | -h, --help help for copy 23 | ``` 24 | 25 | ### Options inherited from parent commands 26 | 27 | ``` 28 | -p, --absolute-path show absolute path in output 29 | -a, --address string address of the Vault server 30 | --destination-address string address of the destination Vault server 31 | --destination-namespace string name of the vault namespace to use in the destination client 32 | --destination-token string token for the destination vault server (alias for --token) 33 | --format string output format: text|json (default "text") 34 | --ignore-read-errors ignore path read errors and continue 35 | -i, --indent-char string string used for indents (default " ") 36 | -n, --namespace string name of the vault namespace to use in the source client 37 | -s, --sort sort output text (default true) 38 | --source-address string address of the source Vault server (alias for --address) 39 | --source-namespace string name of the vault namespace to use in the source client (alias for --namespace) 40 | --source-token string token for the source vault server (alias for --token) 41 | -t, --token string token for the vault server 42 | -w, --workers int number of concurrent workers (default 10) 43 | ``` 44 | 45 | ### SEE ALSO 46 | 47 | * [vaku folder](vaku_folder.md) - Commands that act on Vault folders 48 | 49 | -------------------------------------------------------------------------------- /docs/cli/vaku_folder_delete-meta.md: -------------------------------------------------------------------------------- 1 | ## vaku folder delete-meta 2 | 3 | Recursively delete all secrets metadata and versions in a folder. V2 engines only. 4 | 5 | ### Synopsis 6 | 7 | Recursively delete all secrets metadata and versions in a folder. V2 engines only. 8 | 9 | ``` 10 | vaku folder delete-meta [flags] 11 | ``` 12 | 13 | ### Examples 14 | 15 | ``` 16 | vaku folder delete-meta secret/foo 17 | ``` 18 | 19 | ### Options 20 | 21 | ``` 22 | -h, --help help for delete-meta 23 | ``` 24 | 25 | ### Options inherited from parent commands 26 | 27 | ``` 28 | -p, --absolute-path show absolute path in output 29 | -a, --address string address of the Vault server 30 | --destination-address string address of the destination Vault server 31 | --destination-namespace string name of the vault namespace to use in the destination client 32 | --destination-token string token for the destination vault server (alias for --token) 33 | --format string output format: text|json (default "text") 34 | --ignore-read-errors ignore path read errors and continue 35 | -i, --indent-char string string used for indents (default " ") 36 | -n, --namespace string name of the vault namespace to use in the source client 37 | -s, --sort sort output text (default true) 38 | --source-address string address of the source Vault server (alias for --address) 39 | --source-namespace string name of the vault namespace to use in the source client (alias for --namespace) 40 | --source-token string token for the source vault server (alias for --token) 41 | -t, --token string token for the vault server 42 | -w, --workers int number of concurrent workers (default 10) 43 | ``` 44 | 45 | ### SEE ALSO 46 | 47 | * [vaku folder](vaku_folder.md) - Commands that act on Vault folders 48 | 49 | -------------------------------------------------------------------------------- /docs/cli/vaku_folder_delete.md: -------------------------------------------------------------------------------- 1 | ## vaku folder delete 2 | 3 | Recursively delete all secrets in a folder 4 | 5 | ### Synopsis 6 | 7 | Recursively delete all secrets in a folder 8 | 9 | ``` 10 | vaku folder delete [flags] 11 | ``` 12 | 13 | ### Examples 14 | 15 | ``` 16 | vaku folder delete secret/foo 17 | ``` 18 | 19 | ### Options 20 | 21 | ``` 22 | -h, --help help for delete 23 | ``` 24 | 25 | ### Options inherited from parent commands 26 | 27 | ``` 28 | -p, --absolute-path show absolute path in output 29 | -a, --address string address of the Vault server 30 | --destination-address string address of the destination Vault server 31 | --destination-namespace string name of the vault namespace to use in the destination client 32 | --destination-token string token for the destination vault server (alias for --token) 33 | --format string output format: text|json (default "text") 34 | --ignore-read-errors ignore path read errors and continue 35 | -i, --indent-char string string used for indents (default " ") 36 | -n, --namespace string name of the vault namespace to use in the source client 37 | -s, --sort sort output text (default true) 38 | --source-address string address of the source Vault server (alias for --address) 39 | --source-namespace string name of the vault namespace to use in the source client (alias for --namespace) 40 | --source-token string token for the source vault server (alias for --token) 41 | -t, --token string token for the vault server 42 | -w, --workers int number of concurrent workers (default 10) 43 | ``` 44 | 45 | ### SEE ALSO 46 | 47 | * [vaku folder](vaku_folder.md) - Commands that act on Vault folders 48 | 49 | -------------------------------------------------------------------------------- /docs/cli/vaku_folder_list.md: -------------------------------------------------------------------------------- 1 | ## vaku folder list 2 | 3 | Recursively list all paths in a folder 4 | 5 | ### Synopsis 6 | 7 | Recursively list all paths in a folder 8 | 9 | ``` 10 | vaku folder list [flags] 11 | ``` 12 | 13 | ### Examples 14 | 15 | ``` 16 | vaku folder list secret/foo 17 | ``` 18 | 19 | ### Options 20 | 21 | ``` 22 | -h, --help help for list 23 | ``` 24 | 25 | ### Options inherited from parent commands 26 | 27 | ``` 28 | -p, --absolute-path show absolute path in output 29 | -a, --address string address of the Vault server 30 | --destination-address string address of the destination Vault server 31 | --destination-namespace string name of the vault namespace to use in the destination client 32 | --destination-token string token for the destination vault server (alias for --token) 33 | --format string output format: text|json (default "text") 34 | --ignore-read-errors ignore path read errors and continue 35 | -i, --indent-char string string used for indents (default " ") 36 | -n, --namespace string name of the vault namespace to use in the source client 37 | -s, --sort sort output text (default true) 38 | --source-address string address of the source Vault server (alias for --address) 39 | --source-namespace string name of the vault namespace to use in the source client (alias for --namespace) 40 | --source-token string token for the source vault server (alias for --token) 41 | -t, --token string token for the vault server 42 | -w, --workers int number of concurrent workers (default 10) 43 | ``` 44 | 45 | ### SEE ALSO 46 | 47 | * [vaku folder](vaku_folder.md) - Commands that act on Vault folders 48 | 49 | -------------------------------------------------------------------------------- /docs/cli/vaku_folder_move.md: -------------------------------------------------------------------------------- 1 | ## vaku folder move 2 | 3 | Recursively move all secrets in source folder to destination folder 4 | 5 | ### Synopsis 6 | 7 | Recursively move all secrets in source folder to destination folder 8 | 9 | ``` 10 | vaku folder move [flags] 11 | ``` 12 | 13 | ### Examples 14 | 15 | ``` 16 | vaku folder move secret/foo secret/bar 17 | ``` 18 | 19 | ### Options 20 | 21 | ``` 22 | -h, --help help for move 23 | ``` 24 | 25 | ### Options inherited from parent commands 26 | 27 | ``` 28 | -p, --absolute-path show absolute path in output 29 | -a, --address string address of the Vault server 30 | --destination-address string address of the destination Vault server 31 | --destination-namespace string name of the vault namespace to use in the destination client 32 | --destination-token string token for the destination vault server (alias for --token) 33 | --format string output format: text|json (default "text") 34 | --ignore-read-errors ignore path read errors and continue 35 | -i, --indent-char string string used for indents (default " ") 36 | -n, --namespace string name of the vault namespace to use in the source client 37 | -s, --sort sort output text (default true) 38 | --source-address string address of the source Vault server (alias for --address) 39 | --source-namespace string name of the vault namespace to use in the source client (alias for --namespace) 40 | --source-token string token for the source vault server (alias for --token) 41 | -t, --token string token for the vault server 42 | -w, --workers int number of concurrent workers (default 10) 43 | ``` 44 | 45 | ### SEE ALSO 46 | 47 | * [vaku folder](vaku_folder.md) - Commands that act on Vault folders 48 | 49 | -------------------------------------------------------------------------------- /docs/cli/vaku_folder_read.md: -------------------------------------------------------------------------------- 1 | ## vaku folder read 2 | 3 | Recursively read all secrets in a folder 4 | 5 | ### Synopsis 6 | 7 | Recursively read all secrets in a folder 8 | 9 | ``` 10 | vaku folder read [flags] 11 | ``` 12 | 13 | ### Examples 14 | 15 | ``` 16 | vaku folder read secret/foo 17 | ``` 18 | 19 | ### Options 20 | 21 | ``` 22 | -h, --help help for read 23 | ``` 24 | 25 | ### Options inherited from parent commands 26 | 27 | ``` 28 | -p, --absolute-path show absolute path in output 29 | -a, --address string address of the Vault server 30 | --destination-address string address of the destination Vault server 31 | --destination-namespace string name of the vault namespace to use in the destination client 32 | --destination-token string token for the destination vault server (alias for --token) 33 | --format string output format: text|json (default "text") 34 | --ignore-read-errors ignore path read errors and continue 35 | -i, --indent-char string string used for indents (default " ") 36 | -n, --namespace string name of the vault namespace to use in the source client 37 | -s, --sort sort output text (default true) 38 | --source-address string address of the source Vault server (alias for --address) 39 | --source-namespace string name of the vault namespace to use in the source client (alias for --namespace) 40 | --source-token string token for the source vault server (alias for --token) 41 | -t, --token string token for the vault server 42 | -w, --workers int number of concurrent workers (default 10) 43 | ``` 44 | 45 | ### SEE ALSO 46 | 47 | * [vaku folder](vaku_folder.md) - Commands that act on Vault folders 48 | 49 | -------------------------------------------------------------------------------- /docs/cli/vaku_folder_search.md: -------------------------------------------------------------------------------- 1 | ## vaku folder search 2 | 3 | Recursively search all secrets in a folder for a search string 4 | 5 | ### Synopsis 6 | 7 | Recursively search all secrets in a folder for a search string 8 | 9 | ``` 10 | vaku folder search [flags] 11 | ``` 12 | 13 | ### Examples 14 | 15 | ``` 16 | vaku folder search secret/foo bar 17 | ``` 18 | 19 | ### Options 20 | 21 | ``` 22 | -h, --help help for search 23 | ``` 24 | 25 | ### Options inherited from parent commands 26 | 27 | ``` 28 | -p, --absolute-path show absolute path in output 29 | -a, --address string address of the Vault server 30 | --destination-address string address of the destination Vault server 31 | --destination-namespace string name of the vault namespace to use in the destination client 32 | --destination-token string token for the destination vault server (alias for --token) 33 | --format string output format: text|json (default "text") 34 | --ignore-read-errors ignore path read errors and continue 35 | -i, --indent-char string string used for indents (default " ") 36 | -n, --namespace string name of the vault namespace to use in the source client 37 | -s, --sort sort output text (default true) 38 | --source-address string address of the source Vault server (alias for --address) 39 | --source-namespace string name of the vault namespace to use in the source client (alias for --namespace) 40 | --source-token string token for the source vault server (alias for --token) 41 | -t, --token string token for the vault server 42 | -w, --workers int number of concurrent workers (default 10) 43 | ``` 44 | 45 | ### SEE ALSO 46 | 47 | * [vaku folder](vaku_folder.md) - Commands that act on Vault folders 48 | 49 | -------------------------------------------------------------------------------- /docs/cli/vaku_folder_write.md: -------------------------------------------------------------------------------- 1 | ## vaku folder write 2 | 3 | write a folder of secrets. WARNING: command expects a very specific json input 4 | 5 | ### Synopsis 6 | 7 | write a folder of secrets. WARNING: command expects a very specific json input 8 | 9 | ``` 10 | vaku folder write [flags] 11 | ``` 12 | 13 | ### Examples 14 | 15 | ``` 16 | vaku folder write '{"a/b/c": {"foo": "bar"}}' 17 | ``` 18 | 19 | ### Options 20 | 21 | ``` 22 | -h, --help help for write 23 | ``` 24 | 25 | ### Options inherited from parent commands 26 | 27 | ``` 28 | -p, --absolute-path show absolute path in output 29 | -a, --address string address of the Vault server 30 | --destination-address string address of the destination Vault server 31 | --destination-namespace string name of the vault namespace to use in the destination client 32 | --destination-token string token for the destination vault server (alias for --token) 33 | --format string output format: text|json (default "text") 34 | --ignore-read-errors ignore path read errors and continue 35 | -i, --indent-char string string used for indents (default " ") 36 | -n, --namespace string name of the vault namespace to use in the source client 37 | -s, --sort sort output text (default true) 38 | --source-address string address of the source Vault server (alias for --address) 39 | --source-namespace string name of the vault namespace to use in the source client (alias for --namespace) 40 | --source-token string token for the source vault server (alias for --token) 41 | -t, --token string token for the vault server 42 | -w, --workers int number of concurrent workers (default 10) 43 | ``` 44 | 45 | ### SEE ALSO 46 | 47 | * [vaku folder](vaku_folder.md) - Commands that act on Vault folders 48 | 49 | -------------------------------------------------------------------------------- /docs/cli/vaku_path.md: -------------------------------------------------------------------------------- 1 | ## vaku path 2 | 3 | Commands that act on Vault paths 4 | 5 | ### Synopsis 6 | 7 | Commands that act on Vault paths 8 | 9 | Commands under the path subcommand act on Vault paths. Vaku can list, 10 | copy, move, search, etc.. on Vault paths. 11 | 12 | ### Examples 13 | 14 | ``` 15 | vaku path list secret/foo 16 | ``` 17 | 18 | ### Options 19 | 20 | ``` 21 | -p, --absolute-path show absolute path in output 22 | -a, --address string address of the Vault server 23 | --destination-address string address of the destination Vault server 24 | --destination-namespace string name of the vault namespace to use in the destination client 25 | --destination-token string token for the destination vault server (alias for --token) 26 | -h, --help help for path 27 | --ignore-read-errors ignore path read errors and continue 28 | -n, --namespace string name of the vault namespace to use in the source client 29 | --source-address string address of the source Vault server (alias for --address) 30 | --source-namespace string name of the vault namespace to use in the source client (alias for --namespace) 31 | --source-token string token for the source vault server (alias for --token) 32 | -t, --token string token for the vault server 33 | -w, --workers int number of concurrent workers (default 10) 34 | ``` 35 | 36 | ### Options inherited from parent commands 37 | 38 | ``` 39 | --format string output format: text|json (default "text") 40 | -i, --indent-char string string used for indents (default " ") 41 | -s, --sort sort output text (default true) 42 | ``` 43 | 44 | ### SEE ALSO 45 | 46 | * [vaku](vaku.md) - Vaku is a CLI for working with large Vault k/v secret engines 47 | * [vaku path copy](vaku_path_copy.md) - Copy a secret from a source path to a destination path 48 | * [vaku path delete](vaku_path_delete.md) - Delete a secret at a path 49 | * [vaku path delete-meta](vaku_path_delete-meta.md) - Delete all secret metadata and versions at a path. V2 engines only. 50 | * [vaku path list](vaku_path_list.md) - List all paths at a path 51 | * [vaku path move](vaku_path_move.md) - Move a secret from a source path to a destination path 52 | * [vaku path read](vaku_path_read.md) - Read a secret at a path 53 | * [vaku path search](vaku_path_search.md) - Search a secret for a search string 54 | 55 | -------------------------------------------------------------------------------- /docs/cli/vaku_path_copy.md: -------------------------------------------------------------------------------- 1 | ## vaku path copy 2 | 3 | Copy a secret from a source path to a destination path 4 | 5 | ### Synopsis 6 | 7 | Copy a secret from a source path to a destination path 8 | 9 | ``` 10 | vaku path copy [flags] 11 | ``` 12 | 13 | ### Examples 14 | 15 | ``` 16 | vaku path copy secret/foo secret/bar 17 | ``` 18 | 19 | ### Options 20 | 21 | ``` 22 | -h, --help help for copy 23 | ``` 24 | 25 | ### Options inherited from parent commands 26 | 27 | ``` 28 | -p, --absolute-path show absolute path in output 29 | -a, --address string address of the Vault server 30 | --destination-address string address of the destination Vault server 31 | --destination-namespace string name of the vault namespace to use in the destination client 32 | --destination-token string token for the destination vault server (alias for --token) 33 | --format string output format: text|json (default "text") 34 | --ignore-read-errors ignore path read errors and continue 35 | -i, --indent-char string string used for indents (default " ") 36 | -n, --namespace string name of the vault namespace to use in the source client 37 | -s, --sort sort output text (default true) 38 | --source-address string address of the source Vault server (alias for --address) 39 | --source-namespace string name of the vault namespace to use in the source client (alias for --namespace) 40 | --source-token string token for the source vault server (alias for --token) 41 | -t, --token string token for the vault server 42 | -w, --workers int number of concurrent workers (default 10) 43 | ``` 44 | 45 | ### SEE ALSO 46 | 47 | * [vaku path](vaku_path.md) - Commands that act on Vault paths 48 | 49 | -------------------------------------------------------------------------------- /docs/cli/vaku_path_delete-meta.md: -------------------------------------------------------------------------------- 1 | ## vaku path delete-meta 2 | 3 | Delete all secret metadata and versions at a path. V2 engines only. 4 | 5 | ### Synopsis 6 | 7 | Delete all secret metadata and versions at a path. V2 engines only. 8 | 9 | ``` 10 | vaku path delete-meta [flags] 11 | ``` 12 | 13 | ### Examples 14 | 15 | ``` 16 | vaku path delete-meta secret/foo 17 | ``` 18 | 19 | ### Options 20 | 21 | ``` 22 | -h, --help help for delete-meta 23 | ``` 24 | 25 | ### Options inherited from parent commands 26 | 27 | ``` 28 | -p, --absolute-path show absolute path in output 29 | -a, --address string address of the Vault server 30 | --destination-address string address of the destination Vault server 31 | --destination-namespace string name of the vault namespace to use in the destination client 32 | --destination-token string token for the destination vault server (alias for --token) 33 | --format string output format: text|json (default "text") 34 | --ignore-read-errors ignore path read errors and continue 35 | -i, --indent-char string string used for indents (default " ") 36 | -n, --namespace string name of the vault namespace to use in the source client 37 | -s, --sort sort output text (default true) 38 | --source-address string address of the source Vault server (alias for --address) 39 | --source-namespace string name of the vault namespace to use in the source client (alias for --namespace) 40 | --source-token string token for the source vault server (alias for --token) 41 | -t, --token string token for the vault server 42 | -w, --workers int number of concurrent workers (default 10) 43 | ``` 44 | 45 | ### SEE ALSO 46 | 47 | * [vaku path](vaku_path.md) - Commands that act on Vault paths 48 | 49 | -------------------------------------------------------------------------------- /docs/cli/vaku_path_delete.md: -------------------------------------------------------------------------------- 1 | ## vaku path delete 2 | 3 | Delete a secret at a path 4 | 5 | ### Synopsis 6 | 7 | Delete a secret at a path 8 | 9 | ``` 10 | vaku path delete [flags] 11 | ``` 12 | 13 | ### Examples 14 | 15 | ``` 16 | vaku path delete secret/foo 17 | ``` 18 | 19 | ### Options 20 | 21 | ``` 22 | -h, --help help for delete 23 | ``` 24 | 25 | ### Options inherited from parent commands 26 | 27 | ``` 28 | -p, --absolute-path show absolute path in output 29 | -a, --address string address of the Vault server 30 | --destination-address string address of the destination Vault server 31 | --destination-namespace string name of the vault namespace to use in the destination client 32 | --destination-token string token for the destination vault server (alias for --token) 33 | --format string output format: text|json (default "text") 34 | --ignore-read-errors ignore path read errors and continue 35 | -i, --indent-char string string used for indents (default " ") 36 | -n, --namespace string name of the vault namespace to use in the source client 37 | -s, --sort sort output text (default true) 38 | --source-address string address of the source Vault server (alias for --address) 39 | --source-namespace string name of the vault namespace to use in the source client (alias for --namespace) 40 | --source-token string token for the source vault server (alias for --token) 41 | -t, --token string token for the vault server 42 | -w, --workers int number of concurrent workers (default 10) 43 | ``` 44 | 45 | ### SEE ALSO 46 | 47 | * [vaku path](vaku_path.md) - Commands that act on Vault paths 48 | 49 | -------------------------------------------------------------------------------- /docs/cli/vaku_path_list.md: -------------------------------------------------------------------------------- 1 | ## vaku path list 2 | 3 | List all paths at a path 4 | 5 | ### Synopsis 6 | 7 | List all paths at a path 8 | 9 | ``` 10 | vaku path list [flags] 11 | ``` 12 | 13 | ### Examples 14 | 15 | ``` 16 | vaku path list secret/foo 17 | ``` 18 | 19 | ### Options 20 | 21 | ``` 22 | -h, --help help for list 23 | ``` 24 | 25 | ### Options inherited from parent commands 26 | 27 | ``` 28 | -p, --absolute-path show absolute path in output 29 | -a, --address string address of the Vault server 30 | --destination-address string address of the destination Vault server 31 | --destination-namespace string name of the vault namespace to use in the destination client 32 | --destination-token string token for the destination vault server (alias for --token) 33 | --format string output format: text|json (default "text") 34 | --ignore-read-errors ignore path read errors and continue 35 | -i, --indent-char string string used for indents (default " ") 36 | -n, --namespace string name of the vault namespace to use in the source client 37 | -s, --sort sort output text (default true) 38 | --source-address string address of the source Vault server (alias for --address) 39 | --source-namespace string name of the vault namespace to use in the source client (alias for --namespace) 40 | --source-token string token for the source vault server (alias for --token) 41 | -t, --token string token for the vault server 42 | -w, --workers int number of concurrent workers (default 10) 43 | ``` 44 | 45 | ### SEE ALSO 46 | 47 | * [vaku path](vaku_path.md) - Commands that act on Vault paths 48 | 49 | -------------------------------------------------------------------------------- /docs/cli/vaku_path_move.md: -------------------------------------------------------------------------------- 1 | ## vaku path move 2 | 3 | Move a secret from a source path to a destination path 4 | 5 | ### Synopsis 6 | 7 | Move a secret from a source path to a destination path 8 | 9 | ``` 10 | vaku path move [flags] 11 | ``` 12 | 13 | ### Examples 14 | 15 | ``` 16 | vaku path move secret/foo secret/bar 17 | ``` 18 | 19 | ### Options 20 | 21 | ``` 22 | -h, --help help for move 23 | ``` 24 | 25 | ### Options inherited from parent commands 26 | 27 | ``` 28 | -p, --absolute-path show absolute path in output 29 | -a, --address string address of the Vault server 30 | --destination-address string address of the destination Vault server 31 | --destination-namespace string name of the vault namespace to use in the destination client 32 | --destination-token string token for the destination vault server (alias for --token) 33 | --format string output format: text|json (default "text") 34 | --ignore-read-errors ignore path read errors and continue 35 | -i, --indent-char string string used for indents (default " ") 36 | -n, --namespace string name of the vault namespace to use in the source client 37 | -s, --sort sort output text (default true) 38 | --source-address string address of the source Vault server (alias for --address) 39 | --source-namespace string name of the vault namespace to use in the source client (alias for --namespace) 40 | --source-token string token for the source vault server (alias for --token) 41 | -t, --token string token for the vault server 42 | -w, --workers int number of concurrent workers (default 10) 43 | ``` 44 | 45 | ### SEE ALSO 46 | 47 | * [vaku path](vaku_path.md) - Commands that act on Vault paths 48 | 49 | -------------------------------------------------------------------------------- /docs/cli/vaku_path_read.md: -------------------------------------------------------------------------------- 1 | ## vaku path read 2 | 3 | Read a secret at a path 4 | 5 | ### Synopsis 6 | 7 | Read a secret at a path 8 | 9 | ``` 10 | vaku path read [flags] 11 | ``` 12 | 13 | ### Examples 14 | 15 | ``` 16 | vaku path read secret/foo 17 | ``` 18 | 19 | ### Options 20 | 21 | ``` 22 | -h, --help help for read 23 | ``` 24 | 25 | ### Options inherited from parent commands 26 | 27 | ``` 28 | -p, --absolute-path show absolute path in output 29 | -a, --address string address of the Vault server 30 | --destination-address string address of the destination Vault server 31 | --destination-namespace string name of the vault namespace to use in the destination client 32 | --destination-token string token for the destination vault server (alias for --token) 33 | --format string output format: text|json (default "text") 34 | --ignore-read-errors ignore path read errors and continue 35 | -i, --indent-char string string used for indents (default " ") 36 | -n, --namespace string name of the vault namespace to use in the source client 37 | -s, --sort sort output text (default true) 38 | --source-address string address of the source Vault server (alias for --address) 39 | --source-namespace string name of the vault namespace to use in the source client (alias for --namespace) 40 | --source-token string token for the source vault server (alias for --token) 41 | -t, --token string token for the vault server 42 | -w, --workers int number of concurrent workers (default 10) 43 | ``` 44 | 45 | ### SEE ALSO 46 | 47 | * [vaku path](vaku_path.md) - Commands that act on Vault paths 48 | 49 | -------------------------------------------------------------------------------- /docs/cli/vaku_path_search.md: -------------------------------------------------------------------------------- 1 | ## vaku path search 2 | 3 | Search a secret for a search string 4 | 5 | ### Synopsis 6 | 7 | Search a secret for a search string 8 | 9 | ``` 10 | vaku path search [flags] 11 | ``` 12 | 13 | ### Examples 14 | 15 | ``` 16 | vaku path search secret/foo bar 17 | ``` 18 | 19 | ### Options 20 | 21 | ``` 22 | -h, --help help for search 23 | ``` 24 | 25 | ### Options inherited from parent commands 26 | 27 | ``` 28 | -p, --absolute-path show absolute path in output 29 | -a, --address string address of the Vault server 30 | --destination-address string address of the destination Vault server 31 | --destination-namespace string name of the vault namespace to use in the destination client 32 | --destination-token string token for the destination vault server (alias for --token) 33 | --format string output format: text|json (default "text") 34 | --ignore-read-errors ignore path read errors and continue 35 | -i, --indent-char string string used for indents (default " ") 36 | -n, --namespace string name of the vault namespace to use in the source client 37 | -s, --sort sort output text (default true) 38 | --source-address string address of the source Vault server (alias for --address) 39 | --source-namespace string name of the vault namespace to use in the source client (alias for --namespace) 40 | --source-token string token for the source vault server (alias for --token) 41 | -t, --token string token for the vault server 42 | -w, --workers int number of concurrent workers (default 10) 43 | ``` 44 | 45 | ### SEE ALSO 46 | 47 | * [vaku path](vaku_path.md) - Commands that act on Vault paths 48 | 49 | -------------------------------------------------------------------------------- /docs/cli/vaku_path_write.md: -------------------------------------------------------------------------------- 1 | ## vaku path write 2 | 3 | Write all paths at a path 4 | 5 | ### Synopsis 6 | 7 | Write all paths at a path 8 | 9 | ``` 10 | vaku path write [flags] 11 | ``` 12 | 13 | ### Examples 14 | 15 | ``` 16 | vaku path write secret/foo 17 | ``` 18 | 19 | ### Options 20 | 21 | ``` 22 | -h, --help help for write 23 | ``` 24 | 25 | ### Options inherited from parent commands 26 | 27 | ``` 28 | -p, --absolute-path show absolute path in output 29 | -a, --address string address of the Vault server 30 | --destination-address string address of the destination Vault server 31 | --destination-token string token for the destination vault server (alias for --token) 32 | --format string output format: text|json (default "text") 33 | -i, --indent-char string string used for indents (default " ") 34 | -s, --sort sort output text (default true) 35 | --source-address string address of the source Vault server (alias for --address) 36 | --source-token string token for the source vault server (alias for --token) 37 | -t, --token string token for the vault server 38 | -w, --workers int number of concurrent workers (default 10) 39 | ``` 40 | 41 | ### SEE ALSO 42 | 43 | * [vaku path](vaku_path.md) - Commands that act on Vault paths 44 | 45 | -------------------------------------------------------------------------------- /docs/cli/vaku_version.md: -------------------------------------------------------------------------------- 1 | ## vaku version 2 | 3 | Print vaku version 4 | 5 | ``` 6 | vaku version 7 | ``` 8 | 9 | ### Examples 10 | 11 | ``` 12 | vaku version 13 | ``` 14 | 15 | ### Options 16 | 17 | ``` 18 | -h, --help help for version 19 | ``` 20 | 21 | ### Options inherited from parent commands 22 | 23 | ``` 24 | --format string output format: text|json (default "text") 25 | -i, --indent-char string string used for indents (default " ") 26 | -s, --sort sort output text (default true) 27 | ``` 28 | 29 | ### SEE ALSO 30 | 31 | * [vaku](vaku.md) - Vaku is a CLI for working with large Vault k/v secret engines 32 | 33 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lingrino/vaku/v2 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/hashicorp/go-hclog v1.6.3 7 | github.com/hashicorp/vault/api v1.16.0 8 | github.com/hashicorp/vault/sdk v0.15.2 9 | github.com/spf13/cobra v1.9.1 10 | github.com/stretchr/testify v1.10.0 11 | golang.org/x/sync v0.12.0 12 | ) 13 | 14 | require ( 15 | github.com/Microsoft/go-winio v0.6.2 // indirect 16 | github.com/cenkalti/backoff/v3 v3.2.2 // indirect 17 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 18 | github.com/containerd/log v0.1.0 // indirect 19 | github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect 20 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 21 | github.com/distribution/reference v0.6.0 // indirect 22 | github.com/docker/docker v27.3.1+incompatible // indirect 23 | github.com/docker/go-connections v0.5.0 // indirect 24 | github.com/docker/go-units v0.5.0 // indirect 25 | github.com/fatih/color v1.17.0 // indirect 26 | github.com/felixge/httpsnoop v1.0.4 // indirect 27 | github.com/frankban/quicktest v1.14.6 // indirect 28 | github.com/go-jose/go-jose/v4 v4.0.5 // indirect 29 | github.com/go-logr/logr v1.4.2 // indirect 30 | github.com/go-logr/stdr v1.2.2 // indirect 31 | github.com/gogo/protobuf v1.3.2 // indirect 32 | github.com/golang/snappy v0.0.4 // indirect 33 | github.com/hashicorp/errwrap v1.1.0 // indirect 34 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 35 | github.com/hashicorp/go-multierror v1.1.1 // indirect 36 | github.com/hashicorp/go-retryablehttp v0.7.7 // indirect 37 | github.com/hashicorp/go-rootcerts v1.0.2 // indirect 38 | github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9 // indirect 39 | github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect 40 | github.com/hashicorp/go-sockaddr v1.0.6 // indirect 41 | github.com/hashicorp/go-uuid v1.0.3 // indirect 42 | github.com/hashicorp/hcl v1.0.1-vault-5 // indirect 43 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 44 | github.com/klauspost/compress v1.17.11 // indirect 45 | github.com/mattn/go-colorable v0.1.13 // indirect 46 | github.com/mattn/go-isatty v0.0.20 // indirect 47 | github.com/mitchellh/go-homedir v1.1.0 // indirect 48 | github.com/mitchellh/mapstructure v1.5.0 // indirect 49 | github.com/moby/docker-image-spec v1.3.1 // indirect 50 | github.com/moby/patternmatcher v0.6.0 // indirect 51 | github.com/moby/sys/sequential v0.5.0 // indirect 52 | github.com/moby/sys/user v0.2.0 // indirect 53 | github.com/moby/sys/userns v0.1.0 // indirect 54 | github.com/moby/term v0.5.0 // indirect 55 | github.com/morikuni/aec v1.0.0 // indirect 56 | github.com/natefinch/atomic v1.0.1 // indirect 57 | github.com/opencontainers/go-digest v1.0.0 // indirect 58 | github.com/opencontainers/image-spec v1.1.0 // indirect 59 | github.com/pierrec/lz4 v2.6.1+incompatible // indirect 60 | github.com/pkg/errors v0.9.1 // indirect 61 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 62 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 63 | github.com/ryanuber/go-glob v1.0.0 // indirect 64 | github.com/sirupsen/logrus v1.9.3 // indirect 65 | github.com/spf13/pflag v1.0.6 // indirect 66 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect 67 | go.opentelemetry.io/otel v1.31.0 // indirect 68 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 // indirect 69 | go.opentelemetry.io/otel/metric v1.31.0 // indirect 70 | go.opentelemetry.io/otel/sdk v1.31.0 // indirect 71 | go.opentelemetry.io/otel/trace v1.31.0 // indirect 72 | go.uber.org/atomic v1.11.0 // indirect 73 | golang.org/x/crypto v0.36.0 // indirect 74 | golang.org/x/net v0.38.0 // indirect 75 | golang.org/x/sys v0.31.0 // indirect 76 | golang.org/x/text v0.23.0 // indirect 77 | golang.org/x/time v0.9.0 // indirect 78 | google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f // indirect 79 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 80 | gopkg.in/yaml.v3 v3.0.1 // indirect 81 | gotest.tools/v3 v3.5.1 // indirect 82 | ) 83 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/lingrino/vaku/v2/cmd" 7 | ) 8 | 9 | // version is populated at build time by goreleaser. 10 | var version = "dev" 11 | 12 | // used for testing. 13 | var executeCMD = cmd.Execute 14 | var exitCmd = os.Exit 15 | 16 | func main() { 17 | code := executeCMD(version, os.Args[1:], os.Stdout, os.Stderr) 18 | exitCmd(code) 19 | } 20 | -------------------------------------------------------------------------------- /main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestMain(t *testing.T) { 11 | t.Parallel() 12 | 13 | executeCMD = func(v string, args []string, outW, err io.Writer) int { return 0 } 14 | exitCmd = func(i int) {} 15 | assert.NotPanics(t, func() { main() }) 16 | } 17 | -------------------------------------------------------------------------------- /www/assets/css/style.css: -------------------------------------------------------------------------------- 1 | /* Custom Styles Go Here */ 2 | 3 | body { 4 | font-size: 1.25rem; 5 | } 6 | -------------------------------------------------------------------------------- /www/assets/css/water/dark.min.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8";body{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;line-height:1.4;max-width:800px;margin:20px auto;padding:0 10px;color:#dbdbdb;background:#202b38;text-rendering:optimizeLegibility}button,input,textarea{transition:background-color .1s linear,border-color .1s linear,color .1s linear,box-shadow .1s linear,transform .1s ease}h1{font-size:2.2em;margin-top:0}h1,h2,h3,h4,h5,h6{margin-bottom:12px}h1,h2,h3,h4,h5,h6,strong{color:#fff}b,h1,h2,h3,h4,h5,h6,strong,th{font-weight:600}blockquote{border-left:4px solid rgba(0,150,191,.67);margin:1.5em 0;padding:.5em 1em;font-style:italic}blockquote>footer{margin-top:10px;font-style:normal}address,blockquote cite{font-style:normal}a[href^=mailto]:before{content:"📧 "}a[href^=tel]:before{content:"📞 "}a[href^=sms]:before{content:"💬 "}button,input[type=button],input[type=checkbox],input[type=submit]{cursor:pointer}input:not([type=checkbox]):not([type=radio]),select{display:block}button,input,select,textarea{color:#fff;background-color:#161f27;font-family:inherit;font-size:inherit;margin-right:6px;margin-bottom:6px;padding:10px;border:none;border-radius:6px;outline:none}button,input:not([type=checkbox]):not([type=radio]),select,textarea{-webkit-appearance:none}textarea{margin-right:0;width:100%;box-sizing:border-box;resize:vertical}button,input[type=button],input[type=submit]{padding-right:30px;padding-left:30px}button:hover,input[type=button]:hover,input[type=submit]:hover{background:#324759}button:focus,input:focus,select:focus,textarea:focus{box-shadow:0 0 0 2px rgba(0,150,191,.67)}button:active,input[type=button]:active,input[type=checkbox]:active,input[type=radio]:active,input[type=submit]:active{transform:translateY(2px)}button:disabled,input:disabled,select:disabled,textarea:disabled{cursor:not-allowed;opacity:.5}::-webkit-input-placeholder{color:#a9a9a9}:-ms-input-placeholder{color:#a9a9a9}::-ms-input-placeholder{color:#a9a9a9}::placeholder{color:#a9a9a9}a{text-decoration:none;color:#41adff}a:hover{text-decoration:underline}code,kbd{background:#161f27;color:#ffbe85;padding:5px;border-radius:6px}pre>code{padding:10px;display:block;overflow-x:auto}img{max-width:100%}hr{border:none;border-top:1px solid #dbdbdb}table{border-collapse:collapse;margin-bottom:10px;width:100%}td,th{padding:6px;text-align:left}th{border-bottom:1px solid #dbdbdb}tbody tr:nth-child(2n){background-color:#161f27}::-webkit-scrollbar{height:10px;width:10px}::-webkit-scrollbar-track{background:#161f27;border-radius:6px}::-webkit-scrollbar-thumb{background:#324759;border-radius:6px}::-webkit-scrollbar-thumb:hover{background:#415c73} 2 | /*# sourceMappingURL=dark.min.css.map */ 3 | -------------------------------------------------------------------------------- /www/assets/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingrino/vaku/b9f104688be59db59843669e6c82546295bf07f2/www/assets/icons/favicon-16x16.png -------------------------------------------------------------------------------- /www/assets/icons/favicon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingrino/vaku/b9f104688be59db59843669e6c82546295bf07f2/www/assets/icons/favicon-180x180.png -------------------------------------------------------------------------------- /www/assets/icons/favicon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingrino/vaku/b9f104688be59db59843669e6c82546295bf07f2/www/assets/icons/favicon-192x192.png -------------------------------------------------------------------------------- /www/assets/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingrino/vaku/b9f104688be59db59843669e6c82546295bf07f2/www/assets/icons/favicon-32x32.png -------------------------------------------------------------------------------- /www/assets/icons/favicon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingrino/vaku/b9f104688be59db59843669e6c82546295bf07f2/www/assets/icons/favicon-512x512.png -------------------------------------------------------------------------------- /www/assets/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingrino/vaku/b9f104688be59db59843669e6c82546295bf07f2/www/assets/icons/favicon.ico -------------------------------------------------------------------------------- /www/assets/images/logo-vaku-sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingrino/vaku/b9f104688be59db59843669e6c82546295bf07f2/www/assets/images/logo-vaku-sm.png -------------------------------------------------------------------------------- /www/assets/images/logo-vaku-sm.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingrino/vaku/b9f104688be59db59843669e6c82546295bf07f2/www/assets/images/logo-vaku-sm.webp -------------------------------------------------------------------------------- /www/assets/images/logo-vaku.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingrino/vaku/b9f104688be59db59843669e6c82546295bf07f2/www/assets/images/logo-vaku.png -------------------------------------------------------------------------------- /www/assets/images/logo-vaku.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lingrino/vaku/b9f104688be59db59843669e6c82546295bf07f2/www/assets/images/logo-vaku.webp -------------------------------------------------------------------------------- /www/assets/site/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vaku", 3 | "short_name": "vaku", 4 | "description": "Vaku Website", 5 | "display": "standalone", 6 | "theme_color": "#93d3e7", 7 | "background_color": "#7e8898", 8 | "icons": [ 9 | { 10 | "src": "assets/icons/favicon-192x192.png", 11 | "sizes": "192x192", 12 | "type": "image/png" 13 | }, 14 | { 15 | "src": "assets/icons/favicon-512x512.png", 16 | "sizes": "512x512", 17 | "type": "image/png" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /www/fetch-assets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Pulls the latest css from remote sources 4 | curl https://raw.githubusercontent.com/kognise/water.css/HEAD/dist/dark.min.css -o ./www/assets/css/water/dark.min.css 5 | curl https://raw.githubusercontent.com/kognise/water.css/HEAD/dist/dark.min.css.map -o ./www/assets/css/water/dark.min.css.map 6 | -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 24 | 33 | 34 | 39 | 43 | 44 | 48 | 49 | 50 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | Vaku 62 | 63 | 64 | 69 | 70 | 71 | 72 | 73 | 74 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 |
88 |

Vaku

89 | 90 | 91 | 92 | Vaku Logo 93 | 94 |

brew install lingrino/tap/vaku

95 |

96 | Vaku is a CLI and Go API that extends the official Hashicorp Vault CLI and API with useful 97 | high-level functions such as the ability to copy, move, and search Vault paths and folders. 98 | The CLI and API are purpose-built to deal with the actual secret content of paths and 99 | folders and they therefore do not make use of or reveal Vault features like metadata and 100 | wrapping. Use the tools as you see fit, and please file issues for anything you think could 101 | be improved. Thanks! 102 |

103 | GitHub 105 |  •  106 | CLI Docs 108 |  •  109 | API Docs 111 |  •  Built By 112 | Sean Lingren 114 |
115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /www/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | --------------------------------------------------------------------------------