1 package io.apicurio.registry.maven;
2
3 import com.microsoft.kiota.ApiException;
4 import io.apicurio.registry.client.RegistryClientFactory;
5 import io.apicurio.registry.client.common.RegistryClientOptions;
6 import io.apicurio.registry.rest.client.RegistryClient;
7 import io.apicurio.registry.rest.client.models.ProblemDetails;
8 import io.apicurio.registry.rest.client.models.RuleViolationProblemDetails;
9 import io.apicurio.registry.types.ContentTypes;
10 import io.vertx.core.Vertx;
11 import io.vertx.core.VertxOptions;
12 import io.vertx.core.file.FileSystemOptions;
13 import org.apache.maven.plugin.AbstractMojo;
14 import org.apache.maven.plugin.MojoExecutionException;
15 import org.apache.maven.plugin.MojoFailureException;
16 import org.apache.maven.plugins.annotations.Parameter;
17
18 import java.util.Locale;
19 import java.util.concurrent.ExecutionException;
20
21
22
23
24 public abstract class AbstractRegistryMojo extends AbstractMojo {
25
26
27
28
29 @Parameter(required = true, property = "apicurio.url")
30 String registryUrl;
31
32
33
34
35 @Parameter(property = "auth.server.url")
36 String authServerUrl;
37
38
39
40
41 @Parameter(property = "client.id")
42 String clientId;
43
44
45
46
47 @Parameter(property = "client.secret")
48 String clientSecret;
49
50
51
52
53 @Parameter(property = "client.scope")
54 String clientScope;
55
56
57
58
59 @Parameter(property = "username")
60 String username;
61
62
63
64
65 @Parameter(property = "password")
66 String password;
67
68
69
70
71
72
73 @Parameter(property = "trustStorePath")
74 String trustStorePath;
75
76
77
78
79 @Parameter(property = "trustStorePassword")
80 String trustStorePassword;
81
82
83
84
85
86 @Parameter(property = "trustStoreType")
87 String trustStoreType;
88
89
90
91
92
93
94 @Parameter(property = "keyStorePath")
95 String keyStorePath;
96
97
98
99
100 @Parameter(property = "keyStorePassword")
101 String keyStorePassword;
102
103
104
105
106
107 @Parameter(property = "keyStoreType")
108 String keyStoreType;
109
110
111
112
113
114 @Parameter(property = "keyStorePemKeyPath")
115 String keyStorePemKeyPath;
116
117
118
119
120
121 @Parameter(property = "trustAll", defaultValue = "false")
122 boolean trustAll;
123
124
125
126
127
128 @Parameter(property = "verifyHostname", defaultValue = "true")
129 boolean verifyHostname;
130
131 protected Vertx createVertx() {
132 var options = new VertxOptions();
133 var fsOpts = new FileSystemOptions();
134 fsOpts.setFileCachingEnabled(false);
135 fsOpts.setClassPathResolvingEnabled(false);
136 options.setFileSystemOptions(fsOpts);
137 return Vertx.vertx(options);
138 }
139
140 protected RegistryClient createClient(Vertx vertx) {
141 RegistryClientOptions clientOptions = RegistryClientOptions.create(registryUrl, vertx);
142
143
144 if (authServerUrl != null && clientId != null && clientSecret != null) {
145 if (clientScope != null && !clientScope.isEmpty()) {
146 getLog().info("Creating registry client with OAuth2 authentication with scope.");
147 clientOptions.oauth2(authServerUrl, clientId, clientSecret, clientScope);
148 } else {
149 getLog().info("Creating registry client with OAuth2 authentication.");
150 clientOptions.oauth2(authServerUrl, clientId, clientSecret);
151 }
152 } else if (username != null && password != null) {
153 getLog().info("Creating registry client with Basic authentication.");
154 clientOptions.basicAuth(username, password);
155 } else {
156 getLog().info("Creating registry client without authentication.");
157 }
158
159
160 configureTrustStore(clientOptions);
161
162
163 configureKeyStore(clientOptions);
164
165
166 if (trustAll) {
167 getLog().warn("TLS trust-all mode enabled. This is insecure and should only be used for development/testing.");
168 clientOptions.trustAll(true);
169 }
170 if (!verifyHostname) {
171 getLog().warn("Hostname verification disabled. This reduces security and should only be used for development/testing.");
172 clientOptions.verifyHost(false);
173 }
174
175 return RegistryClientFactory.create(clientOptions.retry());
176 }
177
178
179 void configureTrustStore(RegistryClientOptions clientOptions) {
180 if (trustStorePath == null || trustStorePath.isEmpty()) {
181 return;
182 }
183
184 String detectedType = detectStoreType(trustStorePath, trustStoreType);
185 getLog().info("Configuring trust store: " + trustStorePath + " (type: " + detectedType + ")");
186
187 switch (detectedType.toUpperCase(Locale.ROOT)) {
188 case "JKS":
189 clientOptions.trustStoreJks(trustStorePath, trustStorePassword);
190 break;
191 case "PKCS12":
192 clientOptions.trustStorePkcs12(trustStorePath, trustStorePassword);
193 break;
194 case "PEM":
195 clientOptions.trustStorePem(trustStorePath);
196 break;
197 default:
198 getLog().warn("Unknown trust store type: " + detectedType + ". Attempting to use as PKCS12.");
199 clientOptions.trustStorePkcs12(trustStorePath, trustStorePassword);
200 }
201 }
202
203
204 void configureKeyStore(RegistryClientOptions clientOptions) {
205 if (keyStorePath == null || keyStorePath.isEmpty()) {
206 return;
207 }
208
209 String detectedType = detectStoreType(keyStorePath, keyStoreType);
210 getLog().info("Configuring key store for mTLS: " + keyStorePath + " (type: " + detectedType + ")");
211
212 switch (detectedType.toUpperCase(Locale.ROOT)) {
213 case "JKS":
214 clientOptions.keystoreJks(keyStorePath, keyStorePassword);
215 break;
216 case "PKCS12":
217 clientOptions.keystorePkcs12(keyStorePath, keyStorePassword);
218 break;
219 case "PEM":
220 if (keyStorePemKeyPath == null || keyStorePemKeyPath.isEmpty()) {
221 throw new IllegalArgumentException(
222 "keyStorePemKeyPath is required when using PEM format for mTLS. " +
223 "Set keyStorePath to the certificate file and keyStorePemKeyPath to the private key file.");
224 }
225 clientOptions.keystorePem(keyStorePath, keyStorePemKeyPath);
226 break;
227 default:
228 getLog().warn("Unknown key store type: " + detectedType + ". Attempting to use as PKCS12.");
229 clientOptions.keystorePkcs12(keyStorePath, keyStorePassword);
230 }
231 }
232
233
234 String detectStoreType(String path, String explicitType) {
235 if (explicitType != null && !explicitType.isEmpty()) {
236 return explicitType;
237 }
238 String lowerPath = path.toLowerCase(Locale.ROOT);
239 if (lowerPath.endsWith(".jks")) {
240 return "JKS";
241 } else if (lowerPath.endsWith(".p12") || lowerPath.endsWith(".pfx")) {
242 return "PKCS12";
243 } else if (lowerPath.endsWith(".pem") || lowerPath.endsWith(".crt") || lowerPath.endsWith(".cer")) {
244 return "PEM";
245 }
246 return "PKCS12";
247 }
248
249 @Override
250 public void execute() throws MojoExecutionException, MojoFailureException {
251 try {
252 executeInternal();
253 } catch (ExecutionException e) {
254 throw new MojoExecutionException(e);
255 } catch (InterruptedException e) {
256 throw new MojoFailureException(e);
257 }
258 closeClients();
259 }
260
261 private void closeClients() {
262
263 }
264
265 protected abstract void executeInternal()
266 throws MojoExecutionException, MojoFailureException, ExecutionException, InterruptedException;
267
268 protected String getContentTypeByExtension(String fileName) {
269 if (fileName == null)
270 return null;
271 String[] temp = fileName.split("[.]");
272 String extension = temp[temp.length - 1];
273 switch (extension.toLowerCase(Locale.ROOT)) {
274 case "avro":
275 case "avsc":
276 case "json":
277 return ContentTypes.APPLICATION_JSON;
278 case "yml":
279 case "yaml":
280 return ContentTypes.APPLICATION_YAML;
281 case "graphql":
282 return ContentTypes.APPLICATION_GRAPHQL;
283 case "proto":
284 return ContentTypes.APPLICATION_PROTOBUF;
285 case "wsdl":
286 case "xsd":
287 case "xml":
288 return ContentTypes.APPLICATION_XML;
289 }
290 return null;
291 }
292
293 public void setRegistryUrl(String registryUrl) {
294 this.registryUrl = registryUrl;
295 }
296
297 public void setAuthServerUrl(String authServerUrl) {
298 this.authServerUrl = authServerUrl;
299 }
300
301 public void setClientId(String clientId) {
302 this.clientId = clientId;
303 }
304
305 public void setClientSecret(String clientSecret) {
306 this.clientSecret = clientSecret;
307 }
308
309 public void setClientScope(String clientScope) {
310 this.clientScope = clientScope;
311 }
312
313 public void setUsername(String username) {
314 this.username = username;
315 }
316
317 public void setPassword(String password) {
318 this.password = password;
319 }
320
321 public void setTrustStorePath(String trustStorePath) {
322 this.trustStorePath = trustStorePath;
323 }
324
325 public void setTrustStorePassword(String trustStorePassword) {
326 this.trustStorePassword = trustStorePassword;
327 }
328
329 public void setTrustStoreType(String trustStoreType) {
330 this.trustStoreType = trustStoreType;
331 }
332
333 public void setKeyStorePath(String keyStorePath) {
334 this.keyStorePath = keyStorePath;
335 }
336
337 public void setKeyStorePassword(String keyStorePassword) {
338 this.keyStorePassword = keyStorePassword;
339 }
340
341 public void setKeyStoreType(String keyStoreType) {
342 this.keyStoreType = keyStoreType;
343 }
344
345 public void setKeyStorePemKeyPath(String keyStorePemKeyPath) {
346 this.keyStorePemKeyPath = keyStorePemKeyPath;
347 }
348
349 public void setTrustAll(boolean trustAll) {
350 this.trustAll = trustAll;
351 }
352
353 public void setVerifyHostname(boolean verifyHostname) {
354 this.verifyHostname = verifyHostname;
355 }
356
357 protected void logAndThrow(ApiException e) throws MojoExecutionException, MojoFailureException {
358 if (e instanceof RuleViolationProblemDetails) {
359 logAndThrow((RuleViolationProblemDetails) e);
360 }
361 if (e instanceof ProblemDetails) {
362 logAndThrow((ProblemDetails) e);
363 }
364 }
365
366 protected void logAndThrow(ProblemDetails e) throws MojoExecutionException {
367 getLog().error("---");
368 getLog().error("Error registering artifact: " + e.getName());
369 getLog().error(e.getTitle());
370 getLog().error(e.getDetail());
371 getLog().error("---");
372 throw new MojoExecutionException("Error registering artifact: " + e.getName(), e);
373 }
374
375 protected void logAndThrow(RuleViolationProblemDetails e) throws MojoFailureException {
376 getLog().error("---");
377 getLog().error("Registry rule validation failure: " + e.getName());
378 getLog().error(e.getTitle());
379 if (e.getCauses() != null) {
380 e.getCauses().forEach(cause -> {
381 getLog().error("\t-> " + cause.getContext());
382 getLog().error("\t " + cause.getDescription());
383 });
384 }
385 getLog().error("---");
386 throw new MojoFailureException("Registry rule validation failure: " + e.getName(), e);
387 }
388
389 }