github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/types/app_test.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 types 18 19 import ( 20 "fmt" 21 "testing" 22 23 "github.com/gravitational/trace" 24 "github.com/stretchr/testify/require" 25 26 "github.com/gravitational/teleport/api/constants" 27 ) 28 29 // TestAppPublicAddrValidation tests PublicAddr field validation to make sure that 30 // an app with internal "kube-teleport-proxy-alpn." ServerName prefix won't be created. 31 func TestAppPublicAddrValidation(t *testing.T) { 32 type check func(t *testing.T, err error) 33 34 hasNoErr := func() check { 35 return func(t *testing.T, err error) { 36 require.NoError(t, err) 37 } 38 } 39 hasErrTypeBadParameter := func() check { 40 return func(t *testing.T, err error) { 41 require.True(t, trace.IsBadParameter(err)) 42 } 43 } 44 45 tests := []struct { 46 name string 47 publicAddr string 48 check check 49 }{ 50 { 51 name: "kubernetes app", 52 publicAddr: "kubernetes.example.com:3080", 53 check: hasNoErr(), 54 }, 55 { 56 name: "kubernetes app public addr without port", 57 publicAddr: "kubernetes.example.com", 58 check: hasNoErr(), 59 }, 60 { 61 name: "kubernetes app http", 62 publicAddr: "http://kubernetes.example.com:3080", 63 check: hasNoErr(), 64 }, 65 { 66 name: "kubernetes app https", 67 publicAddr: "https://kubernetes.example.com:3080", 68 check: hasNoErr(), 69 }, 70 { 71 name: "public address with internal kube ServerName prefix", 72 publicAddr: constants.KubeTeleportProxyALPNPrefix + "example.com:3080", 73 check: hasErrTypeBadParameter(), 74 }, 75 { 76 name: "https public address with internal kube ServerName prefix", 77 publicAddr: "https://" + constants.KubeTeleportProxyALPNPrefix + "example.com:3080", 78 check: hasErrTypeBadParameter(), 79 }, 80 } 81 82 for _, tc := range tests { 83 t.Run(tc.name, func(t *testing.T) { 84 _, err := NewAppV3(Metadata{ 85 Name: "TestApp", 86 }, AppSpecV3{ 87 PublicAddr: tc.publicAddr, 88 URI: "localhost:3080", 89 }) 90 tc.check(t, err) 91 }) 92 } 93 } 94 95 func TestAppServerSorter(t *testing.T) { 96 t.Parallel() 97 98 testValsUnordered := []string{"d", "b", "a", "c"} 99 100 makeServers := func(testVals []string, testField string) []AppServer { 101 servers := make([]AppServer, len(testVals)) 102 for i := 0; i < len(testVals); i++ { 103 testVal := testVals[i] 104 var err error 105 servers[i], err = NewAppServerV3(Metadata{ 106 Name: "_", 107 }, AppServerSpecV3{ 108 HostID: "_", 109 App: &AppV3{ 110 Metadata: Metadata{ 111 Name: getTestVal(testField == ResourceMetadataName, testVal), 112 Description: getTestVal(testField == ResourceSpecDescription, testVal), 113 }, 114 Spec: AppSpecV3{ 115 URI: "_", 116 PublicAddr: getTestVal(testField == ResourceSpecPublicAddr, testVal), 117 }, 118 }, 119 }) 120 require.NoError(t, err) 121 } 122 return servers 123 } 124 125 cases := []struct { 126 name string 127 fieldName string 128 }{ 129 { 130 name: "by name", 131 fieldName: ResourceMetadataName, 132 }, 133 { 134 name: "by description", 135 fieldName: ResourceSpecDescription, 136 }, 137 { 138 name: "by publicAddr", 139 fieldName: ResourceSpecPublicAddr, 140 }, 141 } 142 143 for _, c := range cases { 144 c := c 145 t.Run(fmt.Sprintf("%s desc", c.name), func(t *testing.T) { 146 sortBy := SortBy{Field: c.fieldName, IsDesc: true} 147 servers := AppServers(makeServers(testValsUnordered, c.fieldName)) 148 require.NoError(t, servers.SortByCustom(sortBy)) 149 targetVals, err := servers.GetFieldVals(c.fieldName) 150 require.NoError(t, err) 151 require.IsDecreasing(t, targetVals) 152 }) 153 154 t.Run(fmt.Sprintf("%s asc", c.name), func(t *testing.T) { 155 sortBy := SortBy{Field: c.fieldName} 156 servers := AppServers(makeServers(testValsUnordered, c.fieldName)) 157 require.NoError(t, servers.SortByCustom(sortBy)) 158 targetVals, err := servers.GetFieldVals(c.fieldName) 159 require.NoError(t, err) 160 require.IsIncreasing(t, targetVals) 161 }) 162 } 163 164 // Test error. 165 sortBy := SortBy{Field: "unsupported"} 166 servers := makeServers(testValsUnordered, "does-not-matter") 167 require.True(t, trace.IsNotImplemented(AppServers(servers).SortByCustom(sortBy))) 168 } 169 170 func TestAppIsAWSConsole(t *testing.T) { 171 tests := []struct { 172 name string 173 uri string 174 cloud string 175 assertIsAWSConsole require.BoolAssertionFunc 176 }{ 177 { 178 name: "AWS Standard", 179 uri: "https://console.aws.amazon.com/ec2/v2/home", 180 assertIsAWSConsole: require.True, 181 }, 182 { 183 name: "AWS China", 184 uri: "https://console.amazonaws.cn/console/home", 185 assertIsAWSConsole: require.True, 186 }, 187 { 188 name: "AWS GovCloud (US)", 189 uri: "https://console.amazonaws-us-gov.com/console/home", 190 assertIsAWSConsole: require.True, 191 }, 192 { 193 name: "Region based not supported yet", 194 uri: "https://us-west-1.console.aws.amazon.com", 195 assertIsAWSConsole: require.False, 196 }, 197 { 198 name: "Not an AWS Console URL", 199 uri: "https://hello.world", 200 assertIsAWSConsole: require.False, 201 }, 202 { 203 name: "CLI-only AWS App", 204 cloud: CloudAWS, 205 assertIsAWSConsole: require.True, 206 }, 207 } 208 209 for _, test := range tests { 210 t.Run(test.name, func(t *testing.T) { 211 app, err := NewAppV3(Metadata{ 212 Name: "aws", 213 }, AppSpecV3{ 214 URI: test.uri, 215 Cloud: test.cloud, 216 }) 217 require.NoError(t, err) 218 219 test.assertIsAWSConsole(t, app.IsAWSConsole()) 220 }) 221 } 222 } 223 224 func TestApplicationGetAWSExternalID(t *testing.T) { 225 t.Parallel() 226 227 tests := []struct { 228 name string 229 appAWS *AppAWS 230 expectedExternalID string 231 }{ 232 { 233 name: "not configured", 234 }, 235 { 236 name: "configured", 237 appAWS: &AppAWS{ 238 ExternalID: "default-external-id", 239 }, 240 expectedExternalID: "default-external-id", 241 }, 242 } 243 244 for _, test := range tests { 245 t.Run(test.name, func(t *testing.T) { 246 app, err := NewAppV3(Metadata{ 247 Name: "aws", 248 }, AppSpecV3{ 249 URI: constants.AWSConsoleURL, 250 AWS: test.appAWS, 251 }) 252 require.NoError(t, err) 253 254 require.Equal(t, test.expectedExternalID, app.GetAWSExternalID()) 255 }) 256 } 257 } 258 259 func TestAppIsAzureCloud(t *testing.T) { 260 tests := []struct { 261 name string 262 cloud string 263 expected bool 264 }{ 265 { 266 name: "Azure Cloud", 267 cloud: CloudAzure, 268 expected: true, 269 }, 270 { 271 name: "not Azure Cloud", 272 cloud: CloudAWS, 273 expected: false, 274 }, 275 } 276 277 for _, test := range tests { 278 t.Run(test.name, func(t *testing.T) { 279 app, err := NewAppV3(Metadata{Name: "myapp"}, AppSpecV3{Cloud: test.cloud}) 280 require.NoError(t, err) 281 require.Equal(t, test.expected, app.IsAzureCloud()) 282 }) 283 } 284 } 285 286 func TestNewAppV3(t *testing.T) { 287 tests := []struct { 288 name string 289 meta Metadata 290 spec AppSpecV3 291 want *AppV3 292 wantErr require.ErrorAssertionFunc 293 }{ 294 { 295 name: "empty app", 296 meta: Metadata{}, 297 spec: AppSpecV3{}, 298 want: nil, 299 wantErr: require.Error, 300 }, 301 { 302 name: "non-cloud app", 303 meta: Metadata{ 304 Name: "myapp", 305 Description: "my fancy app", 306 ID: 123, 307 }, 308 spec: AppSpecV3{URI: "example.com"}, 309 want: &AppV3{ 310 Kind: "app", 311 Version: "v3", 312 Metadata: Metadata{ 313 Name: "myapp", 314 Namespace: "default", 315 Description: "my fancy app", 316 ID: 123, 317 }, Spec: AppSpecV3{URI: "example.com"}, 318 }, 319 wantErr: require.NoError, 320 }, 321 { 322 name: "non-cloud app #2", 323 meta: Metadata{ 324 Name: "myapp", 325 Description: "my fancy app", 326 ID: 123, 327 }, 328 spec: AppSpecV3{URI: "example.com"}, 329 want: &AppV3{ 330 Kind: "app", 331 Version: "v3", 332 Metadata: Metadata{ 333 Name: "myapp", 334 Namespace: "default", 335 Description: "my fancy app", 336 ID: 123, 337 }, 338 Spec: AppSpecV3{URI: "example.com"}, 339 }, 340 wantErr: require.NoError, 341 }, 342 { 343 name: "azure app", 344 meta: Metadata{Name: "myazure"}, 345 spec: AppSpecV3{Cloud: CloudAzure}, 346 want: &AppV3{ 347 Kind: "app", 348 Version: "v3", 349 Metadata: Metadata{Name: "myazure", Namespace: "default"}, 350 Spec: AppSpecV3{URI: "cloud://Azure", Cloud: CloudAzure}, 351 }, 352 wantErr: require.NoError, 353 }, 354 { 355 name: "aws app CLI only", 356 meta: Metadata{Name: "myaws"}, 357 spec: AppSpecV3{Cloud: CloudAWS}, 358 want: &AppV3{ 359 Kind: "app", 360 Version: "v3", 361 Metadata: Metadata{Name: "myaws", Namespace: "default"}, 362 Spec: AppSpecV3{URI: "cloud://AWS", Cloud: CloudAWS}, 363 }, 364 wantErr: require.NoError, 365 }, 366 { 367 name: "aws app console", 368 meta: Metadata{Name: "myaws"}, 369 spec: AppSpecV3{Cloud: CloudAWS, URI: constants.AWSConsoleURL}, 370 want: &AppV3{ 371 Kind: "app", 372 Version: "v3", 373 Metadata: Metadata{Name: "myaws", Namespace: "default"}, 374 Spec: AppSpecV3{URI: constants.AWSConsoleURL, Cloud: CloudAWS}, 375 }, 376 wantErr: require.NoError, 377 }, 378 { 379 name: "aws app using integration", 380 meta: Metadata{Name: "myaws"}, 381 spec: AppSpecV3{Cloud: CloudAWS, URI: constants.AWSConsoleURL, Integration: "my-integration"}, 382 want: &AppV3{ 383 Kind: "app", 384 Version: "v3", 385 Metadata: Metadata{Name: "myaws", Namespace: "default"}, 386 Spec: AppSpecV3{URI: constants.AWSConsoleURL, Cloud: CloudAWS, Integration: "my-integration"}, 387 }, 388 wantErr: require.NoError, 389 }, 390 { 391 name: "invalid cloud identifier", 392 meta: Metadata{Name: "dummy"}, 393 spec: AppSpecV3{Cloud: "dummy"}, 394 want: nil, 395 wantErr: require.Error, 396 }, 397 } 398 for _, tt := range tests { 399 t.Run(tt.name, func(t *testing.T) { 400 actual, err := NewAppV3(tt.meta, tt.spec) 401 tt.wantErr(t, err) 402 require.Equal(t, tt.want, actual) 403 }) 404 } 405 }