Repository: ratify-project/ratify
Pull request #1727 (2024-08-19 15:48:47)

feat: KMP periodic retrieval with k8s requeue
See #1625 for details.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project) Report
Attention: Patch coverage is `83.52941%` with `42 lines` in your changes missing coverage. Please review.

| [Files](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project) | Patch % | Lines |
|---|---|---|
| [pkg/keymanagementprovider/refresh/kubeRefresh.go](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&filepath=pkg%2Fkeymanagementprovider%2Frefresh%2FkubeRefresh.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project#diff-cGtnL2tleW1hbmFnZW1lbnRwcm92aWRlci9yZWZyZXNoL2t1YmVSZWZyZXNoLmdv) | 81.81% | [13 Missing and 5 partials :warning: ](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project) |
| [...anagementprovider/refresh/kubeRefreshNamespaced.go](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&filepath=pkg%2Fkeymanagementprovider%2Frefresh%2FkubeRefreshNamespaced.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project#diff-cGtnL2tleW1hbmFnZW1lbnRwcm92aWRlci9yZWZyZXNoL2t1YmVSZWZyZXNoTmFtZXNwYWNlZC5nbw==) | 81.81% | [13 Missing and 5 partials :warning: ](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project) |
| [...lusterresource/keymanagementprovider\_controller.go](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&filepath=pkg%2Fcontrollers%2Fclusterresource%2Fkeymanagementprovider_controller.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project#diff-cGtnL2NvbnRyb2xsZXJzL2NsdXN0ZXJyZXNvdXJjZS9rZXltYW5hZ2VtZW50cHJvdmlkZXJfY29udHJvbGxlci5nbw==) | 85.71% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project) |
| [...espaceresource/keymanagementprovider\_controller.go](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&filepath=pkg%2Fcontrollers%2Fnamespaceresource%2Fkeymanagementprovider_controller.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project#diff-cGtnL2NvbnRyb2xsZXJzL25hbWVzcGFjZXJlc291cmNlL2tleW1hbmFnZW1lbnRwcm92aWRlcl9jb250cm9sbGVyLmdv) | 85.71% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project) |
| [pkg/keymanagementprovider/refresh/factory.go](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&filepath=pkg%2Fkeymanagementprovider%2Frefresh%2Ffactory.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project#diff-cGtnL2tleW1hbmFnZW1lbnRwcm92aWRlci9yZWZyZXNoL2ZhY3RvcnkuZ28=) | 88.23% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project) |

| [Files](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project) | Coverage Δ | |
|---|---|---|
| [...kg/keymanagementprovider/azurekeyvault/provider.go](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&filepath=pkg%2Fkeymanagementprovider%2Fazurekeyvault%2Fprovider.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project#diff-cGtnL2tleW1hbmFnZW1lbnRwcm92aWRlci9henVyZWtleXZhdWx0L3Byb3ZpZGVyLmdv) | `69.88% <100.00%> (+0.34%)` | :arrow_up: |
| [pkg/keymanagementprovider/inline/provider.go](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&filepath=pkg%2Fkeymanagementprovider%2Finline%2Fprovider.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project#diff-cGtnL2tleW1hbmFnZW1lbnRwcm92aWRlci9pbmxpbmUvcHJvdmlkZXIuZ28=) | `78.37% <100.00%> (+18.37%)` | :arrow_up: |
| [pkg/keymanagementprovider/keymanagementprovider.go](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&filepath=pkg%2Fkeymanagementprovider%2Fkeymanagementprovider.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project#diff-cGtnL2tleW1hbmFnZW1lbnRwcm92aWRlci9rZXltYW5hZ2VtZW50cHJvdmlkZXIuZ28=) | `92.72% <ø> (ø)` | |
| [pkg/keymanagementprovider/mocks/client.go](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&filepath=pkg%2Fkeymanagementprovider%2Fmocks%2Fclient.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project#diff-cGtnL2tleW1hbmFnZW1lbnRwcm92aWRlci9tb2Nrcy9jbGllbnQuZ28=) | `100.00% <100.00%> (ø)` | |
| [pkg/keymanagementprovider/mocks/factory.go](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&filepath=pkg%2Fkeymanagementprovider%2Fmocks%2Ffactory.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project#diff-cGtnL2tleW1hbmFnZW1lbnRwcm92aWRlci9tb2Nrcy9mYWN0b3J5Lmdv) | `100.00% <100.00%> (ø)` | |
| [pkg/keymanagementprovider/mocks/types.go](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&filepath=pkg%2Fkeymanagementprovider%2Fmocks%2Ftypes.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project#diff-cGtnL2tleW1hbmFnZW1lbnRwcm92aWRlci9tb2Nrcy90eXBlcy5nbw==) | `100.00% <100.00%> (+100.00%)` | :arrow_up: |
| [...lusterresource/keymanagementprovider\_controller.go](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&filepath=pkg%2Fcontrollers%2Fclusterresource%2Fkeymanagementprovider_controller.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project#diff-cGtnL2NvbnRyb2xsZXJzL2NsdXN0ZXJyZXNvdXJjZS9rZXltYW5hZ2VtZW50cHJvdmlkZXJfY29udHJvbGxlci5nbw==) | `68.18% <85.71%> (-6.47%)` | :arrow_down: |
| [...espaceresource/keymanagementprovider\_controller.go](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&filepath=pkg%2Fcontrollers%2Fnamespaceresource%2Fkeymanagementprovider_controller.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project#diff-cGtnL2NvbnRyb2xsZXJzL25hbWVzcGFjZXJlc291cmNlL2tleW1hbmFnZW1lbnRwcm92aWRlcl9jb250cm9sbGVyLmdv) | `59.09% <85.71%> (-15.56%)` | :arrow_down: |
| [pkg/keymanagementprovider/refresh/factory.go](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&filepath=pkg%2Fkeymanagementprovider%2Frefresh%2Ffactory.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project#diff-cGtnL2tleW1hbmFnZW1lbnRwcm92aWRlci9yZWZyZXNoL2ZhY3RvcnkuZ28=) | `88.23% <88.23%> (ø)` | |
| [pkg/keymanagementprovider/refresh/kubeRefresh.go](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree&filepath=pkg%2Fkeymanagementprovider%2Frefresh%2FkubeRefresh.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project#diff-cGtnL2tleW1hbmFnZW1lbnRwcm92aWRlci9yZWZyZXNoL2t1YmVSZWZyZXNoLmdv) | `81.81% <81.81%> (ø)` | |
| ... and [1 more](https://app.codecov.io/gh/ratify-project/ratify/pull/1727?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project) | |

... and [27 files with indirect coverage changes](https://app.codecov.io/gh/ratify-project/ratify/pull/1727/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=ratify-project)


Patch:
diff --git a/api/v1beta1/keymanagementproviders_types.go b/api/v1beta1/keymanagementproviders_types.go
index d957f38..2797cca 100644
--- a/api/v1beta1/keymanagementproviders_types.go
+++ b/api/v1beta1/keymanagementproviders_types.go
@@ -31,6 +31,10 @@ type KeyManagementProviderSpec struct {
 	// Name of the key management provider
 	Type string `json:"type,omitempty"`
 
+	// Refresh interval for fetching the certificate/key files from the provider. Only for providers that are refreshable. The value is in the format of "1h30m" where "h" means hour and "m" means minute. Valid time units are units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
+	// +kubebuilder:default=""
+	RefreshInterval string `json:"refreshInterval,omitempty"`
+
 	// +kubebuilder:pruning:PreserveUnknownFields
 	// Parameters of the key management provider
 	Parameters runtime.RawExtension `json:"parameters,omitempty"`
diff --git a/api/v1beta1/namespacedkeymanagementprovider_types.go b/api/v1beta1/namespacedkeymanagementprovider_types.go
index f4d3e58..d124e88 100644
--- a/api/v1beta1/namespacedkeymanagementprovider_types.go
+++ b/api/v1beta1/namespacedkeymanagementprovider_types.go
@@ -32,6 +32,10 @@ type NamespacedKeyManagementProviderSpec struct {
 	// Name of the key management provider
 	Type string `json:"type,omitempty"`
 
+	// Refresh interval for the key management provider. Only used if the key management provider is refreshable. Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
+	// +kubebuilder:default=""
+	RefreshInterval string `json:"refreshInterval,omitempty"`
+
 	// +kubebuilder:pruning:PreserveUnknownFields
 	// Parameters of the key management provider
 	Parameters runtime.RawExtension `json:"parameters,omitempty"`
diff --git a/charts/ratify/crds/keymanagementprovider-customresourcedefinition.yaml b/charts/ratify/crds/keymanagementprovider-customresourcedefinition.yaml
index ec41a63..bbb7a9c 100644
--- a/charts/ratify/crds/keymanagementprovider-customresourcedefinition.yaml
+++ b/charts/ratify/crds/keymanagementprovider-customresourcedefinition.yaml
@@ -14,73 +14,87 @@ spec:
     singular: keymanagementprovider
   scope: Cluster
   versions:
-  - additionalPrinterColumns:
-    - jsonPath: .status.issuccess
-      name: IsSuccess
-      type: boolean
-    - jsonPath: .status.brieferror
-      name: Error
-      type: string
-    - jsonPath: .status.lastfetchedtime
-      name: LastFetchedTime
-      type: date
-    name: v1beta1
-    schema:
-      openAPIV3Schema:
-        description: KeyManagementProvider is the Schema for the keymanagementproviders
-          API
-        properties:
-          apiVersion:
-            description: 'APIVersion defines the versioned schema of this representation
-              of an object. Servers should convert recognized schemas to the latest
-              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
-            type: string
-          kind:
-            description: 'Kind is a string value representing the REST resource this
-              object represents. Servers may infer this from the endpoint the client
-              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
-            type: string
-          metadata:
-            type: object
-          spec:
-            description: KeyManagementProviderSpec defines the desired state of KeyManagementProvider
-            properties:
-              parameters:
-                description: Parameters of the key management provider
-                type: object
-                x-kubernetes-preserve-unknown-fields: true
-              type:
-                description: Name of the key management provider
-                type: string
-            type: object
-          status:
-            description: KeyManagementProviderStatus defines the observed state of
-              KeyManagementProvider
-            properties:
-              brieferror:
-                description: Truncated error message if the message is too long
-                type: string
-              error:
-                description: Error message if operation was unsuccessful
-                type: string
-              issuccess:
-                description: Is successful in loading certificate/key files
-                type: boolean
-              lastfetchedtime:
-                description: The time stamp of last successful certificate/key fetch
-                  operation. If operation failed, last fetched time shows the time
-                  of error
-                format: date-time
-                type: string
+    - additionalPrinterColumns:
+        - jsonPath: .status.issuccess
+          name: IsSuccess
+          type: boolean
+        - jsonPath: .status.brieferror
+          name: Error
+          type: string
+        - jsonPath: .status.lastfetchedtime
+          name: LastFetchedTime
+          type: date
+      name: v1beta1
+      schema:
+        openAPIV3Schema:
+          description:
+            KeyManagementProvider is the Schema for the keymanagementproviders
+            API
+          properties:
+            apiVersion:
+              description:
+                "APIVersion defines the versioned schema of this representation
+                of an object. Servers should convert recognized schemas to the latest
+                internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources"
+              type: string
+            kind:
+              description:
+                "Kind is a string value representing the REST resource this
+                object represents. Servers may infer this from the endpoint the client
+                submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds"
+              type: string
+            metadata:
+              type: object
+            spec:
+              description: KeyManagementProviderSpec defines the desired state of KeyManagementProvider
               properties:
-                description: provider specific properties of the each individual certificate/key
-                type: object
-                x-kubernetes-preserve-unknown-fields: true
-            required:
-            - issuccess
-            type: object
-        type: object
-    served: true
-    storage: true
-    subresources:
-      status: {}
+                refreshInterval:
+                  default: ""
+                  description:
+                    Refresh interval for fetching the certificate/key files
+                    from the provider. Only for providers that are refreshable. The
+                    value is in the format of "1h30m" where "h" means hour and "m" means
+                    minute. Valid time units are units are "ns", "us" (or "µs"), "ms",
+                    "s", "m", "h".
+                  type: string
+                parameters:
+                  description: Parameters of the key management provider
+                  type: object
+                  x-kubernetes-preserve-unknown-fields: true
+                type:
+                  description: Name of the key management provider
+                  type: string
+              type: object
+            status:
+              description:
+                KeyManagementProviderStatus defines the observed state of
+                KeyManagementProvider
+              properties:
+                brieferror:
+                  description: Truncated error message if the message is too long
+                  type: string
+                error:
+                  description: Error message if operation was unsuccessful
+                  type: string
+                issuccess:
+                  description: Is successful in loading certificate/key files
+                  type: boolean
+                lastfetchedtime:
+                  description:
+                    The time stamp of last successful certificate/key fetch
+                    operation. If operation failed, last fetched time shows the time
+                    of error
+                  format: date-time
+                  type: string
+                properties:
+                  description: provider specific properties of the each individual certificate/key
+                  type: object
+                  x-kubernetes-preserve-unknown-fields: true
+              required:
+                - issuccess
+              type: object
+          type: object
+      served: true
+      storage: true
+      subresources:
+        status: {}
diff --git a/charts/ratify/crds/namespacedkeymanagementprovider-customresourcedefinition.yaml b/charts/ratify/crds/namespacedkeymanagementprovider-customresourcedefinition.yaml
index ef70f27..4055178 100644
--- a/charts/ratify/crds/namespacedkeymanagementprovider-customresourcedefinition.yaml
+++ b/charts/ratify/crds/namespacedkeymanagementprovider-customresourcedefinition.yaml
@@ -14,74 +14,89 @@ spec:
     singular: namespacedkeymanagementprovider
   scope: Namespaced
   versions:
-  - additionalPrinterColumns:
-    - jsonPath: .status.issuccess
-      name: IsSuccess
-      type: boolean
-    - jsonPath: .status.brieferror
-      name: Error
-      type: string
-    - jsonPath: .status.lastfetchedtime
-      name: LastFetchedTime
-      type: date
-    name: v1beta1
-    schema:
-      openAPIV3Schema:
-        description: NamespacedKeyManagementProvider is the Schema for the namespacedkeymanagementproviders
-          API
-        properties:
-          apiVersion:
-            description: 'APIVersion defines the versioned schema of this representation
-              of an object. Servers should convert recognized schemas to the latest
-              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
-            type: string
-          kind:
-            description: 'Kind is a string value representing the REST resource this
-              object represents. Servers may infer this from the endpoint the client
-              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
-            type: string
-          metadata:
-            type: object
-          spec:
-            description: NamespacedKeyManagementProviderSpec defines the desired state
-              of NamespacedKeyManagementProvider
-            properties:
-              parameters:
-                description: Parameters of the key management provider
-                type: object
-                x-kubernetes-preserve-unknown-fields: true
-              type:
-                description: Name of the key management provider
-                type: string
-            type: object
-          status:
-            description: NamespacedKeyManagementProviderStatus defines the observed
-              state of NamespacedKeyManagementProvider
-            properties:
-              brieferror:
-                description: Truncated error message if the message is too long
-                type: string
-              error:
-                description: Error message if operation was unsuccessful
-                type: string
-              issuccess:
-                description: Is successful in loading certificate/key files
-                type: boolean
-              lastfetchedtime:
-                description: The time stamp of last successful certificate/key fetch
-                  operation. If operation failed, last fetched time shows the time
-                  of error
-                format: date-time
-                type: string
+    - additionalPrinterColumns:
+        - jsonPath: .status.issuccess
+          name: IsSuccess
+          type: boolean
+        - jsonPath: .status.brieferror
+          name: Error
+          type: string
+        - jsonPath: .status.lastfetchedtime
+          name: LastFetchedTime
+          type: date
+      name: v1beta1
+      schema:
+        openAPIV3Schema:
+          description:
+            NamespacedKeyManagementProvider is the Schema for the namespacedkeymanagementproviders
+            API
+          properties:
+            apiVersion:
+              description:
+                "APIVersion defines the versioned schema of this representation
+                of an object. Servers should convert recognized schemas to the latest
+                internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources"
+              type: string
+            kind:
+              description:
+                "Kind is a string value representing the REST resource this
+                object represents. Servers may infer this from the endpoint the client
+                submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds"
+              type: string
+            metadata:
+              type: object
+            spec:
+              description:
+                NamespacedKeyManagementProviderSpec defines the desired state
+                of NamespacedKeyManagementProvider
               properties:
-                description: provider specific properties of the each individual certificate/key
-                type: object
-                x-kubernetes-preserve-unknown-fields: true
-            required:
-            - issuccess
-            type: object
-        type: object
-    served: true
-    storage: true
-    subresources:
-      status: {}
+                refreshInterval:
+                  default: ""
+                  description:
+                    Refresh interval for fetching the certificate/key files
+                    from the provider. Only for providers that are refreshable. The
+                    value is in the format of "1h30m" where "h" means hour and "m" means
+                    minute. Valid time units are units are "ns", "us" (or "µs"), "ms",
+                    "s", "m", "h".
+                  type: string
+                parameters:
+                  description: Parameters of the key management provider
+                  type: object
+                  x-kubernetes-preserve-unknown-fields: true
+                type:
+                  description: Name of the key management provider
+                  type: string
+              type: object
+            status:
+              description:
+                NamespacedKeyManagementProviderStatus defines the observed
+                state of NamespacedKeyManagementProvider
+              properties:
+                brieferror:
+                  description: Truncated error message if the message is too long
+                  type: string
+                error:
+                  description: Error message if operation was unsuccessful
+                  type: string
+                issuccess:
+                  description: Is successful in loading certificate/key files
+                  type: boolean
+                lastfetchedtime:
+                  description:
+                    The time stamp of last successful certificate/key fetch
+                    operation. If operation failed, last fetched time shows the time
+                    of error
+                  format: date-time
+                  type: string
+                properties:
+                  description: provider specific properties of the each individual certificate/key
+                  type: object
+                  x-kubernetes-preserve-unknown-fields: true
+              required:
+                - issuccess
+              type: object
+          type: object
+      served: true
+      storage: true
+      subresources:
+        status: {}
diff --git a/config/crd/bases/config.ratify.deislabs.io_keymanagementproviders.yaml b/config/crd/bases/config.ratify.deislabs.io_keymanagementproviders.yaml
index d924d7d..2a3658d 100644
--- a/config/crd/bases/config.ratify.deislabs.io_keymanagementproviders.yaml
+++ b/config/crd/bases/config.ratify.deislabs.io_keymanagementproviders.yaml
@@ -54,6 +54,14 @@ spec:
                 description: Parameters of the key management provider
                 type: object
                 x-kubernetes-preserve-unknown-fields: true
+              refreshInterval:
+                default: ""
+                description: Refresh interval for fetching the certificate/key files
+                  from the provider. Only for providers that are refreshable. The
+                  value is in the format of "1h30m" where "h" means hour and "m" means
+                  minute. Valid time units are units are "ns", "us" (or "µs"), "ms",
+                  "s", "m", "h".
+                type: string
               type:
                 description: Name of the key management provider
                 type: string
diff --git a/config/crd/bases/config.ratify.deislabs.io_namespacedkeymanagementproviders.yaml b/config/crd/bases/config.ratify.deislabs.io_namespacedkeymanagementproviders.yaml
index 188294d..0054b1e 100644
--- a/config/crd/bases/config.ratify.deislabs.io_namespacedkeymanagementproviders.yaml
+++ b/config/crd/bases/config.ratify.deislabs.io_namespacedkeymanagementproviders.yaml
@@ -55,6 +55,12 @@ spec:
                 description: Parameters of the key management provider
                 type: object
                 x-kubernetes-preserve-unknown-fields: true
+              refreshInterval:
+                default: ""
+                description: Refresh interval for the key management provider. Only
+                  used if the key management provider is refreshable. Valid time units
+                  are "ns", "us" (or "µs"), "ms", "s", "m", "h".
+                type: string
               type:
                 description: Name of the key management provider
                 type: string
diff --git a/config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_akv.yaml b/config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_akv.yaml
index ae25f7f..7f92290 100644
--- a/config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_akv.yaml
+++ b/config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_akv.yaml
@@ -1,13 +1,13 @@
 apiVersion: config.ratify.deislabs.io/v1beta1
 kind: KeyManagementProvider
 metadata:
-  name: keymanagementprovider-inline
+  name: keymanagementprovider-akv
 spec:
   type: azurekeyvault
   parameters:
     vaultURI: https://yourkeyvault.vault.azure.net/
     certificates:
       - name: yourCertName
-        version: yourCertVersion # Optional, fetch latest version if empty 
+        version: yourCertVersion # Optional, fetch latest version if empty
     tenantID:
-    clientID: 
+    clientID:
diff --git a/config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_akv_refresh_enabled.yaml b/config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_akv_refresh_enabled.yaml
new file mode 100644
index 0000000..3b820f6
--- /dev/null
+++ b/config/samples/clustered/kmp/config_v1beta1_keymanagementprovider_akv_refresh_enabled.yaml
@@ -0,0 +1,14 @@
+apiVersion: config.ratify.deislabs.io/v1beta1
+kind: KeyManagementProvider
+metadata:
+  name: keymanagementprovider-akv
+spec:
+  type: azurekeyvault
+  refreshInterval: 1m
+  parameters:
+    vaultURI: https://yourkeyvault.vault.azure.net/
+    certificates:
+      - name: yourCertName
+        version: yourCertVersion # Optional, fetch latest version if empty
+    tenantID:
+    clientID:
diff --git a/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_akv.yaml b/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_akv.yaml
index e8971e1..7b9afff 100644
--- a/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_akv.yaml
+++ b/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_akv.yaml
@@ -1,13 +1,13 @@
 apiVersion: config.ratify.deislabs.io/v1beta1
 kind: NamespacedKeyManagementProvider
 metadata:
-  name: keymanagementprovider-inline
+  name: keymanagementprovider-akv
 spec:
   type: azurekeyvault
   parameters:
     vaultURI: https://yourkeyvault.vault.azure.net/
     certificates:
       - name: yourCertName
-        version: yourCertVersion # Optional, fetch latest version if empty 
+        version: yourCertVersion # Optional, fetch latest version if empty
     tenantID:
-    clientID: 
+    clientID:
diff --git a/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_akv_refresh_enabled.yaml b/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_akv_refresh_enabled.yaml
new file mode 100644
index 0000000..28aeaac
--- /dev/null
+++ b/config/samples/namespaced/kmp/config_v1beta1_keymanagementprovider_akv_refresh_enabled.yaml
@@ -0,0 +1,14 @@
+apiVersion: config.ratify.deislabs.io/v1beta1
+kind: NamespacedKeyManagementProvider
+metadata:
+  name: keymanagementprovider-akv
+spec:
+  type: azurekeyvault
+  refreshInterval: 1m
+  parameters:
+    vaultURI: https://yourkeyvault.vault.azure.net/
+    certificates:
+      - name: yourCertName
+        version: yourCertVersion # Optional, fetch latest version if empty
+    tenantID:
+    clientID:
diff --git a/pkg/controllers/clusterresource/keymanagementprovider_controller.go b/pkg/controllers/clusterresource/keymanagementprovider_controller.go
index 11a32ed..93dda98 100644
--- a/pkg/controllers/clusterresource/keymanagementprovider_controller.go
+++ b/pkg/controllers/clusterresource/keymanagementprovider_controller.go
@@ -18,24 +18,17 @@ package clusterresource
 
 import (
 	"context"
-	"encoding/json"
 	"fmt"
-	"maps"
 
-	"github.com/ratify-project/ratify/internal/constants"
 	_ "github.com/ratify-project/ratify/pkg/keymanagementprovider/azurekeyvault" // register azure key vault key management provider
 	_ "github.com/ratify-project/ratify/pkg/keymanagementprovider/inline"        // register inline key management provider
-	apierrors "k8s.io/apimachinery/pkg/api/errors"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"github.com/ratify-project/ratify/pkg/keymanagementprovider/refresh"
 	"k8s.io/apimachinery/pkg/runtime"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/predicate"
 
 	configv1beta1 "github.com/ratify-project/ratify/api/v1beta1"
-	cutils "github.com/ratify-project/ratify/pkg/controllers/utils"
-	kmp "github.com/ratify-project/ratify/pkg/keymanagementprovider"
-	"github.com/sirupsen/logrus"
 )
 
 // KeyManagementProviderReconciler reconciles a KeyManagementProvider object
@@ -44,76 +37,33 @@ type KeyManagementProviderReconciler struct {
 	Scheme *runtime.Scheme
 }
 
-// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=keymanagementproviders,verbs=get;list;watch;create;update;patch;delete
-// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=keymanagementproviders/status,verbs=get;update;patch
-// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=keymanagementproviders/finalizers,verbs=update
-func (r *KeyManagementProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
-	logger := logrus.WithContext(ctx)
-
-	var resource = req.Name
-	var keyManagementProvider configv1beta1.KeyManagementProvider
-
-	logger.Infof("reconciling cluster key management provider '%v'", resource)
-
-	if err := r.Get(ctx, req.NamespacedName, &keyManagementProvider); err != nil {
-		if apierrors.IsNotFound(err) {
-			logger.Infof("deletion detected, removing key management provider %v", resource)
-			kmp.DeleteCertificatesFromMap(resource)
-			kmp.DeleteKeysFromMap(resource)
-		} else {
-			logger.Error(err, "unable to fetch key management provider")
-		}
-
-		return ctrl.Result{}, client.IgnoreNotFound(err)
-	}
-
-	lastFetchedTime := metav1.Now()
-	isFetchSuccessful := false
-
-	// get certificate store list to check if certificate store is configured
-	// TODO: remove check in v2.0.0+
-	var certificateStoreList configv1beta1.CertificateStoreList
-	if err := r.List(ctx, &certificateStoreList); err != nil {
-		logger.Error(err, "unable to list certificate stores")
+func (r *KeyManagementProviderReconciler) ReconcileWithConfig(ctx context.Context, config map[string]interface{}) (ctrl.Result, error) {
+	refresher, err := refresh.CreateRefresherFromConfig(config)
+	if err != nil {
 		return ctrl.Result{}, err
 	}
-	// if certificate store is configured, return error. Only one of certificate store and key management provider can be configured
-	if len(certificateStoreList.Items) > 0 {
-		// Note: for backwards compatibility in upgrade scenarios, Ratify will only log a warning statement.
-		logger.Warn("Certificate Store already exists. Key management provider and certificate store should not be configured together. Please migrate to key management provider and delete certificate store.")
-	}
-
-	provider, err := cutils.SpecToKeyManagementProvider(keyManagementProvider.Spec.Parameters.Raw, keyManagementProvider.Spec.Type)
+	err = refresher.Refresh(ctx)
 	if err != nil {
-		writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil)
 		return ctrl.Result{}, err
 	}
 
-	// fetch certificates and store in map
-	certificates, certAttributes, err := provider.GetCertificates(ctx)
-	if err != nil {
-		writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil)
-		return ctrl.Result{}, fmt.Errorf("Error fetching certificates in KMProvider %v with %v provider, error: %w", resource, keyManagementProvider.Spec.Type, err)
+	result, ok := refresher.GetResult().(ctrl.Result)
+	if !ok {
+		return ctrl.Result{}, fmt.Errorf("unexpected type returned from GetResult: %T", refresher.GetResult())
 	}
+	return result, nil
+}
 
-	// fetch keys and store in map
-	keys, keyAttributes, err := provider.GetKeys(ctx)
-	if err != nil {
-		writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil)
-		return ctrl.Result{}, fmt.Errorf("Error fetching keys in KMProvider %v with %v provider, error: %w", resource, keyManagementProvider.Spec.Type, err)
+// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=keymanagementproviders,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=keymanagementproviders/status,verbs=get;update;patch
+// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=keymanagementproviders/finalizers,verbs=update
+func (r *KeyManagementProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+	refresherConfig := map[string]interface{}{
+		"type":    refresh.KubeRefresherType,
+		"client":  r.Client,
+		"request": req,
 	}
-	kmp.SetCertificatesInMap(resource, certificates)
-	kmp.SetKeysInMap(resource, keyManagementProvider.Spec.Type, keys)
-	// merge certificates and keys status into one
-	maps.Copy(keyAttributes, certAttributes)
-	isFetchSuccessful = true
-	emptyErrorString := ""
-	writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, emptyErrorString, lastFetchedTime, keyAttributes)
-
-	logger.Infof("%v certificate(s) & %v key(s) fetched for key management provider %v", len(certificates), len(keys), resource)
-
-	// returning empty result and no error to indicate we’ve successfully reconciled this object
-	return ctrl.Result{}, nil
+	return r.ReconcileWithConfig(ctx, refresherConfig)
 }
 
 // SetupWithManager sets up the controller with the Manager.
@@ -127,46 +77,3 @@ func (r *KeyManagementProviderReconciler) SetupWithManager(mgr ctrl.Manager) err
 		For(&configv1beta1.KeyManagementProvider{}).WithEventFilter(pred).
 		Complete(r)
 }
-
-// writeKMProviderStatus updates the status of the key management provider resource
-func writeKMProviderStatus(ctx context.Context, r client.StatusClient, keyManagementProvider *configv1beta1.KeyManagementProvider, logger *logrus.Entry, isSuccess bool, errorString string, operationTime metav1.Time, kmProviderStatus kmp.KeyManagementProviderStatus) {
-	if isSuccess {
-		updateKMProviderSuccessStatus(keyManagementProvider, &operationTime, kmProviderStatus)
-	} else {
-		updateKMProviderErrorStatus(keyManagementProvider, errorString, &operationTime)
-	}
-	if statusErr := r.Status().Update(ctx, keyManagementProvider); statusErr != nil {
-		logger.Error(statusErr, ",unable to update key management provider error status")
-	}
-}
-
-// updateKMProviderErrorStatus updates the key management provider status with error, brief error and last fetched time
-func updateKMProviderErrorStatus(keyManagementProvider *configv1beta1.KeyManagementProvider, errorString string, operationTime *metav1.Time) {
-	// truncate brief error string to maxBriefErrLength
-	briefErr := errorString
-	if len(errorString) > constants.MaxBriefErrLength {
-		briefErr = fmt.Sprintf("%s...", errorString[:constants.MaxBriefErrLength])
-	}
-	keyManagementProvider.Status.IsSuccess = false
-	keyManagementProvider.Status.Error = errorString
-	keyManagementProvider.Status.BriefError = briefErr
-	keyManagementProvider.Status.LastFetchedTime = operationTime
-}
-
-// updateKMProviderSuccessStatus updates the key management provider status if status argument is non nil
-// Success status includes last fetched time and other provider-specific properties
-func updateKMProviderSuccessStatus(keyManagementProvider *configv1beta1.KeyManagementProvider, lastOperationTime *metav1.Time, kmProviderStatus kmp.KeyManagementProviderStatus) {
-	keyManagementProvider.Status.IsSuccess = true
-	keyManagementProvider.Status.Error = ""
-	keyManagementProvider.Status.BriefError = ""
-	keyManagementProvider.Status.LastFetchedTime = lastOperationTime
-
-	if kmProviderStatus != nil {
-		jsonString, _ := json.Marshal(kmProviderStatus)
-
-		raw := runtime.RawExtension{
-			Raw: jsonString,
-		}
-		keyManagementProvider.Status.Properties = raw
-	}
-}
diff --git a/pkg/controllers/namespaceresource/keymanagementprovider_controller.go b/pkg/controllers/namespaceresource/keymanagementprovider_controller.go
index 8216dd9..ba439a2 100644
--- a/pkg/controllers/namespaceresource/keymanagementprovider_controller.go
+++ b/pkg/controllers/namespaceresource/keymanagementprovider_controller.go
@@ -18,24 +18,17 @@ package namespaceresource
 
 import (
 	"context"
-	"encoding/json"
 	"fmt"
-	"maps"
 
-	"github.com/ratify-project/ratify/internal/constants"
 	_ "github.com/ratify-project/ratify/pkg/keymanagementprovider/azurekeyvault" // register azure key vault key management provider
 	_ "github.com/ratify-project/ratify/pkg/keymanagementprovider/inline"        // register inline key management provider
-	apierrors "k8s.io/apimachinery/pkg/api/errors"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"github.com/ratify-project/ratify/pkg/keymanagementprovider/refresh"
 	"k8s.io/apimachinery/pkg/runtime"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/predicate"
 
 	configv1beta1 "github.com/ratify-project/ratify/api/v1beta1"
-	cutils "github.com/ratify-project/ratify/pkg/controllers/utils"
-	kmp "github.com/ratify-project/ratify/pkg/keymanagementprovider"
-	"github.com/sirupsen/logrus"
 )
 
 // KeyManagementProviderReconciler reconciles a KeyManagementProvider object
@@ -44,76 +37,33 @@ type KeyManagementProviderReconciler struct {
 	Scheme *runtime.Scheme
 }
 
-// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedkeymanagementproviders,verbs=get;list;watch;create;update;patch;delete
-// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedkeymanagementproviders/status,verbs=get;update;patch
-// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedkeymanagementproviders/finalizers,verbs=update
-func (r *KeyManagementProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
-	logger := logrus.WithContext(ctx)
-
-	var resource = req.NamespacedName.String()
-	var keyManagementProvider configv1beta1.NamespacedKeyManagementProvider
-
-	logger.Infof("reconciling namespaced key management provider '%v'", resource)
-
-	if err := r.Get(ctx, req.NamespacedName, &keyManagementProvider); err != nil {
-		if apierrors.IsNotFound(err) {
-			logger.Infof("deletion detected, removing key management provider %v", resource)
-			kmp.DeleteCertificatesFromMap(resource)
-			kmp.DeleteKeysFromMap(resource)
-		} else {
-			logger.Error(err, "unable to fetch key management provider")
-		}
-
-		return ctrl.Result{}, client.IgnoreNotFound(err)
-	}
-
-	lastFetchedTime := metav1.Now()
-	isFetchSuccessful := false
-
-	// get certificate store list to check if certificate store is configured
-	// TODO: remove check in v2.0.0+
-	var certificateStoreList configv1beta1.CertificateStoreList
-	if err := r.List(ctx, &certificateStoreList); err != nil {
-		logger.Error(err, "unable to list certificate stores")
+func (r *KeyManagementProviderReconciler) ReconcileWithConfig(ctx context.Context, config map[string]interface{}) (ctrl.Result, error) {
+	refresher, err := refresh.CreateRefresherFromConfig(config)
+	if err != nil {
 		return ctrl.Result{}, err
 	}
-	// if certificate store is configured, return error. Only one of certificate store and key management provider can be configured
-	if len(certificateStoreList.Items) > 0 {
-		// Note: for backwards compatibility in upgrade scenarios, Ratify will only log a warning statement.
-		logger.Warn("Certificate Store already exists. Key management provider and certificate store should not be configured together. Please migrate to key management provider and delete certificate store.")
-	}
-
-	provider, err := cutils.SpecToKeyManagementProvider(keyManagementProvider.Spec.Parameters.Raw, keyManagementProvider.Spec.Type)
+	err = refresher.Refresh(ctx)
 	if err != nil {
-		writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil)
 		return ctrl.Result{}, err
 	}
 
-	// fetch certificates and store in map
-	certificates, certAttributes, err := provider.GetCertificates(ctx)
-	if err != nil {
-		writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil)
-		return ctrl.Result{}, fmt.Errorf("Error fetching certificates in KMProvider %v with %v provider, error: %w", resource, keyManagementProvider.Spec.Type, err)
+	result, ok := refresher.GetResult().(ctrl.Result)
+	if !ok {
+		return ctrl.Result{}, fmt.Errorf("unexpected type returned from GetResult: %T", refresher.GetResult())
 	}
+	return result, nil
+}
 
-	// fetch keys and store in map
-	keys, keyAttributes, err := provider.GetKeys(ctx)
-	if err != nil {
-		writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil)
-		return ctrl.Result{}, fmt.Errorf("Error fetching keys in KMProvider %v with %v provider, error: %w", resource, keyManagementProvider.Spec.Type, err)
+// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedkeymanagementproviders,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedkeymanagementproviders/status,verbs=get;update;patch
+// +kubebuilder:rbac:groups=config.ratify.deislabs.io,resources=namespacedkeymanagementproviders/finalizers,verbs=update
+func (r *KeyManagementProviderReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+	refresherConfig := map[string]interface{}{
+		"type":    refresh.KubeRefresherNamespacedType,
+		"client":  r.Client,
+		"request": req,
 	}
-	kmp.SetCertificatesInMap(resource, certificates)
-	kmp.SetKeysInMap(resource, keyManagementProvider.Spec.Type, keys)
-	// merge certificates and keys status into one
-	maps.Copy(keyAttributes, certAttributes)
-	isFetchSuccessful = true
-	emptyErrorString := ""
-	writeKMProviderStatus(ctx, r, &keyManagementProvider, logger, isFetchSuccessful, emptyErrorString, lastFetchedTime, keyAttributes)
-
-	logger.Infof("%v certificate(s) & %v key(s) fetched for key management provider %v", len(certificates), len(keys), resource)
-
-	// returning empty result and no error to indicate we’ve successfully reconciled this object
-	return ctrl.Result{}, nil
+	return r.ReconcileWithConfig(ctx, refresherConfig)
 }
 
 // SetupWithManager sets up the controller with the Manager.
@@ -127,46 +77,3 @@ func (r *KeyManagementProviderReconciler) SetupWithManager(mgr ctrl.Manager) err
 		For(&configv1beta1.NamespacedKeyManagementProvider{}).WithEventFilter(pred).
 		Complete(r)
 }
-
-// writeKMProviderStatus updates the status of the key management provider resource
-func writeKMProviderStatus(ctx context.Context, r client.StatusClient, keyManagementProvider *configv1beta1.NamespacedKeyManagementProvider, logger *logrus.Entry, isSuccess bool, errorString string, operationTime metav1.Time, kmProviderStatus kmp.KeyManagementProviderStatus) {
-	if isSuccess {
-		updateKMProviderSuccessStatus(keyManagementProvider, &operationTime, kmProviderStatus)
-	} else {
-		updateKMProviderErrorStatus(keyManagementProvider, errorString, &operationTime)
-	}
-	if statusErr := r.Status().Update(ctx, keyManagementProvider); statusErr != nil {
-		logger.Error(statusErr, ",unable to update key management provider error status")
-	}
-}
-
-// updateKMProviderErrorStatus updates the key management provider status with error, brief error and last fetched time
-func updateKMProviderErrorStatus(keyManagementProvider *configv1beta1.NamespacedKeyManagementProvider, errorString string, operationTime *metav1.Time) {
-	// truncate brief error string to maxBriefErrLength
-	briefErr := errorString
-	if len(errorString) > constants.MaxBriefErrLength {
-		briefErr = fmt.Sprintf("%s...", errorString[:constants.MaxBriefErrLength])
-	}
-	keyManagementProvider.Status.IsSuccess = false
-	keyManagementProvider.Status.Error = errorString
-	keyManagementProvider.Status.BriefError = briefErr
-	keyManagementProvider.Status.LastFetchedTime = operationTime
-}
-
-// updateKMProviderSuccessStatus updates the key management provider status if status argument is non nil
-// Success status includes last fetched time and other provider-specific properties
-func updateKMProviderSuccessStatus(keyManagementProvider *configv1beta1.NamespacedKeyManagementProvider, lastOperationTime *metav1.Time, kmProviderStatus kmp.KeyManagementProviderStatus) {
-	keyManagementProvider.Status.IsSuccess = true
-	keyManagementProvider.Status.Error = ""
-	keyManagementProvider.Status.BriefError = ""
-	keyManagementProvider.Status.LastFetchedTime = lastOperationTime
-
-	if kmProviderStatus != nil {
-		jsonString, _ := json.Marshal(kmProviderStatus)
-
-		raw := runtime.RawExtension{
-			Raw: jsonString,
-		}
-		keyManagementProvider.Status.Properties = raw
-	}
-}
diff --git a/pkg/keymanagementprovider/azurekeyvault/provider.go b/pkg/keymanagementprovider/azurekeyvault/provider.go
index f241147..c433117 100644
--- a/pkg/keymanagementprovider/azurekeyvault/provider.go
+++ b/pkg/keymanagementprovider/azurekeyvault/provider.go
@@ -193,6 +193,10 @@ func (s *akvKMProvider) GetKeys(ctx context.Context) (map[keymanagementprovider.
 	return keysMap, getStatusMap(keysStatus, types.KeysStatus), nil
 }
 
+func (s *akvKMProvider) IsRefreshable() bool {
+	return true
+}
+
 // azure keyvault provider certificate/key status is a map from "certificates" key or "keys" key to an array of key management provider status
 func getStatusMap(statusMap []map[string]string, contentType string) keymanagementprovider.KeyManagementProviderStatus {
 	status := keymanagementprovider.KeyManagementProviderStatus{}
diff --git a/pkg/keymanagementprovider/inline/provider.go b/pkg/keymanagementprovider/inline/provider.go
index 2bd6b83..81740a7 100644
--- a/pkg/keymanagementprovider/inline/provider.go
+++ b/pkg/keymanagementprovider/inline/provider.go
@@ -114,3 +114,7 @@ func (s *inlineKMProvider) GetCertificates(_ context.Context) (map[keymanagement
 func (s *inlineKMProvider) GetKeys(_ context.Context) (map[keymanagementprovider.KMPMapKey]crypto.PublicKey, keymanagementprovider.KeyManagementProviderStatus, error) {
 	return s.keys, nil, nil
 }
+
+func (s *inlineKMProvider) IsRefreshable() bool {
+	return false
+}
diff --git a/pkg/keymanagementprovider/keymanagementprovider.go b/pkg/keymanagementprovider/keymanagementprovider.go
index 714deda..3c9c695 100644
--- a/pkg/keymanagementprovider/keymanagementprovider.go
+++ b/pkg/keymanagementprovider/keymanagementprovider.go
@@ -54,6 +54,8 @@ type KeyManagementProvider interface {
 	GetCertificates(ctx context.Context) (map[KMPMapKey][]*x509.Certificate, KeyManagementProviderStatus, error)
 	// Returns an array of keys and the provider specific key attributes
 	GetKeys(ctx context.Context) (map[KMPMapKey]crypto.PublicKey, KeyManagementProviderStatus, error)
+	// Returns if the provider supports refreshing of certificates/keys
+	IsRefreshable() bool
 }
 
 // static concurrency-safe map to store certificates fetched from key management provider
diff --git a/pkg/keymanagementprovider/mocks/client.go b/pkg/keymanagementprovider/mocks/client.go
new file mode 100644
index 0000000..f24c190
--- /dev/null
+++ b/pkg/keymanagementprovider/mocks/client.go
@@ -0,0 +1,32 @@
+/*
+Copyright The Ratify Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package mocks
+
+import (
+	"context"
+	"fmt"
+
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type TestClient struct {
+	client.Client
+}
+
+func (m TestClient) Get(_ context.Context, _ types.NamespacedName, _ client.Object, _ ...client.GetOption) error {
+	return fmt.Errorf("error")
+}
diff --git a/pkg/keymanagementprovider/mocks/factory.go b/pkg/keymanagementprovider/mocks/factory.go
new file mode 100644
index 0000000..0230582
--- /dev/null
+++ b/pkg/keymanagementprovider/mocks/factory.go
@@ -0,0 +1,33 @@
+/*
+Copyright The Ratify Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package mocks
+
+import (
+	"crypto"
+	"crypto/x509"
+
+	"github.com/ratify-project/ratify/pkg/keymanagementprovider"
+	"github.com/ratify-project/ratify/pkg/keymanagementprovider/config"
+)
+
+type TestKeyManagementProviderFactory struct {
+}
+
+func (f *TestKeyManagementProviderFactory) Create(_ string, _ config.KeyManagementProviderConfig, _ string) (keymanagementprovider.KeyManagementProvider, error) {
+	var certMap map[keymanagementprovider.KMPMapKey][]*x509.Certificate
+	var keyMap map[keymanagementprovider.KMPMapKey]crypto.PublicKey
+	return &TestKeyManagementProvider{certificates: certMap, keys: keyMap}, nil
+}
diff --git a/pkg/keymanagementprovider/mocks/types.go b/pkg/keymanagementprovider/mocks/types.go
index 829b08e..246de21 100644
--- a/pkg/keymanagementprovider/mocks/types.go
+++ b/pkg/keymanagementprovider/mocks/types.go
@@ -37,3 +37,7 @@ func (c *TestKeyManagementProvider) GetCertificates(_ context.Context) (map[keym
 func (c *TestKeyManagementProvider) GetKeys(_ context.Context) (map[keymanagementprovider.KMPMapKey]crypto.PublicKey, keymanagementprovider.KeyManagementProviderStatus, error) {
 	return c.keys, c.status, c.err
 }
+
+func (c *TestKeyManagementProvider) IsRefreshable() bool {
+	return true
+}
diff --git a/pkg/keymanagementprovider/refresh/factory.go b/pkg/keymanagementprovider/refresh/factory.go
new file mode 100644
index 0000000..186951c
--- /dev/null
+++ b/pkg/keymanagementprovider/refresh/factory.go
@@ -0,0 +1,53 @@
+/*
+Copyright The Ratify Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package refresh
+
+import "fmt"
+
+var refresherFactories = make(map[string]RefresherFactory)
+
+type RefresherFactory interface {
+	// Create creates a new instance of the refresher using the provided configuration
+	Create(config map[string]interface{}) (Refresher, error)
+}
+
+// Refresher is an interface that defines methods to be implemented by a each refresher
+func Register(name string, factory RefresherFactory) {
+	if factory == nil {
+		panic("refresher factory cannot be nil")
+	}
+	_, registered := refresherFactories[name]
+	if registered {
+		panic(fmt.Sprintf("refresher factory named %s already registered", name))
+	}
+	refresherFactories[name] = factory
+}
+
+// CreateRefresherFromConfig creates a new instance of the refresher using the provided configuration
+func CreateRefresherFromConfig(refresherConfig map[string]interface{}) (Refresher, error) {
+	refresherType, ok := refresherConfig["type"].(string)
+	if !ok {
+		return nil, fmt.Errorf("refresher type is not a string")
+	}
+	if !ok || refresherType == "" {
+		return nil, fmt.Errorf("refresher type cannot be empty")
+	}
+	factory, ok := refresherFactories[refresherType]
+	if !ok {
+		return nil, fmt.Errorf("refresher factory with name %s not found", refresherType)
+	}
+	return factory.Create(refresherConfig)
+}
diff --git a/pkg/keymanagementprovider/refresh/kubeRefresh.go b/pkg/keymanagementprovider/refresh/kubeRefresh.go
new file mode 100644
index 0000000..21098d4
--- /dev/null
+++ b/pkg/keymanagementprovider/refresh/kubeRefresh.go
@@ -0,0 +1,210 @@
+/*
+Copyright The Ratify Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package refresh
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"maps"
+	"time"
+
+	configv1beta1 "github.com/ratify-project/ratify/api/v1beta1"
+	"github.com/ratify-project/ratify/internal/constants"
+	cutils "github.com/ratify-project/ratify/pkg/controllers/utils"
+	kmp "github.com/ratify-project/ratify/pkg/keymanagementprovider"
+	"github.com/sirupsen/logrus"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type KubeRefresher struct {
+	client.Client
+	Request ctrl.Request
+	Result  ctrl.Result
+}
+
+// Register registers the kubeRefresher factory
+func init() {
+	Register(KubeRefresherType, &KubeRefresher{})
+}
+
+// Refresh the certificates/keys for the key management provider by calling the GetCertificates and GetKeys methods
+func (kr *KubeRefresher) Refresh(ctx context.Context) error {
+	logger := logrus.WithContext(ctx)
+
+	var resource = kr.Request.Name
+	var keyManagementProvider configv1beta1.KeyManagementProvider
+
+	logger.Infof("reconciling cluster key management provider '%v'", resource)
+
+	if err := kr.Get(ctx, kr.Request.NamespacedName, &keyManagementProvider); err != nil {
+		if apierrors.IsNotFound(err) {
+			logger.Infof("deletion detected, removing key management provider %v", resource)
+			kmp.DeleteCertificatesFromMap(resource)
+			kmp.DeleteKeysFromMap(resource)
+		} else {
+			logger.Error(err, "unable to fetch key management provider")
+		}
+
+		kr.Result = ctrl.Result{}
+
+		return client.IgnoreNotFound(err)
+	}
+
+	lastFetchedTime := metav1.Now()
+	isFetchSuccessful := false
+
+	// get certificate store list to check if certificate store is configured
+	// TODO: remove check in v2.0.0+
+	var certificateStoreList configv1beta1.CertificateStoreList
+	if err := kr.List(ctx, &certificateStoreList); err != nil {
+		logger.Error(err, "unable to list certificate stores")
+		kr.Result = ctrl.Result{}
+		return err
+	}
+
+	if len(certificateStoreList.Items) > 0 {
+		// Note: for backwards compatibility in upgrade scenarios, Ratify will only log a warning statement.
+		logger.Warn("Certificate Store already exists. Key management provider and certificate store should not be configured together. Please migrate to key management provider and delete certificate store.")
+	}
+
+	provider, err := cutils.SpecToKeyManagementProvider(keyManagementProvider.Spec.Parameters.Raw, keyManagementProvider.Spec.Type)
+	if err != nil {
+		writeKMProviderStatus(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil)
+		kr.Request = ctrl.Request{}
+		return err
+	}
+
+	// fetch certificates and store in map
+	certificates, certAttributes, err := provider.GetCertificates(ctx)
+	if err != nil {
+		writeKMProviderStatus(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil)
+		kr.Request = ctrl.Request{}
+		return fmt.Errorf("error fetching certificates in KMProvider %v with %v provider, error: %w", resource, keyManagementProvider.Spec.Type, err)
+	}
+
+	// fetch keys and store in map
+	keys, keyAttributes, err := provider.GetKeys(ctx)
+	if err != nil {
+		writeKMProviderStatus(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil)
+		kr.Request = ctrl.Request{}
+		return fmt.Errorf("error fetching keys in KMProvider %v with %v provider, error: %w", resource, keyManagementProvider.Spec.Type, err)
+	}
+	kmp.SetCertificatesInMap(resource, certificates)
+	kmp.SetKeysInMap(resource, keyManagementProvider.Spec.Type, keys)
+	// merge certificates and keys status into one
+	maps.Copy(keyAttributes, certAttributes)
+	isFetchSuccessful = true
+	emptyErrorString := ""
+	writeKMProviderStatus(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, emptyErrorString, lastFetchedTime, keyAttributes)
+
+	logger.Infof("%v certificate(s) & %v key(s) fetched for key management provider %v", len(certificates), len(keys), resource)
+
+	// returning empty result and no error to indicate we’ve successfully reconciled this object
+	// will not reconcile again unless resource is recreated
+	if !provider.IsRefreshable() {
+		kr.Request = ctrl.Request{}
+		return nil
+	}
+
+	// if interval is not set, disable refresh
+	if keyManagementProvider.Spec.RefreshInterval == "" {
+		kr.Result = ctrl.Result{}
+		return nil
+	}
+	// resource is refreshable, requeue after interval
+	intervalDuration, err := time.ParseDuration(keyManagementProvider.Spec.RefreshInterval)
+	if err != nil {
+		logger.Error(err, "unable to parse interval duration")
+		kr.Result = ctrl.Result{}
+		return err
+	}
+
+	logger.Info("Reconciled KeyManagementProvider", "intervalDuration", intervalDuration)
+	kr.Result = ctrl.Result{RequeueAfter: intervalDuration}
+
+	return nil
+}
+
+// GetResult returns the result of the refresh as a ctrl.Result
+func (kr *KubeRefresher) GetResult() interface{} {
+	return kr.Result
+}
+
+// Create creates a new KubeRefresher instance
+func (kr *KubeRefresher) Create(config map[string]interface{}) (Refresher, error) {
+	client, ok := config["client"].(client.Client)
+	if !ok {
+		return nil, fmt.Errorf("client is required in config")
+	}
+
+	request, ok := config["request"].(ctrl.Request)
+	if !ok {
+		return nil, fmt.Errorf("request is required in config")
+	}
+
+	return &KubeRefresher{
+		Client:  client,
+		Request: request,
+	}, nil
+}
+
+func writeKMProviderStatus(ctx context.Context, r client.StatusClient, keyManagementProvider *configv1beta1.KeyManagementProvider, logger *logrus.Entry, isSuccess bool, errorString string, operationTime metav1.Time, kmProviderStatus kmp.KeyManagementProviderStatus) {
+	if isSuccess {
+		updateKMProviderSuccessStatus(keyManagementProvider, &operationTime, kmProviderStatus)
+	} else {
+		updateKMProviderErrorStatus(keyManagementProvider, errorString, &operationTime)
+	}
+	if statusErr := r.Status().Update(ctx, keyManagementProvider); statusErr != nil {
+		logger.Error(statusErr, ",unable to update key management provider error status")
+	}
+}
+
+// updateKMProviderErrorStatus updates the key management provider status with error, brief error and last fetched time
+func updateKMProviderErrorStatus(keyManagementProvider *configv1beta1.KeyManagementProvider, errorString string, operationTime *metav1.Time) {
+	// truncate brief error string to maxBriefErrLength
+	briefErr := errorString
+	if len(errorString) > constants.MaxBriefErrLength {
+		briefErr = fmt.Sprintf("%s...", errorString[:constants.MaxBriefErrLength])
+	}
+	keyManagementProvider.Status.IsSuccess = false
+	keyManagementProvider.Status.Error = errorString
+	keyManagementProvider.Status.BriefError = briefErr
+	keyManagementProvider.Status.LastFetchedTime = operationTime
+}
+
+// updateKMProviderSuccessStatus updates the key management provider status if status argument is non nil
+// Success status includes last fetched time and other provider-specific properties
+func updateKMProviderSuccessStatus(keyManagementProvider *configv1beta1.KeyManagementProvider, lastOperationTime *metav1.Time, kmProviderStatus kmp.KeyManagementProviderStatus) {
+	keyManagementProvider.Status.IsSuccess = true
+	keyManagementProvider.Status.Error = ""
+	keyManagementProvider.Status.BriefError = ""
+	keyManagementProvider.Status.LastFetchedTime = lastOperationTime
+
+	if kmProviderStatus != nil {
+		jsonString, _ := json.Marshal(kmProviderStatus)
+
+		raw := runtime.RawExtension{
+			Raw: jsonString,
+		}
+		keyManagementProvider.Status.Properties = raw
+	}
+}
diff --git a/pkg/keymanagementprovider/refresh/kubeRefreshNamespaced.go b/pkg/keymanagementprovider/refresh/kubeRefreshNamespaced.go
new file mode 100644
index 0000000..ab72e10
--- /dev/null
+++ b/pkg/keymanagementprovider/refresh/kubeRefreshNamespaced.go
@@ -0,0 +1,208 @@
+/*
+Copyright The Ratify Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package refresh
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"maps"
+	"time"
+
+	configv1beta1 "github.com/ratify-project/ratify/api/v1beta1"
+	"github.com/ratify-project/ratify/internal/constants"
+	cutils "github.com/ratify-project/ratify/pkg/controllers/utils"
+	kmp "github.com/ratify-project/ratify/pkg/keymanagementprovider"
+	"github.com/sirupsen/logrus"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+type KubeRefresherNamespaced struct {
+	client.Client
+	Request ctrl.Request
+	Result  ctrl.Result
+}
+
+// Register registers the kubeRefresherNamespaced factory
+func init() {
+	Register(KubeRefresherNamespacedType, &KubeRefresherNamespaced{})
+}
+
+// Refresh the certificates/keys for the key management provider by calling the GetCertificates and GetKeys methods
+func (kr *KubeRefresherNamespaced) Refresh(ctx context.Context) error {
+	logger := logrus.WithContext(ctx)
+
+	var resource = kr.Request.NamespacedName.String()
+	var keyManagementProvider configv1beta1.NamespacedKeyManagementProvider
+
+	logger.Infof("reconciling namespaced key management provider '%v'", resource)
+
+	if err := kr.Get(ctx, kr.Request.NamespacedName, &keyManagementProvider); err != nil {
+		if apierrors.IsNotFound(err) {
+			logger.Infof("deletion detected, removing key management provider %v", resource)
+			kmp.DeleteCertificatesFromMap(resource)
+			kmp.DeleteKeysFromMap(resource)
+		} else {
+			logger.Error(err, "unable to fetch key management provider")
+		}
+
+		kr.Result = ctrl.Result{}
+
+		return client.IgnoreNotFound(err)
+	}
+
+	lastFetchedTime := metav1.Now()
+	isFetchSuccessful := false
+
+	// get certificate store list to check if certificate store is configured
+	// TODO: remove check in v2.0.0+
+	var certificateStoreList configv1beta1.CertificateStoreList
+	if err := kr.List(ctx, &certificateStoreList); err != nil {
+		logger.Error(err, "unable to list certificate stores")
+		kr.Result = ctrl.Result{}
+		return err
+	}
+	// if certificate store is configured, return error. Only one of certificate store and key management provider can be configured
+	if len(certificateStoreList.Items) > 0 {
+		// Note: for backwards compatibility in upgrade scenarios, Ratify will only log a warning statement.
+		logger.Warn("Certificate Store already exists. Key management provider and certificate store should not be configured together. Please migrate to key management provider and delete certificate store.")
+	}
+
+	provider, err := cutils.SpecToKeyManagementProvider(keyManagementProvider.Spec.Parameters.Raw, keyManagementProvider.Spec.Type)
+	if err != nil {
+		writeKMProviderStatusNamespaced(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil)
+		kr.Result = ctrl.Result{}
+		return err
+	}
+
+	// fetch certificates and store in map
+	certificates, certAttributes, err := provider.GetCertificates(ctx)
+	if err != nil {
+		writeKMProviderStatusNamespaced(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil)
+		kr.Result = ctrl.Result{}
+		return fmt.Errorf("error fetching certificates in KMProvider %v with %v provider, error: %w", resource, keyManagementProvider.Spec.Type, err)
+	}
+
+	// fetch keys and store in map
+	keys, keyAttributes, err := provider.GetKeys(ctx)
+	if err != nil {
+		writeKMProviderStatusNamespaced(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, err.Error(), lastFetchedTime, nil)
+		kr.Result = ctrl.Result{}
+		return fmt.Errorf("error fetching keys in KMProvider %v with %v provider, error: %w", resource, keyManagementProvider.Spec.Type, err)
+	}
+	kmp.SetCertificatesInMap(resource, certificates)
+	kmp.SetKeysInMap(resource, keyManagementProvider.Spec.Type, keys)
+	// merge certificates and keys status into one
+	maps.Copy(keyAttributes, certAttributes)
+	isFetchSuccessful = true
+	emptyErrorString := ""
+	writeKMProviderStatusNamespaced(ctx, kr, &keyManagementProvider, logger, isFetchSuccessful, emptyErrorString, lastFetchedTime, keyAttributes)
+
+	logger.Infof("%v certificate(s) & %v key(s) fetched for key management provider %v", len(certificates), len(keys), resource)
+
+	if !provider.IsRefreshable() {
+		kr.Result = ctrl.Result{}
+		return nil
+	}
+
+	// if interval is not set, disable refresh
+	if keyManagementProvider.Spec.RefreshInterval == "" {
+		kr.Result = ctrl.Result{}
+		return nil
+	}
+
+	intervalDuration, err := time.ParseDuration(keyManagementProvider.Spec.RefreshInterval)
+	if err != nil {
+		logger.Error(err, "unable to parse interval duration")
+		kr.Result = ctrl.Result{}
+		return err
+	}
+
+	logger.Info("Reconciled KeyManagementProvider", "intervalDuration", intervalDuration)
+	kr.Result = ctrl.Result{RequeueAfter: intervalDuration}
+
+	return nil
+}
+
+// GetResult returns the result of the refresh as ctrl.Result
+func (kr *KubeRefresherNamespaced) GetResult() interface{} {
+	return kr.Result
+}
+
+// Create creates a new instance of KubeRefresherNamespaced
+func (kr *KubeRefresherNamespaced) Create(config map[string]interface{}) (Refresher, error) {
+	client, ok := config["client"].(client.Client)
+	if !ok {
+		return nil, fmt.Errorf("client is required in config")
+	}
+
+	request, ok := config["request"].(ctrl.Request)
+	if !ok {
+		return nil, fmt.Errorf("request is required in config")
+	}
+
+	return &KubeRefresherNamespaced{
+		Client:  client,
+		Request: request,
+	}, nil
+}
+
+// writeKMProviderStatus updates the status of the key management provider resource
+func writeKMProviderStatusNamespaced(ctx context.Context, r client.StatusClient, keyManagementProvider *configv1beta1.NamespacedKeyManagementProvider, logger *logrus.Entry, isSuccess bool, errorString string, operationTime metav1.Time, kmProviderStatus kmp.KeyManagementProviderStatus) {
+	if isSuccess {
+		updateKMProviderSuccessStatusNamespaced(keyManagementProvider, &operationTime, kmProviderStatus)
+	} else {
+		updateKMProviderErrorStatusNamespaced(keyManagementProvider, errorString, &operationTime)
+	}
+	if statusErr := r.Status().Update(ctx, keyManagementProvider); statusErr != nil {
+		logger.Error(statusErr, ",unable to update key management provider error status")
+	}
+}
+
+// updateKMProviderErrorStatus updates the key management provider status with error, brief error and last fetched time
+func updateKMProviderErrorStatusNamespaced(keyManagementProvider *configv1beta1.NamespacedKeyManagementProvider, errorString string, operationTime *metav1.Time) {
+	// truncate brief error string to maxBriefErrLength
+	briefErr := errorString
+	if len(errorString) > constants.MaxBriefErrLength {
+		briefErr = fmt.Sprintf("%s...", errorString[:constants.MaxBriefErrLength])
+	}
+	keyManagementProvider.Status.IsSuccess = false
+	keyManagementProvider.Status.Error = errorString
+	keyManagementProvider.Status.BriefError = briefErr
+	keyManagementProvider.Status.LastFetchedTime = operationTime
+}
+
+// updateKMProviderSuccessStatus updates the key management provider status if status argument is non nil
+// Success status includes last fetched time and other provider-specific properties
+func updateKMProviderSuccessStatusNamespaced(keyManagementProvider *configv1beta1.NamespacedKeyManagementProvider, lastOperationTime *metav1.Time, kmProviderStatus kmp.KeyManagementProviderStatus) {
+	keyManagementProvider.Status.IsSuccess = true
+	keyManagementProvider.Status.Error = ""
+	keyManagementProvider.Status.BriefError = ""
+	keyManagementProvider.Status.LastFetchedTime = lastOperationTime
+
+	if kmProviderStatus != nil {
+		jsonString, _ := json.Marshal(kmProviderStatus)
+
+		raw := runtime.RawExtension{
+			Raw: jsonString,
+		}
+		keyManagementProvider.Status.Properties = raw
+	}
+}
diff --git a/pkg/keymanagementprovider/refresh/refresh.go b/pkg/keymanagementprovider/refresh/refresh.go
new file mode 100644
index 0000000..78e32b1
--- /dev/null
+++ b/pkg/keymanagementprovider/refresh/refresh.go
@@ -0,0 +1,34 @@
+/*
+Copyright The Ratify Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package refresh
+
+import (
+	"context"
+)
+
+const (
+	KubeRefresherType           = "kubeRefresher"
+	KubeRefresherNamespacedType = "kubeRefresherNamespaced"
+)
+
+// Refresher is an interface that defines methods to be implemented by a each refresher
+type Refresher interface {
+	// Refresh is a method that refreshes the certificates/keys
+	Refresh(ctx context.Context) error
+	// GetResult is a method that returns the result of the refresh
+	GetResult() interface{}
+}


Test patch:
diff --git a/pkg/controllers/clusterresource/keymanagementprovider_controller_test.go b/pkg/controllers/clusterresource/keymanagementprovider_controller_test.go
index caa2011..d4129e2 100644
--- a/pkg/controllers/clusterresource/keymanagementprovider_controller_test.go
+++ b/pkg/controllers/clusterresource/keymanagementprovider_controller_test.go
@@ -17,267 +17,143 @@ package clusterresource
 
 import (
 	"context"
-	"fmt"
+	"errors"
+	"reflect"
 	"testing"
 
-	configv1beta1 "github.com/ratify-project/ratify/api/v1beta1"
-	"github.com/ratify-project/ratify/internal/constants"
-	"github.com/ratify-project/ratify/pkg/keymanagementprovider"
-	"github.com/sirupsen/logrus"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/client/fake"
-	"sigs.k8s.io/controller-runtime/pkg/reconcile"
-
+	"github.com/ratify-project/ratify/pkg/keymanagementprovider/refresh"
 	test "github.com/ratify-project/ratify/pkg/utils"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
-	"k8s.io/apimachinery/pkg/types"
-)
-
-const (
-	kmpName = "kmpName"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 )
 
-// TestUpdateErrorStatus tests the updateErrorStatus method
-func TestKMProviderUpdateErrorStatus(t *testing.T) {
-	var parametersString = "{\"certs\":{\"name\":\"certName\"}}"
-	var kmProviderStatus = []byte(parametersString)
-
-	status := configv1beta1.KeyManagementProviderStatus{
-		IsSuccess: true,
-		Properties: runtime.RawExtension{
-			Raw: kmProviderStatus,
+func TestKeyManagementProviderReconciler_ReconcileWithConfig(t *testing.T) {
+	tests := []struct {
+		name              string
+		refresherType     string
+		createConfigError bool
+		refreshError      bool
+		expectedError     bool
+	}{
+		{
+			name:              "Successful Reconcile",
+			refresherType:     "mockRefresher",
+			createConfigError: false,
+			refreshError:      false,
+			expectedError:     false,
+		},
+		{
+			name:              "Refresher Error",
+			refresherType:     "mockRefresher",
+			createConfigError: false,
+			refreshError:      true,
+			expectedError:     true,
+		},
+		{
+			name:              "Invalid Refresher Type",
+			refresherType:     "invalidRefresher",
+			createConfigError: true,
+			refreshError:      false,
+			expectedError:     true,
 		},
 	}
-	keyManagementProvider := configv1beta1.KeyManagementProvider{
-		Status: status,
-	}
-	expectedErr := "it's a long error from unit test"
-	lastFetchedTime := metav1.Now()
-	updateKMProviderErrorStatus(&keyManagementProvider, expectedErr, &lastFetchedTime)
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			req := ctrl.Request{
+				NamespacedName: client.ObjectKey{
+					Name:      "fake-name",
+					Namespace: "fake-namespace",
+				},
+			}
+			scheme, _ := test.CreateScheme()
+			client := fake.NewClientBuilder().WithScheme(scheme).Build()
 
-	if keyManagementProvider.Status.IsSuccess != false {
-		t.Fatalf("Unexpected error, expected isSuccess to be false , actual %+v", keyManagementProvider.Status.IsSuccess)
-	}
+			r := &KeyManagementProviderReconciler{
+				Client: client,
+				Scheme: runtime.NewScheme(),
+			}
 
-	if keyManagementProvider.Status.Error != expectedErr {
-		t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedErr, keyManagementProvider.Status.Error)
-	}
-	expectedBriedErr := fmt.Sprintf("%s...", expectedErr[:30])
-	if keyManagementProvider.Status.BriefError != expectedBriedErr {
-		t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedBriedErr, keyManagementProvider.Status.Error)
-	}
+			refresherConfig := map[string]interface{}{
+				"type":              tt.refresherType,
+				"client":            client,
+				"request":           req,
+				"createConfigError": tt.createConfigError,
+				"refreshError":      tt.refreshError,
+				"shouldError":       tt.expectedError,
+			}
 
-	//make sure properties of last cached cert was not overridden
-	if len(keyManagementProvider.Status.Properties.Raw) == 0 {
-		t.Fatalf("Unexpected properties,  expected %+v, got %+v", parametersString, string(keyManagementProvider.Status.Properties.Raw))
+			_, err := r.ReconcileWithConfig(context.TODO(), refresherConfig)
+			if tt.expectedError && err == nil {
+				t.Errorf("Expected error, got nil")
+			}
+			if !tt.expectedError && err != nil {
+				t.Errorf("Expected no error, got %v", err)
+			}
+		})
 	}
 }
-
-// TestKMProviderUpdateSuccessStatus tests the updateSuccessStatus method
-func TestKMProviderUpdateSuccessStatus(t *testing.T) {
-	kmProviderStatus := keymanagementprovider.KeyManagementProviderStatus{}
-	properties := map[string]string{}
-	properties["Name"] = "wabbit"
-	properties["Version"] = "ABC"
-
-	kmProviderStatus["Certificates"] = properties
-
-	lastFetchedTime := metav1.Now()
-
-	status := configv1beta1.KeyManagementProviderStatus{
-		IsSuccess: false,
-		Error:     "error from last operation",
-	}
-	keyManagementProvider := configv1beta1.KeyManagementProvider{
-		Status: status,
+func TestKeyManagementProviderReconciler_Reconcile(t *testing.T) {
+	req := ctrl.Request{
+		NamespacedName: client.ObjectKey{
+			Name:      "fake-name",
+			Namespace: "fake-namespace",
+		},
 	}
 
-	updateKMProviderSuccessStatus(&keyManagementProvider, &lastFetchedTime, kmProviderStatus)
+	// Create a fake client and scheme
+	scheme, _ := test.CreateScheme()
+	client := fake.NewClientBuilder().WithScheme(scheme).Build()
 
-	if keyManagementProvider.Status.IsSuccess != true {
-		t.Fatalf("Expected isSuccess to be true , actual %+v", keyManagementProvider.Status.IsSuccess)
+	r := &KeyManagementProviderReconciler{
+		Client: client,
+		Scheme: runtime.NewScheme(),
 	}
 
-	if keyManagementProvider.Status.Error != "" {
-		t.Fatalf("Unexpected error string, actual %+v", keyManagementProvider.Status.Error)
+	// Call the Reconcile method
+	result, err := r.Reconcile(context.TODO(), req)
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
 	}
 
-	//make sure properties of last cached cert was updated
-	if len(keyManagementProvider.Status.Properties.Raw) == 0 {
-		t.Fatalf("Properties should not be empty")
+	// Check the result
+	expectedResult := ctrl.Result{}
+	if !reflect.DeepEqual(result, expectedResult) {
+		t.Errorf("Expected result %v, got %v", expectedResult, result)
 	}
 }
 
-// TestKMProviderUpdateSuccessStatus tests the updateSuccessStatus method with empty properties
-func TestKMProviderUpdateSuccessStatus_emptyProperties(t *testing.T) {
-	lastFetchedTime := metav1.Now()
-	status := configv1beta1.KeyManagementProviderStatus{
-		IsSuccess: false,
-		Error:     "error from last operation",
-	}
-	keyManagementProvider := configv1beta1.KeyManagementProvider{
-		Status: status,
-	}
-
-	updateKMProviderSuccessStatus(&keyManagementProvider, &lastFetchedTime, nil)
-
-	if keyManagementProvider.Status.IsSuccess != true {
-		t.Fatalf("Expected isSuccess to be true , actual %+v", keyManagementProvider.Status.IsSuccess)
-	}
-
-	if keyManagementProvider.Status.Error != "" {
-		t.Fatalf("Unexpected error string, actual %+v", keyManagementProvider.Status.Error)
-	}
-
-	//make sure properties of last cached cert was updated
-	if len(keyManagementProvider.Status.Properties.Raw) != 0 {
-		t.Fatalf("Properties should be empty")
-	}
+type MockRefresher struct {
+	Results           ctrl.Result
+	CreateConfigError bool
+	RefreshError      bool
+	ShouldError       bool
 }
 
-func TestWriteKMProviderStatus(t *testing.T) {
-	logger := logrus.WithContext(context.Background())
-	lastFetchedTime := metav1.Now()
-	testCases := []struct {
-		name       string
-		isSuccess  bool
-		kmProvider *configv1beta1.KeyManagementProvider
-		errString  string
-		reconciler client.StatusClient
-	}{
-		{
-			name:       "success status",
-			isSuccess:  true,
-			errString:  "",
-			kmProvider: &configv1beta1.KeyManagementProvider{},
-			reconciler: &test.MockStatusClient{},
-		},
-		{
-			name:       "error status",
-			isSuccess:  false,
-			kmProvider: &configv1beta1.KeyManagementProvider{},
-			errString:  "a long error string that exceeds the max length of 30 characters",
-			reconciler: &test.MockStatusClient{},
-		},
-		{
-			name:       "status update failed",
-			isSuccess:  true,
-			kmProvider: &configv1beta1.KeyManagementProvider{},
-			reconciler: &test.MockStatusClient{
-				UpdateFailed: true,
-			},
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			writeKMProviderStatus(context.Background(), tc.reconciler, tc.kmProvider, logger, tc.isSuccess, tc.errString, lastFetchedTime, nil)
-
-			if tc.kmProvider.Status.IsSuccess != tc.isSuccess {
-				t.Fatalf("Expected isSuccess to be %+v , actual %+v", tc.isSuccess, tc.kmProvider.Status.IsSuccess)
-			}
-
-			if tc.kmProvider.Status.Error != tc.errString {
-				t.Fatalf("Expected Error to be %+v , actual %+v", tc.errString, tc.kmProvider.Status.Error)
-			}
-		})
+func (mr *MockRefresher) Refresh(_ context.Context) error {
+	if mr.RefreshError {
+		return errors.New("refresh error")
 	}
+	return nil
 }
 
-func TestKMPReconcile(t *testing.T) {
-	tests := []struct {
-		name             string
-		description      string
-		provider         *configv1beta1.KeyManagementProvider
-		req              *reconcile.Request
-		expectedErr      bool
-		expectedKMPCount int
-	}{
-		{
-			name:        "nonexistent KMP",
-			description: "Reconciling a non-existent KMP CR, it should be deleted from maps",
-			req: &reconcile.Request{
-				NamespacedName: types.NamespacedName{Name: "nonexistent"},
-			},
-			provider: &configv1beta1.KeyManagementProvider{
-				ObjectMeta: metav1.ObjectMeta{
-					Namespace: constants.EmptyNamespace,
-					Name:      kmpName,
-				},
-				Spec: configv1beta1.KeyManagementProviderSpec{
-					Type: "inline",
-				},
-			},
-			expectedErr:      false,
-			expectedKMPCount: 0,
-		},
-		{
-			name:        "invalid params",
-			description: "Received invalid parameters of the KMP Spec, it should fail the reconcile and return an error",
-			provider: &configv1beta1.KeyManagementProvider{
-				ObjectMeta: metav1.ObjectMeta{
-					Namespace: constants.EmptyNamespace,
-					Name:      kmpName,
-				},
-				Spec: configv1beta1.KeyManagementProviderSpec{
-					Type: "inline",
-				},
-			},
-			expectedErr:      true,
-			expectedKMPCount: 0,
-		},
-		{
-			name:        "valid params",
-			description: "Received a valid KMP manifest, it should be added to the cert map",
-			provider: &configv1beta1.KeyManagementProvider{
-				ObjectMeta: metav1.ObjectMeta{
-					Namespace: constants.EmptyNamespace,
-					Name:      kmpName,
-				},
-				Spec: configv1beta1.KeyManagementProviderSpec{
-					Type: "inline",
-					Parameters: runtime.RawExtension{
-						Raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`),
-					},
-				},
-			},
-			expectedErr:      false,
-			expectedKMPCount: 1,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			resetKMP()
-			scheme, _ := test.CreateScheme()
-			client := fake.NewClientBuilder().WithScheme(scheme)
-			client.WithObjects(tt.provider)
-			r := &KeyManagementProviderReconciler{
-				Scheme: scheme,
-				Client: client.Build(),
-			}
-			var req reconcile.Request
-			if tt.req != nil {
-				req = *tt.req
-			} else {
-				req = reconcile.Request{
-					NamespacedName: test.KeyFor(tt.provider),
-				}
-			}
+func (mr *MockRefresher) GetResult() interface{} {
+	return ctrl.Result{}
+}
 
-			_, err := r.Reconcile(context.Background(), req)
-			if tt.expectedErr != (err != nil) {
-				t.Fatalf("Reconcile() expected error %v, actual %v", tt.expectedErr, err)
-			}
-			certs, _ := keymanagementprovider.GetCertificatesFromMap(context.Background(), kmpName)
-			if len(certs) != tt.expectedKMPCount {
-				t.Fatalf("Cert map expected size %v, actual %v", tt.expectedKMPCount, len(certs))
-			}
-		})
+func (mr *MockRefresher) Create(config map[string]interface{}) (refresh.Refresher, error) {
+	createConfigError := config["createConfigError"].(bool)
+	refreshError := config["refreshError"].(bool)
+	if createConfigError {
+		return nil, errors.New("create error")
 	}
+	return &MockRefresher{
+		CreateConfigError: createConfigError,
+		RefreshError:      refreshError,
+	}, nil
 }
 
-func resetKMP() {
-	keymanagementprovider.DeleteCertificatesFromMap(storeName)
+func init() {
+	refresh.Register("mockRefresher", &MockRefresher{})
 }
diff --git a/pkg/controllers/namespaceresource/keymanagementprovider_controller_test.go b/pkg/controllers/namespaceresource/keymanagementprovider_controller_test.go
index fe0728d..7e4717a 100644
--- a/pkg/controllers/namespaceresource/keymanagementprovider_controller_test.go
+++ b/pkg/controllers/namespaceresource/keymanagementprovider_controller_test.go
@@ -17,264 +17,144 @@ package namespaceresource
 
 import (
 	"context"
-	"fmt"
+	"errors"
+	"reflect"
 	"testing"
 
-	configv1beta1 "github.com/ratify-project/ratify/api/v1beta1"
-	"github.com/ratify-project/ratify/pkg/keymanagementprovider"
-	"github.com/sirupsen/logrus"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/client/fake"
-	"sigs.k8s.io/controller-runtime/pkg/reconcile"
-
-	"github.com/ratify-project/ratify/internal/constants"
-	ctxUtils "github.com/ratify-project/ratify/internal/context"
+	"github.com/ratify-project/ratify/pkg/keymanagementprovider/refresh"
 	test "github.com/ratify-project/ratify/pkg/utils"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
-	"k8s.io/apimachinery/pkg/types"
-)
-
-const (
-	kmpName = "kmpName"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 )
 
-// TestUpdateErrorStatus tests the updateErrorStatus method
-func TestKMProviderUpdateErrorStatus(t *testing.T) {
-	var parametersString = "{\"certs\":{\"name\":\"certName\"}}"
-	var kmProviderStatus = []byte(parametersString)
-
-	status := configv1beta1.NamespacedKeyManagementProviderStatus{
-		IsSuccess: true,
-		Properties: runtime.RawExtension{
-			Raw: kmProviderStatus,
+func TestKeyManagementProviderReconciler_ReconcileWithConfig(t *testing.T) {
+	tests := []struct {
+		name              string
+		refresherType     string
+		createConfigError bool
+		refreshError      bool
+		expectedError     bool
+	}{
+		{
+			name:              "Successful Reconcile",
+			refresherType:     "mockRefresher",
+			createConfigError: false,
+			refreshError:      false,
+			expectedError:     false,
+		},
+		{
+			name:              "Refresher Error",
+			refresherType:     "mockRefresher",
+			createConfigError: false,
+			refreshError:      true,
+			expectedError:     true,
+		},
+		{
+			name:              "Invalid Refresher Type",
+			refresherType:     "invalidRefresher",
+			createConfigError: true,
+			refreshError:      false,
+			expectedError:     true,
 		},
 	}
-	keyManagementProvider := configv1beta1.NamespacedKeyManagementProvider{
-		Status: status,
-	}
-	expectedErr := "it's a long error from unit test"
-	lastFetchedTime := metav1.Now()
-	updateKMProviderErrorStatus(&keyManagementProvider, expectedErr, &lastFetchedTime)
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			req := ctrl.Request{
+				NamespacedName: client.ObjectKey{
+					Name:      "fake-name",
+					Namespace: "fake-namespace",
+				},
+			}
+			scheme, _ := test.CreateScheme()
+			client := fake.NewClientBuilder().WithScheme(scheme).Build()
 
-	if keyManagementProvider.Status.IsSuccess != false {
-		t.Fatalf("Unexpected error, expected isSuccess to be false , actual %+v", keyManagementProvider.Status.IsSuccess)
-	}
+			r := &KeyManagementProviderReconciler{
+				Client: client,
+				Scheme: runtime.NewScheme(),
+			}
 
-	if keyManagementProvider.Status.Error != expectedErr {
-		t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedErr, keyManagementProvider.Status.Error)
-	}
-	expectedBriedErr := fmt.Sprintf("%s...", expectedErr[:30])
-	if keyManagementProvider.Status.BriefError != expectedBriedErr {
-		t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedBriedErr, keyManagementProvider.Status.Error)
-	}
+			refresherConfig := map[string]interface{}{
+				"type":              tt.refresherType,
+				"client":            client,
+				"request":           req,
+				"createConfigError": tt.createConfigError,
+				"refreshError":      tt.refreshError,
+				"shouldError":       tt.expectedError,
+			}
 
-	//make sure properties of last cached cert was not overridden
-	if len(keyManagementProvider.Status.Properties.Raw) == 0 {
-		t.Fatalf("Unexpected properties,  expected %+v, got %+v", parametersString, string(keyManagementProvider.Status.Properties.Raw))
+			_, err := r.ReconcileWithConfig(context.TODO(), refresherConfig)
+			if tt.expectedError && err == nil {
+				t.Errorf("Expected error, got nil")
+			}
+			if !tt.expectedError && err != nil {
+				t.Errorf("Expected no error, got %v", err)
+			}
+		})
 	}
 }
 
-// TestKMProviderUpdateSuccessStatus tests the updateSuccessStatus method
-func TestKMProviderUpdateSuccessStatus(t *testing.T) {
-	kmProviderStatus := keymanagementprovider.KeyManagementProviderStatus{}
-	properties := map[string]string{}
-	properties["Name"] = "wabbit"
-	properties["Version"] = "ABC"
-
-	kmProviderStatus["Certificates"] = properties
-
-	lastFetchedTime := metav1.Now()
-
-	status := configv1beta1.NamespacedKeyManagementProviderStatus{
-		IsSuccess: false,
-		Error:     "error from last operation",
-	}
-	keyManagementProvider := configv1beta1.NamespacedKeyManagementProvider{
-		Status: status,
+func TestKeyManagementProviderReconciler_Reconcile(t *testing.T) {
+	req := ctrl.Request{
+		NamespacedName: client.ObjectKey{
+			Name:      "fake-name",
+			Namespace: "fake-namespace",
+		},
 	}
 
-	updateKMProviderSuccessStatus(&keyManagementProvider, &lastFetchedTime, kmProviderStatus)
+	// Create a fake client and scheme
+	scheme, _ := test.CreateScheme()
+	client := fake.NewClientBuilder().WithScheme(scheme).Build()
 
-	if keyManagementProvider.Status.IsSuccess != true {
-		t.Fatalf("Expected isSuccess to be true , actual %+v", keyManagementProvider.Status.IsSuccess)
+	r := &KeyManagementProviderReconciler{
+		Client: client,
+		Scheme: runtime.NewScheme(),
 	}
 
-	if keyManagementProvider.Status.Error != "" {
-		t.Fatalf("Unexpected error string, actual %+v", keyManagementProvider.Status.Error)
+	// Call the Reconcile method
+	result, err := r.Reconcile(context.TODO(), req)
+	if err != nil {
+		t.Errorf("Unexpected error: %v", err)
 	}
 
-	//make sure properties of last cached cert was updated
-	if len(keyManagementProvider.Status.Properties.Raw) == 0 {
-		t.Fatalf("Properties should not be empty")
+	// Check the result
+	expectedResult := ctrl.Result{}
+	if !reflect.DeepEqual(result, expectedResult) {
+		t.Errorf("Expected result %v, got %v", expectedResult, result)
 	}
 }
 
-// TestKMProviderUpdateSuccessStatus tests the updateSuccessStatus method with empty properties
-func TestKMProviderUpdateSuccessStatus_emptyProperties(t *testing.T) {
-	lastFetchedTime := metav1.Now()
-	status := configv1beta1.NamespacedKeyManagementProviderStatus{
-		IsSuccess: false,
-		Error:     "error from last operation",
-	}
-	keyManagementProvider := configv1beta1.NamespacedKeyManagementProvider{
-		Status: status,
-	}
-
-	updateKMProviderSuccessStatus(&keyManagementProvider, &lastFetchedTime, nil)
-
-	if keyManagementProvider.Status.IsSuccess != true {
-		t.Fatalf("Expected isSuccess to be true , actual %+v", keyManagementProvider.Status.IsSuccess)
-	}
-
-	if keyManagementProvider.Status.Error != "" {
-		t.Fatalf("Unexpected error string, actual %+v", keyManagementProvider.Status.Error)
-	}
-
-	//make sure properties of last cached cert was updated
-	if len(keyManagementProvider.Status.Properties.Raw) != 0 {
-		t.Fatalf("Properties should be empty")
-	}
+type MockRefresher struct {
+	Results           ctrl.Result
+	CreateConfigError bool
+	RefreshError      bool
+	ShouldError       bool
 }
 
-func TestWriteKMProviderStatus(t *testing.T) {
-	logger := logrus.WithContext(context.Background())
-	lastFetchedTime := metav1.Now()
-	testCases := []struct {
-		name       string
-		isSuccess  bool
-		kmProvider *configv1beta1.NamespacedKeyManagementProvider
-		errString  string
-		reconciler client.StatusClient
-	}{
-		{
-			name:       "success status",
-			isSuccess:  true,
-			errString:  "",
-			kmProvider: &configv1beta1.NamespacedKeyManagementProvider{},
-			reconciler: &test.MockStatusClient{},
-		},
-		{
-			name:       "error status",
-			isSuccess:  false,
-			kmProvider: &configv1beta1.NamespacedKeyManagementProvider{},
-			errString:  "a long error string that exceeds the max length of 30 characters",
-			reconciler: &test.MockStatusClient{},
-		},
-		{
-			name:       "status update failed",
-			isSuccess:  true,
-			kmProvider: &configv1beta1.NamespacedKeyManagementProvider{},
-			reconciler: &test.MockStatusClient{
-				UpdateFailed: true,
-			},
-		},
+func (mr *MockRefresher) Refresh(_ context.Context) error {
+	if mr.RefreshError {
+		return errors.New("refresh error")
 	}
+	return nil
+}
 
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			writeKMProviderStatus(context.Background(), tc.reconciler, tc.kmProvider, logger, tc.isSuccess, tc.errString, lastFetchedTime, nil)
-
-			if tc.kmProvider.Status.IsSuccess != tc.isSuccess {
-				t.Fatalf("Expected isSuccess to be %+v , actual %+v", tc.isSuccess, tc.kmProvider.Status.IsSuccess)
-			}
-
-			if tc.kmProvider.Status.Error != tc.errString {
-				t.Fatalf("Expected Error to be %+v , actual %+v", tc.errString, tc.kmProvider.Status.Error)
-			}
-		})
-	}
+func (mr *MockRefresher) GetResult() interface{} {
+	return ctrl.Result{}
 }
 
-func TestKMPReconcile(t *testing.T) {
-	tests := []struct {
-		name             string
-		description      string
-		provider         *configv1beta1.NamespacedKeyManagementProvider
-		req              *reconcile.Request
-		expectedErr      bool
-		expectedKMPCount int
-	}{
-		{
-			name:        "nonexistent KMP",
-			description: "Reconciling a non-existent KMP CR, it should be deleted from maps",
-			req: &reconcile.Request{
-				NamespacedName: types.NamespacedName{Name: "nonexistent"},
-			},
-			provider: &configv1beta1.NamespacedKeyManagementProvider{
-				ObjectMeta: metav1.ObjectMeta{
-					Namespace: testNamespace,
-					Name:      kmpName,
-				},
-				Spec: configv1beta1.NamespacedKeyManagementProviderSpec{
-					Type: "inline",
-				},
-			},
-			expectedErr:      false,
-			expectedKMPCount: 0,
-		},
-		{
-			name:        "invalid params",
-			description: "Received invalid parameters of the KMP Spec, it should fail the reconcile and return an error",
-			provider: &configv1beta1.NamespacedKeyManagementProvider{
-				ObjectMeta: metav1.ObjectMeta{
-					Namespace: testNamespace,
-					Name:      kmpName,
-				},
-				Spec: configv1beta1.NamespacedKeyManagementProviderSpec{
-					Type: "inline",
-				},
-			},
-			expectedErr:      true,
-			expectedKMPCount: 0,
-		},
-		{
-			name:        "valid params",
-			description: "Received a valid KMP manifest, it should be added to the cert map",
-			provider: &configv1beta1.NamespacedKeyManagementProvider{
-				ObjectMeta: metav1.ObjectMeta{
-					Namespace: testNamespace,
-					Name:      kmpName,
-				},
-				Spec: configv1beta1.NamespacedKeyManagementProviderSpec{
-					Type: "inline",
-					Parameters: runtime.RawExtension{
-						Raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`),
-					},
-				},
-			},
-			expectedErr:      false,
-			expectedKMPCount: 1,
-		},
+func (mr *MockRefresher) Create(config map[string]interface{}) (refresh.Refresher, error) {
+	createConfigError := config["shouldError"].(bool)
+	refreshError := config["refreshError"].(bool)
+	if createConfigError {
+		return nil, errors.New("create error")
 	}
+	return &MockRefresher{
+		CreateConfigError: createConfigError,
+		RefreshError:      refreshError,
+	}, nil
+}
 
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			scheme, _ := test.CreateScheme()
-			client := fake.NewClientBuilder().WithScheme(scheme)
-			client.WithObjects(tt.provider)
-			r := &KeyManagementProviderReconciler{
-				Scheme: scheme,
-				Client: client.Build(),
-			}
-			var req reconcile.Request
-			if tt.req != nil {
-				req = *tt.req
-			} else {
-				req = reconcile.Request{
-					NamespacedName: test.KeyFor(tt.provider),
-				}
-			}
-
-			_, err := r.Reconcile(context.Background(), req)
-			if tt.expectedErr != (err != nil) {
-				t.Fatalf("Reconcile() expected error %v, actual %v", tt.expectedErr, err)
-			}
-			ctx := ctxUtils.SetContextWithNamespace(context.Background(), testNamespace)
-			certs, _ := keymanagementprovider.GetCertificatesFromMap(ctx, testNamespace+constants.NamespaceSeperator+kmpName)
-			if len(certs) != tt.expectedKMPCount {
-				t.Fatalf("Cert map expected size %v, actual %v", tt.expectedKMPCount, len(certs))
-			}
-		})
-	}
+func init() {
+	refresh.Register("mockRefresher", &MockRefresher{})
 }
diff --git a/pkg/keymanagementprovider/azurekeyvault/provider_test.go b/pkg/keymanagementprovider/azurekeyvault/provider_test.go
index 9d134e5..ce95d24 100644
--- a/pkg/keymanagementprovider/azurekeyvault/provider_test.go
+++ b/pkg/keymanagementprovider/azurekeyvault/provider_test.go
@@ -238,6 +238,26 @@ func TestGetKeys(t *testing.T) {
 	assert.Nil(t, keyStatus)
 }
 
+func TestIsRefreshable(t *testing.T) {
+	factory := &akvKMProviderFactory{}
+	config := config.KeyManagementProviderConfig{
+		"vaultUri": "https://testkv.vault.azure.net/",
+		"tenantID": "tid",
+		"clientID": "clientid",
+		"certificates": []map[string]interface{}{
+			{
+				"name":    "cert1",
+				"version": "",
+			},
+		},
+	}
+
+	provider, _ := factory.Create("v1", config, "")
+	if provider.IsRefreshable() != true {
+		t.Fatalf("expected true, got false")
+	}
+}
+
 // TestGetStatusMap tests the getStatusMap function
 func TestGetStatusMap(t *testing.T) {
 	certsStatus := []map[string]string{}
diff --git a/pkg/keymanagementprovider/inline/provider_test.go b/pkg/keymanagementprovider/inline/provider_test.go
index 68b4615..7cd56f6 100644
--- a/pkg/keymanagementprovider/inline/provider_test.go
+++ b/pkg/keymanagementprovider/inline/provider_test.go
@@ -116,3 +116,40 @@ func TestGetCertificates(t *testing.T) {
 		})
 	}
 }
+
+func TestGetKeys(t *testing.T) {
+	factory := &inlineKMProviderFactory{}
+	config := config.KeyManagementProviderConfig{
+		"type":        "inline",
+		"contentType": "key",
+		"value": `-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEozC27QupU+1GvAL0tqR7bT3Vpyyf
+OSeWVmPjy6J5x8+6OIpmTs8PKQB1vTF0gErwa1gS/QaOElLaxDKy0GS9Jg==
+-----END PUBLIC KEY-----`,
+	}
+
+	provider, err := factory.Create("v1.0", config, "")
+	if err != nil {
+		t.Fatalf("failed to create provider: %v", err)
+	}
+	keys, _, _ := provider.GetKeys(context.TODO())
+	if len(keys) != 1 {
+		t.Fatalf("expected 1 key, got %d", len(keys))
+	}
+}
+
+func TestIsRefreshable(t *testing.T) {
+	config := config.KeyManagementProviderConfig{
+		"type":        "inline",
+		"contentType": "certificate",
+		"value":       "-----BEGIN CERTIFICATE-----\nMIIEeDCCAmCgAwIBAgIUbztxbi/gSPMZGN53oZQZW1h/lbAwDQYJKoZIhvcNAQEL\nBQAwazELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdSZWRtb25k\nMRMwEQYDVQQKDApNeSBDb21wYW55MQ8wDQYDVQQLDAZNeSBPcmcxFzAVBgNVBAMM\nDmNhLmV4YW1wbGUuY29tMB4XDTIzMDIwMTIyNTMxMFoXDTI0MDIwMTIyNTMxMFow\nbTEZMBcGA1UEAxMQbGVhZi5leGFtcGxlLmNvbTEPMA0GA1UECxMGTXkgT3JnMRMw\nEQYDVQQKEwpNeSBDb21wYW55MRAwDgYDVQQHEwdSZWRtb25kMQswCQYDVQQIEwJX\nQTELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCs\nMixf+WT34edYXs2c80zOg4Z/cxOVHU05gywjuISeaP+KS+Joc3emgbub1t5dPclk\nieIwrj3Olk3tvkrPiarOJIcrNR2zfBmQAufR4AUjoc4n1GQSp/voGgw1Hvh0wTkO\nYjhzLomrF242Ond8WTVO3Vq6/tfApfZMFM59eK9LMBkuvwTV4NeLnEnPvpLAoAvV\n9ZvCu7FuQ849R93Aoag2bZc3Tc3UCbahoJs9rTE/rnAqOhJWMGv2J1Y2Wu2eIvkD\n2uCmcVlY+7owG3TwLHTuIOBFl/5MXMvfR+B7yp1OkG23rTwwuSEBlMhYRzJFvssv\n8FX0sea7zhIg5dtoRjIlAgMBAAGjEjAQMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG\n9w0BAQsFAAOCAgEANRUu+9aBAuRf3OsdUWJflMAvuyzREp3skWSOUs4dw0MhcB6x\nb7BSyNdrBgPImLBpqYzNU6IT2eIlLXrYnKLehvPyZQx7LHvIeompA09aKMFFesqi\ngVoW5GRtp3qL3oNuuZJ80r/uKlB6Cj51TWqUbLcctBGHX7TWxFeWmFRnN0Bki00U\nJW/ElaTsr4GB+ltgZM+5USUqSNQqTa8t3d+vH6oVikyV1oYunM41xAfiRZtID04z\no15sLSkWTjavfmZ3+NjllipXFY2tnLqymCcObgdKtJHmTMFSDRngDjY+3+RVj4EY\npNaCCCepvtmXz1C5f06tlgY4ofaautJuAL7K93p/Q9ZcsIhmYWkCUZ0dkWq+eMdT\n9/lB9rQHbrDTaRxEQNIUezFMQEBxR9eC5JQfpw98LobAgA3r4vizQjQPsN0UZ54h\ncAHiyoo1VeckkotXaToRsoLjixPO9Fmss4H3urJTLpcU0drbVoG3emNh4K289vgR\nrjV11TenqvvR3+jJ2AX2ewSsF25m0afheZbrq2ZtyITPAbOqwMwTbTOvJT3HUztt\nhUP3qwsKNPR/hF3FSqZewiYOSqJi5Dk28Vd6mUEQzZa/Ma9RpBR+BAmfgH3Z9gX5\n0TqmAVQn1P8yh+bhEjiNa20bTJ+y5vQ9OrA7fiQ+6vpZCio4NFiEbYK4UBI=\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIFxzCCA6+gAwIBAgIUY1fnGcYJFIGNk8fevKdGtOuZ5vcwDQYJKoZIhvcNAQEL\nBQAwazELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdSZWRtb25k\nMRMwEQYDVQQKDApNeSBDb21wYW55MQ8wDQYDVQQLDAZNeSBPcmcxFzAVBgNVBAMM\nDmNhLmV4YW1wbGUuY29tMB4XDTIzMDIwMTIyNTIwN1oXDTMzMDEyOTIyNTIwN1ow\nazELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdSZWRtb25kMRMw\nEQYDVQQKDApNeSBDb21wYW55MQ8wDQYDVQQLDAZNeSBPcmcxFzAVBgNVBAMMDmNh\nLmV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtQXd\n+qySVVMHx7iGz9xRdpDKb+zATK3asMFnMXWBn0MCkcOMJvhjajA8yrCpfMudjiW0\nReQ6xcjEfnJqkzwxrK7tPE9cQ6JzQGCxsmscRzKf1NoJL2ske5xBteKuJhSfZ9la\ncIZn/EU2F6eMAl9U4Y+ncIIrl4UoB5H/AJJj62WMl5QAvzXXwCBwlLHQe4/T3Axu\n9xmD3HTC7iQExOUFLdJx86fK3ym0futi1RgOUgD+OrnyDEIkD8mGxffPYPgszS71\nUuJX+NTsLZ/JW3ER/PMAPnBsMsMTTxEIGnrp1CXf2RnwQnDHVsxZFMgkLTS6dksT\nTGevnulTNtVvSKsZ7MpzE01j7zDie4V4dQJzBJMktbeVq9KRoPIEX0WcKpg8bGS8\nd5p5pr+Lu/NOv6ur+av8M649qCPwJAv5i2P6ggT4YMNtY0wMD2kjcHJ9/l2gYpZj\n3DG0Hy3Xo8uKUmTSC7iGhLsSjleNhJkKyh3RCsuMKB9juE4qeXPoaoPWBIuarbkq\neVVZJu2PlgN8UcxM/ym+9GNJIfNJ18WGWm+5P3IDvfBbJ39yvzZlG2czju4BUzYn\nlPOHA/Z18TxZhlPrPVnyKSVbeg7sW17yMUI2LCeaFIOYdIFvM09RaCyLIGrQwhpe\nkLh56xXk702oNHaLxyh/v5kz8EyxnpXIlDntis0CAwEAAaNjMGEwHQYDVR0OBBYE\nFCESfoahHMx7GyBdTBARen7mc37nMB8GA1UdIwQYMBaAFCESfoahHMx7GyBdTBAR\nen7mc37nMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3\nDQEBCwUAA4ICAQBst1MbRJd3FLSY36qaNuDhGncvcXIcYqP2A/SXVhjuAhVOTsrE\nejDYBSkjfxCgoA3LZnQLTcbcPwRL5dTBEvqBqzbyArOjc+G3kbjOeuZYv09M8kUU\nYG1bVnxXV17GMk7zcBUnr1iwnp4E0pzB9gTv+Jv1oV+EtAHe5QOTOmW9mm2SUXbH\nmIya+KlzkIgVJJ8kiGOyXsr8i4wpyDXZf720tqTzPQFTf6rUXo9PhYzOWrrj8c7V\n+bmJurV3XkgvdOiwNase17wXUG9Ad8FhVYpUicq3Csfx5M87IXUIlx51AxaOQK3G\n3skyJyAm8R8pHzhcsEuVV7bGZlbFPZeWAHpbIEwpKHlLoN6qMINk2kEbcVahL2Mu\nXBUcvJdO2LbmEvfS+1imr32YbJ5Ufetru+G4mwAp6a0P6u5FU8lbE1ZoFHhflsVq\nErvOcRKhKjAim4iwIVGS55Xyx1IpF7YSTYpL89vlMmaJsssEoCQAkf+lxmC3eEuy\n2kBu3QB3cUHZXJK+01krC5MqeHcEVuc/fbNcTsCBp+RYXNRjYsp6IzGjqltUfInE\n3Tywg3P0ZLPO06WLNjNeAFZw6T8yV5gLcTJ1xc5pEf4UiY1uCf4NmDpeWhT+vvto\n2AxC/+7x7EkEfZnYiD6tcyHyY+iuroptws8lc5wRis859kydnq3vtxbXPQ==\n-----END CERTIFICATE-----\n",
+	}
+	factory := &inlineKMProviderFactory{}
+	provider, err := factory.Create("v1.0", config, "")
+	if err != nil {
+		t.Fatalf("failed to create provider: %v", err)
+	}
+	if provider.IsRefreshable() != false {
+		t.Fatalf("expected false")
+	}
+}
diff --git a/pkg/keymanagementprovider/refresh/factory_test.go b/pkg/keymanagementprovider/refresh/factory_test.go
new file mode 100644
index 0000000..a4a267c
--- /dev/null
+++ b/pkg/keymanagementprovider/refresh/factory_test.go
@@ -0,0 +1,119 @@
+/*
+Copyright The Ratify Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package refresh
+
+import (
+	"context"
+	"testing"
+)
+
+type MockRefresher struct{}
+
+func (f *MockRefresher) Create(_ map[string]interface{}) (Refresher, error) {
+	return &MockRefresher{}, nil
+}
+
+func (f *MockRefresher) Refresh(_ context.Context) error {
+	return nil
+}
+
+func (f *MockRefresher) GetResult() interface{} {
+	return nil
+}
+
+func TestRefreshFactory_Create(t *testing.T) {
+	Register("mockRefresher", &MockRefresher{})
+	refresherConfig := map[string]interface{}{
+		"type": "mockRefresher",
+	}
+	factory := refresherFactories["mockRefresher"]
+	refresher, err := factory.Create(refresherConfig)
+	// refresher, err := CreateRefresherFromConfig(refresherConfig)
+	if _, ok := refresher.(*MockRefresher); !ok {
+		t.Errorf("Expected refresher to be of type MockRefresher, got %v", refresher)
+	}
+	if err != nil {
+		t.Errorf("Expected no error, got %v", err)
+	}
+}
+
+func TestRegister_InvalidFactory(t *testing.T) {
+	defer func() {
+		if r := recover(); r == nil {
+			t.Errorf("Expected panic, got nil")
+		}
+	}()
+
+	Register("invalidRefresher", nil)
+}
+
+func TestRegister_DuplicateFactory(t *testing.T) {
+	defer func() {
+		if r := recover(); r == nil {
+			t.Errorf("Expected panic, got nil")
+		}
+	}()
+
+	Register("duplicateRefresher", &MockRefresher{})
+	Register("duplicateRefresher", &MockRefresher{})
+}
+
+func TestRegister_ValidFactory(t *testing.T) {
+	refresherFactories = make(map[string]RefresherFactory)
+	Register("validRefresher", &MockRefresher{})
+	if len(refresherFactories) != 1 {
+		t.Errorf("Expected 1 factory to be registered, got %d", len(refresherFactories))
+	}
+}
+
+func TestCreateRefresherFromConfig(t *testing.T) {
+	Register("mockRefresher", &MockRefresher{})
+	tests := []struct {
+		name          string
+		refresherType string
+		expectedError bool
+	}{
+		{
+			name:          "Valid Refresher Type",
+			refresherType: "mockRefresher",
+			expectedError: false,
+		},
+		{
+			name:          "Invalid Refresher Type",
+			refresherType: "invalidRefresher",
+			expectedError: true,
+		},
+		{
+			name:          "Empty Refresher Type",
+			refresherType: "",
+			expectedError: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			refresherConfig := map[string]interface{}{
+				"type": tt.refresherType,
+			}
+			_, err := CreateRefresherFromConfig(refresherConfig)
+			if tt.expectedError && err == nil {
+				t.Errorf("Expected error, got nil")
+			}
+			if !tt.expectedError && err != nil {
+				t.Errorf("Expected no error, got %v", err)
+			}
+		})
+	}
+}
diff --git a/pkg/keymanagementprovider/refresh/kubeRefreshNamedspaced_test.go b/pkg/keymanagementprovider/refresh/kubeRefreshNamedspaced_test.go
new file mode 100644
index 0000000..884d94f
--- /dev/null
+++ b/pkg/keymanagementprovider/refresh/kubeRefreshNamedspaced_test.go
@@ -0,0 +1,376 @@
+/*
+Copyright The Ratify Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package refresh
+
+import (
+	"context"
+	"fmt"
+	"reflect"
+	"testing"
+	"time"
+
+	configv1beta1 "github.com/ratify-project/ratify/api/v1beta1"
+	"github.com/ratify-project/ratify/pkg/keymanagementprovider"
+	"github.com/ratify-project/ratify/pkg/keymanagementprovider/mocks"
+	test "github.com/ratify-project/ratify/pkg/utils"
+	"github.com/sirupsen/logrus"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+)
+
+func TestKubeRefresherNamespaced_Refresh(t *testing.T) {
+	tests := []struct {
+		name           string
+		provider       *configv1beta1.NamespacedKeyManagementProvider
+		request        ctrl.Request
+		mockClient     bool
+		expectedResult ctrl.Result
+		expectedError  bool
+	}{
+		{
+			name: "Non-refreshable",
+			provider: &configv1beta1.NamespacedKeyManagementProvider{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "",
+					Name:      "kmpName",
+				},
+				Spec: configv1beta1.NamespacedKeyManagementProviderSpec{
+					Type: "inline",
+					Parameters: runtime.RawExtension{
+						Raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`),
+					},
+				},
+			},
+			request: ctrl.Request{
+				NamespacedName: client.ObjectKey{
+					Namespace: "",
+					Name:      "kmpName",
+				},
+			},
+			expectedResult: ctrl.Result{},
+			expectedError:  false,
+		},
+		{
+			name: "Disabled",
+			provider: &configv1beta1.NamespacedKeyManagementProvider{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "",
+					Name:      "kmpName",
+				},
+				Spec: configv1beta1.NamespacedKeyManagementProviderSpec{
+					Type:            "test-kmp",
+					RefreshInterval: "",
+					Parameters: runtime.RawExtension{
+						Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`),
+					},
+				},
+			},
+			request: ctrl.Request{
+				NamespacedName: client.ObjectKey{
+					Namespace: "",
+					Name:      "kmpName",
+				},
+			},
+			expectedResult: ctrl.Result{},
+			expectedError:  false,
+		},
+		{
+			name: "Refreshable",
+			provider: &configv1beta1.NamespacedKeyManagementProvider{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "",
+					Name:      "kmpName",
+				},
+				Spec: configv1beta1.NamespacedKeyManagementProviderSpec{
+					Type:            "test-kmp",
+					RefreshInterval: "1m",
+					Parameters: runtime.RawExtension{
+						Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`),
+					},
+				},
+			},
+			request: ctrl.Request{
+				NamespacedName: client.ObjectKey{
+					Namespace: "",
+					Name:      "kmpName",
+				},
+			},
+			expectedResult: ctrl.Result{RequeueAfter: time.Minute},
+			expectedError:  false,
+		},
+		{
+			name: "Invalid Interval",
+			provider: &configv1beta1.NamespacedKeyManagementProvider{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "",
+					Name:      "kmpName",
+				},
+				Spec: configv1beta1.NamespacedKeyManagementProviderSpec{
+					Type:            "",
+					RefreshInterval: "1mm",
+					Parameters: runtime.RawExtension{
+						Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`),
+					},
+				},
+			},
+			request: ctrl.Request{
+				NamespacedName: client.ObjectKey{
+					Namespace: "",
+					Name:      "kmpName",
+				},
+			},
+			expectedResult: ctrl.Result{},
+			expectedError:  true,
+		},
+		{
+			name: "IsNotFound",
+			provider: &configv1beta1.NamespacedKeyManagementProvider{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "",
+					Name:      "kmpName",
+				},
+				Spec: configv1beta1.NamespacedKeyManagementProviderSpec{
+					Type:            "",
+					RefreshInterval: "",
+					Parameters: runtime.RawExtension{
+						Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`),
+					},
+				},
+			},
+			expectedResult: ctrl.Result{},
+			expectedError:  false,
+		},
+		{
+			name:          "UnableToFetchKMP",
+			mockClient:    true,
+			expectedError: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var client client.Client
+
+			if tt.mockClient {
+				client = mocks.TestClient{}
+			} else {
+				scheme, _ := test.CreateScheme()
+				client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(tt.provider).Build()
+			}
+
+			kr := &KubeRefresherNamespaced{
+				Client:  client,
+				Request: tt.request,
+			}
+			err := kr.Refresh(context.Background())
+			result := kr.GetResult()
+			if !reflect.DeepEqual(result, tt.expectedResult) {
+				t.Fatalf("Expected nil but got %v with error %v", result, err)
+			}
+			if tt.expectedError && err == nil {
+				t.Fatalf("Expected error but got nil")
+			}
+		})
+	}
+}
+
+func TestKubeRefresherNamespaced_Create(t *testing.T) {
+	tests := []struct {
+		name          string
+		config        map[string]interface{}
+		expectedError bool
+	}{
+		{
+			name: "Success",
+			config: map[string]interface{}{
+				"client":  &mocks.TestClient{},
+				"request": ctrl.Request{},
+			},
+			expectedError: false,
+		},
+		{
+			name: "ClientMissing",
+			config: map[string]interface{}{
+				"request": ctrl.Request{},
+			},
+			expectedError: true,
+		},
+		{
+			name: "RequestMissing",
+			config: map[string]interface{}{
+				"client": &mocks.TestClient{},
+			},
+			expectedError: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			kr := &KubeRefresherNamespaced{}
+			_, err := kr.Create(tt.config)
+			if tt.expectedError && err == nil {
+				t.Fatalf("Expected error but got nil")
+			}
+		})
+	}
+}
+
+func TestKMProviderUpdateErrorStatusNamespaced(t *testing.T) {
+	var parametersString = "{\"certs\":{\"name\":\"certName\"}}"
+	var kmProviderStatus = []byte(parametersString)
+
+	status := configv1beta1.NamespacedKeyManagementProviderStatus{
+		IsSuccess: true,
+		Properties: runtime.RawExtension{
+			Raw: kmProviderStatus,
+		},
+	}
+	keyManagementProvider := configv1beta1.NamespacedKeyManagementProvider{
+		Status: status,
+	}
+	expectedErr := "it's a long error from unit test"
+	lastFetchedTime := metav1.Now()
+	updateKMProviderErrorStatusNamespaced(&keyManagementProvider, expectedErr, &lastFetchedTime)
+
+	if keyManagementProvider.Status.IsSuccess != false {
+		t.Fatalf("Unexpected error, expected isSuccess to be false , actual %+v", keyManagementProvider.Status.IsSuccess)
+	}
+
+	if keyManagementProvider.Status.Error != expectedErr {
+		t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedErr, keyManagementProvider.Status.Error)
+	}
+	expectedBriedErr := fmt.Sprintf("%s...", expectedErr[:30])
+	if keyManagementProvider.Status.BriefError != expectedBriedErr {
+		t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedBriedErr, keyManagementProvider.Status.Error)
+	}
+
+	//make sure properties of last cached cert was not overridden
+	if len(keyManagementProvider.Status.Properties.Raw) == 0 {
+		t.Fatalf("Unexpected properties,  expected %+v, got %+v", parametersString, string(keyManagementProvider.Status.Properties.Raw))
+	}
+}
+
+func TestKMProviderUpdateSuccessStatusNamespaced(t *testing.T) {
+	kmProviderStatus := keymanagementprovider.KeyManagementProviderStatus{}
+	properties := map[string]string{}
+	properties["Name"] = "wabbit"
+	properties["Version"] = "ABC"
+
+	kmProviderStatus["Certificates"] = properties
+
+	lastFetchedTime := metav1.Now()
+
+	status := configv1beta1.NamespacedKeyManagementProviderStatus{
+		IsSuccess: false,
+		Error:     "error from last operation",
+	}
+	keyManagementProvider := configv1beta1.NamespacedKeyManagementProvider{
+		Status: status,
+	}
+
+	updateKMProviderSuccessStatusNamespaced(&keyManagementProvider, &lastFetchedTime, kmProviderStatus)
+
+	if keyManagementProvider.Status.IsSuccess != true {
+		t.Fatalf("Expected isSuccess to be true , actual %+v", keyManagementProvider.Status.IsSuccess)
+	}
+
+	if keyManagementProvider.Status.Error != "" {
+		t.Fatalf("Unexpected error string, actual %+v", keyManagementProvider.Status.Error)
+	}
+
+	//make sure properties of last cached cert was updated
+	if len(keyManagementProvider.Status.Properties.Raw) == 0 {
+		t.Fatalf("Properties should not be empty")
+	}
+}
+
+func TestKMProviderUpdateSuccessStatusNamespaced_emptyProperties(t *testing.T) {
+	lastFetchedTime := metav1.Now()
+	status := configv1beta1.NamespacedKeyManagementProviderStatus{
+		IsSuccess: false,
+		Error:     "error from last operation",
+	}
+	keyManagementProvider := configv1beta1.NamespacedKeyManagementProvider{
+		Status: status,
+	}
+
+	updateKMProviderSuccessStatusNamespaced(&keyManagementProvider, &lastFetchedTime, nil)
+
+	if keyManagementProvider.Status.IsSuccess != true {
+		t.Fatalf("Expected isSuccess to be true , actual %+v", keyManagementProvider.Status.IsSuccess)
+	}
+
+	if keyManagementProvider.Status.Error != "" {
+		t.Fatalf("Unexpected error string, actual %+v", keyManagementProvider.Status.Error)
+	}
+
+	//make sure properties of last cached cert was updated
+	if len(keyManagementProvider.Status.Properties.Raw) != 0 {
+		t.Fatalf("Properties should be empty")
+	}
+}
+
+func TestWriteKMProviderStatusNamespaced(t *testing.T) {
+	logger := logrus.WithContext(context.Background())
+	lastFetchedTime := metav1.Now()
+	testCases := []struct {
+		name       string
+		isSuccess  bool
+		kmProvider *configv1beta1.NamespacedKeyManagementProvider
+		errString  string
+		reconciler client.StatusClient
+	}{
+		{
+			name:       "success status",
+			isSuccess:  true,
+			errString:  "",
+			kmProvider: &configv1beta1.NamespacedKeyManagementProvider{},
+			reconciler: &test.MockStatusClient{},
+		},
+		{
+			name:       "error status",
+			isSuccess:  false,
+			kmProvider: &configv1beta1.NamespacedKeyManagementProvider{},
+			errString:  "a long error string that exceeds the max length of 30 characters",
+			reconciler: &test.MockStatusClient{},
+		},
+		{
+			name:       "status update failed",
+			isSuccess:  true,
+			kmProvider: &configv1beta1.NamespacedKeyManagementProvider{},
+			reconciler: &test.MockStatusClient{
+				UpdateFailed: true,
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			writeKMProviderStatusNamespaced(context.Background(), tc.reconciler, tc.kmProvider, logger, tc.isSuccess, tc.errString, lastFetchedTime, nil)
+
+			if tc.kmProvider.Status.IsSuccess != tc.isSuccess {
+				t.Fatalf("Expected isSuccess to be %+v , actual %+v", tc.isSuccess, tc.kmProvider.Status.IsSuccess)
+			}
+
+			if tc.kmProvider.Status.Error != tc.errString {
+				t.Fatalf("Expected Error to be %+v , actual %+v", tc.errString, tc.kmProvider.Status.Error)
+			}
+		})
+	}
+}
diff --git a/pkg/keymanagementprovider/refresh/kubeRefresh_test.go b/pkg/keymanagementprovider/refresh/kubeRefresh_test.go
new file mode 100644
index 0000000..beb5037
--- /dev/null
+++ b/pkg/keymanagementprovider/refresh/kubeRefresh_test.go
@@ -0,0 +1,380 @@
+/*
+Copyright The Ratify Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package refresh
+
+import (
+	"context"
+	"fmt"
+	"reflect"
+	"testing"
+	"time"
+
+	configv1beta1 "github.com/ratify-project/ratify/api/v1beta1"
+	"github.com/ratify-project/ratify/pkg/keymanagementprovider"
+	_ "github.com/ratify-project/ratify/pkg/keymanagementprovider/inline"
+	"github.com/ratify-project/ratify/pkg/keymanagementprovider/mocks"
+	test "github.com/ratify-project/ratify/pkg/utils"
+	"github.com/sirupsen/logrus"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+)
+
+func TestKubeRefresher_Refresh(t *testing.T) {
+	tests := []struct {
+		name           string
+		provider       *configv1beta1.KeyManagementProvider
+		request        ctrl.Request
+		mockClient     bool
+		expectedResult ctrl.Result
+		expectedError  bool
+	}{
+		{
+			name: "Non-refreshable",
+			provider: &configv1beta1.KeyManagementProvider{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "",
+					Name:      "kmpName",
+				},
+				Spec: configv1beta1.KeyManagementProviderSpec{
+					Type: "inline",
+					Parameters: runtime.RawExtension{
+						Raw: []byte(`{"type": "inline", "contentType": "certificate", "value": "-----BEGIN CERTIFICATE-----\nMIID2jCCAsKgAwIBAgIQXy2VqtlhSkiZKAGhsnkjbDANBgkqhkiG9w0BAQsFADBvMRswGQYDVQQD\nExJyYXRpZnkuZXhhbXBsZS5jb20xDzANBgNVBAsTBk15IE9yZzETMBEGA1UEChMKTXkgQ29tcGFu\neTEQMA4GA1UEBxMHUmVkbW9uZDELMAkGA1UECBMCV0ExCzAJBgNVBAYTAlVTMB4XDTIzMDIwMTIy\nNDUwMFoXDTI0MDIwMTIyNTUwMFowbzEbMBkGA1UEAxMScmF0aWZ5LmV4YW1wbGUuY29tMQ8wDQYD\nVQQLEwZNeSBPcmcxEzARBgNVBAoTCk15IENvbXBhbnkxEDAOBgNVBAcTB1JlZG1vbmQxCzAJBgNV\nBAgTAldBMQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL10bM81\npPAyuraORABsOGS8M76Bi7Guwa3JlM1g2D8CuzSfSTaaT6apy9GsccxUvXd5cmiP1ffna5z+EFmc\nizFQh2aq9kWKWXDvKFXzpQuhyqD1HeVlRlF+V0AfZPvGt3VwUUjNycoUU44ctCWmcUQP/KShZev3\n6SOsJ9q7KLjxxQLsUc4mg55eZUThu8mGB8jugtjsnLUYvIWfHhyjVpGrGVrdkDMoMn+u33scOmrt\nsBljvq9WVo4T/VrTDuiOYlAJFMUae2Ptvo0go8XTN3OjLblKeiK4C+jMn9Dk33oGIT9pmX0vrDJV\nX56w/2SejC1AxCPchHaMuhlwMpftBGkCAwEAAaNyMHAwDgYDVR0PAQH/BAQDAgeAMAkGA1UdEwQC\nMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHwYDVR0jBBgwFoAU0eaKkZj+MS9jCp9Dg1zdv3v/aKww\nHQYDVR0OBBYEFNHmipGY/jEvYwqfQ4Nc3b97/2isMA0GCSqGSIb3DQEBCwUAA4IBAQBNDcmSBizF\nmpJlD8EgNcUCy5tz7W3+AAhEbA3vsHP4D/UyV3UgcESx+L+Nye5uDYtTVm3lQejs3erN2BjW+ds+\nXFnpU/pVimd0aYv6mJfOieRILBF4XFomjhrJOLI55oVwLN/AgX6kuC3CJY2NMyJKlTao9oZgpHhs\nLlxB/r0n9JnUoN0Gq93oc1+OLFjPI7gNuPXYOP1N46oKgEmAEmNkP1etFrEjFRgsdIFHksrmlOlD\nIed9RcQ087VLjmuymLgqMTFX34Q3j7XgN2ENwBSnkHotE9CcuGRW+NuiOeJalL8DBmFXXWwHTKLQ\nPp5g6m1yZXylLJaFLKz7tdMmO355\n-----END CERTIFICATE-----\n"}`),
+					},
+				},
+			},
+			request: ctrl.Request{
+				NamespacedName: client.ObjectKey{
+					Namespace: "",
+					Name:      "kmpName",
+				},
+			},
+			expectedResult: ctrl.Result{},
+			expectedError:  false,
+		},
+		{
+			name: "Disabled",
+			provider: &configv1beta1.KeyManagementProvider{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "",
+					Name:      "kmpName",
+				},
+				Spec: configv1beta1.KeyManagementProviderSpec{
+					Type:            "test-kmp",
+					RefreshInterval: "",
+					Parameters: runtime.RawExtension{
+						Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`),
+					},
+				},
+			},
+			request: ctrl.Request{
+				NamespacedName: client.ObjectKey{
+					Namespace: "",
+					Name:      "kmpName",
+				},
+			},
+			expectedResult: ctrl.Result{},
+			expectedError:  false,
+		},
+		{
+			name: "Refreshable",
+			provider: &configv1beta1.KeyManagementProvider{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "",
+					Name:      "kmpName",
+				},
+				Spec: configv1beta1.KeyManagementProviderSpec{
+					Type:            "test-kmp",
+					RefreshInterval: "1m",
+					Parameters: runtime.RawExtension{
+						Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`),
+					},
+				},
+			},
+			request: ctrl.Request{
+				NamespacedName: client.ObjectKey{
+					Namespace: "",
+					Name:      "kmpName",
+				},
+			},
+			expectedResult: ctrl.Result{RequeueAfter: time.Minute},
+			expectedError:  false,
+		},
+		{
+			name: "Invalid Interval",
+			provider: &configv1beta1.KeyManagementProvider{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "",
+					Name:      "kmpName",
+				},
+				Spec: configv1beta1.KeyManagementProviderSpec{
+					Type:            "",
+					RefreshInterval: "1mm",
+					Parameters: runtime.RawExtension{
+						Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`),
+					},
+				},
+			},
+			request: ctrl.Request{
+				NamespacedName: client.ObjectKey{
+					Namespace: "",
+					Name:      "kmpName",
+				},
+			},
+			expectedResult: ctrl.Result{},
+			expectedError:  true,
+		},
+		{
+			name: "IsNotFound",
+			provider: &configv1beta1.KeyManagementProvider{
+				ObjectMeta: metav1.ObjectMeta{
+					Namespace: "",
+					Name:      "kmpName",
+				},
+				Spec: configv1beta1.KeyManagementProviderSpec{
+					Type:            "",
+					RefreshInterval: "",
+					Parameters: runtime.RawExtension{
+						Raw: []byte(`{"vaultURI": "https://yourkeyvault.vault.azure.net/", "certificates": [{"name": "cert1", "version": "1"}], "tenantID": "yourtenantID", "clientID": "yourclientID"}`),
+					},
+				},
+			},
+			expectedResult: ctrl.Result{},
+			expectedError:  false,
+		},
+		{
+			name:          "UnableToFetchKMP",
+			mockClient:    true,
+			expectedError: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			var client client.Client
+
+			if tt.mockClient {
+				client = mocks.TestClient{}
+			} else {
+				scheme, _ := test.CreateScheme()
+				client = fake.NewClientBuilder().WithScheme(scheme).WithObjects(tt.provider).Build()
+			}
+
+			kr := &KubeRefresher{
+				Client:  client,
+				Request: tt.request,
+			}
+			err := kr.Refresh(context.Background())
+			result := kr.GetResult()
+			if !reflect.DeepEqual(result, tt.expectedResult) {
+				t.Fatalf("Expected nil but got %v with error %v", result, err)
+			}
+			if tt.expectedError && err == nil {
+				t.Fatalf("Expected error but got nil")
+			}
+		})
+	}
+}
+
+func TestKubeRefresher_Create(t *testing.T) {
+	tests := []struct {
+		name          string
+		config        map[string]interface{}
+		expectedError bool
+	}{
+		{
+			name: "Success",
+			config: map[string]interface{}{
+				"client":  &mocks.TestClient{},
+				"request": ctrl.Request{},
+			},
+			expectedError: false,
+		},
+		{
+			name: "ClientMissing",
+			config: map[string]interface{}{
+				"request": ctrl.Request{},
+			},
+			expectedError: true,
+		},
+		{
+			name: "RequestMissing",
+			config: map[string]interface{}{
+				"client": &mocks.TestClient{},
+			},
+			expectedError: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			kr := &KubeRefresher{}
+			_, err := kr.Create(tt.config)
+			if tt.expectedError && err == nil {
+				t.Fatalf("Expected error but got nil")
+			}
+		})
+	}
+}
+
+func TestKMProviderUpdateErrorStatus(t *testing.T) {
+	var parametersString = "{\"certs\":{\"name\":\"certName\"}}"
+	var kmProviderStatus = []byte(parametersString)
+
+	status := configv1beta1.KeyManagementProviderStatus{
+		IsSuccess: true,
+		Properties: runtime.RawExtension{
+			Raw: kmProviderStatus,
+		},
+	}
+	keyManagementProvider := configv1beta1.KeyManagementProvider{
+		Status: status,
+	}
+	expectedErr := "it's a long error from unit test"
+	lastFetchedTime := metav1.Now()
+	updateKMProviderErrorStatus(&keyManagementProvider, expectedErr, &lastFetchedTime)
+
+	if keyManagementProvider.Status.IsSuccess != false {
+		t.Fatalf("Unexpected error, expected isSuccess to be false , actual %+v", keyManagementProvider.Status.IsSuccess)
+	}
+
+	if keyManagementProvider.Status.Error != expectedErr {
+		t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedErr, keyManagementProvider.Status.Error)
+	}
+	expectedBriedErr := fmt.Sprintf("%s...", expectedErr[:30])
+	if keyManagementProvider.Status.BriefError != expectedBriedErr {
+		t.Fatalf("Unexpected error string, expected %+v, got %+v", expectedBriedErr, keyManagementProvider.Status.Error)
+	}
+
+	//make sure properties of last cached cert was not overridden
+	if len(keyManagementProvider.Status.Properties.Raw) == 0 {
+		t.Fatalf("Unexpected properties,  expected %+v, got %+v", parametersString, string(keyManagementProvider.Status.Properties.Raw))
+	}
+}
+
+// TestKMProviderUpdateSuccessStatus tests the updateSuccessStatus method
+func TestKMProviderUpdateSuccessStatus(t *testing.T) {
+	kmProviderStatus := keymanagementprovider.KeyManagementProviderStatus{}
+	properties := map[string]string{}
+	properties["Name"] = "wabbit"
+	properties["Version"] = "ABC"
+
+	kmProviderStatus["Certificates"] = properties
+
+	lastFetchedTime := metav1.Now()
+
+	status := configv1beta1.KeyManagementProviderStatus{
+		IsSuccess: false,
+		Error:     "error from last operation",
+	}
+	keyManagementProvider := configv1beta1.KeyManagementProvider{
+		Status: status,
+	}
+
+	updateKMProviderSuccessStatus(&keyManagementProvider, &lastFetchedTime, kmProviderStatus)
+
+	if keyManagementProvider.Status.IsSuccess != true {
+		t.Fatalf("Expected isSuccess to be true , actual %+v", keyManagementProvider.Status.IsSuccess)
+	}
+
+	if keyManagementProvider.Status.Error != "" {
+		t.Fatalf("Unexpected error string, actual %+v", keyManagementProvider.Status.Error)
+	}
+
+	//make sure properties of last cached cert was updated
+	if len(keyManagementProvider.Status.Properties.Raw) == 0 {
+		t.Fatalf("Properties should not be empty")
+	}
+}
+
+// TestKMProviderUpdateSuccessStatus tests the updateSuccessStatus method with empty properties
+func TestKMProviderUpdateSuccessStatus_emptyProperties(t *testing.T) {
+	lastFetchedTime := metav1.Now()
+	status := configv1beta1.KeyManagementProviderStatus{
+		IsSuccess: false,
+		Error:     "error from last operation",
+	}
+	keyManagementProvider := configv1beta1.KeyManagementProvider{
+		Status: status,
+	}
+
+	updateKMProviderSuccessStatus(&keyManagementProvider, &lastFetchedTime, nil)
+
+	if keyManagementProvider.Status.IsSuccess != true {
+		t.Fatalf("Expected isSuccess to be true , actual %+v", keyManagementProvider.Status.IsSuccess)
+	}
+
+	if keyManagementProvider.Status.Error != "" {
+		t.Fatalf("Unexpected error string, actual %+v", keyManagementProvider.Status.Error)
+	}
+
+	//make sure properties of last cached cert was updated
+	if len(keyManagementProvider.Status.Properties.Raw) != 0 {
+		t.Fatalf("Properties should be empty")
+	}
+}
+
+func TestWriteKMProviderStatus(t *testing.T) {
+	logger := logrus.WithContext(context.Background())
+	lastFetchedTime := metav1.Now()
+	testCases := []struct {
+		name       string
+		isSuccess  bool
+		kmProvider *configv1beta1.KeyManagementProvider
+		errString  string
+		reconciler client.StatusClient
+	}{
+		{
+			name:       "success status",
+			isSuccess:  true,
+			errString:  "",
+			kmProvider: &configv1beta1.KeyManagementProvider{},
+			reconciler: &test.MockStatusClient{},
+		},
+		{
+			name:       "error status",
+			isSuccess:  false,
+			kmProvider: &configv1beta1.KeyManagementProvider{},
+			errString:  "a long error string that exceeds the max length of 30 characters",
+			reconciler: &test.MockStatusClient{},
+		},
+		{
+			name:       "status update failed",
+			isSuccess:  true,
+			kmProvider: &configv1beta1.KeyManagementProvider{},
+			reconciler: &test.MockStatusClient{
+				UpdateFailed: true,
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			writeKMProviderStatus(context.Background(), tc.reconciler, tc.kmProvider, logger, tc.isSuccess, tc.errString, lastFetchedTime, nil)
+
+			if tc.kmProvider.Status.IsSuccess != tc.isSuccess {
+				t.Fatalf("Expected isSuccess to be %+v , actual %+v", tc.isSuccess, tc.kmProvider.Status.IsSuccess)
+			}
+
+			if tc.kmProvider.Status.Error != tc.errString {
+				t.Fatalf("Expected Error to be %+v , actual %+v", tc.errString, tc.kmProvider.Status.Error)
+			}
+		})
+	}
+}
diff --git a/pkg/keymanagementprovider/refresh/test_helper_test.go b/pkg/keymanagementprovider/refresh/test_helper_test.go
new file mode 100644
index 0000000..0fd82a1
--- /dev/null
+++ b/pkg/keymanagementprovider/refresh/test_helper_test.go
@@ -0,0 +1,26 @@
+/*
+Copyright The Ratify Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+	http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package refresh
+
+import (
+	"github.com/ratify-project/ratify/pkg/keymanagementprovider/factory"
+	"github.com/ratify-project/ratify/pkg/keymanagementprovider/mocks"
+)
+
+func init() {
+	// Register the mock KeyManagementProviderFactory
+	factory.Register("test-kmp", &mocks.TestKeyManagementProviderFactory{})
+}