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 if (registryUrl == null || registryUrl.isEmpty()) {
142 throw new IllegalArgumentException(
143 "Missing registry URL. Provide -Dapicurio.url=...");
144 }
145 RegistryClientOptions clientOptions = RegistryClientOptions.create(registryUrl, vertx);
146
147
148 if (authServerUrl != null && clientId != null && clientSecret != null) {
149 if (clientScope != null && !clientScope.isEmpty()) {
150 getLog().info("Creating registry client with OAuth2 authentication with scope.");
151 clientOptions.oauth2(authServerUrl, clientId, clientSecret, clientScope);
152 } else {
153 getLog().info("Creating registry client with OAuth2 authentication.");
154 clientOptions.oauth2(authServerUrl, clientId, clientSecret);
155 }
156 } else if (username != null && password != null) {
157 getLog().info("Creating registry client with Basic authentication.");
158 clientOptions.basicAuth(username, password);
159 } else {
160 getLog().info("Creating registry client without authentication.");
161 }
162
163
164 configureTrustStore(clientOptions);
165
166
167 configureKeyStore(clientOptions);
168
169
170 if (trustAll) {
171 getLog().warn("TLS trust-all mode enabled. This is insecure and should only be used for development/testing.");
172 clientOptions.trustAll(true);
173 }
174 if (!verifyHostname) {
175 getLog().warn("Hostname verification disabled. This reduces security and should only be used for development/testing.");
176 clientOptions.verifyHost(false);
177 }
178
179 return RegistryClientFactory.create(clientOptions.retry());
180 }
181
182
183 void configureTrustStore(RegistryClientOptions clientOptions) {
184 if (trustStorePath == null || trustStorePath.isEmpty()) {
185 return;
186 }
187
188 String detectedType = detectStoreType(trustStorePath, trustStoreType);
189 getLog().info("Configuring trust store: " + trustStorePath + " (type: " + detectedType + ")");
190
191 switch (detectedType.toUpperCase(Locale.ROOT)) {
192 case "JKS":
193 clientOptions.trustStoreJks(trustStorePath, trustStorePassword);
194 break;
195 case "PKCS12":
196 clientOptions.trustStorePkcs12(trustStorePath, trustStorePassword);
197 break;
198 case "PEM":
199 clientOptions.trustStorePem(trustStorePath);
200 break;
201 default:
202 getLog().warn("Unknown trust store type: " + detectedType + ". Attempting to use as PKCS12.");
203 clientOptions.trustStorePkcs12(trustStorePath, trustStorePassword);
204 }
205 }
206
207
208 void configureKeyStore(RegistryClientOptions clientOptions) {
209 if (keyStorePath == null || keyStorePath.isEmpty()) {
210 return;
211 }
212
213 String detectedType = detectStoreType(keyStorePath, keyStoreType);
214 getLog().info("Configuring key store for mTLS: " + keyStorePath + " (type: " + detectedType + ")");
215
216 switch (detectedType.toUpperCase(Locale.ROOT)) {
217 case "JKS":
218 clientOptions.keystoreJks(keyStorePath, keyStorePassword);
219 break;
220 case "PKCS12":
221 clientOptions.keystorePkcs12(keyStorePath, keyStorePassword);
222 break;
223 case "PEM":
224 if (keyStorePemKeyPath == null || keyStorePemKeyPath.isEmpty()) {
225 throw new IllegalArgumentException(
226 "keyStorePemKeyPath is required when using PEM format for mTLS. " +
227 "Set keyStorePath to the certificate file and keyStorePemKeyPath to the private key file.");
228 }
229 clientOptions.keystorePem(keyStorePath, keyStorePemKeyPath);
230 break;
231 default:
232 getLog().warn("Unknown key store type: " + detectedType + ". Attempting to use as PKCS12.");
233 clientOptions.keystorePkcs12(keyStorePath, keyStorePassword);
234 }
235 }
236
237
238 String detectStoreType(String path, String explicitType) {
239 if (explicitType != null && !explicitType.isEmpty()) {
240 return explicitType;
241 }
242 String lowerPath = path.toLowerCase(Locale.ROOT);
243 if (lowerPath.endsWith(".jks")) {
244 return "JKS";
245 } else if (lowerPath.endsWith(".p12") || lowerPath.endsWith(".pfx")) {
246 return "PKCS12";
247 } else if (lowerPath.endsWith(".pem") || lowerPath.endsWith(".crt") || lowerPath.endsWith(".cer")) {
248 return "PEM";
249 }
250 return "PKCS12";
251 }
252
253 @Override
254 public void execute() throws MojoExecutionException, MojoFailureException {
255 try {
256 executeInternal();
257 } catch (ExecutionException e) {
258 throw new MojoExecutionException(e);
259 } catch (InterruptedException e) {
260 throw new MojoFailureException(e);
261 }
262 closeClients();
263 }
264
265 private void closeClients() {
266
267 }
268
269 protected abstract void executeInternal()
270 throws MojoExecutionException, MojoFailureException, ExecutionException, InterruptedException;
271
272 protected String getContentTypeByExtension(String fileName) {
273 if (fileName == null)
274 return null;
275 String[] temp = fileName.split("[.]");
276 String extension = temp[temp.length - 1];
277 switch (extension.toLowerCase(Locale.ROOT)) {
278 case "avro":
279 case "avsc":
280 case "json":
281 return ContentTypes.APPLICATION_JSON;
282 case "yml":
283 case "yaml":
284 return ContentTypes.APPLICATION_YAML;
285 case "graphql":
286 return ContentTypes.APPLICATION_GRAPHQL;
287 case "proto":
288 return ContentTypes.APPLICATION_PROTOBUF;
289 case "wsdl":
290 case "xsd":
291 case "xml":
292 return ContentTypes.APPLICATION_XML;
293 }
294 return null;
295 }
296
297 public void setRegistryUrl(String registryUrl) {
298 this.registryUrl = registryUrl;
299 }
300
301 public void setAuthServerUrl(String authServerUrl) {
302 this.authServerUrl = authServerUrl;
303 }
304
305 public void setClientId(String clientId) {
306 this.clientId = clientId;
307 }
308
309 public void setClientSecret(String clientSecret) {
310 this.clientSecret = clientSecret;
311 }
312
313 public void setClientScope(String clientScope) {
314 this.clientScope = clientScope;
315 }
316
317 public void setUsername(String username) {
318 this.username = username;
319 }
320
321 public void setPassword(String password) {
322 this.password = password;
323 }
324
325 public void setTrustStorePath(String trustStorePath) {
326 this.trustStorePath = trustStorePath;
327 }
328
329 public void setTrustStorePassword(String trustStorePassword) {
330 this.trustStorePassword = trustStorePassword;
331 }
332
333 public void setTrustStoreType(String trustStoreType) {
334 this.trustStoreType = trustStoreType;
335 }
336
337 public void setKeyStorePath(String keyStorePath) {
338 this.keyStorePath = keyStorePath;
339 }
340
341 public void setKeyStorePassword(String keyStorePassword) {
342 this.keyStorePassword = keyStorePassword;
343 }
344
345 public void setKeyStoreType(String keyStoreType) {
346 this.keyStoreType = keyStoreType;
347 }
348
349 public void setKeyStorePemKeyPath(String keyStorePemKeyPath) {
350 this.keyStorePemKeyPath = keyStorePemKeyPath;
351 }
352
353 public void setTrustAll(boolean trustAll) {
354 this.trustAll = trustAll;
355 }
356
357 public void setVerifyHostname(boolean verifyHostname) {
358 this.verifyHostname = verifyHostname;
359 }
360
361 protected void logAndThrow(ApiException e) throws MojoExecutionException, MojoFailureException {
362 if (e instanceof RuleViolationProblemDetails) {
363 logAndThrow((RuleViolationProblemDetails) e);
364 }
365 if (e instanceof ProblemDetails) {
366 logAndThrow((ProblemDetails) e);
367 }
368 }
369
370 protected void logAndThrow(ProblemDetails e) throws MojoExecutionException {
371 getLog().error("---");
372 getLog().error("Error registering artifact: " + e.getName());
373 getLog().error(e.getTitle());
374 getLog().error(e.getDetail());
375 getLog().error("---");
376 throw new MojoExecutionException("Error registering artifact: " + e.getName(), e);
377 }
378
379 protected void logAndThrow(RuleViolationProblemDetails e) throws MojoFailureException {
380 getLog().error("---");
381 getLog().error("Registry rule validation failure: " + e.getName());
382 getLog().error(e.getTitle());
383 if (e.getCauses() != null) {
384 e.getCauses().forEach(cause -> {
385 getLog().error("\t-> " + cause.getContext());
386 getLog().error("\t " + cause.getDescription());
387 });
388 }
389 getLog().error("---");
390 throw new MojoFailureException("Registry rule validation failure: " + e.getName(), e);
391 }
392
393 }