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 }