view manifests/templates.pp @ 476:b0531370e183

Avoid fully controllable rewrite prefix Apache doesn't guarantee that rewrites are URLs or paths within the prefix and will process them rather than error, so close a fully controllable rewrite prefix: https://blog.orange.tw/2024/08/confusion-attacks-en.html?m=1
author IBBoard <dev@ibboard.co.uk>
date Sun, 11 Aug 2024 13:29:59 +0100
parents 9437c6ffa07c
children 2c3e745be8d2
line wrap: on
line source

# Make sure packages come after their repos
File<| tag == 'repo-config' |>
-> anchor { 'Repo-config': }
-> YumRepo<| |>
-> Apt::Source<| |>
-> anchor { 'Repos': }
-> Package<| |>

# Make sure all files are in  place before starting services
# FIXME: Title matches are to fix a dependency cycle
File<| tag != 'post-service' and title != '/etc/sysconfig/ip6tables' and title != '/etc/sysconfig/iptables' |>
-> anchor { 'Pre-Service Files': }
-> Service<| |>

# Set some shortcut variables
#$os = $operatingsystem
$osver = $operatingsystemmajrelease
$server = ''


class basenode {
	include sudo

	include defaultusers
	include logwatch

	file { '/etc/puppet/hiera.yaml':
		ensure => present,
		content => "
# Let the system set defaults
version: 5
",
	}
	service { 'puppet':
		ensure => stopped,
		enable => false,
	}
	if $operatingsystem == 'Ubuntu' {
		package { 'locales':
			ensure => present
		} ->
		file { '/etc/locale.gen':
			ensure => present,
			content => "en_GB.UTF-8 UTF-8",
			notify => Exec['Regen locales']
		}
		exec { 'Regen locales':
			command => 'locale-gen',
			refreshonly => true
		}
		# Don't waste space with Snap and do everything properly with system packages
		[ 'lxd', 'core18', 'core20', 'snapd'].each |$snap| {
			exec { "remove $snap snap package":
				command => "snap remove $snap",
				onlyif => "which snap && snap list $snap",
				tag => 'snap',
			}
		}
		Exec<| tag == 'snap' |> ->
		package { 'snapd':
			ensure => purged,
		}
	}
}

class basevpsnode (
	$primary_ip,
	$gateway_ip = undef,
	$proxy_4to6_ip_prefix = undef,
	$proxy_upstream = undef,
	$nat64_ranges = [],
	$mailserver,
	$imapserver,
	$mailrelays = [],
	$firewall_cmd = 'iptables',
	) {

	if $firewall_cmd == 'iptables' {
		class { 'vpsfirewall':
			fw_protocol => $primary_ip =~ Stdlib::IP::Address::V6 ? { true => 'IPv6', default => 'IPv4'},
		}
	}

	#VPS is a self-mastered Puppet machine, so bodge a Hosts file
	if $primary_ip =~ Stdlib::IP::Address::V6 {
		$lo_ip = '::1'
	} else {
		$lo_ip = '127.0.0.1'
	}
	file { '/etc/hosts':
		ensure => present,
		content => "${lo_ip}   localhost\n${primary_ip} ${fqdn}",
	}

	if $proxy_4to6_ip_prefix != undef {
		# …:1 to …:9 for websites, …:10 for mail
		# Note: IPv6 is hexadecimal and so 0x10 is not in a /124 netblock with 0x01 to 0x09!
		$ipv6_addresses = Integer[1, 10].map |$octet| { "$proxy_4to6_ip_prefix:$octet" }
		if $operatingsystem == 'Ubuntu' {
			# Ubuntu can't parse the existing file, so we need to brute-force it with a template
			file { "/etc/network/interfaces.d/eth0":
				content => epp('privat/eth0.epp',
					{
						default_address => $primary_ip,
						gateway_address => $gateway_ip,
					}
				),
				notify => Exec["restart_networking"],
			}
			# …:1 to …:9 for websites, …:10 for mail
			Integer[1, 10].each |$octet| {
				file { "/etc/network/interfaces.d/eth0:$octet":
					content => epp('privat/eth0-alias.epp',
						{
							prefix_address => $proxy_4to6_ip_prefix,
							octet => $octet,
						}
					),
					notify => Exec["restart_networking"],
				}
			}
		}
		else {
			# …:1 to …:9 for websites, …:10 for mail
			$ipv6_addresses = Integer[1, 10].map |$octet| { "$proxy_4to6_ip_prefix:$octet" }
			$ipv6_secondaries = join($ipv6_addresses, " ")

			augeas {'IPv6 secondary addresses':
				context => "/files/etc/sysconfig/network-scripts/ifcfg-eth0",
				changes => "set IPV6ADDR_SECONDARIES '\"$ipv6_secondaries\"'",
				notify => Exec["restart_networking"],
			}
		}

		Exec { 'restart_networking':
			command => 'systemctl restart networking',
			refreshonly => true,
		}
	}

	require repos
	include basenode
	include privat
	include dnsresolver
	include ::privat::params
	class { '::ssh':
		sshd_config_port => $::privat::params::ssh_port[$::fqdn]
	}
	include vcs::server
	include vcs::client
	class { 'webserver':
		primary_ip => $primary_ip,
		proxy_4to6_ip_prefix => $proxy_4to6_ip_prefix,
		proxy_4to6_mask => 124,
		proxy_upstream => $proxy_upstream,
	}
	include cronjobs
	include logrotate
	class { 'fail2ban':
		firewall_cmd => $firewall_cmd,
	}
	include tools
	class { 'email':
		mailserver => $mailserver,
		imapserver => $imapserver,
		mailserver_ip => $primary_ip,
		proxy_ip => $proxy_4to6_ip_prefix != undef ? { true => "${proxy_4to6_ip_prefix}:10", default => undef },
		proxy_upstream => $proxy_upstream,
		nat64_ranges => $nat64_ranges,
		mailrelays => $mailrelays,
	}
}

