Repository: mosn/layotto (go)

Issue:
make Secret API ready for production
<!-- Please only use this template for submitting enhancement requests -->

**What would you like to be added**:
Layotto already has the feature of Secret API, see https://github.com/mosn/layotto/pull/343
However, there are still many problems to be solved before it can be used in a production environment, including:
  - [x] Demos and docs. see https://github.com/mosn/layotto/issues/460
  - [ ] Let the components of other APIs refer to the secret component in the configuration file. 
For example, currently if users want to use the redis component for the state API, they should write the password of redis in the configuration file, which is insecure.
To solve this problem, we should allow users to "refer to" the secret component in the configuration and have the secret component automatically obtain the password.
See https://docs.dapr.io/operations/components/component-secrets/

  - [ ] Access control. For example, we should retrict which keys can be accessed by app1, see  https://docs.dapr.io/developing-applications/building-blocks/secrets/secrets-scopes/ 

**Why is this needed**:
To make Secret API ready for production

---

chinese:

虽然 Layotto 支持了 secret API，见 https://github.com/mosn/layotto/pull/343
但是距离在生产环境用，还需要:
  - [ ] 文档补充. https://github.com/mosn/layotto/issues/460
  - [ ] 让其他API的组件在配置文件中可以引用密文。比如，当前用户想用 state API的redis组件时，必须在配置文件中配置 redis 的密码，这不安全。作为解决方案，可以允许用户在配置里”引用“secret 组件，由 secret 组件自动获取密码。见 https://docs.dapr.io/operations/components/component-secrets/
  - [ ] 密钥的访问控制，比如限制app1能访问的密钥的白名单、黑名单等，见 https://docs.dapr.io/developing-applications/building-blocks/secrets/secrets-scopes/ 这个我们也可以配置在Layotto的json配置文件里

Pull request description:
feat: dynamic secret
<!--  Thanks for sending a pull request!
Read contributing.md before commit pull request.
-->

**What this PR does**:

User can reference secrets in their config.json. Layotto will help inject the refered secrets into the component metadata

