├── LICENSE ├── README.md ├── go.mod ├── go.sum └── mfa.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 JDevelop 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Why 2 | If you have an [MFA-enabled](https://aws.amazon.com/iam/details/mfa/) account on Amazon AWS, you need to refresh the token periodically, in order to use [aws cli toolkit](https://aws.amazon.com/cli/). 3 | 4 | The sequence of actions is: 5 | 6 | * using the primary AWS account, request the [list of MFA devices](http://docs.aws.amazon.com/IAM/latest/APIReference/API_ListMFADevices.html) configured for this account 7 | * issue an STS request to [get the session token](http://docs.aws.amazon.com/STS/latest/APIReference/API_GetSessionToken.html) 8 | * update the `~/.aws/credentials` file with the received access key, secret key and session token for the given profile 9 | 10 | This simple flow is implemented as Go utility, that only updates the existing profile in the `~/.aws/credentials` with the access/secret/session tokens. 11 | 12 | There is another utility [awsmfa](https://github.com/dcoker/awsmfa/) with extended functionality for AWS key management / rotation. 13 | 14 | ### How 15 | 16 | ``` 17 | Usage of ./go-aws-mfa: 18 | -d string 19 | MFA-enabled profile 20 | -s string 21 | Source (primary) profile 22 | ``` 23 | 24 | where 25 | 26 | * `-s` specifies the IAM role that has an [MFA device configured](http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa_enable_virtual.html) 27 | * `-d` specifies the target profile to add/replace the credentials to. 28 | 29 | #### Example 30 | 31 | `./go-aws-mfa -s user1 -d user1-mfa` will ask for the token code for MFA device configured for `user1`. Then the temporary credentials will be stored for `user1-mfa`. 32 | In order to use that temporary account with `awscli`, you need to set the `AWS_PROFILE` environment variable to `user1-mfa` and then invoke `aws` command normally, for example: 33 | 34 | ``` 35 | AWS_PROFILE=user1-mfa aws s3 ls s3://bucket-user1/ 36 | ``` 37 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jdevelop/go-aws-mfa 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.19.8 7 | github.com/go-ini/ini v1.42.0 8 | github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a // indirect 9 | github.com/stretchr/testify v1.3.0 // indirect 10 | golang.org/x/net v0.0.0-20190328230028-74de082e2cca // indirect 11 | gopkg.in/ini.v1 v1.42.0 // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/aws/aws-sdk-go v1.19.8 h1:hTQRVRsg4Fwvm8SODN5ufmlqRUqFCLl/xG+BXabqVXw= 2 | github.com/aws/aws-sdk-go v1.19.8/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/go-ini/ini v1.42.0 h1:TWr1wGj35+UiWHlBA8er89seFXxzwFn11spilrrj+38= 6 | github.com/go-ini/ini v1.42.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 7 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 8 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 9 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= 10 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= 11 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 12 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 13 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 14 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 15 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 16 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 17 | github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs= 18 | github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 19 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 20 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 21 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 22 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 23 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 24 | golang.org/x/net v0.0.0-20190328230028-74de082e2cca h1:hyA6yiAgbUwuWqtscNvWAI7U1CtlaD1KilQ6iudt1aI= 25 | golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 26 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 27 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 28 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 29 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 30 | gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= 31 | gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 32 | -------------------------------------------------------------------------------- /mfa.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "github.com/aws/aws-sdk-go/aws" 8 | "github.com/aws/aws-sdk-go/aws/credentials" 9 | "github.com/aws/aws-sdk-go/aws/session" 10 | "github.com/aws/aws-sdk-go/service/iam" 11 | "github.com/aws/aws-sdk-go/service/sts" 12 | "github.com/go-ini/ini" 13 | "log" 14 | "os" 15 | "os/user" 16 | ) 17 | 18 | func fatalErr(err error) { 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | } 23 | 24 | func main() { 25 | 26 | srcF := flag.String("s", "default", "Source (primary) profile") 27 | dstF := flag.String("d", "", "MFA-enabled profile") 28 | 29 | flag.Parse() 30 | 31 | if *srcF == "" || *dstF == "" { 32 | flag.Usage() 33 | os.Exit(1) 34 | } 35 | 36 | conf := &aws.Config{ 37 | Credentials: credentials.NewSharedCredentials("", *srcF), 38 | } 39 | 40 | sess, err := session.NewSession(conf) 41 | 42 | fatalErr(err) 43 | 44 | _iam := iam.New(sess) 45 | 46 | devices, err := _iam.ListMFADevices(&iam.ListMFADevicesInput{}) 47 | 48 | fatalErr(err) 49 | 50 | if len(devices.MFADevices) == 0 { 51 | log.Fatal("No MFA devices configured") 52 | } 53 | 54 | sn := devices.MFADevices[0].SerialNumber 55 | 56 | fmt.Printf("Using device %1s\n", *sn) 57 | 58 | _sts := sts.New(sess) 59 | 60 | fmt.Printf("Enter MFA code: ") 61 | 62 | r := bufio.NewReader(os.Stdin) 63 | code, _, err := r.ReadLine() 64 | 65 | fatalErr(err) 66 | 67 | codeStr := string(code) 68 | 69 | res, err := _sts.GetSessionToken(&sts.GetSessionTokenInput{ 70 | TokenCode: &codeStr, 71 | SerialNumber: sn, 72 | }) 73 | 74 | fatalErr(err) 75 | 76 | usr, err := user.Current() 77 | 78 | fatalErr(err) 79 | 80 | filePath := usr.HomeDir + "/.aws/credentials" 81 | 82 | credFile, err := ini.Load(filePath) 83 | 84 | fatalErr(err) 85 | 86 | sect, err := credFile.NewSection(*dstF) 87 | 88 | fatalErr(err) 89 | 90 | sect.NewKey("aws_access_key_id", *res.Credentials.AccessKeyId) 91 | sect.NewKey("aws_secret_access_key", *res.Credentials.SecretAccessKey) 92 | sect.NewKey("aws_session_token", *res.Credentials.SessionToken) 93 | 94 | credFile.SaveTo(filePath) 95 | 96 | fatalErr(err) 97 | 98 | fmt.Printf("Access token updated for %1s\n", *dstF) 99 | 100 | } 101 | --------------------------------------------------------------------------------