diff --git a/adapter/config/default_config.go b/adapter/config/default_config.go index e13a06f9bf..0b33e7e230 100644 --- a/adapter/config/default_config.go +++ b/adapter/config/default_config.go @@ -124,6 +124,16 @@ var defaultConfig = &Config{ DNS: upstreamDNS{ DNSRefreshRate: 5000, RespectDNSTtl: false, + DNSResolver: dnsResolverConfig{ + ResolverType: "", + CAres: cAres{ + Resolvers: []socketAddress{}, + UseResolversAsFallback: false, + FilterUnroutableFamilies: false, + UseTCPForDNSLookups: false, + NoDefaultSearchDomain: false, + }, + }, }, }, Connection: connection{ diff --git a/adapter/config/parser.go b/adapter/config/parser.go index 8de3660fcf..0f59766f23 100644 --- a/adapter/config/parser.go +++ b/adapter/config/parser.go @@ -104,6 +104,12 @@ func ReadConfigs() (*Config, error) { pkgconf.ResolveConfigEnvValues(reflect.ValueOf(&(adapterConfig.GlobalAdapter)).Elem(), "GlobalAdapter", true) pkgconf.ResolveConfigEnvValues(reflect.ValueOf(&(adapterConfig.Enforcer)).Elem(), "Enforcer", false) pkgconf.ResolveConfigEnvValues(reflect.ValueOf(&(adapterConfig.Analytics)).Elem(), "Analytics", false) + + err = adapterConfig.validateConfig() + if err != nil { + logger.Fatal("Error parsing the configuration: ", err) + return + } }) return adapterConfig, e } @@ -242,6 +248,10 @@ func (config *Config) resolveJWTGeneratorConfig() error { return nil } +func (config *Config) validateConfig() error { + return config.Envoy.Upstream.DNS.DNSResolver.ResolverType.isValid() +} + func printDeprecatedWarningLog(deprecatedTerm, currentTerm string) { logger.Warnf("%s is deprecated. Use %s instead", deprecatedTerm, currentTerm) } diff --git a/adapter/config/types.go b/adapter/config/types.go index 4a7f556a33..3ea08a5a29 100644 --- a/adapter/config/types.go +++ b/adapter/config/types.go @@ -17,6 +17,7 @@ package config import ( + "fmt" "sync" "time" ) @@ -266,6 +267,43 @@ type upstreamHealth struct { type upstreamDNS struct { DNSRefreshRate int32 RespectDNSTtl bool + DNSResolver dnsResolverConfig `toml:"DNSResolver"` +} + +type dnsResolverConfig struct { + ResolverType dnsResolverType + CAres cAres +} + +type dnsResolverType string + +const ( + // DNSResolverCAres is the c-ares DNS resolver type + DNSResolverCAres dnsResolverType = "c-ares" +) + +func (r dnsResolverType) isValid() error { + switch r { + case DNSResolverCAres: // if required we can include DNS_RESOLVER_APPLE here + return nil + case "": // for Envoy default settings + return nil + } + return fmt.Errorf("invalid DNS resolver type: %q, supported types [%q]", r, DNSResolverCAres) +} + +type cAres struct { + Resolvers []socketAddress + UseResolversAsFallback bool + FilterUnroutableFamilies bool + UseTCPForDNSLookups bool + NoDefaultSearchDomain bool +} + +type socketAddress struct { + Protocol string + Address string + Port uint32 } type upstreamRetry struct { diff --git a/adapter/internal/oasparser/envoyconf/dns_resolver.go b/adapter/internal/oasparser/envoyconf/dns_resolver.go new file mode 100644 index 0000000000..2934fae1ae --- /dev/null +++ b/adapter/internal/oasparser/envoyconf/dns_resolver.go @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package envoyconf + +import ( + "fmt" + + corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + caresv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/cares/v3" + "github.com/wso2/product-microgateway/adapter/config" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" +) + +func getDNSResolverConf() (*corev3.TypedExtensionConfig, error) { + conf, _ := config.ReadConfigs() + var dnsResolverConf proto.Message + + switch conf.Envoy.Upstream.DNS.DNSResolver.ResolverType { + case "": // Use Envoy default settings + return nil, nil + case config.DNSResolverCAres: + resolvers := []*corev3.Address{} + for _, resolver := range conf.Envoy.Upstream.DNS.DNSResolver.CAres.Resolvers { + protocol := corev3.SocketAddress_Protocol_value[resolver.Protocol] + resolvers = append(resolvers, &corev3.Address{ + Address: &corev3.Address_SocketAddress{ + SocketAddress: &corev3.SocketAddress{ + Protocol: corev3.SocketAddress_Protocol(protocol), + Address: resolver.Address, + PortSpecifier: &corev3.SocketAddress_PortValue{ + PortValue: resolver.Port, + }, + }, + }, + }) + } + + dnsResolverConf = &caresv3.CaresDnsResolverConfig{ + Resolvers: resolvers, + UseResolversAsFallback: conf.Envoy.Upstream.DNS.DNSResolver.CAres.UseResolversAsFallback, + FilterUnroutableFamilies: conf.Envoy.Upstream.DNS.DNSResolver.CAres.FilterUnroutableFamilies, + DnsResolverOptions: &corev3.DnsResolverOptions{ + UseTcpForDnsLookups: conf.Envoy.Upstream.DNS.DNSResolver.CAres.UseTCPForDNSLookups, + NoDefaultSearchDomain: conf.Envoy.Upstream.DNS.DNSResolver.CAres.NoDefaultSearchDomain, + }, + } + // case config.DNS_RESOLVER_APPLE: // If required we can support other resolvers here + default: + return nil, fmt.Errorf("unsupported DNS resolver type: %s", conf.Envoy.Upstream.DNS.DNSResolver.ResolverType) + } + + dnsResolverConfPbAny, err := anypb.New(dnsResolverConf) + if err != nil { + return nil, err + } + + return &corev3.TypedExtensionConfig{ + Name: "Upstream DNS resolver", + TypedConfig: dnsResolverConfPbAny, + }, nil +} diff --git a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go index 09c5cc813f..86369351d6 100644 --- a/adapter/internal/oasparser/envoyconf/routes_with_clusters.go +++ b/adapter/internal/oasparser/envoyconf/routes_with_clusters.go @@ -563,6 +563,11 @@ func processEndpoints(clusterName string, clusterDetails *model.EndpointCluster, } conf, _ := config.ReadConfigs() + dnsResolverConf, err := getDNSResolverConf() + if err != nil { + return nil, nil, err + } + cluster := clusterv3.Cluster{ Name: clusterName, ConnectTimeout: ptypes.DurationProto(timeout * time.Second), @@ -576,6 +581,7 @@ func processEndpoints(clusterName string, clusterDetails *model.EndpointCluster, TransportSocketMatches: transportSocketMatches, DnsRefreshRate: durationpb.New(time.Duration(conf.Envoy.Upstream.DNS.DNSRefreshRate) * time.Millisecond), RespectDnsTtl: conf.Envoy.Upstream.DNS.RespectDNSTtl, + TypedDnsResolverConfig: dnsResolverConf, } if len(clusterDetails.Endpoints) > 1 { diff --git a/resources/conf/config.toml.template b/resources/conf/config.toml.template index fe8eae8e33..c39389e048 100644 --- a/resources/conf/config.toml.template +++ b/resources/conf/config.toml.template @@ -134,12 +134,30 @@ retainKeys = ["self_validate_jwt", "issuer", "claim_mappings", "consumer_key_cla # Disable SSL verification disableSslVerification = false -[router.upstream.dns] - # DNS refresh rate in miliseconds +[router.upstream.dNS] + # DNS refresh rate in milliseconds dNSRefreshRate = 5000 # set cluster’s DNS refresh rate to resource record’s TTL which comes from DNS resolution respectDNSTtl = false - + +[router.upstream.dNS.DNSResolver] + # DNS resolver type. Available options: "c-ares" + resolverType = "c-ares" +[router.upstream.dNS.DNSResolver.cAres] + # Use the system DNS resolver as a fallback when the c-ares resolver fails. If false the resolvers listed in the resolvers list will override the default system resolvers. + useResolversAsFallback = false + # If there are no available network interfaces for a given IP family, filter those addresses from the results + filterUnroutableFamilies = false + # Use TCP for all DNS queries instead of the default protocol UDP. + useTCPForDNSLookups = false + # Do not use the default search domains; only query hostnames as-is or as aliases. + noDefaultSearchDomain = false +[[router.upstream.dNS.DNSResolver.cAres.resolvers]] + # Available options: "UDP", "TCP" + protocol = "TCP" + address = "0.0.0.0" + port = 53 + # health configs for upstream clusters [router.upstream.health] # time in seconds to wait for a health check response