Compare commits

...

53 Commits

Author SHA1 Message Date
Alexander Aleksandrovič Klimov
f2c83fbbf2
Merge pull request #9950 from Icinga/probot/sync-changelog/support/2.14/9fa41f3b4fbae83d4ca5bbfce4771031b6bd1fa4
CHANGELOG.md: add v2.13.9
2023-12-20 17:30:01 +01:00
Alexander A. Klimov
b85cd98eb1 CHANGELOG.md: add v2.13.9 2023-12-20 16:27:56 +00:00
Alexander Aleksandrovič Klimov
9fa41f3b4f
Merge pull request #9940 from Icinga/2141
Icinga 2.14.1
2023-12-20 17:27:43 +01:00
Alexander Aleksandrovič Klimov
61d190f892
Merge pull request #9947 from Icinga/2141morebackport
Truncate too big notification command lines, fix GelfWriter deadlock and return 503 in /v1/console/* during reload
2023-12-20 12:44:07 +01:00
Alexander Aleksandrovič Klimov
3ddbbebc63
Merge pull request #9946 from Icinga/2141backport
Disable TLS renegotiation, bump Windows deps and fix Icinga DB crashes
2023-12-20 12:40:41 +01:00
Alexander A. Klimov
41b692793b Icinga 2.14.1 2023-12-20 10:56:15 +01:00
Alexander A. Klimov
fecb209fe0 GelfWriter: protect m_Stream via m_WorkQueue, not ObjectLock(this)
On shutdown or HA re-connect ConfigObject#SetAuthority(false) is called which
does ObjectLock(this) and ConfigObject#Pause(). GelfWriter#Pause(), with the
above ObjectLock, calls m_WorkQueue.Join(). But items inside that also doing
ObjectLock(this) cause a deadlock.
2023-12-20 10:46:51 +01:00
Mattia Codato
85c5a7c901 Prevent calls to command API while the configuration is reloading.
Fixes #9840
2023-12-20 10:46:51 +01:00
Alexander A. Klimov
0eeac3b385 PluginNotificationTask::ScriptFunc(): on Linux truncate output and comment
not to run into an exec(3) error E2BIG due to a too long argument.
This sends a notification with truncated output instead of not sending.
2023-12-20 10:46:51 +01:00
Alexander A. Klimov
7efdae6a53 IcingaDB#SendConfigDelete(): fix missing nullptr check before deref 2023-12-20 10:30:01 +01:00
Alexander A. Klimov
79efda7a14 Icinga DB downtime history: provide cancel_time where has_been_cancelled may be 1
The table sla_history_downtime requires a downtime_end.
The Go daemon takes the cancel_time if has_been_cancelled is 1.
So we must supply a cancel_time whereever has_been_cancelled is 1.
Otherwise the Go daemon can't process some entries.
2023-12-20 10:30:01 +01:00
Alexander A. Klimov
8c9f3ede4a Bump OpenSSL shipped for Windows to v3.0.12 2023-12-20 10:14:00 +01:00
Alexander A. Klimov
4547c1e5a3 Bump Boost shipped for Windows to v1.83
Note: For doc/21-development.md use:

perl -pi -e 's/(boost[-\w]*?1[-_]?)82/${1}83/g' doc/21-development.md
2023-12-20 10:14:00 +01:00
Alexander A. Klimov
ec77b6f1e3 Disable TLS renegotiation
The API doesn't need it and a customer's security scanner
is afraid of a potential DoS attack vector.
2023-12-20 10:14:00 +01:00
Alexander Aleksandrovič Klimov
bbb45894dd
Merge pull request #9944 from Icinga/targeted-api-filter-214
FilterUtility::GetFilterTargets(): don't run filter for specific object(s) for all objects
2023-12-19 17:40:59 +01:00
Alexander Aleksandrovič Klimov
b55a14d536
Merge pull request #9921 from Icinga/doc2141
Update documentation
2023-12-19 17:01:31 +01:00
Alexander A. Klimov
03aa5adb7a Tests: config_apply/gettargetservices_*: use BOOST_CHECK_EQUAL_COLLECTIONS()
to show the value diff in case of mismatch.

Co-authored-by: Yonas Habteab <yonas.habteab@icinga.com>
2023-12-19 15:19:20 +01:00
Alexander A. Klimov
b99db24100 Test ApplyRule::GetTarget*s() 2023-12-19 15:19:20 +01:00
Alexander A. Klimov
bcbb1aee52 FilterUtility::GetFilterTargets(): don't run filter for specific object(s) for all objects 2023-12-19 15:19:20 +01:00
Alexander A. Klimov
60b7e96adc ApplyRule::GetTarget*s(): support constant strings from variables
in addition to literal strings. This is for sandboxed filters with some
variables pre-set by the caller. They're "constant" in that scope, too.
2023-12-19 15:19:20 +01:00
Alexander A. Klimov
8248fa110c Introduce DictExpression#GetExpressions() 2023-12-19 15:19:20 +01:00
Alexander A. Klimov
5c10bad86f Introduce Dictionary#GetRef() 2023-12-19 15:19:20 +01:00
Alexander Aleksandrovič Klimov
5059d0f8b0
Merge pull request #9933 from Icinga/renew-the-ca-9890-214
ApiListener#Start(): auto-renew CA on its owner
2023-12-19 15:15:00 +01:00
Alexander A. Klimov
12c706a8ac Update AUTHORS 2023-12-19 12:29:51 +01:00
Julian Brost
5835e2e03b Update .mailmap 2023-12-19 12:29:51 +01:00
Alexander A. Klimov
61900b73e1 Doc: Troubleshooting: remove obsolete section "Analyze Notification Result"
This feature has been reverted and won't be re-introduced anytime soon.
2023-12-19 12:29:51 +01:00
Alexander A. Klimov
4195f8d0f0 RequestCertificateHandler(): also renew if CA needs a renewal
and a newer one is available.
2023-12-18 17:04:59 +01:00
Alexander A. Klimov
6b000fbce6 CertificateToString(): allow raw pointer input 2023-12-18 17:04:59 +01:00
Alexander A. Klimov
32f43c4873 ApiListener#Start(): auto-renew CA on its owner
otherwise it would expire.
2023-12-18 17:04:59 +01:00
Alexander A. Klimov
b3dee0bb0a ApiListener#RenewCert(): enable optional CA creation 2023-12-18 17:04:59 +01:00
Alexander A. Klimov
0cb037c698 CreateCertIcingaCA(EVP_PKEY*, X509_NAME*): enable optional CA creation 2023-12-18 17:04:59 +01:00
Alexander A. Klimov
17eac30868 Test IsCertUptodate() and IsCaUptodate() 2023-12-18 17:04:59 +01:00
Alexander A. Klimov
0f4723e567 Introduce IsCaUptodate() by splitting IsCertUptodate() 2023-12-18 17:04:59 +01:00
Alexander Aleksandrovič Klimov
bff7e69991
Merge pull request #9932 from Icinga/do-not-re-notify-if-filtered-states-don-t-change-4503-214
Discard likely duplicate problem notifications via Notification#last_notified_state_per_user
2023-12-13 18:15:56 +01:00
Alexander A. Klimov
d7500ca1bd Notification#BeginExecuteNotification(): on recovery clear last_notified_state_per_user 2023-12-13 16:14:57 +01:00
Alexander A. Klimov
bbadf1f27b Notification#BeginExecuteNotification(): discard likely duplicate problem notifications 2023-12-13 16:14:57 +01:00
Alexander A. Klimov
0ae2bdc444 Cluster-sync Notification#last_notified_state_per_user 2023-12-13 16:14:57 +01:00
Alexander A. Klimov
66ba9f446a Notification#BeginExecuteNotification(): track state change notifications 2023-12-13 16:14:57 +01:00
Alexander A. Klimov
9a08472162 Docs: change "Amazon Linux 2" to "Amazon Linux" where applicable
We also support Amazon Linux 2023 now.
2023-11-24 17:29:35 +01:00
Alvar Penning
0dd66f9886 Document host Common Runtime Attribute 2023-11-24 17:29:35 +01:00
Alvar Penning
bb717cf177 Fix link text for Downtime* Event Stream Types
The link text for all Downtime* Event Stream Types contains "Comment"
instead of "Downtime" even when pointing to the correct object.
2023-11-24 17:29:35 +01:00
Yonas Habteab
04dbf4aa13 Fix downtime host/service name attribute descriptions 2023-11-24 17:29:35 +01:00
Alexander Aleksandrovič Klimov
2e03a2e528 Doc: ITL: correct $ifw_api_crl$ default
In contrast to cert/key/CA, no CRL means no CRL.
(The behavior of the API is the same.)
2023-11-24 17:29:35 +01:00
Mathias Aerts
99aa33b85f Fix typo 2023-11-24 17:29:35 +01:00
Alexander Aleksandrovič Klimov
0ceff4c09b
Merge pull request #9918 from Icinga/gha2141
Update GitHub actions
2023-11-24 17:26:27 +01:00
Alexander A. Klimov
9944437b7b Update AUTHORS 2023-11-24 15:06:52 +01:00
Lord Hepipud
968f7401cf Adds ProgressPreference SilentlyContinue
We should use `$Global:ProgressPreference = 'SilentlyContinue';` to disable the progress bar during download.
By doing so, information are directly written to the disk instead of written inside the memory and dumped to the disk afterwards
2023-11-24 14:43:52 +01:00
Alexander Aleksandrovič Klimov
d583598d08 GHA: drop EOL Fedora 36 2023-11-24 14:38:44 +01:00
Alexander A. Klimov
de47878991 GHA: complain if PR adds commits from people not yet listed in ./AUTHORS
not to have to update ./AUTHORS or .mailmap after merging.
2023-11-24 14:38:40 +01:00
Alexander A. Klimov
3801b8a7cb GHA: cancel runs on PR, but not on push
In a PR one top commit replaces the previous one.
But the central branches are more like timelines.
It's nice to have red crosses in a such timeline
as clear indicators that something was actually broken.
2023-11-24 14:38:00 +01:00
Alexander Aleksandrovič Klimov
35ef622cc6 GHA: add upcoming (already frozen) Ubuntu 23.10 2023-11-24 14:38:00 +01:00
Alexander Aleksandrovič Klimov
1f4ac7e651 GHA: add upcoming (already frozen) Fedora 39 2023-11-24 14:38:00 +01:00
Alexander Aleksandrovič Klimov
19927d0043 GHA: drop EOL Ubuntu 22.10 2023-11-24 14:38:00 +01:00
47 changed files with 1297 additions and 214 deletions

39
.github/workflows/authors-file.yml vendored Normal file
View File

@ -0,0 +1,39 @@
name: AUTHORS file
on:
pull_request: { }
jobs:
authors-file:
name: AUTHORS file
runs-on: ubuntu-latest
steps:
- name: Checkout HEAD
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Check whether ./AUTHORS is up-to-date
run: |
set -exo pipefail
sort -uo AUTHORS AUTHORS
git add AUTHORS
git log --format='format:%aN <%aE>' "$(
git merge-base "origin/$GITHUB_BASE_REF" "origin/$GITHUB_HEAD_REF"
)..origin/$GITHUB_HEAD_REF" >> AUTHORS
sort -uo AUTHORS AUTHORS
git diff AUTHORS >> AUTHORS.diff
- name: Complain if ./AUTHORS isn't up-to-date
run: |
if [ -s AUTHORS.diff ]; then
cat <<'EOF' >&2
There are the following new authors. If the commit author data is correct,
either add them to the AUTHORS file or update .mailmap. See gitmailmap(5) or:
https://git-scm.com/docs/gitmailmap
Don't hesitate to ask us for help if necessary.
EOF
cat AUTHORS.diff
exit 1
fi

View File

@ -11,7 +11,7 @@ on:
- published
concurrency:
group: docker-${{ github.ref }}
group: docker-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: true
jobs:

View File

@ -8,7 +8,7 @@ on:
pull_request: {}
concurrency:
group: linux-${{ github.ref }}
group: linux-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: true
jobs:
@ -27,9 +27,9 @@ jobs:
- debian:10
- debian:11 # and Raspbian 11
- debian:12 # and Raspbian 12
- fedora:36
- fedora:37
- fedora:38
- fedora:39
- opensuse/leap:15.3 # SLES 15.3
- opensuse/leap:15.4 # and SLES 15.4
- opensuse/leap:15.5 # and SLES 15.5
@ -37,8 +37,8 @@ jobs:
- rockylinux:9 # RHEL 9
- ubuntu:20.04
- ubuntu:22.04
- ubuntu:22.10
- ubuntu:23.04
- ubuntu:23.10
steps:
- name: Checkout HEAD

View File

@ -8,7 +8,7 @@ on:
pull_request: {}
concurrency:
group: rpm-${{ github.ref }}
group: rpm-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: true
jobs:

View File

@ -8,7 +8,7 @@ on:
pull_request: {}
concurrency:
group: windows-${{ github.ref }}
group: windows-${{ github.event_name == 'push' && github.sha || github.ref }}
cancel-in-progress: true
jobs:

View File

@ -23,6 +23,7 @@ Alexander A. Klimov <alexander.klimov@icinga.com> <alexander.klimov@icinga.com>
<marius@graylog.com> <marius@torch.sh>
<markus.frosch@icinga.com> <lazyfrosch@icinga.org>
<markus.frosch@icinga.com> <markus@lazyfrosch.de>
<mathias.aerts@delta.blue> <mathiasaerts@users.noreply.github.com>
<michael.friedrich@icinga.com> <michael.friedrich@gmail.com>
<michael.friedrich@icinga.com> <Michael.Friedrich@netways.de>
<nicole.lang@icinga.com> <nicole.lang@netways.de>

View File

@ -11,6 +11,7 @@ Alexander Fuhr <alexander.fuhr@netways.de>
Alexander Schomburg <script.acc@alex.schomb.org>
Alexander Stoll <astoll@netways.de>
Alexander Wirt <formorer@debian.org>
Alvar Penning <alvar.penning@icinga.com>
Andrea Avancini <andrea.avancini@wuerth-phoenix.com>
Andrea Kao <eirinikos@gmail.com>
Andreas Maus <maus@badphish.ypbind.de>
@ -153,6 +154,7 @@ Lennart Betz <lennart.betz@icinga.com>
Leon Stringer <leon@priorsvle.com>
lihan <tclh123@gmail.com>
log1-c <24474580+log1-c@users.noreply.github.com>
Lord Hepipud <contact@lordhepipud.de>
Lorenz Kästle <lorenz.kaestle@netways.de>
Louis Sautier <sautier.louis@gmail.com>
Luca Lesinigo <luca@lm-net.it>

View File

@ -7,6 +7,32 @@ documentation before upgrading to a new release.
Released closed milestones can be found on [GitHub](https://github.com/Icinga/icinga2/milestones?state=closed).
## 2.14.1 (2023-12-21)
Version 2.14.1 is a hotfix release for masters and satellites that mainly
prevents permanent disintegration of a whole cluster due to root CA expiry.
### Security
* Automatically renew own root CA and distribute it to all nodes. #9933
* Update OpenSSL shipped on Windows to v3.0.12. #9946
* Disable TLS renegotiation (handshake on existing connection). #9946
### Bugfixes
* Icinga DB feature: fix crash due to missing NULL pointer check. #9946
* Icinga DB feature: fix data written into Redis crashing the Go daemon. #9946
* GelfWriter: fix deadlock on stop/reload caused by busy queue. #9947
* Don't lose notifications due to too long output, truncate it. #9947
### Enhancements
* Discard duplicate problem notifications due to state filtering. #9932
* Speed up API filters targeting specific hosts/services to O(1). #9944
* POST /v1/console/\*: return HTTP 503 while Icinga is reloading. #9947
* Update Boost shipped on Windows to v1.83. #9946
* Documentation: several fixes and improvements. #9921
## 2.14.0 (2023-07-12)
[Issues and PRs](https://github.com/Icinga/icinga2/issues?q=is%3Aclosed+milestone%3A2.14.0)
@ -199,6 +225,26 @@ Add `linux_netdev` check command. #9045
* Several code quality improvements. #8815 #9106 #9250
#9508 #9517 #9537 #9594 #9605 #9606 #9641 #9658 #9702 #9717 #9738
## 2.13.9 (2023-12-21)
Version 2.13.9 is a hotfix release for masters and satellites that mainly
prevents permanent disintegration of a whole cluster due to root CA expiry.
### Security
* Automatically renew own root CA and distribute it to all nodes. #9934
* Update OpenSSL shipped on Windows to v3.0.12. #9945
* Disable TLS renegotiation (handshake on existing connection). #9945
### Bugfixes
* Icinga DB feature: fix crash due to missing NULL pointer check. #9945
* Icinga DB feature: fix data written into Redis crashing the Go daemon. #9945
### Updates
* Update Boost shipped on Windows to v1.83. #9945
## 2.13.8 (2023-07-12)
Version 2.13.8 is a maintenance release that fixes some bugs,

View File

@ -111,6 +111,12 @@ refs #1234
You can add multiple commits during your journey to finish your patch.
Don't worry, you can squash those changes into a single commit later on.
Ensure your name and email address in the commit metadata are correct.
In your first contribution (PR) also add them to [AUTHORS](./AUTHORS).
If those metadata changed since your last successful contribution,
you should update [AUTHORS](./AUTHORS) and [.mailmap](./.mailmap).
For the latter see [gitmailmap(5)](https://git-scm.com/docs/gitmailmap).
## <a id="contributing-pull-requests"></a> Pull Requests
Once you've commited your changes, please update your local master

View File

@ -1,2 +1,2 @@
Version: 2.14.0
Version: 2.14.1
Revision: 1

View File

@ -200,7 +200,7 @@ zypper ar https://download.opensuse.org/repositories/server:/monitoring/15.3/ser
<!-- {% endif %} -->
<!-- {% if amazon_linux %} -->
### Amazon Linux 2 Repository <a id="amazon-linux-2-repository"></a>
### Amazon Linux Repository <a id="amazon-linux-2-repository"></a>
!!! info
@ -214,12 +214,14 @@ rpm --import https://packages.icinga.com/icinga.key
wget https://packages.icinga.com/subscription/amazon/ICINGA-release.repo -O /etc/yum.repos.d/ICINGA-release.repo
```
The packages for Amazon Linux 2 depend on other packages which are distributed
The packages for **Amazon Linux 2** depend on other packages which are distributed
as part of the [EPEL repository](https://fedoraproject.org/wiki/EPEL).
```bash
yum install epel-release
```
The packages for newer versions of Amazon Linux don't require additional repositories.
<!-- {% endif %} -->
<!-- {% if windows %} -->
@ -307,7 +309,7 @@ zypper install icinga2
<!-- {% if amazon_linux %} -->
<!-- {% if not icingaDocs %} -->
#### Amazon Linux 2
#### Amazon Linux
<!-- {% endif %} -->
```bash
yum install icinga2
@ -418,15 +420,17 @@ zypper install --recommends monitoring-plugins-all
<!-- {% if amazon_linux %} -->
<!-- {% if not icingaDocs %} -->
#### Amazon Linux 2
#### Amazon Linux
<!-- {% endif %} -->
The packages for Amazon Linux 2 depend on other packages which are distributed as part of the EPEL repository.
The packages for **Amazon Linux 2** depend on other packages which are distributed as part of the EPEL repository.
```bash
amazon-linux-extras install epel
yum install nagios-plugins-all
```
Unfortunately newer versions of Amazon Linux don't provide those plugins, yet.
<!-- {% endif %} -->
## Set up Icinga 2 API <a id="set-up-icinga2-api"></a>
@ -505,7 +509,7 @@ Use your distribution's package manager to install the `icingadb-redis` package
<!-- {% if amazon_linux %} -->
<!-- {% if not icingaDocs %} -->
##### Amazon Linux 2
##### Amazon Linux
<!-- {% endif %} -->
```bash
yum install icingadb-redis

View File

@ -225,12 +225,12 @@ apply Service "db-size-" for (db_name => config in host.vars.databases) {
check_command = "mysql_health"
if (config.mysql_health_username) {
vars.mysql_healt_username = config.mysql_health_username
vars.mysql_health_username = config.mysql_health_username
} else {
vars.mysql_health_username = "root"
}
if (config.mysql_health_password) {
vars.mysql_healt_password = config.mysql_health_password
vars.mysql_health_password = config.mysql_health_password
} else {
vars.mysql_health_password = "icingar0xx"
}

View File

@ -34,6 +34,7 @@ the [Icinga 2 API](12-icinga2-api.md#icinga2-api-config-objects).
templates | Array | Templates imported on object compilation.
package | String | [Configuration package name](12-icinga2-api.md#icinga2-api-config-management) this object belongs to. Local configuration is set to `_etc`, runtime created objects use `_api`.
source\_location | Dictionary | Location information where the configuration files are stored.
name | String | Object name. Might be used in [apply rules](03-monitoring-basics.md#using-apply).
## Monitoring Objects <a id="object-types-monitoring"></a>
@ -731,7 +732,6 @@ Configuration Attributes:
event\_command | Object name | **Optional.** The name of an event command that should be executed every time the service's state changes or the service is in a `SOFT` state.
volatile | Boolean | **Optional.** Treat all state changes as HARD changes. See [here](08-advanced-topics.md#volatile-services-hosts) for details. Defaults to `false`.
zone | Object name | **Optional.** The zone this object is a member of. Please read the [distributed monitoring](06-distributed-monitoring.md#distributed-monitoring) chapter for details.
name | String | **Required.** The service name. Must be unique on a per-host basis. For advanced usage in [apply rules](03-monitoring-basics.md#using-apply) only.
command\_endpoint | Object name | **Optional.** The endpoint where commands are executed on.
notes | String | **Optional.** Notes for the service.
notes\_url | String | **Optional.** URL for notes for the service (for example, in notification commands).
@ -1046,8 +1046,8 @@ Configuration Attributes:
Name | Type | Description
--------------------------|-----------------------|----------------------------------
host\_name | Object name | **Required.** The name of the host this comment belongs to.
service\_name | Object name | **Optional.** The short name of the service this comment belongs to. If omitted, this comment object is treated as host comment.
host\_name | Object name | **Required.** The name of the host this downtime belongs to.
service\_name | Object name | **Optional.** The short name of the service this downtime belongs to. If omitted, this downtime object is treated as host downtime.
author | String | **Required.** The author's name.
comment | String | **Required.** The comment text.
start\_time | Timestamp | **Required.** The start time as UNIX timestamp.

View File

@ -215,7 +215,7 @@ Optional custom variables passed as [command parameters](03-monitoring-basics.md
| ifw\_api\_cert | null (Icinga PKI) | TLS client certificate path. |
| ifw\_api\_key | null (Icinga PKI) | TLS client private key path. |
| ifw\_api\_ca | null (Icinga PKI) | Peer TLS CA certificate path. |
| ifw\_api\_crl | null (Icinga PKI) | Path to TLS CRL to check peer against. |
| ifw\_api\_crl | null (none) | Path to TLS CRL to check peer against. |
| ifw\_api\_username | null (none) | Basic auth username. |
| ifw\_api\_password | null (none) | Basic auth password. |

View File

@ -1850,7 +1850,7 @@ Example for all object events:
--------------|---------------|--------------------------
type | String | Event type `DowntimeAdded`.
timestamp | Timestamp | Unix timestamp when the event happened.
downtime | Dictionary | Serialized [Comment](09-object-types.md#objecttype-downtime) object.
downtime | Dictionary | Serialized [Downtime](09-object-types.md#objecttype-downtime) object.
#### <a id="icinga2-api-event-streams-type-downtimeremoved"></a> Event Stream Type: DowntimeRemoved
@ -1858,7 +1858,7 @@ Example for all object events:
--------------|---------------|--------------------------
type | String | Event type `DowntimeRemoved`.
timestamp | Timestamp | Unix timestamp when the event happened.
downtime | Dictionary | Serialized [Comment](09-object-types.md#objecttype-downtime) object.
downtime | Dictionary | Serialized [Downtime](09-object-types.md#objecttype-downtime) object.
#### <a id="icinga2-api-event-streams-type-downtimestarted"></a> Event Stream Type: DowntimeStarted
@ -1867,7 +1867,7 @@ Example for all object events:
--------------|---------------|--------------------------
type | String | Event type `DowntimeStarted`.
timestamp | Timestamp | Unix timestamp when the event happened.
downtime | Dictionary | Serialized [Comment](09-object-types.md#objecttype-downtime) object.
downtime | Dictionary | Serialized [Downtime](09-object-types.md#objecttype-downtime) object.
#### <a id="icinga2-api-event-streams-type-downtimetriggered"></a> Event Stream Type: DowntimeTriggered
@ -1876,7 +1876,7 @@ Example for all object events:
--------------|---------------|--------------------------
type | String | Event type `DowntimeTriggered`.
timestamp | Timestamp | Unix timestamp when the event happened.
downtime | Dictionary | Serialized [Comment](09-object-types.md#objecttype-downtime) object.
downtime | Dictionary | Serialized [Downtime](09-object-types.md#objecttype-downtime) object.
### Event Stream Filter <a id="icinga2-api-event-streams-filter"></a>

View File

@ -843,7 +843,7 @@ yum install icinga2-ido-mysql
zypper install icinga2-ido-mysql
```
###### Amazon Linux 2
###### Amazon Linux
```bash
yum install icinga2-ido-mysql
@ -942,7 +942,7 @@ yum install icinga2-ido-pgsql
zypper install icinga2-ido-pgsql
```
###### Amazon Linux 2
###### Amazon Linux
```bash
yum install icinga2-ido-pgsql

View File

@ -950,95 +950,6 @@ curl -k -s -u root:icinga -H 'Accept: application/json' -X POST 'https://localho
```
### Analyze Notification Result <a id="troubleshooting-notifications-result"></a>
> **Note**
>
> This feature is available since v2.11 and requires all endpoints
> being updated.
Notifications inside a HA enabled zone are balanced between the endpoints,
just like checks.
Sometimes notifications may fail, and with looking into the (debug) logs
for both masters, you cannot correlate this correctly.
The `last_notification_result` runtime attribute is stored and synced for Notification
objects and can be queried via REST API.
Example for retrieving the notification object and result from all `disk` services using a
[regex match](18-library-reference.md#global-functions-regex) on the name:
```
$ curl -k -s -u root:icinga -H 'Accept: application/json' -H 'X-HTTP-Method-Override: GET' -X POST 'https://localhost:5665/v1/objects/notifications' \
-d '{ "filter": "regex(pattern, service.name)", "filter_vars": { "pattern": "^disk" }, "attrs": [ "__name", "last_notification_result" ], "pretty": true }'
{
"results": [
{
"attrs": {
"last_notification_result": {
"active": true,
"command": [
"/etc/icinga2/scripts/mail-service-notification.sh",
"-4",
"",
"-6",
"",
"-b",
"",
"-c",
"",
"-d",
"2019-08-02 10:54:16 +0200",
"-e",
"disk",
"-l",
"icinga2-agent1.localdomain",
"-n",
"icinga2-agent1.localdomain",
"-o",
"DISK OK - free space: / 38108 MB (90.84% inode=100%);",
"-r",
"user@localdomain",
"-s",
"OK",
"-t",
"RECOVERY",
"-u",
"disk"
],
"execution_end": 1564736056.186217,
"execution_endpoint": "icinga2-master1.localdomain",
"execution_start": 1564736056.132323,
"exit_status": 0.0,
"output": "",
"type": "NotificationResult"
}
},
"joins": {},
"meta": {},
"name": "icinga2-agent1.localdomain!disk!mail-service-notification",
"type": "Notification"
}
...
]
}
```
Example with the debug console:
```
$ ICINGA2_API_PASSWORD=icinga icinga2 console --connect 'https://root@localhost:5665/' --eval 'get_object(Notification, "icinga2-agent1.localdomain!disk!mail-service-notification").last_notification_result.execution_endpoint' | jq
"icinga2-agent1.localdomain"
```
Whenever a notification command failed to execute, you can fetch the output as well.
## Feature Troubleshooting <a id="troubleshooting-features"></a>
### Feature is not working <a id="feature-not-working"></a>

View File

@ -1514,6 +1514,76 @@ Message updates will be dropped when:
* Notification does not exist.
* Origin endpoint's zone is not allowed to access this checkable.
#### event::UpdateLastNotifiedStatePerUser <a id="technical-concepts-json-rpc-messages-event-updatelastnotifiedstateperuser"></a>
> Location: `clusterevents.cpp`
##### Message Body
Key | Value
----------|---------
jsonrpc | 2.0
method | event::UpdateLastNotifiedStatePerUser
params | Dictionary
##### Params
Key | Type | Description
-------------|--------|------------------
notification | String | Notification name
user | String | User name
state | Number | Checkable state the user just got a problem notification for
Used to sync the state of a notification object within the same HA zone.
##### Functions
Event Sender: `Notification::OnLastNotifiedStatePerUserUpdated`
Event Receiver: `LastNotifiedStatePerUserUpdatedAPIHandler`
##### Permissions
The receiver will not process messages from not configured endpoints.
Message updates will be dropped when:
* Notification does not exist.
* Origin endpoint is not within the local zone.
#### event::ClearLastNotifiedStatePerUser <a id="technical-concepts-json-rpc-messages-event-clearlastnotifiedstateperuser"></a>
> Location: `clusterevents.cpp`
##### Message Body
Key | Value
----------|---------
jsonrpc | 2.0
method | event::ClearLastNotifiedStatePerUser
params | Dictionary
##### Params
Key | Type | Description
-------------|--------|------------------
notification | String | Notification name
Used to sync the state of a notification object within the same HA zone.
##### Functions
Event Sender: `Notification::OnLastNotifiedStatePerUserCleared`
Event Receiver: `LastNotifiedStatePerUserClearedAPIHandler`
##### Permissions
The receiver will not process messages from not configured endpoints.
Message updates will be dropped when:
* Notification does not exist.
* Origin endpoint is not within the local zone.
#### event::SetForceNextCheck <a id="technical-concepts-json-rpc-messages-event-setforcenextcheck"></a>
> Location: `clusterevents.cpp`

View File

@ -477,18 +477,18 @@ File Type: EXECUTABLE IMAGE
Image has the following dependencies:
boost_coroutine-vc142-mt-gd-x64-1_82.dll
boost_date_time-vc142-mt-gd-x64-1_82.dll
boost_filesystem-vc142-mt-gd-x64-1_82.dll
boost_thread-vc142-mt-gd-x64-1_82.dll
boost_regex-vc142-mt-gd-x64-1_82.dll
boost_coroutine-vc142-mt-gd-x64-1_83.dll
boost_date_time-vc142-mt-gd-x64-1_83.dll
boost_filesystem-vc142-mt-gd-x64-1_83.dll
boost_thread-vc142-mt-gd-x64-1_83.dll
boost_regex-vc142-mt-gd-x64-1_83.dll
libssl-3_0-x64.dll
libcrypto-3_0-x64.dll
WS2_32.dll
dbghelp.dll
SHLWAPI.dll
msi.dll
boost_unit_test_framework-vc142-mt-gd-x64-1_82.dll
boost_unit_test_framework-vc142-mt-gd-x64-1_83.dll
KERNEL32.dll
SHELL32.dll
ADVAPI32.dll
@ -1763,7 +1763,7 @@ mkdir build
cd .\build\
& "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" `
-DICINGA2_UNITY_BUILD=OFF -DBoost_INCLUDE_DIR=C:\local\boost_1_82_0-Win64 `
-DICINGA2_UNITY_BUILD=OFF -DBoost_INCLUDE_DIR=C:\local\boost_1_83_0-Win64 `
-DBISON_EXECUTABLE=C:\ProgramData\chocolatey\lib\winflexbison3\tools\win_bison.exe `
-DFLEX_EXECUTABLE=C:\ProgramData\chocolatey\lib\winflexbison3\tools\win_flex.exe ..
@ -1935,16 +1935,16 @@ Download the [boost-binaries](https://sourceforge.net/projects/boost/files/boost
- 64 for 64 bit builds
```
https://sourceforge.net/projects/boost/files/boost-binaries/1.82.0/boost_1_82_0-msvc-14.2-64.exe/download
https://sourceforge.net/projects/boost/files/boost-binaries/1.82.0/boost_1_83_0-msvc-14.2-64.exe/download
```
Run the installer and leave the default installation path in `C:\local\boost_1_82_0`.
Run the installer and leave the default installation path in `C:\local\boost_1_83_0`.
##### Source & Compile
In order to use the boost development header and library files you need to [download](https://www.boost.org/users/download/)
Boost and then extract it to e.g. `C:\local\boost_1_82_0`.
Boost and then extract it to e.g. `C:\local\boost_1_83_0`.
> **Note**
>
@ -1952,12 +1952,12 @@ Boost and then extract it to e.g. `C:\local\boost_1_82_0`.
> the archive contains more than 70k files.
In order to integrate Boost into Visual Studio, open the `Developer Command Prompt` from the start menu,
and navigate to `C:\local\boost_1_82_0`.
and navigate to `C:\local\boost_1_83_0`.
Execute `bootstrap.bat` first.
```
cd C:\local\boost_1_82_0
cd C:\local\boost_1_83_0
bootstrap.bat
```
@ -2040,8 +2040,8 @@ You need to specify the previously installed component paths.
Variable | Value | Description
----------------------|----------------------------------------------------------------------|-------------------------------------------------------
`BOOST_ROOT` | `C:\local\boost_1_82_0` | Root path where you've extracted and compiled Boost.
`BOOST_LIBRARYDIR` | Binary: `C:\local\boost_1_82_0\lib64-msvc-14.2`, Source: `C:\local\boost_1_82_0\stage` | Path to the static compiled Boost libraries, directory must contain `lib`.
`BOOST_ROOT` | `C:\local\boost_1_83_0` | Root path where you've extracted and compiled Boost.
`BOOST_LIBRARYDIR` | Binary: `C:\local\boost_1_83_0\lib64-msvc-14.2`, Source: `C:\local\boost_1_83_0\stage` | Path to the static compiled Boost libraries, directory must contain `lib`.
`BISON_EXECUTABLE` | `C:\ProgramData\chocolatey\lib\winflexbison\tools\win_bison.exe` | Path to the Bison executable.
`FLEX_EXECUTABLE` | `C:\ProgramData\chocolatey\lib\winflexbison\tools\win_flex.exe` | Path to the Flex executable.
`ICINGA2_UNITY_BUILD` | OFF | Disable unity builds for development environments.
@ -2076,8 +2076,8 @@ $env:ICINGA2_INSTALLPATH = 'C:\Program Files\Icinga2-debug'
$env:ICINGA2_BUILDPATH='debug'
$env:CMAKE_BUILD_TYPE='Debug'
$env:OPENSSL_ROOT_DIR='C:\OpenSSL-Win64'
$env:BOOST_ROOT='C:\local\boost_1_82_0'
$env:BOOST_LIBRARYDIR='C:\local\boost_1_82_0\lib64-msvc-14.2'
$env:BOOST_ROOT='C:\local\boost_1_83_0'
$env:BOOST_LIBRARYDIR='C:\local\boost_1_83_0\lib64-msvc-14.2'
```
#### Icinga 2 in Visual Studio

View File

@ -13,8 +13,8 @@ function ThrowOnNativeFailure {
$VsVersion = 2019
$MsvcVersion = '14.2'
$BoostVersion = @(1, 82, 0)
$OpensslVersion = '3_0_9'
$BoostVersion = @(1, 83, 0)
$OpensslVersion = '3_0_12'
switch ($Env:BITS) {
32 { }
@ -91,6 +91,8 @@ if (-not $Env:GITHUB_ACTIONS) {
ThrowOnNativeFailure
}
# Disable the progress bar for downloads from the Web, which will speed up the entire download process
$Global:ProgressPreference = 'SilentlyContinue';
Install-Exe -Url "https://packages.icinga.com/windows/dependencies/boost_$($BoostVersion -join '_')-msvc-${MsvcVersion}-${Env:BITS}.exe" -Dir "C:\local\boost_$($BoostVersion -join '_')-Win${Env:BITS}"

View File

@ -67,6 +67,20 @@ bool Dictionary::Get(const String& key, Value *result) const
return true;
}
/**
* Retrieves a value's address from a dictionary.
*
* @param key The key whose value's address should be retrieved.
* @returns nullptr if the key was not found.
*/
const Value * Dictionary::GetRef(const String& key) const
{
std::shared_lock<std::shared_timed_mutex> lock (m_DataMutex);
auto it (m_Data.find(key));
return it == m_Data.end() ? nullptr : &it->second;
}
/**
* Sets a value in the dictionary.
*

View File

@ -42,6 +42,7 @@ public:
Value Get(const String& key) const;
bool Get(const String& key, Value *result) const;
const Value * GetRef(const String& key) const;
void Set(const String& key, Value value, bool overrideFrozen = false);
bool Contains(const String& key) const;

View File

@ -11,6 +11,8 @@
#include <boost/asio/ssl/context.hpp>
#include <openssl/opensslv.h>
#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/ssl3.h>
#include <fstream>
namespace icinga
@ -91,6 +93,16 @@ static void InitSslContext(const Shared<boost::asio::ssl::context>::Ptr& context
flags |= SSL_OP_CIPHER_SERVER_PREFERENCE;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
SSL_CTX_set_info_callback(sslContext, [](const SSL* ssl, int where, int) {
if (where & SSL_CB_HANDSHAKE_DONE) {
ssl->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS;
}
});
#else /* OPENSSL_VERSION_NUMBER < 0x10100000L */
flags |= SSL_OP_NO_RENEGOTIATION;
#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
SSL_CTX_set_options(sslContext, flags);
SSL_CTX_set_mode(sslContext, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
@ -714,7 +726,7 @@ String GetIcingaCADir()
return Configuration::DataDir + "/ca";
}
std::shared_ptr<X509> CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject)
std::shared_ptr<X509> CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject, bool ca)
{
char errbuf[256];
@ -751,7 +763,7 @@ std::shared_ptr<X509> CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject)
EVP_PKEY *privkey = EVP_PKEY_new();
EVP_PKEY_assign_RSA(privkey, rsa);
return CreateCert(pubkey, subject, X509_get_subject_name(cacert.get()), privkey, false);
return CreateCert(pubkey, subject, X509_get_subject_name(cacert.get()), privkey, ca);
}
std::shared_ptr<X509> CreateCertIcingaCA(const std::shared_ptr<X509>& cert)
@ -760,24 +772,37 @@ std::shared_ptr<X509> CreateCertIcingaCA(const std::shared_ptr<X509>& cert)
return CreateCertIcingaCA(pkey.get(), X509_get_subject_name(cert.get()));
}
static inline
bool CertExpiresWithin(X509* cert, int seconds)
{
time_t renewalStart = time(nullptr) + seconds;
return X509_cmp_time(X509_get_notAfter(cert), &renewalStart) < 0;
}
bool IsCertUptodate(const std::shared_ptr<X509>& cert)
{
time_t now;
time(&now);
if (CertExpiresWithin(cert.get(), RENEW_THRESHOLD)) {
return false;
}
/* auto-renew all certificates which were created before 2017 to force an update of the CA,
* because Icinga versions older than 2.4 sometimes create certificates with an invalid
* serial number. */
time_t forceRenewalEnd = 1483228800; /* January 1st, 2017 */
time_t renewalStart = now + RENEW_THRESHOLD;
return X509_cmp_time(X509_get_notBefore(cert.get()), &forceRenewalEnd) != -1 && X509_cmp_time(X509_get_notAfter(cert.get()), &renewalStart) != -1;
return X509_cmp_time(X509_get_notBefore(cert.get()), &forceRenewalEnd) >= 0;
}
String CertificateToString(const std::shared_ptr<X509>& cert)
bool IsCaUptodate(X509* cert)
{
return !CertExpiresWithin(cert, LEAF_VALID_FOR);
}
String CertificateToString(X509* cert)
{
BIO *mem = BIO_new(BIO_s_mem());
PEM_write_bio_X509(mem, cert.get());
PEM_write_bio_X509(mem, cert);
char *data;
long len = BIO_get_mem_data(mem, &data);

View File

@ -58,12 +58,18 @@ int MakeX509CSR(const String& cn, const String& keyfile, const String& csrfile =
std::shared_ptr<X509> CreateCert(EVP_PKEY *pubkey, X509_NAME *subject, X509_NAME *issuer, EVP_PKEY *cakey, bool ca);
String GetIcingaCADir();
String CertificateToString(const std::shared_ptr<X509>& cert);
String CertificateToString(X509* cert);
inline String CertificateToString(const std::shared_ptr<X509>& cert)
{
return CertificateToString(cert.get());
}
std::shared_ptr<X509> StringToCertificate(const String& cert);
std::shared_ptr<X509> CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject);
std::shared_ptr<X509> CreateCertIcingaCA(EVP_PKEY *pubkey, X509_NAME *subject, bool ca = false);
std::shared_ptr<X509> CreateCertIcingaCA(const std::shared_ptr<X509>& cert);
bool IsCertUptodate(const std::shared_ptr<X509>& cert);
bool IsCaUptodate(X509* cert);
String PBKDF2_SHA1(const String& password, const String& salt, int iterations);
String PBKDF2_SHA256(const String& password, const String& salt, int iterations);

View File

@ -96,16 +96,16 @@ bool ApplyRule::AddTargetedRule(const ApplyRule::Ptr& rule, const String& target
*
* @returns Whether the given assign filter is like above.
*/
bool ApplyRule::GetTargetHosts(Expression* assignFilter, std::vector<const String *>& hosts)
bool ApplyRule::GetTargetHosts(Expression* assignFilter, std::vector<const String *>& hosts, const Dictionary::Ptr& constants)
{
auto lor (dynamic_cast<LogicalOrExpression*>(assignFilter));
if (lor) {
return GetTargetHosts(lor->GetOperand1().get(), hosts)
&& GetTargetHosts(lor->GetOperand2().get(), hosts);
return GetTargetHosts(lor->GetOperand1().get(), hosts, constants)
&& GetTargetHosts(lor->GetOperand2().get(), hosts, constants);
}
auto name (GetComparedName(assignFilter, "host"));
auto name (GetComparedName(assignFilter, "host", constants));
if (name) {
hosts.emplace_back(name);
@ -124,16 +124,16 @@ bool ApplyRule::GetTargetHosts(Expression* assignFilter, std::vector<const Strin
*
* @returns Whether the given assign filter is like above.
*/
bool ApplyRule::GetTargetServices(Expression* assignFilter, std::vector<std::pair<const String *, const String *>>& services)
bool ApplyRule::GetTargetServices(Expression* assignFilter, std::vector<std::pair<const String *, const String *>>& services, const Dictionary::Ptr& constants)
{
auto lor (dynamic_cast<LogicalOrExpression*>(assignFilter));
if (lor) {
return GetTargetServices(lor->GetOperand1().get(), services)
&& GetTargetServices(lor->GetOperand2().get(), services);
return GetTargetServices(lor->GetOperand1().get(), services, constants)
&& GetTargetServices(lor->GetOperand2().get(), services, constants);
}
auto service (GetTargetService(assignFilter));
auto service (GetTargetService(assignFilter, constants));
if (service.first) {
services.emplace_back(service);
@ -152,7 +152,7 @@ bool ApplyRule::GetTargetServices(Expression* assignFilter, std::vector<std::pai
*
* @returns {host, service} on success and {nullptr, nullptr} on failure.
*/
std::pair<const String *, const String *> ApplyRule::GetTargetService(Expression* assignFilter)
std::pair<const String *, const String *> ApplyRule::GetTargetService(Expression* assignFilter, const Dictionary::Ptr& constants)
{
auto land (dynamic_cast<LogicalAndExpression*>(assignFilter));
@ -162,15 +162,15 @@ std::pair<const String *, const String *> ApplyRule::GetTargetService(Expression
auto op1 (land->GetOperand1().get());
auto op2 (land->GetOperand2().get());
auto host (GetComparedName(op1, "host"));
auto host (GetComparedName(op1, "host", constants));
if (!host) {
std::swap(op1, op2);
host = GetComparedName(op1, "host");
host = GetComparedName(op1, "host", constants);
}
if (host) {
auto service (GetComparedName(op2, "service"));
auto service (GetComparedName(op2, "service", constants));
if (service) {
return {host, service};
@ -189,7 +189,7 @@ std::pair<const String *, const String *> ApplyRule::GetTargetService(Expression
*
* @returns The object name on success and nullptr on failure.
*/
const String * ApplyRule::GetComparedName(Expression* assignFilter, const char * lcType)
const String * ApplyRule::GetComparedName(Expression* assignFilter, const char * lcType, const Dictionary::Ptr& constants)
{
auto eq (dynamic_cast<EqualExpression*>(assignFilter));
@ -200,12 +200,12 @@ const String * ApplyRule::GetComparedName(Expression* assignFilter, const char *
auto op1 (eq->GetOperand1().get());
auto op2 (eq->GetOperand2().get());
if (IsNameIndexer(op1, lcType)) {
return GetLiteralStringValue(op2);
if (IsNameIndexer(op1, lcType, constants)) {
return GetConstString(op2, constants);
}
if (IsNameIndexer(op2, lcType)) {
return GetLiteralStringValue(op1);
if (IsNameIndexer(op2, lcType, constants)) {
return GetConstString(op1, constants);
}
return nullptr;
@ -214,7 +214,7 @@ const String * ApplyRule::GetComparedName(Expression* assignFilter, const char *
/**
* @returns Whether the given expression is like $lcType$.name.
*/
bool ApplyRule::IsNameIndexer(Expression* exp, const char * lcType)
bool ApplyRule::IsNameIndexer(Expression* exp, const char * lcType, const Dictionary::Ptr& constants)
{
auto ixr (dynamic_cast<IndexerExpression*>(exp));
@ -228,27 +228,39 @@ bool ApplyRule::IsNameIndexer(Expression* exp, const char * lcType)
return false;
}
auto val (GetLiteralStringValue(ixr->GetOperand2().get()));
auto val (GetConstString(ixr->GetOperand2().get(), constants));
return val && *val == "name";
}
/**
* @returns If the given expression is a string literal, the string. nullptr on failure.
* @returns If the given expression is a constant string, its address. nullptr on failure.
*/
const String * ApplyRule::GetLiteralStringValue(Expression* exp)
const String * ApplyRule::GetConstString(Expression* exp, const Dictionary::Ptr& constants)
{
auto cnst (GetConst(exp, constants));
return cnst && cnst->IsString() ? &cnst->Get<String>() : nullptr;
}
/**
* @returns If the given expression is a constant, its address. nullptr on failure.
*/
const Value * ApplyRule::GetConst(Expression* exp, const Dictionary::Ptr& constants)
{
auto lit (dynamic_cast<LiteralExpression*>(exp));
if (!lit) {
return nullptr;
if (lit) {
return &lit->GetValue();
}
auto& val (lit->GetValue());
if (constants) {
auto var (dynamic_cast<VariableExpression*>(exp));
if (!val.IsString()) {
return nullptr;
if (var) {
return constants->GetRef(var->GetVariable());
}
}
return &val.Get<String>();
return nullptr;
}

View File

@ -82,6 +82,8 @@ public:
static const std::vector<ApplyRule::Ptr>& GetRules(const Type::Ptr& sourceType, const Type::Ptr& targetType);
static const std::set<ApplyRule::Ptr>& GetTargetedHostRules(const Type::Ptr& sourceType, const String& host);
static const std::set<ApplyRule::Ptr>& GetTargetedServiceRules(const Type::Ptr& sourceType, const String& host, const String& service);
static bool GetTargetHosts(Expression* assignFilter, std::vector<const String *>& hosts, const Dictionary::Ptr& constants = nullptr);
static bool GetTargetServices(Expression* assignFilter, std::vector<std::pair<const String *, const String *>>& services, const Dictionary::Ptr& constants = nullptr);
static void RegisterType(const String& sourceType, const std::vector<String>& targetTypes);
static bool IsValidSourceType(const String& sourceType);
@ -108,12 +110,11 @@ private:
static RuleMap m_Rules;
static bool AddTargetedRule(const ApplyRule::Ptr& rule, const String& targetType, PerSourceType& rules);
static bool GetTargetHosts(Expression* assignFilter, std::vector<const String *>& hosts);
static bool GetTargetServices(Expression* assignFilter, std::vector<std::pair<const String *, const String *>>& services);
static std::pair<const String *, const String *> GetTargetService(Expression* assignFilter);
static const String * GetComparedName(Expression* assignFilter, const char * lcType);
static bool IsNameIndexer(Expression* exp, const char * lcType);
static const String * GetLiteralStringValue(Expression* exp);
static std::pair<const String *, const String *> GetTargetService(Expression* assignFilter, const Dictionary::Ptr& constants);
static const String * GetComparedName(Expression* assignFilter, const char * lcType, const Dictionary::Ptr& constants);
static bool IsNameIndexer(Expression* exp, const char * lcType, const Dictionary::Ptr& constants);
static const String * GetConstString(Expression* exp, const Dictionary::Ptr& constants);
static const Value * GetConst(Expression* exp, const Dictionary::Ptr& constants);
ApplyRule(String name, Expression::Ptr expression,
Expression::Ptr filter, String package, String fkvar, String fvvar, Expression::Ptr fterm,

View File

@ -622,6 +622,11 @@ public:
void MakeInline();
inline const std::vector<std::unique_ptr<Expression>>& GetExpressions() const noexcept
{
return m_Expressions;
}
protected:
ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override;

View File

@ -29,6 +29,8 @@ REGISTER_APIFUNCTION(SetStateBeforeSuppression, event, &ClusterEvents::StateBefo
REGISTER_APIFUNCTION(SetSuppressedNotifications, event, &ClusterEvents::SuppressedNotificationsChangedAPIHandler);
REGISTER_APIFUNCTION(SetSuppressedNotificationTypes, event, &ClusterEvents::SuppressedNotificationTypesChangedAPIHandler);
REGISTER_APIFUNCTION(SetNextNotification, event, &ClusterEvents::NextNotificationChangedAPIHandler);
REGISTER_APIFUNCTION(UpdateLastNotifiedStatePerUser, event, &ClusterEvents::LastNotifiedStatePerUserUpdatedAPIHandler);
REGISTER_APIFUNCTION(ClearLastNotifiedStatePerUser, event, &ClusterEvents::LastNotifiedStatePerUserClearedAPIHandler);
REGISTER_APIFUNCTION(SetForceNextCheck, event, &ClusterEvents::ForceNextCheckChangedAPIHandler);
REGISTER_APIFUNCTION(SetForceNextNotification, event, &ClusterEvents::ForceNextNotificationChangedAPIHandler);
REGISTER_APIFUNCTION(SetAcknowledgement, event, &ClusterEvents::AcknowledgementSetAPIHandler);
@ -50,6 +52,8 @@ void ClusterEvents::StaticInitialize()
Checkable::OnSuppressedNotificationsChanged.connect(&ClusterEvents::SuppressedNotificationsChangedHandler);
Notification::OnSuppressedNotificationsChanged.connect(&ClusterEvents::SuppressedNotificationTypesChangedHandler);
Notification::OnNextNotificationChanged.connect(&ClusterEvents::NextNotificationChangedHandler);
Notification::OnLastNotifiedStatePerUserUpdated.connect(&ClusterEvents::LastNotifiedStatePerUserUpdatedHandler);
Notification::OnLastNotifiedStatePerUserCleared.connect(&ClusterEvents::LastNotifiedStatePerUserClearedHandler);
Checkable::OnForceNextCheckChanged.connect(&ClusterEvents::ForceNextCheckChangedHandler);
Checkable::OnForceNextNotificationChanged.connect(&ClusterEvents::ForceNextNotificationChangedHandler);
Checkable::OnNotificationsRequested.connect(&ClusterEvents::SendNotificationsHandler);
@ -529,6 +533,115 @@ Value ClusterEvents::NextNotificationChangedAPIHandler(const MessageOrigin::Ptr&
return Empty;
}
void ClusterEvents::LastNotifiedStatePerUserUpdatedHandler(const Notification::Ptr& notification, const String& user, uint_fast8_t state, const MessageOrigin::Ptr& origin)
{
auto listener (ApiListener::GetInstance());
if (!listener) {
return;
}
Dictionary::Ptr params = new Dictionary();
params->Set("notification", notification->GetName());
params->Set("user", user);
params->Set("state", state);
Dictionary::Ptr message = new Dictionary();
message->Set("jsonrpc", "2.0");
message->Set("method", "event::UpdateLastNotifiedStatePerUser");
message->Set("params", params);
listener->RelayMessage(origin, notification, message, true);
}
Value ClusterEvents::LastNotifiedStatePerUserUpdatedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
{
auto endpoint (origin->FromClient->GetEndpoint());
if (!endpoint) {
Log(LogNotice, "ClusterEvents")
<< "Discarding 'last notified state of user updated' message from '" << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
return Empty;
}
if (origin->FromZone && origin->FromZone != Zone::GetLocalZone()) {
Log(LogNotice, "ClusterEvents")
<< "Discarding 'last notified state of user updated' message from '"
<< origin->FromClient->GetIdentity() << "': Unauthorized access.";
return Empty;
}
auto notification (Notification::GetByName(params->Get("notification")));
if (!notification) {
return Empty;
}
auto state (params->Get("state"));
if (!state.IsNumber()) {
return Empty;
}
notification->GetLastNotifiedStatePerUser()->Set(params->Get("user"), state);
Notification::OnLastNotifiedStatePerUserUpdated(notification, params->Get("user"), state, origin);
return Empty;
}
void ClusterEvents::LastNotifiedStatePerUserClearedHandler(const Notification::Ptr& notification, const MessageOrigin::Ptr& origin)
{
auto listener (ApiListener::GetInstance());
if (!listener) {
return;
}
Dictionary::Ptr params = new Dictionary();
params->Set("notification", notification->GetName());
Dictionary::Ptr message = new Dictionary();
message->Set("jsonrpc", "2.0");
message->Set("method", "event::ClearLastNotifiedStatePerUser");
message->Set("params", params);
listener->RelayMessage(origin, notification, message, true);
}
Value ClusterEvents::LastNotifiedStatePerUserClearedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params)
{
auto endpoint (origin->FromClient->GetEndpoint());
if (!endpoint) {
Log(LogNotice, "ClusterEvents")
<< "Discarding 'last notified state of user cleared' message from '"
<< origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed).";
return Empty;
}
if (origin->FromZone && origin->FromZone != Zone::GetLocalZone()) {
Log(LogNotice, "ClusterEvents")
<< "Discarding 'last notified state of user cleared' message from '"
<< origin->FromClient->GetIdentity() << "': Unauthorized access.";
return Empty;
}
auto notification (Notification::GetByName(params->Get("notification")));
if (!notification) {
return Empty;
}
notification->GetLastNotifiedStatePerUser()->Clear();
Notification::OnLastNotifiedStatePerUserCleared(notification, origin);
return Empty;
}
void ClusterEvents::ForceNextCheckChangedHandler(const Checkable::Ptr& checkable, const MessageOrigin::Ptr& origin)
{
ApiListener::Ptr listener = ApiListener::GetInstance();

View File

@ -41,6 +41,12 @@ public:
static void NextNotificationChangedHandler(const Notification::Ptr& notification, const MessageOrigin::Ptr& origin);
static Value NextNotificationChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
static void LastNotifiedStatePerUserUpdatedHandler(const Notification::Ptr& notification, const String& user, uint_fast8_t state, const MessageOrigin::Ptr& origin);
static Value LastNotifiedStatePerUserUpdatedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
static void LastNotifiedStatePerUserClearedHandler(const Notification::Ptr& notification, const MessageOrigin::Ptr& origin);
static Value LastNotifiedStatePerUserClearedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);
static void ForceNextCheckChangedHandler(const Checkable::Ptr& checkable, const MessageOrigin::Ptr& origin);
static Value ForceNextCheckChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params);

View File

@ -23,6 +23,8 @@ std::map<String, int> Notification::m_StateFilterMap;
std::map<String, int> Notification::m_TypeFilterMap;
boost::signals2::signal<void (const Notification::Ptr&, const MessageOrigin::Ptr&)> Notification::OnNextNotificationChanged;
boost::signals2::signal<void (const Notification::Ptr&, const String&, uint_fast8_t, const MessageOrigin::Ptr&)> Notification::OnLastNotifiedStatePerUserUpdated;
boost::signals2::signal<void (const Notification::Ptr&, const MessageOrigin::Ptr&)> Notification::OnLastNotifiedStatePerUserCleared;
String NotificationNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const
{
@ -231,6 +233,13 @@ void Notification::BeginExecuteNotification(NotificationType type, const CheckRe
<< "notifications of type '" << notificationTypeName
<< "' for notification object '" << notificationName << "'.";
if (type == NotificationRecovery) {
auto states (GetLastNotifiedStatePerUser());
states->Clear();
OnLastNotifiedStatePerUserCleared(this, nullptr);
}
Checkable::Ptr checkable = GetCheckable();
if (!force) {
@ -439,6 +448,22 @@ void Notification::BeginExecuteNotification(NotificationType type, const CheckRe
}
}
if (type == NotificationProblem && !reminder && !checkable->GetVolatile()) {
auto [host, service] = GetHostService(checkable);
uint_fast8_t state = service ? service->GetState() : host->GetState();
if (state == (uint_fast8_t)GetLastNotifiedStatePerUser()->Get(userName)) {
auto stateStr (service ? NotificationServiceStateToString(service->GetState()) : NotificationHostStateToString(host->GetState()));
Log(LogNotice, "Notification")
<< "Notification object '" << notificationName << "': We already notified user '" << userName << "' for a " << stateStr
<< " problem. Likely after that another state change notification was filtered out by config. Not sending duplicate '"
<< stateStr << "' notification.";
continue;
}
}
Log(LogInformation, "Notification")
<< "Sending " << (reminder ? "reminder " : "") << "'" << NotificationTypeToString(type) << "' notification '"
<< notificationName << "' for user '" << userName << "'";
@ -452,6 +477,16 @@ void Notification::BeginExecuteNotification(NotificationType type, const CheckRe
/* collect all notified users */
allNotifiedUsers.insert(user);
if (type == NotificationProblem) {
auto [host, service] = GetHostService(checkable);
uint_fast8_t state = service ? service->GetState() : host->GetState();
if (state != (uint_fast8_t)GetLastNotifiedStatePerUser()->Get(userName)) {
GetLastNotifiedStatePerUser()->Set(userName, state);
OnLastNotifiedStatePerUserUpdated(this, userName, state, nullptr);
}
}
/* store all notified users for later recovery checks */
if (type == NotificationProblem && !notifiedProblemUsers->Contains(userName))
notifiedProblemUsers->Add(userName);

View File

@ -13,6 +13,7 @@
#include "remote/endpoint.hpp"
#include "remote/messageorigin.hpp"
#include "base/array.hpp"
#include <cstdint>
namespace icinga
{
@ -92,6 +93,8 @@ public:
static String NotificationHostStateToString(HostState state);
static boost::signals2::signal<void (const Notification::Ptr&, const MessageOrigin::Ptr&)> OnNextNotificationChanged;
static boost::signals2::signal<void (const Notification::Ptr&, const String&, uint_fast8_t, const MessageOrigin::Ptr&)> OnLastNotifiedStatePerUserUpdated;
static boost::signals2::signal<void (const Notification::Ptr&, const MessageOrigin::Ptr&)> OnLastNotifiedStatePerUserCleared;
void Validate(int types, const ValidationUtils& utils) override;
@ -105,7 +108,6 @@ public:
static const std::map<String, int>& GetStateFilterMap();
static const std::map<String, int>& GetTypeFilterMap();
protected:
void OnConfigLoaded() override;
void OnAllConfigLoaded() override;
void Start(bool runtimeCreated) override;

View File

@ -90,6 +90,10 @@ class Notification : CustomVarObject < NotificationNameComposer
default {{{ return 0; }}}
};
[state, no_user_view, no_user_modify] Dictionary::Ptr last_notified_state_per_user {
default {{{ return new Dictionary(); }}}
};
[config, navigation] name(Endpoint) command_endpoint (CommandEndpointRaw) {
navigate {{{
return Endpoint::GetByName(GetCommandEndpointRaw());

View File

@ -1577,6 +1577,9 @@ IcingaDB::CreateConfigUpdate(const ConfigObject::Ptr& object, const String typeN
void IcingaDB::SendConfigDelete(const ConfigObject::Ptr& object)
{
if (!m_Rcon || !m_Rcon->IsConnected())
return;
Type::Ptr type = object->GetReflectionType();
String typeName = type->GetName().ToLower();
String objectKey = GetObjectIdentifier(object);
@ -1860,6 +1863,7 @@ void IcingaDB::SendStartedDowntime(const Downtime::Ptr& downtime)
"scheduled_end_time", Convert::ToString(TimestampToMilliseconds(downtime->GetEndTime())),
"has_been_cancelled", Convert::ToString((unsigned short)downtime->GetWasCancelled()),
"trigger_time", Convert::ToString(TimestampToMilliseconds(downtime->GetTriggerTime())),
"cancel_time", Convert::ToString(TimestampToMilliseconds(downtime->GetRemoveTime())),
"event_id", CalcEventID("downtime_start", downtime),
"event_type", "downtime_start"
});

View File

@ -12,6 +12,20 @@
#include "base/process.hpp"
#include "base/convert.hpp"
#ifdef __linux__
# include <linux/binfmts.h>
# include <unistd.h>
# ifndef PAGE_SIZE
// MAX_ARG_STRLEN is a multiple of PAGE_SIZE which is missing
# define PAGE_SIZE getpagesize()
# endif /* PAGE_SIZE */
// Make e.g. the $host.output$ itself even 10% shorter to leave enough room
// for e.g. --host-output= as in --host-output=$host.output$, but without int overflow
const static auto l_MaxOutLen = MAX_ARG_STRLEN - MAX_ARG_STRLEN / 10u;
#endif /* __linux__ */
using namespace icinga;
REGISTER_FUNCTION_NONCONST(Internal, PluginNotification, &PluginNotificationTask::ScriptFunc, "notification:user:cr:itype:author:comment:resolvedMacros:useResolvedMacros");
@ -33,7 +47,11 @@ void PluginNotificationTask::ScriptFunc(const Notification::Ptr& notification,
Dictionary::Ptr notificationExtra = new Dictionary({
{ "type", Notification::NotificationTypeToStringCompat(type) }, //TODO: Change that to our types.
{ "author", author },
#ifdef __linux__
{ "comment", comment.SubStr(0, l_MaxOutLen) }
#else /* __linux__ */
{ "comment", comment }
#endif /* __linux__ */
});
Host::Ptr host;
@ -48,8 +66,35 @@ void PluginNotificationTask::ScriptFunc(const Notification::Ptr& notification,
resolvers.emplace_back("user", user);
resolvers.emplace_back("notification", notificationExtra);
resolvers.emplace_back("notification", notification);
if (service)
if (service) {
#ifdef __linux__
auto cr (service->GetLastCheckResult());
if (cr) {
auto output (cr->GetOutput());
if (output.GetLength() > l_MaxOutLen) {
resolvers.emplace_back("service", new Dictionary({{"output", output.SubStr(0, l_MaxOutLen)}}));
}
}
#endif /* __linux__ */
resolvers.emplace_back("service", service);
}
#ifdef __linux__
auto hcr (host->GetLastCheckResult());
if (hcr) {
auto output (hcr->GetOutput());
if (output.GetLength() > l_MaxOutLen) {
resolvers.emplace_back("host", new Dictionary({{"output", output.SubStr(0, l_MaxOutLen)}}));
}
}
#endif /* __linux__ */
resolvers.emplace_back("host", host);
resolvers.emplace_back("command", commandObj);

View File

@ -114,18 +114,17 @@ void GelfWriter::Pause()
m_ReconnectTimer->Stop(true);
try {
ReconnectInternal();
} catch (const std::exception&) {
Log(LogInformation, "GelfWriter")
<< "'" << GetName() << "' paused. Unable to connect, not flushing buffers. Data may be lost on reload.";
ObjectImpl<GelfWriter>::Pause();
return;
}
m_WorkQueue.Enqueue([this]() {
try {
ReconnectInternal();
} catch (const std::exception&) {
Log(LogInformation, "GelfWriter")
<< "Unable to connect, not flushing buffers. Data may be lost.";
}
}, PriorityImmediate);
m_WorkQueue.Enqueue([this]() { DisconnectInternal(); }, PriorityLow);
m_WorkQueue.Join();
DisconnectInternal();
Log(LogInformation, "GelfWriter")
<< "'" << GetName() << "' paused.";
@ -513,8 +512,6 @@ void GelfWriter::SendLogMessage(const Checkable::Ptr& checkable, const String& g
String log = msgbuf.str();
ObjectLock olock(this);
if (!GetConnected())
return;

View File

@ -181,12 +181,12 @@ void ApiListener::OnConfigLoaded()
UpdateSSLContext();
}
std::shared_ptr<X509> ApiListener::RenewCert(const std::shared_ptr<X509>& cert)
std::shared_ptr<X509> ApiListener::RenewCert(const std::shared_ptr<X509>& cert, bool ca)
{
std::shared_ptr<EVP_PKEY> pubkey (X509_get_pubkey(cert.get()), EVP_PKEY_free);
auto subject (X509_get_subject_name(cert.get()));
auto cacert (GetX509Certificate(GetDefaultCaPath()));
auto newcert (CreateCertIcingaCA(pubkey.get(), subject));
auto newcert (CreateCertIcingaCA(pubkey.get(), subject, ca));
/* verify that the new cert matches the CA we're using for the ApiListener;
* this ensures that the CA we have in /var/lib/icinga2/ca matches the one
@ -248,7 +248,12 @@ void ApiListener::Start(bool runtimeCreated)
if (Utility::PathExists(GetIcingaCADir() + "/ca.key")) {
RenewOwnCert();
m_RenewOwnCertTimer->OnTimerExpired.connect([this](const Timer * const&) { RenewOwnCert(); });
RenewCA();
m_RenewOwnCertTimer->OnTimerExpired.connect([this](const Timer * const&) {
RenewOwnCert();
RenewCA();
});
} else {
m_RenewOwnCertTimer->OnTimerExpired.connect([this](const Timer * const&) {
JsonRpcConnection::SendCertificateRequest(nullptr, nullptr, String());
@ -329,6 +334,31 @@ void ApiListener::RenewOwnCert()
UpdateSSLContext();
}
void ApiListener::RenewCA()
{
auto certPath (GetCaDir() + "/ca.crt");
auto cert (GetX509Certificate(certPath));
if (IsCaUptodate(cert.get())) {
return;
}
Log(LogInformation, "ApiListener")
<< "Our CA will expire soon, but we own it. Renewing.";
cert = RenewCert(cert, true);
if (!cert) {
return;
}
auto certStr (CertificateToString(cert));
AtomicFile::Write(GetDefaultCaPath(), 0644, certStr);
AtomicFile::Write(certPath, 0644, certStr);
UpdateSSLContext();
}
void ApiListener::Stop(bool runtimeDeleted)
{
m_ApiPackageIntegrityTimer->Stop(true);

View File

@ -91,7 +91,7 @@ public:
static String GetCaDir();
static String GetCertificateRequestsDir();
std::shared_ptr<X509> RenewCert(const std::shared_ptr<X509>& cert);
std::shared_ptr<X509> RenewCert(const std::shared_ptr<X509>& cert, bool ca = false);
void UpdateSSLContext();
static ApiListener::Ptr GetInstance();
@ -227,6 +227,7 @@ private:
void SyncLocalZoneDirs() const;
void SyncLocalZoneDir(const Zone::Ptr& zone) const;
void RenewOwnCert();
void RenewCA();
void SendConfigUpdate(const JsonRpcConnection::Ptr& aclient);

View File

@ -1,5 +1,6 @@
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
#include "remote/configobjectslock.hpp"
#include "remote/consolehandler.hpp"
#include "remote/httputility.hpp"
#include "remote/filterutility.hpp"
@ -88,6 +89,13 @@ bool ConsoleHandler::HandleRequest(
bool sandboxed = HttpUtility::GetLastParameter(params, "sandboxed");
ConfigObjectsSharedLock lock (std::try_to_lock);
if (!lock) {
HttpUtility::SendJsonError(response, params, 503, "Icinga is reloading.");
return true;
}
if (methodName == "execute-script")
return ExecuteScriptHelper(request, response, params, command, session, sandboxed);
else if (methodName == "auto-complete-script")

View File

@ -2,6 +2,7 @@
#include "remote/filterutility.hpp"
#include "remote/httputility.hpp"
#include "config/applyrule.hpp"
#include "config/configcompiler.hpp"
#include "config/expression.hpp"
#include "base/namespace.hpp"
@ -271,18 +272,73 @@ std::vector<Value> FilterUtility::GetFilterTargets(const QueryDescription& qd, c
if (query->Contains("filter")) {
String filter = HttpUtility::GetLastParameter(query, "filter");
std::unique_ptr<Expression> ufilter = ConfigCompiler::CompileText("<API query>", filter);
Dictionary::Ptr filter_vars = query->Get("filter_vars");
if (filter_vars) {
ObjectLock olock(filter_vars);
for (const Dictionary::Pair& kv : filter_vars) {
frameNS->Set(kv.first, kv.second);
bool targeted = false;
std::vector<ConfigObject::Ptr> targets;
if (dynamic_cast<ConfigObjectTargetProvider*>(provider.get())) {
auto dict (dynamic_cast<DictExpression*>(ufilter.get()));
if (dict) {
auto& subex (dict->GetExpressions());
if (subex.size() == 1u) {
if (type == "Host") {
std::vector<const String *> targetNames;
if (ApplyRule::GetTargetHosts(subex.at(0).get(), targetNames, filter_vars)) {
static const auto typeHost (Type::GetByName("Host"));
static const auto ctypeHost (dynamic_cast<ConfigType*>(typeHost.get()));
targeted = true;
for (auto name : targetNames) {
auto target (ctypeHost->GetObject(*name));
if (target) {
targets.emplace_back(target);
}
}
}
} else if (type == "Service") {
std::vector<std::pair<const String *, const String *>> targetNames;
if (ApplyRule::GetTargetServices(subex.at(0).get(), targetNames, filter_vars)) {
static const auto typeService (Type::GetByName("Service"));
static const auto ctypeService (dynamic_cast<ConfigType*>(typeService.get()));
targeted = true;
for (auto name : targetNames) {
auto target (ctypeService->GetObject(*name.first + "!" + *name.second));
if (target) {
targets.emplace_back(target);
}
}
}
}
}
}
}
provider->FindTargets(type, [&permissionFrame, &permissionFilter, &frame, &ufilter, &result, variableName](const Object::Ptr& target) {
FilteredAddTarget(permissionFrame, permissionFilter.get(), frame, &*ufilter, result, variableName, target);
});
if (targeted) {
for (auto& target : targets) {
if (FilterUtility::EvaluateFilter(permissionFrame, permissionFilter.get(), target, variableName)) {
result.emplace_back(std::move(target));
}
}
} else {
if (filter_vars) {
ObjectLock olock (filter_vars);
for (auto& kv : filter_vars) {
frameNS->Set(kv.first, kv.second);
}
}
provider->FindTargets(type, [&permissionFrame, &permissionFilter, &frame, &ufilter, &result, variableName](const Object::Ptr& target) {
FilteredAddTarget(permissionFrame, permissionFilter.get(), frame, &*ufilter, result, variableName, target);
});
}
} else {
/* Ensure to pass a nullptr as filter expression.
* GCC 8.1.1 on F28 causes problems, see GH #6533.

View File

@ -14,6 +14,7 @@
#include <boost/thread/once.hpp>
#include <boost/regex.hpp>
#include <fstream>
#include <openssl/asn1.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
@ -31,11 +32,11 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona
std::shared_ptr<X509> cert;
Dictionary::Ptr result = new Dictionary();
auto& tlsConn (origin->FromClient->GetStream()->next_layer());
/* Use the presented client certificate if not provided. */
if (certText.IsEmpty()) {
auto stream (origin->FromClient->GetStream());
cert = stream->next_layer().GetPeerCertificate();
cert = tlsConn.GetPeerCertificate();
} else {
cert = StringToCertificate(certText);
}
@ -77,13 +78,54 @@ Value RequestCertificateHandler(const MessageOrigin::Ptr& origin, const Dictiona
}
}
if (signedByCA) {
if (IsCertUptodate(cert)) {
std::shared_ptr<X509> parsedRequestorCA;
X509* requestorCA = nullptr;
if (signedByCA) {
bool uptodate = IsCertUptodate(cert);
if (uptodate) {
// Even if the leaf is up-to-date, the root may expire soon.
// In a regular setup where Icinga manages the PKI, there is only one CA.
// Icinga includes it in handshakes, let's see whether the peer needs a fresh one...
if (cn == origin->FromClient->GetIdentity()) {
auto chain (SSL_get_peer_cert_chain(tlsConn.native_handle()));
if (chain) {
auto len (sk_X509_num(chain));
for (int i = 0; i < len; ++i) {
auto link (sk_X509_value(chain, i));
if (!X509_NAME_cmp(X509_get_subject_name(link), X509_get_issuer_name(link))) {
requestorCA = link;
}
}
}
} else {
Value requestorCaStr;
if (params->Get("requestor_ca", &requestorCaStr)) {
parsedRequestorCA = StringToCertificate(requestorCaStr);
requestorCA = parsedRequestorCA.get();
}
}
if (requestorCA && !IsCaUptodate(requestorCA)) {
int days;
if (ASN1_TIME_diff(&days, nullptr, X509_get_notAfter(requestorCA), X509_get_notAfter(cacert.get())) && days > 0) {
uptodate = false;
}
}
}
if (uptodate) {
Log(LogInformation, "JsonRpcConnection")
<< "The certificate for CN '" << cn << "' is valid and uptodate. Skipping automated renewal.";
<< "The certificates for CN '" << cn << "' and its root CA are valid and uptodate. Skipping automated renewal.";
result->Set("status_code", 1);
result->Set("error", "The certificate for CN '" + cn + "' is valid and uptodate. Skipping automated renewal.");
result->Set("error", "The certificates for CN '" + cn + "' and its root CA are valid and uptodate. Skipping automated renewal.");
return result;
}
}
@ -230,6 +272,10 @@ delayed_request:
{ "ticket", params->Get("ticket") }
});
if (requestorCA) {
request->Set("requestor_ca", CertificateToString(requestorCA));
}
Utility::SaveJsonFile(requestPath, 0600, request);
JsonRpcConnection::SendCertificateRequest(nullptr, origin, requestPath);
@ -291,8 +337,7 @@ void JsonRpcConnection::SendCertificateRequest(const JsonRpcConnection::Ptr& acl
if (request->Contains("cert_response"))
return;
params->Set("cert_request", request->Get("cert_request"));
params->Set("ticket", request->Get("ticket"));
request->CopyTo(params);
}
/* Send the request to a) the connected client

View File

@ -24,6 +24,7 @@ set(base_test_SOURCES
base-type.cpp
base-utility.cpp
base-value.cpp
config-apply.cpp
config-ops.cpp
icinga-checkresult.cpp
icinga-dependencies.cpp
@ -31,12 +32,14 @@ set(base_test_SOURCES
icinga-macros.cpp
icinga-notification.cpp
icinga-perfdata.cpp
methods-pluginnotificationtask.cpp
remote-configpackageutility.cpp
remote-url.cpp
${base_OBJS}
$<TARGET_OBJECTS:config>
$<TARGET_OBJECTS:remote>
$<TARGET_OBJECTS:icinga>
$<TARGET_OBJECTS:methods>
)
if(ICINGA2_UNITY_BUILD)
@ -109,6 +112,11 @@ add_boost_test(base
base_timer/invoke
base_timer/scope
base_tlsutility/sha1
base_tlsutility/iscauptodate_ok
base_tlsutility/iscauptodate_expiring
base_tlsutility/iscertuptodate_ok
base_tlsutility/iscertuptodate_expiring
base_tlsutility/iscertuptodate_old
base_type/gettype
base_type/assign
base_type/byname
@ -123,6 +131,38 @@ add_boost_test(base
base_value/scalar
base_value/convert
base_value/format
config_apply/gettargethosts_literal
config_apply/gettargethosts_const
config_apply/gettargethosts_swapped
config_apply/gettargethosts_two
config_apply/gettargethosts_three
config_apply/gettargethosts_mixed
config_apply/gettargethosts_redundant
config_apply/gettargethosts_badconst
config_apply/gettargethosts_notliteral
config_apply/gettargethosts_wrongop
config_apply/gettargethosts_wrongattr
config_apply/gettargethosts_wrongvar
config_apply/gettargethosts_noindexer
config_apply/gettargetservices_literal
config_apply/gettargetservices_const
config_apply/gettargetservices_swapped_outer
config_apply/gettargetservices_swapped_inner
config_apply/gettargetservices_two
config_apply/gettargetservices_three
config_apply/gettargetservices_mixed
config_apply/gettargetservices_redundant
config_apply/gettargetservices_badconst
config_apply/gettargetservices_notliteral
config_apply/gettargetservices_wrongop_outer
config_apply/gettargetservices_wrongop_host
config_apply/gettargetservices_wrongop_service
config_apply/gettargetservices_wrongattr_host
config_apply/gettargetservices_wrongattr_service
config_apply/gettargetservices_wrongvar_host
config_apply/gettargetservices_wrongvar_service
config_apply/gettargetservices_noindexer_host
config_apply/gettargetservices_noindexer_service
config_ops/simple
config_ops/advanced
icinga_checkresult/host_1attempt
@ -138,6 +178,11 @@ add_boost_test(base
icinga_notification/strings
icinga_notification/state_filter
icinga_notification/type_filter
icinga_notification/no_filter_problem_no_duplicate
icinga_notification/filter_problem_no_duplicate
icinga_notification/volatile_filter_problem_duplicate
icinga_notification/no_recovery_filter_no_duplicate
icinga_notification/recovery_filter_duplicate
icinga_macros/simple
icinga_legacytimeperiod/simple
icinga_legacytimeperiod/advanced
@ -156,6 +201,7 @@ add_boost_test(base
icinga_perfdata/multi
icinga_perfdata/scientificnotation
icinga_perfdata/parse_edgecases
methods_pluginnotificationtask/truncate_long_output
remote_configpackageutility/ValidateName
remote_url/id_and_path
remote_url/parameters

View File

@ -2,11 +2,61 @@
#include "base/tlsutility.hpp"
#include <BoostTestTargetConfig.h>
#include <functional>
#include <memory>
#include <openssl/asn1.h>
#include <openssl/bn.h>
#include <openssl/evp.h>
#include <openssl/obj_mac.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <utility>
#include <vector>
using namespace icinga;
static EVP_PKEY* GenKeypair()
{
InitializeOpenSSL();
auto e (BN_new());
BOOST_REQUIRE(e);
auto rsa (RSA_new());
BOOST_REQUIRE(rsa);
auto key (EVP_PKEY_new());
BOOST_REQUIRE(key);
BOOST_REQUIRE(BN_set_word(e, RSA_F4));
BOOST_REQUIRE(RSA_generate_key_ex(rsa, 4096, e, nullptr));
BOOST_REQUIRE(EVP_PKEY_assign_RSA(key, rsa));
return key;
}
static std::shared_ptr<X509> MakeCert(const char* issuer, EVP_PKEY* signer, const char* subject, EVP_PKEY* pubkey, std::function<void(ASN1_TIME*, ASN1_TIME*)> setTimes)
{
auto cert (X509_new());
BOOST_REQUIRE(cert);
auto serial (BN_new());
BOOST_REQUIRE(serial);
BOOST_REQUIRE(X509_set_version(cert, 0x2));
BOOST_REQUIRE(BN_to_ASN1_INTEGER(serial, X509_get_serialNumber(cert)));
BOOST_REQUIRE(X509_NAME_add_entry_by_NID(X509_get_issuer_name(cert), NID_commonName, MBSTRING_ASC, (unsigned char*)issuer, -1, -1, 0));
setTimes(X509_get_notBefore(cert), X509_get_notAfter(cert));
BOOST_REQUIRE(X509_NAME_add_entry_by_NID(X509_get_subject_name(cert), NID_commonName, MBSTRING_ASC, (unsigned char*)subject, -1, -1, 0));
BOOST_REQUIRE(X509_set_pubkey(cert, pubkey));
BOOST_REQUIRE(X509_sign(cert, signer, EVP_sha256()));
return std::shared_ptr<X509>(cert, X509_free);
}
static const long l_2016 = 1480000000; // Thu Nov 24 15:06:40 UTC 2016
static const long l_2017 = 1490000000; // Mon Mar 20 08:53:20 UTC 2017
BOOST_AUTO_TEST_SUITE(base_tlsutility)
BOOST_AUTO_TEST_CASE(sha1)
@ -35,4 +85,51 @@ BOOST_AUTO_TEST_CASE(sha1)
}
}
BOOST_AUTO_TEST_CASE(iscauptodate_ok)
{
auto key (GenKeypair());
BOOST_CHECK(IsCaUptodate(MakeCert("Icinga CA", key, "Icinga CA", key, [](ASN1_TIME* notBefore, ASN1_TIME* notAfter) {
BOOST_REQUIRE(X509_gmtime_adj(notBefore, 0));
BOOST_REQUIRE(X509_gmtime_adj(notAfter, LEAF_VALID_FOR + 60 * 60));
}).get()));
}
BOOST_AUTO_TEST_CASE(iscauptodate_expiring)
{
auto key (GenKeypair());
BOOST_CHECK(!IsCaUptodate(MakeCert("Icinga CA", key, "Icinga CA", key, [](ASN1_TIME* notBefore, ASN1_TIME* notAfter) {
BOOST_REQUIRE(X509_gmtime_adj(notBefore, 0));
BOOST_REQUIRE(X509_gmtime_adj(notAfter, LEAF_VALID_FOR - 60 * 60));
}).get()));
}
BOOST_AUTO_TEST_CASE(iscertuptodate_ok)
{
BOOST_CHECK(IsCertUptodate(MakeCert("Icinga CA", GenKeypair(), "example.com", GenKeypair(), [](ASN1_TIME* notBefore, ASN1_TIME* notAfter) {
time_t epoch = 0;
BOOST_REQUIRE(X509_time_adj(notBefore, l_2017, &epoch));
BOOST_REQUIRE(X509_gmtime_adj(notAfter, RENEW_THRESHOLD + 60 * 60));
})));
}
BOOST_AUTO_TEST_CASE(iscertuptodate_expiring)
{
BOOST_CHECK(!IsCertUptodate(MakeCert("Icinga CA", GenKeypair(), "example.com", GenKeypair(), [](ASN1_TIME* notBefore, ASN1_TIME* notAfter) {
time_t epoch = 0;
BOOST_REQUIRE(X509_time_adj(notBefore, l_2017, &epoch));
BOOST_REQUIRE(X509_gmtime_adj(notAfter, RENEW_THRESHOLD - 60 * 60));
})));
}
BOOST_AUTO_TEST_CASE(iscertuptodate_old)
{
BOOST_CHECK(!IsCertUptodate(MakeCert("Icinga CA", GenKeypair(), "example.com", GenKeypair(), [](ASN1_TIME* notBefore, ASN1_TIME* notAfter) {
time_t epoch = 0;
BOOST_REQUIRE(X509_time_adj(notBefore, l_2016, &epoch));
BOOST_REQUIRE(X509_gmtime_adj(notAfter, RENEW_THRESHOLD + 60 * 60));
})));
}
BOOST_AUTO_TEST_SUITE_END()

251
test/config-apply.cpp Normal file
View File

@ -0,0 +1,251 @@
/* Icinga 2 | (c) 2023 Icinga GmbH | GPLv2+ */
#include "config/applyrule.hpp"
#include "config/configcompiler.hpp"
#include <BoostTestTargetConfig.h>
using namespace icinga;
static Expression* RequireActualExpression(const std::unique_ptr<Expression>& compiledExpression)
{
BOOST_REQUIRE_NE(compiledExpression.get(), nullptr);
auto dict (dynamic_cast<DictExpression*>(compiledExpression.get()));
BOOST_REQUIRE_NE(dict, nullptr);
auto& subex (dict->GetExpressions());
BOOST_REQUIRE_EQUAL(subex.size(), 1u);
auto sub0 (subex.at(0).get());
BOOST_REQUIRE_NE(sub0, nullptr);
return sub0;
}
template<>
struct boost::test_tools::tt_detail::print_log_value<std::pair<String, String>>
{
inline void operator()(std::ostream& os, const std::pair<String, String>& hs)
{
os << hs.first << "!" << hs.second;
}
};
static void GetTargetHostsHelper(
const String& filter, const Dictionary::Ptr& constants, bool targeted, const std::vector<String>& hosts = {}
)
{
auto compiled (ConfigCompiler::CompileText("<test>", filter));
auto expr (RequireActualExpression(compiled));
std::vector<const String*> actualHosts;
BOOST_CHECK_EQUAL(ApplyRule::GetTargetHosts(expr, actualHosts, constants), targeted);
if (targeted) {
std::vector<String> actualHostNames;
actualHostNames.reserve(actualHosts.size());
for (auto h : actualHosts) {
actualHostNames.emplace_back(*h);
}
BOOST_CHECK_EQUAL_COLLECTIONS(actualHostNames.begin(), actualHostNames.end(), hosts.begin(), hosts.end());
}
}
static void GetTargetServicesHelper(
const String& filter, const Dictionary::Ptr& constants, bool targeted, const std::vector<std::pair<String, String>>& services = {}
)
{
auto compiled (ConfigCompiler::CompileText("<test>", filter));
auto expr (RequireActualExpression(compiled));
std::vector<std::pair<const String*, const String*>> actualServices;
BOOST_CHECK_EQUAL(ApplyRule::GetTargetServices(expr, actualServices, constants), targeted);
if (targeted) {
std::vector<std::pair<String, String>> actualServiceNames;
actualServiceNames.reserve(actualServices.size());
for (auto s : actualServices) {
actualServiceNames.emplace_back(*s.first, *s.second);
}
BOOST_CHECK_EQUAL_COLLECTIONS(actualServiceNames.begin(), actualServiceNames.end(), services.begin(), services.end());
}
}
BOOST_AUTO_TEST_SUITE(config_apply)
BOOST_AUTO_TEST_CASE(gettargethosts_literal)
{
GetTargetHostsHelper("host.name == \"foo\"", nullptr, true, {"foo"});
}
BOOST_AUTO_TEST_CASE(gettargethosts_const)
{
GetTargetHostsHelper("host.name == x", new Dictionary({{"x", "foo"}}), true, {"foo"});
}
BOOST_AUTO_TEST_CASE(gettargethosts_swapped)
{
GetTargetHostsHelper("\"foo\" == host.name", nullptr, true, {"foo"});
}
BOOST_AUTO_TEST_CASE(gettargethosts_two)
{
GetTargetHostsHelper("host.name == \"foo\" || host.name == \"bar\"", nullptr, true, {"foo", "bar"});
}
BOOST_AUTO_TEST_CASE(gettargethosts_three)
{
GetTargetHostsHelper(
"host.name == \"foo\" || host.name == \"bar\" || host.name == \"foobar\"",
nullptr, true, {"foo", "bar", "foobar"}
);
}
BOOST_AUTO_TEST_CASE(gettargethosts_mixed)
{
GetTargetHostsHelper("host.name == x || \"bar\" == host.name", new Dictionary({{"x", "foo"}}), true, {"foo", "bar"});
}
BOOST_AUTO_TEST_CASE(gettargethosts_redundant)
{
GetTargetHostsHelper("host.name == \"foo\" && 1", nullptr, false);
}
BOOST_AUTO_TEST_CASE(gettargethosts_badconst)
{
GetTargetHostsHelper("host.name == NodeName", new Dictionary({{"x", "foo"}}), false);
}
BOOST_AUTO_TEST_CASE(gettargethosts_notliteral)
{
GetTargetHostsHelper("host.name == \"foo\" + \"bar\"", nullptr, false);
}
BOOST_AUTO_TEST_CASE(gettargethosts_wrongop)
{
GetTargetHostsHelper("host.name != \"foo\"", nullptr, false);
}
BOOST_AUTO_TEST_CASE(gettargethosts_wrongattr)
{
GetTargetHostsHelper("host.__name == \"foo\"", nullptr, false);
}
BOOST_AUTO_TEST_CASE(gettargethosts_wrongvar)
{
GetTargetHostsHelper("service.name == \"foo\"", nullptr, false);
}
BOOST_AUTO_TEST_CASE(gettargethosts_noindexer)
{
GetTargetHostsHelper("name == \"foo\"", nullptr, false);
}
BOOST_AUTO_TEST_CASE(gettargetservices_literal)
{
GetTargetServicesHelper("host.name == \"foo\" && service.name == \"bar\"", nullptr, true, {{"foo", "bar"}});
}
BOOST_AUTO_TEST_CASE(gettargetservices_const)
{
GetTargetServicesHelper("host.name == x && service.name == y", new Dictionary({{"x", "foo"}, {"y", "bar"}}), true, {{"foo", "bar"}});
}
BOOST_AUTO_TEST_CASE(gettargetservices_swapped_outer)
{
GetTargetServicesHelper("service.name == \"bar\" && host.name == \"foo\"", nullptr, true, {{"foo", "bar"}});
}
BOOST_AUTO_TEST_CASE(gettargetservices_swapped_inner)
{
GetTargetServicesHelper("\"foo\" == host.name && \"bar\" == service.name", nullptr, true, {{"foo", "bar"}});
}
BOOST_AUTO_TEST_CASE(gettargetservices_two)
{
GetTargetServicesHelper(
"host.name == \"foo\" && service.name == \"bar\" || host.name == \"oof\" && service.name == \"rab\"",
nullptr, true, {{"foo", "bar"}, {"oof", "rab"}}
);
}
BOOST_AUTO_TEST_CASE(gettargetservices_three)
{
GetTargetServicesHelper(
"host.name == \"foo\" && service.name == \"bar\" || host.name == \"oof\" && service.name == \"rab\" || host.name == \"ofo\" && service.name == \"rba\"",
nullptr, true, {{"foo", "bar"}, {"oof", "rab"}, {"ofo", "rba"}}
);
}
BOOST_AUTO_TEST_CASE(gettargetservices_mixed)
{
GetTargetServicesHelper("\"bar\" == service.name && x == host.name", new Dictionary({{"x", "foo"}}), true, {{"foo", "bar"}});
}
BOOST_AUTO_TEST_CASE(gettargetservices_redundant)
{
GetTargetServicesHelper("host.name == \"foo\" && service.name == \"bar\" && 1", nullptr, false);
}
BOOST_AUTO_TEST_CASE(gettargetservices_badconst)
{
GetTargetServicesHelper("host.name == NodeName && service.name == \"bar\"", new Dictionary({{"x", "foo"}}), false);
}
BOOST_AUTO_TEST_CASE(gettargetservices_notliteral)
{
GetTargetServicesHelper("host.name == \"foo\" && service.name == \"b\" + \"ar\"", nullptr, false);
}
BOOST_AUTO_TEST_CASE(gettargetservices_wrongop_outer)
{
GetTargetServicesHelper("host.name == \"foo\" & service.name == \"bar\"", nullptr, false);
}
BOOST_AUTO_TEST_CASE(gettargetservices_wrongop_host)
{
GetTargetServicesHelper("host.name != \"foo\" && service.name == \"bar\"", nullptr, false);
}
BOOST_AUTO_TEST_CASE(gettargetservices_wrongop_service)
{
GetTargetServicesHelper("host.name == \"foo\" && service.name != \"bar\"", nullptr, false);
}
BOOST_AUTO_TEST_CASE(gettargetservices_wrongattr_host)
{
GetTargetServicesHelper("host.__name == \"foo\" && service.name == \"bar\"", nullptr, false);
}
BOOST_AUTO_TEST_CASE(gettargetservices_wrongattr_service)
{
GetTargetServicesHelper("host.name == \"foo\" && service.__name == \"bar\"", nullptr, false);
}
BOOST_AUTO_TEST_CASE(gettargetservices_wrongvar_host)
{
GetTargetServicesHelper("horst.name == \"foo\" && service.name == \"bar\"", nullptr, false);
}
BOOST_AUTO_TEST_CASE(gettargetservices_wrongvar_service)
{
GetTargetServicesHelper("host.name == \"foo\" && sehrvice.name == \"bar\"", nullptr, false);
}
BOOST_AUTO_TEST_CASE(gettargetservices_noindexer_host)
{
GetTargetServicesHelper("name == \"foo\" && service.name == \"bar\"", nullptr, false);
}
BOOST_AUTO_TEST_CASE(gettargetservices_noindexer_service)
{
GetTargetServicesHelper("host.name == \"foo\" && name == \"bar\"", nullptr, false);
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -1,11 +1,74 @@
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
#include "icinga/host.hpp"
#include "icinga/notification.hpp"
#include "icinga/notificationcommand.hpp"
#include "icinga/service.hpp"
#include "icinga/user.hpp"
#include <BoostTestTargetConfig.h>
#include <iostream>
using namespace icinga;
struct DuplicateDueToFilterHelper
{
Host::Ptr h = new Host();
Service::Ptr s = new Service();
User::Ptr u = new User();
NotificationCommand::Ptr nc = new NotificationCommand();
Notification::Ptr n = new Notification();
unsigned int called = 0;
DuplicateDueToFilterHelper(int typeFilter, int stateFilter)
{
h->SetName("example.com", true);
h->Register();
s->SetShortName("disk", true);
h->AddService(s);
u->SetName("jdoe", true);
u->SetTypeFilter(~0);
u->SetStateFilter(~0);
u->Register();
nc->SetName("mail", true);
nc->SetExecute(new Function("", [this]() { ++called; }), true);
nc->Register();
n->SetFieldByName("host_name", "example.com", false, DebugInfo());
n->SetFieldByName("service_name", "disk", false, DebugInfo());
n->SetFieldByName("command", "mail", false, DebugInfo());
n->SetUsersRaw(new Array({"jdoe"}), true);
n->SetTypeFilter(typeFilter);
n->SetStateFilter(stateFilter);
n->OnAllConfigLoaded(); // link Service
}
~DuplicateDueToFilterHelper()
{
h->Unregister();
u->Unregister();
nc->Unregister();
}
void SendStateNotification(ServiceState state, bool isSent)
{
auto calledBefore (called);
s->SetStateRaw(state, true);
Application::GetTP().Start();
n->BeginExecuteNotification(
state == ServiceOK ? NotificationRecovery : NotificationProblem,
nullptr, false, false, "", ""
);
Application::GetTP().Stop();
BOOST_CHECK_EQUAL(called > calledBefore, isSent);
}
};
BOOST_AUTO_TEST_SUITE(icinga_notification)
BOOST_AUTO_TEST_CASE(strings)
@ -102,4 +165,51 @@ BOOST_AUTO_TEST_CASE(type_filter)
std::cout << "#4 Notification type: " << ftype << " against " << notification->GetTypeFilter() << " must fail." << std::endl;
BOOST_CHECK(!(notification->GetTypeFilter() & ftype));
}
BOOST_AUTO_TEST_CASE(no_filter_problem_no_duplicate)
{
DuplicateDueToFilterHelper helper (~0, ~0);
helper.SendStateNotification(ServiceCritical, true);
helper.SendStateNotification(ServiceWarning, true);
helper.SendStateNotification(ServiceCritical, true);
}
BOOST_AUTO_TEST_CASE(filter_problem_no_duplicate)
{
DuplicateDueToFilterHelper helper (~0, ~StateFilterWarning);
helper.SendStateNotification(ServiceCritical, true);
helper.SendStateNotification(ServiceWarning, false);
helper.SendStateNotification(ServiceCritical, false);
}
BOOST_AUTO_TEST_CASE(volatile_filter_problem_duplicate)
{
DuplicateDueToFilterHelper helper (~0, ~StateFilterWarning);
helper.s->SetVolatile(true, true);
helper.SendStateNotification(ServiceCritical, true);
helper.SendStateNotification(ServiceWarning, false);
helper.SendStateNotification(ServiceCritical, true);
}
BOOST_AUTO_TEST_CASE(no_recovery_filter_no_duplicate)
{
DuplicateDueToFilterHelper helper (~0, ~0);
helper.SendStateNotification(ServiceCritical, true);
helper.SendStateNotification(ServiceOK, true);
helper.SendStateNotification(ServiceCritical, true);
}
BOOST_AUTO_TEST_CASE(recovery_filter_duplicate)
{
DuplicateDueToFilterHelper helper (~NotificationRecovery, ~0);
helper.SendStateNotification(ServiceCritical, true);
helper.SendStateNotification(ServiceOK, false);
helper.SendStateNotification(ServiceCritical, true);
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -0,0 +1,88 @@
/* Icinga 2 | (c) 2023 Icinga GmbH | GPLv2+ */
#include "base/array.hpp"
#include "icinga/checkresult.hpp"
#include "icinga/host.hpp"
#include "icinga/notification.hpp"
#include "icinga/notificationcommand.hpp"
#include "icinga/service.hpp"
#include "icinga/user.hpp"
#include "methods/pluginnotificationtask.hpp"
#include <BoostTestTargetConfig.h>
#include <future>
using namespace icinga;
BOOST_AUTO_TEST_SUITE(methods_pluginnotificationtask)
BOOST_AUTO_TEST_CASE(truncate_long_output)
{
#ifdef __linux__
Host::Ptr h = new Host();
CheckResult::Ptr hcr = new CheckResult();
CheckResult::Ptr scr = new CheckResult();
Service::Ptr s = new Service();
User::Ptr u = new User();
NotificationCommand::Ptr nc = new NotificationCommand();
Notification::Ptr n = new Notification();
String placeHolder (1024 * 1024, 'x');
std::promise<String> promise;
auto future (promise.get_future());
hcr->SetOutput("H" + placeHolder + "h", true);
scr->SetOutput("S" + placeHolder + "s", true);
h->SetName("example.com", true);
h->SetLastCheckResult(hcr, true);
h->Register();
s->SetHostName("example.com", true);
s->SetShortName("disk", true);
s->SetLastCheckResult(scr, true);
s->OnAllConfigLoaded(); // link Host
nc->SetCommandLine(
new Array({
"echo",
"host_output=$host.output$",
"service_output=$service.output$",
"notification_comment=$notification.comment$",
"output=$output$",
"comment=$comment$"
}),
true
);
nc->SetName("mail", true);
nc->Register();
n->SetFieldByName("host_name", "example.com", false, DebugInfo());
n->SetFieldByName("service_name", "disk", false, DebugInfo());
n->SetFieldByName("command", "mail", false, DebugInfo());
n->OnAllConfigLoaded(); // link Service
Checkable::ExecuteCommandProcessFinishedHandler = [&promise](const Value&, const ProcessResult& pr) {
promise.set_value(pr.Output);
};
PluginNotificationTask::ScriptFunc(n, u, nullptr, NotificationCustom, "jdoe", "C" + placeHolder + "c", nullptr, false);
future.wait();
Checkable::ExecuteCommandProcessFinishedHandler = nullptr;
h->Unregister();
nc->Unregister();
auto output (future.get());
BOOST_CHECK(output.Contains("host_output=Hx"));
BOOST_CHECK(!output.Contains("xh"));
BOOST_CHECK(output.Contains("x service_output=Sx"));
BOOST_CHECK(!output.Contains("xs"));
BOOST_CHECK(output.Contains("x notification_comment=Cx"));
BOOST_CHECK(!output.Contains("xc"));
BOOST_CHECK(output.Contains("x output=Sx"));
BOOST_CHECK(output.Contains("x comment=Cx"));
#endif /* __linux__ */
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -31,10 +31,10 @@ if (-not (Test-Path env:OPENSSL_ROOT_DIR)) {
$env:OPENSSL_ROOT_DIR = 'c:\local\OpenSSL-Win64'
}
if (-not (Test-Path env:BOOST_ROOT)) {
$env:BOOST_ROOT = 'c:\local\boost_1_82_0'
$env:BOOST_ROOT = 'c:\local\boost_1_83_0'
}
if (-not (Test-Path env:BOOST_LIBRARYDIR)) {
$env:BOOST_LIBRARYDIR = 'c:\local\boost_1_82_0\lib64-msvc-14.2'
$env:BOOST_LIBRARYDIR = 'c:\local\boost_1_83_0\lib64-msvc-14.2'
}
if (-not (Test-Path env:FLEX_BINARY)) {
$env:FLEX_BINARY = 'C:\ProgramData\chocolatey\bin\win_flex.exe'

View File

@ -30,13 +30,13 @@ if (-not (Test-Path env:CMAKE_GENERATOR_PLATFORM)) {
}
}
if (-not (Test-Path env:OPENSSL_ROOT_DIR)) {
$env:OPENSSL_ROOT_DIR = "c:\local\OpenSSL_3_0_9-Win${env:BITS}"
$env:OPENSSL_ROOT_DIR = "c:\local\OpenSSL_3_0_12-Win${env:BITS}"
}
if (-not (Test-Path env:BOOST_ROOT)) {
$env:BOOST_ROOT = "c:\local\boost_1_82_0-Win${env:BITS}"
$env:BOOST_ROOT = "c:\local\boost_1_83_0-Win${env:BITS}"
}
if (-not (Test-Path env:BOOST_LIBRARYDIR)) {
$env:BOOST_LIBRARYDIR = "c:\local\boost_1_82_0-Win${env:BITS}\lib${env:BITS}-msvc-14.2"
$env:BOOST_LIBRARYDIR = "c:\local\boost_1_83_0-Win${env:BITS}\lib${env:BITS}-msvc-14.2"
}
if (-not (Test-Path env:FLEX_BINARY)) {
$env:FLEX_BINARY = 'C:\ProgramData\chocolatey\bin\win_flex.exe'