mirror of https://github.com/halo-dev/halo
feat: provide a secret extension to store sensitive data (#3594)
#### What type of PR is this? /kind feature /milestone 2.4.x /area core #### What this PR does / why we need it: 提供 Secret 自定义模型用于存储敏感数据 例如:密码、token 等 参考自: https://kubernetes.io/docs/concepts/configuration/secret #### Which issue(s) this PR fixes: Fixes #3267 #### Does this PR introduce a user-facing change? ```release-note 提供 Secret 自定义模型用于存储敏感数据 ```pull/3568/head
parent
403702021c
commit
3339b381c8
|
@ -0,0 +1,54 @@
|
|||
package run.halo.app.extension;
|
||||
|
||||
import java.util.Map;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* Secret is a small piece of sensitive data which should be kept secret, such as a password,
|
||||
* a token, or a key.
|
||||
*
|
||||
* @author guqing
|
||||
* @see
|
||||
* <a href="https://github.com/kubernetes/kubernetes/blob/f33498a8256b455b677ad4d30440869318b84204/staging/src/k8s.io/api/core/v1/types.go">kebernetes Secret</a>
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@GVK(group = "", version = "v1alpha1", kind = Secret.KIND, plural = "secrets", singular = "secret")
|
||||
public class Secret extends AbstractExtension {
|
||||
public static final String KIND = "Secret";
|
||||
|
||||
public static final String SECRET_TYPE_OPAQUE = "Opaque";
|
||||
|
||||
public static final int MAX_SECRET_SIZE = 1024 * 1024;
|
||||
|
||||
/**
|
||||
* Used to facilitate programmatic handling of secret data.
|
||||
* More info:
|
||||
* <a href="https://kubernetes.io/docs/concepts/configuration/secret/#secret-types">secret-types</a>
|
||||
*/
|
||||
private String type;
|
||||
|
||||
/**
|
||||
* <p>The total bytes of the values in
|
||||
* the Data field must be less than {@link #MAX_SECRET_SIZE} bytes.</p>
|
||||
* <p>{@code data} contains the secret data. Each key must consist of alphanumeric
|
||||
* characters, '-', '_' or '.'. The serialized form of the secret data is a
|
||||
* base64 encoded string, representing the arbitrary (possibly non-string)
|
||||
* data value here. Described in
|
||||
* <a href="https://tools.ietf.org/html/rfc4648#section-4">rfc4648#section-4</a>
|
||||
* </p>
|
||||
*/
|
||||
private Map<String, byte[]> data;
|
||||
|
||||
/**
|
||||
* {@code stringData} allows specifying non-binary secret data in string form.
|
||||
* It is provided as a write-only input field for convenience.
|
||||
* All keys and values are merged into the data field on write, overwriting any existing
|
||||
* values.
|
||||
* The stringData field is never output when reading from the API.
|
||||
*/
|
||||
private Map<String, String> stringData;
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
package run.halo.app.extension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
|
||||
import java.util.Map;
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.skyscreamer.jsonassert.JSONAssert;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
|
||||
/**
|
||||
* Tests for {@link Secret}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.4.0
|
||||
*/
|
||||
class SecretTest {
|
||||
|
||||
@Test
|
||||
void serialize() throws JSONException {
|
||||
Secret secret = new Secret();
|
||||
secret.setMetadata(new Metadata());
|
||||
secret.getMetadata().setName("test-secret");
|
||||
secret.setType(Secret.SECRET_TYPE_OPAQUE);
|
||||
secret.setData(Map.of("password", "admin".getBytes()));
|
||||
String s = JsonUtils.objectToJson(secret);
|
||||
JSONAssert.assertEquals(testJsonString(), s, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserialize() {
|
||||
String s = testJsonString();
|
||||
Secret secret = JsonUtils.jsonToObject(s, Secret.class);
|
||||
assertThat(secret).isNotNull();
|
||||
assertThat(secret.getMetadata().getName()).isEqualTo("test-secret");
|
||||
assertThat(secret.getType()).isEqualTo(Secret.SECRET_TYPE_OPAQUE);
|
||||
assertThat(secret.getData()).containsEntry("password", "admin".getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeWithUnstructured() throws JsonProcessingException {
|
||||
Secret secret = Unstructured.OBJECT_MAPPER.readValue(testJsonString(), Secret.class);
|
||||
assertThat(secret.getMetadata().getName()).isEqualTo("test-secret");
|
||||
assertThat(secret.getType()).isEqualTo(Secret.SECRET_TYPE_OPAQUE);
|
||||
assertThat(secret.getData()).containsEntry("password", "admin".getBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeYamlWithStringData() throws JsonProcessingException {
|
||||
String s = """
|
||||
apiVersion: v1alpha1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: secret-basic-auth
|
||||
type: halo.run/basic-auth
|
||||
stringData:
|
||||
username: admin
|
||||
password: t0p-Secret
|
||||
""";
|
||||
Secret secret = new YAMLMapper().readValue(s, Secret.class);
|
||||
assertThat(secret.getMetadata().getName()).isEqualTo("secret-basic-auth");
|
||||
assertThat(secret.getType()).isEqualTo("halo.run/basic-auth");
|
||||
assertThat(secret.getStringData()).containsEntry("username", "admin");
|
||||
assertThat(secret.getStringData()).containsEntry("password", "t0p-Secret");
|
||||
}
|
||||
|
||||
private String testJsonString() {
|
||||
return """
|
||||
{
|
||||
"apiVersion": "v1alpha1",
|
||||
"kind": "Secret",
|
||||
"metadata": {
|
||||
"name": "test-secret"
|
||||
},
|
||||
"type": "Opaque",
|
||||
"data": {
|
||||
"password": "YWRtaW4="
|
||||
}
|
||||
}
|
||||
""";
|
||||
}
|
||||
}
|
|
@ -31,6 +31,7 @@ import run.halo.app.core.extension.content.Snapshot;
|
|||
import run.halo.app.core.extension.content.Tag;
|
||||
import run.halo.app.extension.ConfigMap;
|
||||
import run.halo.app.extension.SchemeManager;
|
||||
import run.halo.app.extension.Secret;
|
||||
import run.halo.app.plugin.extensionpoint.ExtensionDefinition;
|
||||
import run.halo.app.plugin.extensionpoint.ExtensionPointDefinition;
|
||||
import run.halo.app.search.extension.SearchEngine;
|
||||
|
@ -66,6 +67,7 @@ public class SchemeInitializer implements ApplicationListener<ApplicationStarted
|
|||
schemeManager.register(Setting.class);
|
||||
schemeManager.register(AnnotationSetting.class);
|
||||
schemeManager.register(ConfigMap.class);
|
||||
schemeManager.register(Secret.class);
|
||||
schemeManager.register(Theme.class);
|
||||
schemeManager.register(Menu.class);
|
||||
schemeManager.register(MenuItem.class);
|
||||
|
|
Loading…
Reference in New Issue