Skip to content

Commit

Permalink
feat: exposed secrets scanning
Browse files Browse the repository at this point in the history
  • Loading branch information
josedonizetti committed Jun 20, 2022
1 parent d54d0ab commit 701749c
Show file tree
Hide file tree
Showing 11 changed files with 356 additions and 40 deletions.
2 changes: 1 addition & 1 deletion deploy/crd/exposedsecretreports.crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ spec:
- severity
- match
properties:
ruleID:
target:
description: |
Target is where the exposed secret was found.
type: string
Expand Down
2 changes: 1 addition & 1 deletion pkg/apis/aquasecurity/v1alpha1/exposed_secrets_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ type ExposedSecretReportData struct {
Summary ExposedSecretSummary `json:"summary"`

// Secrets is a list of passwords, api keys, tokens and others items found in the Artifact.
Secrets []ExposedSecret `json:"exposed secrets"`
Secrets []ExposedSecret `json:"secrets"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
Expand Down
112 changes: 112 additions & 0 deletions pkg/exposedsecretreport/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package exposedsecretreport

import (
"fmt"
"strings"
"time"

"github.com/aquasecurity/trivy-operator/pkg/apis/aquasecurity/v1alpha1"
"github.com/aquasecurity/trivy-operator/pkg/kube"
"github.com/aquasecurity/trivy-operator/pkg/trivyoperator"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

type ReportBuilder struct {
scheme *runtime.Scheme
controller client.Object
container string
hash string
data v1alpha1.ExposedSecretReportData
reportTTL *time.Duration
}

func NewReportBuilder(scheme *runtime.Scheme) *ReportBuilder {
return &ReportBuilder{
scheme: scheme,
}
}

func (b *ReportBuilder) Controller(controller client.Object) *ReportBuilder {
b.controller = controller
return b
}

func (b *ReportBuilder) Container(name string) *ReportBuilder {
b.container = name
return b
}

func (b *ReportBuilder) PodSpecHash(hash string) *ReportBuilder {
b.hash = hash
return b
}

func (b *ReportBuilder) Data(data v1alpha1.ExposedSecretReportData) *ReportBuilder {
b.data = data
return b
}

func (b *ReportBuilder) ReportTTL(ttl *time.Duration) *ReportBuilder {
b.reportTTL = ttl
return b
}

func (b *ReportBuilder) reportName() string {
kind := b.controller.GetObjectKind().GroupVersionKind().Kind
name := b.controller.GetName()
reportName := fmt.Sprintf("%s-%s-%s", strings.ToLower(kind), name, b.container)
if len(validation.IsValidLabelValue(reportName)) == 0 {
return reportName
}

return fmt.Sprintf("%s-%s", strings.ToLower(kind), kube.ComputeHash(name+"-"+b.container))
}

func (b *ReportBuilder) Get() (v1alpha1.ExposedSecretReport, error) {
labels := map[string]string{
trivyoperator.LabelContainerName: b.container,
}

if b.hash != "" {
labels[trivyoperator.LabelResourceSpecHash] = b.hash
}

report := v1alpha1.ExposedSecretReport{
ObjectMeta: metav1.ObjectMeta{
Name: b.reportName(),
Namespace: b.controller.GetNamespace(),
Labels: labels,
},
Report: b.data,
}

// TODO: do we support TTL?
if b.reportTTL != nil {
report.Annotations = map[string]string{
v1alpha1.TTLReportAnnotation: b.reportTTL.String(),
}
}
err := kube.ObjectToObjectMeta(b.controller, &report.ObjectMeta)
if err != nil {
return v1alpha1.ExposedSecretReport{}, err
}
err = controllerutil.SetControllerReference(b.controller, &report, b.scheme)
if err != nil {
return v1alpha1.ExposedSecretReport{}, fmt.Errorf("setting controller reference: %w", err)
}
// The OwnerReferencesPermissionsEnforcement admission controller protects the
// access to metadata.ownerReferences[x].blockOwnerDeletion of an object, so
// that only users with "update" permission to the finalizers subresource of the
// referenced owner can change it.
// We set metadata.ownerReferences[x].blockOwnerDeletion to false so that
// additional RBAC permissions are not required when the OwnerReferencesPermissionsEnforcement
// is enabled.
// See https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#ownerreferencespermissionenforcement
report.OwnerReferences[0].BlockOwnerDeletion = pointer.BoolPtr(false)
return report, nil
}
122 changes: 122 additions & 0 deletions pkg/exposedsecretreport/io.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package exposedsecretreport

import (
"context"
"fmt"

"github.com/aquasecurity/trivy-operator/pkg/apis/aquasecurity/v1alpha1"
"github.com/aquasecurity/trivy-operator/pkg/kube"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// Writer is the interface that wraps the basic Write method.
//
// Write creates or updates the given slice of v1alpha1.VulnerabilityReport
// instances.
type Writer interface {
Write(context.Context, []v1alpha1.ExposedSecretReport) error
}

// Reader is the interface that wraps methods for finding v1alpha1.VulnerabilityReport objects.
//
// FindByOwner returns the slice of v1alpha1.VulnerabilityReport instances
// owned by the given kube.ObjectRef or an empty slice if the reports are not found.
//
// FindByOwnerInHierarchy is similar to FindByOwner except it tries to lookup
// v1alpha1.VulnerabilityReport objects owned by related Kubernetes objects.
// For example, if the given owner is a Deployment, but reports are owned by the
// active ReplicaSet (current revision) this method will return the reports.
type Reader interface {
FindByOwner(context.Context, kube.ObjectRef) ([]v1alpha1.ExposedSecretReport, error)
FindByOwnerInHierarchy(ctx context.Context, object kube.ObjectRef) ([]v1alpha1.ExposedSecretReport, error)
}

type ReadWriter interface {
Reader
Writer
}

type readWriter struct {
*kube.ObjectResolver
}

// NewReadWriter constructs a new ReadWriter which is using the client package
// provided by the controller-runtime libraries for interacting with the
// Kubernetes API server.
func NewReadWriter(client client.Client) ReadWriter {
return &readWriter{
ObjectResolver: &kube.ObjectResolver{Client: client},
}
}

func (r *readWriter) Write(ctx context.Context, reports []v1alpha1.ExposedSecretReport) error {
for _, report := range reports {
err := r.createOrUpdate(ctx, report)
if err != nil {
return err
}
}
return nil
}

func (r *readWriter) createOrUpdate(ctx context.Context, report v1alpha1.ExposedSecretReport) error {
var existing v1alpha1.ExposedSecretReport
err := r.Get(ctx, types.NamespacedName{
Name: report.Name,
Namespace: report.Namespace,
}, &existing)

if err == nil {
copied := existing.DeepCopy()
copied.Labels = report.Labels
copied.Report = report.Report

return r.Update(ctx, copied)
}

if errors.IsNotFound(err) {
return r.Create(ctx, &report)
}

return err
}

func (r *readWriter) FindByOwner(ctx context.Context, owner kube.ObjectRef) ([]v1alpha1.ExposedSecretReport, error) {
var list v1alpha1.ExposedSecretReportList

labels := client.MatchingLabels(kube.ObjectRefToLabels(owner))

err := r.List(ctx, &list, labels, client.InNamespace(owner.Namespace))
if err != nil {
return nil, err
}

return list.DeepCopy().Items, nil
}

func (r *readWriter) FindByOwnerInHierarchy(ctx context.Context, owner kube.ObjectRef) ([]v1alpha1.ExposedSecretReport, error) {
reports, err := r.FindByOwner(ctx, owner)
if err != nil {
return nil, err
}

// no reports found for provided owner, look for reports in related replicaset
if len(reports) == 0 && (owner.Kind == kube.KindDeployment || owner.Kind == kube.KindPod) {
rsName, err := r.RelatedReplicaSetName(ctx, owner)
if err != nil {
return nil, fmt.Errorf("getting replicaset related to %s/%s: %w", owner.Kind, owner.Name, err)
}
reports, err = r.FindByOwner(ctx, kube.ObjectRef{
Kind: kube.KindReplicaSet,
Name: rsName,
Namespace: owner.Namespace,
})
if err != nil {
return nil, err
}
}

return reports, nil
}
24 changes: 13 additions & 11 deletions pkg/operator/operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/aquasecurity/trivy-operator/pkg/compliance"
"github.com/aquasecurity/trivy-operator/pkg/configauditreport"
"github.com/aquasecurity/trivy-operator/pkg/exposedsecretreport"
"github.com/aquasecurity/trivy-operator/pkg/ext"
"github.com/aquasecurity/trivy-operator/pkg/kube"
"github.com/aquasecurity/trivy-operator/pkg/metrics"
Expand Down Expand Up @@ -140,17 +141,18 @@ func Start(ctx context.Context, buildInfo trivyoperator.BuildInfo, operatorConfi
}

if err = (&vulnerabilityreport.WorkloadController{
Logger: ctrl.Log.WithName("reconciler").WithName("vulnerabilityreport"),
Config: operatorConfig,
ConfigData: trivyOperatorConfig,
Client: mgr.GetClient(),
ObjectResolver: objectResolver,
LimitChecker: limitChecker,
LogsReader: logsReader,
SecretsReader: secretsReader,
Plugin: plugin,
PluginContext: pluginContext,
ReadWriter: vulnerabilityreport.NewReadWriter(mgr.GetClient()),
Logger: ctrl.Log.WithName("reconciler").WithName("vulnerabilityreport"),
Config: operatorConfig,
ConfigData: trivyOperatorConfig,
Client: mgr.GetClient(),
ObjectResolver: objectResolver,
LimitChecker: limitChecker,
LogsReader: logsReader,
SecretsReader: secretsReader,
Plugin: plugin,
PluginContext: pluginContext,
ReadWriter: vulnerabilityreport.NewReadWriter(mgr.GetClient()),
ExposedSecretReadWriter: exposedsecretreport.NewReadWriter(mgr.GetClient()),
}).SetupWithManager(mgr); err != nil {
return fmt.Errorf("unable to setup vulnerabilityreport reconciler: %w", err)
}
Expand Down
10 changes: 10 additions & 0 deletions pkg/plugin/trivy/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
type ScanResult struct {
Target string `json:"Target"`
Vulnerabilities []Vulnerability `json:"Vulnerabilities"`
Secrets []Secret `json:"Secrets"`
}

type ScanReport struct {
Expand Down Expand Up @@ -36,3 +37,12 @@ type Layer struct {
Digest string `json:"Digest"`
DiffID string `json:"DiffID"`
}

type Secret struct {
Target string `json:"Target"`
RuleID string `json:"RuleID"`
Category string `json:"Category"`
Severity v1alpha1.Severity `json:"Severity"`
Title string `json:"Title"`
Match string `json:"Match"`
}
Loading

0 comments on commit 701749c

Please sign in to comment.