icinga-cert-service 0.18.4 → 0.20.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/icinga2-cert-service.rb +46 -2
- data/bin/installer.sh +38 -6
- data/lib/cert-service.rb +20 -3
- data/lib/cert-service/certificate_handler.rb +385 -92
- data/lib/cert-service/endpoint_handler.rb +11 -6
- data/lib/cert-service/version.rb +1 -1
- data/lib/logging.rb +3 -2
- metadata +17 -4
- data/bin/test.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 859374a9a0babbeac29da3e0bbaaf8728d1577f62da0084f96e9137bcab41937
|
4
|
+
data.tar.gz: 91077b678f06f00b26748ecbf32ae26dd9325fc72d37a9acfa2d4a445255f9cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba2d51aeb1f6f670e17d369910f145ea5c9b6546352bacfba4f91810e6cac836f3fcb3b10e9c0b80a653f8910298d195cc21099ab4dd3d17041f75088d6fd083
|
7
|
+
data.tar.gz: 472ecdd2c342c81d9d803210f16c946737d95f97cbf90de84354280203e9de6985c02197234b50802f68d5f1fa9ab2d0fb4590cbfc2b7bda024b61b7ff80543d
|
data/bin/icinga2-cert-service.rb
CHANGED
@@ -20,7 +20,9 @@ require_relative '../lib/logging'
|
|
20
20
|
# -----------------------------------------------------------------------------
|
21
21
|
|
22
22
|
module Sinatra
|
23
|
+
|
23
24
|
class CertServiceRest < Base
|
25
|
+
|
24
26
|
register Sinatra::BasicAuth
|
25
27
|
|
26
28
|
include Logging
|
@@ -36,7 +38,14 @@ module Sinatra
|
|
36
38
|
|
37
39
|
config_file = ENV.fetch('CONFIG_FILE' , '/etc/icinga2-cert-service.yaml')
|
38
40
|
|
41
|
+
#unless( File.exist?(config_file) )
|
42
|
+
# logger.debug( format('INFO: The configuration file \'%s\' is missing. I use the default parameter.', config_file ) )
|
43
|
+
#else
|
44
|
+
# logger.debug( format('INFO: Use the configuration file \'%s\'', config_file ) )
|
45
|
+
#end
|
46
|
+
|
39
47
|
configure do
|
48
|
+
|
40
49
|
set :environment, :production
|
41
50
|
|
42
51
|
# default configuration
|
@@ -169,6 +178,41 @@ module Sinatra
|
|
169
178
|
end
|
170
179
|
end
|
171
180
|
|
181
|
+
# create an PKI ticket
|
182
|
+
#
|
183
|
+
protect 'API' do
|
184
|
+
get '/v2/ticket/:host' do
|
185
|
+
result = ics.pki_ticket( host: params[:host], request: request.env )
|
186
|
+
|
187
|
+
# logger.debug(result)
|
188
|
+
|
189
|
+
result_status, result_message = result.values
|
190
|
+
|
191
|
+
status result_status
|
192
|
+
result_message + "\n"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# enable endpoint
|
197
|
+
#
|
198
|
+
protect 'API' do
|
199
|
+
get '/v2/enable_endpoint/:host' do
|
200
|
+
result = ics.enable_endpoint( host: params[:host], request: request.env )
|
201
|
+
|
202
|
+
# logger.debug(result)
|
203
|
+
|
204
|
+
result_status = result.dig(:status).to_i
|
205
|
+
|
206
|
+
status result_status
|
207
|
+
|
208
|
+
JSON.pretty_generate(result) + "\n"
|
209
|
+
|
210
|
+
# result_status, result_message = result.values
|
211
|
+
#
|
212
|
+
# status result_status
|
213
|
+
# result_message + "\n"
|
214
|
+
end
|
215
|
+
end
|
172
216
|
|
173
217
|
# download an certificate
|
174
218
|
#
|
@@ -176,7 +220,7 @@ module Sinatra
|
|
176
220
|
get '/v2/cert/:host' do
|
177
221
|
result = ics.check_certificate( host: params[:host], request: request.env )
|
178
222
|
|
179
|
-
|
223
|
+
# logger.debug(result)
|
180
224
|
|
181
225
|
result_status = result.dig(:status).to_i
|
182
226
|
|
@@ -238,7 +282,7 @@ module Sinatra
|
|
238
282
|
|
239
283
|
result = ics.download(file_name: params[:file_name], request: request.env)
|
240
284
|
|
241
|
-
|
285
|
+
# logger.debug(result)
|
242
286
|
|
243
287
|
result_status = result.dig(:status).to_i
|
244
288
|
|
data/bin/installer.sh
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
#!/bin/
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
set -e
|
2
4
|
|
3
5
|
DESTINATION_DIR="/usr/local/icinga2-cert-service"
|
4
6
|
SOURCE_DIR="/tmp/ruby-icinga-cert-service"
|
@@ -11,23 +13,53 @@ echo "install icinga2-cert-service .."
|
|
11
13
|
|
12
14
|
cd ${SOURCE_DIR}
|
13
15
|
|
16
|
+
echo "[i] update gems"
|
14
17
|
bundle update --quiet
|
15
|
-
gem uninstall --quiet io-console bundler
|
16
18
|
|
17
19
|
for i in lib bin templates assets
|
18
20
|
do
|
19
|
-
cp -
|
21
|
+
cp -av ${SOURCE_DIR}/${i} ${DESTINATION_DIR}/
|
20
22
|
done
|
21
23
|
|
22
|
-
if [[ -e
|
24
|
+
if [[ -e /.dockerenv ]]
|
23
25
|
then
|
24
|
-
|
26
|
+
echo "[i] docker environment ..."
|
27
|
+
|
28
|
+
gem uninstall --quiet io-console bundler 2> /dev/null
|
29
|
+
|
30
|
+
else
|
31
|
+
|
32
|
+
if [[ ! -e /.dockerenv ]] && [[ ! -e /etc/icinga2-cert-service.yaml ]]
|
33
|
+
then
|
34
|
+
echo "[i] install config"
|
35
|
+
cp -v config_example.yaml /etc/icinga2-cert-service.yaml
|
36
|
+
fi
|
37
|
+
|
38
|
+
|
39
|
+
if [[ $(command -v openrc-run | wc -l) -eq 1 ]]
|
40
|
+
then
|
41
|
+
echo "[i] install openrc init and configuration files"
|
42
|
+
|
43
|
+
cp ${SOURCE_DIR}/init-script/openrc/icinga2-cert-service /etc/init.d/
|
44
|
+
|
45
|
+
cat << EOF > /etc/conf.d/icinga2-cert-service
|
25
46
|
|
26
47
|
# Icinga2 cert service
|
27
48
|
CERT_SERVICE_BIN="/usr/local/icinga2-cert-service/bin/icinga2-cert-service.rb"
|
28
49
|
|
29
50
|
EOF
|
30
|
-
|
51
|
+
fi
|
52
|
+
|
53
|
+
if [[ $(command -v systemctl | wc -l) -eq 1 ]]
|
54
|
+
then
|
55
|
+
echo "[i] install system unit file"
|
56
|
+
|
57
|
+
cp ${SOURCE_DIR}/init-script/systemd/icinga-cert-service.service /etc/systemd/system/
|
58
|
+
|
59
|
+
systemctl daemon-reload
|
60
|
+
systemctl enable icinga-cert-service
|
61
|
+
systemctl start icinga-cert-service
|
62
|
+
fi
|
31
63
|
fi
|
32
64
|
|
33
65
|
export CERT_SERVICE=${DESTINATION_DIR}
|
data/lib/cert-service.rb
CHANGED
@@ -76,13 +76,13 @@ module IcingaCertService
|
|
76
76
|
@tmp_directory = '/tmp/icinga-pki'
|
77
77
|
|
78
78
|
version = IcingaCertService::VERSION
|
79
|
-
date = '
|
79
|
+
date = '2019-05-12'
|
80
80
|
detect_version
|
81
81
|
|
82
82
|
logger.info('-----------------------------------------------------------------')
|
83
83
|
logger.info(' certificate service for Icinga2')
|
84
84
|
logger.info(format(' Version %s (%s)', version, date))
|
85
|
-
logger.info(' Copyright 2017-
|
85
|
+
logger.info(' Copyright 2017-2020 Bodo Schulz')
|
86
86
|
logger.info(format(' Icinga2 base version %s', @icinga_version))
|
87
87
|
logger.info('-----------------------------------------------------------------')
|
88
88
|
logger.info('')
|
@@ -126,7 +126,8 @@ module IcingaCertService
|
|
126
126
|
@icinga_version = parts['v'].to_s.strip if(parts)
|
127
127
|
end
|
128
128
|
|
129
|
-
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH => e
|
129
|
+
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::EADDRNOTAVAIL => e
|
130
|
+
logger.debug( "#{e}" )
|
130
131
|
sleep( sleep_between_retries )
|
131
132
|
retry
|
132
133
|
rescue RestClient::ExceptionWithResponse => e
|
@@ -305,6 +306,22 @@ module IcingaCertService
|
|
305
306
|
{ status: 200, message: 'service restarted' }
|
306
307
|
end
|
307
308
|
|
309
|
+
def read_ticket_salt
|
310
|
+
|
311
|
+
file_name = '/etc/icinga2/constants.conf'
|
312
|
+
|
313
|
+
return { status: 500, message: format( 'icinga2 not successful configured! file %s missing', file_name ) } unless( File.exist?(file_name) )
|
314
|
+
|
315
|
+
file = File.open(file_name, 'r')
|
316
|
+
contents = file.read
|
317
|
+
|
318
|
+
salt = contents.scan(/const TicketSalt(.*)=(.*)"(?<salt>.+\S)"/)
|
319
|
+
# salt = salt.flatten.first
|
320
|
+
|
321
|
+
salt.flatten.first
|
322
|
+
end
|
323
|
+
|
324
|
+
|
308
325
|
# returns the hostname of itself
|
309
326
|
#
|
310
327
|
def icinga2_server_name
|
@@ -26,10 +26,12 @@ module IcingaCertService
|
|
26
26
|
#
|
27
27
|
def create_certificate( params )
|
28
28
|
|
29
|
-
host
|
30
|
-
api_user
|
31
|
-
api_password
|
32
|
-
|
29
|
+
host = params.dig(:host)
|
30
|
+
api_user = params.dig(:request, 'HTTP_X_API_USER')
|
31
|
+
api_password = params.dig(:request, 'HTTP_X_API_PASSWORD')
|
32
|
+
api_hostname = params.dig(:request, 'HTTP_X_API_HOSTNAME')
|
33
|
+
api_ticketsalt = params.dig(:request, 'HTTP_X_API_TICKETSALT')
|
34
|
+
remote_addr = params.dig(:request, 'REMOTE_ADDR')
|
33
35
|
|
34
36
|
return { status: 500, message: 'no hostname' } if( host.nil? )
|
35
37
|
return { status: 500, message: 'missing API Credentials - API_USER' } if( api_user.nil?)
|
@@ -41,64 +43,78 @@ module IcingaCertService
|
|
41
43
|
|
42
44
|
logger.info(format('got certificate request from %s', remote_addr))
|
43
45
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
46
|
+
server_ip, server_name = icinga_server_name
|
47
|
+
|
48
|
+
# if( @icinga_master.nil? )
|
49
|
+
# begin
|
50
|
+
# server_name = icinga2_server_name
|
51
|
+
# rescue => e
|
52
|
+
# logger.error(e)
|
53
|
+
# server_name = @icinga_master
|
54
|
+
# else
|
55
|
+
# server_ip = icinga2_server_ip
|
56
|
+
# end
|
57
|
+
# else
|
58
|
+
# server_name = @icinga_master
|
59
|
+
# begin
|
60
|
+
# server_ip = icinga2_server_ip(server_name)
|
61
|
+
# rescue => e
|
62
|
+
# logger.error(server_name)
|
63
|
+
# logger.error(e)
|
64
|
+
#
|
65
|
+
# server_ip = '127.0.0.1'
|
66
|
+
# end
|
67
|
+
# end
|
68
|
+
|
69
|
+
status, message = ensure_master_certificate_exists( server_name, host ).values
|
70
|
+
|
71
|
+
return { status: status, message: message } unless( status == 200 )
|
64
72
|
|
65
73
|
pki_base_directory = '/etc/icinga2/pki'
|
66
74
|
pki_base_directory = '/var/lib/icinga2/certs' if( @icinga_version != '2.7' )
|
67
75
|
|
68
|
-
return { status: 500, message: 'no PKI directory found. Please configure first the Icinga2 Master!' } if( pki_base_directory.nil? )
|
69
|
-
|
70
76
|
pki_master_key = format('%s/%s.key', pki_base_directory, server_name)
|
71
77
|
pki_master_csr = format('%s/%s.csr', pki_base_directory, server_name)
|
72
78
|
pki_master_crt = format('%s/%s.crt', pki_base_directory, server_name)
|
73
79
|
pki_master_ca = format('%s/ca.crt', pki_base_directory)
|
74
80
|
|
75
|
-
|
76
|
-
|
77
|
-
zone_base_directory = '/etc/icinga2/zone.d'
|
78
|
-
|
79
|
-
FileUtils.mkpath( format('%s/global-templates', zone_base_directory) )
|
80
|
-
FileUtils.mkpath( format('%s/%s', zone_base_directory, host) )
|
81
|
-
|
81
|
+
#pki_base_directory = '/etc/icinga2/pki'
|
82
|
+
#pki_base_directory = '/var/lib/icinga2/certs' if( @icinga_version != '2.7' )
|
82
83
|
#
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
84
|
+
#return { status: 500, message: 'no PKI directory found. Please configure first the Icinga2 Master!' } if( pki_base_directory.nil? )
|
85
|
+
#
|
86
|
+
#pki_master_key = format('%s/%s.key', pki_base_directory, server_name)
|
87
|
+
#pki_master_csr = format('%s/%s.csr', pki_base_directory, server_name)
|
88
|
+
#pki_master_crt = format('%s/%s.crt', pki_base_directory, server_name)
|
89
|
+
#pki_master_ca = format('%s/ca.crt', pki_base_directory)
|
90
|
+
#
|
91
|
+
#return { status: 500, message: 'no PKI directory found. Please configure first the Icinga2 Master!' } unless( File.exist?(pki_base_directory) )
|
92
|
+
#
|
93
|
+
#zone_base_directory = '/etc/icinga2/zone.d'
|
94
|
+
#
|
95
|
+
#FileUtils.mkpath( format('%s/global-templates', zone_base_directory) )
|
96
|
+
#FileUtils.mkpath( format('%s/%s', zone_base_directory, host) )
|
97
|
+
#
|
98
|
+
##
|
99
|
+
#unless File.exist?(format('%s/global-templates/services.conf', zone_base_directory) )
|
100
|
+
#
|
101
|
+
# if( File.exist?('/etc/icinga2/conf.d/services.conf') )
|
102
|
+
# FileUtils.mv('/etc/icinga2/conf.d/services.conf', format('%s/global-templates/services.conf', zone_base_directory))
|
103
|
+
# else
|
104
|
+
# logger.error('missing services.conf under /etc/icinga2/conf.d')
|
105
|
+
# end
|
106
|
+
#end
|
107
|
+
#
|
108
|
+
#logger.debug(format('search PKI files for the Master \'%s\'', server_name))
|
109
|
+
#
|
110
|
+
#if( !File.exist?(pki_master_key) || !File.exist?(pki_master_csr) || !File.exist?(pki_master_crt) )
|
111
|
+
# logger.error('missing file')
|
112
|
+
# logger.debug(pki_master_key)
|
113
|
+
# logger.debug(pki_master_csr)
|
114
|
+
# logger.debug(pki_master_crt)
|
115
|
+
#
|
116
|
+
# return { status: 500, message: format('missing PKI for Icinga2 Master \'%s\'', server_name) }
|
117
|
+
#end
|
102
118
|
|
103
119
|
tmp_host_directory = format('%s/%s', @tmp_directory, host)
|
104
120
|
# uid = File.stat('/etc/icinga2/conf.d').uid
|
@@ -115,7 +131,7 @@ module IcingaCertService
|
|
115
131
|
pki_satellite_key = format('%s/%s.key', tmp_host_directory, host)
|
116
132
|
pki_satellite_csr = format('%s/%s.csr', tmp_host_directory, host)
|
117
133
|
pki_satellite_crt = format('%s/%s.crt', tmp_host_directory, host)
|
118
|
-
pki_ticket
|
134
|
+
pki_ticket = '%PKI_TICKET%'
|
119
135
|
|
120
136
|
commands = []
|
121
137
|
|
@@ -221,6 +237,133 @@ module IcingaCertService
|
|
221
237
|
end
|
222
238
|
|
223
239
|
|
240
|
+
def pki_ticket( params )
|
241
|
+
|
242
|
+
host = params.dig(:host)
|
243
|
+
api_user = params.dig(:request, 'HTTP_X_API_USER')
|
244
|
+
api_password = params.dig(:request, 'HTTP_X_API_PASSWORD')
|
245
|
+
api_hostname = params.dig(:request, 'HTTP_X_API_HOSTNAME')
|
246
|
+
api_ticketsalt = params.dig(:request, 'HTTP_X_API_TICKETSALT')
|
247
|
+
remote_addr = params.dig(:request, 'REMOTE_ADDR')
|
248
|
+
real_ip = params.dig(:request, 'HTTP_X_REAL_IP')
|
249
|
+
forwarded_for = params.dig(:request, 'HTTP_X_FORWARDED_FOR')
|
250
|
+
|
251
|
+
logger.error('no hostname') if( host.nil? )
|
252
|
+
logger.error('missing API Credentials - API_USER') if( api_user.nil? )
|
253
|
+
logger.error('missing API Credentials - API_PASSWORD') if( api_password.nil? )
|
254
|
+
|
255
|
+
return { status: 401, message: 'no hostname' } if( host.nil? )
|
256
|
+
return { status: 401, message: 'missing API Credentials - API_USER' } if( api_user.nil?)
|
257
|
+
return { status: 401, message: 'missing API Credentials - API_PASSWORD' } if( api_password.nil? )
|
258
|
+
|
259
|
+
password = read_api_credentials( api_user: api_user )
|
260
|
+
salt_passend = ( read_ticket_salt == api_ticketsalt )
|
261
|
+
|
262
|
+
logger.error('wrong API Credentials') if( password.nil? || api_password != password )
|
263
|
+
logger.error('wrong Icinga2 Version (the master required => 2.8)') if( @icinga_version == '2.7' )
|
264
|
+
|
265
|
+
return { status: 401, message: 'wrong API Credentials' } if( password.nil? || api_password != password )
|
266
|
+
|
267
|
+
auth_params = { remote_addr: remote_addr, real_ip: real_ip, forwarded_for: forwarded_for, host: host, salt_passend: salt_passend }
|
268
|
+
|
269
|
+
# logger.debug( " - auth_params : #{auth_params}" )
|
270
|
+
# logger.debug( " - api ticketsalt : #{api_ticketsalt}" )
|
271
|
+
# logger.debug( " - ticketsalt passed: #{salt_passend}" )
|
272
|
+
|
273
|
+
authorized, remote_short, host_short = is_remote_clients_authorized( auth_params ).values
|
274
|
+
|
275
|
+
if( authorized == false )
|
276
|
+
logger.error(format('This client (%s/%s) cannot get an pki ticket for %s', remote_addr, real_ip, host ) ) unless( host_short == remote_short )
|
277
|
+
|
278
|
+
return { status: 409, message: format('This client cannot get an pki ticket for %s', host ) } unless( host_short == remote_short )
|
279
|
+
end
|
280
|
+
|
281
|
+
logger.info(format('got ticket request from %s (%s)', host, real_ip))
|
282
|
+
|
283
|
+
server_ip, server_name = icinga_server_name
|
284
|
+
|
285
|
+
status, message = ensure_master_certificate_exists( server_name, host ).values
|
286
|
+
|
287
|
+
return { status: status, message: message } unless( status == 200 )
|
288
|
+
|
289
|
+
command = format('icinga2 pki ticket --cn %s', host)
|
290
|
+
|
291
|
+
result = exec_command(cmd: command)
|
292
|
+
|
293
|
+
logger.debug( "#{result} (#{result.class})")
|
294
|
+
|
295
|
+
exec_code, exec_message = result.values
|
296
|
+
|
297
|
+
logger.debug( format( ' - [%s] %s', exec_code, exec_message.strip ) )
|
298
|
+
|
299
|
+
if( exec_code != true )
|
300
|
+
logger.error(exec_message)
|
301
|
+
logger.error(format(' command \'%s\'', command))
|
302
|
+
logger.error(format(' returned with exit-code \'%s\'', exec_code))
|
303
|
+
|
304
|
+
return { status: 500, message: format('Internal Error: \'%s\' - \'cmd %s\'', exec_message, command) }
|
305
|
+
end
|
306
|
+
|
307
|
+
{ status: 200, message: exec_message.strip }
|
308
|
+
end
|
309
|
+
|
310
|
+
|
311
|
+
def enable_endpoint( params )
|
312
|
+
|
313
|
+
host = params.dig(:host)
|
314
|
+
api_user = params.dig(:request, 'HTTP_X_API_USER')
|
315
|
+
api_password = params.dig(:request, 'HTTP_X_API_PASSWORD')
|
316
|
+
api_hostname = params.dig(:request, 'HTTP_X_API_HOSTNAME')
|
317
|
+
api_ticketsalt = params.dig(:request, 'HTTP_X_API_TICKETSALT')
|
318
|
+
remote_addr = params.dig(:request, 'REMOTE_ADDR')
|
319
|
+
real_ip = params.dig(:request, 'HTTP_X_REAL_IP')
|
320
|
+
forwarded_for = params.dig(:request, 'HTTP_X_FORWARDED_FOR')
|
321
|
+
|
322
|
+
logger.error('no hostname') if( host.nil? )
|
323
|
+
logger.error('missing API Credentials - API_USER') if( api_user.nil? )
|
324
|
+
logger.error('missing API Credentials - API_PASSWORD') if( api_password.nil? )
|
325
|
+
|
326
|
+
return { status: 401, message: 'no hostname' } if( host.nil? )
|
327
|
+
return { status: 401, message: 'missing API Credentials - API_USER' } if( api_user.nil?)
|
328
|
+
return { status: 401, message: 'missing API Credentials - API_PASSWORD' } if( api_password.nil? )
|
329
|
+
|
330
|
+
password = read_api_credentials( api_user: api_user )
|
331
|
+
salt_passend = ( read_ticket_salt == api_ticketsalt )
|
332
|
+
|
333
|
+
logger.error('wrong API Credentials') if( password.nil? || api_password != password )
|
334
|
+
logger.error('wrong Icinga2 Version (the master required => 2.8)') if( @icinga_version == '2.7' )
|
335
|
+
|
336
|
+
return { status: 401, message: 'wrong API Credentials' } if( password.nil? || api_password != password )
|
337
|
+
|
338
|
+
auth_params = { remote_addr: remote_addr, real_ip: real_ip, forwarded_for: forwarded_for, host: host, salt_passend: salt_passend }
|
339
|
+
|
340
|
+
authorized, remote_short, host_short = is_remote_clients_authorized( auth_params ).values
|
341
|
+
|
342
|
+
if( authorized == false )
|
343
|
+
logger.error(format('This client (%s/%s) cannot enable an endpoint for %s', remote_addr, real_ip, host ) ) unless( host_short == remote_short )
|
344
|
+
|
345
|
+
return { status: 409, message: format('This client cannot enable an endpoint for %s', host ) } unless( host_short == remote_short )
|
346
|
+
end
|
347
|
+
|
348
|
+
# add params to create the endpoint not in zones.d
|
349
|
+
#
|
350
|
+
params[:satellite] = false
|
351
|
+
|
352
|
+
# add API User for this Endpoint
|
353
|
+
#
|
354
|
+
# add_api_user(params)
|
355
|
+
|
356
|
+
# add Endpoint (and API User)
|
357
|
+
# and create a backup of the generated files
|
358
|
+
#
|
359
|
+
result = add_endpoint(host: host, satellite: false)
|
360
|
+
|
361
|
+
logger.debug( result )
|
362
|
+
|
363
|
+
result
|
364
|
+
end
|
365
|
+
|
366
|
+
|
224
367
|
# check the certificate Data
|
225
368
|
#
|
226
369
|
# @param [Hash, #read] params
|
@@ -241,11 +384,13 @@ module IcingaCertService
|
|
241
384
|
#
|
242
385
|
def check_certificate( params )
|
243
386
|
|
244
|
-
host
|
245
|
-
checksum
|
246
|
-
api_user
|
247
|
-
api_password
|
248
|
-
|
387
|
+
host = params.dig(:host)
|
388
|
+
checksum = params.dig(:request, 'HTTP_X_CHECKSUM')
|
389
|
+
api_user = params.dig(:request, 'HTTP_X_API_USER')
|
390
|
+
api_password = params.dig(:request, 'HTTP_X_API_PASSWORD')
|
391
|
+
api_hostname = params.dig(:request, 'HTTP_X_API_HOSTNAME')
|
392
|
+
api_ticketsalt = params.dig(:request, 'HTTP_X_API_TICKETSALT')
|
393
|
+
remote_addr = params.dig(:request, 'REMOTE_ADDR')
|
249
394
|
|
250
395
|
return { status: 500, message: 'no valid data to get the certificate' } if( host.nil? || checksum.nil? )
|
251
396
|
|
@@ -335,12 +480,14 @@ module IcingaCertService
|
|
335
480
|
#
|
336
481
|
def sign_certificate( params )
|
337
482
|
|
338
|
-
host
|
339
|
-
api_user
|
340
|
-
api_password
|
341
|
-
|
342
|
-
|
343
|
-
|
483
|
+
host = params.dig(:host)
|
484
|
+
api_user = params.dig(:request, 'HTTP_X_API_USER')
|
485
|
+
api_password = params.dig(:request, 'HTTP_X_API_PASSWORD')
|
486
|
+
api_hostname = params.dig(:request, 'HTTP_X_API_HOSTNAME')
|
487
|
+
api_ticketsalt = params.dig(:request, 'HTTP_X_API_TICKETSALT')
|
488
|
+
remote_addr = params.dig(:request, 'REMOTE_ADDR')
|
489
|
+
real_ip = params.dig(:request, 'HTTP_X_REAL_IP')
|
490
|
+
forwarded_for = params.dig(:request, 'HTTP_X_FORWARDED_FOR')
|
344
491
|
|
345
492
|
# logger.debug(params)
|
346
493
|
|
@@ -352,7 +499,8 @@ module IcingaCertService
|
|
352
499
|
return { status: 401, message: 'missing API Credentials - API_USER' } if( api_user.nil?)
|
353
500
|
return { status: 401, message: 'missing API Credentials - API_PASSWORD' } if( api_password.nil? )
|
354
501
|
|
355
|
-
password
|
502
|
+
password = read_api_credentials( api_user: api_user )
|
503
|
+
salt_passend = ( read_ticket_salt == api_ticketsalt )
|
356
504
|
|
357
505
|
logger.error('wrong API Credentials') if( password.nil? || api_password != password )
|
358
506
|
logger.error('wrong Icinga2 Version (the master required => 2.8)') if( @icinga_version == '2.7' )
|
@@ -360,40 +508,51 @@ module IcingaCertService
|
|
360
508
|
return { status: 401, message: 'wrong API Credentials' } if( password.nil? || api_password != password )
|
361
509
|
return { status: 401, message: 'wrong Icinga2 Version (the master required => 2.8)' } if( @icinga_version == '2.7' )
|
362
510
|
|
363
|
-
|
364
|
-
logger.info('we running behind a proxy')
|
511
|
+
auth_params = { remote_addr: remote_addr, real_ip: real_ip, forwarded_for: forwarded_for, host: host, salt_passend: salt_passend }
|
365
512
|
|
366
|
-
|
367
|
-
logger.debug("real ip #{real_ip}")
|
368
|
-
logger.debug("forwarded for #{forwarded_for}")
|
513
|
+
authorized, remote_short, host_short = is_remote_clients_authorized( auth_params ).values
|
369
514
|
|
370
|
-
|
371
|
-
end
|
372
|
-
|
373
|
-
unless( remote_addr.nil? )
|
374
|
-
host_short = host.split('.')
|
375
|
-
host_short = if( host_short.count > 0 )
|
376
|
-
host_short.first
|
377
|
-
else
|
378
|
-
host
|
379
|
-
end
|
380
|
-
|
381
|
-
remote_fqdn = Resolv.getnames(remote_addr).sort.last
|
382
|
-
remote_short = remote_fqdn.split('.')
|
383
|
-
remote_short = if( remote_short.count > 0 )
|
384
|
-
remote_short.first
|
385
|
-
else
|
386
|
-
remote_fqdn
|
387
|
-
end
|
515
|
+
if( authorized == false )
|
388
516
|
|
389
|
-
logger.
|
390
|
-
logger.debug( "remote_short #{remote_short}" )
|
391
|
-
|
392
|
-
logger.error(format('This client (%s) cannot sign the certificate for %s', remote_fqdn, host ) ) unless( host_short == remote_short )
|
517
|
+
logger.error(format('This client (%s) cannot sign the certificate for %s', remote_addr, host ) ) unless( host_short == remote_short )
|
393
518
|
|
394
519
|
return { status: 409, message: format('This client cannot sign the certificate for %s', host ) } unless( host_short == remote_short )
|
395
520
|
end
|
396
521
|
|
522
|
+
# unless(remote_addr.nil? && real_ip.nil?)
|
523
|
+
# logger.info('we running behind a proxy')
|
524
|
+
#
|
525
|
+
# logger.debug("remote addr #{remote_addr}")
|
526
|
+
# logger.debug("real ip #{real_ip}")
|
527
|
+
# logger.debug("forwarded for #{forwarded_for}")
|
528
|
+
#
|
529
|
+
# remote_addr = forwarded_for
|
530
|
+
# end
|
531
|
+
#
|
532
|
+
# unless( remote_addr.nil? )
|
533
|
+
# host_short = host.split('.')
|
534
|
+
# host_short = if( host_short.count > 0 )
|
535
|
+
# host_short.first
|
536
|
+
# else
|
537
|
+
# host
|
538
|
+
# end
|
539
|
+
#
|
540
|
+
# remote_fqdn = Resolv.getnames(remote_addr).sort.last
|
541
|
+
# remote_short = remote_fqdn.split('.')
|
542
|
+
# remote_short = if( remote_short.count > 0 )
|
543
|
+
# remote_short.first
|
544
|
+
# else
|
545
|
+
# remote_fqdn
|
546
|
+
# end
|
547
|
+
#
|
548
|
+
# logger.debug( "host_short #{host_short}" )
|
549
|
+
# logger.debug( "remote_short #{remote_short}" )
|
550
|
+
#
|
551
|
+
# logger.error(format('This client (%s) cannot sign the certificate for %s', remote_fqdn, host ) ) unless( host_short == remote_short )
|
552
|
+
#
|
553
|
+
# return { status: 409, message: format('This client cannot sign the certificate for %s', host ) } unless( host_short == remote_short )
|
554
|
+
# end
|
555
|
+
|
397
556
|
logger.info( format('sign certificate for %s', host) )
|
398
557
|
|
399
558
|
# /etc/icinga2 # icinga2 ca list | grep icinga2-satellite-1.matrix.lan | sort -k2
|
@@ -462,5 +621,139 @@ module IcingaCertService
|
|
462
621
|
|
463
622
|
{ status: 204 }
|
464
623
|
end
|
624
|
+
|
625
|
+
|
626
|
+
private
|
627
|
+
def ensure_master_certificate_exists( server_name, host )
|
628
|
+
|
629
|
+
|
630
|
+
pki_base_directory = '/etc/icinga2/pki'
|
631
|
+
pki_base_directory = '/var/lib/icinga2/certs' if( @icinga_version != '2.7' )
|
632
|
+
|
633
|
+
return { status: 500, message: 'no PKI directory found. Please configure first the Icinga2 Master!' } if( pki_base_directory.nil? )
|
634
|
+
|
635
|
+
pki_master_key = format('%s/%s.key', pki_base_directory, server_name)
|
636
|
+
pki_master_csr = format('%s/%s.csr', pki_base_directory, server_name)
|
637
|
+
pki_master_crt = format('%s/%s.crt', pki_base_directory, server_name)
|
638
|
+
#pki_master_ca = format('%s/ca.crt', pki_base_directory)
|
639
|
+
|
640
|
+
return { status: 500, message: 'no PKI directory found. Please configure first the Icinga2 Master!' } unless( File.exist?(pki_base_directory) )
|
641
|
+
|
642
|
+
zone_base_directory = '/etc/icinga2/zone.d'
|
643
|
+
|
644
|
+
FileUtils.mkpath( format('%s/global-templates', zone_base_directory) )
|
645
|
+
FileUtils.mkpath( format('%s/%s', zone_base_directory, host) )
|
646
|
+
|
647
|
+
#
|
648
|
+
unless File.exist?(format('%s/global-templates/services.conf', zone_base_directory) )
|
649
|
+
|
650
|
+
if( File.exist?('/etc/icinga2/conf.d/services.conf') )
|
651
|
+
FileUtils.mv('/etc/icinga2/conf.d/services.conf', format('%s/global-templates/services.conf', zone_base_directory))
|
652
|
+
else
|
653
|
+
logger.error('missing services.conf under /etc/icinga2/conf.d')
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
logger.debug(format('search PKI files for the Master \'%s\'', server_name))
|
658
|
+
|
659
|
+
if( !File.exist?(pki_master_key) || !File.exist?(pki_master_csr) || !File.exist?(pki_master_crt) )
|
660
|
+
logger.error('missing file')
|
661
|
+
logger.error(format(' - %s', pki_master_key)) if( !File.exist?(pki_master_key))
|
662
|
+
logger.error(format(' - %s', pki_master_csr)) if( !File.exist?(pki_master_csr))
|
663
|
+
logger.error(format(' - %s', pki_master_crt)) if( !File.exist?(pki_master_crt))
|
664
|
+
|
665
|
+
return { status: 500, message: format('missing PKI for Icinga2 Master \'%s\'', server_name) }
|
666
|
+
end
|
667
|
+
|
668
|
+
return { status: 200 }
|
669
|
+
|
670
|
+
end
|
671
|
+
|
672
|
+
|
673
|
+
def icinga_server_name
|
674
|
+
|
675
|
+
if( @icinga_master.nil? )
|
676
|
+
begin
|
677
|
+
server_name = icinga2_server_name
|
678
|
+
rescue => e
|
679
|
+
logger.error(e)
|
680
|
+
server_name = @icinga_master
|
681
|
+
else
|
682
|
+
server_ip = icinga2_server_ip
|
683
|
+
end
|
684
|
+
else
|
685
|
+
server_name = @icinga_master
|
686
|
+
begin
|
687
|
+
server_ip = icinga2_server_ip(server_name)
|
688
|
+
rescue => e
|
689
|
+
logger.error(server_name)
|
690
|
+
logger.error(e)
|
691
|
+
|
692
|
+
server_ip = '127.0.0.1'
|
693
|
+
end
|
694
|
+
end
|
695
|
+
|
696
|
+
[server_ip, server_name]
|
697
|
+
|
698
|
+
end
|
699
|
+
|
700
|
+
|
701
|
+
def is_remote_clients_authorized( params )
|
702
|
+
|
703
|
+
remote_addr = params.dig(:remote_addr)
|
704
|
+
remote_fqdn = nil
|
705
|
+
remote_short = nil
|
706
|
+
real_ip = params.dig(:real_ip)
|
707
|
+
forwarded_for = params.dig(:forwarded_for)
|
708
|
+
host = params.dig(:host)
|
709
|
+
salt_passend = params.dig(:salt_passend)
|
710
|
+
|
711
|
+
logger.debug("params #{params}")
|
712
|
+
|
713
|
+
result = true
|
714
|
+
|
715
|
+
unless(remote_addr.nil? && real_ip.nil?)
|
716
|
+
logger.debug('we running behind a proxy')
|
717
|
+
|
718
|
+
logger.debug(" remote addr #{remote_addr}")
|
719
|
+
logger.debug(" real ip #{real_ip}")
|
720
|
+
logger.debug(" forwarded for #{forwarded_for}")
|
721
|
+
|
722
|
+
remote_addr = forwarded_for
|
723
|
+
end
|
724
|
+
|
725
|
+
host_short = host.split('.')
|
726
|
+
host_short = if( host_short.count > 0 )
|
727
|
+
host_short.first
|
728
|
+
else
|
729
|
+
host
|
730
|
+
end
|
731
|
+
|
732
|
+
logger.debug( " host_fqdn #{host}" )
|
733
|
+
logger.debug( " host_short #{host_short}" )
|
734
|
+
|
735
|
+
if( salt_passend == false )
|
736
|
+
|
737
|
+
unless( remote_addr.nil? )
|
738
|
+
remote_fqdn = Resolv.getnames(remote_addr)
|
739
|
+
remote_fqdn = remote_fqdn.sort.last
|
740
|
+
remote_short = remote_fqdn.split('.')
|
741
|
+
remote_short = if( remote_short.count > 0 )
|
742
|
+
remote_short.first
|
743
|
+
else
|
744
|
+
remote_fqdn
|
745
|
+
end
|
746
|
+
|
747
|
+
logger.debug( " remote_fqdn #{remote_fqdn}" )
|
748
|
+
logger.debug( " remote_short #{remote_short}" )
|
749
|
+
|
750
|
+
result = false
|
751
|
+
end
|
752
|
+
end
|
753
|
+
|
754
|
+
{ result: result, remote_short: remote_short, host_short: host_short }
|
755
|
+
|
756
|
+
end
|
757
|
+
|
465
758
|
end
|
466
759
|
end
|
@@ -21,8 +21,11 @@ module IcingaCertService
|
|
21
21
|
#
|
22
22
|
def add_endpoint(params)
|
23
23
|
|
24
|
-
|
25
|
-
|
24
|
+
logger.debug("add_endpoint(#{params})")
|
25
|
+
|
26
|
+
host = validate( params, required: true , var: 'host' , type: String )
|
27
|
+
satellite = validate( params, required: false, var: 'satellite', type: Boolean ) || false
|
28
|
+
file_name = '/etc/icinga2/zones.conf'
|
26
29
|
|
27
30
|
return { status: 500, message: 'no hostname to create an endpoint' } if host.nil?
|
28
31
|
|
@@ -36,10 +39,8 @@ module IcingaCertService
|
|
36
39
|
|
37
40
|
# logger.debug( ret )
|
38
41
|
|
39
|
-
|
40
|
-
|
41
|
-
else
|
42
|
-
zone_directory = format('/etc/icinga2/zones.d/%s', host)
|
42
|
+
unless( satellite )
|
43
|
+
zone_directory = format('/etc/icinga2/satellites.d/%s', host)
|
43
44
|
file_name = format('%s/%s.conf', zone_directory, host)
|
44
45
|
|
45
46
|
begin
|
@@ -51,6 +52,8 @@ module IcingaCertService
|
|
51
52
|
|
52
53
|
if( File.exist?(file_name) )
|
53
54
|
|
55
|
+
logger.debug( format('the zone file (%s) for the endpoint %s exists', file_name, host) )
|
56
|
+
|
54
57
|
file = File.open(file_name, 'r')
|
55
58
|
contents = file.read
|
56
59
|
|
@@ -67,6 +70,8 @@ module IcingaCertService
|
|
67
70
|
|
68
71
|
scan_endpoint = result.scan(/object Endpoint(.*)"(?<endpoint>.+\S)"/).flatten
|
69
72
|
|
73
|
+
#logger.debug( "#{scan_endpoint} (#{scan_endpoint.class})" )
|
74
|
+
|
70
75
|
return { status: 200, message: format('the configuration for the endpoint %s already exists', host) } if( scan_endpoint.include?(host) == true )
|
71
76
|
end
|
72
77
|
|
data/lib/cert-service/version.rb
CHANGED
data/lib/logging.rb
CHANGED
@@ -27,10 +27,11 @@ module Logging
|
|
27
27
|
def configure_logger_for( classname )
|
28
28
|
|
29
29
|
log_level = ENV.fetch('LOG_LEVEL', 'DEBUG' )
|
30
|
-
|
30
|
+
|
31
|
+
level = log_level.upcase.dup
|
31
32
|
|
32
33
|
# DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN
|
33
|
-
log_level = case level
|
34
|
+
log_level = case level
|
34
35
|
when 'DEBUG'
|
35
36
|
Logger::DEBUG # Low-level information for developers.
|
36
37
|
when 'INFO'
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: icinga-cert-service
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.20.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bodo Schulz
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-03-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: etc
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.1'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: openssl
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -203,7 +217,6 @@ files:
|
|
203
217
|
- README.md
|
204
218
|
- bin/icinga2-cert-service.rb
|
205
219
|
- bin/installer.sh
|
206
|
-
- bin/test.rb
|
207
220
|
- lib/cert-service.rb
|
208
221
|
- lib/cert-service/backup.rb
|
209
222
|
- lib/cert-service/certificate_handler.rb
|
@@ -239,7 +252,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
239
252
|
version: '0'
|
240
253
|
requirements: []
|
241
254
|
rubyforge_project:
|
242
|
-
rubygems_version: 2.7.
|
255
|
+
rubygems_version: 2.7.10
|
243
256
|
signing_key:
|
244
257
|
specification_version: 4
|
245
258
|
summary: Icinga Certificate Service
|
data/bin/test.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
#
|
3
|
-
# 05.10.2016 - Bodo Schulz
|
4
|
-
#
|
5
|
-
#
|
6
|
-
# v2.1.0
|
7
|
-
|
8
|
-
# -----------------------------------------------------------------------------
|
9
|
-
|
10
|
-
require 'ruby_dig' if RUBY_VERSION < '2.3'
|
11
|
-
|
12
|
-
require 'sinatra/base'
|
13
|
-
require 'sinatra/basic_auth'
|
14
|
-
require 'json'
|
15
|
-
require 'yaml'
|
16
|
-
|
17
|
-
require_relative '../lib/cert-service'
|
18
|
-
require_relative '../lib/logging'
|
19
|
-
|
20
|
-
# -----------------------------------------------------------------------------
|
21
|
-
|
22
|
-
config = {
|
23
|
-
icinga_master: 'localhost'
|
24
|
-
}
|
25
|
-
|
26
|
-
ics = IcingaCertService::Client.new(config)
|
27
|
-
|
28
|
-
# -----------------------------------------------------------------------------
|