tests: minioClient web proxy (#13340)

follow-up to #13306

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests for Go changes

- I added test coverage for Go changes...
  - [x] in their respective `*_test.go` for unit tests.
  - [ ] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I ran...
  - [x] `make pr-go` before pushing

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [ ] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change.
- [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change.

*The decision if the pull request will be shown in the release notes is up to the mergers / release team.*

The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/13340
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
This commit is contained in:
Enrique Sanchez Cardoso 2026-07-07 21:46:35 +02:00 committed by Mathieu Fenniak
commit 7dd5bb8527

View file

@ -5,9 +5,14 @@ package storage
import (
"context"
"fmt"
"io"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"sync/atomic"
"testing"
"time"
@ -257,3 +262,103 @@ func TestNewMinioStorageInitializationTimeout(t *testing.T) {
require.ErrorIs(t, err, context.DeadlineExceeded, "err must be a context deadline exceeded error, but was %v", err)
assert.Nil(t, storage)
}
// newProxyServer starts an HTTP proxy server that implements CONNECT tunneling.
// It sets proxied to true whenever any request is received.
func newProxyServer(t *testing.T, proxied *atomic.Bool) *httptest.Server {
t.Helper()
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
proxied.Store(true)
if r.Method != http.MethodConnect {
http.Error(w, "only CONNECT is supported", http.StatusMethodNotAllowed)
return
}
targetConn, err := net.Dial("tcp", r.Host)
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
defer targetConn.Close()
clientConn, _, err := w.(http.Hijacker).Hijack()
if err != nil {
return
}
defer clientConn.Close()
_, _ = clientConn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n"))
go io.Copy(targetConn, clientConn)
io.Copy(clientConn, targetConn)
}))
}
func TestMinioStorageProxy(t *testing.T) {
// Start a fake TLS S3-compatible server.
// `UseSSL: true` triggers CONNECT-style proxying.
// `InsecureSkipVerify: true` accepts the self-signed cert.
// `BucketLookup: path` forces path-style URLs so the fake server stays simple.
s3Server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
w.Header().Set("Content-Type", "application/xml")
fmt.Fprint(w, `<?xml version="1.0" encoding="UTF-8"?><VersioningConfiguration xmlns="http://test/"></VersioningConfiguration>`)
case http.MethodHead:
w.WriteHeader(http.StatusOK)
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer s3Server.Close()
minioStorageCfg := func() *setting.Storage {
return &setting.Storage{
MinioConfig: setting.MinioStorageConfig{
Endpoint: s3Server.Listener.Addr().String(),
AccessKeyID: "123456",
SecretAccessKey: "12345678",
Bucket: "bucket",
Location: "us-east-1",
UseSSL: true,
InsecureSkipVerify: true,
BucketLookup: "path",
},
}
}
t.Run("ProxyIsUsed", func(t *testing.T) {
var proxied atomic.Bool
proxyServer := newProxyServer(t, &proxied)
defer proxyServer.Close()
proxyURL, err := url.Parse(proxyServer.URL)
require.NoError(t, err)
defer test.MockVariableValue(&setting.Proxy.Enabled, true)()
defer test.MockVariableValue(&setting.Proxy.ProxyURL, proxyServer.URL)()
defer test.MockVariableValue(&setting.Proxy.ProxyURLFixed, proxyURL)()
defer test.MockVariableValue(&setting.Proxy.ProxyHosts, []string{"*"})()
storage, err := NewMinioStorage(t.Context(), minioStorageCfg())
require.NoError(t, err)
assert.NotNil(t, storage)
assert.True(t, proxied.Load(), "expected minio client to connect through the proxy")
})
t.Run("ProxyIsNotUsed", func(t *testing.T) {
var proxied atomic.Bool
proxyServer := newProxyServer(t, &proxied)
defer proxyServer.Close()
proxyURL, err := url.Parse(proxyServer.URL)
require.NoError(t, err)
// Proxy is fully configured but disabled; minioClient should connect directly to s3Server.
defer test.MockVariableValue(&setting.Proxy.Enabled, false)()
defer test.MockVariableValue(&setting.Proxy.ProxyURL, proxyServer.URL)()
defer test.MockVariableValue(&setting.Proxy.ProxyURLFixed, proxyURL)()
defer test.MockVariableValue(&setting.Proxy.ProxyHosts, []string{"*"})()
storage, err := NewMinioStorage(t.Context(), minioStorageCfg())
require.NoError(t, err)
assert.NotNil(t, storage)
assert.False(t, proxied.Load(), "expected minio client to bypass the proxy when proxy is disabled")
})
}