package channels import ( "fmt" "regexp" "time" "github.com/google/uuid" ) const ( maxDatasyncOutageWindow = 3 * 24 * time.Hour MinExpireBufferDuration = maxDatasyncOutageWindow MinRotationIntervalDuration = maxDatasyncOutageWindow // BearerTokenSecretSuffix is appended to channel names to create the secret that helm workloads mount. BearerTokenSecretSuffix = "-encryption-token" // validChannelNameLength ensures that the BearerTokenSecretSuffix can be appended to the channel name, // without exceeding the 63 character maximum length for k8s for names. validChannelNameLength = 63 - len(BearerTokenSecretSuffix) ) var ( // The channel name must conform to k8s naming rules. reValidChannelName = regexp.MustCompile("^[a-z]([-]?[a-z0-9])+$") // reValidSecretManagerLink verifies the following format: "projects/${gcp_project_name}/secrets/${secret_manager_secret_resource_id}" reValidSecretManagerLink = regexp.MustCompile("^projects/[a-z]([-]?[a-z0-9])+/secrets/[a-z]([-]?[a-z0-9])+$") ) // Tiny sanity check in lieu of a dedicated test. func init() { if secondStr != fmt.Sprintf("%d", time.Second) { panic("secondStr does not equal time.Second") } } /* Channel validation */ func (c *Channel) validateRequiredFields() error { switch { case c.Name == "": return fmt.Errorf("missing Name") case len(c.Name) > validChannelNameLength: return fmt.Errorf("Name length must be less than %d characters", validChannelNameLength) case !reValidChannelName.MatchString(c.Name): return fmt.Errorf("invalid Name") case c.Team == "": return fmt.Errorf("missing Team") case c.Description == "": return fmt.Errorf("missing Description") } return nil } func (c *Channel) validateCreate() error { // only set the optional fields when validating for creation. c.setDefaults() switch { case c.ID != uuid.Nil: return fmt.Errorf("ID is readonly") case !c.CreatedAt.IsZero(): return fmt.Errorf("CreatedAt is readonly") case c.ExpireBufferDuration < MinExpireBufferDuration: return fmt.Errorf("ExpireBufferDuration must be greater than or equal to %v", MinExpireBufferDuration) case c.RotationIntervalDuration < MinRotationIntervalDuration: return fmt.Errorf("RotationIntervalDuration must be greater than or equal to %v", MinRotationIntervalDuration) } return c.validateRequiredFields() } func (c *Channel) validateScan() error { // MinExpireBufferDuration and MinRotationIntervalDuration are only checked during Channel creation. // These values can change, so they shouldn't be checked when Channels are scanned. switch { case c.ID == uuid.Nil: return fmt.Errorf("missing ID") case c.ExpireBufferDuration == 0: return fmt.Errorf("missing ExpireBufferDuration") case c.RotationIntervalDuration == 0: return fmt.Errorf("missing RotationIntervalDuration") case c.CreatedAt.IsZero(): return fmt.Errorf("missing CreatedAt") } return c.validateRequiredFields() } /* ChannelUpdateRequest validation */ func (r *ChannelUpdateRequest) validate() error { if r.Team == nil && r.Description == nil && r.ExpireBufferDuration == nil && r.RotationIntervalDuration == nil { return fmt.Errorf("nothing to update") } switch { case r.Team != nil && *r.Team == "": return fmt.Errorf("team is an empty string") case r.Description != nil && *r.Description == "": return fmt.Errorf("description is an empty string") case r.ExpireBufferDuration != nil && *r.ExpireBufferDuration < MinExpireBufferDuration: return fmt.Errorf("expire buffer duration must be greater than or equal to %v", MinExpireBufferDuration) case r.RotationIntervalDuration != nil && *r.RotationIntervalDuration < MinRotationIntervalDuration: return fmt.Errorf("rotation interval duration must be greater than or equal to %v", MinRotationIntervalDuration) } return nil } /* ChannelKeyVersion validation */ func (ckv *ChannelKeyVersion) validateRequiredFields() error { switch { case ckv.ChannelID == uuid.Nil: return fmt.Errorf("missing ChannelID") case ckv.BannerEdgeID == uuid.Nil: return fmt.Errorf("missing BannerEdgeId") case ckv.Version < 1: return fmt.Errorf("Version must be greater than 0") case ckv.SecretManagerLink == "": return fmt.Errorf("missing SecretManagerLink") case !reValidSecretManagerLink.MatchString(ckv.SecretManagerLink): return fmt.Errorf("invalid SecretManagerLink format") } return nil } func (ckv *ChannelKeyVersion) validateCreate() error { switch { case ckv.ID != uuid.Nil: return fmt.Errorf("ID is readonly") case !ckv.CreatedAt.IsZero(): return fmt.Errorf("CreatedAt is readonly") case !ckv.ExpireAt.IsZero(): return fmt.Errorf("ExpireAt is readonly") case !ckv.RotateAt.IsZero(): return fmt.Errorf("RotateAt is readonly") } return ckv.validateRequiredFields() } func (ckv *ChannelKeyVersion) validateScan() error { // The time checks ensure the query's `*_at` columns were scanned in the correct order. // RotateAt can be zero. switch { case ckv.ID == uuid.Nil: return fmt.Errorf("missing ID") case ckv.CreatedAt.IsZero(): return fmt.Errorf("missing CreatedAt") case ckv.ExpireAt.IsZero(): return fmt.Errorf("missing ExpireAt") case ckv.CreatedAt.After(ckv.ExpireAt): return fmt.Errorf("CreatedAt is greater than ExpireAt") case ckv.RotateAt.After(ckv.ExpireAt): return fmt.Errorf("RotateAt is greater than ExpireAt") } return ckv.validateRequiredFields() } /* ParsedHelmConfigChannels */ func (config *ParsedHelmConfigChannels) validateParsed() error { for _, name := range config.Names() { switch { case name == "": return fmt.Errorf("empty channel Name") case len(name) > validChannelNameLength: return fmt.Errorf("invalid channel Name with length greater than %d characters", validChannelNameLength) case !reValidChannelName.MatchString(name): return fmt.Errorf("invalid channel Name: %q", name) } } return nil }