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

ADC Service Impersonation missing client_id #212

Open
stevemk14ebr opened this issue Feb 28, 2024 · 4 comments
Open

ADC Service Impersonation missing client_id #212

stevemk14ebr opened this issue Feb 28, 2024 · 4 comments

Comments

@stevemk14ebr
Copy link

stevemk14ebr commented Feb 28, 2024

Executing:

gcloud auth application-default login --impersonate-service-account <account_name>.iam.gserviceaccount.com

With the code:

async fn get_user_adc_auth() -> Result<Authenticator<HttpsConnector<HttpConnector>>> {
    let home = std::env::var("HOME").unwrap();

    let user_secret = oauth2::read_authorized_user_secret(format!(
        "{}/.config/gcloud/application_default_credentials.json",
        home
    ))
    .await
    .expect("user secret");

    let authenticator = oauth2::AuthorizedUserAuthenticator::builder(user_secret)
             .build()
             .await
             .expect("failed to create authenticator");
    
    Ok(authenticator)
}

Results in:

user secret: Custom { kind: InvalidData, error: "Bad authorized user secret: missing field `client_id` at line 13 column 1" }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

This is unexpected, as the client_id value is present

{
  "delegates": [],
  "service_account_impersonation_url": "<redacted>.iam.gserviceaccount.com:ge>
  "source_credentials": {
    "account": "",
    "client_id": "<redacted>.apps.googleusercontent.com",
    "client_secret": "<redacted>",
    "refresh_token": "<redacted>",
    "type": "authorized_user",
    "universe_domain": "googleapis.com"
  },
  "type": "impersonated_service_account"
}

this service_account_impersonation example also fails with this credential but with the error:

Token retrieval failed: JSON Error; this might be a bug with unexpected server responses! missing field `accessToken`

At the time of use instead.

This is important as this gcloud command is the recommended way to do local development without code changes. A user is expected to impersonate as a service account and the application will then behave as if it is live in production actually using said service account.

@dermesser
Copy link
Owner

can you try just placing the source_credentials object in the credentials file? You will see that it expects different fields than the top-level ones in your example.

@SirMishaa
Copy link

SirMishaa commented May 9, 2024

@dermesser
Also not working for me (Failed to authenticate: JSONError(Error("missing field `accessToken`", line: 17, column: 1))). The documentation is really not clear at all. Despite hours of trying, I'm unable to connect in Impersonation to my service account just to view and add events to my calendar into my principal account.

It would be really great if you can provide a fully working example from A to Z, so the good way to do it.
Thank you in advance, it helps a lot.

Reproduce :

gcloud auth application-default login --impersonate-service-account [email protected]

Result should looks like that :

{
  "delegates": [],
  "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/[email protected]:generateAccessToken",
  "source_credentials": {
    "account": "",
    "client_id": "<>.apps.googleusercontent.com",
    "client_secret": "<>",
    "refresh_token": "<>",
    "type": "authorized_user",
    "universe_domain": "googleapis.com"
  },
  "type": "impersonated_service_account"
}

Code :

let user_secret =
        read_authorized_user_secret("private/service-account-credentials.json").await?;
    let email = "[email protected]";

    info!("User secret: {:?}", user_secret);

    let auth = ServiceAccountImpersonationAuthenticator::builder(user_secret.clone(), email)
        .build()
        .await
        .expect("authenticator");

    let scopes = &["https://www.googleapis.com/auth/calendar"];
    let token = match auth.token(scopes).await {
        Ok(token) => token,
        Err(e) => {
            error!("Failed to authenticate: {:?}", e);
            process::exit(1);
        }
    };

    info!("Token: {:?}", token);

    let hub = CalendarHub::new(
        hyper::Client::builder().build(
            hyper_rustls::HttpsConnectorBuilder::new()
                .with_native_roots()
                .https_only()
                .enable_http2()
                .build(),
        ),
        auth,
    );

    let calendar = hub.calendar_list().list().doit().await?;

    info!("Calendar ID: {:?}", calendar.1);

Tried following your comment about source_credentials
private/service-account-credentials.json:

{
  "account": "",
  "client_id": "<>.apps.googleusercontent.com",
  "client_secret": "<Redacted>",
  "refresh_token": "<Redacted>",
  "type": "authorized_user", // also tried with impersonated_service_account
  "universe_domain": "googleapis.com"
}

@stevemk14ebr
Copy link
Author

stevemk14ebr commented Jun 27, 2024

Seems similar to firebase/firebase-admin-node#1861 like ImpersonatedServiceAccount type needs implemented. Otherwise seems similar to AuthorizedUserAuthenticator

@stevemk14ebr
Copy link
Author

I finally found a workaround. First go to your service account in GCP and under permissions click grant access

image

Add your personal user account as a principal to the service account with the service token creator role:

image

Wait a few minutes. And then you can do this:

gcloud auth application-default login

And in rust use those crediantials while impersonating by this:

async fn get_user_impersonation_auth() -> Result<Authenticator<HttpsConnector<HttpConnector>>> {
    let home_dir = env::var("HOME")?;

    // Construct the full path
    let path = format!("{}/{}", home_dir, ".config/gcloud/application_default_credentials.json");

    // Continue with the rest of your code 
    let secret = oauth2::read_authorized_user_secret(&path).await.unwrap(); 
    let service_account = "[email protected]";
    let authenticator = oauth2::ServiceAccountImpersonationAuthenticator::builder(secret, service_account).build().await?;
    Ok(authenticator)
}

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

No branches or pull requests

3 participants