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 }