diff --git a/README.md b/README.md index 777fabc..5aab28f 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ The intent here is to create an all-in-one social server build * AWS CLI * AWS SessionManager plugin (http://docs.aws.amazon.com/console/systems-manager/session-manager-plugin-not-found) * Python 3.6+ for credentials +* python3-bs4 (BeautifulSoup 4) Your AWS account needs to be moved from the SES sandbox into production in the region you're deploying to. This is requested through the AWS console. diff --git a/ansible/roles/fediblockhole/bin/get_token.py b/ansible/roles/fediblockhole/bin/get_token.py new file mode 100755 index 0000000..1908eb4 --- /dev/null +++ b/ansible/roles/fediblockhole/bin/get_token.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 + +import sys,requests +from bs4 import BeautifulSoup + +# Generate an app token for fediblockhole. + +# Mastodon has opted not to expose admin tasks like this in any way that can be automated easily. + +# This whole thing is a house of cards that will topple as soon as Mastodon's UI changes, +# which is often. It's a bad idea and I regret doing it. Anyway, enjoy. + +# args are simple: +# - server domain +# - app name +# - admin email +# - path to password file + +try: + domain = sys.argv[1] + appname = sys.argv[2] + email = sys.argv[3] + pwpath = sys.argv[4] +except IndexError: + print(f"Usage: {sys.argv[0]} domain email path_to_password_file") + +password = "" +with open(pwpath) as fh: + password = fh.read() + +with requests.Session() as session: + + session.max_redirects = 10 + + # log in first + r = session.get(f"https://{domain}/auth/sign_in") + soup = BeautifulSoup(r.text, "html.parser") + + form = soup.find("form") + assert form.get("action") == "/auth/sign_in" + params = {} + + for field in form.find_all("input"): + params[field.get("name")] = field.get("value") + + params["user[email]"] = email + params["user[password]"] = password + + r = session.post(f"https://{domain}{form.get('action')}", data=params) + + assert r.status_code == 200 + + r = session.get(f"https://{domain}/settings/applications/new") + + soup = BeautifulSoup(r.text, "html.parser") + form = soup.find("form") + + params = {} + for field in form.find_all("input"): + params[field.get("name")] = field.get("value") + for field in form.find_all("textarea"): + params[field.get("name")] = field.get("value") + + params["doorkeeper_application[name]"] = appname + params["doorkeeper_application[website]"] = f"https://{domain}/" + params["doorkeeper_application[redirect_uri]"] = "urn:ietf:wg:oauth:2.0:oob" + params["doorkeeper_application[scopes][]"] = [ "admin:read", "admin:write" ] + + r = session.post(f"https://{domain}{form.get('action')}", data=params) + + soup = BeautifulSoup(r.text, "html.parser") + search = soup.find_all(lambda x: x.name == "a" and x.text == appname) + + tag = search[0] # we will just take the first one + r = session.get(f"https://{domain}{tag['href']}") + + soup = BeautifulSoup(r.text, "html.parser") + tag = soup.find_all(lambda x: x.text == "Your access token")[0] + print(tag.find_next('code').text) + diff --git a/ansible/roles/fediblockhole/tasks/main.yaml b/ansible/roles/fediblockhole/tasks/main.yaml new file mode 100644 index 0000000..8319ed2 --- /dev/null +++ b/ansible/roles/fediblockhole/tasks/main.yaml @@ -0,0 +1,82 @@ +--- + +- name: install base apps + apt: + force_apt_get: yes + name: + - python3-pip + +- name: base path + file: + path: "/etc/fediblockhole/blocklists" + state: directory + recurse: true + +- name: install/upgrade fediblockhole + command: python3 -m pip install --upgrade fediblockhole + +#- name: install/upgrade Mastodon.py +# command: python3 -m pip install --upgrade Mastodon.py + +- name: ensure our domain is in the safelist + lineinfile: + path: /etc/fediblockhole/safelist.csv + create: true + line: "{{ domain_name }}" + +- name: check fediblockhole API credentials + delegate_to: localhost + become: false + stat: + path: credentials/fediblockhole/token + register: token_file + +- name: generate a fediblockhole token + block: + + - name: make fediblockhole credentials dir + delegate_to: localhost + become: false + file: + path: "credentials/fediblockhole" + state: directory + recurse: true + + - name: request app token + delegate_to: localhost + become: false + command: roles/fediblockhole/bin/get_token.py {{ domain_name }} fediblockhole {{ admin_email }} credentials/mastodon/masto_admin_pw + register: apptoken + + - name: write token to file + delegate_to: localhost + become: false + copy: + dest: credentials/fediblockhole/token + content: "{{ apptoken.stdout }}" + + when: token_file.stat.exists != true + +- name: pull config file + template: + src: templates/pull.conf.toml + dest: /etc/fediblockhole/pull.conf.toml + +- name: push config file + template: + src: templates/push.conf.toml + dest: /etc/fediblockhole/push.conf.toml + vars: + token: "{{ lookup('ansible.builtin.file', 'credentials/fediblockhole/token') }}" + +- name: daily cron file + copy: + dest: /etc/cron.daily/fediblockhole + mode: '0755' + content: | + #!/bin/bash + + set -e + /usr/local/bin/fediblock-sync -c /etc/fediblockhole/pull.conf.toml + /usr/local/bin/fediblock-sync -c /etc/fediblockhole/push.conf.toml + diff --git a/ansible/roles/fediblockhole/templates/pull.conf.toml b/ansible/roles/fediblockhole/templates/pull.conf.toml new file mode 100644 index 0000000..cfd30f7 --- /dev/null +++ b/ansible/roles/fediblockhole/templates/pull.conf.toml @@ -0,0 +1,40 @@ +blocklist_instance_sources = [ + # { domain = 'mastodon.social'}, + # { domain = 'union.place'}, +] + +blocklist_url_sources = [ + # some oliphant CSV blocklist files - you only need one "tier" file + { url = 'https://codeberg.org/oliphant/blocklists/raw/branch/main/blocklists/_unified_tier0_blocklist.csv', format = 'csv' }, + #{ url = 'https://codeberg.org/oliphant/blocklists/raw/branch/main/blocklists/_unified_tier1_blocklist.csv', format = 'csv' }, + #{ url = 'https://codeberg.org/oliphant/blocklists/raw/branch/main/blocklists/_unified_tier2_blocklist.csv', format = 'csv' }, + #{ url = 'https://codeberg.org/oliphant/blocklists/raw/branch/main/blocklists/_unified_tier3_blocklist.csv', format = 'csv' }, + #{ url = 'https://codeberg.org/oliphant/blocklists/raw/branch/main/blocklists/oliphant.social.csv', format = 'csv' }, +] + +# reference your safelist +allowlist_url_sources = [ + { url = 'file:///etc/fediblockhole/safelist.csv', format = 'csv' }, +] + +# this file isn't going to push to an instance +no_push_instance = true + +# Store a local copy of the remote blocklists after we fetch them +# set to true if you want (optional) +save_intermediate = false + +## Directory to store the local blocklist copies +savedir = '/etc/fediblockhole/blocklists' + +## File to save the fully merged blocklist into +blocklist_savefile = '/etc/fediblockhole/blocklists/merged_blocklist.csv' + +mergeplan = 'max' + +# merge must match at least [count|pct] sources +# merge_threshold_type = 'count' +# merge_threshold = 0 + +import_fields = ['reject_media', 'reject_reports', 'public_comment', 'private_comment', 'obfuscate'] +export_fields = ['reject_media', 'reject_reports', 'public_comment', 'private_comment', 'obfuscate'] diff --git a/ansible/roles/fediblockhole/templates/push.conf.toml b/ansible/roles/fediblockhole/templates/push.conf.toml new file mode 100644 index 0000000..cd8c39e --- /dev/null +++ b/ansible/roles/fediblockhole/templates/push.conf.toml @@ -0,0 +1,45 @@ +blocklist_instance_sources = [ + # { domain = 'mastodon.social'}, + # { domain = 'union.place'}, +] + +blocklist_url_sources = [ + # some oliphant CSV blocklist files - you only need one "tier" file + { url = 'https://codeberg.org/oliphant/blocklists/raw/branch/main/blocklists/_unified_tier0_blocklist.csv', format = 'csv' }, + #{ url = 'https://codeberg.org/oliphant/blocklists/raw/branch/main/blocklists/_unified_tier1_blocklist.csv', format = 'csv' }, + #{ url = 'https://codeberg.org/oliphant/blocklists/raw/branch/main/blocklists/_unified_tier2_blocklist.csv', format = 'csv' }, + #{ url = 'https://codeberg.org/oliphant/blocklists/raw/branch/main/blocklists/_unified_tier3_blocklist.csv', format = 'csv' }, + #{ url = 'https://codeberg.org/oliphant/blocklists/raw/branch/main/blocklists/oliphant.social.csv', format = 'csv' }, +] + +# reference your safelist +allowlist_url_sources = [ + { url = 'file:///etc/fediblockhole/safelist.csv', format = 'csv' }, +] + +# this file isn't going to push to an instance +no_push_instance = false + +# Store a local copy of the remote blocklists after we fetch them +# set to true if you want (optional) +save_intermediate = false + +## Directory to store the local blocklist copies +savedir = '/etc/fediblockhole/blocklists' + +## File to save the fully merged blocklist into +blocklist_savefile = '/etc/fediblockhole/blocklists/merged_blocklist.csv' + +mergeplan = 'max' + +# merge must match at least [count|pct] sources +# merge_threshold_type = 'count' +# merge_threshold = 0 + +import_fields = ['reject_media', 'reject_reports', 'public_comment', 'private_comment', 'obfuscate'] +export_fields = ['reject_media', 'reject_reports', 'public_comment', 'private_comment', 'obfuscate'] + +blocklist_instance_destinations = [ + { domain = '{{ domain_name }}', token = '{{ token }}', max_followed_severity = 'silence'}, +] + diff --git a/ansible/site.yaml b/ansible/site.yaml index 0d886cc..daf51c4 100644 --- a/ansible/site.yaml +++ b/ansible/site.yaml @@ -11,4 +11,5 @@ - { role: certbot, become: yes } - { role: nginx, become: yes } - { role: mastodon, become: yes } + - { role: fediblockhole, become: yes }