Skip to content

Commit

Permalink
feat(compute): Add instance checks GCS_VM_ (#11)
Browse files Browse the repository at this point in the history
* feat(instance): GCP_VM_001: check instance does not have public IP

* feat(instance): GCP_VM_002: check disk are encrypted customer-managed

* chore(deps): run go mod tidy
  • Loading branch information
corrieriluca committed Apr 24, 2023
1 parent ff84bca commit 4da74a7
Show file tree
Hide file tree
Showing 9 changed files with 440 additions and 3 deletions.
18 changes: 18 additions & 0 deletions .yatas.example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
plugins:
- name: "gcp"
enabled: true
source: "github.com/padok-team/yatas-gcp"
version: "latest"
description: "Check for GCP good practices"

pluginsConfiguration:
- pluginName: "gcp"
accounts:
- project: "project-1"
computeRegions:
- europe-west1
- europe-west2
- europe-west3
- project: "project-2"
computeRegions:
- us-east1
108 changes: 108 additions & 0 deletions gcp/instance/getter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package instance

import (
"context"
"strings"

compute "cloud.google.com/go/compute/apiv1"
"cloud.google.com/go/compute/apiv1/computepb"
"github.com/padok-team/yatas-gcp/internal"
"github.com/padok-team/yatas-gcp/logger"
"google.golang.org/api/iterator"
)

// Get the compute zones based on the list of compute regions provided in the config
func GetComputeZones(account internal.GCPAccount) []string {
ctx := context.Background()
client, err := compute.NewZonesRESTClient(ctx)
if err != nil {
logger.Logger.Error("Failed to create Compute Zones client", "error", err)
}
defer client.Close()

req := &computepb.ListZonesRequest{
Project: account.Project,
}
var zones []string
it := client.List(ctx, req)
for {
resp, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
logger.Logger.Error("Failed to list Compute Zones", "error", err.Error())
break
}

region := resp.GetRegion()
for _, r := range account.ComputeRegions {
if strings.HasSuffix(region, r) {
zones = append(zones, resp.GetName())
}
}
}

logger.Logger.Debug("Compute Zones", "zones", zones)

return zones
}

// Get all the VM instances of the account for the given compute zone
func GetInstances(account internal.GCPAccount, computeZone string) []computepb.Instance {
ctx := context.Background()
client, err := compute.NewInstancesRESTClient(ctx)
if err != nil {
logger.Logger.Error("Failed to create Instance client", "error", err)
}
defer client.Close()

req := &computepb.ListInstancesRequest{
Project: account.Project,
Zone: computeZone,
}
var instances []computepb.Instance
it := client.List(ctx, req)
for {
resp, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
logger.Logger.Error("Failed to list VM Instances", "error", err.Error())
break
}
instances = append(instances, *resp)
}

return instances
}

func GetDisks(account internal.GCPAccount, computeZone string) []computepb.Disk {
ctx := context.Background()
client, err := compute.NewDisksRESTClient(ctx)
if err != nil {
logger.Logger.Error("Failed to create Disk client", "error", err)
}
defer client.Close()

req := &computepb.ListDisksRequest{
Project: account.Project,
Zone: computeZone,
}
var disks []computepb.Disk
it := client.List(ctx, req)
for {
resp, err := it.Next()
if err == iterator.Done {
break
}
if err != nil {
logger.Logger.Error("Failed to list Disks", "error", err.Error())
break
}
disks = append(disks, *resp)
}

return disks
}
78 changes: 78 additions & 0 deletions gcp/instance/instance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package instance

import (
"sync"

"cloud.google.com/go/compute/apiv1/computepb"
"github.com/padok-team/yatas-gcp/internal"
"github.com/padok-team/yatas/plugins/commons"
)

func RunChecks(wa *sync.WaitGroup, account internal.GCPAccount, c *commons.Config, queue chan []commons.Check) {
var checkConfig commons.CheckConfig
checkConfig.Init(c)
var checks []commons.Check

computeZones := GetComputeZones(account)

// Get all the instances in all the compute zones specified
var instances []computepb.Instance
for _, zone := range computeZones {
instances = append(instances, GetInstances(account, zone)...)
}

// Get all the disks in all the compute zones specified
var disks []computepb.Disk
for _, zone := range computeZones {
disks = append(disks, GetDisks(account, zone)...)
}

instanceChecks := []commons.CheckDefinition{
{
Title: "GCP_VM_001",
Description: "Check if VM instance is not using a public IP address",
Categories: []string{"Security", "Good Practice"},
ConditionFn: InstanceNoPublicIPAttached,
SuccessMessage: "VM instance is not using a public IP address",
FailureMessage: "VM instance is using a public IP address",
},
}

diskChecks := []commons.CheckDefinition{
{
Title: "GCP_VM_002",
Description: "Check if VM Disk is encrypted with a customer-managed key",
Categories: []string{"Security", "Good Practice"},
ConditionFn: DiskIsCustomerEncrypted,
SuccessMessage: "VM Disk is encrypted with a customer-managed key",
FailureMessage: "VM Disk is not encrypted with a customer-managed key",
},
}

var resources []commons.Resource
for _, instance := range instances {
resources = append(resources, &VMInstance{Instance: instance})
}
var diskResources []commons.Resource
for _, disk := range disks {
diskResources = append(diskResources, &VMDisk{Disk: disk})
}

commons.AddChecks(&checkConfig, instanceChecks)
commons.AddChecks(&checkConfig, diskChecks)
go commons.CheckResources(checkConfig, resources, instanceChecks)
go commons.CheckResources(checkConfig, diskResources, diskChecks)

go func() {
for t := range checkConfig.Queue {
t.EndCheck()
checks = append(checks, t)

checkConfig.Wg.Done()
}
}()

checkConfig.Wg.Wait()

queue <- checks
}
37 changes: 37 additions & 0 deletions gcp/instance/instanceConditions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package instance

import (
"github.com/padok-team/yatas/plugins/commons"
)

func InstanceNoPublicIPAttached(resource commons.Resource) bool {
instance, ok := resource.(*VMInstance)
if !ok {
return false
}

if instance.Instance.NetworkInterfaces != nil {
for _, networkInterface := range instance.Instance.NetworkInterfaces {
if networkInterface.AccessConfigs != nil {
for _, accessConfig := range networkInterface.AccessConfigs {
// If there is an external IPv4 or IPv6 address, return false
if accessConfig.NatIP != nil && *accessConfig.NatIP != "" {
return false
}
if accessConfig.ExternalIpv6 != nil {
return false
}
}
}
}
}
return true
}

func DiskIsCustomerEncrypted(resource commons.Resource) bool {
disk, ok := resource.(*VMDisk)
if !ok {
return false
}
return disk.Disk.DiskEncryptionKey != nil
}
Loading

0 comments on commit 4da74a7

Please sign in to comment.