Files
minio/cmd/policy-strict.go
T
Feng Ruohang 34ba7cc1ff 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>
2026-11-12 03:06:15 +08:00

160 lines
4.8 KiB
Go

// Copyright (c) 2026 PGSTY
//
// This file is part of MinIO Object Storage stack
//
// 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 cmd
import (
"bytes"
"encoding/json"
"io"
"github.com/pgsty/silo-pkg/v3/policy"
)
type strictIAMStatementWire struct {
SID policy.ID `json:"Sid,omitempty"`
Effect policy.Effect `json:"Effect"`
Actions json.RawMessage `json:"Action,omitempty"`
NotActions json.RawMessage `json:"NotAction,omitempty"`
Resources json.RawMessage `json:"Resource,omitempty"`
NotResources json.RawMessage `json:"NotResource,omitempty"`
Conditions json.RawMessage `json:"Condition,omitempty"`
}
type strictIAMPolicyWire struct {
ID policy.ID `json:"ID,omitempty"`
Version string `json:"Version"`
Statements []strictIAMStatementWire `json:"Statement"`
}
type strictPrincipalWire struct {
AWS json.RawMessage `json:"AWS"`
}
type strictBucketStatementWire struct {
SID policy.ID `json:"Sid,omitempty"`
Effect policy.Effect `json:"Effect"`
Principal json.RawMessage `json:"Principal,omitempty"`
Actions json.RawMessage `json:"Action,omitempty"`
NotActions json.RawMessage `json:"NotAction,omitempty"`
Resources json.RawMessage `json:"Resource,omitempty"`
NotResources json.RawMessage `json:"NotResource,omitempty"`
Conditions json.RawMessage `json:"Condition,omitempty"`
}
type strictBucketPolicyWire struct {
ID policy.ID `json:"ID,omitempty"`
Version string `json:"Version"`
Statements []strictBucketStatementWire `json:"Statement"`
}
func validateStrictIAMPolicyWire(data []byte) ([]byte, error) {
trimmed := bytes.TrimSpace(data)
if len(trimmed) == 0 || trimmed[0] != '{' {
return nil, policy.Errorf("policy must be a JSON object")
}
var wire strictIAMPolicyWire
dec := json.NewDecoder(bytes.NewReader(trimmed))
dec.DisallowUnknownFields()
if err := dec.Decode(&wire); err != nil {
return nil, policy.Errorf("%w", err)
}
var extra any
if err := dec.Decode(&extra); err != io.EOF {
if err == nil {
return nil, policy.Errorf("unexpected trailing JSON data in policy")
}
return nil, policy.Errorf("%w", err)
}
return trimmed, nil
}
func parsePolicyStrictBytes(data []byte) (*policy.Policy, error) {
trimmed, err := validateStrictIAMPolicyWire(data)
if err != nil {
return nil, err
}
p, err := policy.ParseConfigStrict(bytes.NewReader(trimmed))
if err != nil {
return nil, err
}
if p.IsEmpty() {
return nil, policy.Errorf("empty policies are not allowed")
}
return p, nil
}
func parseSessionPolicyStrictBytes(data []byte) (*policy.Policy, error) {
trimmed, err := validateStrictIAMPolicyWire(data)
if err != nil {
return nil, err
}
return policy.ParseConfigStrict(bytes.NewReader(trimmed))
}
// Snapshot imports retain supported historical policy semantics while rejecting
// unknown JSON fields that could silently discard restrictions.
func parsePolicySnapshotBytes(data []byte) (*policy.Policy, error) {
trimmed, err := validateStrictIAMPolicyWire(data)
if err != nil {
return nil, err
}
return policy.ParseConfig(bytes.NewReader(trimmed))
}
func validateStrictPrincipalWire(raw json.RawMessage) error {
trimmed := bytes.TrimSpace(raw)
if len(trimmed) == 0 || trimmed[0] != '{' {
return nil
}
var pw strictPrincipalWire
dec := json.NewDecoder(bytes.NewReader(trimmed))
dec.DisallowUnknownFields()
if err := dec.Decode(&pw); err != nil {
return policy.Errorf("%w", err)
}
var extra any
if err := dec.Decode(&extra); err != io.EOF {
if err == nil {
return policy.Errorf("unexpected trailing JSON data in principal")
}
return policy.Errorf("%w", err)
}
if len(bytes.TrimSpace(pw.AWS)) == 0 {
return policy.Errorf("invalid principal '%s'", string(raw))
}
return nil
}
func parseBucketPolicyStrictBytes(data []byte, bucket string) (*policy.BucketPolicy, error) {
trimmed := bytes.TrimSpace(data)
if len(trimmed) == 0 || trimmed[0] != '{' {
return nil, policy.Errorf("bucket policy must be a JSON object")
}
var wire strictBucketPolicyWire
dec := json.NewDecoder(bytes.NewReader(trimmed))
dec.DisallowUnknownFields()
if err := dec.Decode(&wire); err != nil {
return nil, policy.Errorf("%w", err)
}
var extra any
if err := dec.Decode(&extra); err != io.EOF {
if err == nil {
return nil, policy.Errorf("unexpected trailing JSON data in bucket policy")
}
return nil, policy.Errorf("%w", err)
}
for _, st := range wire.Statements {
if err := validateStrictPrincipalWire(st.Principal); err != nil {
return nil, err
}
}
return policy.ParseBucketPolicyConfig(bytes.NewReader(trimmed), bucket)
}