Skip to content

Commit ebb77e5

Browse files
authored
⚡️ gcp: verify connection only once (#5442)
* ⚡️ gcp: verify connection only once We are hitting rate limits like: ``` GCP integration error: rpc error: code = Unknown desc = googleapi: Error 429: Quota exceeded for quota metric 'Project V3 get requests' and limit 'Project V3 get requests per minute' of service 'cloudresourcemanager.googleapis.com' for consumer 'project_number:1234567890 ``` This change reduces the number of requests by verifying the connection only once per configuration. We have done this in the past for Github. Closes #5095 Signed-off-by: Salim Afiune Maya <afiune@mondoo.com> * ➡️ move connection options to diff struct Because if not, we get different hashes all the time! Signed-off-by: Salim Afiune Maya <afiune@mondoo.com> --------- Signed-off-by: Salim Afiune Maya <afiune@mondoo.com>
1 parent 2df3a12 commit ebb77e5

File tree

9 files changed

+99
-38
lines changed

9 files changed

+99
-38
lines changed

providers/gcp/connection/clients.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ func (c *GcpConnection) Credentials(scopes ...string) (*googleoauth.Credentials,
2222
ctx := context.Background()
2323
credParams := googleoauth.CredentialsParams{
2424
Scopes: scopes,
25-
Subject: c.serviceAccountSubject,
25+
Subject: c.opts.serviceAccountSubject,
2626
}
27-
if c.cred != nil {
27+
if c.opts.cred != nil {
2828
// use service account from secret
29-
data, err := credsServiceAccountData(c.cred)
29+
data, err := credsServiceAccountData(c.opts.cred)
3030
if err != nil {
3131
return nil, err
3232
}
@@ -42,12 +42,12 @@ func (c *GcpConnection) Client(scope ...string) (*http.Client, error) {
4242
ctx := context.Background()
4343

4444
// use service account from secret if one is provided
45-
if c.cred != nil {
46-
data, err := credsServiceAccountData(c.cred)
45+
if c.opts.cred != nil {
46+
data, err := credsServiceAccountData(c.opts.cred)
4747
if err != nil {
4848
return nil, err
4949
}
50-
return serviceAccountAuth(ctx, c.serviceAccountSubject, data, scope...)
50+
return serviceAccountAuth(ctx, c.opts.serviceAccountSubject, data, scope...)
5151
}
5252

5353
// otherwise fallback to default google sdk authentication

providers/gcp/connection/connection.go

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package connection
66
import (
77
"errors"
88

9+
"github.com/mitchellh/hashstructure/v2"
910
"github.com/rs/zerolog/log"
1011
"go.mondoo.com/cnquery/v11/providers-sdk/v1/inventory"
1112
"go.mondoo.com/cnquery/v11/providers-sdk/v1/plugin"
@@ -33,6 +34,11 @@ type GcpConnection struct {
3334
plugin.Connection
3435
Conf *inventory.Config
3536
asset *inventory.Asset
37+
38+
opts gcpConnectionOptions
39+
}
40+
41+
type gcpConnectionOptions struct {
3642
// custom connection fields
3743
resourceType ResourceType
3844
resourceID string
@@ -47,6 +53,7 @@ func NewGcpConnection(id uint32, asset *inventory.Asset, conf *inventory.Config)
4753
Connection: plugin.NewConnection(id, asset),
4854
Conf: conf,
4955
asset: asset,
56+
opts: gcpConnectionOptions{},
5057
}
5158

5259
// initialize connection
@@ -56,8 +63,11 @@ func NewGcpConnection(id uint32, asset *inventory.Asset, conf *inventory.Config)
5663
cred = conf.Credentials[0]
5764
}
5865
if conf.Type == "gcp" {
59-
if conf.Options == nil || (conf.Options["project-id"] == "" && conf.Options["organization-id"] == "" && conf.Options["folder-id"] == "") {
60-
return nil, errors.New("google provider requires a gcp organization id, gcp project id or google workspace customer id. please set option `project-id` or `organization-id` or `customer-id` or `folder-id`")
66+
if conf.Options == nil ||
67+
(conf.Options["project-id"] == "" && conf.Options["organization-id"] == "" && conf.Options["folder-id"] == "") {
68+
return nil, errors.New(
69+
"google provider requires a gcp organization id, gcp project id or google workspace customer id. " +
70+
"Please set option `project-id` or `organization-id` or `customer-id` or `folder-id`")
6171
}
6272
} else {
6373
return nil, plugin.ErrProviderTypeDoesNotMatch
@@ -90,28 +100,45 @@ func NewGcpConnection(id uint32, asset *inventory.Asset, conf *inventory.Config)
90100
override = conf.Options["platform-override"]
91101
}
92102

93-
conn.resourceID = resourceID
94-
conn.resourceType = resourceType
95-
conn.cred = cred
96-
conn.platformOverride = override
103+
conn.opts.resourceID = resourceID
104+
conn.opts.resourceType = resourceType
105+
conn.opts.cred = cred
106+
conn.opts.platformOverride = override
107+
108+
return conn, nil
109+
}
97110

111+
func (c *GcpConnection) Hash() uint64 {
112+
// generate hash of the config options used to generate this connection
113+
// used to avoid verifying a client with the same options more than once
114+
hash, err := hashstructure.Hash(c.opts, hashstructure.FormatV2, nil)
115+
if err != nil {
116+
log.Error().Err(err).Msg("unable to hash connection")
117+
}
118+
return hash
119+
}
120+
121+
func (c *GcpConnection) Verify() error {
98122
// verify that we have access to the organization or project
99-
switch resourceType {
123+
switch c.ResourceType() {
100124
case Organization:
101-
_, err := conn.GetOrganization(resourceID)
125+
_, err := c.GetOrganization(c.ResourceID())
102126
if err != nil {
103-
log.Error().Err(err).Msgf("could not find or have no access to organization %s", resourceID)
104-
return nil, err
127+
log.Error().Err(err).
128+
Str("organization", c.ResourceID()).
129+
Msg("could not find, or have no access to organization")
130+
return err
105131
}
106132
case Project, Gcr:
107-
_, err := conn.GetProject(resourceID)
133+
_, err := c.GetProject(c.ResourceID())
108134
if err != nil {
109-
log.Error().Err(err).Msgf("could not find or have no access to project %s", resourceID)
110-
return nil, err
135+
log.Error().Err(err).
136+
Str("project", c.ResourceID()).
137+
Msg("could not find, or have no access to project")
138+
return err
111139
}
112140
}
113-
114-
return conn, nil
141+
return nil
115142
}
116143

117144
func (c *GcpConnection) Name() string {

providers/gcp/connection/gcp_organization.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func (c *GcpConnection) OrganizationID() (string, error) {
3030
}
3131

3232
// TODO: GetAncestry is not available in v3 anymore, we need to find an alternative approach
33-
ancest, err := svc.Projects.GetAncestry(c.resourceID, &v1cloudresourcemanager.GetAncestryRequest{}).Do()
33+
ancest, err := svc.Projects.GetAncestry(c.ResourceID(), &v1cloudresourcemanager.GetAncestryRequest{}).Do()
3434
if err != nil {
3535
return "", err
3636
}
@@ -42,7 +42,7 @@ func (c *GcpConnection) OrganizationID() (string, error) {
4242
}
4343
}
4444
case Organization:
45-
return c.resourceID, nil
45+
return c.ResourceID(), nil
4646
}
4747

4848
return "", errors.New("could not find the organization")

providers/gcp/connection/gcpinstancesnapshot/provider.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,16 @@ type GcpSnapshotConnection struct {
250250
identifier string
251251
}
252252

253+
// no-op
254+
func (c *GcpSnapshotConnection) Hash() uint64 {
255+
return 0
256+
}
257+
258+
// no-op
259+
func (c *GcpSnapshotConnection) Verify() error {
260+
return nil
261+
}
262+
253263
func (c *GcpSnapshotConnection) Close() {
254264
log.Debug().Msg("closing gcp snapshot connection")
255265
if c == nil {

providers/gcp/connection/platform.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,37 +29,37 @@ func NewResourcePlatformID(service, project, region, objectType, name string) st
2929
func (c *GcpConnection) Identifier() (string, error) {
3030
switch c.ResourceType() {
3131
case Organization:
32-
return NewOrganizationPlatformID(c.resourceID), nil
32+
return NewOrganizationPlatformID(c.ResourceID()), nil
3333
case Project:
34-
return NewProjectPlatformID(c.resourceID), nil
34+
return NewProjectPlatformID(c.ResourceID()), nil
3535
case Folder:
36-
return NewFolderPlatformID(c.resourceID), nil
36+
return NewFolderPlatformID(c.ResourceID()), nil
3737
default:
3838
return "", fmt.Errorf("unsupported resource type %d", c.ResourceType())
3939
}
4040
}
4141

4242
func (c *GcpConnection) ResourceType() ResourceType {
43-
return c.resourceType
43+
return c.opts.resourceType
4444
}
4545

4646
func (c *GcpConnection) ResourceID() string {
47-
return c.resourceID
47+
return c.opts.resourceID
4848
}
4949

5050
func (c *GcpConnection) PlatformInfo() (*inventory.Platform, error) {
5151
// TODO: this is a hack and we need to find a better way to do this
52-
if c.platformOverride != "" && c.platformOverride != "gcp" {
52+
if c.opts.platformOverride != "" && c.opts.platformOverride != "gcp" {
5353
return &inventory.Platform{
54-
Name: c.platformOverride,
55-
Title: getTitleForPlatformName(c.platformOverride),
54+
Name: c.opts.platformOverride,
55+
Title: getTitleForPlatformName(c.opts.platformOverride),
5656
Family: []string{"google"},
5757
Kind: "gcp-object",
5858
Runtime: "gcp",
5959
}, nil
6060
}
6161

62-
switch c.resourceType {
62+
switch c.ResourceType() {
6363
case Organization:
6464
return &inventory.Platform{
6565
Name: "gcp-org",

providers/gcp/connection/shared/shared.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ type GcpConnection interface {
1616
Type() ConnectionType
1717
Config() *inventory.Config
1818
Asset() *inventory.Asset
19+
20+
// Used to avoid verifying a client with the same options more than once
21+
Verify() error
22+
Hash() uint64
1923
}
2024

2125
type Capabilities byte

providers/gcp/go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ require (
2424
github.com/cockroachdb/errors v1.11.3
2525
github.com/google/go-containerregistry v0.20.3
2626
github.com/hashicorp/go-cleanhttp v0.5.2
27+
github.com/mitchellh/hashstructure/v2 v2.0.2
2728
github.com/rs/zerolog v1.34.0
2829
github.com/stretchr/testify v1.10.0
2930
go.mondoo.com/cnquery/v11 v11.47.1
@@ -60,7 +61,6 @@ require (
6061
github.com/inconshreveable/mousetrap v1.1.0 // indirect
6162
github.com/json-iterator/go v1.1.12 // indirect
6263
github.com/knqyf263/go-rpmdb v0.1.1 // indirect
63-
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
6464
github.com/moby/buildkit v0.19.0 // indirect
6565
github.com/moby/sys/mount v0.3.4 // indirect
6666
github.com/moby/sys/mountinfo v0.7.2 // indirect
@@ -69,6 +69,7 @@ require (
6969
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
7070
github.com/ncruces/go-strftime v0.1.9 // indirect
7171
github.com/package-url/packageurl-go v0.1.3 // indirect
72+
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
7273
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
7374
github.com/prometheus/client_golang v1.20.5 // indirect
7475
github.com/prometheus/client_model v0.6.1 // indirect

providers/gcp/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,8 @@ github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoX
723723
github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0=
724724
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
725725
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
726+
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
727+
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
726728
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
727729
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
728730
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=

providers/gcp/provider/provider.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,43 @@ package provider
66
import (
77
"context"
88
"errors"
9+
"fmt"
910
"os"
11+
"time"
1012

13+
"github.com/rs/zerolog/log"
1114
"go.mondoo.com/ranger-rpc/codes"
1215
"go.mondoo.com/ranger-rpc/status"
1316

1417
"go.mondoo.com/cnquery/v11"
15-
"go.mondoo.com/cnquery/v11/providers-sdk/v1/vault"
16-
1718
"go.mondoo.com/cnquery/v11/llx"
1819
"go.mondoo.com/cnquery/v11/providers-sdk/v1/inventory"
1920
"go.mondoo.com/cnquery/v11/providers-sdk/v1/plugin"
2021
"go.mondoo.com/cnquery/v11/providers-sdk/v1/upstream"
22+
"go.mondoo.com/cnquery/v11/providers-sdk/v1/util/memoize"
23+
"go.mondoo.com/cnquery/v11/providers-sdk/v1/vault"
2124
"go.mondoo.com/cnquery/v11/providers/gcp/connection"
2225
"go.mondoo.com/cnquery/v11/providers/gcp/connection/gcpinstancesnapshot"
2326
"go.mondoo.com/cnquery/v11/providers/gcp/connection/shared"
2427
"go.mondoo.com/cnquery/v11/providers/gcp/resources"
2528
)
2629

27-
const (
28-
ConnectionType = "gcp"
30+
const ConnectionType = "gcp"
31+
32+
var (
33+
cacheExpirationTime = 3 * time.Hour
34+
cacheCleanupTime = 6 * time.Hour
2935
)
3036

3137
type Service struct {
3238
*plugin.Service
39+
*memoize.Memoizer
3340
}
3441

3542
func Init() *Service {
3643
return &Service{
37-
Service: plugin.NewService(),
44+
plugin.NewService(),
45+
memoize.NewMemoizer(cacheExpirationTime, cacheCleanupTime),
3846
}
3947
}
4048

@@ -237,6 +245,15 @@ func (s *Service) connect(req *plugin.ConnectReq, callback plugin.ProviderCallba
237245
return nil, err
238246
}
239247

248+
// verify the connection only once
249+
_, err, _ = s.Memoize(fmt.Sprintf("conn_%d", conn.Hash()), func() (interface{}, error) {
250+
log.Trace().Str("type", string(conn.Type())).Msg("verifying connection client")
251+
return nil, conn.Verify()
252+
})
253+
if err != nil {
254+
return nil, err
255+
}
256+
240257
var upstream *upstream.UpstreamClient
241258
if req.Upstream != nil && !req.Upstream.Incognito {
242259
upstream, err = req.Upstream.InitClient(context.Background())

0 commit comments

Comments
 (0)