Add XOAuth2 support for GMail

This commit is contained in:
Ivan Martinez-Ortiz 2020-11-02 23:03:43 +01:00
parent 4ba3145635
commit a3c399cad0
7 changed files with 306 additions and 5 deletions

View file

@ -1,4 +1,19 @@
ARG ALPINE_VERSION=latest
FROM alpine:${ALPINE_VERSION} as build
ARG SASL_XOAUTH2_REPO_URL=https://github.com/tarickb/sasl-xoauth2.git
ARG SASL_XOAUTH2_GIT_REF=release-0.9
RUN true && \
apk add --no-cache --upgrade git && \
apk add --no-cache --upgrade cmake clang make gcc g++ libc-dev pkgconfig curl-dev jsoncpp-dev cyrus-sasl-dev && \
git clone --depth 1 --branch ${SASL_XOAUTH2_GIT_REF} ${SASL_XOAUTH2_REPO_URL} /sasl-xoauth2 && \
cd /sasl-xoauth2 && \
mkdir build && \
cd build && \
cmake -DCMAKE_INSTALL_PREFIX=/ .. && \
make
FROM alpine:${ALPINE_VERSION}
LABEL maintaner="Bojan Cekrlic - https://github.com/bokysan/docker-postfix/"
@ -10,8 +25,12 @@ RUN true && \
apk add --no-cache postfix && \
apk add --no-cache opendkim && \
apk add --no-cache --upgrade ca-certificates tzdata supervisor rsyslog musl musl-utils bash opendkim-utils && \
apk add --no-cache --upgrade libcurl jsoncpp && \
(rm "/tmp/"* 2>/dev/null || true) && (rm -rf /var/cache/apk/* 2>/dev/null || true)
# Copy SASL-XOAUTH2 plugin
COPY --from=build /sasl-xoauth2/build/src/libsasl-xoauth2.so /usr/lib/sasl2/
# Set up configuration
COPY /configs/supervisord.conf /etc/supervisord.conf
COPY /configs/rsyslog*.conf /etc/

View file

@ -15,6 +15,7 @@ Simple postfix relay host ("postfix null client") for your Docker containers. Ba
* [Postfix-specific options](#postfix-specific-options)
* [RELAYHOST, RELAYHOST_USERNAME and RELAYHOST_PASSWORD](#relayhost-relayhost_username-and-relayhost_password)
* [RELAYHOST_TLS_LEVEL](#relayhost_tls_level)
* [XOAUTH2_CLIENT_ID, XOAUTH2_SECRET, XOAUTH2_INITIAL_ACCESS_TOKEN and XOAUTH2_INITIAL_REFRESH_TOKEN](#xoauth2_client_id-xoauth2_secret-xoauth2_initial_access_token-and-xoauth2_initial_refresh_token)
* [MASQUERADED_DOMAINS](#masqueraded_domains)
* [SMTP_HEADER_CHECKS](#smtp_header_checks)
* [POSTFIX_hostname](#postfix_hostname)
@ -27,6 +28,7 @@ Simple postfix relay host ("postfix null client") for your Docker containers. Ba
* [Changing the DKIM selector](#changing-the-dkim-selector)
* [Overriding specific OpenDKIM settings](#overriding-specific-opendkim-settings)
* [Verifying your DKIM setup](#verifying-your-dkim-setup)
* [Docker Secrets](#docker-secrets)
* [Helm chart](#helm-chart)
* [Extending the image](#extending-the-image)
* [Using custom init scripts](#using-custom-init-scripts)
@ -137,7 +139,11 @@ To change the log format, set the (unsurprisingly named) variable `LOG_FORMAT=js
* `RELAYHOST` = Host that relays your messages
* `RELAYHOST_USERNAME` = An (optional) username for the relay server
* `RELAYHOST_PASSWORD` = An (optional) login password for the relay server
* `RELAYHOST_TLS_LEVEL` = Relay host TLS connection leve
* `RELAYHOST_TLS_LEVEL` = Relay host TLS connection level
* `XOAUTH2_CLIENT_ID` = OAuth2 client id used when configured as a relayhost.
* `XOAUTH2_SECRET` = OAuth2 secret used when configured as a relayhost.
* `XOAUTH2_INITIAL_ACCESS_TOKEN` = Initial OAuth2 access token.
* `XOAUTH2_INITIAL_REFRESH_TOKEN` = Initial OAuth2 refresh token.
* `MASQUERADED_DOMAINS` = domains where you want to masquerade internal hosts
* `SMTP_HEADER_CHECKS`= Set to `1` to enable header checks of to a location of the file for header checks
* `POSTFIX_hostname` = Set tha name of this postfix server
@ -181,6 +187,48 @@ Define relay host TLS connection level. See [smtp_tls_security_level](http://www
This level defines how the postfix will connect to your upstream server.
#### `XOAUTH2_CLIENT_ID`, `XOAUTH2_SECRET`, `XOAUTH2_INITIAL_ACCESS_TOKEN` and `XOAUTH2_INITIAL_REFRESH_TOKEN`
> Note: These parameters are used when `RELAYHOST` and `RELAYHOST_USERNAME` are provided.
These parameters allow you to configure a relayhost that requires (or recommends) the [XOAuth2 authentication method](https://github.com/tarickb/sasl-xoauth2) (e.g. GMail).
- `XOAUTH2_CLIENT_ID` and `XOAUTH2_SECRET` are the [OAuth2 client credentials](#oauth2-client-credentials-gmail).
- `XOAUTH2_INITIAL_ACCESS_TOKEN` and `XOAUTH2_INITIAL_REFRESH_TOKEN` are the [initial access token and refresh tokens](#obtain-initial-access-token-gmail). These values are only required to initialize the token file `/var/spool/postfix/xoauth2-tokens/$RELAYHOST_USERNAME`.
Example:
```
docker run --rm --name pruebas-postfix \
-e RELAYHOST="[smtp.gmail.com]:587" \
-e RELAYHOST_USERNAME="<put.your.account>@gmail.com" \
-e RELAYHOST_TLS_LEVEL="encrypt" \
-e XOAUTH2_CLIENT_ID="<put_your_oauth2_client_id>" \
-e XOAUTH2_SECRET="<put_your_oauth2_secret>" \
-e ALLOW_EMPTY_SENDER_DOMAINS="true" \
-e XOAUTH2_INITIAL_ACCESS_TOKEN="<put_your_acess_token>" \
-e XOAUTH2_INITIAL_REFRESH_TOKEN="<put_your_refresh_token>" \
boky/postfix
```
Next sections describe how to obtain these values.
##### OAuth2 Client Credentials (GMail)
Visit the [Google API Console](https://console.developers.google.com/) to obtain OAuth 2 credentials (a client ID and client secret) for an "Installed application" application type.
Save the client ID and secret and use them to initialize `XOAUTH2_CLIENT_ID` and `XOAUTH2_SECRET` respectively.
We'll also need these credentials in the next step.
##### Obtain Initial Access Token (GMail)
Use the [Gmail OAuth2 developer tools](https://github.com/google/gmail-oauth2-tools/) to obtain an OAuth token by following the [Creating and Authorizing an OAuth Token](https://github.com/google/gmail-oauth2-tools/wiki/OAuth2DotPyRunThrough#creating-and-authorizing-an-oauth-token) instructions.
Save the resulting tokens and use them to initialize `XOAUTH2_INITIAL_ACCESS_TOKEN` and `XOAUTH2_INITIAL_REFRESH_TOKEN`.
##### Debug XOAuth2 issues
If you have XOAuth2 authentication issues you can enable XOAuth2 debug message setting `XOAUTH2_SYSLOG_ON_FAILURE` to `"yes"` (default: `"no"`). If you need a more detailed log trace about XOAuth2 you can set `XOAUTH2_FULL_TRACE` to `"yes"` (default: `"no"`).
#### `MASQUERADED_DOMAINS`
If you don't want outbound mails to expose hostnames, you can use this variable to enable Postfix's
@ -333,6 +381,26 @@ variable from OpenDKIM config.
I strongly suggest using a service such as [dkimvalidator](https://dkimvalidator.com/) to make sure your keys are set up
properly and your DNS server is serving them with the correct records.
### Docker Secrets
As an alternative to passing sensitive information via environment variables, _FILE may be appended to some environment variables (see below), causing the initialization script to load the values for those variables from files present in the container. In particular, this can be used to load passwords from Docker secrets stored in /run/secrets/<secret_name> files. For example:
```
docker run --rm --name pruebas-postfix \
-e RELAYHOST="[smtp.gmail.com]:587" \
-e RELAYHOST_USERNAME="<put.your.account>@gmail.com" \
-e RELAYHOST_TLS_LEVEL="encrypt" \
-e XOAUTH2_CLIENT_ID_FILE="/run/secrets/xoauth2-client-id" \
-e XOAUTH2_SECRET_FILE="/run/secrets/xoauth2-secret" \
-e ALLOW_EMPTY_SENDER_DOMAINS="true" \
-e XOAUTH2_INITIAL_ACCESS_TOKEN_FILE="/run/secrets/xoauth2-access-token" \
-e XOAUTH2_INITIAL_REFRESH_TOKEN_FILE="/run/secrets/xoauth2-refresh-token" \
boky/postfix
```
Currently, this is only supported for `XOAUTH2_CLIENT_ID`, `XOAUTH2_SECRET`, `XOAUTH2_INITIAL_ACCESS_TOKEN` and `XOAUTH2_INITIAL_REFRESH_TOKEN`.
## Helm chart
This image comes with its own helm chart. The chart versions are aligned with the releases of the image. Charts are hosted
@ -428,11 +496,12 @@ account which will use `UID:GID` of `100:101`. `opendkim` will run under account
### Relaying messages through your Gmail account
Please note that Gmail does not support using your password with non-OAuth2 clients, which -- technically -- postfix is.
You will need to enable [Less secure apps](https://support.google.com/accounts/answer/6010255?hl=en) in your account
and assign an "app password". You'll also need to use (only) your email as the sender address.
Please note that Gmail does not support using your password with non-OAuth2 clients. You will need to either enable
[Less secure apps](https://support.google.com/accounts/answer/6010255?hl=en) in your account and assign an "app password"
or [configure postfix support for XOAuth2 authentication](#xoauth2_client_id-xoauth2_secret-xoauth2_initial_access_token-and-xoauth2_initial_refresh_token).
You'll also need to use (only) your email as the sender address.
Your configuration would be as follows:
If you follow the *less than secure* route, your configuration would be as follows:
```shell script
RELAYHOST=smtp.gmail.com:587

View file

@ -104,6 +104,8 @@ postfix_setup_relayhost() {
# smtp_tls_CApath
do_postconf -e "smtp_tls_CAfile=/etc/ssl/certs/ca-certificates.crt"
file_env 'RELAYHOST_PASSWORD'
if [ -n "$RELAYHOST_USERNAME" ] && [ -n "$RELAYHOST_PASSWORD" ]; then
echo -e " using username ${emphasis}$RELAYHOST_USERNAME${reset} and password ${emphasis}(redacted)${reset}."
if [[ -f /etc/postfix/sasl_passwd ]]; then
@ -131,6 +133,51 @@ postfix_setup_relayhost() {
fi
}
postfix_setup_xoauth2_pre_setup() {
file_env 'XOAUTH2_CLIENT_ID'
file_env 'XOAUTH2_SECRET'
if [ -n "$XOAUTH2_CLIENT_ID" ] && [ -n "$XOAUTH2_SECRET" ]; then
cat <<EOF > /etc/sasl-xoauth2.conf
{
"client_id": "${XOAUTH2_CLIENT_ID}",
"client_secret": "${XOAUTH2_SECRET}",
"log_to_syslog_on_failure": "${XOAUTH2_SYSLOG_ON_FAILURE:-no}",
"log_full_trace_on_failure": "${XOAUTH2_FULL_TRACE:-no}"
}
EOF
if [ -z "$RELAYHOST" ] || [ -z "${RELAYHOST_USERNAME}" ]; then
error "You need to specify RELAYHOST and RELAYHOST_USERNAME otherwise Postfix will not run!"
exit 1
fi
export RELAYHOST_PASSWORD="/var/spool/postfix/xoauth2-tokens/${RELAYHOST_USERNAME}"
if [ ! -d "/var/spool/postfix/xoauth2-tokens" ]; then
mkdir -p "/var/spool/postfix/xoauth2-tokens"
fi
if [ ! -f "/var/spool/postfix/xoauth2-tokens/${RELAYHOST_USERNAME}" ] && [ -n "$XOAUTH2_INITIAL_ACCESS_TOKEN" ] && [ -n "$XOAUTH2_INITIAL_REFRESH_TOKEN" ]; then
cat <<EOF > "/var/spool/postfix/xoauth2-tokens/${RELAYHOST_USERNAME}"
{
"access_token" : "${XOAUTH2_INITIAL_ACCESS_TOKEN}",
"refresh_token" : "${XOAUTH2_INITIAL_REFRESH_TOKEN}",
"expiry" : "0"
}
EOF
fi
chown -R postfix:root "/var/spool/postfix/xoauth2-tokens"
fi
}
postfix_setup_xoauth2_post_setup() {
if [ -n "$XOAUTH2_CLIENT_ID" ] && [ -n "$XOAUTH2_SECRET" ]; then
do_postconf -e 'smtp_sasl_security_options='
do_postconf -e 'smtp_sasl_mechanism_filter=xoauth2'
do_postconf -e 'smtp_tls_session_cache_database=btree:${data_directory}/smtp_scache'
fi
}
postfix_setup_networks() {
if [ ! -z "$MYNETWORKS" ]; then
deprecated "${emphasis}MYNETWORKS${reset} variable is deprecated. Please use ${emphasis}POSTFIX_mynetworks${reset} instead."
@ -388,3 +435,11 @@ execute_post_init_scripts() {
done
fi
}
unset_sensible_variables() {
unset RELAYHOST_PASSWORD
unset XOAUTH2_CLIENT_ID
unset XOAUTH2_SECRET
unset XOAUTH2_INITIAL_ACCESS_TOKEN
unset XOAUTH2_INITIAL_REFRESH_TOKEN
}

View file

@ -172,4 +172,26 @@ do_postconf() {
}
# usage: file_env VAR [DEFAULT]
# ie: file_env 'XYZ_DB_PASSWORD' 'example'
# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of
# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature)
#
file_env() {
local var="$1"
local fileVar="${var}_FILE"
local def="${2:-}"
if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
error "Both $var and $fileVar are set (but are exclusive)"
fi
local val="$def"
if [ "${!var:-}" ]; then
val="${!var}"
elif [ "${!fileVar:-}" ]; then
val="$(< "${!fileVar}")"
fi
export "$var"="$val"
unset "$fileVar"
}
export reset green yellow orange orange_emphasis lightblue red gray emphasis underline

View file

@ -17,7 +17,9 @@ postfix_restrict_message_size # Restrict the size of messages (or set them
postfix_reject_invalid_helos # Reject invalid HELOs
postfix_set_hostname # Set up host name
postfix_set_relay_tls_level # Set TLS level security for relays
postfix_setup_xoauth2_pre_setup # (Pre) Setup XOAUTH2 authentication
postfix_setup_relayhost # Setup a relay host, if defined
postfix_setup_xoauth2_post_setup # (Post) Setup XOAUTH2 autentication
postfix_setup_networks # Set MYNETWORKS
postfix_setup_debugging # Enable debugging, if defined
postfix_setup_sender_domains # Configure allowed sender domains
@ -28,6 +30,7 @@ postfix_custom_commands # Apply custom postfix settings
opendkim_custom_commands # Apply custom OpenDKIM settings
postfix_open_submission_port # Enable the submission port
execute_post_init_scripts # Execute any scripts found in /docker-init.db/
unset_sensible_variables # Remove environment variables that contains sensible values (secrets) that are read from conf files
notice "Starting: ${emphasis}rsyslog${reset}, ${emphasis}postfix${reset}$DKIM_ENABLED"
exec supervisord -c /etc/supervisord.conf

View file

@ -1,4 +1,19 @@
ARG ALPINE_VERSION=latest
FROM alpine:${ALPINE_VERSION} as build
ARG SASL_XOAUTH2_REPO_URL=https://github.com/tarickb/sasl-xoauth2.git
ARG SASL_XOAUTH2_GIT_REF=release-0.9
RUN true && \
apk add --no-cache --upgrade git && \
apk add --no-cache --upgrade cmake clang make gcc g++ libc-dev pkgconfig curl-dev jsoncpp-dev cyrus-sasl-dev && \
git clone --depth 1 --branch ${SASL_XOAUTH2_GIT_REF} ${SASL_XOAUTH2_REPO_URL} /sasl-xoauth2 && \
cd /sasl-xoauth2 && \
mkdir build && \
cd build && \
cmake -DCMAKE_INSTALL_PREFIX=/ .. && \
make
FROM alpine:${ALPINE_VERSION}
LABEL maintaner="Bojan Cekrlic - https://github.com/bokysan/docker-postfix/"
@ -7,10 +22,14 @@ RUN true && \
apk add --no-cache postfix && \
apk add --no-cache opendkim && \
apk add --no-cache --upgrade ca-certificates tzdata supervisor rsyslog musl musl-utils bash opendkim-utils && \
apk add --no-cache --upgrade libcurl jsoncpp && \
(rm "/tmp/"* 2>/dev/null || true) && (rm -rf /var/cache/apk/* 2>/dev/null || true)
RUN apk add --no-cache bash bats && \
(rm "/tmp/"* 2>/dev/null || true) && (rm -rf /var/cache/apk/* 2>/dev/null || true)
# Copy SASL-XOAUTH2 plugin
COPY --from=build /sasl-xoauth2/build/src/libsasl-xoauth2.so /usr/lib/sasl2/
WORKDIR /code
ENTRYPOINT ["/usr/bin/bats"]
CMD ["-v"]

View file

@ -0,0 +1,114 @@
#!/usr/bin/env bats
load /code/scripts/common.sh
load /code/scripts/common-run.sh
@test "check sentive variables are unset" {
local RELAYHOST_PASSWORD="password"
local XOAUTH2_CLIENT_ID="client_id"
local XOAUTH2_SECRET="secret"
local XOAUTH2_INITIAL_ACCESS_TOKEN="access_token"
local XOAUTH2_INITIAL_REFRESH_TOKEN="refres_token"
unset_sensible_variables
[ -z "$RELAYHOST_PASSWORD" ]
[ -z "$XOAUTH2_CLIENT_ID" ]
[ -z "$XOAUTH2_SECRET" ]
[ -z "$XOAUTH2_INITIAL_ACCESS_TOKEN" ]
[ -z "$XOAUTH2_INITIAL_REFRESH_TOKEN" ]
}
@test "reading sensitive values from environment or from file" {
local RELAYHOST_PASSWORD="password"
local tmp_file=$(mktemp)
echo "password" > $tmp_file
local XOAUTH2_CLIENT_ID_FILE="$tmp_file"
file_env 'RELAYHOST_PASSWORD'
file_env 'XOAUTH2_CLIENT_ID'
[ -n "$RELAYHOST_PASSWORD" ]
[ -n "$XOAUTH2_CLIENT_ID" ]
}
@test "pre-configure xoauth2 in postfix only if relayhost is configured" {
local RELAYHOST="[smtp.example.org]:597"
local RELAYHOST_USERNAME="your.acount@example.org"
local XOAUTH2_CLIENT_ID="client_id"
local XOAUTH2_SECRET="secret"
local XOAUTH2_SYSLOG_ON_FAILURE="no"
local XOAUTH2_FULL_TRACE="yes"
local XOAUTH2_INITIAL_ACCESS_TOKEN="access_token"
local XOAUTH2_INITIAL_REFRESH_TOKEN="refresh_token"
postfix_setup_xoauth2_pre_setup
[ -f "/etc/sasl-xoauth2.conf" ]
result=$(cat /etc/sasl-xoauth2.conf | grep -e 'client_id' | sed -r 's/\s*"[^"]+"\s*:\s*"([^,]*)"\s*,?/\1/')
[ "$result" == "$XOAUTH2_CLIENT_ID" ]
result=$(cat /etc/sasl-xoauth2.conf | grep -e 'client_secret' | sed -r 's/\s*"[^"]+"\s*:\s*"([^,]*)"\s*,?/\1/')
[ "$result" == "$XOAUTH2_SECRET" ]
result=$(cat /etc/sasl-xoauth2.conf | grep -e 'log_to_syslog_on_failure' | sed -r 's/\s*"[^"]+"\s*:\s*"([^,]*)"\s*,?/\1/')
[ "$result" == "$XOAUTH2_SYSLOG_ON_FAILURE" ]
result=$(cat /etc/sasl-xoauth2.conf | grep -e 'log_full_trace_on_failure' | sed -r 's/\s*"[^"]+"\s*:\s*"([^,]*)"\s*,?/\1/')
[ "$result" == "$XOAUTH2_FULL_TRACE" ]
[ "$RELAYHOST_PASSWORD" == "/var/spool/postfix/xoauth2-tokens/${RELAYHOST_USERNAME}" ]
result=$(cat "${RELAYHOST_PASSWORD}" | grep -e 'access_token' | sed -r 's/\s*"[^"]+"\s*:\s*"([^,]*)"\s*,?/\1/')
[ "$result" == "$XOAUTH2_INITIAL_ACCESS_TOKEN" ]
result=$(cat "${RELAYHOST_PASSWORD}" | grep -e 'refresh_token' | sed -r 's/\s*"[^"]+"\s*:\s*"([^,]*)"\s*,?/\1/')
[ "$result" == "$XOAUTH2_INITIAL_REFRESH_TOKEN" ]
}
@test "pre-configure error trying to configure xoauth2 in postfix if relayhost is not configured" {
local XOAUTH2_CLIENT_ID="client_id"
local XOAUTH2_SECRET="secret"
local RELAYHOST="[smtp.example.org]:597"
run postfix_setup_xoauth2_pre_setup
[ "$status" -eq 1 ]
[ "$output" == "‣ ERROR You need to specify RELAYHOST and RELAYHOST_USERNAME otherwise Postfix will not run!" ]
unset RELAYHOST
local RELAYHOST_USERNAME="your.acount@example.org"
run postfix_setup_xoauth2_pre_setup
[ "$status" -eq 1 ]
[ "$output" == "‣ ERROR You need to specify RELAYHOST and RELAYHOST_USERNAME otherwise Postfix will not run!" ]
}
@test "post-configure xoauth2 not needed" {
local XOAUTH2_CLIENT_ID="client_id"
postfix_setup_xoauth2_post_setup
postfix check
result=$(cat /etc/postfix/main.cf | grep -e 'smtp_sasl_mechanism_filter' | sed -r 's/\s*[^\s]+\s*=\s*([^\s]*)/\1/')
[ "$result" != "xoauth2" ]
}
@test "post-configure xoauth2 required" {
local XOAUTH2_CLIENT_ID="client_id"
local XOAUTH2_SECRET="secret"
postfix_setup_xoauth2_post_setup
postfix check
cat /etc/postfix/main.cf | grep -q -E '^\s*smtp_sasl_security_options\s*=\s*$'
local status=$?
[ "$status" -eq 0 ]
cat /etc/postfix/main.cf | grep -q -E '^\s*smtp_sasl_mechanism_filter\s*=\s*xoauth2$'
local status=$?
[ "$status" -eq 0 ]
cat /etc/postfix/main.cf | grep -q -E '^\s*smtp_tls_session_cache_database\s*=\s*btree:\$\{data_directory\}/smtp_scache$'
local status=$?
[ "$status" -eq 0 ]
}