From b6d8ce371cabab5c2829cb2b4a38e96a1a8f455d Mon Sep 17 00:00:00 2001 From: polarathene <5098581+polarathene@users.noreply.github.com> Date: Mon, 1 Mar 2021 00:53:15 +1300 Subject: [PATCH 01/14] docs(config): Add initial `mkdocs.yml` config --- docs/mkdocs.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 docs/mkdocs.yml diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml new file mode 100644 index 00000000..0fac38a7 --- /dev/null +++ b/docs/mkdocs.yml @@ -0,0 +1,35 @@ +site_name: 'Docker Mailserver' +site_description: 'A fullstack but simple mail server (SMTP, IMAP, LDAP, Antispam, Antivirus, etc.) using Docker.' +site_author: 'docker-mailserver (Github Organization)' +repo_name: 'docker-mailserver' +repo_url: 'https://github.com/docker-mailserver/docker-mailserver' +copyright: '

© Docker Mailserver Organization
This project is licensed under the MIT license.

' + +docs_dir: 'content/' +theme: + name: 'material' + icon: + repo: fontawesome/brands/github + features: + - navigation.tabs + - navigation.expand + +markdown_extensions: + - toc: + permalink: ⚓︎ + - abbr + - attr_list + - admonition + - pymdownx.highlight: + extend_pygments_lang: + - name: yml + lang: yaml + - name: cf + lang: cfg + - name: conf + lang: cfg + - pymdownx.superfences + - pymdownx.magiclink + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg From 48cf6ffeb0eb6e6a2b03cb322d139dff11489e13 Mon Sep 17 00:00:00 2001 From: polarathene <5098581+polarathene@users.noreply.github.com> Date: Fri, 5 Mar 2021 01:39:26 +1300 Subject: [PATCH 02/14] docs(assets): Add SVG logo and ICO favicon. Adds the SVG logo I created (composited rather) in mid-feb in one of my PRs that went slightly off-topic about migrating docs from Github Wiki. Enables the logo and favicon in `mkdocs.yml`. SVG sources for both full colour and simplified monochromatic are included along with PNG images for use anywhere else, such as the organization logo. The two SVG of the same names from `src` dir, are used by the docs and have been processed through SVGOMG, an SVG file size optimizer. Any future modifications should use the source files. --- docs/content/assets/logo/dmo-logo-white.svg | 1 + docs/content/assets/logo/dmo-logo.svg | 1 + docs/content/assets/logo/favicon-32x32.png | Bin 0 -> 1130 bytes .../assets/logo/src/dmo-logo-white.png | Bin 0 -> 33684 bytes .../assets/logo/src/dmo-logo-white.svg | 109 +++++++++++++++ docs/content/assets/logo/src/dmo-logo.png | Bin 0 -> 53338 bytes docs/content/assets/logo/src/dmo-logo.svg | 126 ++++++++++++++++++ docs/mkdocs.yml | 3 + 8 files changed, 240 insertions(+) create mode 100644 docs/content/assets/logo/dmo-logo-white.svg create mode 100644 docs/content/assets/logo/dmo-logo.svg create mode 100644 docs/content/assets/logo/favicon-32x32.png create mode 100644 docs/content/assets/logo/src/dmo-logo-white.png create mode 100644 docs/content/assets/logo/src/dmo-logo-white.svg create mode 100644 docs/content/assets/logo/src/dmo-logo.png create mode 100644 docs/content/assets/logo/src/dmo-logo.svg diff --git a/docs/content/assets/logo/dmo-logo-white.svg b/docs/content/assets/logo/dmo-logo-white.svg new file mode 100644 index 00000000..70d4bfac --- /dev/null +++ b/docs/content/assets/logo/dmo-logo-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/content/assets/logo/dmo-logo.svg b/docs/content/assets/logo/dmo-logo.svg new file mode 100644 index 00000000..d738edb3 --- /dev/null +++ b/docs/content/assets/logo/dmo-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/content/assets/logo/favicon-32x32.png b/docs/content/assets/logo/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..f129fc449c1ae90b39f32da80e214e28e8dfc26c GIT binary patch literal 1130 zcmV-w1eN=VP)5r;fx)y$f4it-@ka^JE;gxu!Q z>0+nTF@7!^6XzYX!i2p}gg#C~z-`VC;Wn?JgWNM!k8;_?6a$pY@?`}GwC{R2=rP0d zcJ|30jGxWIM4tmUzT5|Yy$b*PD*P^iz=ykMg&FhM_7Fly><(=Us9z0w8(d+8&vpPBmy7VFxF}BAcPQ0s@%mC1EOgNNx7wnbVEEz zxh{zin-=dvhgmc?fWOt44_(-yK$BKQ2qCdtw=u~5pO>LrWl}#yhKy= z$H_zS>itv^%1mcx8|mG(wuzov8+vMO=&rS)t5QSPk;VPh%UZDgdA(_WhMqoMi_3DR z+8|xSkgWj-*Dyr$HKe?7IugPvjuXaZGgAz#i&Yy^%3P3bA*l6NV6Ey#QqfT`%?8S4 zXG~1Yy@Yb3DJi!MqVdl)w`?APacT_en?Hidy^&B{{%YZ(m=F>bIVOrcmXKWnC@wNQKA3+=6+Rt1->0qiVwfhopK3l}L1PJCRd z*v1qC%4_GR7pm2LV2XjJ58TjC{C0=%AW~mE1E$(w;quuf&plI&ACd|yA=^Tc0LS~m z6a$GyI`q21mAAdckCgIH!SpmxF6(Z4@i40_VVVi0#(V1*yzwoxw|`#QuG>P;PYxmZ z=>{-$H<%*dogTKOB{nfG`x7z_K|L`z-HDYA+coeSFA{SOgK>F-T4m`nz_+w5MQo+q zxSWu22-fO;2-nDMgUHq(^piv2H9jO2zY3H&Ne3&C z`qEjbb$>u>xC*`TZ|IGGL2I}Q^}S259_vN&;YK7Lcq-CvZv0NUcw)&rewm84aB+|- z#sK9q%rq16>G?>=DndeLA>z{l wLapM6g^On>7Y>V6#1cZH*BrW`4c%@13!ImS;1L_K4gdfE07*qoM6N<$f;%P|Pyhe` literal 0 HcmV?d00001 diff --git a/docs/content/assets/logo/src/dmo-logo-white.png b/docs/content/assets/logo/src/dmo-logo-white.png new file mode 100644 index 0000000000000000000000000000000000000000..5768359865334f984add5ab49a0269f3a6afafb4 GIT binary patch literal 33684 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/content/assets/logo/src/dmo-logo.png b/docs/content/assets/logo/src/dmo-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..cd049f8d541d83e1bad890b4c7d33171bfe20351 GIT binary patch literal 53338 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 0fac38a7..8fd516b7 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -6,8 +6,11 @@ repo_url: 'https://github.com/docker-mailserver/docker-mailserver' copyright: '

© Docker Mailserver Organization
This project is licensed under the MIT license.

' docs_dir: 'content/' +site_url: 'https://docker-mailserver.github.io/docker-mailserver' theme: name: 'material' + favicon: assets/logo/favicon-32x32.png + logo: assets/logo/dmo-logo-white.svg icon: repo: fontawesome/brands/github features: From 95983cbebc7251b610f11d2228ba1064978ede88 Mon Sep 17 00:00:00 2001 From: polarathene <5098581+polarathene@users.noreply.github.com> Date: Mon, 1 Mar 2021 01:14:12 +1300 Subject: [PATCH 03/14] docs(ci): Add workflow to build and deploy docs Separate workflows for `push` and `pull_request` events. This avoids a `skipped` job status (`Check Run`?) always being presented for the `deploy` job in Pull Requests. --- chore(`.gitignore`): Ignore the `docs/site/` build output Ignore to avoid local builds output appearing in git as unstaged. --- .github/workflows/deploy-docs.yml | 33 +++++++++++++++++++++++++++++++ .github/workflows/pr-docs.yml | 22 +++++++++++++++++++++ .gitignore | 2 ++ 3 files changed, 57 insertions(+) create mode 100644 .github/workflows/deploy-docs.yml create mode 100644 .github/workflows/pr-docs.yml diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 00000000..8aeba50a --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,33 @@ +name: 'Documentation' + +on: + workflow_dispatch: + push: + branches: + - master + paths: + - '.github/workflows/deploy-docs.yml' + - 'docs/**' + +# Jobs will run shell commands from this subdirectory: +defaults: + run: + working-directory: docs + +jobs: + deploy: + name: 'Deploy Docs' + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + + - name: 'Build with mkdocs-material via Docker' + run: docker run --rm -v ${PWD}:/docs squidfunk/mkdocs-material build --strict + + - name: 'Deploy to Github Pages' + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/site + user_name: 'github-actions[bot]' + user_email: '41898282+github-actions[bot]@users.noreply.github.com' diff --git a/.github/workflows/pr-docs.yml b/.github/workflows/pr-docs.yml new file mode 100644 index 00000000..d0f1d030 --- /dev/null +++ b/.github/workflows/pr-docs.yml @@ -0,0 +1,22 @@ +name: 'Documentation' + +on: + pull_request: + paths: + - '.github/workflows/pr-docs.yml' + - 'docs/**' + +# Jobs will run shell commands from this subdirectory: +defaults: + run: + working-directory: docs + +jobs: + build: + name: 'Verify Build' + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + + - name: 'Build with mkdocs-material via Docker' + run: docker run --rm -v ${PWD}:/docs squidfunk/mkdocs-material build --strict diff --git a/.gitignore b/.gitignore index 71cc9fed..0b897101 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,5 @@ test/duplicate_configs config.bak testconfig.bak + +docs/site From 251a87e622c67ceeb1f425feaaf3ef02ff569efb Mon Sep 17 00:00:00 2001 From: polarathene <5098581+polarathene@users.noreply.github.com> Date: Thu, 11 Mar 2021 17:57:16 +1300 Subject: [PATCH 04/14] docs(ci): Add versioning support without using 'mike' It seems it may have be simpler to just use 'mike'.. Additionally squashes related commits providing minor fixes + improvements: - Use a job dependency (`needs`) to avoid `push` event race conditions due to parallel jobs. - Improve workflow file documentation via inline comments. - Make ShellCheck linting happy. - `chown` doesn't seem to work unless on the default branch for CI. Opted to use the docker `--user` approach instead. --- .github/workflows/deploy-docs.yml | 85 +++++++++++++++++-- .../scripts/docs/update-versions-json.sh | 59 +++++++++++++ docs/mkdocs.yml | 7 ++ 3 files changed, 144 insertions(+), 7 deletions(-) create mode 100755 .github/workflows/scripts/docs/update-versions-json.sh diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 8aeba50a..a721a8ed 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -8,11 +8,18 @@ on: paths: - '.github/workflows/deploy-docs.yml' - 'docs/**' + # Responds to tags being pushed (branches and paths conditions above do not apply to tags). + # Takes a snapshot of the docs from the tag (unaffected by branch or path restraints above), + # Stores build in a subdirectory with name matching the git tag `v.` substring: + tags: + - 'v[0-9]+.[0-9]+*' -# Jobs will run shell commands from this subdirectory: -defaults: - run: - working-directory: docs +env: + # Default docs version to build and deploy: + DOCS_VERSION: edge + # Assign commit authorship to official Github Actions bot when pushing to the `gh-pages` branch: + GIT_USER: 'github-actions[bot]' + GIT_EMAIL: '41898282+github-actions[bot]@users.noreply.github.com' jobs: deploy: @@ -21,13 +28,77 @@ jobs: steps: - uses: actions/checkout@v2 + - name: 'Check if deploy is for a `v.` tag version instead of `edge`' + if: startsWith(github.ref, 'refs/tags/') + working-directory: docs + run: | + DOCS_VERSION=$(grep -oE 'v[0-9]+\.[0-9]+' <<< "${GITHUB_REF}") + echo "DOCS_VERSION=${DOCS_VERSION}" >> "${GITHUB_ENV}" + + # Docs should build referencing the tagged version instead: + sed -i "s|^\(site_url:.*\)edge|\1${DOCS_VERSION}|" mkdocs.yml + - name: 'Build with mkdocs-material via Docker' - run: docker run --rm -v ${PWD}:/docs squidfunk/mkdocs-material build --strict + working-directory: docs + # --user is required for build output file ownership to match the CI user instead of the containers internal user + run: docker run --rm --user "$(id -u):$(id -g)" -v "${PWD}:/docs" squidfunk/mkdocs-material build --strict + + - name: 'If a tagged version, fix canonical links and remove `404.html`' + if: startsWith(github.ref, 'refs/tags/') + working-directory: docs/site + run: | + # 404 is not useful due to how Github Pages implement custom 404 support: + # (Note the edge 404.html isn't useful either as it's not copied to the `gh-pages` branch root) + rm 404.html + + # Replace '${DOCS_VERSION}' (defaults to 'edge') in the 'canonical' link element of HTML files, + # with the tagged docs version: + find . -type f -name "*.html" -exec \ + sed -i "s|^\(.*.` substring from tag name' + id: add-version + continue-on-error: true + working-directory: gh-pages + run: '../.github/workflows/scripts/docs/update-versions-json.sh' + + # If an actual change was made to `versions.json`, commit and push it. + # Otherwise the step is skipped instead of reporting job failure. + - name: 'Push update for `versions.json`' + if: ${{ steps.add-version.outcome == 'success' }} + working-directory: gh-pages + run: | + git config user.name ${{ env.GIT_USER }} + git config user.email ${{ env.GIT_EMAIL }} + git add versions.json + git commit -m "chore: Add ${{ env.DOCS_VERSION }} to version selector list" + git push diff --git a/.github/workflows/scripts/docs/update-versions-json.sh b/.github/workflows/scripts/docs/update-versions-json.sh new file mode 100755 index 00000000..6c27dc85 --- /dev/null +++ b/.github/workflows/scripts/docs/update-versions-json.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +# CI ENV `GITHUB_REF` from Github Actions CI provides the tag or branch that triggered the build +# See `github.ref`: https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#github-context +# https://docs.github.com/en/actions/reference/environment-variables +function _update-versions-json { + # Extract the version tag, truncate `` version and any suffix beyond it. + local MAJOR_MINOR + MAJOR_MINOR=$(grep -oE 'v[0-9]+\.[0-9]+' <<< "${GITHUB_REF}") + # Github Actions CI method for exporting ENV vars to share across a jobs steps + # https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable + echo "DOCS_VERSION=${MAJOR_MINOR}" >> "${GITHUB_ENV}" + + if [[ -z "${MAJOR_MINOR}" ]] + then + echo "Could not extract valid \`v.\` substring, exiting.." + exit 1 + fi + + local VERSIONS_JSON='versions.json' + local IS_VALID + IS_VALID=$(jq '.' "${VERSIONS_JSON}") + + if [[ ! -f "${VERSIONS_JSON}" ]] || [[ -z "${IS_VALID}" ]] + then + echo "'${VERSIONS_JSON}' doesn't exist or is invalid. Creating.." + echo '[{"version": "edge", "title": "edge", "aliases": []}]' > "${VERSIONS_JSON}" + fi + + + # Only add this tag version the first time it's encountered: + local VERSION_EXISTS + VERSION_EXISTS=$(jq --arg version "${MAJOR_MINOR}" '[.[].version == $version] | any' "${VERSIONS_JSON}") + + if [[ "${VERSION_EXISTS}" == "true" ]] + then + echo "${MAJOR_MINOR} docs are already supported. Nothing to change, exiting.." + exit 1 + else + echo "Added support for ${MAJOR_MINOR} docs." + # Add any logic here if you want the version selector to have a different label (`title`) than the `version` URL/subdirectory. + local TITLE=${TITLE:-${MAJOR_MINOR}} + + # Assumes the first element is always the "latest" unreleased version (`edge` for us), and then newest version to oldest. + # `jq` takes the first array element of array as slice, concats with new element, then takes the slice of remaining original elements to concat. + # Thus assumes this script is always triggered by newer versions, no older major/minor releases as our build workflow isn't setup to support rebuilding older docs. + local UPDATED_JSON + UPDATED_JSON=$(jq --arg version "${MAJOR_MINOR}" --arg title "${TITLE}" \ + '.[:1] + [{version: $version, title: $title, aliases: []}] + .[1:]' \ + "${VERSIONS_JSON}" + ) + + # See `jq` FAQ advising this approach to update file: + # https://github.com/stedolan/jq/wiki/FAQ + echo "${UPDATED_JSON}" > tmp.json && mv tmp.json "${VERSIONS_JSON}" + fi +} + +_update-versions-json diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 8fd516b7..c5eb1b00 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -17,6 +17,13 @@ theme: - navigation.tabs - navigation.expand +# We do not use `mike`, but enabling this will enable the version selector UI. +# It references `versions.json` on `gh-pages` branch, +# however we have a basic setup that only matches `version` to a subdirectory. +extra: + version: + provider: mike + markdown_extensions: - toc: permalink: ⚓︎ From f13df19b874e56ae3a2c5c95a1660d98e4111c98 Mon Sep 17 00:00:00 2001 From: polarathene <5098581+polarathene@users.noreply.github.com> Date: Thu, 11 Mar 2021 16:49:43 +1300 Subject: [PATCH 05/14] docs(styles): Add external link icon workaround Adds some third-party CSS as`mkdocs-material` doesn't seem interested in a PR to upstream this feature to the community. --- Uses a font icon approach for the external link as alternatives like SVG was reported in PR as breaking on Chrome. The logo has been made larger than theme default, it needs a little push from the left to align well with the tabs below it. --- Unrelated: Additionally experiment with the Instant Navigation feature. --- docs(styles): Various improvements Multiple related commits from original PR have been squashed into this. Some messages may be redundant due to loss of history. --- docs(styles): Minor improvements - Use relative path for external-link - UI enhancement for version selector - Improve inline documentation for `customizations.css` Make separate styling sections more evident (since we're not using multiple files or build tools). --- docs(styles): Replace permalink to fix UX bug --- docs(styles): Replace permalink feature for alternative approach Previous commit already switched `permalink` for `anchorlink` option, but the `#` symbol had UI concerns regarding font-size/scale and fitting into the gutter. Gutter change reverted, switch to REM units and symbol replaced by thin vertical rectangle scaled by font height, far better consistency for placement. --- docs(styles): Refactor the heading link style Effectively ended up making a border-left line style, just not as consistent and more complicated. Fixed that by adjusting styles. Adds optional background fill and restores inline code style for headings. --- docs/content/assets/css/customizations.css | 84 +++++++++++++++++++ docs/content/assets/fonts/external-link.woff | Bin 0 -> 1008 bytes docs/mkdocs.yml | 6 +- 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 docs/content/assets/css/customizations.css create mode 100644 docs/content/assets/fonts/external-link.woff diff --git a/docs/content/assets/css/customizations.css b/docs/content/assets/css/customizations.css new file mode 100644 index 00000000..53fee32d --- /dev/null +++ b/docs/content/assets/css/customizations.css @@ -0,0 +1,84 @@ +/* This file adds our styling additions / fixes to maintain. */ + +/* ============================================================================================================= */ + +/* External Link icon feature. Rejected from upstreaming to `mkdocs-material`. +Alternative solution using SVG icon here (Broken on Chrome?): https://github.com/squidfunk/mkdocs-material/issues/2318#issuecomment-789461149 +Tab or Nav sidebar with non-relative links will prepend an icon (font glyph) +If you want to append instead, switch `::before` to `::after`. +*/ +/* reference the icon font to use */ +@font-face { + font-family: 'external-link'; + src: url('../fonts/external-link.woff') format('woff'); +} + +/* Matches the two nav link classes that start with `http` `href` values, regular docs pages use relative URLs instead. */ +.md-tabs__link[href^="http"]::before, .md-nav__link[href^="http"]::before { + display: inline-block; /* treat similar to text */ + font-family: 'external-link'; + content:'\0041'; /* represents "A" which our font renders as an icon instead of the "A" glyph */ + font-size: 80%; /* icon is a little too big by default, scale it down */ +} + +/* ============================================================================================================= */ + +/* UI Improvement: Header bar (top of page) adjustments - Increase scale of logo and adjust white-space */ +/* Make the logo larger without impacting other header components */ +.md-header__button.md-logo > img { transform: scale(180%); margin-left: 0.4rem; } +/* Reduce the white-space between the Logo and Title components */ +.md-header__title { margin-left: 0.3rem; } + +/* ============================================================================================================= */ + +/* UI Improvement: Add light colour bg for the version selector, with some rounded corners */ +.md-version__current { + background-color: rgb(255,255,255,0.18); /* white with 18% opacity */ + padding: 5px; + border-radius: 3px; +} + +/* ============================================================================================================= */ + +/* + UX Bugfix for permalink affecting typography in headings. + Upstream will not fix: https://github.com/squidfunk/mkdocs-material/issues/2369 +*/ + +/* Headings are configured to be links (instead of only the permalink symbol), removes the link colour */ +div.md-content article.md-content__inner a.toclink { + color: currentColor; +} + +/* Instead of a permalink symbol at the end of heading text, use a border line on the left spanning height of heading */ +/* Includes optional background fill with rounded right-side corners, and restores inline code style */ +/* NOTE: Headings with markdown links embedded disrupt the bg fill style, as they're not children of `a.toclink` element */ +div.md-content article.md-content__inner a.toclink { + display: inline-block; /* Enables multi-line support for both border and bg color */ + border-left: .2rem solid transparent; /* transparent placeholder to avoid heading shift during reveal transition */ + margin-left: -0.6rem; /* Offset heading to the left */ + padding-left: 0.4rem; /* Push heading back to original position, margin-left - border-left widths */ + transition: background-color 200ms,border-left 200ms; + + /* Only relevant if using background highlight style */ + border-radius: 0 0.25rem 0.25rem 0; + padding-right: 0.4rem; +} + +div.md-content article.md-content__inner a.toclink:hover, +div.md-content article.md-content__inner :target > a.toclink { + border-left: .2rem solid #448aff; /* highlight line on the left */ + background-color: #b3dbff6e; /* background highlight fill */ + transition: background-color 200ms,border-left 200ms; +} + +/* Upstream overrides some of the `code` element styles for headings, restore them */ +div.md-content article.md-content__inner a.toclink code { + padding: 0 0.3em; /* padding to the left and right, not top and bottom */ + border-radius: 0.2rem; /* 0.1rem of original style bit too small */ + background-color: var(--md-code-bg-color); +} + +.highlight.no-copy .md-clipboard { display: none; } + +/* ============================================================================================================= */ diff --git a/docs/content/assets/fonts/external-link.woff b/docs/content/assets/fonts/external-link.woff new file mode 100644 index 0000000000000000000000000000000000000000..6e888e08eb8c9d68a544cb12a2a6a303ad4783cb GIT binary patch literal 1008 zcmXT-cXMN4WB>x@4-8x&nk@&y2eDCsf3Ut0P~-~`+W~P{*w$p@iE&7HoVeGWu_LPA+1V@RsCEZ( zH91JM{J(kRUS08I(~X-<15|h>7Q}1S+|Djx>3?w1$}7K=b7^l{LR(|L$ArnxeJo=; zC(NJl+xkVo_Pe~(AFYr)UCk~Xj=u^DtXHga=FO8jg7$-}_6 zm=PG;6?1C)z5Nawh`6r*?YyOlk%Mh+f}sP`l`qN%7X&X!V+&%RYv2{V+}^xTXnU6V z0l5eU?L^rPjAjQ_zp8lTu1TL?Ui0+{gT-NUma_Y-*~gjG-;~815L_oK{xV;7`icI& z(<>gHENuOEvrzPVMQO}o%X=1%Q~GY+eRy}HgY5i1Zi}PSA1nRse=PKB(VHDT-i1$h zIQ@UHqpPu^Fy?UYj}M_sCLLI{Q6_Kc`t2XDta8cS;o6w@B67=%)hE5!E2n3(X?dor zP0C$A_4Ce)FRLzXG@Fu`G;LG5(&{Z{Mt)~vde@k5HG5|^d*SDMCv+Y VJ+?Ot8s|BJm>C$aF$e)80|54Ca) Date: Mon, 1 Mar 2021 23:41:19 +1300 Subject: [PATCH 06/14] docs(refactor): Large refactor + additions + fixes Consistency pass, formatting cleanup and fixes, introduce admonitions, add front-matter. --- docs: Add front-matter --- docs: Fix and format links - Some links were invalid (eg files moved or renamed) - Some were valid but had invalid section headers (content removed or migrated) - Some use `http://` instead of `https://` when the website supports a secure connection. - Some already used the `[name][reference]` convention but often with a number that wasn't as useful for maintenance. - All referenced docs needed URLs replaced. Opted for the `[name][reference]` approach to group them all clearly at the bottom of the doc, especially with the relative URLs and in some cases many duplicate entries. - All `tomav` references from the original repo prior to switch to an organization have been corrected. - Minor cosmetic changes to the `name` part of the URL, such as for referencing issues to be consistent. - Some small changes to text body, usually due to duplicate URL reference that was unnecessary (open relay, youtous) - Switched other links to use the `[name][reference]` format when there was a large group of URLs such as wikipedia or kubernetes. Github repos that reference projects related to `docker-mailserver` also got placed here so they're noticed better by maintainers. This also helped quite a bit with `mermaid` external links that are very long. - There was a Github Wiki supported syntax in use `[[name | link]]` for `fetchmail` page that isn't compatible by default with MkDocs (needs a plugin), converted to `[name][reference]` instead since it's a relative link. --- docs: Update commit link for LDAP override script Logic moved to another file, keeping the permalink commit reference so it's unaffected by any changes in the file referenced in future. --- docs: Heading corrections Consistency pass. Helps with the Table of Contents (top-right UI) aka Document Outline. docs: codefence cleanup --- docs: misc cleanup --- docs: Add Admonitions Switches `
` usage for collapsible admonitions (`???`) while other text content is switched to the visually more distinct admoniton (`!!!` or `???+`) style. This does affect editor syntax highlighting a bit and markdown linting as it's custom non-standard markdown syntax. --- docs/content/advanced/auth-ldap.md | 40 +- docs/content/advanced/full-text-search.md | 72 +-- docs/content/advanced/ipv6.md | 16 +- docs/content/advanced/kubernetes.md | 176 +++---- docs/content/advanced/mail-fetchmail.md | 64 ++- .../advanced/mail-forwarding/aws-ses.md | 31 +- .../advanced/mail-forwarding/relay-hosts.md | 51 +- docs/content/advanced/mail-sieve.md | 42 +- .../maintenance/update-and-cleanup.md | 16 +- docs/content/advanced/optional-config.md | 78 ++- .../advanced/override-defaults/dovecot.md | 53 +- .../advanced/override-defaults/postfix.md | 22 +- .../config/best-practices/autodiscover.md | 10 +- docs/content/config/best-practices/dkim.md | 43 +- docs/content/config/best-practices/dmarc.md | 13 +- docs/content/config/best-practices/spf.md | 38 +- docs/content/config/pop3.md | 23 +- docs/content/config/security/fail2ban.md | 38 +- docs/content/config/security/ssl.md | 491 ++++++++++-------- .../security/understanding-the-ports.md | 89 ++-- docs/content/config/setup.sh.md | 16 +- .../config/troubleshooting/debugging.md | 54 +- docs/content/config/troubleshooting/faq.md | 272 ++++++---- .../config/user-management/accounts.md | 33 +- .../content/config/user-management/aliases.md | 25 +- docs/content/index.md | 29 +- docs/content/introduction.md | 94 +++- .../tutorials/installation-examples.md | 436 ++++++++-------- ...nly-mailserver-with-ldap-authentication.md | 20 +- docs/mkdocs.yml | 11 + 30 files changed, 1369 insertions(+), 1027 deletions(-) diff --git a/docs/content/advanced/auth-ldap.md b/docs/content/advanced/auth-ldap.md index e602240c..8e0f4eb7 100644 --- a/docs/content/advanced/auth-ldap.md +++ b/docs/content/advanced/auth-ldap.md @@ -1,4 +1,8 @@ -### Introduction +--- +title: 'LDAP Authentication' +--- + +## Introduction Getting started with ldap and this mailserver we need to take 3 parts in account: @@ -6,9 +10,10 @@ Getting started with ldap and this mailserver we need to take 3 parts in account * DOVECOT * SASLAUTHD (this can also be handled by dovecot above) -### List with the variables to control the container provisioning +## Variables to Control Provisioning by the Container __POSTFIX__: + * `LDAP_QUERY_FILTER_USER` * `LDAP_QUERY_FILTER_GROUP` * `LDAP_QUERY_FILTER_ALIAS` @@ -19,14 +24,16 @@ __SASLAUTHD__: * `SASLAUTHD_LDAP_FILTER` __DOVECOT__: + * `DOVECOT_USER_FILTER` * `DOVECOT_PASS_FILTER` +!!! note + This page will provide several use cases like recipes to show, how this project can be used with it's LDAP Features. -**NOTE**: This page will provide several use cases like recipes to show, how this project can be used with it's LDAP Features. +## LDAP Setup - Kopano / Zarafa -### Ldap Setup - Kopano/Zarafa -```yml +```yaml --- version: '2' @@ -55,7 +62,7 @@ services: - ENABLE_CLAMAV=1 - ENABLE_FAIL2BAN=1 - ENABLE_POSTGREY=1 - - SASLAUTHD_PASSWD= + - SASLAUTHD_PASSWD= # >>> SASL Authentication - ENABLE_SASLAUTHD=1 @@ -100,19 +107,18 @@ volumes: driver: local ``` -If your directory has not the postfix-book schema installed, then you must change the internal attribute handling for dovecot. For this you have to change the ```pass_attr``` and the ```user_attr``` mapping, as shown in the example below: +If your directory has not the postfix-book schema installed, then you must change the internal attribute handling for dovecot. For this you have to change the `pass_attr` and the `user_attr` mapping, as shown in the example below: -```yml - - DOVECOT_PASS_ATTR==user,=password - - DOVECOT_USER_ATTR==home,=mail,=uid, =gid +```yaml +- DOVECOT_PASS_ATTR==user,=password +- DOVECOT_USER_ATTR==home,=mail,=uid, =gid ``` -The following example illustrates this for a directory that has the qmail-schema installed and that uses ```uid```: +The following example illustrates this for a directory that has the qmail-schema installed and that uses `uid`: -```yml - - DOVECOT_PASS_ATTRS=uid=user,userPassword=password - - DOVECOT_USER_ATTRS=homeDirectory=home,qmailUID=uid,qmailGID=gid,mailMessageStore=mail - - DOVECOT_PASS_FILTER=(&(objectClass=qmailUser)(uid=%u)(accountStatus=active)) - - DOVECOT_USER_FILTER=(&(objectClass=qmailUser)(uid=%u)(accountStatus=active)) +```yaml +- DOVECOT_PASS_ATTRS=uid=user,userPassword=password +- DOVECOT_USER_ATTRS=homeDirectory=home,qmailUID=uid,qmailGID=gid,mailMessageStore=mail +- DOVECOT_PASS_FILTER=(&(objectClass=qmailUser)(uid=%u)(accountStatus=active)) +- DOVECOT_USER_FILTER=(&(objectClass=qmailUser)(uid=%u)(accountStatus=active)) ``` - diff --git a/docs/content/advanced/full-text-search.md b/docs/content/advanced/full-text-search.md index b4ec6f6a..2a73507c 100644 --- a/docs/content/advanced/full-text-search.md +++ b/docs/content/advanced/full-text-search.md @@ -1,3 +1,7 @@ +--- +title: 'Full-Text Search' +--- + ## Overview Full-text search allows all messages to be indexed, so that mail clients can quickly and efficiently search messages by their full text content. @@ -6,49 +10,49 @@ The [dovecot-solr Plugin](https://wiki2.dovecot.org/Plugins/FTS/Solr) is used in ## Setup Steps -1. docker-compose.yml: +1. `docker-compose.yml`: + + ```yaml + solr: + image: lmmdock/dovecot-solr:latest + volumes: + - solr-dovecot:/opt/solr/server/solr/dovecot + restart: always + + mailserver: + image: tvial/docker-mailserver:latest + ... + volumes: + ... + - ./etc/dovecot/conf.d/10-plugin.conf:/etc/dovecot/conf.d/10-plugin.conf:ro + ... -``` - solr: - image: lmmdock/dovecot-solr:latest volumes: - - solr-dovecot:/opt/solr/server/solr/dovecot - restart: always - - mailserver: - image: tvial/docker-mailserver:latest - ... - volumes: - ... - - ./etc/dovecot/conf.d/10-plugin.conf:/etc/dovecot/conf.d/10-plugin.conf:ro - ... - -volumes: - solr-dovecot: - driver: local - -``` + solr-dovecot: + driver: local + ``` 2. `etc/dovecot/conf.d/10-plugin.conf`: -``` -mail_plugins = $mail_plugins fts fts_solr -plugin { - fts = solr - fts_autoindex = yes - fts_solr = url=http://solr:8983/solr/dovecot/ -} -``` + ```conf + mail_plugins = $mail_plugins fts fts_solr + + plugin { + fts = solr + fts_autoindex = yes + fts_solr = url=http://solr:8983/solr/dovecot/ + } + ``` 3. Start the solr container: `docker-compose up -d --remove-orphans solr` 4. Restart the mailserver container: `docker-compose restart mailserver` -5. Flag all user mailbox FTS indexes as invalid, so they are rescanned on demand when they are next searched -``` -docker-compose exec mailserver doveadm fts rescan -A -``` +5. Flag all user mailbox FTS indexes as invalid, so they are rescanned on demand when they are next searched: `docker-compose exec mailserver doveadm fts rescan -A` -## Further discussion -See [issue #905](https://github.com/tomav/docker-mailserver/issues/905) \ No newline at end of file +## Further Discussion + +See [#905][github-issue-905] + +[github-issue-905]: https://github.com/docker-mailserver/docker-mailserver/issues/905 diff --git a/docs/content/advanced/ipv6.md b/docs/content/advanced/ipv6.md index 4a00485e..c5ec5fb1 100644 --- a/docs/content/advanced/ipv6.md +++ b/docs/content/advanced/ipv6.md @@ -1,6 +1,10 @@ +--- +title: 'IPv6' +--- + ## Background -If your container host supports IPv6, then `docker-mailserver` will automatically accept IPv6 connections by way of the docker host's IPv6. However, incoming mail will fail SPF checks because they will appear to come from the IPv4 gateway that docker is using to proxy the IPv6 connection (172.20.0.1 is the gateway). +If your container host supports IPv6, then `docker-mailserver` will automatically accept IPv6 connections by way of the docker host's IPv6. However, incoming mail will fail SPF checks because they will appear to come from the IPv4 gateway that docker is using to proxy the IPv6 connection (`172.20.0.1` is the gateway). This can be solved by supporting IPv6 connections all the way to the `docker-mailserver` container. @@ -11,9 +15,9 @@ This can be solved by supporting IPv6 connections all the way to the `docker-mai @@ -1,4 +1,4 @@ -version: '2' +version: '2.1' - + @@ -32,6 +32,16 @@ services: - + + ipv6nat: + image: robbertkl/ipv6nat + restart: always @@ -37,6 +41,8 @@ This can be solved by supporting IPv6 connections all the way to the `docker-mai + gateway: fd00:0123:4567::1 ``` -## Further discussion +## Further Discussion -See [issue #1438](https://github.com/tomav/docker-mailserver/issues/1438) \ No newline at end of file +See [#1438][github-issue-1438] + +[github-issue-1438]: https://github.com/docker-mailserver/docker-mailserver/issues/1438 diff --git a/docs/content/advanced/kubernetes.md b/docs/content/advanced/kubernetes.md index ecd24ef1..74a3bc31 100644 --- a/docs/content/advanced/kubernetes.md +++ b/docs/content/advanced/kubernetes.md @@ -1,13 +1,17 @@ -## Deployment example +--- +title: 'Kubernetes' +--- -There is nothing much in deploying mailserver to Kubernetes itself. The things are pretty same as in [`docker-compose.yml`][1], but with Kubernetes syntax. +## Deployment Example + +There is nothing much in deploying mailserver to Kubernetes itself. The things are pretty same as in [`docker-compose.yml`][github-file-compose], but with Kubernetes syntax. ```yaml apiVersion: v1 kind: Namespace metadata: name: mailserver ---- +--- kind: ConfigMap apiVersion: v1 metadata: @@ -62,7 +66,7 @@ data: TrustedHosts: | 127.0.0.1 localhost - + #user-patches.sh: | # #!/bin/bash @@ -224,27 +228,24 @@ spec: claimName: mail-storage - name: tmp-files emptyDir: {} - ``` -__Note:__ -Any sensitive data (keys, etc) should be deployed via [Secrets][50]. Other configuration just fits well into [ConfigMaps][51]. +!!! note + Any sensitive data (keys, etc) should be deployed via [Secrets][k8s-config-secret]. Other configuration just fits well into [ConfigMaps][k8s-config-pod]. -__Note:__ -Make sure that [Pod][52] is [assigned][59] to specific [Node][53] in case you're using volume for data directly with `hostPath`. Otherwise Pod can be rescheduled on a different Node and previous data won't be found. Except the case when you're using some shared filesystem on your Nodes. +!!! note + Make sure that [Pod][k8s-workload-pod] is [assigned][k8s-assign-pod-node] to specific [Node][k8s-nodes] in case you're using volume for data directly with `hostPath`. Otherwise Pod can be rescheduled on a different Node and previous data won't be found. Except the case when you're using some shared filesystem on your Nodes. - - - -## Exposing to outside world +## Exposing to the Outside World The hard part with Kubernetes is to expose deployed mailserver to outside world. Kubernetes provides multiple ways for doing that. Each has its downsides and complexity. -The major problem with exposing mailserver to outside world in Kubernetes is to [preserve real client IP][57]. Real client IP is required by mailserver for performing IP-based SPF checks and spam checks. +The major problem with exposing mailserver to outside world in Kubernetes is to [preserve real client IP][k8s-service-source-ip]. Real client IP is required by mailserver for performing IP-based SPF checks and spam checks. -Preserving real client IP is relatively [non-trivial in Kubernetes][57] and most exposing ways do not provide it. So, it's up to you to decide which exposing way suits better your needs in a price of complexity. +Preserving real client IP is relatively [non-trivial in Kubernetes][k8s-service-source-ip] and most exposing ways do not provide it. So, it's up to you to decide which exposing way suits better your needs in a price of complexity. + +If you do not require SPF checks for incoming mails you may disable them in [Postfix configuration][docs-postfix] by dropping following line (which removes `check_policy_service unix:private/policyd-spf` option): -If you do not require SPF checks for incoming mails you may disable them in [Postfix configuration][2] by dropping following line (which removes `check_policy_service unix:private/policyd-spf` option): ```yaml kind: ConfigMap apiVersion: v1 @@ -264,18 +265,16 @@ apiVersion: extensions/v1beta1 metadata: name: mailserver # ... - volumeMounts: - - name: config - subPath: postfix-main.cf - mountPath: /tmp/docker-mailserver/postfix-main.cf - readOnly: true -# ... + volumeMounts: + - name: config + subPath: postfix-main.cf + mountPath: /tmp/docker-mailserver/postfix-main.cf + readOnly: true ``` - ### External IPs Service -The simplest way is to expose mailserver as a [Service][55] with [external IPs][56]. +The simplest way is to expose mailserver as a [Service][k8s-network-service] with [external IPs][k8s-network-external-ip]. ```yaml kind: Service @@ -292,29 +291,27 @@ spec: port: 25 targetPort: smtp # ... - externalIPs: - - 80.11.12.10 + externalIPs: + - 80.11.12.10 ``` -##### Downsides +**Downsides** - __Real client IP is not preserved__, so SPF check of incoming mail will fail. - Requirement to specify exposed IPs explicitly. - ### Proxy port to Service -The [Proxy Pod][58] helps to avoid necessity of specifying external IPs explicitly. This comes in price of complexity: you must deploy Proxy Pod on each [Node][53] you want to expose mailserver on. +The [Proxy Pod][k8s-proxy-service] helps to avoid necessity of specifying external IPs explicitly. This comes in price of complexity: you must deploy Proxy Pod on each [Node][k8s-nodes] you want to expose mailserver on. -##### Downsides +**Downsides** - __Real client IP is not preserved__, so SPF check of incoming mail will fail. - ### Bind to concrete Node and use host network -The simplest way to preserve real client IP is to use `hostPort` and `hostNetwork: true` in the mailserver [Pod][52]. This comes in price of availability: you can talk to mailserver from outside world only via IPs of [Node][53] where mailserver is deployed. +The simplest way to preserve real client IP is to use `hostPort` and `hostNetwork: true` in the mailserver [Pod][k8s-workload-pod]. This comes in price of availability: you can talk to mailserver from outside world only via IPs of [Node][k8s-nodes] where mailserver is deployed. ```yaml kind: Deployment @@ -326,7 +323,7 @@ metadata: hostNetwork: true # ... containers: -# ... +# ... ports: - name: smtp containerPort: 25 @@ -340,31 +337,32 @@ metadata: # ... ``` -##### Downsides +**Downsides** - Not possible to access mailserver via other cluster Nodes, only via the one mailserver deployed at. - Every Port within the Container is exposed on the Host side, regardless of what the `ports` section in the Configuration defines. - -### Proxy port to Service via PROXY protocol +### Proxy Port to Service via PROXY Protocol This way is ideologically the same as [using Proxy Pod](#proxy-port-to-service), but instead of a separate proxy pod, you configure your ingress to proxy TCP traffic to the mailserver pod using the PROXY protocol, which preserves the real client IP. -#### Configure your ingress -With an [NGINX ingress controller][12], set `externalTrafficPolicy: Local` for its service, and add the following to the TCP services config map (as described [here][13]): +#### Configure your Ingress + +With an [NGINX ingress controller][k8s-nginx], set `externalTrafficPolicy: Local` for its service, and add the following to the TCP services config map (as described [here][k8s-nginx-expose]): + ```yaml -# ... - 25: "mailserver/mailserver:25::PROXY" - 465: "mailserver/mailserver:465::PROXY" - 587: "mailserver/mailserver:587::PROXY" - 993: "mailserver/mailserver:993::PROXY" -# ... +25: "mailserver/mailserver:25::PROXY" +465: "mailserver/mailserver:465::PROXY" +587: "mailserver/mailserver:587::PROXY" +993: "mailserver/mailserver:993::PROXY" ``` -With [HAProxy][11], the configuration should look similar to the above. If you know what it actually looks like, add an example here. :) +With [HAProxy][dockerhub-haproxy], the configuration should look similar to the above. If you know what it actually looks like, add an example here. :smiley: + +#### Configure the Mailserver + +Then, configure both [Postfix][docs-postfix] and [Dovecot][docs-dovecot] to expect the PROXY protocol: -#### Configure the mailserver -Then, configure both [Postfix][2] and [Dovecot][3] to expect the PROXY protocol: ```yaml kind: ConfigMap apiVersion: v1 @@ -379,7 +377,8 @@ data: submission/inet/smtpd_upstream_proxy_protocol=haproxy smtps/inet/smtpd_upstream_proxy_protocol=haproxy dovecot.cf: | - haproxy_trusted_networks = 10.0.0.0/8, 127.0.0.0/8 # Assuming your ingress controller is bound to 10.0.0.0/8 + # Assuming your ingress controller is bound to 10.0.0.0/8 + haproxy_trusted_networks = 10.0.0.0/8, 127.0.0.0/8 service imap-login { inet_listener imaps { haproxy = yes @@ -410,18 +409,15 @@ spec: subPath: dovecot.cf mountPath: /tmp/docker-mailserver/dovecot.cf readOnly: true -# ... ``` -##### Downsides +**Downsides** - Not possible to access mailserver via inner cluster Kubernetes DNS, as PROXY protocol is required for incoming connections. +## Let's Encrypt Certificates - -## Let's Encrypt certificates - -[Kube-Lego][10] may be used for a role of Let's Encrypt client. It works with Kubernetes [Ingress Resources][54] and automatically issues/manages certificates/keys for exposed services via Ingresses. +[Kube-Lego][kube-lego] may be used for a role of Let's Encrypt client. It works with Kubernetes [Ingress Resources][k8s-network-ingress] and automatically issues/manages certificates/keys for exposed services via Ingresses. ```yaml kind: Ingress @@ -447,49 +443,43 @@ spec: - example.com ``` -Now, you can use Let's Encrypt cert and key from `mailserver.tls` [Secret][50] -in your [Pod][52] spec. +Now, you can use Let's Encrypt cert and key from `mailserver.tls` [Secret][k8s-config-secret] in your [Pod][k8s-workload-pod] spec: ```yaml # ... - env: - - name: SSL_TYPE - value: 'manual' - - name: SSL_CERT_PATH - value: '/etc/ssl/mailserver/tls.crt' - - name: SSL_KEY_PATH - value: '/etc/ssl/mailserver/tls.key' +env: + - name: SSL_TYPE + value: 'manual' + - name: SSL_CERT_PATH + value: '/etc/ssl/mailserver/tls.crt' + - name: SSL_KEY_PATH + value: '/etc/ssl/mailserver/tls.key' # ... - volumeMounts: - - name: tls - mountPath: /etc/ssl/mailserver - readOnly: true -# ... - volumes: - - name: tls - secret: - secretName: mailserver.tls +volumeMounts: + - name: tls + mountPath: /etc/ssl/mailserver + readOnly: true # ... +volumes: + - name: tls + secret: + secretName: mailserver.tls ``` - - - - -[1]: https://github.com/tomav/docker-mailserver/blob/master/docker-compose.yml.dist -[2]: https://github.com/tomav/docker-mailserver/wiki/Overwrite-Default-Postfix-Configuration -[3]: https://github.com/tomav/docker-mailserver/wiki/Override-Default-Dovecot-Configuration -[10]: https://github.com/jetstack/kube-lego -[11]: https://hub.docker.com/_/haproxy -[12]: https://kubernetes.github.io/ingress-nginx/ -[13]: https://kubernetes.github.io/ingress-nginx/user-guide/exposing-tcp-udp-services/ -[50]: https://kubernetes.io/docs/concepts/configuration/secret -[51]: https://kubernetes.io/docs/tasks/configure-pod-container/configmap -[52]: https://kubernetes.io/docs/concepts/workloads/pods/pod -[53]: https://kubernetes.io/docs/concepts/architecture/nodes -[54]: https://kubernetes.io/docs/concepts/services-networking/ingress -[55]: https://kubernetes.io/docs/concepts/services-networking/service -[56]: https://kubernetes.io/docs/concepts/services-networking/service/#external-ips -[57]: https://kubernetes.io/docs/tutorials/services/source-ip -[58]: https://github.com/kubernetes/contrib/tree/master/for-demos/proxy-to-service -[59]: https://kubernetes.io/docs/concepts/configuration/assign-pod-node +[docs-dovecot]: ./override-defaults/dovecot.md +[docs-postfix]: ./override-defaults/postfix.md +[github-file-compose]: https://github.com/docker-mailserver/docker-mailserver/blob/master/docker-compose.yml +[dockerhub-haproxy]: https://hub.docker.com/_/haproxy +[kube-lego]: https://github.com/jetstack/kube-lego +[k8s-assign-pod-node]: https://kubernetes.io/docs/concepts/configuration/assign-pod-node +[k8s-config-pod]: https://kubernetes.io/docs/tasks/configure-pod-container/configmap +[k8s-config-secret]: https://kubernetes.io/docs/concepts/configuration/secret +[k8s-nginx]: https://kubernetes.github.io/ingress-nginx +[k8s-nginx-expose]: https://kubernetes.github.io/ingress-nginx/user-guide/exposing-tcp-udp-services +[k8s-network-ingress]: https://kubernetes.io/docs/concepts/services-networking/ingress +[k8s-network-service]: https://kubernetes.io/docs/concepts/services-networking/service +[k8s-network-external-ip]: https://kubernetes.io/docs/concepts/services-networking/service/#external-ips +[k8s-nodes]: https://kubernetes.io/docs/concepts/architecture/nodes +[k8s-proxy-service]: https://github.com/kubernetes/contrib/tree/master/for-demos/proxy-to-service +[k8s-service-source-ip]: https://kubernetes.io/docs/tutorials/services/source-ip +[k8s-workload-pod]: https://kubernetes.io/docs/concepts/workloads/pods/pod diff --git a/docs/content/advanced/mail-fetchmail.md b/docs/content/advanced/mail-fetchmail.md index 596056db..30aa6208 100644 --- a/docs/content/advanced/mail-fetchmail.md +++ b/docs/content/advanced/mail-fetchmail.md @@ -1,16 +1,18 @@ -To enable the [fetchmail](http://www.fetchmail.info) service to retrieve e-mails set the environment variable `ENABLE_FETCHMAIL` to `1`. Your `docker-compose.yml` file should look like following snippet: +--- +title: 'Email Gathering with Fetchmail' +--- + +To enable the [fetchmail][fetchmail-website] service to retrieve e-mails set the environment variable `ENABLE_FETCHMAIL` to `1`. Your `docker-compose.yml` file should look like following snippet: ```yaml -... environment: - ENABLE_FETCHMAIL=1 - FETCHMAIL_POLL=300 -... ``` Generate a file called `fetchmail.cf` and place it in the `config` folder. Your `docker-mailserver` folder should look like this example: -``` +```txt ├── config │   ├── dovecot.cf │   ├── fetchmail.cf @@ -20,56 +22,59 @@ Generate a file called `fetchmail.cf` and place it in the `config` folder. Your └── README.md ``` -# Configuration +## Configuration -A detailed description of the configuration options can be found in the [online version of the manual page](http://www.fetchmail.info/fetchmail-man.html). +A detailed description of the configuration options can be found in the [online version of the manual page][fetchmail-docs]. -## Example IMAP configuration +### Example IMAP Configuration -``` +```fetchmailrc poll 'imap.example.com' proto imap - user 'username' - pass 'secret' - is 'user1@domain.tld' - ssl + user 'username' + pass 'secret' + is 'user1@domain.tld' + ssl ``` -## Example POP3 configuration +### Example POP3 Configuration -``` +```fetchmailrc poll 'pop3.example.com' proto pop3 - user 'username' - pass 'secret' - is 'user2@domain.tld' - ssl + user 'username' + pass 'secret' + is 'user2@domain.tld' + ssl ``` -__IMPORTANT__: Don’t forget the last line: e. g. `is 'user1@domain.tld'`. After `is` you have to specify one email address from the configuration file `config/postfix-accounts.cf`. +!!! caution + Don’t forget the last line: eg: `is 'user1@domain.tld'`. After `is` you have to specify one email address from the configuration file `config/postfix-accounts.cf`. -More details how to configure fetchmail can be found in the [fetchmail man page in the chapter “The run control file”](http://www.fetchmail.info/fetchmail-man.html#31). +More details how to configure fetchmail can be found in the [fetchmail man page in the chapter “The run control file”][fetchmail-docs-run]. -## Polling interval +### Polling Interval -By default the fetchmail service searches every 5 minutes for new mails on your external mail accounts. You can override this default value by changing the ENV variable `FETCHMAIL_POLL`. +By default the fetchmail service searches every 5 minutes for new mails on your external mail accounts. You can override this default value by changing the ENV variable `FETCHMAIL_POLL`: ```yaml +environment: - FETCHMAIL_POLL=60 ``` + You must specify a numeric argument which is a polling interval in seconds. The example above polls every minute for new mails. -# Debugging +## Debugging To debug your `fetchmail.cf` configuration run this command: -``` +```sh ./setup.sh debug fetchmail ``` -For more informations about the configuration script `setup.sh` [[read the corresponding wiki page|Setup-docker-mailserver-using-the-script-setup.sh]]. +For more informations about the configuration script `setup.sh` [read the corresponding docs][docs-setup]. Here a sample output of `./setup.sh debug fetchmail`: -``` +```log fetchmail: 6.3.26 querying outlook.office365.com (protocol POP3) at Mon Aug 29 22:11:09 2016: poll started Trying to connect to 132.245.48.18/995...connected. fetchmail: Server certificate: @@ -107,4 +112,9 @@ fetchmail: POP3> QUIT fetchmail: POP3< +OK Microsoft Exchange Server 2016 POP3 server signing off. fetchmail: 6.3.26 querying outlook.office365.com (protocol POP3) at Mon Aug 29 22:11:11 2016: poll completed fetchmail: normal termination, status 1 -``` \ No newline at end of file +``` + +[docs-setup]: ../config/setup.sh.md +[fetchmail-website]: https://www.fetchmail.info +[fetchmail-docs]: https://www.fetchmail.info/fetchmail-man.html +[fetchmail-docs-run]: https://www.fetchmail.info/fetchmail-man.html#31 diff --git a/docs/content/advanced/mail-forwarding/aws-ses.md b/docs/content/advanced/mail-forwarding/aws-ses.md index efd371c6..6e27588a 100644 --- a/docs/content/advanced/mail-forwarding/aws-ses.md +++ b/docs/content/advanced/mail-forwarding/aws-ses.md @@ -1,26 +1,35 @@ -Note: new configuration, see [Configure Relay Hosts](https://github.com/tomav/docker-mailserver/wiki/Configure-Relay-Hosts) +--- +title: 'Mail Forwarding | AWS SES' +--- -Instead of letting postfix deliver mail directly it is possible to configure it to deliver outgoing email via Amazon SES (Simple Email Service). (Receiving inbound email via SES is not implemented.) The configuration follows the guidelines provided by AWS in http://docs.aws.amazon.com/ses/latest/DeveloperGuide/postfix.html, specifically, the STARTTLS method. +!!! note + New configuration, see [Configure Relay Hosts][docs-relay] -As described in the AWS Developer Guide you will have to generate SMTP credentials and define the following two environment variables in the docker-compose.yml with the appropriate values for your AWS SES subscription (the values for AWS_SES_USERPASS are the "SMTP username" and "SMTP password" provided when you create SMTP credentials for SES): +Instead of letting postfix deliver mail directly it is possible to configure it to deliver outgoing email via Amazon SES (Simple Email Service). (Receiving inbound email via SES is not implemented.) The configuration follows the guidelines provided by AWS in https://docs.aws.amazon.com/ses/latest/DeveloperGuide/postfix.html, specifically, the `STARTTLS` method. -``` - environment: - - AWS_SES_HOST=email-smtp.us-east-1.amazonaws.com - - AWS_SES_USERPASS=AKIAXXXXXXXXXXXXXXXX:kqXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +As described in the AWS Developer Guide you will have to generate SMTP credentials and define the following two environment variables in the docker-compose.yml with the appropriate values for your AWS SES subscription (the values for `AWS_SES_USERPASS` are the "SMTP username" and "SMTP password" provided when you create SMTP credentials for SES): + +```yaml +environment: + - AWS_SES_HOST=email-smtp.us-east-1.amazonaws.com + - AWS_SES_USERPASS=AKIAXXXXXXXXXXXXXXXX:kqXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ``` -If necessary, you can also provide AWS_SES_PORT. If not provided, it defaults to 25. +If necessary, you can also provide `AWS_SES_PORT`. If not provided, it defaults to 25. When you start the container you will see a log line as follows confirming the configuration: -``` + +```log Setting up outgoing email via AWS SES host email-smtp.us-east-1.amazonaws.com ``` + To verify proper operation, send an email to some external account of yours and inspect the mail headers. You will also see the connection to SES in the mail logs. For example: -``` + +```log May 23 07:09:36 mail postfix/smtp[692]: Trusted TLS connection established to email-smtp.us-east-1.amazonaws.com[107.20.142.169]:25: TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits) May 23 07:09:36 mail postfix/smtp[692]: 8C82A7E7: to=, relay=email-smtp.us-east-1.amazonaws.com[107.20.142.169]:25, delay=0.35, delays=0/0.02/0.13/0.2, dsn=2.0.0, status=sent (250 Ok 01000154dc729264-93fdd7ea-f039-43d6-91ed-653e8547867c-000000) - ``` + +[docs-relay]: ./relay-hosts.md diff --git a/docs/content/advanced/mail-forwarding/relay-hosts.md b/docs/content/advanced/mail-forwarding/relay-hosts.md index 70003536..ac11ffcd 100644 --- a/docs/content/advanced/mail-forwarding/relay-hosts.md +++ b/docs/content/advanced/mail-forwarding/relay-hosts.md @@ -1,10 +1,14 @@ -# Introduction +--- +title: 'Mail Forwarding | Relay Hosts' +--- + +## Introduction Rather than having Postfix deliver mail directly, you can configure Postfix to send mail via another mail relay (smarthost). Examples include [Mailgun](https://www.mailgun.com/), [Sendgrid](https://sendgrid.com/) and [AWS SES](https://aws.amazon.com/ses/). Depending on the domain of the sender, you may want to send via a different relay, or authenticate in a different way. -# Basic Configuration +## Basic Configuration Basic configuration is done via environment variables: @@ -15,57 +19,62 @@ Basic configuration is done via environment variables: Setting these environment variables will cause mail for all sender domains to be routed via the specified host, authenticating with the user/password combination. -Note for users of the previous AWS_SES_* variables: please update your configuration to use these new variables, no other configuration is required. +!!! note + For users of the previous `AWS_SES_*` variables: please update your configuration to use these new variables, no other configuration is required. -# Advanced Configuration -## Sender-dependent Authentication +## Advanced Configuration -Sender dependent authentication is done in `config/postfix-sasl-password.cf`. You can create this file manually, or use +### Sender-dependent Authentication -```bash +Sender dependent authentication is done in `config/postfix-sasl-password.cf`. You can create this file manually, or use: + +```sh setup.sh relay add-auth [] ``` An example configuration file looks like this: -``` +```txt @domain1.com relay_user_1:password_1 @domain2.com relay_user_2:password_2 ``` -If there is no other configuration, this will cause Postfix to deliver email throught the relay specified in `RELAY_HOST` env variable, authenticating as `relay_user_1` when sent from domain1.com and authenticating as `relay_user_2` when sending from domain2.com. +If there is no other configuration, this will cause Postfix to deliver email throught the relay specified in `RELAY_HOST` env variable, authenticating as `relay_user_1` when sent from `domain1.com` and authenticating as `relay_user_2` when sending from domain2.com. -**NOTE** to activate the configuration you must either restart the container, or you can also trigger an update by modifying a mail account. +!!! note + To activate the configuration you must either restart the container, or you can also trigger an update by modifying a mail account. -## Sender-dependent Relay Host +### Sender-dependent Relay Host -Sender dependent relay hosts are configured in `config/postfix-relaymap.cf`. You can create this file manually, or use +Sender dependent relay hosts are configured in `config/postfix-relaymap.cf`. You can create this file manually, or use: -```bash +```sh setup.sh relay add-domain [] ``` An example configuration file looks like this: -``` +```txt @domain1.com [relay1.org]:587 @domain2.com [relay2.org]:2525 ``` -Combined with the previous configuration in `config/postfix-sasl-password.cf`, this will cause Postfix to deliver mail sent from domain1.com via `relay1.org:587`, authenticating as `relay_user_1`, and mail sent from domain2.com via `relay2.org:2525` authenticating as `relay_user_2`. -**NOTE** You still have to define RELAY_HOST to activate the feature +Combined with the previous configuration in `config/postfix-sasl-password.cf`, this will cause Postfix to deliver mail sent from domain1.com via `relay1.org:587`, authenticating as `relay_user_1`, and mail sent from domain2.com via `relay2.org:2525` authenticating as `relay_user_2`. -## Excluding Sender Domains +!!! note + You still have to define `RELAY_HOST` to activate the feature -If you want mail sent from some domains to be delivered directly, you can exclude them from being delivered via the default relay by adding them to `config/postfix-relaymap.cf` with no destination. You can also do this via +### Excluding Sender Domains -```bash +If you want mail sent from some domains to be delivered directly, you can exclude them from being delivered via the default relay by adding them to `config/postfix-relaymap.cf` with no destination. You can also do this via: + +```sh setup.sh relay exclude-domain ``` Extending the configuration file from above: -``` +```txt @domain1.com [relay1.org]:587 @domain2.com [relay2.org]:2525 @domain3.com @@ -73,7 +82,7 @@ Extending the configuration file from above: This will cause email sent from domain3.com to be delivered directly. -### References +#### References Thanks to the author of [this article][1] for the inspiration. This is also worth reading to understand a bit more about how to set up Mailgun to work with this. diff --git a/docs/content/advanced/mail-sieve.md b/docs/content/advanced/mail-sieve.md index 83654dba..001648f0 100644 --- a/docs/content/advanced/mail-sieve.md +++ b/docs/content/advanced/mail-sieve.md @@ -1,4 +1,8 @@ -### User-defined sieve filters +--- +title: 'Email Filtering with Sieve' +--- + +## User-Defined Sieve Filters [Sieve](http://sieve.info/) allows to specify filtering rules for incoming emails that allow for example sorting mails into different folders depending on the title of an email. There are global and user specific filters which are filtering the incoming emails in the following order: @@ -11,62 +15,62 @@ If any filter in this filtering chain discards an incoming mail, the delivery pr To specify a user-defined Sieve filter place a `.dovecot.sieve` file into a virtual user's mail folder e.g. `/var/mail/domain.com/user1/.dovecot.sieve`. If this file exists dovecot will apply the filtering rules. -It's even possible to install a user provided Sieve filter at startup during users setup: simply include a Sieve file in the `config `path for each user login that need a filter. The file name provided should be in the form **\.dovecot.sieve**, so for example for `user1@domain.tld` you should provide a Sieve file named `config/user1@domain.tld.dovecot.sieve`. +It's even possible to install a user provided Sieve filter at startup during users setup: simply include a Sieve file in the `config` path for each user login that need a filter. The file name provided should be in the form `.dovecot.sieve`, so for example for `user1@domain.tld` you should provide a Sieve file named `config/user1@domain.tld.dovecot.sieve`. An example of a sieve filter that moves mails to a folder `INBOX/spam` depending on the sender address: -``` +```sieve require ["fileinto", "reject"]; if address :contains ["From"] "spam@spam.com" { - fileinto "INBOX.spam"; + fileinto "INBOX.spam"; } else { - keep; + keep; } ``` -***Note:*** that folders have to exist beforehand if sieve should move them. - +!!! note + That folders have to exist beforehand if sieve should move them. Another example of a sieve filter that forward mails to a different address: -``` +```sieve require ["copy"]; redirect :copy "user2@otherdomain.tld"; ``` Just forward all incoming emails and do not save them locally: -``` + +```sieve redirect "user2@otherdomain.tld"; ``` You can also use external programs to filter or pipe (process) messages by adding executable scripts in `config/sieve-pipe` or `config/sieve-filter`. This can be used in lieu of a local alias file, for instance to forward an email to a webservice. These programs can then be referenced by filename, by all users. Note that the process running the scripts run as a privileged user. For further information see [Dovecot's wiki](https://wiki.dovecot.org/Pigeonhole/Sieve/Plugins/Pipe). -``` + +```sieve require ["vnd.dovecot.pipe"]; pipe "external-program"; ``` - For more examples or a detailed description of the Sieve language have a look at [the official site](http://sieve.info/examplescripts). Other resources are available on the internet where you can find several [examples](https://support.tigertech.net/sieve#sieve-example-rules-jmp). -### Manage Sieve +## Manage Sieve The [Manage Sieve](https://doc.dovecot.org/admin_manual/pigeonhole_managesieve_server/) extension allows users to modify their Sieve script by themselves. The authentication mechanisms are the same as for the main dovecot service. ManageSieve runs on port `4190` and needs to be enabled using the `ENABLE_MANAGESIEVE=1` environment variable. -``` -(docker-compose.yml) +```yaml +# docker-compose.yml ports: - - ... - - "4190:4190" + - "4190:4190" environment: - - ... - - ENABLE_MANAGESIEVE=1 + - ENABLE_MANAGESIEVE=1 ``` All user defined sieve scripts that are managed by ManageSieve are stored in the user's home folder in `/var/mail/domain.com/user1/sieve`. Just one sieve script might be active for a user and is sym-linked to `/var/mail/domain.com/user1/.dovecot.sieve` automatically. -***Note:*** ManageSieve makes sure to not overwrite an existing `.dovecot.sieve` file. If a user activates a new sieve script the old one is backuped and moved to the `sieve` folder. +!!! note + ManageSieve makes sure to not overwrite an existing `.dovecot.sieve` file. If a user activates a new sieve script the old one is backuped and moved to the `sieve` folder. The extension is known to work with the following ManageSieve clients: * **Sieve Editor** a portable standalone application based on the former Thunderbird plugin (https://github.com/thsmi/sieve). diff --git a/docs/content/advanced/maintenance/update-and-cleanup.md b/docs/content/advanced/maintenance/update-and-cleanup.md index c6f07ce2..4b4a885a 100644 --- a/docs/content/advanced/maintenance/update-and-cleanup.md +++ b/docs/content/advanced/maintenance/update-and-cleanup.md @@ -1,10 +1,15 @@ -## Automatic update +--- +title: 'Maintenance | Update and Cleanup' +--- + +## Automatic Update Docker images are handy but it can get a a hassle to keep them updated. Also when a repository is automated you want to get these images when they get out. -One could setup a complex action/hook-based workflow using probes, but there is a nice, easy to use docker image that solves this issue and could prove useful: [watchtower](https://hub.docker.com/r/containrrr/watchtower). +One could setup a complex action/hook-based workflow using probes, but there is a nice, easy to use docker image that solves this issue and could prove useful: [`watchtower`](https://hub.docker.com/r/containrrr/watchtower). A docker-compose example: + ```yaml services: watchtower: @@ -16,11 +21,12 @@ services: For more details, see the [manual](https://containrrr.github.io/watchtower/) -## Automatic cleanup +## Automatic Cleanup -When you are pulling new images in automatically, it would be nice to have them cleaned up as well. There is also a docker image for this: [spotify/docker-gc](https://hub.docker.com/r/spotify/docker-gc/). +When you are pulling new images in automatically, it would be nice to have them cleaned up as well. There is also a docker image for this: [`spotify/docker-gc`](https://hub.docker.com/r/spotify/docker-gc/). A docker-compose example: + ```yaml services: docker-gc: @@ -32,4 +38,4 @@ services: For more details, see the [manual](https://github.com/spotify/docker-gc/blob/master/README.md) -Or you can just use the [`--cleanup`](https://containrrr.github.io/watchtower/arguments/#cleanup) option provided by containrrr/watchtower. \ No newline at end of file +Or you can just use the [`--cleanup`](https://containrrr.github.io/watchtower/arguments/#cleanup) option provided by `containrrr/watchtower`. diff --git a/docs/content/advanced/optional-config.md b/docs/content/advanced/optional-config.md index a8bbe564..97702c0b 100644 --- a/docs/content/advanced/optional-config.md +++ b/docs/content/advanced/optional-config.md @@ -1,32 +1,54 @@ +--- +title: 'Optional Configuration' +hide: + - toc # Hide Table of Contents for this page +--- + This is a list of all configuration files and directories which are optional or automatically generated in your `config` directory. -## Directories: -- **sieve-filter:** directory for sieve filter scripts. See [wiki](https://github.com/tomav/docker-mailserver/wiki/Configure-Sieve-filters) -- **sieve-pipe:** directory for sieve pipe scripts. See [wiki](https://github.com/tomav/docker-mailserver/wiki/Configure-Sieve-filters) -- **opendkim:** DKIM directory. Autoconfigurable via [setup.sh config dkim](https://github.com/tomav/docker-mailserver/wiki/Setup-docker-mailserver-using-the-script-setup.sh#config). See [wiki](https://github.com/tomav/docker-mailserver/wiki/Configure-DKIM) for further info -- **ssl:** SSL Certificate directory. Autoconfigurable via [setup.sh config ssl](https://github.com/tomav/docker-mailserver/wiki/Setup-docker-mailserver-using-the-script-setup.sh#config). Make sure to read the [wiki](https://github.com/tomav/docker-mailserver/wiki/Configure-SSL) as well to get a working mail server. +## Directories -## Files: -- **{user_email_address}.dovecot.sieve:** User specific Sieve filter file. See [wiki](https://github.com/tomav/docker-mailserver/wiki/Configure-Sieve-filters) -- **before.dovecot.sieve:** Global Sieve filter file, applied prior to the ${login}.dovecot.sieve filter. See [wiki](https://github.com/tomav/docker-mailserver/wiki/Configure-Sieve-filters) -- **after.dovecot.sieve**: Global Sieve filter file, applied after the ${login}.dovecot.sieve filter. See [wiki](https://github.com/tomav/docker-mailserver/wiki/Configure-Sieve-filters) -- **postfix-main.cf:** Every line will be added to the postfix main configuration. See [wiki](https://github.com/tomav/docker-mailserver/wiki/Override-Default-Postfix-Configuration) -- **postfix-master.cf:** Every line will be added to the postfix master configuration. See [wiki](https://github.com/tomav/docker-mailserver/wiki/Override-Default-Postfix-Configuration) -- **postfix-accounts.cf:** User accounts file. Modify via the [setup.sh email](https://github.com/tomav/docker-mailserver/wiki/Setup-docker-mailserver-using-the-script-setup.sh#email) script. -- **postfix-send-access.cf:** List of users denied sending. Modify via [setup.sh email restrict](https://github.com/tomav/docker-mailserver/wiki/Setup-docker-mailserver-using-the-script-setup.sh#email) -- **postfix-receive-access.cf:** List of users denied receiving. Modify via [setup.sh email restrict](https://github.com/tomav/docker-mailserver/wiki/Setup-docker-mailserver-using-the-script-setup.sh#email) -- **postfix-virtual.cf:** Alias configuration file. Modify via [setup.sh alias](https://github.com/tomav/docker-mailserver/wiki/Setup-docker-mailserver-using-the-script-setup.sh#alias) -- **postfix-sasl-password.cf:** listing of relayed domains with their respective username:password. Modify via `setup.sh relay add-auth []`. See [wiki](https://github.com/tomav/docker-mailserver/wiki/Configure-Relay-Hosts#sender-dependent-authentication) -- **postfix-relaymap.cf:** domain-specific relays and exclusions Modify via `setup.sh relay add-domain` and `setup.sh relay exclude-domain`. See [wiki](https://github.com/tomav/docker-mailserver/wiki/Configure-Relay-Hosts#sender-dependent-relay-host) -- **postfix-regexp.cf:** Regular expression alias file. See [wiki](https://github.com/tomav/docker-mailserver/wiki/Configure-Aliases#configuring-regexp-aliases) -- **ldap-users.cf:** Configuration for the virtual user mapping (virtual_mailbox_maps). See the [start-mailserver.sh](https://github.com/tomav/docker-mailserver/blob/a564cca0e55feba40e273a5419d4c9a864460bf6/target/start-mailserver.sh#L583) script -- **ldap-groups.cf:** Configuration for the virtual alias mapping (virtual_alias_maps). See the [start-mailserver.sh](https://github.com/tomav/docker-mailserver/blob/a564cca0e55feba40e273a5419d4c9a864460bf6/target/start-mailserver.sh#L583) script -- **ldap-aliases.cf:** Configuration for the virtual alias mapping (virtual_alias_maps). See the [start-mailserver.sh](https://github.com/tomav/docker-mailserver/blob/a564cca0e55feba40e273a5419d4c9a864460bf6/target/start-mailserver.sh#L583) script -- **ldap-domains.cf:** Configuration for the virtual domain mapping (virtual_mailbox_domains). See the [start-mailserver.sh](https://github.com/tomav/docker-mailserver/blob/a564cca0e55feba40e273a5419d4c9a864460bf6/target/start-mailserver.sh#L583) script +- **sieve-filter:** directory for sieve filter scripts. (Docs: [Sieve][docs-sieve]) +- **sieve-pipe:** directory for sieve pipe scripts. (Docs: [Sieve][docs-sieve]) +- **opendkim:** DKIM directory. Auto-configurable via [`setup.sh config dkim`][docs-setupsh]. (Docs: [DKIM][docs-dkim]) +- **ssl:** SSL Certificate directory. Auto-configurable via [`setup.sh config ssl`][docs-setupsh]. (Docs: [SSL][docs-ssl]) + +## Files + +- **{user_email_address}.dovecot.sieve:** User specific Sieve filter file. (Docs: [Sieve][docs-sieve]) +- **before.dovecot.sieve:** Global Sieve filter file, applied prior to the `${login}.dovecot.sieve` filter. (Docs: [Sieve][docs-sieve]) +- **after.dovecot.sieve**: Global Sieve filter file, applied after the `${login}.dovecot.sieve` filter. (Docs: [Sieve][docs-sieve]) +- **postfix-main.cf:** Every line will be added to the postfix main configuration. (Docs: [Override Postfix Defaults][docs-override-postfix]) +- **postfix-master.cf:** Every line will be added to the postfix master configuration. (Docs: [Override Postfix Defaults][docs-override-postfix]) +- **postfix-accounts.cf:** User accounts file. Modify via the [`setup.sh email`][docs-setupsh] script. +- **postfix-send-access.cf:** List of users denied sending. Modify via [`setup.sh email restrict`][docs-setupsh]. +- **postfix-receive-access.cf:** List of users denied receiving. Modify via [`setup.sh email restrict`][docs-setupsh]. +- **postfix-virtual.cf:** Alias configuration file. Modify via [`setup.sh alias`][docs-setupsh]. +- **postfix-sasl-password.cf:** listing of relayed domains with their respective `:`. Modify via `setup.sh relay add-auth []`. (Docs: [Relay-Hosts Auth][docs-relayhosts-senderauth]) +- **postfix-relaymap.cf:** domain-specific relays and exclusions. Modify via `setup.sh relay add-domain` and `setup.sh relay exclude-domain`. (Docs: [Relay-Hosts Senders][docs-relayhosts-senderhost]) +- **postfix-regexp.cf:** Regular expression alias file. (Docs: [Aliases][docs-aliases-regex]) +- **ldap-users.cf:** Configuration for the virtual user mapping `virtual_mailbox_maps`. See the [`setup-stack.sh`][github-commit-setup-stack.sh-L411] script. +- **ldap-groups.cf:** Configuration for the virtual alias mapping `virtual_alias_maps`. See the [`setup-stack.sh`][github-commit-setup-stack.sh-L411] script. +- **ldap-aliases.cf:** Configuration for the virtual alias mapping `virtual_alias_maps`. See the [`setup-stack.sh`][github-commit-setup-stack.sh-L411] script. +- **ldap-domains.cf:** Configuration for the virtual domain mapping `virtual_mailbox_domains`. See the [`setup-stack.sh`][github-commit-setup-stack.sh-L411] script. - **whitelist_clients.local:** Whitelisted domains, not considered by postgrey. Enter one host or domain per line. -- **spamassassin-rules.cf:** Antispam rules for Spamassassin. See [wiki](https://github.com/tomav/docker-mailserver/wiki/FAQ-and-Tips#how-can-i-manage-my-custom-spamassassin-rules) -- **fail2ban-fail2ban.cf:** Additional config options for fail2ban.cf. See [wiki](https://github.com/tomav/docker-mailserver/wiki/Configure-Fail2ban) -- **fail2ban-jail.cf:** Additional config options for fail2ban's jail behaviour. See [wiki](https://github.com/tomav/docker-mailserver/wiki/Configure-Fail2ban) -- **amavis.cf:** replaces the /etc/amavis/conf.d/50-user file -- **dovecot.cf:** replaces /etc/dovecot/local.conf. See [wiki](https://github.com/tomav/docker-mailserver/wiki/Override-Default-Dovecot-Configuration) -- **dovecot-quotas.cf:** list of custom quotas per mailbox. See [wiki](https://github.com/tomav/docker-mailserver/wiki/Configure-Accounts#mailbox-quota) +- **spamassassin-rules.cf:** Antispam rules for Spamassassin. (Docs: [FAQ - SpamAssassin Rules][docs-faq-spamrules]) +- **fail2ban-fail2ban.cf:** Additional config options for `fail2ban.cf`. (Docs: [Fail2Ban][docs-fail2ban]) +- **fail2ban-jail.cf:** Additional config options for fail2ban's jail behaviour. (Docs: [Fail2Ban][docs-fail2ban]) +- **amavis.cf:** replaces the `/etc/amavis/conf.d/50-user` file +- **dovecot.cf:** replaces `/etc/dovecot/local.conf`. (Docs: [Override Dovecot Defaults][docs-override-dovecot]) +- **dovecot-quotas.cf:** list of custom quotas per mailbox. (Docs: [Accounts][docs-accounts-quota]) + +[docs-accounts-quota]: ../config/user-management/accounts.md#notes +[docs-aliases-regex]: ../config/user-management/aliases.md#configuring-regexp-aliases +[docs-dkim]: ../config/best-practices/dkim.md +[docs-fail2ban]: ../config/security/fail2ban.md +[docs-faq-spamrules]: ../config/troubleshooting/faq.md#how-can-i-manage-my-custom-spamassassin-rules +[docs-override-postfix]: ./override-defaults/postfix.md +[docs-override-dovecot]: ./override-defaults/dovecot.md +[docs-relayhosts-senderauth]: ./mail-forwarding/relay-hosts.md#sender-dependent-authentication +[docs-relayhosts-senderhost]: ./mail-forwarding/relay-hosts.md#sender-dependent-relay-host +[docs-sieve]: ./mail-sieve.md +[docs-setupsh]: ../config/setup.sh.md +[docs-ssl]: ../config/security/ssl.md +[github-commit-setup-stack.sh-L411]: https://github.com/docker-mailserver/docker-mailserver/blob/941e7acdaebe271eaf3d296b36d4d81df4c54b90/target/scripts/startup/setup-stack.sh#L411 diff --git a/docs/content/advanced/override-defaults/dovecot.md b/docs/content/advanced/override-defaults/dovecot.md index 85c0d02b..e4ef7490 100644 --- a/docs/content/advanced/override-defaults/dovecot.md +++ b/docs/content/advanced/override-defaults/dovecot.md @@ -1,11 +1,15 @@ -# Add configuration +--- +title: 'Override the Default Configs | Dovecot' +--- + +## Add Configuration The Dovecot default configuration can easily be extended providing a `config/dovecot.cf` file. -[Dovecot documentation](http://wiki.dovecot.org/FrontPage) remains the best place to find configuration options. +[Dovecot documentation](https://wiki.dovecot.org) remains the best place to find configuration options. Your `docker-mailserver` folder should look like this example: -``` +```txt ├── config │ ├── dovecot.cf │ ├── postfix-accounts.cf @@ -16,47 +20,40 @@ Your `docker-mailserver` folder should look like this example: One common option to change is the maximum number of connections per user: -``` +```cf mail_max_userip_connections = 100 ``` -Another important option is the `default_process_limit` (defaults to `100`). If high-security mode is enabled you'll need to make sure this count is higher than the maximum number of users that can be logged in simultaneously. This limit is quickly reached if users connect to the mail server with multiple end devices. +Another important option is the `default_process_limit` (defaults to `100`). If high-security mode is enabled you'll need to make sure this count is higher than the maximum number of users that can be logged in simultaneously. -# Override configuration +This limit is quickly reached if users connect to the mail server with multiple end devices. -For major configuration changes it’s best to override the `dovecot` configuration files. For each configuration file you want to override, add a list entry under the `volumes:` key. +## Override Configuration + +For major configuration changes it’s best to override the dovecot configuration files. For each configuration file you want to override, add a list entry under the `volumes` key. ```yaml -version: '2' - services: mail: - ... volumes: - maildata:/var/mail - ... - ./config/dovecot/10-master.conf:/etc/dovecot/conf.d/10-master.conf - ``` -# Debugging +## Debugging -To debug your dovecot configuration you can use this command: +To debug your dovecot configuration you can use: + +- This command: `./setup.sh debug login doveconf | grep ` +- Or: `docker exec -it doveconf | grep ` + +!!! note + [`setup.sh`][github-file-setupsh] is included in the `docker-mailserver` repository. + +The `config/dovecot.cf` is copied internally to `/etc/dovecot/local.conf`. To check this file run: ```sh -./setup.sh debug login doveconf | grep +docker exec -it cat /etc/dovecot/local.conf ``` -[setup.sh](https://github.com/tomav/docker-mailserver/blob/master/setup.sh) is included in the `docker-mailserver` repository. - -or - -```sh -docker exec -ti doveconf | grep -``` - -The `config/dovecot.cf` is copied to `/etc/dovecot/local.conf`. To check this file run: - -```sh -docker exec -ti cat /etc/dovecot/local.conf -``` +[github-file-setupsh]: https://github.com/docker-mailserver/docker-mailserver/blob/master/setup.sh diff --git a/docs/content/advanced/override-defaults/postfix.md b/docs/content/advanced/override-defaults/postfix.md index d8ed58eb..e380b231 100644 --- a/docs/content/advanced/override-defaults/postfix.md +++ b/docs/content/advanced/override-defaults/postfix.md @@ -1,24 +1,32 @@ +--- +title: 'Override the Default Configs | Postfix' +--- + The Postfix default configuration can easily be extended by providing a `config/postfix-main.cf` in postfix format. This can also be used to add configuration that is not in our default configuration. For example, one common use of this file is for increasing the default maximum message size: -``` + +```cf # increase maximum message size - message_size_limit = 52428800 +message_size_limit = 52428800 ``` -That specific example is now supported and can be handled by setting POSTFIX_MESSAGE_SIZE_LIMIT. +That specific example is now supported and can be handled by setting `POSTFIX_MESSAGE_SIZE_LIMIT`. [Postfix documentation](http://www.postfix.org/documentation.html) remains the best place to find configuration options. Each line in the provided file will be loaded into postfix. -In the same way it is possible to add a custom `config/postfix-master.cf` file that will override the standard `master.cf`. Each line in the file will be passed to `postconf -P`. The expected format is service_name/type/parameter, for example: -``` +In the same way it is possible to add a custom `config/postfix-master.cf` file that will override the standard `master.cf`. Each line in the file will be passed to `postconf -P`. The expected format is `//`, for example: + +```cf submission/inet/smtpd_reject_unlisted_recipient=no ``` + Run `postconf -P` in the container without arguments to see the active master options. -Note! There should be no space between the parameter and the value. +!!! note + There should be no space between the parameter and the value. -Have a look at the code for more information. \ No newline at end of file +Have a look at the code for more information. diff --git a/docs/content/config/best-practices/autodiscover.md b/docs/content/config/best-practices/autodiscover.md index 7bd5b34e..fef6b64b 100644 --- a/docs/content/config/best-practices/autodiscover.md +++ b/docs/content/config/best-practices/autodiscover.md @@ -1,5 +1,11 @@ +--- +title: 'Best Practices | Auto-discovery' +hide: + - toc # Hide Table of Contents for this page +--- + Email auto-discovery means a client email is able to automagically find out about what ports and security options to use, based on the mail server URL. It can help simplify the tedious / confusing task of adding own's email account for non-tech savvy users. -Basically, email clients will search for auto-discoverable settings and prefill almost everything when a user enters its email address :heart: +Email clients will search for auto-discoverable settings and prefill almost everything when a user enters its email address :heart: -There exists [autodiscover-email-settings](https://hub.docker.com/r/monogramm/autodiscover-email-settings/) on hub.docker.com which provides IMAP/POP/SMTP/LDAP autodiscover capabilities on Microsoft Outlook/Apple Mail, autoconfig capabilities for Thunderbird or kmail and configuration profiles for iOS/Apple Mail. \ No newline at end of file +There exists [autodiscover-email-settings](https://hub.docker.com/r/monogramm/autodiscover-email-settings/) on which provides IMAP/POP/SMTP/LDAP autodiscover capabilities on Microsoft Outlook/Apple Mail, autoconfig capabilities for Thunderbird or kmail and configuration profiles for iOS/Apple Mail. diff --git a/docs/content/config/best-practices/dkim.md b/docs/content/config/best-practices/dkim.md index 253e2407..e012470d 100644 --- a/docs/content/config/best-practices/dkim.md +++ b/docs/content/config/best-practices/dkim.md @@ -1,35 +1,39 @@ +--- +title: 'Best Practices | DKIM' +--- + DKIM is a security measure targeting email spoofing. It is greatly recommended one activates it. See [the Wikipedia page](https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail) for more details on DKIM. -#### Enabling DKIM signature +## Enabling DKIM Signature To enable DKIM signature, **you must have created at least one email account**. Once its done, just run the following command to generate the signature: -``` BASH +```sh ./setup.sh config dkim ``` After generating DKIM keys, you should restart the mail server. DNS edits may take a few minutes to hours to propagate. The script assumes you're being in the directory where the `config/` directory is located. The default keysize when generating the signature is 4096 bits for now. If you need to change it (e.g. your DNS provider limits the size), then provide the size as the first parameter of the command: -``` BASH +```sh ./setup.sh config dkim ``` For LDAP systems that do not have any directly created user account you can run the following command (since `8.0.0`) to generate the signature by additionally providing the desired domain name (if you have multiple domains use the command multiple times or provide a comma-separated list of domains): -``` BASH +```sh ./setup.sh config dkim [,] ``` Now the keys are generated, you can configure your DNS server with DKIM signature, simply by adding a TXT record. If you have direct access to your DNS zone file, then it's only a matter of pasting the content of `config/opendkim/keys/domain.tld/mail.txt` in your `domain.tld.hosts` zone. -``` BASH +```console $ dig mail._domainkey.domain.tld TXT --- ;; ANSWER SECTION mail._domainkey. 300 IN TXT "v=DKIM1; k=rsa; p=AZERTYUIOPQSDFGHJKLMWXCVBN/AZERTYUIOPQSDFGHJKLMWXCVBN/AZERTYUIOPQSDFGHJKLMWXCVBN/AZERTYUIOPQSDFGHJKLMWXCVBN/AZERTYUIOPQSDFGHJKLMWXCVBN/AZERTYUIOPQSDFGHJKLMWXCVBN/AZERTYUIOPQSDFGHJKLMWXCVBN/AZERTYUIOPQSDFGHJKLMWXCVBN" ``` -#### Configuration using a web interface +## Configuration using a Web Interface 1. Generate a new record of the type `TXT`. 2. Paste `mail._domainkey` the `Name` txt field. @@ -37,24 +41,25 @@ mail._domainkey. 300 IN TXT "v=DKIM1; k=rsa; p=AZERTYUIOPQSDFGHJKLMWX 4. In `TTL` (time to live): Time span in seconds. How long the DNS server should cache the `TXT` record. 5. Save. -**Note**: Sometimes the key in `config/opendkim/keys/domain.tld/mail.txt` can be on multiple lines. If so then you need to concatenate the values in the TXT record: +!!! note + Sometimes the key in `config/opendkim/keys/domain.tld/mail.txt` can be on multiple lines. If so then you need to concatenate the values in the TXT record: -``` BASH +```console $ dig mail._domainkey.domain.tld TXT --- ;; ANSWER SECTION -mail._domainkey. 300 IN TXT "v=DKIM1; k=rsa; " - "p=AZERTYUIOPQSDF..." - "asdfQWERTYUIOPQSDF..." +mail._domainkey. 300 IN TXT "v=DKIM1; k=rsa; " + "p=AZERTYUIOPQSDF..." + "asdfQWERTYUIOPQSDF..." ``` The target (or value) field must then have all the parts together: `v=DKIM1; k=rsa; p=AZERTYUIOPQSDF...asdfQWERTYUIOPQSDF...` -#### Verify-only +## Verify-Only -If you want DKIM to only _verify_ incoming emails, the following version of /etc/opendkim.conf may be useful (right now there is no easy mechanism for installing it other than forking the repo): +If you want DKIM to only _verify_ incoming emails, the following version of `/etc/opendkim.conf` may be useful (right now there is no easy mechanism for installing it other than forking the repo): -``` TXT +```conf # This is a simple config file verifying messages only #LogWhy yes @@ -70,12 +75,12 @@ SendReports yes Mode v ``` -#### Debugging +## Debugging - [DKIM-verifer](https://addons.mozilla.org/en-US/thunderbird/addon/dkim-verifier): A add-on for the mail client Thunderbird. - You can debug your TXT records with the `dig` tool. -``` BASH +```console $ dig TXT mail._domainkey.domain.tld --- ; <<>> DiG 9.10.3-P4-Debian <<>> TXT mail._domainkey.domain.tld @@ -90,7 +95,7 @@ $ dig TXT mail._domainkey.domain.tld ;mail._domainkey.domain.tld. IN TXT ;; ANSWER SECTION: -mail._domainkey.domain.tld. 3600 IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxBSjG6RnWAdU3oOlqsdf2WC0FOUmU8uHVrzxPLW2R3yRBPGLrGO1++yy3tv6kMieWZwEBHVOdefM6uQOQsZ4brahu9lhG8sFLPX4MaKYN/NR6RK4gdjrZu+MYSdfk3THgSbNwIDAQAB" +mail._domainkey.domain.tld. 3600 IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxBSjG6RnWAdU3oOlqsdf2WC0FOUmU8uHVrzxPLW2R3yRBPGLrGO1++yy3tv6kMieWZwEBHVOdefM6uQOQsZ4brahu9lhG8sFLPX4MaKYN/NR6RK4gdjrZu+MYSdfk3THgSbNwIDAQAB" ;; Query time: 50 msec ;; SERVER: 127.0.1.1#53(127.0.1.1) @@ -98,6 +103,6 @@ mail._domainkey.domain.tld. 3600 IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBA ;; MSG SIZE rcvd: 310 ``` -#### Switch off DKIM +## Switch Off DKIM -Simply remove the DKIM key by recreating (not just relaunching) the mailserver container. \ No newline at end of file +Simply remove the DKIM key by recreating (not just relaunching) the mailserver container. diff --git a/docs/content/config/best-practices/dmarc.md b/docs/content/config/best-practices/dmarc.md index 12129211..e8309c66 100644 --- a/docs/content/config/best-practices/dmarc.md +++ b/docs/content/config/best-practices/dmarc.md @@ -1,10 +1,16 @@ +--- +title: 'Best Practices | DMARC' +hide: + - toc # Hide Table of Contents for this page +--- + DMARC Guide: https://github.com/internetstandards/toolbox-wiki/blob/master/DMARC-how-to.md ## Enabling DMARC In `docker-mailserver`, DMARC is pre-configured out-of the box. The only thing you need to do in order to enable it, is to add new TXT entry to your DNS. -In contrast with [DKIM](https://github.com/tomav/docker-mailserver/wiki/Configure-DKIM), DMARC DNS entry does not require any keys, but merely setting the [configuration values](https://github.com/internetstandards/toolbox-wiki/blob/master/DMARC-how-to.md#overview-of-dmarc-configuration-tags). You can either handcraft the entry by yourself or use one of available generators (like https://dmarcguide.globalcyberalliance.org/). +In contrast with [DKIM][docs-dkim], DMARC DNS entry does not require any keys, but merely setting the [configuration values](https://github.com/internetstandards/toolbox-wiki/blob/master/DMARC-how-to.md#overview-of-dmarc-configuration-tags). You can either handcraft the entry by yourself or use one of available generators (like https://dmarcguide.globalcyberalliance.org/). Typically something like this should be good to start with (don't forget to replace `@domain.com` to your actual domain) ``` @@ -18,4 +24,7 @@ _dmarc IN TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc.report@domain.com; ruf=m DMARC status is not being displayed instantly in Gmail for instance. If you want to check it directly after DNS entries, you can use some services around the Internet such as https://dmarcguide.globalcyberalliance.org/ or https://ondmarc.redsift.com/. In other case, email clients will show "DMARC: PASS" in ~1 day or so. -Reference: [#1511](https://github.com/tomav/docker-mailserver/issues/1511) \ No newline at end of file +Reference: [#1511][github-issue-1511] + +[docs-dkim]: ./dkim.md +[github-issue-1511]: https://github.com/docker-mailserver/docker-mailserver/issues/1511 diff --git a/docs/content/config/best-practices/spf.md b/docs/content/config/best-practices/spf.md index c82f381a..15bfc55b 100644 --- a/docs/content/config/best-practices/spf.md +++ b/docs/content/config/best-practices/spf.md @@ -1,35 +1,44 @@ +--- +title: 'Best Practices | SPF' +hide: + - toc # Hide Table of Contents for this page +--- + From [Wikipedia](https://en.wikipedia.org/wiki/Sender_Policy_Framework): > Sender Policy Framework (SPF) is a simple email-validation system designed to detect email spoofing by providing a mechanism to allow receiving mail exchangers to check that incoming mail from a domain comes from a host authorized by that domain's administrators. The list of authorized sending hosts for a domain is published in the Domain Name System (DNS) records for that domain in the form of a specially formatted TXT record. Email spam and phishing often use forged "from" addresses, so publishing and checking SPF records can be considered anti-spam techniques. For a more technical review: https://github.com/internetstandards/toolbox-wiki/blob/master/SPF-how-to.md -## Add a SPF record +## Add a SPF Record To add a SPF record in your DNS, insert the following line in your DNS zone: - ; MX record must be declared for SPF to work - domain.com. IN MX 1 mail.domain.com. +```txt +; MX record must be declared for SPF to work +domain.com. IN MX 1 mail.domain.com. - ; SPF record - domain.com. IN TXT "v=spf1 mx ~all" +; SPF record +domain.com. IN TXT "v=spf1 mx ~all" +``` This enables the _Softfail_ mode for SPF. You could first add this SPF record with a very low TTL. _SoftFail_ is a good setting for getting started and testing, as it lets all email through, with spams tagged as such in the mailbox. -After verification, you _might_ want to change your SPF record to `v=spf1 mx -all` so as to enforce the _HardFail_ policy. See http://www.open-spf.org/SPF_Record_Syntax/ for more details about SPF policies. +After verification, you _might_ want to change your SPF record to `v=spf1 mx -all` so as to enforce the _HardFail_ policy. See http://www.open-spf.org/SPF_Record_Syntax for more details about SPF policies. In any case, increment the SPF record's TTL to its final value. ## Backup MX, Secondary MX -For whitelisting a IP-Address from the SPF test, you can create a config file (see [policyd-spf.conf](http://www.linuxcertif.com/man/5/policyd-spf.conf/)) and mount that file into `/etc/postfix-policyd-spf-python/policyd-spf.conf`. +For whitelisting a IP Address from the SPF test, you can create a config file (see [`policyd-spf.conf`](https://www.linuxcertif.com/man/5/policyd-spf.conf)) and mount that file into `/etc/postfix-policyd-spf-python/policyd-spf.conf`. **Example:** -Create and edit a policyd-spf.conf file here `//config/postfix-policyd-spf.conf`: -```shell -debugLevel = 1 +Create and edit a `policyd-spf.conf` file here `//config/postfix-policyd-spf.conf`: + +```conf +debugLevel = 1 #0(only errors)-4(complete data received) skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1 @@ -37,10 +46,11 @@ skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1 # Preferably use IP-Addresses for whitelist lookups: Whitelist = 192.168.0.0/31,192.168.1.0/30 # Domain_Whitelist = mx1.mybackupmx.com,mx2.mybackupmx.com - ``` -Then add this line to `docker-compose.yml` below the `volumes:` section + +Then add this line to `docker-compose.yml`: ```yaml -- ./config/postfix-policyd-spf.conf:/etc/postfix-policyd-spf-python/policyd-spf.conf -``` \ No newline at end of file +volumes: + - ./config/postfix-policyd-spf.conf:/etc/postfix-policyd-spf-python/policyd-spf.conf +``` diff --git a/docs/content/config/pop3.md b/docs/content/config/pop3.md index b95580b2..caa44891 100644 --- a/docs/content/config/pop3.md +++ b/docs/content/config/pop3.md @@ -1,18 +1,23 @@ +--- +title: Mail Delivery with POP3 +hide: + - toc # Hide Table of Contents for this page +--- + **We do not recommend using POP. Use IMAP instead.** If you really want to have POP3 running, add 3 lines to the docker-compose.yml : Add the ports 110 and 995, and add environment variable ENABLE_POP : -``` +```yaml mail: - [...] ports: - - "25:25" - - "143:143" - - "587:587" - - "993:993" - - "110:110" - - "995:995" + - "25:25" + - "143:143" + - "587:587" + - "993:993" + - "110:110" + - "995:995" environment: - - ENABLE_POP3=1 + - ENABLE_POP3=1 ``` diff --git a/docs/content/config/security/fail2ban.md b/docs/content/config/security/fail2ban.md index e04558bc..4ea7df63 100644 --- a/docs/content/config/security/fail2ban.md +++ b/docs/content/config/security/fail2ban.md @@ -1,17 +1,35 @@ -Fail2ban is installed automatically and bans IP addresses for 3 hours after 3 failed attempts in 10 minutes by default. If you want to change this, you can easily edit [config/fail2ban-jail.cf](https://github.com/tomav/docker-mailserver/blob/master/config/fail2ban-jail.cf). -You can do the same with the values from fail2ban.conf, e.g dbpurgeage. In that case you need to edit [config/fail2ban-fail2ban.cf](https://github.com/tomav/docker-mailserver/blob/master/config/fail2ban-fail2ban.cf) +--- +title: 'Security | Fail2Ban' +hide: + - toc # Hide Table of Contents for this page +--- -__Important__: The mail container must be launched with the NET_ADMIN capability in order to be able to install the iptable rules that actually ban IP addresses. Thus either include `--cap-add=NET_ADMIN` in the docker run commandline or the equivalent docker-compose.yml: -``` - cap_add: - - NET_ADMIN -``` -If you don't you will see errors of the form -``` +Fail2Ban is installed automatically and bans IP addresses for 3 hours after 3 failed attempts in 10 minutes by default. If you want to change this, you can easily edit [`config/fail2ban-jail.cf`][github-file-f2bjail]. + +You can do the same with the values from `fail2ban.conf`, e.g `dbpurgeage`. In that case you need to edit [`config/fail2ban-fail2ban.cf`][github-file-f2bconfig]. + +!!! attention + The mail container must be launched with the `NET_ADMIN` capability in order to be able to install the iptable rules that actually ban IP addresses. + + Thus either include `--cap-add=NET_ADMIN` in the docker run commandline or the equivalent `docker-compose.yml`: + + ```yaml + cap_add: + - NET_ADMIN + ``` + +If you don't you will see errors the form of: + +```log iptables -w -X f2b-postfix -- stderr: "getsockopt failed strangely: Operation not permitted\niptables v1.4.21: can't initialize iptabl es table `filter': Permission denied (you must be root)\nPerhaps iptables or your kernel needs to be upgraded.\niptables v1.4.21: can' t initialize iptables table `filter': Permission denied (you must be root)\nPerhaps iptables or your kernel needs to be upgraded.\n" 2016-06-01 00:53:51,284 fail2ban.action [678]: ERROR iptables -w -D INPUT -p tcp -m multiport --dports smtp,465,submission - j f2b-postfix ``` -You can also manage and list the banned IPs with the [setup.sh](https://github.com/tomav/docker-mailserver/wiki/Setup-docker-mailserver-using-the-script-setup.sh) script. \ No newline at end of file + +You can also manage and list the banned IPs with the [`setup.sh`][docs-setupsh] script. + +[docs-setupsh]: ../setup.sh.md +[github-file-f2bjail]: https://github.com/docker-mailserver/docker-mailserver/blob/master/config/fail2ban-jail.cf +[github-file-f2bconfig]: https://github.com/docker-mailserver/docker-mailserver/blob/master/config/fail2ban-fail2ban.cf diff --git a/docs/content/config/security/ssl.md b/docs/content/config/security/ssl.md index 3f2ae276..95a8f7f1 100644 --- a/docs/content/config/security/ssl.md +++ b/docs/content/config/security/ssl.md @@ -1,112 +1,140 @@ +--- +title: 'Security | TLS (aka SSL)' +--- + There are multiple options to enable SSL: -* using [letsencrypt](#lets-encrypt-recommended) (recommended) -* using [Caddy](#caddy) -* using [Traefik](#traefik) -* using [self-signed certificates](#self-signed-certificates-testing-only) with the provided tool -* using [your own certificates](#custom-certificate-files) +- Using [letsencrypt](#lets-encrypt-recommended) (recommended) +- Using [Caddy](#caddy) +- Using [Traefik](#traefik) +- Using [self-signed certificates](#self-signed-certificates-testing-only) with the provided tool +- Using [your own certificates](#custom-certificate-files) After installation, you can test your setup with: -- [checktls.com](https://www.checktls.com/TestReceiver) -- [testssl.sh](https://github.com/drwetter/testssl.sh) -### Let's encrypt (recommended) +- [`checktls.com`](https://www.checktls.com/TestReceiver) +- [`testssl.sh`](https://github.com/drwetter/testssl.sh) + +## Let's Encrypt (Recommended) To enable Let's Encrypt on your mail server, you have to: -* get your certificate using [letsencrypt client](https://github.com/letsencrypt/letsencrypt) -* add an environment variable `SSL_TYPE` with value `letsencrypt` (see `docker-compose.yml.dist`) -* mount your whole `letsencrypt` folder to `/etc/letsencrypt` -* the certs folder name located in `letsencrypt/live/` must be the `fqdn` of your container responding to the `hostname` command. The full qualified domain name (`fqdn`) inside the docker container is built combining the `hostname` and `domainname` values of the docker-compose file, e. g.: hostname: `mail`; domainname: `myserver.tld`; fqdn: `mail.myserver.tld` +- Get your certificate using [letsencrypt client](https://github.com/letsencrypt/letsencrypt) +- Add an environment variable `SSL_TYPE` with value `letsencrypt` (see [`docker-compose.yml`][github-file-compose]) +- Mount your whole `letsencrypt` folder to `/etc/letsencrypt` +- The certs folder name located in `letsencrypt/live/` must be the `fqdn` of your container responding to the `hostname` command. The `fqdn` (full qualified domain name) inside the docker container is built combining the `hostname` and `domainname` values of the `docker-compose` file, eg: + + ```yaml + services: + mail: + hostname: mail + domainname: myserver.tld + fqdn: mail.myserver.tld + ``` You don't have anything else to do. Enjoy. +### Example using Docker for Let's Encrypt +1. Make a directory to store your letsencrypt logs and configs. In my case: -#### Example using docker for letsencrypt -Make a directory to store your letsencrypt logs and configs. + ```sh + mkdir -p /home/ubuntu/docker/letsencrypt + cd /home/ubuntu/docker/letsencrypt + ``` -In my case -``` -mkdir -p /home/ubuntu/docker/letsencrypt -cd /home/ubuntu/docker/letsencrypt +2. Now get the certificate (modify `mail.myserver.tld`) and following the certbot instructions. + +3. This will need access to port 80 from the internet, adjust your firewall if needed: + + ```sh + docker run --rm -it \ + -v $PWD/log/:/var/log/letsencrypt/ \ + -v $PWD/etc/:/etc/letsencrypt/ \ + -p 80:80 \ + certbot/certbot certonly --standalone -d mail.myserver.tld + ``` + +4. You can now mount `/home/ubuntu/docker/letsencrypt/etc/` in `/etc/letsencrypt` of `docker-mailserver`. + + To renew your certificate just run (this will need access to port 443 from the internet, adjust your firewall if needed): + + ```sh + docker run --rm -it \ + -v $PWD/log/:/var/log/letsencrypt/ \ + -v $PWD/etc/:/etc/letsencrypt/ \ + -p 80:80 \ + -p 443:443 \ + certbot/certbot renew + ``` + +### Example using Docker, `nginx-proxy` and `letsencrypt-nginx-proxy-companion` + +If you are running a web server already, it is non-trivial to generate a Let's Encrypt certificate for your mail server using `certbot`, because port 80 is already occupied. In the following example, we show how `docker-mailserver` can be run alongside the docker containers `nginx-proxy` and `letsencrypt-nginx-proxy-companion`. + +There are several ways to start `nginx-proxy` and `letsencrypt-nginx-proxy-companion`. Any method should be suitable here. + +For example start `nginx-proxy` as in the `letsencrypt-nginx-proxy-companion` [documentation](https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion): + +```sh +docker run --detach \ + --name nginx-proxy \ + --restart always \ + --publish 80:80 \ + --publish 443:443 \ + --volume /server/letsencrypt/etc:/etc/nginx/certs:ro \ + --volume /etc/nginx/vhost.d \ + --volume /usr/share/nginx/html \ + --volume /var/run/docker.sock:/tmp/docker.sock:ro \ + jwilder/nginx-proxy ``` -Now get the certificate (modify ```mail.myserver.tld```) and following the certbot instructions. -This will need access to port 80 from the internet, adjust your firewall if needed -``` -docker run --rm -ti -v $PWD/log/:/var/log/letsencrypt/ -v $PWD/etc/:/etc/letsencrypt/ -p 80:80 certbot/certbot certonly --standalone -d mail.myserver.tld -``` -You can now mount /home/ubuntu/docker/letsencrypt/etc/ in /etc/letsencrypt of ```docker-mailserver``` +Then start `nginx-proxy-letsencrypt`: -To renew your certificate just run (this will need access to port 443 from the internet, adjust your firewall if needed) -``` -docker run --rm -ti -v $PWD/log/:/var/log/letsencrypt/ -v $PWD/etc/:/etc/letsencrypt/ -p 80:80 -p 443:443 certbot/certbot renew +```sh +docker run --detach \ + --name nginx-proxy-letsencrypt \ + --restart always \ + --volume /server/letsencrypt/etc:/etc/nginx/certs:rw \ + --volumes-from nginx-proxy \ + --volume /var/run/docker.sock:/var/run/docker.sock:ro \ + jrcs/letsencrypt-nginx-proxy-companion ``` -#### Example using docker, nginx-proxy and letsencrypt-nginx-proxy-companion #### -If you are running a web server already, it is non-trivial to generate a Let's Encrypt certificate for your mail server using ```certbot```, because port 80 is already occupied. In the following example, we show how ```docker-mailserver``` can be run alongside the docker containers ```nginx-proxy``` and ```letsencrypt-nginx-proxy-companion```. - -There are several ways to start ```nginx-proxy``` and ```letsencrypt-nginx-proxy-companion```. Any method should be suitable here. For example start ```nginx-proxy``` as in the ```letsencrypt-nginx-proxy-companion``` [documentation](https://github.com/JrCs/docker-letsencrypt-nginx-proxy-companion): - -``` - docker run --detach \ - --name nginx-proxy \ - --restart always \ - --publish 80:80 \ - --publish 443:443 \ - --volume /server/letsencrypt/etc:/etc/nginx/certs:ro \ - --volume /etc/nginx/vhost.d \ - --volume /usr/share/nginx/html \ - --volume /var/run/docker.sock:/tmp/docker.sock:ro \ - jwilder/nginx-proxy -``` - -Then start ```nginx-proxy-letsencrypt```: -``` - docker run --detach \ - --name nginx-proxy-letsencrypt \ - --restart always \ - --volume /server/letsencrypt/etc:/etc/nginx/certs:rw \ - --volumes-from nginx-proxy \ - --volume /var/run/docker.sock:/var/run/docker.sock:ro \ - jrcs/letsencrypt-nginx-proxy-companion -``` Start the rest of your web server containers as usual. -Start another container for your ```mail.myserver.tld```. This will generate a Let's Encrypt certificate for your domain, which can be used by ```docker-mailserver```. It will also run a web server on port 80 at that address.: -``` +Start another container for your `mail.myserver.tld`. This will generate a Let's Encrypt certificate for your domain, which can be used by `docker-mailserver`. It will also run a web server on port 80 at that address: + +```sh docker run -d \ - --name webmail \ - -e "VIRTUAL_HOST=mail.myserver.tld" \ - -e "LETSENCRYPT_HOST=mail.myserver.tld" \ - -e "LETSENCRYPT_EMAIL=foo@bar.com" \ - library/nginx -``` -You may want to add ```-e LETSENCRYPT_TEST=true``` to the above while testing to avoid the Let's Encrypt certificate generation rate limits. - -Finally, start the mailserver with the docker-compose.yml -Make sure your mount path to the letsencrypt certificates is correct. -Inside your /path/to/mailserver/docker-compose.yml ( for the mailserver from this repo ) make sure volumes look like below example; - -``` - volumes: - - maildata:/var/mail - - mailstate:/var/mail-state - - ./config/:/tmp/docker-mailserver/ - - /server/letsencrypt/etc:/etc/letsencrypt/live + --name webmail \ + -e "VIRTUAL_HOST=mail.myserver.tld" \ + -e "LETSENCRYPT_HOST=mail.myserver.tld" \ + -e "LETSENCRYPT_EMAIL=foo@bar.com" \ + library/nginx ``` -Then +You may want to add `-e LETSENCRYPT_TEST=true` to the above while testing to avoid the Let's Encrypt certificate generation rate limits. -/path/to/mailserver/docker-compose up -d mail +Finally, start the mailserver with the `docker-compose.yml`. Make sure your mount path to the letsencrypt certificates is correct. +Inside your `/path/to/mailserver/docker-compose.yml` (for the mailserver from this repo) make sure volumes look like below example: +```yaml +volumes: + - maildata:/var/mail + - mailstate:/var/mail-state + - ./config/:/tmp/docker-mailserver/ + - /server/letsencrypt/etc:/etc/letsencrypt/live +``` -#### Example using docker, nginx-proxy and letsencrypt-nginx-proxy-companion with docker-compose -The following docker-compose.yml is the basic setup you need for using letsencrypt-nginx-proxy-companion. It is mainly derived from its own wiki/documenation. +Then: `/path/to/mailserver/docker-compose up -d mail` -```YAML +### Example using Docker, `nginx-proxy` and `letsencrypt-nginx-proxy-companion` with `docker-compose` + +The following `docker-compose.yml` is the basic setup you need for using `letsencrypt-nginx-proxy-companion`. It is mainly derived from its own wiki/documenation. + +```yaml version: "2" services: @@ -156,9 +184,9 @@ networks: name: nginx-proxy ``` -The second part of the setup is the actual mail container. So, in another folder, create another docker-compose.yml with the following content (Removed all ENV variables for this example): +The second part of the setup is the actual mail container. So, in another folder, create another `docker-compose.yml` with the following content (Removed all ENV variables for this example): -``` YAML +```yaml version: '2' services: mail: @@ -192,60 +220,62 @@ services: networks: - proxy-tier restart: always - + networks: proxy-tier: external: name: nginx-proxy - ``` -The mail container needs to have the letsencrypt certificate folder mounted as a volume. No further changes are needed. The second container is a dummy-sidecar we need, because the mail-container do not expose any web-ports. Set your ENV variables as you need. (VIRTUAL_HOST and LETSENCRYPT_HOST are mandandory, see documentation) +The mail container needs to have the letsencrypt certificate folder mounted as a volume. No further changes are needed. The second container is a dummy-sidecar we need, because the mail-container do not expose any web-ports. Set your ENV variables as you need. (`VIRTUAL_HOST` and `LETSENCRYPT_HOST` are mandandory, see documentation) +### Example using the Let's Encrypt Certificates on a Synology NAS -#### Example using the letsencrypt certificates on a Synology NAS +Version 6.2 and later of the Synology NAS DSM OS now come with an interface to generate and renew letencrypt certificates. Navigation into your DSM control panel and go to Security, then click on the tab Certificate to generate and manage letsencrypt certificates. -Version 6.2 and later of the Synology NAS DSM OS now come with an interface to generate and renew letencrypt certificates. Navigation into your DSM control panel and go to Security, then click on the tab Certificate to generate and manage letsencrypt certificates. Amongst other things, you can use these to secure your mail server. DSM locates the generated certificates in a folder below ```/usr/syno/etc/certificate/_archive/```. Navigate to that folder and note the 6 character random folder name of the certificate you'd like to use. Then, add the following to your ```docker-compose.yml``` declaration file: +Amongst other things, you can use these to secure your mail server. DSM locates the generated certificates in a folder below `/usr/syno/etc/certificate/_archive/`. -``` +Navigate to that folder and note the 6 character random folder name of the certificate you'd like to use. Then, add the following to your `docker-compose.yml` declaration file: + +```yaml volumes: - - /usr/syno/etc/certificate/_archive/YOUR_FOLDER/:/tmp/ssl -... + - /usr/syno/etc/certificate/_archive//:/tmp/ssl environment: - - SSL_TYPE=manual - - SSL_CERT_PATH=/tmp/ssl/fullchain.pem - - SSL_KEY_PATH=/tmp/ssl/privkey.pem - + - SSL_TYPE=manual + - SSL_CERT_PATH=/tmp/ssl/fullchain.pem + - SSL_KEY_PATH=/tmp/ssl/privkey.pem ``` + DSM-generated letsencrypt certificates get auto-renewed every three months. -### Caddy +## Caddy -If you are using Caddy to renew your certificates, please note that only RSA certificates work. Read [issue 1440](https://github.com/tomav/docker-mailserver/issues/1440) for details. In short for Caddy v1 the Caddyfile should look something like: +If you are using Caddy to renew your certificates, please note that only RSA certificates work. Read [#1440][github-issue-1440] for details. In short for Caddy v1 the `Caddyfile` should look something like: -``` +```caddyfile https://mail.domain.com { - tls yourcurrentemail@gmail.com { - key_type rsa2048 - } + tls yourcurrentemail@gmail.com { + key_type rsa2048 + } } ``` -For Caddy v2 you can specify the key_type in your server's global settings, which would end up looking something like this if you're using a Caddyfile: -``` -{ -debug -admin localhost:2019 -http_port 80 -https_port 443 -default_sni mywebserver.com -key_type rsa4096 +For Caddy v2 you can specify the `key_type` in your server's global settings, which would end up looking something like this if you're using a `Caddyfile`: + +```caddyfile +{ + debug + admin localhost:2019 + http_port 80 + https_port 443 + default_sni mywebserver.com + key_type rsa4096 } -```` +``` If you are instead using a json config for Caddy v2, you can set it in your site's TLS automation policies: -``` +```json { "apps": { "http": { @@ -309,8 +339,10 @@ If you are instead using a json config for Caddy v2, you can set it in your site } } ``` + The generated certificates can be mounted: -``` + +```yaml volumes: - ${CADDY_DATA_DIR}/certificates/acme-v02.api.letsencrypt.org-directory/mail.domain.com/mail.domain.com.crt:/etc/letsencrypt/live/mail.domain.com/fullchain.pem - ${CADDY_DATA_DIR}/certificates/acme-v02.api.letsencrypt.org-directory/mail.domain.com/mail.domain.com.key:/etc/letsencrypt/live/mail.domain.com/privkey.pem @@ -318,32 +350,32 @@ volumes: EC certificates fail in the TLS handshake: -``` +```log CONNECTED(00000003) 140342221178112:error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:ssl/record/rec_layer_s3.c:1543:SSL alert number 40 no peer certificate available No client certificate CA names sent ``` -### Traefik +## Traefik [Traefik](https://github.com/containous/traefik) is an open-source Edge Router which handles ACME protocol using [lego](https://github.com/go-acme/lego). Traefik can request certificates for domains through the ACME protocol (see [Traefik's documentation about its ACME negotiation & storage mechanism](https://docs.traefik.io/https/acme/)). Traefik's router will take care of renewals, challenge negotiations, etc. -##### Traefik v2 +### Traefik v2 (For Traefik v1 see [next section](#traefik-v1)) Traefik's V2 storage format is natively supported if the `acme.json` store is mounted into the container at `/etc/letsencrypt/acme.json`. The file is also monitored for changes and will trigger a reload of the mail services. Lookup of the certificate domain happens in the following order: - 1. $SSL_DOMAIN - 2. $HOSTNAME - 3. $DOMAINNAME +1. `$SSL_DOMAIN` +2. `$HOSTNAME` +3. `$DOMAINNAME` -This allows for support of wild card certificates: `"SSL_DOMAIN=*.example.com"`. Here is an example setup for [docker-compose](https://docs.docker.com/compose/): +This allows for support of wild card certificates: `SSL_DOMAIN=*.example.com`. Here is an example setup for [`docker-compose`](https://docs.docker.com/compose/): -``` YAML +```yaml version: '3.8' services: mail: @@ -384,127 +416,150 @@ services: This setup only comes with one caveat: The domain has to be configured on another service for traefik to actually request it from lets-encrypt (`whoami` in this case). -##### Traefik V1 +### Traefik v1 -If you are using Traefik v1, you might want to _push_ your Traefik-managed certificates to the mailserver container, in order to reuse them. Not an easy task, but fortunately, [youtous/mailserver-traefik](https://github.com/youtous/docker-mailserver-traefik) is a certificate renewal service for docker-mailserver. +If you are using Traefik v1, you might want to _push_ your Traefik-managed certificates to the mailserver container, in order to reuse them. Not an easy task, but fortunately, [`youtous/mailserver-traefik`][youtous-mailtraefik] is a certificate renewal service for `docker-mailserver`. -Depending of your Traefik configuration, certificates may be stored using a file or a KV Store (consul, etcd...) Either way, certificates will be renewed by Traefik, then automatically pushed to the mailserver thanks to the cert-renewer service. Finally, dovecot and postfix will be restarted. +Depending of your Traefik configuration, certificates may be stored using a file or a KV Store (consul, etcd...) Either way, certificates will be renewed by Traefik, then automatically pushed to the mailserver thanks to the `cert-renewer` service. Finally, dovecot and postfix will be restarted. -Documentation: https://github.com/youtous/docker-mailserver-traefik - - -### Self-signed certificates (testing only) +## Self-Signed Certificates (Testing Only) You can easily generate a self-signed SSL certificate by using the following command: - docker run -ti --rm -v "$(pwd)"/config/ssl:/tmp/docker-mailserver/ssl -h mail.my-domain.com -t tvial/docker-mailserver generate-ssl-certificate +```sh +docker run -it --rm -v "$(pwd)"/config/ssl:/tmp/docker-mailserver/ssl -h mail.my-domain.com -t tvial/docker-mailserver generate-ssl-certificate - # Press enter - # Enter a password when needed - # Fill information like Country, Organisation name - # Fill "my-domain.com" as FQDN for CA, and "mail.my-domain.com" for the certificate. - # They HAVE to be different, otherwise you'll get a `TXT_DB error number 2` - # Don't fill extras - # Enter same password when needed - # Sign the certificate? [y/n]:y - # 1 out of 1 certificate requests certified, commit? [y/n]y +# Press enter +# Enter a password when needed +# Fill information like Country, Organisation name +# Fill "my-domain.com" as FQDN for CA, and "mail.my-domain.com" for the certificate. +# They HAVE to be different, otherwise you'll get a `TXT_DB error number 2` +# Don't fill extras +# Enter same password when needed +# Sign the certificate? [y/n]:y +# 1 out of 1 certificate requests certified, commit? [y/n]y - # will generate: - # config/ssl/mail.my-domain.com-key.pem (used in postfix) - # config/ssl/mail.my-domain.com-req.pem (only used to generate other files) - # config/ssl/mail.my-domain.com-cert.pem (used in postfix) - # config/ssl/mail.my-domain.com-combined.pem (used in courier) - # config/ssl/demoCA/cacert.pem (certificate authority) +# will generate: +# config/ssl/mail.my-domain.com-key.pem (used in postfix) +# config/ssl/mail.my-domain.com-req.pem (only used to generate other files) +# config/ssl/mail.my-domain.com-cert.pem (used in postfix) +# config/ssl/mail.my-domain.com-combined.pem (used in courier) +# config/ssl/demoCA/cacert.pem (certificate authority) +``` -Note that the certificate will be generate for the container `fqdn`, that is passed as `-h` argument. -Check the following page for more information regarding [postfix and SSL/TLS configuration](http://www.mad-hacking.net/documentation/linux/applications/mail/using-ssl-tls-postfix-courier.xml). +!!! note + The certificate will be generate for the container `fqdn`, that is passed as `-h` argument. + + Check the following page for more information regarding [postfix and SSL/TLS configuration](http://www.mad-hacking.net/documentation/linux/applications/mail/using-ssl-tls-postfix-courier.xml). To use the certificate: -* add `SSL_TYPE=self-signed` to your container environment variables -* if a matching certificate (files listed above) is found in `config/ssl`, it will be automatically setup in postfix and dovecot. You just have to place them in `config/ssl` folder. +- Add `SSL_TYPE=self-signed` to your container environment variables +- If a matching certificate (files listed above) is found in `config/ssl`, it will be automatically setup in postfix and dovecot. You just have to place them in `config/ssl` folder. -### Custom certificate files +## Custom Certificate Files You can also provide your own certificate files. Add these entries to your `docker-compose.yml`: - volumes: - - /etc/ssl:/tmp/ssl:ro - environment: - - SSL_TYPE=manual - - SSL_CERT_PATH=/tmp/ssl/cert/public.crt - - SSL_KEY_PATH=/tmp/ssl/private/private.key +```yaml +volumes: + - /etc/ssl:/tmp/ssl:ro +environment: + - SSL_TYPE=manual + - SSL_CERT_PATH=/tmp/ssl/cert/public.crt + - SSL_KEY_PATH=/tmp/ssl/private/private.key +``` This will mount the path where your ssl certificates reside as read-only under `/tmp/ssl`. Then all you have to do is to specify the location of your private key and the certificate. -Please note that you may have to restart your mailserver once the certificates change. +!!! note + You may have to restart your mailserver once the certificates change. -### Testing certificate +## Testing a Certificate is Valid -From your host: +- From your host: - docker exec mail openssl s_client -connect 0.0.0.0:25 -starttls smtp -CApath /etc/ssl/certs/ + ```sh + docker exec mail openssl s_client \ + -connect 0.0.0.0:25 \ + -starttls smtp \ + -CApath /etc/ssl/certs/ + ``` -or +- Or: - docker exec mail openssl s_client -connect 0.0.0.0:143 -starttls imap -CApath /etc/ssl/certs/ + ```sh + docker exec mail openssl s_client \ + -connect 0.0.0.0:143 \ + -starttls imap \ + -CApath /etc/ssl/certs/ + ``` - -And you should see the certificate chain, the server certificate and: - - Verify return code: 0 (ok) +And you should see the certificate chain, the server certificate and: `Verify return code: 0 (ok)` In addition, to verify certificate dates: - docker exec mail openssl s_client -connect 0.0.0.0:25 -starttls smtp -CApath /etc/ssl/certs/ 2>/dev/null | openssl x509 -noout -dates +```sh +docker exec mail openssl s_client \ + -connect 0.0.0.0:25 \ + -starttls smtp \ + -CApath /etc/ssl/certs/ \ + 2>/dev/null | openssl x509 -noout -dates +``` - -### Plain text access +## Plain-Text Access Not recommended for purposes other than testing. -Just add this to config/dovecot.cf: +Just add this to `config/dovecot.cf`: -``` +```cf ssl = yes disable_plaintext_auth=no ``` These options in conjunction mean: -``` -ssl=yes and disable_plaintext_auth=no: SSL/TLS is offered to the client, but the client isn't required to use it. The client is allowed to login with plaintext authentication even when SSL/TLS isn't enabled on the connection. This is insecure, because the plaintext password is exposed to the internet. -``` +- SSL/TLS is offered to the client, but the client isn't required to use it. +- The client is allowed to login with plaintext authentication even when SSL/TLS isn't enabled on the connection. +- **This is insecure**, because the plaintext password is exposed to the internet. -### Importing certificates obtained via another source -If you have another source for SSL/TLS certificates you can import them into the server via an external script. The external script can be found here: [external certificate import script](https://github.com/hanscees/dockerscripts/blob/master/scripts/tomav-renew-certs) +## Importing Certificates Obtained via Another Source + +If you have another source for SSL/TLS certificates you can import them into the server via an external script. The external script can be found here: [external certificate import script][hanscees-renewcerts]. The steps to follow are these: -1. Transport the new certificates to ./config/sll (/tmp/ssl in the container) -2. You should provide fullchain.key and privkey.pem -3. Place the script in ./config/ (or /tmp/docker-mailserver/ inside the container) -4. Make the script executable (chmod +x tomav-renew-certs.sh ) -5. Run the script: docker exec mail /tmp/docker-mailserver/tomav-renew-certs.sh + +1. Transport the new certificates to `./config/ssl` (`/tmp/ssl` in the container) +2. You should provide `fullchain.key` and `privkey.pem` +3. Place the script in `./config/` (or `/tmp/docker-mailserver/` inside the container) +4. Make the script executable (`chmod +x tomav-renew-certs.sh`) +5. Run the script: `docker exec mail /tmp/docker-mailserver/tomav-renew-certs.sh` If an error occurs the script will inform you. If not you will see both postfix and dovecot restart. -After the certificates have been loaded you can check the certificate: +After the certificates have been loaded you can check the certificate: -``` +```sh +openssl s_client \ + -servername mail.mydomain.net \ + -connect 192.168.0.72:465 \ + 2>/dev/null | openssl x509 -openssl s_client -servername mail.mydomain.net -connect 192.168.0.72:465 2>/dev/null | openssl x509 - -# or - -openssl s_client -servername mail.mydomain.net -connect mail.mydomain.net:465 2>/dev/null | openssl x509 +# or +openssl s_client \ + -servername mail.mydomain.net \ + -connect mail.mydomain.net:465 \ + 2>/dev/null | openssl x509 ``` Or you can check how long the new certificate is valid with commands like: -``` + +```sh export SITE_URL="mail.mydomain.net" -export SITE_IP_URL="192.168.0.72" ## can also be mail.mydomain.net -export SITE_SSL_PORT="465" ##imap port dovecot +export SITE_IP_URL="192.168.0.72" # can also be `mail.mydomain.net` +export SITE_SSL_PORT="993" # imap port dovecot ##works: check if certificate will expire in two weeks #2 weeks is 1209600 seconds @@ -513,39 +568,39 @@ export SITE_SSL_PORT="465" ##imap port dovecot #15 weeks is 9072000 certcheck_2weeks=`openssl s_client -connect ${SITE_IP_URL}:${SITE_SSL_PORT} \ - -servername ${SITE_URL} 2> /dev/null | openssl x509 -noout -checkend 1209600` + -servername ${SITE_URL} 2> /dev/null | openssl x509 -noout -checkend 1209600` #################################### -#notes: output can be +#notes: output can be #Certificate will not expire #Certificate will expire #################### - ``` What does the script that imports the certificates do: -1. Check if there are new certs in the /tmp/ssl folder -2. check with the ssl cert fingerprint if they differ from the current certificates -3. if so it will copy the certs to the right places -4. and restart postfix and dovecot -You can ofcourse run the script by cron once a week or something. In that way you could automate cert renewal. If you do so it is probably wise to run an automated check on certificate expiry as well. Such a check could look something like this: -``` +1. Check if there are new certs in the `/tmp/ssl` folder. +2. Check with the ssl cert fingerprint if they differ from the current certificates. +3. If so it will copy the certs to the right places. +4. And restart postfix and dovecot. +You can of course run the script by cron once a week or something. In that way you could automate cert renewal. If you do so it is probably wise to run an automated check on certificate expiry as well. Such a check could look something like this: + +```sh ## code below will alert if certificate expires in less than two weeks ## please adjust varables! ## make sure the mail -s command works! Test! export SITE_URL="mail.mydomain.net" -export SITE_IP_URL="192.168.2.72" ## can also be mail.mydomain.net -export SITE_SSL_PORT="465" ##imap port dovecot +export SITE_IP_URL="192.168.2.72" # can also be `mail.mydomain.net` +export SITE_SSL_PORT="993" # imap port dovecot export ALERT_EMAIL_ADDR="bill@gates321boom.com" certcheck_2weeks=`openssl s_client -connect ${SITE_IP_URL}:${SITE_SSL_PORT} \ - -servername ${SITE_URL} 2> /dev/null | openssl x509 -noout -checkend 1209600` + -servername ${SITE_URL} 2> /dev/null | openssl x509 -noout -checkend 1209600` #################################### -#notes: output can be +#notes: output can be #Certificate will not expire #Certificate will expire #################### @@ -555,27 +610,17 @@ certcheck_2weeks=`openssl s_client -connect ${SITE_IP_URL}:${SITE_SSL_PORT} \ ##automated check you might run by cron or something ## does tls/ssl certificate expire within two weeks? -if [ "$certcheck_2weeks" = "Certificate will not expire" ]; then - echo "all is wel, certwatch 2 weeks says $certcheck_2weeks" - else - echo "Cert seems to be expiring pretty soon, within two weeks: $certcheck_2weeks" - echo "we will send an alert email and log as well" - logger Certwatch: cert $SITE_URL will expire in two weeks - echo "Certwatch: cert $SITE_URL will expire in two weeks" | mail -s "cert $SITE_URL expires in two weeks " $ALERT_EMAIL_ADDR +if [ "$certcheck_2weeks" = "Certificate will not expire" ]; then + echo "all is well, certwatch 2 weeks says $certcheck_2weeks" + else + echo "Cert seems to be expiring pretty soon, within two weeks: $certcheck_2weeks" + echo "we will send an alert email and log as well" + logger Certwatch: cert $SITE_URL will expire in two weeks + echo "Certwatch: cert $SITE_URL will expire in two weeks" | mail -s "cert $SITE_URL expires in two weeks " $ALERT_EMAIL_ADDR fi - ``` - - - - - - - - - - - - - +[github-file-compose]: https://github.com/docker-mailserver/docker-mailserver/blob/master/docker-compose.yml +[github-issue-1440]: https://github.com/docker-mailserver/docker-mailserver/issues/1440 +[hanscees-renewcerts]: https://github.com/hanscees/dockerscripts/blob/master/scripts/tomav-renew-certs +[youtous-mailtraefik]: https://github.com/youtous/docker-mailserver-traefik diff --git a/docs/content/config/security/understanding-the-ports.md b/docs/content/config/security/understanding-the-ports.md index ba853e70..6e2b8bd5 100644 --- a/docs/content/config/security/understanding-the-ports.md +++ b/docs/content/config/security/understanding-the-ports.md @@ -1,8 +1,12 @@ +--- +title: 'Security | Understanding the Ports' +--- + ## Quick Reference Prefer Implicit TLS ports, they're more secure and if you use a Reverse Proxy, should be less hassle (although it's probably wiser to expose these ports directly to `docker-mailserver`). -## Overview of email ports +## Overview of Email Ports | Protocol | Explicit TLS1 | Implicit TLS | Purpose | |----------|--------------------------|-----------------|----------------------| @@ -11,56 +15,54 @@ Prefer Implicit TLS ports, they're more secure and if you use a Reverse Proxy, s | POP3 | 110 | 995 | Retrieval | | IMAP4 | 143 | 993 | Retrieval | -1. A connection *may* be secured over TLS when both ends support `STARTTLS`. On ports 110, 143 and 587, `docker-mailserver` will reject a connection that cannot be secured. Port 25 is [required](https://serverfault.com/questions/623692/is-it-still-wrong-to-require-starttls-on-incoming-smtp-messages) to support insecure connections. +1. A connection *may* be secured over TLS when both ends support `STARTTLS`. On ports 110, 143 and 587, `docker-mailserver` will reject a connection that cannot be secured. Port 25 is [required][ref-port25-mandatory] to support insecure connections. 2. Receives email, `docker-mailserver` additionally filters for spam and viruses. For submitting email to the server to be sent to third-parties, you should prefer the *submission* ports(465, 587) - which require authentication. Unless a relay host is configured(eg SendGrid), outgoing email will leave the server via port 25(thus outbound traffic must not be blocked by your provider or firewall). -3. A *submission* port since 2018 ([RFC 8314](https://tools.ietf.org/html/rfc8314)). Previously a secure variant of port 25. +3. A *submission* port since 2018 ([RFC 8314][rfc-8314]). Previously a secure variant of port 25. -### What ports should I use? (SMTP) +### What Ports Should I Use? (SMTP) -[![Best Practice - Ports (SMTP)](https://mermaid.ink/img/eyJjb2RlIjoiZmxvd2NoYXJ0IExSXG4gICAgc3ViZ3JhcGggeW91ci1zZXJ2ZXIgW1wiWW91ciBTZXJ2ZXJcIl1cbiAgICAgICAgaW5fMjUoMjUpIC0tPiBzZXJ2ZXJcbiAgICAgICAgaW5fNDY1KDQ2NSkgLS0-IHNlcnZlclxuICAgICAgICBzZXJ2ZXIoKFwiZG9ja2VyLW1haWxzZXJ2ZXI8YnIvPmhlbGxvQHdvcmxkLmNvbVwiKSlcbiAgICAgICAgc2VydmVyIC0tLSBvdXRfMjUoMjUpXG4gICAgICAgIHNlcnZlciAtLS0gb3V0XzQ2NSg0NjUpXG4gICAgZW5kXG5cbiAgICB0aGlyZC1wYXJ0eShcIlRoaXJkLXBhcnR5PGJyLz4oc2VuZGluZyB5b3UgZW1haWwpXCIpIC0tLXxcIlJlY2VpdmUgZW1haWwgZm9yPGJyLz5oZWxsb0B3b3JsZC5jb21cInwgaW5fMjVcblxuICAgIHN1YmdyYXBoIGNsaWVudHMgW1wiQ2xpZW50cyAoTVVBKVwiXVxuICAgICAgICBtdWEtY2xpZW50KFRodW5kZXJiaXJkLDxici8-V2VibWFpbCw8YnIvPk11dHQsPGJyLz5ldGMpXG4gICAgICAgIG11YS1zZXJ2aWNlKEJhY2tlbmQgc29mdHdhcmU8YnIvPm9uIGFub3RoZXIgc2VydmVyKVxuICAgIGVuZFxuICAgIGNsaWVudHMgLS0tfFwiU2VuZCBlbWFpbCBhczxici8-aGVsbG9Ad29ybGQuY29tXCJ8IGluXzQ2NVxuXG4gICAgb3V0XzI1KDI1KSAtLT58XCJEaXJlY3Q8YnIvPkRlbGl2ZXJ5XCJ8IHRpbl8yNVxuICAgIG91dF80NjUoNDY1KSAtLT4gcmVsYXkoXCJNVEE8YnIvPlJlbGF5IFNlcnZlclwiKSAtLT4gdGluXzI1KDI1KVxuXG4gICAgc3ViZ3JhcGggdGhpcmQtcGFydHktc2VydmVyW1wiVGhpcmQtcGFydHkgU2VydmVyXCJdXG4gICAgICAgIHRoaXJkLXBhcnR5LW10YShcIk1UQTxici8-ZnJpZW5kQGV4YW1wbGUuY29tXCIpXG4gICAgICAgIHRpbl8yNSgyNSkgLS0-IHRoaXJkLXBhcnR5LW10YVxuICAgIGVuZCIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZmxvd2NoYXJ0IExSXG4gICAgc3ViZ3JhcGggeW91ci1zZXJ2ZXIgW1wiWW91ciBTZXJ2ZXJcIl1cbiAgICAgICAgaW5fMjUoMjUpIC0tPiBzZXJ2ZXJcbiAgICAgICAgaW5fNDY1KDQ2NSkgLS0-IHNlcnZlclxuICAgICAgICBzZXJ2ZXIoKFwiZG9ja2VyLW1haWxzZXJ2ZXI8YnIvPmhlbGxvQHdvcmxkLmNvbVwiKSlcbiAgICAgICAgc2VydmVyIC0tLSBvdXRfMjUoMjUpXG4gICAgICAgIHNlcnZlciAtLS0gb3V0XzQ2NSg0NjUpXG4gICAgZW5kXG5cbiAgICB0aGlyZC1wYXJ0eShcIlRoaXJkLXBhcnR5PGJyLz4oc2VuZGluZyB5b3UgZW1haWwpXCIpIC0tLXxcIlJlY2VpdmUgZW1haWwgZm9yPGJyLz5oZWxsb0B3b3JsZC5jb21cInwgaW5fMjVcblxuICAgIHN1YmdyYXBoIGNsaWVudHMgW1wiQ2xpZW50cyAoTVVBKVwiXVxuICAgICAgICBtdWEtY2xpZW50KFRodW5kZXJiaXJkLDxici8-V2VibWFpbCw8YnIvPk11dHQsPGJyLz5ldGMpXG4gICAgICAgIG11YS1zZXJ2aWNlKEJhY2tlbmQgc29mdHdhcmU8YnIvPm9uIGFub3RoZXIgc2VydmVyKVxuICAgIGVuZFxuICAgIGNsaWVudHMgLS0tfFwiU2VuZCBlbWFpbCBhczxici8-aGVsbG9Ad29ybGQuY29tXCJ8IGluXzQ2NVxuXG4gICAgb3V0XzI1KDI1KSAtLT58XCJEaXJlY3Q8YnIvPkRlbGl2ZXJ5XCJ8IHRpbl8yNVxuICAgIG91dF80NjUoNDY1KSAtLT4gcmVsYXkoXCJNVEE8YnIvPlJlbGF5IFNlcnZlclwiKSAtLT4gdGluXzI1KDI1KVxuXG4gICAgc3ViZ3JhcGggdGhpcmQtcGFydHktc2VydmVyW1wiVGhpcmQtcGFydHkgU2VydmVyXCJdXG4gICAgICAgIHRoaXJkLXBhcnR5LW10YShcIk1UQTxici8-ZnJpZW5kQGV4YW1wbGUuY29tXCIpXG4gICAgICAgIHRpbl8yNSgyNSkgLS0-IHRoaXJkLXBhcnR5LW10YVxuICAgIGVuZCIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9) +[![Best Practice - Ports (SMTP)][asset-external-mermaid-smtp]][ref-mermaid-live-smtp] -
-Flowchart - Mermaid.js source: +??? "Flowchart - Mermaid.js source:" -View in the [Live Editor](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZmxvd2NoYXJ0IExSXG4gICAgc3ViZ3JhcGggeW91ci1zZXJ2ZXIgW1wiWW91ciBTZXJ2ZXJcIl1cbiAgICAgICAgaW5fMjUoMjUpIC0tPiBzZXJ2ZXJcbiAgICAgICAgaW5fNDY1KDQ2NSkgLS0-IHNlcnZlclxuICAgICAgICBzZXJ2ZXIoKFwiZG9ja2VyLW1haWxzZXJ2ZXI8YnIvPmhlbGxvQHdvcmxkLmNvbVwiKSlcbiAgICAgICAgc2VydmVyIC0tLSBvdXRfMjUoMjUpXG4gICAgICAgIHNlcnZlciAtLS0gb3V0XzQ2NSg0NjUpXG4gICAgZW5kXG5cbiAgICB0aGlyZC1wYXJ0eShcIlRoaXJkLXBhcnR5PGJyLz4oc2VuZGluZyB5b3UgZW1haWwpXCIpIC0tLXxcIlJlY2VpdmUgZW1haWwgZm9yPGJyLz5oZWxsb0B3b3JsZC5jb21cInwgaW5fMjVcblxuICAgIHN1YmdyYXBoIGNsaWVudHMgW1wiQ2xpZW50cyAoTVVBKVwiXVxuICAgICAgICBtdWEtY2xpZW50KFRodW5kZXJiaXJkLDxici8-V2VibWFpbCw8YnIvPk11dHQsPGJyLz5ldGMpXG4gICAgICAgIG11YS1zZXJ2aWNlKEJhY2tlbmQgc29mdHdhcmU8YnIvPm9uIGFub3RoZXIgc2VydmVyKVxuICAgIGVuZFxuICAgIGNsaWVudHMgLS0tfFwiU2VuZCBlbWFpbCBhczxici8-aGVsbG9Ad29ybGQuY29tXCJ8IGluXzQ2NVxuXG4gICAgb3V0XzI1KDI1KSAtLT58XCJEaXJlY3Q8YnIvPkRlbGl2ZXJ5XCJ8IHRpbl8yNVxuICAgIG91dF80NjUoNDY1KSAtLT4gcmVsYXkoXCJNVEE8YnIvPlJlbGF5IFNlcnZlclwiKSAtLT4gdGluXzI1KDI1KVxuXG4gICAgc3ViZ3JhcGggdGhpcmQtcGFydHktc2VydmVyW1wiVGhpcmQtcGFydHkgU2VydmVyXCJdXG4gICAgICAgIHRoaXJkLXBhcnR5LW10YShcIk1UQTxici8-ZnJpZW5kQGV4YW1wbGUuY29tXCIpXG4gICAgICAgIHRpbl8yNSgyNSkgLS0-IHRoaXJkLXBhcnR5LW10YVxuICAgIGVuZCIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9). + View in the [Live Editor][ref-mermaid-live-smtp]. -``` -flowchart LR - subgraph your-server ["Your Server"] - in_25(25) --> server - in_465(465) --> server - server(("docker-mailserver
hello@world.com")) - server --- out_25(25) - server --- out_465(465) - end + ```mermaid + flowchart LR + subgraph your-server ["Your Server"] + in_25(25) --> server + in_465(465) --> server + server(("docker-mailserver
hello@world.com")) + server --- out_25(25) + server --- out_465(465) + end - third-party("Third-party
(sending you email)") ---|"Receive email for
hello@world.com"| in_25 + third-party("Third-party
(sending you email)") ---|"Receive email for
hello@world.com"| in_25 - subgraph clients ["Clients (MUA)"] - mua-client(Thunderbird,
Webmail,
Mutt,
etc) - mua-service(Backend software
on another server) - end - clients ---|"Send email as
hello@world.com"| in_465 + subgraph clients ["Clients (MUA)"] + mua-client(Thunderbird,
Webmail,
Mutt,
etc) + mua-service(Backend software
on another server) + end + clients ---|"Send email as
hello@world.com"| in_465 - out_25(25) -->|"Direct
Delivery"| tin_25 - out_465(465) --> relay("MTA
Relay Server") --> tin_25(25) + out_25(25) -->|"Direct
Delivery"| tin_25 + out_465(465) --> relay("MTA
Relay Server") --> tin_25(25) - subgraph third-party-server["Third-party Server"] - third-party-mta("MTA
friend@example.com") - tin_25(25) --> third-party-mta - end -``` + subgraph third-party-server["Third-party Server"] + third-party-mta("MTA
friend@example.com") + tin_25(25) --> third-party-mta + end + ``` --- -
+#### Inbound Traffic (On the left) - -#### Inbound Traffic (On the left): - **Port 25:** Think of this like a physical mailbox, it is open to receive email from anyone who wants to. `docker-mailserver` will actively filter email delivered on this port for spam or viruses and refuse mail from known bad sources. While you could also use this port internally to send email outbound without requiring authentication, you really should prefer the *Submission* ports(587, 465). - **Port 465(*and 587*):** This is the equivalent of a post office box where you would send email to be delivered on your behalf(`docker-mailserver` is that metaphorical post office, aka the MTA). Unlike port 25, these two ports are known as the *Submission* ports and require a valid email account on the server with a password to be able to send email to anyone outside of the server(an MTA you do not control, eg Outlook or Gmail). Prefer port 465 which provides Implicit TLS. -#### Outbound Traffic (On the Right): +#### Outbound Traffic (On the Right) + - **Port 25:** Send the email directly to the given email address MTA as possible. Like your own `docker-mailserver` port 25, this is the standard port for receiving email on, thus email will almost always arrive to the final MTA on this port. Note that, there may be additional MTAs further in the chain, but this would be the public facing one representing that email address. - **Port 465(*and 587*):** SMTP Relays are a popular choice to hand-off delivery of email through. Services like SendGrid are useful for bulk email(marketing) or when your webhost or ISP are preventing you from using standard ports like port 25 to send out email(which can be abused by spammers). @@ -70,11 +72,11 @@ flowchart LR #### Explicit TLS (aka Opportunistic TLS) - Opt-in Encryption -Communication on these ports begin in [cleartext](https://www.denimgroup.com/resources/blog/2007/10/cleartext-vs-pl/), indicating support for `STARTTLS`. If both client and server support `STARTTLS` the connection will be secured over TLS, otherwise no encryption will be used. +Communication on these ports begin in [cleartext][ref-clear-vs-plain], indicating support for `STARTTLS`. If both client and server support `STARTTLS` the connection will be secured over TLS, otherwise no encryption will be used. Support for `STARTTLS` is not always implemented correctly, which can lead to leaking credentials(client sending too early) prior to a TLS connection being established. Third-parties such as some ISPs have also been known to intercept the `STARTTLS` exchange, modifying network traffic to prevent establishing a secure connection. -Due to these security concerns, [RFC 8314 (Section 4.1)](https://tools.ietf.org/html/rfc8314#section-4.1) encourages you to **prefer Implicit TLS ports where possible**. +Due to these security concerns, [RFC 8314 (Section 4.1)][rfc-8314-s41] encourages you to **prefer Implicit TLS ports where possible**. #### Implicit TLS - Enforced Encryption @@ -86,12 +88,21 @@ Additionally, referring to port 465 as *SMTPS* would be incorrect, as it is a su ## Security -**TODO:** *This section should provide any related configuration advice, and probably expand on and link to resources about DANE, DNSSEC, MTA-STS and STARTTLS Policy list, with advice on how to configure/setup these added security layers.* +!!! todo + This section should provide any related configuration advice, and probably expand on and link to resources about DANE, DNSSEC, MTA-STS and STARTTLS Policy list, with advice on how to configure/setup these added security layers. -**TODO:** *A related section or page on ciphers used may be useful, although less important for users to be concerned about.* +!!! todo + A related section or page on ciphers used may be useful, although less important for users to be concerned about. ### TLS connections on mail servers, compared to web browsers Unlike with HTTP where a web browser client communicates directly with the server providing a website, a secure TLS connection as discussed below is not the equivalent safety that HTTPS provides when the transit of email (receiving or sending) is sent through third-parties, as the secure connection is only between two machines, any additional machines (MTAs) between the MUA and the MDA depends on them establishing secure connections between one another successfully. -Other machines that facilitate a connection that generally aren't taken into account can exist between a client and server, such as those where your connection passes through your ISP provider are capable of compromising a cleartext connection through interception. \ No newline at end of file +Other machines that facilitate a connection that generally aren't taken into account can exist between a client and server, such as those where your connection passes through your ISP provider are capable of compromising a cleartext connection through interception. + +[asset-external-mermaid-smtp]: https://mermaid.ink/img/eyJjb2RlIjoiZmxvd2NoYXJ0IExSXG4gICAgc3ViZ3JhcGggeW91ci1zZXJ2ZXIgW1wiWW91ciBTZXJ2ZXJcIl1cbiAgICAgICAgaW5fMjUoMjUpIC0tPiBzZXJ2ZXJcbiAgICAgICAgaW5fNDY1KDQ2NSkgLS0-IHNlcnZlclxuICAgICAgICBzZXJ2ZXIoKFwiZG9ja2VyLW1haWxzZXJ2ZXI8YnIvPmhlbGxvQHdvcmxkLmNvbVwiKSlcbiAgICAgICAgc2VydmVyIC0tLSBvdXRfMjUoMjUpXG4gICAgICAgIHNlcnZlciAtLS0gb3V0XzQ2NSg0NjUpXG4gICAgZW5kXG5cbiAgICB0aGlyZC1wYXJ0eShcIlRoaXJkLXBhcnR5PGJyLz4oc2VuZGluZyB5b3UgZW1haWwpXCIpIC0tLXxcIlJlY2VpdmUgZW1haWwgZm9yPGJyLz5oZWxsb0B3b3JsZC5jb21cInwgaW5fMjVcblxuICAgIHN1YmdyYXBoIGNsaWVudHMgW1wiQ2xpZW50cyAoTVVBKVwiXVxuICAgICAgICBtdWEtY2xpZW50KFRodW5kZXJiaXJkLDxici8-V2VibWFpbCw8YnIvPk11dHQsPGJyLz5ldGMpXG4gICAgICAgIG11YS1zZXJ2aWNlKEJhY2tlbmQgc29mdHdhcmU8YnIvPm9uIGFub3RoZXIgc2VydmVyKVxuICAgIGVuZFxuICAgIGNsaWVudHMgLS0tfFwiU2VuZCBlbWFpbCBhczxici8-aGVsbG9Ad29ybGQuY29tXCJ8IGluXzQ2NVxuXG4gICAgb3V0XzI1KDI1KSAtLT58XCJEaXJlY3Q8YnIvPkRlbGl2ZXJ5XCJ8IHRpbl8yNVxuICAgIG91dF80NjUoNDY1KSAtLT4gcmVsYXkoXCJNVEE8YnIvPlJlbGF5IFNlcnZlclwiKSAtLT4gdGluXzI1KDI1KVxuXG4gICAgc3ViZ3JhcGggdGhpcmQtcGFydHktc2VydmVyW1wiVGhpcmQtcGFydHkgU2VydmVyXCJdXG4gICAgICAgIHRoaXJkLXBhcnR5LW10YShcIk1UQTxici8-ZnJpZW5kQGV4YW1wbGUuY29tXCIpXG4gICAgICAgIHRpbl8yNSgyNSkgLS0-IHRoaXJkLXBhcnR5LW10YVxuICAgIGVuZCIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9 +[ref-clear-vs-plain]: https://www.denimgroup.com/resources/blog/2007/10/cleartext-vs-pl +[ref-port25-mandatory]: https://serverfault.com/questions/623692/is-it-still-wrong-to-require-starttls-on-incoming-smtp-messages +[ref-mermaid-live-smtp]: https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZmxvd2NoYXJ0IExSXG4gICAgc3ViZ3JhcGggeW91ci1zZXJ2ZXIgW1wiWW91ciBTZXJ2ZXJcIl1cbiAgICAgICAgaW5fMjUoMjUpIC0tPiBzZXJ2ZXJcbiAgICAgICAgaW5fNDY1KDQ2NSkgLS0-IHNlcnZlclxuICAgICAgICBzZXJ2ZXIoKFwiZG9ja2VyLW1haWxzZXJ2ZXI8YnIvPmhlbGxvQHdvcmxkLmNvbVwiKSlcbiAgICAgICAgc2VydmVyIC0tLSBvdXRfMjUoMjUpXG4gICAgICAgIHNlcnZlciAtLS0gb3V0XzQ2NSg0NjUpXG4gICAgZW5kXG5cbiAgICB0aGlyZC1wYXJ0eShcIlRoaXJkLXBhcnR5PGJyLz4oc2VuZGluZyB5b3UgZW1haWwpXCIpIC0tLXxcIlJlY2VpdmUgZW1haWwgZm9yPGJyLz5oZWxsb0B3b3JsZC5jb21cInwgaW5fMjVcblxuICAgIHN1YmdyYXBoIGNsaWVudHMgW1wiQ2xpZW50cyAoTVVBKVwiXVxuICAgICAgICBtdWEtY2xpZW50KFRodW5kZXJiaXJkLDxici8-V2VibWFpbCw8YnIvPk11dHQsPGJyLz5ldGMpXG4gICAgICAgIG11YS1zZXJ2aWNlKEJhY2tlbmQgc29mdHdhcmU8YnIvPm9uIGFub3RoZXIgc2VydmVyKVxuICAgIGVuZFxuICAgIGNsaWVudHMgLS0tfFwiU2VuZCBlbWFpbCBhczxici8-aGVsbG9Ad29ybGQuY29tXCJ8IGluXzQ2NVxuXG4gICAgb3V0XzI1KDI1KSAtLT58XCJEaXJlY3Q8YnIvPkRlbGl2ZXJ5XCJ8IHRpbl8yNVxuICAgIG91dF80NjUoNDY1KSAtLT4gcmVsYXkoXCJNVEE8YnIvPlJlbGF5IFNlcnZlclwiKSAtLT4gdGluXzI1KDI1KVxuXG4gICAgc3ViZ3JhcGggdGhpcmQtcGFydHktc2VydmVyW1wiVGhpcmQtcGFydHkgU2VydmVyXCJdXG4gICAgICAgIHRoaXJkLXBhcnR5LW10YShcIk1UQTxici8-ZnJpZW5kQGV4YW1wbGUuY29tXCIpXG4gICAgICAgIHRpbl8yNSgyNSkgLS0-IHRoaXJkLXBhcnR5LW10YVxuICAgIGVuZCIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9 +[rfc-8314]: https://tools.ietf.org/html/rfc8314 +[rfc-8314-s41]: https://tools.ietf.org/html/rfc8314#section-4.1 diff --git a/docs/content/config/setup.sh.md b/docs/content/config/setup.sh.md index e33388c7..8b43a02c 100644 --- a/docs/content/config/setup.sh.md +++ b/docs/content/config/setup.sh.md @@ -1,8 +1,14 @@ -[`setup.sh`](https://github.com/docker-mailserver/docker-mailserver/blob/master/setup.sh) is an administration script that helps with the most common tasks, including initial configuration. It is intented to be used from the host machine, _not_ from within your running container. +--- +title: Your best friend setup.sh +hide: + - toc # Hide Table of Contents for this page +--- + +[`setup.sh`][github-file-setupsh] is an administration script that helps with the most common tasks, including initial configuration. It is intented to be used from the host machine, _not_ from within your running container. The latest version of the script is included in the `docker-mailserver` repository. You may retrieve it at any time by running this command in your console: -``` BASH +```sh wget https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/master/setup.sh chmod a+x ./setup.sh ``` @@ -11,7 +17,7 @@ chmod a+x ./setup.sh Run `./setup.sh -h` and you'll get some usage information: -``` BASH +```bash setup.sh Bootstrapping Script Usage: ./setup.sh [-i IMAGE_NAME] [-c CONTAINER_NAME] [args] @@ -75,4 +81,6 @@ SUBCOMMANDS: ./setup.sh debug login help: Show this help dialogue -``` \ No newline at end of file +``` + +[github-file-setupsh]: https://github.com/docker-mailserver/docker-mailserver/blob/master/setup.sh diff --git a/docs/content/config/troubleshooting/debugging.md b/docs/content/config/troubleshooting/debugging.md index 102563ed..4d608847 100644 --- a/docs/content/config/troubleshooting/debugging.md +++ b/docs/content/config/troubleshooting/debugging.md @@ -1,54 +1,54 @@ -..todo.. - Please contribute more to help others debug this package +--- +title: 'Troubleshooting | Debugging' +--- -## Enable verbose debugging output +!!! info "Contributions Welcome!" + Please contribute your solutions to help the community :heart: -You may find it useful to enable the [DMS_DEBUG](https://github.com/tomav/docker-mailserver#dms_debug) environment variable. +## Enable Verbose Debugging Output -## Invalid username or Password +You may find it useful to enable the [`DMS_DEBUG`][github-file-env-dmsdebug] environment variable. +## Invalid Username or Password -1. Login Container +1. Shell into the container: -```bash -docker exec -it bash -``` + ```sh + docker exec -it bash + ``` -2. Check log files +2. Check log files in `/var/log/mail` could not find any mention of incorrect logins here neither in the dovecot logs. -`/var/log/mail` -could not find any mention of incorrect logins here -neither in the dovecot logs +3. Check the supervisors logs in `/var/log/supervisor`. You can find the logs for startup of fetchmail, postfix and others here - they might indicate problems during startup. -3. Check the supervisors logfiles -`/var/log/supervisor` -You can find the logs for startup of fetchmail, postfix and others here - they might indicate problems during startup - -4. Make sure you set your hostname to 'mail' or whatever you specified in your docker-compose.yml file or else your FQDN will be wrong +4. Make sure you set your hostname to `mail` or whatever you specified in your `docker-compose.yml` file or else your FQDN will be wrong. ## Installation Errors -1. During setup, if you get errors trying to edit files inside of the container, you likely need to install vi: +During setup, if you get errors trying to edit files inside of the container, you likely need to install `vi`: -``` bash +```sh sudo su -docker exec -it apt-get install -y vim +docker exec -it apt-get install -y vim ``` -## Testing Connection -I spent HOURS trying to debug "Connection Refused" and "Connection closed by foreign host" errors when trying to use telnet to troubleshoot my connection. I was also trying to connect from my email client (macOS mail) around the same time. Telnet had also worked earlier, so I was extremely confused as to why it suddenly stopped working. I stumbled upon fail2ban.log in my container. In short, when trying to get my macOS client working, I exceeded the number of failed login attempts and fail2ban put dovecot and postfix in jail! I got around it by whitelisting my ipaddresses (my ec2 instance and my local computer) -```bash +## Testing Connection + +I spent HOURS trying to debug "Connection Refused" and "Connection closed by foreign host" errors when trying to use telnet to troubleshoot my connection. I was also trying to connect from my email client (macOS mail) around the same time. Telnet had also worked earlier, so I was extremely confused as to why it suddenly stopped working. I stumbled upon `fail2ban.log` in my container. In short, when trying to get my macOS client working, I exceeded the number of failed login attempts and fail2ban put dovecot and postfix in jail! I got around it by whitelisting my ipaddresses (my ec2 instance and my local computer) + +```sh sudo su docker exec -ti mail bash cd /var/log cat fail2ban.log | grep dovecot -# Whitelist ip addresses: +# Whitelist IP addresses: fail2ban-client set dovecot addignoreip # Server fail2ban-client set postfix addignoreip fail2ban-client set dovecot addignoreip # Client fail2ban-client set postfix addignoreip -# this will delete the jails entirely - nuclear option +# This will delete the jails entirely - nuclear option fail2ban-client stop dovecot fail2ban-client stop postfix ``` @@ -59,4 +59,6 @@ Some hosting provides have a stealth block on port 25. Make sure to check with y Common hosting providers known to have this issue: - [Azure](https://docs.microsoft.com/en-us/azure/virtual-network/troubleshoot-outbound-smtp-connectivity) -- [AWS EC2](https://aws.amazon.com/premiumsupport/knowledge-center/ec2-port-25-throttle/) \ No newline at end of file +- [AWS EC2](https://aws.amazon.com/premiumsupport/knowledge-center/ec2-port-25-throttle/) + +[github-file-env-dmsdebug]: https://github.com/docker-mailserver/docker-mailserver/blob/master/ENVIRONMENT.md#dms_debug diff --git a/docs/content/config/troubleshooting/faq.md b/docs/content/config/troubleshooting/faq.md index 9ed73b52..f262f7a6 100644 --- a/docs/content/config/troubleshooting/faq.md +++ b/docs/content/config/troubleshooting/faq.md @@ -1,3 +1,7 @@ +--- +title: 'Troubleshooting | FAQ' +--- + ### What kind of database are you using? None! No database is required. Filesystem is the database. @@ -5,77 +9,91 @@ This image is based on config files that can be persisted using Docker volumes, ### Where are emails stored? -Mails are stored in `/var/mail/${domain}/${username}`. -You should use a [data volume container](https://medium.com/@ramangupta/why-docker-data-containers-are-good-589b3c6c749e#.uxyrp7xpu) for `/var/mail` to persist data. Otherwise, your data may be lost. +Mails are stored in `/var/mail/${domain}/${username}`. + +You should use a [data volume container](https://medium.com/@ramangupta/why-docker-data-containers-are-good-589b3c6c749e#.uxyrp7xpu) for `/var/mail` to persist data. + +Otherwise, your data may be lost. ### How to alter the running mailserver instance _without_ relaunching the container? -docker-mailserver aggregates multiple "sub-services", such as Postfix, Dovecot, Fail2ban, SpamAssasin, etc. In many cases, on may edit a sub-service's config and reload that very sub-service, without stopping and relaunching the whole mail server. +`docker-mailserver` aggregates multiple "sub-services", such as Postfix, Dovecot, Fail2ban, SpamAssasin, etc. In many cases, one may edit a sub-service's config and reload that very sub-service, without stopping and relaunching the whole mail server. In order to do so, you'll probably want to push your config updates to your server through a Docker volume, then restart the sub-service to apply your changes, using `supervisorctl`. For instance, after editing fail2ban's config: `supervisorctl restart fail2ban`. See [supervisorctl's documentation](http://supervisord.org/running.html#running-supervisorctl). -Tips: to add/update/delete an email account, there is no need to restart postfix/dovecot service inside the container after using setup.sh script. -For more information, see [issues/1639](https://github.com/tomav/docker-mailserver/issues/1639) +!!! tip + To add, update or delete an email account; there is no need to restart postfix / dovecot service inside the container after using `setup.sh` script. + + For more information, see [#1639][github-issue-1639]. ### How can I sync container with host date/time? Timezone? -Share the host's [`/etc/localtime`](https://www.freedesktop.org/software/systemd/man/localtime.html) with the docker-mailserver container, using a Docker volume: +Share the host's [`/etc/localtime`](https://www.freedesktop.org/software/systemd/man/localtime.html) with the `docker-mailserver` container, using a Docker volume: -``` +```yaml volumes: - /etc/localtime:/etc/localtime:ro ``` -(optional) Add one line to `.env` or `env-mailserver` to set timetzone for container, for example: -``` -TZ=Europe/Berlin -``` -check here for [`tz name list`](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) +!!! help "Optional" + Add one line to `.env` or `env-mailserver` to set timetzone for container, for example: + + ```env + TZ=Europe/Berlin + ``` + + Check here for the [`tz name list`](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) ### What is the file format? All files are using the Unix format with `LF` line endings. + Please do not use `CRLF`. ### What about backups? Assuming that you use `docker-compose` and a data volumes, you can backup your user mails like this: -``` +```sh docker run --rm -ti \ -v maildata:/var/mail \ -v mailstate:/var/mail-state \ -v /backup/mail:/backup \ alpine:3.2 \ - tar czf /backup/mail-`date +%y%m%d-%H%M%S`.tgz /var/mail /var/mail-state + tar czf "/backup/mail-$(date +%y%m%d-%H%M%S).tgz" /var/mail /var/mail-state find /backup/mail -type f -mtime +30 -exec rm -f {} \; ``` ### What about `mail-state` folder? + This folder consolidates all data generated by the server itself to persist when you upgrade. Example of data folder persisted: lib-amavis, lib-clamav, lib-fail2ban, lib-postfix, lib-postgrey, lib-spamassasin, lib-spamassassin, spool-postfix, ... ### How can I configure my email client? -Login are full email address (`user@domain.com`). - # imap - username: - password: - server: - imap port: 143 or 993 with ssl (recommended) - imap path prefix: INBOX +Login are full email address (`user@domain.com`). - # smtp - smtp port: 25 or 587 with ssl (recommended) - username: - password: +```properties +# imap +username: +password: +server: +imap port: 143 or 993 with ssl (recommended) +imap path prefix: INBOX + +# smtp +smtp port: 25 or 587 with ssl (recommended) +username: +password: +``` Please use `STARTTLS`. ### How can I manage my custom Spamassassin rules? + Antispam rules are managed in `config/spamassassin-rules.cf`. ### What are acceptable `SA_SPAM_SUBJECT` values? @@ -84,37 +102,40 @@ For no subject set `SA_SPAM_SUBJECT=undef`. For a trailing white-space subject one can define the whole variable with quotes in `docker-compose.yml`: -```docker-compose - environment: - - "SA_SPAM_SUBJECT=[SPAM] " +```yaml +environment: + - "SA_SPAM_SUBJECT=[SPAM] " ``` ### Can I use naked/bare domains (no host name)? -Yes, but not without some configuration changes. Normally it is assumed that docker-mailserver runs on a host with a name, so the fully qualified host name might be `mail.example.com` with the domain `example.com`. The MX records point to `mail.example.com`. To use a bare domain where the host name is `example.com` and the domain is also `example.com`, change mydestination from: +Yes, but not without some configuration changes. Normally it is assumed that `docker-mailserver` runs on a host with a name, so the fully qualified host name might be `mail.example.com` with the domain `example.com`. The MX records point to `mail.example.com`. -`mydestination = $myhostname, localhost.$mydomain, localhost` +To use a bare domain where the host name is `example.com` and the domain is also `example.com`, change `mydestination`: -To: +- From: `mydestination = $myhostname, localhost.$mydomain, localhost` +- To: `mydestination = localhost.$mydomain, localhost` -`mydestination = localhost.$mydomain, localhost` +Add the latter line to `config/postfix-main.cf`. That should work. Without that change there will be warnings in the logs like: -Add the latter line to config/postfix-main.cf. That should work. Without that change there will be warnings in the logs like: - -`warning: do not list domain example.com in BOTH mydestination and virtual_mailbox_domains` +```log +warning: do not list domain example.com in BOTH mydestination and virtual_mailbox_domains +``` Plus of course mail delivery fails. -### Why are Spamassassin x-headers not inserted into my sample.domain.com subdomain emails? +### Why are Spamassassin `x-headers` not inserted into my `sample.domain.com` subdomain emails? -In the default setup, amavis only applies Spamassassin x-headers into domains matching the template listed in the config file 05-domain_id (in the amavis defaults). The default setup @local_domains_acl = ( ".$mydomain" ); does not match subdomains. To match subdomains, you can override the @local_domains_acl directive in the amavis user config file 50-user with @local_domains_maps = ("."); to match any sort of domain template. +In the default setup, amavis only applies Spamassassin x-headers into domains matching the template listed in the config file (`05-domain_id` in the amavis defaults). -### How can I make SpamAssassin learn spam? +The default setup `@local_domains_acl = ( ".$mydomain" );` does not match subdomains. To match subdomains, you can override the `@local_domains_acl` directive in the amavis user config file `50-user` with `@local_domains_maps = (".");` to match any sort of domain template. + +### How can I make SpamAssassin better recognize spam? Put received spams in `.Junk/` imap folder using `SPAMASSASSIN_SPAM_TO_INBOX=1` and `MOVE_SPAM_TO_JUNK=1` and add a _user_ cron like the following: -``` -# This assumes you're having `environment: ONE_DIR=1` in the env-mailserver, +```conf +# This assumes you're having `environment: ONE_DIR=1` in the `mailserver.env`, # with a consolidated config in `/var/mail-state` # # m h dom mon dow command @@ -122,11 +143,12 @@ Put received spams in `.Junk/` imap folder using `SPAMASSASSIN_SPAM_TO_INBOX=1` 0 2 * * * docker exec mail sa-learn --spam /var/mail/domain.com/username/.Junk --dbpath /var/mail-state/lib-amavis/.spamassassin ``` -If you run the server with docker-compose, you can leverage on docker configs and the mailserver's own cron. This is less problematic than the simple solution shown above, because it decouples the learning from the host on which the mailserver is running and avoids errors if the server is not running. +If you run the server with `docker-compose`, you can leverage on docker configs and the mailserver's own cron. This is less problematic than the simple solution shown above, because it decouples the learning from the host on which the mailserver is running and avoids errors if the server is not running. The following configuration works nicely: -create a _system_ cron file: +Create a _system_ cron file: + ```sh # in the docker-compose.yml root directory mkdir cron @@ -135,8 +157,9 @@ chown root:root cron/sa-learn chmod 0644 cron/sa-learn ``` -edit the system cron file `nano cron/sa-learn`, and set an appropriate configuration: -``` +Edit the system cron file `nano cron/sa-learn`, and set an appropriate configuration: + +```conf # This assumes you're having `environment: ONE_DIR=1` in the env-mailserver, # with a consolidated config in `/var/mail-state` # @@ -159,20 +182,19 @@ edit the system cron file `nano cron/sa-learn`, and set an appropriate configura 30 3 * * * root sa-learn --ham /var/mail/otherdomain.com/*/cur* --dbpath /var/mail-state/lib-amavis/.spamassassin ``` -with plain docker-compose: -```docker-compose -version: "2" +Then with plain `docker-compose`: +```yaml services: mail: image: tvial/docker-mailserver:latest - # ... volumes: - ./cron/sa-learn:/etc/cron.d/sa-learn ``` -with [docker swarm](https://docs.docker.com/engine/swarm/configs/): -```docker-compose +Or with [docker swarm](https://docs.docker.com/engine/swarm/configs/): + +```yaml version: "3.3" services: @@ -191,19 +213,25 @@ configs: With the default settings, Spamassassin will require 200 mails trained for spam (for example with the method explained above) and 200 mails trained for ham (using the same command as above but using `--ham` and providing it with some ham mails). Until you provided these 200+200 mails, Spamassasin will not take the learned mails into account. For further reference, see the [Spamassassin Wiki](https://wiki.apache.org/spamassassin/BayesNotWorking). ### How can I configure a catch-all? + Considering you want to redirect all incoming e-mails for the domain `domain.tld` to `user1@domain.tld`, add the following line to `config/postfix-virtual.cf`: -``` + +```cf @domain.tld user1@domain.tld ``` -### How can I delete all the e-mails for a specific user? +### How can I delete all the emails for a specific user? + First of all, create a special alias named `devnull` by editing `config/postfix-aliases.cf`: + +```cf +devnull: /dev/null ``` -devnull: /dev/null -``` + Considering you want to delete all the e-mails received for `baduser@domain.tld`, add the following line to `config/postfix-virtual.cf`: -``` -baduser@domain.tld devnull + +```cf +baduser@domain.tld devnull ``` ### How do I have more control about what SPAMASSASIN is filtering? @@ -211,24 +239,26 @@ baduser@domain.tld devnull By default, SPAM and INFECTED emails are put to a quarantine which is not very straight forward to access. Several config settings are affecting this behavior: First, make sure you have the proper thresholds set: -``` + +```conf SA_TAG=-100000.0 SA_TAG2=3.75 SA_KILL=100000.0 -``` - -The very negative vaue in `SA_TAG` makes sure, that all emails have the Spamassasin headers included. -`SA_TAG2` is the actual threshold to set the YES/NO flag for spam detection. -`SA_KILL` needs to be very high, to make sure nothing is bounced at all (`SA_KILL` superseeds `SPAMASSASSIN_SPAM_TO_INBOX`) - -Make sure everything (including SPAM) is delivered to the inbox and not quarantined. ``` + +- The very negative vaue in `SA_TAG` makes sure, that all emails have the Spamassasin headers included. +- `SA_TAG2` is the actual threshold to set the YES/NO flag for spam detection. +- `SA_KILL` needs to be very high, to make sure nothing is bounced at all (`SA_KILL` superseeds `SPAMASSASSIN_SPAM_TO_INBOX`) + +Make sure everything (including SPAM) is delivered to the inbox and not quarantined: + +```conf SPAMASSASSIN_SPAM_TO_INBOX=1 ``` -Use `MOVE_SPAM_TO_JUNK=1` or create a sieve script which puts spam to the Junk folder. +Use `MOVE_SPAM_TO_JUNK=1` or create a sieve script which puts spam to the Junk folder: -``` +```sieve require ["comparator-i;ascii-numeric","relational","fileinto"]; if header :contains "X-Spam-Flag" "YES" { @@ -241,49 +271,59 @@ if header :contains "X-Spam-Flag" "YES" { ``` Create a dedicated mailbox for emails which are infected/bad header and everything amavis is blocking by default and put its address into `config/amavis.cf` -``` + +```cf $clean_quarantine_to = "amavis\@domain.com"; $virus_quarantine_to = "amavis\@domain.com"; $banned_quarantine_to = "amavis\@domain.com"; $bad_header_quarantine_to = "amavis\@domain.com"; $spam_quarantine_to = "amavis\@domain.com"; - ``` ### What kind of SSL certificates can I use? -You can use the same certificates you use with another mail server. + +You can use the same certificates you use with another mail server. + The only thing is that we provide a `self-signed` certificate tool and a `letsencrypt` certificate loader. -### I just moved from my old mail server but "it doesn't work". +### I just moved from my old mail server, but "it doesn't work"? + If this migration implies a DNS modification, be sure to wait for DNS propagation before opening an issue. -Few examples of symptoms can be found [here](https://github.com/tomav/docker-mailserver/issues/95) or [here](https://github.com/tomav/docker-mailserver/issues/97). -This could be related to a modification of your `MX` record, or the IP mapped to `mail.my-domain.tld`. Additionally, [validate your DNS configuration](https://intodns.com/). +Few examples of symptoms can be found [here][github-issue-95] or [here][github-issue-97]. + +This could be related to a modification of your `MX` record, or the IP mapped to `mail.my-domain.tld`. Additionally, [validate your DNS configuration](https://intodns.com/). If everything is OK regarding DNS, please provide [formatted logs](https://guides.github.com/features/mastering-markdown/) and config files. This will allow us to help you. If we're blind, we won't be able to do anything. -### Which system requirements needs my container to run `docker-mailserver` effectively? +### What system requirements are required to run `docker-mailserver` effectively? + 1 core and 1GB of RAM + swap partition is recommended to run `docker-mailserver` with clamav. Otherwise, it could work with 512M of RAM. -Please note that clamav can consume a lot of memory, as it reads the entire signature database into RAM. Current figure is about 850M and growing. If you get errors about clamav or amavis failing to allocate memory you need more RAM or more swap and of course docker must be allowed to use swap (not always the case). If you can't use swap at all you may need 3G RAM. +!!! note + Clamav can consume a lot of memory, as it reads the entire signature database into RAM. -### Is `docker-mailserver` running in a [rancher environment](http://rancher.com/rancher/)? + Current figure is about 850M and growing. If you get errors about clamav or amavis failing to allocate memory you need more RAM or more swap and of course docker must be allowed to use swap (not always the case). If you can't use swap at all you may need 3G RAM. -Yes, by Adding the Environment Variable `PERMIT_DOCKER: network`. +### Can `docker-mailserver` run in a [Rancher Environment](http://rancher.com/rancher/)? -**WARNING**: Adding the docker network's gateway to the list of trusted hosts, e.g. using the `network` or `connected-networks` option, can create an [**open relay**](https://en.wikipedia.org/wiki/Open_mail_relay), [for instance](https://github.com/tomav/docker-mailserver/issues/1405#issuecomment-590106498) if IPv6 is enabled on the host machine but not in Docker. ([#1405](https://github.com/tomav/docker-mailserver/issues/1405)) +Yes, by adding the environment variable `PERMIT_DOCKER: network`. -### How can I authenticate users with SMTP_ONLY? +!!! warning + Adding the docker network's gateway to the list of trusted hosts, e.g. using the `network` or `connected-networks` option, can create an [**open relay**](https://en.wikipedia.org/wiki/Open_mail_relay), for instance [if IPv6 is enabled on the host machine but not in Docker][github-issue-1405-comment]. -See https://github.com/tomav/docker-mailserver/issues/1247 for an example. +### How can I Authenticate Users with `SMTP_ONLY`? -*ToDo: Write a HowTo/UseCase/Tutorial about authentication with SMTP_ONLY.* +See [#1247][github-issue-1247] for an example. -### Common errors +!!! todo + Write a How-to / Use-Case / Tutorial about authentication with `SMTP_ONLY`. -``` +### Common Errors + +```log warning: connect to Milter service inet:localhost:8893: Connection refused # DMARC not running # => /etc/init.d/opendmarc restart @@ -299,66 +339,72 @@ mail amavis[1459]: (01459-01) (!!)AV: ALL VIRUS SCANNERS FAILED # Clamav is not running (not started or because you don't have enough memory) # => check requirements and/or start Clamav ``` -### Using behind proxy + +### How to use when behind a Proxy + Add to `/etc/postfix/main.cf` : -``` - -proxy_interfaces = X.X.X.X (your public IP) +```cf +proxy_interfaces = X.X.X.X (your public IP) ``` -### What about updates +### What About Updates You can of course use a own script or every now and then pull && stop && rm && start the images but there are tools available for this. -There is a page in the [Update and cleanup](Update-and-cleanup) wiki page that explains how to use it the docker way. +There is a page in the [Update and Cleanup][docs-maintenance] wiki page that explains how to use it the docker way. +### How to adjust settings with the `user-patches.sh` script -### Howto adjust settings with the user-patches.sh script Suppose you want to change a number of settings that are not listed as variables or add things to the server that are not included? This docker-container has a built-in way to do post-install processes. If you place a script called **user-patches.sh** in the config directory it will be run after all configuration files are set up, but before the postfix, amavis and other daemons are started. -The config file I am talking about is this volume in the yml file: - -`- ./config/:/tmp/docker-mailserver/` +The config file I am talking about is this volume in the yml file: `./config/:/tmp/docker-mailserver/` To place such a script you can just make it in the config dir, for instance like this: -`cd ./config` +```sh +cd ./config +touch user-patches.sh +chmod +x user-patches.sh +``` -`touch user-patches.sh` - -`chmod +x user-patches.sh` - -and then fill it with suitable code. +Then fill `user-patches.sh` with suitable code. If you want to test it you can move into the running container, run it and see if it does what you want. For instance: -`./setup.sh debug login # start shell in container` +```sh +# start shell in container +./setup.sh debug login -`cat /tmp/docker-mailserver/user-patches.sh #check the file` +# check the file +cat /tmp/docker-mailserver/user-patches.sh -`/tmp/docker-mailserver/user-patches.sh ## run the script` +# run the script +/tmp/docker-mailserver/user-patches.sh -`exit` +# exit the container shell back to the host shell +exit +``` -You can do a lot of things with such a script. You can find an example user-patches.sh script here: [example user-patches.sh script](https://github.com/hanscees/dockerscripts/blob/master/scripts/tomav-user-patches.sh) +You can do a lot of things with such a script. You can find an example `user-patches.sh` script here: [example `user-patches.sh` script][hanscees-userpatches] + +#### Special use-case - Patching the `supervisord` config -#### Special case patching supervisord config It seems worth noting, that the `user-patches.sh` gets executed trough supervisord. If you need to patch some supervisord config (e.g. `/etc/supervisor/conf.d/saslauth.conf`), the patching happens too late. + An easy workaround is to make the `user-patches.sh` reload the supervisord config after patching it: + ```bash #!/bin/bash sed -i 's/rimap -r/rimap/' /etc/supervisor/conf.d/saslauth.conf supervisorctl update ``` - - - - - - - - - +[docs-maintenance]: ../../advanced/maintenance/update-and-cleanup.md +[github-issue-95]: https://github.com/docker-mailserver/docker-mailserver/issues/95 +[github-issue-97]: https://github.com/docker-mailserver/docker-mailserver/issues/97 +[github-issue-1247]: https://github.com/docker-mailserver/docker-mailserver/issues/1247 +[github-issue-1405-comment]: https://github.com/docker-mailserver/docker-mailserver/issues/1405#issuecomment-590106498 +[github-issue-1639]: https://github.com/docker-mailserver/docker-mailserver/issues/1639 +[hanscees-userpatches]: https://github.com/hanscees/dockerscripts/blob/master/scripts/tomav-user-patches.sh diff --git a/docs/content/config/user-management/accounts.md b/docs/content/config/user-management/accounts.md index f69ad5a7..3bbda0ce 100644 --- a/docs/content/config/user-management/accounts.md +++ b/docs/content/config/user-management/accounts.md @@ -1,25 +1,37 @@ -## Adding a new account +--- +title: 'User Management | Accounts' +hide: + - toc # Hide Table of Contents for this page +--- -Users (email accounts) are managed in `/tmp/docker-mailserver/postfix-accounts.cf`. **_The best way to manage accounts is to use the reliable [setup.sh](https://github.com/docker-mailserver/docker-mailserver/wiki/Setup-docker-mailserver-using-the-script-setup.sh) script_**. Or you may directly add the _full_ email address and its encrypted password, separated by a pipe: +## Adding a New Account -``` INI +Users (email accounts) are managed in `/tmp/docker-mailserver/postfix-accounts.cf`. **_The best way to manage accounts is to use the reliable [`setup.sh`][docs-setupsh] script_**. Or you may directly add the _full_ email address and its encrypted password, separated by a pipe: + +```cf user1@domain.tld|{SHA512-CRYPT}$6$2YpW1nYtPBs2yLYS$z.5PGH1OEzsHHNhl3gJrc3D.YMZkvKw/vp.r5WIiwya6z7P/CQ9GDEJDr2G2V0cAfjDFeAQPUoopsuWPXLk3u1 user2@otherdomain.tld|{SHA512-CRYPT}$6$2YpW1nYtPBs2yLYS$z.5PGH1OEzsHHNhl3gJrc3D.YMZkvKw/vp.r5WIiwya6z7P/CQ9GDEJDr2G2V0cAfjDFeAQPUoopsuWPXLk3u1 -```` +``` In the example above, we've added 2 mail accounts for 2 different domains. Consequently, the mail server will automatically be configured for multi-domains. Therefore, to generate a new mail account data, directly from your docker host, you could for example run the following: -``` BASH +```sh docker run --rm \ -e MAIL_USER=user1@domain.tld \ -e MAIL_PASS=mypassword \ - -ti mailserver/docker-mailserver:latest \ + -it mailserver/docker-mailserver:latest \ /bin/sh -c 'echo "$MAIL_USER|$(doveadm pw -s SHA512-CRYPT -u $MAIL_USER -p $MAIL_PASS)"' >> config/postfix-accounts.cf ``` -You will then be asked for a password, and be given back the data for a new account entry, as text. To actually _add_ this new account, just copy all the output text in `config/postfix-accounts.cf` file of your running container. Please note the `doveadm pw` command lets you choose between several encryption schemes for the password. Use doveadm pw -l to get a list of the currently supported encryption schemes. +You will then be asked for a password, and be given back the data for a new account entry, as text. To actually _add_ this new account, just copy all the output text in `config/postfix-accounts.cf` file of your running container. -**Note**: Changes to the accounts list require a restart of the container, using `supervisord`. See [#552](https://github.com/docker-mailserver/docker-mailserver/issues/552). +!!! note + `doveadm pw` command lets you choose between several encryption schemes for the password. + + Use `doveadm pw -l` to get a list of the currently supported encryption schemes. + +!!! note + Changes to the accounts list require a restart of the container, using `supervisord`. See [#552][github-issue-552]. --- @@ -27,4 +39,7 @@ You will then be asked for a password, and be given back the data for a new acco - `imap-quota` is enabled and allow clients to query their mailbox usage. - When the mailbox is deleted, the quota directive is deleted as well. -- Dovecot quotas support LDAP, **but it's not implemented** (_PR are welcome!_). \ No newline at end of file +- Dovecot quotas support LDAP, **but it's not implemented** (_PR are welcome!_). + +[docs-setupsh]: ../setup.sh.md +[github-issue-552]: https://github.com/docker-mailserver/docker-mailserver/issues/552 diff --git a/docs/content/config/user-management/aliases.md b/docs/content/config/user-management/aliases.md index 95f4c81d..2393eea2 100644 --- a/docs/content/config/user-management/aliases.md +++ b/docs/content/config/user-management/aliases.md @@ -1,13 +1,17 @@ +--- +title: 'User Management | Aliases' +--- + Please read the [Postfix documentation on virtual aliases](http://www.postfix.org/VIRTUAL_README.html#virtual_alias) first. -You can use [setup.sh](https://github.com/docker-mailserver/docker-mailserver/wiki/Setup-docker-mailserver-using-the-script-setup.sh#alias) instead of creating and editing files manually. Aliases are managed in `/tmp/docker-mailserver/postfix-virtual.cf`. An alias is a _full_ email address that will either be: +You can use [`setup.sh`][docs-setupsh] instead of creating and editing files manually. Aliases are managed in `/tmp/docker-mailserver/postfix-virtual.cf`. An alias is a _full_ email address that will either be: * delivered to an existing account registered in `/tmp/docker-mailserver/postfix-accounts.cf` * redirected to one or more other email addresses Alias and target are space separated. An example on a server with domain.tld as its domain: -``` INI +```cf # Alias delivered to an existing account alias1@domain.tld user1@domain.tld @@ -15,20 +19,23 @@ alias1@domain.tld user1@domain.tld alias2@domain.tld external@gmail.com ``` -### Configuring regexp aliases +## Configuring RegExp Aliases -Additional regexp aliases can be configured by placing them into `config/postfix-regexp.cf`. The regexp aliases get evaluated after the virtual aliases (`/tmp/docker-mailserver/postfix-virtual.cf`). For example, the following `config/postfix-regexp.cf` causes all email to "test" users to be delivered to qa@example.com: +Additional regexp aliases can be configured by placing them into `config/postfix-regexp.cf`. The regexp aliases get evaluated after the virtual aliases (`/tmp/docker-mailserver/postfix-virtual.cf`). For example, the following `config/postfix-regexp.cf` causes all email to "test" users to be delivered to `qa@example.com`: -``` INI +```cf /^test[0-9][0-9]*@example.com/ qa@example.com ``` -### Address tags (extension delimiters) as an alternative to aliases +## Address Tags (Extension Delimiters) an Alternative to Aliases -Postfix supports so-called address tags, in the form of plus (+) tags - i.e. address+tag@example.com will end up at address@example.com. This is configured by default and the (configurable !) separator is set to `+`. For more info, see [How to use Address Tagging (user+tag@example.com) with Postfix](https://www.stevejenkins.com/blog/2011/03/how-to-use-address-tagging-usertagexample-com-with-postfix/) and the [official documentation](http://www.postfix.org/postconf.5.html#recipient_delimiter). +Postfix supports so-called address tags, in the form of plus (+) tags - i.e. address+tag@example.com will end up at address@example.com. This is configured by default and the (configurable !) separator is set to `+`. For more info, see [How to use Address Tagging (`user+tag@example.com`) with Postfix](https://www.stevejenkins.com/blog/2011/03/how-to-use-address-tagging-usertagexample-com-with-postfix/) and the [official documentation](http://www.postfix.org/postconf.5.html#recipient_delimiter). -Note that if you do decide to change the configurable separator, you must add the same line to *both* `config/postfix-main.cf` and `config/dovecot.cf`, because Dovecot is acting as the delivery agent. For example, to switch to `-`, add: +!!! note + If you do decide to change the configurable separator, you must add the same line to *both* `config/postfix-main.cf` and `config/dovecot.cf`, because Dovecot is acting as the delivery agent. For example, to switch to `-`, add: -``` INI +```cf recipient_delimiter = - ``` + +[docs-setupsh]: ../setup.sh.md diff --git a/docs/content/index.md b/docs/content/index.md index ef77024d..942be6f8 100644 --- a/docs/content/index.md +++ b/docs/content/index.md @@ -1,11 +1,26 @@ -### Welcome to the extended documentation for docker-mailserver! +--- +title: Home +--- -Please first have a look at the [`README.md`](https://github.com/docker-mailserver/docker-mailserver/blob/master/README.md) to setup and configure this server. This wiki provides you with advanced configuration, detailed examples, hints - see navigation on the right side. +# Welcome to the Extended Documentation for `docker-mailserver`! -#### To get you started +Please first have a look at the [`README.md`][github-file-readme] to setup and configure this server. -1. The script `setup.sh` is supplied with this project. It supports you in **configuring and administrating** your server. Information on how to get it and how to use it is available [on a dedicated page](https://github.com/docker-mailserver/docker-mailserver/wiki/setup.sh). +This wiki provides you with advanced configuration, detailed examples, and hints. + +## Getting Started + +1. The script [`setup.sh`][github-file-setupsh] is supplied with this project. It supports you in **configuring and administrating** your server. Information on how to get it and how to use it is available [on a dedicated page][docs-setupsh]. 2. Be aware that advanced tasks may still require tweaking environment variables, reading through documentation and sometimes inspecting your running container for debugging purposes. After all, a mail server is a complex arrangement of various programs. -3. A list of all configuration options is provided in [`ENVIRONMENT.md`](https://github.com/docker-mailserver/docker-mailserver/blob/master/ENVIRONMENT.md). The [`README.md`](https://github.com/docker-mailserver/docker-mailserver/blob/master/REEADME.md) is a good starting point to understand what this image is capable of. -4. A list of all optional and automatically created configuration files and directories is available [on the dedicated page](https://github.com/docker-mailserver/docker-mailserver/wiki/List-of-optional-config-files-&-directories). -5. See the [FAQ](https://github.com/docker-mailserver/docker-mailserver/wiki/FAQ-and-Tips) for some more tips! +3. A list of all configuration options is provided in [`ENVIRONMENT.md`][github-file-env]. The [`README.md`][github-file-readme] is a good starting point to understand what this image is capable of. +4. A list of all optional and automatically created configuration files and directories is available [on the dedicated page][docs-optionalconfig]. + +!!! tip + See the [FAQ][docs-faq] for some more tips! + +[docs-faq]: ./config/troubleshooting/faq.md +[docs-optionalconfig]: ./advanced/optional-config.md +[docs-setupsh]: ./config/setup.sh.md +[github-file-readme]: https://github.com/docker-mailserver/docker-mailserver/blob/master/README.md +[github-file-env]: https://github.com/docker-mailserver/docker-mailserver/blob/master/ENVIRONMENT.md +[github-file-setupsh]: https://github.com/docker-mailserver/docker-mailserver/blob/master/setup.sh diff --git a/docs/content/introduction.md b/docs/content/introduction.md index feadc837..66d58f14 100644 --- a/docs/content/introduction.md +++ b/docs/content/introduction.md @@ -1,11 +1,18 @@ -What is a mail server and how does it perform its duty? +--- +title: An Introduction to Mail Servers +--- + +# An Introduction to Mail Servers + +What is a mail server and how does it perform its duty? + Here's an introduction to the field that covers everything you need to know to get started with `docker-mailserver`. -## Anatomy of a mail server +## Anatomy of a Mail Server -A mail server is only a part of a [client-server relationship](https://en.wikipedia.org/wiki/Client%E2%80%93server_model) aimed at exchanging information in the form of [emails](https://en.wikipedia.org/wiki/Email). Exchanging emails requires using specific means (programs and protocols). +A mail server is only a part of a [client-server relationship][wikipedia-clientserver] aimed at exchanging information in the form of [emails][wikipedia-email]. Exchanging emails requires using specific means (programs and protocols). -`docker-mailserver` provides you with the server portion, whereas the client can be anything from a terminal via text-based software (eg. [Mutt](https://en.wikipedia.org/wiki/Mutt_(email_client))) to a fully-fledged desktop application (eg. [Mozilla Thunderbird](https://en.wikipedia.org/wiki/Mozilla_Thunderbird), [Microsoft Outlook](https://en.wikipedia.org/wiki/Microsoft_Outlook)…), to a web interface, etc. +`docker-mailserver` provides you with the server portion, whereas the client can be anything from a terminal via text-based software (eg. [Mutt][software-mutt]) to a fully-fledged desktop application (eg. [Mozilla Thunderbird][software-thunderbird], [Microsoft Outlook][software-outlook]…), to a web interface, etc. Unlike the client-side where usually a single program is used to perform retrieval and viewing of emails, the server-side is composed of many specialized components. The mail server is capable of accepting, forwarding, delivering, storing and overall exchanging messages, but each one of those tasks is actually handled by a specific piece of software. All of these "agents" must be integrated with one another for the exchange to take place. @@ -13,11 +20,11 @@ Unlike the client-side where usually a single program is used to perform retriev ## Components -The following components are required to create a [complete delivery chain](https://en.wikipedia.org/wiki/Email_agent_(infrastructure)): +The following components are required to create a [complete delivery chain][wikipedia-emailagent]: -- MUA: a [Mail User Agent](https://en.wikipedia.org/wiki/Email_client) is basically any client/program capable of sending emails to arbitrary mail servers; while also capable of fetching emails from mail servers for presenting them to the end users. -- MTA: a [Mail Transfer Agent](https://en.wikipedia.org/wiki/Message_transfer_agent) is the so-called "mail server" as seen from the MUA's perspective. It's a piece of software dedicated to accepting submitted emails, then forwarding them-where exactly will depend on an email's final destination. If the receiving MTA is responsible for the hostname the email is sent to, then an MTA is to forward that email to an MDA (see below). Otherwise, it is to transfer (ie. forward, relay) to another MTA, "closer" to the email's final destination. -- MDA: a [Mail Delivery Agent](https://en.wikipedia.org/wiki/Mail_delivery_agent) is responsible for accepting emails from an MTA and dropping them into their recipients' mailboxes, whichever the form. +- MUA: a [Mail User Agent][wikipedia-mua] is basically any client/program capable of sending emails to arbitrary mail servers; while also capable of fetching emails from mail servers for presenting them to the end users. +- MTA: a [Mail Transfer Agent][wikipedia-mta] is the so-called "mail server" as seen from the MUA's perspective. It's a piece of software dedicated to accepting submitted emails, then forwarding them-where exactly will depend on an email's final destination. If the receiving MTA is responsible for the hostname the email is sent to, then an MTA is to forward that email to an MDA (see below). Otherwise, it is to transfer (ie. forward, relay) to another MTA, "closer" to the email's final destination. +- MDA: a [Mail Delivery Agent][wikipedia-mda] is responsible for accepting emails from an MTA and dropping them into their recipients' mailboxes, whichever the form. Here's a schematic view of mail delivery: @@ -60,7 +67,7 @@ For instance, Postfix is both an SMTP server (accepting emails) and a relaying M The exact relationship between all the components and their respective (sometimes shared) responsibilities is beyond the scope of this document. Please explore this wiki & the web to get more insights about `docker-mailserver`'s toolchain. -## About security & ports +## About Security & Ports In the previous section, different components were outlined. Each one of those is responsible for a specific task, it has a specific purpose. @@ -70,7 +77,7 @@ Three main purposes exist when it comes to exchanging emails: - _Transfer_ (aka. _Relay_): for an MTA, the act of sending actual email data over the network, toward another MTA (server) closer to the final destination (where an MTA will forward data to an MDA). - _Retrieval_: for a MUA (client), the act of fetching actual email data over the network, from an MDA. -Postfix handles Submission (and might handle Relay), whereas Dovecot handles Retrieval. They both need to be accessible by MUAs in order to act as servers, therefore they expose public endpoints on specific TCP ports (see. [_Understanding the ports_](https://github.com/tomav/docker-mailserver/wiki/Understanding-the-ports) for more details). Those endpoints _may_ be secured, using an encryption scheme and TLS certificates. +Postfix handles Submission (and might handle Relay), whereas Dovecot handles Retrieval. They both need to be accessible by MUAs in order to act as servers, therefore they expose public endpoints on specific TCP ports (see. [_Understanding the ports_][docs-understandports] for more details). Those endpoints _may_ be secured, using an encryption scheme and TLS certificates. When it comes to the specifics of email exchange, we have to look at protocols and ports enabled to support all the identified purposes. There are several valid options and they've been evolving overtime. @@ -100,9 +107,9 @@ Read on to expand your understanding and learn about `docker-mailserver`'s confi ### Submission - SMTP -For a MUA to send an email to an MTA, it needs to establish a connection with that server, then push data packets over a network that both the MUA (client) and the MTA (server) are connected to. The server implements the [SMTP](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol) protocol, which makes it capable of handling _Submission_. +For a MUA to send an email to an MTA, it needs to establish a connection with that server, then push data packets over a network that both the MUA (client) and the MTA (server) are connected to. The server implements the [SMTP][wikipedia-smtp] protocol, which makes it capable of handling _Submission_. -In the case of `docker-mailserver`, the MTA (SMTP server) is Postfix. The MUA (client) may vary, yet its Submission request is performed as [TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol) packets sent over the _public_ internet. This exchange of information may be secured in order to counter eavesdropping. +In the case of `docker-mailserver`, the MTA (SMTP server) is Postfix. The MUA (client) may vary, yet its Submission request is performed as [TCP][wikipedia-tcp] packets sent over the _public_ internet. This exchange of information may be secured in order to counter eavesdropping. #### Two kinds of Submission @@ -132,23 +139,24 @@ Me ---------------> ┤ ├ -----------------> ┊ ##### Outward Submission -The best practice as of 2020 when it comes to securing Outward Submission is to use _Implicit TLS connection via ESMTP on port 465_ (see [RFC 8314](https://tools.ietf.org/html/rfc8314)). Let's break it down. +The best practice as of 2020 when it comes to securing Outward Submission is to use _Implicit TLS connection via ESMTP on port 465_ (see [RFC 8314][rfc-8314]). Let's break it down. -- Implicit TLS means the server _enforces_ the client into using an encrypted TCP connection, using [TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security). With this kind of connection, the MUA _has_ to establish a TLS-encrypted connection from the get go (TLS is implied, hence the name "Implicit"). Any client attempting to either submit email in cleartext (unencrypted, not secure), or requesting a cleartext connection to be upgraded to a TLS-encrypted one using `STARTTLS`, is to be denied. Implicit TLS is sometimes called Enforced TLS for that reason. -- [ESMTP](https://en.wikipedia.org/wiki/ESMTP) is [SMTP](https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol) + extensions. It's the version of the SMTP protocol that most mail servers speak nowadays. For the purpose of this documentation, ESMTP and SMTP are synonymous. +- Implicit TLS means the server _enforces_ the client into using an encrypted TCP connection, using [TLS][wikipedia-tls]. With this kind of connection, the MUA _has_ to establish a TLS-encrypted connection from the get go (TLS is implied, hence the name "Implicit"). Any client attempting to either submit email in cleartext (unencrypted, not secure), or requesting a cleartext connection to be upgraded to a TLS-encrypted one using `STARTTLS`, is to be denied. Implicit TLS is sometimes called Enforced TLS for that reason. +- [ESMTP][wikipedia-esmtp] is [SMTP][wikipedia-smtp] + extensions. It's the version of the SMTP protocol that most mail servers speak nowadays. For the purpose of this documentation, ESMTP and SMTP are synonymous. - Port 465 is the reserved TCP port for Implicit TLS Submission (since 2018). There is actually a boisterous history to that ports usage, but let's keep it simple. -> Note: This Submission setup is sometimes refered to as [SMTPS](https://en.wikipedia.org/wiki/SMTPS). Long story short: this is incorrect and should be avoided. +!!! note + This Submission setup is sometimes refered to as [SMTPS][wikipedia-smtps]. Long story short: this is incorrect and should be avoided. -Although a very satisfactory setup, Implicit TLS on port 465 is somewhat "cutting edge". There exists another well established mail Submission setup that must be supported as well, SMTP+STARTTLS on port 587. It uses Explicit TLS: the client starts with a cleartext connection, then the server informs a TLS-encrypted "upgraded" connection may be established, and the client _may_ eventually decide to establish it prior to the Submission. Basically it's an opportunistic, opt-in TLS upgrade of the connection between the client and the server, at the client's discretion, using a mechanism known as [STARTTLS](https://en.wikipedia.org/wiki/Opportunistic_TLS) that both ends need to implement. +Although a very satisfactory setup, Implicit TLS on port 465 is somewhat "cutting edge". There exists another well established mail Submission setup that must be supported as well, SMTP+STARTTLS on port 587. It uses Explicit TLS: the client starts with a cleartext connection, then the server informs a TLS-encrypted "upgraded" connection may be established, and the client _may_ eventually decide to establish it prior to the Submission. Basically it's an opportunistic, opt-in TLS upgrade of the connection between the client and the server, at the client's discretion, using a mechanism known as [STARTTLS][wikipedia-starttls] that both ends need to implement. -In many implementations, the mail server doesn't enforce TLS encryption, for backwards compatibility. Clients are thus free to deny the TLS-upgrade proposal (or [misled by a hacker](https://security.stackexchange.com/questions/168998/what-happens-if-starttls-dropped-in-smtp) about STARTTLS not being available), and the server accepts unencrypted (cleartext) mail exchange, which poses a confidentiality threat and, to some extent, spam issues. [RFC 8314 (section 3.3)](https://tools.ietf.org/html/rfc8314) recommends for mail servers to support both Implicit and Explicit TLS for Submission, _and_ to enforce TLS-encryption on ports 587 (Explicit TLS) and 465 (Implicit TLS). That's exactly `docker-mailserver`'s default configuration: abiding by RFC 8314, it [enforces a strict (`encrypt`) STARTTLS policy](http://www.postfix.org/postconf.5.html#smtpd_tls_security_level), where a denied TLS upgrade terminates the connection thus (hopefully but at the client's discretion) preventing unencrypted (cleartext) Submission. +In many implementations, the mail server doesn't enforce TLS encryption, for backwards compatibility. Clients are thus free to deny the TLS-upgrade proposal (or [misled by a hacker](https://security.stackexchange.com/questions/168998/what-happens-if-starttls-dropped-in-smtp) about STARTTLS not being available), and the server accepts unencrypted (cleartext) mail exchange, which poses a confidentiality threat and, to some extent, spam issues. [RFC 8314 (section 3.3)][rfc-8314-s33] recommends for mail servers to support both Implicit and Explicit TLS for Submission, _and_ to enforce TLS-encryption on ports 587 (Explicit TLS) and 465 (Implicit TLS). That's exactly `docker-mailserver`'s default configuration: abiding by RFC 8314, it [enforces a strict (`encrypt`) STARTTLS policy](http://www.postfix.org/postconf.5.html#smtpd_tls_security_level), where a denied TLS upgrade terminates the connection thus (hopefully but at the client's discretion) preventing unencrypted (cleartext) Submission. - **`docker-mailserver`'s default configuration enables and _requires_ Explicit TLS (STARTTLS) on port 587 for Outward Submission.** - It does not enable Implicit TLS Outward Submission on port 465 by default. One may enable it through simple custom configuration, either as a replacement or (better!) supplementary mean of secure Submission. - It does not support old MUAs (clients) not supporting TLS encryption on ports 587/465 (those should perform Submission on port 25, more details below). One may relax that constraint through advanced custom configuration, for backwards compatibility. -A final Outward Submission setup exists and is akin SMTP+STARTTLS on port 587, but on port 25. That port has historically been reserved specifically for unencrypted (cleartext) mail exchange though, making STARTTLS a bit wrong to use. As is expected by [RFC 5321](https://tools.ietf.org/html/rfc5321), `docker-mailserver` uses port 25 for unencrypted Submission in order to support older clients, but most importantly for unencrypted Transfer/Relay between MTAs. +A final Outward Submission setup exists and is akin SMTP+STARTTLS on port 587, but on port 25. That port has historically been reserved specifically for unencrypted (cleartext) mail exchange though, making STARTTLS a bit wrong to use. As is expected by [RFC 5321][rfc-5321], `docker-mailserver` uses port 25 for unencrypted Submission in order to support older clients, but most importantly for unencrypted Transfer/Relay between MTAs. - **`docker-mailserver`'s default configuration also enables unencrypted (cleartext) on port 25 for Outward Submission.** - It does not enable Explicit TLS (STARTTLS) on port 25 by default. One may enable it through advanced custom configuration, either as a replacement (bad!) or as a supplementary mean of secure Outward Submission. @@ -176,13 +184,13 @@ Me -- STARTTLS ---> ┤(587) My MTA │ ┊ Third-part ### Retrieval - IMAP -A MUA willing to fetch an email from a mail server will most likely communicate with its [IMAP](https://en.wikipedia.org/wiki/IMAP) server. As with SMTP described earlier, communication will take place in the form of data packets exchanged over a network that both the client and the server are connected to. The IMAP protocol makes the server capable of handling _Retrieval_. +A MUA willing to fetch an email from a mail server will most likely communicate with its [IMAP][wikipedia-imap] server. As with SMTP described earlier, communication will take place in the form of data packets exchanged over a network that both the client and the server are connected to. The IMAP protocol makes the server capable of handling _Retrieval_. -In the case of `docker-mailserver`, the IMAP server is Dovecot. The MUA (client) may vary, yet its Retrieval request is performed as [TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol) packets sent over the _public_ internet. This exchange of information may be secured in order to counter eavesdropping. +In the case of `docker-mailserver`, the IMAP server is Dovecot. The MUA (client) may vary, yet its Retrieval request is performed as [TCP][wikipedia-tcp] packets sent over the _public_ internet. This exchange of information may be secured in order to counter eavesdropping. -Again, as with SMTP described earlier, the IMAP protocol may be secured with either Implicit TLS (aka. [IMAPS](https://en.wikipedia.org/wiki/IMAPS)/IMAP4S) or Explicit TLS (using STARTTLS). +Again, as with SMTP described earlier, the IMAP protocol may be secured with either Implicit TLS (aka. [IMAPS][wikipedia-imaps] / IMAP4S) or Explicit TLS (using STARTTLS). -The best practice as of 2020 is to enforce IMAPS on port 993, rather than IMAP+STARTTLS on port 143 (see [RFC 8314](https://tools.ietf.org/html/rfc8314)); yet the latter is usually provided for backwards compatibility. +The best practice as of 2020 is to enforce IMAPS on port 993, rather than IMAP+STARTTLS on port 143 (see [RFC 8314][rfc-8314]); yet the latter is usually provided for backwards compatibility. **`docker-mailserver`'s default configuration enables both Implicit and Explicit TLS for Retrievial, on ports 993 and 143 respectively.** @@ -190,7 +198,7 @@ The best practice as of 2020 is to enforce IMAPS on port 993, rather than IMAP+S Similarly to IMAP, the older POP3 protocol may be secured with either Implicit or Explicit TLS. -The best practice as of 2020 would be [POP3S](https://en.wikipedia.org/wiki/POP3S) on port 995, rather than [POP3](https://en.wikipedia.org/wiki/POP3)+STARTTLS on port 110 (see [RFC 8314](https://tools.ietf.org/html/rfc8314)). +The best practice as of 2020 would be [POP3S][wikipedia-pop3s] on port 995, rather than [POP3][wikipedia-pop3]+STARTTLS on port 110 (see [RFC 8314][rfc-8314]). **`docker-mailserver`'s default configuration disables POP3 altogether.** One should expect MUAs to use TLS-encrypted IMAP for Retrieval. @@ -199,16 +207,46 @@ The best practice as of 2020 would be [POP3S](https://en.wikipedia.org/wiki/POP3 As a _batteries included_ Docker image, `docker-mailserver` provides you with all the required components and a default configuration, to run a decent and secure mail server. One may then customize all aspects of its internal components. -- Simple customization is supported through [docker-compose configuration](https://github.com/tomav/docker-mailserver/blob/master/docker-compose.yml.dist) and the [env-mailserver](https://github.com/tomav/docker-mailserver/blob/master/env-mailserver.dist) configuration file. -- Advanced customization is supported through providing "monkey-patching" configuration files and/or [deriving your own image](https://github.com/tomav/docker-mailserver/blob/master/Dockerfile) from `docker-mailserver`'s upstream, for a complete control over how things run. + +- Simple customization is supported through [docker-compose configuration][github-file-compose] and the [env-mailserver][github-file-envmailserver] configuration file. +- Advanced customization is supported through providing "monkey-patching" configuration files and/or [deriving your own image][github-file-dockerfile] from `docker-mailserver`'s upstream, for a complete control over how things run. On the subject of security, one might consider `docker-mailserver`'s **default** configuration to _not_ be 100% secure: - it enables unencrypted traffic on port 25 - it enables Explicit TLS (STARTTLS) on port 587, instead of Implicit TLS on port 465 -We believe `docker-mailserver`'s default configuration to be a good middle ground: it goes slightly beyond "old" (1999) [RFC 2487](https://tools.ietf.org/html/rfc2487); and with developer friendly configuration settings, it makes it pretty easy to abide by the "newest" (2018) [RFC 8314](https://tools.ietf.org/html/rfc8314). +We believe `docker-mailserver`'s default configuration to be a good middle ground: it goes slightly beyond "old" (1999) [RFC 2487][rfc-2487]; and with developer friendly configuration settings, it makes it pretty easy to abide by the "newest" (2018) [RFC 8314][rfc-8314]. Eventually, it is up to _you_ deciding exactly what kind of transportation/encryption to use and/or enforce, and to customize your instance accordingly (with looser or stricter security). Be also aware that protocols and ports on your server can only go so far with security; third-party MTAs might relay your emails on insecure connections, man-in-the-middle attacks might still prove effective, etc. Advanced counter-measure such as DANE, MTA-STS and/or full body encryption (eg. PGP) should be considered as well for increased confidentiality, but ideally without compromising backwards compatibility so as to not block emails. -The [README](https://github.com/tomav/docker-mailserver) is the best starting point in configuring and running your mail server. You may then explore this wiki to cover additional topics, including but not limited to, security. \ No newline at end of file +The [README][github-file-readme] is the best starting point in configuring and running your mail server. You may then explore this wiki to cover additional topics, including but not limited to, security. + +[docs-understandports]: ./config/security/understanding-the-ports.md +[github-file-compose]: https://github.com/docker-mailserver/docker-mailserver/blob/master/docker-compose.yml +[github-file-envmailserver]: https://github.com/docker-mailserver/docker-mailserver/blob/master/mailserver.env +[github-file-dockerfile]: https://github.com/docker-mailserver/docker-mailserver/blob/master/Dockerfile +[github-file-readme]: https://github.com/docker-mailserver/docker-mailserver/blob/master/README.md +[rfc-2487]: https://tools.ietf.org/html/rfc2487 +[rfc-5321]: https://tools.ietf.org/html/rfc5321 +[rfc-8314]: https://tools.ietf.org/html/rfc8314 +[rfc-8314-s33]: https://tools.ietf.org/html/rfc8314#section-3.3 +[software-mutt]: https://en.wikipedia.org/wiki/Mutt_(email_client) +[software-outlook]: https://en.wikipedia.org/wiki/Microsoft_Outlook +[software-thunderbird]: https://en.wikipedia.org/wiki/Mozilla_Thunderbird +[wikipedia-clientserver]: https://en.wikipedia.org/wiki/Client%E2%80%93server_model +[wikipedia-email]: https://en.wikipedia.org/wiki/Email +[wikipedia-emailagent]: https://en.wikipedia.org/wiki/Email_agent_(infrastructure) +[wikipedia-esmtp]: https://en.wikipedia.org/wiki/ESMTP +[wikipedia-imap]: https://en.wikipedia.org/wiki/IMAP +[wikipedia-imaps]: https://en.wikipedia.org/wiki/IMAPS +[wikipedia-mda]: https://en.wikipedia.org/wiki/Mail_delivery_agent +[wikipedia-mta]: https://en.wikipedia.org/wiki/Message_transfer_agent +[wikipedia-mua]: https://en.wikipedia.org/wiki/Email_client +[wikipedia-pop3]: https://en.wikipedia.org/wiki/POP3 +[wikipedia-pop3s]: https://en.wikipedia.org/wiki/POP3S +[wikipedia-smtp]: https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol +[wikipedia-smtps]: https://en.wikipedia.org/wiki/SMTPS +[wikipedia-starttls]: https://en.wikipedia.org/wiki/Opportunistic_TLS +[wikipedia-tcp]: https://en.wikipedia.org/wiki/Transmission_Control_Protocol +[wikipedia-tls]: https://en.wikipedia.org/wiki/Transport_Layer_Security diff --git a/docs/content/tutorials/installation-examples.md b/docs/content/tutorials/installation-examples.md index 9ca64f1c..cf8ecb08 100644 --- a/docs/content/tutorials/installation-examples.md +++ b/docs/content/tutorials/installation-examples.md @@ -1,261 +1,268 @@ -## Building a simple mailserver +--- +title: 'Tutorials | Installation Examples' +--- -**WARNING**: Adding the docker network's gateway to the list of trusted hosts, e.g. using the `network` or `connected-networks` option, can create an [**open relay**](https://en.wikipedia.org/wiki/Open_mail_relay), [for instance](https://github.com/tomav/docker-mailserver/issues/1405#issuecomment-590106498) if IPv6 is enabled on the host machine but not in Docker. ([#1405](https://github.com/tomav/docker-mailserver/issues/1405)) +## Building a Simple Mailserver + +!!! warning + Adding the docker network's gateway to the list of trusted hosts, e.g. using the `network` or `connected-networks` option, can create an [**open relay**](https://en.wikipedia.org/wiki/Open_mail_relay), for instance [if IPv6 is enabled on the host machine but not in Docker][github-issue-1405-comment]. We are going to use this docker based mailserver: - First create a directory for the mailserver and get the setup script: - ``` - mkdir -p /var/ds/mail.example.org - cd /var/ds/mail.example.org/ - curl -o setup.sh \ - https://raw.githubusercontent.com/tomav/docker-mailserver/master/setup.sh - chmod a+x ./setup.sh - ``` + ```sh + mkdir -p /var/ds/mail.example.org + cd /var/ds/mail.example.org/ + + curl -o setup.sh \ + https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/master/setup.sh + chmod a+x ./setup.sh + ``` - Create the file `docker-compose.yml` with a content like this: - ``` - version: '2' - services: - mail: - image: tvial/docker-mailserver:latest - hostname: mail - domainname: example.org - container_name: mail - ports: - - "25:25" - - "587:587" - - "465:465" - volumes: - - ./data/:/var/mail/ - - ./state/:/var/mail-state/ - - ./config/:/tmp/docker-mailserver/ - - /var/ds/wsproxy/letsencrypt/:/etc/letsencrypt/ - environment: - - PERMIT_DOCKER=network - - SSL_TYPE=letsencrypt - - ONE_DIR=1 - - DMS_DEBUG=1 - - SPOOF_PROTECTION=0 - - REPORT_RECIPIENT=1 - - ENABLE_SPAMASSASSIN=0 - - ENABLE_CLAMAV=0 - - ENABLE_FAIL2BAN=1 - - ENABLE_POSTGREY=0 - cap_add: - - NET_ADMIN - - SYS_PTRACE - ``` - - For more details about the environment variables that can be used, - and their meaning and possible values, check also these: - - https://github.com/tomav/docker-mailserver#environment-variables - - https://github.com/tomav/docker-mailserver/blob/master/.env.dist - - Make sure to set the proper `domainname` that you will use for the - emails. We forward only SMTP ports (not POP3 and IMAP) because we - are not interested in accessing the mailserver directly (from a - client). We also use these settings: - - `PERMIT_DOCKER=network` because we want to send emails from other - docker containers. - - `SSL_TYPE=letsencrypt` because we will manage SSL certificates - with letsencrypt. + ```yaml + version: '2' -- We need to open these ports on the firewall: `25`, `587`, `465` - ``` - ufw allow 25 - ufw allow 587 - ufw allow 465 - ``` - On your server you may have to do it differently. + services: + mail: + image: tvial/docker-mailserver:latest + hostname: mail + domainname: example.org + container_name: mail + ports: + - "25:25" + - "587:587" + - "465:465" + volumes: + - ./data/:/var/mail/ + - ./state/:/var/mail-state/ + - ./config/:/tmp/docker-mailserver/ + - /var/ds/wsproxy/letsencrypt/:/etc/letsencrypt/ + environment: + - PERMIT_DOCKER=network + - SSL_TYPE=letsencrypt + - ONE_DIR=1 + - DMS_DEBUG=1 + - SPOOF_PROTECTION=0 + - REPORT_RECIPIENT=1 + - ENABLE_SPAMASSASSIN=0 + - ENABLE_CLAMAV=0 + - ENABLE_FAIL2BAN=1 + - ENABLE_POSTGREY=0 + cap_add: + - NET_ADMIN + - SYS_PTRACE + ``` -- Pull the docker image: - ``` - docker pull tvial/docker-mailserver:latest - ``` + For more details about the environment variables that can be used, and their meaning and possible values, check also these: -- Now generate the DKIM keys with `./setup.sh config dkim` and copy - the content of the file `config/opendkim/keys/domain.tld/mail.txt` - on the domain zone configuration at the DNS server. I use - [bind9](https://github.com/docker-scripts/bind9) for managing my - domains, so I just paste it on `example.org.db`: - ``` - mail._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; " - "p=MIIBIjANBgkqhkiG9w0BAQEFACAQ8AMIIBCgKCAQEAaH5KuPYPSF3Ppkt466BDMAFGOA4mgqn4oPjZ5BbFlYA9l5jU3bgzRj3l6/Q1n5a9lQs5fNZ7A/HtY0aMvs3nGE4oi+LTejt1jblMhV/OfJyRCunQBIGp0s8G9kIUBzyKJpDayk2+KJSJt/lxL9Iiy0DE5hIv62ZPP6AaTdHBAsJosLFeAzuLFHQ6USyQRojefqFQtgYqWQ2JiZQ3" - "iqq3bD/BVlwKRp5gH6TEYEmx8EBJUuDxrJhkWRUk2VDl1fqhVBy8A9O7Ah+85nMrlOHIFsTaYo9o6+cDJ6t1i6G1gu+bZD0d3/3bqGLPBQV9LyEL1Rona5V7TJBGg099NQkTz1IwIDAQAB" ) ; ----- DKIM key mail for example.org + - [Environtment Variables][github-file-env] + - [`mailserver.env` file][github-file-dotenv] - ``` + Make sure to set the proper `domainname` that you will use for the emails. We forward only SMTP ports (not POP3 and IMAP) because we are not interested in accessing the mailserver directly (from a client). We also use these settings: + + - `PERMIT_DOCKER=network` because we want to send emails from other docker containers. + - `SSL_TYPE=letsencrypt` because we will manage SSL certificates with letsencrypt. + +- We need to open ports `25`, `587` and `465` on the firewall: + + ```sh + ufw allow 25 + ufw allow 587 + ufw allow 465 + ``` + + On your server you may have to do it differently. + +- Pull the docker image: `docker pull tvial/docker-mailserver:latest` + +- Now generate the DKIM keys with `./setup.sh config dkim` and copy the content of the file `config/opendkim/keys/domain.tld/mail.txt` on the domain zone configuration at the DNS server. I use [bind9](https://github.com/docker-scripts/bind9) for managing my domains, so I just paste it on `example.org.db`: + + ```txt + mail._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; " + "p=MIIBIjANBgkqhkiG9w0BAQEFACAQ8AMIIBCgKCAQEAaH5KuPYPSF3Ppkt466BDMAFGOA4mgqn4oPjZ5BbFlYA9l5jU3bgzRj3l6/Q1n5a9lQs5fNZ7A/HtY0aMvs3nGE4oi+LTejt1jblMhV/OfJyRCunQBIGp0s8G9kIUBzyKJpDayk2+KJSJt/lxL9Iiy0DE5hIv62ZPP6AaTdHBAsJosLFeAzuLFHQ6USyQRojefqFQtgYqWQ2JiZQ3" + "iqq3bD/BVlwKRp5gH6TEYEmx8EBJUuDxrJhkWRUk2VDl1fqhVBy8A9O7Ah+85nMrlOHIFsTaYo9o6+cDJ6t1i6G1gu+bZD0d3/3bqGLPBQV9LyEL1Rona5V7TJBGg099NQkTz1IwIDAQAB" ) ; ----- DKIM key mail for example.org + ``` - Add these configurations as well on the same file on the DNS server: - ``` - mail IN A 10.11.12.13 - - ; mailservers for example.org - 3600 IN MX 1 mail.example.org. - - ; Add SPF record - IN TXT "v=spf1 mx ~all" - ``` - Then don't forget to change the serial number and to restart the service. -- Get an SSL certificate from letsencrypt. I use - [wsproxy](https://github.com/docker-scripts/wsproxy) for managing - SSL letsencrypt certificates of my domains: - ``` - cd /var/ds/wsproxy - ds domains-add mail mail.example.org - ds get-ssl-cert myemail@gmail.com mail.example.org --test - ds get-ssl-cert myemail@gmail.com mail.example.org - ``` - Now the certificates will be available on - `/var/ds/wsproxy/letsencrypt/live/mail.example.org`. + ```txt + mail IN A 10.11.12.13 + + ; mailservers for example.org + 3600 IN MX 1 mail.example.org. + + ; Add SPF record + IN TXT "v=spf1 mx ~all" + ``` + + Then don't forget to change the serial number and to restart the service. + +- Get an SSL certificate from letsencrypt. I use [wsproxy](https://github.com/docker-scripts/wsproxy) for managing SSL letsencrypt certificates of my domains: + + ```sh + cd /var/ds/wsproxy + ds domains-add mail mail.example.org + ds get-ssl-cert myemail@gmail.com mail.example.org --test + ds get-ssl-cert myemail@gmail.com mail.example.org + ``` + + Now the certificates will be available on `/var/ds/wsproxy/letsencrypt/live/mail.example.org`. - Start the mailserver and check for any errors: - ``` - apt install docker-compose - docker-compose up mail - ``` + + ```sh + apt install docker-compose + docker-compose up mail + ``` - Create email accounts and aliases with `SPOOF_PROTECTION=0`: - ``` - ./setup.sh email add admin@example.org passwd123 - ./setup.sh email add info@example.org passwd123 - ./setup.sh alias add admin@example.org myemail@gmail.com - ./setup.sh alias add info@example.org myemail@gmail.com - ./setup.sh email list - ./setup.sh alias list - ``` - Aliases make sure that any email that comes to these accounts is - forwarded to my real email address, so that I don't need to use - POP3/IMAP in order to get these messages. Also no anti-spam and - anti-virus software is needed, making the mailserver lighter. + + ```sh + ./setup.sh email add admin@example.org passwd123 + ./setup.sh email add info@example.org passwd123 + ./setup.sh alias add admin@example.org myemail@gmail.com + ./setup.sh alias add info@example.org myemail@gmail.com + ./setup.sh email list + ./setup.sh alias list + ``` + + Aliases make sure that any email that comes to these accounts is forwarded to my real email address, so that I don't need to use POP3/IMAP in order to get these messages. Also no anti-spam and anti-virus software is needed, making the mailserver lighter. - Or create email accounts and aliases with `SPOOF_PROTECTION=1`: - ``` - ./setup.sh email add admin.gmail@example.org passwd123 - ./setup.sh email add info.gmail@example.org passwd123 - ./setup.sh alias add admin@example.org admin.gmail@example.org - ./setup.sh alias add info@example.org info.gmail@example.org - ./setup.sh alias add admin.gmail@example.org myemail@gmail.com - ./setup.sh alias add info.gmail@example.org myemail@gmail.com - ./setup.sh email list - ./setup.sh alias list - ``` - This extra step is required to avoid the `553 5.7.1 Sender address rejected: not owned by user` error (the account used for setting up gmail is `admin.gmail@example.org` and `info.gmail@example.org` ) - -- Send some test emails to these addresses and make other tests. Then - stop the container with `Ctrl+c` and start it again as a daemon: - `docker-compose up -d mail`. -- Now save on Moodle configuration the SMTP settings and test by - trying to send some messages to other users: - - **SMTP hosts**: `mail.example.org:465` - - **SMTP security**: `SSL` - - **SMTP username**: `info@example.org` - - **SMTP password**: `passwd123` + ```sh + ./setup.sh email add admin.gmail@example.org passwd123 + ./setup.sh email add info.gmail@example.org passwd123 + ./setup.sh alias add admin@example.org admin.gmail@example.org + ./setup.sh alias add info@example.org info.gmail@example.org + ./setup.sh alias add admin.gmail@example.org myemail@gmail.com + ./setup.sh alias add info.gmail@example.org myemail@gmail.com + ./setup.sh email list + ./setup.sh alias list + ``` + + This extra step is required to avoid the `553 5.7.1 Sender address rejected: not owned by user` error (the account used for setting up gmail is `admin.gmail@example.org` and `info.gmail@example.org` ) + +- Send some test emails to these addresses and make other tests. Then stop the container with `ctrl+c` and start it again as a daemon: `docker-compose up -d mail`. + +- Now save on Moodle configuration the SMTP settings and test by trying to send some messages to other users: + + - **SMTP hosts**: `mail.example.org:465` + - **SMTP security**: `SSL` + - **SMTP username**: `info@example.org` + - **SMTP password**: `passwd123` + +## Using `docker-mailserver` behind a Proxy -## Using docker-mailserver behind proxy ### Information + If you are hiding your container behind a proxy service you might have discovered that the proxied requests from now on contain the proxy IP as the request origin. Whilst this behavior is technical correct it produces certain problems on the containers behind the proxy as they cannot distinguish the real origin of the requests anymore. To solve this problem on TCP connections we can make use of the [proxy protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt). Compared to other workarounds that exist (`X-Forwarded-For` which only works for HTTP requests or `Tproxy` that requires you to recompile your kernel) the proxy protocol: -- it is protocol agnostic (can work with any layer 7 protocols, even when encrypted). -- it does not require any infrastructure changes -- nat-ing firewalls have no impact it -- it is scalable -The is only one condition: **both endpoints** of the connection MUST be compatible with proxy protocol. -Luckily `dovecot` and `postfix` are both Proxy-Protocol ready softwares so it depends only on your used reverse-proxy/loadbalancer. +- It is protocol agnostic (can work with any layer 7 protocols, even when encrypted). +- It does not require any infrastructure changes. +- NAT-ing firewalls have no impact it. +- It is scalable. -### Configuration of the used proxy software +There is only one condition: **both endpoints** of the connection MUST be compatible with proxy protocol. -The configuration depends on the used proxy system. I will provide the configuration examples of [traefik v2](https://traefik.io/) using IMAP and SMTP with implicit TLS. Feel free to add your configuration if you achived the same goal using different proxy software below: +Luckily `dovecot` and `postfix` are both Proxy-Protocol ready softwares so it depends only on your used reverse-proxy / loadbalancer. -
- traefik v2 +### Configuration of the used Proxy Software - Truncated configuration of traefik itself: -``` -version: '3.7' -services: - reverse-proxy: - image: traefik:v2.4 - container_name: docker-traefik - restart: always - command: - - "--providers.docker" - - "--providers.docker.exposedbydefault=false" - - "--providers.docker.network=proxy" - - "--entrypoints.web.address=:80" - - "--entryPoints.websecure.address=:443" - - "--entryPoints.smtp.address=:25" - - "--entryPoints.smtp-ssl.address=:465" - - "--entryPoints.imap-ssl.address=:993" - - "--entryPoints.sieve.address=:4190" - ports: - - "25:25" - - "465:465" - - "993:993" - - "4190:4190" -[...] -``` +The configuration depends on the used proxy system. I will provide the configuration examples of [traefik v2](https://traefik.io/) using IMAP and SMTP with implicit TLS. -Truncated list of neccessary labels on the mailserver container: +Feel free to add your configuration if you achived the same goal using different proxy software below: -``` -version: '2' -services: - mail: - image: tvial/docker-mailserver:release-v7.2.0 - restart: always - networks: - - proxy - labels: - - "traefik.enable=true" - - "traefik.tcp.routers.smtp.rule=HostSNI(`*`)" - - "traefik.tcp.routers.smtp.entrypoints=smtp" - - "traefik.tcp.routers.smtp.service=smtp" - - "traefik.tcp.services.smtp.loadbalancer.server.port=25" - - "traefik.tcp.services.smtp.loadbalancer.proxyProtocol.version=1" - - "traefik.tcp.routers.smtp-ssl.rule=HostSNI(`*`)" - - "traefik.tcp.routers.smtp-ssl.entrypoints=smtp-ssl" - - "traefik.tcp.routers.smtp-ssl.service=smtp-ssl" - - "traefik.tcp.services.smtp-ssl.loadbalancer.server.port=465" - - "traefik.tcp.services.smtp-ssl.loadbalancer.proxyProtocol.version=1" - - "traefik.tcp.routers.imap-ssl.rule=HostSNI(`*`)" - - "traefik.tcp.routers.imap-ssl.entrypoints=imap-ssl" - - "traefik.tcp.routers.imap-ssl.service=imap-ssl" - - "traefik.tcp.services.imap-ssl.loadbalancer.server.port=10993" - - "traefik.tcp.services.imap-ssl.loadbalancer.proxyProtocol.version=2" - - "traefik.tcp.routers.sieve.rule=HostSNI(`*`)" - - "traefik.tcp.routers.sieve.entrypoints=sieve" - - "traefik.tcp.routers.sieve.service=sieve" - - "traefik.tcp.services.sieve.loadbalancer.server.port=4190" -[...] -``` -Keep in mind that it is neccessary to use port `10993` here. More information below at `dovecot` configuration. +??? "Traefik v2" -
+ Truncated configuration of traefik itself: -### Configuration of the backend (`dovecot` and `postfix`) + ```yaml + version: '3.7' + services: + reverse-proxy: + image: traefik:v2.4 + container_name: docker-traefik + restart: always + command: + - "--providers.docker" + - "--providers.docker.exposedbydefault=false" + - "--providers.docker.network=proxy" + - "--entrypoints.web.address=:80" + - "--entryPoints.websecure.address=:443" + - "--entryPoints.smtp.address=:25" + - "--entryPoints.smtp-ssl.address=:465" + - "--entryPoints.imap-ssl.address=:993" + - "--entryPoints.sieve.address=:4190" + ports: + - "25:25" + - "465:465" + - "993:993" + - "4190:4190" + [...] + ``` -The following changes can be achived completely by adding the content to the appropriate files by using the projects [function to overwrite config files](https://github.com/docker-mailserver/docker-mailserver/wiki/List-of-optional-config-files-&-directories). + Truncated list of neccessary labels on the mailserver container: + + ```yaml + version: '2' + services: + mail: + image: tvial/docker-mailserver:release-v7.2.0 + restart: always + networks: + - proxy + labels: + - "traefik.enable=true" + - "traefik.tcp.routers.smtp.rule=HostSNI(`*`)" + - "traefik.tcp.routers.smtp.entrypoints=smtp" + - "traefik.tcp.routers.smtp.service=smtp" + - "traefik.tcp.services.smtp.loadbalancer.server.port=25" + - "traefik.tcp.services.smtp.loadbalancer.proxyProtocol.version=1" + - "traefik.tcp.routers.smtp-ssl.rule=HostSNI(`*`)" + - "traefik.tcp.routers.smtp-ssl.entrypoints=smtp-ssl" + - "traefik.tcp.routers.smtp-ssl.service=smtp-ssl" + - "traefik.tcp.services.smtp-ssl.loadbalancer.server.port=465" + - "traefik.tcp.services.smtp-ssl.loadbalancer.proxyProtocol.version=1" + - "traefik.tcp.routers.imap-ssl.rule=HostSNI(`*`)" + - "traefik.tcp.routers.imap-ssl.entrypoints=imap-ssl" + - "traefik.tcp.routers.imap-ssl.service=imap-ssl" + - "traefik.tcp.services.imap-ssl.loadbalancer.server.port=10993" + - "traefik.tcp.services.imap-ssl.loadbalancer.proxyProtocol.version=2" + - "traefik.tcp.routers.sieve.rule=HostSNI(`*`)" + - "traefik.tcp.routers.sieve.entrypoints=sieve" + - "traefik.tcp.routers.sieve.service=sieve" + - "traefik.tcp.services.sieve.loadbalancer.server.port=4190" + [...] + ``` + + Keep in mind that it is neccessary to use port `10993` here. More information below at `dovecot` configuration. + +### Configuration of the Backend (`dovecot` and `postfix`) + +The following changes can be achived completely by adding the content to the appropriate files by using the projects [function to overwrite config files][docs-optionalconfig]. Changes for `postfix` can be applied by adding the following content to `config/postfix-main.cf`: -``` + +```cf postscreen_upstream_proxy_protocol = haproxy ``` -and to `config/postfix-master.cd`: -``` +and to `config/postfix-master.cf`: + +```cf submission/inet/smtpd_upstream_proxy_protocol=haproxy smtps/inet/smtpd_upstream_proxy_protocol=haproxy ``` Changes for `dovecot` can be applied by adding the following content to `config/dovecot.cf`: -``` + +```cf haproxy_trusted_networks = , haproxy_timeout = 3 secs service imap-login { @@ -266,4 +273,11 @@ service imap-login { } } ``` -Note that port `10993` is used here to avoid conflicts with internal systems like `postscreen` and `amavis` as they will exchange messages on the default port and obviously have a different origin then compared to the proxy. \ No newline at end of file + +!!! note + Port `10993` is used here to avoid conflicts with internal systems like `postscreen` and `amavis` as they will exchange messages on the default port and obviously have a different origin then compared to the proxy. + +[docs-optionalconfig]: ../advanced/optional-config.md +[github-file-env]: https://github.com/docker-mailserver/docker-mailserver/blob/master/ENVIRONMENT.md +[github-file-dotenv]: https://github.com/docker-mailserver/docker-mailserver/blob/master/mailserver.env +[github-issue-1405-comment]: https://github.com/docker-mailserver/docker-mailserver/issues/1405#issuecomment-590106498 diff --git a/docs/content/uses-cases/forward-only-mailserver-with-ldap-authentication.md b/docs/content/uses-cases/forward-only-mailserver-with-ldap-authentication.md index 3ab795f1..fbc23736 100644 --- a/docs/content/uses-cases/forward-only-mailserver-with-ldap-authentication.md +++ b/docs/content/uses-cases/forward-only-mailserver-with-ldap-authentication.md @@ -1,11 +1,14 @@ +--- +title: 'Use Cases | Forward-Only Mailserver with LDAP' +--- -## Building a Forward-Only mailserver +## Building a Forward-Only Mailserver A **forward-only** mailserver does not have any local mailboxes. Instead, it has only aliases that forward emails to external email accounts (for example to a gmail account). You can also send email from the localhost (the computer where the mailserver is installed), using as sender any of the alias addresses. The important settings for this setup (on `mailserver.env`) are these: -```console +```env PERMIT_DOCKER=host ENABLE_POP3= ENABLE_CLAMAV=0 @@ -18,15 +21,15 @@ Since there are no local mailboxes, we use `SMTP_ONLY=1` to disable `dovecot`. W We can create aliases with `./setup.sh`, like this: -```bash +```sh ./setup.sh alias add ``` ## Authenticating with LDAP -If you want to send emails from outside the mailserver you have to authenticate somehow (with a username and password). One way of doing it is described in [this discussion](https://github.com/tomav/docker-mailserver/issues/1247). However if there are many user accounts, it is better to use authentication with LDAP. The settings for this on `mailserver.env` are: +If you want to send emails from outside the mailserver you have to authenticate somehow (with a username and password). One way of doing it is described in [this discussion][github-issue-1247]. However if there are many user accounts, it is better to use authentication with LDAP. The settings for this on `mailserver.env` are: -```console +```env ENABLE_LDAP=1 LDAP_START_TLS=yes LDAP_SERVER_HOST=ldap.example.org @@ -47,7 +50,7 @@ SASLAUTHD_LDAP_FILTER=(&(uid=%U)(objectClass=inetOrgPerson)) My LDAP data structure is very basic, containing only the username, password, and the external email address where to forward emails for this user. An entry looks like this -```console +```properties add uid=username,ou=users,dc=example,dc=org uid: username objectClass: inetOrgPerson @@ -57,7 +60,7 @@ userPassword: {SSHA}abcdefghi123456789 email: real-email-address@external-domain.com ``` -This structure is different from what is expected/assumed from the configuration scripts of the mailserver, so it doesn't work just by using the `LDAP_QUERY_FILTER_...` settings. Instead, I had to do [custom configuration](https://github.com/tomav/docker-mailserver#custom-user-changes--patches). I created the script `config/user-patches.sh`, with a content like this: +This structure is different from what is expected/assumed from the configuration scripts of the mailserver, so it doesn't work just by using the `LDAP_QUERY_FILTER_...` settings. Instead, I had to do [custom configuration][github-file-readme-patches]. I created the script `config/user-patches.sh`, with a content like this: ```bash #!/bin/bash @@ -97,3 +100,6 @@ You see that besides `query_filter`, I had to customize as well `result_attribut For more details about using LDAP see: [LDAP managed mail server with Postfix and Dovecot for multiple domains](https://www.vennedey.net/resources/2-LDAP-managed-mail-server-with-Postfix-and-Dovecot-for-multiple-domains) Another solution that serves as a forward-only mailserver is this: https://gitlab.com/docker-scripts/postfix + +[github-file-readme-patches]: https://github.com/docker-mailserver/docker-mailserver/blob/master/README.md#custom-user-changes--patches +[github-issue-1247]: https://github.com/docker-mailserver/docker-mailserver/issues/1247 diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 297ba9d9..081b3512 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -34,6 +34,7 @@ markdown_extensions: - abbr - attr_list - admonition + - pymdownx.details - pymdownx.highlight: extend_pygments_lang: - name: yml @@ -42,6 +43,16 @@ markdown_extensions: lang: cfg - name: conf lang: cfg + - name: env + lang: properties + # Not helpful with Python Pygments lexer highlighting, but we might change to a JS highlighter in future + # Ideally, this type of codefence might also have word-wrap enabled (CSS: {white-space: pre-wrap}) + - name: log + lang: shell-session + - name: fetchmailrc + lang: txt + - name: caddyfile + lang: txt - pymdownx.superfences - pymdownx.magiclink - pymdownx.emoji: From 1a8552b96cc6c18476f1f9657a9a75ead94d4552 Mon Sep 17 00:00:00 2001 From: wernerfred <20406381+wernerfred@users.noreply.github.com> Date: Mon, 1 Mar 2021 20:50:18 +0100 Subject: [PATCH 07/14] docs(refactor): Restructure document hierarchy Additionally rename `installation-examples.md` and split --- .../{ => config}/advanced/auth-ldap.md | 0 .../{ => config}/advanced/full-text-search.md | 0 docs/content/{ => config}/advanced/ipv6.md | 0 .../{ => config}/advanced/kubernetes.md | 0 .../{ => config}/advanced/mail-fetchmail.md | 0 .../advanced/mail-forwarding/aws-ses.md | 0 .../advanced/mail-forwarding/relay-hosts.md | 0 .../{ => config}/advanced/mail-sieve.md | 0 .../maintenance/update-and-cleanup.md | 0 .../{ => config}/advanced/optional-config.md | 0 .../advanced/override-defaults/dovecot.md | 0 .../advanced/override-defaults/postfix.md | 0 .../tutorials/basic-installation.md} | 123 ------------------ .../tutorials/mailserver-behind-proxy.md | 123 ++++++++++++++++++ ...nly-mailserver-with-ldap-authentication.md | 0 .../{config/troubleshooting => }/faq.md | 0 16 files changed, 123 insertions(+), 123 deletions(-) rename docs/content/{ => config}/advanced/auth-ldap.md (100%) rename docs/content/{ => config}/advanced/full-text-search.md (100%) rename docs/content/{ => config}/advanced/ipv6.md (100%) rename docs/content/{ => config}/advanced/kubernetes.md (100%) rename docs/content/{ => config}/advanced/mail-fetchmail.md (100%) rename docs/content/{ => config}/advanced/mail-forwarding/aws-ses.md (100%) rename docs/content/{ => config}/advanced/mail-forwarding/relay-hosts.md (100%) rename docs/content/{ => config}/advanced/mail-sieve.md (100%) rename docs/content/{ => config}/advanced/maintenance/update-and-cleanup.md (100%) rename docs/content/{ => config}/advanced/optional-config.md (100%) rename docs/content/{ => config}/advanced/override-defaults/dovecot.md (100%) rename docs/content/{ => config}/advanced/override-defaults/postfix.md (100%) rename docs/content/{tutorials/installation-examples.md => examples/tutorials/basic-installation.md} (56%) create mode 100644 docs/content/examples/tutorials/mailserver-behind-proxy.md rename docs/content/{ => examples}/uses-cases/forward-only-mailserver-with-ldap-authentication.md (100%) rename docs/content/{config/troubleshooting => }/faq.md (100%) diff --git a/docs/content/advanced/auth-ldap.md b/docs/content/config/advanced/auth-ldap.md similarity index 100% rename from docs/content/advanced/auth-ldap.md rename to docs/content/config/advanced/auth-ldap.md diff --git a/docs/content/advanced/full-text-search.md b/docs/content/config/advanced/full-text-search.md similarity index 100% rename from docs/content/advanced/full-text-search.md rename to docs/content/config/advanced/full-text-search.md diff --git a/docs/content/advanced/ipv6.md b/docs/content/config/advanced/ipv6.md similarity index 100% rename from docs/content/advanced/ipv6.md rename to docs/content/config/advanced/ipv6.md diff --git a/docs/content/advanced/kubernetes.md b/docs/content/config/advanced/kubernetes.md similarity index 100% rename from docs/content/advanced/kubernetes.md rename to docs/content/config/advanced/kubernetes.md diff --git a/docs/content/advanced/mail-fetchmail.md b/docs/content/config/advanced/mail-fetchmail.md similarity index 100% rename from docs/content/advanced/mail-fetchmail.md rename to docs/content/config/advanced/mail-fetchmail.md diff --git a/docs/content/advanced/mail-forwarding/aws-ses.md b/docs/content/config/advanced/mail-forwarding/aws-ses.md similarity index 100% rename from docs/content/advanced/mail-forwarding/aws-ses.md rename to docs/content/config/advanced/mail-forwarding/aws-ses.md diff --git a/docs/content/advanced/mail-forwarding/relay-hosts.md b/docs/content/config/advanced/mail-forwarding/relay-hosts.md similarity index 100% rename from docs/content/advanced/mail-forwarding/relay-hosts.md rename to docs/content/config/advanced/mail-forwarding/relay-hosts.md diff --git a/docs/content/advanced/mail-sieve.md b/docs/content/config/advanced/mail-sieve.md similarity index 100% rename from docs/content/advanced/mail-sieve.md rename to docs/content/config/advanced/mail-sieve.md diff --git a/docs/content/advanced/maintenance/update-and-cleanup.md b/docs/content/config/advanced/maintenance/update-and-cleanup.md similarity index 100% rename from docs/content/advanced/maintenance/update-and-cleanup.md rename to docs/content/config/advanced/maintenance/update-and-cleanup.md diff --git a/docs/content/advanced/optional-config.md b/docs/content/config/advanced/optional-config.md similarity index 100% rename from docs/content/advanced/optional-config.md rename to docs/content/config/advanced/optional-config.md diff --git a/docs/content/advanced/override-defaults/dovecot.md b/docs/content/config/advanced/override-defaults/dovecot.md similarity index 100% rename from docs/content/advanced/override-defaults/dovecot.md rename to docs/content/config/advanced/override-defaults/dovecot.md diff --git a/docs/content/advanced/override-defaults/postfix.md b/docs/content/config/advanced/override-defaults/postfix.md similarity index 100% rename from docs/content/advanced/override-defaults/postfix.md rename to docs/content/config/advanced/override-defaults/postfix.md diff --git a/docs/content/tutorials/installation-examples.md b/docs/content/examples/tutorials/basic-installation.md similarity index 56% rename from docs/content/tutorials/installation-examples.md rename to docs/content/examples/tutorials/basic-installation.md index cf8ecb08..3e89b558 100644 --- a/docs/content/tutorials/installation-examples.md +++ b/docs/content/examples/tutorials/basic-installation.md @@ -155,129 +155,6 @@ We are going to use this docker based mailserver: - **SMTP username**: `info@example.org` - **SMTP password**: `passwd123` -## Using `docker-mailserver` behind a Proxy - -### Information - -If you are hiding your container behind a proxy service you might have discovered that the proxied requests from now on contain the proxy IP as the request origin. Whilst this behavior is technical correct it produces certain problems on the containers behind the proxy as they cannot distinguish the real origin of the requests anymore. - -To solve this problem on TCP connections we can make use of the [proxy protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt). Compared to other workarounds that exist (`X-Forwarded-For` which only works for HTTP requests or `Tproxy` that requires you to recompile your kernel) the proxy protocol: - -- It is protocol agnostic (can work with any layer 7 protocols, even when encrypted). -- It does not require any infrastructure changes. -- NAT-ing firewalls have no impact it. -- It is scalable. - -There is only one condition: **both endpoints** of the connection MUST be compatible with proxy protocol. - -Luckily `dovecot` and `postfix` are both Proxy-Protocol ready softwares so it depends only on your used reverse-proxy / loadbalancer. - -### Configuration of the used Proxy Software - -The configuration depends on the used proxy system. I will provide the configuration examples of [traefik v2](https://traefik.io/) using IMAP and SMTP with implicit TLS. - -Feel free to add your configuration if you achived the same goal using different proxy software below: - -??? "Traefik v2" - - Truncated configuration of traefik itself: - - ```yaml - version: '3.7' - services: - reverse-proxy: - image: traefik:v2.4 - container_name: docker-traefik - restart: always - command: - - "--providers.docker" - - "--providers.docker.exposedbydefault=false" - - "--providers.docker.network=proxy" - - "--entrypoints.web.address=:80" - - "--entryPoints.websecure.address=:443" - - "--entryPoints.smtp.address=:25" - - "--entryPoints.smtp-ssl.address=:465" - - "--entryPoints.imap-ssl.address=:993" - - "--entryPoints.sieve.address=:4190" - ports: - - "25:25" - - "465:465" - - "993:993" - - "4190:4190" - [...] - ``` - - Truncated list of neccessary labels on the mailserver container: - - ```yaml - version: '2' - services: - mail: - image: tvial/docker-mailserver:release-v7.2.0 - restart: always - networks: - - proxy - labels: - - "traefik.enable=true" - - "traefik.tcp.routers.smtp.rule=HostSNI(`*`)" - - "traefik.tcp.routers.smtp.entrypoints=smtp" - - "traefik.tcp.routers.smtp.service=smtp" - - "traefik.tcp.services.smtp.loadbalancer.server.port=25" - - "traefik.tcp.services.smtp.loadbalancer.proxyProtocol.version=1" - - "traefik.tcp.routers.smtp-ssl.rule=HostSNI(`*`)" - - "traefik.tcp.routers.smtp-ssl.entrypoints=smtp-ssl" - - "traefik.tcp.routers.smtp-ssl.service=smtp-ssl" - - "traefik.tcp.services.smtp-ssl.loadbalancer.server.port=465" - - "traefik.tcp.services.smtp-ssl.loadbalancer.proxyProtocol.version=1" - - "traefik.tcp.routers.imap-ssl.rule=HostSNI(`*`)" - - "traefik.tcp.routers.imap-ssl.entrypoints=imap-ssl" - - "traefik.tcp.routers.imap-ssl.service=imap-ssl" - - "traefik.tcp.services.imap-ssl.loadbalancer.server.port=10993" - - "traefik.tcp.services.imap-ssl.loadbalancer.proxyProtocol.version=2" - - "traefik.tcp.routers.sieve.rule=HostSNI(`*`)" - - "traefik.tcp.routers.sieve.entrypoints=sieve" - - "traefik.tcp.routers.sieve.service=sieve" - - "traefik.tcp.services.sieve.loadbalancer.server.port=4190" - [...] - ``` - - Keep in mind that it is neccessary to use port `10993` here. More information below at `dovecot` configuration. - -### Configuration of the Backend (`dovecot` and `postfix`) - -The following changes can be achived completely by adding the content to the appropriate files by using the projects [function to overwrite config files][docs-optionalconfig]. - -Changes for `postfix` can be applied by adding the following content to `config/postfix-main.cf`: - -```cf -postscreen_upstream_proxy_protocol = haproxy -``` - -and to `config/postfix-master.cf`: - -```cf -submission/inet/smtpd_upstream_proxy_protocol=haproxy -smtps/inet/smtpd_upstream_proxy_protocol=haproxy -``` - -Changes for `dovecot` can be applied by adding the following content to `config/dovecot.cf`: - -```cf -haproxy_trusted_networks = , -haproxy_timeout = 3 secs -service imap-login { - inet_listener imaps { - haproxy = yes - ssl = yes - port = 10993 - } -} -``` - -!!! note - Port `10993` is used here to avoid conflicts with internal systems like `postscreen` and `amavis` as they will exchange messages on the default port and obviously have a different origin then compared to the proxy. - -[docs-optionalconfig]: ../advanced/optional-config.md [github-file-env]: https://github.com/docker-mailserver/docker-mailserver/blob/master/ENVIRONMENT.md [github-file-dotenv]: https://github.com/docker-mailserver/docker-mailserver/blob/master/mailserver.env [github-issue-1405-comment]: https://github.com/docker-mailserver/docker-mailserver/issues/1405#issuecomment-590106498 diff --git a/docs/content/examples/tutorials/mailserver-behind-proxy.md b/docs/content/examples/tutorials/mailserver-behind-proxy.md new file mode 100644 index 00000000..1154ed1e --- /dev/null +++ b/docs/content/examples/tutorials/mailserver-behind-proxy.md @@ -0,0 +1,123 @@ +## Using `docker-mailserver` behind a Proxy + +### Information + +If you are hiding your container behind a proxy service you might have discovered that the proxied requests from now on contain the proxy IP as the request origin. Whilst this behavior is technical correct it produces certain problems on the containers behind the proxy as they cannot distinguish the real origin of the requests anymore. + +To solve this problem on TCP connections we can make use of the [proxy protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt). Compared to other workarounds that exist (`X-Forwarded-For` which only works for HTTP requests or `Tproxy` that requires you to recompile your kernel) the proxy protocol: + +- It is protocol agnostic (can work with any layer 7 protocols, even when encrypted). +- It does not require any infrastructure changes. +- NAT-ing firewalls have no impact it. +- It is scalable. + +There is only one condition: **both endpoints** of the connection MUST be compatible with proxy protocol. + +Luckily `dovecot` and `postfix` are both Proxy-Protocol ready softwares so it depends only on your used reverse-proxy / loadbalancer. + +### Configuration of the used Proxy Software + +The configuration depends on the used proxy system. I will provide the configuration examples of [traefik v2](https://traefik.io/) using IMAP and SMTP with implicit TLS. + +Feel free to add your configuration if you achived the same goal using different proxy software below: + +??? "Traefik v2" + + Truncated configuration of traefik itself: + + ```yaml + version: '3.7' + services: + reverse-proxy: + image: traefik:v2.4 + container_name: docker-traefik + restart: always + command: + - "--providers.docker" + - "--providers.docker.exposedbydefault=false" + - "--providers.docker.network=proxy" + - "--entrypoints.web.address=:80" + - "--entryPoints.websecure.address=:443" + - "--entryPoints.smtp.address=:25" + - "--entryPoints.smtp-ssl.address=:465" + - "--entryPoints.imap-ssl.address=:993" + - "--entryPoints.sieve.address=:4190" + ports: + - "25:25" + - "465:465" + - "993:993" + - "4190:4190" + [...] + ``` + + Truncated list of neccessary labels on the mailserver container: + + ```yaml + version: '2' + services: + mail: + image: tvial/docker-mailserver:release-v7.2.0 + restart: always + networks: + - proxy + labels: + - "traefik.enable=true" + - "traefik.tcp.routers.smtp.rule=HostSNI(`*`)" + - "traefik.tcp.routers.smtp.entrypoints=smtp" + - "traefik.tcp.routers.smtp.service=smtp" + - "traefik.tcp.services.smtp.loadbalancer.server.port=25" + - "traefik.tcp.services.smtp.loadbalancer.proxyProtocol.version=1" + - "traefik.tcp.routers.smtp-ssl.rule=HostSNI(`*`)" + - "traefik.tcp.routers.smtp-ssl.entrypoints=smtp-ssl" + - "traefik.tcp.routers.smtp-ssl.service=smtp-ssl" + - "traefik.tcp.services.smtp-ssl.loadbalancer.server.port=465" + - "traefik.tcp.services.smtp-ssl.loadbalancer.proxyProtocol.version=1" + - "traefik.tcp.routers.imap-ssl.rule=HostSNI(`*`)" + - "traefik.tcp.routers.imap-ssl.entrypoints=imap-ssl" + - "traefik.tcp.routers.imap-ssl.service=imap-ssl" + - "traefik.tcp.services.imap-ssl.loadbalancer.server.port=10993" + - "traefik.tcp.services.imap-ssl.loadbalancer.proxyProtocol.version=2" + - "traefik.tcp.routers.sieve.rule=HostSNI(`*`)" + - "traefik.tcp.routers.sieve.entrypoints=sieve" + - "traefik.tcp.routers.sieve.service=sieve" + - "traefik.tcp.services.sieve.loadbalancer.server.port=4190" + [...] + ``` + + Keep in mind that it is neccessary to use port `10993` here. More information below at `dovecot` configuration. + +### Configuration of the Backend (`dovecot` and `postfix`) + +The following changes can be achived completely by adding the content to the appropriate files by using the projects [function to overwrite config files][docs-optionalconfig]. + +Changes for `postfix` can be applied by adding the following content to `config/postfix-main.cf`: + +```cf +postscreen_upstream_proxy_protocol = haproxy +``` + +and to `config/postfix-master.cf`: + +```cf +submission/inet/smtpd_upstream_proxy_protocol=haproxy +smtps/inet/smtpd_upstream_proxy_protocol=haproxy +``` + +Changes for `dovecot` can be applied by adding the following content to `config/dovecot.cf`: + +```cf +haproxy_trusted_networks = , +haproxy_timeout = 3 secs +service imap-login { + inet_listener imaps { + haproxy = yes + ssl = yes + port = 10993 + } +} +``` + +!!! note + Port `10993` is used here to avoid conflicts with internal systems like `postscreen` and `amavis` as they will exchange messages on the default port and obviously have a different origin then compared to the proxy. + +[docs-optionalconfig]: ../advanced/optional-config.md diff --git a/docs/content/uses-cases/forward-only-mailserver-with-ldap-authentication.md b/docs/content/examples/uses-cases/forward-only-mailserver-with-ldap-authentication.md similarity index 100% rename from docs/content/uses-cases/forward-only-mailserver-with-ldap-authentication.md rename to docs/content/examples/uses-cases/forward-only-mailserver-with-ldap-authentication.md diff --git a/docs/content/config/troubleshooting/faq.md b/docs/content/faq.md similarity index 100% rename from docs/content/config/troubleshooting/faq.md rename to docs/content/faq.md From cc0706a6faacbda032998c5cfebba57f65435cff Mon Sep 17 00:00:00 2001 From: wernerfred <20406381+wernerfred@users.noreply.github.com> Date: Tue, 2 Mar 2021 19:40:43 +0100 Subject: [PATCH 08/14] docs: Add a contributing section --- docs/content/contributing/coding-style.md | 155 ++++++++++++++++++ docs/content/contributing/documentation.md | 6 + .../contributing/issues-and-pull-requests.md | 50 ++++++ docs/content/contributing/tests.md | 6 + docs/content/index.md | 5 + 5 files changed, 222 insertions(+) create mode 100644 docs/content/contributing/coding-style.md create mode 100644 docs/content/contributing/documentation.md create mode 100644 docs/content/contributing/issues-and-pull-requests.md create mode 100644 docs/content/contributing/tests.md diff --git a/docs/content/contributing/coding-style.md b/docs/content/contributing/coding-style.md new file mode 100644 index 00000000..9b5a4be5 --- /dev/null +++ b/docs/content/contributing/coding-style.md @@ -0,0 +1,155 @@ +--- +title: 'Contributing | Coding Style' +--- + +##Bash and Shell + +When refactoring, writing or altering scripts, that is Shell and bash scripts, in any way, adhere to these rules: + +1. **Adjust your style of coding to the style that is already present**! Even if you do not like it, this is due to consistency. There was a lot of work involved in making all scripts consistent. +2. **Use `shellcheck` to check your scripts**! Your contributions are checked by GitHub Actions too, so you will need to do this. You can **lint your work with `make lint`** to check against all targets. +3. **Use the provided `.editorconfig`** file. +4. Use `/bin/bash` or `/usr/bin/envbash` instead of `/bin/sh`. Adjust the style accordingly. +5. `setup.sh` provides a good starting point to look for. +6. When appropriate, use the `set` builtin. We recommend `set -euEo pipefail` or `set -uE`. + +## Styling rules + +### If-Else-Statements + +```bash +# when using braces, use double braces +# remember you do not need "" when using [[ ]] +if [[ ]] && [[ -f ${FILE} ]] +then + +# when running commands, you don't need braces +elif + +else + +fi + +# equality checks with numbers are done +# with -eq/-ne/-lt/-ge, not != or == +if [[ ${VAR} -ne 42 ]] || [[ ${SOME_VAR} -eq 6 ]] +then + +fi +``` + +### Variables & Braces + +!!! attention + + Variables are always uppercase. We always use braces. + +If you forgot this and want to change it later, you can use [this link][regex]. The used regex is `\$([^{("\\'\/])([a-zA-Z0-9_]*)([^}\/ \t'"\n.\]:(=\\-]*)`, where you should in practice be able to replace all variable occurrences without braces with occurrences with braces. + +```bash +# good +local VAR="good" +local NEW="${VAR}" + +# bad -> TravisCI will fail +var="bad" +new=$var +``` + +### Loops + +Like `if-else`, loops look like this + +```bash +for / while +do + +done +``` + +### Functions + +It's always nice to see the use of functions as it also provides a clear structure. If scripts are small, this is unnecessary, but if they become larger, please consider using functions. When doing so, provide `function _main`. + +```bash +function _ +{ + + + # variables that can be local should be local + local +} +``` + +### Error Tracing + +A construct to trace error in your scripts looks like this. Remember: Remove `set -x` in the end. This is for debugging purposes only. + +```bash +set -xeuEo pipefail +trap '__log_err ${FUNCNAME[0]:-"?"} ${BASH_COMMAND:-"?"} ${LINENO:-"?"} ${?:-"?"}' ERR + +SCRIPT='name_of_this_script.sh' + +function __log_err +{ + printf "\n––– \e[1m\e[31mUNCHECKED ERROR\e[0m\n%s\n%s\n%s\n%s\n\n" \ + " – script = ${SCRIPT:-${0}}" \ + " – function = ${1} / ${2}" \ + " – line = ${3}" \ + " – exit code = ${4}" 1>&2 + + +} +``` + +### Comments, Descriptiveness & An Example + +Comments should only describe non-obvious matters. Comments should start lowercase when they aren't sentences. Make the code **self-descriptive** by using meaningful names! Make comments not longer than approximately 80 columns, then wrap the line. + +A positive example, which is taken from `start-mailserver.sh`, would be + +```bash +function _setup_postfix_aliases +{ + _notify 'task' 'Setting up Postfix Aliases' + + : >/etc/postfix/virtual + : >/etc/postfix/regexp + + if [[ -f /tmp/docker-mailserver/postfix-virtual.cf ]] + then + # fixing old virtual user file + if grep -q ",$" /tmp/docker-mailserver/postfix-virtual.cf + then + sed -i -e "s/, /,/g" -e "s/,$//g" /tmp/docker-mailserver/postfix-virtual.cf + fi + + cp -f /tmp/docker-mailserver/postfix-virtual.cf /etc/postfix/virtual + + # the `to` is important, don't delete it + # shellcheck disable=SC2034 + while read -r FROM TO + do + # Setting variables for better readability + UNAME=$(echo "${FROM}" | cut -d @ -f1) + DOMAIN=$(echo "${FROM}" | cut -d @ -f2) + + # if they are equal it means the line looks like: "user1 other@domain.tld" + [[ "${UNAME}" != "${DOMAIN}" ]] && echo "${DOMAIN}" >> /tmp/vhost.tmp + done < <(grep -v "^\s*$\|^\s*\#" /tmp/docker-mailserver/postfix-virtual.cf || true) + else + _notify 'inf' "Warning 'config/postfix-virtual.cf' is not provided. No mail alias/forward created." + fi + + ... +} +``` + +## YAML + +When formatting YAML files, use [Prettier][prettier], an opinionated formatter. There are many plugins for IDEs around. + +[semver]: https://semver.org/ +[regex]: https://regex101.com/r/ikzJpF/7 +[prettier]: https://prettier.io diff --git a/docs/content/contributing/documentation.md b/docs/content/contributing/documentation.md new file mode 100644 index 00000000..e9470de1 --- /dev/null +++ b/docs/content/contributing/documentation.md @@ -0,0 +1,6 @@ +--- +title: 'Contributing | Documentation' +--- + +!!! todo + This section should provide a detailed step by step guide on how to contribute to documentation \ No newline at end of file diff --git a/docs/content/contributing/issues-and-pull-requests.md b/docs/content/contributing/issues-and-pull-requests.md new file mode 100644 index 00000000..801e06f0 --- /dev/null +++ b/docs/content/contributing/issues-and-pull-requests.md @@ -0,0 +1,50 @@ +--- +title: 'Contributing | Issues and Pull Requests' +--- + +This project is Open Source. That means that you can contribute on enhancements, bug fixing or improving the documentation. + +## Opening an Issue + +!!! attention + + **Before opening an issue**, read the [`README`][github-file-readme] carefully, use the [Wiki][wiki], the Postfix/Dovecot documentation and your search engine you trust. The issue tracker is not meant to be used for unrelated questions! + +When opening an issue, please provide details use case to let the community reproduce your problem. Please start the mail server with env `DMS_DEBUG=1` and paste the output into the issue. + +!!! attention + + **Use the issue templates** to provide the necessary information. Issues which do not use these templates are not worked on and closed. + +By raising issues, I agree to these terms and I understand, that the rules set for the issue tracker will help both maintainers as well as everyone to find a solution. + +Maintainers take the time to improve on this project and help by solving issues together. It is therefore expected from others to make an effort and **comply with the rules**. + +## Pull Requests + +### Submit a Pull-Request + +!!! question "Motivation" + + You want to add a feature? Feel free to start creating an issue explaining what you want to do and how you're thinking doing it. Other users may have the same need and collaboration may lead to better results. + +The development workflow is the following: + +1. Fork the project and clone your fork + 1. Create a new branch to work on + 2. Run `git submodule update --init --recursive` +2. Write the code that is needed :D +3. Add integration tests if necessary +4. Get the linters with `make install_linters` and install `jq` with the package manager of your OS +5. Use `make clean all` to build image locally and run tests (note that tests work on Linux **only**) +6. Document your improvements if necessary (e.g. if you introduced new environment variables, write the description in [`ENVIRONMENT.md`][github-file-env]) +7. [Commit][commit] and [sign your commit][gpg], push and create a pull-request to merge into `master`. Please **use the pull-request template** to provide a minimum of contextual information and make sure to meet the requirements of the checklist. + 1. Pull requests are automatically tested against the CI and will be reviewed when tests pass + 2. When your changes are validated, your branch is merged + 3. CI builds the new `:edge` image immediately and your changes will be includes in the next version release. + +[wiki]: https://docker-mailserver.github.io/docker-mailserver +[github-file-readme]: https://github.com/docker-mailserver/docker-mailserver/blob/master/README.md +[github-file-env]: https://github.com/docker-mailserver/docker-mailserver/blob/master/ENVIRONMENT.md +[commit]: https://help.github.com/articles/closing-issues-via-commit-messages/ +[gpg]: https://docs.github.com/en/github/authenticating-to-github/generating-a-new-gpg-key diff --git a/docs/content/contributing/tests.md b/docs/content/contributing/tests.md new file mode 100644 index 00000000..f41eeba3 --- /dev/null +++ b/docs/content/contributing/tests.md @@ -0,0 +1,6 @@ +--- +title: 'Contributing | Tests' +--- + +!!! todo + This section should provide a detailed step by step guide on how to write tests \ No newline at end of file diff --git a/docs/content/index.md b/docs/content/index.md index 942be6f8..c683a61a 100644 --- a/docs/content/index.md +++ b/docs/content/index.md @@ -18,6 +18,11 @@ This wiki provides you with advanced configuration, detailed examples, and hints !!! tip See the [FAQ][docs-faq] for some more tips! +## Contributing + +We are always happy to welcome new contributors. For guidelines and entrypoints please have a look at the [Contributing section][docs-contributing]. + +[docs-contributing]: ./contributing/issues-and-pull-requests.md [docs-faq]: ./config/troubleshooting/faq.md [docs-optionalconfig]: ./advanced/optional-config.md [docs-setupsh]: ./config/setup.sh.md From 237a932f8e9f1e8c4ec82bb30e37fa74fe1dfa6d Mon Sep 17 00:00:00 2001 From: wernerfred <20406381+wernerfred@users.noreply.github.com> Date: Tue, 2 Mar 2021 18:48:28 +0100 Subject: [PATCH 09/14] docs(config): Add the nav section Various PR commits related to the nav section have been merged together: docs: Fix indentation for linter check docs: update edit uri to content subdirectory docs: add ghcr link docs: shorten nav entry name docs: quote ghcr nav entry docs(config): update nav section for relocated documents Additional nav section fixes: - consistent usage of quotes - eclint padding error - wrong indentation of pop3 - remove a leading slash from a filepath --- docs/mkdocs.yml | 53 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 081b3512..baf62d3b 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -4,7 +4,7 @@ site_author: 'docker-mailserver (Github Organization)' repo_name: 'docker-mailserver' repo_url: 'https://github.com/docker-mailserver/docker-mailserver' copyright: '

© Docker Mailserver Organization
This project is licensed under the MIT license.

' - +edit_uri: 'edit/master/docs/content' docs_dir: 'content/' site_url: 'https://docker-mailserver.github.io/docker-mailserver' theme: @@ -58,3 +58,54 @@ markdown_extensions: - pymdownx.emoji: emoji_index: !!python/name:materialx.emoji.twemoji emoji_generator: !!python/name:materialx.emoji.to_svg + +nav: + - 'Home': index.md + - 'Introduction': introduction.md + - 'Configuration': + - 'Your Best Friend setup.sh': config/setup.sh.md + - 'User Management': + - 'Accounts': config/user-management/accounts.md + - 'Aliases': config/user-management/aliases.md + - 'Best Practices': + - 'DKIM': config/best-practices/dkim.md + - 'DMARC': config/best-practices/dmarc.md + - 'SPF': config/best-practices/spf.md + - 'Auto-discovery': config/best-practices/autodiscover.md + - 'Security': + - 'Understanding the Ports': config/security/understanding-the-ports.md + - 'SSL/TLS': config/security/ssl.md + - 'Fail2Ban': config/security/fail2ban.md + - 'Troubleshooting': + - 'Debugging': config/troubleshooting/debugging.md + - 'Mail Delivery with POP3': config/pop3.md + - 'Advanced Configuration': + - 'Optional Configuration': config/advanced/optional-config.md + - 'Maintenance': + - 'Update and Cleanup': config/advanced/maintenance/update-and-cleanup.md + - 'Override the Default Configs': + - 'Dovecot': config/advanced/override-defaults/dovecot.md + - 'Postfix': config/advanced/override-defaults/postfix.md + - 'LDAP Authentication': config/advanced/auth-ldap.md + - 'Email Filtering with Sieve': config/advanced/mail-sieve.md + - 'Email Gathering with Fetchmail': config/advanced/mail-fetchmail.md + - 'Email Forwarding': + - 'Relay Hosts': config/advanced/mail-forwarding/relay-hosts.md + - 'AWS SES': config/advanced/mail-forwarding/aws-ses.md + - 'Full-Text Search': config/advanced/full-text-search.md + - 'Kubernetes': config/advanced/kubernetes.md + - 'IPv6': config/advanced/ipv6.md + - 'Examples': + - 'Tutorials': + - 'Basic Installation': examples/tutorials/basic-installation.md + - 'Mailserver behind Proxy': examples/tutorials/mailserver-behind-proxy.md + - 'Use Cases': + - 'Forward-Only Mailserver with LDAP': examples/uses-cases/forward-only-mailserver-with-ldap-authentication.md + - 'FAQ' : faq/faq.md + - 'Contributing': + - 'Issues and Pull Requests': contributing/issues-and-pull-requests.md + - 'Coding Style': contributing/coding-style.md + - 'Tests': contributing/tests.md + - 'Documentation': contributing/documentation.md + - 'DockerHub': https://hub.docker.com/repository/docker/mailserver/docker-mailserver + - 'GHCR': https://github.com/orgs/docker-mailserver/packages/container/package/docker-mailserver From 724fe72d2076a8dd326fa85daa0fb7df84306027 Mon Sep 17 00:00:00 2001 From: wernerfred <20406381+wernerfred@users.noreply.github.com> Date: Thu, 11 Mar 2021 20:41:24 +0100 Subject: [PATCH 10/14] docs: Update metadata and links to new locations Also removed the FAQ inline heading link for Rancher. It's not a relevant link (as the question already expects knowledge of Rancher), and breaks out of the bg colour heading style due to the HTML generation logic from mkdocs. --- docs/content/config/advanced/auth-ldap.md | 2 +- docs/content/config/advanced/full-text-search.md | 2 +- docs/content/config/advanced/ipv6.md | 2 +- docs/content/config/advanced/kubernetes.md | 2 +- docs/content/config/advanced/mail-fetchmail.md | 4 ++-- docs/content/config/advanced/mail-sieve.md | 2 +- docs/content/config/advanced/optional-config.md | 16 ++++++++-------- .../examples/tutorials/basic-installation.md | 2 +- .../tutorials/mailserver-behind-proxy.md | 6 +++++- docs/content/faq.md | 6 +++--- docs/content/index.md | 4 ++-- docs/mkdocs.yml | 2 +- 12 files changed, 27 insertions(+), 23 deletions(-) diff --git a/docs/content/config/advanced/auth-ldap.md b/docs/content/config/advanced/auth-ldap.md index 8e0f4eb7..e369d310 100644 --- a/docs/content/config/advanced/auth-ldap.md +++ b/docs/content/config/advanced/auth-ldap.md @@ -1,5 +1,5 @@ --- -title: 'LDAP Authentication' +title: 'Advanced | LDAP Authentication' --- ## Introduction diff --git a/docs/content/config/advanced/full-text-search.md b/docs/content/config/advanced/full-text-search.md index 2a73507c..29a88d58 100644 --- a/docs/content/config/advanced/full-text-search.md +++ b/docs/content/config/advanced/full-text-search.md @@ -1,5 +1,5 @@ --- -title: 'Full-Text Search' +title: 'Advanced | Full-Text Search' --- ## Overview diff --git a/docs/content/config/advanced/ipv6.md b/docs/content/config/advanced/ipv6.md index c5ec5fb1..56c02ee4 100644 --- a/docs/content/config/advanced/ipv6.md +++ b/docs/content/config/advanced/ipv6.md @@ -1,5 +1,5 @@ --- -title: 'IPv6' +title: 'Advanced | IPv6' --- ## Background diff --git a/docs/content/config/advanced/kubernetes.md b/docs/content/config/advanced/kubernetes.md index 74a3bc31..bf4d90cd 100644 --- a/docs/content/config/advanced/kubernetes.md +++ b/docs/content/config/advanced/kubernetes.md @@ -1,5 +1,5 @@ --- -title: 'Kubernetes' +title: 'Advanced | Kubernetes' --- ## Deployment Example diff --git a/docs/content/config/advanced/mail-fetchmail.md b/docs/content/config/advanced/mail-fetchmail.md index 30aa6208..7713db52 100644 --- a/docs/content/config/advanced/mail-fetchmail.md +++ b/docs/content/config/advanced/mail-fetchmail.md @@ -1,5 +1,5 @@ --- -title: 'Email Gathering with Fetchmail' +title: 'Advanced | Email Gathering with Fetchmail' --- To enable the [fetchmail][fetchmail-website] service to retrieve e-mails set the environment variable `ENABLE_FETCHMAIL` to `1`. Your `docker-compose.yml` file should look like following snippet: @@ -114,7 +114,7 @@ fetchmail: 6.3.26 querying outlook.office365.com (protocol POP3) at Mon Aug 29 2 fetchmail: normal termination, status 1 ``` -[docs-setup]: ../config/setup.sh.md +[docs-setup]: ../../config/setup.sh.md [fetchmail-website]: https://www.fetchmail.info [fetchmail-docs]: https://www.fetchmail.info/fetchmail-man.html [fetchmail-docs-run]: https://www.fetchmail.info/fetchmail-man.html#31 diff --git a/docs/content/config/advanced/mail-sieve.md b/docs/content/config/advanced/mail-sieve.md index 001648f0..db6e779b 100644 --- a/docs/content/config/advanced/mail-sieve.md +++ b/docs/content/config/advanced/mail-sieve.md @@ -1,5 +1,5 @@ --- -title: 'Email Filtering with Sieve' +title: 'Advanced | Email Filtering with Sieve' --- ## User-Defined Sieve Filters diff --git a/docs/content/config/advanced/optional-config.md b/docs/content/config/advanced/optional-config.md index 97702c0b..3d8dc57b 100644 --- a/docs/content/config/advanced/optional-config.md +++ b/docs/content/config/advanced/optional-config.md @@ -1,5 +1,5 @@ --- -title: 'Optional Configuration' +title: 'Advanced | Optional Configuration' hide: - toc # Hide Table of Contents for this page --- @@ -39,16 +39,16 @@ This is a list of all configuration files and directories which are optional or - **dovecot.cf:** replaces `/etc/dovecot/local.conf`. (Docs: [Override Dovecot Defaults][docs-override-dovecot]) - **dovecot-quotas.cf:** list of custom quotas per mailbox. (Docs: [Accounts][docs-accounts-quota]) -[docs-accounts-quota]: ../config/user-management/accounts.md#notes -[docs-aliases-regex]: ../config/user-management/aliases.md#configuring-regexp-aliases -[docs-dkim]: ../config/best-practices/dkim.md -[docs-fail2ban]: ../config/security/fail2ban.md -[docs-faq-spamrules]: ../config/troubleshooting/faq.md#how-can-i-manage-my-custom-spamassassin-rules +[docs-accounts-quota]: ../../config/user-management/accounts.md#notes +[docs-aliases-regex]: ../../config/user-management/aliases.md#configuring-regexp-aliases +[docs-dkim]: ../../config/best-practices/dkim.md +[docs-fail2ban]: ../../config/security/fail2ban.md +[docs-faq-spamrules]: ../../faq.md#how-can-i-manage-my-custom-spamassassin-rules [docs-override-postfix]: ./override-defaults/postfix.md [docs-override-dovecot]: ./override-defaults/dovecot.md [docs-relayhosts-senderauth]: ./mail-forwarding/relay-hosts.md#sender-dependent-authentication [docs-relayhosts-senderhost]: ./mail-forwarding/relay-hosts.md#sender-dependent-relay-host [docs-sieve]: ./mail-sieve.md -[docs-setupsh]: ../config/setup.sh.md -[docs-ssl]: ../config/security/ssl.md +[docs-setupsh]: ../../config/setup.sh.md +[docs-ssl]: ../../config/security/ssl.md [github-commit-setup-stack.sh-L411]: https://github.com/docker-mailserver/docker-mailserver/blob/941e7acdaebe271eaf3d296b36d4d81df4c54b90/target/scripts/startup/setup-stack.sh#L411 diff --git a/docs/content/examples/tutorials/basic-installation.md b/docs/content/examples/tutorials/basic-installation.md index 3e89b558..7b63fab0 100644 --- a/docs/content/examples/tutorials/basic-installation.md +++ b/docs/content/examples/tutorials/basic-installation.md @@ -1,5 +1,5 @@ --- -title: 'Tutorials | Installation Examples' +title: 'Tutorials | Basic Installation' --- ## Building a Simple Mailserver diff --git a/docs/content/examples/tutorials/mailserver-behind-proxy.md b/docs/content/examples/tutorials/mailserver-behind-proxy.md index 1154ed1e..febccbf7 100644 --- a/docs/content/examples/tutorials/mailserver-behind-proxy.md +++ b/docs/content/examples/tutorials/mailserver-behind-proxy.md @@ -1,3 +1,7 @@ +--- +title: 'Tutorials | Mailserver behind Proxy' +--- + ## Using `docker-mailserver` behind a Proxy ### Information @@ -120,4 +124,4 @@ service imap-login { !!! note Port `10993` is used here to avoid conflicts with internal systems like `postscreen` and `amavis` as they will exchange messages on the default port and obviously have a different origin then compared to the proxy. -[docs-optionalconfig]: ../advanced/optional-config.md +[docs-optionalconfig]: ../../config/advanced/optional-config.md diff --git a/docs/content/faq.md b/docs/content/faq.md index f262f7a6..e142bbe4 100644 --- a/docs/content/faq.md +++ b/docs/content/faq.md @@ -1,5 +1,5 @@ --- -title: 'Troubleshooting | FAQ' +title: 'FAQ' --- ### What kind of database are you using? @@ -307,7 +307,7 @@ Otherwise, it could work with 512M of RAM. Current figure is about 850M and growing. If you get errors about clamav or amavis failing to allocate memory you need more RAM or more swap and of course docker must be allowed to use swap (not always the case). If you can't use swap at all you may need 3G RAM. -### Can `docker-mailserver` run in a [Rancher Environment](http://rancher.com/rancher/)? +### Can `docker-mailserver` run in a Rancher Environment? Yes, by adding the environment variable `PERMIT_DOCKER: network`. @@ -401,7 +401,7 @@ sed -i 's/rimap -r/rimap/' /etc/supervisor/conf.d/saslauth.conf supervisorctl update ``` -[docs-maintenance]: ../../advanced/maintenance/update-and-cleanup.md +[docs-maintenance]: ./config/advanced/maintenance/update-and-cleanup.md [github-issue-95]: https://github.com/docker-mailserver/docker-mailserver/issues/95 [github-issue-97]: https://github.com/docker-mailserver/docker-mailserver/issues/97 [github-issue-1247]: https://github.com/docker-mailserver/docker-mailserver/issues/1247 diff --git a/docs/content/index.md b/docs/content/index.md index c683a61a..14485769 100644 --- a/docs/content/index.md +++ b/docs/content/index.md @@ -23,8 +23,8 @@ This wiki provides you with advanced configuration, detailed examples, and hints We are always happy to welcome new contributors. For guidelines and entrypoints please have a look at the [Contributing section][docs-contributing]. [docs-contributing]: ./contributing/issues-and-pull-requests.md -[docs-faq]: ./config/troubleshooting/faq.md -[docs-optionalconfig]: ./advanced/optional-config.md +[docs-faq]: ./faq.md +[docs-optionalconfig]: ./config/advanced/optional-config.md [docs-setupsh]: ./config/setup.sh.md [github-file-readme]: https://github.com/docker-mailserver/docker-mailserver/blob/master/README.md [github-file-env]: https://github.com/docker-mailserver/docker-mailserver/blob/master/ENVIRONMENT.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index baf62d3b..49f10003 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -101,7 +101,7 @@ nav: - 'Mailserver behind Proxy': examples/tutorials/mailserver-behind-proxy.md - 'Use Cases': - 'Forward-Only Mailserver with LDAP': examples/uses-cases/forward-only-mailserver-with-ldap-authentication.md - - 'FAQ' : faq/faq.md + - 'FAQ' : faq.md - 'Contributing': - 'Issues and Pull Requests': contributing/issues-and-pull-requests.md - 'Coding Style': contributing/coding-style.md From 463bc967d229a55de6522f4b08d3a6427a3f8f67 Mon Sep 17 00:00:00 2001 From: wernerfred <20406381+wernerfred@users.noreply.github.com> Date: Mon, 1 Mar 2021 21:32:45 +0100 Subject: [PATCH 11/14] docs(fix): Update wiki references to the new docs url Additionally replaces old references to `tvial` images with the new `mailserver` docker image name. --- .github/ISSUE_TEMPLATE/bug_report.md | 14 +++++++------- .github/ISSUE_TEMPLATE/config.yml | 4 ++-- .github/pull_request_template.md | 2 +- CODE_OF_CONDUCT.md | 2 +- CONTRIBUTING.md | 4 ++-- ENVIRONMENT.md | 4 ++-- README.md | 12 ++++++------ docs/content/config/advanced/auth-ldap.md | 2 +- docs/content/config/advanced/full-text-search.md | 2 +- docs/content/config/advanced/kubernetes.md | 2 +- docs/content/config/security/ssl.md | 6 +++--- .../contributing/issues-and-pull-requests.md | 4 ++-- .../examples/tutorials/basic-installation.md | 4 ++-- .../examples/tutorials/mailserver-behind-proxy.md | 2 +- docs/content/faq.md | 6 +++--- docs/content/index.md | 2 +- mailserver.env | 2 +- 17 files changed, 37 insertions(+), 37 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 37ef22cb..c54d02aa 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -13,21 +13,21 @@ Possible answers to your issue https://github.com/docker-mailserver/docker-mailserver#requirements * Email seen as spam: - https://github.com/docker-mailserver/docker-mailserver/wiki/Configure-SPF - https://github.com/docker-mailserver/docker-mailserver/wiki/Configure-DKIM + https://docker-mailserver.github.io/docker-mailserver/edge/config/best-practices/spf + https://docker-mailserver.github.io/docker-mailserver/edge/config/best-practices/dkim * Creating new domains and accounts - https://github.com/docker-mailserver/docker-mailserver/wiki/Configure-Accounts + https://docker-mailserver.github.io/docker-mailserver/edge/config/user-management/accounts * Use a relay mail server - https://github.com/docker-mailserver/docker-mailserver/wiki/Configure-AWS-SES + https://docker-mailserver.github.io/docker-mailserver/edge/config/advanced/mail-forwarding/aws-ses The variable name can be used for other email servers. * FAQ and tips - https://github.com/docker-mailserver/docker-mailserver/wiki/FAQ-and-Tips + https://docker-mailserver.github.io/docker-mailserver/edge/faq -* The wiki - https://github.com/docker-mailserver/docker-mailserver/wiki +* The documentation + https://docker-mailserver.github.io/docker-mailserver/edge * Open issues https://github.com/docker-mailserver/docker-mailserver/issues diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 9bd2dd71..ed2eb258 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,8 +2,8 @@ blank_issues_enabled: false contact_links: - - name: Wiki - url: https://github.com/docker-mailserver/docker-mailserver/wiki + - name: Documentation + url: https://docker-mailserver.github.io/docker-mailserver/edge about: Extended documentaton - visit this first before opening issues - name: Default Documentation url: https://github.com/docker-mailserver/docker-mailserver/blob/master/README.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 0a739ebf..9e6fda57 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -20,6 +20,6 @@ Fixes # (issue) - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas -- [ ] I have made corresponding changes to the documentation (README.md or ENVIRONMENT.md or the Wiki) +- [ ] I have made corresponding changes to the documentation (README.md or ENVIRONMENT.md or the documentation) - [ ] If necessary I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 63216313..17e8fe48 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -29,7 +29,7 @@ Examples of unacceptable behavior include: Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. -Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. +Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, documentation edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 12ec220b..44465d0f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing -This project is Open Source. That means that you can contribute on enhancements, bug fixing or improving the documentation in the [Wiki](https://github.com/docker-mailserver/docker-mailserver/wiki). +This project is Open Source. That means that you can contribute on enhancements, bug fixing or improving the [documentation](https://docker-mailserver.github.io/docker-mailserver/edge). 1. [Issues & PRs](#issues--prs) 1. [Opening an Issue](#opening-an-issue) @@ -13,7 +13,7 @@ This project is Open Source. That means that you can contribute on enhancements, ### Opening an Issue -**Before opening an issue**, read the [`README`](./README.md) carefully, use the [Wiki](https://github.com/docker-mailserver/docker-mailserver/wiki/), the Postfix/Dovecot documentation and your search engine you trust. The issue tracker is not meant to be used for unrelated questions! When opening an issue, please provide details use case to let the community reproduce your problem. Please start the mail server with env `DMS_DEBUG=1` and paste the output into the issue. **Use the issue templates** to provide the necessary information. Issues which do not use these templates are not worked on and closed. By raising issues, I agree to these terms and I understand, that the rules set for the issue tracker will help both maintainers as well as everyone to find a solution. +**Before opening an issue**, read the [`README`](./README.md) carefully, use the [Documentation](https://docker-mailserver.github.io/docker-mailserver/edge), the Postfix/Dovecot documentation and your search engine you trust. The issue tracker is not meant to be used for unrelated questions! When opening an issue, please provide details use case to let the community reproduce your problem. Please start the mail server with env `DMS_DEBUG=1` and paste the output into the issue. **Use the issue templates** to provide the necessary information. Issues which do not use these templates are not worked on and closed. By raising issues, I agree to these terms and I understand, that the rules set for the issue tracker will help both maintainers as well as everyone to find a solution. Maintainers take the time to improve on this project and help by solving issues together. It is therefore expected from others to make an effort and **comply with the rules**. diff --git a/ENVIRONMENT.md b/ENVIRONMENT.md index 89f60de2..ae3c0089 100644 --- a/ENVIRONMENT.md +++ b/ENVIRONMENT.md @@ -71,7 +71,7 @@ Otherwise, `iptables` won't be able to ban IPs. - Optional: `SSL_ALT_CERT_PATH` and `SSL_ALT_KEY_PATH` allow providing a 2nd certificate as a fallback for dual (aka hybrid) certificate support. Useful for ECDSA with an RSA fallback. Presently only `manual` mode supports this feature. - self-signed => Enables self-signed certificates. -Please read [the SSL page in the wiki](https://github.com/docker-mailserver/docker-mailserver/wiki/Configure-SSL) for more information. +Please read [the SSL page in the documentation](https://docker-mailserver.github.io/docker-mailserver/edge/config/security/ssl) for more information. ##### TLS_LEVEL @@ -144,7 +144,7 @@ Set the mailbox size limit for all users. If set to zero, the size will be unlim - **1** => Dovecot quota is enabled - 0 => Dovecot quota is disabled -See [mailbox quota](https://github.com/docker-mailserver/docker-mailserver/wiki/Configure-Accounts#mailbox-quota). +See [mailbox quota](https://docker-mailserver.github.io/docker-mailserver/edge/config/user-management/accounts/#notes). ##### POSTFIX\_MESSAGE\_SIZE\_LIMIT diff --git a/README.md b/README.md index 1518b0f3..330a23a1 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ A fullstack but simple mail server (SMTP, IMAP, LDAP, Antispam, Antivirus, etc.) ## Included Services - [Postfix](http://www.postfix.org) with SMTP or LDAP auth -- [Dovecot](https://www.dovecot.org) for SASL, IMAP (or POP3), with LDAP Auth, Sieve and [quotas](https://github.com/docker-mailserver/docker-mailserver/wiki/Configure-Accounts#mailbox-quota) +- [Dovecot](https://www.dovecot.org) for SASL, IMAP (or POP3), with LDAP Auth, Sieve and [quotas](https://docker-mailserver.github.io/docker-mailserver/edge/config/user-management/accounts#notes) - [Amavis](https://www.amavis.org/) - [Spamassasin](http://spamassassin.apache.org/) supporting custom rules - [ClamAV](https://www.clamav.net/) with automatic updates @@ -33,8 +33,8 @@ A fullstack but simple mail server (SMTP, IMAP, LDAP, Antispam, Antivirus, etc.) - [Postscreen](http://www.postfix.org/POSTSCREEN_README.html) - [Postgrey](https://postgrey.schweikert.ch/) - [LetsEncrypt](https://letsencrypt.org/) and self-signed certificates -- [Setup script](https://github.com/docker-mailserver/docker-mailserver/wiki/setup.sh) to easily configure and maintain your mailserver -- Basic [Sieve support](https://github.com/docker-mailserver/docker-mailserver/wiki/Configure-Sieve-filters) using dovecot +- [Setup script](https://docker-mailserver.github.io/docker-mailserver/edge/config/setup.sh) to easily configure and maintain your mailserver +- Basic [Sieve support](https://docker-mailserver.github.io/docker-mailserver/edge/config/advanced/mail-sieve) using dovecot - SASLauthd with LDAP auth - Persistent data and state - [CI/CD](https://github.com/docker-mailserver/docker-mailserver/actions) @@ -53,7 +53,7 @@ A fullstack but simple mail server (SMTP, IMAP, LDAP, Antispam, Antivirus, etc.) - 1 vCore - 512MB RAM -**Note:** You'll need to deactivate some services like ClamAV to be able to run on a host with 512MB of RAM. Even with 1G RAM you may run into problems without swap, see [FAQ](https://github.com/docker-mailserver/docker-mailserver/wiki/FAQ-and-Tips). +**Note:** You'll need to deactivate some services like ClamAV to be able to run on a host with 512MB of RAM. Even with 1G RAM you may run into problems without swap, see [FAQ](https://docker-mailserver.github.io/docker-mailserver/edge/faq/#what-system-requirements-are-required-to-run-docker-mailserver-effectively). ## Usage @@ -108,7 +108,7 @@ chmod a+x ./setup.sh - don't quote your values - variable substitution is *not* supported (e.g. `OVERRIDE_HOSTNAME=$HOSTNAME.$DOMAINNAME`). - Variables in `.env` are expanded in the `docker-compose.yml` file **only** and **not** in the container. The file `mailserver.env` serves this case where environment variables are used in the container. -- If you want to use a bare domain (host name = domain name), see [FAQ](https://github.com/docker-mailserver/docker-mailserver/wiki/FAQ-and-Tips#can-i-use-nakedbare-domains-no-host-name) +- If you want to use a bare domain (host name = domain name), see [FAQ](https://docker-mailserver.github.io/docker-mailserver/edge/faq#can-i-use-nakedbare-domains-no-host-name) ### Get up and running @@ -223,7 +223,7 @@ If you got any problems with SPF and/or forwarding mails, give [SRS](https://git 2. Receives email and filters for spam and viruses. For submitting outgoing mail you should prefer the submission ports(465, 587), which require authentication. Unless a relay host is configured, outgoing email will leave the server via port 25(thus outbound traffic must not be blocked by your provider or firewall). 3. A submission port since 2018, [RFC 8314](https://tools.ietf.org/html/rfc8314). Originally a secure variant of port 25. -See the [wiki](https://github.com/docker-mailserver/docker-mailserver/wiki) for further details and best practice advice, especially regarding security concerns. +See the [documentation](https://docker-mailserver.github.io/docker-mailserver/edge/config/security/understanding-the-ports/) for further details and best practice advice, especially regarding security concerns. ## Examples diff --git a/docs/content/config/advanced/auth-ldap.md b/docs/content/config/advanced/auth-ldap.md index e369d310..8ce8e2c5 100644 --- a/docs/content/config/advanced/auth-ldap.md +++ b/docs/content/config/advanced/auth-ldap.md @@ -39,7 +39,7 @@ version: '2' services: mail: - image: tvial/docker-mailserver:latest + image: mailserver/docker-mailserver:latest hostname: mail domainname: domain.com container_name: mail diff --git a/docs/content/config/advanced/full-text-search.md b/docs/content/config/advanced/full-text-search.md index 29a88d58..8a626bb4 100644 --- a/docs/content/config/advanced/full-text-search.md +++ b/docs/content/config/advanced/full-text-search.md @@ -20,7 +20,7 @@ The [dovecot-solr Plugin](https://wiki2.dovecot.org/Plugins/FTS/Solr) is used in restart: always mailserver: - image: tvial/docker-mailserver:latest + image: mailserver/docker-mailserver:latest ... volumes: ... diff --git a/docs/content/config/advanced/kubernetes.md b/docs/content/config/advanced/kubernetes.md index bf4d90cd..c3f4c86f 100644 --- a/docs/content/config/advanced/kubernetes.md +++ b/docs/content/config/advanced/kubernetes.md @@ -144,7 +144,7 @@ spec: # mountPath: /tmp/files containers: - name: docker-mailserver - image: tvial/docker-mailserver:latest + image: mailserver/docker-mailserver:latest imagePullPolicy: Always volumeMounts: - name: config diff --git a/docs/content/config/security/ssl.md b/docs/content/config/security/ssl.md index 95a8f7f1..c151e2e4 100644 --- a/docs/content/config/security/ssl.md +++ b/docs/content/config/security/ssl.md @@ -190,7 +190,7 @@ The second part of the setup is the actual mail container. So, in another folder version: '2' services: mail: - image: tvial/docker-mailserver:latest + image: mailserver/docker-mailserver:latest hostname: ${HOSTNAME} domainname: ${DOMAINNAME} container_name: ${CONTAINER_NAME} @@ -379,7 +379,7 @@ This allows for support of wild card certificates: `SSL_DOMAIN=*.example.com`. H version: '3.8' services: mail: - image: tvial/docker-mailserver:stable + image: mailserver/docker-mailserver:stable hostname: mail domainname: example.com volumes: @@ -427,7 +427,7 @@ Depending of your Traefik configuration, certificates may be stored using a file You can easily generate a self-signed SSL certificate by using the following command: ```sh -docker run -it --rm -v "$(pwd)"/config/ssl:/tmp/docker-mailserver/ssl -h mail.my-domain.com -t tvial/docker-mailserver generate-ssl-certificate +docker run -it --rm -v "$(pwd)"/config/ssl:/tmp/docker-mailserver/ssl -h mail.my-domain.com -t mailserver/docker-mailserver generate-ssl-certificate # Press enter # Enter a password when needed diff --git a/docs/content/contributing/issues-and-pull-requests.md b/docs/content/contributing/issues-and-pull-requests.md index 801e06f0..6ae3cb48 100644 --- a/docs/content/contributing/issues-and-pull-requests.md +++ b/docs/content/contributing/issues-and-pull-requests.md @@ -8,7 +8,7 @@ This project is Open Source. That means that you can contribute on enhancements, !!! attention - **Before opening an issue**, read the [`README`][github-file-readme] carefully, use the [Wiki][wiki], the Postfix/Dovecot documentation and your search engine you trust. The issue tracker is not meant to be used for unrelated questions! + **Before opening an issue**, read the [`README`][github-file-readme] carefully, study the [documentation][docs], the Postfix/Dovecot documentation and your search engine you trust. The issue tracker is not meant to be used for unrelated questions! When opening an issue, please provide details use case to let the community reproduce your problem. Please start the mail server with env `DMS_DEBUG=1` and paste the output into the issue. @@ -43,7 +43,7 @@ The development workflow is the following: 2. When your changes are validated, your branch is merged 3. CI builds the new `:edge` image immediately and your changes will be includes in the next version release. -[wiki]: https://docker-mailserver.github.io/docker-mailserver +[docs]: https://docker-mailserver.github.io/docker-mailserver/edge [github-file-readme]: https://github.com/docker-mailserver/docker-mailserver/blob/master/README.md [github-file-env]: https://github.com/docker-mailserver/docker-mailserver/blob/master/ENVIRONMENT.md [commit]: https://help.github.com/articles/closing-issues-via-commit-messages/ diff --git a/docs/content/examples/tutorials/basic-installation.md b/docs/content/examples/tutorials/basic-installation.md index 7b63fab0..50f934ff 100644 --- a/docs/content/examples/tutorials/basic-installation.md +++ b/docs/content/examples/tutorials/basic-installation.md @@ -27,7 +27,7 @@ We are going to use this docker based mailserver: services: mail: - image: tvial/docker-mailserver:latest + image: mailserver/docker-mailserver:latest hostname: mail domainname: example.org container_name: mail @@ -76,7 +76,7 @@ We are going to use this docker based mailserver: On your server you may have to do it differently. -- Pull the docker image: `docker pull tvial/docker-mailserver:latest` +- Pull the docker image: `docker pull mailserver/docker-mailserver:latest` - Now generate the DKIM keys with `./setup.sh config dkim` and copy the content of the file `config/opendkim/keys/domain.tld/mail.txt` on the domain zone configuration at the DNS server. I use [bind9](https://github.com/docker-scripts/bind9) for managing my domains, so I just paste it on `example.org.db`: diff --git a/docs/content/examples/tutorials/mailserver-behind-proxy.md b/docs/content/examples/tutorials/mailserver-behind-proxy.md index febccbf7..cc4c5620 100644 --- a/docs/content/examples/tutorials/mailserver-behind-proxy.md +++ b/docs/content/examples/tutorials/mailserver-behind-proxy.md @@ -60,7 +60,7 @@ Feel free to add your configuration if you achived the same goal using different version: '2' services: mail: - image: tvial/docker-mailserver:release-v7.2.0 + image: mailserver/docker-mailserver:release-v7.2.0 restart: always networks: - proxy diff --git a/docs/content/faq.md b/docs/content/faq.md index e142bbe4..d55e56f8 100644 --- a/docs/content/faq.md +++ b/docs/content/faq.md @@ -187,7 +187,7 @@ Then with plain `docker-compose`: ```yaml services: mail: - image: tvial/docker-mailserver:latest + image: mailserver/docker-mailserver:latest volumes: - ./cron/sa-learn:/etc/cron.d/sa-learn ``` @@ -199,7 +199,7 @@ version: "3.3" services: mail: - image: tvial/docker-mailserver:latest + image: mailserver/docker-mailserver:latest # ... configs: - source: my_sa_crontab @@ -351,7 +351,7 @@ proxy_interfaces = X.X.X.X (your public IP) ### What About Updates You can of course use a own script or every now and then pull && stop && rm && start the images but there are tools available for this. -There is a page in the [Update and Cleanup][docs-maintenance] wiki page that explains how to use it the docker way. +There is a section in the [Update and Cleanup][docs-maintenance] documentation page that explains how to use it the docker way. ### How to adjust settings with the `user-patches.sh` script diff --git a/docs/content/index.md b/docs/content/index.md index 14485769..923ee83c 100644 --- a/docs/content/index.md +++ b/docs/content/index.md @@ -6,7 +6,7 @@ title: Home Please first have a look at the [`README.md`][github-file-readme] to setup and configure this server. -This wiki provides you with advanced configuration, detailed examples, and hints. +This documentation provides you with advanced configuration, detailed examples, and hints. ## Getting Started diff --git a/mailserver.env b/mailserver.env index 0eb4e328..a039a779 100644 --- a/mailserver.env +++ b/mailserver.env @@ -83,7 +83,7 @@ POSTSCREEN_ACTION=enforce # 1 => only launch postfix smtp SMTP_ONLY= -# Please read [the SSL page in the wiki](https://github.com/docker-mailserver/docker-mailserver/wiki/Configure-SSL) for more information. +# Please read [the SSL page in the documentation](https://docker-mailserver.github.io/docker-mailserver/edge/config/security/ssl) for more information. # # empty => SSL disabled # letsencrypt => Enables Let's Encrypt certificates From 711b4c9d83f5ee4b21b78823fcead12c6f646a7e Mon Sep 17 00:00:00 2001 From: wernerfred <20406381+wernerfred@users.noreply.github.com> Date: Tue, 2 Mar 2021 17:39:06 +0100 Subject: [PATCH 12/14] docs(refactor): Convert more content to use admonitions + improvements --- docs/content/config/advanced/auth-ldap.md | 171 ++-- docs/content/config/advanced/kubernetes.md | 783 +++++++++--------- .../content/config/advanced/mail-fetchmail.md | 36 +- .../advanced/mail-forwarding/aws-ses.md | 2 +- .../advanced/mail-forwarding/relay-hosts.md | 10 +- docs/content/config/advanced/mail-sieve.md | 57 +- .../advanced/override-defaults/dovecot.md | 2 +- .../advanced/override-defaults/postfix.md | 4 +- docs/content/config/best-practices/dkim.md | 5 +- docs/content/config/best-practices/dmarc.md | 3 +- docs/content/config/best-practices/spf.md | 6 +- docs/content/config/pop3.md | 7 +- docs/content/config/security/ssl.md | 374 +++++---- docs/content/config/setup.sh.md | 4 + .../examples/tutorials/basic-installation.md | 66 +- ...nly-mailserver-with-ldap-authentication.md | 8 +- docs/content/faq.md | 117 +-- docs/content/introduction.md | 21 +- 18 files changed, 884 insertions(+), 792 deletions(-) diff --git a/docs/content/config/advanced/auth-ldap.md b/docs/content/config/advanced/auth-ldap.md index 8ce8e2c5..66e641e1 100644 --- a/docs/content/config/advanced/auth-ldap.md +++ b/docs/content/config/advanced/auth-ldap.md @@ -6,106 +6,107 @@ title: 'Advanced | LDAP Authentication' Getting started with ldap and this mailserver we need to take 3 parts in account: -* POSTFIX -* DOVECOT -* SASLAUTHD (this can also be handled by dovecot above) +- `postfix` +- `dovecot` +- `saslauthd` (this can also be handled by dovecot) ## Variables to Control Provisioning by the Container -__POSTFIX__: +Have a look at the [`ENVIRONMENT.md`][github-file-env] for information on the default values. -* `LDAP_QUERY_FILTER_USER` -* `LDAP_QUERY_FILTER_GROUP` -* `LDAP_QUERY_FILTER_ALIAS` -* `LDAP_QUERY_FILTER_DOMAIN` +!!! example "postfix" -__SASLAUTHD__: + - `LDAP_QUERY_FILTER_USER` + - `LDAP_QUERY_FILTER_GROUP` + - `LDAP_QUERY_FILTER_ALIAS` + - `LDAP_QUERY_FILTER_DOMAIN` -* `SASLAUTHD_LDAP_FILTER` +!!! example "saslauthd" -__DOVECOT__: + - `SASLAUTHD_LDAP_FILTER` -* `DOVECOT_USER_FILTER` -* `DOVECOT_PASS_FILTER` +!!! example "dovecot" -!!! note - This page will provide several use cases like recipes to show, how this project can be used with it's LDAP Features. + - `DOVECOT_USER_FILTER` + - `DOVECOT_PASS_FILTER` ## LDAP Setup - Kopano / Zarafa -```yaml ---- -version: '2' +???+ example "Example Code" -services: - mail: - image: mailserver/docker-mailserver:latest - hostname: mail - domainname: domain.com - container_name: mail + ```yaml + --- + version: '2' - ports: - - "25:25" - - "143:143" - - "587:587" - - "993:993" + services: + mail: + image: mailserver/docker-mailserver:latest + hostname: mail + domainname: domain.com + container_name: mail + + ports: + - "25:25" + - "143:143" + - "587:587" + - "993:993" + + volumes: + - maildata:/var/mail + - mailstate:/var/mail-state + - ./config/:/tmp/docker-mailserver/ + + environment: + # We are not using dovecot here + - SMTP_ONLY=1 + - ENABLE_SPAMASSASSIN=1 + - ENABLE_CLAMAV=1 + - ENABLE_FAIL2BAN=1 + - ENABLE_POSTGREY=1 + - SASLAUTHD_PASSWD= + + # >>> SASL Authentication + - ENABLE_SASLAUTHD=1 + - SASLAUTHD_LDAP_SERVER= + - SASLAUTHD_LDAP_PROTO= + - SASLAUTHD_LDAP_BIND_DN=cn=Administrator,cn=Users,dc=mydomain,dc=loc + - SASLAUTHD_LDAP_PASSWORD=mypassword + - SASLAUTHD_LDAP_SEARCH_BASE=dc=mydomain,dc=loc + - SASLAUTHD_LDAP_FILTER=(&(sAMAccountName=%U)(objectClass=person)) + - SASLAUTHD_MECHANISMS=ldap + # <<< SASL Authentication + + # >>> Postfix Ldap Integration + - ENABLE_LDAP=1 + - LDAP_SERVER_HOST= + - LDAP_SEARCH_BASE=dc=mydomain,dc=loc + - LDAP_BIND_DN=cn=Administrator,cn=Users,dc=mydomain,dc=loc + - LDAP_BIND_PW=mypassword + - LDAP_QUERY_FILTER_USER=(&(objectClass=user)(mail=%s)) + - LDAP_QUERY_FILTER_GROUP=(&(objectclass=group)(mail=%s)) + - LDAP_QUERY_FILTER_ALIAS=(&(objectClass=user)(otherMailbox=%s)) + - LDAP_QUERY_FILTER_DOMAIN=(&(|(mail=*@%s)(mailalias=*@%s)(mailGroupMember=*@%s))(mailEnabled=TRUE)) + # <<< Postfix Ldap Integration + + # >>> Kopano Integration + - ENABLE_POSTFIX_VIRTUAL_TRANSPORT=1 + - POSTFIX_DAGENT=lmtp:kopano:2003 + # <<< Kopano Integration + + - ONE_DIR=1 + - DMS_DEBUG=0 + - SSL_TYPE=letsencrypt + - PERMIT_DOCKER=host + + cap_add: + - NET_ADMIN volumes: - - maildata:/var/mail - - mailstate:/var/mail-state - - ./config/:/tmp/docker-mailserver/ - - environment: - # We are not using dovecot here - - SMTP_ONLY=1 - - ENABLE_SPAMASSASSIN=1 - - ENABLE_CLAMAV=1 - - ENABLE_FAIL2BAN=1 - - ENABLE_POSTGREY=1 - - SASLAUTHD_PASSWD= - - # >>> SASL Authentication - - ENABLE_SASLAUTHD=1 - - SASLAUTHD_LDAP_SERVER= - - SASLAUTHD_LDAP_PROTO= - - SASLAUTHD_LDAP_BIND_DN=cn=Administrator,cn=Users,dc=mydomain,dc=loc - - SASLAUTHD_LDAP_PASSWORD=mypassword - - SASLAUTHD_LDAP_SEARCH_BASE=dc=mydomain,dc=loc - - SASLAUTHD_LDAP_FILTER=(&(sAMAccountName=%U)(objectClass=person)) - - SASLAUTHD_MECHANISMS=ldap - # <<< SASL Authentication - - # >>> Postfix Ldap Integration - - ENABLE_LDAP=1 - - LDAP_SERVER_HOST= - - LDAP_SEARCH_BASE=dc=mydomain,dc=loc - - LDAP_BIND_DN=cn=Administrator,cn=Users,dc=mydomain,dc=loc - - LDAP_BIND_PW=mypassword - - LDAP_QUERY_FILTER_USER=(&(objectClass=user)(mail=%s)) - - LDAP_QUERY_FILTER_GROUP=(&(objectclass=group)(mail=%s)) - - LDAP_QUERY_FILTER_ALIAS=(&(objectClass=user)(otherMailbox=%s)) - - LDAP_QUERY_FILTER_DOMAIN=(&(|(mail=*@%s)(mailalias=*@%s)(mailGroupMember=*@%s))(mailEnabled=TRUE)) - # <<< Postfix Ldap Integration - - # >>> Kopano Integration - - ENABLE_POSTFIX_VIRTUAL_TRANSPORT=1 - - POSTFIX_DAGENT=lmtp:kopano:2003 - # <<< Kopano Integration - - - ONE_DIR=1 - - DMS_DEBUG=0 - - SSL_TYPE=letsencrypt - - PERMIT_DOCKER=host - - cap_add: - - NET_ADMIN - -volumes: - maildata: - driver: local - mailstate: - driver: local -``` + maildata: + driver: local + mailstate: + driver: local + ``` If your directory has not the postfix-book schema installed, then you must change the internal attribute handling for dovecot. For this you have to change the `pass_attr` and the `user_attr` mapping, as shown in the example below: @@ -122,3 +123,5 @@ The following example illustrates this for a directory that has the qmail-schema - DOVECOT_PASS_FILTER=(&(objectClass=qmailUser)(uid=%u)(accountStatus=active)) - DOVECOT_USER_FILTER=(&(objectClass=qmailUser)(uid=%u)(accountStatus=active)) ``` + +[github-file-env]: https://github.com/docker-mailserver/docker-mailserver/blob/master/ENVIRONMENT.md \ No newline at end of file diff --git a/docs/content/config/advanced/kubernetes.md b/docs/content/config/advanced/kubernetes.md index c3f4c86f..357e3621 100644 --- a/docs/content/config/advanced/kubernetes.md +++ b/docs/content/config/advanced/kubernetes.md @@ -6,231 +6,258 @@ title: 'Advanced | Kubernetes' There is nothing much in deploying mailserver to Kubernetes itself. The things are pretty same as in [`docker-compose.yml`][github-file-compose], but with Kubernetes syntax. -```yaml -apiVersion: v1 -kind: Namespace -metadata: - name: mailserver ---- -kind: ConfigMap -apiVersion: v1 -metadata: - name: mailserver.env.config - namespace: mailserver - labels: - app: mailserver -data: - OVERRIDE_HOSTNAME: example.com - ENABLE_FETCHMAIL: "0" - FETCHMAIL_POLL: "120" - ENABLE_SPAMASSASSIN: "0" - ENABLE_CLAMAV: "0" - ENABLE_FAIL2BAN: "0" - ENABLE_POSTGREY: "0" - ONE_DIR: "1" - DMS_DEBUG: "0" +??? example "ConfigMap" ---- -kind: ConfigMap -apiVersion: v1 -metadata: - name: mailserver.config - namespace: mailserver - labels: - app: mailserver -data: - postfix-accounts.cf: | - user1@example.com|{SHA512-CRYPT}$6$2YpW1nYtPBs2yLYS$z.5PGH1OEzsHHNhl3gJrc3D.YMZkvKw/vp.r5WIiwya6z7P/CQ9GDEJDr2G2V0cAfjDFeAQPUoopsuWPXLk3u1 - - postfix-virtual.cf: | - alias1@example.com user1@dexample.com - - #dovecot.cf: | - # service stats { - # unix_listener stats-reader { - # group = docker - # mode = 0666 - # } - # unix_listener stats-writer { - # group = docker - # mode = 0666 - # } - # } - - SigningTable: | - *@example.com mail._domainkey.example.com - - KeyTable: | - mail._domainkey.example.com example.com:mail:/etc/opendkim/keys/example.com-mail.key - - TrustedHosts: | - 127.0.0.1 - localhost - - #user-patches.sh: | - # #!/bin/bash - - #fetchmail.cf: | - ---- -kind: Secret -apiVersion: v1 -metadata: - name: mailserver.opendkim.keys - namespace: mailserver - labels: - app: mailserver -type: Opaque -data: - example.com-mail.key: 'base64-encoded-DKIM-key' - ---- -kind: Service -apiVersion: v1 -metadata: - name: mailserver - namespace: mailserver - labels: - app: mailserver -spec: - selector: - app: mailserver - ports: - - name: smtp - port: 25 - targetPort: smtp - - name: smtp-secure - port: 465 - targetPort: smtp-secure - - name: smtp-auth - port: 587 - targetPort: smtp-auth - - name: imap - port: 143 - targetPort: imap - - name: imap-secure - port: 993 - targetPort: imap-secure ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: mailserver - namespace: mailserver -spec: - replicas: 1 - selector: - matchLabels: - app: mailserver - template: + ```yaml + apiVersion: v1 + kind: Namespace metadata: + name: mailserver + --- + kind: ConfigMap + apiVersion: v1 + metadata: + name: mailserver.env.config + namespace: mailserver labels: app: mailserver - role: mail - tier: backend - spec: - #nodeSelector: - # kubernetes.io/hostname: local.k8s - #initContainers: - #- name: init-myservice - # image: busybox - # command: ["/bin/sh", "-c", "cp /tmp/user-patches.sh /tmp/files"] - # volumeMounts: - # - name: config - # subPath: user-patches.sh - # mountPath: /tmp/user-patches.sh - # readOnly: true - # - name: tmp-files - # mountPath: /tmp/files - containers: - - name: docker-mailserver - image: mailserver/docker-mailserver:latest - imagePullPolicy: Always - volumeMounts: - - name: config - subPath: postfix-accounts.cf - mountPath: /tmp/docker-mailserver/postfix-accounts.cf - readOnly: true - #- name: config - # subPath: postfix-main.cf - # mountPath: /tmp/docker-mailserver/postfix-main.cf - # readOnly: true - - name: config - subPath: postfix-virtual.cf - mountPath: /tmp/docker-mailserver/postfix-virtual.cf - readOnly: true - - name: config - subPath: fetchmail.cf - mountPath: /tmp/docker-mailserver/fetchmail.cf - readOnly: true - - name: config - subPath: dovecot.cf - mountPath: /tmp/docker-mailserver/dovecot.cf - readOnly: true - #- name: config - # subPath: user1.example.com.dovecot.sieve - # mountPath: /tmp/docker-mailserver/user1@example.com.dovecot.sieve - # readOnly: true - #- name: tmp-files - # subPath: user-patches.sh - # mountPath: /tmp/docker-mailserver/user-patches.sh - - name: config - subPath: SigningTable - mountPath: /tmp/docker-mailserver/opendkim/SigningTable - readOnly: true - - name: config - subPath: KeyTable - mountPath: /tmp/docker-mailserver/opendkim/KeyTable - readOnly: true - - name: config - subPath: TrustedHosts - mountPath: /tmp/docker-mailserver/opendkim/TrustedHosts - readOnly: true - - name: opendkim-keys - mountPath: /tmp/docker-mailserver/opendkim/keys - readOnly: true - - name: data - mountPath: /var/mail - subPath: data - - name: data - mountPath: /var/mail-state - subPath: state - - name: data - mountPath: /var/log/mail - subPath: log - ports: - - name: smtp - containerPort: 25 - protocol: TCP - - name: smtp-secure - containerPort: 465 - protocol: TCP - - name: smtp-auth - containerPort: 587 - - name: imap - containerPort: 143 - protocol: TCP - - name: imap-secure - containerPort: 993 - protocol: TCP - envFrom: - - configMapRef: - name: mailserver.env.config - volumes: - - name: config - configMap: - name: mailserver.config - - name: opendkim-keys - secret: - secretName: mailserver.opendkim.keys - - name: data - persistentVolumeClaim: - claimName: mail-storage - - name: tmp-files - emptyDir: {} -``` + data: + OVERRIDE_HOSTNAME: example.com + ENABLE_FETCHMAIL: "0" + FETCHMAIL_POLL: "120" + ENABLE_SPAMASSASSIN: "0" + ENABLE_CLAMAV: "0" + ENABLE_FAIL2BAN: "0" + ENABLE_POSTGREY: "0" + ONE_DIR: "1" + DMS_DEBUG: "0" -!!! note + --- + kind: ConfigMap + apiVersion: v1 + metadata: + name: mailserver.config + namespace: mailserver + labels: + app: mailserver + data: + postfix-accounts.cf: | + user1@example.com|{SHA512-CRYPT}$6$2YpW1nYtPBs2yLYS$z.5PGH1OEzsHHNhl3gJrc3D.YMZkvKw/vp.r5WIiwya6z7P/CQ9GDEJDr2G2V0cAfjDFeAQPUoopsuWPXLk3u1 + + postfix-virtual.cf: | + alias1@example.com user1@dexample.com + + #dovecot.cf: | + # service stats { + # unix_listener stats-reader { + # group = docker + # mode = 0666 + # } + # unix_listener stats-writer { + # group = docker + # mode = 0666 + # } + # } + + SigningTable: | + *@example.com mail._domainkey.example.com + + KeyTable: | + mail._domainkey.example.com example.com:mail:/etc/opendkim/keys/example.com-mail.key + + TrustedHosts: | + 127.0.0.1 + localhost + + #user-patches.sh: | + # #!/bin/bash + + #fetchmail.cf: | + ``` + +??? example "Secret" + + ```yaml + apiVersion: v1 + kind: Namespace + metadata: + name: mailserver + --- + kind: Secret + apiVersion: v1 + metadata: + name: mailserver.opendkim.keys + namespace: mailserver + labels: + app: mailserver + type: Opaque + data: + example.com-mail.key: 'base64-encoded-DKIM-key' + ``` + +??? example "Service" + + ```yaml + apiVersion: v1 + kind: Namespace + metadata: + name: mailserver + --- + kind: Service + apiVersion: v1 + metadata: + name: mailserver + namespace: mailserver + labels: + app: mailserver + spec: + selector: + app: mailserver + ports: + - name: smtp + port: 25 + targetPort: smtp + - name: smtp-secure + port: 465 + targetPort: smtp-secure + - name: smtp-auth + port: 587 + targetPort: smtp-auth + - name: imap + port: 143 + targetPort: imap + - name: imap-secure + port: 993 + targetPort: imap-secure + ``` + +??? example "Deployment" + + ```yaml + apiVersion: v1 + kind: Namespace + metadata: + name: mailserver + --- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: mailserver + namespace: mailserver + spec: + replicas: 1 + selector: + matchLabels: + app: mailserver + template: + metadata: + labels: + app: mailserver + role: mail + tier: backend + spec: + #nodeSelector: + # kubernetes.io/hostname: local.k8s + #initContainers: + #- name: init-myservice + # image: busybox + # command: ["/bin/sh", "-c", "cp /tmp/user-patches.sh /tmp/files"] + # volumeMounts: + # - name: config + # subPath: user-patches.sh + # mountPath: /tmp/user-patches.sh + # readOnly: true + # - name: tmp-files + # mountPath: /tmp/files + containers: + - name: docker-mailserver + image: mailserver/docker-mailserver:latest + imagePullPolicy: Always + volumeMounts: + - name: config + subPath: postfix-accounts.cf + mountPath: /tmp/docker-mailserver/postfix-accounts.cf + readOnly: true + #- name: config + # subPath: postfix-main.cf + # mountPath: /tmp/docker-mailserver/postfix-main.cf + # readOnly: true + - name: config + subPath: postfix-virtual.cf + mountPath: /tmp/docker-mailserver/postfix-virtual.cf + readOnly: true + - name: config + subPath: fetchmail.cf + mountPath: /tmp/docker-mailserver/fetchmail.cf + readOnly: true + - name: config + subPath: dovecot.cf + mountPath: /tmp/docker-mailserver/dovecot.cf + readOnly: true + #- name: config + # subPath: user1.example.com.dovecot.sieve + # mountPath: /tmp/docker-mailserver/user1@example.com.dovecot.sieve + # readOnly: true + #- name: tmp-files + # subPath: user-patches.sh + # mountPath: /tmp/docker-mailserver/user-patches.sh + - name: config + subPath: SigningTable + mountPath: /tmp/docker-mailserver/opendkim/SigningTable + readOnly: true + - name: config + subPath: KeyTable + mountPath: /tmp/docker-mailserver/opendkim/KeyTable + readOnly: true + - name: config + subPath: TrustedHosts + mountPath: /tmp/docker-mailserver/opendkim/TrustedHosts + readOnly: true + - name: opendkim-keys + mountPath: /tmp/docker-mailserver/opendkim/keys + readOnly: true + - name: data + mountPath: /var/mail + subPath: data + - name: data + mountPath: /var/mail-state + subPath: state + - name: data + mountPath: /var/log/mail + subPath: log + ports: + - name: smtp + containerPort: 25 + protocol: TCP + - name: smtp-secure + containerPort: 465 + protocol: TCP + - name: smtp-auth + containerPort: 587 + - name: imap + containerPort: 143 + protocol: TCP + - name: imap-secure + containerPort: 993 + protocol: TCP + envFrom: + - configMapRef: + name: mailserver.env.config + volumes: + - name: config + configMap: + name: mailserver.config + - name: opendkim-keys + secret: + secretName: mailserver.opendkim.keys + - name: data + persistentVolumeClaim: + claimName: mail-storage + - name: tmp-files + emptyDir: {} + ``` + +!!! warning Any sensitive data (keys, etc) should be deployed via [Secrets][k8s-config-secret]. Other configuration just fits well into [ConfigMaps][k8s-config-pod]. !!! note @@ -246,54 +273,58 @@ Preserving real client IP is relatively [non-trivial in Kubernetes][k8s-service- If you do not require SPF checks for incoming mails you may disable them in [Postfix configuration][docs-postfix] by dropping following line (which removes `check_policy_service unix:private/policyd-spf` option): -```yaml -kind: ConfigMap -apiVersion: v1 -metadata: - name: mailserver.config - labels: - app: mailserver -data: - postfix-main.cf: | - smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination, reject_unauth_pipelining, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, reject_unknown_recipient_domain, reject_rbl_client zen.spamhaus.org, reject_rbl_client bl.spamcop.net -# ... +!!! example ---- + ```yaml + kind: ConfigMap + apiVersion: v1 + metadata: + name: mailserver.config + labels: + app: mailserver + data: + postfix-main.cf: | + smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination, reject_unauth_pipelining, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, reject_unknown_recipient_domain, reject_rbl_client zen.spamhaus.org, reject_rbl_client bl.spamcop.net + # ... -kind: Deployment -apiVersion: extensions/v1beta1 -metadata: - name: mailserver -# ... - volumeMounts: - - name: config - subPath: postfix-main.cf - mountPath: /tmp/docker-mailserver/postfix-main.cf - readOnly: true -``` + --- + + kind: Deployment + apiVersion: extensions/v1beta1 + metadata: + name: mailserver + # ... + volumeMounts: + - name: config + subPath: postfix-main.cf + mountPath: /tmp/docker-mailserver/postfix-main.cf + readOnly: true + ``` ### External IPs Service The simplest way is to expose mailserver as a [Service][k8s-network-service] with [external IPs][k8s-network-external-ip]. -```yaml -kind: Service -apiVersion: v1 -metadata: - name: mailserver - labels: - app: mailserver -spec: - selector: - app: mailserver - ports: - - name: smtp - port: 25 - targetPort: smtp -# ... - externalIPs: - - 80.11.12.10 -``` +!!! example + + ```yaml + kind: Service + apiVersion: v1 + metadata: + name: mailserver + labels: + app: mailserver + spec: + selector: + app: mailserver + ports: + - name: smtp + port: 25 + targetPort: smtp + # ... + externalIPs: + - 80.11.12.10 + ``` **Downsides** @@ -313,29 +344,31 @@ The [Proxy Pod][k8s-proxy-service] helps to avoid necessity of specifying extern The simplest way to preserve real client IP is to use `hostPort` and `hostNetwork: true` in the mailserver [Pod][k8s-workload-pod]. This comes in price of availability: you can talk to mailserver from outside world only via IPs of [Node][k8s-nodes] where mailserver is deployed. -```yaml -kind: Deployment -apiVersion: extensions/v1beta1 -metadata: - name: mailserver -# ... - spec: - hostNetwork: true -# ... - containers: -# ... - ports: - - name: smtp - containerPort: 25 - hostPort: 25 - - name: smtp-auth - containerPort: 587 - hostPort: 587 - - name: imap-secure - containerPort: 993 - hostPort: 993 -# ... -``` +!!! example + + ```yaml + kind: Deployment + apiVersion: extensions/v1beta1 + metadata: + name: mailserver + # ... + spec: + hostNetwork: true + # ... + containers: + # ... + ports: + - name: smtp + containerPort: 25 + hostPort: 25 + - name: smtp-auth + containerPort: 587 + hostPort: 587 + - name: imap-secure + containerPort: 993 + hostPort: 993 + # ... + ``` **Downsides** @@ -363,53 +396,55 @@ With [HAProxy][dockerhub-haproxy], the configuration should look similar to the Then, configure both [Postfix][docs-postfix] and [Dovecot][docs-dovecot] to expect the PROXY protocol: -```yaml -kind: ConfigMap -apiVersion: v1 -metadata: - name: mailserver.config - labels: - app: mailserver -data: - postfix-main.cf: | - postscreen_upstream_proxy_protocol = haproxy - postfix-master.cf: | - submission/inet/smtpd_upstream_proxy_protocol=haproxy - smtps/inet/smtpd_upstream_proxy_protocol=haproxy - dovecot.cf: | - # Assuming your ingress controller is bound to 10.0.0.0/8 - haproxy_trusted_networks = 10.0.0.0/8, 127.0.0.0/8 - service imap-login { - inet_listener imaps { - haproxy = yes - } - } -# ... ---- +!!! example -kind: Deployment -apiVersion: extensions/v1beta1 -metadata: - name: mailserver -spec: - template: + ```yaml + kind: ConfigMap + apiVersion: v1 + metadata: + name: mailserver.config + labels: + app: mailserver + data: + postfix-main.cf: | + postscreen_upstream_proxy_protocol = haproxy + postfix-master.cf: | + submission/inet/smtpd_upstream_proxy_protocol=haproxy + smtps/inet/smtpd_upstream_proxy_protocol=haproxy + dovecot.cf: | + # Assuming your ingress controller is bound to 10.0.0.0/8 + haproxy_trusted_networks = 10.0.0.0/8, 127.0.0.0/8 + service imap-login { + inet_listener imaps { + haproxy = yes + } + } + # ... + --- + + kind: Deployment + apiVersion: extensions/v1beta1 + metadata: + name: mailserver spec: - containers: - - name: docker-mailserver - volumeMounts: - - name: config - subPath: postfix-main.cf - mountPath: /tmp/docker-mailserver/postfix-main.cf - readOnly: true - - name: config - subPath: postfix-master.cf - mountPath: /tmp/docker-mailserver/postfix-master.cf - readOnly: true - - name: config - subPath: dovecot.cf - mountPath: /tmp/docker-mailserver/dovecot.cf - readOnly: true -``` + template: + spec: + containers: + - name: docker-mailserver + volumeMounts: + - name: config + subPath: postfix-main.cf + mountPath: /tmp/docker-mailserver/postfix-main.cf + readOnly: true + - name: config + subPath: postfix-master.cf + mountPath: /tmp/docker-mailserver/postfix-master.cf + readOnly: true + - name: config + subPath: dovecot.cf + mountPath: /tmp/docker-mailserver/dovecot.cf + readOnly: true + ``` **Downsides** @@ -419,52 +454,56 @@ spec: [Kube-Lego][kube-lego] may be used for a role of Let's Encrypt client. It works with Kubernetes [Ingress Resources][k8s-network-ingress] and automatically issues/manages certificates/keys for exposed services via Ingresses. -```yaml -kind: Ingress -apiVersion: extensions/v1beta1 -metadata: - name: mailserver - labels: - app: mailserver - annotations: - kubernetes.io/tls-acme: 'true' -spec: - rules: - - host: example.com - http: - paths: - - path: / - backend: - serviceName: default-backend - servicePort: 80 - tls: - - secretName: mailserver.tls - hosts: - - example.com -``` +!!! example + + ```yaml + kind: Ingress + apiVersion: extensions/v1beta1 + metadata: + name: mailserver + labels: + app: mailserver + annotations: + kubernetes.io/tls-acme: 'true' + spec: + rules: + - host: example.com + http: + paths: + - path: / + backend: + serviceName: default-backend + servicePort: 80 + tls: + - secretName: mailserver.tls + hosts: + - example.com + ``` Now, you can use Let's Encrypt cert and key from `mailserver.tls` [Secret][k8s-config-secret] in your [Pod][k8s-workload-pod] spec: -```yaml -# ... -env: - - name: SSL_TYPE - value: 'manual' - - name: SSL_CERT_PATH - value: '/etc/ssl/mailserver/tls.crt' - - name: SSL_KEY_PATH - value: '/etc/ssl/mailserver/tls.key' -# ... -volumeMounts: - - name: tls - mountPath: /etc/ssl/mailserver - readOnly: true -# ... -volumes: - - name: tls - secret: - secretName: mailserver.tls -``` +!!! example + + ```yaml + # ... + env: + - name: SSL_TYPE + value: 'manual' + - name: SSL_CERT_PATH + value: '/etc/ssl/mailserver/tls.crt' + - name: SSL_KEY_PATH + value: '/etc/ssl/mailserver/tls.key' + # ... + volumeMounts: + - name: tls + mountPath: /etc/ssl/mailserver + readOnly: true + # ... + volumes: + - name: tls + secret: + secretName: mailserver.tls + ``` [docs-dovecot]: ./override-defaults/dovecot.md [docs-postfix]: ./override-defaults/postfix.md diff --git a/docs/content/config/advanced/mail-fetchmail.md b/docs/content/config/advanced/mail-fetchmail.md index 7713db52..1621a1a5 100644 --- a/docs/content/config/advanced/mail-fetchmail.md +++ b/docs/content/config/advanced/mail-fetchmail.md @@ -26,25 +26,29 @@ Generate a file called `fetchmail.cf` and place it in the `config` folder. Your A detailed description of the configuration options can be found in the [online version of the manual page][fetchmail-docs]. -### Example IMAP Configuration +### IMAP Configuration -```fetchmailrc -poll 'imap.example.com' proto imap - user 'username' - pass 'secret' - is 'user1@domain.tld' - ssl -``` +!!! example -### Example POP3 Configuration + ```fetchmailrc + poll 'imap.example.com' proto imap + user 'username' + pass 'secret' + is 'user1@domain.tld' + ssl + ``` -```fetchmailrc -poll 'pop3.example.com' proto pop3 - user 'username' - pass 'secret' - is 'user2@domain.tld' - ssl -``` +### POP3 Configuration + +!!! example + + ```fetchmailrc + poll 'pop3.example.com' proto pop3 + user 'username' + pass 'secret' + is 'user2@domain.tld' + ssl + ``` !!! caution Don’t forget the last line: eg: `is 'user1@domain.tld'`. After `is` you have to specify one email address from the configuration file `config/postfix-accounts.cf`. diff --git a/docs/content/config/advanced/mail-forwarding/aws-ses.md b/docs/content/config/advanced/mail-forwarding/aws-ses.md index 6e27588a..1f1a64df 100644 --- a/docs/content/config/advanced/mail-forwarding/aws-ses.md +++ b/docs/content/config/advanced/mail-forwarding/aws-ses.md @@ -2,7 +2,7 @@ title: 'Mail Forwarding | AWS SES' --- -!!! note +!!! warning New configuration, see [Configure Relay Hosts][docs-relay] Instead of letting postfix deliver mail directly it is possible to configure it to deliver outgoing email via Amazon SES (Simple Email Service). (Receiving inbound email via SES is not implemented.) The configuration follows the guidelines provided by AWS in https://docs.aws.amazon.com/ses/latest/DeveloperGuide/postfix.html, specifically, the `STARTTLS` method. diff --git a/docs/content/config/advanced/mail-forwarding/relay-hosts.md b/docs/content/config/advanced/mail-forwarding/relay-hosts.md index ac11ffcd..5bb672e5 100644 --- a/docs/content/config/advanced/mail-forwarding/relay-hosts.md +++ b/docs/content/config/advanced/mail-forwarding/relay-hosts.md @@ -12,14 +12,14 @@ Depending on the domain of the sender, you may want to send via a different rela Basic configuration is done via environment variables: -* **RELAY_HOST** _default host to relay mail through, empty will disable this feature_ -* **RELAY_PORT** _port on default relay, defaults to port 25_ -* **RELAY_USER** _username for the default relay_ -* **RELAY_PASSWORD** _password for the default user_ +* `RELAY_HOST`: _default host to relay mail through, empty will disable this feature_ +* `RELAY_PORT`: _port on default relay, defaults to port 25_ +* `RELAY_USER`: _username for the default relay_ +* `RELAY_PASSWORD`: _password for the default user_ Setting these environment variables will cause mail for all sender domains to be routed via the specified host, authenticating with the user/password combination. -!!! note +!!! warning For users of the previous `AWS_SES_*` variables: please update your configuration to use these new variables, no other configuration is required. ## Advanced Configuration diff --git a/docs/content/config/advanced/mail-sieve.md b/docs/content/config/advanced/mail-sieve.md index db6e779b..cb3f6ea3 100644 --- a/docs/content/config/advanced/mail-sieve.md +++ b/docs/content/config/advanced/mail-sieve.md @@ -19,32 +19,38 @@ It's even possible to install a user provided Sieve filter at startup during use An example of a sieve filter that moves mails to a folder `INBOX/spam` depending on the sender address: -```sieve -require ["fileinto", "reject"]; +!!! example -if address :contains ["From"] "spam@spam.com" { - fileinto "INBOX.spam"; -} else { - keep; -} -``` + ```sieve + require ["fileinto", "reject"]; -!!! note + if address :contains ["From"] "spam@spam.com" { + fileinto "INBOX.spam"; + } else { + keep; + } + ``` + +!!! warning That folders have to exist beforehand if sieve should move them. Another example of a sieve filter that forward mails to a different address: -```sieve -require ["copy"]; +!!! example -redirect :copy "user2@otherdomain.tld"; -``` + ```sieve + require ["copy"]; + + redirect :copy "user2@otherdomain.tld"; + ``` Just forward all incoming emails and do not save them locally: -```sieve -redirect "user2@otherdomain.tld"; -``` +!!! example + + ```sieve + redirect "user2@otherdomain.tld"; + ``` You can also use external programs to filter or pipe (process) messages by adding executable scripts in `config/sieve-pipe` or `config/sieve-filter`. This can be used in lieu of a local alias file, for instance to forward an email to a webservice. These programs can then be referenced by filename, by all users. Note that the process running the scripts run as a privileged user. For further information see [Dovecot's wiki](https://wiki.dovecot.org/Pigeonhole/Sieve/Plugins/Pipe). @@ -59,13 +65,15 @@ For more examples or a detailed description of the Sieve language have a look at The [Manage Sieve](https://doc.dovecot.org/admin_manual/pigeonhole_managesieve_server/) extension allows users to modify their Sieve script by themselves. The authentication mechanisms are the same as for the main dovecot service. ManageSieve runs on port `4190` and needs to be enabled using the `ENABLE_MANAGESIEVE=1` environment variable. -```yaml -# docker-compose.yml -ports: - - "4190:4190" -environment: - - ENABLE_MANAGESIEVE=1 -``` +!!! example + + ```yaml + # docker-compose.yml + ports: + - "4190:4190" + environment: + - ENABLE_MANAGESIEVE=1 + ``` All user defined sieve scripts that are managed by ManageSieve are stored in the user's home folder in `/var/mail/domain.com/user1/sieve`. Just one sieve script might be active for a user and is sym-linked to `/var/mail/domain.com/user1/.dovecot.sieve` automatically. @@ -73,4 +81,5 @@ All user defined sieve scripts that are managed by ManageSieve are stored in the ManageSieve makes sure to not overwrite an existing `.dovecot.sieve` file. If a user activates a new sieve script the old one is backuped and moved to the `sieve` folder. The extension is known to work with the following ManageSieve clients: -* **Sieve Editor** a portable standalone application based on the former Thunderbird plugin (https://github.com/thsmi/sieve). + +- **Sieve Editor** a portable standalone application based on the former Thunderbird plugin (https://github.com/thsmi/sieve). diff --git a/docs/content/config/advanced/override-defaults/dovecot.md b/docs/content/config/advanced/override-defaults/dovecot.md index e4ef7490..e0650916 100644 --- a/docs/content/config/advanced/override-defaults/dovecot.md +++ b/docs/content/config/advanced/override-defaults/dovecot.md @@ -48,7 +48,7 @@ To debug your dovecot configuration you can use: - Or: `docker exec -it doveconf | grep ` !!! note - [`setup.sh`][github-file-setupsh] is included in the `docker-mailserver` repository. + [`setup.sh`][github-file-setupsh] is included in the `docker-mailserver` repository. Make sure to grap the one matching your image version. The `config/dovecot.cf` is copied internally to `/etc/dovecot/local.conf`. To check this file run: diff --git a/docs/content/config/advanced/override-defaults/postfix.md b/docs/content/config/advanced/override-defaults/postfix.md index e380b231..3ce0a895 100644 --- a/docs/content/config/advanced/override-defaults/postfix.md +++ b/docs/content/config/advanced/override-defaults/postfix.md @@ -14,7 +14,9 @@ message_size_limit = 52428800 That specific example is now supported and can be handled by setting `POSTFIX_MESSAGE_SIZE_LIMIT`. -[Postfix documentation](http://www.postfix.org/documentation.html) remains the best place to find configuration options. +!!! seealso + + [Postfix documentation](http://www.postfix.org/documentation.html) remains the best place to find configuration options. Each line in the provided file will be loaded into postfix. diff --git a/docs/content/config/best-practices/dkim.md b/docs/content/config/best-practices/dkim.md index e012470d..569de5b3 100644 --- a/docs/content/config/best-practices/dkim.md +++ b/docs/content/config/best-practices/dkim.md @@ -2,7 +2,10 @@ title: 'Best Practices | DKIM' --- -DKIM is a security measure targeting email spoofing. It is greatly recommended one activates it. See [the Wikipedia page](https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail) for more details on DKIM. +DKIM is a security measure targeting email spoofing. It is greatly recommended one activates it. + +!!! seealso + See [the Wikipedia page](https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail) for more details on DKIM. ## Enabling DKIM Signature diff --git a/docs/content/config/best-practices/dmarc.md b/docs/content/config/best-practices/dmarc.md index e8309c66..ccc36a5a 100644 --- a/docs/content/config/best-practices/dmarc.md +++ b/docs/content/config/best-practices/dmarc.md @@ -4,7 +4,8 @@ hide: - toc # Hide Table of Contents for this page --- -DMARC Guide: https://github.com/internetstandards/toolbox-wiki/blob/master/DMARC-how-to.md +!!! seealso + DMARC Guide: https://github.com/internetstandards/toolbox-wiki/blob/master/DMARC-how-to.md ## Enabling DMARC diff --git a/docs/content/config/best-practices/spf.md b/docs/content/config/best-practices/spf.md index 15bfc55b..dacfc4ce 100644 --- a/docs/content/config/best-practices/spf.md +++ b/docs/content/config/best-practices/spf.md @@ -6,9 +6,11 @@ hide: From [Wikipedia](https://en.wikipedia.org/wiki/Sender_Policy_Framework): -> Sender Policy Framework (SPF) is a simple email-validation system designed to detect email spoofing by providing a mechanism to allow receiving mail exchangers to check that incoming mail from a domain comes from a host authorized by that domain's administrators. The list of authorized sending hosts for a domain is published in the Domain Name System (DNS) records for that domain in the form of a specially formatted TXT record. Email spam and phishing often use forged "from" addresses, so publishing and checking SPF records can be considered anti-spam techniques. +!!! quote + Sender Policy Framework (SPF) is a simple email-validation system designed to detect email spoofing by providing a mechanism to allow receiving mail exchangers to check that incoming mail from a domain comes from a host authorized by that domain's administrators. The list of authorized sending hosts for a domain is published in the Domain Name System (DNS) records for that domain in the form of a specially formatted TXT record. Email spam and phishing often use forged "from" addresses, so publishing and checking SPF records can be considered anti-spam techniques. -For a more technical review: https://github.com/internetstandards/toolbox-wiki/blob/master/SPF-how-to.md +!!! seealso + For a more technical review: https://github.com/internetstandards/toolbox-wiki/blob/master/SPF-how-to.md ## Add a SPF Record diff --git a/docs/content/config/pop3.md b/docs/content/config/pop3.md index caa44891..4350f455 100644 --- a/docs/content/config/pop3.md +++ b/docs/content/config/pop3.md @@ -4,10 +4,11 @@ hide: - toc # Hide Table of Contents for this page --- -**We do not recommend using POP. Use IMAP instead.** +!!! warning -If you really want to have POP3 running, add 3 lines to the docker-compose.yml : -Add the ports 110 and 995, and add environment variable ENABLE_POP : + **We do not recommend using POP3. Use IMAP instead.** + +If you really want to have POP3 running add the ports 110 and 995 and the environment variable `ENABLE_POP3` to your `docker-compose.yml`: ```yaml mail: diff --git a/docs/content/config/security/ssl.md b/docs/content/config/security/ssl.md index c151e2e4..b136416e 100644 --- a/docs/content/config/security/ssl.md +++ b/docs/content/config/security/ssl.md @@ -134,98 +134,102 @@ Then: `/path/to/mailserver/docker-compose up -d mail` The following `docker-compose.yml` is the basic setup you need for using `letsencrypt-nginx-proxy-companion`. It is mainly derived from its own wiki/documenation. -```yaml -version: "2" +???+ example "Example Code" + + ```yaml + version: "2" + + services: + nginx: + image: nginx + container_name: nginx + ports: + - 80:80 + - 443:443 + volumes: + - /mnt/data/nginx/htpasswd:/etc/nginx/htpasswd + - /mnt/data/nginx/conf.d:/etc/nginx/conf.d + - /mnt/data/nginx/vhost.d:/etc/nginx/vhost.d + - /mnt/data/nginx/html:/usr/share/nginx/html + - /mnt/data/nginx/certs:/etc/nginx/certs:ro + networks: + - proxy-tier + restart: always + + nginx-gen: + image: jwilder/docker-gen + container_name: nginx-gen + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - /mnt/data/nginx/templates/nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl:ro + volumes_from: + - nginx + entrypoint: /usr/local/bin/docker-gen -notify-sighup nginx -watch -wait 5s:30s /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf + restart: always + + letsencrypt-nginx-proxy-companion: + image: jrcs/letsencrypt-nginx-proxy-companion + container_name: letsencrypt-companion + volumes_from: + - nginx + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - /mnt/data/nginx/certs:/etc/nginx/certs:rw + environment: + - NGINX_DOCKER_GEN_CONTAINER=nginx-gen + - DEBUG=false + restart: always -services: - nginx: - image: nginx - container_name: nginx - ports: - - 80:80 - - 443:443 - volumes: - - /mnt/data/nginx/htpasswd:/etc/nginx/htpasswd - - /mnt/data/nginx/conf.d:/etc/nginx/conf.d - - /mnt/data/nginx/vhost.d:/etc/nginx/vhost.d - - /mnt/data/nginx/html:/usr/share/nginx/html - - /mnt/data/nginx/certs:/etc/nginx/certs:ro networks: - - proxy-tier - restart: always - - nginx-gen: - image: jwilder/docker-gen - container_name: nginx-gen - volumes: - - /var/run/docker.sock:/tmp/docker.sock:ro - - /mnt/data/nginx/templates/nginx.tmpl:/etc/docker-gen/templates/nginx.tmpl:ro - volumes_from: - - nginx - entrypoint: /usr/local/bin/docker-gen -notify-sighup nginx -watch -wait 5s:30s /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf - restart: always - - letsencrypt-nginx-proxy-companion: - image: jrcs/letsencrypt-nginx-proxy-companion - container_name: letsencrypt-companion - volumes_from: - - nginx - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - /mnt/data/nginx/certs:/etc/nginx/certs:rw - environment: - - NGINX_DOCKER_GEN_CONTAINER=nginx-gen - - DEBUG=false - restart: always - -networks: - proxy-tier: - external: - name: nginx-proxy -``` + proxy-tier: + external: + name: nginx-proxy + ``` The second part of the setup is the actual mail container. So, in another folder, create another `docker-compose.yml` with the following content (Removed all ENV variables for this example): -```yaml -version: '2' -services: - mail: - image: mailserver/docker-mailserver:latest - hostname: ${HOSTNAME} - domainname: ${DOMAINNAME} - container_name: ${CONTAINER_NAME} - ports: - - "25:25" - - "143:143" - - "465:465" - - "587:587" - - "993:993" - volumes: - - ./mail:/var/mail - - ./mail-state:/var/mail-state - - ./config/:/tmp/docker-mailserver/ - - /mnt/data/nginx/certs/:/etc/letsencrypt/live/:ro - cap_add: - - NET_ADMIN - - SYS_PTRACE - restart: always +???+ example "Example Code" + + ```yaml + version: '2' + services: + mail: + image: mailserver/docker-mailserver:latest + hostname: ${HOSTNAME} + domainname: ${DOMAINNAME} + container_name: ${CONTAINER_NAME} + ports: + - "25:25" + - "143:143" + - "465:465" + - "587:587" + - "993:993" + volumes: + - ./mail:/var/mail + - ./mail-state:/var/mail-state + - ./config/:/tmp/docker-mailserver/ + - /mnt/data/nginx/certs/:/etc/letsencrypt/live/:ro + cap_add: + - NET_ADMIN + - SYS_PTRACE + restart: always + + cert-companion: + image: nginx + environment: + - "VIRTUAL_HOST=" + - "VIRTUAL_NETWORK=nginx-proxy" + - "LETSENCRYPT_HOST=" + - "LETSENCRYPT_EMAIL=" + networks: + - proxy-tier + restart: always - cert-companion: - image: nginx - environment: - - "VIRTUAL_HOST=" - - "VIRTUAL_NETWORK=nginx-proxy" - - "LETSENCRYPT_HOST=" - - "LETSENCRYPT_EMAIL=" networks: - - proxy-tier - restart: always - -networks: - proxy-tier: - external: - name: nginx-proxy -``` + proxy-tier: + external: + name: nginx-proxy + ``` The mail container needs to have the letsencrypt certificate folder mounted as a volume. No further changes are needed. The second container is a dummy-sidecar we need, because the mail-container do not expose any web-ports. Set your ENV variables as you need. (`VIRTUAL_HOST` and `LETSENCRYPT_HOST` are mandandory, see documentation) @@ -275,70 +279,72 @@ For Caddy v2 you can specify the `key_type` in your server's global settings, wh If you are instead using a json config for Caddy v2, you can set it in your site's TLS automation policies: -```json -{ - "apps": { - "http": { - "servers": { - "srv0": { - "listen": [ - ":443" - ], - "routes": [ - { - "match": [ - { - "host": [ - "mail.domain.com", - ] - } +???+ example "Example Code" + + ```json + { + "apps": { + "http": { + "servers": { + "srv0": { + "listen": [ + ":443" ], - "handle": [ + "routes": [ { - "handler": "subroute", - "routes": [ + "match": [ { - "handle": [ + "host": [ + "mail.domain.com", + ] + } + ], + "handle": [ + { + "handler": "subroute", + "routes": [ { - "body": "", - "handler": "static_response" + "handle": [ + { + "body": "", + "handler": "static_response" + } + ] } ] } - ] - } - ], - "terminal": true - }, - ] - } - } - }, - "tls": { - "automation": { - "policies": [ - { - "subjects": [ - "mail.domain.com", - ], - "key_type": "rsa2048", - "issuer": { - "email": "email@email.com", - "module": "acme" - } - }, - { - "issuer": { - "email": "email@email.com", - "module": "acme" + ], + "terminal": true + }, + ] } } - ] + }, + "tls": { + "automation": { + "policies": [ + { + "subjects": [ + "mail.domain.com", + ], + "key_type": "rsa2048", + "issuer": { + "email": "email@email.com", + "module": "acme" + } + }, + { + "issuer": { + "email": "email@email.com", + "module": "acme" + } + } + ] + } + } } } - } -} -``` + ``` The generated certificates can be mounted: @@ -375,44 +381,46 @@ Traefik's V2 storage format is natively supported if the `acme.json` store is mo This allows for support of wild card certificates: `SSL_DOMAIN=*.example.com`. Here is an example setup for [`docker-compose`](https://docs.docker.com/compose/): -```yaml -version: '3.8' -services: - mail: - image: mailserver/docker-mailserver:stable - hostname: mail - domainname: example.com - volumes: - - /etc/ssl/acme-v2.json:/etc/letsencrypt/acme.json:ro - environment: - SSL_TYPE: letsencrypt - # SSL_DOMAIN: "*.example.com" - traefik: - image: traefik:v2.2 - restart: always - ports: - - "80:80" - - "443:443" - command: - - --providers.docker - - --entrypoints.web.address=:80 - - --entrypoints.web.http.redirections.entryPoint.to=websecure - - --entrypoints.web.http.redirections.entryPoint.scheme=https - - --entrypoints.websecure.address=:443 - - --entrypoints.websecure.http.middlewares=hsts@docker - - --entrypoints.websecure.http.tls.certResolver=le - - --certificatesresolvers.le.acme.email=admin@example.net - - --certificatesresolvers.le.acme.storage=/acme.json - - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - /etc/ssl/acme-v2.json:/acme.json +???+ example "Example Code" - whoami: - image: containous/whoami - labels: - - "traefik.http.routers.whoami.rule=Host(`mail.example.com`)" -``` + ```yaml + version: '3.8' + services: + mail: + image: mailserver/docker-mailserver:stable + hostname: mail + domainname: example.com + volumes: + - /etc/ssl/acme-v2.json:/etc/letsencrypt/acme.json:ro + environment: + SSL_TYPE: letsencrypt + # SSL_DOMAIN: "*.example.com" + traefik: + image: traefik:v2.2 + restart: always + ports: + - "80:80" + - "443:443" + command: + - --providers.docker + - --entrypoints.web.address=:80 + - --entrypoints.web.http.redirections.entryPoint.to=websecure + - --entrypoints.web.http.redirections.entryPoint.scheme=https + - --entrypoints.websecure.address=:443 + - --entrypoints.websecure.http.middlewares=hsts@docker + - --entrypoints.websecure.http.tls.certResolver=le + - --certificatesresolvers.le.acme.email=admin@example.net + - --certificatesresolvers.le.acme.storage=/acme.json + - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - /etc/ssl/acme-v2.json:/acme.json + + whoami: + image: containous/whoami + labels: + - "traefik.http.routers.whoami.rule=Host(`mail.example.com`)" + ``` This setup only comes with one caveat: The domain has to be configured on another service for traefik to actually request it from lets-encrypt (`whoami` in this case). @@ -422,9 +430,13 @@ If you are using Traefik v1, you might want to _push_ your Traefik-managed certi Depending of your Traefik configuration, certificates may be stored using a file or a KV Store (consul, etcd...) Either way, certificates will be renewed by Traefik, then automatically pushed to the mailserver thanks to the `cert-renewer` service. Finally, dovecot and postfix will be restarted. -## Self-Signed Certificates (Testing Only) +## Self-Signed Certificates -You can easily generate a self-signed SSL certificate by using the following command: +!!! warning + + Use self-signed certificates only for testing purposes! + +You can generate a self-signed SSL certificate by using the following command: ```sh docker run -it --rm -v "$(pwd)"/config/ssl:/tmp/docker-mailserver/ssl -h mail.my-domain.com -t mailserver/docker-mailserver generate-ssl-certificate @@ -472,7 +484,7 @@ environment: This will mount the path where your ssl certificates reside as read-only under `/tmp/ssl`. Then all you have to do is to specify the location of your private key and the certificate. -!!! note +!!! info You may have to restart your mailserver once the certificates change. ## Testing a Certificate is Valid @@ -509,9 +521,11 @@ docker exec mail openssl s_client \ ## Plain-Text Access -Not recommended for purposes other than testing. +!!! warning -Just add this to `config/dovecot.cf`: + Not recommended for purposes other than testing. + +Add this to `config/dovecot.cf`: ```cf ssl = yes diff --git a/docs/content/config/setup.sh.md b/docs/content/config/setup.sh.md index 8b43a02c..70bff7a5 100644 --- a/docs/content/config/setup.sh.md +++ b/docs/content/config/setup.sh.md @@ -13,6 +13,10 @@ wget https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/maste chmod a+x ./setup.sh ``` +!!! info + + Make sure to get the `setup.sh` that comes with the release you're using. Look up the release and the git commit on which this release is based upon by selecting the appropriate tag on GitHub. This can done with the "Switch branches/tags" button on GitHub, choosing the right tag. This is done in order to rule out possible inconsistencies between versions. + ## Usage Run `./setup.sh -h` and you'll get some usage information: diff --git a/docs/content/examples/tutorials/basic-installation.md b/docs/content/examples/tutorials/basic-installation.md index 50f934ff..6a008096 100644 --- a/docs/content/examples/tutorials/basic-installation.md +++ b/docs/content/examples/tutorials/basic-installation.md @@ -22,39 +22,41 @@ We are going to use this docker based mailserver: - Create the file `docker-compose.yml` with a content like this: - ```yaml - version: '2' + !!! example - services: - mail: - image: mailserver/docker-mailserver:latest - hostname: mail - domainname: example.org - container_name: mail - ports: - - "25:25" - - "587:587" - - "465:465" - volumes: - - ./data/:/var/mail/ - - ./state/:/var/mail-state/ - - ./config/:/tmp/docker-mailserver/ - - /var/ds/wsproxy/letsencrypt/:/etc/letsencrypt/ - environment: - - PERMIT_DOCKER=network - - SSL_TYPE=letsencrypt - - ONE_DIR=1 - - DMS_DEBUG=1 - - SPOOF_PROTECTION=0 - - REPORT_RECIPIENT=1 - - ENABLE_SPAMASSASSIN=0 - - ENABLE_CLAMAV=0 - - ENABLE_FAIL2BAN=1 - - ENABLE_POSTGREY=0 - cap_add: - - NET_ADMIN - - SYS_PTRACE - ``` + ```yaml + version: '2' + + services: + mail: + image: mailserver/docker-mailserver:latest + hostname: mail + domainname: example.org + container_name: mail + ports: + - "25:25" + - "587:587" + - "465:465" + volumes: + - ./data/:/var/mail/ + - ./state/:/var/mail-state/ + - ./config/:/tmp/docker-mailserver/ + - /var/ds/wsproxy/letsencrypt/:/etc/letsencrypt/ + environment: + - PERMIT_DOCKER=network + - SSL_TYPE=letsencrypt + - ONE_DIR=1 + - DMS_DEBUG=1 + - SPOOF_PROTECTION=0 + - REPORT_RECIPIENT=1 + - ENABLE_SPAMASSASSIN=0 + - ENABLE_CLAMAV=0 + - ENABLE_FAIL2BAN=1 + - ENABLE_POSTGREY=0 + cap_add: + - NET_ADMIN + - SYS_PTRACE + ``` For more details about the environment variables that can be used, and their meaning and possible values, check also these: diff --git a/docs/content/examples/uses-cases/forward-only-mailserver-with-ldap-authentication.md b/docs/content/examples/uses-cases/forward-only-mailserver-with-ldap-authentication.md index fbc23736..b3033299 100644 --- a/docs/content/examples/uses-cases/forward-only-mailserver-with-ldap-authentication.md +++ b/docs/content/examples/uses-cases/forward-only-mailserver-with-ldap-authentication.md @@ -97,9 +97,13 @@ postfix reload You see that besides `query_filter`, I had to customize as well `result_attribute` and `result_format`. -For more details about using LDAP see: [LDAP managed mail server with Postfix and Dovecot for multiple domains](https://www.vennedey.net/resources/2-LDAP-managed-mail-server-with-Postfix-and-Dovecot-for-multiple-domains) +!!! sealso -Another solution that serves as a forward-only mailserver is this: https://gitlab.com/docker-scripts/postfix + For more details about using LDAP see: [LDAP managed mail server with Postfix and Dovecot for multiple domains](https://www.vennedey.net/resources/2-LDAP-managed-mail-server-with-Postfix-and-Dovecot-for-multiple-domains) + +!!! seealso + + Another solution that serves as a forward-only mailserver is this: https://gitlab.com/docker-scripts/postfix [github-file-readme-patches]: https://github.com/docker-mailserver/docker-mailserver/blob/master/README.md#custom-user-changes--patches [github-issue-1247]: https://github.com/docker-mailserver/docker-mailserver/issues/1247 diff --git a/docs/content/faq.md b/docs/content/faq.md index d55e56f8..256a6a55 100644 --- a/docs/content/faq.md +++ b/docs/content/faq.md @@ -9,11 +9,11 @@ This image is based on config files that can be persisted using Docker volumes, ### Where are emails stored? -Mails are stored in `/var/mail/${domain}/${username}`. +Mails are stored in `/var/mail/${domain}/${username}`. Since `v9.0.0` it is possible to add custom `user_attributes` for each accounts to have a different mailbox configuration (See [#1792][github-issue-1792]). -You should use a [data volume container](https://medium.com/@ramangupta/why-docker-data-containers-are-good-589b3c6c749e#.uxyrp7xpu) for `/var/mail` to persist data. +!!! warning -Otherwise, your data may be lost. + You should use a [data volume container](https://medium.com/@ramangupta/why-docker-data-containers-are-good-589b3c6c749e#.uxyrp7xpu) for `/var/mail` to persist data. Otherwise, your data may be lost. ### How to alter the running mailserver instance _without_ relaunching the container? @@ -147,68 +147,70 @@ If you run the server with `docker-compose`, you can leverage on docker configs The following configuration works nicely: -Create a _system_ cron file: +??? example -```sh -# in the docker-compose.yml root directory -mkdir cron -touch cron/sa-learn -chown root:root cron/sa-learn -chmod 0644 cron/sa-learn -``` + Create a _system_ cron file: -Edit the system cron file `nano cron/sa-learn`, and set an appropriate configuration: + ```sh + # in the docker-compose.yml root directory + mkdir cron + touch cron/sa-learn + chown root:root cron/sa-learn + chmod 0644 cron/sa-learn + ``` -```conf -# This assumes you're having `environment: ONE_DIR=1` in the env-mailserver, -# with a consolidated config in `/var/mail-state` -# -# m h dom mon dow user command -# -# Everyday 2:00AM, learn spam from a specific user -# spam: junk directory -0 2 * * * root sa-learn --spam /var/mail/domain.com/username/.Junk --dbpath /var/mail-state/lib-amavis/.spamassassin -# ham: archive directories -15 2 * * * root sa-learn --ham /var/mail/domain.com/username/.Archive* --dbpath /var/mail-state/lib-amavis/.spamassassin -# ham: inbox subdirectories -30 2 * * * root sa-learn --ham /var/mail/domain.com/username/cur* --dbpath /var/mail-state/lib-amavis/.spamassassin -# -# Everyday 3:00AM, learn spam from all users of a domain -# spam: junk directory -0 3 * * * root sa-learn --spam /var/mail/otherdomain.com/*/.Junk --dbpath /var/mail-state/lib-amavis/.spamassassin -# ham: archive directories -15 3 * * * root sa-learn --ham /var/mail/otherdomain.com/*/.Archive* --dbpath /var/mail-state/lib-amavis/.spamassassin -# ham: inbox subdirectories -30 3 * * * root sa-learn --ham /var/mail/otherdomain.com/*/cur* --dbpath /var/mail-state/lib-amavis/.spamassassin -``` + Edit the system cron file `nano cron/sa-learn`, and set an appropriate configuration: -Then with plain `docker-compose`: + ```conf + # This assumes you're having `environment: ONE_DIR=1` in the env-mailserver, + # with a consolidated config in `/var/mail-state` + # + # m h dom mon dow user command + # + # Everyday 2:00AM, learn spam from a specific user + # spam: junk directory + 0 2 * * * root sa-learn --spam /var/mail/domain.com/username/.Junk --dbpath /var/mail-state/lib-amavis/.spamassassin + # ham: archive directories + 15 2 * * * root sa-learn --ham /var/mail/domain.com/username/.Archive* --dbpath /var/mail-state/lib-amavis/.spamassassin + # ham: inbox subdirectories + 30 2 * * * root sa-learn --ham /var/mail/domain.com/username/cur* --dbpath /var/mail-state/lib-amavis/.spamassassin + # + # Everyday 3:00AM, learn spam from all users of a domain + # spam: junk directory + 0 3 * * * root sa-learn --spam /var/mail/otherdomain.com/*/.Junk --dbpath /var/mail-state/lib-amavis/.spamassassin + # ham: archive directories + 15 3 * * * root sa-learn --ham /var/mail/otherdomain.com/*/.Archive* --dbpath /var/mail-state/lib-amavis/.spamassassin + # ham: inbox subdirectories + 30 3 * * * root sa-learn --ham /var/mail/otherdomain.com/*/cur* --dbpath /var/mail-state/lib-amavis/.spamassassin + ``` -```yaml -services: - mail: - image: mailserver/docker-mailserver:latest - volumes: - - ./cron/sa-learn:/etc/cron.d/sa-learn -``` + Then with plain `docker-compose`: -Or with [docker swarm](https://docs.docker.com/engine/swarm/configs/): + ```yaml + services: + mail: + image: mailserver/docker-mailserver:latest + volumes: + - ./cron/sa-learn:/etc/cron.d/sa-learn + ``` -```yaml -version: "3.3" + Or with [docker swarm](https://docs.docker.com/engine/swarm/configs/): + + ```yaml + version: "3.3" + + services: + mail: + image: mailserver/docker-mailserver:latest + # ... + configs: + - source: my_sa_crontab + target: /etc/cron.d/sa-learn -services: - mail: - image: mailserver/docker-mailserver:latest - # ... configs: - - source: my_sa_crontab - target: /etc/cron.d/sa-learn - -configs: - my_sa_crontab: - file: ./cron/sa-learn -``` + my_sa_crontab: + file: ./cron/sa-learn + ``` With the default settings, Spamassassin will require 200 mails trained for spam (for example with the method explained above) and 200 mails trained for ham (using the same command as above but using `--ham` and providing it with some ham mails). Until you provided these 200+200 mails, Spamassasin will not take the learned mails into account. For further reference, see the [Spamassassin Wiki](https://wiki.apache.org/spamassassin/BayesNotWorking). @@ -302,7 +304,7 @@ If we're blind, we won't be able to do anything. 1 core and 1GB of RAM + swap partition is recommended to run `docker-mailserver` with clamav. Otherwise, it could work with 512M of RAM. -!!! note +!!! warning Clamav can consume a lot of memory, as it reads the entire signature database into RAM. Current figure is about 850M and growing. If you get errors about clamav or amavis failing to allocate memory you need more RAM or more swap and of course docker must be allowed to use swap (not always the case). If you can't use swap at all you may need 3G RAM. @@ -407,4 +409,5 @@ supervisorctl update [github-issue-1247]: https://github.com/docker-mailserver/docker-mailserver/issues/1247 [github-issue-1405-comment]: https://github.com/docker-mailserver/docker-mailserver/issues/1405#issuecomment-590106498 [github-issue-1639]: https://github.com/docker-mailserver/docker-mailserver/issues/1639 +[github-issue-1792]: https://github.com/docker-mailserver/docker-mailserver/pull/1792 [hanscees-userpatches]: https://github.com/hanscees/dockerscripts/blob/master/scripts/tomav-user-patches.sh diff --git a/docs/content/introduction.md b/docs/content/introduction.md index 66d58f14..14ea6fb8 100644 --- a/docs/content/introduction.md +++ b/docs/content/introduction.md @@ -51,15 +51,16 @@ Fetching an email: MUA <------------------------------ ┫ MDA ╯ ┃ ┗━━━━━━━┛ ``` -> Let's say Alice owns a Gmail account, `alice@gmail.com`; and Bob owns an account on a `docker-mailserver`'s instance, `bob@dms.io`. -> -> Make sure not to conflate these two very different scenarios: -> A) Alice sends an email to `bob@dms.io` => the email is first submitted to MTA `smtp.gmail.com`, then relayed to MTA `smtp.dms.io` where it is then delivered into Bob's mailbox. -> B) Bob sends an email to `alice@gmail.com` => the email is first submitted to MTA `smtp.dms.io`, then relayed to MTA `smtp.gmail.com` and eventually delivered into Alice's mailbox. -> -> In scenario *A* the email leaves Gmail's premises, that email's *initial* submission is _not_ handled by your `docker-mailserver` instance(MTA); it merely receives the email after it has been relayed by Gmail's MTA. In scenario *B*, the `docker-mailserver` instance(MTA) handles the submission, prior to relaying. -> -> The main takeaway is that when a third-party sends an email to a `docker-mailserver` instance(MTA) (or any MTA for that matter), it does _not_ establish a direct connection with that MTA. Email submission first goes through the sender's MTA, then some relaying between at least two MTAs is required to deliver the email. That will prove very important when it comes to security management. +!!! example + Let's say Alice owns a Gmail account, `alice@gmail.com`; and Bob owns an account on a `docker-mailserver`'s instance, `bob@dms.io`. + + Make sure not to conflate these two very different scenarios: + A) Alice sends an email to `bob@dms.io` => the email is first submitted to MTA `smtp.gmail.com`, then relayed to MTA `smtp.dms.io` where it is then delivered into Bob's mailbox. + B) Bob sends an email to `alice@gmail.com` => the email is first submitted to MTA `smtp.dms.io`, then relayed to MTA `smtp.gmail.com` and eventually delivered into Alice's mailbox. + + In scenario *A* the email leaves Gmail's premises, that email's *initial* submission is _not_ handled by your `docker-mailserver` instance(MTA); it merely receives the email after it has been relayed by Gmail's MTA. In scenario *B*, the `docker-mailserver` instance(MTA) handles the submission, prior to relaying. + + The main takeaway is that when a third-party sends an email to a `docker-mailserver` instance(MTA) (or any MTA for that matter), it does _not_ establish a direct connection with that MTA. Email submission first goes through the sender's MTA, then some relaying between at least two MTAs is required to deliver the email. That will prove very important when it comes to security management. One important thing to note is that MTA and MDA programs may actually handle _multiple_ tasks (which is the case with `docker-mailserver`'s Postfix and Dovecot). @@ -145,7 +146,7 @@ The best practice as of 2020 when it comes to securing Outward Submission is to - [ESMTP][wikipedia-esmtp] is [SMTP][wikipedia-smtp] + extensions. It's the version of the SMTP protocol that most mail servers speak nowadays. For the purpose of this documentation, ESMTP and SMTP are synonymous. - Port 465 is the reserved TCP port for Implicit TLS Submission (since 2018). There is actually a boisterous history to that ports usage, but let's keep it simple. -!!! note +!!! warning This Submission setup is sometimes refered to as [SMTPS][wikipedia-smtps]. Long story short: this is incorrect and should be avoided. Although a very satisfactory setup, Implicit TLS on port 465 is somewhat "cutting edge". There exists another well established mail Submission setup that must be supported as well, SMTP+STARTTLS on port 587. It uses Explicit TLS: the client starts with a cleartext connection, then the server informs a TLS-encrypted "upgraded" connection may be established, and the client _may_ eventually decide to establish it prior to the Submission. Basically it's an opportunistic, opt-in TLS upgrade of the connection between the client and the server, at the client's discretion, using a mechanism known as [STARTTLS][wikipedia-starttls] that both ends need to implement. From bbaca9a468f0a614011be2038b3ed754d6f30c9d Mon Sep 17 00:00:00 2001 From: polarathene <5098581+polarathene@users.noreply.github.com> Date: Wed, 10 Mar 2021 17:30:57 +1300 Subject: [PATCH 13/14] docs(config): Tidy up and better document `mkdocs.yml` --- docs/mkdocs.yml | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 49f10003..efea8172 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -1,12 +1,28 @@ +# Site specific: site_name: 'Docker Mailserver' site_description: 'A fullstack but simple mail server (SMTP, IMAP, LDAP, Antispam, Antivirus, etc.) using Docker.' site_author: 'docker-mailserver (Github Organization)' +copyright: '

© Docker Mailserver Organization
This project is licensed under the MIT license.

' + +# Project source specific: repo_name: 'docker-mailserver' repo_url: 'https://github.com/docker-mailserver/docker-mailserver' -copyright: '

© Docker Mailserver Organization
This project is licensed under the MIT license.

' + +# All docs `edit` button will go to this subpath of the `repo_url`: +# For versioned docs, this may be a little misleading for a user not viewing the `edge` docs which match the `master` branch. edit_uri: 'edit/master/docs/content' + +# The main docs content lives here, any files will be copied over to the deployed version, +# unless the file or directory name is prefixed with a `.` docs_dir: 'content/' -site_url: 'https://docker-mailserver.github.io/docker-mailserver' + +# Canonical URL (HTML content instructs search engines that this is where the preferred version of the docs is located, useful when we have duplicate content like versioned docs) +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Choosing_between_www_and_non-www_URLs#using_%3Clink_relcanonical%3E +# Also required for `sitemap.xml` generation at build time; which bots use to assist them crawling and indexing a website, +# the `mkdocs-material` 'Instant Navigation' feature utilizes the sitemap data to work. +site_url: 'https://docker-mailserver.github.io/docker-mailserver/edge' + +# Anything related to the `mkdocs-material` theme config goes here: theme: name: 'material' favicon: assets/logo/favicon-32x32.png @@ -18,6 +34,7 @@ theme: - navigation.expand - navigation.instant +# We make some minor style adjustments or resolve issues that upstream `mkdocs-material` would not accept a PR for: extra_css: - assets/css/customizations.css @@ -28,6 +45,7 @@ extra: version: provider: mike +# Various extensions for `mkdocs` are enabled and configured here to extend supported markdown syntax/features. markdown_extensions: - toc: anchorlink: true @@ -35,6 +53,11 @@ markdown_extensions: - attr_list - admonition - pymdownx.details + - pymdownx.superfences + - pymdownx.magiclink + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg - pymdownx.highlight: extend_pygments_lang: - name: yml @@ -53,12 +76,12 @@ markdown_extensions: lang: txt - name: caddyfile lang: txt - - pymdownx.superfences - - pymdownx.magiclink - - pymdownx.emoji: - emoji_index: !!python/name:materialx.emoji.twemoji - emoji_generator: !!python/name:materialx.emoji.to_svg +# Hard-coded navigation list. Key(UI label): Value(relative filepath from `docs_dir`). +# - If referencing a file more than once, the URLs will all point to the nav hierarchy of the last file reference entry. That usually breaks UX, try avoid it. +# - The top-level elements are presented as tabs (due to `theme.features.navigation.tabs`). +# - Nested elements appear in the sidebar (left) of each tabs page. +# - 3rd level and beyond are automatically expanded in the sidebar instead of collapsed (due to `theme.features.navigation.expand`) nav: - 'Home': index.md - 'Introduction': introduction.md From 1b971a89cb6d945897b71e593ffc398c4e70fff5 Mon Sep 17 00:00:00 2001 From: polarathene <5098581+polarathene@users.noreply.github.com> Date: Wed, 24 Mar 2021 12:44:28 +1300 Subject: [PATCH 14/14] docs(sync): Add Github Wiki contributions During the long-lived PR, multiple contributions to the existing Github Wiki were made, this commit applies those here and mentions the files and authors attributed to the changes: JaapD: dkim.md + forward-only-mailserver-with-ldap-authentication.md Added corrections to `setup.sh config dkim` command. Added compatibility warning about 4096-bit key sizes or greater. Added ldap tip. --- fred727-temp: optional-config.md Added a mention for `user-patches.sh`. --- Semir Patel: setup.sh.md + debugging.md Minor corrections. Additionally corrected `tvial` references that had already been updated in this PR series. --- Stefan Neben: kubernetes.md > Port 25 proxy protocol configuration in master.cf was missing docs(sync): Add Github Wiki contributions > IMAP with STARTTLS is also active, so we need that option here as well docs(sync): Add Github Wiki contribution --- docs/content/config/advanced/kubernetes.md | 4 ++++ docs/content/config/advanced/optional-config.md | 2 ++ docs/content/config/best-practices/dkim.md | 16 ++++++++++++---- docs/content/config/setup.sh.md | 4 ++-- docs/content/config/troubleshooting/debugging.md | 2 +- ...d-only-mailserver-with-ldap-authentication.md | 4 ++++ 6 files changed, 25 insertions(+), 7 deletions(-) diff --git a/docs/content/config/advanced/kubernetes.md b/docs/content/config/advanced/kubernetes.md index 357e3621..0223368f 100644 --- a/docs/content/config/advanced/kubernetes.md +++ b/docs/content/config/advanced/kubernetes.md @@ -409,12 +409,16 @@ Then, configure both [Postfix][docs-postfix] and [Dovecot][docs-dovecot] to expe postfix-main.cf: | postscreen_upstream_proxy_protocol = haproxy postfix-master.cf: | + smtp/inet/postscreen_upstream_proxy_protocol=haproxy submission/inet/smtpd_upstream_proxy_protocol=haproxy smtps/inet/smtpd_upstream_proxy_protocol=haproxy dovecot.cf: | # Assuming your ingress controller is bound to 10.0.0.0/8 haproxy_trusted_networks = 10.0.0.0/8, 127.0.0.0/8 service imap-login { + inet_listener imap { + haproxy = yes + } inet_listener imaps { haproxy = yes } diff --git a/docs/content/config/advanced/optional-config.md b/docs/content/config/advanced/optional-config.md index 3d8dc57b..7ad053a2 100644 --- a/docs/content/config/advanced/optional-config.md +++ b/docs/content/config/advanced/optional-config.md @@ -38,12 +38,14 @@ This is a list of all configuration files and directories which are optional or - **amavis.cf:** replaces the `/etc/amavis/conf.d/50-user` file - **dovecot.cf:** replaces `/etc/dovecot/local.conf`. (Docs: [Override Dovecot Defaults][docs-override-dovecot]) - **dovecot-quotas.cf:** list of custom quotas per mailbox. (Docs: [Accounts][docs-accounts-quota]) +- **user-patches.sh:** this file will be run after all configuration files are set up, but before the postfix, amavis and other daemons are started. (Docs: [FAQ - How to adjust settings with the `user-patches.sh` script][docs-faq-userpatches]) [docs-accounts-quota]: ../../config/user-management/accounts.md#notes [docs-aliases-regex]: ../../config/user-management/aliases.md#configuring-regexp-aliases [docs-dkim]: ../../config/best-practices/dkim.md [docs-fail2ban]: ../../config/security/fail2ban.md [docs-faq-spamrules]: ../../faq.md#how-can-i-manage-my-custom-spamassassin-rules +[docs-faq-userpatches]: ../../faq.md#how-to-adjust-settings-with-the-user-patchessh-script [docs-override-postfix]: ./override-defaults/postfix.md [docs-override-dovecot]: ./override-defaults/dovecot.md [docs-relayhosts-senderauth]: ./mail-forwarding/relay-hosts.md#sender-dependent-authentication diff --git a/docs/content/config/best-practices/dkim.md b/docs/content/config/best-practices/dkim.md index 569de5b3..0e71058c 100644 --- a/docs/content/config/best-practices/dkim.md +++ b/docs/content/config/best-practices/dkim.md @@ -18,13 +18,13 @@ To enable DKIM signature, **you must have created at least one email account**. After generating DKIM keys, you should restart the mail server. DNS edits may take a few minutes to hours to propagate. The script assumes you're being in the directory where the `config/` directory is located. The default keysize when generating the signature is 4096 bits for now. If you need to change it (e.g. your DNS provider limits the size), then provide the size as the first parameter of the command: ```sh -./setup.sh config dkim +./setup.sh config dkim keysize ``` For LDAP systems that do not have any directly created user account you can run the following command (since `8.0.0`) to generate the signature by additionally providing the desired domain name (if you have multiple domains use the command multiple times or provide a comma-separated list of domains): ```sh -./setup.sh config dkim [,] +./setup.sh config dkim keysize domain [,] ``` Now the keys are generated, you can configure your DNS server with DKIM signature, simply by adding a TXT record. If you have direct access to your DNS zone file, then it's only a matter of pasting the content of `config/opendkim/keys/domain.tld/mail.txt` in your `domain.tld.hosts` zone. @@ -78,6 +78,10 @@ SendReports yes Mode v ``` +## Switch Off DKIM + +Simply remove the DKIM key by recreating (not just relaunching) the mailserver container. + ## Debugging - [DKIM-verifer](https://addons.mozilla.org/en-US/thunderbird/addon/dkim-verifier): A add-on for the mail client Thunderbird. @@ -106,6 +110,10 @@ mail._domainkey.domain.tld. 3600 IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBA ;; MSG SIZE rcvd: 310 ``` -## Switch Off DKIM +--- -Simply remove the DKIM key by recreating (not just relaunching) the mailserver container. +!!! warning "Key sizes >=4096-bit" + + Keys of 4096 bits could de denied by some mailservers. According to https://tools.ietf.org/html/rfc6376 keys are preferably between 512 and 2048 bits. See issue [#1854][github-issue-1854]. + +[github-issue-1854]: https://github.com/docker-mailserver/docker-mailserver/issues/1854 diff --git a/docs/content/config/setup.sh.md b/docs/content/config/setup.sh.md index 70bff7a5..48b99498 100644 --- a/docs/content/config/setup.sh.md +++ b/docs/content/config/setup.sh.md @@ -19,7 +19,7 @@ chmod a+x ./setup.sh ## Usage -Run `./setup.sh -h` and you'll get some usage information: +Run `./setup.sh help` and you'll get some usage information: ```bash setup.sh Bootstrapping Script @@ -30,7 +30,7 @@ OPTIONS: -i IMAGE_NAME The name of the docker-mailserver image The default value is - 'docker.io/mailserver/docker-maiserver:latest' + 'docker.io/mailserver/docker-mailserver:latest' -c CONTAINER_NAME The name of the running container. diff --git a/docs/content/config/troubleshooting/debugging.md b/docs/content/config/troubleshooting/debugging.md index 4d608847..0c7deb87 100644 --- a/docs/content/config/troubleshooting/debugging.md +++ b/docs/content/config/troubleshooting/debugging.md @@ -53,7 +53,7 @@ fail2ban-client stop dovecot fail2ban-client stop postfix ``` -## Send email is never received +## Sent email is never received Some hosting provides have a stealth block on port 25. Make sure to check with your hosting provider that traffic on port 25 is allowed diff --git a/docs/content/examples/uses-cases/forward-only-mailserver-with-ldap-authentication.md b/docs/content/examples/uses-cases/forward-only-mailserver-with-ldap-authentication.md index b3033299..b62964e6 100644 --- a/docs/content/examples/uses-cases/forward-only-mailserver-with-ldap-authentication.md +++ b/docs/content/examples/uses-cases/forward-only-mailserver-with-ldap-authentication.md @@ -105,5 +105,9 @@ You see that besides `query_filter`, I had to customize as well `result_attribut Another solution that serves as a forward-only mailserver is this: https://gitlab.com/docker-scripts/postfix +!!! tip + + One user reports only having success if `ENABLE_LDAP=0` was set. + [github-file-readme-patches]: https://github.com/docker-mailserver/docker-mailserver/blob/master/README.md#custom-user-changes--patches [github-issue-1247]: https://github.com/docker-mailserver/docker-mailserver/issues/1247