Skip to content

Commit cb7ca14

Browse files
authored
Merge pull request #16 from Baker/deepsec
Security hardening, etc
2 parents 6ae1c2d + ed2ef0d commit cb7ca14

14 files changed

Lines changed: 262 additions & 78 deletions

File tree

.github/workflows/ci.yml

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,41 +10,42 @@ concurrency:
1010
group: ${{ github.workflow }}-${{ github.ref }}
1111
cancel-in-progress: true
1212

13-
permissions: write-all
13+
permissions:
14+
contents: read
1415

1516
jobs:
1617
lint:
1718
name: 🧹 Lint
1819
runs-on: ubuntu-latest
1920
steps:
2021
- name: 📥 Checkout
21-
uses: actions/checkout@v4
22+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
2223

2324
- name: 🔧 Set up Go
24-
uses: actions/setup-go@v5
25+
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
2526
with:
2627
go-version-file: go.mod
2728
cache: true
2829

2930
- name: 🕵️ Install golangci-lint
30-
uses: golangci/golangci-lint-action@v9.2.0
31+
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
3132
with:
3233
args: --timeout=5m
3334

3435
- name: 🛡️ Run govulncheck
3536
run: |
36-
go install golang.org/x/vuln/cmd/govulncheck@latest
37-
govulncheck ./...
37+
GOTOOLCHAIN=go$(awk '/^go /{print $2; exit}' go.mod) go install golang.org/x/vuln/cmd/govulncheck@latest
38+
GOTOOLCHAIN=go$(awk '/^go /{print $2; exit}' go.mod) govulncheck ./...
3839
3940
deps:
4041
name: 📦 Dependencies
4142
runs-on: ubuntu-latest
4243
steps:
4344
- name: 📥 Checkout
44-
uses: actions/checkout@v4
45+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
4546

4647
- name: 🔧 Set up Go
47-
uses: actions/setup-go@v5
48+
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
4849
with:
4950
go-version-file: go.mod
5051
cache: true
@@ -64,10 +65,10 @@ jobs:
6465
runs-on: ubuntu-latest
6566
steps:
6667
- name: 📥 Checkout
67-
uses: actions/checkout@v4
68+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
6869

6970
- name: 🔧 Set up Go
70-
uses: actions/setup-go@v5
71+
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
7172
with:
7273
go-version-file: go.mod
7374
cache: true
@@ -96,10 +97,10 @@ jobs:
9697
go: ${{ (github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'release-run')) && fromJSON('["stable"]') || fromJSON('["stable", "oldstable"]') }}
9798
steps:
9899
- name: 📥 Checkout
99-
uses: actions/checkout@v4
100+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
100101

101102
- name: 🔧 Set up Go
102-
uses: actions/setup-go@v5
103+
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
103104
with:
104105
go-version: ${{ matrix.go }}
105106
cache: true
@@ -117,10 +118,10 @@ jobs:
117118
runs-on: ubuntu-latest
118119
steps:
119120
- name: 📥 Checkout
120-
uses: actions/checkout@v4
121+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
121122

122123
- name: 🔧 Set up Go
123-
uses: actions/setup-go@v5
124+
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
124125
with:
125126
go-version-file: go.mod
126127
cache: true
@@ -129,7 +130,7 @@ jobs:
129130
run: go test -covermode=atomic -coverpkg=./... -coverprofile=cover.out ./...
130131

131132
- name: ✅ Enforce coverage threshold
132-
uses: vladopajic/go-test-coverage@v2
133+
uses: vladopajic/go-test-coverage@a2cbf1fbcac20b3a5d09badb628331fa7bded52c # v2.14.0
133134
with:
134135
profile: cover.out
135136
config: ./.github/.testcoverage.yml
@@ -149,10 +150,10 @@ jobs:
149150
go: ${{ (github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'release-run')) && fromJSON('["stable"]') || fromJSON('["stable", "oldstable"]') }}
150151
steps:
151152
- name: 📥 Checkout
152-
uses: actions/checkout@v4
153+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
153154

