-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8470d2c
commit 39bf423
Showing
14 changed files
with
620 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* @albertollamaso |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
All Submissions: | ||
|
||
* [ ] Have you followed the guidelines in our Contributing document? | ||
* [ ] Have you checked to ensure there aren't other open [Pull Requests](../../../pulls) for the same update/change? | ||
|
||
### New Feature Submissions: | ||
|
||
1. [ ] Does your submission pass tests? | ||
2. [ ] Have you lint your code locally before submission? | ||
|
||
### Changes to Core Features: | ||
|
||
* [ ] Have you added an explanation of what your changes do and why you'd like us to include them? | ||
* [ ] Have you written new tests for your core changes, as applicable? | ||
* [ ] Have you successfully run tests with your changes locally? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Binaries for programs and plugins | ||
*.exe | ||
*.exe~ | ||
*.dll | ||
*.so | ||
*.dylib | ||
|
||
# Test binary, built with `go test -c` | ||
*.test | ||
|
||
# Output of the go coverage tool, specifically when used with LiteIDE | ||
*.out | ||
|
||
# Dependency directories (remove the comment below to include it) | ||
# vendor/ | ||
|
||
# Go workspace file | ||
go.work | ||
|
||
# Falco config file | ||
config/ | ||
|
||
# Plugin binary | ||
libnomad.so | ||
|
||
# IDE | ||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
SHELL=/bin/bash -o pipefail | ||
GO ?= go | ||
|
||
NAME := nomad | ||
OUTPUT := lib$(NAME).so | ||
|
||
ifeq ($(DEBUG), 1) | ||
GODEBUGFLAGS= GODEBUG=cgocheck=2 | ||
else | ||
GODEBUGFLAGS= GODEBUG=cgocheck=0 | ||
endif | ||
|
||
all: $(OUTPUT) | ||
|
||
clean: | ||
@rm -f $(OUTPUT) | ||
|
||
$(OUTPUT): | ||
@$(GODEBUGFLAGS) $(GO) build -buildmode=c-shared -o $(OUTPUT) ./plugin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# Falcosecurity Nomad Plugin | ||
|
||
This repositry contains the Nomad plugin, which can fetch event stream containing [nomad](https://www.nomadproject.io/) events, parse the events, and emit sinsp/scap events (e.g. the events used by Falco) for each nomad event. | ||
|
||
## Event Source | ||
The event source for nomad events is the `/event/stream` endpoint used to stream events generated by Nomad. | ||
|
||
## Supported Fields | ||
Here is the current set of supported fields: | ||
|
||
|
||
| NAME | TYPE | ARG | DESCRIPTION | | ||
|-----------------------------------|----------|------|------------------------------------------------------------------------------------------------------------------------------------------------------| | ||
| `nomad.index` | `uint64` | None | The index of the nomad event. | | ||
| `nomad.alloc.name` | `string` | None | The name of the nomad allocation. | | ||
| `nomad.alloc.namespace` | `string` | None | The namespace of the allocation. | | ||
| `nomad.alloc.jobID` | `string` | None | The job ID of the allocation. | | ||
| `nomad.alloc.clientStatus` | `string` | None | The client status of the allocation. | | ||
| `nomad.alloc.images` | `string (list)` | None | The list of container images on allocations. | | ||
| `nomad.alloc.images.tags` | `string (list)` | None | The tags of each container image on allocations. | | ||
| `nomad.alloc.images.repositories` | `string (list)` | None | The container repositories used on allocations container images. | | ||
| `nomad.alloc.taskStates.type` | `string (list)` | None | The state of the task on the allocations. | | ||
| `nomad.alloc.res.cpu` | `uint64` | None | The CPU required to run this allocation in MHz. | | ||
| `nomad.alloc.res.cores` | `uint64` | None | The number of CPU cores to reserve for the allocation. | | ||
| `nomad.alloc.res.diskMB` | `uint64` | None | the amount of disk required for the allocation. | | ||
| `nomad.alloc.res.iops` | `uint64` | None | the number of iops required for the allocation. | | ||
| `nomad.alloc.res.memoryMB` | `uint64` | None | The memory required in MB for the allocation. | | ||
| `nomad.alloc.res.memoryMaxMB` | `uint64` | None | The maximum memory the allocation may use. | | ||
| `nomad.event.topic` | `string` | None | The topic of the nomad event. | | ||
| `nomad.event.type` | `string` | None | The type of the nomad event. | | ||
## Configuration | ||
|
||
|
||
### `falco.yaml` Example | ||
|
||
```yaml | ||
plugins: | ||
- name: nomad | ||
library_path: libnomad.so | ||
init_config: | ||
address: http://127.0.0.1:4646 | ||
token: "" | ||
namespace: "*" | ||
|
||
# Optional. If not specified the first entry in plugins is used. | ||
load_plugins: [nomad, json] | ||
``` | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package nomad | ||
|
||
// Defining a type for the plugin configuration. | ||
// In this simple example, users can define the starting value the event | ||
// counter. the `jsonschema` tags is used to automatically generate a | ||
// JSON Schema definition, so that the framework can perform automatic | ||
// validations. | ||
type PluginConfig struct { | ||
Address string `json:"address" jsonschema:"title=Nomad address,description=The address of the Nomad server.,default=http://localhost:4646"` | ||
Token string `json:"token" jsonschema:"title=Nomad token,description=The token to use to connect to the Nomad server.,default="` | ||
Namespace string `json:"namespace" jsonschema:"title=Nomad namespace,description=The namespace to use to connect to the Nomad server.,default=*"` | ||
} | ||
|
||
// Resets sets the configuration to its default values | ||
func (p *PluginConfig) Reset() { | ||
p.Address = "http://localhost:4646" | ||
p.Token = "" | ||
p.Namespace = "*" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package nomad | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"strconv" | ||
|
||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk" | ||
"github.com/hashicorp/nomad/api" | ||
) | ||
|
||
// This method is mandatory the field extraction capability. | ||
// If the Extract method is defined, the framework expects an Fields method | ||
// to be specified too. | ||
func (p *Plugin) Extract(req sdk.ExtractRequest, evt sdk.EventReader) error { | ||
var event api.Event | ||
encoder := json.NewDecoder(evt.Reader()) | ||
if err := encoder.Decode(&event); err != nil { | ||
return err | ||
} | ||
|
||
switch req.Field() { | ||
case "nomad.index": | ||
req.SetValue(event.Index) | ||
case "nomad.alloc.name": | ||
alloc, err := event.Allocation() | ||
if err == nil { | ||
req.SetValue(string(alloc.Name)) | ||
} | ||
case "nomad.alloc.namespace": | ||
alloc, err := event.Allocation() | ||
if err == nil { | ||
req.SetValue(string(alloc.Namespace)) | ||
} | ||
case "nomad.alloc.jobID": | ||
alloc, err := event.Allocation() | ||
if err == nil { | ||
req.SetValue(string(alloc.JobID)) | ||
} | ||
case "nomad.alloc.clientStatus": | ||
alloc, err := event.Allocation() | ||
if err == nil { | ||
req.SetValue(string(alloc.ClientStatus)) | ||
} | ||
case "nomad.alloc.taskStates.type": | ||
var driver []string | ||
alloc, err := event.Allocation() | ||
if err == nil { | ||
for _, task := range alloc.TaskStates { | ||
for _, taskEvent := range task.Events { | ||
if taskEvent.Type == "Driver" { | ||
driver = append(driver, taskEvent.Type) | ||
} | ||
} | ||
} | ||
} | ||
req.SetValue(driver) | ||
case "nomad.alloc.res.cpu": | ||
alloc, err := event.Allocation() | ||
if err == nil { | ||
req.SetValue(uint64(*alloc.Resources.CPU)) | ||
} | ||
case "nomad.alloc.res.cores": | ||
alloc, err := event.Allocation() | ||
if err == nil { | ||
req.SetValue(uint64(*alloc.Resources.Cores)) | ||
} | ||
case "nomad.alloc.res.diskMB": | ||
alloc := event.Payload["Allocation"] | ||
valStr := fmt.Sprintf("%v", alloc.(map[string]interface{})["Resources"].(map[string]interface{})["DiskMB"]) // bug in nomad api. event.Allocation() returns <nil> for alloc.Resources.DiskMB | ||
value, _ := strconv.ParseUint(valStr, 10, 64) | ||
req.SetValue(value) | ||
case "nomad.alloc.res.iops": | ||
alloc, err := event.Allocation() | ||
if err == nil { | ||
req.SetValue(uint64(*alloc.Resources.IOPS)) | ||
} | ||
case "nomad.alloc.res.memoryMB": | ||
alloc := event.Payload["Allocation"] | ||
valStr := fmt.Sprintf("%v", alloc.(map[string]interface{})["Resources"].(map[string]interface{})["MemoryMB"]) // bug in nomad api. event.Allocation() returns <nil> for alloc.Resources.MemoryMB | ||
value, _ := strconv.ParseUint(valStr, 10, 64) | ||
req.SetValue(value) | ||
case "nomad.alloc.res.memoryMaxMB": | ||
alloc := event.Payload["Allocation"] | ||
valStr := fmt.Sprintf("%v", alloc.(map[string]interface{})["Resources"].(map[string]interface{})["MemoryMaxMB"]) // bug in nomad api. event.Allocation() returns <nil> for alloc.Resources.MemoryMaxMB | ||
value, _ := strconv.ParseUint(valStr, 10, 64) | ||
req.SetValue(value) | ||
case "nomad.alloc.images": | ||
images, err := getAllocImages(&event) | ||
if err == nil { | ||
req.SetValue(images) | ||
} | ||
case "nomad.alloc.images.tags": | ||
tags, err := getAllocTags(&event) | ||
if err == nil { | ||
req.SetValue(tags) | ||
} | ||
case "nomad.alloc.images.repositories": | ||
repos, err := getAllocRepos(&event) | ||
if err == nil { | ||
req.SetValue(repos) | ||
} | ||
case "nomad.event.topic": | ||
req.SetValue(string(event.Topic)) | ||
case "nomad.event.type": | ||
req.SetValue(event.Type) | ||
default: | ||
return fmt.Errorf("unsupported field: %s", req.Field()) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package nomad | ||
|
||
import ( | ||
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk" | ||
) | ||
|
||
// Fields return the list of extractor fields exported by this plugin. | ||
// This method is mandatory the field extraction capability. | ||
// If the Fields method is defined, the framework expects an Extract method | ||
// to be specified too. | ||
func (p *Plugin) Fields() []sdk.FieldEntry { | ||
return []sdk.FieldEntry{ | ||
{Type: "uint64", Name: "nomad.index", Display: "Event index", Desc: "the index of the nomad event."}, | ||
{Type: "string", Name: "nomad.alloc.name", Display: "Allocation name", Desc: "the name of the nomad allocation."}, | ||
{Type: "string", Name: "nomad.alloc.namespace", Display: "Allocation namespace", Desc: "the namespace of the allocation."}, | ||
{Type: "string", Name: "nomad.alloc.jobID", Display: "Allocation Job ID", Desc: "the job ID of the allocation."}, | ||
{Type: "string", Name: "nomad.alloc.clientStatus", Display: "Allocation client status", Desc: "the client status of the allocation."}, | ||
{Type: "string", Name: "nomad.alloc.images", Display: "Allocation container images", Desc: "the list of container images on allocations.", IsList: true}, | ||
{Type: "string", Name: "nomad.alloc.images.tags", Display: "Allocation container tags", Desc: "the tags of each container image on allocations.", IsList: true}, | ||
{Type: "string", Name: "nomad.alloc.images.repositories", Display: "Allocation container repositories", Desc: "the container repositories used on allocations container images.", IsList: true}, | ||
{Type: "string", Name: "nomad.alloc.taskStates.type", Display: "Allocation Task State", Desc: "the state of the task on the allocations.", IsList: true}, | ||
{Type: "uint64", Name: "nomad.alloc.res.cpu", Display: "Allocation CPU Resources", Desc: "the CPU required to run this allocation in MHz."}, | ||
{Type: "uint64", Name: "nomad.alloc.res.cores", Display: "Allocation CPU Cores Resources", Desc: "the number of CPU cores to reserve for the allocation."}, | ||
{Type: "uint64", Name: "nomad.alloc.res.diskMB", Display: "Allocation Disk in MB Resources", Desc: "the amount of disk required for the allocation."}, | ||
{Type: "uint64", Name: "nomad.alloc.res.iops", Display: "Allocation IOPS Resources", Desc: "the number of iops required for the allocation."}, | ||
{Type: "uint64", Name: "nomad.alloc.res.memoryMB", Display: "Allocation Memory in MB Resources", Desc: "the memory required in MB for the allocation."}, | ||
{Type: "uint64", Name: "nomad.alloc.res.memoryMaxMB", Display: "Allocation Max Memory in MB Resources", Desc: "the maximum memory the allocation may use."}, | ||
{Type: "string", Name: "nomad.event.topic", Display: "Nomad Event Topic", Desc: "the topic of the nomad event."}, | ||
{Type: "string", Name: "nomad.event.type", Display: "Nomad Event type", Desc: "the type of the nomad event."}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package nomad | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/hashicorp/nomad/api" | ||
) | ||
|
||
func getAllocImages(evt *api.Event) ([]string, error) { | ||
alloc, err := evt.Allocation() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var images []string | ||
for _, task := range alloc.TaskStates { | ||
for _, taskEvent := range task.Events { | ||
image := taskEvent.Details["image"] | ||
fmt.Println("------------------") | ||
fmt.Println(image) | ||
fmt.Println("------------------") | ||
tokens := strings.Split(image, ":") | ||
if strings.Contains(tokens[0], "/") { | ||
// cases | ||
// [registry]/[repository_name]/[repo_path_component]/[image]:[tag] (repository name could have two or more path components, they must be separated by a forward slash (“/”).) | ||
// [registry]/[repository_name]/[image]:[tag] | ||
// [registry]/[image]:[tag] | ||
tokens = strings.Split(tokens[0], "/") | ||
images = append(images, tokens[len(tokens)-1]) | ||
} else { | ||
// case [image]:[tag] | ||
images = append(images, tokens[0]) | ||
} | ||
} | ||
} | ||
|
||
return images, nil | ||
} | ||
|
||
func getAllocTags(evt *api.Event) ([]string, error) { | ||
alloc, err := evt.Allocation() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var tags []string | ||
for _, task := range alloc.TaskStates { | ||
for _, taskEvent := range task.Events { | ||
tokens := strings.Split(taskEvent.Details["image"], ":") | ||
tags = append(tags, tokens[len(tokens)-1]) | ||
} | ||
} | ||
|
||
return tags, nil | ||
} | ||
|
||
func getAllocRepos(evt *api.Event) ([]string, error) { | ||
alloc, err := evt.Allocation() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var repos []string | ||
for _, task := range alloc.TaskStates { | ||
for _, taskEvent := range task.Events { | ||
tokens := strings.Split(taskEvent.Details["image"], ":") | ||
if len(tokens) == 2 { | ||
if strings.Contains(tokens[0], "/") { | ||
// cases | ||
// [registry]/[repository_name]/[repo_path_component]/[image]:[tag] (repository name could have two or more path components, they must be separated by a forward slash (“/”).) | ||
// [registry]/[repository_name]/[image]:[tag] | ||
// [registry]/[image]:[tag] | ||
tokens = strings.Split(tokens[0], "/") | ||
tokens = tokens[:len(tokens)-1] | ||
tokenStr := strings.Join(tokens, "/") | ||
repos = append(repos, tokenStr) | ||
} else { | ||
// case [image]:[tag] | ||
repos = append(repos, "docker.io") | ||
} | ||
} | ||
} | ||
} | ||
|
||
return repos, nil | ||
} |
Oops, something went wrong.