diff --git a/Dockerfile b/Dockerfile index bee6436..59c3131 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM golang:1.26-alpine3.23 AS build -ARG BUILD_VERSION=1.5.1-dev +ARG BUILD_VERSION=1.6.0-dev ARG BUILD_REVISION=development ARG BUILD_TIME=unknown WORKDIR /src diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index baba740..88a0ce4 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,6 +1,6 @@ FROM cgr.dev/chainguard/go:latest-dev AS build -ARG BUILD_VERSION=1.5.1-dev +ARG BUILD_VERSION=1.6.0-dev ARG BUILD_REVISION=development ARG BUILD_TIME=unknown USER root diff --git a/VERSION b/VERSION index 26ca594..dc1e644 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.1 +1.6.0 diff --git a/internal/artifact/s3.go b/internal/artifact/s3.go index 68d42d3..9bc4040 100644 --- a/internal/artifact/s3.go +++ b/internal/artifact/s3.go @@ -13,6 +13,8 @@ import ( "github.com/minio/minio-go/v7/pkg/credentials" ) +const internalRedirectHeader = "X-TypeType-Artifact-Proxy" + type S3Config struct { Endpoint string PublicEndpoint string @@ -27,9 +29,10 @@ type S3Config struct { } type S3Store struct { - client *minio.Client - presign *minio.Client - cfg S3Config + client *minio.Client + presign *minio.Client + proxyRedirect bool + cfg S3Config } func (s *S3Store) Name() string { return "s3" } @@ -67,7 +70,9 @@ func NewS3Store(cfg S3Config) (*S3Store, error) { if cfg.URLTTL <= 0 { cfg.URLTTL = 15 * time.Minute } - return &S3Store{client: client, presign: presign, cfg: cfg}, nil + proxyRedirect := cfg.PublicEndpoint == "" || + (cfg.PublicEndpoint == cfg.Endpoint && cfg.PublicUseSSL == cfg.UseSSL) + return &S3Store{client: client, presign: presign, proxyRedirect: proxyRedirect, cfg: cfg}, nil } func (s *S3Store) Save(ctx context.Context, localPath string, objectKey string) (Saved, error) { @@ -100,6 +105,9 @@ func (s *S3Store) ServeHTTP(w http.ResponseWriter, r *http.Request, saved Saved, if err != nil { return err } + if s.proxyRedirect { + w.Header().Set(internalRedirectHeader, "1") + } http.Redirect(w, r, url.String(), http.StatusFound) return nil } diff --git a/internal/artifact/s3_test.go b/internal/artifact/s3_test.go new file mode 100644 index 0000000..5575a74 --- /dev/null +++ b/internal/artifact/s3_test.go @@ -0,0 +1,55 @@ +package artifact + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestS3StoreMarksInternalRedirect(t *testing.T) { + store := newTestS3Store(t, "typetype-garage:3900") + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/artifact", nil) + + if err := store.ServeHTTP(response, request, Saved{Location: "artifact.mp4"}, "video.mp4"); err != nil { + t.Fatal(err) + } + + if response.Code != http.StatusFound { + t.Fatalf("status = %d", response.Code) + } + if response.Header().Get(internalRedirectHeader) != "1" { + t.Fatalf("internal redirect header = %q", response.Header().Get(internalRedirectHeader)) + } +} + +func TestS3StoreLeavesPublicRedirectUnmarked(t *testing.T) { + store := newTestS3Store(t, "downloads.example.com") + response := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/artifact", nil) + + if err := store.ServeHTTP(response, request, Saved{Location: "artifact.mp4"}, "video.mp4"); err != nil { + t.Fatal(err) + } + + if response.Header().Get(internalRedirectHeader) != "" { + t.Fatalf("public redirect was marked internal") + } +} + +func newTestS3Store(t *testing.T, publicEndpoint string) *S3Store { + t.Helper() + store, err := NewS3Store(S3Config{ + Endpoint: "typetype-garage:3900", + PublicEndpoint: publicEndpoint, + Region: "garage", + Bucket: "downloads", + AccessKey: "key", + SecretKey: "secret", + PathStyle: true, + }) + if err != nil { + t.Fatal(err) + } + return store +} diff --git a/internal/storage/monitor_test.go b/internal/storage/monitor_test.go index cd4c8df..dbdc7d6 100644 --- a/internal/storage/monitor_test.go +++ b/internal/storage/monitor_test.go @@ -61,7 +61,7 @@ func TestMonitorTracksAndReleasesReservations(t *testing.T) { if capacity.ReservedBytes != reserved || !capacity.Available { t.Fatalf("capacity = %#v", capacity) } - if _, err := monitor.Reserve("second", 1); !errors.Is(err, ErrInsufficientStorage) { + if _, err := monitor.Reserve("second", capacity.TotalBytes); !errors.Is(err, ErrInsufficientStorage) { t.Fatalf("reserve error = %v", err) } release()