google-apis-rs / generator

A binding and CLI generator for all google APIs
Apache License 2.0
71 stars 14 forks source link

Support Application Default Credentials #20

Open mwilliammyers opened 4 years ago

mwilliammyers commented 4 years ago

What do you think about supporting Application Default Credentials? To start we could just check GOOGLE_APPLICATION_CREDENTIALS.

Down the road, we could also communicate with the metadata server to automatically obtain credentials if GOOGLE_APPLICATION_CREDENTIALS wasn’t provided. This would obviously be more involved but I think reverse engineering this process from one of the official client libraries wouldn’t be too bad.

So the flow would be:

  1. Try to use an explicitly provided service account.
  2. If None, check GOOGLE_APPLICATION_CREDENTIALS
  3. (Future work) If None, communicate with metadata server to use default service account
  4. If still not found, Err.

This might belong in yup-oauth2 because right now google_api_auth pretty much wraps it, but this might be a little too Google-specific for yup-oauth2?

Maybe we could support this auth flow (which would use yup-oauth2 under the hood) as a sibling to the one provided by the with-yup-oauth2 feature? Honestly, I think this abstracted away flow should be the default and users could opt in to with-yup-oauth2 if they want more control. Although, it would be a little weird that this default feature would depend on yup-oauth2, in addition to the explicit non-default feature depending on it as well...

One of the things I love about GCP (as opposed to AWS etc) is their authentication mechanism. It is so seamless and uniform across all their APIs. This would be a big step towards that UX.

Once we decide on a direction, I would be happy to open a PR.

Byron commented 4 years ago

Hi @mwilliammyers , I am super excited about your participation in this project, especially seeing that my own participation faded a little. The reason being that I really don't have any use for google services myself right now, knowing that I will be living in China in a few days 😅.

As the project is still in early stages and you bringing a unique perspective to it, maybe you could become an official contributor with write permissions to master. Thus far, @ggriffiniii was working primarily on the library generator, I was doing infrastructure and started working on generating the CLI code.

Is this becoming a maintainer here something you would generally be open for?

mwilliammyers commented 4 years ago

Wow! That sounds fun!

And yeah I am definitely open to that idea. I am using this for a project at work (not in production yet), so that would be good.

ggriffiniii commented 4 years ago

I second what Byron said. I've been on vacation for the last few weeks so haven't checked in for a while and would welcome any additional help and certainly real world perspective from users are always welcome.

I personally get overwhelmed by the seemingly innumerable ways authentication can be handled and would welcome any suggestions to make it better. Regarding your particular proposal I would suggest seeing if it's something that yup-oauth2 may want to provide, and if they think it's not a great fit for yup-oauth2, feel free to add it to the google_api_auth crate.

Byron commented 4 years ago

Welcome, @mwilliammyers ! I have invited you to the organisation.

It's great to have you! Please feel free to do with the CLI code whatever you see fit, in case you need that sort of thing, just because I don't know when I will finish it. It uses liquid instead of syn for code generation as that would allow me to iterate faster without any perceived disadvantages. You are free to take them where you like in case you would find them useful.

Just to make interactions with the codebase more explicit, thus far I have not worked in branches or using PRs as @ggriffiniii and I worked on disjoint parts of the project. But if I had to touch the generator code, I would do that only for minor changes, and would bounce everything else to @ggriffiniii for discussion. The latter I found very valuable and fruitful at all times, @ggriffiniii is incredibly skilled and a fantastic communicator.

The generated repository has some machinery to swiftly regenerate and proof (cargo check) all google APIs, and I consider it feature complete for my requirements. Feel free to take it where you need it to be.

And of course, if you have any questions or concerns, just let us know, the gitter channel also linked from the readme should be a good starting point.

arraypad commented 3 years ago

I got this working for my purposes based on the comments here and in the linked issues, hope this helps:

struct GoogleMetadataAccess {}

impl GetToken for GoogleMetadataAccess {
    fn token<I, T>(
        &mut self,
        _scopes: I,
    ) -> Box<dyn Future<Item = Token, Error = RequestError> + Send>
    where
        T: Into<String>,
        I: IntoIterator<Item = T>,
    {
        let client = reqwest::blocking::Client::new();
        let res = match client.get("http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token")
            .header("Metadata-Flavor", "Google")
            .send() {
                Ok(res) => res,
                Err(e) => return Box::new(future::err(
                    RequestError::BadServerResponse(e.to_string()),
                )),
        };

        return Box::new(match res.json::<TokenResponse>() {
            Ok(token) => future::ok(token.to_oauth_token()),
            Err(e) => future::err(RequestError::BadServerResponse(e.to_string())),
        }) as Box<dyn Future<Item = Token, Error = RequestError> + Send>;
    }

    fn application_secret(&self) -> ApplicationSecret {
        Default::default()
    }

    fn api_key(&mut self) -> Option<String> {
        None
    }
}

// later...

    let gcs_client = if let Ok(creds_file) = std::env::var("GOOGLE_APPLICATION_CREDENTIALS") {
        let key = yup_oauth2::service_account_key_from_file(creds_file)?;
        let sa = yup_oauth2::ServiceAccountAccess::new(key).build();
        let auth = google_api_auth::yup_oauth2::from_authenticator(
            sa,
            vec!["https://www.googleapis.com/auth/devstorage.read_write"],
        );
        Some(google_storage::Client::new(auth))
    } else if let Ok(_) = std::env::var("K_SERVICE") {
        let ma = GoogleMetadataAccess{};
        let auth = google_api_auth::yup_oauth2::from_authenticator(
            ma,
            vec!["https://www.googleapis.com/auth/devstorage.read_write"],
        );
        Some(google_storage::Client::new(auth))
    } else {
        None
    };
mwilliammyers commented 3 years ago

@arraypad awesome! We should open a PR at yup_oauth2 and add something like this solution.