154155
- name: 🔧 Set up Go
155-
uses: actions/setup-go@v5
156+
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
156157
with:
157158
go-version: ${{ matrix.go }}
158159
cache: true

.github/workflows/cleanup-deployments.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
runs-on: ubuntu-latest
3131
steps:
3232
- name: Checkout
33-
uses: actions/checkout@v4
33+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
3434

3535
- name: Cleanup GitHub deployments
3636
env:
@@ -44,6 +44,11 @@ jobs:
4444
run: |
4545
set -euo pipefail
4646
47+
if ! printf '%s' "${KEEP}" | grep -Eq '^[0-9]+$'; then
48+
echo "keep_latest must be a non-negative integer"
49+
exit 1
50+
fi
51+
4752
# Ensure jq is available
4853
if ! command -v jq >/dev/null 2>&1; then
4954
sudo apt-get update -y
@@ -85,7 +90,7 @@ jobs:
8590
echo "Found $total deployment(s)${ENV_FILTER:+ for environment '${ENV_FILTER}'}"
8691
8792
# Determine which to delete (skip the first KEEP entries)
88-
to_delete=$(echo "$deployments_sorted" | jq ".[${KEEP}:]")
93+
to_delete=$(echo "$deployments_sorted" | jq --argjson skip "$KEEP" '.[$skip:]')
8994
del_count=$(echo "$to_delete" | jq 'length')
9095
echo "Keeping ${KEEP}, candidate deletions: $del_count"
9196
if [ "$del_count" -eq 0 ]; then

.github/workflows/release.yml

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ jobs:
2222
goarch: 386
2323
steps:
2424
- name: Checkout
25-
uses: actions/checkout@v4
25+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
2626

