ksconnect 0.1.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.
@@ -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: