Skip to content
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

feat: Provide credentials in imagePullSecret without global access #2161

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ go tool cover -html=coverage.txt

### Run Operator envtest

The Operator envtest spin us partial k8s components (api-server, etcd) and test controllers for reousce, workload, ttl, rbac and more
The Operator envtest spins up partial k8s components (api-server, etcd) and test controllers for resource, workload, ttl, rbac and more

```
mage test:envtest
Expand Down
1 change: 1 addition & 0 deletions deploy/helm/templates/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ rules:
- create
- get
- delete
- update
{{- end }}
57 changes: 29 additions & 28 deletions deploy/static/trivy-operator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3169,6 +3169,25 @@ spec:
app.kubernetes.io/instance: trivy-operator
type: ClusterIP
---
# Source: trivy-operator/templates/rbac/clusterrolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: trivy-operator
labels:
app.kubernetes.io/name: trivy-operator
app.kubernetes.io/instance: trivy-operator
app.kubernetes.io/version: "0.21.3"
app.kubernetes.io/managed-by: kubectl
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: trivy-operator
subjects:
- kind: ServiceAccount
name: trivy-operator
namespace: trivy-system
---
# Source: trivy-operator/templates/rbac/clusterrole.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
Expand Down Expand Up @@ -3537,20 +3556,21 @@ rules:
verbs:
- get
---
# Source: trivy-operator/templates/rbac/clusterrolebinding.yaml
# Source: trivy-operator/templates/rbac/leader-election-rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
kind: RoleBinding
metadata:
name: trivy-operator
name: trivy-operator-leader-election
namespace: trivy-system
labels:
app.kubernetes.io/name: trivy-operator
app.kubernetes.io/instance: trivy-operator
app.kubernetes.io/version: "0.21.3"
app.kubernetes.io/managed-by: kubectl
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: trivy-operator
kind: Role
name: trivy-operator-leader-election
subjects:
- kind: ServiceAccount
name: trivy-operator
Expand Down Expand Up @@ -3584,11 +3604,11 @@ rules:
verbs:
- create
---
# Source: trivy-operator/templates/rbac/leader-election-rolebinding.yaml
# Source: trivy-operator/templates/rbac/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: trivy-operator-leader-election
name: trivy-operator
namespace: trivy-system
labels:
app.kubernetes.io/name: trivy-operator
Expand All @@ -3598,7 +3618,7 @@ metadata:
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: trivy-operator-leader-election
name: trivy-operator
subjects:
- kind: ServiceAccount
name: trivy-operator
Expand Down Expand Up @@ -3633,26 +3653,7 @@ rules:
- create
- get
- delete
---
# Source: trivy-operator/templates/rbac/rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: trivy-operator
namespace: trivy-system
labels:
app.kubernetes.io/name: trivy-operator
app.kubernetes.io/instance: trivy-operator
app.kubernetes.io/version: "0.21.3"
app.kubernetes.io/managed-by: kubectl
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: trivy-operator
subjects:
- kind: ServiceAccount
name: trivy-operator
namespace: trivy-system
- update
---
# Source: trivy-operator/templates/rbac/view-configauditreports-clusterrole.yaml
# permissions for end users to view configauditreports
Expand Down
27 changes: 18 additions & 9 deletions pkg/kube/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ const (
type SecretsReader interface {
ListByLocalObjectReferences(ctx context.Context, refs []corev1.LocalObjectReference, ns string) ([]corev1.Secret, error)
ListImagePullSecretsByPodSpec(ctx context.Context, spec corev1.PodSpec, ns string) ([]corev1.Secret, error)
CredentialsByServer(ctx context.Context, workload client.Object, secretsInfo map[string]string, multiSecretSupport bool) (map[string]docker.Auth, error)
CredentialsByServer(ctx context.Context, workload client.Object, secretsInfo map[string]string, multiSecretSupport bool, globalAccessEnabled bool) (map[string]docker.Auth, error)
}

// NewSecretsReader constructs a new SecretsReader which is using the client
Expand Down Expand Up @@ -208,19 +208,28 @@ func (r *secretsReader) GetSecretsFromEnv(ctx context.Context, secretsInfo map[s
return secretsFromEnv, nil
}

func (r *secretsReader) CredentialsByServer(ctx context.Context, workload client.Object, secretsInfo map[string]string, multiSecretSupport bool) (map[string]docker.Auth, error) {
spec, err := GetPodSpec(workload)
if err != nil {
return nil, fmt.Errorf("getting Pod template: %w", err)
}
imagePullSecrets, err := r.ListImagePullSecretsByPodSpec(ctx, spec, workload.GetNamespace())
if err != nil {
return nil, err
func (r *secretsReader) CredentialsByServer(ctx context.Context, workload client.Object, secretsInfo map[string]string, multiSecretSupport bool, globalAccessEnabled bool) (map[string]docker.Auth, error) {
var imagePullSecrets []corev1.Secret

if globalAccessEnabled {
spec, err := GetPodSpec(workload)
if err != nil {
return nil, fmt.Errorf("getting Pod template: %w", err)
}

imagePullSecretsFromSpec, err := r.ListImagePullSecretsByPodSpec(ctx, spec, workload.GetNamespace())
if err != nil {
return nil, err
}

imagePullSecrets = append(imagePullSecrets, imagePullSecretsFromSpec...)
}

secretsFromEnv, err := r.GetSecretsFromEnv(ctx, secretsInfo)
if err != nil {
return nil, err
}

imagePullSecrets = append(imagePullSecrets, secretsFromEnv...)

return MapDockerRegistryServersToAuths(imagePullSecrets, multiSecretSupport)
Expand Down
75 changes: 72 additions & 3 deletions pkg/kube/secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/aquasecurity/trivy-operator/pkg/docker"
"github.com/aquasecurity/trivy-operator/pkg/kube"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestMapDockerRegistryServersToAuths(t *testing.T) {
Expand Down Expand Up @@ -360,15 +361,37 @@ func loadResource(filePath string, resource interface{}) error {
}

func Test_secretsReader_CredentialsByServer(t *testing.T) {
t.Run("Test with service account and secret with same registry domain should map container images to Docker authentication credentials from Pod secret", func(t *testing.T) {
t.Run("Test with no secrets or serviceaccounts configured and globalAccess disabled should not map any credentials", func(t *testing.T) {

client := fake.NewClientBuilder().WithScheme(trivyoperator.NewScheme()).Build()
sr := kube.NewSecretsReader(client)
pod := corev1.Pod{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Image: "quay.io/nginx:1.16",
},
},
},
}

auths, err := sr.CredentialsByServer(context.Background(), &pod, map[string]string{}, false, false)
require.NoError(t, err)
assert.Equal(t, 0, len(auths))
})

t.Run("Test with secrets configured but globalAccess disabled should map container images to Docker authentication credentials from serviceaccount secret", func(t *testing.T) {

var secret corev1.Secret
err := loadResource("./testdata/fixture/secret_same_domain.json", &secret)
require.NoError(t, err)
var imagePullSecret corev1.Secret
err = loadResource("./testdata/fixture/secret_same_domain_imagePullSecret.json", &imagePullSecret)
require.NoError(t, err)
var sa corev1.ServiceAccount
err = loadResource("./testdata/fixture/sa_with_image_pull_secret_same_domain.json", &sa)
require.NoError(t, err)
client := fake.NewClientBuilder().WithScheme(trivyoperator.NewScheme()).WithObjects(&secret).WithObjects(&sa).Build()
client := fake.NewClientBuilder().WithScheme(trivyoperator.NewScheme()).WithObjects(&secret).WithObjects(&sa).WithObjects(&imagePullSecret).Build()
sr := kube.NewSecretsReader(client)
pod := corev1.Pod{
Spec: corev1.PodSpec{
Expand All @@ -377,16 +400,62 @@ func Test_secretsReader_CredentialsByServer(t *testing.T) {
Image: "quay.io/nginx:1.16",
},
},
ImagePullSecrets: []corev1.LocalObjectReference{
{
Name: "private-regcred",
},
},
},
}

auths, err := sr.CredentialsByServer(context.Background(), &pod, map[string]string{
"default": "regcred",
}, false)
}, false, false)
require.NoError(t, err)
assert.Equal(t, 1, len(auths))
assert.Equal(t, map[string]docker.Auth{
"quay.io": {Auth: "dXNlcjpBZG1pbjEyMzQ1", Username: "user", Password: "Admin12345"},
}, auths)
})

t.Run("Test with secrets configured and globalAccess enabled should map container images to Docker authentication credentials from serviceaccount and imagePullSecret", func(t *testing.T) {

var secret corev1.Secret
err := loadResource("./testdata/fixture/secret_same_domain.json", &secret)
require.NoError(t, err)
var imagePullSecret corev1.Secret
err = loadResource("./testdata/fixture/secret_same_domain_imagePullSecret.json", &imagePullSecret)
require.NoError(t, err)
var sa corev1.ServiceAccount
err = loadResource("./testdata/fixture/sa_with_image_pull_secret_same_domain.json", &sa)
require.NoError(t, err)
client := fake.NewClientBuilder().WithScheme(trivyoperator.NewScheme()).WithObjects(&secret).WithObjects(&sa).WithObjects(&imagePullSecret).Build()
sr := kube.NewSecretsReader(client)
pod := corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Image: "quay.io/nginx:1.16",
},
},
ImagePullSecrets: []corev1.LocalObjectReference{
{
Name: "private-regcred",
},
},
},
}

auths, err := sr.CredentialsByServer(context.Background(), &pod, map[string]string{
"default": "regcred",
}, true, true)
require.NoError(t, err)
assert.Equal(t, 1, len(auths))
assert.Equal(t, map[string]docker.Auth{
"quay.io": {Auth: "", Username: "admin,user", Password: "Password12345,Admin12345"},
}, auths)
})
}
15 changes: 15 additions & 0 deletions pkg/kube/testdata/fixture/secret_same_domain_imagePullSecret.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"apiVersion": "v1",
"data": {
".dockerconfigjson": "eyJhdXRocyI6eyJxdWF5LmlvIjp7ImF1dGgiOiJZV1J0YVc0NlVHRnpjM2R2Y21ReE1qTTBOUT09In19fQ=="
},
"kind": "Secret",
"metadata": {
"creationTimestamp": "2022-07-10T08:36:58Z",
"name": "private-regcred",
"namespace": "default",
"resourceVersion": "317629",
"uid": "ab0f9c59-ee6d-4c2d-98f6-a6fac0e9a35d"
},
"type": "kubernetes.io/dockerconfigjson"
}
33 changes: 24 additions & 9 deletions pkg/vulnerabilityreport/controller/workload.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,19 +252,34 @@ func (r *WorkloadController) SubmitScanJob(ctx context.Context, owner client.Obj
"name", owner.GetName(), "namespace", owner.GetNamespace())
var err error
credentials := make(map[string]docker.Auth, 0)

privateRegistrySecrets, err := r.Config.GetPrivateRegistryScanSecretsNames()
if err != nil {
return err
}

pConfig, err := r.PluginContext.GetConfig()
if err != nil {
return err
}

multiSecretSupport := trivy.MultiSecretSupport(trivy.Config{PluginConfig: pConfig})

if r.AccessGlobalSecretsAndServiceAccount && len(reusedReports) == 0 {
privateRegistrySecrets, err := r.Config.GetPrivateRegistryScanSecretsNames()
if err != nil {
return err
}
pConfig, err := r.PluginContext.GetConfig()
// Global access is enabled - therefore imagePullSecrets from the podSpec can be used
credentials, err = r.CredentialsByServer(ctx, owner, privateRegistrySecrets, multiSecretSupport, true)
if err != nil {
return err
}
multiSecretSupport := trivy.MultiSecretSupport(trivy.Config{PluginConfig: pConfig})
credentials, err = r.CredentialsByServer(ctx, owner, privateRegistrySecrets, multiSecretSupport)
if err != nil {
return err
} else {
// Global access is disabled - check if privateRegistrySecrets references an imagePullSecret in the operator namespace
imagePullSecretName, ok := privateRegistrySecrets[r.Config.Namespace]
if ok {
// privateRegistrySecrets references an imagePullSecret in the operator namespace - use this for pulling private images
credentials, err = r.CredentialsByServer(ctx, owner, map[string]string{r.Config.Namespace: imagePullSecretName}, multiSecretSupport, false)
if err != nil {
return err
}
}
}

Expand Down
Loading