Showing preview only (250K chars total). Download the full file or copy to clipboard to get everything.
Repository: 99designs/aws-vault
Branch: master
Commit: 74e2f7ac256f
Files: 78
Total size: 232.0 KB
Directory structure:
gitextract_qqh0xl3v/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ └── bug_report.md
│ └── workflows/
│ ├── go.yml
│ └── stale.yml
├── .gitignore
├── .golangci.yml
├── LICENSE
├── Makefile
├── README.md
├── USAGE.md
├── bin/
│ └── create-dmg
├── cli/
│ ├── add.go
│ ├── add_test.go
│ ├── clear.go
│ ├── exec.go
│ ├── exec_test.go
│ ├── export.go
│ ├── export_test.go
│ ├── global.go
│ ├── list.go
│ ├── list_test.go
│ ├── login.go
│ ├── proxy.go
│ ├── remove.go
│ └── rotate.go
├── contrib/
│ ├── _aws-vault-proxy/
│ │ ├── Dockerfile
│ │ ├── docker-compose.yml
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── main.go
│ ├── completions/
│ │ ├── bash/
│ │ │ └── aws-vault.bash
│ │ ├── fish/
│ │ │ └── aws-vault.fish
│ │ └── zsh/
│ │ └── aws-vault.zsh
│ ├── docker/
│ │ └── Dockerfile
│ └── scripts/
│ ├── aws-configure-with-env-vars.sh
│ ├── aws-iam-create-yubikey-mfa.sh
│ └── aws-iam-resync-yubikey-mfa.sh
├── go.mod
├── go.sum
├── iso8601/
│ ├── iso8601.go
│ └── iso8601_test.go
├── main.go
├── prompt/
│ ├── kdialog.go
│ ├── osascript.go
│ ├── prompt.go
│ ├── terminal.go
│ ├── wincredui_windows.go
│ ├── ykman.go
│ └── zenity.go
├── server/
│ ├── ec2alias_bsd.go
│ ├── ec2alias_linux.go
│ ├── ec2alias_windows.go
│ ├── ec2proxy.go
│ ├── ec2proxy_default.go
│ ├── ec2proxy_unix.go
│ ├── ec2server.go
│ ├── ecsserver.go
│ └── httplog.go
└── vault/
├── assumeroleprovider.go
├── assumerolewithwebidentityprovider.go
├── cachedsessionprovider.go
├── config.go
├── config_test.go
├── credentialkeyring.go
├── credentialprocessprovider.go
├── credentialprocessprovider_test.go
├── executeprocess.go
├── federationtokenprovider.go
├── getuser.go
├── keyringprovider.go
├── mfa.go
├── oidctokenkeyring.go
├── sessionkeyring.go
├── sessionkeyring_test.go
├── sessiontokenprovider.go
├── ssorolecredentialsprovider.go
├── stsendpointresolver.go
├── vault.go
└── vault_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
- [ ] I am using the latest release of AWS Vault
- [ ] I have provided my `.aws/config` (redacted if necessary)
- [ ] I have provided the debug output using `aws-vault --debug` (redacted if necessary)
================================================
FILE: .github/workflows/go.yml
================================================
name: Continuous Integration
on:
push:
pull_request:
branches:
- master
permissions:
contents: read
jobs:
test:
name: test
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-go@v3
with:
go-version: '1.20'
- uses: actions/checkout@v3
- name: Run tests
run: go test -race ./...
lint:
permissions:
contents: read # for actions/checkout to fetch code
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
name: lint
strategy:
matrix:
os: [macos-latest, ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-go@v3
with:
go-version: '1.20'
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3.4.0
with:
version: v1.52.0
================================================
FILE: .github/workflows/stale.yml
================================================
# See https://github.com/actions/stale
name: Mark and close stale issues
on:
schedule:
- cron: '15 10 * * *'
jobs:
stale:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/stale@v7
with:
days-before-stale: 180
days-before-close: 7
stale-issue-message: 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.'
exempt-issue-labels: pinned,security,feature
================================================
FILE: .gitignore
================================================
/aws-vault
/aws-vault-*
/SHA256SUMS
================================================
FILE: .golangci.yml
================================================
linters:
enable:
- bodyclose
- contextcheck
- depguard
- durationcheck
- dupl
- errchkjson
- errname
- exhaustive
- exportloopref
- gofmt
- goimports
- makezero
- misspell
- nakedret
- nilerr
- nilnil
- noctx
- prealloc
- revive
# - rowserrcheck
- thelper
- tparallel
- unconvert
- unparam
# - wastedassign
- whitespace
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015 99designs
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:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
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.
================================================
FILE: Makefile
================================================
VERSION=$(shell git describe --tags --candidates=1 --dirty)
BUILD_FLAGS=-ldflags="-X main.Version=$(VERSION)" -trimpath
CERT_ID ?= Developer ID Application: 99designs Inc (NRM9HVJ62Z)
SRC=$(shell find . -name '*.go') go.mod
INSTALL_DIR ?= ~/bin
.PHONY: binaries clean release install
ifeq ($(shell uname), Darwin)
aws-vault: $(SRC)
go build -ldflags="-X main.Version=$(VERSION)" -o $@ .
codesign --options runtime --timestamp --sign "$(CERT_ID)" $@
else
aws-vault: $(SRC)
go build -ldflags="-X main.Version=$(VERSION)" -o $@ .
endif
install: aws-vault
mkdir -p $(INSTALL_DIR)
rm -f $(INSTALL_DIR)/aws-vault
cp -a ./aws-vault $(INSTALL_DIR)/aws-vault
binaries: aws-vault-linux-amd64 aws-vault-linux-arm64 aws-vault-linux-ppc64le aws-vault-linux-arm7 aws-vault-darwin-amd64 aws-vault-darwin-arm64 aws-vault-windows-386.exe aws-vault-windows-arm64.exe aws-vault-freebsd-amd64
dmgs: aws-vault-darwin-amd64.dmg aws-vault-darwin-arm64.dmg
clean:
rm -f ./aws-vault ./aws-vault-*-* ./SHA256SUMS
release: binaries dmgs SHA256SUMS
@echo "\nTo create a new release run:\n\n gh release create --title $(VERSION) $(VERSION) \
aws-vault-darwin-amd64.dmg \
aws-vault-darwin-arm64.dmg \
aws-vault-freebsd-amd64 \
aws-vault-linux-amd64 \
aws-vault-linux-arm64 \
aws-vault-linux-arm7 \
aws-vault-linux-ppc64le \
aws-vault-windows-386.exe \
aws-vault-windows-arm64.exe \
SHA256SUMS\n"
@echo "\nTo update homebrew-cask run:\n\n brew bump-cask-pr --version $(shell echo $(VERSION) | sed 's/v\(.*\)/\1/') aws-vault\n"
aws-vault-darwin-amd64: $(SRC)
GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 SDKROOT=$(shell xcrun --sdk macosx --show-sdk-path) go build $(BUILD_FLAGS) -o $@ .
aws-vault-darwin-arm64: $(SRC)
GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 SDKROOT=$(shell xcrun --sdk macosx --show-sdk-path) go build $(BUILD_FLAGS) -o $@ .
aws-vault-freebsd-amd64: $(SRC)
GOOS=freebsd GOARCH=amd64 go build $(BUILD_FLAGS) -o $@ .
aws-vault-linux-amd64: $(SRC)
GOOS=linux GOARCH=amd64 go build $(BUILD_FLAGS) -o $@ .
aws-vault-linux-arm64: $(SRC)
GOOS=linux GOARCH=arm64 go build $(BUILD_FLAGS) -o $@ .
aws-vault-linux-ppc64le: $(SRC)
GOOS=linux GOARCH=ppc64le go build $(BUILD_FLAGS) -o $@ .
aws-vault-linux-arm7: $(SRC)
GOOS=linux GOARCH=arm GOARM=7 go build $(BUILD_FLAGS) -o $@ .
aws-vault-windows-386.exe: $(SRC)
GOOS=windows GOARCH=386 go build $(BUILD_FLAGS) -o $@ .
aws-vault-windows-arm64.exe: $(SRC)
GOOS=windows GOARCH=arm64 go build $(BUILD_FLAGS) -o $@ .
aws-vault-darwin-amd64.dmg: aws-vault-darwin-amd64
./bin/create-dmg aws-vault-darwin-amd64 $@
aws-vault-darwin-arm64.dmg: aws-vault-darwin-arm64
./bin/create-dmg aws-vault-darwin-arm64 $@
SHA256SUMS: binaries dmgs
shasum -a 256 \
aws-vault-darwin-amd64.dmg \
aws-vault-darwin-arm64.dmg \
aws-vault-freebsd-amd64 \
aws-vault-linux-amd64 \
aws-vault-linux-arm64 \
aws-vault-linux-arm7 \
aws-vault-linux-ppc64le \
aws-vault-windows-386.exe \
aws-vault-windows-arm64.exe \
> $@
================================================
FILE: README.md
================================================
# AWS Vault
[](https://github.com/99designs/aws-vault/releases)
[](https://github.com/99designs/aws-vault/actions)
> [!WARNING]
> This project has been abandoned and it's not receiving any more updates. If you want to continue to receive updates
> or contribute, please feel free to look at the active fork at: https://github.com/ByteNess/aws-vault
AWS Vault is a tool to securely store and access AWS credentials in a development environment.
AWS Vault stores IAM credentials in your operating system's secure keystore and then generates temporary credentials from those to expose to your shell and applications. It's designed to be complementary to the AWS CLI tools, and is aware of your [profiles and configuration in `~/.aws/config`](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#cli-config-files).
Check out the [announcement blog post](https://99designs.com.au/tech-blog/blog/2015/10/26/aws-vault/) for more details.
## Installing
You can install AWS Vault:
- by downloading the [latest release](https://github.com/99designs/aws-vault/releases/latest)
- on macOS with [Homebrew](https://formulae.brew.sh/formula/aws-vault): `brew install aws-vault`
- on macOS with [MacPorts](https://ports.macports.org/port/aws-vault/summary): `port install aws-vault`
- on Windows with [Chocolatey](https://chocolatey.org/packages/aws-vault): `choco install aws-vault`
- on Windows with [Scoop](https://scoop.sh/): `scoop install aws-vault`
- on Linux with [Homebrew on Linux](https://formulae.brew.sh/formula/aws-vault): `brew install aws-vault`
- on [Arch Linux](https://www.archlinux.org/packages/community/x86_64/aws-vault/): `pacman -S aws-vault`
- on [Gentoo Linux](https://github.com/gentoo/guru/tree/master/app-admin/aws-vault): `emerge --ask app-admin/aws-vault` ([enable Guru first](https://wiki.gentoo.org/wiki/Project:GURU/Information_for_End_Users))
- on [FreeBSD](https://www.freshports.org/security/aws-vault/): `pkg install aws-vault`
- on [OpenSUSE](https://software.opensuse.org/package/aws-vault): enable devel:languages:go repo then `zypper install aws-vault`
- with [Nix](https://search.nixos.org/packages?show=aws-vault&query=aws-vault): `nix-env -i aws-vault`
- with [asdf-vm](https://github.com/karancode/asdf-aws-vault): `asdf plugin-add aws-vault https://github.com/karancode/asdf-aws-vault.git && asdf install aws-vault <version>`
## Documentation
Config, usage, tips and tricks are available in the [USAGE.md](./USAGE.md) file.
## Vaulting Backends
The supported vaulting backends are:
* [macOS Keychain](https://support.apple.com/en-au/guide/keychain-access/welcome/mac)
* [Windows Credential Manager](https://support.microsoft.com/en-au/help/4026814/windows-accessing-credential-manager)
* Secret Service ([Gnome Keyring](https://wiki.gnome.org/Projects/GnomeKeyring), [KWallet](https://kde.org/applications/system/org.kde.kwalletmanager5))
* [KWallet](https://kde.org/applications/system/org.kde.kwalletmanager5)
* [Pass](https://www.passwordstore.org/)
* Encrypted file
Use the `--backend` flag or `AWS_VAULT_BACKEND` environment variable to specify.
## Quick start
```shell
# Store AWS credentials for the "jonsmith" profile
$ aws-vault add jonsmith
Enter Access Key Id: ABDCDEFDASDASF
Enter Secret Key: %%%
# Execute a command (using temporary credentials)
$ aws-vault exec jonsmith -- aws s3 ls
bucket_1
bucket_2
# open a browser window and login to the AWS Console
$ aws-vault login jonsmith
# List credentials
$ aws-vault list
Profile Credentials Sessions
======= =========== ========
jonsmith jonsmith -
# Start a subshell with temporary credentials
$ aws-vault exec jonsmith
Starting subshell /bin/zsh, use `exit` to exit the subshell
$ aws s3 ls
bucket_1
bucket_2
```
## How it works
`aws-vault` uses Amazon's STS service to generate [temporary credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html) via the `GetSessionToken` or `AssumeRole` API calls. These expire in a short period of time, so the risk of leaking credentials is reduced.
AWS Vault then exposes the temporary credentials to the sub-process in one of two ways
1. **Environment variables** are written to the sub-process. Notice in the below example how the AWS credentials get written out
```shell
$ aws-vault exec jonsmith -- env | grep AWS
AWS_VAULT=jonsmith
AWS_DEFAULT_REGION=us-east-1
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=%%%
AWS_SECRET_ACCESS_KEY=%%%
AWS_SESSION_TOKEN=%%%
AWS_CREDENTIAL_EXPIRATION=2020-04-16T11:16:27Z
```
2. **Local metadata server** is started. This approach has the advantage that anything that uses Amazon's SDKs will automatically refresh credentials as needed, so session times can be as short as possible.
```shell
$ aws-vault exec --server jonsmith -- env | grep AWS
AWS_VAULT=jonsmith
AWS_DEFAULT_REGION=us-east-1
AWS_REGION=us-east-1
AWS_CONTAINER_CREDENTIALS_FULL_URI=%%%
AWS_CONTAINER_AUTHORIZATION_TOKEN=%%%
```
The default is to use environment variables, but you can opt-in to the local instance metadata server with the `--server` flag on the `exec` command.
## Roles and MFA
[Best-practice](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#delegate-using-roles) is to [create Roles to delegate permissions](https://docs.aws.amazon.com/cli/latest/userguide/cli-roles.html). For security, you should also require that users provide a one-time key generated from a multi-factor authentication (MFA) device.
First you'll need to create the users and roles in IAM, as well as [setup an MFA device](https://docs.aws.amazon.com/IAM/latest/UserGuide/GenerateMFAConfigAccount.html). You can then [set up IAM roles to enforce MFA](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-role.html#cli-configure-role-mfa).
Here's an example configuration using roles and MFA:
```ini
[default]
region = us-east-1
[profile jonsmith]
mfa_serial = arn:aws:iam::111111111111:mfa/jonsmith
[profile foo-readonly]
source_profile = jonsmith
role_arn = arn:aws:iam::22222222222:role/ReadOnly
[profile foo-admin]
source_profile = jonsmith
role_arn = arn:aws:iam::22222222222:role/Administrator
mfa_serial = arn:aws:iam::111111111111:mfa/jonsmith
[profile bar-role1]
source_profile = jonsmith
role_arn = arn:aws:iam::333333333333:role/Role1
mfa_serial = arn:aws:iam::111111111111:mfa/jonsmith
[profile bar-role2]
source_profile = bar-role1
role_arn = arn:aws:iam::333333333333:role/Role2
mfa_serial = arn:aws:iam::111111111111:mfa/jonsmith
```
Here's what you can expect from aws-vault
| Command | Credentials | Cached | MFA |
|------------------------------------------|-----------------------------|---------------|-----|
| `aws-vault exec jonsmith --no-session` | Long-term credentials | No | No |
| `aws-vault exec jonsmith` | session-token | session-token | Yes |
| `aws-vault exec foo-readonly` | role | No | No |
| `aws-vault exec foo-admin` | session-token + role | session-token | Yes |
| `aws-vault exec foo-admin --duration=2h` | role | role | Yes |
| `aws-vault exec bar-role2` | session-token + role + role | session-token | Yes |
| `aws-vault exec bar-role2 --no-session` | role + role | role | Yes |
## Development
The [macOS release builds](https://github.com/99designs/aws-vault/releases) are code-signed to avoid extra prompts in Keychain. You can verify this with:
```shell
$ codesign --verify --verbose $(which aws-vault)
```
If you are developing or compiling the aws-vault binary yourself, you can [generate a self-signed certificate](https://support.apple.com/en-au/guide/keychain-access/kyca8916/mac) by accessing Keychain Access > Certificate Assistant > Create Certificate -> Certificate Type: Code Signing. You can then sign your binary with:
```shell
$ go build .
$ codesign --sign <Name of certificate created above> ./aws-vault
```
## References and Inspiration
* https://github.com/pda/aws-keychain
* https://docs.aws.amazon.com/IAM/latest/UserGuide/MFAProtectedAPI.html
* https://docs.aws.amazon.com/IAM/latest/UserGuide/IAMBestPractices.html#create-iam-users
* https://github.com/makethunder/awsudo
* https://github.com/AdRoll/hologram
* https://github.com/realestate-com-au/credulous
* https://github.com/dump247/aws-mock-metadata
* https://boto.readthedocs.org/en/latest/boto_config_tut.html
================================================
FILE: USAGE.md
================================================
# Usage
- [Usage](#usage)
- [Getting Help](#getting-help)
- [Typical use-cases for aws-vault](#typical-use-cases-for-aws-vault)
- [Use-case 1: aws-vault is the executor and provides the environment](#use-case-1-aws-vault-is-the-executor-and-provides-the-environment)
- [Use-case 2: aws-vault is a "master credentials vault" for AWS SDK](#use-case-2-aws-vault-is-a-master-credentials-vault-for-aws-sdk)
- [Use-case 3: aws-vault is a "MFA session cache" for AWS SDK](#use-case-3-aws-vault-is-a-mfa-session-cache-for-aws-sdk)
- [Use-case 4: aws-vault caches alternative credential sources](#use-case-4-aws-vault-caches-alternative-credential-sources)
- [Config](#config)
- [AWS config file](#aws-config-file)
- [`include_profile`](#include_profile)
- [`session_tags` and `transitive_session_tags`](#session_tags-and-transitive_session_tags)
- [`source_identity`](#source_identity)
- [`mfa_process`](#mfa_process)
- [Environment variables](#environment-variables)
- [Backends](#backends)
- [Keychain](#keychain)
- [Managing credentials](#managing-credentials)
- [Using multiple profiles](#using-multiple-profiles)
- [Listing profiles and credentials](#listing-profiles-and-credentials)
- [Removing credentials](#removing-credentials)
- [Rotating credentials](#rotating-credentials)
- [Managing Sessions](#managing-sessions)
- [Executing a command](#executing-a-command)
- [Logging into AWS console](#logging-into-aws-console)
- [Removing stored sessions](#removing-stored-sessions)
- [Using --no-session](#using---no-session)
- [Session duration](#session-duration)
- [Using `--server`](#using---server)
- [`--ec2-server`](#--ec2-server)
- [`--ecs-server`](#--ecs-server)
- [Temporary credentials limitations with STS, IAM](#temporary-credentials-limitations-with-sts-iam)
- [MFA](#mfa)
- [Gotchas with MFA config](#gotchas-with-mfa-config)
- [Single Sign On (SSO)](#single-sign-on-sso)
- [Assuming roles with web identities](#assuming-roles-with-web-identities)
- [Using `credential_process`](#using-credential_process)
- [Invoking `aws-vault` via `credential_process`](#invoking-aws-vault-via-credential_process)
- [Invoking `credential_process` via `aws-vault`](#invoking-credential_process-via-aws-vault)
- [Using a Yubikey](#using-a-yubikey)
- [Prerequisites](#prerequisites)
- [Setup](#setup)
- [Usage](#usage-1)
- [Shell completion](#shell-completion)
- [Desktop apps](#desktop-apps)
- [Docker](#docker)
## Getting Help
Context-sensitive help is available for every command in `aws-vault`.
```shell
# Show general help about aws-vault
$ aws-vault --help
# Show longer help about all options in aws-vault
$ aws-vault --help-long
# Show the most detailed information about the exec command
$ aws-vault exec --help
```
## Typical use-cases for aws-vault
There are a few different ways aws-vault can be used
### Use-case 1: aws-vault is the executor and provides the environment
Use aws-vault exclusively as a command executor, where aws-vault provides the environment and runs a command.
```ini
; master creds added with 'aws-vault add my_profile_master'
[profile my_profile_master]
[profile my_profile_role]
source_profile=my_profile_master
role_arn=xxx
```
```bash
aws-vault exec my_profile_master ./my-command # success, uses sts session generated by aws-vault
aws-vault exec my_profile_role ./my-command # success, uses role creds generated by aws-vault
AWS_PROFILE=my_profile_master ./my-command # Not expected to be functional
AWS_PROFILE=my_profile_role ./my-command # Not expected to be functional
```
In this scenario, the profile name and aws config is used exclusively by aws-vault, which provides the environment for the command to run in.
This is a very unix-y and 12-factor approach. It's the original and the primary use-case of aws-vault - it's why `aws-vault exec` exists.
### Use-case 2: aws-vault is a "master credentials vault" for AWS SDK
aws-vault can be used in `credential_process` in the AWS config to provide master creds. This is more in-line with the AWS SDK way of approaching the problem via `credential_process` and `AWS_PROFILE`
```ini
; master creds added with 'aws-vault add my_profile_master'
[profile my_profile_master]
credential_process = aws-vault export --format=json --no-session my_profile_master
[profile my_profile_role]
source_profile=my_profile_master
role_arn=xxx
```
```bash
aws-vault exec my_profile_master ./my-command # success (uses master creds)
aws-vault exec my_profile_role ./my-command # success (aws-vault role)
AWS_PROFILE=my_profile_master ./my-command # success (uses credential_process to get aws-vault master creds)
AWS_PROFILE=my_profile_role ./my-command # success (SDK role)
```
### Use-case 3: aws-vault is a "MFA session cache" for AWS SDK
Very similar to Use-case 2, aws-vault can be used to cache STS MFA credentials between profiles. This means you are not forced to re-authenticate with MFA every time you switch profiles
```ini
; master creds added with 'aws-vault add my_profile_master'
[profile my_profile_master]
mfa_serial=mmm
credential_process = aws-vault export --format=json my_profile_master
[profile my_profile_role]
source_profile=my_profile_master
mfa_serial=mmm
role_arn=xxx1
[profile my_profile_role2]
source_profile=my_profile_master
mfa_serial=mmm
role_arn=xxx2
```
```bash
aws-vault exec my_profile_master ./my-command # success (STS session)
aws-vault exec my_profile_role ./my-command # success (role)
AWS_PROFILE=my_profile_master ./my-command # success (uses credential_process to get aws-vault session)
AWS_PROFILE=my_profile_role ./my-command # success (uses aws-vault session + SDK role)
```
### Use-case 4: aws-vault caches alternative credential sources
aws-vault caches credentials from alternative credential sources like `sso_start_url`, `web_identity_token_process`, `credential_process`
```ini
[profile my_profile_using_sso]
sso_start_url = https://mycompany.awsapps.com/start
[profile my_profile_using_process]
credential_process = my-custom-creds-cmd
```
```bash
aws-vault exec my_profile_using_sso ./my-command # success, uses aws-vault caching
aws-vault exec my_profile_using_process ./my-command # success, uses aws-vault caching
AWS_PROFILE=my_profile_using_sso ./my-command # success, no caching
AWS_PROFILE=my_profile_using_process ./my-command # success, no caching
```
## Config
### AWS config file
aws-vault uses your `~/.aws/config` to load AWS config. This should work identically to the config specified by the [aws-cli docs](https://docs.aws.amazon.com/cli/latest/topic/config-vars.html).
#### `include_profile`
(Note: aws-vault v5 calls this `parent_profile`)
AWS Vault also recognises an extra config variable, `include_profile`, which is not recognised by the aws-cli. This variable allows a profile to load configuration horizontally from another profile.
This is a flexible mechanism for more complex configurations.
For example you can use it in "mixin" style where you import a common fragment. In this example, the `root`, `order-dev` and `order-staging-admin` profiles include the `region`, `mfa_serial` and `source_profile` configuration from `common`.
```ini
; The "common" profile here operates as a "config fragment" rather than a profile
[profile common]
region=eu-west-1
mfa_serial=arn:aws:iam::123456789:mfa/johnsmith
source_profile = root
[profile root]
include_profile = common
[profile order-dev]
include_profile = common
role_arn=arn:aws:iam::123456789:role/developers
[profile order-staging-admin]
include_profile = common
role_arn=arn:aws:iam::123456789:role/administrators
```
Or you could use it in "parent" style where you conflate the fragment with the profile. In this example the `order-dev` and `order-staging-admin` profiles include the `region`, `mfa_serial` and `source_profile` configuration from `root`, while also using the credentials stored against the `root` profile as the source credentials `source_profile = root`
```ini
; The "root" profile here operates as a profile, a config fragment as well as a source_profile
[profile root]
region=eu-west-1
mfa_serial=arn:aws:iam::123456789:mfa/johnsmith
source_profile = root
[profile order-dev]
include_profile = root
role_arn=arn:aws:iam::123456789:role/developers
[profile order-staging-admin]
include_profile = root
role_arn=arn:aws:iam::123456789:role/administrators
```
#### `session_tags` and `transitive_session_tags`
It is possible to set [session tags](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html) when `AssumeRole` is used. Two custom config variables could be defined for that: `session_tags` and `transitive_session_tags`. The former defines a comma separated key=value list of tags and the latter is a comma separated list of tags that should be persisted during role chaining:
```ini
[profile root]
region=eu-west-1
[profile order-dev]
source_profile = root
role_arn=arn:aws:iam::123456789:role/developers
session_tags = key1=value1,key2=value2,key3=value3
transitive_session_tags = key1,key2
```
#### `source_identity`
It is possible to set [source identity](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_control-access_monitor.html) when `AssumeRole` is used. Custom config variable `source_identity` allows you to set the value.
```ini
[profile root]
region=eu-west-1
[profile order-dev]
source_profile = root
role_arn=arn:aws:iam::123456789:role/developers
source_identity=your_user_name
```
#### `mfa_process`
If you have a method to generate an MFA token, you can use it with `aws-vault` by specifying the `mfa_process` option in a profile of your `~/.aws/config` file. The value of `mfa_process` should be a command that will output the MFA token to stdout.
For example, to use `pass` to retrieve an MFA token from a password store entry, you could use the following:
```ini
[profile foo]
mfa_serial=arn:aws:iam::123456789:mfa/johnsmith
mfa_process=pass otp my_aws_mfa
```
Or another example using 1Password
```ini
[profile foo]
mfa_serial=arn:aws:iam::123456789:mfa/johnsmith
mfa_process=op item get my_aws_mfa --otp
```
WARNING: Use of this option runs against security best practices. It is recommended that you use a dedicated MFA device.
### Environment variables
To configure the default flag values of `aws-vault` and its subcommands:
* `AWS_VAULT_BACKEND`: Secret backend to use (see the flag `--backend`)
* `AWS_VAULT_KEYCHAIN_NAME`: Name of macOS keychain to use (see the flag `--keychain`)
* `AWS_VAULT_PROMPT`: Prompt driver to use (see the flag `--prompt`)
* `AWS_VAULT_PASS_PASSWORD_STORE_DIR`: Pass password store directory (see the flag `--pass-dir`)
* `AWS_VAULT_PASS_CMD`: Name of the pass executable (see the flag `--pass-cmd`)
* `AWS_VAULT_PASS_PREFIX`: Prefix to prepend to the item path stored in pass (see the flag `--pass-prefix`)
* `AWS_VAULT_FILE_DIR`: Directory for the "file" password store (see the flag `--file-dir`)
* `AWS_VAULT_FILE_PASSPHRASE`: Password for the "file" password store
* `AWS_CONFIG_FILE`: The location of the AWS config file
To override the AWS config file (used in the `exec`, `login` and `rotate` subcommands):
* `AWS_REGION`: The AWS region
* `AWS_DEFAULT_REGION`: The AWS region, applied only if `AWS_REGION` isn't set
* `AWS_STS_REGIONAL_ENDPOINTS`: STS endpoint resolution logic, must be "regional" or "legacy"
* `AWS_MFA_SERIAL`: The identification number of the MFA device to use
* `AWS_ROLE_ARN`: Specifies the ARN of an IAM role in the active profile
* `AWS_ROLE_SESSION_NAME`: Specifies the name to attach to the role session in the active profile
To override session durations (used in `exec` and `login`):
* `AWS_SESSION_TOKEN_TTL`: Expiration time for the `GetSessionToken` credentials. Defaults to 1h
* `AWS_CHAINED_SESSION_TOKEN_TTL`: Expiration time for the `GetSessionToken` credentials when chaining profiles. Defaults to 8h
* `AWS_ASSUME_ROLE_TTL`: Expiration time for the `AssumeRole` credentials. Defaults to 1h
* `AWS_FEDERATION_TOKEN_TTL`: Expiration time for the `GetFederationToken` credentials. Defaults to 1h
* `AWS_MIN_TTL`: The minimum expiration time allowed for a credential. Defaults to 5m
Note that the session durations above expect a unit after the number (e.g. 12h or 43200s).
To override or set session tagging (used in `exec`):
* `AWS_SESSION_TAGS`: Comma separated key-value list of tags passed with the `AssumeRole` call, overrides `session_tags` profile config variable
* `AWS_TRANSITIVE_TAGS`: Comma separated list of transitive tags passed with the `AssumeRole` call, overrides `transitive_session_tags` profile config variable
To override or set the source identity (used in `exec` and `login`):
* `AWS_SOURCE_IDENTITY`: Specifies the source identity for assumed role sessions
## Backends
You can choose among different pluggable secret storage backends. You can set the backend using the `--backend` flag or the `AWS_VAULT_BACKEND` environment variable. Run `aws-vault --help` to see what your `--backend` flag supports.
### Keychain
If you're looking to configure the amount of time between having to enter your Keychain password for each usage of a particular profile, you can do so through Keychain:
1. Open "Keychain Access"
2. Open the aws-vault keychain. If you do not have "aws-vault" in the sidebar of the Keychain app, then you can do "File -> Add Keychain" and select the `aws-vault.keychain-db`. This is typically created in `Users/{USER}/Library/Keychains`.
3. Right click on aws-vault keychain, and select "Change Settings for Keychain 'aws-vault"
4. Update "Lock after X minutes of inactivity" to your desired value.
5. Hit save.

## Managing credentials
### Using multiple profiles
In addition to using IAM roles to assume temporary privileges as described in [README.md](./USAGE.md), aws-vault can also be used with multiple profiles directly. This allows you to use multiple separate AWS accounts that have no relation to one another, such as work and home.
```shell
# Store AWS credentials for the "home" profile
$ aws-vault add home
Enter Access Key Id: ABDCDEFDASDASF
Enter Secret Key: %
# Execute a command using temporary credentials
$ aws-vault exec home -- aws s3 ls
bucket_1
bucket_2
# store credentials for the "work" profile
$ aws-vault add work
Enter Access Key Id: ABDCDEFDASDASF
Enter Secret Key: %
# Execute a command using temporary credentials
$ aws-vault exec work -- aws s3 ls
another_bucket
```
Here is an example `~/.aws/config` file, to help show the configuration. It defines two AWS accounts: "home" and "work", both of which use MFA. The work account provides two roles, allowing the user to become either profile.
```ini
[default]
region = us-east-1
[profile home]
mfa_serial = arn:aws:iam::111111111111:mfa/home-account
[profile work]
mfa_serial = arn:aws:iam::111111111111:mfa/work-account
role_arn = arn:aws:iam::111111111111:role/ReadOnly
[profile work-admin]
role_arn = arn:aws:iam::111111111111:role/Administrator
source_profile = work
```
### Listing profiles and credentials
You can use the `aws-vault list` command to list out the defined profiles, and any session associated with them.
```shell
$ aws-vault list
Profile Credentials Sessions
======= =========== ========
home home
work work 1525456570
work-read-only work
work-admin work
```
### Removing credentials
The `aws-vault remove` command can be used to remove credentials. It works similarly to the `aws-vault add` command.
```shell
# Remove AWS credentials for the "work" profile
$ aws-vault remove work
Delete credentials for profile "work"? (y|N) y
Deleted credentials.
```
### Rotating credentials
Regularly rotating your access keys is a critical part of credential management. You can do this with the `aws-vault rotate <profile>` command as often as you like. [Restrictions on IAM access](#temporary-credentials-limitations-with-sts-iam) using `GetSessionToken` means you will need to have [configured MFA](#mfa) or use the `--no-session` flag.
The minimal IAM policy required to rotate your own credentials is:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iam:CreateAccessKey",
"iam:DeleteAccessKey",
"iam:GetUser"
],
"Resource": [
"arn:aws:iam::*:user/${aws:username}"
]
}
]
}
```
## Managing Sessions
### Executing a command
Running `aws-vault exec` will run a command with AWS credentials.
When using exec, you may find it useful to use the builtin `--` feature in bash, zsh and other POSIX shells. For example
```shell
aws-vault exec myprofile -- aws s3 ls
```
Using `--` signifies the end of the `aws-vault` options, and allows the shell autocomplete to kick in and offer autocompletions for the proceeding command.
If you use `exec` without specifying a command, AWS Vault will create a new interactive subshell. Note that when creating an interactive subshell, bash, zsh and other POSIX shells will execute the `~/.bashrc` or `~/.zshrc` file. If you have local variables, functions or aliases (for example your `PS1` prompt), ensure that they are defined in the rc file so they get executed when the subshell begins.
### Logging into AWS console
You can use the `aws-vault login` command to open a browser window and login to AWS Console for a given account:
```shell
$ aws-vault login work
```
If you have credentials already available in your environment, aws-vault will use these credentials to sign you in to the AWS console.
```shell
$ export AWS_ACCESS_KEY_ID=%%%
$ export AWS_SECRET_ACCESS_KEY=%%%
$ export AWS_SESSION_TOKEN=%%%
$ aws-vault login
```
### Removing stored sessions
If you want to remove sessions managed by `aws-vault` before they expire, you can do this with `aws-vault clear` command.
You can also specify a profile to remove sessions for this profile only.
```shell
aws-vault clear [profile]
```
### Using --no-session
AWS Vault will typically create temporary credentials using a combination of `GetSessionToken` and `AssumeRole`, depending on the config. The `GetSessionToken` call is made with MFA if available, and the resulting session is cached in the backend vault and can be used to assume roles from different profiles without further MFA prompts.
If you wish to skip the `GetSessionToken` call, you can use the `--no-session` flag.
However, consider that if you use `--no-session` with a profile using IAM credentials and NO `role_arn`, then your IAM credentials will be directly exposed to the terminal/application you are running. This is the opposite of what you are normally trying to achieve by using AWS Vault. You can easily witness that by doing
```shell
aws-vault exec <iam_user_profile> -- env | grep AWS
```
You'll see an `AWS_ACCESS_KEY_ID` of the form `ASIAxxxxxx` which is a temporary one. Doing
```shell
aws-vault exec <iam_user_profile> --no-session -- env | grep AWS
```
You'll see your IAM user `AWS_ACCESS_KEY_ID` of the form `AKIAxxxxx` directly exposed, as well as the corresponding `AWS_SECRET_KEY_ID`.
### Session duration
If you try to [assume a role](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-role.html) from a temporary session or another role, AWS considers that as [role chaining](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_terms-and-concepts.html#iam-term-role-chaining) and limits your ability to assume the target role to 1h. Trying to use a duration longer than 1h may result in an error:
```
aws-vault: error: Failed to get credentials for default: ValidationError: The requested DurationSeconds exceeds the MaxSessionDuration set for this role.
status code: 400, request id: aa58fa50-4a5e-11e9-9566-293ea5c350ee
```
For that reason, AWS Vault will not use `GetSessionToken` if `--duration` or the role's `duration_seconds` is longer than 1h.
### Using `--server`
There may be scenarios where you'd like to assume a role for a long length of time, or perhaps when using a tool where using temporary sessions on demand is preferable. For example, when using a tool like [Terraform](https://www.terraform.io/), you need to have AWS credentials available to the application for the entire duration of the infrastructure change.
AWS Vault can run a background server to imitate the metadata endpoint that you would have on an EC2 or ECS instance. When your application uses the AWS SDK to locate credentials, it will automatically connect to this server that will issue a new set of temporary credentials (using the same profile as the one the server was started with). This server will continue to generate temporary credentials any time the application requests it.
#### `--ec2-server`
This approach has the major security drawback that while this `aws-vault` server runs, any application wanting to connect to AWS will be able to do so, using the profile the server was started with. Thanks to `aws-vault`, the credentials are not exposed, but the ability to use them to connect to AWS is!
To use `--ec2-server`, AWS Vault needs root/administrator privileges in order to bind to the privileged port. AWS Vault runs a minimal proxy as the root user, proxying through to the real aws-vault instance.
#### `--ecs-server`
The ECS Credential provider binds to a random, ephemeral port and requires an authorization token, which offers the following advantages over the EC2 Metadata provider:
1. Does not require root/administrator privileges
2. Allows multiple providers simultaneously for discrete processes
3. Mitigates the security issues that accompany the EC2 Metadata Service because the address is not well-known and the authorization token is only exposed to the subprocess via environment variables
However, this will only work with the AWS SDKs [that support `AWS_CONTAINER_CREDENTIALS_FULL_URI`](https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html).
The ECS server also responds to requests on `/role-arn/YOUR_ROLE_ARN` with the role credentials, making it usable with `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` when combined with a reverse proxy (see the Docker section below).
### Temporary credentials limitations with STS, IAM
When using temporary credentials you are restricted from using some STS and IAM APIs (see [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_request.html#stsapi_comparison)). The restriction is enforced with `InvalidClientTokenId` error response.
```shell
$ aws-vault exec <iam_user_profile> -- aws iam get-user
An error occurred (InvalidClientTokenId) when calling the GetUser operation: The security token included in the request is invalid
```
For restricted IAM operation you can add MFA to the IAM User and update your ~/.aws/config file with [MFA configuration](#mfa). Alternately you may avoid the temporary session entirely by using `--no-session`.
## MFA
To enable MFA for a profile, specify the `mfa_serial` in `~/.aws/config`. You can retrieve the MFA's serial (ARN) in the web console, under IAM > Users > `<User>` > Security Configuration. If you have an account with an MFA associated, but you don't provide the ARN, you are unable to call IAM services, even if you have the correct permissions to do so.
AWS Vault will attempt to re-use a `GetSessionToken` between profiles that share a common `mfa_serial`. In the following example, aws-vault will cache and re-use sessions between role1 and role2. This means you don't have to continually enter MFA codes if the MFA method is the same.
```ini
[profile tom]
mfa_serial = arn:aws:iam::111111111111:mfa/tom
[profile role1]
source_profile = tom
role_arn = arn:aws:iam::22222222222:role/role1
mfa_serial = arn:aws:iam::111111111111:mfa/tom
[profile role2]
source_profile = tom
role_arn = arn:aws:iam::33333333333:role/role2
mfa_serial = arn:aws:iam::111111111111:mfa/tom
```
Be sure to specify the `mfa_serial` for the source profile (in the above example `tom`) so that aws-vault can match the common `mfa_serial`.
You can also set the `mfa_serial` with the environment variable `AWS_MFA_SERIAL`.
### Gotchas with MFA config
aws-vault v4 would inherit the `mfa_serial` from the `source_profile`. While this was intuitive for some, it made certain configurations difficult to express and is different behaviour to the aws-cli.
aws-vault v5 corrected this problem. The `mfa_serial` must be specified for _each_ profile, the same way the aws-cli interprets the configuration. If you wish to avoid specifying the `mfa_serial` for each profile, consider using the `mfa_serial` in the `[default]` section, the `AWS_MFA_SERIAL` environment variable, or [`include_profile`](#include_profile). For example:
```ini
[profile jon]
mfa_serial = arn:aws:iam::111111111111:mfa/jon
source_profile=jon
[profile role1]
role_arn = arn:aws:iam::22222222222:role/role1
include_profile = jon
[profile role2]
role_arn = arn:aws:iam::33333333333:role/role2
include_profile = jon
```
## Single Sign On (SSO)
_AWS IAM Identity Center provides single sign on, and was previously known as AWS SSO._
If your organization uses [AWS IAM Identity Center](https://aws.amazon.com/iam/identity-center/) for single sign on, AWS Vault provides a method for using the credential information defined by [`aws sso`](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html) from v2 of the AWS CLI. The configuration options are as follows:
* `sso_session` Name of the `[sso-session]` section in the same file with the common options, or:
* `sso_start_url` The URL that points to the organization's AWS IAM Identity Center user portal.
* `sso_region` The AWS Region that contains the AWS IAM Identity Center user portal host. This is separate from, and can be a different region than the default CLI region parameter.
* `sso_account_id` The AWS account ID that contains the IAM role that you want to use with this profile.
* `sso_role_name` The name of the Identity Center Permission Group that defines the user's permissions when using this profile.
Here is an example configuration using AWS IAM Identity Center for single sign on.
```ini
[profile Administrator-123456789012]
sso_start_url=https://aws-sso-portal.awsapps.com/start
sso_region=eu-west-1
sso_account_id=123456789012
sso_role_name=Administrator
```
## Assuming roles with web identities
AWS supports assuming roles using [web identity federation and OpenID Connect](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-role.html#cli-configure-role-oidc), including login using Amazon, Google, Facebook or any other OpenID Connect server. The configuration options are as follows:
* `web_identity_token_file` A file that contains an OpenID Connect identity token. The token is loaded and passed as the `WebIdentityToken` argument of the `AssumeRoleWithWebIdentity` operation.
* `web_identity_token_process` A command that executes to generate an OpenID Connect identity token. The token written to the command's standard out is passed as the `WebIdentityToken` argument of the `AssumeRoleWithWebIdentity` operation. This is a custom option supported only by `aws-vault`.
An example configuration using a static token:
```ini
[profile role1]
role_arn = arn:aws:iam::22222222222:role/role1
web_identity_token_file = /path/to/token.txt
```
An example using a token generated by an external command:
```ini
[profile role2]
role_arn = arn:aws:iam::33333333333:role/role2
web_identity_token_process = oidccli raw
```
## Using `credential_process`
The [AWS CLI config](https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#sourcing-credentials-from-external-processes) supports sourcing credentials directly from an external process, using `credential_process`.
### Invoking `aws-vault` via `credential_process`
```ini
[profile home]
credential_process = aws-vault export --format=json home
```
If `mfa_serial` is set, please define the prompt driver (for example `osascript` for macOS), else the prompt will not show up.
```ini
[profile work]
mfa_serial = arn:aws:iam::123456789012:mfa/jonsmith
credential_process = aws-vault --prompt=osascript export --format=json work
```
Note that `credential_process` is designed for retrieving master credentials, while aws-vault outputs STS credentials by default. If a role is present, the AWS CLI/SDK uses the master credentials from the `credential_process` to generate STS credentials itself. So depending on your use-case, it might make sense for aws-vault to output master credentials by using a profile without a role and the `--no-session` argument. For example:
```ini
[profile jon]
credential_process = aws-vault export --no-session --format=json jon
[profile work]
mfa_serial = arn:aws:iam::123456789012:mfa/jonsmith
role_arn = arn:aws:iam::33333333333:role/role2
source_profile = jon
```
If you're using `credential_process` in your config to invoke `aws-vault exec` you should not use `aws-vault exec` on the command line to execute commands directly - the AWS SDK executes `aws-vault` for you.
### Invoking `credential_process` via `aws-vault`
When executing a profile via `aws-vault exec` that has `credential_process` set, `aws-vault` will execute the specified command to obtain a credential. This will allow `aws-vault` to cache credentials obtained via `credential_process`.
## Using a Yubikey
Yubikeys can be used with AWS Vault via Yubikey's OATH-TOTP support. TOTP is necessary because FIDO-U2F is unsupported on the AWS CLI and SDKs; even though it's supported on the AWS Console.
### Prerequisites
1. [A Yubikey that supports OATH-TOTP](https://support.yubico.com/support/solutions/articles/15000006419-using-your-yubikey-with-authenticator-codes)
2. `ykman`, the [YubiKey Manager CLI](https://github.com/Yubico/yubikey-manager) tool.
You can verify these prerequisites by running `ykman info` and checking `OATH` is enabled.
### Setup
1. Log into the AWS web console with your IAM user credentials, and navigate to _My Security Credentials_
2. Under _Multi-factor authentication (MFA)_, click `Manage MFA device` and add a Virtual MFA device
3. Instead of showing the QR code, click on `Show secret key` and copy the key.
4. On a command line, run:
```shell
ykman oath accounts add -t arn:aws:iam::${ACCOUNT_ID}:mfa/${MFA_DEVICE_NAME}
```
replacing `${ACCOUNT_ID}` with your AWS account ID and `${MFA_DEVICE_NAME}` with the name you gave to the MFA device. It will prompt you for a base32 text and you can input the key from step 3. Notice the above command uses `-t` which requires you to touch your YubiKey to generate authentication codes.
5. Now you have to enter two consecutive MFA codes into the AWS website to assign your key to your AWS login. Just run `ykman oath accounts code arn:aws:iam::${ACCOUNT_ID}:mfa/${MFA_DEVICE_NAME}` to get an authentication code. The codes are re-generated every 30 seconds, so you have to run this command twice with about 30 seconds in between to get two distinct codes. Enter the two codes in the AWS form and click `Assign MFA`.
A script can be found at [contrib/scripts/aws-iam-create-yubikey-mfa.sh](contrib/scripts/aws-iam-create-yubikey-mfa.sh) to automate the process. Note that this script requires your `$MFA_DEVICE_NAME` to be your IAM username as the `aws iam enable-mfa-device` command in the CLI does not yet offer specifying the name. When only one MFA device was allowed per IAM user, the `$MFA_DEVICE_NAME` would always be your IAM username.
In case of TOTP being out of sync (AWS API doesn't accept MFA codes), a yubikey resync script can be found at [contrib/scripts/aws-iam-resync-yubikey-mfa.sh](contrib/scripts/aws-iam-resync-yubikey-mfa.sh) to resync the yubikey with AWS. As above, this script requires your `$MFA_DEVICE_NAME` to be your IAM username.
Note that each `[profile <name>]` in your `~/.aws/config` only supports one `mfa_serial` entry. If you wish to use multiple Yubikeys, or mix and match MFA devices, you'll need to add a profile for each method.
### Usage
Using the `ykman` prompt driver, aws-vault will execute `ykman` to generate tokens for any profile in your `.aws/config` using an `mfa_device`.
```shell
aws-vault exec --prompt ykman ${AWS_VAULT_PROFILE_USING_MFA} -- aws s3 ls
```
An alternative to manually supplying the prompt driver as a CLI argument to `aws-vault` is setting the [`mfa_process`](#mfa_process) parameter in your `.aws/config` for the profiles that should use a YubiKey to generate tokens. Example:
(Note: Remember to swap out the name of the OATH account used in `mfa_process` below with the name you gave it during [YubiKey setup](#setup))
```ini
[profile jon]
mfa_serial = arn:aws:iam::123456789012:mfa/jonsmith
mfa_process = ykman oath accounts code --single arn:aws:iam::123456789012:mfa/jonsmith
```
Further config:
- `AWS_VAULT_PROMPT=ykman`: to avoid specifying `--prompt` each time
- `YKMAN_OATH_CREDENTIAL_NAME`: to use an alternative ykman credential
- `AWS_VAULT_YKMAN_VERSION`: to set the major version of the ykman cli being used. Defaults to "4"
- `YKMAN_OATH_DEVICE_SERIAL`: to set the device serial of a specific Yubikey if you have multiple Yubikeys plugged into your computer.
## Shell completion
You can generate shell completions for
- bash: `eval "$(curl -fs https://raw.githubusercontent.com/99designs/aws-vault/master/contrib/completions/bash/aws-vault.bash)"`
- zsh: `eval "$(curl -fs https://raw.githubusercontent.com/99designs/aws-vault/master/contrib/completions/zsh/aws-vault.zsh)"`
- fish: `eval "$(curl -fs https://raw.githubusercontent.com/99designs/aws-vault/master/contrib/completions/fish/aws-vault.fish)"`
Find the completion scripts at [contrib/completions](contrib/completions).
## Desktop apps
You can use desktop apps with temporary credentials from AWS Vault too! For example on macOS run
```shell
aws-vault exec --server jonsmith -- open -W -a Lens
```
* `--server`: starts the background server so that temporary credentials get refreshed automatically
* `open -W -a Lens`: run the applications, waiting for it to exit
## Docker
It's possible for Docker containers to retrieve credentials from aws-vault running on the host.

The ECS server responds to requests on `/role-arn/YOUR_ROLE_ARN` with the role credentials, making it usable with the `AWS_CONTAINER_CREDENTIALS_FULL_URI` or `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` environment
variables. These environment variables are used by the AWS SDKs as part of the [default credential provider chain](https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default).
In particular, this is designed to allow aws-vault to run on your local host while docker images access role credentials dynamically. This is achieved via a reverse-proxy container (started with `aws-vault exec --ecs-server --lazy PROFILE -- docker-compose up ...`) using the default ECS IP address `169.254.170.2`. Docker containers no longer need AWS keys at all - instead they can specify the role they want to assume with `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI`.
This use-case is similar to the goal of [amazon-ecs-local-container-endpoints](https://github.com/awslabs/amazon-ecs-local-container-endpoints/blob/mainline/docs/features.md#vend-credentials-to-containers), however the difference here is that the long-lived AWS credentials are getting sourced from your keychain via aws-vault.
To test it out:
1. Add a base role to your `~/.aws/config` (replacing with valid values)
```ini
[profile base-role]
source_profile=myprofile
role_arn=arn:aws:iam::222222222222:role/aws-vault-test
mfa_serial=arn:aws:iam::222222222222:mfa/<your.aws.username>
```
2. Start a reverse proxy:
```shell
$ cd contrib/_aws-vault-proxy
$ aws-vault --debug exec --server --lazy base-role -- docker compose up --build aws-vault-proxy
```
3. In a new terminal, assume a new role
```shell
$ export AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=/role-arn/arn:aws:iam::222222222222:role/another-role-that-can-be-assumed-by-base-role
$ docker-compose run testapp
testapp $ aws sts get-caller-identity
```
================================================
FILE: bin/create-dmg
================================================
#!/bin/bash
#
# create-dmg packages the aws-vault CLI binary for macOS
# using Apple's signing and notorizing process
#
#
# As per https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow
# AC_PASSWORD can be set in your keychain with:
# xcrun notarytool store-credentials "AC_PASSWORD"
# --apple-id "AC_USERNAME"
# --team-id <WWDRTeamID>
# --password <secret_2FA_password>
#
set -euo pipefail
BIN_PATH="$1"
DMG_PATH="${2:-$1.dmg}"
CERT_ID="${CERT_ID:-"Developer ID Application: 99designs Inc (NRM9HVJ62Z)"}"
KEYCHAIN_PROFILE="${KEYCHAIN_PROFILE:-AC_PASSWORD}"
if [[ -f "$DMG_PATH" ]] ; then
echo "File '$DMG_PATH' already exists. Remove it and try again"
exit 1
fi
tmpdir="$(mktemp -d)"
trap "rm -rf $tmpdir" EXIT
cp -a $BIN_PATH $tmpdir/aws-vault
src_path="$tmpdir/aws-vault"
echo "Signing binary"
codesign --options runtime --timestamp --sign "$CERT_ID" "$src_path"
echo "Creating dmg"
hdiutil create -quiet -srcfolder "$src_path" "$DMG_PATH"
echo "Signing dmg"
codesign --timestamp --sign "$CERT_ID" "$DMG_PATH"
echo "Submitting notorization request"
xcrun notarytool submit $DMG_PATH --keychain-profile "$KEYCHAIN_PROFILE" --wait
echo "Stapling"
xcrun stapler staple -q $DMG_PATH
================================================
FILE: cli/add.go
================================================
package cli
import (
"fmt"
"log"
"os"
"github.com/99designs/aws-vault/v7/prompt"
"github.com/99designs/aws-vault/v7/vault"
"github.com/99designs/keyring"
"github.com/alecthomas/kingpin/v2"
"github.com/aws/aws-sdk-go-v2/aws"
)
type AddCommandInput struct {
ProfileName string
FromEnv bool
AddConfig bool
}
func ConfigureAddCommand(app *kingpin.Application, a *AwsVault) {
input := AddCommandInput{}
cmd := app.Command("add", "Add credentials to the secure keystore.")
cmd.Arg("profile", "Name of the profile").
Required().
StringVar(&input.ProfileName)
cmd.Flag("env", "Read the credentials from the environment (AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY)").
BoolVar(&input.FromEnv)
cmd.Flag("add-config", "Add a profile to ~/.aws/config if one doesn't exist").
Default("true").
BoolVar(&input.AddConfig)
cmd.Action(func(c *kingpin.ParseContext) error {
keyring, err := a.Keyring()
if err != nil {
return err
}
awsConfigFile, err := a.AwsConfigFile()
if err != nil {
return err
}
err = AddCommand(input, keyring, awsConfigFile)
app.FatalIfError(err, "add")
return nil
})
}
func AddCommand(input AddCommandInput, keyring keyring.Keyring, awsConfigFile *vault.ConfigFile) error {
var accessKeyID, secretKey string
p, _ := awsConfigFile.ProfileSection(input.ProfileName)
if p.SourceProfile != "" {
return fmt.Errorf("Your profile has a source_profile of %s, adding credentials to %s won't have any effect",
p.SourceProfile, input.ProfileName)
}
if input.FromEnv {
if accessKeyID = os.Getenv("AWS_ACCESS_KEY_ID"); accessKeyID == "" {
return fmt.Errorf("Missing value for AWS_ACCESS_KEY_ID")
}
if secretKey = os.Getenv("AWS_SECRET_ACCESS_KEY"); secretKey == "" {
return fmt.Errorf("Missing value for AWS_SECRET_ACCESS_KEY")
}
} else {
var err error
if accessKeyID, err = prompt.TerminalPrompt("Enter Access Key ID: "); err != nil {
return err
}
if secretKey, err = prompt.TerminalSecretPrompt("Enter Secret Access Key: "); err != nil {
return err
}
}
creds := aws.Credentials{AccessKeyID: accessKeyID, SecretAccessKey: secretKey}
ckr := &vault.CredentialKeyring{Keyring: keyring}
if err := ckr.Set(input.ProfileName, creds); err != nil {
return err
}
fmt.Printf("Added credentials to profile %q in vault\n", input.ProfileName)
sk := &vault.SessionKeyring{Keyring: keyring}
if n, _ := sk.RemoveForProfile(input.ProfileName); n > 0 {
fmt.Printf("Deleted %d existing sessions.\n", n)
}
if _, hasProfile := awsConfigFile.ProfileSection(input.ProfileName); !hasProfile {
if input.AddConfig {
newProfileSection := vault.ProfileSection{
Name: input.ProfileName,
}
log.Printf("Adding profile %s to config at %s", input.ProfileName, awsConfigFile.Path)
if err := awsConfigFile.Add(newProfileSection); err != nil {
return fmt.Errorf("Error adding profile: %w", err)
}
}
}
return nil
}
================================================
FILE: cli/add_test.go
================================================
package cli
import (
"log"
"os"
"github.com/alecthomas/kingpin/v2"
)
func ExampleAddCommand() {
f, err := os.CreateTemp("", "aws-config")
if err != nil {
log.Fatal(err)
}
defer os.Remove(f.Name())
os.Setenv("AWS_CONFIG_FILE", f.Name())
os.Setenv("AWS_ACCESS_KEY_ID", "llamas")
os.Setenv("AWS_SECRET_ACCESS_KEY", "rock")
os.Setenv("AWS_VAULT_BACKEND", "file")
os.Setenv("AWS_VAULT_FILE_PASSPHRASE", "password")
defer os.Unsetenv("AWS_ACCESS_KEY_ID")
defer os.Unsetenv("AWS_SECRET_ACCESS_KEY")
defer os.Unsetenv("AWS_VAULT_BACKEND")
defer os.Unsetenv("AWS_VAULT_FILE_PASSPHRASE")
app := kingpin.New(`aws-vault`, ``)
ConfigureAddCommand(app, ConfigureGlobals(app))
kingpin.MustParse(app.Parse([]string{"add", "--debug", "--env", "foo"}))
// Output:
// Added credentials to profile "foo" in vault
}
================================================
FILE: cli/clear.go
================================================
package cli
import (
"fmt"
"github.com/99designs/aws-vault/v7/vault"
"github.com/99designs/keyring"
"github.com/alecthomas/kingpin/v2"
)
type ClearCommandInput struct {
ProfileName string
}
func ConfigureClearCommand(app *kingpin.Application, a *AwsVault) {
input := ClearCommandInput{}
cmd := app.Command("clear", "Clear temporary credentials from the secure keystore.")
cmd.Arg("profile", "Name of the profile").
HintAction(a.MustGetProfileNames).
StringVar(&input.ProfileName)
cmd.Action(func(c *kingpin.ParseContext) (err error) {
keyring, err := a.Keyring()
if err != nil {
return err
}
awsConfigFile, err := a.AwsConfigFile()
if err != nil {
return err
}
err = ClearCommand(input, awsConfigFile, keyring)
app.FatalIfError(err, "clear")
return nil
})
}
func ClearCommand(input ClearCommandInput, awsConfigFile *vault.ConfigFile, keyring keyring.Keyring) error {
sessions := &vault.SessionKeyring{Keyring: keyring}
oidcTokens := &vault.OIDCTokenKeyring{Keyring: keyring}
var oldSessionsRemoved, numSessionsRemoved, numTokensRemoved int
var err error
if input.ProfileName == "" {
oldSessionsRemoved, err = sessions.RemoveOldSessions()
if err != nil {
return err
}
numSessionsRemoved, err = sessions.RemoveAll()
if err != nil {
return err
}
numTokensRemoved, err = oidcTokens.RemoveAll()
if err != nil {
return err
}
} else {
numSessionsRemoved, err = sessions.RemoveForProfile(input.ProfileName)
if err != nil {
return err
}
if profileSection, ok := awsConfigFile.ProfileSection(input.ProfileName); ok {
if exists, _ := oidcTokens.Has(profileSection.SSOStartURL); exists {
err = oidcTokens.Remove(profileSection.SSOStartURL)
if err != nil {
return err
}
numTokensRemoved = 1
}
}
}
fmt.Printf("Cleared %d sessions.\n", oldSessionsRemoved+numSessionsRemoved+numTokensRemoved)
return nil
}
================================================
FILE: cli/exec.go
================================================
package cli
import (
"context"
"fmt"
"log"
"net/http"
"os"
osexec "os/exec"
"os/signal"
"runtime"
"strings"
"syscall"
"time"
"github.com/99designs/aws-vault/v7/iso8601"
"github.com/99designs/aws-vault/v7/server"
"github.com/99designs/aws-vault/v7/vault"
"github.com/99designs/keyring"
"github.com/alecthomas/kingpin/v2"
"github.com/aws/aws-sdk-go-v2/aws"
)
type ExecCommandInput struct {
ProfileName string
Command string
Args []string
StartEc2Server bool
StartEcsServer bool
Lazy bool
JSONDeprecated bool
Config vault.ProfileConfig
SessionDuration time.Duration
NoSession bool
UseStdout bool
ShowHelpMessages bool
}
func (input ExecCommandInput) validate() error {
if input.StartEc2Server && input.StartEcsServer {
return fmt.Errorf("Can't use --ec2-server with --ecs-server")
}
if input.StartEc2Server && input.JSONDeprecated {
return fmt.Errorf("Can't use --ec2-server with --json")
}
if input.StartEc2Server && input.NoSession {
return fmt.Errorf("Can't use --ec2-server with --no-session")
}
if input.StartEcsServer && input.JSONDeprecated {
return fmt.Errorf("Can't use --ecs-server with --json")
}
if input.StartEcsServer && input.NoSession {
return fmt.Errorf("Can't use --ecs-server with --no-session")
}
if input.StartEcsServer && input.Config.MfaPromptMethod == "terminal" {
return fmt.Errorf("Can't use --prompt=terminal with --ecs-server. Specify a different prompt driver")
}
if input.StartEc2Server && input.Config.MfaPromptMethod == "terminal" {
return fmt.Errorf("Can't use --prompt=terminal with --ec2-server. Specify a different prompt driver")
}
return nil
}
func hasBackgroundServer(input ExecCommandInput) bool {
return input.StartEcsServer || input.StartEc2Server
}
func ConfigureExecCommand(app *kingpin.Application, a *AwsVault) {
input := ExecCommandInput{}
cmd := app.Command("exec", "Execute a command with AWS credentials.")
cmd.Flag("duration", "Duration of the temporary or assume-role session. Defaults to 1h").
Short('d').
DurationVar(&input.SessionDuration)
cmd.Flag("no-session", "Skip creating STS session with GetSessionToken").
Short('n').
BoolVar(&input.NoSession)
cmd.Flag("region", "The AWS region").
StringVar(&input.Config.Region)
cmd.Flag("mfa-token", "The MFA token to use").
Short('t').
StringVar(&input.Config.MfaToken)
cmd.Flag("json", "Output credentials in JSON that can be used by credential_process").
Short('j').
Hidden().
BoolVar(&input.JSONDeprecated)
cmd.Flag("server", "Alias for --ecs-server").
Short('s').
BoolVar(&input.StartEcsServer)
cmd.Flag("ec2-server", "Run a EC2 metadata server in the background for credentials").
BoolVar(&input.StartEc2Server)
cmd.Flag("ecs-server", "Run a ECS credential server in the background for credentials (the SDK or app must support AWS_CONTAINER_CREDENTIALS_FULL_URI)").
BoolVar(&input.StartEcsServer)
cmd.Flag("lazy", "When using --ecs-server, lazily fetch credentials").
BoolVar(&input.Lazy)
cmd.Flag("stdout", "Print the SSO link to the terminal without automatically opening the browser").
BoolVar(&input.UseStdout)
cmd.Arg("profile", "Name of the profile").
Required().
HintAction(a.MustGetProfileNames).
StringVar(&input.ProfileName)
cmd.Arg("cmd", "Command to execute, defaults to $SHELL").
StringVar(&input.Command)
cmd.Arg("args", "Command arguments").
StringsVar(&input.Args)
cmd.Action(func(c *kingpin.ParseContext) (err error) {
input.Config.MfaPromptMethod = a.PromptDriver(hasBackgroundServer(input))
input.Config.NonChainedGetSessionTokenDuration = input.SessionDuration
input.Config.AssumeRoleDuration = input.SessionDuration
input.Config.SSOUseStdout = input.UseStdout
input.ShowHelpMessages = !a.Debug && input.Command == "" && isATerminal() && os.Getenv("AWS_VAULT_DISABLE_HELP_MESSAGE") != "1"
f, err := a.AwsConfigFile()
if err != nil {
return err
}
keyring, err := a.Keyring()
if err != nil {
return err
}
exitcode := 0
if input.JSONDeprecated {
exportCommandInput := ExportCommandInput{
ProfileName: input.ProfileName,
Format: "json",
Config: input.Config,
SessionDuration: input.SessionDuration,
NoSession: input.NoSession,
}
err = ExportCommand(exportCommandInput, f, keyring)
} else {
exitcode, err = ExecCommand(input, f, keyring)
}
app.FatalIfError(err, "exec")
// override exit code if not err
os.Exit(exitcode)
return nil
})
}
func ExecCommand(input ExecCommandInput, f *vault.ConfigFile, keyring keyring.Keyring) (exitcode int, err error) {
if os.Getenv("AWS_VAULT") != "" {
return 0, fmt.Errorf("running in an existing aws-vault subshell; 'exit' from the subshell or unset AWS_VAULT to force")
}
if err := input.validate(); err != nil {
return 0, err
}
config, err := vault.NewConfigLoader(input.Config, f, input.ProfileName).GetProfileConfig(input.ProfileName)
if err != nil {
return 0, fmt.Errorf("Error loading config: %w", err)
}
credsProvider, err := vault.NewTempCredentialsProvider(config, &vault.CredentialKeyring{Keyring: keyring}, input.NoSession, false)
if err != nil {
return 0, fmt.Errorf("Error getting temporary credentials: %w", err)
}
subshellHelp := ""
if input.Command == "" {
input.Command = getDefaultShell()
subshellHelp = fmt.Sprintf("Starting subshell %s, use `exit` to exit the subshell", input.Command)
}
cmdEnv := createEnv(input.ProfileName, config.Region)
if input.StartEc2Server {
if server.IsProxyRunning() {
return 0, fmt.Errorf("Another process is already bound to 169.254.169.254:80")
}
printHelpMessage("Warning: Starting a local EC2 credential server on 169.254.169.254:80; AWS credentials will be accessible to any process while it is running", input.ShowHelpMessages)
if err := server.StartEc2EndpointProxyServerProcess(); err != nil {
return 0, err
}
defer server.StopProxy()
if err = server.StartEc2CredentialsServer(context.TODO(), credsProvider, config.Region); err != nil {
return 0, fmt.Errorf("Failed to start credential server: %w", err)
}
printHelpMessage(subshellHelp, input.ShowHelpMessages)
} else if input.StartEcsServer {
printHelpMessage("Starting a local ECS credential server; your app's AWS sdk must support AWS_CONTAINER_CREDENTIALS_FULL_URI.", input.ShowHelpMessages)
if err = startEcsServerAndSetEnv(credsProvider, config, input.Lazy, &cmdEnv); err != nil {
return 0, err
}
printHelpMessage(subshellHelp, input.ShowHelpMessages)
} else {
if err = addCredsToEnv(credsProvider, input.ProfileName, &cmdEnv); err != nil {
return 0, err
}
printHelpMessage(subshellHelp, input.ShowHelpMessages)
err = doExecSyscall(input.Command, input.Args, cmdEnv) // will not return if exec syscall succeeds
if err != nil {
log.Println("Error doing execve syscall:", err.Error())
log.Println("Falling back to running a subprocess")
}
}
return runSubProcess(input.Command, input.Args, cmdEnv)
}
func printHelpMessage(helpMsg string, showHelpMessages bool) {
if helpMsg != "" {
if showHelpMessages {
printToStderr(helpMsg)
} else {
log.Println(helpMsg)
}
}
}
func printToStderr(helpMsg string) {
fmt.Fprint(os.Stderr, helpMsg, "\n")
}
func createEnv(profileName string, region string) environ {
env := environ(os.Environ())
env.Unset("AWS_ACCESS_KEY_ID")
env.Unset("AWS_SECRET_ACCESS_KEY")
env.Unset("AWS_SESSION_TOKEN")
env.Unset("AWS_SECURITY_TOKEN")
env.Unset("AWS_CREDENTIAL_FILE")
env.Unset("AWS_DEFAULT_PROFILE")
env.Unset("AWS_PROFILE")
env.Unset("AWS_SDK_LOAD_CONFIG")
env.Set("AWS_VAULT", profileName)
if region != "" {
// AWS_REGION is used by most SDKs. But boto3 (Python SDK) uses AWS_DEFAULT_REGION
// See https://docs.aws.amazon.com/sdkref/latest/guide/feature-region.html
log.Printf("Setting subprocess env: AWS_REGION=%s, AWS_DEFAULT_REGION=%s", region, region)
env.Set("AWS_REGION", region)
env.Set("AWS_DEFAULT_REGION", region)
}
return env
}
func startEcsServerAndSetEnv(credsProvider aws.CredentialsProvider, config *vault.ProfileConfig, lazy bool, cmdEnv *environ) error {
ecsServer, err := server.NewEcsServer(context.TODO(), credsProvider, config, "", 0, lazy)
if err != nil {
return err
}
go func() {
err = ecsServer.Serve()
if err != http.ErrServerClosed { // ErrServerClosed is a graceful close
log.Fatalf("ecs server: %s", err.Error())
}
}()
log.Println("Setting subprocess env AWS_CONTAINER_CREDENTIALS_FULL_URI, AWS_CONTAINER_AUTHORIZATION_TOKEN")
cmdEnv.Set("AWS_CONTAINER_CREDENTIALS_FULL_URI", ecsServer.BaseURL())
cmdEnv.Set("AWS_CONTAINER_AUTHORIZATION_TOKEN", ecsServer.AuthToken())
return nil
}
func addCredsToEnv(credsProvider aws.CredentialsProvider, profileName string, cmdEnv *environ) error {
creds, err := credsProvider.Retrieve(context.TODO())
if err != nil {
return fmt.Errorf("Failed to get credentials for %s: %w", profileName, err)
}
log.Println("Setting subprocess env: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY")
cmdEnv.Set("AWS_ACCESS_KEY_ID", creds.AccessKeyID)
cmdEnv.Set("AWS_SECRET_ACCESS_KEY", creds.SecretAccessKey)
if creds.SessionToken != "" {
log.Println("Setting subprocess env: AWS_SESSION_TOKEN")
cmdEnv.Set("AWS_SESSION_TOKEN", creds.SessionToken)
}
if creds.CanExpire {
log.Println("Setting subprocess env: AWS_CREDENTIAL_EXPIRATION")
cmdEnv.Set("AWS_CREDENTIAL_EXPIRATION", iso8601.Format(creds.Expires))
}
return nil
}
// environ is a slice of strings representing the environment, in the form "key=value".
type environ []string
// Unset an environment variable by key
func (e *environ) Unset(key string) {
for i := range *e {
if strings.HasPrefix((*e)[i], key+"=") {
(*e)[i] = (*e)[len(*e)-1]
*e = (*e)[:len(*e)-1]
break
}
}
}
// Set adds an environment variable, replacing any existing ones of the same key
func (e *environ) Set(key, val string) {
e.Unset(key)
*e = append(*e, key+"="+val)
}
func getDefaultShell() string {
command := os.Getenv("SHELL")
if command == "" {
if runtime.GOOS == "windows" {
command = "cmd.exe"
} else {
command = "/bin/sh"
}
}
return command
}
func runSubProcess(command string, args []string, env []string) (int, error) {
log.Printf("Starting a subprocess: %s %s", command, strings.Join(args, " "))
cmd := osexec.Command(command, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = env
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan)
if err := cmd.Start(); err != nil {
return 0, err
}
// proxy signals to process
go func() {
for {
sig := <-sigChan
_ = cmd.Process.Signal(sig)
}
}()
if err := cmd.Wait(); err != nil {
_ = cmd.Process.Signal(os.Kill)
return 0, fmt.Errorf("Failed to wait for command termination: %v", err)
}
waitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus)
return waitStatus.ExitStatus(), nil
}
func doExecSyscall(command string, args []string, env []string) error {
log.Printf("Exec command %s %s", command, strings.Join(args, " "))
argv0, err := osexec.LookPath(command)
if err != nil {
return fmt.Errorf("Couldn't find the executable '%s': %w", command, err)
}
log.Printf("Found executable %s", argv0)
argv := make([]string, 0, 1+len(args))
argv = append(argv, command)
argv = append(argv, args...)
return syscall.Exec(argv0, argv, env)
}
================================================
FILE: cli/exec_test.go
================================================
package cli
import (
"github.com/alecthomas/kingpin/v2"
"github.com/99designs/keyring"
)
func ExampleExecCommand() {
app := kingpin.New("aws-vault", "")
awsVault := ConfigureGlobals(app)
awsVault.keyringImpl = keyring.NewArrayKeyring([]keyring.Item{
{Key: "llamas", Data: []byte(`{"AccessKeyID":"ABC","SecretAccessKey":"XYZ"}`)},
})
ConfigureExecCommand(app, awsVault)
kingpin.MustParse(app.Parse([]string{
"--debug", "exec", "--no-session", "llamas", "--", "sh", "-c", "echo $AWS_ACCESS_KEY_ID",
}))
// Output:
// ABC
}
================================================
FILE: cli/export.go
================================================
package cli
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"time"
"github.com/99designs/aws-vault/v7/iso8601"
"github.com/99designs/aws-vault/v7/vault"
"github.com/99designs/keyring"
"github.com/alecthomas/kingpin/v2"
"github.com/aws/aws-sdk-go-v2/aws"
ini "gopkg.in/ini.v1"
)
type ExportCommandInput struct {
ProfileName string
Format string
Config vault.ProfileConfig
SessionDuration time.Duration
NoSession bool
UseStdout bool
}
var (
FormatTypeEnv = "env"
FormatTypeExportEnv = "export-env"
FormatTypeExportJSON = "json"
FormatTypeExportINI = "ini"
)
func ConfigureExportCommand(app *kingpin.Application, a *AwsVault) {
input := ExportCommandInput{}
cmd := app.Command("export", "Export AWS credentials.")
cmd.Flag("duration", "Duration of the temporary or assume-role session. Defaults to 1h").
Short('d').
DurationVar(&input.SessionDuration)
cmd.Flag("no-session", "Skip creating STS session with GetSessionToken").
Short('n').
BoolVar(&input.NoSession)
cmd.Flag("region", "The AWS region").
StringVar(&input.Config.Region)
cmd.Flag("mfa-token", "The MFA token to use").
Short('t').
StringVar(&input.Config.MfaToken)
cmd.Flag("format", fmt.Sprintf("Format to output credentials. Valid formats: %s, %s, %s, %s", FormatTypeEnv, FormatTypeExportEnv, FormatTypeExportJSON, FormatTypeExportINI)).
Default(FormatTypeEnv).
EnumVar(&input.Format, FormatTypeEnv, FormatTypeExportEnv, FormatTypeExportJSON, FormatTypeExportINI)
cmd.Flag("stdout", "Print the SSO link to the terminal without automatically opening the browser").
BoolVar(&input.UseStdout)
cmd.Arg("profile", "Name of the profile").
Required().
HintAction(a.MustGetProfileNames).
StringVar(&input.ProfileName)
cmd.Action(func(c *kingpin.ParseContext) (err error) {
input.Config.MfaPromptMethod = a.PromptDriver(false)
input.Config.NonChainedGetSessionTokenDuration = input.SessionDuration
input.Config.AssumeRoleDuration = input.SessionDuration
input.Config.SSOUseStdout = input.UseStdout
f, err := a.AwsConfigFile()
if err != nil {
return err
}
keyring, err := a.Keyring()
if err != nil {
return err
}
err = ExportCommand(input, f, keyring)
app.FatalIfError(err, "exec")
return nil
})
}
func ExportCommand(input ExportCommandInput, f *vault.ConfigFile, keyring keyring.Keyring) error {
if os.Getenv("AWS_VAULT") != "" {
return fmt.Errorf("in an existing aws-vault subshell; 'exit' from the subshell or unset AWS_VAULT to force")
}
config, err := vault.NewConfigLoader(input.Config, f, input.ProfileName).GetProfileConfig(input.ProfileName)
if err != nil {
return fmt.Errorf("Error loading config: %w", err)
}
ckr := &vault.CredentialKeyring{Keyring: keyring}
credsProvider, err := vault.NewTempCredentialsProvider(config, ckr, input.NoSession, false)
if err != nil {
return fmt.Errorf("Error getting temporary credentials: %w", err)
}
if input.Format == FormatTypeExportJSON {
return printJSON(input, credsProvider)
} else if input.Format == FormatTypeExportINI {
return printINI(credsProvider, input.ProfileName, config.Region)
} else if input.Format == FormatTypeExportEnv {
return printEnv(input, credsProvider, config.Region, "export ")
} else {
return printEnv(input, credsProvider, config.Region, "")
}
}
func printJSON(input ExportCommandInput, credsProvider aws.CredentialsProvider) error {
// AwsCredentialHelperData is metadata for AWS CLI credential process
// See https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#sourcing-credentials-from-external-processes
type AwsCredentialHelperData struct {
Version int `json:"Version"`
AccessKeyID string `json:"AccessKeyId"`
SecretAccessKey string `json:"SecretAccessKey"`
SessionToken string `json:"SessionToken,omitempty"`
Expiration string `json:"Expiration,omitempty"`
}
creds, err := credsProvider.Retrieve(context.TODO())
if err != nil {
return fmt.Errorf("Failed to get credentials for %s: %w", input.ProfileName, err)
}
credentialData := AwsCredentialHelperData{
Version: 1,
AccessKeyID: creds.AccessKeyID,
SecretAccessKey: creds.SecretAccessKey,
SessionToken: creds.SessionToken,
}
if creds.CanExpire {
credentialData.Expiration = iso8601.Format(creds.Expires)
}
json, err := json.MarshalIndent(&credentialData, "", " ")
if err != nil {
return fmt.Errorf("Error creating credential json: %w", err)
}
fmt.Print(string(json) + "\n")
return nil
}
func mustNewKey(s *ini.Section, name, val string) {
if val != "" {
_, err := s.NewKey(name, val)
if err != nil {
log.Fatalln("Failed to create ini key:", err.Error())
}
}
}
func printINI(credsProvider aws.CredentialsProvider, profilename, region string) error {
creds, err := credsProvider.Retrieve(context.TODO())
if err != nil {
return fmt.Errorf("Failed to get credentials for %s: %w", profilename, err)
}
f := ini.Empty()
s, err := f.NewSection(profilename)
if err != nil {
return fmt.Errorf("Failed to create ini section: %w", err)
}
mustNewKey(s, "aws_access_key_id", creds.AccessKeyID)
mustNewKey(s, "aws_secret_access_key", creds.SecretAccessKey)
mustNewKey(s, "aws_session_token", creds.SessionToken)
if creds.CanExpire {
mustNewKey(s, "aws_credential_expiration", iso8601.Format(creds.Expires))
}
mustNewKey(s, "region", region)
_, err = f.WriteTo(os.Stdout)
if err != nil {
return fmt.Errorf("Failed to output ini: %w", err)
}
return nil
}
func printEnv(input ExportCommandInput, credsProvider aws.CredentialsProvider, region, prefix string) error {
creds, err := credsProvider.Retrieve(context.TODO())
if err != nil {
return fmt.Errorf("Failed to get credentials for %s: %w", input.ProfileName, err)
}
fmt.Printf("%sAWS_ACCESS_KEY_ID=%s\n", prefix, creds.AccessKeyID)
fmt.Printf("%sAWS_SECRET_ACCESS_KEY=%s\n", prefix, creds.SecretAccessKey)
if creds.SessionToken != "" {
fmt.Printf("%sAWS_SESSION_TOKEN=%s\n", prefix, creds.SessionToken)
}
if creds.CanExpire {
fmt.Printf("%sAWS_CREDENTIAL_EXPIRATION=%s\n", prefix, iso8601.Format(creds.Expires))
}
if region != "" {
fmt.Printf("%sAWS_REGION=%s\n", prefix, region)
fmt.Printf("%sAWS_DEFAULT_REGION=%s\n", prefix, region)
}
return nil
}
================================================
FILE: cli/export_test.go
================================================
package cli
import (
"github.com/alecthomas/kingpin/v2"
"github.com/99designs/keyring"
)
func ExampleExportCommand() {
app := kingpin.New("aws-vault", "")
awsVault := ConfigureGlobals(app)
awsVault.keyringImpl = keyring.NewArrayKeyring([]keyring.Item{
{Key: "llamas", Data: []byte(`{"AccessKeyID":"ABC","SecretAccessKey":"XYZ"}`)},
})
ConfigureExportCommand(app, awsVault)
kingpin.MustParse(app.Parse([]string{
"export", "--format=ini", "--no-session", "llamas",
}))
// Output:
// [llamas]
// aws_access_key_id=ABC
// aws_secret_access_key=XYZ
// region=us-east-1
}
================================================
FILE: cli/global.go
================================================
package cli
import (
"fmt"
"io"
"log"
"os"
"strings"
"github.com/99designs/aws-vault/v7/prompt"
"github.com/99designs/aws-vault/v7/vault"
"github.com/99designs/keyring"
"github.com/alecthomas/kingpin/v2"
isatty "github.com/mattn/go-isatty"
"golang.org/x/term"
)
var keyringConfigDefaults = keyring.Config{
ServiceName: "aws-vault",
FilePasswordFunc: fileKeyringPassphrasePrompt,
LibSecretCollectionName: "awsvault",
KWalletAppID: "aws-vault",
KWalletFolder: "aws-vault",
KeychainTrustApplication: true,
WinCredPrefix: "aws-vault",
}
type AwsVault struct {
Debug bool
KeyringConfig keyring.Config
KeyringBackend string
promptDriver string
keyringImpl keyring.Keyring
awsConfigFile *vault.ConfigFile
}
func isATerminal() bool {
fd := os.Stdout.Fd()
return isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd)
}
func (a *AwsVault) PromptDriver(avoidTerminalPrompt bool) string {
if a.promptDriver == "" {
a.promptDriver = "terminal"
if !isATerminal() || avoidTerminalPrompt {
for _, driver := range prompt.Available() {
a.promptDriver = driver
if driver != "terminal" {
break
}
}
}
}
log.Println("Using prompt driver: " + a.promptDriver)
return a.promptDriver
}
func (a *AwsVault) Keyring() (keyring.Keyring, error) {
if a.keyringImpl == nil {
if a.KeyringBackend != "" {
a.KeyringConfig.AllowedBackends = []keyring.BackendType{keyring.BackendType(a.KeyringBackend)}
}
var err error
a.keyringImpl, err = keyring.Open(a.KeyringConfig)
if err != nil {
return nil, err
}
}
return a.keyringImpl, nil
}
func (a *AwsVault) AwsConfigFile() (*vault.ConfigFile, error) {
if a.awsConfigFile == nil {
var err error
a.awsConfigFile, err = vault.LoadConfigFromEnv()
if err != nil {
return nil, err
}
}
return a.awsConfigFile, nil
}
func (a *AwsVault) MustGetProfileNames() []string {
config, err := a.AwsConfigFile()
if err != nil {
log.Fatalf("Error loading AWS config: %s", err.Error())
}
return config.ProfileNames()
}
func ConfigureGlobals(app *kingpin.Application) *AwsVault {
a := &AwsVault{
KeyringConfig: keyringConfigDefaults,
}
backendsAvailable := []string{}
for _, backendType := range keyring.AvailableBackends() {
backendsAvailable = append(backendsAvailable, string(backendType))
}
promptsAvailable := prompt.Available()
app.Flag("debug", "Show debugging output").
BoolVar(&a.Debug)
app.Flag("backend", fmt.Sprintf("Secret backend to use %v", backendsAvailable)).
Default(backendsAvailable[0]).
Envar("AWS_VAULT_BACKEND").
EnumVar(&a.KeyringBackend, backendsAvailable...)
app.Flag("prompt", fmt.Sprintf("Prompt driver to use %v", promptsAvailable)).
Envar("AWS_VAULT_PROMPT").
StringVar(&a.promptDriver)
app.Validate(func(app *kingpin.Application) error {
if a.promptDriver == "" {
return nil
}
if a.promptDriver == "pass" {
kingpin.Fatalf("--prompt=pass (or AWS_VAULT_PROMPT=pass) has been removed from aws-vault as using TOTPs without " +
"a dedicated device goes against security best practices. If you wish to continue using pass, " +
"add `mfa_process = pass otp <your mfa_serial>` to profiles in your ~/.aws/config file.")
}
for _, v := range promptsAvailable {
if v == a.promptDriver {
return nil
}
}
return fmt.Errorf("--prompt value must be one of %s, got '%s'", strings.Join(promptsAvailable, ","), a.promptDriver)
})
app.Flag("keychain", "Name of macOS keychain to use, if it doesn't exist it will be created").
Default("aws-vault").
Envar("AWS_VAULT_KEYCHAIN_NAME").
StringVar(&a.KeyringConfig.KeychainName)
app.Flag("secret-service-collection", "Name of secret-service collection to use, if it doesn't exist it will be created").
Default("awsvault").
Envar("AWS_VAULT_SECRET_SERVICE_COLLECTION_NAME").
StringVar(&a.KeyringConfig.LibSecretCollectionName)
app.Flag("pass-dir", "Pass password store directory").
Envar("AWS_VAULT_PASS_PASSWORD_STORE_DIR").
StringVar(&a.KeyringConfig.PassDir)
app.Flag("pass-cmd", "Name of the pass executable").
Envar("AWS_VAULT_PASS_CMD").
StringVar(&a.KeyringConfig.PassCmd)
app.Flag("pass-prefix", "Prefix to prepend to the item path stored in pass").
Envar("AWS_VAULT_PASS_PREFIX").
StringVar(&a.KeyringConfig.PassPrefix)
app.Flag("file-dir", "Directory for the \"file\" password store").
Default("~/.awsvault/keys/").
Envar("AWS_VAULT_FILE_DIR").
StringVar(&a.KeyringConfig.FileDir)
app.PreAction(func(c *kingpin.ParseContext) error {
if !a.Debug {
log.SetOutput(io.Discard)
}
keyring.Debug = a.Debug
log.Printf("aws-vault %s", app.Model().Version)
return nil
})
return a
}
func fileKeyringPassphrasePrompt(prompt string) (string, error) {
if password, ok := os.LookupEnv("AWS_VAULT_FILE_PASSPHRASE"); ok {
return password, nil
}
fmt.Fprintf(os.Stderr, "%s: ", prompt)
b, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return "", err
}
fmt.Println()
return string(b), nil
}
================================================
FILE: cli/list.go
================================================
package cli
import (
"fmt"
"os"
"strings"
"text/tabwriter"
"time"
"github.com/99designs/aws-vault/v7/vault"
"github.com/99designs/keyring"
"github.com/alecthomas/kingpin/v2"
)
type ListCommandInput struct {
OnlyProfiles bool
OnlySessions bool
OnlyCredentials bool
}
func ConfigureListCommand(app *kingpin.Application, a *AwsVault) {
input := ListCommandInput{}
cmd := app.Command("list", "List profiles, along with their credentials and sessions.")
cmd.Alias("ls")
cmd.Flag("profiles", "Show only the profile names").
BoolVar(&input.OnlyProfiles)
cmd.Flag("sessions", "Show only the session names").
BoolVar(&input.OnlySessions)
cmd.Flag("credentials", "Show only the profiles with stored credential").
BoolVar(&input.OnlyCredentials)
cmd.Action(func(c *kingpin.ParseContext) (err error) {
keyring, err := a.Keyring()
if err != nil {
return err
}
awsConfigFile, err := a.AwsConfigFile()
if err != nil {
return err
}
err = ListCommand(input, awsConfigFile, keyring)
app.FatalIfError(err, "list")
return nil
})
}
type stringslice []string
func (ss stringslice) remove(stringsToRemove []string) (newSS []string) {
xx := stringslice(stringsToRemove)
for _, s := range ss {
if !xx.has(s) {
newSS = append(newSS, s)
}
}
return
}
func (ss stringslice) has(s string) bool {
for _, t := range ss {
if s == t {
return true
}
}
return false
}
func sessionLabel(sess vault.SessionMetadata) string {
return fmt.Sprintf("%s:%s", sess.Type, time.Until(sess.Expiration).Truncate(time.Second))
}
func ListCommand(input ListCommandInput, awsConfigFile *vault.ConfigFile, keyring keyring.Keyring) (err error) {
credentialKeyring := &vault.CredentialKeyring{Keyring: keyring}
oidcTokenKeyring := &vault.OIDCTokenKeyring{Keyring: credentialKeyring.Keyring}
sessionKeyring := &vault.SessionKeyring{Keyring: credentialKeyring.Keyring}
credentialsNames, err := credentialKeyring.Keys()
if err != nil {
return err
}
tokens, err := oidcTokenKeyring.Keys()
if err != nil {
return err
}
sessions, err := sessionKeyring.GetAllMetadata()
if err != nil {
return err
}
allSessionLabels := []string{}
for _, t := range tokens {
allSessionLabels = append(allSessionLabels, fmt.Sprintf("oidc:%s", t))
}
for _, sess := range sessions {
allSessionLabels = append(allSessionLabels, sessionLabel(sess))
}
if input.OnlyCredentials {
for _, c := range credentialsNames {
fmt.Println(c)
}
return nil
}
if input.OnlyProfiles {
for _, profileName := range awsConfigFile.ProfileNames() {
fmt.Println(profileName)
}
return nil
}
if input.OnlySessions {
for _, l := range allSessionLabels {
fmt.Println(l)
}
return nil
}
displayedSessionLabels := []string{}
w := tabwriter.NewWriter(os.Stdout, 25, 4, 2, ' ', 0)
fmt.Fprintln(w, "Profile\tCredentials\tSessions\t")
fmt.Fprintln(w, "=======\t===========\t========\t")
// list out known profiles first
for _, profileName := range awsConfigFile.ProfileNames() {
fmt.Fprintf(w, "%s\t", profileName)
hasCred, err := credentialKeyring.Has(profileName)
if err != nil {
return err
}
if hasCred {
fmt.Fprintf(w, "%s\t", profileName)
} else {
fmt.Fprintf(w, "-\t")
}
var sessionLabels []string
// check oidc keyring
if profileSection, ok := awsConfigFile.ProfileSection(profileName); ok {
if exists, _ := oidcTokenKeyring.Has(profileSection.SSOStartURL); exists {
sessionLabels = append(sessionLabels, fmt.Sprintf("oidc:%s", profileSection.SSOStartURL))
}
}
// check session keyring
for _, sess := range sessions {
if profileName == sess.ProfileName {
sessionLabels = append(sessionLabels, sessionLabel(sess))
}
}
if len(sessionLabels) > 0 {
fmt.Fprintf(w, "%s\t\n", strings.Join(sessionLabels, ", "))
} else {
fmt.Fprintf(w, "-\t\n")
}
displayedSessionLabels = append(displayedSessionLabels, sessionLabels...)
}
// show credentials that don't have profiles
for _, credentialName := range credentialsNames {
_, ok := awsConfigFile.ProfileSection(credentialName)
if !ok {
fmt.Fprintf(w, "-\t%s\t-\t\n", credentialName)
}
}
// show sessions that don't have profiles
sessionsWithoutProfiles := stringslice(allSessionLabels).remove(displayedSessionLabels)
for _, s := range sessionsWithoutProfiles {
fmt.Fprintf(w, "-\t-\t%s\t\n", s)
}
return w.Flush()
}
================================================
FILE: cli/list_test.go
================================================
package cli
import (
"github.com/alecthomas/kingpin/v2"
"github.com/99designs/keyring"
)
func ExampleListCommand() {
app := kingpin.New("aws-vault", "")
awsVault := ConfigureGlobals(app)
awsVault.keyringImpl = keyring.NewArrayKeyring([]keyring.Item{
{Key: "llamas", Data: []byte(`{"AccessKeyID":"ABC","SecretAccessKey":"XYZ"}`)},
})
ConfigureListCommand(app, awsVault)
kingpin.MustParse(app.Parse([]string{
"list", "--credentials",
}))
// Output:
// llamas
}
================================================
FILE: cli/login.go
================================================
package cli
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"strings"
"time"
"github.com/99designs/aws-vault/v7/vault"
"github.com/99designs/keyring"
"github.com/alecthomas/kingpin/v2"
"github.com/aws/aws-sdk-go-v2/aws"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/skratchdot/open-golang/open"
)
type LoginCommandInput struct {
ProfileName string
UseStdout bool
Path string
Config vault.ProfileConfig
SessionDuration time.Duration
NoSession bool
}
func ConfigureLoginCommand(app *kingpin.Application, a *AwsVault) {
input := LoginCommandInput{}
cmd := app.Command("login", "Generate a login link for the AWS Console.")
cmd.Flag("duration", "Duration of the assume-role or federated session. Defaults to 1h").
Short('d').
DurationVar(&input.SessionDuration)
cmd.Flag("no-session", "Skip creating STS session with GetSessionToken").
Short('n').
BoolVar(&input.NoSession)
cmd.Flag("mfa-token", "The MFA token to use").
Short('t').
StringVar(&input.Config.MfaToken)
cmd.Flag("path", "The AWS service you would like access").
StringVar(&input.Path)
cmd.Flag("region", "The AWS region").
StringVar(&input.Config.Region)
cmd.Flag("stdout", "Print login URL to stdout instead of opening in default browser").
Short('s').
BoolVar(&input.UseStdout)
cmd.Arg("profile", "Name of the profile. If none given, credentials will be sourced from env vars").
HintAction(a.MustGetProfileNames).
StringVar(&input.ProfileName)
cmd.Action(func(c *kingpin.ParseContext) (err error) {
input.Config.MfaPromptMethod = a.PromptDriver(false)
input.Config.NonChainedGetSessionTokenDuration = input.SessionDuration
input.Config.AssumeRoleDuration = input.SessionDuration
input.Config.GetFederationTokenDuration = input.SessionDuration
keyring, err := a.Keyring()
if err != nil {
return err
}
f, err := a.AwsConfigFile()
if err != nil {
return err
}
err = LoginCommand(context.Background(), input, f, keyring)
app.FatalIfError(err, "login")
return nil
})
}
func getCredsProvider(input LoginCommandInput, config *vault.ProfileConfig, keyring keyring.Keyring) (credsProvider aws.CredentialsProvider, err error) {
if input.ProfileName == "" {
// When no profile is specified, source credentials from the environment
configFromEnv, err := awsconfig.NewEnvConfig()
if err != nil {
return nil, fmt.Errorf("unable to authenticate to AWS through your environment variables: %w", err)
}
if configFromEnv.Credentials.AccessKeyID == "" {
return nil, fmt.Errorf("argument 'profile' not provided, nor any AWS env vars found. Try --help")
}
credsProvider = credentials.StaticCredentialsProvider{Value: configFromEnv.Credentials}
} else {
// Use a profile from the AWS config file
ckr := &vault.CredentialKeyring{Keyring: keyring}
t := vault.TempCredentialsCreator{
Keyring: ckr,
DisableSessions: input.NoSession,
DisableSessionsForProfile: config.ProfileName,
}
credsProvider, err = t.GetProviderForProfile(config)
if err != nil {
return nil, fmt.Errorf("profile %s: %w", input.ProfileName, err)
}
}
return credsProvider, err
}
// LoginCommand creates a login URL for the AWS Management Console using the method described at
// https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_enable-console-custom-url.html
func LoginCommand(ctx context.Context, input LoginCommandInput, f *vault.ConfigFile, keyring keyring.Keyring) error {
config, err := vault.NewConfigLoader(input.Config, f, input.ProfileName).GetProfileConfig(input.ProfileName)
if err != nil {
return fmt.Errorf("Error loading config: %w", err)
}
credsProvider, err := getCredsProvider(input, config, keyring)
if err != nil {
return err
}
// if we already know the type of credentials being created, avoid calling isCallerIdentityAssumedRole
canCredsBeUsedInLoginURL, err := canProviderBeUsedForLogin(credsProvider)
if err != nil {
return err
}
if !canCredsBeUsedInLoginURL {
// use a static creds provider so that we don't request credentials from AWS more than once
credsProvider, err = createStaticCredentialsProvider(ctx, credsProvider)
if err != nil {
return err
}
// if the credentials have come from an unknown source like credential_process, check the
// caller identity to see if it's an assumed role
isAssumedRole, err := isCallerIdentityAssumedRole(ctx, credsProvider, config)
if err != nil {
return err
}
if !isAssumedRole {
log.Println("Creating a federated session")
credsProvider, err = vault.NewFederationTokenProvider(ctx, credsProvider, config)
if err != nil {
return err
}
}
}
creds, err := credsProvider.Retrieve(ctx)
if err != nil {
return err
}
if creds.CanExpire {
log.Printf("Requesting a signin token for session expiring in %s", time.Until(creds.Expires))
}
loginURLPrefix, destination := generateLoginURL(config.Region, input.Path)
signinToken, err := requestSigninToken(ctx, creds, loginURLPrefix)
if err != nil {
return err
}
loginURL := fmt.Sprintf("%s?Action=login&Issuer=aws-vault&Destination=%s&SigninToken=%s",
loginURLPrefix, url.QueryEscape(destination), url.QueryEscape(signinToken))
if input.UseStdout {
fmt.Println(loginURL)
} else if err = open.Run(loginURL); err != nil {
return fmt.Errorf("Failed to open %s: %w", loginURL, err)
}
return nil
}
func generateLoginURL(region string, path string) (string, string) {
loginURLPrefix := "https://signin.aws.amazon.com/federation"
destination := "https://console.aws.amazon.com/"
if region != "" {
destinationDomain := "console.aws.amazon.com"
switch {
case strings.HasPrefix(region, "cn-"):
loginURLPrefix = "https://signin.amazonaws.cn/federation"
destinationDomain = "console.amazonaws.cn"
case strings.HasPrefix(region, "us-gov-"):
loginURLPrefix = "https://signin.amazonaws-us-gov.com/federation"
destinationDomain = "console.amazonaws-us-gov.com"
}
if path != "" {
destination = fmt.Sprintf("https://%s.%s/%s?region=%s",
region, destinationDomain, path, region)
} else {
destination = fmt.Sprintf("https://%s.%s/console/home?region=%s",
region, destinationDomain, region)
}
}
return loginURLPrefix, destination
}
func isCallerIdentityAssumedRole(ctx context.Context, credsProvider aws.CredentialsProvider, config *vault.ProfileConfig) (bool, error) {
cfg := vault.NewAwsConfigWithCredsProvider(credsProvider, config.Region, config.STSRegionalEndpoints)
client := sts.NewFromConfig(cfg)
id, err := client.GetCallerIdentity(ctx, nil)
if err != nil {
return false, err
}
arn := aws.ToString(id.Arn)
arnParts := strings.Split(arn, ":")
if len(arnParts) < 6 {
return false, fmt.Errorf("unable to parse ARN: %s", arn)
}
if strings.HasPrefix(arnParts[5], "assumed-role") {
return true, nil
}
return false, nil
}
func createStaticCredentialsProvider(ctx context.Context, credsProvider aws.CredentialsProvider) (sc credentials.StaticCredentialsProvider, err error) {
creds, err := credsProvider.Retrieve(ctx)
if err != nil {
return sc, err
}
return credentials.StaticCredentialsProvider{Value: creds}, nil
}
// canProviderBeUsedForLogin returns true if the credentials produced by the provider is known to be usable by the login URL endpoint
func canProviderBeUsedForLogin(credsProvider aws.CredentialsProvider) (bool, error) {
if _, ok := credsProvider.(*vault.AssumeRoleProvider); ok {
return true, nil
}
if _, ok := credsProvider.(*vault.SSORoleCredentialsProvider); ok {
return true, nil
}
if _, ok := credsProvider.(*vault.AssumeRoleWithWebIdentityProvider); ok {
return true, nil
}
if c, ok := credsProvider.(*vault.CachedSessionProvider); ok {
return canProviderBeUsedForLogin(c.SessionProvider)
}
return false, nil
}
// Create a signin token
func requestSigninToken(ctx context.Context, creds aws.Credentials, loginURLPrefix string) (string, error) {
jsonSession, err := json.Marshal(map[string]string{
"sessionId": creds.AccessKeyID,
"sessionKey": creds.SecretAccessKey,
"sessionToken": creds.SessionToken,
})
if err != nil {
return "", err
}
req, err := http.NewRequestWithContext(ctx, "GET", loginURLPrefix, nil)
if err != nil {
return "", err
}
q := req.URL.Query()
q.Add("Action", "getSigninToken")
q.Add("Session", string(jsonSession))
req.URL.RawQuery = q.Encode()
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
if resp.StatusCode != http.StatusOK {
log.Printf("Response body was %s", body)
return "", fmt.Errorf("Call to getSigninToken failed with %v", resp.Status)
}
var respParsed map[string]string
err = json.Unmarshal(body, &respParsed)
if err != nil {
return "", err
}
signinToken, ok := respParsed["SigninToken"]
if !ok {
return "", fmt.Errorf("Expected a response with SigninToken")
}
return signinToken, nil
}
================================================
FILE: cli/proxy.go
================================================
package cli
import (
"os"
"os/signal"
"syscall"
"github.com/99designs/aws-vault/v7/server"
"github.com/alecthomas/kingpin/v2"
)
func ConfigureProxyCommand(app *kingpin.Application) {
stop := false
cmd := app.Command("proxy", "Start a proxy for the ec2 instance role server locally.").
Alias("server").
Hidden()
cmd.Flag("stop", "Stop the proxy").
BoolVar(&stop)
cmd.Action(func(*kingpin.ParseContext) error {
if stop {
server.StopProxy()
return nil
}
handleSigTerm()
return server.StartProxy()
})
}
func handleSigTerm() {
// shutdown
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
server.Shutdown()
os.Exit(1)
}()
}
================================================
FILE: cli/remove.go
================================================
package cli
import (
"fmt"
"strings"
"github.com/99designs/aws-vault/v7/prompt"
"github.com/99designs/aws-vault/v7/vault"
"github.com/99designs/keyring"
"github.com/alecthomas/kingpin/v2"
)
type RemoveCommandInput struct {
ProfileName string
SessionsOnly bool
Force bool
}
func ConfigureRemoveCommand(app *kingpin.Application, a *AwsVault) {
input := RemoveCommandInput{}
cmd := app.Command("remove", "Remove credentials from the secure keystore.")
cmd.Alias("rm")
cmd.Arg("profile", "Name of the profile").
Required().
HintAction(a.MustGetProfileNames).
StringVar(&input.ProfileName)
cmd.Flag("sessions-only", "Only remove sessions, leave credentials intact").
Short('s').
Hidden().
BoolVar(&input.SessionsOnly)
cmd.Flag("force", "Force-remove the profile without a prompt").
Short('f').
BoolVar(&input.Force)
cmd.Action(func(c *kingpin.ParseContext) error {
keyring, err := a.Keyring()
if err != nil {
return err
}
err = RemoveCommand(input, keyring)
app.FatalIfError(err, "remove")
return nil
})
}
func RemoveCommand(input RemoveCommandInput, keyring keyring.Keyring) error {
ckr := &vault.CredentialKeyring{Keyring: keyring}
// Legacy --sessions-only option for backwards compatibility, use aws-vault clear instead
if input.SessionsOnly {
sk := &vault.SessionKeyring{Keyring: ckr.Keyring}
n, err := sk.RemoveForProfile(input.ProfileName)
if err != nil {
return err
}
fmt.Printf("Deleted %d sessions.\n", n)
return nil
}
if !input.Force {
r, err := prompt.TerminalPrompt(fmt.Sprintf("Delete credentials for profile %q? (y|N) ", input.ProfileName))
if err != nil {
return err
}
if !strings.EqualFold(r, "y") && !strings.EqualFold(r, "yes") {
return nil
}
}
if err := ckr.Remove(input.ProfileName); err != nil {
return err
}
fmt.Printf("Deleted credentials.\n")
return nil
}
================================================
FILE: cli/rotate.go
================================================
package cli
import (
"context"
"fmt"
"log"
"time"
"github.com/99designs/aws-vault/v7/vault"
"github.com/99designs/keyring"
"github.com/alecthomas/kingpin/v2"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
)
type RotateCommandInput struct {
NoSession bool
ProfileName string
Config vault.ProfileConfig
}
func ConfigureRotateCommand(app *kingpin.Application, a *AwsVault) {
input := RotateCommandInput{}
cmd := app.Command("rotate", "Rotate credentials.")
cmd.Flag("no-session", "Use master credentials, no session or role used").
Short('n').
BoolVar(&input.NoSession)
cmd.Arg("profile", "Name of the profile").
Required().
HintAction(a.MustGetProfileNames).
StringVar(&input.ProfileName)
cmd.Action(func(c *kingpin.ParseContext) (err error) {
input.Config.MfaPromptMethod = a.PromptDriver(false)
keyring, err := a.Keyring()
if err != nil {
return err
}
f, err := a.AwsConfigFile()
if err != nil {
return err
}
err = RotateCommand(input, f, keyring)
app.FatalIfError(err, "rotate")
return nil
})
}
func RotateCommand(input RotateCommandInput, f *vault.ConfigFile, keyring keyring.Keyring) error {
configLoader := vault.NewConfigLoader(input.Config, f, input.ProfileName)
config, err := configLoader.GetProfileConfig(input.ProfileName)
if err != nil {
return fmt.Errorf("Error loading config: %w", err)
}
ckr := &vault.CredentialKeyring{Keyring: keyring}
masterCredentialsName, err := vault.FindMasterCredentialsNameFor(input.ProfileName, ckr, config)
if err != nil {
return fmt.Errorf("Error determining credential name for '%s': %w", input.ProfileName, err)
}
if input.NoSession {
fmt.Printf("Rotating credentials stored for profile '%s' using master credentials (takes 10-20 seconds)\n", masterCredentialsName)
} else {
fmt.Printf("Rotating credentials stored for profile '%s' using a session from profile '%s' (takes 10-20 seconds)\n", masterCredentialsName, input.ProfileName)
}
// Get the existing credentials access key ID
oldMasterCreds, err := vault.NewMasterCredentialsProvider(ckr, masterCredentialsName).Retrieve(context.TODO())
if err != nil {
return fmt.Errorf("Error loading source credentials for '%s': %w", masterCredentialsName, err)
}
oldMasterCredsAccessKeyID := vault.FormatKeyForDisplay(oldMasterCreds.AccessKeyID)
log.Printf("Rotating access key %s\n", oldMasterCredsAccessKeyID)
fmt.Println("Creating a new access key")
// create a session to rotate the credentials
var credsProvider aws.CredentialsProvider
if input.NoSession {
credsProvider = vault.NewMasterCredentialsProvider(ckr, config.ProfileName)
} else {
// Can't always disable sessions completely, might need to use session for MFA-Protected API Access
credsProvider, err = vault.NewTempCredentialsProvider(config, ckr, input.NoSession, true)
if err != nil {
return fmt.Errorf("Error getting temporary credentials: %w", err)
}
}
cfg := vault.NewAwsConfigWithCredsProvider(credsProvider, config.Region, config.STSRegionalEndpoints)
// A username is needed for some IAM calls if the credentials have assumed a role
iamUserName, err := getUsernameIfAssumingRole(context.TODO(), cfg, config)
if err != nil {
return err
}
iamClient := iam.NewFromConfig(cfg)
// Create a new access key
createOut, err := iamClient.CreateAccessKey(context.TODO(), &iam.CreateAccessKeyInput{
UserName: iamUserName,
})
if err != nil {
return fmt.Errorf("Error creating a new access key: %w", err)
}
fmt.Printf("Created new access key %s\n", vault.FormatKeyForDisplay(*createOut.AccessKey.AccessKeyId))
newMasterCreds := aws.Credentials{
AccessKeyID: *createOut.AccessKey.AccessKeyId,
SecretAccessKey: *createOut.AccessKey.SecretAccessKey,
}
err = ckr.Set(masterCredentialsName, newMasterCreds)
if err != nil {
return fmt.Errorf("Error storing new access key %s: %w", vault.FormatKeyForDisplay(newMasterCreds.AccessKeyID), err)
}
// Delete old sessions
sk := &vault.SessionKeyring{Keyring: ckr.Keyring}
profileNames, err := getProfilesInChain(input.ProfileName, configLoader)
for _, profileName := range profileNames {
if n, _ := sk.RemoveForProfile(profileName); n > 0 {
fmt.Printf("Deleted %d sessions for %s\n", n, profileName)
}
}
// Use new credentials to delete old access key
fmt.Printf("Deleting old access key %s\n", oldMasterCredsAccessKeyID)
err = retry(time.Second*20, time.Second*2, func() error {
_, err = iamClient.DeleteAccessKey(context.TODO(), &iam.DeleteAccessKeyInput{
AccessKeyId: &oldMasterCreds.AccessKeyID,
UserName: iamUserName,
})
return err
})
if err != nil {
return fmt.Errorf("Can't delete old access key %s: %w", oldMasterCredsAccessKeyID, err)
}
fmt.Printf("Deleted old access key %s\n", oldMasterCredsAccessKeyID)
fmt.Println("Finished rotating access key")
return nil
}
func retry(maxTime time.Duration, sleep time.Duration, f func() error) (err error) {
t0 := time.Now()
i := 0
for {
i++
err = f()
if err == nil {
return // nolint
}
elapsed := time.Since(t0)
if elapsed > maxTime {
return fmt.Errorf("After %d attempts, last error: %s", i, err)
}
time.Sleep(sleep)
log.Println("Retrying after error:", err)
}
}
func getUsernameIfAssumingRole(ctx context.Context, awsCfg aws.Config, config *vault.ProfileConfig) (*string, error) {
if config.RoleARN != "" {
n, err := vault.GetUsernameFromSession(ctx, awsCfg)
if err != nil {
return nil, fmt.Errorf("Error getting IAM username from session: %w", err)
}
log.Printf("Found IAM username '%s'", n)
return &n, nil
}
return nil, nil //nolint
}
func getProfilesInChain(profileName string, configLoader *vault.ConfigLoader) (profileNames []string, err error) {
profileNames = append(profileNames, profileName)
config, err := configLoader.GetProfileConfig(profileName)
if err != nil {
return profileNames, err
}
if config.SourceProfile != nil {
newProfileNames, err := getProfilesInChain(config.SourceProfileName, configLoader)
if err != nil {
return profileNames, err
}
profileNames = append(profileNames, newProfileNames...)
}
return profileNames, nil
}
================================================
FILE: contrib/_aws-vault-proxy/Dockerfile
================================================
FROM golang:1.17
WORKDIR /usr/src/aws-vault-proxy
COPY . /usr/src/aws-vault-proxy
RUN go build -v -o /usr/local/bin/aws-vault-proxy ./...
CMD ["/usr/local/bin/aws-vault-proxy"]
================================================
FILE: contrib/_aws-vault-proxy/docker-compose.yml
================================================
version: "2.4"
networks:
aws-vault:
driver: bridge
ipam:
config:
- subnet: "169.254.170.0/24"
gateway: "169.254.170.1"
services:
aws-vault-proxy:
build: .
environment:
- AWS_CONTAINER_CREDENTIALS_FULL_URI
- AWS_CONTAINER_AUTHORIZATION_TOKEN
networks:
aws-vault:
ipv4_address: "169.254.170.2" # This special IP address is recognized by the AWS SDKs and AWS CLI
healthcheck:
test: pgrep aws-vault-proxy
testapp:
image: amazon/aws-cli
entrypoint: ""
command: /bin/bash
environment:
- AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
networks:
aws-vault: {}
default: {}
================================================
FILE: contrib/_aws-vault-proxy/go.mod
================================================
module aws-vault-ecs-server-reverse-proxy
go 1.17
require github.com/gorilla/handlers v1.5.1
require github.com/felixge/httpsnoop v1.0.1 // indirect
================================================
FILE: contrib/_aws-vault-proxy/go.sum
================================================
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
================================================
FILE: contrib/_aws-vault-proxy/main.go
================================================
package main
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"github.com/gorilla/handlers"
)
func GetReverseProxyTarget() *url.URL {
url, err := url.Parse(os.Getenv("AWS_CONTAINER_CREDENTIALS_FULL_URI"))
if err != nil {
log.Fatalln("Bad AWS_CONTAINER_CREDENTIALS_FULL_URI:", err.Error())
}
url.Host = "host.docker.internal:" + url.Port()
return url
}
func addAuthorizationHeader(authToken string, next http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
r.Header.Add("Authorization", authToken)
next.ServeHTTP(w, r)
}
}
func main() {
target := GetReverseProxyTarget()
authToken := os.Getenv("AWS_CONTAINER_AUTHORIZATION_TOKEN")
log.Printf("reverse proxying target:%s auth:%s\n", target, authToken)
handler := handlers.LoggingHandler(os.Stderr,
addAuthorizationHeader(authToken,
httputil.NewSingleHostReverseProxy(target)))
_ = http.ListenAndServe(":80", handler)
}
================================================
FILE: contrib/completions/bash/aws-vault.bash
================================================
_aws-vault_bash_autocomplete() {
local i cur prev opts base
for (( i=1; i < COMP_CWORD; i++ )); do
if [[ ${COMP_WORDS[i]} == -- ]]; then
_command_offset $i+1
return
fi
done
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
opts=$( ${COMP_WORDS[0]} --completion-bash "${COMP_WORDS[@]:1:$COMP_CWORD}" )
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
}
complete -F _aws-vault_bash_autocomplete -o default aws-vault
================================================
FILE: contrib/completions/fish/aws-vault.fish
================================================
if status --is-interactive
complete -ec aws-vault
# switch based on seeing a `--`
complete -c aws-vault -n 'not __fish_aws_vault_is_commandline' -xa '(__fish_aws_vault_complete_arg)'
complete -c aws-vault -n '__fish_aws_vault_is_commandline' -xa '(__fish_aws_vault_complete_commandline)'
function __fish_aws_vault_is_commandline
string match -q -r '^--$' -- (commandline -opc)
end
function __fish_aws_vault_complete_arg
set -l parts (commandline -opc)
set -e parts[1]
aws-vault --completion-bash $parts
end
function __fish_aws_vault_complete_commandline
set -l parts (string split --max 1 '--' -- (commandline -pc))
complete "-C$parts[2]"
end
end
================================================
FILE: contrib/completions/zsh/aws-vault.zsh
================================================
#compdef aws-vault
_aws-vault() {
local i
for (( i=2; i < CURRENT; i++ )); do
if [[ ${words[i]} == -- ]]; then
shift $i words
(( CURRENT -= i ))
_normal
return
fi
done
local matches=($(${words[1]} --completion-bash ${(@)words[2,$CURRENT]}))
compadd -a matches
if [[ $compstate[nmatches] -eq 0 && $words[$CURRENT] != -* ]]; then
_files
fi
}
if [[ "$(basename -- ${(%):-%x})" != "_aws-vault" ]]; then
compdef _aws-vault aws-vault
fi
================================================
FILE: contrib/docker/Dockerfile
================================================
FROM debian:bullseye-slim
RUN apt update && apt install -y curl
RUN curl -fLs -o /usr/local/bin/aws-vault https://github.com/99designs/aws-vault/releases/download/v6.3.1/aws-vault-linux-amd64 && chmod 755 /usr/local/bin/aws-vault
ENV AWS_VAULT_BACKEND=file
ENTRYPOINT ["/usr/local/bin/aws-vault"]
# Example usage:
# docker build -t aws-vault .
# docker run -it -e COLUMNS=$(tput cols) -v ~/.aws/config:/root/.aws/config -v ~/.awsvault:/root/.awsvault aws-vault
================================================
FILE: contrib/scripts/aws-configure-with-env-vars.sh
================================================
#!/bin/sh
# Configure aws-cli using the AWS env vars created with aws-vault
#
# Usage: aws-vault exec <SOURCE_PROFILE> -- aws-configure-with-env-vars.sh [TARGET_PROFILE]
#
set -eu
aws configure --profile "${1:-$AWS_VAULT}" set region "$AWS_REGION"
aws configure --profile "${1:-$AWS_VAULT}" set aws_access_key_id "$AWS_ACCESS_KEY_ID"
aws configure --profile "${1:-$AWS_VAULT}" set aws_secret_access_key "$AWS_SECRET_ACCESS_KEY"
aws configure --profile "${1:-$AWS_VAULT}" set aws_session_token "${AWS_SESSION_TOKEN:-}"
================================================
FILE: contrib/scripts/aws-iam-create-yubikey-mfa.sh
================================================
#!/bin/sh
# Adds a Yubikey TOTP device to IAM using your IAM User as the $MFA_DEVICE_NAME
# Currently, aws iam enable-mfa-device doesn't support specifying your MFA Device Name.
set -eu
if [ -n "${AWS_SESSION_TOKEN:-}" ]; then
echo "aws-vault must be run without a STS session, please run it with the --no-session flag" >&2
exit 1
fi
ACCOUNT_ARN=$(aws sts get-caller-identity --query Arn --output text)
# Assume that the final portion of the ARN is the username
# Works for ARNs like `users/<user>` and `users/engineers/<user>`
USERNAME=$(echo "$ACCOUNT_ARN" | rev | cut -d/ -f1 | rev)
OUTFILE=$(mktemp)
trap 'rm -f "$OUTFILE"' EXIT
SERIAL_NUMBER=$(aws iam create-virtual-mfa-device \
--virtual-mfa-device-name "$USERNAME" \
--bootstrap-method Base32StringSeed \
--outfile "$OUTFILE" \
--query VirtualMFADevice.SerialNumber \
--output text)
ykman oath accounts add -ft "$SERIAL_NUMBER" < "$OUTFILE" 2> /dev/null
CODE1=$(ykman oath accounts code -s "$SERIAL_NUMBER")
WAIT_TIME=$((30-$(date +%s)%30))
echo "Waiting $WAIT_TIME seconds before generating a second code" >&2
sleep $WAIT_TIME
CODE2=$(ykman oath accounts code -s "$SERIAL_NUMBER")
aws iam enable-mfa-device \
--user-name "$USERNAME" \
--serial-number "$SERIAL_NUMBER" \
--authentication-code1 "$CODE1" \
--authentication-code2 "$CODE2"
================================================
FILE: contrib/scripts/aws-iam-resync-yubikey-mfa.sh
================================================
#!/bin/sh
# Resync a Yubikey TOTP device to IAM using your IAM User as the $MFA_DEVICE_NAME
# Currently, aws iam resync-mfa-device doesn't support specifying your MFA Device Name.
set -eu
ACCOUNT_ARN=$(aws sts get-caller-identity --query Arn --output text)
# Assume that the final portion of the ARN is the username
# Works for ARNs like `users/<user>` and `users/engineers/<user>`
USERNAME=$(echo "$ACCOUNT_ARN" | rev | cut -d/ -f1 | rev)
ACCOUNT_ID=$(echo "$ACCOUNT_ARN" | cut -d: -f5)
SERIAL_NUMBER="arn:aws:iam::${ACCOUNT_ID}:mfa/${USERNAME}"
CODE1=$(ykman oath accounts code -s "$SERIAL_NUMBER")
WAIT_TIME=$((30-$(date +%s)%30))
echo "Waiting $WAIT_TIME seconds before generating a second code" >&2
sleep $WAIT_TIME
CODE2=$(ykman oath accounts code -s "$SERIAL_NUMBER")
aws iam resync-mfa-device \
--user-name "$USERNAME" \
--serial-number "$SERIAL_NUMBER" \
--authentication-code1 "$CODE1" \
--authentication-code2 "$CODE2"
================================================
FILE: go.mod
================================================
module github.com/99designs/aws-vault/v7
go 1.20
require (
github.com/99designs/keyring v1.2.2
github.com/alecthomas/kingpin/v2 v2.3.2
github.com/aws/aws-sdk-go-v2 v1.17.7
github.com/aws/aws-sdk-go-v2/config v1.18.19
github.com/aws/aws-sdk-go-v2/credentials v1.13.18
github.com/aws/aws-sdk-go-v2/service/iam v1.19.8
github.com/aws/aws-sdk-go-v2/service/sso v1.12.6
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6
github.com/aws/aws-sdk-go-v2/service/sts v1.18.7
github.com/google/go-cmp v0.5.9
github.com/mattn/go-isatty v0.0.18
github.com/mattn/go-tty v0.0.4
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
golang.org/x/term v0.6.0
gopkg.in/ini.v1 v1.67.0
)
require (
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/danieljoos/wincred v1.1.2 // indirect
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
golang.org/x/sys v0.6.0 // indirect
)
================================================
FILE: go.sum
================================================
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
github.com/alecthomas/kingpin/v2 v2.3.2 h1:H0aULhgmSzN8xQ3nX1uxtdlTHYoPLu5AhHxWrKI6ocU=
github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/aws/aws-sdk-go-v2 v1.17.7 h1:CLSjnhJSTSogvqUGhIC6LqFKATMRexcxLZ0i/Nzk9Eg=
github.com/aws/aws-sdk-go-v2 v1.17.7/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2/config v1.18.19 h1:AqFK6zFNtq4i1EYu+eC7lcKHYnZagMn6SW171la0bGw=
github.com/aws/aws-sdk-go-v2/config v1.18.19/go.mod h1:XvTmGMY8d52ougvakOv1RpiTLPz9dlG/OQHsKU/cMmY=
github.com/aws/aws-sdk-go-v2/credentials v1.13.18 h1:EQMdtHwz0ILTW1hoP+EwuWhwCG1hD6l3+RWFQABET4c=
github.com/aws/aws-sdk-go-v2/credentials v1.13.18/go.mod h1:vnwlwjIe+3XJPBYKu1et30ZPABG3VaXJYr8ryohpIyM=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 h1:gt57MN3liKiyGopcqgNzJb2+d9MJaKT/q1OksHNXVE4=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1/go.mod h1:lfUx8puBRdM5lVVMQlwt2v+ofiG/X6Ms+dy0UkG/kXw=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 h1:sJLYcS+eZn5EeNINGHSCRAwUJMFVqklwkH36Vbyai7M=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31/go.mod h1:QT0BqUvX1Bh2ABdTGnjqEjvjzrCfIniM9Sc8zn9Yndo=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 h1:1mnRASEKnkqsntcxHaysxwgVoUUp5dkiB+l3llKnqyg=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25/go.mod h1:zBHOPwhBc3FlQjQJE/D3IfPWiWaQmT06Vq9aNukDo0k=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 h1:p5luUImdIqywn6JpQsW3tq5GNOxKmOnEpybzPx+d1lk=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.32/go.mod h1:XGhIBZDEgfqmFIugclZ6FU7v75nHhBDtzuB4xB/tEi4=
github.com/aws/aws-sdk-go-v2/service/iam v1.19.8 h1:kQsBeGgm68kT0xc90spgC5qEOQGH74V2bFqgBgG21Bo=
github.com/aws/aws-sdk-go-v2/service/iam v1.19.8/go.mod h1:lf/oAjt//UvPsmnOgPT61F+q4K6U0q4zDd1s1yx2NZs=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25 h1:5LHn8JQ0qvjD9L9JhMtylnkcw7j05GDZqM9Oin6hpr0=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25/go.mod h1:/95IA+0lMnzW6XzqYJRpjjsAbKEORVeO0anQqjd2CNU=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.6 h1:5V7DWLBd7wTELVz5bPpwzYy/sikk0gsgZfj40X+l5OI=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.6/go.mod h1:Y1VOmit/Fn6Tz1uFAeCO6Q7M2fmfXSCLeL5INVYsLuY=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6 h1:B8cauxOH1W1v7rd8RdI/MWnoR4Ze0wIHWrb90qczxj4=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6/go.mod h1:Lh/bc9XUf8CfOY6Jp5aIkQtN+j1mc+nExc+KXj9jx2s=
github.com/aws/aws-sdk-go-v2/service/sts v1.18.7 h1:bWNgNdRko2x6gqa0blfATqAZKZokPIeM1vfmQt2pnvM=
github.com/aws/aws-sdk-go-v2/service/sts v1.18.7/go.mod h1:JuTnSoeePXmMVe9G8NcjjwgOKEfZ4cOjMuT2IBT/2eI=
github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM=
github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-tty v0.0.4 h1:NVikla9X8MN0SQAqCYzpGyXv0jY7MNl3HOWD2dkle7E=
github.com/mattn/go-tty v0.0.4/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3pxse28=
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
================================================
FILE: iso8601/iso8601.go
================================================
package iso8601
import "time"
// Format outputs an ISO-8601 datetime string from the given time,
// in a format compatible with all of the AWS SDKs
func Format(t time.Time) string {
return t.UTC().Format(time.RFC3339)
}
================================================
FILE: iso8601/iso8601_test.go
================================================
package iso8601
import (
"testing"
"time"
)
func TestFormat(t *testing.T) {
input, _ := time.Parse(time.RFC3339, "2009-02-04T21:00:57-08:00")
want := "2009-02-05T05:00:57Z"
result := Format(input)
if result != want {
t.Errorf("expected %s for %q got %s", want, input, result)
}
}
func TestFormatForIssue655(t *testing.T) {
input, _ := time.Parse(time.RFC3339, "2020-09-10T18:16:52+02:00")
want := "2020-09-10T16:16:52Z"
result := Format(input)
if result != want {
t.Errorf("expected %s for %q got %s", want, input, result)
}
}
================================================
FILE: main.go
================================================
package main
import (
"os"
"github.com/99designs/aws-vault/v7/cli"
"github.com/alecthomas/kingpin/v2"
)
// Version is provided at compile time
var Version = "dev"
func main() {
app := kingpin.New("aws-vault", "A vault for securely storing and accessing AWS credentials in development environments.")
app.Version(Version)
a := cli.ConfigureGlobals(app)
cli.ConfigureAddCommand(app, a)
cli.ConfigureRemoveCommand(app, a)
cli.ConfigureListCommand(app, a)
cli.ConfigureRotateCommand(app, a)
cli.ConfigureExecCommand(app, a)
cli.ConfigureExportCommand(app, a)
cli.ConfigureClearCommand(app, a)
cli.ConfigureLoginCommand(app, a)
cli.ConfigureProxyCommand(app)
kingpin.MustParse(app.Parse(os.Args[1:]))
}
================================================
FILE: prompt/kdialog.go
================================================
package prompt
import (
"os/exec"
"strings"
)
func KDialogMfaPrompt(mfaSerial string) (string, error) {
cmd := exec.Command("kdialog", "--inputbox", mfaPromptMessage(mfaSerial), "--title", "aws-vault")
out, err := cmd.Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(out)), nil
}
func init() {
if _, err := exec.LookPath("kdialog"); err == nil {
Methods["kdialog"] = KDialogMfaPrompt
}
}
================================================
FILE: prompt/osascript.go
================================================
package prompt
import (
"fmt"
"os/exec"
"strings"
)
func OSAScriptMfaPrompt(mfaSerial string) (string, error) {
cmd := exec.Command("osascript", "-e", fmt.Sprintf(`
display dialog %q default answer "" buttons {"OK", "Cancel"} default button 1
text returned of the result
return result`,
mfaPromptMessage(mfaSerial)))
out, err := cmd.Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(out)), nil
}
func init() {
if _, err := exec.LookPath("osascript"); err == nil {
Methods["osascript"] = OSAScriptMfaPrompt
}
}
================================================
FILE: prompt/prompt.go
================================================
package prompt
import (
"fmt"
"sort"
)
type Func func(string) (string, error)
var Methods = map[string]Func{}
func Available() []string {
methods := []string{}
for k := range Methods {
methods = append(methods, k)
}
sort.Strings(methods)
return methods
}
func Method(s string) Func {
m, ok := Methods[s]
if !ok {
panic(fmt.Sprintf("Prompt method %q doesn't exist", s))
}
return m
}
func mfaPromptMessage(mfaSerial string) string {
return fmt.Sprintf("Enter MFA code for %s: ", mfaSerial)
}
================================================
FILE: prompt/terminal.go
================================================
package prompt
import (
"fmt"
"strings"
"github.com/mattn/go-tty"
)
func TerminalPrompt(message string) (string, error) {
tty, err := tty.Open()
if err != nil {
return "", err
}
defer tty.Close()
fmt.Fprint(tty.Output(), message)
text, err := tty.ReadString()
if err != nil {
return "", err
}
return strings.TrimSpace(text), nil
}
func TerminalSecretPrompt(message string) (string, error) {
tty, err := tty.Open()
if err != nil {
return "", err
}
defer tty.Close()
fmt.Fprint(tty.Output(), message)
text, err := tty.ReadPassword()
if err != nil {
return "", err
}
return strings.TrimSpace(text), nil
}
func TerminalMfaPrompt(mfaSerial string) (string, error) {
return TerminalPrompt(mfaPromptMessage(mfaSerial))
}
func init() {
Methods["terminal"] = TerminalMfaPrompt
}
================================================
FILE: prompt/wincredui_windows.go
================================================
package prompt
import (
"errors"
"strings"
"syscall"
"unsafe"
)
const (
CREDUI_FLAGS_ALWAYS_SHOW_UI = 0x00080
CREDUI_FLAGS_GENERIC_CREDENTIALS = 0x40000
CREDUI_FLAGS_KEEP_USERNAME = 0x100000
)
type creduiInfoA struct {
cbSize uint32
hwndParent uintptr
pszMessageText *uint16
pszCaptionText *uint16
hbmBanner uintptr
}
func WinCredUiPrompt(mfaSerial string) (string, error) {
info := &creduiInfoA{
hwndParent: 0,
pszCaptionText: syscall.StringToUTF16Ptr("Enter MFA code for aws-vault"),
pszMessageText: syscall.StringToUTF16Ptr(mfaPromptMessage(mfaSerial)),
hbmBanner: 0,
}
info.cbSize = uint32(unsafe.Sizeof(*info))
passwordBuf := make([]uint16, 64)
save := false
flags := CREDUI_FLAGS_ALWAYS_SHOW_UI | CREDUI_FLAGS_KEEP_USERNAME | CREDUI_FLAGS_GENERIC_CREDENTIALS
shortSerial := strings.ReplaceAll(strings.ReplaceAll(mfaSerial, "arn:aws:iam::", ""), ":mfa", "")
ret, _, _ := syscall.NewLazyDLL("credui.dll").NewProc("CredUIPromptForCredentialsW").Call(
uintptr(unsafe.Pointer(info)),
uintptr(unsafe.Pointer(syscall.StringBytePtr("aws-vault"))),
0,
0,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(shortSerial))),
uintptr(len(shortSerial)+1),
uintptr(unsafe.Pointer(&passwordBuf[0])),
64,
uintptr(unsafe.Pointer(&save)),
uintptr(flags),
)
if ret != 0 {
return "", errors.New("wincredui: call to CredUIPromptForCredentialsW failed")
}
return strings.TrimSpace(syscall.UTF16ToString(passwordBuf)), nil
}
func init() {
Methods["wincredui"] = WinCredUiPrompt
}
================================================
FILE: prompt/ykman.go
================================================
package prompt
import (
"fmt"
"log"
"os"
"os/exec"
"strings"
)
// YkmanProvider runs ykman to generate a OATH-TOTP token from the Yubikey device
// To set up ykman, first run `ykman oath accounts add`
func YkmanMfaProvider(mfaSerial string) (string, error) {
args := []string{}
yubikeyOathCredName := os.Getenv("YKMAN_OATH_CREDENTIAL_NAME")
if yubikeyOathCredName == "" {
yubikeyOathCredName = mfaSerial
}
// Get the serial number of the yubikey device to use.
yubikeyDeviceSerial := os.Getenv("YKMAN_OATH_DEVICE_SERIAL")
if yubikeyDeviceSerial != "" {
// If the env var was set, extend args to support passing the serial.
args = append(args, "--device", yubikeyDeviceSerial)
}
// default to v4 and above
switch os.Getenv("AWS_VAULT_YKMAN_VERSION") {
case "1", "2", "3":
args = append(args, "oath", "code", "--single", yubikeyOathCredName)
default:
args = append(args, "oath", "accounts", "code", "--single", yubikeyOathCredName)
}
log.Printf("Fetching MFA code using `ykman %s`", strings.Join(args, " "))
cmd := exec.Command("ykman", args...)
cmd.Stderr = os.Stderr
out, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("ykman: %w", err)
}
return strings.TrimSpace(string(out)), nil
}
func init() {
if _, err := exec.LookPath("ykman"); err == nil {
Methods["ykman"] = YkmanMfaProvider
}
}
================================================
FILE: prompt/zenity.go
================================================
package prompt
import (
"os/exec"
"strings"
)
func ZenityMfaPrompt(mfaSerial string) (string, error) {
cmd := exec.Command("zenity", "--entry", "--title", "aws-vault", "--text", mfaPromptMessage(mfaSerial))
out, err := cmd.Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(out)), nil
}
func init() {
if _, err := exec.LookPath("zenity"); err == nil {
Methods["zenity"] = ZenityMfaPrompt
}
}
================================================
FILE: server/ec2alias_bsd.go
================================================
//go:build darwin || freebsd || openbsd
// +build darwin freebsd openbsd
package server
import "os/exec"
func installEc2EndpointNetworkAlias() ([]byte, error) {
return exec.Command("ifconfig", "lo0", "alias", "169.254.169.254").CombinedOutput()
}
func removeEc2EndpointNetworkAlias() ([]byte, error) {
return exec.Command("ifconfig", "lo0", "-alias", "169.254.169.254").CombinedOutput()
}
================================================
FILE: server/ec2alias_linux.go
================================================
//go:build linux
// +build linux
package server
import "os/exec"
func installEc2EndpointNetworkAlias() ([]byte, error) {
return exec.Command("ip", "addr", "add", "169.254.169.254/24", "dev", "lo", "label", "lo:0").CombinedOutput()
}
func removeEc2EndpointNetworkAlias() ([]byte, error) {
return exec.Command("ip", "addr", "del", "169.254.169.254/24", "dev", "lo", "label", "lo:0").CombinedOutput()
}
================================================
FILE: server/ec2alias_windows.go
================================================
//go:build windows
// +build windows
package server
import (
"fmt"
"os/exec"
"strings"
)
var alreadyRegisteredLocalised = []string{
"The object already exists",
"Das Objekt ist bereits vorhanden",
"El objeto ya existe",
}
var runAsAdministratorLocalised = []string{
"Run as administrator",
// truncate before 'Umlaut' to avoid encoding problems coming from Windows cmd
"Als Administrator ausf",
"Ejecutar como administrador",
}
func msgFound(localised []string, toTest string) bool {
for _, value := range localised {
if strings.Contains(toTest, value) {
return true
}
}
return false
}
func runAndWrapAdminErrors(name string, arg ...string) ([]byte, error) {
out, err := exec.Command(name, arg...).CombinedOutput()
if msgFound(runAsAdministratorLocalised, string(out)) {
err = fmt.Errorf("Creation of network alias for server mode requires elevated permissions, run as administrator", err)
}
return out, err
}
func installEc2EndpointNetworkAlias() ([]byte, error) {
out, err := runAndWrapAdminErrors("netsh", "interface", "ipv4", "add", "address", "Loopback Pseudo-Interface 1", "169.254.169.254", "255.255.0.0")
if msgFound(alreadyRegisteredLocalised, string(out)) {
return []byte{}, nil
}
return out, err
}
func removeEc2EndpointNetworkAlias() ([]byte, error) {
return runAndWrapAdminErrors("netsh", "interface", "ipv4", "delete", "address", "Loopback Pseudo-Interface 1", "169.254.169.254", "255.255.0.0")
}
================================================
FILE: server/ec2proxy.go
================================================
package server
import (
"fmt"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"os"
"strings"
"time"
)
const (
ec2MetadataEndpointIP = "169.254.169.254"
ec2MetadataEndpointAddr = "169.254.169.254:80"
)
// StartProxy starts a http proxy server that listens on the standard EC2 Instance Metadata endpoint http://169.254.169.254:80/
// and forwards requests through to the running `aws-vault exec` command
func StartProxy() error {
var localServerURL, err = url.Parse(fmt.Sprintf("http://%s/", ec2CredentialsServerAddr))
if err != nil {
return err
}
if output, err := installEc2EndpointNetworkAlias(); err != nil {
return fmt.Errorf("%s: %s", strings.TrimSpace(string(output)), err.Error())
}
l, err := net.Listen("tcp", ec2MetadataEndpointAddr)
if err != nil {
return err
}
handler := http.NewServeMux()
handler.HandleFunc("/stop", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
go Shutdown()
})
handler.Handle("/", httputil.NewSingleHostReverseProxy(localServerURL))
log.Printf("EC2 Instance Metadata endpoint proxy server running on %s", l.Addr())
return http.Serve(l, handler)
}
func IsProxyRunning() bool {
_, err := net.DialTimeout("tcp", ec2MetadataEndpointAddr, time.Millisecond*10)
return err == nil
}
func Shutdown() {
_, err := removeEc2EndpointNetworkAlias()
if err != nil {
log.Fatalln(err)
}
os.Exit(0)
}
// StopProxy stops the http proxy server on the standard EC2 Instance Metadata endpoint
func StopProxy() {
_, _ = http.Get(fmt.Sprintf("http://%s/stop", ec2MetadataEndpointAddr)) //nolint
}
func awsVaultExecutable() string {
awsVaultPath, err := os.Executable()
if err != nil {
return awsVaultPath
}
return os.Args[0]
}
================================================
FILE: server/ec2proxy_default.go
================================================
//go:build !darwin && !freebsd && !openbsd && !linux
// +build !darwin,!freebsd,!openbsd,!linux
package server
import (
"errors"
"log"
"os"
"os/exec"
"time"
)
// StartEc2EndpointProxyServerProcess starts a `aws-vault proxy` process
func StartEc2EndpointProxyServerProcess() error {
log.Println("Starting `aws-vault proxy`")
cmd := exec.Command(awsVaultExecutable(), "proxy")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
return err
}
time.Sleep(time.Second * 1)
if !IsProxyRunning() {
return errors.New("The EC2 Instance Metadata endpoint proxy server isn't running. Run `aws-vault proxy` as Administrator or root in the background and then try this command again")
}
return nil
}
================================================
FILE: server/ec2proxy_unix.go
================================================
//go:build darwin || freebsd || openbsd || linux
// +build darwin freebsd openbsd linux
package server
import (
"log"
"os"
"os/exec"
)
// StartEc2EndpointProxyServerProcess starts a `aws-vault proxy` process
func StartEc2EndpointProxyServerProcess() error {
log.Println("Starting `aws-vault proxy` as root in the background")
cmd := exec.Command("sudo", "-b", awsVaultExecutable(), "proxy")
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
================================================
FILE: server/ec2server.go
================================================
package server
import (
"context"
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"time"
"github.com/99designs/aws-vault/v7/iso8601"
"github.com/aws/aws-sdk-go-v2/aws"
)
const ec2CredentialsServerAddr = "127.0.0.1:9099"
// StartEc2CredentialsServer starts a EC2 Instance Metadata server and endpoint proxy
func StartEc2CredentialsServer(ctx context.Context, credsProvider aws.CredentialsProvider, region string) error {
credsCache := aws.NewCredentialsCache(credsProvider)
// pre-fetch credentials so that we can respond quickly to the first request
// SDKs seem to very aggressively timeout
_, _ = credsCache.Retrieve(ctx)
go startEc2CredentialsServer(credsCache, region)
return nil
}
func startEc2CredentialsServer(credsProvider aws.CredentialsProvider, region string) {
log.Printf("Starting EC2 Instance Metadata server on %s", ec2CredentialsServerAddr)
router := http.NewServeMux()
router.HandleFunc("/latest/meta-data/iam/security-credentials/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "local-credentials")
})
// The AWS Go SDK checks the instance-id endpoint to validate the existence of EC2 Metadata
router.HandleFunc("/latest/meta-data/instance-id/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "aws-vault")
})
// The AWS .NET SDK checks this endpoint during obtaining credentials/refreshing them
router.HandleFunc("/latest/meta-data/iam/info/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `{"Code" : "Success"}`)
})
// used by AWS SDK to determine region
router.HandleFunc("/latest/dynamic/instance-identity/document", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `{"region": "`+region+`"}`)
})
router.HandleFunc("/latest/meta-data/iam/security-credentials/local-credentials", credsHandler(credsProvider))
log.Fatalln(http.ListenAndServe(ec2CredentialsServerAddr, withLogging(withSecurityChecks(router))))
}
// withSecurityChecks is middleware to protect the server from attack vectors
func withSecurityChecks(next *http.ServeMux) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Check the remote ip is from the loopback, otherwise clients on the same network segment could
// potentially route traffic via 169.254.169.254:80
// See https://developer.apple.com/library/content/qa/qa1357/_index.html
ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if !net.ParseIP(ip).IsLoopback() {
http.Error(w, "Access denied from non-localhost address", http.StatusUnauthorized)
return
}
// Check that the request is to 169.254.169.254
// Without this it's possible for an attacker to mount a DNS rebinding attack
// See https://github.com/99designs/aws-vault/issues/578
if r.Host != ec2MetadataEndpointIP && r.Host != ec2MetadataEndpointAddr {
http.Error(w, fmt.Sprintf("Access denied for host '%s'", r.Host), http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
}
}
func credsHandler(credsProvider aws.CredentialsProvider) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
creds, err := credsProvider.Retrieve(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusGatewayTimeout)
return
}
log.Printf("Serving credentials via http ****************%s, expiration of %s (%s)",
creds.AccessKeyID[len(creds.AccessKeyID)-4:],
creds.Expires.Format(time.RFC3339),
time.Until(creds.Expires).String())
err = json.NewEncoder(w).Encode(map[string]interface{}{
"Code": "Success",
"LastUpdated": iso8601.Format(time.Now()),
"Type": "AWS-HMAC",
"AccessKeyId": creds.AccessKeyID,
"SecretAccessKey": creds.SecretAccessKey,
"Token": creds.SessionToken,
"Expiration": iso8601.Format(creds.Expires),
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}
================================================
FILE: server/ecsserver.go
================================================
package server
import (
"context"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"net"
"net/http"
"strings"
"sync"
"github.com/99designs/aws-vault/v7/iso8601"
"github.com/99designs/aws-vault/v7/vault"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/sts"
)
func writeErrorMessage(w http.ResponseWriter, msg string, statusCode int) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(statusCode)
if err := json.NewEncoder(w).Encode(map[string]string{"Message": msg}); err != nil {
log.Println(err.Error())
}
}
func withAuthorizationCheck(authToken string, next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != authToken {
writeErrorMessage(w, "invalid Authorization token", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
}
}
func writeCredsToResponse(creds aws.Credentials, w http.ResponseWriter) {
err := json.NewEncoder(w).Encode(map[string]string{
"AccessKeyId": creds.AccessKeyID,
"SecretAccessKey": creds.SecretAccessKey,
"Token": creds.SessionToken,
"Expiration": iso8601.Format(creds.Expires),
})
if err != nil {
writeErrorMessage(w, err.Error(), http.StatusInternalServerError)
return
}
}
func generateRandomString() string {
b := make([]byte, 30)
if _, err := rand.Read(b); err != nil {
panic(err)
}
return base64.RawURLEncoding.EncodeToString(b)
}
type EcsServer struct {
listener net.Listener
authToken string
server http.Server
cache sync.Map
baseCredsProvider aws.CredentialsProvider
config *vault.ProfileConfig
}
func NewEcsServer(ctx context.Context, baseCredsProvider aws.CredentialsProvider, config *vault.ProfileConfig, authToken string, port int, lazyLoadBaseCreds bool) (*EcsServer, error) {
listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
if err != nil {
return nil, err
}
if authToken == "" {
authToken = generateRandomString()
}
credsCache := aws.NewCredentialsCache(baseCredsProvider)
if !lazyLoadBaseCreds {
_, err := credsCache.Retrieve(ctx)
if err != nil {
return nil, fmt.Errorf("Retrieving creds: %w", err)
}
}
e := &EcsServer{
listener: listener,
authToken: authToken,
baseCredsProvider: credsCache,
config: config,
}
router := http.NewServeMux()
router.HandleFunc("/", e.DefaultRoute)
router.HandleFunc("/role-arn/", e.AssumeRoleArnRoute)
e.server.Handler = withLogging(withAuthorizationCheck(e.authToken, router.ServeHTTP))
return e, nil
}
func (e *EcsServer) BaseURL() string {
return fmt.Sprintf("http://%s", e.listener.Addr().String())
}
func (e *EcsServer) AuthToken() string {
return e.authToken
}
func (e *EcsServer) Serve() error {
return e.server.Serve(e.listener)
}
func (e *EcsServer) DefaultRoute(w http.ResponseWriter, r *http.Request) {
creds, err := e.baseCredsProvider.Retrieve(r.Context())
if err != nil {
writeErrorMessage(w, err.Error(), http.StatusInternalServerError)
return
}
writeCredsToResponse(creds, w)
}
func (e *EcsServer) getRoleProvider(roleArn string) aws.CredentialsProvider {
var roleProviderCache *aws.CredentialsCache
v, ok := e.cache.Load(roleArn)
if ok {
roleProviderCache = v.(*aws.CredentialsCache)
} else {
cfg := vault.NewAwsConfigWithCredsProvider(e.baseCredsProvider, e.config.Region, e.config.STSRegionalEndpoints)
roleProvider := &vault.AssumeRoleProvider{
StsClient: sts.NewFromConfig(cfg),
RoleARN: roleArn,
Duration: e.config.AssumeRoleDuration,
}
roleProviderCache = aws.NewCredentialsCache(roleProvider)
e.cache.Store(roleArn, roleProviderCache)
}
return roleProviderCache
}
func (e *EcsServer) AssumeRoleArnRoute(w http.ResponseWriter, r *http.Request) {
roleArn := strings.TrimPrefix(r.URL.Path, "/role-arn/")
roleProvider := e.getRoleProvider(roleArn)
creds, err := roleProvider.Retrieve(r.Context())
if err != nil {
writeErrorMessage(w, err.Error(), http.StatusInternalServerError)
return
}
writeCredsToResponse(creds, w)
}
================================================
FILE: server/httplog.go
================================================
package server
import (
"log"
"net/http"
"time"
)
type loggingMiddlewareResponseWriter struct {
http.ResponseWriter
Code int
}
func (w *loggingMiddlewareResponseWriter) WriteHeader(statusCode int) {
w.Code = statusCode
w.ResponseWriter.WriteHeader(statusCode)
}
func withLogging(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestStart := time.Now()
w2 := &loggingMiddlewareResponseWriter{w, http.StatusOK}
handler.ServeHTTP(w2, r)
log.Printf("http: %s: %d %s %s (%s)", r.RemoteAddr, w2.Code, r.Method, r.URL, time.Since(requestStart))
})
}
================================================
FILE: vault/assumeroleprovider.go
================================================
package vault
import (
"context"
"fmt"
"log"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/sts"
ststypes "github.com/aws/aws-sdk-go-v2/service/sts/types"
)
// AssumeRoleProvider retrieves temporary credentials from STS using AssumeRole
type AssumeRoleProvider struct {
StsClient *sts.Client
RoleARN string
RoleSessionName string
ExternalID string
Duration time.Duration
Tags map[string]string
TransitiveTagKeys []string
SourceIdentity string
Mfa
}
// Retrieve generates a new set of temporary credentials using STS AssumeRole
func (p *AssumeRoleProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {
role, err := p.RetrieveStsCredentials(ctx)
if err != nil {
return aws.Credentials{}, err
}
return aws.Credentials{
AccessKeyID: *role.AccessKeyId,
SecretAccessKey: *role.SecretAccessKey,
SessionToken: *role.SessionToken,
CanExpire: true,
Expires: *role.Expiration,
}, nil
}
func (p *AssumeRoleProvider) roleSessionName() string {
if p.RoleSessionName == "" {
// Try to work out a role name that will hopefully end up unique.
return fmt.Sprintf("%d", time.Now().UTC().UnixNano())
}
return p.RoleSessionName
}
func (p *AssumeRoleProvider) RetrieveStsCredentials(ctx context.Context) (*ststypes.Credentials, error) {
var err error
input := &sts.AssumeRoleInput{
RoleArn: aws.String(p.RoleARN),
RoleSessionName: aws.String(p.roleSessionName()),
DurationSeconds: aws.Int32(int32(p.Duration.Seconds())),
}
if p.ExternalID != "" {
input.ExternalId = aws.String(p.ExternalID)
}
if p.MfaSerial != "" {
input.SerialNumber = aws.String(p.MfaSerial)
input.TokenCode, err = p.GetMfaToken()
if err != nil {
return nil, err
}
}
if len(p.Tags) > 0 {
input.Tags = make([]ststypes.Tag, 0)
for key, value := range p.Tags {
tag := ststypes.Tag{
Key: aws.String(key),
Value: aws.String(value),
}
input.Tags = append(input.Tags, tag)
}
}
if len(p.TransitiveTagKeys) > 0 {
input.TransitiveTagKeys = p.TransitiveTagKeys
}
if p.SourceIdentity != "" {
input.SourceIdentity = aws.String(p.SourceIdentity)
}
resp, err := p.StsClient.AssumeRole(ctx, input)
if err != nil {
return nil, err
}
log.Printf("Generated credentials %s using AssumeRole, expires in %s", FormatKeyForDisplay(*resp.Credentials.AccessKeyId), time.Until(*resp.Credentials.Expiration).String())
return resp.Credentials, nil
}
================================================
FILE: vault/assumerolewithwebidentityprovider.go
================================================
package vault
import (
"context"
"fmt"
"log"
"os"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/sts"
ststypes "github.com/aws/aws-sdk-go-v2/service/sts/types"
)
// AssumeRoleWithWebIdentityProvider retrieves temporary credentials from STS using AssumeRoleWithWebIdentity
type AssumeRoleWithWebIdentityProvider struct {
StsClient *sts.Client
RoleARN string
RoleSessionName string
WebIdentityTokenFile string
WebIdentityTokenProcess string
ExternalID string
Duration time.Duration
}
// Retrieve generates a new set of temporary credentials using STS AssumeRoleWithWebIdentity
func (p *AssumeRoleWithWebIdentityProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {
creds, err := p.RetrieveStsCredentials(ctx)
if err != nil {
return aws.Credentials{}, err
}
return aws.Credentials{
AccessKeyID: aws.ToString(creds.AccessKeyId),
SecretAccessKey: aws.ToString(creds.SecretAccessKey),
SessionToken: aws.ToString(creds.SessionToken),
CanExpire: true,
Expires: aws.ToTime(creds.Expiration),
}, nil
}
func (p *AssumeRoleWithWebIdentityProvider) roleSessionName() string {
if p.RoleSessionName == "" {
// Try to work out a role name that will hopefully end up unique.
return fmt.Sprintf("%d", time.Now().UTC().UnixNano())
}
return p.RoleSessionName
}
func (p *AssumeRoleWithWebIdentityProvider) RetrieveStsCredentials(ctx context.Context) (*ststypes.Credentials, error) {
var err error
webIdentityToken, err := p.webIdentityToken()
if err != nil {
return nil, err
}
resp, err := p.StsClient.AssumeRoleWithWebIdentity(ctx, &sts.AssumeRoleWithWebIdentityInput{
RoleArn: aws.String(p.RoleARN),
RoleSessionName: aws.String(p.roleSessionName()),
DurationSeconds: aws.Int32(int32(p.Duration.Seconds())),
WebIdentityToken: aws.String(webIdentityToken),
})
if err != nil {
return nil, err
}
log.Printf("Generated credentials %s using AssumeRoleWithWebIdentity, expires in %s", FormatKeyForDisplay(*resp.Credentials.AccessKeyId), time.Until(*resp.Credentials.Expiration).String())
return resp.Credentials, nil
}
func (p *AssumeRoleWithWebIdentityProvider) webIdentityToken() (string, error) {
// Read OpenID Connect token from WebIdentityTokenFile
if p.WebIdentityTokenFile != "" {
b, err := os.ReadFile(p.WebIdentityTokenFile)
if err != nil {
return "", fmt.Errorf("unable to read file at %s: %v", p.WebIdentityTokenFile, err)
}
return string(b), nil
}
// Exec WebIdentityTokenProcess to retrieve OpenID Connect token
return executeProcess(p.WebIdentityTokenProcess)
}
================================================
FILE: vault/cachedsessionprovider.go
================================================
package vault
import (
"context"
"log"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
ststypes "github.com/aws/aws-sdk-go-v2/service/sts/types"
)
type StsSessionProvider interface {
aws.CredentialsProvider
RetrieveStsCredentials(ctx context.Context) (*ststypes.Credentials, error)
}
// CachedSessionProvider retrieves cached credentials from the keyring, or if no credentials are cached
// retrieves temporary credentials using the CredentialsFunc
type CachedSessionProvider struct {
SessionKey SessionMetadata
SessionProvider StsSessionProvider
Keyring *SessionKeyring
ExpiryWindow time.Duration
}
func (p *CachedSessionProvider) RetrieveStsCredentials(ctx context.Context) (*ststypes.Credentials, error) {
creds, err := p.Keyring.Get(p.SessionKey)
if err != nil || time.Until(*creds.Expiration) < p.ExpiryWindow {
// lookup missed, we need to create a new one.
creds, err = p.SessionProvider.RetrieveStsCredentials(ctx)
if err != nil {
return nil, err
}
err = p.Keyring.Set(p.SessionKey, creds)
if err != nil {
return nil, err
}
} else {
log.Printf("Re-using cached credentials %s from %s, expires in %s", FormatKeyForDisplay(*creds.AccessKeyId), p.SessionKey.Type, time.Until(*creds.Expiration).String())
}
return creds, nil
}
// Retrieve returns cached credentials from the keyring, or if no credentials are cached
// generates a new set of temporary credentials using the CredentialsFunc
func (p *CachedSessionProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {
creds, err := p.RetrieveStsCredentials(ctx)
if err != nil {
return aws.Credentials{}, err
}
return aws.Credentials{
AccessKeyID: aws.ToString(creds.AccessKeyId),
SecretAccessKey: aws.ToString(creds.SecretAccessKey),
SessionToken: aws.ToString(creds.SessionToken),
CanExpire: true,
Expires: aws.ToTime(creds.Expiration),
}, nil
}
================================================
FILE: vault/config.go
================================================
package vault
import (
"errors"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"time"
ini "gopkg.in/ini.v1"
)
const (
// DefaultSessionDuration is the default duration for GetSessionToken or AssumeRole sessions
DefaultSessionDuration = time.Hour * 1
// DefaultChainedSessionDuration is the default duration for GetSessionToken sessions when chaining
DefaultChainedSessionDuration = time.Hour * 8
defaultSectionName = "default"
roleChainingMaximumDuration = 1 * time.Hour
)
func init() {
ini.PrettyFormat = false
}
// ConfigFile is an abstraction over what is in ~/.aws/config
type ConfigFile struct {
Path string
iniFile *ini.File
}
// configPath returns either $AWS_CONFIG_FILE or ~/.aws/config
func configPath() (string, error) {
file := os.Getenv("AWS_CONFIG_FILE")
if file == "" {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
file = filepath.Join(home, "/.aws/config")
} else {
log.Printf("Using AWS_CONFIG_FILE value: %s", file)
}
return file, nil
}
// createConfigFilesIfMissing will create the config directory and file if they do not exist
func createConfigFilesIfMissing() error {
file, err := configPath()
if err != nil {
return err
}
dir := filepath.Dir(file)
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.Mkdir(dir, 0700)
if err != nil {
return err
}
log.Printf("Config directory %s created", dir)
}
if _, err := os.Stat(file); os.IsNotExist(err) {
newFile, err := os.Create(file)
if err != nil {
log.Printf("Config file %s not created", file)
return err
}
newFile.Close()
log.Printf("Config file %s created", file)
}
return nil
}
// LoadConfig loads and parses a config file. No error is returned if the file doesn't exist
func LoadConfig(path string) (*ConfigFile, error) {
config := &ConfigFile{
Path: path,
}
if _, err := os.Stat(path); err == nil {
if parseErr := config.parseFile(); parseErr != nil {
return nil, parseErr
}
} else {
log.Printf("Config file %s doesn't exist so lets create it", path)
err := createConfigFilesIfMissing()
if err != nil {
return nil, err
}
if parseErr := config.parseFile(); parseErr != nil {
return nil, parseErr
}
}
return config, nil
}
// LoadConfigFromEnv finds the config file from the environment
func LoadConfigFromEnv() (*ConfigFile, error) {
file, err := configPath()
if err != nil {
return nil, err
}
log.Printf("Loading config file %s", file)
return LoadConfig(file)
}
func (c *ConfigFile) parseFile() error {
log.Printf("Parsing config file %s", c.Path)
f, err := ini.LoadSources(ini.LoadOptions{
AllowNestedValues: true,
InsensitiveSections: false,
InsensitiveKeys: true,
}, c.Path)
if err != nil {
return fmt.Errorf("Error parsing config file %s: %w", c.Path, err)
}
c.iniFile = f
return nil
}
// ProfileSection is a profile section of the config file
type ProfileSection struct {
Name string `ini:"-"`
MfaSerial string `ini:"mfa_serial,omitempty"`
RoleARN string `ini:"role_arn,omitempty"`
ExternalID string `ini:"external_id,omitempty"`
Region string `ini:"region,omitempty"`
RoleSessionName string `ini:"role_session_name,omitempty"`
DurationSeconds uint `ini:"duration_seconds,omitempty"`
SourceProfile string `ini:"source_profile,omitempty"`
IncludeProfile string `ini:"include_profile,omitempty"`
SSOSession string `ini:"sso_session,omitempty"`
SSOStartURL string `ini:"sso_start_url,omitempty"`
SSORegion string `ini:"sso_region,omitempty"`
SSOAccountID string `ini:"sso_account_id,omitempty"`
SSORoleName string `ini:"sso_role_name,omitempty"`
WebIdentityTokenFile string `ini:"web_identity_token_file,omitempty"`
WebIdentityTokenProcess string `ini:"web_identity_token_process,omitempty"`
STSRegionalEndpoints string `ini:"sts_regional_endpoints,omitempty"`
SessionTags string `ini:"session_tags,omitempty"`
TransitiveSessionTags string `ini:"transitive_session_tags,omitempty"`
SourceIdentity string `ini:"source_identity,omitempty"`
CredentialProcess string `ini:"credential_process,omitempty"`
MfaProcess string `ini:"mfa_process,omitempty"`
}
// SSOSessionSection is a [sso-session] section of the config file
type SSOSessionSection struct {
Name string `ini:"-"`
SSOStartURL string `ini:"sso_start_url,omitempty"`
SSORegion string `ini:"sso_region,omitempty"`
SSORegistrationScopes string `ini:"sso_registration_scopes,omitempty"`
}
func (s ProfileSection) IsEmpty() bool {
s.Name = ""
return s == ProfileSection{}
}
// ProfileSections returns all the profile sections in the config
func (c *ConfigFile) ProfileSections() []ProfileSection {
result := []ProfileSection{}
if c.iniFile == nil {
return result
}
for _, section := range c.iniFile.SectionStrings() {
if section == defaultSectionName || strings.HasPrefix(section, "profile ") {
profile, _ := c.ProfileSection(strings.TrimPrefix(section, "profile "))
// ignore the default profile if it's empty
if section == defaultSectionName && profile.IsEmpty() {
continue
}
result = append(result, profile)
} else if strings.HasPrefix(section, "sso-session ") {
// Not a profile
continue
} else {
log.Printf("Unrecognised ini file section: %s", section)
continue
}
}
return result
}
// ProfileSection returns the profile section with the matching name. If there isn't any,
// an empty profile with the provided name is returned, along with false.
func (c *ConfigFile) ProfileSection(name string) (ProfileSection, bool) {
profile := ProfileSection{
Name: name,
}
if c.iniFile == nil {
return profile, false
}
// default profile name has a slightly different section format
sectionName := "profile " + name
if name == defaultSectionName {
sectionName = defaultSectionName
}
section, err := c.iniFile.GetSection(sectionName)
if err != nil {
return profile, false
}
if err = section.MapTo(&profile); err != nil {
panic(err)
}
return profile, true
}
// SSOSessionSection returns the [sso-session] section with the matching name. If there isn't any,
// an empty sso-session with the provided name is returned, along with false.
func (c *ConfigFile) SSOSessionSection(name string) (SSOSessionSection, bool) {
ssoSession := SSOSessionSection{
Name: name,
}
if c.iniFile == nil {
return ssoSession, false
}
sectionName := "sso-session " + name
section, err := c.iniFile.GetSection(sectionName)
if err != nil {
return ssoSession, false
}
if err = section.MapTo(&ssoSession); err != nil {
panic(err)
}
return ssoSession, true
}
func (c *ConfigFile) Save() error {
return c.iniFile.SaveTo(c.Path)
}
// Add the profile to the configuration file
func (c *ConfigFile) Add(profile ProfileSection) error {
if c.iniFile == nil {
return errors.New("No iniFile to add to")
}
// default profile name has a slightly different section format
sectionName := "profile " + profile.Name
if profile.Name == defaultSectionName {
sectionName = defaultSectionName
}
section, err := c.iniFile.NewSection(sectionName)
if err != nil {
return fmt.Errorf("Error creating section %q: %v", profile.Name, err)
}
if err = section.ReflectFrom(&profile); err != nil {
return fmt.Errorf("Error mapping profile to ini file: %v", err)
}
return c.Save()
}
// ProfileNames returns a slice of profile names from the AWS config
func (c *ConfigFile) ProfileNames() []string {
profileNames := []string{}
for _, profile := range c.ProfileSections() {
profileNames = append(profileNames, profile.Name)
}
return profileNames
}
// ConfigLoader loads config from configfile and environment variables
type ConfigLoader struct {
BaseConfig ProfileConfig
File *ConfigFile
ActiveProfile string
visitedProfiles []string
}
func NewConfigLoader(baseConfig ProfileConfig, file *ConfigFile, activeProfile string) *ConfigLoader {
return &ConfigLoader{
BaseConfig: baseConfig,
File: file,
ActiveProfile: activeProfile,
}
}
func (cl *ConfigLoader) visitProfile(name string) bool {
for _, p := range cl.visitedProfiles {
if p == name {
return false
}
}
cl.visitedProfiles = append(cl.visitedProfiles, name)
return true
}
func (cl *ConfigLoader) resetLoopDetection() {
cl.visitedProfiles = []string{}
}
func (cl *ConfigLoader) populateFromDefaults(config *ProfileConfig) {
if config.AssumeRoleDuration == 0 {
config.AssumeRoleDuration = DefaultSessionDuration
}
if config.GetFederationTokenDuration == 0 {
config.GetFederationTokenDuration = DefaultSessionDuration
}
if config.NonChainedGetSessionTokenDuration == 0 {
config.NonChainedGetSessionTokenDuration = DefaultSessionDuration
}
if config.ChainedGetSessionTokenDuration == 0 {
config.ChainedGetSessionTokenDuration = DefaultChainedSessionDuration
}
}
func (cl *ConfigLoader) populateFromConfigFile(config *ProfileConfig, profileName string) error {
if !cl.visitProfile(profileName) {
return fmt.Errorf("Loop detected in config file for profile '%s'", profileName)
}
psection, ok := cl.File.ProfileSection(profileName)
if !ok {
// ignore missing profiles
log.Printf("Profile '%s' missing in config file", profileName)
}
if config.MfaSerial == "" {
config.MfaSerial = psection.MfaSerial
}
if config.RoleARN == "" {
config.RoleARN = psection.RoleARN
}
if config.ExternalID == "" {
config.ExternalID = psection.ExternalID
}
if config.Region == "" {
config.Region = psection.Region
}
if config.RoleSessionName == "" {
config.RoleSessionName = psection.RoleSessionName
}
if config.AssumeRoleDuration == 0 {
config.AssumeRoleDuration = time.Duration(psection.DurationSeconds) * time.Second
}
if config.SourceProfileName == "" {
config.SourceProfileName = psection.SourceProfile
}
if config.SSOSession == "" {
config.SSOSession = psection.SSOSession
if psection.SSOSession != "" {
// Populate profile with values from [sso-session].
ssoSection, ok := cl.File.SSOSessionSection(psection.SSOSession)
if ok {
config.SSOStartURL = ssoSection.SSOStartURL
config.SSORegion = ssoSection.SSORegion
config.SSORegistrationScopes = ssoSection.SSORegistrationScopes
} else {
// ignore missing profiles
log.Printf("[sso-session] '%s' missing in config file", psection.SSOSession)
}
}
}
if config.SSOStartURL == "" {
config.SSOStartURL = psection.SSOStartURL
}
if config.SSORegion == "" {
config.SSORegion = psection.SSORegion
}
if config.SSOAccountID == "" {
config.SSOAccountID = psection.SSOAccountID
}
if config.SSORoleName == "" {
config.SSORoleName = psection.SSORoleName
}
if config.WebIdentityTokenFile == "" {
config.WebIdentityTokenFile = psection.WebIdentityTokenFile
}
if config.WebIdentityTokenProcess == "" {
config.WebIdentityTokenProcess = psection.WebIdentityTokenProcess
}
if config.STSRegionalEndpoints == "" {
config.STSRegionalEndpoints = psection.STSRegionalEndpoints
}
if config.SourceIdentity == "" {
config.SourceIdentity = psection.SourceIdentity
}
if config.CredentialProcess == "" {
config.CredentialProcess = psection.CredentialProcess
}
if config.MfaProcess == "" {
config.MfaProcess = psection.MfaProcess
}
if sessionTags := psection.SessionTags; sessionTags != "" && config.SessionTags == nil {
err := config.SetSessionTags(sessionTags)
if err != nil {
return fmt.Errorf("Failed to parse session_tags profile setting: %s", err)
}
}
if transitiveSessionTags := psection.TransitiveSessionTags; transitiveSessionTags != "" && config.TransitiveSessionTags == nil {
config.SetTransitiveSessionTags(transitiveSessionTags)
}
if psection.IncludeProfile != "" {
err := cl.populateFromConfigFile(config, psection.IncludeProfile)
if err != nil {
return err
}
} else if profileName != defaultSectionName {
err := cl.populateFromConfigFile(config, defaultSectionName)
if err != nil {
return err
}
}
// Ignore source_profile if it recursively refers to the profile
if config.SourceProfileName == config.ProfileName {
config.SourceProfileName = ""
}
return nil
}
func (cl *ConfigLoader) populateFromEnv(profile *ProfileConfig) {
if region := os.Getenv("AWS_REGION"); region != "" && profile.Region == "" {
log.Printf("Using region %q from AWS_REGION", region)
profile.Region = region
}
if region := os.Getenv("AWS_DEFAULT_REGION"); region != "" && profile.Region == "" {
log.Printf("Using region %q from AWS_DEFAULT_REGION", region)
profile.Region = region
}
if stsRegionalEndpoints := os.Getenv("AWS_STS_REGIONAL_ENDPOINTS"); stsRegionalEndpoints != "" && profile.STSRegionalEndpoints == "" {
log.Printf("Using %q from AWS_STS_REGIONAL_ENDPOINTS", stsRegionalEndpoints)
profile.STSRegionalEndpoints = stsRegionalEndpoints
}
if mfaSerial := os.Getenv("AWS_MFA_SERIAL"); mfaSerial != "" && profile.MfaSerial == "" {
log.Printf("Using mfa_serial %q from AWS_MFA_SERIAL", mfaSerial)
profile.MfaSerial = mfaSerial
}
var err error
if assumeRoleTTL := os.Getenv("AWS_ASSUME_ROLE_TTL"); assumeRoleTTL != "" && profile.AssumeRoleDuration == 0 {
profile.AssumeRoleDuration, err = time.ParseDuration(assumeRoleTTL)
if err == nil {
log.Printf("Using duration_seconds %q from AWS_ASSUME_ROLE_TTL", profile.AssumeRoleDuration)
}
}
if sessionTTL := os.Getenv("AWS_SESSION_TOKEN_TTL"); sessionTTL != "" && profile.NonChainedGetSessionTokenDuration == 0 {
profile.NonChainedGetSessionTokenDuration, err = time.ParseDuration(sessionTTL)
if err == nil {
log.Printf("Using a session duration of %q from AWS_SESSION_TOKEN_TTL", profile.NonChainedGetSessionTokenDuration)
}
}
if sessionTTL := os.Getenv("AWS_CHAINED_SESSION_TOKEN_TTL"); sessionTTL != "" && profile.ChainedGetSessionTokenDuration == 0 {
profile.ChainedGetSessionTokenDuration, err = time.ParseDuration(sessionTTL)
if err == nil {
log.Printf("Using a cached MFA session duration of %q from AWS_CACHED_SESSION_TOKEN_TTL", profile.ChainedGetSessionTokenDuration)
}
}
if federationTokenTTL := os.Getenv("AWS_FEDERATION_TOKEN_TTL"); federationTokenTTL != "" && profile.GetFederationTokenDuration == 0 {
profile.GetFederationTokenDuration, err = time.ParseDuration(federationTokenTTL)
if err == nil {
log.Printf("Using a session duration of %q from AWS_FEDERATION_TOKEN_TTL", profile.GetFederationTokenDuration)
}
}
// AWS_ROLE_ARN, AWS_ROLE_SESSION_NAME, AWS_SESSION_TAGS, AWS_TRANSITIVE_TAGS and AWS_SOURCE_IDENTITY only apply to the target profile
if profile.ProfileName == cl.ActiveProfile {
if roleARN := os.Getenv("AWS_ROLE_ARN"); roleARN != "" && profile.RoleARN == "" {
log.Printf("Using role_arn %q from AWS_ROLE_ARN", roleARN)
profile.RoleARN = roleARN
}
if roleSessionName := os.Getenv("AWS_ROLE_SESSION_NAME"); roleSessionName != "" && profile.RoleSessionName == "" {
log.Printf("Using role_session_name %q from AWS_ROLE_SESSION_NAME", roleSessionName)
profile.RoleSessionName = roleSessionName
}
if sessionTags := os.Getenv("AWS_SESSION_TAGS"); sessionTags != "" && profile.SessionTags == nil {
err := profile.SetSessionTags(sessionTags)
if err != nil {
log.Fatalf("Failed to parse AWS_SESSION_TAGS environment variable: %s", err)
}
log.Printf("Using session_tags %v from AWS_SESSION_TAGS", profile.SessionTags)
}
if transitiveSessionTags := os.Getenv("AWS_TRANSITIVE_TAGS"); transitiveSessionTags != "" && profile.TransitiveSessionTags == nil {
profile.SetTransitiveSessionTags(transitiveSessionTags)
log.Printf("Using transitive_session_tags %v from AWS_TRANSITIVE_TAGS", profile.TransitiveSessionTags)
}
if sourceIdentity := os.Getenv("AWS_SOURCE_IDENTITY"); sourceIdentity != "" && profile.SourceIdentity == "" {
profile.SourceIdentity = sourceIdentity
log.Printf("Using source_identity %v from AWS_SOURCE_IDENTITY", profile.SourceIdentity)
}
}
}
func (cl *ConfigLoader) hydrateSourceConfig(config *ProfileConfig) error {
if config.SourceProfileName != "" {
sc, err := cl.GetProfileConfig(config.SourceProfileName)
if err != nil {
return err
}
sc.ChainedFromProfile = config
config.SourceProfile = sc
}
return nil
}
// GetProfileConfig loads the profile from the config file and environment variables into config
func (cl *ConfigLoader) GetProfileConfig(profileName string) (*ProfileConfig, error) {
config := cl.BaseConfig
config.ProfileName = profileName
cl.populateFromEnv(&config)
cl.resetLoopDetection()
err := cl.populateFromConfigFile(&config, profileName)
if err != nil {
return nil, err
}
cl.populateFromDefaults(&config)
err = cl.hydrateSourceConfig(&config)
if err != nil {
return nil, err
}
return &config, nil
}
// ProfileConfig is a collection of configuration options for creating temporary credentials
type ProfileConfig struct {
// ProfileName specifies the name of the profile config
ProfileName string
// SourceProfile is the profile where credentials come from
SourceProfileName string
// SourceProfile is the profile where credentials come from
SourceProfile *ProfileConfig
// ChainedFromProfile is the profile that used this profile as its source profile
ChainedFromProfile *ProfileConfig
// Region is the AWS region
Region string
// STSRegionalEndpoints sets STS endpoint resolution logic, must be "regional" or "legacy"
STSRegionalEndpoints string
// Mfa config
MfaSerial string
MfaToken string
MfaPromptMethod string
// MfaProcess specifies external command to run to get an MFA token
MfaProcess string
// AssumeRole config
RoleARN string
RoleSessionName string
ExternalID string
// AssumeRoleWithWebIdentity config
WebIdentityTokenFile string
WebIdentityTokenProcess string
// GetSessionTokenDuration specifies the wanted duration for credentials generated with AssumeRole
AssumeRoleDuration time.Duration
// NonChainedGetSessionTokenDuration specifies the wanted duration for credentials generated with GetSessionToken
NonChainedGetSessionTokenDuration time.Duration
// ChainedGetSessionTokenDuration specifies the wanted duration for credentials generated with GetSessionToken when chaining
ChainedGetSessionTokenDuration time.Duration
// GetFederationTokenDuration specifies the wanted duration for credentials generated with GetFederationToken
GetFederationTokenDuration time.Duration
// SSOSession specifies the [sso-session] section name.
SSOSession string
// SSOStartURL specifies the URL for the AWS IAM Identity Center user portal, legacy option.
SSOStartURL string
// SSORegion specifies the region for the AWS IAM Identity Center user portal, legacy option.
SSORegion string
// SSORegistrationScopes specifies registration scopes for the AWS IAM Identity Center user portal.
SSORegistrationScopes string
// SSOAccountID specifies the AWS account ID for the profile.
SSOAccountID string
// SSORoleName specifies the AWS IAM Role name to target.
SSORoleName string
// SSOUseStdout specifies that the system browser should not be automatically opened
SSOUseStdout bool
// SessionTags specifies assumed role Session Tags
SessionTags map[string]string
// TransitiveSessionTags specifies assumed role Transitive Session Tags keys
TransitiveSessionTags []string
// SourceIdentity specifies assumed role Source Identity
SourceIdentity string
// CredentialProcess specifies external command to run to get an AWS credential
CredentialProcess string
}
// SetSessionTags parses a comma separated key=vaue string and sets Config.SessionTags map
func (c *ProfileConfig) SetSessionTags(s string) error {
c.SessionTags = make(map[string]string)
for _, tag := range strings.Split(s, ",") {
kvPair := strings.SplitN(tag, "=", 2)
if len(kvPair) != 2 {
return errors.New("session tags string must be <key1>=<value1>,[<key2>=<value2>[,...]]")
}
c.SessionTags[strings.TrimSpace(kvPair[0])] = strings.TrimSpace(kvPair[1])
}
return nil
}
// SetTransitiveSessionTags parses a comma separated string and sets Config.TransitiveSessionTags
func (c *ProfileConfig) SetTransitiveSessionTags(s string) {
for _, tag := range strings.Split(s, ",") {
if tag = strings.TrimSpace(tag); tag != "" {
c.TransitiveSessionTags = append(c.TransitiveSessionTags, tag)
}
}
}
func (c *ProfileConfig) IsChained() bool {
return c.ChainedFromProfile != nil
}
func (c *ProfileConfig) HasSourceProfile() bool {
return c.SourceProfile != nil
}
func (c *ProfileConfig) HasMfaSerial() bool {
return c.MfaSerial != ""
}
func (c *ProfileConfig) HasRole() bool {
return c.RoleARN != ""
}
func (c *ProfileConfig) HasSSOSession() bool {
return c.SSOSession != ""
}
func (c *ProfileConfig) HasSSOStartURL() bool {
return c.SSOStartURL != ""
}
func (c *ProfileConfig) HasWebIdentity() bool {
return c.WebIdentityTokenFile != "" || c.WebIdentityTokenProcess != ""
}
func (c *ProfileConfig) HasCredentialProcess() bool {
return c.CredentialProcess != ""
}
func (c *ProfileConfig) GetSessionTokenDuration() time.Duration {
if c.IsChained() {
return c.ChainedGetSessionTokenDuration
}
return c.NonChainedGetSessionTokenDuration
}
================================================
FILE: vault/config_test.go
================================================
package vault_test
import (
"bytes"
"fmt"
"os"
"reflect"
"testing"
"github.com/99designs/aws-vault/v7/vault"
"github.com/google/go-cmp/cmp"
)
// see http://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html
var exampleConfig = []byte(`# an example profile file
[default]
region=us-west-2
output=json
[profile user2]
REGION=us-east-1
output=text
[profile withsource]
source_profile=user2
region=us-east-1
[profile withMFA]
source_profile=user2
Role_Arn=arn:aws:iam::4451234513441615400570:role/aws_admin
mfa_Serial=arn:aws:iam::1234513441:mfa/blah
Region=us-east-1
duration_seconds=1200
sts_regional_endpoints=legacy
[profile testincludeprofile1]
region=us-east-1
[profile testincludeprofile2]
include_profile=testincludeprofile1
[profile with-sso-session]
sso_session = moon-sso
sso_account_id=123456
region = moon-1 # Different from sso region
[sso-session moon-sso]
sso_start_url = https://d-123456789.example.com/start
sso_region = moon-2 # Different from profile region
sso_registration_scopes = sso:account:access
`)
var nestedConfig = []byte(`[default]
[profile testing]
aws_access_key_id=foo
aws_secret_access_key=bar
region=us-west-2
s3=
max_concurrent_requests=10
max_queue_size=1000
`)
var defaultsOnlyConfigWithHeader = []byte(`[default]
region=us-west-2
output=json
`)
func newConfigFile(t *testing.T, b []byte) string {
t.Helper()
f, err := os.CreateTemp("", "aws-config")
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(f.Name(), b, 0600); err != nil {
t.Fatal(err)
}
return f.Name()
}
func TestProfileNameCaseSensitivity(t *testing.T) {
f := newConfigFile(t, exampleConfig)
defer os.Remove(f)
cfg, err := vault.LoadConfig(f)
if err != nil {
t.Fatal(err)
}
def, ok := cfg.ProfileSection("withMFA")
if !ok {
t.Fatalf("Expected to match profile withMFA")
}
expectedMfaSerial := "arn:aws:iam::1234513441:mfa/blah"
if def.MfaSerial != expectedMfaSerial {
t.Fatalf("Expected %s, got %s", expectedMfaSerial, def.MfaSerial)
}
}
func TestConfigParsingProfiles(t *testing.T) {
f := newConfigFile(t, exampleConfig)
defer os.Remove(f)
cfg, err := vault.LoadConfig(f)
if err != nil {
t.Fatal(err)
}
var testCases = []struct {
expected vault.ProfileSection
ok bool
}{
{vault.ProfileSection{Name: "user2", Region: "us-east-1"}, true},
{vault.ProfileSection{Name: "withsource", SourceProfile: "user2", Region: "us-east-1"}, true},
{vault.ProfileSection{Name: "withMFA", MfaSerial: "arn:aws:iam::1234513441:mfa/blah", RoleARN: "arn:aws:iam::4451234513441615400570:role/aws_admin", Region: "us-east-1", DurationSeconds: 1200, SourceProfile: "user2", STSRegionalEndpoints: "legacy"}, true},
{vault.ProfileSection{Name: "nopenotthere"}, false},
}
for _, tc := range testCases {
t.Run(fmt.Sprintf("profile_%s", tc.expected.Name), func(t *testing.T) {
actual, ok := cfg.ProfileSection(tc.expected.Name)
if ok != tc.ok {
t.Fatalf("Expected second param to be %v, got %v", tc.ok, ok)
}
if diff := cmp.Diff(tc.expected, actual); diff != "" {
t.Errorf("ProfileSection() mismatch (-expected +actual):\n%s", diff)
}
})
}
}
func TestConfigParsingDefault(t *testing.T) {
f := newConfigFile(t, exampleConfig)
defer os.Remove(f)
cfg, err := vault.LoadConfig(f)
if err != nil {
t.Fatal(err)
}
def, ok := cfg.ProfileSection("default")
if !ok {
t.Fatalf("Expected to find default profile")
}
expected := vault.ProfileSection{
Name: "default",
Region: "us-west-2",
}
if !reflect.DeepEqual(def, expected) {
t.Fatalf("Expected %+v, got %+v", expected, def)
}
}
func TestProfilesFromConfig(t *testing.T) {
f := newConfigFile(t, exampleConfig)
defer os.Remove(f)
cfg, err := vault.LoadConfig(f)
if err != nil {
t.Fatal(err)
}
expected := []vault.ProfileSection{
{Name: "default", Region: "us-west-2"},
{Name: "user2", Region: "us-east-1"},
{Name: "withsource", Region: "us-east-1", SourceProfile: "user2"},
{Name: "withMFA", MfaSerial: "arn:aws:iam::1234513441:mfa/blah", RoleARN: "arn:aws:iam::4451234513441615400570:role/aws_admin", Region: "us-east-1", DurationSeconds: 1200, SourceProfile: "user2", STSRegionalEndpoints: "legacy"},
{Name: "testincludeprofile1", Region: "us-east-1"},
{Name: "testincludeprofile2", IncludeProfile: "testincludeprofile1"},
{Name: "with-sso-session", SSOSession: "moon-sso", Region: "moon-1", SSOAccountID: "123456"},
}
actual := cfg.ProfileSections()
if diff := cmp.Diff(expected, actual); diff != "" {
t.Errorf("ProfileSections() mismatch (-expected +actual):\n%s", diff)
}
}
func TestAddProfileToExistingConfig(t *testing.T) {
f := newConfigFile(t, exampleConfig)
defer os.Remove(f)
cfg, err := vault.LoadConfig(f)
if err != nil {
t.Fatal(err)
}
err = cfg.Add(vault.ProfileSection{
Name: "llamas",
MfaSerial: "testserial",
Region: "us-east-1",
SourceProfile: "default",
})
if err != nil {
t.Fatalf("Error adding profile: %#v", err)
}
expected := []vault.ProfileSection{
{Name: "default", Region: "us-west-2"},
{Name: "user2", Region: "us-east-1"},
{Name: "withsource", Region: "us-east-1", SourceProfile: "user2"},
{Name: "withMFA", MfaSerial: "arn:aws:iam::1234513441:mfa/blah", RoleARN: "arn:aws:iam::4451234513441615400570:role/aws_admin", Region: "us-east-1", DurationSeconds: 1200, SourceProfile: "user2", STSRegionalEndpoints: "legacy"},
{Name: "testincludeprofile1", Region: "us-east-1"},
{Name: "testincludeprofile2", IncludeProfile: "testincludeprofile1"},
{Name: "with-sso-session", SSOSession: "moon-sso", Region: "moon-1", SSOAccountID: "123456"},
{Name: "llamas", MfaSerial: "testserial", Region: "us-east-1", SourceProfile: "default"},
}
actual := cfg.ProfileSections()
if diff := cmp.Diff(expected, actual); diff != "" {
t.Errorf("ProfileSections() mismatch (-expected +actual):\n%s", diff)
}
}
func TestAddProfileToExistingNestedConfig(t *testing.T) {
f := newConfigFile(t, nestedConfig)
defer os.Remove(f)
cfg, err := vault.LoadConfig(f)
if err != nil {
t.Fatal(err)
}
err = cfg.Add(vault.ProfileSection{
Name: "llamas",
MfaSerial: "testserial",
Region: "us-east-1",
})
if err != nil {
t.Fatalf("Error adding profile: %#v", err)
}
expected := append(nestedConfig, []byte(
"\n[profile llamas]\nmfa_serial=testserial\nregion=us-east-1\n",
)...)
b, _ := os.ReadFile(f)
if !bytes.Equal(expected, b) {
t.Fatalf("Expected:\n%q\nGot:\n%q", expected, b)
}
}
func TestIncludeProfile(t *testing.T) {
f := newConfigFile(t, exampleConfig)
defer os.Remove(f)
configFile, err := vault.LoadConfig(f)
if err != nil {
t.Fatal(err)
}
configLoader := &vault.ConfigLoader{File: configFile}
config, err := configLoader.GetProfileConfig("testincludeprofile2")
if err != nil {
t.Fatalf("Should have found a profile: %v", err)
}
if config.Region != "us-east-1" {
t.Fatalf("Expected region %q, got %q", "us-east-1", config.Region)
}
}
func TestIncludeSsoSession(t *testing.T) {
f := newConfigFile(t, exampleConfig)
defer os.Remove(f)
configFile, err := vault.LoadConfig(f)
if err != nil {
t.Fatal(err)
}
configLoader := &vault.ConfigLoader{File: configFile}
config, err := configLoader.GetProfileConfig("with-sso-session")
if err != nil {
t.Fatalf("Should have found a profile: %v", err)
}
if config.Region != "moon-1" { // Test not the same as SSO region
t.Fatalf("Expected region %q, got %q", "moon-1", config.Region)
}
ssoStartURL := "https://d-123456789.example.com/start"
if config.SSOStartURL != ssoStartURL {
t.Fatalf("Expected sso_start_url %q, got %q", ssoStartURL, config.Region)
}
if config.SSORegion != "moon-2" { // Test not the same as profile region
t.Fatalf("Expected sso_region %q, got %q", "moon-2", config.Region)
}
// Not checking sso_registration_scopes as it seems to be unused by aws-cli.
}
func TestProfileIsEmpty(t *testing.T) {
p := vault.ProfileSection{Name: "foo"}
if !p.IsEmpty() {
t.Errorf("Expected p to be empty")
}
}
func TestIniWithHeaderSavesWithHeader(t *testing.T) {
f := newConfigFile(t, defaultsOnlyConfigWithHeader)
defer os.Remove(f)
cfg, err := vault.LoadConfig(f)
if err != nil {
t.Fatal(err)
}
err = cfg.Save()
if err != nil {
t.Fatal(err)
}
expected := defaultsOnlyConfigWithHeader
b, _ := os.ReadFile(f)
if !bytes.Equal(expected, b) {
t.Fatalf("Expected:\n%q\nGot:\n%q", expected, b)
}
}
func TestIniWithDEFAULTHeader(t *testing.T) {
f := newConfigFile(t, []byte(`[DEFAULT]
region=us-east-1
[default]
region=us-west-2
`))
defer os.Remove(f)
cfg, err := vault.LoadConfig(f)
if err != nil {
t.Fatal(err)
}
expected := []vault.ProfileSection{
{Name: "default", Region: "us-west-2"},
}
actual := cfg.ProfileSections()
if diff := cmp.Diff(expected, actual); diff != "" {
t.Errorf("ProfileSections() mismatch (-expected +actual):\n%s", diff)
}
}
func TestLoadedProfileDoesntReferToItself(t *testing.T) {
f := newConfigFile(t, []byte(`
[profile foo]
source_profile=foo
`))
defer os.Remove(f)
configFile, err := vault.LoadConfig(f)
if err != nil {
t.Fatal(err)
}
def, ok := configFile.ProfileSection("foo")
if !ok {
t.Fatalf("Couldn't load profile foo")
}
expectedSourceProfile := "foo"
if def.SourceProfile != expectedSourceProfile {
t.Fatalf("Expected '%s', got '%s'", expectedSourceProfile, def.SourceProfile)
}
configLoader := &vault.ConfigLoader{File: configFile}
config, err := configLoader.GetProfileConfig("foo")
if err != nil {
t.Fatalf("Should have found a profile: %v", err)
}
expectedSourceProfileName := ""
if config.SourceProfileName != expectedSourceProfileName {
t.Fatalf("Expected '%s', got '%s'", expectedSourceProfileName, config.SourceProfileName)
}
}
func TestSourceProfileCanReferToParent(t *testing.T) {
f := newConfigFile(t, []byte(`
[profile root]
[profile foo]
include_profile=root
source_profile=root
`))
defer os.Remove(f)
configFile, err := vault.LoadConfig(f)
if err != nil {
t.Fatal(err)
}
def, ok := configFile.ProfileSection("foo")
if !ok {
t.Fatalf("Couldn't load profile foo")
}
expectedSourceProfile := "root"
if def.SourceProfile != expectedSourceProfile {
t.Fatalf("Expected '%s', got '%s'", expectedSourceProfile, def.SourceProfile)
}
configLoader := &vault.ConfigLoader{File: configFile}
config, err := configLoader.GetProfileConfig("foo")
if err != nil {
t.Fatalf("Should have found a profile: %v", err)
}
expectedSourceProfileName := "root"
if config.SourceProfileName != expectedSourceProfileName {
t.Fatalf("Expected '%s', got '%s'", expectedSourceProfileName, config.SourceProfileName)
}
}
func TestSetSessionTags(t *testing.T) {
var testCases = []struct {
stringValue string
expected map[string]string
ok bool
}{
{"tag1=value1", map[string]string{"tag1": "value1"}, true},
{
"tag2=value2,tag3=value3,tag4=value4",
map[string]string{"tag2": "value2", "tag3": "value3", "tag4": "value4"},
true,
},
{" tagA = valueA , tagB = valueB , tagC = valueC ",
map[string]string{"tagA": "valueA", "tagB": "valueB", "tagC": "valueC"},
true,
},
{"", nil, false},
{"tag1=value1,", nil, false},
{"tagA=valueA,tagB", nil, false},
{"tagOne,tagTwo=valueTwo", nil, false},
{"tagI=valueI,tagII,tagIII=valueIII", nil, false},
}
for _, tc := range testCases {
config := vault.ProfileConfig{}
err := config.SetSessionTags(tc.stringValue)
if tc.ok {
if err != nil {
t.Fatalf("Unsexpected parsing error: %s", err)
}
if !reflect.DeepEqual(tc.expected, config.SessionTags) {
t.Fatalf("Expected SessionTags: %+v, got %+v", tc.expected, config.SessionTags)
}
} else {
if err == nil {
t.Fatalf("Expected an error parsing %#v, but got none", tc.stringValue)
}
}
}
}
func TestSetTransitiveSessionTags(t *testing.T) {
var testCases = []struct {
stringValue string
expected []string
}{
{"tag1", []string{"tag1"}},
{"tag2,tag3,tag4", []string{"tag2", "tag3", "tag4"}},
{" tagA , tagB , tagC ", []string{"tagA", "tagB", "tagC"}},
{"tag1,", []string{"tag1"}},
{",tagA", []string{"tagA"}},
{"", nil},
{",", nil},
}
for _, tc := range testCases {
config := vault.ProfileConfig{}
config.SetTransitiveSessionTags(tc.stringValue)
if !reflect.DeepEqual(tc.expected, config.TransitiveSessionTags) {
t.Fatalf("Expected TransitiveSessionTags: %+v, got %+v", tc.expected, config.TransitiveSessionTags)
}
}
}
func TestSessionTaggingFromIni(t *testing.T) {
os.Unsetenv("AWS_SESSION_TAGS")
os.Unsetenv("AWS_TRANSITIVE_TAGS")
f := newConfigFile(t, []byte(`
[profile tagged]
session_tags = tag1 = value1 , tag2=value2 ,tag3=value3
transitive_session_tags = tagOne ,tagTwo,tagThree
`))
defer os.Remove(f)
configFile, err := vault.LoadConfig(f)
if err != nil {
t.Fatal(err)
}
configLoader := &vault.ConfigLoader{File: configFile, ActiveProfile: "tagged"}
config, err := configLoader.GetProfileConfig("tagged")
if err != nil {
t.Fatalf("Should have found a profile: %v", err)
}
expectedSessionTags := map[string]string{
"tag1": "value1",
"tag2": "value2",
"tag3": "value3",
}
if !reflect.DeepEqual(expectedSessionTags, config.SessionTags) {
t.Fatalf("Expected session_tags: %+v, got %+v", expectedSessionTags, config.SessionTags)
}
expectedTransitiveSessionTags := []string{"tagOne", "tagTwo", "tagThree"}
if !reflect.DeepEqual(expectedTransitiveSessionTags, config.TransitiveSessionTags) {
t.Fatalf("Expected transitive_session_tags: %+v, got %+v", expectedTransitiveSessionTags, config.TransitiveSessionTags)
}
}
func TestSessionTaggingFromEnvironment(t *testing.T) {
os.Setenv("AWS_SESSION_TAGS", " tagA = val1 , tagB=val2 ,tagC=val3")
os.Setenv("AWS_TRANSITIVE_TAGS", " tagD ,tagE")
defer os.Unsetenv("AWS_SESSION_TAGS")
defer os.Unsetenv("AWS_TRANSITIVE_TAGS")
f := newConfigFile(t, []byte(`
[profile tagged]
session_tags = tag1 = value1 , tag2=value2 ,tag3=value3
transitive_session_tags = tagOne ,tagTwo,tagThree
`))
defer os.Remove(f)
configFile, err := vault.LoadConfig(f)
if err != nil {
t.Fatal(err)
}
configLoader := &vault.ConfigLoader{File: configFile, ActiveProfile: "tagged"}
config, err := configLoader.GetProfileConfig("tagged")
if err != nil {
t.Fatalf("Should have found a profile: %v", err)
}
expectedSessionTags := map[string]string{
"tagA": "val1",
"tagB": "val2",
"tagC": "val3",
}
if !reflect.DeepEqual(expectedSessionTags, config.SessionTags) {
t.Fatalf("Expected session_tags: %+v, got %+v", expectedSessionTags, config.SessionTags)
}
expectedTransitiveSessionTags := []string{"tagD", "tagE"}
if !reflect.DeepEqual(expectedTransitiveSessionTags, config.TransitiveSessionTags) {
t.Fatalf("Expected transitive_session_tags: %+v, got %+v", expectedTransitiveSessionTags, config.TransitiveSessionTags)
}
}
func TestSessionTaggingFromEnvironmentChainedRoles(t *testing.T) {
os.Setenv("AWS_SESSION_TAGS", "tagI=valI")
os.Setenv("AWS_TRANSITIVE_TAGS", " tagII")
defer os.Unsetenv("AWS_SESSION_TAGS")
defer os.Unsetenv("AWS_TRANSITIVE_TAGS")
f := newConfigFile(t, []byte(`
[profile base]
[profile interim]
session_tags=tag1=value1
transitive_session_tags=tag2
source_profile = base
[profile target]
session_tags=tagA=valueA
transitive_session_tags=tagB
source_profile = interim
`))
defer os.Remove(f)
configFile, err := vault.LoadConfig(f)
if err != nil {
t.Fatal(err)
}
configLoader := &vault.ConfigLoader{File: configFile, ActiveProfile: "target"}
config, err := configLoader.GetProfileConfig("target")
if err != nil {
t.Fatalf("Should have found a profile: %v", err)
}
// Testing target profile, should have values populated from environment variables
expectedSessionTags := map[string]string{"tagI": "valI"}
if !reflect.DeepEqual(expectedSessionTags, config.SessionTags) {
t.Fatalf("Expected session_tags: %+v, got %+v", expectedSessionTags, config.SessionTags)
}
expectedTransitiveSessionTags := []string{"tagII"}
if !reflect.DeepEqual(expectedTransitiveSessionTags, config.TransitiveSessionTags) {
t.Fatalf("Expected transitive_session_tags: %+v, got %+v", expectedTransitiveSessionTags, config.TransitiveSessionTags)
}
// Testing interim profile, parameters should come from the config, not environment
interimConfig := config.SourceProfile
expectedSessionTags = map[string]string{"tag1": "value1"}
if !reflect.DeepEqual(expectedSessionTags, interimConfig.SessionTags) {
t.Fatalf("Expected session_tags: %+v, got %+v", expectedSessionTags, interimConfig.SessionTags)
}
expectedTransitiveSessionTags = []string{"tag2"}
if !reflect.DeepEqual(expectedTransitiveSessionTags, interimConfig.TransitiveSessionTags) {
t.Fatalf("Expected transitive_session_tags: %+v, got %+v", expectedTransitiveSessionTags, interimConfig.TransitiveSessionTags)
}
// Testing base profile, should have empty parameters
baseConfig := interimConfig.SourceProfile
if len(baseConfig.SessionTags) > 0 {
t.Fatalf("Expected session_tags to be empty, got %+v", baseConfig.SessionTags)
}
if len(baseConfig.TransitiveSessionTags) > 0 {
t.Fatalf("Expected transitive_session_tags to be empty, got %+v", baseConfig.TransitiveSessionTags)
}
}
================================================
FILE: vault/credentialkeyring.go
================================================
package vault
import (
"encoding/json"
"fmt"
"github.com/99designs/keyring"
"github.com/aws/aws-sdk-go-v2/aws"
)
type CredentialKeyring struct {
Keyring keyring.Keyring
}
func (ck *CredentialKeyring) Keys() (credentialsNames []string, err error) {
allKeys, err := ck.Keyring.Keys()
if err != nil {
return credentialsNames, err
}
for _, keyName := range allKeys {
if !IsSessionKey(keyName) && !IsOIDCTokenKey(keyName) {
credentialsNames = append(credentialsNames, keyName)
}
}
return credentialsNames, nil
}
func (ck *CredentialKeyring) Has(credentialsName string) (bool, error) {
allKeys, err := ck.Keyring.Keys()
if err != nil {
return false, err
}
for _, keyName := range allKeys {
if keyName == credentialsName {
return true, nil
}
}
return false, nil
}
func (ck *CredentialKeyring) Get(credentialsName string) (creds aws.Credentials, err error) {
item, err := ck.Keyring.Get(credentialsName)
if err != nil {
return creds, err
}
if err = json.Unmarshal(item.Data, &creds); err != nil {
return creds, fmt.Errorf("Invalid data in keyring: %v", err)
}
return creds, err
}
func (ck *CredentialKeyring) Set(credentialsName string, creds aws.Credentials) error {
bytes, err := json.Marshal(creds)
if err != nil {
return err
}
return ck.Keyring.Set(keyring.Item{
Key: credentialsName,
Label: fmt.Sprintf("aws-vault (%s)", credentialsName),
Data: bytes,
// specific Keychain settings
KeychainNotTrustApplication: true,
})
}
func (ck *CredentialKeyring) Remove(credentials
gitextract_qqh0xl3v/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ └── bug_report.md
│ └── workflows/
│ ├── go.yml
│ └── stale.yml
├── .gitignore
├── .golangci.yml
├── LICENSE
├── Makefile
├── README.md
├── USAGE.md
├── bin/
│ └── create-dmg
├── cli/
│ ├── add.go
│ ├── add_test.go
│ ├── clear.go
│ ├── exec.go
│ ├── exec_test.go
│ ├── export.go
│ ├── export_test.go
│ ├── global.go
│ ├── list.go
│ ├── list_test.go
│ ├── login.go
│ ├── proxy.go
│ ├── remove.go
│ └── rotate.go
├── contrib/
│ ├── _aws-vault-proxy/
│ │ ├── Dockerfile
│ │ ├── docker-compose.yml
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── main.go
│ ├── completions/
│ │ ├── bash/
│ │ │ └── aws-vault.bash
│ │ ├── fish/
│ │ │ └── aws-vault.fish
│ │ └── zsh/
│ │ └── aws-vault.zsh
│ ├── docker/
│ │ └── Dockerfile
│ └── scripts/
│ ├── aws-configure-with-env-vars.sh
│ ├── aws-iam-create-yubikey-mfa.sh
│ └── aws-iam-resync-yubikey-mfa.sh
├── go.mod
├── go.sum
├── iso8601/
│ ├── iso8601.go
│ └── iso8601_test.go
├── main.go
├── prompt/
│ ├── kdialog.go
│ ├── osascript.go
│ ├── prompt.go
│ ├── terminal.go
│ ├── wincredui_windows.go
│ ├── ykman.go
│ └── zenity.go
├── server/
│ ├── ec2alias_bsd.go
│ ├── ec2alias_linux.go
│ ├── ec2alias_windows.go
│ ├── ec2proxy.go
│ ├── ec2proxy_default.go
│ ├── ec2proxy_unix.go
│ ├── ec2server.go
│ ├── ecsserver.go
│ └── httplog.go
└── vault/
├── assumeroleprovider.go
├── assumerolewithwebidentityprovider.go
├── cachedsessionprovider.go
├── config.go
├── config_test.go
├── credentialkeyring.go
├── credentialprocessprovider.go
├── credentialprocessprovider_test.go
├── executeprocess.go
├── federationtokenprovider.go
├── getuser.go
├── keyringprovider.go
├── mfa.go
├── oidctokenkeyring.go
├── sessionkeyring.go
├── sessionkeyring_test.go
├── sessiontokenprovider.go
├── ssorolecredentialsprovider.go
├── stsendpointresolver.go
├── vault.go
└── vault_test.go
SYMBOL INDEX (302 symbols across 55 files)
FILE: cli/add.go
type AddCommandInput (line 15) | type AddCommandInput struct
function ConfigureAddCommand (line 21) | func ConfigureAddCommand(app *kingpin.Application, a *AwsVault) {
function AddCommand (line 52) | func AddCommand(input AddCommandInput, keyring keyring.Keyring, awsConfi...
FILE: cli/add_test.go
function ExampleAddCommand (line 10) | func ExampleAddCommand() {
FILE: cli/clear.go
type ClearCommandInput (line 11) | type ClearCommandInput struct
function ConfigureClearCommand (line 15) | func ConfigureClearCommand(app *kingpin.Application, a *AwsVault) {
function ClearCommand (line 40) | func ClearCommand(input ClearCommandInput, awsConfigFile *vault.ConfigFi...
FILE: cli/exec.go
type ExecCommandInput (line 24) | type ExecCommandInput struct
method validate (line 39) | func (input ExecCommandInput) validate() error {
function hasBackgroundServer (line 65) | func hasBackgroundServer(input ExecCommandInput) bool {
function ConfigureExecCommand (line 69) | func ConfigureExecCommand(app *kingpin.Application, a *AwsVault) {
function ExecCommand (line 161) | func ExecCommand(input ExecCommandInput, f *vault.ConfigFile, keyring ke...
function printHelpMessage (line 225) | func printHelpMessage(helpMsg string, showHelpMessages bool) {
function printToStderr (line 235) | func printToStderr(helpMsg string) {
function createEnv (line 239) | func createEnv(profileName string, region string) environ {
function startEcsServerAndSetEnv (line 263) | func startEcsServerAndSetEnv(credsProvider aws.CredentialsProvider, conf...
function addCredsToEnv (line 282) | func addCredsToEnv(credsProvider aws.CredentialsProvider, profileName st...
type environ (line 305) | type environ
method Unset (line 308) | func (e *environ) Unset(key string) {
method Set (line 319) | func (e *environ) Set(key, val string) {
function getDefaultShell (line 324) | func getDefaultShell() string {
function runSubProcess (line 336) | func runSubProcess(command string, args []string, env []string) (int, er...
function doExecSyscall (line 370) | func doExecSyscall(command string, args []string, env []string) error {
FILE: cli/exec_test.go
function ExampleExecCommand (line 9) | func ExampleExecCommand() {
FILE: cli/export.go
type ExportCommandInput (line 19) | type ExportCommandInput struct
function ConfigureExportCommand (line 35) | func ConfigureExportCommand(app *kingpin.Application, a *AwsVault) {
function ExportCommand (line 88) | func ExportCommand(input ExportCommandInput, f *vault.ConfigFile, keyrin...
function printJSON (line 115) | func printJSON(input ExportCommandInput, credsProvider aws.CredentialsPr...
function mustNewKey (line 152) | func mustNewKey(s *ini.Section, name, val string) {
function printINI (line 161) | func printINI(credsProvider aws.CredentialsProvider, profilename, region...
function printEnv (line 189) | func printEnv(input ExportCommandInput, credsProvider aws.CredentialsPro...
FILE: cli/export_test.go
function ExampleExportCommand (line 9) | func ExampleExportCommand() {
FILE: cli/global.go
type AwsVault (line 28) | type AwsVault struct
method PromptDriver (line 43) | func (a *AwsVault) PromptDriver(avoidTerminalPrompt bool) string {
method Keyring (line 62) | func (a *AwsVault) Keyring() (keyring.Keyring, error) {
method AwsConfigFile (line 77) | func (a *AwsVault) AwsConfigFile() (*vault.ConfigFile, error) {
method MustGetProfileNames (line 89) | func (a *AwsVault) MustGetProfileNames() []string {
function isATerminal (line 38) | func isATerminal() bool {
function ConfigureGlobals (line 97) | func ConfigureGlobals(app *kingpin.Application) *AwsVault {
function fileKeyringPassphrasePrompt (line 177) | func fileKeyringPassphrasePrompt(prompt string) (string, error) {
FILE: cli/list.go
type ListCommandInput (line 15) | type ListCommandInput struct
function ConfigureListCommand (line 21) | func ConfigureListCommand(app *kingpin.Application, a *AwsVault) {
type stringslice (line 51) | type stringslice
method remove (line 53) | func (ss stringslice) remove(stringsToRemove []string) (newSS []string) {
method has (line 64) | func (ss stringslice) has(s string) bool {
function sessionLabel (line 73) | func sessionLabel(sess vault.SessionMetadata) string {
function ListCommand (line 77) | func ListCommand(input ListCommandInput, awsConfigFile *vault.ConfigFile...
FILE: cli/list_test.go
function ExampleListCommand (line 9) | func ExampleListCommand() {
FILE: cli/login.go
type LoginCommandInput (line 24) | type LoginCommandInput struct
function ConfigureLoginCommand (line 33) | func ConfigureLoginCommand(app *kingpin.Application, a *AwsVault) {
function getCredsProvider (line 84) | func getCredsProvider(input LoginCommandInput, config *vault.ProfileConf...
function LoginCommand (line 116) | func LoginCommand(ctx context.Context, input LoginCommandInput, f *vault...
function generateLoginURL (line 183) | func generateLoginURL(region string, path string) (string, string) {
function isCallerIdentityAssumedRole (line 208) | func isCallerIdentityAssumedRole(ctx context.Context, credsProvider aws....
function createStaticCredentialsProvider (line 226) | func createStaticCredentialsProvider(ctx context.Context, credsProvider ...
function canProviderBeUsedForLogin (line 235) | func canProviderBeUsedForLogin(credsProvider aws.CredentialsProvider) (b...
function requestSigninToken (line 253) | func requestSigninToken(ctx context.Context, creds aws.Credentials, logi...
FILE: cli/proxy.go
function ConfigureProxyCommand (line 12) | func ConfigureProxyCommand(app *kingpin.Application) {
function handleSigTerm (line 32) | func handleSigTerm() {
FILE: cli/remove.go
type RemoveCommandInput (line 13) | type RemoveCommandInput struct
function ConfigureRemoveCommand (line 19) | func ConfigureRemoveCommand(app *kingpin.Application, a *AwsVault) {
function RemoveCommand (line 50) | func RemoveCommand(input RemoveCommandInput, keyring keyring.Keyring) er...
FILE: cli/rotate.go
type RotateCommandInput (line 16) | type RotateCommandInput struct
function ConfigureRotateCommand (line 22) | func ConfigureRotateCommand(app *kingpin.Application, a *AwsVault) {
function RotateCommand (line 53) | func RotateCommand(input RotateCommandInput, f *vault.ConfigFile, keyrin...
function retry (line 150) | func retry(maxTime time.Duration, sleep time.Duration, f func() error) (...
function getUsernameIfAssumingRole (line 171) | func getUsernameIfAssumingRole(ctx context.Context, awsCfg aws.Config, c...
function getProfilesInChain (line 183) | func getProfilesInChain(profileName string, configLoader *vault.ConfigLo...
FILE: contrib/_aws-vault-proxy/main.go
function GetReverseProxyTarget (line 13) | func GetReverseProxyTarget() *url.URL {
function addAuthorizationHeader (line 22) | func addAuthorizationHeader(authToken string, next http.Handler) http.Ha...
function main (line 29) | func main() {
FILE: iso8601/iso8601.go
function Format (line 7) | func Format(t time.Time) string {
FILE: iso8601/iso8601_test.go
function TestFormat (line 8) | func TestFormat(t *testing.T) {
function TestFormatForIssue655 (line 17) | func TestFormatForIssue655(t *testing.T) {
FILE: main.go
function main (line 13) | func main() {
FILE: prompt/kdialog.go
function KDialogMfaPrompt (line 8) | func KDialogMfaPrompt(mfaSerial string) (string, error) {
function init (line 19) | func init() {
FILE: prompt/osascript.go
function OSAScriptMfaPrompt (line 9) | func OSAScriptMfaPrompt(mfaSerial string) (string, error) {
function init (line 24) | func init() {
FILE: prompt/prompt.go
type Func (line 8) | type Func
function Available (line 12) | func Available() []string {
function Method (line 21) | func Method(s string) Func {
function mfaPromptMessage (line 29) | func mfaPromptMessage(mfaSerial string) string {
FILE: prompt/terminal.go
function TerminalPrompt (line 10) | func TerminalPrompt(message string) (string, error) {
function TerminalSecretPrompt (line 27) | func TerminalSecretPrompt(message string) (string, error) {
function TerminalMfaPrompt (line 44) | func TerminalMfaPrompt(mfaSerial string) (string, error) {
function init (line 48) | func init() {
FILE: prompt/wincredui_windows.go
constant CREDUI_FLAGS_ALWAYS_SHOW_UI (line 11) | CREDUI_FLAGS_ALWAYS_SHOW_UI = 0x00080
constant CREDUI_FLAGS_GENERIC_CREDENTIALS (line 12) | CREDUI_FLAGS_GENERIC_CREDENTIALS = 0x40000
constant CREDUI_FLAGS_KEEP_USERNAME (line 13) | CREDUI_FLAGS_KEEP_USERNAME = 0x100000
type creduiInfoA (line 16) | type creduiInfoA struct
function WinCredUiPrompt (line 24) | func WinCredUiPrompt(mfaSerial string) (string, error) {
function init (line 56) | func init() {
FILE: prompt/ykman.go
function YkmanMfaProvider (line 13) | func YkmanMfaProvider(mfaSerial string) (string, error) {
function init (line 48) | func init() {
FILE: prompt/zenity.go
function ZenityMfaPrompt (line 8) | func ZenityMfaPrompt(mfaSerial string) (string, error) {
function init (line 19) | func init() {
FILE: server/ec2alias_bsd.go
function installEc2EndpointNetworkAlias (line 8) | func installEc2EndpointNetworkAlias() ([]byte, error) {
function removeEc2EndpointNetworkAlias (line 12) | func removeEc2EndpointNetworkAlias() ([]byte, error) {
FILE: server/ec2alias_linux.go
function installEc2EndpointNetworkAlias (line 8) | func installEc2EndpointNetworkAlias() ([]byte, error) {
function removeEc2EndpointNetworkAlias (line 12) | func removeEc2EndpointNetworkAlias() ([]byte, error) {
FILE: server/ec2alias_windows.go
function msgFound (line 25) | func msgFound(localised []string, toTest string) bool {
function runAndWrapAdminErrors (line 35) | func runAndWrapAdminErrors(name string, arg ...string) ([]byte, error) {
function installEc2EndpointNetworkAlias (line 44) | func installEc2EndpointNetworkAlias() ([]byte, error) {
function removeEc2EndpointNetworkAlias (line 53) | func removeEc2EndpointNetworkAlias() ([]byte, error) {
FILE: server/ec2proxy.go
constant ec2MetadataEndpointIP (line 16) | ec2MetadataEndpointIP = "169.254.169.254"
constant ec2MetadataEndpointAddr (line 17) | ec2MetadataEndpointAddr = "169.254.169.254:80"
function StartProxy (line 22) | func StartProxy() error {
function IsProxyRunning (line 48) | func IsProxyRunning() bool {
function Shutdown (line 53) | func Shutdown() {
function StopProxy (line 62) | func StopProxy() {
function awsVaultExecutable (line 66) | func awsVaultExecutable() string {
FILE: server/ec2proxy_default.go
function StartEc2EndpointProxyServerProcess (line 15) | func StartEc2EndpointProxyServerProcess() error {
FILE: server/ec2proxy_unix.go
function StartEc2EndpointProxyServerProcess (line 13) | func StartEc2EndpointProxyServerProcess() error {
FILE: server/ec2server.go
constant ec2CredentialsServerAddr (line 16) | ec2CredentialsServerAddr = "127.0.0.1:9099"
function StartEc2CredentialsServer (line 19) | func StartEc2CredentialsServer(ctx context.Context, credsProvider aws.Cr...
function startEc2CredentialsServer (line 31) | func startEc2CredentialsServer(credsProvider aws.CredentialsProvider, re...
function withSecurityChecks (line 60) | func withSecurityChecks(next *http.ServeMux) http.HandlerFunc {
function credsHandler (line 87) | func credsHandler(credsProvider aws.CredentialsProvider) http.HandlerFunc {
FILE: server/ecsserver.go
function writeErrorMessage (line 21) | func writeErrorMessage(w http.ResponseWriter, msg string, statusCode int) {
function withAuthorizationCheck (line 29) | func withAuthorizationCheck(authToken string, next http.HandlerFunc) htt...
function writeCredsToResponse (line 39) | func writeCredsToResponse(creds aws.Credentials, w http.ResponseWriter) {
function generateRandomString (line 52) | func generateRandomString() string {
type EcsServer (line 60) | type EcsServer struct
method BaseURL (line 101) | func (e *EcsServer) BaseURL() string {
method AuthToken (line 104) | func (e *EcsServer) AuthToken() string {
method Serve (line 108) | func (e *EcsServer) Serve() error {
method DefaultRoute (line 112) | func (e *EcsServer) DefaultRoute(w http.ResponseWriter, r *http.Reques...
method getRoleProvider (line 121) | func (e *EcsServer) getRoleProvider(roleArn string) aws.CredentialsPro...
method AssumeRoleArnRoute (line 140) | func (e *EcsServer) AssumeRoleArnRoute(w http.ResponseWriter, r *http....
function NewEcsServer (line 69) | func NewEcsServer(ctx context.Context, baseCredsProvider aws.Credentials...
FILE: server/httplog.go
type loggingMiddlewareResponseWriter (line 9) | type loggingMiddlewareResponseWriter struct
method WriteHeader (line 14) | func (w *loggingMiddlewareResponseWriter) WriteHeader(statusCode int) {
function withLogging (line 19) | func withLogging(handler http.Handler) http.Handler {
FILE: vault/assumeroleprovider.go
type AssumeRoleProvider (line 15) | type AssumeRoleProvider struct
method Retrieve (line 28) | func (p *AssumeRoleProvider) Retrieve(ctx context.Context) (aws.Creden...
method roleSessionName (line 43) | func (p *AssumeRoleProvider) roleSessionName() string {
method RetrieveStsCredentials (line 52) | func (p *AssumeRoleProvider) RetrieveStsCredentials(ctx context.Contex...
FILE: vault/assumerolewithwebidentityprovider.go
type AssumeRoleWithWebIdentityProvider (line 16) | type AssumeRoleWithWebIdentityProvider struct
method Retrieve (line 27) | func (p *AssumeRoleWithWebIdentityProvider) Retrieve(ctx context.Conte...
method roleSessionName (line 42) | func (p *AssumeRoleWithWebIdentityProvider) roleSessionName() string {
method RetrieveStsCredentials (line 51) | func (p *AssumeRoleWithWebIdentityProvider) RetrieveStsCredentials(ctx...
method webIdentityToken (line 74) | func (p *AssumeRoleWithWebIdentityProvider) webIdentityToken() (string...
FILE: vault/cachedsessionprovider.go
type StsSessionProvider (line 12) | type StsSessionProvider interface
type CachedSessionProvider (line 19) | type CachedSessionProvider struct
method RetrieveStsCredentials (line 26) | func (p *CachedSessionProvider) RetrieveStsCredentials(ctx context.Con...
method Retrieve (line 48) | func (p *CachedSessionProvider) Retrieve(ctx context.Context) (aws.Cre...
FILE: vault/config.go
constant DefaultSessionDuration (line 17) | DefaultSessionDuration = time.Hour * 1
constant DefaultChainedSessionDuration (line 20) | DefaultChainedSessionDuration = time.Hour * 8
constant defaultSectionName (line 22) | defaultSectionName = "default"
constant roleChainingMaximumDuration (line 23) | roleChainingMaximumDuration = 1 * time.Hour
function init (line 26) | func init() {
type ConfigFile (line 31) | type ConfigFile struct
method parseFile (line 110) | func (c *ConfigFile) parseFile() error {
method ProfileSections (line 165) | func (c *ConfigFile) ProfileSections() []ProfileSection {
method ProfileSection (line 195) | func (c *ConfigFile) ProfileSection(name string) (ProfileSection, bool) {
method SSOSessionSection (line 219) | func (c *ConfigFile) SSOSessionSection(name string) (SSOSessionSection...
method Save (line 237) | func (c *ConfigFile) Save() error {
method Add (line 242) | func (c *ConfigFile) Add(profile ProfileSection) error {
method ProfileNames (line 262) | func (c *ConfigFile) ProfileNames() []string {
function configPath (line 37) | func configPath() (string, error) {
function createConfigFilesIfMissing (line 52) | func createConfigFilesIfMissing() error {
function LoadConfig (line 78) | func LoadConfig(path string) (*ConfigFile, error) {
function LoadConfigFromEnv (line 100) | func LoadConfigFromEnv() (*ConfigFile, error) {
type ProfileSection (line 126) | type ProfileSection struct
method IsEmpty (line 159) | func (s ProfileSection) IsEmpty() bool {
type SSOSessionSection (line 152) | type SSOSessionSection struct
type ConfigLoader (line 271) | type ConfigLoader struct
method visitProfile (line 287) | func (cl *ConfigLoader) visitProfile(name string) bool {
method resetLoopDetection (line 297) | func (cl *ConfigLoader) resetLoopDetection() {
method populateFromDefaults (line 301) | func (cl *ConfigLoader) populateFromDefaults(config *ProfileConfig) {
method populateFromConfigFile (line 316) | func (cl *ConfigLoader) populateFromConfigFile(config *ProfileConfig, ...
method populateFromEnv (line 423) | func (cl *ConfigLoader) populateFromEnv(profile *ProfileConfig) {
method hydrateSourceConfig (line 505) | func (cl *ConfigLoader) hydrateSourceConfig(config *ProfileConfig) err...
method GetProfileConfig (line 518) | func (cl *ConfigLoader) GetProfileConfig(profileName string) (*Profile...
function NewConfigLoader (line 279) | func NewConfigLoader(baseConfig ProfileConfig, file *ConfigFile, activeP...
type ProfileConfig (line 540) | type ProfileConfig struct
method SetSessionTags (line 623) | func (c *ProfileConfig) SetSessionTags(s string) error {
method SetTransitiveSessionTags (line 637) | func (c *ProfileConfig) SetTransitiveSessionTags(s string) {
method IsChained (line 645) | func (c *ProfileConfig) IsChained() bool {
method HasSourceProfile (line 649) | func (c *ProfileConfig) HasSourceProfile() bool {
method HasMfaSerial (line 653) | func (c *ProfileConfig) HasMfaSerial() bool {
method HasRole (line 657) | func (c *ProfileConfig) HasRole() bool {
method HasSSOSession (line 661) | func (c *ProfileConfig) HasSSOSession() bool {
method HasSSOStartURL (line 665) | func (c *ProfileConfig) HasSSOStartURL() bool {
method HasWebIdentity (line 669) | func (c *ProfileConfig) HasWebIdentity() bool {
method HasCredentialProcess (line 673) | func (c *ProfileConfig) HasCredentialProcess() bool {
method GetSessionTokenDuration (line 677) | func (c *ProfileConfig) GetSessionTokenDuration() time.Duration {
FILE: vault/config_test.go
function newConfigFile (line 69) | func newConfigFile(t *testing.T, b []byte) string {
function TestProfileNameCaseSensitivity (line 81) | func TestProfileNameCaseSensitivity(t *testing.T) {
function TestConfigParsingProfiles (line 101) | func TestConfigParsingProfiles(t *testing.T) {
function TestConfigParsingDefault (line 133) | func TestConfigParsingDefault(t *testing.T) {
function TestProfilesFromConfig (line 157) | func TestProfilesFromConfig(t *testing.T) {
function TestAddProfileToExistingConfig (line 182) | func TestAddProfileToExistingConfig(t *testing.T) {
function TestAddProfileToExistingNestedConfig (line 218) | func TestAddProfileToExistingNestedConfig(t *testing.T) {
function TestIncludeProfile (line 247) | func TestIncludeProfile(t *testing.T) {
function TestIncludeSsoSession (line 267) | func TestIncludeSsoSession(t *testing.T) {
function TestProfileIsEmpty (line 297) | func TestProfileIsEmpty(t *testing.T) {
function TestIniWithHeaderSavesWithHeader (line 304) | func TestIniWithHeaderSavesWithHeader(t *testing.T) {
function TestIniWithDEFAULTHeader (line 327) | func TestIniWithDEFAULTHeader(t *testing.T) {
function TestLoadedProfileDoesntReferToItself (line 349) | func TestLoadedProfileDoesntReferToItself(t *testing.T) {
function TestSourceProfileCanReferToParent (line 383) | func TestSourceProfileCanReferToParent(t *testing.T) {
function TestSetSessionTags (line 420) | func TestSetSessionTags(t *testing.T) {
function TestSetTransitiveSessionTags (line 461) | func TestSetTransitiveSessionTags(t *testing.T) {
function TestSessionTaggingFromIni (line 484) | func TestSessionTaggingFromIni(t *testing.T) {
function TestSessionTaggingFromEnvironment (line 518) | func TestSessionTaggingFromEnvironment(t *testing.T) {
function TestSessionTaggingFromEnvironmentChainedRoles (line 555) | func TestSessionTaggingFromEnvironmentChainedRoles(t *testing.T) {
FILE: vault/credentialkeyring.go
type CredentialKeyring (line 11) | type CredentialKeyring struct
method Keys (line 15) | func (ck *CredentialKeyring) Keys() (credentialsNames []string, err er...
method Has (line 28) | func (ck *CredentialKeyring) Has(credentialsName string) (bool, error) {
method Get (line 41) | func (ck *CredentialKeyring) Get(credentialsName string) (creds aws.Cr...
method Set (line 52) | func (ck *CredentialKeyring) Set(credentialsName string, creds aws.Cre...
method Remove (line 68) | func (ck *CredentialKeyring) Remove(credentialsName string) error {
FILE: vault/credentialprocessprovider.go
type CredentialProcessProvider (line 17) | type CredentialProcessProvider struct
method validateJSONCredential (line 21) | func (p *CredentialProcessProvider) validateJSONCredential(cred *ststy...
method Retrieve (line 39) | func (p *CredentialProcessProvider) Retrieve(ctx context.Context) (aws...
method retrieveWith (line 43) | func (p *CredentialProcessProvider) retrieveWith(ctx context.Context, ...
method RetrieveStsCredentials (line 58) | func (p *CredentialProcessProvider) RetrieveStsCredentials(ctx context...
method callCredentialProcessWith (line 62) | func (p *CredentialProcessProvider) callCredentialProcessWith(_ contex...
FILE: vault/credentialprocessprovider_test.go
function executeFail (line 16) | func executeFail(_ string) (string, error) {
function executeGetBadJSON (line 20) | func executeGetBadJSON(_ string) (string, error) {
function executeGetCredential (line 24) | func executeGetCredential(accessKeyID *string, expiration *time.Time, se...
function TestCredentialProcessProvider_Retrieve (line 34) | func TestCredentialProcessProvider_Retrieve(t *testing.T) {
FILE: vault/executeprocess.go
function executeProcess (line 10) | func executeProcess(process string) (string, error) {
FILE: vault/federationtokenprovider.go
constant allowAllIAMPolicy (line 12) | allowAllIAMPolicy = `{"Version":"2012-10-17","Statement":[{"Effect":"All...
type FederationTokenProvider (line 15) | type FederationTokenProvider struct
method name (line 21) | func (f *FederationTokenProvider) name() string {
method Retrieve (line 30) | func (f *FederationTokenProvider) Retrieve(ctx context.Context) (creds...
FILE: vault/getuser.go
function GetUsernameFromSession (line 16) | func GetUsernameFromSession(ctx context.Context, cfg aws.Config) (string...
FILE: vault/keyringprovider.go
type KeyringProvider (line 11) | type KeyringProvider struct
method Retrieve (line 16) | func (p *KeyringProvider) Retrieve(_ context.Context) (aws.Credentials...
FILE: vault/mfa.go
type Mfa (line 16) | type Mfa struct
method GetMfaToken (line 22) | func (m Mfa) GetMfaToken() (*string, error) {
function NewMfa (line 31) | func NewMfa(config *ProfileConfig) Mfa {
function ProcessMfaProvider (line 49) | func ProcessMfaProvider(processCmd string) (string, error) {
FILE: vault/oidctokenkeyring.go
type OIDCTokenKeyring (line 14) | type OIDCTokenKeyring struct
method fmtKey (line 25) | func (o *OIDCTokenKeyring) fmtKey(startURL string) string {
method Has (line 33) | func (o OIDCTokenKeyring) Has(startURL string) (bool, error) {
method Get (line 48) | func (o OIDCTokenKeyring) Get(startURL string) (*ssooidc.CreateTokenOu...
method Set (line 73) | func (o OIDCTokenKeyring) Set(startURL string, token *ssooidc.CreateTo...
method Remove (line 92) | func (o OIDCTokenKeyring) Remove(startURL string) error {
method RemoveAll (line 96) | func (o *OIDCTokenKeyring) RemoveAll() (n int, err error) {
method Keys (line 110) | func (o *OIDCTokenKeyring) Keys() (kk []string, err error) {
type OIDCTokenData (line 18) | type OIDCTokenData struct
constant oidcTokenKeyPrefix (line 23) | oidcTokenKeyPrefix = "oidc:"
function IsOIDCTokenKey (line 29) | func IsOIDCTokenKey(k string) bool {
FILE: vault/sessionkeyring.go
function IsOldSessionKey (line 26) | func IsOldSessionKey(s string) bool {
function IsCurrentSessionKey (line 35) | func IsCurrentSessionKey(s string) bool {
function IsSessionKey (line 40) | func IsSessionKey(s string) bool {
type SessionMetadata (line 44) | type SessionMetadata struct
method String (line 51) | func (k *SessionMetadata) String() string {
method StringForMatching (line 61) | func (k *SessionMetadata) StringForMatching() string {
function NewSessionKeyFromString (line 70) | func NewSessionKeyFromString(s string) (SessionMetadata, error) {
type SessionKeyring (line 97) | type SessionKeyring struct
method lookupKeyName (line 103) | func (sk *SessionKeyring) lookupKeyName(key SessionMetadata) (string, ...
method Has (line 116) | func (sk *SessionKeyring) Has(key SessionMetadata) (bool, error) {
method Get (line 128) | func (sk *SessionKeyring) Get(key SessionMetadata) (creds *ststypes.Cr...
method Set (line 146) | func (sk *SessionKeyring) Set(key SessionMetadata, creds *ststypes.Cre...
method Remove (line 177) | func (sk *SessionKeyring) Remove(key SessionMetadata) error {
method RemoveAll (line 186) | func (sk *SessionKeyring) RemoveAll() (n int, err error) {
method Keys (line 200) | func (sk *SessionKeyring) Keys() (kk []SessionMetadata, err error) {
method realSessionKey (line 215) | func (sk *SessionKeyring) realSessionKey(key SessionMetadata) (m Sessi...
method GetAllMetadata (line 227) | func (sk *SessionKeyring) GetAllMetadata() (mm []SessionMetadata, err ...
method RemoveForProfile (line 245) | func (sk *SessionKeyring) RemoveForProfile(profileName string) (n int,...
method RemoveOldSessions (line 263) | func (sk *SessionKeyring) RemoveOldSessions() (n int, err error) {
FILE: vault/sessionkeyring_test.go
function TestIsSessionKey (line 9) | func TestIsSessionKey(t *testing.T) {
FILE: vault/sessiontokenprovider.go
type SessionTokenProvider (line 14) | type SessionTokenProvider struct
method Retrieve (line 21) | func (p *SessionTokenProvider) Retrieve(ctx context.Context) (aws.Cred...
method RetrieveStsCredentials (line 37) | func (p *SessionTokenProvider) RetrieveStsCredentials(ctx context.Cont...
FILE: vault/ssorolecredentialsprovider.go
type OIDCTokenCacher (line 23) | type OIDCTokenCacher interface
type SSORoleCredentialsProvider (line 30) | type SSORoleCredentialsProvider struct
method Retrieve (line 45) | func (p *SSORoleCredentialsProvider) Retrieve(ctx context.Context) (aw...
method getRoleCredentials (line 60) | func (p *SSORoleCredentialsProvider) getRoleCredentials(ctx context.Co...
method RetrieveStsCredentials (line 96) | func (p *SSORoleCredentialsProvider) RetrieveStsCredentials(ctx contex...
method getRoleCredentialsAsStsCredemtials (line 101) | func (p *SSORoleCredentialsProvider) getRoleCredentialsAsStsCredemtial...
method getOIDCToken (line 115) | func (p *SSORoleCredentialsProvider) getOIDCToken(ctx context.Context)...
method newOIDCToken (line 139) | func (p *SSORoleCredentialsProvider) newOIDCToken(ctx context.Context)...
function millisecondsTimeValue (line 40) | func millisecondsTimeValue(v int64) time.Time {
FILE: vault/stsendpointresolver.go
function getSTSEndpointResolver (line 12) | func getSTSEndpointResolver(stsRegionalEndpoints string) aws.EndpointRes...
FILE: vault/vault.go
function init (line 19) | func init() {
function NewAwsConfig (line 25) | func NewAwsConfig(region, stsRegionalEndpoints string) aws.Config {
function NewAwsConfigWithCredsProvider (line 32) | func NewAwsConfigWithCredsProvider(credsProvider aws.CredentialsProvider...
function FormatKeyForDisplay (line 40) | func FormatKeyForDisplay(k string) string {
function isMasterCredentialsProvider (line 44) | func isMasterCredentialsProvider(credsProvider aws.CredentialsProvider) ...
function NewMasterCredentialsProvider (line 50) | func NewMasterCredentialsProvider(k *CredentialKeyring, credentialsName ...
function NewSessionTokenProvider (line 54) | func NewSessionTokenProvider(credsProvider aws.CredentialsProvider, k ke...
function NewAssumeRoleProvider (line 80) | func NewAssumeRoleProvider(credsProvider aws.CredentialsProvider, k keyr...
function NewAssumeRoleWithWebIdentityProvider (line 113) | func NewAssumeRoleWithWebIdentityProvider(k keyring.Keyring, config *Pro...
function NewSSORoleCredentialsProvider (line 141) | func NewSSORoleCredentialsProvider(k keyring.Keyring, config *ProfileCon...
function NewCredentialProcessProvider (line 172) | func NewCredentialProcessProvider(k keyring.Keyring, config *ProfileConf...
function NewFederationTokenProvider (line 192) | func NewFederationTokenProvider(ctx context.Context, credsProvider aws.C...
function FindMasterCredentialsNameFor (line 208) | func FindMasterCredentialsNameFor(profileName string, keyring *Credentia...
type TempCredentialsCreator (line 225) | type TempCredentialsCreator struct
method getSourceCreds (line 237) | func (t *TempCredentialsCreator) getSourceCreds(config *ProfileConfig,...
method getSourceCredWithSession (line 251) | func (t *TempCredentialsCreator) getSourceCredWithSession(config *Prof...
method GetProviderForProfile (line 279) | func (t *TempCredentialsCreator) GetProviderForProfile(config *Profile...
method canUseGetSessionToken (line 308) | func (t *TempCredentialsCreator) canUseGetSessionToken(c *ProfileConfi...
function mfaDetails (line 337) | func mfaDetails(mfaChained bool, config *ProfileConfig) string {
function NewTempCredentialsProvider (line 348) | func NewTempCredentialsProvider(config *ProfileConfig, keyring *Credenti...
FILE: vault/vault_test.go
function TestUsageWebIdentityExample (line 11) | func TestUsageWebIdentityExample(t *testing.T) {
function TestIssue1176 (line 40) | func TestIssue1176(t *testing.T) {
function TestIssue1195 (line 76) | func TestIssue1195(t *testing.T) {
Condensed preview — 78 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (259K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 305,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n- [ ] I am using"
},
{
"path": ".github/workflows/go.yml",
"chars": 952,
"preview": "name: Continuous Integration\non:\n push:\n pull_request:\n branches:\n - master\npermissions:\n contents: read\n\njob"
},
{
"path": ".github/workflows/stale.yml",
"chars": 541,
"preview": "# See https://github.com/actions/stale\nname: Mark and close stale issues\non:\n schedule:\n - cron: '15 10 * * *'\njobs:"
},
{
"path": ".gitignore",
"chars": 36,
"preview": "/aws-vault\n/aws-vault-*\n/SHA256SUMS\n"
},
{
"path": ".golangci.yml",
"chars": 426,
"preview": "linters:\n enable:\n - bodyclose\n - contextcheck\n - depguard\n - durationcheck\n - dupl\n - errchkjson\n "
},
{
"path": "LICENSE",
"chars": 1077,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2015 99designs\n\nPermission is hereby granted, free of charge, to any person obtaini"
},
{
"path": "Makefile",
"chars": 2993,
"preview": "VERSION=$(shell git describe --tags --candidates=1 --dirty)\nBUILD_FLAGS=-ldflags=\"-X main.Version=$(VERSION)\" -trimpath\n"
},
{
"path": "README.md",
"chars": 8871,
"preview": "# AWS Vault\n\n[](https://github.com/99"
},
{
"path": "USAGE.md",
"chars": 36695,
"preview": "# Usage\n\n- [Usage](#usage)\n - [Getting Help](#getting-help)\n - [Typical use-cases for aws-vault](#typical-use-cases-fo"
},
{
"path": "bin/create-dmg",
"chars": 1323,
"preview": "#!/bin/bash\n#\n# create-dmg packages the aws-vault CLI binary for macOS\n# using Apple's signing and notorizing process\n#\n"
},
{
"path": "cli/add.go",
"chars": 2931,
"preview": "package cli\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/99designs/aws-vault/v7/prompt\"\n\t\"github.com/99designs/aws-vault/"
},
{
"path": "cli/add_test.go",
"chars": 825,
"preview": "package cli\n\nimport (\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/alecthomas/kingpin/v2\"\n)\n\nfunc ExampleAddCommand() {\n\tf, err := os.Crea"
},
{
"path": "cli/clear.go",
"chars": 1915,
"preview": "package cli\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/99designs/aws-vault/v7/vault\"\n\t\"github.com/99designs/keyring\"\n\t\"github.com/al"
},
{
"path": "cli/exec.go",
"chars": 11481,
"preview": "package cli\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\tosexec \"os/exec\"\n\t\"os/signal\"\n\t\"runtime\"\n\t\"strings\"\n\t\""
},
{
"path": "cli/exec_test.go",
"chars": 540,
"preview": "package cli\n\nimport (\n\t\"github.com/alecthomas/kingpin/v2\"\n\n\t\"github.com/99designs/keyring\"\n)\n\nfunc ExampleExecCommand() "
},
{
"path": "cli/export.go",
"chars": 6319,
"preview": "package cli\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/99designs/aws-vault/v7/iso86"
},
{
"path": "cli/export_test.go",
"chars": 588,
"preview": "package cli\n\nimport (\n\t\"github.com/alecthomas/kingpin/v2\"\n\n\t\"github.com/99designs/keyring\"\n)\n\nfunc ExampleExportCommand("
},
{
"path": "cli/global.go",
"chars": 5066,
"preview": "package cli\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/99designs/aws-vault/v7/prompt\"\n\t\"github.com/99d"
},
{
"path": "cli/list.go",
"chars": 4413,
"preview": "package cli\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\t\"time\"\n\n\t\"github.com/99designs/aws-vault/v7/vault\"\n\t\"gi"
},
{
"path": "cli/list_test.go",
"chars": 478,
"preview": "package cli\n\nimport (\n\t\"github.com/alecthomas/kingpin/v2\"\n\n\t\"github.com/99designs/keyring\"\n)\n\nfunc ExampleListCommand() "
},
{
"path": "cli/login.go",
"chars": 9171,
"preview": "package cli\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"githu"
},
{
"path": "cli/proxy.go",
"chars": 710,
"preview": "package cli\n\nimport (\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\t\"github.com/99designs/aws-vault/v7/server\"\n\t\"github.com/alecthomas"
},
{
"path": "cli/remove.go",
"chars": 1890,
"preview": "package cli\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/99designs/aws-vault/v7/prompt\"\n\t\"github.com/99designs/aws-vault/v7"
},
{
"path": "cli/rotate.go",
"chars": 6190,
"preview": "package cli\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/99designs/aws-vault/v7/vault\"\n\t\"github.com/99design"
},
{
"path": "contrib/_aws-vault-proxy/Dockerfile",
"chars": 177,
"preview": "FROM golang:1.17\nWORKDIR /usr/src/aws-vault-proxy\nCOPY . /usr/src/aws-vault-proxy\nRUN go build -v -o /usr/local/bin/aws-"
},
{
"path": "contrib/_aws-vault-proxy/docker-compose.yml",
"chars": 678,
"preview": "version: \"2.4\"\nnetworks:\n aws-vault:\n driver: bridge\n ipam:\n config:\n - subnet: \"169.254.170.0/24\"\n "
},
{
"path": "contrib/_aws-vault-proxy/go.mod",
"chars": 152,
"preview": "module aws-vault-ecs-server-reverse-proxy\n\ngo 1.17\n\nrequire github.com/gorilla/handlers v1.5.1\n\nrequire github.com/felix"
},
{
"path": "contrib/_aws-vault-proxy/go.sum",
"chars": 348,
"preview": "github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=\ngithub.com/felixge/httpsnoop v1.0.1/"
},
{
"path": "contrib/_aws-vault-proxy/main.go",
"chars": 949,
"preview": "package main\n\nimport (\n\t\"log\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"net/url\"\n\t\"os\"\n\n\t\"github.com/gorilla/handlers\"\n)\n\nfunc "
},
{
"path": "contrib/completions/bash/aws-vault.bash",
"chars": 491,
"preview": "_aws-vault_bash_autocomplete() {\n local i cur prev opts base\n\n for (( i=1; i < COMP_CWORD; i++ )); do\n if ["
},
{
"path": "contrib/completions/fish/aws-vault.fish",
"chars": 697,
"preview": "if status --is-interactive\n complete -ec aws-vault\n\n # switch based on seeing a `--`\n complete -c aws-vault -n 'not _"
},
{
"path": "contrib/completions/zsh/aws-vault.zsh",
"chars": 539,
"preview": "#compdef aws-vault\n\n_aws-vault() {\n local i\n for (( i=2; i < CURRENT; i++ )); do\n if [[ ${words[i]} == -- ]"
},
{
"path": "contrib/docker/Dockerfile",
"chars": 470,
"preview": "FROM debian:bullseye-slim\nRUN apt update && apt install -y curl\nRUN curl -fLs -o /usr/local/bin/aws-vault https://github"
},
{
"path": "contrib/scripts/aws-configure-with-env-vars.sh",
"chars": 520,
"preview": "#!/bin/sh\n# Configure aws-cli using the AWS env vars created with aws-vault\n#\n# Usage: aws-vault exec <SOURCE_PROFILE> -"
},
{
"path": "contrib/scripts/aws-iam-create-yubikey-mfa.sh",
"chars": 1328,
"preview": "#!/bin/sh\n# Adds a Yubikey TOTP device to IAM using your IAM User as the $MFA_DEVICE_NAME\n# Currently, aws iam enable-mf"
},
{
"path": "contrib/scripts/aws-iam-resync-yubikey-mfa.sh",
"chars": 946,
"preview": "#!/bin/sh\n# Resync a Yubikey TOTP device to IAM using your IAM User as the $MFA_DEVICE_NAME\n# Currently, aws iam resync-"
},
{
"path": "go.mod",
"chars": 1667,
"preview": "module github.com/99designs/aws-vault/v7\n\ngo 1.20\n\nrequire (\n\tgithub.com/99designs/keyring v1.2.2\n\tgithub.com/alecthomas"
},
{
"path": "go.sum",
"chars": 8829,
"preview": "github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=\ngith"
},
{
"path": "iso8601/iso8601.go",
"chars": 223,
"preview": "package iso8601\n\nimport \"time\"\n\n// Format outputs an ISO-8601 datetime string from the given time,\n// in a format compat"
},
{
"path": "iso8601/iso8601_test.go",
"chars": 546,
"preview": "package iso8601\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestFormat(t *testing.T) {\n\tinput, _ := time.Parse(time.RFC3339, \"2"
},
{
"path": "main.go",
"chars": 720,
"preview": "package main\n\nimport (\n\t\"os\"\n\n\t\"github.com/99designs/aws-vault/v7/cli\"\n\t\"github.com/alecthomas/kingpin/v2\"\n)\n\n// Version"
},
{
"path": "prompt/kdialog.go",
"chars": 431,
"preview": "package prompt\n\nimport (\n\t\"os/exec\"\n\t\"strings\"\n)\n\nfunc KDialogMfaPrompt(mfaSerial string) (string, error) {\n\tcmd := exec"
},
{
"path": "prompt/osascript.go",
"chars": 573,
"preview": "package prompt\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n\t\"strings\"\n)\n\nfunc OSAScriptMfaPrompt(mfaSerial string) (string, error) {\n\tcm"
},
{
"path": "prompt/prompt.go",
"chars": 512,
"preview": "package prompt\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n)\n\ntype Func func(string) (string, error)\n\nvar Methods = map[string]Func{}\n\nfunc"
},
{
"path": "prompt/terminal.go",
"chars": 813,
"preview": "package prompt\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/mattn/go-tty\"\n)\n\nfunc TerminalPrompt(message string) (string, e"
},
{
"path": "prompt/wincredui_windows.go",
"chars": 1558,
"preview": "package prompt\n\nimport (\n\t\"errors\"\n\t\"strings\"\n\t\"syscall\"\n\t\"unsafe\"\n)\n\nconst (\n\tCREDUI_FLAGS_ALWAYS_SHOW_UI = 0x0008"
},
{
"path": "prompt/ykman.go",
"chars": 1349,
"preview": "package prompt\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n)\n\n// YkmanProvider runs ykman to generate a OATH-TOT"
},
{
"path": "prompt/zenity.go",
"chars": 433,
"preview": "package prompt\n\nimport (\n\t\"os/exec\"\n\t\"strings\"\n)\n\nfunc ZenityMfaPrompt(mfaSerial string) (string, error) {\n\tcmd := exec."
},
{
"path": "server/ec2alias_bsd.go",
"chars": 395,
"preview": "//go:build darwin || freebsd || openbsd\n// +build darwin freebsd openbsd\n\npackage server\n\nimport \"os/exec\"\n\nfunc install"
},
{
"path": "server/ec2alias_linux.go",
"chars": 406,
"preview": "//go:build linux\n// +build linux\n\npackage server\n\nimport \"os/exec\"\n\nfunc installEc2EndpointNetworkAlias() ([]byte, error"
},
{
"path": "server/ec2alias_windows.go",
"chars": 1455,
"preview": "//go:build windows\n// +build windows\n\npackage server\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n\t\"strings\"\n)\n\nvar alreadyRegisteredLoca"
},
{
"path": "server/ec2proxy.go",
"chars": 1733,
"preview": "package server\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n)\n\nco"
},
{
"path": "server/ec2proxy_default.go",
"chars": 761,
"preview": "//go:build !darwin && !freebsd && !openbsd && !linux\n// +build !darwin,!freebsd,!openbsd,!linux\n\npackage server\n\nimport "
},
{
"path": "server/ec2proxy_unix.go",
"chars": 489,
"preview": "//go:build darwin || freebsd || openbsd || linux\n// +build darwin freebsd openbsd linux\n\npackage server\n\nimport (\n\t\"log\""
},
{
"path": "server/ec2server.go",
"chars": 3989,
"preview": "package server\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/99designs/aw"
},
{
"path": "server/ecsserver.go",
"chars": 4145,
"preview": "package server\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n"
},
{
"path": "server/httplog.go",
"chars": 626,
"preview": "package server\n\nimport (\n\t\"log\"\n\t\"net/http\"\n\t\"time\"\n)\n\ntype loggingMiddlewareResponseWriter struct {\n\thttp.ResponseWrite"
},
{
"path": "vault/assumeroleprovider.go",
"chars": 2519,
"preview": "package vault\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\t\"github.com/aws/aws-sdk-g"
},
{
"path": "vault/assumerolewithwebidentityprovider.go",
"chars": 2690,
"preview": "package vault\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\t\"github.com/aws/aws"
},
{
"path": "vault/cachedsessionprovider.go",
"chars": 1907,
"preview": "package vault\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\tststypes \"github.com/aws/aws-sdk"
},
{
"path": "vault/config.go",
"chars": 21353,
"preview": "package vault\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\tini \"gopkg.in/ini.v1\"\n)\n\ncon"
},
{
"path": "vault/config_test.go",
"chars": 17236,
"preview": "package vault_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/99designs/aws-vault/v7/vault\"\n\t\"g"
},
{
"path": "vault/credentialkeyring.go",
"chars": 1607,
"preview": "package vault\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/99designs/keyring\"\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n)\n\n"
},
{
"path": "vault/credentialprocessprovider.go",
"chars": 2848,
"preview": "package vault\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"reflect\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\tststypes \"gi"
},
{
"path": "vault/credentialprocessprovider_test.go",
"chars": 2800,
"preview": "package vault\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/aws"
},
{
"path": "vault/executeprocess.go",
"chars": 541,
"preview": "package vault\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"runtime\"\n)\n\nfunc executeProcess(process string) (string, error) {\n\tvar"
},
{
"path": "vault/federationtokenprovider.go",
"chars": 1708,
"preview": "package vault\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\t\"github.com/aws/aws-sdk-go-v2/se"
},
{
"path": "vault/getuser.go",
"chars": 1198,
"preview": "package vault\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\t\"github.com/aws/aws"
},
{
"path": "vault/keyringprovider.go",
"chars": 420,
"preview": "package vault\n\nimport (\n\t\"context\"\n\t\"log\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n)\n\n// KeyringProvider stores and retrieve"
},
{
"path": "vault/mfa.go",
"chars": 1257,
"preview": "package vault\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/99designs/aws-vault/v7/prompt\""
},
{
"path": "vault/oidctokenkeyring.go",
"chars": 2574,
"preview": "package vault\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/99designs/keyring\"\n\t\"github.com/"
},
{
"path": "vault/sessionkeyring.go",
"chars": 6591,
"preview": "package vault\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"git"
},
{
"path": "vault/sessionkeyring_test.go",
"chars": 746,
"preview": "package vault_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/99designs/aws-vault/v7/vault\"\n)\n\nfunc TestIsSessionKey(t *testing."
},
{
"path": "vault/sessiontokenprovider.go",
"chars": 1688,
"preview": "package vault\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"time\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\t\"github.com/aws/aws-sdk-go-v2/se"
},
{
"path": "vault/ssorolecredentialsprovider.go",
"chars": 6512,
"preview": "package vault\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/99designs/keyring\"\n\t\""
},
{
"path": "vault/stsendpointresolver.go",
"chars": 1256,
"preview": "package vault\n\nimport (\n\t\"log\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\t\"github.com/aws/aws-sdk-go-v2/service/sts\"\n)\n\n// ge"
},
{
"path": "vault/vault.go",
"chars": 12188,
"preview": "package vault\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/99designs/keyring\"\n\t\"github.com/aws/aws-sdk"
},
{
"path": "vault/vault_test.go",
"chars": 3286,
"preview": "package vault_test\n\nimport (\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/99designs/aws-vault/v7/vault\"\n\t\"github.com/99designs/keyring"
}
]
About this extraction
This page contains the full source code of the 99designs/aws-vault GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 78 files (232.0 KB), approximately 67.9k tokens, and a symbol index with 302 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.