Skip to content

Commit 1f00f09

Browse files
authored
Merge pull request #594 from jetstack/VC-36510-compress-requests
VC-36510: Key Pair and Venafi Connection modes now use gzip compression
2 parents 8731003 + 620a050 commit 1f00f09

File tree

5 files changed

+256
-27
lines changed

5 files changed

+256
-27
lines changed

pkg/agent/config.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ import (
2323
"github.com/jetstack/preflight/pkg/version"
2424
)
2525

26-
// Config wraps the options for a run of the agent.
26+
// Config defines the YAML configuration file that you can pass using
27+
// `--config-file` or `-c`.
2728
type Config struct {
2829
// Deprecated: Schedule doesn't do anything. Use `period` instead.
2930
Schedule string `yaml:"schedule"`
@@ -159,6 +160,11 @@ type AgentCmdFlags struct {
159160

160161
// Prometheus (--enable-metrics) enables the Prometheus metrics server.
161162
Prometheus bool
163+
164+
// DisableCompression (--disable-compression) disables the GZIP compression
165+
// when uploading the data. Useful for debugging purposes, or when an
166+
// intermediate proxy doesn't like compressed data.
167+
DisableCompression bool
162168
}
163169

164170
func InitAgentCmdFlags(c *cobra.Command, cfg *AgentCmdFlags) {
@@ -285,6 +291,12 @@ func InitAgentCmdFlags(c *cobra.Command, cfg *AgentCmdFlags) {
285291
"Enables Prometheus metrics server on the agent (port: 8081).",
286292
)
287293

294+
c.PersistentFlags().BoolVar(
295+
&cfg.DisableCompression,
296+
"disable-compression",
297+
false,
298+
"Disables GZIP compression when uploading the data. Useful for debugging purposes or when an intermediate proxy doesn't like compressed data.",
299+
)
288300
}
289301

290302
type AuthMode string
@@ -326,6 +338,9 @@ type CombinedConfig struct {
326338
VenConnName string
327339
VenConnNS string
328340

341+
// VenafiCloudKeypair and VenafiCloudVenafiConnection modes only.
342+
DisableCompression bool
343+
329344
// Only used for testing purposes.
330345
OutputPath string
331346
InputPath string
@@ -558,6 +573,14 @@ func ValidateAndCombineConfig(log *log.Logger, cfg Config, flags AgentCmdFlags)
558573
}
559574
}
560575

576+
// Validation of --disable-compression.
577+
{
578+
if flags.DisableCompression && res.AuthMode != VenafiCloudKeypair && res.AuthMode != VenafiCloudVenafiConnection {
579+
errs = multierror.Append(errs, fmt.Errorf("--disable-compression can only be used with the %s and %s modes", VenafiCloudKeypair, VenafiCloudVenafiConnection))
580+
}
581+
res.DisableCompression = flags.DisableCompression
582+
}
583+
561584
if errs != nil {
562585
return CombinedConfig{}, nil, errs
563586
}
@@ -652,7 +675,7 @@ func validateCredsAndCreateClient(log *log.Logger, flagCredentialsPath, flagClie
652675
break // Don't continue with the client if kubeconfig wasn't loaded.
653676
}
654677

655-
preflightClient, err = client.NewVenConnClient(restCfg, metadata, cfg.InstallNS, cfg.VenConnName, cfg.VenConnNS, nil)
678+
preflightClient, err = client.NewVenConnClient(restCfg, metadata, cfg.InstallNS, cfg.VenConnName, cfg.VenConnNS, nil, cfg.DisableCompression)
656679
if err != nil {
657680
errs = multierror.Append(errs, err)
658681
}
@@ -710,7 +733,7 @@ func createCredentialClient(log *log.Logger, credentials client.Credentials, cfg
710733
log.Println("Loading upload_path from \"venafi-cloud\" configuration.")
711734
uploadPath = cfg.UploadPath
712735
}
713-
return client.NewVenafiCloudClient(agentMetadata, creds, cfg.Server, uploaderID, uploadPath)
736+
return client.NewVenafiCloudClient(agentMetadata, creds, cfg.Server, uploaderID, uploadPath, cfg.DisableCompression)
714737

715738
case *client.OAuthCredentials:
716739
return client.NewOAuthClient(agentMetadata, creds, cfg.Server)

pkg/agent/config_test.go

Lines changed: 144 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package agent
22

33
import (
44
"bytes"
5+
"compress/gzip"
56
"context"
67
"fmt"
78
"io"
@@ -373,6 +374,19 @@ func Test_ValidateAndCombineConfig(t *testing.T) {
373374
assert.IsType(t, &client.OAuthClient{}, cl)
374375
})
375376

