Skip to content

Commit

Permalink
Merge pull request #556 from 99designs/ecs-server-2
Browse files Browse the repository at this point in the history
Add ECS server
  • Loading branch information
mtibben authored Apr 19, 2020
2 parents 0673f0b + 2d2303d commit ec402aa
Show file tree
Hide file tree
Showing 10 changed files with 234 additions and 78 deletions.
100 changes: 69 additions & 31 deletions cli/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ type ExecCommandInput struct {
Command string
Args []string
Keyring keyring.Keyring
StartServer bool
StartEc2Server bool
StartEcsServer bool
CredentialHelper bool
Config vault.Config
SessionDuration time.Duration
Expand Down Expand Up @@ -63,9 +64,17 @@ func ConfigureExecCommand(app *kingpin.Application) {
Short('j').
BoolVar(&input.CredentialHelper)

cmd.Flag("server", "Run the server in the background for credentials").
cmd.Flag("server", "Run a server in the background for credentials").
Short('s').
BoolVar(&input.StartServer)
BoolVar(&input.StartEc2Server)

cmd.Flag("ec2-server", "Run a EC2 metadata server in the background for credentials").
Hidden().
BoolVar(&input.StartEc2Server)

cmd.Flag("ecs-server", "Run a ECS credential server in the background for credentials").
Hidden().
BoolVar(&input.StartEcsServer)

cmd.Arg("profile", "Name of the profile").
Required().
Expand Down Expand Up @@ -118,12 +127,21 @@ func ExecCommand(input ExecCommandInput) error {
return fmt.Errorf("aws-vault sessions should be nested with care, unset $AWS_VAULT to force")
}

if input.StartServer && input.CredentialHelper {
if input.StartEc2Server && input.StartEcsServer {
return fmt.Errorf("Can't use --server with --ecs-server")
}
if input.StartEc2Server && input.CredentialHelper {
return fmt.Errorf("Can't use --server with --json")
}
if input.StartServer && input.NoSession {
if input.StartEc2Server && input.NoSession {
return fmt.Errorf("Can't use --server with --no-session")
}
if input.StartEcsServer && input.CredentialHelper {
return fmt.Errorf("Can't use --ecs-server with --json")
}
if input.StartEcsServer && input.NoSession {
return fmt.Errorf("Can't use --ecs-server with --no-session")
}

vault.UseSession = !input.NoSession

Expand All @@ -140,32 +158,63 @@ func ExecCommand(input ExecCommandInput) error {
return fmt.Errorf("Error getting temporary credentials: %w", err)
}

if input.StartServer {
return execServer(input, config, creds)
if input.StartEc2Server {
return execEc2Server(input, config, creds)
}

if input.StartEcsServer {
return execEcsServer(input, config, creds)
}

if input.CredentialHelper {
return execCredentialHelper(input, config, creds)
}

return execEnvironment(input, config, creds)
}

func execServer(input ExecCommandInput, config *vault.Config, creds *credentials.Credentials) error {
if err := server.StartLocalServer(creds, config.Region); err != nil {
return fmt.Errorf("Failed to start credential server: %w", err)
}

env := environ(os.Environ())
env.Set("AWS_VAULT", input.ProfileName)
func updateEnvForAwsVault(env environ, profileName string, region string) environ {
env.Unset("AWS_ACCESS_KEY_ID")
env.Unset("AWS_SECRET_ACCESS_KEY")
env.Unset("AWS_SESSION_TOKEN")
env.Unset("AWS_SECURITY_TOKEN")
env.Unset("AWS_CREDENTIAL_FILE")
env.Unset("AWS_DEFAULT_PROFILE")
env.Unset("AWS_PROFILE")
env.Unset("AWS_DEFAULT_REGION")
env.Unset("AWS_REGION")
env.Unset("AWS_SDK_LOAD_CONFIG")

env.Set("AWS_VAULT", profileName)

log.Printf("Setting subprocess env: AWS_DEFAULT_REGION=%s, AWS_REGION=%s", region, region)
env.Set("AWS_DEFAULT_REGION", region)
env.Set("AWS_REGION", region)

return env
}

func execEc2Server(input ExecCommandInput, config *vault.Config, creds *credentials.Credentials) error {
if err := server.StartEc2CredentialsServer(creds, config.Region); err != nil {
return fmt.Errorf("Failed to start credential server: %w", err)
}

env := environ(os.Environ())
env = updateEnvForAwsVault(env, input.ProfileName, config.Region)

return execCmd(input.Command, input.Args, env)
}

func execEcsServer(input ExecCommandInput, config *vault.Config, creds *credentials.Credentials) error {
uri, token, err := server.StartEcsCredentialServer(creds)
if err != nil {
return fmt.Errorf("Failed to start credential server: %w", err)
}

env := environ(os.Environ())
env = updateEnvForAwsVault(env, input.ProfileName, config.Region)

log.Println("Setting subprocess env AWS_CONTAINER_CREDENTIALS_FULL_URI, AWS_CONTAINER_AUTHORIZATION_TOKEN")
env.Set("AWS_CONTAINER_CREDENTIALS_FULL_URI", uri)
env.Set("AWS_CONTAINER_AUTHORIZATION_TOKEN", token)

return execCmd(input.Command, input.Args, env)
}
Expand Down Expand Up @@ -205,22 +254,11 @@ func execEnvironment(input ExecCommandInput, config *vault.Config, creds *creden
}

env := environ(os.Environ())
env.Set("AWS_VAULT", input.ProfileName)

env.Unset("AWS_ACCESS_KEY_ID")
env.Unset("AWS_SECRET_ACCESS_KEY")
env.Unset("AWS_SESSION_TOKEN")
env.Unset("AWS_SECURITY_TOKEN")
env.Unset("AWS_CREDENTIAL_FILE")
env.Unset("AWS_DEFAULT_PROFILE")
env.Unset("AWS_PROFILE")
env.Unset("AWS_SESSION_EXPIRATION")
env = updateEnvForAwsVault(env, input.ProfileName, config.Region)

if config.Region != "" {
log.Printf("Setting subprocess env: AWS_DEFAULT_REGION=%s, AWS_REGION=%s", config.Region, config.Region)
env.Set("AWS_DEFAULT_REGION", config.Region)
env.Set("AWS_REGION", config.Region)
}
log.Printf("Setting subprocess env: AWS_DEFAULT_REGION=%s, AWS_REGION=%s", config.Region, config.Region)
env.Set("AWS_DEFAULT_REGION", config.Region)
env.Set("AWS_REGION", config.Region)

log.Println("Setting subprocess env: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY")
env.Set("AWS_ACCESS_KEY_ID", val.AccessKeyID)
Expand Down
2 changes: 1 addition & 1 deletion cli/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func ConfigureServerCommand(app *kingpin.Application) {
}

func ServerCommand(app *kingpin.Application, input ServerCommandInput) {
if err := server.StartEc2MetadataProxyServer(); err != nil {
if err := server.StartEc2MetadataEndpointProxy(); err != nil {
app.Fatalf("Server failed: %v", err)
}
}
78 changes: 41 additions & 37 deletions server/server.go → server/ec2.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,28 @@ import (
)

const (
awsTimeFormat = "2006-01-02T15:04:05Z"
ec2ServerBind = "169.254.169.254:80"
localServerBind = "127.0.0.1:9099"
awsTimeFormat = "2006-01-02T15:04:05Z"
ec2MetadataEndpointAddr = "169.254.169.254:80"
ec2CredentialsServerAddr = "127.0.0.1:9099"
)

// StartEc2MetadataProxyServer starts a proxy server on the standard Ec2 Instance Metadata address
func StartEc2MetadataProxyServer() error {
var localServerURL, err = url.Parse(fmt.Sprintf("http://%s/", localServerBind))
// StartEc2MetadataEndpointProxy starts a http proxy server on the standard EC2 Instance Metadata endpoint
func StartEc2MetadataEndpointProxy() error {
var localServerURL, err = url.Parse(fmt.Sprintf("http://%s/", ec2CredentialsServerAddr))
if err != nil {
log.Fatal(err)
}

if _, err := installNetworkAlias(); err != nil {
if _, err := installEc2EndpointNetworkAlias(); err != nil {
return err
}

l, err := net.Listen("tcp", ec2ServerBind)
l, err := net.Listen("tcp", ec2MetadataEndpointAddr)
if err != nil {
return err
}

log.Printf("EC2 Instance Metadata server running on %s", l.Addr())
log.Printf("EC2 Instance Metadata endpoint proxy server running on %s", l.Addr())
return http.Serve(l, httputil.NewSingleHostReverseProxy(localServerURL))
}

Expand All @@ -44,43 +44,49 @@ func isServerRunning(bind string) bool {
return err == nil
}

// StartLocalServer starts a http server to service the EC2 Instance Metadata endpoint
func StartLocalServer(creds *credentials.Credentials, region string) error {
if !isServerRunning(ec2ServerBind) {
if err := StartProxyServerProcess(); err != nil {
// StartEc2CredentialsServer starts a EC2 Instance Metadata server and endpoint proxy
func StartEc2CredentialsServer(creds *credentials.Credentials, region string) error {
if !isServerRunning(ec2MetadataEndpointAddr) {
if err := StartEc2EndpointProxyServerProcess(); err != nil {
return err
}
}

log.Printf("Starting local credentials server on %s", localServerBind)
go func() {
router := http.NewServeMux()
// pre-fetch credentials so that we can respond quickly to the first request
_, _ = creds.Get()

router.HandleFunc("/latest/meta-data/iam/security-credentials/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "local-credentials")
})
go startEc2CredentialsServer(creds, region)

// The AWS Go SDK checks the instance-id endpoint to validate the existence of EC2 Metadata
router.HandleFunc("/latest/meta-data/instance-id/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "aws-vault")
})
return nil
}

// The AWS .NET SDK checks this endpoint during obtaining credentials/refreshing them
router.HandleFunc("/latest/meta-data/iam/info/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `{"Code" : "Success"}`)
})
func startEc2CredentialsServer(creds *credentials.Credentials, region string) {

// used by AWS SDK to determine region
router.HandleFunc("/latest/meta-data/dynamic/instance-identity/document", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `{"region": "`+region+`"}`)
})
log.Printf("Starting EC2 Instance Metadata server on %s", ec2CredentialsServerAddr)
router := http.NewServeMux()

router.HandleFunc("/latest/meta-data/iam/security-credentials/local-credentials", credsHandler(creds))
router.HandleFunc("/latest/meta-data/iam/security-credentials/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "local-credentials")
})

log.Fatalln(http.ListenAndServe(localServerBind, withLoopbackSecurityCheck(router)))
}()
// The AWS Go SDK checks the instance-id endpoint to validate the existence of EC2 Metadata
router.HandleFunc("/latest/meta-data/instance-id/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "aws-vault")
})

return nil
// The AWS .NET SDK checks this endpoint during obtaining credentials/refreshing them
router.HandleFunc("/latest/meta-data/iam/info/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `{"Code" : "Success"}`)
})

// used by AWS SDK to determine region
router.HandleFunc("/latest/meta-data/dynamic/instance-identity/document", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `{"region": "`+region+`"}`)
})

router.HandleFunc("/latest/meta-data/iam/security-credentials/local-credentials", credsHandler(creds))

log.Fatalln(http.ListenAndServe(ec2CredentialsServerAddr, logRequest(withLoopbackSecurityCheck(router))))
}

// withLoopbackSecurityCheck is middleware to check that the request comes from the loopback device
Expand All @@ -100,8 +106,6 @@ func withLoopbackSecurityCheck(next *http.ServeMux) http.HandlerFunc {
return
}

log.Printf("RemoteAddr = %v", r.RemoteAddr)

next.ServeHTTP(w, r)
}
}
Expand Down
2 changes: 1 addition & 1 deletion server/alias_bsd.go → server/ec2alias_bsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ package server

