package validation import ( "net" "net/url" "os" "strings" kvalidation "github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation" "github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors" "github.com/openshift/origin/pkg/cmd/server/api" ) func ValidateBindAddress(bindAddress string) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} if len(bindAddress) == 0 { allErrs = append(allErrs, fielderrors.NewFieldRequired("bindAddress")) } else if _, _, err := net.SplitHostPort(bindAddress); err != nil { allErrs = append(allErrs, fielderrors.NewFieldInvalid("bindAddress", bindAddress, "must be a host:port")) } return allErrs } func ValidateServingInfo(info api.ServingInfo) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} allErrs = append(allErrs, ValidateBindAddress(info.BindAddress)...) if len(info.ServerCert.CertFile) > 0 { if _, err := os.Stat(info.ServerCert.CertFile); err != nil { allErrs = append(allErrs, fielderrors.NewFieldInvalid("certFile", info.ServerCert.CertFile, "could not read file")) } if len(info.ServerCert.KeyFile) == 0 { allErrs = append(allErrs, fielderrors.NewFieldRequired("keyFile")) } else if _, err := os.Stat(info.ServerCert.KeyFile); err != nil { allErrs = append(allErrs, fielderrors.NewFieldInvalid("keyFile", info.ServerCert.KeyFile, "could not read file")) } if len(info.ClientCA) > 0 { if _, err := os.Stat(info.ClientCA); err != nil { allErrs = append(allErrs, fielderrors.NewFieldInvalid("clientCA", info.ClientCA, "could not read file")) } } } else { if len(info.ServerCert.KeyFile) > 0 { allErrs = append(allErrs, fielderrors.NewFieldInvalid("keyFile", info.ServerCert.KeyFile, "cannot specify a keyFile without a certFile")) } if len(info.ClientCA) > 0 { allErrs = append(allErrs, fielderrors.NewFieldInvalid("clientCA", info.ClientCA, "cannot specify a clientCA without a certFile")) } } return allErrs } func ValidateKubeConfig(path string, field string) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} allErrs = append(allErrs, ValidateFile(path, field)...) // TODO: load and parse return allErrs } func ValidateKubernetesMasterConfig(config *api.KubernetesMasterConfig) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} if len(config.MasterIP) > 0 { allErrs = append(allErrs, ValidateSpecifiedIP(config.MasterIP, "masterIP")...) } if len(config.ServicesSubnet) > 0 { if _, _, err := net.ParseCIDR(strings.TrimSpace(config.ServicesSubnet)); err != nil { allErrs = append(allErrs, fielderrors.NewFieldInvalid("servicesSubnet", config.ServicesSubnet, "must be a valid CIDR notation IP range (e.g. 172.30.17.0/24)")) } } if len(config.SchedulerConfigFile) > 0 { allErrs = append(allErrs, ValidateFile(config.SchedulerConfigFile, "schedulerConfigFile")...) } return allErrs } func ValidateSpecifiedIP(ipString string, field string) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} ip := net.ParseIP(ipString) if ip == nil { allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, ipString, "must be a valid IP")) } else if ip.IsUnspecified() { allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, ipString, "cannot be an unspecified IP")) } return allErrs } func ValidateURL(urlString string, field string) (*url.URL, fielderrors.ValidationErrorList) { allErrs := fielderrors.ValidationErrorList{} urlObj, err := url.Parse(urlString) if err != nil { allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, urlString, "must be a valid URL")) return nil, allErrs } if len(urlObj.Scheme) == 0 { allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, urlString, "must contain a scheme (e.g. http://)")) } if len(urlObj.Host) == 0 { allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, urlString, "must contain a host")) } return urlObj, allErrs } func ValidateAssetConfig(config *api.AssetConfig) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} allErrs = append(allErrs, ValidateServingInfo(config.ServingInfo).Prefix("servingInfo")...) urlObj, urlErrs := ValidateURL(config.PublicURL, "publicURL") if len(urlErrs) > 0 { allErrs = append(allErrs, urlErrs...) } if urlObj != nil { if !strings.HasSuffix(urlObj.Path, "/") { allErrs = append(allErrs, fielderrors.NewFieldInvalid("publicURL", config.PublicURL, "must have a trailing slash in path")) } } return allErrs } func ValidateMasterConfig(config *api.MasterConfig) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} allErrs = append(allErrs, ValidateServingInfo(config.ServingInfo).Prefix("servingInfo")...) if config.AssetConfig != nil { allErrs = append(allErrs, ValidateAssetConfig(config.AssetConfig).Prefix("assetConfig")...) colocated := config.AssetConfig.ServingInfo.BindAddress == config.ServingInfo.BindAddress if colocated { publicURL, _ := url.Parse(config.AssetConfig.PublicURL) if publicURL.Path == "/" { allErrs = append(allErrs, fielderrors.NewFieldInvalid("assetConfig.publicURL", config.AssetConfig.PublicURL, "path can not be / when colocated with master API")) } } if config.OAuthConfig != nil && config.OAuthConfig.AssetPublicURL != config.AssetConfig.PublicURL { allErrs = append(allErrs, fielderrors.NewFieldInvalid("assetConfig.publicURL", config.AssetConfig.PublicURL, "must match oauthConfig.assetPublicURL"), fielderrors.NewFieldInvalid("oauthConfig.assetPublicURL", config.OAuthConfig.AssetPublicURL, "must match assetConfig.publicURL"), ) } // TODO warn when the CORS list does not include the assetConfig.publicURL host:port // only warn cause they could handle CORS headers themselves in a proxy } if config.DNSConfig != nil { allErrs = append(allErrs, ValidateBindAddress(config.DNSConfig.BindAddress).Prefix("dnsConfig")...) } if config.KubernetesMasterConfig != nil { allErrs = append(allErrs, ValidateKubernetesMasterConfig(config.KubernetesMasterConfig).Prefix("kubernetesMasterConfig")...) } allErrs = append(allErrs, ValidatePolicyConfig(config.PolicyConfig).Prefix("policyConfig")...) allErrs = append(allErrs, ValidateKubeConfig(config.MasterClients.DeployerKubeConfig, "deployerKubeConfig").Prefix("masterClients")...) allErrs = append(allErrs, ValidateKubeConfig(config.MasterClients.OpenShiftLoopbackKubeConfig, "openShiftLoopbackKubeConfig").Prefix("masterClients")...) allErrs = append(allErrs, ValidateKubeConfig(config.MasterClients.KubernetesKubeConfig, "kubernetesKubeConfig").Prefix("masterClients")...) return allErrs } func ValidatePolicyConfig(config api.PolicyConfig) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} allErrs = append(allErrs, ValidateFile(config.BootstrapPolicyFile, "bootstrapPolicyFile")...) allErrs = append(allErrs, ValidateNamespace(config.MasterAuthorizationNamespace, "masterAuthorizationNamespace")...) allErrs = append(allErrs, ValidateNamespace(config.OpenShiftSharedResourcesNamespace, "openShiftSharedResourcesNamespace")...) return allErrs } func ValidateNamespace(namespace, field string) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} if len(namespace) == 0 { allErrs = append(allErrs, fielderrors.NewFieldRequired(field)) } else if ok, _ := kvalidation.ValidateNamespaceName(namespace, false); !ok { allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, namespace, "must be a valid namespace")) } return allErrs } func ValidateNodeConfig(config *api.NodeConfig) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} if len(config.NodeName) == 0 { allErrs = append(allErrs, fielderrors.NewFieldRequired("nodeName")) } allErrs = append(allErrs, ValidateServingInfo(config.ServingInfo).Prefix("servingInfo")...) allErrs = append(allErrs, ValidateKubeConfig(config.MasterKubeConfig, "masterKubeConfig")...) if len(config.DNSIP) > 0 { allErrs = append(allErrs, ValidateSpecifiedIP(config.DNSIP, "dnsIP")...) } if len(config.NetworkContainerImage) == 0 { allErrs = append(allErrs, fielderrors.NewFieldRequired("networkContainerImage")) } return allErrs } func ValidateFile(path string, field string) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} if len(path) == 0 { allErrs = append(allErrs, fielderrors.NewFieldRequired(field)) } else if _, err := os.Stat(path); err != nil { allErrs = append(allErrs, fielderrors.NewFieldInvalid(field, path, "could not read file")) } return allErrs } func ValidateAllInOneConfig(master *api.MasterConfig, node *api.NodeConfig) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} allErrs = append(allErrs, ValidateMasterConfig(master).Prefix("masterConfig")...) allErrs = append(allErrs, ValidateNodeConfig(node).Prefix("nodeConfig")...) // Validation between the configs return allErrs }