377+
t.Run("jetstack-secure-oauth-auth: can't use --disable-compression", func(t *testing.T) {
378+
path := withFile(t, `{"user_id":"[email protected]","user_secret":"foo","client_id": "k3TrDbfLhCgnpAbOiiT2kIE1AbovKzjo","client_secret": "f39w_3KT9Vp0VhzcPzvh-uVbudzqCFmHER3Huj0dvHgJwVrjxsoOQPIw_1SDiCfa","auth_server_domain":"auth.jetstack.io"}`)
379+
_, _, err := ValidateAndCombineConfig(discardLogs(),
380+
withConfig(testutil.Undent(`
381+
server: https://api.venafi.eu
382+
period: 1h
383+
organization_id: foo
384+
cluster_id: bar
385+
`)),
386+
withCmdLineFlags("--disable-compression", "--credentials-file", path, "--install-namespace", "venafi"))
387+
require.EqualError(t, err, "1 error occurred:\n\t* --disable-compression can only be used with the Venafi Cloud Key Pair Service Account and Venafi Cloud VenafiConnection modes\n\n")
388+
})
389+
376390
t.Run("jetstack-secure-oauth-auth: --credential-file used but file is missing", func(t *testing.T) {
377391
t.Setenv("POD_NAMESPACE", "venafi")
378392
got, _, err := ValidateAndCombineConfig(discardLogs(),
@@ -632,6 +646,83 @@ func Test_ValidateAndCombineConfig_VenafiCloudKeyPair(t *testing.T) {
632646
err = cl.PostDataReadingsWithOptions(nil, client.Options{ClusterName: "test cluster name"})
633647
require.NoError(t, err)
634648
})
649+
650+
t.Run("the request body is compressed", func(t *testing.T) {
651+
srv, cert, setVenafiCloudAssert := testutil.FakeVenafiCloud(t)
652+
setVenafiCloudAssert(func(t testing.TB, gotReq *http.Request) {
653+
if gotReq.URL.Path == "/v1/oauth/token/serviceaccount" {
654+
return
655+
}
656+
assert.Equal(t, "/v1/tlspk/upload/clusterdata/no", gotReq.URL.Path)
657+
658+
// Let's check that the body is compressed as expected.
659+
assert.Equal(t, "gzip", gotReq.Header.Get("Content-Encoding"))
660+
uncompressR, err := gzip.NewReader(gotReq.Body)
661+
require.NoError(t, err, "body might not be compressed")
662+
defer uncompressR.Close()
663+
uncompressed, err := io.ReadAll(uncompressR)
664+
require.NoError(t, err)
665+
assert.Contains(t, string(uncompressed), `{"agent_metadata":{"version":"development","cluster_id":"test cluster name"}`)
666+
})
667+
privKeyPath := withFile(t, fakePrivKeyPEM)
668+
got, cl, err := ValidateAndCombineConfig(discardLogs(),
669+
withConfig(testutil.Undent(`
670+
server: `+srv.URL+`
671+
period: 1h
672+
cluster_id: "test cluster name"
673+
venafi-cloud:
674+
uploader_id: no
675+
upload_path: /v1/tlspk/upload/clusterdata
676+
`)),
677+
withCmdLineFlags("--client-id", "5bc7d07c-45da-11ef-a878-523f1e1d7de1", "--private-key-path", privKeyPath, "--install-namespace", "venafi"),
678+
)
679+
require.NoError(t, err)
680+
testutil.TrustCA(t, cl, cert)
681+
assert.Equal(t, VenafiCloudKeypair, got.AuthMode)
682+
require.NoError(t, err)
683+
684+
err = cl.PostDataReadingsWithOptions(nil, client.Options{ClusterName: "test cluster name"})
685+
require.NoError(t, err)
686+
})
687+
688+
t.Run("--disable-compression works", func(t *testing.T) {
689+
srv, cert, setVenafiCloudAssert := testutil.FakeVenafiCloud(t)
690+
setVenafiCloudAssert(func(t testing.TB, gotReq *http.Request) {
691+
// Only care about /v1/tlspk/upload/clusterdata/:uploader_id?name=
692+
if gotReq.URL.Path == "/v1/oauth/token/serviceaccount" {
693+
return
694+
}
695+
696+
assert.Equal(t, "/v1/tlspk/upload/clusterdata/no", gotReq.URL.Path)
697+
698+
// Let's check that the body isn't compressed.
699+
assert.Equal(t, "", gotReq.Header.Get("Content-Encoding"))
700+
b := new(bytes.Buffer)
701+
_, err := b.ReadFrom(gotReq.Body)
702+
require.NoError(t, err)
703+
assert.Contains(t, b.String(), `{"agent_metadata":{"version":"development","cluster_id":"test cluster name"}`)
704+
})
705+
706+
privKeyPath := withFile(t, fakePrivKeyPEM)
707+
got, cl, err := ValidateAndCombineConfig(discardLogs(),
708+
withConfig(testutil.Undent(`
709+
server: `+srv.URL+`
710+
period: 1h
711+
cluster_id: "test cluster name"
712+
venafi-cloud:
713+
uploader_id: no
714+
upload_path: /v1/tlspk/upload/clusterdata
715+
`)),
716+
withCmdLineFlags("--disable-compression", "--client-id", "5bc7d07c-45da-11ef-a878-523f1e1d7de1", "--private-key-path", privKeyPath, "--install-namespace", "venafi"),
717+
)
718+
require.NoError(t, err)
719+
testutil.TrustCA(t, cl, cert)
720+
assert.Equal(t, VenafiCloudKeypair, got.AuthMode)
721+
require.NoError(t, err)
722+
723+
err = cl.PostDataReadingsWithOptions(nil, client.Options{ClusterName: "test cluster name"})
724+
require.NoError(t, err)
725+
})
635726
}
636727

637728
// Slower test cases due to envtest. That's why they are separated from the
@@ -711,8 +802,12 @@ func Test_ValidateAndCombineConfig_VenafiConnection(t *testing.T) {
711802
})
712803

