Repository: deislabs/ratify
Pull request #158 (2022-04-05 21:54:32)

CSI Driver template update for workload identity
 update CSI driver to use also use workload identity so user won't have to configure workload identity as well as pod identity.


Patch:
diff --git a/README.md b/README.md
index 20ea41d..09115d8 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,8 @@ by its developers, nor is it "supported" software.
 
 Try out ratify in Kuberenetes through Gatekeeper as the admission controller.
 
+Prerequisite: Kubernetes v1.20 or higher
+
 - Setup Gatekeeper with [external data](https://open-policy-agent.github.io/gatekeeper/website/docs/externaldata)
 
 ```bash
@@ -35,9 +37,11 @@ helm install gatekeeper/gatekeeper  \
     --name-template=gatekeeper \
     --namespace gatekeeper-system --create-namespace \
     --set enableExternalData=true \
-    --set controllerManager.dnsPolicy=ClusterFirst,audit.dnsPolicy=ClusterFirst
+    --set validatingWebhookTimeoutSeconds=7
 ```
 
+NOTE: `validatingWebhookTimeoutSeconds` increased from 3 to 7 so all Ratify operations complete in complex scenarios. Kubernetes v1.20 or higher is REQUIRED to increase timeout.  
+
 - Deploy ratify and a `demo` constraint on gatekeeper
 
 ```bash
diff --git a/charts/ratify-gatekeeper/values.yaml b/charts/ratify-gatekeeper/values.yaml
index 34d444d..0fcf5f0 100644
--- a/charts/ratify-gatekeeper/values.yaml
+++ b/charts/ratify-gatekeeper/values.yaml
@@ -8,7 +8,7 @@ gatekeeper:
   auditFromCache: false
   disableMutation: false
   disableValidatingWebhook: false
-  validatingWebhookTimeoutSeconds: 3
+  validatingWebhookTimeoutSeconds: 7
   validatingWebhookFailurePolicy: Ignore
   validatingWebhookCheckIgnoreFailurePolicy: Fail
   enableDeleteOperations: false
diff --git a/charts/ratify/templates/akv-secret-provider.yaml b/charts/ratify/templates/akv-secret-provider.yaml
index b9a9d23..33d0c0d 100644
--- a/charts/ratify/templates/akv-secret-provider.yaml
+++ b/charts/ratify/templates/akv-secret-provider.yaml
@@ -6,7 +6,6 @@ metadata:
 spec:
   provider: azure
   parameters:
-    usePodIdentity: "true"
     keyvaultName: {{ required "vaultName must be provided when AKV cert config is enabled" .Values.akvCertConfig.vaultName  }}
     objects:  |
       array:
@@ -15,4 +14,5 @@ spec:
           objectType: cert
           objectAlias: ratify-test.crt
     tenantId: {{ required "tenantId must be provided when AKV cert config is enabled" .Values.akvCertConfig.tenantId  }}
+    clientID: {{ required "clientID must be provided when AKV cert config is enabled" .Values.akvCertConfig.clientId  }}
 {{ end }}
\ No newline at end of file
diff --git a/charts/ratify/templates/configmap.yaml b/charts/ratify/templates/configmap.yaml
index a2ee985..3521645 100644
--- a/charts/ratify/templates/configmap.yaml
+++ b/charts/ratify/templates/configmap.yaml
@@ -5,6 +5,9 @@ metadata:
 data:
   config.json: |
     {
+      "executor": {
+        "requestTimeout": 6800
+      },
       "stores": {
         "version": "1.0.0",
         "plugins": [
diff --git a/charts/ratify/templates/deployment.yaml b/charts/ratify/templates/deployment.yaml
index 0cce93c..0a2c1e0 100644
--- a/charts/ratify/templates/deployment.yaml
+++ b/charts/ratify/templates/deployment.yaml
@@ -14,10 +14,10 @@ spec:
     metadata:
       labels:
         {{- include "ratify.selectorLabels" . | nindent 8 }}
-        {{- if .Values.akvCertConfig.enabled }}
-        aadpodidbinding: {{ required "aadPodIdentity must be provided when AKV cert config is enabled" .Values.akvCertConfig.aadPodIdentity  }}
-        {{- end }}
     spec:
+      {{- if or (.Values.akvCertConfig.enabled) (.Values.oras.authProviders.azureWorkloadIdentityEnabled)}} 
+      serviceAccountName: {{ required "service account must be provided when using workload identity for AKV cert config or oras authentication" .Values.serviceAccount.name  }}
+      {{- end }}
       containers:
         - name: {{ .Chart.Name }}
           image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
diff --git a/charts/ratify/values.yaml b/charts/ratify/values.yaml
index fd70913..1a33391 100644
--- a/charts/ratify/values.yaml
+++ b/charts/ratify/values.yaml
@@ -39,7 +39,7 @@ akvCertConfig:
   vaultName:
   certName:
   tenantId:
-  aadPodIdentity:
+  clientId:
 oras:
   authProviders:
     azureWorkloadIdentityEnabled: false
diff --git a/cmd/ratify/cmd/referrer.go b/cmd/ratify/cmd/referrer.go
index 4ec3f2b..3b562ab 100644
--- a/cmd/ratify/cmd/referrer.go
+++ b/cmd/ratify/cmd/referrer.go
@@ -26,7 +26,6 @@ import (
 	"github.com/deislabs/ratify/pkg/ocispecs"
 	sf "github.com/deislabs/ratify/pkg/referrerstore/factory"
 	"github.com/deislabs/ratify/pkg/utils"
-	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/spf13/cobra"
 )
 
@@ -152,7 +151,7 @@ func showBlob(opts referrerCmdOptions) error {
 
 	for _, referrerStore := range stores {
 		if referrerStore.Name() == opts.storeName {
-			content, err := referrerStore.GetBlobContent(context.Background(), subRef, digest, v1.Descriptor{})
+			content, err := referrerStore.GetBlobContent(context.Background(), subRef, digest)
 			if err != nil {
 				return err
 			}
diff --git a/cmd/ratify/cmd/serve.go b/cmd/ratify/cmd/serve.go
index d90bcd0..070b03d 100644
--- a/cmd/ratify/cmd/serve.go
+++ b/cmd/ratify/cmd/serve.go
@@ -92,6 +92,7 @@ func serve(opts serveCmdOptions) error {
 		Verifiers:      verifiers,
 		ReferrerStores: stores,
 		PolicyEnforcer: policyEnforcer,
+		Config:         &cf.ExecutorConfig,
 	}
 
 	if opts.httpServerAddress != "" {
diff --git a/docs/README.md b/docs/README.md
index f68ee49..568d017 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -74,7 +74,7 @@ type ReferrerStore interface {
  Name() string
  ListReferrers(ctx context.Context, subjectReference common.Reference, artifactTypes []string, nextToken string, subjectDesc *ocispecs.SubjectReference) (ListReferrersResult, error)
  // Used for small objects.
- GetBlobContent(ctx context.Context, subjectReference common.Reference, digest digest.Digest, blobDesc oci.Descriptor) ([]byte, error)
+ GetBlobContent(ctx context.Context, subjectReference common.Reference, digest digest.Digest) ([]byte, error)
  GetReferenceManifest(ctx context.Context, subjectReference common.Reference, referenceDesc ocispecs.ReferenceDescriptor) (ocispecs.ReferenceManifest, error)
 }
 ```
diff --git a/go.mod b/go.mod
index e5312fa..76e3ea2 100644
--- a/go.mod
+++ b/go.mod
@@ -24,5 +24,5 @@ require (
 	k8s.io/api v0.22.5
 	k8s.io/apimachinery v0.22.5
 	k8s.io/client-go v0.22.5
-	oras.land/oras-go/v2 v2.0.0-20220329045430-dd7d79fb99a1
+	oras.land/oras-go/v2 v2.0.0-20220411024522-db4a48eade0e
 )
diff --git a/go.sum b/go.sum
index 0d71faf..ca28325 100644
--- a/go.sum
+++ b/go.sum
@@ -2907,8 +2907,8 @@ mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIa
 mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
 mvdan.cc/unparam v0.0.0-20210104141923-aac4ce9116a7/go.mod h1:hBpJkZE8H/sb+VRFvw2+rBpHNsTBcvSpk61hr8mzXZE=
 nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
-oras.land/oras-go/v2 v2.0.0-20220329045430-dd7d79fb99a1 h1:MQBoC4YYIOnGtLqMGCvK+eQnfopaRmY1yoBxr/OA9uU=
-oras.land/oras-go/v2 v2.0.0-20220329045430-dd7d79fb99a1/go.mod h1:0IQiLwHUJuMs0+QYGavaeQWw5FD4ABD/RP5YamXT/sc=
+oras.land/oras-go/v2 v2.0.0-20220411024522-db4a48eade0e h1:njS1UlPEXosOodJ9T2NGMJPmgiEzAwSHDJPfnjq7nXo=
+oras.land/oras-go/v2 v2.0.0-20220411024522-db4a48eade0e/go.mod h1:0IQiLwHUJuMs0+QYGavaeQWw5FD4ABD/RP5YamXT/sc=
 pack.ag/amqp v0.11.2/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4=
 pgregory.net/rapid v0.3.3/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
diff --git a/pkg/referrerstore/api.go b/pkg/referrerstore/api.go
index 991dabb..ed2da7f 100644
--- a/pkg/referrerstore/api.go
+++ b/pkg/referrerstore/api.go
@@ -22,7 +22,6 @@ import (
 	"github.com/deislabs/ratify/pkg/ocispecs"
 	"github.com/deislabs/ratify/pkg/referrerstore/config"
 	"github.com/opencontainers/go-digest"
-	oci "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
 // ListReferrersResult represents the result of ListReferrers API
@@ -42,7 +41,7 @@ type ReferrerStore interface {
 
 	// GetBlobContent returns the blob with the given digest
 	// WARNING: This API is intended to use for small objects like signatures, SBoMs
-	GetBlobContent(ctx context.Context, subjectReference common.Reference, digest digest.Digest, blobDesc oci.Descriptor) ([]byte, error)
+	GetBlobContent(ctx context.Context, subjectReference common.Reference, digest digest.Digest) ([]byte, error)
 
 	// GetReferenceManifest returns the reference artifact manifest as given by the descriptor
 	GetReferenceManifest(ctx context.Context, subjectReference common.Reference, referenceDesc ocispecs.ReferenceDescriptor) (ocispecs.ReferenceManifest, error)
diff --git a/pkg/referrerstore/mocks/types.go b/pkg/referrerstore/mocks/types.go
index 55b556f..3856940 100644
--- a/pkg/referrerstore/mocks/types.go
+++ b/pkg/referrerstore/mocks/types.go
@@ -24,7 +24,6 @@ import (
 	"github.com/deislabs/ratify/pkg/referrerstore"
 	"github.com/deislabs/ratify/pkg/referrerstore/config"
 	"github.com/opencontainers/go-digest"
-	oci "github.com/opencontainers/image-spec/specs-go/v1"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
@@ -41,7 +40,7 @@ func (s *TestStore) ListReferrers(ctx context.Context, subjectReference common.R
 	return referrerstore.ListReferrersResult{Referrers: s.References}, nil
 }
 
-func (s *TestStore) GetBlobContent(ctx context.Context, subjectReference common.Reference, digest digest.Digest, blobDesc oci.Descriptor) ([]byte, error) {
+func (s *TestStore) GetBlobContent(ctx context.Context, subjectReference common.Reference, digest digest.Digest) ([]byte, error) {
 	return nil, nil
 }
 
diff --git a/pkg/referrerstore/oras/oras.go b/pkg/referrerstore/oras/oras.go
index 6d4de45..cec07b5 100644
--- a/pkg/referrerstore/oras/oras.go
+++ b/pkg/referrerstore/oras/oras.go
@@ -22,6 +22,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
+	"io/ioutil"
 	"net/http"
 	paths "path/filepath"
 	"time"
@@ -123,7 +124,7 @@ func (store *orasStore) GetConfig() *config.StoreConfig {
 }
 
 func (store *orasStore) ListReferrers(ctx context.Context, subjectReference common.Reference, artifactTypes []string, nextToken string, subjectDesc *ocispecs.SubjectDescriptor) (referrerstore.ListReferrersResult, error) {
-	repository, err := store.createRepository(ctx, subjectReference)
+	repository, expiry, err := store.createRepository(ctx, subjectReference)
 	if err != nil {
 		return referrerstore.ListReferrersResult{}, err
 	}
@@ -135,6 +136,7 @@ func (store *orasStore) ListReferrers(ctx context.Context, subjectReference comm
 	} else {
 		resolvedSubjectDesc, err = store.GetSubjectDescriptor(ctx, subjectReference)
 		if err != nil {
+			store.evictAuthCache(subjectReference.Original, err)
 			return referrerstore.ListReferrersResult{}, err
 		}
 	}
@@ -145,8 +147,11 @@ func (store *orasStore) ListReferrers(ctx context.Context, subjectReference comm
 		referrerDescriptors = referrers
 		return nil
 	}); err != nil {
+		store.evictAuthCache(subjectReference.Original, err)
 		return referrerstore.ListReferrersResult{}, err
 	}
+	// add the repository client to the auth cache if all repository operations successful
+	store.addAuthCache(subjectReference.Original, repository, expiry)
 
 	// convert artifact descriptors to oci descriptor with artifact type
 	var referrers []ocispecs.ReferenceDescriptor
@@ -165,50 +170,51 @@ func (store *orasStore) ListReferrers(ctx context.Context, subjectReference comm
 	return referrerstore.ListReferrersResult{Referrers: referrers}, nil
 }
 
-func (store *orasStore) GetBlobContent(ctx context.Context, subjectReference common.Reference, digest digest.Digest, blobDesc oci.Descriptor) ([]byte, error) {
+func (store *orasStore) GetBlobContent(ctx context.Context, subjectReference common.Reference, digest digest.Digest) ([]byte, error) {
 	var err error
-	repository, err := store.createRepository(ctx, subjectReference)
+	repository, expiry, err := store.createRepository(ctx, subjectReference)
 	if err != nil {
 		return nil, err
 	}
 
-	// resolve blob descriptor if not provided
-	var resolvedBlobDesc oci.Descriptor
-	if blobDesc.Digest != "" {
-		resolvedBlobDesc = blobDesc
-	} else {
-		ref := fmt.Sprintf("%s@%s", subjectReference.Path, digest)
-		resolvedBlobDesc, err = repository.Blobs().Resolve(ctx, ref)
-		if err != nil {
-			return nil, err
-		}
+	// create a dummy Descriptor to check the local store cache
+	blobDescriptor := oci.Descriptor{
+		Digest: digest,
+		Size:   0, // dummy size value
 	}
 
 	// check if blob exists in local ORAS cache
-	isCached, err := store.localCache.Exists(ctx, resolvedBlobDesc)
+	isCached, err := store.localCache.Exists(ctx, blobDescriptor)
 	if err != nil {
 		return nil, err
 	}
 
 	if !isCached {
+		// generate the reference path with digest
+		ref := fmt.Sprintf("%s@%s", subjectReference.Path, digest)
+
 		// fetch blob content from remote repository
-		rc, err := repository.Fetch(ctx, resolvedBlobDesc)
+		blobDesc, rc, err := repository.Blobs().FetchReference(ctx, ref)
 		if err != nil {
+			store.evictAuthCache(subjectReference.Original, err)
 			return nil, err
 		}
 
 		// push fetched content to local ORAS cache
-		err = store.localCache.Push(ctx, resolvedBlobDesc, rc)
+		err = store.localCache.Push(ctx, blobDesc, rc)
 		if err != nil {
 			return nil, err
 		}
 	}
 
-	return store.getRawContentFromCache(ctx, resolvedBlobDesc)
+	// add the repository client to the auth cache if all repository operations successful
+	store.addAuthCache(subjectReference.Original, repository, expiry)
+
+	return store.getRawContentFromCache(ctx, blobDescriptor)
 }
 
 func (store *orasStore) GetReferenceManifest(ctx context.Context, subjectReference common.Reference, referenceDesc ocispecs.ReferenceDescriptor) (ocispecs.ReferenceManifest, error) {
-	repository, err := store.createRepository(ctx, subjectReference)
+	repository, expiry, err := store.createRepository(ctx, subjectReference)
 	if err != nil {
 		return ocispecs.ReferenceManifest{}, err
 	}
@@ -223,6 +229,7 @@ func (store *orasStore) GetReferenceManifest(ctx context.Context, subjectReferen
 		// fetch manifest content from repository
 		manifestReader, err := repository.Fetch(ctx, referenceDesc.Descriptor)
 		if err != nil {
+			store.evictAuthCache(subjectReference.Original, err)
 			return ocispecs.ReferenceManifest{}, err
 		}
 
@@ -236,6 +243,9 @@ func (store *orasStore) GetReferenceManifest(ctx context.Context, subjectReferen
 		if err != nil {
 			return ocispecs.ReferenceManifest{}, err
 		}
+
+		// add the repository client to the auth cache if all repository operations successful
+		store.addAuthCache(subjectReference.Original, repository, expiry)
 	} else {
 		manifestBytes, err = store.getRawContentFromCache(ctx, referenceDesc.Descriptor)
 		if err != nil {
@@ -253,28 +263,32 @@ func (store *orasStore) GetReferenceManifest(ctx context.Context, subjectReferen
 }
 
 func (store *orasStore) GetSubjectDescriptor(ctx context.Context, subjectReference common.Reference) (*ocispecs.SubjectDescriptor, error) {
-	repository, err := store.createRepository(ctx, subjectReference)
+	repository, expiry, err := store.createRepository(ctx, subjectReference)
 	if err != nil {
 		return nil, err
 	}
 
 	desc, err := repository.Resolve(ctx, subjectReference.Original)
 	if err != nil {
+		store.evictAuthCache(subjectReference.Original, err)
 		return nil, err
 	}
 
+	// add the repository client to the auth cache if all repository operations successful
+	store.addAuthCache(subjectReference.Original, repository, expiry)
+
 	return &ocispecs.SubjectDescriptor{Descriptor: desc}, nil
 }
 
-func (store *orasStore) createRepository(ctx context.Context, targetRef common.Reference) (*remote.Repository, error) {
+func (store *orasStore) createRepository(ctx context.Context, targetRef common.Reference) (*remote.Repository, time.Time, error) {
 	if store.authProvider == nil || !store.authProvider.Enabled(ctx) {
-		return nil, fmt.Errorf("auth provider not properly enabled")
+		return nil, time.Now(), fmt.Errorf("auth provider not properly enabled")
 	}
 
 	if cacheEntry, ok := store.authCache[targetRef.Original]; ok {
 		// if the auth cache entry expiration has not expired or it was never set
 		if cacheEntry.expiresOn.IsZero() || cacheEntry.expiresOn.After(time.Now()) {
-			return cacheEntry.client, nil
+			return cacheEntry.client, cacheEntry.expiresOn, nil
 		}
 	}
 
@@ -287,7 +301,7 @@ func (store *orasStore) createRepository(ctx context.Context, targetRef common.R
 	// create new ORAS repository target to the image/repository reference
 	repository, err := remote.NewRepository(targetRef.Original)
 	if err != nil {
-		return nil, err
+		return nil, time.Now(), err
 	}
 
 	// set the provider to return the resolved credentials
@@ -324,12 +338,7 @@ func (store *orasStore) createRepository(ctx context.Context, targetRef common.R
 	// enable plain HTTP if specified in config
 	repository.PlainHTTP = store.config.UseHttp
 
-	store.authCache[targetRef.Original] = authCacheEntry{
-		client:    repository,
-		expiresOn: authConfig.ExpiresOn,
-	}
-
-	return repository, nil
+	return repository, authConfig.ExpiresOn, nil
 }
 
 func (store *orasStore) getRawContentFromCache(ctx context.Context, descriptor oci.Descriptor) ([]byte, error) {
@@ -337,10 +346,25 @@ func (store *orasStore) getRawContentFromCache(ctx context.Context, descriptor o
 	if err != nil {
 		return nil, err
 	}
-	buf := make([]byte, descriptor.Size)
-	_, err = reader.Read(buf)
+
+	buf, err := ioutil.ReadAll(reader)
 	if err != nil {
 		return nil, err
 	}
 	return buf, nil
 }
+
+func (store *orasStore) addAuthCache(ref string, repository *remote.Repository, expiry time.Time) {
+	_, ok := store.authCache[ref]
+	if !ok {
+		store.authCache[ref] = authCacheEntry{
+			client:    repository,
+			expiresOn: expiry,
+		}
+	}
+}
+
+func (store *orasStore) evictAuthCache(ref string, err error) {
+	delete(store.authCache, ref)
+	// TODO: add reliable way to conditionally evict based on error code
+}
diff --git a/pkg/referrerstore/plugin/plugin.go b/pkg/referrerstore/plugin/plugin.go
index b0797c2..5137061 100644
--- a/pkg/referrerstore/plugin/plugin.go
+++ b/pkg/referrerstore/plugin/plugin.go
@@ -29,7 +29,6 @@ import (
 	"github.com/deislabs/ratify/pkg/referrerstore/config"
 	"github.com/deislabs/ratify/pkg/referrerstore/types"
 	"github.com/opencontainers/go-digest"
-	oci "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
 // StorePlugin describes a store that is implemented by invoking the plugins
@@ -97,7 +96,7 @@ func (sp *StorePlugin) Name() string {
 	return sp.name
 }
 
-func (sp *StorePlugin) GetBlobContent(ctx context.Context, subjectReference common.Reference, digest digest.Digest, blobDesc oci.Descriptor) ([]byte, error) {
+func (sp *StorePlugin) GetBlobContent(ctx context.Context, subjectReference common.Reference, digest digest.Digest) ([]byte, error) {
 	pluginPath, err := sp.executor.FindInPaths(sp.name, sp.path)
 	if err != nil {
 		return nil, err
diff --git a/pkg/referrerstore/plugin/skel/skel.go b/pkg/referrerstore/plugin/skel/skel.go
index 8c0bc2e..3949639 100644
--- a/pkg/referrerstore/plugin/skel/skel.go
+++ b/pkg/referrerstore/plugin/skel/skel.go
@@ -32,7 +32,6 @@ import (
 	"github.com/deislabs/ratify/pkg/referrerstore/types"
 	"github.com/deislabs/ratify/pkg/utils"
 	"github.com/opencontainers/go-digest"
-	oci "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
 type pcontext struct {
@@ -43,7 +42,7 @@ type pcontext struct {
 }
 
 type ListReferrers func(args *CmdArgs, subjectReference common.Reference, artifactTypes []string, nextToken string, subjectDesc *ocispecs.SubjectDescriptor) (*referrerstore.ListReferrersResult, error)
-type GetBlobContent func(args *CmdArgs, subjectReference common.Reference, digest digest.Digest, blobDesc oci.Descriptor) ([]byte, error)
+type GetBlobContent func(args *CmdArgs, subjectReference common.Reference, digest digest.Digest) ([]byte, error)
 type GetReferenceManifest func(args *CmdArgs, subjectReference common.Reference, digest digest.Digest) (ocispecs.ReferenceManifest, error)
 type GetSubjectDescriptor func(args *CmdArgs, subjectReference common.Reference) (*ocispecs.SubjectDescriptor, error)
 
@@ -154,7 +153,7 @@ func (c *pcontext) cmdGetBlob(cmdArgs *CmdArgs, pluginFunc GetBlobContent) *plug
 		return plugin.NewError(types.ErrArgsParsingFailure, fmt.Sprintf("cannot parse digest arg %s", digestArg), err.Error())
 	}
 
-	result, err := pluginFunc(cmdArgs, cmdArgs.subjectRef, digest, oci.Descriptor{})
+	result, err := pluginFunc(cmdArgs, cmdArgs.subjectRef, digest)
 
 	if err != nil {
 		return plugin.NewError(types.ErrPluginCmdFailure, fmt.Sprintf("plugin command %s failed", sp.ListReferrersCommand), err.Error())
diff --git a/pkg/utils/certificateUtils.go b/pkg/utils/certificateUtils.go
index 3ba82f9..94bf764 100644
--- a/pkg/utils/certificateUtils.go
+++ b/pkg/utils/certificateUtils.go
@@ -17,27 +17,63 @@ package utils
 
 import (
 	"crypto/x509"
+	"io/fs"
 	"os"
+	"strings"
 
 	"path/filepath"
 
+	"github.com/deislabs/ratify/pkg/homedir"
 	"github.com/notaryproject/notation-go-lib/crypto/cryptoutil"
+	"github.com/pkg/errors"
+	"github.com/sirupsen/logrus"
 )
 
+// Return list of certificates loaded from path
+// when path is a directory, this method loads all certs in directory and resolve symlink if needed
 func GetCertificatesFromPath(path string) ([]*x509.Certificate, error) {
-
 	var certs []*x509.Certificate
+	fileMap := map[string]bool{} //a map to track path of physical files
+
+	path = ReplaceHomeShortcut(path)
 
 	err := filepath.Walk(path, func(file string, info os.FileInfo, err error) error {
-		if !info.IsDir() {
-			cert, certError := cryptoutil.ReadCertificateFile(file) // ReadCertificateFile returns empty if file was not a certificate
-			if certError != nil {
-				return certError
+
+		targetFileInfo := info
+		targetFilePath := file
+
+		if info == nil || err != nil {
+			logrus.Warnf("Invalid path '%v' skipped, error %v", file, err)
+			return nil
+		}
+
+		// In a cluster environment, each mounted file results in a physical file and a symlink
+		// check if file is a link and get the actual file path
+		if isSymbolicLink(info) {
+			targetFilePath, err = filepath.EvalSymlinks(file)
+			if err != nil || len(targetFilePath) == 0 {
+				logrus.Errorf("Unable to resolve symbolic link %v , error '%v'", file, err)
+				return nil
 			}
-			if cert != nil {
-				certs = append(certs, cert...)
+
+			targetFileInfo, err = os.Lstat(targetFilePath)
+
+			if err != nil {
+				logrus.Errorf("error getting file info for path '%v', error '%v'", targetFilePath, err)
+				return nil
+			}
+		}
+
+		// if filepath.EvalSymlinks fails to resolve multi level sym link, skip this file
+		if targetFileInfo != nil && !targetFileInfo.IsDir() && !isSymbolicLink(targetFileInfo) {
+			if _, ok := fileMap[targetFilePath]; !ok {
+				certs, err = loadCertFile(targetFileInfo, targetFilePath, certs, fileMap)
+				if err != nil {
+					return errors.Wrap(err, "error reading certificate file "+targetFilePath)
+				}
 			}
 		}
+
 		return nil
 	})
 
@@ -45,5 +81,38 @@ func GetCertificatesFromPath(path string) ([]*x509.Certificate, error) {
 		return nil, err
 	}
 
+	logrus.Infof("%v notary verification certificates loaded from path '%v'", len(certs), path)
 	return certs, nil
 }
+
+func isSymbolicLink(info fs.FileInfo) bool {
+	return info.Mode()&os.ModeSymlink != 0
+}
+
+func loadCertFile(fileInfo fs.FileInfo, filePath string, certificate []*x509.Certificate, fileMap map[string]bool) ([]*x509.Certificate, error) {
+	cert, certError := cryptoutil.ReadCertificateFile(filePath) // ReadCertificateFile returns empty if file was not a certificate
+	if certError != nil {
+		return certificate, certError
+	}
+	if cert != nil {
+		certificate = append(certificate, cert...)
+		fileMap[filePath] = true
+	}
+
+	return certificate, nil
+}
+
+// Replace the shortcut prefix in a path with the home directory
+// For example in a unix os, ~/.config/ becomes /home/azureuser/.config after replacement
+func ReplaceHomeShortcut(path string) string {
+	shortcutPrefix := homedir.GetShortcutString() + string(os.PathSeparator)
+	if strings.HasPrefix(path, shortcutPrefix) {
+		home := homedir.Get()
+		if len(home) > 0 {
+			return strings.Replace(path, homedir.GetShortcutString(), home, 1) // replace 1 instance
+		} else {
+			logrus.Warningf("Path '%v' replacement failed , value of Home dir '%v'", path, home)
+		}
+	}
+	return path
+}
diff --git a/pkg/verifier/api.go b/pkg/verifier/api.go
index 2fe6873..f2a6add 100644
--- a/pkg/verifier/api.go
+++ b/pkg/verifier/api.go
@@ -27,7 +27,7 @@ import (
 // VerifierResult describes the result of verifying a reference manifest for a subject
 type VerifierResult struct {
 	Subject       string           `json:"subject,omitempty"`
-	IsSuccess     bool             `json:"isSuccess,omitempty"`
+	IsSuccess     bool             `json:"isSuccess"`
 	Name          string           `json:"name,omitempty"`
 	Results       []string         `json:"results,omitempty"`
 	NestedResults []VerifierResult `json:"nestedResults,omitempty"`
diff --git a/pkg/verifier/notaryv2/notaryv2.go b/pkg/verifier/notaryv2/notaryv2.go
index a820d92..cf5c651 100644
--- a/pkg/verifier/notaryv2/notaryv2.go
+++ b/pkg/verifier/notaryv2/notaryv2.go
@@ -113,19 +113,19 @@ func (v *notaryV2Verifier) Verify(ctx context.Context,
 	referenceManifest, err := store.GetReferenceManifest(ctx, subjectReference, referenceDescriptor)
 
 	if err != nil {
-		return verifier.VerifierResult{}, err
+		return verifier.VerifierResult{IsSuccess: false}, err
 	}
 
 	for _, blobDesc := range referenceManifest.Blobs {
-		refBlob, err := store.GetBlobContent(ctx, subjectReference, blobDesc.Digest, blobDesc)
+		refBlob, err := store.GetBlobContent(ctx, subjectReference, blobDesc.Digest)
 		if err != nil {
-			return verifier.VerifierResult{}, err
+			return verifier.VerifierResult{IsSuccess: false}, err
 		}
 
 		var opts notation.VerifyOptions
 		vdesc, err := v.notationVerifier.Verify(context.Background(), refBlob, opts)
 		if err != nil {
-			return verifier.VerifierResult{}, err
+			return verifier.VerifierResult{IsSuccess: false}, err
 		}
 
 		// TODO get the subject descriptor and verify all the properties other than digest.
diff --git a/pkg/verifier/plugin/plugin.go b/pkg/verifier/plugin/plugin.go
index 6873ef4..e07c7ff 100644
--- a/pkg/verifier/plugin/plugin.go
+++ b/pkg/verifier/plugin/plugin.go
@@ -104,7 +104,7 @@ func (vp *VerifierPlugin) Verify(ctx context.Context,
 		nestedVerifyResult, err := executor.VerifySubject(ctx, verifyParameters)
 
 		if err != nil {
-			return verifier.VerifierResult{}, err
+			return verifier.VerifierResult{IsSuccess: false}, err
 		}
 
 		for _, vr := range nestedVerifyResult.VerifierReports {
@@ -127,7 +127,7 @@ func (vp *VerifierPlugin) Verify(ctx context.Context,
 	referrerStoreConfig := store.GetConfig()
 	vr, err := vp.verifyReference(ctx, subjectReference, referenceDescriptor, referrerStoreConfig)
 	if err != nil {
-		return verifier.VerifierResult{}, err
+		return verifier.VerifierResult{IsSuccess: false}, err
 	}
 
 	vr.NestedResults = nestedResults
diff --git a/plugins/referrerstore/sample/sample.go b/plugins/referrerstore/sample/sample.go
index e4b161c..c266dc3 100644
--- a/plugins/referrerstore/sample/sample.go
+++ b/plugins/referrerstore/sample/sample.go
@@ -41,7 +41,7 @@ func ListReferrers(args *skel.CmdArgs, subjectReference common.Reference, artifa
 	}, nil
 }
 
-func GetBlobContent(args *skel.CmdArgs, subjectReference common.Reference, digest digest.Digest, blobDesc v1.Descriptor) ([]byte, error) {
+func GetBlobContent(args *skel.CmdArgs, subjectReference common.Reference, digest digest.Digest) ([]byte, error) {
 	return []byte(digest.String()), nil
 }
 
diff --git a/plugins/verifier/cosign/cosign.go b/plugins/verifier/cosign/cosign.go
index 4a4111c..ffe91cc 100644
--- a/plugins/verifier/cosign/cosign.go
+++ b/plugins/verifier/cosign/cosign.go
@@ -26,6 +26,7 @@ import (
 	"github.com/deislabs/ratify/pkg/common"
 	"github.com/deislabs/ratify/pkg/ocispecs"
 	"github.com/deislabs/ratify/pkg/referrerstore"
+	"github.com/deislabs/ratify/pkg/utils"
 	"github.com/deislabs/ratify/pkg/verifier"
 	"github.com/deislabs/ratify/pkg/verifier/plugin/skel"
 	"github.com/google/go-containerregistry/pkg/authn"
@@ -133,7 +134,8 @@ func signatures(ctx context.Context, img string, keyRef string, config *PluginIn
 }
 
 func loadPublicKey(ctx context.Context, keyRef string) (verifier signature.Verifier, err error) {
-	raw, err := ioutil.ReadFile(filepath.Clean(keyRef))
+	keyPath := filepath.Clean(utils.ReplaceHomeShortcut(keyRef))
+	raw, err := ioutil.ReadFile(keyPath)
 	if err != nil {
 		return nil, err
 	}
diff --git a/plugins/verifier/licensechecker/licensechecker.go b/plugins/verifier/licensechecker/licensechecker.go
index c6adcdd..c04fe14 100644
--- a/plugins/verifier/licensechecker/licensechecker.go
+++ b/plugins/verifier/licensechecker/licensechecker.go
@@ -68,7 +68,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, desc
 	}
 
 	for _, blobDesc := range referenceManifest.Blobs {
-		refBlob, err := store.GetBlobContent(ctx, subjectReference, blobDesc.Digest, blobDesc)
+		refBlob, err := store.GetBlobContent(ctx, subjectReference, blobDesc.Digest)
 		if err != nil {
 			return nil, err
 		}
diff --git a/plugins/verifier/sbom/sbom.go b/plugins/verifier/sbom/sbom.go
index 0812c81..0908026 100644
--- a/plugins/verifier/sbom/sbom.go
+++ b/plugins/verifier/sbom/sbom.go
@@ -76,7 +76,7 @@ func VerifyReference(args *skel.CmdArgs, subjectReference common.Reference, refe
 	}
 
 	for _, blobDesc := range referenceManifest.Blobs {
-		refBlob, err := referrerStore.GetBlobContent(ctx, subjectReference, blobDesc.Digest, blobDesc)
+		refBlob, err := referrerStore.GetBlobContent(ctx, subjectReference, blobDesc.Digest)
 		if err != nil {
 			return nil, err
 		}


Test patch:
diff --git a/pkg/referrerstore/plugin/plugin_test.go b/pkg/referrerstore/plugin/plugin_test.go
index 766a2fd..b0a72d6 100644
--- a/pkg/referrerstore/plugin/plugin_test.go
+++ b/pkg/referrerstore/plugin/plugin_test.go
@@ -26,7 +26,6 @@ import (
 	"github.com/deislabs/ratify/pkg/ocispecs"
 	"github.com/deislabs/ratify/pkg/referrerstore/config"
 	"github.com/opencontainers/go-digest"
-	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 )
 
 type TestExecutor struct {
@@ -111,7 +110,7 @@ func TestPluginMain_GetBlobContent_InvokeExpected(t *testing.T) {
 	subject := common.Reference{
 		Original: "localhost",
 	}
-	result, err := storePlugin.GetBlobContent(context.Background(), subject, "", v1.Descriptor{})
+	result, err := storePlugin.GetBlobContent(context.Background(), subject, "")
 	if err != nil {
 		t.Fatalf("plugin execution failed %v", err)
 	}
diff --git a/pkg/referrerstore/plugin/skel/skel_test.go b/pkg/referrerstore/plugin/skel/skel_test.go
index 8c30623..7348227 100644
--- a/pkg/referrerstore/plugin/skel/skel_test.go
+++ b/pkg/referrerstore/plugin/skel/skel_test.go
@@ -31,7 +31,7 @@ import (
 )
 
 func TestPluginMain_GetBlobContent_ReturnsExpected(t *testing.T) {
-	getBlobContent := func(args *CmdArgs, subjectReference common.Reference, digest digest.Digest, blobDesc v1.Descriptor) ([]byte, error) {
+	getBlobContent := func(args *CmdArgs, subjectReference common.Reference, digest digest.Digest) ([]byte, error) {
 		return []byte(digest.String()), nil
 	}
 	environment := map[string]string{
@@ -171,7 +171,7 @@ func TestPluginMain_GetSubjectDesc_ReturnsExpected(t *testing.T) {
 }
 
 func TestPluginMain_ErrorCases(t *testing.T) {
-	getBlobContent := func(args *CmdArgs, subjectReference common.Reference, digest digest.Digest, blobDesc v1.Descriptor) ([]byte, error) {
+	getBlobContent := func(args *CmdArgs, subjectReference common.Reference, digest digest.Digest) ([]byte, error) {
 		return nil, fmt.Errorf("simulated error")
 	}
 	environment := map[string]string{
@@ -244,7 +244,7 @@ func TestPluginMain_ErrorCases(t *testing.T) {
 }
 
 func TestPluginMain_GetBlobContent_ErrorCases(t *testing.T) {
-	getBlobContent := func(args *CmdArgs, subjectReference common.Reference, digest digest.Digest, blobDesc v1.Descriptor) ([]byte, error) {
+	getBlobContent := func(args *CmdArgs, subjectReference common.Reference, digest digest.Digest) ([]byte, error) {
 		return []byte(digest.String()), nil
 	}
 	environment := map[string]string{
diff --git a/pkg/utils/certificateUtil_test.go b/pkg/utils/certificateUtil_test.go
index cb0e07a..7ab5c24 100644
--- a/pkg/utils/certificateUtil_test.go
+++ b/pkg/utils/certificateUtil_test.go
@@ -18,8 +18,34 @@ import (
 	"io/ioutil"
 	"os"
 	"testing"
+
+	"github.com/deislabs/ratify/pkg/homedir"
 )
 
+func TestHomepathReplacement(t *testing.T) {
+
+	sampleFoldername := "test"
+	testPath := homedir.GetShortcutString() + string(os.PathSeparator) + sampleFoldername
+
+	result := ReplaceHomeShortcut(testPath)
+	home, err := os.UserHomeDir()
+	expectedPath := home + "/" + sampleFoldername
+
+	if result != expectedPath {
+		t.Fatalf("sample input %v ,result expected to be %v, actual result %v, error %v", testPath, expectedPath, result, err)
+	}
+}
+
+func TestReadCertificatesFromPath_InvalidPath(t *testing.T) {
+
+	files, err := GetCertificatesFromPath("/invalid/path")
+
+	expectedFileCount := 0
+	if len(files) != expectedFileCount || err != nil {
+		t.Fatalf("response length expected to be %v, actual %v, error %v", expectedFileCount, len(files), err)
+	}
+}
+
 func TestReadCertificatesFromPath_NestedDirectory(t *testing.T) {
 	// Setup to create nested directory structure
 	testDir := "TestDirectory"
@@ -50,6 +76,56 @@ func TestReadCertificatesFromPath_NestedDirectory(t *testing.T) {
 	}
 }
 
+func TestReadFilesFromPath_SymbolicLink(t *testing.T) {
+
+	// Setup
+	currPath, _ := os.Getwd()
+	testDirName := "TestDirectory"
+	absTestDirPath := currPath + string(os.PathSeparator) + testDirName + string(os.PathSeparator)
+	testFile1 := absTestDirPath + "file1.Crt"
+
+	setupDirectoryForTesting(t, testDirName)
+	createCertFile(t, testFile1)
+
+	symlink := absTestDirPath + "symlink"
+	os.Symlink(testFile1, symlink)
+	files, err := GetCertificatesFromPath(symlink)
+
+	// Teardown
+	os.RemoveAll(absTestDirPath)
+
+	// Validate
+	if len(files) != 1 || err != nil {
+		t.Fatalf("response length expected to be 1, actual %v, error %v", len(files), err)
+	}
+}
+
+func TestReadFilesFromPath_MultilevelSymbolicLink(t *testing.T) {
+
+	// Setup
+	currPath, _ := os.Getwd()
+	testDirName := "TestDirectory"
+	absTestDirPath := currPath + string(os.PathSeparator) + testDirName + string(os.PathSeparator)
+	testFile1 := absTestDirPath + "file1.Crt"
+
+	setupDirectoryForTesting(t, testDirName)
+	createCertFile(t, testFile1)
+
+	symlink := absTestDirPath + "symlink"
+	twoLevelSymlink := absTestDirPath + "symlink2"
+	os.Symlink(testFile1, symlink)
+	os.Symlink(symlink, twoLevelSymlink)
+	files, err := GetCertificatesFromPath(twoLevelSymlink)
+
+	// Teardown
+	os.RemoveAll(absTestDirPath)
+
+	// validate
+	if len(files) != 1 || err != nil {
+		t.Fatalf("response length expected to be 1, actual %v, error %v", len(files), err)
+	}
+}
+
 func TestReadFilesFromPath_SingleFile(t *testing.T) {
 	// Setup
 	testDir := "TestDirectory"
diff --git a/pkg/verifier/factory/factory_test.go b/pkg/verifier/factory/factory_test.go
index 0b199d8..cf53edb 100644
--- a/pkg/verifier/factory/factory_test.go
+++ b/pkg/verifier/factory/factory_test.go
@@ -45,7 +45,7 @@ func (s *TestVerifier) Verify(ctx context.Context,
 	referenceDescriptor ocispecs.ReferenceDescriptor,
 	referrerStore referrerstore.ReferrerStore,
 	executor executor.Executor) (verifier.VerifierResult, error) {
-	return verifier.VerifierResult{}, nil
+	return verifier.VerifierResult{IsSuccess: false}, nil
 }
 
 func (f *TestVerifierFactory) Create(version string, verifierConfig config.VerifierConfig) (verifier.ReferenceVerifier, error) {