Repository: Kong/kubernetes-ingress-controller
Pull request #5500 (2024-01-29 14:25:58)

chore(deps): bump github.com/evanphx/json-patch/v5 from 5.8.1 to 5.9.0
Bumps [github.com/evanphx/json-patch/v5](https://github.com/evanphx/json-patch) from 5.8.1 to 5.9.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/evanphx/json-patch/releases">github.com/evanphx/json-patch/v5's releases</a>.</em></p>
<blockquote>
<h2>v5.9.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Validate that the partialDoc is decoded correctly by <a href="https://github.com/evanphx"><code>@​evanphx</code></a> in <a href="https://redirect.github.com/evanphx/json-patch/pull/201">evanphx/json-patch#201</a></li>
<li>Add option to control if the output is HTMLEscaped by <a href="https://github.com/evanphx"><code>@​evanphx</code></a> in <a href="https://redirect.github.com/evanphx/json-patch/pull/202">evanphx/json-patch#202</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/evanphx/json-patch/compare/v5.8.1...v5.9.0">https://github.com/evanphx/json-patch/compare/v5.8.1...v5.9.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/evanphx/json-patch/commit/b7a4e4a87a35414cd02460dac07e879df729df37"><code>b7a4e4a</code></a> Merge pull request <a href="https://redirect.github.com/evanphx/json-patch/issues/202">#202</a> from evanphx/f-html-escape</li>
<li><a href="https://github.com/evanphx/json-patch/commit/7eef36c732df996ab6508bfb5cee17a7763097f1"><code>7eef36c</code></a> Guard using options to avoid a crash bug</li>
<li><a href="https://github.com/evanphx/json-patch/commit/1bcbd0f1a056dee833c821b7dc979c04cdf090e3"><code>1bcbd0f</code></a> Merge pull request <a href="https://redirect.github.com/evanphx/json-patch/issues/201">#201</a> from evanphx/b-null</li>
<li><a href="https://github.com/evanphx/json-patch/commit/9d7ba23eb0b55bb113546596be809ff0c9245811"><code>9d7ba23</code></a> Add option to control if the output is HTMLEscaped</li>
<li><a href="https://github.com/evanphx/json-patch/commit/009bc564fa613d3e711dcc8a0984c6e639729a54"><code>009bc56</code></a> Validate that the partialDoc is decoded correctly</li>
<li>See full diff in <a href="https://github.com/evanphx/json-patch/compare/v5.8.1...v5.9.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/evanphx/json-patch/v5&package-manager=go_modules&previous-version=5.8.1&new-version=5.9.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 333d91a4c..668cbad59 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -161,8 +161,11 @@ Adding a new version? You'll need three changes:
   to Konnect.
   [#5453](https://github.com/Kong/kubernetes-ingress-controller/pull/5453)
 - Added `SanitizeKonnectConfigDumps` feature gate allowing to enable sanitizing
-  sensitive data in Konnect configuration dumps. 
+  sensitive data in Konnect configuration dumps.
   [#5489](https://github.com/Kong/kubernetes-ingress-controller/pull/5489)
+- Kong Plugin's `config` field now is sanitized when it contains sensitive data
+  sourced from a Secret (i.e. `configFrom` or `configPatches` is used).
+  [#5495](https://github.com/Kong/kubernetes-ingress-controller/pull/5495)
 
 ### Fixed
 
diff --git a/go.mod b/go.mod
index a2d6d2adf..c518c0222 100644
--- a/go.mod
+++ b/go.mod
@@ -49,7 +49,7 @@ require (
 	github.com/stretchr/testify v1.8.4
 	github.com/testcontainers/testcontainers-go v0.27.0
 	go.uber.org/zap v1.26.0
-	google.golang.org/api v0.158.0
+	google.golang.org/api v0.159.0
 	k8s.io/api v0.29.1
 	k8s.io/apiextensions-apiserver v0.29.1
 	k8s.io/apimachinery v0.29.1
@@ -124,7 +124,7 @@ require (
 	github.com/docker/go-units v0.5.0 // indirect
 	github.com/emicklei/go-restful/v3 v3.11.0 // indirect
 	github.com/evanphx/json-patch v5.7.0+incompatible // indirect
-	github.com/evanphx/json-patch/v5 v5.8.1
+	github.com/evanphx/json-patch/v5 v5.9.0
 	github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
 	github.com/fatih/camelcase v1.0.0 // indirect
 	github.com/fatih/color v1.15.0 // indirect
diff --git a/go.sum b/go.sum
index a4f12e9da..e9efdc010 100644
--- a/go.sum
+++ b/go.sum
@@ -98,8 +98,8 @@ github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7
 github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI=
 github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
-github.com/evanphx/json-patch/v5 v5.8.1 h1:iPEdwg0XayoS+E7Mth9JxwUtOgyVxnDTXHtKhZPlZxA=
-github.com/evanphx/json-patch/v5 v5.8.1/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
+github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
+github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
 github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
 github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
 github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
@@ -599,8 +599,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
 gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
-google.golang.org/api v0.158.0 h1:7SKwlRqzrXT2ULl6a3iESb+1pOak5IOd5F+ay5ULiV4=
-google.golang.org/api v0.158.0/go.mod h1:0mu0TpK33qnydLvWqbImq2b1eQ5FHRSDCBzAxX9ZHyw=
+google.golang.org/api v0.159.0 h1:fVTj+7HHiUYz4JEZCHHoRIeQX7h5FMzrA2RF/DzDdbs=
+google.golang.org/api v0.159.0/go.mod h1:0mu0TpK33qnydLvWqbImq2b1eQ5FHRSDCBzAxX9ZHyw=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
diff --git a/internal/dataplane/kongstate/kongstate.go b/internal/dataplane/kongstate/kongstate.go
index 33825fe32..a25c46b31 100644
--- a/internal/dataplane/kongstate/kongstate.go
+++ b/internal/dataplane/kongstate/kongstate.go
@@ -47,7 +47,9 @@ func (ks *KongState) SanitizedCopy() *KongState {
 			return
 		}(),
 		CACertificates: ks.CACertificates,
-		Plugins:        ks.Plugins,
+		Plugins: lo.Map(ks.Plugins, func(p Plugin, _ int) Plugin {
+			return p.SanitizedCopy()
+		}),
 		Consumers: func() (res []Consumer) {
 			for _, v := range ks.Consumers {
 				res = append(res, *v.SanitizedCopy())
diff --git a/internal/dataplane/kongstate/plugin.go b/internal/dataplane/kongstate/plugin.go
index 56b746588..a14de0ae1 100644
--- a/internal/dataplane/kongstate/plugin.go
+++ b/internal/dataplane/kongstate/plugin.go
@@ -4,11 +4,14 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"strings"
 
 	jsonpatch "github.com/evanphx/json-patch/v5"
 	"github.com/kong/go-kong/kong"
+	"github.com/samber/lo"
 	corev1 "k8s.io/api/core/v1"
 	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/yaml"
 
 	"github.com/kong/kubernetes-ingress-controller/v3/internal/store"
@@ -16,6 +19,107 @@ import (
 	kongv1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1"
 )
 
+// Plugin represents a plugin Object in Kong.
+type Plugin struct {
+	kong.Plugin
+	K8sParent           client.Object
+	SensitiveFieldsMeta PluginSensitiveFieldsMetadata
+}
+
+func (p Plugin) DeepCopy() Plugin {
+	return Plugin{
+		Plugin:              *p.Plugin.DeepCopy(),
+		K8sParent:           p.K8sParent,
+		SensitiveFieldsMeta: p.SensitiveFieldsMeta,
+	}
+}
+
+func (p Plugin) SanitizedCopy() Plugin {
+	// We do not want to return an error if any of below fails - the best we can do
+	// is to return a plugin with wholly redacted config.
+	// Let's have a closure returning a plugin with wholly redacted config prepared.
+	whollySanitized := func() Plugin {
+		p := p.DeepCopy()
+		p.Config = sanitizeWholePluginConfig(p.Config)
+		return p
+	}
+
+	// If the whole config is sensitive, we need to redact the entire config.
+	if p.SensitiveFieldsMeta.WholeConfigIsSensitive {
+		return whollySanitized()
+	}
+
+	// If there are JSON paths, we need to redact them.
+	if len(p.SensitiveFieldsMeta.JSONPaths) > 0 {
+		var patchOperations []string
+		for _, path := range p.SensitiveFieldsMeta.JSONPaths {
+			// If the path is empty, we need to sanitize the whole config.
+			// An empty path means that the patch is on the root of the config.
+			if path == "" {
+				return whollySanitized()
+			}
+
+			patchOperations = append(patchOperations, fmt.Sprintf(
+				`{"op":"replace","path":"%s","value":"%s"}`,
+				path,
+				*redactedString,
+			))
+		}
+
+		// Decode the patch and apply it to the config.
+		// We need to marshal the config to JSON and then unmarshal it back to Configuration
+		// because the patch library works with bytes.
+		patch, err := jsonpatch.DecodePatch([]byte(fmt.Sprintf("[%s]", strings.Join(patchOperations, ","))))
+		if err != nil {
+			return whollySanitized()
+		}
+		configB, err := json.Marshal(p.Config)
+		if err != nil {
+			return whollySanitized()
+		}
+		sanitizedConfigB, err := patch.Apply(configB)
+		if err != nil {
+			return whollySanitized()
+		}
+		sanitizedConfig := kong.Configuration{}
+		if err := json.Unmarshal(sanitizedConfigB, &sanitizedConfig); err != nil {
+			return whollySanitized()
+		}
+
+		sanitized := p.DeepCopy()
+		sanitized.Config = sanitizedConfig
+		return sanitized
+	}
+
+	// Nothing to sanitize.
+	return p
+}
+
+// sanitizeWholePluginConfig redacts the entire config of a plugin by replacing all of its
+// values with a redacted string.
+func sanitizeWholePluginConfig(config kong.Configuration) kong.Configuration {
+	sanitized := config.DeepCopy()
+	for k := range config {
+		sanitized[k] = *redactedString
+	}
+	return sanitized
+}
+
+// PluginSensitiveFieldsMetadata holds metadata about sensitive fields in a plugin's configuration.
+// It can be used to sanitize them before exposing the configuration to the user (e.g. in debug dumps
+// or in Konnect Admin API).
+type PluginSensitiveFieldsMetadata struct {
+	// WholeConfigIsSensitive indicates that the entire configuration of the plugin is sensitive.
+	// If this is true, the configuration should be redacted entirely (each of its fields' values
+	// should be replaced with a redacted string).
+	WholeConfigIsSensitive bool
+
+	// JSONPaths holds a list of JSON paths to sensitive fields in the plugin's configuration.
+	// If this is not empty, the configuration should be redacted by replacing the values of the
+	// fields at these paths with a redacted string.
+	JSONPaths []string
+}
+
 // getKongPluginOrKongClusterPlugin fetches a KongPlugin or KongClusterPlugin (as fallback) from the store.
 // If both are not found, an error is returned.
 func getKongPluginOrKongClusterPlugin(s store.Storer, namespace, name string) (
@@ -77,6 +181,14 @@ func kongPluginFromK8SClusterPlugin(
 		}
 	}
 
+	// Prepare sensitive fields metadata for the plugin.
+	sensitiveFieldsMeta := PluginSensitiveFieldsMetadata{
+		JSONPaths: lo.Map(k8sPlugin.ConfigPatches, func(patch kongv1.NamespacedConfigPatch, _ int) string {
+			return patch.Path
+		}),
+		WholeConfigIsSensitive: k8sPlugin.ConfigFrom != nil,
+	}
+
 	return Plugin{
 		Plugin: plugin{
 			Name:   k8sPlugin.PluginName,
@@ -89,7 +201,8 @@ func kongPluginFromK8SClusterPlugin(
 			Protocols:    protocolsToStrings(k8sPlugin.Protocols),
 			Tags:         util.GenerateTagsForObject(&k8sPlugin),
 		}.toKongPlugin(),
-		K8sParent: &k8sPlugin,
+		K8sParent:           &k8sPlugin,
+		SensitiveFieldsMeta: sensitiveFieldsMeta,
 	}, nil
 }
 
@@ -131,6 +244,14 @@ func kongPluginFromK8SPlugin(
 		}
 	}
 
+	// Prepare sensitive fields metadata for the plugin.
+	sensitiveFieldsMeta := PluginSensitiveFieldsMetadata{
+		JSONPaths: lo.Map(k8sPlugin.ConfigPatches, func(patch kongv1.ConfigPatch, _ int) string {
+			return patch.Path
+		}),
+		WholeConfigIsSensitive: k8sPlugin.ConfigFrom != nil,
+	}
+
 	return Plugin{
 		Plugin: plugin{
 			Name:   k8sPlugin.PluginName,
@@ -143,7 +264,8 @@ func kongPluginFromK8SPlugin(
 			Protocols:    protocolsToStrings(k8sPlugin.Protocols),
 			Tags:         util.GenerateTagsForObject(&k8sPlugin),
 		}.toKongPlugin(),
-		K8sParent: &k8sPlugin,
+		K8sParent:           &k8sPlugin,
+		SensitiveFieldsMeta: sensitiveFieldsMeta,
 	}, nil
 }
 
diff --git a/internal/dataplane/kongstate/types.go b/internal/dataplane/kongstate/types.go
index fcfa7afe5..282d81081 100644
--- a/internal/dataplane/kongstate/types.go
+++ b/internal/dataplane/kongstate/types.go
@@ -4,7 +4,6 @@ import (
 	"fmt"
 
 	"github.com/kong/go-kong/kong"
-	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
 type PortMode int
@@ -66,16 +65,3 @@ func (c *Certificate) SanitizedCopy() *Certificate {
 		},
 	}
 }
-
-// Plugin represents a plugin Object in Kong.
-type Plugin struct {
-	kong.Plugin
-	K8sParent client.Object
-}
-
-func (p Plugin) DeepCopy() Plugin {
-	return Plugin{
-		Plugin:    *p.Plugin.DeepCopy(),
-		K8sParent: p.K8sParent,
-	}
-}


Test patch:
diff --git a/internal/dataplane/kongstate/kongstate_test.go b/internal/dataplane/kongstate/kongstate_test.go
index 52455a55a..7c4b9b893 100644
--- a/internal/dataplane/kongstate/kongstate_test.go
+++ b/internal/dataplane/kongstate/kongstate_test.go
@@ -57,7 +57,10 @@ func TestKongState_SanitizedCopy(t *testing.T) {
 				Upstreams:      []Upstream{{Upstream: kong.Upstream{ID: kong.String("1")}}},
 				Certificates:   []Certificate{{Certificate: kong.Certificate{ID: kong.String("1"), Key: kong.String("secret")}}},
 				CACertificates: []kong.CACertificate{{ID: kong.String("1")}},
-				Plugins:        []Plugin{{Plugin: kong.Plugin{ID: kong.String("1")}}},
+				Plugins: []Plugin{{
+					SensitiveFieldsMeta: PluginSensitiveFieldsMetadata{WholeConfigIsSensitive: true},
+					Plugin:              kong.Plugin{ID: kong.String("1"), Config: kong.Configuration{"secret": "secretValue"}},
+				}},
 				Consumers: []Consumer{{
 					KeyAuths: []*KeyAuth{{kong.KeyAuth{ID: kong.String("1"), Key: kong.String("secret")}}},
 				}},
@@ -78,7 +81,10 @@ func TestKongState_SanitizedCopy(t *testing.T) {
 				Upstreams:      []Upstream{{Upstream: kong.Upstream{ID: kong.String("1")}}},
 				Certificates:   []Certificate{{Certificate: kong.Certificate{ID: kong.String("1"), Key: redactedString}}},
 				CACertificates: []kong.CACertificate{{ID: kong.String("1")}},
-				Plugins:        []Plugin{{Plugin: kong.Plugin{ID: kong.String("1")}}},
+				Plugins: []Plugin{{
+					SensitiveFieldsMeta: PluginSensitiveFieldsMetadata{WholeConfigIsSensitive: true},
+					Plugin:              kong.Plugin{ID: kong.String("1"), Config: kong.Configuration{"secret": *redactedString}},
+				}},
 				Consumers: []Consumer{{
 					KeyAuths: []*KeyAuth{{kong.KeyAuth{ID: kong.String("1"), Key: redactedString}}},
 				}},
diff --git a/internal/dataplane/kongstate/plugin_test.go b/internal/dataplane/kongstate/plugin_test.go
index fed7b3f1c..5faf399c2 100644
--- a/internal/dataplane/kongstate/plugin_test.go
+++ b/internal/dataplane/kongstate/plugin_test.go
@@ -5,6 +5,7 @@ import (
 
 	"github.com/kong/go-kong/kong"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 	corev1 "k8s.io/api/core/v1"
 	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -14,7 +15,6 @@ import (
 )
 
 func TestKongPluginFromK8SClusterPlugin(t *testing.T) {
-	assert := assert.New(t)
 	store, _ := store.NewFakeStore(store.FakeObjects{
 		Secrets: []*corev1.Secret{
 			{
@@ -32,277 +32,284 @@ func TestKongPluginFromK8SClusterPlugin(t *testing.T) {
 			},
 		},
 	})
-	type args struct {
-		plugin kongv1.KongClusterPlugin
-	}
+
 	tests := []struct {
 		name    string
-		args    args
-		want    kong.Plugin
+		plugin  kongv1.KongClusterPlugin
+		want    Plugin
 		wantErr bool
 	}{
 		{
 			name: "basic configuration",
-			args: args{
-				plugin: kongv1.KongClusterPlugin{
-					Protocols:    []kongv1.KongProtocol{"http"},
-					PluginName:   "correlation-id",
-					InstanceName: "example",
-					Config: apiextensionsv1.JSON{
-						Raw: []byte(`{"header_name": "foo"}`),
-					},
+			plugin: kongv1.KongClusterPlugin{
+				Protocols:    []kongv1.KongProtocol{"http"},
+				PluginName:   "correlation-id",
+				InstanceName: "example",
+				Config: apiextensionsv1.JSON{
+					Raw: []byte(`{"header_name": "foo"}`),
 				},
 			},
-			want: kong.Plugin{
-				Name: kong.String("correlation-id"),
-				Config: kong.Configuration{
-					"header_name": "foo",
+			want: Plugin{
+				Plugin: kong.Plugin{
+					Name: kong.String("correlation-id"),
+					Config: kong.Configuration{
+						"header_name": "foo",
+					},
+					Protocols:    kong.StringSlice("http"),
+					InstanceName: kong.String("example"),
 				},
-				Protocols:    kong.StringSlice("http"),
-				InstanceName: kong.String("example"),
+				SensitiveFieldsMeta: PluginSensitiveFieldsMetadata{JSONPaths: []string{}},
 			},
 			wantErr: false,
 		},
 		{
 			name: "secret configuration",
-			args: args{
-				plugin: kongv1.KongClusterPlugin{
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					ConfigFrom: &kongv1.NamespacedConfigSource{
-						SecretValue: kongv1.NamespacedSecretValueFromSource{
-							Key:       "correlation-id-config",
-							Secret:    "conf-secret",
-							Namespace: "default",
-						},
+			plugin: kongv1.KongClusterPlugin{
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				ConfigFrom: &kongv1.NamespacedConfigSource{
+					SecretValue: kongv1.NamespacedSecretValueFromSource{
+						Key:       "correlation-id-config",
+						Secret:    "conf-secret",
+						Namespace: "default",
 					},
 				},
 			},
-			want: kong.Plugin{
-				Name: kong.String("correlation-id"),
-				Config: kong.Configuration{
-					"header_name": "foo",
+			want: Plugin{
+				Plugin: kong.Plugin{
+					Name: kong.String("correlation-id"),
+					Config: kong.Configuration{
+						"header_name": "foo",
+					},
+					Protocols: kong.StringSlice("http"),
+				},
+				SensitiveFieldsMeta: PluginSensitiveFieldsMetadata{
+					WholeConfigIsSensitive: true,
+					JSONPaths:              []string{},
 				},
-				Protocols: kong.StringSlice("http"),
 			},
 			wantErr: false,
 		},
 		{
 			name: "missing secret configuration",
-			args: args{
-				plugin: kongv1.KongClusterPlugin{
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					ConfigFrom: &kongv1.NamespacedConfigSource{
-						SecretValue: kongv1.NamespacedSecretValueFromSource{
-							Key:       "correlation-id-config",
-							Secret:    "missing",
-							Namespace: "default",
-						},
+			plugin: kongv1.KongClusterPlugin{
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				ConfigFrom: &kongv1.NamespacedConfigSource{
+					SecretValue: kongv1.NamespacedSecretValueFromSource{
+						Key:       "correlation-id-config",
+						Secret:    "missing",
+						Namespace: "default",
 					},
 				},
 			},
-			want:    kong.Plugin{},
+			want:    Plugin{},
 			wantErr: true,
 		},
 		{
 			name: "non-JSON configuration",
-			args: args{
-				plugin: kongv1.KongClusterPlugin{
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					Config: apiextensionsv1.JSON{
-						Raw: []byte(`{{}`),
-					},
+			plugin: kongv1.KongClusterPlugin{
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				Config: apiextensionsv1.JSON{
+					Raw: []byte(`{{}`),
 				},
 			},
-			want:    kong.Plugin{},
+			want:    Plugin{},
 			wantErr: true,
 		},
 		{
 			name: "both Config and ConfigFrom set",
-			args: args{
-				plugin: kongv1.KongClusterPlugin{
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					Config: apiextensionsv1.JSON{
-						Raw: []byte(`{"header_name": "foo"}`),
-					},
-					ConfigFrom: &kongv1.NamespacedConfigSource{
-						SecretValue: kongv1.NamespacedSecretValueFromSource{
-							Key:       "correlation-id-config",
-							Secret:    "conf-secret",
-							Namespace: "default",
-						},
+			plugin: kongv1.KongClusterPlugin{
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				Config: apiextensionsv1.JSON{
+					Raw: []byte(`{"header_name": "foo"}`),
+				},
+				ConfigFrom: &kongv1.NamespacedConfigSource{
+					SecretValue: kongv1.NamespacedSecretValueFromSource{
+						Key:       "correlation-id-config",
+						Secret:    "conf-secret",
+						Namespace: "default",
 					},
 				},
 			},
-			want:    kong.Plugin{},
+			want:    Plugin{},
 			wantErr: true,
 		},
 		{
 			name: "Config and ConfigPatches set",
-			args: args{
-				plugin: kongv1.KongClusterPlugin{
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					Config: apiextensionsv1.JSON{
-						Raw: []byte(`{"header_name": "foo"}`),
-					},
-					ConfigPatches: []kongv1.NamespacedConfigPatch{
-						{
-							Path: "/generator",
-							ValueFrom: kongv1.NamespacedConfigSource{
-								SecretValue: kongv1.NamespacedSecretValueFromSource{
-									Key:       "correlation-id-generator",
-									Secret:    "conf-secret",
-									Namespace: "default",
-								},
+			plugin: kongv1.KongClusterPlugin{
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				Config: apiextensionsv1.JSON{
+					Raw: []byte(`{"header_name": "foo"}`),
+				},
+				ConfigPatches: []kongv1.NamespacedConfigPatch{
+					{
+						Path: "/generator",
+						ValueFrom: kongv1.NamespacedConfigSource{
+							SecretValue: kongv1.NamespacedSecretValueFromSource{
+								Key:       "correlation-id-generator",
+								Secret:    "conf-secret",
+								Namespace: "default",
 							},
 						},
 					},
 				},
 			},
-			want: kong.Plugin{
-				Name: kong.String("correlation-id"),
-				Config: kong.Configuration{
-					"header_name": "foo",
-					"generator":   "uuid",
+			want: Plugin{
+				Plugin: kong.Plugin{
+					Name: kong.String("correlation-id"),
+					Config: kong.Configuration{
+						"header_name": "foo",
+						"generator":   "uuid",
+					},
+					Protocols: kong.StringSlice("http"),
+				},
+				SensitiveFieldsMeta: PluginSensitiveFieldsMetadata{
+					JSONPaths: []string{"/generator"},
 				},
-				Protocols: kong.StringSlice("http"),
 			},
 		},
 		{
 			name: "configPatch on subpath of non-exist path",
-			args: args{
-				plugin: kongv1.KongClusterPlugin{
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "response-transformer",
-					Config: apiextensionsv1.JSON{
-						Raw: []byte(`{"replace":{"headers":["foo:bar"]}}`),
-					},
-					ConfigPatches: []kongv1.NamespacedConfigPatch{
-						{
-							Path: "/add/headers",
-							ValueFrom: kongv1.NamespacedConfigSource{
-								SecretValue: kongv1.NamespacedSecretValueFromSource{
-									Namespace: "default",
-									Key:       "response-transformer-add-headers",
-									Secret:    "conf-secret",
-								},
+			plugin: kongv1.KongClusterPlugin{
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "response-transformer",
+				Config: apiextensionsv1.JSON{
+					Raw: []byte(`{"replace":{"headers":["foo:bar"]}}`),
+				},
+				ConfigPatches: []kongv1.NamespacedConfigPatch{
+					{
+						Path: "/add/headers",
+						ValueFrom: kongv1.NamespacedConfigSource{
+							SecretValue: kongv1.NamespacedSecretValueFromSource{
+								Namespace: "default",
+								Key:       "response-transformer-add-headers",
+								Secret:    "conf-secret",
 							},
 						},
 					},
 				},
 			},
-			want: kong.Plugin{
-				Name: kong.String("response-transformer"),
-				Config: kong.Configuration{
-					"replace": map[string]interface{}{
-						"headers": []interface{}{
-							"foo:bar",
+			want: Plugin{
+				Plugin: kong.Plugin{
+					Name: kong.String("response-transformer"),
+					Config: kong.Configuration{
+						"replace": map[string]interface{}{
+							"headers": []interface{}{
+								"foo:bar",
+							},
 						},
-					},
-					"add": map[string]interface{}{
-						"headers": []interface{}{
-							"h1:v1",
-							"h2:v2",
+						"add": map[string]interface{}{
+							"headers": []interface{}{
+								"h1:v1",
+								"h2:v2",
+							},
 						},
 					},
+					Protocols: kong.StringSlice("http"),
+				},
+				SensitiveFieldsMeta: PluginSensitiveFieldsMetadata{
+					JSONPaths: []string{"/add/headers"},
 				},
-				Protocols: kong.StringSlice("http"),
 			},
 		},
 		{
 			name: "empty config and configPatch for particular paths",
-			args: args{
-				plugin: kongv1.KongClusterPlugin{
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					Config:     apiextensionsv1.JSON{},
-					ConfigPatches: []kongv1.NamespacedConfigPatch{
-						{
-							Path: "/header_name",
-							ValueFrom: kongv1.NamespacedConfigSource{
-								SecretValue: kongv1.NamespacedSecretValueFromSource{
-									Namespace: "default",
-									Key:       "correlation-id-headername",
-									Secret:    "conf-secret",
-								},
+			plugin: kongv1.KongClusterPlugin{
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				Config:     apiextensionsv1.JSON{},
+				ConfigPatches: []kongv1.NamespacedConfigPatch{
+					{
+						Path: "/header_name",
+						ValueFrom: kongv1.NamespacedConfigSource{
+							SecretValue: kongv1.NamespacedSecretValueFromSource{
+								Namespace: "default",
+								Key:       "correlation-id-headername",
+								Secret:    "conf-secret",
 							},
 						},
-						{
-							Path: "/generator",
-							ValueFrom: kongv1.NamespacedConfigSource{
-								SecretValue: kongv1.NamespacedSecretValueFromSource{
-									Namespace: "default",
-									Key:       "correlation-id-generator",
-									Secret:    "conf-secret",
-								},
+					},
+					{
+						Path: "/generator",
+						ValueFrom: kongv1.NamespacedConfigSource{
+							SecretValue: kongv1.NamespacedSecretValueFromSource{
+								Namespace: "default",
+								Key:       "correlation-id-generator",
+								Secret:    "conf-secret",
 							},
 						},
 					},
 				},
 			},
-			want: kong.Plugin{
-				Name: kong.String("correlation-id"),
-				Config: kong.Configuration{
-					"header_name": "foo",
-					"generator":   "uuid",
+			want: Plugin{
+				Plugin: kong.Plugin{
+					Name: kong.String("correlation-id"),
+					Config: kong.Configuration{
+						"header_name": "foo",
+						"generator":   "uuid",
+					},
+					Protocols: kong.StringSlice("http"),
+				},
+				SensitiveFieldsMeta: PluginSensitiveFieldsMetadata{
+					JSONPaths: []string{"/header_name", "/generator"},
 				},
-				Protocols: kong.StringSlice("http"),
 			},
 		},
 		{
 			name: "empty config and configPatch for whole object",
-			args: args{
-				plugin: kongv1.KongClusterPlugin{
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					Config:     apiextensionsv1.JSON{},
-					ConfigPatches: []kongv1.NamespacedConfigPatch{
-						{
-							Path: "",
-							ValueFrom: kongv1.NamespacedConfigSource{
-								SecretValue: kongv1.NamespacedSecretValueFromSource{
-									Namespace: "default",
-									Key:       "correlation-id-config",
-									Secret:    "conf-secret",
-								},
+			plugin: kongv1.KongClusterPlugin{
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				Config:     apiextensionsv1.JSON{},
+				ConfigPatches: []kongv1.NamespacedConfigPatch{
+					{
+						Path: "",
+						ValueFrom: kongv1.NamespacedConfigSource{
+							SecretValue: kongv1.NamespacedSecretValueFromSource{
+								Namespace: "default",
+								Key:       "correlation-id-config",
+								Secret:    "conf-secret",
 							},
 						},
 					},
 				},
 			},
-			want: kong.Plugin{
-				Name: kong.String("correlation-id"),
-				Config: kong.Configuration{
-					"header_name": "foo",
+			want: Plugin{
+				Plugin: kong.Plugin{
+					Name: kong.String("correlation-id"),
+					Config: kong.Configuration{
+						"header_name": "foo",
+					},
+					Protocols: kong.StringSlice("http"),
+				},
+				SensitiveFieldsMeta: PluginSensitiveFieldsMetadata{
+					JSONPaths: []string{""},
 				},
-				Protocols: kong.StringSlice("http"),
 			},
 		},
 		{
 			name: "missing secret in configPatches",
-			args: args{
-				plugin: kongv1.KongClusterPlugin{
-					ObjectMeta: metav1.ObjectMeta{
-						Name: "test",
-					},
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					Config: apiextensionsv1.JSON{
-						Raw: []byte(`{"header_name": "foo"}`),
-					},
-					ConfigPatches: []kongv1.NamespacedConfigPatch{
-						{
-							Path: "/generator",
-							ValueFrom: kongv1.NamespacedConfigSource{
-								SecretValue: kongv1.NamespacedSecretValueFromSource{
-									Namespace: "default",
-									Key:       "correlation-id-generator",
-									Secret:    "missing-secret",
-								},
+			plugin: kongv1.KongClusterPlugin{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "test",
+				},
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				Config: apiextensionsv1.JSON{
+					Raw: []byte(`{"header_name": "foo"}`),
+				},
+				ConfigPatches: []kongv1.NamespacedConfigPatch{
+					{
+						Path: "/generator",
+						ValueFrom: kongv1.NamespacedConfigSource{
+							SecretValue: kongv1.NamespacedSecretValueFromSource{
+								Namespace: "default",
+								Key:       "correlation-id-generator",
+								Secret:    "missing-secret",
 							},
 						},
 					},
@@ -312,25 +319,23 @@ func TestKongPluginFromK8SClusterPlugin(t *testing.T) {
 		},
 		{
 			name: "missing key of secret in cofigPatches",
-			args: args{
-				plugin: kongv1.KongClusterPlugin{
-					ObjectMeta: metav1.ObjectMeta{
-						Name: "test",
-					},
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					Config: apiextensionsv1.JSON{
-						Raw: []byte(`{"header_name": "foo"}`),
-					},
-					ConfigPatches: []kongv1.NamespacedConfigPatch{
-						{
-							Path: "/generator",
-							ValueFrom: kongv1.NamespacedConfigSource{
-								SecretValue: kongv1.NamespacedSecretValueFromSource{
-									Namespace: "default",
-									Key:       "correlation-id-missing",
-									Secret:    "conf-secret",
-								},
+			plugin: kongv1.KongClusterPlugin{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "test",
+				},
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				Config: apiextensionsv1.JSON{
+					Raw: []byte(`{"header_name": "foo"}`),
+				},
+				ConfigPatches: []kongv1.NamespacedConfigPatch{
+					{
+						Path: "/generator",
+						ValueFrom: kongv1.NamespacedConfigSource{
+							SecretValue: kongv1.NamespacedSecretValueFromSource{
+								Namespace: "default",
+								Key:       "correlation-id-missing",
+								Secret:    "conf-secret",
 							},
 						},
 					},
@@ -340,25 +345,23 @@ func TestKongPluginFromK8SClusterPlugin(t *testing.T) {
 		},
 		{
 			name: "invalid value in configPatches",
-			args: args{
-				plugin: kongv1.KongClusterPlugin{
-					ObjectMeta: metav1.ObjectMeta{
-						Name: "test",
-					},
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					Config: apiextensionsv1.JSON{
-						Raw: []byte(`{"header_name": "foo"}`),
-					},
-					ConfigPatches: []kongv1.NamespacedConfigPatch{
-						{
-							Path: "/generator",
-							ValueFrom: kongv1.NamespacedConfigSource{
-								SecretValue: kongv1.NamespacedSecretValueFromSource{
-									Namespace: "default",
-									Key:       "correlation-id-invalid",
-									Secret:    "conf-secret",
-								},
+			plugin: kongv1.KongClusterPlugin{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "test",
+				},
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				Config: apiextensionsv1.JSON{
+					Raw: []byte(`{"header_name": "foo"}`),
+				},
+				ConfigPatches: []kongv1.NamespacedConfigPatch{
+					{
+						Path: "/generator",
+						ValueFrom: kongv1.NamespacedConfigSource{
+							SecretValue: kongv1.NamespacedSecretValueFromSource{
+								Namespace: "default",
+								Key:       "correlation-id-invalid",
+								Secret:    "conf-secret",
 							},
 						},
 					},
@@ -369,19 +372,18 @@ func TestKongPluginFromK8SClusterPlugin(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := kongPluginFromK8SClusterPlugin(store, tt.args.plugin)
-			if (err != nil) != tt.wantErr {
-				t.Errorf("kongPluginFromK8SClusterPlugin error = %v, wantErr %v", err, tt.wantErr)
+			got, err := kongPluginFromK8SClusterPlugin(store, tt.plugin)
+			if tt.wantErr {
+				require.Error(t, err)
 				return
 			}
-			assert.Equal(tt.want, got.Plugin)
-			assert.NotEmpty(t, got.K8sParent)
+			tt.want.K8sParent = tt.plugin.DeepCopy()
+			assert.Equal(t, tt.want, got)
 		})
 	}
 }
 
 func TestKongPluginFromK8SPlugin(t *testing.T) {
-	assert := assert.New(t)
 	store, _ := store.NewFakeStore(store.FakeObjects{
 		Secrets: []*corev1.Secret{
 			{
@@ -399,293 +401,296 @@ func TestKongPluginFromK8SPlugin(t *testing.T) {
 			},
 		},
 	})
-	type args struct {
-		plugin kongv1.KongPlugin
-	}
 	tests := []struct {
 		name    string
-		args    args
-		want    kong.Plugin
+		plugin  kongv1.KongPlugin
+		want    Plugin
 		wantErr bool
 	}{
 		{
 			name: "basic configuration",
-			args: args{
-				plugin: kongv1.KongPlugin{
-					Protocols:    []kongv1.KongProtocol{"http"},
-					PluginName:   "correlation-id",
-					InstanceName: "example",
-					Config: apiextensionsv1.JSON{
-						Raw: []byte(`{"header_name": "foo"}`),
-					},
+			plugin: kongv1.KongPlugin{
+				Protocols:    []kongv1.KongProtocol{"http"},
+				PluginName:   "correlation-id",
+				InstanceName: "example",
+				Config: apiextensionsv1.JSON{
+					Raw: []byte(`{"header_name": "foo"}`),
 				},
 			},
-			want: kong.Plugin{
-				Name: kong.String("correlation-id"),
-				Config: kong.Configuration{
-					"header_name": "foo",
+			want: Plugin{
+				Plugin: kong.Plugin{
+					Name: kong.String("correlation-id"),
+					Config: kong.Configuration{
+						"header_name": "foo",
+					},
+					Protocols:    kong.StringSlice("http"),
+					InstanceName: kong.String("example"),
 				},
-				Protocols:    kong.StringSlice("http"),
-				InstanceName: kong.String("example"),
+				SensitiveFieldsMeta: PluginSensitiveFieldsMetadata{JSONPaths: []string{}},
 			},
 			wantErr: false,
 		},
 		{
 			name: "secret configuration",
-			args: args{
-				plugin: kongv1.KongPlugin{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:      "foo",
-						Namespace: "default",
-					},
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					ConfigFrom: &kongv1.ConfigSource{
-						SecretValue: kongv1.SecretValueFromSource{
-							Key:    "correlation-id-config",
-							Secret: "conf-secret",
-						},
+			plugin: kongv1.KongPlugin{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "foo",
+					Namespace: "default",
+				},
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				ConfigFrom: &kongv1.ConfigSource{
+					SecretValue: kongv1.SecretValueFromSource{
+						Key:    "correlation-id-config",
+						Secret: "conf-secret",
 					},
 				},
 			},
-			want: kong.Plugin{
-				Name: kong.String("correlation-id"),
-				Config: kong.Configuration{
-					"header_name": "foo",
+			want: Plugin{
+				Plugin: kong.Plugin{
+					Name: kong.String("correlation-id"),
+					Config: kong.Configuration{
+						"header_name": "foo",
+					},
+					Protocols: kong.StringSlice("http"),
+				},
+				SensitiveFieldsMeta: PluginSensitiveFieldsMetadata{
+					WholeConfigIsSensitive: true,
+					JSONPaths:              []string{},
 				},
-				Protocols: kong.StringSlice("http"),
 			},
 			wantErr: false,
 		},
 		{
 			name: "missing secret configuration",
-			args: args{
-				plugin: kongv1.KongPlugin{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:      "foo",
-						Namespace: "default",
-					},
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					ConfigFrom: &kongv1.ConfigSource{
-						SecretValue: kongv1.SecretValueFromSource{
-							Key:    "correlation-id-config",
-							Secret: "missing",
-						},
+			plugin: kongv1.KongPlugin{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "foo",
+					Namespace: "default",
+				},
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				ConfigFrom: &kongv1.ConfigSource{
+					SecretValue: kongv1.SecretValueFromSource{
+						Key:    "correlation-id-config",
+						Secret: "missing",
 					},
 				},
 			},
-			want:    kong.Plugin{},
 			wantErr: true,
 		},
 		{
 			name: "non-JSON configuration",
-			args: args{
-				plugin: kongv1.KongPlugin{
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					Config: apiextensionsv1.JSON{
-						Raw: []byte(`{{}`),
-					},
+			plugin: kongv1.KongPlugin{
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				Config: apiextensionsv1.JSON{
+					Raw: []byte(`{{}`),
 				},
 			},
-			want:    kong.Plugin{},
 			wantErr: true,
 		},
 		{
 			name: "both Config and ConfigFrom set",
-			args: args{
-				plugin: kongv1.KongPlugin{
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					Config: apiextensionsv1.JSON{
-						Raw: []byte(`{"header_name": "foo"}`),
-					},
-					ConfigFrom: &kongv1.ConfigSource{
-						SecretValue: kongv1.SecretValueFromSource{
-							Key:    "correlation-id-config",
-							Secret: "conf-secret",
-						},
+			plugin: kongv1.KongPlugin{
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				Config: apiextensionsv1.JSON{
+					Raw: []byte(`{"header_name": "foo"}`),
+				},
+				ConfigFrom: &kongv1.ConfigSource{
+					SecretValue: kongv1.SecretValueFromSource{
+						Key:    "correlation-id-config",
+						Secret: "conf-secret",
 					},
 				},
 			},
-			want:    kong.Plugin{},
 			wantErr: true,
 		},
 		{
 			name: "config and configPatches set",
-			args: args{
-				plugin: kongv1.KongPlugin{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:      "test",
-						Namespace: "default",
-					},
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					Config: apiextensionsv1.JSON{
-						Raw: []byte(`{"header_name": "foo"}`),
-					},
-					ConfigPatches: []kongv1.ConfigPatch{
-						{
-							Path: "/generator",
-							ValueFrom: kongv1.ConfigSource{
-								SecretValue: kongv1.SecretValueFromSource{
-									Key:    "correlation-id-generator",
-									Secret: "conf-secret",
-								},
+			plugin: kongv1.KongPlugin{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test",
+					Namespace: "default",
+				},
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				Config: apiextensionsv1.JSON{
+					Raw: []byte(`{"header_name": "foo"}`),
+				},
+				ConfigPatches: []kongv1.ConfigPatch{
+					{
+						Path: "/generator",
+						ValueFrom: kongv1.ConfigSource{
+							SecretValue: kongv1.SecretValueFromSource{
+								Key:    "correlation-id-generator",
+								Secret: "conf-secret",
 							},
 						},
 					},
 				},
 			},
-			want: kong.Plugin{
-				Name: kong.String("correlation-id"),
-				Config: kong.Configuration{
-					"header_name": "foo",
-					"generator":   "uuid",
+			want: Plugin{
+				Plugin: kong.Plugin{
+					Name: kong.String("correlation-id"),
+					Config: kong.Configuration{
+						"header_name": "foo",
+						"generator":   "uuid",
+					},
+					Protocols: kong.StringSlice("http"),
+				},
+				SensitiveFieldsMeta: PluginSensitiveFieldsMetadata{
+					JSONPaths: []string{"/generator"},
 				},
-				Protocols: kong.StringSlice("http"),
 			},
 		},
 		{
 			name: "configPatch on subpath of non-exist path",
-			args: args{
-				plugin: kongv1.KongPlugin{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:      "test",
-						Namespace: "default",
-					},
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "response-transformer",
-					Config: apiextensionsv1.JSON{
-						Raw: []byte(`{"replace":{"headers":["foo:bar"]}}`),
-					},
-					ConfigPatches: []kongv1.ConfigPatch{
-						{
-							Path: "/add/headers",
-							ValueFrom: kongv1.ConfigSource{
-								SecretValue: kongv1.SecretValueFromSource{
-									Key:    "response-transformer-add-headers",
-									Secret: "conf-secret",
-								},
+			plugin: kongv1.KongPlugin{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test",
+					Namespace: "default",
+				},
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "response-transformer",
+				Config: apiextensionsv1.JSON{
+					Raw: []byte(`{"replace":{"headers":["foo:bar"]}}`),
+				},
+				ConfigPatches: []kongv1.ConfigPatch{
+					{
+						Path: "/add/headers",
+						ValueFrom: kongv1.ConfigSource{
+							SecretValue: kongv1.SecretValueFromSource{
+								Key:    "response-transformer-add-headers",
+								Secret: "conf-secret",
 							},
 						},
 					},
 				},
 			},
-			want: kong.Plugin{
-				Name: kong.String("response-transformer"),
-				Config: kong.Configuration{
-					"replace": map[string]interface{}{
-						"headers": []interface{}{
-							"foo:bar",
+			want: Plugin{
+				Plugin: kong.Plugin{
+					Name: kong.String("response-transformer"),
+					Config: kong.Configuration{
+						"replace": map[string]interface{}{
+							"headers": []interface{}{
+								"foo:bar",
+							},
 						},
-					},
-					"add": map[string]interface{}{
-						"headers": []interface{}{
-							"h1:v1",
-							"h2:v2",
+						"add": map[string]interface{}{
+							"headers": []interface{}{
+								"h1:v1",
+								"h2:v2",
+							},
 						},
 					},
+					Protocols: kong.StringSlice("http"),
+				},
+				SensitiveFieldsMeta: PluginSensitiveFieldsMetadata{
+					JSONPaths: []string{"/add/headers"},
 				},
-				Protocols: kong.StringSlice("http"),
 			},
 		},
 		{
 			name: "empty config and configPatch for particular paths",
-			args: args{
-				plugin: kongv1.KongPlugin{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:      "test",
-						Namespace: "default",
-					},
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					Config:     apiextensionsv1.JSON{},
-					ConfigPatches: []kongv1.ConfigPatch{
-						{
-							Path: "/header_name",
-							ValueFrom: kongv1.ConfigSource{
-								SecretValue: kongv1.SecretValueFromSource{
-									Key:    "correlation-id-headername",
-									Secret: "conf-secret",
-								},
+			plugin: kongv1.KongPlugin{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test",
+					Namespace: "default",
+				},
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				Config:     apiextensionsv1.JSON{},
+				ConfigPatches: []kongv1.ConfigPatch{
+					{
+						Path: "/header_name",
+						ValueFrom: kongv1.ConfigSource{
+							SecretValue: kongv1.SecretValueFromSource{
+								Key:    "correlation-id-headername",
+								Secret: "conf-secret",
 							},
 						},
-						{
-							Path: "/generator",
-							ValueFrom: kongv1.ConfigSource{
-								SecretValue: kongv1.SecretValueFromSource{
-									Key:    "correlation-id-generator",
-									Secret: "conf-secret",
-								},
+					},
+					{
+						Path: "/generator",
+						ValueFrom: kongv1.ConfigSource{
+							SecretValue: kongv1.SecretValueFromSource{
+								Key:    "correlation-id-generator",
+								Secret: "conf-secret",
 							},
 						},
 					},
 				},
 			},
-			want: kong.Plugin{
-				Name: kong.String("correlation-id"),
-				Config: kong.Configuration{
-					"header_name": "foo",
-					"generator":   "uuid",
+			want: Plugin{
+				Plugin: kong.Plugin{
+					Name: kong.String("correlation-id"),
+					Config: kong.Configuration{
+						"header_name": "foo",
+						"generator":   "uuid",
+					},
+					Protocols: kong.StringSlice("http"),
+				},
+				SensitiveFieldsMeta: PluginSensitiveFieldsMetadata{
+					JSONPaths: []string{"/header_name", "/generator"},
 				},
-				Protocols: kong.StringSlice("http"),
 			},
 		},
 		{
 			name: "empty config and configPatch for whole object",
-			args: args{
-				plugin: kongv1.KongPlugin{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:      "test",
-						Namespace: "default",
-					},
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					Config:     apiextensionsv1.JSON{},
-					ConfigPatches: []kongv1.ConfigPatch{
-						{
-							Path: "",
-							ValueFrom: kongv1.ConfigSource{
-								SecretValue: kongv1.SecretValueFromSource{
-									Key:    "correlation-id-config",
-									Secret: "conf-secret",
-								},
+			plugin: kongv1.KongPlugin{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test",
+					Namespace: "default",
+				},
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				Config:     apiextensionsv1.JSON{},
+				ConfigPatches: []kongv1.ConfigPatch{
+					{
+						Path: "",
+						ValueFrom: kongv1.ConfigSource{
+							SecretValue: kongv1.SecretValueFromSource{
+								Key:    "correlation-id-config",
+								Secret: "conf-secret",
 							},
 						},
 					},
 				},
 			},
-			want: kong.Plugin{
-				Name: kong.String("correlation-id"),
-				Config: kong.Configuration{
-					"header_name": "foo",
+			want: Plugin{
+				Plugin: kong.Plugin{
+					Name: kong.String("correlation-id"),
+					Config: kong.Configuration{
+						"header_name": "foo",
+					},
+					Protocols: kong.StringSlice("http"),
+				},
+				SensitiveFieldsMeta: PluginSensitiveFieldsMetadata{
+					JSONPaths: []string{""},
 				},
-				Protocols: kong.StringSlice("http"),
 			},
 		},
 		{
 			name: "missing secret in configPatches",
-			args: args{
-				plugin: kongv1.KongPlugin{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:      "test",
-						Namespace: "default",
-					},
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					Config: apiextensionsv1.JSON{
-						Raw: []byte(`{"header_name": "foo"}`),
-					},
-					ConfigPatches: []kongv1.ConfigPatch{
-						{
-							Path: "/generator",
-							ValueFrom: kongv1.ConfigSource{
-								SecretValue: kongv1.SecretValueFromSource{
-									Key:    "correlation-id-generator",
-									Secret: "missing-secret",
-								},
+			plugin: kongv1.KongPlugin{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test",
+					Namespace: "default",
+				},
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				Config: apiextensionsv1.JSON{
+					Raw: []byte(`{"header_name": "foo"}`),
+				},
+				ConfigPatches: []kongv1.ConfigPatch{
+					{
+						Path: "/generator",
+						ValueFrom: kongv1.ConfigSource{
+							SecretValue: kongv1.SecretValueFromSource{
+								Key:    "correlation-id-generator",
+								Secret: "missing-secret",
 							},
 						},
 					},
@@ -695,25 +700,23 @@ func TestKongPluginFromK8SPlugin(t *testing.T) {
 		},
 		{
 			name: "missing key of secret in configPatches",
-			args: args{
-				plugin: kongv1.KongPlugin{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:      "test",
-						Namespace: "default",
-					},
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					Config: apiextensionsv1.JSON{
-						Raw: []byte(`{"header_name": "foo"}`),
-					},
-					ConfigPatches: []kongv1.ConfigPatch{
-						{
-							Path: "/generator",
-							ValueFrom: kongv1.ConfigSource{
-								SecretValue: kongv1.SecretValueFromSource{
-									Key:    "correlation-id-missing",
-									Secret: "conf-secret",
-								},
+			plugin: kongv1.KongPlugin{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test",
+					Namespace: "default",
+				},
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				Config: apiextensionsv1.JSON{
+					Raw: []byte(`{"header_name": "foo"}`),
+				},
+				ConfigPatches: []kongv1.ConfigPatch{
+					{
+						Path: "/generator",
+						ValueFrom: kongv1.ConfigSource{
+							SecretValue: kongv1.SecretValueFromSource{
+								Key:    "correlation-id-missing",
+								Secret: "conf-secret",
 							},
 						},
 					},
@@ -723,25 +726,23 @@ func TestKongPluginFromK8SPlugin(t *testing.T) {
 		},
 		{
 			name: "invalid value in configPatches",
-			args: args{
-				plugin: kongv1.KongPlugin{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:      "test",
-						Namespace: "default",
-					},
-					Protocols:  []kongv1.KongProtocol{"http"},
-					PluginName: "correlation-id",
-					Config: apiextensionsv1.JSON{
-						Raw: []byte(`{"header_name": "foo"}`),
-					},
-					ConfigPatches: []kongv1.ConfigPatch{
-						{
-							Path: "/generator",
-							ValueFrom: kongv1.ConfigSource{
-								SecretValue: kongv1.SecretValueFromSource{
-									Key:    "correlation-id-invalid",
-									Secret: "conf-secret",
-								},
+			plugin: kongv1.KongPlugin{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "test",
+					Namespace: "default",
+				},
+				Protocols:  []kongv1.KongProtocol{"http"},
+				PluginName: "correlation-id",
+				Config: apiextensionsv1.JSON{
+					Raw: []byte(`{"header_name": "foo"}`),
+				},
+				ConfigPatches: []kongv1.ConfigPatch{
+					{
+						Path: "/generator",
+						ValueFrom: kongv1.ConfigSource{
+							SecretValue: kongv1.SecretValueFromSource{
+								Key:    "correlation-id-invalid",
+								Secret: "conf-secret",
 							},
 						},
 					},
@@ -752,15 +753,108 @@ func TestKongPluginFromK8SPlugin(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := kongPluginFromK8SPlugin(store, tt.args.plugin)
-			if (err != nil) != tt.wantErr {
-				t.Errorf("kongPluginFromK8SPlugin error = %v, wantErr %v", err, tt.wantErr)
+			got, err := kongPluginFromK8SPlugin(store, tt.plugin)
+			if tt.wantErr {
+				require.Error(t, err)
 				return
 			}
 			// don't care about tags in this test
 			got.Tags = nil
-			assert.Equal(tt.want, got.Plugin)
-			assert.NotEmpty(t, got.K8sParent)
+			tt.want.K8sParent = tt.plugin.DeepCopy()
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestPlugin_SanitizedCopy(t *testing.T) {
+	testCases := []struct {
+		name                    string
+		config                  kong.Configuration
+		sensitiveFieldsMeta     PluginSensitiveFieldsMetadata
+		expectedSanitizedConfig kong.Configuration
+	}{
+		{
+			name: "sensitive fields are redacted with JSONPaths",
+			config: kong.Configuration{
+				"secret": "secret-value",
+				"object": map[string]interface{}{
+					"secretObjectField": "secret-object-field-value",
+				},
+			},
+			sensitiveFieldsMeta: PluginSensitiveFieldsMetadata{
+				JSONPaths: []string{
+					"/secret",
+					"/object/secretObjectField",
+				},
+			},
+			expectedSanitizedConfig: kong.Configuration{
+				"secret": "{vault://redacted-value}",
+				"object": map[string]interface{}{
+					"secretObjectField": "{vault://redacted-value}",
+				},
+			},
+		},
+		{
+			name: "invalid JSONPath doesn't panic and redacts whole config as fallback",
+			config: kong.Configuration{
+				"secret": "secret-value",
+				"object": map[string]interface{}{
+					"secretObjectField": "secret-object-field-value",
+				},
+			},
+			sensitiveFieldsMeta: PluginSensitiveFieldsMetadata{
+				JSONPaths: []string{
+					"/not-existing-path",
+				},
+			},
+			expectedSanitizedConfig: kong.Configuration{
+				"secret": "{vault://redacted-value}",
+				"object": "{vault://redacted-value}",
+			},
+		},
+		{
+			name: "whole config to sanitize",
+			config: kong.Configuration{
+				"secret": "secret-value",
+				"object": map[string]interface{}{
+					"secretObjectField": "secret-object-field-value",
+				},
+			},
+			sensitiveFieldsMeta: PluginSensitiveFieldsMetadata{
+				WholeConfigIsSensitive: true,
+			},
+			expectedSanitizedConfig: kong.Configuration{
+				"secret": "{vault://redacted-value}",
+				"object": "{vault://redacted-value}",
+			},
+		},
+		{
+			name: "single empty JSON path - whole config is redacted",
+			config: kong.Configuration{
+				"secret": "secret-value",
+				"object": map[string]interface{}{
+					"secretObjectField": "secret-object-field-value",
+				},
+			},
+			sensitiveFieldsMeta: PluginSensitiveFieldsMetadata{
+				JSONPaths: []string{""},
+			},
+			expectedSanitizedConfig: kong.Configuration{
+				"secret": "{vault://redacted-value}",
+				"object": "{vault://redacted-value}",
+			},
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			p := Plugin{
+				Plugin: kong.Plugin{
+					Config: tc.config,
+				},
+				SensitiveFieldsMeta: tc.sensitiveFieldsMeta,
+			}
+			sanitized := p.SanitizedCopy()
+			assert.Equal(t, tc.expectedSanitizedConfig, sanitized.Config)
 		})
 	}
 }