## Classes to allow facet behaviour using preconfigured setups of classes

class vpsfirewall ($fw_protocol) {
	resources { "firewall":
		purge => false,
	}
	class { "my_fw":
		ip_version => $fw_protocol,
	}
	# Control what does and doesn't get pruned in the main filter chain
	firewallchain { "INPUT:filter:$fw_protocol":
		purge => true,
		ignore => [
			'-j f2b-[^ ]+$',
			'^(:|-A )f2b-',
			'--comment "Great Firewall of China"',
			'--comment "Do not purge',
			],
	}
	include privat::params
	$ssh_ports = $::privat::params::ssh_port[$::fqdn]
	firewall { '090 Allow SSH':
		dport => $ssh_ports - 22,
		proto => 'tcp',
		action => 'accept',
	}
	if ($fw_protocol != "IPv6") {
		firewall { '010 Whitelist Googlebot':
			source => '66.249.64.0/19',
			dport => [80,443],
			proto => tcp,
			action => accept,
		}
		# Block a spammer hitting our contact forms (also on StopForumSpam list A LOT)
		firewall { '099 Blacklist spammers 1':
			source => '107.181.78.172',
			dport => [80, 443],
			proto => tcp,
			action => 'reject',
		}
		firewall { '099 Blacklist IODC bot':
			# IODC bot makes too many bad requests, and contact form is broken
			# They don't publish a robots.txt name, so firewall it!
			source => '86.153.145.149',
			dport => [ 80, 443 ],
			proto => tcp,
			action => 'reject',
		}
		firewall { '099 Blacklist Baidu Brazil':
			#Baidu got a Brazilian netblock and are hitting us hard
			#Baidu doesn't honour "crawl-delay" in robots.txt
			#Baidu gets firewalled
			source => '131.161.8.0/22',
			dport => [ 80, 443 ],
			proto => tcp,
			action => 'reject',
		}
	}
	firewallchain { "GREATFIREWALLOFCHINA:filter:$fw_protocol":
		ensure => present,
	}
	firewall { '050 Check our Great Firewall Against China':
		chain => 'INPUT',
		jump => 'GREATFIREWALLOFCHINA',
	}
	firewallchain { "Fail2Ban:filter:$fw_protocol":
		ensure => present,
	}
	firewall { '060 Check Fail2Ban':
		chain => 'INPUT',
		jump => 'Fail2Ban',
	}
}

class dnsresolver {
	package { 'unbound':
		ensure => present,
	}
	package { 'named':
		ensure => absent,
	}

	service { 'named':
		ensure => stopped,
		enable => false,
	}
	service { 'unbound':
		ensure => running,
		enable => true,
	}

	file { '/etc/named.conf':
		ensure => absent,
	}
	file { '/etc/unbound/unbound.conf':
		ensure => present,
		source => [
                        "puppet:///common/unbound.conf-${::hostname}",
                        "puppet:///common/unbound.conf",
                ],
		require => Package['unbound'],
		notify => Service['unbound'],
	}
	file { ['/etc/NetworkManager', '/etc/NetworkManager/conf.d']:
		ensure => directory
	}
	file { '/etc/NetworkManager/conf.d/local-dns-resolver.conf':
		ensure => present,
		content => "[main]
dns=none",
	}

	file { '/etc/sysconfig/named':
		ensure => absent,
	}
	file { '/etc/resolv.conf':
		ensure => file,
		# "ipaddress" key only exists for machines with IPv4 addresses
		content => has_key($facts, 'ipaddress') ? { true => "nameserver 127.0.0.1", default => "nameserver ::1" },
		require => Service['unbound'],
		tag => 'post-service',
	}
}

class repos {
	include ::python::params
	if $operatingsystem == 'CentOS' {
		yumrepo { 'epel':
			mirrorlist => 'https://mirrors.fedoraproject.org/metalink?repo=epel-$releasever&arch=$basearch',
			descr => "Extra Packages for Enterprise Linux",
			enabled => 1,
			failovermethod => absent,
			gpgcheck => 1,
			gpgkey => "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-$osver",
		}
		file { "/etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-$osver":
			ensure => present,
			source => "puppet:///common/RPM-GPG-KEY-EPEL-$osver",
			tag => 'repo-config',
		}
		yumrepo { 'ibboard':
			baseurl => 'https://download.opensuse.org/repositories/home:/IBBoard:/server/CentOS_$releasever/',
			descr => 'Extra packages from IBBoard',
			enabled => 1,
			gpgcheck => 1,
			gpgkey => 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-ibboard',
		}
		file { '/etc/pki/rpm-gpg/RPM-GPG-KEY-ibboard':
			ensure => present,
			source => 'puppet:///common/RPM-GPG-KEY-ibboard',
			tag => 'repo-config',
		}
		yumrepo { 'webtatic':
			ensure => absent,
		}
		file { '/etc/pki/rpm-gpg/RPM-GPG-KEY-webtatic-andy':
			ensure => absent,
		}
		file { '/etc/pki/rpm-gpg/RPM-GPG-KEY-webtatic-el7':
			ensure => absent,
		}
		# Python requires the `devel` package on CentOS, but by default the module tries to uninstall it
		$dev = 'present'
	}
	else {
		# Other distros can take the default devel status
		$dev = $::python::params::dev
		# CentOS-like distros have PKI by default. Others need it creating.
		file { '/etc/pki/':
			ensure => directory,
		}
		file { '/etc/apt/trusted.gpg.d/home_IBBoard_server.gpg':
			# https://download.opensuse.org/repositories/home:/IBBoard:/server/xUbuntu_22.04/Release.key
			# Then GPG exported with `cat Release.key | gpg --dearmour -o common/home_IBBoard_server.gpg
			source => 'puppet:///common/home_IBBoard_server.gpg'
		}

		apt::source {
			'ibboard':
				location => 'http://download.opensuse.org/repositories/home:/IBBoard:/server/xUbuntu_22.04/',
				release => '/',
				repos => '',
				keyring => '/etc/apt/trusted.gpg.d/home_IBBoard_server.gpg',
		}
	}

	if $operatingsystem == 'CentOS' and versioncmp($operatingsystemrelease, '8') >= 0 {
		# The following may possibly work to ensure a CentOS Streams install.
		# Or it might fail for inexplicable reasons.
		# FIXME: Should be "centos-release-stream" to migrate (provides repos), but then that gets replaced by centos-stream-release,
		# which Puppet doesn't recognise as the same and so keeps trying to re-install. May need an "unless" or maybe "allow_virtual"
		package { 'centos-stream-release':
			ensure => installed,
			notify => Exec['migrate to streams'];
		}
		exec { 'migrate to streams':
			command => '/usr/bin/dnf swap centos-linux-repos centos-stream-repos; /usr/bin/dnf distro-sync -y',
			refreshonly => true
		}
	}

	class { 'python':
		ensure => 'present',
		version => 'python3',
		pip => 'present',
		use_epel => false,
		dev => $dev,
	}
}

class tools {
	$packages = [ 'sqlite', 'bash-completion', 'nano', 'zip', 'bzip2', 'mlocate', 'patch', 'tmux', 'wget', 'rsync' ]
	package { $packages:
		ensure => installed;
	}
	if $osfamily == 'RedHat' {
		package { 'yum-utils':
			ensure => installed
		}
	}
	elsif $osfamily == 'Debian' {
		package { 'dnsutils':
			ensure => installed
		}
	}
}

class logrotate {
	package { 'logrotate':
		ensure => installed;
	}
	file { '/etc/logrotate.d/httpd':
		ensure => present,
		source => 'puppet:///common/logrotate-httpd',
		require => Package['logrotate'],
	}
}

class logwatch {
	package { 'logwatch':
		ensure => installed;
	}
	File {
		ensure => present,
		require => Package['logwatch'],
	}
	file { '/etc/cron.daily/0logwatch':
		source => 'puppet:///common/0logwatch';
	}
	$logwatch_dirs = [
		'/etc/logwatch/',
		'/etc/logwatch/conf/',
		'/etc/logwatch/conf/logfiles/',
		'/etc/logwatch/conf/services/',
		'/etc/logwatch/scripts/',
		'/etc/logwatch/scripts/services/',
	]
	file { $logwatch_dirs:
		ensure => directory,
	}
	file { '/etc/logwatch/conf/logwatch.conf':
		content => 'Detail = Med',
	}
	file { '/etc/logwatch/conf/logfiles/http.conf':
		source => 'puppet:///common/logwatch/logfiles_http.conf',
	}
	file { '/etc/logwatch/conf/logfiles/http-error.conf':
		source => 'puppet:///common/logwatch/logfiles_http-error.conf',
	}
	file { '/etc/logwatch/conf/logfiles/mysql.conf':
		source => 'puppet:///common/logwatch/logfiles_mysql.conf',
	}
	file { '/etc/logwatch/conf/logfiles/php.conf':
		source => 'puppet:///common/logwatch/logfiles_php.conf',
	}
	file { '/etc/logwatch/conf/services/php.conf':
		source => 'puppet:///common/logwatch/services_php.conf',
	}
	file { '/etc/logwatch/conf/services/contact-form.conf':
		source => 'puppet:///common/logwatch/services_contact-form.conf',
	}
	file { '/etc/logwatch/scripts/services/dovecot':
		source => 'puppet:///common/logwatch/dovecot',
	}
	file { '/etc/logwatch/scripts/services/postfix':
		source => 'puppet:///common/logwatch/postfix',
	}
	file { '/etc/logwatch/scripts/services/systemd':
		source => 'puppet:///common/logwatch/systemd',
	}
	file { '/etc/logwatch/scripts/services/php':
		source => 'puppet:///common/logwatch/php',
	}
	file { '/etc/logwatch/scripts/services/contact-form':
		source => 'puppet:///common/logwatch/contact-form',
	}
}

