1
0
mirror of https://github.com/openshift/installer.git synced 2026-02-07 03:47:13 +01:00
Files
installer/pkg/validate/validate.go
staebler 64bb638a85 validate: remove unused prefixError function
We are using github.com/pkg/errors to handle error wrapping.
The prefixError function is obsolete.
2018-11-11 13:54:02 -05:00

256 lines
6.3 KiB
Go

// Package validate contains validation utilities for installer types.
package validate
import (
"encoding/json"
"errors"
"fmt"
"net"
"regexp"
"strconv"
"strings"
"unicode/utf8"
)
// DomainName checks if the given string is a valid domain name and returns an error if not.
func DomainName(v string) error {
if err := nonEmpty(v); err != nil {
return err
}
split := strings.Split(v, ".")
for i, segment := range split {
// Trailing dot is OK
if len(segment) == 0 && i == len(split)-1 {
continue
}
if !isMatch("^[a-zA-Z0-9-]{1,63}$", segment) {
return errors.New("invalid domain name")
}
}
return nil
}
// Email checks if the given string is a valid email address and returns an error if not.
func Email(v string) error {
if err := nonEmpty(v); err != nil {
return err
}
invalidError := errors.New("invalid email address")
split := strings.Split(v, "@")
if len(split) != 2 {
return invalidError
}
localPart := split[0]
domain := split[1]
if nonEmpty(localPart) != nil {
return invalidError
}
// No whitespace allowed in local-part
if isMatch(`\s`, localPart) {
return invalidError
}
return DomainName(domain)
}
// JSON validates that the given data is valid JSON.
func JSON(data []byte) error {
var dummy interface{}
return json.Unmarshal(data, &dummy)
}
func isMatch(re string, v string) bool {
return regexp.MustCompile(re).MatchString(v)
}
// ClusterName checks if the given string is a valid name for a cluster and returns an error if not.
func ClusterName(v string) error {
if err := nonEmpty(v); err != nil {
return err
}
if length := utf8.RuneCountInString(v); length < 1 || length > 253 {
return errors.New("must be between 1 and 253 characters")
}
if strings.ToLower(v) != v {
return errors.New("must be lower case")
}
if !isMatch("^[a-z0-9-.]*$", v) {
return errors.New("only lower case alphanumeric [a-z0-9], dashes and dots are allowed")
}
isAlphaNum := regexp.MustCompile("^[a-z0-9]$").MatchString
// If we got this far, we know the string is ASCII and has at least one character
if !isAlphaNum(v[:1]) || !isAlphaNum(v[len(v)-1:]) {
return errors.New("must start and end with a lower case alphanumeric character [a-z0-9]")
}
for _, segment := range strings.Split(v, ".") {
// Each segment can have up to 63 characters
if utf8.RuneCountInString(segment) > 63 {
return errors.New("no segment between dots can be more than 63 characters")
}
if !isAlphaNum(segment[:1]) || !isAlphaNum(segment[len(segment)-1:]) {
return errors.New("segments between dots must start and end with a lower case alphanumeric character [a-z0-9]")
}
}
return nil
}
// nonEmpty checks if the given string contains at least one non-whitespace character and returns an error if not.
func nonEmpty(v string) error {
if utf8.RuneCountInString(strings.TrimSpace(v)) == 0 {
return errors.New("cannot be empty")
}
return nil
}
// SubnetCIDR checks if the given string is a valid CIDR for a master nodes or worker nodes subnet and returns an error if not.
func SubnetCIDR(v string) error {
if err := nonEmpty(v); err != nil {
return err
}
split := strings.Split(v, "/")
if len(split) == 1 {
return errors.New("must provide a CIDR netmask (eg, /24)")
}
if len(split) != 2 {
return errors.New("invalid IPv4 address")
}
ip := split[0]
if err := IPv4(ip); err != nil {
return errors.New("invalid IPv4 address")
}
if mask, err := strconv.Atoi(split[1]); err != nil || mask < 0 || mask > 32 {
return errors.New("invalid netmask size (must be between 0 and 32)")
}
// Catch any invalid CIDRs not caught by the checks above
if _, _, err := net.ParseCIDR(v); err != nil {
return errors.New("invalid CIDR")
}
if strings.HasPrefix(ip, "172.17.") {
return errors.New("overlaps with default Docker Bridge subnet (172.17.0.0/16)")
}
return nil
}
// CIDRsDontOverlap ensures two given CIDRs don't overlap
// with one another. CIDR starting IPs are canonicalized
// before being compared.
func CIDRsDontOverlap(acidr, bcidr string) error {
_, a, err := net.ParseCIDR(acidr)
if err != nil {
return fmt.Errorf("invalid CIDR %q: %v", acidr, err)
}
if err := canonicalizeIP(&a.IP); err != nil {
return fmt.Errorf("invalid CIDR %q: %v", acidr, err)
}
_, b, err := net.ParseCIDR(bcidr)
if err != nil {
return fmt.Errorf("invalid CIDR %q: %v", bcidr, err)
}
if err := canonicalizeIP(&b.IP); err != nil {
return fmt.Errorf("invalid CIDR %q: %v", bcidr, err)
}
err = fmt.Errorf("%q and %q overlap", acidr, bcidr)
// IPs are of different families.
if len(a.IP) != len(b.IP) {
return nil
}
if a.Contains(b.IP) {
return err
}
if a.Contains(lastIP(b)) {
return err
}
if b.Contains(a.IP) {
return err
}
if b.Contains(lastIP(a)) {
return err
}
return nil
}
// IPv4 checks if the given string is a valid IP v4 address and returns an error if not.
// Based on net.ParseIP.
func IPv4(v string) error {
if err := nonEmpty(v); err != nil {
return err
}
if ip := net.ParseIP(v); ip == nil || !strings.Contains(v, ".") {
return errors.New("invalid IPv4 address")
}
return nil
}
// canonicalizeIP ensures that the given IP is in standard form
// and returns an error otherwise.
func canonicalizeIP(ip *net.IP) error {
if ip.To4() != nil {
*ip = ip.To4()
return nil
}
if ip.To16() != nil {
*ip = ip.To16()
return nil
}
return fmt.Errorf("IP %q is of unknown type", ip)
}
func lastIP(cidr *net.IPNet) net.IP {
var last net.IP
for i := 0; i < len(cidr.IP); i++ {
last = append(last, cidr.IP[i]|^cidr.Mask[i])
}
return last
}
// SSHPublicKey checks if the given string is a valid OpenSSH public key
// and returns an error if not.
func SSHPublicKey(v string) error {
trimmed := strings.TrimSpace(v)
// Don't let users hang themselves
if isMatch(`-BEGIN [\w-]+ PRIVATE KEY-`, trimmed) {
return errors.New("invalid SSH public key (appears to be a private key)")
}
if strings.Contains(trimmed, "\n") {
return errors.New("invalid SSH public key (should not contain any newline characters)")
}
invalidError := errors.New("invalid SSH public key")
keyParts := regexp.MustCompile(`\s+`).Split(trimmed, -1)
if len(keyParts) < 2 {
return invalidError
}
keyType := keyParts[0]
keyBase64 := keyParts[1]
if !isMatch(`^[\w-]+$`, keyType) || !isMatch(`^[A-Za-z0-9+\/]+={0,2}$`, keyBase64) {
return invalidError
}
return nil
}