2727
- name: Set up Go
28-
uses: actions/setup-go@v5
28+
uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0
2929
with:
3030
go-version-file: go.mod
3131
cache: true
@@ -42,7 +42,7 @@ jobs:
4242
go build -trimpath -ldflags "-s -w" -o "dist/${BIN_NAME}-${{ matrix.goos }}-${{ matrix.goarch }}" ./
4343
4444
- name: Upload artifact
45-
uses: actions/upload-artifact@v4
45+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
4646
with:
4747
name: inboundparse-${{ matrix.goos }}-${{ matrix.goarch }}
4848
path: dist/*
@@ -52,17 +52,20 @@ jobs:
5252
runs-on: ubuntu-latest
5353
steps:
5454
- name: Download artifacts
55-
uses: actions/download-artifact@v4
55+
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
5656
with:
5757
path: dist
5858

5959
- name: Assemble assets
6060
run: |
6161
mkdir -p assets
62-
find dist -type f -maxdepth 2 -print -exec bash -lc 'f="{}"; base=$(basename "$f"); cp "$f" "assets/$base"' \;
62+
find dist -type f -maxdepth 2 -print0 | while IFS= read -r -d '' f; do
63+
base=$(basename "$f")
64+
cp "$f" "assets/$base"
65+
done
6366
6467
- name: Create GitHub Release
65-
uses: softprops/action-gh-release@v2
68+
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
6669
with:
6770
files: assets/*
6871
env:

Dockerfile

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Build stage
2-
FROM golang:1.26.1-alpine AS builder
2+
FROM golang:1.26.2-alpine3.21 AS builder
33

44
WORKDIR /app
55

@@ -16,27 +16,30 @@ COPY . .
1616
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o inboundparse ./cmd/inboundparse
1717

1818
# Final stage
19-
FROM alpine:latest
19+
FROM alpine:3.21
2020

21-
# Install ca-certificates, openssl, and curl for HTTPS webhook calls and certificate management
22-
RUN apk --no-cache add ca-certificates openssl curl socat
21+
ARG ACME_SH_VERSION=3.0.7
2322

24-
# Install acme.sh
25-
RUN curl https://get.acme.sh | sh -s email=${ACME_SH_EMAIL:-dev@inboundparse.com}
23+
# Install ca-certificates, openssl, socat, git, bash; pin acme.sh via git tag (no curl|sh)
24+
RUN apk add --no-cache ca-certificates openssl socat git bash curl \
25+
&& git clone --depth 1 --branch "${ACME_SH_VERSION}" https://github.com/acmesh-official/acme.sh.git /opt/acme.sh \
26+
&& addgroup -S inboundparse \
27+
&& adduser -S -G inboundparse -h /home/inboundparse inboundparse \
28+
&& mkdir -p /cert /home/inboundparse \
29+
&& chown -R inboundparse:inboundparse /cert /home/inboundparse /opt/acme.sh \
30+
&& ln -sf /opt/acme.sh/acme.sh /usr/local/bin/acme.sh \
31+
&& rm -rf /opt/acme.sh/.git
2632

27-
WORKDIR /root/
33+
WORKDIR /home/inboundparse
2834

29-
# Copy the binary from builder stage
30-
COPY --from=builder /app/inboundparse .
35+
COPY --from=builder /app/inboundparse /usr/local/bin/inboundparse
36+
COPY start.sh /usr/local/bin/start.sh
3137

32-
# Copy scripts
33-
COPY start.sh ./start.sh
38+
RUN chmod +x /usr/local/bin/inboundparse /usr/local/bin/start.sh \
39+
&& chown inboundparse:inboundparse /usr/local/bin/inboundparse /usr/local/bin/start.sh
3440

35-
# Make scripts executable
36-
RUN chmod +x ./start.sh
37-
38-
# Create cert directory
39-
RUN mkdir -p /cert
41+
USER inboundparse
42+
ENV HOME=/home/inboundparse
4043

4144
# Environment variables for acme.sh and SMTP server
4245
ENV CF_Token=""
@@ -61,4 +64,4 @@ EXPOSE 587
6164
EXPOSE 9090
6265

6366
# Run the startup script
64-
ENTRYPOINT ["./start.sh"]
67+
ENTRYPOINT ["/usr/local/bin/start.sh"]

Makefile

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
BINARY_NAME=inboundparse
66
DOCKER_IMAGE=inboundparse:latest
77
GOBIN := $(shell go env GOPATH)/bin
8+
# govulncheck must load packages with the same Go version as go.mod; otherwise
9+
# the loader can fail (e.g. internal error: x/sys/unix "without types").
10+
GOTOOLCHAIN_GO := go$(shell awk '/^go /{print $$2; exit}' go.mod)
811

912
help:
1013
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "%-30s %s\n", $$1, $$2}'
@@ -68,9 +71,9 @@ lint: ## Lint (golangci-lint) + vuln scan (govulncheck)
6871
@echo "Running golangci-lint..."
6972
@command -v $(GOBIN)/golangci-lint >/dev/null 2>&1 || go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
7073
@$(GOBIN)/golangci-lint run --fix --timeout=5m
71-
@echo "Running govulncheck..."
72-
@command -v $(GOBIN)/govulncheck >/dev/null 2>&1 || go install golang.org/x/vuln/cmd/govulncheck@latest
73-
@$(GOBIN)/govulncheck ./...
74+
@echo "Running govulncheck ($(GOTOOLCHAIN_GO))..."
75+
@GOTOOLCHAIN=$(GOTOOLCHAIN_GO) go install golang.org/x/vuln/cmd/govulncheck@latest
76+
@GOTOOLCHAIN=$(GOTOOLCHAIN_GO) $(GOBIN)/govulncheck ./...
7477

7578
fl: format lint vet ## Format, lint, and vet the code
7679

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ A SMTP server that receives emails from any domain without authentication and fo
55
> [!NOTE]
66
> This is my first major Golang product, so I may not do things the **golang way**, but I am open to feedback. I have used this application privately for the last 6 months, and have received well over **100,000** of pieces of mail (mostly spam, but thats for another day).
77
8-
98
## Features
109

1110
### Core SMTP Functionality
@@ -17,27 +16,31 @@ A SMTP server that receives emails from any domain without authentication and fo
1716
- 📏 **Message Size Limits**: Configurable maximum message size (default: 10MB)
1817

1918
### Email Authentication (RFC Compliant)
19+
2020
- 🧐 **SPF Validation (RFC 7208)**: Validates Sender Policy Framework with HELO and envelope sender
2121
- 🖊️ **DKIM Validation (RFC 6376)**: Verifies DomainKeys Identified Mail signatures with multi-signature support
2222
- 🕵️‍♂️ **DMARC Validation (RFC 7489)**: Evaluates DMARC policy with hierarchical domain lookup and subdomain policy inheritance
2323
- 📝 **Comprehensive Results**: Detailed authentication results with domain, mechanism, and alignment data
2424
- 🔍 **Domain Hierarchy Tracking**: Tracks all attempted DMARC lookups with detailed per-domain results
2525

2626
### Observability & Monitoring
27+
2728
- 📋 **Structured Logging**: JSON-formatted logs with configurable levels
2829
- 📊 **Prometheus Metrics**: Comprehensive metrics collection and monitoring
2930
- 🛎️ **Sentry Integration**: Error tracking and performance monitoring
3031
- ❤️ **Health Checks**: Built-in health endpoint for load balancers
3132
- 📈 **Grafana Dashboards**: Pre-configured monitoring dashboards
3233

3334
### Deployment & Operations
35+
3436
- 🐳 **Docker Support**: Multi-stage Docker builds with Alpine Linux
3537
- 🧩 **Docker Compose**: Complete development stack with monitoring
3638
- 🚀 **Fly.io Ready**: Pre-configured for Fly.io deployment (more to come!)
3739
- 🔑 **Automatic Certificates**: Let's Encrypt integration with DNS validation
3840
- ⚙️ **Environment Configuration**: Flexible configuration via environment variables
3941

4042
### Webhook Integration
43+
4144
- 🔐 **HTTP Basic Auth**: Secure webhook authentication
4245
- 📨 **Rich Payload**: Complete email data including headers and attachments
4346
- 🔁 **Reliability Features**: Automatic retry with exponential backoff, rate limiting, and circuit breaker
@@ -102,6 +105,7 @@ make dev
102105
```
103106

104107
With the dev stack running, you'll have:
108+
105109
- **InboundParse SMTP server** (`:25`, `:587`, `:9090`)
106110
- **Prometheus** for metrics (`:9091`)
107111
- **Grafana** dashboards (`:3000`, login `admin`/`admin`)
@@ -146,9 +150,7 @@ Usage of ./inboundparse:
146150
-sentry-release string Sentry release version (optional)
147151
```
148152

149-
150-
151-
### 🔒 Environment Variables
153+
### 🔒 Environment Variables
152154

153155
You can configure InboundParse using environment variables or command line flags:
154156

@@ -265,12 +267,12 @@ The service sends a JSON payload to your webhook with comprehensive email data:
265267
}
266268
```
267269

268-
269270
## 🤝 Contributing
270271

271272
Contributions of all kinds are welcome! See [CONTRIBUTE.md](./docs/CONTRIBUTE.md) for guidelines, and don’t hesitate to open issues, fork, or create PRs.
272273

273274
---
274275

275276
> **Need help or want to suggest new features?**
276-
> Open an issue or start a discussion on GitHub!
277+
> Open an issue or start a discussion on GitHub!
278+

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module inboundparse
22

3-
go 1.26.1
3+
go 1.26.2
44

55
require (
66
github.com/emersion/go-msgauth v0.7.0

0 commit comments

Comments
 (0)