/* Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package configtx import ( "regexp" "github.com/golang/protobuf/proto" cb "github.com/hyperledger/fabric-protos-go/common" "github.com/hyperledger/fabric/common/flogging" "github.com/hyperledger/fabric/common/policies" "github.com/hyperledger/fabric/protoutil" "github.com/pkg/errors" ) var logger = flogging.MustGetLogger("common.configtx") // Constraints for valid channel and config IDs var ( ChannelAllowedChars = "[a-z][a-z0-9.-]*" configAllowedChars = "[a-zA-Z0-9.-]+" MaxLength = 249 illegalNames = map[string]struct{}{ ".": {}, "..": {}, } ) // ValidatorImpl implements the Validator interface type ValidatorImpl struct { channelID string sequence uint64 configMap map[string]comparable configProto *cb.Config namespace string pm policies.Manager } // validateConfigID makes sure that the config element names (ie map key of // ConfigGroup) comply with the following restrictions // 1. Contain only ASCII alphanumerics, dots '.', dashes '-' // 2. Are shorter than 250 characters. // 3. Are not the strings "." or "..". func validateConfigID(configID string) error { re, _ := regexp.Compile(configAllowedChars) // Length if len(configID) <= 0 { return errors.New("config ID illegal, cannot be empty") } if len(configID) > MaxLength { return errors.Errorf("config ID illegal, cannot be longer than %d", MaxLength) } // Illegal name if _, ok := illegalNames[configID]; ok { return errors.Errorf("name '%s' for config ID is not allowed", configID) } // Illegal characters matched := re.FindString(configID) if len(matched) != len(configID) { return errors.Errorf("config ID '%s' contains illegal characters", configID) } return nil } // ValidateChannelID makes sure that proposed channel IDs comply with the // following restrictions: // 1. Contain only lower case ASCII alphanumerics, dots '.', and dashes '-' // 2. Are shorter than 250 characters. // 3. Start with a letter // // This is the intersection of the Kafka restrictions and CouchDB restrictions // with the following exception: '.' is converted to '_' in the CouchDB naming // This is to accommodate existing channel names with '.', especially in the // behave tests which rely on the dot notation for their sluggification. // // note: this function is a copy of the same in core/tx/endorser/parser.go func ValidateChannelID(channelID string) error { re, _ := regexp.Compile(ChannelAllowedChars) // Length if len(channelID) <= 0 { return errors.Errorf("channel ID illegal, cannot be empty") } if len(channelID) > MaxLength { return errors.Errorf("channel ID illegal, cannot be longer than %d", MaxLength) } // Illegal characters matched := re.FindString(channelID) if len(matched) != len(channelID) { return errors.Errorf("'%s' contains illegal characters", channelID) } return nil } // NewValidatorImpl constructs a new implementation of the Validator interface. func NewValidatorImpl(channelID string, config *cb.Config, namespace string, pm policies.Manager) (*ValidatorImpl, error) { if config == nil { return nil, errors.Errorf("nil config parameter") } if config.ChannelGroup == nil { return nil, errors.Errorf("nil channel group") } if err := ValidateChannelID(channelID); err != nil { return nil, errors.Errorf("bad channel ID: %s", err) } configMap, err := mapConfig(config.ChannelGroup, namespace) if err != nil { return nil, errors.Errorf("error converting config to map: %s", err) } return &ValidatorImpl{ namespace: namespace, pm: pm, sequence: config.Sequence, configMap: configMap, channelID: channelID, configProto: config, }, nil } // ProposeConfigUpdate takes in an Envelope of type CONFIG_UPDATE and produces a // ConfigEnvelope to be used as the Envelope Payload Data of a CONFIG message func (vi *ValidatorImpl) ProposeConfigUpdate(configtx *cb.Envelope) (*cb.ConfigEnvelope, error) { return vi.proposeConfigUpdate(configtx) } func (vi *ValidatorImpl) proposeConfigUpdate(configtx *cb.Envelope) (*cb.ConfigEnvelope, error) { configUpdateEnv, err := protoutil.EnvelopeToConfigUpdate(configtx) if err != nil { return nil, errors.Errorf("error converting envelope to config update: %s", err) } configMap, err := vi.authorizeUpdate(configUpdateEnv) if err != nil { return nil, errors.Errorf("error authorizing update: %s", err) } channelGroup, err := configMapToConfig(configMap, vi.namespace) if err != nil { return nil, errors.Errorf("could not turn configMap back to channelGroup: %s", err) } return &cb.ConfigEnvelope{ Config: &cb.Config{ Sequence: vi.sequence + 1, ChannelGroup: channelGroup, }, LastUpdate: configtx, }, nil } // Validate simulates applying a ConfigEnvelope to become the new config func (vi *ValidatorImpl) Validate(configEnv *cb.ConfigEnvelope) error { if configEnv == nil { return errors.Errorf("config envelope is nil") } if configEnv.Config == nil { return errors.Errorf("config envelope has nil config") } if configEnv.Config.Sequence != vi.sequence+1 { return errors.Errorf("config currently at sequence %d, cannot validate config at sequence %d", vi.sequence, configEnv.Config.Sequence) } configUpdateEnv, err := protoutil.EnvelopeToConfigUpdate(configEnv.LastUpdate) if err != nil { return err } configMap, err := vi.authorizeUpdate(configUpdateEnv) if err != nil { return err } channelGroup, err := configMapToConfig(configMap, vi.namespace) if err != nil { return errors.Errorf("could not turn configMap back to channelGroup: %s", err) } // reflect.Equal will not work here, because it considers nil and empty maps as different if !proto.Equal(channelGroup, configEnv.Config.ChannelGroup) { return errors.Errorf("ConfigEnvelope LastUpdate did not produce the supplied config result") } return nil } // ChannelID retrieves the channel ID associated with this manager func (vi *ValidatorImpl) ChannelID() string { return vi.channelID } // Sequence returns the sequence number of the config func (vi *ValidatorImpl) Sequence() uint64 { return vi.sequence } // ConfigProto returns the config proto which initialized this Validator func (vi *ValidatorImpl) ConfigProto() *cb.Config { return vi.configProto }