Repository: 99designs/aws-vault (language: go)

Issue:
Segfault when using ecs-server mode and /role-arn url
- [X] I am using the latest release of AWS Vault
- [X] I have provided my `.aws/config` (redacted if necessary)
- [x] I have provided the debug output using `aws-vault --debug` (redacted if necessary)

Here is my `.aws/config`: 

```
[profile eh-dev02]
region = eu-central-1
```


When using aws-vault in ecs server mode, I have a segfault when using the `/role-arn` route. 

```
2023/03/15 16:45:21 aws-vault 7.1.0-Homebrew
2023/03/15 16:45:21 Using prompt driver: osascript
2023/03/15 16:45:21 Loading config file /Users/j.foucault/.aws/config
2023/03/15 16:45:21 Parsing config file /Users/j.foucault/.aws/config
2023/03/15 16:45:21 [keyring] Considering backends: [keychain]
2023/03/15 16:45:21 [keyring] Querying keychain for service="aws-vault", keychain="aws-vault.keychain"
2023/03/15 16:45:21 [keyring] Found 5 results
2023/03/15 16:45:21 profile eh-dev02: using stored credentials
2023/03/15 16:45:21 profile eh-dev02: using GetSessionToken
2023/03/15 16:45:21 Setting subprocess env: AWS_REGION=eu-central-1, AWS_DEFAULT_REGION=eu-central-1
2023/03/15 16:45:21 Starting a local ECS credential server; your app's AWS sdk must support AWS_CONTAINER_CREDENTIALS_FULL_URI.
2023/03/15 16:45:21 Setting subprocess env AWS_CONTAINER_CREDENTIALS_FULL_URI, AWS_CONTAINER_AUTHORIZATION_TOKEN
2023/03/15 16:45:21 Starting a subprocess: docker compose up --build aws-vault-proxy
[+] Building 1.0s (9/9) FINISHED
 => [internal] load build definition from Dockerfile                                                                                                 0.0s
 => => transferring dockerfile: 32B                                                                                                                  0.0s
 => [internal] load .dockerignore                                                                                                                    0.0s
 => => transferring context: 2B                                                                                                                      0.0s
 => [internal] load metadata for docker.io/library/golang:1.17                                                                                       0.9s
 => [internal] load build context                                                                                                                    0.0s
 => => transferring context: 111B                                                                                                                    0.0s
 => [1/4] FROM docker.io/library/golang:1.17@sha256:87262e4a4c7db56158a80a18fefdc4fee5accc41b59cde821e691d05541bbb18                                 0.0s
 => CACHED [2/4] WORKDIR /usr/src/aws-vault-proxy                                                                                                    0.0s
 => CACHED [3/4] COPY . /usr/src/aws-vault-proxy                                                                                                     0.0s
 => CACHED [4/4] RUN go build -v -o /usr/local/bin/aws-vault-proxy ./...                                                                             0.0s
 => exporting to image                                                                                                                               0.0s
 => => exporting layers                                                                                                                              0.0s
 => => writing image sha256:51f4ab94b15599046f049c632846a17102d399c00869348aede96d0c403eeb6d                                                         0.0s
 => => naming to docker.io/library/amp-local-dev_aws-vault-proxy                                                                                     0.0s
[+] Running 0/0
[+] Running 1/1p-local-dev-aws-vault-proxy-1  Recreate                                                                                               0.0s
 ⠿ Container amp-local-dev-aws-vault-proxy-1  Recreated                                                                                              0.2s
Attaching to amp-local-dev-aws-vault-proxy-1
amp-local-dev-aws-vault-proxy-1  | 2023/03/15 15:45:23 reverse proxying target:http://host.docker.internal:51570 auth:*******
panic: runtime error: invalid memory address or nil pointer dereference

runtime/debug.Stack()
	/opt/homebrew/Cellar/go/1.20.2/libexec/src/runtime/debug/stack.go:24 +0x64
github.com/aws/aws-sdk-go-v2/internal/sync/singleflight.newPanicError({0x102792220?, 0x102b0c710})
	/Users/brew/Library/Caches/Homebrew/go_mod_cache/pkg/mod/github.com/aws/aws-sdk-go-v2@v1.17.5/internal/sync/singleflight/singleflight.go:33 +0x28
github.com/aws/aws-sdk-go-v2/internal/sync/singleflight.(*Group).doCall.func2.1()
	/Users/brew/Library/Caches/Homebrew/go_mod_cache/pkg/mod/github.com/aws/aws-sdk-go-v2@v1.17.5/internal/sync/singleflight/singleflight.go:186 +0x40
panic({0x102792220, 0x102b0c710})
	/opt/homebrew/Cellar/go/1.20.2/libexec/src/runtime/panic.go:884 +0x1f4
github.com/99designs/aws-vault/v7/vault.(*Mfa).GetMfaSerial(...)
	/private/tmp/aws-vault-20230313-4300-1p2g1w7/aws-vault-7.1.0/vault/mfa.go:33
github.com/99designs/aws-vault/v7/vault.(*AssumeRoleProvider).assumeRole(0x140001ac100, {0x10281fe60, 0x1400018e140})
	/private/tmp/aws-vault-20230313-4300-1p2g1w7/aws-vault-7.1.0/vault/assumeroleprovider.go:65 +0x208
github.com/99designs/aws-vault/v7/vault.(*AssumeRoleProvider).Retrieve(0x10a0c3488?, {0x10281fe60?, 0x1400018e140?})
	/private/tmp/aws-vault-20230313-4300-1p2g1w7/aws-vault-7.1.0/vault/assumeroleprovider.go:29 +0x40
github.com/aws/aws-sdk-go-v2/aws.(*CredentialsCache).singleRetrieve(0x140001b6100, {0x10281fe60, 0x1400018e140})
	/Users/brew/Library/Caches/Homebrew/go_mod_cache/pkg/mod/github.com/aws/aws-sdk-go-v2@v1.17.5/aws/credential_cache.go:120 +0x138
github.com/aws/aws-sdk-go-v2/aws.(*CredentialsCache).Retrieve.func1()
	/Users/brew/Library/Caches/Homebrew/go_mod_cache/pkg/mod/github.com/aws/aws-sdk-go-v2@v1.17.5/aws/credential_cache.go:104 +0x7c
github.com/aws/aws-sdk-go-v2/internal/sync/singleflight.(*Group).doCall.func2(0x14000121f46, 0x140001b2120, 0x0?)
	/Users/brew/Library/Caches/Homebrew/go_mod_cache/pkg/mod/github.com/aws/aws-sdk-go-v2@v1.17.5/internal/sync/singleflight/singleflight.go:191 +0x60
github.com/aws/aws-sdk-go-v2/internal/sync/singleflight.(*Group).doCall(0x0?, 0x0?, {0x0?, 0x0?}, 0x0?)
	/Users/brew/Library/Caches/Homebrew/go_mod_cache/pkg/mod/github.com/aws/aws-sdk-go-v2@v1.17.5/internal/sync/singleflight/singleflight.go:193 +0x84
created by github.com/aws/aws-sdk-go-v2/internal/sync/singleflight.(*Group).DoChan
	/Users/brew/Library/Caches/Homebrew/go_mod_cache/pkg/mod/github.com/aws/aws-sdk-go-v2@v1.17.5/internal/sync/singleflight/singleflight.go:131 +0x388


goroutine 22 [running]:
github.com/aws/aws-sdk-go-v2/internal/sync/singleflight.(*Group).doCall.func1.2()
	/Users/brew/Library/Caches/Homebrew/go_mod_cache/pkg/mod/github.com/aws/aws-sdk-go-v2@v1.17.5/internal/sync/singleflight/singleflight.go:160 +0x2c
created by github.com/aws/aws-sdk-go-v2/internal/sync/singleflight.(*Group).doCall.func1
	/Users/brew/Library/Caches/Homebrew/go_mod_cache/pkg/mod/github.com/aws/aws-sdk-go-v2@v1.17.5/internal/sync/singleflight/singleflight.go:160 +0x284
amp-local-dev-aws-vault-proxy-1  | 2023/03/15 15:45:30 http: proxy error: EOF
amp-local-dev-aws-vault-proxy-1  | 169.254.170.3 - - [15/Mar/2023:15:45:30 +0000] "GET /role-arn/arn:aws:iam::****:role/role-my-role-arn HTTP/1.1" 502 0
```


