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  }