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  }