firebase-admin-node
firebase-admin-node copied to clipboard
[FR] Is it possible to use impersonated service account?
Is your feature request related to a problem? Please describe.
maybe related to #1703
I think It is not natural for the client to always have the service account json file, and I want to access the firebase admin through impersonate service account generated by gcloud auth application-default login --impersonate-service-account=
which the service account has permission for generating custom token.
However, when I use the service account which now stored in ADC, firebase admin throws an error message: 'Refresh token must contain a "client_id" property.'
.
In fact, the impersonate service account does not have a client_id itself, but it is contained in source_credentials like:
{
"delegates": [],
"service_account_impersonation_url": "",
"source_credentials": {
"client_id": "",
"client_secret": "",
"refresh_token": "",
"type": "authorized_user"
},
"type": "impersonated_service_account"
}
Describe the solution you'd like
When checking ADC's service account, if type is impersonated_service_account
, then check source_credentials
.
Describe alternatives you've considered I can manually get my service account from ADC and use 'source_credentials'. But is it really the only solution for checking impersonated service account?
Additional context No additional context
I found a few problems with this issue:
- I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.
- This issue does not seem to follow the issue template. Make sure you provide all the required information.
Hey @blue-hope Thank you for your feature request and the PR! I am looking into this now. Thank you for your patience!
@lahirumaramba Are there any updates?
Hello, any chance to get that reviewed ? It will helps a lot to reduce the risk of generating a json key. Thanks !
This is fixed in #1862 and now included in the v11.5.0 release.
Thanks folks for your patience on this! Try out the new feature if you get a chance and let us know what you think. If you encounter any issues, please open a new issue on Github. Thank you!
Hi @blue-hope ! Thanks for working on this! Could you let me know what your initializeApp
function looks like?
I'm initializing firebase like this:
initializeApp({
credential: applicationDefault()
})
But I get the following error:
Error: //cloud.google.com/docs/authentication/. If you are getting this error with curl or similar tools, you may need to specify 'X-Goog-User-Project' HTTP header for quota and billing purposes. For more information regarding 'X-Goog-User-Project' header, please check https://cloud.google.com/apis/docs/system-parameters. Raw server response: "{"error":{"code":403,"message":"Your application has authenticated using end user credentials from the Google Cloud SDK or Google Cloud Shell which are not supported by the identitytoolkit.googleapis.com. We recommend configuring the billing/quota_project setting in gcloud or using a service account through the auth/impersonate_service_account setting. For more information about service accounts and how to use them in your application, see https://cloud.google.com/docs/authentication/. If you are getting this error with curl or similar tools, you may need to specify 'X-Goog-User-Project' HTTP header for quota and billing purposes. For more information regarding 'X-Goog-User-Project' header, please check https://cloud.google.com/apis/docs/system-parameters.","errors":[{"message":"Your application has authenticated using end user credentials from the Google Cloud SDK or Google Cloud Shell which are not supported by the identitytoolkit.googleapis.com. We recommend configuring the billing/quota_project setting in gcloud or using a service account through the auth/impersonate_service_account setting. For more information about service accounts and how to use them in your application, see https://cloud.google.com/docs/authentication/. If you are getting this error with curl or similar tools, you may need to specify 'X-Goog-User-Project' HTTP header for quota and billing purposes. For more information regarding 'X-Goog-User-Project' header, please check https://cloud.google.com/apis/docs/system-parameters.","domain":"usageLimits","reason":"accessNotConfigured","extendedHelp":"https://console.developers.google.com"}],"status":"PERMISSION_DENIED","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"SERVICE_DISABLED","domain":"googleapis.com","metadata":{"consumer":"projects/764086051850","service":"identitytoolkit.googleapis.com"}}]}}"
Hi @blue-hope ! Thanks for working on this! Could you let me know what your
initializeApp
function looks like?I'm initializing firebase like this:
initializeApp({ credential: applicationDefault() })
But I get the following error:
Error: //cloud.google.com/docs/authentication/. If you are getting this error with curl or similar tools, you may need to specify 'X-Goog-User-Project' HTTP header for quota and billing purposes. For more information regarding 'X-Goog-User-Project' header, please check https://cloud.google.com/apis/docs/system-parameters. Raw server response: "{"error":{"code":403,"message":"Your application has authenticated using end user credentials from the Google Cloud SDK or Google Cloud Shell which are not supported by the identitytoolkit.googleapis.com. We recommend configuring the billing/quota_project setting in gcloud or using a service account through the auth/impersonate_service_account setting. For more information about service accounts and how to use them in your application, see https://cloud.google.com/docs/authentication/. If you are getting this error with curl or similar tools, you may need to specify 'X-Goog-User-Project' HTTP header for quota and billing purposes. For more information regarding 'X-Goog-User-Project' header, please check https://cloud.google.com/apis/docs/system-parameters.","errors":[{"message":"Your application has authenticated using end user credentials from the Google Cloud SDK or Google Cloud Shell which are not supported by the identitytoolkit.googleapis.com. We recommend configuring the billing/quota_project setting in gcloud or using a service account through the auth/impersonate_service_account setting. For more information about service accounts and how to use them in your application, see https://cloud.google.com/docs/authentication/. If you are getting this error with curl or similar tools, you may need to specify 'X-Goog-User-Project' HTTP header for quota and billing purposes. For more information regarding 'X-Goog-User-Project' header, please check https://cloud.google.com/apis/docs/system-parameters.","domain":"usageLimits","reason":"accessNotConfigured","extendedHelp":"https://console.developers.google.com"}],"status":"PERMISSION_DENIED","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","reason":"SERVICE_DISABLED","domain":"googleapis.com","metadata":{"consumer":"projects/764086051850","service":"identitytoolkit.googleapis.com"}}]}}"
+1
Hello, you need to authenticate as a service account, not an end-user.
gcloud auth application-default login --impersonate-service-account [email protected]
Of course your end-user will need specific permission like Service Account User on the impersonated service account
Hey @Klaitos , that's what i have been doing but without luck. I'm impersonating the firebase service account that has the following permissions:
And as for the end user, my account has Owner permissions so i should be good.
Is there anything else I could be doing wrong?
Hum yes you're right, it does not simply work on my local computer either.
I figured it out how to make it works but it requires a lot of changes in the class ImpersonatedServiceAccountCredential
, i can try to make a pull request but i don't know many things on oauth2 scopes.
@blue-hope i think we might need your help on this one. I tried some code on my laptop, it works when we tweak the function getAccessToken
of class ImpersonatedServiceAccountCredential
with something like
const auth = new GoogleAuth({
scopes: 'https://www.googleapis.com/auth/cloud-platform',
});
const client = await auth.getClient();
const json = await client.getAccessToken()
if (!json.token || !json.res?.data.expireTime) {
throw new FirebaseAppError(
AppErrorCodes.INVALID_CREDENTIAL,
`Unexpected response while fetching impersonated access token: ${JSON.stringify(json)}`,
);
}
return {
access_token: json.token,
expires_in: new Date(json.res.data.expireTime).valueOf() - new Date().valueOf()
}
but i'm not aware of unexpected consequences
It looks like we are not handling the token creation for impersonated accounts correctly here: https://github.com/firebase/firebase-admin-node/blob/49251a82b8fd1e30e09a82ca3797623d8691f4d0/src/app/credential-internal.ts#L399
Looking at the implementation in google-auth-library-nodejs
this needs a bit more work. We need to send the token request to iamcredentials.googleapis.com
instead of using the REFRESH_TOKEN_HOST
. We also need to include the required scopes for admin sdk and add support for delegates
in impersonated credentials. See: https://github.com/googleapis/google-auth-library-nodejs/blob/bdc6339014ae13945bbf82576f7ff71534851ab1/src/auth/impersonated.ts#L132
The proper fix for this is to migrate Admin SDK credentials handling logic to use google-auth-library-nodejs
. This migration prevents us from having to maintain a separate codebase to perform the same actions that google-auth-library-nodejs
handles better.
This is part of the reason for @Klaitos's workaround above to work, as it creates a new GoogleAuth
client using ADC (and the client is smart enough to detect impersonated service account credentials from your environment).
We are currently doing the initial planning for credentials migration to google-auth-library-nodejs
and part of that work is also related to #1377
Sorry for my naive approach for getting access token.
Don't we have to hot-fix REFRESH_TOKEN_HOST
to iamcredentials.googleapis.com
before implementing google-auth-library-nodejs
? @lahirumaramba
And if I can get a chance, I would like to participate the migration task.
It's not that simple because you need to call the route iamcredentials.googleapis.com
with an access token from the source user in order to get the access token for the service account and we do not have a simple way to get the first one i think
Folks, is there an open issue tracking this? It seems like a huge problem that firebase doesn't work out of the box with Google's Recommended Security Practices for Service Accounts.
Can we re-open this please?
I implemented a workaround to do service account impersonation in https://gist.github.com/lox/8bff5607c3e713c92a03a631796ab3f3.
Any updates on this? Is @lox workaround still the only way? gcloud's documentation goes into incredible depth of why service accounts are risky but there's no official implementation for service account impersonation in firebase admin.
Hey @velocd we have made some changes internally to migrate credentials handling to use google-auth-library
. Check out https://github.com/firebase/firebase-admin-node/issues/1377#issuecomment-1971607584 for a custom build if you have some time to test it out and share any early feedback. Thanks!