14
14
package io .trino .plugin .postgresql ;
15
15
16
16
import com .google .common .collect .ImmutableMap ;
17
+ import com .google .common .collect .ImmutableSet ;
18
+ import io .airlift .configuration .Config ;
19
+ import io .airlift .configuration .ConfigSecuritySensitive ;
20
+ import io .github .classgraph .AnnotationInfo ;
21
+ import io .github .classgraph .AnnotationParameterValueList ;
22
+ import io .github .classgraph .ClassGraph ;
23
+ import io .github .classgraph .ScanResult ;
24
+ import io .trino .plugin .base .config .ConfigPropertyMetadata ;
17
25
import io .trino .spi .Plugin ;
18
26
import io .trino .spi .connector .ConnectorFactory ;
19
27
import org .junit .jupiter .api .Test ;
20
28
29
+ import java .io .File ;
30
+ import java .io .IOException ;
31
+ import java .net .URISyntaxException ;
32
+ import java .net .URL ;
33
+ import java .nio .file .Files ;
34
+ import java .nio .file .Path ;
35
+ import java .util .Set ;
36
+
37
+ import static com .google .common .base .Preconditions .checkState ;
38
+ import static com .google .common .collect .ImmutableSet .toImmutableSet ;
21
39
import static com .google .common .collect .Iterables .getOnlyElement ;
40
+ import static org .assertj .core .api .Assertions .assertThat ;
22
41
23
42
public class TestPostgreSqlPlugin
24
43
{
@@ -34,4 +53,97 @@ public void testCreateConnector()
34
53
"bootstrap.quiet" , "true" ),
35
54
new TestingPostgreSqlConnectorContext ()).shutdown ();
36
55
}
56
+
57
+ @ Test
58
+ void testUnknownPropertiesAreSecuritySensitive ()
59
+ {
60
+ Plugin plugin = new PostgreSqlPlugin ();
61
+ ConnectorFactory connectorFactory = getOnlyElement (plugin .getConnectorFactories ());
62
+ Set <String > unknownProperties = ImmutableSet .of ("unknown" );
63
+
64
+ Set <String > sensitiveProperties = connectorFactory .getSecuritySensitivePropertyNames (unknownProperties );
65
+
66
+ assertThat (sensitiveProperties ).isEqualTo (unknownProperties );
67
+ }
68
+
69
+ @ Test
70
+ void testSecuritySensitiveProperties ()
71
+ throws Exception
72
+ {
73
+ // The purpose of this test is to help identify security-sensitive properties that
74
+ // may be used by the connector. These properties are detected by scanning the
75
+ // plugin's runtime classpath and collecting all property names annotated with
76
+ // @ConfigSecuritySensitive. The scan includes all configuration classes, whether
77
+ // they are always used, conditionally used, or never used. This approach has both
78
+ // advantages and disadvantages.
79
+ //
80
+ // One advantage is that we don't need to rely on the plugin's configuration to
81
+ // retrieve properties that are used conditionally. However, this method may also
82
+ // capture properties that are not used at all but are pulled into the classpath
83
+ // by dependencies. With that in mind, if this test fails, it likely indicates that
84
+ // either a property needs to be added to the connector's security-sensitive
85
+ // property names list, or it should be added to the excluded properties list below.
86
+ Set <String > excludedClasses = ImmutableSet .of (
87
+ "io.trino.plugin.base.ldap.LdapClientConfig" ,
88
+ "io.airlift.http.client.HttpClientConfig" ,
89
+ "io.airlift.node.NodeConfig" ,
90
+ "io.airlift.log.LoggingConfiguration" ,
91
+ "io.trino.plugin.base.security.FileBasedAccessControlConfig" ,
92
+ "io.airlift.configuration.secrets.SecretsPluginConfig" ,
93
+ "io.trino.plugin.base.jmx.ObjectNameGeneratorConfig" );
94
+ Plugin plugin = new PostgreSqlPlugin ();
95
+ ConnectorFactory connectorFactory = getOnlyElement (plugin .getConnectorFactories ());
96
+
97
+ Set <ConfigPropertyMetadata > propertiesFoundInClasspath = findPropertiesInRuntimeClasspath (excludedClasses );
98
+ Set <String > allPropertyNames = propertiesFoundInClasspath .stream ()
99
+ .map (ConfigPropertyMetadata ::name )
100
+ .collect (toImmutableSet ());
101
+ Set <String > expectedProperties = propertiesFoundInClasspath .stream ()
102
+ .filter (ConfigPropertyMetadata ::sensitive )
103
+ .map (ConfigPropertyMetadata ::name )
104
+ .collect (toImmutableSet ());
105
+
106
+ Set <String > sensitiveProperties = connectorFactory .getSecuritySensitivePropertyNames (allPropertyNames );
107
+
108
+ assertThat (sensitiveProperties ).isEqualTo (expectedProperties );
109
+ }
110
+
111
+ private static Set <ConfigPropertyMetadata > findPropertiesInRuntimeClasspath (Set <String > excludedClassNames )
112
+ throws URISyntaxException , IOException
113
+ {
114
+ try (ScanResult scanResult = new ClassGraph ()
115
+ .overrideClasspath (buildRuntimeClasspath ())
116
+ .enableAllInfo ()
117
+ .scan ()) {
118
+ return scanResult .getClassesWithMethodAnnotation (Config .class ).stream ()
119
+ .filter (classInfo -> !excludedClassNames .contains (classInfo .getName ()))
120
+ .flatMap (classInfo -> classInfo .getMethodInfo ().stream ())
121
+ .filter (methodInfo -> methodInfo .hasAnnotation (Config .class ))
122
+ .map (methodInfo -> {
123
+ boolean sensitive = methodInfo .hasAnnotation (ConfigSecuritySensitive .class );
124
+ AnnotationInfo annotationInfo = methodInfo .getAnnotationInfo (Config .class );
125
+ checkState (annotationInfo != null , "Missing @Config annotation for %s" , methodInfo );
126
+ AnnotationParameterValueList parameterValues = annotationInfo .getParameterValues ();
127
+ checkState (parameterValues .size () == 1 , "Expected exactly one parameter for %s" , annotationInfo );
128
+ String propertyName = (String ) parameterValues .getFirst ().getValue ();
129
+ return new ConfigPropertyMetadata (propertyName , sensitive );
130
+ })
131
+ .collect (toImmutableSet ());
132
+ }
133
+ }
134
+
135
+ private static String buildRuntimeClasspath ()
136
+ throws URISyntaxException , IOException
137
+ {
138
+ // This file is generated by the maven-dependency-plugin, which is configured in the connector's pom.xml file.
139
+ String runtimeDependenciesFile = "runtime-dependencies.txt" ;
140
+ URL runtimeDependenciesUrl = TestPostgreSqlPlugin .class .getClassLoader ().getResource (runtimeDependenciesFile );
141
+ checkState (runtimeDependenciesUrl != null , "Missing %s file" , runtimeDependenciesUrl );
142
+ String runtimeDependenciesClasspath = Files .readString (Path .of (runtimeDependenciesUrl .toURI ()));
143
+
144
+ File classDirectory = new File (new File (runtimeDependenciesUrl .toURI ()).getParentFile ().getParentFile (), "classes/" );
145
+ checkState (classDirectory .exists (), "Missing %s directory" , classDirectory .getAbsolutePath ());
146
+
147
+ return "%s:%s" .formatted (runtimeDependenciesClasspath , classDirectory .getAbsolutePath ());
148
+ }
37
149
}
0 commit comments