Files
seaweedFS/weed/s3api/policy_engine/conditions.go
Chris Lu 995dfc4d5d chore: remove ~50k lines of unreachable dead code (#8913)
* chore: remove unreachable dead code across the codebase

Remove ~50,000 lines of unreachable code identified by static analysis.

Major removals:
- weed/filer/redis_lua: entire unused Redis Lua filer store implementation
- weed/wdclient/net2, resource_pool: unused connection/resource pool packages
- weed/plugin/worker/lifecycle: unused lifecycle plugin worker
- weed/s3api: unused S3 policy templates, presigned URL IAM, streaming copy,
  multipart IAM, key rotation, and various SSE helper functions
- weed/mq/kafka: unused partition mapping, compression, schema, and protocol functions
- weed/mq/offset: unused SQL storage and migration code
- weed/worker: unused registry, task, and monitoring functions
- weed/query: unused SQL engine, parquet scanner, and type functions
- weed/shell: unused EC proportional rebalance functions
- weed/storage/erasure_coding/distribution: unused distribution analysis functions
- Individual unreachable functions removed from 150+ files across admin,
  credential, filer, iam, kms, mount, mq, operation, pb, s3api, server,
  shell, storage, topology, and util packages

* fix(s3): reset shared memory store in IAM test to prevent flaky failure

TestLoadIAMManagerFromConfig_EmptyConfigWithFallbackKey was flaky because
the MemoryStore credential backend is a singleton registered via init().
Earlier tests that create anonymous identities pollute the shared store,
causing LookupAnonymous() to unexpectedly return true.

Fix by calling Reset() on the memory store before the test runs.

* style: run gofmt on changed files

* fix: restore KMS functions used by integration tests

* fix(plugin): prevent panic on send to closed worker session channel

The Plugin.sendToWorker method could panic with "send on closed channel"
when a worker disconnected while a message was being sent. The race was
between streamSession.close() closing the outgoing channel and sendToWorker
writing to it concurrently.

Add a done channel to streamSession that is closed before the outgoing
channel, and check it in sendToWorker's select to safely detect closed
sessions without panicking.
2026-04-03 16:04:27 -07:00

756 lines
21 KiB
Go

package policy_engine
import (
"fmt"
"net"
"reflect"
"strconv"
"strings"
"sync"
"time"
"github.com/seaweedfs/seaweedfs/weed/glog"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/util/wildcard"
)
// LRUNode represents a node in the doubly-linked list for efficient LRU operations
type LRUNode struct {
key string
value []string
prev *LRUNode
next *LRUNode
}
// NormalizedValueCache provides size-limited caching for normalized values with efficient LRU eviction
type NormalizedValueCache struct {
mu sync.RWMutex
cache map[string]*LRUNode
maxSize int
head *LRUNode // Most recently used
tail *LRUNode // Least recently used
}
// NewNormalizedValueCache creates a new normalized value cache with configurable size
func NewNormalizedValueCache(maxSize int) *NormalizedValueCache {
if maxSize <= 0 {
maxSize = 1000 // Default size
}
// Create dummy head and tail nodes for easier list manipulation
head := &LRUNode{}
tail := &LRUNode{}
head.next = tail
tail.prev = head
return &NormalizedValueCache{
cache: make(map[string]*LRUNode),
maxSize: maxSize,
head: head,
tail: tail,
}
}
// Get retrieves a cached value and updates access order in O(1) time
func (c *NormalizedValueCache) Get(key string) ([]string, bool) {
c.mu.Lock()
defer c.mu.Unlock()
if node, exists := c.cache[key]; exists {
// Move to head (most recently used) - O(1) operation
c.moveToHead(node)
return node.value, true
}
return nil, false
}
// Set stores a value in the cache with size limit enforcement in O(1) time
func (c *NormalizedValueCache) Set(key string, value []string) {
c.mu.Lock()
defer c.mu.Unlock()
if node, exists := c.cache[key]; exists {
// Update existing node and move to head
node.value = value
c.moveToHead(node)
return
}
// Create new node
newNode := &LRUNode{
key: key,
value: value,
}
// If at max size, evict least recently used
if len(c.cache) >= c.maxSize {
c.evictLeastRecentlyUsed()
}
// Add to cache and move to head
c.cache[key] = newNode
c.addToHead(newNode)
}
// moveToHead moves a node to the head of the list (most recently used) - O(1)
func (c *NormalizedValueCache) moveToHead(node *LRUNode) {
c.removeNode(node)
c.addToHead(node)
}
// addToHead adds a node right after the head - O(1)
func (c *NormalizedValueCache) addToHead(node *LRUNode) {
node.prev = c.head
node.next = c.head.next
c.head.next.prev = node
c.head.next = node
}
// removeNode removes a node from the list - O(1)
func (c *NormalizedValueCache) removeNode(node *LRUNode) {
node.prev.next = node.next
node.next.prev = node.prev
}
// removeTail removes the last node before tail (least recently used) - O(1)
func (c *NormalizedValueCache) removeTail() *LRUNode {
lastNode := c.tail.prev
c.removeNode(lastNode)
return lastNode
}
// evictLeastRecentlyUsed removes the least recently used item in O(1) time
func (c *NormalizedValueCache) evictLeastRecentlyUsed() {
tail := c.removeTail()
delete(c.cache, tail.key)
}
// Global cache instance with size limit
var normalizedValueCache = NewNormalizedValueCache(1000)
// getCachedNormalizedValues returns cached normalized values or caches new ones
func getCachedNormalizedValues(value interface{}) []string {
// Create a string key for caching - more efficient than fmt.Sprintf
typeStr := reflect.TypeOf(value).String()
cacheKey := typeStr + ":" + fmt.Sprint(value)
// Try to get from cache
if cached, exists := normalizedValueCache.Get(cacheKey); exists {
return cached
}
// Not in cache, normalize and store
// Use the error-handling version for better error reporting
normalized, err := normalizeToStringSliceWithError(value)
if err != nil {
glog.Warningf("Failed to normalize policy value %v: %v", value, err)
// Fallback to string conversion for backward compatibility
normalized = []string{fmt.Sprintf("%v", value)}
}
normalizedValueCache.Set(cacheKey, normalized)
return normalized
}
// ConditionEvaluator evaluates policy conditions
type ConditionEvaluator interface {
Evaluate(conditionValue interface{}, contextValues []string) bool
}
// StringEqualsEvaluator evaluates StringEquals conditions
type StringEqualsEvaluator struct{}
func (e *StringEqualsEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
expectedValues := getCachedNormalizedValues(conditionValue)
for _, expected := range expectedValues {
for _, contextValue := range contextValues {
if expected == contextValue {
return true
}
}
}
return false
}
// StringNotEqualsEvaluator evaluates StringNotEquals conditions
type StringNotEqualsEvaluator struct{}
func (e *StringNotEqualsEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
expectedValues := getCachedNormalizedValues(conditionValue)
for _, expected := range expectedValues {
for _, contextValue := range contextValues {
if expected == contextValue {
return false
}
}
}
return true
}
// StringLikeEvaluator evaluates StringLike conditions (supports wildcards)
type StringLikeEvaluator struct{}
func (e *StringLikeEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
patterns := getCachedNormalizedValues(conditionValue)
for _, pattern := range patterns {
for _, contextValue := range contextValues {
if wildcard.MatchesWildcard(pattern, contextValue) {
return true
}
}
}
return false
}
// StringNotLikeEvaluator evaluates StringNotLike conditions
type StringNotLikeEvaluator struct{}
func (e *StringNotLikeEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
patterns := getCachedNormalizedValues(conditionValue)
for _, pattern := range patterns {
for _, contextValue := range contextValues {
if wildcard.MatchesWildcard(pattern, contextValue) {
return false
}
}
}
return true
}
// NumericEqualsEvaluator evaluates NumericEquals conditions
type NumericEqualsEvaluator struct{}
func (e *NumericEqualsEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
expectedValues := getCachedNormalizedValues(conditionValue)
for _, expected := range expectedValues {
expectedFloat, err := strconv.ParseFloat(expected, 64)
if err != nil {
continue
}
for _, contextValue := range contextValues {
contextFloat, err := strconv.ParseFloat(contextValue, 64)
if err != nil {
continue
}
if expectedFloat == contextFloat {
return true
}
}
}
return false
}
// NumericNotEqualsEvaluator evaluates NumericNotEquals conditions
type NumericNotEqualsEvaluator struct{}
func (e *NumericNotEqualsEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
expectedValues := getCachedNormalizedValues(conditionValue)
for _, expected := range expectedValues {
expectedFloat, err := strconv.ParseFloat(expected, 64)
if err != nil {
continue
}
for _, contextValue := range contextValues {
contextFloat, err := strconv.ParseFloat(contextValue, 64)
if err != nil {
continue
}
if expectedFloat == contextFloat {
return false
}
}
}
return true
}
// NumericLessThanEvaluator evaluates NumericLessThan conditions
type NumericLessThanEvaluator struct{}
func (e *NumericLessThanEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
expectedValues := getCachedNormalizedValues(conditionValue)
for _, expected := range expectedValues {
expectedFloat, err := strconv.ParseFloat(expected, 64)
if err != nil {
continue
}
for _, contextValue := range contextValues {
contextFloat, err := strconv.ParseFloat(contextValue, 64)
if err != nil {
continue
}
if contextFloat < expectedFloat {
return true
}
}
}
return false
}
// NumericLessThanEqualsEvaluator evaluates NumericLessThanEquals conditions
type NumericLessThanEqualsEvaluator struct{}
func (e *NumericLessThanEqualsEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
expectedValues := getCachedNormalizedValues(conditionValue)
for _, expected := range expectedValues {
expectedFloat, err := strconv.ParseFloat(expected, 64)
if err != nil {
continue
}
for _, contextValue := range contextValues {
contextFloat, err := strconv.ParseFloat(contextValue, 64)
if err != nil {
continue
}
if contextFloat <= expectedFloat {
return true
}
}
}
return false
}
// NumericGreaterThanEvaluator evaluates NumericGreaterThan conditions
type NumericGreaterThanEvaluator struct{}
func (e *NumericGreaterThanEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
expectedValues := getCachedNormalizedValues(conditionValue)
for _, expected := range expectedValues {
expectedFloat, err := strconv.ParseFloat(expected, 64)
if err != nil {
continue
}
for _, contextValue := range contextValues {
contextFloat, err := strconv.ParseFloat(contextValue, 64)
if err != nil {
continue
}
if contextFloat > expectedFloat {
return true
}
}
}
return false
}
// NumericGreaterThanEqualsEvaluator evaluates NumericGreaterThanEquals conditions
type NumericGreaterThanEqualsEvaluator struct{}
func (e *NumericGreaterThanEqualsEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
expectedValues := getCachedNormalizedValues(conditionValue)
for _, expected := range expectedValues {
expectedFloat, err := strconv.ParseFloat(expected, 64)
if err != nil {
continue
}
for _, contextValue := range contextValues {
contextFloat, err := strconv.ParseFloat(contextValue, 64)
if err != nil {
continue
}
if contextFloat >= expectedFloat {
return true
}
}
}
return false
}
// DateEqualsEvaluator evaluates DateEquals conditions
type DateEqualsEvaluator struct{}
func (e *DateEqualsEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
expectedValues := getCachedNormalizedValues(conditionValue)
for _, expected := range expectedValues {
expectedTime, err := time.Parse(time.RFC3339, expected)
if err != nil {
continue
}
for _, contextValue := range contextValues {
contextTime, err := time.Parse(time.RFC3339, contextValue)
if err != nil {
continue
}
if expectedTime.Equal(contextTime) {
return true
}
}
}
return false
}
// DateNotEqualsEvaluator evaluates DateNotEquals conditions
type DateNotEqualsEvaluator struct{}
func (e *DateNotEqualsEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
expectedValues := getCachedNormalizedValues(conditionValue)
for _, expected := range expectedValues {
expectedTime, err := time.Parse(time.RFC3339, expected)
if err != nil {
continue
}
for _, contextValue := range contextValues {
contextTime, err := time.Parse(time.RFC3339, contextValue)
if err != nil {
continue
}
if expectedTime.Equal(contextTime) {
return false
}
}
}
return true
}
// DateLessThanEvaluator evaluates DateLessThan conditions
type DateLessThanEvaluator struct{}
func (e *DateLessThanEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
expectedValues := getCachedNormalizedValues(conditionValue)
for _, expected := range expectedValues {
expectedTime, err := time.Parse(time.RFC3339, expected)
if err != nil {
continue
}
for _, contextValue := range contextValues {
contextTime, err := time.Parse(time.RFC3339, contextValue)
if err != nil {
continue
}
if contextTime.Before(expectedTime) {
return true
}
}
}
return false
}
// DateLessThanEqualsEvaluator evaluates DateLessThanEquals conditions
type DateLessThanEqualsEvaluator struct{}
func (e *DateLessThanEqualsEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
expectedValues := getCachedNormalizedValues(conditionValue)
for _, expected := range expectedValues {
expectedTime, err := time.Parse(time.RFC3339, expected)
if err != nil {
continue
}
for _, contextValue := range contextValues {
contextTime, err := time.Parse(time.RFC3339, contextValue)
if err != nil {
continue
}
if contextTime.Before(expectedTime) || contextTime.Equal(expectedTime) {
return true
}
}
}
return false
}
// DateGreaterThanEvaluator evaluates DateGreaterThan conditions
type DateGreaterThanEvaluator struct{}
func (e *DateGreaterThanEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
expectedValues := getCachedNormalizedValues(conditionValue)
for _, expected := range expectedValues {
expectedTime, err := time.Parse(time.RFC3339, expected)
if err != nil {
continue
}
for _, contextValue := range contextValues {
contextTime, err := time.Parse(time.RFC3339, contextValue)
if err != nil {
continue
}
if contextTime.After(expectedTime) {
return true
}
}
}
return false
}
// DateGreaterThanEqualsEvaluator evaluates DateGreaterThanEquals conditions
type DateGreaterThanEqualsEvaluator struct{}
func (e *DateGreaterThanEqualsEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
expectedValues := getCachedNormalizedValues(conditionValue)
for _, expected := range expectedValues {
expectedTime, err := time.Parse(time.RFC3339, expected)
if err != nil {
continue
}
for _, contextValue := range contextValues {
contextTime, err := time.Parse(time.RFC3339, contextValue)
if err != nil {
continue
}
if contextTime.After(expectedTime) || contextTime.Equal(expectedTime) {
return true
}
}
}
return false
}
// BoolEvaluator evaluates Bool conditions
type BoolEvaluator struct{}
func (e *BoolEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
expectedValues := getCachedNormalizedValues(conditionValue)
for _, expected := range expectedValues {
for _, contextValue := range contextValues {
if strings.ToLower(expected) == strings.ToLower(contextValue) {
return true
}
}
}
return false
}
// IpAddressEvaluator evaluates IpAddress conditions
type IpAddressEvaluator struct{}
func (e *IpAddressEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
expectedValues := getCachedNormalizedValues(conditionValue)
for _, expected := range expectedValues {
_, expectedNet, err := net.ParseCIDR(expected)
if err != nil {
// Try parsing as single IP
expectedIP := net.ParseIP(expected)
if expectedIP == nil {
glog.V(3).Infof("Failed to parse expected IP address: %s", expected)
continue
}
for _, contextValue := range contextValues {
contextIP := net.ParseIP(contextValue)
if contextIP == nil {
glog.V(3).Infof("Failed to parse IP address: %s", contextValue)
continue
}
if contextIP.Equal(expectedIP) {
return true
}
}
} else {
// CIDR network
for _, contextValue := range contextValues {
contextIP := net.ParseIP(contextValue)
if contextIP == nil {
glog.V(3).Infof("Failed to parse IP address: %s", contextValue)
continue
}
if expectedNet.Contains(contextIP) {
return true
}
}
}
}
return false
}
// NotIpAddressEvaluator evaluates NotIpAddress conditions
type NotIpAddressEvaluator struct{}
func (e *NotIpAddressEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
expectedValues := getCachedNormalizedValues(conditionValue)
for _, expected := range expectedValues {
_, expectedNet, err := net.ParseCIDR(expected)
if err != nil {
// Try parsing as single IP
expectedIP := net.ParseIP(expected)
if expectedIP == nil {
glog.V(3).Infof("Failed to parse expected IP address: %s", expected)
continue
}
for _, contextValue := range contextValues {
contextIP := net.ParseIP(contextValue)
if contextIP == nil {
glog.V(3).Infof("Failed to parse IP address: %s", contextValue)
continue
}
if contextIP.Equal(expectedIP) {
return false
}
}
} else {
// CIDR network
for _, contextValue := range contextValues {
contextIP := net.ParseIP(contextValue)
if contextIP == nil {
glog.V(3).Infof("Failed to parse IP address: %s", contextValue)
continue
}
if expectedNet.Contains(contextIP) {
return false
}
}
}
}
return true
}
// ArnEqualsEvaluator evaluates ArnEquals conditions
type ArnEqualsEvaluator struct{}
func (e *ArnEqualsEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
expectedValues := getCachedNormalizedValues(conditionValue)
for _, expected := range expectedValues {
for _, contextValue := range contextValues {
if expected == contextValue {
return true
}
}
}
return false
}
// ArnLikeEvaluator evaluates ArnLike conditions
type ArnLikeEvaluator struct{}
func (e *ArnLikeEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
patterns := getCachedNormalizedValues(conditionValue)
for _, pattern := range patterns {
for _, contextValue := range contextValues {
if wildcard.MatchesWildcard(pattern, contextValue) {
return true
}
}
}
return false
}
// NullEvaluator evaluates Null conditions
type NullEvaluator struct{}
func (e *NullEvaluator) Evaluate(conditionValue interface{}, contextValues []string) bool {
expectedValues := getCachedNormalizedValues(conditionValue)
for _, expected := range expectedValues {
expectedBool := strings.ToLower(expected) == "true"
contextExists := len(contextValues) > 0
if expectedBool && !contextExists {
return true // Key should be null and it is
}
if !expectedBool && contextExists {
return true // Key should not be null and it isn't
}
}
return false
}
// GetConditionEvaluator returns the appropriate evaluator for a condition operator
func GetConditionEvaluator(operator string) (ConditionEvaluator, error) {
switch operator {
case "StringEquals":
return &StringEqualsEvaluator{}, nil
case "StringNotEquals":
return &StringNotEqualsEvaluator{}, nil
case "StringLike":
return &StringLikeEvaluator{}, nil
case "StringNotLike":
return &StringNotLikeEvaluator{}, nil
case "NumericEquals":
return &NumericEqualsEvaluator{}, nil
case "NumericNotEquals":
return &NumericNotEqualsEvaluator{}, nil
case "NumericLessThan":
return &NumericLessThanEvaluator{}, nil
case "NumericLessThanEquals":
return &NumericLessThanEqualsEvaluator{}, nil
case "NumericGreaterThan":
return &NumericGreaterThanEvaluator{}, nil
case "NumericGreaterThanEquals":
return &NumericGreaterThanEqualsEvaluator{}, nil
case "DateEquals":
return &DateEqualsEvaluator{}, nil
case "DateNotEquals":
return &DateNotEqualsEvaluator{}, nil
case "DateLessThan":
return &DateLessThanEvaluator{}, nil
case "DateLessThanEquals":
return &DateLessThanEqualsEvaluator{}, nil
case "DateGreaterThan":
return &DateGreaterThanEvaluator{}, nil
case "DateGreaterThanEquals":
return &DateGreaterThanEqualsEvaluator{}, nil
case "Bool":
return &BoolEvaluator{}, nil
case "IpAddress":
return &IpAddressEvaluator{}, nil
case "NotIpAddress":
return &NotIpAddressEvaluator{}, nil
case "ArnEquals":
return &ArnEqualsEvaluator{}, nil
case "ArnLike":
return &ArnLikeEvaluator{}, nil
case "Null":
return &NullEvaluator{}, nil
default:
return nil, fmt.Errorf("unsupported condition operator: %s", operator)
}
}
// ExistingObjectTagPrefix is the prefix for S3 policy condition keys
const ExistingObjectTagPrefix = "s3:ExistingObjectTag/"
// getConditionContextValue resolves the value(s) for a condition key.
// For s3:ExistingObjectTag/<key> conditions, it looks up the tag in objectEntry.
// For other condition keys, it looks up the value in contextValues.
func getConditionContextValue(key string, contextValues map[string][]string, objectEntry map[string][]byte) []string {
if strings.HasPrefix(key, ExistingObjectTagPrefix) {
tagKey := key[len(ExistingObjectTagPrefix):]
if tagKey == "" {
return []string{} // Invalid: empty tag key
}
metadataKey := s3_constants.AmzObjectTaggingPrefix + tagKey
if objectEntry != nil {
if tagValue, exists := objectEntry[metadataKey]; exists {
return []string{string(tagValue)}
}
}
return []string{}
}
if vals, exists := contextValues[key]; exists {
return vals
}
return []string{}
}
// EvaluateConditions evaluates all conditions in a policy statement
// objectEntry is the object's metadata from entry.Extended (can be nil)
// claims are JWT claims for jwt:* policy variables (can be nil)
func EvaluateConditions(conditions PolicyConditions, contextValues map[string][]string, objectEntry map[string][]byte, claims map[string]interface{}) bool {
if len(conditions) == 0 {
return true // No conditions means always true
}
for operator, conditionMap := range conditions {
conditionEvaluator, err := GetConditionEvaluator(operator)
if err != nil {
glog.Warningf("Unsupported condition operator: %s", operator)
continue
}
for key, value := range conditionMap {
contextVals := getConditionContextValue(key, contextValues, objectEntry)
// Substitute variables in expected values
expectedValues := value.Strings()
substitutedValues := make([]string, len(expectedValues))
for i, v := range expectedValues {
substitutedValues[i] = SubstituteVariables(v, contextValues, claims)
}
// Pass substituted values (casted to interface{} to match signature if needed, or update evaluators to accept []string)
// The evaluators take interface{}, but getCachedNormalizedValues handles []string.
if !conditionEvaluator.Evaluate(substitutedValues, contextVals) {
return false // If any condition fails, the whole condition block fails
}
}
}
return true
}