Skip to content

Commit

Permalink
Securely build and push Docker images (#292)
Browse files Browse the repository at this point in the history
Refs #70.
Closes #224.
  • Loading branch information
AlekSi committed Jan 29, 2022
1 parent 2aca99a commit ba7f06a
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 118 deletions.
36 changes: 0 additions & 36 deletions .github/workflows/docker-build.yml

This file was deleted.

73 changes: 0 additions & 73 deletions .github/workflows/docker-push.yml

This file was deleted.

106 changes: 106 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
# This action is dangerous and should be handled with a lot of care to avoid security problems. We use
# `pull_request_target` event to give pull requests access to `GITHUB_TOKEN` secret with permissions
# to publish Docker images. We want to do that to allow Dance to test those PRs. But rogue PR authors could
# try to steal our secrets. We prevent that with the following:
#
# * We require approval for PRs from first-time contributors. That's a built-in feature for all actions.
# * After reviewing changes, we require the `trust` label to be assigned to PRs by FerretDB maintainers.
# Only a few trusted people have permission to do that.
# * Thanks to the way `pull_request_target` trigger works, PR changes in the workflow itself are not run
# until they are merged.
# * We use a short-living automatic `GITHUB_TOKEN` instead of a long-living personal access token (PAT).
# It also has minimal permissions.
# * We publish Docker images from PRs as a separate package that should not be run by users.
# * We limit what third-party actions can be used.
#
# We also tried a different approach: build Docker image in one normal, secure `pull_request` workflow,
# upload artifact, and then download and publish it in another workflow that has access to secrets, but treats
# artifact as passive data. We use buildx for building multi-platform images, and there is a way to export
# multi-platform OCI tarball: https://docs.docker.com/engine/reference/commandline/buildx_build/#output
# Unfortunately, it seems that there is no way to import that tarball in another workflow and publish it
# as a Docker image, as strange as it sounds: https://github.com/docker/buildx/issues/186
#
# Relevant GitHub documentation is scattered. The first article gives a good overview:
# * https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
# * https://docs.github.com/en/actions/security-guides/automatic-token-authentication
# * https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions
# * https://docs.github.com/en/packages/learn-github-packages/configuring-a-packages-access-control-and-visibility
# * https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-action
# * https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry

name: Docker
on:
pull_request_target:
types:
- labeled
- opened
- reopened
- synchronize
push:
branches:
- main
schedule:
- cron: '42 4 * * *'

env:
GOPATH: /home/runner/go
GOCACHE: /home/runner/go/cache
GOMODCACHE: /home/runner/go/cache/mod
GOPROXY: https://proxy.golang.org # remove direct

jobs:
build:
name: Build
runs-on: ubuntu-20.04

if: github.event_name != 'pull_request_target' || contains(github.event.pull_request.labels.*.name, 'trust')

permissions:
packages: write

steps:
- name: Checkout code
if: github.event_name != 'pull_request_target'
uses: actions/checkout@v2
with:
fetch-depth: 0

- name: Checkout pull request code
if: github.event_name == 'pull_request_target'
uses: actions/checkout@v2
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}

- name: Setup Go
uses: FerretDB/github-actions/setup-go@main
with:
cache-key: build

- name: Run init
run: make init

- name: Initialize Docker Buildx builder
run: make docker-init

- name: Extract Docker image name and tag
id: extract
uses: FerretDB/github-actions/extract-docker-tag@main

- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build Docker image ${{ steps.extract.outputs.ghcr }}
run: make docker-push
env:
DOCKER_IMAGE: ${{ steps.extract.outputs.ghcr }}

- name: Check dirty
run: |
git status
git diff --exit-code
6 changes: 5 additions & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
---
name: Go
on:
pull_request:
types:
- opened
- reopened
- synchronize
push:
branches:
- main
pull_request:
schedule:
- cron: '42 3 * * *'

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ old.txt
new.txt

internal/util/version/version.txt
internal/util/version/commit.txt
internal/util/version/branch.txt

# for now
Expand Down
13 changes: 9 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
FROM scratch

ARG VERSION
ARG COMMIT
ARG TARGETARCH

ADD bin/ferretdb-${TARGETARCH} /ferretdb
Expand All @@ -9,9 +11,12 @@ EXPOSE 27017
ENTRYPOINT [ "/ferretdb" ]
CMD [ "-mode=diff-normal" ]

LABEL org.opencontainers.image.source=https://github.com/FerretDB/FerretDB
LABEL org.opencontainers.image.url=https://ferretdb.io/
LABEL org.opencontainers.image.title=FerretDB
# https://github.com/opencontainers/image-spec/blob/main/annotations.md
LABEL org.opencontainers.image.description="A truly Open Source MongoDB alternative"
LABEL org.opencontainers.image.vendor="FerretDB Inc."
LABEL org.opencontainers.image.licenses="Apache-2.0"
LABEL org.opencontainers.image.revision="${COMMIT}"
LABEL org.opencontainers.image.source="https://github.com/FerretDB/FerretDB"
LABEL org.opencontainers.image.title="FerretDB"
LABEL org.opencontainers.image.url="https://ferretdb.io/"
LABEL org.opencontainers.image.vendor="FerretDB Inc."
LABEL org.opencontainers.image.version="${VERSION}"
13 changes: 10 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,19 @@ docker-build: build-testcover
env GOOS=linux GOARCH=amd64 go test -c -o=bin/ferretdb-amd64 -trimpath -tags=testcover -coverpkg=./... ./cmd/ferretdb

docker-local: docker-build
docker buildx build --builder=ferretdb --tag=ferretdb-local --load .
docker buildx build --builder=ferretdb \
--build-arg VERSION=$(shell cat internal/util/version/version.txt) \
--build-arg COMMIT=$(shell cat internal/util/version/commit.txt) \
--tag=ferretdb-local \
--load .

docker-push: docker-build
test $(DOCKER_IMAGE)
test $(DOCKER_TAG)
docker buildx build --builder=ferretdb --platform=linux/arm64,linux/amd64 --tag=$(DOCKER_IMAGE):$(DOCKER_TAG) --push .
docker buildx build --builder=ferretdb --platform=linux/arm64,linux/amd64 \
--build-arg VERSION=$(shell cat internal/util/version/version.txt) \
--build-arg COMMIT=$(shell cat internal/util/version/commit.txt) \
--tag=$(DOCKER_IMAGE) \
--push .

bin/golangci-lint:
$(MAKE) init
Expand Down
1 change: 1 addition & 0 deletions internal/util/version/generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
set -e

git describe --tags --dirty > version.txt
git rev-parse HEAD > commit.txt
git branch --show-current > branch.txt
10 changes: 9 additions & 1 deletion internal/util/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package version

import (
_ "embed"
"fmt"
"runtime/debug"
"strconv"
"strings"
Expand All @@ -31,6 +32,9 @@ var (
//go:embed version.txt
version string

//go:embed commit.txt
commit string

//go:embed branch.txt
branch string
)
Expand All @@ -53,6 +57,7 @@ func Get() *Info {
func init() {
info = &Info{
Version: strings.TrimSpace(version),
Commit: strings.TrimSpace(commit),
Branch: strings.TrimSpace(branch),
}

Expand All @@ -64,9 +69,12 @@ func init() {
info.BuildEnvironment = types.MustNewDocument()
for _, s := range buildInfo.Settings {
info.BuildEnvironment.Set(s.Key, s.Value)

switch s.Key {
case "vcs.revision":
info.Commit = s.Value
if s.Value != info.Commit {
panic(fmt.Sprintf("commit.txt value %q != vcs.revision value %q", info.Commit, s.Value))
}
case "vcs.modified":
info.Dirty, _ = strconv.ParseBool(s.Value)
case "-race":
Expand Down

0 comments on commit ba7f06a

Please sign in to comment.