Skip to content

Commit

Permalink
test(cli): scanning images from private registries (#840)
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Pacak <[email protected]>
  • Loading branch information
danielpacak authored Dec 15, 2021
1 parent c24e5bc commit 85723da
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 31 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ jobs:
make itests-starboard
env:
KUBECONFIG: /home/runner/.kube/config
STARBOARD_CLI_LOG_LEVEL: "0"
STARBOARD_TEST_CLI_LOG_LEVEL: "0"
- name: Upload code coverage
uses: codecov/codecov-action@v2
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
make itests-starboard
env:
KUBECONFIG: /home/runner/.kube/config
STARBOARD_CLI_LOG_LEVEL: "0"
STARBOARD_TEST_CLI_LOG_LEVEL: "0"
itest-starboard-operator:
name: Run integration tests / Starboard Operator
needs:
Expand Down
122 changes: 116 additions & 6 deletions itest/helper/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import (
"time"

"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
"github.com/aquasecurity/starboard/pkg/docker"
"github.com/aquasecurity/starboard/pkg/kube"
"github.com/aquasecurity/starboard/pkg/kubebench"
"github.com/aquasecurity/starboard/pkg/starboard"
"github.com/caarlos0/env/v6"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -23,11 +25,23 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
)

type PrivateRegistryConfig struct {
Server string `env:"STARBOARD_TEST_REGISTRY_SERVER"`
Username string `env:"STARBOARD_TEST_REGISTRY_USERNAME"`
Password string `env:"STARBOARD_TEST_REGISTRY_PASSWORD"`
ImageRef string `env:"STARBOARD_TEST_REGISTRY_PRIVATE_IMAGE_REF"`
}

func (c *PrivateRegistryConfig) Parse() error {
return env.Parse(c)
}

type PodBuilder struct {
name string
namespace string
containers []corev1.Container
imagePullSecrets []corev1.LocalObjectReference
name string
namespace string
containers []corev1.Container
serviceAccountName string
imagePullSecrets []corev1.LocalObjectReference
}

func NewPod() *PodBuilder {
Expand Down Expand Up @@ -56,6 +70,11 @@ func (b *PodBuilder) WithContainer(name, image string) *PodBuilder {
return b
}

func (b *PodBuilder) WithServiceAccountName(name string) *PodBuilder {
b.serviceAccountName = name
return b
}

func (b *PodBuilder) WithImagePullSecret(name string) *PodBuilder {
b.imagePullSecrets = append(b.imagePullSecrets, corev1.LocalObjectReference{
Name: name,
Expand All @@ -70,10 +89,101 @@ func (b *PodBuilder) Build() *corev1.Pod {
Namespace: b.namespace,
},
Spec: corev1.PodSpec{
Containers: b.containers,
ImagePullSecrets: b.imagePullSecrets,
ServiceAccountName: b.serviceAccountName,
Containers: b.containers,
ImagePullSecrets: b.imagePullSecrets,
},
}
}

type ServiceAccountBuilder struct {
target *corev1.ServiceAccount
}

func NewServiceAccount() *ServiceAccountBuilder {
return &ServiceAccountBuilder{
target: &corev1.ServiceAccount{},
}
}

func (b *ServiceAccountBuilder) WithRandomName(prefix string) *ServiceAccountBuilder {
b.target.Name = prefix + "-" + rand.String(5)
return b
}

func (b *ServiceAccountBuilder) WithNamespace(namespace string) *ServiceAccountBuilder {
b.target.Namespace = namespace
return b
}

func (b *ServiceAccountBuilder) WithImagePullSecret(name string) *ServiceAccountBuilder {
b.target.ImagePullSecrets = append(b.target.ImagePullSecrets, corev1.LocalObjectReference{Name: name})
return b
}

func (b *ServiceAccountBuilder) Build() *corev1.ServiceAccount {
return b.target
}

type SecretBuilder struct {
target *corev1.Secret

registryServer string
registryUsername string
registryPassword string
}

func NewDockerRegistrySecret() *SecretBuilder {
return &SecretBuilder{
target: &corev1.Secret{},
}
}

func (b *SecretBuilder) WithRandomName(name string) *SecretBuilder {
b.target.Name = name + "-" + rand.String(5)
return b
}

func (b *SecretBuilder) WithNamespace(namespace string) *SecretBuilder {
b.target.Namespace = namespace
return b
}

func (b *SecretBuilder) WithServer(server string) *SecretBuilder {
b.registryServer = server
return b
}

func (b *SecretBuilder) WithUsername(username string) *SecretBuilder {
b.registryUsername = username
return b
}

func (b *SecretBuilder) WithPassword(password string) *SecretBuilder {
b.registryPassword = password
return b
}

func (b *SecretBuilder) Build() (*corev1.Secret, error) {
dockerConfig, err := docker.Config{
Auths: map[string]docker.Auth{
b.registryServer: {
Username: b.registryUsername,
Password: b.registryPassword,
Auth: docker.NewBasicAuth(b.registryUsername, b.registryPassword),
},
},
}.Write()
if err != nil {
return nil, err
}

b.target.Type = corev1.SecretTypeDockerConfigJson
b.target.Data = map[string][]byte{
corev1.DockerConfigJsonKey: dockerConfig,
}

return b.target, nil
}

type DeploymentBuilder struct {
Expand Down
128 changes: 106 additions & 22 deletions itest/starboard/starboard_cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
. "github.com/onsi/gomega/gstruct"

"context"
"os"
"strings"
"time"

Expand Down Expand Up @@ -48,9 +47,11 @@ var _ = Describe("Starboard CLI", func() {

Describe("Command install", func() {

It("should initialize Starboard", func() {
It("should install Starboard", func() {

crdList, err := customResourceDefinitions.List(context.TODO(), metav1.ListOptions{})
crdList, err := customResourceDefinitions.List(context.TODO(), metav1.ListOptions{
LabelSelector: "app.kubernetes.io/managed-by=starboard",
})
Expect(err).ToNot(HaveOccurred())

groupByName := func(element interface{}) string {
Expand Down Expand Up @@ -204,7 +205,7 @@ var _ = Describe("Starboard CLI", func() {

BeforeEach(func() {
ctx = context.TODO()
pod = helper.NewPod().WithName("nginx").
pod = helper.NewPod().WithRandomName("nginx").
WithNamespace(testNamespace.Name).
WithContainer("nginx-container", "nginx:1.16").
Build()
Expand Down Expand Up @@ -284,33 +285,113 @@ var _ = Describe("Starboard CLI", func() {

})

// TODO Run with other integration tests
// The only reason this test is marked as pending is that I don't know
// how to pass DockerHub private repository credentials to this test case.
// TODO Run pending specs with other tests used to validate each PR.
//
// The only reason these tests are marked as pending is that I don't know how to pass private repository
// credentials from GitHub Actions runner via STARBOARD_TEST_REGISTRY_USERNAME and STARBOARD_TEST_REGISTRY_PASSWORD
// environment variables down to Go tests. The main challenge is that GitHub secrets are not available to
// workflow runs initiated from forked repositories. In other words, expressions like
// ${{ secrets.STARBOARD_TEST_REGISTRY_PASSWORD }} will evaluate to a blank string.
PContext("when unmanaged Pod with private image is specified as workload", func() {

var pod *corev1.Pod
var ctx context.Context
var imagePullSecret *corev1.Secret
var pod *corev1.Pod

BeforeEach(func() {
var err error
imagePullSecret, err = kube.NewImagePullSecret(metav1.ObjectMeta{
Name: "registry-credentials",
Namespace: testNamespace.Name,
}, "https://index.docker.io/v1",
os.Getenv("STARBOARD_TEST_DOCKERHUB_REGISTRY_USERNAME"),
os.Getenv("STARBOARD_TEST_DOCKERHUB_REGISTRY_PASSWORD"))

ctx = context.TODO()

imagePullSecret, err = helper.NewDockerRegistrySecret().
WithRandomName("regcred").
WithNamespace(testNamespace.Name).
WithServer(privateRegistryConfig.Server).
WithUsername(privateRegistryConfig.Username).
WithPassword(privateRegistryConfig.Password).
Build()
Expect(err).ToNot(HaveOccurred())
err = kubeClient.Create(ctx, imagePullSecret)
Expect(err).ToNot(HaveOccurred())

pod = helper.NewPod().WithRandomName("private-pod").
WithNamespace(testNamespace.Name).
WithContainer("private", privateRegistryConfig.ImageRef).
WithImagePullSecret(imagePullSecret.Name).
Build()
err = kubeClient.Create(ctx, pod)
Expect(err).ToNot(HaveOccurred())
})

It("should create VulnerabilityReport", func() {
err := cmd.Run(versionInfo, []string{
"starboard",
"scan", "vulnerabilityreports", "po/" + pod.Name,
"--namespace", pod.Namespace,
"-v", starboardCLILogLevel,
}, GinkgoWriter, GinkgoWriter)
Expect(err).ToNot(HaveOccurred())

err = kubeClient.Create(context.TODO(), imagePullSecret)
var reportList v1alpha1.VulnerabilityReportList
err = kubeClient.List(ctx, &reportList, client.MatchingLabels{
starboard.LabelResourceKind: string(kube.KindPod),
starboard.LabelResourceName: pod.Name,
starboard.LabelResourceNamespace: pod.Namespace,
})
Expect(err).ToNot(HaveOccurred())
Expect(reportList.Items).To(MatchAllElements(groupByContainerName, Elements{
"private": IsVulnerabilityReportForContainerOwnedBy("private", pod),
}))
})

pod = helper.NewPod().WithName("nginx-with-private-image").
AfterEach(func() {
err := kubeClient.Delete(ctx, pod)
Expect(err).ToNot(HaveOccurred())

err = kubeClient.Delete(ctx, imagePullSecret)
Expect(err).ToNot(HaveOccurred())
})

})

PContext("when unmanaged Pod with private image and service account is specified as workload", func() {

var ctx context.Context
var imagePullSecret *corev1.Secret
var serviceAccount *corev1.ServiceAccount
var pod *corev1.Pod

BeforeEach(func() {
var err error

ctx = context.TODO()

imagePullSecret, err = helper.NewDockerRegistrySecret().
WithRandomName("regcred").
WithNamespace(testNamespace.Name).
WithServer(privateRegistryConfig.Server).
WithUsername(privateRegistryConfig.Username).
WithPassword(privateRegistryConfig.Password).
Build()
Expect(err).ToNot(HaveOccurred())
err = kubeClient.Create(ctx, imagePullSecret)
Expect(err).ToNot(HaveOccurred())

serviceAccount = helper.NewServiceAccount().
WithRandomName("test-sa").
WithNamespace(testNamespace.Name).
WithContainer("nginx-container", "starboardcicd/private-nginx:1.16").
WithImagePullSecret(imagePullSecret.Name).
Build()
err = kubeClient.Create(context.TODO(), pod)
err = kubeClient.Create(ctx, serviceAccount)
Expect(err).ToNot(HaveOccurred())

pod = helper.NewPod().
WithRandomName("private-pod").
WithNamespace(testNamespace.Name).
WithContainer("private", privateRegistryConfig.ImageRef).
WithServiceAccountName(serviceAccount.Name).
Build()
err = kubeClient.Create(ctx, pod)
Expect(err).ToNot(HaveOccurred())
})

Expand All @@ -324,22 +405,25 @@ var _ = Describe("Starboard CLI", func() {
Expect(err).ToNot(HaveOccurred())

var reportList v1alpha1.VulnerabilityReportList
err = kubeClient.List(context.TODO(), &reportList, client.MatchingLabels{
err = kubeClient.List(ctx, &reportList, client.MatchingLabels{
starboard.LabelResourceKind: string(kube.KindPod),
starboard.LabelResourceName: pod.Name,
starboard.LabelResourceNamespace: pod.Namespace,
})
Expect(err).ToNot(HaveOccurred())
Expect(reportList.Items).To(MatchAllElements(groupByContainerName, Elements{
"nginx-container": IsVulnerabilityReportForContainerOwnedBy("nginx-container", pod),
"private": IsVulnerabilityReportForContainerOwnedBy("private", pod),
}))
})

AfterEach(func() {
err := kubeClient.Delete(context.TODO(), pod)
err := kubeClient.Delete(ctx, pod)
Expect(err).ToNot(HaveOccurred())

err = kubeClient.Delete(ctx, serviceAccount)
Expect(err).ToNot(HaveOccurred())

err = kubeClient.Delete(context.TODO(), imagePullSecret)
err = kubeClient.Delete(ctx, imagePullSecret)
Expect(err).ToNot(HaveOccurred())
})

Expand Down
6 changes: 5 additions & 1 deletion itest/starboard/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ var (
Name: "starboard-itest",
},
}
privateRegistryConfig = &helper.PrivateRegistryConfig{}

conftestConfigMap = &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Expand Down Expand Up @@ -93,10 +94,13 @@ var _ = BeforeSuite(func() {

klog.InitFlags(nil)

if logLevel, ok := os.LookupEnv("STARBOARD_CLI_LOG_LEVEL"); ok {
if logLevel, ok := os.LookupEnv("STARBOARD_TEST_CLI_LOG_LEVEL"); ok {
starboardCLILogLevel = logLevel
}

err = privateRegistryConfig.Parse()
Expect(err).ToNot(HaveOccurred())

config, err := clientcmd.BuildConfigFromFlags("", os.Getenv("KUBECONFIG"))
Expect(err).ToNot(HaveOccurred())

Expand Down

0 comments on commit 85723da

Please sign in to comment.