From ede39d8edc44ca05d4d2604fb27e996314c3db60 Mon Sep 17 00:00:00 2001 From: Fabricio Duarte Date: Tue, 27 Aug 2024 16:12:31 -0300 Subject: [PATCH] Configuration to disable URL validation when registering templates/ISOs (#8751) --- .../com/cloud/template/TemplateManager.java | 9 ++++ .../template/HypervisorTemplateAdapter.java | 4 +- .../cloud/template/TemplateManagerImpl.java | 2 +- .../main/java/com/cloud/utils/UriUtils.java | 45 ++++++++++++++----- 4 files changed, 45 insertions(+), 15 deletions(-) diff --git a/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java b/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java index 3b3537d5488..997ae3985f1 100644 --- a/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java +++ b/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java @@ -51,6 +51,11 @@ public interface TemplateManager { static final ConfigKey TemplatePreloaderPoolSize = new ConfigKey("Advanced", Integer.class, TemplatePreloaderPoolSizeCK, "8", "Size of the TemplateManager threadpool", false, ConfigKey.Scope.Global); + ConfigKey ValidateUrlIsResolvableBeforeRegisteringTemplate = new ConfigKey<>("Advanced", Boolean.class, + "validate.url.is.resolvable.before.registering.template", "true", "Indicates whether CloudStack " + + "will validate if the provided URL is resolvable during the register of templates/ISOs before persisting them in the database.", + true); + static final String VMWARE_TOOLS_ISO = "vmware-tools.iso"; static final String XS_TOOLS_ISO = "xs-tools.iso"; @@ -139,4 +144,8 @@ public interface TemplateManager { TemplateType validateTemplateType(BaseCmd cmd, boolean isAdmin, boolean isCrossZones); List getTemplateDisksOnImageStore(Long templateId, DataStoreRole role, String configurationId); + + static Boolean getValidateUrlIsResolvableBeforeRegisteringTemplateValue() { + return ValidateUrlIsResolvableBeforeRegisteringTemplate.value(); + } } diff --git a/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java b/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java index b7a511aad36..026a9350f33 100644 --- a/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java +++ b/server/src/main/java/com/cloud/template/HypervisorTemplateAdapter.java @@ -204,7 +204,7 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase { public TemplateProfile prepare(RegisterIsoCmd cmd) throws ResourceAllocationException { TemplateProfile profile = super.prepare(cmd); String url = profile.getUrl(); - UriUtils.validateUrl(ImageFormat.ISO.getFileExtension(), url); + UriUtils.validateUrl(ImageFormat.ISO.getFileExtension(), url, !TemplateManager.getValidateUrlIsResolvableBeforeRegisteringTemplateValue(), false); boolean followRedirects = StorageManager.DataStoreDownloadFollowRedirects.value(); if (cmd.isDirectDownload()) { DigestHelper.validateChecksumString(cmd.getChecksum()); @@ -238,7 +238,7 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase { public TemplateProfile prepare(RegisterTemplateCmd cmd) throws ResourceAllocationException { TemplateProfile profile = super.prepare(cmd); String url = profile.getUrl(); - UriUtils.validateUrl(cmd.getFormat(), url, cmd.isDirectDownload()); + UriUtils.validateUrl(cmd.getFormat(), url, !TemplateManager.getValidateUrlIsResolvableBeforeRegisteringTemplateValue(), cmd.isDirectDownload()); Hypervisor.HypervisorType hypervisor = Hypervisor.HypervisorType.getType(cmd.getHypervisor()); boolean followRedirects = StorageManager.DataStoreDownloadFollowRedirects.value(); if (cmd.isDirectDownload()) { diff --git a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java index 4d095d09cc6..af558304981 100755 --- a/server/src/main/java/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/main/java/com/cloud/template/TemplateManagerImpl.java @@ -2349,7 +2349,7 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager, @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[] {AllowPublicUserTemplates, TemplatePreloaderPoolSize}; + return new ConfigKey[] {AllowPublicUserTemplates, TemplatePreloaderPoolSize, ValidateUrlIsResolvableBeforeRegisteringTemplate}; } public List getTemplateAdapters() { diff --git a/utils/src/main/java/com/cloud/utils/UriUtils.java b/utils/src/main/java/com/cloud/utils/UriUtils.java index e1766e691c2..4964215020d 100644 --- a/utils/src/main/java/com/cloud/utils/UriUtils.java +++ b/utils/src/main/java/com/cloud/utils/UriUtils.java @@ -263,10 +263,17 @@ public class UriUtils { } public static Pair validateUrl(String format, String url) throws IllegalArgumentException { - return validateUrl(format, url, false); + return validateUrl(format, url, false, false); } - public static Pair validateUrl(String format, String url, boolean skipIpv6Check) throws IllegalArgumentException { + /** + * Verifies whether the provided URL is valid. + * @param skipHostCheck if false, this function will verify whether the provided URL is resolvable, if it is a legal address and if it does not use IPv6 (configured by `skipIpv6Check`). If any of these conditions are false, an exception will be thrown. + * @param skipIpv6Check if false, this function will verify whether the host uses IPv6 and, if it does, an exception will be thrown. This check is also skipped if `skipHostCheck` is true. + * @return a pair containing the host and the corresponding port. + * @throws IllegalArgumentException if the provided URL is invalid. + */ + public static Pair validateUrl(String format, String url, boolean skipHostCheck, boolean skipIpv6Check) throws IllegalArgumentException { try { URI uri = new URI(url); if ((uri.getScheme() == null) || @@ -282,16 +289,8 @@ public class UriUtils { } String host = uri.getHost(); - try { - InetAddress hostAddr = InetAddress.getByName(host); - if (hostAddr.isAnyLocalAddress() || hostAddr.isLinkLocalAddress() || hostAddr.isLoopbackAddress() || hostAddr.isMulticastAddress()) { - throw new IllegalArgumentException("Illegal host specified in url"); - } - if (!skipIpv6Check && hostAddr instanceof Inet6Address) { - throw new IllegalArgumentException("IPV6 addresses not supported (" + hostAddr.getHostAddress() + ")"); - } - } catch (UnknownHostException uhe) { - throw new IllegalArgumentException("Unable to resolve " + host); + if (!skipHostCheck) { + checkHost(host, skipIpv6Check); } // verify format @@ -305,6 +304,28 @@ public class UriUtils { } } + /** + * Verifies whether the provided host is valid. Throws an `IllegalArgumentException` if: + *
    + *
  • The host is not resolvable;
  • + *
  • The host address is illegal (any local, link local, loopback or multicast address);
  • + *
  • The host uses IPv6. This check is skipped if `skipIv6Check` is set to true.
  • + *
+ */ + private static void checkHost(String host, boolean skipIpv6Check) { + try { + InetAddress hostAddr = InetAddress.getByName(host); + if (hostAddr.isAnyLocalAddress() || hostAddr.isLinkLocalAddress() || hostAddr.isLoopbackAddress() || hostAddr.isMulticastAddress()) { + throw new IllegalArgumentException("Illegal host specified in URL."); + } + if (!skipIpv6Check && hostAddr instanceof Inet6Address) { + throw new IllegalArgumentException(String.format("IPv6 addresses are not supported (%s).", hostAddr.getHostAddress())); + } + } catch (UnknownHostException uhe) { + throw new IllegalArgumentException(String.format("Unable to resolve %s.", host)); + } + } + /** * Add element to priority list examining node attributes: priority (for urls) and type (for checksums) */