github.com/avenga/couper@v1.12.2/config/runtime/backend.go (about)

     1  package runtime
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"net/http"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/hcl/v2"
    10  	"github.com/hashicorp/hcl/v2/gohcl"
    11  	"github.com/hashicorp/hcl/v2/hclsyntax"
    12  	"github.com/sirupsen/logrus"
    13  	"github.com/zclconf/go-cty/cty"
    14  
    15  	"github.com/avenga/couper/backend"
    16  	"github.com/avenga/couper/cache"
    17  	"github.com/avenga/couper/config"
    18  	hclbody "github.com/avenga/couper/config/body"
    19  	"github.com/avenga/couper/errors"
    20  	"github.com/avenga/couper/eval"
    21  	"github.com/avenga/couper/handler/producer"
    22  	"github.com/avenga/couper/handler/ratelimit"
    23  	"github.com/avenga/couper/handler/transport"
    24  	"github.com/avenga/couper/handler/validation"
    25  )
    26  
    27  func NewBackend(ctx *hcl.EvalContext, body *hclsyntax.Body, log *logrus.Entry,
    28  	conf *config.Couper, store *cache.MemoryStore) (http.RoundTripper, error) {
    29  	const prefix = "backend_"
    30  	name, err := getBackendName(ctx, body)
    31  
    32  	if err != nil {
    33  		return nil, err
    34  	}
    35  
    36  	// Making use of the store here since a global variable leads to extra efforts for integration tests.
    37  	// The store is newly created per run.
    38  	b := store.Get(prefix + name)
    39  	if b != nil {
    40  		return backend.NewContext(body, b.(http.RoundTripper)), nil
    41  	}
    42  
    43  	b, err = newBackend(ctx, body, log, conf, store)
    44  	if err != nil {
    45  		return nil, errors.Configuration.Label(name).With(err)
    46  	}
    47  
    48  	// to prevent weird debug sessions; max to set the internal memStore ttl limit.
    49  	store.Set(prefix+name, b, math.MaxInt64)
    50  
    51  	return b.(http.RoundTripper), nil
    52  }
    53  
    54  func newBackend(evalCtx *hcl.EvalContext, backendCtx *hclsyntax.Body, log *logrus.Entry,
    55  	conf *config.Couper, memStore *cache.MemoryStore) (http.RoundTripper, error) {
    56  	beConf := &config.Backend{}
    57  	if diags := gohcl.DecodeBody(backendCtx, evalCtx, beConf); diags.HasErrors() {
    58  		return nil, diags
    59  	}
    60  
    61  	var err error
    62  	if beConf.Name == "" {
    63  		beConf.Name, err = getBackendName(evalCtx, backendCtx)
    64  		if err != nil {
    65  			return nil, err
    66  		}
    67  	}
    68  
    69  	tc := &transport.Config{
    70  		BackendName:            beConf.Name,
    71  		Certificate:            conf.Settings.Certificate,
    72  		Context:                conf.Context,
    73  		DisableCertValidation:  beConf.DisableCertValidation,
    74  		DisableConnectionReuse: beConf.DisableConnectionReuse,
    75  		HTTP2:                  beConf.HTTP2,
    76  		NoProxyFromEnv:         conf.Settings.NoProxyFromEnv,
    77  		MaxConnections:         beConf.MaxConnections,
    78  	}
    79  
    80  	tc.CACertificate, tc.ClientCertificate, err = transport.ReadCertificates(beConf.TLS)
    81  	if err != nil {
    82  		return nil, hcl.Diagnostics{&hcl.Diagnostic{
    83  			Severity: hcl.DiagError,
    84  			Summary:  err.(errors.GoError).LogError(),
    85  			Subject:  &backendCtx.SrcRange,
    86  		}}
    87  	}
    88  
    89  	if len(beConf.RateLimits) > 0 {
    90  		if strings.HasPrefix(beConf.Name, "anonymous_") {
    91  			return nil, fmt.Errorf("anonymous backend '%s' cannot define 'beta_rate_limit' block(s)", beConf.Name)
    92  		}
    93  
    94  		tc.RateLimits, err = ratelimit.ConfigureRateLimits(conf.Context, beConf.RateLimits, log)
    95  		if err != nil {
    96  			return nil, err
    97  		}
    98  	}
    99  
   100  	options := &transport.BackendOptions{}
   101  
   102  	opts, err := validation.NewOpenAPIOptions(beConf.OpenAPI)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	options.OpenAPI = opts
   108  
   109  	if beConf.Health != nil {
   110  		origin, diags := eval.ValueFromBodyAttribute(evalCtx, backendCtx, "origin")
   111  		if diags != nil {
   112  			return nil, diags
   113  		}
   114  
   115  		if origin == cty.NilVal {
   116  			return nil, fmt.Errorf("missing origin for backend %q", beConf.Name)
   117  		}
   118  
   119  		options.HealthCheck, err = config.NewHealthCheck(origin.AsString(), beConf.Health, conf)
   120  		if err != nil {
   121  			return nil, err
   122  		}
   123  	}
   124  
   125  	for _, blockType := range []string{
   126  		config.OAuthBlockSchema.Blocks[0].Type,
   127  		config.TokenRequestBlockSchema.Blocks[0].Type,
   128  	} {
   129  		blocks := hclbody.BlocksOfType(backendCtx, blockType)
   130  		for _, block := range blocks {
   131  			var requestAuthorizer transport.RequestAuthorizer
   132  			requestAuthorizer, err = newRequestAuthorizer(evalCtx, block, log, conf, memStore)
   133  			if err != nil {
   134  				return nil, err
   135  			}
   136  			options.RequestAuthz = append(options.RequestAuthz, requestAuthorizer)
   137  		}
   138  	}
   139  
   140  	b := transport.NewBackend(backendCtx, tc, options, log)
   141  	return b, nil
   142  }
   143  
   144  func newRequestAuthorizer(evalCtx *hcl.EvalContext, block *hclsyntax.Block,
   145  	log *logrus.Entry, conf *config.Couper, memStore *cache.MemoryStore) (transport.RequestAuthorizer, error) {
   146  	var authorizerConfig interface{}
   147  	switch block.Type {
   148  	case config.OAuthBlockSchema.Blocks[0].Type:
   149  		var one uint8 = 1
   150  		authorizerConfig = &config.OAuth2ReqAuth{
   151  			Retries: &one,
   152  		}
   153  	case config.TokenRequestBlockSchema.Blocks[0].Type:
   154  		// block is guaranteed to have a label ("default" being added at configload)
   155  		authorizerConfig = &config.TokenRequest{Name: block.Labels[0]}
   156  	default:
   157  		return nil, errors.Configuration.Messagef("request authorizer not implemented: %s", block.Type)
   158  	}
   159  
   160  	if diags := gohcl.DecodeBody(block.Body, evalCtx, authorizerConfig); diags.HasErrors() {
   161  		return nil, diags
   162  	}
   163  
   164  	backendBlocks := hclbody.BlocksOfType(block.Body, "backend")
   165  	if len(backendBlocks) == 0 {
   166  		r := block.Body.SrcRange
   167  		diag := &hcl.Diagnostics{&hcl.Diagnostic{
   168  			Subject: &r,
   169  			Summary: "missing backend initialization",
   170  		}}
   171  		return nil, errors.Configuration.Label("unexpected").With(diag)
   172  	}
   173  
   174  	innerBackend := backendBlocks[0] // backend block is set by configload package
   175  	authorizerBackend, err := NewBackend(evalCtx, innerBackend.Body, log, conf, memStore)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  
   180  	switch impl := authorizerConfig.(type) {
   181  	case *config.OAuth2ReqAuth:
   182  		return transport.NewOAuth2ReqAuth(evalCtx, impl, memStore, authorizerBackend)
   183  	case *config.TokenRequest:
   184  		req := &producer.Request{
   185  			Backend: authorizerBackend,
   186  			Context: impl.HCLBody(),
   187  			Name:    impl.Name,
   188  		}
   189  		return transport.NewTokenRequest(impl, memStore, req)
   190  	default:
   191  		return nil, errors.Configuration.Message("unknown authorizer type")
   192  	}
   193  }
   194  
   195  func getBackendName(evalCtx *hcl.EvalContext, backendCtx *hclsyntax.Body) (string, error) {
   196  	if n, exist := backendCtx.Attributes["name"]; exist {
   197  		v, err := eval.Value(evalCtx, n.Expr)
   198  		if err != nil {
   199  			return "", err
   200  		}
   201  
   202  		return v.AsString(), nil
   203  	}
   204  
   205  	return "", nil
   206  }