Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scbctl/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func NewRootCommand() *cobra.Command {
kubeconfigArgs.AddFlags(rootCmd.PersistentFlags())

rootCmd.AddCommand(NewScanCommand())
rootCmd.AddCommand(NewTriggerCommand())

return rootCmd
}
90 changes: 90 additions & 0 deletions scbctl/cmd/trigger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// SPDX-FileCopyrightText: the secureCodeBox authors
//
// SPDX-License-Identifier: Apache-2.0
package cmd

import (
"context"
"fmt"

v1 "github.com/secureCodeBox/secureCodeBox/operator/apis/execution/v1"
"github.com/secureCodeBox/secureCodeBox/operator/utils"

"github.com/spf13/cobra"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func NewTriggerCommand() *cobra.Command {
triggerCmd := &cobra.Command{
Use: "trigger [scheduledScan name]",
Short: "Trigger a scheduled scan",
Long: `Trigger a new execution (Scan) of a ScheduledScan, ahead of its usual execution schedule.`,
Args: cobra.ExactArgs(1),
Example: `
# Trigger a new scan for ScheduledScan "nmap"
scbctl trigger nmap

# Trigger a ScheduledScan outside of your current namespace
scbctl trigger nmap --namespace foobar
`,
SilenceUsage: true,

RunE: func(cmd *cobra.Command, args []string) error {
scheduledScanName := args[0]
kubeclient, namespace, err := clientProvider.GetClient(kubeconfigArgs)
if err != nil {
return fmt.Errorf("error initializing kubernetes client, your kubeconfig is likely malformed or invalid")
}

if namespaceFlag, err := cmd.Flags().GetString("namespace"); err == nil && namespaceFlag != "" {
namespace = namespaceFlag
}

var scan v1.ScheduledScan

err = kubeclient.Get(cmd.Context(), types.NamespacedName{Name: scheduledScanName, Namespace: namespace}, &scan)
if err != nil {
if apierrors.IsNotFound(err) {
return fmt.Errorf("could not find ScheduledScan '%s' in namespace '%s'", scheduledScanName, namespace)
} else {
panic(err)
}
}

utils.RetriggerScheduledScan(context.TODO(), kubeclient.Status(), scan)
fmt.Printf("triggered new Scan for ScheduledScan '%s'\n", scheduledScanName)

return nil
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
kubeclient, namespace, err := clientProvider.GetClient(kubeconfigArgs)
if err != nil {
fmt.Printf("Error initializing kubernetes client: %v\n", err)
return nil, cobra.ShellCompDirectiveError
}

if namespaceFlag, err := cmd.Flags().GetString("namespace"); err == nil && namespaceFlag != "" {
namespace = namespaceFlag
}

var scans v1.ScheduledScanList

err = kubeclient.List(cmd.Context(), &scans, client.InNamespace(namespace))
if err != nil {
fmt.Printf("Error listing ScheduledScans: %v\n", err)
return nil, cobra.ShellCompDirectiveError
}

scanNames := make([]string, len(scans.Items))
for i, scan := range scans.Items {
scanNames[i] = scan.Name
}

return scanNames, cobra.ShellCompDirectiveDefault
},
}

return triggerCmd
}
116 changes: 116 additions & 0 deletions scbctl/cmd/trigger_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// SPDX-FileCopyrightText: the secureCodeBox authors
//
// SPDX-License-Identifier: Apache-2.0
package cmd

import (
"context"
"errors"
"testing"

v1 "github.com/secureCodeBox/secureCodeBox/operator/apis/execution/v1"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

type TestClientProvider struct {
client.Client
namespace string
err error
}

func (m *TestClientProvider) GetClient(_ *genericclioptions.ConfigFlags) (client.Client, string, error) {
return m.Client, m.namespace, m.err
}

type triggerTestcase struct {
name string
args []string
namespace string
expectedError error
setup func(client.Client)
}

func TestTriggerCommand(t *testing.T) {
testcases := []triggerTestcase{
{
name: "Should trigger a scheduled scan successfully",
args: []string{"trigger", "nmap"},
namespace: "foobar",
expectedError: nil,
setup: func(client client.Client) {
scan := &v1.ScheduledScan{
ObjectMeta: metav1.ObjectMeta{
Name: "nmap",
Namespace: "foobar",
},
}
client.Create(context.Background(), scan)
},
},
{
name: "Should return error if scheduled scan not found",
args: []string{"trigger", "nonexistent-scan"},
namespace: "foobar",
expectedError: errors.New("could not find ScheduledScan 'nonexistent-scan' in namespace 'foobar'"),
setup: func(client.Client) {},
},
{
name: "Should return error if no scan name is provided",
args: []string{"trigger"},
namespace: "default",
expectedError: errors.New("accepts 1 arg(s), received 0"),
setup: func(client.Client) {},
},
{
name: "Should trigger a scheduled scan in a custom namespace",
args: []string{"trigger", "nmap", "--namespace", "foobar"},
namespace: "custom-ns",
expectedError: nil,
setup: func(client client.Client) {
scan := &v1.ScheduledScan{
ObjectMeta: metav1.ObjectMeta{
Name: "nmap",
Namespace: "foobar",
},
}
client.Create(context.Background(), scan)
},
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
scheme := runtime.NewScheme()
utilruntime.Must(v1.AddToScheme(scheme))
client := fake.NewClientBuilder().WithScheme(scheme).Build()
clientProvider = &TestClientProvider{
Client: client,
namespace: tc.namespace,
err: nil,
}

if tc.setup != nil {
tc.setup(client)
}

rootCmd := NewRootCommand()

rootCmd.SetArgs(tc.args)
rootCmd.SilenceUsage = true

err := rootCmd.Execute()

if tc.expectedError != nil {
assert.Equal(t, tc.expectedError.Error(), err.Error())
} else {
assert.NoError(t, err)
}
})
}
}
9 changes: 5 additions & 4 deletions scbctl/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ go 1.22.0
toolchain go1.22.3

require (
github.com/secureCodeBox/secureCodeBox/operator v0.0.0-20240603123116-d52caf587550
github.com/secureCodeBox/secureCodeBox/operator v0.0.0-20240709091631-4e5c4b973bf2
github.com/spf13/cobra v1.7.0
k8s.io/api v0.30.1
k8s.io/apimachinery v0.30.1
k8s.io/api v0.30.2
k8s.io/apimachinery v0.30.2
k8s.io/cli-runtime v0.30.1
sigs.k8s.io/controller-runtime v0.18.4
)
Expand Down Expand Up @@ -43,6 +43,7 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
Expand All @@ -66,7 +67,7 @@ require (
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/client-go v0.30.1 // indirect
k8s.io/client-go v0.30.2 // indirect
k8s.io/klog/v2 v2.120.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
Expand Down
10 changes: 10 additions & 0 deletions scbctl/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
Expand Down Expand Up @@ -124,6 +126,8 @@ github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncj
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/secureCodeBox/secureCodeBox/operator v0.0.0-20240603123116-d52caf587550 h1:LoDWFat5Y9maj3D32x6VJd+BC4ZOGeX1caVcJsn0DPQ=
github.com/secureCodeBox/secureCodeBox/operator v0.0.0-20240603123116-d52caf587550/go.mod h1:n80o6zgDki2s2qODX2p0ipn0xKqP8jnOPQB6vFyDMBY=
github.com/secureCodeBox/secureCodeBox/operator v0.0.0-20240709091631-4e5c4b973bf2 h1:uiycWm3Pi8r2tiiO0XyxzMfeRvXCFqUuVU7YzmiKWMM=
github.com/secureCodeBox/secureCodeBox/operator v0.0.0-20240709091631-4e5c4b973bf2/go.mod h1:jmmowH/Q4f+WLU5027SGuHa8OznKTF2c9aA+3+UzwCk=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
Expand Down Expand Up @@ -252,14 +256,20 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY=
k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM=
k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI=
k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI=
k8s.io/apiextensions-apiserver v0.30.1 h1:4fAJZ9985BmpJG6PkoxVRpXv9vmPUOVzl614xarePws=
k8s.io/apiextensions-apiserver v0.30.1/go.mod h1:R4GuSrlhgq43oRY9sF2IToFh7PVlF1JjfWdoG3pixk4=
k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U=
k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg=
k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/cli-runtime v0.30.1 h1:kSBBpfrJGS6lllc24KeniI9JN7ckOOJKnmFYH1RpTOw=
k8s.io/cli-runtime v0.30.1/go.mod h1:zhHgbqI4J00pxb6gM3gJPVf2ysDjhQmQtnTxnMScab8=
k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q=
k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc=
k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50=
k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
Expand Down