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,344 @@
1
+ icinga-cert-service
2
+ ===================
3
+
4
+ The Icinga-Cert-Service is a small service for creating, downloading or signing an Icinga2 certificate.
5
+ The service can be used to connect Icinga2 satellites or agents dynamically to an Icinga2 master.
6
+
7
+ The Cert service is implemented in ruby and offers a simple REST API.
8
+
9
+ # Status
10
+ [![Build Status](https://travis-ci.org/bodsch/ruby-icinga-cert-service.svg)][travis]
11
+ [![Dependency Status](https://gemnasium.com/badges/github.com/bodsch/ruby-icinga-cert-service.svg)][gemnasium]
12
+
13
+ [travis]: https://travis-ci.org/bodsch/ruby-icinga-cert-service
14
+ [gemnasium]: https://gemnasium.com/github.com/bodsch/ruby-icinga-cert-service
15
+
16
+
17
+ # Start
18
+
19
+ To start them run `ruby bin/rest-service.rb`
20
+
21
+ The following environment variables can be set:
22
+
23
+ - `ICINGA2_MASTER` (default: `nil`)
24
+ - `ICINGA2_API_PORT` (default: `5665`)
25
+ - `ICINGA2_API_USER` (default: `root`)
26
+ - `ICINGA2_API_PASSWORD` (default: `icinga`)
27
+ - `REST_SERVICE_PORT` (default: `8080`)
28
+ - `REST_SERVICE_BIND` (default: `0.0.0.0`)
29
+ - `BASIC_AUTH_USER` (default: `admin`)
30
+ - `BASIC_AUTH_PASS` (default: `admin`)
31
+
32
+ The REST-service uses an basic-authentication for the first security step.
33
+ The second Step is an configured API user into the Icinga2-Master.
34
+ The API user credentials must be set as HTTP-Header vars (see the examples below).
35
+
36
+ To overwrite the default configuration for the REST-Service, put a `icinga2-cert-service.yaml` into `/etc` :
37
+
38
+ ```yaml
39
+ ---
40
+ icinga:
41
+ server: master-server
42
+ api:
43
+ port: 5665
44
+ user: root
45
+ password: icinga
46
+
47
+ rest-service:
48
+ port: 8080
49
+ bind: 192.168.10.10
50
+
51
+ basic-auth:
52
+ user: ba-user
53
+ password: v2rys3cr3t
54
+ ```
55
+
56
+
57
+ # Who to used it
58
+
59
+ ## With Icinga2 Version 2.8, we can use the new PKI-Proxy Mode
60
+
61
+ You can use `expect` on a *satellite* or *agent* to create an certificate request with the *icinga2 node wizard*.
62
+ (A complete `expect` example can be found below)
63
+
64
+ ```bash
65
+ expect /init/node-wizard.expect
66
+ ```
67
+
68
+ After this, you can use the *cert-service* to sign this request:
69
+
70
+ ```bash
71
+ curl \
72
+ --user ${CERTIFICATE_SERVICE_BA_USER}:${CERTIFICATE_SERVICE_BA_PASSWORD} \
73
+ --silent \
74
+ --request GET \
75
+ --header "X-API-USER: ${CERTIFICATE_SERVICE_API_USER}" \
76
+ --header "X-API-PASSWORD: ${CERTIFICATE_SERVICE_API_PASSWORD}" \
77
+ --write-out "%{http_code}\n" \
78
+ --output /tmp/sign_${HOSTNAME}.json \
79
+ http://${CERTIFICATE_SERVICE_SERVER}:${CERTIFICATE_SERVICE_PORT}/v2/sign/${HOSTNAME}
80
+ ```
81
+
82
+ ## Otherwise, the pre 2.8 Mode works well
83
+
84
+ To create a certificate:
85
+
86
+ ```bash
87
+ curl \
88
+ --request GET \
89
+ --user ${CERTIFICATE_SERVICE_BA_USER}:${CERTIFICATE_SERVICE_BA_PASSWORD} \
90
+ --silent \
91
+ --header "X-API-USER: ${CERTIFICATE_SERVICE_API_USER}" \
92
+ --header "X-API-KEY: ${CERTIFICATE_SERVICE_API_PASSWORD}" \
93
+ --output /tmp/request_${HOSTNAME}.json \
94
+ http://${CERTIFICATE_SERVICE_SERVER}:${CERTIFICATE_SERVICE_PORT}/v2/request/${HOSTNAME}
95
+ ```
96
+
97
+ this creates an output file, that we use to download the certificate.
98
+
99
+ ## Download the created certificate:
100
+
101
+ ```bash
102
+ checksum=$(jq --raw-output .checksum /tmp/request_${HOSTNAME}.json)
103
+ master_name=$(jq --raw-output .master_name /tmp/request_${HOSTNAME}.json)
104
+
105
+ curl \
106
+ --request GET \
107
+ --user ${CERTIFICATE_SERVICE_BA_USER}:${CERTIFICATE_SERVICE_BA_PASSWORD} \
108
+ --silent \
109
+ --header "X-API-USER: ${CERTIFICATE_SERVICE_API_USER}" \
110
+ --header "X-API-KEY: ${CERTIFICATE_SERVICE_API_PASSWORD}" \
111
+ --header "X-CHECKSUM: ${checksum}" \
112
+ --output ${WORK_DIR}/pki/${HOSTNAME}/${HOSTNAME}.tgz \
113
+ http://${CERTIFICATE_SERVICE_SERVER}:${CERTIFICATE_SERVICE_PORT}/v2/cert/${HOSTNAME}
114
+ ```
115
+
116
+ ## Create the Satellite `Endpoint`
117
+
118
+ ```bash
119
+ cat << EOF > /etc/icinga2/zones.conf
120
+
121
+ /* the following line specifies that the client connects to the master and not vice versa */
122
+ object Endpoint "${master_name}" { host = "${ICINGA_MASTER}"; port = "5665" }
123
+ object Zone "master" { endpoints = [ "${master_name}" ] }
124
+
125
+ object Endpoint NodeName {}
126
+ object Zone ZoneName { endpoints = [ NodeName ] ; parent = "master" }
127
+
128
+ object Zone "global-templates" { global = true }
129
+ object Zone "director-global" { global = true }
130
+
131
+ EOF
132
+ ```
133
+
134
+ ## NOTE
135
+ The generated certificate has an timeout from 10 minutes between beginning of creation and download.
136
+
137
+
138
+ # API
139
+
140
+ following API Calls are implemented:
141
+
142
+ ## Health Check
143
+
144
+ The Health Check is important to determine whether the certificate service has started.
145
+
146
+ ```bash
147
+ curl \
148
+ --request GET \
149
+ --silent \
150
+ http://${CERTIFICATE_SERVICE_SERVER}:${CERTIFICATE_SERVICE_PORT}/v2/health-check
151
+ ```
152
+
153
+ The health check returns only a string with `healthy` as content.
154
+
155
+ ## Icinga Version
156
+
157
+ Returns the Icinga Version
158
+
159
+ ```bash
160
+ curl \
161
+ --request GET \
162
+ --silent \
163
+ http://${CERTIFICATE_SERVICE_SERVER}:${CERTIFICATE_SERVICE_PORT}/v2/icinga-version
164
+ ```
165
+
166
+ The icinga version call returns only a string with the shortend version as content: `2.8`
167
+
168
+ ## create a certificate request
169
+
170
+ Create an Certificate request
171
+
172
+ ```bash
173
+ curl \
174
+ --user ${CERTIFICATE_SERVICE_BA_USER}:${CERTIFICATE_SERVICE_BA_PASSWORD} \
175
+ --request GET \
176
+ --header "X-API-USER: cert-service" \
177
+ --header "X-API-KEY: knockknock" \
178
+ --output /tmp/request_${HOSTNAME}.json \
179
+ http://${CERTIFICATE_SERVICE_SERVER}:${CERTIFICATE_SERVICE_PORT}/v2/request/${HOSTNAME}
180
+ ```
181
+
182
+ ## download an certificate
183
+
184
+ After an certificate request, you can download the created certificate:
185
+
186
+ ```bash
187
+ checksum=$(jq --raw-output .checksum /tmp/request_${HOSTNAME}.json)
188
+
189
+ curl \
190
+ --user ${CERTIFICATE_SERVICE_BA_USER}:${CERTIFICATE_SERVICE_BA_PASSWORD} \
191
+ --request GET \
192
+ --header "X-API-USER: cert-service" \
193
+ --header "X-API-KEY: knockknock" \
194
+ --header "X-CHECKSUM: ${checksum}" \
195
+ --output /tmp/cert_${HOSTNAME}.tgz \
196
+ http://${CERTIFICATE_SERVICE_SERVER}:${CERTIFICATE_SERVICE_PORT}/v2/cert/${HOSTNAME}
197
+ ```
198
+
199
+ ## validate the satellite CA
200
+
201
+ If the CA has been renewed on the master, all satellites or agents will no longer be able to connect to the master.
202
+ To be able to detect this possibility, you can create a checksum of the `ca.crt` file and have it checked by the certificats service.
203
+
204
+ The following algorithms are supported to create a checksum:
205
+ - `md5`
206
+ - `sha256`
207
+ - `sha384`
208
+ - `sha512`
209
+
210
+ ```bash
211
+ checksum=$(sha256sum ${ICINGA_CERT_DIR}/ca.crt | cut -f 1 -d ' ')
212
+
213
+ curl \
214
+ --user ${CERTIFICATE_SERVICE_BA_USER}:${CERTIFICATE_SERVICE_BA_PASSWORD} \
215
+ --request GET \
216
+ http://${CERTIFICATE_SERVICE_SERVER}:${CERTIFICATE_SERVICE_PORT}/v2/validate/${checksum}
217
+ ```
218
+
219
+ ## sign a certificate request
220
+
221
+ Version 2.8 of Icinga2 came with a CA proxy.
222
+ Here you can use the well-known `node wizard` to create a certificate request on a satellite or agent.
223
+ This certificate only has to be confirmed at the Icinga2 Master.
224
+
225
+ The certificate files are then replicated to the respective applicant.
226
+
227
+ With the following API call you can confirm the certificate without being logged on to the master.
228
+
229
+ ```bash
230
+ curl \
231
+ --user ${CERTIFICATE_SERVICE_BA_USER}:${CERTIFICATE_SERVICE_BA_PASSWORD} \
232
+ --request POST \
233
+ --header "X-API-USER: cert-service" \
234
+ --header "X-API-KEY: knockknock" \
235
+ http://${CERTIFICATE_SERVICE_SERVER}:${CERTIFICATE_SERVICE_PORT}/v2/sign/${HOSTNAME}
236
+ ```
237
+
238
+ ## download an generic script for combine the latest 3 steps
239
+
240
+ For an own service, you can download an generic script, thats compine the lates 3 steps.
241
+
242
+
243
+
244
+
245
+ ```bash
246
+ curl \
247
+ --user ${CERTIFICATE_SERVICE_BA_USER}:${CERTIFICATE_SERVICE_BA_PASSWORD} \
248
+ http://${CERTIFICATE_SERVICE_SERVER}:${CERTIFICATE_SERVICE_PORT}/v2/download/icinga2_certificates.sh
249
+ ```
250
+
251
+ ```bash
252
+ ./icinga2_certificates.sh --help
253
+
254
+ Download a script to handle icinga2 certificates
255
+
256
+ Version 0.8.0 (05.02.2018)
257
+
258
+ Usage: icinga2_certificates [-h] [-v] ...
259
+ -h : Show this help
260
+ -v : Prints out the Version
261
+ --ba-user : Basic Auth User for the certificate Service. Also set as ENVIRONMENT variable BA_USER
262
+ --ba-password : Basic AUth Password for the certificate Service. Also set as ENVIRONMENT variable BA_PASSWORD
263
+ --api-user : Icinga2 API User. Also set as ENVIRONMENT variable API_USER
264
+ --api-password : Icinga2 API Password. Also set as ENVIRONMENT variable API_PASSWORD
265
+ -I|--icinga2-master : the Icinga2 Master himself. Also set as ENVIRONMENT variable ICINGA2_MASTER
266
+ -P|--icinga2-port : the Icinga2 API Port (default: 5665). Also set as ENVIRONMENT variable ICINGA2_API_PORT
267
+ -c|--certificate-server : the certificate server. Also set as ENVIRONMENT variable CERTIFICATE_SERVER
268
+ -p|--certifiacte-port : the port for the certificate service (default: 8080). Also set as ENVIRONMENT variable CERTIFICATE_PORT
269
+ -a|--certifiacte-path : the url path for the certifiacte service (default: /). Also set as ENVIRONMENT variable CERTIFICATE_PATH
270
+ -d|--destination : the local destination directory for storing certificate files (default: .) Also set as ENVIRONMENT variable DESTINATION_DIR
271
+ -r|--retry : how often are the backendservices attempted to reach you. Also set as ENVIRONMENT variable RETRY
272
+ -s|--sleep-for-restart : seconds before the Icinga2 Master is restarted. Also set as ENVIRONMENT variable SLEEP_FOR_RESTART
273
+ this is needed to activate the certificate and the generated configuration
274
+
275
+ Examples
276
+ icinga2_certificates.sh --icinga2-master localhost --api-user root --api-password icinga --certificate-server localhost
277
+ ```
278
+
279
+ ---
280
+
281
+
282
+ The `node wizard` can also be automated (via `expect`):
283
+
284
+ ```
285
+ cat << EOF >> ~/node-wizard.expect
286
+
287
+ #!/usr/bin/expect
288
+
289
+ # exp_internal 1
290
+
291
+ log_user 1
292
+ set timeout 3
293
+
294
+ spawn icinga2 node wizard
295
+
296
+ expect -re "Please specify if this is a satellite/client setup" {
297
+ send -- "y\r"
298
+ }
299
+ expect -re "Please specify the common name " {
300
+ send -- "[exec hostname -f]\r"
301
+ }
302
+ expect -re "Master/Satellite Common Name" {
303
+ send -- "$env(ICINGA_MASTER)\r"
304
+ }
305
+ expect -re "Do you want to establish a connection to the parent node" {
306
+ send -- "y\r"
307
+ }
308
+ expect -re "endpoint host" {
309
+ send -- "$env(ICINGA_MASTER)\r"
310
+ }
311
+ expect -re "endpoint port" {
312
+ send -- "5665\r"
313
+ }
314
+ expect -re "Add more master/satellite endpoints" {
315
+ send -- "n\r"
316
+ }
317
+ expect -re "Is this information correct" {
318
+ send -- "y\r"
319
+ }
320
+ expect -re "Please specify the request ticket generated on your Icinga 2 master" {
321
+ send -- "\r"
322
+ }
323
+ expect -re "Bind Host" {
324
+ send -- "\r"
325
+ }
326
+ expect -re "Bind Port" {
327
+ send -- "\r"
328
+ }
329
+ expect -re "config from parent node" {
330
+ send -- "y\r"
331
+ }
332
+ expect -re "commands from parent node" {
333
+ send -- "y\r"
334
+ }
335
+
336
+ interact
337
+
338
+ EOF
339
+
340
+
341
+ expect ~/node-wizard.expect 1> /dev/null
342
+ ```
343
+
344
+
@@ -0,0 +1,278 @@
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
+ module Sinatra
23
+ class CertServiceRest < Base
24
+ register Sinatra::BasicAuth
25
+
26
+ include Logging
27
+
28
+ @icinga_master = ENV.fetch('ICINGA2_MASTER' , nil)
29
+ @icinga_api_port = ENV.fetch('ICINGA2_API_PORT' , 5665 )
30
+ @icinga_api_user = ENV.fetch('ICINGA2_API_USER' , 'root' )
31
+ @icinga_api_password = ENV.fetch('ICINGA2_API_PASSWORD', 'icinga' )
32
+ @rest_service_port = ENV.fetch('REST_SERVICE_PORT' , 8080 )
33
+ @rest_service_bind = ENV.fetch('REST_SERVICE_BIND' , '0.0.0.0' )
34
+ @basic_auth_user = ENV.fetch('BASIC_AUTH_USER' , 'admin')
35
+ @basic_auth_pass = ENV.fetch('BASIC_AUTH_PASS' , 'admin')
36
+
37
+ config_file = ENV.fetch('CONFIG_FILE' , '/etc/icinga2-cert-service.yaml')
38
+
39
+ configure do
40
+ set :environment, :production
41
+
42
+ # default configuration
43
+ @rest_service_port = 8080
44
+ @rest_service_bind = '0.0.0.0'
45
+
46
+ if( File.exist?('/etc/rest-service.yaml') )
47
+ puts ' WARNING: The configuration file \'/etc/rest-service.yaml\' is OBSOLETE'
48
+ puts ' Please use \'/etc/icinga2-cert-service.yaml\' or use the environment variable \'CONFIG_FILE\''
49
+ end
50
+
51
+ if(File.exist?(config_file) && File.exist?('/etc/rest-service.yaml'))
52
+ puts ' WARNING: The configuration file \'/etc/rest-service.yaml\' is OBSOLETE'
53
+ puts ' for compatibility reasons this is used anyway.'
54
+
55
+ config_file = '/etc/rest-service.yaml'
56
+ end
57
+
58
+ if( File.exist?(config_file) )
59
+
60
+ begin
61
+ config = YAML.load_file(config_file)
62
+
63
+ @icinga_master = config.dig('icinga', 'server')
64
+ @icinga_api_port = config.dig('icinga', 'api', 'port') || 5665
65
+ @icinga_api_user = config.dig('icinga', 'api', 'user') || 5665
66
+ @icinga_api_password = config.dig('icinga', 'api', 'password') || 5665
67
+ @rest_service_port = config.dig('rest-service', 'port') || 8080
68
+ @rest_service_bind = config.dig('rest-service', 'bind') || '0.0.0.0'
69
+ @basic_auth_user = config.dig('basic-auth', 'user') || 'admin'
70
+ @basic_auth_pass = config.dig('basic-auth', 'password') || 'admin'
71
+ rescue => error
72
+ puts error
73
+ end
74
+ end
75
+ end
76
+
77
+ set :logging, true
78
+ set :app_file, caller_files.first || $PROGRAM_NAME
79
+ set :run, proc { $PROGRAM_NAME == app_file }
80
+ set :dump_errors, true
81
+ set :show_exceptions, true
82
+ set :public_folder, '/var/www/'
83
+
84
+ set :bind, @rest_service_bind
85
+ set :port, @rest_service_port.to_i
86
+
87
+ # -----------------------------------------------------------------------------
88
+
89
+ error do
90
+ msg = "ERROR\n\nThe cert-rest-service has nasty error - " + env['sinatra.error']
91
+
92
+ msg.message
93
+ end
94
+
95
+ # -----------------------------------------------------------------------------
96
+
97
+ before do
98
+ content_type :json
99
+ end
100
+
101
+ before '/v2/*/:host' do
102
+ request.body.rewind
103
+ @request_paylod = request.body.read
104
+ end
105
+
106
+ # -----------------------------------------------------------------------------
107
+
108
+ # configure Basic Auth
109
+ authorize 'API' do |username, password|
110
+ username == @basic_auth_user && password == @basic_auth_pass
111
+ end
112
+
113
+ # -----------------------------------------------------------------------------
114
+
115
+ config = {
116
+ icinga: {
117
+ server: @icinga_master,
118
+ api: {
119
+ port: @icinga_api_port,
120
+ user: @icinga_api_user,
121
+ password: @icinga_api_password,
122
+ pki_path: @icinga_api_pki_path,
123
+ node_name: @icinga_api_node_name
124
+ }
125
+ }
126
+ }
127
+
128
+ ics = IcingaCertService::Client.new(config)
129
+
130
+ # Health Check
131
+ #
132
+ get '/v2/health-check' do
133
+ status 200
134
+ 'healthy'
135
+ end
136
+
137
+ # return the icinga2 version
138
+ #
139
+ get '/v2/icinga-version' do
140
+ status 200
141
+ result = ics.icinga_version
142
+ result + "\n"
143
+ end
144
+
145
+
146
+ # create a certificate request
147
+ #
148
+ protect 'API' do
149
+ get '/v2/request/:host' do
150
+ result = ics.create_certificate(host: params[:host], request: request.env)
151
+ result_status = result.dig(:status).to_i
152
+
153
+ status result_status
154
+
155
+ JSON.pretty_generate(result) + "\n"
156
+ end
157
+ end
158
+
159
+ # validate the satellite CA
160
+ #
161
+ protect 'API' do
162
+ get '/v2/validate/:checksum' do
163
+ result = ics.validate_certificate(checksum: params[:checksum])
164
+ result_status = result.dig(:status).to_i
165
+
166
+ status result_status
167
+ content_type :json
168
+ JSON.pretty_generate(result) + "\n"
169
+ end
170
+ end
171
+
172
+
173
+ # download an certificate
174
+ #
175
+ protect 'API' do
176
+ get '/v2/cert/:host' do
177
+ result = ics.check_certificate( host: params[:host], request: request.env )
178
+
179
+ logger.debug(result)
180
+
181
+ result_status = result.dig(:status).to_i
182
+
183
+ if result_status == 200
184
+
185
+ path = result.dig(:path)
186
+ file_name = result.dig(:file_name)
187
+
188
+ status result_status
189
+
190
+ send_file(format('%s/%s', path, file_name), filename: file_name, type: 'Application/octet-stream')
191
+ else
192
+
193
+ status result_status
194
+
195
+ JSON.pretty_generate(result) + "\n"
196
+ end
197
+ end
198
+ end
199
+
200
+ # download the master ca.crt
201
+ #
202
+ protect 'API' do
203
+ get '/v2/master-ca' do
204
+
205
+ path = '/var/lib/icinga2/certs'
206
+ file_name = 'ca.crt'
207
+ if( File.exist?(format('%s/%s', path, file_name) ) )
208
+ status 200
209
+ send_file(format('%s/%s', path, file_name), filename: file_name, type: 'Application/octet-stream')
210
+ else
211
+
212
+ status 404
213
+
214
+ JSON.pretty_generate('no ca file found') + "\n"
215
+ end
216
+ end
217
+ end
218
+
219
+ # sign a certificate request
220
+ #
221
+ protect 'API' do
222
+ get '/v2/sign/:host' do
223
+ status 200
224
+
225
+ result = ics.sign_certificate(host: params[:host], request: request.env)
226
+
227
+ JSON.pretty_generate(result) + "\n"
228
+ end
229
+ end
230
+
231
+ # download a generic scrip to handle simple certificate requests against
232
+ # ths service
233
+ # this script is needed by dashing and other clients
234
+ #
235
+ protect 'API' do
236
+ get '/v2/download/:file_name' do
237
+ status 200
238
+
239
+ result = ics.download(file_name: params[:file_name], request: request.env)
240
+
241
+ logger.debug(result)
242
+
243
+ result_status = result.dig(:status).to_i
244
+
245
+ if result_status == 200
246
+
247
+ path = result.dig(:path)
248
+ file_name = result.dig(:file_name)
249
+
250
+ status result_status
251
+
252
+ send_file(format('%s/%s', path, file_name), filename: file_name, type: 'Application/octet-stream')
253
+ else
254
+
255
+ status result_status
256
+
257
+ JSON.pretty_generate(result) + "\n"
258
+ end
259
+ end
260
+ end
261
+
262
+
263
+ not_found do
264
+ jj = {
265
+ 'meta' => {
266
+ 'code' => 404,
267
+ 'message' => 'Request not found.'
268
+ }
269
+ }
270
+ content_type :json
271
+ JSON.pretty_generate(jj)
272
+ end
273
+
274
+ # -----------------------------------------------------------------------------
275
+ run! if app_file == $PROGRAM_NAME
276
+ # -----------------------------------------------------------------------------
277
+ end
278
+ end