rightscale-nanite-dev 0.4.1.10
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.
- data/LICENSE +201 -0
- data/README.rdoc +430 -0
- data/Rakefile +78 -0
- data/TODO +24 -0
- data/bin/nanite-admin +65 -0
- data/bin/nanite-agent +79 -0
- data/bin/nanite-mapper +50 -0
- data/lib/nanite.rb +74 -0
- data/lib/nanite/actor.rb +71 -0
- data/lib/nanite/actor_registry.rb +26 -0
- data/lib/nanite/admin.rb +138 -0
- data/lib/nanite/agent.rb +274 -0
- data/lib/nanite/amqp.rb +58 -0
- data/lib/nanite/cluster.rb +256 -0
- data/lib/nanite/config.rb +111 -0
- data/lib/nanite/console.rb +39 -0
- data/lib/nanite/daemonize.rb +13 -0
- data/lib/nanite/identity.rb +16 -0
- data/lib/nanite/job.rb +104 -0
- data/lib/nanite/local_state.rb +38 -0
- data/lib/nanite/log.rb +66 -0
- data/lib/nanite/log/formatter.rb +39 -0
- data/lib/nanite/mapper.rb +315 -0
- data/lib/nanite/mapper_proxy.rb +75 -0
- data/lib/nanite/nanite_dispatcher.rb +92 -0
- data/lib/nanite/packets.rb +401 -0
- data/lib/nanite/pid_file.rb +52 -0
- data/lib/nanite/reaper.rb +39 -0
- data/lib/nanite/redis_tag_store.rb +141 -0
- data/lib/nanite/security/cached_certificate_store_proxy.rb +24 -0
- data/lib/nanite/security/certificate.rb +55 -0
- data/lib/nanite/security/certificate_cache.rb +66 -0
- data/lib/nanite/security/distinguished_name.rb +34 -0
- data/lib/nanite/security/encrypted_document.rb +46 -0
- data/lib/nanite/security/rsa_key_pair.rb +53 -0
- data/lib/nanite/security/secure_serializer.rb +68 -0
- data/lib/nanite/security/signature.rb +46 -0
- data/lib/nanite/security/static_certificate_store.rb +35 -0
- data/lib/nanite/security_provider.rb +47 -0
- data/lib/nanite/serializer.rb +52 -0
- data/lib/nanite/state.rb +135 -0
- data/lib/nanite/streaming.rb +125 -0
- data/lib/nanite/util.rb +78 -0
- metadata +111 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
module Nanite
|
2
|
+
class PidFile
|
3
|
+
def initialize(identity, options)
|
4
|
+
@pid_dir = File.expand_path(options[:pid_dir] || options[:root] || Dir.pwd)
|
5
|
+
@pid_file = File.join(@pid_dir, "nanite.#{identity}.pid")
|
6
|
+
end
|
7
|
+
|
8
|
+
def check
|
9
|
+
if pid = read_pid
|
10
|
+
if process_running? pid
|
11
|
+
raise "#{@pid_file} already exists (pid: #{pid})"
|
12
|
+
else
|
13
|
+
Log.info "removing stale pid file: #{@pid_file}"
|
14
|
+
remove
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def ensure_dir
|
20
|
+
FileUtils.mkdir_p @pid_dir
|
21
|
+
end
|
22
|
+
|
23
|
+
def write
|
24
|
+
ensure_dir
|
25
|
+
open(@pid_file,'w') {|f| f.write(Process.pid) }
|
26
|
+
File.chmod(0644, @pid_file)
|
27
|
+
end
|
28
|
+
|
29
|
+
def remove
|
30
|
+
File.delete(@pid_file) if exists?
|
31
|
+
end
|
32
|
+
|
33
|
+
def read_pid
|
34
|
+
open(@pid_file,'r') {|f| f.read.to_i } if exists?
|
35
|
+
end
|
36
|
+
|
37
|
+
def exists?
|
38
|
+
File.exists? @pid_file
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
@pid_file
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def process_running?(pid)
|
47
|
+
Process.getpgid(pid) != -1
|
48
|
+
rescue Errno::ESRCH
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Nanite
|
2
|
+
class Reaper
|
3
|
+
attr_reader :timeouts
|
4
|
+
def initialize(frequency=2)
|
5
|
+
@timeouts = {}
|
6
|
+
EM.add_periodic_timer(frequency) { EM.next_tick { reap } }
|
7
|
+
end
|
8
|
+
|
9
|
+
# Add the specified token to the internal timeout hash.
|
10
|
+
# The reaper will then check this instance on every reap.
|
11
|
+
def register(token, seconds, &blk)
|
12
|
+
@timeouts[token] = {:timestamp => Time.now + seconds, :seconds => seconds, :callback => blk}
|
13
|
+
end
|
14
|
+
|
15
|
+
def unregister(token)
|
16
|
+
@timeouts.delete(token)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Updates the timeout timestamp for the given token. If the token is
|
20
|
+
# unknown to this reaper instance it will be auto-registered, usually
|
21
|
+
# happening when you have several mappers and not all of them know
|
22
|
+
# this agent yet, but received a ping from it.
|
23
|
+
def update(token, seconds, &blk)
|
24
|
+
unless @timeouts[token]
|
25
|
+
register(token, seconds, &blk)
|
26
|
+
end
|
27
|
+
@timeouts[token][:timestamp] = Time.now + @timeouts[token][:seconds]
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def reap
|
33
|
+
time = Time.now
|
34
|
+
@timeouts.reject! do |token, data|
|
35
|
+
time > data[:timestamp] and data[:callback].call
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'redis'
|
2
|
+
|
3
|
+
module Nanite
|
4
|
+
|
5
|
+
# Implementation of a tag store on top of Redis
|
6
|
+
# For a nanite with the identity 'nanite-foobar', we store the following:
|
7
|
+
#
|
8
|
+
# s-nanite-foobar: { /foo/bar, /foo/nik } # a SET of the provided services
|
9
|
+
# tg-nanite-foobar: { foo-42, customer-12 } # a SET of the tags for this agent
|
10
|
+
#
|
11
|
+
# Also we do an inverted index for quick lookup of agents providing a certain
|
12
|
+
# service, so for each service the agent provides, we add the nanite to a SET
|
13
|
+
# of all the nanites that provide said service:
|
14
|
+
#
|
15
|
+
# foo/bar: { nanite-foobar, nanite-nickelbag, nanite-another } # redis SET
|
16
|
+
#
|
17
|
+
# We do that same thing for tags:
|
18
|
+
#
|
19
|
+
# some-tag: { nanite-foobar, nanite-nickelbag, nanite-another } # redis SET
|
20
|
+
#
|
21
|
+
# This way we can do a lookup of what nanites provide a set of services and tags based
|
22
|
+
# on redis SET intersection:
|
23
|
+
#
|
24
|
+
# nanites_for('/gems/list', 'some-tag')
|
25
|
+
# => returns an array of nanites that provide the intersection of these two service tags
|
26
|
+
|
27
|
+
class RedisTagStore
|
28
|
+
|
29
|
+
# Initialize tag store with given redis handle
|
30
|
+
def initialize(redis)
|
31
|
+
@redis = redis
|
32
|
+
end
|
33
|
+
|
34
|
+
# Store services and tags for given agent
|
35
|
+
def store(nanite, services, tags)
|
36
|
+
services = nil if services.compact.empty?
|
37
|
+
tags = nil if tags.compact.empty?
|
38
|
+
log_redis_error do
|
39
|
+
if services
|
40
|
+
obsolete_services = @redis.set_members("s-#{nanite}") - services
|
41
|
+
update_elems(nanite, services, obsolete_services, "s-#{nanite}", 'naniteservices')
|
42
|
+
end
|
43
|
+
if tags
|
44
|
+
obsolete_tags = @redis.set_members("tg-#{nanite}") - tags
|
45
|
+
update_elems(nanite, tags, obsolete_tags, "tg-#{nanite}", 'nanitestags')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Update tags for given agent
|
51
|
+
def update(nanite, new_tags, obsolete_tags)
|
52
|
+
update_elems(nanite, new_tags, obsolete_tags, "tg-#{nanite}", 'nanitestags')
|
53
|
+
end
|
54
|
+
|
55
|
+
# Delete services and tags for given agent
|
56
|
+
def delete(nanite)
|
57
|
+
delete_elems(nanite, "s-#{nanite}", 'naniteservices')
|
58
|
+
delete_elems(nanite, "tg-#{nanite}", 'nanitestags')
|
59
|
+
end
|
60
|
+
|
61
|
+
# Services implemented by given agent
|
62
|
+
def services(nanite)
|
63
|
+
@redis.set_members("s-#{nanite}")
|
64
|
+
end
|
65
|
+
|
66
|
+
# Tags exposed by given agent
|
67
|
+
def tags(nanite)
|
68
|
+
@redis.set_members("tg-#{nanite}")
|
69
|
+
end
|
70
|
+
|
71
|
+
# Retrieve all agents services
|
72
|
+
def all_services
|
73
|
+
log_redis_error do
|
74
|
+
@redis.set_members('naniteservices')
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Retrieve all agents tags
|
79
|
+
def all_tags
|
80
|
+
log_redis_error do
|
81
|
+
@redis.set_members('nanitetags')
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Retrieve nanites implementing given service and exposing given tags
|
86
|
+
def nanites_for(from, service, tags)
|
87
|
+
keys = tags && tags.dup || []
|
88
|
+
keys << service
|
89
|
+
log_redis_error do
|
90
|
+
@redis.set_intersect(keys.compact)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# Update values stored for given agent
|
97
|
+
# Also store reverse lookup information using both a unique and
|
98
|
+
# a global key (so it's possible to retrieve that agent value or
|
99
|
+
# all related values)
|
100
|
+
def update_elems(nanite, new_tags, obsolete_tags, elem_key, global_key)
|
101
|
+
new_tags = nil if new_tags.compact.empty?
|
102
|
+
obsolete_tags = nil if obsolete_tags.compact.empty?
|
103
|
+
log_redis_error do
|
104
|
+
obsolete_tags.each do |val|
|
105
|
+
@redis.set_delete(val, nanite)
|
106
|
+
@redis.set_delete(elem_key, val)
|
107
|
+
@redis.set_delete(global_key, val)
|
108
|
+
end if obsolete_tags
|
109
|
+
new_tags.each do |val|
|
110
|
+
@redis.set_add(val, nanite)
|
111
|
+
@redis.set_add(elem_key, val)
|
112
|
+
@redis.set_add(global_key, val)
|
113
|
+
end if new_tags
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Delete all values for given nanite agent
|
118
|
+
# Also delete reverse lookup information
|
119
|
+
def delete_elems(nanite, elem_key, global_key)
|
120
|
+
log_redis_error do
|
121
|
+
(@redis.set_members(elem_key)||[]).each do |val|
|
122
|
+
@redis.set_delete(val, nanite)
|
123
|
+
if @redis.set_count(val) == 0
|
124
|
+
@redis.delete(val)
|
125
|
+
@redis.set_delete(global_key, val)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
@redis.delete(elem_key)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Helper method, catch and log errors
|
133
|
+
def log_redis_error(&blk)
|
134
|
+
blk.call
|
135
|
+
rescue Exception => e
|
136
|
+
Nanite::Log.warn("redis error in method: #{caller[0]}")
|
137
|
+
raise e
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Nanite
|
2
|
+
|
3
|
+
# Proxy to actual certificate store which caches results in an LRU
|
4
|
+
# cache.
|
5
|
+
class CachedCertificateStoreProxy
|
6
|
+
|
7
|
+
# Initialize cache proxy with given certificate store.
|
8
|
+
def initialize(store)
|
9
|
+
@signer_cache = CertificateCache.new
|
10
|
+
@store = store
|
11
|
+
end
|
12
|
+
|
13
|
+
# Results from 'get_recipients' are not cached
|
14
|
+
def get_recipients(obj)
|
15
|
+
@store.get_recipients(obj)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Check cache for signer certificate
|
19
|
+
def get_signer(id)
|
20
|
+
@signer_cache.get(id) { @store.get_signer(id) }
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Nanite
|
2
|
+
|
3
|
+
# X.509 Certificate management
|
4
|
+
class Certificate
|
5
|
+
|
6
|
+
# Underlying OpenSSL cert
|
7
|
+
attr_accessor :raw_cert
|
8
|
+
|
9
|
+
# Generate a signed X.509 certificate
|
10
|
+
#
|
11
|
+
# Arguments:
|
12
|
+
# - key: RsaKeyPair, key pair used to sign certificate
|
13
|
+
# - issuer: DistinguishedName, certificate issuer
|
14
|
+
# - subject: DistinguishedName, certificate subject
|
15
|
+
# - valid_for: Time in seconds before certificate expires (10 years by default)
|
16
|
+
def initialize(key, issuer, subject, valid_for = 3600*24*365*10)
|
17
|
+
@raw_cert = OpenSSL::X509::Certificate.new
|
18
|
+
@raw_cert.version = 2
|
19
|
+
@raw_cert.serial = 1
|
20
|
+
@raw_cert.subject = subject.to_x509
|
21
|
+
@raw_cert.issuer = issuer.to_x509
|
22
|
+
@raw_cert.public_key = key.to_public.raw_key
|
23
|
+
@raw_cert.not_before = Time.now
|
24
|
+
@raw_cert.not_after = Time.now + valid_for
|
25
|
+
@raw_cert.sign(key.raw_key, OpenSSL::Digest::SHA1.new)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Load certificate from file
|
29
|
+
def self.load(file)
|
30
|
+
from_data(File.new(file))
|
31
|
+
end
|
32
|
+
|
33
|
+
# Initialize with raw certificate
|
34
|
+
def self.from_data(data)
|
35
|
+
cert = OpenSSL::X509::Certificate.new(data)
|
36
|
+
res = Certificate.allocate
|
37
|
+
res.instance_variable_set(:@raw_cert, cert)
|
38
|
+
res
|
39
|
+
end
|
40
|
+
|
41
|
+
# Save certificate to file in PEM format
|
42
|
+
def save(file)
|
43
|
+
File.open(file, "w") do |f|
|
44
|
+
f.write(@raw_cert.to_pem)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Certificate data in PEM format
|
49
|
+
def data
|
50
|
+
@raw_cert.to_pem
|
51
|
+
end
|
52
|
+
alias :to_s :data
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Nanite
|
2
|
+
|
3
|
+
# Implements a simple LRU cache: items that are the least accessed are
|
4
|
+
# deleted first.
|
5
|
+
class CertificateCache
|
6
|
+
|
7
|
+
# Max number of items to keep in memory
|
8
|
+
DEFAULT_CACHE_MAX_COUNT = 100
|
9
|
+
|
10
|
+
# Initialize cache
|
11
|
+
def initialize(max_count = DEFAULT_CACHE_MAX_COUNT)
|
12
|
+
@items = {}
|
13
|
+
@list = []
|
14
|
+
@max_count = max_count
|
15
|
+
end
|
16
|
+
|
17
|
+
# Add item to cache
|
18
|
+
def put(key, item)
|
19
|
+
if @items.include?(key)
|
20
|
+
delete(key)
|
21
|
+
end
|
22
|
+
if @list.size == @max_count
|
23
|
+
delete(@list.first)
|
24
|
+
end
|
25
|
+
@items[key] = item
|
26
|
+
@list.push(key)
|
27
|
+
item
|
28
|
+
end
|
29
|
+
alias :[]= :put
|
30
|
+
|
31
|
+
# Retrieve item from cache
|
32
|
+
# Store item returned by given block if any
|
33
|
+
def get(key)
|
34
|
+
if @items.include?(key)
|
35
|
+
@list.each_index do |i|
|
36
|
+
if @list[i] == key
|
37
|
+
@list.delete_at(i)
|
38
|
+
break
|
39
|
+
end
|
40
|
+
end
|
41
|
+
@list.push(key)
|
42
|
+
@items[key]
|
43
|
+
else
|
44
|
+
return nil unless block_given?
|
45
|
+
self[key] = yield
|
46
|
+
end
|
47
|
+
end
|
48
|
+
alias :[] :get
|
49
|
+
|
50
|
+
# Delete item from cache
|
51
|
+
def delete(key)
|
52
|
+
c = @items[key]
|
53
|
+
if c
|
54
|
+
@items.delete(key)
|
55
|
+
@list.each_index do |i|
|
56
|
+
if @list[i] == key
|
57
|
+
@list.delete_at(i)
|
58
|
+
break
|
59
|
+
end
|
60
|
+
end
|
61
|
+
c
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Nanite
|
2
|
+
|
3
|
+
# Build X.509 compliant distinguished names
|
4
|
+
# Distinghuished names are used to desccribe both a certificate issuer and
|
5
|
+
# subject.
|
6
|
+
class DistinguishedName
|
7
|
+
|
8
|
+
# Initialize distinguished name from hash
|
9
|
+
# e.g.:
|
10
|
+
# { 'C' => 'US',
|
11
|
+
# 'ST' => 'California',
|
12
|
+
# 'L' => 'Santa Barbara',
|
13
|
+
# 'O' => 'RightScale',
|
14
|
+
# 'OU' => 'Certification Services',
|
15
|
+
# 'CN' => 'rightscale.com/emailAddress=cert@rightscale.com' }
|
16
|
+
#
|
17
|
+
def initialize(hash)
|
18
|
+
@value = hash
|
19
|
+
end
|
20
|
+
|
21
|
+
# Conversion to OpenSSL X509 DN
|
22
|
+
def to_x509
|
23
|
+
if @value
|
24
|
+
OpenSSL::X509::Name.new(@value.to_a, OpenSSL::X509::Name::OBJECT_TYPE_TEMPLATE)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Human readable form
|
29
|
+
def to_s
|
30
|
+
'/' + @value.to_a.collect { |p| p.join('=') }.join('/') if @value
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Nanite
|
2
|
+
|
3
|
+
# Represents a signed an encrypted document that can be later decrypted using
|
4
|
+
# the right private key and whose signature can be verified using the right
|
5
|
+
# cert.
|
6
|
+
# This class can be used both to encrypt and sign data and to then check the
|
7
|
+
# signature and decrypt an encrypted document.
|
8
|
+
class EncryptedDocument
|
9
|
+
|
10
|
+
# Encrypt and sign data using certificate and key pair.
|
11
|
+
#
|
12
|
+
# Arguments:
|
13
|
+
# - 'data': Data to be encrypted
|
14
|
+
# - 'certs': Recipient certificates (certificates corresponding to private
|
15
|
+
# keys that may be used to decrypt data)
|
16
|
+
# - 'cipher': Cipher used for encryption, AES 256 CBC by default
|
17
|
+
#
|
18
|
+
def initialize(data, certs, cipher = 'AES-256-CBC')
|
19
|
+
cipher = OpenSSL::Cipher::Cipher.new(cipher)
|
20
|
+
certs = [ certs ] unless certs.respond_to?(:collect)
|
21
|
+
raw_certs = certs.collect { |c| c.raw_cert }
|
22
|
+
@pkcs7 = OpenSSL::PKCS7.encrypt(raw_certs, data, cipher, OpenSSL::PKCS7::BINARY)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Initialize from encrypted data.
|
26
|
+
def self.from_data(encrypted_data)
|
27
|
+
doc = EncryptedDocument.allocate
|
28
|
+
doc.instance_variable_set(:@pkcs7, Nanite::PKCS7.new(encrypted_data))
|
29
|
+
doc
|
30
|
+
end
|
31
|
+
|
32
|
+
# Encrypted data using DER format
|
33
|
+
def encrypted_data
|
34
|
+
@pkcs7.to_pem
|
35
|
+
end
|
36
|
+
|
37
|
+
# Decrypted data
|
38
|
+
#
|
39
|
+
# Arguments:
|
40
|
+
# - 'key': Key used for decryption
|
41
|
+
# - 'cert': Certificate to use for decryption
|
42
|
+
def decrypted_data(key, cert)
|
43
|
+
@pkcs7.decrypt(key.raw_key, cert.raw_cert)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|