Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gcs output and cloud function #464

Merged
merged 3 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ DO-NOT-COMMIT-local-setup.yaml
deployment/compose/minio/data
deployment/compose/tempo/data
deployment/compose/loki/data
.env
.env
pod*.yaml
2 changes: 2 additions & 0 deletions actionners/actionners.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
lambdaInvoke "github.com/falco-talon/falco-talon/actionners/aws/lambda"
calicoNetworkpolicy "github.com/falco-talon/falco-talon/actionners/calico/networkpolicy"
ciliumNetworkpolicy "github.com/falco-talon/falco-talon/actionners/cilium/networkpolicy"
gcpFunctionCall "github.com/falco-talon/falco-talon/actionners/gcp/function"
k8sCordon "github.com/falco-talon/falco-talon/actionners/kubernetes/cordon"
k8sDelete "github.com/falco-talon/falco-talon/actionners/kubernetes/delete"
k8sDownload "github.com/falco-talon/falco-talon/actionners/kubernetes/download"
Expand Down Expand Up @@ -81,6 +82,7 @@ func ListDefaultActionners() *Actionners {
k8sDownload.Register(),
k8sTcpdump.Register(),
lambdaInvoke.Register(),
gcpFunctionCall.Register(),
calicoNetworkpolicy.Register(),
ciliumNetworkpolicy.Register(),
)
Expand Down
254 changes: 254 additions & 0 deletions actionners/gcp/function/function.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
package functions

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"time"

"cloud.google.com/go/functions/apiv2/functionspb"
"google.golang.org/api/idtoken"

"github.com/falco-talon/falco-talon/internal/events"
"github.com/falco-talon/falco-talon/internal/gcp/checks"
"github.com/falco-talon/falco-talon/internal/gcp/client"
"github.com/falco-talon/falco-talon/internal/models"
"github.com/falco-talon/falco-talon/internal/rules"
"github.com/falco-talon/falco-talon/utils"
)

const (
Name string = "function"
Category string = "gcp"
Description string = "Invoke a GCP function forwarding the Falco event payload"
Source string = "any"
Continue bool = true
AllowOutput bool = false
RequireOutput bool = false
Permissions string = `{
"roles/cloudfunctions.invoker"
}`
Example string = `- action: Invoke GCP Cloud Function
actionner: gcp:function
parameters:
gcp_function_name: sample-function
gcp_function_location: us-central1
gcp_function_timeout: 10
`
)

var (
RequiredOutputFields = []string{}
)

type Parameters struct {
GCPFunctionName string `mapstructure:"gcp_function_name" validate:"required"`
GCPFunctionLocation string `mapstructure:"gcp_function_location" validate:"required"`
GCPFunctionTimeout int `mapstructure:"gcp_function_timeout"`
IgorEulalio marked this conversation as resolved.
Show resolved Hide resolved
}

type Actionner struct{}

func Register() *Actionner {
return new(Actionner)
}

func (a Actionner) Init() error {
return client.Init()
}

func (a Actionner) Information() models.Information {
return models.Information{
Name: Name,
FullName: Category + ":" + Name,
Category: Category,
Description: Description,
Source: Source,
RequiredOutputFields: RequiredOutputFields,
Permissions: Permissions,
Example: Example,
Continue: Continue,
AllowOutput: AllowOutput,
RequireOutput: RequireOutput,
}
}

func (a Actionner) Parameters() models.Parameters {
return Parameters{
GCPFunctionName: "",
GCPFunctionLocation: "us-central1", // Default location
}
}

func (a Actionner) Checks(_ *events.Event, action *rules.Action) error {
var parameters Parameters
err := utils.DecodeParams(action.GetParameters(), &parameters)
if err != nil {
return err
}

return checks.CheckFunctionExist{}.Run(parameters.GCPFunctionName, parameters.GCPFunctionLocation)
}

func (a Actionner) Run(event *events.Event, action *rules.Action) (utils.LogLine, *models.Data, error) {
gcpClient, err := client.GetGCPClient()
if err != nil {
return utils.LogLine{
Objects: nil,
Error: err.Error(),
Status: utils.FailureStr,
}, nil, err
}
return a.RunWithClient(gcpClient, event, action)
}