713804
cfg, cl, err := ValidateAndCombineConfig(discardLogs(),
714-
Config{Server: "http://this-url-should-be-ignored", Period: 1 * time.Hour, ClusterID: "test cluster name"},
715-
AgentCmdFlags{VenConnName: "venafi-components", InstallNS: "venafi"})
805+
withConfig(testutil.Undent(`
806+
server: http://this-url-should-be-ignored
807+
period: 1h
808+
cluster_id: test cluster name
809+
`)),
810+
withCmdLineFlags("--venafi-connection", "venafi-components", "--install-namespace", "venafi"))
716811
require.NoError(t, err)
717812

718813
testutil.VenConnStartWatching(t, cl)
@@ -724,6 +819,53 @@ func Test_ValidateAndCombineConfig_VenafiConnection(t *testing.T) {
724819
err = cl.PostDataReadingsWithOptions(nil, client.Options{ClusterName: cfg.ClusterID})
725820
require.NoError(t, err)
726821
})
822+
823+
t.Run("the request is compressed by default", func(t *testing.T) {
824+
setVenafiCloudAssert(func(t testing.TB, gotReq *http.Request) {
825+
// Let's check that the body is compressed as expected.
826+
assert.Equal(t, "gzip", gotReq.Header.Get("Content-Encoding"))
827+
uncompressR, err := gzip.NewReader(gotReq.Body)
828+
require.NoError(t, err, "body might not be compressed")
829+
defer uncompressR.Close()
830+
uncompressed, err := io.ReadAll(uncompressR)
831+
require.NoError(t, err)
832+
assert.Contains(t, string(uncompressed), `{"agent_metadata":{"version":"development","cluster_id":"test cluster name"}`)
833+
})
834+
cfg, cl, err := ValidateAndCombineConfig(discardLogs(),
835+
withConfig(testutil.Undent(`
836+
period: 1h
837+
cluster_id: test cluster name
838+
`)),
839+
withCmdLineFlags("--venafi-connection", "venafi-components", "--install-namespace", "venafi"))
840+
require.NoError(t, err)
841+
testutil.VenConnStartWatching(t, cl)
842+
testutil.TrustCA(t, cl, cert)
843+
err = cl.PostDataReadingsWithOptions(nil, client.Options{ClusterName: cfg.ClusterID})
844+
require.NoError(t, err)
845+
})
846+
847+
t.Run("--disable-compression works", func(t *testing.T) {
848+
setVenafiCloudAssert(func(t testing.TB, gotReq *http.Request) {
849+
// Let's check that the body isn't compressed.
850+
assert.Equal(t, "", gotReq.Header.Get("Content-Encoding"))
851+
b := new(bytes.Buffer)
852+
_, err := b.ReadFrom(gotReq.Body)
853+
require.NoError(t, err)
854+
assert.Contains(t, b.String(), `{"agent_metadata":{"version":"development","cluster_id":"test cluster name"}`)
855+
})
856+
cfg, cl, err := ValidateAndCombineConfig(discardLogs(),
857+
withConfig(testutil.Undent(`
858+
server: `+srv.URL+`
859+
period: 1h
860+
cluster_id: test cluster name
861+
`)),
862+
withCmdLineFlags("--disable-compression", "--venafi-connection", "venafi-components", "--install-namespace", "venafi"))
863+
require.NoError(t, err)
864+
testutil.VenConnStartWatching(t, cl)
865+
testutil.TrustCA(t, cl, cert)
866+
err = cl.PostDataReadingsWithOptions(nil, client.Options{ClusterName: cfg.ClusterID})
867+
require.NoError(t, err)
868+
})
727869
}
728870

