├── .github ├── CODEOWNERS └── workflows │ └── ci.yml ├── .gitignore ├── .goreleaser.yml ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── OLDREADME.md ├── README.md ├── aws ├── aws.go └── aws_test.go ├── cache └── cache.go ├── cli ├── assume_role.go ├── exec.go ├── exec_test.go └── login.go ├── cmd ├── list_roles.go ├── print_credentials.go ├── root.go └── shim.go ├── code-of-conduct.md ├── format ├── format.go └── format_test.go ├── go.mod ├── go.sum ├── main.go ├── okta └── okta.go ├── saml ├── saml.go └── saml_test.go └── static ├── completions ├── yak.bash └── yak.zsh └── delivery-engineers.pub.asc /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @redbubble/delivery-reliability-engineering 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: tests 3 | on: [ push, pull_request ] 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - uses: actions/setup-go@v2 10 | with: 11 | go-version: '^1.19.1' 12 | - run: make fmt && git diff --exit-code 13 | - run: GO111MODULE=off go get gotest.tools/gotestsum 14 | - run: make test 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/* 2 | bin/* 3 | yak 4 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | dist: ./bin 2 | builds: 3 | - binary: yak 4 | goos: 5 | - darwin 6 | - linux 7 | - windows 8 | goarch: 9 | - amd64 10 | - arm64 11 | archives: 12 | - format: tar.gz 13 | files: 14 | - LICENSE 15 | - README.md 16 | - static/completions/* 17 | nfpms: 18 | - description: A tool to log in to AWS through Okta 19 | maintainer: Redbubble 20 | formats: 21 | - deb 22 | - rpm 23 | license: MIT 24 | contents: 25 | - src: "static/completions/yak.zsh" 26 | dst : "/usr/share/zsh/vendor-completions/_yak" 27 | - src: "static/completions/yak.bash" 28 | dst: "/usr/share/bash-completion/completions/yak" 29 | brews: 30 | - tap: 31 | owner: redbubble 32 | name: homebrew-redbubble 33 | commit_author: 34 | name: Redbubble Delivery Engineering Team 35 | email: delivery-engineers@redbubble.com 36 | 37 | folder: Formula 38 | homepage: https://github.com/redbubble/yak 39 | description: A tool to log in to AWS through Okta 40 | dependencies: 41 | - name: pinentry-mac 42 | 43 | test: | 44 | system "#{bin}/yak --help" 45 | install: | 46 | bin.install "yak" 47 | bash_completion.install "static/completions/yak.bash" => "yak" 48 | zsh_completion.install "static/completions/yak.zsh" => "_yak" 49 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `yak` 2 | 3 | ## Issues 4 | 5 | If you encounter any unexpected or undesirable behaviour when using `yak`, please let us know by creating a 6 | [GitHub issue](https://github.com/redbubble/yak/issues); we really appreciate knowing about any problems! 7 | 8 | When reporting an issue, please also include: 9 | * The version of `yak` you're using (this can be found with `yak --version`) 10 | * Your operating system and version 11 | 12 | Before posting your issue, though, please search the issue database for duplicates. If you find that an issue already 13 | exists for your bug, but you have extra information, please leave a comment on that issue letting us know. 14 | 15 | ## Pull Requests 16 | 17 | We always welcome pull requests! If you see something you'd like to improve in `yak`, even if it's as small as a typo, 18 | we'd love to see contributions from anybody. 19 | 20 | If you're going to make a significant change or improvement to `yak`, please document that change as a 21 | [GitHub issue](https://github.com/redbubble/yak/issues) first to allow for discussion with the maintainers. 22 | 23 | ## Releases 24 | 25 | At this stage, there is no concrete schedule for releases of `yak`. 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Redbubble 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION ?= 1.6.5 2 | GIT_HASH = $(shell git rev-parse --short HEAD) 3 | DELIVERY_ENGINEERING_GPG_KEY = 0x877817E441F4F9B0 4 | 5 | .PHONY: test install 6 | 7 | test: 8 | command -v gotestsum || go install gotest.tools/gotestsum@latest 9 | gotestsum 10 | 11 | fmt: 12 | go fmt ./... 13 | 14 | install: 15 | go install -ldflags "-X main.version=${VERSION}-${GIT_HASH}-dev" 16 | 17 | release: 18 | -git tag -a "v${VERSION}" -m "Releasing version ${VERSION}" 19 | git push --tags 20 | goreleaser --rm-dist 21 | 22 | release-deb: 23 | deb-s3 upload --s3-region=ap-southeast-2 --bucket=apt.redbubble.com --sign=${DELIVERY_ENGINEERING_GPG_KEY} bin/yak_${VERSION}_linux_amd64.deb bin/yak_${VERSION}_linux_arm64.deb 24 | -------------------------------------------------------------------------------- /OLDREADME.md: -------------------------------------------------------------------------------- 1 | # yak 2 | 3 | [![Build Status](https://github.com/redbubble/yak/actions/workflows/ci.yml/badge.svg)](https://github.com/redbubble/yak/actions) 4 | 5 | A tool to generate access keys for AWS using Okta. If you want a backronym, try 'Your AWS Kredentials'. 6 | 7 | ## Usage 8 | 9 | ### Installation 10 | 11 | We produce builds of `yak` for macOS and Linux. 12 | 13 | #### macOS with Homebrew 14 | 15 | The easiest option for macOS users is to install `yak` via Homebrew. 16 | This will also help keep `yak` up-to-date when you run `brew upgrade` 17 | as usual. 18 | 19 | ```sh 20 | brew tap redbubble/redbubble 21 | brew install yak 22 | ``` 23 | 24 | This will also put ZSH and Bash completions in the right spot; they 25 | should be usable next time you reload your shell config. 26 | 27 | #### Ubuntu/Debian APT repository 28 | 29 | `yak` can be installed from our APT repo. This should get you up and 30 | running: 31 | 32 | ```sh 33 | sudo apt install curl gnupg2 34 | # This is the Redbubble GPG key, to verify releases: 35 | curl -Lq https://raw.githubusercontent.com/redbubble/yak/master/static/delivery-engineers.pub.asc | sudo gpg --no-default-keyring --import --keyring gnupg-ring:/etc/apt/trusted.gpg.d/redbubble.gpg 36 | sudo chmod a+r /etc/apt/trusted.gpg.d/redbubble.gpg 37 | echo "deb http://apt.redbubble.com/ stable main" | sudo tee /etc/apt/sources.list.d/redbubble.list 38 | sudo apt update 39 | sudo apt install yak 40 | ``` 41 | 42 | #### Standalone DEB/RPM packages 43 | 44 | We generate Deb and RPM packages as part of our release. 45 | 46 | Download the package appropriate for your distro from the [latest 47 | release](https://github.com/redbubble/yak/releases/latest) page. 48 | Unfortunately, this won't give you nice automatic updates. 49 | 50 | #### A note about completions 51 | 52 | We've seen issues using tab-completion on older versions of ZSH. It seems 53 | that version 5.1 or newer will work correctly. 54 | 55 | #### Manually 56 | 57 | Download the [latest release](https://github.com/redbubble/yak/releases/latest) for your architecture. The `yak` executable is statically linked, 58 | so all you should need to do is put the executable somewhere in your `$PATH`. 59 | 60 | This method will not give you tab-completion; if you'd like that, the completions files are available in 61 | [/static/completions](https://github.com/redbubble/yak/tree/master/static/completions). 62 | 63 | ### Running 64 | 65 | You can run `yak` like this: 66 | 67 | ``` 68 | yak [] 69 | ``` 70 | 71 | and will run `command` as `role`. 72 | 73 | More specifically, `yak` runs `command` in the same environment it was called from, with the credentials for `role` 74 | injected as environment variables. 75 | 76 | When run without a command, `yak` prints those variables as `export` statements; this is intended to allow easy sourcing 77 | into your shell. 78 | 79 | If run with the `--list-roles` flag like this: 80 | 81 | ``` 82 | yak --list-roles 83 | ``` 84 | 85 | `yak` will print a list of available roles and exit. 86 | 87 | Note that to pass `-/--` flags to commands you want to run, you'll need to put a `--` before the 88 | ``, to let `yak` know you're done passing flags to *it*, like this: 89 | 90 | ``` 91 | yak [flags] -- 92 | ``` 93 | 94 | For example: 95 | 96 | ``` 97 | yak --cache-only nonprod -- npx cdk --app 'npx ts-node --prefer-ts-exts bin/my-stack.ts' list 98 | ``` 99 | 100 | 101 | #### Arguments 102 | 103 | ``` 104 | -d, --aws-session-duration int The session duration to request from AWS (in seconds) 105 | --cache-only Only use cache, do not make external requests. Mutually exclusive with --no-cache 106 | --clear-cache Delete all data from yak's cache. If no other arguments are given, exit without error 107 | -h, --help Display this help message and exit 108 | -l, --list-roles List available AWS roles and exit 109 | --no-cache Ignore cache for this request. Mutually exclusive with --cache-only 110 | --okta-aws-saml-endpoint string The app embed path for the AWS app within Okta 111 | --okta-domain string The domain to use for requests to Okta 112 | --okta-mfa-provider string The Okta MFA provider name for login 113 | --okta-mfa-type string The Okta MFA type for login 114 | -u, --okta-username string Your Okta username 115 | -o, --output-format string Can be set to either 'json' or 'env'. The format in which to output credential data 116 | --pinentry Use the pinentry to prompt for credentials, instead of terminal (useful for GUI applications) 117 | --version Print the current version and exit 118 | -- Terminator for -/-- flags. Necessary if you want to pass -/-- flags to commands 119 | ``` 120 | 121 | #### Environment Variables 122 | 123 | | Variable | Effect | 124 | |-----------------|--------------------------------------------------------------------------------------------| 125 | | `OKTA_PASSWORD` | The value set in this variable will be passed to Okta as the 'password' component of login | 126 | 127 | Please note that setting the `OKTA_PASSWORD` variable in plain text, especially on the command-line, is not a good idea 128 | from a security perspective. A suggested mode of use for this variable would be something like: 129 | 130 | ``` 131 | OKTA_PASSWORD=$(get-password-from-password-manager) yak ... 132 | ``` 133 | 134 | ### Configuring 135 | 136 | Yak can be configured with a configuration file at `~/.config/yak/config.toml` (`~/.yak/config.toml` is also supported). 137 | 138 | #### Okta Config 139 | 140 | ```toml 141 | [okta] 142 | # Required. The URL for your okta domain. 143 | domain = "https://.okta.com" 144 | 145 | # Required. The path for fetching the SAML assertion from okta. 146 | aws_saml_endpoint = "/home///" 147 | 148 | # Optional. Your okta username. 149 | username = "" 150 | 151 | # Optional. Your okta MFA device type and provider so that you don't have to choose. 152 | # Yak supports the following values for mfa_type: token:software:totp, token:hardware or push 153 | # For a full list of Okta-supported factors and providers see [this page](https://developer.okta.com/docs/api/resources/factors#supported-factors-for-providers) 154 | mfa_type = "" 155 | mfa_provider = "" 156 | ``` 157 | 158 | ##### How to find your config values 159 | 160 | `domain`: This the same domain where you log in to Okta. 161 | 162 | `aws_saml_endpoint`: To get this value, you'll need to: 163 | 164 | 1. Log in to Okta 165 | 2. Find the AWS application 166 | 3. Copy the URL for the AWS application, e.g. by right-clicking and selecting 167 | "Copy Link Address" or similar 168 | 4. Remove everything up to `okta.com/` (inclusive) 169 | 5. Remove everything from the `?` onwards 170 | 171 | OR ask your organisation's Okta administrator. 172 | 173 | If you're an Okta administrator, you can also: 174 | 175 | 1. Log in to Okta 176 | 2. Click the "Admin" button 177 | 3. Navigate to Applications 178 | 4. Open the "Amazon Web Services" application 179 | 5. On the General tab, copy the App Embed Link 180 | 6. Remove everything up to `okta.com/` (inclusive) 181 | 182 | `username`: The username you use when logging in to Okta. If in doubt, consult 183 | your organisation's Okta administrator. 184 | 185 | #### AWS Config 186 | 187 | ```toml 188 | [aws] 189 | # Optional. Duration in seconds for the AWS credentials to last. Default 1 hour, maximum 12 hours. 190 | session_duration = 3600 191 | ``` 192 | 193 | #### Other Config 194 | 195 | ```toml 196 | [login] 197 | # Optional. Duration in seconds from the start of the login process until it times out. 198 | timeout = 180 199 | ``` 200 | 201 | ```toml 202 | # Optional. Prompt for password and MFA token using pinentry. Useful for when using GUI tools like Lens. 203 | pinentry = true 204 | ``` 205 | 206 | #### Aliases 207 | 208 | You can configure *role aliases* in the `[alias]` section of your config file; these can be used instead of having to 209 | remember the whole ARN: 210 | 211 | ```toml 212 | [alias] 213 | prod = "arn:aws:some:long:role:path" 214 | ``` 215 | 216 | This configuration would allow you to log in with: 217 | ``` 218 | yak prod [] 219 | ``` 220 | 221 | ## Development 222 | 223 | To hack on `yak`, you'll want to get a copy of the source. Then: 224 | 225 | ``` 226 | go build 227 | ``` 228 | 229 | ### Releasing changes 230 | 231 | If you want to do releases, you'll also want the `deb-s3` package. 232 | You'll also want `gnupg2` to be able to sign releases, but i'll leave 233 | installation of that up to you. 234 | 235 | ```sh 236 | gem install deb-s3 237 | ``` 238 | 239 | ### Running locally 240 | 241 | The `make install` target will compile the application and 'install' it into your `$GOPATH`. 242 | 243 | You can then run `$GOPATH/bin/yak`. 244 | 245 | ### Running tests 246 | 247 | Just run: 248 | 249 | ``` 250 | make test 251 | ``` 252 | 253 | If `gotestsum` isn't available we'll try and install it. 254 | To run tests without gotestsum, or to run the tests for any individual package, you can run: 255 | 256 | ``` 257 | go test 258 | ``` 259 | 260 | ## License 261 | 262 | `yak` is provided under an MIT license. See the [LICENSE](https://github.com/redbubble/yak/blob/master/LICENSE) file for 263 | details. 264 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yak 2 | 3 | 4 | **:information_source: Due to changes in the way Okta works `yak` is now deprecated. We suggest looking at using the native `aws sso` commands.** 5 | 6 | [Old README.md](OLDREADME.md) 7 | -------------------------------------------------------------------------------- /aws/aws.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/aws/session" 5 | "github.com/aws/aws-sdk-go/service/sts" 6 | 7 | "github.com/redbubble/yak/saml" 8 | ) 9 | 10 | func AssumeRole(login saml.LoginData, role saml.LoginRole, duration int64) (*sts.AssumeRoleWithSAMLOutput, error) { 11 | session := session.Must(session.NewSession()) 12 | stsClient := sts.New(session) 13 | 14 | input := sts.AssumeRoleWithSAMLInput{ 15 | DurationSeconds: &duration, 16 | PrincipalArn: &role.PrincipalArn, 17 | RoleArn: &role.RoleArn, 18 | SAMLAssertion: &login.Assertion, 19 | } 20 | 21 | return stsClient.AssumeRoleWithSAML(&input) 22 | } 23 | 24 | func EnvironmentVariables(stsOutput *sts.AssumeRoleWithSAMLOutput) map[string]string { 25 | subject := make(map[string]string) 26 | 27 | subject["AWS_ACCESS_KEY_ID"] = *stsOutput.Credentials.AccessKeyId 28 | subject["AWS_SECRET_ACCESS_KEY"] = *stsOutput.Credentials.SecretAccessKey 29 | subject["AWS_SESSION_TOKEN"] = *stsOutput.Credentials.SessionToken 30 | subject["AWS_METADATA_USER_ARN"] = *stsOutput.AssumedRoleUser.Arn 31 | 32 | return subject 33 | } 34 | -------------------------------------------------------------------------------- /aws/aws_test.go: -------------------------------------------------------------------------------- 1 | package aws 2 | 3 | import ( 4 | "github.com/aws/aws-sdk-go/service/sts" 5 | "testing" 6 | ) 7 | 8 | func TestEnvironmentVariables(t *testing.T) { 9 | accessKeyId := "llama" 10 | secretAccessKey := "alpaca" 11 | sessionToken := "guanaco" 12 | assumedRoleArn := "arn:aws:iam::1234123123:role/sso-vicuña-role" 13 | 14 | creds := sts.AssumeRoleWithSAMLOutput{ 15 | AssumedRoleUser: &sts.AssumedRoleUser{ 16 | Arn: &assumedRoleArn, 17 | }, 18 | Credentials: &sts.Credentials{ 19 | AccessKeyId: &accessKeyId, 20 | SecretAccessKey: &secretAccessKey, 21 | SessionToken: &sessionToken, 22 | }, 23 | } 24 | 25 | subject := EnvironmentVariables(&creds) 26 | 27 | if subject["AWS_ACCESS_KEY_ID"] != accessKeyId { 28 | t.Log("---------------") 29 | t.Log("Did not correctly set AWS_ACCESS_KEY_ID") 30 | t.Logf("Expected: %s", accessKeyId) 31 | t.Logf("Got: %s", subject["AWS_ACCESS_KEY_ID"]) 32 | t.Fail() 33 | } 34 | 35 | if subject["AWS_SECRET_ACCESS_KEY"] != secretAccessKey { 36 | t.Log("---------------") 37 | t.Log("Did not correctly set AWS_SECRET_ACCESS_KEY") 38 | t.Logf("Expected: %s", secretAccessKey) 39 | t.Logf("Got: %s", subject["AWS_SECRET_ACCESS_KEY"]) 40 | t.Fail() 41 | } 42 | 43 | if subject["AWS_SESSION_TOKEN"] != sessionToken { 44 | t.Log("---------------") 45 | t.Log("Did not correctly set AWS_SESSION_TOKEN") 46 | t.Logf("Expected: %s", sessionToken) 47 | t.Logf("Got: %s", subject["AWS_SESSION_TOKEN"]) 48 | t.Fail() 49 | } 50 | 51 | if subject["AWS_METADATA_USER_ARN"] != assumedRoleArn { 52 | t.Log("---------------") 53 | t.Log("Did not correctly set AWS_METADATA_USER_ARN") 54 | t.Logf("Expected: %s", assumedRoleArn) 55 | t.Logf("Got: %s", subject["AWS_METADATA_USER_ARN"]) 56 | t.Fail() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "bufio" 5 | "encoding/gob" 6 | "os" 7 | "time" 8 | 9 | "github.com/aws/aws-sdk-go/service/sts" 10 | gocache "github.com/patrickmn/go-cache" 11 | "github.com/spf13/viper" 12 | 13 | "github.com/redbubble/yak/okta" 14 | ) 15 | 16 | var cacheHandle *gocache.Cache 17 | 18 | func cache() *gocache.Cache { 19 | if cacheHandle == nil { 20 | roleExpiryDuration := time.Duration(viper.GetInt("aws.session_duration")) * time.Second 21 | err := importCache(roleExpiryDuration) 22 | 23 | if err != nil { 24 | cacheHandle = gocache.New(roleExpiryDuration, roleExpiryDuration) 25 | } 26 | } 27 | 28 | return cacheHandle 29 | } 30 | 31 | func Enabled() bool { 32 | return !viper.GetBool("cache.no_cache") 33 | } 34 | 35 | func gobInit() { 36 | gob.Register(sts.AssumeRoleWithSAMLOutput{}) 37 | gob.Register(okta.OktaSession{}) 38 | } 39 | 40 | func importCache(roleExpiryDuration time.Duration) error { 41 | cacheFile, err := os.Open(viper.GetString("cache.file_location")) 42 | defer cacheFile.Close() 43 | 44 | if err != nil { 45 | return err 46 | } 47 | 48 | gobInit() 49 | decoder := gob.NewDecoder(bufio.NewReader(cacheFile)) 50 | var items map[string]gocache.Item 51 | 52 | if err = decoder.Decode(&items); err != nil { 53 | return err 54 | } 55 | 56 | cacheHandle = gocache.NewFrom(roleExpiryDuration, roleExpiryDuration, items) 57 | 58 | return nil 59 | } 60 | 61 | func Write(key string, value interface{}, duration time.Duration) { 62 | if !Enabled() { 63 | return 64 | } 65 | 66 | cache().Set(key, value, duration) 67 | } 68 | 69 | func WriteDefault(key string, value interface{}) { 70 | if !Enabled() { 71 | return 72 | } 73 | 74 | cache().SetDefault(key, value) 75 | } 76 | 77 | func Check(key string) interface{} { 78 | if !Enabled() { 79 | return nil 80 | } 81 | 82 | data, dataExists := cache().Get(key) 83 | 84 | if !dataExists { 85 | return nil 86 | } 87 | 88 | return data 89 | } 90 | 91 | func Export() error { 92 | if !Enabled() { 93 | return nil 94 | } 95 | 96 | cacheFile, err := os.Create(viper.GetString("cache.file_location")) 97 | defer cacheFile.Close() 98 | 99 | if err != nil { 100 | return err 101 | } 102 | 103 | writer := bufio.NewWriter(cacheFile) 104 | gobInit() 105 | enc := gob.NewEncoder(writer) 106 | if err = enc.Encode(cache().Items()); err != nil { 107 | return err 108 | } 109 | 110 | return writer.Flush() 111 | } 112 | -------------------------------------------------------------------------------- /cli/assume_role.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/aws/aws-sdk-go/aws/arn" 8 | "github.com/aws/aws-sdk-go/service/sts" 9 | "github.com/spf13/viper" 10 | 11 | "github.com/redbubble/yak/aws" 12 | "github.com/redbubble/yak/cache" 13 | "github.com/redbubble/yak/saml" 14 | log "github.com/sirupsen/logrus" 15 | ) 16 | 17 | var notARoleErrorMessage = `'%s' is neither an IAM role ARN nor a configured alias. 18 | 19 | Run 'yak --list-roles' to see which roles and aliases you can use.` 20 | 21 | func AssumeRole(role string) (*sts.AssumeRoleWithSAMLOutput, error) { 22 | creds := getAssumedRoleFromCache(role) 23 | 24 | if creds == nil { 25 | log.Infof("Role %s not in cache", role) 26 | if viper.GetBool("cache.cache_only") { 27 | return nil, errors.New("Could not find credentials in cache and --cache-only specified. Run `yak ` to remedy.") 28 | } 29 | 30 | loginData, err := GetLoginDataWithTimeout() 31 | 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | CacheLoginRoles(loginData.Roles) 37 | creds, err = assumeRoleFromAws(loginData, role) 38 | 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | log.WithField("role", creds).Debug("assume_role.go: Role assumption credentials from AWS") 44 | 45 | cache.WriteDefault(role, creds) 46 | cache.Export() 47 | } 48 | 49 | return creds, nil 50 | } 51 | 52 | func getAssumedRoleFromCache(role string) *sts.AssumeRoleWithSAMLOutput { 53 | data, ok := cache.Check(role).(sts.AssumeRoleWithSAMLOutput) 54 | 55 | if !ok { 56 | return nil 57 | } 58 | 59 | return &data 60 | } 61 | 62 | func ResolveRole(roleName string) (string, error) { 63 | if viper.IsSet("alias." + roleName) { 64 | return viper.GetString("alias." + roleName), nil 65 | } 66 | 67 | if isIamRoleArn(roleName) { 68 | return roleName, nil 69 | } 70 | 71 | return "", fmt.Errorf(notARoleErrorMessage, roleName) 72 | } 73 | 74 | func assumeRoleFromAws(login saml.LoginData, desiredRole string) (*sts.AssumeRoleWithSAMLOutput, error) { 75 | log.Infof("Assuming role %s from AWS", desiredRole) 76 | 77 | role, err := login.GetLoginRole(desiredRole) 78 | 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | return aws.AssumeRole(login, role, viper.GetInt64("aws.session_duration")) 84 | } 85 | 86 | func isIamRoleArn(roleName string) bool { 87 | return arn.IsARN(roleName) 88 | } 89 | -------------------------------------------------------------------------------- /cli/exec.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | ) 8 | 9 | func EnrichedEnvironment(extraEnv map[string]string) []string { 10 | env := os.Environ() 11 | 12 | for key, value := range extraEnv { 13 | env = append(env, fmt.Sprintf("%s=%s", key, value)) 14 | } 15 | 16 | return env 17 | } 18 | 19 | func Exec(command []string, environment []string) error { 20 | cmd := exec.Command(command[0], command[1:]...) 21 | cmd.Env = environment 22 | 23 | cmd.Stdout = os.Stdout 24 | cmd.Stderr = os.Stderr 25 | cmd.Stdin = os.Stdin 26 | 27 | err := cmd.Start() 28 | 29 | if err != nil { 30 | return err 31 | } 32 | 33 | err = cmd.Wait() 34 | return err 35 | } 36 | -------------------------------------------------------------------------------- /cli/exec_test.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestEnrichedEnvironment(t *testing.T) { 10 | extraVars := map[string]string{} 11 | 12 | extraVars["CHEESE"] = "Challerhocker" 13 | extraVars["CAMELID"] = "Dromedary" 14 | 15 | subject := EnrichedEnvironment(extraVars) 16 | 17 | if len(subject) != len(os.Environ())+2 { 18 | t.Log("---------------") 19 | t.Log("Did not inject the environment correctly") 20 | t.Logf("Expected length: %d", len(os.Environ())+2) 21 | t.Logf("Got: %d", len(subject)) 22 | t.Fail() 23 | } 24 | 25 | for expectedKey, expectedValue := range extraVars { 26 | var key, value string 27 | 28 | for _, variable := range subject { 29 | parts := strings.Split(variable, "=") 30 | 31 | if parts[0] == expectedKey { 32 | key = parts[0] 33 | value = parts[1] 34 | break 35 | } 36 | } 37 | 38 | if key != expectedKey { 39 | t.Log("---------------") 40 | t.Logf("Did not add variable %s to the environment", expectedKey) 41 | t.Fail() 42 | } else if value != expectedValue { 43 | t.Log("---------------") 44 | t.Logf("Variable %s was added to the environment with an incorrect value", expectedKey) 45 | t.Logf("Expected: %s", expectedValue) 46 | t.Logf("Got: %s", value) 47 | t.Fail() 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /cli/login.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | "runtime" 10 | "strconv" 11 | "strings" 12 | "syscall" 13 | "time" 14 | 15 | "github.com/spf13/viper" 16 | "github.com/twpayne/go-pinentry" 17 | "golang.org/x/crypto/ssh/terminal" 18 | 19 | "github.com/redbubble/yak/cache" 20 | "github.com/redbubble/yak/okta" 21 | "github.com/redbubble/yak/saml" 22 | log "github.com/sirupsen/logrus" 23 | ) 24 | 25 | const maxLoginRetries = 3 26 | 27 | var acceptableAuthFactors = [...]string{ 28 | "token:software:totp", 29 | "token:hardware", 30 | "push", 31 | } 32 | 33 | func GetRolesFromCache() ([]saml.LoginRole, bool) { 34 | data, ok := cache.Check("aws:roles").([]string) 35 | 36 | if !ok { 37 | return []saml.LoginRole{}, false 38 | } 39 | 40 | roles := []saml.LoginRole{} 41 | for _, datum := range data { 42 | role, ok := saml.CreateLoginRole(datum) 43 | 44 | if ok { 45 | roles = append(roles, role) 46 | } 47 | } 48 | 49 | return roles, true 50 | } 51 | 52 | func oktaDomain() string { 53 | return viper.GetString("okta.domain") 54 | } 55 | 56 | func oktaUsername() string { 57 | return viper.GetString("okta.username") 58 | } 59 | 60 | func oktaSessionCacheKey() string { 61 | return fmt.Sprintf("okta:sessionToken:%s:%s", oktaDomain(), oktaUsername()) 62 | } 63 | 64 | func getOktaSessionFromCache() (*okta.OktaSession, bool) { 65 | data, ok := cache.Check(oktaSessionCacheKey()).(okta.OktaSession) 66 | return &data, ok 67 | } 68 | 69 | func cacheOktaSession(session *okta.OktaSession) { 70 | expires := session.ExpiresAt.Sub(time.Now()) 71 | expiryLimit := time.Duration(viper.GetInt64("okta.session_cache_limit")) * time.Second 72 | 73 | if expiryLimit > 0 && expiryLimit < expires { 74 | log.Debugf("Okta session expires in %.0f seconds, but we're configured to only cache that for %.0f seconds", expires.Seconds(), expiryLimit.Seconds()) 75 | expires = expiryLimit 76 | } 77 | 78 | cache.Write(oktaSessionCacheKey(), *session, expires) 79 | } 80 | 81 | func checkOktaSession(session *okta.OktaSession) bool { 82 | response, err := okta.GetSession(oktaDomain(), session) 83 | 84 | // This needs explaining: Okta's "Create Session" API call gives 85 | // us a session ID that we set as the `sid` cookie. Get & Refresh return a 86 | // *different* ID that we can't use as the cookie, but they both 87 | // extend the calling session. 88 | 89 | if err == nil { 90 | session.ExpiresAt = response.ExpiresAt 91 | cacheOktaSession(session) 92 | } 93 | 94 | return err == nil 95 | } 96 | 97 | func GetLoginDataWithTimeout() (saml.LoginData, error) { 98 | errorChannel := make(chan error) 99 | resultChannel := make(chan saml.LoginData) 100 | 101 | go func() { 102 | data, err := getLoginData() 103 | 104 | if err != nil { 105 | errorChannel <- err 106 | } else { 107 | resultChannel <- data 108 | } 109 | }() 110 | 111 | timeoutSeconds := viper.GetDuration("login.timeout") * time.Second 112 | 113 | if timeoutSeconds != 0 { 114 | select { 115 | case err := <-errorChannel: 116 | return saml.LoginData{}, err 117 | case data := <-resultChannel: 118 | return data, nil 119 | case <-time.After(timeoutSeconds): 120 | return saml.LoginData{}, errors.New("Login timeout") 121 | } 122 | } else { 123 | select { 124 | case err := <-errorChannel: 125 | return saml.LoginData{}, err 126 | case data := <-resultChannel: 127 | return data, nil 128 | } 129 | } 130 | } 131 | 132 | func getLoginData() (saml.LoginData, error) { 133 | session, gotSession := getOktaSessionFromCache() 134 | 135 | if gotSession && session.ExpiresAt.After(time.Now()) { 136 | log.Infof("Okta session found in cache (%s), expires %s", session.Id, session.ExpiresAt.String()) 137 | gotSession = checkOktaSession(session) 138 | if gotSession { 139 | log.Infof("Refreshed session, now expires %s", session.ExpiresAt.String()) 140 | } 141 | } 142 | 143 | if !gotSession { 144 | var authResponse okta.OktaAuthResponse 145 | var err error 146 | 147 | log.Infof("Okta session not in cache or no longer valid, re-authenticating") 148 | 149 | if viper.GetBool("cache.cache_only") { 150 | return saml.LoginData{}, errors.New("Could not find credentials in cache and --cache-only specified. Run `yak ` to remedy.") 151 | } 152 | 153 | authResponse, err = promptLogin() 154 | 155 | if err != nil { 156 | return saml.LoginData{}, err 157 | 158 | } 159 | 160 | for authResponse.Status == "MFA_REQUIRED" { 161 | selectedFactor, err := chooseMFA(authResponse) 162 | 163 | if err != nil { 164 | return saml.LoginData{}, err 165 | } 166 | 167 | authResponse, err = promptMFA(selectedFactor, authResponse.StateToken) 168 | 169 | if err != nil { 170 | return saml.LoginData{}, err 171 | } 172 | } 173 | 174 | session, err = getOktaSession(authResponse) 175 | if err != nil { 176 | return saml.LoginData{}, err 177 | } 178 | 179 | } 180 | 181 | samlPayload, err := okta.AwsSamlLogin(oktaDomain(), viper.GetString("okta.aws_saml_endpoint"), *session) 182 | if err != nil { 183 | return saml.LoginData{}, err 184 | } 185 | 186 | samlResponse, err := saml.ParseResponse(samlPayload) 187 | 188 | if err != nil { 189 | return saml.LoginData{}, err 190 | } 191 | log.WithField("saml", samlResponse).Debug("okta.go: SAML response from Okta") 192 | 193 | return saml.CreateLoginData(samlResponse, samlPayload), nil 194 | } 195 | 196 | func chooseMFA(authResponse okta.OktaAuthResponse) (okta.AuthResponseFactor, error) { 197 | acceptableFactors := getAcceptableFactors(authResponse.Embedded.Factors) 198 | 199 | if len(acceptableFactors) == 0 { 200 | return okta.AuthResponseFactor{}, errors.New("No usable MFA factors found, but MFA was requested. Aborting.") 201 | } 202 | 203 | factor, gotFactor := getConfiguredMFAFactor(acceptableFactors) 204 | 205 | if gotFactor { 206 | return factor, nil 207 | } else if len(acceptableFactors) > 1 { 208 | var factorIndex int 209 | validIndex := false 210 | maxIndex := len(acceptableFactors) - 1 211 | 212 | for index, factor := range acceptableFactors { 213 | fmt.Fprintf(os.Stderr, "[%d] %s (%s)\n", index, factor.FactorType, factor.Provider) 214 | } 215 | 216 | for !validIndex { 217 | fmt.Fprint(os.Stderr, "Select an MFA factor (0): ") 218 | factorIndexString, err := getLine() 219 | 220 | if err != nil { 221 | return factor, err 222 | } 223 | 224 | if factorIndexString == "" { 225 | factorIndex = 0 226 | } else { 227 | factorIndex, err = strconv.Atoi(factorIndexString) 228 | 229 | if err != nil || factorIndex > maxIndex || factorIndex < 0 { 230 | fmt.Fprintf(os.Stderr, "Please enter a number between 0 and %d\n", maxIndex) 231 | continue 232 | } 233 | } 234 | 235 | factor = acceptableFactors[factorIndex] 236 | validIndex = true 237 | } 238 | 239 | fmt.Fprintf(os.Stderr, "Set as default MFA factor by adding mfa_type = \"%s\" and mfa_provider = \"%s\" to the [okta] section in your config!\n", factor.FactorType, factor.Provider) 240 | return factor, nil 241 | } 242 | 243 | // If no factor is chosen by this point, take the first acceptable factor 244 | return acceptableFactors[0], nil 245 | } 246 | 247 | func getOktaSession(authResponse okta.OktaAuthResponse) (session *okta.OktaSession, err error) { 248 | log.Infof("Creating new Okta session for %s", oktaDomain()) 249 | session, err = okta.CreateSession(oktaDomain(), authResponse) 250 | 251 | if err == nil { 252 | cacheOktaSession(session) 253 | } 254 | 255 | return 256 | } 257 | 258 | func getAcceptableFactors(factors []okta.AuthResponseFactor) []okta.AuthResponseFactor { 259 | acceptableFactors := []okta.AuthResponseFactor{} 260 | 261 | for _, factor := range factors { 262 | if factorAcceptable(factor) { 263 | acceptableFactors = append(acceptableFactors, factor) 264 | } 265 | } 266 | 267 | return acceptableFactors 268 | } 269 | 270 | func factorAcceptable(factor okta.AuthResponseFactor) bool { 271 | for _, acceptableFactor := range acceptableAuthFactors { 272 | if factor.FactorType == acceptableFactor { 273 | return true 274 | } 275 | } 276 | 277 | return false 278 | } 279 | 280 | func getConfiguredMFAFactor(factors []okta.AuthResponseFactor) (okta.AuthResponseFactor, bool) { 281 | providerAcceptable := false 282 | typeAcceptable := false 283 | 284 | if viper.GetString("okta.mfa_type") != "" || viper.GetString("okta.mfa_provider") != "" { 285 | for _, factor := range factors { 286 | if factor.FactorType == viper.GetString("okta.mfa_type") { 287 | typeAcceptable = true 288 | 289 | if factor.Provider == strings.ToUpper(viper.GetString("okta.mfa_provider")) { 290 | providerAcceptable = true 291 | return factor, true 292 | } 293 | } 294 | } 295 | 296 | if !typeAcceptable { 297 | fmt.Fprintf(os.Stderr, "Warning: no factors of type '%s' available\n", viper.GetString("okta.mfa_type")) 298 | } else if !providerAcceptable { 299 | fmt.Fprintf(os.Stderr, "Warning: no factors from provider %s available\n", viper.GetString("okta.mfa_provider")) 300 | } 301 | } 302 | 303 | return okta.AuthResponseFactor{}, false 304 | } 305 | 306 | func promptMFA(factor okta.AuthResponseFactor, stateToken string) (okta.OktaAuthResponse, error) { 307 | var authResponse okta.OktaAuthResponse 308 | var err error 309 | retries := 0 310 | unauthorised := true 311 | 312 | for unauthorised && (retries < maxLoginRetries) { 313 | retries++ 314 | 315 | switch factor.FactorType { 316 | case "push": 317 | authResponse, err = okta.VerifyPush(factor.Links.VerifyLink.Href, okta.PushRequest{stateToken}) 318 | case "token:software:totp": 319 | passCode, _ := promptOrPinentry(fmt.Sprintf("Okta MFA token (from %s): ", okta.TotpFactorName(factor.Provider)), false) 320 | authResponse, err = okta.VerifyTotp(factor.Links.VerifyLink.Href, okta.TotpRequest{stateToken, passCode}) 321 | case "token:hardware": 322 | passCode, _ := promptOrPinentry(fmt.Sprintf("Okta MFA token (from %s): ", okta.TotpFactorName(factor.Provider)), false) 323 | authResponse, err = okta.VerifyTotp(factor.Links.VerifyLink.Href, okta.TotpRequest{stateToken, passCode}) 324 | default: 325 | err := errors.New("Unknown factor type selected. Exiting.") 326 | return authResponse, err 327 | } 328 | 329 | if authResponse.YakStatusCode == okta.YAK_STATUS_UNAUTHORISED && retries < maxLoginRetries { 330 | fmt.Fprintln(os.Stderr, "Sorry, Try again.") 331 | } else { 332 | unauthorised = false 333 | } 334 | } 335 | 336 | return authResponse, err 337 | } 338 | 339 | func promptLogin() (okta.OktaAuthResponse, error) { 340 | var authResponse okta.OktaAuthResponse 341 | var err error 342 | retries := 0 343 | unauthorised := true 344 | 345 | for unauthorised && (retries < maxLoginRetries) { 346 | retries++ 347 | username := oktaUsername() 348 | promptUsername := (username == "") 349 | 350 | // Viper isn't used here because it's really hard to get Viper to not accept values through the config file 351 | password := os.Getenv("OKTA_PASSWORD") 352 | envPassword := (password != "") 353 | 354 | if promptUsername { 355 | fmt.Fprint(os.Stderr, "Okta username: ") 356 | username, err = getLine() 357 | 358 | if err != nil { 359 | return authResponse, err 360 | } 361 | } 362 | 363 | if password == "" { 364 | prompt := "Okta password" 365 | if !promptUsername { 366 | prompt = prompt + " (" + username + ")" 367 | } 368 | 369 | password, err = promptOrPinentry(fmt.Sprintf("%s: ", prompt), true) 370 | 371 | if err != nil { 372 | return authResponse, err 373 | } 374 | } 375 | 376 | authResponse, err = okta.Authenticate(oktaDomain(), okta.UserData{username, password}) 377 | 378 | if authResponse.YakStatusCode == okta.YAK_STATUS_UNAUTHORISED && retries < maxLoginRetries && !envPassword { 379 | fmt.Fprintln(os.Stderr, "Sorry, try again.") 380 | } else { 381 | unauthorised = false 382 | } 383 | } 384 | 385 | return authResponse, err 386 | } 387 | 388 | func CacheLoginRoles(roles []saml.LoginRole) { 389 | data := []string{} 390 | 391 | for _, role := range roles { 392 | data = append(data, saml.SerialiseLoginRole(role)) 393 | } 394 | 395 | cache.WriteDefault("aws:roles", data) 396 | } 397 | 398 | func promptOrPinentry(prompt string, secret bool) (string, error) { 399 | // Whether to use pinentry for (GUI) password prompt, or the original way 400 | if viper.GetBool("pinentry") { 401 | return getPinentry(prompt, secret) 402 | } else { 403 | fmt.Fprintf(os.Stderr, prompt) 404 | // If it's secret, don't echo the user's response. 405 | if secret { 406 | return getPassword() 407 | } else { 408 | return getLine() 409 | } 410 | } 411 | } 412 | 413 | func getPinentry(prompt string, secret bool) (string, error) { 414 | clientOptions := pinentry.WithBinaryNameFromGnuPGAgentConf() 415 | 416 | // Rather that rely on darwin users having a gpgagent conf just look for pinentry-mac. 417 | // Simplifies config for the most common use case. 418 | // Users that are specifically asking for pinentry to be used almost certainly need the GUI version, whereas the default is the CLI version. This is for e.g., GUI database clients that call yak in the background where stdout and stdin are unavailable. 419 | if runtime.GOOS == "darwin" { 420 | pinentry_absolute, err := exec.LookPath("pinentry-mac") 421 | if err != nil { 422 | return "", err 423 | } 424 | clientOptions = pinentry.WithBinaryName(pinentry_absolute) 425 | } 426 | 427 | p, err := pinentry.NewClient(clientOptions, 428 | pinentry.WithDesc(prompt), 429 | pinentry.WithPrompt(""), 430 | pinentry.WithTitle("Yak")) 431 | 432 | if err != nil { 433 | return "", fmt.Errorf("pinentry error: %w", err) 434 | } 435 | defer p.Close() 436 | 437 | pw, _, err := p.GetPIN() 438 | if err != nil { 439 | return "", fmt.Errorf("pinentry error: %w", err) 440 | } 441 | 442 | pass := string(pw) 443 | return pass, nil 444 | } 445 | 446 | func getPassword() (string, error) { 447 | bytes, err := terminal.ReadPassword(int(syscall.Stdin)) 448 | fmt.Fprint(os.Stderr, "\n") 449 | 450 | return string(bytes), err 451 | } 452 | 453 | func getLine() (string, error) { 454 | scanner := bufio.NewScanner(os.Stdin) 455 | scanner.Scan() 456 | 457 | return scanner.Text(), scanner.Err() 458 | } 459 | -------------------------------------------------------------------------------- /cmd/list_roles.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | log "github.com/sirupsen/logrus" 7 | "github.com/spf13/cobra" 8 | "github.com/spf13/viper" 9 | 10 | "github.com/redbubble/yak/cache" 11 | "github.com/redbubble/yak/cli" 12 | ) 13 | 14 | func listRolesCmd(cmd *cobra.Command, args []string) error { 15 | roles, gotRoles := cli.GetRolesFromCache() 16 | 17 | if !gotRoles { 18 | log.Infof("Role list not in cache, grabbing from AWS") 19 | loginData, err := cli.GetLoginDataWithTimeout() 20 | 21 | if err != nil { 22 | return err 23 | } 24 | 25 | cli.CacheLoginRoles(loginData.Roles) 26 | cache.Export() 27 | 28 | roles = (loginData.Roles) 29 | } 30 | 31 | aliases, _ := getAliases() 32 | 33 | for _, alias := range aliases { 34 | fmt.Printf(" %s\n", alias) 35 | } 36 | 37 | for _, role := range roles { 38 | fmt.Printf(" %s\n", role.RoleArn) 39 | } 40 | fmt.Println() 41 | 42 | return nil 43 | } 44 | 45 | func getAliases() ([]string, error) { 46 | var aliases map[string]string 47 | 48 | if !viper.IsSet("alias") { 49 | return []string{}, nil 50 | } 51 | 52 | err := viper.Sub("alias").Unmarshal(&aliases) 53 | 54 | if err != nil { 55 | return []string{}, err 56 | } 57 | 58 | keys := []string{} 59 | 60 | for key, _ := range aliases { 61 | keys = append(keys, key) 62 | } 63 | 64 | return keys, nil 65 | } 66 | -------------------------------------------------------------------------------- /cmd/print_credentials.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/spf13/viper" 8 | 9 | "github.com/redbubble/yak/cli" 10 | "github.com/redbubble/yak/format" 11 | ) 12 | 13 | func printCredentialsCmd(cmd *cobra.Command, args []string) error { 14 | roleName, err := cli.ResolveRole(args[0]) 15 | 16 | if err != nil { 17 | return err 18 | } 19 | 20 | creds, err := cli.AssumeRole(roleName) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | output, err := format.Credentials(viper.GetString("output.format"), creds) 26 | 27 | if err != nil { 28 | return err 29 | } 30 | 31 | fmt.Print(output) 32 | 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "encoding/base64" 5 | "errors" 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | "os/signal" 10 | "path" 11 | "syscall" 12 | 13 | "github.com/mitchellh/go-homedir" 14 | log "github.com/sirupsen/logrus" 15 | "github.com/spf13/cobra" 16 | "github.com/spf13/viper" 17 | "golang.org/x/crypto/ssh/terminal" 18 | 19 | "github.com/redbubble/yak/format" 20 | ) 21 | 22 | var rootCmd = &cobra.Command{ 23 | Use: "yak [flags] [--list-roles | [--] []]", 24 | Short: "A shim to do stuff with AWS credentials using Okta", 25 | Long: `A shim to do stuff with AWS credentials using Okta 26 | 27 | * With --list-roles, print a list of your available AWS roles. 28 | Otherwise, yak will attempt to generate AWS keys for . 29 | 30 | * If is set, yak will attempt to execute it with the 31 | AWS keys injected into the environment. Otherwise, the 32 | credentials will conveniently be printed stdout. 33 | 34 | Note that if you want to pass -/-- flags to your , 35 | you'll need to put a '--' separator before the so yak 36 | knows not to interpret those arguments for itself`, 37 | SilenceUsage: true, 38 | SilenceErrors: true, 39 | RunE: func(cmd *cobra.Command, args []string) error { 40 | var err error 41 | 42 | if viper.GetBool("version") { 43 | versionCmd() 44 | return nil 45 | } 46 | 47 | if viper.GetBool("credits") { 48 | creditsCmd() 49 | return nil 50 | } 51 | 52 | // The no-cache and cache-only flags are mutually exclusive, so bail out when both are specified 53 | if viper.GetBool("cache.no_cache") && viper.GetBool("cache.cache_only") { 54 | return errors.New("Please don't use --cache-only and --no-cache simultaneously.") 55 | } 56 | 57 | // Likewise, it doesn't make much sense to clear the cache if --no-cache was specified too 58 | if viper.GetBool("cache.no_cache") && viper.GetBool("clear-cache") { 59 | return errors.New("Please don't use --no-cache and --clear-cache simultaneously.") 60 | } 61 | 62 | // If we've made it to this point, we need to have an Okta domain and an AWS path 63 | if viper.GetString("okta.domain") == "" || viper.GetString("okta.aws_saml_endpoint") == "" { 64 | return errors.New(`An Okta domain and an AWS SAML Endpoint must be configured for yak to work. 65 | These can be configured either in the [okta] section of ~/.config/yak/config.toml or by passing the --okta-domain and --okta-aws-saml-endpoint arguments.`) 66 | } 67 | 68 | // If the output format is invalid, exit here to provide consistent UX across all commands 69 | err = format.ValidateOutputFormat(viper.GetString("output.format")) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | if viper.GetBool("debug") { 75 | log.SetLevel(log.DebugLevel) 76 | } else if viper.GetBool("verbose") { 77 | log.SetLevel(log.InfoLevel) 78 | } else { 79 | log.SetLevel(log.WarnLevel) 80 | } 81 | 82 | if viper.GetBool("clear-cache") { 83 | clearCache() 84 | 85 | if !viper.GetBool("list-roles") && len(args) == 0 { 86 | return nil 87 | } 88 | } 89 | 90 | state, stateErr := terminal.GetState(int(syscall.Stdin)) 91 | channel := make(chan os.Signal, 2) 92 | signal.Notify(channel, os.Interrupt, syscall.SIGTERM) 93 | go func() { 94 | <-channel 95 | fmt.Fprintln(os.Stderr, "Received termination signal, exiting...") 96 | if stateErr == nil { 97 | terminal.Restore(int(syscall.Stdin), state) 98 | } 99 | 100 | os.Exit(1) 101 | }() 102 | 103 | if viper.GetBool("list-roles") { 104 | err = listRolesCmd(cmd, args) 105 | } else if len(args) == 1 { 106 | err = printCredentialsCmd(cmd, args) 107 | } else if len(args) > 1 { 108 | err = shimCmd(cmd, args) 109 | } else { 110 | cmd.Help() 111 | } 112 | 113 | return err 114 | }, 115 | } 116 | 117 | func init() { 118 | cobra.OnInitialize(defaultConfigValues) 119 | cobra.OnInitialize(initConfig) 120 | cobra.OnInitialize(initCache) 121 | 122 | rootCmd.PersistentFlags().BoolP("help", "h", false, "Display this help message and exit") 123 | rootCmd.PersistentFlags().BoolP("list-roles", "l", false, "List available AWS roles and exit") 124 | rootCmd.PersistentFlags().Bool("clear-cache", false, "Delete all data from yak's cache. If no other arguments are given, exit without error") 125 | rootCmd.PersistentFlags().Bool("version", false, "Print the current version and exit") 126 | rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Print our actions as we take them") 127 | rootCmd.PersistentFlags().Bool("debug", false, "Print detailed debug information, including session tokens") 128 | 129 | rootCmd.PersistentFlags().Bool("credits", false, "Print the contributing authors") 130 | viper.BindPFlag("list-roles", rootCmd.PersistentFlags().Lookup("list-roles")) 131 | viper.BindPFlag("clear-cache", rootCmd.PersistentFlags().Lookup("clear-cache")) 132 | viper.BindPFlag("version", rootCmd.PersistentFlags().Lookup("version")) 133 | viper.BindPFlag("credits", rootCmd.PersistentFlags().Lookup("credits")) 134 | viper.BindPFlag("verbose", rootCmd.PersistentFlags().Lookup("verbose")) 135 | viper.BindPFlag("debug", rootCmd.PersistentFlags().Lookup("debug")) 136 | 137 | rootCmd.PersistentFlags().StringP("okta-username", "u", "", "Your Okta username") 138 | rootCmd.PersistentFlags().String("okta-domain", "", "The domain to use for requests to Okta") 139 | rootCmd.PersistentFlags().String("okta-aws-saml-endpoint", "", "The app embed path for the AWS app within Okta") 140 | rootCmd.PersistentFlags().String("okta-mfa-type", "", "The Okta MFA type for login") 141 | rootCmd.PersistentFlags().String("okta-mfa-provider", "", "The Okta MFA provider name for login") 142 | rootCmd.PersistentFlags().StringP("output-format", "o", "", "Can be set to either 'json' or 'env'. The format in which to output credential data") 143 | rootCmd.PersistentFlags().Int64P("aws-session-duration", "d", 0, "The session duration to request from AWS (in seconds)") 144 | rootCmd.PersistentFlags().Bool("no-cache", false, "Ignore cache for this request. Mutually exclusive with --cache-only") 145 | rootCmd.PersistentFlags().Bool("cache-only", false, "Only use cache, do not make external requests. Mutually exclusive with --no-cache") 146 | rootCmd.PersistentFlags().Bool("pinentry", false, "Use the pinentry to prompt for credentials, instead of terminal (useful for GUI applications)") 147 | viper.BindPFlag("okta.username", rootCmd.PersistentFlags().Lookup("okta-username")) 148 | viper.BindPFlag("okta.domain", rootCmd.PersistentFlags().Lookup("okta-domain")) 149 | viper.BindPFlag("okta.aws_saml_endpoint", rootCmd.PersistentFlags().Lookup("okta-aws-saml-endpoint")) 150 | viper.BindPFlag("okta.mfa_type", rootCmd.PersistentFlags().Lookup("okta-mfa-type")) 151 | viper.BindPFlag("okta.mfa_provider", rootCmd.PersistentFlags().Lookup("okta-mfa-provider")) 152 | viper.BindPFlag("aws.session_duration", rootCmd.PersistentFlags().Lookup("aws-session-duration")) 153 | viper.BindPFlag("cache.no_cache", rootCmd.PersistentFlags().Lookup("no-cache")) 154 | viper.BindPFlag("cache.cache_only", rootCmd.PersistentFlags().Lookup("cache-only")) 155 | viper.BindPFlag("output.format", rootCmd.PersistentFlags().Lookup("output-format")) 156 | viper.BindPFlag("pinentry", rootCmd.PersistentFlags().Lookup("pinentry")) 157 | } 158 | 159 | func versionCmd() { 160 | fmt.Printf("yak v%s\n", viper.GetString("yak.version")) 161 | 162 | yabytes, _ := base64.StdEncoding.DecodeString(` 163 | IC8gICAgIFwKLyAgICAgICBcCiBcIF9fXyAvCiAgXG8gby9fX19fICAgICAg 164 | eQogICB8dnwgdiB2IFxfX19fLwogICAgVSAgeSAgWSAgdiAgXAogICAgICBc 165 | IFYgICBWIFkgLwogICAgICAgfHxWdlZ2Vnx8CiAgICAgICB8fCAgICAgfHwK`) 166 | var yascii = string(yabytes) 167 | fmt.Printf("\n%s\n", yascii) 168 | } 169 | 170 | func creditsCmd() { 171 | contributors := []string{"Adam Thalhammer", "Amanda Koh", "Dave Schweisguth", "John Murphy", "Kaitlyn Mesa", "Lucas Wilson-Richter", "Michael Vigilante", "Nova Tan", "paul david"} 172 | 173 | fmt.Println("Contributors:") 174 | 175 | for _, contributor := range contributors { 176 | fmt.Printf(" * %s\n", contributor) 177 | } 178 | } 179 | 180 | func initCache() { 181 | viper.SetDefault("cache.file_location", path.Join(getCacheBasePath(), "cache")) 182 | } 183 | 184 | func clearCache() { 185 | os.Remove(viper.GetString("cache.file_location")) 186 | } 187 | 188 | func initConfig() { 189 | viper.AddConfigPath(getConfigPath()) 190 | viper.AddConfigPath(oldConfigPath()) 191 | 192 | viper.SetConfigName("config") 193 | viper.ReadInConfig() 194 | } 195 | 196 | func getCacheBasePath() string { 197 | dataPath := os.Getenv("XDG_CACHE_HOME") 198 | 199 | if dataPath == "" { 200 | home, err := homedir.Dir() 201 | 202 | if err != nil { 203 | fmt.Fprintln(os.Stderr, err) 204 | os.Exit(1) 205 | } 206 | 207 | dataPath = path.Join(home, ".cache") 208 | } 209 | 210 | yakPath := path.Join(dataPath, "yak") 211 | 212 | if _, err := os.Stat(yakPath); os.IsNotExist(err) { 213 | os.MkdirAll(yakPath, 0700) 214 | } 215 | 216 | return yakPath 217 | } 218 | 219 | func oldConfigPath() string { 220 | home, err := homedir.Dir() 221 | 222 | if err != nil { 223 | fmt.Fprintln(os.Stderr, err) 224 | os.Exit(1) 225 | } 226 | 227 | return path.Join(home, ".yak") 228 | } 229 | 230 | func getConfigPath() string { 231 | configPath := os.Getenv("XDG_CONFIG_HOME") 232 | 233 | if configPath == "" { 234 | home, err := homedir.Dir() 235 | 236 | if err != nil { 237 | fmt.Fprintln(os.Stderr, err) 238 | os.Exit(1) 239 | } 240 | 241 | configPath = path.Join(home, ".config") 242 | } 243 | 244 | yakPath := path.Join(configPath, "yak") 245 | 246 | if _, err := os.Stat(yakPath); os.IsNotExist(err) { 247 | os.MkdirAll(yakPath, 0700) 248 | } 249 | 250 | return yakPath 251 | } 252 | 253 | func defaultConfigValues() { 254 | viper.SetDefault("okta.session_cache_limit", 86400) 255 | viper.SetDefault("aws.session_duration", 3600) 256 | viper.SetDefault("output.format", "env") 257 | viper.SetDefault("login.timeout", 180) 258 | } 259 | 260 | func Execute() { 261 | err := rootCmd.Execute() 262 | 263 | if err != nil { 264 | exitError, isExitError := err.(*exec.ExitError) 265 | 266 | if isExitError { 267 | // In this case, we had a subprocess and that subprocess returned an error code; we should return the same 268 | // exit code as it did. 269 | os.Exit(getExitCode(exitError)) 270 | } else { 271 | // In this case, something went wrong, but there was either no subprocess or that subprocess didn't return 272 | // an error code; we should output an error message because it's likely nothing went to stderr. 273 | fmt.Fprintf(os.Stderr, "yak: %v\n", err) 274 | os.Exit(1) 275 | } 276 | } 277 | } 278 | 279 | func getExitCode(err *exec.ExitError) int { 280 | ws := err.Sys().(syscall.WaitStatus) 281 | return ws.ExitStatus() 282 | } 283 | -------------------------------------------------------------------------------- /cmd/shim.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | 6 | "github.com/redbubble/yak/aws" 7 | "github.com/redbubble/yak/cli" 8 | ) 9 | 10 | func shimCmd(cmd *cobra.Command, args []string) error { 11 | roleName, err := cli.ResolveRole(args[0]) 12 | 13 | if err != nil { 14 | return err 15 | } 16 | 17 | command := args[1:] 18 | 19 | creds, err := cli.AssumeRole(roleName) 20 | if err != nil { 21 | return err 22 | } 23 | 24 | return cli.Exec( 25 | command, 26 | cli.EnrichedEnvironment( 27 | aws.EnvironmentVariables(creds), 28 | ), 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing compassion towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior 58 | may be reported by contacting the project team at 59 | delivery-engineers@redbubble.com. All complaints will be reviewed and 60 | investigated and will result in a response that is deemed necessary 61 | and appropriate to the circumstances. The project team is obligated to 62 | maintain confidentiality with regard to the reporter of an incident. 63 | Further details of specific enforcement policies may be posted 64 | separately. 65 | 66 | Project maintainers who do not follow or enforce the Code of Conduct in good 67 | faith may face temporary or permanent repercussions as determined by other 68 | members of the project's leadership. 69 | 70 | ## Attribution 71 | 72 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 73 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 74 | 75 | [homepage]: https://www.contributor-covenant.org 76 | -------------------------------------------------------------------------------- /format/format.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/aws/aws-sdk-go/service/sts" 10 | 11 | "github.com/redbubble/yak/aws" 12 | ) 13 | 14 | var outputFormatters map[string]func(*sts.AssumeRoleWithSAMLOutput) (string, error) = map[string]func(*sts.AssumeRoleWithSAMLOutput) (string, error){ 15 | "json": func(creds *sts.AssumeRoleWithSAMLOutput) (string, error) { 16 | data, err := json.Marshal(creds.Credentials) 17 | 18 | return string(append(data, '\n')), err 19 | }, 20 | "env": func(creds *sts.AssumeRoleWithSAMLOutput) (string, error) { 21 | output := bytes.Buffer{} 22 | 23 | var outputFormat string 24 | if isPowerShell() { 25 | outputFormat = "$env:%s = \"%s\"\n" 26 | } else { 27 | outputFormat = "export %s=%s\n" 28 | } 29 | 30 | for key, value := range aws.EnvironmentVariables(creds) { 31 | output.WriteString(fmt.Sprintf(outputFormat, key, value)) 32 | } 33 | 34 | return output.String(), nil 35 | }, 36 | } 37 | 38 | func Credentials(format string, creds *sts.AssumeRoleWithSAMLOutput) (string, error) { 39 | return outputFormatters[format](creds) 40 | } 41 | 42 | func ValidateOutputFormat(format string) error { 43 | if validOutputFormat(format) { 44 | return nil 45 | } 46 | 47 | return fmt.Errorf("Invalid output format '%s' specified. Valid output formats: %v", format, validOutputFormats()) 48 | } 49 | 50 | func isPowerShell() bool { 51 | _, ok := os.LookupEnv("PSModulePath") 52 | return ok 53 | } 54 | 55 | func validOutputFormat(format string) bool { 56 | for f, _ := range outputFormatters { 57 | if format == f { 58 | return true 59 | } 60 | } 61 | 62 | return false 63 | } 64 | 65 | func validOutputFormats() []string { 66 | formats := make([]string, 0, len(outputFormatters)) 67 | 68 | for format, _ := range outputFormatters { 69 | formats = append(formats, format) 70 | } 71 | 72 | return formats 73 | } 74 | -------------------------------------------------------------------------------- /format/format_test.go: -------------------------------------------------------------------------------- 1 | package format 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/aws/aws-sdk-go/service/sts" 11 | ) 12 | 13 | var accessKeyId string = "llama" 14 | var secretAccessKey string = "alpaca" 15 | var sessionToken string = "guanaco" 16 | var assumedRoleArn string = "arn:aws:iam::1234123123:role/sso-vicuña-role" 17 | 18 | var innerCreds sts.Credentials = sts.Credentials{ 19 | AccessKeyId: &accessKeyId, 20 | SecretAccessKey: &secretAccessKey, 21 | SessionToken: &sessionToken, 22 | } 23 | 24 | var creds sts.AssumeRoleWithSAMLOutput = sts.AssumeRoleWithSAMLOutput{ 25 | AssumedRoleUser: &sts.AssumedRoleUser{ 26 | Arn: &assumedRoleArn, 27 | }, 28 | Credentials: &innerCreds, 29 | } 30 | 31 | func TestDefaultEnvCredentials(t *testing.T) { 32 | scenarios := []struct { 33 | shellName string 34 | expectedLines []string 35 | setUp func() 36 | tearDown func() 37 | }{ 38 | { 39 | shellName: "default shell", 40 | expectedLines: []string{ 41 | fmt.Sprintf(`export AWS_ACCESS_KEY_ID=%s`, accessKeyId), 42 | fmt.Sprintf(`export AWS_SECRET_ACCESS_KEY=%s`, secretAccessKey), 43 | fmt.Sprintf(`export AWS_SESSION_TOKEN=%s`, sessionToken), 44 | fmt.Sprintf(`export AWS_METADATA_USER_ARN=%s`, assumedRoleArn), 45 | }, 46 | setUp: func() { 47 | os.Unsetenv("PSModulePath") 48 | }, 49 | tearDown: func() {}, 50 | }, 51 | { 52 | shellName: "PowerShell", 53 | expectedLines: []string{ 54 | fmt.Sprintf(`$env:AWS_ACCESS_KEY_ID = "%s"`, accessKeyId), 55 | fmt.Sprintf(`$env:AWS_SECRET_ACCESS_KEY = "%s"`, secretAccessKey), 56 | fmt.Sprintf(`$env:AWS_SESSION_TOKEN = "%s"`, sessionToken), 57 | fmt.Sprintf(`$env:AWS_METADATA_USER_ARN = "%s"`, assumedRoleArn), 58 | }, 59 | setUp: func() { 60 | os.Setenv("PSModulePath", "something") 61 | }, 62 | tearDown: func() { 63 | os.Unsetenv("PSModulePath") 64 | }, 65 | }, 66 | } 67 | 68 | for _, scenario := range scenarios { 69 | 70 | t.Run(fmt.Sprintf("should output environment variables correctly for %s", scenario.shellName), func(t *testing.T) { 71 | scenario.setUp() 72 | defer scenario.tearDown() 73 | 74 | text, err := Credentials("env", &creds) 75 | 76 | if err != nil { 77 | t.Log("---------------") 78 | t.Logf("Got an error formatting as \"env\" for %s", scenario.shellName) 79 | t.Logf("Error: %v", err) 80 | t.Fail() 81 | } 82 | 83 | lines := strings.Split(text, "\n") 84 | 85 | for _, expectedLine := range scenario.expectedLines { 86 | ok := false 87 | for _, line := range lines { 88 | if line == expectedLine { 89 | ok = true 90 | break 91 | } 92 | } 93 | 94 | if !ok { 95 | t.Log("---------------") 96 | t.Logf("Failed to format credentials as \"env\" for %s", scenario.shellName) 97 | t.Logf("Expected content: %v", scenario.expectedLines) 98 | t.Logf("Actual content: %v", lines) 99 | t.Fail() 100 | break 101 | } 102 | } 103 | }) 104 | } 105 | } 106 | 107 | func TestJsonCredentials(t *testing.T) { 108 | jsonData, err := Credentials("json", &creds) 109 | 110 | if err != nil { 111 | t.Log("---------------") 112 | t.Log("Got an error formatting as \"json\"") 113 | t.Logf("Error: %v", err) 114 | t.Fail() 115 | } 116 | 117 | receivedCreds := sts.Credentials{} 118 | 119 | err = json.Unmarshal([]byte(jsonData), &receivedCreds) 120 | 121 | if err != nil { 122 | t.Log("---------------") 123 | t.Log("Got an error formatting as \"json\"") 124 | t.Logf("Error: %v", err) 125 | t.Fail() 126 | } 127 | 128 | if *receivedCreds.AccessKeyId != *innerCreds.AccessKeyId || 129 | *receivedCreds.SecretAccessKey != *innerCreds.SecretAccessKey || 130 | *receivedCreds.SessionToken != *innerCreds.SessionToken { 131 | t.Log("---------------") 132 | t.Log("Failed to format credentials as \"json\"") 133 | t.Logf("Expected content: %v", innerCreds) 134 | t.Logf("Actual content: %v", receivedCreds) 135 | t.Fail() 136 | } 137 | } 138 | 139 | func TestValidateOutputFormat(t *testing.T) { 140 | if err := ValidateOutputFormat("env"); err != nil { 141 | t.Log("---------------") 142 | t.Log("Got an error from ValidateOutputFormat when requesting \"env\"") 143 | t.Logf("Error: %v", err) 144 | t.Fail() 145 | } 146 | 147 | if err := ValidateOutputFormat("json"); err != nil { 148 | t.Log("---------------") 149 | t.Log("Got an error from ValidateOutputFormat when requesting \"json\"") 150 | t.Logf("Error: %v", err) 151 | t.Fail() 152 | } 153 | 154 | if ValidateOutputFormat("frankenscript") == nil { 155 | t.Log("---------------") 156 | t.Log("Got no error ValidateOutputFormat when requesting an invalid format") 157 | t.Fail() 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/redbubble/yak 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.44.95 7 | github.com/mitchellh/go-homedir v1.1.0 8 | github.com/patrickmn/go-cache v2.1.0+incompatible 9 | github.com/sirupsen/logrus v1.9.0 10 | github.com/spf13/cobra v1.5.0 11 | github.com/spf13/viper v1.13.0 12 | github.com/twpayne/go-pinentry v0.2.0 13 | golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 14 | golang.org/x/net v0.0.0-20220909164309-bea034e7d591 15 | ) 16 | 17 | require ( 18 | github.com/fsnotify/fsnotify v1.5.4 // indirect 19 | github.com/hashicorp/hcl v1.0.0 // indirect 20 | github.com/inconshreveable/mousetrap v1.0.1 // indirect 21 | github.com/jmespath/go-jmespath v0.4.0 // indirect 22 | github.com/magiconair/properties v1.8.6 // indirect 23 | github.com/mattn/go-colorable v0.1.13 // indirect 24 | github.com/mattn/go-isatty v0.0.16 // indirect 25 | github.com/mitchellh/mapstructure v1.5.0 // indirect 26 | github.com/pelletier/go-toml v1.9.5 // indirect 27 | github.com/pelletier/go-toml/v2 v2.0.5 // indirect 28 | github.com/rs/zerolog v1.28.0 // indirect 29 | github.com/spf13/afero v1.9.2 // indirect 30 | github.com/spf13/cast v1.5.0 // indirect 31 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 32 | github.com/spf13/pflag v1.0.5 // indirect 33 | github.com/subosito/gotenv v1.4.1 // indirect 34 | go.uber.org/atomic v1.10.0 // indirect 35 | go.uber.org/multierr v1.8.0 // indirect 36 | golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 // indirect 37 | golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect 38 | golang.org/x/text v0.3.7 // indirect 39 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 40 | gopkg.in/ini.v1 v1.67.0 // indirect 41 | gopkg.in/yaml.v2 v2.4.0 // indirect 42 | gopkg.in/yaml.v3 v3.0.1 // indirect 43 | ) 44 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 7 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 8 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 9 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 10 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 11 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 12 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 13 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 14 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 15 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 16 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 17 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= 18 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= 19 | cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= 20 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 21 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 22 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 23 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 24 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 25 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 26 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 27 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 28 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 29 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 30 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 31 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 32 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 33 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 34 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 35 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 36 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 37 | cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= 38 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 39 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 40 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 41 | github.com/aws/aws-sdk-go v1.44.78 h1:B/V28YXFLmxjMQqJeyCt7NDRIJdep0sJixIAeee2BF0= 42 | github.com/aws/aws-sdk-go v1.44.78/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= 43 | github.com/aws/aws-sdk-go v1.44.79 h1:IZCtfBq9VlJ1Eu34I+2Y76q+XkvTtZYbEwaoVM1gzoA= 44 | github.com/aws/aws-sdk-go v1.44.79/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= 45 | github.com/aws/aws-sdk-go v1.44.95 h1:QwmA+PeR6v4pF0f/dPHVPWGAshAhb9TnGZBTM5uKuI8= 46 | github.com/aws/aws-sdk-go v1.44.95/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= 47 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 48 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 49 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 50 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 51 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 52 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 53 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 54 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 55 | github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 56 | github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 57 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 58 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 59 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 60 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 61 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 62 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 63 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 64 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 65 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 66 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 67 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= 68 | github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= 69 | github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= 70 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 71 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 72 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 73 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 74 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 75 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 76 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 77 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 78 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 79 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 80 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 81 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 82 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 83 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 84 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 85 | github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= 86 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 87 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 88 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 89 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 90 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 91 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 92 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 93 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 94 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 95 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 96 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 97 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 98 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 99 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 100 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 101 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 102 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 103 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 104 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 105 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 106 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 107 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 108 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 109 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 110 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 111 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 112 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 113 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 114 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 115 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 116 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 117 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 118 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 119 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 120 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 121 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 122 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 123 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 124 | github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 125 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 126 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 127 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 128 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 129 | github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= 130 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 131 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 132 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 133 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 134 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 135 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 136 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 137 | github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= 138 | github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 139 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 140 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 141 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 142 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 143 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 144 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 145 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 146 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 147 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 148 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 149 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 150 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 151 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 152 | github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= 153 | github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= 154 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 155 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 156 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 157 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 158 | github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= 159 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 160 | github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= 161 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 162 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 163 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 164 | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= 165 | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= 166 | github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= 167 | github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= 168 | github.com/pelletier/go-toml/v2 v2.0.3 h1:h9JoA60e1dVEOpp0PFwJSmt1Htu057NUq9/bUwaO61s= 169 | github.com/pelletier/go-toml/v2 v2.0.3/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= 170 | github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= 171 | github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= 172 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 173 | github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 174 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 175 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 176 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 177 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 178 | github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= 179 | github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 180 | github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 181 | github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE= 182 | github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo= 183 | github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs= 184 | github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U= 185 | github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= 186 | github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= 187 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 188 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 189 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 190 | github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= 191 | github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= 192 | github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= 193 | github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= 194 | github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= 195 | github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= 196 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= 197 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 198 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 199 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 200 | github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= 201 | github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= 202 | github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= 203 | github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= 204 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 205 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 206 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 207 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 208 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 209 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 210 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 211 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 212 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 213 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 214 | github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs= 215 | github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo= 216 | github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= 217 | github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= 218 | github.com/twpayne/go-pinentry v0.2.0 h1:hS5NEJiilop9xP9pBX/1NYduzDlGGMdg1KamTBTrOWw= 219 | github.com/twpayne/go-pinentry v0.2.0/go.mod h1:r6buhMwARxnnL0VRBqfd1tE6Fadk1kfP00GRMutEspY= 220 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 221 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 222 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 223 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 224 | github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 225 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 226 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 227 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 228 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 229 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 230 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 231 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= 232 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 233 | go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= 234 | go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 235 | go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec= 236 | go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= 237 | go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= 238 | go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= 239 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 240 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 241 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 242 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 243 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 244 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 245 | golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 246 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= 247 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 248 | golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c= 249 | golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 250 | golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= 251 | golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 252 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 253 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 254 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 255 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 256 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 257 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 258 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 259 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 260 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 261 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 262 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 263 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 264 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 265 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 266 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 267 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 268 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 269 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 270 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 271 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 272 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 273 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 274 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 275 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 276 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 277 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 278 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 279 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 280 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 281 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 282 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 283 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 284 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 285 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 286 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 287 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 288 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 289 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 290 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 291 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 292 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 293 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 294 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 295 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 296 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 297 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 298 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 299 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 300 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 301 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 302 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 303 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 304 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 305 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 306 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 307 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 308 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 309 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 310 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 311 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 312 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 313 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 314 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 315 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 316 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 317 | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 318 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 319 | golang.org/x/net v0.0.0-20220812174116-3211cb980234 h1:RDqmgfe7SvlMWoqC3xwQ2blLO3fcWcxMa3eBLRdRW7E= 320 | golang.org/x/net v0.0.0-20220812174116-3211cb980234/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 321 | golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI= 322 | golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 323 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 324 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 325 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 326 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 327 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 328 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 329 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 330 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 331 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 332 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 333 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 334 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 335 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 336 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 337 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 338 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 339 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 340 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 341 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 342 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 343 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 344 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 345 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 346 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 347 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 348 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 349 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 350 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 351 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 352 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 353 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 354 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 355 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 356 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 357 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 358 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 359 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 360 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 361 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 362 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 363 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 364 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 365 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 366 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 367 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 368 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 369 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 370 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 371 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 372 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 373 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 374 | golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 375 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 376 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 377 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 378 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 379 | golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 380 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 381 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 382 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 383 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 384 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 385 | golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2 h1:fqTvyMIIj+HRzMmnzr9NtpHP6uVpvB5fkHcgPDC4nu8= 386 | golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 387 | golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 h1:wM1k/lXfpc5HdkJJyW9GELpd8ERGdnh8sMGL6Gzq3Ho= 388 | golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 389 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 390 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 391 | golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc= 392 | golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 393 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 394 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 395 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 396 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 397 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 398 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 399 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 400 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 401 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 402 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 403 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 404 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 405 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 406 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 407 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 408 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 409 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 410 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 411 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 412 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 413 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 414 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 415 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 416 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 417 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 418 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 419 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 420 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 421 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 422 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 423 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 424 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 425 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 426 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 427 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 428 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 429 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 430 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 431 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 432 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 433 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 434 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 435 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 436 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 437 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 438 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 439 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 440 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 441 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 442 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 443 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 444 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 445 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 446 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 447 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 448 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 449 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 450 | golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 451 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 452 | golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= 453 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 454 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 455 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 456 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 457 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 458 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 459 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 460 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 461 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 462 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 463 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 464 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 465 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 466 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 467 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 468 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 469 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 470 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 471 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 472 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 473 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 474 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= 475 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 476 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 477 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 478 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 479 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 480 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 481 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 482 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 483 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 484 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 485 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 486 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 487 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 488 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 489 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 490 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 491 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 492 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 493 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 494 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 495 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 496 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 497 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 498 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 499 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 500 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 501 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 502 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 503 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 504 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 505 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 506 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 507 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 508 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 509 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 510 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 511 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 512 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 513 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 514 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 515 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 516 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 517 | google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 518 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 519 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 520 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 521 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 522 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 523 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 524 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 525 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 526 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 527 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 528 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 529 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 530 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 531 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 532 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 533 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= 534 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 535 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 536 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 537 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 538 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 539 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 540 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 541 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 542 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 543 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 544 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 545 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 546 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 547 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 548 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 549 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 550 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 551 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 552 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 553 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 554 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 555 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 556 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 557 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 558 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 559 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 560 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 561 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 562 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 563 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 564 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 565 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 566 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 567 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 568 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 569 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 570 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/spf13/viper" 5 | 6 | "github.com/redbubble/yak/cmd" 7 | ) 8 | 9 | var version string 10 | 11 | func main() { 12 | viper.Set("yak.version", version) 13 | cmd.Execute() 14 | } 15 | -------------------------------------------------------------------------------- /okta/okta.go: -------------------------------------------------------------------------------- 1 | package okta 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "net/http" 12 | "net/http/cookiejar" 13 | "net/url" 14 | "os" 15 | "time" 16 | 17 | log "github.com/sirupsen/logrus" 18 | "golang.org/x/net/html" 19 | ) 20 | 21 | type UserData struct { 22 | Username string `json:"username"` 23 | Password string `json:"password"` 24 | } 25 | 26 | type TotpRequest struct { 27 | StateToken string `json:"stateToken"` 28 | PassCode string `json:"passCode"` 29 | } 30 | 31 | type PushRequest struct { 32 | StateToken string `json:"stateToken"` 33 | } 34 | 35 | type OktaLink struct { 36 | Href string `json:"href"` 37 | } 38 | 39 | type AuthResponseFactorLinks struct { 40 | VerifyLink OktaLink `json:"verify"` 41 | } 42 | 43 | type PushRequestResponseLinks struct { 44 | PollLink OktaLink `json:"next"` 45 | } 46 | 47 | type PushRequestResponse struct { 48 | Links PushRequestResponseLinks `json:"_links"` 49 | FactorResult string `json:"factorResult"` 50 | } 51 | 52 | type AuthResponseFactor struct { 53 | Links AuthResponseFactorLinks `json:"_links"` 54 | FactorType string `json:"factorType"` 55 | Provider string `json:"provider"` 56 | } 57 | 58 | type AuthResponseEmbedded struct { 59 | Factors []AuthResponseFactor `json:"factors"` 60 | } 61 | 62 | const ( 63 | YAK_STATUS_OK = iota 64 | YAK_STATUS_UNAUTHORISED = iota 65 | YAK_STATUS_DATA_ERROR = iota 66 | YAK_STATUS_NET_ERROR = iota 67 | YAK_STATUS_BAD_RESPONSE = iota 68 | ) 69 | 70 | type OktaAuthResponse struct { 71 | StateToken string `json:"stateToken"` 72 | SessionToken string `json:"sessionToken"` 73 | ExpiresAt string `json:"expiresAt"` 74 | Status string `json:"status"` 75 | Embedded AuthResponseEmbedded `json:"_embedded"` 76 | YakStatusCode int 77 | } 78 | 79 | type OktaSession struct { 80 | Id string `json:"id"` 81 | ExpiresAt time.Time `json:"expiresAt"` 82 | } 83 | 84 | // TODO: DRY 85 | func CreateSession(oktaHref string, authResponse OktaAuthResponse) (*OktaSession, error) { 86 | authBody, err := json.Marshal(map[string]string{"sessionToken": authResponse.SessionToken}) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | oktaUrl, err := url.Parse(oktaHref) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | sessionEndpoint, _ := url.Parse("/api/v1/sessions") 97 | sessionUrl := oktaUrl.ResolveReference(sessionEndpoint) 98 | 99 | resp, err := http.Post(sessionUrl.String(), "application/json", bytes.NewBuffer(authBody)) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | body, err := ioutil.ReadAll(resp.Body) 105 | if err != nil { 106 | return nil, err 107 | } 108 | 109 | session := OktaSession{} 110 | if err := json.Unmarshal(body, &session); err != nil { 111 | return nil, err 112 | } 113 | log.WithField("session", session).Debug("okta.go: Created Session from Okta") 114 | return &session, nil 115 | } 116 | 117 | // TODO: DRY 118 | func GetSession(oktaHref string, session *OktaSession) (*OktaSession, error) { 119 | oktaUrl, err := url.Parse(oktaHref) 120 | if err != nil { 121 | return nil, err 122 | } 123 | 124 | sessionEndpoint, _ := url.Parse("/api/v1/sessions/me") 125 | sessionUrl := oktaUrl.ResolveReference(sessionEndpoint) 126 | 127 | jar, _ := cookiejar.New(nil) 128 | jar.SetCookies(sessionUrl, []*http.Cookie{{Name: "sid", Value: session.Id}}) 129 | 130 | client := http.Client{ 131 | Jar: jar, 132 | } 133 | 134 | resp, err := client.Get(sessionUrl.String()) 135 | if err != nil { 136 | return nil, err 137 | } 138 | 139 | if resp.StatusCode > 300 { 140 | return nil, fmt.Errorf("Status code %d, expected < 2xx", resp.StatusCode) 141 | } 142 | 143 | body, err := ioutil.ReadAll(resp.Body) 144 | if err != nil { 145 | return nil, err 146 | } 147 | 148 | newSession := OktaSession{} 149 | if err := json.Unmarshal(body, &newSession); err != nil { 150 | return nil, err 151 | } 152 | log.WithField("session", string(body)).Debug("okta.go: Retrieved Session from Okta") 153 | return &newSession, nil 154 | } 155 | 156 | func Authenticate(oktaHref string, userData UserData) (OktaAuthResponse, error) { 157 | authBody, err := json.Marshal(userData) 158 | 159 | if err != nil { 160 | return OktaAuthResponse{YakStatusCode: YAK_STATUS_DATA_ERROR}, err 161 | } 162 | 163 | oktaUrl, err := url.Parse(oktaHref) 164 | 165 | if err != nil { 166 | return OktaAuthResponse{YakStatusCode: YAK_STATUS_DATA_ERROR}, err 167 | } 168 | 169 | primaryAuthEndpoint, _ := url.Parse("/api/v1/authn") 170 | primaryAuthUrl := oktaUrl.ResolveReference(primaryAuthEndpoint) 171 | 172 | body, yakStatus, err := makeRequest(primaryAuthUrl.String(), bytes.NewBuffer(authBody)) 173 | 174 | if err != nil { 175 | log.WithField("err", err).Debug("okta.go: Okta login error") 176 | return OktaAuthResponse{YakStatusCode: yakStatus}, err 177 | } 178 | 179 | authResponse := OktaAuthResponse{YakStatusCode: YAK_STATUS_OK} 180 | json.Unmarshal(body, &authResponse) 181 | log.WithField("response", authResponse).Debug("okta.go: Auth response for Okta login") 182 | 183 | return authResponse, nil 184 | } 185 | 186 | func VerifyTotp(url string, totpRequestBody TotpRequest) (OktaAuthResponse, error) { 187 | totpJson, err := json.Marshal(totpRequestBody) 188 | 189 | if err != nil { 190 | return OktaAuthResponse{YakStatusCode: YAK_STATUS_DATA_ERROR}, err 191 | } 192 | 193 | body, yakStatus, err := makeRequest(url, bytes.NewBuffer(totpJson)) 194 | 195 | if err != nil { 196 | return OktaAuthResponse{YakStatusCode: yakStatus}, err 197 | } 198 | 199 | authResponse := OktaAuthResponse{YakStatusCode: YAK_STATUS_OK} 200 | json.Unmarshal(body, &authResponse) 201 | 202 | return authResponse, nil 203 | } 204 | 205 | func VerifyPush(url string, pushRequestBody PushRequest) (OktaAuthResponse, error) { 206 | pushJson, err := json.Marshal(pushRequestBody) 207 | 208 | if err != nil { 209 | return OktaAuthResponse{YakStatusCode: YAK_STATUS_DATA_ERROR}, err 210 | } 211 | 212 | body, yakStatus, err := makeRequest(url, bytes.NewBuffer(pushJson)) 213 | 214 | if err != nil { 215 | return OktaAuthResponse{YakStatusCode: yakStatus}, err 216 | } 217 | 218 | pushRequestResponse := PushRequestResponse{} 219 | json.Unmarshal(body, &pushRequestResponse) 220 | 221 | errorsRemaining := 6 222 | fmt.Fprintf(os.Stderr, "Waiting for MFA response") 223 | for { 224 | body, yakStatus, err := makeRequest(pushRequestResponse.Links.PollLink.Href, bytes.NewBuffer(pushJson)) 225 | 226 | authResponse := OktaAuthResponse{YakStatusCode: yakStatus} 227 | 228 | if err != nil { 229 | errorsRemaining-- 230 | if errorsRemaining == 0 { 231 | fmt.Fprintf(os.Stderr, "\nToo many network errors, aborting...") 232 | return authResponse, err 233 | } 234 | continue 235 | } 236 | 237 | json.Unmarshal(body, &authResponse) 238 | 239 | if authResponse.Status != "MFA_CHALLENGE" { 240 | if authResponse.Status == "SUCCESS" { 241 | fmt.Fprintf(os.Stderr, "\n") 242 | return authResponse, nil 243 | } 244 | 245 | fmt.Fprintf(os.Stderr, "\n") 246 | authResponse.YakStatusCode = YAK_STATUS_BAD_RESPONSE 247 | return authResponse, errors.New("Bad status from Okta API: " + authResponse.Status) 248 | } 249 | 250 | fmt.Fprintf(os.Stderr, ".") 251 | 252 | time.Sleep(5 * time.Second) 253 | } 254 | } 255 | 256 | func AwsSamlLogin(oktaHref string, samlHref string, oktasession OktaSession) (string, error) { 257 | oktaUrl, err := url.Parse(oktaHref) 258 | 259 | if err != nil { 260 | return "", err 261 | } 262 | 263 | samlEndpoint, err := url.Parse(samlHref) 264 | 265 | if err != nil { 266 | return "", err 267 | } 268 | 269 | samlUrl := oktaUrl.ResolveReference(samlEndpoint) 270 | 271 | jar, err := cookiejar.New(nil) 272 | jar.SetCookies(samlUrl, []*http.Cookie{{Name: "sid", Value: oktasession.Id}}) 273 | 274 | if err != nil { 275 | return "", err 276 | } 277 | 278 | client := http.Client{ 279 | Jar: jar, 280 | } 281 | 282 | resp, err := client.Get(samlUrl.String()) 283 | 284 | if err != nil { 285 | return "", err 286 | } else if resp.StatusCode >= 300 { 287 | return "", errors.New("Could not get SAML payload" + resp.Status + ")") 288 | } 289 | 290 | body, err := ioutil.ReadAll(resp.Body) 291 | 292 | if err != nil { 293 | return "", err 294 | } 295 | 296 | data, err := extractSamlPayload(body) 297 | 298 | if err != nil { 299 | return "", err 300 | } 301 | 302 | saml, err := base64.StdEncoding.DecodeString(data) 303 | 304 | if err != nil { 305 | return "", err 306 | } 307 | 308 | return string(saml), nil 309 | } 310 | 311 | func makeRequest(url string, body io.Reader) ([]byte, int, error) { 312 | resp, err := http.Post(url, "application/json", body) 313 | if resp != nil { 314 | log.WithField("url", url).WithField("statusCode", resp.StatusCode).Debug("okta.go: Okta request") 315 | } else { 316 | log.WithField("url", url).Debug("okta.go: Okta returned a nil response") 317 | } 318 | 319 | if err != nil { 320 | return []byte{}, YAK_STATUS_NET_ERROR, err 321 | } else if resp.StatusCode == 401 || resp.StatusCode == 403 { 322 | return []byte{}, YAK_STATUS_UNAUTHORISED, errors.New("Unauthorised (" + resp.Status + ")") 323 | } else if resp.StatusCode >= 300 { 324 | return []byte{}, YAK_STATUS_NET_ERROR, errors.New("Network error (" + resp.Status + ")") 325 | } 326 | 327 | defer resp.Body.Close() 328 | 329 | responseBody, err := ioutil.ReadAll(resp.Body) 330 | 331 | if err != nil { 332 | return responseBody, YAK_STATUS_BAD_RESPONSE, err 333 | } 334 | 335 | return responseBody, YAK_STATUS_OK, err 336 | } 337 | 338 | func extractSamlPayload(htmlDocument []byte) (string, error) { 339 | tokeniser := html.NewTokenizer(bytes.NewBuffer(htmlDocument)) 340 | 341 | var data string 342 | 343 | for { 344 | tokeniser.Next() 345 | token := tokeniser.Token() 346 | 347 | if token.Type == html.ErrorToken { 348 | return "", errors.New("No SAML payload found in response from Okta") 349 | } 350 | 351 | if (token.Type == html.SelfClosingTagToken || token.Type == html.StartTagToken) && token.Data == "input" { 352 | var inputName string 353 | var inputValue string 354 | 355 | for _, attribute := range token.Attr { 356 | if attribute.Key == "name" { 357 | inputName = attribute.Val 358 | } 359 | 360 | if attribute.Key == "value" { 361 | inputValue = attribute.Val 362 | } 363 | } 364 | 365 | if inputName == "SAMLResponse" { 366 | data = inputValue 367 | break 368 | } 369 | } 370 | } 371 | 372 | return data, nil 373 | } 374 | 375 | func TotpFactorName(key string) string { 376 | switch key { 377 | case "GOOGLE": 378 | return "Google Authenticator" 379 | default: 380 | return key 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /saml/saml.go: -------------------------------------------------------------------------------- 1 | package saml 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/xml" 6 | "fmt" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | type samlResponse struct { 12 | Assertion samlAssertion `xml:"Assertion"` 13 | } 14 | 15 | type samlAssertion struct { 16 | Attributes []samlAssertionAttribute `xml:"AttributeStatement>Attribute"` 17 | Conditions samlAssertionConditions `xml:Conditions` 18 | } 19 | 20 | type samlAssertionConditions struct { 21 | NotBefore time.Time `xml:"NotBefore,attr"` 22 | NotOnOrAfter time.Time `xml:"NotOnOrAfter,attr"` 23 | } 24 | 25 | type samlAssertionAttribute struct { 26 | Name string `xml:"Name,attr"` 27 | Values []string `xml:"AttributeValue"` 28 | } 29 | 30 | type LoginRole struct { 31 | RoleArn string 32 | PrincipalArn string 33 | } 34 | 35 | type LoginData struct { 36 | Roles []LoginRole 37 | Assertion string 38 | } 39 | 40 | func ParseResponse(saml string) (samlResponse, error) { 41 | var response samlResponse 42 | err := xml.Unmarshal([]byte(saml), &response) 43 | 44 | return response, err 45 | } 46 | 47 | func CreateLoginData(response samlResponse, payload string) LoginData { 48 | login := LoginData{ 49 | Roles: []LoginRole{}, 50 | Assertion: base64.StdEncoding.EncodeToString([]byte(payload)), 51 | } 52 | 53 | for _, attribute := range response.Assertion.Attributes { 54 | if attribute.Name == "https://aws.amazon.com/SAML/Attributes/Role" { 55 | for _, value := range attribute.Values { 56 | role, ok := CreateLoginRole(value) 57 | 58 | if ok { 59 | login.Roles = append(login.Roles, role) 60 | } 61 | } 62 | } 63 | } 64 | 65 | return login 66 | } 67 | 68 | func CreateLoginRole(roleData string) (LoginRole, bool) { 69 | parts := strings.Split(roleData, ",") 70 | 71 | if len(parts) == 2 { 72 | return LoginRole{ 73 | RoleArn: parts[1], 74 | PrincipalArn: parts[0], 75 | }, true 76 | } else { 77 | return LoginRole{}, false 78 | } 79 | } 80 | 81 | func SerialiseLoginRole(role LoginRole) string { 82 | return fmt.Sprintf("%s,%s", role.PrincipalArn, role.RoleArn) 83 | } 84 | 85 | func (login LoginData) GetLoginRole(roleArn string) (LoginRole, error) { 86 | for _, role := range login.Roles { 87 | if role.RoleArn == roleArn { 88 | return role, nil 89 | } 90 | } 91 | 92 | return LoginRole{}, fmt.Errorf("ARN %s is not in the list of available roles for this user", roleArn) 93 | } 94 | -------------------------------------------------------------------------------- /saml/saml_test.go: -------------------------------------------------------------------------------- 1 | package saml 2 | 3 | import ( 4 | "encoding/base64" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestXMLUnmarshal(t *testing.T) { 10 | xml := ` 11 | 12 | 13 | 14 | 15 | 16 | llama 17 | 18 | 19 | manchego 20 | reypenaer 21 | 22 | 23 | 24 | ` 25 | 26 | response, err := ParseResponse(xml) 27 | 28 | if err != nil { 29 | t.Log("---------------") 30 | t.Log("Failed to parse SAML at all!") 31 | t.Errorf("Got error: %s", err) 32 | } 33 | 34 | conditions := response.Assertion.Conditions 35 | 36 | expectedNotBefore, _ := time.Parse(time.RFC3339, "2018-04-04T23:49:54.598Z") 37 | 38 | if conditions.NotBefore != expectedNotBefore { 39 | t.Log("---------------") 40 | t.Log("Failed to parse the NotBefore condition") 41 | t.Logf("Expected: %s", expectedNotBefore) 42 | t.Logf("Got: %s", conditions.NotBefore) 43 | t.Fail() 44 | } 45 | 46 | expectedNotOnOrAfter, _ := time.Parse(time.RFC3339, "2018-04-04T23:59:54.598Z") 47 | 48 | if conditions.NotOnOrAfter != expectedNotOnOrAfter { 49 | t.Log("---------------") 50 | t.Log("Failed to parse the NotOnOrAfter condition") 51 | t.Logf("Expected: %s", expectedNotOnOrAfter) 52 | t.Logf("Got: %s", conditions.NotOnOrAfter) 53 | t.Fail() 54 | } 55 | 56 | attributes := response.Assertion.Attributes 57 | 58 | expectedAttributes := []samlAssertionAttribute{ 59 | samlAssertionAttribute{"camelid", []string{"llama"}}, 60 | samlAssertionAttribute{"cheeses", []string{"manchego", "reypenaer"}}, 61 | } 62 | 63 | if len(attributes) != len(expectedAttributes) { 64 | t.Log("---------------") 65 | t.Log("Parsed the wrong number of attributes from the XML!") 66 | t.Logf("Expected: %d", len(expectedAttributes)) 67 | t.Logf("Got: %d", len(attributes)) 68 | t.Fail() 69 | } 70 | 71 | for index, attribute := range attributes { 72 | expectedAttribute := expectedAttributes[index] 73 | 74 | if attribute.Name != expectedAttribute.Name { 75 | t.Log("---------------") 76 | t.Logf("Did not correctly parse out name of attribute #%d", index) 77 | t.Logf("Expected: %s", expectedAttribute.Name) 78 | t.Logf("Got: %s", attribute.Name) 79 | t.Fail() 80 | } 81 | 82 | if len(attribute.Values) != len(expectedAttribute.Values) { 83 | t.Log("---------------") 84 | t.Logf("Did not correctly parse out the values of attribute #%d", index) 85 | t.Logf("Expected: %s", expectedAttribute.Values) 86 | t.Logf("Got: %s", attribute.Values) 87 | t.Fail() 88 | } 89 | 90 | for vIndex, value := range attribute.Values { 91 | expectedValue := expectedAttribute.Values[vIndex] 92 | 93 | if value != expectedValue { 94 | t.Log("---------------") 95 | t.Logf("Did not correctly parse out the values of attribute #%d", index) 96 | t.Logf("Expected: %s", attribute.Values) 97 | t.Logf("Got: %s", expectedAttribute.Values) 98 | t.Fail() 99 | } 100 | } 101 | } 102 | } 103 | 104 | func TestCreateLoginData(t *testing.T) { 105 | payload := "abloboftext" 106 | encodedPayload := base64.StdEncoding.EncodeToString([]byte(payload)) 107 | roleAttribute := samlAssertionAttribute{ 108 | Name: "https://aws.amazon.com/SAML/Attributes/Role", 109 | Values: []string{ 110 | "cheese,manchego", 111 | "cheese,reypenaer", 112 | }, 113 | } 114 | 115 | response := samlResponse{ 116 | Assertion: samlAssertion{ 117 | Conditions: samlAssertionConditions{}, 118 | Attributes: []samlAssertionAttribute{ 119 | roleAttribute, 120 | }, 121 | }, 122 | } 123 | 124 | subject := CreateLoginData(response, payload) 125 | 126 | if subject.Assertion != encodedPayload { 127 | t.Log("---------------") 128 | t.Log("Did not correctly encode the SAML assertion") 129 | t.Logf("Expected: %s", encodedPayload) 130 | t.Logf("Got: %s", subject.Assertion) 131 | t.Fail() 132 | } 133 | 134 | if len(subject.Roles) != len(roleAttribute.Values) { 135 | t.Log("---------------") 136 | t.Log("Did not correctly translate the list of roles ") 137 | t.Logf("Expected length: %d", len(roleAttribute.Values)) 138 | t.Logf("Got: %s", subject.Roles) 139 | t.Fail() 140 | } 141 | 142 | for index, value := range roleAttribute.Values { 143 | role := subject.Roles[index] 144 | actualRoleString := role.PrincipalArn + "," + role.RoleArn 145 | 146 | if actualRoleString != value { 147 | t.Log("---------------") 148 | t.Log("A role was not correctly parsed out of the SAML response") 149 | t.Logf("Expected: %s", value) 150 | t.Logf("Got: %s", actualRoleString) 151 | t.Fail() 152 | } 153 | } 154 | } 155 | 156 | func TestGetLoginRole(t *testing.T) { 157 | expectedRole := LoginRole{ 158 | RoleArn: "aws:arn:llama", 159 | PrincipalArn: "aws:arn:principal", 160 | } 161 | 162 | subject := LoginData{ 163 | Roles: []LoginRole{ 164 | expectedRole, 165 | }, 166 | } 167 | 168 | actualRole, err := subject.GetLoginRole("aws:arn:llama") 169 | 170 | if err != nil { 171 | t.Log("---------------") 172 | t.Log("GetLoginRole didn't return the correct role!") 173 | t.Logf("Expected: %s", expectedRole.RoleArn) 174 | t.Log("Got nothing!") 175 | t.Fail() 176 | } else if actualRole != expectedRole { 177 | t.Log("---------------") 178 | t.Log("GetLoginRole didn't return the correct role!") 179 | t.Logf("Expected: %s", expectedRole.RoleArn) 180 | t.Logf("Got: %s", actualRole.RoleArn) 181 | t.Fail() 182 | } 183 | 184 | actualRole, err = subject.GetLoginRole("aws:arn:notarole") 185 | 186 | if err == nil { 187 | t.Log("---------------") 188 | t.Log("GetLoginRole returned a role when it shouldn't have!") 189 | t.Log("Expected nothing") 190 | t.Logf("Got: %s", actualRole.RoleArn) 191 | t.Fail() 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /static/completions/yak.bash: -------------------------------------------------------------------------------- 1 | _yak() 2 | { 3 | local cur prev opts roles cmd_offset nonoption_count 4 | COMPREPLY=() 5 | cur="${COMP_WORDS[COMP_CWORD]}" 6 | prev="${COMP_WORDS[COMP_CWORD-1]}" 7 | opts="-h --help -l --list-roles -u --okta-username --okta-domain --okta-aws-saml-endpoint -d --aws-session-duration --no-cache --cache-only --version" 8 | roles=$(yak --list-roles --cache-only 2>/dev/null) 9 | 10 | nonoption_count=0 11 | 12 | for (( i=1; i <= COMP_CWORD; i++ )); do 13 | if [[ ${COMP_WORDS[i]} != -* ]]; then 14 | (( nonoption_count++ )) 15 | if [[ $nonoption_count == 2 ]]; then 16 | cmd_offset=$i 17 | fi 18 | fi 19 | done 20 | 21 | if [[ $nonoption_count < 2 ]]; then 22 | if [[ ${cur} == -* ]] ; then 23 | COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) 24 | return 0 25 | else 26 | COMPREPLY=( $(compgen -W "${roles}" -- ${cur}) ) 27 | return 0 28 | fi 29 | else 30 | local root_command=${COMP_WORDS[cmd_offset]} 31 | _command_offset $cmd_offset 32 | return 0 33 | fi 34 | } 35 | complete -F _yak yak 36 | -------------------------------------------------------------------------------- /static/completions/yak.zsh: -------------------------------------------------------------------------------- 1 | #compdef _yak yak 2 | 3 | function _yak { 4 | local roles=($(yak --list-roles --cache-only 2>/dev/null)) 5 | _arguments -S \ 6 | '-h[Display this help message and exit]' \ 7 | '--help[Display this help message and exit]' \ 8 | '-l[List available AWS roles and exit]' \ 9 | '--list-roles[List available AWS roles and exit]' \ 10 | '-u[Your Okta username]:username' \ 11 | '--okta-username[Your Okta username]:username' \ 12 | '--okta-domain[The domain to use for requests to Okta]:domain' \ 13 | '--okta-aws-saml-endpoint[The app embed path for the AWS app within Okta]:path' \ 14 | '-d[The session duration to request from AWS (in seconds)]:duration' \ 15 | '--aws-session-duration[The session duration to request from AWS (in seconds)]:duration' \ 16 | '--no-cache[Ignore cache for this request. Mutually exclusive with --cache-only]' \ 17 | '--cache-only[Only use cache, do not make external requests. Mutually exclusive with --no-cache]' \ 18 | '--version[Print the current version and exit]' \ 19 | '1:environment:(${roles})' '*:::command:_normal' 20 | } 21 | -------------------------------------------------------------------------------- /static/delivery-engineers.pub.asc: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQINBGFD6UQBEADGDmwORp36hYdbw3c9QVXG0uRUYQvfC3tbasCjo74RFaF3sCLY 4 | j29OZTy9M4D8dEjko4Qzk9tR+tQOvoE4oABEVYiLyf78eNQbBXSz5FTBHNV74P+j 5 | 5UdtaT8EeV192ZbqW9tdZzl+QBeGTV74xXBXdiBSOfxArWwZnQB3OeyXoxWT5Ntt 6 | 83jE8TYDSD9w6HyOrhq5URZJq3FnBZOnfJkzC9n8H8k4xck5xzuvoUzp9C5trMgf 7 | vHsopsuNLHuE6tpokntav1zs5Hh1jys+Fhv9N9Lte1HviOFzfBCBPBT17PFwXxu8 8 | oCx43eh1zr2BLx+odxcgWDgkvpb4VC1WT/CsyYFJLx1Vol1mSQOGCcdcESLJOLZy 9 | qVYFj1UWwNILHbWGMWK0qiD9AZDIq/V/Incb2hlSsgRnDH984nksnPaFIGmJwA/S 10 | IOSoIzps4pErNfRFUPU+OQxtDmdDaVHfC6fQx6Ek1VbceiyLFpLjQfOCzphVvEiW 11 | IKYKs64VqiZe/Sdjo4xxkHZX5vtdC8mt5zoYmNSiGscm8DEaS6B4Lcb2ed6mno+q 12 | eZwSyEkdb1i46ajjIPXtmUhUNMaAIK8WJTvpV2XMfV68rzurpVco19T5Bed037Hf 13 | nq3GU/CC9btA4Fpdoa049BEVBSNQRx+cJbq7v4US0eZSB2TZG1BheMQwGwARAQAB 14 | tFFEZWxpdmVyeSAmIFJlbGlhYmlsaXR5IEVuZ2luZWVyaW5nIChSZWRidWJibGUp 15 | IDxkZWxpdmVyeS1lbmdpbmVlcnNAcmVkYnViYmxlLmNvbT6JAlQEEwEKAD4WIQRn 16 | 15zsCbhPuxpw38OHeBfkQfT5sAUCYUPpRAIbAwUJA8JnAAULCQgHAwUVCgkICwUW 17 | AgMBAAIeBQIXgAAKCRCHeBfkQfT5sHDgD/9Q+QVpq9l/Jv3GuYAhpNjBdFqvYz7k 18 | SB7bFL82P6ghSc5PEP1kMgqhArZxu03Vqvti2UZ8VupNTM8rVlPwdfHQOkaifu7M 19 | XbDmwD6jCrjkm9q+7jkpKrffFYx6HoRTIt36gTqrzuw/2FNyAgm5yDXIksiIuIrU 20 | UuORlbTqA4xakhw62a5JmrAFshY6n/Q+P2zY+Z6PidM2TVhsFSGWOIi+4DrdfGOP 21 | VxLM1SmYcX3NCHtdkXoxI6uKUuR3ygduqQymb85fhtEVSbv9GE9LcjLpJ5hRQVSj 22 | BJSh94KYLslwXulGnVXTH+kgW528BHTI1jK73nhPKUdnm/gh7puTIg3YMt4Zrxvd 23 | 0RPNofT4ShzmY+DLI+6DRBZwOfIsTHod6NNwavSDqfhQ1K/9e+bCoCzxdpu7Djo7 24 | r9QAVhDmLuzI4v8sbyLnhqwdNn3/MY5FXukz3X2STB94szFeiMLCyztgqiA7Udl5 25 | GD5KZpmIWCtdCRM2ihby5o9IMazXhyxtdLW0w59mt4Gsmosg1J6zyNJSlMD+mZcJ 26 | Km+/Tpc+9L1o51wl7ysLrHT2+/hIHokBWT6F4XcAwbx8jzkYYWrUsqg19DdAzaky 27 | PrVT0Y0z/5dOwUioDhp/VC4bvnCYerHCNXu6U2pmdOy7M1LjjCgeG85p1G0Meis2 28 | DRW6bSvN+QchILkCDQRhQ+lEARAAp6qwAT82UOwzwsDG+brEflbClrUDXTidDb6h 29 | XxjDrRWilUUm5Jrn2RtsPN7WCNVJyQ1ZUc4xeRnBPQ/qHbJASHMQHDz4QzIM6fhe 30 | u0lZNiVcaRnDQufUzhCgxpDPEcpGvX32tF001gMEkcpIQsUGQKJZWACmdtzGM4d4 31 | MaiMS5azejiIB+lCdPy9tZZ1US3jHGd+bQ/id3X0pqM/acPyG6ahkBXERYwHDWve 32 | 7VYxp2mconVaElKA+UkZmbDgc9Tp6FDTXN4au/P9ur/D3XBc6q1Mu3KwaNe7R3qh 33 | FNYji0pvmrC+8mS/Mtm9U+YSwsafTYoEJMjn+lB4oiLFBPe3oMUYjapURPrf+k/Z 34 | 4TuvvJsCuvw5d80F4WPPvFqms1R/1LtCeTx5buaTfr1LVsf7IR2t2zm2ce+Kf15v 35 | 4wcrSQCsC9iWlX9ks+RAxNFmqZ5nhv/jTYAcxeCm6c8o3qBCFPAGz43Lvvp8f0Vg 36 | xwNpNM3hiAwzQAP2rA2nxBNABhawoQRwgeCpgqxrTlSNRmu9ZPovCBfwPttWt+by 37 | SFTtxcjkLCPwV0dvk+GIskjvE/M6niBtMKORgS2af1BK+jniXa5KqkdfzkZdHOjL 38 | PssRHvF4kYIGkFdjtyI3BkdhNezw8EhsOnZq+UIoG5QShAjwmAM9l44Sebu39Fjx 39 | JA/Bz6EAEQEAAYkCPAQYAQoAJhYhBGfXnOwJuE+7GnDfw4d4F+RB9PmwBQJhQ+lE 40 | AhsMBQkDwmcAAAoJEId4F+RB9Pmw90YQAJ4vBbWjfafvPQ1JgwGrciPwwmU8Uwh7 41 | HPzNHLpXxrFQexjRWF4Bm83myhPh2U6OubP7E3oksNy/k3VU8dhVi6J2KCYTRc24 42 | Q/1VAaSaNktft8Yer6MJy657gsx2A9ovvgd6A2+/qgP/p1GYvnwj0PueKzaQliLq 43 | eL3xCTcCh/+SUSMCK50q64uGvlc1zAlheA4Cz6DGlGo/KWBykHw0mA0iqosc4EBI 44 | unqPTGmHN1/ra8/hKorjfYYVqnlpnLGQE/io6xnAh+urc1rbxoU7apqX5YGsm5SJ 45 | 6u0qPw3szXxLljhn80/GTUfD90ZbixfVu/jNIUfuIX+LvpIQ/4e1VhEgClqhCQmR 46 | Fm1KQgIDRAzt3BBntLA0qDiyNY2AdKmCowFZ6pSbot4Ja1S38tyn3Cn23jK6nGyA 47 | dYy2OdvzjITyty2VHPisyYTa/cBjBdtMdHm7b3fPeIsOuxoc0IhwgzayuljsOg+1 48 | UH+HRokTRJYAwfg7erNKLkC3avy+l4uIgXuPU/ZOW0BT9g6jCSZcsxVfr5hFLA+i 49 | JqDAz/1oh1Gjqfr54nK9zeGXZsFWrcuhNlECd9mEnm3J/mnnqtBwLKsXsv3IQoYi 50 | nniTvyvy4stngyniSpL4Jv+EfQj1qN7IDZefVbaGfilVgt31zQh3T9eRpqUbv4br 51 | fH5/1+U8v9B/ 52 | =ZaXU 53 | -----END PGP PUBLIC KEY BLOCK----- 54 | --------------------------------------------------------------------------------