#Our web server with our configs, not just a stock one
class webserver (
	$primary_ip,
	$proxy_4to6_ip_prefix = undef,
	$proxy_4to6_mask = undef,
	$proxy_upstream = undef,
	) {

	#Setup base website parameters
	class { 'website':
		base_dir => '/srv/sites',
		primary_ip => $primary_ip,
		proxy_4to6_ip_prefix => $proxy_4to6_ip_prefix,
		proxy_4to6_mask => $proxy_4to6_mask,
		proxy_upstream => $proxy_upstream,
		default_owner => $defaultusers::default_user,
		default_group => $defaultusers::default_user,
		default_tld => 'co.uk',
		default_extra_tlds => [ 'com' ],
	}

	if $operatingsystem == 'CentOS' {
		$php_suffix = ''
		$ini_prefix_20 = '20-'
		$ini_prefix_30 = '30-'
		$ini_prefix_40 = '40-'
		$variant_prefix = 'php-'
		$extra_prefix = 'pecl-'
		$extra_extras = {
			'posix' => {
			        ini_prefix => '20-',
			},
			# Sodium has been bundled since 7.2, but CentOS packages it separately
			'sodium' => {
			        ini_prefix => '20-',
			},
			# JSON is integrated into PHP 8+ and so it's only a plugin in CentOS
			'json' => {
			        ini_prefix => '20-',
			},
		}
		if versioncmp($operatingsystemrelease, '8') >= 0 {
			yumrepo { 'remirepo-safe':
				mirrorlist => 'http://cdn.remirepo.net/enterprise/$releasever/safe/$basearch/mirror',
				descr => "Extra CentOS packages from Remi",
				enabled => 1,
				failovermethod => absent,
				gpgcheck => 1,
				gpgkey => 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-remi',
			}
			yumrepo { 'remirepo-php':
				mirrorlist => 'http://cdn.remirepo.net/enterprise/8/modular/$basearch/mirror',
				descr => 'Remi\'s Modular repository for Enterprise Linux 8 - $basearch',
				enabled => 1,
				failovermethod => absent,
				gpgcheck => 1,
				gpgkey => 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-remi',
			}
			file { '/etc/pki/rpm-gpg/RPM-GPG-KEY-remi':
				ensure => present,
				source => 'puppet:///common/RPM-GPG-KEY-remi.el8',
				tag => 'repo-config',
			}
		} else {
			yumrepo { 'remirepo-safe':
				mirrorlist => 'http://cdn.remirepo.net/enterprise/$releasever/safe/mirror',
				descr => "Extra CentOS packages from Remi",
				enabled => 1,
				failovermethod => absent,
				gpgcheck => 1,
				gpgkey => 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-remi',
			}
			yumrepo { 'remirepo-php':
				mirrorlist => 'http://cdn.remirepo.net/enterprise/$releasever/php74/mirror',
				descr => "PHP7.4 for CentOS from Remi",
				enabled => 1,
				failovermethod => absent,
				gpgcheck => 1,
				gpgkey => 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-remi',
			}
			file { '/etc/pki/rpm-gpg/RPM-GPG-KEY-remi':
				ensure => present,
				source => 'puppet:///common/RPM-GPG-KEY-remi',
				tag => 'repo-config',
			}
		}
	}
	elsif $operatingsystem == 'Ubuntu' {
		$php_suffix = ''
		$ini_prefix_20 = ''
		$ini_prefix_30 = ''
		$ini_prefix_40 = ''
		$variant_prefix = 'php-'
		$extra_prefix = ''
		# Work around constant re-install by enabling virtual packages
		# https://github.com/voxpupuli/puppet-php/issues/387
		Package {
			allow_virtual => true
		}
		$extra_extras = {
			'xdebug' => {
				# Occasional profiling retained in case I want to dig into
				# Wordpress page load inefficiencies any more
				ensure => absent,
#				settings => {
#					"xdebug.mode" => "profile",
#					"xdebug.start_with_request" => "trigger",
#				}
			}
		}
	}

	#Configure the PHP version to use
	class { 'website::php':
		suffix => $php_suffix,
		module => ($operatingsystem == 'CentOS' and versioncmp($operatingsystemrelease, '8') >= 0) ? { true => 'remi-7.4', default => undef },
		extras => {
			'bcmath' => {
			        ini_prefix => $ini_prefix_20,
			},
			'curl' => {
			        ini_prefix => $ini_prefix_20,
			},
			'dom' => {
			        ini_prefix => $ini_prefix_20,
			},
			'enchant' => {
			        ini_prefix => $ini_prefix_20,
			},
			'exif' => {
			        ini_prefix => $ini_prefix_20,
			},
			'fileinfo' => {
			        ini_prefix => $ini_prefix_20,
			},
			'gmp' => {
			        ini_prefix => $ini_prefix_20,
			},
			'intl' => {
			        ini_prefix => $ini_prefix_20,
			},
			'mysqlnd' => {
				ini_prefix => $ini_prefix_20,
			},
			'pdo' => {
				ini_prefix => $ini_prefix_20,
			},
			'simplexml' => {
			        ini_prefix => $ini_prefix_20,
			},
			'soap' => {
			        ini_prefix => $ini_prefix_20,
			},
			'xmlwriter' => {
			        ini_prefix => $ini_prefix_20,
			},
			'mysqli' => {
				ini_prefix => $ini_prefix_30,
			},
			'pdo_mysql' => {
				ini_prefix => $ini_prefix_30,
				# Provided by the php-mysql package in CentOS and declared with "Provides"
				# And Ubuntu is the same but without the "Provides"
				provider => "none",
			},
			'xmlreader' => {
				ini_prefix => $ini_prefix_30,
			},
			'zip' => {
			        ini_prefix => $ini_prefix_30,
				package_prefix => "${variant_prefix}${extra_prefix}"
			},
			'apcu' => {
			        ini_prefix => $ini_prefix_40,
				package_prefix => "${variant_prefix}${extra_prefix}"
			},
			'imagick' => {
			        ini_prefix => $ini_prefix_40,
				package_prefix => "${variant_prefix}${extra_prefix}"
			},
		} + $extra_extras,
	}

	#Setup MySQL, using (private) templates to make sure that we set non-std passwords and a default user
	if $operatingsystem == 'CentOS' {
		if versioncmp($operatingsystemrelease, '7') >= 0 {
			$mysqlpackage = 'mariadb'
			$mysqlsuffix = ''

			# Required for SELinux rule setting/status checks
			if versioncmp($operatingsystemrelease, '8') >= 0 {
				$semanage_package_name = 'policycoreutils-python-utils'
			} else {
				$semanage_package_name = 'policycoreutils-python'
			}

			package { 'policycoreutils-python':
				name => $semanage_package_name,
				ensure => present,
			}

			$extra_packages = [
				'perl-Sys-Syslog', #Required for Perl SPF checking
			]

			package { $extra_packages:
				 ensure => installed
			}
		}
		else {
			$mysqlpackage = 'mysql'
			$mysqlsuffix = '55w'
		}
		$phpmysqlsuffix = 'nd'
	}
	elsif $operatingsystem == 'Ubuntu' {
		$mysqlpackage = 'mariadb'
		$mysqlsuffix = ''
		$phpmysqlsuffix = ''
	}
	else {
		fail("No MySQL support for ${operatingsystem}")
	}
	include ::defaultusers::params
	class { 'website::mysql':
		mysqluser => $::defaultusers::params::mysql_user,
		mysqlpassword => $::defaultusers::params::mysql_password,
		mysqlprefix => $mysqlpackage,
		mysqlsuffix => $mysqlsuffix,
		phpsuffix => $php_suffix,
		phpmysqlsuffix => $phpmysqlsuffix
	}

	# Additional supporting directories that aren't served as sites
	file { [ '/srv/sites/errorhandling', '/srv/sites/private', '/srv/cms' ]:
		ensure => directory,
	}
}

