Repository: avenga/couper
Pull request #253 (2021-06-11 14:28:17)

config error chaining


Patch:
diff --git a/accesscontrol/basic_auth.go b/accesscontrol/basic_auth.go
index 76a6781d..f017325c 100644
--- a/accesscontrol/basic_auth.go
+++ b/accesscontrol/basic_auth.go
@@ -3,6 +3,7 @@ package accesscontrol
 import (
 	"bufio"
 	"crypto/subtle"
+	"fmt"
 	"net/http"
 	"os"
 	"strconv"
@@ -36,7 +37,7 @@ func NewBasicAuth(name, user, pass, file string) (*BasicAuth, error) {
 
 	fp, err := os.Open(file)
 	if err != nil {
-		return nil, errors.Configuration.Label(name).With(err)
+		return nil, err
 	}
 	defer fp.Close()
 
@@ -50,18 +51,18 @@ func NewBasicAuth(name, user, pass, file string) (*BasicAuth, error) {
 		}
 
 		if len(line) > 255 {
-			return nil, errors.Configuration.Label(name).Message("parse error: line length exceeded: 255")
+			return nil, fmt.Errorf("parse error: line length exceeded: 255")
 		}
 
 		up := strings.SplitN(line, ":", 2)
 		if len(up) != 2 {
-			return nil, errors.Configuration.Label(name).Message("parse error: invalid line: " + strconv.Itoa(lineNr))
+			return nil, fmt.Errorf("parse error: invalid line: " + strconv.Itoa(lineNr))
 		}
 
 		username, password := up[0], up[1]
 
 		if _, ok := ba.htFile[username]; ok {
-			return nil, errors.Configuration.Label(name).Message("multiple user: " + username)
+			return nil, fmt.Errorf("multiple user: " + username)
 		}
 
 		switch pwdType := getPwdType(password); pwdType {
@@ -75,7 +76,7 @@ func NewBasicAuth(name, user, pass, file string) (*BasicAuth, error) {
 
 			parts := strings.Split(strings.TrimPrefix(password, prefix), "$")
 			if len(parts) != 2 {
-				return nil, errors.Configuration.Label(name).Message("parse error: malformed password for user: " + username)
+				return nil, fmt.Errorf("parse error: malformed password for user: " + username)
 			}
 
 			ba.htFile[username] = pwd{
@@ -90,7 +91,7 @@ func NewBasicAuth(name, user, pass, file string) (*BasicAuth, error) {
 				pwdType: pwdType,
 			}
 		default:
-			return nil, errors.Configuration.Label(name).Message("parse error: algorithm not supported")
+			return nil, fmt.Errorf("parse error: algorithm not supported")
 		}
 	}
 
diff --git a/accesscontrol/jwt.go b/accesscontrol/jwt.go
index a7861b6b..82dddf12 100644
--- a/accesscontrol/jwt.go
+++ b/accesscontrol/jwt.go
@@ -75,8 +75,6 @@ func NewJWTSource(cookie, header string) JWTSource {
 
 // NewJWT parses the key and creates Validation obj which can be referenced in related handlers.
 func NewJWT(options *JWTOptions) (*JWT, error) {
-	confErr := errors.Configuration.Label(options.Name)
-
 	jwtAC := &JWT{
 		algorithm:      NewAlgorithm(options.Algorithm),
 		claims:         options.Claims,
@@ -86,16 +84,16 @@ func NewJWT(options *JWTOptions) (*JWT, error) {
 	}
 
 	if jwtAC.source.Type == Invalid {
-		return nil, confErr.Message("token source is invalid")
+		return nil, fmt.Errorf("token source is invalid")
 	}
 
 	if jwtAC.algorithm == AlgorithmUnknown {
-		return nil, confErr.Message("algorithm is not supported")
+		return nil, fmt.Errorf("algorithm is not supported")
 	}
 
 	parser, err := newParser(jwtAC.algorithm, jwtAC.claims)
 	if err != nil {
-		return nil, confErr.With(err)
+		return nil, err
 	}
 	jwtAC.parser = parser
 
@@ -106,7 +104,7 @@ func NewJWT(options *JWTOptions) (*JWT, error) {
 
 	pubKey, err := parsePublicPEMKey(options.Key)
 	if err != nil {
-		return nil, confErr.With(err)
+		return nil, err
 	}
 
 	jwtAC.pubKey = pubKey
diff --git a/accesscontrol/oauth2.go b/accesscontrol/oauth2.go
index 4f2331dc..bff225b0 100644
--- a/accesscontrol/oauth2.go
+++ b/accesscontrol/oauth2.go
@@ -5,6 +5,7 @@ import (
 	"crypto/sha256"
 	"encoding/base64"
 	"encoding/json"
+	"fmt"
 	"io/ioutil"
 	"net/http"
 	"strings"
@@ -31,32 +32,30 @@ type OAuth2Callback struct {
 
 // NewOAuth2Callback creates a new AC-OAuth2 object
 func NewOAuth2Callback(conf *config.OAuth2AC, oauth2 *transport.OAuth2) (*OAuth2Callback, error) {
-	confErr := errors.Configuration.Label(conf.Name)
-
 	const grantType = "authorization_code"
 	if conf.GrantType != grantType {
-		return nil, confErr.Messagef("grant_type %s not supported", conf.GrantType)
+		return nil, fmt.Errorf("grant_type %s not supported", conf.GrantType)
 	}
 	if conf.Pkce == nil && conf.Csrf == nil {
-		return nil, confErr.Message("CSRF protection not configured")
+		return nil, fmt.Errorf("CSRF protection not configured")
 	}
 	if conf.Csrf != nil {
 		if conf.Csrf.TokenParam != "state" && conf.Csrf.TokenParam != "nonce" {
-			return nil, confErr.Messagef("csrf_token_param %s not supported", conf.Csrf.TokenParam)
+			return nil, fmt.Errorf("csrf_token_param %s not supported", conf.Csrf.TokenParam)
 		}
 		content, _, diags := conf.Csrf.HCLBody().PartialContent(conf.Csrf.Schema(true))
 		if diags.HasErrors() {
-			return nil, errors.Evaluation.With(diags)
+			return nil, diags
 		}
 		conf.Csrf.Content = content
 	}
 	if conf.Pkce != nil {
 		if conf.Pkce.CodeChallengeMethod != lib.CcmPlain && conf.Pkce.CodeChallengeMethod != lib.CcmS256 {
-			return nil, confErr.Messagef("code_challenge_method %s not supported", conf.Pkce.CodeChallengeMethod)
+			return nil, fmt.Errorf("code_challenge_method %s not supported", conf.Pkce.CodeChallengeMethod)
 		}
 		content, _, diags := conf.Pkce.HCLBody().PartialContent(conf.Pkce.Schema(true))
 		if diags.HasErrors() {
-			return nil, errors.Evaluation.With(diags)
+			return nil, diags
 		}
 		conf.Pkce.Content = content
 	}
diff --git a/config/runtime/server.go b/config/runtime/server.go
index 0c156202..50d14f76 100644
--- a/config/runtime/server.go
+++ b/config/runtime/server.go
@@ -407,27 +407,29 @@ func configureAccessControls(conf *config.Couper, confCtx *hcl.EvalContext, log
 
 	if conf.Definitions != nil {
 		for _, baConf := range conf.Definitions.BasicAuth {
+			confErr := errors.Configuration.Label(baConf.Name)
 			basicAuth, err := ac.NewBasicAuth(baConf.Name, baConf.User, baConf.Pass, baConf.File)
 			if err != nil {
-				return nil, err
+				return nil, confErr.With(err)
 			}
 
 			if err = accessControls.Add(baConf.Name, basicAuth, baConf.ErrorHandler); err != nil {
-				return nil, err
+				return nil, confErr.With(err)
 			}
 		}
 
 		for _, jwtConf := range conf.Definitions.JWT {
+			confErr := errors.Configuration.Label(jwtConf.Name)
 			key, err := reader.ReadFromAttrFile("jwt key", jwtConf.Key, jwtConf.KeyFile)
 			if err != nil {
-				return nil, errors.Configuration.Label(jwtConf.Name).With(err)
+				return nil, confErr.With(err)
 			}
 
 			var claims map[string]interface{}
 			if jwtConf.Claims != nil { // TODO: dynamic expr eval ?
 				c, diags := seetie.ExpToMap(confCtx, jwtConf.Claims)
 				if diags.HasErrors() {
-					return nil, diags
+					return nil, confErr.With(diags)
 				}
 				claims = c
 			}
@@ -440,47 +442,50 @@ func configureAccessControls(conf *config.Couper, confCtx *hcl.EvalContext, log
 				Source:         ac.NewJWTSource(jwtConf.Cookie, jwtConf.Header),
 			})
 			if err != nil {
-				return nil, fmt.Errorf("loading jwt definition failed: %s", err)
+				return nil, confErr.With(err)
 			}
 
 			if err = accessControls.Add(jwtConf.Name, jwt, jwtConf.ErrorHandler); err != nil {
-				return nil, err
+				return nil, confErr.With(err)
 			}
 		}
 
 		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, errors.Configuration.Label(saml.Name).With(err)
+				return nil, confErr.With(err)
 			}
+
 			s, err := ac.NewSAML2ACS(metadata, saml.Name, saml.SpAcsUrl, saml.SpEntityId, saml.ArrayAttributes)
 			if err != nil {
-				return nil, fmt.Errorf("loading saml definition failed: %s", err)
+				return nil, confErr.With(err)
 			}
 
 			if err = accessControls.Add(saml.Name, s, saml.ErrorHandler); err != nil {
-				return nil, err
+				return nil, confErr.With(err)
 			}
 		}
 
 		for _, oauth2Conf := range conf.Definitions.OAuth2AC {
+			confErr := errors.Configuration.Label(oauth2Conf.Name)
 			authBackend, authErr := newBackend(confCtx, oauth2Conf.Backend, log, conf.Settings.NoProxyFromEnv, memStore)
 			if authErr != nil {
-				return nil, fmt.Errorf("loading oauth2 definition failed: %s", authErr)
+				return nil, confErr.With(authErr)
 			}
 
 			oauth2, err := transport.NewOAuth2(oauth2Conf, authBackend)
 			if err != nil {
-				return nil, fmt.Errorf("loading oauth2 definition failed: %s", err)
+				return nil, confErr.With(err)
 			}
 
 			oa, err := ac.NewOAuth2Callback(oauth2Conf, oauth2)
 			if err != nil {
-				return nil, fmt.Errorf("loading oauth2 definition failed: %s", err)
+				return nil, confErr.With(err)
 			}
 
 			if err = accessControls.Add(oauth2Conf.Name, oa, oauth2Conf.ErrorHandler); err != nil {
-				return nil, err
+				return nil, confErr.With(err)
 			}
 		}
 	}


Test patch:
diff --git a/accesscontrol/basic_auth_test.go b/accesscontrol/basic_auth_test.go
index 082dda04..c9e38e8c 100644
--- a/accesscontrol/basic_auth_test.go
+++ b/accesscontrol/basic_auth_test.go
@@ -33,12 +33,12 @@ func Test_NewBasicAuth(t *testing.T) {
 		{"name", "", "", "", "", false},
 		{"name", "user", "pass", "testdata/htpasswd", "", false},
 		{"name", "john", "pass", "testdata/htpasswd", "", false},
-		{"name", "user", "pass", "file", "configuration error: name: open file: no such file or directory", true},
-		{"name", "user", "pass", "testdata/htpasswd_err_invalid", "configuration error: name: parse error: invalid line: 1", true},
-		{"name", "user", "pass", "testdata/htpasswd_err_too_long", "configuration error: name: parse error: line length exceeded: 255", true},
-		{"name", "user", "pass", "testdata/htpasswd_err_malformed", `configuration error: name: parse error: malformed password for user: foo`, true},
-		{"name", "user", "pass", "testdata/htpasswd_err_multi", `configuration error: name: multiple user: foo`, true},
-		{"name", "user", "pass", "testdata/htpasswd_err_unsupported", "configuration error: name: parse error: algorithm not supported", true},
+		{"name", "user", "pass", "file", "open file: no such file or directory", true},
+		{"name", "user", "pass", "testdata/htpasswd_err_invalid", "parse error: invalid line: 1", true},
+		{"name", "user", "pass", "testdata/htpasswd_err_too_long", "parse error: line length exceeded: 255", true},
+		{"name", "user", "pass", "testdata/htpasswd_err_malformed", `parse error: malformed password for user: foo`, true},
+		{"name", "user", "pass", "testdata/htpasswd_err_multi", `multiple user: foo`, true},
+		{"name", "user", "pass", "testdata/htpasswd_err_unsupported", "parse error: algorithm not supported", true},
 	} {
 		ba, err = ac.NewBasicAuth(tc.name, tc.user, tc.pass, tc.file)
 		if tc.shouldFail && ba != nil {
@@ -46,13 +46,8 @@ func Test_NewBasicAuth(t *testing.T) {
 		}
 
 		if tc.shouldFail && err != nil && tc.expErrMsg != "" {
-			if err.Error() != couperErr.Configuration.Label("name").Error() {
-				t.Errorf("Expected a configuration error")
-			}
-			gerr := err.(couperErr.GoError)
-
-			if gerr.LogError() != tc.expErrMsg {
-				t.Errorf("Expected error message: %q, got: %q", tc.expErrMsg, gerr.LogError())
+			if err.Error() != tc.expErrMsg {
+				t.Errorf("Expected error message: %q, got: %q", tc.expErrMsg, err.Error())
 			}
 		} else if err != nil {
 			t.Error(err)
diff --git a/accesscontrol/jwt_test.go b/accesscontrol/jwt_test.go
index eddec757..ce12458f 100644
--- a/accesscontrol/jwt_test.go
+++ b/accesscontrol/jwt_test.go
@@ -93,7 +93,7 @@ QolLGgj3tz4NbDEitq+zKMr0uTHvP1Vyu1mXAflcpYcJA4ZmuB3Oj39e0U0gnmr/
 			{"Priv", fields{
 				algorithm: alg,
 				pubKey:    privKeyBytes,
-			}, "configuration error: test_ac: key is not a valid RSA public key"},
+			}, "key is not a valid RSA public key"},
 		}
 
 		for _, tt := range tests {
@@ -118,9 +118,8 @@ QolLGgj3tz4NbDEitq+zKMr0uTHvP1Vyu1mXAflcpYcJA4ZmuB3Oj39e0U0gnmr/
 					Source:         ac.NewJWTSource("", "Authorization"),
 				})
 				if jerr != nil {
-					gerr := jerr.(errors.GoError)
-					if tt.wantErr != gerr.LogError() {
-						subT.Errorf("error: %v, want: %v", gerr.LogError(), tt.wantErr)
+					if tt.wantErr != jerr.Error() {
+						subT.Errorf("error: %v, want: %v", jerr.Error(), tt.wantErr)
 					}
 				} else if tt.wantErr != "" {
 					subT.Errorf("error expected: %v", tt.wantErr)
diff --git a/server/http_integration_test.go b/server/http_integration_test.go
index bf1c3bd5..1c383776 100644
--- a/server/http_integration_test.go
+++ b/server/http_integration_test.go
@@ -30,6 +30,7 @@ import (
 	"github.com/avenga/couper/command"
 	"github.com/avenga/couper/config"
 	"github.com/avenga/couper/config/configload"
+	"github.com/avenga/couper/errors"
 	"github.com/avenga/couper/internal/test"
 	"github.com/avenga/couper/logging"
 )
@@ -2434,11 +2435,14 @@ func TestJWTAccessControlSourceConfig(t *testing.T) {
 	log, _ := logrustest.NewNullLogger()
 	ctx := context.TODO()
 
-	expectedMsg := "loading jwt definition failed: configuration error"
+	expectedMsg := "configuration error: missing-source: token source is invalid"
 
 	err = command.NewRun(ctx).Execute([]string{couperConfig.Filename}, couperConfig, log.WithContext(ctx))
-	if err == nil || err.Error() != expectedMsg {
-		t.Errorf("\nwant:\t%s\ngot:\t%v", expectedMsg, err)
+	logErr, _ := err.(errors.GoError)
+	if logErr == nil {
+		t.Error("logErr should not be nil")
+	} else if logErr.LogError() != expectedMsg {
+		t.Errorf("\nwant:\t%s\ngot:\t%v", expectedMsg, logErr.LogError())
 	}
 }