bixby-agent 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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