Skip to content
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

Credentials Provider Initial Implementation & Design #179

Merged
merged 1 commit into from
Jan 28, 2021

Conversation

rcoh
Copy link
Collaborator

@rcoh rcoh commented Jan 28, 2021

Issue #, if available: Fixes #149, Fixes #148
*Description of changes:*Initial Implementation of credential providers, design validated & used in #107

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

This also introduces aws/rust-runtime, runtime components specific to AWS.

@rcoh rcoh requested a review from a team January 28, 2021 13:47

/// Load Credentials from Environment Variables
pub struct EnvironmentVariableCredentialsProvider {
env: Box<dyn Fn(&str) -> Result<String, VarError> + Send + Sync>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question

Why does the provider need to hold onto the environment?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in Rust, all the UTs run in a parallel so setting environment variables before tests doesn't work very well. This allows us to provide a HashMap of environment variables (or any function) for test purposes.

I'm imagining eventually we'll have protocol tests that specify the environment they're running in as well.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this might justify a separate module to handle env vars that could be appropriately mocked in unit tests

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I think the second time I do this I'll extract the module


const ENV_PROVIDER: &'static str = "EnvironmentVariable";

impl ProvideCredentials for EnvironmentVariableCredentialsProvider {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question

What balance are you going to strike with CRT providers? Environment provider is obviously rather simple, some of the others are more involved (imds, x509, etc)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As soon as CRT bindings can get done, pretty much 100% CRT. But we're doing a private beta starting 2/15 and I just need something that mostly works that people can play with.


/// A Credentials Provider
///
/// This interface is intentionally NOT async. Credential providers should provide a separate
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question

Can you elaborate on this? Why would you not want this to be async?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a few reasons:

  • predictability in the request pipeline. When you call call, I want to be as close as possible to that executing exactly one HTTP request. With Rusoto, there are a decent number of issues that are hard to track down where a credential provider hangs, then hangs all the requests. By pushing credential provider refreshes out of band, I'm hoping to side step that
  • Improved testability. If the credentials provider refresh is out of band, you can have full control over the HTTP stack its using, whether or not it refreshes, etc.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like there should be a larger discussion about this one. even if the creds fetch only has a file system dependency, it'd be reasonable to make it async.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be clear, it's not that retrieving credentials is something sync necessarily—but the credentials provider that lives in the request path is sync (because the credentials have always been preloaded)

@rcoh rcoh merged commit 61dadae into main Jan 28, 2021
@rcoh rcoh deleted the credential-providers branch January 28, 2021 15:39
Comment on lines +34 to +36
let mut creds = f.debug_struct("Credentials");
creds.field("provider_name", &self.provider_name);
creds.finish()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you might consider the AKID in this debug string. AKIDs are generally considered non-sensitive without their corresponding secret keys. could be worth a double check on that though.

/// If these credentials never expire, this value will be set to `None`
expires_after: Option<SystemTime>,

provider_name: &'static str,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a good reason for this to be a &'static str instead of just a String? is it performance? i wonder if provider name could be generated more dynamically in some use cases, for instance a role creds provider including the role name in provider_name. something to think about.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, that's a good point. I was thinking this string was always going to be hard coded but I may be wrong. In any case, this field is private at the moment so it's easy to change to String at some point


const STATIC_CREDENTIALS: &'static str = "static";
impl Credentials {
pub fn from_keys(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for clarity you might consider two separate constructors (not sure how much i like the names):

fn from_user_keys(access_key_id: String, secret_access_key: String);
fn from_session_keys(access_key_id: String, secret_access_key: String, session_token: String);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not a bad idea. I'm not sure how often this constructor will actually get used in practice outside of examples (99.9% of people will use some form of environment provider)


/// A Credentials Provider
///
/// This interface is intentionally NOT async. Credential providers should provide a separate

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like there should be a larger discussion about this one. even if the creds fetch only has a file system dependency, it'd be reasonable to make it async.

Comment on lines +93 to +97
impl ProvideCredentials for Credentials {
fn credentials(&self) -> Result<Credentials, CredentialsError> {
Ok(self.clone())
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this avoid the clone? perhaps by making ProvideCredentials return pointers to the strings?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the way you do this is by making Credentials borrow its fields. Not opposed to that, just seemed like a premature optimization at this point. For comparison, this is how it works in Rusoto right now (with clone)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another decent option could be having this function return &Credentials. I considered adding credentials_ref() for that exact reason but skipped it for now for the purpose of simplicity.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general though, these credentials may outlive the provider (or at least the credentials within the provider)


/// Load Credentials from Environment Variables
pub struct EnvironmentVariableCredentialsProvider {
env: Box<dyn Fn(&str) -> Result<String, VarError> + Send + Sync>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this might justify a separate module to handle env vars that could be appropriately mocked in unit tests

std::env::var(key)
}

const ENV_PROVIDER: &'static str = "EnvironmentVariable";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

np: might want to consolidate on a capitalization strategy with these provider strings

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CredentialProvider trait design MvP Credential Provider implementation
3 participants