class ibboardvpsnode (
	$primary_ip,
	$gateway_ip = undef,
	$proxy_4to6_ip_prefix = undef,
	$proxy_upstream = undef,
	$nat64_ranges = [],
	$mailserver,
	$imapserver,
	$mailrelays = [],
	$firewall_cmd = 'iptables',
	){
	class { 'basevpsnode':
		primary_ip => $primary_ip,
		gateway_ip => $gateway_ip,
		proxy_4to6_ip_prefix => $proxy_4to6_ip_prefix,
		proxy_upstream => $proxy_upstream,
		nat64_ranges => $nat64_ranges,
		mailserver => $mailserver,
		imapserver => $imapserver,
		mailrelays => $mailrelays,
		firewall_cmd => $firewall_cmd,
	}

	# Set timezone to something sensible
	file { "/etc/localtime":
		ensure => 'link',
		target => '/usr/share/zoneinfo/Europe/London',
	}

	file { "${apache::conf_dir}/conf.d/greatfirewallagainstchina.conf":
		ensure => file,
	}

	# Debian doesn't handle sensible depends like module names because of the underscore
	# So we need to use the package name
	package { "mod_cspnonce":
		name => $osfamily == 'Debian' ? { true => "libapache2-mod-cspnonce", default => "mod_cspnonce" },
		ensure => installed,
	}

	# Common modules used by multiple sites (mod_auth_basic is safe because we HTTPS all the things)
	$mods = [
		'auth_basic',
		'authn_core',
		'authn_file',
		'authz_user',
		'deflate',
		'xsendfile',
		'cspnonce'
		]
	apache::mod {
		$mods:;
	}

	#Configure our sites, using templates for the custom fragments where the extra content is too long
	class { "devsite":
		proxy_4to6_ip => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:01", default => undef }
	}
	class { "adminsite":
		proxy_4to6_ip => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:02", default => undef }
	}
	website::https::multitld { 'www.ibboard':
		proxy_4to6_ip => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:03", default => undef },
		custom_fragment => template("privat/apache/ibboard.fragment"),
		letsencrypt_name => 'ibboard.co.uk',
		csp_override => {
			"report-uri" => "https://ibboard.report-uri.com/r/d/csp/enforce",
			"default-src" => "'none'",
			"img-src" => "'self' https://live.staticflickr.com/",
			"script-src" => "'self' https://auth-server.herokuapp.com/proxy https://api.tumblr.com/",
			"style-src" => "'self'",
			"font-src" => "'self'",
			"form-action" => "'self'",
			"connect-src" => "'self' https://api.tumblr.com/",
		}
	}
	website::https::redir { 'mail.ibboard.co.uk':
		proxy_4to6_ip => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:03", default => undef },
		redir => 'https://ibboard.co.uk/',
		docroot => "${website::basedir}/ibboard",
		letsencrypt_name => 'ibboard.co.uk',
		separate_log => true,
	}
	website::https::redir { 'imap.ibboard.co.uk':
		proxy_4to6_ip => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:03", default => undef },
		redir => 'https://ibboard.co.uk/',
		docroot => "${website::basedir}/ibboard",
		letsencrypt_name => 'ibboard.co.uk',
		separate_log => true,
	}
	class { "hiveworldterrasite":
		proxy_4to6_ip => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:04", default => undef }
	}
	class { "bdstrikesite":
		proxy_4to6_ip => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:05", default => undef }
	}
	website::https::multitld { 'www.abiknight':
		proxy_4to6_ip => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:06", default => undef },
		custom_fragment => "$website::htmlphpfragment
	ErrorDocument 404 /error.php",
		letsencrypt_name => 'abiknight.co.uk',
	}
	class { "webmailpimsite":
		proxy_4to6_ip_pim => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:08", default => undef },
		proxy_4to6_ip_webmail => $proxy_4to6_ip_prefix != undef ? { true => "$proxy_4to6_ip_prefix:09", default => undef },
	}
}

