From 3ea56140295901683e06d62f7b9d7738a2061ef2 Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Tue, 27 Aug 2024 13:33:25 +0100 Subject: [PATCH 01/10] If unmarshal results in a float64, return the string representation of a big int instead Signed-off-by: Matthew Whitehead --- pkg/fftypes/jsonany.go | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/pkg/fftypes/jsonany.go b/pkg/fftypes/jsonany.go index 05d1e1b..85f3120 100644 --- a/pkg/fftypes/jsonany.go +++ b/pkg/fftypes/jsonany.go @@ -21,6 +21,7 @@ import ( "crypto/sha256" "database/sql/driver" "encoding/json" + "math/big" "github.com/hyperledger/firefly-common/pkg/i18n" "github.com/hyperledger/firefly-common/pkg/log" @@ -76,7 +77,38 @@ func (h *JSONAny) Unmarshal(ctx context.Context, v interface{}) error { if h == nil { return i18n.NewError(ctx, i18n.MsgNilOrNullObject) } - return json.Unmarshal([]byte(*h), v) + + err := json.Unmarshal([]byte(*h), v) + if err != nil { + return err + } + + // To support large numbers, check if Go unmarshalled the data to a float64 and then + // unmarshal it to a string instead + if vt, ok := v.(*interface{}); ok { + if _, ok := (*vt).(float64); ok { + // If the value has unmarshalled to a float64 we can't be sure the number + // didn't overflow 2^64-1 so we'll use parseFloat on the original value + // and return the string representation of the number. + i := new(big.Int) + f, _, err := big.ParseFloat(h.String(), 10, 256, big.ToNearestEven) + if err != nil { + return err + } + i, accuracy := f.Int(i) + if accuracy != big.Exact { + // If we weren't able to decode without losing precision, return an error + return i18n.NewError(ctx, i18n.MsgBigIntParseFailed) + } + + err = json.Unmarshal([]byte("\""+i.String()+"\""), v) + if err != nil { + return err + } + } + } + + return err } func (h *JSONAny) Hash() *Bytes32 { From 28e4bc6195906d09880d6a32a47285a3f7e7315a Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Tue, 27 Aug 2024 14:41:50 +0100 Subject: [PATCH 02/10] Use decoder.UseNumber() to avoid unmarshalling to floats Signed-off-by: Matthew Whitehead --- pkg/fftypes/jsonany.go | 36 ++++++------------------------------ pkg/fftypes/jsonany_test.go | 13 +++++++++++++ 2 files changed, 19 insertions(+), 30 deletions(-) diff --git a/pkg/fftypes/jsonany.go b/pkg/fftypes/jsonany.go index 85f3120..7550ef4 100644 --- a/pkg/fftypes/jsonany.go +++ b/pkg/fftypes/jsonany.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // @@ -21,7 +21,7 @@ import ( "crypto/sha256" "database/sql/driver" "encoding/json" - "math/big" + "strings" "github.com/hyperledger/firefly-common/pkg/i18n" "github.com/hyperledger/firefly-common/pkg/log" @@ -78,37 +78,13 @@ func (h *JSONAny) Unmarshal(ctx context.Context, v interface{}) error { return i18n.NewError(ctx, i18n.MsgNilOrNullObject) } - err := json.Unmarshal([]byte(*h), v) - if err != nil { + d := json.NewDecoder(strings.NewReader(h.String())) + d.UseNumber() + if err := d.Decode(v); err != nil { return err } - // To support large numbers, check if Go unmarshalled the data to a float64 and then - // unmarshal it to a string instead - if vt, ok := v.(*interface{}); ok { - if _, ok := (*vt).(float64); ok { - // If the value has unmarshalled to a float64 we can't be sure the number - // didn't overflow 2^64-1 so we'll use parseFloat on the original value - // and return the string representation of the number. - i := new(big.Int) - f, _, err := big.ParseFloat(h.String(), 10, 256, big.ToNearestEven) - if err != nil { - return err - } - i, accuracy := f.Int(i) - if accuracy != big.Exact { - // If we weren't able to decode without losing precision, return an error - return i18n.NewError(ctx, i18n.MsgBigIntParseFailed) - } - - err = json.Unmarshal([]byte("\""+i.String()+"\""), v) - if err != nil { - return err - } - } - } - - return err + return nil } func (h *JSONAny) Hash() *Bytes32 { diff --git a/pkg/fftypes/jsonany_test.go b/pkg/fftypes/jsonany_test.go index 9ed37c3..6827579 100644 --- a/pkg/fftypes/jsonany_test.go +++ b/pkg/fftypes/jsonany_test.go @@ -181,6 +181,19 @@ func TestUnmarshal(t *testing.T) { assert.Equal(t, "value1", myObj.Key1) } +func TestUnmarshalHugeNumber(t *testing.T) { + + var h *JSONAny + var myObj struct { + Key1 interface{} `json:"key1"` + } + + h = JSONAnyPtr(`{"key1":123456789123456789123456789}`) + err := h.Unmarshal(context.Background(), &myObj) + assert.NoError(t, err) + assert.Equal(t, json.Number("123456789123456789123456789"), myObj.Key1) +} + func TestNilHash(t *testing.T) { assert.Nil(t, (*JSONAny)(nil).Hash()) } From edb07289f1564c22623642b1f759fcea74f382eb Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Tue, 27 Aug 2024 14:48:06 +0100 Subject: [PATCH 03/10] Add error test case Signed-off-by: Matthew Whitehead --- pkg/fftypes/jsonany_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pkg/fftypes/jsonany_test.go b/pkg/fftypes/jsonany_test.go index 6827579..8aab48e 100644 --- a/pkg/fftypes/jsonany_test.go +++ b/pkg/fftypes/jsonany_test.go @@ -194,6 +194,18 @@ func TestUnmarshalHugeNumber(t *testing.T) { assert.Equal(t, json.Number("123456789123456789123456789"), myObj.Key1) } +func TestUnmarshalHugeNumberError(t *testing.T) { + + var h *JSONAny + var myObj struct { + Key1 interface{} `json:"key1"` + } + + h = JSONAnyPtr(`{"key1":1234567891invalidchars234569}`) + err := h.Unmarshal(context.Background(), &myObj) + assert.Error(t, err) +} + func TestNilHash(t *testing.T) { assert.Nil(t, (*JSONAny)(nil).Hash()) } From 3165432b9ab5d68899461ca0dc05ffce6e084a9b Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Wed, 28 Aug 2024 10:10:26 +0100 Subject: [PATCH 04/10] Explicitly test for attempts to unmarshal to float64 and fail Signed-off-by: Matthew Whitehead --- pkg/fftypes/jsonany.go | 4 ++++ pkg/fftypes/jsonany_test.go | 24 ++++++++++++++++++++++-- pkg/i18n/en_base_error_messages.go | 1 + 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/pkg/fftypes/jsonany.go b/pkg/fftypes/jsonany.go index 7550ef4..7e3bfda 100644 --- a/pkg/fftypes/jsonany.go +++ b/pkg/fftypes/jsonany.go @@ -78,6 +78,10 @@ func (h *JSONAny) Unmarshal(ctx context.Context, v interface{}) error { return i18n.NewError(ctx, i18n.MsgNilOrNullObject) } + if _, ok := v.(*float64); ok { + return i18n.NewError(ctx, i18n.MsgUnmarshalToFloat64NotSupported) + } + d := json.NewDecoder(strings.NewReader(h.String())) d.UseNumber() if err := d.Decode(v); err != nil { diff --git a/pkg/fftypes/jsonany_test.go b/pkg/fftypes/jsonany_test.go index 8aab48e..1eb44b8 100644 --- a/pkg/fftypes/jsonany_test.go +++ b/pkg/fftypes/jsonany_test.go @@ -183,15 +183,35 @@ func TestUnmarshal(t *testing.T) { func TestUnmarshalHugeNumber(t *testing.T) { + var myInt64Variable int64 + var myFloat64Variable float64 + ctx := context.Background() var h *JSONAny var myObj struct { Key1 interface{} `json:"key1"` + Key2 JSONAny `json:"key2"` + Key3 JSONAny `json:"key3"` } - h = JSONAnyPtr(`{"key1":123456789123456789123456789}`) - err := h.Unmarshal(context.Background(), &myObj) + h = JSONAnyPtr(`{"key1":123456789123456789123456789, "key2":123456789123456789123456789, "key3":1234}`) + err := h.Unmarshal(ctx, &myObj) assert.NoError(t, err) assert.Equal(t, json.Number("123456789123456789123456789"), myObj.Key1) + + assert.NoError(t, err) + assert.Equal(t, "123456789123456789123456789", myObj.Key2.String()) + + err = myObj.Key2.Unmarshal(ctx, &myInt64Variable) + assert.Error(t, err) + assert.Regexp(t, "cannot unmarshal number 123456789123456789123456789 into Go value of type int64", err) + + err = myObj.Key3.Unmarshal(ctx, &myInt64Variable) + assert.NoError(t, err) + assert.Equal(t, int64(1234), myInt64Variable) + + err = myObj.Key2.Unmarshal(ctx, &myFloat64Variable) + assert.Error(t, err) + assert.Regexp(t, "FF00249", err) } func TestUnmarshalHugeNumberError(t *testing.T) { diff --git a/pkg/i18n/en_base_error_messages.go b/pkg/i18n/en_base_error_messages.go index 6a5219f..f82e399 100644 --- a/pkg/i18n/en_base_error_messages.go +++ b/pkg/i18n/en_base_error_messages.go @@ -183,4 +183,5 @@ var ( MsgDBExecFailed = ffe("FF00245", "Database update failed") MsgDBErrorBuildingStatement = ffe("FF00247", "Error building statement: %s") MsgDBReadInsertTSFailed = ffe("FF00248", "Failed to read timestamp from database optimized upsert: %s") + MsgUnmarshalToFloat64NotSupported = ffe("FF00249", "Unmarshalling to a float64 is not supported due to possible precision loss. Consider unmarshalling to an interface, json.Number or fftypes.JSONAny instead") ) From 60b8dc2fb9b067cd0cd1c34e9c7ebf75d2de321b Mon Sep 17 00:00:00 2001 From: hfuss Date: Tue, 10 Sep 2024 17:11:11 -0400 Subject: [PATCH 05/10] [fswatcher] File Reconciler w/ Resync Interval Signed-off-by: hfuss --- pkg/fswatcher/fswatcher.go | 35 ++++++++++++++++++++++++++++++--- pkg/fswatcher/fswatcher_test.go | 18 ++++++++++++----- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/pkg/fswatcher/fswatcher.go b/pkg/fswatcher/fswatcher.go index c56edee..249ea80 100644 --- a/pkg/fswatcher/fswatcher.go +++ b/pkg/fswatcher/fswatcher.go @@ -20,6 +20,7 @@ import ( "context" "os" "path" + "time" "github.com/fsnotify/fsnotify" "github.com/hyperledger/firefly-common/pkg/fftypes" @@ -35,9 +36,19 @@ import ( // - Only fires if the data in the file is different to the last notification // - Does not reload the config - that's the caller's responsibility func Watch(ctx context.Context, fullFilePath string, onChange, onClose func()) error { + return sync(ctx, fullFilePath, onChange, onClose, nil, nil) +} + +// Reconcile behaves the same as Watch, except it allows for running the onSync func on a provided +// interval. The default re-sync internal is 1m. +func Reconcile(ctx context.Context, fullFilePath string, onChange, onClose, onSync func(), resyncInterval *time.Duration) error { + return sync(ctx, fullFilePath, onChange, onClose, onSync, resyncInterval) +} + +func sync(ctx context.Context, fullFilePath string, onChange, onClose, onSync func(), resyncInterval *time.Duration) error { filePath := path.Dir(fullFilePath) fileName := path.Base(fullFilePath) - log.L(ctx).Debugf("Starting file listener for '%s' in directory '%s'", fileName, filePath) + log.L(ctx).Debugf("Starting file reconciler for '%s' in directory '%s'", fileName, filePath) watcher, err := fsnotify.NewWatcher() if err == nil { @@ -46,7 +57,7 @@ func Watch(ctx context.Context, fullFilePath string, onChange, onClose func()) e if onClose != nil { onClose() } - }, watcher.Events, watcher.Errors) + }, onSync, resyncInterval, watcher.Events, watcher.Errors) err = watcher.Add(filePath) } if err != nil { @@ -56,9 +67,18 @@ func Watch(ctx context.Context, fullFilePath string, onChange, onClose func()) e return nil } -func fsListenerLoop(ctx context.Context, fullFilePath string, onChange, onClose func(), events chan fsnotify.Event, errors chan error) { +func fsListenerLoop(ctx context.Context, fullFilePath string, onChange, onClose, onSync func(), resyncInterval *time.Duration, events chan fsnotify.Event, errors chan error) { defer onClose() + timeout := resyncInterval + if timeout == nil { + timeout = func() *time.Duration { + defaultTimeout := time.Minute + return &defaultTimeout + }() + } + log.L(ctx).Debugf("re-sync interval set to '%s'", *timeout) + var lastHash *fftypes.Bytes32 for { select { @@ -83,6 +103,15 @@ func fsListenerLoop(ctx context.Context, fullFilePath string, onChange, onClose lastHash = dataHash } } + case <-time.After(*timeout): + if onSync != nil { + data, err := os.ReadFile(fullFilePath) + if err == nil { + dataHash := fftypes.HashString(string(data)) + log.L(ctx).Infof("Config file re-sync. Event=Resync Name=%s Size=%d Hash=%s", fullFilePath, len(data), dataHash) + onSync() + } + } case err, ok := <-errors: if ok { log.L(ctx).Errorf("FSEvent error: %s", err) diff --git a/pkg/fswatcher/fswatcher_test.go b/pkg/fswatcher/fswatcher_test.go index c38c588..d81b2df 100644 --- a/pkg/fswatcher/fswatcher_test.go +++ b/pkg/fswatcher/fswatcher_test.go @@ -21,6 +21,7 @@ import ( "fmt" "os" "testing" + "time" "github.com/fsnotify/fsnotify" "github.com/sirupsen/logrus" @@ -28,7 +29,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestFileListenerE2E(t *testing.T) { +func TestFileReconcilerE2E(t *testing.T) { logrus.SetLevel(logrus.DebugLevel) tmpDir := t.TempDir() @@ -47,14 +48,20 @@ func TestFileListenerE2E(t *testing.T) { // Start listener on config file fsListenerDone := make(chan struct{}) fsListenerFired := make(chan bool) + reSyncFired := make(chan bool) + reSyncInterval := 1 * time.Second ctx, cancelCtx := context.WithCancel(context.Background()) - err := Watch(ctx, filePath, func() { + err := Reconcile(ctx, filePath, func() { err := viper.ReadInConfig() assert.NoError(t, err) fsListenerFired <- true }, func() { close(fsListenerDone) - }) + }, func() { + err := viper.ReadInConfig() + assert.NoError(t, err) + reSyncFired <- true + }, &reSyncInterval) assert.NoError(t, err) // Delete and rename in another file @@ -63,6 +70,7 @@ func TestFileListenerE2E(t *testing.T) { os.Rename(fmt.Sprintf("%s/another.yaml", tmpDir), fmt.Sprintf("%s/test.yaml", tmpDir)) <-fsListenerFired assert.Equal(t, "two", viper.Get("ut_conf")) + <-reSyncFired defer func() { cancelCtx() @@ -74,7 +82,7 @@ func TestFileListenerE2E(t *testing.T) { } -func TestFileListenerFail(t *testing.T) { +func TestFileWatcherFail(t *testing.T) { logrus.SetLevel(logrus.DebugLevel) tmpDir := t.TempDir() @@ -95,7 +103,7 @@ func TestFileListenerLogError(t *testing.T) { defer cancelCtx() errors := make(chan error) fsListenerDone := make(chan struct{}) - go fsListenerLoop(ctx, "somefile", func() {}, func() { close(fsListenerDone) }, make(chan fsnotify.Event), errors) + go fsListenerLoop(ctx, "somefile", func() {}, func() { close(fsListenerDone) }, nil, nil, make(chan fsnotify.Event), errors) errors <- fmt.Errorf("pop") cancelCtx() From 16921a842c0c9b9840b5a17afcb89d685e8f846e Mon Sep 17 00:00:00 2001 From: hfuss Date: Tue, 10 Sep 2024 17:17:59 -0400 Subject: [PATCH 06/10] fix lint Signed-off-by: hfuss --- pkg/fswatcher/fswatcher.go | 2 +- pkg/fswatcher/fswatcher_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/fswatcher/fswatcher.go b/pkg/fswatcher/fswatcher.go index 249ea80..3fa7e23 100644 --- a/pkg/fswatcher/fswatcher.go +++ b/pkg/fswatcher/fswatcher.go @@ -1,4 +1,4 @@ -// Copyright © 2023 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // diff --git a/pkg/fswatcher/fswatcher_test.go b/pkg/fswatcher/fswatcher_test.go index d81b2df..919ae24 100644 --- a/pkg/fswatcher/fswatcher_test.go +++ b/pkg/fswatcher/fswatcher_test.go @@ -1,4 +1,4 @@ -// Copyright © 2022 Kaleido, Inc. +// Copyright © 2024 Kaleido, Inc. // // SPDX-License-Identifier: Apache-2.0 // From 5fb3c9e9f1ae344eab1eeea0d0c6dd1d6005a42d Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Wed, 11 Sep 2024 13:08:11 +0100 Subject: [PATCH 07/10] Update Go to 1.22 Signed-off-by: Matthew Whitehead --- .github/workflows/go.yml | 2 +- go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 85ddad5..ecf01c0 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: "1.21" + go-version: "1.22" - name: Build and Test run: make diff --git a/go.mod b/go.mod index d14f8e1..aa8c043 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/hyperledger/firefly-common -go 1.21 +go 1.22 require ( github.com/DATA-DOG/go-sqlmock v1.5.2 From c73cd77cd9b4719db86a9c42c9844127237c9eb0 Mon Sep 17 00:00:00 2001 From: Matthew Whitehead Date: Fri, 13 Sep 2024 14:37:18 +0100 Subject: [PATCH 08/10] Route handler to use UseNumber decoding Signed-off-by: Matthew Whitehead --- pkg/ffapi/handler.go | 4 ++- pkg/ffapi/handler_test.go | 69 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/pkg/ffapi/handler.go b/pkg/ffapi/handler.go index 40e5487..504ce8a 100644 --- a/pkg/ffapi/handler.go +++ b/pkg/ffapi/handler.go @@ -181,7 +181,9 @@ func (hs *HandlerFactory) RouteHandler(route *Route) http.HandlerFunc { fallthrough case strings.HasPrefix(strings.ToLower(contentType), "application/json"): if jsonInput != nil { - err = json.NewDecoder(req.Body).Decode(&jsonInput) + d := json.NewDecoder(req.Body) + d.UseNumber() + err = d.Decode(&jsonInput) } case strings.HasPrefix(strings.ToLower(contentType), "text/plain"): default: diff --git a/pkg/ffapi/handler_test.go b/pkg/ffapi/handler_test.go index b8ffd21..bc024ec 100644 --- a/pkg/ffapi/handler_test.go +++ b/pkg/ffapi/handler_test.go @@ -21,8 +21,6 @@ import ( "context" "encoding/json" "fmt" - "github.com/getkin/kin-openapi/openapi3" - "github.com/stretchr/testify/require" "io" "mime/multipart" "net/http" @@ -31,6 +29,9 @@ import ( "testing" "time" + "github.com/getkin/kin-openapi/openapi3" + "github.com/stretchr/testify/require" + "github.com/gorilla/mux" "github.com/hyperledger/firefly-common/pkg/config" "github.com/hyperledger/firefly-common/pkg/httpserver" @@ -38,6 +39,14 @@ import ( "github.com/stretchr/testify/assert" ) +const largeParamLiteral = `{ + "largeNumberParam": 10000000000000000000000000001 +}` + +const scientificParamLiteral = `{ + "scientificNumberParam": 1e+24 +}` + const configDir = "../../test/data/config" func newTestHandlerFactory(basePath string, basePathParams []*PathParam) *HandlerFactory { @@ -112,6 +121,62 @@ func TestRouteServePOST201WithParams(t *testing.T) { assert.Equal(t, "value2", resJSON["output1"]) } +func TestRouteServePOST201WithParamsLargeNumber(t *testing.T) { + s, _, done := newTestServer(t, []*Route{{ + Name: "testRoute", + Path: "/test/{something}", + Method: "POST", + PathParams: []*PathParam{}, + QueryParams: []*QueryParam{}, + JSONInputValue: func() interface{} { return make(map[string]interface{}) }, + JSONOutputValue: func() interface{} { return make(map[string]interface{}) }, + JSONOutputCodes: []int{201}, + JSONHandler: func(r *APIRequest) (output interface{}, err error) { + assert.Equal(t, r.Input, map[string]interface{}{"largeNumberParam": json.Number("10000000000000000000000000001")}) + // Echo the input back as the response + return r.Input, nil + }, + }}, "", nil) + defer done() + + res, err := http.Post(fmt.Sprintf("http://%s/test/stuff", s.Addr()), "application/json", bytes.NewReader([]byte(largeParamLiteral))) + assert.NoError(t, err) + assert.Equal(t, 201, res.StatusCode) + var resJSON map[string]interface{} + d := json.NewDecoder(res.Body) + d.UseNumber() + d.Decode(&resJSON) + assert.Equal(t, json.Number("10000000000000000000000000001"), resJSON["largeNumberParam"]) +} + +func TestRouteServePOST201WithParamsScientificNumber(t *testing.T) { + s, _, done := newTestServer(t, []*Route{{ + Name: "testRoute", + Path: "/test/{something}", + Method: "POST", + PathParams: []*PathParam{}, + QueryParams: []*QueryParam{}, + JSONInputValue: func() interface{} { return make(map[string]interface{}) }, + JSONOutputValue: func() interface{} { return make(map[string]interface{}) }, + JSONOutputCodes: []int{201}, + JSONHandler: func(r *APIRequest) (output interface{}, err error) { + assert.Equal(t, r.Input, map[string]interface{}{"scientificNumberParam": json.Number("1e+24")}) + // Echo the input back as the response + return r.Input, nil + }, + }}, "", nil) + defer done() + + res, err := http.Post(fmt.Sprintf("http://%s/test/stuff", s.Addr()), "application/json", bytes.NewReader([]byte(scientificParamLiteral))) + assert.NoError(t, err) + assert.Equal(t, 201, res.StatusCode) + var resJSON map[string]interface{} + d := json.NewDecoder(res.Body) + d.UseNumber() + d.Decode(&resJSON) + assert.Equal(t, json.Number("1e+24"), resJSON["scientificNumberParam"]) +} + func TestJSONHTTPResponseEncodeFail(t *testing.T) { s, _, done := newTestServer(t, []*Route{{ Name: "testRoute", From f2d135ca1c4e7d00a15ab50232f63eea5b484d75 Mon Sep 17 00:00:00 2001 From: Enrique Lacal Date: Mon, 14 Oct 2024 20:23:19 +0100 Subject: [PATCH 09/10] Provide the client certificate without relying on golang matching it from a list Signed-off-by: Enrique Lacal --- pkg/fftls/fftls.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pkg/fftls/fftls.go b/pkg/fftls/fftls.go index 70f9bc1..9de5e87 100644 --- a/pkg/fftls/fftls.go +++ b/pkg/fftls/fftls.go @@ -89,6 +89,7 @@ func NewTLSConfig(ctx context.Context, config *Config, tlsType TLSType) (*tls.Co tlsConfig.RootCAs = rootCAs + var configuredCert *tls.Certificate // For mTLS we need both the cert and key if config.CertFile != "" && config.KeyFile != "" { // Read the key pair to create certificate @@ -96,13 +97,26 @@ func NewTLSConfig(ctx context.Context, config *Config, tlsType TLSType) (*tls.Co if err != nil { return nil, i18n.WrapError(ctx, err, i18n.MsgInvalidKeyPairFiles) } - tlsConfig.Certificates = []tls.Certificate{cert} + configuredCert = &cert } else if config.Cert != "" && config.Key != "" { cert, err := tls.X509KeyPair([]byte(config.Cert), []byte(config.Key)) if err != nil { return nil, i18n.WrapError(ctx, err, i18n.MsgInvalidKeyPairFiles) } - tlsConfig.Certificates = []tls.Certificate{cert} + configuredCert = &cert + } + + if configuredCert != nil { + // Rather than letting Golang pick a certificate it thinks matches from the list of one, + // we directly supply it the one we have in all cases. + tlsConfig.GetClientCertificate = func(_ *tls.CertificateRequestInfo) (*tls.Certificate, error) { + log.L(ctx).Debugf("Supplying client certificate") + return configuredCert, nil + } + tlsConfig.GetCertificate = func(_ *tls.ClientHelloInfo) (*tls.Certificate, error) { + log.L(ctx).Debugf("Supplying server certificate") + return configuredCert, nil + } } if tlsType == ServerType { From 2fa6c001e96150d28239c2da57f032640df0c54b Mon Sep 17 00:00:00 2001 From: hfuss Date: Tue, 22 Oct 2024 10:35:57 -0400 Subject: [PATCH 10/10] [ffresty] [bug] Set maxConnsPerHost Based on Config Signed-off-by: hfuss --- pkg/ffresty/config.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/ffresty/config.go b/pkg/ffresty/config.go index 7e3e592..b63f355 100644 --- a/pkg/ffresty/config.go +++ b/pkg/ffresty/config.go @@ -135,6 +135,7 @@ func GenerateConfig(ctx context.Context, conf config.Section) (*Config, error) { HTTPRequestTimeout: fftypes.FFDuration(conf.GetDuration(HTTPConfigRequestTimeout)), HTTPIdleConnTimeout: fftypes.FFDuration(conf.GetDuration(HTTPIdleTimeout)), HTTPMaxIdleConns: conf.GetInt(HTTPMaxIdleConns), + HTTPMaxConnsPerHost: conf.GetInt(HTTPMaxConnsPerHost), HTTPConnectionTimeout: fftypes.FFDuration(conf.GetDuration(HTTPConnectionTimeout)), HTTPTLSHandshakeTimeout: fftypes.FFDuration(conf.GetDuration(HTTPTLSHandshakeTimeout)), HTTPExpectContinueTimeout: fftypes.FFDuration(conf.GetDuration(HTTPExpectContinueTimeout)),