github.com/nginxinc/kubernetes-ingress@v1.12.5/pkg/apis/configuration/validation/common.go (about) 1 package validation 2 3 import ( 4 "fmt" 5 "regexp" 6 "strings" 7 8 "github.com/nginxinc/kubernetes-ingress/internal/configs" 9 "k8s.io/apimachinery/pkg/util/validation" 10 "k8s.io/apimachinery/pkg/util/validation/field" 11 ) 12 13 const ( 14 escapedStringsFmt = `([^"\\]|\\.)*` 15 escapedStringsErrMsg = `must have all '"' (double quotes) escaped and must not end with an unescaped '\' (backslash)` 16 ) 17 18 var escapedStringsFmtRegexp = regexp.MustCompile("^" + escapedStringsFmt + "$") 19 20 func validateVariable(nVar string, validVars map[string]bool, fieldPath *field.Path) field.ErrorList { 21 allErrs := field.ErrorList{} 22 23 if !validVars[nVar] { 24 msg := fmt.Sprintf("'%v' contains an invalid NGINX variable. Accepted variables are: %v", nVar, mapToPrettyString(validVars)) 25 allErrs = append(allErrs, field.Invalid(fieldPath, nVar, msg)) 26 } 27 return allErrs 28 } 29 30 // isValidSpecialHeaderLikeVariable validates special variables $http_, $jwt_header_, $jwt_claim_ 31 func isValidSpecialHeaderLikeVariable(value string) []string { 32 // underscores in a header-like variable represent '-'. 33 errMsgs := validation.IsHTTPHeaderName(strings.Replace(value, "_", "-", -1)) 34 if len(errMsgs) >= 1 || strings.Contains(value, "-") { 35 return []string{"a valid special variable must consists of alphanumeric characters or '_'"} 36 } 37 return nil 38 } 39 40 func parseSpecialVariable(nVar string, fieldPath *field.Path) (name string, value string, allErrs field.ErrorList) { 41 // parse NGINX Plus variables 42 if strings.HasPrefix(nVar, "jwt_header") || strings.HasPrefix(nVar, "jwt_claim") { 43 parts := strings.SplitN(nVar, "_", 3) 44 if len(parts) != 3 { 45 allErrs = append(allErrs, field.Invalid(fieldPath, nVar, "is invalid variable")) 46 return name, value, allErrs 47 } 48 49 // ex: jwt_header_name_one -> jwt_header, name_one 50 return strings.Join(parts[:2], "_"), parts[2], allErrs 51 } 52 53 // parse common NGINX and NGINX Plus variables 54 parts := strings.SplitN(nVar, "_", 2) 55 if len(parts) != 2 { 56 allErrs = append(allErrs, field.Invalid(fieldPath, nVar, "is invalid variable")) 57 return name, value, allErrs 58 } 59 60 // ex: http_name_one -> http, name_one 61 return parts[0], parts[1], allErrs 62 } 63 64 func validateSpecialVariable(nVar string, fieldPath *field.Path, isPlus bool) field.ErrorList { 65 name, value, allErrs := parseSpecialVariable(nVar, fieldPath) 66 if len(allErrs) > 0 { 67 return allErrs 68 } 69 70 addErrors := func(errors []string) { 71 for _, msg := range errors { 72 allErrs = append(allErrs, field.Invalid(fieldPath, nVar, msg)) 73 } 74 } 75 76 switch name { 77 case "arg": 78 addErrors(isArgumentName(value)) 79 case "http": 80 addErrors(isValidSpecialHeaderLikeVariable(value)) 81 case "cookie": 82 addErrors(isCookieName(value)) 83 case "jwt_header", "jwt_claim": 84 if !isPlus { 85 allErrs = append(allErrs, field.Forbidden(fieldPath, "is only supported in NGINX Plus")) 86 } else { 87 addErrors(isValidSpecialHeaderLikeVariable(value)) 88 } 89 default: 90 allErrs = append(allErrs, field.Invalid(fieldPath, nVar, "unknown special variable")) 91 } 92 93 return allErrs 94 } 95 96 func validateStringWithVariables(str string, fieldPath *field.Path, specialVars []string, validVars map[string]bool, isPlus bool) field.ErrorList { 97 allErrs := field.ErrorList{} 98 99 if strings.HasSuffix(str, "$") { 100 return append(allErrs, field.Invalid(fieldPath, str, "must not end with $")) 101 } 102 103 for i, c := range str { 104 if c == '$' { 105 msg := "variables must be enclosed in curly braces, for example ${host}" 106 107 if str[i+1] != '{' { 108 return append(allErrs, field.Invalid(fieldPath, str, msg)) 109 } 110 111 if !strings.Contains(str[i+1:], "}") { 112 return append(allErrs, field.Invalid(fieldPath, str, msg)) 113 } 114 } 115 } 116 117 nginxVars := captureVariables(str) 118 for _, nVar := range nginxVars { 119 special := false 120 for _, specialVar := range specialVars { 121 if strings.HasPrefix(nVar, specialVar) { 122 special = true 123 break 124 } 125 } 126 127 if special { 128 allErrs = append(allErrs, validateSpecialVariable(nVar, fieldPath, isPlus)...) 129 } else { 130 allErrs = append(allErrs, validateVariable(nVar, validVars, fieldPath)...) 131 } 132 } 133 134 return allErrs 135 } 136 137 func validateTime(time string, fieldPath *field.Path) field.ErrorList { 138 allErrs := field.ErrorList{} 139 140 if time == "" { 141 return allErrs 142 } 143 144 if _, err := configs.ParseTime(time); err != nil { 145 return append(allErrs, field.Invalid(fieldPath, time, err.Error())) 146 } 147 148 return allErrs 149 } 150 151 // http://nginx.org/en/docs/syntax.html 152 const offsetErrMsg = "must consist of numeric characters followed by a valid size suffix. 'k|K|m|M|g|G" 153 154 func validateOffset(offset string, fieldPath *field.Path) field.ErrorList { 155 allErrs := field.ErrorList{} 156 157 if offset == "" { 158 return allErrs 159 } 160 161 if _, err := configs.ParseOffset(offset); err != nil { 162 msg := validation.RegexError(offsetErrMsg, configs.OffsetFmt, "16", "32k", "64M", "2G") 163 return append(allErrs, field.Invalid(fieldPath, offset, msg)) 164 } 165 166 return allErrs 167 } 168 169 // http://nginx.org/en/docs/syntax.html 170 const sizeErrMsg = "must consist of numeric characters followed by a valid size suffix. 'k|K|m|M" 171 172 func validateSize(size string, fieldPath *field.Path) field.ErrorList { 173 allErrs := field.ErrorList{} 174 175 if size == "" { 176 return allErrs 177 } 178 179 if _, err := configs.ParseSize(size); err != nil { 180 msg := validation.RegexError(sizeErrMsg, configs.SizeFmt, "16", "32k", "64M") 181 return append(allErrs, field.Invalid(fieldPath, size, msg)) 182 } 183 return allErrs 184 } 185 186 // validateSecretName checks if a secret name is valid. 187 // It performs the same validation as ValidateSecretName from k8s.io/kubernetes/pkg/apis/core/validation/validation.go. 188 func validateSecretName(name string, fieldPath *field.Path) field.ErrorList { 189 allErrs := field.ErrorList{} 190 191 if name == "" { 192 return allErrs 193 } 194 195 for _, msg := range validation.IsDNS1123Subdomain(name) { 196 allErrs = append(allErrs, field.Invalid(fieldPath, name, msg)) 197 } 198 199 return allErrs 200 } 201 202 func mapToPrettyString(m map[string]bool) string { 203 var out []string 204 205 for k := range m { 206 out = append(out, k) 207 } 208 209 return strings.Join(out, ", ") 210 } 211 212 // validateParameter validates a parameter against a map of valid parameters for the directive 213 func validateParameter(nPar string, validParams map[string]bool, fieldPath *field.Path) field.ErrorList { 214 allErrs := field.ErrorList{} 215 216 if !validParams[nPar] { 217 msg := fmt.Sprintf("'%v' contains an invalid NGINX parameter. Accepted parameters are: %v", nPar, mapToPrettyString(validParams)) 218 allErrs = append(allErrs, field.Invalid(fieldPath, nPar, msg)) 219 } 220 return allErrs 221 }