class adminsite ($proxy_4to6_ip) {
	apache::mod { 'info':; 'status':; 'cgi':; }
	website::https::multitld { 'admin.ibboard':
		proxy_4to6_ip => $proxy_4to6_ip,
		force_no_index => false,
		ssl_ca_chain => '',
		csp_override => {
			"report-uri" => "https://ibboard.report-uri.com/r/d/csp/enforce",
			"img-src" => "'self' data:",
		},
		csp_report_override => {
			"img-src" => "'self' data:",
		},
		custom_fragment => template("privat/apache/admin.fragment"),
	}
	if $osfamily == 'RedHat' {
		$cron_user = 'apache'
	}
	elsif $osfamily == 'Debian' {
		$cron_user = 'www-data'
	}
	cron { 'loadavg':
		command => '/usr/local/bin/run-loadavg-logger',
		user => $cron_user,
		minute => '*/6'
	}
	cron { 'awstats':
		command => '/usr/local/bin/update-awstats > /srv/sites/admin/awstats.log',
		user => $cron_user,
		hour => '*/6',
		minute => '0'
	}
}

class hiveworldterrasite ($proxy_4to6_ip) {
	website::https::multitld { 'www.hiveworldterra':
		proxy_4to6_ip => $proxy_4to6_ip,
		force_no_www => false,
		letsencrypt_name => 'hiveworldterra.co.uk',
		custom_fragment => template("privat/apache/hwt.fragment"),
	}
	website::https::multitld { 'forums.hiveworldterra': 
		proxy_4to6_ip => $proxy_4to6_ip,
		letsencrypt_name => 'forums.hiveworldterra.co.uk',
		custom_fragment => template("privat/apache/forums.fragment"),
	}
	website::https::multitld { 'skins.hiveworldterra':
		proxy_4to6_ip => $proxy_4to6_ip,
		letsencrypt_name => 'skins.hiveworldterra.co.uk',
		custom_fragment => template("privat/apache/skins.fragment"),
	}
	website::https::redir { 'hiveworldterra.ibboard.co.uk':
		proxy_4to6_ip => $proxy_4to6_ip,
		redir => 'https://www.hiveworldterra.co.uk/',
		docroot => "${website::basedir}/hiveworldterra",
		letsencrypt_name => 'hiveworldterra.ibboard.co.uk',
		separate_log => true,
	}
}
class bdstrikesite ($proxy_4to6_ip) {
	website::https::multitld { 'www.bdstrike':
		proxy_4to6_ip => $proxy_4to6_ip,
		docroot_owner => $defaultusers::secondary_user,
		docroot_group => 'editors',
		letsencrypt_name => 'bdstrike.co.uk',
		custom_fragment => template("privat/apache/bdstrike.fragment"),
		csp_override => {
			"report-uri" => "https://ibboard.report-uri.com/r/d/csp/enforce",
			"font-src" => "'self' https://fonts.gstatic.com/ https://s0.wp.com/i/fonts/inter/ data:",
			"img-src" => "'self' https://secure.gravatar.com/ https://ps.w.org/ https://s.w.org/ data:",
			"style-src" => "'self' https://fonts.googleapis.com/ 'unsafe-inline'",
			"connect-src" => "'self' https://www.sandbox.paypal.com/ https://www.paypal.com/",
			"frame-ancestors" => "'self'"
		},
		csp_report_override => {
			"report-uri" => "https://ibboard.report-uri.com/r/d/csp/reportOnly",
			"font-src" => "'self' https://fonts.gstatic.com/ https://s0.wp.com/i/fonts/inter/ data:", # TODO: What's generating it?
			"img-src" => "'self' https://secure.gravatar.com/ data:",
			"style-src" => "'self' https://fonts.googleapis.com/ https://ajax.googleapis.com/ajax/libs/jqueryui/ 'nonce-%{CSP_NONCE}e' 'unsafe-hashes' 'sha256-anQSeQoEnQnBulZOQkDOFf+e6xBIGmqh7M8YFT992co=' 'sha256-zJDyuABAg68wtWDFyIh+RRe+6Vm/r+BLwaNRCGNVyXI=' 'sha256-qMalr/MPLUDW4lX/rq/cGp1Eu/H0cu0Yg98pdu69Jxs=' 'sha256-mshqJ+hidJMRDeNLHknuDAeYLOPg2OTIIA3nZmHgi9U=' 'sha256-YnRUd/QjP/NuFgfjMHhNfMCqXh0RQIGdvQfMCOf6qkw=' 'sha256-EwdiFJgqhefinoeAymrWxOYW4kza2Ekos5MY0PlXYI0=' 'sha256-G4K9vh8e+37+l69S+lHTyX3CfcK95mQUgyxYPCb7uME=' 'sha256-t6oewASd7J1vBg5mQtX4hl8bg8FeegYFM3scKLIhYUc=' 'sha256-mAQYxa3mIYqoLBrm1zLu6sLajr8vUHVFLYNpl6dAakM=' 'sha256-A8foknjCsFBi1PlRehOrHq0pVySigUurqAUgZ2y2U8c=' 'sha256-biLFinpqYMtWHmXfkA1BPeCY0/fNt46SAZ+BBk5YUog=' 'sha256-WzSByVQ8yW/DKrr77TWVt7WEMzueRcfJZImOkjTBKmc=' 'sha256-efof3agGBAL/yN8TplyNbLEgDZ3wIGMK3UMYbe8slkA='",
			"script-src" => "'self' 'nonce-%{CSP_NONCE}e' 'sha256-hPnbct+H2uwUiwoh3kect6TJt4waDlLPfj47TO58lXc=' 'sha256-80Mr5Xc2f6hVSJwvFRRcNjAI9RMcnuTVAIzr6pIQswI=' 'sha256-zwGmIUR+Z6gWKbwoJ2Z3yGxI/XLETLqDqCRIV0qt/WA='",
			"connect-src" => "'self' https://www.sandbox.paypal.com/ https://www.paypal.com/",
		},
	}

