diff --git a/api/src/main/java/run/halo/app/extension/Secret.java b/api/src/main/java/run/halo/app/extension/Secret.java new file mode 100644 index 000000000..ce029d807 --- /dev/null +++ b/api/src/main/java/run/halo/app/extension/Secret.java @@ -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 + * kebernetes Secret + * @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: + * secret-types + */ + private String type; + + /** + *

The total bytes of the values in + * the Data field must be less than {@link #MAX_SECRET_SIZE} bytes.

+ *

{@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 + * rfc4648#section-4 + *

+ */ + private Map 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 stringData; + +} diff --git a/api/src/test/java/run/halo/app/extension/SecretTest.java b/api/src/test/java/run/halo/app/extension/SecretTest.java new file mode 100644 index 000000000..af18827f7 --- /dev/null +++ b/api/src/test/java/run/halo/app/extension/SecretTest.java @@ -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=" + } + } + """; + } +} diff --git a/application/src/main/java/run/halo/app/infra/SchemeInitializer.java b/application/src/main/java/run/halo/app/infra/SchemeInitializer.java index 326e27108..ce5f21ba8 100644 --- a/application/src/main/java/run/halo/app/infra/SchemeInitializer.java +++ b/application/src/main/java/run/halo/app/infra/SchemeInitializer.java @@ -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