Skip to content

Commit

Permalink
gcp compute images discovery as assets
Browse files Browse the repository at this point in the history
Signed-off-by: Ivan Milchev <[email protected]>
  • Loading branch information
imilchev committed Jan 23, 2023
1 parent 37ddce5 commit 1523288
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 11 deletions.
13 changes: 13 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,19 @@
"-c",
"gcp.project.kms.keyrings"
],
},
{
"name": "cnquery-gcp-assets",
"type": "go",
"request": "launch",
"program": "${workspaceRoot}/apps/cnquery/cnquery.go",
"cwd": "${workspaceRoot}/",
"args": [
"scan",
"gcp",
"--discover",
"compute-images"
],
}
]
}
5 changes: 3 additions & 2 deletions motor/discovery/gcp/discovery.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gcp

const (
DiscoveryInstances = "instances"
DiscoveryProjects = "projects"
DiscoveryInstances = "instances"
DiscoveryProjects = "projects"
DiscoveryComputeImages = "compute-images"
)
46 changes: 46 additions & 0 deletions motor/discovery/gcp/mql_asset_objects.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package gcp

import (
"github.com/cockroachdb/errors"
"go.mondoo.com/cnquery/motor/asset"
"go.mondoo.com/cnquery/motor/providers"
)

func getTitleFamily(o gcpObject) (gcpObjectPlatformInfo, error) {
switch o.service {
case "compute":
if o.objectType == "image" {
return gcpObjectPlatformInfo{title: "GCP Compute Image", platform: "gcp-compute-image"}, nil
}
}
return gcpObjectPlatformInfo{}, errors.Newf("missing runtime info for gcp object service %s type %s", o.service, o.objectType)
}

func computeImages(m *MqlDiscovery, project string, tc *providers.Config) []*asset.Asset {
assets := []*asset.Asset{}
images := m.GetList("gcp.project.compute.images { id name labels }")
for i := range images {
b := images[i].(map[string]interface{})
id := b["id"].(string)
name := b["name"].(string)
tags := b["labels"].(map[string]interface{})
stringLabels := make(map[string]string)
for k, v := range tags {
stringLabels[k] = v.(string)
}

assets = append(assets, MqlObjectToAsset(project,
mqlObject{
name: name, labels: stringLabels,
gcpObject: gcpObject{
project: project,
region: "global", // Not region-based
name: name,
id: id,
service: "compute",
objectType: "image",
},
}, tc))
}
return assets
}
156 changes: 156 additions & 0 deletions motor/discovery/gcp/mql_assets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package gcp

import (
"errors"

"github.com/mitchellh/mapstructure"
"github.com/rs/zerolog/log"
"go.mondoo.com/cnquery"
"go.mondoo.com/cnquery/llx"
"go.mondoo.com/cnquery/motor"
"go.mondoo.com/cnquery/motor/asset"
"go.mondoo.com/cnquery/motor/discovery/common"
"go.mondoo.com/cnquery/motor/platform"
"go.mondoo.com/cnquery/motor/providers"
gcpprovider "go.mondoo.com/cnquery/motor/providers/google"
"go.mondoo.com/cnquery/mql"
"go.mondoo.com/cnquery/resources"
resource_pack "go.mondoo.com/cnquery/resources/packs/gcp"
)

const RegionLabel string = "mondoo.com/region"

type MqlDiscovery struct {
rt *resources.Runtime
}

func NewMQLAssetsDiscovery(provider *gcpprovider.Provider) (*MqlDiscovery, error) {
m, err := motor.New(provider)
if err != nil {
return nil, err
}
rt := resources.NewRuntime(resource_pack.Registry, m)
return &MqlDiscovery{rt: rt}, nil
}

func (md *MqlDiscovery) Close() {
if md.rt != nil && md.rt.Motor != nil {
md.rt.Motor.Close()
}
}

func (md *MqlDiscovery) GetList(query string) []interface{} {
mqlExecutor := mql.New(md.rt, cnquery.DefaultFeatures)
value, err := mqlExecutor.Exec(query, map[string]*llx.Primitive{})
if err != nil {
return nil
}

a := []interface{}{}
d, _ := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
Result: &a,
})
d.Decode(value.Value)
return a
}

func GatherMQLObjects(tc *providers.Config, project string) ([]*asset.Asset, error) {
assets := []*asset.Asset{}
pCfg := tc.Clone()
at, err := gcpprovider.New(pCfg)
if err != nil {
return nil, err
}
m, err := NewMQLAssetsDiscovery(at)
if err != nil {
return nil, err
}
if tc.IncludesOneOfDiscoveryTarget(common.DiscoveryAll, common.DiscoveryAuto, DiscoveryComputeImages) {
assets = append(assets, computeImages(m, project, tc)...)
}

return assets, nil
}

type mqlObject struct {
name string
labels map[string]string
gcpObject gcpObject
}

type gcpObject struct {
project string
region string
id string
service string
objectType string
name string
}

type gcpObjectPlatformInfo struct {
title string
platform string
}

