From 0aca9935b60889948757cd5019c2e7153cf2f862 Mon Sep 17 00:00:00 2001 From: Iristyle Date: Thu, 7 Feb 2013 15:44:13 -0500 Subject: [PATCH] Add support for https protocol. Closes GH-15. - Added 'protocol' with default of 'http' to options - grunt.fatal if protocol is not 'http' or 'https' - Added set of self-signed certs with passphrase 'grunt' to be used by default / with tests - Updated docs with info on how to configure custom HTTPS --- Gruntfile.js | 18 ++++++++++++ README.md | 62 ++++++++++++++++++++++++++++++++++++++-- docs/connect-examples.md | 52 +++++++++++++++++++++++++++++++++ docs/connect-options.md | 6 ++++ package.json | 2 +- tasks/certs/ca.crt | 21 ++++++++++++++ tasks/certs/ca.key | 30 +++++++++++++++++++ tasks/certs/ca.srl | 1 + tasks/certs/server.crt | 16 +++++++++++ tasks/certs/server.csr | 11 +++++++ tasks/certs/server.key | 15 ++++++++++ tasks/connect.js | 24 ++++++++++++++-- test/connect_test.js | 8 ++++-- 13 files changed, 259 insertions(+), 7 deletions(-) create mode 100644 tasks/certs/ca.crt create mode 100644 tasks/certs/ca.key create mode 100644 tasks/certs/ca.srl create mode 100644 tasks/certs/server.crt create mode 100644 tasks/certs/server.csr create mode 100644 tasks/certs/server.key diff --git a/Gruntfile.js b/Gruntfile.js index ff7226b..77e9594 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -7,6 +7,8 @@ */ 'use strict'; +var path = require('path'); +var certs = path.join(__dirname, 'tasks', 'certs'); module.exports = function(grunt) { grunt.initConfig({ @@ -36,6 +38,22 @@ module.exports = function(grunt) { port: 9000, }, }, + custom_https: { + options: { + port: 8001, + protocol: 'https', + } + }, + custom_https_certs: { + options: { + port: 8002, + protocol: 'https', + key: grunt.file.read(path.join(certs, 'server.key')).toString(), + cert: grunt.file.read(path.join(certs, 'server.crt')).toString(), + ca: grunt.file.read(path.join(certs, 'ca.crt')).toString(), + passphrase: 'grunt', + } + }, custom_middleware: { options: { port: 9001, diff --git a/README.md b/README.md index 90b3ca2..464a9a3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# grunt-contrib-connect [![Build Status](https://secure.travis-ci.org/gruntjs/grunt-contrib-connect.png?branch=master)](http://travis-ci.org/gruntjs/grunt-contrib-connect) +# grunt-contrib-connect v0.3.0 [![Build Status](https://travis-ci.org/gruntjs/grunt-contrib-connect.png?branch=master)](https://travis-ci.org/gruntjs/grunt-contrib-connect) > Start a connect web server. @@ -36,6 +36,12 @@ Default: `8000` The port on which the webserver will respond. The task will fail if the specified port is already in use. You can use the special values `0` or `'?'` to use a system-assigned port. +#### protocol +Type: `String` +Default: `'http'` + +May be `'http'` or `'https'`. + #### hostname Type: `String` Default: `'localhost'` @@ -150,6 +156,58 @@ grunt.registerTask('connect', 'Start a custom static web server.', function() { }); ``` +#### Support for HTTPS + +A default certificate authority, certificate and key file are provided and pre- +configured for use when `protocol` has been set to `https`. + +NOTE: The passphrase used on the files is `grunt` + +####### Advanced HTTPS config + +If the default certificate setup is unsuitable for your environment, OpenSSL +can be used to create a set of self-signed certificates with a local ca root. + +```shell +### Generate the CA key +### Set a passphrase and remember what it is +openssl genrsa -des3 -out ca.key 2048 +### Generate a CA root +openssl req -new -x509 -days 3650 -key ca.key -out ca.crt + +### Generate the server key +openssl genrsa -out server.key 1024 +### Generate the request to the self-signed CA root +openssl req -new -key server.key -out server.csr +### Generate the server certificate +openssl x509 -req -in server.csr -out server.crt -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 +``` + +For more details on the various options that can be set when configuring SSL, +please see the Node documentation for [TLS][]. + +Grunt configuration would become + +```javascript +// Project configuration. +grunt.initConfig({ + connect: { + server: { + options: { + protocol: 'https', + port: 8443, + key: grunt.file.read('server.key').toString(), + cert: grunt.file.read('server.crt').toString(), + ca: grunt.file.read('ca.crt').toString(), + passphrase: 'grunt', + }, + }, + }, +}); +``` + +[TLS]: http://nodejs.org/api/tls.html#tls_tls_createserver_options_secureconnectionlistener + ## Release History @@ -165,4 +223,4 @@ grunt.registerTask('connect', 'Start a custom static web server.', function() { Task submitted by ["Cowboy" Ben Alman](http://benalman.com) -*This file was generated on Thu Apr 11 2013 00:01:44.* +*This file was generated on Sun Sep 01 2013 19:52:23.* diff --git a/docs/connect-examples.md b/docs/connect-examples.md index 6aa8808..7cfb40a 100644 --- a/docs/connect-examples.md +++ b/docs/connect-examples.md @@ -69,3 +69,55 @@ grunt.registerTask('connect', 'Start a custom static web server.', function() { connect(connect.static('www-root')).listen(9001); }); ``` + +## Support for HTTPS + +A default certificate authority, certificate and key file are provided and pre- +configured for use when `protocol` has been set to `https`. + +NOTE: The passphrase used on the files is `grunt` + +##### Advanced HTTPS config + +If the default certificate setup is unsuitable for your environment, OpenSSL +can be used to create a set of self-signed certificates with a local ca root. + +```shell +# Generate the CA key +# Set a passphrase and remember what it is +openssl genrsa -des3 -out ca.key 2048 +# Generate a CA root +openssl req -new -x509 -days 3650 -key ca.key -out ca.crt + +# Generate the server key +openssl genrsa -out server.key 1024 +# Generate the request to the self-signed CA root +openssl req -new -key server.key -out server.csr +# Generate the server certificate +openssl x509 -req -in server.csr -out server.crt -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 +``` + +For more details on the various options that can be set when configuring SSL, +please see the Node documentation for [TLS][]. + +Grunt configuration would become + +```javascript +// Project configuration. +grunt.initConfig({ + connect: { + server: { + options: { + protocol: 'https', + port: 8443, + key: grunt.file.read('server.key').toString(), + cert: grunt.file.read('server.crt').toString(), + ca: grunt.file.read('ca.crt').toString(), + passphrase: 'grunt', + }, + }, + }, +}); +``` + +[TLS]: http://nodejs.org/api/tls.html#tls_tls_createserver_options_secureconnectionlistener diff --git a/docs/connect-options.md b/docs/connect-options.md index 8d0c79d..6b3cbf4 100644 --- a/docs/connect-options.md +++ b/docs/connect-options.md @@ -6,6 +6,12 @@ Default: `8000` The port on which the webserver will respond. The task will fail if the specified port is already in use. You can use the special values `0` or `'?'` to use a system-assigned port. +## protocol +Type: `String` +Default: `'http'` + +May be `'http'` or `'https'`. + ## hostname Type: `String` Default: `'localhost'` diff --git a/package.json b/package.json index d15ed85..0dff3f4 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "test": "grunt test" }, "dependencies": { - "connect": "~2.7.3" + "connect": "~2.7.11" }, "devDependencies": { "grunt-contrib-jshint": "~0.2.0", diff --git a/tasks/certs/ca.crt b/tasks/certs/ca.crt new file mode 100644 index 0000000..a11cf8a --- /dev/null +++ b/tasks/certs/ca.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDczCCAlugAwIBAgIJAKdGg4YIc6/TMA0GCSqGSIb3DQEBBQUAMFAxCzAJBgNV +BAYTAlVTMRMwEQYDVQQIDApTb21lLVN0YXRlMRAwDgYDVQQKDAdHcnVudEpTMRow +GAYDVQQDDBFodHRwczovL2xvY2FsaG9zdDAeFw0xMzAyMDcyMDI5NDVaFw0yMzAy +MDUyMDI5NDVaMFAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApTb21lLVN0YXRlMRAw +DgYDVQQKDAdHcnVudEpTMRowGAYDVQQDDBFodHRwczovL2xvY2FsaG9zdDCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANY8SGJgaPsW/MfKyXx5VgtdNM4X +UJCYK5EC6hjBOi8CT46l85vM5c6dall5IfGZPvJtxydM+bnevlhmgBr28Q0Z+ury +U/sgzCn9fs4nqJPCX62jxYJmJDDLxnqDWBq+TMyX1keGpfJ9V9TOd8S/PkKfa8AN +aglB4Nf4USEbcERIwKt5nLZA44n62E98zm78YKrOdkx/hB8nwOfiphVkyUnDGIXj +fTmWduoVEf4bLq91Wk6lgKzVq26IxFQRP5T59kX19XH0j/s0a3M+2rrT/dIH1NjZ +wgdYPBBXsIfLAAw3eB8OobUcI+ILlXUG/ezIxTCsC/fLEnzK7COnVd0iY8UCAwEA +AaNQME4wHQYDVR0OBBYEFDsffAk3JaQRD1xJb7g06pgpPrhyMB8GA1UdIwQYMBaA +FDsffAk3JaQRD1xJb7g06pgpPrhyMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF +BQADggEBAG8jDiUmZs/g4SyDeLRkos/vyEUaSzxolU9/7S4gbo7ckq35Uk+1dz8x +NfwIYmhdW5BsDK2HHC1lQ3HLyM5IeIQmULHacCPhbbEJ2dRtx3F4oRxeRn5jVWu3 +RI8CWAC7klH3WcE9cajY6WS/6EbpypMceT060hAPBXxPT7YTnY87VByj3Q/e4FMt +WnlvEuoFUfekayd4GFXhd+ReCL6xf8IXWmI6d5c/YJmY1p2AV8ILeXf0RDTfr832 +mxSuKzG+VopTJrUBkzkoSdYG9srftTlnSBE174rcgDzpqDDTFnwQYpMa5dIKOUHA +n/DK9mAnOaZHymcWl4bl/HSiXfMy/2U= +-----END CERTIFICATE----- diff --git a/tasks/certs/ca.key b/tasks/certs/ca.key new file mode 100644 index 0000000..c1c2fd2 --- /dev/null +++ b/tasks/certs/ca.key @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,D6AD3B624B34C274 + +Vg+XXN7UnJRk9FQpHRXWxVZy21qwQZcfkICIx5vcUjQZKArpTVWUpGq9QCXhGuUR +NnzrdScjQCGBDrmlMCS7ENltE1WXImOzZfI9gvU+Tc0Yhze7Dou8QNvB59+7Kye/ +RYOkQH4KdUsw9IMEt2ELrxCnO9D2qwCofUJq9tdh/R5uqKPlybSKMBaiKDY/JV88 +rNAp9uJhgFoDsZb8rKLoZqFSgrGHbH+eg67A8ZmHDAf5hMFo7Bt1cL61w+iEFAvU +wFdecJK6RlZRRGBkOJ6y1Wq1WL2Vlp1WHQJOSsPoV+cuC2DqRyb+liH2lffcYnIS +0cL1jAZ3FQ7V+8kZ37sZmAvLm4ELD0A72PH3Zv54/DVkQCHA/BHsOGm95zW+BoN/ +DnoRqbBQ6weH55dPHGCdbhADMsEuFqS2cSPOO2cik4Uvmnoh0M/wZYBWxbFO6qSE +Za1E60wvdcPWPGnu320qC05DQjCGvCNWFudovKYbbUv6xyfxqjEw0I1FjdUzJsJR +gF9hEPe99sAbRGSvU3aNoksjTTdgJaVk5+DMSekMmTF9M/mY9VgWu9YHL8tsEUv0 +6BcHK2hWXfZWwf/+HJmgI9JBXUfxVCpeoh+VHK6kpDL2ezjXAd/3jK4EhANc24/7 +Lk47KCs+1F8JXAnma1Hkit3nNdyWql4rHM6W7j0+v8vAG1t7pbr9ixipLD7qp8Jl +x30j+GiffzbBOZ3JWUcUm0MYwrfen+0ed3qQmrv7DMgK6uiwyLZJMEDA+0Pu920L +Zw6Z7YkYHZWfPciWDBP+aXKzFayg6SSFnYLdUUOjNBc2DZGTr8UuR07xdK/dNNW4 +WE0Wlbxma6jwrU3qYmsPY/21x5e54cu4o0qZWrYWbAsvAKaqQIkR6mDI4KIQbJ7d ++tfKe5Hck4CsTZJbjszQ4EZYOtMHCHg8tPYZTOGEBUeG3CTe4VY0zmjv5Jg6YUmq +88Cnz0WyNXA1dDcQDp/vUWQZBUZbIVxk1iAB8ZhHO8z51GnZQAcKoOq2R4fDQhlE +BMSbcn3gKEXf5dqwR6caosQmFA+kIg8ydonMbysucuUeCfShx5s9SjVDaOVlsb1V +LMXtt/IPGaBnNAOKLgtczaViOWsFULMdC5lAbg60P+PnhadDPTV40uOj9dDp8Lji +uKd5oj8REGT/TxQGdGS87RntJced0ukkc09JVVXdHCTeUbQ8zujxq2jIXFycvNtk +8oTSmPUzIKhuhkBq4++MoiGnYk/Ya9UW5Byl4FIoSTJkFTgGOI18WW7V0CcZ9xPW +rdlo5OjNGf9FhsoNyUVkm1460iwUnknT/+AeCkGrIkh9NKXD6KR9hgR3mZN/k6/I +2o4PlUMSpf5i94gzscMrZ8YsuT3Cu4MK7IlpnRrtnxKQqBW8Kv7zq4Xk9LRcf1bQ +xbf5rq3/i/7J7lV7VAY1y+wmN+KidmQ8Q/souaetVW24zG2c2kHTNp5WgQd8lA0T +n5WrpYJXiLYyiCVtoNTIsne9tDcbkMfiHJqyAjnDYfMOsOuZkmU/jh8L8FBdjM3v +11zk/6XQK8c4hFM0MOMRHcCs3yGcyRAQn7VwaYHqM5qt6tzFLteL+w== +-----END RSA PRIVATE KEY----- diff --git a/tasks/certs/ca.srl b/tasks/certs/ca.srl new file mode 100644 index 0000000..13e7e2c --- /dev/null +++ b/tasks/certs/ca.srl @@ -0,0 +1 @@ +BE1BAB345BC0CC88 diff --git a/tasks/certs/server.crt b/tasks/certs/server.crt new file mode 100644 index 0000000..2e4efc3 --- /dev/null +++ b/tasks/certs/server.crt @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICmDCCAYACCQC+G6s0W8DMiDANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJV +UzETMBEGA1UECAwKU29tZS1TdGF0ZTEQMA4GA1UECgwHR3J1bnRKUzEaMBgGA1UE +AwwRaHR0cHM6Ly9sb2NhbGhvc3QwHhcNMTMwMjA3MjAzMjU1WhcNMjMwMjA1MjAz +MjU1WjBQMQswCQYDVQQGEwJVUzETMBEGA1UECAwKU29tZS1TdGF0ZTEQMA4GA1UE +CgwHR3J1bnRKUzEaMBgGA1UEAwwRaHR0cHM6Ly9sb2NhbGhvc3QwgZ8wDQYJKoZI +hvcNAQEBBQADgY0AMIGJAoGBAM6KcAJQb4fxTS/rDEx7rxaeAdEsGUzp03yMAzHz +l74oCTCqikzNFSn7A5pUAlU7W80hR81h7wuT6/gR9BkRRckN2LlNOkKuF5EjLwvc +K0B6zljerBbjrjEQ6DEm83uhgqrOtgynM3f0wYrQm2C4LKi82hlG2ApFzwsj8G3w +7MZFAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBABKCuy9DP25tRKQ+kd6d/N0IS8N2 +3rLKD79+MQ3Y3fSdwjJnCjEMa0oAOJnLnG14x2BYCSAd6HCnZ42yemMt1BwITASJ +dXK4lWDALf4VOKhVIJjcWw81970JBDXpPGhJ1t0D5k2CAJXb72WATGLX7UIuRdJ2 +7f7ZTlXo3KRbHMwE4B3BwT+e/MWfwndMuvsSxQBGclw8RpmeF1W/wCLvYvpCtDeW +exBJohEZs/cb0Tu2dkrborYAmhCGt2ujB5612PVRxttCTua0eM615XW1UGlBZBW1 +vSKglmeWDK022gATPvplWVNp98yMMYmNmu3DHutCbdFxslLyqYlpCXq/3co= +-----END CERTIFICATE----- diff --git a/tasks/certs/server.csr b/tasks/certs/server.csr new file mode 100644 index 0000000..adef607 --- /dev/null +++ b/tasks/certs/server.csr @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBjzCB+QIBADBQMQswCQYDVQQGEwJVUzETMBEGA1UECAwKU29tZS1TdGF0ZTEQ +MA4GA1UECgwHR3J1bnRKUzEaMBgGA1UEAwwRaHR0cHM6Ly9sb2NhbGhvc3QwgZ8w +DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM6KcAJQb4fxTS/rDEx7rxaeAdEsGUzp +03yMAzHzl74oCTCqikzNFSn7A5pUAlU7W80hR81h7wuT6/gR9BkRRckN2LlNOkKu +F5EjLwvcK0B6zljerBbjrjEQ6DEm83uhgqrOtgynM3f0wYrQm2C4LKi82hlG2ApF +zwsj8G3w7MZFAgMBAAGgADANBgkqhkiG9w0BAQUFAAOBgQCguHE/Ae/It4pKK73B +80Wehc0gglUXtPpLvu0n4NwGQ7kEybYrpR4EFnjD76Jt6oJ/vqfVB/8tLPRX3Mvq +8U+spcyD2km4stApbp3u6V8XxPlVcvY0YNzL1XIRv5p9ODCjF6+xnz2BEW/WjbfP +c29uUw2q+5OydPffx7Zd1YPABA== +-----END CERTIFICATE REQUEST----- diff --git a/tasks/certs/server.key b/tasks/certs/server.key new file mode 100644 index 0000000..ba75116 --- /dev/null +++ b/tasks/certs/server.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDOinACUG+H8U0v6wxMe68WngHRLBlM6dN8jAMx85e+KAkwqopM +zRUp+wOaVAJVO1vNIUfNYe8Lk+v4EfQZEUXJDdi5TTpCrheRIy8L3CtAes5Y3qwW +464xEOgxJvN7oYKqzrYMpzN39MGK0JtguCyovNoZRtgKRc8LI/Bt8OzGRQIDAQAB +AoGBAJG7eKEJZEjFaDMhZEIrGzWYM6wFs6xjaEf++toqCK2xzho6mu7p3b8LUFV6 +ZVMCWRn6G5G9QlAnBV66PEPbviKCi1SuarGQe3B7QsxdkUSpdg3RrmhrzsHsSjkW +YGBwF3XvOxiWv1tlkKLiizoIP9wGE503AK/vtR9n0jFTuseBAkEA/2ZjrtXwMdJX +XNfZGMrbyhH2/8fjKHdeOwAdGwp7scdd2B1BBqwfa4tttbQUfuWaWaA2gShcTHVK +PfJNdXvMVQJBAM8GqWdBgBL9kn4IO8j1fXdEa04+PZLCzLT0deCFbySUajv7BIzP +NvAYDNsMskdq1wnA+G0Nz2NADH57JcwaAjECQQCbza6J/eElw5ef/91kjnw+bW8s +4pflG8zUWWFoGaET9vd823vLwjz4snofGthWAWODwYT+jcygp/y+hY5TWU5xAkBV +kmpZPNDEiL2RjLOxiA9ZShWUnNN0o0JcFaPXry/WjeYvbr1dupT5vucpb+EM9hN2 +e6Xz5b5wRtwjN6HS8HkxAkBxrTNDV2/+8TYtUp6KzHOiXpF1iaC6MtLLUPsLvccT +C/TlBD8ZwvW02G6BLgJF/woJ273EmK3Oehlf9Uwgm9yv +-----END RSA PRIVATE KEY----- diff --git a/tasks/connect.js b/tasks/connect.js index 5a0c3b8..5ac5566 100644 --- a/tasks/connect.js +++ b/tasks/connect.js @@ -11,10 +11,13 @@ module.exports = function(grunt) { var path = require('path'); var connect = require('connect'); + var http = require('http'); + var https = require('https'); grunt.registerMultiTask('connect', 'Start a connect web server.', function() { // Merge task-specific options with these defaults. var options = this.options({ + protocol: 'http', port: 8000, hostname: 'localhost', base: '.', @@ -29,6 +32,10 @@ module.exports = function(grunt) { } }); + if (options.protocol !== 'http' && options.protocol !== 'https') { + grunt.fatal('protocol option must be \'http\' or \'https\''); + } + // Connect requires the base path to be absolute. options.base = path.resolve(options.base); @@ -56,8 +63,21 @@ module.exports = function(grunt) { var taskTarget = this.target; var keepAlive = this.flags.keepalive || options.keepalive; - var server = connect - .apply(null, middleware) + var app = connect.apply(null, middleware); + var server = null; + + if (options.protocol === 'https') { + server = https.createServer({ + key: options.key || grunt.file.read(path.join(__dirname, 'certs', 'server.key')).toString(), + cert: options.cert || grunt.file.read(path.join(__dirname, 'certs', 'server.crt')).toString(), + ca: options.ca || grunt.file.read(path.join(__dirname, 'certs', 'ca.crt')).toString(), + passphrase: options.passphrase || 'grunt', + }, app); + } else { + server = http.createServer(app); + } + + server .listen(options.port, options.hostname) .on('listening', function() { var address = server.address(); diff --git a/test/connect_test.js b/test/connect_test.js index 5711adc..7fe84d9 100644 --- a/test/connect_test.js +++ b/test/connect_test.js @@ -2,9 +2,14 @@ var grunt = require('grunt'); var http = require('http'); +var https = require('https'); function get(url, done) { - http.get(url, function(res) { + var client = http; + if (url.toLowerCase().indexOf('https') === 0) { + client = https; + } + client.get(url, function(res) { var body = ''; res.on('data', function(chunk) { body += chunk; @@ -23,5 +28,4 @@ exports.connect = { test.done(); }); }, - };