From b6e2583bd3a6c58b4c210f477bc93c9da2978c04 Mon Sep 17 00:00:00 2001 From: Feng Ruohang Date: Thu, 6 Aug 2026 12:05:06 +0800 Subject: [PATCH] fix: remove the vendor encryption key and close the proxy-set path Follow-up to the rebranding review. - support inspect no longer falls back to MinIO's support public key when the operator has not supplied one. That fallback encrypted locally saved object data to a key only MinIO holds, so the output was neither local nor recoverable by the operator who produced it. Without a key, the server generates one and returns it to the caller, who is its only holder; an operator-supplied support_public.pem still takes precedence. The encrypted upload path fails closed instead of reaching for a vendor key, and the key blob is gone from the binary entirely. - support proxy set configured the server's outbound path to SUBNET, so it now exits with the stable disabled error; show and remove keep working so operators can inspect and clear a legacy setting. - the DCO exemption keyed on the display name, which any author can choose; it now requires a GitHub-issued bot address. - source builds printed 'Copyright (c) 2025-0000 PGSTY'; the placeholder now falls back to the current year, with release ldflags unchanged. - PR template section renamed to match the no-CLA policy, and CONTRIBUTING states that assistive-tooling trailers carry no authorship or copyright and that the human signatory remains responsible. - fixed 6 pre-existing whitespace errors surfaced by the brand sweep. Regression coverage: fail-closed encryption test, plus a brand-gate rule rejecting any embedded public key in cmd/. Co-Authored-By: Claude Fable 5 Signed-off-by: Feng Ruohang --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/workflows/dco.yml | 7 ++++--- CONTRIBUTING.md | 7 +++++++ buildscripts/check-branding.sh | 8 ++++++++ cmd/admin-user-add.go | 2 +- cmd/build-constants.go | 12 ++++++++++++ cmd/idp-ldap-accesskey-list.go | 4 ++-- cmd/idp-openid-accesskey-list.go | 2 +- cmd/main.go | 7 ++++--- cmd/mirror-main.go | 2 +- cmd/put-main.go | 2 +- cmd/subnet-disabled_test.go | 15 +++++++++++++++ cmd/subnet-file-uploader.go | 10 +++++----- cmd/support-inspect.go | 14 ++++---------- cmd/support-proxy-set.go | 18 ++++++++++++++---- 15 files changed, 80 insertions(+), 32 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3334a56493..32a652c57d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,4 @@ -## Community Contribution License +## Contribution Licensing (no CLA, inbound=outbound, DCO required) This project does not use a CLA; contributions are accepted inbound=outbound. All community contributions in this pull request are licensed under the [GNU AGPL v3.0 or later](https://www.gnu.org/licenses/agpl-3.0.html), the diff --git a/.github/workflows/dco.yml b/.github/workflows/dco.yml index ce5faf0471..fe7b148f2b 100644 --- a/.github/workflows/dco.yml +++ b/.github/workflows/dco.yml @@ -21,7 +21,8 @@ jobs: # Every non-merge commit in the pull request must carry a Signed-off-by # trailer matching the commit author's email, certifying the Developer # Certificate of Origin 1.1 (https://developercertificate.org/). - # Bot-authored commits (dependabot etc.) are exempt. + # Only commits authored from a GitHub-issued bot address are exempt; a + # display name is attacker-controlled and must never grant the exemption. - name: Check Signed-off-by trailers env: BASE_SHA: ${{ github.event.pull_request.base.sha }} @@ -31,8 +32,8 @@ jobs: while read -r sha; do author_name="$(git log -1 --format='%an' "${sha}")" author_email="$(git log -1 --format='%ae' "${sha}")" - case "${author_name}" in - *"[bot]"*) continue ;; + case "${author_email}" in + *"[bot]@users.noreply.github.com") continue ;; esac if ! git log -1 --format='%(trailers:key=Signed-off-by,valueonly)' "${sha}" | grep -qiF "<${author_email}>"; then diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8169f74fb0..9550ea5919 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -78,3 +78,10 @@ this fork does not try to. * **Squash merges** must keep the `Signed-off-by:` trailers in the resulting commit message. + +* **Authorship and tooling.** The human contributor is the author of the + commit and the sole signatory of its DCO sign-off. Attribution trailers for + assistive tooling (for example `Co-Authored-By:` naming an AI assistant) are + informational only: they record which tools were used, and do not create + authorship, co-authorship, or any copyright claim. Whoever signs off remains + responsible for the content of the commit, whatever produced it. diff --git a/buildscripts/check-branding.sh b/buildscripts/check-branding.sh index 09bac6658d..dff392df8b 100755 --- a/buildscripts/check-branding.sh +++ b/buildscripts/check-branding.sh @@ -52,6 +52,14 @@ if ! grep -q 'func subnetServicesEnabled() bool { return false }' cmd/subnet-uti err "subnetServicesEnabled() is no longer hard-disabled" fi +# 6. No vendor encryption key may ship in the binary. Diagnostic output has to +# stay decryptable by the operator who produced it, so encryption must use +# an operator-supplied key or a server-generated one - never a key whose +# private half belongs to a third party. +if git grep -nE 'MII[A-Za-z0-9+/]{64,}' -- 'cmd/*.go' ':!cmd/*_test.go'; then + err "embedded public key found in cmd/; diagnostics must not encrypt to a vendor key" +fi + if [ "${fail}" -ne 0 ]; then echo "check-branding: FAILED - review docs/rebranding policy before changing brand surfaces" >&2 exit 1 diff --git a/cmd/admin-user-add.go b/cmd/admin-user-add.go index 6336ac6b06..610a05e500 100644 --- a/cmd/admin-user-add.go +++ b/cmd/admin-user-add.go @@ -70,7 +70,7 @@ EXAMPLES: {{.EnableHistory}} 4. Add a new user 'foobar' to Silo/MinIO server, then attach IAM policy "writeonly". - {{.Prompt}} {{.HelpName}} mysilo foobar foo12345 + {{.Prompt}} {{.HelpName}} mysilo foobar foo12345 {{.Prompt}} mc admin policy attach mysilo writeonly --user foobar `, } diff --git a/cmd/build-constants.go b/cmd/build-constants.go index 64022068ad..9209c42cf1 100644 --- a/cmd/build-constants.go +++ b/cmd/build-constants.go @@ -17,6 +17,8 @@ package cmd +import "strconv" + var ( // Version - version time.RFC3339. Version = "DEVELOPMENT.GOGET" @@ -29,3 +31,13 @@ var ( // CopyrightYear - dynamic value of the copyright end year CopyrightYear = "0000" ) + +// copyrightEndYear returns the end year shown in copyright notices. Release +// builds inject CopyrightYear through ldflags; source builds keep the "0000" +// placeholder, for which the current year is used instead. +func copyrightEndYear() string { + if CopyrightYear == "" || CopyrightYear == "0000" { + return strconv.Itoa(UTCNow().Year()) + } + return CopyrightYear +} diff --git a/cmd/idp-ldap-accesskey-list.go b/cmd/idp-ldap-accesskey-list.go index 02b748cbb1..ec4e7935c4 100644 --- a/cmd/idp-ldap-accesskey-list.go +++ b/cmd/idp-ldap-accesskey-list.go @@ -77,13 +77,13 @@ EXAMPLES: {{.Prompt}} {{.HelpName}} mysilo/ --temp-only 4. Get list of access keys associated with user 'bobfisher' - {{.Prompt}} {{.HelpName}} mysilo/ uid=bobfisher,dc=example,dc=com + {{.Prompt}} {{.HelpName}} mysilo/ uid=bobfisher,dc=example,dc=com 5. Get list of access keys associated with user 'bobfisher' (alt) {{.Prompt}} {{.HelpName}} mysilo/ bobfisher 6. Get list of access keys associated with users 'bobfisher' and 'cody3' - {{.Prompt}} {{.HelpName}} mysilo/ uid=bobfisher,dc=example,dc=com uid=cody3,dc=example,dc=com + {{.Prompt}} {{.HelpName}} mysilo/ uid=bobfisher,dc=example,dc=com uid=cody3,dc=example,dc=com 7. Get authenticated user and associated access keys in local server (if not admin) {{.Prompt}} {{.HelpName}} local/ diff --git a/cmd/idp-openid-accesskey-list.go b/cmd/idp-openid-accesskey-list.go index 99606b56f3..71a2ee53b8 100644 --- a/cmd/idp-openid-accesskey-list.go +++ b/cmd/idp-openid-accesskey-list.go @@ -83,7 +83,7 @@ EXAMPLES: {{.Prompt}} {{.HelpName}} mysilo/ --temp-only 4. Get list of access keys associated with internal name 'openidinternalname' - {{.Prompt}} {{.HelpName}} mysilo/ openidinternalname + {{.Prompt}} {{.HelpName}} mysilo/ openidinternalname 5. Get list of access keys associated with ID claim 'openidsub' (default claim is sub) {{.Prompt}} {{.HelpName}} mysilo/ openidsub diff --git a/cmd/main.go b/cmd/main.go index 9ddab7ac3e..d1abf23385 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -54,7 +54,8 @@ var mcFlags = []cli.Flag{ }, } -// Help template for mc +// Help template for mc. Built at init time so the copyright end year can fall +// back to the current year in source builds, where ldflags inject nothing. var mcHelpTemplate = `NAME: {{.Name}} - {{.Usage}} @@ -72,7 +73,7 @@ TIP: COPYRIGHT: Copyright (c) 2015-2025 MinIO, Inc. - Copyright (c) 2025-` + CopyrightYear + ` PGSTY + Copyright (c) 2025-` + copyrightEndYear() + ` PGSTY LICENSE: GNU AGPLv3 @@ -446,7 +447,7 @@ func printMCVersion(c *cli.Context) { fmt.Fprintf(c.App.Writer, "Silo object storage client, based on MinIO technology\n") fmt.Fprintf(c.App.Writer, "Runtime: %s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH) fmt.Fprintf(c.App.Writer, "Copyright (c) 2015-2025 MinIO, Inc.\n") - fmt.Fprintf(c.App.Writer, "Copyright (c) 2025-%s PGSTY\n", CopyrightYear) + fmt.Fprintf(c.App.Writer, "Copyright (c) 2025-%s PGSTY\n", copyrightEndYear()) fmt.Fprintf(c.App.Writer, "License GNU AGPLv3 \n") } diff --git a/cmd/mirror-main.go b/cmd/mirror-main.go index 76985a42df..223ac28871 100644 --- a/cmd/mirror-main.go +++ b/cmd/mirror-main.go @@ -217,7 +217,7 @@ EXAMPLES: {{.Prompt}} {{.HelpName}} --older-than 30d s3/test ~/test 13. Mirror server encrypted objects from Amazon S3 cloud storage to a bucket on Amazon S3 cloud storage - {{.Prompt}} {{.HelpName}} --enc-c "mysilo/archive=MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDA" --enc-c "s3/archive=MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5BBB" s3/archive/ mysilo/archive/ + {{.Prompt}} {{.HelpName}} --enc-c "mysilo/archive=MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDA" --enc-c "s3/archive=MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5BBB" s3/archive/ mysilo/archive/ 14. Update 'Cache-Control' header on all existing objects recursively. {{.Prompt}} {{.HelpName}} --attr "Cache-Control=max-age=90000,min-fresh=9000" mysilo/video-files mysilo/video-files diff --git a/cmd/put-main.go b/cmd/put-main.go index c895d9e76c..ddb8a39040 100644 --- a/cmd/put-main.go +++ b/cmd/put-main.go @@ -91,7 +91,7 @@ EXAMPLES: {{.Prompt}} {{.HelpName}} path-to/object mysilo/mybucket/object-prefix/ 4. Put an object to Silo/MinIO storage using sse-c encryption - {{.Prompt}} {{.HelpName}} --enc-c "mysilo/mybucket/object=MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDA" path-to/object mysilo/mybucket/object + {{.Prompt}} {{.HelpName}} --enc-c "mysilo/mybucket/object=MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDA" path-to/object mysilo/mybucket/object 5. Put an object to Silo/MinIO storage using sse-kms encryption {{.Prompt}} {{.HelpName}} --enc-kms path-to/object mysilo/mybucket/object diff --git a/cmd/subnet-disabled_test.go b/cmd/subnet-disabled_test.go index ffe7744fbd..1ede1aad46 100644 --- a/cmd/subnet-disabled_test.go +++ b/cmd/subnet-disabled_test.go @@ -19,6 +19,7 @@ package cmd import ( + "io" "strings" "testing" ) @@ -67,6 +68,20 @@ func TestFreshConfigHasNoThirdPartyDemoAlias(t *testing.T) { } } +func TestEncryptedUploadRequiresAnExplicitKey(t *testing.T) { + // Encryption must never fall back to a vendor key: data the operator + // cannot decrypt is not a local artifact. With no PubKey configured the + // encrypted path has no recipient and must fail closed. + up := &SubnetFileUploader{AutoEncrypt: true, FilePath: "testdata/undelivered.txt"} + req, e := up.subnetUploadReq() + if e != nil { + t.Fatalf("subnetUploadReq() = %v, want a request whose body fails closed", e) + } + if _, e = io.ReadAll(req.Body); e == nil { + t.Fatal("encrypted upload body succeeded without a public key; it must fail closed") + } +} + func TestAGPLMessageHasNoCommercialPitch(t *testing.T) { msg := getAGPLMessage() for _, needle := range []string{"min.io", "commercial", "subscription"} { diff --git a/cmd/subnet-file-uploader.go b/cmd/subnet-file-uploader.go index 80b062a6fc..4e3b597af4 100644 --- a/cmd/subnet-file-uploader.go +++ b/cmd/subnet-file-uploader.go @@ -20,8 +20,8 @@ package cmd import ( "crypto/rsa" "crypto/x509" - "encoding/base64" "encoding/pem" + "errors" "io" "mime/multipart" "net/http" @@ -133,10 +133,10 @@ func (i *SubnetFileUploader) subnetUploadReq() (*http.Request, error) { errfn = sw.AddError key := i.PubKey if key == nil { - key, e = base64.StdEncoding.DecodeString(defaultPublicKey) - if e != nil { - return - } + // This build ships no vendor encryption key: without an + // explicit public key there is no recipient to encrypt to. + e = errors.New("no public encryption key configured") + return } pk, e := bytesToPublicKey(key) if e != nil { diff --git a/cmd/support-inspect.go b/cmd/support-inspect.go index 5d1031dacf..7fa923d1d9 100644 --- a/cmd/support-inspect.go +++ b/cmd/support-inspect.go @@ -19,7 +19,6 @@ package cmd import ( "context" - "encoding/base64" "encoding/binary" "encoding/hex" "errors" @@ -42,10 +41,6 @@ import ( "github.com/minio/pkg/v3/console" ) -const ( - defaultPublicKey = "MIIBCgKCAQEAs/128UFS9A8YSJY1XqYKt06dLVQQCGDee69T+0Tip/1jGAB4z0/3QMpH0MiS8Wjs4BRWV51qvkfAHzwwdU7y6jxU05ctb/H/WzRj3FYdhhHKdzear9TLJftlTs+xwj2XaADjbLXCV1jGLS889A7f7z5DgABlVZMQd9BjVAR8ED3xRJ2/ZCNuQVJ+A8r7TYPGMY3wWvhhPgPk3Lx4WDZxDiDNlFs4GQSaESSsiVTb9vyGe/94CsCTM6Cw9QG6ifHKCa/rFszPYdKCabAfHcS3eTr0GM+TThSsxO7KfuscbmLJkfQev1srfL2Ii2RbnysqIJVWKEwdW05ID8ryPkuTuwIDAQAB" -) - var supportInspectFlags = append(subnetCommonFlags, cli.BoolFlag{ Name: "legacy", @@ -173,11 +168,10 @@ func mainSupportInspect(ctx *cli.Context) error { } } - // Fall back to MinIO public key. - if len(publicKey) == 0 { - // Public key for MinIO confidential information. - publicKey, _ = base64.StdEncoding.DecodeString(defaultPublicKey) - } + // Without a user-supplied public key the server generates a random + // key and returns it to the caller, who is the only holder. This + // build never falls back to a vendor key: inspect output must stay + // decryptable by the operator who produced it. } key, r, e := client.Inspect(context.Background(), madmin.InspectOptions{ diff --git a/cmd/support-proxy-set.go b/cmd/support-proxy-set.go index 22c7a905b2..5e12e32ddf 100644 --- a/cmd/support-proxy-set.go +++ b/cmd/support-proxy-set.go @@ -43,7 +43,7 @@ func (s supportProxySetMessage) JSON() string { var supportProxySetCmd = cli.Command{ Name: "set", - Usage: "configure proxy to given URL", + Usage: "configure the SUBNET proxy URL (disabled in this build)", Action: mainSupportProxySet, OnUsageError: onUsageError, Before: setGlobalsFromContext, @@ -58,9 +58,14 @@ USAGE: FLAGS: {{range .VisibleFlags}}{{.}} {{end}} -EXAMPLES: - 1. Set the proxy to http://my.proxy for cluster with alias 'mysilo' - {{.Prompt}} {{.HelpName}} mysilo http://my.proxy +DESCRIPTION: + This setting configures the outbound path a server uses to reach MinIO + SUBNET. Since this Silo build disables SUBNET entirely, the command is + retained for CLI compatibility and always exits with an error. Use + '{{.HelpName}} ..' -> 'remove' to clear an existing proxy setting. + +EXIT STATUS: + 1 - SUBNET services are disabled `, } @@ -72,6 +77,11 @@ func checkSupportProxySetSyntax(ctx *cli.Context) { // mainSupportProxySet is the handle for "mc support proxy set" command. func mainSupportProxySet(ctx *cli.Context) error { + if !subnetServicesEnabled() { + // Setting a proxy configures the server's outbound path to SUBNET. + // Removal stays available so operators can clear a legacy setting. + return subnetDisabledExit() + } // Check for command syntax checkSupportProxySetSyntax(ctx) setSuccessMessageColor()