SDK
Smithy's SDK.
Component
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_BACKEND_STORE_TYPE |
string |
yes |
- |
local, test, *remote |
SSMITHY_BACKEND_STORE_DSN |
string |
no |
smithy.db |
* |
SMITHY_LOG_LEVEL |
string |
false |
info, debug, warn, error |
|
For local
development, an SQLite
Backend Store Type will be used.
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"),
)
Components
Target
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)
}
}
Scanner
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)
}
}
Enricher
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)
}
}
Filter
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)
}
}
Reporter
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)
}
}
Utilities
Logging
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)
}