summaryrefslogtreecommitdiff
path: root/sitemodules
diff options
context:
space:
mode:
Diffstat (limited to 'sitemodules')
-rw-r--r--sitemodules/profiles/files/icinga2_master/check_puppetdb_nodes500
-rw-r--r--sitemodules/profiles/files/icinga2_master/icinga2-git-pull-hook16
-rw-r--r--sitemodules/profiles/manifests/base.pp45
-rw-r--r--sitemodules/profiles/manifests/gitea.pp4
-rw-r--r--sitemodules/profiles/manifests/icinga2_master.pp73
-rwxr-xr-xsitemodules/profiles/templates/base/update-crls.epp160
-rw-r--r--sitemodules/profiles/templates/gitea/app.ini.epp6
-rw-r--r--sitemodules/profiles/templates/squid/squid.conf.epp1
-rw-r--r--sitemodules/roles/manifests/authserver.pp29
-rw-r--r--sitemodules/roles/manifests/idp.pp29
10 files changed, 568 insertions, 295 deletions
diff --git a/sitemodules/profiles/files/icinga2_master/check_puppetdb_nodes b/sitemodules/profiles/files/icinga2_master/check_puppetdb_nodes
index 727a328..1145e4e 100644
--- a/sitemodules/profiles/files/icinga2_master/check_puppetdb_nodes
+++ b/sitemodules/profiles/files/icinga2_master/check_puppetdb_nodes
@@ -1,253 +1,277 @@
-#!/usr/bin/perl
-
-# Copyright (c) 2014, Evgeni Golov
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without modification,
-# are permitted provided that the following conditions are met:
-#
-# * Redistributions of source code must retain the above copyright notice, this
-# list of conditions and the following disclaimer.
-#
-# * Redistributions in binary form must reproduce the above copyright notice, this
-# list of conditions and the following disclaimer in the documentation and/or
-# other materials provided with the distribution.
-#
-# * Neither the name of the {organization} nor the names of its
-# contributors may be used to endorse or promote products derived from
-# this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
-# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-use strict;
-use warnings;
-use JSON;
-use LWP;
-use Monitoring::Plugin;
-use Date::Parse;
-
-my $np = Monitoring::Plugin->new(
- usage => "Usage: %s [ -H|--hostname=<hostname>] "
- . "[ -p|--port=<port> ] [-s] [ -w|--warning=<minutes> ] "
- . "[ -c|--critical=<minutes> ] [ -W|--warnfails=<num> ] "
- . "[ -C|--critfails=<num> ] [ -n|--node=<node> ]"
- . "[ -a|--apiversion=<num> ]"
- . "[ -i|--ignore=<list> ]",
- shortname => 'Check last node runs from PuppetDB',
- url => 'https://github.com/evgeni/check_puppetdb_nodes',
- version => '1.0',
- license => 'This plugin is free software, and comes with ABSOLUTELY
-NO WARRANTY. It may be used, redistributed and/or modified under
-the terms of the BSD 3-clause license.',
-);
-
-$np->add_arg(
- spec => 'warning|w=i',
- help => "Exit with WARNING status if nodes did not update for "
- . "more than INTEGER minutes (default: %s)",
- default => 120,
-);
-
-$np->add_arg(
- spec => 'critical|c=i',
- help => "Exit with CRITICAL status if nodes did not update for "
- . "more than INTEGER minutes (default: %s)",
- default => 1440,
-);
-
-$np->add_arg(
- spec => 'warnfails|W=i',
- help => "Exit with WARNING status if nodes had at least INTEGER "
- . "failures in the last run (default: %s)",
- default => 1,
-);
-
-$np->add_arg(
- spec => 'critfails|C=i',
- help => "Exit with CRITICAL status if nodes had at least INTEGER "
- . "failures in the last run (default: %s)",
- default => 1,
-);
-
-$np->add_arg(
- spec => 'hostname|H=s',
- help => 'Hostname of the PuppetDB (default: %s)',
- default => 'localhost',
-);
-
-$np->add_arg(
- spec => 'port|p=i',
- help => 'Port PuppetDB is running on (default: %s)',
- default => 8080,
-);
-
-$np->add_arg(
- spec => 'node|n=s',
- help => 'Node name to check, if not given, all nodes will be checked',
-);
-
-$np->add_arg(
- spec => 'ssl|s',
- help => "Use HTTPS instead of HTTP",
-);
-
-$np->add_arg(
- spec => 'insecure|k',
- help => "Allow connections via HTTPS without checking certificates",
-);
-
-$np->add_arg(
- spec => 'apiversion|a=n',
- help => 'Specify PupppetDB API version (default: %s)',
- default => 3,
-);
-
-$np->add_arg(
- spec => 'ignore|i=s',
- help => 'Node names to ignore (comma-separated list) (default: %s)',
- default => '',
-);
-
-$np->getopts;
-
-my %apiurls = (
- 3 => { 'nodes' => 'v3/nodes', 'event-counts' => 'v3/event-counts' },
- 4 => { 'nodes' => 'pdb/query/v4/nodes', 'event-counts' => 'pdb/query/v4/event-counts', 'logs' => 'pdb/query/v4/reports/{hash}/logs' },
-);
-if ( !exists $apiurls{$np->opts->apiversion} ) {
- $np->nagios_exit( 'UNKNOWN', 'Unsupported PuppetDB API version ' . $np->opts->apiversion );
+#!/usr/bin/env python3
+"""Nagios/Icinga plugin to check puppetdb status of a node."""
+
+import argparse
+import logging
+from datetime import datetime
+from urllib.parse import urljoin
+
+import nagiosplugin
+import requests
+
+_log = logging.getLogger("check_puppetdb_nodes")
+
+api_urls = {
+ 3: {"nodes": "v3/nodes", "event-counts": "v3/event-counts"},
+ 4: {
+ "nodes": "pdb/query/v4/nodes",
+ "event-counts": "pdb/query/v4/event-counts",
+ "logs": "pdb/query/v4/reports/{hash}/logs",
+ },
}
-my @ignore_list = split( ',', $np->opts->ignore );
-my $url = sprintf( 'http%s://%s:%d/',
- defined( $np->opts->ssl ) ? 's' : '',
- $np->opts->hostname, $np->opts->port );
+class BoolContext(nagiosplugin.Context):
+ def evaluate(self, metric, resource):
+ if metric != 0:
+ return self.result_cls(nagiosplugin.Critical, metric)
-my $ua = new LWP::UserAgent;
-$ua->default_header( 'Accept' => 'application/json' );
-if ( defined( $np->opts->insecure ) ) {
- $ua->ssl_opts( verify_hostname => 0 ,SSL_verify_mode => 0x00);
-}
-my %parameters = ();
-if ( defined( $np->opts->node ) ) {
- %parameters = ( 'query' => '["=","certname","' . $np->opts->node . '"]' );
-}
-my $uri = URI->new( $url . $apiurls{$np->opts->apiversion}{'nodes'} );
-$uri->query_form(%parameters);
-my $response = $ua->get($uri);
+class PuppetDBReport(nagiosplugin.Resource):
+ """Domain model: last report for node.
-if ( !$response->is_success ) {
- $np->nagios_exit( 'UNKNOWN',
- $response->code . ": " . $response->status_line );
-}
+ Determines the age of the last puppetdb report for the given node.
+ """
-my $data = decode_json( $response->decoded_content );
+ def __init__(self, api_client, node_name, ignored):
+ self.api_client = api_client
+ self.node_name = node_name
+ self.ignored = ignored or []
-my $now = time();
+ def probe(self):
+ node_info = self.api_client.fetch_node_information(self.node_name)
-if ( defined( $np->opts->node ) and !@$data ) {
- $np->add_message( CRITICAL,
- $np->opts->node . " not found in puppetdb\n" );
-}
+ for node in node_info:
+ if "certname" in node:
+ certname = node["certname"]
+ else:
+ certname = node["name"]
+ deactivated = node["deactivated"]
+ catalog_timestamp = node["catalog_timestamp"]
+ report_hash = node["latest_report_hash"]
+ ts = datetime.strptime(catalog_timestamp, "%Y-%m-%dT%H:%M:%S.%fZ")
+ delta = datetime.utcnow() - ts
-foreach my $node (@$data) {
- my $certname = defined($node->{'certname'}) ? $node->{'certname'} : $node->{'name'} ;
- my $deactivated = $node->{'deactivated'};
- my $catalog_timestamp = $node->{'catalog_timestamp'};
- my $report_hash = $node->{'latest_report_hash'};
- my $ts = str2time($catalog_timestamp);
-
- next if grep { $certname eq $_ } @ignore_list;
-
- if ( !defined $deactivated and ( !length $catalog_timestamp or !length $report_hash )) {
- $np->add_message( CRITICAL,
- "$certname last run UNAVAILABLE\n" );
- }
- if ( !defined $deactivated and length $catalog_timestamp and $report_hash) {
- my $delta = ( $now - $ts );
- if ( $delta > ( $np->opts->critical * 60 ) ) {
- $np->add_message( CRITICAL,
- "$certname did not update since $catalog_timestamp\n" );
- }
- elsif ( $delta > ( $np->opts->warning * 60 ) ) {
- $np->add_message( WARNING,
- "$certname did not update since $catalog_timestamp\n" );
- }
-
- my %apiparameters = (
- 3 => {
- 'query' => '["and",["=","certname","'
- . $certname
- . '"],["=","latest-report?",true]]',
- 'summarize-by' => 'certname',
- 'count-by' => 'resource',
- },
- 4 => {
- 'query' => '["and",["=","certname","'
- . $certname
- . '"],["=","latest_report?",true]]',
- 'summarize_by' => 'certname',
- 'count_by' => 'resource',
- }
- );
- my $uri = URI->new( $url . $apiurls{$np->opts->apiversion}{'event-counts'} );
- $uri->query_form($apiparameters{$np->opts->apiversion});
- $response = $ua->get($uri);
-
- if ( $response->is_success ) {
- my $node_data = decode_json( $response->decoded_content );
-
- my $failures = 0;
- if ( defined( @$node_data[0] )
- and defined( @$node_data[0]->{'failures'} ) )
- {
- $failures = @$node_data[0]->{'failures'};
- }
+ if deactivated:
+ yield nagiosplugin.Metric(f"missing-{certname}", 1, min=0)
+ continue
- if ( $failures >= $np->opts->critfails ) {
- $np->add_message( CRITICAL,
- "$certname had $failures failures in the last run\n" );
- }
- elsif ( $failures >= $np->opts->warnfails ) {
- $np->add_message( WARNING,
- "$certname had $failures failures in the last run\n" );
- }
- elsif ( exists $apiurls{$np->opts->apiversion}{'logs'} ) {
- my $apiurl = $apiurls{$np->opts->apiversion}{'logs'};
- $apiurl =~ s/{hash}/$report_hash/;
- $uri = URI->new( $url . $apiurl );
- $response = $ua->get($uri);
- if ( $response->is_success ) {
- my $logs = decode_json( $response->decoded_content );
- foreach my $log (@$logs) {
- my $tags = $log->{'tags'};
- if ( grep(/^err$/, @$tags) ) {
- $np->add_message( WARNING, "$certname, $log->{'message'}" );
- }
- }
- }
- }
+ if certname in self.ignored:
+ continue
- } else {
- $np->nagios_exit( 'UNKNOWN', 'Unsupported query ' . $response->decoded_content);
- }
+ yield nagiosplugin.Metric(
+ f"last-{certname}", round(delta.total_seconds()), "s", min=0
+ )
- }
-}
+ failures = self.api_client.fetch_failure_report(certname, report_hash)
+ yield nagiosplugin.Metric(f"failed-{certname}", failures, min=0)
+
+
+def comma_separated(string):
+ result = []
+ for part in [item.trim for item in string.split(",")]:
+ if len(part) > 0:
+ result.append(part)
+ return result
-my $code;
-my $message;
-( $code, $message ) = $np->check_messages;
-$np->nagios_exit( $code, $message );
+class PuppetDBClient:
+ def __init__(self, hostname, port, tls, insecure, roots, api_version):
+ scheme = tls and "https" or "http"
+ self.base_url = f"{scheme}://{hostname}:{port}/"
+ self.allow_insecure = insecure
+ self.use_roots = roots
+ self.api_version = api_version
+
+ self.client = requests.Session()
+ self.client.headers = {"Accept": "application/json"}
+ self.node_info = []
+
+ def fetch_node_information(self, node_name):
+ if self.node_info:
+ return self.node_info
+
+ url = urljoin(self.base_url, api_urls[self.api_version]["nodes"])
+ if node_name:
+ r = self.client.get(
+ url, params={"query": f'["=","certname","{node_name}"]'}
+ )
+ else:
+ r = self.client.get(url)
+
+ r.raise_for_status()
+
+ self.node_info = r.json()
+
+ return self.node_info
+
+ def fetch_failure_report(self, cert_name, report_hash):
+ url = urljoin(self.base_url, api_urls[self.api_version]["event-counts"])
+
+ if self.api_version == 3:
+ query_args = {
+ "query": f'["and",["=","certname","{cert_name}"],["=","latest-report?",true]]',
+ "summarize-by": "certname",
+ "count-by": "resource",
+ }
+ else:
+ query_args = {
+ "query": f'["and",["=","certname","{cert_name}"],["=","latest_report?",true]]',
+ "summarize_by": "certname",
+ "count_by": "resource",
+ }
+
+ r = self.client.get(url, params=query_args)
+
+ r.raise_for_status()
+
+ report = r.json()
+
+ failures = 0
+
+ if report and "failures" in report[0]:
+ failures += report[0]["failures"]
+
+ if "logs" in api_urls[self.api_version]:
+ url = urljoin(self.base_url, api_urls[self.api_version]["logs"]).format(
+ hash=report_hash
+ )
+ self.client.get(url)
+
+ r.raise_for_status()
+
+ logs = r.json()
+
+ for log in logs:
+ if "tags" in log and "err" in log["tags"]:
+ failures += 1
+
+ return failures
+
+ def get_cert_names(self, node_name):
+ result = []
+
+ for node in self.fetch_node_information(node_name):
+ if "certname" in node:
+ result.append(node["certname"])
+ else:
+ result.append(node["name"])
+
+ return sorted(result)
+
+
+@nagiosplugin.guarded
+def main():
+ argp = argparse.ArgumentParser(description=__doc__)
+
+ argp.add_argument(
+ "-w",
+ "--warning",
+ type=int,
+ default=120,
+ help="Exit with WARNING status if nodes did not update for more then given minutes",
+ )
+ argp.add_argument(
+ "-c",
+ "--critical",
+ type=int,
+ default=1440,
+ help="Exit with CRITICAL status if nodes did not update for more then given minutes",
+ )
+ argp.add_argument(
+ "-W",
+ "--warnfails",
+ type=int,
+ default=1,
+ help="Exit with WARNING status if nodes had at least the given number of failures in the last run",
+ )
+ argp.add_argument(
+ "-C",
+ "--critfails",
+ type=int,
+ default=1,
+ help="Exit with CRITICAL status if nodes had at least the given number of failures in the last run",
+ )
+ argp.add_argument(
+ "-H",
+ "--hostname",
+ type=str,
+ default="localhost",
+ help="Hostname of the PuppetDB",
+ )
+ argp.add_argument(
+ "-p", "--port", type=int, default=8080, help="Port PuppetDB is running on"
+ )
+ argp.add_argument(
+ "-n",
+ "--node",
+ type=str,
+ help="Node name to check, if not given, all nodes will be checked",
+ )
+ argp.add_argument(
+ "-t",
+ "--tls",
+ action="store_true",
+ help="Use HTTPS instead of HTTP",
+ )
+ argp.add_argument(
+ "-k",
+ "--insecure",
+ action="store_true",
+ help="Allow connections via HTTPS without checking certificates",
+ )
+ argp.add_argument(
+ "-r",
+ "--roots",
+ type=str,
+ help="Use the given root certificate file for certificate validation",
+ )
+ argp.add_argument(
+ "-a",
+ "--apiversion",
+ dest="api_version",
+ type=int,
+ default=4,
+ choices=api_urls.keys(),
+ help="Specify PuppetDB API version",
+ )
+ argp.add_argument(
+ "-i",
+ "--ignore",
+ type=comma_separated,
+ help="Node names to ignore (comma-separated list)",
+ )
+ argp.add_argument(
+ "-v",
+ "--verbose",
+ action="count",
+ default=0,
+ help="increase output verbosity (use up to 3 times)",
+ )
+
+ args = argp.parse_args()
+
+ api_client = PuppetDBClient(
+ args.hostname, args.port, args.tls, args.insecure, args.roots, args.api_version
+ )
+
+ check = nagiosplugin.Check(PuppetDBReport(api_client, args.node, args.ignore))
+
+ for certname in api_client.get_cert_names(args.node):
+ check.add(BoolContext(f"missing-{certname}"))
+ check.add(
+ nagiosplugin.ScalarContext(
+ f"last-{certname}", args.warning * 60, args.critical * 60
+ )
+ )
+ check.add(
+ nagiosplugin.ScalarContext(
+ f"failed-{certname}", f"@{args.warnfails}:", f"@{args.critfails}:"
+ )
+ )
+
+ check.main(verbose=args.verbose)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/sitemodules/profiles/files/icinga2_master/icinga2-git-pull-hook b/sitemodules/profiles/files/icinga2_master/icinga2-git-pull-hook
index a0d3711..c786017 100644
--- a/sitemodules/profiles/files/icinga2_master/icinga2-git-pull-hook
+++ b/sitemodules/profiles/files/icinga2_master/icinga2-git-pull-hook
@@ -88,6 +88,17 @@ class GitHookRequestHandler(BaseHTTPRequestHandler):
self.wfile.write(("%s\r\n" % message).encode("UTF-8"))
def _handle_pull(self):
+ args = [
+ "sshpass",
+ "-e",
+ "-P",
+ "passphrase",
+ "git",
+ "pull",
+ GIT_REPOSITORY,
+ GIT_BRANCH,
+ ]
+ self.log.info("running '%s'", " ".join(args))
try:
git_proc = subprocess.run(
[
@@ -96,15 +107,12 @@ class GitHookRequestHandler(BaseHTTPRequestHandler):
"-P",
"passphrase",
"git",
- "subtree",
"pull",
- "--prefix",
- "icinga2/conf.d",
GIT_REPOSITORY,
GIT_BRANCH,
],
env=ENV_FOR_GIT,
- cwd="/etc",
+ cwd=GIT_DIRECTORY,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=True,
diff --git a/sitemodules/profiles/manifests/base.pp b/sitemodules/profiles/manifests/base.pp
index fd6f225..ff3d9e4 100644
--- a/sitemodules/profiles/manifests/base.pp
+++ b/sitemodules/profiles/manifests/base.pp
@@ -14,9 +14,7 @@
#
# @param rootalias alias that gets emails for root
#
-# @param crl_job_enable whether to setup the hourly CRL update job
-#
-# @param crl_job_services which services to reload after the CRL update
+# @param crl_job configure the hourly CRL update job
#
# @param is_external whether the node is outside of CAcert infrastructure
#
@@ -36,15 +34,21 @@
# Copyright
# ---------
#
-# Copyright 2016-2021 Jan Dittberner
+# Copyright 2016-2022 Jan Dittberner
#
class profiles::base (
- Array[String] $admins = [],
- Hash[String, Data] $users = {},
- String $rootalias = "${trusted['certname']}-admin@cacert.org",
- Boolean $crl_job_enable = false,
- Array[String] $crl_job_services = [],
- Boolean $is_external = false,
+ Array[String] $admins = [],
+ Hash[String, Data] $users = {},
+ String $rootalias = "${trusted['certname']}-admin@cacert.org",
+ Hash[String, Data] $crl_job = {
+ 'enable' => false,
+ 'hostname' => $trusted['certname'],
+ 'services' => [],
+ 'check_url' => 'https://monitor.infra.cacert.org:5665/v1/actions/process-check-result',
+ 'api_user' => '',
+ 'api_password' => '',
+ },
+ Boolean $is_external = false,
) {
# ensure admin users for this container
$admins.each |String $username| {
@@ -172,7 +176,10 @@ class profiles::base (
repos => 'main',
release => "${::lsbdistcodename}-updates",
}
- if Integer($facts['os']['release']['major']) < 11 {
+
+ $os_major = Integer($facts['os']['release']['major'])
+
+ if $os_major < 11 {
apt::source { "security.debian.org-${::lsbdistcodename}-security":
location => 'http://security.debian.org/debian-security',
repos => 'main',
@@ -255,7 +262,11 @@ class profiles::base (
subscribe => [File[$cacert_class1_file], File[$cacert_class3_file]],
}
- if ($crl_job_enable) {
+ if ($crl_job['enable']) {
+ package { 'python3-requests':
+ ensure => installed,
+ }
+
file { '/var/local/ssl':
ensure => directory,
owner => 'root',
@@ -278,9 +289,17 @@ class profiles::base (
mode => '0755',
content => epp(
'profiles/base/update-crls.epp',
- { 'services' => $crl_job_services }),
+ {
+ 'services' => $crl_job['services'],
+ 'check_url' => $crl_job['check_url'],
+ 'api_user' => $crl_job['api_user'],
+ 'api_password' => $crl_job['api_password'],
+ 'hostname' => $crl_job['hostname'],
+ },
+ ),
require => [
Package['ca-certificates'],
+ Package['python3-requests'],
File['/var/local/ssl/crls'],
File[$cacert_class1_file],
File[$cacert_class3_file]
diff --git a/sitemodules/profiles/manifests/gitea.pp b/sitemodules/profiles/manifests/gitea.pp
index 27a882e..0f9d9f6 100644
--- a/sitemodules/profiles/manifests/gitea.pp
+++ b/sitemodules/profiles/manifests/gitea.pp
@@ -49,8 +49,8 @@ class profiles::gitea (
String $gitea_fqdn = 'code.cacert.org',
String $gitea_socket = '/run/gitea/gitea.sock',
) {
- $gitea_version = '1.16.6'
- $gitea_checksum = 'a96751af12d5e96301a97c280bafb92782e0e9b7a0bbe8960c704c0c0361e576'
+ $gitea_version = '1.16.9'
+ $gitea_checksum = '821dd30afed9ae42b18e727174b078ea9118a6ccc5106d8246bebf8180fcbef3'
$gitea_url = "https://dl.gitea.io/gitea/${gitea_version}/gitea-${gitea_version}-linux-amd64"
$gitea_service = '/etc/systemd/system/gitea.service'
diff --git a/sitemodules/profiles/manifests/icinga2_master.pp b/sitemodules/profiles/manifests/icinga2_master.pp
index e8f4968..6f83146 100644
--- a/sitemodules/profiles/manifests/icinga2_master.pp
+++ b/sitemodules/profiles/manifests/icinga2_master.pp
@@ -50,7 +50,7 @@
# Copyright
# ---------
#
-# Copyright 2019-2021 Jan Dittberner
+# Copyright 2019-2022 Jan Dittberner
class profiles::icinga2_master (
String $ido_database_password,
String $web2_database_password,
@@ -69,7 +69,7 @@ class profiles::icinga2_master (
include profiles::systemd_reload
include postgresql::server
- class { '::icinga2':
+ class { 'icinga2':
manage_repo => false,
features => ['mainlog', 'checker', 'notification'],
constants => {
@@ -78,7 +78,7 @@ class profiles::icinga2_master (
},
}
- class { '::icinga2::pki::ca':
+ class { 'icinga2::pki::ca':
ca_cert => $ca_certificate,
ca_key => $ca_key,
}
@@ -88,7 +88,7 @@ class profiles::icinga2_master (
password => postgresql_password('icinga2', $ido_database_password),
}
- class { '::icinga2::feature::idopgsql':
+ class { 'icinga2::feature::idopgsql':
user => 'icinga2',
password => $ido_database_password,
database => 'icinga2',
@@ -96,7 +96,7 @@ class profiles::icinga2_master (
require => Postgresql::Server::Db['icinga2'],
}
- class { '::icinga2::feature::api':
+ class { 'icinga2::feature::api':
pki => 'none',
}
@@ -123,7 +123,7 @@ class profiles::icinga2_master (
),
}
- class { '::icingaweb2':
+ class { 'icingaweb2':
manage_repo => false,
import_schema => true,
db_type => 'pgsql',
@@ -134,7 +134,7 @@ class profiles::icinga2_master (
require => Postgresql::Server::Db['icingaweb2'],
}
- class { '::icingaweb2::module::monitoring':
+ class { 'icingaweb2::module::monitoring':
ido_type => 'pgsql',
ido_host => 'localhost',
ido_port => 5432,
@@ -146,19 +146,19 @@ class profiles::icinga2_master (
transport => 'api',
username => 'root',
password => $api_users['root']['password'],
- }
- }
+ },
+ },
}
icingaweb2::config::authmethod { 'external-authentication':
backend => 'external',
- require => Class['::icingaweb2'],
+ require => Class['icingaweb2'],
}
icingaweb2::config::role { 'admin':
users => join($icingaweb_admins, ','),
permissions => '*',
- require => Class['::icingaweb2'],
+ require => Class['icingaweb2'],
}
package { ['sshpass', 'git']:
@@ -206,14 +206,21 @@ class profiles::icinga2_master (
notify => Exec['reload systemd configuration'],
}
- file { '/usr/local/lib/nagios-plugins':
+ file { '/usr/local/lib/nagios':
ensure => directory,
owner => 'root',
group => 'staff',
- mode => '0755'
+ mode => '0755',
}
- file { '/usr/local/lib/nagios-plugins/check_puppetdb_nodes':
+ file { '/usr/local/lib/nagios/plugins':
+ ensure => directory,
+ owner => 'root',
+ group => 'staff',
+ mode => '0755',
+ }
+
+ file { '/usr/local/lib/nagios/plugins/check_puppetdb_nodes':
ensure => file,
owner => 'root',
group => 'staff',
@@ -221,6 +228,19 @@ class profiles::icinga2_master (
source => 'puppet:///modules/profiles/icinga2_master/check_puppetdb_nodes',
}
+ package {['rsync', 'python3-nagiosplugin', 'python3-cryptography']:
+ ensure => present,
+ }
+
+ file { '/usr/local/lib/nagios/plugins/check_cacert_crl':
+ ensure => file,
+ owner => 'root',
+ group => 'root',
+ mode => '0755',
+ source => 'puppet:///modules/profiles/icinga2_external_commands/cacert_check_crl.py',
+ require => [Package['rsync'], Package['python3-nagiosplugin'], Package['python3-cryptography']],
+ }
+
service { 'icinga2-git-pull-hook':
ensure => running,
enable => true,
@@ -231,7 +251,7 @@ class profiles::icinga2_master (
],
}
- include ::icinga2
+ include icinga2
file { '/etc/icinga2/zones.d/global-templates':
ensure => directory,
@@ -262,10 +282,29 @@ class profiles::icinga2_master (
target => '/etc/icinga2/zones.d/global-templates/ocsp-command.conf',
}
::icinga2::object::checkcommand { 'cacert_crl':
- ensure => present,
- command => [
+ ensure => present,
+ command => [
'/usr/local/lib/nagios/plugins/check_cacert_crl',
],
+ arguments => {
+ '--rsync-url' => {
+ 'value' => '$cacert_crl_rsync_url$',
+ 'description' => 'rsync URL to check',
+ },
+ '--warning-last-age' => {
+ 'value' => '$cacert_crl_warning_last_age$',
+ 'description' => 'warning if last age is more than that many minutes',
+ },
+ '--critical-last-age' => {
+ 'value' => '$cacert_crl_critical_last_age$',
+ 'description' => 'critical if last age is more than that many minutes',
+ },
+ },
+ vars => {
+ 'cacert_crl_rsync_url' => 'rsync://crl.cacert.org/crl/',
+ 'cacert_crl_warning_last_age' => '1500', # 25h
+ 'cacert_crl_critical_last_age' => '2160', # 36h
+ },
target => '/etc/icinga2/zones.d/global-templates/cacert_crl-command.conf',
}
diff --git a/sitemodules/profiles/templates/base/update-crls.epp b/sitemodules/profiles/templates/base/update-crls.epp
index eefdfe2..def45ab 100755
--- a/sitemodules/profiles/templates/base/update-crls.epp
+++ b/sitemodules/profiles/templates/base/update-crls.epp
@@ -1,30 +1,148 @@
-<%- | Array[String] $services | -%>
-#!/bin/sh
+<%- |
+ Array[String] $services,
+ String $check_url,
+ String $api_user,
+ String $api_password,
+ String $hostname,
+| -%>
+#!/usr/bin/env python3
# THIS FILE IS MANAGED BY PUPPET, MANUAL CHANGES WILL BE OVERWRITTEN AT THE
# NEXT PUPPET RUN.
-set -e
+import glob
+import subprocess
+import sys
+from datetime import datetime
+from os import path
-CRL_PATH='/var/local/ssl/crls/'
-CA_CERT='/etc/ssl/certs/ca-certificates.crt'
-RSYNC_LOCATION='crl.cacert.org::crl'
+import requests
-rsync -aqz "$RSYNC_LOCATION" "$CRL_PATH"
+CRL_PATH = "/var/local/ssl/crls/"
+CA_CERT = "/etc/ssl/certs/ca-certificates.crt"
+RSYNC_LOCATION = "crl2.intra.cacert.org::crl"
+ICINGA_CA = "/var/lib/icinga2/certs/ca.crt"
-for crl in "$CRL_PATH"*.crl
-do
- if openssl crl -noout -inform DER -in "$crl" -CAfile "$CA_CERT" 2>/dev/null
- then
- openssl crl -inform DER -in "$crl" -out "$crl".pem
- else
- echo "Error: Could not validate the CRL at $crl" >&2
- fi
-done
-c_rehash "$CRL_PATH" 2>/dev/null >&2
-<% $services.each |$service| { -%>
-service <%= $service %> reload > /dev/null
-<% } %>
+def json_timestamp(ts):
+ return int(ts.timestamp())
-exit 0
+
+def report_result(success, output, start):
+ data = {
+ "type": "Service",
+ "filter": 'host.name=="<%= $hostname %>" && service.name=="crl-sync"',
+ "exit_status": 0 if success else 2,
+ "plugin_output": "OK" if success else f"CRITICAL CRL sync failed\n{output}",
+ "check_source": "<%= $hostname %>",
+ "execution_start": json_timestamp(start),
+ "execution_end": json_timestamp(datetime.utcnow()),
+ "ttl": 3720,
+ }
+
+ r = requests.post(
+ "<%= $check_url %>",
+ auth=("<%= $api_user %>", "<%= $api_password %>"),
+ headers={"Accept": "application/json"},
+ json=data,
+ verify=ICINGA_CA,
+ )
+
+ if not r.ok:
+ print("could not submit passive check")
+ print(r.status_code, r.reason)
+ print(r.text)
+ sys.exit(1)
+
+
+def run_command(args, timeout=10):
+ try:
+ res = subprocess.run(args, capture_output=True, timeout=timeout, text=True)
+ except subprocess.TimeoutExpired:
+ return False, "timeout of {} expired running '{}'".format(
+ timeout, " ".join(args)
+ )
+
+ return res.returncode == 0, res.stderr
+
+
+def verify_crl(crl, ca_certificates=CA_CERT):
+ return run_command(
+ [
+ "openssl",
+ "crl",
+ "-noout",
+ "-inform",
+ "DER",
+ "-in",
+ crl,
+ "-CAfile",
+ ca_certificates,
+ ],
+ )
+
+
+def convert_to_pem(crl):
+ return run_command(
+ ["openssl", "crl", "-inform", "DER", "-in", crl, "-out", f"{crl}.pem"]
+ )
+
+
+def rehash_crls(crl_path=CRL_PATH):
+ return run_command(["c_rehash", crl_path])
+
+
+def run_rsync(rsync_source, dest_path):
+ return run_command(["rsync", "-aqz", rsync_source, dest_path], 60)
+
+
+def restart_service(service):
+ return run_command(["systemctl", "restart", f"{service}.service"], 30)
+
+
+def main():
+ start = datetime.utcnow()
+
+ ok, output = run_rsync(RSYNC_LOCATION, CRL_PATH)
+ if not ok:
+ report_result(False, f"rsync run failed:\n{output}", start)
+ return
+
+ error_output = []
+
+ for crl in glob.glob(path.join(CRL_PATH, "*.crl")):
+ ok, output = verify_crl(crl)
+ if not ok:
+ error_output.append(f"crl validation for {crl} failed:\n{output}")
+ continue
+
+ ok, output = convert_to_pem(crl)
+ if not ok:
+ error_output.append(f"pem conversion for {crl} failed:\n{output}")
+
+ if error_output:
+ report_result(False, "\n\n".join(error_output), start)
+ return
+
+ ok, output = rehash_crls(CRL_PATH)
+ if not ok:
+ report_result(False, f"c_rehash for {CRL_PATH} failed:\n{output}", start)
+ return
+
+ services = [<% if $services { %>"<%= $services.join(", ") %>"<% } %>]
+
+ for service in services:
+ ok, output = restart_service(service)
+ if not ok:
+ error_output.append(f"service restart failed for {service}:\n{output}")
+ continue
+
+ if error_output:
+ report_result(False, "\n\n".join(error_output), start)
+ return
+
+ report_result(True, "", start)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/sitemodules/profiles/templates/gitea/app.ini.epp b/sitemodules/profiles/templates/gitea/app.ini.epp
index 0aa9945..a1854f2 100644
--- a/sitemodules/profiles/templates/gitea/app.ini.epp
+++ b/sitemodules/profiles/templates/gitea/app.ini.epp
@@ -81,6 +81,12 @@ COOKIE_SECURE = true
DOMAIN = <%= $gitea_fqdn %>
SAME_SITE = strict
+[migrations]
+ALLOW_LOCALNETWORKS = true
+
+[webhook]
+ALLOWED_HOST_LIST = external,private
+
[log]
MODE = file,console
ENABLE_ACCESS_LOG = true
diff --git a/sitemodules/profiles/templates/squid/squid.conf.epp b/sitemodules/profiles/templates/squid/squid.conf.epp
index cab60bf..d071279 100644
--- a/sitemodules/profiles/templates/squid/squid.conf.epp
+++ b/sitemodules/profiles/templates/squid/squid.conf.epp
@@ -13,3 +13,4 @@ http_access <%= $access_rule -%>
<% } %>
maximum_object_size 500 MB
+cache_dir aufs /var/spool/squid 20000 16 256
diff --git a/sitemodules/roles/manifests/authserver.pp b/sitemodules/roles/manifests/authserver.pp
new file mode 100644
index 0000000..792bc71
--- /dev/null
+++ b/sitemodules/roles/manifests/authserver.pp
@@ -0,0 +1,29 @@
+# Class: roles::authserver
+# ========================
+#
+# This class defines the authserver role for a Hydra OAuth2/OpenID connect API
+# server used for authentication/authorization.
+# You should assign this class using hiera or via an ENC.
+#
+# Examples
+# --------
+#
+# @example
+# class { 'roles::authserver': }
+#
+# Authors
+# -------
+#
+# Jan Dittberner <jandd@cacert.org>
+#
+# Copyright
+# ---------
+#
+# Copyright 2022 Jan Dittberner
+#
+class roles::authserver {
+ include profiles::base
+ include profiles::rsyslog
+ include profiles::icinga2_agent
+}
+
diff --git a/sitemodules/roles/manifests/idp.pp b/sitemodules/roles/manifests/idp.pp
new file mode 100644
index 0000000..2878931
--- /dev/null
+++ b/sitemodules/roles/manifests/idp.pp
@@ -0,0 +1,29 @@
+# Class: roles::idp
+# ========================
+#
+# This class defines the idp role for an OAuth2/OpenID identity provider
+# used for authentication/authorization.
+# You should assign this class using hiera or via an ENC.
+#
+# Examples
+# --------
+#
+# @example
+# class { 'roles::idp': }
+#
+# Authors
+# -------
+#
+# Jan Dittberner <jandd@cacert.org>
+#
+# Copyright
+# ---------
+#
+# Copyright 2022 Jan Dittberner
+#
+class roles::idp {
+ include profiles::base
+ include profiles::rsyslog
+ include profiles::icinga2_agent
+}
+