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
29 changes: 29 additions & 0 deletions scbctl/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: the secureCodeBox authors
//
// SPDX-License-Identifier: Apache-2.0
package cmd

import (
kubernetes "github.com/secureCodeBox/secureCodeBox/scbctl/pkg"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
)

var kubeconfigArgs = genericclioptions.NewConfigFlags(false)

var (
clientProvider kubernetes.ClientProvider = &kubernetes.DefaultClientProvider{}
)

func NewRootCommand() *cobra.Command {
rootCmd := &cobra.Command{
Use: "scbctl",
Short: "cli app to manage scans & other secureCodeBox resources",
Long: ``,
}
kubeconfigArgs.AddFlags(rootCmd.PersistentFlags())

rootCmd.AddCommand(NewScanCommand())

return rootCmd
}
147 changes: 72 additions & 75 deletions scbctl/cmd/scans.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,93 +4,90 @@
package cmd

import (
"context"
"errors"
"fmt"
"strings"

v1 "github.com/secureCodeBox/secureCodeBox/operator/apis/execution/v1"
kubernetes "github.com/secureCodeBox/secureCodeBox/scbctl/pkg"

"github.com/spf13/cobra"
metav2 "k8s.io/apimachinery/pkg/api/errors"
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"
)

var (
kubeconfigArgs = genericclioptions.NewConfigFlags(false)
clientProvider kubernetes.ClientProvider = &kubernetes.DefaultClientProvider{}
scheme = runtime.NewScheme()
)
func NewScanCommand() *cobra.Command {
scanCmd := &cobra.Command{
Use: "scan [scanType] -- [parameters...]",
Short: "Create a new scan",
Long: `Create a new Scan custom resource in the the current namespace`,
Args: cobra.MinimumNArgs(1),
Example: `
# Create a new scan
scbctl scan nmap -- scanme.nmap.org

# Create a scan with a custom name
scbctl scan nmap --name scanme-nmap-org -- scanme.nmap.org

# Create a with a different scan type
scbctl scan nuclei -- -target example.com

# Create in a different namespace
scbctl scan --namespace foobar nmap -- -p 80 scanme.nmap.org
`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
scanType := args[0]

scanName := scanType
if name, err := cmd.Flags().GetString("name"); err == nil && name != "" {
scanName = name
}
paramIndex := cmd.ArgsLenAtDash()
if paramIndex == -1 {
return fmt.Errorf("you must use '--' to separate scan parameters")
}

func init() {
utilruntime.Must(v1.AddToScheme(scheme))
ScanCmd.Flags().StringP("namespace", "n", "", "Namespace in which to create the scan")
}
parameters := args[paramIndex:]

kubeclient, namespace, err := clientProvider.GetClient(kubeconfigArgs)
if err != nil {
return fmt.Errorf("error initializing kubernetes client. your kubeconfig is likely malformed or invalid. %s", err)
}

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

var ScanCmd = &cobra.Command{
Use: "scan [name] -- [parameters...]",
Short: "Create a new scanner",
Long: `Create a new execution (Scan) in the default namespace if no namespace is provided`,
Example: `
# Create a new scan
scbctl scan nmap
# Create in a different namespace
scbctl scan --namespace foobar nmap -- scanme.nmap.org -p 90
`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {

scanName := args[0]
paramIndex := cmd.ArgsLenAtDash()
if paramIndex == -1 {
return errors.New("You must use '--' to separate scan parameters")
}

parameters := args[paramIndex:]

fmt.Println("🎬 Initializing Kubernetes client")

kubeclient, namespace, err := clientProvider.GetClient(kubeconfigArgs)
if err != nil {
return fmt.Errorf("Error initializing Kubernetes client: %s", err)
}

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

fmt.Printf("🆕 Creating a new scan with name '%s' and parameters '%s'\n", scanName, strings.Join(parameters, " "))

scan := &v1.Scan{
TypeMeta: metav1.TypeMeta{
Kind: "Scan",
APIVersion: "execution.securecodebox.io/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: scanName,
Namespace: namespace,
},
Spec: v1.ScanSpec{
ScanType: scanName,
Parameters: parameters,
},
}

fmt.Println("🔁 Launching the scan")

err = kubeclient.Create(context.TODO(), scan)
if err != nil {
if metav2.IsNotFound(err) {
return fmt.Errorf("failed to create Scan: namespace '%s' not found", namespace)
fmt.Printf("🆕 Creating a new scan with name '%s' and parameters '%s'\n", scanName, strings.Join(parameters, " "))

scan := &v1.Scan{
TypeMeta: metav1.TypeMeta{
Kind: "Scan",
APIVersion: "execution.securecodebox.io/v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: scanName,
Namespace: namespace,
},
Spec: v1.ScanSpec{
ScanType: scanType,
Parameters: parameters,
},
}
return fmt.Errorf("Failed to create Scan: %s", err)
}

fmt.Printf("🚀 Successfully created a new Scan '%s'\n", args[0])
return nil
err = kubeclient.Create(cmd.Context(), scan)
if err != nil {
if metav2.IsNotFound(err) {
return fmt.Errorf("failed to create Scan: namespace '%s' not found", namespace)
}
return fmt.Errorf("failed to create scan: %s", err)
}

fmt.Printf("🚀 Successfully created a new Scan '%s'\n", scanName)
return nil
},
}

scanCmd.Flags().String("name", "", "Name of the created scan. If no name is provided, the ScanType will be used as the name")

},
return scanCmd
}
132 changes: 89 additions & 43 deletions scbctl/cmd/scans_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"testing"

v1 "github.com/secureCodeBox/secureCodeBox/operator/apis/execution/v1"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
Expand All @@ -27,78 +27,124 @@ func (m *MockClientProvider) GetClient(_ *genericclioptions.ConfigFlags) (client
return m.Client, m.namespace, m.err
}

func TestScanCommand(t *testing.T) {
scheme := runtime.NewScheme()
utilruntime.Must(v1.AddToScheme(scheme))
type testcase struct {
name string
args []string
expectedError error
expectedScan *expectedScan
}

testcases := []struct {
name string
args []string
expectedError error
}{
type expectedScan struct {
name string
scanType string
namespace string
parameters []string
}

func TestScanCommand(t *testing.T) {
testcases := []testcase{
{
name: "Valid entry ",
args: []string{"nmap", "--", "scanme.nmap.org"},
name: "Should create nmap scan with a single parameter",
args: []string{"scan", "nmap", "--", "scanme.nmap.org"},
expectedError: nil,
expectedScan: &expectedScan{
name: "nmap",
scanType: "nmap",
namespace: "default",
parameters: []string{"scanme.nmap.org"},
},
},
{
name: "Valid entry with multiple parameters",
args: []string{"nmap", "--", "scanme.nmap.org", "-p", "90"},
name: "Should create nmap scan with multiple parameters",
args: []string{"scan", "nmap", "--", "scanme.nmap.org", "-p", "90"},
expectedError: nil,
expectedScan: &expectedScan{
name: "nmap",
scanType: "nmap",
namespace: "default",
parameters: []string{"scanme.nmap.org", "-p", "90"},
},
},
{
name: "Valid entry with namespace",
name: "Should use --name flag as the name of the scan if provided",
args: []string{"scan", "--name", "scanme-nmap-org", "nmap", "--", "scanme.nmap.org"},
expectedError: nil,
expectedScan: &expectedScan{
scanType: "nmap",
name: "scanme-nmap-org",
namespace: "default",
parameters: []string{"scanme.nmap.org"},
},
},
{
name: "Should create nmap in a custom namespace",
args: []string{"scan", "--namespace", "foobar", "nmap", "--", "scanme.nmap.org"},
expectedError: nil,
expectedScan: &expectedScan{
name: "nmap",
scanType: "nmap",
namespace: "foobar",
parameters: []string{"scanme.nmap.org"},
},
},
{
name: "Flags provided after the `--` seperator should be passed as parameters, not flags",
args: []string{"scan", "--namespace", "foobar", "kubeaudit", "--", "--namespace", "some-other-namespace"},
expectedError: nil,
expectedScan: &expectedScan{
name: "kubeaudit",
scanType: "kubeaudit",
namespace: "foobar",
parameters: []string{"--namespace", "some-other-namespace"},
},
},
{
name: "No scan parameters provided",
args: []string{"nmap"},
expectedError: errors.New("You must use '--' to separate scan parameters"),
name: "Should throw an error when no parameters are provided",
args: []string{"scan", "nmap"},
expectedError: errors.New("you must use '--' to separate scan parameters"),
expectedScan: nil,
},
{
name: "Should throw an error when no `--` separator is used before the scan parameters",
args: []string{"scan", "nmap", "scanme.nmap.org"},
expectedError: errors.New("you must use '--' to separate scan parameters"),
expectedScan: nil,
},
}

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 = &MockClientProvider{
Client: client,
namespace: "default",
err: nil,
}

cmd := &cobra.Command{
Use: ScanCmd.Use,
Short: ScanCmd.Short,
Long: ScanCmd.Long,
Example: ScanCmd.Example,
RunE: ScanCmd.RunE,
}
rootCmd := NewRootCommand()

cmd.Flags().AddFlagSet(ScanCmd.Flags())
rootCmd.SetArgs(tc.args)
rootCmd.SilenceUsage = true

cmd.SetArgs(tc.args)
cmd.SilenceUsage = true
err := rootCmd.Execute()

err := cmd.Execute()
if tc.expectedError != nil {
if err == nil || err.Error() != tc.expectedError.Error() {
t.Errorf("expected error: %v, got: %v", tc.expectedError, err)
}
} else if err != nil {
t.Errorf("expected no error, but got: %v", err)
}
assert.Equal(t, tc.expectedError, err, "error returned by scan should match")

if tc.expectedError == nil {
if tc.expectedScan != nil {
scans := &v1.ScanList{}
if listErr := client.List(context.Background(), scans); listErr != nil {
t.Fatalf("failed to list scans: %v", listErr)
}
if len(scans.Items) != 1 {
t.Fatalf("expected 1 scan to created but got %d", len(scans.Items))
}
}
listErr := client.List(context.Background(), scans)
assert.Nil(t, listErr, "failed to list scans")
assert.Len(t, scans.Items, 1, "expected 1 scan to be created")

scan := scans.Items[0]

assert.Equal(t, tc.expectedScan.name, scan.Name)
assert.Equal(t, tc.expectedScan.namespace, scan.Namespace)
assert.Equal(t, tc.expectedScan.scanType, scan.Spec.ScanType)
assert.Equal(t, tc.expectedScan.parameters, scan.Spec.Parameters)
}
})
}
}
Loading