53c7aa34 |
package validation
import ( |
57894264 |
"fmt" |
53c7aa34 |
"net"
"net/url" |
81b520f2 |
"reflect" |
a8dd903f |
"regexp" |
53c7aa34 |
"strings" |
83e7080d |
"time" |
53c7aa34 |
|
7aabe6b9 |
apiserveroptions "k8s.io/kubernetes/cmd/kube-apiserver/app/options"
controlleroptions "k8s.io/kubernetes/cmd/kube-controller-manager/app/options" |
83c702b4 |
kvalidation "k8s.io/kubernetes/pkg/api/validation" |
43ba781b |
"k8s.io/kubernetes/pkg/serviceaccount"
knet "k8s.io/kubernetes/pkg/util/net" |
3dd75654 |
"k8s.io/kubernetes/pkg/util/sets" |
987aca9b |
kuval "k8s.io/kubernetes/pkg/util/validation" |
398ef03e |
"k8s.io/kubernetes/pkg/util/validation/field" |
53c7aa34 |
"github.com/openshift/origin/pkg/cmd/server/api" |
878d37a8 |
"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy" |
ec5a5ac6 |
"github.com/openshift/origin/pkg/security/mcs"
"github.com/openshift/origin/pkg/security/uid" |
abd33eda |
"github.com/openshift/origin/pkg/util/labelselector" |
53c7aa34 |
)
|
e00edaae |
// TODO: this should just be two return arrays, no need to be clever |
26c990fe |
type ValidationResults struct { |
398ef03e |
Warnings field.ErrorList
Errors field.ErrorList |
26c990fe |
}
func (r *ValidationResults) Append(additionalResults ValidationResults) {
r.AddErrors(additionalResults.Errors...)
r.AddWarnings(additionalResults.Warnings...)
}
|
398ef03e |
func (r *ValidationResults) AddErrors(errors ...*field.Error) { |
26c990fe |
if len(errors) == 0 {
return
}
r.Errors = append(r.Errors, errors...)
}
|
398ef03e |
func (r *ValidationResults) AddWarnings(warnings ...*field.Error) { |
26c990fe |
if len(warnings) == 0 {
return
}
r.Warnings = append(r.Warnings, warnings...)
}
|
398ef03e |
func ValidateMasterConfig(config *api.MasterConfig, fldPath *field.Path) ValidationResults { |
26c990fe |
validationResults := ValidationResults{} |
53c7aa34 |
|
398ef03e |
if _, urlErrs := ValidateURL(config.MasterPublicURL, fldPath.Child("masterPublicURL")); len(urlErrs) > 0 { |
26c990fe |
validationResults.AddErrors(urlErrs...) |
f53feeed |
}
|
69491ff4 |
switch {
case config.ControllerLeaseTTL > 300,
config.ControllerLeaseTTL < -1,
config.ControllerLeaseTTL > 0 && config.ControllerLeaseTTL < 10: |
398ef03e |
validationResults.AddErrors(field.Invalid(fldPath.Child("controllerLeaseTTL"), config.ControllerLeaseTTL, "TTL must be -1 (disabled), 0 (default), or between 10 and 300 seconds")) |
69491ff4 |
}
|
398ef03e |
validationResults.AddErrors(ValidateDisabledFeatures(config.DisabledFeatures, fldPath.Child("disabledFeatures"))...) |
642797f7 |
|
53c7aa34 |
if config.AssetConfig != nil { |
398ef03e |
assetConfigPath := fldPath.Child("assetConfig")
validationResults.Append(ValidateAssetConfig(config.AssetConfig, assetConfigPath)) |
53c7aa34 |
colocated := config.AssetConfig.ServingInfo.BindAddress == config.ServingInfo.BindAddress
if colocated {
publicURL, _ := url.Parse(config.AssetConfig.PublicURL)
if publicURL.Path == "/" { |
398ef03e |
validationResults.AddErrors(field.Invalid(assetConfigPath.Child("publicURL"), config.AssetConfig.PublicURL, "path can not be / when colocated with master API")) |
53c7aa34 |
} |
3d3db986 |
// Warn if they have customized the asset certificates in ways that will be ignored
if !reflect.DeepEqual(config.AssetConfig.ServingInfo.ServerCert, config.ServingInfo.ServerCert) ||
!reflect.DeepEqual(config.AssetConfig.ServingInfo.NamedCertificates, config.ServingInfo.NamedCertificates) { |
398ef03e |
validationResults.AddWarnings(field.Invalid(assetConfigPath.Child("servingInfo"), "<not displayed>", "changes to assetConfig certificate configuration are not used when colocated with master API")) |
81b520f2 |
} |
53c7aa34 |
}
if config.OAuthConfig != nil {
if config.OAuthConfig.AssetPublicURL != config.AssetConfig.PublicURL { |
26c990fe |
validationResults.AddErrors( |
398ef03e |
field.Invalid(assetConfigPath.Child("publicURL"), config.AssetConfig.PublicURL, "must match oauthConfig.assetPublicURL"),
field.Invalid(fldPath.Child("oauthConfig", "assetPublicURL"), config.OAuthConfig.AssetPublicURL, "must match assetConfig.publicURL"), |
53c7aa34 |
)
}
}
// 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 { |
398ef03e |
dnsConfigPath := fldPath.Child("dnsConfig")
validationResults.AddErrors(ValidateHostPort(config.DNSConfig.BindAddress, dnsConfigPath.Child("bindAddress"))...) |
429c4b0b |
switch config.DNSConfig.BindNetwork {
case "tcp", "tcp4", "tcp6":
default: |
398ef03e |
validationResults.AddErrors(field.Invalid(dnsConfigPath.Child("bindNetwork"), config.DNSConfig.BindNetwork, "must be 'tcp', 'tcp4', or 'tcp6'")) |
429c4b0b |
} |
53c7aa34 |
}
if config.EtcdConfig != nil { |
398ef03e |
etcdConfigErrs := ValidateEtcdConfig(config.EtcdConfig, fldPath.Child("etcdConfig")) |
81b520f2 |
validationResults.Append(etcdConfigErrs) |
53c7aa34 |
|
81b520f2 |
if len(etcdConfigErrs.Errors) == 0 { |
53c7aa34 |
// Validate the etcdClientInfo with the internal etcdConfig |
398ef03e |
validationResults.AddErrors(ValidateEtcdConnectionInfo(config.EtcdClientInfo, config.EtcdConfig, fldPath.Child("etcdClientInfo"))...) |
53c7aa34 |
} else {
// Validate the etcdClientInfo by itself |
398ef03e |
validationResults.AddErrors(ValidateEtcdConnectionInfo(config.EtcdClientInfo, nil, fldPath.Child("etcdClientInfo"))...) |
53c7aa34 |
}
} else {
// Validate the etcdClientInfo by itself |
398ef03e |
validationResults.AddErrors(ValidateEtcdConnectionInfo(config.EtcdClientInfo, nil, fldPath.Child("etcdClientInfo"))...) |
53c7aa34 |
} |
398ef03e |
validationResults.AddErrors(ValidateEtcdStorageConfig(config.EtcdStorageConfig, fldPath.Child("etcdStorageConfig"))...) |
53c7aa34 |
|
398ef03e |
validationResults.AddErrors(ValidateImageConfig(config.ImageConfig, fldPath.Child("imageConfig"))...) |
53c7aa34 |
|
0e9494df |
validationResults.AddErrors(ValidateImagePolicyConfig(config.ImagePolicyConfig, fldPath.Child("imagePolicyConfig"))...)
|
398ef03e |
validationResults.AddErrors(ValidateKubeletConnectionInfo(config.KubeletClientInfo, fldPath.Child("kubeletClientInfo"))...) |
53c7aa34 |
|
878d37a8 |
builtInKubernetes := config.KubernetesMasterConfig != nil |
53c7aa34 |
if config.KubernetesMasterConfig != nil { |
398ef03e |
validationResults.Append(ValidateKubernetesMasterConfig(config.KubernetesMasterConfig, fldPath.Child("kubernetesMasterConfig"))) |
53c7aa34 |
} |
da1980d3 |
if (config.KubernetesMasterConfig == nil) && (len(config.MasterClients.ExternalKubernetesKubeConfig) == 0) { |
398ef03e |
validationResults.AddErrors(field.Invalid(fldPath.Child("kubernetesMasterConfig"), config.KubernetesMasterConfig, "either kubernetesMasterConfig or masterClients.externalKubernetesKubeConfig must have a value")) |
da1980d3 |
}
if (config.KubernetesMasterConfig != nil) && (len(config.MasterClients.ExternalKubernetesKubeConfig) != 0) { |
398ef03e |
validationResults.AddErrors(field.Invalid(fldPath.Child("kubernetesMasterConfig"), config.KubernetesMasterConfig, "kubernetesMasterConfig and masterClients.externalKubernetesKubeConfig are mutually exclusive")) |
da1980d3 |
} |
53c7aa34 |
|
543dd90e |
if len(config.NetworkConfig.ServiceNetworkCIDR) > 0 {
if _, _, err := net.ParseCIDR(strings.TrimSpace(config.NetworkConfig.ServiceNetworkCIDR)); err != nil { |
398ef03e |
validationResults.AddErrors(field.Invalid(fldPath.Child("networkConfig", "serviceNetworkCIDR"), config.NetworkConfig.ServiceNetworkCIDR, "must be a valid CIDR notation IP range (e.g. 172.30.0.0/16)")) |
543dd90e |
} else if config.KubernetesMasterConfig != nil && len(config.KubernetesMasterConfig.ServicesSubnet) > 0 && config.KubernetesMasterConfig.ServicesSubnet != config.NetworkConfig.ServiceNetworkCIDR { |
398ef03e |
validationResults.AddErrors(field.Invalid(fldPath.Child("networkConfig", "serviceNetworkCIDR"), config.NetworkConfig.ServiceNetworkCIDR, fmt.Sprintf("must match kubernetesMasterConfig.servicesSubnet value of %q", config.KubernetesMasterConfig.ServicesSubnet))) |
543dd90e |
}
} |
290ade01 |
if len(config.NetworkConfig.ExternalIPNetworkCIDRs) > 0 {
for i, s := range config.NetworkConfig.ExternalIPNetworkCIDRs {
if strings.HasPrefix(s, "!") {
s = s[1:]
}
if _, _, err := net.ParseCIDR(s); err != nil {
validationResults.AddErrors(field.Invalid(fldPath.Child("networkConfig", "externalIPNetworkCIDRs").Index(i), config.NetworkConfig.ExternalIPNetworkCIDRs[i], "must be a valid CIDR notation IP range (e.g. 172.30.0.0/16) with an optional leading !"))
}
}
} |
346982a0 |
validationResults.AddErrors(ValidateIngressIPNetworkCIDR(config, fldPath.Child("networkConfig", "ingressIPNetworkCIDR").Index(0))...) |
543dd90e |
|
398ef03e |
validationResults.AddErrors(ValidateKubeConfig(config.MasterClients.OpenShiftLoopbackKubeConfig, fldPath.Child("masterClients", "openShiftLoopbackKubeConfig"))...) |
da1980d3 |
if len(config.MasterClients.ExternalKubernetesKubeConfig) > 0 { |
398ef03e |
validationResults.AddErrors(ValidateKubeConfig(config.MasterClients.ExternalKubernetesKubeConfig, fldPath.Child("masterClients", "externalKubernetesKubeConfig"))...) |
da1980d3 |
} |
53c7aa34 |
|
398ef03e |
validationResults.AddErrors(ValidatePolicyConfig(config.PolicyConfig, fldPath.Child("policyConfig"))...) |
53c7aa34 |
if config.OAuthConfig != nil { |
398ef03e |
validationResults.Append(ValidateOAuthConfig(config.OAuthConfig, fldPath.Child("oauthConfig"))) |
53c7aa34 |
}
|
398ef03e |
validationResults.Append(ValidateServiceAccountConfig(config.ServiceAccountConfig, builtInKubernetes, fldPath.Child("serviceAccountConfig"))) |
878d37a8 |
|
398ef03e |
validationResults.Append(ValidateHTTPServingInfo(config.ServingInfo, fldPath.Child("servingInfo"))) |
53c7aa34 |
|
398ef03e |
validationResults.Append(ValidateProjectConfig(config.ProjectConfig, fldPath.Child("projectConfig"))) |
384f11fa |
|
398ef03e |
validationResults.AddErrors(ValidateRoutingConfig(config.RoutingConfig, fldPath.Child("routingConfig"))...) |
321d1032 |
|
398ef03e |
validationResults.Append(ValidateAPILevels(config.APILevels, api.KnownOpenShiftAPILevels, api.DeadOpenShiftAPILevels, fldPath.Child("apiLevels"))) |
26c990fe |
|
00b6e181 |
if config.AdmissionConfig.PluginConfig != nil { |
398ef03e |
validationResults.AddErrors(ValidateAdmissionPluginConfig(config.AdmissionConfig.PluginConfig, fldPath.Child("admissionConfig", "pluginConfig"))...) |
3a98b97f |
validationResults.Append(ValidateAdmissionPluginConfigConflicts(config))
}
if len(config.AdmissionConfig.PluginOrderOverride) != 0 {
validationResults.AddWarnings(field.Invalid(fldPath.Child("admissionConfig", "pluginOrderOverride"), config.AdmissionConfig.PluginOrderOverride, "specified admission ordering is being phased out. Convert to DefaultAdmissionConfig in admissionConfig.pluginConfig.")) |
00b6e181 |
}
|
99df912d |
validationResults.Append(ValidateControllerConfig(config.ControllerConfig, fldPath.Child("controllerConfig")))
|
59e59e37 |
validationResults.Append(ValidateAuditConfig(config.AuditConfig, fldPath.Child("auditConfig")))
return validationResults
}
func ValidateAuditConfig(config api.AuditConfig, fldPath *field.Path) ValidationResults {
validationResults := ValidationResults{}
if len(config.AuditFilePath) == 0 {
// for backwards compatibility reasons we can't error this out
validationResults.AddWarnings(field.Required(fldPath.Child("auditFilePath"), "audit can now be logged to a separate file"))
}
if config.MaximumFileRetentionDays < 0 {
validationResults.AddErrors(field.Invalid(fldPath.Child("maximumFileRetentionDays"), config.MaximumFileRetentionDays, "must be greater than or equal to 0"))
}
if config.MaximumRetainedFiles < 0 {
validationResults.AddErrors(field.Invalid(fldPath.Child("maximumRetainedFiles"), config.MaximumRetainedFiles, "must be greater than or equal to 0"))
}
if config.MaximumFileSizeMegabytes < 0 {
validationResults.AddErrors(field.Invalid(fldPath.Child("maximumFileSizeMegabytes"), config.MaximumFileSizeMegabytes, "must be greater than or equal to 0"))
}
|
99df912d |
return validationResults
}
func ValidateControllerConfig(config api.ControllerConfig, fldPath *field.Path) ValidationResults {
validationResults := ValidationResults{}
if config.ServiceServingCert.Signer != nil {
validationResults.AddErrors(ValidateCertInfo(*config.ServiceServingCert.Signer, true, fldPath.Child("serviceServingCert.signer"))...)
}
|
26c990fe |
return validationResults
}
|
398ef03e |
func ValidateAPILevels(apiLevels []string, knownAPILevels, deadAPILevels []string, fldPath *field.Path) ValidationResults { |
26c990fe |
validationResults := ValidationResults{}
if len(apiLevels) == 0 { |
43ba781b |
validationResults.AddErrors(field.Required(fldPath, "")) |
26c990fe |
}
|
3dd75654 |
deadLevels := sets.NewString(deadAPILevels...)
knownLevels := sets.NewString(knownAPILevels...) |
26c990fe |
for i, apiLevel := range apiLevels { |
398ef03e |
idxPath := fldPath.Index(i) |
26c990fe |
if deadLevels.Has(apiLevel) { |
398ef03e |
validationResults.AddWarnings(field.Invalid(idxPath, apiLevel, "unsupported level")) |
26c990fe |
}
if !knownLevels.Has(apiLevel) { |
398ef03e |
validationResults.AddWarnings(field.Invalid(idxPath, apiLevel, "unknown level")) |
26c990fe |
}
}
return validationResults |
53c7aa34 |
}
|
398ef03e |
func ValidateEtcdStorageConfig(config api.EtcdStorageConfig, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} |
6a7364d3 |
|
1eb472bb |
allErrs = append(allErrs, ValidateStorageVersionLevel(
config.KubernetesStorageVersion,
api.KnownKubernetesStorageVersionLevels,
api.DeadKubernetesStorageVersionLevels, |
398ef03e |
fldPath.Child("kubernetesStorageVersion"))...) |
1eb472bb |
allErrs = append(allErrs, ValidateStorageVersionLevel(
config.OpenShiftStorageVersion,
api.KnownOpenShiftStorageVersionLevels,
api.DeadOpenShiftStorageVersionLevels, |
398ef03e |
fldPath.Child("openShiftStorageVersion"))...) |
6a7364d3 |
|
4580178f |
if strings.ContainsRune(config.KubernetesStoragePrefix, '%') { |
398ef03e |
allErrs = append(allErrs, field.Invalid(fldPath.Child("kubernetesStoragePrefix"), config.KubernetesStoragePrefix, "the '%' character may not be used in etcd path prefixes")) |
4580178f |
}
if strings.ContainsRune(config.OpenShiftStoragePrefix, '%') { |
398ef03e |
allErrs = append(allErrs, field.Invalid(fldPath.Child("openShiftStoragePrefix"), config.OpenShiftStoragePrefix, "the '%' character may not be used in etcd path prefixes")) |
4580178f |
}
|
6a7364d3 |
return allErrs
}
|
398ef03e |
func ValidateStorageVersionLevel(level string, knownAPILevels, deadAPILevels []string, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} |
1eb472bb |
if len(level) == 0 { |
43ba781b |
allErrs = append(allErrs, field.Required(fldPath, "")) |
1eb472bb |
return allErrs
}
supportedLevels := sets.NewString(knownAPILevels...)
supportedLevels.Delete(deadAPILevels...)
if !supportedLevels.Has(level) { |
398ef03e |
allErrs = append(allErrs, field.NotSupported(fldPath, level, supportedLevels.List())) |
1eb472bb |
}
return allErrs
}
|
398ef03e |
func ValidateServiceAccountConfig(config api.ServiceAccountConfig, builtInKubernetes bool, fldPath *field.Path) ValidationResults { |
89364aa9 |
validationResults := ValidationResults{} |
878d37a8 |
|
3dd75654 |
managedNames := sets.NewString(config.ManagedNames...) |
398ef03e |
managedNamesPath := fldPath.Child("managedNames") |
878d37a8 |
if !managedNames.Has(bootstrappolicy.BuilderServiceAccountName) { |
398ef03e |
validationResults.AddWarnings(field.Invalid(managedNamesPath, "", fmt.Sprintf("missing %q, which will require manual creation in each namespace before builds can run", bootstrappolicy.BuilderServiceAccountName))) |
89364aa9 |
}
if !managedNames.Has(bootstrappolicy.DeployerServiceAccountName) { |
398ef03e |
validationResults.AddWarnings(field.Invalid(managedNamesPath, "", fmt.Sprintf("missing %q, which will require manual creation in each namespace before deployments can run", bootstrappolicy.DeployerServiceAccountName))) |
878d37a8 |
}
if builtInKubernetes && !managedNames.Has(bootstrappolicy.DefaultServiceAccountName) { |
398ef03e |
validationResults.AddWarnings(field.Invalid(managedNamesPath, "", fmt.Sprintf("missing %q, which will prevent creation of pods that do not specify a valid service account", bootstrappolicy.DefaultServiceAccountName))) |
878d37a8 |
}
for i, name := range config.ManagedNames { |
8201efbe |
if reasons := kvalidation.ValidateServiceAccountName(name, false); len(reasons) != 0 {
validationResults.AddErrors(field.Invalid(managedNamesPath.Index(i), name, strings.Join(reasons, ", "))) |
878d37a8 |
}
}
if len(config.PrivateKeyFile) > 0 { |
398ef03e |
privateKeyFilePath := fldPath.Child("privateKeyFile")
if fileErrs := ValidateFile(config.PrivateKeyFile, privateKeyFilePath); len(fileErrs) > 0 { |
89364aa9 |
validationResults.AddErrors(fileErrs...) |
878d37a8 |
} else if privateKey, err := serviceaccount.ReadPrivateKey(config.PrivateKeyFile); err != nil { |
398ef03e |
validationResults.AddErrors(field.Invalid(privateKeyFilePath, config.PrivateKeyFile, err.Error())) |
878d37a8 |
} else if err := privateKey.Validate(); err != nil { |
398ef03e |
validationResults.AddErrors(field.Invalid(privateKeyFilePath, config.PrivateKeyFile, err.Error())) |
878d37a8 |
}
} else if builtInKubernetes { |
398ef03e |
validationResults.AddWarnings(field.Invalid(fldPath.Child("privateKeyFile"), "", "no service account tokens will be generated, which could prevent builds and deployments from working")) |
878d37a8 |
}
if len(config.PublicKeyFiles) == 0 { |
398ef03e |
validationResults.AddWarnings(field.Invalid(fldPath.Child("publicKeyFiles"), "", "no service account tokens will be accepted by the API, which will prevent builds and deployments from working")) |
878d37a8 |
}
for i, publicKeyFile := range config.PublicKeyFiles { |
398ef03e |
idxPath := fldPath.Child("publicKeyFiles").Index(i)
if fileErrs := ValidateFile(publicKeyFile, idxPath); len(fileErrs) > 0 { |
89364aa9 |
validationResults.AddErrors(fileErrs...) |
878d37a8 |
} else if _, err := serviceaccount.ReadPublicKey(publicKeyFile); err != nil { |
398ef03e |
validationResults.AddErrors(field.Invalid(idxPath, publicKeyFile, err.Error())) |
878d37a8 |
}
}
|
5b4ee6a4 |
if len(config.MasterCA) > 0 { |
398ef03e |
validationResults.AddErrors(ValidateFile(config.MasterCA, fldPath.Child("masterCA"))...) |
5b4ee6a4 |
} else if builtInKubernetes { |
398ef03e |
validationResults.AddWarnings(field.Invalid(fldPath.Child("masterCA"), "", "master CA information will not be automatically injected into pods, which will prevent verification of the API server from inside a pod")) |
5b4ee6a4 |
}
|
89364aa9 |
return validationResults |
878d37a8 |
}
|
398ef03e |
func ValidateAssetConfig(config *api.AssetConfig, fldPath *field.Path) ValidationResults { |
81b520f2 |
validationResults := ValidationResults{} |
53c7aa34 |
|
398ef03e |
validationResults.Append(ValidateHTTPServingInfo(config.ServingInfo, fldPath.Child("servingInfo"))) |
53c7aa34 |
|
0fd35257 |
if len(config.LogoutURL) > 0 { |
398ef03e |
_, urlErrs := ValidateURL(config.LogoutURL, fldPath.Child("logoutURL")) |
0fd35257 |
if len(urlErrs) > 0 { |
81b520f2 |
validationResults.AddErrors(urlErrs...) |
0fd35257 |
}
}
|
398ef03e |
urlObj, urlErrs := ValidateURL(config.PublicURL, fldPath.Child("publicURL")) |
53c7aa34 |
if len(urlErrs) > 0 { |
81b520f2 |
validationResults.AddErrors(urlErrs...) |
53c7aa34 |
}
if urlObj != nil {
if !strings.HasSuffix(urlObj.Path, "/") { |
398ef03e |
validationResults.AddErrors(field.Invalid(fldPath.Child("publicURL"), config.PublicURL, "must have a trailing slash in path")) |
53c7aa34 |
}
}
|
398ef03e |
if _, urlErrs := ValidateURL(config.MasterPublicURL, fldPath.Child("masterPublicURL")); len(urlErrs) > 0 { |
81b520f2 |
validationResults.AddErrors(urlErrs...) |
f53feeed |
}
|
8fbf136d |
if len(config.LoggingPublicURL) > 0 { |
398ef03e |
if _, loggingURLErrs := ValidateSecureURL(config.LoggingPublicURL, fldPath.Child("loggingPublicURL")); len(loggingURLErrs) > 0 { |
81b520f2 |
validationResults.AddErrors(loggingURLErrs...) |
8fbf136d |
} |
363e9a33 |
} else { |
398ef03e |
validationResults.AddWarnings(field.Invalid(fldPath.Child("loggingPublicURL"), "", "required to view aggregated container logs in the console")) |
8fbf136d |
}
if len(config.MetricsPublicURL) > 0 { |
398ef03e |
if _, metricsURLErrs := ValidateSecureURL(config.MetricsPublicURL, fldPath.Child("metricsPublicURL")); len(metricsURLErrs) > 0 { |
81b520f2 |
validationResults.AddErrors(metricsURLErrs...) |
8fbf136d |
} |
363e9a33 |
} else { |
398ef03e |
validationResults.AddWarnings(field.Invalid(fldPath.Child("metricsPublicURL"), "", "required to view cluster metrics in the console")) |
8fbf136d |
}
|
a8dd903f |
for i, scriptFile := range config.ExtensionScripts { |
398ef03e |
validationResults.AddErrors(ValidateFile(scriptFile, fldPath.Child("extensionScripts").Index(i))...) |
a8dd903f |
}
for i, stylesheetFile := range config.ExtensionStylesheets { |
398ef03e |
validationResults.AddErrors(ValidateFile(stylesheetFile, fldPath.Child("extensionStylesheets").Index(i))...) |
a8dd903f |
}
nameTaken := map[string]bool{}
for i, extConfig := range config.Extensions { |
398ef03e |
idxPath := fldPath.Child("extensions").Index(i)
extConfigErrors := ValidateAssetExtensionsConfig(extConfig, idxPath) |
81b520f2 |
validationResults.AddErrors(extConfigErrors...) |
a8dd903f |
if nameTaken[extConfig.Name] { |
398ef03e |
dupError := field.Invalid(idxPath.Child("name"), extConfig.Name, "duplicate extension name") |
81b520f2 |
validationResults.AddErrors(dupError) |
a8dd903f |
} else {
nameTaken[extConfig.Name] = true
}
}
|
81b520f2 |
return validationResults |
a8dd903f |
}
var extNameExp = regexp.MustCompile(`^[A-Za-z0-9_]+$`)
|
398ef03e |
func ValidateAssetExtensionsConfig(extConfig api.AssetExtensionsConfig, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} |
a8dd903f |
|
398ef03e |
allErrs = append(allErrs, ValidateDir(extConfig.SourceDirectory, fldPath.Child("sourceDirectory"))...) |
a8dd903f |
if len(extConfig.Name) == 0 { |
43ba781b |
allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) |
a8dd903f |
} else if !extNameExp.MatchString(extConfig.Name) { |
398ef03e |
allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), extConfig.Name, fmt.Sprintf("does not match %v", extNameExp))) |
a8dd903f |
}
|
53c7aa34 |
return allErrs
}
|
398ef03e |
func ValidateImageConfig(config api.ImageConfig, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} |
53c7aa34 |
if len(config.Format) == 0 { |
43ba781b |
allErrs = append(allErrs, field.Required(fldPath.Child("format"), "")) |
53c7aa34 |
}
return allErrs
}
|
0e9494df |
func ValidateImagePolicyConfig(config api.ImagePolicyConfig, fldPath *field.Path) field.ErrorList {
errs := field.ErrorList{}
if config.MaxImagesBulkImportedPerRepository == 0 || config.MaxImagesBulkImportedPerRepository < -1 {
errs = append(errs, field.Invalid(fldPath.Child("maxImagesBulkImportedPerRepository"), config.MaxImagesBulkImportedPerRepository, "must be a positive integer or -1"))
}
if config.ScheduledImageImportMinimumIntervalSeconds <= 0 {
errs = append(errs, field.Invalid(fldPath.Child("scheduledImageImportMinimumIntervalSeconds"), config.ScheduledImageImportMinimumIntervalSeconds, "must be a positive integer"))
}
if config.MaxScheduledImageImportsPerMinute == 0 || config.MaxScheduledImageImportsPerMinute < -1 {
errs = append(errs, field.Invalid(fldPath.Child("maxScheduledImageImportsPerMinute"), config.MaxScheduledImageImportsPerMinute, "must be a positive integer or -1"))
}
return errs
}
|
398ef03e |
func ValidateKubeletConnectionInfo(config api.KubeletConnectionInfo, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} |
0e9494df |
|
53c7aa34 |
if config.Port == 0 { |
43ba781b |
allErrs = append(allErrs, field.Required(fldPath.Child("port"), "")) |
53c7aa34 |
}
if len(config.CA) > 0 { |
398ef03e |
allErrs = append(allErrs, ValidateFile(config.CA, fldPath.Child("ca"))...) |
53c7aa34 |
} |
398ef03e |
allErrs = append(allErrs, ValidateCertInfo(config.ClientCert, false, fldPath)...) |
53c7aa34 |
return allErrs
}
|
398ef03e |
func ValidateKubernetesMasterConfig(config *api.KubernetesMasterConfig, fldPath *field.Path) ValidationResults { |
26c990fe |
validationResults := ValidationResults{} |
53c7aa34 |
if len(config.MasterIP) > 0 { |
398ef03e |
validationResults.AddErrors(ValidateSpecifiedIP(config.MasterIP, fldPath.Child("masterIP"))...) |
53c7aa34 |
}
|
5cbeeacd |
if config.MasterCount == 0 || config.MasterCount < -1 { |
398ef03e |
validationResults.AddErrors(field.Invalid(fldPath.Child("masterCount"), config.MasterCount, "must be a positive integer or -1")) |
52723316 |
}
|
398ef03e |
validationResults.AddErrors(ValidateCertInfo(config.ProxyClientInfo, false, fldPath.Child("proxyClientInfo"))...) |
e4a83a7a |
if len(config.ProxyClientInfo.CertFile) == 0 && len(config.ProxyClientInfo.KeyFile) == 0 { |
398ef03e |
validationResults.AddWarnings(field.Invalid(fldPath.Child("proxyClientInfo"), "", "if no client certificate is specified, TLS pods and services cannot validate requests came from the proxy")) |
e4a83a7a |
}
|
53c7aa34 |
if len(config.ServicesSubnet) > 0 {
if _, _, err := net.ParseCIDR(strings.TrimSpace(config.ServicesSubnet)); err != nil { |
398ef03e |
validationResults.AddErrors(field.Invalid(fldPath.Child("servicesSubnet"), config.ServicesSubnet, "must be a valid CIDR notation IP range (e.g. 172.30.0.0/16)")) |
53c7aa34 |
}
}
|
e00edaae |
if len(config.ServicesNodePortRange) > 0 { |
43ba781b |
if _, err := knet.ParsePortRange(strings.TrimSpace(config.ServicesNodePortRange)); err != nil { |
398ef03e |
validationResults.AddErrors(field.Invalid(fldPath.Child("servicesNodePortRange"), config.ServicesNodePortRange, "must be a valid port range (e.g. 30000-32000)")) |
e00edaae |
}
}
|
53c7aa34 |
if len(config.SchedulerConfigFile) > 0 { |
398ef03e |
validationResults.AddErrors(ValidateFile(config.SchedulerConfigFile, fldPath.Child("schedulerConfigFile"))...) |
53c7aa34 |
}
|
57894264 |
for i, nodeName := range config.StaticNodeNames {
if len(nodeName) == 0 { |
398ef03e |
validationResults.AddErrors(field.Invalid(fldPath.Child("staticNodeName").Index(i), nodeName, "may not be empty")) |
55ac6a57 |
} else { |
398ef03e |
validationResults.AddWarnings(field.Invalid(fldPath.Child("staticNodeName").Index(i), nodeName, "static nodes are not supported")) |
57894264 |
}
}
|
83e7080d |
if len(config.PodEvictionTimeout) > 0 {
if _, err := time.ParseDuration(config.PodEvictionTimeout); err != nil { |
398ef03e |
validationResults.AddErrors(field.Invalid(fldPath.Child("podEvictionTimeout"), config.PodEvictionTimeout, "must be a valid time duration string (e.g. '300ms' or '2m30s'). Valid time units are 'ns', 'us', 'ms', 's', 'm', 'h'")) |
83e7080d |
}
}
|
3ceb818d |
for group, versions := range config.DisabledAPIGroupVersions { |
398ef03e |
keyPath := fldPath.Child("disabledAPIGroupVersions").Key(group) |
3ceb818d |
if !api.KnownKubeAPIGroups.Has(group) { |
398ef03e |
validationResults.AddWarnings(field.NotSupported(keyPath, group, api.KnownKubeAPIGroups.List())) |
3ceb818d |
continue
}
allowedVersions := sets.NewString(api.KubeAPIGroupsToAllowedVersions[group]...)
for i, version := range versions {
if version == "*" {
continue
}
if !allowedVersions.Has(version) { |
398ef03e |
validationResults.AddWarnings(field.NotSupported(keyPath.Index(i), version, allowedVersions.List())) |
3ceb818d |
}
}
} |
26c990fe |
|
00b6e181 |
if config.AdmissionConfig.PluginConfig != nil { |
398ef03e |
validationResults.AddErrors(ValidateAdmissionPluginConfig(config.AdmissionConfig.PluginConfig, fldPath.Child("admissionConfig", "pluginConfig"))...) |
00b6e181 |
} |
3a98b97f |
if len(config.AdmissionConfig.PluginOrderOverride) != 0 {
validationResults.AddWarnings(field.Invalid(fldPath.Child("admissionConfig", "pluginOrderOverride"), config.AdmissionConfig.PluginOrderOverride, "specified admission ordering is being phased out. Convert to DefaultAdmissionConfig in admissionConfig.pluginConfig."))
} |
00b6e181 |
|
3a98b97f |
validationResults.Append(ValidateAPIServerExtendedArguments(config.APIServerArguments, fldPath.Child("apiServerArguments"))) |
398ef03e |
validationResults.AddErrors(ValidateControllerExtendedArguments(config.ControllerArguments, fldPath.Child("controllerArguments"))...) |
0b833843 |
|
26c990fe |
return validationResults |
53c7aa34 |
}
|
398ef03e |
func ValidatePolicyConfig(config api.PolicyConfig, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} |
53c7aa34 |
|
398ef03e |
allErrs = append(allErrs, ValidateFile(config.BootstrapPolicyFile, fldPath.Child("bootstrapPolicyFile"))...)
allErrs = append(allErrs, ValidateNamespace(config.OpenShiftSharedResourcesNamespace, fldPath.Child("openShiftSharedResourcesNamespace"))...)
allErrs = append(allErrs, ValidateNamespace(config.OpenShiftInfrastructureNamespace, fldPath.Child("openShiftInfrastructureNamespace"))...) |
53c7aa34 |
|
049217ce |
for i, matchingRule := range config.UserAgentMatchingConfig.DeniedClients {
_, err := regexp.Compile(matchingRule.Regex)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("userAgentMatchingConfig", "deniedClients").Index(i), matchingRule.Regex, err.Error()))
}
}
for i, matchingRule := range config.UserAgentMatchingConfig.RequiredClients {
_, err := regexp.Compile(matchingRule.Regex)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("userAgentMatchingConfig", "requiredClients").Index(i), matchingRule.Regex, err.Error()))
}
}
|
53c7aa34 |
return allErrs
} |
384f11fa |
|
398ef03e |
func ValidateProjectConfig(config api.ProjectConfig, fldPath *field.Path) ValidationResults { |
b035b8e9 |
validationResults := ValidationResults{} |
384f11fa |
|
970f2c0f |
if _, _, err := api.ParseNamespaceAndName(config.ProjectRequestTemplate); err != nil { |
398ef03e |
validationResults.AddErrors(field.Invalid(fldPath.Child("projectRequestTemplate"), config.ProjectRequestTemplate, "must be in the form: namespace/templateName")) |
2baf326a |
}
|
97f43232 |
if len(config.DefaultNodeSelector) > 0 {
_, err := labelselector.Parse(config.DefaultNodeSelector)
if err != nil { |
398ef03e |
validationResults.AddErrors(field.Invalid(fldPath.Child("defaultNodeSelector"), config.DefaultNodeSelector, "must be a valid label selector")) |
97f43232 |
}
}
|
ec5a5ac6 |
if alloc := config.SecurityAllocator; alloc != nil { |
398ef03e |
securityAllocatorPath := fldPath.Child("securityAllocator") |
ec5a5ac6 |
if _, err := uid.ParseRange(alloc.UIDAllocatorRange); err != nil { |
398ef03e |
validationResults.AddErrors(field.Invalid(securityAllocatorPath.Child("uidAllocatorRange"), alloc.UIDAllocatorRange, err.Error())) |
ec5a5ac6 |
}
if _, err := mcs.ParseRange(alloc.MCSAllocatorRange); err != nil { |
398ef03e |
validationResults.AddErrors(field.Invalid(securityAllocatorPath.Child("mcsAllocatorRange"), alloc.MCSAllocatorRange, err.Error())) |
ec5a5ac6 |
}
if alloc.MCSLabelsPerProject <= 0 { |
398ef03e |
validationResults.AddErrors(field.Invalid(securityAllocatorPath.Child("mcsLabelsPerProject"), alloc.MCSLabelsPerProject, "must be a positive integer")) |
ec5a5ac6 |
} |
b035b8e9 |
} else { |
398ef03e |
validationResults.AddWarnings(field.Invalid(fldPath.Child("securityAllocator"), "null", "allocation of UIDs and MCS labels to a project must be done manually")) |
b035b8e9 |
|
ec5a5ac6 |
} |
b035b8e9 |
return validationResults |
384f11fa |
} |
321d1032 |
|
398ef03e |
func ValidateRoutingConfig(config api.RoutingConfig, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} |
321d1032 |
if len(config.Subdomain) == 0 { |
43ba781b |
allErrs = append(allErrs, field.Required(fldPath.Child("subdomain"), "")) |
8201efbe |
} else if len(kuval.IsDNS1123Subdomain(config.Subdomain)) != 0 { |
398ef03e |
allErrs = append(allErrs, field.Invalid(fldPath.Child("subdomain"), config.Subdomain, "must be a valid subdomain")) |
321d1032 |
}
return allErrs
} |
0b833843 |
|
3a98b97f |
func ValidateAPIServerExtendedArguments(config api.ExtendedArguments, fldPath *field.Path) ValidationResults {
validationResults := ValidationResults{}
validationResults.AddErrors(ValidateExtendedArguments(config, apiserveroptions.NewAPIServer().AddFlags, fldPath)...)
if len(config["admission-control"]) > 0 {
validationResults.AddWarnings(field.Invalid(fldPath.Key("admission-control"), config["admission-control"], "specified admission ordering is being phased out. Convert to DefaultAdmissionConfig in admissionConfig.pluginConfig."))
}
if len(config["admission-control-config-file"]) > 0 {
validationResults.AddWarnings(field.Invalid(fldPath.Key("admission-control-config-file"), config["admission-control-config-file"], "specify a single admission control config file is being phased out. Convert to admissionConfig.pluginConfig, one file per plugin."))
}
return validationResults |
0b833843 |
}
|
398ef03e |
func ValidateControllerExtendedArguments(config api.ExtendedArguments, fldPath *field.Path) field.ErrorList { |
7aabe6b9 |
return ValidateExtendedArguments(config, controlleroptions.NewCMServer().AddFlags, fldPath) |
0b833843 |
} |
00b6e181 |
|
398ef03e |
func ValidateAdmissionPluginConfig(pluginConfig map[string]api.AdmissionPluginConfig, fieldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} |
00b6e181 |
for name, config := range pluginConfig { |
43ba781b |
if len(config.Location) > 0 && config.Configuration != nil { |
398ef03e |
allErrs = append(allErrs, field.Invalid(fieldPath.Key(name), "", "cannot specify both location and embedded config")) |
00b6e181 |
} |
43ba781b |
if len(config.Location) == 0 && config.Configuration == nil { |
398ef03e |
allErrs = append(allErrs, field.Invalid(fieldPath.Key(name), "", "must specify either a location or an embedded config")) |
00b6e181 |
}
}
return allErrs
} |
3a98b97f |
func ValidateAdmissionPluginConfigConflicts(masterConfig *api.MasterConfig) ValidationResults {
validationResults := ValidationResults{}
if masterConfig.KubernetesMasterConfig != nil {
// check for collisions between openshift and kube plugin config
for pluginName, kubeConfig := range masterConfig.KubernetesMasterConfig.AdmissionConfig.PluginConfig {
if openshiftConfig, exists := masterConfig.AdmissionConfig.PluginConfig[pluginName]; exists && !reflect.DeepEqual(kubeConfig, openshiftConfig) {
validationResults.AddWarnings(field.Invalid(field.NewPath("kubernetesMasterConfig", "admissionConfig", "pluginConfig").Key(pluginName), masterConfig.AdmissionConfig.PluginConfig[pluginName], "conflicts with kubernetesMasterConfig.admissionConfig.pluginConfig. Separate admission chains are being phased out. Convert to admissionConfig.pluginConfig."))
}
}
}
return validationResults
} |
346982a0 |
|
ee401ccd |
func ValidateIngressIPNetworkCIDR(config *api.MasterConfig, fldPath *field.Path) (errors field.ErrorList) { |
346982a0 |
cidr := config.NetworkConfig.IngressIPNetworkCIDR
if len(cidr) == 0 { |
ee401ccd |
return |
346982a0 |
}
addError := func(errMessage string) {
errors = append(errors, field.Invalid(fldPath, cidr, errMessage))
}
|
ee401ccd |
_, ipNet, err := net.ParseCIDR(cidr)
if err != nil { |
f980b4c5 |
addError(fmt.Sprintf("must be a valid CIDR notation IP range (e.g. %s)", api.DefaultIngressIPNetworkCIDR)) |
ee401ccd |
return
}
|
346982a0 |
// TODO Detect cloud provider when not using built-in kubernetes
kubeConfig := config.KubernetesMasterConfig
noCloudProvider := kubeConfig != nil && (len(kubeConfig.ControllerArguments["cloud-provider"]) == 0 || kubeConfig.ControllerArguments["cloud-provider"][0] == "")
if noCloudProvider { |
ee401ccd |
if api.CIDRsOverlap(cidr, config.NetworkConfig.ClusterNetworkCIDR) {
addError("conflicts with cluster network CIDR") |
346982a0 |
} |
ee401ccd |
if api.CIDRsOverlap(cidr, config.NetworkConfig.ServiceNetworkCIDR) {
addError("conflicts with service network CIDR")
}
} else if !ipNet.IP.IsUnspecified() { |
346982a0 |
addError("should not be provided when a cloud-provider is enabled")
}
|
ee401ccd |
return |
346982a0 |
} |