Skip to content

Commit

Permalink
Initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
Mikael Johansson committed Mar 18, 2016
0 parents commit 7840779
Show file tree
Hide file tree
Showing 10 changed files with 347 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
awsu*
target
coverage.txt
.vscode
vendor/
.aws
keys/
21 changes: 21 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
language: go
sudo: false
os:
- linux
- osx
go:
- 1.6

# Deploy executables to Github release tags
deploy:
provider: releases
api_key:
secure: JotH2XgNOyR2Pi+5NMQRS2t7VhMamxzhlxWf+pXC3hUUzeIKSTszX0DVtfgjKkLkJzoai9Y62kEUhEvOHEqdQN8hsE89mjbBVCTPpO0Jl0NNJ8HKbIlg0iY5vgMesQTO5LrSIQoYfILhGFVUqVvRPw/7CX7Y5wSV6ca4SvvruSSzyG2dEyehCR5TcPFJR/aH4DmO/I4AumPEA82uuv3naaGmFl1y45Q51jgUWWq2WvJK/i0pECWzSc6HtVUAvFvrypZKRu4L+EFC0DS8mSUa6lgEsUnp714/YnYM2zXqXy2vBZR/ZNZVN3/67BGpqaZeGmoKEH0l5cXzBdPTiuihneziI6v3E+CP6E/bhVTSiDKJw1yeA8E5fdotnjhvXVHAxftA182yh3TVPnDinn/QKgTsdtS4ofs99AOyqtaMGdIGp5WRDJsP/40bsy9uRApdrxiKqpSxWcSQndqs3x506fVcd2VnuITSIA9q4lM6d1x1pP74qtcGx175iI1TLt06wu59mnXC8Ba+uZh6ycY37Zsiqdvr1J+hJCUJrTMXDghFtrHSUhxOvVWtehQbG2VL53/NhvwCauwZLVJiiNmqZcppyWDww6Y7+zSNBPm9MIY0o/bgr3nCHSDRiQ3mhmauO12hab6svmPIhcDQ/n1lMpF4OgXbdWwlPCoGh/sZAYg=
file: "awsu-$(uname -s)-$(uname -m)"
skip_cleanup: true
on:
tags: true

