-
Notifications
You must be signed in to change notification settings - Fork 193
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
Conversation
|
||
/// Load Credentials from Environment Variables | ||
pub struct EnvironmentVariableCredentialsProvider { | ||
env: Box<dyn Fn(&str) -> Result<String, VarError> + Send + Sync> |
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.
question
Why does the provider need to hold onto the environment?
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.
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.
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 might justify a separate module to handle env vars that could be appropriately mocked in unit tests
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.
yeah I think the second time I do this I'll extract the module
|
||
const ENV_PROVIDER: &'static str = "EnvironmentVariable"; | ||
|
||
impl ProvideCredentials for EnvironmentVariableCredentialsProvider { |
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.
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)
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.
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 |
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.
question
Can you elaborate on this? Why would you not want this to be async?
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'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.
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.
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.
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.
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)
let mut creds = f.debug_struct("Credentials"); | ||
creds.field("provider_name", &self.provider_name); | ||
creds.finish() |
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.
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, |
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.
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.
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.
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( |
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.
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);
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.
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 |
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.
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.
impl ProvideCredentials for Credentials { | ||
fn credentials(&self) -> Result<Credentials, CredentialsError> { | ||
Ok(self.clone()) | ||
} | ||
} |
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.
should this avoid the clone? perhaps by making ProvideCredentials
return pointers to the strings?
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 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
)
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.
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.
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.
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> |
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 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"; |
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.
np: might want to consolidate on a capitalization strategy with these provider strings
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.