mirror of
https://github.com/pgsty/minio.git
synced 2026-08-09 15:53:28 +03:00
feat(server): present Silo identity and close the inherited upstream services
Two coupled changes that must land together, because the same files carry both: the server now identifies itself as Silo, and every path that would have called home to a MinIO-operated service is closed. Product identity - build-constants.go: store name, UA name and startup banner become Silo. The Go identifiers (MinioStoreName, MinioBannerName, ...) keep their names on purpose - renaming exported symbols would churn the compatibility surface for a cosmetic gain, and the rebrand guard freezes that surface. - main.go, server-startup-msg.go, ftp-server.go and the user-visible log, help and error strings across cmd/ and internal/ switch to Silo. Original MinIO copyright, LICENSE, NOTICE and CREDITS are untouched; --version now prints the upstream copyright, the pgsty modification notice, and the trademark policy's approved "based on MinIO technology" attribution. - api-headers.go: the HTTP Server header becomes "Silo". This is the one externally observable identity change, so TestCommonHeadersUseSiloProductName pins it - probes that sniff for "MinIO" must move to capability detection. - Prometheus metric HELP strings keep their MinIO wording. They are part of the metrics contract the guard protects, not product copy. Configuration directory - config-dir.go: new installs use ~/.silo. If only ~/.minio exists it is still read, with a one-time notice and no files moved. If both exist ~/.silo wins and an ambiguity warning is emitted; an explicit --config-dir always wins. Covered by TestSelectDefaultConfigDir. The internal .minio.sys layout is never renamed - this rule applies to the user config directory only. Upstream service lockdown - globalInplaceUpdateDisabled is now true at initialization rather than being set from MINIO_UPDATE. common-main.go still parses MINIO_UPDATE so upgrading nodes do not fail on an unknown key, but warns that the value is ignored; there is no way to re-enable the updater. TestInplaceUpdateCannotBeEnabled guards that. Without this, an admin with mc could have overwritten /usr/bin/silo with an upstream MinIO binary. - verifyBinary and commitBinary refuse early; the ServerUpdate v1/v2 admin routes and the peer-rest update endpoints stay registered and keep returning the existing programmatic error, so clients see a stable failure rather than a 404. - MinioReleaseBaseURL and defaultMinisignPubkey are emptied: no dl.min.io download root, and upstream's minisign key is no longer a trust root for anything this fork ships. - cmd/callhome.go is deleted and internal/config/subnet/ is reduced to parsing its old keys and reporting that the integration is disabled. config-current.go warns instead of failing when callhome or SUBNET settings are present, so an upgraded node with those keys still starts. - internal/config/errors.go replaces the MinIO Slack and support entry points with Silo documentation and issue links. Error codes and programmatic fields are unchanged. Verified: the compatibility baseline is unchanged except for the deliberate removal of the /api/health/upload SUBNET route; go build, go vet and the full cmd/ and internal/ unit suites pass; a locally built binary starts, serves S3/Admin/metrics on the unchanged /minio/* routes, answers with Server: Silo, and falls back to a pre-existing ~/.minio with the expected notice. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -29,7 +29,7 @@ var (
|
||||
config.HelpKV{
|
||||
Key: Enable,
|
||||
Type: "on|off",
|
||||
Description: "set to enable callhome" + defaultHelpPostfix(Enable),
|
||||
Description: "legacy callhome setting; retained for compatibility but unsupported by Silo" + defaultHelpPostfix(Enable),
|
||||
Optional: true,
|
||||
},
|
||||
config.HelpKV{
|
||||
|
||||
+13
-13
@@ -21,8 +21,8 @@ package config
|
||||
var (
|
||||
ErrInvalidXLValue = newErrFn(
|
||||
"Invalid drive path",
|
||||
"Please provide a fresh drive for single drive MinIO setup",
|
||||
"MinIO only supports fresh drive paths",
|
||||
"Please provide a fresh drive for a single-drive Silo setup",
|
||||
"Silo only supports fresh drive paths",
|
||||
)
|
||||
|
||||
ErrInvalidBrowserValue = newErrFn(
|
||||
@@ -118,14 +118,14 @@ var (
|
||||
ErrStorageClassValue = newErrFn(
|
||||
"Invalid storage class value",
|
||||
"Please check the value",
|
||||
`MINIO_STORAGE_CLASS_STANDARD: Format "EC:<Default_Parity_Standard_Class>" (e.g. "EC:3"). This sets the number of parity drives for MinIO server in Standard mode. Objects are stored in Standard mode, if storage class is not defined in Put request
|
||||
MINIO_STORAGE_CLASS_RRS: Format "EC:<Default_Parity_Reduced_Redundancy_Class>" (e.g. "EC:3"). This sets the number of parity drives for MinIO server in Reduced Redundancy mode. Objects are stored in Reduced Redundancy mode, if Put request specifies RRS storage class
|
||||
Refer to the link https://github.com/minio/minio/tree/master/docs/erasure/storage-class for more information`,
|
||||
`MINIO_STORAGE_CLASS_STANDARD: Format "EC:<Default_Parity_Standard_Class>" (e.g. "EC:3"). This sets the number of parity drives for Silo in Standard mode. Objects are stored in Standard mode if no storage class is defined in the Put request.
|
||||
MINIO_STORAGE_CLASS_RRS: Format "EC:<Default_Parity_Reduced_Redundancy_Class>" (e.g. "EC:3"). This sets the number of parity drives for Silo in Reduced Redundancy mode. Objects are stored in Reduced Redundancy mode if the Put request specifies the RRS storage class.
|
||||
See https://silo.pgsty.com/operations/concepts/erasure-coding/ for more information.`,
|
||||
)
|
||||
|
||||
ErrUnexpectedBackendVersion = newErrFn(
|
||||
"Backend version seems to be too recent",
|
||||
"Please update to the latest MinIO version",
|
||||
"Please update to the latest Silo version",
|
||||
"",
|
||||
)
|
||||
|
||||
@@ -143,8 +143,8 @@ Refer to the link https://github.com/minio/minio/tree/master/docs/erasure/storag
|
||||
"Please check the endpoint",
|
||||
`Single-Node modes requires absolute path without hostnames:
|
||||
Examples:
|
||||
$ minio server /data/minio/ #Single Node Single Drive
|
||||
$ minio server /data-{1...4}/minio # Single Node Multi Drive`,
|
||||
$ silo server /data/silo/ # Single Node Single Drive
|
||||
$ silo server /data-{1...4}/silo # Single Node Multi Drive`,
|
||||
)
|
||||
|
||||
ErrUnsupportedBackend = newErrFn(
|
||||
@@ -155,8 +155,8 @@ Examples:
|
||||
|
||||
ErrUnableToWriteInBackend = newErrFn(
|
||||
"Unable to write to the backend",
|
||||
"Please ensure MinIO binary has write permissions for the backend",
|
||||
`Verify if MinIO binary is running as the same user who has write permissions for the backend`,
|
||||
"Please ensure the Silo binary has write permissions for the backend",
|
||||
`Verify that the Silo binary is running as the same user who has write permissions for the backend`,
|
||||
)
|
||||
|
||||
ErrPortAlreadyInUse = newErrFn(
|
||||
@@ -167,8 +167,8 @@ Examples:
|
||||
|
||||
ErrPortAccess = newErrFn(
|
||||
"Unable to use specified port",
|
||||
"Please ensure MinIO binary has 'cap_net_bind_service=+ep' permissions",
|
||||
`Use 'sudo setcap cap_net_bind_service=+ep /path/to/minio' to provide sufficient permissions`,
|
||||
"Please ensure the Silo binary has 'cap_net_bind_service=+ep' permissions",
|
||||
`Use 'sudo setcap cap_net_bind_service=+ep /path/to/silo' to provide sufficient permissions`,
|
||||
)
|
||||
|
||||
ErrTLSReadError = newErrFn(
|
||||
@@ -209,7 +209,7 @@ Examples:
|
||||
|
||||
ErrUnexpectedError = newErrFn(
|
||||
"Unexpected error",
|
||||
"Please contact MinIO at https://slack.min.io",
|
||||
"Please report this Silo error at https://github.com/pgsty/minio/issues",
|
||||
"",
|
||||
)
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ var (
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: Vendor,
|
||||
Description: `Specify vendor type for vendor specific behavior to checking validity of temporary credentials and service accounts on MinIO` + defaultHelpPostfix(Vendor),
|
||||
Description: `Specify vendor type for vendor-specific behavior when checking temporary credentials and service accounts on Silo` + defaultHelpPostfix(Vendor),
|
||||
Optional: true,
|
||||
Type: "string",
|
||||
},
|
||||
|
||||
@@ -29,11 +29,6 @@ import (
|
||||
xnet "github.com/minio/pkg/v3/net"
|
||||
)
|
||||
|
||||
const (
|
||||
baseURL = "https://subnet.min.io"
|
||||
baseURLDev = "http://localhost:9000"
|
||||
)
|
||||
|
||||
// DefaultKVS - default KV config for subnet settings
|
||||
var DefaultKVS = config.KVS{
|
||||
config.KV{
|
||||
@@ -70,29 +65,21 @@ type Config struct {
|
||||
|
||||
var configLock sync.RWMutex
|
||||
|
||||
// Registered indicates if cluster is registered or not
|
||||
// Registered reports false because Silo does not use MinIO SUBNET. The
|
||||
// inherited credentials remain parseable for configuration compatibility.
|
||||
func (c *Config) Registered() bool {
|
||||
configLock.RLock()
|
||||
defer configLock.RUnlock()
|
||||
|
||||
return len(c.APIKey) > 0
|
||||
return false
|
||||
}
|
||||
|
||||
// ApplyEnv - applies the current subnet config to Console UI specific environment variables.
|
||||
func (c *Config) ApplyEnv() {
|
||||
configLock.RLock()
|
||||
defer configLock.RUnlock()
|
||||
|
||||
if c.License != "" {
|
||||
os.Setenv("CONSOLE_SUBNET_LICENSE", c.License)
|
||||
}
|
||||
if c.APIKey != "" {
|
||||
os.Setenv("CONSOLE_SUBNET_API_KEY", c.APIKey)
|
||||
}
|
||||
if c.Proxy != "" {
|
||||
os.Setenv("CONSOLE_SUBNET_PROXY", c.Proxy)
|
||||
}
|
||||
os.Setenv("CONSOLE_SUBNET_URL", c.BaseURL)
|
||||
// Do not expose inherited commercial-service credentials or an endpoint to
|
||||
// the embedded Console. These names are outputs, not the MINIO_* input
|
||||
// compatibility surface.
|
||||
os.Unsetenv("CONSOLE_SUBNET_LICENSE")
|
||||
os.Unsetenv("CONSOLE_SUBNET_API_KEY")
|
||||
os.Unsetenv("CONSOLE_SUBNET_PROXY")
|
||||
os.Unsetenv("CONSOLE_SUBNET_URL")
|
||||
}
|
||||
|
||||
// Update - in-place update with new license and registration information.
|
||||
@@ -103,15 +90,12 @@ func (c *Config) Update(ncfg Config, isDevEnv bool) {
|
||||
c.License = ncfg.License
|
||||
c.APIKey = ncfg.APIKey
|
||||
c.Proxy = ncfg.Proxy
|
||||
c.transport = ncfg.transport
|
||||
c.BaseURL = baseURL
|
||||
c.transport = nil
|
||||
c.BaseURL = ""
|
||||
|
||||
if isDevEnv {
|
||||
c.BaseURL = os.Getenv("_MINIO_SUBNET_URL")
|
||||
if c.BaseURL == "" {
|
||||
c.BaseURL = baseURLDev
|
||||
}
|
||||
}
|
||||
// Retain the hidden compatibility input but deliberately ignore its value.
|
||||
_ = os.Getenv("_MINIO_SUBNET_URL")
|
||||
_ = isDevEnv
|
||||
}
|
||||
|
||||
// LookupConfig - lookup config and override with valid environment settings if any.
|
||||
|
||||
@@ -18,97 +18,27 @@
|
||||
package subnet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
)
|
||||
|
||||
const (
|
||||
respBodyLimit = 1 << 20 // 1 MiB
|
||||
|
||||
// LoggerWebhookName - subnet logger webhook target
|
||||
LoggerWebhookName = "subnet"
|
||||
)
|
||||
|
||||
var errSiloSubnetDisabled = errors.New("MinIO SUBNET integration is disabled in Silo")
|
||||
|
||||
// Upload given file content (payload) to specified URL
|
||||
func (c Config) Upload(reqURL string, filename string, payload []byte) (string, error) {
|
||||
if !c.Registered() {
|
||||
return "", errors.New("Deployment is not registered with SUBNET. Please register the deployment via 'mc license register ALIAS'")
|
||||
}
|
||||
|
||||
var body bytes.Buffer
|
||||
writer := multipart.NewWriter(&body)
|
||||
part, e := writer.CreateFormFile("file", filename)
|
||||
if e != nil {
|
||||
return "", e
|
||||
}
|
||||
|
||||
if _, e = part.Write(payload); e != nil {
|
||||
return "", e
|
||||
}
|
||||
writer.Close()
|
||||
|
||||
r, e := http.NewRequest(http.MethodPost, reqURL, &body)
|
||||
if e != nil {
|
||||
return "", e
|
||||
}
|
||||
r.Header.Add("Content-Type", writer.FormDataContentType())
|
||||
|
||||
return c.submitPost(r)
|
||||
return "", errSiloSubnetDisabled
|
||||
}
|
||||
|
||||
func (c Config) submitPost(r *http.Request) (string, error) {
|
||||
configLock.RLock()
|
||||
r.Header.Set(xhttp.SubnetAPIKey, c.APIKey)
|
||||
configLock.RUnlock()
|
||||
r.Header.Set(xhttp.MinioDeploymentID, xhttp.GlobalDeploymentID)
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
Transport: c.transport,
|
||||
}
|
||||
|
||||
resp, err := client.Do(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer xhttp.DrainBody(resp.Body)
|
||||
|
||||
respBytes, err := io.ReadAll(io.LimitReader(resp.Body, respBodyLimit))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
respStr := string(respBytes)
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
return respStr, nil
|
||||
}
|
||||
|
||||
return respStr, fmt.Errorf("SUBNET request failed with code %d and error: %s", resp.StatusCode, respStr)
|
||||
func (c Config) submitPost(_ *http.Request) (string, error) {
|
||||
return "", errSiloSubnetDisabled
|
||||
}
|
||||
|
||||
// Post submit 'payload' to specified URL
|
||||
func (c Config) Post(reqURL string, payload any) (string, error) {
|
||||
if !c.Registered() {
|
||||
return "", errors.New("Deployment is not registered with SUBNET. Please register the deployment via 'mc license register ALIAS'")
|
||||
}
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
r, err := http.NewRequest(http.MethodPost, reqURL, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
r.Header.Set("Content-Type", "application/json")
|
||||
|
||||
return c.submitPost(r)
|
||||
return "", errSiloSubnetDisabled
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2026 PGSTY contributors.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
package subnet
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSiloSubnetIsPermanentlyDisabled(t *testing.T) {
|
||||
cfg := Config{License: "legacy-license", APIKey: "legacy-key", BaseURL: "https://example.invalid"}
|
||||
if cfg.Registered() {
|
||||
t.Fatal("legacy SUBNET credentials must not register a Silo deployment")
|
||||
}
|
||||
|
||||
for _, name := range []string{"CONSOLE_SUBNET_LICENSE", "CONSOLE_SUBNET_API_KEY", "CONSOLE_SUBNET_PROXY", "CONSOLE_SUBNET_URL"} {
|
||||
t.Setenv(name, "must-be-cleared")
|
||||
}
|
||||
cfg.ApplyEnv()
|
||||
for _, name := range []string{"CONSOLE_SUBNET_LICENSE", "CONSOLE_SUBNET_API_KEY", "CONSOLE_SUBNET_PROXY", "CONSOLE_SUBNET_URL"} {
|
||||
if _, ok := os.LookupEnv(name); ok {
|
||||
t.Fatalf("%s remains set", name)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := cfg.Upload("https://example.invalid", "health.json", nil); !errors.Is(err, errSiloSubnetDisabled) {
|
||||
t.Fatalf("Upload error = %v, want %v", err, errSiloSubnetDisabled)
|
||||
}
|
||||
if _, err := cfg.Post("https://example.invalid", struct{}{}); !errors.Is(err, errSiloSubnetDisabled) {
|
||||
t.Fatalf("Post error = %v, want %v", err, errSiloSubnetDisabled)
|
||||
}
|
||||
if _, err := cfg.submitPost(nil); !errors.Is(err, errSiloSubnetDisabled) {
|
||||
t.Fatalf("submitPost error = %v, want %v", err, errSiloSubnetDisabled)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user