Google app engine removes session cookies when authorizing socket.io connection - google-app-engine

I am trying to get the sessionID of a user from express-session when the user opens up a WebSocket connection. The code below shows how this is done. The problem is that when I deploy my server to google app engine, I can no longer see the session cookie when authorizing the socket connection. It works fine when running locally, so google app engine must be doing something to remove the cookie? I'm not too familiar with the google app engine so any help would be greatly appreciated.
I've tried many different solutions I found online such as passport.socketio from npm, many of which I believe implement the same solution that I ended up writing myself.
Edit 1:
To be more specific, data.headers.cookies contains an io, connect.sid and express.sid cookie when running from localhost. When running on the google app engine, it only contains the io cookie. (End of edit)
Edit 2:
I thought it could be an issue with having different instances of my server due to google's automatic scaling, so I've changed my setting in app.yaml to ensure that wont happen:
network:
session_affinity: true
manual_scaling:
instances: 1
(End of edit)
Code:
var session = require('express-session');
var MemoryStore = require('memorystore')(session);
var store = new MemoryStore({
checkPeriod: 86400000
}); // Likely to switch to RedisStore
app.use(session({
store: store,
secret: 'jpcs-0001080900DRXPXL',
saveUninitialized: false,
resave: true,
key: 'express.sid'
}));
// ...
io.set('authorization', (data, accept) => {
if (data && data.headers && data.headers.cookie) {
console.log('COOKIES:');
console.log(data.headers.cookie);
cookies_str = data.headers.cookie;
cookies_arr = cookies_str.split(';');
cookies = {};
for (index in cookies_arr) {
cookie = cookies_arr[index].split('=');
if (cookie.length != 2) continue;
key = cookie[0].replace(/ /g,'');
val = cookie[1];
cookies[key] = val;
}
if (!cookies['express.sid']) accept('User not signed in', null);
// HERE IS MY PROBLEM:
// When running from google app engine cookies['express.sid'] is null
sessionId = cookies['express.sid'].split('.')[0].substring(4);
data.sessionId = sessionId;
store.get(sessionId, (err, session) => {
if (!err && session) {
data.session = session;
accept(null, true);
}
else if (err) accept('Could not get session', false);
else if (!session) accept('Could not get session', false)
});
}
accept('Could not get session', false);
});
So I expect cookies['express.sid'] to contain the sessionID in it, but on google app engine it is null.
App.yaml
runtime: nodejs10 # For Node.js 8, use runtime: nodejs8
#instance_class: F2
#env_variables:
#BUCKET_NAME: "example-gcs-bucket"
handlers:
- url: .*
secure: always
redirect_http_response_code: 301
script: auto
network:
session_affinity: true
manual_scaling:
instances: 1

It's more likely that your application cannot retrieve the user from the session store when running on GAE due to a network or app configuration. Some more info on which session store you are using, the configuration for it in express, and the app.yaml would be more helpful in solving this. Its unlikely that GAE would be removing the cookie from the HTTP response headers.
I found that in order to use redis with express/express-session I needed to set the network name in app.yaml.
network:
name: default
Unfortunately it doesn't seem like you can use this for standard environments, so I also had to change:
runtime: nodejs
env: flex
And also needed to specify an engine in package.json when switching to a flexible environment.
"engines": {
"node": "10.x"
}

Related

(Node.js / Express) MongoDB connect different Database for different Environment (Google App Engine)

