Page MenuHomeDevCentral

No OneTemporary

diff --git a/_modules/node.py b/_modules/node.py
index 5a85ff1..44da6ff 100644
--- a/_modules/node.py
+++ b/_modules/node.py
@@ -1,434 +1,447 @@
# -*- coding: utf-8 -*-
# -------------------------------------------------------------
# Salt — Node execution module
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Project: Nasqueron
# Created: 2017-10-21
# Description: Functions related to the nodes' pillar entry
# License: BSD-2-Clause
# -------------------------------------------------------------
from salt.exceptions import CommandExecutionError, SaltCloudConfigError
from salt._compat import ipaddress
DEPLOY_ROLES = [
"devserver",
"salt-primary",
"viperserv",
"webserver-alkane",
"webserver-legacy",
]
+WITH_NGINX_ROLES = [
+ "webserver-core",
+ "paas-docker",
+]
+
+
def _get_all_nodes():
return __pillar__.get("nodes", {})
def get_all_properties(nodename=None):
"""
A function to get a node pillar configuration.
CLI Example:
salt * node.get_all_properties
"""
if nodename is None:
nodename = __grains__["id"]
all_nodes = _get_all_nodes()
if nodename not in all_nodes:
raise CommandExecutionError(
SaltCloudConfigError("Node {0} not declared in pillar.".format(nodename))
)
return all_nodes[nodename]
def get(key, nodename=None):
"""
A function to get a node pillar configuration key.
CLI Example:
salt * node.get hostname
"""
return _get_property(key, nodename, None)
def _explode_key(k):
return k.split(":")
def _get_first_key(k):
return _explode_key(k)[0]
def _strip_first_key(k):
return ":".join(_explode_key(k)[1:])
def _get_property(key, nodename, default_value, parent=None):
if parent is None:
parent = get_all_properties(nodename)
if ":" in key:
first_key = _get_first_key(key)
if first_key in parent:
return _get_property(
_strip_first_key(key), nodename, default_value, parent[first_key]
)
elif key in parent:
return parent[key]
return default_value
def get_list(key, nodename=None):
"""
A function to get a node pillar configuration.
Returns a list if found, or an empty list if not found.
CLI Example:
salt * node.list network:ipv4_aliases
"""
return _get_property(key, nodename, [])
def has(key, nodename=None):
"""
A function to get a node pillar configuration.
Returns a boolean, False if not found.
CLI Example:
salt * node.has network:ipv6_tunnel
"""
value = _get_property(key, nodename, False)
return bool(value)
def has_role(role, nodename=None):
"""
A function to determine if a node has the specified role.
Returns a boolean, False if not found.
CLI Example:
salt * node.has_role devserver
"""
return role in get_list("roles", nodename)
def filter_by_role(pillar_key, nodename=None):
"""
A function to filter a dictionary by roles.
The dictionary must respect the following structure:
- keys are role to check the current node against
- values are list of items
If a key '*' is also present, it will be included
for every role.
Returns a list, extending all the filtered lists.
CLI Example:
salt * node.filter_by_role web_content_sls
"""
roles = get_list("roles", nodename)
dictionary = __pillar__.get(pillar_key, {})
filtered_list = []
for role, items in dictionary.items():
if role == "*" or role in roles:
filtered_list.extend(items)
return filtered_list
def filter_by_name(pillar_key, nodename=None):
"""
A function to filter a dictionary by node name.
The dictionary must respect the following structure:
- keys are names to check the current node against
- values are list of items
If a key '*' is also present, it will be included
for every node.
Returns a list, extending all the filtered lists.
CLI Example:
salt * node.filter_by_name mars
"""
if nodename is None:
nodename = __grains__["id"]
dictionary = __pillar__.get(pillar_key, {})
filtered_list = []
for name, items in dictionary.items():
if name == "*" or name == nodename:
filtered_list.extend(items)
return filtered_list
def has_deployment(nodename=None):
"""
A function to determine if this server does continuous delivery.
"""
return any(role in DEPLOY_ROLES for role in get_list("roles", nodename))
+def has_nginx(nodename=None):
+ """
+ A function to determine if this server role should include nginx.
+ """
+ return any(role in WITH_NGINX_ROLES for role in get_list("roles", nodename))
+
+
def get_wwwroot(nodename=None):
"""
A function to determine the wwwroot folder to use.
Returns a string depending on the FQDN.
CLI Example:
salt * node.get_wwwroot
"""
hostname = _get_property("hostname", nodename, None)
if hostname is None:
raise CommandExecutionError(
SaltCloudConfigError(
"Node {0} doesn't have a hostname property".format(nodename)
)
)
if hostname.count(".") < 2:
return "wwwroot/{0}/www".format(hostname)
fqdn = hostname.split(".")
return "wwwroot/{1}/{0}".format(".".join(fqdn[0:-2]), ".".join(fqdn[-2:]))
def get_ipv6_list():
"""
A function to get a list of IPv6, enclosed by [].
Returns a string depending on the IPv6 currently assigned.
CLI Example:
salt * node.get_ipv6_list
"""
ipv6 = __grains__.get("ipv6")
return " ".join(["[" + ip + "]" for ip in ipv6])
def resolve_network():
"""
A function to determine canonical properties of networks
from the nodes pillar.
CLI Example:
salt * node.resolve_network
"""
network = {
"ipv4_address": "",
"ipv4_gateway": "",
}
private_network = network.copy()
is_private_network_stable = True
interfaces = _get_property("network:interfaces", __grains__["id"], {})
for interface_name, interface in interfaces.items():
if "ipv4" not in interface:
continue
ipv4 = interface["ipv4"]["address"]
if ipaddress.ip_address(ipv4).is_private:
target = private_network
else:
target = network
if target["ipv4_address"] != "":
continue
target["ipv4_address"] = ipv4
try:
target["ipv4_gateway"] = interface["ipv4"]["gateway"]
except KeyError:
pass
if network["ipv4_address"] == "":
main_network = private_network
else:
main_network = network
if private_network["ipv4_address"] == "":
is_private_network_stable = False
tunnels = resolve_gre_tunnels()
if tunnels:
tunnel = tunnels[0]
private_network = {
"ipv4_address": tunnel["src"],
"ipv4_gateway": tunnel["gateway"],
}
return main_network | {
"private_ipv4_address": private_network["ipv4_address"],
"private_ipv4_gateway": private_network["ipv4_gateway"],
"is_private_network_stable": is_private_network_stable,
}
def _resolve_gre_tunnels_for_router(network, netmask):
tunnels = []
for node, tunnel in __pillar__.get(f"{network}_gre_tunnels", {}).items():
tunnels.append(
{
"network": network,
"description": f"{network}_to_{node}",
"interface": tunnel["router"]["interface"],
"src": tunnel["router"]["addr"],
"dst": tunnel["node"]["addr"],
"netmask": netmask,
"icann_src": get("network")["canonical_public_ipv4"],
"icann_dst": get("network", node)["canonical_public_ipv4"],
}
)
return tunnels
def resolve_gre_tunnels():
"""
A function to get the GRE tunnels for a node
CLI Example:
salt * node.resolve_gre_tunnels
"""
gre_tunnels = []
for network, network_args in __pillar__.get("networks", {}).items():
if __grains__["id"] == network_args["router"]:
gre_tunnels += _resolve_gre_tunnels_for_router(
network, network_args["netmask"]
)
continue
tunnel = __salt__["pillar.get"](f"{network}_gre_tunnels:{__grains__['id']}")
if not tunnel:
continue
gre_tunnels.append(
{
"network": network,
"description": f"{network}_via_{network_args['router']}",
"interface": tunnel["node"].get("interface", "gre0"),
"src": tunnel["node"]["addr"],
"dst": tunnel["router"]["addr"],
"netmask": network_args["netmask"],
"gateway": network_args["default_gateway"],
"icann_src": get("network")["canonical_public_ipv4"],
"icann_dst": get("network", network_args["router"])[
"canonical_public_ipv4"
],
}
)
return gre_tunnels
def get_gateway(network):
# For tunnels, gateway is the tunnel endpoint
tunnel = __salt__["pillar.get"](f"{network}_gre_tunnels:{__grains__['id']}")
if tunnel:
return tunnel["router"]["addr"]
return __salt__["pillar.get"](f"networks:{network}:default_gateway")
def _get_static_route(cidr, gateway):
if __grains__["os_family"] == "FreeBSD":
return f"-net {cidr} {gateway}"
if __grains__["kernel"] == "Linux":
return f"{cidr} via {gateway}"
raise ValueError("No static route implementation for " + __grains__["os_family"])
def _get_default_route(gateway):
if __grains__["os_family"] == "FreeBSD":
return f"default {gateway}"
if __grains__["kernel"] == "Linux":
return f"default via {gateway}"
raise ValueError("No static route implementation for " + __grains__["os_family"])
def _get_interface_route(ip, interface):
if __grains__["os_family"] == "FreeBSD":
return f"-net {ip}/32 -interface {interface}"
if __grains__["kernel"] == "Linux":
return f"{ip} dev {interface}"
raise ValueError("No static route implementation for " + __grains__["os_family"])
def _get_routes_for_private_networks():
"""
Every node, excepted the routeur, should have a route
for the private network CIDR to the router.
For GRE tunnels, the gateway is the tunnel endpoint.
In other cases, the gateway is the main router (private) IP.
"""
routes = {}
for network, network_args in __pillar__.get("networks", {}).items():
if network_args["router"] == __grains__["id"]:
continue
gateway = get_gateway(network)
routes[f"private_{network}"] = _get_static_route(network_args["cidr"], gateway)
return routes
def get_routes():
routes = {}
interfaces = _get_property("network:interfaces", __grains__["id"], {})
for interface_name, interface in interfaces.items():
flags = interface.get("flags", [])
if "gateway" in interface.get("ipv4", {}):
gateway = interface["ipv4"]["gateway"]
if "ipv4_ovh_failover" in flags:
routes[f"{interface_name}_gateway"] = _get_interface_route(
gateway, interface["device"]
)
if __grains__["os_family"] != "RedHat":
# On RHEL/CentOS/Rocky, legacy network scripts take care of this with GATEWAY=
routes[f"{interface_name}_default"] = _get_default_route(gateway)
routes.update(_get_routes_for_private_networks())
return routes
diff --git a/_tests/scripts/bats/test_edit_acme_dns_accounts.sh b/_tests/scripts/bats/test_edit_acme_dns_accounts.sh
index ebd63c4..053a4fe 100755
--- a/_tests/scripts/bats/test_edit_acme_dns_accounts.sh
+++ b/_tests/scripts/bats/test_edit_acme_dns_accounts.sh
@@ -1,55 +1,55 @@
#!/usr/bin/env bats
-SCRIPT="../roles/paas-docker/letsencrypt/files/edit-acme-dns-accounts.py"
+SCRIPT="../roles/core/certificates/files/edit-acme-dns-accounts.py"
# -------------------------------------------------------------
# Arguments parsing
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@test "exit with error code if no arg provided" {
run $SCRIPT
[ "$status" -ne 0 ]
}
@test "exit with error code if command doesn't exist" {
run $SCRIPT somenonexistingcommand
[ "$status" -ne 0 ]
}
@test "exit with error code if no enough arg" {
run $SCRIPT import
[ "$status" -ne 0 ]
}
# -------------------------------------------------------------
# Import
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@test "can't import a file into itself" {
export ACME_ACCOUNTS=/dev/null
run $SCRIPT import /dev/null
[ "$output" = "You're trying to import /dev/null to itself" ]
[ "$status" -eq 2 ]
}
@test "can merge correctly two credentials files" {
ACME_ACCOUNTS=$(mktemp)
export ACME_ACCOUNTS
cp data/acmedns.json "$ACME_ACCOUNTS"
run $SCRIPT import data/acmedns-toimport.json
[ "$status" -eq 0 ]
isValid=0
run jsondiff "$ACME_ACCOUNTS" data/acmedns-merged.json
rm "$ACME_ACCOUNTS"
[ "$status" -eq 0 ]
[ "$output" = "{}" ] || isValid=1
if [ $isValid -ne 0 ]; then
echo "Non matching part according jsondiff:"
echo "$output"
fi
return $isValid
}
diff --git a/_tests/scripts/python/test_edit_acme_dns_accounts.py b/_tests/scripts/python/test_edit_acme_dns_accounts.py
index ab351e9..d65347b 100755
--- a/_tests/scripts/python/test_edit_acme_dns_accounts.py
+++ b/_tests/scripts/python/test_edit_acme_dns_accounts.py
@@ -1,47 +1,47 @@
#!/usr/bin/env python3
from importlib.machinery import SourceFileLoader
import os
import unittest
os.environ["ACME_ACCOUNTS"] = "/path/to/acmedns.json"
-path = "roles/paas-docker/letsencrypt/files/edit-acme-dns-accounts.py"
+path = "roles/core/certificates/files/edit-acme-dns-accounts.py"
script = SourceFileLoader("script", "../" + path).load_module()
class TestInstance(unittest.TestCase):
def setUp(self):
self.testAccounts = script.AcmeAccounts("/dev/null")
pass
def test_read_path_from_environment(self):
self.assertEqual("/path/to/acmedns.json", script.get_acme_accounts_path())
def test_accounts_are_empty_on_init(self):
self.assertEqual({}, self.testAccounts.accounts)
def test_add(self):
self.testAccounts.add("foo.tld", {})
self.assertEqual(1, len(self.testAccounts.accounts))
self.assertIn("foo.tld", self.testAccounts.accounts)
def test_remove_existing(self):
self.testAccounts.add("foo.tld", {})
self.assertTrue(self.testAccounts.remove("foo.tld"))
self.assertEqual(0, len(self.testAccounts.accounts))
def test_remove_non_existing(self):
self.assertFalse(self.testAccounts.remove("not-existing.tld"))
def test_merge(self):
accounts_to_merge = script.AcmeAccounts("/dev/null").add("bar.tld", {})
self.testAccounts.add("foo.tld", {}).merge_with(accounts_to_merge)
self.assertEqual(2, len(self.testAccounts.accounts))
if __name__ == "__main__":
unittest.main()
diff --git a/pillar/certificates/certificates.sls b/pillar/certificates/certificates.sls
deleted file mode 100644
index 58b558e..0000000
--- a/pillar/certificates/certificates.sls
+++ /dev/null
@@ -1,15 +0,0 @@
-# -------------------------------------------------------------
-# Salt — Let's encrypt certificates
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-# Project: Nasqueron
-# Created: 2017-04-27
-# License: Trivial work, not eligible to copyright
-# -------------------------------------------------------------
-
-# -------------------------------------------------------------
-# Certificates
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-certificates_letsencrypt:
- eglide:
- - www.eglide.org
diff --git a/pillar/top.sls b/pillar/top.sls
index 2c4ddf2..da52fc4 100644
--- a/pillar/top.sls
+++ b/pillar/top.sls
@@ -1,74 +1,73 @@
# -------------------------------------------------------------
# Salt configuration for Nasqueron servers
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Project: Nasqueron
# Created: 2016-04-10
# License: Trivial work, not eligible to copyright
# -------------------------------------------------------------
base:
'*':
- core.users
- core.groups
- core.network
- - certificates.certificates
- nodes.nodes
- nodes.forests
- hotfixes.roles
- services.monitoring-reporting
- services.table
- webserver.sites
- credentials.vault
cloudhugger:
- opensearch.software
- opensearch.clusters
complector:
- credentials.vault
# To provision services
- saas.rabbitmq
docker-002:
- notifications.config
- paas.docker
- saas.jenkins
- saas.phpbb
db-A-001:
- dbserver.cluster-A
db-B-001:
- dbserver.cluster-B
dwellers:
- paas.docker
- saas.airflow
- saas.jenkins
eglide:
- shellserver.quassel
hervil:
- mailserver.vimbadmin
ysul:
- devserver.repos
- saas.mediawiki
- viperserv.bots
- viperserv.fantoir
- webserver.labs
- webserver.wwwroot51
web-001:
- saas.mediawiki
- saas.wordpress
windriver:
- devserver.datacubes
- devserver.ports
- devserver.repos
- webserver.labs
- webserver.wwwroot51
diff --git a/roles/core/certificates/files/730.letsencrypt b/roles/core/certificates/files/730.letsencrypt
new file mode 100755
index 0000000..e68f24d
--- /dev/null
+++ b/roles/core/certificates/files/730.letsencrypt
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+# -------------------------------------------------------------
+# Fetch ports
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# Author : FreeBSD contributors
+# License: BSD-2-Clause
+# Source file: roles/core/certificates/files/730.letsencrypt
+# -------------------------------------------------------------
+#
+# <auto-generated>
+# This file is managed by our rOPS SaltStack repository.
+#
+# Changes to this file may cause incorrect behavior
+# and will be lost if the state is redeployed.
+# </auto-generated>
+# -------------------------------------------------------------
+
+
+# If there is a global system configuration file, suck it in.
+#
+if [ -r /etc/defaults/periodic.conf ]
+then
+ . /etc/defaults/periodic.conf
+ source_periodic_confs
+fi
+
+case "$daily_letsencrypt_enable" in
+ [Yy][Ee][Ss])
+ echo ""
+ echo "Running Let's Encrypt renewal:"
+
+ letsencrypt-renewal && rc=0 || rc=3;;
+
+ *) rc=0;;
+esac
+
+exit $rc
diff --git a/roles/paas-docker/letsencrypt/files/acme-dns-auth.py b/roles/core/certificates/files/acme-dns-auth.py
similarity index 97%
rename from roles/paas-docker/letsencrypt/files/acme-dns-auth.py
rename to roles/core/certificates/files/acme-dns-auth.py
index 6c913c3..150676e 100755
--- a/roles/paas-docker/letsencrypt/files/acme-dns-auth.py
+++ b/roles/core/certificates/files/acme-dns-auth.py
@@ -1,169 +1,169 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# -------------------------------------------------------------
# PaaS Docker
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Author: Joona Hoikkala
# License: MIT
-# Source file: roles/paas-docker/letsencrypt/files/acme-dns-auth.py
+# Source file: roles/core/certificates/files/acme-dns-auth.py
# -------------------------------------------------------------
#
# <auto-generated>
# This file is managed by our rOPS SaltStack repository.
#
# Changes to this file may cause incorrect behavior
# and will be lost if the state is redeployed.
# </auto-generated>
import json
import os
import requests
import sys
ACMEDNS_URL = "https://acme.nasqueron.org"
-STORAGE_PATH = "/etc/letsencrypt/acmedns.json"
+STORAGE_PATH = "/usr/local/etc/acmedns.json"
ALLOW_FROM = []
FORCE_REGISTER = False
DOMAIN = os.environ["CERTBOT_DOMAIN"]
if DOMAIN.startswith("*."):
DOMAIN = DOMAIN[2:]
VALIDATION_DOMAIN = "_acme-challenge." + DOMAIN
VALIDATION_TOKEN = os.environ["CERTBOT_VALIDATION"]
class AcmeDnsClient(object):
"""
Handles the communication with ACME-DNS API
"""
def __init__(self, acmedns_url):
self.acmedns_url = acmedns_url
def register_account(self, allowfrom):
"""Registers a new ACME-DNS account"""
if allowfrom:
# Include allowed networks to the registration call
reg_data = {"allowfrom": allowfrom}
res = requests.post(
self.acmedns_url + "/register", data=json.dumps(reg_data)
)
else:
res = requests.post(self.acmedns_url + "/register")
if res.status_code == 201:
# The request was successful
return res.json()
else:
# Encountered an error
msg = (
"Encountered an error while trying to register a new "
"acme-dns account. HTTP status {}, Response body: {}"
)
print(msg.format(res.status_code, res.text))
sys.exit(1)
def update_txt_record(self, account, txt):
"""Updates the TXT challenge record to ACME-DNS subdomain."""
update = {"subdomain": account["subdomain"], "txt": txt}
headers = {
"X-Api-User": account["username"],
"X-Api-Key": account["password"],
"Content-Type": "application/json",
}
res = requests.post(
self.acmedns_url + "/update", headers=headers, data=json.dumps(update)
)
if res.status_code == 200:
# Successful update
return
else:
msg = (
"Encountered an error while trying to update TXT record in "
"acme-dns. \n"
"------- Request headers:\n{}\n"
"------- Request body:\n{}\n"
"------- Response HTTP status: {}\n"
"------- Response body: {}"
)
s_headers = json.dumps(headers, indent=2, sort_keys=True)
s_update = json.dumps(update, indent=2, sort_keys=True)
s_body = json.dumps(res.json(), indent=2, sort_keys=True)
print(msg.format(s_headers, s_update, res.status_code, s_body))
sys.exit(1)
class Storage(object):
def __init__(self, storagepath):
self.storagepath = storagepath
self._data = self.load()
def load(self):
"""Reads the storage content from the disk to a dict structure"""
data = dict()
filedata = ""
try:
with open(self.storagepath, "r") as fh:
filedata = fh.read()
except IOError:
if os.path.isfile(self.storagepath):
# Only error out if file exists, but cannot be read
print("ERROR: Storage file exists but cannot be read")
sys.exit(1)
try:
data = json.loads(filedata)
except ValueError:
if len(filedata) > 0:
# Storage file is corrupted
print("ERROR: Storage JSON is corrupted")
sys.exit(1)
return data
def save(self):
"""Saves the storage content to disk"""
serialized = json.dumps(self._data)
try:
with os.fdopen(
os.open(self.storagepath, os.O_WRONLY | os.O_CREAT, 0o600), "w"
) as fh:
fh.truncate()
fh.write(serialized)
except IOError:
print("ERROR: Could not write storage file.")
sys.exit(1)
def put(self, key, value):
"""Puts the configuration value to storage and sanitize it"""
# If wildcard domain, remove the wildcard part as this will use the
# same validation record name as the base domain
if key.startswith("*."):
key = key[2:]
self._data[key] = value
def fetch(self, key):
"""Gets configuration value from storage"""
try:
return self._data[key]
except KeyError:
return None
if __name__ == "__main__":
# Init
client = AcmeDnsClient(ACMEDNS_URL)
storage = Storage(STORAGE_PATH)
# Check if an account already exists in storage
account = storage.fetch(DOMAIN)
if FORCE_REGISTER or not account:
# Create and save the new account
account = client.register_account(ALLOW_FROM)
storage.put(DOMAIN, account)
storage.save()
# Display the notification for the user to update the main zone
print("Please add the following CNAME record to your main DNS zone:\n")
print("{} CNAME {}.".format(VALIDATION_DOMAIN, account["fulldomain"]))
# Update the TXT record in acme-dns instance
client.update_txt_record(account, VALIDATION_TOKEN)
diff --git a/roles/webserver-core/letsencrypt/files/check-letsencrypt-certificates.py b/roles/core/certificates/files/check-letsencrypt-certificates.py
similarity index 93%
rename from roles/webserver-core/letsencrypt/files/check-letsencrypt-certificates.py
rename to roles/core/certificates/files/check-letsencrypt-certificates.py
index 9080e30..450fd69 100644
--- a/roles/webserver-core/letsencrypt/files/check-letsencrypt-certificates.py
+++ b/roles/core/certificates/files/check-letsencrypt-certificates.py
@@ -1,109 +1,108 @@
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
# -------------------------------------------------------------
# Let's encrypt — Certificates web server configuration checker
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Project: Nasqueron
-# Created: 2016-06-05
# Description: Check if /.well-known/acme-challenge works
# for the mapping directory webserver for each
-# certificate to renew.
+# certificate to renew. HTTP only.
# License: BSD-2-Clause
-# Source file: roles/webserver-core/letsencrypt/files/check-letsencrypt-certificates.py
+# Source file: roles/core/certificates/files/check-letsencrypt-certificates.py
# -------------------------------------------------------------
# -------------------------------------------------------------
# Table of contents
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# :: Configuration
# :: Checker code
# :: Run task
#
# -------------------------------------------------------------
import os
import random
import string
from urllib.error import HTTPError
from urllib.request import urlopen
+
# -------------------------------------------------------------
# Configuration
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
dirs = {
"/usr/local/etc/letsencrypt/renewal",
"/srv/data/letsencrypt/etc/renewal",
}
# -------------------------------------------------------------
# Checker code
# -------------------------------------------------------------
def check_directories(directories):
for directory in directories:
if os.path.isdir(directory):
check_directory(directory)
def check_directory(directory):
for file in os.listdir(directory):
if file.endswith(".conf"):
fullpath = os.path.join(directory, file)
check_certificate(fullpath)
def check_certificate(file):
lines = [line.rstrip("\n") for line in open(file)]
skip = True
for line in lines:
if not skip:
check_mapping_line(line)
if line == "[[webroot_map]]":
skip = False
def check_mapping_line(line):
params = line.split(" = ")
check_mapping(params[0], params[1])
def get_challenge():
chars = string.ascii_letters + string.digits
return "".join([random.choice(chars) for _ in range(32)])
def check_mapping(domain, directory):
challenge = get_challenge()
write_challenge_file(directory, challenge)
check_challenge(domain, challenge)
def write_challenge_file(directory, challenge):
challenge_file = os.path.join(directory, ".well-known", "acme-challenge", "qa")
with open(challenge_file, "w") as file:
file.write(challenge)
def check_challenge(domain, challenge):
url = "http://" + domain + "/.well-known/acme-challenge/qa"
try:
content = urlopen(url).read()
if not content == challenge:
print(domain, "DOES NOT MATCH")
except HTTPError as err:
print(domain, err.code)
# -------------------------------------------------------------
# Run task
# -------------------------------------------------------------
check_directories(dirs)
diff --git a/roles/webserver-core/letsencrypt/files/cli.ini b/roles/core/certificates/files/cli.ini
similarity index 86%
rename from roles/webserver-core/letsencrypt/files/cli.ini
rename to roles/core/certificates/files/cli.ini
index 4e76889..0339422 100644
--- a/roles/webserver-core/letsencrypt/files/cli.ini
+++ b/roles/core/certificates/files/cli.ini
@@ -1,27 +1,25 @@
# -------------------------------------------------------------
# Let's encrypt
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Project: Nasqueron
-# Created: 2017-04-27
# License: Trivial work, not eligible to copyright
-# Source file: roles/webserver-core/letsencrypt/files/cli.ini
+# Source file: roles/core/certificates/files/cli.ini
# -------------------------------------------------------------
#
# <auto-generated>
# This file is managed by our rOPS SaltStack repository.
#
# Changes to this file may cause incorrect behavior
# and will be lost if the state is redeployed.
# </auto-generated>
# Configuration
server = https://acme-v02.api.letsencrypt.org/directory
-authenticator = webroot
webroot-path = /var/letsencrypt-auto
# Automation
email = ops-tls@nasqueron.org
agree-tos = True
keep-until-expiring = True
eff-email = False
expand = True
diff --git a/roles/paas-docker/letsencrypt/files/edit-acme-dns-accounts.py b/roles/core/certificates/files/edit-acme-dns-accounts.py
similarity index 84%
rename from roles/paas-docker/letsencrypt/files/edit-acme-dns-accounts.py
rename to roles/core/certificates/files/edit-acme-dns-accounts.py
index 32b24a4..8fdce03 100755
--- a/roles/paas-docker/letsencrypt/files/edit-acme-dns-accounts.py
+++ b/roles/core/certificates/files/edit-acme-dns-accounts.py
@@ -1,110 +1,115 @@
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
# -------------------------------------------------------------
# Let's encrypt — ACME DNS server accounts editor
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Project: Nasqueron
-# Created: 2020-02-22
-# Description: Edit /srv/letsencrypt/etc/acmedns.json to import
-# credentials for a specific subdomain to verify.
+# Description: Edit acmedns.json to import credentials
+# for a specific subdomain to verify.
# License: BSD-2-Clause
+# Source file: roles/core/certificates/files/edit-acme-dns-accounts.py
# -------------------------------------------------------------
-
+#
+# <auto-generated>
+# This file is managed by our rOPS SaltStack repository.
+#
+# Changes to this file may cause incorrect behavior
+# and will be lost if the state is redeployed.
+# </auto-generated>
import json
import os
import sys
def get_acme_accounts_path():
try:
return os.environ["ACME_ACCOUNTS"]
except KeyError:
- return "/srv/letsencrypt/etc/acmedns.json"
+ return "/usr/local/etc/acmedns.json"
ACME_ACCOUNTS_PATH = get_acme_accounts_path()
class AcmeAccounts:
def __init__(self, path):
self.path = path
self.accounts = {}
def read_from_file(self):
with open(self.path) as fd:
self.accounts = json.load(fd)
return self
def write_to_file(self):
with open(self.path, "w") as fd:
json.dump(self.accounts, fd)
return self
def add(self, domain, account_parameters):
self.accounts[domain] = account_parameters
return self
def remove(self, domain):
try:
del self.accounts[domain]
return True
except KeyError:
return False
def merge_with(self, other_accounts: "AcmeAccounts"):
self.accounts.update(other_accounts.accounts)
return self
def usage():
print(f"Usage: {sys.argv[0]} <command> [parameters]", file=sys.stderr)
exit(1)
def import_other_file(file_to_import):
if file_to_import == ACME_ACCOUNTS_PATH:
print(f"You're trying to import {ACME_ACCOUNTS_PATH} to itself")
exit(2)
accounts_to_import = AcmeAccounts(file_to_import).read_from_file()
AcmeAccounts(ACME_ACCOUNTS_PATH).read_from_file().merge_with(
accounts_to_import
).write_to_file()
commands = {
"import": {
"required_argc": 3,
"command_usage": "import <file>",
"callable": import_other_file,
},
}
if __name__ == "__main__":
argc = len(sys.argv)
if argc < 2 or sys.argv[1] in ["-h", "--help", "/?", "/help"]:
usage()
command = sys.argv[1]
if command not in commands:
print(f"Unknown command: {command}", file=sys.stderr)
usage()
command = commands[command]
if argc < command["required_argc"]:
print(f"Usage: {sys.argv[0]} {command['command_usage']}", file=sys.stderr)
exit(1)
# We're good, time to invoke our command
command["callable"](*sys.argv[2:])
diff --git a/roles/webserver-core/letsencrypt/files/letsencrypt-renew.service b/roles/core/certificates/files/letsencrypt-renew.service
similarity index 81%
copy from roles/webserver-core/letsencrypt/files/letsencrypt-renew.service
copy to roles/core/certificates/files/letsencrypt-renew.service
index 11f9a3e..6c8814b 100644
--- a/roles/webserver-core/letsencrypt/files/letsencrypt-renew.service
+++ b/roles/core/certificates/files/letsencrypt-renew.service
@@ -1,22 +1,25 @@
# -------------------------------------------------------------
# Let's encrypt
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Project: Nasqueron
-# Created: 2016-08-24
# License: Trivial work, not eligible to copyright
-# Source file: roles/webserver-core/letsencrypt/files/letsencrypt.service
+# Source file: roles/core/certificates/files/letsencrypt-renew.service
# -------------------------------------------------------------
#
# <auto-generated>
# This file is managed by our rOPS SaltStack repository.
#
# Changes to this file may cause incorrect behavior
# and will be lost if the state is redeployed.
# </auto-generated>
[Unit]
Description=Renew Let's encrypt certificates.
+Wants=letsencrypt-renew.timer
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/letsencrypt-renewal
+
+[Install]
+WantedBy=multi-user.target
diff --git a/roles/webserver-core/letsencrypt/files/letsencrypt-renew.timer b/roles/core/certificates/files/letsencrypt-renew.timer
similarity index 76%
rename from roles/webserver-core/letsencrypt/files/letsencrypt-renew.timer
rename to roles/core/certificates/files/letsencrypt-renew.timer
index 2809a1f..7fa3910 100644
--- a/roles/webserver-core/letsencrypt/files/letsencrypt-renew.timer
+++ b/roles/core/certificates/files/letsencrypt-renew.timer
@@ -1,25 +1,24 @@
# -------------------------------------------------------------
# Let's encrypt
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Project: Nasqueron
-# Created: 2016-08-24
# License: Trivial work, not eligible to copyright
-# Source file: roles/webserver-core/letsencrypt/files/letsencrypt.timer
+# Source file: roles/core/certificates/files/letsencrypt-renew.timer
# -------------------------------------------------------------
#
# <auto-generated>
# This file is managed by our rOPS SaltStack repository.
#
# Changes to this file may cause incorrect behavior
# and will be lost if the state is redeployed.
# </auto-generated>
[Unit]
-Description=Runs letsencrypt-renewal every month
+Description=Check and renew Let's Encrypt certificates
[Timer]
-OnCalendar=*-*-26 12:15:00
+OnCalendar=*-*-* 12:15:00
Persistent=yes
[Install]
WantedBy=timers.target
diff --git a/roles/webserver-core/letsencrypt/files/letsencrypt-renew.service b/roles/core/certificates/files/letsencrypt-renewal-without-nginx.sh
similarity index 69%
rename from roles/webserver-core/letsencrypt/files/letsencrypt-renew.service
rename to roles/core/certificates/files/letsencrypt-renewal-without-nginx.sh
index 11f9a3e..4e6837a 100644
--- a/roles/webserver-core/letsencrypt/files/letsencrypt-renew.service
+++ b/roles/core/certificates/files/letsencrypt-renewal-without-nginx.sh
@@ -1,22 +1,18 @@
+#!/bin/sh
+
# -------------------------------------------------------------
# Let's encrypt
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Project: Nasqueron
-# Created: 2016-08-24
# License: Trivial work, not eligible to copyright
-# Source file: roles/webserver-core/letsencrypt/files/letsencrypt.service
+# Source file: roles/core/certificates/files/letsencrypt-renewal-without-nginx.sh
# -------------------------------------------------------------
#
# <auto-generated>
# This file is managed by our rOPS SaltStack repository.
#
# Changes to this file may cause incorrect behavior
# and will be lost if the state is redeployed.
# </auto-generated>
-[Unit]
-Description=Renew Let's encrypt certificates.
-
-[Service]
-Type=oneshot
-ExecStart=/usr/local/sbin/letsencrypt-renewal
+certbot renew
diff --git a/roles/webserver-core/letsencrypt/files/letsencrypt-renewal.sh b/roles/core/certificates/files/letsencrypt-renewal.sh
old mode 100755
new mode 100644
similarity index 81%
rename from roles/webserver-core/letsencrypt/files/letsencrypt-renewal.sh
rename to roles/core/certificates/files/letsencrypt-renewal.sh
index 7c4d8ca..217a377
--- a/roles/webserver-core/letsencrypt/files/letsencrypt-renewal.sh
+++ b/roles/core/certificates/files/letsencrypt-renewal.sh
@@ -1,31 +1,29 @@
#!/bin/sh
# -------------------------------------------------------------
# Let's encrypt
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Project: Nasqueron
-# Created: 2016-08-24
# License: Trivial work, not eligible to copyright
-# Source file: roles/webserver-core/letsencrypt/files/letsencrypt-renewal.sh
+# Source file: roles/core/certificates/files/letsencrypt-renewal.sh
# -------------------------------------------------------------
#
# <auto-generated>
# This file is managed by our rOPS SaltStack repository.
#
# Changes to this file may cause incorrect behavior
# and will be lost if the state is redeployed.
# </auto-generated>
nginx_test() {
nginx_output="$(nginx -t 2>&1)"
nginx_return_code="$?"
if [ "$nginx_return_code" -eq 0 ] && [ -n "$(echo "${nginx_output}" | grep warn)" ]; then
return 2;
else
return "$nginx_return_code";
fi;
}
-
-certbot renew && nginx_test && service nginx restart
+certbot renew && nginx_test && nginx -s reload
diff --git a/roles/core/certificates/init.sls b/roles/core/certificates/init.sls
index 9642fae..3aa87d7 100644
--- a/roles/core/certificates/init.sls
+++ b/roles/core/certificates/init.sls
@@ -1,9 +1,10 @@
# -------------------------------------------------------------
# Salt - Deploy certificates
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Project: Nasqueron
# License: Trivial work, not eligible to copyright
# -------------------------------------------------------------
include:
- .nasqueron
+ - .letsencrypt
diff --git a/roles/core/certificates/letsencrypt.sls b/roles/core/certificates/letsencrypt.sls
new file mode 100644
index 0000000..74f08c3
--- /dev/null
+++ b/roles/core/certificates/letsencrypt.sls
@@ -0,0 +1,97 @@
+# -------------------------------------------------------------
+# Salt - Deploy certificates
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# License: Trivial work, not eligible to copyright
+# -------------------------------------------------------------
+
+{% from "map.jinja" import dirs, packages with context %}
+
+{% set has_nginx = salt['node']['has_nginx']() %}
+
+# -------------------------------------------------------------
+# Software
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+letsencrypt_software:
+ pkg.installed:
+ - name: {{ packages.certbot }}
+
+# -------------------------------------------------------------
+# Working directory and configuration
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+/var/letsencrypt-auto:
+ file.directory:
+ - user: root
+ - dir_mode: 711
+
+{{ dirs.etc }}/letsencrypt/cli.ini:
+ file.managed:
+ - source: salt://roles/core/certificates/files/cli.ini
+ - makedirs: True
+
+# -------------------------------------------------------------
+# Extra utilities
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+{{ dirs.bin }}/check-letsencrypt-certificates:
+ file.managed:
+ - source: salt://roles/core/certificates/files/check-letsencrypt-certificates.py
+ - mode: 755
+
+{{ dirs.etc }}/letsencrypt/acme-dns-auth:
+ file.managed:
+ - source: salt://roles/core/certificates/files/acme-dns-auth.py
+ - mode: 755
+ - makedirs: True
+
+{{ dirs.bin }}/edit-acme-dns-accounts:
+ file.managed:
+ - source: salt://roles/core/certificates/files/edit-acme-dns-accounts.py
+ - mode: 755
+
+# -------------------------------------------------------------
+# Check and renew certificates daily
+#
+# FreeBSD ... periodic
+# Linux ..... systemd timer
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+{% if has_nginx %}
+{% set renewal_script = "letsencrypt-renewal.sh" %}
+{% else %}
+{% set renewal_script = "letsencrypt-renewal-without-nginx.sh" %}
+{% endif %}
+
+/usr/local/sbin/letsencrypt-renewal:
+ file.managed:
+ - source: salt://roles/core/certificates/files/{{ renewal_script }}
+ - mode: 755
+
+{% if grains["os_family"] == "FreeBSD" %}
+
+/usr/local/etc/periodic/daily/730.letsencrypt:
+ file.managed:
+ - source: salt://roles/core/certificates/files/730.letsencrypt
+
+{% elif services["manager"] == "systemd" %}
+
+/etc/systemd/system/letsencrypt-renew.timer:
+ file.managed:
+ - source: salt://roles/core/certificates/files/letsencrypt-renew.timer
+
+/etc/systemd/system/letsencrypt-renew.service:
+ file.managed:
+ - source: salt://roles/core/certificates/files/letsencrypt-renew.service
+
+letsencrypt_renew_enable:
+ service.enabled:
+ - name: letsencrypt-renew
+
+letsencrypt_renew_timer_start:
+ service.running:
+ - name: letsencrypt-renew.timer
+ - enable: True
+
+{% endif %}
diff --git a/roles/core/rc/files/periodic.conf b/roles/core/rc/files/periodic.conf
index dac1f2d..a1fe5f9 100644
--- a/roles/core/rc/files/periodic.conf
+++ b/roles/core/rc/files/periodic.conf
@@ -1,11 +1,13 @@
# 300.calendar
daily_portsnap_enable="YES"
# 480.status-ntpd
daily_status_ntpd_enable="YES"
+
+# 730.letsencrypt
+daily_letsencrypt_enable="YES"
+
{% if use_zfs %}
# 800.scrub-zfs
daily_scrub_zfs_enable="YES"
{% endif %}
-# 500.certbot
-weekly_certbot_enable="YES"
diff --git a/roles/paas-docker/init.sls b/roles/paas-docker/init.sls
index 1108f7c..903f619 100644
--- a/roles/paas-docker/init.sls
+++ b/roles/paas-docker/init.sls
@@ -1,25 +1,24 @@
# -------------------------------------------------------------
# Salt — Provision Docker engine
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Project: Nasqueron
# Created: 2018-09-13
# License: Trivial work, not eligible to copyright
# -------------------------------------------------------------
{% from "map.jinja" import dirs with context %}
include:
- .kernel
- .salt
- .docker
- .containers
- .systemd-unit
- .wwwroot-502
- .wwwroot-content
- .nginx
- .monitoring
- - .letsencrypt
- .wrappers
{% if salt['node.has']('flags:install_docker_devel_tools') %}
- .devel
{% endif %}
diff --git a/roles/paas-docker/letsencrypt/init.sls b/roles/paas-docker/letsencrypt/init.sls
deleted file mode 100644
index 29e4977..0000000
--- a/roles/paas-docker/letsencrypt/init.sls
+++ /dev/null
@@ -1,56 +0,0 @@
-# -------------------------------------------------------------
-# Salt — Provision Docker engine
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-# Project: Nasqueron
-# Created: 2018-03-16
-# License: Trivial work, not eligible to copyright
-# -------------------------------------------------------------
-
-{% set has_selinux = salt['grains.get']('selinux:enabled', False) %}
-
-# -------------------------------------------------------------
-# See also
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-# Wrapper script
-# - wrappers/init.sls
-# - wrappers/files/certbot.sh
-#
-# Image
-# - /pillar/paas/docker.sls
-#
-# Nginx configuration
-# - nginx/files/includes/letsencrypt
-
-# -------------------------------------------------------------
-# Data directory
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-/srv/letsencrypt:
- file.directory
-
-{% if has_selinux %}
-selinux_context_letsencrypt_home:
- selinux.fcontext_policy_present:
- - name: /srv/letsencrypt
- - sel_type: container_file_t
-
-selinux_context_letsencrypt_home_applied:
- selinux.fcontext_policy_applied:
- - name: /srv/letsencrypt
-{% endif %}
-
-# -------------------------------------------------------------
-# Plug-ins
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-/srv/letsencrypt/etc/acme-dns-auth:
- file.managed:
- - source: salt://roles/paas-docker/letsencrypt/files/acme-dns-auth.py
- - mode: 755
- - makedirs: True
-
-/usr/local/bin/edit-acme-dns-accounts:
- file.managed:
- - source: salt://roles/paas-docker/letsencrypt/files/edit-acme-dns-accounts.py
- - mode: 755
diff --git a/roles/paas-docker/wrappers/files/certbot.sh b/roles/paas-docker/wrappers/files/certbot.sh
deleted file mode 100755
index 9a52ced..0000000
--- a/roles/paas-docker/wrappers/files/certbot.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/sh
-
-# -------------------------------------------------------------
-# PaaS Docker
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-# Project: Nasqueron
-# Created: 2018-03-15
-# License: Trivial work, not eligible to copyright
-# Source file: roles/paas-docker/wrappers/files/certbot.sh
-# -------------------------------------------------------------
-#
-# <auto-generated>
-# This file is managed by our rOPS SaltStack repository.
-#
-# Changes to this file may cause incorrect behavior
-# and will be lost if the state is redeployed.
-# </auto-generated>
-
-if [ "$1" = "acme-dns-certonly" ]; then
- COMMAND=certonly
- EXTRA_ARGS="--manual --manual-auth-hook /etc/letsencrypt/acme-dns-auth --preferred-challenges dns --debug-challenge"
-else
- COMMAND=$1
-fi
-shift
-
-docker run -it --rm \
- -v /srv/letsencrypt/etc:/etc/letsencrypt \
- -v /srv/letsencrypt/var:/var/lib/letsencrypt \
- -v /srv/letsencrypt/log:/var/log/letsencrypt \
- -v /srv/letsencrypt/www:/www \
- certbot/certbot:latest "$COMMAND" $@ $EXTRA_ARGS
diff --git a/roles/paas-docker/wrappers/init.sls b/roles/paas-docker/wrappers/init.sls
index daa2a5d..34cad86 100644
--- a/roles/paas-docker/wrappers/init.sls
+++ b/roles/paas-docker/wrappers/init.sls
@@ -1,57 +1,57 @@
# -------------------------------------------------------------
# Salt — Provision Docker engine
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Project: Nasqueron
# Created: 2018-03-15
# License: Trivial work, not eligible to copyright
# -------------------------------------------------------------
{% from "map.jinja" import dirs with context %}
# -------------------------------------------------------------
# Wrapper binaries
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-{% for command in ['certbot', 'jenkins', 'phpbb', 'mysql', 'openfire', 'geoipupdate'] %}
+{% for command in ['jenkins', 'phpbb', 'mysql', 'openfire', 'geoipupdate'] %}
{{ dirs.bin }}/{{ command }}:
file.managed:
- source: salt://roles/paas-docker/wrappers/files/{{ command }}.sh
- mode: 755
{% endfor %}
{% for command in ['airflow', 'sentry'] %}
{{ dirs.bin }}/{{ command }}:
file.managed:
- source: salt://roles/paas-docker/wrappers/files/run-by-realm.sh.jinja
- mode: 755
- template: jinja
- context:
service: {{ command }}
{% endfor %}
{% for command in ['pad-delete'] %}
{{ dirs.bin }}/{{ command }}:
file.managed:
- source: salt://roles/paas-docker/wrappers/files/{{ command }}.py
- mode: 755
{% endfor %}
# -------------------------------------------------------------
# Required directories
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% set has_selinux = salt['grains.get']('selinux:enabled', False) %}
/srv/geoip:
file.directory
{% if has_selinux %}
selinux_context_geoip_data:
selinux.fcontext_policy_present:
- name: /srv/geoip
- sel_type: container_file_t
selinux_context_geoip_data_applied:
selinux.fcontext_policy_applied:
- name: /srv/geoip
{% endif %}
diff --git a/roles/webserver-core/init.sls b/roles/webserver-core/init.sls
index 4580956..f705861 100644
--- a/roles/webserver-core/init.sls
+++ b/roles/webserver-core/init.sls
@@ -1,14 +1,11 @@
# -------------------------------------------------------------
# Salt — Webserver core units for all webservers roles
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Project: Nasqueron
# Created: 2017-10-25
# License: Trivial work, not eligible to copyright
# -------------------------------------------------------------
include:
- .nginx
- .tools
- {% if 'paas-docker' not in salt['node.get_list']('roles') %}
- - .letsencrypt
- {% endif %}
diff --git a/roles/webserver-core/letsencrypt/certificates.sls b/roles/webserver-core/letsencrypt/certificates.sls
deleted file mode 100644
index c4c4a21..0000000
--- a/roles/webserver-core/letsencrypt/certificates.sls
+++ /dev/null
@@ -1,20 +0,0 @@
-# -------------------------------------------------------------
-# Salt — Let's encrypt certificates
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-# Project: Nasqueron
-# Created: 2017-04-27
-# License: Trivial work, not eligible to copyright
-# -------------------------------------------------------------
-
-{% from "map.jinja" import dirs with context %}
-
-# -------------------------------------------------------------
-# Certificates
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-{% for domain in salt['pillar.get']("certificates_letsencrypt:" + grains['id'], []) %}
-certificate_{{ domain }}:
- cmd.run:
- - name: certbot certonly -d {{ domain }}
- - creates: {{ dirs.etc }}/letsencrypt/live/{{ domain }}/fullchain.pem
-{% endfor %}
diff --git a/roles/webserver-core/letsencrypt/init.sls b/roles/webserver-core/letsencrypt/init.sls
deleted file mode 100644
index d72db88..0000000
--- a/roles/webserver-core/letsencrypt/init.sls
+++ /dev/null
@@ -1,12 +0,0 @@
-# -------------------------------------------------------------
-# Salt — Let's encrypt certificates
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-# Project: Nasqueron
-# Created: 2017-04-27
-# License: Trivial work, not eligible to copyright
-# -------------------------------------------------------------
-
-include:
- - .software
- - .service
- - .certificates
diff --git a/roles/webserver-core/letsencrypt/service.sls b/roles/webserver-core/letsencrypt/service.sls
deleted file mode 100644
index 8ed4a16..0000000
--- a/roles/webserver-core/letsencrypt/service.sls
+++ /dev/null
@@ -1,44 +0,0 @@
-# -------------------------------------------------------------
-# Salt — Let's encrypt certificates
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-# Project: Nasqueron
-# Created: 2017-04-27
-# Description: Provide a renewal service
-# License: Trivial work, not eligible to copyright
-# -------------------------------------------------------------
-
-{% from "map.jinja" import services with context %}
-
-# -------------------------------------------------------------
-# Renew script
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-/usr/local/sbin/letsencrypt-renewal:
- file.managed:
- - source: salt://roles/webserver-core/letsencrypt/files/letsencrypt-renewal.sh
- - mode: 755
-
-# -------------------------------------------------------------
-# Unit configuration
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-{% if services['manager'] == 'systemd' %}
-
-letsencrypt_renew_unit:
- file.managed:
- - name: /etc/systemd/system/letsencrypt-renew.service
- - source: salt://roles/webserver-core/letsencrypt/files/letsencrypt-renew.service
- - mode: 644
- module.run:
- - service.force_reload:
- - name: letsencrypt-renew
- - onchanges:
- - file: letsencrypt_renew_unit
-
-letsencrypt_renew_enable:
- service.enabled:
- - name: letsencrypt-renew
- - watch:
- - module: letsencrypt_renew_unit
-
-{% endif %}
diff --git a/roles/webserver-core/letsencrypt/software.sls b/roles/webserver-core/letsencrypt/software.sls
deleted file mode 100644
index 026d5af..0000000
--- a/roles/webserver-core/letsencrypt/software.sls
+++ /dev/null
@@ -1,44 +0,0 @@
-# -------------------------------------------------------------
-# Salt — Let's encrypt certificates
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-# Project: Nasqueron
-# Created: 2017-04-27
-# License: Trivial work, not eligible to copyright
-# -------------------------------------------------------------
-
-{% from "map.jinja" import dirs, packages with context %}
-
-# -------------------------------------------------------------
-# Software
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-letsencrypt_software:
- pkg.installed:
- - name: {{ packages.certbot }}
-
-# -------------------------------------------------------------
-# Working directory
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-/var/letsencrypt-auto:
- file.directory:
- - user: root
- - dir_mode: 711
-
-# -------------------------------------------------------------
-# Configuration file
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-{{ dirs.etc }}/letsencrypt/cli.ini:
- file.managed:
- - source: salt://roles/webserver-core/letsencrypt/files/cli.ini
- - makedirs: True
-
-# -------------------------------------------------------------
-# Extra utilities
-# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-{{ dirs.bin }}/check-letsencrypt-certificates:
- file.managed:
- - source: salt://roles/webserver-core/letsencrypt/files/check-letsencrypt-certificates.py
- - mode: 755
diff --git a/roles/webserver-core/map.jinja b/roles/webserver-core/map.jinja
index 5122db9..c882359 100644
--- a/roles/webserver-core/map.jinja
+++ b/roles/webserver-core/map.jinja
@@ -1,18 +1,12 @@
{% set options = salt["grains.filter_by"]({
"Debian": {
"www_user": "nobody",
},
"FreeBSD": {
"www_user": "www",
},
"RedHat": {
"www_user": "nginx",
"pid_path": "/run/nginx.pid",
}
}, default="Debian") %}
-
-{% if salt["node.has_role"]("paas-docker") %}
-{% set certbot_dir = "/srv/letsencrypt/www" %}
-{% else %}
-{% set certbot_dir = "/var/letsencrypt-auto" %}
-{% endif %}
diff --git a/roles/webserver-core/nginx/config.sls b/roles/webserver-core/nginx/config.sls
index 64ab7dd..b9f7d2a 100644
--- a/roles/webserver-core/nginx/config.sls
+++ b/roles/webserver-core/nginx/config.sls
@@ -1,119 +1,119 @@
# -------------------------------------------------------------
# Salt — Webserver core units for all webservers roles
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Project: Nasqueron
# License: Trivial work, not eligible to copyright
# -------------------------------------------------------------
{% from "map.jinja" import dirs with context %}
-{% from "roles/webserver-core/map.jinja" import options, certbot_dir with context %}
+{% from "roles/webserver-core/map.jinja" import options with context %}
{% set has_selinux = salt['grains.get']('selinux:enabled', False) %}
# -------------------------------------------------------------
# Accounts - web group
#
# A group shared between nginx, back-end and content directories
# to allow ACL giving access to the nginx process.
#
# This group will so be used by:
# - nginx process (configured in nginx.conf)
# - back-end UNIX sockets like php-fpm sockets can be 660
# - more private folders can use 007 as umask
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
webserver_core_group:
group.present:
- name: web
- gid: 9003
- system: True
# -------------------------------------------------------------
# Base configuration
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ dirs.etc }}/nginx/nginx.conf:
file.managed:
- source: salt://roles/webserver-core/nginx/files/nginx.conf
- template: jinja
- context:
nginx_dir: {{ dirs.etc }}/nginx
nginx_options: {{ options }}
# -------------------------------------------------------------
# includes folder
#
# :: general configuration
# :: application-specific code
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
webserver_core_nginx_includes:
file.recurse:
- name: {{ dirs.etc }}/nginx/includes
- source: salt://roles/webserver-core/nginx/files/includes
- dir_mode: 755
- file_mode: 644
- template: jinja
- context:
nginx_version: {{ salt["nginx.version"]() }}
nginx_dir: {{ dirs.etc }}/nginx
nginx_options: {{ options }}
- certbot_dir: {{ certbot_dir }}
+ certbot_dir: /var/letsencrypt-auto
# -------------------------------------------------------------
# Parameters for Diffie-Hellman
#
# Some ciphers still require DH exchange. They contain "DHE" in
# the name, e.g. DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
webserver_core_nginx_dh:
cmd.run:
- name: openssl dhparam -out {{ dirs.etc }}/nginx/dhparams.pem 4096
- creates: {{ dirs.etc }}/nginx/dhparams.pem
# -------------------------------------------------------------
# OCSP - Online Certificate Status Protocol
#
# To allow nginx to verify TLS certificate presented by CA
# when it makes requests to the CRL, a bundle of CA certificates
# should be available.
#
# To generate the bundle file on this repository, use `make`.
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/usr/local/share/certs/ocsp-ca-certs.pem:
file.managed:
- source: salt://roles/webserver-core/nginx/files/ocsp-ca-certs.pem
- makedirs: True
- mode: 644
# -------------------------------------------------------------
# Logs
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/var/log/www:
file.directory:
- user: {{ options["www_user"] }}
- group: web
- dir_mode: 711
{% if has_selinux %}
selinux_context_nginx_logs:
selinux.fcontext_policy_present:
- name: /var/log/www
- sel_type: httpd_log_t
selinux_context_nginx_logs_applied:
selinux.fcontext_policy_applied:
- name: /var/log/www
{% endif %}
# -------------------------------------------------------------
# vhost folder
#
# To be filled by the specific web role or unit
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{{ dirs.etc }}/nginx/vhosts:
file.directory

File Metadata

Mime Type
text/x-diff
Expires
Sun, Nov 24, 19:43 (3 h, 41 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2258517
Default Alt Text
(61 KB)

Event Timeline