Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
45 changes: 45 additions & 0 deletions contrib/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Build stage
FROM golang:1.23-bookworm AS build
Copy link
Owner

Choose a reason for hiding this comment

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

Please don't use an LLM (w/o Internet access) to pin "latest" versions.

Especially in this case, these versions are dangerously outdated, as Go only supports the two last major versions, being 1.24 and 1.25 at the time of writing. Since Go 1.23's EOL, there were multiple security fixes in 1.24 and 1.25, many of those might also affect 1.23.

Furthermore, Debian stable is trixie now.


WORKDIR /src
COPY . .

RUN go mod download
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /gosh .

# Runtime stage
FROM gcr.io/distroless/static-debian12:latest

# gosh needs root at startup to chroot() and setuid()/setgid()
# It drops privileges itself to the configured user/group

# Copy the binary
COPY --from=build /gosh /gosh

# Create passwd/group files for user.Lookup() to work
# Using UID/GID 65532 which is the "nonroot" user in distroless
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

# We need to add passwd and group files for gosh's user.Lookup()
# distroless has no shell, so we create these in the build stage
FROM golang:1.23-bookworm AS passwd-builder
RUN echo 'root:x:0:0:root:/root:/sbin/nologin' > /tmp/passwd && \
echo 'gosh:x:65532:65532:gosh:/nonexistent:/sbin/nologin' >> /tmp/passwd
RUN echo 'root:x:0:' > /tmp/group && \
echo 'gosh:x:65532:' >> /tmp/group

# Final stage
FROM gcr.io/distroless/static-debian12:latest

COPY --from=build /gosh /gosh
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=passwd-builder /tmp/passwd /etc/passwd
COPY --from=passwd-builder /tmp/group /etc/group

# Create store directory (will be owned by root initially, gosh chowns it)
WORKDIR /data

EXPOSE 8080

ENTRYPOINT ["/gosh"]
CMD ["-config", "/config/gosh.yml"]
Copy link
Owner

Choose a reason for hiding this comment

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

The /config directory is not mentioned as a VOLUME in the Dockerfile, but only referenced through the docker-compose.yml file.

53 changes: 53 additions & 0 deletions contrib/docker/config/gosh.yml
Copy link
Owner

Choose a reason for hiding this comment

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

I am not a huge fan of having duplicated configuration files. Changes to the main gosh.yml will be forgotten and not being reapplied here.

Thus, how about copying it into the container, do the necessary changes via sed or yq within the container build stage and still allow users to overlay/overwrite it?

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
# Container configuration for gosh
#
# Mount structure:
# /config/gosh.yml - this file
# /config/index.html - custom index template (optional)
# /config/favicon.ico - favicon (optional)
# /config/custom.css - custom styles (optional)
# /data/ - persistent storage

user: "gosh"
group: "gosh"

store:
path: "/data/store"

id_generator:
type: "random"
length: 8

webserver:
listen:
protocol: "tcp"
bound: ":8080"

protocol: "http"

url_prefix: ""

# Custom index.html template
custom_index: "/config/index.html"

# Optional: static files served by gosh
# Uncomment and adjust as needed
# static_files:
# "/favicon.ico":
# path: "/config/favicon.ico"
# mime: "image/vnd.microsoft.icon"
# "/custom.css":
# path: "/config/custom.css"
# mime: "text/css"

item_config:
max_size: "10MiB"
max_lifetime: "24h"

mime_drop:
- "application/vnd.microsoft.portable-executable"
- "application/x-msdownload"
mime_map:
"text/html": "text/plain"

contact: "admin@example.com"
146 changes: 146 additions & 0 deletions contrib/docker/config/index.html
Copy link
Owner

Choose a reason for hiding this comment

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

Quite same as https://github.com/oxzi/gosh/pull/73/changes#r2738584441. Is there even a change compared to ./index.html?

Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<!DOCTYPE html>
<html>
<head>
<title>gosh! Go Share</title>

<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<style>
* {
font-family: monospace;
}

body {
margin: 0 auto;
padding: 1rem;
width: 50%;
}