It seems to be linked to an issue with MfaSerial, thing is I have no mfa setup for this aws profile. 


Discussion / hints:

Fix patch:
diff --git a/go.mod b/go.mod
index 6f01ae45e..c7957e40c 100644
--- a/go.mod
+++ b/go.mod
@@ -14,6 +14,7 @@ require (
 	github.com/aws/aws-sdk-go-v2/service/sts v1.18.5
 	github.com/google/go-cmp v0.5.9
 	github.com/mattn/go-isatty v0.0.17
+	github.com/mattn/go-tty v0.0.4
 	github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
 	golang.org/x/term v0.5.0
 	gopkg.in/ini.v1 v1.67.0
@@ -32,7 +33,6 @@ require (
 	github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
 	github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
 	github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
-	github.com/mattn/go-tty v0.0.4 // indirect
 	github.com/mtibben/percent v0.2.1 // indirect
 	github.com/xhit/go-str2duration v1.2.0 // indirect
 	golang.org/x/sys v0.5.0 // indirect
diff --git a/vault/assumeroleprovider.go b/vault/assumeroleprovider.go
index 2d5042fe5..d482fb33d 100644
--- a/vault/assumeroleprovider.go
+++ b/vault/assumeroleprovider.go
@@ -21,7 +21,7 @@ type AssumeRoleProvider struct {
 	Tags              map[string]string
 	TransitiveTagKeys []string
 	SourceIdentity    string
-	*Mfa
+	Mfa
 }
 
 // Retrieve generates a new set of temporary credentials using STS AssumeRole
@@ -62,8 +62,8 @@ func (p *AssumeRoleProvider) assumeRole(ctx context.Context) (*ststypes.Credenti
 		input.ExternalId = aws.String(p.ExternalID)
 	}
 
-	if p.GetMfaSerial() != "" {
-		input.SerialNumber = aws.String(p.GetMfaSerial())
+	if p.MfaSerial != "" {
+		input.SerialNumber = aws.String(p.MfaSerial)
 		input.TokenCode, err = p.GetMfaToken()
 		if err != nil {
 			return nil, err
diff --git a/vault/mfa.go b/vault/mfa.go
index 1b1313840..eb5de34ba 100644
--- a/vault/mfa.go
+++ b/vault/mfa.go
@@ -14,28 +14,23 @@ import (
 
 // Mfa contains options for an MFA device
 type Mfa struct {
-	mfaSerial     string
+	MfaSerial     string
 	mfaPromptFunc prompt.Func
 }
 
 // GetMfaToken returns the MFA token
-func (m *Mfa) GetMfaToken() (*string, error) {
+func (m Mfa) GetMfaToken() (*string, error) {
 	if m.mfaPromptFunc != nil {
-		token, err := m.mfaPromptFunc(m.mfaSerial)
+		token, err := m.mfaPromptFunc(m.MfaSerial)
 		return aws.String(token), err
 	}
 
 	return nil, errors.New("No prompt found")
 }
 
-// GetMfaSerial returns the MFA serial
-func (m *Mfa) GetMfaSerial() string {
-	return m.mfaSerial
-}
-
-func NewMfa(config *ProfileConfig) *Mfa {
+func NewMfa(config *ProfileConfig) Mfa {
 	m := Mfa{
-		mfaSerial: config.MfaSerial,
+		MfaSerial: config.MfaSerial,
 	}
 	if config.MfaToken != "" {
 		m.mfaPromptFunc = func(_ string) (string, error) { return config.MfaToken, nil }
@@ -48,7 +43,7 @@ func NewMfa(config *ProfileConfig) *Mfa {
 		m.mfaPromptFunc = prompt.Method(config.MfaPromptMethod)
 	}
 
-	return &m
+	return m
 }
 
 func ProcessMfaProvider(processCmd string) (string, error) {
diff --git a/vault/sessiontokenprovider.go b/vault/sessiontokenprovider.go
index e17b79076..e19634ccf 100644
--- a/vault/sessiontokenprovider.go
+++ b/vault/sessiontokenprovider.go
@@ -14,7 +14,7 @@ import (
 type SessionTokenProvider struct {
 	StsClient *sts.Client
 	Duration  time.Duration
-	*Mfa
+	Mfa
 }
 
 // Retrieve generates a new set of temporary credentials using STS GetSessionToken
@@ -41,8 +41,8 @@ func (p *SessionTokenProvider) GetSessionToken(ctx context.Context) (*ststypes.C
 		DurationSeconds: aws.Int32(int32(p.Duration.Seconds())),
 	}
 
-	if p.GetMfaSerial() != "" {
-		input.SerialNumber = aws.String(p.GetMfaSerial())
+	if p.MfaSerial != "" {
+		input.SerialNumber = aws.String(p.MfaSerial)
 		input.TokenCode, err = p.GetMfaToken()
 		if err != nil {
 			return nil, err


Test patch: