* 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.
756 lines
21 KiB
Go
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
|
|
}
|