Repository: deislabs/ratify
Pull request #1319 (2024-03-01 01:44:54)

feat: add debug logs to k8s secret and docker config auth providers
# Description

## What this PR does / why we need it:
Adds extra debug logging in k8s secrets auth provider and docker config auth provider when attempting hostname parsing and matching.

## Which issue(s) this PR fixes *(optional, using `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when the PR gets merged)*:
Fixes #

## Type of change

Please delete options that are not relevant.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Helm Chart Change (any edit/addition/update that is necessary for changes merged to the `main` branch)
- [ ] This change requires a documentation update

# How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Please also list any relevant details for your test configuration

- [ ] Test A
- [ ] Test B

# Checklist:

- [ ] Does the affected code have corresponding tests?
- [ ] Are the changes documented, not just with inline documentation, but also with conceptual documentation such as an overview of a new feature, or task-based documentation like a tutorial? Consider if this change should be announced on your project blog.
- [ ] Does this introduce breaking changes that would require an announcement or bumping the major version?
- [ ] Do all new files have appropriate license header?

# Post Merge Requirements
- [ ] MAINTAINERS: manually trigger the "Publish Package" workflow after merging any PR that indicates `Helm Chart Change`


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/deislabs/ratify/pull/1319?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deislabs) Report
Attention: Patch coverage is `28.57143%` with `20 lines` in your changes are missing coverage. Please review.
> Project coverage is 64.79%. Comparing base [(`45c0f81`)](https://app.codecov.io/gh/deislabs/ratify/commit/45c0f81314ea5d26ec69672def593a6a8f464f1c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deislabs) to head [(`61ca120`)](https://app.codecov.io/gh/deislabs/ratify/pull/1319?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deislabs).
> Report is 14 commits behind head on main.

| [Files](https://app.codecov.io/gh/deislabs/ratify/pull/1319?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deislabs) | Patch % | Lines |
|---|---|---|
| [.../common/oras/authprovider/k8secret\_authprovider.go](https://app.codecov.io/gh/deislabs/ratify/pull/1319?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deislabs#diff-cGtnL2NvbW1vbi9vcmFzL2F1dGhwcm92aWRlci9rOHNlY3JldF9hdXRocHJvdmlkZXIuZ28=) | 31.57% | [13 Missing :warning: ](https://app.codecov.io/gh/deislabs/ratify/pull/1319?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deislabs) |
| [pkg/common/oras/authprovider/authprovider.go](https://app.codecov.io/gh/deislabs/ratify/pull/1319?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deislabs#diff-cGtnL2NvbW1vbi9vcmFzL2F1dGhwcm92aWRlci9hdXRocHJvdmlkZXIuZ28=) | 22.22% | [6 Missing and 1 partial :warning: ](https://app.codecov.io/gh/deislabs/ratify/pull/1319?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deislabs) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1319      +/-   ##
==========================================
+ Coverage   61.60%   64.79%   +3.19%     
==========================================
  Files          98       80      -18     
  Lines        6248     4230    -2018     
==========================================
- Hits         3849     2741    -1108     
+ Misses       2066     1227     -839     
+ Partials      333      262      -71     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/deislabs/ratify/pull/1319?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deislabs).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=deislabs).


Patch:
diff --git a/go.mod b/go.mod
index e1388f8..a48836f 100644
--- a/go.mod
+++ b/go.mod
@@ -90,6 +90,7 @@ require (
 	github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect
 	github.com/dimchansky/utfbom v1.1.1 // indirect
 	github.com/emicklei/go-restful/v3 v3.11.0 // indirect
+	github.com/evanphx/json-patch v4.12.0+incompatible // indirect
 	github.com/evanphx/json-patch/v5 v5.6.0 // indirect
 	github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
 	github.com/go-ini/ini v1.67.0 // indirect
diff --git a/pkg/common/oras/authprovider/authprovider.go b/pkg/common/oras/authprovider/authprovider.go
index 053185c..6d08941 100644
--- a/pkg/common/oras/authprovider/authprovider.go
+++ b/pkg/common/oras/authprovider/authprovider.go
@@ -25,6 +25,7 @@ import (
 	"time"
 
 	re "github.com/deislabs/ratify/errors"
+	"github.com/deislabs/ratify/internal/logger"
 	"github.com/docker/cli/cli/config"
 	"github.com/docker/cli/cli/config/configfile"
 	"github.com/docker/cli/cli/config/types"
@@ -63,6 +64,8 @@ type defaultAuthProviderConf struct {
 const DefaultAuthProviderName string = "dockerConfig"
 const DefaultDockerAuthTTL = 1 * time.Hour
 
+var logOpt = logger.Option{ComponentType: logger.AuthProvider}
+
 // init calls Register for our default provider, which simply reads the .dockercfg file.
 func init() {
 	Register(DefaultAuthProviderName, &defaultProviderFactory{})
@@ -96,7 +99,7 @@ func (d *defaultAuthProvider) Enabled(_ context.Context) bool {
 }
 
 // Provide reads docker config file and returns corresponding credentials from file if exists
-func (d *defaultAuthProvider) Provide(_ context.Context, artifact string) (AuthConfig, error) {
+func (d *defaultAuthProvider) Provide(ctx context.Context, artifact string) (AuthConfig, error) {
 	// load docker config file at default path if config file path not specified
 	var cfg *configfile.ConfigFile
 	if d.configPath == "" {
@@ -124,7 +127,16 @@ func (d *defaultAuthProvider) Provide(_ context.Context, artifact string) (AuthC
 		return AuthConfig{}, re.ErrorCodeHostNameInvalid.WithError(err).WithComponentType(re.AuthProvider)
 	}
 
-	dockerAuthConfig := cfg.AuthConfigs[artifactHostName]
+	dockerAuthConfig, exists := cfg.AuthConfigs[artifactHostName]
+	if !exists {
+		logger.GetLogger(ctx, logOpt).Debugf("no credentials found for registry hostname: %s", artifactHostName)
+		hostnames := []string{}
+		for k := range cfg.AuthConfigs {
+			hostnames = append(hostnames, k)
+		}
+		logger.GetLogger(ctx, logOpt).Debugf("list of registry host names in config : %v", hostnames)
+		return AuthConfig{}, nil
+	}
 	if dockerAuthConfig == (types.AuthConfig{}) {
 		return AuthConfig{}, nil
 	}
diff --git a/pkg/common/oras/authprovider/k8secret_authprovider.go b/pkg/common/oras/authprovider/k8secret_authprovider.go
index 2c4a71c..10fd746 100644
--- a/pkg/common/oras/authprovider/k8secret_authprovider.go
+++ b/pkg/common/oras/authprovider/k8secret_authprovider.go
@@ -25,10 +25,12 @@ import (
 	"time"
 
 	re "github.com/deislabs/ratify/errors"
+	"github.com/deislabs/ratify/internal/logger"
 	"github.com/deislabs/ratify/pkg/utils"
 
 	"github.com/docker/cli/cli/config"
 	core "k8s.io/api/core/v1"
+	e "k8s.io/apimachinery/pkg/api/errors"
 	meta "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
 	"k8s.io/client-go/rest"
@@ -38,7 +40,7 @@ type k8SecretProviderFactory struct{}
 type k8SecretAuthProvider struct {
 	ratifyNamespace  string
 	config           k8SecretAuthProviderConf
-	clusterClientSet *kubernetes.Clientset
+	clusterClientSet kubernetes.Interface
 }
 
 type secretConfig struct {
@@ -121,6 +123,7 @@ func (d *k8SecretAuthProvider) Provide(ctx context.Context, artifact string) (Au
 		return AuthConfig{}, re.ErrorCodeHostNameInvalid.WithError(err).WithComponentType(re.AuthProvider)
 	}
 
+	logger.GetLogger(ctx, logOpt).Debugf("attempting to resolve credentials for registry hostname: %s", hostName)
 	// iterate through config secrets, resolve each secret, and store in map
 	for _, k8secret := range d.config.Secrets {
 		// default value of secret is assumed to be ratify namespace
@@ -129,13 +132,16 @@ func (d *k8SecretAuthProvider) Provide(ctx context.Context, artifact string) (Au
 		}
 
 		secret, err := d.clusterClientSet.CoreV1().Secrets(k8secret.Namespace).Get(ctx, k8secret.SecretName, meta.GetOptions{})
-		if err != nil {
+		if e.IsNotFound(err) {
+			logger.GetLogger(ctx, logOpt).Debugf("secret %s not found in namespace %s", k8secret.SecretName, k8secret.Namespace)
+			continue
+		} else if err != nil {
 			return AuthConfig{}, re.ErrorCodeGetClusterResourceFailure.NewError(re.AuthProvider, "", re.EmptyLink, err, fmt.Sprintf("failed to pull secret %s from cluster.", k8secret.SecretName), re.HideStackTrace)
 		}
 
 		// only docker config json secret type allowed
 		if secret.Type == core.SecretTypeDockerConfigJson {
-			authConfig, err := d.resolveCredentialFromSecret(hostName, secret)
+			authConfig, err := d.resolveCredentialFromSecret(ctx, hostName, secret)
 			if err != nil && !errors.Is(err, re.ErrorCodeNoMatchingCredential) {
 				return AuthConfig{}, err
 			}
@@ -157,13 +163,16 @@ func (d *k8SecretAuthProvider) Provide(ctx context.Context, artifact string) (Au
 	// extract the imagePullSecrets linked to service account
 	for _, imagePullSecret := range serviceAccount.ImagePullSecrets {
 		secret, err := d.clusterClientSet.CoreV1().Secrets(d.ratifyNamespace).Get(ctx, imagePullSecret.Name, meta.GetOptions{})
-		if err != nil {
+		if e.IsNotFound(err) {
+			logger.GetLogger(ctx, logOpt).Debugf("image pull secret %s not found in namespace %s", imagePullSecret.Name, d.ratifyNamespace)
+			continue
+		} else if err != nil {
 			return AuthConfig{}, re.ErrorCodeGetClusterResourceFailure.WithError(err).WithComponentType(re.AuthProvider)
 		}
 
 		// only dockercfg or docker config json secret type allowed
 		if secret.Type == core.SecretTypeDockercfg || secret.Type == core.SecretTypeDockerConfigJson {
-			authConfig, err := d.resolveCredentialFromSecret(hostName, secret)
+			authConfig, err := d.resolveCredentialFromSecret(ctx, hostName, secret)
 			if err != nil && !errors.Is(err, re.ErrorCodeNoMatchingCredential) {
 				return AuthConfig{}, err
 			}
@@ -171,13 +180,15 @@ func (d *k8SecretAuthProvider) Provide(ctx context.Context, artifact string) (Au
 			if err == nil {
 				return authConfig, nil
 			}
+		} else {
+			logger.GetLogger(ctx, logOpt).Debugf("image pull secret %s of type %s not supported", imagePullSecret.Name, secret.Type)
 		}
 	}
 
 	return AuthConfig{}, fmt.Errorf("could not find credentials for %s", artifact)
 }
 
-func (d *k8SecretAuthProvider) resolveCredentialFromSecret(hostName string, secret *core.Secret) (AuthConfig, error) {
+func (d *k8SecretAuthProvider) resolveCredentialFromSecret(ctx context.Context, hostName string, secret *core.Secret) (AuthConfig, error) {
 	dockercfg, exists := secret.Data[core.DockerConfigJsonKey]
 	if !exists {
 		return AuthConfig{}, re.ErrorCodeConfigInvalid.WithDetail("could not extract auth configs from docker config")
@@ -190,6 +201,12 @@ func (d *k8SecretAuthProvider) resolveCredentialFromSecret(hostName string, secr
 
 	authConfig, exist := configFile.AuthConfigs[hostName]
 	if !exist {
+		logger.GetLogger(ctx, logOpt).Debugf("host name %s not found in image pull secret %s", hostName, secret.Name)
+		hostnames := []string{}
+		for k := range configFile.AuthConfigs {
+			hostnames = append(hostnames, k)
+		}
+		logger.GetLogger(ctx, logOpt).Debugf("list of host names in image pull secret %s: %v", secret.Name, hostnames)
 		return AuthConfig{}, re.ErrorCodeNoMatchingCredential
 	}
 


Test patch:
diff --git a/pkg/common/oras/authprovider/k8secret_authprovider_test.go b/pkg/common/oras/authprovider/k8secret_authprovider_test.go
index c925b31..52be795 100644
--- a/pkg/common/oras/authprovider/k8secret_authprovider_test.go
+++ b/pkg/common/oras/authprovider/k8secret_authprovider_test.go
@@ -16,16 +16,19 @@ limitations under the License.
 package authprovider
 
 import (
+	"context"
 	"errors"
 	"testing"
 
 	ratifyerrors "github.com/deislabs/ratify/errors"
 	core "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes/fake"
 )
 
-// Checks K8s Docker Json Config Secret is properly extracted and
-// credentials returned when Provide is called
-func TestProvide_K8SecretDockerConfigJson_ReturnsExpected(t *testing.T) {
+// TestResolveCredentialFromSecret_K8SecretDockerConfigJson_ReturnsExpected checks
+// K8s Docker Json Config Secret is properly extracted and credentials returned when Provide is called
+func TestResolveCredentialFromSecret_K8SecretDockerConfigJson_ReturnsExpected(t *testing.T) {
 	var testSecret core.Secret
 	testSecret.Data = make(map[string][]byte)
 	testSecret.Data[core.DockerConfigJsonKey] = []byte(secretContent)
@@ -33,7 +36,7 @@ func TestProvide_K8SecretDockerConfigJson_ReturnsExpected(t *testing.T) {
 
 	var k8secretprovider k8SecretAuthProvider
 
-	authConfig, err := k8secretprovider.resolveCredentialFromSecret("index.docker.io", &testSecret)
+	authConfig, err := k8secretprovider.resolveCredentialFromSecret(context.Background(), "index.docker.io", &testSecret)
 	if err != nil {
 		t.Fatalf("resolveCredentialFromSecret failed to get credential with err %v", err)
 	}
@@ -43,7 +46,9 @@ func TestProvide_K8SecretDockerConfigJson_ReturnsExpected(t *testing.T) {
 	}
 }
 
-func TestProvide_K8SecretDockerConfigJsonWithIdentityToken_ReturnsExpected(t *testing.T) {
+// TestResolveCredentialFromSecret_K8SecretDockerConfigJsonWithIdentityToken_ReturnsExpected checks
+// K8s Docker Json Config Secret is properly extracted and credentials returned when Provide is called with an identity token
+func TestResolveCredentialFromSecret_K8SecretDockerConfigJsonWithIdentityToken_ReturnsExpected(t *testing.T) {
 	var testSecret core.Secret
 	testSecret.Data = make(map[string][]byte)
 	testSecret.Data[core.DockerConfigJsonKey] = []byte(secretContentIdentityToken)
@@ -51,7 +56,7 @@ func TestProvide_K8SecretDockerConfigJsonWithIdentityToken_ReturnsExpected(t *te
 
 	var k8secretprovider k8SecretAuthProvider
 
-	authConfig, err := k8secretprovider.resolveCredentialFromSecret("index.docker.io", &testSecret)
+	authConfig, err := k8secretprovider.resolveCredentialFromSecret(context.Background(), "index.docker.io", &testSecret)
 	if err != nil {
 		t.Fatalf("resolveCredentialFromSecret failed to get credential with err %v", err)
 	}
@@ -62,7 +67,7 @@ func TestProvide_K8SecretDockerConfigJsonWithIdentityToken_ReturnsExpected(t *te
 }
 
 // Checks an error is returned for non-existent registry credential
-func TestProvide_K8SecretNonExistentRegistry_ReturnsExpected(t *testing.T) {
+func TestResolveCredentialFromSecret_K8SecretNonExistentRegistry_ReturnsExpected(t *testing.T) {
 	var testSecret core.Secret
 	testSecret.Data = make(map[string][]byte)
 	testSecret.Data[core.DockerConfigJsonKey] = []byte(secretContent)
@@ -70,7 +75,288 @@ func TestProvide_K8SecretNonExistentRegistry_ReturnsExpected(t *testing.T) {
 
 	var k8secretprovider k8SecretAuthProvider
 
-	if _, err := k8secretprovider.resolveCredentialFromSecret("nonexistent.ghcr.io", &testSecret); !errors.Is(err, ratifyerrors.ErrorCodeNoMatchingCredential) {
+	if _, err := k8secretprovider.resolveCredentialFromSecret(context.Background(), "nonexistent.ghcr.io", &testSecret); !errors.Is(err, ratifyerrors.ErrorCodeNoMatchingCredential) {
 		t.Fatalf("resolveCredentialFromSecret should have failed to get credential with err %v but returned err %v", ratifyerrors.ErrorCodeNoMatchingCredential, err)
 	}
 }
+
+// TestProvide_NotEnabled_ReturnsExpected tests that the Provide method
+// returns an error when the k8SecretAuthProvider is not enabled
+func TestProvide_NotEnabled_ReturnsExpected(t *testing.T) {
+	var k8secretprovider k8SecretAuthProvider
+
+	if _, err := k8secretprovider.Provide(context.Background(), "nonexistent.ghcr.io/artifact:v1"); err == nil {
+		t.Fatalf("Provide should have failed to get credential with err but returned nil")
+	}
+}
+
+// TestProvide_InvalidHostName_ReturnsExpected tests that the Provide method
+// returns an error when the hostname is invalid
+func TestProvide_InvalidHostName_ReturnsExpected(t *testing.T) {
+	k8secretprovider := k8SecretAuthProvider{
+		ratifyNamespace:  "gatekeeper-system",
+		clusterClientSet: fake.NewSimpleClientset(),
+	}
+
+	if _, err := k8secretprovider.Provide(context.Background(), "badhostname/artifact:v1"); err == nil {
+		t.Fatalf("Provide should have failed to get credential with err but returned nil")
+	}
+}
+
+// TestProvide_SecretNotFound_ReturnsExpected tests that the Provide method
+// returns an error when the secret is not found
+func TestProvider_SecretNotFound_ReturnsExpected(t *testing.T) {
+	k8secretprovider := k8SecretAuthProvider{
+		ratifyNamespace: "gatekeeper-system",
+		clusterClientSet: fake.NewSimpleClientset(&core.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "non-matching-secret",
+				Namespace: "gatekeeper-system",
+			},
+		}),
+		config: k8SecretAuthProviderConf{
+			Secrets: []secretConfig{
+				{
+					SecretName: "test-secret",
+				},
+			},
+		},
+	}
+
+	if _, err := k8secretprovider.Provide(context.Background(), "ghcr.io/artifact:v1"); err == nil {
+		t.Fatalf("Provide should have failed to get credential with err but returned nil")
+	}
+}
+
+// TestProvide_SecretIncorrectType_ReturnsExpected tests that the Provide method
+// returns an error when the secret type is incorrect
+func TestProvider_IncorrectSecretType_ReturnsExpected(t *testing.T) {
+	k8secretprovider := k8SecretAuthProvider{
+		ratifyNamespace: "gatekeeper-system",
+		clusterClientSet: fake.NewSimpleClientset(&core.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "test-secret",
+				Namespace: "gatekeeper-system",
+			},
+			Type: core.SecretTypeBasicAuth,
+		}),
+		config: k8SecretAuthProviderConf{
+			Secrets: []secretConfig{
+				{
+					SecretName: "test-secret",
+				},
+			},
+		},
+	}
+
+	if _, err := k8secretprovider.Provide(context.Background(), "ghcr.io/artifact:v1"); err == nil {
+		t.Fatalf("Provide should have failed to get credential with err but returned nil")
+	}
+}
+
+// TestProvide_NoMatchingCredential_ReturnsExpected tests that the Provide method
+// returns an error when no matching credential is found
+func TestProvider_NoMatchingCredential_ReturnsExpected(t *testing.T) {
+	k8secretprovider := k8SecretAuthProvider{
+		ratifyNamespace: "gatekeeper-system",
+		clusterClientSet: fake.NewSimpleClientset(&core.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "test-secret",
+				Namespace: "gatekeeper-system",
+			},
+			Type: core.SecretTypeDockerConfigJson,
+			Data: map[string][]byte{
+				core.DockerConfigJsonKey: []byte(secretContent),
+			},
+		}),
+		config: k8SecretAuthProviderConf{
+			Secrets: []secretConfig{
+				{
+					SecretName: "test-secret",
+				},
+			},
+		},
+	}
+
+	if _, err := k8secretprovider.Provide(context.Background(), "ghcr.io/artifact:v1"); err == nil {
+		t.Fatalf("Provide should have failed to get credential with err but returned nil")
+	}
+}
+
+// TestProvide_ServiceAccountNotFound_ReturnsExpected tests that the Provide method
+// returns an error when the service account has no image pull secrets
+func TestProvider_ServiceAccountNoSecrets_ReturnsExpected(t *testing.T) {
+	k8secretprovider := k8SecretAuthProvider{
+		ratifyNamespace: "gatekeeper-system",
+		clusterClientSet: fake.NewSimpleClientset(&core.ServiceAccount{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "ratify-admin",
+				Namespace: "gatekeeper-system",
+			},
+		}),
+		config: k8SecretAuthProviderConf{
+			ServiceAccountName: "ratify-admin",
+		},
+	}
+
+	if _, err := k8secretprovider.Provide(context.Background(), "ghcr.io/artifact:v1"); err == nil {
+		t.Fatalf("Provide should have failed to get credential with err but returned nil")
+	}
+}
+
+// TestProvide_ServiceAccountSecretNotFound_ReturnsExpected tests that the Provide method
+// returns an error when the service account refers to a secret that does not exist
+func TestProvider_ServiceAccountSecretNotFound_ReturnsExpected(t *testing.T) {
+	k8secretprovider := k8SecretAuthProvider{
+		ratifyNamespace: "gatekeeper-system",
+		clusterClientSet: fake.NewSimpleClientset(&core.ServiceAccount{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "ratify-admin",
+				Namespace: "gatekeeper-system",
+			},
+			ImagePullSecrets: []core.LocalObjectReference{
+				{
+					Name: "non-existent-secret",
+				},
+			},
+		}),
+		config: k8SecretAuthProviderConf{
+			ServiceAccountName: "ratify-admin",
+		},
+	}
+
+	if _, err := k8secretprovider.Provide(context.Background(), "ghcr.io/artifact:v1"); err == nil {
+		t.Fatalf("Provide should have failed to get credential with err but returned nil")
+	}
+}
+
+// TestProvide_ServiceAccountSecretIncorrectType_ReturnsExpected tests that the Provide method
+// returns an error when the service account refers to a secret with an incorrect type
+func TestProvider_ServiceAccountSecretIncorrectType_ReturnsExpected(t *testing.T) {
+	k8secretprovider := k8SecretAuthProvider{
+		ratifyNamespace: "gatekeeper-system",
+		clusterClientSet: fake.NewSimpleClientset(&core.ServiceAccount{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "ratify-admin",
+				Namespace: "gatekeeper-system",
+			},
+			ImagePullSecrets: []core.LocalObjectReference{
+				{
+					Name: "test-secret",
+				},
+			},
+		}, &core.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "test-secret",
+				Namespace: "gatekeeper-system",
+			},
+			Type: core.SecretTypeBasicAuth,
+		}),
+		config: k8SecretAuthProviderConf{
+			ServiceAccountName: "ratify-admin",
+		},
+	}
+
+	if _, err := k8secretprovider.Provide(context.Background(), "ghcr.io/artifact:v1"); err == nil {
+		t.Fatalf("Provide should have failed to get credential with err but returned nil")
+	}
+}
+
+// TestProvide_ServiceAccountNoMatchingCredential_ReturnsExpected tests that the Provide method
+// returns an error when no matching credential is found for any of the service account image pull secrets
+func TestProvider_ServiceAccountNoMatchingCredential_ReturnsExpected(t *testing.T) {
+	k8secretprovider := k8SecretAuthProvider{
+		ratifyNamespace: "gatekeeper-system",
+		clusterClientSet: fake.NewSimpleClientset(&core.ServiceAccount{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "ratify-admin",
+				Namespace: "gatekeeper-system",
+			},
+			ImagePullSecrets: []core.LocalObjectReference{
+				{
+					Name: "test-secret",
+				},
+			},
+		}, &core.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "test-secret",
+				Namespace: "gatekeeper-system",
+			},
+			Type: core.SecretTypeDockerConfigJson,
+			Data: map[string][]byte{
+				core.DockerConfigJsonKey: []byte(secretContent),
+			},
+		}),
+		config: k8SecretAuthProviderConf{
+			ServiceAccountName: "ratify-admin",
+		},
+	}
+
+	if _, err := k8secretprovider.Provide(context.Background(), "ghcr.io/artifact:v1"); err == nil {
+		t.Fatalf("Provide should have failed to get credential with err but returned nil")
+	}
+}
+
+// TestProvide_ServiceAccountSecretFound_ReturnsSuccess tests that the Provide method
+// returns auth config when a matching credential is found for a user defined secret
+func TestProvider_SecretFound_ReturnsSuccess(t *testing.T) {
+	k8secretprovider := k8SecretAuthProvider{
+		ratifyNamespace: "gatekeeper-system",
+		clusterClientSet: fake.NewSimpleClientset(&core.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "test-secret",
+				Namespace: "gatekeeper-system",
+			},
+			Type: core.SecretTypeDockerConfigJson,
+			Data: map[string][]byte{
+				core.DockerConfigJsonKey: []byte(secretContent),
+			},
+		}),
+		config: k8SecretAuthProviderConf{
+			Secrets: []secretConfig{
+				{
+					SecretName: "test-secret",
+				},
+			},
+		},
+	}
+
+	if _, err := k8secretprovider.Provide(context.Background(), "index.docker.io/artifact:v1"); err != nil {
+		t.Fatalf("Provide failed to get credential with err %v", err)
+	}
+}
+
+// TestProvide_ServiceAccountSecretFound_ReturnsSuccess tests that the Provide method
+// returns auth config when a matching credential is found for a service account image pull secret
+func TestProvider_ServiceAccountSecretFound_ReturnsSuccess(t *testing.T) {
+	k8secretprovider := k8SecretAuthProvider{
+		ratifyNamespace: "gatekeeper-system",
+		clusterClientSet: fake.NewSimpleClientset(&core.ServiceAccount{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "ratify-admin",
+				Namespace: "gatekeeper-system",
+			},
+			ImagePullSecrets: []core.LocalObjectReference{
+				{
+					Name: "test-secret",
+				},
+			},
+		}, &core.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "test-secret",
+				Namespace: "gatekeeper-system",
+			},
+			Type: core.SecretTypeDockerConfigJson,
+			Data: map[string][]byte{
+				core.DockerConfigJsonKey: []byte(secretContent),
+			},
+		}),
+		config: k8SecretAuthProviderConf{
+			ServiceAccountName: "ratify-admin",
+		},
+	}
+
+	if _, err := k8secretprovider.Provide(context.Background(), "index.docker.io/artifact:v1"); err != nil {
+		t.Fatalf("Provide failed to get credential with err %v", err)
+	}
+}