במאמרים הקודמים על דוקר הראיתי והדגמתי איך מרימים קונטיינרים של דוקר על מנת להרים וורדפרס. במאמר הזה אני מדגים איך אני מרים מכונת node\express ומכין אותה לעבודה עם https כדי לדמות לחלוטין שרת node. כמובן שאפשר להשתמש בעקרונות האלו כדי להקים כל שרת אחר ולאפשר לו לעבוד עם https.
אז קודם כל, בואו ונרים שרת express. איך עושים את זה? עם קובץ compose שמכיל קונטיינר. איזה קונטיינר? ובכן. בחרתח באחד פופולרי של אקספרס וביצעתי בו התאמות מסוימות – כמו להעיף את mongo למשל שאותו אני לא צריך.
version: '2'
services:
myapp:
tty: true # Enables debugging capabilities when attached to this container.
image: 'bitnami/express:latest'
labels:
kompose.service.type: nodeport
command: npm run development
environment:
- PORT=3000
- NODE_ENV=development
- SKIP_DB_WAIT=0
- SKIP_DB_MIGRATION=0
- SKIP_NPM_INSTALL=0
- SKIP_BOWER_INSTALL=0
ports:
- 80:3000
- 443:8000
- 3000:3000
volumes:
- .:/app
כדאי לשים לכמה דברים חשובים פה. הדבר החשוב ביותר הוא שיש לי כאן מיפויים ב-ports. יש לנו את המיפוי והכנסתי את 443 שימופה לפורט 8000 שהוא הפורט של האפליקציה שלי. כשיש לנו node\express. הוא עובד מאחורי נתב שמעביר את כל התנועה מהפורטים השונים (80, 8080 ו-443) לפורט 3000 או לכל פורט אחר שבחרנו. פורט 443 הוא חשוב במיוחד כי הוא הפורט של הפרוטוקול המאובטח HTTPS ואני מפנה אותו לפורט הפנימי של 8000.
אני אשמור את הקובץ הזה בתיקיה ריקה ואריץ בטרמינל של docker:
docker-compose up
האפליקציה תיבנה הישר בתיקית app ואני בעצם אוכל לראות אותה אם אכנס ל-IP. אבל אני רוצה תמיכה מלאה ב-https. איך עושים את זה? מבחינת דוקר עשיתי את כל מה שאני יכול לעשות. עכשיו צריך לטפל באפליקציה.
בניגוד ל-Apache\NginX וחבריהם שיודעים לעבוד עם HTTPS ישר מהקופסה, עם node אני צריך לגרום ל-https לעבוד. איך? קודם כל אני צריך ליצור לעצמי מפתח.
למי שלא יודע, תקשורת https עובדת עם RSA (החיבור הראשוני עובד כך, התקשורת עצמה עובדת בהצפנה סימטרית) שמחייבת מפתח פומבי ומפתח פרטי. אני צריך ליצור אותם. זה לא כזה מסובך. אני אצור תיקיה בפרויקט שלי שתקרא sslcert ואריץ בה את הפקודה הזו:
openssl req -newkey rsa:2048 -new -nodes -keyout key.pem -out csr.pem
מה שהפקודה הזו עושה זה ליצור מפתח הצפנה פרטי ופומבי. מפתח ההצפנה הפרטי הוא key.pem ואם תפתחו אותו תוכלו לראות שיש בו private key. מפתח ההצפנה הפומבי הוא csr.pem שמשמש אותנו לסרטיפיקייט. זה כל מה שאנחנו צריכים.
עכשיו רק צריך לגרום לשרת לעבוד עם https וגם זה הרבה יותר פשוט. יש כמה גישות שאפשר לעבוד איתן. אנו נתמקד בגישה הפשוטה יותר, ליצור בקובץ האתחול שרת https נוסף שזהה לחלוטין לזה של http. הוא מאזין ל-8000 ויתפוס את כל התקשורת המאובטחת. נשמע כמו הגיהנום? ממש לא. ככה זה נראה:
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var appSecured = require('../app'); // Creating appSecured
var debug = require('debug')('app:server');
var http = require('http');
var https = require('https'); // require native node's native https module
var fs = require('fs');
/**
* Get port from environment and store in Express.
*/
var privateKey = fs.readFileSync('/app/sslcert/key.pem', 'utf8');
var certificate = fs.readFileSync('/app/sslcert/server.crt', 'utf8');
/**
* Prepare the credentials
*/
var credentials = {key: privateKey, cert: certificate};
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
var portSecured = normalizePort(process.env.PORT_SECURED || '8000'); // defining port secured
app.set('port', port);
appSecured.set('port', portSecured); // appSecured listen to 8000 port
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Create HTTPS server.
*/
var serverSecured = https.createServer(credentials, appSecured);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* HTTPS Listen on provided port, on all network interfaces.
*/
serverSecured.listen(portSecured);
serverSecured.on('error', onError);
serverSecured.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
ממש לא צריך להבהל. זה קובץ ההגדרות הרגיל שמגיע יחד עם הקונטיינר והוספתי לתוכו פשוט את הקוד שיוצר את השרת המאובטח שמאזין לפורט 800. זה כל הקוד שהוספתי:
var appSecured = require('../app'); // Creating appSecured
var https = require('https'); // require native node's native https module
var fs = require('fs');
/**
* Get port from environment and store in Express.
*/
var privateKey = fs.readFileSync('/app/sslcert/key.pem', 'utf8');
var certificate = fs.readFileSync('/app/sslcert/server.crt', 'utf8');
/**
* Prepare the credentials
*/
var credentials = {key: privateKey, cert: certificate};
var portSecured = normalizePort(process.env.PORT_SECURED || '8000'); // defining port secured
appSecured.set('port', portSecured); // appSecured listen to 8000 port
/**
* Create HTTPS server.
*/
var serverSecured = https.createServer(credentials, appSecured);
/**
* HTTPS Listen on provided port, on all network interfaces.
*/
serverSecured.listen(portSecured);
serverSecured.on('error', onError);
serverSecured.on('listening', onListening);
כשאני אכנס לכתובת של המכונה שלי עם https, אני אקבל שגיאת https כמובן שמראה שהכל תקין. למה? כי יש חיבור. מן הסתם הדפדפן שלי לא מכיר ולא סומך על הסקרטיפקט שלי, אבל אם אני אעשה proceed אני יכול להתחיל לעבוד.
אפשר לגרום להודעה המרגיזה להעלם ולאפשר לדפדפן שלי לקבל את האישור הזה. אבל זה סיפור אחר ולמאמר אחר.
חשוב לי להדגיש משהו חשוב: המאמר הזה נוגע אך ורק להתקנה של אתר/שרת בסביבה המקומית שלכם. לא לפרודקשן.
תגובה אחת
Hi,
I'm writing in English because of 'right to left' issue, I was trying to build a docker by your example but for some reason I get the following error:
myapp_1 | Error: EACCES: permission denied, mkdir '/app/public'
myapp_1 | at Object.fs.mkdirSync (fs.js:895:18)
myapp_1 | at Function.sync (/opt/bitnami/node/lib/node_modules/express/node_modules/mkdirp/index.js:71:13)
myapp_1 | at mkdir (/opt/bitnami/node/lib/node_modules/express/bin/express-cli.js:498:10)
myapp_1 | at createApplication (/opt/bitnami/node/lib/node_modules/express/bin/express-cli.js:184:3)
myapp_1 | at /opt/bitnami/node/lib/node_modules/express/bin/express-cli.js:472:7
myapp_1 | at /opt/bitnami/node/lib/node_modules/express/bin/express-cli.js:384:5
myapp_1 | at FSReqWrap.oncomplete (fs.js:114:15)
internetisrael_myapp_1 exited with code 1
Why does it try to create this folder there, I've looked in the code, and haven't found this folder creation. Can you please help me?