diff --git a/api/types/constants.go b/api/types/constants.go index ebd04952f40f..e67bdd48e4ec 100644 --- a/api/types/constants.go +++ b/api/types/constants.go @@ -474,26 +474,29 @@ const ( ) const ( + // TeleportInternalLabelPrefix is the prefix used by all Teleport internal labels + TeleportInternalLabelPrefix = "teleport.internal/" + // BotLabel is a label used to identify a resource used by a certificate renewal bot. - BotLabel = "teleport.internal/bot" + BotLabel = TeleportInternalLabelPrefix + "bot" // BotGenerationLabel is a label used to record the certificate generation counter. - BotGenerationLabel = "teleport.internal/bot-generation" + BotGenerationLabel = TeleportInternalLabelPrefix + "bot-generation" // InternalResourceIDLabel is a label used to store an ID to correlate between two resources // A pratical example of this is to create a correlation between a Node Provision Token and // the Node that used that token to join the cluster - InternalResourceIDLabel = "teleport.internal/resource-id" + InternalResourceIDLabel = TeleportInternalLabelPrefix + "resource-id" // AlertOnLogin is an internal label that indicates an alert should be displayed to users on login - AlertOnLogin = "teleport.internal/alert-on-login" + AlertOnLogin = TeleportInternalLabelPrefix + "alert-on-login" // AlertPermitAll is an internal label that indicates that an alert is suitable for display // to all users. - AlertPermitAll = "teleport.internal/alert-permit-all" + AlertPermitAll = TeleportInternalLabelPrefix + "alert-permit-all" // AlertLink is an internal label that indicates that an alert is a link. - AlertLink = "teleport.internal/link" + AlertLink = TeleportInternalLabelPrefix + "link" // AlertVerbPermit is an internal label that permits a user to view the alert if they // hold a specific resource permission verb (e.g. 'node:list'). Note that this label is @@ -502,17 +505,17 @@ const ( // label selectors or where clauses, it can't reliably protect information related to a // specific resource. This label should be used only for permitting of alerts that are // of concern to holders of a given : capability in the most general case. - AlertVerbPermit = "teleport.internal/alert-verb-permit" + AlertVerbPermit = TeleportInternalLabelPrefix + "alert-verb-permit" // AlertSupersedes is an internal label used to indicate when one alert supersedes // another. Teleport may choose to hide the superseded alert if the superseding alert // is also visible to the user and of higher or equivalent severity. This intended as // a mechanism for reducing noise/redundancy, and is not a form of access control. Use // one of the "permit" labels if you need to restrict viewership of an alert. - AlertSupersedes = "teleport.internal/alert-supersedes" + AlertSupersedes = TeleportInternalLabelPrefix + "alert-supersedes" // AlertLicenseExpired is an internal label that indicates that the license has expired. - AlertLicenseExpired = "teleport.internal/license-expired-warning" + AlertLicenseExpired = TeleportInternalLabelPrefix + "license-expired-warning" ) // RequestableResourceKinds lists all Teleport resource kinds users can request access to. diff --git a/lib/web/ui/app.go b/lib/web/ui/app.go index 269726ceba12..9e5efa9a37ed 100644 --- a/lib/web/ui/app.go +++ b/lib/web/ui/app.go @@ -18,7 +18,6 @@ package ui import ( "fmt" - "sort" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/tlsca" @@ -66,15 +65,7 @@ func MakeApps(c MakeAppsConfig) []App { result := []App{} for _, teleApp := range c.Apps { fqdn := AssembleAppFQDN(c.LocalClusterName, c.LocalProxyDNSName, c.AppClusterName, teleApp) - labels := []Label{} - for name, value := range teleApp.GetAllLabels() { - labels = append(labels, Label{ - Name: name, - Value: value, - }) - } - - sort.Sort(sortedLabels(labels)) + labels := makeLabels(teleApp.GetAllLabels()) app := App{ Name: teleApp.GetName(), diff --git a/lib/web/ui/app_test.go b/lib/web/ui/app_test.go new file mode 100644 index 000000000000..71f07c1e17df --- /dev/null +++ b/lib/web/ui/app_test.go @@ -0,0 +1,76 @@ +/* +Copyright 2020 Gravitational, Inc. + +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 ui + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport/api/types" +) + +func TestMakeAppsLabelFilter(t *testing.T) { + type testCase struct { + types.Apps + expected []App + name string + } + + testCases := []testCase{ + { + name: "Single App with teleport.internal/ label", + Apps: types.Apps{ + &types.AppV3{ + Metadata: types.Metadata{ + Name: "App1", + Labels: map[string]string{ + "first": "value1", + "teleport.internal/dd": "hidden1", + }, + }, + }, + }, + expected: []App{ + { + Name: "App1", + Labels: []Label{ + { + Name: "first", + Value: "value1", + }, + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + config := MakeAppsConfig{ + Apps: tc.Apps, + } + apps := MakeApps(config) + + for i, app := range apps { + expectedLabels := tc.expected[i].Labels + + require.Equal(t, expectedLabels, app.Labels) + } + }) + } +} diff --git a/lib/web/ui/labels.go b/lib/web/ui/labels.go new file mode 100644 index 000000000000..ec358728ed75 --- /dev/null +++ b/lib/web/ui/labels.go @@ -0,0 +1,59 @@ +/* +Copyright 2020 Gravitational, Inc. + +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 ui + +import ( + "sort" + "strings" + + "github.com/gravitational/teleport/api/types" +) + +// makeLabels is a function that transforms map[string]string arguments passed to it to sorted slice of Labels. +// It also removes all Teleport internal labels from output. +func makeLabels(labelMaps ...map[string]string) []Label { + length := 0 + for _, labelMap := range labelMaps { + length += len(labelMap) + } + + labels := make([]Label, 0, length) + + for _, labelMap := range labelMaps { + for name, value := range labelMap { + if strings.HasPrefix(name, types.TeleportInternalLabelPrefix) { + continue + } + + labels = append(labels, Label{Name: name, Value: value}) + } + } + + sort.Sort(sortedLabels(labels)) + + return labels +} + +func transformCommandLabels(commandLabels map[string]types.CommandLabel) map[string]string { + labels := make(map[string]string, len(commandLabels)) + + for name, cmd := range commandLabels { + labels[name] = cmd.GetResult() + } + + return labels +} diff --git a/lib/web/ui/labels_test.go b/lib/web/ui/labels_test.go new file mode 100644 index 000000000000..3daac9343a0c --- /dev/null +++ b/lib/web/ui/labels_test.go @@ -0,0 +1,135 @@ +/* +Copyright 2020 Gravitational, Inc. + +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 ui + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport/api/types" +) + +func TestMakeLabels(t *testing.T) { + type testCase struct { + name string + labelMaps []map[string]string + expected []Label + } + + testCases := []testCase{ + { + name: "Single map single label case", + labelMaps: []map[string]string{ + { + "label1": "value1", + }, + }, + expected: []Label{ + { + Name: "label1", + Value: "value1", + }, + }, + }, + { + name: "Single map multiple labels case", + labelMaps: []map[string]string{ + { + "label1": "value1", + "label2": "value2", + }, + }, + expected: []Label{ + { + Name: "label1", + Value: "value1", + }, + { + Name: "label2", + Value: "value2", + }, + }, + }, + { + name: "Multiple maps single label case", + labelMaps: []map[string]string{ + { + "label1": "value1", + }, + { + "label2": "value2", + }, + }, + expected: []Label{ + { + Name: "label1", + Value: "value1", + }, + { + Name: "label2", + Value: "value2", + }, + }, + }, + { + name: "Multiple maps with internal labels", + labelMaps: []map[string]string{ + { + "label1": "value1", + "teleport.internal/label3": "value3", + }, + { + "label2": "value2", + "teleport.internal/label4": "value4", + }, + }, + expected: []Label{ + { + Name: "label1", + Value: "value1", + }, + { + Name: "label2", + Value: "value2", + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + labels := makeLabels(tc.labelMaps...) + + require.Equal(t, tc.expected, labels) + }) + } +} + +func TestTransformCommandLabels(t *testing.T) { + commandLabels := map[string]types.CommandLabel{ + "label1": &types.CommandLabelV2{ + Result: "value1", + }, + } + labels := transformCommandLabels(commandLabels) + expected := map[string]string{ + "label1": "value1", + } + + require.Equal(t, expected, labels) +} diff --git a/lib/web/ui/server.go b/lib/web/ui/server.go index e4b6fd7a19b0..a94d2238f1a0 100644 --- a/lib/web/ui/server.go +++ b/lib/web/ui/server.go @@ -17,7 +17,6 @@ limitations under the License. package ui import ( - "sort" "strconv" "strings" @@ -72,24 +71,9 @@ func (s sortedLabels) Swap(i, j int) { func MakeServers(clusterName string, servers []types.Server, userRoles services.RoleSet) []Server { uiServers := []Server{} for _, server := range servers { - uiLabels := []Label{} serverLabels := server.GetStaticLabels() - for name, value := range serverLabels { - uiLabels = append(uiLabels, Label{ - Name: name, - Value: value, - }) - } - serverCmdLabels := server.GetCmdLabels() - for name, cmd := range serverCmdLabels { - uiLabels = append(uiLabels, Label{ - Name: name, - Value: cmd.GetResult(), - }) - } - - sort.Sort(sortedLabels(uiLabels)) + uiLabels := makeLabels(serverLabels, transformCommandLabels(serverCmdLabels)) serverLogins := userRoles.EnumerateServerLogins(server) @@ -125,23 +109,7 @@ func MakeKubeClusters(clusters []types.KubeCluster, userRoles services.RoleSet) for _, cluster := range clusters { staticLabels := cluster.GetStaticLabels() dynamicLabels := cluster.GetDynamicLabels() - uiLabels := make([]Label, 0, len(staticLabels)+len(dynamicLabels)) - - for name, value := range staticLabels { - uiLabels = append(uiLabels, Label{ - Name: name, - Value: value, - }) - } - - for name, cmd := range dynamicLabels { - uiLabels = append(uiLabels, Label{ - Name: name, - Value: cmd.GetResult(), - }) - } - - sort.Sort(sortedLabels(uiLabels)) + uiLabels := makeLabels(staticLabels, transformCommandLabels(dynamicLabels)) kubeUsers, kubeGroups := getAllowedKubeUsersAndGroupsForCluster(userRoles, cluster) @@ -236,17 +204,7 @@ type Database struct { // MakeDatabase creates database objects. func MakeDatabase(database types.Database, dbUsers, dbNames []string) Database { - - uiLabels := []Label{} - - for name, value := range database.GetAllLabels() { - uiLabels = append(uiLabels, Label{ - Name: name, - Value: value, - }) - } - - sort.Sort(sortedLabels(uiLabels)) + uiLabels := makeLabels(database.GetAllLabels()) return Database{ Name: database.GetName(), @@ -294,16 +252,7 @@ func MakeDesktop(windowsDesktop types.WindowsDesktop) Desktop { } return addr } - uiLabels := []Label{} - - for name, value := range windowsDesktop.GetAllLabels() { - uiLabels = append(uiLabels, Label{ - Name: name, - Value: value, - }) - } - - sort.Sort(sortedLabels(uiLabels)) + uiLabels := makeLabels(windowsDesktop.GetAllLabels()) return Desktop{ OS: constants.WindowsOS, @@ -339,16 +288,7 @@ type DesktopService struct { // MakeDesktop converts a desktop from its API form to a type the UI can display. func MakeDesktopService(desktopService types.WindowsDesktopService) DesktopService { - uiLabels := []Label{} - - for name, value := range desktopService.GetAllLabels() { - uiLabels = append(uiLabels, Label{ - Name: name, - Value: value, - }) - } - - sort.Sort(sortedLabels(uiLabels)) + uiLabels := makeLabels(desktopService.GetAllLabels()) return DesktopService{ Name: desktopService.GetName(), diff --git a/lib/web/ui/server_test.go b/lib/web/ui/server_test.go index 27c8ce2227ce..20521c1aa993 100644 --- a/lib/web/ui/server_test.go +++ b/lib/web/ui/server_test.go @@ -307,3 +307,154 @@ func makeTestKubeCluster(t *testing.T, labels map[string]string) types.KubeClust require.NoError(t, err) return s } + +func TestMakeClusterHiddenLabels(t *testing.T) { + type testCase struct { + name string + clusters []types.KubeCluster + expectedLabels [][]Label + roleSet services.RoleSet + } + + testCases := []testCase{ + { + name: "Single server with internal label", + clusters: []types.KubeCluster{ + makeTestKubeCluster(t, map[string]string{ + "teleport.internal/test": "value1", + "label2": "value2", + }), + }, + expectedLabels: [][]Label{ + { + { + Name: "label2", + Value: "value2", + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + clusters := MakeKubeClusters(tc.clusters, tc.roleSet) + for i, cluster := range clusters { + require.Equal(t, tc.expectedLabels[i], cluster.Labels) + } + }) + } +} + +func TestMakeServersHiddenLabels(t *testing.T) { + type testCase struct { + name string + clusterName string + servers []types.Server + expectedLabels [][]Label + roleSet services.RoleSet + } + + testCases := []testCase{ + { + name: "Single server with internal label", + clusterName: "cluster1", + servers: []types.Server{ + makeTestServer(t, "server1", map[string]string{ + "simple": "value1", + "teleport.internal/app": "app1", + }), + }, + expectedLabels: [][]Label{ + { + { + Name: "simple", + Value: "value1", + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + servers := MakeServers(tc.clusterName, tc.servers, tc.roleSet) + for i, server := range servers { + require.Equal(t, tc.expectedLabels[i], server.Labels) + } + }) + } +} + +func makeTestServer(t *testing.T, name string, labels map[string]string) types.Server { + server, err := types.NewServerWithLabels(name, types.KindNode, types.ServerSpecV2{}, labels) + require.NoError(t, err) + return server +} + +func TestMakeDatabaseHiddenLabels(t *testing.T) { + inputDb := &types.DatabaseV3{ + Metadata: types.Metadata{ + Name: "db name", + Labels: map[string]string{ + "label": "value1", + "teleport.internal/label2": "value2", + }, + }, + } + + outputDb := MakeDatabase(inputDb, nil, nil) + + require.Equal(t, []Label{ + { + Name: "label", + Value: "value1", + }, + }, outputDb.Labels) +} + +func TestMakeDesktopHiddenLabel(t *testing.T) { + windowsDesktop := &types.WindowsDesktopV3{ + ResourceHeader: types.ResourceHeader{ + Metadata: types.Metadata{ + Labels: map[string]string{ + "teleport.internal/t2": "tt", + "label3": "value2", + }, + }, + }, + } + + desktop := MakeDesktop(windowsDesktop) + labels := []Label{ + { + Name: "label3", + Value: "value2", + }, + } + + require.Equal(t, labels, desktop.Labels) +} + +func TestMakeDesktopServiceHiddenLabel(t *testing.T) { + windowsDesktopService := &types.WindowsDesktopServiceV3{ + ResourceHeader: types.ResourceHeader{ + Metadata: types.Metadata{ + Labels: map[string]string{ + "teleport.internal/t2": "tt", + "label3": "value2", + }, + }, + }, + } + + desktopService := MakeDesktopService(windowsDesktopService) + labels := []Label{ + { + Name: "label3", + Value: "value2", + }, + } + + require.Equal(t, labels, desktopService.Labels) +}