![image](https://user-images.githubusercontent.com/26001097/177733447-0143f1a5-e80b-41f9-91fd-bf0bfa64c6c4.png)



**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #472

**Special notes for your reviewer**:

**Does this PR introduce a user-facing change?**:
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".
-->
```release-note

```

Patch:
diff --git a/components/custom/config.go b/components/custom/config.go
index 56afd10e..82a79487 100644
--- a/components/custom/config.go
+++ b/components/custom/config.go
@@ -13,7 +13,10 @@
 // limitations under the License.
 package custom
 
+import "mosn.io/layotto/components/ref"
+
 type Config struct {
+	ref.Config
 	Type     string            `json:"type"`
 	Version  string            `json:"version"`
 	Metadata map[string]string `json:"metadata"`
diff --git a/components/lock/types.go b/components/lock/types.go
index ab7d25d2..517a991d 100644
--- a/components/lock/types.go
+++ b/components/lock/types.go
@@ -13,10 +13,13 @@
 // limitations under the License.
 package lock
 
+import "mosn.io/layotto/components/ref"
+
 type Feature string
 
 // Lock's metadata
 type Config struct {
+	ref.Config
 	Type     string            `json:"type"`
 	Metadata map[string]string `json:"metadata"`
 }
diff --git a/components/ref/config.go b/components/ref/config.go
new file mode 100644
index 00000000..7eed9c80
--- /dev/null
+++ b/components/ref/config.go
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2021 Layotto Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ref
+
+//Config is ref json config
+type Config struct {
+	SecretRef []*Item `json:"secret_ref"`
+}
+
+type Item struct {
+	Name string `json:"name"`
+	Key  string `json:"key"`
+}
diff --git a/components/sequencer/types.go b/components/sequencer/types.go
index 4cc366b1..44658a95 100644
--- a/components/sequencer/types.go
+++ b/components/sequencer/types.go
@@ -11,9 +11,13 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+
 package sequencer
 
+import "mosn.io/layotto/components/ref"
+
 type Config struct {
+	ref.Config
 	Type       string            `json:"type"`
 	BiggerThan map[string]int64  `json:"biggerThan"`
 	Metadata   map[string]string `json:"metadata"`
diff --git a/configs/config_ref_example.json b/configs/config_ref_example.json
new file mode 100644
index 00000000..bd9afaf0
--- /dev/null
+++ b/configs/config_ref_example.json
@@ -0,0 +1,96 @@
+{
+  "servers": [
+    {
+      "default_log_path": "stdout",
+      "default_log_level": "DEBUG",
+      "routers": [
+        {
+          "router_config_name": "actuator_dont_need_router"
+        }
+      ],
+      "listeners": [
+        {
+          "name": "grpc",
+          "address": "127.0.0.1:34904",
+          "bind_port": true,
+          "filter_chains": [
+            {
+              "filters": [
+                {
+                  "type": "grpc",
+                  "config": {
+                    "server_name": "runtime",
+                    "grpc_config": {
+                      "hellos": {
+                        "helloworld": {
+                          "type": "helloworld",
+                          "hello": "greeting"
+                        }
+                      },
+                      "secret_store": {
+                        "local.file": {
+                          "type":"local.file",
+                          "metadata": {
+                            "secretsFile": "../../configs/secret/config_secret_local_file.json"
+                          }
+                        },
+                        "local.env": {
+                          "type":"local.env",
+                          "metadata": {
+                          }
+                        }
+                      },
+                      "sequencer": {
+                        "redis": {
+                          "type":"redis",
+                          "metadata": {
+                            "redisHost": "127.0.0.1:6380",
+                            "redisPassword": ""
+                          },
+                          "secret_ref": [
+                            {
+                              "name": "local.file",
+                              "key": "redisPassword"
+                            }
+                          ]
+                        }
+                      },
+                      "app": {
+                        "app_id": "app1",
+                        "grpc_callback_port": 9999
+                      }
+                    }
+                  }
+                }
+              ]
+            }
+          ]
+        },
+        {
+          "name": "actuator",
+          "address": "127.0.0.1:34999",
+          "bind_port": true,
+          "filter_chains": [
+            {
+              "filters": [
+                {
+                  "type": "proxy",
+                  "config": {
+                    "downstream_protocol": "Http1",
+                    "upstream_protocol": "Http1",
+                    "router_config_name": "actuator_dont_need_router"
+                  }
+                }
+              ]
+            }
+          ],
+          "stream_filters": [
+            {
+              "type": "actuator_filter"
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/configs/secret/config_secret_local_file.json b/configs/secret/config_secret_local_file.json
index 80d55f49..f26a0eee 100644
--- a/configs/secret/config_secret_local_file.json
+++ b/configs/secret/config_secret_local_file.json
@@ -2,5 +2,6 @@
   "db-user-pass": {
     "username": "devuser",
     "password": "S!S*d$zDsb="
-  }
+  },
+  "redisPassword": "redis123"
 }
\ No newline at end of file
diff --git a/docs/en/start/secret/secret_ref.md b/docs/en/start/secret/secret_ref.md
new file mode 100644
index 00000000..c1a506fa
--- /dev/null
+++ b/docs/en/start/secret/secret_ref.md
@@ -0,0 +1,87 @@
+# use Secret Ref to obtain secret
+
+we can inject secrets to other components using secret store.
+
+using `secret_ref` config:
+
+```json
+{
+  "sequencer": {
+    "redis": {
+      "type": "redis",
+      "metadata": {
+        "redisHost": "127.0.0.1:6380",
+        "redisPassword": ""
+      },
+      "secret_ref": [
+        {
+          "name": "local.file",
+          "key": "redisPassword"
+        }
+      ]
+    }
+  }
+}
+```
+
+## Quick start
+
+This example shows how to inject redis password to sequencer component using redis store
+
+### Step 0:  Run Redis with password
+
+```shell
+docker run --name redis -p 6380:6379 -d --restart=always redis:5.0.3 redis-server --appendonly yes --requirepass "redis123"
+```
+
+### Step 1:  Run Layotto
+
+After downloading the project code to the local, switch the code directory and compile:
+
+```shell
+cd ${project_path}/cmd/layotto
+```
+
+build:
+
+```shell @if.not.exist layotto
+go build -o layotto
+```
+
+Once finished, the layotto file will be generated in the directory, run it:
+
+```shell @background
+./layotto start -c ../../configs/config_ref_example.json
+```
+
+### Step 2: Run the client program and call Layotto to get the sequence
+
+```shell
+ cd ${project_path}/demo/sequencer/common/
+```
+
+```shell @if.not.exist client
+ go build -o client
+```
+
+```shell
+ ./client -s "redis"
+```
+
+If the following information is printed, the demo is successful:
+
+```bash
+Try to get next id.Key:key666 
+Next id:next_id:1 
+Next id:next_id:2 
+Next id:next_id:3 
+Next id:next_id:4 
+Next id:next_id:5 
+Next id:next_id:6 
+Next id:next_id:7 
+Next id:next_id:8 
+Next id:next_id:9 
+Next id:next_id:10 
+Demo success!
+
+```
\ No newline at end of file
diff --git a/docs/zh/start/secret/secret_ref.md b/docs/zh/start/secret/secret_ref.md
new file mode 100644
index 00000000..2feea757
--- /dev/null
+++ b/docs/zh/start/secret/secret_ref.md
@@ -0,0 +1,88 @@
+# 使用 Secret Ref 注入secret
+
+我们可以用secret store将secrets注入到其他组件。
+
+用 `secret_ref` 来配置:
+
+```json
+{
+  "sequencer": {
+    "redis": {
+      "type": "redis",
+      "metadata": {
+        "redisHost": "127.0.0.1:6380",
+        "redisPassword": ""
+      },
+      "secret_ref": [
+        {
+          "name": "local.file",
+          "key": "redisPassword"
+        }
+      ]
+    }
+  }
+}
+```
+
+## 快速开始
+
+该示例展示了如何注入redis password到sequencer组件
+
+
+### Step 0:  运行redis并初试密码
+
+```shell
+docker run --name redis -p 6380:6379 -d --restart=always redis:5.0.3 redis-server --appendonly yes --requirepass "redis123"
+```
+
+### Step 1:  运行 Layotto
+
+将项目代码下载到本地后，切换代码目录、编译：
+
+```shell
+cd ${project_path}/cmd/layotto
+```
+
+build:
+
+```shell @if.not.exist layotto
+go build -o layotto
+```
+
+完成后目录下会生成layotto文件，运行它：
+
+```shell @background
+./layotto start -c ../../configs/config_ref_example.json
+```
+
+### 第二步：运行客户端程序，调用 Layotto 获取sequence
+
+```shell
+ cd ${project_path}/demo/sequencer/common/
+```
+
+```shell @if.not.exist client
+ go build -o client
+```
+
+```shell
+ ./client -s "redis"
+```
+
+打印出如下信息则代表调用成功：
+
+```bash
+Try to get next id.Key:key666 
+Next id:next_id:1 
+Next id:next_id:2 
+Next id:next_id:3 
+Next id:next_id:4 
+Next id:next_id:5 
+Next id:next_id:6 
+Next id:next_id:7 
+Next id:next_id:8 
+Next id:next_id:9 
+Next id:next_id:10 
+Demo success!
+
+```
\ No newline at end of file
diff --git a/pkg/runtime/bindings/metadata.go b/pkg/runtime/bindings/metadata.go
index 7d1ea084..8f2bb77a 100644
--- a/pkg/runtime/bindings/metadata.go
+++ b/pkg/runtime/bindings/metadata.go
@@ -16,7 +16,10 @@
 
 package bindings
 
+import "mosn.io/layotto/components/ref"
+
 type Metadata struct {
+	ref.Config
 	Type     string `json:"type"`
 	Version  string
 	Metadata map[string]string `json:"metadata"`
diff --git a/pkg/runtime/pubsub/config.go b/pkg/runtime/pubsub/config.go
index 8e7d7307..9e2aa6d7 100644
--- a/pkg/runtime/pubsub/config.go
+++ b/pkg/runtime/pubsub/config.go
@@ -16,8 +16,11 @@
 
 package pubsub
 
+import "mosn.io/layotto/components/ref"
+
 // Config wraps configuration for a pubsub implementation
 type Config struct {
+	ref.Config
 	Type     string            `json:"type"`
 	Metadata map[string]string `json:"metadata"`
 }
diff --git a/pkg/runtime/ref/contianer.go b/pkg/runtime/ref/contianer.go
new file mode 100644
index 00000000..9f1d883d
--- /dev/null
+++ b/pkg/runtime/ref/contianer.go
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2021 Layotto Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ref
+
+import (
+	"github.com/dapr/components-contrib/secretstores"
+
+	"mosn.io/layotto/components/configstores"
+)
+
+// RefContainer  hold all secret&config store
+type RefContainer struct {
+	SecretRef map[string]secretstores.SecretStore
+	ConfigRef map[string]configstores.Store
+}
+
+func NewRefContainer() *RefContainer {
+	return &RefContainer{
+		SecretRef: make(map[string]secretstores.SecretStore),
+		ConfigRef: make(map[string]configstores.Store),
+	}
+}
+
+func (r *RefContainer) GetSecretStore(key string) secretstores.SecretStore {
+	return r.SecretRef[key]
+}
+
+func (r *RefContainer) GetConfigStore(key string) configstores.Store {
+	return r.ConfigRef[key]
+}
diff --git a/pkg/runtime/ref/injector.go b/pkg/runtime/ref/injector.go
new file mode 100644
index 00000000..3972fb13
--- /dev/null
+++ b/pkg/runtime/ref/injector.go
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2021 Layotto Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ref
+
+import (
+	"github.com/dapr/components-contrib/secretstores"
+
+	"mosn.io/layotto/components/ref"
+)
+
+type DefaultInjector struct {
+	Container RefContainer
+}
+
+//InjectSecretRef  inject secret to metaData
+// TODO: permission control
+func (i *DefaultInjector) InjectSecretRef(items []*ref.Item, metaData map[string]string) (map[string]string, error) {
+	if metaData == nil {
+		metaData = make(map[string]string)
+	}
+	if len(items) == 0 {
+		return metaData, nil
+	}
+
+	meta := make(map[string]string)
+	for _, item := range items {
+		store := i.Container.GetSecretStore(item.Name)
+		secret, err := store.GetSecret(secretstores.GetSecretRequest{
+			Name: item.Key,
+		})
+		if err != nil {
+			return metaData, err
+		}
+		for k, v := range secret.Data {
+			meta[k] = v
+		}
+	}
+	//avoid part of assign because of err
+	for k, v := range meta {
+		metaData[k] = v
+	}
+	return metaData, nil
+}
diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go
index 82c9580c..afbe73e8 100644
--- a/pkg/runtime/runtime.go
+++ b/pkg/runtime/runtime.go
@@ -23,6 +23,8 @@ import (
 	"strings"
 	"time"
 
+	"mosn.io/layotto/pkg/runtime/ref"
+
 	"github.com/dapr/components-contrib/secretstores"
 
 	"mosn.io/layotto/components/custom"
@@ -70,6 +72,7 @@ type MosnRuntime struct {
 	bindingsRegistry        mbindings.Registry
 	secretStoresRegistry    msecretstores.Registry
 	customComponentRegistry custom.Registry
+	Injector                *ref.DefaultInjector
 	// component pool
 	hellos map[string]hello.HelloService
 	// config management system component
@@ -231,13 +234,23 @@ func DefaultInitRuntimeStage(o *runtimeOptions, m *MosnRuntime) error {
 		return err
 	}
 	// init all kinds of components with config
-	if err := m.initCustomComponents(o.services.custom); err != nil {
+	//init secret & config first
+	if err := m.initSecretStores(o.services.secretStores...); err != nil {
 		return err
 	}
-	if err := m.initHellos(o.services.hellos...); err != nil {
+	if err := m.initConfigStores(o.services.configStores...); err != nil {
 		return err
 	}
-	if err := m.initConfigStores(o.services.configStores...); err != nil {
+	m.Injector = &ref.DefaultInjector{
+		Container: ref.RefContainer{
+			SecretRef: m.secretStores,
+			ConfigRef: m.configStores,
+		},
+	}
+	if err := m.initCustomComponents(o.services.custom); err != nil {
+		return err
+	}
+	if err := m.initHellos(o.services.hellos...); err != nil {
 		return err
 	}
 	if err := m.initStates(o.services.states...); err != nil {
@@ -261,10 +274,7 @@ func DefaultInitRuntimeStage(o *runtimeOptions, m *MosnRuntime) error {
 	if err := m.initSequencers(o.services.sequencers...); err != nil {
 		return err
 	}
-	if err := m.initInputBinding(o.services.inputBinding...); err != nil {
-		return err
-	}
-	return m.initSecretStores(o.services.secretStores...)
+	return m.initInputBinding(o.services.inputBinding...)
 }
 
 func (m *MosnRuntime) initHellos(hellos ...*hello.HelloFactory) error {
@@ -345,6 +355,10 @@ func (m *MosnRuntime) initPubSubs(factorys ...*runtime_pubsub.Factory) error {
 			}
 			config.Metadata["consumerID"] = m.runtimeConfig.AppManagement.AppId
 		}
+		//inject secret to component
+		if config.Metadata, err = m.Injector.InjectSecretRef(config.SecretRef, config.Metadata); err != nil {
+			return err
+		}
 		// init this component with the config
 		if err := comp.Init(pubsub.Metadata{Properties: config.Metadata}); err != nil {
 			m.errInt(err, "init pubsub component %s failed", name)
@@ -368,6 +382,10 @@ func (m *MosnRuntime) initStates(factorys ...*runtime_state.Factory) error {
 			m.errInt(err, "create state component %s failed", name)
 			return err
 		}
+		//inject secret to component
+		if config.Metadata, err = m.Injector.InjectSecretRef(config.SecretRef, config.Metadata); err != nil {
+			return err
+		}
 		if err := comp.Init(state.Metadata{Properties: config.Metadata}); err != nil {
 			m.errInt(err, "init state component %s failed", name)
 			return err
@@ -415,6 +433,10 @@ func (m *MosnRuntime) initLocks(factorys ...*runtime_lock.Factory) error {
 			m.errInt(err, "create lock component %s failed", name)
 			return err
 		}
+		//inject secret to component
+		if config.Metadata, err = m.Injector.InjectSecretRef(config.SecretRef, config.Metadata); err != nil {
+			return err
+		}
 		// 2.2. init
 		if err := comp.Init(lock.Metadata{Properties: config.Metadata}); err != nil {
 			m.errInt(err, "init lock component %s failed", name)
@@ -443,6 +465,10 @@ func (m *MosnRuntime) initSequencers(factorys ...*runtime_sequencer.Factory) err
 			m.errInt(err, "create sequencer component %s failed", name)
 			return err
 		}
+		//inject secret to component
+		if config.Metadata, err = m.Injector.InjectSecretRef(config.SecretRef, config.Metadata); err != nil {
+			return err
+		}
 		// 2.2. init
 		if err = comp.Init(sequencer.Configuration{
 			Properties: config.Metadata,
@@ -501,6 +527,10 @@ func (m *MosnRuntime) initOutputBinding(factorys ...*mbindings.OutputBindingFact
 			m.errInt(err, "create outbinding component %s failed", name)
 			return err
 		}
+		//inject secret to component
+		if config.Metadata, err = m.Injector.InjectSecretRef(config.SecretRef, config.Metadata); err != nil {
+			return err
+		}
 		// 2.2. init
 		if err := comp.Init(bindings.Metadata{Name: name, Properties: config.Metadata}); err != nil {
 			m.errInt(err, "init outbinding component %s failed", name)
@@ -606,6 +636,10 @@ func (m *MosnRuntime) initCustomComponents(kind2factorys map[string][]*custom.Co
 				m.errInt(err, "create custom component %s failed", name)
 				return err
 			}
+			//inject secret to component
+			if config.Metadata, err = m.Injector.InjectSecretRef(config.SecretRef, config.Metadata); err != nil {
+				return err
+			}
 			// init
 			if err := comp.Initialize(context.TODO(), config); err != nil {
 				m.errInt(err, "init custom component %s failed", name)
diff --git a/pkg/runtime/state/config.go b/pkg/runtime/state/config.go
index 08e60f85..5f95a57e 100644
--- a/pkg/runtime/state/config.go
+++ b/pkg/runtime/state/config.go
@@ -16,8 +16,11 @@
 
 package state
 
+import "mosn.io/layotto/components/ref"
+
 // Config wraps configuration for a state implementation
 type Config struct {
+	ref.Config
 	Type     string            `json:"type"`
 	Metadata map[string]string `json:"metadata"`
 }


Test patch:
diff --git a/etc/script/test-quickstart.sh b/etc/script/test-quickstart.sh
index 813045a2..ba8f1b46 100755
--- a/etc/script/test-quickstart.sh
+++ b/etc/script/test-quickstart.sh
@@ -39,6 +39,8 @@ quickstarts_in_default="docs/en/start/configuration/start.md
   docs/zh/start/wasm/start.md
   docs/en/start/secret/start.md
   docs/zh/start/secret/start.md
+  docs/en/start/secret/secret_ref.md
+  docs/zh/start/secret/secret_ref.md
 "
 
 # In advance mod, we test these docs with golang 1.17
diff --git a/pkg/runtime/ref/contianer_test.go b/pkg/runtime/ref/contianer_test.go
new file mode 100644
index 00000000..0a86a69b
--- /dev/null
+++ b/pkg/runtime/ref/contianer_test.go
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2021 Layotto Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ref
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"mosn.io/layotto/pkg/mock"
+	"mosn.io/layotto/pkg/mock/components/secret"
+)
+
+func TestRefContainer(t *testing.T) {
+
+	container := NewRefContainer()
+
+	ss := &secret.FakeSecretStore{}
+	container.SecretRef["fake"] = ss
+	cf := &mock.MockStore{}
+	container.ConfigRef["mock"] = cf
+	assert.Equal(t, ss, container.GetSecretStore("fake"))
+	assert.Equal(t, cf, container.GetConfigStore("mock"))
+
+}
diff --git a/pkg/runtime/ref/injector_test.go b/pkg/runtime/ref/injector_test.go
new file mode 100644
index 00000000..6a32c9fe
--- /dev/null
+++ b/pkg/runtime/ref/injector_test.go
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2021 Layotto Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ref
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"mosn.io/layotto/components/ref"
+	"mosn.io/layotto/pkg/mock"
+	"mosn.io/layotto/pkg/mock/components/secret"
+)
+
+func TestInject(t *testing.T) {
+
+	container := NewRefContainer()
+
+	ss := &secret.FakeSecretStore{}
+	container.SecretRef["fake_secret_store"] = ss
+	cf := &mock.MockStore{}
+	container.ConfigRef["mock_config_store"] = cf
+
+	injector := DefaultInjector{Container: *container}
+	meta := make(map[string]string)
+	var items []*ref.Item
+	items = append(items, &ref.Item{
+		Name: "fake_secret_store",
+		Key:  "good-key",
+	})
+	injector.InjectSecretRef(items, meta)
+	assert.Equal(t, meta["good-key"], "life is good")
+
+}