Skip to content

Commit 5642814

Browse files
committed
Add GitHub Actions workflow and security improvements
- Add Docker publish workflow (triggers on version tags) - Switch to Distroless base image for minimal attack surface - Add --healthcheck flag for container health checks - Inject version from git tag via ldflags
1 parent 4f12bdd commit 5642814

3 files changed

Lines changed: 103 additions & 30 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Build and Publish Docker Image
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
8+
env:
9+
REGISTRY: ghcr.io
10+
IMAGE_NAME: ${{ github.repository }}
11+
12+
jobs:
13+
build-and-push:
14+
runs-on: ubuntu-latest
15+
16+
permissions:
17+
contents: read
18+
packages: write
19+
20+
steps:
21+
- name: Checkout repository
22+
uses: actions/checkout@v4
23+
24+
- name: Set up Docker Buildx
25+
uses: docker/setup-buildx-action@v3
26+
27+
- name: Log in to Container Registry
28+
uses: docker/login-action@v3
29+
with:
30+
registry: ${{ env.REGISTRY }}
31+
username: ${{ github.actor }}
32+
password: ${{ secrets.GITHUB_TOKEN }}
33+
34+
- name: Extract version from tag
35+
id: version
36+
run: |
37+
# Remove 'v' prefix from tag (v1.0.11 -> 1.0.11)
38+
VERSION=${GITHUB_REF_NAME#v}
39+
echo "version=$VERSION" >> $GITHUB_OUTPUT
40+
echo "Version: $VERSION"
41+
42+
- name: Extract metadata for Docker
43+
id: meta
44+
uses: docker/metadata-action@v5
45+
with:
46+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
47+
tags: |
48+
type=raw,value=latest
49+
type=raw,value=${{ steps.version.outputs.version }}
50+
51+
- name: Build and push Docker image
52+
uses: docker/build-push-action@v5
53+
with:
54+
context: .
55+
push: true
56+
tags: ${{ steps.meta.outputs.tags }}
57+
labels: ${{ steps.meta.outputs.labels }}
58+
build-args: |
59+
VERSION=${{ steps.version.outputs.version }}
60+
cache-from: type=gha
61+
cache-to: type=gha,mode=max

Dockerfile

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
# Build stage
2-
FROM golang:1.25-alpine AS builder
2+
FROM golang:1.23-alpine AS builder
33

44
WORKDIR /build
55

6+
# Version is passed as build argument
7+
ARG VERSION=dev
8+
69
# Install build dependencies
7-
RUN apk add --no-cache git ca-certificates
10+
RUN apk add --no-cache git ca-certificates tzdata
811

912
# Copy go mod files first for better caching
1013
COPY go.mod go.sum ./
@@ -13,52 +16,36 @@ RUN go mod download
1316
# Copy source code
1417
COPY . .
1518

16-
# Build the binary
17-
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o netflowmap ./cmd/netflowmap
19+
# Build the binary (static, stripped, with version injected)
20+
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w -X main.version=${VERSION}" -o netflowmap ./cmd/netflowmap
1821

19-
# Runtime stage
20-
FROM alpine:3.19
22+
# Runtime stage - Distroless for minimal attack surface
23+
FROM gcr.io/distroless/static-debian12:nonroot
2124

2225
WORKDIR /app
2326

24-
# Install runtime dependencies
25-
RUN apk add --no-cache ca-certificates tzdata
26-
27-
# Create non-root user
28-
RUN adduser -D -u 1000 netflowmap
29-
3027
# Copy binary from builder
3128
COPY --from=builder /build/netflowmap /app/netflowmap
3229

3330
# Copy web assets
3431
COPY --from=builder /build/web /app/web
3532

36-
# Copy example configs
37-
COPY --from=builder /build/configs /app/configs
38-
39-
# Create data directory for GeoIP database
40-
RUN mkdir -p /app/data && chown -R netflowmap:netflowmap /app
41-
42-
# Switch to non-root user
43-
USER netflowmap
33+
# Copy timezone data for proper time handling
34+
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
4435

4536
# Expose ports
4637
# 8080 - HTTP Web UI
4738
# 2055 - NetFlow UDP
4839
EXPOSE 8080
4940
EXPOSE 2055/udp
5041

51-
# Health check
42+
# Health check using built-in healthcheck command
5243
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
53-
CMD wget -q --spider http://localhost:8080/api/health || exit 1
44+
CMD ["/app/netflowmap", "--healthcheck"]
45+
46+
# Run as non-root (distroless:nonroot runs as uid 65532)
47+
USER nonroot:nonroot
5448

5549
# Run
5650
ENTRYPOINT ["/app/netflowmap"]
5751
CMD ["--config", "/app/config.yml"]
58-
59-
60-
61-
62-
63-
64-

cmd/netflowmap/main.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ import (
2626
)
2727

2828
var (
29-
version = "1.0.11"
29+
// version is set via ldflags at build time: -ldflags="-X main.version=1.0.12"
30+
version = "dev"
3031
configPath string
3132
)
3233

@@ -35,13 +36,20 @@ func main() {
3536
flag.StringVar(&configPath, "config", "config.yml", "Path to configuration file")
3637
showVersion := flag.Bool("version", false, "Show version and exit")
3738
hashPassword := flag.Bool("hash-password", false, "Generate a password hash for users.yml")
39+
healthCheck := flag.Bool("healthcheck", false, "Run health check and exit")
3840
flag.Parse()
3941

4042
if *showVersion {
4143
fmt.Printf("NetFlowMap v%s\n", version)
4244
os.Exit(0)
4345
}
4446

47+
// Handle healthcheck command (for Docker HEALTHCHECK)
48+
if *healthCheck {
49+
runHealthCheck()
50+
return
51+
}
52+
4553
// Handle hash-password command
4654
if *hashPassword {
4755
runHashPassword()
@@ -363,6 +371,23 @@ func logStats(store *flowstore.Store, fgManager *fortigate.Manager, collector *n
363371
)
364372
}
365373

374+
// runHealthCheck performs a health check against the local server.
375+
func runHealthCheck() {
376+
resp, err := http.Get("http://localhost:8080/api/health")
377+
if err != nil {
378+
fmt.Fprintf(os.Stderr, "Health check failed: %v\n", err)
379+
os.Exit(1)
380+
}
381+
defer resp.Body.Close()
382+
383+
if resp.StatusCode != http.StatusOK {
384+
fmt.Fprintf(os.Stderr, "Health check failed: status %d\n", resp.StatusCode)
385+
os.Exit(1)
386+
}
387+
388+
os.Exit(0)
389+
}
390+
366391
// runHashPassword interactively generates a password hash.
367392
func runHashPassword() {
368393
fmt.Println("Password Hash Generator")

0 commit comments

Comments
 (0)