Smithy's SDK.
The component package can be used to write Smithy components that represents Vulnerability Findings data in OCSF format.
OCSF is a standard for representing vulnerability reports that can be understood by a variety of security tools.
This package allows you to focus on writing the business logic for your component while taking care of the boring things for you:
- running the components steps in a predictable and reliable way
- deal with persisting and updating findings data in an underlying storage
- handle intricacies like cancellations and graceful shutdown
- taking care of logging and panic handling
- reporting common metrics to track what your component is doing
You can customise a component using the following environment variables:
Environment Variable | Type | Required | Default | Possible Values |
---|---|---|---|---|
SMITHY_COMPONENT_NAME | string | yes | - | - |
SMITHY_LOG_LEVEL | string | false | info, debug, warn, error | |
SMITHY_STORE_TYPE | string | no | sqlite | sqlite, postgresql, findings-client |
Runners
can be supplied with RunnerConfigOption
s to customise how a component runs.
In the following example you can see how we change the component name:
component.RunTarget(
ctx,
sampleTarget{},
component.RunnerWithComponentName("sample-target"),
)
For local development, a default SQLite
Backend Store Type will be used. This can be customised with:
component.RunTarget(
ctx,
sampleTarget{},
component.RunnerWithStorer(//a storer),
)
A Target
component should be used to prepare a target for scanning.
For example, cloning a repository and make it available for a
Scanner
to scan.
A git-clone
component is an example of a Target
.
You can create a new Target
component like follows:
package main
import (
"context"
"log"
"time"
"github.com/smithy-security/smithy/sdk/component"
)
type sampleTarget struct{}
func (s sampleTarget) Prepare(ctx context.Context) error {
// Prepare the target here!
// This is the main execution method of the Target component type.
// Here you need to implement your logic.
// For example for a target component that clones a repository here is where you
// setup your arguments and call git clone.
return nil
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
if err := component.RunTarget(ctx, sampleTarget{}); err != nil {
log.Fatalf("unexpected run error: %v", err)
}
}
A Scanner
scans a Target
to find vulnerabilities.
Go-Sec
component is an example of a scanner component.
You can create a new Scanner
component like follows:
package main
import (
"context"
"log"
"time"
"github.com/smithy-security/smithy/sdk/component"
ocsf "github.com/smithy-security/smithy/sdk/gen/com/github/ocsf/ocsf_schema/v1"
)
type sampleScanner struct{}
func (s sampleScanner) Transform(ctx context.Context) ([]*ocsf.VulnerabilityFinding, error) {
// Transform your payload to ocsf format here!
// Read raw findings prepared by a Target and transform them to a format that makes sense!
return make([]*ocsf.VulnerabilityFinding, 0, 10), nil
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
if err := component.RunScanner(ctx, sampleScanner{}); err != nil {
log.Fatalf("unexpected run error: %v", err)
}
}
An Enricher
annotates vulnerability findings with extra information.
Deduplication
component is an example Enricher
.
You can create a new Enricher
component like follows:
package main
import (
"context"
"log"
"time"
"github.com/smithy-security/smithy/sdk/component"
ocsf "github.com/smithy-security/smithy/sdk/gen/com/github/ocsf/ocsf_schema/v1"
)
type sampleEnricher struct{}
func (s sampleEnricher) Annotate(ctx context.Context, findings []*ocsf.VulnerabilityFinding) ([]*ocsf.VulnerabilityFinding, error) {
// Enrich your vulnerability findings here!
// Make sense of you vulnerability data and add enriching annotations to take smarter decisions.
return make([]*ocsf.VulnerabilityFinding, 0, 10), nil
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
if err := component.RunEnricher(ctx, sampleEnricher{}); err != nil {
log.Fatalf("unexpected run error: %v", err)
}
}
A Filter
component allows to filter out some vulnerability
findings based on arbitrary criteria.
For example, you might want to filter out vulnerabilities on a specific path in a repository.
You can create a new Filter
component like follows:
package main
import (
"context"
"log"
"time"
"github.com/smithy-security/smithy/sdk/component"
ocsf "github.com/smithy-security/smithy/sdk/gen/com/github/ocsf/ocsf_schema/v1"
)
type sampleFilter struct{}
func (s sampleFilter) Filter(ctx context.Context, findings []*ocsf.VulnerabilityFinding) ([]*ocsf.VulnerabilityFinding, bool, error) {
// Filter out your vulnerability findings here!
// Remove the noise from your pipeline and ignore findings based on a supplied criteria.
return make([]*ocsf.VulnerabilityFinding, 0, 80), true, nil
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
if err := component.RunFilter(ctx, sampleFilter{}); err != nil {
log.Fatalf("unexpected run error: %v", err)
}
}
A Reporter
component allows you to report vulnerabilities on
your favourite destination.
For example, report each one of them as ticket on a ticketing
system or dump them into a data lake. Slack
is an
example Reporter
.
You can create a new Reporter
component like follows:
package main
import (
"context"
"log"
"time"
"github.com/smithy-security/smithy/sdk/component"
ocsf "github.com/smithy-security/smithy/sdk/gen/com/github/ocsf/ocsf_schema/v1"
)
type sampleReporter struct{}
func (s sampleReporter) Report(ctx context.Context, findings []*ocsf.VulnerabilityFinding) error {
// Report your vulnerability findings here.
// Raise them as tickets on Jira or post a message on Slack here!
return nil
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
if err := component.RunReporter(ctx, sampleReporter{}); err != nil {
log.Fatalf("unexpected run error: %v", err)
}
}
component
makes it easy for you to leverage the default logger
in your business logic.
You can access the logger anytime using component.LoggerFromContext(ctx)
.
For example:
func (s sampleEnricher) Update(ctx context.Context, findings []*ocsf.VulnerabilityFinding) error {
component.LoggerFromContext(ctx).Info("Preparing to update findings")
return nil
}
You can also customise the logger if you wish:
type noopLogger struct {}
func (n *noopLogger) Debug(msg string, keyvals ...any) {}
func (n *noopLogger) Info(msg string, keyvals ...any) {}
func (n *noopLogger) Warn(msg string, keyvals ...any) {}
func (n *noopLogger) Error(msg string, keyvals ...any) {}
func (n *noopLogger) With(args ...any) Logger {
return &noopLogger{}
}
...
logger := noopLogger{}
if err := component.RunReporter(
ctx,
sampleReporter{},
component.RunnerWithLogger(logger),
); err != nil {
log.Fatalf("unexpected run error: %v", err)
}
Smithy SDK allows to configure different storages to boost adoption.
By default, sqlite is used for local development.
You can configure a Postgresql storage by plugging in
/store/remote/postgresql
or configuring the required
environment variables defined in its README.
You can configure a grpc findings client storage by plugging in
/store/remote/findings-client
or configuring the required
environment variables defined in its README.
You can supply your own implementation of a storage by satisfying
the componenent.Storer
interface and leveraging the
RunnerWithStorer
option.
They are generated using sqlc from real SQL schemas and queries.
You can generate types mapping schemas and queries by leveraging the go:generate
entries in tools.go
.
Components require a common database/tables setup to function properly.
This is achieved with migrations with atlas.
Atlas uses a atlas.sum
to ensure migration files' integrity.
Postgresql migrations live in ./component/store/local/sqlc/sqlc/migrations
.
In order to create a new migration, you can follow these steps:
- run
$ make new-sqlite-migration migration_name=my_migration
- edit the migration
- run
$ update-sqlite-migrations-sum
to updateatlas.sum
Postgresql migrations live in ./component/store/remote/postgresql/sqlc/migrations
.
In order to create a new migration, you can follow these steps:
- run
$ make new-postgres-migration migration_name=my_migration
- edit the migration
- run
$ update-postgres-migrations-sum
to updateatlas.sum