mirror of https://github.com/minio/minio.git
				
				
				
			
		
			
	
	
		
			323 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Go
		
	
	
	
		
		
			
		
	
	
			323 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Go
		
	
	
	
|  | /* | ||
|  |  * Minio Cloud Storage, (C) 2016 Minio, Inc. | ||
|  |  * | ||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||
|  |  * you may not use this file except in compliance with the License. | ||
|  |  * You may obtain a copy of the License at | ||
|  |  * | ||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0
 | ||
|  |  * | ||
|  |  * Unless required by applicable law or agreed to in writing, software | ||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
|  |  * See the License for the specific language governing permissions and | ||
|  |  * limitations under the License. | ||
|  |  */ | ||
|  | 
 | ||
|  | package cmd | ||
|  | 
 | ||
|  | import "strings" | ||
|  | 
 | ||
|  | // List of valid event types.
 | ||
|  | var suppportedEventTypes = map[string]struct{}{ | ||
|  | 	// Object created event types.
 | ||
|  | 	"s3:ObjectCreated:*":                       {}, | ||
|  | 	"s3:ObjectCreated:Put":                     {}, | ||
|  | 	"s3:ObjectCreated:Post":                    {}, | ||
|  | 	"s3:ObjectCreated:Copy":                    {}, | ||
|  | 	"s3:ObjectCreated:CompleteMultipartUpload": {}, | ||
|  | 	// Object removed event types.
 | ||
|  | 	"s3:ObjectRemoved:*":      {}, | ||
|  | 	"s3:ObjectRemoved:Delete": {}, | ||
|  | } | ||
|  | 
 | ||
|  | // checkEvent - checks if an event is supported.
 | ||
|  | func checkEvent(event string) APIErrorCode { | ||
|  | 	_, ok := suppportedEventTypes[event] | ||
|  | 	if !ok { | ||
|  | 		return ErrEventNotification | ||
|  | 	} | ||
|  | 	return ErrNone | ||
|  | } | ||
|  | 
 | ||
|  | // checkEvents - checks given list of events if all of them are valid.
 | ||
|  | // given if one of them is invalid, this function returns an error.
 | ||
