Skip to content
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
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ backend/.env
frontend/node_modules
frontend/dist
frontend/.tanstack
*.exe
18 changes: 18 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,23 @@ jobs:
matrix:
include:
- name: hub
base_image: gcr.io/distroless/base-nossl-debian13:nonroot
- name: agent
base_image: gcr.io/distroless/base-nossl-debian13:latest
Comment thread
timokoessler marked this conversation as resolved.
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Install cosign
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1

- name: Verify distroless base image signature
run: |
cosign verify \
${{ matrix.base_image }} \
--certificate-oidc-issuer https://accounts.google.com \
--certificate-identity keyless@distroless.iam.gserviceaccount.com

- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0

Expand Down Expand Up @@ -82,6 +94,7 @@ jobs:
type=raw,value=main,enable=${{ github.event_name == 'workflow_dispatch' }}

- name: Build and push ${{ matrix.name }} image
id: build
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
with:
context: .
Expand All @@ -98,3 +111,8 @@ jobs:
cache-to: type=gha,mode=max
provenance: true
sbom: true

- name: Sign ${{ matrix.name }} image
run: |
cosign sign --yes \
${{ env.IMAGE_PREFIX }}/${{ matrix.name }}@${{ steps.build.outputs.digest }}
8 changes: 4 additions & 4 deletions agent.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
FROM bufbuild/buf:1.68 AS buf

FROM golang:1.26-alpine AS builder
FROM golang:1.26-trixie AS builder

ARG VERSION=dev
ARG COMMIT=none
ARG BUILD_DATE=unknown

RUN apk add --no-cache gcc musl-dev

WORKDIR /src
COPY backend/go.mod backend/go.sum ./
RUN go mod download
Expand All @@ -26,7 +24,9 @@ RUN CGO_ENABLED=1 go build \
-X github.com/OrcaCD/orca-cd/internal/version.BuildDate=${BUILD_DATE}" \
-o /bin/agent ./cmd/agent

FROM alpine:3.23
# Not using nonroot variant as agent has access to the docker socket which is basically root access
# Changing to nonroot user would complicate setup for users with minimal security benefits
FROM gcr.io/distroless/base-nossl-debian13:latest

WORKDIR /app

Expand Down
40 changes: 40 additions & 0 deletions backend/internal/hub/data_dir.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package hub

import (
"fmt"
"os"
)
Comment thread
timokoessler marked this conversation as resolved.

func initDataDir() error {
if err := os.MkdirAll("data", 0750); err != nil {
return err
}
return checkDataDirWritable()
}

func checkDataDirWritable() error {
return checkWritable("data")
}

func checkWritable(dir string) error {
f, err := os.CreateTemp(dir, ".write-check-*")
if err == nil {
err = f.Close()
if err != nil {
return err
}
err = os.Remove(f.Name())
if err != nil {
return err
}
return nil
}
if !os.IsPermission(err) {
return err
}
uid := os.Getuid()
return fmt.Errorf(
"directory is not writable, run: sudo chown -R %d:%d ./data",
uid, os.Getgid(),
Comment thread
timokoessler marked this conversation as resolved.
Outdated
)
}
23 changes: 23 additions & 0 deletions backend/internal/hub/data_dir_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package hub

import (
"os"
"testing"
)

func TestCheckWritable_WritableDir(t *testing.T) {
dir := t.TempDir()
if err := checkWritable(dir); err != nil {
t.Fatalf("expected nil for writable directory, got %v", err)
}
}

func TestCheckWritable_NonExistentDir(t *testing.T) {
err := checkWritable(t.TempDir() + "/does-not-exist")
if err == nil {
t.Fatal("expected error for non-existent directory, got nil")
}
if os.IsPermission(err) {
t.Errorf("expected a non-permission error for missing directory, got: %v", err)
}
}
30 changes: 30 additions & 0 deletions backend/internal/hub/data_dir_unix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//go:build !windows

package hub

import (
"os"
"strings"
"testing"
)

func TestCheckWritable_ReadOnlyDir(t *testing.T) {
if os.Getuid() == 0 {
t.Skip("skipping: chmod has no effect when running as root")
}
dir := t.TempDir()
if err := os.Chmod(dir, 0o444); err != nil {

Check failure on line 16 in backend/internal/hub/data_dir_unix_test.go

View workflow job for this annotation

GitHub Actions / backend

G302: Expect file permissions to be 0600 or less (gosec)
t.Fatalf("chmod: %v", err)
}
// Restore before t.TempDir cleanup so the directory can be removed.
// t.Cleanup runs in LIFO order, so this runs before the TempDir cleanup.
t.Cleanup(func() { os.Chmod(dir, 0o750) })

Check failure on line 21 in backend/internal/hub/data_dir_unix_test.go

View workflow job for this annotation

GitHub Actions / backend

Error return value of `os.Chmod` is not checked (errcheck)

err := checkWritable(dir)
if err == nil {
t.Fatal("expected error for read-only directory, got nil")
}
if !strings.Contains(err.Error(), "not writable") {
t.Errorf("error %q does not contain expected message", err)
}
}
5 changes: 5 additions & 0 deletions backend/internal/hub/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ func Run(cfg Config) error {
return err
}

if err := initDataDir(); err != nil {
Log.Error().Err(err).Msg("failed to initialize data directory")
return err
}

Comment thread
timokoessler marked this conversation as resolved.
dbLogger := Log.With().Str("component", "gorm").Logger()
err := db.Connect(dbLogger, cfg.LogLevel, cfg.Demo)
if err != nil {
Expand Down
18 changes: 7 additions & 11 deletions hub.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,12 @@ RUN npm run build

FROM bufbuild/buf:1.68 AS buf

FROM golang:1.26-alpine AS builder
FROM golang:1.26-trixie AS builder

ARG VERSION=dev
ARG COMMIT=none
ARG BUILD_DATE=unknown

RUN apk add --no-cache gcc musl-dev sqlite-dev

WORKDIR /src
COPY backend/go.mod backend/go.sum ./
RUN go mod download
Expand All @@ -33,19 +31,17 @@ RUN buf generate

RUN CGO_ENABLED=1 go build \
-ldflags "-s -w \
-X github.com/OrcaCD/orca-cd/internal/version.Version=${VERSION} \
-X github.com/OrcaCD/orca-cd/internal/version.Commit=${COMMIT} \
-X github.com/OrcaCD/orca-cd/internal/version.BuildDate=${BUILD_DATE}" \
-X github.com/OrcaCD/orca-cd/internal/version.Version=${VERSION} \
-X github.com/OrcaCD/orca-cd/internal/version.Commit=${COMMIT} \
-X github.com/OrcaCD/orca-cd/internal/version.BuildDate=${BUILD_DATE}" \
-o /bin/hub ./cmd/hub

FROM alpine:3.23
FROM gcr.io/distroless/base-nossl-debian13:nonroot

WORKDIR /app

RUN apk add --no-cache ca-certificates sqlite-libs

COPY --from=builder /bin/hub /app/hub
COPY --from=frontend-builder /app/frontend/dist ./frontend/dist
COPY --from=builder --chown=nonroot:nonroot /bin/hub /app/hub
COPY --from=frontend-builder --chown=nonroot:nonroot /app/frontend/dist ./frontend/dist

EXPOSE 8080

Expand Down
Loading