This is an automated email from the ASF dual-hosted git repository.

apkhmv pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 785ef6baec IGNITE-19166 Introduce @Secret annotation for configuration 
framework (#2073)
785ef6baec is described below

commit 785ef6baeca64d0594e16497d6b2d5dd4824b285
Author: Ivan Gagarkin <[email protected]>
AuthorDate: Wed May 17 14:54:23 2023 +0400

    IGNITE-19166 Introduce @Secret annotation for configuration framework 
(#2073)
---
 .../processor/ItConfigurationProcessorTest.java    |   8 ++
 .../SecretMustBeStringConfigurationSchema.java}    |  17 ++--
 .../processor/ConfigurationProcessor.java          |  11 +++
 .../ignite/configuration/annotation/Secret.java}   |  21 ++--
 modules/configuration/build.gradle                 |   1 +
 .../configuration/hocon/HoconConverter.java        |   7 +-
 .../configuration/tree/ConverterToMapVisitor.java  |  56 ++++++++---
 .../tree/ConverterToMapVisitorBuilder.java         |  58 +++++++++++
 .../tree/ConverterToMapVisitorTest.java            | 106 +++++++++++++++++++++
 .../configuration/util/ConfigurationUtilTest.java  |  33 ++++++-
 .../org.mockito.plugins.MockMaker                  |   2 +
 .../ignite/internal/index/IndexManagerTest.java    |   8 +-
 .../configuration/KeyStoreConfigurationSchema.java |   2 +
 .../KeyStoreConfigurationSchemaTest.java}          |  29 +++---
 .../AbstractConfigurationController.java           |  29 +-----
 .../internal/rest/configuration/JsonMasker.java    |  94 ------------------
 .../ConfigurationControllerBaseTest.java           |  15 ++-
 .../rest/configuration/JsonMaskerTest.java         | 105 --------------------
 .../TestSubSensitiveConfigurationSchema.java       |   2 +
 .../storage/LocalFileConfigurationStorage.java     |  10 +-
 ...cAuthenticationProviderConfigurationSchema.java |   2 +
 ...enticationProviderConfigurationSchemaTest.java} |  26 ++---
 22 files changed, 350 insertions(+), 292 deletions(-)

diff --git 
a/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ItConfigurationProcessorTest.java
 
b/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ItConfigurationProcessorTest.java
index fee7307113..b47f017e45 100644
--- 
a/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ItConfigurationProcessorTest.java
+++ 
b/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ItConfigurationProcessorTest.java
@@ -547,6 +547,14 @@ public class ItConfigurationProcessorTest extends 
AbstractProcessorTest {
                 () -> batchCompile(packageName, 
"ConfigMustNotContainInternalIdWithAbstractConfigConfigurationSchema"),
                 "Field with @InternalId is already present in the superclass"
         );
+
+        // Let's check @Secret must be String.
+
+        assertThrowsEx(
+                IllegalStateException.class,
+                () -> batchCompile(packageName, 
"SecretMustBeStringConfigurationSchema"),
+                "must be String. Only String field can be annotated with 
@Secret"
+        );
     }
 
     @Test
diff --git 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/TestSubSensitiveConfigurationSchema.java
 
b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/abstractconfig/validation/SecretMustBeStringConfigurationSchema.java
similarity index 58%
copy from 
modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/TestSubSensitiveConfigurationSchema.java
copy to 
modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/abstractconfig/validation/SecretMustBeStringConfigurationSchema.java
index acfb752c1f..de38c6217f 100644
--- 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/TestSubSensitiveConfigurationSchema.java
+++ 
b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/abstractconfig/validation/SecretMustBeStringConfigurationSchema.java
@@ -15,17 +15,20 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.rest.configuration;
+package 
org.apache.ignite.internal.configuration.processor.abstractconfig.validation;
 
+import java.util.UUID;
+import org.apache.ignite.configuration.annotation.AbstractConfiguration;
 import org.apache.ignite.configuration.annotation.Config;
-import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.annotation.InternalId;
+import org.apache.ignite.configuration.annotation.Secret;
+import 
org.apache.ignite.internal.configuration.processor.abstractconfig.AbstractConfigConfigurationSchema;
 
 /**
- * Test sub sensitive configuration schema.
+ * Checks that field annotated with {@link 
org.apache.ignite.configuration.annotation.Secret} must be {@link String}.
  */
 @Config
