github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/utils/azure/endpoints.go (about) 1 /* 2 Copyright 2022 Gravitational, Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package azure 18 19 import ( 20 "net" 21 "net/url" 22 "strings" 23 24 "github.com/gravitational/trace" 25 ) 26 27 // IsAzureEndpoint returns true if the input URI is an Azure endpoint. 28 // 29 // The code implements approximate solution based on: 30 // - https://management.azure.com/metadata/endpoints?api-version=2019-05-01 31 // - https://github.com/Azure/azure-cli/blob/dev/src/azure-cli-core/azure/cli/core/cloud.py 32 func IsAzureEndpoint(hostname string) bool { 33 suffixes := []string{ 34 "management.azure.com", 35 "graph.windows.net", 36 "batch.core.windows.net", 37 "rest.media.azure.net", 38 "datalake.azure.net", 39 "management.core.windows.net", 40 "gallery.azure.com", 41 42 "azuredatalakestore.net", 43 "azurecr.io", 44 "database.windows.net", 45 "azuredatalakeanalytics.net", 46 "vault.azure.net", 47 "core.windows.net", 48 "azurefd.net", 49 50 "login.microsoftonline.com", // required for "az logout" 51 "graph.microsoft.com", // Azure AD 52 } 53 54 for _, suffix := range suffixes { 55 // exact match 56 if hostname == suffix { 57 return true 58 } 59 // .suffix match 60 if strings.HasSuffix(hostname, "."+suffix) { 61 return true 62 } 63 } 64 65 return false 66 } 67 68 // IsDatabaseEndpoint returns true if provided endpoint is a valid database 69 // endpoint. 70 func IsDatabaseEndpoint(endpoint string) bool { 71 return strings.Contains(endpoint, DatabaseEndpointSuffix) 72 } 73 74 // IsCacheForRedisEndpoint returns true if provided endpoint is a valid Azure 75 // Cache for Redis endpoint. 76 func IsCacheForRedisEndpoint(endpoint string) bool { 77 return IsRedisEndpoint(endpoint) || IsRedisEnterpriseEndpoint(endpoint) 78 } 79 80 // IsRedisEndpoint returns true if provided endpoint is a valid Redis 81 // (non-Enterprise tier) endpoint. 82 func IsRedisEndpoint(endpoint string) bool { 83 return strings.Contains(endpoint, RedisEndpointSuffix) 84 } 85 86 // IsRedisEnterpriseEndpoint returns true if provided endpoint is a valid Redis 87 // Enterprise endpoint. 88 func IsRedisEnterpriseEndpoint(endpoint string) bool { 89 return strings.Contains(endpoint, RedisEnterpriseEndpointSuffix) 90 } 91 92 // IsMSSQLServerEndpoint returns true if provided endpoint is a valid SQL server 93 // database endpoint. 94 func IsMSSQLServerEndpoint(endpoint string) bool { 95 return strings.Contains(endpoint, MSSQLEndpointSuffix) 96 } 97 98 // ParseDatabaseEndpoint extracts database server name from Azure endpoint. 99 func ParseDatabaseEndpoint(endpoint string) (name string, err error) { 100 host, _, err := net.SplitHostPort(endpoint) 101 if err != nil { 102 return "", trace.Wrap(err) 103 } 104 // Azure endpoint looks like this: 105 // name.mysql.database.azure.com 106 parts := strings.Split(host, ".") 107 if !strings.HasSuffix(host, DatabaseEndpointSuffix) || len(parts) != 5 { 108 return "", trace.BadParameter("failed to parse %v as Azure endpoint", endpoint) 109 } 110 return parts[0], nil 111 } 112 113 // ParseCacheForRedisEndpoint extracts database server name from Azure Cache 114 // for Redis endpoint. 115 func ParseCacheForRedisEndpoint(endpoint string) (name string, err error) { 116 // Note that the Redis URI may contain schema and parameters. 117 host, err := GetHostFromRedisURI(endpoint) 118 if err != nil { 119 return "", trace.Wrap(err) 120 } 121 122 switch { 123 // Redis (non-Enterprise) endpoint looks like this: 124 // name.redis.cache.windows.net 125 case strings.HasSuffix(host, RedisEndpointSuffix): 126 return strings.TrimSuffix(host, RedisEndpointSuffix), nil 127 128 // Redis Enterprise endpoint looks like this: 129 // name.region.redisenterprise.cache.azure.net 130 case strings.HasSuffix(host, RedisEnterpriseEndpointSuffix): 131 name, _, ok := strings.Cut(strings.TrimSuffix(host, RedisEnterpriseEndpointSuffix), ".") 132 if !ok { 133 return "", trace.BadParameter("failed to parse %v as Azure Cache endpoint", endpoint) 134 } 135 return name, nil 136 137 default: 138 return "", trace.BadParameter("failed to parse %v as Azure Cache endpoint", endpoint) 139 } 140 } 141 142 // GetHostFromRedisURI extracts host name from a Redis URI. The URI may start 143 // with "redis://", "rediss://", or without. The URI may also have parameters 144 // like "?mode=cluster". 145 func GetHostFromRedisURI(uri string) (string, error) { 146 // Add a temporary schema to make a valid URL for url.Parse if schema is 147 // not found. 148 if !strings.Contains(uri, "://") { 149 uri = "schema://" + uri 150 } 151 152 parsed, err := url.Parse(uri) 153 if err != nil { 154 return "", trace.Wrap(err) 155 } 156 return parsed.Hostname(), nil 157 } 158 159 // ParseMSSQLEndpoint extracts database server name from Azure endpoint. 160 func ParseMSSQLEndpoint(endpoint string) (name string, err error) { 161 host, _, err := net.SplitHostPort(endpoint) 162 if err != nil { 163 return "", trace.Wrap(err) 164 } 165 // Azure endpoint looks like this: 166 // name.database.windows.net 167 parts := strings.Split(host, ".") 168 if !strings.HasSuffix(host, MSSQLEndpointSuffix) || len(parts) != 4 { 169 return "", trace.BadParameter("failed to parse %v as Azure MSSQL endpoint", endpoint) 170 } 171 172 if parts[0] == "" { 173 return "", trace.BadParameter("endpoint %v must contain database name", endpoint) 174 } 175 176 return parts[0], nil 177 } 178 179 const ( 180 // DatabaseEndpointSuffix is the Azure database endpoint suffix. Used for 181 // MySQL, PostgresSQL, etc. 182 DatabaseEndpointSuffix = ".database.azure.com" 183 184 // RedisEndpointSuffix is the endpoint suffix for Redis. 185 RedisEndpointSuffix = ".redis.cache.windows.net" 186 187 // RedisEnterpriseEndpointSuffix is the endpoint suffix for Redis Enterprise. 188 RedisEnterpriseEndpointSuffix = ".redisenterprise.cache.azure.net" 189 190 // MSSQLEndpointSuffix is the Azure SQL Server endpoint suffix. 191 MSSQLEndpointSuffix = ".database.windows.net" 192 )