fenris 0.0.6 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
data/bin/fenris CHANGED
@@ -3,14 +3,14 @@
3
3
  lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
4
  $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
5
 
6
- require 'fenris/em'
7
6
  require 'fenris/command'
8
7
  require 'fenris/client'
8
+ require 'fenris/connection'
9
9
 
10
- args = ARGV.dup
11
- ARGV.clear
12
- command = args.shift.strip rescue 'help'
10
+ #args = ARGV.dup
11
+ #ARGV.clear
12
+ #command = args.shift.strip rescue 'help'
13
13
  #arg = args.shift.strip rescue nil
14
14
 
15
- Fenris::Command.run command, *args
15
+ Fenris::Command.run ARGV
16
16
 
data/bin/xx ADDED
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'socket'
5
+ require 'ostruct'
6
+ require 'uri'
7
+
8
+ module Fenris
9
+ class Command
10
+ def self.run(args)
11
+ url = URI.parse(ENV['FENRIS_URL'] || 'https://broker.fenris.cc/')
12
+ args, options, help = parse(args)
13
+ fenris = Fenris::Client.new url, options
14
+ command = args.shift
15
+ arg = args.first
16
+
17
+ case [ command, args.length ]
18
+ when [ "info", 0 ]
19
+ fenris.update_user_config
20
+ printf "INFO:\n"
21
+ printf " %s\n", fenris.user_name
22
+ unless fenris.users.empty?
23
+ puts "SUBACCOUNTS:"
24
+ fenris.users.each { |c| printf " %-20s\n", c["name"] }
25
+ end
26
+ unless fenris.consumers.empty?
27
+ puts "CLIENTS:"
28
+ fenris.consumers.each { |c| printf " %-20s\n", c["name"] }
29
+ end
30
+ unless fenris.providers.empty?
31
+ puts "SERVICES:"
32
+ fenris.providers.each { |c| printf " %-20s %s\n", c["name"], c["location"] }
33
+ end
34
+ when [ "useradd", 1 ]
35
+ Fenris::Client.new(url, options).useradd(arg)
36
+ puts "user added"
37
+ when [ "userdel", 1 ]
38
+ Fenris::Client.new(url, options).userdel(arg)
39
+ puts "ok"
40
+ when [ "sync", 0 ]
41
+ fenris.update_config
42
+ when [ "rekey", 0 ]
43
+ Fenris::Client.new(url, options).rekey
44
+ puts "rekey complete"
45
+ when [ "add", 1 ]
46
+ Fenris::Client.new(url, options).add(arg)
47
+ puts "ok"
48
+ when [ "remove", 1 ]
49
+ Fenris::Client.new(url, options).remove(arg)
50
+ puts "ok"
51
+ when [ "provide", 1 ]
52
+ Fenris::Client.new(url, options).provide(arg)
53
+ when [ "consume", 0 ]
54
+ Fenris::Client.new(url, options).consume
55
+ when [ "consume", 1 ]
56
+ Fenris::Client.new(url, options).consume(arg)
57
+ when [ "consume", 2 ]
58
+ Fenris::Client.new(url, options).consume(args[0] => args[1])
59
+ else
60
+ case commands[0]
61
+ when "exec"
62
+ options.quiet = true
63
+ Fenris::Client.new(url, options).exec(*args)
64
+ else
65
+ puts help
66
+ exit
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ def self.parse(args)
73
+ options = OpenStruct.new
74
+ options.port = 10001
75
+ options.host = Socket.gethostname
76
+ options.user = ENV['FENRIS_USER']
77
+ options.password = ENV['FENRIS_AUTHKEY']
78
+ options.config = ENV['FENRIS_CONFIG'] || "#{ENV["HOME"]}/.fenris"
79
+ options.debug = !! ENV['FENRIS_DEBUG']
80
+ options.autosync = true
81
+
82
+ opts = OptionParser.new do |opts|
83
+ opts.banner = "Usage: fenris [options] COMMAND"
84
+ opts.separator ""
85
+ opts.separator "Commands:"
86
+ opts.separator ""
87
+ opts.separator " fenris info # info about current user and connections"
88
+ opts.separator " fenris useradd NAME # create a new subaccount"
89
+ opts.separator " fenris userdel NAME # delete a subaccount"
90
+ opts.separator " fenris add USER # allow USER to consume service"
91
+ opts.separator " fenris remove USER # disallow USER to consume service"
92
+ opts.separator " fenris provide BINDING # provide a local service"
93
+ opts.separator " fenris consume [ USER [ BINDING ] ] # consume one (or all) remote services"
94
+ opts.separator " fenris exec COMMAND # consume services and run COMMAND. Terminates when command exits."
95
+ opts.separator " fenris sync # sync config with broker"
96
+ opts.separator " fenris rekey # generate a new authkey for the user"
97
+ opts.separator " fenris bind USER BINDING # bind a remote service to a local binding"
98
+ opts.separator ""
99
+ opts.separator "Bindings can take the form of:"
100
+ opts.separator ""
101
+ opts.separator " PORT, :PORT, HOST:PORT, /PATH/TO/UNIX_SOCKET, or -- for STDIN"
102
+ opts.separator ""
103
+ opts.separator "Options:"
104
+
105
+ opts.on("-u", "--user USER", "Connect as USER. Default is $FENRIS_USER.") do |user|
106
+ options.user = user
107
+ end
108
+
109
+ opts.on("-c", "--config DIR", "Store config information in DIR. Default is $HOME/.fenris or $FENRIS_CONFIG.") do |dir|
110
+ options.config = dir
111
+ end
112
+
113
+ opts.on("-h", "--hostname HOST", "Hostname given to consumers on 'provide'. Default is `hostname`.") do |dir|
114
+ options.config = dir
115
+ end
116
+
117
+ opts.on("-p", "--port PORT", "") do |dir|
118
+ options.config = dir
119
+ end
120
+
121
+ opts.on("-q", "--quiet", "Do not print log messages.") do
122
+ options.quiet = true
123
+ end
124
+
125
+ opts.on("-d", "--debug", "Print debugging messages. Default is $FENRIS_DEBUG.") do
126
+ options.debug = true
127
+ end
128
+
129
+ opts.on("-a", "--[no-]autosync", "Automatically sync updates from the broker. Default is true.") do |auto|
130
+ options.autosync = auto
131
+ end
132
+
133
+ opts.on("-h", "--help", "Show this message") do
134
+ puts opts
135
+ exit
136
+ end
137
+
138
+ opts.on("--version", "Show version") do
139
+ puts Fenris::VERSION
140
+ exit
141
+ end
142
+ opts.separator ""
143
+ end
144
+
145
+ begin
146
+ commands = opts.parse!(args)
147
+ rescue OptionParser::InvalidOption
148
+ puts opts
149
+ exit
150
+ end
151
+ [ commands, options, opts.to_s ]
152
+ end
153
+ end
154
+ end
155
+
156
+ Fenris::Command.run(ARGV)
@@ -3,64 +3,191 @@ require 'json'
3
3
  require 'openssl'
4
4
  require 'uri'
5
5
  require 'eventmachine'
6
+ require 'ostruct'
6
7
 
7
8
  module Fenris
9
+
10
+ class NoSuchProvider < RuntimeError; end
11
+
8
12
  class Client
9
- def initialize(url)
13
+ def initialize(url, options = OpenStruct.new)
10
14
  @url = url
11
15
  @url = URI.parse(url) unless url.is_a? URI
12
- @quiet = false
16
+ options = OpenStruct.new options if options.is_a? Hash
17
+ @options = options
18
+ @verbose = false
19
+ end
20
+
21
+ def options
22
+ @options
23
+ end
24
+
25
+ def url
26
+ @url.user = options.user
27
+ @url.password = read_config_file("#{user_name}.authkey")
28
+ @url.password ||= options.password
29
+ @url.password ||= get_authkey_from_stdin
30
+ @url
31
+ end
32
+
33
+ def get_authkey_from_stdin
34
+ system "stty -echo"
35
+ print "Authkey: "
36
+ password = gets.chomp
37
+ system "stty echo"
38
+ password
39
+ end
40
+
41
+ def config_dir
42
+ options.config
43
+ end
44
+
45
+ def verify_private_key
46
+ if key = read_config_file("#{user_name}.key")
47
+ log "using existing key #{digest key}" if @verbose
48
+ else
49
+ key = OpenSSL::PKey::RSA.new(2048)
50
+ write key
51
+ log "new rsa key generated #{digest key}"
52
+ end
53
+ end
54
+
55
+ def verify_cert(cn)
56
+ if cert = read_cert(cn)
57
+ log "existing cert #{digest cert} :: #{cert.not_after} :: #{cn}" if @verbose
58
+ else
59
+ cert = OpenSSL::X509::Certificate.new(RestClient.post("#{url}cert", :csr => generate_csr(cn)))
60
+ write cert
61
+ log "new cert received #{digest cert} :: #{cert.not_after} :: #{cn}"
62
+ end
63
+ end
64
+
65
+ def read_cert cn
66
+ if cert = read_config_file("#{cn}.crt")
67
+ if cert.not_after > Time.now
68
+ cert
69
+ else
70
+ log "cert expired #{digest cert} :: #{cert.not_after} :: #{cn}"
71
+ nil
72
+ end
73
+ end
74
+ end
75
+
76
+ def my_cert
77
+ read_config_file "#{user_name}.crt"
78
+ end
79
+
80
+ def my_key
81
+ read_config_file "#{user_name}.key"
82
+ end
83
+
84
+ def cert(name)
85
+ read_config_file "#{user_name}:#{name}.crt"
86
+ end
87
+
88
+ def cert_path(name)
89
+ "#{config_dir}/#{user_name}:#{name}.crt"
90
+ end
91
+
92
+ def my_cert_path
93
+ "#{config_dir}/#{user_name}.crt"
94
+ end
95
+
96
+ def my_key_path
97
+ "#{config_dir}/#{user_name}.key"
13
98
  end
14
99
 
15
- def quiet= val
16
- @quiet = val
100
+ def write_cert(cert)
101
+ write_config_file "#{get_cn(cert)}.crt", cert
102
+ end
103
+
104
+ def write_config_file name, data
105
+ File.umask 0077
106
+ Dir.mkdir config_dir unless Dir.exists? config_dir
107
+ File.open("#{config_dir}/#{name}","w") { |f| f.write(data) }
108
+ end
109
+
110
+ def read_config_file name
111
+ path = "#{config_dir}/#{name}"
112
+ if not File.exists? path
113
+ nil
114
+ elsif name =~ /[.]json$/
115
+ JSON.parse File.read(path)
116
+ elsif name =~ /[.]crt$/
117
+ OpenSSL::X509::Certificate.new File.read(path)
118
+ elsif name =~ /[.]key$/
119
+ OpenSSL::PKey::RSA.new File.read(path)
120
+ else
121
+ File.read path
122
+ end
123
+ end
124
+
125
+ def write object
126
+ if object.is_a? OpenSSL::X509::Certificate
127
+ write_config_file "#{get_cn(object)}.crt", object.to_pem
128
+ elsif object.is_a? OpenSSL::PKey::RSA
129
+ write_config_file "#{user_name}.key", object.to_pem
130
+ else
131
+ write_config_file "#{user_name}.json", object.to_json
132
+ end
17
133
  end
18
134
 
19
135
  def debug(message)
20
- puts "DEBUG: #{message}" if ENV['DEBUG'] and !@quiet
136
+ puts "DEBUG: #{message}" if options.debug and not options.quiet
21
137
  end
22
138
 
23
139
  def log(message)
24
- puts "LOG: #{message}" if not @quiet
140
+ puts "LOG: #{message}" if not options.quiet
25
141
  end
26
142
 
27
- def update(location)
28
- RestClient.put("#{@url}", { :location => location }, :content_type => :json, :accept => :json)
143
+ def post_location
144
+ RestClient.put("#{url}", { :location => location }, :content_type => :json, :accept => :json)
29
145
  end
30
-
146
+
31
147
  def user
32
- @user ||= JSON.parse RestClient.get("#{@url}", :content_type => :json, :accept => :json)
148
+ @user ||= read_config_file "config.json"
33
149
  end
34
150
 
35
151
  def ssl?
36
152
  @url.scheme == "https"
37
153
  end
38
154
 
39
- def async_connection
40
- @async_connection = nil if @async_connection && @async_connection.error?
41
- @async_connection ||= EM::Protocols::HttpClient2.connect :host => @url.host, :port => @url.port, :ssl => ssl?
155
+ def update_user_config
156
+ @user = JSON.parse RestClient.get("#{url}", :content_type => :json, :accept => :json)
157
+ write_config_file "#{user_name}.authkey", @url.password
158
+ write user
42
159
  end
43
160
 
44
- def auth_string
45
- "Basic " + ["#{@url.user}:#{@url.password}"].pack('m').strip.gsub(/\n/,'')
161
+ def update_config(verbose = true)
162
+ @verbose = verbose
163
+ log "updating config in #{config_dir}" if @verbose
164
+ update_user_config
165
+ log "have update user config" if @verbose
166
+ write_config_file "root.crt", RestClient.get("#{url}cert") ## TODO find a way to get this out of the connection info
167
+ verify_private_key
168
+ verify_cert user_name
169
+ providers.each do |p|
170
+ verify_cert "#{user_name}:#{p["name"]}"
171
+ end
172
+ @verbose = false
46
173
  end
47
174
 
48
- def async_update(&blk)
49
- request = async_connection.get(:uri => "/", :authorization => auth_string )
50
- request.callback do |response|
51
- if response.status == 200
52
- # TODO - output if something has changed
53
- @broker ||= OpenSSL::X509::Certificate.new(async_connection.get_peer_cert) if ssl?
54
- @user = JSON.parse response.content
55
- else
56
- log "Error updating user info"
57
- debug response.status
58
- @async_connection = nil
59
- end
60
- blk.call if blk
175
+ def provider_map
176
+ Hash[ * providers.map { |p| [ p["name"], p["binding"] ] }.flatten ]
177
+ end
178
+
179
+ def location_of(provider)
180
+ if p = providers.detect { |p| p["name"] == provider }
181
+ p["locations"].first
182
+ else
183
+ raise NoSuchProvider, provider
61
184
  end
62
185
  end
63
186
 
187
+ def location
188
+ "#{options.host}:#{options.port}"
189
+ end
190
+
64
191
  def consumers
65
192
  user["consumers"]
66
193
  end
@@ -70,27 +197,31 @@ module Fenris
70
197
  end
71
198
 
72
199
  def user_name
73
- user["name"]
74
- end
75
-
76
- def route
77
- user["location"]
200
+ options.user
78
201
  end
79
202
 
80
203
  def remove(name)
81
- RestClient.delete("#{@url}consumers/#{name}");
204
+ update_user_config
205
+ RestClient.delete("#{url}consumers/#{name}");
82
206
  end
83
207
 
84
208
  def add(name)
85
- RestClient.post("#{@url}consumers", { :name => name }, :content_type => :json, :accept => :json);
209
+ update_user_config
210
+ RestClient.post("#{url}consumers", { :name => name }, :content_type => :json, :accept => :json);
86
211
  end
87
212
 
88
213
  def useradd(name)
89
- JSON.parse RestClient.post("#{@url}users", { :name => name }, :content_type => :json, :accept => :json);
214
+ update_user_config
215
+ newuser = JSON.parse RestClient.post("#{url}users", { :name => name }, :content_type => :json, :accept => :json);
216
+ write_config_file "#{newuser["name"]}.authkey", newuser["authkey"]
217
+ newuser
90
218
  end
91
219
 
92
220
  def rekey
93
- RestClient.post("#{@url}authkeys", { }, :content_type => :json, :accept => :json);
221
+ update_user_config
222
+ newkey = RestClient.post("#{url}authkeys", { }, :content_type => :json, :accept => :json);
223
+ write_config_file "#{user_name}.authkey", newkey
224
+ newkey
94
225
  end
95
226
 
96
227
  def users
@@ -98,24 +229,21 @@ module Fenris
98
229
  end
99
230
 
100
231
  def userdel(name)
101
- RestClient.delete("#{@url}users/#{name}", :content_type => :json, :accept => :json);
232
+ update_user_config
233
+ RestClient.delete("#{url}users/#{name}", :content_type => :json, :accept => :json);
102
234
  end
103
235
 
104
236
  def bind(name, binding)
105
- RestClient.put("#{@url}providers/#{name}", { :binding => binding }, :content_type => :json, :accept => :json);
237
+ update_user_config
238
+ RestClient.put("#{url}providers/#{name}", { :binding => binding }, :content_type => :json, :accept => :json);
106
239
  end
107
240
 
108
241
  def digest obj
109
242
  OpenSSL::Digest::SHA1.new(obj.to_der).to_s
110
243
  end
111
244
 
112
- def generate_csr(provider)
113
- if provider == :self
114
- subject = OpenSSL::X509::Name.parse "/DC=org/DC=fenris/CN=#{user_name}"
115
- else
116
- subject = OpenSSL::X509::Name.parse "/DC=org/DC=fenris/CN=#{user_name}:#{provider}"
117
- end
118
- log "CSR: #{subject}"
245
+ def generate_csr(cn)
246
+ subject = OpenSSL::X509::Name.parse "/DC=org/DC=fenris/CN=#{cn}"
119
247
  digest = OpenSSL::Digest::SHA1.new
120
248
  req = OpenSSL::X509::Request.new
121
249
  req.version = 0
@@ -138,7 +266,7 @@ module Fenris
138
266
  consumer_cn,provider_cn = cert_cn.split ":"
139
267
  provider_cn_ok = !!user_name
140
268
  consumer_cn_ok = !!valid_peer_names.detect { |name| name == consumer_cn }
141
- cert_ok = !!consumer_cert.verify(broker.public_key)
269
+ cert_ok = !!consumer_cert.verify(root.public_key)
142
270
  log "Consumer Cert CN '#{cert_cn}' displays correct provider? #{provider_cn_ok}"
143
271
  log "Consumer Cert CN '#{cert_cn}' in allowed_list? #{consumer_cn_ok}"
144
272
  log "Consumer Cert Signed By Broker? '#{cert_ok}'"
@@ -150,71 +278,50 @@ module Fenris
150
278
  result
151
279
  end
152
280
 
153
- def cleanup
154
- providers.each do |provider|
155
- log "Deleting socket '#{provider["binding"]}'."
156
- File.delete provider["binding"] if File.exists? provider["binding"]
157
- end
158
- ## TODO - gawd! ugly!
159
- [ *providers.map { |c| c["name"] }.map { |provider| cert_path(provider) }, cert_path, key_path ].each do |f|
160
- if File.exists? f
161
- log "Deleting file #{f}"
162
- File.delete f
163
- end
164
- end
165
- end
166
-
167
- def save_keys
168
- providers.map { |c| c["name"] }.each do |provider|
169
- File.open(cert_path(provider),"w") { |f| f.write cert(provider).to_pem } unless File.exists? cert_path(provider)
170
- end
171
- File.open(cert_path,"w") { |f| f.write cert.to_pem } unless File.exists? cert_path
172
- File.open(key_path,"w") { |f| f.write key.to_pem } unless File.exists? key_path
281
+ def root
282
+ read_config_file "root.crt"
173
283
  end
174
284
 
175
- def gen_cert(provider)
176
- cert = OpenSSL::X509::Certificate.new(RestClient.post("#{@url}cert", :csr => generate_csr(provider)))
177
- log "new cert received #{digest cert}"
178
- cert
285
+ def key
286
+ read_config_file "#{user_name}.key"
179
287
  end
180
288
 
181
- def gen_key
182
- key = OpenSSL::PKey::RSA.new(2048)
183
- log "new rsa key generated #{digest key}"
184
- key
185
- end
289
+ UPDATE_INTERVAL = 10
186
290
 
187
- def get_broker
188
- cert = OpenSSL::X509::Certificate.new(RestClient.get("#{@url}cert"))
189
- log "got cert from broker #{digest cert} #{cert.subject}"
190
- cert
291
+ def provide(local_binding)
292
+ update_config
293
+ EventMachine::run do
294
+ EventMachine::PeriodicTimer.new(UPDATE_INTERVAL) { Thread.new { update_config(false) } } if options.autosync
295
+ post_location
296
+ ProviderServer.begin(self, location, local_binding)
297
+ end
191
298
  end
192
299
 
193
- def broker
194
- @broker ||= OpenSSL::X509::Certificate.new(RestClient.get("#{@url}cert"))
195
- end
300
+ def consume(arg = nil, &blk)
301
+ update_config
196
302
 
197
- def cert(provider = :self)
198
- @cert ||= {}
199
- @cert[provider] ||= OpenSSL::X509::Certificate.new(File.read(cert_path(provider))) rescue nil
200
- @cert[provider] ||= gen_cert(provider)
201
- end
303
+ map = arg if arg.is_a? Hash
304
+ map = { arg => provider_map[arg] } if arg.is_a? String
305
+ map ||= provider_map
202
306
 
203
- def key
204
- @key ||= OpenSSL::PKey::RSA.new(File.read(key_path)) rescue nil
205
- @key ||= gen_key
206
- end
207
-
208
- def cert_path(provider = :self)
209
- if provider == :self
210
- ".#{user_name}.cert"
211
- else
212
- ".#{user_name}:#{provider}.cert"
307
+ EventMachine::run do
308
+ EventMachine::PeriodicTimer.new(UPDATE_INTERVAL) { update_config(false) } if options.autosync
309
+ map.each do |provider, binding|
310
+ ConsumerLocal.begin(self, location_of(provider), provider, binding)
311
+ end
312
+ blk.call if blk
213
313
  end
214
314
  end
215
315
 
216
- def key_path
217
- ".#{user_name}.key"
316
+ def exec(*args)
317
+ command = args.join(' ')
318
+ consume do
319
+ Thread.new do
320
+ system command
321
+ EventMachine::stop
322
+ end
323
+ end
218
324
  end
219
325
  end
220
326
  end
327
+
@@ -1,87 +1,178 @@
1
+ require 'optparse'
2
+ require 'socket'
3
+ require 'ostruct'
4
+ require 'uri'
5
+
1
6
  module Fenris
2
- module Command
3
- def self.run(command, *args)
4
- arg, name = args
7
+ class Command
8
+ def self.run(args)
5
9
  begin
6
- url = URI.parse(ENV['FENRIS_URL'] || 'https://broker.fenris.cc/')
7
- url.user ||= ENV['FENRIS_USER']
8
- url.password ||= ENV['FENRIS_AUTHKEY']
9
- client = Fenris::Client.new url
10
- help = [ "Usage: fenris help\n",
11
- " fenris info\n",
12
- " fenris bind PROVIDER BINDING\n",
13
- " fenris useradd NAME\n",
14
- " fenris userdel NAME\n",
15
- " fenris users\n",
16
- " fenris rekey\n",
17
- " fenris add CONSUMER\n",
18
- " fenris remove CONSUMER\n",
19
- " fenris exec COMMAND\n",
20
- " fenris provide BINDING\n",
21
- " fenris consume [ USER [ BINDING ] ]" ]
10
+ run_command(args)
11
+ rescue RestClient::Conflict
12
+ puts "Duplicate"
13
+ rescue RestClient::Unauthorized
14
+ puts "Unauthorized"
15
+ rescue RestClient::ResourceNotFound
16
+ puts "Resource Not Found"
17
+ rescue Fenris::NoSuchProvider => e
18
+ puts "No such provider '#{e.message}'"
19
+ exit
20
+ end
21
+ end
22
+
23
+ def self.run_command(args)
24
+ url = URI.parse(ENV['FENRIS_URL'] || 'https://broker.fenris.cc/')
25
+ args, options, help = parse(args)
26
+ fenris = Fenris::Client.new url, options
27
+ command = args.shift
28
+ arg = args.first
29
+
30
+ case [ command, args.length ]
31
+ when [ "info", 0 ]
32
+ fenris.update_user_config
33
+ printf "INFO:\n"
34
+ printf " %s\n", fenris.user_name
35
+ unless fenris.users.empty?
36
+ puts "SUBACCOUNTS:"
37
+ fenris.users.each { |c| printf " %-20s\n", c["name"] }
38
+ end
39
+ unless fenris.consumers.empty?
40
+ puts "CLIENTS:"
41
+ fenris.consumers.each { |c| printf " %-20s\n", c["name"] }
42
+ end
43
+ unless fenris.providers.empty?
44
+ puts "SERVICES:"
45
+ fenris.providers.each { |c| printf " %-20s [%s] %s\n", c["name"], c["binding"], c["location"] }
46
+ end
47
+ when [ "useradd", 1 ]
48
+ fenris.useradd(arg)
49
+ puts "user added"
50
+ when [ "userdel", 1 ]
51
+ fenris.userdel(arg)
52
+ puts "ok"
53
+ when [ "bind", 2 ]
54
+ fenris.bind(args[0], args[1])
55
+ when [ "sync", 0 ]
56
+ fenris.update_config
57
+ when [ "rekey", 0 ]
58
+ fenris.rekey
59
+ puts "rekey complete"
60
+ when [ "add", 1 ]
61
+ fenris.add(arg)
62
+ puts "ok"
63
+ when [ "remove", 1 ]
64
+ fenris.remove(arg)
65
+ puts "ok"
66
+ when [ "provide", 1 ]
67
+ fenris.provide(arg)
68
+ when [ "consume", 0 ]
69
+ fenris.consume
70
+ when [ "consume", 1 ]
71
+ fenris.consume(arg)
72
+ when [ "consume", 2 ]
73
+ fenris.consume(args[0] => args[1])
74
+ else
22
75
  case command
23
- when "users"
24
- client.users.each do |u|
25
- puts u["name"]
26
- end
27
- when "userdel"
28
- client.userdel(arg)
29
- puts "ok"
30
- when "useradd"
31
- new_user = client.useradd(arg)
32
- puts "New user created"
33
- puts "export FENRIS_USER='#{new_user["name"]}'"
34
- puts "export FENRIS_AUTHKEY='#{new_user["authkey"]}'"
35
- when "rekey"
36
- newkey = client.rekey
37
- puts "New Key Assigned:"
38
- puts "export FENRIS_AUTHKEY='#{newkey}'"
39
- when "cert"
40
- puts client.cert.to_text
41
- when "bind"
42
- client.bind(arg,name)
43
- when "add"
44
- client.add(arg)
45
- when "remove"
46
- client.remove(arg)
47
- when "info"
48
- printf "INFO:\n"
49
- printf " %s\n", client.user["name"]
50
- unless client.users.empty?
51
- puts "SUBACCOUNTS:"
52
- client.users.each { |c| printf " %-20s\n", c["name"] }
53
- end
54
- unless client.consumers.empty?
55
- puts "CLIENTS:"
56
- client.consumers.each { |c| printf " %-20s\n", c["name"] }
57
- end
58
- unless client.providers.empty?
59
- puts "SERVICES:"
60
- client.providers.each { |c| printf " %-20s %s\n", c["name"], c["location"] }
61
- end
62
- when "provide"
63
- external = "#{Socket.gethostname}:#{10001}"
64
- internal = arg
65
- Fenris::Base.provide(client, external, internal)
66
- when "consume"
67
- Fenris::Base.consume(client, arg, name)
68
- when "exec"
69
- client.quiet = true
70
- Fenris::Base.exec(client, *args)
71
- else
72
- puts command.inspect
73
- puts help
76
+ when "exec"
77
+ options.quiet = true
78
+ Fenris::Client.new(url, options).exec(*args)
79
+ else
80
+ puts help
81
+ exit
82
+ end
83
+ end
84
+ end
85
+
86
+ def self.parse(args)
87
+ extra = []
88
+ options = OpenStruct.new
89
+ options.port = 10001
90
+ options.host = Socket.gethostname
91
+ options.user = ENV['FENRIS_USER']
92
+ options.password = ENV['FENRIS_AUTHKEY']
93
+ options.config = ENV['FENRIS_CONFIG'] || "#{ENV["HOME"]}/.fenris"
94
+ options.debug = !! ENV['FENRIS_DEBUG']
95
+ options.autosync = true
96
+
97
+ if i = args.index("exec")
98
+ extra = args[i..(args.length)]
99
+ if i > 0
100
+ args = args[0..(i-1)]
101
+ else
102
+ args = []
103
+ end
104
+ end
105
+
106
+ opts = OptionParser.new do |opts|
107
+ opts.banner = "Usage: fenris [options] COMMAND"
108
+ opts.separator ""
109
+ opts.separator "Commands:"
110
+ opts.separator ""
111
+ opts.separator " fenris info # info about current user and connections"
112
+ opts.separator " fenris useradd NAME # create a new subaccount"
113
+ opts.separator " fenris userdel NAME # delete a subaccount"
114
+ opts.separator " fenris add USER # allow USER to consume service"
115
+ opts.separator " fenris remove USER # disallow USER to consume service"
116
+ opts.separator " fenris provide BINDING # provide a local service"
117
+ opts.separator " fenris consume [ USER [ BINDING ] ] # consume one (or all) remote services"
118
+ opts.separator " fenris exec COMMAND # consume services and run COMMAND. Terminates when command exits."
119
+ opts.separator " fenris sync # sync config with broker"
120
+ opts.separator " fenris rekey # generate a new authkey for the user"
121
+ opts.separator " fenris bind USER BINDING # bind a remote service to a local binding"
122
+ opts.separator ""
123
+ opts.separator "Bindings can take the form of:"
124
+ opts.separator ""
125
+ opts.separator " PORT, :PORT, HOST:PORT, /PATH/TO/UNIX_SOCKET, or -- for STDIN"
126
+ opts.separator ""
127
+ opts.separator "Options:"
128
+
129
+ opts.on("-u", "--user USER", "Connect as USER. Default is $FENRIS_USER.") do |user|
130
+ options.user = user
74
131
  end
132
+
133
+ opts.on("-c", "--config DIR", "Store config information in DIR. Default is $HOME/.fenris or $FENRIS_CONFIG.") do |dir|
134
+ options.config = dir
135
+ end
136
+
137
+ opts.on("-h", "--hostname HOST", "Hostname given to consumers on 'provide'. Default is `hostname`.") do |dir|
138
+ options.config = dir
139
+ end
140
+
141
+ opts.on("-p", "--port PORT", "") do |dir|
142
+ options.config = dir
143
+ end
144
+
145
+ opts.on("-q", "--quiet", "Do not print log messages.") do
146
+ options.quiet = true
147
+ end
148
+
149
+ opts.on("-d", "--debug", "Print debugging messages. Default is $FENRIS_DEBUG.") do
150
+ options.debug = true
151
+ end
152
+
153
+ opts.on("-a", "--[no-]autosync", "Automatically sync updates from the broker. Default is true.") do |auto|
154
+ options.autosync = auto
155
+ end
156
+
157
+ opts.on("-h", "--help", "Show this message") do
158
+ puts opts
159
+ exit
160
+ end
161
+
162
+ opts.on("--version", "Show version") do
163
+ puts Fenris::VERSION
164
+ exit
165
+ end
166
+ opts.separator ""
167
+ end
168
+
169
+ begin
170
+ commands = opts.parse!(args)
171
+ rescue OptionParser::InvalidOption
172
+ puts opts
75
173
  exit
76
174
  end
77
- rescue SystemExit
78
- rescue RestClient::Conflict
79
- puts "Duplicate"
80
- rescue RestClient::Unauthorized
81
- puts "Unauthorized"
82
- rescue RestClient::ResourceNotFound
83
- puts "Resource Not Found"
84
- exit
175
+ [ commands + extra, options, opts.to_s ]
85
176
  end
86
177
  end
87
178
  end
@@ -0,0 +1,90 @@
1
+ module Fenris
2
+ module Command
3
+ def self.run(command, *args)
4
+ arg, name = args
5
+ begin
6
+ url = URI.parse(ENV['FENRIS_URL'] || 'https://broker.fenris.cc/')
7
+ url.user ||= ENV['FENRIS_USER']
8
+ url.password ||= ENV['FENRIS_AUTHKEY']
9
+ client = Fenris::Client.new url
10
+ help = [ "Usage: fenris help\n",
11
+ " fenris info\n",
12
+ " fenris bind PROVIDER BINDING\n",
13
+ " fenris useradd NAME\n",
14
+ " fenris userdel NAME\n",
15
+ " fenris update\n",
16
+ " fenris rekey\n",
17
+ " fenris add CONSUMER\n",
18
+ " fenris remove CONSUMER\n",
19
+ " fenris exec COMMAND\n",
20
+ " fenris provide BINDING\n",
21
+ " fenris consume [ USER [ BINDING ] ]" ]
22
+ case command
23
+ when "update"
24
+ client.update_config
25
+ when "userdel"
26
+ client.update_user_config
27
+ client.userdel(arg)
28
+ puts "ok"
29
+ when "useradd"
30
+ client.update_user_config
31
+ new_user = client.useradd(arg)
32
+ puts "New user created '#{new_user["name"]}'"
33
+ when "rekey"
34
+ client.update_user_config
35
+ newkey = client.rekey
36
+ puts "Rekey Complete"
37
+ when "cert"
38
+ client.update_user_config
39
+ puts client.cert.to_text
40
+ when "bind"
41
+ client.update_user_config
42
+ client.bind(arg,name)
43
+ when "add"
44
+ client.update_user_config
45
+ client.add(arg)
46
+ when "remove"
47
+ client.update_user_config
48
+ client.remove(arg)
49
+ when "info"
50
+ client.update_user_config
51
+ printf "INFO:\n"
52
+ printf " %s\n", client.user["name"]
53
+ unless client.users.empty?
54
+ puts "SUBACCOUNTS:"
55
+ client.users.each { |c| printf " %-20s\n", c["name"] }
56
+ end
57
+ unless client.consumers.empty?
58
+ puts "CLIENTS:"
59
+ client.consumers.each { |c| printf " %-20s\n", c["name"] }
60
+ end
61
+ unless client.providers.empty?
62
+ puts "SERVICES:"
63
+ client.providers.each { |c| printf " %-20s %s\n", c["name"], c["location"] }
64
+ end
65
+ when "provide"
66
+ external = "#{Socket.gethostname}:#{10001}"
67
+ internal = arg
68
+ client.provide(external, internal)
69
+ when "consume"
70
+ client.consume(arg, name)
71
+ when "exec"
72
+ client.quiet = true
73
+ client.exec(*args)
74
+ else
75
+ puts command.inspect
76
+ puts help
77
+ end
78
+ exit
79
+ end
80
+ rescue SystemExit
81
+ rescue RestClient::Conflict
82
+ puts "Duplicate"
83
+ rescue RestClient::Unauthorized
84
+ puts "Unauthorized"
85
+ rescue RestClient::ResourceNotFound
86
+ puts "Resource Not Found"
87
+ exit
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,124 @@
1
+ require 'eventmachine'
2
+
3
+ module Fenris
4
+ class Connection < EventMachine::Connection
5
+ def self.mkbinding(action, binding)
6
+ if binding =~ /^:?(\d+)$/
7
+ [ action, "0.0.0.0", $1.to_i ]
8
+ elsif binding =~ /^(.+):(\d+)/
9
+ [ action, $1, $2.to_i ]
10
+ elsif binding == "--"
11
+ [ :attach, $stdin ]
12
+ else
13
+ [ action, binding ]
14
+ end
15
+ end
16
+
17
+ def initialize(client = nil, options = {})
18
+ @client = client
19
+ ## TODO -- peer_to_be vs @peer is a poor name choice - refactor
20
+ ## sometimes I call the proxying connection on the local box 'peer', sometimes I call the other fenris process 'peer' - confusing
21
+ @binding = options[:binding]
22
+ @peer_to_be = options[:peer]
23
+ @peer_name = options[:peer_name]
24
+ @peer_binding = options[:peer_binding]
25
+ @peer = []
26
+ end
27
+
28
+ def log msg
29
+ @client.log msg if @client
30
+ end
31
+
32
+ def push(data)
33
+ # this is the black magic to switch @peer between a connection and an array
34
+ send_data data
35
+ end
36
+
37
+ def ssl_verify_peer(pem)
38
+ @verify ||= @client.validate_peer pem, @peer_to_be, @peer_name
39
+ end
40
+
41
+ def unbind
42
+ log "Connection closed"
43
+ @unbound = true
44
+ ## TODO clean up buffer vs peer vs peer_to_be
45
+ (@peer_to_be || @peer).close_connection_after_writing rescue nil
46
+ close_connection
47
+ EventMachine::stop if @binding[0] == :attach
48
+ end
49
+
50
+ def ssl_handshake_completed
51
+ @post_ssl.call
52
+ end
53
+
54
+ def proxy(peer, leader = true)
55
+ peer.close if @unbound
56
+ @peer.each { |d| peer.push d}
57
+ @peer = peer
58
+ @peer_to_be = nil
59
+ if leader
60
+ @peer.proxy(self, false)
61
+ EventMachine::enable_proxy(self, @peer)
62
+ end
63
+ end
64
+
65
+ def proxy_target_unbound
66
+ unbind
67
+ end
68
+
69
+ def receive_data data
70
+ @peer.push data
71
+ end
72
+ end
73
+
74
+ class ProviderServer < Connection
75
+ def self.begin(client, provider_binding, local_binding)
76
+ client.log "Serving port #{local_binding} on #{provider_binding}"
77
+ server_em = mkbinding(:start_server, provider_binding)
78
+ local_em = mkbinding(:connect, local_binding)
79
+ EventMachine::__send__ *server_em, self, client, :peer_binding => local_em, :binding => server_em
80
+ end
81
+
82
+ def post_init
83
+ log "New connection - begin ssl handshake"
84
+ start_tls :private_key_file => @client.my_key_path, :cert_chain_file => @client.my_cert_path, :verify_peer => true
85
+ end
86
+
87
+ def ssl_handshake_completed
88
+ log "SSL complete - open local connection"
89
+ EventMachine::__send__ *@peer_binding, ProviderLocal, @client, :peer => self, :binding => @peer_binding
90
+ end
91
+ end
92
+
93
+ class ProviderLocal < Connection
94
+ def post_init
95
+ log "start proxying"
96
+ @peer_to_be.proxy self
97
+ end
98
+ end
99
+
100
+ class ConsumerLocal < Connection
101
+ def self.begin(client, provider_binding, provider_name, consumer_binding)
102
+ local_em = mkbinding(:start_server, consumer_binding)
103
+ provider_em = mkbinding(:connect, provider_binding)
104
+ client.log consumer_binding.inspect
105
+ EventMachine::__send__ *local_em, self, client, :peer_name => provider_name, :peer_binding => provider_em, :binding => local_em
106
+ end
107
+
108
+ def post_init
109
+ EventMachine::__send__ *@peer_binding, ConsumerProvider, @client, :peer_name => @peer_name, :peer => self, :binding => @peer_binding
110
+ end
111
+ end
112
+
113
+ class ConsumerProvider < Connection
114
+ def post_init
115
+ log "Connection to the server made, starting ssl"
116
+ start_tls :private_key_file => @client.my_key_path, :cert_chain_file => @client.cert_path(@peer_name), :verify_peer => true
117
+ end
118
+
119
+ def ssl_handshake_completed
120
+ log "SSL complete - start proxying"
121
+ @peer_to_be.proxy self
122
+ end
123
+ end
124
+ end
@@ -1,3 +1,3 @@
1
1
  module Fenris
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.8"
3
3
  end
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fenris
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.8
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 6
8
+ - 8
9
9
  prerelease: false
10
10
  platform: ruby
11
11
  authors:
@@ -13,12 +13,12 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2011-10-26 00:00:00.000000000 -07:00
16
+ date: 2011-11-01 00:00:00.000000000 -07:00
17
17
  default_executable:
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: eventmachine
21
- requirement: &2153842880 !ruby/object:Gem::Requirement
21
+ requirement: &2154437240 !ruby/object:Gem::Requirement
22
22
  none: false
23
23
  requirements:
24
24
  - - ! '>='
@@ -30,10 +30,10 @@ dependencies:
30
30
  - 10
31
31
  type: :runtime
32
32
  prerelease: false
33
- version_requirements: *2153842880
33
+ version_requirements: *2154437240
34
34
  - !ruby/object:Gem::Dependency
35
35
  name: rest-client
36
- requirement: &2153842060 !ruby/object:Gem::Requirement
36
+ requirement: &2154436420 !ruby/object:Gem::Requirement
37
37
  none: false
38
38
  requirements:
39
39
  - - ! '>='
@@ -45,10 +45,10 @@ dependencies:
45
45
  - 7
46
46
  type: :runtime
47
47
  prerelease: false
48
- version_requirements: *2153842060
48
+ version_requirements: *2154436420
49
49
  - !ruby/object:Gem::Dependency
50
50
  name: multi_json
51
- requirement: &2153841300 !ruby/object:Gem::Requirement
51
+ requirement: &2154435660 !ruby/object:Gem::Requirement
52
52
  none: false
53
53
  requirements:
54
54
  - - ! '>='
@@ -60,7 +60,7 @@ dependencies:
60
60
  - 3
61
61
  type: :runtime
62
62
  prerelease: false
63
- version_requirements: *2153841300
63
+ version_requirements: *2154435660
64
64
  description: An authentication and service location service.
65
65
  email: orion.henry@gmail.com
66
66
  executables:
@@ -69,9 +69,11 @@ extensions: []
69
69
  extra_rdoc_files: []
70
70
  files:
71
71
  - bin/fenris
72
+ - bin/xx
72
73
  - lib/fenris/client.rb
73
74
  - lib/fenris/command.rb
