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  }