|  | func checkEvents(events []string) APIErrorCode { | ||
|  | 	for _, event := range events { | ||
|  | 		if s3Error := checkEvent(event); s3Error != ErrNone { | ||
|  | 			return s3Error | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return ErrNone | ||
|  | } | ||
|  | 
 | ||
|  | // Valid if filterName is 'prefix'.
 | ||
|  | func isValidFilterNamePrefix(filterName string) bool { | ||
|  | 	return "prefix" == filterName | ||
|  | } | ||
|  | 
 | ||
|  | // Valid if filterName is 'suffix'.
 | ||
|  | func isValidFilterNameSuffix(filterName string) bool { | ||
|  | 	return "suffix" == filterName | ||
|  | } | ||
|  | 
 | ||
|  | // Is this a valid filterName? - returns true if valid.
 | ||
|  | func isValidFilterName(filterName string) bool { | ||
|  | 	return isValidFilterNamePrefix(filterName) || isValidFilterNameSuffix(filterName) | ||
|  | } | ||
|  | 
 | ||
|  | // checkFilterRules - checks given list of filter rules if all of them are valid.
 | ||
|  | func checkFilterRules(filterRules []filterRule) APIErrorCode { | ||
|  | 	ruleSetMap := make(map[string]string) | ||
|  | 	// Validate all filter rules.
 | ||
|  | 	for _, filterRule := range filterRules { | ||
|  | 		// Unknown filter rule name found, returns an appropriate error.
 | ||
|  | 		if !isValidFilterName(filterRule.Name) { | ||
|  | 			return ErrFilterNameInvalid | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Filter names should not be set twice per notification service
 | ||
|  | 		// configuration, if found return an appropriate error.
 | ||
|  | 		if _, ok := ruleSetMap[filterRule.Name]; ok { | ||
|  | 			if isValidFilterNamePrefix(filterRule.Name) { | ||
|  | 				return ErrFilterNamePrefix | ||
|  | 			} else if isValidFilterNameSuffix(filterRule.Name) { | ||
|  | 				return ErrFilterNameSuffix | ||
|  | 			} else { | ||
|  | 				return ErrFilterNameInvalid | ||
|  | 			} | ||
|  | 		} | ||
|  | 
 | ||
|  | 		if !IsValidObjectPrefix(filterRule.Value) { | ||
|  | 			return ErrFilterValueInvalid | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Set the new rule name to keep track of duplicates.
 | ||
|  | 		ruleSetMap[filterRule.Name] = filterRule.Value | ||
|  | 	} | ||
|  | 	// Success all prefixes validated.
 | ||
|  | 	return ErrNone | ||
|  | } | ||
|  | 
 | ||
|  | // Checks validity of input ARN for a given arnType.
 | ||
|  | func checkARN(arn, arnType string) APIErrorCode { | ||
|  | 	if !strings.HasPrefix(arn, arnType) { | ||
|  | 		return ErrARNNotification | ||
|  | 	} | ||
|  | 	if !strings.HasPrefix(arn, arnType+serverConfig.GetRegion()+":") { | ||
|  | 		return ErrRegionNotification | ||
|  | 	} | ||
|  | 	account := strings.SplitN(strings.TrimPrefix(arn, arnType+serverConfig.GetRegion()+":"), ":", 2) | ||
|  | 	switch len(account) { | ||
|  | 	case 1: | ||
|  | 		// This means ARN is malformed, account should have min of 2elements.
 | ||
|  | 		return ErrARNNotification | ||
|  | 	case 2: | ||
|  | 		// Account topic id or topic name cannot be empty.
 | ||
|  | 		if account[0] == "" || account[1] == "" { | ||
|  | 			return ErrARNNotification | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return ErrNone | ||
|  | } | ||
|  | 
 | ||
|  | // checkQueueARN - check if the queue arn is valid.
 | ||
|  | func checkQueueARN(queueARN string) APIErrorCode { | ||
|  | 	return checkARN(queueARN, minioSqs) | ||
|  | } | ||
|  | 
 | ||
|  | // checkTopicARN - check if the topic arn is valid.
 | ||
|  | func checkTopicARN(topicARN string) APIErrorCode { | ||
|  | 	return checkARN(topicARN, minioTopic) | ||
|  | } | ||
|  | 
 | ||
|  | // Returns true if the topicARN is for an Minio sns listen type.
 | ||
|  | func isMinioSNS(topicARN arnTopic) bool { | ||
|  | 	return strings.HasSuffix(topicARN.Type, snsTypeMinio) | ||
|  | } | ||
|  | 
 | ||
|  | // isMinioSNSConfigured - verifies if one topic ARN is valid and is enabled.
 | ||
|  | func isMinioSNSConfigured(topicARN string, topicConfigs []topicConfig) bool { | ||
|  | 	for _, topicConfig := range topicConfigs { | ||
|  | 		// Validate if topic ARN is already enabled.
 | ||
|  | 		if topicARN == topicConfig.TopicARN { | ||
|  | 			return true | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return false | ||
|  | } | ||
|  | 
 | ||
|  | // Validate if we recognize the queue type.
 | ||
|  | func isValidQueue(sqsARN arnSQS) bool { | ||
|  | 	amqpQ := isAMQPQueue(sqsARN)       // Is amqp queue?.
 | ||
|  | 	elasticQ := isElasticQueue(sqsARN) // Is elastic queue?.
 | ||
|  | 	redisQ := isRedisQueue(sqsARN)     // Is redis queue?.
 | ||
|  | 	return amqpQ || elasticQ || redisQ | ||
|  | } | ||
|  | 
 | ||
|  | // Validate if we recognize the topic type.
 | ||
|  | func isValidTopic(topicARN arnTopic) bool { | ||
|  | 	return isMinioSNS(topicARN) // Is minio topic?.
 | ||
|  | } | ||
|  | 
 | ||
|  | // Validates account id for input queue ARN.
 | ||
|  | func isValidQueueID(queueARN string) bool { | ||
|  | 	// Unmarshals QueueARN into structured object.
 | ||
|  | 	sqsARN := unmarshalSqsARN(queueARN) | ||
|  | 	// AMQP queue.
 | ||
|  | 	if isAMQPQueue(sqsARN) { | ||
|  | 		amqpN := serverConfig.GetAMQPNotifyByID(sqsARN.AccountID) | ||
|  | 		return amqpN.Enable && amqpN.URL != "" | ||
|  | 	} else if isElasticQueue(sqsARN) { // Elastic queue.
 | ||
|  | 		elasticN := serverConfig.GetElasticSearchNotifyByID(sqsARN.AccountID) | ||
|  | 		return elasticN.Enable && elasticN.URL != "" | ||
|  | 
 | ||
|  | 	} else if isRedisQueue(sqsARN) { // Redis queue.
 | ||
|  | 		redisN := serverConfig.GetRedisNotifyByID(sqsARN.AccountID) | ||
|  | 		return redisN.Enable && redisN.Addr != "" | ||
|  | 	} | ||
|  | 	return false | ||
|  | } | ||
|  | 
 | ||
|  | // Check - validates queue configuration and returns error if any.
 | ||
|  | func checkQueueConfig(qConfig queueConfig) APIErrorCode { | ||
|  | 	// Check queue arn is valid.
 | ||
|  | 	if s3Error := checkQueueARN(qConfig.QueueARN); s3Error != ErrNone { | ||
|  | 		return s3Error | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Unmarshals QueueARN into structured object.
 | ||
|  | 	sqsARN := unmarshalSqsARN(qConfig.QueueARN) | ||
|  | 	// Validate if sqsARN requested any of the known supported queues.
 | ||
|  | 	if !isValidQueue(sqsARN) { | ||
|  | 		return ErrARNNotification | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Validate if the account ID is correct.
 | ||
|  | 	if !isValidQueueID(qConfig.QueueARN) { | ||
|  | 		return ErrARNNotification | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Check if valid events are set in queue config.
 | ||
|  | 	if s3Error := checkEvents(qConfig.Events); s3Error != ErrNone { | ||
|  | 		return s3Error | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Check if valid filters are set in queue config.
 | ||
|  | 	if s3Error := checkFilterRules(qConfig.Filter.Key.FilterRules); s3Error != ErrNone { | ||
|  | 		return s3Error | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Success.
 | ||
|  | 	return ErrNone | ||
|  | } | ||
|  | 
 | ||
|  | // Check - validates queue configuration and returns error if any.
 | ||
|  | func checkTopicConfig(tConfig topicConfig) APIErrorCode { | ||
|  | 	// Check queue arn is valid.
 | ||
|  | 	if s3Error := checkTopicARN(tConfig.TopicARN); s3Error != ErrNone { | ||
|  | 		return s3Error | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Unmarshals QueueARN into structured object.
 | ||
|  | 	topicARN := unmarshalTopicARN(tConfig.TopicARN) | ||
|  | 	// Validate if topicARN requested any of the known supported queues.
 | ||
|  | 	if !isValidTopic(topicARN) { | ||
|  | 		return ErrARNNotification | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Check if valid events are set in queue config.
 | ||
|  | 	if s3Error := checkEvents(tConfig.Events); s3Error != ErrNone { | ||
|  | 		return s3Error | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Check if valid filters are set in queue config.
 | ||
|  | 	if s3Error := checkFilterRules(tConfig.Filter.Key.FilterRules); s3Error != ErrNone { | ||
|  | 		return s3Error | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Success.
 | ||
|  | 	return ErrNone | ||
|  | } | ||
|  | 
 | ||
|  | // Validates all incoming queue configs, checkQueueConfig validates if the
 | ||
|  | // input fields for each queues is not malformed and has valid configuration
 | ||
|  | // information.  If validation fails bucket notifications are not enabled.
 | ||
|  | func validateQueueConfigs(queueConfigs []queueConfig) APIErrorCode { | ||
|  | 	for _, qConfig := range queueConfigs { | ||
|  | 		if s3Error := checkQueueConfig(qConfig); s3Error != ErrNone { | ||
|  | 			return s3Error | ||
|  | 		} | ||
|  | 	} | ||
|  | 	// Success.
 | ||
|  | 	return ErrNone | ||
|  | } | ||
|  | 
 | ||
|  | // Validates all incoming topic configs, checkTopicConfig validates if the
 | ||
|  | // input fields for each queues is not malformed and has valid configuration
 | ||
|  | // information.  If validation fails bucket notifications are not enabled.
 | ||
|  | func validateTopicConfigs(topicConfigs []topicConfig) APIErrorCode { | ||
|  | 	for _, tConfig := range topicConfigs { | ||
|  | 		if s3Error := checkTopicConfig(tConfig); s3Error != ErrNone { | ||
|  | 			return s3Error | ||
|  | 		} | ||
|  | 	} | ||
|  | 	// Success.
 | ||
|  | 	return ErrNone | ||
|  | } | ||
|  | 
 | ||
|  | // Validates all the bucket notification configuration for their validity,
 | ||
|  | // if one of the config is malformed or has invalid data it is rejected.
 | ||
|  | // Configuration is never applied partially.
 | ||
|  | func validateNotificationConfig(nConfig notificationConfig) APIErrorCode { | ||
|  | 	if s3Error := validateQueueConfigs(nConfig.QueueConfigs); s3Error != ErrNone { | ||
|  | 		return s3Error | ||
|  | 	} | ||
|  | 	if s3Error := validateTopicConfigs(nConfig.TopicConfigs); s3Error != ErrNone { | ||
|  | 		return s3Error | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Add validation for other configurations.
 | ||
|  | 	return ErrNone | ||
|  | } | ||
|  | 
 | ||
|  | // Unmarshals input value of AWS ARN format into minioTopic object.
 | ||
|  | // Returned value represents minio topic type, currently supported are
 | ||
|  | // - listen
 | ||
|  | func unmarshalTopicARN(topicARN string) arnTopic { | ||
|  | 	topic := arnTopic{} | ||
|  | 	if !strings.HasPrefix(topicARN, minioTopic+serverConfig.GetRegion()+":") { | ||
|  | 		return topic | ||
|  | 	} | ||
|  | 	topicType := strings.TrimPrefix(topicARN, minioTopic+serverConfig.GetRegion()+":") | ||
|  | 	switch { | ||
|  | 	case strings.HasSuffix(topicType, snsTypeMinio): | ||
|  | 		topic.Type = snsTypeMinio | ||
|  | 	} // Add more topic here.
 | ||
|  | 	topic.AccountID = strings.TrimSuffix(topicType, ":"+topic.Type) | ||
|  | 	return topic | ||
|  | } | ||
|  | 
 | ||
|  | // Unmarshals input value of AWS ARN format into minioSqs object.
 | ||
|  | // Returned value represents minio sqs types, currently supported are
 | ||
|  | // - amqp
 | ||
|  | // - elasticsearch
 | ||
|  | // - redis
 | ||
|  | func unmarshalSqsARN(queueARN string) (mSqs arnSQS) { | ||
|  | 	mSqs = arnSQS{} | ||
|  | 	if !strings.HasPrefix(queueARN, minioSqs+serverConfig.GetRegion()+":") { | ||
|  | 		return mSqs | ||
|  | 	} | ||
|  | 	sqsType := strings.TrimPrefix(queueARN, minioSqs+serverConfig.GetRegion()+":") | ||
|  | 	switch { | ||
|  | 	case strings.HasSuffix(sqsType, queueTypeAMQP): | ||
|  | 		mSqs.Type = queueTypeAMQP | ||
|  | 	case strings.HasSuffix(sqsType, queueTypeElastic): | ||
|  | 		mSqs.Type = queueTypeElastic | ||
|  | 	case strings.HasSuffix(sqsType, queueTypeRedis): | ||
|  | 		mSqs.Type = queueTypeRedis | ||
|  | 	} // Add more queues here.
 | ||
|  | 	mSqs.AccountID = strings.TrimSuffix(sqsType, ":"+mSqs.Type) | ||
|  | 	return mSqs | ||
|  | } |