nova 0.0.2
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.
- checksums.yaml +7 -0
- data/LICENSE +19 -0
- data/README.md +29 -0
- data/bin/nova +8 -0
- data/lib/generator/template/new_install/galaxy/some_star.rb +3 -0
- data/lib/generator/template/new_install/supernova.yml +16 -0
- data/lib/nova.rb +49 -0
- data/lib/nova/cli.rb +62 -0
- data/lib/nova/commands/server.rb +71 -0
- data/lib/nova/common.rb +17 -0
- data/lib/nova/common/event_handler.rb +165 -0
- data/lib/nova/common/event_handler/event.rb +147 -0
- data/lib/nova/common/features.rb +93 -0
- data/lib/nova/common/features/feature.rb +65 -0
- data/lib/nova/common/metadata.rb +65 -0
- data/lib/nova/common/metadata/data.rb +171 -0
- data/lib/nova/common/star_management.rb +164 -0
- data/lib/nova/constructor.rb +84 -0
- data/lib/nova/exceptions.rb +16 -0
- data/lib/nova/project.rb +199 -0
- data/lib/nova/remote.rb +10 -0
- data/lib/nova/remote/fake.rb +51 -0
- data/lib/nova/remote/fake/commands.rb +44 -0
- data/lib/nova/remote/fake/file_system.rb +76 -0
- data/lib/nova/remote/fake/operating_system.rb +52 -0
- data/lib/nova/remote/fake/platform.rb +89 -0
- data/lib/nova/star.rb +25 -0
- data/lib/nova/starbound.rb +14 -0
- data/lib/nova/starbound/client.rb +59 -0
- data/lib/nova/starbound/encryptor.rb +134 -0
- data/lib/nova/starbound/encryptors.rb +13 -0
- data/lib/nova/starbound/encryptors/openssl.rb +122 -0
- data/lib/nova/starbound/encryptors/plaintext.rb +64 -0
- data/lib/nova/starbound/encryptors/rbnacl.rb +67 -0
- data/lib/nova/starbound/protocol.rb +81 -0
- data/lib/nova/starbound/protocol/encryption.rb +48 -0
- data/lib/nova/starbound/protocol/exceptions.rb +38 -0
- data/lib/nova/starbound/protocol/messages.rb +116 -0
- data/lib/nova/starbound/protocol/packet.rb +267 -0
- data/lib/nova/starbound/protocol/socket.rb +231 -0
- data/lib/nova/starbound/server.rb +182 -0
- data/lib/nova/version.rb +5 -0
- data/spec/constructor_spec.rb +20 -0
- data/spec/local_spec.rb +26 -0
- data/spec/nova_spec.rb +7 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/star/some_type.rb +27 -0
- data/spec/star_spec.rb +107 -0
- data/spec/starbound/encryptor_spec.rb +33 -0
- data/spec/starbound/openssl_encryptor_spec.rb +80 -0
- data/spec/starbound/packet_spec.rb +61 -0
- data/spec/starbound/plaintext_encryptor_spec.rb +27 -0
- data/spec/starbound/protocol_spec.rb +163 -0
- data/spec/starbound/rbnacl_encryptor_spec.rb +70 -0
- metadata +166 -0
@@ -0,0 +1,182 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Nova
|
4
|
+
module Starbound
|
5
|
+
|
6
|
+
# A Starbound server.
|
7
|
+
#
|
8
|
+
# @todo Default proc.
|
9
|
+
class Server
|
10
|
+
|
11
|
+
# The options passed to the server on initialization.
|
12
|
+
#
|
13
|
+
# @return [Hash]
|
14
|
+
attr_reader :options
|
15
|
+
|
16
|
+
# The default options.
|
17
|
+
DEFAULT_OPTIONS = {
|
18
|
+
:type => :tcp,
|
19
|
+
:host => "127.0.0.1",
|
20
|
+
:port => 2010,
|
21
|
+
:path => "/tmp/sock"
|
22
|
+
}
|
23
|
+
|
24
|
+
# Whether or not to run. Once this is set to false, the server
|
25
|
+
# stops listening for clients.
|
26
|
+
#
|
27
|
+
# @return [Boolean]
|
28
|
+
attr_accessor :run
|
29
|
+
|
30
|
+
# The list of active threads that are running.
|
31
|
+
#
|
32
|
+
# @return [Array<Thread>]
|
33
|
+
attr_reader :thread_list
|
34
|
+
|
35
|
+
# Initialize the server with the given options. If the options
|
36
|
+
# has a +:protocol+ key, it is removed from the options and set
|
37
|
+
# as the protocol options.
|
38
|
+
#
|
39
|
+
# @param options [Hash]
|
40
|
+
# @option options [Symbol] :type the type of server. Can be any
|
41
|
+
# of +:tcp+ or +:unix+. Defaults to +:tcp+.
|
42
|
+
# @option options [String] :host the host to bind to. Defaults
|
43
|
+
# to +"127.0.0.1"+. Only used if +:type+ is +:tcp+.
|
44
|
+
# @option options [Numeric] :port the port to bind to. Defaults
|
45
|
+
# to +2010+. Only used if +:type+ is +:tcp+.
|
46
|
+
# @option options [String] :path the path to the unix socket.
|
47
|
+
# Only used if +:type+ is +:unix+.
|
48
|
+
def initialize(options = {})
|
49
|
+
@options = DEFAULT_OPTIONS.merge options
|
50
|
+
@protocol_options = (options.delete(:protocol) || {}).dup
|
51
|
+
@run = true
|
52
|
+
@thread_list = []
|
53
|
+
end
|
54
|
+
|
55
|
+
# How to handle the client. Accepts a block, which is used in
|
56
|
+
# {#listen} when a client connects. If no block is given, the
|
57
|
+
# a block is returned.
|
58
|
+
#
|
59
|
+
# @yieldparam protocol [Protocol] the protocol instance that is
|
60
|
+
# used to handle the client. By the time it is yielded, the
|
61
|
+
# client has already done a handshake with the server.
|
62
|
+
# @return [Proc]
|
63
|
+
def with_client(&block)
|
64
|
+
if block_given?
|
65
|
+
@with_client = block
|
66
|
+
else
|
67
|
+
@with_client ||= proc {}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Reads a ruby file in the instance of this server, so that it
|
72
|
+
# can build a {#with_client} block for it.
|
73
|
+
#
|
74
|
+
# @param file [String] the file to read from.
|
75
|
+
def read_client_file(file)
|
76
|
+
return unless file && File.exists?(file)
|
77
|
+
|
78
|
+
instance_eval File.open(file, "r").read, file, 1
|
79
|
+
end
|
80
|
+
|
81
|
+
# Listen for clients. Calls {#handle_client} when a client is
|
82
|
+
# found.
|
83
|
+
#
|
84
|
+
# @return [void]
|
85
|
+
def listen
|
86
|
+
Nova.logger.info { "Server started." }
|
87
|
+
while run
|
88
|
+
next unless IO.select [server], nil, nil, 1
|
89
|
+
thread_list << Thread.start(server.accept) do |client|
|
90
|
+
Nova.logger.info { "Client accepted." }
|
91
|
+
|
92
|
+
handle_client client
|
93
|
+
|
94
|
+
thread_list.delete(Thread.current)
|
95
|
+
Nova.logger.info { "Client disconnected." }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Shuts down the server.
|
101
|
+
#
|
102
|
+
# @return [void]
|
103
|
+
def shutdown
|
104
|
+
puts "shutting down"
|
105
|
+
@run = false
|
106
|
+
thread_list.each do |thread|
|
107
|
+
thread.raise ExitError
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns the server type that this server should run as,
|
112
|
+
# already instantized.
|
113
|
+
#
|
114
|
+
# @return [Object]
|
115
|
+
def server
|
116
|
+
@_server ||= case options.fetch(:type, :tcp)
|
117
|
+
when :tcp
|
118
|
+
TCPServer.new options.fetch(:host, "127.0.0.1"),
|
119
|
+
options.fetch(:port, 2010)
|
120
|
+
when :unix
|
121
|
+
UNIXServer.new options.fetch(:path)
|
122
|
+
when :pipe
|
123
|
+
FakeServer.new options.fetch(:pipe)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
# Handles a client after they connect. Creates a {Protocol},
|
130
|
+
# initiates the handshake, and calls {#with_client} to handle
|
131
|
+
# the protocol afterwards.
|
132
|
+
#
|
133
|
+
# @param client [IO] the client to handle.
|
134
|
+
# @return [void]
|
135
|
+
def handle_client(client)
|
136
|
+
protocol = Protocol.new(
|
137
|
+
@protocol_options.merge(:type => :server))
|
138
|
+
|
139
|
+
protocol.socket = client
|
140
|
+
protocol.handshake
|
141
|
+
|
142
|
+
with_client.call(protocol)
|
143
|
+
|
144
|
+
protocol.close(false)
|
145
|
+
|
146
|
+
rescue ExitError => e
|
147
|
+
Nova.logger.error { "Closing while client is connected. Notifying..." }
|
148
|
+
if protocol
|
149
|
+
protocol.close(:shutdown)
|
150
|
+
else
|
151
|
+
client.close unless client.closed?
|
152
|
+
end
|
153
|
+
rescue RemoteCloseError
|
154
|
+
rescue ProtocolError => e
|
155
|
+
Nova.logger.error { "Client failed: #{e.message} #{e.backtrace.join("\n")}" }
|
156
|
+
client.close unless client.closed?
|
157
|
+
ensure
|
158
|
+
protocol.close(false)
|
159
|
+
end
|
160
|
+
|
161
|
+
# A fake server that wraps a pipe.
|
162
|
+
class FakeServer
|
163
|
+
|
164
|
+
# Initialize the fake server with the pipe.
|
165
|
+
def initialize(pipe)
|
166
|
+
@pipe = pipe
|
167
|
+
end
|
168
|
+
|
169
|
+
# Does nothing.
|
170
|
+
def listen(_); end
|
171
|
+
|
172
|
+
# Returns the pipe.
|
173
|
+
#
|
174
|
+
# @return [Object]
|
175
|
+
def accept
|
176
|
+
@pipe
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
data/lib/nova/version.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
describe Nova::Constructor do
|
2
|
+
|
3
|
+
it "should create stars from types" do
|
4
|
+
d = described_class.new(non_existant_star: :some_name) {}
|
5
|
+
|
6
|
+
expect {
|
7
|
+
d.modify_or_create
|
8
|
+
}.to raise_error(Nova::NoStarError)
|
9
|
+
|
10
|
+
d = described_class.new(star: :some_name) do
|
11
|
+
def some_method; 5; end
|
12
|
+
end
|
13
|
+
|
14
|
+
klass = d.modify_or_create
|
15
|
+
expect(klass.as).to be :some_name
|
16
|
+
expect(klass.instance_methods).to include(:some_method)
|
17
|
+
expect(klass.new.some_method).to be 5
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/spec/local_spec.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
require 'nova/remote/local'
|
4
|
+
|
5
|
+
describe Nova::Remote::Local do
|
6
|
+
|
7
|
+
before :each do
|
8
|
+
Nova::Star.remote = Nova::Remote::Local
|
9
|
+
end
|
10
|
+
|
11
|
+
context "platforms" do
|
12
|
+
|
13
|
+
it "should determine platforms" do
|
14
|
+
expect(Nova::Star.new.platform).to have_at_least(3).items
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context "commands" do
|
19
|
+
it "should execute the command" do
|
20
|
+
star = Nova::Star.new
|
21
|
+
expect(star.line("echo", "hello").pass.stdout).to eq("hello\n")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
=end
|
data/spec/nova_spec.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'coveralls'
|
2
|
+
|
3
|
+
Coveralls.wear!
|
4
|
+
|
5
|
+
module NovaHelper
|
6
|
+
|
7
|
+
def self.build_packet(type = 0, body = "hello world", data = { :packet_id => 1, :nonce => "" })
|
8
|
+
Nova::Starbound::Protocol::Packet.build(type, body, data)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.packet_from_socket(sock)
|
12
|
+
Nova::Starbound::Protocol::Packet.from_socket(sock)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.build_response(type, body, pack)
|
16
|
+
Nova::Starbound::Protocol::Packet.build_response(type, body, pack, :nonce => "")
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class SomeType < Nova::Star
|
2
|
+
star_type :some_type
|
3
|
+
|
4
|
+
metadata do
|
5
|
+
require_options :hello
|
6
|
+
end
|
7
|
+
|
8
|
+
feature :some_feature do
|
9
|
+
on :some_event do; end
|
10
|
+
|
11
|
+
on :enable do
|
12
|
+
tag :enable
|
13
|
+
|
14
|
+
3
|
15
|
+
end
|
16
|
+
|
17
|
+
on :disable do
|
18
|
+
tag :disable
|
19
|
+
|
20
|
+
4
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
on :foo do; 1; end
|
25
|
+
|
26
|
+
on :bar, requires: :an_option do; 2; end
|
27
|
+
end
|
data/spec/star_spec.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
require_relative 'star/some_type'
|
2
|
+
|
3
|
+
describe Nova::Star do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
Nova::Star.remote = Nova::Remote::Fake
|
7
|
+
end
|
8
|
+
|
9
|
+
context "management" do
|
10
|
+
it "handles types" do
|
11
|
+
expect(Nova::Star.types).to eq(star: Nova::Star, some_type: SomeType)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "has pretty inspect" do
|
15
|
+
expect(Nova::Star.inspect).to eq("Nova::Star")
|
16
|
+
expect(SomeType.inspect).to eq("Nova::Star/SomeType")
|
17
|
+
end
|
18
|
+
|
19
|
+
it "has a default remote" do
|
20
|
+
expect(SomeType.remote).to be(Nova::Remote::Fake)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "options manager" do
|
25
|
+
it "validates options" do
|
26
|
+
expect {
|
27
|
+
star = SomeType.new
|
28
|
+
star.options = {}
|
29
|
+
}.to raise_error(Nova::InvalidOptionsError)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "event manager" do
|
34
|
+
it "manages events" do
|
35
|
+
event = Nova::Common::EventHandler::Event
|
36
|
+
[SomeType, SomeType.new].each do |type|
|
37
|
+
expect(type.events).to be_instance_of Set
|
38
|
+
expect(type.has_event?(:foo)).to be_instance_of event
|
39
|
+
expect(type.has_event?(:not_foo)).to be_nil
|
40
|
+
expect(type.has_event?(:bar)).to be_instance_of event
|
41
|
+
|
42
|
+
expect(type.has_event_with_options?(:foo)).to be_instance_of event
|
43
|
+
expect(type.has_event_with_options?(:bar)).to be_nil
|
44
|
+
expect(type.has_event_with_options?(:bar, an_option: true)).to be_instance_of event
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it "runs events" do
|
49
|
+
star = SomeType.new
|
50
|
+
context = double(:context)
|
51
|
+
star.bind! context
|
52
|
+
|
53
|
+
expect(star.run!(:foo)).to be 1
|
54
|
+
|
55
|
+
expect {
|
56
|
+
star.run!(:bar)
|
57
|
+
}.to raise_error(Nova::NoEventError)
|
58
|
+
expect(star.run(:bar)).to be_instance_of Nova::NoEventError
|
59
|
+
expect(star.run!(:bar, an_option: true)).to be 2
|
60
|
+
|
61
|
+
expect {
|
62
|
+
star.run! :not_an_event
|
63
|
+
}.to raise_error(Nova::NoEventError)
|
64
|
+
expect(star.run(:not_an_event)).to be_instance_of Nova::NoEventError
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "features" do
|
69
|
+
it "supports defined features" do
|
70
|
+
expect(SomeType.supports?(:some_feature)).to be true
|
71
|
+
expect(SomeType.supports?(:another_feature)).to be false
|
72
|
+
end
|
73
|
+
|
74
|
+
it "gives fake features" do
|
75
|
+
star = SomeType.new
|
76
|
+
expect(star.feature(:some_feature)).to_not be_fake
|
77
|
+
expect(star.feature(:another_feature)).to be_fake
|
78
|
+
|
79
|
+
expect(star.supports?(:some_feature)).to be true
|
80
|
+
expect(star.supports?(:another_feature)).to be false
|
81
|
+
end
|
82
|
+
|
83
|
+
it "runs enable and disable events" do
|
84
|
+
star = SomeType.new
|
85
|
+
context = stub(:context)
|
86
|
+
context.should_receive(:tag).with(:enable)
|
87
|
+
expect(star.feature(:some_feature).bind(context).enable!).to be 3
|
88
|
+
|
89
|
+
context = stub(:context)
|
90
|
+
context.should_receive(:tag).with(:disable)
|
91
|
+
expect(star.feature(:some_feature).bind(context).disable!).to be 4
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "platforms" do
|
96
|
+
it "displays correct platforms" do
|
97
|
+
expect(SomeType.new.remote.platform.types).to eq([])
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context "commands" do
|
102
|
+
it "gives a command line" do
|
103
|
+
star = SomeType.new
|
104
|
+
expect(star.remote.command.line("something", "arguments")).to be_instance_of Command::Runner
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
describe Nova::Starbound::Encryptor do
|
2
|
+
|
3
|
+
it "is not available" do
|
4
|
+
expect(described_class).to_not be_available
|
5
|
+
|
6
|
+
expect {
|
7
|
+
subject
|
8
|
+
}.to raise_error NotImplementedError
|
9
|
+
end
|
10
|
+
|
11
|
+
it "is not plaintext" do
|
12
|
+
expect(described_class).to_not be_plaintext
|
13
|
+
end
|
14
|
+
|
15
|
+
it "sorts the encryptors" do
|
16
|
+
expect(described_class.sorted_encryptors).to have(3).items
|
17
|
+
expect(described_class.sorted_encryptors.first.preference).to be > described_class.sorted_encryptors.last.preference
|
18
|
+
end
|
19
|
+
|
20
|
+
context "encrypting" do
|
21
|
+
before(:each) { described_class.stub(:available?).and_return(true) }
|
22
|
+
|
23
|
+
it "raises errors" do
|
24
|
+
[:encrypt, :decrypt, :private_key!, :public_key,
|
25
|
+
:other_public_key=].each do |m|
|
26
|
+
|
27
|
+
expect(subject).to respond_to m
|
28
|
+
|
29
|
+
expect { subject.send(m) }.to raise_error NotImplementedError
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
describe Nova::Starbound::Encryptors::OpenSSL do
|
2
|
+
|
3
|
+
it "is available" do
|
4
|
+
expect(described_class).to be_available
|
5
|
+
end
|
6
|
+
|
7
|
+
context "handling keys" do
|
8
|
+
subject { described_class.new }
|
9
|
+
its(:private_key!) { should be_instance_of ::OpenSSL::PKey::RSA }
|
10
|
+
|
11
|
+
it "has a public key" do
|
12
|
+
subject.private_key! # initialize the private key
|
13
|
+
expect(subject.public_key).to be_a String
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
before :each do
|
18
|
+
@packet = NovaHelper.build_packet
|
19
|
+
end
|
20
|
+
|
21
|
+
it "encrypts a packet successfully" do
|
22
|
+
|
23
|
+
subject.private_key!
|
24
|
+
public_key = subject.public_key
|
25
|
+
|
26
|
+
secret = ::OpenSSL::Random.random_bytes(4096 / 16)
|
27
|
+
encrypted_secret = ::OpenSSL::PKey::RSA.new(public_key).public_encrypt(secret)
|
28
|
+
|
29
|
+
subject.other_public_key = encrypted_secret
|
30
|
+
|
31
|
+
expect(subject.options[:shared_secret]).to eq secret
|
32
|
+
encrypted = nil
|
33
|
+
|
34
|
+
expect {
|
35
|
+
encrypted = subject.encrypt(@packet)
|
36
|
+
}.to_not raise_error
|
37
|
+
|
38
|
+
expect(encrypted).to be_instance_of Nova::Starbound::Protocol::Packet
|
39
|
+
expect(encrypted.body.bytesize).to eq encrypted[:size]
|
40
|
+
end
|
41
|
+
|
42
|
+
it "decrypts a packet successfully" do
|
43
|
+
|
44
|
+
subject.private_key!
|
45
|
+
|
46
|
+
public_key = ::OpenSSL::PKey::RSA.new(4096)
|
47
|
+
subject.other_public_key = public_key.public_key.to_der
|
48
|
+
encrypted_secret = subject.public_key
|
49
|
+
|
50
|
+
secret = public_key.private_decrypt(encrypted_secret)
|
51
|
+
expect(secret).to eq subject.options[:shared_secret]
|
52
|
+
|
53
|
+
encrypted = subject.encrypt(@packet)
|
54
|
+
decrypted = nil
|
55
|
+
|
56
|
+
expect {
|
57
|
+
decrypted = subject.decrypt(encrypted)
|
58
|
+
}.to_not raise_error
|
59
|
+
|
60
|
+
expect(decrypted.body).to eq @packet.body
|
61
|
+
end
|
62
|
+
|
63
|
+
it "raises an error on non-matching hashes" do
|
64
|
+
subject.private_key!
|
65
|
+
public_key = subject.public_key
|
66
|
+
|
67
|
+
secret = ::OpenSSL::Random.random_bytes(4096 / 16)
|
68
|
+
encrypted_secret = ::OpenSSL::PKey::RSA.new(public_key).public_encrypt(secret)
|
69
|
+
|
70
|
+
subject.other_public_key = encrypted_secret
|
71
|
+
|
72
|
+
encrypted = subject.encrypt(@packet)
|
73
|
+
encrypted.body.replace("\x00" * encrypted.size)
|
74
|
+
|
75
|
+
expect {
|
76
|
+
subject.decrypt(encrypted)
|
77
|
+
}.to raise_error(Nova::Starbound::EncryptorError)
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|