added 'read' service and bug fixes

* added read service (kavita)
* renamed script to match repo

bugs:
* chown keys after custom site scripts in case of pfx or other generation (this was not getting chowned before and breaking the site)
This commit is contained in:
LuKe Tidd 2022-10-01 14:10:55 -04:00
parent f0cd9433de
commit a4e16f4cb1
2 changed files with 33 additions and 25 deletions

View File

@ -5,21 +5,20 @@ automation for cert renewal with local hooks
given a service: given a service:
* start letsencrypt's certbot "manually", getting ownership proof data * start letsencrypt's certbot "manually", getting ownership proof data
* write proof into nginx's serving path * turn up a custom nginx site for the proof
* log into the firewall, allow http for the given service * log into the firewall, allow http to the given service
* enable http for the given service * enable http for the given service in nginx
* instruct let's encrypt to check the proof * instruct let's encrypt to check the proof
* get new keys * new expiration date on certs
* disable http for the service * disable http for the service
* log into firewall, block http for the given service * log into firewall, block http for the given service
* set permissions and ownership on new keys
* perform service specific hooks * perform service specific hooks
* jellyfin/plex: generate a pkcs12 key and * jellyfin/plex: generate a pkcs12 key and
put it in the right place put it in the right place
* set permissions and ownership on new keys
All secrets are GPG encrypted and one password prompt allows for script access All secrets are GPG encrypted and one password prompt allows for script access
to all secrets necessary. to all secrets necessary.
State: State:
* running for all services, no known bugs at this time * running for all services, no known bugs at this time

View File