729871
func Test_ParseConfig(t *testing.T) {

pkg/client/client_venafi_cloud.go

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package client
22

33
import (
44
"bytes"
5+
"compress/gzip"
56
"crypto"
67
"crypto/ecdsa"
78
"crypto/ed25519"
@@ -50,6 +51,8 @@ type (
5051
jwtSigningAlg jwt.SigningMethod
5152
lock sync.RWMutex
5253

54+
disableCompression bool
55+
5356
// Made public for testing purposes.
5457
Client *http.Client
5558
}
@@ -84,7 +87,7 @@ const (
8487

8588
// NewVenafiCloudClient returns a new instance of the VenafiCloudClient type that will perform HTTP requests using a bearer token
8689
// to authenticate to the backend API.
87-
func NewVenafiCloudClient(agentMetadata *api.AgentMetadata, credentials *VenafiSvcAccountCredentials, baseURL string, uploaderID string, uploadPath string) (*VenafiCloudClient, error) {
90+
func NewVenafiCloudClient(agentMetadata *api.AgentMetadata, credentials *VenafiSvcAccountCredentials, baseURL string, uploaderID string, uploadPath string, disableCompression bool) (*VenafiCloudClient, error) {
8891
if err := credentials.Validate(); err != nil {
8992
return nil, fmt.Errorf("cannot create VenafiCloudClient: %w", err)
9093
}
@@ -107,15 +110,16 @@ func NewVenafiCloudClient(agentMetadata *api.AgentMetadata, credentials *VenafiS
107110
}
108111

109112
return &VenafiCloudClient{
110-
agentMetadata: agentMetadata,
111-
credentials: credentials,
112-
baseURL: baseURL,
113-
accessToken: &venafiCloudAccessToken{},
114-
Client: &http.Client{Timeout: time.Minute},
115-
uploaderID: uploaderID,
116-
uploadPath: uploadPath,
117-
privateKey: privateKey,
118-
jwtSigningAlg: jwtSigningAlg,
113+
agentMetadata: agentMetadata,
114+
credentials: credentials,
115+
baseURL: baseURL,
116+
accessToken: &venafiCloudAccessToken{},
117+
Client: &http.Client{Timeout: time.Minute},
118+
uploaderID: uploaderID,
119+
uploadPath: uploadPath,
120+
privateKey: privateKey,
121+
jwtSigningAlg: jwtSigningAlg,
122+
disableCompression: disableCompression,
119123
}, nil
120124
}
121125

@@ -260,11 +264,39 @@ func (c *VenafiCloudClient) Post(path string, body io.Reader) (*http.Response, e
260264
return nil, err
261265
}
262266

263-
req, err := http.NewRequest(http.MethodPost, fullURL(c.baseURL, path), body)
267+
var encodedBody io.Reader
268+
if c.disableCompression {
269+
encodedBody = body
270+
} else {
271+
compressed := new(bytes.Buffer)
272+
gz := gzip.NewWriter(compressed)
273+
if _, err := io.Copy(gz, body); err != nil {
274+
return nil, err
275+
}
276+
err := gz.Close()
277+
if err != nil {
278+
return nil, err
279+
}
280+
encodedBody = compressed
281+
}
282+
283+
req, err := http.NewRequest(http.MethodPost, fullURL(c.baseURL, path), encodedBody)
264284
if err != nil {
265285
return nil, err
266286
}
267287

288+
// We have noticed that NGINX, which is Venafi Control Plane's API gateway,
289+
// has a limit on the request body size we can send (client_max_body_size).
290+
// On large clusters, the agent may exceed this limit, triggering the error
291+
// "413 Request Entity Too Large". Although this limit has been raised to
292+
// 1GB, NGINX still buffers the requests that the agent sends because
293+
// proxy_request_buffering isn't set to off. To reduce the strain on NGINX'
294+
// memory and disk, to avoid further 413s, and to avoid reaching the maximum
295+
// request body size of customer's proxies, we have decided to enable GZIP
296+
// compression. Ref: https://venafi.atlassian.net/browse/VC-36434.
297+
if !c.disableCompression {
298+
req.Header.Set("Content-Encoding", "gzip")
299+
}
268300
req.Header.Set("Accept", "application/json")
269301
req.Header.Set("Content-Type", "application/json")
270302

0 commit comments

Comments
 (0)