h1 {
padding-top: 3rem;
}

h2 {
padding-top: 2rem;
}

h3 {
padding-top: 1rem;
}

pre {
background-color: #eee;
padding: 0.5rem;
}

form {
padding: 0.5rem;
position: relative;
margin: auto;
background-color: #eee;
}

#grid {
display: grid;
grid-gap: 1rem;
grid-template-columns: 1fr 1fr;
grid-template-rows: repeat(3, 3rem);
margin-bottom: 1rem;
}

#grid > * {
margin: auto 0;
}

#grid input[type="checkbox"] {
margin-right: auto;
}

button {
width: 100%;
}
</style>
</head>

<body>
<h1># gosh! Go Share</h1>
<p>
Upload your files to this server and share them with your friends or, if
non-existent, shady people from the Internet.
</p>
<p>
Your file will expire after {{.Expires}} or earlier, if explicitly
specified. Optionally, the file can be deleted directly after the first
retrieval. For each upload, a deletion URL will also be generated which
can be used to delete the file before expiration. In addition, the
maximum file size is {{.Size}}.
</p>
<p>
This is no place to share questionable or illegal data. Please use another
service or stop it completely. Get some help.
</p>
<p>
The gosh software can be obtained from
<a href="https://github.com/oxzi/gosh">https://github.com/oxzi/gosh</a>
</p>

<h2>## Posting</h2>

<h3>### curl</h3>

HTTP POST your file:

<pre>$ curl -F 'file=@foo.png' {{.Proto}}://{{.Hostname}}{{.Prefix}}/</pre>

Burn after reading:

<pre>$ curl -F 'file=@foo.png' -F 'burn=1' {{.Proto}}://{{.Hostname}}{{.Prefix}}/</pre>

Set a custom expiry date, e.g., one minute:

<pre>$ curl -F 'file=@foo.png' -F 'time=1m' {{.Proto}}://{{.Hostname}}{{.Prefix}}/</pre>

Or all together:

<pre>$ curl -F 'file=@foo.png' -F 'time=1m' -F 'burn=1' {{.Proto}}://{{.Hostname}}{{.Prefix}}/</pre>

Print only URL as response:

<pre>$ curl -F 'file=@foo.png' -F {{.Proto}}://{{.Hostname}}{{.Prefix}}/?onlyURL</pre>

<h3>### form</h3>

<form
action="{{.Proto}}://{{.Hostname}}{{.Prefix}}/"
method="POST"
enctype="multipart/form-data">
<div id="grid">
<label for="file">Your file:</label>
<input type="file" name="file" />
<label for="burn">Burn after reading:</label>
<input type="checkbox" name="burn" value="1" />
<label for="time">Optionally, set a custom expiry date:</label>
<input
type="text"
name="time"
pattern="{{.DurationPattern}}"
title="A duration string is sequence of decimal numbers, each with a unit suffix. Valid time units in order are 'y', 'mo', 'w', 'd', 'h', 'm', 's'"
/>
</div>
<button>Upload</button>
</form>

<h2>## Privacy</h2>

This software stores the IP address for each upload. This information is
stored as long as the file is available. A normal download is logged without
user information.

<h2>## Abuse</h2>

If, for whatever reason, you would like to have a file removed prematurely,
please write an e-mail to
<a href="mailto:{{.EMail}}">&lt;{{.EMail}}&gt;</a>. Please allow me a
certain amount of time to react and work on your request.
</body>
</html>
17 changes: 17 additions & 0 deletions contrib/docker/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
services:
gosh:
build:
context: ../..
dockerfile: contrib/docker/Dockerfile
image: gosh:distroless
ports:
- "8080:8080"
volumes:
# Config directory with gosh.yml and optional static files
- ./config:/config:ro
# Persistent data storage
- gosh-data:/data
restart: unless-stopped

volumes:
gosh-data:
54 changes: 54 additions & 0 deletions contrib/docker/gosh.container.yml
Copy link
Owner

Choose a reason for hiding this comment

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

