Repository: kumahq/kuma
Pull request #1603 (2021-02-23 13:03:36)

feat(kuma-cp) global secrets
# Summary
Introducing the concept of Global Scoped Secret.

Until now, every secret was a part of a mesh. Now the secret can be either global scoped or mesh scoped.
Global scoped secret cannot be used as mesh scoped (ie. you cannot create a global scoped secret with CA and use it across many meshes).

# API
GET /global-secrets/{name} to list single global secret
PUT /global-secrets/{name} to put single global secret
DELETE /global-secrets/{name} to delete single global secret

# kumactl
kumactl get global-secrets
kumactl get global-secret sample-global-secret
kumactl delete global-secret sample-global-secret

No breaking changes.

# Documentation

- [X] https://github.com/kumahq/kuma-website/pull/375



Patch:
diff --git a/app/kumactl/cmd/get/get.go b/app/kumactl/cmd/get/get.go
index 344d166ad..5f1459557 100644
--- a/app/kumactl/cmd/get/get.go
+++ b/app/kumactl/cmd/get/get.go
@@ -34,6 +34,7 @@ func NewGetCmd(pctx *kumactl_cmd.RootContext) *cobra.Command {
 	cmd.AddCommand(WithPaginationArgs(NewGetResourcesCmd(pctx, "retries", core_mesh.RetryType, BasicResourceTablePrinter), &pctx.ListContext))
 	cmd.AddCommand(WithPaginationArgs(NewGetResourcesCmd(pctx, "timeouts", core_mesh.TimeoutType, BasicResourceTablePrinter), &pctx.ListContext))
 	cmd.AddCommand(NewGetResourcesCmd(pctx, "secrets", core_system.SecretType, BasicResourceTablePrinter))
+	cmd.AddCommand(NewGetResourcesCmd(pctx, "global-secrets", core_system.GlobalSecretType, BasicGlobalResourceTablePrinter))
 	cmd.AddCommand(WithPaginationArgs(NewGetResourcesCmd(pctx, "zones", core_system.ZoneType, printZones), &pctx.ListContext))
 
 	cmd.AddCommand(NewGetResourceCmd(pctx, "mesh", core_mesh.MeshType, printMeshes))
@@ -50,6 +51,7 @@ func NewGetCmd(pctx *kumactl_cmd.RootContext) *cobra.Command {
 	cmd.AddCommand(NewGetResourceCmd(pctx, "retry", core_mesh.RetryType, BasicResourceTablePrinter))
 	cmd.AddCommand(NewGetResourceCmd(pctx, "timeout", core_mesh.TimeoutType, BasicResourceTablePrinter))
 	cmd.AddCommand(NewGetResourceCmd(pctx, "secret", core_system.SecretType, BasicResourceTablePrinter))
+	cmd.AddCommand(NewGetResourceCmd(pctx, "global-secret", core_system.GlobalSecretType, BasicGlobalResourceTablePrinter))
 	cmd.AddCommand(NewGetResourceCmd(pctx, "zone", core_mesh.RetryType, printZones))
 	return cmd
 }
diff --git a/app/kumactl/cmd/get/table_printer.go b/app/kumactl/cmd/get/table_printer.go
index 0b40cc1e3..e80a2268f 100644
--- a/app/kumactl/cmd/get/table_printer.go
+++ b/app/kumactl/cmd/get/table_printer.go
@@ -35,3 +35,27 @@ func BasicResourceTablePrinter(rootTime time.Time, resources model.ResourceList,
 	}
 	return printers.NewTablePrinter().Print(data, out)
 }
