Repository: deislabs/ratify (go)

Issue:
Auth cache failures when using public registry like ghcr
### What happened in your environment?

The new ratify quickstart image now relies on ghcr for verification. In the logs, we see errors of the form:

```
time=2023-08-22T23:41:13Zlevel=warningmsg=Error: cache not set, Code: CACHE_NOT_SET, Component Type: cache, Detail: failed to set auth cache for ghcr.io
```
Root Cause: In the default auth provider (docker cred store), if it cannot find the registry host (ghcr.io in this case) in the cred store, the default auth provider returns an empty AuthConfig object, and the ExpiresOn value will be empty. Ristretto/any other cache provider will attempt to use the empty ExpiresOn value for the ttl set leading to cache write errors for auth operations. 

Repair items:

- [ ] Define a default TTL for docker auth and set value to default in Docker auth provider
- [ ] Add log statement to cache providers to warn if TTL passed is not valid
- [ ] Add empty auth config check in ORAS store's credential fetch function. Should default to anonymous credentials if returned auth config is empty and err == nil
- [ ] Add new error code CACHE_OPERATION_FAILURE to distinguish between cache creation error (CACHE_NOT_SET) and operation error.

### What did you expect to happen?

There should be no Error reported and instead only a log saying anonymous credentials will be used

### What version of Kubernetes are you running?

_No response_

### What version of Ratify are you running?

v1.0.0-rc6

### Anything else you would like to add?

_No response_

### Are you willing to submit PRs to contribute to this bug fix?

- [X] Yes, I am willing to implement it.

Pull request:
fix: update auth cache miss error handling
# Description

## What this PR does / why we need it:
- Updates cache providers to check for invalid TTL and emit log + fail early
- Updates oras implementation to check if auth provider returned an empty auth config
- Update docker auth provider to set a default TTL of 1 hour
- Add tests

