Repository: openservicemesh/osm
Pull request #5154 (2022-09-26 21:40:30)

ref: move envoy.Proxy to models.Proxy
<!--

Please describe the motivation for this PR and provide enough
information so that others can review it.

-->
**Description**:

Refactor: move `envoy.Proxy` to `models.Proxy`

https://github.com/openservicemesh/osm/issues/5112
 
<!--

Please describe how this change was tested. You could include supporting information
such as logs, snippets, and screenshots.

-->
**Testing done**:

* test suite
<!--

Please mark with X for applicable areas.

-->
**Affected area**:
| Functional Area            |     |
| -------------------------- | --- |
| Other                      | [X] |


Please answer the following questions with yes/no.

1. Does this change contain code from or inspired by another project?
    -   Did you notify the maintainers and provide attribution?

No

2. Is this a breaking change?

No

3. Has documentation corresponding to this change been updated in the [osm-docs](https://github.com/openservicemesh/osm-docs) repo (if applicable)?
No


Patch:
diff --git a/DESIGN.md b/DESIGN.md
index b0a3ec8a..ee87b715 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -306,7 +306,7 @@ type MeshCataloger interface {
 
   // ListServicesForProxy returns a list of services the given Envoy is a member of based on its certificate,
   // which is a cert issued to an Envoy for XDS communication (not Envoy-to-Envoy).
-	ListServicesForProxy(*envoy.Proxy) ([]service.MeshService, error)
+	ListServicesForProxy(*models.Proxy) ([]service.MeshService, error)
 
 	// GetServicesForServiceIdentity returns a list of services corresponding to a service identity
 	GetServicesForServiceIdentity(identity.ServiceIdentityt) ([]service.MeshService, error)
diff --git a/charts/osm/README.md b/charts/osm/README.md
index 346df97a..20863be8 100644
--- a/charts/osm/README.md
+++ b/charts/osm/README.md
@@ -99,6 +99,7 @@ The following table lists the configurable parameters of the osm chart and their
 | osm.featureFlags.enableIngressBackendPolicy | bool | `true` | Enables OSM's IngressBackend policy API. When enabled, OSM will use the IngressBackend API allow ingress traffic to mesh backends |
 | osm.featureFlags.enableMeshRootCertificate | bool | `false` | Enable the MeshRootCertificate to configure the OSM certificate provider |
 | osm.featureFlags.enableRetryPolicy | bool | `false` | Enable Retry Policy for automatic request retries |
+| osm.featureFlags.enableSPIFFE | bool | `false` | Enable adding a SPIFFE ID to certificatess |
 | osm.featureFlags.enableSnapshotCacheMode | bool | `false` | Enables SnapshotCache feature for Envoy xDS server. |
 | osm.featureFlags.enableWASMStats | bool | `true` | Enable extra Envoy statistics generated by a custom WASM extension |
 | osm.fluentBit.enableProxySupport | bool | `false` | Enable proxy support toggle for Fluent Bit |
diff --git a/charts/osm/templates/preset-mesh-root-certificate.yaml b/charts/osm/templates/preset-mesh-root-certificate.yaml
index 41070fdb..12c10c8f 100644
--- a/charts/osm/templates/preset-mesh-root-certificate.yaml
+++ b/charts/osm/templates/preset-mesh-root-certificate.yaml
@@ -7,6 +7,7 @@ metadata:
 data:
   preset-mesh-root-certificate.json: |
     {
+      "spiffeEnabled": {{ .Values.osm.featureFlags.enableSPIFFE | mustToJson }},
       "trustDomain": {{.Values.osm.trustDomain | mustToJson}},
       "provider": {
         {{- if eq (.Values.osm.certificateProvider.kind | lower) "tresor"}}
diff --git a/charts/osm/values.schema.json b/charts/osm/values.schema.json
index 5a443cd4..e5a3e3e4 100644
--- a/charts/osm/values.schema.json
+++ b/charts/osm/values.schema.json
@@ -982,7 +982,8 @@
             "enableEnvoyActiveHealthChecks",
             "enableSnapshotCacheMode",
             "enableRetryPolicy",
-            "enableMeshRootCertificate"
+            "enableMeshRootCertificate",
+            "enableSPIFFE"
           ],
           "properties": {
             "enableWASMStats": {
@@ -1056,6 +1057,15 @@
               "examples": [
                 false
               ]
+            },
+            "enableSPIFFE": {
+              "$id": "#/properties/osm/properties/featureFlags/properties/enableSPIFFE",
+              "type": "boolean",
+              "title": "Enable adding a SPIFFE ID to the certificates",
+              "description": "Enable adding a SPIFFE ID to the certificates, creating a SPIFFE compatible x509 SVID document",
+              "examples": [
+                false
+              ]
             }
           },
           "additionalProperties": false
diff --git a/charts/osm/values.yaml b/charts/osm/values.yaml
index 33ccdb75..af87db25 100644
--- a/charts/osm/values.yaml
+++ b/charts/osm/values.yaml
@@ -487,6 +487,8 @@ osm:
     enableRetryPolicy: false
     # -- Enable the MeshRootCertificate to configure the OSM certificate provider
     enableMeshRootCertificate: false
+    # -- Enable adding a SPIFFE ID to certificatess
+    enableSPIFFE: false
 
   # -- Node tolerations applied to control plane pods.
   # The specified tolerations allow pods to schedule onto nodes with matching taints.
diff --git a/cmd/cli/metrics_disable.go b/cmd/cli/metrics_disable.go
index e662f1fe..9e78803d 100644
--- a/cmd/cli/metrics_disable.go
+++ b/cmd/cli/metrics_disable.go
@@ -52,6 +52,9 @@ func newMetricsDisable(out io.Writer) *cobra.Command {
 
 	f := cmd.Flags()
 	f.StringSliceVar(&disableCmd.namespaces, "namespace", []string{}, "One or more namespaces to disable metrics on")
+	//nolint: errcheck
+	//#nosec G104: Errors unhandled
+	cmd.MarkFlagRequired("namespace")
 
 	return cmd
 }
diff --git a/cmd/cli/metrics_enable.go b/cmd/cli/metrics_enable.go
index 939a1433..d413cbab 100644
--- a/cmd/cli/metrics_enable.go
+++ b/cmd/cli/metrics_enable.go
@@ -56,6 +56,9 @@ func newMetricsEnable(out io.Writer) *cobra.Command {
 	//add mesh name flag
 	f := cmd.Flags()
 	f.StringSliceVar(&enableCmd.namespaces, "namespace", []string{}, "One or more namespaces to enable metrics on")
+	//nolint: errcheck
+	//#nosec G104: Errors unhandled
+	cmd.MarkFlagRequired("namespace")
 
 	return cmd
 }
diff --git a/cmd/osm-bootstrap/crds/config_mesh_root_certificate.yaml b/cmd/osm-bootstrap/crds/config_mesh_root_certificate.yaml
index 0e37fa81..8c7f9b7d 100644
--- a/cmd/osm-bootstrap/crds/config_mesh_root_certificate.yaml
+++ b/cmd/osm-bootstrap/crds/config_mesh_root_certificate.yaml
@@ -61,6 +61,10 @@ spec:
                     - passive
                     - active
                   default: passive
+                spiffeEnabled:
+                  description: Adds a SPIFFE ID to the certificates, creating a SPIFFE compatible x509 SVID document
+                  type: boolean
+                  default: false
                 provider:
                   description: Certificate provider used by the mesh control plane
                   type: object
diff --git a/go.mod b/go.mod
index deca0799..ebec080a 100644
--- a/go.mod
+++ b/go.mod
@@ -79,6 +79,7 @@ require (
 )
 
 require (
+	github.com/spiffe/go-spiffe/v2 v2.1.1
 	golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b
 	k8s.io/kubectl v0.24.2
 )
@@ -358,6 +359,7 @@ require (
 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect
 	github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
 	github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
+	github.com/zeebo/errs v1.2.2 // indirect
 	go.etcd.io/bbolt v1.3.6 // indirect
 	go.opencensus.io v0.23.0 // indirect
 	go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
diff --git a/go.sum b/go.sum
index d4501ff0..2df080ff 100644
--- a/go.sum
+++ b/go.sum
@@ -1549,6 +1549,8 @@ github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5q
 github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
 github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
 github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
+github.com/spiffe/go-spiffe/v2 v2.1.1 h1:RT9kM8MZLZIsPTH+HKQEP5yaAk3yd/VBzlINaRjXs8k=
+github.com/spiffe/go-spiffe/v2 v2.1.1/go.mod h1:5qg6rpqlwIub0JAiF1UK9IMD6BpPTmvG6yfSgDBs5lg=
 github.com/ssgreg/nlreturn/v2 v2.1.0 h1:6/s4Rc49L6Uo6RLjhWZGBpWWjfzk2yrf1nIW8m4wgVA=
 github.com/ssgreg/nlreturn/v2 v2.1.0/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I=
 github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
@@ -1648,6 +1650,8 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
 github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI=
 github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE=
 github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY=
+github.com/zeebo/errs v1.2.2 h1:5NFypMTuSdoySVTqlNs1dEoU21QVamMQJxW/Fii5O7g=
+github.com/zeebo/errs v1.2.2/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
 github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
 github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
 github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
@@ -2230,6 +2234,7 @@ google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEY
 google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
 google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
@@ -2328,6 +2333,7 @@ google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu
 google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w=
 google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
+google.golang.org/grpc/examples v0.0.0-20201130180447-c456688b1860/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -2370,6 +2376,7 @@ gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
+gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
 gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
diff --git a/pkg/apis/config/v1alpha2/meshrootcertificate.go b/pkg/apis/config/v1alpha2/meshrootcertificate.go
index ca2f6ab1..aa8a2996 100644
--- a/pkg/apis/config/v1alpha2/meshrootcertificate.go
+++ b/pkg/apis/config/v1alpha2/meshrootcertificate.go
@@ -36,6 +36,10 @@ type MeshRootCertificateSpec struct {
 
 	// Intent of the MeshRootCertificate resource
 	Intent MeshRootCertificateIntent `json:"intent"`
+
+	// SpiffeEnabled will add a SPIFFE ID to the certificates, creating a SPIFFE compatible x509 SVID document
+	// To use SPIFFE ID for validation and routing, 'enableSPIFFE' must be true in the MeshConfig after the MeshRootCertificate is made 'active'
+	SpiffeEnabled bool `json:"spiffeEnabled"`
 }
 
 // ProviderSpec defines the certificate provider used by the mesh control plane
diff --git a/pkg/catalog/mock_catalog_generated.go b/pkg/catalog/mock_catalog_generated.go
index 4faca898..f801808e 100644
--- a/pkg/catalog/mock_catalog_generated.go
+++ b/pkg/catalog/mock_catalog_generated.go
@@ -12,7 +12,6 @@ import (
 	v1alpha2 "github.com/openservicemesh/osm/pkg/apis/config/v1alpha2"
 	v1alpha1 "github.com/openservicemesh/osm/pkg/apis/policy/v1alpha1"
 	endpoint "github.com/openservicemesh/osm/pkg/endpoint"
-	envoy "github.com/openservicemesh/osm/pkg/envoy"
 	identity "github.com/openservicemesh/osm/pkg/identity"
 	models "github.com/openservicemesh/osm/pkg/models"
 	service "github.com/openservicemesh/osm/pkg/service"
@@ -218,7 +217,7 @@ func (mr *MockMeshCatalogerMockRecorder) GetOutboundMeshTrafficPolicy(arg0 inter
 }
 
 // GetProxyStatsHeaders mocks base method.
-func (m *MockMeshCataloger) GetProxyStatsHeaders(arg0 *envoy.Proxy) (map[string]string, error) {
+func (m *MockMeshCataloger) GetProxyStatsHeaders(arg0 *models.Proxy) (map[string]string, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "GetProxyStatsHeaders", arg0)
 	ret0, _ := ret[0].(map[string]string)
@@ -345,7 +344,7 @@ func (mr *MockMeshCatalogerMockRecorder) GetUpstreamTrafficSettingByService(arg0
 }
 
 // IsMetricsEnabled mocks base method.
-func (m *MockMeshCataloger) IsMetricsEnabled(arg0 *envoy.Proxy) (bool, error) {
+func (m *MockMeshCataloger) IsMetricsEnabled(arg0 *models.Proxy) (bool, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "IsMetricsEnabled", arg0)
 	ret0, _ := ret[0].(bool)
@@ -644,7 +643,7 @@ func (mr *MockMeshCatalogerMockRecorder) ListServices() *gomock.Call {
 }
 
 // ListServicesForProxy mocks base method.
-func (m *MockMeshCataloger) ListServicesForProxy(arg0 *envoy.Proxy) ([]service.MeshService, error) {
+func (m *MockMeshCataloger) ListServicesForProxy(arg0 *models.Proxy) ([]service.MeshService, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "ListServicesForProxy", arg0)
 	ret0, _ := ret[0].([]service.MeshService)
@@ -789,7 +788,7 @@ func (mr *MockMeshCatalogerMockRecorder) UpdateUpstreamTrafficSettingStatus(arg0
 }
 
 // VerifyProxy mocks base method.
-func (m *MockMeshCataloger) VerifyProxy(arg0 *envoy.Proxy) error {
+func (m *MockMeshCataloger) VerifyProxy(arg0 *models.Proxy) error {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "VerifyProxy", arg0)
 	ret0, _ := ret[0].(error)
diff --git a/pkg/certificate/manager.go b/pkg/certificate/manager.go
index 11b286cb..a36abf6d 100644
--- a/pkg/certificate/manager.go
+++ b/pkg/certificate/manager.go
@@ -130,7 +130,7 @@ func (m *Manager) handleMRCEvent(mrcClient MRCClient, event MRCEvent) error {
 			return err
 		}
 
-		c := &issuer{Issuer: client, ID: mrc.Name, CertificateAuthority: ca, TrustDomain: mrc.Spec.TrustDomain}
+		c := &issuer{Issuer: client, ID: mrc.Name, CertificateAuthority: ca, TrustDomain: mrc.Spec.TrustDomain, SpiffeEnabled: mrc.Spec.SpiffeEnabled}
 		switch {
 		case mrc.Status.State == constants.MRCStateActive:
 			m.mu.Lock()
@@ -303,6 +303,7 @@ func (m *Manager) issueCertificate(options IssueOptions) (*Certificate, error) {
 
 	options.ValidityDuration = m.getValidityDurationForCertType(options.certType)
 	options.trustDomain = signingIssuer.TrustDomain
+	options.spiffeEnabled = signingIssuer.SpiffeEnabled
 	newCert, err := signingIssuer.IssueCertificate(options)
 	if err != nil {
 		return nil, err
diff --git a/pkg/certificate/options.go b/pkg/certificate/options.go
index 4156d24b..92f452ca 100644
--- a/pkg/certificate/options.go
+++ b/pkg/certificate/options.go
@@ -2,6 +2,8 @@ package certificate
 
 import (
 	"fmt"
+	"net/url"
+	"strings"
 	"time"
 
 	"github.com/openservicemesh/osm/pkg/identity"
@@ -17,6 +19,7 @@ type IssueOptions struct {
 	commonNamePrefix string
 	certType         certType
 	ValidityDuration time.Duration
+	spiffeEnabled    bool
 }
 
 func (o IssueOptions) cacheKey() string {
@@ -38,6 +41,23 @@ func (o IssueOptions) CommonName() CommonName {
 	return CommonName(fmt.Sprintf("%s.%s", o.commonNamePrefix, o.trustDomain))
 }
 
+// URISAN generates a URL in the Spiffe format spiffe://trustdomain/sa/svc
+func (o IssueOptions) URISAN() *url.URL {
+	if !o.spiffeEnabled {
+		return &url.URL{}
+	}
+
+	// if the trust domain is already appended remove it.
+	path := strings.TrimSuffix(o.commonNamePrefix, "."+o.trustDomain)
+	path = strings.ReplaceAll(path, ".", "/")
+
+	return &url.URL{
+		Scheme: "spiffe",
+		Path:   path,
+		Host:   o.trustDomain,
+	}
+}
+
 func withCommonNamePrefix(prefix string) IssueOption {
 	return func(opts *IssueOptions) {
 		opts.commonNamePrefix = prefix
@@ -56,6 +76,12 @@ func withCertType(certType certType) IssueOption {
 	}
 }
 
+func withSpiffeEnabled() IssueOption {
+	return func(opts *IssueOptions) {
+		opts.spiffeEnabled = true
+	}
+}
+
 // ForServiceIdentity creates a service certificate with the given prefix for the common name
 // The trust domain will be appended to the Common Name
 func ForServiceIdentity(identity identity.ServiceIdentity) IssueOption {
@@ -111,3 +137,14 @@ func NewCertOptionsWithFullName(fullCommonName string, validity time.Duration) I
 	}
 	return *opts
 }
+
+// NewCertOptionsWithTrustDomain creates the IssueOptions for the issuing a certificate with a given full common name
+func NewCertOptionsWithTrustDomain(prefix string, trustDomain string, validity time.Duration, spiffeEnabled bool) IssueOptions {
+	opts := &IssueOptions{
+		ValidityDuration: validity,
+		trustDomain:      trustDomain,
+		commonNamePrefix: prefix,
+		spiffeEnabled:    spiffeEnabled,
+	}
+	return *opts
+}
diff --git a/pkg/certificate/providers/certmanager/certificate_manager.go b/pkg/certificate/providers/certmanager/certificate_manager.go
index c5ce8c3c..b092f01f 100644
--- a/pkg/certificate/providers/certmanager/certificate_manager.go
+++ b/pkg/certificate/providers/certmanager/certificate_manager.go
@@ -8,6 +8,7 @@ import (
 	"crypto/x509/pkix"
 	"errors"
 	"fmt"
+	"net/url"
 	"time"
 
 	cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
@@ -110,6 +111,11 @@ func (cm *CertManager) IssueCertificate(options certificate.IssueOptions) (*cert
 		DNSNames: []string{options.CommonName().String()},
 	}
 
+	if options.URISAN().String() != "" {
+		log.Trace().Str("cn", options.CommonName().String()).Msg("Generating Certificate with Uri SAN")
+		csr.URIs = []*url.URL{options.URISAN()}
+	}
+
 	csrDER, err := x509.CreateCertificateRequest(rand.Reader, csr, certPrivKey)
 	if err != nil {
 		// TODO(#3962): metric might not be scraped before process restart resulting from this error
diff --git a/pkg/certificate/providers/tresor/certificate_manager.go b/pkg/certificate/providers/tresor/certificate_manager.go
index 58961a11..d3daacc9 100644
--- a/pkg/certificate/providers/tresor/certificate_manager.go
+++ b/pkg/certificate/providers/tresor/certificate_manager.go
@@ -6,6 +6,7 @@ import (
 	"crypto/x509"
 	"crypto/x509/pkix"
 	"fmt"
+	"net/url"
 	"time"
 
 	"github.com/openservicemesh/osm/pkg/certificate"
@@ -75,6 +76,11 @@ func (cm *CertManager) IssueCertificate(opts certificate.IssueOptions) (*certifi
 		BasicConstraintsValid: true,
 	}
 
+	if opts.URISAN().String() != "" {
+		log.Trace().Str("cn", opts.CommonName().String()).Msg("Generating Certificate with Uri SAN")
+		template.URIs = []*url.URL{opts.URISAN()}
+	}
+
 	x509Root, err := certificate.DecodePEMCertificate(cm.ca.GetCertificateChain())
 	if err != nil {
 		// TODO(#3962): metric might not be scraped before process restart resulting from this error
diff --git a/pkg/certificate/providers/vault/certificate_manager.go b/pkg/certificate/providers/vault/certificate_manager.go
index 9187f3df..0bddc58a 100644
--- a/pkg/certificate/providers/vault/certificate_manager.go
+++ b/pkg/certificate/providers/vault/certificate_manager.go
@@ -16,13 +16,14 @@ var log = logger.New("vault")
 
 const (
 	// The string value of the JSON key containing the certificate's Serial Number.
-	// See: https://www.vaultproject.io/api-docs/secret/pki#sample-response-8
+	// See: https://www.vaultproject.io/api-docs/secret/pki#sign-certificate
 	serialNumberField = "serial_number"
 	certificateField  = "certificate"
 	privateKeyField   = "private_key"
 	issuingCAField    = "issuing_ca"
 	commonNameField   = "common_name"
 	ttlField          = "ttl"
+	uriSans           = "uri_sans"
 )
 
 // New constructs a new certificate client using Vault's cert-manager
@@ -55,7 +56,7 @@ func New(vaultAddr, token, role string) (*CertManager, error) {
 
 // IssueCertificate requests a new signed certificate from the configured Vault issuer.
 func (cm *CertManager) IssueCertificate(options certificate.IssueOptions) (*certificate.Certificate, error) {
-	secret, err := cm.client.Logical().Write(getIssueURL(cm.role), getIssuanceData(options.CommonName(), options.ValidityDuration))
+	secret, err := cm.client.Logical().Write(getIssueURL(cm.role), getIssuanceData(options))
 	if err != nil {
 		// TODO(#3962): metric might not be scraped before process restart resulting from this error
 		log.Error().Err(err).Str(errcode.Kind, errcode.GetErrCodeWithMetric(errcode.ErrIssuingCert)).
diff --git a/pkg/certificate/providers/vault/tools.go b/pkg/certificate/providers/vault/tools.go
index 775e2a01..680b3cca 100644
--- a/pkg/certificate/providers/vault/tools.go
+++ b/pkg/certificate/providers/vault/tools.go
@@ -15,9 +15,18 @@ func getIssueURL(role string) string {
 	return fmt.Sprintf("pki/issue/%+v", role)
 }
 
-func getIssuanceData(cn certificate.CommonName, validityPeriod time.Duration) map[string]interface{} {
-	return map[string]interface{}{
-		commonNameField: cn.String(),
-		ttlField:        getDurationInMinutes(validityPeriod),
+func getIssuanceData(options certificate.IssueOptions) map[string]interface{} {
+	issuanceData := map[string]interface{}{
+		commonNameField: options.CommonName().String(),
+		ttlField:        getDurationInMinutes(options.ValidityDuration),
 	}
+
+	if options.URISAN().String() != "" {
+		log.Trace().Str("cn", options.CommonName().String()).Msg("Generating Certificate with Uri SAN")
+
+		// Comma delimited list; For SPIFFE compatibility it should only be one.
+		issuanceData[uriSans] = options.URISAN().String()
+	}
+
+	return issuanceData
 }
diff --git a/pkg/certificate/types.go b/pkg/certificate/types.go
index 72e4ab6d..f92112f0 100644
--- a/pkg/certificate/types.go
+++ b/pkg/certificate/types.go
@@ -94,8 +94,9 @@ type Issuer interface {
 
 type issuer struct {
 	Issuer
-	ID          string
-	TrustDomain string
+	ID            string
+	TrustDomain   string
+	SpiffeEnabled bool
 	// memoized once the first certificate is issued
 	CertificateAuthority pem.RootCertificate
 }
diff --git a/pkg/compute/kube/client.go b/pkg/compute/kube/client.go
index 69a0a7ee..e0105cdf 100644
--- a/pkg/compute/kube/client.go
+++ b/pkg/compute/kube/client.go
@@ -17,7 +17,6 @@ import (
 	policyv1alpha1 "github.com/openservicemesh/osm/pkg/apis/policy/v1alpha1"
 
 	"github.com/openservicemesh/osm/pkg/constants"
-	"github.com/openservicemesh/osm/pkg/envoy"
 
 	"github.com/openservicemesh/osm/pkg/endpoint"
 	"github.com/openservicemesh/osm/pkg/identity"
@@ -136,7 +135,7 @@ func (c *client) GetServicesForServiceIdentity(svcIdentity identity.ServiceIdent
 }
 
 // ListServicesForProxy maps an Envoy instance to a number of Kubernetes services.
-func (c *client) ListServicesForProxy(p *envoy.Proxy) ([]service.MeshService, error) {
+func (c *client) ListServicesForProxy(p *models.Proxy) ([]service.MeshService, error) {
 	pod, err := c.kubeController.GetPodForProxy(p)
 	if err != nil {
 		return nil, err
@@ -248,7 +247,7 @@ func (c *client) ListServices() []service.MeshService {
 }
 
 // IsMetricsEnabled checks if prometheus metrics scraping are enabled on this pod.
-func (c *client) IsMetricsEnabled(proxy *envoy.Proxy) (bool, error) {
+func (c *client) IsMetricsEnabled(proxy *models.Proxy) (bool, error) {
 	pod, err := c.kubeController.GetPodForProxy(proxy)
 	if err != nil {
 		return false, err
@@ -410,7 +409,7 @@ func DetectIngressBackendConflicts(x policyv1alpha1.IngressBackend, y policyv1al
 }
 
 // GetProxyStatsHeaders returns stats headers for the given proxy.
-func (c *client) GetProxyStatsHeaders(p *envoy.Proxy) (map[string]string, error) {
+func (c *client) GetProxyStatsHeaders(p *models.Proxy) (map[string]string, error) {
 	pod, err := c.kubeController.GetPodForProxy(p)
 	if err != nil {
 		log.Warn().Str("proxy", p.String()).Msg("Could not find pod for connecting proxy. No metadata was recorded.")
@@ -444,7 +443,7 @@ func (c *client) GetProxyStatsHeaders(p *envoy.Proxy) (map[string]string, error)
 }
 
 // VerifyProxy attempts to lookup a pod that matches the given proxy instance by service identity, namespace, and UUID.
-func (c *client) VerifyProxy(proxy *envoy.Proxy) error {
+func (c *client) VerifyProxy(proxy *models.Proxy) error {
 	_, err := c.kubeController.GetPodForProxy(proxy)
 	return err
 }
diff --git a/pkg/compute/mock_compute_client_generated.go b/pkg/compute/mock_compute_client_generated.go
index 406e45ca..c9ebcb69 100644
--- a/pkg/compute/mock_compute_client_generated.go
+++ b/pkg/compute/mock_compute_client_generated.go
@@ -12,7 +12,6 @@ import (
 	v1alpha2 "github.com/openservicemesh/osm/pkg/apis/config/v1alpha2"
 	v1alpha1 "github.com/openservicemesh/osm/pkg/apis/policy/v1alpha1"
 	endpoint "github.com/openservicemesh/osm/pkg/endpoint"
-	envoy "github.com/openservicemesh/osm/pkg/envoy"
 	identity "github.com/openservicemesh/osm/pkg/identity"
 	models "github.com/openservicemesh/osm/pkg/models"
 	service "github.com/openservicemesh/osm/pkg/service"
@@ -145,7 +144,7 @@ func (mr *MockInterfaceMockRecorder) GetOSMNamespace() *gomock.Call {
 }
 
 // GetProxyStatsHeaders mocks base method.
-func (m *MockInterface) GetProxyStatsHeaders(arg0 *envoy.Proxy) (map[string]string, error) {
+func (m *MockInterface) GetProxyStatsHeaders(arg0 *models.Proxy) (map[string]string, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "GetProxyStatsHeaders", arg0)
 	ret0, _ := ret[0].(map[string]string)
@@ -272,7 +271,7 @@ func (mr *MockInterfaceMockRecorder) GetUpstreamTrafficSettingByService(arg0 int
 }
 
 // IsMetricsEnabled mocks base method.
-func (m *MockInterface) IsMetricsEnabled(arg0 *envoy.Proxy) (bool, error) {
+func (m *MockInterface) IsMetricsEnabled(arg0 *models.Proxy) (bool, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "IsMetricsEnabled", arg0)
 	ret0, _ := ret[0].(bool)
@@ -486,7 +485,7 @@ func (mr *MockInterfaceMockRecorder) ListServices() *gomock.Call {
 }
 
 // ListServicesForProxy mocks base method.
-func (m *MockInterface) ListServicesForProxy(arg0 *envoy.Proxy) ([]service.MeshService, error) {
+func (m *MockInterface) ListServicesForProxy(arg0 *models.Proxy) ([]service.MeshService, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "ListServicesForProxy", arg0)
 	ret0, _ := ret[0].([]service.MeshService)
@@ -631,7 +630,7 @@ func (mr *MockInterfaceMockRecorder) UpdateUpstreamTrafficSettingStatus(arg0 int
 }
 
 // VerifyProxy mocks base method.
-func (m *MockInterface) VerifyProxy(arg0 *envoy.Proxy) error {
+func (m *MockInterface) VerifyProxy(arg0 *models.Proxy) error {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "VerifyProxy", arg0)
 	ret0, _ := ret[0].(error)
diff --git a/pkg/compute/types.go b/pkg/compute/types.go
index 87686484..7779c197 100644
--- a/pkg/compute/types.go
+++ b/pkg/compute/types.go
@@ -8,7 +8,6 @@ import (
 	policyv1alpha1 "github.com/openservicemesh/osm/pkg/apis/policy/v1alpha1"
 
 	"github.com/openservicemesh/osm/pkg/endpoint"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/identity"
 	"github.com/openservicemesh/osm/pkg/k8s"
 	"github.com/openservicemesh/osm/pkg/models"
@@ -50,12 +49,12 @@ type Interface interface {
 	// the scope of the provider
 	GetResolvableEndpointsForService(service.MeshService) []endpoint.Endpoint
 
-	IsMetricsEnabled(*envoy.Proxy) (bool, error)
+	IsMetricsEnabled(*models.Proxy) (bool, error)
 
 	GetHostnamesForService(svc service.MeshService, localNamespace bool) []string
 
 	// ListServicesForProxy gets the services that map to the given proxy.
-	ListServicesForProxy(p *envoy.Proxy) ([]service.MeshService, error)
+	ListServicesForProxy(p *models.Proxy) ([]service.MeshService, error)
 
 	// ListEgressPoliciesForServiceAccount lists the Egress policies for the given source identity based on service accounts
 	ListEgressPoliciesForServiceAccount(sa identity.K8sServiceAccount) []*policyv1alpha1.Egress
@@ -75,10 +74,10 @@ type Interface interface {
 	// GetUpstreamTrafficSettingByHost returns the UpstreamTrafficSetting resource that matches the host
 	GetUpstreamTrafficSettingByHost(host string) *policyv1alpha1.UpstreamTrafficSetting
 
-	GetProxyStatsHeaders(p *envoy.Proxy) (map[string]string, error)
+	GetProxyStatsHeaders(p *models.Proxy) (map[string]string, error)
 
 	// VerifyProxy attempts to lookup a pod that matches the given proxy instance by service identity, namespace, and UUID
-	VerifyProxy(proxy *envoy.Proxy) error
+	VerifyProxy(proxy *models.Proxy) error
 
 	// ListNamespaces returns the namespaces monitored by the mesh
 	ListNamespaces() ([]string, error)
diff --git a/pkg/debugger/certificate.go b/pkg/debugger/certificate.go
index 7ac9b481..6202968b 100644
--- a/pkg/debugger/certificate.go
+++ b/pkg/debugger/certificate.go
@@ -51,6 +51,7 @@ func (ds DebugConfig) getCertHandler() http.Handler {
 			_, _ = fmt.Fprintf(w, "\t x509.BasicConstraintsValid: %+v\n", x509.BasicConstraintsValid)
 			_, _ = fmt.Fprintf(w, "\t x509.IsCA: %+v\n", x509.IsCA)
 			_, _ = fmt.Fprintf(w, "\t x509.DNSNames: %+v\n", x509.DNSNames)
+			_, _ = fmt.Fprintf(w, "\t x509.URISANs: %+v\n", x509.URIs)
 
 			_, _ = fmt.Fprintf(w, "\t Cert struct expiration vs. x509.NotAfter: %+v\n", x509.NotAfter.Sub(cert.GetExpiration()))
 
diff --git a/pkg/debugger/proxy.go b/pkg/debugger/proxy.go
index 378a26f4..bd4ef2f4 100644
--- a/pkg/debugger/proxy.go
+++ b/pkg/debugger/proxy.go
@@ -7,7 +7,7 @@ import (
 	"strconv"
 	"time"
 
-	envoy "github.com/openservicemesh/osm/pkg/envoy"
+	"github.com/openservicemesh/osm/pkg/models"
 )
 
 const (
@@ -45,7 +45,7 @@ func (ds DebugConfig) printProxies(w http.ResponseWriter) {
 	// This function is needed to convert the list of connected proxies to
 	// the type (map) required by the printProxies function.
 	proxyMap := ds.proxyRegistry.ListConnectedProxies()
-	proxies := make([]*envoy.Proxy, 0, len(proxyMap))
+	proxies := make([]*models.Proxy, 0, len(proxyMap))
 	for _, proxy := range proxyMap {
 		proxies = append(proxies, proxy)
 	}
diff --git a/pkg/envoy/generator/cds.go b/pkg/envoy/generator/cds.go
index d3354f1b..b2de3ca1 100644
--- a/pkg/envoy/generator/cds.go
+++ b/pkg/envoy/generator/cds.go
@@ -8,11 +8,12 @@ import (
 	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/envoy/generator/cds"
 	"github.com/openservicemesh/osm/pkg/errcode"
+	"github.com/openservicemesh/osm/pkg/models"
 	"github.com/openservicemesh/osm/pkg/utils"
 )
 
 // generateRDS creates a new Cluster Discovery Response.
-func (g *EnvoyConfigGenerator) generateCDS(ctx context.Context, proxy *envoy.Proxy) ([]types.Resource, error) {
+func (g *EnvoyConfigGenerator) generateCDS(ctx context.Context, proxy *models.Proxy) ([]types.Resource, error) {
 	meshConfig := g.catalog.GetMeshConfig()
 	cb := cds.NewClusterBuilder().SetProxyIdentity(proxy.Identity).SetSidecarSpec(meshConfig.Spec.Sidecar).SetEgressEnabled(meshConfig.Spec.Traffic.EnableEgress)
 
diff --git a/pkg/envoy/generator/eds.go b/pkg/envoy/generator/eds.go
index 2cd75fd1..cebd10f5 100644
--- a/pkg/envoy/generator/eds.go
+++ b/pkg/envoy/generator/eds.go
@@ -6,13 +6,13 @@ import (
 	"github.com/envoyproxy/go-control-plane/pkg/cache/types"
 
 	"github.com/openservicemesh/osm/pkg/endpoint"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/envoy/generator/eds"
+	"github.com/openservicemesh/osm/pkg/models"
 	"github.com/openservicemesh/osm/pkg/service"
 )
 
 // generateCDS creates a new Endpoint Discovery Response.
-func (g *EnvoyConfigGenerator) generateEDS(ctx context.Context, proxy *envoy.Proxy) ([]types.Resource, error) {
+func (g *EnvoyConfigGenerator) generateEDS(ctx context.Context, proxy *models.Proxy) ([]types.Resource, error) {
 	meshSvcEndpoints := make(map[service.MeshService][]endpoint.Endpoint)
 	builder := eds.NewEndpointsBuilder()
 
diff --git a/pkg/envoy/generator/generator.go b/pkg/envoy/generator/generator.go
index 3e52f4f6..1a1f6752 100644
--- a/pkg/envoy/generator/generator.go
+++ b/pkg/envoy/generator/generator.go
@@ -12,6 +12,7 @@ import (
 	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/errcode"
 	"github.com/openservicemesh/osm/pkg/logger"
+	"github.com/openservicemesh/osm/pkg/models"
 )
 
 var (
@@ -21,7 +22,7 @@ var (
 // EnvoyConfigGenerator is used to generate all xDS response types per proxy.
 type EnvoyConfigGenerator struct {
 	catalog        catalog.MeshCataloger
-	generators     map[envoy.TypeURI]func(context.Context, *envoy.Proxy) ([]types.Resource, error)
+	generators     map[envoy.TypeURI]func(context.Context, *models.Proxy) ([]types.Resource, error)
 	certManager    *certificate.Manager
 	xdsMapLogMutex sync.Mutex
 	xdsLog         map[string]map[envoy.TypeURI][]time.Time
@@ -34,7 +35,7 @@ func NewEnvoyConfigGenerator(catalog catalog.MeshCataloger, certManager *certifi
 		certManager: certManager,
 		xdsLog:      make(map[string]map[envoy.TypeURI][]time.Time),
 	}
-	g.generators = map[envoy.TypeURI]func(context.Context, *envoy.Proxy) ([]types.Resource, error){
+	g.generators = map[envoy.TypeURI]func(context.Context, *models.Proxy) ([]types.Resource, error){
 		envoy.TypeCDS: g.generateCDS,
 		envoy.TypeEDS: g.generateEDS,
 		envoy.TypeLDS: g.generateLDS,
@@ -45,7 +46,7 @@ func NewEnvoyConfigGenerator(catalog catalog.MeshCataloger, certManager *certifi
 }
 
 // GenerateConfig generates and returns the resources for the given proxy.
-func (g *EnvoyConfigGenerator) GenerateConfig(ctx context.Context, proxy *envoy.Proxy) (map[string][]types.Resource, error) {
+func (g *EnvoyConfigGenerator) GenerateConfig(ctx context.Context, proxy *models.Proxy) (map[string][]types.Resource, error) {
 	cacheResourceMap := map[string][]types.Resource{}
 	for typeURI, handler := range g.generators {
 		log.Trace().Str("proxy", proxy.String()).Msgf("Getting resources for type %s", typeURI.Short())
diff --git a/pkg/envoy/generator/lds.go b/pkg/envoy/generator/lds.go
index 003a2f25..22be304e 100644
--- a/pkg/envoy/generator/lds.go
+++ b/pkg/envoy/generator/lds.go
@@ -8,9 +8,9 @@ import (
 	"github.com/envoyproxy/go-control-plane/pkg/cache/types"
 
 	"github.com/openservicemesh/osm/pkg/constants"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/envoy/generator/lds"
 	"github.com/openservicemesh/osm/pkg/errcode"
+	"github.com/openservicemesh/osm/pkg/models"
 	"github.com/openservicemesh/osm/pkg/utils"
 )
 
@@ -19,7 +19,7 @@ import (
 // 1. Inbound listener to handle incoming traffic
 // 2. Outbound listener to handle outgoing traffic
 // 3. Prometheus listener for metrics
-func (g *EnvoyConfigGenerator) generateLDS(ctx context.Context, proxy *envoy.Proxy) ([]types.Resource, error) {
+func (g *EnvoyConfigGenerator) generateLDS(ctx context.Context, proxy *models.Proxy) ([]types.Resource, error) {
 	var ldsResources []types.Resource
 
 	var statsHeaders map[string]string
diff --git a/pkg/envoy/generator/profile.go b/pkg/envoy/generator/profile.go
index 0f8762ad..86eb486d 100644
--- a/pkg/envoy/generator/profile.go
+++ b/pkg/envoy/generator/profile.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/metricsstore"
+	"github.com/openservicemesh/osm/pkg/models"
 )
 
 const (
@@ -52,7 +53,7 @@ func (g *EnvoyConfigGenerator) trackXDSLog(proxyUUID string, typeURL envoy.TypeU
 	g.xdsLog[proxyUUID][typeURL] = timeSlice
 }
 
-func xdsPathTimeTrack(startedAt time.Time, typeURI envoy.TypeURI, proxy *envoy.Proxy, success bool) {
+func xdsPathTimeTrack(startedAt time.Time, typeURI envoy.TypeURI, proxy *models.Proxy, success bool) {
 	elapsed := time.Since(startedAt)
 
 	log.Debug().Str("proxy", proxy.String()).Msgf("Time taken proxy to generate response for request with typeURI=%s: %s", typeURI, elapsed)
diff --git a/pkg/envoy/generator/rds.go b/pkg/envoy/generator/rds.go
index e815e39f..dded38b4 100644
--- a/pkg/envoy/generator/rds.go
+++ b/pkg/envoy/generator/rds.go
@@ -5,14 +5,14 @@ import (
 
 	"github.com/envoyproxy/go-control-plane/pkg/cache/types"
 
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/envoy/generator/rds"
 	"github.com/openservicemesh/osm/pkg/errcode"
+	"github.com/openservicemesh/osm/pkg/models"
 	"github.com/openservicemesh/osm/pkg/trafficpolicy"
 )
 
 // generateRDS creates a new Route Discovery Response.
-func (g *EnvoyConfigGenerator) generateRDS(ctx context.Context, proxy *envoy.Proxy) ([]types.Resource, error) {
+func (g *EnvoyConfigGenerator) generateRDS(ctx context.Context, proxy *models.Proxy) ([]types.Resource, error) {
 	proxyServices, err := g.catalog.ListServicesForProxy(proxy)
 	if err != nil {
 		log.Error().Err(err).Str(errcode.Kind, errcode.GetErrCodeWithMetric(errcode.ErrFetchingServiceList)).
diff --git a/pkg/envoy/generator/rds/builder.go b/pkg/envoy/generator/rds/builder.go
index 96a57da8..5825157d 100644
--- a/pkg/envoy/generator/rds/builder.go
+++ b/pkg/envoy/generator/rds/builder.go
@@ -5,7 +5,7 @@ import (
 	xds_route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
 	"github.com/envoyproxy/go-control-plane/pkg/cache/types"
 
-	"github.com/openservicemesh/osm/pkg/envoy"
+	"github.com/openservicemesh/osm/pkg/models"
 	"github.com/openservicemesh/osm/pkg/trafficpolicy"
 )
 
@@ -28,7 +28,7 @@ type routesBuilder struct {
 	outboundPortSpecificRouteConfigs map[int][]*trafficpolicy.OutboundTrafficPolicy
 	ingressTrafficPolicies           []*trafficpolicy.InboundTrafficPolicy
 	egressPortSpecificRouteConfigs   map[int][]*trafficpolicy.EgressHTTPRouteConfig
-	proxy                            *envoy.Proxy
+	proxy                            *models.Proxy
 	statsHeaders                     map[string]string
 	trustDomain                      string
 }
@@ -57,7 +57,7 @@ func (b *routesBuilder) EgressPortSpecificRouteConfigs(egressPortSpecificRouteCo
 	return b
 }
 
-func (b *routesBuilder) Proxy(proxy *envoy.Proxy) *routesBuilder {
+func (b *routesBuilder) Proxy(proxy *models.Proxy) *routesBuilder {
 	b.proxy = proxy
 	return b
 }
diff --git a/pkg/envoy/generator/sds.go b/pkg/envoy/generator/sds.go
index ef648ed1..cdbf0b41 100644
--- a/pkg/envoy/generator/sds.go
+++ b/pkg/envoy/generator/sds.go
@@ -6,14 +6,14 @@ import (
 	"github.com/envoyproxy/go-control-plane/pkg/cache/types"
 
 	"github.com/openservicemesh/osm/pkg/certificate"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/envoy/generator/sds"
 	"github.com/openservicemesh/osm/pkg/identity"
+	"github.com/openservicemesh/osm/pkg/models"
 	"github.com/openservicemesh/osm/pkg/service"
 )
 
 // NewResponse creates a new Secrets Discovery Response.
-func (g *EnvoyConfigGenerator) generateSDS(ctx context.Context, proxy *envoy.Proxy) ([]types.Resource, error) {
+func (g *EnvoyConfigGenerator) generateSDS(ctx context.Context, proxy *models.Proxy) ([]types.Resource, error) {
 	log.Info().Str("proxy", proxy.String()).Msg("Composing SDS Discovery Response")
 
 	// sdsBuilder: builds the Secret Discovery Response
diff --git a/pkg/envoy/generator/sds/builder.go b/pkg/envoy/generator/sds/builder.go
index f9cb4853..5bbbe99e 100644
--- a/pkg/envoy/generator/sds/builder.go
+++ b/pkg/envoy/generator/sds/builder.go
@@ -6,15 +6,15 @@ import (
 	xds_matcher "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
 
 	"github.com/openservicemesh/osm/pkg/certificate"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/envoy/secrets"
 	"github.com/openservicemesh/osm/pkg/identity"
+	"github.com/openservicemesh/osm/pkg/models"
 	"github.com/openservicemesh/osm/pkg/service"
 )
 
 // SecretsBuilder is responsible for constructing the SDS response.
 type SecretsBuilder struct {
-	proxy *envoy.Proxy
+	proxy *models.Proxy
 
 	// Service certificate for this proxy
 	serviceCert *certificate.Certificate
@@ -31,7 +31,7 @@ func NewBuilder() *SecretsBuilder { //nolint: revive // unexported-return
 }
 
 // SetProxy sets the proxy these secrets are destined for.
-func (b *SecretsBuilder) SetProxy(proxy *envoy.Proxy) *SecretsBuilder {
+func (b *SecretsBuilder) SetProxy(proxy *models.Proxy) *SecretsBuilder {
 	b.proxy = proxy
 	return b
 }
diff --git a/pkg/envoy/registry/registry.go b/pkg/envoy/registry/registry.go
index de3c8fdc..897e82ce 100644
--- a/pkg/envoy/registry/registry.go
+++ b/pkg/envoy/registry/registry.go
@@ -1,18 +1,16 @@
 package registry
 
-import (
-	"github.com/openservicemesh/osm/pkg/envoy"
-)
+import "github.com/openservicemesh/osm/pkg/models"
 
 // NewProxyRegistry initializes a new empty *ProxyRegistry.
 func NewProxyRegistry() *ProxyRegistry {
 	return &ProxyRegistry{
-		connectedProxies: make(map[int64]*envoy.Proxy),
+		connectedProxies: make(map[int64]*models.Proxy),
 	}
 }
 
 // RegisterProxy registers a newly connected proxy.
-func (pr *ProxyRegistry) RegisterProxy(proxy *envoy.Proxy) {
+func (pr *ProxyRegistry) RegisterProxy(proxy *models.Proxy) {
 	pr.mu.Lock()
 	defer pr.mu.Unlock()
 	pr.connectedProxies[proxy.GetConnectionID()] = proxy
@@ -20,7 +18,7 @@ func (pr *ProxyRegistry) RegisterProxy(proxy *envoy.Proxy) {
 }
 
 // GetConnectedProxy loads a connected proxy from the registry.
-func (pr *ProxyRegistry) GetConnectedProxy(connectionID int64) *envoy.Proxy {
+func (pr *ProxyRegistry) GetConnectedProxy(connectionID int64) *models.Proxy {
 	pr.mu.Lock()
 	defer pr.mu.Unlock()
 	return pr.connectedProxies[connectionID]
@@ -43,11 +41,11 @@ func (pr *ProxyRegistry) GetConnectedProxyCount() int {
 }
 
 // ListConnectedProxies lists the Envoy proxies already connected and the time they first connected.
-func (pr *ProxyRegistry) ListConnectedProxies() map[string]*envoy.Proxy {
+func (pr *ProxyRegistry) ListConnectedProxies() map[string]*models.Proxy {
 	pr.mu.Lock()
 	defer pr.mu.Unlock()
 
-	proxies := make(map[string]*envoy.Proxy, len(pr.connectedProxies))
+	proxies := make(map[string]*models.Proxy, len(pr.connectedProxies))
 	for _, p := range pr.connectedProxies {
 		// A proxy could connect twice quickly and not register the disconnect, so we return the proxy with the higher connection ID.
 		if prior := proxies[p.UUID.String()]; prior == nil || prior.GetConnectionID() < p.GetConnectionID() {
diff --git a/pkg/envoy/registry/types.go b/pkg/envoy/registry/types.go
index 4756e016..e46cf434 100644
--- a/pkg/envoy/registry/types.go
+++ b/pkg/envoy/registry/types.go
@@ -3,8 +3,8 @@ package registry
 import (
 	"sync"
 
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/logger"
+	"github.com/openservicemesh/osm/pkg/models"
 )
 
 var log = logger.New("proxy-registry")
@@ -13,5 +13,5 @@ var log = logger.New("proxy-registry")
 // from the control plane.
 type ProxyRegistry struct {
 	mu               sync.Mutex
-	connectedProxies map[int64]*envoy.Proxy
+	connectedProxies map[int64]*models.Proxy
 }
diff --git a/pkg/envoy/server/server.go b/pkg/envoy/server/server.go
index 3e4dbfbf..0de44dca 100644
--- a/pkg/envoy/server/server.go
+++ b/pkg/envoy/server/server.go
@@ -11,8 +11,8 @@ import (
 	serverv3 "github.com/envoyproxy/go-control-plane/pkg/server/v3"
 
 	"github.com/openservicemesh/osm/pkg/certificate"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/logger"
+	"github.com/openservicemesh/osm/pkg/models"
 )
 
 const (
@@ -60,7 +60,7 @@ func (s *Server) Start(ctx context.Context, certManager *certificate.Manager, ca
 // UpdateProxy stores a group of resources as a new Snapshot with a new version in the cache.
 // It also runs a consistency check on the snapshot (will warn if there are missing resources referenced in
 // the snapshot)
-func (s *Server) UpdateProxy(ctx context.Context, proxy *envoy.Proxy, snapshotResources map[string][]types.Resource) error {
+func (s *Server) UpdateProxy(ctx context.Context, proxy *models.Proxy, snapshotResources map[string][]types.Resource) error {
 	uuid := proxy.UUID.String()
 
 	s.configVerMutex.Lock()
diff --git a/pkg/injector/patch.go b/pkg/injector/patch.go
index fd4109a0..ec1d73ab 100644
--- a/pkg/injector/patch.go
+++ b/pkg/injector/patch.go
@@ -17,10 +17,10 @@ import (
 
 	"github.com/openservicemesh/osm/pkg/certificate"
 	"github.com/openservicemesh/osm/pkg/constants"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/errcode"
 	"github.com/openservicemesh/osm/pkg/identity"
 	"github.com/openservicemesh/osm/pkg/metricsstore"
+	"github.com/openservicemesh/osm/pkg/models"
 	"github.com/openservicemesh/osm/pkg/utils"
 )
 
@@ -29,7 +29,7 @@ func (wh *mutatingWebhook) createPatch(pod *corev1.Pod, req *admissionv1.Admissi
 	namespace := req.Namespace
 
 	// Issue a certificate for the proxy sidecar - used for Envoy to connect to XDS (not Envoy-to-Envoy connections)
-	cnPrefix := envoy.NewXDSCertCNPrefix(proxyUUID, envoy.KindSidecar, identity.New(pod.Spec.ServiceAccountName, namespace))
+	cnPrefix := models.NewXDSCertCNPrefix(proxyUUID, models.KindSidecar, identity.New(pod.Spec.ServiceAccountName, namespace))
 	log.Debug().Msgf("Patching POD spec: service-account=%s, namespace=%s with certificate CN prefix=%s", pod.Spec.ServiceAccountName, namespace, cnPrefix)
 	startTime := time.Now()
 	bootstrapCertificate, err := wh.certManager.IssueCertificate(certificate.ForCommonNamePrefix(cnPrefix))
diff --git a/pkg/k8s/client.go b/pkg/k8s/client.go
index 6f41f615..2fb65498 100644
--- a/pkg/k8s/client.go
+++ b/pkg/k8s/client.go
@@ -20,7 +20,6 @@ import (
 	"github.com/openservicemesh/osm/pkg/models"
 
 	"github.com/openservicemesh/osm/pkg/constants"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/errcode"
 	"github.com/openservicemesh/osm/pkg/k8s/informers"
 	"github.com/openservicemesh/osm/pkg/messaging"
@@ -335,7 +334,7 @@ func (c *Client) GetMeshConfig() configv1alpha2.MeshConfig {
 
 // GetPodForProxy returns the pod that the given proxy is attached to, based on the UUID and service identity.
 // TODO(4863): move this to kube/client.go
-func (c *Client) GetPodForProxy(proxy *envoy.Proxy) (*v1.Pod, error) {
+func (c *Client) GetPodForProxy(proxy *models.Proxy) (*v1.Pod, error) {
 	proxyUUID, svcAccount := proxy.UUID.String(), proxy.Identity.ToK8sServiceAccount()
 	log.Trace().Msgf("Looking for pod with label %q=%q", constants.EnvoyUniqueIDLabelName, proxyUUID)
 	podList := c.ListPods()
diff --git a/pkg/k8s/mock_controller_generated.go b/pkg/k8s/mock_controller_generated.go
index da01f0df..34bb99fa 100644
--- a/pkg/k8s/mock_controller_generated.go
+++ b/pkg/k8s/mock_controller_generated.go
@@ -11,7 +11,6 @@ import (
 	gomock "github.com/golang/mock/gomock"
 	v1alpha2 "github.com/openservicemesh/osm/pkg/apis/config/v1alpha2"
 	v1alpha1 "github.com/openservicemesh/osm/pkg/apis/policy/v1alpha1"
-	envoy "github.com/openservicemesh/osm/pkg/envoy"
 	models "github.com/openservicemesh/osm/pkg/models"
 	v1alpha3 "github.com/servicemeshinterface/smi-sdk-go/pkg/apis/access/v1alpha3"
 	v1alpha4 "github.com/servicemeshinterface/smi-sdk-go/pkg/apis/specs/v1alpha4"
@@ -129,7 +128,7 @@ func (mr *MockControllerMockRecorder) GetOSMNamespace() *gomock.Call {
 }
 
 // GetPodForProxy mocks base method.
-func (m *MockController) GetPodForProxy(arg0 *envoy.Proxy) (*v1.Pod, error) {
+func (m *MockController) GetPodForProxy(arg0 *models.Proxy) (*v1.Pod, error) {
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "GetPodForProxy", arg0)
 	ret0, _ := ret[0].(*v1.Pod)
diff --git a/pkg/k8s/types.go b/pkg/k8s/types.go
index 41262568..38edfdea 100644
--- a/pkg/k8s/types.go
+++ b/pkg/k8s/types.go
@@ -16,7 +16,6 @@ import (
 
 	configv1alpha2 "github.com/openservicemesh/osm/pkg/apis/config/v1alpha2"
 	policyv1alpha1 "github.com/openservicemesh/osm/pkg/apis/policy/v1alpha1"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	configv1alpha2Client "github.com/openservicemesh/osm/pkg/gen/client/config/clientset/versioned"
 	policyv1alpha1Client "github.com/openservicemesh/osm/pkg/gen/client/policy/clientset/versioned"
 
@@ -136,7 +135,7 @@ type Controller interface {
 	GetEndpoints(name, namespace string) (*corev1.Endpoints, error)
 
 	// GetPodForProxy returns the pod that the given proxy is attached to, based on the UUID and service identity.
-	GetPodForProxy(proxy *envoy.Proxy) (*corev1.Pod, error)
+	GetPodForProxy(proxy *models.Proxy) (*corev1.Pod, error)
 }
 
 // PassthroughInterface is the interface for methods that are implemented by the k8s.Client, but are not considered
diff --git a/pkg/envoy/proxy.go b/pkg/models/proxy.go
similarity index 99%
rename from pkg/envoy/proxy.go
rename to pkg/models/proxy.go
index 1d147fde..2ac00c6c 100644
--- a/pkg/envoy/proxy.go
+++ b/pkg/models/proxy.go
@@ -1,4 +1,4 @@
-package envoy
+package models
 
 import (
 	"fmt"
diff --git a/pkg/models/types.go b/pkg/models/types.go
new file mode 100644
index 00000000..2e989bdc
--- /dev/null
+++ b/pkg/models/types.go
@@ -0,0 +1,9 @@
+package models
+
+// ProxyKind is the type used to define the proxy's kind
+type ProxyKind string
+
+const (
+	// KindSidecar implies the proxy is a sidecar
+	KindSidecar ProxyKind = "sidecar"
+)
diff --git a/pkg/osm/callbacks.go b/pkg/osm/callbacks.go
index 136bf629..28b2a28e 100644
--- a/pkg/osm/callbacks.go
+++ b/pkg/osm/callbacks.go
@@ -12,11 +12,11 @@ import (
 
 	"github.com/openservicemesh/osm/pkg/certificate"
 	"github.com/openservicemesh/osm/pkg/constants"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/errcode"
 	"github.com/openservicemesh/osm/pkg/identity"
 	"github.com/openservicemesh/osm/pkg/messaging"
 	"github.com/openservicemesh/osm/pkg/metricsstore"
+	"github.com/openservicemesh/osm/pkg/models"
 	"github.com/openservicemesh/osm/pkg/utils"
 )
 
@@ -43,7 +43,7 @@ func (cp *ControlPlane[T]) ProxyConnected(ctx context.Context, connectionID int6
 		return fmt.Errorf("error parsing certificate common name %s: %w", certCommonName, err)
 	}
 
-	proxy := envoy.NewProxy(kind, uuid, si, utils.GetIPFromContext(ctx), connectionID)
+	proxy := models.NewProxy(kind, uuid, si, utils.GetIPFromContext(ctx), connectionID)
 
 	if err := cp.catalog.VerifyProxy(proxy); err != nil {
 		return err
@@ -77,7 +77,7 @@ func (cp *ControlPlane[T]) ProxyConnected(ctx context.Context, connectionID int6
 	return nil
 }
 
-func (cp *ControlPlane[T]) scheduleUpdate(ctx context.Context, proxy *envoy.Proxy) {
+func (cp *ControlPlane[T]) scheduleUpdate(ctx context.Context, proxy *models.Proxy) {
 	var wg sync.WaitGroup
 	wg.Add(1)
 	cp.workqueues.AddJob(
@@ -94,7 +94,7 @@ func (cp *ControlPlane[T]) scheduleUpdate(ctx context.Context, proxy *envoy.Prox
 	wg.Wait()
 }
 
-func (cp *ControlPlane[T]) update(ctx context.Context, proxy *envoy.Proxy) error {
+func (cp *ControlPlane[T]) update(ctx context.Context, proxy *models.Proxy) error {
 	resources, err := cp.configGenerator.GenerateConfig(ctx, proxy)
 	if err != nil {
 		return err
@@ -114,7 +114,7 @@ func (cp *ControlPlane[T]) ProxyDisconnected(connectionID int64) {
 	metricsstore.DefaultMetricsStore.ProxyConnectCount.Dec()
 }
 
-func getCertificateCommonNameMeta(cn certificate.CommonName) (envoy.ProxyKind, uuid.UUID, identity.ServiceIdentity, error) {
+func getCertificateCommonNameMeta(cn certificate.CommonName) (models.ProxyKind, uuid.UUID, identity.ServiceIdentity, error) {
 	// XDS cert CN is of the form <proxy-UUID>.<kind>.<proxy-identity>.<trust-domain>
 	chunks := strings.SplitN(cn.String(), constants.DomainDelimiter, 5)
 	if len(chunks) < 4 {
@@ -136,5 +136,5 @@ func getCertificateCommonNameMeta(cn certificate.CommonName) (envoy.ProxyKind, u
 		return "", uuid.UUID{}, "", errInvalidCertificateCN
 	}
 
-	return envoy.ProxyKind(chunks[1]), proxyUUID, identity.New(chunks[2], chunks[3]), nil
+	return models.ProxyKind(chunks[1]), proxyUUID, identity.New(chunks[2], chunks[3]), nil
 }
diff --git a/pkg/osm/control_plane.go b/pkg/osm/control_plane.go
index c647f124..7f5110f4 100644
--- a/pkg/osm/control_plane.go
+++ b/pkg/osm/control_plane.go
@@ -5,9 +5,9 @@ import (
 
 	"github.com/openservicemesh/osm/pkg/catalog"
 	"github.com/openservicemesh/osm/pkg/certificate"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/envoy/registry"
 	"github.com/openservicemesh/osm/pkg/messaging"
+	"github.com/openservicemesh/osm/pkg/models"
 	"github.com/openservicemesh/osm/pkg/workerpool"
 )
 
@@ -18,13 +18,13 @@ const (
 // ProxyUpdater is an abstraction over a type that updates a proxy with a Config of type `T` to the proxy passed in
 // the UpdateProxy method.
 type ProxyUpdater[T any] interface {
-	UpdateProxy(context.Context, *envoy.Proxy, T) error
+	UpdateProxy(context.Context, *models.Proxy, T) error
 }
 
 // ProxyConfigGenerator is an abstraction over a type that generates a Config of type `T` for the proxy passed in the
 // GenerateConfig method. It is not meant to actually update the proxy itself.
 type ProxyConfigGenerator[T any] interface {
-	GenerateConfig(context.Context, *envoy.Proxy) (T, error)
+	GenerateConfig(context.Context, *models.Proxy) (T, error)
 }
 
 // ControlPlane is the central part of OSM, that ties in config generation, proxy updates, the message broker, and
diff --git a/pkg/validator/validators.go b/pkg/validator/validators.go
index d9ce1b57..7ef454bf 100644
--- a/pkg/validator/validators.go
+++ b/pkg/validator/validators.go
@@ -325,6 +325,10 @@ func (kc *validator) validateMRCOnUpdate(oldMRC *configv1alpha2.MeshRootCertific
 		return fmt.Errorf("cannot update trust domain for MRC %s. Create a new MRC and initiate root certificate rotation to update the trust domain", getNamespacedMRC(oldMRC))
 	}
 
+	if oldMRC.Spec.SpiffeEnabled != newMRC.Spec.SpiffeEnabled {
+		return fmt.Errorf("cannot update SpiffeEnabled for MRC %s. Create a new MRC and initiate root certificate rotation to enable SPIFFE certificates", getNamespacedMRC(oldMRC))
+	}
+
 	if oldMRC.Spec.Intent != newMRC.Spec.Intent && newMRC.Spec.Intent == constants.MRCIntentActive {
 		foundActive, err := kc.checkForExistingActiveMRC(newMRC)
 		if err != nil {
diff --git a/tilt_config.json b/tilt_config.json
index fbfdc821..f6be1c28 100644
--- a/tilt_config.json
+++ b/tilt_config.json
@@ -1,6 +1,7 @@
 {
   "osm-values":[
-    "osm.featureFlags.enableMeshRootCertificate=false",
+    "osm.featureFlags.enableMeshRootCertificate=true",
+    "osm.featureFlags.enableSPIFFE=true",
     "osm.enableDebugServer=true",
     "osm.enablePermissiveTrafficPolicy=false"
   ]


Test patch:
diff --git a/cmd/cli/metrics_test.go b/cmd/cli/metrics_test.go
index a4e023d2..54261748 100644
--- a/cmd/cli/metrics_test.go
+++ b/cmd/cli/metrics_test.go
@@ -88,6 +88,13 @@ func TestRun_MetricsEnable(t *testing.T) {
 	}
 
 	testScenarios := []test{
+		{
+			cmd: &metricsEnableCmd{
+				out:        new(bytes.Buffer),
+				namespaces: []string{},
+				clientSet:  fakeClient,
+			},
+		},
 		{
 			cmd: &metricsEnableCmd{
 				out:        new(bytes.Buffer),
@@ -124,7 +131,9 @@ func TestRun_MetricsEnable(t *testing.T) {
 		}
 
 		err := scenario.cmd.run()
-		assert.Nil(err)
+		if err != nil {
+			assert.EqualError(err, "required flag(s) "+"\"namespace\""+" not set", "no namespace mentioned")
+		}
 
 		// Test expectation for scenario
 		for _, ns := range scenario.cmd.namespaces {
@@ -158,6 +167,13 @@ func TestRun_MetricsDisable(t *testing.T) {
 	}
 
 	testScenarios := []test{
+		{
+			cmd: &metricsDisableCmd{
+				out:        new(bytes.Buffer),
+				namespaces: []string{},
+				clientSet:  fakeClient,
+			},
+		},
 		{
 			cmd: &metricsDisableCmd{
 				out:        new(bytes.Buffer),
@@ -194,7 +210,9 @@ func TestRun_MetricsDisable(t *testing.T) {
 		}
 
 		err := scenario.cmd.run()
-		assert.Nil(err)
+		if err != nil {
+			assert.EqualError(err, "required flag(s) "+"\"namespace\""+" not set", "no namespace mentioned")
+		}
 
 		// Test expectation for scenario
 		for _, ns := range scenario.cmd.namespaces {
diff --git a/cmd/osm-bootstrap/osm-bootstrap_test.go b/cmd/osm-bootstrap/osm-bootstrap_test.go
index 7ce2dc24..2c794064 100644
--- a/cmd/osm-bootstrap/osm-bootstrap_test.go
+++ b/cmd/osm-bootstrap/osm-bootstrap_test.go
@@ -188,6 +188,7 @@ func TestBuildMeshRootCertificate(t *testing.T) {
 	assert.Equal(meshRootCertificate.Spec.Provider.Tresor.CA.SecretRef.Namespace, testNamespace)
 	assert.Nil(meshRootCertificate.Spec.Provider.Vault)
 	assert.Nil(meshRootCertificate.Spec.Provider.CertManager)
+	assert.False(meshRootCertificate.Spec.SpiffeEnabled)
 }
 
 func TestValidateCLIParams(t *testing.T) {
diff --git a/pkg/certificate/options_test.go b/pkg/certificate/options_test.go
index 8f1d4522..ca9ee468 100644
--- a/pkg/certificate/options_test.go
+++ b/pkg/certificate/options_test.go
@@ -2,6 +2,9 @@ package certificate
 
 import (
 	"testing"
+
+	"github.com/spiffe/go-spiffe/v2/spiffeid"
+	tassert "github.com/stretchr/testify/assert"
 )
 
 func TestIssueOptions_CommonName(t *testing.T) {
@@ -87,3 +90,67 @@ func TestIssueOptions_CommonName(t *testing.T) {
 		})
 	}
 }
+
+func TestIssueOptions_SpiffeUrl(t *testing.T) {
+	tests := []struct {
+		name          string
+		trustDomain   string
+		issueOption   []IssueOption
+		spiffeEnabled bool
+		want          string
+	}{
+		{
+			name:          "should return spiffe id composed with spiffe id",
+			trustDomain:   "cluster.local",
+			issueOption:   []IssueOption{ForServiceIdentity("sa.ns")},
+			spiffeEnabled: true,
+			want:          "spiffe://cluster.local/sa/ns",
+		},
+		{
+			name:          "should return spiffe id composed with spiffe id for simple commonname",
+			trustDomain:   "cluster.local",
+			issueOption:   []IssueOption{ForCommonNamePrefix("ads")},
+			spiffeEnabled: true,
+			want:          "spiffe://cluster.local/ads",
+		},
+		{
+			name:          "should return spiffe id composed with spiffe id for ingress gateway",
+			trustDomain:   "cluster.local",
+			issueOption:   []IssueOption{ForIngressGateway("ingress.svc.cluster.local")},
+			spiffeEnabled: true,
+			want:          "spiffe://cluster.local/ingress/svc",
+		},
+		{
+			name:          "should return empty if spiffe id not enabled",
+			trustDomain:   "cluster.local",
+			issueOption:   []IssueOption{ForCommonNamePrefix("test.test")},
+			spiffeEnabled: false,
+			want:          "",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			assert := tassert.New(t)
+
+			if tt.spiffeEnabled {
+				tt.issueOption = append(tt.issueOption, withSpiffeEnabled())
+			}
+
+			o := NewCertOptions(tt.issueOption...)
+
+			o.trustDomain = tt.trustDomain
+			o.spiffeEnabled = tt.spiffeEnabled
+
+			assert.Equal(tt.want, o.URISAN().String())
+
+			// validate it is a valid SPIFFE ID using spiffe package which runs validation checks
+			id, err := spiffeid.FromURI(o.URISAN())
+			if tt.spiffeEnabled {
+				assert.Nil(err)
+				assert.Equal(tt.want, id.String())
+			} else {
+				assert.ErrorContains(err, "cannot be empty")
+			}
+		})
+	}
+}
diff --git a/pkg/certificate/providers/tresor/certificate_manager_test.go b/pkg/certificate/providers/tresor/certificate_manager_test.go
index 03d6ff72..8889ea86 100644
--- a/pkg/certificate/providers/tresor/certificate_manager_test.go
+++ b/pkg/certificate/providers/tresor/certificate_manager_test.go
@@ -5,6 +5,7 @@ import (
 
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
+	"github.com/spiffe/go-spiffe/v2/svid/x509svid"
 
 	"github.com/openservicemesh/osm/pkg/certificate"
 )
@@ -58,6 +59,37 @@ var _ = Describe("Test Certificate Manager", func() {
 			Expect(err).ToNot(HaveOccurred(), string(pemRootCert))
 			Expect(xRootCert.Subject.CommonName).To(Equal(cn.String()))
 		})
+
+		It("should issue a certificate with spiffe enabled and compatible", func() {
+			Expect(newCertError).ToNot(HaveOccurred())
+			options := certificate.NewCertOptionsWithTrustDomain(serviceFQDN, "cluster.local", validity, true)
+			cert, issueCertificateError := m.IssueCertificate(options)
+			Expect(issueCertificateError).ToNot(HaveOccurred())
+			Expect(cert.GetCommonName()).To(Equal(certificate.CommonName(serviceFQDN + ".cluster.local")))
+
+			x509Cert, err := certificate.DecodePEMCertificate(rootCert.GetCertificateChain())
+			Expect(err).ToNot(HaveOccurred())
+
+			issuingCAPEM, err := certificate.EncodeCertDERtoPEM(x509Cert.Raw)
+			Expect(err).ToNot(HaveOccurred())
+			Expect([]byte(cert.GetIssuingCA())).To(Equal([]byte(issuingCAPEM)))
+
+			pemCert := cert.GetCertificateChain()
+			xCert, err := certificate.DecodePEMCertificate(pemCert)
+			Expect(err).ToNot(HaveOccurred())
+			Expect(xCert.Subject.CommonName).To(Equal(serviceFQDN + ".cluster.local"))
+			Expect(xCert.URIs[0].String()).To(Equal("spiffe://cluster.local/a/b/c"))
+
+			pemRootCert := cert.GetIssuingCA()
+			xRootCert, err := certificate.DecodePEMCertificate(pemRootCert)
+			Expect(err).ToNot(HaveOccurred(), string(pemRootCert))
+			Expect(xRootCert.Subject.CommonName).To(Equal(cn.String()))
+
+			// parse will throw error if it is not a valid x509 SVID
+			svid, err := x509svid.Parse(cert.CertChain, cert.PrivateKey)
+			Expect(err).ToNot(HaveOccurred())
+			Expect(svid.ID.String()).To(Equal("spiffe://cluster.local/a/b/c"))
+		})
 	})
 
 	Context("Test nil certificate issue", func() {
diff --git a/pkg/certificate/providers/vault/certificate_manager_test.go b/pkg/certificate/providers/vault/certificate_manager_test.go
index 0769e537..2b327e97 100644
--- a/pkg/certificate/providers/vault/certificate_manager_test.go
+++ b/pkg/certificate/providers/vault/certificate_manager_test.go
@@ -126,10 +126,11 @@ func TestIssueCertificate(t *testing.T) {
 	mockVaultwithPKI(t, cm)
 
 	testCases := []struct {
-		description string
-		cn          string
-		vP          time.Duration
-		wantErr     bool
+		description   string
+		cn            string
+		vP            time.Duration
+		spiffeEnabled bool
+		wantErr       bool
 	}{
 		{
 			description: "valid inputs",
@@ -137,6 +138,13 @@ func TestIssueCertificate(t *testing.T) {
 			vP:          validityPeriod,
 			wantErr:     false,
 		},
+		{
+			description:   "valid inputs with spiffe Id",
+			cn:            commonName,
+			vP:            validityPeriod,
+			spiffeEnabled: true,
+			wantErr:       false,
+		},
 		{
 			description: "error with invalid common name",
 			cn:          " ",
@@ -147,11 +155,22 @@ func TestIssueCertificate(t *testing.T) {
 	for _, tc := range testCases {
 		t.Run(tc.description, func(t *testing.T) {
 			tassert := assert.New(t)
-			_, err = cm.IssueCertificate(certificate.NewCertOptionsWithFullName(tc.cn, tc.vP))
+			o := certificate.NewCertOptionsWithTrustDomain(tc.cn, "cluster.local", tc.vP, tc.spiffeEnabled)
+			cert, err := cm.IssueCertificate(o)
+
 			if tc.wantErr {
 				tassert.Error(err, "expected error, got nil")
-			} else {
-				tassert.NoError(err, "did not expect error, got %v", err)
+				return
+			}
+
+			tassert.NoError(err, "did not expect error, got %v", err)
+			xCert, err := certificate.DecodePEMCertificate(cert.CertChain)
+			tassert.NoError(err, "did not expect error, got %v", err)
+			tassert.Equal(tc.cn+".cluster.local", xCert.Subject.CommonName)
+
+			if tc.spiffeEnabled {
+				uri := xCert.URIs[0]
+				tassert.Equal("spiffe://cluster.local/"+tc.cn, uri.String())
 			}
 		})
 	}
@@ -202,7 +221,11 @@ func mockVaultwithPKI(t *testing.T, cm *CertManager) {
 		t.Fatal("expected ca info, response from vault was nil")
 	}
 
-	_, err = cm.client.Logical().Write("pki/roles/default_role", map[string]interface{}{})
+	_, err = cm.client.Logical().Write("pki/roles/default_role", map[string]interface{}{
+		"allowed_uri_sans": "spiffe://*",
+		"allowed_domains":  "cluster.local",
+		"allow_subdomains": "true",
+	})
 
 	req := cm.client.NewRequest("POST", "/v1/pki_int/roles/default_role")
 	req.BodyBytes = []byte(`{
diff --git a/pkg/certificate/providers/vault/tools_test.go b/pkg/certificate/providers/vault/tools_test.go
index 95e8b4b5..419c54d9 100644
--- a/pkg/certificate/providers/vault/tools_test.go
+++ b/pkg/certificate/providers/vault/tools_test.go
@@ -33,13 +33,25 @@ var _ = Describe("Test tools", func() {
 
 	Context("Test cert issuance data for request", func() {
 		It("creates a map w/ correct fields", func() {
-			cn := certificate.CommonName("blah.foo.com")
-			actual := getIssuanceData(cn, 8123*time.Minute)
+			options := certificate.NewCertOptionsWithFullName("blah.foo.com", 8123*time.Minute)
+			actual := getIssuanceData(options)
 			expected := map[string]interface{}{
 				"common_name": "blah.foo.com",
 				"ttl":         "135h",
 			}
 			Expect(actual).To(Equal(expected))
 		})
+
+		It("creates a map w/ correct fields when Spiffe Is enabled", func() {
+			options := certificate.NewCertOptionsWithTrustDomain("sa.svc", "foo.com", 8123*time.Minute, true)
+			actual := getIssuanceData(options)
+			expected := map[string]interface{}{
+				"common_name": "sa.svc.foo.com",
+				"ttl":         "135h",
+				"uri_sans":    "spiffe://foo.com/sa/svc",
+			}
+			Expect(actual).To(Equal(expected))
+		})
 	})
+
 })
diff --git a/pkg/compute/kube/client_test.go b/pkg/compute/kube/client_test.go
index 4aa67fb5..edf54641 100644
--- a/pkg/compute/kube/client_test.go
+++ b/pkg/compute/kube/client_test.go
@@ -30,7 +30,6 @@ import (
 
 	"github.com/openservicemesh/osm/pkg/constants"
 	"github.com/openservicemesh/osm/pkg/endpoint"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/identity"
 	"github.com/openservicemesh/osm/pkg/k8s"
 	"github.com/openservicemesh/osm/pkg/k8s/informers"
@@ -616,7 +615,7 @@ func TestIsMetricsEnabled(t *testing.T) {
 			}
 			c := NewClient(k)
 
-			actual, err := c.IsMetricsEnabled(&envoy.Proxy{})
+			actual, err := c.IsMetricsEnabled(&models.Proxy{})
 			assert.Equal(tc.expectEnabled, actual)
 			if tc.expectErr {
 				assert.Error(err)
@@ -634,14 +633,14 @@ func TestListServicesForProxy(t *testing.T) {
 		name      string
 		endpoints identity.ServiceIdentity
 		pods      []*corev1.Pod
-		proxy     *envoy.Proxy
+		proxy     *models.Proxy
 		services  []*corev1.Service
 		expected  []service.MeshService
 		expectErr bool
 	}{
 		{
 			name:  "Returns the list of MeshServices matching the given pod",
-			proxy: envoy.NewProxy(envoy.KindSidecar, goodUUID, identity.New("sa1", "ns1"), &net.IPAddr{}, 1),
+			proxy: models.NewProxy(models.KindSidecar, goodUUID, identity.New("sa1", "ns1"), &net.IPAddr{}, 1),
 			pods: []*corev1.Pod{
 				{
 					ObjectMeta: metav1.ObjectMeta{
@@ -702,7 +701,7 @@ func TestListServicesForProxy(t *testing.T) {
 		},
 		{
 			name:  "No matching services found",
-			proxy: envoy.NewProxy(envoy.KindSidecar, goodUUID, identity.New("sa1", "ns1"), &net.IPAddr{}, 1),
+			proxy: models.NewProxy(models.KindSidecar, goodUUID, identity.New("sa1", "ns1"), &net.IPAddr{}, 1),
 			pods: []*corev1.Pod{
 				{
 					ObjectMeta: metav1.ObjectMeta{
@@ -747,7 +746,7 @@ func TestListServicesForProxy(t *testing.T) {
 		},
 		{
 			name:  "Error: pod not found",
-			proxy: envoy.NewProxy(envoy.KindSidecar, goodUUID, identity.New("sa1", "ns1"), &net.IPAddr{}, 1),
+			proxy: models.NewProxy(models.KindSidecar, goodUUID, identity.New("sa1", "ns1"), &net.IPAddr{}, 1),
 			pods: []*corev1.Pod{
 				{
 					ObjectMeta: metav1.ObjectMeta{
@@ -1519,19 +1518,19 @@ func TestGetProxyStatsHeaders(t *testing.T) {
 	const namespace = "ns1"
 	testCases := []struct {
 		name      string
-		proxy     *envoy.Proxy
+		proxy     *models.Proxy
 		pod       *corev1.Pod
 		expected  map[string]string
 		expectErr bool
 	}{
 		{
 			name:      "pod not found",
-			proxy:     envoy.NewProxy(envoy.KindSidecar, uuid1, identity.New("sa1", namespace), &net.IPAddr{}, 1),
+			proxy:     models.NewProxy(models.KindSidecar, uuid1, identity.New("sa1", namespace), &net.IPAddr{}, 1),
 			expectErr: true,
 		},
 		{
 			name:  "pod has bad service account",
-			proxy: envoy.NewProxy(envoy.KindSidecar, uuid1, identity.New("sa1", namespace), &net.IPAddr{}, 1),
+			proxy: models.NewProxy(models.KindSidecar, uuid1, identity.New("sa1", namespace), &net.IPAddr{}, 1),
 			pod: &corev1.Pod{
 				ObjectMeta: metav1.ObjectMeta{
 					Namespace: namespace,
@@ -1547,7 +1546,7 @@ func TestGetProxyStatsHeaders(t *testing.T) {
 		},
 		{
 			name:  "full stats headers from deployment",
-			proxy: envoy.NewProxy(envoy.KindSidecar, uuid1, identity.New("sa1", namespace), &net.IPAddr{}, 1),
+			proxy: models.NewProxy(models.KindSidecar, uuid1, identity.New("sa1", namespace), &net.IPAddr{}, 1),
 
 			pod: &corev1.Pod{
 				ObjectMeta: metav1.ObjectMeta{
@@ -1581,7 +1580,7 @@ func TestGetProxyStatsHeaders(t *testing.T) {
 		},
 		{
 			name:  "no owner references",
-			proxy: envoy.NewProxy(envoy.KindSidecar, uuid1, identity.New("sa1", namespace), &net.IPAddr{}, 1),
+			proxy: models.NewProxy(models.KindSidecar, uuid1, identity.New("sa1", namespace), &net.IPAddr{}, 1),
 
 			pod: &corev1.Pod{
 				ObjectMeta: metav1.ObjectMeta{
@@ -1610,7 +1609,7 @@ func TestGetProxyStatsHeaders(t *testing.T) {
 		},
 		{
 			name:  "full stats headers from replicaset with hyphen",
-			proxy: envoy.NewProxy(envoy.KindSidecar, uuid1, identity.New("sa1", namespace), &net.IPAddr{}, 1),
+			proxy: models.NewProxy(models.KindSidecar, uuid1, identity.New("sa1", namespace), &net.IPAddr{}, 1),
 
 			pod: &corev1.Pod{
 				ObjectMeta: metav1.ObjectMeta{
@@ -1644,7 +1643,7 @@ func TestGetProxyStatsHeaders(t *testing.T) {
 		},
 		{
 			name:  "full stats headers from replicaset without hyphen",
-			proxy: envoy.NewProxy(envoy.KindSidecar, uuid1, identity.New("sa1", namespace), &net.IPAddr{}, 1),
+			proxy: models.NewProxy(models.KindSidecar, uuid1, identity.New("sa1", namespace), &net.IPAddr{}, 1),
 
 			pod: &corev1.Pod{
 				ObjectMeta: metav1.ObjectMeta{
diff --git a/pkg/envoy/generator/cds_test.go b/pkg/envoy/generator/cds_test.go
index b57a7a09..0866a0ae 100644
--- a/pkg/envoy/generator/cds_test.go
+++ b/pkg/envoy/generator/cds_test.go
@@ -24,6 +24,7 @@ import (
 	testclient "k8s.io/client-go/kubernetes/fake"
 
 	configv1alpha2 "github.com/openservicemesh/osm/pkg/apis/config/v1alpha2"
+	"github.com/openservicemesh/osm/pkg/models"
 
 	"github.com/openservicemesh/osm/pkg/catalog"
 	"github.com/openservicemesh/osm/pkg/constants"
@@ -45,7 +46,7 @@ func TestGenerateCDS(t *testing.T) {
 	mockCatalog := catalog.NewMockMeshCataloger(mockCtrl)
 
 	proxyUUID := uuid.New()
-	proxy := envoy.NewProxy(envoy.KindSidecar, proxyUUID, identity.New(tests.BookbuyerServiceAccountName, tests.Namespace), nil, 1)
+	proxy := models.NewProxy(models.KindSidecar, proxyUUID, identity.New(tests.BookbuyerServiceAccountName, tests.Namespace), nil, 1)
 
 	testMeshSvc := service.MeshService{
 		Namespace:  tests.BookbuyerService.Namespace,
@@ -403,7 +404,7 @@ func TestGenerateCDS(t *testing.T) {
 }
 
 func TestNewResponseListServicesError(t *testing.T) {
-	proxy := envoy.NewProxy(envoy.KindSidecar, uuid.New(), identity.New(tests.BookbuyerServiceAccountName, tests.Namespace), nil, 1)
+	proxy := models.NewProxy(models.KindSidecar, uuid.New(), identity.New(tests.BookbuyerServiceAccountName, tests.Namespace), nil, 1)
 
 	ctrl := gomock.NewController(t)
 	meshCatalog := catalog.NewMockMeshCataloger(ctrl)
@@ -421,7 +422,7 @@ func TestNewResponseListServicesError(t *testing.T) {
 func TestNewResponseGetEgressTrafficPolicyError(t *testing.T) {
 	proxyIdentity := identity.K8sServiceAccount{Name: "svcacc", Namespace: "ns"}.ToServiceIdentity()
 	proxyUUID := uuid.New()
-	proxy := envoy.NewProxy(envoy.KindSidecar, proxyUUID, identity.New("svcacc", "ns"), nil, 1)
+	proxy := models.NewProxy(models.KindSidecar, proxyUUID, identity.New("svcacc", "ns"), nil, 1)
 
 	ctrl := gomock.NewController(t)
 	meshCatalog := catalog.NewMockMeshCataloger(ctrl)
@@ -443,7 +444,7 @@ func TestNewResponseGetEgressTrafficPolicyError(t *testing.T) {
 func TestNewResponseGetEgressTrafficPolicyNotEmpty(t *testing.T) {
 	proxyIdentity := identity.K8sServiceAccount{Name: "svcacc", Namespace: "ns"}.ToServiceIdentity()
 	proxyUUID := uuid.New()
-	proxy := envoy.NewProxy(envoy.KindSidecar, proxyUUID, identity.New("svcacc", "ns"), nil, 1)
+	proxy := models.NewProxy(models.KindSidecar, proxyUUID, identity.New("svcacc", "ns"), nil, 1)
 
 	ctrl := gomock.NewController(t)
 	meshCatalog := catalog.NewMockMeshCataloger(ctrl)
diff --git a/pkg/envoy/generator/eds_test.go b/pkg/envoy/generator/eds_test.go
index 2292de3b..6e4cfc34 100644
--- a/pkg/envoy/generator/eds_test.go
+++ b/pkg/envoy/generator/eds_test.go
@@ -13,15 +13,15 @@ import (
 
 	"github.com/openservicemesh/osm/pkg/apis/config/v1alpha2"
 	"github.com/openservicemesh/osm/pkg/compute"
+	"github.com/openservicemesh/osm/pkg/models"
 	"github.com/openservicemesh/osm/pkg/service"
 
 	catalogFake "github.com/openservicemesh/osm/pkg/catalog/fake"
 	"github.com/openservicemesh/osm/pkg/constants"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/tests"
 )
 
-func getProxy(kubeClient kubernetes.Interface) (*envoy.Proxy, error) {
+func getProxy(kubeClient kubernetes.Interface) (*models.Proxy, error) {
 	podLabels := map[string]string{
 		constants.AppLabel:               tests.BookbuyerService.Name,
 		constants.EnvoyUniqueIDLabelName: tests.ProxyUUID,
@@ -46,7 +46,7 @@ func getProxy(kubeClient kubernetes.Interface) (*envoy.Proxy, error) {
 		}
 	}
 
-	return envoy.NewProxy(envoy.KindSidecar, uuid.MustParse(tests.ProxyUUID), tests.BookbuyerServiceIdentity, nil, 1), nil
+	return models.NewProxy(models.KindSidecar, uuid.MustParse(tests.ProxyUUID), tests.BookbuyerServiceIdentity, nil, 1), nil
 }
 
 func TestEndpointConfiguration(t *testing.T) {
@@ -74,7 +74,7 @@ func TestEndpointConfiguration(t *testing.T) {
 	assert.NotNil(meshCatalog)
 	assert.NotNil(proxy)
 
-	proxy = envoy.NewProxy(envoy.KindSidecar, uuid.MustParse(tests.ProxyUUID), tests.BookbuyerServiceIdentity, nil, 1)
+	proxy = models.NewProxy(models.KindSidecar, uuid.MustParse(tests.ProxyUUID), tests.BookbuyerServiceIdentity, nil, 1)
 
 	g := NewEnvoyConfigGenerator(meshCatalog, nil)
 	resources, err := g.generateEDS(context.Background(), proxy)
diff --git a/pkg/envoy/generator/generator_benchmark_test.go b/pkg/envoy/generator/generator_benchmark_test.go
index 589c6839..87357d27 100644
--- a/pkg/envoy/generator/generator_benchmark_test.go
+++ b/pkg/envoy/generator/generator_benchmark_test.go
@@ -16,19 +16,19 @@ import (
 	"github.com/openservicemesh/osm/pkg/k8s/informers"
 	"github.com/openservicemesh/osm/pkg/logger"
 	"github.com/openservicemesh/osm/pkg/messaging"
+	"github.com/openservicemesh/osm/pkg/models"
 	"github.com/openservicemesh/osm/pkg/signals"
 
 	"github.com/openservicemesh/osm/pkg/certificate"
 
 	"github.com/openservicemesh/osm/pkg/constants"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	configFake "github.com/openservicemesh/osm/pkg/gen/client/config/clientset/versioned/fake"
 	policyFake "github.com/openservicemesh/osm/pkg/gen/client/policy/clientset/versioned/fake"
 	"github.com/openservicemesh/osm/pkg/k8s"
 	"github.com/openservicemesh/osm/pkg/tests"
 )
 
-func setupTestGenerator(b *testing.B) (*envoy.Proxy, *EnvoyConfigGenerator) {
+func setupTestGenerator(b *testing.B) (*models.Proxy, *EnvoyConfigGenerator) {
 	stop := signals.RegisterExitHandlers()
 	msgBroker := messaging.NewBroker(stop)
 	kubeClient := k8sClientFake.NewSimpleClientset()
@@ -119,7 +119,7 @@ func setupTestGenerator(b *testing.B) (*envoy.Proxy, *EnvoyConfigGenerator) {
 		b.Fatalf("Failed to create service: %v", err)
 	}
 
-	proxy := envoy.NewProxy(envoy.KindSidecar, proxyUUID, proxySvcAccount.ToServiceIdentity(), nil, 1)
+	proxy := models.NewProxy(models.KindSidecar, proxyUUID, proxySvcAccount.ToServiceIdentity(), nil, 1)
 
 	return proxy, NewEnvoyConfigGenerator(mc, certManager)
 }
diff --git a/pkg/envoy/generator/generator_test.go b/pkg/envoy/generator/generator_test.go
index a922c9eb..34277227 100644
--- a/pkg/envoy/generator/generator_test.go
+++ b/pkg/envoy/generator/generator_test.go
@@ -17,9 +17,9 @@ import (
 	tresorFake "github.com/openservicemesh/osm/pkg/certificate/providers/tresor/fake"
 	"github.com/openservicemesh/osm/pkg/compute"
 	"github.com/openservicemesh/osm/pkg/endpoint"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/identity"
 	"github.com/openservicemesh/osm/pkg/messaging"
+	"github.com/openservicemesh/osm/pkg/models"
 	"github.com/openservicemesh/osm/pkg/service"
 	"github.com/openservicemesh/osm/pkg/tests"
 )
@@ -27,7 +27,7 @@ import (
 func TestGenerateConfig(t *testing.T) {
 	tassert := assert.New(t)
 	proxyUUID := uuid.New()
-	proxy := envoy.NewProxy(envoy.KindSidecar, proxyUUID, tests.BookbuyerServiceIdentity, nil, 1)
+	proxy := models.NewProxy(models.KindSidecar, proxyUUID, tests.BookbuyerServiceIdentity, nil, 1)
 
 	mockCtrl := gomock.NewController(t)
 
diff --git a/pkg/envoy/generator/lds_test.go b/pkg/envoy/generator/lds_test.go
index 41e167d7..b4ddc68a 100644
--- a/pkg/envoy/generator/lds_test.go
+++ b/pkg/envoy/generator/lds_test.go
@@ -25,6 +25,7 @@ import (
 	"github.com/openservicemesh/osm/pkg/envoy/generator/lds"
 	"github.com/openservicemesh/osm/pkg/identity"
 	"github.com/openservicemesh/osm/pkg/messaging"
+	"github.com/openservicemesh/osm/pkg/models"
 	"github.com/openservicemesh/osm/pkg/service"
 	"github.com/openservicemesh/osm/pkg/tests"
 )
@@ -41,7 +42,7 @@ func TestGenerateLDS(t *testing.T) {
 	pod.Annotations = map[string]string{
 		constants.PrometheusScrapeAnnotation: "true",
 	}
-	proxy := envoy.NewProxy(envoy.KindSidecar, uuid.MustParse(tests.ProxyUUID), identity.New(tests.BookbuyerServiceAccountName, tests.Namespace), nil, 1)
+	proxy := models.NewProxy(models.KindSidecar, uuid.MustParse(tests.ProxyUUID), identity.New(tests.BookbuyerServiceAccountName, tests.Namespace), nil, 1)
 	provider := compute.NewMockInterface(mockCtrl)
 	provider.EXPECT().ListTrafficTargets().Return([]*access.TrafficTarget{&tests.TrafficTarget, &tests.BookstoreV2TrafficTarget}).AnyTimes()
 	provider.EXPECT().ListHTTPTrafficSpecs().Return([]*specs.HTTPRouteGroup{&tests.HTTPRouteGroup}).AnyTimes()
diff --git a/pkg/envoy/generator/rds/builder_test.go b/pkg/envoy/generator/rds/builder_test.go
index e1b1a506..5d526aee 100644
--- a/pkg/envoy/generator/rds/builder_test.go
+++ b/pkg/envoy/generator/rds/builder_test.go
@@ -14,6 +14,7 @@ import (
 
 	policyv1alpha1 "github.com/openservicemesh/osm/pkg/apis/policy/v1alpha1"
 	"github.com/openservicemesh/osm/pkg/envoy"
+	"github.com/openservicemesh/osm/pkg/models"
 
 	"github.com/openservicemesh/osm/pkg/identity"
 	"github.com/openservicemesh/osm/pkg/service"
@@ -403,7 +404,7 @@ func TestBuildInboundMeshRouteConfiguration(t *testing.T) {
 			assert := tassert.New(t)
 			rb := &routesBuilder{
 				inboundPortSpecificRouteConfigs: testInbound.HTTPRouteConfigsPerPort,
-				proxy:                           &envoy.Proxy{},
+				proxy:                           &models.Proxy{},
 				statsHeaders:                    tc.statsHeaders,
 				trustDomain:                     "cluster.local",
 			}
diff --git a/pkg/envoy/generator/rds_test.go b/pkg/envoy/generator/rds_test.go
index db8510d7..e916887a 100644
--- a/pkg/envoy/generator/rds_test.go
+++ b/pkg/envoy/generator/rds_test.go
@@ -26,9 +26,9 @@ import (
 	"github.com/openservicemesh/osm/pkg/compute/kube"
 	"github.com/openservicemesh/osm/pkg/constants"
 	"github.com/openservicemesh/osm/pkg/endpoint"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/identity"
 	"github.com/openservicemesh/osm/pkg/k8s"
+	"github.com/openservicemesh/osm/pkg/models"
 	"github.com/openservicemesh/osm/pkg/service"
 	"github.com/openservicemesh/osm/pkg/tests"
 	"github.com/openservicemesh/osm/pkg/trafficpolicy"
@@ -818,7 +818,7 @@ func TestRDSRespose(t *testing.T) {
 	}
 }
 
-func getBookstoreV1Proxy(kubeClient kubernetes.Interface) (*envoy.Proxy, error) {
+func getBookstoreV1Proxy(kubeClient kubernetes.Interface) (*models.Proxy, error) {
 	// Create pod for bookbuyer
 	bookbuyerPodLabels := map[string]string{
 		constants.AppLabel:               tests.BookbuyerService.Name,
@@ -866,10 +866,10 @@ func getBookstoreV1Proxy(kubeClient kubernetes.Interface) (*envoy.Proxy, error)
 		}
 	}
 
-	return envoy.NewProxy(envoy.KindSidecar, uuid.MustParse(tests.ProxyUUID), tests.BookstoreServiceIdentity, nil, 1), nil
+	return models.NewProxy(models.KindSidecar, uuid.MustParse(tests.ProxyUUID), tests.BookstoreServiceIdentity, nil, 1), nil
 }
 
-func getSidecarProxy(kubeClient kubernetes.Interface, proxyUUID uuid.UUID, svcIdentity identity.ServiceIdentity) (*envoy.Proxy, error) {
+func getSidecarProxy(kubeClient kubernetes.Interface, proxyUUID uuid.UUID, svcIdentity identity.ServiceIdentity) (*models.Proxy, error) {
 	bookbuyerPodLabels := map[string]string{
 		constants.AppLabel:               tests.BookbuyerService.Name,
 		constants.EnvoyUniqueIDLabelName: tests.ProxyUUID,
@@ -902,5 +902,5 @@ func getSidecarProxy(kubeClient kubernetes.Interface, proxyUUID uuid.UUID, svcId
 		}
 	}
 
-	return envoy.NewProxy(envoy.KindSidecar, proxyUUID, svcIdentity, nil, 1), nil
+	return models.NewProxy(models.KindSidecar, proxyUUID, svcIdentity, nil, 1), nil
 }
diff --git a/pkg/envoy/generator/sds/builder_test.go b/pkg/envoy/generator/sds/builder_test.go
index 7cdd7eb7..56f9009e 100644
--- a/pkg/envoy/generator/sds/builder_test.go
+++ b/pkg/envoy/generator/sds/builder_test.go
@@ -11,8 +11,8 @@ import (
 
 	"github.com/openservicemesh/osm/pkg/certificate"
 	"github.com/openservicemesh/osm/pkg/envoy/secrets"
+	"github.com/openservicemesh/osm/pkg/models"
 
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/identity"
 	"github.com/openservicemesh/osm/pkg/service"
 )
@@ -74,7 +74,7 @@ func TestSecretsBuilder(t *testing.T) {
 	for i, tc := range testCases {
 		t.Run(fmt.Sprintf("Testing test case %d: %s", i, tc.name), func(t *testing.T) {
 			builder := NewBuilder()
-			proxy := envoy.NewProxy(envoy.KindSidecar, uuid.New(), identity.New("sa-1", "ns-1"), nil, 1)
+			proxy := models.NewProxy(models.KindSidecar, uuid.New(), identity.New("sa-1", "ns-1"), nil, 1)
 			builder.SetProxy(proxy).SetProxyCert(cert).SetTrustDomain("cluster.local")
 
 			builder.SetServiceIdentitiesForService(tc.serviceIdentitiesForService)
diff --git a/pkg/envoy/generator/sds_test.go b/pkg/envoy/generator/sds_test.go
index fb382f88..e37d3b87 100644
--- a/pkg/envoy/generator/sds_test.go
+++ b/pkg/envoy/generator/sds_test.go
@@ -13,9 +13,9 @@ import (
 
 	"github.com/openservicemesh/osm/pkg/catalog"
 	tresorFake "github.com/openservicemesh/osm/pkg/certificate/providers/tresor/fake"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/envoy/secrets"
 	"github.com/openservicemesh/osm/pkg/identity"
+	"github.com/openservicemesh/osm/pkg/models"
 	"github.com/openservicemesh/osm/pkg/service"
 )
 
@@ -74,7 +74,7 @@ func TestGenerateSDS(t *testing.T) {
 			assert := tassert.New(t)
 			// The Common Name of the xDS Certificate (issued to the Envoy on the Pod by the Injector) will
 			// have be prefixed with the ID of the pod. It is the first chunk of a dot-separated string.
-			proxy := envoy.NewProxy(envoy.KindSidecar, uuid.New(), proxySvcID, nil, 1)
+			proxy := models.NewProxy(models.KindSidecar, uuid.New(), proxySvcID, nil, 1)
 			meshCatalog := catalog.NewMockMeshCataloger(mockCtrl)
 
 			var services []service.MeshService
diff --git a/pkg/envoy/registry/registry_test.go b/pkg/envoy/registry/registry_test.go
index 5241a902..a86921ac 100644
--- a/pkg/envoy/registry/registry_test.go
+++ b/pkg/envoy/registry/registry_test.go
@@ -11,14 +11,14 @@ import (
 
 	tassert "github.com/stretchr/testify/assert"
 
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/identity"
 	"github.com/openservicemesh/osm/pkg/logger"
+	"github.com/openservicemesh/osm/pkg/models"
 )
 
 var _ = Describe("Test catalog proxy register/unregister", func() {
 	proxyRegistry := NewProxyRegistry()
-	proxy := envoy.NewProxy(envoy.KindSidecar, uuid.New(), identity.New("foo", "bar"), nil, 1)
+	proxy := models.NewProxy(models.KindSidecar, uuid.New(), identity.New("foo", "bar"), nil, 1)
 
 	It("Proxy is valid", func() {
 		Expect(proxy).ToNot((BeNil()))
@@ -54,7 +54,7 @@ func TestRegisterUnregister(t *testing.T) {
 	proxyUUID := uuid.New()
 	var i int64
 	for i = 0; i < 10; i++ {
-		proxy := envoy.NewProxy(envoy.KindSidecar, proxyUUID, identity.New("foo", "bar"), nil, i)
+		proxy := models.NewProxy(models.KindSidecar, proxyUUID, identity.New("foo", "bar"), nil, i)
 		assert.Nil(proxyRegistry.GetConnectedProxy(i))
 		proxyRegistry.RegisterProxy(proxy)
 		assert.Equal(proxy, proxyRegistry.GetConnectedProxy(i))
@@ -79,7 +79,7 @@ func BenchmarkRegistryAdd(b *testing.B) {
 		total := 10000
 
 		for j := 0; j < total; j++ {
-			proxy := envoy.NewProxy(envoy.KindSidecar, uuid.New(), identity.New("foo", "bar"), nil, int64(j))
+			proxy := models.NewProxy(models.KindSidecar, uuid.New(), identity.New("foo", "bar"), nil, int64(j))
 			proxyRegistry.RegisterProxy(proxy)
 			proxyRegistry.UnregisterProxy(int64(j))
 		}
@@ -102,7 +102,7 @@ func BenchmarkRegistryGetCount(b *testing.B) {
 	for j := 0; j < total; j++ {
 		go func() {
 			proxyRegistry.RegisterProxy(
-				envoy.NewProxy(envoy.KindSidecar, uuid.New(), identity.New("foo", "bar"), nil, 1))
+				models.NewProxy(models.KindSidecar, uuid.New(), identity.New("foo", "bar"), nil, 1))
 			wg.Done()
 		}()
 	}
diff --git a/pkg/envoy/server/server_test.go b/pkg/envoy/server/server_test.go
index 9766b4c6..a6179739 100644
--- a/pkg/envoy/server/server_test.go
+++ b/pkg/envoy/server/server_test.go
@@ -17,6 +17,7 @@ import (
 	"github.com/openservicemesh/osm/pkg/envoy/generator"
 	"github.com/openservicemesh/osm/pkg/envoy/secrets"
 	"github.com/openservicemesh/osm/pkg/metricsstore"
+	"github.com/openservicemesh/osm/pkg/models"
 	"github.com/openservicemesh/osm/pkg/tests"
 )
 
@@ -27,7 +28,7 @@ func TestADSResponse(t *testing.T) {
 	proxyUUID := uuid.New()
 	proxySvcID := tests.BookstoreServiceIdentity
 
-	proxy := envoy.NewProxy(envoy.KindSidecar, proxyUUID, proxySvcID, nil, 1)
+	proxy := models.NewProxy(models.KindSidecar, proxyUUID, proxySvcID, nil, 1)
 	a.NotNil(proxy)
 
 	provider := compute.NewMockInterface(mockCtrl)
diff --git a/pkg/k8s/client_test.go b/pkg/k8s/client_test.go
index 9963281e..d2db2fac 100644
--- a/pkg/k8s/client_test.go
+++ b/pkg/k8s/client_test.go
@@ -26,7 +26,6 @@ import (
 	configv1alpha2 "github.com/openservicemesh/osm/pkg/apis/config/v1alpha2"
 	policyv1alpha1 "github.com/openservicemesh/osm/pkg/apis/policy/v1alpha1"
 	"github.com/openservicemesh/osm/pkg/constants"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	fakeConfigClient "github.com/openservicemesh/osm/pkg/gen/client/config/clientset/versioned/fake"
 	fakePolicyClient "github.com/openservicemesh/osm/pkg/gen/client/policy/clientset/versioned/fake"
 	"github.com/openservicemesh/osm/pkg/identity"
@@ -856,33 +855,33 @@ func TestGetPodForProxy(t *testing.T) {
 	testCases := []struct {
 		name  string
 		pod   *corev1.Pod
-		proxy *envoy.Proxy
+		proxy *models.Proxy
 		err   error
 	}{
 		{
 			name:  "fails when UUID does not match",
-			proxy: envoy.NewProxy(envoy.KindSidecar, uuid.New(), tests.BookstoreServiceIdentity, nil, 1),
+			proxy: models.NewProxy(models.KindSidecar, uuid.New(), tests.BookstoreServiceIdentity, nil, 1),
 			err:   errDidNotFindPodForUUID,
 		},
 		{
 			name:  "fails when service account does not match certificate",
-			proxy: &envoy.Proxy{UUID: proxyUUID, Identity: identity.New("bad-name", namespace)},
+			proxy: &models.Proxy{UUID: proxyUUID, Identity: identity.New("bad-name", namespace)},
 			err:   errServiceAccountDoesNotMatchProxy,
 		},
 		{
 			name:  "2 pods with same uuid",
-			proxy: envoy.NewProxy(envoy.KindSidecar, someOtherEnvoyUID, tests.BookstoreServiceIdentity, nil, 1),
+			proxy: models.NewProxy(models.KindSidecar, someOtherEnvoyUID, tests.BookstoreServiceIdentity, nil, 1),
 			err:   errMoreThanOnePodForUUID,
 		},
 		{
 			name:  "fails when namespace does not match certificate",
-			proxy: envoy.NewProxy(envoy.KindSidecar, proxyUUID, identity.New(tests.BookstoreServiceAccountName, "bad-namespace"), nil, 1),
+			proxy: models.NewProxy(models.KindSidecar, proxyUUID, identity.New(tests.BookstoreServiceAccountName, "bad-namespace"), nil, 1),
 			err:   errNamespaceDoesNotMatchProxy,
 		},
 		{
 			name:  "works as expected",
 			pod:   pod,
-			proxy: envoy.NewProxy(envoy.KindSidecar, proxyUUID, tests.BookstoreServiceIdentity, nil, 1),
+			proxy: models.NewProxy(models.KindSidecar, proxyUUID, tests.BookstoreServiceIdentity, nil, 1),
 		},
 	}
 
diff --git a/pkg/envoy/proxy_test.go b/pkg/models/proxy_test.go
similarity index 98%
rename from pkg/envoy/proxy_test.go
rename to pkg/models/proxy_test.go
index 50af4fa4..85b24c3b 100644
--- a/pkg/envoy/proxy_test.go
+++ b/pkg/models/proxy_test.go
@@ -1,4 +1,4 @@
-package envoy
+package models
 
 import (
 	"github.com/google/uuid"
diff --git a/pkg/osm/callbacks_test.go b/pkg/osm/callbacks_test.go
index 6f5a8607..5cbd7be3 100644
--- a/pkg/osm/callbacks_test.go
+++ b/pkg/osm/callbacks_test.go
@@ -8,8 +8,8 @@ import (
 	tassert "github.com/stretchr/testify/assert"
 
 	"github.com/openservicemesh/osm/pkg/certificate"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/identity"
+	"github.com/openservicemesh/osm/pkg/models"
 )
 
 func TestGetCertificateCommonNameMeta(t *testing.T) {
@@ -44,14 +44,14 @@ func TestGetCertificateCommonNameMeta(t *testing.T) {
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
 			assert := tassert.New(t)
-			cn := certificate.CommonName(fmt.Sprintf("%s.%s.%s", tc.uuid, envoy.KindSidecar, tc.identity))
+			cn := certificate.CommonName(fmt.Sprintf("%s.%s.%s", tc.uuid, models.KindSidecar, tc.identity))
 
 			kind, uuid, si, err := getCertificateCommonNameMeta(cn)
 
 			assert.Equal(tc.err, err)
 
 			if err == nil {
-				assert.Equal(envoy.KindSidecar, kind)
+				assert.Equal(models.KindSidecar, kind)
 				assert.Equal(tc.uuid, uuid)
 				assert.Equal(tc.identity, si)
 			}
diff --git a/pkg/osm/control_plane_test.go b/pkg/osm/control_plane_test.go
index 46551297..5bc1e9b7 100644
--- a/pkg/osm/control_plane_test.go
+++ b/pkg/osm/control_plane_test.go
@@ -19,10 +19,10 @@ import (
 	"github.com/openservicemesh/osm/pkg/certificate"
 	tresorFake "github.com/openservicemesh/osm/pkg/certificate/providers/tresor/fake"
 	"github.com/openservicemesh/osm/pkg/compute"
-	"github.com/openservicemesh/osm/pkg/envoy"
 	"github.com/openservicemesh/osm/pkg/envoy/registry"
 	"github.com/openservicemesh/osm/pkg/identity"
 	"github.com/openservicemesh/osm/pkg/messaging"
+	"github.com/openservicemesh/osm/pkg/models"
 )
 
 type fakeConfig string
@@ -38,7 +38,7 @@ func (g *fakeGenerator) getCallCount(uuid string) int {
 	return g.callCount[uuid]
 }
 
-func (g *fakeGenerator) GenerateConfig(ctx context.Context, proxy *envoy.Proxy) (fakeConfig, error) {
+func (g *fakeGenerator) GenerateConfig(ctx context.Context, proxy *models.Proxy) (fakeConfig, error) {
 	g.mu.Lock()
 	defer g.mu.Unlock()
 	g.callCount[proxy.UUID.String()]++
@@ -64,7 +64,7 @@ func (s *fakeServer) getCallCount(uuid string) int {
 	return s.callCount[uuid]
 }
 
-func (s *fakeServer) UpdateProxy(ctx context.Context, proxy *envoy.Proxy, config fakeConfig) error {
+func (s *fakeServer) UpdateProxy(ctx context.Context, proxy *models.Proxy, config fakeConfig) error {
 	s.mu.Lock()
 	defer s.mu.Unlock()
 	s.callCount[proxy.UUID.String()]++
@@ -110,12 +110,12 @@ func TestControlLoop(t *testing.T) {
 
 	uuid1 := uuid.New()
 	id1 := identity.New("p2", "ns2")
-	cert1, err := certManager.IssueCertificate(certificate.ForCommonNamePrefix(envoy.NewXDSCertCNPrefix(uuid1, envoy.KindSidecar, id1)))
+	cert1, err := certManager.IssueCertificate(certificate.ForCommonNamePrefix(models.NewXDSCertCNPrefix(uuid1, models.KindSidecar, id1)))
 	tassert.NoError(err)
 
 	uuid2 := uuid.New()
 	id2 := identity.New("p2", "ns2")
-	cert2, err := certManager.IssueCertificate(certificate.ForCommonNamePrefix(envoy.NewXDSCertCNPrefix(uuid2, envoy.KindSidecar, id2)))
+	cert2, err := certManager.IssueCertificate(certificate.ForCommonNamePrefix(models.NewXDSCertCNPrefix(uuid2, models.KindSidecar, id2)))
 	tassert.NoError(err)
 
 	x509Cert1, err := certificate.DecodePEMCertificate(cert1.GetCertificateChain())
diff --git a/pkg/validator/validators_test.go b/pkg/validator/validators_test.go
index 81f7d9be..1be196e8 100644
--- a/pkg/validator/validators_test.go
+++ b/pkg/validator/validators_test.go
@@ -1490,6 +1490,72 @@ func TestMeshRootCertificateValidator(t *testing.T) {
 			},
 			expErrStr: "cannot update trust domain for MRC osm-system/osm-mesh-root-certificate. Create a new MRC and initiate root certificate rotation to update the trust domain",
 		},
+		{
+			name: "MeshRootCertificate with invalid spiffe update",
+			input: &admissionv1.AdmissionRequest{
+				Operation: admissionv1.Update,
+				Kind: metav1.GroupVersionKind{
+					Group:   "configv1alpha2",
+					Version: "config.openservicemesh.io",
+					Kind:    "MeshRootCertificate",
+				},
+				Object: runtime.RawExtension{
+					Raw: []byte(`
+					{
+						"apiVersion": "config.openservicemesh.io/configv1alpha2",
+						"kind": "MeshRootCertificate",
+						"metadata": {
+							"name": "osm-mesh-root-certificate",
+							"namespace": "osm-system"
+						},
+						"spec": {
+							"trustDomain": "oldtrustdomain",
+							"spiffeEnabled": true,
+							"intent": "active",
+							"provider": {
+								"tresor": {
+							 		"ca": {
+										"secretRef": {
+											"name": "osm-ca-bundle",
+											"namespace": "osm-system"
+							  			}
+							 		}
+								}
+							}
+						}
+					}
+					`),
+				},
+				OldObject: runtime.RawExtension{
+					Raw: []byte(`
+					{
+						"apiVersion": "config.openservicemesh.io/configv1alpha2",
+						"kind": "MeshRootCertificate",
+						"metadata": {
+							"name": "osm-mesh-root-certificate",
+							"namespace": "osm-system"
+						},
+						"spec": {
+							"trustDomain": "oldtrustdomain",
+							"intent": "active",
+							"spiffeEnabled": false,
+							"provider": {
+								"tresor": {
+							 		"ca": {
+										"secretRef": {
+											"name": "osm-ca-bundle",
+											"namespace": "osm-system"
+							  			}
+							 		}
+								}
+							}
+						}
+					}
+					`),
+				},
+			},
+			expErrStr: "cannot update SpiffeEnabled for MRC osm-system/osm-mesh-root-certificate. Create a new MRC and initiate root certificate rotation to enable SPIFFE certificates",
+		},
 		{
 			name: "MeshRootCertificate with invalid trust domain on create",
 			input: &admissionv1.AdmissionRequest{
diff --git a/tests/e2e/e2e_certmanager_test.go b/tests/e2e/e2e_certmanager_test.go
index 4fbf50af..e9226c9b 100644
--- a/tests/e2e/e2e_certmanager_test.go
+++ b/tests/e2e/e2e_certmanager_test.go
@@ -20,6 +20,10 @@ var _ = OSMDescribe("1 Client pod -> 1 Server pod test using cert-manager",
 		Context("CertManagerSimpleClientServer", func() {
 			testCertManager()
 		})
+
+		Context("with SPIFFE Enabled", func() {
+			testCertManager(WithSpiffeEnabled())
+		})
 	})
 
 func testCertManager(installOptions ...InstallOsmOpt) {
@@ -138,5 +142,10 @@ func testCertManager(installOptions ...InstallOsmOpt) {
 			Expect(cond).To(BeTrue())
 			time.Sleep(time.Second * 6) // 6 seconds guarantee the certs are rotated.
 		}
+
+		if installOpts.EnableSPIFFE {
+			verifySpiffeIDInPodCert(srcPod)
+			verifySpiffeIDInPodCert(dstPod)
+		}
 	})
 }
diff --git a/tests/e2e/e2e_hashivault_test.go b/tests/e2e/e2e_hashivault_test.go
index 1ddbebd9..262ed19d 100644
--- a/tests/e2e/e2e_hashivault_test.go
+++ b/tests/e2e/e2e_hashivault_test.go
@@ -18,11 +18,15 @@ var _ = OSMDescribe("1 Client pod -> 1 Server pod test using Vault",
 	},
 	func() {
 		Context("HashivaultSimpleClientServer", func() {
-			testHashiCorpVault()
+			testHashCorpVault()
+		})
+
+		Context("with SPIFFE Enabled", func() {
+			testHashCorpVault(WithSpiffeEnabled())
 		})
 	})
 
-func testHashiCorpVault(installOptions ...InstallOsmOpt) {
+func testHashCorpVault(installOptions ...InstallOsmOpt) {
 	var (
 		clientNamespace = framework.RandomNameWithPrefix("client")
 		serverNamespace = framework.RandomNameWithPrefix("server")
@@ -61,7 +65,7 @@ func testHashiCorpVault(installOptions ...InstallOsmOpt) {
 
 		serverServiceAccount, err := Td.CreateServiceAccount(serverNamespace, &serverSvcAccDef)
 		Expect(err).NotTo(HaveOccurred())
-		_, err = Td.CreatePod(serverNamespace, serverPodDef)
+		serverPod, err := Td.CreatePod(serverNamespace, serverPodDef)
 		Expect(err).NotTo(HaveOccurred())
 		serverService, err := Td.CreateService(serverNamespace, serverSvcDef)
 		Expect(err).NotTo(HaveOccurred())
@@ -132,5 +136,10 @@ func testHashiCorpVault(installOptions ...InstallOsmOpt) {
 			return true
 		}, 5 /*consecutive success threshold*/, 60*time.Second /*timeout*/)
 		Expect(cond).To(BeTrue())
+
+		if installOpts.EnableSPIFFE {
+			verifySpiffeIDInPodCert(srcPod)
+			verifySpiffeIDInPodCert(serverPod)
+		}
 	})
 }
diff --git a/tests/e2e/e2e_permissive_smi_switching_test.go b/tests/e2e/e2e_permissive_smi_switching_test.go
index 285454e9..dde12c10 100644
--- a/tests/e2e/e2e_permissive_smi_switching_test.go
+++ b/tests/e2e/e2e_permissive_smi_switching_test.go
@@ -22,11 +22,15 @@ var _ = OSMDescribe("Test HTTP traffic from N deployment client -> 1 deployment
 	},
 	func() {
 		Context("PermissiveToSmiSwitching", func() {
-			testPermissiveToSmiSwitching()
+			testPermissiveModeSwitching()
+		})
+
+		Context("with SPIFFE Enabled", func() {
+			testPermissiveModeSwitching(WithSpiffeEnabled())
 		})
 	})
 
-func testPermissiveToSmiSwitching(installOptions ...InstallOsmOpt) {
+func testPermissiveModeSwitching(installOptions ...InstallOsmOpt) {
 	var (
 		destApp           = framework.RandomNameWithPrefix("server")
 		sourceAppBaseName = framework.RandomNameWithPrefix("client")
@@ -47,7 +51,8 @@ func testPermissiveToSmiSwitching(installOptions ...InstallOsmOpt) {
 
 	It("Tests HTTP traffic from multiple client deployments to a server deployment", func() {
 		// Install OSM
-		Expect(Td.InstallOSM(Td.GetOSMInstallOpts(installOptions...))).To(Succeed())
+		installOptions := Td.GetOSMInstallOpts(installOptions...)
+		Expect(Td.InstallOSM(installOptions)).To(Succeed())
 
 		// Server NS
 		Expect(Td.CreateNs(destApp, nil)).To(Succeed())
@@ -183,6 +188,10 @@ func testPermissiveToSmiSwitching(installOptions ...InstallOsmOpt) {
 		By("Succeeds when disabling Permissive now that SMI rules are in place")
 		Expect(setPermissiveMode(false)).To(BeNil())
 		Expect(trafficTest(true, requests)).To(BeTrue())
+
+		if installOptions.EnableSPIFFE {
+			verifySpiffeIDForDeployment(deploymentDef)
+		}
 	})
 }
 
diff --git a/tests/e2e/e2e_permissive_test.go b/tests/e2e/e2e_permissive_test.go
index 3c65d431..de32bcb5 100644
--- a/tests/e2e/e2e_permissive_test.go
+++ b/tests/e2e/e2e_permissive_test.go
@@ -31,7 +31,7 @@ var _ = OSMDescribe("Permissive Traffic Policy Mode",
 		})
 	})
 
-func testPermissiveMode(withSourceKubernetesService bool) {
+func testPermissiveMode(withSourceKubernetesService bool, installOptions ...InstallOsmOpt) {
 	const sourceNs = "client"
 	const destNs = "server"
 	const extSourceNs = "ext-client"
@@ -39,7 +39,7 @@ func testPermissiveMode(withSourceKubernetesService bool) {
 
 	It("Tests HTTP traffic for client pod -> server pod with permissive mode", func() {
 		// Install OSM
-		installOpts := Td.GetOSMInstallOpts()
+		installOpts := Td.GetOSMInstallOpts(installOptions...)
 		installOpts.EnablePermissiveMode = true
 		Expect(Td.InstallOSM(installOpts)).To(Succeed())
 		meshConfig, _ := Td.GetMeshConfig(Td.OsmNamespace)
diff --git a/tests/e2e/e2e_spiffe_test.go b/tests/e2e/e2e_spiffe_test.go
new file mode 100644
index 00000000..0b5cdcef
--- /dev/null
+++ b/tests/e2e/e2e_spiffe_test.go
@@ -0,0 +1,88 @@
+package e2e
+
+import (
+	"fmt"
+	"path/filepath"
+	"time"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+	appsv1 "k8s.io/api/apps/v1"
+	v1 "k8s.io/api/core/v1"
+
+	"github.com/openservicemesh/osm/tests/framework"
+	. "github.com/openservicemesh/osm/tests/framework"
+)
+
+var _ = OSMDescribe("SPIFFE",
+	OSMDescribeInfo{
+		Tier:   2,
+		Bucket: 10,
+	},
+	func() {
+		Context("with Tressor", func() {
+			testSpiffeCert()
+		})
+	})
+
+func testSpiffeCert() {
+	var testPod = framework.RandomNameWithPrefix("testpod")
+	var meshNs = []string{testPod}
+
+	It("has a Pod Certificate with a SPIFFE ID", func() {
+		By("Installing OSM with SPIFFE Enabled")
+		installOpts := Td.GetOSMInstallOpts(WithSpiffeEnabled())
+		Expect(Td.InstallOSM(installOpts)).To(Succeed())
+
+		// Create test NS in mesh
+		By("Creating Namespace for test")
+		for _, n := range meshNs {
+			Expect(Td.CreateNs(n, nil)).To(Succeed())
+			Expect(Td.AddNsToMesh(true, n)).To(Succeed())
+		}
+
+		// Get simple pod definitions for the HTTP server
+		svcAccDef, podDef, svcDef, err := Td.SimplePodApp(
+			SimplePodAppDef{
+				PodName:   testPod,
+				Namespace: testPod,
+				Image:     "kennethreitz/httpbin",
+				Ports:     []int{80},
+				OS:        Td.ClusterOS,
+			})
+		Expect(err).NotTo(HaveOccurred())
+
+		By("Creating a pod with Service")
+		_, err = Td.CreateServiceAccount(testPod, &svcAccDef)
+		Expect(err).NotTo(HaveOccurred())
+		pod, err := Td.CreatePod(testPod, podDef)
+		Expect(err).NotTo(HaveOccurred())
+		_, err = Td.CreateService(testPod, svcDef)
+		Expect(err).NotTo(HaveOccurred())
+
+		Expect(Td.WaitForPodsRunningReady(testPod, 90*time.Second, 1, nil)).To(Succeed())
+
+		verifySpiffeIDInPodCert(pod)
+	})
+}
+
+func verifySpiffeIDInPodCert(pod *v1.Pod) {
+	By("Verifying pod has SPIFFE cert in URI SAN")
+	args := []string{"proxy", "get", "certs", pod.Name, fmt.Sprintf("-n=%s", pod.Namespace)}
+	stdout, _, err := Td.RunLocal(filepath.FromSlash("../../bin/osm"), args...)
+	Expect(err).NotTo(HaveOccurred())
+
+	Td.T.Logf("stdout:\n%s", stdout)
+	Expect(stdout).Should(ContainSubstring(fmt.Sprintf("\"uri\": \"spiffe://cluster.local/%s/%s", pod.Spec.ServiceAccountName, pod.Namespace)))
+}
+
+func verifySpiffeIDForDeployment(deployment appsv1.Deployment) {
+	By("By getting pods in deployment")
+
+	pods, err := Td.GetPodsForLabel(deployment.Namespace, *deployment.Spec.Selector)
+	Expect(err).To(BeNil())
+
+	for i := range pods {
+		verifySpiffeIDInPodCert(&pods[i])
+	}
+}
diff --git a/tests/framework/common.go b/tests/framework/common.go
index d6ce0103..20ee6264 100644
--- a/tests/framework/common.go
+++ b/tests/framework/common.go
@@ -153,6 +153,7 @@ func registerFlags(td *OsmTestData) {
 	flag.BoolVar(&td.DeployOnOpenShift, "deployOnOpenShift", false, "Configure tests to run on OpenShift")
 	flag.BoolVar(&td.DeployOnWindowsWorkers, "deployOnWindowsWorkers", false, "Configure tests to run on Windows workers")
 	flag.BoolVar(&td.RetryAppPodCreation, "retryAppPodCreation", true, "Retry app pod creation on error")
+	flag.BoolVar(&td.EnableSPIFFE, "enableSPIFFE", false, "Globally Enables SPIFFE IDs when running tests")
 }
 
 // ValidateStringParams validates input string parameters are valid
@@ -353,6 +354,13 @@ func WithLocalProxyMode(mode configv1alpha2.LocalProxyMode) InstallOsmOpt {
 	}
 }
 
+// WithSpiffeEnabled turns on SPIFFE feature flag for OSM
+func WithSpiffeEnabled() InstallOsmOpt {
+	return func(opts *InstallOSMOpts) {
+		opts.EnableSPIFFE = true
+	}
+}
+
 // GetOSMInstallOpts initializes install options for OSM
 func (td *OsmTestData) GetOSMInstallOpts(options ...InstallOsmOpt) InstallOSMOpts {
 	enablePrivilegedInitContainer := false
@@ -392,6 +400,7 @@ func (td *OsmTestData) GetOSMInstallOpts(options ...InstallOsmOpt) InstallOSMOpt
 
 		EnablePrivilegedInitContainer: enablePrivilegedInitContainer,
 		EnableIngressBackendPolicy:    true,
+		EnableSPIFFE:                  td.EnableSPIFFE,
 	}
 
 	for _, opt := range options {
@@ -529,6 +538,13 @@ func (td *OsmTestData) InstallOSM(instOpts InstallOSMOpts) error {
 		fmt.Sprintf("osm.enableReconciler=%v", instOpts.EnableReconciler),
 	)
 
+	if instOpts.EnableSPIFFE {
+		instOpts.SetOverrides = append(instOpts.SetOverrides,
+			fmt.Sprintf("osm.featureFlags.enableSPIFFE=%v", instOpts.EnableSPIFFE),
+			"osm.featureFlags.enableMeshRootCertificate=true",
+		)
+	}
+
 	if instOpts.LocalProxyMode != "" {
 		instOpts.SetOverrides = append(instOpts.SetOverrides, fmt.Sprintf("osm.localProxyMode=%s", instOpts.LocalProxyMode))
 	}
@@ -740,7 +756,7 @@ vault secrets tune -max-lease-ttl=87700h pki;
 vault write pki/config/urls issuing_certificates='http://127.0.0.1:8200/v1/pki/ca' crl_distribution_points='http://127.0.0.1:8200/v1/pki/crl';
 
 # Configure a role for OSM (See: https://www.vaultproject.io/docs/secrets/pki#configure-a-role)
-vault write pki/roles/%s allow_any_name=true allow_subdomains=true max_ttl=87700h;
+vault write pki/roles/%s allow_any_name=true allow_subdomains=true max_ttl=87700h allowed_uri_sans=spiffe://*;
 
 # Create the root certificate (See: https://www.vaultproject.io/docs/secrets/pki#setup)
 vault write pki/root/generate/internal common_name='osm.root' ttl='87700h';
diff --git a/tests/framework/types.go b/tests/framework/types.go
index c761fbaa..4284a240 100644
--- a/tests/framework/types.go
+++ b/tests/framework/types.go
@@ -91,6 +91,7 @@ type OsmTestData struct {
 	DeployOnWindowsWorkers bool // Determines whether to configure tests to run on Windows workers
 
 	RetryAppPodCreation bool // Whether to retry app pod creation due to issue #3973
+	EnableSPIFFE        bool
 }
 
 // InstallOSMOpts describes install options for OSM
@@ -132,6 +133,7 @@ type InstallOSMOpts struct {
 	EnablePrivilegedInitContainer bool
 	EnableIngressBackendPolicy    bool
 	EnableRetryPolicy             bool
+	EnableSPIFFE                  bool
 }
 
 // InstallOsmOpt is a function type for setting install options