@ -18,7 +18,8 @@ import subprocess
import sys import sys
import time import time
supported_services = ['git', 'plex', 'jellyfin', 'photoprism', 'nextcloud'] supported_services = [
'git', 'plex', 'jellyfin', 'photoprism', 'nextcloud', 'read']
restart_delay = { restart_delay = {
'plex': 10 'plex': 10
@ -29,22 +30,22 @@ pfx_key_path = {
'jellyfin': '/etc/letsencrypt/live/jellyfin.drheck.dev/jellyfin.pfx', 'jellyfin': '/etc/letsencrypt/live/jellyfin.drheck.dev/jellyfin.pfx',
} }
# Cert owning user if different than the name of the service
users = { users = {
'jellyfin': 'jellyfin',
'git': 'gitea', 'git': 'gitea',
'plex': 'plex', 'read': 'http',
'photoprism': 'photoprism',
'nextcloud': 'nextcloud',
} }
# systemd service names that don't match the service name
# service : systemd_service
systemd_services = { systemd_services = {
'git': 'gitea', 'git': 'gitea',
'plex': 'plexmediaserver', 'plex': 'plexmediaserver',
'read': 'kavita',
} }
cert_files = ['privkey1.pem', 'fullchain1.pem', 'chain1.pem', 'cert1.pem'] cert_files = ['privkey1.pem', 'fullchain1.pem', 'chain1.pem', 'cert1.pem']
router = 'danknasty' router = 'danknasty'
router_user = 'luke131' router_user = 'luke131'
router_key = '/root/.ssh/id_autofirewall' router_key = '/root/.ssh/id_autofirewall'
@ -61,7 +62,9 @@ def firewall_mod(state, service, decrypt_pp):
log.info(f'env for fw: ssl_service: {service}') log.info(f'env for fw: ssl_service: {service}')
log.info(f'cmd to connect to firewall: "{" ".join(cmd)}"') log.info(f'cmd to connect to firewall: "{" ".join(cmd)}"')
p = pexpect.spawnu(' '.join(cmd)) p = pexpect.spawnu(' '.join(cmd))
res = p.expect([f'Enter passphrase for key "{router_key}":', p.logfile = sys.stderr
log.info(f'key string: {router_key}')
res = p.expect([f'''Enter passphrase for key ['"]{router_key}['"]:''',
pexpect.TIMEOUT, pexpect.EOF]) pexpect.TIMEOUT, pexpect.EOF])
if res > 0: if res > 0:
sys.exit('Couldnt send decryption key to ssh.') sys.exit('Couldnt send decryption key to ssh.')
@ -149,8 +152,8 @@ def restart(service):
cmd = ['/usr/bin/systemctl', 'status', systemd_service] cmd = ['/usr/bin/systemctl', 'status', systemd_service]
log.info(f'cmd to show status of service: "{" ".join(cmd)}"') log.info(f'cmd to show status of service: "{" ".join(cmd)}"')
p = subprocess.run(cmd, capture_output=True) p = subprocess.run(cmd, capture_output=True)
stderr = p.stdout.decode('UTF-8') stderr = p.stderr.decode('UTF-8')
sys.stderr.write(p.stdout) sys.stderr.write(stderr)
sys.exit(1) sys.exit(1)
log.info(f'{service} has restarted OK') log.info(f'{service} has restarted OK')
@ -175,6 +178,7 @@ def pfx_gen(service):
'-certfile', f'/etc/letsencrypt/live/{service}.drheck.dev/chain.pem'] '-certfile', f'/etc/letsencrypt/live/{service}.drheck.dev/chain.pem']
log.info(f'cmd to encrypt private key: "{" ".join(cmd)}"') log.info(f'cmd to encrypt private key: "{" ".join(cmd)}"')
p = pexpect.spawnu(' '.join(cmd)) p = pexpect.spawnu(' '.join(cmd))
p.logfile = sys.stderr
res = p.expect(['Enter Export Password:', pexpect.EOF, pexpect.TIMEOUT]) res = p.expect(['Enter Export Password:', pexpect.EOF, pexpect.TIMEOUT])
if res > 0: if res > 0:
sys.exit('Failed to run openssl to generate ' sys.exit('Failed to run openssl to generate '
@ -196,7 +200,8 @@ def pfx_gen(service):
def main(args): def main(args):
logging.basicConfig(level=os.environ.get("LOGLEVEL", "WARNING")) # logging.basicConfig(level=os.environ.get("LOGLEVEL", "WARNING"))
logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
log.info(f'program start: {sys.argv}') log.info(f'program start: {sys.argv}')
if len(args) != 1: if len(args) != 1:
@ -232,9 +237,9 @@ def main(args):
fqdn = f'{service}.drheck.dev' fqdn = f'{service}.drheck.dev'
log.info(f'fqdn: {fqdn}') log.info(f'fqdn: {fqdn}')
# Dry run: # Dry run:
cmd = ['/usr/bin/certbot', '--dry-run', 'certonly', '--manual', '-d', fqdn] # cmd = ['/usr/bin/certbot', '--dry-run', 'certonly', '--manual', '-d', fqdn]
# Real run: # Real run:
# cmd = ['/usr/bin/certbot', 'certonly', '--manual', '-d', fqdn] cmd = ['/usr/bin/certbot', 'certonly', '--manual', '-d', fqdn]
log.info(f'certbot cmd: "{" ".join(cmd)}"') log.info(f'certbot cmd: "{" ".join(cmd)}"')
cb = pexpect.spawnu(' '.join(cmd)) cb = pexpect.spawnu(' '.join(cmd))
res = cb.expect( res = cb.expect(
@ -319,12 +324,21 @@ def main(args):
log.info(f'live keypath: {live}') log.info(f'live keypath: {live}')
log.info(f'archive keypath: {archive}') log.info(f'archive keypath: {archive}')
user = users[service] if service in globals():
log.info(f'{service} has a service specific function to run')
eval(f'{service}()')
log.info(f'{service} specific work complete.')
user = service
if service in users:
user = users[service]
log.info(f'service {service} has user {user}') log.info(f'service {service} has user {user}')
uid = pwd.getpwnam(user).pw_uid uid = pwd.getpwnam(user).pw_uid
gid = pwd.getpwnam(user).pw_gid gid = pwd.getpwnam(user).pw_gid
log.info(f'uid: {uid} gid: {gid}')
# chown after custom service in case pfx or other key is generated
log.info(f'uid: {uid} gid: {gid}')
os.chown(live, uid, gid) os.chown(live, uid, gid)
log.info(f'live keypath chmodded') log.info(f'live keypath chmodded')
os.chown(archive, uid, gid) os.chown(archive, uid, gid)
@ -334,11 +348,6 @@ def main(args):
log.info(f'{cert_file} chowned to service user') log.info(f'{cert_file} chowned to service user')
log.info(f'chmodded new keys from certbot') log.info(f'chmodded new keys from certbot')
if service in globals():
log.info(f'{service} has a service specific function to run')
eval(f'{service}()')
log.info(f'{service} specific work complete.')
restart(service) restart(service)