func (a Actionner) CheckParameters(action *rules.Action) error {
var parameters Parameters
err := utils.DecodeParams(action.GetParameters(), &parameters)
if err != nil {
return err
}

err = utils.ValidateStruct(parameters)
if err != nil {
return err
}
return nil
}

func (a Actionner) RunWithClient(c client.GCPClientAPI, event *events.Event, action *rules.Action) (utils.LogLine, *models.Data, error) {
var parameters Parameters
err := utils.DecodeParams(action.GetParameters(), &parameters)
if err != nil {
return utils.LogLine{
Objects: nil,
Issif marked this conversation as resolved.
Show resolved Hide resolved
Error: err.Error(),
Status: utils.FailureStr,
}, nil, err
}

objects := map[string]string{
"name": parameters.GCPFunctionName,
"location": parameters.GCPFunctionLocation,
}

functionName := fmt.Sprintf("projects/%s/locations/%s/functions/%s", c.ProjectID(), parameters.GCPFunctionLocation, parameters.GCPFunctionName)

getFunctionReq := &functionspb.GetFunctionRequest{
Name: functionName,
}

gcpFunctionClient, err := c.GetGcpFunctionClient(context.Background())
if err != nil {
return utils.LogLine{
Objects: objects,
Error: err.Error(),
Status: utils.FailureStr,
}, nil, err
}

ctx := context.Background()

function, err := gcpFunctionClient.GetFunction(ctx, getFunctionReq)
if err != nil {
return utils.LogLine{
Objects: objects,
Error: fmt.Sprintf("failed to get function: %v", err),
Status: utils.FailureStr,
}, nil, err
}

if function.ServiceConfig.Uri == "" {
return utils.LogLine{
Objects: objects,
Error: "function does not have a valid URL",
Status: utils.FailureStr,
}, nil, fmt.Errorf("function does not have a valid URL")
}

functionURL := function.ServiceConfig.Uri

payload, err := json.Marshal(event)
if err != nil {
return utils.LogLine{
Objects: objects,
Error: err.Error(),
Status: utils.FailureStr,
}, nil, err
}

tokenSource, err := idtoken.NewTokenSource(ctx, functionURL)
if err != nil {
return utils.LogLine{
Objects: objects,
Error: fmt.Sprintf("failed to create ID token source: %v", err),
Status: utils.FailureStr,
}, nil, err
}
token, err := tokenSource.Token()
if err != nil {
return utils.LogLine{
Objects: objects,
Error: fmt.Sprintf("failed to obtain ID token: %v", err),
Status: utils.FailureStr,
}, nil, err
}

req, err := http.NewRequestWithContext(ctx, "POST", functionURL, bytes.NewReader(payload))
if err != nil {
return utils.LogLine{
Objects: objects,
Error: fmt.Sprintf("failed to create HTTP request: %v", err),
Status: utils.FailureStr,
}, nil, err
}

req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token.AccessToken)

if parameters.GCPFunctionTimeout > 0 {
httpClient := http.Client{
Timeout: time.Duration(parameters.GCPFunctionTimeout),
}
c.SetHTTPClient(&httpClient)
}

resp, err := c.HTTPClient().Do(req)
if err != nil {
return utils.LogLine{
Objects: objects,
Error: fmt.Sprintf("failed to invoke function: %v", err),
Status: utils.FailureStr,
}, nil, err
}
defer resp.Body.Close()

respBody, err := io.ReadAll(resp.Body)
if err != nil {
return utils.LogLine{
Objects: objects,
Error: fmt.Sprintf("failed to read response body: %v", err),
Status: utils.FailureStr,
}, nil, err
}

if resp.StatusCode != http.StatusOK {
return utils.LogLine{
Objects: objects,
Error: fmt.Sprintf("function invocation failed with status %d: %s", resp.StatusCode, string(respBody)),
Status: utils.FailureStr,
}, nil, fmt.Errorf("function invocation failed with status %d: %s", resp.StatusCode, string(respBody))
}

objects["function_response"] = string(respBody)
objects["function_response_status"] = strconv.Itoa(resp.StatusCode)

return utils.LogLine{
Objects: objects,
Status: utils.SuccessStr,
}, nil, nil
}
32 changes: 19 additions & 13 deletions configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,20 @@ type Otel struct {
}

type Configuration struct {
Notifiers map[string]map[string]any `mapstructure:"notifiers"`
AwsConfig AwsConfig `mapstructure:"aws"`
LogFormat string `mapstructure:"log_format"`
KubeConfig string `mapstructure:"kubeconfig"`
ListenAddress string `mapstructure:"listen_address"`
MinioConfig MinioConfig `mapstructure:"minio"`
RulesFiles []string `mapstructure:"rules_files"`
DefaultNotifiers []string `mapstructure:"default_notifiers"`
Otel Otel `mapstructure:"otel"`
Deduplication deduplication `mapstructure:"deduplication"`
ListenPort int `mapstructure:"listen_port"`
WatchRules bool `mapstructure:"watch_rules"`
PrintAllEvents bool `mapstructure:"print_all_events"`
Notifiers map[string]map[string]interface{} `mapstructure:"notifiers"`
AwsConfig AwsConfig `mapstructure:"aws"`
GcpConfig GcpConfig `mapstructure:"gcp"`
LogFormat string `mapstructure:"log_format"`
KubeConfig string `mapstructure:"kubeconfig"`
ListenAddress string `mapstructure:"listen_address"`
MinioConfig MinioConfig `mapstructure:"minio"`
RulesFiles []string `mapstructure:"rules_files"`
DefaultNotifiers []string `mapstructure:"default_notifiers"`
Otel Otel `mapstructure:"otel"`
Deduplication deduplication `mapstructure:"deduplication"`
ListenPort int `mapstructure:"listen_port"`
WatchRules bool `mapstructure:"watch_rules"`
PrintAllEvents bool `mapstructure:"print_all_events"`
}

type deduplication struct {
Expand All @@ -63,6 +64,11 @@ type AwsConfig struct {
ExternalID string `mapstructure:"external_id"`
}

type GcpConfig struct {
Region string `mapstructure:"region"`
CredentialsPath string `mapstructure:"credentials_path"`
}

type MinioConfig struct {
Endpoint string `mapstructure:"endpoint"`
AccessKey string `mapstructure:"access_key"`
Expand Down
17 changes: 17 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ go 1.22.0
toolchain go1.22.2

require (
cloud.google.com/go/functions v1.19.1
cloud.google.com/go/iam v1.2.1
cloud.google.com/go/storage v1.43.0
github.com/aws/aws-sdk-go-v2 v1.31.0
github.com/aws/aws-sdk-go-v2/config v1.27.39
github.com/aws/aws-sdk-go-v2/credentials v1.17.37
Expand All @@ -29,6 +32,7 @@ require (
github.com/rs/zerolog v1.33.0
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0
go.opentelemetry.io/otel v1.30.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.30.0
Expand All @@ -39,6 +43,7 @@ require (
go.opentelemetry.io/otel/sdk/metric v1.30.0
go.opentelemetry.io/otel/trace v1.30.0
golang.org/x/text v0.18.0
google.golang.org/api v0.196.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.31.1
Expand All @@ -49,6 +54,11 @@ require (
)

require (
cloud.google.com/go v0.115.1 // indirect
cloud.google.com/go/auth v0.9.3 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
cloud.google.com/go/compute/metadata v0.5.0 // indirect
cloud.google.com/go/longrunning v0.6.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect
Expand Down Expand Up @@ -98,6 +108,9 @@ require (
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/s2a-go v0.1.8 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.3 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
Expand Down Expand Up @@ -145,11 +158,14 @@ require (
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.6-0.20210604193023-d5e0c0615ace // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/vishvananda/netlink v1.2.1-beta.2.0.20240524165444-4d4ba1473f21 // indirect
github.com/vishvananda/netns v0.0.4 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.mongodb.org/mongo-driver v1.15.1 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/dig v1.17.1 // indirect
Expand All @@ -163,6 +179,7 @@ require (
golang.org/x/sys v0.25.0 // indirect
golang.org/x/term v0.24.0 // indirect
golang.org/x/time v0.6.0 // indirect
google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
google.golang.org/grpc v1.66.1 // indirect
Expand Down
Loading
Loading