diff options
-rw-r--r-- | certs/dkim/8f00fb94ec6c37aacb48bd43e073f9b7:fripost.org.pub (renamed from certs/dkim/8f00fb94ec6c37aacb48bd43e073f9b7.pub) | 0 | ||||
-rw-r--r-- | certs/dkim/9df9cdc7e101629b5003b587945afa70:x.fripost.org.pub (renamed from certs/dkim/9df9cdc7e101629b5003b587945afa70.pub) | 0 | ||||
-rw-r--r-- | certs/dkim/README | 7 | ||||
-rw-r--r-- | group_vars/all.yml | 2 | ||||
-rw-r--r-- | roles/amavis/tasks/main.yml | 10 | ||||
-rw-r--r-- | roles/amavis/templates/etc/amavis/conf.d/50-user.j2 | 2 | ||||
-rwxr-xr-x | roles/common/files/usr/local/bin/genkeypair.sh | 16 |
7 files changed, 13 insertions, 24 deletions
diff --git a/certs/dkim/8f00fb94ec6c37aacb48bd43e073f9b7.pub b/certs/dkim/8f00fb94ec6c37aacb48bd43e073f9b7:fripost.org.pub index ef400f4..ef400f4 100644 --- a/certs/dkim/8f00fb94ec6c37aacb48bd43e073f9b7.pub +++ b/certs/dkim/8f00fb94ec6c37aacb48bd43e073f9b7:fripost.org.pub diff --git a/certs/dkim/9df9cdc7e101629b5003b587945afa70.pub b/certs/dkim/9df9cdc7e101629b5003b587945afa70:x.fripost.org.pub index 2574f71..2574f71 100644 --- a/certs/dkim/9df9cdc7e101629b5003b587945afa70.pub +++ b/certs/dkim/9df9cdc7e101629b5003b587945afa70:x.fripost.org.pub diff --git a/certs/dkim/README b/certs/dkim/README index e5addf9..2137a8d 100644 --- a/certs/dkim/README +++ b/certs/dkim/README @@ -1,11 +1,12 @@ -To convert a PEM-encoded public key to a TXT record, run +To convert the PEM-encoded public keys from this directory to TXT +records, run $ SELECTOR="8f00fb94ec6c37aacb48bd43e073f9b7" $ DOMAIN="fripost.org" - $ printf "%s._domainkey%s IN TXT (\n" "$SELECTOR" "${DOMAIN:+.$DOMAIN.}"; \ + $ printf "%s._domainkey.%s IN TXT (\n" "$SELECTOR" "$DOMAIN"; \ { printf "v=DKIM1; k=rsa; t=s; s=email; p="; - sed '/^--.*--$/d' <"./certs/dkim/$SELECTOR.pub" | tr -d '\n'; + openssl pkey -pubin -in "./certs/dkim/$SELECTOR:$DOMAIN.pub" -outform DER | base64 -w0 } | fold -w64 | sed 's/.*/ "&"/; $s/$/ )\n/' Remove the "t=s" tag if subdomaining or third-party signature (hosted domain) is required, cf. RFC 6376 sec. 3.6.1. diff --git a/group_vars/all.yml b/group_vars/all.yml index f222b56..4fcfc39 100644 --- a/group_vars/all.yml +++ b/group_vars/all.yml @@ -36,25 +36,25 @@ postfix_instance: , port: 2526 } MX: { name: mx, group: mta } out: { name: out, group: mta , addr: "{{ (groups.all | length > 1) | ternary( ipsec[ hostvars[groups.out[0]].inventory_hostname_short ], '127.0.0.1') }}" , port: 2525 } MSA: { name: msa , addr: "{{ (groups.all | length > 1) | ternary( ipsec[ hostvars[groups.MSA[0]].inventory_hostname_short ], '127.0.0.1') }}" , port: 2587 } lists: { name: lists , addr: "{{ (groups.all | length > 1) | ternary( ipsec[ hostvars[groups.lists[0]].inventory_hostname_short ], '127.0.0.1') }}" , port: 2527 } imapsvr_addr: "{{ postfix_instance.IMAP.addr | ipaddr }}" dkim_keys: giraff: # match key "fripost.org": # domain of the entity signing the message (should be unique accross match keys) d: fripost.org - # selector (should be globally unique and random) + # selector (randomly generated with `xxd -p -l16 </dev/urandom`) s: 8f00fb94ec6c37aacb48bd43e073f9b7 "~": # catch-all, for our virtual domains d: x.fripost.org s: 9df9cdc7e101629b5003b587945afa70 diff --git a/roles/amavis/tasks/main.yml b/roles/amavis/tasks/main.yml index 92a0e81..3036c52 100644 --- a/roles/amavis/tasks/main.yml +++ b/roles/amavis/tasks/main.yml @@ -35,57 +35,57 @@ lineinfile: dest=/etc/aliases create=yes regexp="^amavis{{':'}} " line="amavis{{':'}} root" - name: Compile the static local Postfix database postmap: cmd=postalias src=/etc/aliases db=lmdb owner=root group=root mode=0644 - name: Create directory /etc/amavis/dkim file: path=/etc/amavis/dkim state=directory owner=root group=root mode=0755 when: "'out' in group_names" tags: - genkey - dkim - name: Generate a private key for DKIM signing - command: genkeypair.sh dkim --privkey=/etc/amavis/dkim/{{ item }}.pem -t rsa -b 2048 - with_items: "{{ (dkim_keys[inventory_hostname_short] | default({})).values() | map(attribute='s') | list }}" + command: genkeypair.sh dkim --privkey="/etc/amavis/dkim/{{ item.s }}:{{ item.d }}.pem" -t rsa -b 2048 + with_items: "{{ (dkim_keys[inventory_hostname_short] | default({})).values() | list }}" register: dkim changed_when: dkim.rc == 0 failed_when: dkim.rc > 1 when: "'out' in group_names" notify: - Restart Amavis tags: - genkey - dkim - name: Fetch DKIM keys fetch_cmd: cmd="openssl pkey -pubout -outform PEM" - stdin=/etc/amavis/dkim/{{ item }}.pem - dest=certs/dkim/{{ item }}.pub - with_items: "{{ (dkim_keys[inventory_hostname_short] | default({})).values() | map(attribute='s') | list }}" + stdin="/etc/amavis/dkim/{{ item.s }}:{{ item.d }}.pem" + dest="certs/dkim/{{ item.s }}:{{ item.d }}.pub" + with_items: "{{ (dkim_keys[inventory_hostname_short] | default({})).values() | list }}" tags: - genkey - dkim - name: Configure Amavis template: src=etc/amavis/conf.d/50-user.j2 dest=/etc/amavis/conf.d/50-user owner=root group=root mode=0644 register: r3 notify: - Restart Amavis - meta: flush_handlers - name: Start Amavis service: name=amavis state=started - name: Install 'amavis' Munin plugin diff --git a/roles/amavis/templates/etc/amavis/conf.d/50-user.j2 b/roles/amavis/templates/etc/amavis/conf.d/50-user.j2 index f3ff416..a09c366 100644 --- a/roles/amavis/templates/etc/amavis/conf.d/50-user.j2 +++ b/roles/amavis/templates/etc/amavis/conf.d/50-user.j2 @@ -16,41 +16,41 @@ use strict; $max_servers = 5; $recipient_delimiter = '+'; $mydomain = 'fripost.org'; $X_HEADER_LINE = "Debian $myproduct_name at $mydomain"; @mynetworks_maps = (); @remove_existing_spam_headers_maps = (); @bypass_virus_checks_maps = (); # load virus checking code $enable_dkim_verification = 1; # load DKIM signing/verifying code {% if 'out' not in group_names %} undef $enable_dkim_signing; @bypass_spam_checks_maps = (); # load spam checking code {% else %} $enable_dkim_signing = 1; # Sign *all* outgoing mails with *our* key (yes, amavis complains, but this is # safe as we force our domain with the 'd' tag). {% for x,k in dkim_keys[inventory_hostname_short] | default({}) | dictsort() -%} -dkim_key({{ (x == "~") | ternary('qr/./', "'"+x+"'") }}, '{{ k.s }}', '/etc/amavis/dkim/{{ k.s }}.pem'); +dkim_key({{ (x == "~") | ternary('qr/./', "'"+x+"'") }}, '{{ k.s }}', '/etc/amavis/dkim/{{ k.s }}:{{ k.d }}.pem'); {% endfor -%} @dkim_signature_options_bysender_maps = ( {% for x,k in dkim_keys[inventory_hostname_short] | default({}) | dictsort() %} { '{{ (x == "~") | ternary('.', x) }}' => { d => '{{ k.d }}' , a => 'rsa-sha256' , ttl => 21*24*3600 , c => 'relaxed/simple' } }{% if not loop.last %}, {% endif %} {% endfor %} ); # Conform to RFC 4871 and don't sign Received: headers. $signed_header_fields{received} = 0; {% endif %} # Defang viruses and nothing else %defang_maps_by_ccat = ( &CC_VIRUS => 1 diff --git a/roles/common/files/usr/local/bin/genkeypair.sh b/roles/common/files/usr/local/bin/genkeypair.sh index 01b279a..ad65aef 100755 --- a/roles/common/files/usr/local/bin/genkeypair.sh +++ b/roles/common/files/usr/local/bin/genkeypair.sh @@ -4,40 +4,41 @@ # certificates or Certificate Signing Requests, or DKIM private keys. # Inspired from make-ssl-cert(8) and opendkim-genkey(8). # # Copyright © 2014 Guilhem Moulin <guilhem@fripost.org> # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. set -ue PATH=/usr/bin:/bin +export PATH # Default values type=rsa bits= hash= force=0 config= pubkey=pubkey.pem privkey=privkey.pem dns= ou= cn= usage= mode= owner= group= usage() { cat >&2 <<- EOF @@ -57,52 +58,40 @@ usage() { --cn: common Name (default: \$(hostname --fqdn) --dns: hostname for AltName; can be repeated -f: force; can be repeated (0: don't overwrite, default; 1: reuse private key if it exists; 2: overwrite both keys if they exist) --config: configuration file --pubkey: public key file (default: pubkey.pem) --privkey: private key file (default: privkey.pem) --usage: key usage (default: digitalSignature,keyEncipherment,keyCertSign) --mode: set privkey's permission mode (default: 0600) --owner: set privkey's owner (default: the process' current owner) --group: set privkey's group (default: the process' current group) Return values: 0 The key pair was successfully generated 1 The public or private key file exists, and -f is not set 2 The key generation failed EOF } -dkiminfo() { - echo "Add the following TXT record to your DNS zone:" - echo "${cn:-$(date +%Y%m%d)}._domainkey\tIN\tTXT ( " - # See https://tools.ietf.org/html/rfc4871#section-3.6.1 - # t=s: the "i=" domain in signature headers MUST NOT be a subdomain of "d=" - # s=email: limit DKIM signing to email - openssl pkey -pubout <"$privkey" | sed '/^--.*--$/d' \ - | { echo -n "v=DKIM1; k=$type; t=s; s=email; p="; tr -d '\n'; } \ - | fold -w 250 \ - | { sed 's/.*/\t"&"/'; echo ' )'; } -} - [ $# -gt 0 ] || { usage; exit 2; } cmd="$1"; shift case "$cmd" in x509|csr|dkim|keypair) ;; *) echo "Unrecognized command: $cmd" >&2; exit 2 esac nou=1 while [ $# -gt 0 ]; do case "$1" in -t) shift; type="$1";; -t*) type="${1#-t}";; -b) shift; bits="$1";; -b*) bits="${1#-b}";; -h) shift; hash="$1";; -h*) hash="${1#-h}";; --dns=?*) dns="${dns:+$dns, }DNS:${1#--dns=}";; @@ -164,44 +153,43 @@ if [ -z "$config" -a \( "$cmd" = x509 -o "$cmd" = csr \) ]; then req_extensions = v3_req x509_extensions = v3_req [ req_distinguished_name ] organizationName = Fripost organizationalUnitName = SSLcerts $(echo "$ou") commonName = ${cn:-/} [ v3_req ] subjectAltName = email:admin@fripost.org${dns:+, $dns} basicConstraints = critical, CA:FALSE # https://security.stackexchange.com/questions/24106/which-key-usages-are-required-by-each-key-exchange-method keyUsage = critical, ${usage:-digitalSignature, keyEncipherment, keyCertSign} subjectKeyIdentifier = hash EOF fi if [ -s "$privkey" -a $force -eq 0 ]; then echo "Error: private key exists: $privkey" >&2 - [ "$cmd" = dkim ] && dkiminfo exit 1 elif [ ! -s "$privkey" -o $force -ge 2 ]; then install --mode="${mode:-0600}" ${owner:+--owner="$owner"} ${group:+--group="$group"} /dev/null "$privkey" || exit 2 openssl $genkey -rand /dev/urandom $genkeyargs >"$privkey" || exit 2 - [ "$cmd" = dkim ] && { dkiminfo; exit; } + [ "$cmd" = dkim ] && exit fi if [ "$cmd" = x509 -a "$pubkey" = "$privkey" ]; then pubkey=$(mktemp) openssl req -config "$config" -new -x509 ${hash:+-$hash} -days 3650 -key "$privkey" >"$pubkey" || exit 2 cat "$pubkey" >>"$privkey" || exit 2 rm -f "$pubkey" elif [ "$cmd" = x509 -o "$cmd" = csr ]; then if [ -s "$pubkey" -a $force -eq 0 ]; then echo "Error: public key exists: $pubkey" >&2 exit 1 else [ "$cmd" = x509 ] && x509=-x509 || x509= openssl req -config "$config" -new $x509 ${hash:+-$hash} -days 3650 -key "$privkey" >"$pubkey" || exit 2 fi elif [ "$cmd" = keypair -a "$pubkey" ]; then openssl pkey -pubout <"$privkey" >"$pubkey" fi |