-public class TestSubSensitiveConfigurationSchema {
-
-    @Value(hasDefault = true)
-    public String password = "";
+public class SecretMustBeStringConfigurationSchema {
+    @Secret
+    public int id;
 }
diff --git 
a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessor.java
 
b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessor.java
index 9c743b55a5..3e8775c304 100644
--- 
a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessor.java
+++ 
b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/ConfigurationProcessor.java
@@ -85,6 +85,7 @@ import 
org.apache.ignite.configuration.annotation.NamedConfigValue;
 import org.apache.ignite.configuration.annotation.PolymorphicConfig;
 import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
 import org.apache.ignite.configuration.annotation.PolymorphicId;
+import org.apache.ignite.configuration.annotation.Secret;
 import org.apache.ignite.configuration.annotation.Value;
 import org.jetbrains.annotations.Nullable;
 
@@ -112,6 +113,8 @@ public class ConfigurationProcessor extends 
AbstractProcessor {
     /** Error format is that the field must be a specific class. */
     private static final String FIELD_MUST_BE_SPECIFIC_CLASS_ERROR_FORMAT = 
"%s %s.%s field must be a %s";
 
+    private static final String SECRET_FIELD_MUST_BE_STRING = "%s.%s must be 
String. Only String field can be annotated with @Secret";
+
     /** Postfix with which any configuration schema class name must end. */
     private static final String CONFIGURATION_SCHEMA_POSTFIX = 
"ConfigurationSchema";
 
@@ -226,6 +229,14 @@ public class ConfigurationProcessor extends 
AbstractProcessor {
                     }
                 }
 
+                if (field.getAnnotation(Secret.class) != null) {
+                    if (!isClass(field.asType(), String.class)) {
+                        throw new ConfigurationProcessorException(
+                                String.format(SECRET_FIELD_MUST_BE_STRING, 
clazz.getQualifiedName(), field.getSimpleName())
+                        );
+                    }
+                }
+
                 createGetters(configurationInterfaceBuilder, fieldName, 
interfaceGetMethodType);
             }
 
diff --git 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/TestSubSensitiveConfigurationSchema.java
 
b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/Secret.java
similarity index 65%
copy from 
modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/TestSubSensitiveConfigurationSchema.java
copy to 
modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/Secret.java
index acfb752c1f..6e72ad85f0 100644
--- 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/TestSubSensitiveConfigurationSchema.java
+++ 
b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/Secret.java
@@ -15,17 +15,20 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.rest.configuration;
+package org.apache.ignite.configuration.annotation;
 
-import org.apache.ignite.configuration.annotation.Config;
-import org.apache.ignite.configuration.annotation.Value;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
 
 /**
- * Test sub sensitive configuration schema.
+ * This annotation is used to mark a configuration field as sensitive 
information.
  */
