tunnelss 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ /*.gem
2
+ /Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in tunnels.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # Tunnelss
2
+
3
+ Tunnelss is a proxy from HTTPS to HTTP to help with web development over HTTPS.
4
+
5
+ It's like the [tunnels gem](https://github.com/jugyo/tunnels) (sure, it's a fork), but it does more, since it makes your HTTPS recognized as valid by the browser!
6
+
7
+ So now you can finally have a not-far-from-real SSL connection with Pow, with a minimum of efforts!
8
+
9
+ ## The Magic
10
+
11
+ Tunnelss is a mix between the [tunnels gem](https://github.com/jugyo/tunnels) and the [powssl script](https://gist.github.com/paulnicholson/2050941).
12
+
13
+ 1. It builds a root-level certificate (a Certificate Authority) and registers it as a trusted root certificate (you will need to do it manually for Firefox).
14
+ 2. It generates a SSL certificate matching the Pow `.dev` domains.
15
+ 2. It runs an EventMachine server which acts as proxy from HTTPS to HTTP (just like tunnels), using the generated certificate so that your browser will not complain your SSL connection is not valid!
16
+
17
+ ## Why?
18
+
19
+ Because:
20
+
21
+ * setting up Nginx to do reverse-proxying for development is overkill,
22
+ * you would have to add a Nginx config file each time you start a new project,
23
+ * you like the `.dev` domains provided by Pow,
24
+ * you want to run your app over SSL just like in production so that you can check the redirections,
25
+ * working with external APIs over HTTPS, secured cookies and CORS seems really difficult if your SSL certificate is not valid (your browser may refuse to perform CORS requests),
26
+ * your app server needs to know that the request was over HTTPS, even if it was proxied to HTTP (which the powssl script which uses Stud doesn't allow).
27
+
28
+ ## Disclaimer
29
+
30
+ This gem is in early developments and has only been tested under MacOS X 10.8. It may not work in other environments, but feel free to submit pull requests if you make the necessary fixes.
31
+
32
+ ## Installation
33
+
34
+ $ gem install tunnelss
35
+
36
+ If you're using rbenv:
37
+
38
+ $ rbenv rehash
39
+
40
+ ## Run
41
+
42
+ $ sudo tunnelss
43
+
44
+ Don't worry, the first time you launch it it will generate a certificate and ask for your permission to add it to trusted authorities (see _The Magic_ above for more details).
45
+
46
+ If you are using rvm:
47
+
48
+ $ rvmsudo tunnels
49
+
50
+ By default, proxy to 80 port from 443 port.
51
+
52
+ Specify HTTP port and HTTPS port with:
53
+
54
+ $ sudo tunnels 443 3000
55
+
56
+ or
57
+
58
+ $ sudo tunnels 127.0.0.1:443 127.0.0.1:3000
59
+
60
+ ## History
61
+
62
+ ### 0.1.0
63
+
64
+ Initial release based on tunnels 1.2.2.
65
+
66
+ ## Credits
67
+
68
+ * [tunnels](https://github.com/jugyo/tunnels) from which most code comes
69
+ * [powssl](https://gist.github.com/paulnicholson/2050941), a gist of Paul Nicholson which I translated to Ruby to perform Tunnelss certificate configuration based on Pow's.
70
+
71
+ ## Copyright
72
+
73
+ [tunnelss](http://github.com/rchampourlier/tunnelss)
74
+ Copyright (c) 2013 rchampourlier, released under the MIT license.
75
+
76
+ [tunnels](https://github.com/jugyo/tunnels)
77
+ Copyright (c) 2012 jugyo, released under the MIT license.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/tunnelss ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ require 'tunnelss'
3
+ unless ARGV.size == 0 || ARGV.size == 2
4
+ puts <<-D
5
+ Usage:
6
+ tunnelss [from to]
7
+
8
+ Examples:
9
+ tunnelss 443 3000
10
+ tunnelss localhost:443 localhost:3000
11
+
12
+ D
13
+ exit!
14
+ end
15
+
16
+ Tunnelss.run!(*ARGV)
@@ -0,0 +1,37 @@
1
+ [ ca ]
2
+ default_ca = CA_default
3
+
4
+ [ CA_default ]
5
+ dir = --CA_DIR--
6
+ certs = $dir/certs
7
+ crl_dir = $dir/crl
8
+ database = $dir/index.txt
9
+ unique_subject = no
10
+ new_certs_dir = $dir/newcerts
11
+ certificate = $dir/cert.pem
12
+ serial = $dir/serial
13
+ crlnumber = $dir/crlnumber
14
+ crl = $dir/crl.pem
15
+ private_key = $dir/key.pem
16
+ RANDFILE = $dir/.rand
17
+ default_days = 365 # how long to certify for
18
+ default_crl_days = 30 # how long before next CRL
19
+ default_md = sha1 # which md to use.
20
+ x509_extensions = usr_cert
21
+
22
+ [ policy_anything ]
23
+ countryName = optional
24
+ stateOrProvinceName = optional
25
+ localityName = optional
26
+ organizationName = optional
27
+ organizationalUnitName = optional
28
+ commonName = supplied
29
+ emailAddress = optional
30
+
31
+ [ usr_cert ]
32
+ basicConstraints = CA:FALSE
33
+ nsCertType = server
34
+ nsComment = "OpenSSL Generated Certificate"
35
+ subjectKeyIdentifier = hash
36
+ authorityKeyIdentifier = keyid,issuer
37
+ subjectAltName = --DOMAINS--
@@ -0,0 +1,118 @@
1
+ require "pry"
2
+
3
+ module Tunnelss::ConfigureWithPow
4
+
5
+ def configure_with_pow
6
+ build_or_update_certificate
7
+ end
8
+
9
+ def pow_present?
10
+ File.exists? pow_dir
11
+ end
12
+
13
+ private
14
+
15
+ def build_or_update_certificate
16
+ unless File.exists?(dir)
17
+ build_dir
18
+ build_ca
19
+ build_certificate
20
+ else
21
+ unless ca_exists?
22
+ build_ca
23
+ build_certificate
24
+ else
25
+ build_certificate if pow_domains_changed?
26
+ end
27
+ end
28
+ end
29
+
30
+ def build_dir
31
+ Dir.mkdir(dir)
32
+ end
33
+
34
+ def ca_exists?
35
+ File.exists?(ca_dir) and File.exists?("#{ca_dir}/key.pem") and File.exists?("#{ca_dir}/cert.pem")
36
+ end
37
+
38
+ def build_ca
39
+ FileUtils.rm_rf(ca_dir) if File.exists?(ca_dir)
40
+ Dir.mkdir(ca_dir)
41
+
42
+ puts "Creating SSL keypair for signing *.dev certificate"
43
+ system "openssl req -newkey rsa:2048 -batch -x509 -nodes -subj \"/C=US/O=Developer Certificate/CN=*.dev Domain CA\" -keyout #{ca_dir}/key.pem -out #{ca_dir}/cert.pem -days 9999 &> /dev/null"
44
+ puts "Adding certificate to login keychain as trusted."
45
+ system "security add-trusted-cert -d -r trustRoot -k #{ENV['HOME']}/Library/Keychains/login.keychain #{ca_dir}/cert.pem"
46
+ puts "================================================================================"
47
+ puts "To use the certificate without a warning in Firefox you must add the\n\"#{ca_dir}/cert.pem\" certificate to your Firefox root certificates."
48
+ puts "================================================================================"
49
+ end
50
+
51
+ def build_certificate
52
+ prepare_openssl_config
53
+
54
+ puts "Generating new *.dev certificate"
55
+ system "openssl req -newkey rsa:2048 -batch -nodes -subj \"/C=US/O=Developer Certificate/CN=*.dev\" -keyout #{dir}/key.pem -out #{dir}/csr.pem -days 9999 &> /dev/null"
56
+ puts "Signing *.dev certificate"
57
+ system "openssl ca -config #{ca_dir}/openssl.cnf -policy policy_anything -batch -days 9999 -out #{dir}/cert.pem -infiles #{dir}/csr.pem &> /dev/null"
58
+
59
+ # Build cert chain
60
+ system "cat #{dir}/cert.pem > #{dir}/server.crt"
61
+ system "cat #{ca_dir}/cert.pem >> #{dir}/server.crt"
62
+
63
+ write_pow_domains_to_cache
64
+
65
+ puts "Generated certificate for your Pow .dev domains."
66
+ true
67
+ end
68
+
69
+ def prepare_openssl_config
70
+ Dir.mkdir("#{ca_dir}/newcerts") unless File.exists?("#{ca_dir}/newcerts")
71
+ system "touch #{ca_dir}/index.txt"
72
+ File.open("#{ca_dir}/serial", 'w') {|f| f.puts '01'}
73
+ build_openssl_config_file
74
+ end
75
+
76
+ def build_openssl_config_file
77
+ openssl_conf = File.read(File.expand_path('../../../generators/openssl.cnf', __FILE__))
78
+ openssl_conf.gsub!('--CA_DIR--', ca_dir)
79
+ openssl_conf.gsub!('--DOMAINS--', pow_domains_str)
80
+ File.open("#{ca_dir}/openssl.cnf", "w") {|f| f.puts openssl_conf}
81
+ end
82
+
83
+ def dir
84
+ "#{ENV['HOME']}/.tunnelss"
85
+ end
86
+
87
+ def ca_dir
88
+ "#{dir}/ca"
89
+ end
90
+
91
+ def pow_domains_changed?
92
+ read_pow_domains_from_cache != pow_domains.join(',')
93
+ end
94
+
95
+ def read_pow_domains_from_cache
96
+ File.exists?(pow_domains_cache_file) ? File.read(pow_domains_cache_file).strip : ''
97
+ end
98
+
99
+ def write_pow_domains_to_cache
100
+ File.open(pow_domains_cache_file, 'w') {|f| f.puts pow_domains.join(',')}
101
+ end
102
+
103
+ def pow_domains_cache_file
104
+ "#{dir}/pow_domains"
105
+ end
106
+
107
+ def pow_domains
108
+ @pow_domains ||= Dir["#{pow_dir}/*"].collect {|f| File.basename(f)}
109
+ end
110
+
111
+ def pow_domains_str
112
+ pow_domains.map {|d| "DNS:#{d},DNS:*.#{d}.dev"}.join(',')
113
+ end
114
+
115
+ def pow_dir
116
+ "#{ENV['HOME']}/.pow"
117
+ end
118
+ end
@@ -0,0 +1,3 @@
1
+ module Tunnelss
2
+ VERSION = "0.1.0"
3
+ end
data/lib/tunnelss.rb ADDED
@@ -0,0 +1,137 @@
1
+ require "tunnelss/version"
2
+ require "tunnelss/configure_with_pow"
3
+ require "eventmachine"
4
+
5
+ # [Tunnels](http://github.com/rchampourlier/tunnelss)
6
+ #
7
+ # LICENSE
8
+ #
9
+ # MIT License
10
+ #
11
+ # COPYRIGHTS / CREDITS
12
+ #
13
+ # Most of the code is from [tunnels](https://github.com/jugyo/tunnels) which was a simpler
14
+ # version designed to tunnel HTTPS to HTTP, without performing automatic certificate
15
+ # configuration.
16
+ #
17
+ # [tunnels](https://github.com/jugyo/tunnels)
18
+ # Copyright (c) 2012 jugyo, released under the MIT license.
19
+ #
20
+ # TUNNELS COPYRIGHTS / CREDITS
21
+ #
22
+ # Most of code is from [thin-glazed](https://github.com/freelancing-god/thin-glazed).
23
+ # Copyright © 2012, Thin::Glazed was a Rails Camp New Zealand project, and is developed
24
+ # and maintained by Pat Allan. It is released under the open MIT Licence.
25
+
26
+ module Tunnelss
27
+ extend ConfigureWithPow
28
+
29
+ def self.run!(from = '127.0.0.1:443', to = '127.0.0.1:80')
30
+ configure_with_pow if pow_present?
31
+
32
+ from_host, from_port = parse_host_str(from)
33
+ to_host, to_port = parse_host_str(to)
34
+ puts "#{from_host}:#{from_port} --(--)--> #{to_host}:#{to_port}"
35
+
36
+ EventMachine.run do
37
+ EventMachine.start_server(from_host, from_port, HttpsProxy, to_port)
38
+ puts "Ready :)"
39
+ end
40
+ rescue => e
41
+ puts e.message
42
+ puts "Maybe you should run on `sudo`"
43
+ end
44
+
45
+ def self.parse_host_str(str)
46
+ raise ArgumentError, 'arg must not be empty' if str.empty?
47
+ parts = str.split(':')
48
+ if parts.size == 1
49
+ ['127.0.0.1', parts[0].to_i]
50
+ else
51
+ [parts[0], parts[1].to_i]
52
+ end
53
+ end
54
+
55
+ class HttpClient < EventMachine::Connection
56
+ attr_reader :proxy
57
+
58
+ def initialize(proxy)
59
+ @proxy = proxy
60
+ @connected = EventMachine::DefaultDeferrable.new
61
+ end
62
+
63
+ def connection_completed
64
+ @connected.succeed
65
+ end
66
+
67
+ def receive_data(data)
68
+ proxy.relay_from_client(data)
69
+ end
70
+
71
+ def send(data)
72
+ @connected.callback { send_data data }
73
+ end
74
+
75
+ def unbind
76
+ proxy.unbind_client
77
+ end
78
+ end
79
+
80
+ class HttpProxy < EventMachine::Connection
81
+ attr_reader :client_port
82
+
83
+ def initialize(client_port)
84
+ @client_port = client_port
85
+ end
86
+
87
+ def receive_data(data)
88
+ client.send_data data unless data.nil?
89
+ end
90
+
91
+ def relay_from_client(data)
92
+ send_data data unless data.nil?
93
+ end
94
+
95
+ def unbind
96
+ client.close_connection
97
+ @client = nil
98
+ end
99
+
100
+ def unbind_client
101
+ close_connection_after_writing
102
+ @client = nil
103
+ end
104
+
105
+ private
106
+
107
+ def client
108
+ @client ||= EventMachine.connect '127.0.0.1', client_port, HttpClient, self
109
+ end
110
+ end
111
+
112
+ class HttpsProxy < HttpProxy
113
+ include ConfigureWithPow
114
+
115
+ def post_init
116
+ if pow_present?
117
+ start_tls(:private_key_file => "#{dir}/key.pem", :cert_chain_file => "#{dir}/server.crt", :verify_peer => false)
118
+ else
119
+ start_tls
120
+ end
121
+ end
122
+
123
+ def relay_from_client(data)
124
+ super
125
+ @x_forwarded_proto_header_inserted = false
126
+ end
127
+
128
+ def receive_data(data)
129
+ if !@x_forwarded_proto_header_inserted && data =~ /\r\n\r\n/
130
+ super data.gsub(/\r\n\r\n/, "\r\nX_FORWARDED_PROTO: https\r\n\r\n")
131
+ @x_forwarded_proto_header_inserted = true
132
+ else
133
+ super
134
+ end
135
+ end
136
+ end
137
+ end
data/script/console ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ project_dir = File.expand_path(File.join(File.dirname(__FILE__), '..'))
4
+ $LOAD_PATH.unshift File.join(project_dir, 'lib')
5
+
6
+ require 'tunnelss'
7
+
8
+ # prevent STDOUT & STDERR to be reopened (apps do this to be able to log under Passenger)
9
+ def STDOUT.reopen(*args); end
10
+ def STDERR.reopen(*args); end
11
+
12
+ begin
13
+ require "pry"
14
+ Interpreter = Pry
15
+ rescue LoadError
16
+ require "irb"
17
+ require "irb/completion"
18
+ Interpreter = IRB
19
+ end
20
+
21
+ # START
22
+ Interpreter.start
@@ -0,0 +1,6 @@
1
+ require 'rspec'
2
+ require 'tunnelss'
3
+
4
+ RSpec.configure do |config|
5
+ config.mock_with :rr
6
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ # to stub
4
+ def EventMachine.run(&block)
5
+ block.call
6
+ end
7
+
8
+ describe Tunnelss do
9
+ describe '.run!' do
10
+ context 'with no args' do
11
+ it 'works' do
12
+ mock(EventMachine).start_server('127.0.0.1', 443, Tunnelss::HttpsProxy, 80)
13
+ Tunnelss.run!
14
+ end
15
+ end
16
+
17
+ context 'with args' do
18
+ it 'works' do
19
+ mock(EventMachine).start_server('127.0.0.1', 443, Tunnelss::HttpsProxy, 80)
20
+ Tunnelss.run!('127.0.0.1:443', '127.0.0.1:80')
21
+ end
22
+ end
23
+ end
24
+ end
data/tunnelss.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "tunnelss/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "tunnelss"
7
+ s.version = Tunnelss::VERSION
8
+ s.authors = ["rchampourlier"]
9
+ s.email = ["romain@softr.li"]
10
+ s.homepage = "https://github.com/rchampourlier/tunnelss"
11
+ s.summary = %q{Pow + SSL, automated}
12
+ s.description = %q{This tunnels HTTPS to HTTP and configures itself using Pow's config.}
13
+
14
+ s.rubyforge_project = "tunnelss"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "rspec"
22
+ s.add_development_dependency "rr"
23
+ s.add_development_dependency "pry"
24
+ s.add_development_dependency "pry-debugger"
25
+ s.add_runtime_dependency "eventmachine"
26
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tunnelss
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - rchampourlier
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ version_requirements: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ none: false
21
+ name: rspec
22
+ type: :development
23
+ prerelease: false
24
+ requirement: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ! '>='
27
+ - !ruby/object:Gem::Version
28
+ version: '0'
29
+ none: false
30
+ - !ruby/object:Gem::Dependency
31
+ version_requirements: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ! '>='
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ none: false
37
+ name: rr
38
+ type: :development
39
+ prerelease: false
40
+ requirement: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ none: false
46
+ - !ruby/object:Gem::Dependency
47
+ version_requirements: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ none: false
53
+ name: pry
54
+ type: :development
55
+ prerelease: false
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ none: false
62
+ - !ruby/object:Gem::Dependency
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ none: false
69
+ name: pry-debugger
70
+ type: :development
71
+ prerelease: false
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ none: false
78
+ - !ruby/object:Gem::Dependency
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ none: false
85
+ name: eventmachine
86
+ type: :runtime
87
+ prerelease: false
88
+ requirement: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ none: false
94
+ description: This tunnels HTTPS to HTTP and configures itself using Pow's config.
95
+ email:
96
+ - romain@softr.li
97
+ executables:
98
+ - tunnelss
99
+ extensions: []
100
+ extra_rdoc_files: []
101
+ files:
102
+ - .gitignore
103
+ - Gemfile
104
+ - README.md
105
+ - Rakefile
106
+ - bin/tunnelss
107
+ - generators/openssl.cnf
108
+ - lib/tunnelss.rb
109
+ - lib/tunnelss/configure_with_pow.rb
110
+ - lib/tunnelss/version.rb
111
+ - script/console
112
+ - spec/spec_helper.rb
113
+ - spec/tunnelss_spec.rb
114
+ - tunnelss.gemspec
115
+ homepage: https://github.com/rchampourlier/tunnelss
116
+ licenses: []
117
+ post_install_message:
118
+ rdoc_options: []
119
+ require_paths:
120
+ - lib
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ none: false
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ none: false
133
+ requirements: []
134
+ rubyforge_project: tunnelss
135
+ rubygems_version: 1.8.23
136
+ signing_key:
137
+ specification_version: 3
138
+ summary: Pow + SSL, automated
139
+ test_files:
140
+ - spec/spec_helper.rb
141
+ - spec/tunnelss_spec.rb