Skip to content

Commit

Permalink
feat: Get ConfigAuditReports from ReplicaSet in the same hierarchy (#397
Browse files Browse the repository at this point in the history
)

Signed-off-by: Daniel Pacak <pacak.daniel@gmail.com>
  • Loading branch information
danielpacak committed Feb 17, 2021
1 parent 776bb1e commit 5d98f63
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 236 deletions.
20 changes: 12 additions & 8 deletions pkg/cmd/get_configaudit.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ import (
"io"

"github.com/aquasecurity/starboard/pkg/configauditreport"

"github.com/aquasecurity/starboard/pkg/starboard"

clientset "github.com/aquasecurity/starboard/pkg/generated/clientset/versioned"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func NewGetConfigAuditCmd(executable string, cf *genericclioptions.ConfigFlags, outWriter io.Writer) *cobra.Command {
Expand All @@ -37,15 +36,14 @@ NAME is the name of a particular Kubernetes workload.
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()

config, err := cf.ToRESTConfig()
kubeConfig, err := cf.ToRESTConfig()
if err != nil {
return err
}
client, err := clientset.NewForConfig(config)
kubeClientset, err := kubernetes.NewForConfig(kubeConfig)
if err != nil {
return err
}

ns, _, err := cf.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
Expand All @@ -58,7 +56,13 @@ NAME is the name of a particular Kubernetes workload.
if err != nil {
return err
}
report, err := configauditreport.NewReadWriter(client).FindByOwner(ctx, workload)
scheme := starboard.NewScheme()
kubeClient, err := client.New(kubeConfig, client.Options{Scheme: scheme})
if err != nil {
return err
}
reader := configauditreport.NewReadWriter(kubeClient, kubeClientset)
report, err := reader.FindByOwnerInHierarchy(ctx, workload)
if err != nil {
return nil
}
Expand All @@ -70,7 +74,7 @@ NAME is the name of a particular Kubernetes workload.

format := cmd.Flag("output").Value.String()
printer, err := genericclioptions.NewPrintFlags("").
WithTypeSetter(starboard.NewScheme()).
WithTypeSetter(scheme).
WithDefaultOutput(format).
ToPrinter()
if err != nil {
Expand Down
14 changes: 8 additions & 6 deletions pkg/cmd/get_report.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import (
"fmt"
"io"

clientset "github.com/aquasecurity/starboard/pkg/generated/clientset/versioned"
"github.com/aquasecurity/starboard/pkg/generated/clientset/versioned"
"github.com/aquasecurity/starboard/pkg/report"
"github.com/aquasecurity/starboard/pkg/starboard"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func NewGetReportCmd(info starboard.BuildInfo, cf *genericclioptions.ConfigFlags, outWriter io.Writer) *cobra.Command {
Expand All @@ -24,18 +25,19 @@ NAME is the name of a particular Kubernetes workload.
Example: fmt.Sprintf(` # Save report to a file
%[1]s get report deploy/nginx > report.html`, info.Executable),
RunE: func(cmd *cobra.Command, args []string) error {
config, err := cf.ToRESTConfig()
kubeConfig, err := cf.ToRESTConfig()
if err != nil {
return err
}
starboardClientset, err := clientset.NewForConfig(config)
starboardClientset, err := versioned.NewForConfig(kubeConfig)
if err != nil {
return err
}
kubernetesClientset, err := kubernetes.NewForConfig(config)
kubeClientset, err := kubernetes.NewForConfig(kubeConfig)
if err != nil {
return err
}
kubeClient, err := client.New(kubeConfig, client.Options{Scheme: starboard.NewScheme()})
ns, _, err := cf.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
Expand All @@ -49,8 +51,8 @@ NAME is the name of a particular Kubernetes workload.
return err
}

return report.NewHTMLReporter(starboardClientset, kubernetesClientset).
GenerateReport(workload, outWriter)
reporter := report.NewHTMLReporter(starboardClientset, kubeClientset, kubeClient)
return reporter.GenerateReport(workload, outWriter)
},
}
}
12 changes: 5 additions & 7 deletions pkg/cmd/scan_configaudit.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import (

"github.com/aquasecurity/starboard/pkg/configauditreport"
"github.com/aquasecurity/starboard/pkg/ext"
"github.com/aquasecurity/starboard/pkg/generated/clientset/versioned"
"github.com/aquasecurity/starboard/pkg/polaris"
"github.com/aquasecurity/starboard/pkg/starboard"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
Expand Down Expand Up @@ -53,6 +53,8 @@ func ScanConfigAuditReports(cf *genericclioptions.ConfigFlags) func(cmd *cobra.C
if err != nil {
return err
}
scheme := starboard.NewScheme()
kubeClient, err := client.New(kubeConfig, client.Options{Scheme: scheme})
opts, err := getScannerOpts(cmd)
if err != nil {
return err
Expand All @@ -62,16 +64,12 @@ func ScanConfigAuditReports(cf *genericclioptions.ConfigFlags) func(cmd *cobra.C
return err
}
plugin := polaris.NewPlugin(ext.NewSystemClock(), config)
scanner := configauditreport.NewScanner(starboard.NewScheme(), kubeClientset, opts, plugin)
scanner := configauditreport.NewScanner(scheme, kubeClientset, opts, plugin)
report, err := scanner.Scan(ctx, workload, gvk)
if err != nil {
return err
}
starboardClientset, err := versioned.NewForConfig(kubeConfig)
if err != nil {
return nil
}
writer := configauditreport.NewReadWriter(starboardClientset)
writer := configauditreport.NewReadWriter(kubeClient, kubeClientset)
return writer.Write(ctx, report)
}
}
111 changes: 45 additions & 66 deletions pkg/configauditreport/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ package configauditreport

import (
"context"
"fmt"
"k8s.io/client-go/kubernetes"

"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
"github.com/aquasecurity/starboard/pkg/generated/clientset/versioned"
"github.com/aquasecurity/starboard/pkg/kube"
"github.com/aquasecurity/starboard/pkg/kube/rs"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/klog"
"sigs.k8s.io/controller-runtime/pkg/client"
)

Expand All @@ -21,12 +20,18 @@ type Writer interface {
Write(ctx context.Context, report v1alpha1.ConfigAuditReport) error
}

// Reader is the interface that wraps basic FindByOwner method.
// Reader is the interface that wraps methods for finding v1alpha1.ConfigAuditReport objects.
//
// FindByOwner returns a v1alpha1.ConfigAuditReport owned by the given
// kube.Object or nil if the report is not found.
//
// FindByOwnerInHierarchy is similar to FindByOwner except that it tries to lookup
// the v1alpha1.ConfigAuditReport objects owned by related Kubernetes objects.
// For example, if the given owner is a Deployment, but a report is owned
// by the active ReplicaSet (current revision) this method will return the report.
type Reader interface {
FindByOwner(ctx context.Context, owner kube.Object) (*v1alpha1.ConfigAuditReport, error)
FindByOwnerInHierarchy(ctx context.Context, owner kube.Object) (*v1alpha1.ConfigAuditReport, error)
}

type ReadWriter interface {
Expand All @@ -35,74 +40,22 @@ type ReadWriter interface {
}

type readWriter struct {
clientset versioned.Interface
client client.Client
// TODO Get rid of it once we refactor ReplicaSet resolver
clientset kubernetes.Interface
}

// NewReadWriter constructs a new ReadWriter which is using the client-go
// module for interacting with the Kubernetes API server.
func NewReadWriter(clientset versioned.Interface) ReadWriter {
// 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, clientset kubernetes.Interface) ReadWriter {
return &readWriter{
client: client,
clientset: clientset,
}
}

func (r *readWriter) Write(ctx context.Context, report v1alpha1.ConfigAuditReport) error {
existing, err := r.clientset.AquasecurityV1alpha1().ConfigAuditReports(report.Namespace).
Get(ctx, report.Name, metav1.GetOptions{})

if err == nil {
klog.V(3).Infof("Updating ConfigAuditReport %q", report.Namespace+"/"+report.Name)
deepCopy := existing.DeepCopy()
deepCopy.Labels = report.Labels
deepCopy.Report = report.Report

_, err = r.clientset.AquasecurityV1alpha1().ConfigAuditReports(report.Namespace).
Update(ctx, deepCopy, metav1.UpdateOptions{})
return err
}

if errors.IsNotFound(err) {
klog.V(3).Infof("Creating ConfigAuditReport %q", report.Namespace+"/"+report.Name)
_, err = r.clientset.AquasecurityV1alpha1().ConfigAuditReports(report.Namespace).
Create(ctx, &report, metav1.CreateOptions{})
return err
}

return err
}

func (r *readWriter) FindByOwner(ctx context.Context, workload kube.Object) (*v1alpha1.ConfigAuditReport, error) {
list, err := r.clientset.AquasecurityV1alpha1().ConfigAuditReports(workload.Namespace).List(ctx, metav1.ListOptions{
LabelSelector: labels.Set{
kube.LabelResourceKind: string(workload.Kind),
kube.LabelResourceName: workload.Name,
kube.LabelResourceNamespace: workload.Namespace,
}.String(),
})
if err != nil {
return nil, err
}
// Only one config audit per specific workload exists on the cluster
if len(list.Items) > 0 {
return &list.DeepCopy().Items[0], nil
}
return nil, nil
}

type crReadWriter struct {
client client.Client
}

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

func (r *crReadWriter) Write(ctx context.Context, report v1alpha1.ConfigAuditReport) error {
var existing v1alpha1.ConfigAuditReport
err := r.client.Get(ctx, types.NamespacedName{
Name: report.Name,
Expand All @@ -124,7 +77,7 @@ func (r *crReadWriter) Write(ctx context.Context, report v1alpha1.ConfigAuditRep
return err
}

func (r *crReadWriter) FindByOwner(ctx context.Context, owner kube.Object) (*v1alpha1.ConfigAuditReport, error) {
func (r *readWriter) FindByOwner(ctx context.Context, owner kube.Object) (*v1alpha1.ConfigAuditReport, error) {
var list v1alpha1.ConfigAuditReportList

err := r.client.List(ctx, &list, client.MatchingLabels{
Expand All @@ -142,3 +95,29 @@ func (r *crReadWriter) FindByOwner(ctx context.Context, owner kube.Object) (*v1a
}
return nil, nil
}

func (r *readWriter) FindByOwnerInHierarchy(ctx context.Context, owner kube.Object) (*v1alpha1.ConfigAuditReport, error) {
report, err := r.FindByOwner(ctx, owner)
if err != nil {
return nil, err
}

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

}

if report != nil {
return report.DeepCopy(), nil
}
return nil, nil
}

0 comments on commit 5d98f63

Please sign in to comment.