-@Config
-public class TestSubSensitiveConfigurationSchema {
-
-    @Value(hasDefault = true)
-    public String password = "";
+@Target(FIELD)
+@Retention(RUNTIME)
+@Documented
+public @interface Secret {
 }
diff --git a/modules/configuration/build.gradle 
b/modules/configuration/build.gradle
index 1093086543..59dda0d1b4 100644
--- a/modules/configuration/build.gradle
+++ b/modules/configuration/build.gradle
@@ -35,6 +35,7 @@ dependencies {
     testImplementation(testFixtures(project(':ignite-core')))
     testImplementation libs.hamcrest.core
     testImplementation libs.mockito.core
+    testImplementation libs.mockito.junit
 
     testFixturesAnnotationProcessor 
project(':ignite-configuration-annotation-processor')
     testFixturesImplementation project(':ignite-core')
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconConverter.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconConverter.java
index a6ca6223ee..74bdb1d223 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconConverter.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconConverter.java
@@ -42,7 +42,12 @@ public class HoconConverter {
             ConfigurationRegistry registry,
             @NotNull List<String> path
     ) throws IllegalArgumentException {
-        Object res = registry.represent(path, new 
ConverterToMapVisitor(false));
+        Object res = registry.represent(
+                path,
+                ConverterToMapVisitor.builder()
+                        .includeInternal(false)
+                        .maskSecretValues(true)
+                        .build());
 
         return ConfigImpl.fromAnyRef(res, null);
     }
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
index c4a26c1d88..76a396e6f7 100644
--- 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
@@ -31,38 +31,47 @@ import java.util.UUID;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
+import org.apache.ignite.configuration.annotation.Secret;
 
 /**
  * {@link ConfigurationVisitor} implementation that converts a configuration 
tree to a combination of {@link Map} and {@link List} objects.
  */
 public class ConverterToMapVisitor implements ConfigurationVisitor<Object> {
+
+    /** Leaf node value will be replaced with this string, if the {@link 
Field} of the value has {@link Secret} annotation. */
+    private static final String MASKED_VALUE = "********";
+
     /** Include internal configuration nodes (private configuration 
extensions). */
     private final boolean includeInternal;
 
     /** Skip nulls, empty Maps and empty lists. */
     private final boolean skipEmptyValues;
 
+    /** Mask values, if their {@link Field} has {@link Secret} annotation. */
+    private final boolean maskSecretValues;
+
     /** Stack with intermediate results. Used to store values during recursive 
calls. */
     private final Deque<Object> deque = new ArrayDeque<>();
 
+    public static ConverterToMapVisitorBuilder builder() {
+        return new ConverterToMapVisitorBuilder();
+    }
+
     /**
      * Constructor.
      *
      * @param includeInternal Include internal configuration nodes (private 
configuration extensions).
      * @param skipEmptyValues Skip empty values.
+     * @param maskSecretValues Mask values, if their {@link Field} has {@link 
Secret} annotation.
      */
-    public ConverterToMapVisitor(boolean includeInternal, boolean 
skipEmptyValues) {
+    ConverterToMapVisitor(
+            boolean includeInternal,
+            boolean skipEmptyValues,
+            boolean maskSecretValues
+    ) {
         this.includeInternal = includeInternal;
         this.skipEmptyValues = skipEmptyValues;
-    }
-
-    /**
-     * Constructor.
-     *
-     * @param includeInternal Include internal configuration nodes (private 
configuration extensions).
-     */
-    public ConverterToMapVisitor(boolean includeInternal) {
-        this(includeInternal, false);
+        this.maskSecretValues = maskSecretValues;
     }
 
     /** {@inheritDoc} */
@@ -73,7 +82,9 @@ public class ConverterToMapVisitor implements 
ConfigurationVisitor<Object> {
         if (val instanceof Character || val instanceof UUID) {
             valObj = val.toString();
         } else if (val != null && val.getClass().isArray()) {
-            valObj = toListOfObjects(val);
+            valObj = toListOfObjects(field, val);
+        } else if (val instanceof String) {
+            valObj = maskIfNeeded(field, (String) val);
         }
 
         addToParent(key, valObj);
@@ -162,10 +173,11 @@ public class ConverterToMapVisitor implements 
ConfigurationVisitor<Object> {
     /**
      * Converts array into a list of objects. Boxes array elements if they are 
primitive values.
      *
+     * @param field Field of the array
      * @param val Array of primitives or array of {@link String}s
      * @return List of objects corresponding to the passed array.
      */
-    private List<?> toListOfObjects(Serializable val) {
+    private List<?> toListOfObjects(Field field, Serializable val) {
         Stream<?> stream = IntStream.range(0, Array.getLength(val)).mapToObj(i 
-> Array.get(val, i));
 
         if (val.getClass().getComponentType() == char.class || 
val.getClass().getComponentType() == UUID.class) {
@@ -174,4 +186,24 @@ public class ConverterToMapVisitor implements 
ConfigurationVisitor<Object> {
 
         return stream.collect(Collectors.toList());
     }
+
+    /**
+     * Returns {@link ConverterToMapVisitor#MASKED_VALUE} if the field is 
annotated with {@link Secret} and
+     * {@link ConverterToMapVisitor#maskSecretValues} is {@code true}.
+     *
+     * @param field Field to check
+     * @param val Value to mask
+     * @return Masked value
+     */
+    private Object maskIfNeeded(Field field, String val) {
+        if (!maskSecretValues) {
+            return val;
+        }
+
+        if (field != null && field.isAnnotationPresent(Secret.class)) {
+            return MASKED_VALUE;
+        } else {
+            return val;
+        }
+    }
 }
diff --git 
a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitorBuilder.java
 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitorBuilder.java
new file mode 100644
index 0000000000..15973cf514
--- /dev/null
+++ 
b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitorBuilder.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.configuration.tree;
+
+import java.lang.reflect.Field;
+import org.apache.ignite.configuration.annotation.Secret;
+
+/**
+ * Builder for {@link ConverterToMapVisitor}.
+ */
+public final class ConverterToMapVisitorBuilder {
+
+    /** Include internal configuration nodes (private configuration 
extensions). */
+    private boolean includeInternal = true;
+
+    /** Skip nulls, empty Maps and empty lists. */
+    private boolean skipEmptyValues = false;
+
+    /** Mask values, if their {@link Field} has {@link Secret} annotation. */
+    private boolean maskSecretValues = false;
+
+    ConverterToMapVisitorBuilder() {
+    }
+
+    public ConverterToMapVisitorBuilder includeInternal(boolean 
includeInternal) {
+        this.includeInternal = includeInternal;
+        return this;
+    }
+
+    public ConverterToMapVisitorBuilder skipEmptyValues(boolean 
skipEmptyValues) {
+        this.skipEmptyValues = skipEmptyValues;
+        return this;
+    }
+
+    public ConverterToMapVisitorBuilder maskSecretValues(boolean 
maskSecretValues) {
+        this.maskSecretValues = maskSecretValues;
+        return this;
+    }
+
+    public ConverterToMapVisitor build() {
+        return new ConverterToMapVisitor(includeInternal, skipEmptyValues, 
maskSecretValues);
+    }
+}
diff --git 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitorTest.java
 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitorTest.java
new file mode 100644
index 0000000000..ce5e320cf8
--- /dev/null
+++ 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitorTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.ignite.internal.configuration.tree;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.matchesPattern;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.lenient;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.UUID;
+import org.apache.ignite.configuration.annotation.Secret;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+class ConverterToMapVisitorTest {
+
+    private final ConverterToMapVisitor maskingVisitor = 
ConverterToMapVisitor.builder()
+            .maskSecretValues(true)
+            .build();
+
+    private final ConverterToMapVisitor notMaskingVisitor = 
ConverterToMapVisitor.builder()
+            .maskSecretValues(false)
+            .build();
+
+    @Mock
+    Field secretField;
+
+    @Mock
+    Field notSecretField;
+
+
+    @BeforeEach
+    void setUp() {
+        
lenient().when(secretField.isAnnotationPresent(Secret.class)).thenReturn(true);
+        
lenient().when(notSecretField.isAnnotationPresent(Secret.class)).thenReturn(false);
+    }
+
+    @Test
+    public void notSecretStringNotMasked() {
+        Object password = maskingVisitor.visitLeafNode(notSecretField, null, 
"password");
+        assertEquals("password", password.toString());
+    }
+
+    @Test
+    public void secretStringIsMasked() {
+        Object password = maskingVisitor.visitLeafNode(secretField, null, 
"password");
+        assertThat(password.toString(), matchesPattern("^[\\*]+$"));
+    }
+
+    @Test
+    public void secretStringIsNotMaskedIfMaskSecretValuesIsFalse() {
+        Object password = notMaskingVisitor.visitLeafNode(secretField, null, 
"password");
+        assertEquals("password", password.toString());
+    }
+
+    @Test
+    public void secretStringsHaveSameLength() {
+        Object string1 = maskingVisitor.visitLeafNode(secretField, null, 
"password");
+        Object string2 = maskingVisitor.visitLeafNode(secretField, null, 
"admin");
+        assertEquals(string1.toString().length(), string2.toString().length());
+    }
+
+    @Test
+    public void notStringsNotMasked() {
+        // int value
+        assertEquals(1, maskingVisitor.visitLeafNode(secretField, null, 1));
+
+        // long value
+        assertEquals(1L, maskingVisitor.visitLeafNode(secretField, null, 1L));
+
+        // double value
+        assertEquals(1.1, maskingVisitor.visitLeafNode(secretField, null, 
1.1));
+
+        // Character value
+        assertEquals("a", maskingVisitor.visitLeafNode(secretField, null, 
'a'));
+
+        // UUID value
+        UUID uuid = UUID.randomUUID();
+        assertEquals(uuid.toString(), 
maskingVisitor.visitLeafNode(secretField, null, uuid));
+
+        // array value
+        String[] strings = {"a", "b", "c", "d"};
+        assertEquals(Arrays.asList(strings), 
maskingVisitor.visitLeafNode(secretField, null, strings));
+    }
+}
diff --git 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
index 984a42023f..04d5ec045d 100644
--- 
a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
+++ 
b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
@@ -673,7 +673,13 @@ public class ConfigurationUtilTest {
 
         addDefaults(innerNode);
 
-        Map<String, Object> config = (Map<String, Object>) 
innerNode.accept(null, null, new ConverterToMapVisitor(false));
+        Map<String, Object> config = (Map<String, Object>) innerNode.accept(
+                null,
+                null,
+                ConverterToMapVisitor.builder()
+                        .includeInternal(false)
+                        .build()
+        );
 
         // Check that no internal configuration will be received.
 
@@ -690,7 +696,13 @@ public class ConfigurationUtilTest {
 
         // Check that no internal configuration will be received.
 
-        config = (Map<String, Object>) innerNode.accept(null, null, new 
ConverterToMapVisitor(true));
+        config = (Map<String, Object>) innerNode.accept(
+                null,
+                null,
+                ConverterToMapVisitor.builder()
+                        .includeInternal(true)
+                        .build()
+        );
 
         assertEquals(7, config.size());
         assertNull(config.get("str0"));
@@ -739,12 +751,23 @@ public class ConfigurationUtilTest {
 
         assertNotNull(find(List.of(schemaKey.key()), superRoot, true));
 
-        Map<String, Object> config =
-                (Map<String, Object>) superRoot.accept(null, schemaKey.key(), 
new ConverterToMapVisitor(false));
+        Map<String, Object> config = (Map<String, Object>) superRoot.accept(
+                        null,
+                        schemaKey.key(),
+                        ConverterToMapVisitor.builder()
+                                .includeInternal(false)
+                                .build()
+                );
 
         assertTrue(config.isEmpty());
 
-        config = (Map<String, Object>) superRoot.accept(null, schemaKey.key(), 
new ConverterToMapVisitor(true));
+        config = (Map<String, Object>) superRoot.accept(
+                null,
+                schemaKey.key(),
+                ConverterToMapVisitor.builder()
+                        .includeInternal(true)
+                        .build()
+        );
 
         assertEquals(1, config.size());
         assertNotNull(config.get(schemaKey.key()));
diff --git 
a/modules/configuration/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
 
b/modules/configuration/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000000..1ed358c179
--- /dev/null
+++ 
b/modules/configuration/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1,2 @@
+# to mock final classes
+mock-maker-inline
diff --git 
a/modules/index/src/test/java/org/apache/ignite/internal/index/IndexManagerTest.java
 
b/modules/index/src/test/java/org/apache/ignite/internal/index/IndexManagerTest.java
index 33d576539b..b01675ec75 100644
--- 
a/modules/index/src/test/java/org/apache/ignite/internal/index/IndexManagerTest.java
+++ 
b/modules/index/src/test/java/org/apache/ignite/internal/index/IndexManagerTest.java
@@ -213,7 +213,13 @@ public class IndexManagerTest {
     private static Object toMap(Object obj) {
         assert obj instanceof TraversableTreeNode;
 
-        return ((TraversableTreeNode) obj).accept(null, null, new 
ConverterToMapVisitor(false));
+        return ((TraversableTreeNode) obj).accept(
+                null,
+                null,
+                ConverterToMapVisitor.builder()
+                        .includeInternal(false)
+                        .build()
+        );
     }
 
     private static void assertSameObjects(Object expected, Object actual) {
diff --git 
a/modules/network/src/main/java/org/apache/ignite/internal/network/configuration/KeyStoreConfigurationSchema.java
 
b/modules/network/src/main/java/org/apache/ignite/internal/network/configuration/KeyStoreConfigurationSchema.java
index bfb3fb1664..cac0c6cf99 100644
--- 
a/modules/network/src/main/java/org/apache/ignite/internal/network/configuration/KeyStoreConfigurationSchema.java
+++ 
b/modules/network/src/main/java/org/apache/ignite/internal/network/configuration/KeyStoreConfigurationSchema.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.network.configuration;
 
 import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.Secret;
 import org.apache.ignite.configuration.annotation.Value;
 
 /** Keystore configuration schema. */
@@ -32,6 +33,7 @@ public class KeyStoreConfigurationSchema {
     public String path = "";
 
     /** Keystore password. */
+    @Secret
     @Value(hasDefault = true)
     public String password = "";
 }
diff --git 
a/modules/network/src/main/java/org/apache/ignite/internal/network/configuration/KeyStoreConfigurationSchema.java
 
b/modules/network/src/test/java/org/apache/ignite/internal/network/configuration/KeyStoreConfigurationSchemaTest.java
similarity index 59%
copy from 
modules/network/src/main/java/org/apache/ignite/internal/network/configuration/KeyStoreConfigurationSchema.java
copy to 
modules/network/src/test/java/org/apache/ignite/internal/network/configuration/KeyStoreConfigurationSchemaTest.java
index bfb3fb1664..58d3722145 100644
--- 
a/modules/network/src/main/java/org/apache/ignite/internal/network/configuration/KeyStoreConfigurationSchema.java
+++ 
b/modules/network/src/test/java/org/apache/ignite/internal/network/configuration/KeyStoreConfigurationSchemaTest.java
@@ -17,21 +17,22 @@
 
 package org.apache.ignite.internal.network.configuration;
 
-import org.apache.ignite.configuration.annotation.Config;
-import org.apache.ignite.configuration.annotation.Value;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
-/** Keystore configuration schema. */
-@Config
-public class KeyStoreConfigurationSchema {
-    /** Keystore type. */
-    @Value(hasDefault = true)
-    public String type = "PKCS12";
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import org.apache.ignite.configuration.annotation.Secret;
+import org.junit.jupiter.api.Test;
 
-    /** Keystore path. */
-    @Value(hasDefault = true)
-    public String path = "";
+class KeyStoreConfigurationSchemaTest {
 
-    /** Keystore password. */
-    @Value(hasDefault = true)
-    public String password = "";
+    @Test
+    public void passwordIsSecret() {
+        Field password = 
Arrays.stream(KeyStoreConfigurationSchema.class.getDeclaredFields())
+                .filter(it -> it.getName().equals("password"))
+                .findFirst()
+                .orElseThrow();
+
+        assertTrue(password.isAnnotationPresent(Secret.class));
+    }
 }
diff --git 
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/configuration/AbstractConfigurationController.java
 
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/configuration/AbstractConfigurationController.java
index f04f272a5d..33ba7b5d0f 100644
--- 
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/configuration/AbstractConfigurationController.java
+++ 
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/configuration/AbstractConfigurationController.java
@@ -17,12 +17,8 @@
 
 package org.apache.ignite.internal.rest.configuration;
 
-import java.util.Collections;
-import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionException;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
 import 
org.apache.ignite.configuration.validation.ConfigurationValidationException;
 import 
org.apache.ignite.internal.configuration.presentation.ConfigurationPresentation;
 import org.apache.ignite.lang.IgniteException;
@@ -32,10 +28,6 @@ import org.apache.ignite.lang.IgniteException;
  */
 public abstract class AbstractConfigurationController {
 
-    private final Set<String> keysToMask = Set.of("password");
-    private final Pattern sensitiveInformationPattern = 
sensitiveInformationPattern(keysToMask);
-    private final JsonMasker jsonMasker = new JsonMasker();
-
     /** Presentation of the configuration. */
     private final ConfigurationPresentation<String> cfgPresentation;
 
@@ -49,7 +41,7 @@ public abstract class AbstractConfigurationController {
      * @return the presentation of configuration.
      */
     public String getConfiguration() {
-        return maskSensitiveInformation(cfgPresentation.represent());
+        return cfgPresentation.represent();
     }
 
     /**
@@ -60,7 +52,7 @@ public abstract class AbstractConfigurationController {
      */
     public String getConfigurationByPath(String path) {
         try {
-            return maskSensitiveInformation(path, 
cfgPresentation.representByPath(path));
+            return cfgPresentation.representByPath(path);
         } catch (IllegalArgumentException ex) {
             throw new IgniteException(ex);
         }
@@ -84,21 +76,4 @@ public abstract class AbstractConfigurationController {
                     throw new IgniteException(ex);
                 });
     }
-
-    private String maskSensitiveInformation(String configuration) {
-        return maskSensitiveInformation("", configuration);
-    }
-
-    private String maskSensitiveInformation(String path, String configuration) 
{
-        boolean containsOnlySensitiveInformation = 
sensitiveInformationPattern.matcher(path).find();
-        Set<String> maskedKeys = containsOnlySensitiveInformation ? 
Collections.emptySet() : keysToMask;
-        return jsonMasker.mask(configuration, maskedKeys).toString();
-    }
-
-    private Pattern sensitiveInformationPattern(Set<String> keys) {
-        String regexp = keys.stream()
-                .map(it -> "(." + it + "$)")
-                .collect(Collectors.joining("|"));
-        return Pattern.compile(regexp);
-    }
 }
diff --git 
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/configuration/JsonMasker.java
 
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/configuration/JsonMasker.java
deleted file mode 100644
index 7e9174ac36..0000000000
--- 
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/configuration/JsonMasker.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.ignite.internal.rest.configuration;
-
-import static java.util.Collections.emptySet;
-
-import com.fasterxml.jackson.core.JsonFactory;
-import com.fasterxml.jackson.core.JsonParser;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ArrayNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import com.fasterxml.jackson.databind.node.TextNode;
-import java.io.IOException;
-import java.util.Locale;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * JSON masker.
- */
-public class JsonMasker {
-
-    private static final String MASKING_STRING = "******";
-
-    private final ObjectMapper mapper = new ObjectMapper();
-
-    /**
-     * Masks the given JSON string.
-     *
-     * @param json JSON string.
-     * @param keysToMask Set of keys to mask. If empty, all keys will be 
masked.
-     * @return Masked {@link JsonNode}.
-     */
-    public JsonNode mask(String json, Set<String> keysToMask) {
-        Set<String> keysToMaskInLowerCase = keysToMask.stream()
-                .map(it -> it.toLowerCase(Locale.ROOT))
-                .collect(Collectors.toSet());
-        return traverseAndMask(stringToJsonNode(json).deepCopy(), 
keysToMaskInLowerCase);
-    }
-
-    private JsonNode traverseAndMask(JsonNode target, Set<String> keysToMask) {
-        if (target.isTextual() && !target.textValue().isBlank() && 
keysToMask.isEmpty()) {
-            return new TextNode(MASKING_STRING);
-        }
-
-        if (target.isArray()) {
-            for (int i = 0; i < target.size(); i++) {
-                ((ArrayNode) target).set(i, traverseAndMask(target.get(i), 
keysToMask));
-            }
-        }
-
-        if (target.isObject()) {
-            ObjectNode targetObjectNode = (ObjectNode) target;
-            target.fields().forEachRemaining(field -> {
-                        if (keysToMask.isEmpty()) {
-                            targetObjectNode.replace(field.getKey(), 
traverseAndMask(field.getValue(), emptySet()));
-                        } else if 
(keysToMask.contains(field.getKey().toLowerCase(Locale.ROOT))) {
-                            targetObjectNode.replace(field.getKey(), 
traverseAndMask(field.getValue(), emptySet()));
-                        } else {
-                            traverseAndMask(field.getValue(), keysToMask);
-                        }
-                    }
-            );
-        }
-
-        return target;
-    }
-
-    private JsonNode stringToJsonNode(String str) {
-        JsonFactory factory = mapper.getFactory();
-        try {
-            JsonParser parser = factory.createParser(str);
-            return mapper.readTree(parser);
-        } catch (IOException e) {
-            throw new IllegalArgumentException("Invalid JSON string: " + str, 
e);
-        }
-    }
-}
diff --git 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/ConfigurationControllerBaseTest.java
 
b/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/ConfigurationControllerBaseTest.java
index 3c09cda83a..26a43a3b75 100644
--- 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/ConfigurationControllerBaseTest.java
+++ 
b/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/ConfigurationControllerBaseTest.java
@@ -17,12 +17,13 @@
 
 package org.apache.ignite.internal.rest.configuration;
 
-import static java.util.Collections.emptySet;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.matchesPattern;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import io.micronaut.context.ApplicationContext;
@@ -53,8 +54,6 @@ public abstract class ConfigurationControllerBaseTest {
 
     private final Set<String> secretKeys = Set.of("password");
 
-    private final JsonMasker jsonMasker = new JsonMasker();
-
     @Inject
     private EmbeddedServer server;
 
@@ -79,10 +78,14 @@ public abstract class ConfigurationControllerBaseTest {
     @Test
     void testGetConfig() {
         var response = client().toBlocking().exchange("", String.class);
-        var expectedBody = jsonMasker.mask(cfgPresentation.represent(), 
secretKeys).toString();
+        var expectedBody = cfgPresentation.represent();
 
         assertEquals(HttpStatus.OK, response.status());
         assertEquals(expectedBody, response.body());
+        assertEquals(
+                
"{\"root\":{\"foo\":\"foo\",\"sensitive\":{\"password\":\"********\"},\"subCfg\":{\"bar\":\"bar\"}}}",
+                response.body()
+        );
     }
 
     @Test
@@ -91,15 +94,17 @@ public abstract class ConfigurationControllerBaseTest {
 
         assertEquals(HttpStatus.OK, response.status());
         assertEquals(cfgPresentation.representByPath("root.subCfg"), 
response.body());
+        assertNotNull(response.body());
     }
 
     @Test
     void testGetSensitiveInformationByPath() {
         var response = 
client().toBlocking().exchange("/root.sensitive.password", String.class);
-        var expectedBody = 
jsonMasker.mask(cfgPresentation.representByPath("root.sensitive.password"), 
emptySet()).toString();
+        var expectedBody = 
cfgPresentation.representByPath("root.sensitive.password");
 
         assertEquals(HttpStatus.OK, response.status());
         assertEquals(expectedBody, response.body());
+        assertThat(response.body(), matchesPattern("^\\\"[\\*]+\\\"$"));
     }
 
     @Test
diff --git 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/JsonMaskerTest.java
 
b/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/JsonMaskerTest.java
deleted file mode 100644
index be4cc184d2..0000000000
--- 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/JsonMaskerTest.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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 org.apache.ignite.internal.rest.configuration;
-
-import static java.util.Collections.emptySet;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import java.util.Set;
-import org.junit.jupiter.api.Test;
-
-class JsonMaskerTest {
-
-    private final Set<String> keysToMask = Set.of("credentials");
-    private final JsonMasker jsonMasker = new JsonMasker();
-
-    @Test
-    void notingToMask() {
-        String json = "{\"name\": \"John Doe\", \"age\": 30}";
-        String masked = jsonMasker.mask(json, keysToMask).toString();
-        assertEquals("{\"name\":\"John Doe\",\"age\":30}", masked);
-    }
-
-    @Test
-    void maskAll() {
-        String json = "{\"name\": \"John Doe\", \"age\": 30}";
-        String masked = jsonMasker.mask(json, emptySet()).toString();
-        assertEquals("{\"name\":\"******\",\"age\":30}", masked);
-    }
-
-    @Test
-    void maskField() {
-        String json = "{\"name\": \"John Doe\", \"age\": 30, \"credentials\": 
\"admin\"}";
-        String masked = jsonMasker.mask(json, keysToMask).toString();
-        assertEquals("{\"name\":\"John 
Doe\",\"age\":30,\"credentials\":\"******\"}", masked);
-    }
-
-    @Test
-    void maskNestedArray() {
-        String json = "{\"name\": \"John Doe\", \"age\": 30, \"credentials\": 
[\"admin1\", \"admin2\"]}";
-        String masked = jsonMasker.mask(json, keysToMask).toString();
-        assertEquals("{\"name\":\"John 
Doe\",\"age\":30,\"credentials\":[\"******\",\"******\"]}", masked);
-    }
-
-    @Test
-    void maskNestedObject() {
-        String json = "{\n"
-                + "  \"name\": \"John Doe\",\n"
-                + "  \"age\": 30,\n"
-                + "  \"credentials\": {\n"
-                + "    \"basic\": {\n"
-                + "      \"user\": \"admin\",\n"
-                + "      \"password\": \"admin\"\n"
-                + "    }\n"
-                + "  }\n"
-                + "}";
-        String masked = jsonMasker.mask(json, keysToMask).toString();
-        assertEquals(
-                "{\"name\":\"John 
Doe\",\"age\":30,\"credentials\":{\"basic\":{\"user\":\"******\",\"password\":\"******\"}}}",
-                masked
-        );
-    }
-
-    @Test
-    void booleanNotMasked() {
-        String json = "{\"name\": \"John Doe\", \"age\": 30, \"credentials\": 
true}";
-        String masked = jsonMasker.mask(json, keysToMask).toString();
-        assertEquals("{\"name\":\"John 
Doe\",\"age\":30,\"credentials\":true}", masked);
-    }
-
-    @Test
-    void numberNotMasked() {
-        String json = "{\"name\": \"John Doe\", \"age\": 30, \"credentials\": 
123}";
-        String masked = jsonMasker.mask(json, keysToMask).toString();
-        assertEquals("{\"name\":\"John Doe\",\"age\":30,\"credentials\":123}", 
masked);
-    }
-
-    @Test
-    void emptyNotMasked() {
-        String json = "{\"name\": \"John Doe\", \"age\": 30, \"credentials\": 
\"\"}";
-        String masked = jsonMasker.mask(json, keysToMask).toString();
-        assertEquals("{\"name\":\"John 
Doe\",\"age\":30,\"credentials\":\"\"}", masked);
-    }
-
-    @Test
-    void fieldInUpperCase() {
-        String json = "{\"name\": \"John Doe\", \"age\": 30, \"CREDENTIALS\": 
\"admin\"}";
-        String masked = jsonMasker.mask(json, keysToMask).toString();
-        assertEquals("{\"name\":\"John 
Doe\",\"age\":30,\"CREDENTIALS\":\"******\"}", masked);
-    }
-}
diff --git 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/TestSubSensitiveConfigurationSchema.java
 
b/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/TestSubSensitiveConfigurationSchema.java
index acfb752c1f..e3aeb5d95b 100644
--- 
a/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/TestSubSensitiveConfigurationSchema.java
+++ 
b/modules/rest/src/test/java/org/apache/ignite/internal/rest/configuration/TestSubSensitiveConfigurationSchema.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.rest.configuration;
 
 import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.Secret;
 import org.apache.ignite.configuration.annotation.Value;
 
 /**
@@ -26,6 +27,7 @@ import org.apache.ignite.configuration.annotation.Value;
 @Config
 public class TestSubSensitiveConfigurationSchema {
 
+    @Secret
     @Value(hasDefault = true)
     public String password = "";
 }
diff --git 
a/modules/runner/src/main/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorage.java
 
b/modules/runner/src/main/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorage.java
index b52d5715a3..7afc824278 100644
--- 
a/modules/runner/src/main/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorage.java
+++ 
b/modules/runner/src/main/java/org/apache/ignite/internal/configuration/storage/LocalFileConfigurationStorage.java
@@ -261,7 +261,15 @@ public class LocalFileConfigurationStorage implements 
ConfigurationStorage {
 
         fillFromPrefixMap(rootNode, toPrefixMap(latest));
 
-        Object transformed = rootNode.accept(null, null, new 
ConverterToMapVisitor(false, true));
+        Object transformed = rootNode.accept(
+                null,
+                null,
+                ConverterToMapVisitor.builder()
+                        .includeInternal(false)
+                        .skipEmptyValues(true)
+                        .maskSecretValues(false)
+                        .build()
+        );
 
         ConfigValue conf = ConfigImpl.fromAnyRef(transformed, null);
 
diff --git 
a/modules/security/src/main/java/org/apache/ignite/internal/configuration/BasicAuthenticationProviderConfigurationSchema.java
 
b/modules/security/src/main/java/org/apache/ignite/internal/configuration/BasicAuthenticationProviderConfigurationSchema.java
index f639362c89..52415f4cc8 100644
--- 
a/modules/security/src/main/java/org/apache/ignite/internal/configuration/BasicAuthenticationProviderConfigurationSchema.java
+++ 
b/modules/security/src/main/java/org/apache/ignite/internal/configuration/BasicAuthenticationProviderConfigurationSchema.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.configuration;
 
 import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.Secret;
 import org.apache.ignite.configuration.annotation.Value;
 
 /** Basic authentication configuration. */
@@ -29,6 +30,7 @@ public class BasicAuthenticationProviderConfigurationSchema 
extends Authenticati
     public String username;
 
     /** Password. */
+    @Secret
     @Value
     public String password;
 }
diff --git 
a/modules/security/src/main/java/org/apache/ignite/internal/configuration/BasicAuthenticationProviderConfigurationSchema.java
 
b/modules/security/src/test/java/org/apache/ignite/internal/configuration/BasicAuthenticationProviderConfigurationSchemaTest.java
similarity index 57%
copy from 
modules/security/src/main/java/org/apache/ignite/internal/configuration/BasicAuthenticationProviderConfigurationSchema.java
copy to 
modules/security/src/test/java/org/apache/ignite/internal/configuration/BasicAuthenticationProviderConfigurationSchemaTest.java
index f639362c89..13f82c1502 100644
--- 
a/modules/security/src/main/java/org/apache/ignite/internal/configuration/BasicAuthenticationProviderConfigurationSchema.java
+++ 
b/modules/security/src/test/java/org/apache/ignite/internal/configuration/BasicAuthenticationProviderConfigurationSchemaTest.java
@@ -17,18 +17,22 @@
 
 package org.apache.ignite.internal.configuration;
 
-import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
-import org.apache.ignite.configuration.annotation.Value;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
-/** Basic authentication configuration. */
-@PolymorphicConfigInstance(AuthenticationProviderConfigurationSchema.TYPE_BASIC)
-public class BasicAuthenticationProviderConfigurationSchema extends 
AuthenticationProviderConfigurationSchema {
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import org.apache.ignite.configuration.annotation.Secret;
+import org.junit.jupiter.api.Test;
 
-    /** Username. */
-    @Value
-    public String username;
+class BasicAuthenticationProviderConfigurationSchemaTest {
 
-    /** Password. */
-    @Value
-    public String password;
+    @Test
+    public void passwordIsSecret() {
+        Field password = 
Arrays.stream(BasicAuthenticationProviderConfigurationSchema.class.getDeclaredFields())
+                .filter(it -> it.getName().equals("password"))
+                .findFirst()
+                .orElseThrow();
+
+        assertTrue(password.isAnnotationPresent(Secret.class));
+    }
 }

Reply via email to