github.com/avenga/couper@v1.12.2/config/configload/validate.go (about)

     1  package configload
     2  
     3  import (
     4  	"fmt"
     5  	"path"
     6  	"regexp"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/hcl/v2"
    10  	"github.com/hashicorp/hcl/v2/hclsyntax"
    11  	"github.com/zclconf/go-cty/cty"
    12  
    13  	bodySyntax "github.com/avenga/couper/config/body"
    14  	"github.com/avenga/couper/eval"
    15  	"github.com/avenga/couper/utils"
    16  )
    17  
    18  var (
    19  	regexLabel     = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
    20  	reCleanPattern = regexp.MustCompile(`{([^}]+)}`)
    21  )
    22  
    23  // https://datatracker.ietf.org/doc/html/rfc7231#section-4
    24  // https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6
    25  var methodRegExp = regexp.MustCompile("^[!#$%&'*+\\-.\\^_`|~0-9a-zA-Z]+$")
    26  
    27  func newDiagErr(subject *hcl.Range, summary string) error {
    28  	return hcl.Diagnostics{&hcl.Diagnostic{
    29  		Severity: hcl.DiagError,
    30  		Summary:  summary,
    31  		Subject:  subject,
    32  	}}
    33  }
    34  
    35  func validateBody(body *hclsyntax.Body, afterMerge bool) error {
    36  	for _, outerBlock := range body.Blocks {
    37  		if outerBlock.Type == definitions {
    38  			uniqueBackends := make(map[string]struct{})
    39  			uniqueACs := make(map[string]struct{})
    40  			uniqueJWTSigningProfiles := make(map[string]struct{})
    41  			uniqueProxies := make(map[string]struct{})
    42  			for _, innerBlock := range outerBlock.Body.Blocks {
    43  				if !afterMerge {
    44  					if len(innerBlock.Labels) == 0 {
    45  						return newDiagErr(&innerBlock.OpenBraceRange, "missing label")
    46  					}
    47  				}
    48  				label := innerBlock.Labels[0]
    49  				label = strings.TrimSpace(label)
    50  				labelRange := innerBlock.LabelRanges[0]
    51  
    52  				switch innerBlock.Type {
    53  				case backend:
    54  					if !afterMerge {
    55  						if err := validLabel(label, &labelRange); err != nil {
    56  							return err
    57  						}
    58  
    59  						if strings.HasPrefix(label, "anonymous_") {
    60  							return newDiagErr(&labelRange, "backend label must not start with 'anonymous_'")
    61  						}
    62  
    63  						if _, set := uniqueBackends[label]; set {
    64  							return newDiagErr(&labelRange, "backend labels must be unique")
    65  						}
    66  						uniqueBackends[label] = struct{}{}
    67  					}
    68  
    69  					if err := validateBackendTLS(innerBlock); err != nil {
    70  						return err
    71  					}
    72  
    73  				case "basic_auth", "beta_oauth2", "oidc", "saml":
    74  					err := checkAC(uniqueACs, label, labelRange, afterMerge)
    75  					if err != nil {
    76  						return err
    77  					}
    78  				case "jwt":
    79  					err := checkAC(uniqueACs, label, labelRange, afterMerge)
    80  					if err != nil {
    81  						return err
    82  					}
    83  
    84  					attrs, _ := innerBlock.Body.JustAttributes() // just get attributes, ignore diags for now
    85  					if _, set := attrs["signing_ttl"]; set {
    86  						err = checkJWTSigningProfiles(uniqueJWTSigningProfiles, label, labelRange)
    87  						if err != nil {
    88  							return err
    89  						}
    90  					}
    91  				case "jwt_signing_profile":
    92  					err := checkJWTSigningProfiles(uniqueJWTSigningProfiles, label, labelRange)
    93  					if err != nil {
    94  						return err
    95  					}
    96  				case proxy:
    97  					if !afterMerge {
    98  						if _, set := uniqueProxies[label]; set {
    99  							return newDiagErr(&labelRange, "proxy labels must be unique")
   100  						}
   101  						uniqueProxies[label] = struct{}{}
   102  					}
   103  				}
   104  			}
   105  		} else if outerBlock.Type == server {
   106  			uniqueEndpoints := make(map[string]struct{})
   107  			serverBasePath, err := getBasePath(outerBlock, afterMerge)
   108  			if err != nil {
   109  				return err
   110  			}
   111  
   112  			serverBasePath = path.Join("/", serverBasePath)
   113  			for _, innerBlock := range outerBlock.Body.Blocks {
   114  				if innerBlock.Type == endpoint {
   115  					if err = registerEndpointPattern(uniqueEndpoints, serverBasePath, innerBlock, afterMerge); err != nil {
   116  						return err
   117  					}
   118  				} else if innerBlock.Type == api {
   119  					var apiBasePath string
   120  					if apiBasePath, err = getBasePath(innerBlock, afterMerge); err != nil {
   121  						return err
   122  					}
   123  
   124  					apiBasePath = path.Join(serverBasePath, apiBasePath)
   125  					for _, innerInnerBlock := range innerBlock.Body.Blocks {
   126  						if innerInnerBlock.Type == endpoint {
   127  							if err = registerEndpointPattern(uniqueEndpoints, apiBasePath, innerInnerBlock, afterMerge); err != nil {
   128  								return err
   129  							}
   130  						}
   131  					}
   132  				}
   133  			}
   134  		}
   135  	}
   136  
   137  	return nil
   138  }
   139  
   140  func checkPathSegments(pathType, path string, r hcl.Range) error {
   141  	for _, segment := range strings.Split(path, "/") {
   142  		if segment == "." || segment == ".." {
   143  			return newDiagErr(&r, pathType+` must not contain "." or ".." segments`)
   144  		}
   145  	}
   146  	return nil
   147  }
   148  
   149  func getBasePath(bl *hclsyntax.Block, afterMerge bool) (string, error) {
   150  	basePath := ""
   151  	if bp, set := bl.Body.Attributes["base_path"]; set {
   152  		bpv, diags := bp.Expr.Value(nil)
   153  		if diags.HasErrors() {
   154  			return "", diags
   155  		}
   156  		r := bp.Expr.StartRange()
   157  		if !afterMerge && bpv.Type() != cty.String {
   158  			return "", newDiagErr(&r, "base_path must evaluate to string")
   159  		}
   160  		basePath = bpv.AsString()
   161  		if !afterMerge {
   162  			if err := checkPathSegments("base_path", basePath, r); err != nil {
   163  				return "", err
   164  			}
   165  		}
   166  	}
   167  
   168  	return basePath, nil
   169  }
   170  
   171  func registerEndpointPattern(endpointPatterns map[string]struct{}, basePath string, bl *hclsyntax.Block, afterMerge bool) error {
   172  	pattern := bl.Labels[0]
   173  	if !afterMerge {
   174  		if !strings.HasPrefix(pattern, "/") {
   175  			return newDiagErr(&bl.LabelRanges[0], `endpoint path pattern must start with "/"`)
   176  		}
   177  
   178  		if err := checkPathSegments("endpoint path pattern", pattern, bl.LabelRanges[0]); err != nil {
   179  			return err
   180  		}
   181  	}
   182  
   183  	pattern = utils.JoinOpenAPIPath(basePath, pattern)
   184  	pattern = reCleanPattern.ReplaceAllString(pattern, "{}")
   185  	if _, set := endpointPatterns[pattern]; set {
   186  		return newDiagErr(&bl.LabelRanges[0], "duplicate endpoint")
   187  	}
   188  
   189  	endpointPatterns[pattern] = struct{}{}
   190  	return nil
   191  }
   192  
   193  func checkAC(acLabels map[string]struct{}, label string, labelRange hcl.Range, afterMerge bool) error {
   194  	if !afterMerge {
   195  		if label == "" {
   196  			return newDiagErr(&labelRange, "accessControl requires a label")
   197  		}
   198  
   199  		if eval.IsReservedContextName(label) {
   200  			return newDiagErr(&labelRange, "accessControl uses reserved name as label")
   201  		}
   202  	}
   203  
   204  	if _, set := acLabels[label]; set {
   205  		return newDiagErr(&labelRange, "AC labels must be unique")
   206  	}
   207  
   208  	acLabels[label] = struct{}{}
   209  	return nil
   210  }
   211  
   212  func checkJWTSigningProfiles(spLabels map[string]struct{}, label string, labelRange hcl.Range) error {
   213  	if _, set := spLabels[label]; set {
   214  		return newDiagErr(&labelRange, "JWT signing profile labels must be unique")
   215  	}
   216  
   217  	spLabels[label] = struct{}{}
   218  	return nil
   219  }
   220  
   221  func validLabel(name string, subject *hcl.Range) error {
   222  	if name == "" {
   223  		return newDiagErr(subject, "label is empty")
   224  	}
   225  
   226  	if !regexLabel.MatchString(name) {
   227  		return newDiagErr(subject, "label contains invalid character(s), allowed are 'a-z', 'A-Z', '0-9' and '_'")
   228  	}
   229  
   230  	return nil
   231  }
   232  
   233  func uniqueLabelName(scopeOfUniqueness string, unique map[string]struct{}, name string, hr *hcl.Range) error {
   234  	if _, exist := unique[name]; exist {
   235  		return newDiagErr(hr, fmt.Sprintf("%s names (either default or explicitly set via label) must be unique: %q", scopeOfUniqueness, name))
   236  	}
   237  
   238  	unique[name] = struct{}{}
   239  
   240  	return nil
   241  }
   242  
   243  func verifyBodyAttributes(blockName string, body *hclsyntax.Body) error {
   244  	_, existsBody := body.Attributes["body"]
   245  	_, existsFormBody := body.Attributes["form_body"]
   246  	_, existsJSONBody := body.Attributes["json_body"]
   247  
   248  	if existsBody && existsFormBody || existsBody && existsJSONBody || existsFormBody && existsJSONBody {
   249  		rangeAttr := "body"
   250  		if !existsBody {
   251  			rangeAttr = "form_body"
   252  		}
   253  
   254  		r := body.Attributes[rangeAttr].Range()
   255  		return newDiagErr(&r,
   256  			blockName+" can only have one of body, form_body or json_body attributes")
   257  	}
   258  
   259  	return nil
   260  }
   261  
   262  func verifyResponseBodyAttrs(b *hclsyntax.Body) error {
   263  	_, existsBody := b.Attributes["body"]
   264  	_, existsJSONBody := b.Attributes["json_body"]
   265  	if existsBody && existsJSONBody {
   266  		r := b.Attributes["body"].Range()
   267  		return newDiagErr(&r, "response can only have one of body or json_body attributes")
   268  	}
   269  	return nil
   270  }
   271  
   272  var invalidAttributes = []string{"disable_certificate_validation", "disable_connection_reuse", "http2", "max_connections"}
   273  
   274  func invalidRefinement(body *hclsyntax.Body) error {
   275  	const message = "backend reference: refinement for %q is not permitted"
   276  	for _, name := range invalidAttributes {
   277  		attr, exist := body.Attributes[name]
   278  		if exist {
   279  			return newDiagErr(&attr.NameRange, fmt.Sprintf(message, attr.Name))
   280  		}
   281  	}
   282  
   283  	if len(body.Blocks) > 0 {
   284  		r := body.Blocks[0].DefRange()
   285  		return newDiagErr(&r, fmt.Sprintf(message, body.Blocks[0].Type))
   286  	}
   287  
   288  	return nil
   289  }
   290  
   291  func invalidOriginRefinement(reference, params *hclsyntax.Body) error {
   292  	const origin = "origin"
   293  	refOrigin := reference.Attributes[origin]
   294  	paramOrigin := params.Attributes[origin]
   295  
   296  	if paramOrigin != nil && refOrigin != nil {
   297  		if paramOrigin.Expr != refOrigin.Expr {
   298  			r := paramOrigin.Range()
   299  			return newDiagErr(&r, "backend reference: origin must be equal")
   300  		}
   301  	}
   302  	return nil
   303  }
   304  
   305  func validMethods(methods []string, attr *hclsyntax.Attribute) error {
   306  	for _, method := range methods {
   307  		if !methodRegExp.MatchString(method) {
   308  			r := attr.Range()
   309  			return newDiagErr(&r, "method contains invalid character(s)")
   310  		}
   311  	}
   312  
   313  	return nil
   314  }
   315  
   316  // validateBackendTLS determines irrational backend certificate configuration.
   317  func validateBackendTLS(block *hclsyntax.Block) error {
   318  	validateCertAttr := block.Body.Attributes["disable_certificate_validation"]
   319  	if tlsBlocks := bodySyntax.BlocksOfType(block.Body, "tls"); validateCertAttr != nil && len(tlsBlocks) > 0 {
   320  		var attrName string
   321  		if caCert := tlsBlocks[0].Body.Attributes["server_ca_certificate"]; caCert != nil {
   322  			attrName = caCert.Name
   323  		} else if caCertFile := tlsBlocks[0].Body.Attributes["server_ca_certificate_file"]; caCertFile != nil {
   324  			attrName = caCertFile.Name
   325  		}
   326  		if attrName != "" {
   327  			r := tlsBlocks[0].Range()
   328  			return newDiagErr(&r, fmt.Sprintf("configured 'disable_certificate_validation' along with '%s' attribute", attrName))
   329  		}
   330  	}
   331  	return nil
   332  }
   333  
   334  func checkReferencedAccessControls(body *hclsyntax.Body, acs, dacs []string, definedACs map[string]struct{}) error {
   335  	for _, ac := range acs {
   336  		if ac = strings.TrimSpace(ac); ac == "" {
   337  			continue
   338  		}
   339  		if _, set := definedACs[ac]; !set {
   340  			r := body.Attributes["access_control"].Expr.Range()
   341  			return newDiagErr(&r, fmt.Sprintf("referenced access control %q is not defined", ac))
   342  		}
   343  	}
   344  	for _, ac := range dacs {
   345  		if ac = strings.TrimSpace(ac); ac == "" {
   346  			continue
   347  		}
   348  		if _, set := definedACs[ac]; !set {
   349  			r := body.Attributes["disable_access_control"].Expr.Range()
   350  			return newDiagErr(&r, fmt.Sprintf("referenced access control %q is not defined", ac))
   351  		}
   352  	}
   353  
   354  	return nil
   355  }