Default a scheme-less s3.* endpoint to TLS #178

Merged
clawbot merged 1 commits from issue-158-s3-use-ssl-default into next 2026-09-22 11:11:34 +02:00
4 changed files with 69 additions and 5 deletions
+1 -1
View File
@@ -192,8 +192,8 @@ storage_url: ""
# access_key_id: YOUR_ACCESS_KEY # access_key_id: YOUR_ACCESS_KEY
# secret_access_key: YOUR_SECRET_KEY # secret_access_key: YOUR_SECRET_KEY
# # region: us-east-1 # Default: us-east-1 # # region: us-east-1 # Default: us-east-1
# # use_ssl: true # Default: true
# # part_size: 5MB # Multipart upload part size. Default: 5MB # # part_size: 5MB # Multipart upload part size. Default: 5MB
# # For the s3:// form, disable TLS with ?ssl=false in the URL, not use_ssl.
# ─── OPTIONAL ──────────────────────────────────────────────────────────────── # ─── OPTIONAL ────────────────────────────────────────────────────────────────
+4 -2
View File
@@ -162,8 +162,10 @@ type S3Config struct {
AccessKeyID string `yaml:"access_key_id"` AccessKeyID string `yaml:"access_key_id"`
SecretAccessKey string `yaml:"secret_access_key"` SecretAccessKey string `yaml:"secret_access_key"`
Region string `yaml:"region"` Region string `yaml:"region"`
UseSSL bool `yaml:"use_ssl"` // UseSSL selects HTTPS for a scheme-less endpoint. Omitted (nil) means
PartSize Size `yaml:"part_size"` // the default, TLS; set it to false only to force plain HTTP.
UseSSL *bool `yaml:"use_ssl"`
PartSize Size `yaml:"part_size"`
} }
// Path wraps the config file path for fx dependency injection. // Path wraps the config file path for fx dependency injection.
+3 -2
View File
@@ -111,10 +111,11 @@ func storerFromParsedS3URL(parsed *URL, cfg *config.Config) (Storer, error) {
func storerFromLegacyS3Config(cfg *config.Config) (Storer, error) { func storerFromLegacyS3Config(cfg *config.Config) (Storer, error) {
endpoint := cfg.S3.Endpoint endpoint := cfg.S3.Endpoint
// Ensure protocol is present // Ensure protocol is present. Absent an explicit use_ssl, default to TLS;
// plain HTTP only when use_ssl is written as false.
if !strings.HasPrefix(endpoint, "http://") && if !strings.HasPrefix(endpoint, "http://") &&
!strings.HasPrefix(endpoint, "https://") { !strings.HasPrefix(endpoint, "https://") {
if cfg.S3.UseSSL { if cfg.S3.UseSSL == nil || *cfg.S3.UseSSL {
endpoint = "https://" + endpoint endpoint = "https://" + endpoint
} else { } else {
endpoint = "http://" + endpoint endpoint = "http://" + endpoint
+61
View File
@@ -0,0 +1,61 @@
package storage_test
import (
"strings"
"testing"
"sneak.berlin/go/vaultik/internal/config"
"sneak.berlin/go/vaultik/internal/storage"
)
// legacyS3Config returns a minimal s3.* (no storage_url) configuration with a
// scheme-less endpoint. useSSL mirrors the config file: nil means the key is
// omitted, a pointer means it was written explicitly.
func legacyS3Config(useSSL *bool) *config.Config {
return &config.Config{
S3: config.S3Config{
Endpoint: "s3.example.com",
Bucket: "bucket",
AccessKeyID: "key",
SecretAccessKey: "secret",
Region: "us-east-1",
UseSSL: useSSL,
},
}
}
// endpointScheme builds the storer from cfg and returns the scheme its
// resolved endpoint carries (Info().Location is "endpoint/bucket").
func endpointScheme(t *testing.T, cfg *config.Config) string {
t.Helper()
storer, err := storage.NewStorer(cfg)
if err != nil {
t.Fatalf("NewStorer: %v", err)
}
location := storer.Info().Location
switch {
case strings.HasPrefix(location, "https://"):
return "https"
case strings.HasPrefix(location, "http://"):
return "http"
default:
t.Fatalf("endpoint has no http(s) scheme: %q", location)
return ""
}
}
func TestLegacyS3SchemelessEndpointDefaultsToTLS(t *testing.T) {
t.Parallel()
if got := endpointScheme(t, legacyS3Config(nil)); got != "https" {
t.Errorf("use_ssl omitted: got %q scheme, want https", got)
}
no := false
if got := endpointScheme(t, legacyS3Config(&no)); got != "http" {
t.Errorf("use_ssl: false: got %q scheme, want http", got)
}
}