Goal:
I am currently trying to set up a staging and demo environment for my application. I am using Google App Engine and deploy to a different URL and App Engine project for all of them.
The Idea is that every Instance connects to their own URL based on the environment.
Problem:
The problem currently is that even though I can see in console logs that the Apps do get the correct Environment Variables every instance does try to read and write to the production database.
The Console even logs "Connected to Database: Demo / Staging on the demo environment or staging environment. Still I get the collections and data from the production database.
This is not true for running the app locally where it does connect to the staging database. (The environment variable for NODE_ENV is undefined when run locally)
Code:
My production instance is deploying to a google app engine project using this configuration.
production.yaml
runtime: nodejs10
env: standard
instance_class: F1
handlers:
- url: /favicon\.ico
static_files: favicon.ico
upload: favicon.ico
- url: /static
static_dir: public
- url: /.*
secure: always
redirect_http_response_code: 301
script: auto
automatic_scaling:
min_idle_instances: 1
max_idle_instances: 3
min_instances: 1
min_pending_latency: automatic
max_pending_latency: automatic
network: {}
env_variables:
NODE_ENV: production
LOGIN_PAGE: https://login.myproductionurl.com/
API_ENDPOINT: https://myproductionurl.appspot.com/
PORT: 8080
Demo and Staging have a similar set up with the change of the environment variables set for their case.
demo.yaml
...
env_variables:
NODE_ENV: demo
LOGIN_PAGE: https://login.mydemourl.com/
API_ENDPOINT: https://mydemourl.appspot.com/
PORT: 8080
staging.yaml
...
env_variables:
NODE_ENV: staging
LOGIN_PAGE: https://login.mystagingurl.com/
API_ENDPOINT: https://mystagingurl.appspot.com/
PORT: 8080
In the Express server itself I then try to connect to a different MongoDB database within Cloud Atlas based on the environment variable.
Database connection in server.js
if (process.env.NODE_ENV === "demo") {
console.log("Connecting to Database: Demo");
mongoose.connect(
"mongodb+srv://(myusername):(mypassword)#(myucluster).mongodb.net/demo",
{ useNewUrlParser: true }
);
} else if (process.env.NODE_ENV === "production") {
console.log("Connecting to Database: Production");
mongoose.connect(
"mongodb+srv://(myusername):(mypassword)#(myucluster).mongodb.net/production",
{ useNewUrlParser: true }
);
} else {
console.log("Connecting to Database: Staging");
mongoose.connect(
"mongodb+srv://(myusername):(mypassword)#(myucluster).mongodb.net/staging",
{ useNewUrlParser: true }
);
}
const db = mongoose.connection;
db.on("error", (error) => console.error(error));
db.once("open", () => console.log("connected to database"));
server.set("database", db);
Additional Info:
There are no connections specified anywhere else in the project.
Seems like DB Name for clusters is different as mongoose doc says
You should use, instead of dbname in connection string, use the dbName option:
mongoose.connect('mongodb+srv://(un):(pw)#(cluster).mongodb.net/staging', { dbName: 'staging' });
Related answer is Fail to connect Mongoose to Atlas

Proxy outbound API calls from Google App Engine via Google Compute Engine running Squid

I'm trying to proxy outbound API calls made from a Google App Engine application via a Google Compute Engine server VM instance running Squid proxy server.
The aim is that the REST api calls will all be made from a static ip address so that the 3rd party API will be able to identify and permit the calls via their firewall.
I have read and followed the instructions on this post:
connect Google App Engine and Google Compute Engine
I have managed to do the following so far:
Created a Google cloud compute VM and successfully assigned it a static external IP address.
Created a Serverless VPC access connector successfully (all resources are located in the same GAE region).
Added the vpc_access_connector name to my app.yaml in the Google App Engine project (which runs on Node.js).
Deployed the app using gcloud beta, with api calls being targeted towards the internal IP address of the proxy server, using the correct Squid default port (3128).
On issuing a request from the GAE app, I can see from the server logs that the correct IP address and port are being attempted but get the following error: "Error: tunneling socket could not be established, cause=connect ECONNREFUSED [my-internal-ip-address]:3128"
I've also tried running a curl command from the cloud shell interface, but the request times out every time.
If anyone could help solve this issue, I will be very grateful.
Here is a possible example of how to proxy outbound HTTP requests from an App Engine Standard application on NodeJS runtime via a Compute Engine VM running Squid, based on a slight modification of the available Google Cloud Platform documentation 1 2 and Quickstarts 3.
1. Create a Serverless VPC Access conector: Basically follow 2 to create the connector. After updating the gcloud components and enabling the Serverless VPC Access API on your project running the following command should suffice:
gcloud compute networks vpc-access connectors create [CONNECTOR_NAME] \
--network [VPC_NETWORK] \
--region [REGION] \
--range [IP_RANGE]
2. Create a Compute Engine VM to use as proxy: Basically follow 1 to set up a Squid proxy server:
a. Reserve a static external IP address and assign it to a Compute Engine VM.
b. Add a Firewall rule to allow traffic on Squid's default port: 3128. This command should work if you are using the default VPC network: gcloud compute firewall-rules create [FIREWALL_RULE_NAME] --network default --allow tcp:3128
c. Install Squid on the VM with the following command sudo apt-get install squid3.
d. Enable the acl localnet src entries in the Squid config files for the VPC Access connector:
sudo sed -i 's:#\(http_access allow localnet\):\1:' /etc/squid/squid.conf
sudo sed -i 's:#\(acl localnet src [IP_RANGE]/28.*\):\1:' /etc/squid/squid.conf
For example: if you used 10.8.0.0 as value for the [IP_RANGE] field for creating the connector, it should look something like sudo sed -i 's:#\(acl localnet src 10.8.0.0/28.*\):\1:' /etc/squid/squid.conf
e. Start the server with sudo service squid start
3. Modifications on App Engine application: Based on the Quickstart for Node.js modify the following files in order to create an application that crawls a webpage using the request-promise library and displays the HTML of the webpage. The request is send to the webpage using the VPC Access connector and the VM as a proxy with the modifications of the app.yaml and app.js files.
a. package.json
...
"test": "mocha --exit test/*.test.js"
},
"dependencies": {
"express": "^4.16.3",
"request": "^2.88.0",
"request-promise": "^4.2.5"
},
"devDependencies": {
"mocha": "^7.0.0",
...
b. app.js
'use strict';
// [START gae_node_request_example]
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res
.status(200)
.send('Hello, world!')
.end();
});
//Add a handler to test the web crawler
app.get('/test', (req, res) => {
var request = require('request-promise');
request('http://www.input-your-awesome-website.com')
.then(function (htmlString) {
res.send(htmlString)
.end();
})
.catch(function (err) {
res.send("Crawling Failed...")
.end();
});
});
// Start the server
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`App listening on port ${PORT}`);
console.log('Press Ctrl+C to quit.');
});
// [END gae_node_request_example]
c. app.yaml
runtime: nodejs10
vpc_access_connector:
name: "projects/[PROJECT]/locations/[REGION]/connectors/[CONNECTOR_NAME]"
env_variables:
HTTP_PROXY: "http://[Compute-Engine-IP-Address]:3128"
HTTPS_PROXY: "http://[Compute-Engine-IP-Address]:3128"
Each time you go to the /test handler monitor that the requests go through the proxy by using sudo tail -f /var/log/squid/access.log command from the VM and checking the changes on the logs.
Notes: The connector, application and VM need to be on the same region to work and these are the supported regions for the connector.