# Code coverage for master branch using https://codecov.io/github/meltwater/awsu
after_success:
- if [ "$TRAVIS_TAG" == "" ] && [ "$TRAVIS_OS_NAME" == "linux" ]; then bash <(curl -s https://codecov.io/bash); fi
30 changes: 30 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export GO15VENDOREXPERIMENT=1

all: tools deps fmt build test lint

tools:
go get -u golang.org/x/tools/cmd/cover
go get -u github.com/golang/lint/golint
go get -u github.com/Masterminds/glide

deps:
glide install

# http://golang.org/cmd/go/#hdr-Run_gofmt_on_package_sources
fmt:
go fmt ./...

build:
CGO_ENABLED=0 go build -o "awsu-`uname -s`-`uname -m`"
ln -sf "awsu-`uname -s`-`uname -m`" awsu

test:
go test -bench=. -v -coverprofile=coverage.txt -covermode=atomic

lint:
golint

clean:
rm -f ./awsu

.PHONY: tools deps fmt build test lint clean
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Assume AWS IAM role
[![Travis CI](https://img.shields.io/travis/meltwater/awsu/master.svg)](https://travis-ci.org/meltwater/awsu)

Assumes an IAM role and passes the temporary credentials to another command or shell.

If you manage multiple AWS accounts and use IAM role switching to perform work in them, this would
allow you to use tools like Terraform, Docker Machine or Vagrant in the accounts. Cross account
IAM role switching is described at

* https://aws.amazon.com/blogs/aws/new-cross-account-access-in-the-aws-management-console/

## Usage

```
Assume a AWS IAM role and execute a command or shell
Usage:
awsu IAMRoleARN [command] [args]... [flags]
Flags:
--duration int Expiration time in seconds for the temporary credentials (default 900)
```

It could be useful to setup ~/.bash_aliases for roles in different accounts

```
alias ondev='awsu arn:aws:iam::123456789:role/Developer'
alias onprod='awsu arn:aws:iam::891234567:role/Developer'
```

For example

```
$ ondev terraform plan
$ ondev docker-machine create --driver amazonec2 ...
```

Inspired by

* https://github.com/mlrobinson/aws-profile
* https://github.com/jbuck/assume-aws-role
* http://blog.sinica.me/aws_multi_account_with_terraform.html
68 changes: 68 additions & 0 deletions commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package main

import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sts"
"os"
"os/exec"
"strings"
"syscall"
)

func filterExistingCredentials(list []string) []string {
result := make([]string, 0)
for _, item := range list {
if !strings.HasPrefix(item, "AWS_ACCESS_KEY_ID=") &&
!strings.HasPrefix(item, "AWS_SECRET_ACCESS_KEY=") &&
!strings.HasPrefix(item, "AWS_SESSION_TOKEN=") {
result = append(result, item)
}
}

return result
}

// Encrypts data from stdin and writes to stdout
func executeCommand(durationSeconds int64, iamRole string, args []string) {
hostname, err := os.Hostname()
sessionName := fmt.Sprintf("%s-%s-%s",
defaults(os.Getenv("USER"), "unknown"),
defaults(hostname, os.Getenv("HOST"), os.Getenv("HOSTNAME"), "unknown"),
randSeq(8))

svc := sts.New(session.New())

params := &sts.AssumeRoleInput{
RoleArn: aws.String(iamRole), // Required
RoleSessionName: aws.String(sessionName), // Required
DurationSeconds: aws.Int64(durationSeconds),
// ExternalId: aws.String("externalIdType"),
// Policy: aws.String("sessionPolicyDocumentType"),
// SerialNumber: aws.String("serialNumberType"),
// TokenCode: aws.String("tokenCodeType"),
}

resp, err := svc.AssumeRole(params)
check(err)

// Default to launch a subshell
binary := defaults(os.Getenv("SHELL"), "/bin/sh")

// Resolve absolute path of binary
if len(args) >= 1 {
binary, err = exec.LookPath(args[0])
check(err)
}

// Inject the temporary credentials
env := append(filterExistingCredentials(os.Environ()),
fmt.Sprintf("AWS_ACCESS_KEY_ID=%s", *resp.Credentials.AccessKeyId),
fmt.Sprintf("AWS_SECRET_ACCESS_KEY=%s", *resp.Credentials.SecretAccessKey),
fmt.Sprintf("AWS_SESSION_TOKEN=%s", *resp.Credentials.SessionToken))

// Execute subcommand
err = syscall.Exec(binary, args, env)
check(err)
}
20 changes: 20 additions & 0 deletions glide.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions glide.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package: github.com/meltwater/awsu
import:
- package: github.com/go-errors/errors
- package: github.com/spf13/cobra
- package: github.com/stretchr/testify
subpackages:
- assert
- package: github.com/aws/aws-sdk-go
subpackages:
- aws/session
44 changes: 44 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package main

import (
"fmt"
"math/rand"
"os"
"time"

"github.com/go-errors/errors"
"github.com/spf13/cobra"
)

func main() {
rand.Seed(time.Now().UnixNano())

var durationSeconds int64

rootCmd := &cobra.Command{
Use: "awsu IAMRoleARN [command] [args]...",
Short: "Assume a AWS IAM role and execute a command or shell",
Run: func(cmd *cobra.Command, args []string) {
assertThat(len(args) >= 1, "Expected an IAM role")
executeCommand(durationSeconds, args[0], args[1:])
},
}

rootCmd.Flags().Int64VarP(&durationSeconds, "duration", "", int64(900), "Expiration time in seconds for the temporary credentials")

// Handle checked errors nicely
defer func() {
if err := recover(); err != nil {
switch err.(type) {
case *CommandError:
fmt.Fprintf(os.Stderr, "%s\n", err)
default:
fmt.Fprintf(os.Stderr, "%s\n", errors.Wrap(err, 2).ErrorStack())
}

os.Exit(1)
}
}()

rootCmd.Execute()
}
56 changes: 56 additions & 0 deletions util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package main

import (
"fmt"
"math/rand"
)

var randAlphabet = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

// CommandError is the checked exception thrown on runtime errors
type CommandError struct {
msg string // description of error
err error // inner error
}

func (e *CommandError) Error() string { return e.msg }

// Panics with a message if the given error isn't nil
func check(err error, a ...interface{}) {
if err != nil {
var msg string
if len(a) > 0 {
msg = fmt.Sprintf("%s (%s)", fmt.Sprintf(a[0].(string), a[1:]...), err)
} else {
msg = fmt.Sprintf("%s", err)
}

panic(&CommandError{msg, err})
}
}

// Panics with a message if the given condition isn't true
func assertThat(condition bool, msg string, a ...interface{}) {
if !condition {
panic(&CommandError{fmt.Sprintf(msg, a...), nil})
}
}

func defaults(a ...string) string {
for _, item := range a {
if len(item) > 0 {
return item
}
}

return ""
}

func randSeq(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = randAlphabet[rand.Intn(len(randAlphabet))]
}

return string(b)
}
49 changes: 49 additions & 0 deletions util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package main

import (
"errors"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

func TestCheck(t *testing.T) {
// Handle checked errors nicely
defer func() {
if err := recover(); err != nil {
switch err.(type) {
case *CommandError:
assert.Equal(t, "Test Error (Inner Error)", fmt.Sprintf("%s", err))
default:
t.Errorf("Expected to catch a CommandError but got %v", err)
}
}
}()

check(errors.New("Inner Error"), "Test Error")
}

func TestAssert(t *testing.T) {
// Handle checked errors nicely
defer func() {
if err := recover(); err != nil {
switch err.(type) {
case *CommandError:
assert.Equal(t, "Test Error", fmt.Sprintf("%s", err))
default:
t.Errorf("Expected to catch a CommandError but got %v", err)
}
}
}()

assertThat(false, "Test Error")
}

func TestDefaults(t *testing.T) {
assert.Equal(t, "abc", defaults("abc", "123"))
assert.Equal(t, "123", defaults("", "123"))
assert.Equal(t, "", defaults("", ""))
assert.Equal(t, "", defaults(""))
assert.Equal(t, "", defaults())
}

0 comments on commit 7840779

Please sign in to comment.