-
-
Notifications
You must be signed in to change notification settings - Fork 40
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
Using GOOGLE_APPLICATION_CREDENTIALS to specify an authorized_user or impersonated_service_account JSON key file giving "missing field private_key
" error
#56
Comments
Hi @liufuyang did you manage to solve this problem? I'm thinking it could have something to do with newer version of gcloud? I'll investigate |
I am not quite sure. there is no field of |
Hi, @liufuyang did you fix the issue?
Based on my testing, I am able to decrypt based on the token from
To see if there is any difference from yours, I'd love to check this up. Or, you could provide a repository that reproduces the issue for me. Thanks 🙏🏻 |
Thanks a lot. Very sorry I lost my environment when I tested this and tried again with the setup and it works fine. But I remember previously I was playing with So I tried again to generate a json key with the command above, using Then the generated {
"delegates": [],
"service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/[email protected]:generateAccessToken",
"source_credentials": {
"client_id": "xxxxx.apps.googleusercontent.com",
"client_secret": "xxxxx",
"refresh_token": "xxxxx",
"type": "authorized_user"
},
"type": "impersonated_service_account"
} And when using this Perhaps you can take a look in this direction? Maybe it is nice to be able to support impersonated_service_account? Otherwise, it is not anything urgent as it might be a future not many people would use in practice. |
private_key
" errorprivate_key
" error
I'm sorry for not getting back to you sooner. I was too busy to check this up for you lately. Below are the steps on how I tested it.
ALL TESTs passed. use reqwest::header::CONTENT_TYPE;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let authentication_manager = gcp_auth::AuthenticationManager::new().await?;
let _token = authentication_manager
.get_token(&["https://www.googleapis.com/auth/cloud-platform"])
.await?;
// Ref: https://cloud.google.com/storage/docs/listing-objects#permissions-rest
let bucket_name = "REPLACE_THIS_BY_YOURS_BUCKET_NAME".to_string();
let client = reqwest::Client::new();
let uri = format!(
"https://storage.googleapis.com/storage/v1/b/{}/o",
bucket_name
);
let response = client.get(uri)
.header(CONTENT_TYPE, "application/x-www-form-urlencoded")
.bearer_auth(&_token.as_str().to_string()).send().await?.text().await?;
println!("Response: {}", response);
Ok(())
} |
thanks @dacozai for a suggestion, however I also see value in having direct support for impersonation so I'll leave this open so far |
@liufuyang May I ask which version you use? thanks 🙏🏻 |
Google Cloud SDK 406.0.0 @dacozai Okay, it seems a bug (or just missing a feature) in the code when the
If an impersonation account JSON is used, then the error shows as a different line 11: My testing code is: use gcp_auth::AuthenticationManager;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let authentication_manager = gcp_auth::AuthenticationManager::new().await?;
let token = authentication_manager
.get_token(&["https://www.googleapis.com/auth/cloud-platform"])
.await?;
println!("{}", token.as_str());
Ok(())
}
So I guess if you just run with |
private_key
" errorprivate_key
" error
This isn't restricted to impersonated service accounts. I am also seeing this with just using the standard Version
Steps to reproduce
{
"client_id": "*******.apps.googleusercontent.com",
"client_secret": "*******",
"quota_project_id": "*******",
"refresh_token": "*******",
"type": "authorized_user"
} The reason I need to use GOOGLE_APPLICATION_CREDENTIALS instead of other methods is because I am trying to run my program locally under docker-compose where I won't have gcloud sdk installed. |
Looking into this further, I found a stack overflow posts from back in 2019 that reference the application_default_credentials matching the format I am seeing, so this isn't an issue with a gcloud sdk update. The problem is that gcp_auth doesn't support using GOOGLE_APPLICATION_CREDENTIALS to reference This behavior is contrary to other google tools. For example the cloud_sql_proxy handles I believe this issue should be split into 2 issues
|
For others seeing this error due to docker, I can work around this by creating a non-root user with a home dir at |
Okay, so it appears there are (at least) two different kinds of JSON being used, one perhaps for users and one for service accounts? The one for service accounts includes a In order to get this right, it would be good if someone could dig out references to Google documentation (or, to Google source code, for example in their SDKs for other languages) that more clearly shows how these differ and when we're supposed to use one vs the other. |
@djc Thanks. Perhaps following some idea illustrated here? Here you also see the |
Maybe the right fix here is to make the I'm not great at reading Go code but it seems like that might be more in line with what they do there. @valkum this might be of interest to you. |
Using I see some fields in the go implementation that are missing in Looking at the go implementation the file can have different formats: type cred struct {
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
RedirectURIs []string `json:"redirect_uris"`
AuthURI string `json:"auth_uri"`
TokenURI string `json:"token_uri"`
}
var j struct {
Web *cred `json:"web"`
Installed *cred `json:"installed"`
} And if that fails, it tries this one: type credentialsFile struct {
Type string `json:"type"`
// Service Account fields
ClientEmail string `json:"client_email"`
PrivateKeyID string `json:"private_key_id"`
PrivateKey string `json:"private_key"`
AuthURL string `json:"auth_uri"`
TokenURL string `json:"token_uri"`
ProjectID string `json:"project_id"`
// User Credential fields
// (These typically come from gcloud auth.)
ClientSecret string `json:"client_secret"`
ClientID string `json:"client_id"`
RefreshToken string `json:"refresh_token"`
// External Account fields
Audience string `json:"audience"`
SubjectTokenType string `json:"subject_token_type"`
TokenURLExternal string `json:"token_url"`
TokenInfoURL string `json:"token_info_url"`
ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"`
ServiceAccountImpersonation serviceAccountImpersonationInfo `json:"service_account_impersonation"`
Delegates []string `json:"delegates"`
CredentialSource externalaccount.CredentialSource `json:"credential_source"`
QuotaProjectID string `json:"quota_project_id"`
WorkforcePoolUserProject string `json:"workforce_pool_user_project"`
// Service account impersonation
SourceCredentials *credentialsFile `json:"source_credentials"`
}
type serviceAccountImpersonationInfo struct {
TokenLifetimeSeconds int `json:"token_lifetime_seconds"`
} We should update our implementation here to match the structs used in the official sdk. The go code and the python code also distinguish the content of the file loaded via the env var. See here for go.. This logic might be missing in |
I don't think we should list all the fields we don't need here -- serde will ignore fields we don't need by default, which seems right for this. I think the way we should structure the data types here is to have an untagged enum to separate out what the Go code calls "Service Account fields" from the "User Credential fields". However, to the extent the token updating routines would be different for service account vs user credentials, it probably still makes sense to leave those in the two different trait implementations while extracting the code that inspects the |
I think the enum approach from @djc sounds the most straightforward and idiomatic. But I think we could do it with a tagged enum instead of an untagged enum. Looking at the google code, they discriminate on the Looking over the go reference code, the tag names used are these:
And with this approach, it would be very straightforward to extend the enum to support the other credential methods listed in the go reference
|
The struct is internally tagged by the enum Credentials {
ServiceAccount(ServiceAccountCredentials),
UserCredentials(UserCredentials),
ExternalAccount(ExternalAccountCredentials),
ImpersonateServiceAccount(ImpersonateServiceAccountCredentials)
}
struct ServiceAccountCredentials {
client_email: String,
private_key: String,
private_key_id: String,
token_uri: Option<String>,
audience: String
}
// implementation to turn ServiceAccountCredentials into some kind of common config form.
// Needs optional `scopes` and optional `subject` user to impersonate.
// Replaces `token_url` with fallback.
// Refresh logic: https://github.com/golang/oauth2/blob/2e4a4e2bfb69ca7609cb423438c55caa131431c1/jwt/jwt.go#L101
struct UserCredentials {
client_id: String,
client_secret: String,
auth_uri: Option<String>,
token_uri: Option<String>,
refresh_token: String
}
// implementation to turn UserCredentials into some kind of common config form
// Needs optional `scopes`
// Replaces `auth_url` and `token_url` with fallback
// Refresh logic: https://github.com/golang/oauth2/blob/master/oauth2.go#L269
struct ExternalAccountCredentials {
audience: String,
subject_token_type: String,
token_url: String, // Note: this is not token_uri from the other types.
token_info_url: String,
service_account_impersonation_url: Option<String>,
service_account_impersonation: ServiceTokenImpersonationInfo
client_secret: String,
client_id: String,
credential_source: CredentialSource,
quota_project_id: String,
workforce_pool_user_project: Option<String>
}
struct ServiceTokenImpersonationInfo {
token_lifetime_seconds: i64
}
// Omitting CredentialSource for now.
struct ImpersonateServiceAccountCredentials {
// Either an untagged enum or this
service_account_impersonation_url: Option<String>,
credential_source: Option<CredentialSource>,
delegates: Vec<String>
} The Options are based on All of the fields from above are used here. Edit: Sorry for pointing out similar things @msdrigg. Had this in my editor while looking through the go implementation. |
Fair that we can use a tagged enum here. @msdrigg would you be able to take a shot at a PR? |
I certainly would. I will take a crack at it in the next couple days or this weekend at the latest. |
Awesome, thanks! |
I have a PR that should fit all use cases except ExternalAccountCredentials. I need to test it but I would love anyone interested here to take a look and offer any feedback they can. |
Hi there, thanks for creating this nice package.
Does it support all kinds of different key jsons.
I tried to use the the json generated via
gcloud auth application-default login
and the key is located at
/Users/fuyangl/.config/gcloud/application_default_credentials.json
The key as "client_id", "client_secret", "refresh_token" and "type" as "authorized_user", that is all the fields there.
The text was updated successfully, but these errors were encountered: