omf_common 6.0.0 → 6.0.2.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/Gemfile +4 -0
  2. data/bin/file_broadcaster.rb +56 -0
  3. data/bin/file_receiver.rb +62 -0
  4. data/bin/omf_keygen +21 -0
  5. data/bin/{monitor_topic.rb → omf_monitor_topic} +21 -8
  6. data/bin/omf_send_create +118 -0
  7. data/bin/{send_request.rb → omf_send_request} +12 -7
  8. data/example/engine_alt.rb +23 -24
  9. data/example/ls_app.yaml +21 -0
  10. data/lib/omf_common.rb +73 -12
  11. data/lib/omf_common/auth.rb +15 -0
  12. data/lib/omf_common/auth/certificate.rb +174 -0
  13. data/lib/omf_common/auth/certificate_store.rb +72 -0
  14. data/lib/omf_common/auth/ssh_pub_key_convert.rb +80 -0
  15. data/lib/omf_common/comm.rb +66 -9
  16. data/lib/omf_common/comm/amqp/amqp_communicator.rb +40 -13
  17. data/lib/omf_common/comm/amqp/amqp_file_transfer.rb +259 -0
  18. data/lib/omf_common/comm/amqp/amqp_topic.rb +14 -21
  19. data/lib/omf_common/comm/local/local_communicator.rb +31 -2
  20. data/lib/omf_common/comm/local/local_topic.rb +19 -3
  21. data/lib/omf_common/comm/topic.rb +48 -34
  22. data/lib/omf_common/comm/xmpp/communicator.rb +19 -10
  23. data/lib/omf_common/comm/xmpp/topic.rb +22 -81
  24. data/lib/omf_common/default_logging.rb +11 -0
  25. data/lib/omf_common/eventloop.rb +14 -0
  26. data/lib/omf_common/eventloop/em.rb +39 -6
  27. data/lib/omf_common/eventloop/local_evl.rb +15 -0
  28. data/lib/omf_common/exec_app.rb +29 -15
  29. data/lib/omf_common/message.rb +53 -5
  30. data/lib/omf_common/message/json/json_message.rb +149 -39
  31. data/lib/omf_common/message/xml/message.rb +112 -39
  32. data/lib/omf_common/protocol/6.0.rnc +5 -1
  33. data/lib/omf_common/protocol/6.0.rng +12 -0
  34. data/lib/omf_common/version.rb +1 -1
  35. data/omf_common.gemspec +7 -2
  36. data/test/fixture/omf_test.cert.pem +15 -0
  37. data/test/fixture/omf_test.pem +15 -0
  38. data/test/fixture/omf_test.pub +1 -0
  39. data/test/fixture/omf_test.pub.pem +6 -0
  40. data/test/omf_common/auth/certificate_spec.rb +113 -0
  41. data/test/omf_common/auth/ssh_pub_key_convert_spec.rb +13 -0
  42. data/test/omf_common/comm/topic_spec.rb +175 -0
  43. data/test/omf_common/comm/xmpp/communicator_spec.rb +15 -16
  44. data/test/omf_common/comm/xmpp/topic_spec.rb +63 -10
  45. data/test/omf_common/comm_spec.rb +66 -9
  46. data/test/omf_common/message/xml/message_spec.rb +43 -13
  47. data/test/omf_common/message_spec.rb +14 -0
  48. data/test/test_helper.rb +25 -0
  49. metadata +78 -15
  50. data/bin/send_create.rb +0 -94
@@ -0,0 +1,21 @@
1
+ #
2
+ # Describes how to run simple 'ls' on a node
3
+ #
4
+ create:
5
+ type: application
6
+ properties:
7
+ binary_path: /bin/ls
8
+ state: running
9
+ membership: apps
10
+ #environment: # (Hash) the environment variables to set prior to starting this app.
11
+ parameters:
12
+ p1:
13
+ cmd: -l
14
+ value: true
15
+ type: Boolean
16
+ order: 1
17
+ p2:
18
+ cmd: /
19
+ value: true
20
+ type: Boolean
21
+ order: 2
data/lib/omf_common.rb CHANGED
@@ -6,7 +6,7 @@ require 'omf_common/measure'
6
6
  require 'omf_common/message'
7
7
  require 'omf_common/comm'
8
8
  require 'omf_common/command'
9
- require 'omf_common/key'
9
+ require 'omf_common/auth'
10
10
  require 'omf_common/core_ext/string'
11
11
  require 'omf_common/eventloop'
12
12
 
@@ -19,8 +19,9 @@ module OmfCommon
19
19
  type: 'em'
20
20
  },