What is this file for? Is it even being referenced?

Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
# Container configuration for gosh
#
# Mount structure:
# /config/gosh.yml - this file
# /config/index.html - custom index template (optional)
# /config/favicon.ico - favicon (optional)
# /config/custom.css - custom styles (optional)
# /data/ - persistent storage

user: "gosh"
group: "gosh"

store:
path: "/data/store"

id_generator:
type: "random"
length: 8

webserver:
listen:
protocol: "tcp"
bound: ":8080"

protocol: "http"

url_prefix: ""

# Optional: custom index.html template
# Uncomment if you provide /config/index.html
# custom_index: "/config/index.html"

# Optional: static files served by gosh
# Uncomment and adjust as needed
# static_files:
# "/favicon.ico":
# path: "/config/favicon.ico"
# mime: "image/vnd.microsoft.icon"
# "/custom.css":
# path: "/config/custom.css"
# mime: "text/css"

item_config:
max_size: "10MiB"
max_lifetime: "24h"

mime_drop:
- "application/vnd.microsoft.portable-executable"
- "application/x-msdownload"
mime_map:
"text/html": "text/plain"

contact: "admin@example.com"
48 changes: 27 additions & 21 deletions gosh_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"log/slog"
"os"
"os/signal"
"runtime"

"golang.org/x/sys/unix"
)
Expand Down Expand Up @@ -37,7 +38,7 @@ func ensureStoreDir(path, username, groupname string) error {
}

func mainStore(conf Config) {
slog.Debug("Starting store child", slog.Any("config", conf.Store))
slog.Debug("Starting store child", slog.Any("config", conf.Store), slog.String("arch", runtime.GOARCH))

var idGenerator func() (string, error)
switch conf.Store.IdGenerator.Type {
Expand Down Expand Up @@ -70,26 +71,31 @@ func mainStore(conf Config) {
os.Exit(1)
}

err = restrict(restrict_linux_seccomp,
[]string{
"@system-service",
"~@chown",
"~@clock",
"~@cpu-emulation",
"~@debug",
"~@keyring",
"~@memlock",
"~@module",
"~@mount",
"~@privileged",
"~@reboot",
"~@sandbox",
"~@setuid",
"~@swap",
/* @process */ "~execve", "~execveat", "~fork", "~kill",
/* @network-io */ "~bind", "~connect", "~listen",
"fstatat", // for aarch64, same as newfstatat
})
seccompFilter := []string{
"@system-service",
"~@chown",
"~@clock",
"~@cpu-emulation",
"~@debug",
"~@keyring",
"~@memlock",
"~@module",
"~@mount",
"~@privileged",
"~@reboot",
"~@sandbox",
"~@setuid",
"~@swap",
/* @process */ "~execve", "~execveat", "~fork", "~kill",
/* @network-io */ "~bind", "~connect", "~listen",
}
// fstatat syscall name differs per architecture
if runtime.GOARCH == "arm64" {
seccompFilter = append(seccompFilter, "fstatat")
Comment on lines +92 to +94
Copy link
Owner

Choose a reason for hiding this comment

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

Meh. Seems like I don't know my own library. I assumed that syscallset-go ignore unknown system calls, but it only does so when passing a syscall set.

I have changed this with the just released - and already bumped here - version 0.1.7.

Thus, no further changes in the Go code are required from this PR. Just rebase :)

} else {
seccompFilter = append(seccompFilter, "newfstatat")
Comment on lines +95 to +96
Copy link
Owner

Choose a reason for hiding this comment

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

This is not required, since newfstatat is already part of @system-services. Already described when introducing this change in c55f4fa.

https://github.com/oxzi/syscallset-go/blob/v0.1.7/syscalls.go#L1133-L1333

Furthermore, this binary logic assumes that newfstatat is available on every architecture that is not arm64. Unsure about that.

}
err = restrict(restrict_linux_seccomp, seccompFilter)
if err != nil {
slog.Error("Failed to apply seccomp-bpf filter", slog.Any("error", err))
os.Exit(1)
Expand Down