ksconnect 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e7d84e395ec798ed06bbd384d903b8d792e40be4
4
+ data.tar.gz: 33129b381efe034aabbd70828f4327c7d81cd4ee
5
+ SHA512:
6
+ metadata.gz: 035bfbbf2af3736d47754bbc370cb7ab2dbac83110c435b39909cdb81b37022b9d824b61f6623d3c028e5a94eccb9188381353e2af4c09222bf6d4d06c72f989
7
+ data.tar.gz: 669b9a916103bb0e6a2ee8e8756a8cb860c8acedfa733b3cf15a459592a8ab86a70ae7433ee2323e0e1b9f9fa044bcca0b474199fd82d014f7e2df77e05d55b8
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Kloudsec
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,70 @@
1
+ # KSConnect
2
+
3
+ KSConnect provides a Ruby connection interface for Kloudsec plugins by exposing a simple to use
4
+ API for managing and synchronizing plugin data.
5
+
6
+ # Usage
7
+
8
+ Initialize the api:
9
+
10
+ `api = KSConnect.new(:ssl).api`
11
+
12
+ Use it:
13
+
14
+ `api.domains['domain-1.com'].data['some_flag'] = true`
15
+
16
+ ## Options
17
+
18
+ Initialize with additional (untested) helpers:
19
+
20
+ ```
21
+ api = KSConnect.new(:ssl, use_helpers: true).api
22
+ api.ip_address_for('example.com') => # 127.0.0.1
23
+ ```
24
+
25
+ ## Getting the full domain list
26
+
27
+ All domain objects:
28
+
29
+ `api.all_domains # => { 'domain-1.com' => <Domain>, ... }`
30
+
31
+ Domain names only:
32
+
33
+ `api.all_domains.keys # => ['domain-1.com',..]`
34
+
35
+ ## Getting data
36
+
37
+ `api.domains['domain-1.com'].data['key'] # => 'value'`
38
+
39
+ `api.domains['domain-1.com'].data.getall # => { 'key' => 'value', ... }`
40
+
41
+ ## Setting data
42
+
43
+ `api.domains['domain-1.com'].data['key'] = new_value`
44
+
45
+ `api.domains['domain-1.com'].data.setall = { 'key': new_value, ... }`
46
+
47
+ ## Event callbacks
48
+
49
+ Available callbacks for `<plugin>:push`:
50
+
51
+ - When plugin is enabled: `on_initialize`
52
+ - When plugin's IP is updated: `on_update`
53
+ - When plugin is removed: `on_teardown`
54
+ - Any other incoming message: `on_push`
55
+
56
+ ```
57
+ api = KSConnect.new(:ssl).api
58
+ api.configure do |config|
59
+ config.on_initialize = lambda { |msg| do_some_initialization(msg) }
60
+ end
61
+ ```
62
+
63
+ NOTE: there is no need to re-message proxy / core plugins to re-read configuration. This is done automatically.
64
+
65
+ ## Channels
66
+
67
+ Safely acquire a redis channel in a separate thread by doing:
68
+
69
+ `new_channel = KSConnect.channel('channel_name') { |msg| puts msg }`
70
+
@@ -0,0 +1,40 @@
1
+ require 'logs'
2
+
3
+ class KSConnect
4
+ include Logs
5
+ attr_reader :api
6
+ attr_reader :plugin
7
+
8
+ def initialize(*args)
9
+ plugins = args
10
+
11
+ additional_options = args.last.is_a?(Hash) ? args.last : nil
12
+ if additional_options
13
+ plugins.pop
14
+ else
15
+ additional_options = {}
16
+ end
17
+
18
+ @api = KSConnect::API.new(enabled_plugins: plugins, use_helpers: additional_options[:use_helpers])
19
+ end
20
+
21
+ def self.channel(name)
22
+ Thread.start do
23
+ begin
24
+ Redis.new.subscribe(name) do |on|
25
+ logger.info "Subscribing to redis channel: #{name}"
26
+ on.message do |channel, message|
27
+ yield message
28
+ end
29
+ $stdout.flush
30
+ end
31
+ rescue Exception => error
32
+ logger.error "#{error} on redis channel #{name}, restarting in 0.5s"
33
+ logger.error error.backtrace
34
+ $stdout.flush
35
+ sleep 0.5
36
+ retry
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,25 @@
1
+ class KSConnect
2
+ class API
3
+ attr_reader :plugins
4
+ attr_reader :plugin # the current / default plugin
5
+
6
+ def initialize(opts)
7
+ enabled_plugins = *opts[:enabled_plugins] || []
8
+ plugins = enabled_plugins.unshift(:core).uniq # always load core first
9
+ @plugins = plugins.reduce({}) { |hash, plugin_name| hash.tap { |h| h[plugin_name] = KSConnect::API::Plugin.new(plugin_name.to_s) } }
10
+ @plugin = @plugins[*opts[:enabled_plugins].first || :core]
11
+
12
+ if opts[:use_helpers]
13
+ extend KSConnect::Helpers
14
+ end
15
+ end
16
+
17
+ def domains
18
+ @plugin.domains
19
+ end
20
+
21
+ def all_domains
22
+ @plugins[:core].domains
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,102 @@
1
+ require 'redis'
2
+ require 'json'
3
+
4
+ class KSConnect
5
+ class API
6
+ class Plugin
7
+ attr_accessor :domains
8
+ attr_reader :name
9
+ attr_writer :config
10
+
11
+ def initialize(name)
12
+ @name = name
13
+ @domains = {}
14
+
15
+ load_domains
16
+ subscribe_to_events
17
+ end
18
+
19
+ def config
20
+ @config ||= Config.new
21
+ end
22
+
23
+ def configure
24
+ yield(config)
25
+ end
26
+
27
+ # This method loads the domain list from Redis, adding or removing domains as appropriate.
28
+ # Note that it does not update the ip address of existing domains.
29
+ def load_domains
30
+ # load domain list
31
+ domain_to_ip = redis.hgetall(domains_key)
32
+
33
+ # add new domains
34
+ new_domains = domain_to_ip.keys - @domains.values.map(&:name)
35
+ new_domains.each { |domain_name| @domains[domain_name] = Domain.new(domain_name, domain_to_ip[domain_name], @name) }
36
+
37
+ # remove old domains
38
+ @domains.select! { |k, _| domain_to_ip.keys.include?(k) }
39
+ end
40
+
41
+ def subscribe_to_events
42
+ KSConnect.channel("#{name}:push") do |message|
43
+ begin
44
+ msg = JSON.parse(message)
45
+ rescue Exception => e
46
+ puts "Error parsing message as JSON: #{msg}"
47
+ next
48
+ end
49
+
50
+ if %w(initialize update teardown).include? msg['request_type']
51
+ perform_request(msg)
52
+ else
53
+ config.on_push.call(message)
54
+ end
55
+ end
56
+ end
57
+
58
+ def perform_request(request)
59
+ request.stringify_keys!
60
+
61
+ domain_name = request['domain_name']
62
+ puts "Invalid push request with no domain: #{request}" and return unless domain_name
63
+
64
+ request_type = request['request_type']
65
+ case request_type
66
+ when 'initialize'
67
+ @domains[domain_name] = Domain.new(domain_name, get_ip_for(domain_name), @name)
68
+ config.on_initialize.call(request)
69
+ when 'update'
70
+ @domains[domain_name].ip_address = get_ip_for(domain_name)
71
+ config.on_update.call(request)
72
+ when 'teardown'
73
+ @domains.delete(domain_name)
74
+ config.on_teardown.call(request)
75
+ else
76
+ raise "Invalid request type"
77
+ end
78
+
79
+ redis.publish("core:push", { domain_name: domain_name, plugin_name: @name, request_type: request_type }.to_json) unless plugin_is_core?
80
+ end
81
+
82
+ def domains_key
83
+ @domain_list_uuid ||= redis.hget("#{@name}:data", "domain_names")
84
+ "kloudsec_data:#{@domain_list_uuid}"
85
+ end
86
+
87
+ private
88
+
89
+ def plugin_is_core?
90
+ @name == 'core'
91
+ end
92
+
93
+ def get_ip_for(domain_name)
94
+ redis.hget(domains_key, domain_name)
95
+ end
96
+
97
+ def redis
98
+ Redis.current
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,13 @@
1
+ class KSConnect
2
+ class API
3
+ class Plugin
4
+ class Config
5
+ attr_accessor :on_initialize, :on_update, :on_teardown, :on_push
6
+
7
+ def initialize
8
+ @on_initialize = @on_update = @on_teardown = @on_push = lambda { |msg| "No callback set for msg: #{msg}" }
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,87 @@
1
+ require 'redis'
2
+ require 'active_support/hash_with_indifferent_access'
3
+
4
+ class KSConnect
5
+ class API
6
+ class Plugin
7
+ class Data
8
+ attr_reader :type
9
+
10
+ def initialize(plugin_name, domain_name, type = :data, use_cache = true)
11
+ @plugin_name = plugin_name
12
+ @domain_name = domain_name
13
+ @type = type
14
+ @use_cache = use_cache
15
+
16
+ @data = ActiveSupport::HashWithIndifferentAccess.new if @use_cache
17
+ @data_uuid = nil
18
+ end
19
+
20
+ def []=(field, value)
21
+ @data[field] = value if @use_cache
22
+ redis.hset(key, field, value)
23
+ redis.publish("core:push", { plugin_name: @plugin_name, domain_name: @domain_name, request_type: 'update' })
24
+ end
25
+
26
+ def setall(hash)
27
+ @data = @data.merge(hash) if @use_cache
28
+ redis.mapped_hmset(key, hash)
29
+ redis.publish("core:push", { plugin_name: @plugin_name, domain_name: @domain_name, request_type: 'update' })
30
+ end
31
+
32
+ def [](field)
33
+ if @use_cache
34
+ @data ||= redis.hgetall(key)
35
+ @data[field]
36
+ else
37
+ redis.hget(key, field)
38
+ end
39
+ end
40
+
41
+ def getall
42
+ if @use_cache
43
+ @data ||= redis.hgetall(key)
44
+ else
45
+ redis.hgetall(key)
46
+ end
47
+ end
48
+
49
+ def reload
50
+ @data = redis.hgetall(key) if @use_cache
51
+ end
52
+
53
+ def delete(field)
54
+ @data.delete(field) if @use_cache
55
+ redis.hdel(key, field)
56
+ end
57
+
58
+ def key
59
+ set_data_uuid unless @data_uuid
60
+ "kloudsec_data:#{@data_uuid}"
61
+ end
62
+
63
+ private
64
+
65
+ def set_data_uuid
66
+ begin
67
+ tries ||= 3
68
+ id = redis.hget("#{@plugin_name}:#{@type}", @domain_name)
69
+ if id
70
+ @data_uuid = id
71
+ else
72
+ @data_uuid = SecureRandom.uuid
73
+ raise "Race on setting data key failed." unless redis.hsetnx("#{@plugin_name}:#{@type}", @domain_name, @data_uuid)
74
+ end
75
+ rescue Exception => e
76
+ puts e.message
77
+ retry unless (tries -= 1).zero?
78
+ end
79
+ end
80
+
81
+ def redis
82
+ Redis.current
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,41 @@
1
+ require 'redis'
2
+
3
+ class KSConnect
4
+ class API
5
+ class Plugin
6
+ class Domain
7
+ attr_accessor :ip_address
8
+ attr_reader :data
9
+ attr_reader :private_data
10
+ attr_reader :name
11
+ attr_reader :plugin_name
12
+
13
+ def initialize(name, ip_address, plugin_name)
14
+ @name = name
15
+ @ip_address = ip_address
16
+ @plugin_name = plugin_name
17
+ @data = Data.new(plugin_name, name, :data)
18
+ @private_data = Data.new(plugin_name, name, :private_data)
19
+ end
20
+
21
+ def notify(data)
22
+ redis.lpush("kloudsec_notifications", data.merge({ domain_name: @name, plugin_name: @plugin_name }))
23
+ end
24
+
25
+ def add_issue(issue_type, data)
26
+ redis.lpush("kloudsec_issues", data.merge({ domain_name: @name, plugin_name: @plugin_name, issue_type: issue_type, request_type: 'add' }))
27
+ end
28
+
29
+ def clear_issue(issue_type)
30
+ redis.lpush("kloudsec_issues", { domain_name: @name, plugin_name: @plugin_name, issue_type: issue_type, request_type: 'remove' })
31
+ end
32
+
33
+ private
34
+
35
+ def redis
36
+ Redis.current
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,63 @@
1
+ class KSConnect
2
+ module Helpers
3
+ def ip_address_for(domain)
4
+ if all_domains[domain].present?
5
+ all_domains[domain].ip_address
6
+ else
7
+ "kloudsec.com" # go to something safe if ip is invalid
8
+ end
9
+ end
10
+
11
+ def https_enabled?(domain)
12
+ k, c = ssl_key_and_cert_for(domain)
13
+ k && c
14
+ end
15
+
16
+ def https_redirect_enabled?(domain)
17
+ https_enabled?(domain) && plugins[:ssl].domains[domain].data['redirect'] == "true"
18
+ end
19
+
20
+ def https_rewriting_enabled?(domain)
21
+ https_enabled?(domain) && plugins[:ssl].domains[domain].data['rewriteHTTPS'] == "true"
22
+ end
23
+
24
+ def waf_enabled?(domain)
25
+ plugins[:web_shield].domains.include?(domain)
26
+ end
27
+
28
+ def waf_learning?(domain)
29
+ waf_enabled?(domain) && plugins[:web_shield].domains[domain].data['learning']
30
+ end
31
+
32
+ def pagespeed_enabled?(domain)
33
+ plugins[:mod_cache].domains.include?(domain)
34
+ end
35
+
36
+ def pending_autossl?(domain)
37
+ p, k = autossl_verification_path_and_key_for(domain)
38
+ plugins[:autossl].domains.include?(domain) && p && k
39
+ end
40
+
41
+ def autossl_verification_path_and_key_for(domain)
42
+ d = plugins[:autossl].domains[domain].private_data
43
+ if d
44
+ return d['verify_endpoint'], d['verify_content']
45
+ else
46
+ return nil, nil
47
+ end
48
+ end
49
+
50
+ def ssl_key_and_cert_for(domain)
51
+ ssl = plugins[:ssl].domains[domain].private_data
52
+ autossl = plugins[:autossl].domains[domain].private_data
53
+
54
+ if ssl && ssl['key'] && ssl['cert']
55
+ return ssl['key'], ssl['cert']
56
+ elsif autossl && autossl['key'] && autossl['cert']
57
+ return autossl['key'], autossl['cert']
58
+ else
59
+ return nil, nil
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,25 @@
1
+ require 'logger'
2
+ require 'active_support/inflector'
3
+
4
+ module Logs
5
+ def self.included base
6
+ base.send :include, InstanceMethods
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module InstanceMethods
11
+ def logger
12
+ self.class.logger
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+ def logger
18
+ return @_logger if @_logger
19
+
20
+ @_logger = Logger.new(STDOUT)
21
+ @_logger.level = "Logger::#{ENV['LOG_LEVEL'] || INFO}".constantize
22
+ @_logger
23
+ end
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ksconnect
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ivan Poon
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-01-28 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |-
14
+ KSConnect provides a connection interface for Kloudsec plugins by exposing a simple to use
15
+ API for managing and synchronizing plugin data.
16
+ email: ivan@kloudsec.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - LICENSE
22
+ - README.md
23
+ - lib/ksconnect.rb
24
+ - lib/ksconnect/api.rb
25
+ - lib/ksconnect/api/plugin.rb
26
+ - lib/ksconnect/api/plugin/config.rb
27
+ - lib/ksconnect/api/plugin/data.rb
28
+ - lib/ksconnect/api/plugin/domain.rb
29
+ - lib/ksconnect/helpers.rb
30
+ - lib/logs.rb
31
+ homepage: https://kloudsec.com
32
+ licenses:
33
+ - MIT
34
+ metadata: {}
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubyforge_project:
51
+ rubygems_version: 2.4.8
52
+ signing_key:
53
+ specification_version: 4
54
+ summary: Connection API for Kloudsec
55
+ test_files: []
56
+ has_rdoc: