GCP App Engine - Could not load the default credentials - authentication

I'm working on a project, that uses GCP and App Engine. In dev it will print out errors saying:
2020-09-20 15:07:24 dev[development] Error: Could not load the default credentials. Browse
to https://cloud.google.com/docs/authentication/getting-started for more information.
at GoogleAuth.getApplicationDefaultAsync (/workspace/node_modules/google-auth-
library/build/src/auth/googleauth.js:161:19) at runMicrotasks (<anonymous>) at
processTicksAndRejections (internal/process/task_queues.js:97:5) at runNextTicks
(internal/process/task_queues.js:66:3) at listOnTimeout (internal/timers.js:518:9)
at processTimers (internal/timers.js:492:7) at async GoogleAuth.getClient
(/workspace/node_modules/google-auth-library/build/src/auth/googleauth.js:503:17) at
async GrpcClient._getCredentials (/workspace/node_modules/google-
gax/build/src/grpc.js:108:24) at async GrpcClient.createStub
(/workspace/node_modules/google-gax/build/src/grpc.js:229:23)
Keep in mind this is development mode, but is running on the GCP App Engine infrastructure, it is not being run on localhost. I'm viewing the logs with the command:
gcloud app logs tail -s dev
According to the GCP App Engine docs # https://cloud.google.com/docs/authentication/production#cloud-console
If your application runs inside a Google Cloud environment that has a default service
account, your application can retrieve the service account credentials to call Google Cloud
APIs.
I checked my app engine service accounts. And I have a default service account and it is active. Please see the redacted image here:
So I guess my question is: If I have an active default service account, and my application is supposed to automatically use the default service account key when it makes API calls, why am I seeing this authentication error? What am I doing wrong?
Edit: here's the code that is printing out errors:
async function updateCrawledOnDateForLink (crawlRequestKey: EntityKey, linkKey: EntityKey): Promise<void> {
try {
const datastore = new Datastore();
const crawlRequest = await datastore.get(crawlRequestKey)
const brand = crawlRequest[0].brand.replace(/\s/g, "")
await data.Link.update(
+linkKey.id,
{ crawledOn: new Date() },
null,
brand)
} catch (error) {
console.error('updateCrawledOnDateForLink ERROR:', `CrawlRequest: ${crawlRequestKey.id}`, `Link: ${linkKey.id}`, error.message)
}
}
My prediction is that creating a new Datastore() each time is the problem, but let me know your thoughts.

The fact that removing new Datastore() from a couple of functions solves the issue indicates that the issue is not with Authentication with App Engine but it is with Datastore, which confirms the documentation piece you shared.
I believe that the issue is that Datastore is getting lost with the credentials when you are creating multiple instances of it in your code for some unknown reason.
Since you mentioned in the comments that you don't really need multiple instances of Datastore in your code the solution to your problem is to use a single instance in a global variable and use that variable in multiple functions.

Related

Amazon S3 bucket creation attempt yields "Error: TypeError: NetworkError when attempting to fetch resource"

I followed this tutorial to get started using the AWS SDK for JavaScript (version 3) within React Native apps, in order to, say, create and delete buckets stored in Amazon S3. I completed all the steps in the tutorial, but ran into an issue on Step 6: running the example React Native app. The app does run in my browser, fortunately. The problem is, the app keeps giving me the error message in the question title, whenever I type a bucket name into the text input field and subsequently press the button that calls this asynchronous JavaScript function for creating buckets:
const createBucket = async () => {
setSuccessMsg('');
setErrorMsg('');
try {
await client.send(new CreateBucketCommand({ Bucket: bucketName }));
setSuccessMsg(`Bucket '${bucketName}' created.`);
} catch (e) {
setErrorMsg(e);
}
};
What gives? I successfully created a Cognito Identity pool and attached the AmazonS3FullAccess policy to the unauthenticated identities IAM role. And I am positive that I provided the correct credentials—region and identity pool ID—within the React Native code.
If it matters, I used Expo, not the React Native CLI, to create and run the app.

How do I add Google Application Credentials/Secret to Vercel Deployment?

I am using Vercel Deployments with a NextJS app. Deployments are automatically run when I push to master, but I don't want to store keys in GitHub. My serverless functions are reliant on my database. When run locally, I can simply use Google's Default Authentication Credentials, but this is not possible when deployed to Vercel. As such, I created a Service Account to enable the server to have access.
How do I load the service account credentials without pushing they key itself to GitHub?
I tried adding the key as described in this issue, but that didn't work.
AFAIK setting an environment variable in Vercel is not helpful because Google environment variables require a path/JSON file (vs. simply text).
Rather than using a path to a JSON file, you can create an object and include environment variables as the values for each object key. For example:
admin.initializeApp({
credential: admin.credential.cert({
client_email: process.env.FIREBASE_CLIENT_EMAIL,
private_key: process.env.FIREBASE_PRIVATE_KEY,
project_id: 'my-project'
}),
databaseURL: 'https://my-project.firebaseio.com'
});
Then, you can add the environment variables inside your project settings in Vercel.
Adding to #leerob's answer, I found that putting quotes around the FIREBASE_PRIVATE_KEY environment variable in my .env file fixed an error I kept getting relating to the PEM file when making a request. I didn't need any quotes around the key for calls to the standard firebase library though.
This was the config I used to access the Google Cloud Storage API from my app:
const { Storage } = require('#google-cloud/storage');
const storage = new Storage({ projectId: process.env.FIREBASE_PROJECT_ID,
credentials: { client_email: process.env.FIREBASE_CLIENT_EMAIL,
private_key: process.env.FIREBASE_PRIVATE_KEY_WITH_QUOTES
}
})
I had this problem too but with google-auth-library. Most of Googles libraries provide a way to add credentials through a options object that you pass when initializing it. To be able to get information from Google Sheets or Google Forms you can for example do this:
const auth = new GoogleAuth({
credentials:{
client_id: process.env.GOOGLE_CLIENT_ID,
client_email: process.env.GOOGLE_CLIENT_EMAIL,
project_id: process.env.GOOGLE_PROJECT_ID,
private_key: process.env.GOOGLE_PRIVATE_KEY
},
scopes: [
'https://www.googleapis.com/auth/someScopeHere',
'https://www.googleapis.com/auth/someOtherScopeHere'
]
});
You can just copy the info from your credentials.json file to the corresponding environment variables. Just take care that when your working on localhost you will need to have the private_key in double quotes but when you put it into Vercel you should not include the quotes.