import "os/exec"

func installNetworkAlias() ([]byte, error) {
func installEc2EndpointNetworkAlias() ([]byte, error) {
return exec.Command("ifconfig", "lo0", "alias", "169.254.169.254").CombinedOutput()
}
2 changes: 1 addition & 1 deletion server/alias_linux.go → server/ec2alias_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ package server

import "os/exec"

func installNetworkAlias() ([]byte, error) {
func installEc2EndpointNetworkAlias() ([]byte, error) {
return exec.Command("ip", "addr", "add", "169.254.169.254/24", "dev", "lo", "label", "lo:0").CombinedOutput()
}
2 changes: 1 addition & 1 deletion server/alias_windows.go → server/ec2alias_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"strings"
)

func installNetworkAlias() ([]byte, error) {
func installEc2EndpointNetworkAlias() ([]byte, error) {
out, err := exec.Command("netsh", "interface", "ipv4", "add", "address", "Loopback Pseudo-Interface 1", "169.254.169.254", "255.255.0.0").CombinedOutput()

if err == nil || strings.Contains(string(out), "The object already exists") {
Expand Down
8 changes: 4 additions & 4 deletions server/proxy_default.go → server/ec2proxy_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (
"time"
)

// StartProxyServerProcess starts a `aws-vault server` process
func StartProxyServerProcess() error {
// StartEc2EndpointProxyServerProcess starts a `aws-vault server` process
func StartEc2EndpointProxyServerProcess() error {
log.Println("Starting `aws-vault server` in the background")
cmd := exec.Command(os.Args[0], "server")
cmd.Stdin = os.Stdin
Expand All @@ -21,8 +21,8 @@ func StartProxyServerProcess() error {
return err
}
time.Sleep(time.Second * 1)
if !isServerRunning(metadataBind) {
return errors.New("The credential proxy server isn't running. Run aws-vault server as Administrator in the background and then try this command again")
if !isServerRunning(ec2MetadataEndpointAddr) {
return errors.New("The EC2 Instance Metadata endpoint proxy server isn't running. Run `aws-vault server` as Administrator or root in the background and then try this command again")
}
return nil
}
4 changes: 2 additions & 2 deletions server/proxy_unix.go → server/ec2proxy_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"os/exec"
)

// StartProxyServerProcess starts a `aws-vault server` process
func StartProxyServerProcess() error {
// StartEc2EndpointProxyServerProcess starts a `aws-vault server` process
func StartEc2EndpointProxyServerProcess() error {
log.Println("Starting `aws-vault server` as root in the background")
cmd := exec.Command("sudo", "-b", os.Args[0], "server")
cmd.Stdin = os.Stdin
Expand Down
Loading

0 comments on commit ec402aa

Please sign in to comment.