From 7b7284c32fa4d8e30d35fb37116f53bdf4d7116b Mon Sep 17 00:00:00 2001 From: josiah Date: Tue, 10 Nov 2020 23:22:38 -0600 Subject: [PATCH] Add several roles; restructure group vars; restructure inventory. all of this is required for the synology LE role to work. this is still a massive WIP commit. synology LE works, but synology webdav using that LE cert does not yet work. there appears to be some cipher mismatch issue by default. --- ansible/acme-all.yml | 19 +++ ansible/ansible.cfg | 8 +- ansible/group_vars/all/acmedns_stuff.yml | 24 +++ ansible/group_vars/all/main.yml | 5 +- ansible/group_vars/all/vault.yml | 158 ++++++++++-------- .../host_vars/larva.home.jowj.net/main.yml | 1 + ansible/{ => inventory}/hosts.yml | 1 + ansible/readme.org | 4 + ansible/requirements.yml | 4 + ansible/roles/acmedns_base/tasks/main.yml | 84 ++++++++++ .../acmedns_base/templates/wraplego.py.j2 | 157 +++++++++++++++++ ansible/roles/acmedns_base/vars/main.yml | 10 ++ .../acmedns_remote_host/defaults/main.yml | 4 + ansible/roles/acmedns_remote_host/readme.md | 12 ++ .../roles/acmedns_remote_host/tasks/main.yml | 25 +++ .../acmedns_syno_updater/defaults/main.yml | 2 + .../roles/acmedns_syno_updater/meta/main.yml | 2 + .../roles/acmedns_syno_updater/tasks/main.yml | 55 ++++++ .../acmedns_syno_updater_webhook.te.j2 | 14 ++ .../templates/acmedns_update.sh.j2 | 51 ++++++ .../templates/hook.json.j2 | 10 ++ .../roles/acmedns_syno_updater/vars/main.yml | 6 + ansible/roles/synology/tasks/main.yml | 56 +++++++ .../synology/templates/acmedns_update.sh.j2 | 42 +++++ 24 files changed, 682 insertions(+), 72 deletions(-) create mode 100644 ansible/acme-all.yml create mode 100644 ansible/group_vars/all/acmedns_stuff.yml rename ansible/{ => inventory}/hosts.yml (91%) create mode 100644 ansible/requirements.yml create mode 100644 ansible/roles/acmedns_base/tasks/main.yml create mode 100644 ansible/roles/acmedns_base/templates/wraplego.py.j2 create mode 100644 ansible/roles/acmedns_base/vars/main.yml create mode 100644 ansible/roles/acmedns_remote_host/defaults/main.yml create mode 100644 ansible/roles/acmedns_remote_host/readme.md create mode 100644 ansible/roles/acmedns_remote_host/tasks/main.yml create mode 100644 ansible/roles/acmedns_syno_updater/defaults/main.yml create mode 100644 ansible/roles/acmedns_syno_updater/meta/main.yml create mode 100644 ansible/roles/acmedns_syno_updater/tasks/main.yml create mode 100644 ansible/roles/acmedns_syno_updater/templates/acmedns_syno_updater_webhook.te.j2 create mode 100644 ansible/roles/acmedns_syno_updater/templates/acmedns_update.sh.j2 create mode 100644 ansible/roles/acmedns_syno_updater/templates/hook.json.j2 create mode 100644 ansible/roles/acmedns_syno_updater/vars/main.yml create mode 100644 ansible/roles/synology/tasks/main.yml create mode 100644 ansible/roles/synology/templates/acmedns_update.sh.j2 diff --git a/ansible/acme-all.yml b/ansible/acme-all.yml new file mode 100644 index 0000000..be0e56e --- /dev/null +++ b/ansible/acme-all.yml @@ -0,0 +1,19 @@ +--- + +- name: Setup task server for ACME work. + hosts: larva.home.jowj.net + remote_user: "{{ remote_user }}" + roles: + - { name: acmedns_base, tags: ['acmedns_base'] } + +- name: Setup synology to allow for remote cert copy + hosts: storage.home.jowj.net + remote_user: "{{ remote_user }}" + roles: + - { name: acmedns_remote_host, tags: ['acmedns_remote_host'] } + +- name: Pull LE certs and copy them to Synology + hosts: larva.home.jowj.net + remote_user: "{{ remote_user }}" + roles: + - { name: acmedns_syno_updater, tags: ['acmedns_syno_updater'] } diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg index dca62b8..f77e730 100644 --- a/ansible/ansible.cfg +++ b/ansible/ansible.cfg @@ -1,14 +1,18 @@ [defaults] -inventory = hosts.yml +inventory = inventory pipelining = True retry_files_enabled = False -stdout_callback = yaml +# stdout_callback = yaml vault_password_file=open_the_vault.sh ansible_python_interpreter=/usr/bin/python3 [ssh_connection] scp_if_ssh = True retries = 1 +pipelining = True [privilege_escalation] become = True + +[inventory] +enable_plugins = host_list, script, auto, yaml, ini, toml diff --git a/ansible/group_vars/all/acmedns_stuff.yml b/ansible/group_vars/all/acmedns_stuff.yml new file mode 100644 index 0000000..da0c9f1 --- /dev/null +++ b/ansible/group_vars/all/acmedns_stuff.yml @@ -0,0 +1,24 @@ +--- +acmedns_remote_host_user: "{{ ansible_ssh_user }}" +acmedns_remote_host_ssh_client_pubkey: "{{ global_acmedns_ssh_client_pubkey }}" + +# ACME DNS base updater settings +acmedns_base_certificate_dir: "/etc/acmedns/certificates" +acmedns_base_user: acmedns +acmedns_base_group: acmedns +acmedns_base_pubkey: "{{ global_acmedns_ssh_client_pubkey }}" +acmedns_base_privkey: "{{ vault_acmedns_base_privkey }}" + +# ACME DNS Synology updater settings +acmedns_syno_updater_cert_base: "{{ acmedns_base_certificate_dir }}" +acmedns_syno_updater_user: "{{ acmedns_base_user }}" +acmedns_syno_updater_group: "{{ acmedns_base_group }}" +acmedns_syno_updater_job_name: storage +acmedns_syno_updater_email: admin@jowj.net +acmedns_syno_updater_domain: storage.home.jowj.net + +acmedns_syno_updater_syn_user: josiah +acmedns_syno_updater_syn_server: "{{ acmedns_syno_updater_domain }}" +acmedns_syno_updater_syn_server_pubkey: storage.home.jowj.net,192.168.1.221 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNFlSCsoeS1dPFipdZYqr+WY38XRwQLsDds9BuOiRz8k1Palyief8QPxdBNAR28qyJb2QPjqEFlNQ1hHUt/+WTI= +acmedns_syno_updater_pubkey: "{{ global_acmedns_ssh_client_pubkey }}" +acmedns_syno_updater_privkey: "{{ acmedns_base_privkey }}" diff --git a/ansible/group_vars/all/main.yml b/ansible/group_vars/all/main.yml index 1a97dca..aa16b54 100644 --- a/ansible/group_vars/all/main.yml +++ b/ansible/group_vars/all/main.yml @@ -1,5 +1,7 @@ --- +global_acmedns_ssh_client_pubkey: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJkuNfpGFXrTiIE4KU8jU57VltMiMXapDcDEd3vWQcEm acmedns@jowj.net + global_syslog_host: syslog.home.jowj.net global_syslog_netsys_port: "514" global_syslog_netcons_port: "5514" @@ -7,7 +9,7 @@ global_syslog_nettemp_port: "5515" remote_user: josiah gather_facts: True -source_os: arch +source_os: nixos become: yes create_user: josiah ansible_become_pass: "{{ vault_ansible_become_pass }}" @@ -77,7 +79,6 @@ NEXTCLOUD_ADMIN_PASSWORD: "{{ VAULT_NEXTCLOUD_ADMIN_PASSWORD }}" DO_AUTH_TOKEN: "{{ VAULT_DO_AUTH_TOKEN }}" # Syslog shit - sysloghost_share_mountpoint: /syslog/ sysloghost_netsys_port: "{{ global_syslog_netsys_port }}" diff --git a/ansible/group_vars/all/vault.yml b/ansible/group_vars/all/vault.yml index 800041a..19dc00c 100644 --- a/ansible/group_vars/all/vault.yml +++ b/ansible/group_vars/all/vault.yml @@ -1,69 +1,91 @@ $ANSIBLE_VAULT;1.1;AES256 -30353731373832343636383333326236373739643961303834373933393061353034633838646533 -3563343663383134346435613861656265313737356639660a393636306532306533343933343934 -66363537653637636264386362633162376366323263333162376462616430336639623662663336 -3236376266356338650a366230363234353063383966333865616333663030653236323733613332 -34393364633731613838623332313037353064616137356561363861353138366636633136356130 -38393761356663383733323634623238343365643936366431623664626438383633626232363663 -61623331663338323165323132343963663066313835346433323062613239646433653665663533 -31306438306265653361313564623264656636316464343830313833306234373166623731363965 -34353033343166323636373833653765383664336565346431373064613232633261363136316162 -61646538353766393539623066333162353531656562306365313362393434643639346361363239 -63623830626633653964393633306535313135396362663037353834376139323465356636663734 -30616534653435393062666562653837623765666633613432616536353065333964383866613332 -33333362663439316238313464376531303065386133643530356238306337656561623661633933 -36346461346461356137313465396436626532613136376532623939646239326639663863663361 -35326134666333653562346535623763396631336432666633616632333339656462663239393764 -33643937363035633836376332623561336561386330346430363731326463313037323130306532 -63613063633639386564363665326230663263316635363836396538613533393038613838653665 -33333138373261646564623961616365363564333066646437613466393866663930626332346535 -34653239363462626165353539363234343166313165366339356133666565666239626233626531 -66323632346539313566336366346163333333633039356562333831303437333537323032633364 -33636239613236626264623231643465316363656235393038306165613564633530386533643465 -37396563636338313363346332313038646332336436633332623732386264393465393065353539 -66383337656163653335363865333133353430333634666462616630303464326430353435623764 -38643837613532393661316531373135323166396636626566346662616535316464396431373363 -32393563666133363035323438616337613638666463616534633962356131633031626532353736 -36353763363662363362393730386362636535356131383935623163323832653339343531333066 -64343664646266313635306462383762366536346338626664626137623132653964636139373130 -64363264656664373162306639356138636430383330643833663562313230363036373138313739 -36363562316330333835323733383035623763656231316661646432623932633464313861383266 -64616435616131383330663337613762326432363431356465396431356137356466633431303137 -62366531633638383231393932353865326136663864353130626436663835333330373238643330 -34653431343132353235333361333835313639653961313463346235626561613430366136356338 -64663865396431336434346638623039333534353431373136623739653235323234323739323235 -38393932386331643233343336613465336139343533623163306537356139303935623433616137 -37656532613131333565666163623832633466353439303664623162313237323563373662316266 -36386561316665636631356634396439653930613363396536653239373466303366336239316635 -65333532326666376538613931613363623136653738333532323331616130373563383837363762 -35646666653164393031306238343337656130623065386365636637393930363765343138313639 -35373065366263663435343131626537336430366438663463333533326164396664393563363231 -36326666636630643032663863663437656639613036643633666461613233313961646238333865 -62313563376139396563313661643664643862326634653034636239316461343761653162626330 -35343432363232386634343138336230616533613166646335363237313866376639646361306634 -61386630366437326431646439383666653363373462656232616362626131323034656638313164 -33323835303731346632636230613738643765623566366137643236396331323865313133623061 -35616461373031663036373833376665636234656661386166323134306238626136613436383733 -30666531313032393166356466386236323264373538383663303134613364666238653166363963 -39366333383033626334623333363162613966356332366238613261316435313934346435663939 -31636331646165356266626235616564386432343464663362393235373637333338653265633764 -31636330353638313537366365623038366137646235383666343462633533363261633963623265 -64393133636466616335333332326636383963306466356536343232336262363164636363333637 -63343936616166656432343533343632663862646132643865316166353833373734303933343133 -30383365616239646333646261396535613763646634663438393161623530663338626566333639 -63393230373364333633663035396466326339353739656131323433663731333963633863636337 -65633564386261343366663334356466663635313734356231326331653562383032656235643239 -31386364313564323631353266656332626465383439626364646266663138326665383532626562 -66303839643230396665333761353436333832303033343030313061306238613931633538616466 -32393863363430653763396537393730633063656564346132323039663835666539326164623235 -65663836613831333337396161653965643333646139343533633761303134353163383837326163 -62343036396462643462323739613032653961303939656339383737323930326430636438303135 -64613031376534326564633936656430396238656130343735343062386135643366363337346662 -39356338306334333637373564323263613062356661333631623735316231353132626136613031 -61636664323236656265616131613162636365303339386230346564376162616536636562633039 -38373931326432643739623935393863316232626136393166323030353465336561643565656238 -30393339616561306138646335633031646163666562333639633233653338393636653632323366 -31656261666564303333633230643838636565383033326266633566343932383632383436633135 -30353735613737383332663438623962626465336663303437313237623365396264643230373435 -63316466393538636432643561613839663933363631356138646536626565373731386663326561 -38356539613965316232 +30643032326264646432303364373735613262346362643131633532373534613638343365323037 +6538646464353734343265346639636266353436313461330a336337363134646163366133626330 +37646530653363343130613964316463336230646435376632396366643231323266373462343535 +3762333865303462640a333464666335323437643637313535666237323337313931326237633061 +34306665396466306439623035393034346561336430653338303937343164363065333865393934 +34346264373630643961643431663464666130376561373533346166666361643538373737313231 +66376231623934313832313364666338303032303261613537373634616638616264333561396561 +63646364386562613233303633333533393961363539383661633331383735663837616366626339 +37396137353532393635666566656337303137326230313130633465343133653936623032356630 +62346130323864306533653634643133393138316637633136666364383633663132623265353232 +33633562643361363062613734356339366466656436643837376166353839393836653030343766 +30356435626364323231336435613934353138313233393634353961323235616465656630303939 +35376338656666396664636562616337336566336565366465383063373236366364393338653665 +66623931633134303633623665653437376136643035653263343066616132383434636235346232 +63326337613536656435373432383265666466656535643364396465396230613838343761646637 +64643432333333313330656462656133663439333137353661353061613235656663373238613536 +66663236373964343531643335323132376266363436316330333664633038343931633439633230 +63306562363832656131346537383937373834356635383134353231363864363865363036616666 +33633730316135303039633831373130343163633765656330363062656637646236646135393433 +37636633623935306135303938356463616365346365646337623639393737646261346232633537 +65623034383962336430353739373530626461653462393762343864373961653963313539396136 +61363239333461353962356130656632363461373064306266356436323833303464353739396561 +34346262653234333966363037303062393435333066363136643463313363376534663738323632 +34363535386439353833343131633765343166336638663630313465613335393561343530373238 +65633365303264306264626135363232653535373062666136383638623465666163363438333462 +37363536393537323836646531323738636261303532653265653833653632346634346333306131 +62656632383734663666613965386562313663373166336437343238393266383231643033643534 +38356237356433356361633465396533626233326338303333646564366238383766343765393038 +62373735363737633030636562346662356463353861653762663361356362323436313338333365 +36653137356566613662326432636337626232383339393165343036376232333630346663646134 +65333265353230303437663937373762366437616633306131363763633764626362613134636133 +36376530363534373931343739626261633833353938613733336333383131313332613434306564 +62663637643564313532383233636136323235363835313330323463303561663663656232346266 +33383266313463373834353831333061316133633732653039343565323331323532626639363461 +33383064666238383963306337316233646231316531313564616430663236653738636231333832 +35643330303563323863343530636666633361393339653438646131333531666633366238643864 +38306161666439353632633563343461353439396236343232333034346635313032306236653838 +66383838386231366637646530643832386336623837316636376234316363386162316163343931 +35366131653930353337346633383861366232316565613363653966613363396234383766623039 +65666464373062333530373863333863343830316630306236626438393266346239613738343337 +37653663373935333632383638306636366236326438313163626332343132303130316134316636 +63306332646166393365386562396633313232616431646233336464363832333164383937653634 +31363731613133393937346330653864666536663130346564623333316137373736386566613263 +32313131363038323335346663336537333238333965633965333030363163656465396564643763 +37643532356165316666383031353364336331343363346366356466333438326537343363646436 +38363763333063636337613861643366313030383931663665303035396437373539366436616532 +35313863356234343666353631663862646662393535666439343231663661353063643839353033 +34366337613935336632363938616461376239363636626134333765393939643865376638353230 +61633931333062313366383261363665396634343836373030313066333339373163646364363363 +64323366373739383066643436666538366132613931393831326133366466626264313638373235 +63373035323133303437353631656463626131346233353137333464633765323932303431373536 +37306639386137353134613764623334626632326135303437643930663339353233623630633833 +35373938633664666365646536313131626234333731663434383864656161623039393963336537 +31616134336563353132623332386333373532366561373363383639653038383864323637323137 +64343765663234346530353136333064613839666136303230376239663164333134316134613635 +39333939656534643138343462326562653038306464653338653961656332623762663336393731 +65343932656437646161643061616636613333383534363634663138376664306465643664363062 +36393865366331653232326135633663623133313630663437376365306235613062303036363038 +63353439666165306631346534376534643664653031326539343165616236653364323834613063 +32353066353863346637316430616333316234396236356436623733653563366438363065646430 +30393632666435636338656565343966323038386335316333373761666466633764613930333339 +38633162393739613335373862393737383163623065396237623132616665666130633463663964 +65373939323730336564303935323934646662356138356264633461316434383839373265373364 +33356337306132393964393437396565383239323735626432353065626461393535363132336566 +62623564616435613666303834656361396131653030626165386438623237313061613562356566 +36343737623964373832353838366237373966623834666362643266303835633465646662633665 +36316537633639376639336363363737353337373630363861383530663037386163313430643066 +35333437366565636233356237303135306134333234303164313331663666343737393963646265 +64643066663965323864383539656435383363663037323866363163306333356336326134633832 +64316361396439393134663835353863303132663764653966316530303631333032336137356134 +63353031326265663035336662623536346136666264363765613733626366353633613933373430 +65663938633761646533626437303336666639643235316638366361323636633839666334653536 +37303365366233353731306466333665643633343734663330356564303765646630353366306538 +39646365616330346561646136666662376232313037613431636337306561626637393634386538 +39323162653462303661333133633433333038383239333033396637356530636139353833383831 +37366566363930396538636236613861396532656538323963636335316333623461653437643963 +31313339393266383132353562343730353831333366303530613765353635643065656663323064 +36346639343531346333346362343632613565303133626461383636646366303236636336643362 +33623738663434396132386539626332623264306264303764393462346532356666643463303665 +39363931383836356436393634353537326333306162626631646264393063346465336534323766 +38623031323538323462626537343331663962656437656333373939653861343839393633666665 +34356566333864343133636238343732363363653666376130383530303338313131343634646434 +32653239643632373833613862326334656436393066343264613565623237646335663564653339 +63316463663664346534386165386566393439306665393962333066323534646532643437343238 +31313364626161356534646661353338366439623765643437336335366136313466653639363165 +37326563356231346332393539353865626436633131306130333538613261656365366364366532 +66666465646338373034343963333530373264343564373865396432353534333338363866353135 +34636463643461356332633366623232303364323061626661323033643635376465326661376365 +62623564653631363638393838383737323233643737316562396665336534316539303333313663 +3737333935616530323134333861393639326631666234336539 diff --git a/ansible/host_vars/larva.home.jowj.net/main.yml b/ansible/host_vars/larva.home.jowj.net/main.yml index 385b43f..dde047a 100644 --- a/ansible/host_vars/larva.home.jowj.net/main.yml +++ b/ansible/host_vars/larva.home.jowj.net/main.yml @@ -1,2 +1,3 @@ ansible_python_interpreter: /usr/bin/python3 interactive: yes +acmedns_base_lego_arch: arm64 diff --git a/ansible/hosts.yml b/ansible/inventory/hosts.yml similarity index 91% rename from ansible/hosts.yml rename to ansible/inventory/hosts.yml index 987a4dd..7aafe31 100644 --- a/ansible/hosts.yml +++ b/ansible/inventory/hosts.yml @@ -8,6 +8,7 @@ all: hosts: hatchery.home.jowj.net: larva.home.jowj.net: + storage.home.jowj.net: vpn: hosts: vpn.awful.club: diff --git a/ansible/readme.org b/ansible/readme.org index e8abf21..cdc2980 100644 --- a/ansible/readme.org +++ b/ansible/readme.org @@ -1,4 +1,8 @@ * setup from scratch: +** install dependencies +ansible-galaxy collection install -r requirements.yml + +** run a play ~ansible-playbook -i hosts.yml all.yml --ask-vault-pass --ask-become-pass~ ** preparing open_the_vault diff --git a/ansible/requirements.yml b/ansible/requirements.yml new file mode 100644 index 0000000..92a0244 --- /dev/null +++ b/ansible/requirements.yml @@ -0,0 +1,4 @@ +--- +collections: + - name: ansible.posix + - name: community.general diff --git a/ansible/roles/acmedns_base/tasks/main.yml b/ansible/roles/acmedns_base/tasks/main.yml new file mode 100644 index 0000000..b77110c --- /dev/null +++ b/ansible/roles/acmedns_base/tasks/main.yml @@ -0,0 +1,84 @@ +--- +- name: Install prereqs (debian) + apt: + name: + - python3-cryptography + state: latest + update_cache: yes + when: ansible_distribution == "Ubuntu" + +- name: Add acme group + group: + name: "{{ acmedns_base_group }}" + system: yes + +- name: Add acme user + user: + name: "{{ acmedns_base_user }}" + group: "{{ acmedns_base_group }}" + system: yes + create_home: yes + home: "{{ acmedns_base_home }}" + +- name: Create acme user .ssh directory + file: + path: "{{ acmedns_base_home }}/.ssh" + state: directory + owner: "{{ acmedns_base_user }}" + group: "{{ acmedns_base_group }}" + mode: "0700" + +- name: Create acme certificates directory + file: + path: "{{ acmedns_base_certificate_dir }}" + state: directory + owner: "{{ acmedns_base_user }}" + group: "{{ acmedns_base_group }}" + mode: "0700" + +- name: Set acme user ssh key + copy: + content: "{{ item.value }}" + dest: "{{ acmedns_base_home }}/.ssh/{{ item.name }}" + owner: "{{ acmedns_base_user }}" + group: "{{ acmedns_base_group }}" + mode: "0600" + with_items: + - name: id_rsa + value: "{{ acmedns_base_privkey }}" + - name: id_rsa.pub + value: "{{ acmedns_base_pubkey }}" + no_log: yes + +- name: Get lego + get_url: + url: "{{ acmedns_base_lego_uri }}" + dest: "{{ acmedns_base_lego_archive_path }}" + +- name: Create lego extration dir + file: + state: directory + path: "{{ acmedns_base_lego_extracted_path }}" + owner: "{{ acmedns_base_user }}" + group: "{{ acmedns_base_group }}" + mode: "0755" + +- name: Extract lego + unarchive: + src: "{{ acmedns_base_lego_archive_path }}" + dest: "{{ acmedns_base_lego_extracted_path }}" + remote_src: yes + +- name: Install lego + file: + state: link + src: "{{ acmedns_base_lego_extracted_path }}/lego" + dest: /usr/local/bin/lego + +- name: Install lego wrapper + template: + src: wraplego.py.j2 + dest: /usr/local/bin/wraplego.py + owner: root + group: root + mode: "0755" diff --git a/ansible/roles/acmedns_base/templates/wraplego.py.j2 b/ansible/roles/acmedns_base/templates/wraplego.py.j2 new file mode 100644 index 0000000..637ed0a --- /dev/null +++ b/ansible/roles/acmedns_base/templates/wraplego.py.j2 @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 + +"""A wrapper for the lego commandline +Based on inflatable-wharf: https://github.com/mrled/inflatable-wharf +""" + +import argparse +import datetime +import logging +import os +import subprocess +import sys + +from cryptography import x509 +from cryptography.hazmat.backends import default_backend + + +logging.basicConfig( + level=logging.INFO, + format='[%(asctime)s]\t%(levelname)s:\t%(message)s', + datefmt='%Y-%m-%d %H:%M:%S') +LOGGER = logging.getLogger(__name__) + + +def abswalk(path): + """Return a list of absolute paths to files and directories + """ + result = [] + for root, dirs, files in os.walk(path): + for dirname in dirs: + result.append(f"{os.path.join(root, dirname)}{os.path.sep}") + for filename in files: + result.append(f"{os.path.join(root, filename)}") + result.sort() + return result + + +def subprocess_run_log(command, env=os.environ.copy()): + """Run and log a command + command A list making up a command and its arguments + env An optional dictionary of environment variables; defaults to this process's env + Returns the CompletedProcess object if the process exits with a zer return code + Throws subprocess.CalledProcessError if the process exites with a nonzero return code + """ + proc = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) + LOGGER.debug("{cmdname} exited with code {rc}\n{out}\n{err}".format( + cmdname=command[0], + rc=proc.returncode, + out=f"STDOUT:\n{proc.stdout}" if proc.stdout else "STDOUT: NONE", + err=f"STDERR:\n{proc.stderr}" if proc.stderr else "STDERR: NONE")) + if proc.returncode != 0: + raise subprocess.CalledProcessError( + proc.returncode, command, output=proc.stdout, stderr=proc.stderr) + return proc + + +def get_cert_expiration(certificate): + """Get the certificate expiration date as a DateTime object + """ + with open(certificate, 'rb') as certfile: + cert_contents = certfile.read() + cert = x509.load_pem_x509_certificate(cert_contents, default_backend()) + return cert.not_valid_after + + +def shouldrun(certificate_path, min_cert_validity=25): + """Test whether certificates should be requested/renewed + certificate Path to a certificate file + min_cert_validity If cert exists but is invalid this many days into the future, renew it + """ + try: + expires = get_cert_expiration(certificate_path) + expiresdelta = expires - datetime.datetime.now() + if expiresdelta.days <= min_cert_validity: + LOGGER.info( + "Determined lego should be run because the cert expires in " + f"{expiresdelta.days} days") + return True + else: + LOGGER.info( + "Determined lego should NOT be run because the cert expires in " + f"{expiresdelta.days} days") + return False + except FileNotFoundError: + LOGGER.info("Determined lego should be run because the cert does not exist locally") + return True + + +def certpath(legodir, domain, filetype): + """Get the path of a cert file based on its characteristics + """ + result = os.path.join(legodir, "certificates", f"{domain}.{filetype}") + LOGGER.debug(f"certpath({legodir}, {domain}, {filetype}) => {result}") + return result + + +def lego(lego_dir, email, domain, authenticator, key_type=None, production=True, whatif=False): + """Run the lego command. + whatif Do not actually run lego, but show what would have run + """ + + command = [ + '/usr/local/bin/lego', '--accept-tos', + '--path', lego_dir, + '--email', email, + '--domains', domain, + '--dns', authenticator, + '--dns-timeout', '60', # helped w 'could not determine authoritative nameservers' err + ] + if not production: + command += ['--server', 'https://acme-staging-v02.api.letsencrypt.org/directory'] + if key_type: + command += ['--key-type', key_type] + if os.path.exists(certpath(lego_dir, domain, 'crt')): + command += ["renew"] + else: + command += ["run"] + + LOGGER.info("Running lego in {mode} mode as [{cmd}]".format( + mode="WHATIF" if whatif else "OPERATIONAL", + cmd=' '.join(command))) + + if not whatif: + subprocess_run_log(command) + + acme_dir_contents = '\n'.join(abswalk(lego_dir)) + LOGGER.info(f"Current contents of {lego_dir}:\n{acme_dir_contents}") + + +def main(*args, **kwargs): + parser = argparse.ArgumentParser() + parser.add_argument("--legodir") + parser.add_argument("--email") + parser.add_argument("--domain") + parser.add_argument("--authenticator", default="route53") + parser.add_argument("--whatif", "-z", action="store_true") + parser.add_argument("--staging", action="store_true") + parser.add_argument("--renewdays", type=int, default=25) + parser.add_argument("--debug", "-d", action="store_true") + parser.add_argument("--key-type") + parser.add_argument("--verbose", "-v", action="store_true") + parsed = parser.parse_args() + if parsed.debug or parsed.verbose: + LOGGER.setLevel(logging.DEBUG) + if shouldrun(certpath(parsed.legodir, parsed.domain, 'crt'), min_cert_validity=parsed.renewdays): + lego( + parsed.legodir, + parsed.email, + parsed.domain, + parsed.authenticator, + key_type=parsed.key_type, + production=not parsed.staging, + whatif=parsed.whatif) + + +if __name__ == '__main__': + sys.exit(main(*sys.argv)) diff --git a/ansible/roles/acmedns_base/vars/main.yml b/ansible/roles/acmedns_base/vars/main.yml new file mode 100644 index 0000000..85e03e2 --- /dev/null +++ b/ansible/roles/acmedns_base/vars/main.yml @@ -0,0 +1,10 @@ +--- +acmedns_base_home: /home/{{ acmedns_base_user }} + +acmedns_base_lego_version: 3.7.0 +acmedns_base_lego_dir: lego_v{{ acmedns_base_lego_version }}_linux_{{ acmedns_base_lego_arch }} +acmedns_base_lego_archive: "{{ acmedns_base_lego_dir }}.tar.gz" +acmedns_base_lego_uri: https://github.com/go-acme/lego/releases/download/v{{ acmedns_base_lego_version }}/{{ acmedns_base_lego_archive }} +acmedns_base_lego_parent: /usr/local/src +acmedns_base_lego_archive_path: "{{ acmedns_base_lego_parent }}/{{ acmedns_base_lego_archive }}" +acmedns_base_lego_extracted_path: "{{ acmedns_base_lego_parent }}/{{ acmedns_base_lego_dir }}" diff --git a/ansible/roles/acmedns_remote_host/defaults/main.yml b/ansible/roles/acmedns_remote_host/defaults/main.yml new file mode 100644 index 0000000..ec5c9b9 --- /dev/null +++ b/ansible/roles/acmedns_remote_host/defaults/main.yml @@ -0,0 +1,4 @@ +--- +acmedns_remote_host_fix_homedir_permissions: false +acmedns_remote_host_allow_passwordless_sudo: false + diff --git a/ansible/roles/acmedns_remote_host/readme.md b/ansible/roles/acmedns_remote_host/readme.md new file mode 100644 index 0000000..2dfe08a --- /dev/null +++ b/ansible/roles/acmedns_remote_host/readme.md @@ -0,0 +1,12 @@ +# `acmedns_remote_host` + +Set up a host so that an `acmedns_*_updater` role (which may run on another host) can copy certs to it. + +This will include adding an ssh key to `authorized_keys`, and may include some other setup tasks. + +Variables: + +- `acmedns_remote_host_user`: The user on this host that will have the keys scp'd to it +- `acmedns_remote_host_ssh_client_pubkey`: The public key to add to `authorized_keys` +- `acmedns_remote_host_fix_homedir_permissions`: Modify homedir of `acmedns_remote_host_user` to not be world/group writable (required for ssh to allow key auth) +- `acmedns_remote_host_allow_passwordless_sudo`: Modify sudoers to allow `acmedns_remote_host_user` to sudo to root without providing a password diff --git a/ansible/roles/acmedns_remote_host/tasks/main.yml b/ansible/roles/acmedns_remote_host/tasks/main.yml new file mode 100644 index 0000000..5bd7a36 --- /dev/null +++ b/ansible/roles/acmedns_remote_host/tasks/main.yml @@ -0,0 +1,25 @@ +--- + +- name: Fix homedir permissions + # SSH won't accept key auth if homedir is world/group writable, which it is by default on Synology + file: + path: "{{ lookup('env', 'HOME') }}" + mode: "0700" + state: directory + when: acmedns_remote_host_fix_homedir_permissions|bool + +- name: Install SSH key + authorized_key: + user: "{{ acmedns_remote_host_user }}" + state: present + key: "{{ acmedns_remote_host_ssh_client_pubkey }}" + +- name: Allow passwordless sudo + copy: + content: |+ + {{ acmedns_remote_host_user }} ALL=(ALL) NOPASSWD: ALL + dest: /etc/sudoers.d/{{ acmedns_remote_host_user }}_passwordless + owner: root + group: root + mode: "0600" + when: acmedns_remote_host_allow_passwordless_sudo|bool diff --git a/ansible/roles/acmedns_syno_updater/defaults/main.yml b/ansible/roles/acmedns_syno_updater/defaults/main.yml new file mode 100644 index 0000000..1459d23 --- /dev/null +++ b/ansible/roles/acmedns_syno_updater/defaults/main.yml @@ -0,0 +1,2 @@ +--- +acmedns_syno_updater_runonce: true diff --git a/ansible/roles/acmedns_syno_updater/meta/main.yml b/ansible/roles/acmedns_syno_updater/meta/main.yml new file mode 100644 index 0000000..cd21505 --- /dev/null +++ b/ansible/roles/acmedns_syno_updater/meta/main.yml @@ -0,0 +1,2 @@ +--- + diff --git a/ansible/roles/acmedns_syno_updater/tasks/main.yml b/ansible/roles/acmedns_syno_updater/tasks/main.yml new file mode 100644 index 0000000..e01581d --- /dev/null +++ b/ansible/roles/acmedns_syno_updater/tasks/main.yml @@ -0,0 +1,55 @@ +--- + +- name: Add synology server to known_hosts + known_hosts: + name: "{{ acmedns_syno_updater_syn_server }}" + key: "{{ acmedns_syno_updater_syn_server_pubkey }}" + become: yes + become_user: "{{ acmedns_syno_updater_user }}" + +- name: Install script + template: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + owner: root + group: "{{ acmedns_syno_updater_group }}" + mode: "0750" + with_items: + - src: acmedns_update.sh.j2 + dest: "{{ acmedns_syno_updater_script_path }}" + +- name: Configure cronvar + cronvar: + name: "{{ item.name }}" + value: "{{ item.value }}" + cron_file: "{{ acmedns_syno_updater_cron_file }}" + with_items: + - name: MAILTO + value: "{{ acmedns_syno_updater_email }}" + +- name: Configure cronjob + cron: + name: "{{ acmedns_syno_updater_job_name }}" + day: "*" + hour: "3" + minute: "47" + job: "{{ acmedns_syno_updater_script_path }}" + user: "{{ acmedns_syno_updater_user }}" + cron_file: "{{ acmedns_syno_updater_cron_file }}" + +- name: Run wrapper script once + # Wrapper script passes --days, so this won't contact Let's Encrypt unless necessary + command: "{{ acmedns_syno_updater_script_path }}" + become: yes + become_user: "{{ acmedns_syno_updater_user }}" + when: acmedns_syno_updater_runonce|bool + +- name: Allow all users to run wrapper script as our user + lineinfile: + path: /etc/sudoers.d/acmedns_{{ acmedns_syno_updater_job_name }} + line: "ALL ALL=({{ acmedns_syno_updater_user }}) NOPASSWD: {{ acmedns_syno_updater_script_path }}" + owner: root + group: root + mode: "0640" + create: yes + validate: visudo -cf %s diff --git a/ansible/roles/acmedns_syno_updater/templates/acmedns_syno_updater_webhook.te.j2 b/ansible/roles/acmedns_syno_updater/templates/acmedns_syno_updater_webhook.te.j2 new file mode 100644 index 0000000..d8bafa5 --- /dev/null +++ b/ansible/roles/acmedns_syno_updater/templates/acmedns_syno_updater_webhook.te.j2 @@ -0,0 +1,14 @@ +module acmedns_syno_updater_webhook_{{ acmedns_syno_updater_job_name }} 1.0; + +require { + type user_home_t; + type sudo_exec_t; + type init_t; + class file { execute execute_no_trans map open read }; +} + +#============= init_t ============== +allow init_t sudo_exec_t:file { execute execute_no_trans map open read }; +allow init_t user_home_t:file map; + + diff --git a/ansible/roles/acmedns_syno_updater/templates/acmedns_update.sh.j2 b/ansible/roles/acmedns_syno_updater/templates/acmedns_update.sh.j2 new file mode 100644 index 0000000..cc11768 --- /dev/null +++ b/ansible/roles/acmedns_syno_updater/templates/acmedns_update.sh.j2 @@ -0,0 +1,51 @@ +#!/bin/sh +set -eu + +export DO_AUTH_TOKEN={{ DO_AUTH_TOKEN }} +echoexec() { echo "Running: $*"; $*; } + +echoexec /usr/local/bin/wraplego.py \ + --verbose \ + --legodir "{{ acmedns_syno_updater_certificate_dir }}" \ + --email "{{ acmedns_syno_updater_email }}" \ + --domain "{{ acmedns_syno_updater_domain }}" \ + --authenticator "digitalocean" \ + +host="{{ acmedns_syno_updater_syn_user }}@{{ acmedns_syno_updater_syn_server }}" +date=$(date +%Y%m%d) +tmppath=/tmp/${date}-acme-update +scp -r {{ acmedns_syno_updater_certificate_dir }}/certificates $host:$tmppath +user="josiah" +# +# SSH to the remote server and install the certs: +# + +echo "$(cat <