Running serverless offline as integration tests on CI

Is it a good idea to run integration tests on my CI using serverless offline?
I am on AWS and I want to test the Lambda <-> SQS integration.
My Lambda reads from an API Gateway, which I know is emulated on the serverless offline.
const JEST_SLS_OFFLINE_URL = localhost:3000 // Default sls offline url
describe('Version endpoint ', () => {
const fetchUser = async () => {
const url = `${String(JEST_SLS_OFFLINE_URL)}/user/123`
}
test('Should fetchUser', async () => {
expect(await fetchUser()).toBe('')
})
})
An alternative is to spin up a new servererless function on AWS (for every PR), which is quite resource consuming
I don't think this is a right or wrong answer - people kinda create their preferences with this but imo yes it's fine. It's what I do for integration tests. I also spinup a docker with DynamoDb. For SQS however you're going to have to emulate that - For integration tests I spin up a simple server and mock the calls/response - I do this for SQS,SNS,Cognito and a few other things that are either not available in serverless offline or do not provide the type of testing framework I desire. You can check out one of my answers on mocking Cognito - the same process applies to every AWS service which is very handy.

How does vue PWA use the precache? I still get "Page does not work offline"

I have a vue application that I updated to have PWA capability. It uses the firebase messaging service that has overridden the service worker with its own firebase-messaging-sw.js file.
The service worker is registered, active and working, I have added the pwa in the vue.config.js file so that it generates the manifest.json. When you build the production version of the app the following code gets added to the top of the service worker.
importScripts("precache-manifest.7b51ac9589a6dc8041a85d8f1792defa.js", "https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
From what I see the percache works fine.
Should this be enough to get the site to work in offline mode?
Do I need to add some cache management myself?
What am I missing because I still get the "Page does not work offline" error message in Chrome's dev tools under the App manifest tab.
Looks like Google also picked up on the quick hack and the warning has returned.
So since of Chrome93 (AUG-2021) the quick hack, will not work anymore :
self.addEventListener('fetch', function(event) {})
Solution working "for now" (since we never know what requirements Google will add later on)
I've found a nice article which provides with a few solutions, the first one the author provides is Network-Falling-Back-To-Cache strategy:
your service worker will first try to retrieve the resource from your server. Then when it can’t do that — because for example, you’re offline — retrieve it from the cache (if it exists there).
self.addEventListener('fetch', function(event) {
event.respondWith(async function() {
try{
var res = await fetch(event.request);
var cache = await caches.open('cache');
cache.put(event.request.url, res.clone());
return res;
}
catch(error){
return caches.match(event.request);
}
}());
});
You can find all the information and alternative solutions in the article:
https://javascript.plainenglish.io/your-pwa-is-going-to-break-in-august-2021-34982f329f40
I hope this will help futur visitors.
Additional side note:
using the above code you might encounter the following error:
service-worker.js:40 Uncaught (in promise) TypeError: Failed to execute 'put' on 'Cache': Request scheme 'chrome-extension' is unsupported
This error is caused by chrome extentions like Augury or Vue dev-tools. Switching both off will cause the error to disappear.
You need to add this line in the serviceworker. It fools the browser into thinking that the page will work offline:
self.addEventListener('fetch', function(event) {}) // add this to fool it into thinking its offline ready

Meteor: setup accounts-github not working

I'm trying to setup authentication using github.
I followed the documentation. I've installed the packages:
$> meteor add accounts-github
$> meteor add service-configuration
And my code in server/github.js looks like:
ServiceConfiguration.configurations.remove({
service: "github"
});
ServiceConfiguration.configurations.insert({
service: "github",
clientId: '****',
secret: '*************'
});
Meteor.loginWithGithub({
requestPermissions: ['user', 'public_repo']
}, function (err) {
if (err)
Session.set('errorMessage', err.reason || 'Unknown error');
});
When I start meteor now I get the following error:
/Users/me/.meteor/tools/5bf1690853/lib/node_modules/fibers/future.js:173
throw(ex);
^
TypeError: Object #<Object> has no method 'loginWithGithub'
at app/server/github.js:11:8
at app/server/github.js:18:3
....
So it seems the Meteor object doesn't have the loginWithGithub method. Any suggestions why?
You are running the code in the /server directory of your app.
Usually you call this code from the web browser to make Meteor display the Github OAuth login dialog.
This is not available on the server since its only meant to work on the browser side. This is why you see this error.
You would usually fire Meteor.loginWithGithub() in the event listener for when they click a button or some UI element to begin the login process.
Another thing to keep in mind is Session (Session.get, Session.set, etc) only work on the client too.
To see which methods run where use the Meteor documentation. In the top corner of each method it shows where the code can run: Client, Server or Anywhere.