diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c6c85a69..751a03937 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ Unreleased changes are available as `avenga/couper:edge` container.
* **Changed**
* Use nested `jwt_signing_profile` block in [`oauth2` block](https://docs.couper.io/configuration/block/oauth2) for `grant_type` `"urn:ietf:params:oauth:grant-type:jwt-bearer"` in absence of `assertion` attribute ([#619](https://github.com/avenga/couper/pull/619))
* Improved the way an SPA `bootstrap_file` gets cached and served in combination with `bootstrap_data` ([#656](https://github.com/avenga/couper/pull/656))
+ * Harmonized and improved logged error information for references to undefined blocks ([#651](https://github.com/avenga/couper/pull/651))
---
diff --git a/config/configload/endpoint.go b/config/configload/endpoint.go
index f77c153ae..5e9cf9b5d 100644
--- a/config/configload/endpoint.go
+++ b/config/configload/endpoint.go
@@ -25,7 +25,7 @@ func newCatchAllEndpoint() *config.Endpoint {
}
}
-func refineEndpoints(helper *helper, endpoints config.Endpoints, checkPathPattern bool) error {
+func refineEndpoints(helper *helper, endpoints config.Endpoints, checkPathPattern bool, definedACs map[string]struct{}) error {
var err error
for _, ep := range endpoints {
@@ -38,6 +38,12 @@ func refineEndpoints(helper *helper, endpoints config.Endpoints, checkPathPatter
}
endpointBody := ep.HCLBody()
+ if definedACs != nil {
+ if err := checkReferencedAccessControls(endpointBody, ep.AccessControl, ep.DisableAccessControl, definedACs); err != nil {
+ return err
+ }
+ }
+
rp := endpointBody.Attributes["beta_required_permission"]
if rp != nil {
ep.RequiredPermission = rp.Expr
diff --git a/config/configload/endpoint_test.go b/config/configload/endpoint_test.go
index 6ea10d7c6..23cbb2bd2 100644
--- a/config/configload/endpoint_test.go
+++ b/config/configload/endpoint_test.go
@@ -8,7 +8,7 @@ import (
)
func Test_refineEndpoints_noPattern(t *testing.T) {
- err := refineEndpoints(nil, config.Endpoints{{Pattern: ""}}, true)
+ err := refineEndpoints(nil, config.Endpoints{{Pattern: ""}}, true, nil)
if err == nil || !strings.HasSuffix(err.Error(), "endpoint: missing path pattern; ") {
t.Errorf("refineEndpoints() error = %v, wantErr: endpoint: missing path pattern ", err)
}
diff --git a/config/configload/error_handler.go b/config/configload/error_handler.go
index f3ff68cc6..18e9f6fec 100644
--- a/config/configload/error_handler.go
+++ b/config/configload/error_handler.go
@@ -152,7 +152,7 @@ func newErrorHandlerConfig(content kindContent, helper *helper) (*config.ErrorHa
Requests: errHandlerConf.Requests,
}
- if err := refineEndpoints(helper, config.Endpoints{ep}, false); err != nil {
+ if err := refineEndpoints(helper, config.Endpoints{ep}, false, nil); err != nil {
return nil, err
}
diff --git a/config/configload/load.go b/config/configload/load.go
index 3ff945fb8..59ab42c40 100644
--- a/config/configload/load.go
+++ b/config/configload/load.go
@@ -368,6 +368,23 @@ func LoadConfig(body *hclsyntax.Body) (*config.Couper, error) {
WithOAuth2AC(helper.config.Definitions.OAuth2AC).
WithSAML(helper.config.Definitions.SAML)
+ definedACs := make(map[string]struct{})
+ for _, ac := range helper.config.Definitions.BasicAuth {
+ definedACs[ac.Name] = struct{}{}
+ }
+ for _, ac := range helper.config.Definitions.JWT {
+ definedACs[ac.Name] = struct{}{}
+ }
+ for _, ac := range helper.config.Definitions.OAuth2AC {
+ definedACs[ac.Name] = struct{}{}
+ }
+ for _, ac := range helper.config.Definitions.OIDC {
+ definedACs[ac.Name] = struct{}{}
+ }
+ for _, ac := range helper.config.Definitions.SAML {
+ definedACs[ac.Name] = struct{}{}
+ }
+
// Read per server block and merge backend settings which results in a final server configuration.
for _, serverBlock := range hclbody.BlocksOfType(body, server) {
serverConfig := &config.Server{}
@@ -380,6 +397,22 @@ func LoadConfig(body *hclsyntax.Body) (*config.Couper, error) {
serverConfig.Name = serverBlock.Labels[0]
}
+ if err := checkReferencedAccessControls(serverBlock.Body, serverConfig.AccessControl, serverConfig.DisableAccessControl, definedACs); err != nil {
+ return nil, err
+ }
+
+ for _, fileConfig := range serverConfig.Files {
+ if err := checkReferencedAccessControls(fileConfig.HCLBody(), fileConfig.AccessControl, fileConfig.DisableAccessControl, definedACs); err != nil {
+ return nil, err
+ }
+ }
+
+ for _, spaConfig := range serverConfig.SPAs {
+ if err := checkReferencedAccessControls(spaConfig.HCLBody(), spaConfig.AccessControl, spaConfig.DisableAccessControl, definedACs); err != nil {
+ return nil, err
+ }
+ }
+
// Read api blocks and merge backends with server and definitions backends.
for _, apiConfig := range serverConfig.APIs {
apiBody := apiConfig.HCLBody()
@@ -390,12 +423,16 @@ func LoadConfig(body *hclsyntax.Body) (*config.Couper, error) {
}
}
+ if err := checkReferencedAccessControls(apiBody, apiConfig.AccessControl, apiConfig.DisableAccessControl, definedACs); err != nil {
+ return nil, err
+ }
+
rp := apiBody.Attributes["beta_required_permission"]
if rp != nil {
apiConfig.RequiredPermission = rp.Expr
}
- err = refineEndpoints(helper, apiConfig.Endpoints, true)
+ err = refineEndpoints(helper, apiConfig.Endpoints, true, definedACs)
if err != nil {
return nil, err
}
@@ -414,7 +451,7 @@ func LoadConfig(body *hclsyntax.Body) (*config.Couper, error) {
}
// standalone endpoints
- err = refineEndpoints(helper, serverConfig.Endpoints, true)
+ err = refineEndpoints(helper, serverConfig.Endpoints, true, definedACs)
if err != nil {
return nil, err
}
@@ -439,7 +476,7 @@ func LoadConfig(body *hclsyntax.Body) (*config.Couper, error) {
Requests: job.Requests,
}
- err = refineEndpoints(helper, config.Endpoints{endpointConf}, false)
+ err = refineEndpoints(helper, config.Endpoints{endpointConf}, false, nil)
if err != nil {
return nil, err
}
diff --git a/config/configload/merge.go b/config/configload/merge.go
index 319c0599c..a2d720710 100644
--- a/config/configload/merge.go
+++ b/config/configload/merge.go
@@ -767,7 +767,7 @@ func addProxy(block *hclsyntax.Block, proxies map[string]*hclsyntax.Block) error
if !ok {
sr := attr.Expr.StartRange()
- return newDiagErr(&sr, "proxy reference is not defined")
+ return newDiagErr(&sr, fmt.Sprintf("referenced proxy %q is not defined", reference))
}
delete(block.Body.Attributes, proxy)
diff --git a/config/configload/validate.go b/config/configload/validate.go
index 3452b9d8c..7ff975373 100644
--- a/config/configload/validate.go
+++ b/config/configload/validate.go
@@ -330,3 +330,26 @@ func validateBackendTLS(block *hclsyntax.Block) error {
}
return nil
}
+
+func checkReferencedAccessControls(body *hclsyntax.Body, acs, dacs []string, definedACs map[string]struct{}) error {
+ for _, ac := range acs {
+ if ac = strings.TrimSpace(ac); ac == "" {
+ continue
+ }
+ if _, set := definedACs[ac]; !set {
+ r := body.Attributes["access_control"].Expr.Range()
+ return newDiagErr(&r, fmt.Sprintf("referenced access control %q is not defined", ac))
+ }
+ }
+ for _, ac := range dacs {
+ if ac = strings.TrimSpace(ac); ac == "" {
+ continue
+ }
+ if _, set := definedACs[ac]; !set {
+ r := body.Attributes["disable_access_control"].Expr.Range()
+ return newDiagErr(&r, fmt.Sprintf("referenced access control %q is not defined", ac))
+ }
+ }
+
+ return nil
+}
diff --git a/config/configload/validate_test.go b/config/configload/validate_test.go
index 2099a16a4..847eef82d 100644
--- a/config/configload/validate_test.go
+++ b/config/configload/validate_test.go
@@ -1407,6 +1407,11 @@ func TestPermissionMixed(t *testing.T) {
response {}
}
}
+}
+definitions {
+ basic_auth "foo" {
+ password = "asdf"
+ }
}`,
"",
},
@@ -1522,3 +1527,125 @@ definitions {
})
}
}
+
+func Test_checkReferencedAccessControls(t *testing.T) {
+ tests := []struct {
+ name string
+ hcl string
+ error string
+ }{
+ {
+ "missing AC referenced by server access_control",
+ `server {
+ access_control = ["undefined"]
+ }`,
+ `couper.hcl:2,23-36: referenced access control "undefined" is not defined; `,
+ },
+ {
+ "missing AC referenced by server disable_access_control",
+ `server {
+ disable_access_control = ["undefined"]
+ }`,
+ `couper.hcl:2,31-44: referenced access control "undefined" is not defined; `,
+ },
+ {
+ "missing AC referenced by api access_control",
+ `server {
+ api {
+ access_control = ["undefined"]
+ }
+ }`,
+ `couper.hcl:3,25-38: referenced access control "undefined" is not defined; `,
+ },
+ {
+ "missing AC referenced by api disable_access_control",
+ `server {
+ api {
+ disable_access_control = ["undefined"]
+ }
+ }`,
+ `couper.hcl:3,33-46: referenced access control "undefined" is not defined; `,
+ },
+ {
+ "missing AC referenced by files access_control",
+ `server {
+ files {
+ access_control = ["undefined"]
+ document_root = "htdocs"
+ }
+ }`,
+ `couper.hcl:3,25-38: referenced access control "undefined" is not defined; `,
+ },
+ {
+ "missing AC referenced by files disable_access_control",
+ `server {
+ files {
+ disable_access_control = ["undefined"]
+ document_root = "htdocs"
+ }
+ }`,
+ `couper.hcl:3,33-46: referenced access control "undefined" is not defined; `,
+ },
+ {
+ "missing AC referenced by spa access_control",
+ `server {
+ spa {
+ access_control = ["undefined"]
+ bootstrap_file = "foo"
+ paths = ["/**"]
+ }
+ }`,
+ `couper.hcl:3,25-38: referenced access control "undefined" is not defined; `,
+ },
+ {
+ "missing AC referenced by spa disable_access_control",
+ `server {
+ spa {
+ disable_access_control = ["undefined"]
+ bootstrap_file = "foo"
+ paths = ["/**"]
+ }
+ }`,
+ `couper.hcl:3,33-46: referenced access control "undefined" is not defined; `,
+ },
+ {
+ "missing AC referenced by endpoint access_control",
+ `server {
+ endpoint "/" {
+ access_control = ["undefined"]
+ response {
+ body = "OK"
+ }
+ }
+ }`,
+ `couper.hcl:3,25-38: referenced access control "undefined" is not defined; `,
+ },
+ {
+ "missing AC referenced by endpoint disable_access_control",
+ `server {
+ endpoint "/" {
+ disable_access_control = ["undefined"]
+ response {
+ body = "OK"
+ }
+ }
+ }`,
+ `couper.hcl:3,33-46: referenced access control "undefined" is not defined; `,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(subT *testing.T) {
+ _, err := LoadBytes([]byte(tt.hcl), "couper.hcl")
+
+ var errMsg string
+ if err != nil {
+ errMsg = err.Error()
+ }
+
+ if tt.error != errMsg {
+ subT.Errorf("%q: Unexpected configuration error:\n\tWant: %q\n\tGot: %q", tt.name, tt.error, errMsg)
+ }
+ })
+ }
+}
diff --git a/config/files.go b/config/files.go
index 87c77bd66..d31b39da9 100644
--- a/config/files.go
+++ b/config/files.go
@@ -3,6 +3,7 @@ package config
import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
+ "github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/avenga/couper/config/meta"
)
@@ -23,6 +24,11 @@ type Files struct {
Remain hcl.Body `hcl:",remain"`
}
+// HCLBody implements the
interface.
+func (f Files) HCLBody() *hclsyntax.Body {
+ return f.Remain.(*hclsyntax.Body)
+}
+
// Inline implements the interface.
func (f Files) Inline() interface{} {
type Inline struct {
diff --git a/config/runtime/access_control.go b/config/runtime/access_control.go
index 2ca05ab11..bbd04a728 100644
--- a/config/runtime/access_control.go
+++ b/config/runtime/access_control.go
@@ -1,7 +1,6 @@
package runtime
import (
- "fmt"
"strings"
"github.com/avenga/couper/accesscontrol"
@@ -22,15 +21,3 @@ func (m ACDefinitions) Add(name string, ac accesscontrol.AccessControl, eh []*co
ErrorHandler: eh,
}
}
-
-func (m ACDefinitions) MustExist(name string) error {
- if m == nil {
- panic("no accessControl configuration")
- }
-
- if _, ok := m[name]; !ok {
- return fmt.Errorf("accessControl is not defined: " + name)
- }
-
- return nil
-}
diff --git a/config/runtime/server.go b/config/runtime/server.go
index a368b637d..7ee967b09 100644
--- a/config/runtime/server.go
+++ b/config/runtime/server.go
@@ -533,12 +533,7 @@ func configureAccessControls(conf *config.Couper, confCtx *hcl.EvalContext, log
for _, saml := range conf.Definitions.SAML {
confErr := errors.Configuration.Label(saml.Name)
- metadata, err := reader.ReadFromFile("saml2 idp_metadata_file", saml.IdpMetadataFile)
- if err != nil {
- return nil, confErr.With(err)
- }
-
- s, err := ac.NewSAML2ACS(metadata, saml.Name, saml.SpAcsURL, saml.SpEntityID, saml.ArrayAttributes)
+ s, err := ac.NewSAML2ACS(saml.MetadataBytes, saml.Name, saml.SpAcsURL, saml.SpEntityID, saml.ArrayAttributes)
if err != nil {
return nil, confErr.With(err)
}
@@ -651,9 +646,6 @@ func configureProtectedHandler(m ACDefinitions, conf *config.Couper, ctx *hcl.Ev
opts *protectedOptions, log *logrus.Entry) (http.Handler, error) {
var list ac.List
for _, acName := range parentAC.Merge(handlerAC).List() {
- if e := m.MustExist(acName); e != nil {
- return nil, e
- }
eh, err := newErrorHandler(ctx, conf, opts, log, m, acName)
if err != nil {
return nil, err
diff --git a/eval/lib/jwt.go b/eval/lib/jwt.go
index 4c07813d6..127b819d5 100644
--- a/eval/lib/jwt.go
+++ b/eval/lib/jwt.go
@@ -148,14 +148,10 @@ func NewJwtSignFunction(ctx *hcl.EvalContext, jwtSigningConfigs map[string]*JWTS
},
Type: function.StaticReturnType(cty.String),
Impl: func(args []cty.Value, _ cty.Type) (ret cty.Value, err error) {
- if len(jwtSigningConfigs) == 0 {
- return cty.StringVal(""), fmt.Errorf("missing jwt_signing_profile or jwt (with signing_ttl) definitions")
- }
-
label := args[0].AsString()
- signingConfig := jwtSigningConfigs[label]
- if signingConfig == nil {
- return cty.StringVal(""), fmt.Errorf("missing jwt_signing_profile or jwt (with signing_ttl) for given label %q", label)
+ signingConfig, exist := jwtSigningConfigs[label]
+ if !exist {
+ return cty.StringVal(""), fmt.Errorf("missing jwt_signing_profile or jwt (with signing_ttl) block with referenced label %q", label)
}
var claims, argumentClaims, headers map[string]interface{}
diff --git a/eval/lib/jwt_test.go b/eval/lib/jwt_test.go
index c777bed69..15acdf63a 100644
--- a/eval/lib/jwt_test.go
+++ b/eval/lib/jwt_test.go
@@ -862,56 +862,40 @@ func TestJwtSignError(t *testing.T) {
wantErr string
}{
{
- "missing jwt_signing_profile definitions",
+ "missing signing profile definitions",
`
- server "test" {
- endpoint "/" {
- response {
- body = jwt_sign()
- }
- }
- }
+ server {}
definitions {
jwt "MyToken" {
signature_algorithm = "HS256"
key = "$3cRe4"
- claims = {
- iss = to_lower("The_Issuer")
- aud = to_upper("The_Audience")
- }
}
}
`,
"MyToken",
`{"sub": "12345"}`,
- "missing jwt_signing_profile or jwt (with signing_ttl) definitions",
+ `missing jwt_signing_profile or jwt (with signing_ttl) block with referenced label "MyToken"`,
},
{
"No profile for label",
`
- server "test" {
- }
+ server {}
definitions {
jwt_signing_profile "MyToken" {
signature_algorithm = "HS256"
key = "$3cRe4"
ttl = "0"
- claims = {
- iss = to_lower("The_Issuer")
- aud = to_upper("The_Audience")
- }
}
}
`,
"NoProfileForThisLabel",
`{"sub":"12345"}`,
- `missing jwt_signing_profile or jwt (with signing_ttl) for given label "NoProfileForThisLabel"`,
+ `missing jwt_signing_profile or jwt (with signing_ttl) block with referenced label "NoProfileForThisLabel"`,
},
{
"argument claims no object",
`
- server "test" {
- }
+ server {}
definitions {
jwt_signing_profile "MyToken" {
signature_algorithm = "HS256"
@@ -927,23 +911,18 @@ func TestJwtSignError(t *testing.T) {
{
"jwt / No profile for label",
`
- server "test" {
- }
+ server {}
definitions {
jwt "MySelfSignedToken" {
signature_algorithm = "HS256"
key = "$3cRe4"
signing_ttl = "0"
- claims = {
- iss = to_lower("The_Issuer")
- aud = to_upper("The_Audience")
- }
}
}
`,
"NoProfileForThisLabel",
`{"sub": "12345"}`,
- `missing jwt_signing_profile or jwt (with signing_ttl) for given label "NoProfileForThisLabel"`,
+ `missing jwt_signing_profile or jwt (with signing_ttl) block with referenced label "NoProfileForThisLabel"`,
},
{
"jwt / bad curve for algorithm",
@@ -984,8 +963,8 @@ func TestJwtSignError(t *testing.T) {
subT.Error("expected an error, got nothing")
return
}
- if !strings.Contains(err.Error(), tt.wantErr) {
- subT.Errorf("Want:\t%q\nGot:\t%q", tt.wantErr, err.Error())
+ if err.Error() != tt.wantErr {
+ subT.Errorf("\nWant:\t%q\nGot:\t%q", tt.wantErr, err.Error())
}
})
}
diff --git a/eval/lib/oauth2.go b/eval/lib/oauth2.go
index 013b61733..4acf32a29 100644
--- a/eval/lib/oauth2.go
+++ b/eval/lib/oauth2.go
@@ -37,7 +37,7 @@ func NewOAuthAuthorizationURLFunction(ctx *hcl.EvalContext, oauth2s map[string]c
label := args[0].AsString()
oauth2, exist := oauth2s[label]
if !exist {
- return emptyStringVal, fmt.Errorf("undefined reference: %s", label)
+ return emptyStringVal, fmt.Errorf("missing oidc or beta_oauth2 block with referenced label %q", label)
}
authorizationEndpoint, err := oauth2.GetAuthorizationEndpoint()
diff --git a/eval/lib/oauth2_test.go b/eval/lib/oauth2_test.go
index 0fff843f6..a0ba620bd 100644
--- a/eval/lib/oauth2_test.go
+++ b/eval/lib/oauth2_test.go
@@ -14,6 +14,7 @@ import (
"github.com/avenga/couper/cache"
"github.com/avenga/couper/config"
"github.com/avenga/couper/config/configload"
+ "github.com/avenga/couper/config/request"
"github.com/avenga/couper/config/runtime"
"github.com/avenga/couper/eval"
"github.com/avenga/couper/eval/lib"
@@ -419,5 +420,70 @@ definitions {
}
})
}
+}
+
+func TestOAuthAuthorizationURLError(t *testing.T) {
+ tests := []struct {
+ name string
+ config string
+ label string
+ wantErr string
+ }{
+ {
+ "missing oidc/beta_oauth2 definitions",
+ `
+ server {}
+ definitions {
+ }
+ `,
+ "MyLabel",
+ `missing oidc or beta_oauth2 block with referenced label "MyLabel"`,
+ },
+ {
+ "missing referenced oidc/beta_oauth2",
+ `
+ server {}
+ definitions {
+ beta_oauth2 "auth-ref" {
+ grant_type = "authorization_code"
+ client_id = "test-id"
+ client_secret = "test-s3cr3t"
+ authorization_endpoint = "https://a.s./auth"
+ token_endpoint = "https://a.s./token"
+ redirect_uri = "/cb"
+ verifier_method = "ccm_s256"
+ verifier_value = "asdf"
+ }
+ }
+ `,
+ "MyLabel",
+ `missing oidc or beta_oauth2 block with referenced label "MyLabel"`,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(subT *testing.T) {
+ h := test.New(subT)
+ couperConf, err := configload.LoadBytes([]byte(tt.config), "test.hcl")
+ h.Must(err)
+
+ ctx, cancel := context.WithCancel(couperConf.Context)
+ couperConf.Context = ctx
+ defer cancel()
+ evalContext := couperConf.Context.Value(request.ContextType).(*eval.Context)
+ req, err := http.NewRequest(http.MethodGet, "https://www.example.com/foo", nil)
+ h.Must(err)
+ evalContext = evalContext.WithClientRequest(req)
+
+ _, err = evalContext.HCLContext().Functions[lib.FnOAuthAuthorizationURL].Call([]cty.Value{cty.StringVal(tt.label)})
+ if err == nil {
+ subT.Error("expected an error, got nothing")
+ return
+ }
+ if err.Error() != tt.wantErr {
+ subT.Errorf("\nWant:\t%q\nGot:\t%q", tt.wantErr, err.Error())
+ }
+ })
+ }
}
diff --git a/eval/lib/saml.go b/eval/lib/saml.go
index 8752436d5..132ad70ea 100644
--- a/eval/lib/saml.go
+++ b/eval/lib/saml.go
@@ -48,7 +48,7 @@ func NewSamlSsoURLFunction(configs []*config.SAML, origin *url.URL) function.Fun
label := args[0].AsString()
ent, exist := samlEntities[label]
if !exist {
- return cty.StringVal(""), fmt.Errorf("undefined reference: %s", label)
+ return cty.StringVal(""), fmt.Errorf("missing saml block with referenced label %q", label)
}
metadata := ent.descriptor
diff --git a/eval/lib/saml_test.go b/eval/lib/saml_test.go
index 97b543de3..6924e62f9 100644
--- a/eval/lib/saml_test.go
+++ b/eval/lib/saml_test.go
@@ -3,6 +3,7 @@ package lib_test
import (
"bytes"
"compress/flate"
+ "context"
"encoding/base64"
"encoding/xml"
"io"
@@ -15,6 +16,7 @@ import (
"github.com/avenga/couper/config/configload"
"github.com/avenga/couper/config/request"
+ "github.com/avenga/couper/errors"
"github.com/avenga/couper/eval"
"github.com/avenga/couper/eval/lib"
"github.com/avenga/couper/internal/test"
@@ -25,7 +27,6 @@ func Test_SamlSsoURL(t *testing.T) {
name string
hcl string
samlLabel string
- wantErr bool
wantPfx string
}{
{
@@ -43,54 +44,14 @@ func Test_SamlSsoURL(t *testing.T) {
}
`,
"MySAML",
- false,
"https://idp.example.org/saml/SSOService",
},
- {
- "metadata not found",
- `
- server "test" {
- }
- definitions {
- saml "MySAML" {
- idp_metadata_file = "not-there"
- sp_entity_id = "the-sp"
- sp_acs_url = "https://sp.example.com/saml/acs"
- array_attributes = ["memberOf"]
- }
- }
- `,
- "MySAML",
- true,
- "",
- },
- {
- "label mismatch",
- `
- server "test" {
- }
- definitions {
- saml "MySAML" {
- idp_metadata_file = "testdata/idp-metadata.xml"
- sp_entity_id = "the-sp"
- sp_acs_url = "https://sp.example.com/saml/acs"
- array_attributes = ["memberOf"]
- }
- }
- `,
- "NotThere",
- true,
- "",
- },
}
for _, tt := range tests {
t.Run(tt.name, func(subT *testing.T) {
h := test.New(subT)
cf, err := configload.LoadBytes([]byte(tt.hcl), "couper.hcl")
if err != nil {
- if tt.wantErr {
- return
- }
h.Must(err)
}
@@ -100,16 +61,7 @@ func Test_SamlSsoURL(t *testing.T) {
evalContext = evalContext.WithClientRequest(req)
ssoURL, err := evalContext.HCLContext().Functions[lib.FnSamlSsoURL].Call([]cty.Value{cty.StringVal(tt.samlLabel)})
- if err == nil && tt.wantErr {
- subT.Fatal("Error expected")
- }
- if err != nil {
- if !tt.wantErr {
- h.Must(err)
- } else {
- return
- }
- }
+ h.Must(err)
if !strings.HasPrefix(ssoURL.AsString(), tt.wantPfx) {
subT.Errorf("Expected to start with %q, got: %#v", tt.wantPfx, ssoURL.AsString())
@@ -136,5 +88,104 @@ func Test_SamlSsoURL(t *testing.T) {
h.Must(err)
})
}
+}
+func TestSamlConfigError(t *testing.T) {
+ tests := []struct {
+ name string
+ config string
+ label string
+ wantErr string
+ }{
+ {
+ "missing referenced saml IdP metadata",
+ `
+ server {}
+ definitions {
+ saml "MySAML" {
+ idp_metadata_file = "/not/there"
+ sp_entity_id = "the-sp"
+ sp_acs_url = "https://sp.example.com/saml/acs"
+ }
+ }
+ `,
+ "MyLabel",
+ "configuration error: MySAML: saml2 idp_metadata_file: read error: open /not/there: no such file or directory",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(subT *testing.T) {
+ _, err := configload.LoadBytes([]byte(tt.config), "test.hcl")
+ if err == nil {
+ subT.Error("expected an error, got nothing")
+ return
+ }
+ gErr := err.(errors.GoError)
+ if gErr.LogError() != tt.wantErr {
+ subT.Errorf("\nWant:\t%q\nGot:\t%q", tt.wantErr, gErr.LogError())
+ }
+ })
+ }
+}
+
+func TestSamlSsoURLError(t *testing.T) {
+ tests := []struct {
+ name string
+ config string
+ label string
+ wantErr string
+ }{
+ {
+ "missing saml definitions",
+ `
+ server {}
+ definitions {
+ }
+ `,
+ "MyLabel",
+ `missing saml block with referenced label "MyLabel"`,
+ },
+ {
+ "missing referenced saml",
+ `
+ server {}
+ definitions {
+ saml "MySAML" {
+ idp_metadata_file = "testdata/idp-metadata.xml"
+ sp_entity_id = "the-sp"
+ sp_acs_url = "https://sp.example.com/saml/acs"
+ }
+ }
+ `,
+ "MyLabel",
+ `missing saml block with referenced label "MyLabel"`,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(subT *testing.T) {
+ h := test.New(subT)
+ couperConf, err := configload.LoadBytes([]byte(tt.config), "test.hcl")
+ h.Must(err)
+
+ ctx, cancel := context.WithCancel(couperConf.Context)
+ couperConf.Context = ctx
+ defer cancel()
+
+ evalContext := couperConf.Context.Value(request.ContextType).(*eval.Context)
+ req, err := http.NewRequest(http.MethodGet, "https://www.example.com/foo", nil)
+ h.Must(err)
+ evalContext = evalContext.WithClientRequest(req)
+
+ _, err = evalContext.HCLContext().Functions[lib.FnSamlSsoURL].Call([]cty.Value{cty.StringVal(tt.label)})
+ if err == nil {
+ subT.Error("expected an error, got nothing")
+ return
+ }
+ if err.Error() != tt.wantErr {
+ subT.Errorf("\nWant:\t%q\nGot:\t%q", tt.wantErr, err.Error())
+ }
+ })
+ }
}
diff --git a/main_test.go b/main_test.go
index 7f04acca0..24649c1a9 100644
--- a/main_test.go
+++ b/main_test.go
@@ -47,13 +47,12 @@ func Test_realmain(t *testing.T) {
{"-f w/o file", []string{"couper", "run", "-f"}, nil, `level=error msg="flag needs an argument: -f" build=dev`, 1},
{"path from env", []string{"couper", "run", "-f", base + "/path_from_env.hcl"}, nil, `level=error msg="configuration error: token: jwt key: read error: open %s/public.pem: no such file or directory" build=dev`, 1},
{"path from env /w missing key", []string{"couper", "run", "-f", "public/couper.hcl", "-f", base + "/no_key_from_env.hcl"}, nil, "", 0},
- {"undefined AC", []string{"couper", "run", "-f", base + "/04_couper.hcl"}, nil, `level=error msg="accessControl is not defined: undefined" build=dev`, 1},
{"empty string in allowed_methods in endpoint", []string{"couper", "run", "-f", base + "/13_couper.hcl"}, nil, `level=error msg="%s/13_couper.hcl:3,5-27: method contains invalid character(s); " build=dev`, 1},
{"invalid method in allowed_methods in endpoint", []string{"couper", "run", "-f", base + "/14_couper.hcl"}, nil, `level=error msg="%s/14_couper.hcl:3,5-35: method contains invalid character(s); " build=dev`, 1},
{"invalid method in allowed_methods in api", []string{"couper", "run", "-f", base + "/15_couper.hcl"}, nil, `level=error msg="%s/15_couper.hcl:3,5-35: method contains invalid character(s); " build=dev`, 1},
{"rate_limit block in anonymous backend", []string{"couper", "run", "-f", base + "/17_couper.hcl"}, nil, `level=error msg="configuration error: anonymous_3_11: anonymous backend 'anonymous_3_11' cannot define 'beta_rate_limit' block(s)" build=dev`, 1},
{"non-string proxy reference", []string{"couper", "run", "-f", base + "/19_couper.hcl"}, nil, `level=error msg="%s/19_couper.hcl:3,13-14: proxy must evaluate to string; " build=dev`, 1},
- {"proxy reference does not exist", []string{"couper", "run", "-f", base + "/20_couper.hcl"}, nil, `level=error msg="%s/20_couper.hcl:3,14-17: proxy reference is not defined; " build=dev`, 1},
+ {"proxy reference does not exist", []string{"couper", "run", "-f", base + "/20_couper.hcl"}, nil, `level=error msg="%s/20_couper.hcl:3,14-17: referenced proxy \"foo\" is not defined; " build=dev`, 1},
{"circular backend references", []string{"couper", "run", "-f", base + "/21_couper.hcl"}, nil, `level=error msg="configuration error: : configuration error; circular reference:`, 1},
}
for _, tt := range tests {