├── LICENSE ├── Makefile ├── README.md ├── aps ├── go.mod ├── go.sum ├── img ├── screenshot1.png └── screenshot2.png └── main.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Thomas Labarussias 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGET_PATH = bin 2 | GOARCH = GOARCH=amd64 3 | VERSION = 1.0.0 4 | 5 | buildWindows: 6 | env GOOS=windows $(GOARCH) go build -o $(TARGET_PATH)/aps-Windows-$(VERSION).exe 7 | 8 | buildMacOS: 9 | env GOOS=darwin $(GOARCH) go build -o $(TARGET_PATH)/aps-MacOS-$(VERSION) 10 | 11 | buildLinux: 12 | env GOOS=linux $(GOARCH) go build -o $(TARGET_PATH)/aps-Linux-$(VERSION) 13 | 14 | build: buildWindows buildMacOS buildLinux 15 | 16 | clean: 17 | rm -rf bin 18 | 19 | all: clean build -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # APS - Amazon Profile Switcher 2 | 3 | ## Description 4 | 5 | Easy switch between AWS Profiles and Regions. 6 | 7 | ## Why 8 | 9 | As a service provider we have to switch all the time between our customers' accounts and as lazy DevOps we do no want to always pass same args to our commands. Environment variables are so a good solution for helping us. Here comes **APS** aka **Amazon Profile Switcher**. 10 | 11 | ## Usage 12 | 13 | ```bash 14 | usage: aps [] 15 | 16 | Flags: 17 | --help Show context-sensitive help (also try --help-long and --help-man). 18 | -x, --clear Clear env vars related to AWS 19 | -c, --config=$HOME/.aws/config AWS config file 20 | -p, --profile=PROFILE Specify directly the AWS Profile to use 21 | -r, --region=REGION Region selector 22 | -a, --assume=ASSUME If false, auto assume role is disabled (default is true) 23 | ``` 24 | 25 | You can select your profile/region by ←, ↑, → ↓ and filter by **Name**, or **AccountId** (only for profile). **Enter** key to validate. 26 | 27 | ## Output Example 28 | 29 | ![screenshot1](./img/screenshot1.png) 30 | ![screenshot2](./img/screenshot2.png) 31 | 32 | ## Build 33 | 34 | ```bash 35 | make all 36 | ``` 37 | This repository uses `go mod`, so don't `git clone` inside your `$GOPATH`. 38 | 39 | ## Author 40 | 41 | Thomas Labarussias (thomas.labarussias@qonto.com - https://github.com/Issif) -------------------------------------------------------------------------------- /aps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claranet/aps/5ad05c0e5ea5d41a07a42311ab08d9f20ff81dcd/aps -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com:claranet/aps 2 | 3 | require ( 4 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect 5 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect 6 | github.com/aws/aws-sdk-go v1.29.19 7 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e 8 | github.com/go-ini/ini v1.42.0 9 | github.com/manifoldco/promptui v0.3.3-0.20190411181407-35bab80e16a4 10 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 11 | ) 12 | 13 | go 1.13 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/alecthomas/gometalinter v2.0.11+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk= 3 | github.com/alecthomas/gometalinter v3.0.0+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk= 4 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= 5 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 6 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= 7 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 8 | github.com/aws/aws-sdk-go v1.29.19 h1:+jifYixffn6kzWygtGWFWQMv0tDGyISZHNwugF9V2sE= 9 | github.com/aws/aws-sdk-go v1.29.19/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= 10 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 11 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= 12 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 13 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 14 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 15 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 | github.com/go-ini/ini v1.42.0 h1:TWr1wGj35+UiWHlBA8er89seFXxzwFn11spilrrj+38= 18 | github.com/go-ini/ini v1.42.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 19 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 20 | github.com/golang/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= 21 | github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= 22 | github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= 23 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= 24 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 25 | github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= 26 | github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= 27 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 28 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 29 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 30 | github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= 31 | github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= 32 | github.com/manifoldco/promptui v0.3.2 h1:rir7oByTERac6jhpHUPErHuopoRDvO3jxS+FdadEns8= 33 | github.com/manifoldco/promptui v0.3.2/go.mod h1:8JU+igZ+eeiiRku4T5BjtKh2ms8sziGpSYl1gN8Bazw= 34 | github.com/manifoldco/promptui v0.3.3-0.20190411181407-35bab80e16a4 h1:zNtxporN4V2l47+9kRel4xPgHv+0LsRgfU3aNgKwaUs= 35 | github.com/manifoldco/promptui v0.3.3-0.20190411181407-35bab80e16a4/go.mod h1:Qr+HrTC9bwokrkg5IsBOUZYbIFuLE8wzazmgj+/aLxw= 36 | github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= 37 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 38 | github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= 39 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 40 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 41 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 42 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 43 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 44 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 45 | github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9/go.mod h1:q+QjxYvZ+fpjMXqs+XEriussHjSYqeXVnAdSV1tkMYk= 46 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 47 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 48 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 49 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 50 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 51 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 52 | golang.org/x/tools v0.0.0-20181122213734-04b5d21e00f1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 53 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= 54 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 55 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 56 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 57 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 58 | -------------------------------------------------------------------------------- /img/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claranet/aps/5ad05c0e5ea5d41a07a42311ab08d9f20ff81dcd/img/screenshot1.png -------------------------------------------------------------------------------- /img/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claranet/aps/5ad05c0e5ea5d41a07a42311ab08d9f20ff81dcd/img/screenshot2.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "regexp" 8 | "sort" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/aws/aws-sdk-go/aws" 13 | "github.com/aws/aws-sdk-go/aws/credentials/stscreds" 14 | "github.com/aws/aws-sdk-go/aws/session" 15 | "github.com/aws/aws-sdk-go/service/sts" 16 | "github.com/chzyer/readline" 17 | "github.com/go-ini/ini" 18 | "github.com/manifoldco/promptui" 19 | kingpin "gopkg.in/alecthomas/kingpin.v2" 20 | ) 21 | 22 | type profile struct { 23 | Name string 24 | Region string 25 | AccountID string 26 | Role string 27 | RoleARN string 28 | SourceProfile string 29 | } 30 | 31 | var ( 32 | clear = kingpin.Flag("clear", "Clear env vars related to AWS").Short('x').Bool() 33 | configFile = kingpin.Flag("config", "AWS config file").Short('c').Default(os.Getenv("HOME") + "/.aws/config").ExistingFile() 34 | chooseProfile = kingpin.Flag("profile", "Specify directly the AWS Profile to use").Short('p').String() 35 | chooseRegion = kingpin.Flag("region", "Region selector").Short('r').String() 36 | assume = kingpin.Flag("assume", "If false, auto assume role is disabled (default is true)").Short('a').String() 37 | ) 38 | 39 | // Hack from https://github.com/manifoldco/promptui/issues/49#issuecomment-428801411 to avoid annoying bell in some OS 40 | type stderr struct{} 41 | 42 | func (s *stderr) Write(b []byte) (int, error) { 43 | if len(b) == 1 && b[0] == 7 { 44 | return 0, nil 45 | } 46 | return os.Stderr.Write(b) 47 | } 48 | 49 | func (s *stderr) Close() error { 50 | return os.Stderr.Close() 51 | } 52 | 53 | func main() { 54 | readline.Stdout = &stderr{} 55 | 56 | for i, j := range os.Args { 57 | if j == "-r" || j == "--region" { 58 | if len(os.Args) == 2 { 59 | os.Args[i] = "--region=000" 60 | } 61 | } 62 | } 63 | 64 | kingpin.Parse() 65 | 66 | switch { 67 | case *clear == true: 68 | startNewShell(profile{}) 69 | case *chooseRegion != "": 70 | startNewShell(profile{Region: selectRegion(chooseRegion)}) 71 | default: 72 | startNewShell(selectProfile(listProfiles(configFile))) 73 | } 74 | } 75 | 76 | func listProfiles(configFile *string) []profile { 77 | cfg, err := ini.Load(*configFile) 78 | if err != nil { 79 | log.Fatal(err) 80 | } 81 | 82 | reg := regexp.MustCompile("^profile ") 83 | 84 | var profiles []profile 85 | 86 | Names := cfg.SectionStrings() 87 | 88 | for _, n := range Names { 89 | var p profile 90 | p.Name = reg.ReplaceAllString(n, "") 91 | if cfg.Section(n).HasKey("role_arn") { 92 | p.RoleARN = cfg.Section(n).Key("role_arn").String() 93 | r := strings.Split(p.RoleARN, "/") 94 | a := strings.Split(p.RoleARN, ":") 95 | p.Role = r[len(r)-1] 96 | p.AccountID = a[4] 97 | } 98 | if cfg.Section(n).HasKey("source_profile") { 99 | p.SourceProfile = cfg.Section(n).Key("source_profile").String() 100 | } 101 | if cfg.Section(n).HasKey("region") { 102 | p.Region = cfg.Section(n).Key("region").String() 103 | } 104 | } 105 | 106 | return profiles 107 | } 108 | 109 | func selectProfile(profiles []profile) profile { 110 | if *chooseProfile != "" { 111 | for _, i := range profiles { 112 | if i.Name == *chooseProfile { 113 | if i.Region == "" { 114 | if os.Getenv("AWS_DEFAULT_REGION") != "" { 115 | i.Region = os.Getenv("AWS_DEFAULT_REGION") 116 | } else { 117 | i.Region = selectRegion(chooseRegion) 118 | } 119 | } 120 | return i 121 | } 122 | } 123 | } 124 | 125 | current := os.Getenv("AWS_PROFILE") 126 | 127 | templates := &promptui.SelectTemplates{ 128 | // Label: ` `, 129 | Active: `{{ "> " | cyan | bold }}{{ .Name | cyan | bold }}`, 130 | Inactive: ` {{ .Name }}`, 131 | Details: `{{ "AccountID: " }}{{ .AccountID | bold }} | {{ "Role: " }}{{ .Role | bold }} | {{ "Region: " }}{{ .Region | bold }} | {{ "Source: " }}{{ .SourceProfile | bold }}`, 132 | } 133 | 134 | searcher := func(input string, index int) bool { 135 | j := profiles[index] 136 | Name := strings.ToLower(j.Name + j.AccountID) 137 | input = strings.ToLower(input) 138 | 139 | return strings.Contains(Name, input) 140 | } 141 | 142 | prompt := promptui.Select{ 143 | Label: strconv.Itoa(len(profiles)) + " profiles (current: " + current + ")", 144 | Items: profiles, 145 | Templates: templates, 146 | Size: 10, 147 | Searcher: searcher, 148 | HideSelected: true, 149 | StartInSearchMode: true, 150 | } 151 | 152 | selected, _, err := prompt.Run() 153 | if err != nil { 154 | os.Exit(0) 155 | } 156 | 157 | if profiles[selected].Region == "" { 158 | if os.Getenv("AWS_DEFAULT_REGION") != "" { 159 | profiles[selected].Region = os.Getenv("AWS_DEFAULT_REGION") 160 | } else { 161 | profiles[selected].Region = selectRegion(chooseRegion) 162 | } 163 | } 164 | 165 | return profiles[selected] 166 | } 167 | 168 | var regions = []string{ 169 | "us-east-2 | Ohio", 170 | "us-east-1 | N. Virginia", 171 | "us-west-1 | N. California", 172 | "us-west-2 | Oregon", 173 | "ap-south-1 | Mumbai", 174 | "ap-northeast-3 | Osaka-Local", 175 | "ap-northeast-2 | Seoul", 176 | "ap-northeast-1 | Tokyo", 177 | "ap-southeast-1 | Singapore", 178 | "ap-southeast-2 | Sydney", 179 | "ca-central-1 | Central", 180 | "cn-north-1 | Beijing", 181 | "cn-nortwest-1 | Ningxia", 182 | "eu-central-1 | Frankfurt", 183 | "eu-west-1 | Ireland", 184 | "eu-west-2 | London", 185 | "eu-west-3 | Paris", 186 | "eu-north-1 | Stockholm", 187 | "sa-east-1 | São Paulo", 188 | } 189 | 190 | func selectRegion(r *string) string { 191 | if *r != "000" { 192 | return *r 193 | } 194 | templates := &promptui.SelectTemplates{ 195 | // Label: ` `, 196 | Active: `{{ "> " | cyan | bold }}{{ . | cyan | bold }}`, 197 | Inactive: ` {{ . }}`, 198 | } 199 | 200 | searcher := func(input string, index int) bool { 201 | j := regions[index] 202 | Name := strings.ToLower(j) 203 | input = strings.ToLower(input) 204 | 205 | return strings.Contains(Name, input) 206 | } 207 | 208 | sort.Strings(regions) 209 | 210 | prompt := promptui.Select{ 211 | Label: "Regions (current: " + os.Getenv("AWS_DEFAULT_REGION") + ")", 212 | Items: regions, 213 | Templates: templates, 214 | Size: 10, 215 | Searcher: searcher, 216 | HideSelected: true, 217 | StartInSearchMode: true, 218 | } 219 | 220 | selected, _, err := prompt.Run() 221 | if err != nil { 222 | os.Exit(0) 223 | } 224 | 225 | regionWithOutName := strings.Trim(strings.Split(regions[selected], "|")[0], " ") 226 | 227 | return regionWithOutName 228 | } 229 | 230 | func startNewShell(p profile) { 231 | // Get the current working directory. 232 | cwd, err := os.Getwd() 233 | if err != nil { 234 | panic(err) 235 | } 236 | 237 | switch { 238 | case p.Name == "" && p.Region == "": 239 | os.Unsetenv("AWS_PROFILE") 240 | os.Unsetenv("AWS_DEFAULT_REGION") 241 | case p.Name == "" && p.Region != "": 242 | os.Setenv("AWS_DEFAULT_REGION", p.Region) 243 | fmt.Println("Active Region: \033[1m" + p.Region + "\033[0m") 244 | default: 245 | os.Setenv("AWS_PROFILE", p.Name) 246 | fmt.Println("Active Profile: \033[1m" + p.Name + "\033[0m") 247 | os.Setenv("AWS_DEFAULT_REGION", p.Region) 248 | fmt.Println("Active Region: \033[1m" + p.Region + "\033[0m") 249 | if p.RoleARN != "" && *assume != "false" { 250 | setIAMStsEnv(p) 251 | } 252 | } 253 | 254 | // Transfer stdin, stdout, and stderr to the new process 255 | // and also set target directory for the shell to start in. 256 | pa := os.ProcAttr{ 257 | Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}, 258 | Dir: cwd, 259 | } 260 | 261 | // Start up a new shell. 262 | proc, err := os.StartProcess(os.Getenv("SHELL"), []string{os.Getenv("SHELL")}, &pa) 263 | if err != nil { 264 | panic(err) 265 | } 266 | 267 | // Wait until user exits the shell 268 | _, err = proc.Wait() 269 | if err != nil { 270 | panic(err) 271 | } 272 | 273 | // Avoid stacked shell sessions, when exit/ctrl+D caller shell is killed 274 | process, _ := os.FindProcess(os.Getppid()) 275 | process.Kill() 276 | } 277 | 278 | func setIAMStsEnv(p profile) { 279 | awsSession := session.Must(session.NewSessionWithOptions(session.Options{ 280 | SharedConfigState: session.SharedConfigEnable, //enable use of ~/.aws/config 281 | AssumeRoleTokenProvider: stscreds.StdinTokenProvider, //ask for MFA if needed 282 | Profile: p.Name, 283 | Config: aws.Config{Region: aws.String(p.Region)}, 284 | })) 285 | 286 | r, err := sts.New(awsSession).GetCallerIdentity(&sts.GetCallerIdentityInput{}) 287 | if err != nil { 288 | log.Fatal(err.Error()) 289 | } 290 | s := strings.Split(*r.UserId, ":") 291 | t, err := sts.New(awsSession).AssumeRole(&sts.AssumeRoleInput{RoleArn: aws.String(p.RoleARN), RoleSessionName: aws.String(s[1])}) 292 | if err != nil { 293 | log.Fatal(err.Error()) 294 | } 295 | os.Setenv("AWS_ACCESS_KEY_ID", *t.Credentials.AccessKeyId) 296 | os.Setenv("AWS_SECRET_ACCESS_KEY", *t.Credentials.SecretAccessKey) 297 | os.Setenv("AWS_SESSION_TOKEN", *t.Credentials.SessionToken) 298 | } 299 | --------------------------------------------------------------------------------