	cron { 'wordpress_cron':
		# Run "php -f wp-cron.php" on a schedule so that we can auto-update
		# without giving Apache full write access!
		command => "/usr/local/bin/bdstrike-cron",
		user => $website::apache_user,
		minute => '*/15',
	}
}
class devsite ($proxy_4to6_ip) {
	if $operatingsystem == 'CentOS' and versioncmp($operatingsystemrelease, '8') >= 0 {
		$package_name = 'python3-mod_wsgi'
		$mod_path = 'mod_wsgi_python3.so'
	} else {
		$package_name = undef
		$mod_path = undef
	}
	class { 'apache::mod::wsgi':
		  package_name => $package_name,
		  mod_path => $mod_path,
	}

	website::https::multitld { 'dev.ibboard':
		proxy_4to6_ip => $proxy_4to6_ip,
		#Make sure we're the first one hit for the tiny fraction of "no support" cases we care about (potentially Python for Mercurial!)
		# http://en.wikipedia.org/wiki/Server_Name_Indication#No_support
		priority => 1,
		letsencrypt_name => 'dev.ibboard.co.uk',
		custom_fragment => template("privat/apache/dev.fragment"),
		non_proxy_fragment => template("privat/apache/dev-nonproxy.fragment"),
		force_no_index => false,
	}
}

class webmailpimsite ($proxy_4to6_ip_pim, $proxy_4to6_ip_webmail) {
	# Webmail and Personal Information Management (PIM) sites
	website::https { 'webmail.ibboard.co.uk':
		proxy_4to6_ip => $proxy_4to6_ip_webmail,
		force_no_index => false,
		letsencrypt_name => 'webmail.ibboard.co.uk',
		custom_fragment => template("privat/apache/webmail.fragment"),
	}
	include ::apache::params
	website::https { 'pim.ibboard.co.uk':
		proxy_4to6_ip => $proxy_4to6_ip_pim,
		docroot_owner => $apache::params::user,
		docroot_group => 'editors',
		force_no_index => false,
		lockdown_requests => false,
		letsencrypt_name => 'pim.ibboard.co.uk',
		csp => false,
		csp_report => false,
		custom_fragment => template("privat/apache/pim.fragment"),
	}
	cron { 'owncloudcron':
		command => "/usr/local/bin/owncloud-cron",
		user => $apache::params::user,
		minute => '*/15',
	}
}

