-
Notifications
You must be signed in to change notification settings - Fork 827
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Assume role with web identity provider #587
Assume role with web identity provider #587
Conversation
AWS allows users and systems to assume roles using OpenID Connect tokens via the [AssumeRoleWithWebIdentity API call]. This API allows role assumption via "Amazon Cognito, Login with Amazon, Facebook, Google, or any OpenID Connect-compatible identity provider." This change proposes that aws-vault support AssumeRoleWithWebIdentity, either by fetching a token from a static file (via the standard [web_identity_token_file] config option) or by executing a process (via the web_identity_token_process config option, similar to [credential_process] but adapted for web identity). The latter would be a custom option for aws-vault, and would allow for interactive logins through OpenID Connect identity providers that can redirect back to localhost or [urn:ietf:wg:oauth:2.0:oob]. A static example: ``` [profile foo] role_arn = arn:aws:iam::123457890:role/foo web_identity_token_file = /path/to/file ``` A dynamic example, using a tool like [oidccli]: ``` [profile foo] role_arn = arn:aws:iam::123457890:role/foo web_identity_token_process = oidccli -issuer=https://example.com -client-id=aws -client-secret=localonly raw ``` [AssumeRoleWithWebIdentity API call]: https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html [web_identity_token_file]: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-role.html#cli-configure-role-oidc [credential_process]: https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sourcing-external.html [urn:ietf:wg:oauth:2.0:oob]: https://cloud.google.com/iap/docs/authentication-howto#signing_in_to_the_application [oidccli]: https://github.com/pardot/oidc/blob/master/cmd/oidccli
3e75580
to
8b948f2
Compare
cmdArgs = []string{"cmd.exe", "/C", p.WebIdentityTokenProcess} | ||
} else { | ||
cmdArgs = []string{"/bin/sh", "-c", p.WebIdentityTokenProcess} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would using https://github.com/mattn/go-shellwords (instead of invoking the shell) be a better approach? Or are there specific shell features you're expecting to be used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is admittedly modeled after the aws-sdk-go's processcreds provider, since I modeled web_identity_token_process
after credential_process
. That function invokes a shell, so I was inspired to do it here as well.
If you think it's better to avoid executing a shell, I'd be happy to implement it that way though.
vault/vault.go
Outdated
@@ -192,6 +225,8 @@ func (t *tempCredsCreator) provider(config *Config) (credentials.Provider, error | |||
} | |||
} else if config.HasSSOStartURL() { | |||
return NewSSORoleCredentialsProvider(t.keyring.Keyring, config) | |||
} else if config.HasRole() && (config.HasWebIdentityTokenFile() || config.HasWebIdentityTokenProcess()) { | |||
return NewAssumeRoleWithWebIdentityProvider(credentials.NewCredentials(sourceCredProvider), t.keyring.Keyring, config) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Referencing sourceCredProvider
inside this if-else block doesn't seem right, as the sourceCredProvider is defined in the if-else. Does this condition need to move after the final else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Since we're using a web identity to assume a role, we don't actually need source credentials at all. This is a copy & paste error.
I've updated it in 6f25cc6 and c1e355e to use the same method for constructing a session as NewSSORoleCredentialsProvider
which also does not require a sourceCredProvider
.
USAGE.md
Outdated
|
||
AWS supports assuming roles using [web identity federation and OpenID Connect](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-role.html#cli-configure-role-oidc), including login using Amazon, Google, Facebook or any other OpenID Connect server. The configuration options are as follows: | ||
* `web_identity_token_file` A file that contains an OpenID Connect identity token. The token is loaded and passed as the `WebIdentityToken` argument of the `AssumeRoleWithWebIdentity` operation. | ||
* `web_identity_token_process` A command that executes to generate an OpenID Connect identity token. The token written to the command's standard out is passed as the `WebIdentityToken` argument of the `AssumeRoleWithWebIdentity` operation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't find any reference to web_identity_token_process
in the AWS CLI docs. Is this a new config parameter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes I see from the PR description that it is.
Although we have endeavoured to retain compatibility with the aws cli config, this seems pretty reasonable.
We should document this further perhaps in the config section of this doc, pointing out it is not standard
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although we have endeavoured to retain compatibility with the aws cli config, this seems pretty reasonable.
Awesome. It's designed to be really similar to the officially-supported credential_process
option, but for retrieving a web identity token instead of an AWS access key/secret key/session key directly.
We should document this further perhaps in the config section of this doc, pointing out it is not standard
👍 What do you think of 4d712c1?
r, w, err := os.Pipe() | ||
if err != nil { | ||
return "", err | ||
} | ||
defer r.Close() | ||
defer w.Close() | ||
|
||
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) | ||
cmd.Env = os.Environ() | ||
cmd.Stdin = os.Stdin | ||
cmd.Stdout = w | ||
cmd.Stderr = os.Stderr | ||
|
||
if err := cmd.Start(); err != nil { | ||
return "", fmt.Errorf("failed to start command %q: %v", p.WebIdentityTokenProcess, err) | ||
} | ||
defer func() { _ = cmd.Process.Kill() }() | ||
|
||
waitCh := make(chan error, 1) | ||
go func() { waitCh <- cmd.Wait() }() | ||
|
||
b := bytes.NewBuffer(make([]byte, 0, defaultMaxBufSize)) | ||
readCh := make(chan error, 1) | ||
go func() { | ||
w.Close() // close our write end of the pipe | ||
defer r.Close() | ||
|
||
_, err := io.CopyN(b, r, int64(defaultMaxBufSize)) | ||
readCh <- err | ||
}() | ||
|
||
timer := time.NewTimer(defaultTimeout) | ||
defer timer.Stop() | ||
|
||
// Wait for process to exit (or timeout) | ||
select { | ||
case err := <-waitCh: // process exited | ||
if err != nil { | ||
return "", fmt.Errorf("command %q failed: %v", p.WebIdentityTokenProcess, err) | ||
} | ||
case <-timer.C: // timeout | ||
return "", fmt.Errorf("command %q timed out after %s", p.WebIdentityTokenProcess, defaultTimeout) | ||
} | ||
|
||
// Wait for read to finish (or timeout) | ||
select { | ||
case err := <-readCh: // process exited | ||
if err != nil && err != io.EOF { | ||
return "", fmt.Errorf("read output from %q failed: %v", p.WebIdentityTokenProcess, err) | ||
} | ||
case <-timer.C: // timeout | ||
return "", fmt.Errorf("command %q timed out after %s", p.WebIdentityTokenProcess, defaultTimeout) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a lot of code here with channels, defers, etc, and I'm wondering if this is more complex than it needs to be.
Would a out, err := exec.Command(cmdArgs).Output()
suffice here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would suffice for the simple case, yes. This bit of code is again inspired by aws-sdk-go's implementation of processcreds.
This code is more defensive than exec.Command(cmdArgs).Output()
would be in some cases. Specifically:
- If the process writes more than
defaultMaxBufSize
bytes to stdout, we will stop reading and close our end of the pipe. The process will die with EPIPE/SIGPIPE. The amount of memory we use as a buffer is fixed. On the other hand,Output
is implemented with abytes.Buffer
which will expand 'infinitely' (until it runs out of memory). - It will timeout and kill the process after
defaultTimeout
, but this could be also achieved usingexec.CommandContext()
.
Since aws-vault
is configured by the user for their own use on their own machine, we might not need to be as defensive to processes that run away and write lots to stdout. I think in the worse case, aws-vault
would panic with ErrTooLarge
and die? If you think that's the case, I'm happy to simplify this to use exec.Command().Output
or exec.ContextCommand().Output()
. Let me know :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I don't think we need to be as defensive as the SDK, just go with the simple code I reckon
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. I pushed that up as 78eb664.
331c84e
to
c1e355e
Compare
We do not need to be as defensive as the SDK in this case: 99designs#587 (comment)
Thank you @alindeman ! |
Thanks for pulling this in @mtibben. I'm fairly motived to see v6 ship soon, so if there are any other issues I can help with around that, definitely let me know 😄 |
AWS allows users and systems to assume roles using OpenID Connect tokens via the AssumeRoleWithWebIdentity API call. This API allows role assumption via "Amazon Cognito, Login with Amazon, Facebook, Google, or any OpenID Connect-compatible identity provider."
This change proposes that aws-vault support AssumeRoleWithWebIdentity, either by fetching a token from a static file (via the standard web_identity_token_file config option) or by executing a process (via the web_identity_token_process config option, similar to credential_process but adapted for web identity). The latter would be a custom option for aws-vault, and would allow for interactive logins through OpenID Connect identity providers that can redirect back to localhost or urn:ietf:wg:oauth:2.0:oob.
A static example:
A dynamic example, using a tool like oidccli: