zyre 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,14 +10,23 @@
10
10
  #ifndef ZYRE_EXT_H_90322ABD
11
11
  #define ZYRE_EXT_H_90322ABD
12
12
 
13
+ #include "extconf.h"
14
+
13
15
  #include <ruby.h>
14
16
  #include <ruby/intern.h>
15
17
  #include <ruby/thread.h>
16
18
  #include <ruby/encoding.h>
19
+ #include <ruby/version.h>
20
+
21
+ #ifdef HAVE_ZCERT_UNSET_META
22
+ # define CZMQ_BUILD_DRAFT_API 1
23
+ #endif
24
+ #ifdef HAVE_ZYRE_SET_BEACON_PEER_PORT
25
+ # define ZYRE_BUILD_DRAFT_API 1
26
+ #endif
17
27
 
18
28
  #include "zyre.h"
19
29
  #include "czmq.h"
20
- #include "extconf.h"
21
30
 
22
31
  #ifndef TRUE
23
32
  # define TRUE 1
@@ -28,7 +37,8 @@
28
37
  #endif
29
38
 
30
39
 
31
- // For synthesized events
40
+ // For synthesized events, sizeof calculations, copied from the czmq and zyre
41
+ // source
32
42
  struct _zyre_event_t {
33
43
  char *type; // Event type as string
34
44
  char *peer_uuid; // Sender UUID as string
@@ -40,6 +50,7 @@ struct _zyre_event_t {
40
50
  };
41
51
 
42
52
 
53
+
43
54
  /* --------------------------------------------------------------
44
55
  * Declarations
45
56
  * -------------------------------------------------------------- */
@@ -74,6 +85,9 @@ extern VALUE rzyre_mZyre;
74
85
  extern VALUE rzyre_cZyreNode;
75
86
  extern VALUE rzyre_cZyreEvent;
76
87
  extern VALUE rzyre_cZyrePoller;
88
+ extern VALUE rzyre_cZyreCert;
89
+
90
+ extern zactor_t *auth_actor;
77
91
 
78
92
 
79
93
  /* --------------------------------------------------------------
@@ -83,6 +97,7 @@ extern VALUE rzyre_cZyrePoller;
83
97
  #define IsZyreNode( obj ) rb_obj_is_kind_of( (obj), rzyre_cZyreNode )
84
98
  #define IsZyreEvent( obj ) rb_obj_is_kind_of( (obj), rzyre_cZyreEvent )
85
99
  #define IsZyrePoller( obj ) rb_obj_is_kind_of( (obj), rzyre_cZyrePoller )
100
+ #define IsZyreCert( obj ) rb_obj_is_kind_of( (obj), rzyre_cZyreCert )
86
101
 
87
102
  /* --------------------------------------------------------------
88
103
  * Utility functions
@@ -98,8 +113,10 @@ extern void Init_zyre_ext _(( void ));
98
113
  extern void rzyre_init_node _(( void ));
99
114
  extern void rzyre_init_event _(( void ));
100
115
  extern void rzyre_init_poller _(( void ));
116
+ extern void rzyre_init_cert _(( void ));
101
117
 
102
118
  extern zyre_t * rzyre_get_node _(( VALUE ));
119
+ extern zcert_t * rzyre_get_cert _(( VALUE ));
103
120
 
104
121
  #endif /* end of include guard: ZYRE_EXT_H_90322ABD */
105
122
 
data/lib/zyre.rb CHANGED
@@ -1,35 +1,28 @@
1
1
  # -*- ruby -*-
2
2
  # frozen_string_literal: true
3
3
 
4
+ require 'set'
4
5
  require 'loggability'
5
6
 
6
7
  require_relative 'zyre_ext'
7
8
 
8
9
 
9
- # A binding to libzyre
10
+ #--
11
+ # See also: ext/zyre_ext/zyre_ext.c
10
12
  module Zyre
11
13
  extend Loggability
12
14
 
13
15
 
14
16
  # Gem version (semver)
15
- VERSION = '0.3.1'
17
+ VERSION = '0.4.0'
16
18
 
17
19
 
18
20
  # Set up a logger for Zyre classes
19
21
  log_as :zyre
20
22
 
21
23
 
22
- ### Wait on one or more +nodes+ to become readable, returning the first one that does
23
- ### or +nil+ if the +timeout+ is zero or greater and at least that many seconds elapse.
24
- ### Specify a +timeout+ of -1 to wait indefinitely. The timeout is in floating-point
25
- ### seconds.
26
- ###
27
- ### Raises an Interrupt if the call is interrupted or the ZMQ context is destroyed.
28
- def self::wait( *nodes, timeout: -1 )
29
- nodes = nodes.flatten
30
- return nil if nodes.empty?
31
- return self.wait2( nodes, timeout )
32
- end
24
+ @whitelisted_ips = Set.new
25
+ @blacklisted_ips = Set.new
33
26
 
34
27
 
35
28
  ### If the given +key+ is a Symbol, transform it into an RFC822-style header key. If
@@ -50,4 +43,36 @@ module Zyre
50
43
  transform_values {|v| v.to_s.encode('us-ascii') }
51
44
  end
52
45
 
46
+
47
+ ### Allow (whitelist) a list of IP +addresses+. For NULL, all clients from
48
+ ### these addresses will be accepted. For PLAIN and CURVE, they will be
49
+ ### allowed to continue with authentication. You can call this method
50
+ ### multiple times to whitelist more IP addresses. If you whitelist one
51
+ ### or more addresses, any non-whitelisted addresses are treated as
52
+ ### blacklisted:
53
+ def self::allow( *addresses )
54
+ @whitelisted_ips.merge( addresses )
55
+ end
56
+
57
+
58
+ ### Deny (blacklist) a list of IP +addresses+. For all security mechanisms,
59
+ ### this rejects the connection without any further authentication. Use
60
+ ### either a whitelist, or a blacklist, not not both. If you define both
61
+ ### a whitelist and a blacklist, only the whitelist takes effect:
62
+ def self::deny( *addresses )
63
+ @blacklisted_ips.merge( addresses )
64
+ end
65
+
66
+
67
+ ### Returns +true+ if the underlying Czmq library was built with draft APIs.
68
+ def self::has_draft_czmq_apis?
69
+ return Zyre::BUILT_WITH_DRAFT_CZMQ_API ? true : false
70
+ end
71
+
72
+
73
+ ### Returns +true+ if the underlying Zyre library was built with draft APIs.
74
+ def self::has_draft_apis?
75
+ return Zyre::BUILT_WITH_DRAFT_API ? true : false
76
+ end
77
+
53
78
  end # module Zyre
data/lib/zyre/cert.rb ADDED
@@ -0,0 +1,75 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'loggability'
5
+
6
+ require 'zyre' unless defined?( Zyre )
7
+
8
+
9
+ #--
10
+ # See also: ext/zyre_ext/cert.c
11
+ class Zyre::Cert
12
+ extend Loggability
13
+
14
+
15
+ # The placeholder key that is set as the secret key for a public certificate.
16
+ EMPTY_KEY = "\x00" * 32
17
+
18
+
19
+ # Use the Zyre module's logger
20
+ log_to :zyre
21
+
22
+
23
+ # Set up some more Rubyish aliases
24
+ alias_method :private_key, :secret_key
25
+ alias_method :==, :eql?
26
+
27
+
28
+ ### Fetch the value for the cert metadata with the given +name+.
29
+ def []( name )
30
+ return self.meta( name.to_s )
31
+ end
32
+
33
+
34
+ ### Set the value for the cert metadata with the given +name+ to +value+.
35
+ def []=( name, value )
36
+ return self.set_meta( name.to_s, value.to_s )
37
+ end
38
+
39
+
40
+ ### Return the metadata from the cert as a Hash.
41
+ def meta_hash
42
+ hash = self.meta_keys.each_with_object( {} ) do |key, h|
43
+ h[ key ] = self[ key ]
44
+ end
45
+ hash.freeze
46
+ return hash
47
+ end
48
+
49
+
50
+ ### Delete the value for the cert metadata with the given +name+. Requires
51
+ ### CZMQ to have been built with Draft APIs.
52
+ def delete( name )
53
+ name = name.to_s
54
+
55
+ deleted_val = self[ name ]
56
+ self.unset_meta( name )
57
+
58
+ return deleted_val
59
+ end
60
+
61
+
62
+ ### Returns +true+ if the certificate has a secret key.
63
+ def have_secret_key?
64
+ return self.secret_key != EMPTY_KEY
65
+ end
66
+
67
+
68
+ ### Apply the certificate to the specified +zyre_node+, i.e. use the
69
+ ### cert for CURVE security. If the receiving certificate doesn't have a
70
+ ### private key, an exception will be raised.
71
+ def apply( zyre_node )
72
+ return zyre_node.zcert = self
73
+ end
74
+
75
+ end # class Zyre::Cert
data/lib/zyre/event.rb CHANGED
@@ -25,6 +25,7 @@ class Zyre::Event
25
25
  autoload :Exit, 'zyre/event/exit'
26
26
  autoload :Join, 'zyre/event/join'
27
27
  autoload :Leave, 'zyre/event/leave'
28
+ autoload :Leader, 'zyre/event/leader'
28
29
  autoload :Shout, 'zyre/event/shout'
29
30
  autoload :Silent, 'zyre/event/silent'
30
31
  autoload :Stop, 'zyre/event/stop'
@@ -6,4 +6,9 @@ require 'zyre/event' unless defined?( Zyre::Event )
6
6
 
7
7
  class Zyre::Event::Stop < Zyre::Event
8
8
 
9
+ ### Provide the details of the inspect message.
10
+ def inspect_details
11
+ return " node is stopping"
12
+ end
13
+
9
14
  end # class Zyre::Event::Stop
data/lib/zyre/testing.rb CHANGED
@@ -221,6 +221,18 @@ module Zyre::Testing
221
221
  end
222
222
 
223
223
 
224
+ ### Generate a LEADER event.
225
+ def leader( **overrides )
226
+ uuid = overrides.delete( :peer_uuid ) || self.peer_uuid
227
+ config = {
228
+ peer_name: self.peer_name,
229
+ group: self.group
230
+ }.merge( overrides )
231
+
232
+ return Zyre::Event.synthesize( :leader, uuid, **config )
233
+ end
234
+
235
+
224
236
  ### Generate an EXIT event.
225
237
  def exit( **overrides )
226
238
  uuid = overrides.delete( :peer_uuid ) || self.peer_uuid
@@ -231,6 +243,17 @@ module Zyre::Testing
231
243
  return Zyre::Event.synthesize( :exit, uuid, **config )
232
244
  end
233
245
 
246
+
247
+ ### Generate a STOP event.
248
+ def stop( **overrides )
249
+ uuid = overrides.delete( :peer_uuid ) || self.peer_uuid
250
+ config = {
251
+ peer_name: self.peer_name
252
+ }.merge( overrides )
253
+
254
+ return Zyre::Event.synthesize( :stop, uuid, **config )
255
+ end
256
+
234
257
  end # class EventFactory
235
258
 
236
259
 
@@ -260,6 +283,7 @@ module Zyre::Testing
260
283
  def started_node( name=nil )
261
284
  node = Zyre::Node.new( name )
262
285
  node.endpoint = 'inproc://node-test-%s' % [ SecureRandom.hex(16) ]
286
+
263
287
  yield( node ) if block_given?
264
288
 
265
289
  if @gossip_endpoint
data/spec/spec_helper.rb CHANGED
@@ -55,6 +55,10 @@ RSpec.configure do |config|
55
55
  Zyre::Testing.check_fdmax
56
56
  end
57
57
 
58
+ config.filter_run_excluding( :draft_api ) unless Zyre.has_draft_apis?
59
+ config.filter_run_excluding( :czmq_draft_api ) unless Zyre.has_draft_czmq_apis?
60
+ config.filter_run_excluding( :no_czmq_draft_api ) if Zyre.has_draft_czmq_apis?
61
+
58
62
  config.include( Zyre::Testing )
59
63
  config.include( Loggability::SpecHelpers )
60
64
  end
@@ -0,0 +1,218 @@
1
+ #!/usr/bin/env rspec -cfd
2
+
3
+ require_relative '../spec_helper'
4
+
5
+ require 'tempfile'
6
+
7
+ require 'zyre/cert'
8
+
9
+
10
+ RSpec.describe( Zyre::Cert ) do
11
+
12
+ let( :cert_file ) { Tempfile.new('zyre_cert_test') }
13
+
14
+
15
+ it "can be created in-memory" do
16
+ expect( described_class.new ).to be_a( described_class )
17
+ end
18
+
19
+
20
+ it "can be created from a keypair" do
21
+ cert = described_class.new
22
+
23
+ result = described_class.from( cert.public_key, cert.private_key )
24
+
25
+ expect( result ).to be_a( described_class )
26
+ expect( result ).to eq( cert )
27
+ end
28
+
29
+
30
+ it "can be saved to and loaded from a file" do
31
+ cert = described_class.new
32
+ cert.save( cert_file.path )
33
+
34
+ result = described_class.load( cert_file.path )
35
+
36
+ expect( result ).to be_a( described_class )
37
+ expect( result ).to eq( cert )
38
+ end
39
+
40
+
41
+ it "can be saved as a public cert to and loaded from a file" do
42
+ cert = described_class.new
43
+ cert.save_public( cert_file.path )
44
+
45
+ result = described_class.load( cert_file.path )
46
+
47
+ expect( result ).to be_a( described_class )
48
+ expect( result.public_key ).to eq( cert.public_key )
49
+ expect( result.secret_key ).to eq( Zyre::Cert::EMPTY_KEY )
50
+ end
51
+
52
+
53
+ it "can be saved as a secret cert to and loaded from a file" do
54
+ cert = described_class.new
55
+ cert.save_secret( cert_file.path )
56
+
57
+ result = described_class.load( cert_file.path )
58
+
59
+ expect( result ).to be_a( described_class )
60
+ expect( result ).to eq( cert )
61
+ end
62
+
63
+
64
+ it "knows what its public key is" do
65
+ cert = described_class.new
66
+
67
+ key = cert.public_key
68
+
69
+ expect( key.encoding ).to eq( Encoding::ASCII_8BIT )
70
+ expect( key.bytesize ).to eq( 32 )
71
+ end
72
+
73
+
74
+ it "knows what its secret key is" do
75
+ cert = described_class.new
76
+
77
+ key = cert.secret_key
78
+
79
+ expect( key.encoding ).to eq( Encoding::ASCII_8BIT )
80
+ expect( key.bytesize ).to eq( 32 )
81
+ end
82
+
83
+
84
+ it "knows what its Z85-armored public key is" do
85
+ cert = described_class.new
86
+
87
+ key = cert.public_txt
88
+
89
+ expect( key.encoding ).to eq( Encoding::US_ASCII )
90
+ expect( key.length ).to eq( 40 )
91
+ end
92
+
93
+
94
+ it "knows what its Z85-armored secret key is" do
95
+ cert = described_class.new
96
+
97
+ key = cert.secret_txt
98
+
99
+ expect( key.encoding ).to eq( Encoding::US_ASCII )
100
+ expect( key.length ).to eq( 40 )
101
+ end
102
+
103
+
104
+ it "can set metadata on the cert" do
105
+ cert = described_class.new
106
+
107
+ cert[ :username ] = 'timmy'
108
+
109
+ expect( cert['username'] ).to eq( 'timmy' )
110
+ end
111
+
112
+
113
+ it "stringifies non-string metadata on the cert" do
114
+ cert = described_class.new
115
+
116
+ cert[ :euid ] = 12
117
+
118
+ expect( cert[:euid] ).to eq( '12' )
119
+ end
120
+
121
+
122
+ it "fetches non-existant values as nil" do
123
+ cert = described_class.new
124
+
125
+ expect( cert[:nonexistant_key] ).to be_nil
126
+ end
127
+
128
+
129
+ it "silently ignores overwrites of a metadata value if not built with Drafts", :no_czmq_draft_api do
130
+ cert = described_class.new
131
+
132
+ cert.set_meta( 'foo', 'bar' )
133
+ cert.set_meta( 'foo', 'baz' )
134
+
135
+ expect( cert.meta('foo') ).to eq( 'bar' )
136
+ end
137
+
138
+
139
+ it "can overwrite a metadata value if built with Drafts", :czmq_draft_api do
140
+ cert = described_class.new
141
+
142
+ cert.set_meta( 'foo', 'bar' )
143
+ cert.set_meta( 'foo', 'baz' )
144
+
145
+ expect( cert.meta('foo') ).to eq( 'baz' )
146
+ end
147
+
148
+
149
+ it "knows what metadata has been set on the cert" do
150
+ cert = described_class.new
151
+
152
+ cert[ :euid ] = 0
153
+ cert[ :username ] = 'jrandom'
154
+ cert[ 'firstname' ] = 'James'
155
+ cert[ :lastname ] = 'Random'
156
+ cert[ 'key 2' ] = 'cf67c750-c704-4ef7-ab83-ecb2cd2e326c'
157
+
158
+ expect( cert.meta_keys ).
159
+ to contain_exactly( 'euid', 'username', 'firstname', 'lastname', 'key 2' )
160
+ end
161
+
162
+
163
+ it "can delete one of its metadata key-value pairs", :czmq_draft_api do
164
+ cert = described_class.new
165
+
166
+ cert[ :euid ] = 0
167
+ cert[ :username ] = 'jrandom'
168
+ cert[ 'firstname' ] = 'James'
169
+ cert[ :lastname ] = 'Random'
170
+ cert[ 'key 2' ] = 'cf67c750-c704-4ef7-ab83-ecb2cd2e326c'
171
+
172
+ cert.delete( 'lastname' )
173
+
174
+ expect( cert.meta_keys ).
175
+ to contain_exactly( 'euid', 'username', 'firstname', 'key 2' )
176
+ end
177
+
178
+
179
+ it "can return all of its metadata as a Hash" do
180
+ cert = described_class.new
181
+
182
+ cert[ :euid ] = 0
183
+ cert[ :username ] = 'jrandom'
184
+ cert[ 'firstname' ] = 'James'
185
+ cert[ :lastname ] = 'Random'
186
+ cert[ 'key 2' ] = 'cf67c750-c704-4ef7-ab83-ecb2cd2e326c'
187
+
188
+ expect( cert.meta_hash ).to eq({
189
+ 'euid' => '0',
190
+ 'username' => 'jrandom',
191
+ 'firstname' => 'James',
192
+ 'lastname' => 'Random',
193
+ 'key 2' => 'cf67c750-c704-4ef7-ab83-ecb2cd2e326c',
194
+ })
195
+ end
196
+
197
+
198
+ it "can be applied to a Zyre node", :draft_apis do
199
+ node = instance_double( Zyre::Node )
200
+ cert = Zyre::Cert.new
201
+
202
+ expect( node ).to receive( :zcert= ).with( cert )
203
+ cert.apply( node )
204
+ end
205
+
206
+
207
+ it "can be duplicated" do
208
+ cert = described_class.new
209
+ cert[ :node_id ] = '05343500-f908-4903-9441-e648eb1754ec'
210
+ cert[ :node_order ] = 1
211
+
212
+ other = cert.dup
213
+
214
+ expect( other.object_id ).not_to eq( cert.object_id )
215
+ end
216
+
217
+
218
+ end