Class: IcingaCertService::Client

Inherits:
Object
  • Object
show all
Includes:
Backup, CertificateHandler, Download, EndpointHandler, Executor, InMemoryDataCache, Templates, Validator, ZoneHandler, Logging, Util::Tar
Defined in:
lib/cert-service.rb

Overview

Client Class to create on-the-fly a certificate to connect automaticly as satellite to an icinga2-master

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Download

#download

Methods included from Backup

#create_backup

Methods included from InMemoryDataCache

#entries, #find_by_id, #save

Methods included from ZoneHandler

#add_zone

Methods included from EndpointHandler

#add_endpoint

Methods included from CertificateHandler

#check_certificate, #create_certificate, #enable_endpoint, #pki_ticket, #sign_certificate, #validate_certificate

Methods included from Executor

#exec_command

Methods included from Templates

#write_template

Methods included from Validator

#validate

Methods included from Util::Tar

#gzip, #tar, #ungzip, #untar

Methods included from Logging

configure_logger_for, #logger, logger_for

Constructor Details

#initialize(settings) ⇒ Client

create a new instance

Examples:

IcingaCertService::Client.new( icinga_master: 'icinga2-master.example.com' )

Parameters:

  • settings (Hash, #read)

    to configure the Client

  • params (Hash)

    a customizable set of options

Raises:

  • (ArgumentError)


58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/cert-service.rb', line 58

def initialize( settings )

  raise ArgumentError.new('only Hash are allowed') unless( settings.is_a?(Hash) )
  raise ArgumentError.new('missing settings') if( settings.size.zero? )

  @icinga_master       = settings.dig(:icinga, :server)
  @icinga_port         = settings.dig(:icinga, :api, :port)     || 5665
  @icinga_api_user     = settings.dig(:icinga, :api, :user)     || 'root'
  @icinga_api_password = settings.dig(:icinga, :api, :password) || 'icinga'

  @base_directory      = ENV.fetch('CERT_SERVICE', '/usr/local/icinga2-cert-service')

  raise ArgumentError.new('missing \'icinga server\'') if( @icinga_master.nil? )

  raise ArgumentError.new(format('wrong type. \'icinga api port\' must be an Integer, given \'%s\'', @icinga_port.class.to_s)) unless( @icinga_port.is_a?(Integer) )
  raise ArgumentError.new(format('wrong type. \'icinga api user\' must be an String, given \'%s\''    , @icinga_api_user.class.to_s)) unless( @icinga_api_user.is_a?(String) )
  raise ArgumentError.new(format('wrong type. \'icinga api password\' must be an String, given \'%s\'', @icinga_api_password.class.to_s)) unless( @icinga_api_password.is_a?(String) )

  @tmp_directory       = '/tmp/icinga-pki'

  version       = IcingaCertService::VERSION
  date          = '2019-05-12'
  detect_version

  logger.info('-----------------------------------------------------------------')
  logger.info('  certificate service for Icinga2')
  logger.info(format('    Version %s (%s)', version, date))
  logger.info('    Copyright 2017-2020 Bodo Schulz')
  logger.info(format('    Icinga2 base version %s', @icinga_version))
  logger.info('-----------------------------------------------------------------')
  logger.info('')

end

Instance Attribute Details

#icinga_versionObject

Returns the value of attribute icinga_version.



48
49
50
# File 'lib/cert-service.rb', line 48

def icinga_version
  @icinga_version
end

Instance Method Details

#add_api_user(params) ⇒ Hash, #read

Examples:

add_api_user( host: 'icinga2-satellite' )

Parameters:

  • params (Hash, #read)

Options Hash (params):

  • :host (String)

Returns:

  • (Hash, #read)

    if config already created:

    • :status [Integer] 204

    • :message [String] Message

  • nil if successful



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/cert-service.rb', line 213

def add_api_user(params)

  host = params.dig(:host)

  return { status: 500, message: 'no hostname to create an api user' } if( host.nil? )

  file_name = '/etc/icinga2/conf.d/api-users.conf'

  return { status: 500, message: format( 'api user not successful configured! file %s missing', file_name ) } unless( File.exist?(file_name) )

  file     = File.open(file_name, 'r')
  contents = file.read

  regexp_long = / # Match she-bang style C-comment
    \/\*          # Opening delimiter.
    [^*]*\*+      # {normal*} Zero or more non-*, one or more *
    (?:           # Begin {(special normal*)*} construct.
      [^*\/]      # {special} a non-*, non-\/ following star.
      [^*]*\*+    # More {normal*}
    )*            # Finish "Unrolling-the-Loop"
    \/            # Closing delimiter.
  /x
  result = contents.gsub(regexp_long, '')

  scan_api_user     = result.scan(/object ApiUser(.*)"(?<zone>.+\S)"(.*){(.*)/).flatten

  return { status: 200, message: format('the configuration for the api user %s already exists', host) } if( scan_api_user.include?(host) == true )

  logger.debug(format('i miss an configuration for api user %s', host))

  begin

    result = write_template(
      template: 'templates/conf.d/api_users.conf.erb',
      destination_file: file_name,
      environment: {
        host: host
      }
    )

    # logger.debug( result )
  rescue => error

    logger.debug(error)
  end

  return { status: 200, message: format('configuration for api user %s has been created', host) }
end

#detect_versionObject

detect the Icinga2 Version

Examples:

detect_version


97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/cert-service.rb', line 97

def detect_version

  max_retries  = 20
  sleep_between_retries = 8
  retried = 0

  @icinga_version = 'unknown'

  begin
    #response = rest_client.get( headers )
    response = RestClient::Request.execute(
      method: :get,
      url: format('https://%s:%d/v1/status/IcingaApplication', @icinga_master, @icinga_port ),
      timeout: 5,
      headers: { 'Content-Type' => 'application/json', 'Accept' => 'application/json' },
      user: @icinga_api_user,
      password: @icinga_api_password,
      verify_ssl: OpenSSL::SSL::VERIFY_NONE
    )

    response = response.body if(response.is_a?(RestClient::Response))
    response = JSON.parse(response) if(response.is_a?(String))
    results  = response.dig('results') if(response.is_a?(Hash))
    results  = results.first if(results.is_a?(Array))
    app_data = results.dig('status','icingaapplication','app')
    version  = app_data.dig('version') if(app_data.is_a?(Hash))

    if(version.is_a?(String))
      parts    = version.match(/^r(?<v>[0-9]+\.{0}\.[0-9]+)(.*)/i)
      @icinga_version = parts['v'].to_s.strip if(parts)
    end

  rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::EADDRNOTAVAIL => e
    logger.debug( "#{e}" )
    sleep( sleep_between_retries )
    retry
  rescue RestClient::ExceptionWithResponse => e

    if( retried < max_retries )
      retried += 1
      logger.debug( format( 'connection refused (retry %d / %d)', retried, max_retries ) )
      sleep( sleep_between_retries )
      retry
    else
      raise format( 'Maximum retries (%d) reached. Giving up ...', max_retries )
    end
  end
end

#icinga2_server_ip(name = Socket.gethostname) ⇒ Object

returns the IP of name

Parameters:

  • name (String, #read) (defaults to: Socket.gethostname)


335
336
337
# File 'lib/cert-service.rb', line 335

def icinga2_server_ip( name = Socket.gethostname )
  IPSocket.getaddress(name)
end

#icinga2_server_nameObject

returns the hostname of itself



327
328
329
# File 'lib/cert-service.rb', line 327

def icinga2_server_name
  Socket.gethostbyname(Socket.gethostname).first
end

#read_api_credentials(params = {}) ⇒ String, #read

function to read API Credentials from icinga2 Configuration

Examples:

read_api_credentials( api_user: 'admin' )

Parameters:

  • params (Hash, #read) (defaults to: {})

Options Hash (params):

  • :api_user (String)

    the API User, default is ‘cert-service’

Returns:

  • (String, #read)

    the configured Password or nil



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/cert-service.rb', line 156

def read_api_credentials(params = {})

  api_user     = params.dig(:api_user) || 'cert-service'

  file_name    = '/etc/icinga2/conf.d/api-users.conf'

  file        = File.open(file_name, 'r')
  contents    = file.read
  password    = nil

  regexp_long = / # Match she-bang style C-comment
    \/\*          # Opening delimiter.
    [^*]*\*+      # {normal*} Zero or more non-*, one or more *
    (?:           # Begin {(special normal*)*} construct.
      [^*\/]      # {special} a non-*, non-\/ following star.
      [^*]*\*+    # More {normal*}
    )*            # Finish "Unrolling-the-Loop"
    \/            # Closing delimiter.
  /x

  regex       = /\"#{api_user}\"(.*){(.*)password(.*)=(.*)\"(?<password>.+[a-zA-Z0-9])\"(.*)}\n/m

  # remove comments
  result      = contents.gsub(regexp_long, '')

  # split our string into more parts
  result      = result.split('object ApiUser')

  # now, iterate over all blocks and get the password
  #
  result.each do |block|
    password = block.scan(regex)

    next unless password.is_a?(Array) && password.count == 1

    password = password.flatten.first
    break
  end

  password
end

#read_ticket_saltObject



309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/cert-service.rb', line 309

def read_ticket_salt

  file_name    = '/etc/icinga2/constants.conf'

  return { status: 500, message: format( 'icinga2 not successful configured! file %s missing', file_name ) } unless( File.exist?(file_name) )

  file     = File.open(file_name, 'r')
  contents = file.read

  salt = contents.scan(/const TicketSalt(.*)=(.*)"(?<salt>.+\S)"/)
  # salt = salt.flatten.first

  salt.flatten.first
end

#reload_icinga_config(params) ⇒ Object

reload the icinga2-master using the api

Parameters:

  • params (Hash, #read)

Options Hash (params):

  • :request (String)
    • HTTP_X_API_USER

    • HTTP_X_API_PASSWORD



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/cert-service.rb', line 269

def reload_icinga_config(params)

  logger.info( 'restart icinga2 process')

  api_user     = params.dig(:request, 'HTTP_X_API_USER')
  api_password = params.dig(:request, 'HTTP_X_API_PASSWORD')

  return { status: 500, message: 'missing API Credentials - API_USER' } if( api_user.nil?)
  return { status: 500, message: 'missing API Credentials - API_PASSWORD' } if( api_password.nil? )

  password = read_api_credentials( api_user: api_user )

  return { status: 500, message: 'wrong API Credentials' } if( password.nil? || api_password != password )

  options = { user: api_user, password: api_password, verify_ssl: OpenSSL::SSL::VERIFY_NONE }
  headers = { 'Content-Type' => 'application/json', 'Accept' => 'application/json' }
  url     = format('https://%s:5665/v1/actions/restart-process', @icinga_master )

  rest_client = RestClient::Resource.new( URI.encode( url ), options )

  begin

    response = rest_client.post( {}.to_json, headers )

    response = response.body if(response.is_a?(RestClient::Response))
    response = JSON.parse(response) if(response.is_a?(String))

    logger.debug(JSON.pretty_generate(response))

  rescue RestClient::ExceptionWithResponse => e

    logger.error("Error: restart-process has failed: '#{e}'")
    logger.error(JSON.pretty_generate(params))

    return { status: 500, message: e }
  end

  { status: 200, message: 'service restarted' }
end