Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Securely build and push Docker images #292

Merged
merged 2 commits into from
Jan 29, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 the download and publish in another workflow that has access to secrets, but treats
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# upload artifact, and the download and publish in another workflow that has access to secrets, but treats
# 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the way that this ensures that the version info which is used for builds is consistent with git.

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