bixby-agent 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.testguardrc +1 -0
- data/.travis.yml +25 -0
- data/Gemfile +61 -0
- data/Gemfile.lock +237 -0
- data/LICENSE +21 -0
- data/Rakefile +13 -0
- data/VERSION +1 -0
- data/bin/bixby-agent +15 -0
- data/bixby-agent.gemspec +186 -0
- data/etc/bixby-god.initd +70 -0
- data/etc/bixby.god +20 -0
- data/etc/god.d/bixby-agent.god +71 -0
- data/lib/bixby-agent.rb +16 -0
- data/lib/bixby-agent/agent.rb +98 -0
- data/lib/bixby-agent/agent/config.rb +109 -0
- data/lib/bixby-agent/agent/crypto.rb +73 -0
- data/lib/bixby-agent/agent/handshake.rb +81 -0
- data/lib/bixby-agent/agent/shell_exec.rb +111 -0
- data/lib/bixby-agent/agent_handler.rb +38 -0
- data/lib/bixby-agent/app.rb +208 -0
- data/lib/bixby-agent/app/cli.rb +112 -0
- data/lib/bixby-agent/config_exception.rb +5 -0
- data/lib/bixby-agent/help/system_time.rb +41 -0
- data/lib/bixby-agent/version.rb +8 -0
- data/lib/bixby-agent/websocket/client.rb +186 -0
- data/tasks/cane.rake +14 -0
- data/tasks/coverage.rake +2 -0
- data/tasks/coveralls.rake +11 -0
- data/tasks/jeweler.rake +21 -0
- data/tasks/test.rake +5 -0
- data/tasks/yard.rake +6 -0
- data/test/base.rb +92 -0
- data/test/helper.rb +29 -0
- data/test/stub_eventmachine.rb +38 -0
- data/test/support/root_dir/bixby.yml +8 -0
- data/test/support/root_dir/id_rsa +27 -0
- data/test/support/root_dir/server +27 -0
- data/test/support/root_dir/server.pub +9 -0
- data/test/support/test_bundle/bin/cat +2 -0
- data/test/support/test_bundle/bin/echo +2 -0
- data/test/support/test_bundle/digest +17 -0
- data/test/support/test_bundle/manifest.json +0 -0
- data/test/test_agent.rb +110 -0
- data/test/test_agent_exec.rb +53 -0
- data/test/test_agent_handler.rb +72 -0
- data/test/test_app.rb +138 -0
- data/test/test_bixby_common.rb +18 -0
- data/test/test_crypto.rb +78 -0
- data/test/websocket/test_client.rb +110 -0
- metadata +557 -0
data/etc/bixby-god.initd
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
### BEGIN INIT INFO
|
4
|
+
# Provides: bixby-god
|
5
|
+
# Required-Start: $local_fs $remote_fs $syslog $named $network $time
|
6
|
+
# Required-Stop: $local_fs $remote_fs $syslog $named $network
|
7
|
+
# Should-Start:
|
8
|
+
# Should-Stop:
|
9
|
+
# Default-Start: 2 3 4 5
|
10
|
+
# Default-Stop: 0 1 6
|
11
|
+
# Short-Description: Start/Stop the bixby god daemon
|
12
|
+
### END INIT INFO
|
13
|
+
|
14
|
+
# processname: god
|
15
|
+
# pidfile: /opt/bixby/var/bixby-god.pid
|
16
|
+
|
17
|
+
NAME=bixby
|
18
|
+
DESC=bixby
|
19
|
+
|
20
|
+
export BIXBY_HOME=/opt/bixby
|
21
|
+
GOD_PORT=18165 # actually used in unix socket name
|
22
|
+
GOD_CONF="-c $BIXBY_HOME/etc/bixby.god"
|
23
|
+
GOD_PID=$BIXBY_HOME/var/bixby-god.pid
|
24
|
+
GOD_BIN="$BIXBY_HOME/embedded/bin/god -P $GOD_PID -p $GOD_PORT"
|
25
|
+
|
26
|
+
start() {
|
27
|
+
echo " * Starting bixby"
|
28
|
+
$GOD_BIN $GOD_CONF
|
29
|
+
}
|
30
|
+
stop() {
|
31
|
+
echo " * Stopping bixby"
|
32
|
+
$GOD_BIN terminate >/dev/null
|
33
|
+
}
|
34
|
+
quit() {
|
35
|
+
$GOD_BIN quit >/dev/null
|
36
|
+
}
|
37
|
+
|
38
|
+
case $1 in
|
39
|
+
start)
|
40
|
+
start
|
41
|
+
;;
|
42
|
+
stop)
|
43
|
+
stop
|
44
|
+
;;
|
45
|
+
restart)
|
46
|
+
stop
|
47
|
+
start
|
48
|
+
;;
|
49
|
+
reload)
|
50
|
+
quit
|
51
|
+
start
|
52
|
+
;;
|
53
|
+
status)
|
54
|
+
$GOD_BIN status >/dev/null 2>&1
|
55
|
+
if [[ $? -eq 1 ]]; then
|
56
|
+
echo "Not running"
|
57
|
+
else
|
58
|
+
$GOD_BIN status
|
59
|
+
fi
|
60
|
+
;;
|
61
|
+
god)
|
62
|
+
$GOD_BIN ${@:2}
|
63
|
+
;;
|
64
|
+
*)
|
65
|
+
echo "Usage: $NAME {start|stop|restart|status|reload|god}" >&2
|
66
|
+
exit 1
|
67
|
+
;;
|
68
|
+
esac
|
69
|
+
|
70
|
+
exit 0
|
data/etc/bixby.god
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
# Main God config
|
3
|
+
#
|
4
|
+
# usage:
|
5
|
+
#
|
6
|
+
# # god -P var/bixby-god.pid -c etc/bixby.god
|
7
|
+
#
|
8
|
+
# god must be run as root!
|
9
|
+
#
|
10
|
+
# see also: http://godrb.com/
|
11
|
+
|
12
|
+
BIXBY_HOME = ENV["BIXBY_HOME"] || "/opt/bixby"
|
13
|
+
BIXBY_CLIENT = File.join(BIXBY_HOME, "bin", "bixby")
|
14
|
+
|
15
|
+
God.pid_file_directory = File.join(BIXBY_HOME, "var", "pids")
|
16
|
+
|
17
|
+
path = File.join(BIXBY_HOME, "etc", "god.d", "*.god")
|
18
|
+
Dir.glob(path).each do |conf|
|
19
|
+
God.load conf
|
20
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
|
2
|
+
God.watch do |w|
|
3
|
+
w.dir = BIXBY_HOME
|
4
|
+
w.name = "bixby-agent"
|
5
|
+
w.group = "bixby"
|
6
|
+
w.log = "#{BIXBY_HOME}/var/god.#{w.name}.log"
|
7
|
+
w.pid_file = "#{BIXBY_HOME}/var/#{w.name}.pid"
|
8
|
+
|
9
|
+
w.interval = 30.seconds
|
10
|
+
|
11
|
+
w.env = {}
|
12
|
+
w.start = "#{BIXBY_HOME}/bin/bixby-agent start"
|
13
|
+
w.stop = "#{BIXBY_HOME}/bin/bixby-agent stop"
|
14
|
+
|
15
|
+
w.start_grace = 10.seconds
|
16
|
+
w.restart_grace = 10.seconds
|
17
|
+
|
18
|
+
# other scripts may drop privs
|
19
|
+
# w.uid = USER
|
20
|
+
# w.gid = GROUP
|
21
|
+
|
22
|
+
w.behavior(:clean_pid_file)
|
23
|
+
|
24
|
+
# determine the state on startup
|
25
|
+
w.transition(:init, { true => :up, false => :start }) do |on|
|
26
|
+
on.condition(:process_running) do |c|
|
27
|
+
c.running = true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# determine when process has finished starting
|
32
|
+
w.transition([:start, :restart], :up) do |on|
|
33
|
+
on.condition(:process_running) do |c|
|
34
|
+
c.running = true
|
35
|
+
end
|
36
|
+
|
37
|
+
# failsafe
|
38
|
+
on.condition(:tries) do |c|
|
39
|
+
c.times = 8
|
40
|
+
c.within = 2.minutes
|
41
|
+
c.transition = :start
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# start if process is not running
|
46
|
+
w.transition(:up, :start) do |on|
|
47
|
+
on.condition(:process_exits)
|
48
|
+
end
|
49
|
+
|
50
|
+
# restart if memory gets too high
|
51
|
+
w.transition(:up, :restart) do |on|
|
52
|
+
on.condition(:memory_usage) do |c|
|
53
|
+
c.above = 250.megabytes
|
54
|
+
c.times = 2
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# lifecycle
|
59
|
+
w.lifecycle do |on|
|
60
|
+
on.condition(:flapping) do |c|
|
61
|
+
c.to_state = [:start, :restart]
|
62
|
+
c.times = 5
|
63
|
+
c.within = 5.minute
|
64
|
+
c.transition = :unmonitored
|
65
|
+
c.retry_in = 10.minutes
|
66
|
+
c.retry_times = 5
|
67
|
+
c.retry_within = 2.hours
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
data/lib/bixby-agent.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
# Set BIXBY_HOME when in dev environment
|
3
|
+
path = File.expand_path(File.join(File.dirname(__FILE__), ".."))
|
4
|
+
if !ENV["BIXBY_HOME"] && File.directory?(File.join(path, ".git")) &&
|
5
|
+
File.basename($0) == "bixby-agent" then
|
6
|
+
|
7
|
+
ENV["BIXBY_HOME"] = path
|
8
|
+
end
|
9
|
+
|
10
|
+
require "bixby-common"
|
11
|
+
require "bixby-client"
|
12
|
+
|
13
|
+
require "bixby-agent/agent"
|
14
|
+
require "bixby-agent/version"
|
15
|
+
|
16
|
+
Bixby::Agent.setup_env()
|
@@ -0,0 +1,98 @@
|
|
1
|
+
|
2
|
+
require "uri"
|
3
|
+
require "rbconfig"
|
4
|
+
|
5
|
+
require "bixby-agent/config_exception"
|
6
|
+
require "bixby-agent/agent/handshake"
|
7
|
+
require "bixby-agent/agent/shell_exec"
|
8
|
+
require "bixby-agent/agent/config"
|
9
|
+
|
10
|
+
module Bixby
|
11
|
+
|
12
|
+
class << self
|
13
|
+
attr_accessor :agent
|
14
|
+
end
|
15
|
+
|
16
|
+
class Agent
|
17
|
+
|
18
|
+
DEFAULT_ROOT_DIR = "/opt/bixby"
|
19
|
+
|
20
|
+
include Bixby::Log
|
21
|
+
include Config
|
22
|
+
include Handshake
|
23
|
+
include ShellExec
|
24
|
+
|
25
|
+
attr_accessor :manager_uri, :uuid, :mac_address,
|
26
|
+
:access_key, :secret_key, :client
|
27
|
+
|
28
|
+
def self.create(root_dir=nil, use_config = true)
|
29
|
+
|
30
|
+
agent = load_config(root_dir) if use_config
|
31
|
+
if agent.nil? then
|
32
|
+
# create a new one if unable to load
|
33
|
+
agent = new()
|
34
|
+
end
|
35
|
+
|
36
|
+
# pass config to some modules
|
37
|
+
Bixby.agent = agent
|
38
|
+
return agent if agent.new?
|
39
|
+
|
40
|
+
Bixby.manager_uri = agent.manager_uri
|
41
|
+
Bixby.client = Bixby::Client.new(agent.access_key, agent.secret_key)
|
42
|
+
return agent
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize()
|
46
|
+
#uri, tenant = nil, password = nil, root_dir = nil
|
47
|
+
Bixby::Log.setup_logger()
|
48
|
+
@new = true
|
49
|
+
|
50
|
+
@uuid = create_uuid()
|
51
|
+
@mac_address = get_mac_address()
|
52
|
+
create_keypair()
|
53
|
+
end
|
54
|
+
private_class_method :new
|
55
|
+
|
56
|
+
# Setup the environment for shelling out. Makes sure the correct Ruby
|
57
|
+
# version is on the path and that bixby-agent will be loaded by default
|
58
|
+
def self.setup_env
|
59
|
+
# make sure the correct ruby version is on the path
|
60
|
+
c = begin; ::RbConfig::CONFIG; rescue NameError; ::Config::CONFIG; end
|
61
|
+
ruby_dir = File.expand_path(c['bindir'])
|
62
|
+
|
63
|
+
shell = Mixlib::ShellOut.new("which ruby")
|
64
|
+
shell.run_command
|
65
|
+
if not $?.success? or File.dirname(shell.stdout.strip) != ruby_dir then
|
66
|
+
ENV["PATH"] = ruby_dir + File::PATH_SEPARATOR + ENV["PATH"]
|
67
|
+
end
|
68
|
+
|
69
|
+
# create RUBYLIB paths
|
70
|
+
paths = []
|
71
|
+
if ENV.include? "RUBYLIB" and not ENV["RUBYLIB"].empty? then
|
72
|
+
paths = ENV["RUBYLIB"].split(/:/)
|
73
|
+
end
|
74
|
+
$:.each { |p|
|
75
|
+
if p =~ %r(/gems/) and not paths.include? p then
|
76
|
+
paths << p
|
77
|
+
end
|
78
|
+
}
|
79
|
+
self_lib = File.expand_path(File.join(File.dirname(__FILE__), '../..', 'lib'))
|
80
|
+
paths << self_lib if not paths.include? self_lib
|
81
|
+
|
82
|
+
ENV["RUBYLIB"] = paths.join(":")
|
83
|
+
ENV["RUBYOPT"] = '-rbixby-client/script'
|
84
|
+
end
|
85
|
+
|
86
|
+
# Get the WebSocket API URI
|
87
|
+
#
|
88
|
+
# @return [String] uri
|
89
|
+
def manager_ws_uri
|
90
|
+
# convert manager uri to websocket
|
91
|
+
uri = URI.parse(manager_uri)
|
92
|
+
uri.scheme = (uri.scheme == "https" ? "wss" : "ws")
|
93
|
+
uri.path = "/wsapi"
|
94
|
+
return uri.to_s
|
95
|
+
end
|
96
|
+
|
97
|
+
end # Agent
|
98
|
+
end # Bixby
|
@@ -0,0 +1,109 @@
|
|
1
|
+
|
2
|
+
require 'yaml'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module Bixby
|
6
|
+
class Agent
|
7
|
+
|
8
|
+
module Config
|
9
|
+
|
10
|
+
KEYS = %w{ manager_uri uuid mac_address access_key secret_key log_level }
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
|
14
|
+
def config_dir
|
15
|
+
Bixby.path("etc")
|
16
|
+
end
|
17
|
+
|
18
|
+
def config_file
|
19
|
+
File.join(config_dir, "bixby.yml")
|
20
|
+
end
|
21
|
+
|
22
|
+
def load_config(root_dir)
|
23
|
+
# make sure BIXBY_HOME is set correctly
|
24
|
+
ENV["BIXBY_HOME"] = File.expand_path(root_dir || ENV["BIXBY_HOME"] || Agent::DEFAULT_ROOT_DIR)
|
25
|
+
|
26
|
+
return nil if not File.exists? config_file
|
27
|
+
|
28
|
+
# load it!
|
29
|
+
begin
|
30
|
+
config = YAML.load_file(config_file)
|
31
|
+
if not config.kind_of? Hash or config.empty? then
|
32
|
+
bad_config("corrupted file contents")
|
33
|
+
end
|
34
|
+
|
35
|
+
log_level = config["log_level"]
|
36
|
+
log_level = log_level.strip.downcase if log_level.kind_of? String
|
37
|
+
Bixby::Log.setup_logger(:level => log_level)
|
38
|
+
|
39
|
+
agent = Agent.allocate
|
40
|
+
KEYS.each do |k|
|
41
|
+
m = "#{k}=".to_sym
|
42
|
+
agent.send(m, config[k]) if agent.respond_to? m
|
43
|
+
end
|
44
|
+
agent.new = false
|
45
|
+
|
46
|
+
return agent
|
47
|
+
|
48
|
+
rescue Exception => ex
|
49
|
+
if ex.kind_of? SystemExit then
|
50
|
+
raise ex
|
51
|
+
end
|
52
|
+
bad_config(ex) if ex.message != "exit"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def bad_config(ex = nil)
|
57
|
+
# TODO should force a reinstall/handshake?
|
58
|
+
$stderr.puts "error loading config from #{config_file}"
|
59
|
+
$stderr.puts "(#{ex})" if ex
|
60
|
+
$stderr.puts "exiting"
|
61
|
+
exit 1
|
62
|
+
end
|
63
|
+
|
64
|
+
end # ClassMethods
|
65
|
+
|
66
|
+
def self.included(clazz)
|
67
|
+
clazz.extend(ClassMethods)
|
68
|
+
end
|
69
|
+
|
70
|
+
def new=(val)
|
71
|
+
@new = val
|
72
|
+
end
|
73
|
+
|
74
|
+
def new?
|
75
|
+
@new
|
76
|
+
end
|
77
|
+
|
78
|
+
def config_dir
|
79
|
+
self.class.config_dir
|
80
|
+
end
|
81
|
+
|
82
|
+
def config_file
|
83
|
+
self.class.config_file
|
84
|
+
end
|
85
|
+
|
86
|
+
def init_config_dir
|
87
|
+
return if File.exists? config_dir
|
88
|
+
begin
|
89
|
+
FileUtils.mkdir_p(config_dir)
|
90
|
+
rescue Exception => ex
|
91
|
+
raise IOError.new(ex.message)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def save_config
|
96
|
+
init_config_dir()
|
97
|
+
config = {}
|
98
|
+
KEYS.each do |k|
|
99
|
+
m = k.to_sym
|
100
|
+
config[k] = self.send(m) if self.respond_to? m
|
101
|
+
end
|
102
|
+
config["log_level"] = Logging::Logger.root.level
|
103
|
+
File.open(config_file, 'w') { |out| out.write YAML.dump(config) }
|
104
|
+
end
|
105
|
+
|
106
|
+
end # Config
|
107
|
+
|
108
|
+
end # Agent
|
109
|
+
end # Bixby
|
@@ -0,0 +1,73 @@
|
|
1
|
+
|
2
|
+
require "openssl"
|
3
|
+
require "base64"
|
4
|
+
|
5
|
+
module Bixby
|
6
|
+
class Agent
|
7
|
+
|
8
|
+
module Crypto
|
9
|
+
|
10
|
+
# create crypto keypair and save in config folder
|
11
|
+
def create_keypair
|
12
|
+
init_config_dir()
|
13
|
+
pair = OpenSSL::PKey::RSA.generate(2048)
|
14
|
+
File.open(private_key_file, 'w') { |out| out.write(pair.to_s) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def private_key_file
|
18
|
+
File.join(self.config_dir, "id_rsa")
|
19
|
+
end
|
20
|
+
|
21
|
+
def keypair
|
22
|
+
@keypair ||= OpenSSL::PKey::RSA.new(File.read(private_key_file))
|
23
|
+
end
|
24
|
+
|
25
|
+
def public_key
|
26
|
+
@public_key ||= keypair.public_key
|
27
|
+
end
|
28
|
+
|
29
|
+
def private_key
|
30
|
+
keypair
|
31
|
+
end
|
32
|
+
|
33
|
+
def server_key_file
|
34
|
+
File.join(self.config_dir, "server.pub")
|
35
|
+
end
|
36
|
+
|
37
|
+
def have_server_key?
|
38
|
+
File.exists? server_key_file
|
39
|
+
end
|
40
|
+
|
41
|
+
def server_key
|
42
|
+
@server_key ||= OpenSSL::PKey::RSA.new(File.read(server_key_file))
|
43
|
+
end
|
44
|
+
|
45
|
+
def crypto_enabled?
|
46
|
+
b = ENV["BIXBY_NOCRYPTO"]
|
47
|
+
!(b and %w{1 true yes}.include? b)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Encrypt data using the server's public key
|
51
|
+
#
|
52
|
+
# @param [String] data data to encrypt
|
53
|
+
#
|
54
|
+
# @return [String] Base64 result
|
55
|
+
def encrypt_for_server(data)
|
56
|
+
Bixby::CryptoUtil.encrypt(data, self.uuid, server_key, keypair)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Decrypt data that was encrypted with our public key
|
60
|
+
#
|
61
|
+
# @param [String] data Base64 encoded data
|
62
|
+
#
|
63
|
+
# @return [String] unencrypted data
|
64
|
+
def decrypt_from_server(data)
|
65
|
+
data = StringIO.new(data, 'rb')
|
66
|
+
uuid = data.readline.strip # TODO throwaway the uuid for now
|
67
|
+
Bixby::CryptoUtil.decrypt(data, keypair, server_key)
|
68
|
+
end
|
69
|
+
|
70
|
+
end # Crypto
|
71
|
+
|
72
|
+
end # Agent
|
73
|
+
end # Bixby
|