icinga-cert-service 0.18.4

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,24 @@
1
+
2
+ module IcingaCertService
3
+ # Client Class to create on-the-fly a certificate to connect automaticly as satellite to an icinga2-master
4
+ #
5
+ #
6
+ module Download
7
+
8
+ # allows you to download a static file which is stored in the directory assets
9
+ #
10
+ # currently only 'icinga2_certificates.sh'
11
+ #
12
+ def download(params)
13
+
14
+ file_name = validate( params, required: true, var: 'file_name', type: String )
15
+ request = validate( params, required: true, var: 'request', type: Hash )
16
+
17
+ whitelist = ['icinga2_certificates.sh']
18
+
19
+ return { status: 500, message: 'file are unknown' } unless( whitelist.include?(file_name) )
20
+
21
+ { status: 200, path: format('%s/assets', @base_directory), file_name: file_name }
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,98 @@
1
+
2
+ module IcingaCertService
3
+ # Client Class to create on-the-fly a certificate to connect automaticly as satellite to an icinga2-master
4
+ #
5
+ #
6
+ module EndpointHandler
7
+
8
+ # add a Endpoint for distributed monitoring to the icinga2-master configuration
9
+ #
10
+ # @param [Hash, #read] params
11
+ # @option params [String] :host
12
+ #
13
+ # @example
14
+ # add_endpoint( host: 'icinga2-satellite' )
15
+ #
16
+ # @return [Hash, #read] if config already created:
17
+ # * :status [Integer] 204
18
+ # * :message [String] Message
19
+ #
20
+ # @return nil if successful
21
+ #
22
+ def add_endpoint(params)
23
+
24
+ host = validate( params, required: true, var: 'host', type: String )
25
+ satellite = validate( params, required: false, var: 'satellite', type: Boolean ) || false
26
+
27
+ return { status: 500, message: 'no hostname to create an endpoint' } if host.nil?
28
+
29
+ # add the API user
30
+ #
31
+ add_api_user(params)
32
+
33
+ # add the zone object
34
+ #
35
+ ret = add_zone(host)
36
+
37
+ # logger.debug( ret )
38
+
39
+ if( satellite )
40
+ file_name = '/etc/icinga2/zones.conf'
41
+ else
42
+ zone_directory = format('/etc/icinga2/zones.d/%s', host)
43
+ file_name = format('%s/%s.conf', zone_directory, host)
44
+
45
+ begin
46
+ FileUtils.mkpath(zone_directory) unless File.exist?(zone_directory)
47
+ rescue
48
+
49
+ end
50
+ end
51
+
52
+ if( File.exist?(file_name) )
53
+
54
+ file = File.open(file_name, 'r')
55
+ contents = file.read
56
+
57
+ regexp_long = / # Match she-bang style C-comment
58
+ \/\* # Opening delimiter.
59
+ [^*]*\*+ # {normal*} Zero or more non-*, one or more *
60
+ (?: # Begin {(special normal*)*} construct.
61
+ [^*\/] # {special} a non-*, non-\/ following star.
62
+ [^*]*\*+ # More {normal*}
63
+ )* # Finish "Unrolling-the-Loop"
64
+ \/ # Closing delimiter.
65
+ /x
66
+ result = contents.gsub(regexp_long, '')
67
+
68
+ scan_endpoint = result.scan(/object Endpoint(.*)"(?<endpoint>.+\S)"/).flatten
69
+
70
+ return { status: 200, message: format('the configuration for the endpoint %s already exists', host) } if( scan_endpoint.include?(host) == true )
71
+ end
72
+
73
+ logger.debug(format('i miss an configuration for endpoint \'%s\'', host))
74
+
75
+ begin
76
+
77
+ result = write_template(
78
+ template: 'templates/zones.d/endpoint.conf.erb',
79
+ destination_file: file_name,
80
+ environment: {
81
+ host: host
82
+ }
83
+ )
84
+
85
+ create_backup
86
+
87
+ # logger.debug( result )
88
+ rescue => error
89
+
90
+ logger.debug(error)
91
+ end
92
+
93
+ { status: 200, message: format('configuration for endpoint \'%s\' has been created', host) }
94
+
95
+ end
96
+
97
+ end
98
+ end
@@ -0,0 +1,34 @@
1
+
2
+ require 'open3'
3
+
4
+ module IcingaCertService
5
+ # SubModule to execute some commands
6
+ #
7
+ #
8
+ module Executor
9
+
10
+ # execute system commands with a Open3.popen2() call
11
+ #
12
+ # @param [Hash, #read] params
13
+ # @option params [String] :cmd
14
+ #
15
+ # @return [Hash, #read]
16
+ # * :exit [Integer] Exit-Code
17
+ # * :message [String] Message
18
+ def exec_command( params )
19
+
20
+ cmd = params.dig(:cmd)
21
+
22
+ return { code: 1, message: 'no command found' } if( cmd.nil? )
23
+
24
+ result = {}
25
+
26
+ Open3.popen2( cmd ) do |_stdin, stdout_err, wait_thr|
27
+ return_value = wait_thr.value
28
+ result = { code: return_value.success?, message: stdout_err.gets }
29
+ end
30
+
31
+ result
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,43 @@
1
+
2
+ module IcingaCertService
3
+
4
+ # small in-memory Cache
5
+ #
6
+ module InMemoryDataCache
7
+
8
+ # create a new Instance
9
+ #
10
+ def initialize
11
+ @storage = {}
12
+ end
13
+
14
+ # save data
15
+ #
16
+ # @param [String, #read] id
17
+ # @param [misc, #read] data
18
+ #
19
+ def save(id, data)
20
+ @storage ||= {}
21
+ @storage[id] ||= {}
22
+ @storage[id] = data
23
+ end
24
+
25
+ # get data
26
+ #
27
+ # @param [String, #read] id
28
+ #
29
+ def find_by_id(id)
30
+ if( !@storage.nil? )
31
+ @storage.dig(id) || {}
32
+ else
33
+ {}
34
+ end
35
+ end
36
+
37
+ # get all data
38
+ #
39
+ def entries
40
+ @storage
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,62 @@
1
+
2
+ module IcingaCertService
3
+ # Client Class to create on-the-fly a certificate to connect automaticly as satellite to an icinga2-master
4
+ #
5
+ #
6
+ module Templates
7
+
8
+ # write files based on a template
9
+ #
10
+ # @param [Hash, #read] params
11
+ # @option params [String] :template
12
+ # @option params [String] :destination_file
13
+ # @option params [Hash] :environment
14
+ #
15
+ # @example
16
+ # write_template(
17
+ # template: 'templates/zones.d/endpoint.conf.erb',
18
+ # destination_file: file_name,
19
+ # environment: {
20
+ # host: host
21
+ # }
22
+ # )
23
+ #
24
+ #
25
+ def write_template(params)
26
+
27
+ template = validate( params, required: true, var: 'template', type: String )
28
+ destination_file = validate( params, required: true, var: 'destination_file', type: String )
29
+ environment = validate( params, required: true, var: 'environment', type: Hash )
30
+
31
+ template = format( '%s/%s', @base_directory, template )
32
+
33
+ return { status: 500, message: "template '#{template}' not found." } if( ! File.exist?(template) )
34
+
35
+ begin
36
+ template = ERB.new File.new(template).read
37
+ date = Time.now
38
+ template = template.result( binding )
39
+
40
+ begin
41
+ logger.debug( "write to file: #{destination_file}" )
42
+ file = File.open(destination_file, 'a')
43
+ file.write(template)
44
+ rescue => error
45
+ logger.error(error.to_s)
46
+ { status: 500, message: error.to_s }
47
+ ensure
48
+ file.close unless file.nil?
49
+ end
50
+ rescue => error
51
+ logger.error(error.to_s)
52
+ logger.debug( error.backtrace.join("\n") )
53
+
54
+ { status: 500, message: error.to_s }
55
+ end
56
+
57
+ { status: 200, message: format('file \'%s\' has been created', destination_file) }
58
+
59
+ end
60
+
61
+ end
62
+ end
@@ -0,0 +1,5 @@
1
+
2
+ module IcingaCertService
3
+ # static version string
4
+ VERSION = '0.18.4'.freeze
5
+ end
@@ -0,0 +1,71 @@
1
+
2
+ module IcingaCertService
3
+ # Client Class to create on-the-fly a certificate to connect automaticly as satellite to an icinga2-master
4
+ #
5
+ #
6
+ module ZoneHandler
7
+
8
+ # add a satellite zone to 'zones.conf'
9
+ #
10
+ # @param [String] zone
11
+ #
12
+ # @example
13
+ # add_zone('icinga2-satellite')
14
+ #
15
+ # @return [Hash, #read] if config already created:
16
+ # * :status [Integer] 200
17
+ # * :message [String] Message
18
+ #
19
+ # @return nil if successful
20
+ #
21
+ def add_zone( zone = nil )
22
+
23
+ return { status: 500, message: 'no zone defined' } if zone.nil?
24
+
25
+ zone_file = '/etc/icinga2/zones.conf'
26
+
27
+ if(File.exist?(zone_file))
28
+
29
+ file = File.open(zone_file, 'r')
30
+ contents = file.read
31
+
32
+ regexp_long = / # Match she-bang style C-comment
33
+ \/\* # Opening delimiter.
34
+ [^*]*\*+ # {normal*} Zero or more non-*, one or more *
35
+ (?: # Begin {(special normal*)*} construct.
36
+ [^*\/] # {special} a non-*, non-\/ following star.
37
+ [^*]*\*+ # More {normal*}
38
+ )* # Finish "Unrolling-the-Loop"
39
+ \/ # Closing delimiter.
40
+ /x
41
+ result = contents.gsub(regexp_long, '')
42
+ scan_zone = result.scan(/object Zone(.*)"(?<zone>.+\S)"/).flatten
43
+
44
+ return { status: 200, message: format('the configuration for the zone %s already exists', zone) } if( scan_zone.include?(zone) == true )
45
+ end
46
+
47
+ logger.debug(format('i miss an configuration for zone \'%s\'', zone))
48
+
49
+ begin
50
+ result = write_template(
51
+ template: 'templates/zones.conf.erb',
52
+ destination_file: zone_file,
53
+ environment: {
54
+ zone: zone,
55
+ icinga_master: @icinga_master
56
+ }
57
+ )
58
+ # logger.debug(result)
59
+
60
+ rescue => error
61
+ logger.error(error.to_s)
62
+
63
+ return { status: 500, message: error.to_s }
64
+ end
65
+
66
+ { status: 200, message: format('configuration for zone %s has been created', zone) }
67
+
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,61 @@
1
+
2
+ require 'logger'
3
+
4
+ # -------------------------------------------------------------------------------------------------
5
+
6
+ module Logging
7
+
8
+ # global function to use the logger instance
9
+ #
10
+ def logger
11
+ @logger ||= Logging.logger_for( self.class.name )
12
+ end
13
+
14
+ # Use a hash class-ivar to cache a unique Logger per class:
15
+ @loggers = {}
16
+
17
+ class << self
18
+
19
+ # create an logger instance for classname
20
+ #
21
+ def logger_for( classname )
22
+ @loggers[classname] ||= configure_logger_for( classname )
23
+ end
24
+
25
+ # configure the logger
26
+ #
27
+ def configure_logger_for( classname )
28
+
29
+ log_level = ENV.fetch('LOG_LEVEL', 'DEBUG' )
30
+ level = log_level.dup
31
+
32
+ # DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN
33
+ log_level = case level.upcase
34
+ when 'DEBUG'
35
+ Logger::DEBUG # Low-level information for developers.
36
+ when 'INFO'
37
+ Logger::INFO # Generic (useful) information about system operation.
38
+ when 'WARN'
39
+ Logger::WARN # A warning.
40
+ when 'ERROR'
41
+ Logger::ERROR # A handleable error condition.
42
+ when 'FATAL'
43
+ Logger::FATAL # An unhandleable error that results in a program crash.
44
+ else
45
+ Logger::UNKNOWN # An unknown message that should always be logged.
46
+ end
47
+
48
+ $stdout.sync = true
49
+ logger = Logger.new($stdout)
50
+ logger.level = log_level
51
+ logger.datetime_format = '%Y-%m-%d %H:%M:%S'
52
+ logger.formatter = proc do |severity, datetime, progname, msg|
53
+ "[#{datetime.strftime( logger.datetime_format )}] #{severity.ljust(5)} #{msg}\n"
54
+ end
55
+
56
+ logger
57
+ end
58
+ end
59
+ end
60
+
61
+ # -------------------------------------------------------------------------------------------------
@@ -0,0 +1,128 @@
1
+
2
+ # -----------------------------------------------------------------------------
3
+ # Monkey patches
4
+
5
+ # Modify `Object`
6
+ #
7
+ # original from (https://gist.github.com/Integralist/9503099)
8
+ #
9
+ # None of the above solutions work with a multi-level hash
10
+ # They only work on the first level: {:foo=>"bar", :level1=>{"level2"=>"baz"}}
11
+ # The following two variations solve the problem in the same way
12
+ # transform hash keys to symbols
13
+ #
14
+ # @example
15
+ # multi_hash = { 'foo' => 'bar', 'level1' => { 'level2' => 'baz' } }
16
+ # multi_hash = multi_hash.deep_string_keys
17
+ #
18
+ class Object
19
+
20
+ # transform hash keys to symbols
21
+ #
22
+ # @example
23
+ # multi_hash = { 'foo' => 'bar', 'level1' => { 'level2' => 'baz' } }.deep_string_keys
24
+ #
25
+ # @return
26
+ # { foo: 'bar', level1: { 'level2' => 'baz' } }
27
+ #
28
+ def deep_symbolize_keys
29
+ if( is_a?( Hash ) )
30
+ return inject({}) do |memo, (k, v)|
31
+ memo.tap { |m| m[k.to_sym] = v.deep_string_keys }
32
+ end
33
+ elsif( is_a?( Array ) )
34
+ return map(&:deep_string_keys)
35
+ end
36
+
37
+ self
38
+ end
39
+
40
+ # transform hash keys to strings
41
+ #
42
+ #
43
+ def deep_string_keys
44
+ if( is_a?( Hash ) )
45
+ return inject({}) do |memo, (k, v)|
46
+ memo.tap { |m| m[k.to_s] = v.deep_string_keys }
47
+ end
48
+ elsif( is_a?( Array ) )
49
+ return map(&:deep_string_keys)
50
+ end
51
+
52
+ self
53
+ end
54
+
55
+ end
56
+
57
+ # -----------------------------------------------------------------------------
58
+
59
+ # check if is a checksum
60
+ #
61
+ class Object
62
+
63
+ REGEX = /\A[0-9a-f]{32,128}\z/i
64
+ CHARS = {
65
+ md2: 32,
66
+ md4: 32,
67
+ md5: 32,
68
+ sha1: 40,
69
+ sha224: 56,
70
+ sha256: 64,
71
+ sha384: 96,
72
+ sha512: 128
73
+ }
74
+
75
+ # return if this a checksum
76
+ #
77
+ # @example
78
+ # checksum.be_a_checksum
79
+ #
80
+ def be_a_checksum
81
+ !!(self =~ REGEX)
82
+ end
83
+
84
+ # return true if the checksum created by spezified type
85
+ #
86
+ # @example
87
+ # checksum.produced_by(:md5)
88
+ #
89
+ # checksum.produced_by(:sha256)
90
+ #
91
+ #
92
+ def produced_by( name )
93
+ function = name.to_s.downcase.to_sym
94
+
95
+ raise ArgumentError, "unknown algorithm given to be_a_checksum.produced_by: #{function}" unless CHARS.include?(function)
96
+
97
+ return true if( size == CHARS[function] )
98
+ false
99
+ end
100
+ end
101
+
102
+ # -----------------------------------------------------------------------------
103
+
104
+ # Monkey Patch to implement an Boolean Check
105
+ # original from: https://stackoverflow.com/questions/3028243/check-if-ruby-object-is-a-boolean/3028378#3028378
106
+ #
107
+ #
108
+ module Boolean; end
109
+ class TrueClass; include Boolean; end
110
+ class FalseClass; include Boolean; end
111
+
112
+ true.is_a?(Boolean) #=> true
113
+ false.is_a?(Boolean) #=> true
114
+
115
+ # -----------------------------------------------------------------------------
116
+
117
+ # add minutes
118
+ #
119
+ class Time
120
+
121
+ # add minutes 'm' to Time Object
122
+ #
123
+ def add_minutes(m)
124
+ self + (60 * m)
125
+ end
126
+ end
127
+
128
+ # -----------------------------------------------------------------------------