Prepare SILO 20261112 release candidate

Harden IAM/bucket/import/replication policy validation, enforce tag and
version authorization across read/copy/list/archive endpoints, bind SigV4
credential-scope dates and streaming trailer limits, validate HTTP SSE-KMS
contexts as strict UTF-8 JSON, add release-commit and coordinated
dependency gates, and pin silo-pkg v3.16.1, Console v2.7.1, mcli 20261112,
Go 1.27.4, and Helm chart 7.0.5.

Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
Feng Ruohang
2026-11-12 03:06:15 +08:00
parent bae0ba0b75
commit 34ba7cc1ff
58 changed files with 3536 additions and 418 deletions
+40 -21
View File
@@ -889,8 +889,9 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
// policy the account has. An empty policy document is passed on as
// such: it is the request to remove the embedded policy.
var sp *policy.Policy
if len(updateReq.NewPolicy) > 0 && !bytes.Equal(updateReq.NewPolicy, []byte("null")) {
sp, err = policy.ParseConfigStrict(bytes.NewReader(updateReq.NewPolicy))
trimmedPolicy := bytes.TrimSpace(updateReq.NewPolicy)
if len(trimmedPolicy) > 0 && !bytes.Equal(trimmedPolicy, []byte("null")) {
sp, err = parseSessionPolicyStrictBytes(trimmedPolicy)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
@@ -1766,7 +1767,7 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
return
}
iamPolicy, err := policy.ParseConfigStrict(bytes.NewReader(iamPolicyBytes))
iamPolicy, err := parsePolicyStrictBytes(iamPolicyBytes)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
@@ -2510,23 +2511,34 @@ func (a adminAPIHandlers) importIAM(w http.ResponseWriter, r *http.Request, apiV
return
default:
defer f.Close()
var allPolicies map[string]policy.Policy
var policyDocuments map[string]json.RawMessage
data, err = io.ReadAll(f)
if err != nil {
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrInvalidRequest, err, allPoliciesFile, ""), r.URL)
return
}
err = json.Unmarshal(data, &allPolicies)
err = json.Unmarshal(data, &policyDocuments)
if err != nil {
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminConfigBadJSON, err, allPoliciesFile, ""), r.URL)
return
}
// Validate the entire policy file before applying any entries. Keep
// legacy semantic validation, but never discard unknown wire fields.
allPolicies := make(map[string]*policy.Policy, len(policyDocuments))
for policyName, document := range policyDocuments {
parsed, parseErr := parsePolicySnapshotBytes(document)
if parseErr != nil {
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminConfigBadJSON, parseErr, allPoliciesFile, policyName), r.URL)
return
}
allPolicies[policyName] = parsed
}
for policyName, policy := range allPolicies {
if policy.IsEmpty() {
err = globalIAMSys.DeletePolicy(ctx, policyName, true)
removed.Policies = append(removed.Policies, policyName)
} else {
_, err = globalIAMSys.SetPolicy(ctx, policyName, policy)
_, err = globalIAMSys.SetPolicy(ctx, policyName, *policy)
added.Policies = append(added.Policies, policyName)
}
if err != nil {
@@ -2674,10 +2686,13 @@ func (a adminAPIHandlers) importIAM(w http.ResponseWriter, r *http.Request, apiV
var sp *policy.Policy
var err error
if len(svcAcctReq.SessionPolicy) > 0 {
sp, err = policy.ParseConfig(bytes.NewReader(svcAcctReq.SessionPolicy))
if err != nil {
writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL)
return
trimmedSP := bytes.TrimSpace(svcAcctReq.SessionPolicy)
if len(trimmedSP) > 0 && !bytes.Equal(trimmedSP, []byte("null")) {
sp, err = parsePolicySnapshotBytes(trimmedSP)
if err != nil {
writeErrorResponseJSON(ctx, w, importError(ctx, err, allSvcAcctsFile, user), r.URL)
return
}
}
}
// service account access key cannot have space characters
@@ -2706,15 +2721,16 @@ func (a adminAPIHandlers) importIAM(w http.ResponseWriter, r *http.Request, apiV
}
}
opts := newServiceAccountOpts{
accessKey: user,
secretKey: svcAcctReq.SecretKey,
status: svcAcctReq.Status,
sessionPolicy: sp,
claims: svcAcctReq.Claims,
name: svcAcctReq.Name,
description: svcAcctReq.Description,
expiration: svcAcctReq.Expiration,
allowSiteReplicatorAccount: false,
accessKey: user,
secretKey: svcAcctReq.SecretKey,
status: svcAcctReq.Status,
sessionPolicy: sp,
claims: svcAcctReq.Claims,
name: svcAcctReq.Name,
description: svcAcctReq.Description,
expiration: svcAcctReq.Expiration,
allowSiteReplicatorAccount: false,
preserveEmptyEmbeddedPolicy: svcAcctClaimsHaveEmbeddedPolicy(svcAcctReq.Claims),
}
if _, _, err = globalIAMSys.NewServiceAccount(ctx, svcAcctReq.Parent, svcAcctReq.Groups, opts); err != nil {
@@ -3058,8 +3074,11 @@ func commonAddServiceAccount(r *http.Request, ldap bool) (context.Context, auth.
}
var sp *policy.Policy
if len(createReq.Policy) > 0 {
sp, err = policy.ParseConfigStrict(bytes.NewReader(createReq.Policy))
trimmedCreatePolicy := bytes.TrimSpace(createReq.Policy)
if bytes.Equal(trimmedCreatePolicy, []byte("null")) {
createReq.Policy = nil
} else if len(trimmedCreatePolicy) > 0 {
sp, err = parseSessionPolicyStrictBytes(trimmedCreatePolicy)
if err != nil {
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", toAdminAPIErr(ctx, err)
}