github.com/avenga/couper@v1.12.2/config/configload/backend.go (about) 1 package configload 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/hashicorp/hcl/v2" 8 "github.com/hashicorp/hcl/v2/gohcl" 9 "github.com/hashicorp/hcl/v2/hclsyntax" 10 "github.com/zclconf/go-cty/cty" 11 12 "github.com/avenga/couper/config" 13 hclbody "github.com/avenga/couper/config/body" 14 ) 15 16 var backendBlockSchema = &hcl.BodySchema{ 17 Blocks: []hcl.BlockHeaderSchema{ 18 { 19 Type: backend, 20 LabelNames: []string{"name"}, 21 }, 22 }, 23 } 24 25 func newDefaultBackend() *hclsyntax.Body { 26 return &hclsyntax.Body{ 27 Attributes: map[string]*hclsyntax.Attribute{ 28 "connect_timeout": { 29 Name: "connect_timeout", 30 Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal("10s")}, 31 }, 32 "ttfb_timeout": { 33 Name: "ttfb_timeout", 34 Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal("60s")}, 35 }, 36 "timeout": { 37 Name: "timeout", 38 Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal("300s")}, 39 }, 40 }, 41 } 42 } 43 44 // PrepareBackend is a method which is mandatory to call for preparing any kind of backend. 45 // This applies to defined, reference, anonymous and endpoint/url related configurations. 46 // This method will be called recursively and is used as wrapped injector for 47 // access-control backends via config.PrepareBackendFunc. 48 func PrepareBackend(helper *helper, attrName, attrValue string, block config.Body) (*hclsyntax.Body, error) { 49 var reference string // backend definitions 50 var backendBody *hclsyntax.Body 51 var err error 52 53 reference, backendBody, err = getBackendReference(block) 54 if err != nil { 55 return nil, err 56 } 57 58 if strings.HasSuffix(attrName, "_backend") && attrValue != "" { // specific attribute overrides; prefer 59 reference = attrValue 60 } 61 62 if reference != "" { 63 refBody, ok := helper.defsBackends[reference] 64 if !ok { 65 r := block.HCLBody().SrcRange 66 if backendBody != nil { 67 r = backendBody.SrcRange 68 } 69 return nil, newDiagErr(&r, fmt.Sprintf("referenced backend %q is not defined", reference)) 70 } 71 72 if backendBody == nil { 73 if attrName == "_init" { // initial definitions case 74 backendBody = hclbody.MergeBodies(refBody, newDefaultBackend(), false) 75 } else { // plain reference without params 76 return refBody, nil 77 } 78 } else { // with backend params - do not repeat referenced hcl stack, just the name 79 if err = invalidRefinement(backendBody); err != nil { 80 return nil, err 81 } 82 if err = invalidOriginRefinement(refBody, backendBody); err != nil { 83 return nil, err 84 } 85 setName(reference, backendBody) 86 // no child blocks are allowed, so no need to try to wrap with oauth2 or token request 87 return backendBody, nil 88 } 89 } else { // anonymous backend block 90 var labelRange *hcl.Range 91 var labelSuffix string 92 // anonymous backend based on a single attr, take the attr range instead 93 if attrName != "" && reference == "" { 94 labelRange, labelSuffix = refineAnonLabel(attrName, block.HCLBody()) 95 } 96 97 anonLabel := newAnonLabel(block.HCLBody(), labelRange) + labelSuffix 98 // ensure our default settings 99 if backendBody == nil { 100 backendBody = newDefaultBackend() 101 } else { 102 switch labelSuffix { 103 case "_jwks_uri_backend", "_token_backend", "_userinfo_backend": 104 copied := *backendBody 105 // create new Attributes to allow different name later 106 copied.Attributes = make(map[string]*hclsyntax.Attribute, len(backendBody.Attributes)) 107 for k, v := range backendBody.Attributes { 108 copied.Attributes[k] = v 109 } 110 backendBody = hclbody.MergeBodies(&copied, newDefaultBackend(), false) 111 default: 112 // with OIDC this is used for _configuration_backend 113 backendBody = hclbody.MergeBodies(backendBody, newDefaultBackend(), false) 114 } 115 } 116 117 setName(anonLabel, backendBody) 118 } 119 120 // watch out for oauth blocks and nested backend definitions 121 backendBody, err = setOAuth2Backend(helper, backendBody) 122 if err != nil { 123 return nil, err 124 } 125 126 // watch out for beta_token_request blocks and nested backend definitions 127 return setTokenRequestBackend(helper, backendBody) 128 } 129 130 // getBackendReference reads a referenced backend name and the refined backend block content if any. 131 func getBackendReference(b config.Body) (string, *hclsyntax.Body, error) { 132 var reference string 133 134 backends := hclbody.BlocksOfType(b.HCLBody(), backend) 135 if len(backends) == 0 { 136 if beref, ok := b.(config.BackendReference); ok { 137 if beref.Reference() != "" { 138 reference = beref.Reference() 139 } 140 } 141 return reference, nil, nil 142 } 143 144 body := backends[0].Body 145 if len(backends[0].Labels) > 0 { 146 reference = backends[0].Labels[0] 147 } 148 return reference, body, nil 149 } 150 151 // setOAuth2Backend prepares a nested backend within a backend-oauth2 block. 152 // TODO: Check a possible circular dependency with given parent backend(s). 153 func setOAuth2Backend(helper *helper, parent *hclsyntax.Body) (*hclsyntax.Body, error) { 154 oauthBlocks := hclbody.BlocksOfType(parent, oauth2) 155 if len(oauthBlocks) == 0 { 156 return parent, nil 157 } 158 159 // oauth block exists, read out backend configuration 160 oauthBody := oauthBlocks[0].Body 161 conf := &config.OAuth2ReqAuth{} 162 if diags := gohcl.DecodeBody(oauthBody, helper.context, conf); diags.HasErrors() { 163 return nil, diags 164 } 165 166 backendBody, err := PrepareBackend(helper, "", conf.TokenEndpoint, conf) 167 if err != nil { 168 return nil, err 169 } 170 171 if len(hclbody.BlocksOfType(oauthBody, backend)) == 0 { 172 // only add backend block, if not already there 173 backendBlock := &hclsyntax.Block{ 174 Type: backend, 175 Body: backendBody, 176 } 177 oauthBody.Blocks = append(oauthBody.Blocks, backendBlock) 178 } 179 180 return parent, nil 181 } 182 183 func checkTokenRequestLabels(trbs []*hclsyntax.Block, unique map[string]struct{}) error { 184 for _, trb := range trbs { 185 label := config.DefaultNameLabel 186 dr := trb.DefRange() 187 r := &dr 188 if len(trb.Labels) > 0 { 189 label = trb.Labels[0] 190 r = &trb.LabelRanges[0] 191 if err := validLabel(label, r); err != nil { 192 return err 193 } 194 } else { 195 // add "default" label if no label is configured 196 trb.Labels = append(trb.Labels, label) 197 } 198 199 if err := uniqueLabelName("token request", unique, label, r); err != nil { 200 return err 201 } 202 203 } 204 return nil 205 } 206 207 // setTokenRequestBackend prepares a nested backend within each backend-tokenRequest block. 208 // TODO: Check a possible circular dependency with given parent backend(s). 209 func setTokenRequestBackend(helper *helper, parent *hclsyntax.Body) (*hclsyntax.Body, error) { 210 tokenRequestBlocks := hclbody.BlocksOfType(parent, tokenRequest) 211 if len(tokenRequestBlocks) == 0 { 212 return parent, nil 213 } 214 215 unique := map[string]struct{}{} 216 err := checkTokenRequestLabels(tokenRequestBlocks, unique) 217 if err != nil { 218 return nil, err 219 } 220 221 // beta_token_request block exists, read out backend configuration 222 for _, tokenRequestBlock := range tokenRequestBlocks { 223 tokenRequestBody := tokenRequestBlock.Body 224 conf := &config.TokenRequest{} 225 if diags := gohcl.DecodeBody(tokenRequestBody, helper.context, conf); diags.HasErrors() { 226 return nil, diags 227 } 228 229 if err = verifyBodyAttributes(tokenRequest, tokenRequestBody); err != nil { 230 return nil, err 231 } 232 233 hclbody.RenameAttribute(tokenRequestBody, "headers", "set_request_headers") 234 hclbody.RenameAttribute(tokenRequestBody, "query_params", "set_query_params") 235 236 backendBody, berr := PrepareBackend(helper, "", conf.URL, conf) 237 if berr != nil { 238 return nil, berr 239 } 240 241 if len(hclbody.BlocksOfType(tokenRequestBody, backend)) == 0 { 242 // only add backend block, if not already there 243 backendBlock := &hclsyntax.Block{ 244 Type: backend, 245 Body: backendBody, 246 } 247 tokenRequestBody.Blocks = append(tokenRequestBody.Blocks, backendBlock) 248 } 249 } 250 251 return parent, nil 252 } 253 254 func setName(nameValue string, backendBody *hclsyntax.Body) { 255 backendBody.Attributes["name"] = &hclsyntax.Attribute{ 256 Name: "name", 257 Expr: &hclsyntax.LiteralValueExpr{Val: cty.StringVal(nameValue)}, 258 } 259 } 260 261 func newAnonLabel(body *hclsyntax.Body, labelRange *hcl.Range) string { 262 const anon = "anonymous" 263 itemRange := labelRange 264 if itemRange == nil { 265 itemRange = getRange(body) 266 } 267 268 return fmt.Sprintf("%s_%d_%d", anon, 269 itemRange.Start.Line, 270 itemRange.Start.Column, 271 ) 272 } 273 274 func refineAnonLabel(attrName string, body *hclsyntax.Body) (labelRange *hcl.Range, labelSuffix string) { 275 if attr, exist := body.Attributes[attrName]; exist { 276 labelRange = &attr.NameRange 277 } 278 labelSuffix += "_" + attrName 279 return labelRange, labelSuffix 280 }