Google AppEngine Getting 403 forbidden trying to update cron.yaml

I am following the docs on how to backup datastore using AppEngine.
I am performing a gcloud app deploy cron.yaml command on a GCE VM that is meant to update a cronjob in AppEngine. the GCE VM and AppEngine cron are in the same project, and I have granted AppEngine admin to the GCE VM via a default Service Account. When I run this on my local machine, it updates fine. However on the GCE instance, thats where issues arise
Here are the files
app.yaml
runtime: python27
api_version: 1
threadsafe: true
service: cloud-datastore-admin
libraries:
- name: webapp2
version: "latest"
handlers:
- url: /cloud-datastore-export
script: cloud_datastore_admin.app
login: admin
cron.yaml
cron:
- description: "Daily Cloud Datastore Export"
url: /cloud-datastore-export?namespace_id=&output_url_prefix=gs://<my-project-id>-bucket
target: cloud-datastore-admin
schedule: every 24 hours
cloud_datastore_export.yaml
import datetime
import httplib
import json
import logging
import webapp2
from google.appengine.api import app_identity
from google.appengine.api import urlfetch
class Export(webapp2.RequestHandler):
def get(self):
access_token, _ = app_identity.get_access_token(
'https://www.googleapis.com/auth/datastore')
app_id = app_identity.get_application_id()
timestamp = datetime.datetime.now().strftime('%Y%m%d-%H%M%S')
output_url_prefix = self.request.get('output_url_prefix')
assert output_url_prefix and output_url_prefix.startswith('gs://')
if '/' not in output_url_prefix[5:]:
# Only a bucket name has been provided - no prefix or trailing slash
output_url_prefix += '/' + timestamp
else:
output_url_prefix += timestamp
entity_filter = {
'kinds': self.request.get_all('kind'),
'namespace_ids': self.request.get_all('namespace_id')
}
request = {
'project_id': app_id,
'output_url_prefix': output_url_prefix,
'entity_filter': entity_filter
}
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + access_token
}
url = 'https://datastore.googleapis.com/v1/projects/%s:export' % app_id
try:
result = urlfetch.fetch(
url=url,
payload=json.dumps(request),
method=urlfetch.POST,
deadline=60,
headers=headers)
if result.status_code == httplib.OK:
logging.info(result.content)
elif result.status_code >= 500:
logging.error(result.content)
else:
logging.warning(result.content)
self.response.status_int = result.status_code
except urlfetch.Error:
logging.exception('Failed to initiate export.')
self.response.status_int = httplib.INTERNAL_SERVER_ERROR
app = webapp2.WSGIApplication(
[
('/cloud-datastore-export', Export),
], debug=True)
The Error I'm getting is
Configurations to update:
descriptor: [/usr/local/sbin/pluto/<my-project-id>/datastore/cron.yaml]
type: [cron jobs]
target project: [<my-project-id>]
Do you want to continue (Y/n)?
Updating config [cron]...
failed.
ERROR: (gcloud.app.deploy) Server responded with code [403]:
Forbidden Unexpected HTTP status 403.
You do not have permission to modify this app (app_id=u'e~<my-project-id>').
I have checked other posts related to this, however they seem to deal with an old version/deployment of appengine
Service Accounts!
From Deploying using IAM roles:
To grant a user account the ability to deploy to App Engine:
Click Add member to add the user account to the project and then select all of the roles for that account by using the dropdown menu:
Required roles to allow an account to deploy to App Engine:
a. Set the one of the following roles:
Use the App Engine > App Engine Deployer role to allow the account to deploy a version of an app.
To also allow the dos.yaml or dispatch.yaml files to be deployed with an app, use the App Engine > App Engine Admin role
instead.
The user account now has adequate permission to use the Admin API to deploy apps.
b. To allow use of App Engine tooling to deploy apps, you must also give the user account the Storage > Storage Admin role
so that the tooling has permission to upload to Cloud Storage.
Optional. Give the user account the following roles to grant permission for uploading additional configuration files:
Cloud Scheduler > Cloud Scheduler Admin role: Permissions for uploading cron.yaml files.
Potentially of interest:
Deployments with predefined roles
Predefined roles comparison matrix
Okay after some tinkering. I added the project editor role to the service account linked to the GCE instance running my server. I am not fully aware if this is the role with least priviledge to enable this to work.

