MM-69213: Validate Azure storage account names and use httpservice for custom endpoints (#37014)

* Validate Azure storage account name format

For the commercial and government clouds the storage account name
becomes part of the service hostname, so enforce Azure's documented
format (3 to 24 lowercase letters and digits) both in config
validation and when building the service URL. Also treat an empty
AzureCloud as commercial, consistent with the URL builder.

* Route Azure custom endpoints through httpservice

The Azure custom-cloud backend now issues requests through
httpservice's transport rather than the SDK default, so they honor
ServiceSettings.AllowedUntrustedInternalConnections like other
outbound connections. The commercial and government clouds keep the
SDK default transport, which Private Link deployments rely on.

The allowlist value is plumbed into FileBackendSettings, and the
host/IP checks are factored into a shared httpservice helper that
MakeTransport now uses too.
This commit is contained in:
Alejandro García Montoro
2026-06-15 15:46:42 +02:00
committed by GitHub
parent 0a497aa3e8
commit 82106eb77e
13 changed files with 358 additions and 104 deletions
+56 -28
View File
@@ -71,6 +71,58 @@ func (h *HTTPServiceImpl) MakeClient(trustURLs bool) *http.Client {
}
}
// isAllowedInternalHost reports whether host appears verbatim in a
// space/comma-separated AllowedUntrustedInternalConnections value.
func isAllowedInternalHost(host, allowedUntrustedInternalConnections string) bool {
return slices.Contains(strings.FieldsFunc(allowedUntrustedInternalConnections, splitFields), host)
}
// checkInternalIP returns nil if ip may be dialed, or an error if it is a
// reserved-range or self-assigned IP not covered by a CIDR entry in the
// space/comma-separated AllowedUntrustedInternalConnections value.
func checkInternalIP(ip net.IP, allowedUntrustedInternalConnections string) error {
reservedIP := IsReservedIP(ip)
ownIP, err := IsOwnIP(ip)
if err != nil {
// If there is an error getting the self-assigned IPs, default to the secure option
return fmt.Errorf("unable to determine if IP is own IP: %w", err)
}
// If it's not a reserved IP and it's not self-assigned IP, accept the IP
if !reservedIP && !ownIP {
return nil
}
// Otherwise it needs to be explicitly added to AllowedUntrustedInternalConnections
for _, allowed := range strings.FieldsFunc(allowedUntrustedInternalConnections, splitFields) {
if _, ipRange, err := net.ParseCIDR(allowed); err == nil && ipRange.Contains(ip) {
return nil
}
}
if reservedIP {
return fmt.Errorf("IP %s is in a reserved range and not in AllowedUntrustedInternalConnections", ip)
}
return fmt.Errorf("IP %s is a self-assigned IP and not in AllowedUntrustedInternalConnections", ip)
}
// NewTransportForInternalConnections returns a transport that applies the same
// AllowedUntrustedInternalConnections filtering as MakeTransport, but takes the
// allowlist as a static value instead of reading it from a config service. It is
// for callers that have the allowlist string but not an HTTPService.
func NewTransportForInternalConnections(insecure bool, allowedUntrustedInternalConnections string) *MattermostTransport {
allowHost := func(host string) bool {
return isAllowedInternalHost(host, allowedUntrustedInternalConnections)
}
allowIP := func(ip net.IP) error {
return checkInternalIP(ip, allowedUntrustedInternalConnections)
}
return NewTransport(insecure, allowHost, allowIP)
}
func (h *HTTPServiceImpl) MakeTransport(trustURLs bool) *MattermostTransport {
insecure := h.configService.Config().ServiceSettings.EnableInsecureOutgoingConnections != nil && *h.configService.Config().ServiceSettings.EnableInsecureOutgoingConnections
@@ -78,38 +130,14 @@ func (h *HTTPServiceImpl) MakeTransport(trustURLs bool) *MattermostTransport {
return NewTransport(insecure, nil, nil)
}
// The allowlist is read inside the closures so a long-lived client picks up
// config changes on each dial.
allowHost := func(host string) bool {
if h.configService.Config().ServiceSettings.AllowedUntrustedInternalConnections == nil {
return false
}
return slices.Contains(strings.FieldsFunc(*h.configService.Config().ServiceSettings.AllowedUntrustedInternalConnections, splitFields), host)
return isAllowedInternalHost(host, model.SafeDereference(h.configService.Config().ServiceSettings.AllowedUntrustedInternalConnections))
}
allowIP := func(ip net.IP) error {
reservedIP := IsReservedIP(ip)
ownIP, err := IsOwnIP(ip)
if err != nil {
// If there is an error getting the self-assigned IPs, default to the secure option
return fmt.Errorf("unable to determine if IP is own IP: %w", err)
}
// If it's not a reserved IP and it's not self-assigned IP, accept the IP
if !reservedIP && !ownIP {
return nil
}
// In the case it's the self-assigned IP, enforce that it needs to be explicitly added to the AllowedUntrustedInternalConnections
for _, allowed := range strings.FieldsFunc(model.SafeDereference(h.configService.Config().ServiceSettings.AllowedUntrustedInternalConnections), splitFields) {
if _, ipRange, err := net.ParseCIDR(allowed); err == nil && ipRange.Contains(ip) {
return nil
}
}
if reservedIP {
return fmt.Errorf("IP %s is in a reserved range and not in AllowedUntrustedInternalConnections", ip)
}
return fmt.Errorf("IP %s is a self-assigned IP and not in AllowedUntrustedInternalConnections", ip)
return checkInternalIP(ip, model.SafeDereference(h.configService.Config().ServiceSettings.AllowedUntrustedInternalConnections))
}
return NewTransport(insecure, allowHost, allowIP)