Skip to content

Commit 5435589

Browse files
tas50claude
andauthored
⭐ Add GCP Security Command Center and VPC Service Controls resources (#7118)
* ⭐ Add Security Command Center and VPC Service Controls resources Add two new GCP service areas to the provider: **Security Command Center (SCC):** - Sources, Findings, NotificationConfigs, MuteConfigs, BigQueryExports - Available at org level (gcp.organization.scc*) and project level (gcp.project.sccFindings) - Uses wildcard source listing (sources/-) for cross-source finding queries **VPC Service Controls (Access Context Manager):** - AccessPolicies, AccessLevels, ServicePerimeters - Available at org level (gcp.organization.accessPolicies) - AccessLevels and ServicePerimeters lazy-loaded as sub-resources of AccessPolicy Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address review comments: filter active findings, remove unused constant - Add state="ACTIVE" filter to listSCCFindings to avoid unbounded results - Remove unused service_accesscontextmanager constant (ACM is org-level only) - Update .lr comments to document active-only filtering - Add accesscontextmanager and scc to spelling expect.txt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Address review comments: simplify sccParent, document org-level service check Use g.Id.Data directly in sccParent() instead of redundantly calling conn.OrganizationID(). Add comment explaining why org-level SCC methods skip isServiceEnabled. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * 🐛 Add isServiceEnabled checks to GCP services missing them Discovery would fatally crash when a GCP API was not enabled in the target project. Services like DNS, BigQuery, and GKE already had the serviceEnabled guard pattern, but 10 other services did not: Redis, Secret Manager, Pub/Sub, Cloud Run, Cloud Functions, Logging, KMS, Cloud SQL, Storage, and IAM. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * 🐛 Add missing serviceEnabled guards to logging and cloudrun methods Address review: add guards to metrics(), sinks() on loggingservice and operations() on cloudRunService for consistency with other methods. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 21f6518 commit 5435589

File tree

20 files changed

+2379
-29
lines changed

20 files changed

+2379
-29
lines changed

.github/actions/spelling/expect.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
aad
22
ACCOUNTADMIN
3+
accesscontextmanager
34
activedirectory
45
ACTIVEMQ
56
adr
@@ -247,6 +248,7 @@ runbooks
247248
saas
248249
Sas
249250
sbom
251+
scc
250252
scim
251253
scm
252254
SECRETID

providers/gcp/go.mod

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ go 1.25.7
66

77
require (
88
cloud.google.com/go/accessapproval v1.8.8
9+
cloud.google.com/go/accesscontextmanager v1.9.7
910
cloud.google.com/go/aiplatform v1.121.0
1011
cloud.google.com/go/alloydb v1.21.0
1112
cloud.google.com/go/artifactregistry v1.20.0
@@ -31,6 +32,7 @@ require (
3132
cloud.google.com/go/run v1.16.0
3233
cloud.google.com/go/scheduler v1.11.8
3334
cloud.google.com/go/security v1.19.2
35+
cloud.google.com/go/securitycenter v1.38.1
3436
cloud.google.com/go/serviceusage v1.9.7
3537
cloud.google.com/go/spanner v1.89.0
3638
github.com/aws/smithy-go v1.24.2
@@ -43,7 +45,7 @@ require (
4345
go.mondoo.com/mql/v13 v13.2.0
4446
go.mondoo.com/ranger-rpc v0.8.0
4547
golang.org/x/oauth2 v0.36.0
46-
google.golang.org/api v0.273.0
48+
google.golang.org/api v0.273.1
4749
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7
4850
google.golang.org/protobuf v1.36.11
4951
)
@@ -293,8 +295,8 @@ require (
293295
golang.org/x/time v0.15.0 // indirect
294296
golang.org/x/tools v0.43.0 // indirect
295297
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
296-
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 // indirect
297-
google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 // indirect
298+
google.golang.org/genproto/googleapis/api v0.0.0-20260401001100-f93e5f3e9f0f // indirect
299+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401001100-f93e5f3e9f0f // indirect
298300
google.golang.org/grpc v1.79.3
299301
gopkg.in/warnings.v0 v0.1.2 // indirect
300302
gopkg.in/yaml.v3 v3.0.1 // indirect

providers/gcp/go.sum

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
3333
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
3434
cloud.google.com/go/accessapproval v1.8.8 h1:gq8OS+rQWgGRo91D2qztN+ion6AZ2T1CxBIu0ifCmVo=
3535
cloud.google.com/go/accessapproval v1.8.8/go.mod h1:RFwPY9JDKseP4gJrX1BlAVsP5O6kI8NdGlTmaeDefmk=
36+
cloud.google.com/go/accesscontextmanager v1.9.7 h1:aKIfg7Jyc73pe8bzx0zypNdS5gfFdSvFvB8YNA9k2kA=
37+
cloud.google.com/go/accesscontextmanager v1.9.7/go.mod h1:i6e0nd5CPcrh7+YwGq4bKvju5YB9sgoAip+mXU73aMM=
3638
cloud.google.com/go/aiplatform v1.121.0 h1:8y8sNfVAW1DVhFbSbI7d8rrqBGGJFk6EoV6atidlyQc=
3739
cloud.google.com/go/aiplatform v1.121.0/go.mod h1:juMdDWeNphHV40KhWdN+563zNCOKNmLJjk5D2TA43ls=
3840
cloud.google.com/go/alloydb v1.21.0 h1:f8udyaV5PmAKcsTOOsIlgJdLBf4DrO+ML5o/iJvdCLY=
@@ -110,6 +112,8 @@ cloud.google.com/go/secretmanager v1.16.0 h1:19QT7ZsLJ8FSP1k+4esQvuCD7npMJml6hYz
110112
cloud.google.com/go/secretmanager v1.16.0/go.mod h1://C/e4I8D26SDTz1f3TQcddhcmiC3rMEl0S1Cakvs3Q=
111113
cloud.google.com/go/security v1.19.2 h1:cF3FkCRRbRC1oXuaGZFl3qU2sdu2gP3iOAHKzL5y04Y=
112114
cloud.google.com/go/security v1.19.2/go.mod h1:KXmf64mnOsLVKe8mk/bZpU1Rsvxqc0Ej0A6tgCeN93w=
115+
cloud.google.com/go/securitycenter v1.38.1 h1:D9zpeguY4frQU35GBw8+M6Gw79CiuTF9iVs4sFm3FDY=
116+
cloud.google.com/go/securitycenter v1.38.1/go.mod h1:Ge2D/SlG2lP1FrQD7wXHy8qyeloRenvKXeB4e7zO6z0=
113117
cloud.google.com/go/serviceusage v1.9.7 h1:vrBBeI2ESmri4BLGPz1YH2o37loIQ3DDTloYiDOe2lY=
114118
cloud.google.com/go/serviceusage v1.9.7/go.mod h1:JpBpv+4Zbe7+RiC9ydc6xgBUOntIL9tA85d2xKgV83g=
115119
cloud.google.com/go/spanner v1.89.0 h1:r3h5Z5RA8JRPf3HCvA6ujNhREIMhPY+MrDL9mkY8jS0=
@@ -1351,8 +1355,8 @@ google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdr
13511355
google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU=
13521356
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
13531357
google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw=
1354-
google.golang.org/api v0.273.0 h1:r/Bcv36Xa/te1ugaN1kdJ5LoA5Wj/cL+a4gj6FiPBjQ=
1355-
google.golang.org/api v0.273.0/go.mod h1:JbAt7mF+XVmWu6xNP8/+CTiGH30ofmCmk9nM8d8fHew=
1358+
google.golang.org/api v0.273.1 h1:L7G/TmpAMz0nKx/ciAVssVmWQiOF6+pOuXeKrWVsquY=
1359+
google.golang.org/api v0.273.1/go.mod h1:JbAt7mF+XVmWu6xNP8/+CTiGH30ofmCmk9nM8d8fHew=
13561360
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
13571361
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
13581362
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -1425,10 +1429,10 @@ google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ6
14251429
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
14261430
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
14271431
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
1428-
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI=
1429-
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
1430-
google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 h1:ndE4FoJqsIceKP2oYSnUZqhTdYufCYYkqwtFzfrhI7w=
1431-
google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
1432+
google.golang.org/genproto/googleapis/api v0.0.0-20260401001100-f93e5f3e9f0f h1:K3zPU40OFjwD5YKADLMLoiL0L7JJpBgEdLqGuCNPfp0=
1433+
google.golang.org/genproto/googleapis/api v0.0.0-20260401001100-f93e5f3e9f0f/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y=
1434+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401001100-f93e5f3e9f0f h1:Rka45QInERYknkHYfJEPBQaoobXl+YpxTMjAKgWUq2A=
1435+
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401001100-f93e5f3e9f0f/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
14321436
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
14331437
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
14341438
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// Copyright Mondoo, Inc. 2024, 2026
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package resources
5+
6+
import (
7+
"context"
8+
9+
accesscontextmanager "cloud.google.com/go/accesscontextmanager/apiv1"
10+
acmpb "cloud.google.com/go/accesscontextmanager/apiv1/accesscontextmanagerpb"
11+
"go.mondoo.com/mql/v13/llx"
12+
"go.mondoo.com/mql/v13/providers/gcp/connection"
13+
"google.golang.org/api/iterator"
14+
"google.golang.org/api/option"
15+
)
16+
17+
func (g *mqlGcpAccesscontextmanagerAccessPolicy) id() (string, error) {
18+
return g.Name.Data, g.Name.Error
19+
}
20+
21+
func (g *mqlGcpAccesscontextmanagerAccessLevel) id() (string, error) {
22+
return g.Name.Data, g.Name.Error
23+
}
24+
25+
func (g *mqlGcpAccesscontextmanagerServicePerimeter) id() (string, error) {
26+
return g.Name.Data, g.Name.Error
27+
}
28+
29+
func newACMClient(conn *connection.GcpConnection) (*accesscontextmanager.Client, error) {
30+
creds, err := conn.Credentials(accesscontextmanager.DefaultAuthScopes()...)
31+
if err != nil {
32+
return nil, err
33+
}
34+
return accesscontextmanager.NewClient(context.Background(), option.WithCredentials(creds))
35+
}
36+
37+
func (g *mqlGcpOrganization) accessPolicies() ([]any, error) {
38+
if g.Id.Error != nil {
39+
return nil, g.Id.Error
40+
}
41+
42+
conn := g.MqlRuntime.Connection.(*connection.GcpConnection)
43+
orgId, err := conn.OrganizationID()
44+
if err != nil {
45+
return nil, err
46+
}
47+
48+
client, err := newACMClient(conn)
49+
if err != nil {
50+
return nil, err
51+
}
52+
defer client.Close()
53+
54+
it := client.ListAccessPolicies(context.Background(), &acmpb.ListAccessPoliciesRequest{
55+
Parent: "organizations/" + orgId,
56+
})
57+
58+
var res []any
59+
for {
60+
policy, err := it.Next()
61+
if err == iterator.Done {
62+
break
63+
}
64+
if err != nil {
65+
return nil, err
66+
}
67+
68+
mqlPolicy, err := CreateResource(g.MqlRuntime, "gcp.accesscontextmanager.accessPolicy", map[string]*llx.RawData{
69+
"name": llx.StringData(policy.Name),
70+
"title": llx.StringData(policy.Title),
71+
"parent": llx.StringData(policy.Parent),
72+
"etag": llx.StringData(policy.Etag),
73+
})
74+
if err != nil {
75+
return nil, err
76+
}
77+
res = append(res, mqlPolicy)
78+
}
79+
80+
return res, nil
81+
}
82+
83+
func (g *mqlGcpAccesscontextmanagerAccessPolicy) accessLevels() ([]any, error) {
84+
if g.Name.Error != nil {
85+
return nil, g.Name.Error
86+
}
87+
policyName := g.Name.Data
88+
89+
conn := g.MqlRuntime.Connection.(*connection.GcpConnection)
90+
client, err := newACMClient(conn)
91+
if err != nil {
92+
return nil, err
93+
}
94+
defer client.Close()
95+
96+
it := client.ListAccessLevels(context.Background(), &acmpb.ListAccessLevelsRequest{
97+
Parent: policyName,
98+
})
99+
100+
var res []any
101+
for {
102+
level, err := it.Next()
103+
if err == iterator.Done {
104+
break
105+
}
106+
if err != nil {
107+
return nil, err
108+
}
109+
110+
basic, err := protoToDict(level.GetBasic())
111+
if err != nil {
112+
return nil, err
113+
}
114+
custom, err := protoToDict(level.GetCustom())
115+
if err != nil {
116+
return nil, err
117+
}
118+
119+
mqlLevel, err := CreateResource(g.MqlRuntime, "gcp.accesscontextmanager.accessLevel", map[string]*llx.RawData{
120+
"name": llx.StringData(level.Name),
121+
"title": llx.StringData(level.Title),
122+
"description": llx.StringData(level.Description),
123+
"basic": llx.DictData(basic),
124+
"custom": llx.DictData(custom),
125+
"createTime": llx.TimeDataPtr(timestampAsTimePtr(level.CreateTime)),
126+
"updateTime": llx.TimeDataPtr(timestampAsTimePtr(level.UpdateTime)),
127+
})
128+
if err != nil {
129+
return nil, err
130+
}
131+
res = append(res, mqlLevel)
132+
}
133+
134+
return res, nil
135+
}
136+
137+
func (g *mqlGcpAccesscontextmanagerAccessPolicy) servicePerimeters() ([]any, error) {
138+
if g.Name.Error != nil {
139+
return nil, g.Name.Error
140+
}
141+
policyName := g.Name.Data
142+
143+
conn := g.MqlRuntime.Connection.(*connection.GcpConnection)
144+
client, err := newACMClient(conn)
145+
if err != nil {
146+
return nil, err
147+
}
148+
defer client.Close()
149+
150+
it := client.ListServicePerimeters(context.Background(), &acmpb.ListServicePerimetersRequest{
151+
Parent: policyName,
152+
})
153+
154+
var res []any
155+
for {
156+
perimeter, err := it.Next()
157+
if err == iterator.Done {
158+
break
159+
}
160+
if err != nil {
161+
return nil, err
162+
}
163+
164+
status, err := protoToDict(perimeter.Status)
165+
if err != nil {
166+
return nil, err
167+
}
168+
spec, err := protoToDict(perimeter.Spec)
169+
if err != nil {
170+
return nil, err
171+
}
172+
173+
mqlPerimeter, err := CreateResource(g.MqlRuntime, "gcp.accesscontextmanager.servicePerimeter", map[string]*llx.RawData{
174+
"name": llx.StringData(perimeter.Name),
175+
"title": llx.StringData(perimeter.Title),
176+
"description": llx.StringData(perimeter.Description),
177+
"perimeterType": llx.StringData(perimeter.PerimeterType.String()),
178+
"status": llx.DictData(status),
179+
"spec": llx.DictData(spec),
180+
"useExplicitDryRunSpec": llx.BoolData(perimeter.UseExplicitDryRunSpec),
181+
"createTime": llx.TimeDataPtr(timestampAsTimePtr(perimeter.CreateTime)),
182+
"updateTime": llx.TimeDataPtr(timestampAsTimePtr(perimeter.UpdateTime)),
183+
})
184+
if err != nil {
185+
return nil, err
186+
}
187+
res = append(res, mqlPerimeter)
188+
}
189+
190+
return res, nil
191+
}

providers/gcp/resources/cloud_functions.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"errors"
99
"fmt"
1010

11+
"github.com/rs/zerolog/log"
1112
"go.mondoo.com/mql/v13/providers-sdk/v1/plugin"
1213
"go.mondoo.com/mql/v13/providers-sdk/v1/util/convert"
1314
"go.mondoo.com/mql/v13/providers/gcp/connection"
@@ -24,6 +25,16 @@ func (g *mqlGcpProject) cloudFunctions() ([]any, error) {
2425
if g.Id.Error != nil {
2526
return nil, g.Id.Error
2627
}
28+
29+
serviceEnabled, err := g.isServiceEnabled(service_cloudfunctions)
30+
if err != nil {
31+
return nil, err
32+
}
33+
if !serviceEnabled {
34+
log.Debug().Str("service", service_cloudfunctions).Msg("gcp service is not enabled, skipping")
35+
return nil, nil
36+
}
37+
2738
projectId := g.Id.Data
2839

2940
conn := g.MqlRuntime.Connection.(*connection.GcpConnection)

providers/gcp/resources/cloudrun.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ func initGcpProjectCloudRunService(runtime *plugin.Runtime, args map[string]*llx
4949
return args, nil, nil
5050
}
5151

52+
type mqlGcpProjectCloudRunServiceInternal struct {
53+
serviceEnabled bool
54+
}
55+
5256
func (g *mqlGcpProject) cloudRun() (*mqlGcpProjectCloudRunService, error) {
5357
if g.Id.Error != nil {
5458
return nil, g.Id.Error
@@ -61,7 +65,19 @@ func (g *mqlGcpProject) cloudRun() (*mqlGcpProjectCloudRunService, error) {
6165
if err != nil {
6266
return nil, err
6367
}
64-
return res.(*mqlGcpProjectCloudRunService), nil
68+
69+
serviceEnabled, err := g.isServiceEnabled(service_cloudrun)
70+
if err != nil {
71+
return nil, err
72+
}
73+
74+
svc := res.(*mqlGcpProjectCloudRunService)
75+
svc.serviceEnabled = serviceEnabled
76+
if !serviceEnabled {
77+
log.Debug().Str("service", service_cloudrun).Msg("gcp service is not enabled, skipping")
78+
}
79+
80+
return svc, nil
6581
}
6682

6783
func (g *mqlGcpProjectCloudRunServiceOperation) id() (string, error) {
@@ -207,6 +223,10 @@ func (g *mqlGcpProjectCloudRunServiceJobExecutionTemplateTaskTemplate) id() (str
207223
}
208224

209225
func (g *mqlGcpProjectCloudRunService) regions() ([]any, error) {
226+
if !g.serviceEnabled {
227+
return nil, nil
228+
}
229+
210230
conn := g.MqlRuntime.Connection.(*connection.GcpConnection)
211231

212232
if g.ProjectId.Error != nil {
@@ -238,6 +258,10 @@ func (g *mqlGcpProjectCloudRunService) regions() ([]any, error) {
238258
}
239259

240260
func (g *mqlGcpProjectCloudRunService) operations() ([]any, error) {
261+
if !g.serviceEnabled {
262+
return nil, nil
263+
}
264+
241265
if g.ProjectId.Error != nil {
242266
return nil, g.ProjectId.Error
243267
}
@@ -298,6 +322,10 @@ func (g *mqlGcpProjectCloudRunService) operations() ([]any, error) {
298322
}
299323

300324
func (g *mqlGcpProjectCloudRunService) services() ([]any, error) {
325+
if !g.serviceEnabled {
326+
return nil, nil
327+
}
328+
301329
if g.ProjectId.Error != nil {
302330
return nil, g.ProjectId.Error
303331
}
@@ -523,6 +551,10 @@ func (g *mqlGcpProjectCloudRunServiceJobExecutionTemplateTaskTemplate) serviceAc
523551
}
524552

525553
func (g *mqlGcpProjectCloudRunService) jobs() ([]any, error) {
554+
if !g.serviceEnabled {
555+
return nil, nil
556+
}
557+
526558
if g.ProjectId.Error != nil {
527559
return nil, g.ProjectId.Error
528560
}

0 commit comments

Comments
 (0)