[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n- [ ] I am using the latest release of AWS Vault\n- [ ] I have provided my `.aws/config` (redacted if necessary)\n- [ ] I have provided the debug output using `aws-vault --debug` (redacted if necessary)\n"
  },
  {
    "path": ".github/workflows/go.yml",
    "content": "name: Continuous Integration\non:\n  push:\n  pull_request:\n    branches:\n      - master\npermissions:\n  contents: read\n\njobs:\n  test:\n    name: test\n    strategy:\n      matrix:\n        os: [ubuntu-latest, macos-latest]\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/setup-go@v3\n        with:\n          go-version: '1.20'\n      - uses: actions/checkout@v3\n      - name: Run tests\n        run: go test -race ./...\n  lint:\n    permissions:\n      contents: read  # for actions/checkout to fetch code\n      pull-requests: read  # for golangci/golangci-lint-action to fetch pull requests\n    name: lint\n    strategy:\n      matrix:\n        os: [macos-latest, ubuntu-latest]\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/setup-go@v3\n        with:\n          go-version: '1.20'\n      - uses: actions/checkout@v3\n      - name: golangci-lint\n        uses: golangci/golangci-lint-action@v3.4.0\n        with:\n          version: v1.52.0\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "# See https://github.com/actions/stale\nname: Mark and close stale issues\non:\n  schedule:\n    - cron: '15 10 * * *'\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n    steps:\n      - uses: actions/stale@v7\n        with:\n          days-before-stale: 180\n          days-before-close: 7\n          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.'\n          exempt-issue-labels: pinned,security,feature\n"
  },
  {
    "path": ".gitignore",
    "content": "/aws-vault\n/aws-vault-*\n/SHA256SUMS\n"
  },
  {
    "path": ".golangci.yml",
    "content": "linters:\n  enable:\n    - bodyclose\n    - contextcheck\n    - depguard\n    - durationcheck\n    - dupl\n    - errchkjson\n    - errname\n    - exhaustive\n    - exportloopref\n    - gofmt\n    - goimports\n    - makezero\n    - misspell\n    - nakedret\n    - nilerr\n    - nilnil\n    - noctx\n    - prealloc\n    - revive\n    # - rowserrcheck\n    - thelper\n    - tparallel\n    - unconvert\n    - unparam\n    # - wastedassign\n    - whitespace\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 99designs\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "Makefile",
    "content": "VERSION=$(shell git describe --tags --candidates=1 --dirty)\nBUILD_FLAGS=-ldflags=\"-X main.Version=$(VERSION)\" -trimpath\nCERT_ID ?= Developer ID Application: 99designs Inc (NRM9HVJ62Z)\nSRC=$(shell find . -name '*.go') go.mod\nINSTALL_DIR ?= ~/bin\n.PHONY: binaries clean release install\n\nifeq ($(shell uname), Darwin)\naws-vault: $(SRC)\n\tgo build -ldflags=\"-X main.Version=$(VERSION)\" -o $@ .\n\tcodesign --options runtime --timestamp --sign \"$(CERT_ID)\" $@\nelse\naws-vault: $(SRC)\n\tgo build -ldflags=\"-X main.Version=$(VERSION)\" -o $@ .\nendif\n\ninstall: aws-vault\n\tmkdir -p $(INSTALL_DIR)\n\trm -f $(INSTALL_DIR)/aws-vault\n\tcp -a ./aws-vault $(INSTALL_DIR)/aws-vault\n\nbinaries: 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\ndmgs: aws-vault-darwin-amd64.dmg aws-vault-darwin-arm64.dmg\n\nclean:\n\trm -f ./aws-vault ./aws-vault-*-* ./SHA256SUMS\n\nrelease: binaries dmgs SHA256SUMS\n\n\t@echo \"\\nTo create a new release run:\\n\\n    gh release create --title $(VERSION) $(VERSION) \\\n\taws-vault-darwin-amd64.dmg \\\n\taws-vault-darwin-arm64.dmg \\\n\taws-vault-freebsd-amd64 \\\n\taws-vault-linux-amd64 \\\n\taws-vault-linux-arm64 \\\n\taws-vault-linux-arm7 \\\n\taws-vault-linux-ppc64le \\\n\taws-vault-windows-386.exe \\\n\taws-vault-windows-arm64.exe \\\n\tSHA256SUMS\\n\"\n\n\t@echo \"\\nTo update homebrew-cask run:\\n\\n    brew bump-cask-pr --version $(shell echo $(VERSION) | sed 's/v\\(.*\\)/\\1/') aws-vault\\n\"\n\naws-vault-darwin-amd64: $(SRC)\n\tGOOS=darwin GOARCH=amd64 CGO_ENABLED=1 SDKROOT=$(shell xcrun --sdk macosx --show-sdk-path) go build $(BUILD_FLAGS) -o $@ .\n\naws-vault-darwin-arm64: $(SRC)\n\tGOOS=darwin GOARCH=arm64 CGO_ENABLED=1 SDKROOT=$(shell xcrun --sdk macosx --show-sdk-path) go build $(BUILD_FLAGS) -o $@ .\n\naws-vault-freebsd-amd64: $(SRC)\n\tGOOS=freebsd GOARCH=amd64 go build $(BUILD_FLAGS) -o $@ .\n\naws-vault-linux-amd64: $(SRC)\n\tGOOS=linux GOARCH=amd64 go build $(BUILD_FLAGS) -o $@ .\n\naws-vault-linux-arm64: $(SRC)\n\tGOOS=linux GOARCH=arm64 go build $(BUILD_FLAGS) -o $@ .\n\naws-vault-linux-ppc64le: $(SRC)\n\tGOOS=linux GOARCH=ppc64le go build $(BUILD_FLAGS) -o $@ .\n\naws-vault-linux-arm7: $(SRC)\n\tGOOS=linux GOARCH=arm GOARM=7 go build $(BUILD_FLAGS) -o $@ .\n\naws-vault-windows-386.exe: $(SRC)\n\tGOOS=windows GOARCH=386 go build $(BUILD_FLAGS) -o $@ .\n\naws-vault-windows-arm64.exe: $(SRC)\n\tGOOS=windows GOARCH=arm64 go build $(BUILD_FLAGS) -o $@ .\n\naws-vault-darwin-amd64.dmg: aws-vault-darwin-amd64\n\t./bin/create-dmg aws-vault-darwin-amd64 $@\n\naws-vault-darwin-arm64.dmg: aws-vault-darwin-arm64\n\t./bin/create-dmg aws-vault-darwin-arm64 $@\n\nSHA256SUMS: binaries dmgs\n\tshasum -a 256 \\\n\t  aws-vault-darwin-amd64.dmg \\\n\t  aws-vault-darwin-arm64.dmg \\\n\t  aws-vault-freebsd-amd64 \\\n\t  aws-vault-linux-amd64 \\\n\t  aws-vault-linux-arm64 \\\n\t  aws-vault-linux-arm7 \\\n\t  aws-vault-linux-ppc64le \\\n\t  aws-vault-windows-386.exe \\\n\t  aws-vault-windows-arm64.exe \\\n\t    > $@\n"
  },
  {
    "path": "README.md",
    "content": "# AWS Vault\n\n[![Downloads](https://img.shields.io/github/downloads/99designs/aws-vault/total.svg)](https://github.com/99designs/aws-vault/releases)\n[![Continuous Integration](https://github.com/99designs/aws-vault/workflows/Continuous%20Integration/badge.svg)](https://github.com/99designs/aws-vault/actions)\n\n> [!WARNING]\n> This project has been abandoned and it's not receiving any more updates. If you want to continue to receive updates\n> or contribute, please feel free to look at the active fork at: https://github.com/ByteNess/aws-vault\n\nAWS Vault is a tool to securely store and access AWS credentials in a development environment.\n\nAWS 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).\n\nCheck out the [announcement blog post](https://99designs.com.au/tech-blog/blog/2015/10/26/aws-vault/) for more details.\n\n## Installing\n\nYou can install AWS Vault:\n- by downloading the [latest release](https://github.com/99designs/aws-vault/releases/latest)\n- on macOS with [Homebrew](https://formulae.brew.sh/formula/aws-vault): `brew install aws-vault`\n- on macOS with [MacPorts](https://ports.macports.org/port/aws-vault/summary): `port install aws-vault`\n- on Windows with [Chocolatey](https://chocolatey.org/packages/aws-vault): `choco install aws-vault`\n- on Windows with [Scoop](https://scoop.sh/): `scoop install aws-vault`\n- on Linux with [Homebrew on Linux](https://formulae.brew.sh/formula/aws-vault): `brew install aws-vault`\n- on [Arch Linux](https://www.archlinux.org/packages/community/x86_64/aws-vault/): `pacman -S aws-vault`\n- 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))\n- on [FreeBSD](https://www.freshports.org/security/aws-vault/): `pkg install aws-vault`\n- on [OpenSUSE](https://software.opensuse.org/package/aws-vault): enable devel:languages:go repo then `zypper install aws-vault`\n- with [Nix](https://search.nixos.org/packages?show=aws-vault&query=aws-vault): `nix-env -i aws-vault`\n- 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>`\n\n## Documentation\n\nConfig, usage, tips and tricks are available in the [USAGE.md](./USAGE.md) file.\n\n## Vaulting Backends\n\nThe supported vaulting backends are:\n\n* [macOS Keychain](https://support.apple.com/en-au/guide/keychain-access/welcome/mac)\n* [Windows Credential Manager](https://support.microsoft.com/en-au/help/4026814/windows-accessing-credential-manager)\n* Secret Service ([Gnome Keyring](https://wiki.gnome.org/Projects/GnomeKeyring), [KWallet](https://kde.org/applications/system/org.kde.kwalletmanager5))\n* [KWallet](https://kde.org/applications/system/org.kde.kwalletmanager5)\n* [Pass](https://www.passwordstore.org/)\n* Encrypted file\n\nUse the `--backend` flag or `AWS_VAULT_BACKEND` environment variable to specify.\n\n## Quick start\n\n```shell\n# Store AWS credentials for the \"jonsmith\" profile\n$ aws-vault add jonsmith\nEnter Access Key Id: ABDCDEFDASDASF\nEnter Secret Key: %%%\n\n# Execute a command (using temporary credentials)\n$ aws-vault exec jonsmith -- aws s3 ls\nbucket_1\nbucket_2\n\n# open a browser window and login to the AWS Console\n$ aws-vault login jonsmith\n\n# List credentials\n$ aws-vault list\nProfile                  Credentials              Sessions\n=======                  ===========              ========\njonsmith                 jonsmith                 -\n\n# Start a subshell with temporary credentials\n$ aws-vault exec jonsmith\nStarting subshell /bin/zsh, use `exit` to exit the subshell\n$ aws s3 ls\nbucket_1\nbucket_2\n```\n\n## How it works\n\n`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.\n\nAWS Vault then exposes the temporary credentials to the sub-process in one of two ways\n\n1. **Environment variables** are written to the sub-process. Notice in the below example how the AWS credentials get written out\n   ```shell\n   $ aws-vault exec jonsmith -- env | grep AWS\n   AWS_VAULT=jonsmith\n   AWS_DEFAULT_REGION=us-east-1\n   AWS_REGION=us-east-1\n   AWS_ACCESS_KEY_ID=%%%\n   AWS_SECRET_ACCESS_KEY=%%%\n   AWS_SESSION_TOKEN=%%%\n   AWS_CREDENTIAL_EXPIRATION=2020-04-16T11:16:27Z\n   ```\n2. **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.\n   ```shell\n   $ aws-vault exec --server jonsmith -- env | grep AWS\n   AWS_VAULT=jonsmith\n   AWS_DEFAULT_REGION=us-east-1\n   AWS_REGION=us-east-1\n   AWS_CONTAINER_CREDENTIALS_FULL_URI=%%%\n   AWS_CONTAINER_AUTHORIZATION_TOKEN=%%%\n   ```\n\nThe 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.\n\n## Roles and MFA\n\n[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.\n\nFirst 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).\n\nHere's an example configuration using roles and MFA:\n\n```ini\n[default]\nregion = us-east-1\n\n[profile jonsmith]\nmfa_serial = arn:aws:iam::111111111111:mfa/jonsmith\n\n[profile foo-readonly]\nsource_profile = jonsmith\nrole_arn = arn:aws:iam::22222222222:role/ReadOnly\n\n[profile foo-admin]\nsource_profile = jonsmith\nrole_arn = arn:aws:iam::22222222222:role/Administrator\nmfa_serial = arn:aws:iam::111111111111:mfa/jonsmith\n\n[profile bar-role1]\nsource_profile = jonsmith\nrole_arn = arn:aws:iam::333333333333:role/Role1\nmfa_serial = arn:aws:iam::111111111111:mfa/jonsmith\n\n[profile bar-role2]\nsource_profile = bar-role1\nrole_arn = arn:aws:iam::333333333333:role/Role2\nmfa_serial = arn:aws:iam::111111111111:mfa/jonsmith\n```\n\nHere's what you can expect from aws-vault\n\n| Command                                  | Credentials                 | Cached        | MFA |\n|------------------------------------------|-----------------------------|---------------|-----|\n| `aws-vault exec jonsmith --no-session`   | Long-term credentials       | No            | No  |\n| `aws-vault exec jonsmith`                | session-token               | session-token | Yes |\n| `aws-vault exec foo-readonly`            | role                        | No            | No  |\n| `aws-vault exec foo-admin`               | session-token + role        | session-token | Yes |\n| `aws-vault exec foo-admin --duration=2h` | role                        | role          | Yes |\n| `aws-vault exec bar-role2`               | session-token + role + role | session-token | Yes |\n| `aws-vault exec bar-role2 --no-session`  | role + role                 | role          | Yes |\n\n## Development\n\nThe [macOS release builds](https://github.com/99designs/aws-vault/releases) are code-signed to avoid extra prompts in Keychain. You can verify this with:\n```shell\n$ codesign --verify --verbose $(which aws-vault)\n```\n\nIf 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:\n```shell\n$ go build .\n$ codesign --sign <Name of certificate created above> ./aws-vault\n```\n\n## References and Inspiration\n\n * https://github.com/pda/aws-keychain\n * https://docs.aws.amazon.com/IAM/latest/UserGuide/MFAProtectedAPI.html\n * https://docs.aws.amazon.com/IAM/latest/UserGuide/IAMBestPractices.html#create-iam-users\n * https://github.com/makethunder/awsudo\n * https://github.com/AdRoll/hologram\n * https://github.com/realestate-com-au/credulous\n * https://github.com/dump247/aws-mock-metadata\n * https://boto.readthedocs.org/en/latest/boto_config_tut.html\n"
  },
  {
    "path": "USAGE.md",
    "content": "# Usage\n\n- [Usage](#usage)\n  - [Getting Help](#getting-help)\n  - [Typical use-cases for aws-vault](#typical-use-cases-for-aws-vault)\n    - [Use-case 1: aws-vault is the executor and provides the environment](#use-case-1-aws-vault-is-the-executor-and-provides-the-environment)\n    - [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)\n    - [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)\n    - [Use-case 4: aws-vault caches alternative credential sources](#use-case-4-aws-vault-caches-alternative-credential-sources)\n  - [Config](#config)\n    - [AWS config file](#aws-config-file)\n      - [`include_profile`](#include_profile)\n      - [`session_tags` and `transitive_session_tags`](#session_tags-and-transitive_session_tags)\n      - [`source_identity`](#source_identity)\n      - [`mfa_process`](#mfa_process)\n    - [Environment variables](#environment-variables)\n  - [Backends](#backends)\n    - [Keychain](#keychain)\n  - [Managing credentials](#managing-credentials)\n    - [Using multiple profiles](#using-multiple-profiles)\n    - [Listing profiles and credentials](#listing-profiles-and-credentials)\n    - [Removing credentials](#removing-credentials)\n    - [Rotating credentials](#rotating-credentials)\n  - [Managing Sessions](#managing-sessions)\n    - [Executing a command](#executing-a-command)\n    - [Logging into AWS console](#logging-into-aws-console)\n    - [Removing stored sessions](#removing-stored-sessions)\n    - [Using --no-session](#using---no-session)\n    - [Session duration](#session-duration)\n    - [Using `--server`](#using---server)\n      - [`--ec2-server`](#--ec2-server)\n      - [`--ecs-server`](#--ecs-server)\n    - [Temporary credentials limitations with STS, IAM](#temporary-credentials-limitations-with-sts-iam)\n  - [MFA](#mfa)\n    - [Gotchas with MFA config](#gotchas-with-mfa-config)\n  - [Single Sign On (SSO)](#single-sign-on-sso)\n  - [Assuming roles with web identities](#assuming-roles-with-web-identities)\n  - [Using `credential_process`](#using-credential_process)\n    - [Invoking `aws-vault` via `credential_process`](#invoking-aws-vault-via-credential_process)\n    - [Invoking `credential_process` via `aws-vault`](#invoking-credential_process-via-aws-vault)\n  - [Using a Yubikey](#using-a-yubikey)\n    - [Prerequisites](#prerequisites)\n    - [Setup](#setup)\n    - [Usage](#usage-1)\n  - [Shell completion](#shell-completion)\n  - [Desktop apps](#desktop-apps)\n  - [Docker](#docker)\n\n\n## Getting Help\n\nContext-sensitive help is available for every command in `aws-vault`.\n\n```shell\n# Show general help about aws-vault\n$ aws-vault --help\n\n# Show longer help about all options in aws-vault\n$ aws-vault --help-long\n\n# Show the most detailed information about the exec command\n$ aws-vault exec --help\n```\n\n## Typical use-cases for aws-vault\n\nThere are a few different ways aws-vault can be used\n\n### Use-case 1: aws-vault is the executor and provides the environment\n\nUse aws-vault exclusively as a command executor, where aws-vault provides the environment and runs a command.\n\n```ini\n; master creds added with 'aws-vault add my_profile_master'\n[profile my_profile_master]\n\n[profile my_profile_role]\nsource_profile=my_profile_master\nrole_arn=xxx\n```\n\n```bash\naws-vault exec my_profile_master ./my-command   # success, uses sts session generated by aws-vault\naws-vault exec my_profile_role ./my-command     # success, uses role creds generated by aws-vault\n\nAWS_PROFILE=my_profile_master ./my-command      # Not expected to be functional\nAWS_PROFILE=my_profile_role ./my-command        # Not expected to be functional\n```\n\nIn this scenario, the profile name and aws config is used exclusively by aws-vault, which provides the environment for the command to run in.\n\nThis 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.\n\n\n### Use-case 2: aws-vault is a \"master credentials vault\" for AWS SDK\n\naws-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`\n\n```ini\n; master creds added with 'aws-vault add my_profile_master'\n[profile my_profile_master]\ncredential_process = aws-vault export --format=json --no-session my_profile_master\n\n[profile my_profile_role]\nsource_profile=my_profile_master\nrole_arn=xxx\n```\n\n```bash\naws-vault exec my_profile_master ./my-command   # success (uses master creds)\naws-vault exec my_profile_role ./my-command     # success (aws-vault role)\n\nAWS_PROFILE=my_profile_master ./my-command      # success (uses credential_process to get aws-vault master creds)\nAWS_PROFILE=my_profile_role ./my-command       # success (SDK role)\n```\n\n### Use-case 3: aws-vault is a \"MFA session cache\" for AWS SDK\n\nVery 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\n\n```ini\n; master creds added with 'aws-vault add my_profile_master'\n[profile my_profile_master]\nmfa_serial=mmm\ncredential_process = aws-vault export --format=json my_profile_master\n\n[profile my_profile_role]\nsource_profile=my_profile_master\nmfa_serial=mmm\nrole_arn=xxx1\n\n[profile my_profile_role2]\nsource_profile=my_profile_master\nmfa_serial=mmm\nrole_arn=xxx2\n```\n\n```bash\naws-vault exec my_profile_master ./my-command   # success (STS session)\naws-vault exec my_profile_role ./my-command     # success (role)\n\nAWS_PROFILE=my_profile_master ./my-command      # success (uses credential_process to get aws-vault session)\nAWS_PROFILE=my_profile_role ./my-command        # success (uses aws-vault session + SDK role)\n```\n\n\n### Use-case 4: aws-vault caches alternative credential sources\n\naws-vault caches credentials from alternative credential sources like `sso_start_url`, `web_identity_token_process`, `credential_process`\n\n```ini\n[profile my_profile_using_sso]\nsso_start_url = https://mycompany.awsapps.com/start\n\n[profile my_profile_using_process]\ncredential_process = my-custom-creds-cmd\n```\n\n```bash\naws-vault exec my_profile_using_sso ./my-command       # success, uses aws-vault caching\naws-vault exec my_profile_using_process ./my-command   # success, uses aws-vault caching\n\nAWS_PROFILE=my_profile_using_sso ./my-command          # success, no caching\nAWS_PROFILE=my_profile_using_process ./my-command      # success, no caching\n```\n\n\n## Config\n\n### AWS config file\n\naws-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).\n\n#### `include_profile`\n\n(Note: aws-vault v5 calls this `parent_profile`)\n\nAWS 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.\n\nThis is a flexible mechanism for more complex configurations.\n\nFor 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`.\n\n```ini\n; The \"common\" profile here operates as a \"config fragment\" rather than a profile\n[profile common]\nregion=eu-west-1\nmfa_serial=arn:aws:iam::123456789:mfa/johnsmith\nsource_profile = root\n\n[profile root]\ninclude_profile = common\n\n[profile order-dev]\ninclude_profile = common\nrole_arn=arn:aws:iam::123456789:role/developers\n\n[profile order-staging-admin]\ninclude_profile = common\nrole_arn=arn:aws:iam::123456789:role/administrators\n```\n\nOr 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`\n```ini\n; The \"root\" profile here operates as a profile, a config fragment as well as a source_profile\n[profile root]\nregion=eu-west-1\nmfa_serial=arn:aws:iam::123456789:mfa/johnsmith\nsource_profile = root\n\n[profile order-dev]\ninclude_profile = root\nrole_arn=arn:aws:iam::123456789:role/developers\n\n[profile order-staging-admin]\ninclude_profile = root\nrole_arn=arn:aws:iam::123456789:role/administrators\n```\n\n#### `session_tags` and `transitive_session_tags`\n\nIt 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:\n\n```ini\n[profile root]\nregion=eu-west-1\n\n[profile order-dev]\nsource_profile = root\nrole_arn=arn:aws:iam::123456789:role/developers\nsession_tags = key1=value1,key2=value2,key3=value3\ntransitive_session_tags = key1,key2\n```\n\n#### `source_identity`\n\nIt 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.\n\n```ini\n[profile root]\nregion=eu-west-1\n\n[profile order-dev]\nsource_profile = root\nrole_arn=arn:aws:iam::123456789:role/developers\nsource_identity=your_user_name\n```\n\n#### `mfa_process`\nIf 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.\n\nFor example, to use `pass` to retrieve an MFA token from a password store entry, you could use the following:\n\n```ini\n[profile foo]\nmfa_serial=arn:aws:iam::123456789:mfa/johnsmith\nmfa_process=pass otp my_aws_mfa\n```\n\nOr another example using 1Password\n\n```ini\n[profile foo]\nmfa_serial=arn:aws:iam::123456789:mfa/johnsmith\nmfa_process=op item get my_aws_mfa --otp\n```\n\nWARNING: Use of this option runs against security best practices. It is recommended that you use a dedicated MFA device.\n\n### Environment variables\n\nTo configure the default flag values of `aws-vault` and its subcommands:\n* `AWS_VAULT_BACKEND`: Secret backend to use (see the flag `--backend`)\n* `AWS_VAULT_KEYCHAIN_NAME`: Name of macOS keychain to use (see the flag `--keychain`)\n* `AWS_VAULT_PROMPT`: Prompt driver to use (see the flag `--prompt`)\n* `AWS_VAULT_PASS_PASSWORD_STORE_DIR`: Pass password store directory (see the flag `--pass-dir`)\n* `AWS_VAULT_PASS_CMD`: Name of the pass executable (see the flag `--pass-cmd`)\n* `AWS_VAULT_PASS_PREFIX`: Prefix to prepend to the item path stored in pass (see the flag `--pass-prefix`)\n* `AWS_VAULT_FILE_DIR`: Directory for the \"file\" password store (see the flag `--file-dir`)\n* `AWS_VAULT_FILE_PASSPHRASE`: Password for the \"file\" password store\n* `AWS_CONFIG_FILE`: The location of the AWS config file\n\nTo override the AWS config file (used in the `exec`, `login` and `rotate` subcommands):\n* `AWS_REGION`: The AWS region\n* `AWS_DEFAULT_REGION`: The AWS region, applied only if `AWS_REGION` isn't set\n* `AWS_STS_REGIONAL_ENDPOINTS`: STS endpoint resolution logic, must be \"regional\" or \"legacy\"\n* `AWS_MFA_SERIAL`: The identification number of the MFA device to use\n* `AWS_ROLE_ARN`: Specifies the ARN of an IAM role in the active profile\n* `AWS_ROLE_SESSION_NAME`: Specifies the name to attach to the role session in the active profile\n\nTo override session durations (used in `exec` and `login`):\n* `AWS_SESSION_TOKEN_TTL`: Expiration time for the `GetSessionToken` credentials. Defaults to 1h\n* `AWS_CHAINED_SESSION_TOKEN_TTL`: Expiration time for the `GetSessionToken` credentials when chaining profiles. Defaults to 8h\n* `AWS_ASSUME_ROLE_TTL`: Expiration time for the `AssumeRole` credentials. Defaults to 1h\n* `AWS_FEDERATION_TOKEN_TTL`: Expiration time for the `GetFederationToken` credentials. Defaults to 1h\n* `AWS_MIN_TTL`: The minimum expiration time allowed for a credential. Defaults to 5m\n\nNote that the session durations above expect a unit after the number (e.g. 12h or 43200s).\n\nTo override or set session tagging (used in `exec`):\n* `AWS_SESSION_TAGS`: Comma separated key-value list of tags passed with the `AssumeRole` call, overrides `session_tags` profile config variable\n* `AWS_TRANSITIVE_TAGS`: Comma separated list of transitive tags passed with the `AssumeRole` call, overrides `transitive_session_tags` profile config variable\n\nTo override or set the source identity (used in `exec` and `login`):\n* `AWS_SOURCE_IDENTITY`: Specifies the source identity for assumed role sessions\n\n## Backends\n\nYou 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.\n\n### Keychain\n\nIf 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:\n\n1. Open \"Keychain Access\"\n2. 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`.\n3. Right click on aws-vault keychain, and select \"Change Settings for Keychain 'aws-vault\"\n4. Update \"Lock after X minutes of inactivity\" to your desired value.\n5. Hit save.\n\n![keychain-image](https://imgur.com/ARkr5Ba.png)\n\n\n## Managing credentials\n\n### Using multiple profiles\n\nIn 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.\n\n```shell\n# Store AWS credentials for the \"home\" profile\n$ aws-vault add home\nEnter Access Key Id: ABDCDEFDASDASF\nEnter Secret Key: %\n\n# Execute a command using temporary credentials\n$ aws-vault exec home -- aws s3 ls\nbucket_1\nbucket_2\n\n# store credentials for the \"work\" profile\n$ aws-vault add work\nEnter Access Key Id: ABDCDEFDASDASF\nEnter Secret Key: %\n\n# Execute a command using temporary credentials\n$ aws-vault exec work -- aws s3 ls\nanother_bucket\n```\n\nHere 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.\n\n```ini\n[default]\nregion = us-east-1\n\n[profile home]\nmfa_serial = arn:aws:iam::111111111111:mfa/home-account\n\n[profile work]\nmfa_serial = arn:aws:iam::111111111111:mfa/work-account\nrole_arn = arn:aws:iam::111111111111:role/ReadOnly\n\n[profile work-admin]\nrole_arn = arn:aws:iam::111111111111:role/Administrator\nsource_profile = work\n```\n\n### Listing profiles and credentials\n\nYou can use the `aws-vault list` command to list out the defined profiles, and any session associated with them.\n\n```shell\n$ aws-vault list\nProfile                  Credentials              Sessions\n=======                  ===========              ========\nhome                     home\nwork                     work                     1525456570\nwork-read-only           work\nwork-admin               work\n```\n\n### Removing credentials\n\nThe `aws-vault remove` command can be used to remove credentials. It works similarly to the `aws-vault add` command.\n\n```shell\n# Remove AWS credentials for the \"work\" profile\n$ aws-vault remove work\nDelete credentials for profile \"work\"? (y|N) y\nDeleted credentials.\n```\n\n### Rotating credentials\n\nRegularly 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.\n\nThe minimal IAM policy required to rotate your own credentials is:\n\n```json\n{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"iam:CreateAccessKey\",\n                \"iam:DeleteAccessKey\",\n                \"iam:GetUser\"\n            ],\n            \"Resource\": [\n                \"arn:aws:iam::*:user/${aws:username}\"\n            ]\n        }\n    ]\n}\n```\n\n\n## Managing Sessions\n\n### Executing a command\n\nRunning `aws-vault exec` will run a command with AWS credentials.\n\nWhen using exec, you may find it useful to use the builtin `--` feature in bash, zsh and other POSIX shells. For example\n```shell\naws-vault exec myprofile -- aws s3 ls\n```\nUsing `--` signifies the end of the `aws-vault` options, and allows the shell autocomplete to kick in and offer autocompletions for the proceeding command.\n\nIf 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.\n\n### Logging into AWS console\n\nYou can use the `aws-vault login` command to open a browser window and login to AWS Console for a given account:\n```shell\n$ aws-vault login work\n```\n\nIf you have credentials already available in your environment, aws-vault will use these credentials to sign you in to the AWS console.\n\n```shell\n$ export AWS_ACCESS_KEY_ID=%%%\n$ export AWS_SECRET_ACCESS_KEY=%%%\n$ export AWS_SESSION_TOKEN=%%%\n$ aws-vault login\n```\n\n### Removing stored sessions\n\nIf you want to remove sessions managed by `aws-vault` before they expire, you can do this with `aws-vault clear` command.\n\nYou can also specify a profile to remove sessions for this profile only.\n```shell\naws-vault clear [profile]\n```\n\n### Using --no-session\n\nAWS 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.\n\nIf you wish to skip the `GetSessionToken` call, you can use the `--no-session` flag.\n\nHowever, 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\n```shell\naws-vault exec <iam_user_profile> -- env | grep AWS\n```\nYou'll see an `AWS_ACCESS_KEY_ID` of the form `ASIAxxxxxx` which is a temporary one. Doing\n```shell\naws-vault exec <iam_user_profile> --no-session -- env | grep AWS\n```\nYou'll see your IAM user `AWS_ACCESS_KEY_ID` of the form `AKIAxxxxx` directly exposed, as well as the corresponding `AWS_SECRET_KEY_ID`.\n\n\n### Session duration\n\nIf 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:\n```\naws-vault: error: Failed to get credentials for default: ValidationError: The requested DurationSeconds exceeds the MaxSessionDuration set for this role.\n        status code: 400, request id: aa58fa50-4a5e-11e9-9566-293ea5c350ee\n```\n\nFor that reason, AWS Vault will not use `GetSessionToken` if `--duration` or the role's `duration_seconds` is longer than 1h.\n\n### Using `--server`\n\nThere 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.\n\nAWS 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.\n\n#### `--ec2-server`\n\nThis 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!\n\nTo 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.\n\n#### `--ecs-server`\n\nThe ECS Credential provider binds to a random, ephemeral port and requires an authorization token, which offers the following advantages over the EC2 Metadata provider:\n 1. Does not require root/administrator privileges\n 2. Allows multiple providers simultaneously for discrete processes\n 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\n\nHowever, 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).\n\nThe 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).\n\n### Temporary credentials limitations with STS, IAM\n\nWhen 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.\n\n```shell\n$ aws-vault exec <iam_user_profile> -- aws iam get-user\nAn error occurred (InvalidClientTokenId) when calling the GetUser operation: The security token included in the request is invalid\n```\n\nFor 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`.\n\n\n## MFA\n\nTo 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.\n\nAWS 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.\n\n```ini\n[profile tom]\nmfa_serial = arn:aws:iam::111111111111:mfa/tom\n\n[profile role1]\nsource_profile = tom\nrole_arn = arn:aws:iam::22222222222:role/role1\nmfa_serial = arn:aws:iam::111111111111:mfa/tom\n\n[profile role2]\nsource_profile = tom\nrole_arn = arn:aws:iam::33333333333:role/role2\nmfa_serial = arn:aws:iam::111111111111:mfa/tom\n```\n\nBe 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`.\n\nYou can also set the `mfa_serial` with the environment variable `AWS_MFA_SERIAL`.\n\n### Gotchas with MFA config\n\naws-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.\n\naws-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:\n\n```ini\n[profile jon]\nmfa_serial = arn:aws:iam::111111111111:mfa/jon\nsource_profile=jon\n\n[profile role1]\nrole_arn = arn:aws:iam::22222222222:role/role1\ninclude_profile = jon\n\n[profile role2]\nrole_arn = arn:aws:iam::33333333333:role/role2\ninclude_profile = jon\n```\n\n## Single Sign On (SSO)\n\n_AWS IAM Identity Center provides single sign on, and was previously known as AWS SSO._\n\nIf 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:\n* `sso_session` Name of the `[sso-session]` section in the same file with the common options, or:\n* `sso_start_url` The URL that points to the organization's AWS IAM Identity Center user portal.\n* `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.\n* `sso_account_id` The AWS account ID that contains the IAM role that you want to use with this profile.\n* `sso_role_name` The name of the Identity Center Permission Group that defines the user's permissions when using this profile.\n\nHere is an example configuration using AWS IAM Identity Center for single sign on.\n\n```ini\n[profile Administrator-123456789012]\nsso_start_url=https://aws-sso-portal.awsapps.com/start\nsso_region=eu-west-1\nsso_account_id=123456789012\nsso_role_name=Administrator\n```\n\n## Assuming roles with web identities\n\nAWS 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:\n* `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.\n* `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`.\n\nAn example configuration using a static token:\n\n```ini\n[profile role1]\nrole_arn = arn:aws:iam::22222222222:role/role1\nweb_identity_token_file = /path/to/token.txt\n```\n\nAn example using a token generated by an external command:\n\n```ini\n[profile role2]\nrole_arn = arn:aws:iam::33333333333:role/role2\nweb_identity_token_process = oidccli raw\n```\n\n## Using `credential_process`\n\nThe [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`.\n\n### Invoking `aws-vault` via `credential_process`\n\n```ini\n[profile home]\ncredential_process = aws-vault export --format=json home\n```\n\nIf `mfa_serial` is set, please define the prompt driver (for example `osascript` for macOS), else the prompt will not show up.\n\n```ini\n[profile work]\nmfa_serial = arn:aws:iam::123456789012:mfa/jonsmith\ncredential_process = aws-vault --prompt=osascript export --format=json work\n```\n\nNote 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:\n\n```ini\n[profile jon]\ncredential_process = aws-vault export --no-session --format=json jon\n\n[profile work]\nmfa_serial = arn:aws:iam::123456789012:mfa/jonsmith\nrole_arn = arn:aws:iam::33333333333:role/role2\nsource_profile = jon\n```\n\nIf 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.\n\n### Invoking `credential_process` via `aws-vault`\n\nWhen 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`.\n\n## Using a Yubikey\n\nYubikeys 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.\n\n### Prerequisites\n 1. [A Yubikey that supports OATH-TOTP](https://support.yubico.com/support/solutions/articles/15000006419-using-your-yubikey-with-authenticator-codes)\n 2. `ykman`, the [YubiKey Manager CLI](https://github.com/Yubico/yubikey-manager) tool.\n\nYou can verify these prerequisites by running `ykman info` and checking `OATH` is enabled.\n\n### Setup\n 1. Log into the AWS web console with your IAM user credentials, and navigate to  _My Security Credentials_\n 2. Under _Multi-factor authentication (MFA)_, click `Manage MFA device` and add a Virtual MFA device\n 3. Instead of showing the QR code, click on `Show secret key` and copy the key.\n 4. On a command line, run:\n    ```shell\n    ykman oath accounts add -t arn:aws:iam::${ACCOUNT_ID}:mfa/${MFA_DEVICE_NAME}\n    ```\n    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.\n 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`.\n\nA 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.\n\nIn 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.\n\nNote 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.\n\n### Usage\nUsing the `ykman` prompt driver, aws-vault will execute `ykman` to generate tokens for any profile in your `.aws/config` using an `mfa_device`.\n```shell\naws-vault exec --prompt ykman ${AWS_VAULT_PROFILE_USING_MFA} -- aws s3 ls\n```\n\nAn 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:\n\n(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))\n\n```ini\n[profile jon]\nmfa_serial = arn:aws:iam::123456789012:mfa/jonsmith\nmfa_process = ykman oath accounts code --single arn:aws:iam::123456789012:mfa/jonsmith\n```\n\nFurther config:\n - `AWS_VAULT_PROMPT=ykman`: to avoid specifying `--prompt` each time\n - `YKMAN_OATH_CREDENTIAL_NAME`: to use an alternative ykman credential\n - `AWS_VAULT_YKMAN_VERSION`: to set the major version of the ykman cli being used. Defaults to \"4\"\n - `YKMAN_OATH_DEVICE_SERIAL`: to set the device serial of a specific Yubikey if you have multiple Yubikeys plugged into your computer.\n\n## Shell completion\n\nYou can generate shell completions for\n - bash: `eval \"$(curl -fs https://raw.githubusercontent.com/99designs/aws-vault/master/contrib/completions/bash/aws-vault.bash)\"`\n - zsh: `eval \"$(curl -fs https://raw.githubusercontent.com/99designs/aws-vault/master/contrib/completions/zsh/aws-vault.zsh)\"`\n - fish: `eval \"$(curl -fs https://raw.githubusercontent.com/99designs/aws-vault/master/contrib/completions/fish/aws-vault.fish)\"`\n\nFind the completion scripts at [contrib/completions](contrib/completions).\n\n\n## Desktop apps\n\nYou can use desktop apps with temporary credentials from AWS Vault too! For example on macOS run\n```shell\naws-vault exec --server jonsmith -- open -W -a Lens\n```\n* `--server`: starts the background server so that temporary credentials get refreshed automatically\n* `open -W -a Lens`: run the applications, waiting for it to exit\n\n## Docker\n\nIt's possible for Docker containers to retrieve credentials from aws-vault running on the host.\n\n![Screen Shot 2022-03-03 at 12 16 15 pm](https://user-images.githubusercontent.com/980499/156477380-423f4eb9-f10e-4568-afa8-7fa525a1f3a3.png)\n\nThe 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\nvariables. 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).\n\nIn 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`.\n\nThis 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.\n\nTo test it out:\n1. Add a base role to your `~/.aws/config` (replacing with valid values)\n   ```ini\n   [profile base-role]\n   source_profile=myprofile\n   role_arn=arn:aws:iam::222222222222:role/aws-vault-test\n   mfa_serial=arn:aws:iam::222222222222:mfa/<your.aws.username>\n   ```\n2. Start a reverse proxy:\n   ```shell\n   $ cd contrib/_aws-vault-proxy\n   $ aws-vault --debug exec --server --lazy base-role -- docker compose up --build aws-vault-proxy\n   ```\n3. In a new terminal, assume a new role\n   ```shell\n   $ export AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=/role-arn/arn:aws:iam::222222222222:role/another-role-that-can-be-assumed-by-base-role\n   $ docker-compose run testapp\n   testapp $ aws sts get-caller-identity\n   ```\n"
  },
  {
    "path": "bin/create-dmg",
    "content": "#!/bin/bash\n#\n# create-dmg packages the aws-vault CLI binary for macOS\n# using Apple's signing and notorizing process\n#\n#\n# As per https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow\n# AC_PASSWORD can be set in your keychain with:\n#     xcrun notarytool store-credentials \"AC_PASSWORD\"\n#               --apple-id \"AC_USERNAME\"\n#               --team-id <WWDRTeamID>\n#               --password <secret_2FA_password>\n#\n\nset -euo pipefail\n\nBIN_PATH=\"$1\"\nDMG_PATH=\"${2:-$1.dmg}\"\nCERT_ID=\"${CERT_ID:-\"Developer ID Application: 99designs Inc (NRM9HVJ62Z)\"}\"\nKEYCHAIN_PROFILE=\"${KEYCHAIN_PROFILE:-AC_PASSWORD}\"\n\nif [[ -f \"$DMG_PATH\" ]] ; then\n  echo \"File '$DMG_PATH' already exists. Remove it and try again\"\n  exit 1\nfi\n\ntmpdir=\"$(mktemp -d)\"\ntrap \"rm -rf $tmpdir\" EXIT\n\ncp -a $BIN_PATH $tmpdir/aws-vault\nsrc_path=\"$tmpdir/aws-vault\"\n\necho \"Signing binary\"\ncodesign --options runtime --timestamp --sign \"$CERT_ID\" \"$src_path\"\n\necho \"Creating dmg\"\nhdiutil create -quiet -srcfolder \"$src_path\" \"$DMG_PATH\"\n\necho \"Signing dmg\"\ncodesign --timestamp --sign \"$CERT_ID\" \"$DMG_PATH\"\n\necho \"Submitting notorization request\"\nxcrun notarytool submit $DMG_PATH --keychain-profile \"$KEYCHAIN_PROFILE\" --wait\n\necho \"Stapling\"\nxcrun stapler staple -q $DMG_PATH\n"
  },
  {
    "path": "cli/add.go",
    "content": "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/v7/vault\"\n\t\"github.com/99designs/keyring\"\n\t\"github.com/alecthomas/kingpin/v2\"\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n)\n\ntype AddCommandInput struct {\n\tProfileName string\n\tFromEnv     bool\n\tAddConfig   bool\n}\n\nfunc ConfigureAddCommand(app *kingpin.Application, a *AwsVault) {\n\tinput := AddCommandInput{}\n\n\tcmd := app.Command(\"add\", \"Add credentials to the secure keystore.\")\n\n\tcmd.Arg(\"profile\", \"Name of the profile\").\n\t\tRequired().\n\t\tStringVar(&input.ProfileName)\n\n\tcmd.Flag(\"env\", \"Read the credentials from the environment (AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY)\").\n\t\tBoolVar(&input.FromEnv)\n\n\tcmd.Flag(\"add-config\", \"Add a profile to ~/.aws/config if one doesn't exist\").\n\t\tDefault(\"true\").\n\t\tBoolVar(&input.AddConfig)\n\n\tcmd.Action(func(c *kingpin.ParseContext) error {\n\t\tkeyring, err := a.Keyring()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tawsConfigFile, err := a.AwsConfigFile()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = AddCommand(input, keyring, awsConfigFile)\n\t\tapp.FatalIfError(err, \"add\")\n\t\treturn nil\n\t})\n}\n\nfunc AddCommand(input AddCommandInput, keyring keyring.Keyring, awsConfigFile *vault.ConfigFile) error {\n\tvar accessKeyID, secretKey string\n\n\tp, _ := awsConfigFile.ProfileSection(input.ProfileName)\n\tif p.SourceProfile != \"\" {\n\t\treturn fmt.Errorf(\"Your profile has a source_profile of %s, adding credentials to %s won't have any effect\",\n\t\t\tp.SourceProfile, input.ProfileName)\n\t}\n\n\tif input.FromEnv {\n\t\tif accessKeyID = os.Getenv(\"AWS_ACCESS_KEY_ID\"); accessKeyID == \"\" {\n\t\t\treturn fmt.Errorf(\"Missing value for AWS_ACCESS_KEY_ID\")\n\t\t}\n\t\tif secretKey = os.Getenv(\"AWS_SECRET_ACCESS_KEY\"); secretKey == \"\" {\n\t\t\treturn fmt.Errorf(\"Missing value for AWS_SECRET_ACCESS_KEY\")\n\t\t}\n\t} else {\n\t\tvar err error\n\t\tif accessKeyID, err = prompt.TerminalPrompt(\"Enter Access Key ID: \"); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif secretKey, err = prompt.TerminalSecretPrompt(\"Enter Secret Access Key: \"); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tcreds := aws.Credentials{AccessKeyID: accessKeyID, SecretAccessKey: secretKey}\n\n\tckr := &vault.CredentialKeyring{Keyring: keyring}\n\tif err := ckr.Set(input.ProfileName, creds); err != nil {\n\t\treturn err\n\t}\n\n\tfmt.Printf(\"Added credentials to profile %q in vault\\n\", input.ProfileName)\n\n\tsk := &vault.SessionKeyring{Keyring: keyring}\n\tif n, _ := sk.RemoveForProfile(input.ProfileName); n > 0 {\n\t\tfmt.Printf(\"Deleted %d existing sessions.\\n\", n)\n\t}\n\n\tif _, hasProfile := awsConfigFile.ProfileSection(input.ProfileName); !hasProfile {\n\t\tif input.AddConfig {\n\t\t\tnewProfileSection := vault.ProfileSection{\n\t\t\t\tName: input.ProfileName,\n\t\t\t}\n\t\t\tlog.Printf(\"Adding profile %s to config at %s\", input.ProfileName, awsConfigFile.Path)\n\t\t\tif err := awsConfigFile.Add(newProfileSection); err != nil {\n\t\t\t\treturn fmt.Errorf(\"Error adding profile: %w\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cli/add_test.go",
    "content": "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.CreateTemp(\"\", \"aws-config\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer os.Remove(f.Name())\n\n\tos.Setenv(\"AWS_CONFIG_FILE\", f.Name())\n\tos.Setenv(\"AWS_ACCESS_KEY_ID\", \"llamas\")\n\tos.Setenv(\"AWS_SECRET_ACCESS_KEY\", \"rock\")\n\tos.Setenv(\"AWS_VAULT_BACKEND\", \"file\")\n\tos.Setenv(\"AWS_VAULT_FILE_PASSPHRASE\", \"password\")\n\n\tdefer os.Unsetenv(\"AWS_ACCESS_KEY_ID\")\n\tdefer os.Unsetenv(\"AWS_SECRET_ACCESS_KEY\")\n\tdefer os.Unsetenv(\"AWS_VAULT_BACKEND\")\n\tdefer os.Unsetenv(\"AWS_VAULT_FILE_PASSPHRASE\")\n\n\tapp := kingpin.New(`aws-vault`, ``)\n\tConfigureAddCommand(app, ConfigureGlobals(app))\n\tkingpin.MustParse(app.Parse([]string{\"add\", \"--debug\", \"--env\", \"foo\"}))\n\n\t// Output:\n\t// Added credentials to profile \"foo\" in vault\n}\n"
  },
  {
    "path": "cli/clear.go",
    "content": "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/alecthomas/kingpin/v2\"\n)\n\ntype ClearCommandInput struct {\n\tProfileName string\n}\n\nfunc ConfigureClearCommand(app *kingpin.Application, a *AwsVault) {\n\tinput := ClearCommandInput{}\n\n\tcmd := app.Command(\"clear\", \"Clear temporary credentials from the secure keystore.\")\n\n\tcmd.Arg(\"profile\", \"Name of the profile\").\n\t\tHintAction(a.MustGetProfileNames).\n\t\tStringVar(&input.ProfileName)\n\n\tcmd.Action(func(c *kingpin.ParseContext) (err error) {\n\t\tkeyring, err := a.Keyring()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tawsConfigFile, err := a.AwsConfigFile()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = ClearCommand(input, awsConfigFile, keyring)\n\t\tapp.FatalIfError(err, \"clear\")\n\t\treturn nil\n\t})\n}\n\nfunc ClearCommand(input ClearCommandInput, awsConfigFile *vault.ConfigFile, keyring keyring.Keyring) error {\n\tsessions := &vault.SessionKeyring{Keyring: keyring}\n\toidcTokens := &vault.OIDCTokenKeyring{Keyring: keyring}\n\tvar oldSessionsRemoved, numSessionsRemoved, numTokensRemoved int\n\tvar err error\n\tif input.ProfileName == \"\" {\n\t\toldSessionsRemoved, err = sessions.RemoveOldSessions()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tnumSessionsRemoved, err = sessions.RemoveAll()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tnumTokensRemoved, err = oidcTokens.RemoveAll()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tnumSessionsRemoved, err = sessions.RemoveForProfile(input.ProfileName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif profileSection, ok := awsConfigFile.ProfileSection(input.ProfileName); ok {\n\t\t\tif exists, _ := oidcTokens.Has(profileSection.SSOStartURL); exists {\n\t\t\t\terr = oidcTokens.Remove(profileSection.SSOStartURL)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tnumTokensRemoved = 1\n\t\t\t}\n\t\t}\n\t}\n\tfmt.Printf(\"Cleared %d sessions.\\n\", oldSessionsRemoved+numSessionsRemoved+numTokensRemoved)\n\n\treturn nil\n}\n"
  },
  {
    "path": "cli/exec.go",
    "content": "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\"syscall\"\n\t\"time\"\n\n\t\"github.com/99designs/aws-vault/v7/iso8601\"\n\t\"github.com/99designs/aws-vault/v7/server\"\n\t\"github.com/99designs/aws-vault/v7/vault\"\n\t\"github.com/99designs/keyring\"\n\t\"github.com/alecthomas/kingpin/v2\"\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n)\n\ntype ExecCommandInput struct {\n\tProfileName      string\n\tCommand          string\n\tArgs             []string\n\tStartEc2Server   bool\n\tStartEcsServer   bool\n\tLazy             bool\n\tJSONDeprecated   bool\n\tConfig           vault.ProfileConfig\n\tSessionDuration  time.Duration\n\tNoSession        bool\n\tUseStdout        bool\n\tShowHelpMessages bool\n}\n\nfunc (input ExecCommandInput) validate() error {\n\tif input.StartEc2Server && input.StartEcsServer {\n\t\treturn fmt.Errorf(\"Can't use --ec2-server with --ecs-server\")\n\t}\n\tif input.StartEc2Server && input.JSONDeprecated {\n\t\treturn fmt.Errorf(\"Can't use --ec2-server with --json\")\n\t}\n\tif input.StartEc2Server && input.NoSession {\n\t\treturn fmt.Errorf(\"Can't use --ec2-server with --no-session\")\n\t}\n\tif input.StartEcsServer && input.JSONDeprecated {\n\t\treturn fmt.Errorf(\"Can't use --ecs-server with --json\")\n\t}\n\tif input.StartEcsServer && input.NoSession {\n\t\treturn fmt.Errorf(\"Can't use --ecs-server with --no-session\")\n\t}\n\tif input.StartEcsServer && input.Config.MfaPromptMethod == \"terminal\" {\n\t\treturn fmt.Errorf(\"Can't use --prompt=terminal with --ecs-server. Specify a different prompt driver\")\n\t}\n\tif input.StartEc2Server && input.Config.MfaPromptMethod == \"terminal\" {\n\t\treturn fmt.Errorf(\"Can't use --prompt=terminal with --ec2-server. Specify a different prompt driver\")\n\t}\n\n\treturn nil\n}\n\nfunc hasBackgroundServer(input ExecCommandInput) bool {\n\treturn input.StartEcsServer || input.StartEc2Server\n}\n\nfunc ConfigureExecCommand(app *kingpin.Application, a *AwsVault) {\n\tinput := ExecCommandInput{}\n\n\tcmd := app.Command(\"exec\", \"Execute a command with AWS credentials.\")\n\n\tcmd.Flag(\"duration\", \"Duration of the temporary or assume-role session. Defaults to 1h\").\n\t\tShort('d').\n\t\tDurationVar(&input.SessionDuration)\n\n\tcmd.Flag(\"no-session\", \"Skip creating STS session with GetSessionToken\").\n\t\tShort('n').\n\t\tBoolVar(&input.NoSession)\n\n\tcmd.Flag(\"region\", \"The AWS region\").\n\t\tStringVar(&input.Config.Region)\n\n\tcmd.Flag(\"mfa-token\", \"The MFA token to use\").\n\t\tShort('t').\n\t\tStringVar(&input.Config.MfaToken)\n\n\tcmd.Flag(\"json\", \"Output credentials in JSON that can be used by credential_process\").\n\t\tShort('j').\n\t\tHidden().\n\t\tBoolVar(&input.JSONDeprecated)\n\n\tcmd.Flag(\"server\", \"Alias for --ecs-server\").\n\t\tShort('s').\n\t\tBoolVar(&input.StartEcsServer)\n\n\tcmd.Flag(\"ec2-server\", \"Run a EC2 metadata server in the background for credentials\").\n\t\tBoolVar(&input.StartEc2Server)\n\n\tcmd.Flag(\"ecs-server\", \"Run a ECS credential server in the background for credentials (the SDK or app must support AWS_CONTAINER_CREDENTIALS_FULL_URI)\").\n\t\tBoolVar(&input.StartEcsServer)\n\n\tcmd.Flag(\"lazy\", \"When using --ecs-server, lazily fetch credentials\").\n\t\tBoolVar(&input.Lazy)\n\n\tcmd.Flag(\"stdout\", \"Print the SSO link to the terminal without automatically opening the browser\").\n\t\tBoolVar(&input.UseStdout)\n\n\tcmd.Arg(\"profile\", \"Name of the profile\").\n\t\tRequired().\n\t\tHintAction(a.MustGetProfileNames).\n\t\tStringVar(&input.ProfileName)\n\n\tcmd.Arg(\"cmd\", \"Command to execute, defaults to $SHELL\").\n\t\tStringVar(&input.Command)\n\n\tcmd.Arg(\"args\", \"Command arguments\").\n\t\tStringsVar(&input.Args)\n\n\tcmd.Action(func(c *kingpin.ParseContext) (err error) {\n\t\tinput.Config.MfaPromptMethod = a.PromptDriver(hasBackgroundServer(input))\n\t\tinput.Config.NonChainedGetSessionTokenDuration = input.SessionDuration\n\t\tinput.Config.AssumeRoleDuration = input.SessionDuration\n\t\tinput.Config.SSOUseStdout = input.UseStdout\n\t\tinput.ShowHelpMessages = !a.Debug && input.Command == \"\" && isATerminal() && os.Getenv(\"AWS_VAULT_DISABLE_HELP_MESSAGE\") != \"1\"\n\n\t\tf, err := a.AwsConfigFile()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tkeyring, err := a.Keyring()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\texitcode := 0\n\t\tif input.JSONDeprecated {\n\t\t\texportCommandInput := ExportCommandInput{\n\t\t\t\tProfileName:     input.ProfileName,\n\t\t\t\tFormat:          \"json\",\n\t\t\t\tConfig:          input.Config,\n\t\t\t\tSessionDuration: input.SessionDuration,\n\t\t\t\tNoSession:       input.NoSession,\n\t\t\t}\n\n\t\t\terr = ExportCommand(exportCommandInput, f, keyring)\n\t\t} else {\n\t\t\texitcode, err = ExecCommand(input, f, keyring)\n\t\t}\n\n\t\tapp.FatalIfError(err, \"exec\")\n\n\t\t// override exit code if not err\n\t\tos.Exit(exitcode)\n\n\t\treturn nil\n\t})\n}\n\nfunc ExecCommand(input ExecCommandInput, f *vault.ConfigFile, keyring keyring.Keyring) (exitcode int, err error) {\n\tif os.Getenv(\"AWS_VAULT\") != \"\" {\n\t\treturn 0, fmt.Errorf(\"running in an existing aws-vault subshell; 'exit' from the subshell or unset AWS_VAULT to force\")\n\t}\n\n\tif err := input.validate(); err != nil {\n\t\treturn 0, err\n\t}\n\n\tconfig, err := vault.NewConfigLoader(input.Config, f, input.ProfileName).GetProfileConfig(input.ProfileName)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"Error loading config: %w\", err)\n\t}\n\n\tcredsProvider, err := vault.NewTempCredentialsProvider(config, &vault.CredentialKeyring{Keyring: keyring}, input.NoSession, false)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"Error getting temporary credentials: %w\", err)\n\t}\n\n\tsubshellHelp := \"\"\n\tif input.Command == \"\" {\n\t\tinput.Command = getDefaultShell()\n\t\tsubshellHelp = fmt.Sprintf(\"Starting subshell %s, use `exit` to exit the subshell\", input.Command)\n\t}\n\n\tcmdEnv := createEnv(input.ProfileName, config.Region)\n\n\tif input.StartEc2Server {\n\t\tif server.IsProxyRunning() {\n\t\t\treturn 0, fmt.Errorf(\"Another process is already bound to 169.254.169.254:80\")\n\t\t}\n\n\t\tprintHelpMessage(\"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)\n\t\tif err := server.StartEc2EndpointProxyServerProcess(); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tdefer server.StopProxy()\n\n\t\tif err = server.StartEc2CredentialsServer(context.TODO(), credsProvider, config.Region); err != nil {\n\t\t\treturn 0, fmt.Errorf(\"Failed to start credential server: %w\", err)\n\t\t}\n\t\tprintHelpMessage(subshellHelp, input.ShowHelpMessages)\n\t} else if input.StartEcsServer {\n\t\tprintHelpMessage(\"Starting a local ECS credential server; your app's AWS sdk must support AWS_CONTAINER_CREDENTIALS_FULL_URI.\", input.ShowHelpMessages)\n\t\tif err = startEcsServerAndSetEnv(credsProvider, config, input.Lazy, &cmdEnv); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tprintHelpMessage(subshellHelp, input.ShowHelpMessages)\n\t} else {\n\t\tif err = addCredsToEnv(credsProvider, input.ProfileName, &cmdEnv); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t\tprintHelpMessage(subshellHelp, input.ShowHelpMessages)\n\n\t\terr = doExecSyscall(input.Command, input.Args, cmdEnv) // will not return if exec syscall succeeds\n\t\tif err != nil {\n\t\t\tlog.Println(\"Error doing execve syscall:\", err.Error())\n\t\t\tlog.Println(\"Falling back to running a subprocess\")\n\t\t}\n\t}\n\n\treturn runSubProcess(input.Command, input.Args, cmdEnv)\n}\n\nfunc printHelpMessage(helpMsg string, showHelpMessages bool) {\n\tif helpMsg != \"\" {\n\t\tif showHelpMessages {\n\t\t\tprintToStderr(helpMsg)\n\t\t} else {\n\t\t\tlog.Println(helpMsg)\n\t\t}\n\t}\n}\n\nfunc printToStderr(helpMsg string) {\n\tfmt.Fprint(os.Stderr, helpMsg, \"\\n\")\n}\n\nfunc createEnv(profileName string, region string) environ {\n\tenv := environ(os.Environ())\n\tenv.Unset(\"AWS_ACCESS_KEY_ID\")\n\tenv.Unset(\"AWS_SECRET_ACCESS_KEY\")\n\tenv.Unset(\"AWS_SESSION_TOKEN\")\n\tenv.Unset(\"AWS_SECURITY_TOKEN\")\n\tenv.Unset(\"AWS_CREDENTIAL_FILE\")\n\tenv.Unset(\"AWS_DEFAULT_PROFILE\")\n\tenv.Unset(\"AWS_PROFILE\")\n\tenv.Unset(\"AWS_SDK_LOAD_CONFIG\")\n\n\tenv.Set(\"AWS_VAULT\", profileName)\n\n\tif region != \"\" {\n\t\t// AWS_REGION is used by most SDKs. But boto3 (Python SDK) uses AWS_DEFAULT_REGION\n\t\t// See https://docs.aws.amazon.com/sdkref/latest/guide/feature-region.html\n\t\tlog.Printf(\"Setting subprocess env: AWS_REGION=%s, AWS_DEFAULT_REGION=%s\", region, region)\n\t\tenv.Set(\"AWS_REGION\", region)\n\t\tenv.Set(\"AWS_DEFAULT_REGION\", region)\n\t}\n\n\treturn env\n}\n\nfunc startEcsServerAndSetEnv(credsProvider aws.CredentialsProvider, config *vault.ProfileConfig, lazy bool, cmdEnv *environ) error {\n\tecsServer, err := server.NewEcsServer(context.TODO(), credsProvider, config, \"\", 0, lazy)\n\tif err != nil {\n\t\treturn err\n\t}\n\tgo func() {\n\t\terr = ecsServer.Serve()\n\t\tif err != http.ErrServerClosed { // ErrServerClosed is a graceful close\n\t\t\tlog.Fatalf(\"ecs server: %s\", err.Error())\n\t\t}\n\t}()\n\n\tlog.Println(\"Setting subprocess env AWS_CONTAINER_CREDENTIALS_FULL_URI, AWS_CONTAINER_AUTHORIZATION_TOKEN\")\n\tcmdEnv.Set(\"AWS_CONTAINER_CREDENTIALS_FULL_URI\", ecsServer.BaseURL())\n\tcmdEnv.Set(\"AWS_CONTAINER_AUTHORIZATION_TOKEN\", ecsServer.AuthToken())\n\n\treturn nil\n}\n\nfunc addCredsToEnv(credsProvider aws.CredentialsProvider, profileName string, cmdEnv *environ) error {\n\tcreds, err := credsProvider.Retrieve(context.TODO())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Failed to get credentials for %s: %w\", profileName, err)\n\t}\n\n\tlog.Println(\"Setting subprocess env: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY\")\n\tcmdEnv.Set(\"AWS_ACCESS_KEY_ID\", creds.AccessKeyID)\n\tcmdEnv.Set(\"AWS_SECRET_ACCESS_KEY\", creds.SecretAccessKey)\n\n\tif creds.SessionToken != \"\" {\n\t\tlog.Println(\"Setting subprocess env: AWS_SESSION_TOKEN\")\n\t\tcmdEnv.Set(\"AWS_SESSION_TOKEN\", creds.SessionToken)\n\t}\n\tif creds.CanExpire {\n\t\tlog.Println(\"Setting subprocess env: AWS_CREDENTIAL_EXPIRATION\")\n\t\tcmdEnv.Set(\"AWS_CREDENTIAL_EXPIRATION\", iso8601.Format(creds.Expires))\n\t}\n\n\treturn nil\n}\n\n// environ is a slice of strings representing the environment, in the form \"key=value\".\ntype environ []string\n\n// Unset an environment variable by key\nfunc (e *environ) Unset(key string) {\n\tfor i := range *e {\n\t\tif strings.HasPrefix((*e)[i], key+\"=\") {\n\t\t\t(*e)[i] = (*e)[len(*e)-1]\n\t\t\t*e = (*e)[:len(*e)-1]\n\t\t\tbreak\n\t\t}\n\t}\n}\n\n// Set adds an environment variable, replacing any existing ones of the same key\nfunc (e *environ) Set(key, val string) {\n\te.Unset(key)\n\t*e = append(*e, key+\"=\"+val)\n}\n\nfunc getDefaultShell() string {\n\tcommand := os.Getenv(\"SHELL\")\n\tif command == \"\" {\n\t\tif runtime.GOOS == \"windows\" {\n\t\t\tcommand = \"cmd.exe\"\n\t\t} else {\n\t\t\tcommand = \"/bin/sh\"\n\t\t}\n\t}\n\treturn command\n}\n\nfunc runSubProcess(command string, args []string, env []string) (int, error) {\n\tlog.Printf(\"Starting a subprocess: %s %s\", command, strings.Join(args, \" \"))\n\n\tcmd := osexec.Command(command, args...)\n\tcmd.Stdin = os.Stdin\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\tcmd.Env = env\n\n\tsigChan := make(chan os.Signal, 1)\n\tsignal.Notify(sigChan)\n\n\tif err := cmd.Start(); err != nil {\n\t\treturn 0, err\n\t}\n\n\t// proxy signals to process\n\tgo func() {\n\t\tfor {\n\t\t\tsig := <-sigChan\n\t\t\t_ = cmd.Process.Signal(sig)\n\t\t}\n\t}()\n\n\tif err := cmd.Wait(); err != nil {\n\t\t_ = cmd.Process.Signal(os.Kill)\n\t\treturn 0, fmt.Errorf(\"Failed to wait for command termination: %v\", err)\n\t}\n\n\twaitStatus := cmd.ProcessState.Sys().(syscall.WaitStatus)\n\n\treturn waitStatus.ExitStatus(), nil\n}\n\nfunc doExecSyscall(command string, args []string, env []string) error {\n\tlog.Printf(\"Exec command %s %s\", command, strings.Join(args, \" \"))\n\n\targv0, err := osexec.LookPath(command)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Couldn't find the executable '%s': %w\", command, err)\n\t}\n\n\tlog.Printf(\"Found executable %s\", argv0)\n\n\targv := make([]string, 0, 1+len(args))\n\targv = append(argv, command)\n\targv = append(argv, args...)\n\n\treturn syscall.Exec(argv0, argv, env)\n}\n"
  },
  {
    "path": "cli/exec_test.go",
    "content": "package cli\n\nimport (\n\t\"github.com/alecthomas/kingpin/v2\"\n\n\t\"github.com/99designs/keyring\"\n)\n\nfunc ExampleExecCommand() {\n\tapp := kingpin.New(\"aws-vault\", \"\")\n\tawsVault := ConfigureGlobals(app)\n\tawsVault.keyringImpl = keyring.NewArrayKeyring([]keyring.Item{\n\t\t{Key: \"llamas\", Data: []byte(`{\"AccessKeyID\":\"ABC\",\"SecretAccessKey\":\"XYZ\"}`)},\n\t})\n\tConfigureExecCommand(app, awsVault)\n\tkingpin.MustParse(app.Parse([]string{\n\t\t\"--debug\", \"exec\", \"--no-session\", \"llamas\", \"--\", \"sh\", \"-c\", \"echo $AWS_ACCESS_KEY_ID\",\n\t}))\n\n\t// Output:\n\t// ABC\n}\n"
  },
  {
    "path": "cli/export.go",
    "content": "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/iso8601\"\n\t\"github.com/99designs/aws-vault/v7/vault\"\n\t\"github.com/99designs/keyring\"\n\t\"github.com/alecthomas/kingpin/v2\"\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\tini \"gopkg.in/ini.v1\"\n)\n\ntype ExportCommandInput struct {\n\tProfileName     string\n\tFormat          string\n\tConfig          vault.ProfileConfig\n\tSessionDuration time.Duration\n\tNoSession       bool\n\tUseStdout       bool\n}\n\nvar (\n\tFormatTypeEnv        = \"env\"\n\tFormatTypeExportEnv  = \"export-env\"\n\tFormatTypeExportJSON = \"json\"\n\tFormatTypeExportINI  = \"ini\"\n)\n\nfunc ConfigureExportCommand(app *kingpin.Application, a *AwsVault) {\n\tinput := ExportCommandInput{}\n\n\tcmd := app.Command(\"export\", \"Export AWS credentials.\")\n\n\tcmd.Flag(\"duration\", \"Duration of the temporary or assume-role session. Defaults to 1h\").\n\t\tShort('d').\n\t\tDurationVar(&input.SessionDuration)\n\n\tcmd.Flag(\"no-session\", \"Skip creating STS session with GetSessionToken\").\n\t\tShort('n').\n\t\tBoolVar(&input.NoSession)\n\n\tcmd.Flag(\"region\", \"The AWS region\").\n\t\tStringVar(&input.Config.Region)\n\n\tcmd.Flag(\"mfa-token\", \"The MFA token to use\").\n\t\tShort('t').\n\t\tStringVar(&input.Config.MfaToken)\n\n\tcmd.Flag(\"format\", fmt.Sprintf(\"Format to output credentials. Valid formats: %s, %s, %s, %s\", FormatTypeEnv, FormatTypeExportEnv, FormatTypeExportJSON, FormatTypeExportINI)).\n\t\tDefault(FormatTypeEnv).\n\t\tEnumVar(&input.Format, FormatTypeEnv, FormatTypeExportEnv, FormatTypeExportJSON, FormatTypeExportINI)\n\n\tcmd.Flag(\"stdout\", \"Print the SSO link to the terminal without automatically opening the browser\").\n\t\tBoolVar(&input.UseStdout)\n\n\tcmd.Arg(\"profile\", \"Name of the profile\").\n\t\tRequired().\n\t\tHintAction(a.MustGetProfileNames).\n\t\tStringVar(&input.ProfileName)\n\n\tcmd.Action(func(c *kingpin.ParseContext) (err error) {\n\t\tinput.Config.MfaPromptMethod = a.PromptDriver(false)\n\t\tinput.Config.NonChainedGetSessionTokenDuration = input.SessionDuration\n\t\tinput.Config.AssumeRoleDuration = input.SessionDuration\n\t\tinput.Config.SSOUseStdout = input.UseStdout\n\n\t\tf, err := a.AwsConfigFile()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tkeyring, err := a.Keyring()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = ExportCommand(input, f, keyring)\n\t\tapp.FatalIfError(err, \"exec\")\n\t\treturn nil\n\t})\n}\n\nfunc ExportCommand(input ExportCommandInput, f *vault.ConfigFile, keyring keyring.Keyring) error {\n\tif os.Getenv(\"AWS_VAULT\") != \"\" {\n\t\treturn fmt.Errorf(\"in an existing aws-vault subshell; 'exit' from the subshell or unset AWS_VAULT to force\")\n\t}\n\n\tconfig, err := vault.NewConfigLoader(input.Config, f, input.ProfileName).GetProfileConfig(input.ProfileName)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error loading config: %w\", err)\n\t}\n\n\tckr := &vault.CredentialKeyring{Keyring: keyring}\n\tcredsProvider, err := vault.NewTempCredentialsProvider(config, ckr, input.NoSession, false)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error getting temporary credentials: %w\", err)\n\t}\n\n\tif input.Format == FormatTypeExportJSON {\n\t\treturn printJSON(input, credsProvider)\n\t} else if input.Format == FormatTypeExportINI {\n\t\treturn printINI(credsProvider, input.ProfileName, config.Region)\n\t} else if input.Format == FormatTypeExportEnv {\n\t\treturn printEnv(input, credsProvider, config.Region, \"export \")\n\t} else {\n\t\treturn printEnv(input, credsProvider, config.Region, \"\")\n\t}\n}\n\nfunc printJSON(input ExportCommandInput, credsProvider aws.CredentialsProvider) error {\n\t// AwsCredentialHelperData is metadata for AWS CLI credential process\n\t// See https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#sourcing-credentials-from-external-processes\n\ttype AwsCredentialHelperData struct {\n\t\tVersion         int    `json:\"Version\"`\n\t\tAccessKeyID     string `json:\"AccessKeyId\"`\n\t\tSecretAccessKey string `json:\"SecretAccessKey\"`\n\t\tSessionToken    string `json:\"SessionToken,omitempty\"`\n\t\tExpiration      string `json:\"Expiration,omitempty\"`\n\t}\n\n\tcreds, err := credsProvider.Retrieve(context.TODO())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Failed to get credentials for %s: %w\", input.ProfileName, err)\n\t}\n\n\tcredentialData := AwsCredentialHelperData{\n\t\tVersion:         1,\n\t\tAccessKeyID:     creds.AccessKeyID,\n\t\tSecretAccessKey: creds.SecretAccessKey,\n\t\tSessionToken:    creds.SessionToken,\n\t}\n\n\tif creds.CanExpire {\n\t\tcredentialData.Expiration = iso8601.Format(creds.Expires)\n\t}\n\n\tjson, err := json.MarshalIndent(&credentialData, \"\", \"  \")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating credential json: %w\", err)\n\t}\n\n\tfmt.Print(string(json) + \"\\n\")\n\n\treturn nil\n}\n\nfunc mustNewKey(s *ini.Section, name, val string) {\n\tif val != \"\" {\n\t\t_, err := s.NewKey(name, val)\n\t\tif err != nil {\n\t\t\tlog.Fatalln(\"Failed to create ini key:\", err.Error())\n\t\t}\n\t}\n}\n\nfunc printINI(credsProvider aws.CredentialsProvider, profilename, region string) error {\n\tcreds, err := credsProvider.Retrieve(context.TODO())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Failed to get credentials for %s: %w\", profilename, err)\n\t}\n\n\tf := ini.Empty()\n\ts, err := f.NewSection(profilename)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Failed to create ini section: %w\", err)\n\t}\n\n\tmustNewKey(s, \"aws_access_key_id\", creds.AccessKeyID)\n\tmustNewKey(s, \"aws_secret_access_key\", creds.SecretAccessKey)\n\tmustNewKey(s, \"aws_session_token\", creds.SessionToken)\n\tif creds.CanExpire {\n\t\tmustNewKey(s, \"aws_credential_expiration\", iso8601.Format(creds.Expires))\n\t}\n\tmustNewKey(s, \"region\", region)\n\n\t_, err = f.WriteTo(os.Stdout)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Failed to output ini: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc printEnv(input ExportCommandInput, credsProvider aws.CredentialsProvider, region, prefix string) error {\n\tcreds, err := credsProvider.Retrieve(context.TODO())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Failed to get credentials for %s: %w\", input.ProfileName, err)\n\t}\n\n\tfmt.Printf(\"%sAWS_ACCESS_KEY_ID=%s\\n\", prefix, creds.AccessKeyID)\n\tfmt.Printf(\"%sAWS_SECRET_ACCESS_KEY=%s\\n\", prefix, creds.SecretAccessKey)\n\n\tif creds.SessionToken != \"\" {\n\t\tfmt.Printf(\"%sAWS_SESSION_TOKEN=%s\\n\", prefix, creds.SessionToken)\n\t}\n\tif creds.CanExpire {\n\t\tfmt.Printf(\"%sAWS_CREDENTIAL_EXPIRATION=%s\\n\", prefix, iso8601.Format(creds.Expires))\n\t}\n\tif region != \"\" {\n\t\tfmt.Printf(\"%sAWS_REGION=%s\\n\", prefix, region)\n\t\tfmt.Printf(\"%sAWS_DEFAULT_REGION=%s\\n\", prefix, region)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "cli/export_test.go",
    "content": "package cli\n\nimport (\n\t\"github.com/alecthomas/kingpin/v2\"\n\n\t\"github.com/99designs/keyring\"\n)\n\nfunc ExampleExportCommand() {\n\tapp := kingpin.New(\"aws-vault\", \"\")\n\tawsVault := ConfigureGlobals(app)\n\tawsVault.keyringImpl = keyring.NewArrayKeyring([]keyring.Item{\n\t\t{Key: \"llamas\", Data: []byte(`{\"AccessKeyID\":\"ABC\",\"SecretAccessKey\":\"XYZ\"}`)},\n\t})\n\tConfigureExportCommand(app, awsVault)\n\tkingpin.MustParse(app.Parse([]string{\n\t\t\"export\", \"--format=ini\", \"--no-session\", \"llamas\",\n\t}))\n\n\t// Output:\n\t// [llamas]\n\t// aws_access_key_id=ABC\n\t// aws_secret_access_key=XYZ\n\t// region=us-east-1\n}\n"
  },
  {
    "path": "cli/global.go",
    "content": "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/99designs/aws-vault/v7/vault\"\n\t\"github.com/99designs/keyring\"\n\t\"github.com/alecthomas/kingpin/v2\"\n\tisatty \"github.com/mattn/go-isatty\"\n\t\"golang.org/x/term\"\n)\n\nvar keyringConfigDefaults = keyring.Config{\n\tServiceName:              \"aws-vault\",\n\tFilePasswordFunc:         fileKeyringPassphrasePrompt,\n\tLibSecretCollectionName:  \"awsvault\",\n\tKWalletAppID:             \"aws-vault\",\n\tKWalletFolder:            \"aws-vault\",\n\tKeychainTrustApplication: true,\n\tWinCredPrefix:            \"aws-vault\",\n}\n\ntype AwsVault struct {\n\tDebug          bool\n\tKeyringConfig  keyring.Config\n\tKeyringBackend string\n\tpromptDriver   string\n\n\tkeyringImpl   keyring.Keyring\n\tawsConfigFile *vault.ConfigFile\n}\n\nfunc isATerminal() bool {\n\tfd := os.Stdout.Fd()\n\treturn isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd)\n}\n\nfunc (a *AwsVault) PromptDriver(avoidTerminalPrompt bool) string {\n\tif a.promptDriver == \"\" {\n\t\ta.promptDriver = \"terminal\"\n\n\t\tif !isATerminal() || avoidTerminalPrompt {\n\t\t\tfor _, driver := range prompt.Available() {\n\t\t\t\ta.promptDriver = driver\n\t\t\t\tif driver != \"terminal\" {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tlog.Println(\"Using prompt driver: \" + a.promptDriver)\n\n\treturn a.promptDriver\n}\n\nfunc (a *AwsVault) Keyring() (keyring.Keyring, error) {\n\tif a.keyringImpl == nil {\n\t\tif a.KeyringBackend != \"\" {\n\t\t\ta.KeyringConfig.AllowedBackends = []keyring.BackendType{keyring.BackendType(a.KeyringBackend)}\n\t\t}\n\t\tvar err error\n\t\ta.keyringImpl, err = keyring.Open(a.KeyringConfig)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn a.keyringImpl, nil\n}\n\nfunc (a *AwsVault) AwsConfigFile() (*vault.ConfigFile, error) {\n\tif a.awsConfigFile == nil {\n\t\tvar err error\n\t\ta.awsConfigFile, err = vault.LoadConfigFromEnv()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\treturn a.awsConfigFile, nil\n}\n\nfunc (a *AwsVault) MustGetProfileNames() []string {\n\tconfig, err := a.AwsConfigFile()\n\tif err != nil {\n\t\tlog.Fatalf(\"Error loading AWS config: %s\", err.Error())\n\t}\n\treturn config.ProfileNames()\n}\n\nfunc ConfigureGlobals(app *kingpin.Application) *AwsVault {\n\ta := &AwsVault{\n\t\tKeyringConfig: keyringConfigDefaults,\n\t}\n\n\tbackendsAvailable := []string{}\n\tfor _, backendType := range keyring.AvailableBackends() {\n\t\tbackendsAvailable = append(backendsAvailable, string(backendType))\n\t}\n\n\tpromptsAvailable := prompt.Available()\n\n\tapp.Flag(\"debug\", \"Show debugging output\").\n\t\tBoolVar(&a.Debug)\n\n\tapp.Flag(\"backend\", fmt.Sprintf(\"Secret backend to use %v\", backendsAvailable)).\n\t\tDefault(backendsAvailable[0]).\n\t\tEnvar(\"AWS_VAULT_BACKEND\").\n\t\tEnumVar(&a.KeyringBackend, backendsAvailable...)\n\n\tapp.Flag(\"prompt\", fmt.Sprintf(\"Prompt driver to use %v\", promptsAvailable)).\n\t\tEnvar(\"AWS_VAULT_PROMPT\").\n\t\tStringVar(&a.promptDriver)\n\n\tapp.Validate(func(app *kingpin.Application) error {\n\t\tif a.promptDriver == \"\" {\n\t\t\treturn nil\n\t\t}\n\t\tif a.promptDriver == \"pass\" {\n\t\t\tkingpin.Fatalf(\"--prompt=pass (or AWS_VAULT_PROMPT=pass) has been removed from aws-vault as using TOTPs without \" +\n\t\t\t\t\"a dedicated device goes against security best practices. If you wish to continue using pass, \" +\n\t\t\t\t\"add `mfa_process = pass otp <your mfa_serial>` to profiles in your ~/.aws/config file.\")\n\t\t}\n\t\tfor _, v := range promptsAvailable {\n\t\t\tif v == a.promptDriver {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\treturn fmt.Errorf(\"--prompt value must be one of %s, got '%s'\", strings.Join(promptsAvailable, \",\"), a.promptDriver)\n\t})\n\n\tapp.Flag(\"keychain\", \"Name of macOS keychain to use, if it doesn't exist it will be created\").\n\t\tDefault(\"aws-vault\").\n\t\tEnvar(\"AWS_VAULT_KEYCHAIN_NAME\").\n\t\tStringVar(&a.KeyringConfig.KeychainName)\n\n\tapp.Flag(\"secret-service-collection\", \"Name of secret-service collection to use, if it doesn't exist it will be created\").\n\t\tDefault(\"awsvault\").\n\t\tEnvar(\"AWS_VAULT_SECRET_SERVICE_COLLECTION_NAME\").\n\t\tStringVar(&a.KeyringConfig.LibSecretCollectionName)\n\n\tapp.Flag(\"pass-dir\", \"Pass password store directory\").\n\t\tEnvar(\"AWS_VAULT_PASS_PASSWORD_STORE_DIR\").\n\t\tStringVar(&a.KeyringConfig.PassDir)\n\n\tapp.Flag(\"pass-cmd\", \"Name of the pass executable\").\n\t\tEnvar(\"AWS_VAULT_PASS_CMD\").\n\t\tStringVar(&a.KeyringConfig.PassCmd)\n\n\tapp.Flag(\"pass-prefix\", \"Prefix to prepend to the item path stored in pass\").\n\t\tEnvar(\"AWS_VAULT_PASS_PREFIX\").\n\t\tStringVar(&a.KeyringConfig.PassPrefix)\n\n\tapp.Flag(\"file-dir\", \"Directory for the \\\"file\\\" password store\").\n\t\tDefault(\"~/.awsvault/keys/\").\n\t\tEnvar(\"AWS_VAULT_FILE_DIR\").\n\t\tStringVar(&a.KeyringConfig.FileDir)\n\n\tapp.PreAction(func(c *kingpin.ParseContext) error {\n\t\tif !a.Debug {\n\t\t\tlog.SetOutput(io.Discard)\n\t\t}\n\t\tkeyring.Debug = a.Debug\n\t\tlog.Printf(\"aws-vault %s\", app.Model().Version)\n\t\treturn nil\n\t})\n\n\treturn a\n}\n\nfunc fileKeyringPassphrasePrompt(prompt string) (string, error) {\n\tif password, ok := os.LookupEnv(\"AWS_VAULT_FILE_PASSPHRASE\"); ok {\n\t\treturn password, nil\n\t}\n\n\tfmt.Fprintf(os.Stderr, \"%s: \", prompt)\n\tb, err := term.ReadPassword(int(os.Stdin.Fd()))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tfmt.Println()\n\treturn string(b), nil\n}\n"
  },
  {
    "path": "cli/list.go",
    "content": "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\"github.com/99designs/keyring\"\n\t\"github.com/alecthomas/kingpin/v2\"\n)\n\ntype ListCommandInput struct {\n\tOnlyProfiles    bool\n\tOnlySessions    bool\n\tOnlyCredentials bool\n}\n\nfunc ConfigureListCommand(app *kingpin.Application, a *AwsVault) {\n\tinput := ListCommandInput{}\n\n\tcmd := app.Command(\"list\", \"List profiles, along with their credentials and sessions.\")\n\tcmd.Alias(\"ls\")\n\n\tcmd.Flag(\"profiles\", \"Show only the profile names\").\n\t\tBoolVar(&input.OnlyProfiles)\n\n\tcmd.Flag(\"sessions\", \"Show only the session names\").\n\t\tBoolVar(&input.OnlySessions)\n\n\tcmd.Flag(\"credentials\", \"Show only the profiles with stored credential\").\n\t\tBoolVar(&input.OnlyCredentials)\n\n\tcmd.Action(func(c *kingpin.ParseContext) (err error) {\n\t\tkeyring, err := a.Keyring()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tawsConfigFile, err := a.AwsConfigFile()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = ListCommand(input, awsConfigFile, keyring)\n\t\tapp.FatalIfError(err, \"list\")\n\t\treturn nil\n\t})\n}\n\ntype stringslice []string\n\nfunc (ss stringslice) remove(stringsToRemove []string) (newSS []string) {\n\txx := stringslice(stringsToRemove)\n\tfor _, s := range ss {\n\t\tif !xx.has(s) {\n\t\t\tnewSS = append(newSS, s)\n\t\t}\n\t}\n\n\treturn\n}\n\nfunc (ss stringslice) has(s string) bool {\n\tfor _, t := range ss {\n\t\tif s == t {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc sessionLabel(sess vault.SessionMetadata) string {\n\treturn fmt.Sprintf(\"%s:%s\", sess.Type, time.Until(sess.Expiration).Truncate(time.Second))\n}\n\nfunc ListCommand(input ListCommandInput, awsConfigFile *vault.ConfigFile, keyring keyring.Keyring) (err error) {\n\tcredentialKeyring := &vault.CredentialKeyring{Keyring: keyring}\n\toidcTokenKeyring := &vault.OIDCTokenKeyring{Keyring: credentialKeyring.Keyring}\n\tsessionKeyring := &vault.SessionKeyring{Keyring: credentialKeyring.Keyring}\n\n\tcredentialsNames, err := credentialKeyring.Keys()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttokens, err := oidcTokenKeyring.Keys()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tsessions, err := sessionKeyring.GetAllMetadata()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tallSessionLabels := []string{}\n\tfor _, t := range tokens {\n\t\tallSessionLabels = append(allSessionLabels, fmt.Sprintf(\"oidc:%s\", t))\n\t}\n\tfor _, sess := range sessions {\n\t\tallSessionLabels = append(allSessionLabels, sessionLabel(sess))\n\t}\n\n\tif input.OnlyCredentials {\n\t\tfor _, c := range credentialsNames {\n\t\t\tfmt.Println(c)\n\t\t}\n\t\treturn nil\n\t}\n\n\tif input.OnlyProfiles {\n\t\tfor _, profileName := range awsConfigFile.ProfileNames() {\n\t\t\tfmt.Println(profileName)\n\t\t}\n\t\treturn nil\n\t}\n\n\tif input.OnlySessions {\n\t\tfor _, l := range allSessionLabels {\n\t\t\tfmt.Println(l)\n\t\t}\n\t\treturn nil\n\t}\n\n\tdisplayedSessionLabels := []string{}\n\n\tw := tabwriter.NewWriter(os.Stdout, 25, 4, 2, ' ', 0)\n\n\tfmt.Fprintln(w, \"Profile\\tCredentials\\tSessions\\t\")\n\tfmt.Fprintln(w, \"=======\\t===========\\t========\\t\")\n\n\t// list out known profiles first\n\tfor _, profileName := range awsConfigFile.ProfileNames() {\n\t\tfmt.Fprintf(w, \"%s\\t\", profileName)\n\n\t\thasCred, err := credentialKeyring.Has(profileName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif hasCred {\n\t\t\tfmt.Fprintf(w, \"%s\\t\", profileName)\n\t\t} else {\n\t\t\tfmt.Fprintf(w, \"-\\t\")\n\t\t}\n\n\t\tvar sessionLabels []string\n\n\t\t// check oidc keyring\n\t\tif profileSection, ok := awsConfigFile.ProfileSection(profileName); ok {\n\t\t\tif exists, _ := oidcTokenKeyring.Has(profileSection.SSOStartURL); exists {\n\t\t\t\tsessionLabels = append(sessionLabels, fmt.Sprintf(\"oidc:%s\", profileSection.SSOStartURL))\n\t\t\t}\n\t\t}\n\n\t\t// check session keyring\n\t\tfor _, sess := range sessions {\n\t\t\tif profileName == sess.ProfileName {\n\t\t\t\tsessionLabels = append(sessionLabels, sessionLabel(sess))\n\t\t\t}\n\t\t}\n\n\t\tif len(sessionLabels) > 0 {\n\t\t\tfmt.Fprintf(w, \"%s\\t\\n\", strings.Join(sessionLabels, \", \"))\n\t\t} else {\n\t\t\tfmt.Fprintf(w, \"-\\t\\n\")\n\t\t}\n\n\t\tdisplayedSessionLabels = append(displayedSessionLabels, sessionLabels...)\n\t}\n\n\t// show credentials that don't have profiles\n\tfor _, credentialName := range credentialsNames {\n\t\t_, ok := awsConfigFile.ProfileSection(credentialName)\n\t\tif !ok {\n\t\t\tfmt.Fprintf(w, \"-\\t%s\\t-\\t\\n\", credentialName)\n\t\t}\n\t}\n\n\t// show sessions that don't have profiles\n\tsessionsWithoutProfiles := stringslice(allSessionLabels).remove(displayedSessionLabels)\n\tfor _, s := range sessionsWithoutProfiles {\n\t\tfmt.Fprintf(w, \"-\\t-\\t%s\\t\\n\", s)\n\t}\n\n\treturn w.Flush()\n}\n"
  },
  {
    "path": "cli/list_test.go",
    "content": "package cli\n\nimport (\n\t\"github.com/alecthomas/kingpin/v2\"\n\n\t\"github.com/99designs/keyring\"\n)\n\nfunc ExampleListCommand() {\n\tapp := kingpin.New(\"aws-vault\", \"\")\n\tawsVault := ConfigureGlobals(app)\n\tawsVault.keyringImpl = keyring.NewArrayKeyring([]keyring.Item{\n\t\t{Key: \"llamas\", Data: []byte(`{\"AccessKeyID\":\"ABC\",\"SecretAccessKey\":\"XYZ\"}`)},\n\t})\n\tConfigureListCommand(app, awsVault)\n\tkingpin.MustParse(app.Parse([]string{\n\t\t\"list\", \"--credentials\",\n\t}))\n\n\t// Output:\n\t// llamas\n}\n"
  },
  {
    "path": "cli/login.go",
    "content": "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\"github.com/99designs/aws-vault/v7/vault\"\n\t\"github.com/99designs/keyring\"\n\t\"github.com/alecthomas/kingpin/v2\"\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\tawsconfig \"github.com/aws/aws-sdk-go-v2/config\"\n\t\"github.com/aws/aws-sdk-go-v2/credentials\"\n\t\"github.com/aws/aws-sdk-go-v2/service/sts\"\n\t\"github.com/skratchdot/open-golang/open\"\n)\n\ntype LoginCommandInput struct {\n\tProfileName     string\n\tUseStdout       bool\n\tPath            string\n\tConfig          vault.ProfileConfig\n\tSessionDuration time.Duration\n\tNoSession       bool\n}\n\nfunc ConfigureLoginCommand(app *kingpin.Application, a *AwsVault) {\n\tinput := LoginCommandInput{}\n\n\tcmd := app.Command(\"login\", \"Generate a login link for the AWS Console.\")\n\n\tcmd.Flag(\"duration\", \"Duration of the assume-role or federated session. Defaults to 1h\").\n\t\tShort('d').\n\t\tDurationVar(&input.SessionDuration)\n\n\tcmd.Flag(\"no-session\", \"Skip creating STS session with GetSessionToken\").\n\t\tShort('n').\n\t\tBoolVar(&input.NoSession)\n\n\tcmd.Flag(\"mfa-token\", \"The MFA token to use\").\n\t\tShort('t').\n\t\tStringVar(&input.Config.MfaToken)\n\n\tcmd.Flag(\"path\", \"The AWS service you would like access\").\n\t\tStringVar(&input.Path)\n\n\tcmd.Flag(\"region\", \"The AWS region\").\n\t\tStringVar(&input.Config.Region)\n\n\tcmd.Flag(\"stdout\", \"Print login URL to stdout instead of opening in default browser\").\n\t\tShort('s').\n\t\tBoolVar(&input.UseStdout)\n\n\tcmd.Arg(\"profile\", \"Name of the profile. If none given, credentials will be sourced from env vars\").\n\t\tHintAction(a.MustGetProfileNames).\n\t\tStringVar(&input.ProfileName)\n\n\tcmd.Action(func(c *kingpin.ParseContext) (err error) {\n\t\tinput.Config.MfaPromptMethod = a.PromptDriver(false)\n\t\tinput.Config.NonChainedGetSessionTokenDuration = input.SessionDuration\n\t\tinput.Config.AssumeRoleDuration = input.SessionDuration\n\t\tinput.Config.GetFederationTokenDuration = input.SessionDuration\n\t\tkeyring, err := a.Keyring()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tf, err := a.AwsConfigFile()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = LoginCommand(context.Background(), input, f, keyring)\n\t\tapp.FatalIfError(err, \"login\")\n\t\treturn nil\n\t})\n}\n\nfunc getCredsProvider(input LoginCommandInput, config *vault.ProfileConfig, keyring keyring.Keyring) (credsProvider aws.CredentialsProvider, err error) {\n\tif input.ProfileName == \"\" {\n\t\t// When no profile is specified, source credentials from the environment\n\t\tconfigFromEnv, err := awsconfig.NewEnvConfig()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to authenticate to AWS through your environment variables: %w\", err)\n\t\t}\n\n\t\tif configFromEnv.Credentials.AccessKeyID == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"argument 'profile' not provided, nor any AWS env vars found. Try --help\")\n\t\t}\n\n\t\tcredsProvider = credentials.StaticCredentialsProvider{Value: configFromEnv.Credentials}\n\t} else {\n\t\t// Use a profile from the AWS config file\n\t\tckr := &vault.CredentialKeyring{Keyring: keyring}\n\t\tt := vault.TempCredentialsCreator{\n\t\t\tKeyring:                   ckr,\n\t\t\tDisableSessions:           input.NoSession,\n\t\t\tDisableSessionsForProfile: config.ProfileName,\n\t\t}\n\t\tcredsProvider, err = t.GetProviderForProfile(config)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"profile %s: %w\", input.ProfileName, err)\n\t\t}\n\t}\n\n\treturn credsProvider, err\n}\n\n// LoginCommand creates a login URL for the AWS Management Console using the method described at\n// https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_enable-console-custom-url.html\nfunc LoginCommand(ctx context.Context, input LoginCommandInput, f *vault.ConfigFile, keyring keyring.Keyring) error {\n\tconfig, err := vault.NewConfigLoader(input.Config, f, input.ProfileName).GetProfileConfig(input.ProfileName)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error loading config: %w\", err)\n\t}\n\n\tcredsProvider, err := getCredsProvider(input, config, keyring)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// if we already know the type of credentials being created, avoid calling isCallerIdentityAssumedRole\n\tcanCredsBeUsedInLoginURL, err := canProviderBeUsedForLogin(credsProvider)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif !canCredsBeUsedInLoginURL {\n\t\t// use a static creds provider so that we don't request credentials from AWS more than once\n\t\tcredsProvider, err = createStaticCredentialsProvider(ctx, credsProvider)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// if the credentials have come from an unknown source like credential_process, check the\n\t\t// caller identity to see if it's an assumed role\n\t\tisAssumedRole, err := isCallerIdentityAssumedRole(ctx, credsProvider, config)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif !isAssumedRole {\n\t\t\tlog.Println(\"Creating a federated session\")\n\t\t\tcredsProvider, err = vault.NewFederationTokenProvider(ctx, credsProvider, config)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tcreds, err := credsProvider.Retrieve(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif creds.CanExpire {\n\t\tlog.Printf(\"Requesting a signin token for session expiring in %s\", time.Until(creds.Expires))\n\t}\n\n\tloginURLPrefix, destination := generateLoginURL(config.Region, input.Path)\n\tsigninToken, err := requestSigninToken(ctx, creds, loginURLPrefix)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tloginURL := fmt.Sprintf(\"%s?Action=login&Issuer=aws-vault&Destination=%s&SigninToken=%s\",\n\t\tloginURLPrefix, url.QueryEscape(destination), url.QueryEscape(signinToken))\n\n\tif input.UseStdout {\n\t\tfmt.Println(loginURL)\n\t} else if err = open.Run(loginURL); err != nil {\n\t\treturn fmt.Errorf(\"Failed to open %s: %w\", loginURL, err)\n\t}\n\n\treturn nil\n}\n\nfunc generateLoginURL(region string, path string) (string, string) {\n\tloginURLPrefix := \"https://signin.aws.amazon.com/federation\"\n\tdestination := \"https://console.aws.amazon.com/\"\n\n\tif region != \"\" {\n\t\tdestinationDomain := \"console.aws.amazon.com\"\n\t\tswitch {\n\t\tcase strings.HasPrefix(region, \"cn-\"):\n\t\t\tloginURLPrefix = \"https://signin.amazonaws.cn/federation\"\n\t\t\tdestinationDomain = \"console.amazonaws.cn\"\n\t\tcase strings.HasPrefix(region, \"us-gov-\"):\n\t\t\tloginURLPrefix = \"https://signin.amazonaws-us-gov.com/federation\"\n\t\t\tdestinationDomain = \"console.amazonaws-us-gov.com\"\n\t\t}\n\t\tif path != \"\" {\n\t\t\tdestination = fmt.Sprintf(\"https://%s.%s/%s?region=%s\",\n\t\t\t\tregion, destinationDomain, path, region)\n\t\t} else {\n\t\t\tdestination = fmt.Sprintf(\"https://%s.%s/console/home?region=%s\",\n\t\t\t\tregion, destinationDomain, region)\n\t\t}\n\t}\n\treturn loginURLPrefix, destination\n}\n\nfunc isCallerIdentityAssumedRole(ctx context.Context, credsProvider aws.CredentialsProvider, config *vault.ProfileConfig) (bool, error) {\n\tcfg := vault.NewAwsConfigWithCredsProvider(credsProvider, config.Region, config.STSRegionalEndpoints)\n\tclient := sts.NewFromConfig(cfg)\n\tid, err := client.GetCallerIdentity(ctx, nil)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tarn := aws.ToString(id.Arn)\n\tarnParts := strings.Split(arn, \":\")\n\tif len(arnParts) < 6 {\n\t\treturn false, fmt.Errorf(\"unable to parse ARN: %s\", arn)\n\t}\n\tif strings.HasPrefix(arnParts[5], \"assumed-role\") {\n\t\treturn true, nil\n\t}\n\treturn false, nil\n}\n\nfunc createStaticCredentialsProvider(ctx context.Context, credsProvider aws.CredentialsProvider) (sc credentials.StaticCredentialsProvider, err error) {\n\tcreds, err := credsProvider.Retrieve(ctx)\n\tif err != nil {\n\t\treturn sc, err\n\t}\n\treturn credentials.StaticCredentialsProvider{Value: creds}, nil\n}\n\n// canProviderBeUsedForLogin returns true if the credentials produced by the provider is known to be usable by the login URL endpoint\nfunc canProviderBeUsedForLogin(credsProvider aws.CredentialsProvider) (bool, error) {\n\tif _, ok := credsProvider.(*vault.AssumeRoleProvider); ok {\n\t\treturn true, nil\n\t}\n\tif _, ok := credsProvider.(*vault.SSORoleCredentialsProvider); ok {\n\t\treturn true, nil\n\t}\n\tif _, ok := credsProvider.(*vault.AssumeRoleWithWebIdentityProvider); ok {\n\t\treturn true, nil\n\t}\n\tif c, ok := credsProvider.(*vault.CachedSessionProvider); ok {\n\t\treturn canProviderBeUsedForLogin(c.SessionProvider)\n\t}\n\n\treturn false, nil\n}\n\n// Create a signin token\nfunc requestSigninToken(ctx context.Context, creds aws.Credentials, loginURLPrefix string) (string, error) {\n\tjsonSession, err := json.Marshal(map[string]string{\n\t\t\"sessionId\":    creds.AccessKeyID,\n\t\t\"sessionKey\":   creds.SecretAccessKey,\n\t\t\"sessionToken\": creds.SessionToken,\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", loginURLPrefix, nil)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tq := req.URL.Query()\n\tq.Add(\"Action\", \"getSigninToken\")\n\tq.Add(\"Session\", string(jsonSession))\n\treq.URL.RawQuery = q.Encode()\n\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tdefer resp.Body.Close()\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tlog.Printf(\"Response body was %s\", body)\n\t\treturn \"\", fmt.Errorf(\"Call to getSigninToken failed with %v\", resp.Status)\n\t}\n\n\tvar respParsed map[string]string\n\n\terr = json.Unmarshal(body, &respParsed)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tsigninToken, ok := respParsed[\"SigninToken\"]\n\tif !ok {\n\t\treturn \"\", fmt.Errorf(\"Expected a response with SigninToken\")\n\t}\n\n\treturn signinToken, nil\n}\n"
  },
  {
    "path": "cli/proxy.go",
    "content": "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/kingpin/v2\"\n)\n\nfunc ConfigureProxyCommand(app *kingpin.Application) {\n\tstop := false\n\n\tcmd := app.Command(\"proxy\", \"Start a proxy for the ec2 instance role server locally.\").\n\t\tAlias(\"server\").\n\t\tHidden()\n\n\tcmd.Flag(\"stop\", \"Stop the proxy\").\n\t\tBoolVar(&stop)\n\n\tcmd.Action(func(*kingpin.ParseContext) error {\n\t\tif stop {\n\t\t\tserver.StopProxy()\n\t\t\treturn nil\n\t\t}\n\t\thandleSigTerm()\n\t\treturn server.StartProxy()\n\t})\n}\n\nfunc handleSigTerm() {\n\t// shutdown\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, os.Interrupt, syscall.SIGTERM)\n\tgo func() {\n\t\t<-c\n\t\tserver.Shutdown()\n\t\tos.Exit(1)\n\t}()\n}\n"
  },
  {
    "path": "cli/remove.go",
    "content": "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/vault\"\n\t\"github.com/99designs/keyring\"\n\t\"github.com/alecthomas/kingpin/v2\"\n)\n\ntype RemoveCommandInput struct {\n\tProfileName  string\n\tSessionsOnly bool\n\tForce        bool\n}\n\nfunc ConfigureRemoveCommand(app *kingpin.Application, a *AwsVault) {\n\tinput := RemoveCommandInput{}\n\n\tcmd := app.Command(\"remove\", \"Remove credentials from the secure keystore.\")\n\tcmd.Alias(\"rm\")\n\n\tcmd.Arg(\"profile\", \"Name of the profile\").\n\t\tRequired().\n\t\tHintAction(a.MustGetProfileNames).\n\t\tStringVar(&input.ProfileName)\n\n\tcmd.Flag(\"sessions-only\", \"Only remove sessions, leave credentials intact\").\n\t\tShort('s').\n\t\tHidden().\n\t\tBoolVar(&input.SessionsOnly)\n\n\tcmd.Flag(\"force\", \"Force-remove the profile without a prompt\").\n\t\tShort('f').\n\t\tBoolVar(&input.Force)\n\n\tcmd.Action(func(c *kingpin.ParseContext) error {\n\t\tkeyring, err := a.Keyring()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\terr = RemoveCommand(input, keyring)\n\t\tapp.FatalIfError(err, \"remove\")\n\t\treturn nil\n\t})\n}\n\nfunc RemoveCommand(input RemoveCommandInput, keyring keyring.Keyring) error {\n\tckr := &vault.CredentialKeyring{Keyring: keyring}\n\n\t// Legacy --sessions-only option for backwards compatibility, use aws-vault clear instead\n\tif input.SessionsOnly {\n\t\tsk := &vault.SessionKeyring{Keyring: ckr.Keyring}\n\t\tn, err := sk.RemoveForProfile(input.ProfileName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfmt.Printf(\"Deleted %d sessions.\\n\", n)\n\t\treturn nil\n\t}\n\n\tif !input.Force {\n\t\tr, err := prompt.TerminalPrompt(fmt.Sprintf(\"Delete credentials for profile %q? (y|N) \", input.ProfileName))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif !strings.EqualFold(r, \"y\") && !strings.EqualFold(r, \"yes\") {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tif err := ckr.Remove(input.ProfileName); err != nil {\n\t\treturn err\n\t}\n\tfmt.Printf(\"Deleted credentials.\\n\")\n\n\treturn nil\n}\n"
  },
  {
    "path": "cli/rotate.go",
    "content": "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/99designs/keyring\"\n\t\"github.com/alecthomas/kingpin/v2\"\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\t\"github.com/aws/aws-sdk-go-v2/service/iam\"\n)\n\ntype RotateCommandInput struct {\n\tNoSession   bool\n\tProfileName string\n\tConfig      vault.ProfileConfig\n}\n\nfunc ConfigureRotateCommand(app *kingpin.Application, a *AwsVault) {\n\tinput := RotateCommandInput{}\n\n\tcmd := app.Command(\"rotate\", \"Rotate credentials.\")\n\n\tcmd.Flag(\"no-session\", \"Use master credentials, no session or role used\").\n\t\tShort('n').\n\t\tBoolVar(&input.NoSession)\n\n\tcmd.Arg(\"profile\", \"Name of the profile\").\n\t\tRequired().\n\t\tHintAction(a.MustGetProfileNames).\n\t\tStringVar(&input.ProfileName)\n\n\tcmd.Action(func(c *kingpin.ParseContext) (err error) {\n\t\tinput.Config.MfaPromptMethod = a.PromptDriver(false)\n\t\tkeyring, err := a.Keyring()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tf, err := a.AwsConfigFile()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\terr = RotateCommand(input, f, keyring)\n\t\tapp.FatalIfError(err, \"rotate\")\n\t\treturn nil\n\t})\n}\n\nfunc RotateCommand(input RotateCommandInput, f *vault.ConfigFile, keyring keyring.Keyring) error {\n\tconfigLoader := vault.NewConfigLoader(input.Config, f, input.ProfileName)\n\tconfig, err := configLoader.GetProfileConfig(input.ProfileName)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error loading config: %w\", err)\n\t}\n\n\tckr := &vault.CredentialKeyring{Keyring: keyring}\n\tmasterCredentialsName, err := vault.FindMasterCredentialsNameFor(input.ProfileName, ckr, config)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error determining credential name for '%s': %w\", input.ProfileName, err)\n\t}\n\n\tif input.NoSession {\n\t\tfmt.Printf(\"Rotating credentials stored for profile '%s' using master credentials (takes 10-20 seconds)\\n\", masterCredentialsName)\n\t} else {\n\t\tfmt.Printf(\"Rotating credentials stored for profile '%s' using a session from profile '%s' (takes 10-20 seconds)\\n\", masterCredentialsName, input.ProfileName)\n\t}\n\n\t// Get the existing credentials access key ID\n\toldMasterCreds, err := vault.NewMasterCredentialsProvider(ckr, masterCredentialsName).Retrieve(context.TODO())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error loading source credentials for '%s': %w\", masterCredentialsName, err)\n\t}\n\toldMasterCredsAccessKeyID := vault.FormatKeyForDisplay(oldMasterCreds.AccessKeyID)\n\tlog.Printf(\"Rotating access key %s\\n\", oldMasterCredsAccessKeyID)\n\n\tfmt.Println(\"Creating a new access key\")\n\n\t// create a session to rotate the credentials\n\tvar credsProvider aws.CredentialsProvider\n\tif input.NoSession {\n\t\tcredsProvider = vault.NewMasterCredentialsProvider(ckr, config.ProfileName)\n\t} else {\n\t\t// Can't always disable sessions completely, might need to use session for MFA-Protected API Access\n\t\tcredsProvider, err = vault.NewTempCredentialsProvider(config, ckr, input.NoSession, true)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Error getting temporary credentials: %w\", err)\n\t\t}\n\t}\n\n\tcfg := vault.NewAwsConfigWithCredsProvider(credsProvider, config.Region, config.STSRegionalEndpoints)\n\n\t// A username is needed for some IAM calls if the credentials have assumed a role\n\tiamUserName, err := getUsernameIfAssumingRole(context.TODO(), cfg, config)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tiamClient := iam.NewFromConfig(cfg)\n\t// Create a new access key\n\tcreateOut, err := iamClient.CreateAccessKey(context.TODO(), &iam.CreateAccessKeyInput{\n\t\tUserName: iamUserName,\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating a new access key: %w\", err)\n\t}\n\tfmt.Printf(\"Created new access key %s\\n\", vault.FormatKeyForDisplay(*createOut.AccessKey.AccessKeyId))\n\n\tnewMasterCreds := aws.Credentials{\n\t\tAccessKeyID:     *createOut.AccessKey.AccessKeyId,\n\t\tSecretAccessKey: *createOut.AccessKey.SecretAccessKey,\n\t}\n\n\terr = ckr.Set(masterCredentialsName, newMasterCreds)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error storing new access key %s: %w\", vault.FormatKeyForDisplay(newMasterCreds.AccessKeyID), err)\n\t}\n\n\t// Delete old sessions\n\tsk := &vault.SessionKeyring{Keyring: ckr.Keyring}\n\tprofileNames, err := getProfilesInChain(input.ProfileName, configLoader)\n\tfor _, profileName := range profileNames {\n\t\tif n, _ := sk.RemoveForProfile(profileName); n > 0 {\n\t\t\tfmt.Printf(\"Deleted %d sessions for %s\\n\", n, profileName)\n\t\t}\n\t}\n\n\t// Use new credentials to delete old access key\n\tfmt.Printf(\"Deleting old access key %s\\n\", oldMasterCredsAccessKeyID)\n\terr = retry(time.Second*20, time.Second*2, func() error {\n\t\t_, err = iamClient.DeleteAccessKey(context.TODO(), &iam.DeleteAccessKeyInput{\n\t\t\tAccessKeyId: &oldMasterCreds.AccessKeyID,\n\t\t\tUserName:    iamUserName,\n\t\t})\n\t\treturn err\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Can't delete old access key %s: %w\", oldMasterCredsAccessKeyID, err)\n\t}\n\tfmt.Printf(\"Deleted old access key %s\\n\", oldMasterCredsAccessKeyID)\n\n\tfmt.Println(\"Finished rotating access key\")\n\n\treturn nil\n}\n\nfunc retry(maxTime time.Duration, sleep time.Duration, f func() error) (err error) {\n\tt0 := time.Now()\n\ti := 0\n\tfor {\n\t\ti++\n\n\t\terr = f()\n\t\tif err == nil {\n\t\t\treturn // nolint\n\t\t}\n\n\t\telapsed := time.Since(t0)\n\t\tif elapsed > maxTime {\n\t\t\treturn fmt.Errorf(\"After %d attempts, last error: %s\", i, err)\n\t\t}\n\n\t\ttime.Sleep(sleep)\n\t\tlog.Println(\"Retrying after error:\", err)\n\t}\n}\n\nfunc getUsernameIfAssumingRole(ctx context.Context, awsCfg aws.Config, config *vault.ProfileConfig) (*string, error) {\n\tif config.RoleARN != \"\" {\n\t\tn, err := vault.GetUsernameFromSession(ctx, awsCfg)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"Error getting IAM username from session: %w\", err)\n\t\t}\n\t\tlog.Printf(\"Found IAM username '%s'\", n)\n\t\treturn &n, nil\n\t}\n\treturn nil, nil //nolint\n}\n\nfunc getProfilesInChain(profileName string, configLoader *vault.ConfigLoader) (profileNames []string, err error) {\n\tprofileNames = append(profileNames, profileName)\n\n\tconfig, err := configLoader.GetProfileConfig(profileName)\n\tif err != nil {\n\t\treturn profileNames, err\n\t}\n\n\tif config.SourceProfile != nil {\n\t\tnewProfileNames, err := getProfilesInChain(config.SourceProfileName, configLoader)\n\t\tif err != nil {\n\t\t\treturn profileNames, err\n\t\t}\n\t\tprofileNames = append(profileNames, newProfileNames...)\n\t}\n\n\treturn profileNames, nil\n}\n"
  },
  {
    "path": "contrib/_aws-vault-proxy/Dockerfile",
    "content": "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-vault-proxy ./...\nCMD [\"/usr/local/bin/aws-vault-proxy\"]\n"
  },
  {
    "path": "contrib/_aws-vault-proxy/docker-compose.yml",
    "content": "version: \"2.4\"\nnetworks:\n  aws-vault:\n    driver: bridge\n    ipam:\n      config:\n        - subnet: \"169.254.170.0/24\"\n          gateway: \"169.254.170.1\"\nservices:\n  aws-vault-proxy:\n    build: .\n    environment:\n     - AWS_CONTAINER_CREDENTIALS_FULL_URI\n     - AWS_CONTAINER_AUTHORIZATION_TOKEN\n    networks:\n      aws-vault:\n        ipv4_address: \"169.254.170.2\" # This special IP address is recognized by the AWS SDKs and AWS CLI\n    healthcheck:\n      test: pgrep aws-vault-proxy\n  testapp:\n    image: amazon/aws-cli\n    entrypoint: \"\"\n    command: /bin/bash\n    environment:\n      - AWS_CONTAINER_CREDENTIALS_RELATIVE_URI\n    networks:\n      aws-vault: {}\n      default: {}\n"
  },
  {
    "path": "contrib/_aws-vault-proxy/go.mod",
    "content": "module aws-vault-ecs-server-reverse-proxy\n\ngo 1.17\n\nrequire github.com/gorilla/handlers v1.5.1\n\nrequire github.com/felixge/httpsnoop v1.0.1 // indirect\n"
  },
  {
    "path": "contrib/_aws-vault-proxy/go.sum",
    "content": "github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=\ngithub.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=\ngithub.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=\n"
  },
  {
    "path": "contrib/_aws-vault-proxy/main.go",
    "content": "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 GetReverseProxyTarget() *url.URL {\n\turl, err := url.Parse(os.Getenv(\"AWS_CONTAINER_CREDENTIALS_FULL_URI\"))\n\tif err != nil {\n\t\tlog.Fatalln(\"Bad AWS_CONTAINER_CREDENTIALS_FULL_URI:\", err.Error())\n\t}\n\turl.Host = \"host.docker.internal:\" + url.Port()\n\treturn url\n}\n\nfunc addAuthorizationHeader(authToken string, next http.Handler) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tr.Header.Add(\"Authorization\", authToken)\n\t\tnext.ServeHTTP(w, r)\n\t}\n}\n\nfunc main() {\n\ttarget := GetReverseProxyTarget()\n\tauthToken := os.Getenv(\"AWS_CONTAINER_AUTHORIZATION_TOKEN\")\n\tlog.Printf(\"reverse proxying target:%s auth:%s\\n\", target, authToken)\n\n\thandler := handlers.LoggingHandler(os.Stderr,\n\t\taddAuthorizationHeader(authToken,\n\t\t\thttputil.NewSingleHostReverseProxy(target)))\n\n\t_ = http.ListenAndServe(\":80\", handler)\n}\n"
  },
  {
    "path": "contrib/completions/bash/aws-vault.bash",
    "content": "_aws-vault_bash_autocomplete() {\n    local i cur prev opts base\n\n    for (( i=1; i < COMP_CWORD; i++ )); do\n        if [[ ${COMP_WORDS[i]} == -- ]]; then\n            _command_offset $i+1\n            return\n        fi\n    done\n\n    COMPREPLY=()\n    cur=\"${COMP_WORDS[COMP_CWORD]}\"\n    opts=$( ${COMP_WORDS[0]} --completion-bash \"${COMP_WORDS[@]:1:$COMP_CWORD}\" )\n    COMPREPLY=( $(compgen -W \"${opts}\" -- ${cur}) )\n    return 0\n}\ncomplete -F _aws-vault_bash_autocomplete -o default aws-vault\n"
  },
  {
    "path": "contrib/completions/fish/aws-vault.fish",
    "content": "if status --is-interactive\n  complete -ec aws-vault\n\n  # switch based on seeing a `--`\n  complete -c aws-vault -n 'not __fish_aws_vault_is_commandline' -xa '(__fish_aws_vault_complete_arg)'\n  complete -c aws-vault -n '__fish_aws_vault_is_commandline' -xa '(__fish_aws_vault_complete_commandline)'\n\n  function __fish_aws_vault_is_commandline\n    string match -q -r '^--$' -- (commandline -opc)\n  end\n\n  function __fish_aws_vault_complete_arg\n    set -l parts (commandline -opc)\n    set -e parts[1]\n\n    aws-vault --completion-bash $parts\n  end\n\n  function __fish_aws_vault_complete_commandline\n    set -l parts (string split --max 1 '--' -- (commandline -pc))\n\n    complete \"-C$parts[2]\"\n  end\nend\n"
  },
  {
    "path": "contrib/completions/zsh/aws-vault.zsh",
    "content": "#compdef aws-vault\n\n_aws-vault() {\n    local i\n    for (( i=2; i < CURRENT; i++ )); do\n        if [[ ${words[i]} == -- ]]; then\n            shift $i words\n            (( CURRENT -= i ))\n            _normal\n            return\n        fi\n    done\n\n    local matches=($(${words[1]} --completion-bash ${(@)words[2,$CURRENT]}))\n    compadd -a matches\n\n    if [[ $compstate[nmatches] -eq 0 && $words[$CURRENT] != -* ]]; then\n        _files\n    fi\n}\n\nif [[ \"$(basename -- ${(%):-%x})\" != \"_aws-vault\" ]]; then\n    compdef _aws-vault aws-vault\nfi\n"
  },
  {
    "path": "contrib/docker/Dockerfile",
    "content": "FROM debian:bullseye-slim\nRUN apt update && apt install -y curl\nRUN 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\nENV AWS_VAULT_BACKEND=file\nENTRYPOINT [\"/usr/local/bin/aws-vault\"]\n\n# Example usage:\n#     docker build -t aws-vault .\n#     docker run -it -e COLUMNS=$(tput cols) -v ~/.aws/config:/root/.aws/config -v ~/.awsvault:/root/.awsvault aws-vault\n"
  },
  {
    "path": "contrib/scripts/aws-configure-with-env-vars.sh",
    "content": "#!/bin/sh\n# Configure aws-cli using the AWS env vars created with aws-vault\n#\n# Usage: aws-vault exec <SOURCE_PROFILE> -- aws-configure-with-env-vars.sh [TARGET_PROFILE]\n#\n\nset -eu\n\naws configure --profile \"${1:-$AWS_VAULT}\" set region \"$AWS_REGION\"\naws configure --profile \"${1:-$AWS_VAULT}\" set aws_access_key_id \"$AWS_ACCESS_KEY_ID\"\naws configure --profile \"${1:-$AWS_VAULT}\" set aws_secret_access_key \"$AWS_SECRET_ACCESS_KEY\"\naws configure --profile \"${1:-$AWS_VAULT}\" set aws_session_token \"${AWS_SESSION_TOKEN:-}\"\n"
  },
  {
    "path": "contrib/scripts/aws-iam-create-yubikey-mfa.sh",
    "content": "#!/bin/sh\n# Adds a Yubikey TOTP device to IAM using your IAM User as the $MFA_DEVICE_NAME\n# Currently, aws iam enable-mfa-device doesn't support specifying your MFA Device Name.\n\nset -eu\n\nif [ -n \"${AWS_SESSION_TOKEN:-}\" ]; then\n  echo \"aws-vault must be run without a STS session, please run it with the --no-session flag\" >&2\n  exit 1\nfi\n\nACCOUNT_ARN=$(aws sts get-caller-identity --query Arn --output text)\n\n# Assume that the final portion of the ARN is the username\n# Works for ARNs like `users/<user>` and `users/engineers/<user>`\nUSERNAME=$(echo \"$ACCOUNT_ARN\" | rev | cut -d/ -f1 | rev)\n\nOUTFILE=$(mktemp)\ntrap 'rm -f \"$OUTFILE\"' EXIT\n\nSERIAL_NUMBER=$(aws iam create-virtual-mfa-device \\\n  --virtual-mfa-device-name \"$USERNAME\" \\\n  --bootstrap-method Base32StringSeed \\\n  --outfile \"$OUTFILE\" \\\n  --query VirtualMFADevice.SerialNumber \\\n  --output text)\n\nykman oath accounts add -ft \"$SERIAL_NUMBER\" < \"$OUTFILE\" 2> /dev/null\n\nCODE1=$(ykman oath accounts code -s \"$SERIAL_NUMBER\")\n\nWAIT_TIME=$((30-$(date +%s)%30))\necho \"Waiting $WAIT_TIME seconds before generating a second code\" >&2\nsleep $WAIT_TIME\n\nCODE2=$(ykman oath accounts code -s \"$SERIAL_NUMBER\")\n\naws iam enable-mfa-device \\\n  --user-name \"$USERNAME\" \\\n  --serial-number \"$SERIAL_NUMBER\" \\\n  --authentication-code1 \"$CODE1\" \\\n  --authentication-code2 \"$CODE2\"\n"
  },
  {
    "path": "contrib/scripts/aws-iam-resync-yubikey-mfa.sh",
    "content": "#!/bin/sh\n# Resync a Yubikey TOTP device to IAM using your IAM User as the $MFA_DEVICE_NAME\n# Currently, aws iam resync-mfa-device doesn't support specifying your MFA Device Name.\n\nset -eu\n\nACCOUNT_ARN=$(aws sts get-caller-identity --query Arn --output text)\n\n# Assume that the final portion of the ARN is the username\n# Works for ARNs like `users/<user>` and `users/engineers/<user>`\nUSERNAME=$(echo \"$ACCOUNT_ARN\" | rev | cut -d/ -f1 | rev)\n\nACCOUNT_ID=$(echo \"$ACCOUNT_ARN\" | cut -d: -f5)\nSERIAL_NUMBER=\"arn:aws:iam::${ACCOUNT_ID}:mfa/${USERNAME}\"\n\nCODE1=$(ykman oath accounts code -s \"$SERIAL_NUMBER\")\n\nWAIT_TIME=$((30-$(date +%s)%30))\necho \"Waiting $WAIT_TIME seconds before generating a second code\" >&2\nsleep $WAIT_TIME\n\nCODE2=$(ykman oath accounts code -s \"$SERIAL_NUMBER\")\n\naws iam resync-mfa-device \\\n  --user-name \"$USERNAME\" \\\n  --serial-number \"$SERIAL_NUMBER\" \\\n  --authentication-code1 \"$CODE1\" \\\n  --authentication-code2 \"$CODE2\"\n"
  },
  {
    "path": "go.mod",
    "content": "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/kingpin/v2 v2.3.2\n\tgithub.com/aws/aws-sdk-go-v2 v1.17.7\n\tgithub.com/aws/aws-sdk-go-v2/config v1.18.19\n\tgithub.com/aws/aws-sdk-go-v2/credentials v1.13.18\n\tgithub.com/aws/aws-sdk-go-v2/service/iam v1.19.8\n\tgithub.com/aws/aws-sdk-go-v2/service/sso v1.12.6\n\tgithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6\n\tgithub.com/aws/aws-sdk-go-v2/service/sts v1.18.7\n\tgithub.com/google/go-cmp v0.5.9\n\tgithub.com/mattn/go-isatty v0.0.18\n\tgithub.com/mattn/go-tty v0.0.4\n\tgithub.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966\n\tgolang.org/x/term v0.6.0\n\tgopkg.in/ini.v1 v1.67.0\n)\n\nrequire (\n\tgithub.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect\n\tgithub.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25 // indirect\n\tgithub.com/aws/smithy-go v1.13.5 // indirect\n\tgithub.com/danieljoos/wincred v1.1.2 // indirect\n\tgithub.com/dvsekhvalnov/jose2go v1.5.0 // indirect\n\tgithub.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect\n\tgithub.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect\n\tgithub.com/mtibben/percent v0.2.1 // indirect\n\tgithub.com/xhit/go-str2duration/v2 v2.1.0 // indirect\n\tgolang.org/x/sys v0.6.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=\ngithub.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=\ngithub.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=\ngithub.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=\ngithub.com/alecthomas/kingpin/v2 v2.3.2 h1:H0aULhgmSzN8xQ3nX1uxtdlTHYoPLu5AhHxWrKI6ocU=\ngithub.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=\ngithub.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=\ngithub.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=\ngithub.com/aws/aws-sdk-go-v2 v1.17.7 h1:CLSjnhJSTSogvqUGhIC6LqFKATMRexcxLZ0i/Nzk9Eg=\ngithub.com/aws/aws-sdk-go-v2 v1.17.7/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=\ngithub.com/aws/aws-sdk-go-v2/config v1.18.19 h1:AqFK6zFNtq4i1EYu+eC7lcKHYnZagMn6SW171la0bGw=\ngithub.com/aws/aws-sdk-go-v2/config v1.18.19/go.mod h1:XvTmGMY8d52ougvakOv1RpiTLPz9dlG/OQHsKU/cMmY=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.13.18 h1:EQMdtHwz0ILTW1hoP+EwuWhwCG1hD6l3+RWFQABET4c=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.13.18/go.mod h1:vnwlwjIe+3XJPBYKu1et30ZPABG3VaXJYr8ryohpIyM=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1 h1:gt57MN3liKiyGopcqgNzJb2+d9MJaKT/q1OksHNXVE4=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.1/go.mod h1:lfUx8puBRdM5lVVMQlwt2v+ofiG/X6Ms+dy0UkG/kXw=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 h1:sJLYcS+eZn5EeNINGHSCRAwUJMFVqklwkH36Vbyai7M=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31/go.mod h1:QT0BqUvX1Bh2ABdTGnjqEjvjzrCfIniM9Sc8zn9Yndo=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 h1:1mnRASEKnkqsntcxHaysxwgVoUUp5dkiB+l3llKnqyg=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25/go.mod h1:zBHOPwhBc3FlQjQJE/D3IfPWiWaQmT06Vq9aNukDo0k=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.3.32 h1:p5luUImdIqywn6JpQsW3tq5GNOxKmOnEpybzPx+d1lk=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.3.32/go.mod h1:XGhIBZDEgfqmFIugclZ6FU7v75nHhBDtzuB4xB/tEi4=\ngithub.com/aws/aws-sdk-go-v2/service/iam v1.19.8 h1:kQsBeGgm68kT0xc90spgC5qEOQGH74V2bFqgBgG21Bo=\ngithub.com/aws/aws-sdk-go-v2/service/iam v1.19.8/go.mod h1:lf/oAjt//UvPsmnOgPT61F+q4K6U0q4zDd1s1yx2NZs=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25 h1:5LHn8JQ0qvjD9L9JhMtylnkcw7j05GDZqM9Oin6hpr0=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.25/go.mod h1:/95IA+0lMnzW6XzqYJRpjjsAbKEORVeO0anQqjd2CNU=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.12.6 h1:5V7DWLBd7wTELVz5bPpwzYy/sikk0gsgZfj40X+l5OI=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.12.6/go.mod h1:Y1VOmit/Fn6Tz1uFAeCO6Q7M2fmfXSCLeL5INVYsLuY=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6 h1:B8cauxOH1W1v7rd8RdI/MWnoR4Ze0wIHWrb90qczxj4=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.6/go.mod h1:Lh/bc9XUf8CfOY6Jp5aIkQtN+j1mc+nExc+KXj9jx2s=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.18.7 h1:bWNgNdRko2x6gqa0blfATqAZKZokPIeM1vfmQt2pnvM=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.18.7/go.mod h1:JuTnSoeePXmMVe9G8NcjjwgOKEfZ4cOjMuT2IBT/2eI=\ngithub.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=\ngithub.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=\ngithub.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=\ngithub.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM=\ngithub.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=\ngithub.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=\ngithub.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=\ngithub.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=\ngithub.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=\ngithub.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=\ngithub.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=\ngithub.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=\ngithub.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=\ngithub.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=\ngithub.com/mattn/go-tty v0.0.4 h1:NVikla9X8MN0SQAqCYzpGyXv0jY7MNl3HOWD2dkle7E=\ngithub.com/mattn/go-tty v0.0.4/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3pxse28=\ngithub.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=\ngithub.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=\ngithub.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=\ngithub.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=\ngithub.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=\ngolang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=\ngolang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=\ngopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=\ngopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\n"
  },
  {
    "path": "iso8601/iso8601.go",
    "content": "package iso8601\n\nimport \"time\"\n\n// Format outputs an ISO-8601 datetime string from the given time,\n// in a format compatible with all of the AWS SDKs\nfunc Format(t time.Time) string {\n\treturn t.UTC().Format(time.RFC3339)\n}\n"
  },
  {
    "path": "iso8601/iso8601_test.go",
    "content": "package iso8601\n\nimport (\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestFormat(t *testing.T) {\n\tinput, _ := time.Parse(time.RFC3339, \"2009-02-04T21:00:57-08:00\")\n\twant := \"2009-02-05T05:00:57Z\"\n\tresult := Format(input)\n\tif result != want {\n\t\tt.Errorf(\"expected %s for %q got %s\", want, input, result)\n\t}\n}\n\nfunc TestFormatForIssue655(t *testing.T) {\n\tinput, _ := time.Parse(time.RFC3339, \"2020-09-10T18:16:52+02:00\")\n\twant := \"2020-09-10T16:16:52Z\"\n\tresult := Format(input)\n\tif result != want {\n\t\tt.Errorf(\"expected %s for %q got %s\", want, input, result)\n\t}\n}\n"
  },
  {
    "path": "main.go",
    "content": "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 is provided at compile time\nvar Version = \"dev\"\n\nfunc main() {\n\tapp := kingpin.New(\"aws-vault\", \"A vault for securely storing and accessing AWS credentials in development environments.\")\n\tapp.Version(Version)\n\n\ta := cli.ConfigureGlobals(app)\n\tcli.ConfigureAddCommand(app, a)\n\tcli.ConfigureRemoveCommand(app, a)\n\tcli.ConfigureListCommand(app, a)\n\tcli.ConfigureRotateCommand(app, a)\n\tcli.ConfigureExecCommand(app, a)\n\tcli.ConfigureExportCommand(app, a)\n\tcli.ConfigureClearCommand(app, a)\n\tcli.ConfigureLoginCommand(app, a)\n\tcli.ConfigureProxyCommand(app)\n\n\tkingpin.MustParse(app.Parse(os.Args[1:]))\n}\n"
  },
  {
    "path": "prompt/kdialog.go",
    "content": "package prompt\n\nimport (\n\t\"os/exec\"\n\t\"strings\"\n)\n\nfunc KDialogMfaPrompt(mfaSerial string) (string, error) {\n\tcmd := exec.Command(\"kdialog\", \"--inputbox\", mfaPromptMessage(mfaSerial), \"--title\", \"aws-vault\")\n\n\tout, err := cmd.Output()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn strings.TrimSpace(string(out)), nil\n}\n\nfunc init() {\n\tif _, err := exec.LookPath(\"kdialog\"); err == nil {\n\t\tMethods[\"kdialog\"] = KDialogMfaPrompt\n\t}\n}\n"
  },
  {
    "path": "prompt/osascript.go",
    "content": "package prompt\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n\t\"strings\"\n)\n\nfunc OSAScriptMfaPrompt(mfaSerial string) (string, error) {\n\tcmd := exec.Command(\"osascript\", \"-e\", fmt.Sprintf(`\n\t\tdisplay dialog %q default answer \"\" buttons {\"OK\", \"Cancel\"} default button 1\n        text returned of the result\n        return result`,\n\t\tmfaPromptMessage(mfaSerial)))\n\n\tout, err := cmd.Output()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn strings.TrimSpace(string(out)), nil\n}\n\nfunc init() {\n\tif _, err := exec.LookPath(\"osascript\"); err == nil {\n\t\tMethods[\"osascript\"] = OSAScriptMfaPrompt\n\t}\n}\n"
  },
  {
    "path": "prompt/prompt.go",
    "content": "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 Available() []string {\n\tmethods := []string{}\n\tfor k := range Methods {\n\t\tmethods = append(methods, k)\n\t}\n\tsort.Strings(methods)\n\treturn methods\n}\n\nfunc Method(s string) Func {\n\tm, ok := Methods[s]\n\tif !ok {\n\t\tpanic(fmt.Sprintf(\"Prompt method %q doesn't exist\", s))\n\t}\n\treturn m\n}\n\nfunc mfaPromptMessage(mfaSerial string) string {\n\treturn fmt.Sprintf(\"Enter MFA code for %s: \", mfaSerial)\n}\n"
  },
  {
    "path": "prompt/terminal.go",
    "content": "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, error) {\n\ttty, err := tty.Open()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer tty.Close()\n\n\tfmt.Fprint(tty.Output(), message)\n\n\ttext, err := tty.ReadString()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn strings.TrimSpace(text), nil\n}\n\nfunc TerminalSecretPrompt(message string) (string, error) {\n\ttty, err := tty.Open()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer tty.Close()\n\n\tfmt.Fprint(tty.Output(), message)\n\n\ttext, err := tty.ReadPassword()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn strings.TrimSpace(text), nil\n}\n\nfunc TerminalMfaPrompt(mfaSerial string) (string, error) {\n\treturn TerminalPrompt(mfaPromptMessage(mfaSerial))\n}\n\nfunc init() {\n\tMethods[\"terminal\"] = TerminalMfaPrompt\n}\n"
  },
  {
    "path": "prompt/wincredui_windows.go",
    "content": "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      = 0x00080\n\tCREDUI_FLAGS_GENERIC_CREDENTIALS = 0x40000\n\tCREDUI_FLAGS_KEEP_USERNAME       = 0x100000\n)\n\ntype creduiInfoA struct {\n\tcbSize         uint32\n\thwndParent     uintptr\n\tpszMessageText *uint16\n\tpszCaptionText *uint16\n\thbmBanner      uintptr\n}\n\nfunc WinCredUiPrompt(mfaSerial string) (string, error) {\n\tinfo := &creduiInfoA{\n\t\thwndParent:     0,\n\t\tpszCaptionText: syscall.StringToUTF16Ptr(\"Enter MFA code for aws-vault\"),\n\t\tpszMessageText: syscall.StringToUTF16Ptr(mfaPromptMessage(mfaSerial)),\n\t\thbmBanner:      0,\n\t}\n\tinfo.cbSize = uint32(unsafe.Sizeof(*info))\n\tpasswordBuf := make([]uint16, 64)\n\tsave := false\n\tflags := CREDUI_FLAGS_ALWAYS_SHOW_UI | CREDUI_FLAGS_KEEP_USERNAME | CREDUI_FLAGS_GENERIC_CREDENTIALS\n\tshortSerial := strings.ReplaceAll(strings.ReplaceAll(mfaSerial, \"arn:aws:iam::\", \"\"), \":mfa\", \"\")\n\n\tret, _, _ := syscall.NewLazyDLL(\"credui.dll\").NewProc(\"CredUIPromptForCredentialsW\").Call(\n\t\tuintptr(unsafe.Pointer(info)),\n\t\tuintptr(unsafe.Pointer(syscall.StringBytePtr(\"aws-vault\"))),\n\t\t0,\n\t\t0,\n\t\tuintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(shortSerial))),\n\t\tuintptr(len(shortSerial)+1),\n\t\tuintptr(unsafe.Pointer(&passwordBuf[0])),\n\t\t64,\n\t\tuintptr(unsafe.Pointer(&save)),\n\t\tuintptr(flags),\n\t)\n\tif ret != 0 {\n\t\treturn \"\", errors.New(\"wincredui: call to CredUIPromptForCredentialsW failed\")\n\t}\n\n\treturn strings.TrimSpace(syscall.UTF16ToString(passwordBuf)), nil\n}\n\nfunc init() {\n\tMethods[\"wincredui\"] = WinCredUiPrompt\n}\n"
  },
  {
    "path": "prompt/ykman.go",
    "content": "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-TOTP token from the Yubikey device\n// To set up ykman, first run `ykman oath accounts add`\nfunc YkmanMfaProvider(mfaSerial string) (string, error) {\n\targs := []string{}\n\n\tyubikeyOathCredName := os.Getenv(\"YKMAN_OATH_CREDENTIAL_NAME\")\n\tif yubikeyOathCredName == \"\" {\n\t\tyubikeyOathCredName = mfaSerial\n\t}\n\n\t// Get the serial number of the yubikey device to use.\n\tyubikeyDeviceSerial := os.Getenv(\"YKMAN_OATH_DEVICE_SERIAL\")\n\tif yubikeyDeviceSerial != \"\" {\n\t\t// If the env var was set, extend args to support passing the serial.\n\t\targs = append(args, \"--device\", yubikeyDeviceSerial)\n\t}\n\n\t// default to v4 and above\n\tswitch os.Getenv(\"AWS_VAULT_YKMAN_VERSION\") {\n\tcase \"1\", \"2\", \"3\":\n\t\targs = append(args, \"oath\", \"code\", \"--single\", yubikeyOathCredName)\n\tdefault:\n\t\targs = append(args, \"oath\", \"accounts\", \"code\", \"--single\", yubikeyOathCredName)\n\t}\n\n\tlog.Printf(\"Fetching MFA code using `ykman %s`\", strings.Join(args, \" \"))\n\tcmd := exec.Command(\"ykman\", args...)\n\tcmd.Stderr = os.Stderr\n\n\tout, err := cmd.Output()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"ykman: %w\", err)\n\t}\n\n\treturn strings.TrimSpace(string(out)), nil\n}\n\nfunc init() {\n\tif _, err := exec.LookPath(\"ykman\"); err == nil {\n\t\tMethods[\"ykman\"] = YkmanMfaProvider\n\t}\n}\n"
  },
  {
    "path": "prompt/zenity.go",
    "content": "package prompt\n\nimport (\n\t\"os/exec\"\n\t\"strings\"\n)\n\nfunc ZenityMfaPrompt(mfaSerial string) (string, error) {\n\tcmd := exec.Command(\"zenity\", \"--entry\", \"--title\", \"aws-vault\", \"--text\", mfaPromptMessage(mfaSerial))\n\n\tout, err := cmd.Output()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn strings.TrimSpace(string(out)), nil\n}\n\nfunc init() {\n\tif _, err := exec.LookPath(\"zenity\"); err == nil {\n\t\tMethods[\"zenity\"] = ZenityMfaPrompt\n\t}\n}\n"
  },
  {
    "path": "server/ec2alias_bsd.go",
    "content": "//go:build darwin || freebsd || openbsd\n// +build darwin freebsd openbsd\n\npackage server\n\nimport \"os/exec\"\n\nfunc installEc2EndpointNetworkAlias() ([]byte, error) {\n\treturn exec.Command(\"ifconfig\", \"lo0\", \"alias\", \"169.254.169.254\").CombinedOutput()\n}\n\nfunc removeEc2EndpointNetworkAlias() ([]byte, error) {\n\treturn exec.Command(\"ifconfig\", \"lo0\", \"-alias\", \"169.254.169.254\").CombinedOutput()\n}\n"
  },
  {
    "path": "server/ec2alias_linux.go",
    "content": "//go:build linux\n// +build linux\n\npackage server\n\nimport \"os/exec\"\n\nfunc installEc2EndpointNetworkAlias() ([]byte, error) {\n\treturn exec.Command(\"ip\", \"addr\", \"add\", \"169.254.169.254/24\", \"dev\", \"lo\", \"label\", \"lo:0\").CombinedOutput()\n}\n\nfunc removeEc2EndpointNetworkAlias() ([]byte, error) {\n\treturn exec.Command(\"ip\", \"addr\", \"del\", \"169.254.169.254/24\", \"dev\", \"lo\", \"label\", \"lo:0\").CombinedOutput()\n}\n"
  },
  {
    "path": "server/ec2alias_windows.go",
    "content": "//go:build windows\n// +build windows\n\npackage server\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n\t\"strings\"\n)\n\nvar alreadyRegisteredLocalised = []string{\n\t\"The object already exists\",\n\t\"Das Objekt ist bereits vorhanden\",\n\t\"El objeto ya existe\",\n}\n\nvar runAsAdministratorLocalised = []string{\n\t\"Run as administrator\",\n\t// truncate before 'Umlaut' to avoid encoding problems coming from Windows cmd\n\t\"Als Administrator ausf\",\n\t\"Ejecutar como administrador\",\n}\n\nfunc msgFound(localised []string, toTest string) bool {\n\tfor _, value := range localised {\n\t\tif strings.Contains(toTest, value) {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc runAndWrapAdminErrors(name string, arg ...string) ([]byte, error) {\n\tout, err := exec.Command(name, arg...).CombinedOutput()\n\tif msgFound(runAsAdministratorLocalised, string(out)) {\n\t\terr = fmt.Errorf(\"Creation of network alias for server mode requires elevated permissions, run as administrator\", err)\n\t}\n\n\treturn out, err\n}\n\nfunc installEc2EndpointNetworkAlias() ([]byte, error) {\n\tout, err := runAndWrapAdminErrors(\"netsh\", \"interface\", \"ipv4\", \"add\", \"address\", \"Loopback Pseudo-Interface 1\", \"169.254.169.254\", \"255.255.0.0\")\n\tif msgFound(alreadyRegisteredLocalised, string(out)) {\n\t\treturn []byte{}, nil\n\t}\n\n\treturn out, err\n}\n\nfunc removeEc2EndpointNetworkAlias() ([]byte, error) {\n\treturn runAndWrapAdminErrors(\"netsh\", \"interface\", \"ipv4\", \"delete\", \"address\", \"Loopback Pseudo-Interface 1\", \"169.254.169.254\", \"255.255.0.0\")\n}\n"
  },
  {
    "path": "server/ec2proxy.go",
    "content": "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\nconst (\n\tec2MetadataEndpointIP   = \"169.254.169.254\"\n\tec2MetadataEndpointAddr = \"169.254.169.254:80\"\n)\n\n// StartProxy starts a http proxy server that listens on the standard EC2 Instance Metadata endpoint http://169.254.169.254:80/\n// and forwards requests through to the running `aws-vault exec` command\nfunc StartProxy() error {\n\tvar localServerURL, err = url.Parse(fmt.Sprintf(\"http://%s/\", ec2CredentialsServerAddr))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif output, err := installEc2EndpointNetworkAlias(); err != nil {\n\t\treturn fmt.Errorf(\"%s: %s\", strings.TrimSpace(string(output)), err.Error())\n\t}\n\n\tl, err := net.Listen(\"tcp\", ec2MetadataEndpointAddr)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\thandler := http.NewServeMux()\n\thandler.HandleFunc(\"/stop\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t\tgo Shutdown()\n\t})\n\thandler.Handle(\"/\", httputil.NewSingleHostReverseProxy(localServerURL))\n\n\tlog.Printf(\"EC2 Instance Metadata endpoint proxy server running on %s\", l.Addr())\n\treturn http.Serve(l, handler)\n}\n\nfunc IsProxyRunning() bool {\n\t_, err := net.DialTimeout(\"tcp\", ec2MetadataEndpointAddr, time.Millisecond*10)\n\treturn err == nil\n}\n\nfunc Shutdown() {\n\t_, err := removeEc2EndpointNetworkAlias()\n\tif err != nil {\n\t\tlog.Fatalln(err)\n\t}\n\tos.Exit(0)\n}\n\n// StopProxy stops the http proxy server on the standard EC2 Instance Metadata endpoint\nfunc StopProxy() {\n\t_, _ = http.Get(fmt.Sprintf(\"http://%s/stop\", ec2MetadataEndpointAddr)) //nolint\n}\n\nfunc awsVaultExecutable() string {\n\tawsVaultPath, err := os.Executable()\n\tif err != nil {\n\t\treturn awsVaultPath\n\t}\n\n\treturn os.Args[0]\n}\n"
  },
  {
    "path": "server/ec2proxy_default.go",
    "content": "//go:build !darwin && !freebsd && !openbsd && !linux\n// +build !darwin,!freebsd,!openbsd,!linux\n\npackage server\n\nimport (\n\t\"errors\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"time\"\n)\n\n// StartEc2EndpointProxyServerProcess starts a `aws-vault proxy` process\nfunc StartEc2EndpointProxyServerProcess() error {\n\tlog.Println(\"Starting `aws-vault proxy`\")\n\tcmd := exec.Command(awsVaultExecutable(), \"proxy\")\n\tcmd.Stdin = os.Stdin\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\tif err := cmd.Start(); err != nil {\n\t\treturn err\n\t}\n\ttime.Sleep(time.Second * 1)\n\tif !IsProxyRunning() {\n\t\treturn 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\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "server/ec2proxy_unix.go",
    "content": "//go:build darwin || freebsd || openbsd || linux\n// +build darwin freebsd openbsd linux\n\npackage server\n\nimport (\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n)\n\n// StartEc2EndpointProxyServerProcess starts a `aws-vault proxy` process\nfunc StartEc2EndpointProxyServerProcess() error {\n\tlog.Println(\"Starting `aws-vault proxy` as root in the background\")\n\tcmd := exec.Command(\"sudo\", \"-b\", awsVaultExecutable(), \"proxy\")\n\tcmd.Stdin = os.Stdin\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\treturn cmd.Run()\n}\n"
  },
  {
    "path": "server/ec2server.go",
    "content": "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/aws-vault/v7/iso8601\"\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n)\n\nconst ec2CredentialsServerAddr = \"127.0.0.1:9099\"\n\n// StartEc2CredentialsServer starts a EC2 Instance Metadata server and endpoint proxy\nfunc StartEc2CredentialsServer(ctx context.Context, credsProvider aws.CredentialsProvider, region string) error {\n\tcredsCache := aws.NewCredentialsCache(credsProvider)\n\n\t// pre-fetch credentials so that we can respond quickly to the first request\n\t// SDKs seem to very aggressively timeout\n\t_, _ = credsCache.Retrieve(ctx)\n\n\tgo startEc2CredentialsServer(credsCache, region)\n\n\treturn nil\n}\n\nfunc startEc2CredentialsServer(credsProvider aws.CredentialsProvider, region string) {\n\tlog.Printf(\"Starting EC2 Instance Metadata server on %s\", ec2CredentialsServerAddr)\n\trouter := http.NewServeMux()\n\n\trouter.HandleFunc(\"/latest/meta-data/iam/security-credentials/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintf(w, \"local-credentials\")\n\t})\n\n\t// The AWS Go SDK checks the instance-id endpoint to validate the existence of EC2 Metadata\n\trouter.HandleFunc(\"/latest/meta-data/instance-id/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintf(w, \"aws-vault\")\n\t})\n\n\t// The AWS .NET SDK checks this endpoint during obtaining credentials/refreshing them\n\trouter.HandleFunc(\"/latest/meta-data/iam/info/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintf(w, `{\"Code\" : \"Success\"}`)\n\t})\n\n\t// used by AWS SDK to determine region\n\trouter.HandleFunc(\"/latest/dynamic/instance-identity/document\", func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintf(w, `{\"region\": \"`+region+`\"}`)\n\t})\n\n\trouter.HandleFunc(\"/latest/meta-data/iam/security-credentials/local-credentials\", credsHandler(credsProvider))\n\n\tlog.Fatalln(http.ListenAndServe(ec2CredentialsServerAddr, withLogging(withSecurityChecks(router))))\n}\n\n// withSecurityChecks is middleware to protect the server from attack vectors\nfunc withSecurityChecks(next *http.ServeMux) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\t// Check the remote ip is from the loopback, otherwise clients on the same network segment could\n\t\t// potentially route traffic via 169.254.169.254:80\n\t\t// See https://developer.apple.com/library/content/qa/qa1357/_index.html\n\t\tip, _, err := net.SplitHostPort(r.RemoteAddr)\n\t\tif err != nil {\n\t\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tif !net.ParseIP(ip).IsLoopback() {\n\t\t\thttp.Error(w, \"Access denied from non-localhost address\", http.StatusUnauthorized)\n\t\t\treturn\n\t\t}\n\n\t\t// Check that the request is to 169.254.169.254\n\t\t// Without this it's possible for an attacker to mount a DNS rebinding attack\n\t\t// See https://github.com/99designs/aws-vault/issues/578\n\t\tif r.Host != ec2MetadataEndpointIP && r.Host != ec2MetadataEndpointAddr {\n\t\t\thttp.Error(w, fmt.Sprintf(\"Access denied for host '%s'\", r.Host), http.StatusUnauthorized)\n\t\t\treturn\n\t\t}\n\n\t\tnext.ServeHTTP(w, r)\n\t}\n}\n\nfunc credsHandler(credsProvider aws.CredentialsProvider) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tcreds, err := credsProvider.Retrieve(r.Context())\n\t\tif err != nil {\n\t\t\thttp.Error(w, err.Error(), http.StatusGatewayTimeout)\n\t\t\treturn\n\t\t}\n\n\t\tlog.Printf(\"Serving credentials via http ****************%s, expiration of %s (%s)\",\n\t\t\tcreds.AccessKeyID[len(creds.AccessKeyID)-4:],\n\t\t\tcreds.Expires.Format(time.RFC3339),\n\t\t\ttime.Until(creds.Expires).String())\n\n\t\terr = json.NewEncoder(w).Encode(map[string]interface{}{\n\t\t\t\"Code\":            \"Success\",\n\t\t\t\"LastUpdated\":     iso8601.Format(time.Now()),\n\t\t\t\"Type\":            \"AWS-HMAC\",\n\t\t\t\"AccessKeyId\":     creds.AccessKeyID,\n\t\t\t\"SecretAccessKey\": creds.SecretAccessKey,\n\t\t\t\"Token\":           creds.SessionToken,\n\t\t\t\"Expiration\":      iso8601.Format(creds.Expires),\n\t\t})\n\t\tif err != nil {\n\t\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "server/ecsserver.go",
    "content": "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\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/99designs/aws-vault/v7/iso8601\"\n\t\"github.com/99designs/aws-vault/v7/vault\"\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\t\"github.com/aws/aws-sdk-go-v2/service/sts\"\n)\n\nfunc writeErrorMessage(w http.ResponseWriter, msg string, statusCode int) {\n\tw.Header().Set(\"Content-Type\", \"application/json; charset=utf-8\")\n\tw.WriteHeader(statusCode)\n\tif err := json.NewEncoder(w).Encode(map[string]string{\"Message\": msg}); err != nil {\n\t\tlog.Println(err.Error())\n\t}\n}\n\nfunc withAuthorizationCheck(authToken string, next http.HandlerFunc) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.Header.Get(\"Authorization\") != authToken {\n\t\t\twriteErrorMessage(w, \"invalid Authorization token\", http.StatusForbidden)\n\t\t\treturn\n\t\t}\n\t\tnext.ServeHTTP(w, r)\n\t}\n}\n\nfunc writeCredsToResponse(creds aws.Credentials, w http.ResponseWriter) {\n\terr := json.NewEncoder(w).Encode(map[string]string{\n\t\t\"AccessKeyId\":     creds.AccessKeyID,\n\t\t\"SecretAccessKey\": creds.SecretAccessKey,\n\t\t\"Token\":           creds.SessionToken,\n\t\t\"Expiration\":      iso8601.Format(creds.Expires),\n\t})\n\tif err != nil {\n\t\twriteErrorMessage(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n}\n\nfunc generateRandomString() string {\n\tb := make([]byte, 30)\n\tif _, err := rand.Read(b); err != nil {\n\t\tpanic(err)\n\t}\n\treturn base64.RawURLEncoding.EncodeToString(b)\n}\n\ntype EcsServer struct {\n\tlistener          net.Listener\n\tauthToken         string\n\tserver            http.Server\n\tcache             sync.Map\n\tbaseCredsProvider aws.CredentialsProvider\n\tconfig            *vault.ProfileConfig\n}\n\nfunc NewEcsServer(ctx context.Context, baseCredsProvider aws.CredentialsProvider, config *vault.ProfileConfig, authToken string, port int, lazyLoadBaseCreds bool) (*EcsServer, error) {\n\tlistener, err := net.Listen(\"tcp\", fmt.Sprintf(\"127.0.0.1:%d\", port))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif authToken == \"\" {\n\t\tauthToken = generateRandomString()\n\t}\n\n\tcredsCache := aws.NewCredentialsCache(baseCredsProvider)\n\tif !lazyLoadBaseCreds {\n\t\t_, err := credsCache.Retrieve(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"Retrieving creds: %w\", err)\n\t\t}\n\t}\n\n\te := &EcsServer{\n\t\tlistener:          listener,\n\t\tauthToken:         authToken,\n\t\tbaseCredsProvider: credsCache,\n\t\tconfig:            config,\n\t}\n\n\trouter := http.NewServeMux()\n\trouter.HandleFunc(\"/\", e.DefaultRoute)\n\trouter.HandleFunc(\"/role-arn/\", e.AssumeRoleArnRoute)\n\te.server.Handler = withLogging(withAuthorizationCheck(e.authToken, router.ServeHTTP))\n\n\treturn e, nil\n}\n\nfunc (e *EcsServer) BaseURL() string {\n\treturn fmt.Sprintf(\"http://%s\", e.listener.Addr().String())\n}\nfunc (e *EcsServer) AuthToken() string {\n\treturn e.authToken\n}\n\nfunc (e *EcsServer) Serve() error {\n\treturn e.server.Serve(e.listener)\n}\n\nfunc (e *EcsServer) DefaultRoute(w http.ResponseWriter, r *http.Request) {\n\tcreds, err := e.baseCredsProvider.Retrieve(r.Context())\n\tif err != nil {\n\t\twriteErrorMessage(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\twriteCredsToResponse(creds, w)\n}\n\nfunc (e *EcsServer) getRoleProvider(roleArn string) aws.CredentialsProvider {\n\tvar roleProviderCache *aws.CredentialsCache\n\n\tv, ok := e.cache.Load(roleArn)\n\tif ok {\n\t\troleProviderCache = v.(*aws.CredentialsCache)\n\t} else {\n\t\tcfg := vault.NewAwsConfigWithCredsProvider(e.baseCredsProvider, e.config.Region, e.config.STSRegionalEndpoints)\n\t\troleProvider := &vault.AssumeRoleProvider{\n\t\t\tStsClient: sts.NewFromConfig(cfg),\n\t\t\tRoleARN:   roleArn,\n\t\t\tDuration:  e.config.AssumeRoleDuration,\n\t\t}\n\t\troleProviderCache = aws.NewCredentialsCache(roleProvider)\n\t\te.cache.Store(roleArn, roleProviderCache)\n\t}\n\treturn roleProviderCache\n}\n\nfunc (e *EcsServer) AssumeRoleArnRoute(w http.ResponseWriter, r *http.Request) {\n\troleArn := strings.TrimPrefix(r.URL.Path, \"/role-arn/\")\n\troleProvider := e.getRoleProvider(roleArn)\n\tcreds, err := roleProvider.Retrieve(r.Context())\n\tif err != nil {\n\t\twriteErrorMessage(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\twriteCredsToResponse(creds, w)\n}\n"
  },
  {
    "path": "server/httplog.go",
    "content": "package server\n\nimport (\n\t\"log\"\n\t\"net/http\"\n\t\"time\"\n)\n\ntype loggingMiddlewareResponseWriter struct {\n\thttp.ResponseWriter\n\tCode int\n}\n\nfunc (w *loggingMiddlewareResponseWriter) WriteHeader(statusCode int) {\n\tw.Code = statusCode\n\tw.ResponseWriter.WriteHeader(statusCode)\n}\n\nfunc withLogging(handler http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\trequestStart := time.Now()\n\t\tw2 := &loggingMiddlewareResponseWriter{w, http.StatusOK}\n\t\thandler.ServeHTTP(w2, r)\n\t\tlog.Printf(\"http: %s: %d %s %s (%s)\", r.RemoteAddr, w2.Code, r.Method, r.URL, time.Since(requestStart))\n\t})\n}\n"
  },
  {
    "path": "vault/assumeroleprovider.go",
    "content": "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-go-v2/service/sts\"\n\tststypes \"github.com/aws/aws-sdk-go-v2/service/sts/types\"\n)\n\n// AssumeRoleProvider retrieves temporary credentials from STS using AssumeRole\ntype AssumeRoleProvider struct {\n\tStsClient         *sts.Client\n\tRoleARN           string\n\tRoleSessionName   string\n\tExternalID        string\n\tDuration          time.Duration\n\tTags              map[string]string\n\tTransitiveTagKeys []string\n\tSourceIdentity    string\n\tMfa\n}\n\n// Retrieve generates a new set of temporary credentials using STS AssumeRole\nfunc (p *AssumeRoleProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {\n\trole, err := p.RetrieveStsCredentials(ctx)\n\tif err != nil {\n\t\treturn aws.Credentials{}, err\n\t}\n\n\treturn aws.Credentials{\n\t\tAccessKeyID:     *role.AccessKeyId,\n\t\tSecretAccessKey: *role.SecretAccessKey,\n\t\tSessionToken:    *role.SessionToken,\n\t\tCanExpire:       true,\n\t\tExpires:         *role.Expiration,\n\t}, nil\n}\n\nfunc (p *AssumeRoleProvider) roleSessionName() string {\n\tif p.RoleSessionName == \"\" {\n\t\t// Try to work out a role name that will hopefully end up unique.\n\t\treturn fmt.Sprintf(\"%d\", time.Now().UTC().UnixNano())\n\t}\n\n\treturn p.RoleSessionName\n}\n\nfunc (p *AssumeRoleProvider) RetrieveStsCredentials(ctx context.Context) (*ststypes.Credentials, error) {\n\tvar err error\n\n\tinput := &sts.AssumeRoleInput{\n\t\tRoleArn:         aws.String(p.RoleARN),\n\t\tRoleSessionName: aws.String(p.roleSessionName()),\n\t\tDurationSeconds: aws.Int32(int32(p.Duration.Seconds())),\n\t}\n\n\tif p.ExternalID != \"\" {\n\t\tinput.ExternalId = aws.String(p.ExternalID)\n\t}\n\n\tif p.MfaSerial != \"\" {\n\t\tinput.SerialNumber = aws.String(p.MfaSerial)\n\t\tinput.TokenCode, err = p.GetMfaToken()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tif len(p.Tags) > 0 {\n\t\tinput.Tags = make([]ststypes.Tag, 0)\n\t\tfor key, value := range p.Tags {\n\t\t\ttag := ststypes.Tag{\n\t\t\t\tKey:   aws.String(key),\n\t\t\t\tValue: aws.String(value),\n\t\t\t}\n\t\t\tinput.Tags = append(input.Tags, tag)\n\t\t}\n\t}\n\n\tif len(p.TransitiveTagKeys) > 0 {\n\t\tinput.TransitiveTagKeys = p.TransitiveTagKeys\n\t}\n\n\tif p.SourceIdentity != \"\" {\n\t\tinput.SourceIdentity = aws.String(p.SourceIdentity)\n\t}\n\n\tresp, err := p.StsClient.AssumeRole(ctx, input)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlog.Printf(\"Generated credentials %s using AssumeRole, expires in %s\", FormatKeyForDisplay(*resp.Credentials.AccessKeyId), time.Until(*resp.Credentials.Expiration).String())\n\n\treturn resp.Credentials, nil\n}\n"
  },
  {
    "path": "vault/assumerolewithwebidentityprovider.go",
    "content": "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-sdk-go-v2/service/sts\"\n\tststypes \"github.com/aws/aws-sdk-go-v2/service/sts/types\"\n)\n\n// AssumeRoleWithWebIdentityProvider retrieves temporary credentials from STS using AssumeRoleWithWebIdentity\ntype AssumeRoleWithWebIdentityProvider struct {\n\tStsClient               *sts.Client\n\tRoleARN                 string\n\tRoleSessionName         string\n\tWebIdentityTokenFile    string\n\tWebIdentityTokenProcess string\n\tExternalID              string\n\tDuration                time.Duration\n}\n\n// Retrieve generates a new set of temporary credentials using STS AssumeRoleWithWebIdentity\nfunc (p *AssumeRoleWithWebIdentityProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {\n\tcreds, err := p.RetrieveStsCredentials(ctx)\n\tif err != nil {\n\t\treturn aws.Credentials{}, err\n\t}\n\n\treturn aws.Credentials{\n\t\tAccessKeyID:     aws.ToString(creds.AccessKeyId),\n\t\tSecretAccessKey: aws.ToString(creds.SecretAccessKey),\n\t\tSessionToken:    aws.ToString(creds.SessionToken),\n\t\tCanExpire:       true,\n\t\tExpires:         aws.ToTime(creds.Expiration),\n\t}, nil\n}\n\nfunc (p *AssumeRoleWithWebIdentityProvider) roleSessionName() string {\n\tif p.RoleSessionName == \"\" {\n\t\t// Try to work out a role name that will hopefully end up unique.\n\t\treturn fmt.Sprintf(\"%d\", time.Now().UTC().UnixNano())\n\t}\n\n\treturn p.RoleSessionName\n}\n\nfunc (p *AssumeRoleWithWebIdentityProvider) RetrieveStsCredentials(ctx context.Context) (*ststypes.Credentials, error) {\n\tvar err error\n\n\twebIdentityToken, err := p.webIdentityToken()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresp, err := p.StsClient.AssumeRoleWithWebIdentity(ctx, &sts.AssumeRoleWithWebIdentityInput{\n\t\tRoleArn:          aws.String(p.RoleARN),\n\t\tRoleSessionName:  aws.String(p.roleSessionName()),\n\t\tDurationSeconds:  aws.Int32(int32(p.Duration.Seconds())),\n\t\tWebIdentityToken: aws.String(webIdentityToken),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlog.Printf(\"Generated credentials %s using AssumeRoleWithWebIdentity, expires in %s\", FormatKeyForDisplay(*resp.Credentials.AccessKeyId), time.Until(*resp.Credentials.Expiration).String())\n\n\treturn resp.Credentials, nil\n}\n\nfunc (p *AssumeRoleWithWebIdentityProvider) webIdentityToken() (string, error) {\n\t// Read OpenID Connect token from WebIdentityTokenFile\n\tif p.WebIdentityTokenFile != \"\" {\n\t\tb, err := os.ReadFile(p.WebIdentityTokenFile)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"unable to read file at %s: %v\", p.WebIdentityTokenFile, err)\n\t\t}\n\n\t\treturn string(b), nil\n\t}\n\n\t// Exec WebIdentityTokenProcess to retrieve OpenID Connect token\n\treturn executeProcess(p.WebIdentityTokenProcess)\n}\n"
  },
  {
    "path": "vault/cachedsessionprovider.go",
    "content": "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-go-v2/service/sts/types\"\n)\n\ntype StsSessionProvider interface {\n\taws.CredentialsProvider\n\tRetrieveStsCredentials(ctx context.Context) (*ststypes.Credentials, error)\n}\n\n// CachedSessionProvider retrieves cached credentials from the keyring, or if no credentials are cached\n// retrieves temporary credentials using the CredentialsFunc\ntype CachedSessionProvider struct {\n\tSessionKey      SessionMetadata\n\tSessionProvider StsSessionProvider\n\tKeyring         *SessionKeyring\n\tExpiryWindow    time.Duration\n}\n\nfunc (p *CachedSessionProvider) RetrieveStsCredentials(ctx context.Context) (*ststypes.Credentials, error) {\n\tcreds, err := p.Keyring.Get(p.SessionKey)\n\n\tif err != nil || time.Until(*creds.Expiration) < p.ExpiryWindow {\n\t\t// lookup missed, we need to create a new one.\n\t\tcreds, err = p.SessionProvider.RetrieveStsCredentials(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\terr = p.Keyring.Set(p.SessionKey, creds)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\tlog.Printf(\"Re-using cached credentials %s from %s, expires in %s\", FormatKeyForDisplay(*creds.AccessKeyId), p.SessionKey.Type, time.Until(*creds.Expiration).String())\n\t}\n\n\treturn creds, nil\n}\n\n// Retrieve returns cached credentials from the keyring, or if no credentials are cached\n// generates a new set of temporary credentials using the CredentialsFunc\nfunc (p *CachedSessionProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {\n\tcreds, err := p.RetrieveStsCredentials(ctx)\n\tif err != nil {\n\t\treturn aws.Credentials{}, err\n\t}\n\n\treturn aws.Credentials{\n\t\tAccessKeyID:     aws.ToString(creds.AccessKeyId),\n\t\tSecretAccessKey: aws.ToString(creds.SecretAccessKey),\n\t\tSessionToken:    aws.ToString(creds.SessionToken),\n\t\tCanExpire:       true,\n\t\tExpires:         aws.ToTime(creds.Expiration),\n\t}, nil\n}\n"
  },
  {
    "path": "vault/config.go",
    "content": "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\nconst (\n\t// DefaultSessionDuration is the default duration for GetSessionToken or AssumeRole sessions\n\tDefaultSessionDuration = time.Hour * 1\n\n\t// DefaultChainedSessionDuration is the default duration for GetSessionToken sessions when chaining\n\tDefaultChainedSessionDuration = time.Hour * 8\n\n\tdefaultSectionName          = \"default\"\n\troleChainingMaximumDuration = 1 * time.Hour\n)\n\nfunc init() {\n\tini.PrettyFormat = false\n}\n\n// ConfigFile is an abstraction over what is in ~/.aws/config\ntype ConfigFile struct {\n\tPath    string\n\tiniFile *ini.File\n}\n\n// configPath returns either $AWS_CONFIG_FILE or ~/.aws/config\nfunc configPath() (string, error) {\n\tfile := os.Getenv(\"AWS_CONFIG_FILE\")\n\tif file == \"\" {\n\t\thome, err := os.UserHomeDir()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tfile = filepath.Join(home, \"/.aws/config\")\n\t} else {\n\t\tlog.Printf(\"Using AWS_CONFIG_FILE value: %s\", file)\n\t}\n\treturn file, nil\n}\n\n// createConfigFilesIfMissing will create the config directory and file if they do not exist\nfunc createConfigFilesIfMissing() error {\n\tfile, err := configPath()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdir := filepath.Dir(file)\n\tif _, err := os.Stat(dir); os.IsNotExist(err) {\n\t\terr = os.Mkdir(dir, 0700)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tlog.Printf(\"Config directory %s created\", dir)\n\t}\n\tif _, err := os.Stat(file); os.IsNotExist(err) {\n\t\tnewFile, err := os.Create(file)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Config file %s not created\", file)\n\t\t\treturn err\n\t\t}\n\t\tnewFile.Close()\n\t\tlog.Printf(\"Config file %s created\", file)\n\t}\n\treturn nil\n}\n\n// LoadConfig loads and parses a config file. No error is returned if the file doesn't exist\nfunc LoadConfig(path string) (*ConfigFile, error) {\n\tconfig := &ConfigFile{\n\t\tPath: path,\n\t}\n\tif _, err := os.Stat(path); err == nil {\n\t\tif parseErr := config.parseFile(); parseErr != nil {\n\t\t\treturn nil, parseErr\n\t\t}\n\t} else {\n\t\tlog.Printf(\"Config file %s doesn't exist so lets create it\", path)\n\t\terr := createConfigFilesIfMissing()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif parseErr := config.parseFile(); parseErr != nil {\n\t\t\treturn nil, parseErr\n\t\t}\n\t}\n\treturn config, nil\n}\n\n// LoadConfigFromEnv finds the config file from the environment\nfunc LoadConfigFromEnv() (*ConfigFile, error) {\n\tfile, err := configPath()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlog.Printf(\"Loading config file %s\", file)\n\treturn LoadConfig(file)\n}\n\nfunc (c *ConfigFile) parseFile() error {\n\tlog.Printf(\"Parsing config file %s\", c.Path)\n\n\tf, err := ini.LoadSources(ini.LoadOptions{\n\t\tAllowNestedValues:   true,\n\t\tInsensitiveSections: false,\n\t\tInsensitiveKeys:     true,\n\t}, c.Path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error parsing config file %s: %w\", c.Path, err)\n\t}\n\tc.iniFile = f\n\treturn nil\n}\n\n// ProfileSection is a profile section of the config file\ntype ProfileSection struct {\n\tName                    string `ini:\"-\"`\n\tMfaSerial               string `ini:\"mfa_serial,omitempty\"`\n\tRoleARN                 string `ini:\"role_arn,omitempty\"`\n\tExternalID              string `ini:\"external_id,omitempty\"`\n\tRegion                  string `ini:\"region,omitempty\"`\n\tRoleSessionName         string `ini:\"role_session_name,omitempty\"`\n\tDurationSeconds         uint   `ini:\"duration_seconds,omitempty\"`\n\tSourceProfile           string `ini:\"source_profile,omitempty\"`\n\tIncludeProfile          string `ini:\"include_profile,omitempty\"`\n\tSSOSession              string `ini:\"sso_session,omitempty\"`\n\tSSOStartURL             string `ini:\"sso_start_url,omitempty\"`\n\tSSORegion               string `ini:\"sso_region,omitempty\"`\n\tSSOAccountID            string `ini:\"sso_account_id,omitempty\"`\n\tSSORoleName             string `ini:\"sso_role_name,omitempty\"`\n\tWebIdentityTokenFile    string `ini:\"web_identity_token_file,omitempty\"`\n\tWebIdentityTokenProcess string `ini:\"web_identity_token_process,omitempty\"`\n\tSTSRegionalEndpoints    string `ini:\"sts_regional_endpoints,omitempty\"`\n\tSessionTags             string `ini:\"session_tags,omitempty\"`\n\tTransitiveSessionTags   string `ini:\"transitive_session_tags,omitempty\"`\n\tSourceIdentity          string `ini:\"source_identity,omitempty\"`\n\tCredentialProcess       string `ini:\"credential_process,omitempty\"`\n\tMfaProcess              string `ini:\"mfa_process,omitempty\"`\n}\n\n// SSOSessionSection is a [sso-session] section of the config file\ntype SSOSessionSection struct {\n\tName                  string `ini:\"-\"`\n\tSSOStartURL           string `ini:\"sso_start_url,omitempty\"`\n\tSSORegion             string `ini:\"sso_region,omitempty\"`\n\tSSORegistrationScopes string `ini:\"sso_registration_scopes,omitempty\"`\n}\n\nfunc (s ProfileSection) IsEmpty() bool {\n\ts.Name = \"\"\n\treturn s == ProfileSection{}\n}\n\n// ProfileSections returns all the profile sections in the config\nfunc (c *ConfigFile) ProfileSections() []ProfileSection {\n\tresult := []ProfileSection{}\n\n\tif c.iniFile == nil {\n\t\treturn result\n\t}\n\tfor _, section := range c.iniFile.SectionStrings() {\n\t\tif section == defaultSectionName || strings.HasPrefix(section, \"profile \") {\n\t\t\tprofile, _ := c.ProfileSection(strings.TrimPrefix(section, \"profile \"))\n\n\t\t\t// ignore the default profile if it's empty\n\t\t\tif section == defaultSectionName && profile.IsEmpty() {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tresult = append(result, profile)\n\t\t} else if strings.HasPrefix(section, \"sso-session \") {\n\t\t\t// Not a profile\n\t\t\tcontinue\n\t\t} else {\n\t\t\tlog.Printf(\"Unrecognised ini file section: %s\", section)\n\t\t\tcontinue\n\t\t}\n\t}\n\n\treturn result\n}\n\n// ProfileSection returns the profile section with the matching name. If there isn't any,\n// an empty profile with the provided name is returned, along with false.\nfunc (c *ConfigFile) ProfileSection(name string) (ProfileSection, bool) {\n\tprofile := ProfileSection{\n\t\tName: name,\n\t}\n\tif c.iniFile == nil {\n\t\treturn profile, false\n\t}\n\t// default profile name has a slightly different section format\n\tsectionName := \"profile \" + name\n\tif name == defaultSectionName {\n\t\tsectionName = defaultSectionName\n\t}\n\tsection, err := c.iniFile.GetSection(sectionName)\n\tif err != nil {\n\t\treturn profile, false\n\t}\n\tif err = section.MapTo(&profile); err != nil {\n\t\tpanic(err)\n\t}\n\treturn profile, true\n}\n\n// SSOSessionSection returns the [sso-session] section with the matching name. If there isn't any,\n// an empty sso-session with the provided name is returned, along with false.\nfunc (c *ConfigFile) SSOSessionSection(name string) (SSOSessionSection, bool) {\n\tssoSession := SSOSessionSection{\n\t\tName: name,\n\t}\n\tif c.iniFile == nil {\n\t\treturn ssoSession, false\n\t}\n\tsectionName := \"sso-session \" + name\n\tsection, err := c.iniFile.GetSection(sectionName)\n\tif err != nil {\n\t\treturn ssoSession, false\n\t}\n\tif err = section.MapTo(&ssoSession); err != nil {\n\t\tpanic(err)\n\t}\n\treturn ssoSession, true\n}\n\nfunc (c *ConfigFile) Save() error {\n\treturn c.iniFile.SaveTo(c.Path)\n}\n\n// Add the profile to the configuration file\nfunc (c *ConfigFile) Add(profile ProfileSection) error {\n\tif c.iniFile == nil {\n\t\treturn errors.New(\"No iniFile to add to\")\n\t}\n\t// default profile name has a slightly different section format\n\tsectionName := \"profile \" + profile.Name\n\tif profile.Name == defaultSectionName {\n\t\tsectionName = defaultSectionName\n\t}\n\tsection, err := c.iniFile.NewSection(sectionName)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Error creating section %q: %v\", profile.Name, err)\n\t}\n\tif err = section.ReflectFrom(&profile); err != nil {\n\t\treturn fmt.Errorf(\"Error mapping profile to ini file: %v\", err)\n\t}\n\treturn c.Save()\n}\n\n// ProfileNames returns a slice of profile names from the AWS config\nfunc (c *ConfigFile) ProfileNames() []string {\n\tprofileNames := []string{}\n\tfor _, profile := range c.ProfileSections() {\n\t\tprofileNames = append(profileNames, profile.Name)\n\t}\n\treturn profileNames\n}\n\n// ConfigLoader loads config from configfile and environment variables\ntype ConfigLoader struct {\n\tBaseConfig    ProfileConfig\n\tFile          *ConfigFile\n\tActiveProfile string\n\n\tvisitedProfiles []string\n}\n\nfunc NewConfigLoader(baseConfig ProfileConfig, file *ConfigFile, activeProfile string) *ConfigLoader {\n\treturn &ConfigLoader{\n\t\tBaseConfig:    baseConfig,\n\t\tFile:          file,\n\t\tActiveProfile: activeProfile,\n\t}\n}\n\nfunc (cl *ConfigLoader) visitProfile(name string) bool {\n\tfor _, p := range cl.visitedProfiles {\n\t\tif p == name {\n\t\t\treturn false\n\t\t}\n\t}\n\tcl.visitedProfiles = append(cl.visitedProfiles, name)\n\treturn true\n}\n\nfunc (cl *ConfigLoader) resetLoopDetection() {\n\tcl.visitedProfiles = []string{}\n}\n\nfunc (cl *ConfigLoader) populateFromDefaults(config *ProfileConfig) {\n\tif config.AssumeRoleDuration == 0 {\n\t\tconfig.AssumeRoleDuration = DefaultSessionDuration\n\t}\n\tif config.GetFederationTokenDuration == 0 {\n\t\tconfig.GetFederationTokenDuration = DefaultSessionDuration\n\t}\n\tif config.NonChainedGetSessionTokenDuration == 0 {\n\t\tconfig.NonChainedGetSessionTokenDuration = DefaultSessionDuration\n\t}\n\tif config.ChainedGetSessionTokenDuration == 0 {\n\t\tconfig.ChainedGetSessionTokenDuration = DefaultChainedSessionDuration\n\t}\n}\n\nfunc (cl *ConfigLoader) populateFromConfigFile(config *ProfileConfig, profileName string) error {\n\tif !cl.visitProfile(profileName) {\n\t\treturn fmt.Errorf(\"Loop detected in config file for profile '%s'\", profileName)\n\t}\n\n\tpsection, ok := cl.File.ProfileSection(profileName)\n\tif !ok {\n\t\t// ignore missing profiles\n\t\tlog.Printf(\"Profile '%s' missing in config file\", profileName)\n\t}\n\n\tif config.MfaSerial == \"\" {\n\t\tconfig.MfaSerial = psection.MfaSerial\n\t}\n\tif config.RoleARN == \"\" {\n\t\tconfig.RoleARN = psection.RoleARN\n\t}\n\tif config.ExternalID == \"\" {\n\t\tconfig.ExternalID = psection.ExternalID\n\t}\n\tif config.Region == \"\" {\n\t\tconfig.Region = psection.Region\n\t}\n\tif config.RoleSessionName == \"\" {\n\t\tconfig.RoleSessionName = psection.RoleSessionName\n\t}\n\tif config.AssumeRoleDuration == 0 {\n\t\tconfig.AssumeRoleDuration = time.Duration(psection.DurationSeconds) * time.Second\n\t}\n\tif config.SourceProfileName == \"\" {\n\t\tconfig.SourceProfileName = psection.SourceProfile\n\t}\n\tif config.SSOSession == \"\" {\n\t\tconfig.SSOSession = psection.SSOSession\n\t\tif psection.SSOSession != \"\" {\n\t\t\t// Populate profile with values from [sso-session].\n\t\t\tssoSection, ok := cl.File.SSOSessionSection(psection.SSOSession)\n\t\t\tif ok {\n\t\t\t\tconfig.SSOStartURL = ssoSection.SSOStartURL\n\t\t\t\tconfig.SSORegion = ssoSection.SSORegion\n\t\t\t\tconfig.SSORegistrationScopes = ssoSection.SSORegistrationScopes\n\t\t\t} else {\n\t\t\t\t// ignore missing profiles\n\t\t\t\tlog.Printf(\"[sso-session] '%s' missing in config file\", psection.SSOSession)\n\t\t\t}\n\t\t}\n\t}\n\tif config.SSOStartURL == \"\" {\n\t\tconfig.SSOStartURL = psection.SSOStartURL\n\t}\n\tif config.SSORegion == \"\" {\n\t\tconfig.SSORegion = psection.SSORegion\n\t}\n\tif config.SSOAccountID == \"\" {\n\t\tconfig.SSOAccountID = psection.SSOAccountID\n\t}\n\tif config.SSORoleName == \"\" {\n\t\tconfig.SSORoleName = psection.SSORoleName\n\t}\n\tif config.WebIdentityTokenFile == \"\" {\n\t\tconfig.WebIdentityTokenFile = psection.WebIdentityTokenFile\n\t}\n\tif config.WebIdentityTokenProcess == \"\" {\n\t\tconfig.WebIdentityTokenProcess = psection.WebIdentityTokenProcess\n\t}\n\tif config.STSRegionalEndpoints == \"\" {\n\t\tconfig.STSRegionalEndpoints = psection.STSRegionalEndpoints\n\t}\n\tif config.SourceIdentity == \"\" {\n\t\tconfig.SourceIdentity = psection.SourceIdentity\n\t}\n\tif config.CredentialProcess == \"\" {\n\t\tconfig.CredentialProcess = psection.CredentialProcess\n\t}\n\tif config.MfaProcess == \"\" {\n\t\tconfig.MfaProcess = psection.MfaProcess\n\t}\n\tif sessionTags := psection.SessionTags; sessionTags != \"\" && config.SessionTags == nil {\n\t\terr := config.SetSessionTags(sessionTags)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Failed to parse session_tags profile setting: %s\", err)\n\t\t}\n\t}\n\tif transitiveSessionTags := psection.TransitiveSessionTags; transitiveSessionTags != \"\" && config.TransitiveSessionTags == nil {\n\t\tconfig.SetTransitiveSessionTags(transitiveSessionTags)\n\t}\n\n\tif psection.IncludeProfile != \"\" {\n\t\terr := cl.populateFromConfigFile(config, psection.IncludeProfile)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else if profileName != defaultSectionName {\n\t\terr := cl.populateFromConfigFile(config, defaultSectionName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Ignore source_profile if it recursively refers to the profile\n\tif config.SourceProfileName == config.ProfileName {\n\t\tconfig.SourceProfileName = \"\"\n\t}\n\n\treturn nil\n}\n\nfunc (cl *ConfigLoader) populateFromEnv(profile *ProfileConfig) {\n\tif region := os.Getenv(\"AWS_REGION\"); region != \"\" && profile.Region == \"\" {\n\t\tlog.Printf(\"Using region %q from AWS_REGION\", region)\n\t\tprofile.Region = region\n\t}\n\n\tif region := os.Getenv(\"AWS_DEFAULT_REGION\"); region != \"\" && profile.Region == \"\" {\n\t\tlog.Printf(\"Using region %q from AWS_DEFAULT_REGION\", region)\n\t\tprofile.Region = region\n\t}\n\n\tif stsRegionalEndpoints := os.Getenv(\"AWS_STS_REGIONAL_ENDPOINTS\"); stsRegionalEndpoints != \"\" && profile.STSRegionalEndpoints == \"\" {\n\t\tlog.Printf(\"Using %q from AWS_STS_REGIONAL_ENDPOINTS\", stsRegionalEndpoints)\n\t\tprofile.STSRegionalEndpoints = stsRegionalEndpoints\n\t}\n\n\tif mfaSerial := os.Getenv(\"AWS_MFA_SERIAL\"); mfaSerial != \"\" && profile.MfaSerial == \"\" {\n\t\tlog.Printf(\"Using mfa_serial %q from AWS_MFA_SERIAL\", mfaSerial)\n\t\tprofile.MfaSerial = mfaSerial\n\t}\n\n\tvar err error\n\tif assumeRoleTTL := os.Getenv(\"AWS_ASSUME_ROLE_TTL\"); assumeRoleTTL != \"\" && profile.AssumeRoleDuration == 0 {\n\t\tprofile.AssumeRoleDuration, err = time.ParseDuration(assumeRoleTTL)\n\t\tif err == nil {\n\t\t\tlog.Printf(\"Using duration_seconds %q from AWS_ASSUME_ROLE_TTL\", profile.AssumeRoleDuration)\n\t\t}\n\t}\n\n\tif sessionTTL := os.Getenv(\"AWS_SESSION_TOKEN_TTL\"); sessionTTL != \"\" && profile.NonChainedGetSessionTokenDuration == 0 {\n\t\tprofile.NonChainedGetSessionTokenDuration, err = time.ParseDuration(sessionTTL)\n\t\tif err == nil {\n\t\t\tlog.Printf(\"Using a session duration of %q from AWS_SESSION_TOKEN_TTL\", profile.NonChainedGetSessionTokenDuration)\n\t\t}\n\t}\n\n\tif sessionTTL := os.Getenv(\"AWS_CHAINED_SESSION_TOKEN_TTL\"); sessionTTL != \"\" && profile.ChainedGetSessionTokenDuration == 0 {\n\t\tprofile.ChainedGetSessionTokenDuration, err = time.ParseDuration(sessionTTL)\n\t\tif err == nil {\n\t\t\tlog.Printf(\"Using a cached MFA session duration of %q from AWS_CACHED_SESSION_TOKEN_TTL\", profile.ChainedGetSessionTokenDuration)\n\t\t}\n\t}\n\n\tif federationTokenTTL := os.Getenv(\"AWS_FEDERATION_TOKEN_TTL\"); federationTokenTTL != \"\" && profile.GetFederationTokenDuration == 0 {\n\t\tprofile.GetFederationTokenDuration, err = time.ParseDuration(federationTokenTTL)\n\t\tif err == nil {\n\t\t\tlog.Printf(\"Using a session duration of %q from AWS_FEDERATION_TOKEN_TTL\", profile.GetFederationTokenDuration)\n\t\t}\n\t}\n\n\t// AWS_ROLE_ARN, AWS_ROLE_SESSION_NAME, AWS_SESSION_TAGS, AWS_TRANSITIVE_TAGS and AWS_SOURCE_IDENTITY only apply to the target profile\n\tif profile.ProfileName == cl.ActiveProfile {\n\t\tif roleARN := os.Getenv(\"AWS_ROLE_ARN\"); roleARN != \"\" && profile.RoleARN == \"\" {\n\t\t\tlog.Printf(\"Using role_arn %q from AWS_ROLE_ARN\", roleARN)\n\t\t\tprofile.RoleARN = roleARN\n\t\t}\n\n\t\tif roleSessionName := os.Getenv(\"AWS_ROLE_SESSION_NAME\"); roleSessionName != \"\" && profile.RoleSessionName == \"\" {\n\t\t\tlog.Printf(\"Using role_session_name %q from AWS_ROLE_SESSION_NAME\", roleSessionName)\n\t\t\tprofile.RoleSessionName = roleSessionName\n\t\t}\n\n\t\tif sessionTags := os.Getenv(\"AWS_SESSION_TAGS\"); sessionTags != \"\" && profile.SessionTags == nil {\n\t\t\terr := profile.SetSessionTags(sessionTags)\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatalf(\"Failed to parse AWS_SESSION_TAGS environment variable: %s\", err)\n\t\t\t}\n\t\t\tlog.Printf(\"Using session_tags %v from AWS_SESSION_TAGS\", profile.SessionTags)\n\t\t}\n\n\t\tif transitiveSessionTags := os.Getenv(\"AWS_TRANSITIVE_TAGS\"); transitiveSessionTags != \"\" && profile.TransitiveSessionTags == nil {\n\t\t\tprofile.SetTransitiveSessionTags(transitiveSessionTags)\n\t\t\tlog.Printf(\"Using transitive_session_tags %v from AWS_TRANSITIVE_TAGS\", profile.TransitiveSessionTags)\n\t\t}\n\n\t\tif sourceIdentity := os.Getenv(\"AWS_SOURCE_IDENTITY\"); sourceIdentity != \"\" && profile.SourceIdentity == \"\" {\n\t\t\tprofile.SourceIdentity = sourceIdentity\n\t\t\tlog.Printf(\"Using source_identity %v from AWS_SOURCE_IDENTITY\", profile.SourceIdentity)\n\t\t}\n\t}\n}\n\nfunc (cl *ConfigLoader) hydrateSourceConfig(config *ProfileConfig) error {\n\tif config.SourceProfileName != \"\" {\n\t\tsc, err := cl.GetProfileConfig(config.SourceProfileName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsc.ChainedFromProfile = config\n\t\tconfig.SourceProfile = sc\n\t}\n\treturn nil\n}\n\n// GetProfileConfig loads the profile from the config file and environment variables into config\nfunc (cl *ConfigLoader) GetProfileConfig(profileName string) (*ProfileConfig, error) {\n\tconfig := cl.BaseConfig\n\tconfig.ProfileName = profileName\n\tcl.populateFromEnv(&config)\n\n\tcl.resetLoopDetection()\n\terr := cl.populateFromConfigFile(&config, profileName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcl.populateFromDefaults(&config)\n\n\terr = cl.hydrateSourceConfig(&config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &config, nil\n}\n\n// ProfileConfig is a collection of configuration options for creating temporary credentials\ntype ProfileConfig struct {\n\t// ProfileName specifies the name of the profile config\n\tProfileName string\n\n\t// SourceProfile is the profile where credentials come from\n\tSourceProfileName string\n\n\t// SourceProfile is the profile where credentials come from\n\tSourceProfile *ProfileConfig\n\n\t// ChainedFromProfile is the profile that used this profile as its source profile\n\tChainedFromProfile *ProfileConfig\n\n\t// Region is the AWS region\n\tRegion string\n\n\t// STSRegionalEndpoints sets STS endpoint resolution logic, must be \"regional\" or \"legacy\"\n\tSTSRegionalEndpoints string\n\n\t// Mfa config\n\tMfaSerial       string\n\tMfaToken        string\n\tMfaPromptMethod string\n\n\t// MfaProcess specifies external command to run to get an MFA token\n\tMfaProcess string\n\n\t// AssumeRole config\n\tRoleARN         string\n\tRoleSessionName string\n\tExternalID      string\n\n\t// AssumeRoleWithWebIdentity config\n\tWebIdentityTokenFile    string\n\tWebIdentityTokenProcess string\n\n\t// GetSessionTokenDuration specifies the wanted duration for credentials generated with AssumeRole\n\tAssumeRoleDuration time.Duration\n\n\t// NonChainedGetSessionTokenDuration specifies the wanted duration for credentials generated with GetSessionToken\n\tNonChainedGetSessionTokenDuration time.Duration\n\n\t// ChainedGetSessionTokenDuration specifies the wanted duration for credentials generated with GetSessionToken when chaining\n\tChainedGetSessionTokenDuration time.Duration\n\n\t// GetFederationTokenDuration specifies the wanted duration for credentials generated with GetFederationToken\n\tGetFederationTokenDuration time.Duration\n\n\t// SSOSession specifies the [sso-session] section name.\n\tSSOSession string\n\n\t// SSOStartURL specifies the URL for the AWS IAM Identity Center user portal, legacy option.\n\tSSOStartURL string\n\n\t// SSORegion specifies the region for the AWS IAM Identity Center user portal, legacy option.\n\tSSORegion string\n\n\t// SSORegistrationScopes specifies registration scopes for the AWS IAM Identity Center user portal.\n\tSSORegistrationScopes string\n\n\t// SSOAccountID specifies the AWS account ID for the profile.\n\tSSOAccountID string\n\n\t// SSORoleName specifies the AWS IAM Role name to target.\n\tSSORoleName string\n\n\t// SSOUseStdout specifies that the system browser should not be automatically opened\n\tSSOUseStdout bool\n\n\t// SessionTags specifies assumed role Session Tags\n\tSessionTags map[string]string\n\n\t// TransitiveSessionTags specifies assumed role Transitive Session Tags keys\n\tTransitiveSessionTags []string\n\n\t// SourceIdentity specifies assumed role Source Identity\n\tSourceIdentity string\n\n\t// CredentialProcess specifies external command to run to get an AWS credential\n\tCredentialProcess string\n}\n\n// SetSessionTags parses a comma separated key=vaue string and sets Config.SessionTags map\nfunc (c *ProfileConfig) SetSessionTags(s string) error {\n\tc.SessionTags = make(map[string]string)\n\tfor _, tag := range strings.Split(s, \",\") {\n\t\tkvPair := strings.SplitN(tag, \"=\", 2)\n\t\tif len(kvPair) != 2 {\n\t\t\treturn errors.New(\"session tags string must be <key1>=<value1>,[<key2>=<value2>[,...]]\")\n\t\t}\n\t\tc.SessionTags[strings.TrimSpace(kvPair[0])] = strings.TrimSpace(kvPair[1])\n\t}\n\n\treturn nil\n}\n\n// SetTransitiveSessionTags parses a comma separated string and sets Config.TransitiveSessionTags\nfunc (c *ProfileConfig) SetTransitiveSessionTags(s string) {\n\tfor _, tag := range strings.Split(s, \",\") {\n\t\tif tag = strings.TrimSpace(tag); tag != \"\" {\n\t\t\tc.TransitiveSessionTags = append(c.TransitiveSessionTags, tag)\n\t\t}\n\t}\n}\n\nfunc (c *ProfileConfig) IsChained() bool {\n\treturn c.ChainedFromProfile != nil\n}\n\nfunc (c *ProfileConfig) HasSourceProfile() bool {\n\treturn c.SourceProfile != nil\n}\n\nfunc (c *ProfileConfig) HasMfaSerial() bool {\n\treturn c.MfaSerial != \"\"\n}\n\nfunc (c *ProfileConfig) HasRole() bool {\n\treturn c.RoleARN != \"\"\n}\n\nfunc (c *ProfileConfig) HasSSOSession() bool {\n\treturn c.SSOSession != \"\"\n}\n\nfunc (c *ProfileConfig) HasSSOStartURL() bool {\n\treturn c.SSOStartURL != \"\"\n}\n\nfunc (c *ProfileConfig) HasWebIdentity() bool {\n\treturn c.WebIdentityTokenFile != \"\" || c.WebIdentityTokenProcess != \"\"\n}\n\nfunc (c *ProfileConfig) HasCredentialProcess() bool {\n\treturn c.CredentialProcess != \"\"\n}\n\nfunc (c *ProfileConfig) GetSessionTokenDuration() time.Duration {\n\tif c.IsChained() {\n\t\treturn c.ChainedGetSessionTokenDuration\n\t}\n\treturn c.NonChainedGetSessionTokenDuration\n}\n"
  },
  {
    "path": "vault/config_test.go",
    "content": "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\"github.com/google/go-cmp/cmp\"\n)\n\n// see http://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html\nvar exampleConfig = []byte(`# an example profile file\n[default]\nregion=us-west-2\noutput=json\n\n[profile user2]\nREGION=us-east-1\noutput=text\n\n[profile withsource]\nsource_profile=user2\nregion=us-east-1\n\n[profile withMFA]\nsource_profile=user2\nRole_Arn=arn:aws:iam::4451234513441615400570:role/aws_admin\nmfa_Serial=arn:aws:iam::1234513441:mfa/blah\nRegion=us-east-1\nduration_seconds=1200\nsts_regional_endpoints=legacy\n\n[profile testincludeprofile1]\nregion=us-east-1\n\n[profile testincludeprofile2]\ninclude_profile=testincludeprofile1\n\n[profile with-sso-session]\nsso_session = moon-sso\nsso_account_id=123456\nregion = moon-1 # Different from sso region\n\n[sso-session moon-sso]\nsso_start_url = https://d-123456789.example.com/start\nsso_region = moon-2  # Different from profile region\nsso_registration_scopes = sso:account:access\n`)\n\nvar nestedConfig = []byte(`[default]\n\n[profile testing]\naws_access_key_id=foo\naws_secret_access_key=bar\nregion=us-west-2\ns3=\n  max_concurrent_requests=10\n  max_queue_size=1000\n`)\n\nvar defaultsOnlyConfigWithHeader = []byte(`[default]\nregion=us-west-2\noutput=json\n`)\n\nfunc newConfigFile(t *testing.T, b []byte) string {\n\tt.Helper()\n\tf, err := os.CreateTemp(\"\", \"aws-config\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := os.WriteFile(f.Name(), b, 0600); err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn f.Name()\n}\n\nfunc TestProfileNameCaseSensitivity(t *testing.T) {\n\tf := newConfigFile(t, exampleConfig)\n\tdefer os.Remove(f)\n\n\tcfg, err := vault.LoadConfig(f)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdef, ok := cfg.ProfileSection(\"withMFA\")\n\tif !ok {\n\t\tt.Fatalf(\"Expected to match profile withMFA\")\n\t}\n\n\texpectedMfaSerial := \"arn:aws:iam::1234513441:mfa/blah\"\n\tif def.MfaSerial != expectedMfaSerial {\n\t\tt.Fatalf(\"Expected %s, got %s\", expectedMfaSerial, def.MfaSerial)\n\t}\n}\n\nfunc TestConfigParsingProfiles(t *testing.T) {\n\tf := newConfigFile(t, exampleConfig)\n\tdefer os.Remove(f)\n\n\tcfg, err := vault.LoadConfig(f)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar testCases = []struct {\n\t\texpected vault.ProfileSection\n\t\tok       bool\n\t}{\n\t\t{vault.ProfileSection{Name: \"user2\", Region: \"us-east-1\"}, true},\n\t\t{vault.ProfileSection{Name: \"withsource\", SourceProfile: \"user2\", Region: \"us-east-1\"}, true},\n\t\t{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},\n\t\t{vault.ProfileSection{Name: \"nopenotthere\"}, false},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(fmt.Sprintf(\"profile_%s\", tc.expected.Name), func(t *testing.T) {\n\t\t\tactual, ok := cfg.ProfileSection(tc.expected.Name)\n\t\t\tif ok != tc.ok {\n\t\t\t\tt.Fatalf(\"Expected second param to be %v, got %v\", tc.ok, ok)\n\t\t\t}\n\t\t\tif diff := cmp.Diff(tc.expected, actual); diff != \"\" {\n\t\t\t\tt.Errorf(\"ProfileSection() mismatch (-expected +actual):\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestConfigParsingDefault(t *testing.T) {\n\tf := newConfigFile(t, exampleConfig)\n\tdefer os.Remove(f)\n\n\tcfg, err := vault.LoadConfig(f)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdef, ok := cfg.ProfileSection(\"default\")\n\tif !ok {\n\t\tt.Fatalf(\"Expected to find default profile\")\n\t}\n\n\texpected := vault.ProfileSection{\n\t\tName:   \"default\",\n\t\tRegion: \"us-west-2\",\n\t}\n\n\tif !reflect.DeepEqual(def, expected) {\n\t\tt.Fatalf(\"Expected %+v, got %+v\", expected, def)\n\t}\n}\n\nfunc TestProfilesFromConfig(t *testing.T) {\n\tf := newConfigFile(t, exampleConfig)\n\tdefer os.Remove(f)\n\n\tcfg, err := vault.LoadConfig(f)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\texpected := []vault.ProfileSection{\n\t\t{Name: \"default\", Region: \"us-west-2\"},\n\t\t{Name: \"user2\", Region: \"us-east-1\"},\n\t\t{Name: \"withsource\", Region: \"us-east-1\", SourceProfile: \"user2\"},\n\t\t{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\"},\n\t\t{Name: \"testincludeprofile1\", Region: \"us-east-1\"},\n\t\t{Name: \"testincludeprofile2\", IncludeProfile: \"testincludeprofile1\"},\n\t\t{Name: \"with-sso-session\", SSOSession: \"moon-sso\", Region: \"moon-1\", SSOAccountID: \"123456\"},\n\t}\n\tactual := cfg.ProfileSections()\n\n\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\tt.Errorf(\"ProfileSections() mismatch (-expected +actual):\\n%s\", diff)\n\t}\n}\n\nfunc TestAddProfileToExistingConfig(t *testing.T) {\n\tf := newConfigFile(t, exampleConfig)\n\tdefer os.Remove(f)\n\n\tcfg, err := vault.LoadConfig(f)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = cfg.Add(vault.ProfileSection{\n\t\tName:          \"llamas\",\n\t\tMfaSerial:     \"testserial\",\n\t\tRegion:        \"us-east-1\",\n\t\tSourceProfile: \"default\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"Error adding profile: %#v\", err)\n\t}\n\n\texpected := []vault.ProfileSection{\n\t\t{Name: \"default\", Region: \"us-west-2\"},\n\t\t{Name: \"user2\", Region: \"us-east-1\"},\n\t\t{Name: \"withsource\", Region: \"us-east-1\", SourceProfile: \"user2\"},\n\t\t{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\"},\n\t\t{Name: \"testincludeprofile1\", Region: \"us-east-1\"},\n\t\t{Name: \"testincludeprofile2\", IncludeProfile: \"testincludeprofile1\"},\n\t\t{Name: \"with-sso-session\", SSOSession: \"moon-sso\", Region: \"moon-1\", SSOAccountID: \"123456\"},\n\t\t{Name: \"llamas\", MfaSerial: \"testserial\", Region: \"us-east-1\", SourceProfile: \"default\"},\n\t}\n\tactual := cfg.ProfileSections()\n\n\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\tt.Errorf(\"ProfileSections() mismatch (-expected +actual):\\n%s\", diff)\n\t}\n}\n\nfunc TestAddProfileToExistingNestedConfig(t *testing.T) {\n\tf := newConfigFile(t, nestedConfig)\n\tdefer os.Remove(f)\n\n\tcfg, err := vault.LoadConfig(f)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = cfg.Add(vault.ProfileSection{\n\t\tName:      \"llamas\",\n\t\tMfaSerial: \"testserial\",\n\t\tRegion:    \"us-east-1\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"Error adding profile: %#v\", err)\n\t}\n\n\texpected := append(nestedConfig, []byte(\n\t\t\"\\n[profile llamas]\\nmfa_serial=testserial\\nregion=us-east-1\\n\",\n\t)...)\n\n\tb, _ := os.ReadFile(f)\n\n\tif !bytes.Equal(expected, b) {\n\t\tt.Fatalf(\"Expected:\\n%q\\nGot:\\n%q\", expected, b)\n\t}\n}\n\nfunc TestIncludeProfile(t *testing.T) {\n\tf := newConfigFile(t, exampleConfig)\n\tdefer os.Remove(f)\n\n\tconfigFile, err := vault.LoadConfig(f)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconfigLoader := &vault.ConfigLoader{File: configFile}\n\tconfig, err := configLoader.GetProfileConfig(\"testincludeprofile2\")\n\tif err != nil {\n\t\tt.Fatalf(\"Should have found a profile: %v\", err)\n\t}\n\n\tif config.Region != \"us-east-1\" {\n\t\tt.Fatalf(\"Expected region %q, got %q\", \"us-east-1\", config.Region)\n\t}\n}\n\nfunc TestIncludeSsoSession(t *testing.T) {\n\tf := newConfigFile(t, exampleConfig)\n\tdefer os.Remove(f)\n\n\tconfigFile, err := vault.LoadConfig(f)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tconfigLoader := &vault.ConfigLoader{File: configFile}\n\tconfig, err := configLoader.GetProfileConfig(\"with-sso-session\")\n\tif err != nil {\n\t\tt.Fatalf(\"Should have found a profile: %v\", err)\n\t}\n\n\tif config.Region != \"moon-1\" { // Test not the same as SSO region\n\t\tt.Fatalf(\"Expected region %q, got %q\", \"moon-1\", config.Region)\n\t}\n\n\tssoStartURL := \"https://d-123456789.example.com/start\"\n\tif config.SSOStartURL != ssoStartURL {\n\t\tt.Fatalf(\"Expected sso_start_url %q, got %q\", ssoStartURL, config.Region)\n\t}\n\n\tif config.SSORegion != \"moon-2\" { // Test not the same as profile region\n\t\tt.Fatalf(\"Expected sso_region %q, got %q\", \"moon-2\", config.Region)\n\t}\n\t// Not checking sso_registration_scopes as it seems to be unused by aws-cli.\n}\n\nfunc TestProfileIsEmpty(t *testing.T) {\n\tp := vault.ProfileSection{Name: \"foo\"}\n\tif !p.IsEmpty() {\n\t\tt.Errorf(\"Expected p to be empty\")\n\t}\n}\n\nfunc TestIniWithHeaderSavesWithHeader(t *testing.T) {\n\tf := newConfigFile(t, defaultsOnlyConfigWithHeader)\n\tdefer os.Remove(f)\n\n\tcfg, err := vault.LoadConfig(f)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\terr = cfg.Save()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\texpected := defaultsOnlyConfigWithHeader\n\n\tb, _ := os.ReadFile(f)\n\n\tif !bytes.Equal(expected, b) {\n\t\tt.Fatalf(\"Expected:\\n%q\\nGot:\\n%q\", expected, b)\n\t}\n}\n\nfunc TestIniWithDEFAULTHeader(t *testing.T) {\n\tf := newConfigFile(t, []byte(`[DEFAULT]\nregion=us-east-1\n[default]\nregion=us-west-2\n`))\n\tdefer os.Remove(f)\n\n\tcfg, err := vault.LoadConfig(f)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\texpected := []vault.ProfileSection{\n\t\t{Name: \"default\", Region: \"us-west-2\"},\n\t}\n\tactual := cfg.ProfileSections()\n\n\tif diff := cmp.Diff(expected, actual); diff != \"\" {\n\t\tt.Errorf(\"ProfileSections() mismatch (-expected +actual):\\n%s\", diff)\n\t}\n}\n\nfunc TestLoadedProfileDoesntReferToItself(t *testing.T) {\n\tf := newConfigFile(t, []byte(`\n[profile foo]\nsource_profile=foo\n`))\n\tdefer os.Remove(f)\n\n\tconfigFile, err := vault.LoadConfig(f)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdef, ok := configFile.ProfileSection(\"foo\")\n\tif !ok {\n\t\tt.Fatalf(\"Couldn't load profile foo\")\n\t}\n\n\texpectedSourceProfile := \"foo\"\n\tif def.SourceProfile != expectedSourceProfile {\n\t\tt.Fatalf(\"Expected '%s', got '%s'\", expectedSourceProfile, def.SourceProfile)\n\t}\n\n\tconfigLoader := &vault.ConfigLoader{File: configFile}\n\tconfig, err := configLoader.GetProfileConfig(\"foo\")\n\tif err != nil {\n\t\tt.Fatalf(\"Should have found a profile: %v\", err)\n\t}\n\n\texpectedSourceProfileName := \"\"\n\tif config.SourceProfileName != expectedSourceProfileName {\n\t\tt.Fatalf(\"Expected '%s', got '%s'\", expectedSourceProfileName, config.SourceProfileName)\n\t}\n}\n\nfunc TestSourceProfileCanReferToParent(t *testing.T) {\n\tf := newConfigFile(t, []byte(`\n[profile root]\n\n[profile foo]\ninclude_profile=root\nsource_profile=root\n`))\n\tdefer os.Remove(f)\n\n\tconfigFile, err := vault.LoadConfig(f)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdef, ok := configFile.ProfileSection(\"foo\")\n\tif !ok {\n\t\tt.Fatalf(\"Couldn't load profile foo\")\n\t}\n\n\texpectedSourceProfile := \"root\"\n\tif def.SourceProfile != expectedSourceProfile {\n\t\tt.Fatalf(\"Expected '%s', got '%s'\", expectedSourceProfile, def.SourceProfile)\n\t}\n\n\tconfigLoader := &vault.ConfigLoader{File: configFile}\n\tconfig, err := configLoader.GetProfileConfig(\"foo\")\n\tif err != nil {\n\t\tt.Fatalf(\"Should have found a profile: %v\", err)\n\t}\n\n\texpectedSourceProfileName := \"root\"\n\tif config.SourceProfileName != expectedSourceProfileName {\n\t\tt.Fatalf(\"Expected '%s', got '%s'\", expectedSourceProfileName, config.SourceProfileName)\n\t}\n}\n\nfunc TestSetSessionTags(t *testing.T) {\n\tvar testCases = []struct {\n\t\tstringValue string\n\t\texpected    map[string]string\n\t\tok          bool\n\t}{\n\t\t{\"tag1=value1\", map[string]string{\"tag1\": \"value1\"}, true},\n\t\t{\n\t\t\t\"tag2=value2,tag3=value3,tag4=value4\",\n\t\t\tmap[string]string{\"tag2\": \"value2\", \"tag3\": \"value3\", \"tag4\": \"value4\"},\n\t\t\ttrue,\n\t\t},\n\t\t{\" tagA = valueA ,  tagB  =  valueB  ,  tagC   =   valueC  \",\n\t\t\tmap[string]string{\"tagA\": \"valueA\", \"tagB\": \"valueB\", \"tagC\": \"valueC\"},\n\t\t\ttrue,\n\t\t},\n\t\t{\"\", nil, false},\n\t\t{\"tag1=value1,\", nil, false},\n\t\t{\"tagA=valueA,tagB\", nil, false},\n\t\t{\"tagOne,tagTwo=valueTwo\", nil, false},\n\t\t{\"tagI=valueI,tagII,tagIII=valueIII\", nil, false},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tconfig := vault.ProfileConfig{}\n\t\terr := config.SetSessionTags(tc.stringValue)\n\t\tif tc.ok {\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Unsexpected parsing error: %s\", err)\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(tc.expected, config.SessionTags) {\n\t\t\t\tt.Fatalf(\"Expected SessionTags: %+v, got %+v\", tc.expected, config.SessionTags)\n\t\t\t}\n\t\t} else {\n\t\t\tif err == nil {\n\t\t\t\tt.Fatalf(\"Expected an error parsing %#v, but got none\", tc.stringValue)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestSetTransitiveSessionTags(t *testing.T) {\n\tvar testCases = []struct {\n\t\tstringValue string\n\t\texpected    []string\n\t}{\n\t\t{\"tag1\", []string{\"tag1\"}},\n\t\t{\"tag2,tag3,tag4\", []string{\"tag2\", \"tag3\", \"tag4\"}},\n\t\t{\" tagA ,  tagB  ,   tagC   \", []string{\"tagA\", \"tagB\", \"tagC\"}},\n\t\t{\"tag1,\", []string{\"tag1\"}},\n\t\t{\",tagA\", []string{\"tagA\"}},\n\t\t{\"\", nil},\n\t\t{\",\", nil},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tconfig := vault.ProfileConfig{}\n\t\tconfig.SetTransitiveSessionTags(tc.stringValue)\n\t\tif !reflect.DeepEqual(tc.expected, config.TransitiveSessionTags) {\n\t\t\tt.Fatalf(\"Expected TransitiveSessionTags: %+v, got %+v\", tc.expected, config.TransitiveSessionTags)\n\t\t}\n\t}\n}\n\nfunc TestSessionTaggingFromIni(t *testing.T) {\n\tos.Unsetenv(\"AWS_SESSION_TAGS\")\n\tos.Unsetenv(\"AWS_TRANSITIVE_TAGS\")\n\tf := newConfigFile(t, []byte(`\n[profile tagged]\nsession_tags = tag1 = value1 , tag2=value2 ,tag3=value3\ntransitive_session_tags = tagOne ,tagTwo,tagThree\n`))\n\tdefer os.Remove(f)\n\n\tconfigFile, err := vault.LoadConfig(f)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconfigLoader := &vault.ConfigLoader{File: configFile, ActiveProfile: \"tagged\"}\n\tconfig, err := configLoader.GetProfileConfig(\"tagged\")\n\tif err != nil {\n\t\tt.Fatalf(\"Should have found a profile: %v\", err)\n\t}\n\texpectedSessionTags := map[string]string{\n\t\t\"tag1\": \"value1\",\n\t\t\"tag2\": \"value2\",\n\t\t\"tag3\": \"value3\",\n\t}\n\tif !reflect.DeepEqual(expectedSessionTags, config.SessionTags) {\n\t\tt.Fatalf(\"Expected session_tags: %+v, got %+v\", expectedSessionTags, config.SessionTags)\n\t}\n\n\texpectedTransitiveSessionTags := []string{\"tagOne\", \"tagTwo\", \"tagThree\"}\n\tif !reflect.DeepEqual(expectedTransitiveSessionTags, config.TransitiveSessionTags) {\n\t\tt.Fatalf(\"Expected transitive_session_tags: %+v, got %+v\", expectedTransitiveSessionTags, config.TransitiveSessionTags)\n\t}\n}\n\nfunc TestSessionTaggingFromEnvironment(t *testing.T) {\n\tos.Setenv(\"AWS_SESSION_TAGS\", \" tagA = val1 , tagB=val2 ,tagC=val3\")\n\tos.Setenv(\"AWS_TRANSITIVE_TAGS\", \" tagD ,tagE\")\n\tdefer os.Unsetenv(\"AWS_SESSION_TAGS\")\n\tdefer os.Unsetenv(\"AWS_TRANSITIVE_TAGS\")\n\n\tf := newConfigFile(t, []byte(`\n[profile tagged]\nsession_tags = tag1 = value1 , tag2=value2 ,tag3=value3\ntransitive_session_tags = tagOne ,tagTwo,tagThree\n`))\n\tdefer os.Remove(f)\n\n\tconfigFile, err := vault.LoadConfig(f)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconfigLoader := &vault.ConfigLoader{File: configFile, ActiveProfile: \"tagged\"}\n\tconfig, err := configLoader.GetProfileConfig(\"tagged\")\n\tif err != nil {\n\t\tt.Fatalf(\"Should have found a profile: %v\", err)\n\t}\n\texpectedSessionTags := map[string]string{\n\t\t\"tagA\": \"val1\",\n\t\t\"tagB\": \"val2\",\n\t\t\"tagC\": \"val3\",\n\t}\n\tif !reflect.DeepEqual(expectedSessionTags, config.SessionTags) {\n\t\tt.Fatalf(\"Expected session_tags: %+v, got %+v\", expectedSessionTags, config.SessionTags)\n\t}\n\n\texpectedTransitiveSessionTags := []string{\"tagD\", \"tagE\"}\n\tif !reflect.DeepEqual(expectedTransitiveSessionTags, config.TransitiveSessionTags) {\n\t\tt.Fatalf(\"Expected transitive_session_tags: %+v, got %+v\", expectedTransitiveSessionTags, config.TransitiveSessionTags)\n\t}\n}\n\nfunc TestSessionTaggingFromEnvironmentChainedRoles(t *testing.T) {\n\tos.Setenv(\"AWS_SESSION_TAGS\", \"tagI=valI\")\n\tos.Setenv(\"AWS_TRANSITIVE_TAGS\", \" tagII\")\n\tdefer os.Unsetenv(\"AWS_SESSION_TAGS\")\n\tdefer os.Unsetenv(\"AWS_TRANSITIVE_TAGS\")\n\n\tf := newConfigFile(t, []byte(`\n[profile base]\n\n[profile interim]\nsession_tags=tag1=value1\ntransitive_session_tags=tag2\nsource_profile = base\n\n[profile target]\nsession_tags=tagA=valueA\ntransitive_session_tags=tagB\nsource_profile = interim\n`))\n\tdefer os.Remove(f)\n\n\tconfigFile, err := vault.LoadConfig(f)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconfigLoader := &vault.ConfigLoader{File: configFile, ActiveProfile: \"target\"}\n\tconfig, err := configLoader.GetProfileConfig(\"target\")\n\tif err != nil {\n\t\tt.Fatalf(\"Should have found a profile: %v\", err)\n\t}\n\n\t// Testing target profile, should have values populated from environment variables\n\texpectedSessionTags := map[string]string{\"tagI\": \"valI\"}\n\tif !reflect.DeepEqual(expectedSessionTags, config.SessionTags) {\n\t\tt.Fatalf(\"Expected session_tags: %+v, got %+v\", expectedSessionTags, config.SessionTags)\n\t}\n\n\texpectedTransitiveSessionTags := []string{\"tagII\"}\n\tif !reflect.DeepEqual(expectedTransitiveSessionTags, config.TransitiveSessionTags) {\n\t\tt.Fatalf(\"Expected transitive_session_tags: %+v, got %+v\", expectedTransitiveSessionTags, config.TransitiveSessionTags)\n\t}\n\n\t// Testing interim profile, parameters should come from the config, not environment\n\tinterimConfig := config.SourceProfile\n\texpectedSessionTags = map[string]string{\"tag1\": \"value1\"}\n\tif !reflect.DeepEqual(expectedSessionTags, interimConfig.SessionTags) {\n\t\tt.Fatalf(\"Expected session_tags: %+v, got %+v\", expectedSessionTags, interimConfig.SessionTags)\n\t}\n\n\texpectedTransitiveSessionTags = []string{\"tag2\"}\n\tif !reflect.DeepEqual(expectedTransitiveSessionTags, interimConfig.TransitiveSessionTags) {\n\t\tt.Fatalf(\"Expected transitive_session_tags: %+v, got %+v\", expectedTransitiveSessionTags, interimConfig.TransitiveSessionTags)\n\t}\n\n\t// Testing base profile, should have empty parameters\n\tbaseConfig := interimConfig.SourceProfile\n\tif len(baseConfig.SessionTags) > 0 {\n\t\tt.Fatalf(\"Expected session_tags to be empty, got %+v\", baseConfig.SessionTags)\n\t}\n\n\tif len(baseConfig.TransitiveSessionTags) > 0 {\n\t\tt.Fatalf(\"Expected transitive_session_tags to be empty, got %+v\", baseConfig.TransitiveSessionTags)\n\t}\n}\n"
  },
  {
    "path": "vault/credentialkeyring.go",
    "content": "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\ntype CredentialKeyring struct {\n\tKeyring keyring.Keyring\n}\n\nfunc (ck *CredentialKeyring) Keys() (credentialsNames []string, err error) {\n\tallKeys, err := ck.Keyring.Keys()\n\tif err != nil {\n\t\treturn credentialsNames, err\n\t}\n\tfor _, keyName := range allKeys {\n\t\tif !IsSessionKey(keyName) && !IsOIDCTokenKey(keyName) {\n\t\t\tcredentialsNames = append(credentialsNames, keyName)\n\t\t}\n\t}\n\treturn credentialsNames, nil\n}\n\nfunc (ck *CredentialKeyring) Has(credentialsName string) (bool, error) {\n\tallKeys, err := ck.Keyring.Keys()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tfor _, keyName := range allKeys {\n\t\tif keyName == credentialsName {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\treturn false, nil\n}\n\nfunc (ck *CredentialKeyring) Get(credentialsName string) (creds aws.Credentials, err error) {\n\titem, err := ck.Keyring.Get(credentialsName)\n\tif err != nil {\n\t\treturn creds, err\n\t}\n\tif err = json.Unmarshal(item.Data, &creds); err != nil {\n\t\treturn creds, fmt.Errorf(\"Invalid data in keyring: %v\", err)\n\t}\n\treturn creds, err\n}\n\nfunc (ck *CredentialKeyring) Set(credentialsName string, creds aws.Credentials) error {\n\tbytes, err := json.Marshal(creds)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn ck.Keyring.Set(keyring.Item{\n\t\tKey:   credentialsName,\n\t\tLabel: fmt.Sprintf(\"aws-vault (%s)\", credentialsName),\n\t\tData:  bytes,\n\n\t\t// specific Keychain settings\n\t\tKeychainNotTrustApplication: true,\n\t})\n}\n\nfunc (ck *CredentialKeyring) Remove(credentialsName string) error {\n\treturn ck.Keyring.Remove(credentialsName)\n}\n"
  },
  {
    "path": "vault/credentialprocessprovider.go",
    "content": "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 \"github.com/aws/aws-sdk-go-v2/service/sts/types\"\n)\n\nvar credentialProcessRequiredFields = []string{\"AccessKeyId\", \"Expiration\", \"SecretAccessKey\", \"SessionToken\"}\n\n// CredentialProcessProvider implements interface aws.CredentialsProvider to retrieve credentials from an external executable\n// as described in https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#sourcing-credentials-from-external-processes\ntype CredentialProcessProvider struct {\n\tCredentialProcess string\n}\n\nfunc (p *CredentialProcessProvider) validateJSONCredential(cred *ststypes.Credentials) error {\n\tvar missing []string\n\n\th := reflect.ValueOf(cred).Elem()\n\tfor _, requiredField := range credentialProcessRequiredFields {\n\t\tif h.FieldByName(requiredField).IsNil() {\n\t\t\tmissing = append(missing, requiredField)\n\t\t}\n\t}\n\n\tif len(missing) > 0 {\n\t\treturn fmt.Errorf(\"JSON credential from command %q missing the following fields: %v\", p.CredentialProcess, missing)\n\t}\n\n\treturn nil\n}\n\n// Retrieve obtains a new set of temporary credentials using an external process, required to satisfy interface aws.CredentialsProvider\nfunc (p *CredentialProcessProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {\n\treturn p.retrieveWith(ctx, executeProcess)\n}\n\nfunc (p *CredentialProcessProvider) retrieveWith(ctx context.Context, fn func(string) (string, error)) (aws.Credentials, error) {\n\tcreds, err := p.callCredentialProcessWith(ctx, fn)\n\tif err != nil {\n\t\treturn aws.Credentials{}, err\n\t}\n\n\treturn aws.Credentials{\n\t\tAccessKeyID:     aws.ToString(creds.AccessKeyId),\n\t\tSecretAccessKey: aws.ToString(creds.SecretAccessKey),\n\t\tSessionToken:    aws.ToString(creds.SessionToken),\n\t\tCanExpire:       true,\n\t\tExpires:         aws.ToTime(creds.Expiration),\n\t}, nil\n}\n\nfunc (p *CredentialProcessProvider) RetrieveStsCredentials(ctx context.Context) (*ststypes.Credentials, error) {\n\treturn p.callCredentialProcessWith(ctx, executeProcess)\n}\n\nfunc (p *CredentialProcessProvider) callCredentialProcessWith(_ context.Context, fn func(string) (string, error)) (*ststypes.Credentials, error) {\n\t// Exec CredentialProcess to retrieve AWS creds in JSON format as described in\n\t// https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#sourcing-credentials-from-external-processes\n\toutput, err := fn(p.CredentialProcess)\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Unmarshal the JSON into a ststypes.Credentials object\n\tvar value ststypes.Credentials\n\tif err := json.Unmarshal([]byte(output), &value); err != nil {\n\t\treturn &ststypes.Credentials{}, fmt.Errorf(\"invalid JSON format from command %q: %v\", p.CredentialProcess, err)\n\t}\n\n\t// Validate that all required fields were present in JSON before returning\n\treturn &value, p.validateJSONCredential(&value)\n}\n"
  },
  {
    "path": "vault/credentialprocessprovider_test.go",
    "content": "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/aws-sdk-go-v2/aws\"\n\tststypes \"github.com/aws/aws-sdk-go-v2/service/sts/types\"\n)\n\nfunc executeFail(_ string) (string, error) {\n\treturn \"\", errors.New(\"executing process failed\")\n}\n\nfunc executeGetBadJSON(_ string) (string, error) {\n\treturn \"Junk\", nil\n}\n\nfunc executeGetCredential(accessKeyID *string, expiration *time.Time, secretAccesKey *string, sessionToken *string) (string, error) {\n\tv, err := json.Marshal(ststypes.Credentials{\n\t\tAccessKeyId:     accessKeyID,\n\t\tExpiration:      expiration,\n\t\tSecretAccessKey: secretAccesKey,\n\t\tSessionToken:    sessionToken,\n\t})\n\treturn string(v), err\n}\n\nfunc TestCredentialProcessProvider_Retrieve(t *testing.T) {\n\taccessKeyID := \"abcd\"\n\texpiration := time.Time{}\n\tsecretAccessKey := \"0123\"\n\tsessionToken := \"4567\"\n\n\twant := aws.Credentials{\n\t\tAccessKeyID:     accessKeyID,\n\t\tExpires:         expiration,\n\t\tCanExpire:       true,\n\t\tSecretAccessKey: secretAccessKey,\n\t\tSessionToken:    sessionToken,\n\t}\n\n\ttests := []struct {\n\t\tname                string\n\t\texecFunc            func(string) (string, error)\n\t\twantErr             bool\n\t\texpectMissingFields bool\n\t}{\n\t\t{\n\t\t\tname:                \"process execution fails\",\n\t\t\texecFunc:            executeFail,\n\t\t\twantErr:             true,\n\t\t\texpectMissingFields: false,\n\t\t},\n\t\t{\n\t\t\tname:                \"bad json\",\n\t\t\texecFunc:            executeGetBadJSON,\n\t\t\twantErr:             true,\n\t\t\texpectMissingFields: false,\n\t\t},\n\t\t{\n\t\t\tname: \"successful execution, good cred\",\n\t\t\texecFunc: func(string) (string, error) {\n\t\t\t\treturn executeGetCredential(&accessKeyID, &expiration, &secretAccessKey, &sessionToken)\n\t\t\t},\n\t\t\twantErr:             false,\n\t\t\texpectMissingFields: false,\n\t\t},\n\t\t{\n\t\t\tname: \"fields missing\",\n\t\t\texecFunc: func(string) (string, error) {\n\t\t\t\treturn executeGetCredential(nil, nil, nil, nil)\n\t\t\t},\n\t\t\twantErr:             true,\n\t\t\texpectMissingFields: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tctx := context.Background()\n\t\t\tprovider := CredentialProcessProvider{\n\t\t\t\tCredentialProcess: \"\",\n\t\t\t}\n\t\t\tgot, err := provider.retrieveWith(ctx, tt.execFunc)\n\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CredentialProcessProvider.Retrieve() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif !tt.wantErr && !reflect.DeepEqual(got, want) {\n\t\t\t\tt.Errorf(\"CredentialProcessProvider.Retrieve() = %v, want %v\", got, want)\n\t\t\t}\n\n\t\t\tif tt.wantErr && tt.expectMissingFields {\n\t\t\t\tfor _, expectedMissingField := range credentialProcessRequiredFields {\n\t\t\t\t\tif !strings.Contains(err.Error(), expectedMissingField) {\n\t\t\t\t\t\tt.Errorf(\"expected field '%v' not present in error: %v'\", expectedMissingField, err)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "vault/executeprocess.go",
    "content": "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 cmdArgs []string\n\tif runtime.GOOS == \"windows\" {\n\t\tcmdArgs = []string{\"cmd.exe\", \"/C\", process}\n\t} else {\n\t\tcmdArgs = []string{\"/bin/sh\", \"-c\", process}\n\t}\n\n\tcmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)\n\tcmd.Env = os.Environ()\n\tcmd.Stdin = os.Stdin\n\tcmd.Stderr = os.Stderr\n\n\toutput, err := cmd.Output()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"running command %q: %v\", process, err)\n\t}\n\treturn string(output), nil\n}\n"
  },
  {
    "path": "vault/federationtokenprovider.go",
    "content": "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/service/sts\"\n)\n\nconst allowAllIAMPolicy = `{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":\"*\",\"Resource\":\"*\"}]}`\n\n// FederationTokenProvider retrieves temporary credentials from STS using GetFederationToken\ntype FederationTokenProvider struct {\n\tStsClient *sts.Client\n\tName      string\n\tDuration  time.Duration\n}\n\nfunc (f *FederationTokenProvider) name() string {\n\t// truncate the username if it's longer than 32 characters or else GetFederationToken will fail. see: https://docs.aws.amazon.com/STS/latest/APIReference/API_GetFederationToken.html\n\tif len(f.Name) > 32 {\n\t\treturn f.Name[0:32]\n\t}\n\treturn f.Name\n}\n\n// Retrieve generates a new set of temporary credentials using STS GetFederationToken\nfunc (f *FederationTokenProvider) Retrieve(ctx context.Context) (creds aws.Credentials, err error) {\n\tresp, err := f.StsClient.GetFederationToken(ctx, &sts.GetFederationTokenInput{\n\t\tName:            aws.String(f.name()),\n\t\tDurationSeconds: aws.Int32(int32(f.Duration.Seconds())),\n\t\tPolicy:          aws.String(allowAllIAMPolicy),\n\t})\n\tif err != nil {\n\t\treturn creds, err\n\t}\n\n\tlog.Printf(\"Generated credentials %s using GetFederationToken, expires in %s\", FormatKeyForDisplay(*resp.Credentials.AccessKeyId), time.Until(*resp.Credentials.Expiration).String())\n\n\treturn aws.Credentials{\n\t\tAccessKeyID:     aws.ToString(resp.Credentials.AccessKeyId),\n\t\tSecretAccessKey: aws.ToString(resp.Credentials.SecretAccessKey),\n\t\tSessionToken:    aws.ToString(resp.Credentials.SessionToken),\n\t\tCanExpire:       true,\n\t\tExpires:         aws.ToTime(resp.Credentials.Expiration),\n\t}, nil\n}\n"
  },
  {
    "path": "vault/getuser.go",
    "content": "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-sdk-go-v2/service/iam\"\n)\n\nvar getUserErrorRegexp = regexp.MustCompile(`^AccessDenied: User: arn:aws:iam::(\\d+):user/(.+) is not`)\n\n// GetUsernameFromSession returns the IAM username (or root) associated with the current aws session\nfunc GetUsernameFromSession(ctx context.Context, cfg aws.Config) (string, error) {\n\tiamClient := iam.NewFromConfig(cfg)\n\tresp, err := iamClient.GetUser(ctx, &iam.GetUserInput{})\n\tif err != nil {\n\t\t// Even if GetUser fails, the current user is included in the error. This happens when you have o IAM permissions\n\t\t// on the master credentials, but have permission to use assumeRole later\n\t\tmatches := getUserErrorRegexp.FindStringSubmatch(err.Error())\n\t\tif len(matches) > 0 {\n\t\t\tpathParts := strings.Split(matches[2], \"/\")\n\t\t\treturn pathParts[len(pathParts)-1], nil\n\t\t}\n\n\t\treturn \"\", err\n\t}\n\n\tif resp.User.UserName != nil {\n\t\treturn *resp.User.UserName, nil\n\t}\n\n\tif resp.User.Arn != nil {\n\t\tarnParts := strings.Split(*resp.User.Arn, \":\")\n\t\treturn arnParts[len(arnParts)-1], nil\n\t}\n\n\treturn \"\", fmt.Errorf(\"Couldn't determine current username\")\n}\n"
  },
  {
    "path": "vault/keyringprovider.go",
    "content": "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 retrieves master credentials\ntype KeyringProvider struct {\n\tKeyring         *CredentialKeyring\n\tCredentialsName string\n}\n\nfunc (p *KeyringProvider) Retrieve(_ context.Context) (aws.Credentials, error) {\n\tlog.Printf(\"Looking up keyring for '%s'\", p.CredentialsName)\n\treturn p.Keyring.Get(p.CredentialsName)\n}\n"
  },
  {
    "path": "vault/mfa.go",
    "content": "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\"\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n)\n\n// Mfa contains options for an MFA device\ntype Mfa struct {\n\tMfaSerial     string\n\tmfaPromptFunc prompt.Func\n}\n\n// GetMfaToken returns the MFA token\nfunc (m Mfa) GetMfaToken() (*string, error) {\n\tif m.mfaPromptFunc != nil {\n\t\ttoken, err := m.mfaPromptFunc(m.MfaSerial)\n\t\treturn aws.String(token), err\n\t}\n\n\treturn nil, errors.New(\"No prompt found\")\n}\n\nfunc NewMfa(config *ProfileConfig) Mfa {\n\tm := Mfa{\n\t\tMfaSerial: config.MfaSerial,\n\t}\n\tif config.MfaToken != \"\" {\n\t\tm.mfaPromptFunc = func(_ string) (string, error) { return config.MfaToken, nil }\n\t} else if config.MfaProcess != \"\" {\n\t\tm.mfaPromptFunc = func(_ string) (string, error) {\n\t\t\tlog.Println(\"Executing mfa_process\")\n\t\t\treturn ProcessMfaProvider(config.MfaProcess)\n\t\t}\n\t} else {\n\t\tm.mfaPromptFunc = prompt.Method(config.MfaPromptMethod)\n\t}\n\n\treturn m\n}\n\nfunc ProcessMfaProvider(processCmd string) (string, error) {\n\tcmd := exec.Command(\"/bin/sh\", \"-c\", processCmd)\n\tcmd.Stderr = os.Stderr\n\n\tout, err := cmd.Output()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"process provider: %w\", err)\n\t}\n\n\treturn strings.TrimSpace(string(out)), nil\n}\n"
  },
  {
    "path": "vault/oidctokenkeyring.go",
    "content": "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/aws/aws-sdk-go-v2/service/ssooidc\"\n)\n\ntype OIDCTokenKeyring struct {\n\tKeyring keyring.Keyring\n}\n\ntype OIDCTokenData struct {\n\tToken      ssooidc.CreateTokenOutput\n\tExpiration time.Time\n}\n\nconst oidcTokenKeyPrefix = \"oidc:\"\n\nfunc (o *OIDCTokenKeyring) fmtKey(startURL string) string {\n\treturn oidcTokenKeyPrefix + startURL\n}\n\nfunc IsOIDCTokenKey(k string) bool {\n\treturn strings.HasPrefix(k, oidcTokenKeyPrefix)\n}\n\nfunc (o OIDCTokenKeyring) Has(startURL string) (bool, error) {\n\tkk, err := o.Keyring.Keys()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tfor _, k := range kk {\n\t\tif startURL == k {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\n\treturn false, nil\n}\n\nfunc (o OIDCTokenKeyring) Get(startURL string) (*ssooidc.CreateTokenOutput, error) {\n\titem, err := o.Keyring.Get(o.fmtKey(startURL))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tval := OIDCTokenData{}\n\n\tif err = json.Unmarshal(item.Data, &val); err != nil {\n\t\tlog.Printf(\"Invalid data in keyring: %s\", err.Error())\n\t\treturn nil, keyring.ErrKeyNotFound\n\t}\n\tif time.Now().After(val.Expiration) {\n\t\tlog.Printf(\"OIDC token for '%s' expired, removing\", startURL)\n\t\t_ = o.Remove(startURL)\n\t\treturn nil, keyring.ErrKeyNotFound\n\t}\n\n\tsecondsLeft := time.Until(val.Expiration) / time.Second\n\n\tval.Token.ExpiresIn = int32(secondsLeft)\n\n\treturn &val.Token, err\n}\n\nfunc (o OIDCTokenKeyring) Set(startURL string, token *ssooidc.CreateTokenOutput) error {\n\tval := OIDCTokenData{\n\t\tToken:      *token,\n\t\tExpiration: time.Now().Add(time.Duration(token.ExpiresIn) * time.Second),\n\t}\n\n\tvalJSON, err := json.Marshal(val)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn o.Keyring.Set(keyring.Item{\n\t\tKey:         o.fmtKey(startURL),\n\t\tData:        valJSON,\n\t\tLabel:       fmt.Sprintf(\"aws-vault oidc token for %s (expires %s)\", startURL, val.Expiration.Format(time.RFC3339)),\n\t\tDescription: \"aws-vault oidc token\",\n\t})\n}\n\nfunc (o OIDCTokenKeyring) Remove(startURL string) error {\n\treturn o.Keyring.Remove(o.fmtKey(startURL))\n}\n\nfunc (o *OIDCTokenKeyring) RemoveAll() (n int, err error) {\n\tallKeys, err := o.Keys()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tfor _, key := range allKeys {\n\t\tif err = o.Remove(key); err != nil {\n\t\t\treturn n, err\n\t\t}\n\t\tn++\n\t}\n\treturn n, nil\n}\n\nfunc (o *OIDCTokenKeyring) Keys() (kk []string, err error) {\n\tallKeys, err := o.Keyring.Keys()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, k := range allKeys {\n\t\tif IsOIDCTokenKey(k) {\n\t\t\tkk = append(kk, strings.TrimPrefix(k, oidcTokenKeyPrefix))\n\t\t}\n\t}\n\n\treturn kk, nil\n}\n"
  },
  {
    "path": "vault/sessionkeyring.go",
    "content": "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\"github.com/99designs/keyring\"\n\tststypes \"github.com/aws/aws-sdk-go-v2/service/sts/types\"\n)\n\nvar sessionKeyPattern = regexp.MustCompile(`^(?P<type>[^,]+),(?P<profile>[^,]+),(?P<mfaSerial>[^,]*),(?P<expiration>[0-9]{1,})$`)\n\nvar oldSessionKeyPatterns = []*regexp.Regexp{\n\tregexp.MustCompile(`^session,(?P<profile>[^,]+),(?P<mfaSerial>[^,]*),(?P<expiration>[0-9]{2,})$`),\n\tregexp.MustCompile(`^session:(?P<profile>[^ ]+):(?P<mfaSerial>[^ ]*):(?P<expiration>[^:]+)$`),\n\tregexp.MustCompile(`^(.+?) session \\((\\d+)\\)$`),\n}\nvar base64URLEncodingNoPadding = base64.URLEncoding.WithPadding(base64.NoPadding)\n\nfunc IsOldSessionKey(s string) bool {\n\tfor _, pattern := range oldSessionKeyPatterns {\n\t\tif pattern.MatchString(s) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc IsCurrentSessionKey(s string) bool {\n\t_, err := NewSessionKeyFromString(s)\n\treturn err == nil\n}\n\nfunc IsSessionKey(s string) bool {\n\treturn IsCurrentSessionKey(s) || IsOldSessionKey(s)\n}\n\ntype SessionMetadata struct {\n\tType        string\n\tProfileName string\n\tMfaSerial   string\n\tExpiration  time.Time\n}\n\nfunc (k *SessionMetadata) String() string {\n\treturn fmt.Sprintf(\n\t\t\"%s,%s,%s,%d\",\n\t\tk.Type,\n\t\tbase64URLEncodingNoPadding.EncodeToString([]byte(k.ProfileName)),\n\t\tbase64URLEncodingNoPadding.EncodeToString([]byte(k.MfaSerial)),\n\t\tk.Expiration.Unix(),\n\t)\n}\n\nfunc (k *SessionMetadata) StringForMatching() string {\n\treturn fmt.Sprintf(\n\t\t\"%s,%s,%s,\",\n\t\tk.Type,\n\t\tbase64URLEncodingNoPadding.EncodeToString([]byte(k.ProfileName)),\n\t\tbase64URLEncodingNoPadding.EncodeToString([]byte(k.MfaSerial)),\n\t)\n}\n\nfunc NewSessionKeyFromString(s string) (SessionMetadata, error) {\n\tmatches := sessionKeyPattern.FindStringSubmatch(s)\n\tif len(matches) == 0 {\n\t\treturn SessionMetadata{}, fmt.Errorf(\"failed to parse session name: %s\", s)\n\t}\n\n\tprofileName, err := base64URLEncodingNoPadding.DecodeString(matches[2])\n\tif err != nil {\n\t\treturn SessionMetadata{}, err\n\t}\n\tmfaSerial, err := base64URLEncodingNoPadding.DecodeString(matches[3])\n\tif err != nil {\n\t\treturn SessionMetadata{}, err\n\t}\n\texpiryUnixtime, err := strconv.Atoi(matches[4])\n\tif err != nil {\n\t\treturn SessionMetadata{}, err\n\t}\n\n\treturn SessionMetadata{\n\t\tType:        matches[1],\n\t\tProfileName: string(profileName),\n\t\tMfaSerial:   string(mfaSerial),\n\t\tExpiration:  time.Unix(int64(expiryUnixtime), 0),\n\t}, nil\n}\n\ntype SessionKeyring struct {\n\tKeyring keyring.Keyring\n}\n\nvar ErrNotFound = keyring.ErrKeyNotFound\n\nfunc (sk *SessionKeyring) lookupKeyName(key SessionMetadata) (string, error) {\n\tallKeys, err := sk.Keyring.Keys()\n\tif err != nil {\n\t\treturn key.String(), err\n\t}\n\tfor _, keyName := range allKeys {\n\t\tif strings.HasPrefix(keyName, key.StringForMatching()) {\n\t\t\treturn keyName, nil\n\t\t}\n\t}\n\treturn key.String(), ErrNotFound\n}\n\nfunc (sk *SessionKeyring) Has(key SessionMetadata) (bool, error) {\n\t_, err := sk.lookupKeyName(key)\n\tif err == ErrNotFound {\n\t\treturn false, nil\n\t}\n\tif err == nil {\n\t\treturn true, nil\n\t}\n\n\treturn false, err\n}\n\nfunc (sk *SessionKeyring) Get(key SessionMetadata) (creds *ststypes.Credentials, err error) {\n\t_, _ = sk.RemoveOldSessions()\n\n\tkeyName, err := sk.lookupKeyName(key)\n\tif err != nil && err != ErrNotFound {\n\t\treturn nil, err\n\t}\n\titem, err := sk.Keyring.Get(keyName)\n\tif err != nil {\n\t\treturn creds, err\n\t}\n\tif err = json.Unmarshal(item.Data, &creds); err != nil {\n\t\tlog.Printf(\"SessionKeyring: Ignoring invalid data: %s\", err.Error())\n\t\treturn creds, ErrNotFound\n\t}\n\treturn creds, err\n}\n\nfunc (sk *SessionKeyring) Set(key SessionMetadata, creds *ststypes.Credentials) error {\n\t_, _ = sk.RemoveOldSessions()\n\n\tkey.Expiration = *creds.Expiration\n\n\tvalJSON, err := json.Marshal(creds)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tkeyName, err := sk.lookupKeyName(key)\n\tif err != ErrNotFound {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif keyName != key.String() {\n\t\t\terr = sk.Keyring.Remove(keyName)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn sk.Keyring.Set(keyring.Item{\n\t\tKey:         key.String(),\n\t\tData:        valJSON,\n\t\tLabel:       fmt.Sprintf(\"aws-vault session for %s (expires %s)\", key.ProfileName, creds.Expiration.Format(time.RFC3339)),\n\t\tDescription: \"aws-vault session\",\n\t})\n}\n\nfunc (sk *SessionKeyring) Remove(key SessionMetadata) error {\n\tkeyName, err := sk.lookupKeyName(key)\n\tif err != nil && err != ErrNotFound {\n\t\treturn err\n\t}\n\n\treturn sk.Keyring.Remove(keyName)\n}\n\nfunc (sk *SessionKeyring) RemoveAll() (n int, err error) {\n\tallKeys, err := sk.Keys()\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tfor _, key := range allKeys {\n\t\tif err = sk.Remove(key); err != nil {\n\t\t\treturn n, err\n\t\t}\n\t\tn++\n\t}\n\treturn n, nil\n}\n\nfunc (sk *SessionKeyring) Keys() (kk []SessionMetadata, err error) {\n\tallKeys, err := sk.Keyring.Keys()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, s := range allKeys {\n\t\tif k, err := NewSessionKeyFromString(s); err == nil {\n\t\t\tkk = append(kk, k)\n\t\t}\n\t}\n\n\treturn kk, nil\n}\n\nfunc (sk *SessionKeyring) realSessionKey(key SessionMetadata) (m SessionMetadata, err error) {\n\tkeyName, err := sk.lookupKeyName(key)\n\tif err != nil {\n\t\treturn m, err\n\t}\n\tsessKey, err := NewSessionKeyFromString(keyName)\n\tif err != nil {\n\t\treturn m, err\n\t}\n\treturn sessKey, nil\n}\n\nfunc (sk *SessionKeyring) GetAllMetadata() (mm []SessionMetadata, err error) {\n\tallKeys, err := sk.Keys()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, k := range allKeys {\n\t\tm, err := sk.realSessionKey(k)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"GetAllMetadata: %w\", err)\n\t\t}\n\n\t\tmm = append(mm, m)\n\t}\n\n\treturn mm, nil\n}\n\nfunc (sk *SessionKeyring) RemoveForProfile(profileName string) (n int, err error) {\n\tsessions, err := sk.GetAllMetadata()\n\tif err != nil {\n\t\treturn n, err\n\t}\n\tfor _, s := range sessions {\n\t\tif s.ProfileName == profileName {\n\t\t\terr = sk.Remove(s)\n\t\t\tif err != nil {\n\t\t\t\treturn n, err\n\t\t\t}\n\t\t\tn++\n\t\t}\n\t}\n\n\treturn n, nil\n}\n\nfunc (sk *SessionKeyring) RemoveOldSessions() (n int, err error) {\n\tallKeys, err := sk.Keyring.Keys()\n\tif err != nil {\n\t\tlog.Printf(\"Error while deleting old session: %s\", err.Error())\n\t}\n\n\tfor _, k := range allKeys {\n\t\tif IsOldSessionKey(k) {\n\t\t\terr = sk.Keyring.Remove(k)\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"Error while deleting old session: %s\", err.Error())\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tn++\n\t\t} else {\n\t\t\tstsk, err := NewSessionKeyFromString(k)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif time.Now().After(stsk.Expiration) {\n\t\t\t\terr = sk.Keyring.Remove(k)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Printf(\"Error while deleting old session: %s\", err.Error())\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tn++\n\t\t\t}\n\t\t}\n\t}\n\n\treturn n, nil\n}\n"
  },
  {
    "path": "vault/sessionkeyring_test.go",
    "content": "package vault_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/99designs/aws-vault/v7/vault\"\n)\n\nfunc TestIsSessionKey(t *testing.T) {\n\tvar testCases = []struct {\n\t\tKey       string\n\t\tIsSession bool\n\t}{\n\t\t{\"blah\", false},\n\t\t{\"blah session (61633665646639303539)\", true},\n\t\t{\"blah-iam session (32383863333237616430)\", true},\n\t\t{\"session,c2Vzc2lvbg,,1572281751\", true},\n\t\t{\"session,c2Vzc2lvbg,YXJuOmF3czppYW06OjEyMzQ1Njc4OTA6bWZhL2pzdGV3bW9u,1572281751\", true},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tif tc.IsSession && !vault.IsSessionKey(tc.Key) {\n\t\t\tt.Fatalf(\"%q is a session key, but wasn't detected as one\", tc.Key)\n\t\t} else if !tc.IsSession && vault.IsSessionKey(tc.Key) {\n\t\t\tt.Fatalf(\"%q isn't a session key, but was detected as one\", tc.Key)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "vault/sessiontokenprovider.go",
    "content": "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/service/sts\"\n\tststypes \"github.com/aws/aws-sdk-go-v2/service/sts/types\"\n)\n\n// SessionTokenProvider retrieves temporary credentials from STS using GetSessionToken\ntype SessionTokenProvider struct {\n\tStsClient *sts.Client\n\tDuration  time.Duration\n\tMfa\n}\n\n// Retrieve generates a new set of temporary credentials using STS GetSessionToken\nfunc (p *SessionTokenProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {\n\tcreds, err := p.RetrieveStsCredentials(ctx)\n\tif err != nil {\n\t\treturn aws.Credentials{}, err\n\t}\n\n\treturn aws.Credentials{\n\t\tAccessKeyID:     aws.ToString(creds.AccessKeyId),\n\t\tSecretAccessKey: aws.ToString(creds.SecretAccessKey),\n\t\tSessionToken:    aws.ToString(creds.SessionToken),\n\t\tCanExpire:       true,\n\t\tExpires:         aws.ToTime(creds.Expiration),\n\t}, nil\n}\n\n// GetSessionToken generates a new set of temporary credentials using STS GetSessionToken\nfunc (p *SessionTokenProvider) RetrieveStsCredentials(ctx context.Context) (*ststypes.Credentials, error) {\n\tvar err error\n\n\tinput := &sts.GetSessionTokenInput{\n\t\tDurationSeconds: aws.Int32(int32(p.Duration.Seconds())),\n\t}\n\n\tif p.MfaSerial != \"\" {\n\t\tinput.SerialNumber = aws.String(p.MfaSerial)\n\t\tinput.TokenCode, err = p.GetMfaToken()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tresp, err := p.StsClient.GetSessionToken(ctx, input)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlog.Printf(\"Generated credentials %s using GetSessionToken, expires in %s\", FormatKeyForDisplay(*resp.Credentials.AccessKeyId), time.Until(*resp.Credentials.Expiration).String())\n\n\treturn resp.Credentials, nil\n}\n"
  },
  {
    "path": "vault/ssorolecredentialsprovider.go",
    "content": "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\"github.com/aws/aws-sdk-go-v2/aws\"\n\tawshttp \"github.com/aws/aws-sdk-go-v2/aws/transport/http\"\n\t\"github.com/aws/aws-sdk-go-v2/service/sso\"\n\tssotypes \"github.com/aws/aws-sdk-go-v2/service/sso/types\"\n\t\"github.com/aws/aws-sdk-go-v2/service/ssooidc\"\n\tssooidctypes \"github.com/aws/aws-sdk-go-v2/service/ssooidc/types\"\n\tststypes \"github.com/aws/aws-sdk-go-v2/service/sts/types\"\n\t\"github.com/skratchdot/open-golang/open\"\n)\n\ntype OIDCTokenCacher interface {\n\tGet(string) (*ssooidc.CreateTokenOutput, error)\n\tSet(string, *ssooidc.CreateTokenOutput) error\n\tRemove(string) error\n}\n\n// SSORoleCredentialsProvider creates temporary credentials for an SSO Role.\ntype SSORoleCredentialsProvider struct {\n\tOIDCClient     *ssooidc.Client\n\tOIDCTokenCache OIDCTokenCacher\n\tStartURL       string\n\tSSOClient      *sso.Client\n\tAccountID      string\n\tRoleName       string\n\tUseStdout      bool\n}\n\nfunc millisecondsTimeValue(v int64) time.Time {\n\treturn time.Unix(0, v*int64(time.Millisecond))\n}\n\n// Retrieve generates a new set of temporary credentials using SSO GetRoleCredentials.\nfunc (p *SSORoleCredentialsProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {\n\tcreds, err := p.getRoleCredentials(ctx)\n\tif err != nil {\n\t\treturn aws.Credentials{}, err\n\t}\n\n\treturn aws.Credentials{\n\t\tAccessKeyID:     aws.ToString(creds.AccessKeyId),\n\t\tSecretAccessKey: aws.ToString(creds.SecretAccessKey),\n\t\tSessionToken:    aws.ToString(creds.SessionToken),\n\t\tCanExpire:       true,\n\t\tExpires:         millisecondsTimeValue(creds.Expiration),\n\t}, nil\n}\n\nfunc (p *SSORoleCredentialsProvider) getRoleCredentials(ctx context.Context) (*ssotypes.RoleCredentials, error) {\n\ttoken, cached, err := p.getOIDCToken(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresp, err := p.SSOClient.GetRoleCredentials(ctx, &sso.GetRoleCredentialsInput{\n\t\tAccessToken: token.AccessToken,\n\t\tAccountId:   aws.String(p.AccountID),\n\t\tRoleName:    aws.String(p.RoleName),\n\t})\n\tif err != nil {\n\t\tif cached && p.OIDCTokenCache != nil {\n\t\t\tvar rspError *awshttp.ResponseError\n\t\t\tif !errors.As(err, &rspError) {\n\t\t\t\treturn nil, err\n\t\t\t}\n\n\t\t\t// If the error is a 401, remove the cached oidc token and try\n\t\t\t// again. This is a recursive call but it should only happen once\n\t\t\t// due to the cache being cleared before retrying.\n\t\t\tif rspError.HTTPStatusCode() == http.StatusUnauthorized {\n\t\t\t\terr = p.OIDCTokenCache.Remove(p.StartURL)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\treturn p.getRoleCredentials(ctx)\n\t\t\t}\n\t\t}\n\t\treturn nil, err\n\t}\n\tlog.Printf(\"Got credentials %s for SSO role %s (account: %s), expires in %s\", FormatKeyForDisplay(*resp.RoleCredentials.AccessKeyId), p.RoleName, p.AccountID, time.Until(millisecondsTimeValue(resp.RoleCredentials.Expiration)).String())\n\n\treturn resp.RoleCredentials, nil\n}\n\nfunc (p *SSORoleCredentialsProvider) RetrieveStsCredentials(ctx context.Context) (*ststypes.Credentials, error) {\n\treturn p.getRoleCredentialsAsStsCredemtials(ctx)\n}\n\n// getRoleCredentialsAsStsCredemtials returns getRoleCredentials as sts.Credentials because sessions.Store expects it\nfunc (p *SSORoleCredentialsProvider) getRoleCredentialsAsStsCredemtials(ctx context.Context) (*ststypes.Credentials, error) {\n\tcreds, err := p.getRoleCredentials(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &ststypes.Credentials{\n\t\tAccessKeyId:     creds.AccessKeyId,\n\t\tSecretAccessKey: creds.SecretAccessKey,\n\t\tSessionToken:    creds.SessionToken,\n\t\tExpiration:      aws.Time(millisecondsTimeValue(creds.Expiration)),\n\t}, nil\n}\n\nfunc (p *SSORoleCredentialsProvider) getOIDCToken(ctx context.Context) (token *ssooidc.CreateTokenOutput, cached bool, err error) {\n\tif p.OIDCTokenCache != nil {\n\t\ttoken, err = p.OIDCTokenCache.Get(p.StartURL)\n\t\tif err != nil && err != keyring.ErrKeyNotFound {\n\t\t\treturn nil, false, err\n\t\t}\n\t\tif token != nil {\n\t\t\treturn token, true, nil\n\t\t}\n\t}\n\ttoken, err = p.newOIDCToken(ctx)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\n\tif p.OIDCTokenCache != nil {\n\t\terr = p.OIDCTokenCache.Set(p.StartURL, token)\n\t\tif err != nil {\n\t\t\treturn nil, false, err\n\t\t}\n\t}\n\treturn token, false, err\n}\n\nfunc (p *SSORoleCredentialsProvider) newOIDCToken(ctx context.Context) (*ssooidc.CreateTokenOutput, error) {\n\tclientCreds, err := p.OIDCClient.RegisterClient(ctx, &ssooidc.RegisterClientInput{\n\t\tClientName: aws.String(\"aws-vault\"),\n\t\tClientType: aws.String(\"public\"),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlog.Printf(\"Created new OIDC client (expires at: %s)\", time.Unix(clientCreds.ClientSecretExpiresAt, 0))\n\n\tdeviceCreds, err := p.OIDCClient.StartDeviceAuthorization(ctx, &ssooidc.StartDeviceAuthorizationInput{\n\t\tClientId:     clientCreds.ClientId,\n\t\tClientSecret: clientCreds.ClientSecret,\n\t\tStartUrl:     aws.String(p.StartURL),\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlog.Printf(\"Created OIDC device code for %s (expires in: %ds)\", p.StartURL, deviceCreds.ExpiresIn)\n\n\tif p.UseStdout {\n\t\tfmt.Fprintf(os.Stderr, \"Open the SSO authorization page in a browser (use Ctrl-C to abort)\\n%s\\n\", aws.ToString(deviceCreds.VerificationUriComplete))\n\t} else {\n\t\tlog.Println(\"Opening SSO authorization page in browser\")\n\t\tfmt.Fprintf(os.Stderr, \"Opening the SSO authorization page in your default browser (use Ctrl-C to abort)\\n%s\\n\", aws.ToString(deviceCreds.VerificationUriComplete))\n\t\tif err := open.Run(aws.ToString(deviceCreds.VerificationUriComplete)); err != nil {\n\t\t\tlog.Printf(\"Failed to open browser: %s\", err)\n\t\t}\n\t}\n\n\t// These are the default values defined in the following RFC:\n\t// https://tools.ietf.org/html/draft-ietf-oauth-device-flow-15#section-3.5\n\tvar slowDownDelay = 5 * time.Second\n\tvar retryInterval = 5 * time.Second\n\n\tif i := deviceCreds.Interval; i > 0 {\n\t\tretryInterval = time.Duration(i) * time.Second\n\t}\n\n\tfor {\n\t\tt, err := p.OIDCClient.CreateToken(ctx, &ssooidc.CreateTokenInput{\n\t\t\tClientId:     clientCreds.ClientId,\n\t\t\tClientSecret: clientCreds.ClientSecret,\n\t\t\tDeviceCode:   deviceCreds.DeviceCode,\n\t\t\tGrantType:    aws.String(\"urn:ietf:params:oauth:grant-type:device_code\"),\n\t\t})\n\t\tif err != nil {\n\t\t\tvar sde *ssooidctypes.SlowDownException\n\t\t\tif errors.As(err, &sde) {\n\t\t\t\tretryInterval += slowDownDelay\n\t\t\t}\n\n\t\t\tvar ape *ssooidctypes.AuthorizationPendingException\n\t\t\tif errors.As(err, &ape) {\n\t\t\t\ttime.Sleep(retryInterval)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\treturn nil, err\n\t\t}\n\n\t\tlog.Printf(\"Created new OIDC access token for %s (expires in: %ds)\", p.StartURL, t.ExpiresIn)\n\t\treturn t, nil\n\t}\n}\n"
  },
  {
    "path": "vault/stsendpointresolver.go",
    "content": "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// getEndpointResolver resolves endpoints in accordance with\n// https://docs.aws.amazon.com/credref/latest/refdocs/setting-global-sts_regional_endpoints.html\nfunc getSTSEndpointResolver(stsRegionalEndpoints string) aws.EndpointResolverWithOptionsFunc {\n\treturn func(service, region string, options ...interface{}) (aws.Endpoint, error) {\n\t\tif stsRegionalEndpoints == \"legacy\" && service == sts.ServiceID {\n\t\t\tif region == \"ap-northeast-1\" ||\n\t\t\t\tregion == \"ap-south-1\" ||\n\t\t\t\tregion == \"ap-southeast-1\" ||\n\t\t\t\tregion == \"ap-southeast-2\" ||\n\t\t\t\tregion == \"aws-global\" ||\n\t\t\t\tregion == \"ca-central-1\" ||\n\t\t\t\tregion == \"eu-central-1\" ||\n\t\t\t\tregion == \"eu-north-1\" ||\n\t\t\t\tregion == \"eu-west-1\" ||\n\t\t\t\tregion == \"eu-west-2\" ||\n\t\t\t\tregion == \"eu-west-3\" ||\n\t\t\t\tregion == \"sa-east-1\" ||\n\t\t\t\tregion == \"us-east-1\" ||\n\t\t\t\tregion == \"us-east-2\" ||\n\t\t\t\tregion == \"us-west-1\" ||\n\t\t\t\tregion == \"us-west-2\" {\n\t\t\t\tlog.Println(\"Using legacy STS endpoint sts.amazonaws.com\")\n\n\t\t\t\treturn aws.Endpoint{\n\t\t\t\t\tURL:           \"https://sts.amazonaws.com\",\n\t\t\t\t\tSigningRegion: region,\n\t\t\t\t}, nil\n\t\t\t}\n\t\t}\n\n\t\treturn aws.Endpoint{}, &aws.EndpointNotFoundError{}\n\t}\n}\n"
  },
  {
    "path": "vault/vault.go",
    "content": "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-go-v2/aws\"\n\t\"github.com/aws/aws-sdk-go-v2/service/sso\"\n\t\"github.com/aws/aws-sdk-go-v2/service/ssooidc\"\n\t\"github.com/aws/aws-sdk-go-v2/service/sts\"\n)\n\nvar defaultExpirationWindow = 5 * time.Minute\n\nfunc init() {\n\tif d, err := time.ParseDuration(os.Getenv(\"AWS_MIN_TTL\")); err == nil {\n\t\tdefaultExpirationWindow = d\n\t}\n}\n\nfunc NewAwsConfig(region, stsRegionalEndpoints string) aws.Config {\n\treturn aws.Config{\n\t\tRegion:                      region,\n\t\tEndpointResolverWithOptions: getSTSEndpointResolver(stsRegionalEndpoints),\n\t}\n}\n\nfunc NewAwsConfigWithCredsProvider(credsProvider aws.CredentialsProvider, region, stsRegionalEndpoints string) aws.Config {\n\treturn aws.Config{\n\t\tRegion:                      region,\n\t\tCredentials:                 credsProvider,\n\t\tEndpointResolverWithOptions: getSTSEndpointResolver(stsRegionalEndpoints),\n\t}\n}\n\nfunc FormatKeyForDisplay(k string) string {\n\treturn fmt.Sprintf(\"****************%s\", k[len(k)-4:])\n}\n\nfunc isMasterCredentialsProvider(credsProvider aws.CredentialsProvider) bool {\n\t_, ok := credsProvider.(*KeyringProvider)\n\treturn ok\n}\n\n// NewMasterCredentialsProvider creates a provider for the master credentials\nfunc NewMasterCredentialsProvider(k *CredentialKeyring, credentialsName string) *KeyringProvider {\n\treturn &KeyringProvider{k, credentialsName}\n}\n\nfunc NewSessionTokenProvider(credsProvider aws.CredentialsProvider, k keyring.Keyring, config *ProfileConfig, useSessionCache bool) (aws.CredentialsProvider, error) {\n\tcfg := NewAwsConfigWithCredsProvider(credsProvider, config.Region, config.STSRegionalEndpoints)\n\n\tsessionTokenProvider := &SessionTokenProvider{\n\t\tStsClient: sts.NewFromConfig(cfg),\n\t\tDuration:  config.GetSessionTokenDuration(),\n\t\tMfa:       NewMfa(config),\n\t}\n\n\tif useSessionCache {\n\t\treturn &CachedSessionProvider{\n\t\t\tSessionKey: SessionMetadata{\n\t\t\t\tType:        \"sts.GetSessionToken\",\n\t\t\t\tProfileName: config.ProfileName,\n\t\t\t\tMfaSerial:   config.MfaSerial,\n\t\t\t},\n\t\t\tKeyring:         &SessionKeyring{Keyring: k},\n\t\t\tExpiryWindow:    defaultExpirationWindow,\n\t\t\tSessionProvider: sessionTokenProvider,\n\t\t}, nil\n\t}\n\n\treturn sessionTokenProvider, nil\n}\n\n// NewAssumeRoleProvider returns a provider that generates credentials using AssumeRole\nfunc NewAssumeRoleProvider(credsProvider aws.CredentialsProvider, k keyring.Keyring, config *ProfileConfig, useSessionCache bool) (aws.CredentialsProvider, error) {\n\tcfg := NewAwsConfigWithCredsProvider(credsProvider, config.Region, config.STSRegionalEndpoints)\n\n\tp := &AssumeRoleProvider{\n\t\tStsClient:         sts.NewFromConfig(cfg),\n\t\tRoleARN:           config.RoleARN,\n\t\tRoleSessionName:   config.RoleSessionName,\n\t\tExternalID:        config.ExternalID,\n\t\tDuration:          config.AssumeRoleDuration,\n\t\tTags:              config.SessionTags,\n\t\tTransitiveTagKeys: config.TransitiveSessionTags,\n\t\tSourceIdentity:    config.SourceIdentity,\n\t\tMfa:               NewMfa(config),\n\t}\n\n\tif useSessionCache && config.MfaSerial != \"\" {\n\t\treturn &CachedSessionProvider{\n\t\t\tSessionKey: SessionMetadata{\n\t\t\t\tType:        \"sts.AssumeRole\",\n\t\t\t\tProfileName: config.ProfileName,\n\t\t\t\tMfaSerial:   config.MfaSerial,\n\t\t\t},\n\t\t\tKeyring:         &SessionKeyring{Keyring: k},\n\t\t\tExpiryWindow:    defaultExpirationWindow,\n\t\t\tSessionProvider: p,\n\t\t}, nil\n\t}\n\n\treturn p, nil\n}\n\n// NewAssumeRoleWithWebIdentityProvider returns a provider that generates\n// credentials using AssumeRoleWithWebIdentity\nfunc NewAssumeRoleWithWebIdentityProvider(k keyring.Keyring, config *ProfileConfig, useSessionCache bool) (aws.CredentialsProvider, error) {\n\tcfg := NewAwsConfig(config.Region, config.STSRegionalEndpoints)\n\n\tp := &AssumeRoleWithWebIdentityProvider{\n\t\tStsClient:               sts.NewFromConfig(cfg),\n\t\tRoleARN:                 config.RoleARN,\n\t\tRoleSessionName:         config.RoleSessionName,\n\t\tWebIdentityTokenFile:    config.WebIdentityTokenFile,\n\t\tWebIdentityTokenProcess: config.WebIdentityTokenProcess,\n\t\tDuration:                config.AssumeRoleDuration,\n\t}\n\n\tif useSessionCache {\n\t\treturn &CachedSessionProvider{\n\t\t\tSessionKey: SessionMetadata{\n\t\t\t\tType:        \"sts.AssumeRoleWithWebIdentity\",\n\t\t\t\tProfileName: config.ProfileName,\n\t\t\t},\n\t\t\tKeyring:         &SessionKeyring{Keyring: k},\n\t\t\tExpiryWindow:    defaultExpirationWindow,\n\t\t\tSessionProvider: p,\n\t\t}, nil\n\t}\n\n\treturn p, nil\n}\n\n// NewSSORoleCredentialsProvider creates a provider for SSO credentials\nfunc NewSSORoleCredentialsProvider(k keyring.Keyring, config *ProfileConfig, useSessionCache bool) (aws.CredentialsProvider, error) {\n\tcfg := NewAwsConfig(config.SSORegion, config.STSRegionalEndpoints)\n\n\tssoRoleCredentialsProvider := &SSORoleCredentialsProvider{\n\t\tOIDCClient: ssooidc.NewFromConfig(cfg),\n\t\tStartURL:   config.SSOStartURL,\n\t\tSSOClient:  sso.NewFromConfig(cfg),\n\t\tAccountID:  config.SSOAccountID,\n\t\tRoleName:   config.SSORoleName,\n\t\tUseStdout:  config.SSOUseStdout,\n\t}\n\n\tif useSessionCache {\n\t\tssoRoleCredentialsProvider.OIDCTokenCache = OIDCTokenKeyring{Keyring: k}\n\t\treturn &CachedSessionProvider{\n\t\t\tSessionKey: SessionMetadata{\n\t\t\t\tType:        \"sso.GetRoleCredentials\",\n\t\t\t\tProfileName: config.ProfileName,\n\t\t\t\tMfaSerial:   config.SSOStartURL,\n\t\t\t},\n\t\t\tKeyring:         &SessionKeyring{Keyring: k},\n\t\t\tExpiryWindow:    defaultExpirationWindow,\n\t\t\tSessionProvider: ssoRoleCredentialsProvider,\n\t\t}, nil\n\t}\n\n\treturn ssoRoleCredentialsProvider, nil\n}\n\n// NewCredentialProcessProvider creates a provider to retrieve credentials from an external\n// executable as described in https://docs.aws.amazon.com/cli/latest/topic/config-vars.html#sourcing-credentials-from-external-processes\nfunc NewCredentialProcessProvider(k keyring.Keyring, config *ProfileConfig, useSessionCache bool) (aws.CredentialsProvider, error) {\n\tcredentialProcessProvider := &CredentialProcessProvider{\n\t\tCredentialProcess: config.CredentialProcess,\n\t}\n\n\tif useSessionCache {\n\t\treturn &CachedSessionProvider{\n\t\t\tSessionKey: SessionMetadata{\n\t\t\t\tType:        \"credential_process\",\n\t\t\t\tProfileName: config.ProfileName,\n\t\t\t},\n\t\t\tKeyring:         &SessionKeyring{Keyring: k},\n\t\t\tExpiryWindow:    defaultExpirationWindow,\n\t\t\tSessionProvider: credentialProcessProvider,\n\t\t}, nil\n\t}\n\n\treturn credentialProcessProvider, nil\n}\n\nfunc NewFederationTokenProvider(ctx context.Context, credsProvider aws.CredentialsProvider, config *ProfileConfig) (*FederationTokenProvider, error) {\n\tcfg := NewAwsConfigWithCredsProvider(credsProvider, config.Region, config.STSRegionalEndpoints)\n\n\tname, err := GetUsernameFromSession(ctx, cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tlog.Printf(\"Using GetFederationToken for credentials\")\n\treturn &FederationTokenProvider{\n\t\tStsClient: sts.NewFromConfig(cfg),\n\t\tName:      name,\n\t\tDuration:  config.GetFederationTokenDuration,\n\t}, nil\n}\n\nfunc FindMasterCredentialsNameFor(profileName string, keyring *CredentialKeyring, config *ProfileConfig) (string, error) {\n\thasMasterCreds, err := keyring.Has(profileName)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif hasMasterCreds {\n\t\treturn profileName, nil\n\t}\n\n\tif profileName == config.SourceProfileName {\n\t\treturn \"\", fmt.Errorf(\"No master credentials found\")\n\t}\n\n\treturn FindMasterCredentialsNameFor(config.SourceProfileName, keyring, config)\n}\n\ntype TempCredentialsCreator struct {\n\tKeyring *CredentialKeyring\n\t// DisableSessions will disable the use of GetSessionToken\n\tDisableSessions bool\n\t// DisableCache will disable the use of the session cache\n\tDisableCache bool\n\t// DisableSessionsForProfile is a profile for which sessions should not be used\n\tDisableSessionsForProfile string\n\n\tchainedMfa string\n}\n\nfunc (t *TempCredentialsCreator) getSourceCreds(config *ProfileConfig, hasStoredCredentials bool) (sourcecredsProvider aws.CredentialsProvider, err error) {\n\tif hasStoredCredentials {\n\t\tlog.Printf(\"profile %s: using stored credentials\", config.ProfileName)\n\t\treturn NewMasterCredentialsProvider(t.Keyring, config.ProfileName), nil\n\t}\n\n\tif config.HasSourceProfile() {\n\t\tlog.Printf(\"profile %s: sourcing credentials from profile %s\", config.ProfileName, config.SourceProfile.ProfileName)\n\t\treturn t.GetProviderForProfile(config.SourceProfile)\n\t}\n\n\treturn nil, fmt.Errorf(\"profile %s: credentials missing\", config.ProfileName)\n}\n\nfunc (t *TempCredentialsCreator) getSourceCredWithSession(config *ProfileConfig, hasStoredCredentials bool) (sourcecredsProvider aws.CredentialsProvider, err error) {\n\tsourcecredsProvider, err = t.getSourceCreds(config, hasStoredCredentials)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif config.HasRole() {\n\t\tisMfaChained := config.MfaSerial != \"\" && config.MfaSerial == t.chainedMfa\n\t\tif isMfaChained {\n\t\t\tconfig.MfaSerial = \"\"\n\t\t}\n\t\tlog.Printf(\"profile %s: using AssumeRole %s\", config.ProfileName, mfaDetails(isMfaChained, config))\n\t\treturn NewAssumeRoleProvider(sourcecredsProvider, t.Keyring.Keyring, config, !t.DisableCache)\n\t}\n\n\tif isMasterCredentialsProvider(sourcecredsProvider) {\n\t\tcanUseGetSessionToken, reason := t.canUseGetSessionToken(config)\n\t\tif canUseGetSessionToken {\n\t\t\tt.chainedMfa = config.MfaSerial\n\t\t\tlog.Printf(\"profile %s: using GetSessionToken %s\", config.ProfileName, mfaDetails(false, config))\n\t\t\treturn NewSessionTokenProvider(sourcecredsProvider, t.Keyring.Keyring, config, !t.DisableCache)\n\t\t}\n\t\tlog.Printf(\"profile %s: skipping GetSessionToken because %s\", config.ProfileName, reason)\n\t}\n\n\treturn sourcecredsProvider, nil\n}\n\nfunc (t *TempCredentialsCreator) GetProviderForProfile(config *ProfileConfig) (aws.CredentialsProvider, error) {\n\thasStoredCredentials, err := t.Keyring.Has(config.ProfileName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif hasStoredCredentials || config.HasSourceProfile() {\n\t\treturn t.getSourceCredWithSession(config, hasStoredCredentials)\n\t}\n\n\tif config.HasSSOStartURL() {\n\t\tlog.Printf(\"profile %s: using SSO role credentials\", config.ProfileName)\n\t\treturn NewSSORoleCredentialsProvider(t.Keyring.Keyring, config, !t.DisableCache)\n\t}\n\n\tif config.HasWebIdentity() {\n\t\tlog.Printf(\"profile %s: using web identity\", config.ProfileName)\n\t\treturn NewAssumeRoleWithWebIdentityProvider(t.Keyring.Keyring, config, !t.DisableCache)\n\t}\n\n\tif config.HasCredentialProcess() {\n\t\tlog.Printf(\"profile %s: using credential process\", config.ProfileName)\n\t\treturn NewCredentialProcessProvider(t.Keyring.Keyring, config, !t.DisableCache)\n\t}\n\n\treturn nil, fmt.Errorf(\"profile %s: credentials missing\", config.ProfileName)\n}\n\n// canUseGetSessionToken determines if GetSessionToken should be used, and if not returns a reason\nfunc (t *TempCredentialsCreator) canUseGetSessionToken(c *ProfileConfig) (bool, string) {\n\tif t.DisableSessions {\n\t\treturn false, \"sessions are disabled\"\n\t}\n\tif t.DisableSessionsForProfile == c.ProfileName {\n\t\treturn false, \"sessions are disabled for this profile\"\n\t}\n\n\tif c.IsChained() {\n\t\tif !c.ChainedFromProfile.HasMfaSerial() {\n\t\t\treturn false, fmt.Sprintf(\"profile '%s' has no MFA serial defined\", c.ChainedFromProfile.ProfileName)\n\t\t}\n\n\t\tif !c.HasMfaSerial() && c.ChainedFromProfile.HasMfaSerial() {\n\t\t\treturn false, fmt.Sprintf(\"profile '%s' has no MFA serial defined\", c.ProfileName)\n\t\t}\n\n\t\tif c.ChainedFromProfile.MfaSerial != c.MfaSerial {\n\t\t\treturn false, fmt.Sprintf(\"MFA serial doesn't match profile '%s'\", c.ChainedFromProfile.ProfileName)\n\t\t}\n\n\t\tif c.ChainedFromProfile.AssumeRoleDuration > roleChainingMaximumDuration {\n\t\t\treturn false, fmt.Sprintf(\"duration %s in profile '%s' is greater than the AWS maximum %s for chaining MFA\", c.ChainedFromProfile.AssumeRoleDuration, c.ChainedFromProfile.ProfileName, roleChainingMaximumDuration)\n\t\t}\n\t}\n\n\treturn true, \"\"\n}\n\nfunc mfaDetails(mfaChained bool, config *ProfileConfig) string {\n\tif mfaChained {\n\t\treturn \"(chained MFA)\"\n\t}\n\tif config.HasMfaSerial() {\n\t\treturn \"(with MFA)\"\n\t}\n\treturn \"\"\n}\n\n// NewTempCredentialsProvider creates a credential provider for the given config\nfunc NewTempCredentialsProvider(config *ProfileConfig, keyring *CredentialKeyring, disableSessions bool, disableCache bool) (aws.CredentialsProvider, error) {\n\tt := TempCredentialsCreator{\n\t\tKeyring:         keyring,\n\t\tDisableSessions: disableSessions,\n\t\tDisableCache:    disableCache,\n\t}\n\treturn t.GetProviderForProfile(config)\n}\n"
  },
  {
    "path": "vault/vault_test.go",
    "content": "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\"\n)\n\nfunc TestUsageWebIdentityExample(t *testing.T) {\n\tf := newConfigFile(t, []byte(`\n[profile role2]\nrole_arn = arn:aws:iam::33333333333:role/role2\nweb_identity_token_process = oidccli raw\n`))\n\tdefer os.Remove(f)\n\tconfigFile, err := vault.LoadConfig(f)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconfigLoader := &vault.ConfigLoader{File: configFile, ActiveProfile: \"role2\"}\n\tconfig, err := configLoader.GetProfileConfig(\"role2\")\n\tif err != nil {\n\t\tt.Fatalf(\"Should have found a profile: %v\", err)\n\t}\n\n\tckr := &vault.CredentialKeyring{Keyring: keyring.NewArrayKeyring([]keyring.Item{})}\n\tp, err := vault.NewTempCredentialsProvider(config, ckr, true, true)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t_, ok := p.(*vault.AssumeRoleWithWebIdentityProvider)\n\tif !ok {\n\t\tt.Fatalf(\"Expected AssumeRoleWithWebIdentityProvider, got %T\", p)\n\t}\n}\n\nfunc TestIssue1176(t *testing.T) {\n\tf := newConfigFile(t, []byte(`\n[profile my-shared-base-profile]\ncredential_process=aws-vault exec my-shared-base-profile -j\nmfa_serial=arn:aws:iam::1234567890:mfa/danielholz\nregion=eu-west-1\n\n[profile profile-with-role]\nsource_profile=my-shared-base-profile\ninclude_profile=my-shared-base-profile\nregion=eu-west-1\nrole_arn=arn:aws:iam::12345678901:role/allow-view-only-access-from-other-accounts\n`))\n\tdefer os.Remove(f)\n\tconfigFile, err := vault.LoadConfig(f)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconfigLoader := &vault.ConfigLoader{File: configFile, ActiveProfile: \"my-shared-base-profile\"}\n\tconfig, err := configLoader.GetProfileConfig(\"my-shared-base-profile\")\n\tif err != nil {\n\t\tt.Fatalf(\"Should have found a profile: %v\", err)\n\t}\n\n\tckr := &vault.CredentialKeyring{Keyring: keyring.NewArrayKeyring([]keyring.Item{})}\n\tp, err := vault.NewTempCredentialsProvider(config, ckr, true, true)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t_, ok := p.(*vault.CredentialProcessProvider)\n\tif !ok {\n\t\tt.Fatalf(\"Expected CredentialProcessProvider, got %T\", p)\n\t}\n}\n\nfunc TestIssue1195(t *testing.T) {\n\tf := newConfigFile(t, []byte(`\n[profile test]\nsource_profile=dev\nregion=ap-northeast-2\n\n[profile dev]\nsso_session=common\nsso_account_id=2160xxxx\nsso_role_name=AdministratorAccess\nregion=ap-northeast-2\noutput=json\n\n[default]\nsso_session=common\nsso_account_id=3701xxxx\nsso_role_name=AdministratorAccess\nregion=ap-northeast-2\noutput=json\n\n[sso-session common]\nsso_start_url=https://xxxx.awsapps.com/start\nsso_region=ap-northeast-2\nsso_registration_scopes=sso:account:access\n`))\n\tdefer os.Remove(f)\n\tconfigFile, err := vault.LoadConfig(f)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconfigLoader := &vault.ConfigLoader{File: configFile, ActiveProfile: \"test\"}\n\tconfig, err := configLoader.GetProfileConfig(\"test\")\n\tif err != nil {\n\t\tt.Fatalf(\"Should have found a profile: %v\", err)\n\t}\n\n\tckr := &vault.CredentialKeyring{Keyring: keyring.NewArrayKeyring([]keyring.Item{})}\n\tp, err := vault.NewTempCredentialsProvider(config, ckr, true, true)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tssoProvider, ok := p.(*vault.SSORoleCredentialsProvider)\n\tif !ok {\n\t\tt.Fatalf(\"Expected SSORoleCredentialsProvider, got %T\", p)\n\t}\n\tif ssoProvider.AccountID != \"2160xxxx\" {\n\t\tt.Fatalf(\"Expected AccountID to be 2160xxxx, got %s\", ssoProvider.AccountID)\n\t}\n}\n"
  }
]