Logs don't show errors now for GHCR: 
![image](https://github.com/deislabs/ratify/assets/12980641/e012f1d6-bba0-4046-93b5-5d8b6e24337b)

## 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 #1025 

## Type of change

Please delete options that are not relevant.

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] 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`


Patch:
diff --git a/pkg/cache/dapr/dapr.go b/pkg/cache/dapr/dapr.go
index 3b94e05..76a2822 100644
--- a/pkg/cache/dapr/dapr.go
+++ b/pkg/cache/dapr/dapr.go
@@ -82,6 +82,10 @@ func (d *daprCache) Set(ctx context.Context, key string, value interface{}) bool
 }
 
 func (d *daprCache) SetWithTTL(ctx context.Context, key string, value interface{}, ttl time.Duration) bool {
+	if ttl < 0 {
+		logger.GetLogger(ctx, logOpt).Errorf("Error saving value to redis: ttl provided must be >= 0: %v", ttl)
+		return false
+	}
 	bytes, err := json.Marshal(value)
 	if err != nil {
 		logger.GetLogger(ctx, logOpt).Error("Error marshalling value for redis: ", err)
diff --git a/pkg/cache/ristretto/ristretto.go b/pkg/cache/ristretto/ristretto.go
index d46cc9e..7256af2 100644
--- a/pkg/cache/ristretto/ristretto.go
+++ b/pkg/cache/ristretto/ristretto.go
@@ -86,6 +86,10 @@ func (r *ristrettoCache) Set(ctx context.Context, key string, value interface{})
 }
 
 func (r *ristrettoCache) SetWithTTL(ctx context.Context, key string, value interface{}, ttl time.Duration) bool {
+	if ttl < 0 {
+		logger.GetLogger(ctx, logOpt).Errorf("Error saving value to ristretto: ttl provided must be >= 0: %v", ttl)
+		return false
+	}
 	bytes, err := json.Marshal(value)
 	if err != nil {
 		logger.GetLogger(ctx, logOpt).Error("Error marshalling value for ristretto: ", err)
diff --git a/pkg/common/oras/authprovider/authprovider.go b/pkg/common/oras/authprovider/authprovider.go
index 1acd27b..053185c 100644
--- a/pkg/common/oras/authprovider/authprovider.go
+++ b/pkg/common/oras/authprovider/authprovider.go
@@ -27,6 +27,7 @@ import (
 	re "github.com/deislabs/ratify/errors"
 	"github.com/docker/cli/cli/config"
 	"github.com/docker/cli/cli/config/configfile"
+	"github.com/docker/cli/cli/config/types"
 )
 
 // This config represents the credentials that should be used
@@ -60,6 +61,7 @@ type defaultAuthProviderConf struct {
 }
 
 const DefaultAuthProviderName string = "dockerConfig"
+const DefaultDockerAuthTTL = 1 * time.Hour
 
 // init calls Register for our default provider, which simply reads the .dockercfg file.
 func init() {
@@ -123,10 +125,14 @@ func (d *defaultAuthProvider) Provide(_ context.Context, artifact string) (AuthC
 	}
 
 	dockerAuthConfig := cfg.AuthConfigs[artifactHostName]
+	if dockerAuthConfig == (types.AuthConfig{}) {
+		return AuthConfig{}, nil
+	}
 	authConfig := AuthConfig{
 		Username:      dockerAuthConfig.Username,
 		Password:      dockerAuthConfig.Password,
 		IdentityToken: dockerAuthConfig.IdentityToken,
+		ExpiresOn:     time.Now().Add(DefaultDockerAuthTTL),
 		Provider:      d,
 	}
 
diff --git a/pkg/referrerstore/oras/oras.go b/pkg/referrerstore/oras/oras.go
index 9bb27f9..45bfc99 100644
--- a/pkg/referrerstore/oras/oras.go
+++ b/pkg/referrerstore/oras/oras.go
@@ -417,7 +417,9 @@ func createDefaultRepository(ctx context.Context, store *orasStore, targetRef co
 		authConfig, err = store.authProvider.Provide(ctx, targetRef.Original)
 		if err != nil {
 			logger.GetLogger(ctx, logOpt).Warnf("auth provider failed with err, %v", err)
-			logger.GetLogger(ctx, logOpt).Info("attempting to use anonymous credentials")
+			logger.GetLogger(ctx, logOpt).Debug("attempting to use anonymous credentials")
+		} else if authConfig == (authprovider.AuthConfig{}) {
+			logger.GetLogger(ctx, logOpt).Debug("no credentials found, attempting to use anonymous credentials")
 		} else {
 			if cacheProvider != nil {
 				success := cacheProvider.SetWithTTL(ctx, fmt.Sprintf(cache.CacheKeyOrasAuth, artifactRef.Registry), authConfig, time.Until(authConfig.ExpiresOn))


Test patch:
diff --git a/pkg/cache/dapr/dapr_test.go b/pkg/cache/dapr/dapr_test.go
index e89efc1..c744a0d 100644
--- a/pkg/cache/dapr/dapr_test.go
+++ b/pkg/cache/dapr/dapr_test.go
@@ -169,6 +169,17 @@ func TestDaprCacheProvider_SetWithTTL_Error(t *testing.T) {
 	}
 }
 
+func TestDaprCacheProvider_SetWithTTL_InvalidTTL(t *testing.T) {
+	d := &daprCache{
+		daprClient: &TestDaprClient{},
+		cacheName:  testCacheName,
+	}
+	ok := d.SetWithTTL(context.Background(), testKey, testValue, -10)
+	if ok {
+		t.Errorf("expected ok to be false")
+	}
+}
+
 func TestDaprCacheProvider_Delete_Expected(t *testing.T) {
 	d := &daprCache{
 		daprClient: &TestDaprClient{
diff --git a/pkg/cache/ristretto/ristretto_test.go b/pkg/cache/ristretto/ristretto_test.go
index eb55091..afb6760 100644
--- a/pkg/cache/ristretto/ristretto_test.go
+++ b/pkg/cache/ristretto/ristretto_test.go
@@ -112,6 +112,25 @@ func TestSetWithTTL_Expected(t *testing.T) {
 	}
 }
 
+// TestSetWithTTL_InvalidTTL tests the SetWithTTL function with an invalid TTL
+func TestSetWithTTL_InvalidTTL(t *testing.T) {
+	ctx := context.Background()
+	var err error
+	// first attempt to get the cache provider if it's already been initialized
+	cacheProvider := cache.GetCacheProvider()
+	if cacheProvider == nil {
+		// if no cache provider has been initialized, initialize one
+		cacheProvider, err = cache.NewCacheProvider(ctx, RistrettoCacheType, cache.DefaultCacheName, cache.DefaultCacheSize)
+		if err != nil {
+			t.Errorf("Expected no error, but got %v", err)
+		}
+	}
+	ok := cacheProvider.SetWithTTL(ctx, "test_ttl", "test", -10)
+	if ok {
+		t.Errorf("Expected ok to be false")
+	}
+}
+
 // TestGet_Expected tests the Get function
 func TestGet_Expected(t *testing.T) {
 	ctx := context.Background()
diff --git a/pkg/common/oras/authprovider/authprovider_test.go b/pkg/common/oras/authprovider/authprovider_test.go
index 7d82236..40d1630 100644
--- a/pkg/common/oras/authprovider/authprovider_test.go
+++ b/pkg/common/oras/authprovider/authprovider_test.go
@@ -20,6 +20,7 @@ import (
 	"os"
 	"path/filepath"
 	"testing"
+	"time"
 )
 
 const (
@@ -87,6 +88,10 @@ func TestProvide_ExternalDockerConfigPath_ExpectedResults(t *testing.T) {
 	if authConfig.Username != testUserName || authConfig.Password != testPassword {
 		t.Fatalf("incorrect username %v or password %v returned", authConfig.Username, authConfig.Password)
 	}
+
+	if time.Now().Add(DefaultDockerAuthTTL - time.Minute).After(authConfig.ExpiresOn) {
+		t.Fatalf("incorrect expiration time %v returned", authConfig.ExpiresOn)
+	}
 }
 
 func TestProvide_ExternalDockerConfigPathWithIdentityToken_ExpectedResults(t *testing.T) {