diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9c3d21f..371258c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,8 +3,8 @@ # These owners will be the default owners for everything in the # repo. Unless a later match takes precedence, these owners will be # requested for review when someone opens a pull request. -* @dav3r @felddy @hillaryj @jsf9k @mcdonnnj +* @dav3r @felddy @jsf9k @mcdonnnj # These folks own any files in the .github directory at the root of # the repository and any of its subdirectories. -/.github/ @dav3r @felddy @hillaryj @jsf9k @mcdonnnj +/.github/ @dav3r @felddy @jsf9k @mcdonnnj diff --git a/.github/lineage.yml b/.github/lineage.yml index b10c80c..14f5a0e 100644 --- a/.github/lineage.yml +++ b/.github/lineage.yml @@ -1,6 +1,5 @@ --- -version: "1" - lineage: skeleton: remote-url: https://github.com/cisagov/skeleton-docker.git +version: '1' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 473a363..35b844d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,11 +26,13 @@ on: env: BUILDX_CACHE_DIR: ~/.cache/buildx + CURL_CACHE_DIR: ~/.cache/curl IMAGE_NAME: cisagov/postfix PIP_CACHE_DIR: ~/.cache/pip PLATFORMS: "linux/amd64,linux/arm/v6,linux/arm/v7,\ linux/arm64,linux/ppc64le,linux/s390x" PRE_COMMIT_CACHE_DIR: ~/.cache/pre-commit + RUN_TMATE: ${{ secrets.RUN_TMATE }} jobs: lint: @@ -39,25 +41,88 @@ jobs: name: "Lint sources" runs-on: ubuntu-latest steps: + - uses: cisagov/setup-env-github-action@develop - uses: actions/checkout@v2 - id: setup-python uses: actions/setup-python@v2 with: python-version: 3.9 + # GO_VERSION and GOCACHE are used by the cache task, so the Go + # installation must happen before that. + - uses: actions/setup-go@v2 + with: + go-version: '1.16' + - name: Store installed Go version + run: | + echo "GO_VERSION="\ + "$(go version | sed 's/^go version go\([0-9.]\+\) .*/\1/')" \ + >> $GITHUB_ENV + - name: Lookup Go cache directory + id: go-cache + run: | + echo "::set-output name=dir::$(go env GOCACHE)" - uses: actions/cache@v2 env: BASE_CACHE_KEY: "${{ github.job }}-${{ runner.os }}-\ - py${{ steps.setup-python.outputs.python-version }}-" + py${{ steps.setup-python.outputs.python-version }}-\ + go${{ env.GO_VERSION }}-\ + packer${{ env.PACKER_VERSION }}-\ + tf${{ env.TERRAFORM_VERSION }}-" with: + # Note that the .terraform directory IS NOT included in the + # cache because if we were caching, then we would need to use + # the `-upgrade=true` option. This option blindly pulls down the + # latest modules and providers instead of checking to see if an + # update is required. That behavior defeats the benefits of caching. + # so there is no point in doing it for the .terraform directory. path: | ${{ env.PIP_CACHE_DIR }} ${{ env.PRE_COMMIT_CACHE_DIR }} + ${{ env.CURL_CACHE_DIR }} + ${{ steps.go-cache.outputs.dir }} key: "${{ env.BASE_CACHE_KEY }}\ ${{ hashFiles('**/requirements-test.txt') }}-\ ${{ hashFiles('**/requirements.txt') }}-\ ${{ hashFiles('**/.pre-commit-config.yaml') }}" restore-keys: | ${{ env.BASE_CACHE_KEY }} + - name: Setup curl cache + run: mkdir -p ${{ env.CURL_CACHE_DIR }} + - name: Install Packer + run: | + PACKER_ZIP="packer_${PACKER_VERSION}_linux_amd64.zip" + curl --output ${{ env.CURL_CACHE_DIR }}/"${PACKER_ZIP}" \ + --time-cond ${{ env.CURL_CACHE_DIR }}/"${PACKER_ZIP}" \ + --location \ + "https://releases.hashicorp.com/packer/${PACKER_VERSION}/${PACKER_ZIP}" + sudo unzip -d /opt/packer \ + ${{ env.CURL_CACHE_DIR }}/"${PACKER_ZIP}" + sudo mv /usr/local/bin/packer /usr/local/bin/packer-default + sudo ln -s /opt/packer/packer /usr/local/bin/packer + - name: Install Terraform + run: | + TERRAFORM_ZIP="terraform_${TERRAFORM_VERSION}_linux_amd64.zip" + curl --output ${{ env.CURL_CACHE_DIR }}/"${TERRAFORM_ZIP}" \ + --time-cond ${{ env.CURL_CACHE_DIR }}/"${TERRAFORM_ZIP}" \ + --location \ + "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/${TERRAFORM_ZIP}" + sudo unzip -d /opt/terraform \ + ${{ env.CURL_CACHE_DIR }}/"${TERRAFORM_ZIP}" + sudo mv /usr/local/bin/terraform /usr/local/bin/terraform-default + sudo ln -s /opt/terraform/terraform /usr/local/bin/terraform + - name: Install shfmt + run: go install mvdan.cc/sh/v3/cmd/shfmt@${SHFMT_VERSION} + - name: Install Terraform-docs + run: | + go install \ + github.com/terraform-docs/terraform-docs@${TERRAFORM_DOCS_VERSION} + - name: Find and initialize Terraform directories + run: | + for path in $(find . -not \( -type d -name ".terraform" -prune \) \ + -type f -iname "*.tf" -exec dirname "{}" \; | sort -u); do \ + echo "Initializing '$path'..."; \ + terraform init -input=false -backend=false "$path"; \ + done - name: Install dependencies run: | python -m pip install --upgrade pip @@ -66,7 +131,9 @@ jobs: run: pre-commit install-hooks - name: Run pre-commit on all files run: pre-commit run --all-files - + - name: Setup tmate debug session + uses: mxschmitt/action-tmate@v3 + if: env.RUN_TMATE prepare: # Calculates and publishes outputs that are used by other jobs. # @@ -79,13 +146,13 @@ jobs: # The source version as reported by the `bump_version.sh show` command. # tags: # A comma separated list of Docker tags to be applied to the images on - # DockerHub. The tags will vary depending on: + # Docker Hub. The tags will vary depending on: # - The event that triggered the build. # - The branch the build is based upon. # - The git tag the build is based upon. # # When a build is based on a git tag of the form `v*.*.*` the image will - # be tagged on DockerHub with multiple levels of version specificity. + # be tagged on Docker Hub with multiple levels of version specificity. # For example, a git tag of `v1.2.3+a` will generate Docker tags of # `:1.2.3_a`, `:1.2.3`, `:1.2`, `:1`, and `:latest`. # @@ -119,7 +186,7 @@ jobs: - uses: actions/checkout@v2 - name: Gather repository metadata id: repo - uses: actions/github-script@v3 + uses: actions/github-script@v4 with: script: | const repo = await github.repos.get(context.repo) @@ -156,14 +223,17 @@ jobs: if [ "${{ github.event_name }}" = "push" ]; then TAGS="${TAGS},${IMAGE_NAME}:sha-${GITHUB_SHA::8}" fi + for i in ${TAGS//,/ } + do + TAGS="${TAGS},ghcr.io/${i}" + done echo ::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ') echo ::set-output name=source_version::$(./bump_version.sh show) echo ::set-output name=tags::${TAGS} echo tags=${TAGS} - - name: Setup debug session remote shell + - name: Setup tmate debug session uses: mxschmitt/action-tmate@v3 - if: github.event.inputs.remote-shell == 'true' - + if: github.event.inputs.remote-shell == 'true' || env.RUN_TMATE build: # Builds a single test image for the native platform. This image is saved # as an artifact and loaded by the test job. @@ -232,7 +302,9 @@ jobs: with: name: dist path: dist - + - name: Setup tmate debug session + uses: mxschmitt/action-tmate@v3 + if: env.RUN_TMATE test: # Executes tests on the single-platform image created in the "build" job. name: "Test image" @@ -271,23 +343,32 @@ jobs: env: RELEASE_TAG: ${{ github.event.release.tag_name }} run: pytest --runslow - + - name: Setup tmate debug session + uses: mxschmitt/action-tmate@v3 + if: env.RUN_TMATE build-push-all: # Builds the final set of images for each of the platforms listed in # PLATFORMS environment variable. These images are tagged with the Docker - # tags calculated in the "prepare" job and pushed to DockerHub. The - # contents of README.md is pushed as the image's description. This job is - # skipped when the triggering event is a pull request. + # tags calculated in the "prepare" job and pushed to Docker Hub and the + # GitHub Container Registry. The contents of README.md are pushed as the + # image's description to Docker Hub. This job is skipped when the + # triggering event is a pull request. name: "Build and push all platforms" runs-on: ubuntu-latest needs: [lint, prepare, test] if: github.event_name != 'pull_request' steps: - - name: Login to DockerHub + - name: Login to Docker Hub uses: docker/login-action@v1 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Checkout uses: actions/checkout@v2 - name: Set up QEMU @@ -305,7 +386,7 @@ jobs: ${{ env.BASE_CACHE_KEY }} - name: Create cross-platform support Dockerfile-x run: ./buildx-dockerfile.sh - - name: Build and push platform images to Docker Hub + - name: Build and push platform images to registries id: docker_build uses: docker/build-push-action@v2 with: @@ -348,3 +429,6 @@ jobs: DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} run: ./push_readme.sh + - name: Setup tmate debug session + uses: mxschmitt/action-tmate@v3 + if: env.RUN_TMATE diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 127ef08..33d1999 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -9,6 +9,9 @@ name: "CodeQL" on: push: + # Dependabot triggered push events have read-only access, but uploading code + # scanning requires write access. + branches-ignore: [dependabot/**] pull_request: # The branches below must be a subset of the branches above branches: [develop] diff --git a/.gitignore b/.gitignore index bceb4ee..b1efc19 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,12 @@ +# This file specifies intentionally untracked files that Git should ignore. +# Files already tracked by Git are not affected. +# See: https://git-scm.com/docs/gitignore + +## Docker ## +Dockerfile-x + +## Python ## __pycache__ .mypy_cache .pytest_cache .python-version -Dockerfile-x diff --git a/.mdl_config.json b/.mdl_config.json deleted file mode 100644 index 7a6f3f8..0000000 --- a/.mdl_config.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "MD013": { - "code_blocks": false, - "tables": false - }, - "MD024": { - "allow_different_nesting": true - }, - "default": true -} diff --git a/.mdl_config.yaml b/.mdl_config.yaml new file mode 100644 index 0000000..a2f08f3 --- /dev/null +++ b/.mdl_config.yaml @@ -0,0 +1,40 @@ +--- + +# Default state for all rules +default: true + +# MD003/heading-style/header-style - Heading style +MD003: + # Enforce the ATX-closed style of header + style: "atx_closed" + +# MD004/ul-style - Unordered list style +MD004: + # Enforce dashes for unordered lists + style: "dash" + +# MD013/line-length - Line length +MD013: + # Do not enforce for code blocks + code_blocks: false + # Do not enforce for tables + tables: false + +# MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the +# same content +MD024: + # Allow headers with the same content as long as they are not in the same + # parent heading + allow_different_nesting: true + +# MD029/ol-prefix - Ordered list item prefix +MD029: + # Enforce the `1.` style for ordered lists + style: "one" + +# MD033/no-inline-html - Inline HTML +MD033: + # The h1 and img elements are allowed to permit header images + allowed_elements: + - h1 + - img diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bc4a0de..b82eda1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,7 +5,7 @@ default_language_version: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v4.0.1 hooks: - id: check-case-conflict - id: check-executables-have-shebangs @@ -32,30 +32,43 @@ repos: # Text file hooks - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.26.0 + rev: v0.27.1 hooks: - id: markdownlint args: - - --config=.mdl_config.json + - --config=.mdl_config.yaml - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.2.1 + rev: v2.3.2 hooks: - id: prettier - repo: https://github.com/adrienverge/yamllint - rev: v1.26.0 + rev: v1.26.1 hooks: - id: yamllint args: - --strict + # pre-commit hooks + - repo: https://github.com/pre-commit/pre-commit + rev: v2.13.0 + hooks: + - id: validate_manifest + # Shell script hooks - - repo: https://github.com/lovesegfault/beautysh - rev: 6.0.1 + - repo: https://github.com/cisagov/pre-commit-shfmt + rev: v0.0.2 hooks: - - id: beautysh + - id: shfmt args: - - --indent-size + # Indent by two spaces + - -i - '2' + # Binary operators may start a line + - -bn + # Switch cases are indented + - -ci + # Redirect operators are followed by a space + - -sr - repo: https://github.com/detailyang/pre-commit-shell rev: 1.0.5 hooks: @@ -77,26 +90,26 @@ repos: - id: bandit name: bandit (everything else) exclude: tests - - repo: https://github.com/python/black - rev: 20.8b1 + - repo: https://github.com/psf/black + rev: 21.7b0 hooks: - id: black - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.4 + rev: 3.9.2 hooks: - id: flake8 additional_dependencies: - flake8-docstrings - repo: https://github.com/PyCQA/isort - rev: 5.7.0 + rev: 5.9.2 hooks: - id: isort - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.800 + rev: v0.910 hooks: - id: mypy - repo: https://github.com/asottile/pyupgrade - rev: v2.10.0 + rev: v2.21.2 hooks: - id: pyupgrade @@ -111,7 +124,7 @@ repos: # Terraform hooks - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.45.0 + rev: v1.50.0 hooks: - id: terraform_fmt # There are ongoing issues with how this command works. This issue @@ -133,7 +146,7 @@ repos: # Docker hooks - repo: https://github.com/IamTheFij/docker-pre-commit - rev: v2.0.0 + rev: v2.0.1 hooks: - id: docker-compose-check diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1ea6e89..c57cac0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,6 +64,9 @@ installation is as simple as `brew install pyenv pyenv-virtualenv` and adding this to your profile: ```bash +export PYENV_ROOT="$HOME/.pyenv" +export PATH="$PYENV_ROOT/bin:$PATH" +eval "$(pyenv init --path)" eval "$(pyenv init -)" eval "$(pyenv virtualenv-init -)" ``` @@ -80,14 +83,35 @@ On WSL you should treat your platform as whatever Linux distribution you've chosen to install. Once you have installed `pyenv` you will need to add the following -lines to your `.bashrc`: +lines to your `.bash_profile` (or `.profile`): + +```bash +export PYENV_ROOT="$HOME/.pyenv" +export PATH="$PYENV_ROOT/bin:$PATH" +eval "$(pyenv init --path)" +``` + +and then add the following lines to your `.bashrc`: ```bash -export PATH="$PATH:$HOME/.pyenv/bin" eval "$(pyenv init -)" eval "$(pyenv virtualenv-init -)" ``` +If you want more information about setting up `pyenv` once installed, please run + +```console +pyenv init +``` + +and + +```console +pyenv virtualenv-init +``` + +for the current configuration instructions. + If you are using a shell other than `bash` you should follow the instructions that the `pyenv-installer` script outputs. diff --git a/README.md b/README.md index ada92d5..7df39d3 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # postfix-docker 📮🐳 # -[![GitHub Build Status](https://github.com/cisagov/postfix-docker/workflows/build/badge.svg)](https://github.com/cisagov/postfix-docker/actions) -[![Total alerts](https://img.shields.io/lgtm/alerts/g/cisagov/postfix-docker.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/cisagov/postfix-docker/alerts/) -[![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/cisagov/postfix-docker.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/cisagov/postfix-docker/context:python) +[![GitHub Build Status](https://github.com/cisagov/postfix-docker/workflows/build/badge.svg)](https://github.com/cisagov/postfix-docker/actions/workflows/build.yml) +[![CodeQL](https://github.com/cisagov/postfix-docker/workflows/CodeQL/badge.svg)](https://github.com/cisagov/postfix-docker/actions/workflows/codeql-analysis.yml) +[![Known Vulnerabilities](https://snyk.io/test/github/cisagov/postfix-docker/badge.svg)](https://snyk.io/test/github/cisagov/postfix-docker) ## Docker Image ## @@ -15,55 +15,279 @@ Creates a Docker container with an installation of the server ([dovecot](https://dovecot.org)) for accessing the archvies of sent email. All email is BCC'd to the `mailarchive` account. -## Usage ## +## Running ## -### Install ### +### Running with Docker ### -Pull `cisagov/postfix` from the Docker repository: +To run the `cisagov/postfix` image via Docker: - docker pull cisagov/postfix +```console +docker run cisagov/postfix:0.0.4 +``` -Or build `cisagov/postfix` from source: +### Running with Docker Compose ### - git clone https://github.com/cisagov/postfix-docker.git - cd postfix-docker - docker-compose build --build-arg VERSION=0.0.1 +1. Create a `docker-compose.yml` file similar to the one below to use [Docker Compose](https://docs.docker.com/compose/) +or use the [sample `docker-compose.yml`](docker-compose.yml) provided with +this repository. + + ```yaml + --- + version: "3.7" + + services: + postfix: + build: + # VERSION must be specified on the command line: + # e.g., --build-arg VERSION=0.0.4 + context: . + dockerfile: Dockerfile + image: cisagov/postfix + init: true + restart: always + environment: + - PRIMARY_DOMAIN=example.com + - RELAY_IP=172.16.202.1/32 + networks: + front: + ipv4_address: 172.16.202.2 + ports: + - target: "25" + published: "1025" + protocol: tcp + mode: host + - target: "587" + published: "1587" + protocol: tcp + mode: host + - target: "993" + published: "1993" + protocol: tcp + mode: host + + networks: + front: + driver: bridge + ipam: + driver: default + config: + - subnet: 172.16.202.0/24 + ``` + +1. Start the container and detach: + + ```console + docker-compose up --detach + ``` + +## Using secrets with your container ## + +This container also supports passing sensitive values via [Docker +secrets](https://docs.docker.com/engine/swarm/secrets/). Passing sensitive +values like your credentials can be more secure using secrets than using +environment variables. See the +[secrets](#secrets) section below for a table of all supported secret files. + +1. To use secrets, populate the following files in the `src/secrets` directory: + +- `fullchain.pem` +- `privkey.pem` +- `users.txt` + +1. Then add the secrets to your `docker-compose.yml` file: + + ```yaml + --- + version: "3.7" + + secrets: + fullchain_pem: + file: ./src/secrets/fullchain.pem + privkey_pem: + file: ./src/secrets/privkey.pem + users_txt: + file: ./src/secrets/users.txt + + services: + postfix: + build: + # VERSION must be specified on the command line: + # e.g., --build-arg VERSION=0.0.4 + context: . + dockerfile: Dockerfile + image: cisagov/postfix + init: true + restart: always + environment: + - PRIMARY_DOMAIN=example.com + - RELAY_IP=172.16.202.1/32 + networks: + front: + ipv4_address: 172.16.202.2 + ports: + - target: "25" + published: "1025" + protocol: tcp + mode: host + - target: "587" + published: "1587" + protocol: tcp + mode: host + - target: "993" + published: "1993" + protocol: tcp + mode: host + secrets: + - source: fullchain_pem + target: fullchain.pem + - source: privkey_pem + target: privkey.pem + - source: users_txt + target: users.txt + + networks: + front: + driver: bridge + ipam: + driver: default + config: + - subnet: 172.16.202.0/24 + ``` + +## Updating your container ## + +### Docker Compose ### + +1. Pull the new image from Docker Hub: + + ```console + docker-compose pull + ``` + +1. Recreate the running container by following the [previous instructions](#running-with-docker-compose): + + ```console + docker-compose up --detach + ``` + +### Docker ### -A sample [docker composition](docker-compose.yml) is included in this repository. -To build and start the container use the command: `docker-compose up` +1. Stop the running container: -### Ports ### + ```console + docker stop + ``` -This container exposes the following ports: +1. Pull the new image: -- 25: `smtp` -- 587: `submission` -- 993: `imaps` + ```console + docker pull cisagov/postfix:0.0.4 + ``` -The sample [docker composition](docker-compose.yml) publishes the -exposed ports at 1025, 1587, and 1993. +1. Recreate and run the container by following the [previous instructions](#running-with-docker). -### Environment Variables ### +## Image tags ## -Two environment variables are used to generate the configurations at runtime: +The images of this container are tagged with [semantic +versions](https://semver.org) of the underlying Postfix project that they +containerize. It is recommended that most users use a version tag (e.g. +`:0.0.4`). -- `PRIMARY_DOMAIN`: the domain of the mail server -- `RELAY_IP`: (optional) an IP address that is allowed to relay mail without authentication +| Image:tag | Description | +|-----------|-------------| +|`cisagov/postfix:0.0.4`| An exact release version. | +|`cisagov/postfix:0.0`| The most recent release matching the major and minor version numbers. | +|`cisagov/postfix:0`| The most recent release matching the major version number. | +|`cisagov/postfix:edge` | The most recent image built from a merge into the `develop` branch of this repository. | +|`cisagov/postfix:nightly` | A nightly build of the `develop` branch of this repository. | +|`cisagov/postfix:latest`| The most recent release image pushed to a container registry. Pulling an image using the `:latest` tag [should be avoided.](https://vsupalov.com/docker-latest-tag/) | -### Secrets ### +See the [tags tab](https://hub.docker.com/r/cisagov/postfix/tags) on Docker +Hub for a list of all the supported tags. + +## Volumes ## + +| Mount point | Purpose | +|-------------|---------| +| `/var/log` | System logs | +| `/var/spool/postfix` | Mail queues | + +## Ports ## + +The following ports are exposed by this container: + +| Port | Purpose | +|------|----------------| +| 25 | SMTP relay | +| 587 | Mail submission | +| 993 | IMAPS | + +The sample [Docker composition](docker-compose.yml) publishes the +exposed ports at 1025, 1587, and 1993, respectively. + +## Environment variables ## + +### Required ### + +| Name | Purpose | +|-------|---------| +| `PRIMARY_DOMAIN` | The primary domain of the mail server. | + +### Optional ### + +| Name | Purpose | Default | +|-------|---------|---------| +| `RELAY_IP` | An IP address that is allowed to relay mail without authentication. | `null` | + +## Secrets ## + +| Filename | Purpose | +|--------------|---------| +| `fullchain.pem` | Public key for the Postfix server. | +| `privkey.pem` | Private key for the Postfix server. | +| `users.txt` | Mail account credentials to create at startup. | + +## Building from source ## + +Build the image locally using this git repository as the [build context](https://docs.docker.com/engine/reference/commandline/build/#git-repositories): + +```console +docker build \ + --build-arg VERSION=0.0.4 \ + --tag cisagov/postfix:0.0.4 \ + https://github.com/cisagov/postfix-docker.git#develop +``` + +## Cross-platform builds ## + +To create images that are compatible with other platforms, you can use the +[`buildx`](https://docs.docker.com/buildx/working-with-buildx/) feature of +Docker: + +1. Copy the project to your machine using the `Code` button above + or the command line: + + ```console + git clone https://github.com/cisagov/postfix-docker.git + cd postfix-docker + ``` -- `fullchain.pem`: public key -- `privkey.pem`: private key -- `users.txt`: account credentials to create at startup +1. Create the `Dockerfile-x` file with `buildx` platform support: -### Volumes ### + ```console + ./buildx-dockerfile.sh + ``` -Two optional volumes can be attached to this container to persist the -mail spool directory, as well as the logging directory. (Note that -the mail logs are available using the docker log command.) +1. Build the image using `buildx`: -- `/var/spool/postfix`: mail queues -- `/var/log`: system logs + ```console + docker buildx build \ + --file Dockerfile-x \ + --platform linux/amd64 \ + --build-arg VERSION=0.0.4 \ + --output type=docker \ + --tag cisagov/postfix:0.0.4 . + ``` ## Contributing ## diff --git a/bump_version.sh b/bump_version.sh index 81a394f..a6c8ed9 100755 --- a/bump_version.sh +++ b/bump_version.sh @@ -7,24 +7,26 @@ set -o errexit set -o pipefail VERSION_FILE=src/version.txt +README_FILE=README.md HELP_INFORMATION="bump_version.sh (show|major|minor|patch|prerelease|build|finalize)" old_version=$(sed -n "s/^__version__ = \"\(.*\)\"$/\1/p" $VERSION_FILE) -if [ $# -ne 1 ] -then +if [ $# -ne 1 ]; then echo "$HELP_INFORMATION" else case $1 in - major|minor|patch|prerelease|build) + major | minor | patch | prerelease | build) new_version=$(python -c "import semver; print(semver.bump_$1('$old_version'))") echo Changing version from "$old_version" to "$new_version" tmp_file=/tmp/version.$$ sed "s/$old_version/$new_version/" $VERSION_FILE > $tmp_file mv $tmp_file $VERSION_FILE - git add $VERSION_FILE - git commit -m"Bumping version from $old_version to $new_version" + sed "s/$old_version/$new_version/" $README_FILE > $tmp_file + mv $tmp_file $README_FILE + git add $VERSION_FILE $README_FILE + git commit -m"Bump version from $old_version to $new_version" git push ;; finalize) @@ -33,8 +35,10 @@ else tmp_file=/tmp/version.$$ sed "s/$old_version/$new_version/" $VERSION_FILE > $tmp_file mv $tmp_file $VERSION_FILE - git add $VERSION_FILE - git commit -m"Bumping version from $old_version to $new_version" + sed "s/$old_version/$new_version/" $README_FILE > $tmp_file + mv $tmp_file $README_FILE + git add $VERSION_FILE $README_FILE + git commit -m"Bump version from $old_version to $new_version" git push ;; show) diff --git a/push_readme.sh b/push_readme.sh index 12e2340..29b12aa 100755 --- a/push_readme.sh +++ b/push_readme.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Push the README.md file to the docker hub repository +# Push the README.md file to the Docker Hub repository # Requires the following environment variables to be set: # DOCKER_PASSWORD, DOCKER_USERNAME, IMAGE_NAME @@ -11,19 +11,19 @@ set -o pipefail echo "Logging in and requesting JWT..." token=$(curl --silent --request POST \ - --header "Content-Type: application/json" \ - --data \ - '{"username": "'"$DOCKER_USERNAME"'", "password": "'"$DOCKER_PASSWORD"'"}' \ + --header "Content-Type: application/json" \ + --data \ + '{"username": "'"$DOCKER_USERNAME"'", "password": "'"$DOCKER_PASSWORD"'"}' \ https://hub.docker.com/v2/users/login/ | jq --raw-output .token) echo "Pushing README file..." -code=$(jq --null-input --arg msg "$(" fi - adduser "$username" --quiet --disabled-password --shell /usr/sbin/nologin --gecos "" &>/dev/null || true + adduser "$username" --quiet --disabled-password --shell /usr/sbin/nologin --gecos "" &> /dev/null || true echo "$username:$password" | chpasswd || true done echo "--------------------------------------------" } - if [ "$1" = 'postfix' ]; then echo "Starting mail server with:" echo " PRIMARY_DOMAIN=${PRIMARY_DOMAIN}" diff --git a/tag.sh b/tag.sh new file mode 100755 index 0000000..e1f7447 --- /dev/null +++ b/tag.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +set -o nounset +set -o errexit +set -o pipefail + +version=$(./bump_version.sh show) + +git tag "v$version" && git push --tags diff --git a/tests/conftest.py b/tests/conftest.py index 029522e..e069ffc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,7 +10,7 @@ @pytest.fixture(scope="session") def main_container(dockerc): - """Return the main container from the docker composition.""" + """Return the main container from the Docker composition.""" # find the container by name even if it is stopped already return dockerc.containers(service_names=[MAIN_SERVICE_NAME], stopped=True)[0] diff --git a/tests/container_test.py b/tests/container_test.py index d9a889f..d5a21bc 100644 --- a/tests/container_test.py +++ b/tests/container_test.py @@ -75,18 +75,20 @@ def test_imap_login(username, password): m.login(username, password) +# Note that "username" is changed to "user" in this function to work around +# a CodeQL failure for "Clear-text logging of sensitive information". :( @pytest.mark.parametrize( - "username,password", [(ARCHIVE_USER, ARCHIVE_PW), (TEST_SEND_USER, TEST_SEND_PW)] + "user,password", [(ARCHIVE_USER, ARCHIVE_PW), (TEST_SEND_USER, TEST_SEND_PW)] ) -def test_imap_messages_exist(username, password): +def test_imap_messages_exist(user, password): """Test test existence of our test messages.""" with IMAP4_SSL("localhost", IMAP_PORT) as m: - m.login(username, password) + m.login(user, password) typ, data = m.select() - assert typ == "OK", f"Select did not return OK status for {username}" + assert typ == "OK", f"Select did not return OK status for {user}" message_count = int(data[0]) - print(f"{username} inbox message count: {message_count}") - assert message_count > 0, f"Expected message in the {username} inbox" + print(f"{user} inbox message count: {message_count}") + assert message_count > 0, f"Expected message in the {user} inbox" @pytest.mark.parametrize(