class email (
	$mailserver,
	$imapserver,
	$mailserver_ip,
	$proxy_ip = undef,
	$proxy_upstream = [],
	$nat64_ranges = [],
	$mailrelays = [],
	){
	class { 'postfix':
		mailserver => $mailserver,
		mailserver_ip => $mailserver_ip,
		mailserver_proxy => $proxy_ip,
		proxy_upstream => $proxy_upstream,
		mailrelays => $mailrelays,
		nat64_ranges => $nat64_ranges,
		protocols  => $mailserver_ip =~ Stdlib::IP::Address::V6 ? { true => 'all', default => 'ipv4' },
	}
	class { 'dovecot':
		imapserver => $imapserver,
		imapserver_ip => $mailserver_ip,
		imapserver_proxy => $proxy_ip,
		proxy_upstream => $proxy_upstream,
	}
	# Unspecified SpamAssassin config dependencies that started
	# showing up as errors in our logs
	if $osfamily == 'RedHat' {
		$spamassassin_deps = ['perl-File-MimeInfo']
		$spamassassin_dir = '/etc/mail/spamassassin/'
		$amavis_config = '/etc/amavisd/amavisd.conf'
		$amavis_rundir = '/var/run/amavisd'
		$amavis_spooldir = '/var/spool/amavisd'
		$amavis_quarantinedir = '/var/spool/amavisd/quarantine'
		$amavis_service = 'amavisd'
		# CentOS has a Clam service, but we call on demand (Ubuntu doesn't have a service)
		service { 'clamd@amavisd':
			ensure => 'stopped',
			enable=> 'mask',
		}
	}
	elsif $osfamily == 'Debian' {
		$spamassassin_deps = ['libfile-mimeinfo-perl']
		$spamassassin_dir = '/etc/spamassassin/'
		$amavis_config = '/etc/amavis/conf.d/60-puppeted'
		$amavis_rundir = '/var/run/amavis'
		$amavis_spooldir = '/var/lib/amavis'
		$amavis_quarantinedir = '/var/spool/amavisd/quarantine'
		$amavis_service = 'amavis'
	}
	package { $spamassassin_deps:
		ensure => installed,
	}
	package { [ 'amavisd-new' ]:
		ensure => installed,
		tag => 'av',
	}
	service { $amavis_service:
		ensure => 'running',
		enable => 'true',
	}
	file { $amavis_config:
		ensure => present,
		content => epp('privat/postfix/amavis.conf.epp',
			{
				fqdn => $::fqdn,
				rundir => $amavis_rundir,
				spooldir => $amavis_spooldir,
				quarantinedir => $amavis_quarantinedir,
			}
		),
		tag => 'av',
	}
	file { "${spamassassin_dir}local.cf":
		ensure => present,
		source => 'puppet:///private/postfix/spamassassin-local.cf',
		tag => 'av',
	}
	file { "${spamassassin_dir}ole2macro.cf":
		ensure => present,
		source => 'puppet:///common/ole2macro.cf',
		tag => 'av',
	}
	file { "${spamassassin_dir}ole2macro.pm":
		ensure => present,
		source => 'puppet:///common/spamassassin-vba-macro-master/ole2macro.pm',
		tag => 'av',
	}
	Package<| tag == 'av' |> -> File<| tag == 'av' |>
	File<| tag == 'av' |> {
		notify => Service[$amavis_service],
	}
	cron { 'Postwhite':
		command => "/usr/local/bin/postwhite 2>&1| grep -vE '^(Starting|Recursively|Getting|Querying|Removing|Sorting|$)'",
		user => 'root',
		weekday => 0,
		hour => 2,
		minute => 0,
	}
}

class cronjobs {
	package { 'cron':
		ensure => installed,
		name => $operatingsystem == 'Ubuntu' ? { true => 'cron', default => 'cronie' }
	}
	# Add Mutt for scripts that send emails, but stop it clogging the disk by keeping copies of emails
	package { 'mutt':
		ensure => installed,
	}
	file { '/etc/Muttrc.local':
		content => 'set copy = no',
		require => Package['mutt'],
	}

	# General server-wide cron jobs
	Cron { user => 'root' }
	cron { 'backupalldbs':
		command => "/usr/local/bin/backupalldbs",
		monthday => "*/2",
		hour => "4",
		minute => "9"
	}
	# Only run the Great Firewall Against China on IPv4 (since we don't have an IPv6 list
	# and the PROXY forwards the IPs to services, but not at the network level)
	cron { 'greatfirewallofchina':
		command => '/usr/local/bin/update-great-firewall-of-china',
		ensure => has_key($facts, 'ipaddress') ? { true => "present", default => "absent" },
		hour => 3,
		minute => 30
	}
	cron { 'permissions':
		command => '/usr/local/bin/set-permissions',
		hour => 3,
		minute => 2
	}
	# Since we're only managing the local server, use our script that wraps "puppet apply" instead of PuppetMaster
	cron { 'puppet':
		command => '/usr/local/bin/puppet-apply | grep -v "Compiled catalog for\|Finished catalog run in\|Applied catalog in"',
		hour => '*/6',
		minute => 5
	}
	cron { 'purgecaches':
		command => "/usr/local/bin/purge-caches",
		hour => '4',
		minute => '15',
		weekday => '1',
	}
	# Notify of uncommitted files
	cron { 'check-mercurial-committed':
		command => "/usr/local/bin/check-hg-status",
		user => $defaultusers::default_user,
		hour => '4',
		minute => '20',
		weekday => '0-6/3', #Sunday, Wednesday and Saturday morning
	}
	# Notify of available updates
	cron { 'check-yum-updates':
		ensure => absent,
	}
	cron { 'check-for-updates':
		command => '/usr/local/bin/check-updates',
		hour => '4',
		minute => '30',
		weekday => '0-6/3', #Sunday, Wednesday and Saturday morning
	}
	# And check whether anything needs restarting
	cron { 'check-needs-restarting':
		command => '/usr/local/bin/needs-restarting',
		hour => '4',
		minute => '45',
		weekday => '0-6/3', #Sunday, Wednesday and Saturday morning
	}
}