We're running an instance of Metabase on a App Engine Flexible Custom Runtime with a Dockerfile based on openjdk:8. Currently it allows access on http://[metabase-project].appspot.com/ and https://[metabase-project].appspot.com/. I'd like to force SSL by having all http traffic redirected to https.
The Dockerfile looks something like this:
FROM openjdk:8
ADD https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 ./cloud_sql_proxy
ADD http://downloads.metabase.com/v0.21.1/metabase.jar ./metabase.jar
CMD ./cloud_sql_proxy -instances=$INSTANCE=tcp:$MB_DB_PORT -dir=/cloudsql & java -jar ./metabase.jar
Our app.yaml looks like:
service: metabase
runtime: custom
env: flex
In a normal App Engine app.yaml file, I'd want to add:
handlers:
- url: [something]
secure: always
But in the custom runtime we don't have access to handlers like this. Is there a way to configure the Flexible runtime to perform the redirect for all traffic?
Late to answer, but I had to struggle a lot in order to do this.
I followed various links which mentioned the following code,
app.use(function(req, res, next) {
if(!req.secure) {
return res.redirect(['https://', req.get('Host'), req.url].join(''));
}
next();
});
This might work in other cloud vendors.
But in GCP as rightly mentioned by #zengabor, our app will be running behind an nginx reverse proxy which terminates the SSL connection, we need to check the X-FORWARDED-PROTO which can be done by the following code,
app.use(function(req, res, next) {
if(req.headers['x-forwarded-proto'] && req.headers['x-forwarded-proto'] === "http") {
return res.redirect(['https://', req.get('Host'), req.url].join(''));
}
next();
});
Just adding my answer as after reading #zengabor's code I had to search again on how to achieve it. So above is the readymade code which will work.
App Engine Flex doesn't support handlers, at all:
https://cloud.google.com/appengine/docs/flexible/java/upgrading#appyaml_changes
If you need https:// redirects, you need to do it from within your application. Sorry!
Since your app (env: flex in app.yaml) is running behind an nginx reverse proxy which terminates the SSL connection, you need to check the X-FORWARDED-PROTO header which will be either http or https. If it’s http then you can do the redirect.
This is what worked for me. In my case using Loopback based NodeJS application running in Cloud Sites App Engine flexible environment.
Create a middleware, for example server/middleware/https-redirect.js with the following code:
/**
* Create a middleware to redirect http requests to https
* #param {Object} options Options
* #returns {Function} The express middleware handler
*/
module.exports = function(options) {
options = options || {};
var httpsPort = options.httpsPort || 443;
return function(req, res, next) {
if (req.protocol != 'https' && process.env.NODE_ENV !== 'development') {
var parts = req.get('host').split(':');
var host = parts[0] || '127.0.0.1';
return res.redirect('https://' + host + ':' + httpsPort + req.url);
}
next();
};
};
(based on the step 8 in the post http://www.jonxie.com/blog/2014/11/12/setting-up-loopback-to-use-https-and-ssl-certificates/ but modified to use req.protocol instead of req.secure, also will only redirect if not running in development mode)
Modify the file server/server.js to request:
var httpsRedirect = require('./middleware/https-redirect');
An then, after the boot line:
var httpsPort = app.get('https-port');
app.use(httpsRedirect({httpsPort: httpsPort}));
app.set('trust proxy', true)
Setting app.set('trust proxy', true) will let the req.protocol read the X-Forwarded-Proto header.
References:
http://expressjs.com/es/api.html#req.protocol
http://expressjs.com/en/guide/behind-proxies.html
http://www.jonxie.com/blog/2014/11/12/setting-up-loopback-to-use-https-and-ssl-certificates/
Use the following code
app.use (function (req, res, next) {
var schema = (req.headers['x-forwarded-proto'] || '').toLowerCase();
if (schema === 'https') {
next();
} else {
res.redirect('https://' + req.headers.host + req.url);
}
});
Here's the Node.js Express code I use:
// set the env variable REQUIRE_HTTPS=1 to enable this middleware
.use(function(req, res, next) {
// Redirect HTTP to HTTPS
if (!process.env.REQUIRE_HTTPS) return next();
if (req.headers["x-forwarded-proto"] === "https") return next();
if (req.protocol === "https") return next();
res.redirect(301, `https://${req.hostname}${req.url}`);
})
Put it as the first middleware in your Express app.
This code assumes that you're using standard ports for http/https, as is the case when you're on AppEngine.
Related
This is a personal portfolio page that I'm implementing a contact form within, using nodemailer.
The nodemailer thing is all set from server side. I just need some advice on pointing the client post request to the right place in regards to development and deployment.
I figured as much for setting an environment variable for production vs development and hitting the fetch based upon that. Now I'm just wondering how to go about finding whatever I would put in the fetch for production.
would it be just pointing back into my own app:
fetch(www.mydomain.com/send-email, data) ...
I'm in the Heroku docs trying to figure this out.
Basically, I have a huge blind spot which is hitting a server API from Create React App that isn't launched independently on localhost:3000. I have yet to hit a server route from my client that wasn't served locally on localhost. When I push this to Heroku, I need to have the right route or config, what I need is some advice on how to do this.
I understand proxying somewhat. Just wondering what the steps are to properly hit my server route from an client/server deployed on Heroku as opposed to localhost:3000 during deployment.
When I'm in development I pretty much always axios.post a server that I've spun up on localhost:3000,
which I then hit with something like this coming from my client..
axios.post('localhost:3000/send-email', data)
.then( () => {
setSent(true)
})
.then(() => {
resetForm()
})
.catch((err)=> {
console.log('Message not sent', err)
})
}
...which is then handled by an endpoint on the express server listening on localhost:3000, that looks somewhat like what I've pasted below.
const express =
require('express'),
bodyParser = require('body-parser'),
nodemailer = require('nodemailer'),
cors = require('cors'), path = require('path'),
port = process.env.PORT || 3000, publicPath = path.join(__dirname, '..', 'build');
require('dotenv').config();
const app = express();
app.use(cors());
app.use(express.static(publicPath));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.get('*', (req, res) => {
res.sendFile(path.join(publicPath, 'index.html'));
});
app.post('/send-email', (req, res) => {
console.log('request: ', req.body)
let data = req.body;
let transporter = nodemailer.createTransport({
service: 'gmail',
port: 465,
auth: {
user: process.env.EMAIL,
pass: process.env.PASSWORD
}
});
let mailOptions = {
from: data.email,
to: process.env.EMAIL,
subject: `${data.subject}`,
html: `<p>${data.name}</p>
<p>${data.email}</p>
<p>${data.message}</p>`
};
transporter.sendMail(mailOptions,
(err, res) => {
if(err) {
res.send(err)
} else {
res.send('Success')
}
transporter.close();
});
})
app.listen(port, () => {
console.log(`Server is up on port ${port}!`);
});
folder structure is like this:
main
|-server
|-server.js
|-src
|-components
|-Contact.js
Use the process.env.NODE_ENV variable to differ the environments.
When you run npm start, it is always equal to 'development', when you run npm test it is always equal to 'test', and when you run npm run build to make a production bundle, it is always equal to 'production'. You cannot override NODE_ENV manually.
Therefore, you can create and export a function like
export function apiDomain() {
const production = process.env.NODE_ENV === 'production'
return production ? 'anotherDoman' : 'localhost:3000'
}
or maybe, depending on your requirements
export function apiDomain() {
const { protocol, hostname, origin } = window.location
return hostname === 'localhost' ? `${protocol}//${hostname}` : origin
}
For more details, take a look at https://create-react-app.dev/docs/adding-custom-environment-variables/
I deployed a nodejs web server to GAE flexible environment.
When I access my web server, it using https protocol.
I try to use http protocol, but it seems can not be accessed.
Does GAE support http?
I expect both http and https should work fine.
update 1
app.yaml
runtime: nodejs8
env_variables:
NODE_ENV: production
PORT: 8080
app.flex.yaml
service: get-started-flex
runtime: nodejs
env: flex
env_variables:
NODE_ENV: production
manual_scaling:
instances: 1
resources:
cpu: 1
memory_gb: 0.5
disk_size_gb: 10
server.js
const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const package = require('./package.json');
console.log('version: ', package.version);
console.log('process.env.NODE_ENV: ', process.env.NODE_ENV);
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', (req, res) => {
res.send(`Hello from App Engine! version:${package.version}`);
});
app.get('/submit', (req, res) => {
res.sendFile(path.join(__dirname, '/views/form.html'));
});
app.post('/submit', (req, res) => {
console.log({
name: req.body.name,
message: req.body.message
});
res.send('Thanks for your message!');
});
// Listen to the App Engine-specified port, or 8080 otherwise
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}...`);
});
In App Engine Standard you have the option to state the security of each handler by using the secure tag, however, in Flexible this is not possible.
Besides, in Flexible you can do http and http requests to the handlers but with some restrictions. Note that the application knows what protocol to use depending on the X-Forwarded-Proto header of the request, as you can see in this documentation.
However, I believe you can change this behaviour on the application level of your app. My guess is that you are enforcing https in some part of your application, for example, if you have the line require("https") in your application.
Also, I have found this answer that could help in your case. If you disable SSL security checks in your handler, you might be able to process the requests even if the connection is not secure, but this will make your application insecure.
I'm new to app engine and I'm trying to set it up so that any http requests get redirected to https.
My app.yaml file looks like this. I have script: None in there because if I don't have it there I get some parsing error, but that's not the problem.
env: flex
runtime: nodejs
handlers:
- url: /.*
script: None
secure: always
So right now, if I go to http :// mysite.org it stays on the http version and just shows 'mysite.net' in the url bar. If I go to https :// mysite.org it shows the secured version. If I go to the appspot url that google gave me, the http redirects to the https version just fine. Is there something I'm missing in the app.yaml? This isnt in a custom runtime
Use helmet, secure setting under handlers in app.yaml is depricated in the Google App Engine Latest Release.
https://helmetjs.github.io/docs/hsts/
https://expressjs.com/en/advanced/best-practice-security.html
// Forcing HTTPS connections on Gooogle App Engine Flexible Environment sample app.js
'use strict';
const express = require('express');
const helmet = require('helmet');
const app = express();
const port = process.env.PORT || 8080;
app.disable('x-powered-by');
app.enable('trust proxy');
app.use(helmet.hsts({
maxAge: 31536000,
includeSubDomains: true,
preload: true,
setIf: function (req, res) {
return req.secure;
}
}));
app.get('/', (req, res) => {
if (!req.secure) {
res.redirect(301, "https://" + req.headers.host + req.originalUrl);
}
res.status(200).send("hello, world\n").end();
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
console.log('Press Ctrl+C to quit.');
});
Upgrading to the App Engine Latest Release
The secure setting under handlers is now deprecated for the App Engine flexible environment. If you need SSL redirection, you can update your application code and use the X-Forwarded-Proto header to redirect http traffic.
https://cloud.google.com/appengine/docs/flexible/php/upgrading#appyaml_changes
Forcing HTTPS connections
For security reasons, all applications should encourage clients to connect over https. You can use the Strict-Transport-Security header to instruct the browser to prefer https over http for a given page or an entire domain, for example:
Strict-Transport-Security: max-age=31536000; includeSubDomains
https://cloud.google.com/appengine/docs/flexible/php/how-requests-are-handled
HTTPS and forwarding proxies
With Express.js, use the trust proxy setting
app.set('trust proxy', true);
https://cloud.google.com/appengine/docs/flexible/nodejs/runtime#https_and_forwarding_proxies
Adding the code snippet below to web.xml works just fine on Flexible environment
<security-constraint>
<web-resource-collection>
<web-resource-name>Entire Application</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
my express/node backend app and frontend app used to be separated, the backend run on localhost:3000 and the front end app was started with ng serve and run on localhost:4200
However after I builded the app, and all the frontend stuff got minified and put in /public/ folder, they both run on port 3000. I'm pretty sure they are supposed to work like that. Since i'm using expressJWT middleware to protect some of my routes for visitors without a token, i'm now getting unauthorized 401 when trying to receive the frontend app in the browser.....
As the image shows, i can aparently load the index.html without problems, i can also load all the external hosted sources like boots strap and jquery etc...
but my own .js files is 401. I think it is because of the expressJWT, but i'm not entirely sure. Does anyone know what the problem is and how to solve it ?
It could also be express that is wrong configured?
as you can see i have tried to "ubnlock" the public folder like so:
app.use(expressJWT({secret: secret}).unless({path :
['/','../public/*','/api/authenticate', '/api/register']}))
full express:
const express = require("express")
const bodyParser = require("body-parser")
const logger = require('morgan')
const api = require("./api/api")
const path = require('path')
const secret = require('./models/secrets')
const expressJWT = require('express-jwt')
const cors = require('cors');
const app = express()
app.set("json spaces", 2)
app.use(logger("dev"))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
// CORS middleware
app.use(cors());
app.use(expressJWT({secret: secret}).unless({path : ['/','../public/*','/api/authenticate', '/api/register']}))
app.use("/api/", api)
app.get('*', (req,res) => {
res.sendFile(path.join(__dirname, 'public/index.html'));
});
app.use(function (req, res, next) {
var err = new Error('Not Found')
err.status = 404
next(err)
})
app.use(function (err, req, res, next) {
console.error(err.status)
res.status(err.status || 500)
res.json({ msg: err.message, status: err.status })
})
// Body Parser middleware
app.use(bodyParser.json());
// Set static folder
app.use(express.static(path.join(__dirname, 'public')));
//Call this to initialize mongoose
function initMongoose(dbConnection) {
require("./db/mongooseConnect")(dbConnection)
}
app.initMongoose = initMongoose
module.exports = app
use express static middleware before using jwt.
example:
app
.use(express.static(STATIC_PATH))
.use(
exjwt({ secret: SECRET }).unless({
path: [/\/api\/v1\/identify/]
})
)
I am not 100% sure but I guess you have problem with this line.
app.use(expressJWT({secret: secret}).unless({path : ['/','../public/*','/api/authenticate', '/api/register']}))
app.use("/api/", api)`
Try this. putting single / should solve.
app.use('/api',expressJwt({secret: secret}).unless({path: ['/','/public/*','/api/authenticate', '/api/register']});
Recently I started trying to get into Node.js/React and am using this tutorial https://auth0.com/blog/build-a-chat-app-with-react/.
However, even though I have followed the steps, I seem to be encountering an error. My page is displayed as "Cannot GET /" after hitting yarn start. I've found answers here NodeJS w/Express Error: Cannot GET /
and here "Cannot GET /" with Connect on Node.js
Neither of these made sense to me though, as my code seems to differ from theirs slightly. I understand that the page doesnt know where to look for my GET request, and therefore what information to pull, but im not sure how to fix it.
The code in question, GET request at the end. Thanks.
// server.js
const express = require('express');
const path = require('path');
const bodyParser = require("body-parser");
const app = express();
const Pusher = require('pusher');
//initialize Pusher with your appId, key and secret
const pusher = new Pusher({
appId: 'APP_ID',
key: 'APP_KEY',
secret: 'SECRET',
cluster: 'YOUR CLUSTER',
encrypted: true
});
// Body parser middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
// API route which the chat messages will be sent to
app.post('/message/send', (req, res) => {
// 'private' is prefixed to indicate that this is a private channel
pusher.trigger( 'private-reactchat', 'messages', {
message: req.body.message,
username: req.body.username
});
res.sendStatus(200);
});
// API route used by Pusher as a way of authenticating users
app.post('/pusher/auth', (req, res) => {
const socketId = req.body.socket_id;
const channel = req.body.channel_name;
const auth = pusher.authenticate(socketId, channel);
res.send(auth);
});
// Set port to be used by Node.js
app.set('port', (process.env.PORT || 5000));
app.listen(app.get('port'), function(req, res) {
console.log('Node app is running on port', app.get('port'));
});
I assume that you are sending get request to localhost:5000 which isn't defined in your server so it can't send response back, because you are using react you want to send request on port on which react is running(3000 by default) so try accessing using localhost:3000 and it should work.
You need to have the route available in the code. Try reading up on Express Basic Routing
Try the below and take it from there. I'm assuming that you're running on port 5000, if not, point to whatever port is set in process.env.PORT
app.get('/', function (req, res) {
res.send('hello world');
})