bixby-agent 0.3.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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/.testguardrc +1 -0
  4. data/.travis.yml +25 -0
  5. data/Gemfile +61 -0
  6. data/Gemfile.lock +237 -0
  7. data/LICENSE +21 -0
  8. data/Rakefile +13 -0
  9. data/VERSION +1 -0
  10. data/bin/bixby-agent +15 -0
  11. data/bixby-agent.gemspec +186 -0
  12. data/etc/bixby-god.initd +70 -0
  13. data/etc/bixby.god +20 -0
  14. data/etc/god.d/bixby-agent.god +71 -0
  15. data/lib/bixby-agent.rb +16 -0
  16. data/lib/bixby-agent/agent.rb +98 -0
  17. data/lib/bixby-agent/agent/config.rb +109 -0
  18. data/lib/bixby-agent/agent/crypto.rb +73 -0
  19. data/lib/bixby-agent/agent/handshake.rb +81 -0
  20. data/lib/bixby-agent/agent/shell_exec.rb +111 -0
  21. data/lib/bixby-agent/agent_handler.rb +38 -0
  22. data/lib/bixby-agent/app.rb +208 -0
  23. data/lib/bixby-agent/app/cli.rb +112 -0
  24. data/lib/bixby-agent/config_exception.rb +5 -0
  25. data/lib/bixby-agent/help/system_time.rb +41 -0
  26. data/lib/bixby-agent/version.rb +8 -0
  27. data/lib/bixby-agent/websocket/client.rb +186 -0
  28. data/tasks/cane.rake +14 -0
  29. data/tasks/coverage.rake +2 -0
  30. data/tasks/coveralls.rake +11 -0
  31. data/tasks/jeweler.rake +21 -0
  32. data/tasks/test.rake +5 -0
  33. data/tasks/yard.rake +6 -0
  34. data/test/base.rb +92 -0
  35. data/test/helper.rb +29 -0
  36. data/test/stub_eventmachine.rb +38 -0
  37. data/test/support/root_dir/bixby.yml +8 -0
  38. data/test/support/root_dir/id_rsa +27 -0
  39. data/test/support/root_dir/server +27 -0
  40. data/test/support/root_dir/server.pub +9 -0
  41. data/test/support/test_bundle/bin/cat +2 -0
  42. data/test/support/test_bundle/bin/echo +2 -0
  43. data/test/support/test_bundle/digest +17 -0
  44. data/test/support/test_bundle/manifest.json +0 -0
  45. data/test/test_agent.rb +110 -0
  46. data/test/test_agent_exec.rb +53 -0
  47. data/test/test_agent_handler.rb +72 -0
  48. data/test/test_app.rb +138 -0
  49. data/test/test_bixby_common.rb +18 -0
  50. data/test/test_crypto.rb +78 -0
  51. data/test/websocket/test_client.rb +110 -0
  52. metadata +557 -0
@@ -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
@@ -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