nacos_sdk/api/plugin/
encryption.rs

1use crate::api::plugin::{ConfigFilter, ConfigReq, ConfigResp};
2
3/**
4 * For example:cipher-AES-dataId.
5 */
6pub const DEFAULT_CIPHER_PREFIX: &str = "cipher-";
7
8pub const DEFAULT_CIPHER_SPLIT: &str = "-";
9
10/// EncryptionPlugin for Config.
11#[async_trait::async_trait]
12pub trait EncryptionPlugin: Send + Sync {
13    /**
14     * Whether need to do cipher.
15     *
16     * e.g. data_id = "cipher-AES-dataId"
17     */
18    fn need_cipher(&self, data_id: &str) -> bool {
19        data_id.starts_with(DEFAULT_CIPHER_PREFIX)
20            && self
21                .parse_algorithm_name(data_id)
22                .eq(&self.algorithm_name())
23    }
24
25    /**
26     * Parse encryption algorithm name.
27     *
28     * @param data_id data_id
29     * @return algorithm name
30     */
31    fn parse_algorithm_name(&self, data_id: &str) -> String {
32        data_id
33            .split(DEFAULT_CIPHER_SPLIT)
34            .nth(1)
35            .unwrap()
36            .to_string()
37    }
38
39    /**
40     * Encrypt interface.
41     *
42     * @param secret_key secret key
43     * @param content   content unencrypted
44     * @return encrypt value
45     */
46    async fn encrypt(&self, secret_key: &str, content: &str) -> String;
47
48    /**
49     * Decrypt interface.
50     *
51     * @param secret_key secret key
52     * @param content   encrypted
53     * @return decrypt value
54     */
55    async fn decrypt(&self, secret_key: &str, content: &str) -> String;
56
57    /**
58     * Generate secret key. It only be known by you.
59     *
60     * @return Secret key
61     */
62    async fn generate_secret_key(&self) -> String;
63
64    /**
65     * Algorithm name. e.g. AES,AES128,AES256,DES,3DES,...
66     *
67     * @return name
68     */
69    fn algorithm_name(&self) -> String;
70
71    /**
72     * Encrypt secret Key. It will be transmitted in the network.
73     *
74     * @param secretKey secretKey
75     * @return encrypted secretKey
76     */
77    async fn encrypt_secret_key(&self, secret_key: &str) -> String;
78
79    /**
80     * Decrypt secret Key.
81     *
82     * @param secret_key secretKey
83     * @return decrypted secretKey
84     */
85    async fn decrypt_secret_key(&self, secret_key: &str) -> String;
86}
87
88/// ConfigEncryptionFilter handle with [`EncryptionPlugin`]
89pub struct ConfigEncryptionFilter {
90    encryption_plugins: Vec<Box<dyn EncryptionPlugin>>,
91}
92
93impl ConfigEncryptionFilter {
94    pub fn new(encryption_plugins: Vec<Box<dyn EncryptionPlugin>>) -> Self {
95        Self { encryption_plugins }
96    }
97}
98
99#[async_trait::async_trait]
100impl ConfigFilter for ConfigEncryptionFilter {
101    async fn filter(
102        &self,
103        config_req: Option<&mut ConfigReq>,
104        config_resp: Option<&mut ConfigResp>,
105    ) {
106        // Publish configuration, encrypt
107        if let Some(config_req) = config_req {
108            for plugin in &self.encryption_plugins {
109                if !plugin.need_cipher(&config_req.data_id) {
110                    continue;
111                }
112
113                let secret_key = plugin.generate_secret_key().await;
114                let encrypted_content = plugin.encrypt(&secret_key, &config_req.content).await;
115                let encrypted_secret_key = plugin.encrypt_secret_key(&secret_key).await;
116
117                // set encrypted data.
118                config_req.encrypted_data_key = encrypted_secret_key;
119                config_req.content = encrypted_content;
120                break;
121            }
122        }
123
124        // Get configuration, decrypt
125        if let Some(config_resp) = config_resp {
126            if !config_resp.encrypted_data_key.is_empty() {
127                for plugin in &self.encryption_plugins {
128                    if !plugin.need_cipher(&config_resp.data_id) {
129                        continue;
130                    }
131
132                    // get encrypted data.
133                    let encrypted_secret_key = &config_resp.encrypted_data_key;
134                    let encrypted_content = &config_resp.content;
135
136                    let decrypted_secret_key =
137                        plugin.decrypt_secret_key(encrypted_secret_key).await;
138                    let decrypted_content = plugin
139                        .decrypt(&decrypted_secret_key, encrypted_content)
140                        .await;
141
142                    // set decrypted data.
143                    config_resp.content = decrypted_content;
144                    break;
145                }
146            }
147        }
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use crate::api::plugin::config_filter::{ConfigReq, ConfigResp};
154    use crate::api::plugin::encryption::DEFAULT_CIPHER_PREFIX;
155    use crate::api::plugin::{ConfigEncryptionFilter, ConfigFilter, EncryptionPlugin};
156
157    struct TestEncryptionPlugin;
158
159    #[async_trait::async_trait]
160    impl EncryptionPlugin for TestEncryptionPlugin {
161        async fn encrypt(&self, secret_key: &str, content: &str) -> String {
162            secret_key.to_owned() + content
163        }
164
165        async fn decrypt(&self, secret_key: &str, content: &str) -> String {
166            content.replace(secret_key, "")
167        }
168
169        async fn generate_secret_key(&self) -> String {
170            "secret-key".to_string()
171        }
172
173        fn algorithm_name(&self) -> String {
174            "TEST".to_string()
175        }
176
177        async fn encrypt_secret_key(&self, secret_key: &str) -> String {
178            "crypt_".to_owned() + secret_key
179        }
180
181        async fn decrypt_secret_key(&self, secret_key: &str) -> String {
182            secret_key.replace("crypt_", "")
183        }
184    }
185
186    #[tokio::test]
187    async fn test_config_encryption_filters_empty() {
188        let config_encryption_filter = ConfigEncryptionFilter::new(vec![]);
189
190        let (data_id, group, namespace, content, encrypted_data_key) = (
191            "D".to_string(),
192            "G".to_string(),
193            "N".to_string(),
194            "C".to_string(),
195            "".to_string(),
196        );
197
198        let mut config_req = ConfigReq::new(
199            data_id.clone(),
200            group.clone(),
201            namespace.clone(),
202            content.clone(),
203            encrypted_data_key.clone(),
204        );
205        config_encryption_filter
206            .filter(Some(&mut config_req), None)
207            .await;
208
209        assert_eq!(config_req.content, encrypted_data_key + content.as_str());
210
211        let mut config_resp = ConfigResp::new(
212            config_req.data_id.clone(),
213            config_req.group.clone(),
214            config_req.namespace.clone(),
215            config_req.content.clone(),
216            config_req.encrypted_data_key.clone(),
217        );
218        config_encryption_filter
219            .filter(None, Some(&mut config_resp))
220            .await;
221
222        assert_eq!(config_resp.content, content);
223    }
224
225    #[tokio::test]
226    async fn test_config_encryption_filters() {
227        let config_encryption_filter =
228            ConfigEncryptionFilter::new(vec![Box::new(TestEncryptionPlugin {})]);
229
230        let (data_id, group, namespace, content, encrypted_data_key) = (
231            DEFAULT_CIPHER_PREFIX.to_owned() + "-TEST-D",
232            "G".to_string(),
233            "N".to_string(),
234            "C".to_string(),
235            "E".to_string(),
236        );
237
238        let mut config_req = ConfigReq::new(
239            data_id.clone(),
240            group.clone(),
241            namespace.clone(),
242            content.clone(),
243            encrypted_data_key.clone(),
244        );
245        config_encryption_filter
246            .filter(Some(&mut config_req), None)
247            .await;
248
249        let mut config_resp = ConfigResp::new(
250            config_req.data_id.clone(),
251            config_req.group.clone(),
252            config_req.namespace.clone(),
253            config_req.content.clone(),
254            config_req.encrypted_data_key.clone(),
255        );
256        config_encryption_filter
257            .filter(None, Some(&mut config_resp))
258            .await;
259
260        assert_eq!(config_resp.content, content);
261    }
262
263    #[tokio::test]
264    async fn test_config_encryption_filters_not_need_cipher() {
265        let config_encryption_filter =
266            ConfigEncryptionFilter::new(vec![Box::new(TestEncryptionPlugin {})]);
267
268        let (data_id, group, namespace, content, encrypted_data_key) = (
269            "D".to_string(),
270            "G".to_string(),
271            "N".to_string(),
272            "C".to_string(),
273            "E".to_string(),
274        );
275
276        let mut config_req = ConfigReq::new(
277            data_id.clone(),
278            group.clone(),
279            namespace.clone(),
280            content.clone(),
281            encrypted_data_key.clone(),
282        );
283        config_encryption_filter
284            .filter(Some(&mut config_req), None)
285            .await;
286
287        let mut config_resp = ConfigResp::new(
288            config_req.data_id.clone(),
289            config_req.group.clone(),
290            config_req.namespace.clone(),
291            config_req.content.clone(),
292            config_req.encrypted_data_key.clone(),
293        );
294        config_encryption_filter
295            .filter(None, Some(&mut config_resp))
296            .await;
297
298        assert_eq!(config_resp.content, content);
299    }
300}