How to stop Google AppEngine instances when not being used?

We are developing a small e-commerce project for which we are using GAE as our web service backend but it's draining our financial resources. We studied our current infrastructure and how it was billed. That shows us that it's because of the instances we are using.
We have 7 services running which is using 12 instances as a whole.
Want to understand how to stop Google App Engine instances when not being used as it's the key contributor to our billing.
Services
UpdateFeed.yaml
application: ...
module: updatecategoryfeed
version: uno
runtime: python27
api_version: 1
instance_class: F1
threadsafe: true
automatic_scaling:
min_idle_instances: 6
max_idle_instances: automatic # default value
min_pending_latency: 30ms # default value
max_pending_latency: automatic
max_concurrent_requests: 50
handlers:
- url: /.*
script: FeedModule.FeedBuilder.update_category_feed
libraries:
- name: endpoints
version: 1.0
- name: webapp2
version: "latest"
- name: ssl
version: latest
All other services following the same structure. We have a total of 7 active services.
UPDATE 2
We decreased the auto scaling values as per our project requirements which min idle instances to 0 and max idle instance to be 1. The price dropped drastically. But we are still looking for the answer on how to stop an instance when not being used. For ex. the below graph shows an instance started on its own without any activity and is being billed.
Drop your min_idle_instances configuration in the automatic_scaling section - those configs effectively represent instances running at all times - also called resident instances for that reason.
Their role is not to handle traffic regularly. They only handle overflow traffic for short periods of time when the traffic exceeds the capacity of the running dynamic instances to keep the latencies low, while GAE spins up additional dynamic instances (which are the ones actually handling the bulk of the traffic).
Just in case it's not clear - resident instances aren't going away - it is their job to remain alive even if there is no activity on the site.
You can start and stop GAE services, configuring to manual_scaling and use GAE API to start and stop.
const proj = '' // project id here
const serv = 'default' // or your service id
const vers = '' // version id here
const {google} = require('googleapis')
const appengine = google.appengine ({version: 'v1', auth: auth})
// To retrieve Status:
appengine.apps.services.versions.get ({
appsId: proj,
servicesId: serv,
versionsId: vers,
view: 'BASIC',
})
.then ()
.catch ()
// To Change Status:
newStatus = 'STOPPED' // or 'SERVING'
appengine.apps.services.versions.patch ({
appsId: proj,
servicesId: serv,
versionsId: vers,
updateMask: 'serving_status',
requestBody: {
serving_status: newStatus
}
})
.then ()
.catch ()

Google App Engine service (module) not starting, and flooding 404's to /_ah/start

I'm refactoring an existing codebase. I switched from using the appcfg.py to using the gcloud command, which seemed to go fine. Our entire codebase was running on one default frontend instance, which I'm now trying to break into services. To start, I created one "worker" backend service, and I'm using a cron job to test.
I can see the worker in the console, but no instance is started. The logs for that service are rapidly flooded with 404's to /_ah/start. I've tried manual and basic scaling. The documentation states that it's okay not to have a startup script, and that a 404 at that endpoint is considered success. However, the instance is not starting.
Logs
worker.yaml
service: worker
runtime: python27
api_version: 1
instance_class: B2
manual_scaling:
instances: 1
threadsafe: false
handlers:
- url: /work/.*
script: worker.app
secure: always
login: admin
worker.py
import webapp2
import handlers
config = {
#...
}
app = webapp2.WSGIApplication([
webapp2.Route(
'/work/test<:/?>',
handlers.Test,
methods=['GET'],
),
], debug=True, config=config)
dispatch.yaml
dispatch:
- url: "*/work/*"
module: worker

Resources