+
+func BasicGlobalResourceTablePrinter(rootTime time.Time, resources model.ResourceList, out io.Writer) error {
+	items := resources.GetItems()
+	data := printers.Table{
+		Headers: []string{"NAME", "AGE"},
+		NextRow: func() func() []string {
+			i := 0
+			return func() []string {
+				defer func() { i++ }()
+				if len(items) <= i {
+					return nil
+				}
+				item := items[i]
+
+				return []string{
+					item.GetMeta().GetName(), // NAME
+					table.TimeSince(item.GetMeta().GetModificationTime(), rootTime), // AGE
+				}
+			}
+		}(),
+		Footer: table.PaginationFooter(resources),
+	}
+	return printers.NewTablePrinter().Print(data, out)
+}
diff --git a/app/kumactl/cmd/install/context/install_control_plane_context.go b/app/kumactl/cmd/install/context/install_control_plane_context.go
index 36c69ddd2..231abcfd6 100644
--- a/app/kumactl/cmd/install/context/install_control_plane_context.go
+++ b/app/kumactl/cmd/install/context/install_control_plane_context.go
@@ -57,7 +57,7 @@ type ImageEnvSecret struct {
 
 type InstallCpContext struct {
 	Args                   InstallControlPlaneArgs
-	InstallCpTemplateFiles func(InstallControlPlaneArgs) (data.FileList, error)
+	InstallCpTemplateFiles func(*InstallControlPlaneArgs) (data.FileList, error)
 	NewSelfSignedCert      func(commonName string, certType tls.CertType, hosts ...string) (tls.KeyPair, error)
 	// When Kuma chart is embedded into other chart all the values need to have a prefix. You can set this prefix with this var.
 	HELMValuesPrefix string
@@ -97,7 +97,7 @@ func DefaultInstallCpContext() InstallCpContext {
 			Ingress_service_type:                      "LoadBalancer",
 		},
 		NewSelfSignedCert: tls.NewSelfSignedCert,
-		InstallCpTemplateFiles: func(args InstallControlPlaneArgs) (data.FileList, error) {
+		InstallCpTemplateFiles: func(args *InstallControlPlaneArgs) (data.FileList, error) {
 			return data.ReadFiles(controlplane.HelmTemplates)
 		},
 		HELMValuesPrefix: "",
diff --git a/app/kumactl/cmd/install/install_control_plane.go b/app/kumactl/cmd/install/install_control_plane.go
index 6ae7462ed..c45e36128 100644
--- a/app/kumactl/cmd/install/install_control_plane.go
+++ b/app/kumactl/cmd/install/install_control_plane.go
@@ -47,7 +47,7 @@ This command requires that the KUBECONFIG environment is set`,
 				}
 			}
 
-			templateFiles, err := ctx.InstallCpTemplateFiles(args)
+			templateFiles, err := ctx.InstallCpTemplateFiles(&args)
 			if err != nil {
 				return errors.Wrap(err, "Failed to read template files")
 			}
diff --git a/app/kumactl/pkg/cmd/root_context.go b/app/kumactl/pkg/cmd/root_context.go
index 1704fea17..9c694b0be 100644
--- a/app/kumactl/pkg/cmd/root_context.go
+++ b/app/kumactl/pkg/cmd/root_context.go
@@ -79,6 +79,7 @@ func DefaultRootContext() *RootContext {
 			"circuit-breaker":    core_mesh.CircuitBreakerType,
 			"retry":              core_mesh.RetryType,
 			"secret":             system.SecretType,
+			"global-secret":      system.GlobalSecretType,
 			"zone":               system.ZoneType,
 		},
 		InstallCpContext:      install_context.DefaultInstallCpContext(),
diff --git a/docs/cmd/kumactl/HELP.md b/docs/cmd/kumactl/HELP.md
index cdb7f58fa..678f34ed2 100644
--- a/docs/cmd/kumactl/HELP.md
+++ b/docs/cmd/kumactl/HELP.md
@@ -409,6 +409,8 @@ Available Commands:
   external-services   Show ExternalService
   fault-injection     Show a single FaultInjection resource
   fault-injections    Show FaultInjection
+  global-secret       Show a single GlobalSecret resource
+  global-secrets      Show GlobalSecret
   healthcheck         Show a single HealthCheck resource
   healthchecks        Show HealthCheck
   mesh                Show a single Mesh resource
diff --git a/pkg/api-server/definitions/all.go b/pkg/api-server/definitions/all.go
index e1c0c26a1..e024df621 100644
--- a/pkg/api-server/definitions/all.go
+++ b/pkg/api-server/definitions/all.go
@@ -22,6 +22,7 @@ var DefaultCRUDLEndpoints = []ResourceWsDefinition{
 	ZoneWsDefinition,
 	ZoneInsightWsDefinition,
 	SecretWsDefinition,
+	GlobalSecretWsDefinition,
 	RetryWsDefinition,
 	TimeoutWsDefinition,
 }
diff --git a/pkg/api-server/definitions/global_secret.go b/pkg/api-server/definitions/global_secret.go
new file mode 100644
index 000000000..fc82ede5d
--- /dev/null
+++ b/pkg/api-server/definitions/global_secret.go
@@ -0,0 +1,18 @@
+package definitions
+
+import (
+	"github.com/kumahq/kuma/pkg/core/resources/apis/system"
+	"github.com/kumahq/kuma/pkg/core/resources/model"
+)
+
+var GlobalSecretWsDefinition = ResourceWsDefinition{
+	Name: "GlobalSecret",
+	Path: "global-secrets",
+	ResourceFactory: func() model.Resource {
+		return system.NewGlobalSecretResource()
+	},
+	ResourceListFactory: func() model.ResourceList {
+		return &system.GlobalSecretResourceList{}
+	},
+	Admin: true,
+}
diff --git a/pkg/core/bootstrap/bootstrap.go b/pkg/core/bootstrap/bootstrap.go
index ca8b58e06..6c04f7e5e 100644
--- a/pkg/core/bootstrap/bootstrap.go
+++ b/pkg/core/bootstrap/bootstrap.go
@@ -327,6 +327,7 @@ func initializeResourceManager(cfg kuma_cp.Config, builder *core_runtime.Builder
 	}
 	secretManager := secret_manager.NewSecretManager(builder.SecretStore(), cipher, secretValidator)
 	customManagers[system.SecretType] = secretManager
+	customManagers[system.GlobalSecretType] = secret_manager.NewGlobalSecretManager(builder.SecretStore(), cipher)
 
 	builder.WithResourceManager(customizableManager)
 
diff --git a/pkg/core/resources/apis/system/global_secret.go b/pkg/core/resources/apis/system/global_secret.go
new file mode 100644
index 000000000..42bcd86d0
--- /dev/null
+++ b/pkg/core/resources/apis/system/global_secret.go
@@ -0,0 +1,91 @@
+package system
+
+import (
+	"errors"
+
+	system_proto "github.com/kumahq/kuma/api/system/v1alpha1"
+	"github.com/kumahq/kuma/pkg/core/resources/model"
+	"github.com/kumahq/kuma/pkg/core/resources/registry"
+)
+
+const (
+	GlobalSecretType model.ResourceType = "GlobalSecret"
+)
+
+var _ model.Resource = &GlobalSecretResource{}
+
+type GlobalSecretResource struct {
+	Meta model.ResourceMeta
+	Spec *system_proto.Secret
+}
+
+func NewGlobalSecretResource() *GlobalSecretResource {
+	return &GlobalSecretResource{
+		Spec: &system_proto.Secret{},
+	}
+}
+
+func (t *GlobalSecretResource) GetType() model.ResourceType {
+	return GlobalSecretType
+}
+func (t *GlobalSecretResource) GetMeta() model.ResourceMeta {
+	return t.Meta
+}
+func (t *GlobalSecretResource) SetMeta(m model.ResourceMeta) {
+	t.Meta = m
+}
+func (t *GlobalSecretResource) GetSpec() model.ResourceSpec {
+	return t.Spec
+}
+func (t *GlobalSecretResource) SetSpec(spec model.ResourceSpec) error {
+	value, ok := spec.(*system_proto.Secret)
+	if !ok {
+		return errors.New("invalid type of spec")
+	} else {
+		t.Spec = value
+		return nil
+	}
+}
+func (t *GlobalSecretResource) Validate() error {
+	return nil
+}
+func (t *GlobalSecretResource) Scope() model.ResourceScope {
+	return model.ScopeGlobal
+}
+
+var _ model.ResourceList = &GlobalSecretResourceList{}
+
+type GlobalSecretResourceList struct {
+	Items      []*GlobalSecretResource
+	Pagination model.Pagination
+}
+
+func (l *GlobalSecretResourceList) GetItems() []model.Resource {
+	res := make([]model.Resource, len(l.Items))
+	for i, elem := range l.Items {
+		res[i] = elem
+	}
+	return res
+}
+func (l *GlobalSecretResourceList) GetItemType() model.ResourceType {
+	return GlobalSecretType
+}
+func (l *GlobalSecretResourceList) NewItem() model.Resource {
+	return NewGlobalSecretResource()
+}
+func (l *GlobalSecretResourceList) AddItem(r model.Resource) error {
+	if trr, ok := r.(*GlobalSecretResource); ok {
+		l.Items = append(l.Items, trr)
+		return nil
+	} else {
+		return model.ErrorInvalidItemType((*GlobalSecretResource)(nil), r)
+	}
+}
+func (l *GlobalSecretResourceList) GetPagination() *model.Pagination {
+	return &l.Pagination
+}
+
+func init() {
+	registry.RegisterType(NewGlobalSecretResource())
+	registry.RegistryListType(&GlobalSecretResourceList{})
+}
diff --git a/pkg/core/secrets/manager/global_manager.go b/pkg/core/secrets/manager/global_manager.go
new file mode 100644
index 000000000..c13680369
--- /dev/null
+++ b/pkg/core/secrets/manager/global_manager.go
@@ -0,0 +1,126 @@
+package manager
+
+import (
+	"context"
+	"time"
+
+	"github.com/kumahq/kuma/pkg/core/resources/manager"
+
+	secret_model "github.com/kumahq/kuma/pkg/core/resources/apis/system"
+	"github.com/kumahq/kuma/pkg/core/resources/model"
+	core_store "github.com/kumahq/kuma/pkg/core/resources/store"
+	secret_cipher "github.com/kumahq/kuma/pkg/core/secrets/cipher"
+	secret_store "github.com/kumahq/kuma/pkg/core/secrets/store"
+)
+
+func NewGlobalSecretManager(secretStore secret_store.SecretStore, cipher secret_cipher.Cipher) manager.ResourceManager {
+	return &globalSecretManager{
+		secretStore: secretStore,
+		cipher:      cipher,
+	}
+}
+
+var _ manager.ResourceManager = &globalSecretManager{}
+
+type globalSecretManager struct {
+	secretStore secret_store.SecretStore
+	cipher      secret_cipher.Cipher
+}
+
+func (s *globalSecretManager) Get(ctx context.Context, resource model.Resource, fs ...core_store.GetOptionsFunc) error {
+	secret, ok := resource.(*secret_model.GlobalSecretResource)
+	if !ok {
+		return newInvalidTypeError()
+	}
+	if err := s.secretStore.Get(ctx, secret, fs...); err != nil {
+		return err
+	}
+	return s.decrypt(secret)
+}
+
+func (s *globalSecretManager) List(ctx context.Context, resources model.ResourceList, fs ...core_store.ListOptionsFunc) error {
+	secrets, ok := resources.(*secret_model.GlobalSecretResourceList)
+	if !ok {
+		return newInvalidTypeError()
+	}
+	if err := s.secretStore.List(ctx, secrets, fs...); err != nil {
+		return err
+	}
+	for _, secret := range secrets.Items {
+		if err := s.decrypt(secret); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (s *globalSecretManager) Create(ctx context.Context, resource model.Resource, fs ...core_store.CreateOptionsFunc) error {
+	secret, ok := resource.(*secret_model.GlobalSecretResource)
+	if !ok {
+		return newInvalidTypeError()
+	}
+	if err := s.encrypt(secret); err != nil {
+		return err
+	}
+	if err := s.secretStore.Create(ctx, secret, append(fs, core_store.CreatedAt(time.Now()))...); err != nil {
+		return err
+	}
+	return s.decrypt(secret)
+}
+
+func (s *globalSecretManager) Update(ctx context.Context, resource model.Resource, fs ...core_store.UpdateOptionsFunc) error {
+	secret, ok := resource.(*secret_model.GlobalSecretResource)
+	if !ok {
+		return newInvalidTypeError()
+	}
+	if err := s.encrypt(secret); err != nil {
+		return err
+	}
+	if err := s.secretStore.Update(ctx, secret, append(fs, core_store.ModifiedAt(time.Now()))...); err != nil {
+		return err
+	}
+	return s.decrypt(secret)
+}
+
+func (s *globalSecretManager) Delete(ctx context.Context, resource model.Resource, fs ...core_store.DeleteOptionsFunc) error {
+	secret, ok := resource.(*secret_model.GlobalSecretResource)
+	if !ok {
+		return newInvalidTypeError()
+	}
+	return s.secretStore.Delete(ctx, secret, fs...)
+}
+
+func (s *globalSecretManager) DeleteAll(ctx context.Context, secrets model.ResourceList, fs ...core_store.DeleteAllOptionsFunc) error {
+	list := &secret_model.SecretResourceList{}
+	if err := s.secretStore.List(context.Background(), list); err != nil {
+		return err
+	}
+	for _, item := range list.Items {
+		if err := s.Delete(ctx, item, core_store.DeleteBy(model.MetaToResourceKey(item.Meta))); err != nil && !core_store.IsResourceNotFound(err) {
+			return err
+		}
+	}
+	return nil
+}
+
+func (s *globalSecretManager) encrypt(secret *secret_model.GlobalSecretResource) error {
+	if len(secret.Spec.GetData().GetValue()) > 0 {
+		value, err := s.cipher.Encrypt(secret.Spec.Data.Value)
+		if err != nil {
+			return err
+		}
+		secret.Spec.Data.Value = value
+	}
+	return nil
+}
+
+func (s *globalSecretManager) decrypt(secret *secret_model.GlobalSecretResource) error {
+	if len(secret.Spec.GetData().GetValue()) > 0 {
+		value, err := s.cipher.Decrypt(secret.Spec.Data.Value)
+		if err != nil {
+			return err
+		}
+		secret.Spec.Data.Value = value
+	}
+	return nil
+}
diff --git a/pkg/plugins/common/k8s/names.go b/pkg/plugins/common/k8s/names.go
index 709f79168..dd30204c9 100644
--- a/pkg/plugins/common/k8s/names.go
+++ b/pkg/plugins/common/k8s/names.go
@@ -17,6 +17,12 @@ const (
 
 	// k8sSynced identifies that resource was synced
 	K8sSynced = "k8s.kuma.io/synced"
+
+	// Kubernetes secret type to differentiate Kuma System secrets. Secret is bound to a mesh
+	MeshSecretType = "system.kuma.io/secret"
+
+	// Kubernetes secret type to differentiate Kuma System secrets. Secret is bound to a control plane
+	GlobalSecretType = "system.kuma.io/global-secret"
 )
 
 func ResourceNameExtensions(namespace, name string) core_model.ResourceNameExtensions {
diff --git a/pkg/plugins/runtime/k8s/webhooks/secret_validator.go b/pkg/plugins/runtime/k8s/webhooks/secret_validator.go
index abf4daad9..c1ac38893 100644
--- a/pkg/plugins/runtime/k8s/webhooks/secret_validator.go
+++ b/pkg/plugins/runtime/k8s/webhooks/secret_validator.go
@@ -8,6 +8,7 @@ import (
 
 	secret_manager "github.com/kumahq/kuma/pkg/core/secrets/manager"
 	"github.com/kumahq/kuma/pkg/core/validators"
+	common_k8s "github.com/kumahq/kuma/pkg/plugins/common/k8s"
 	mesh_k8s "github.com/kumahq/kuma/pkg/plugins/resources/k8s/native/api/v1alpha1"
 
 	admissionv1beta1 "k8s.io/api/admission/v1beta1"
@@ -74,7 +75,7 @@ func (v *SecretValidator) handleDelete(ctx context.Context, req admission.Reques
 		}
 		return admission.Errored(http.StatusBadRequest, err)
 	}
-	if isKumaSecret(secret) {
+	if secret.Type == common_k8s.MeshSecretType {
 		if err := v.Validator.ValidateDelete(ctx, req.Name, meshOfSecret(secret)); err != nil {
 			if verr, ok := err.(*validators.ValidationError); ok {
 				return convertValidationErrorOf(*verr, secret, secret)
@@ -87,10 +88,20 @@ func (v *SecretValidator) handleDelete(ctx context.Context, req admission.Reques
 
 func (v *SecretValidator) validate(ctx context.Context, secret *kube_core.Secret, oldSecret *kube_core.Secret) error {
 	verr := &validators.ValidationError{}
-	if !isKumaSecret(secret) {
-		return nil
+
+	switch secret.Type {
+	case common_k8s.MeshSecretType:
+		if err := v.validateMeshSecret(ctx, verr, secret, oldSecret); err != nil {
+			return err
+		}
+	case common_k8s.GlobalSecretType:
+		v.validateGlobalSecret(verr, secret)
 	}
 
+	return verr.OrNil()
+}
+
+func (v *SecretValidator) validateMeshSecret(ctx context.Context, verr *validators.ValidationError, secret *kube_core.Secret, oldSecret *kube_core.Secret) error {
 	// validate mesh exists
 	mesh := mesh_k8s.Mesh{}
 	key := kube_types.NamespacedName{
@@ -110,16 +121,22 @@ func (v *SecretValidator) validate(ctx context.Context, secret *kube_core.Secret
 			verr.AddViolationAt(validators.RootedAt("metadata").Field("labels").Key(meshLabel), "cannot change mesh of the Secret. Delete the Secret first and apply it again.")
 		}
 	}
+	v.validateSecretData(verr, secret)
+	return nil
+}
 
+func (v *SecretValidator) validateGlobalSecret(verr *validators.ValidationError, secret *kube_core.Secret) {
+	if _, ok := secret.GetLabels()[meshLabel]; ok {
+		verr.AddViolationAt(validators.RootedAt("metadata").Field("labels").Key(meshLabel), "mesh cannot be set on global secret")
+	}
+	v.validateSecretData(verr, secret)
+}
+
+func (v *SecretValidator) validateSecretData(verr *validators.ValidationError, secret *kube_core.Secret) {
 	// validate convention of the secret
 	if len(secret.Data["value"]) == 0 {
 		verr.AddViolationAt(validators.RootedAt("data").Field("value"), "cannot be empty.")
 	}
-	return verr.OrNil()
-}
-
-func isKumaSecret(secret *kube_core.Secret) bool {
-	return secret.Type == "system.kuma.io/secret"
 }
 
 func meshOfSecret(secret *kube_core.Secret) string {
diff --git a/pkg/plugins/secrets/k8s/store.go b/pkg/plugins/secrets/k8s/store.go
index 344d60062..c9040efe4 100644
--- a/pkg/plugins/secrets/k8s/store.go
+++ b/pkg/plugins/secrets/k8s/store.go
@@ -24,8 +24,6 @@ import (
 const (
 	// Every Kuma Secret will be annotated with kuma.io/mesh to be able to expose secrets only for given mesh
 	meshLabel = "kuma.io/mesh"
-	// Kubernetes secret type to differentiate Kuma System secrets
-	secretType = "system.kuma.io/secret"
 )
 
 var _ secret_store.SecretStore = &KubernetesStore{}
@@ -48,21 +46,19 @@ func NewStore(reader kube_client.Reader, writer kube_client.Writer, namespace st
 }
 
 func (s *KubernetesStore) Create(ctx context.Context, r core_model.Resource, fs ...core_store.CreateOptionsFunc) error {
-	secretRes, ok := r.(*secret_model.SecretResource)
-	if !ok {
-		return newInvalidTypeError()
-	}
 	opts := core_store.NewCreateOptions(fs...)
-	secret, err := s.converter.ToKubernetesObject(secretRes)
+	secret, err := s.converter.ToKubernetesObject(r)
 	if err != nil {
 		return errors.Wrap(err, "failed to convert core Secret into k8s counterpart")
 	}
 	secret.Namespace = s.namespace
 	secret.Name = opts.Name
-	labels := map[string]string{
-		meshLabel: opts.Mesh,
+	if r.GetType() == secret_model.SecretType {
+		labels := map[string]string{
+			meshLabel: opts.Mesh,
+		}
+		secret.SetLabels(labels)
 	}
-	secret.SetLabels(labels)
 
 	if err := s.writer.Create(ctx, secret); err != nil {
 		if kube_apierrs.IsAlreadyExists(err) {
@@ -70,18 +66,14 @@ func (s *KubernetesStore) Create(ctx context.Context, r core_model.Resource, fs
 		}
 		return errors.Wrap(err, "failed to create k8s Secret")
 	}
-	err = s.converter.ToCoreResource(secret, secretRes)
+	err = s.converter.ToCoreResource(secret, r)
 	if err != nil {
 		return errors.Wrap(err, "failed to convert k8s Secret into core counterpart")
 	}
 	return nil
 }
 func (s *KubernetesStore) Update(ctx context.Context, r core_model.Resource, fs ...core_store.UpdateOptionsFunc) error {
-	secretRes, ok := r.(*secret_model.SecretResource)
-	if !ok {
-		return newInvalidTypeError()
-	}
-	secret, err := s.converter.ToKubernetesObject(secretRes)
+	secret, err := s.converter.ToKubernetesObject(r)
 	if err != nil {
 		return errors.Wrap(err, "failed to convert core Secret into k8s counterpart")
 	}
@@ -92,19 +84,19 @@ func (s *KubernetesStore) Update(ctx context.Context, r core_model.Resource, fs
 		}
 		return errors.Wrap(err, "failed to update k8s Secret")
 	}
-	err = s.converter.ToCoreResource(secret, secretRes)
+	err = s.converter.ToCoreResource(secret, r)
 	if err != nil {
 		return errors.Wrap(err, "failed to convert k8s Secret into core counterpart")
 	}
 	return nil
 }
 func (s *KubernetesStore) Delete(ctx context.Context, r core_model.Resource, fs ...core_store.DeleteOptionsFunc) error {
-	secretRes, ok := r.(*secret_model.SecretResource)
-	if !ok {
-		return newInvalidTypeError()
-	}
 	opts := core_store.NewDeleteOptions(fs...)
-	secret, err := s.converter.ToKubernetesObject(secretRes)
+	if err := s.Get(context.Background(), r, core_store.GetByKey(opts.Name, opts.Mesh)); err != nil {
+		return errors.Wrap(err, "failed to delete k8s secret")
+	}
+
+	secret, err := s.converter.ToKubernetesObject(r)
 	if err != nil {
 		return errors.Wrap(err, "failed to convert core Secret into k8s counterpart")
 	}
@@ -112,18 +104,11 @@ func (s *KubernetesStore) Delete(ctx context.Context, r core_model.Resource, fs
 	secret.Name = opts.Name
 
 	if err := s.writer.Delete(ctx, secret); err != nil {
-		if kube_apierrs.IsNotFound(err) {
-			return nil
-		}
 		return errors.Wrap(err, "failed to delete k8s Secret")
 	}
 	return nil
 }
 func (s *KubernetesStore) Get(ctx context.Context, r core_model.Resource, fs ...core_store.GetOptionsFunc) error {
-	secretRes, ok := r.(*secret_model.SecretResource)
-	if !ok {
-		return newInvalidTypeError()
-	}
 	opts := core_store.NewGetOptions(fs...)
 	secret := &kube_core.Secret{}
 	if err := s.reader.Get(ctx, kube_client.ObjectKey{Namespace: s.namespace, Name: opts.Name}, secret); err != nil {
@@ -132,36 +117,60 @@ func (s *KubernetesStore) Get(ctx context.Context, r core_model.Resource, fs ...
 		}
 		return errors.Wrap(err, "failed to get k8s secret")
 	}
-	if err := s.converter.ToCoreResource(secret, secretRes); err != nil {
+	if err := s.converter.ToCoreResource(secret, r); err != nil {
 		return errors.Wrap(err, "failed to convert k8s Secret into core counterpart")
 	}
-	if r.GetMeta().GetMesh() != opts.Mesh {
-		if err := r.SetSpec(&system_proto.Secret{}); err != nil {
-			return err
+	if err := assertFound(r, secret, opts.Name, opts.Mesh); err != nil {
+		return err
+	}
+	return nil
+}
+
+func assertFound(r core_model.Resource, secret *kube_core.Secret, name string, mesh string) error {
+	switch r.GetType() {
+	case secret_model.SecretType:
+		// secret must match mesh and be a proper type, otherwise return not found
+		if r.GetMeta().GetMesh() != mesh || secret.Type != common_k8s.MeshSecretType {
+			if err := r.SetSpec(&system_proto.Secret{}); err != nil {
+				return err
+			}
+			return core_store.ErrorResourceNotFound(r.GetType(), name, mesh)
+		}
+	case secret_model.GlobalSecretType:
+		// secret must be a proper type, otherwise return not found
+		if secret.Type != common_k8s.GlobalSecretType {
+			if err := r.SetSpec(&system_proto.Secret{}); err != nil {
+				return err
+			}
+			return core_store.ErrorResourceNotFound(r.GetType(), name, mesh)
 		}
-		return core_store.ErrorResourceNotFound(r.GetType(), opts.Name, opts.Mesh)
 	}
 	return nil
 }
+
 func (s *KubernetesStore) List(ctx context.Context, rs core_model.ResourceList, fs ...core_store.ListOptionsFunc) error {
-	secretsRes, ok := rs.(*secret_model.SecretResourceList)
-	if !ok {
-		return newInvalidTypeError()
-	}
 	opts := core_store.NewListOptions(fs...)
 	secrets := &kube_core.SecretList{}
 
-	fields := kube_client.MatchingFields{ // list only Kuma System secrets
-		"type": secretType,
-	}
+	fields := kube_client.MatchingFields{} // list only Kuma System secrets
 	labels := kube_client.MatchingLabels{}
-	if opts.Mesh != "" {
-		labels[meshLabel] = opts.Mesh
+	switch rs.GetItemType() {
+	case secret_model.SecretType:
+		fields = kube_client.MatchingFields{ // list only Kuma System secrets
+			"type": common_k8s.MeshSecretType,
+		}
+		if opts.Mesh != "" {
+			labels[meshLabel] = opts.Mesh
+		}
+	case secret_model.GlobalSecretType:
+		fields = kube_client.MatchingFields{ // list only Kuma System secrets
+			"type": common_k8s.GlobalSecretType,
+		}
 	}
 	if err := s.reader.List(ctx, secrets, kube_client.InNamespace(s.namespace), labels, fields); err != nil {
 		return errors.Wrap(err, "failed to list k8s Secrets")
 	}
-	if err := s.converter.ToCoreList(secrets, secretsRes); err != nil {
+	if err := s.converter.ToCoreList(secrets, rs); err != nil {
 		return errors.Wrap(err, "failed to convert k8s Secret into core counterpart")
 	}
 	return nil
@@ -171,6 +180,7 @@ var _ core_model.ResourceMeta = &KubernetesMetaAdapter{}
 
 type KubernetesMetaAdapter struct {
 	kube_meta.ObjectMeta
+	SecretType kube_core.SecretType
 }
 
 func (m *KubernetesMetaAdapter) GetNameExtensions() core_model.ResourceNameExtensions {
@@ -182,6 +192,9 @@ func (m *KubernetesMetaAdapter) GetVersion() string {
 }
 
 func (m *KubernetesMetaAdapter) GetMesh() string {
+	if m.SecretType == common_k8s.GlobalSecretType {
+		return ""
+	}
 	mesh, exist := m.Labels[meshLabel]
 	if !exist {
 		mesh = core_model.DefaultMesh
@@ -198,9 +211,9 @@ func (m *KubernetesMetaAdapter) GetModificationTime() time.Time {
 }
 
 type Converter interface {
-	ToKubernetesObject(*secret_model.SecretResource) (*kube_core.Secret, error)
-	ToCoreResource(secret *kube_core.Secret, out *secret_model.SecretResource) error
-	ToCoreList(list *kube_core.SecretList, out *secret_model.SecretResourceList) error
+	ToKubernetesObject(resource core_model.Resource) (*kube_core.Secret, error)
+	ToCoreResource(secret *kube_core.Secret, out core_model.Resource) error
+	ToCoreList(list *kube_core.SecretList, out core_model.ResourceList) error
 }
 
 func DefaultConverter() Converter {
@@ -212,19 +225,31 @@ var _ Converter = &SimpleConverter{}
 type SimpleConverter struct {
 }
 
-func (c *SimpleConverter) ToKubernetesObject(r *secret_model.SecretResource) (*kube_core.Secret, error) {
+func (c *SimpleConverter) ToKubernetesObject(r core_model.Resource) (*kube_core.Secret, error) {
 	secret := &kube_core.Secret{}
-	secret.Type = secretType
-	secret.Data = map[string][]byte{
-		"value": r.Spec.GetData().GetValue(),
-	}
-	if r.GetMeta() != nil {
-		if adapter, ok := r.GetMeta().(*KubernetesMetaAdapter); ok {
-			secret.ObjectMeta = adapter.ObjectMeta
+	switch r.GetType() {
+	case secret_model.SecretType:
+		secret.Type = common_k8s.MeshSecretType
+		secret.Data = map[string][]byte{
+			"value": r.(*secret_model.SecretResource).Spec.GetData().GetValue(),
+		}
+		if r.GetMeta() != nil {
 			labels := map[string]string{
 				meshLabel: r.GetMeta().GetMesh(),
 			}
 			secret.SetLabels(labels)
+		}
+	case secret_model.GlobalSecretType:
+		secret.Type = common_k8s.GlobalSecretType
+		secret.Data = map[string][]byte{
+			"value": r.(*secret_model.GlobalSecretResource).Spec.GetData().GetValue(),
+		}
+	default:
+		return nil, errors.Errorf("invalid type %s, expected %s or %s", r.GetType(), secret_model.SecretType, secret_model.GlobalSecretType)
+	}
+	if r.GetMeta() != nil {
+		if adapter, ok := r.GetMeta().(*KubernetesMetaAdapter); ok {
+			secret.ObjectMeta = adapter.ObjectMeta
 		} else {
 			return nil, fmt.Errorf("meta has unexpected type: %#v", r.GetMeta())
 		}
@@ -232,30 +257,45 @@ func (c *SimpleConverter) ToKubernetesObject(r *secret_model.SecretResource) (*k
 	return secret, nil
 }
 
-func (c *SimpleConverter) ToCoreResource(secret *kube_core.Secret, out *secret_model.SecretResource) error {
-	out.SetMeta(&KubernetesMetaAdapter{secret.ObjectMeta})
+func (c *SimpleConverter) ToCoreResource(secret *kube_core.Secret, out core_model.Resource) error {
+	out.SetMeta(&KubernetesMetaAdapter{
+		ObjectMeta: secret.ObjectMeta,
+		SecretType: secret.Type,
+	})
 	if secret.Data != nil {
-		out.Spec = &system_proto.Secret{
+		_ = out.SetSpec(&system_proto.Secret{
 			Data: &wrappers.BytesValue{
 				Value: secret.Data["value"],
 			},
-		}
+		})
 	}
 	return nil
 }
 
-func (c *SimpleConverter) ToCoreList(in *kube_core.SecretList, out *secret_model.SecretResourceList) error {
-	out.Items = make([]*secret_model.SecretResource, len(in.Items))
-	for i, secret := range in.Items {
-		r := secret_model.NewSecretResource()
-		if err := c.ToCoreResource(&secret, r); err != nil {
-			return err
+func (c *SimpleConverter) ToCoreList(in *kube_core.SecretList, out core_model.ResourceList) error {
+	switch out.GetItemType() {
+	case secret_model.SecretType:
+		secOut := out.(*secret_model.SecretResourceList)
+		secOut.Items = make([]*secret_model.SecretResource, len(in.Items))
+		for i, secret := range in.Items {
+			r := secret_model.NewSecretResource()
+			if err := c.ToCoreResource(&secret, r); err != nil {
+				return err
+			}
+			secOut.Items[i] = r
+		}
+	case secret_model.GlobalSecretType:
+		secOut := out.(*secret_model.GlobalSecretResourceList)
+		secOut.Items = make([]*secret_model.GlobalSecretResource, len(in.Items))
+		for i, secret := range in.Items {
+			r := secret_model.NewGlobalSecretResource()
+			if err := c.ToCoreResource(&secret, r); err != nil {
+				return err
+			}
+			secOut.Items[i] = r
 		}
-		out.Items[i] = r
+	default:
+		return errors.Errorf("invalid type %s, expected %s or %s", out.GetItemType(), secret_model.SecretType, secret_model.GlobalSecretType)
 	}
 	return nil
 }
-
-func newInvalidTypeError() error {
-	return errors.New("resource has a wrong type")
-}


Test patch:
diff --git a/app/kumactl/cmd/completion/testdata/bash.golden b/app/kumactl/cmd/completion/testdata/bash.golden
index 73c1c6d6f..95b8d4f1f 100644
--- a/app/kumactl/cmd/completion/testdata/bash.golden
+++ b/app/kumactl/cmd/completion/testdata/bash.golden
@@ -1087,6 +1087,66 @@ _kumactl_get_fault-injections()
     noun_aliases=()
 }
 
+_kumactl_get_global-secret()
+{
+    last_command="kumactl_get_global-secret"
+
+    command_aliases=()
+
+    commands=()
+
+    flags=()
+    two_word_flags=()
+    local_nonpersistent_flags=()
+    flags_with_completion=()
+    flags_completion=()
+
+    flags+=("--config-file=")
+    two_word_flags+=("--config-file")
+    flags+=("--log-level=")
+    two_word_flags+=("--log-level")
+    flags+=("--mesh=")
+    two_word_flags+=("--mesh")
+    two_word_flags+=("-m")
+    flags+=("--output=")
+    two_word_flags+=("--output")
+    two_word_flags+=("-o")
+
+    must_have_one_flag=()
+    must_have_one_noun=()
+    noun_aliases=()
+}
+
+_kumactl_get_global-secrets()
+{
+    last_command="kumactl_get_global-secrets"
+
+    command_aliases=()
+
+    commands=()
+
+    flags=()
+    two_word_flags=()
+    local_nonpersistent_flags=()
+    flags_with_completion=()
+    flags_completion=()
+
+    flags+=("--config-file=")
+    two_word_flags+=("--config-file")
+    flags+=("--log-level=")
+    two_word_flags+=("--log-level")
+    flags+=("--mesh=")
+    two_word_flags+=("--mesh")
+    two_word_flags+=("-m")
+    flags+=("--output=")
+    two_word_flags+=("--output")
+    two_word_flags+=("-o")
+
+    must_have_one_flag=()
+    must_have_one_noun=()
+    noun_aliases=()
+}
+
 _kumactl_get_healthcheck()
 {
     last_command="kumactl_get_healthcheck"
@@ -1802,6 +1862,8 @@ _kumactl_get()
     commands+=("external-services")
     commands+=("fault-injection")
     commands+=("fault-injections")
+    commands+=("global-secret")
+    commands+=("global-secrets")
     commands+=("healthcheck")
     commands+=("healthchecks")
     commands+=("mesh")
diff --git a/app/kumactl/cmd/completion/testdata/zsh.golden b/app/kumactl/cmd/completion/testdata/zsh.golden
index 87f7da2d9..8737d3770 100644
--- a/app/kumactl/cmd/completion/testdata/zsh.golden
+++ b/app/kumactl/cmd/completion/testdata/zsh.golden
@@ -330,6 +330,8 @@ function _kumactl_get {
       "external-services:Show ExternalService"
       "fault-injection:Show a single FaultInjection resource"
       "fault-injections:Show FaultInjection"
+      "global-secret:Show a single GlobalSecret resource"
+      "global-secrets:Show GlobalSecret"
       "healthcheck:Show a single HealthCheck resource"
       "healthchecks:Show HealthCheck"
       "mesh:Show a single Mesh resource"
@@ -382,6 +384,12 @@ function _kumactl_get {
   fault-injections)
     _kumactl_get_fault-injections
     ;;
+  global-secret)
+    _kumactl_get_global-secret
+    ;;
+  global-secrets)
+    _kumactl_get_global-secrets
+    ;;
   healthcheck)
     _kumactl_get_healthcheck
     ;;
@@ -523,6 +531,22 @@ function _kumactl_get_fault-injections {
     '(-o --output)'{-o,--output}'[output format: one of table|yaml|json]:'
 }
 
+function _kumactl_get_global-secret {
+  _arguments \
+    '--config-file[path to the configuration file to use]:' \
+    '--log-level[log level: one of off|info|debug]:' \
+    '(-m --mesh)'{-m,--mesh}'[mesh to use]:' \
+    '(-o --output)'{-o,--output}'[output format: one of table|yaml|json]:'
+}
+
+function _kumactl_get_global-secrets {
+  _arguments \
+    '--config-file[path to the configuration file to use]:' \
+    '--log-level[log level: one of off|info|debug]:' \
+    '(-m --mesh)'{-m,--mesh}'[mesh to use]:' \
+    '(-o --output)'{-o,--output}'[output format: one of table|yaml|json]:'
+}
+
 function _kumactl_get_healthcheck {
   _arguments \
     '--config-file[path to the configuration file to use]:' \
diff --git a/app/kumactl/cmd/get/get_global_secrets_test.go b/app/kumactl/cmd/get/get_global_secrets_test.go
new file mode 100644
index 000000000..ed7b0b4eb
--- /dev/null
+++ b/app/kumactl/cmd/get/get_global_secrets_test.go
@@ -0,0 +1,131 @@
+package get_test
+
+import (
+	"bytes"
+	"context"
+	"path/filepath"
+	"time"
+
+	"github.com/golang/protobuf/ptypes/wrappers"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/ginkgo/extensions/table"
+	. "github.com/onsi/gomega"
+	"github.com/spf13/cobra"
+
+	system_proto "github.com/kumahq/kuma/api/system/v1alpha1"
+	config_proto "github.com/kumahq/kuma/pkg/config/app/kumactl/v1alpha1"
+	"github.com/kumahq/kuma/pkg/core/resources/apis/system"
+	. "github.com/kumahq/kuma/pkg/test/matchers"
+
+	"github.com/kumahq/kuma/app/kumactl/cmd"
+	kumactl_cmd "github.com/kumahq/kuma/app/kumactl/pkg/cmd"
+	core_model "github.com/kumahq/kuma/pkg/core/resources/model"
+	core_store "github.com/kumahq/kuma/pkg/core/resources/store"
+	memory_resources "github.com/kumahq/kuma/pkg/plugins/resources/memory"
+	test_model "github.com/kumahq/kuma/pkg/test/resources/model"
+)
+
+var _ = Describe("kumactl get global-secrets", func() {
+
+	var sampleSecrets []*system.GlobalSecretResource
+
+	BeforeEach(func() {
+		sampleSecrets = []*system.GlobalSecretResource{
+			{
+				Meta: &test_model.ResourceMeta{
+					Mesh: "default",
+					Name: "sec-1",
+				},
+				Spec: &system_proto.Secret{
+					Data: &wrappers.BytesValue{
+						Value: []byte("test"),
+					},
+				},
+			},
+			{
+				Meta: &test_model.ResourceMeta{
+					Mesh: "default",
+					Name: "sec-2",
+				},
+				Spec: &system_proto.Secret{
+					Data: &wrappers.BytesValue{
+						Value: []byte("test2"),
+					},
+				},
+			},
+		}
+	})
+
+	Describe("GetGlobalSecretsCmd", func() {
+
+		var rootCtx *kumactl_cmd.RootContext
+		var rootCmd *cobra.Command
+		var buf *bytes.Buffer
+		var store core_store.ResourceStore
+		rootTime, _ := time.Parse(time.RFC3339, "2008-04-27T16:05:36.995Z")
+		BeforeEach(func() {
+			// setup
+			rootCtx = &kumactl_cmd.RootContext{
+				Runtime: kumactl_cmd.RootRuntime{
+					Now: func() time.Time { return rootTime },
+					NewResourceStore: func(*config_proto.ControlPlaneCoordinates_ApiServer) (core_store.ResourceStore, error) {
+						return store, nil
+					},
+				},
+			}
+
+			store = core_store.NewPaginationStore(memory_resources.NewStore())
+
+			for _, pt := range sampleSecrets {
+				key := core_model.ResourceKey{
+					Mesh: pt.Meta.GetMesh(),
+					Name: pt.Meta.GetName(),
+				}
+				err := store.Create(context.Background(), pt, core_store.CreateBy(key))
+				Expect(err).ToNot(HaveOccurred())
+			}
+
+			rootCmd = cmd.NewRootCmd(rootCtx)
+			buf = &bytes.Buffer{}
+			rootCmd.SetOut(buf)
+		})
+
+		type testCase struct {
+			outputFormat string
+			goldenFile   string
+		}
+
+		DescribeTable("kumactl get secrets -o table|json|yaml",
+			func(given testCase) {
+				// given
+				rootCmd.SetArgs(append([]string{
+					"--config-file", filepath.Join("..", "testdata", "sample-kumactl.config.yaml"),
+					"get", "global-secrets"}, given.outputFormat))
+
+				// when
+				err := rootCmd.Execute()
+
+				// then
+				Expect(err).ToNot(HaveOccurred())
+				Expect(buf.String()).To(MatchGoldenEqual(filepath.Join("testdata", given.goldenFile)))
+			},
+			Entry("should support Table output by default", testCase{
+				outputFormat: "",
+				goldenFile:   "get-global-secrets.golden.txt",
+			}),
+			Entry("should support Table output explicitly", testCase{
+				outputFormat: "-otable",
+				goldenFile:   "get-global-secrets.golden.txt",
+			}),
+			Entry("should support JSON output", testCase{
+				outputFormat: "-ojson",
+				goldenFile:   "get-global-secrets.golden.json",
+			}),
+			Entry("should support YAML output", testCase{
+				outputFormat: "-oyaml",
+				goldenFile:   "get-global-secrets.golden.yaml",
+			}),
+		)
+	})
+})
diff --git a/app/kumactl/cmd/get/get_single_resource_test.go b/app/kumactl/cmd/get/get_single_resource_test.go
index 4e48d842a..c63468139 100644
--- a/app/kumactl/cmd/get/get_single_resource_test.go
+++ b/app/kumactl/cmd/get/get_single_resource_test.go
@@ -3,9 +3,7 @@ package get_test
 import (
 	"bytes"
 	"fmt"
-	"io/ioutil"
 	"path/filepath"
-	"strings"
 	"time"
 
 	. "github.com/onsi/ginkgo"
@@ -20,6 +18,7 @@ import (
 	config_proto "github.com/kumahq/kuma/pkg/config/app/kumactl/v1alpha1"
 	core_store "github.com/kumahq/kuma/pkg/core/resources/store"
 	memory_resources "github.com/kumahq/kuma/pkg/plugins/resources/memory"
+	. "github.com/kumahq/kuma/pkg/test/matchers"
 	kuma_version "github.com/kumahq/kuma/pkg/version"
 )
 
@@ -72,6 +71,7 @@ var _ = Describe("kumactl get [resource] NAME", func() {
 		Entry("traffic-route", "traffic-route"),
 		Entry("traffic-trace", "traffic-trace"),
 		Entry("secret", "secret"),
+		Entry("global-secret", "global-secret"),
 		Entry("retry", "retry"),
 	}
 
@@ -105,7 +105,7 @@ var _ = Describe("kumactl get [resource] NAME", func() {
 			// then
 			Expect(err).To(HaveOccurred())
 			// and
-			if resource == "mesh" {
+			if resource == "mesh" || resource == "global-secret" {
 				Expect(outbuf.String()).To(Equal("Error: No resources found\n"))
 			} else {
 				Expect(outbuf.String()).To(Equal("Error: No resources found in default mesh\n"))
@@ -135,9 +135,7 @@ var _ = Describe("kumactl get [resource] NAME", func() {
 
 			// then
 			Expect(err).ToNot(HaveOccurred())
-			expected, err := ioutil.ReadFile(filepath.Join("testdata", resourceTable))
-			Expect(err).ToNot(HaveOccurred())
-			Expect(outbuf.String()).To(WithTransform(strings.TrimSpace, Equal(strings.TrimSpace(string(expected)))))
+			Expect(outbuf.String()).To(MatchGoldenEqual(filepath.Join("testdata", resourceTable)))
 		},
 		entries...,
 	)
@@ -160,9 +158,7 @@ var _ = Describe("kumactl get [resource] NAME", func() {
 
 			// then
 			Expect(err).ToNot(HaveOccurred())
-			expected, err := ioutil.ReadFile(filepath.Join("testdata", resourceJSON))
-			Expect(err).ToNot(HaveOccurred())
-			Expect(outbuf.String()).To(MatchJSON(expected))
+			Expect(outbuf.String()).To(MatchGoldenEqual(filepath.Join("testdata", resourceJSON)))
 		},
 		entries...,
 	)
@@ -182,9 +178,7 @@ var _ = Describe("kumactl get [resource] NAME", func() {
 
 			// then
 			Expect(err).ToNot(HaveOccurred())
-			expected, err := ioutil.ReadFile(filepath.Join("testdata", resourceYAML))
-			Expect(err).ToNot(HaveOccurred())
-			Expect(outbuf.String()).To(MatchYAML(expected))
+			Expect(outbuf.String()).To(MatchGoldenEqual(filepath.Join("testdata", resourceYAML)))
 		},
 		entries...,
 	)
diff --git a/app/kumactl/cmd/get/testdata/get-circuit-breaker.golden.json b/app/kumactl/cmd/get/testdata/get-circuit-breaker.golden.json
index c14328438..396a8e223 100644
--- a/app/kumactl/cmd/get/testdata/get-circuit-breaker.golden.json
+++ b/app/kumactl/cmd/get/testdata/get-circuit-breaker.golden.json
@@ -31,4 +31,4 @@
       "failure": {}
     }
   }
-}
\ No newline at end of file
+}
diff --git a/app/kumactl/cmd/get/testdata/get-circuit-breaker.golden.txt b/app/kumactl/cmd/get/testdata/get-circuit-breaker.golden.txt
index 1936322fb..7b3bdec27 100644
--- a/app/kumactl/cmd/get/testdata/get-circuit-breaker.golden.txt
+++ b/app/kumactl/cmd/get/testdata/get-circuit-breaker.golden.txt
@@ -1,2 +1,2 @@
 MESH      NAME                AGE
-default   circuit-breaker-1   292y
\ No newline at end of file
+default   circuit-breaker-1   292y
diff --git a/app/kumactl/cmd/get/testdata/get-circuit-breaker.golden.yaml b/app/kumactl/cmd/get/testdata/get-circuit-breaker.golden.yaml
index e0382d72e..c737541b7 100644
--- a/app/kumactl/cmd/get/testdata/get-circuit-breaker.golden.yaml
+++ b/app/kumactl/cmd/get/testdata/get-circuit-breaker.golden.yaml
@@ -1,22 +1,22 @@
 conf:
   baseEjectionTime: 5s
   detectors:
-    totalErrors: {}
+    failure: {}
     gatewayErrors: {}
     localErrors: {}
-    failure: {}
     standardDeviation: {}
+    totalErrors: {}
   interval: 5s
   maxEjectionPercent: 50
-creationTime: 0001-01-01T00:00:00Z
+creationTime: "0001-01-01T00:00:00Z"
+destinations:
+- match:
+    service: backend
 mesh: default
-modificationTime: 0001-01-01T00:00:00Z
+modificationTime: "0001-01-01T00:00:00Z"
 name: circuit-breaker-1
 sources:
-  - match:
-      service: frontend
-      version: "0.1"
-destinations:
-  - match:
-      service: backend
-type: CircuitBreaker
\ No newline at end of file
+- match:
+    service: frontend
+    version: "0.1"
+type: CircuitBreaker
diff --git a/app/kumactl/cmd/get/testdata/get-dataplane.golden.json b/app/kumactl/cmd/get/testdata/get-dataplane.golden.json
index b3281836f..248480e6a 100644
--- a/app/kumactl/cmd/get/testdata/get-dataplane.golden.json
+++ b/app/kumactl/cmd/get/testdata/get-dataplane.golden.json
@@ -1,28 +1,28 @@
 {
-    "type": "Dataplane",
-    "mesh": "default",
-    "name": "dataplane-1",
-    "creationTime": "0001-01-01T00:00:00Z",
-    "modificationTime": "0001-01-01T00:00:00Z",
-    "networking": {
-        "address": "127.0.0.1",
-        "inbound": [
-            {
-                "port": 8080,
-                "servicePort": 80,
-                "tags": {
-                    "service": "mobile",
-                    "version": "v1"
-                }
-            },
-            {
-                "port": 8090,
-                "servicePort": 90,
-                "tags": {
-                    "service": "metrics",
-                    "version": "v1"
-                }
-            }
-        ]
-    }
-}
\ No newline at end of file
+  "type": "Dataplane",
+  "mesh": "default",
+  "name": "dataplane-1",
+  "creationTime": "0001-01-01T00:00:00Z",
+  "modificationTime": "0001-01-01T00:00:00Z",
+  "networking": {
+    "address": "127.0.0.1",
+    "inbound": [
+      {
+        "port": 8080,
+        "servicePort": 80,
+        "tags": {
+          "service": "mobile",
+          "version": "v1"
+        }
+      },
+      {
+        "port": 8090,
+        "servicePort": 90,
+        "tags": {
+          "service": "metrics",
+          "version": "v1"
+        }
+      }
+    ]
+  }
+}
diff --git a/app/kumactl/cmd/get/testdata/get-dataplane.golden.txt b/app/kumactl/cmd/get/testdata/get-dataplane.golden.txt
index fdd8b66b9..048711f03 100644
--- a/app/kumactl/cmd/get/testdata/get-dataplane.golden.txt
+++ b/app/kumactl/cmd/get/testdata/get-dataplane.golden.txt
@@ -1,2 +1,2 @@
 MESH      NAME          TAGS                                AGE
-default   dataplane-1   service=metrics,mobile version=v1   292y
\ No newline at end of file
+default   dataplane-1   service=metrics,mobile version=v1   292y
diff --git a/app/kumactl/cmd/get/testdata/get-dataplane.golden.yaml b/app/kumactl/cmd/get/testdata/get-dataplane.golden.yaml
index 9af81d72e..887739fca 100644
--- a/app/kumactl/cmd/get/testdata/get-dataplane.golden.yaml
+++ b/app/kumactl/cmd/get/testdata/get-dataplane.golden.yaml
@@ -1,18 +1,18 @@
-creationTime: 0001-01-01T00:00:00Z
+creationTime: "0001-01-01T00:00:00Z"
 mesh: default
-modificationTime: 0001-01-01T00:00:00Z
+modificationTime: "0001-01-01T00:00:00Z"
 name: dataplane-1
 networking:
   address: 127.0.0.1
   inbound:
-    - port: 8080
-      servicePort: 80
-      tags:
-        service: mobile
-        version: v1
-    - port: 8090
-      servicePort: 90
-      tags:
-        service: metrics
-        version: v1
-type: Dataplane
\ No newline at end of file
+  - port: 8080
+    servicePort: 80
+    tags:
+      service: mobile
+      version: v1
+  - port: 8090
+    servicePort: 90
+    tags:
+      service: metrics
+      version: v1
+type: Dataplane
diff --git a/app/kumactl/cmd/get/testdata/get-fault-injection.golden.json b/app/kumactl/cmd/get/testdata/get-fault-injection.golden.json
index cd20b07c8..83f00fa76 100644
--- a/app/kumactl/cmd/get/testdata/get-fault-injection.golden.json
+++ b/app/kumactl/cmd/get/testdata/get-fault-injection.golden.json
@@ -1,21 +1,21 @@
 {
-    "type": "FaultInjection",
-    "mesh": "default",
-    "name": "fault-injection-1",
-    "creationTime": "0001-01-01T00:00:00Z",
-    "modificationTime": "0001-01-01T00:00:00Z",
-    "sources": [
-        {
-            "match": {
-                "service": "frontend",
-                "version": "0.1"
-            }
-        }
-    ],
-    "conf": {
-        "abort": {
-            "percentage": 50,
-            "httpStatus": 500
-        }
+  "type": "FaultInjection",
+  "mesh": "default",
+  "name": "fault-injection-1",
+  "creationTime": "0001-01-01T00:00:00Z",
+  "modificationTime": "0001-01-01T00:00:00Z",
+  "sources": [
+    {
+      "match": {
+        "service": "frontend",
+        "version": "0.1"
+      }
     }
-}
\ No newline at end of file
+  ],
+  "conf": {
+    "abort": {
+      "percentage": 50,
+      "httpStatus": 500
+    }
+  }
+}
diff --git a/app/kumactl/cmd/get/testdata/get-fault-injection.golden.txt b/app/kumactl/cmd/get/testdata/get-fault-injection.golden.txt
index 409f5f0fa..4f0705a27 100644
--- a/app/kumactl/cmd/get/testdata/get-fault-injection.golden.txt
+++ b/app/kumactl/cmd/get/testdata/get-fault-injection.golden.txt
@@ -1,2 +1,2 @@
 MESH      NAME                AGE
-default   fault-injection-1   292y
\ No newline at end of file
+default   fault-injection-1   292y
diff --git a/app/kumactl/cmd/get/testdata/get-fault-injection.golden.yaml b/app/kumactl/cmd/get/testdata/get-fault-injection.golden.yaml
index 7119d4d78..0eb5e96c4 100644
--- a/app/kumactl/cmd/get/testdata/get-fault-injection.golden.yaml
+++ b/app/kumactl/cmd/get/testdata/get-fault-injection.golden.yaml
@@ -2,12 +2,12 @@ conf:
   abort:
     httpStatus: 500
     percentage: 50
-creationTime: 0001-01-01T00:00:00Z
+creationTime: "0001-01-01T00:00:00Z"
 mesh: default
-modificationTime: 0001-01-01T00:00:00Z
+modificationTime: "0001-01-01T00:00:00Z"
 name: fault-injection-1
 sources:
-  - match:
-      service: frontend
-      version: "0.1"
-type: FaultInjection
\ No newline at end of file
+- match:
+    service: frontend
+    version: "0.1"
+type: FaultInjection
diff --git a/app/kumactl/cmd/get/testdata/get-global-secret.golden.json b/app/kumactl/cmd/get/testdata/get-global-secret.golden.json
new file mode 100644
index 000000000..a779cc088
--- /dev/null
+++ b/app/kumactl/cmd/get/testdata/get-global-secret.golden.json
@@ -0,0 +1,7 @@
+{
+  "type": "GlobalSecret",
+  "name": "global-secret-1",
+  "creationTime": "0001-01-01T00:00:00Z",
+  "modificationTime": "0001-01-01T00:00:00Z",
+  "data": "dGVzdDIK"
+}
diff --git a/app/kumactl/cmd/get/testdata/get-global-secret.golden.txt b/app/kumactl/cmd/get/testdata/get-global-secret.golden.txt
new file mode 100644
index 000000000..0d30dc289
--- /dev/null
+++ b/app/kumactl/cmd/get/testdata/get-global-secret.golden.txt
@@ -0,0 +1,2 @@
+NAME              AGE
+global-secret-1   292y
diff --git a/app/kumactl/cmd/get/testdata/get-global-secret.golden.yaml b/app/kumactl/cmd/get/testdata/get-global-secret.golden.yaml
new file mode 100644
index 000000000..b577735b4
--- /dev/null
+++ b/app/kumactl/cmd/get/testdata/get-global-secret.golden.yaml
@@ -0,0 +1,5 @@
+creationTime: "0001-01-01T00:00:00Z"
+data: dGVzdDIK
+modificationTime: "0001-01-01T00:00:00Z"
+name: global-secret-1
+type: GlobalSecret
diff --git a/app/kumactl/cmd/get/testdata/get-global-secrets.golden.json b/app/kumactl/cmd/get/testdata/get-global-secrets.golden.json
new file mode 100644
index 000000000..573b0ad20
--- /dev/null
+++ b/app/kumactl/cmd/get/testdata/get-global-secrets.golden.json
@@ -0,0 +1,20 @@
+{
+  "total": 2,
+  "items": [
+    {
+      "type": "GlobalSecret",
+      "name": "sec-1",
+      "creationTime": "0001-01-01T00:00:00Z",
+      "modificationTime": "0001-01-01T00:00:00Z",
+      "data": "dGVzdA=="
+    },
+    {
+      "type": "GlobalSecret",
+      "name": "sec-2",
+      "creationTime": "0001-01-01T00:00:00Z",
+      "modificationTime": "0001-01-01T00:00:00Z",
+      "data": "dGVzdDI="
+    }
+  ],
+  "next": null
+}
diff --git a/app/kumactl/cmd/get/testdata/get-global-secrets.golden.txt b/app/kumactl/cmd/get/testdata/get-global-secrets.golden.txt
new file mode 100644
index 000000000..f4ec94d4c
--- /dev/null
+++ b/app/kumactl/cmd/get/testdata/get-global-secrets.golden.txt
@@ -0,0 +1,3 @@
+NAME    AGE
+sec-1   292y
+sec-2   292y
diff --git a/app/kumactl/cmd/get/testdata/get-global-secrets.golden.yaml b/app/kumactl/cmd/get/testdata/get-global-secrets.golden.yaml
new file mode 100644
index 000000000..f7ee36ff5
--- /dev/null
+++ b/app/kumactl/cmd/get/testdata/get-global-secrets.golden.yaml
@@ -0,0 +1,13 @@
+items:
+- creationTime: "0001-01-01T00:00:00Z"
+  data: dGVzdA==
+  modificationTime: "0001-01-01T00:00:00Z"
+  name: sec-1
+  type: GlobalSecret
+- creationTime: "0001-01-01T00:00:00Z"
+  data: dGVzdDI=
+  modificationTime: "0001-01-01T00:00:00Z"
+  name: sec-2
+  type: GlobalSecret
+next: null
+total: 2
diff --git a/app/kumactl/cmd/get/testdata/get-healthcheck.golden.json b/app/kumactl/cmd/get/testdata/get-healthcheck.golden.json
index f9916acd1..bde8b8bc3 100644
--- a/app/kumactl/cmd/get/testdata/get-healthcheck.golden.json
+++ b/app/kumactl/cmd/get/testdata/get-healthcheck.golden.json
@@ -1,27 +1,27 @@
 {
-    "type": "HealthCheck",
-    "mesh": "default",
-    "name": "healthcheck-1",
-    "creationTime": "0001-01-01T00:00:00Z",
-    "modificationTime": "0001-01-01T00:00:00Z",
-    "sources": [
-        {
-            "match": {
-                "service": "web"
-            }
-        }
-    ],
-    "destinations": [
-        {
-            "match": {
-                "service": "backend"
-            }
-        }
-    ],
-    "conf": {
-        "interval": "10s",
-        "timeout": "2s",
-        "unhealthyThreshold": 3,
-        "healthyThreshold": 1
+  "type": "HealthCheck",
+  "mesh": "default",
+  "name": "healthcheck-1",
+  "creationTime": "0001-01-01T00:00:00Z",
+  "modificationTime": "0001-01-01T00:00:00Z",
+  "sources": [
+    {
+      "match": {
+        "service": "web"
+      }
     }
+  ],
+  "destinations": [
+    {
+      "match": {
+        "service": "backend"
+      }
+    }
+  ],
+  "conf": {
+    "interval": "10s",
+    "timeout": "2s",
+    "unhealthyThreshold": 3,
+    "healthyThreshold": 1
+  }
 }
diff --git a/app/kumactl/cmd/get/testdata/get-healthcheck.golden.txt b/app/kumactl/cmd/get/testdata/get-healthcheck.golden.txt
index 4050ae801..375a3b4be 100644
--- a/app/kumactl/cmd/get/testdata/get-healthcheck.golden.txt
+++ b/app/kumactl/cmd/get/testdata/get-healthcheck.golden.txt
@@ -1,2 +1,2 @@
 MESH      NAME            AGE
-default   healthcheck-1   292y
\ No newline at end of file
+default   healthcheck-1   292y
diff --git a/app/kumactl/cmd/get/testdata/get-healthcheck.golden.yaml b/app/kumactl/cmd/get/testdata/get-healthcheck.golden.yaml
index 5d48a5f6c..f02990fa3 100644
--- a/app/kumactl/cmd/get/testdata/get-healthcheck.golden.yaml
+++ b/app/kumactl/cmd/get/testdata/get-healthcheck.golden.yaml
@@ -1,16 +1,16 @@
 conf:
+  healthyThreshold: 1
   interval: 10s
   timeout: 2s
   unhealthyThreshold: 3
-  healthyThreshold: 1
-creationTime: 0001-01-01T00:00:00Z
+creationTime: "0001-01-01T00:00:00Z"
 destinations:
-  - match:
-      service: backend
+- match:
+    service: backend
 mesh: default
-modificationTime: 0001-01-01T00:00:00Z
+modificationTime: "0001-01-01T00:00:00Z"
 name: healthcheck-1
 sources:
-  - match:
-      service: web
+- match:
+    service: web
 type: HealthCheck
diff --git a/app/kumactl/cmd/get/testdata/get-mesh.golden.json b/app/kumactl/cmd/get/testdata/get-mesh.golden.json
index bc4d91052..2694407e2 100644
--- a/app/kumactl/cmd/get/testdata/get-mesh.golden.json
+++ b/app/kumactl/cmd/get/testdata/get-mesh.golden.json
@@ -1,67 +1,67 @@
 {
-    "type": "Mesh",
-    "name": "mesh-1",
-    "creationTime": "0001-01-01T00:00:00Z",
-    "modificationTime": "0001-01-01T00:00:00Z",
-    "mtls": {
-        "enabledBackend": "builtin-1",
-        "backends": [
-            {
-                "name": "builtin-1",
-                "type": "builtin"
-            }
-        ]
-    },
-    "tracing": {
-        "backends": [
-            {
-                "name": "zipkin-us",
-                "type": "zipkin",
-                "conf": {
-                    "url": "http://zipkin.us:8080/v1/spans"
-                }
-            },
-            {
-                "name": "zipkin-eu",
-                "type": "zipkin",
-                "conf": {
-                    "url": "http://zipkin.eu:8080/v1/spans"
-                }
-            }
-        ]
-    },
-    "logging": {
-        "backends": [
-            {
-                "name": "logstash",
-                "type": "tcp",
-                "conf": {
-                    "address": "127.0.0.1:5000"
-                }
-            },
-            {
-                "name": "file",
-                "type": "file",
-                "conf": {
-                    "path": "/tmp/service.log"
-                }
-            }
-        ]
-    },
-    "metrics": {
-        "enabledBackend": "prometheus-1",
-        "backends": [
-            {
-                "name": "prometheus-1",
-                "type": "prometheus",
-                "conf": {
-                    "port": 1234,
-                    "path": "/non-standard-path"
-                }
-            }
-        ]
-    },
-    "routing": {
-        "localityAwareLoadBalancing": true
-    }
+  "type": "Mesh",
+  "name": "mesh-1",
+  "creationTime": "0001-01-01T00:00:00Z",
+  "modificationTime": "0001-01-01T00:00:00Z",
+  "mtls": {
+    "enabledBackend": "builtin-1",
+    "backends": [
+      {
+        "name": "builtin-1",
+        "type": "builtin"
+      }
+    ]
+  },
+  "tracing": {
+    "backends": [
+      {
+        "name": "zipkin-us",
+        "type": "zipkin",
+        "conf": {
+          "url": "http://zipkin.us:8080/v1/spans"
+        }
+      },
+      {
+        "name": "zipkin-eu",
+        "type": "zipkin",
+        "conf": {
+          "url": "http://zipkin.eu:8080/v1/spans"
+        }
+      }
+    ]
+  },
+  "logging": {
+    "backends": [
+      {
+        "name": "logstash",
+        "type": "tcp",
+        "conf": {
+          "address": "127.0.0.1:5000"
+        }
+      },
+      {
+        "name": "file",
+        "type": "file",
+        "conf": {
+          "path": "/tmp/service.log"
+        }
+      }
+    ]
+  },
+  "metrics": {
+    "enabledBackend": "prometheus-1",
+    "backends": [
+      {
+        "name": "prometheus-1",
+        "type": "prometheus",
+        "conf": {
+          "path": "/non-standard-path",
+          "port": 1234
+        }
+      }
+    ]
+  },
+  "routing": {
+    "localityAwareLoadBalancing": true
+  }
 }
diff --git a/app/kumactl/cmd/get/testdata/get-mesh.golden.yaml b/app/kumactl/cmd/get/testdata/get-mesh.golden.yaml
index dd661266b..05b396eac 100644
--- a/app/kumactl/cmd/get/testdata/get-mesh.golden.yaml
+++ b/app/kumactl/cmd/get/testdata/get-mesh.golden.yaml
@@ -1,39 +1,39 @@
-creationTime: 0001-01-01T00:00:00Z
+creationTime: "0001-01-01T00:00:00Z"
 logging:
   backends:
-    - name: logstash
-      type: tcp
-      conf:
-        address: 127.0.0.1:5000
-    - conf:
-        path: /tmp/service.log
-      type: file
-      name: file
+  - conf:
+      address: 127.0.0.1:5000
+    name: logstash
+    type: tcp
+  - conf:
+      path: /tmp/service.log
+    name: file
+    type: file
 metrics:
-  enabledBackend: prometheus-1
   backends:
-    - name: prometheus-1
-      type: prometheus
-      conf:
-        path: /non-standard-path
-        port: 1234
-modificationTime: 0001-01-01T00:00:00Z
+  - conf:
+      path: /non-standard-path
+      port: 1234
+    name: prometheus-1
+    type: prometheus
+  enabledBackend: prometheus-1
+modificationTime: "0001-01-01T00:00:00Z"
 mtls:
-  enabledBackend: builtin-1
   backends:
-    - name: builtin-1
-      type: builtin
+  - name: builtin-1
+    type: builtin
+  enabledBackend: builtin-1
 name: mesh-1
 routing:
   localityAwareLoadBalancing: true
 tracing:
   backends:
-    - name: zipkin-us
-      type: zipkin
-      conf:
-        url: http://zipkin.us:8080/v1/spans
-    - name: zipkin-eu
-      type: zipkin
-      conf:
-        url: http://zipkin.eu:8080/v1/spans
+  - conf:
+      url: http://zipkin.us:8080/v1/spans
+    name: zipkin-us
+    type: zipkin
+  - conf:
+      url: http://zipkin.eu:8080/v1/spans
+    name: zipkin-eu
+    type: zipkin
 type: Mesh
diff --git a/app/kumactl/cmd/get/testdata/get-proxytemplate.golden.json b/app/kumactl/cmd/get/testdata/get-proxytemplate.golden.json
index 7da0363ec..8dda7dd88 100644
--- a/app/kumactl/cmd/get/testdata/get-proxytemplate.golden.json
+++ b/app/kumactl/cmd/get/testdata/get-proxytemplate.golden.json
@@ -1,19 +1,19 @@
 {
-    "mesh": "default",
-    "name": "proxytemplate-1",
-    "type": "ProxyTemplate",
-    "creationTime": "0001-01-01T00:00:00Z",
-    "modificationTime": "0001-01-01T00:00:00Z",
-    "selectors": [
-        {
-            "match": {
-                "service": "backend"
-            }
-        }
-    ],
-    "conf": {
-        "imports": [
-            "default-proxy"
-        ]
+  "type": "ProxyTemplate",
+  "mesh": "default",
+  "name": "proxytemplate-1",
+  "creationTime": "0001-01-01T00:00:00Z",
+  "modificationTime": "0001-01-01T00:00:00Z",
+  "selectors": [
+    {
+      "match": {
+        "service": "backend"
+      }
     }
-}
\ No newline at end of file
+  ],
+  "conf": {
+    "imports": [
+      "default-proxy"
+    ]
+  }
+}
diff --git a/app/kumactl/cmd/get/testdata/get-proxytemplate.golden.txt b/app/kumactl/cmd/get/testdata/get-proxytemplate.golden.txt
index 0b7142d00..c21e3aede 100644
--- a/app/kumactl/cmd/get/testdata/get-proxytemplate.golden.txt
+++ b/app/kumactl/cmd/get/testdata/get-proxytemplate.golden.txt
@@ -1,2 +1,2 @@
 MESH      NAME              AGE
-default   proxytemplate-1   292y
\ No newline at end of file
+default   proxytemplate-1   292y
diff --git a/app/kumactl/cmd/get/testdata/get-proxytemplate.golden.yaml b/app/kumactl/cmd/get/testdata/get-proxytemplate.golden.yaml
index 400b6ef71..1cf91f271 100644
--- a/app/kumactl/cmd/get/testdata/get-proxytemplate.golden.yaml
+++ b/app/kumactl/cmd/get/testdata/get-proxytemplate.golden.yaml
@@ -1,11 +1,11 @@
 conf:
   imports:
-    - default-proxy
-creationTime: 0001-01-01T00:00:00Z
+  - default-proxy
+creationTime: "0001-01-01T00:00:00Z"
 mesh: default
-modificationTime: 0001-01-01T00:00:00Z
+modificationTime: "0001-01-01T00:00:00Z"
 name: proxytemplate-1
 selectors:
-  - match:
-      service: "backend"
-type: ProxyTemplate
\ No newline at end of file
+- match:
+    service: backend
+type: ProxyTemplate
diff --git a/app/kumactl/cmd/get/testdata/get-retry.golden.json b/app/kumactl/cmd/get/testdata/get-retry.golden.json
index b5f2942fe..cdc6f7613 100644
--- a/app/kumactl/cmd/get/testdata/get-retry.golden.json
+++ b/app/kumactl/cmd/get/testdata/get-retry.golden.json
@@ -26,7 +26,10 @@
         "baseInterval": "0.008s",
         "maxInterval": "1s"
       },
-      "retriableStatusCodes": [500, 501]
+      "retriableStatusCodes": [
+        500,
+        501
+      ]
     }
   }
 }
diff --git a/app/kumactl/cmd/get/testdata/get-retry.golden.yaml b/app/kumactl/cmd/get/testdata/get-retry.golden.yaml
index 6f3c4b27e..b3e13cceb 100644
--- a/app/kumactl/cmd/get/testdata/get-retry.golden.yaml
+++ b/app/kumactl/cmd/get/testdata/get-retry.golden.yaml
@@ -1,21 +1,21 @@
 conf:
   http:
-    numRetries: 56
-    perTryTimeout: 10s
     backOff:
       baseInterval: 0.008s
       maxInterval: 1s
+    numRetries: 56
+    perTryTimeout: 10s
     retriableStatusCodes:
     - 500
     - 501
-creationTime: 0001-01-01T00:00:00Z
+creationTime: "0001-01-01T00:00:00Z"
 destinations:
-  - match:
-      service: backend
+- match:
+    service: backend
 mesh: default
-modificationTime: 0001-01-01T00:00:00Z
+modificationTime: "0001-01-01T00:00:00Z"
 name: retry-1
 sources:
-  - match:
-      service: web
+- match:
+    service: web
 type: Retry
diff --git a/app/kumactl/cmd/get/testdata/get-secret.golden.yaml b/app/kumactl/cmd/get/testdata/get-secret.golden.yaml
index 8bd6afb1d..90a8cb984 100644
--- a/app/kumactl/cmd/get/testdata/get-secret.golden.yaml
+++ b/app/kumactl/cmd/get/testdata/get-secret.golden.yaml
@@ -1,6 +1,6 @@
-type: Secret
-mesh: default
-name: secret-1
 creationTime: "0001-01-01T00:00:00Z"
-modificationTime: "0001-01-01T00:00:00Z"
 data: dGVzdDIK
+mesh: default
+modificationTime: "0001-01-01T00:00:00Z"
+name: secret-1
+type: Secret
diff --git a/app/kumactl/cmd/get/testdata/get-traffic-log.golden.json b/app/kumactl/cmd/get/testdata/get-traffic-log.golden.json
index 96079d047..37972421d 100644
--- a/app/kumactl/cmd/get/testdata/get-traffic-log.golden.json
+++ b/app/kumactl/cmd/get/testdata/get-traffic-log.golden.json
@@ -1,26 +1,26 @@
 {
-    "type": "TrafficLog",
-    "mesh": "default",
-    "name": "traffic-log-1",
-    "creationTime": "0001-01-01T00:00:00Z",
-    "modificationTime": "0001-01-01T00:00:00Z",
-    "sources": [
-        {
-            "match": {
-                "service": "web1",
-                "version": "1.0"
-            }
-        }
-    ],
-    "destinations": [
-        {
-            "match": {
-                "env": "dev",
-                "service": "backend1"
-            }
-        }
-    ],
-    "conf": {
-        "backend": "file"
+  "type": "TrafficLog",
+  "mesh": "default",
+  "name": "traffic-log-1",
+  "creationTime": "0001-01-01T00:00:00Z",
+  "modificationTime": "0001-01-01T00:00:00Z",
+  "sources": [
+    {
+      "match": {
+        "service": "web1",
+        "version": "1.0"
+      }
     }
-}
\ No newline at end of file
+  ],
+  "destinations": [
+    {
+      "match": {
+        "env": "dev",
+        "service": "backend1"
+      }
+    }
+  ],
+  "conf": {
+    "backend": "file"
+  }
+}
diff --git a/app/kumactl/cmd/get/testdata/get-traffic-log.golden.txt b/app/kumactl/cmd/get/testdata/get-traffic-log.golden.txt
index 982646c91..119c08dbc 100644
--- a/app/kumactl/cmd/get/testdata/get-traffic-log.golden.txt
+++ b/app/kumactl/cmd/get/testdata/get-traffic-log.golden.txt
@@ -1,2 +1,2 @@
 MESH      NAME            AGE
-default   traffic-log-1   292y
\ No newline at end of file
+default   traffic-log-1   292y
diff --git a/app/kumactl/cmd/get/testdata/get-traffic-log.golden.yaml b/app/kumactl/cmd/get/testdata/get-traffic-log.golden.yaml
index 810d21682..72909a61d 100644
--- a/app/kumactl/cmd/get/testdata/get-traffic-log.golden.yaml
+++ b/app/kumactl/cmd/get/testdata/get-traffic-log.golden.yaml
@@ -1,15 +1,15 @@
 conf:
   backend: file
-creationTime: 0001-01-01T00:00:00Z
+creationTime: "0001-01-01T00:00:00Z"
 destinations:
-  - match:
-      env: dev
-      service: backend1
+- match:
+    env: dev
+    service: backend1
 mesh: default
-modificationTime: 0001-01-01T00:00:00Z
+modificationTime: "0001-01-01T00:00:00Z"
 name: traffic-log-1
 sources:
-  - match:
-      service: web1
-      version: "1.0"
-type: TrafficLog
\ No newline at end of file
+- match:
+    service: web1
+    version: "1.0"
+type: TrafficLog
diff --git a/app/kumactl/cmd/get/testdata/get-traffic-permission.golden.json b/app/kumactl/cmd/get/testdata/get-traffic-permission.golden.json
index 8da5c6a1c..a830143c9 100644
--- a/app/kumactl/cmd/get/testdata/get-traffic-permission.golden.json
+++ b/app/kumactl/cmd/get/testdata/get-traffic-permission.golden.json
@@ -20,4 +20,4 @@
       }
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/app/kumactl/cmd/get/testdata/get-traffic-permission.golden.txt b/app/kumactl/cmd/get/testdata/get-traffic-permission.golden.txt
index 3ec026c46..6a2dae44f 100644
--- a/app/kumactl/cmd/get/testdata/get-traffic-permission.golden.txt
+++ b/app/kumactl/cmd/get/testdata/get-traffic-permission.golden.txt
@@ -1,2 +1,2 @@
 MESH      NAME                   AGE
-default   traffic-permission-1   292y
\ No newline at end of file
+default   traffic-permission-1   292y
diff --git a/app/kumactl/cmd/get/testdata/get-traffic-permission.golden.yaml b/app/kumactl/cmd/get/testdata/get-traffic-permission.golden.yaml
index 49f69b27f..ff4238df5 100644
--- a/app/kumactl/cmd/get/testdata/get-traffic-permission.golden.yaml
+++ b/app/kumactl/cmd/get/testdata/get-traffic-permission.golden.yaml
@@ -1,13 +1,13 @@
-creationTime: 0001-01-01T00:00:00Z
+creationTime: "0001-01-01T00:00:00Z"
 destinations:
-  - match:
-      env: dev
-      service: backend1
+- match:
+    env: dev
+    service: backend1
 mesh: default
-modificationTime: 0001-01-01T00:00:00Z
+modificationTime: "0001-01-01T00:00:00Z"
 name: traffic-permission-1
 sources:
-  - match:
-      service: web1
-      version: "1.0"
-type: TrafficPermission
\ No newline at end of file
+- match:
+    service: web1
+    version: "1.0"
+type: TrafficPermission
diff --git a/app/kumactl/cmd/get/testdata/get-traffic-route.golden.json b/app/kumactl/cmd/get/testdata/get-traffic-route.golden.json
index 70cf79c26..59d31151d 100644
--- a/app/kumactl/cmd/get/testdata/get-traffic-route.golden.json
+++ b/app/kumactl/cmd/get/testdata/get-traffic-route.golden.json
@@ -1,32 +1,32 @@
 {
-    "type": "TrafficRoute",
-    "mesh": "default",
-    "name": "traffic-route-1",
-    "creationTime": "0001-01-01T00:00:00Z",
-    "modificationTime": "0001-01-01T00:00:00Z",
-    "sources": [
-        {
-            "match": {
-                "service": "backend"
-            }
-        }
-    ],
-    "destinations": [
-        {
-            "match": {
-                "service": "redis"
-            }
-        }
-    ],
-    "conf": {
-        "split": [
-            {
-                "weight": 90,
-                "destination": {
-                    "service": "redis",
-                    "version": "1.0"
-                }
-            }
-        ]
+  "type": "TrafficRoute",
+  "mesh": "default",
+  "name": "traffic-route-1",
+  "creationTime": "0001-01-01T00:00:00Z",
+  "modificationTime": "0001-01-01T00:00:00Z",
+  "sources": [
+    {
+      "match": {
+        "service": "backend"
+      }
     }
+  ],
+  "destinations": [
+    {
+      "match": {
+        "service": "redis"
+      }
+    }
+  ],
+  "conf": {
+    "split": [
+      {
+        "weight": 90,
+        "destination": {
+          "service": "redis",
+          "version": "1.0"
+        }
+      }
+    ]
+  }
 }
diff --git a/app/kumactl/cmd/get/testdata/get-traffic-route.golden.txt b/app/kumactl/cmd/get/testdata/get-traffic-route.golden.txt
index 5f9eb0adb..cd0a27cbd 100644
--- a/app/kumactl/cmd/get/testdata/get-traffic-route.golden.txt
+++ b/app/kumactl/cmd/get/testdata/get-traffic-route.golden.txt
@@ -1,2 +1,2 @@
 MESH      NAME              AGE
-default   traffic-route-1   292y
\ No newline at end of file
+default   traffic-route-1   292y
diff --git a/app/kumactl/cmd/get/testdata/get-traffic-route.golden.yaml b/app/kumactl/cmd/get/testdata/get-traffic-route.golden.yaml
index ef3200859..da81d606f 100644
--- a/app/kumactl/cmd/get/testdata/get-traffic-route.golden.yaml
+++ b/app/kumactl/cmd/get/testdata/get-traffic-route.golden.yaml
@@ -1,17 +1,17 @@
 conf:
   split:
-  - weight: 90
-    destination:
+  - destination:
       service: redis
       version: "1.0"
-creationTime: 0001-01-01T00:00:00Z
+    weight: 90
+creationTime: "0001-01-01T00:00:00Z"
 destinations:
-  - match:
-      service: redis
+- match:
+    service: redis
 mesh: default
-modificationTime: 0001-01-01T00:00:00Z
+modificationTime: "0001-01-01T00:00:00Z"
 name: traffic-route-1
 sources:
-  - match:
-      service: backend
+- match:
+    service: backend
 type: TrafficRoute
diff --git a/app/kumactl/cmd/get/testdata/get-traffic-trace.golden.json b/app/kumactl/cmd/get/testdata/get-traffic-trace.golden.json
index 6ff27b200..ded0869ec 100644
--- a/app/kumactl/cmd/get/testdata/get-traffic-trace.golden.json
+++ b/app/kumactl/cmd/get/testdata/get-traffic-trace.golden.json
@@ -1,18 +1,18 @@
 {
-    "type": "TrafficTrace",
-    "mesh": "default",
-    "name": "traffic-trace-1",
-    "creationTime": "0001-01-01T00:00:00Z",
-    "modificationTime": "0001-01-01T00:00:00Z",
-    "selectors": [
-        {
-            "match": {
-                "service": "web1",
-                "version": "1.0"
-            }
-        }
-    ],
-    "conf": {
-        "backend": "zipkin"
+  "type": "TrafficTrace",
+  "mesh": "default",
+  "name": "traffic-trace-1",
+  "creationTime": "0001-01-01T00:00:00Z",
+  "modificationTime": "0001-01-01T00:00:00Z",
+  "selectors": [
+    {
+      "match": {
+        "service": "web1",
+        "version": "1.0"
+      }
     }
-}
\ No newline at end of file
+  ],
+  "conf": {
+    "backend": "zipkin"
+  }
+}
diff --git a/app/kumactl/cmd/get/testdata/get-traffic-trace.golden.txt b/app/kumactl/cmd/get/testdata/get-traffic-trace.golden.txt
index 789bdb894..9543582e0 100644
--- a/app/kumactl/cmd/get/testdata/get-traffic-trace.golden.txt
+++ b/app/kumactl/cmd/get/testdata/get-traffic-trace.golden.txt
@@ -1,2 +1,2 @@
 MESH      NAME              AGE
-default   traffic-trace-1   292y
\ No newline at end of file
+default   traffic-trace-1   292y
diff --git a/app/kumactl/cmd/get/testdata/get-traffic-trace.golden.yaml b/app/kumactl/cmd/get/testdata/get-traffic-trace.golden.yaml
index b2bae7839..4432ae209 100644
--- a/app/kumactl/cmd/get/testdata/get-traffic-trace.golden.yaml
+++ b/app/kumactl/cmd/get/testdata/get-traffic-trace.golden.yaml
@@ -1,11 +1,11 @@
 conf:
   backend: zipkin
-creationTime: 0001-01-01T00:00:00Z
+creationTime: "0001-01-01T00:00:00Z"
 mesh: default
-modificationTime: 0001-01-01T00:00:00Z
+modificationTime: "0001-01-01T00:00:00Z"
 name: traffic-trace-1
 selectors:
-  - match:
-      service: web1
-      version: "1.0"
-type: TrafficTrace
\ No newline at end of file
+- match:
+    service: web1
+    version: "1.0"
+type: TrafficTrace
diff --git a/pkg/api-server/global_secret_endpoints_test.go b/pkg/api-server/global_secret_endpoints_test.go
new file mode 100644
index 000000000..949491768
--- /dev/null
+++ b/pkg/api-server/global_secret_endpoints_test.go
@@ -0,0 +1,107 @@
+package api_server_test
+
+import (
+	"context"
+	"io/ioutil"
+	"time"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+
+	"github.com/ghodss/yaml"
+
+	system_proto "github.com/kumahq/kuma/api/system/v1alpha1"
+	api_server "github.com/kumahq/kuma/pkg/api-server"
+	config "github.com/kumahq/kuma/pkg/config/api-server"
+	"github.com/kumahq/kuma/pkg/core"
+	mesh_core "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
+	"github.com/kumahq/kuma/pkg/core/resources/model"
+	"github.com/kumahq/kuma/pkg/core/resources/model/rest"
+	"github.com/kumahq/kuma/pkg/core/resources/store"
+	"github.com/kumahq/kuma/pkg/metrics"
+	"github.com/kumahq/kuma/pkg/plugins/resources/memory"
+)
+
+var _ = Describe("GlobalSecret Endpoints", func() {
+	var apiServer *api_server.ApiServer
+	var resourceStore store.ResourceStore
+	var client resourceApiClient
+	var stop chan struct{}
+
+	BeforeEach(func() {
+		core.Now = func() time.Time {
+			now, _ := time.Parse(time.RFC3339, "2018-07-17T16:05:36.995+00:00")
+			return now
+		}
+		resourceStore = memory.NewStore()
+		metrics, err := metrics.NewMetrics("Standalone")
+		Expect(err).ToNot(HaveOccurred())
+		apiServer = createTestApiServer(resourceStore, config.DefaultApiServerConfig(), true, metrics)
+		client = resourceApiClient{
+			apiServer.Address(),
+			"/global-secrets",
+		}
+		stop = make(chan struct{})
+		go func() {
+			defer GinkgoRecover()
+			err := apiServer.Start(stop)
+			Expect(err).ToNot(HaveOccurred())
+		}()
+		waitForServer(&client)
+	}, 5)
+
+	AfterEach(func() {
+		close(stop)
+		core.Now = time.Now
+	})
+
+	BeforeEach(func() {
+		// when
+		err := resourceStore.Create(context.Background(), mesh_core.NewMeshResource(), store.CreateByKey(model.DefaultMesh, model.NoMesh))
+		// then
+		Expect(err).ToNot(HaveOccurred())
+	})
+
+	Describe("PUT => GET", func() {
+
+		given := `
+        type: GlobalSecret
+        name: sec-1
+        creationTime: "2018-07-17T16:05:36.995Z"
+        modificationTime: "2018-07-17T16:05:36.995Z"
+        data: "dGVzdAo="
+`
+		It("GET should return data saved by PUT", func() {
+			// given
+			resource := rest.Resource{
+				Spec: &system_proto.Secret{},
+			}
+
+			// when
+			err := yaml.Unmarshal([]byte(given), &resource)
+			// then
+			Expect(err).ToNot(HaveOccurred())
+
+			// when
+			response := client.put(resource)
+			// then
+			Expect(response.StatusCode).To(Equal(201))
+
+			// when
+			response = client.get("sec-1")
+			// then
+			Expect(response.StatusCode).To(Equal(200))
+			// when
+			body, err := ioutil.ReadAll(response.Body)
+			// then
+			Expect(err).ToNot(HaveOccurred())
+
+			// when
+			actual, err := yaml.JSONToYAML(body)
+			// then
+			Expect(err).ToNot(HaveOccurred())
+			// and
+			Expect(actual).To(MatchYAML(given))
+		})
+	})
+})
diff --git a/pkg/api-server/secret_endpoints_test.go b/pkg/api-server/secret_endpoints_test.go
new file mode 100644
index 000000000..3f6e08f91
--- /dev/null
+++ b/pkg/api-server/secret_endpoints_test.go
@@ -0,0 +1,108 @@
+package api_server_test
+
+import (
+	"context"
+	"io/ioutil"
+	"time"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+
+	"github.com/ghodss/yaml"
+
+	system_proto "github.com/kumahq/kuma/api/system/v1alpha1"
+	api_server "github.com/kumahq/kuma/pkg/api-server"
+	config "github.com/kumahq/kuma/pkg/config/api-server"
+	"github.com/kumahq/kuma/pkg/core"
+	mesh_core "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
+	"github.com/kumahq/kuma/pkg/core/resources/model"
+	"github.com/kumahq/kuma/pkg/core/resources/model/rest"
+	"github.com/kumahq/kuma/pkg/core/resources/store"
+	"github.com/kumahq/kuma/pkg/metrics"
+	"github.com/kumahq/kuma/pkg/plugins/resources/memory"
+)
+
+var _ = Describe("Secret Endpoints", func() {
+	var apiServer *api_server.ApiServer
+	var resourceStore store.ResourceStore
+	var client resourceApiClient
+	var stop chan struct{}
+
+	BeforeEach(func() {
+		core.Now = func() time.Time {
+			now, _ := time.Parse(time.RFC3339, "2018-07-17T16:05:36.995+00:00")
+			return now
+		}
+		resourceStore = memory.NewStore()
+		metrics, err := metrics.NewMetrics("Standalone")
+		Expect(err).ToNot(HaveOccurred())
+		apiServer = createTestApiServer(resourceStore, config.DefaultApiServerConfig(), true, metrics)
+		client = resourceApiClient{
+			apiServer.Address(),
+			"/meshes/default/secrets",
+		}
+		stop = make(chan struct{})
+		go func() {
+			defer GinkgoRecover()
+			err := apiServer.Start(stop)
+			Expect(err).ToNot(HaveOccurred())
+		}()
+		waitForServer(&client)
+	}, 5)
+
+	AfterEach(func() {
+		close(stop)
+		core.Now = time.Now
+	})
+
+	BeforeEach(func() {
+		// when
+		err := resourceStore.Create(context.Background(), mesh_core.NewMeshResource(), store.CreateByKey(model.DefaultMesh, model.NoMesh))
+		// then
+		Expect(err).ToNot(HaveOccurred())
+	})
+
+	Describe("PUT => GET", func() {
+
+		given := `
+        type: Secret
+        name: sec-1
+        mesh: default
+        creationTime: "2018-07-17T16:05:36.995Z"
+        modificationTime: "2018-07-17T16:05:36.995Z"
+        data: "dGVzdAo="
+`
+		It("GET should return data saved by PUT", func() {
+			// given
+			resource := rest.Resource{
+				Spec: &system_proto.Secret{},
+			}
+
+			// when
+			err := yaml.Unmarshal([]byte(given), &resource)
+			// then
+			Expect(err).ToNot(HaveOccurred())
+
+			// when
+			response := client.put(resource)
+			// then
+			Expect(response.StatusCode).To(Equal(201))
+
+			// when
+			response = client.get("sec-1")
+			// then
+			Expect(response.StatusCode).To(Equal(200))
+			// when
+			body, err := ioutil.ReadAll(response.Body)
+			// then
+			Expect(err).ToNot(HaveOccurred())
+
+			// when
+			actual, err := yaml.JSONToYAML(body)
+			// then
+			Expect(err).ToNot(HaveOccurred())
+			// and
+			Expect(actual).To(MatchYAML(given))
+		})
+	})
+})
diff --git a/pkg/plugins/resources/k8s/consistent_kind_type_test.go b/pkg/plugins/resources/k8s/consistent_kind_type_test.go
index bfc35207b..3b8081d62 100644
--- a/pkg/plugins/resources/k8s/consistent_kind_type_test.go
+++ b/pkg/plugins/resources/k8s/consistent_kind_type_test.go
@@ -5,18 +5,26 @@ import (
 	. "github.com/onsi/gomega"
 
 	"github.com/kumahq/kuma/pkg/core/resources/apis/system"
+	"github.com/kumahq/kuma/pkg/core/resources/model"
 	core_registry "github.com/kumahq/kuma/pkg/core/resources/registry"
 	k8s_registry "github.com/kumahq/kuma/pkg/plugins/resources/k8s/native/pkg/registry"
 )
 
+// Those types are not mapped directly to Kubernetes Resource
+var IgnoredTypes = map[model.ResourceType]bool{
+	system.SecretType:       true,
+	system.GlobalSecretType: true,
+	system.ConfigType:       true,
+}
+
 var _ = Describe("Consistent Kind Types", func() {
 	It("Kind for objects is the same as ResourceType", func() {
 		types := core_registry.Global()
 		k8sTypes := k8s_registry.Global()
 
 		for _, typ := range types.ObjectTypes() {
-			if typ == system.SecretType || typ == system.ConfigType {
-				continue // ignore Secret since we cannot map it directly to Secret K8S resource
+			if IgnoredTypes[typ] {
+				continue
 			}
 
 			res, err := types.NewObject(typ)
@@ -33,8 +41,8 @@ var _ = Describe("Consistent Kind Types", func() {
 		k8sTypes := k8s_registry.Global()
 
 		for _, typ := range types.ListTypes() {
-			if typ == system.SecretType || typ == system.ConfigType {
-				continue // ignore Secret since we cannot map it directly to Secret K8S resource
+			if IgnoredTypes[typ] {
+				continue
 			}
 
 			res, err := types.NewObject(typ)
diff --git a/pkg/plugins/runtime/k8s/webhooks/secret_validator_test.go b/pkg/plugins/runtime/k8s/webhooks/secret_validator_test.go
index ff66638c7..d24ed5b76 100644
--- a/pkg/plugins/runtime/k8s/webhooks/secret_validator_test.go
+++ b/pkg/plugins/runtime/k8s/webhooks/secret_validator_test.go
@@ -83,7 +83,7 @@ var _ = Describe("ServiceValidator", func() {
 			Expect(err).ToNot(HaveOccurred())
 			Expect(actual).To(MatchYAML(given.expected))
 		},
-		Entry("should allow properly constructed Secret", testCase{
+		Entry("should allow properly constructed mesh Secret", testCase{
 			request: `
             apiVersion: admission.k8s.io/v1
             kind: AdmissionReview
@@ -114,6 +114,37 @@ var _ = Describe("ServiceValidator", func() {
               code: 200
               metadata: {}
             uid: ""
+`,
+		}),
+		Entry("should allow properly constructed global Secret", testCase{
+			request: `
+            apiVersion: admission.k8s.io/v1
+            kind: AdmissionReview
+            request:
+              uid: 12345
+              kind:
+                group: ""
+                kind: Secret
+                version: v1
+              name: sec-1
+              namespace: kuma-system
+              object:
+                apiVersion: v1
+                kind: Secret
+                metadata:
+                  name: global-sec-1
+                  namespace: kuma-system
+                data:
+                  value: dGVzdAo=
+                type: system.kuma.io/global-secret
+              operation: CREATE
+`,
+			expected: `
+            allowed: true
+            status:
+              code: 200
+              metadata: {}
+            uid: ""
 `,
 		}),
 		Entry("should allow Secret without mesh label (defaults to 'default')", testCase{
@@ -245,6 +276,48 @@ var _ = Describe("ServiceValidator", func() {
               status: Failure
             uid: ""
 `,
+		}),
+		Entry("should not allow mesh in global Secret", testCase{
+			request: `
+            apiVersion: admission.k8s.io/v1
+            kind: AdmissionReview
+            request:
+              uid: 12345
+              kind:
+                group: ""
+                kind: Secret
+                version: v1
+              name: sec-1
+              namespace: kuma-system
+              object:
+                apiVersion: v1
+                kind: Secret
+                metadata:
+                  name: sec-1
+                  namespace: kuma-system
+                  labels:
+                    kuma.io/mesh: default
+                data:
+                  value: dGVzdAo=
+                type: system.kuma.io/global-secret
+              operation: CREATE
+`,
+			expected: `
+            allowed: false
+            status:
+              code: 422
+              details:
+                causes:
+                - field: metadata.labels["kuma.io/mesh"]
+                  message: mesh cannot be set on global secret
+                  reason: FieldValueInvalid
+                kind: Secret
+                name: sec-1
+              message: 'metadata.labels["kuma.io/mesh"]: mesh cannot be set on global secret'
+              metadata: {}
+              reason: Invalid
+              status: Failure
+            uid: ""`,
 		}),
 		Entry("should not allow Secret without data", testCase{
 			request: `
@@ -285,6 +358,45 @@ var _ = Describe("ServiceValidator", func() {
               reason: Invalid
               status: Failure
             uid: ""
+`,
+		}),
+		Entry("should not allow global Secret without data", testCase{
+			request: `
+            apiVersion: admission.k8s.io/v1
+            kind: AdmissionReview
+            request:
+              uid: 12345
+              kind:
+                group: ""
+                kind: Secret
+                version: v1
+              name: sec-1
+              namespace: kuma-system
+              object:
+                apiVersion: v1
+                kind: Secret
+                metadata:
+                  name: sec-1
+                  namespace: kuma-system
+                type: system.kuma.io/global-secret
+              operation: CREATE
+`,
+			expected: `
+            allowed: false
+            status:
+              code: 422
+              details:
+                causes:
+                - field: data.value
+                  message: cannot be empty.
+                  reason: FieldValueInvalid
+                kind: Secret
+                name: sec-1
+              message: 'data.value: cannot be empty.'
+              metadata: {}
+              reason: Invalid
+              status: Failure
+            uid: ""
 `,
 		}),
 		Entry("should not allow deleting secret in use", testCase{
diff --git a/pkg/plugins/secrets/k8s/store_test.go b/pkg/plugins/secrets/k8s/store_test.go
index c70a9f735..d2c82778d 100644
--- a/pkg/plugins/secrets/k8s/store_test.go
+++ b/pkg/plugins/secrets/k8s/store_test.go
@@ -127,6 +127,44 @@ var _ = Describe("KubernetesStore", func() {
 			Expect(actual.ObjectMeta.ResourceVersion).To(Equal(secret.Meta.GetVersion()))
 		})
 
+		It("should create a new global secret", func() {
+			// given
+			secret := &secret_model.GlobalSecretResource{
+				Spec: &system_proto.Secret{
+					Data: &wrappers.BytesValue{
+						Value: []byte("example"),
+					},
+				},
+			}
+			expected := backend.ParseYAML(`
+            apiVersion: v1
+            kind: Secret
+            type: system.kuma.io/global-secret
+            data:
+              value: ZXhhbXBsZQ== # base64(example)
+`).(*kube_core.Secret)
+
+			// when
+			err := s.Create(context.Background(), secret, store.CreateByKey(name, noMesh))
+
+			// then
+			Expect(err).ToNot(HaveOccurred())
+			// and
+			Expect(secret.Meta.GetName()).To(Equal(name))
+			Expect(secret.Meta.GetMesh()).To(Equal(noMesh))
+			Expect(secret.Meta.GetVersion()).ToNot(Equal(""))
+
+			// when
+			actual := kube_core.Secret{}
+			backend.Get(&actual, ns, name)
+
+			// then
+			Expect(actual.Data).To(Equal(expected.Data))
+			Expect(actual.Type).To(Equal(expected.Type))
+			// and
+			Expect(actual.ObjectMeta.ResourceVersion).To(Equal(secret.Meta.GetVersion()))
+		})
+
 		It("should not create a duplicate resource", func() {
 			// setup
 			backend.AssertNotExists(&kube_core.Secret{}, "ignored", name)
@@ -200,6 +238,58 @@ var _ = Describe("KubernetesStore", func() {
 			Expect(actual.ObjectMeta.ResourceVersion).To(Equal(secret.Meta.GetVersion()))
 		})
 
+		It("should update an existing global secret", func() {
+			// setup
+			initial := backend.ParseYAML(fmt.Sprintf(`
+            apiVersion: v1
+            kind: Secret
+            type: system.kuma.io/global-secret
+            metadata:
+              namespace: %s
+              name: %s
+            data:
+              value: ZXhhbXBsZQ== # base64(example)
+`, ns, name))
+			backend.Create(initial)
+			// and
+			expected := backend.ParseYAML(`
+            apiVersion: v1
+            kind: Secret
+            type: system.kuma.io/global-secret
+            data:
+              value: YW5vdGhlcg== # base64(another)
+`).(*kube_core.Secret)
+
+			// given
+			secret := secret_model.NewGlobalSecretResource()
+
+			// when
+			err := s.Get(context.Background(), secret, store.GetByKey(name, noMesh))
+			// then
+			Expect(err).ToNot(HaveOccurred())
+			version := secret.Meta.GetVersion()
+
+			// when
+			secret.Spec.Data.Value = []byte("another")
+			err = s.Update(context.Background(), secret)
+
+			// then
+			Expect(err).ToNot(HaveOccurred())
+			// and
+			Expect(secret.Meta.GetVersion()).ToNot(Equal(""))
+			Expect(secret.Meta.GetVersion()).ToNot(Equal(version))
+
+			// when
+			actual := kube_core.Secret{}
+			backend.Get(&actual, ns, name)
+
+			// then
+			Expect(actual.Data).To(Equal(expected.Data))
+			Expect(actual.Type).To(Equal(expected.Type))
+			// and
+			Expect(actual.ObjectMeta.ResourceVersion).To(Equal(secret.Meta.GetVersion()))
+		})
+
 		It("should return an error if resource is not found", func() {
 			// setup
 			initial := backend.ParseYAML(fmt.Sprintf(`
@@ -326,6 +416,95 @@ var _ = Describe("KubernetesStore", func() {
 			Expect(actual.Spec.Data.Value).To(Equal([]byte("example")))
 		})
 
+		It("should return an existing global secret", func() {
+			// setup
+			expected := backend.ParseYAML(fmt.Sprintf(`
+            apiVersion: v1
+            kind: Secret
+            type: system.kuma.io/global-secret
+            metadata:
+              namespace: %s
+              name: %s
+            data:
+              value: ZXhhbXBsZQ== # base64(example)
+`, ns, name))
+			backend.Create(expected)
+
+			// given
+			actual := secret_model.NewGlobalSecretResource()
+
+			// when
+			err := s.Get(context.Background(), actual, store.GetByKey(name, noMesh))
+
+			// then
+			Expect(err).ToNot(HaveOccurred())
+			// and
+			Expect(actual.Meta.GetName()).To(Equal(name))
+			Expect(actual.Meta.GetMesh()).To(Equal(noMesh))
+			// and
+			Expect(actual.Spec.Data.Value).To(Equal([]byte("example")))
+		})
+
+		It("should not return global secret as regular secret", func() {
+			// setup
+			expected := backend.ParseYAML(fmt.Sprintf(`
+            apiVersion: v1
+            kind: Secret
+            type: system.kuma.io/global-secret
+            metadata:
+              namespace: %s
+              name: %s
+            data:
+              value: ZXhhbXBsZQ== # base64(example)
+`, ns, name))
+			backend.Create(expected)
+
+			// given
+			actual := secret_model.NewSecretResource()
+
+			// when
+			err := s.Get(context.Background(), actual, store.GetByKey(name, "default"))
+
+			// then
+			Expect(err).To(MatchError(`Resource not found: type="Secret" name="demo" mesh="default"`))
+
+			// when
+			err = s.Get(context.Background(), actual, store.GetByKey(name, noMesh))
+
+			// then
+			Expect(err).To(MatchError(`Resource not found: type="Secret" name="demo" mesh=""`))
+		})
+
+		It("should not return regular secret as global secret", func() {
+			// setup
+			expected := backend.ParseYAML(fmt.Sprintf(`
+            apiVersion: v1
+            kind: Secret
+            type: system.kuma.io/secret
+            metadata:
+              namespace: %s
+              name: %s
+            data:
+              value: ZXhhbXBsZQ== # base64(example)
+`, ns, name))
+			backend.Create(expected)
+
+			// given
+			actual := secret_model.NewGlobalSecretResource()
+
+			// when
+			err := s.Get(context.Background(), actual, store.GetByKey(name, "default"))
+
+			// then
+			Expect(err).To(MatchError(`Resource not found: type="GlobalSecret" name="demo" mesh="default"`))
+
+			// when
+			err = s.Get(context.Background(), actual, store.GetByKey(name, noMesh))
+
+			// then
+			Expect(err).To(MatchError(`Resource not found: type="GlobalSecret" name="demo" mesh=""`))
+		})
+
 		It("should return an existing resource with implicit default mesh", func() {
 			// setup
 			expected := backend.ParseYAML(fmt.Sprintf(`
@@ -384,39 +563,87 @@ var _ = Describe("KubernetesStore", func() {
 	})
 
 	Describe("Delete()", func() {
-		It("should succeed if resource is not found", func() {
+		It("should delete an existing resource", func() {
 			// setup
-			backend.AssertNotExists(&kube_core.Secret{}, ns, name)
+			initial := backend.ParseYAML(fmt.Sprintf(`
+            apiVersion: v1
+            kind: Secret
+            type: system.kuma.io/secret
+            metadata:
+              namespace: %s
+              name: %s
+              labels:
+                kuma.io/mesh: demo
+`, ns, name))
+			backend.Create(initial)
 
 			// when
 			err := s.Delete(context.Background(), secret_model.NewSecretResource(), store.DeleteByKey(name, "demo"))
 
 			// then
 			Expect(err).ToNot(HaveOccurred())
+			// and
+			backend.AssertNotExists(&kube_core.Secret{}, ns, name)
 		})
 
-		It("should delete an existing resource", func() {
+		It("should delete an existing global secret", func() {
 			// setup
 			initial := backend.ParseYAML(fmt.Sprintf(`
             apiVersion: v1
             kind: Secret
-            type: system.kuma.io/secret
+            type: system.kuma.io/global-secret
             metadata:
               namespace: %s
               name: %s
-              labels:
-                kuma.io/mesh: demo
 `, ns, name))
 			backend.Create(initial)
 
 			// when
-			err := s.Delete(context.Background(), secret_model.NewSecretResource(), store.DeleteByKey(name, "demo"))
+			err := s.Delete(context.Background(), secret_model.NewGlobalSecretResource(), store.DeleteByKey(name, noMesh))
 
 			// then
 			Expect(err).ToNot(HaveOccurred())
 			// and
 			backend.AssertNotExists(&kube_core.Secret{}, ns, name)
 		})
+
+		It("should no delete an existing global secret if SecretResource is used", func() {
+			// setup
+			initial := backend.ParseYAML(fmt.Sprintf(`
+            apiVersion: v1
+            kind: Secret
+            type: system.kuma.io/global-secret
+            metadata:
+              namespace: %s
+              name: %s
+`, ns, name))
+			backend.Create(initial)
+
+			// when
+			err := s.Delete(context.Background(), secret_model.NewSecretResource(), store.DeleteByKey(name, noMesh))
+
+			// then
+			Expect(err).To(MatchError(`failed to delete k8s secret: Resource not found: type="Secret" name="demo" mesh=""`))
+		})
+
+		It("should no delete an existing mesh scoped secret if GlobalSecretResource", func() {
+			// setup
+			initial := backend.ParseYAML(fmt.Sprintf(`
+            apiVersion: v1
+            kind: Secret
+            type: system.kuma.io/secret
+            metadata:
+              namespace: %s
+              name: %s
+`, ns, name))
+			backend.Create(initial)
+
+			// when
+			err := s.Delete(context.Background(), secret_model.NewGlobalSecretResource(), store.DeleteByKey(name, "default"))
+
+			// then
+			Expect(err).To(MatchError(`failed to delete k8s secret: Resource not found: type="GlobalSecret" name="demo" mesh="default"`))
+		})
 	})
 
 	Describe("List()", func() {
@@ -477,6 +704,18 @@ var _ = Describe("KubernetesStore", func() {
                   value: YW5vdGhlcg== # base64(another)
 `, ns, "three"))
 				backend.Create(three)
+
+				four := backend.ParseYAML(fmt.Sprintf(`
+                apiVersion: v1
+                kind: Secret
+                type: system.kuma.io/global-secret
+                metadata:
+                  namespace: %s
+                  name: %s
+                data:
+                  value: Zm91cg== # base64(four)
+`, ns, "four"))
+				backend.Create(four)
 			})
 
 			It("should return a list of secrets in all meshes", func() {
@@ -514,6 +753,19 @@ var _ = Describe("KubernetesStore", func() {
 				Expect(secrets.Items).To(HaveLen(1))
 				Expect(secrets.Items[0].Spec.Data.Value).To(Equal([]byte("another")))
 			})
+
+			It("should return a list of global secrets", func() {
+				// given
+				secrets := &secret_model.GlobalSecretResourceList{}
+
+				// when
+				err := s.List(context.Background(), secrets)
+
+				// then
+				Expect(err).ToNot(HaveOccurred())
+				Expect(secrets.Items).To(HaveLen(1))
+				Expect(string(secrets.Items[0].Spec.Data.Value)).To(Equal("four"))
+			})
 		})
 
 	})
diff --git a/test/framework/k8s_cluster.go b/test/framework/k8s_cluster.go
index e57678873..28c464e03 100644
--- a/test/framework/k8s_cluster.go
+++ b/test/framework/k8s_cluster.go
@@ -284,6 +284,10 @@ func (c *K8sCluster) yamlForKumaViaKubectl(mode string, opts *deployOptions) (st
 		args = append(args, k, v)
 	}
 
+	for k, v := range opts.env {
+		args = append(args, "--env-var", fmt.Sprintf("%s=%s", k, v))
+	}
+
 	return c.controlplane.InstallCP(args...)
 }