icinga-cert-service 0.18.4 → 0.20.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.
- 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
|
-
# -----------------------------------------------------------------------------
|