Skip to content

feat: add initial implementation #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
rename directory
  • Loading branch information
sreya committed Jan 25, 2024
commit 27a7d2030d7999f08b3413b89860de0872b0e373
11 changes: 8 additions & 3 deletions jfrog/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ type Client struct {
user string
}

func XRayClient(url, user, token string) (*jfroghttpclient.JfrogHttpClient, error) {
func XRayClient(url, user, token string) (*Client, error) {
details := auth.NewXrayDetails()
details.SetAccessToken(token)
details.SetUser(user)
details.SetUrl("https://cdr.jfrog.io")
details.SetUrl(url)
conf, err := config.NewConfigBuilder().SetServiceDetails(details).Build()
if err != nil {
return nil, xerrors.Errorf("new config builder: %w", err)
Expand All @@ -35,7 +35,12 @@ func XRayClient(url, user, token string) (*jfroghttpclient.JfrogHttpClient, erro
if err != nil {
return nil, xerrors.Errorf("new xray manager: %w", err)
}
return mgr.Client(), nil
return &Client{
client: mgr.Client(),
baseURL: url,
user: user,
token: token,
}, nil
}

type securityResultsPayload struct {
Expand Down
38 changes: 27 additions & 11 deletions k8s/reporter.go → reporter/reporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import (
"github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/xray/jfrog"

"cdr.dev/slog"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"

"cdr.dev/slog"
)

type K8sReporter struct {
Expand All @@ -29,6 +30,7 @@ type K8sReporter struct {

ctx context.Context
podInformer cache.SharedIndexInformer
errChan chan error
}

type WorkspaceAgent struct {
Expand All @@ -38,6 +40,7 @@ type WorkspaceAgent struct {

func (k *K8sReporter) Init(ctx context.Context) error {
k.ctx = ctx
k.errChan = make(chan error)

podFactory := informers.NewSharedInformerFactoryWithOptions(k.Client, 0, informers.WithNamespace(k.Namespace), informers.WithTweakListOptions(func(lo *v1.ListOptions) {
lo.FieldSelector = k.FieldSelector
Expand All @@ -54,6 +57,9 @@ func (k *K8sReporter) Init(ctx context.Context) error {
return
}

log := k.Logger.With(
slog.F("pod_name", pod.Name),
)
var isWorkspace bool
for _, container := range pod.Spec.Containers {
var agentToken string
Expand All @@ -69,31 +75,37 @@ func (k *K8sReporter) Init(ctx context.Context) error {
continue
}

log = log.With(
slog.F("container_name", container.Name),
slog.F("container_image", container.Image),
)

image, err := jfrog.ParseImage(container.Image)
if err != nil {
k.Logger.Error(ctx, "parse image",
slog.F("pod_name", pod.Name),
slog.F("container_name", container.Name),
slog.F("container_image", container.Image),
slog.Error(err),
)
log.Error(ctx, "parse image", slog.Error(err))
return
}

scan, err := k.JFrogClient.ScanResults(image)
if err != nil {
k.Logger.Error(ctx, "fetch scan results", slog.Error(err))
log.Error(ctx, "fetch scan results", slog.Error(err))
return
}

agentClient := agentsdk.New(k.CoderURL)
agentClient.SetSessionToken(agentToken)
manifest, err := agentClient.Manifest(ctx)
if err != nil {
k.Logger.Error(ctx, "Get agent manifest", slog.Error(err))
log.Error(ctx, "Get agent manifest", slog.Error(err))
return
}

log = log.With(
slog.F("workspace_id", manifest.WorkspaceID),
slog.F("agent_id", manifest.AgentID),
slog.F("workspace_name", manifest.WorkspaceName),
)

cclient := codersdk.New(k.CoderURL)
cclient.SetSessionToken(k.CoderToken)
err = cclient.PostJFrogXrayScan(ctx, codersdk.JFrogXrayScan{
Expand All @@ -103,12 +115,12 @@ func (k *K8sReporter) Init(ctx context.Context) error {
High: scan.SecurityIssues.High,
})
if err != nil {
k.Logger.Error(ctx, "post xray results", slog.Error(err))
log.Error(ctx, "post xray results", slog.Error(err))
return
}
}
if isWorkspace {
k.Logger.Info(ctx, "uploaded workspace results!", slog.F("name", pod.Name), slog.F("namespace", pod.Namespace))
log.Info(ctx, "uploaded workspace results!", slog.F("pod_name", pod.Name), slog.F("namespace", pod.Namespace))
}
},
})
Expand All @@ -117,3 +129,7 @@ func (k *K8sReporter) Init(ctx context.Context) error {
}
return nil
}

func (k *K8sReporter) Start(stop chan struct{}) {
k.podInformer.Run(stop)
}
65 changes: 62 additions & 3 deletions root.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,81 @@
package main

import (
"fmt"
"net/url"
"os"

"github.com/spf13/cobra"
"golang.org/x/xerrors"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"

"github.com/coder/xray/jfrog"
)

func root() *cobra.Command {
var (
coderURL string
artifactoryURL string
artifactoryUser string
artifactoryToken string
fieldSelector string
kubeConfig string
namespace string
labelSelector string
artifactoryToken string
)
cmd := &cobra.Command{}
cmd.Flags().StringVarP(&coderURL, "coder-url", "u", os.Getenv("CODER_URL"), "URL of the Coder instance")
cmd := &cobra.Command{
Use: "scan",
Short: "Scan Coder Kubernetes workspace images for vulnerabilities",
RunE: func(cmd *cobra.Command, args []string) error {
if coderURL == "" {
return xerrors.New("--coder-url is required")
}

coderParsed, err := url.Parse(coderURL)
if err != nil {
return fmt.Errorf("parse coder URL: %w", err)
}

if artifactoryURL == "" {
return xerrors.New("--coder-url is required")
}

_, err = url.Parse(artifactoryURL)
if err != nil {
return fmt.Errorf("parse coder URL: %w", err)
}

if artifactoryUser == "" {
return xerrors.New("--artifactory-user is required")
}

if artifactoryToken == "" {
return xerrors.New("--artifactory-token is required")
}

config, err := clientcmd.BuildConfigFromFlags("", kubeConfig)
if err != nil {
return xerrors.Errorf("build kubeconfig: %w", err)
}

kclient, err := kubernetes.NewForConfig(config)
if err != nil {
return xerrors.Errorf("create kubernetes config: %w", err)
}

jClient, err := jfrog.XRayClient(artifactoryURL, artifactoryUser, artifactoryToken)
if err != nil {
return xerrors.Errorf("create artifactory client: %w", err)
}

return nil
},
}
cmd.Flags().StringVarP(&coderURL, "coder-url", "cu", os.Getenv("CODER_URL"), "URL of the Coder instance")
cmd.Flags().StringVarP(&artifactoryURL, "artifactory-url", "", os.Getenv("ARTIFACTORY_URL"), "URL of the JFrog Artifactory instance")
cmd.Flags().StringVarP(&artifactoryToken, "artifactory-token", "", os.Getenv("ARTIFACTORY_TOKEN"), "Access Token for JFrog Artifactory instance")
cmd.Flags().StringVarP(&artifactoryUser, "artifactory-user", "", os.Getenv("ARTIFACTORY_USER"), "User to interface with JFrog Artifactory instance")
cmd.Flags().StringVarP(&kubeConfig, "kubeconfig", "k", "~/.kube/config", "Path to the kubeconfig file")
cmd.Flags().StringVarP(&namespace, "namespace", "n", os.Getenv("CODER_NAMESPACE"), "Namespace to use when listing pods")
cmd.Flags().StringVarP(&fieldSelector, "field-selector", "f", "", "Field selector to use when listing pods")
Expand Down