21
21
  logging: {
22
- level: 'debug',
23
-
22
+ level: {
23
+ default: 'debug'
24
+ },
24
25
  appenders: {
25
26
  stdout: {
26
27
  date_pattern: '%H:%M:%S',
@@ -35,8 +36,35 @@ module OmfCommon
35
36
  type: :em
36
37
  },
37
38
  logging: {
38
- level: 'info',
39
+ level: {
40
+ default: 'info'
41
+ },
42
+ appenders: {
43
+ file: {
44
+ log_dir: '/var/log',
45
+ #log_file: 'foo.log',
46
+ date_pattern: '%F %T %z',
47
+ pattern: '[%d] %-5l %c: %m\n'
48
+ }
49
+ }
39
50
 
51
+ }
52
+ },
53
+ daemon: {
54
+ daemonize: {
55
+ dir_mode: :script,
56
+ dir: '/tmp',
57
+ backtrace: true,
58
+ log_dir: '/var/log',
59
+ log_output: true
60
+ },
61
+ eventloop: {
62
+ type: :em
63
+ },
64
+ logging: {
65
+ level: {
66
+ default: 'info'
67
+ },
40
68
  appenders: {
41
69
  file: {
42
70
  log_dir: '/var/log',
@@ -54,8 +82,9 @@ module OmfCommon
54
82
  },
55
83
  eventloop: { type: :local},
56
84
  logging: {
57
- level: 'debug',
58
-
85
+ level: {
86
+ default: 'debug'
87
+ },
59
88
  appenders: {
60
89
  stdout: {
61
90
  date_pattern: '%H:%M:%S',
@@ -65,7 +94,7 @@ module OmfCommon
65
94
  }
66
95
  }
67
96
  },
68
- test_dev: {
97
+ test_daemon: {
69
98
  daemonize: {
70
99
  dir_mode: :script,
71
100
  dir: '/tmp',
@@ -74,10 +103,12 @@ module OmfCommon
74
103
  log_output: true
75
104
  },
76
105
  eventloop: {
77
- type: :local
106
+ type: :em
78
107
  },
79
108
  logging: {
80
- level: 'debug',
109
+ level: {
110
+ default: 'debug'
111
+ },
81
112
  appenders: {
82
113
  file: {
83
114
  log_dir: '/tmp',
@@ -120,9 +151,14 @@ module OmfCommon
120
151
  unless copts = opts[:communication]
121
152
  raise "Missing :communication description"
122
153
  end
123
- eopts = opts[:eventloop]
154
+
155
+ if aopts = opts[:auth]
156
+ require 'omf_common/auth/credential_store'
157
+ OmfCommon::Auth::CredentialStore.init(aopts)
158
+ end
124
159
 
125
160
  # Initialise event loop
161
+ eopts = opts[:eventloop]
126
162
  Eventloop.init(eopts)
127
163
  # start eventloop immediately if we received a run block
128
164
  eventloop.run do
@@ -151,6 +187,10 @@ module OmfCommon
151
187
  # :same - Look in the same directory as '$0'
152
188
  # :remove_root ROOT_NAME: Remove the root node. Throw exception if not ROOT_NAME
153
189
  # :wait_for_readable SECS: Wait until the yaml file becomes readable. Check every SECS
190
+ # :erb_process flag: Run the content of the loaded file through ERB first before YAML parsing
191
+ # :erb_safe_level level: If safe_level is set to a non-nil value, ERB code will be run in a
192
+ # separate thread with $SAFE set to the provided level.
193
+ # :erb_binding binding: Optional binding given to ERB#result
154
194
  #
155
195
  def self.load_yaml(file_name, opts = {})
156
196
  if path_opt = opts[:path]
@@ -167,7 +207,14 @@ module OmfCommon
167
207
  sleep readable_check # wait until file shows up
168
208
  end
169
209
  end
170
- yh = YAML.load_file(file_name)
210
+
211
+ str = File.read(file_name)
212
+ if opts[:erb_process]
213
+ require 'erb'
214
+ str = ERB.new(str, opts[:erb_safe_level]).result(opts[:erb_binding] || binding)
215
+ end
216
+ yh = YAML.load(str)
217
+
171
218
  if opts[:symbolize_keys]
172
219
  yh = _rec_sym_keys(yh)
173
220
  end
@@ -209,11 +256,25 @@ module OmfCommon
209
256
  end
210
257
  end
211
258
  if level = opts[:level]
212
- logger.level = level.to_sym
259
+ if level.is_a? Hash
260
+ # package level settings
261
+ level.each do |name, lvl|
262
+ if name.to_s == 'default'
263
+ logger.level = lvl.to_sym
264
+ else
265
+ Logging.logger[name.to_s].level = lvl.to_sym
266
+ end
267
+ end
268
+ else
269
+ logger.level = level.to_sym
270
+ end
213
271
  end
214
272
  end
215
273
 
216
274
  def self._rec_merge(this_hash, other_hash)
275
+ # if the dominant side is not a hash we stop recursing and pick the primitive value
276
+ return other_hash unless other_hash.is_a? Hash
277
+
217
278
  r = {}
218
279
  this_hash.merge(other_hash) do |key, oldval, newval|
219
280
  r[key] = oldval.is_a?(Hash) ? _rec_merge(oldval, newval) : newval
@@ -0,0 +1,15 @@
1
+
2
+
3
+ module OmfCommon
4
+ module Auth
5
+
6
+ class AuthException < StandardError; end
7
+
8
+ def self.init(opts = {})
9
+ CertificateStore.init(opts)
10
+ end
11
+ end
12
+ end
13
+
14
+ require 'omf_common/auth/certificate_store'
15
+ require 'omf_common/auth/certificate'
@@ -0,0 +1,174 @@
1
+ require 'openssl'
2
+ require 'omf_common/auth'
3
+ require 'omf_common/auth/ssh_pub_key_convert'
4
+
5
+ module OmfCommon::Auth
6
+
7
+ class Certificate
8
+ DEF_DOMAIN_NAME = 'acme'
9
+ DEF_DURATION = 3600
10
+
11
+ BEGIN_CERT = "-----BEGIN CERTIFICATE-----\n"
12
+ END_CERT = "\n-----END CERTIFICATE-----\n"
13
+ @@serial = 0
14
+
15
+ # @param [String] name unique name of the entity (resource name)
16
+ # @param [String] type type of the entity (resource type)
17
+ # @param [String] domain of the resource
18
+ #
19
+ def self.create(address, name, type, domain = DEF_DOMAIN_NAME, issuer = nil, not_before = Time.now, duration = 3600, key = nil)
20
+ subject = _create_name(name, type, domain)
21
+ if key.nil?
22
+ key, digest = _create_key()
23
+ else
24
+ digest = _create_digest
25
+ end
26
+
27
+ c = _create_x509_cert(address, subject, key, digest, issuer, not_before, duration)
28
+ c[:address] = address if address
29
+ self.new c
30
+ end
31
+
32
+ # @param [String] pem is the content of existing x509 cert
33
+ # @param [OpenSSL::PKey::RSA|String] key is the private key which can be attached to the instance for signing.
34
+ def self.create_from_x509(pem, key = nil)
35
+ unless pem.start_with? BEGIN_CERT
36
+ pem = "#{BEGIN_CERT}#{pem}#{END_CERT}"
37
+ end
38
+ cert = OpenSSL::X509::Certificate.new(pem)
39
+
40
+ key = OpenSSL::PKey::RSA.new(key) if key && key.is_a?(String)
41
+
42
+ if key && !cert.check_private_key(key)
43
+ raise ArgumentError, "Private key provided could not match the public key of given certificate"
44
+ end
45
+ self.new({ cert: cert, key: key })
46
+ end
47
+
48
+ # Returns an array with a new RSA key and a SHA1 digest
49
+ #
50
+ def self._create_key(size = 2048)
51
+ [OpenSSL::PKey::RSA.new(size), OpenSSL::Digest::SHA1.new]
52
+ end
53
+
54
+ def self._create_digest
55
+ OpenSSL::Digest::SHA1.new
56
+ end
57
+
58
+ # @param [String] name unique name of the entity (resource name)
59
+ # @param [String] type type of the entity (resource type)
60
+ #
61
+ def self._create_name(name, type, domain = DEF_DOMAIN_NAME)
62
+ OpenSSL::X509::Name.new [['CN', "frcp//#{domain}//frcp.#{type}.#{name}"]], {}
63
+ end
64
+
65
+ # Create a X509 certificate
66
+ #
67
+ # @param [] address
68
+ # @return {cert, key}
69
+ #
70
+ def self._create_x509_cert(address, subject, key, digest = nil,
71
+ issuer = nil, not_before = Time.now, duration = DEF_DURATION, extensions = [])
72
+ extensions << ["subjectAltName", "URI:#{address}", false] if address
73
+
74
+ cert = OpenSSL::X509::Certificate.new
75
+ cert.version = 2
76
+ # TODO change serial to non-sequential secure random numbers for production use
77
+ cert.serial = (@@serial += 1)
78
+ cert.subject = subject
79
+ cert.public_key = key.public_key
80
+ cert.not_before = not_before
81
+ cert.not_after = not_before + duration
82
+ unless extensions.empty?
83
+ issuer_cert = issuer ? issuer.to_x509 : cert
84
+ ef = OpenSSL::X509::ExtensionFactory.new
85
+ ef.subject_certificate = cert
86
+ ef.issuer_certificate = issuer_cert
87
+ extensions.each{|oid, value, critical|
88
+ cert.add_extension(ef.create_extension(oid, value, critical))
89
+ }
90
+ end
91
+ if issuer
92
+ cert.issuer = issuer.subject
93
+ cert.sign(issuer.key, issuer.digest)
94
+ else
95
+ # self signed
96
+ cert.issuer = subject
97
+ cert.sign(key, digest)
98
+ end
99
+ { cert: cert, key: key }
100
+ end
101
+
102
+ attr_reader :address, :subject, :key, :digest
103
+
104
+ def initialize(opts)
105
+ if @cert = opts[:cert]
106
+ @subject = @cert.subject
107
+ end
108
+ unless @address = opts[:address]
109
+ # try to see it it is in cert
110
+ if @cert
111
+ @cert.extensions.each do |ext|
112
+ if ext.oid == 'subjectAltName'
113
+ @address = ext.value[4 .. -1] # strip off 'URI:'
114
+ end
115
+ end
116
+ end
117
+ end
118
+ if @key = opts[:key]
119
+ @digest = opts[:digest] || OpenSSL::Digest::SHA1.new
120
+ end
121
+ unless @subject ||= opts[:subject]
122
+ name = opts[:name]
123
+ type = opts[:type]
124
+ domain = opts[:domain]
125
+ @subject = _create_name(name, type, domain)
126
+ end
127
+ @cert ||= _create_x509_cert(@address, @subject, @key, @digest)[:cert]
128
+ end
129
+
130
+ def create_for(address, name, type, domain = DEF_DOMAIN_NAME, duration = 3600, key = nil)
131
+ raise ArgumentError, "Address required" unless address
132
+ cert = self.class.create(address, name, type, domain, self, Time.now, duration, key)
133
+ CertificateStore.instance.register(cert, address)
134
+ cert
135
+ end
136
+
137
+ # Return the X509 certificate. If it hasn't been passed in, return a self-signed one
138
+ def to_x509()
139
+ @cert
140
+ end
141
+
142
+ def can_sign?
143
+ !@key.nil? && @key.private?
144
+ end
145
+
146
+ def to_pem
147
+ to_x509.to_pem
148
+ end
149
+
150
+ def to_pem_compact
151
+ to_pem.lines.to_a[1 ... -1].join.strip
152
+ end
153
+
154
+ def verify_cert
155
+ if @cert.issuer == self.subject # self signed cert
156
+ @cert.verify(@cert.public_key)
157
+ else
158
+ @cert.verify(CertificateStore.instance.cert_for(@cert.issuer).to_x509.public_key)
159
+ end
160
+ end
161
+
162
+ # Will return one of the following
163
+ #
164
+ # :HS256, :HS384, :HS512, :RS256, :RS384, :RS512, :ES256, :ES384, :ES512
165
+ #
166
+ # def key_algorithm
167
+ #
168
+ # end
169
+
170
+ def to_s
171
+ "#<#{self.class} addr=#{@address} subj=#{@subject} can-sign=#{@key != nil}>"
172
+ end
173
+ end # class
174
+ end # module
@@ -0,0 +1,72 @@
1
+ require 'openssl'
2
+
3
+ require 'omf_common/auth'
4
+
5
+ #require 'singleton'
6
+
7
+ # module OmfCommon
8
+ # class Key
9
+ # include Singleton
10
+ #
11
+ # attr_accessor :private_key
12
+ #
13
+ # def import(filename)
14
+ # self.private_key = OpenSSL::PKey.read(File.read(filename))
15
+ # end
16
+ # end
17
+ # end
18
+
19
+ module OmfCommon::Auth
20
+
21
+ class MissingPrivateKeyException < AuthException; end
22
+
23
+ class CertificateStore
24
+
25
+
26
+ @@instance = nil
27
+
28
+ def self.init(opts = {})
29
+ if @@instance
30
+ raise "CertificateStore already iniitalised"
31
+ end
32
+ @@instance = self.new(opts)
33
+ end
34
+
35
+ def self.instance
36
+ throw "CertificateStore not initialized" unless @@instance
37
+ @@instance
38
+ end
39
+
40
+ def register(certificate, address = nil)
41
+ if address ||= certificate.address
42
+ @certs[address] = certificate if address
43
+ else
44
+ warn "Register certificate without address - #{certificate}"
45
+ end
46
+ @certs[certificate.subject] = certificate
47
+ end
48
+
49
+ def register_x509(cert_pem, address = nil)
50
+ if (cert = Certificate.create_from_x509(cert_pem))
51
+ debug "REGISTERED #{cert}"
52
+ register(cert, address)
53
+ end
54
+ end
55
+
56
+ def cert_for(url)
57
+ @certs[url]
58
+ end
59
+
60
+
61
+ private
62
+ def initialize(opts)
63
+ @certs = {}
64
+ if store = opts[:store]
65
+ else
66
+ @store = {private: {}, public: {}}
67
+ end
68
+ @serial = 0
69
+ end
70
+ end # class
71
+
72
+ end # module
@@ -0,0 +1,80 @@
1
+ require 'base64'
2
+ require 'openssl'
3
+ require 'omf_common/auth'
4
+
5
+ module OmfCommon::Auth
6
+ # This file provides a converter that accepts an SSH public key string
7
+ # and converts it to an OpenSSL::PKey::RSA object for use in verifying
8
+ # received messages. (DSA support pending).
9
+ #
10
+ class SSHPubKeyConvert
11
+ # Unpack a 4-byte unsigned integer from the +bytes+ array.
12
+ #
13
+ # Returns a pair (+u32+, +bytes+), where +u32+ is the extracted
14
+ # unsigned integer, and +bytes+ is the remainder of the original
15
+ # +bytes+ array that follows +u32+.
16
+ #
17
+ def self.unpack_u32(bytes)
18
+ return bytes.unpack("N")[0], bytes[4..-1]
19
+ end
20
+
21
+ # Unpack a string from the +bytes+ array. Exactly +len+ bytes will
22
+ # be extracted.
23
+ #
24
+ # Returns a pair (+string+, +bytes+), where +string+ is the
25
+ # extracted string (of length +len+), and +bytes+ is the remainder
26
+ # of the original +bytes+ array that follows +string+.
27
+ #
28
+ def self.unpack_string(bytes, len)
29
+ return bytes.unpack("A#{len}")[0], bytes[len..-1]
30
+ end
31
+
32
+ # Convert a string in SSH public key format to a key object
33
+ # suitable for use with OpenSSL. If the key is an RSA key then an
34
+ # OpenSSL::PKey::RSA object is returned. If the key is a DSA key
35
+ # then an OpenSSL::PKey::DSA object is returned. In either case,
36
+ # the object returned is suitable for encrypting data or verifying
37
+ # signatures, but cannot be used for decrypting or signing.
38
+ #
39
+ # The +keystring+ should be a single line, as per an SSH public key
40
+ # file as generated by +ssh-keygen+, or a line from an SSH
41
+ # +authorized_keys+ file.
42
+ #
43
+ def self.convert(keystring)
44
+ (type, b64, id) = keystring.split(' ')
45
+ decoded_key = Base64.decode64(b64)
46
+ (n, bytes) = unpack_u32(decoded_key)
47
+ (keytype, bytes) = unpack_string(bytes, n)
48
+
49
+ if keytype == "ssh-rsa"
50
+ (n, bytes) = unpack_u32(bytes)
51
+ (estr, bytes) = unpack_string(bytes, n)
52
+ (n, bytes) = unpack_u32(bytes)
53
+ (nstr, bytes) = unpack_string(bytes, n)
54
+
55
+ key = OpenSSL::PKey::RSA.new
56
+ key.n = OpenSSL::BN.new(nstr, 2)
57
+ key.e = OpenSSL::BN.new(estr, 2)
58
+ key
59
+ elsif keytype == 'ssh-dss'
60
+ (n, bytes) = unpack_u32(bytes)
61
+ (pstr, bytes) = unpack_string(bytes, n)
62
+ (n, bytes) = unpack_u32(bytes)
63
+ (qstr, bytes) = unpack_string(bytes, n)
64
+ (n, bytes) = unpack_u32(bytes)
65
+ (gstr, bytes) = unpack_string(bytes, n)
66
+ (n, bytes) = unpack_u32(bytes)
67
+ (pkstr, bytes) = unpack_string(bytes, n)
68
+
69
+ key = OpenSSL::PKey::DSA.new
70
+ key.p = OpenSSL::BN.new(pstr, 2)
71
+ key.q = OpenSSL::BN.new(qstr, 2)
72
+ key.g = OpenSSL::BN.new(gstr, 2)
73
+ key.pub_key = OpenSSL::BN.new(pkstr, 2)
74
+ key
75
+ else
76
+ raise ArgumentError, "Unknown key type '#{keytype}'"
77
+ end
78
+ end
79
+ end
80
+ end