func MondooObjectID(o gcpObject) string {
return "//platformid.api.mondoo.app/runtime/gcp/" + o.service + "/v1/projects/" + o.project + "/regions/" + o.region + "/" + o.objectType + "/" + o.name
}

func MqlObjectToAsset(account string, mqlObject mqlObject, tc *providers.Config) *asset.Asset {
if mqlObject.name == "" {
mqlObject.name = mqlObject.gcpObject.id
}
if err := validate(mqlObject); err != nil {
log.Error().Err(err).Msg("missing values in mql object to asset translation")
return nil
}
info, err := getTitleFamily(mqlObject.gcpObject)
if err != nil {
log.Error().Err(err).Msg("missing runtime info")
return nil
}
platformid := MondooObjectID(mqlObject.gcpObject)
t := tc.Clone()
t.PlatformId = platformid
return &asset.Asset{
PlatformIds: []string{platformid, mqlObject.gcpObject.id},
Name: mqlObject.name,
Platform: &platform.Platform{
Name: info.platform,
Title: info.title,
Kind: providers.Kind_KIND_GCP_OBJECT,
Runtime: providers.RUNTIME_GCP,
},
State: asset.State_STATE_ONLINE,
Labels: addInformationalLabels(mqlObject.labels, mqlObject),
Connections: []*providers.Config{t},
}
}

func validate(m mqlObject) error {
if m.name == "" {
return errors.New("name required for mql gcp object to asset translation")
}
if m.gcpObject.id == "" {
return errors.New("id required for mql gcp object to asset translation")
}
if m.gcpObject.region == "" {
return errors.New("region required for mql gcp object to asset translation")
}
if m.gcpObject.project == "" {
return errors.New("project required for mql gcp object to asset translation")
}
if m.gcpObject.name == "" {
return errors.New("name required for mql gcp object to asset translation")
}
return nil
}

func addInformationalLabels(l map[string]string, o mqlObject) map[string]string {
if l == nil {
l = make(map[string]string)
}
l[RegionLabel] = o.gcpObject.region
l[common.ParentId] = o.gcpObject.project
return l
}
2 changes: 1 addition & 1 deletion motor/discovery/gcp/resolver_gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func (k *GcpResolver) Name() string {
}

func (r *GcpResolver) AvailableDiscoveryTargets() []string {
return []string{common.DiscoveryAuto, common.DiscoveryAll, DiscoveryProjects, DiscoveryInstances}
return []string{common.DiscoveryAuto, common.DiscoveryAll, DiscoveryProjects, DiscoveryInstances, DiscoveryComputeImages}
}

func (r *GcpResolver) Resolve(ctx context.Context, root *asset.Asset, tc *providers.Config, cfn common.CredentialFn, sfn common.QuerySecretFn, userIdDetectors ...providers.PlatformIdDetector) ([]*asset.Asset, error) {
Expand Down
22 changes: 19 additions & 3 deletions motor/discovery/gcp/resolver_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func (k *GcpProjectResolver) Name() string {
}

func (r *GcpProjectResolver) AvailableDiscoveryTargets() []string {
return []string{common.DiscoveryAuto, common.DiscoveryAll, DiscoveryProjects, DiscoveryInstances}
return []string{common.DiscoveryAuto, common.DiscoveryAll, DiscoveryProjects, DiscoveryInstances, DiscoveryComputeImages}
}

func (r *GcpProjectResolver) Resolve(ctx context.Context, tc *providers.Config, cfn common.CredentialFn, sfn common.QuerySecretFn, userIdDetectors ...providers.PlatformIdDetector) ([]*asset.Asset, error) {
Expand Down Expand Up @@ -65,16 +65,32 @@ func (r *GcpProjectResolver) Resolve(ctx context.Context, tc *providers.Config,
}
// ^^

var resolvedRoot *asset.Asset
if tc.IncludesOneOfDiscoveryTarget(common.DiscoveryAuto, common.DiscoveryAll, DiscoveryProjects) {
resolved = append(resolved, &asset.Asset{
resolvedRoot = &asset.Asset{
PlatformIds: []string{identifier},
Name: "GCP project " + project,
Platform: pf,
Connections: []*providers.Config{tc}, // pass-in the current config
Labels: map[string]string{
common.ParentId: project,
},
})
}
resolved = append(resolved, resolvedRoot)
}

if tc.IncludesOneOfDiscoveryTarget(common.DiscoveryAll, common.DiscoveryAuto, DiscoveryComputeImages) {
assetList, err := GatherMQLObjects(tc, project)
if err != nil {
return nil, err
}
for i := range assetList {
a := assetList[i]
if resolvedRoot != nil {
a.RelatedAssets = append(a.RelatedAssets, resolvedRoot)
}
resolved = append(resolved, a)
}
}

// discover compute instances
Expand Down
14 changes: 9 additions & 5 deletions motor/providers/provider.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions motor/providers/provider.proto
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ enum Kind {
KIND_BARE_METAL = 9;
KIND_NETWORK = 10;
KIND_K8S_OBJECT = 11;
KIND_GCP_OBJECT = 13;
}

message Config {
Expand Down

0 comments on commit 1523288

Please sign in to comment.