74
- - lib/fenris/em.rb
75
+ - lib/fenris/command.rb.old
76
+ - lib/fenris/connection.rb
75
77
  - lib/fenris/version.rb
76
78
  has_rdoc: true
77
79
  homepage: http://github.com/orionz/fenris
@@ -1,164 +0,0 @@
1
- require 'eventmachine'
2
-
3
- module Fenris
4
- module Connection
5
- def initialize
6
- @peer = []
7
- end
8
-
9
- def push(data)
10
- # this is the black magic to switch @peer between a connection and an array
11
- send_data data
12
- end
13
-
14
- def begin_ssl(opts, &blk)
15
- @post_ssl = blk
16
- start_tls :private_key_file => opts[:key_file], :cert_chain_file => opts[:cert_file], :verify_peer => true
17
- end
18
-
19
- def post_init
20
- end
21
-
22
- def validate_peer(&blk)
23
- @validator = blk;
24
- end
25
-
26
- def on_unbind(&blk)
27
- @on_unbind = blk
28
- end
29
-
30
- def ssl_verify_peer(pem)
31
- @verify ||= @validator.call(pem)
32
- end
33
-
34
- def unbind
35
- @on_unbind.call(self) if @on_unbind
36
- @unbound = true
37
- EM::stop if @signature < 3 ## this is for attach($stdin)
38
- @peer.close_connection_after_writing rescue nil
39
- close_connection
40
- end
41
-
42
- def ssl_handshake_completed
43
- @post_ssl.call
44
- end
45
-
46
- def proxy(peer)
47
- peer.close if @unbound
48
- @peer.each { |d| peer.push d}
49
- @peer = peer
50
- end
51
-
52
- def receive_data data
53
- @peer.push data
54
- end
55
- end
56
-
57
- module Base
58
- extend self
59
-
60
- UPDATE_INTERVAL = 10
61
-
62
- def listen(client, external, internal)
63
- return listen_stdio(client, external, internal) if internal == "--"
64
- EventMachine::__send__ *mkbinding(:start_server, external), Fenris::Connection do |consumer|
65
- client.log "New connection - begin ssl handshake"
66
- consumer.validate_peer { |pem| client.validate_peer pem }
67
- consumer.begin_ssl :key_file => client.key_path , :cert_file => client.cert_path do
68
- client.log "SSL complete - open local connection"
69
- EventMachine::__send__ *mkbinding(:connect, internal), Fenris::Connection do |provider|
70
- client.log "start proxying"
71
- provider.proxy consumer; consumer.proxy provider
72
- provider.on_unbind { client.log "Connection closed" }
73
- end
74
- end
75
- end
76
- end
77
-
78
- def listen_stdio(client, external, internal)
79
- EventMachine::__send__ *mkbinding(:connect, internal), Fenris::Connection do |provider|
80
- EventMachine::__send__ *mkbinding(:start_server, external), Fenris::Connection do |consumer|
81
- client.log "stdio connection - begin ssl handshake"
82
- consumer.validate_peer { |pem| client.validate_peer pem }
83
- consumer.begin_ssl :key_file => client.key_path , :cert_file => client.cert_path do
84
- client.log "SSL complete - start proxying"
85
- provider.proxy consumer; consumer.proxy provider
86
- end
87
- end
88
- end
89
- end
90
-
91
- def provide(client, external, internal)
92
- at_exit { client.cleanup }
93
-
94
- EventMachine::run do
95
- client.save_keys
96
- EventMachine::PeriodicTimer.new(UPDATE_INTERVAL) { client.async_update } unless ENV['FENRIS_NO_TIMER']
97
- client.update external
98
- client.log "Serving port #{internal} on #{external}"
99
- listen client, external, internal
100
- end
101
- end
102
-
103
- def mkbinding(action, binding)
104
- if binding =~ /^:?(\d+)$/
105
- [ action, "0.0.0.0", $1.to_i ]
106
- elsif binding =~ /^(.+):(\d+)/
107
- [ action, $1, $2.to_i ]
108
- elsif binding == "--"
109
- [ :attach, $stdin ]
110
- else
111
- [ action, binding ]
112
- end
113
- end
114
-
115
- ## really want to unify all these :(
116
- def consumer_connect(client, consumer, provider_name, provider)
117
- client.log "COMSUMER CONNECT #{consumer.inspect} #{provider_name.inspect} #{provider.inspect}"
118
- EventMachine::__send__ *mkbinding(:start_server, consumer), Fenris::Connection do |consumer|
119
- client.log "New connection: opening connection to the server"
120
- EventMachine::__send__ *mkbinding(:connect, provider), Fenris::Connection do |provider|
121
- client.log "Connection to the server made, starting ssl"
122
- provider.validate_peer { |pem| client.validate_peer pem, consumer, provider_name }
123
- provider.begin_ssl :key_file => client.key_path , :cert_file => client.cert_path(provider_name) do
124
- client.log "SSL complete - start proxying"
125
- provider.proxy consumer; consumer.proxy provider
126
- end
127
- end
128
- end
129
- end
130
-
131
- def consume(client, overide_provider = nil, override_binding = nil)
132
- at_exit { client.cleanup }
133
-
134
- client.save_keys
135
-
136
- abort "No providers" if client.providers.empty?
137
-
138
- providers = client.providers.reject { |p| overide_provider && p["name"] != overide_provider }
139
-
140
- abort "No provider named #{overide_provider}" if providers.empty?
141
- abort "Can only pass a binding for a single provider" if override_binding && providers.length != 1
142
-
143
- EventMachine::run do
144
- EventMachine::PeriodicTimer.new(UPDATE_INTERVAL) { client.async_update } unless ENV['FENRIS_NO_TIMER']
145
- providers.each do |p|
146
- binding = override_binding || p["binding"]
147
- consumer_connect(client, binding, p["name"], p["location"])
148
- end
149
- end
150
- end
151
-
152
- def exec(client, *args)
153
- command = args.join(' ')
154
- EventMachine::run do
155
- consume(client)
156
- Thread.new do
157
- system command
158
- EM::stop
159
- end
160
- end
161
- end
162
- end
163
- end
164
-