diff --git a/lib/services/useracl.go b/lib/services/useracl.go
index 4a6c39b554ee5..05ff20400c2aa 100644
--- a/lib/services/useracl.go
+++ b/lib/services/useracl.go
@@ -120,6 +120,8 @@ type UserACL struct {
Contact ResourceAccess `json:"contact"`
// FileTransferAccess defines the ability to perform remote file operations via SCP or SFTP
FileTransferAccess bool `json:"fileTransferAccess"`
+ // GitServers defines access to Git servers.
+ GitServers ResourceAccess `json:"gitServers"`
}
func hasAccess(roleSet RoleSet, ctx *Context, kind string, verbs ...string) bool {
@@ -164,6 +166,7 @@ func NewUserACL(user types.User, userRoles RoleSet, features proto.Features, des
desktopAccess := newAccess(userRoles, ctx, types.KindWindowsDesktop)
cnDiagnosticAccess := newAccess(userRoles, ctx, types.KindConnectionDiagnostic)
samlIdpServiceProviderAccess := newAccess(userRoles, ctx, types.KindSAMLIdPServiceProvider)
+ gitServersAccess := newAccess(userRoles, ctx, types.KindGitServer)
// active sessions are a special case - if a user's role set has any join_sessions
// policies then the ACL must permit showing active sessions
@@ -266,5 +269,6 @@ func NewUserACL(user types.User, userRoles RoleSet, features proto.Features, des
AccessGraphSettings: accessGraphSettings,
Contact: contact,
FileTransferAccess: fileTransferAccess,
+ GitServers: gitServersAccess,
}
}
diff --git a/lib/services/useracl_test.go b/lib/services/useracl_test.go
index 9eb19e199007e..a9e7a65c147e3 100644
--- a/lib/services/useracl_test.go
+++ b/lib/services/useracl_test.go
@@ -109,6 +109,7 @@ func TestNewUserACL(t *testing.T) {
require.Empty(t, cmp.Diff(userContext.License, denied))
require.Empty(t, cmp.Diff(userContext.Download, denied))
require.Empty(t, cmp.Diff(userContext.Contact, allowedRW))
+ require.Empty(t, cmp.Diff(userContext.GitServers, denied))
// test enabling of the 'Use' verb
require.Empty(t, cmp.Diff(userContext.Integrations, ResourceAccess{true, true, true, true, true, true}))
diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go
index ca3d5a727ff01..3e82ccdf502e0 100644
--- a/lib/web/apiserver.go
+++ b/lib/web/apiserver.go
@@ -3014,6 +3014,7 @@ func makeUnifiedResourceRequest(r *http.Request) (*proto.ListUnifiedResourcesReq
types.KindWindowsDesktop,
types.KindKubernetesCluster,
types.KindSAMLIdPServiceProvider,
+ types.KindGitServer,
}
}
@@ -3140,12 +3141,16 @@ func (h *Handler) clusterUnifiedResourcesGet(w http.ResponseWriter, request *htt
for _, enriched := range page {
switch r := enriched.ResourceWithLabels.(type) {
case types.Server:
- logins, err := calculateSSHLogins(identity, enriched.Logins)
- if err != nil {
- return nil, trace.Wrap(err)
+ switch enriched.GetKind() {
+ case types.KindNode:
+ logins, err := calculateSSHLogins(identity, enriched.Logins)
+ if err != nil {
+ return nil, trace.Wrap(err)
+ }
+ unifiedResources = append(unifiedResources, ui.MakeServer(site.GetName(), r, logins, enriched.RequiresRequest))
+ case types.KindGitServer:
+ unifiedResources = append(unifiedResources, ui.MakeGitServer(site.GetName(), r, enriched.RequiresRequest))
}
-
- unifiedResources = append(unifiedResources, ui.MakeServer(site.GetName(), r, logins, enriched.RequiresRequest))
case types.DatabaseServer:
db := ui.MakeDatabase(r.GetDatabase(), accessChecker, h.cfg.DatabaseREPLRegistry, enriched.RequiresRequest)
unifiedResources = append(unifiedResources, db)
diff --git a/lib/web/apiserver_test.go b/lib/web/apiserver_test.go
index 85e55ebbeea8c..752240dd36575 100644
--- a/lib/web/apiserver_test.go
+++ b/lib/web/apiserver_test.go
@@ -1299,6 +1299,7 @@ func TestUnifiedResourcesGet(t *testing.T) {
role := defaultRoleForNewUser(&types.UserV2{Metadata: types.Metadata{Name: username}}, loginUser)
role.SetAWSRoleARNs(types.Allow, []string{"arn:aws:iam::999999999999:role/ProdInstance"})
role.SetAppLabels(types.Allow, types.Labels{"env": []string{"prod"}})
+ role.SetGitHubPermissions(types.Allow, []types.GitHubPermission{{Organizations: []string{types.Wildcard}}})
// This role is used to test that DevInstance AWS Role is only available to AppServices that have env:dev label.
roleForDev, err := types.NewRole("dev-access", types.RoleSpecV6{
@@ -1395,6 +1396,15 @@ func TestUnifiedResourcesGet(t *testing.T) {
err = env.server.Auth().UpsertWindowsDesktop(context.Background(), win)
require.NoError(t, err)
+ // add git server
+ gitServer, err := types.NewGitHubServer(types.GitHubServerMetadata{
+ Organization: "org1",
+ Integration: "org1",
+ })
+ require.NoError(t, err)
+ _, err = env.server.Auth().GitServers.UpsertGitServer(context.Background(), gitServer)
+ require.NoError(t, err)
+
clusterName := env.server.ClusterName()
endpoint := pack.clt.Endpoint("webapi", "sites", clusterName, "resources")
@@ -1445,7 +1455,7 @@ func TestUnifiedResourcesGet(t *testing.T) {
require.NoError(t, err)
res = clusterNodesGetResponse{}
require.NoError(t, json.Unmarshal(re.Bytes(), &res))
- require.Len(t, res.Items, 10)
+ require.Len(t, res.Items, 11)
require.Equal(t, "", res.StartKey)
// Only list valid AWS Roles for AWS Apps
diff --git a/lib/web/ui/git_server.go b/lib/web/ui/git_server.go
new file mode 100644
index 0000000000000..b1a89c559a854
--- /dev/null
+++ b/lib/web/ui/git_server.go
@@ -0,0 +1,80 @@
+/*
+ * Teleport
+ * Copyright (C) 2025 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package ui
+
+import (
+ "github.com/gravitational/teleport/api/types"
+ "github.com/gravitational/teleport/lib/ui"
+)
+
+// GitServer describes a GitServer for webapp
+type GitServer struct {
+ // Kind is the kind of resource.
+ Kind string `json:"kind"`
+ // SubKind is a git server subkind such as GitHub
+ SubKind string `json:"subKind"`
+ // Name is this server name
+ Name string `json:"id"`
+ // ClusterName is this server cluster name
+ ClusterName string `json:"siteId"`
+ // Hostname is this server hostname
+ Hostname string `json:"hostname"`
+ // Addr is this server ip address
+ Addr string `json:"addr"`
+ // Labels is this server list of labels
+ Labels []ui.Label `json:"tags"`
+ // RequireRequest indicates if a returned resource is only accessible after an access request
+ RequiresRequest bool `json:"requiresRequest,omitempty"`
+ // GitHub contains metadata for GitHub proxy severs.
+ GitHub *GitHubServerMetadata `json:"github,omitempty"`
+}
+
+// GitHubServerMetadata contains metadata for GitHub proxy severs.
+type GitHubServerMetadata struct {
+ Integration string `json:"integration"`
+ Organization string `json:"organization"`
+}
+
+// MakeGitServer creates a git server object for the web ui
+func MakeGitServer(clusterName string, server types.Server, requiresRequest bool) GitServer {
+ serverLabels := server.GetStaticLabels()
+ serverCmdLabels := server.GetCmdLabels()
+ uiLabels := ui.MakeLabelsWithoutInternalPrefixes(serverLabels, ui.TransformCommandLabels(serverCmdLabels))
+
+ uiServer := GitServer{
+ Kind: server.GetKind(),
+ ClusterName: clusterName,
+ Labels: uiLabels,
+ Name: server.GetName(),
+ Hostname: server.GetHostname(),
+ Addr: server.GetAddr(),
+ SubKind: server.GetSubKind(),
+ RequiresRequest: requiresRequest,
+ }
+
+ if server.GetSubKind() == types.SubKindGitHub {
+ if github := server.GetGitHub(); github != nil {
+ uiServer.GitHub = &GitHubServerMetadata{
+ Integration: github.Integration,
+ Organization: github.Organization,
+ }
+ }
+ }
+ return uiServer
+}
diff --git a/lib/web/ui/git_server_test.go b/lib/web/ui/git_server_test.go
new file mode 100644
index 0000000000000..41b01ea5ba4d8
--- /dev/null
+++ b/lib/web/ui/git_server_test.go
@@ -0,0 +1,51 @@
+/*
+ * Teleport
+ * Copyright (C) 2025 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package ui
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "github.com/gravitational/teleport/api/types"
+ "github.com/gravitational/teleport/lib/ui"
+)
+
+func TestMakeGitServer(t *testing.T) {
+ server, err := types.NewGitHubServer(types.GitHubServerMetadata{
+ Integration: "my-integration",
+ Organization: "my-org",
+ })
+ require.NoError(t, err)
+
+ expect := GitServer{
+ ClusterName: "cluster",
+ Kind: "git_server",
+ SubKind: "github",
+ Addr: "github.com:22",
+ Name: server.GetName(),
+ Hostname: "my-org.teleport-github-org",
+ GitHub: &GitHubServerMetadata{
+ Integration: "my-integration",
+ Organization: "my-org",
+ },
+ // Internal labels get filtered.
+ Labels: []ui.Label{},
+ }
+ require.Equal(t, expect, MakeGitServer("cluster", server, false))
+}
diff --git a/web/packages/design/src/ResourceIcon/assets/git-dark.svg b/web/packages/design/src/ResourceIcon/assets/git-dark.svg
new file mode 100644
index 0000000000000..cb1a374e8badc
--- /dev/null
+++ b/web/packages/design/src/ResourceIcon/assets/git-dark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web/packages/design/src/ResourceIcon/assets/git-light.svg b/web/packages/design/src/ResourceIcon/assets/git-light.svg
new file mode 100644
index 0000000000000..5bf444b9be0ca
--- /dev/null
+++ b/web/packages/design/src/ResourceIcon/assets/git-light.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web/packages/design/src/ResourceIcon/icons.ts b/web/packages/design/src/ResourceIcon/icons.ts
index c214fa383b577..0a0fe6c3f8042 100644
--- a/web/packages/design/src/ResourceIcon/icons.ts
+++ b/web/packages/design/src/ResourceIcon/icons.ts
@@ -126,6 +126,8 @@ import g2 from './assets/g2.svg';
import gable from './assets/gable.svg';
import gemDark from './assets/gem-dark.svg';
import gemLight from './assets/gem-light.svg';
+import gitDark from './assets/git-dark.svg';
+import gitLight from './assets/git-light.svg';
import githubDark from './assets/github-dark.svg';
import githubLight from './assets/github-light.svg';
import gitlab from './assets/gitlab.svg';
@@ -413,6 +415,8 @@ export {
gable,
gemDark,
gemLight,
+ gitDark,
+ gitLight,
githubDark,
githubLight,
gitlab,
diff --git a/web/packages/design/src/ResourceIcon/resourceIconSpecs.ts b/web/packages/design/src/ResourceIcon/resourceIconSpecs.ts
index ab45fd7d8d4b9..1669117c1339e 100644
--- a/web/packages/design/src/ResourceIcon/resourceIconSpecs.ts
+++ b/web/packages/design/src/ResourceIcon/resourceIconSpecs.ts
@@ -123,6 +123,7 @@ export const resourceIconSpecs = {
g2: forAllThemes(i.g2),
gable: forAllThemes(i.gable),
gem: { dark: i.gemDark, light: i.gemLight },
+ git: { dark: i.gitDark, light: i.gitLight },
github: { dark: i.githubDark, light: i.githubLight },
gitlab: forAllThemes(i.gitlab),
gmail: forAllThemes(i.gmail),
diff --git a/web/packages/shared/components/AccessRequests/NewRequest/RequestCheckout/RequestCheckout.tsx b/web/packages/shared/components/AccessRequests/NewRequest/RequestCheckout/RequestCheckout.tsx
index 3fc6a366e1f09..237755e753fe0 100644
--- a/web/packages/shared/components/AccessRequests/NewRequest/RequestCheckout/RequestCheckout.tsx
+++ b/web/packages/shared/components/AccessRequests/NewRequest/RequestCheckout/RequestCheckout.tsx
@@ -790,6 +790,8 @@ function getPrettyResourceKind(kind: RequestableResourceKind): string {
return 'Namespace';
case 'aws_ic_account_assignment':
return 'AWS IAM Identity Center Account Assignment';
+ case 'git_server':
+ return 'Git';
default:
kind satisfies never;
return kind;
diff --git a/web/packages/shared/components/AccessRequests/NewRequest/resource.ts b/web/packages/shared/components/AccessRequests/NewRequest/resource.ts
index 7d4b274dd880c..ca5a031f5db89 100644
--- a/web/packages/shared/components/AccessRequests/NewRequest/resource.ts
+++ b/web/packages/shared/components/AccessRequests/NewRequest/resource.ts
@@ -46,5 +46,6 @@ export function getEmptyResourceState(): ResourceMap {
saml_idp_service_provider: {},
namespace: {},
aws_ic_account_assignment: {},
+ git_server: {},
};
}
diff --git a/web/packages/shared/components/UnifiedResources/FilterPanel.tsx b/web/packages/shared/components/UnifiedResources/FilterPanel.tsx
index 405f2af350e02..5c18e12d346ec 100644
--- a/web/packages/shared/components/UnifiedResources/FilterPanel.tsx
+++ b/web/packages/shared/components/UnifiedResources/FilterPanel.tsx
@@ -44,6 +44,7 @@ const kindToLabel: Record = {
kube_cluster: 'Kubernetes',
node: 'Server',
user_group: 'User group',
+ git_server: 'Git',
};
const sortFieldOptions = [
diff --git a/web/packages/shared/components/UnifiedResources/UnifiedResources.story.tsx b/web/packages/shared/components/UnifiedResources/UnifiedResources.story.tsx
index a475378681988..4c2c0339cf4cc 100644
--- a/web/packages/shared/components/UnifiedResources/UnifiedResources.story.tsx
+++ b/web/packages/shared/components/UnifiedResources/UnifiedResources.story.tsx
@@ -32,6 +32,7 @@ import { apps, moreApps } from 'teleport/Apps/fixtures';
import { UrlResourcesParams } from 'teleport/config';
import { databases, moreDatabases } from 'teleport/Databases/fixtures';
import { desktops, moreDesktops } from 'teleport/Desktops/fixtures';
+import { gitServers } from 'teleport/GitServers/fixtures';
import { kubes, moreKubes } from 'teleport/Kubes/fixtures';
import { moreNodes, nodes } from 'teleport/Nodes/fixtures';
import { ResourcesResponse } from 'teleport/services/agents';
@@ -67,6 +68,7 @@ const allResources = [
...moreKubes,
...moreDesktops,
...moreNodes,
+ ...gitServers,
];
const story = ({
diff --git a/web/packages/shared/components/UnifiedResources/UnifiedResources.tsx b/web/packages/shared/components/UnifiedResources/UnifiedResources.tsx
index ec4b705a72f93..01ffdc258fcb2 100644
--- a/web/packages/shared/components/UnifiedResources/UnifiedResources.tsx
+++ b/web/packages/shared/components/UnifiedResources/UnifiedResources.tsx
@@ -659,8 +659,8 @@ function getResourcePinningSupport(
function generateUnifiedResourceKey(
resource: SharedUnifiedResource['resource']
): string {
- if (resource.kind === 'node') {
- return `${resource.hostname}/${resource.id}/node`.toLowerCase();
+ if (resource.kind === 'node' || resource.kind == 'git_server') {
+ return `${resource.hostname}/${resource.id}/${resource.kind}`.toLowerCase();
}
return `${resource.name}/${resource.kind}`.toLowerCase();
}
diff --git a/web/packages/shared/components/UnifiedResources/shared/viewItemsFactory.ts b/web/packages/shared/components/UnifiedResources/shared/viewItemsFactory.ts
index 3eaf6082b4de8..76cdb1baa1ae5 100644
--- a/web/packages/shared/components/UnifiedResources/shared/viewItemsFactory.ts
+++ b/web/packages/shared/components/UnifiedResources/shared/viewItemsFactory.ts
@@ -20,6 +20,7 @@ import {
Application as ApplicationIcon,
Database as DatabaseIcon,
Desktop as DesktopIcon,
+ GitHub as GitHubIcon,
Kubernetes as KubernetesIcon,
Server as ServerIcon,
} from 'design/Icon';
@@ -32,6 +33,7 @@ import {
UnifiedResourceApp,
UnifiedResourceDatabase,
UnifiedResourceDesktop,
+ UnifiedResourceGitServer,
UnifiedResourceKube,
UnifiedResourceNode,
UnifiedResourceUi,
@@ -170,6 +172,26 @@ export function makeUnifiedResourceViewItemUserGroup(
};
}
+export function makeUnifiedResourceViewItemGitServer(
+ resource: UnifiedResourceGitServer,
+ ui: UnifiedResourceUi
+): UnifiedResourceViewItem {
+ return {
+ name: resource.github ? resource.github.organization : resource.hostname,
+ SecondaryIcon: GitHubIcon,
+ primaryIconName: 'git',
+ ActionButton: ui.ActionButton,
+ labels: resource.labels,
+ cardViewProps: {
+ primaryDesc: 'GitHub Organization',
+ },
+ listViewProps: {
+ resourceType: 'GitHub Organization',
+ },
+ requiresRequest: resource.requiresRequest,
+ };
+}
+
function formatNodeSubKind(subKind: NodeSubKind): string {
switch (subKind) {
case 'openssh-ec2-ice':
@@ -214,5 +236,7 @@ export function mapResourceToViewItem({ resource, ui }: SharedUnifiedResource) {
return makeUnifiedResourceViewItemDesktop(resource, ui);
case 'user_group':
return makeUnifiedResourceViewItemUserGroup(resource, ui);
+ case 'git_server':
+ return makeUnifiedResourceViewItemGitServer(resource, ui);
}
}
diff --git a/web/packages/shared/components/UnifiedResources/types.ts b/web/packages/shared/components/UnifiedResources/types.ts
index 9c47a0dc64bfd..3d57c312b7637 100644
--- a/web/packages/shared/components/UnifiedResources/types.ts
+++ b/web/packages/shared/components/UnifiedResources/types.ts
@@ -87,6 +87,19 @@ export type UnifiedResourceUserGroup = {
requiresRequest?: boolean;
};
+export interface UnifiedResourceGitServer {
+ kind: 'git_server';
+ id: string;
+ hostname: string;
+ labels: ResourceLabel[];
+ subKind: 'github';
+ github: {
+ organization: string;
+ integration: string;
+ };
+ requiresRequest?: boolean;
+}
+
export type UnifiedResourceUi = {
ActionButton: React.ReactElement;
};
@@ -98,7 +111,8 @@ export type SharedUnifiedResource = {
| UnifiedResourceNode
| UnifiedResourceKube
| UnifiedResourceDesktop
- | UnifiedResourceUserGroup;
+ | UnifiedResourceUserGroup
+ | UnifiedResourceGitServer;
ui: UnifiedResourceUi;
};
diff --git a/web/packages/teleport/src/GitServers/ConnectDialog.story.tsx b/web/packages/teleport/src/GitServers/ConnectDialog.story.tsx
new file mode 100644
index 0000000000000..6a870b2ced050
--- /dev/null
+++ b/web/packages/teleport/src/GitServers/ConnectDialog.story.tsx
@@ -0,0 +1,43 @@
+/**
+ * Teleport
+ * Copyright (C) 2023 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { ConnectDialog } from './ConnectDialog';
+
+export default {
+ title: 'Teleport/GitServers/Connect',
+};
+
+export const ConnectGitHub = () => (
+ null}
+ authType="local"
+ />
+);
+
+export const ConnectGitHubSSO = () => (
+ null}
+ authType="sso"
+ />
+);
diff --git a/web/packages/teleport/src/GitServers/ConnectDialog.tsx b/web/packages/teleport/src/GitServers/ConnectDialog.tsx
new file mode 100644
index 0000000000000..71a12f6ce41ac
--- /dev/null
+++ b/web/packages/teleport/src/GitServers/ConnectDialog.tsx
@@ -0,0 +1,106 @@
+/**
+ * Teleport
+ * Copyright (C) 2023 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { Box, ButtonSecondary, Link, Text } from 'design';
+import Dialog, {
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from 'design/Dialog';
+import { TextSelectCopy } from 'shared/components/TextSelectCopy';
+
+import { generateTshLoginCommand } from 'teleport/lib/util';
+import { AuthType } from 'teleport/services/user';
+
+export function ConnectDialog({
+ username,
+ clusterId,
+ organization,
+ onClose,
+ authType,
+ accessRequestId,
+}: {
+ organization: string;
+ onClose: () => void;
+ username: string;
+ clusterId: string;
+ authType: AuthType;
+ accessRequestId?: string;
+}) {
+ const repoURL = `https://github.com/orgs/${organization}/repositories`;
+ const title = `Use 'git' for GitHub Organization '${organization}'`;
+ return (
+
+ );
+}
diff --git a/web/packages/teleport/src/GitServers/fixtures.ts b/web/packages/teleport/src/GitServers/fixtures.ts
new file mode 100644
index 0000000000000..2c20c35341aeb
--- /dev/null
+++ b/web/packages/teleport/src/GitServers/fixtures.ts
@@ -0,0 +1,34 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { GitServer } from 'web/packages/teleport/src/services/gitServers';
+
+export const gitServers: GitServer[] = [
+ {
+ kind: 'git_server',
+ id: '00000000-0000-0000-0000-000000000000',
+ clusterId: 'im-a-cluster',
+ hostname: 'my-org.github-org',
+ subKind: 'github',
+ labels: [],
+ github: {
+ organization: 'my-org',
+ integration: 'my-org',
+ },
+ },
+];
diff --git a/web/packages/teleport/src/GitServers/index.ts b/web/packages/teleport/src/GitServers/index.ts
new file mode 100644
index 0000000000000..e2c8486f53824
--- /dev/null
+++ b/web/packages/teleport/src/GitServers/index.ts
@@ -0,0 +1,19 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+export { ConnectDialog } from './ConnectDialog';
diff --git a/web/packages/teleport/src/UnifiedResources/ResourceActionButton.tsx b/web/packages/teleport/src/UnifiedResources/ResourceActionButton.tsx
index 01a880c3b82ea..90c43bde0d99b 100644
--- a/web/packages/teleport/src/UnifiedResources/ResourceActionButton.tsx
+++ b/web/packages/teleport/src/UnifiedResources/ResourceActionButton.tsx
@@ -17,6 +17,7 @@
*/
import React, { useState } from 'react';
+import { GitServer } from 'web/packages/teleport/src/services/gitServers';
import { ButtonBorder, ButtonWithMenu, MenuItem } from 'design';
import { AwsLaunchButton } from 'shared/components/AwsLaunchButton';
@@ -31,6 +32,7 @@ import cfg from 'teleport/config';
import DbConnectDialog from 'teleport/Databases/ConnectDialog';
import type { ResourceSpec } from 'teleport/Discover/SelectResource/types';
import { ResourceKind } from 'teleport/Discover/Shared';
+import { ConnectDialog as GitServerConnectDialog } from 'teleport/GitServers';
import KubeConnectDialog from 'teleport/Kubes/ConnectDialog';
import { openNewTab } from 'teleport/lib/util';
import { useSamlAppAction } from 'teleport/SamlApplications/useSamlAppActions';
@@ -60,6 +62,8 @@ export const ResourceActionButton = ({ resource }: Props) => {
return ;
case 'windows_desktop':
return ;
+ case 'git_server':
+ return ;
default:
return null;
}
@@ -351,6 +355,40 @@ const KubeConnect = ({ kube }: { kube: Kube }) => {
);
};
+function GitServerConnect({ gitServer }: { gitServer: GitServer }) {
+ const ctx = useTeleport();
+ const { clusterId } = useStickyClusterId();
+ const [open, setOpen] = useState(false);
+ const organization = gitServer.github.organization;
+ const username = ctx.storeUser.state.username;
+ const authType = ctx.storeUser.state.authType;
+ const accessRequestId = ctx.storeUser.getAccessRequestId();
+ return (
+ <>
+ {
+ setOpen(true);
+ }}
+ >
+ Connect
+
+ {open && (
+ setOpen(false)}
+ authType={authType}
+ accessRequestId={accessRequestId}
+ />
+ )}
+ >
+ );
+}
+
const makeNodeOptions = (clusterId: string, node: Node | undefined) => {
const nodeLogins = node?.sshLogins || [];
const logins = sortNodeLogins(nodeLogins);
diff --git a/web/packages/teleport/src/UnifiedResources/UnifiedResources.tsx b/web/packages/teleport/src/UnifiedResources/UnifiedResources.tsx
index c10fd8bdab04c..9626a61e14d1a 100644
--- a/web/packages/teleport/src/UnifiedResources/UnifiedResources.tsx
+++ b/web/packages/teleport/src/UnifiedResources/UnifiedResources.tsx
@@ -93,6 +93,10 @@ const getAvailableKindsWithAccess = (flags: FeatureFlags): FilterKind[] => {
kind: 'windows_desktop',
disabled: !flags.desktops,
},
+ {
+ kind: 'git_server',
+ disabled: !flags.gitServers,
+ },
];
};
diff --git a/web/packages/teleport/src/mocks/contexts.ts b/web/packages/teleport/src/mocks/contexts.ts
index 48445672325d2..f23719526b9bc 100644
--- a/web/packages/teleport/src/mocks/contexts.ts
+++ b/web/packages/teleport/src/mocks/contexts.ts
@@ -76,6 +76,7 @@ export const allAccessAcl: Acl = {
accessMonitoringRule: fullAccess,
discoverConfigs: fullAccess,
contacts: fullAccess,
+ gitServers: fullAccess,
};
export function getAcl(cfg?: { noAccess: boolean }) {
diff --git a/web/packages/teleport/src/services/agents/types.ts b/web/packages/teleport/src/services/agents/types.ts
index c15b72a73529b..4fcfcac74b168 100644
--- a/web/packages/teleport/src/services/agents/types.ts
+++ b/web/packages/teleport/src/services/agents/types.ts
@@ -16,6 +16,8 @@
* along with this program. If not, see .
*/
+import { GitServer } from 'web/packages/teleport/src/services/gitServers';
+
import type { Platform } from 'design/platform';
import { IncludedResourceMode } from 'shared/components/UnifiedResources';
@@ -24,9 +26,9 @@ import { Database } from 'teleport/services/databases';
import { Desktop } from 'teleport/services/desktops';
import { Kube } from 'teleport/services/kube';
import { Node } from 'teleport/services/nodes';
+import { UserGroup } from 'teleport/services/userGroups';
import type { MfaChallengeResponse } from '../mfa';
-import { UserGroup } from '../userGroups';
export type UnifiedResource =
| App
@@ -34,7 +36,8 @@ export type UnifiedResource =
| Node
| Kube
| Desktop
- | UserGroup;
+ | UserGroup
+ | GitServer;
export type UnifiedResourceKind = UnifiedResource['kind'];
@@ -88,7 +91,8 @@ export type ResourceIdKind =
| 'user_group'
| 'windows_desktop'
| 'saml_idp_service_provider'
- | 'aws_ic_account_assignment';
+ | 'aws_ic_account_assignment'
+ | 'git_server';
export type AccessRequestScope =
| 'my_requests'
diff --git a/web/packages/teleport/src/services/gitServers/index.ts b/web/packages/teleport/src/services/gitServers/index.ts
new file mode 100644
index 0000000000000..d69acdb0a561f
--- /dev/null
+++ b/web/packages/teleport/src/services/gitServers/index.ts
@@ -0,0 +1,19 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+export * from './types';
diff --git a/web/packages/teleport/src/services/gitServers/makeGitServer.ts b/web/packages/teleport/src/services/gitServers/makeGitServer.ts
new file mode 100644
index 0000000000000..7b92b6fa470d7
--- /dev/null
+++ b/web/packages/teleport/src/services/gitServers/makeGitServer.ts
@@ -0,0 +1,45 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { GitHubMetadata, GitServer } from './types';
+
+export default function makeGitServer(json: any): GitServer {
+ json = json ?? {};
+ const { id, siteId, subKind, hostname, tags, github, requiresRequest } = json;
+
+ return {
+ kind: 'git_server',
+ id,
+ subKind,
+ clusterId: siteId,
+ hostname,
+ labels: tags ?? [],
+ requiresRequest,
+ github: github ? makeGitHubMetadata(github) : undefined,
+ };
+}
+
+function makeGitHubMetadata(json: any): GitHubMetadata {
+ json = json ?? {};
+ const { integration, organization } = json;
+
+ return {
+ integration,
+ organization,
+ };
+}
diff --git a/web/packages/teleport/src/services/gitServers/types.ts b/web/packages/teleport/src/services/gitServers/types.ts
new file mode 100644
index 0000000000000..264ae717000de
--- /dev/null
+++ b/web/packages/teleport/src/services/gitServers/types.ts
@@ -0,0 +1,35 @@
+/**
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+import { ResourceLabel } from 'teleport/services/agents';
+
+export interface GitServer {
+ kind: 'git_server';
+ id: string;
+ clusterId: string;
+ hostname: string;
+ labels: ResourceLabel[];
+ subKind: 'github';
+ github: GitHubMetadata;
+ requiresRequest?: boolean;
+}
+
+export type GitHubMetadata = {
+ integration: string;
+ organization: string;
+};
diff --git a/web/packages/teleport/src/services/resources/makeUnifiedResource.ts b/web/packages/teleport/src/services/resources/makeUnifiedResource.ts
index 0dc4072c92719..608c0c0b1a5dd 100644
--- a/web/packages/teleport/src/services/resources/makeUnifiedResource.ts
+++ b/web/packages/teleport/src/services/resources/makeUnifiedResource.ts
@@ -16,6 +16,8 @@
* along with this program. If not, see .
*/
+import makeGitServer from 'teleport/services/gitServers/makeGitServer';
+
import { UnifiedResource, UnifiedResourceKind } from '../agents';
import makeApp from '../apps/makeApps';
import { makeDatabase } from '../databases/makeDatabase';
@@ -37,6 +39,8 @@ export function makeUnifiedResource(json: any): UnifiedResource {
return makeNode(json);
case 'windows_desktop':
return makeDesktop(json);
+ case 'git_server':
+ return makeGitServer(json);
default:
throw new Error(`Unknown unified resource kind: "${json.kind}"`);
}
diff --git a/web/packages/teleport/src/services/user/makeAcl.ts b/web/packages/teleport/src/services/user/makeAcl.ts
index 560add4279e31..18265162d3c9c 100644
--- a/web/packages/teleport/src/services/user/makeAcl.ts
+++ b/web/packages/teleport/src/services/user/makeAcl.ts
@@ -81,6 +81,7 @@ export function makeAcl(json): Acl {
const discoverConfigs = json.discoverConfigs || defaultAccess;
const contacts = json.contact || defaultAccess;
+ const gitServers = json.gitServers || defaultAccess;
return {
accessList,
@@ -121,6 +122,7 @@ export function makeAcl(json): Acl {
discoverConfigs,
contacts,
fileTransferAccess,
+ gitServers,
};
}
diff --git a/web/packages/teleport/src/services/user/types.ts b/web/packages/teleport/src/services/user/types.ts
index 37239dcd8d34d..188851625b7b9 100644
--- a/web/packages/teleport/src/services/user/types.ts
+++ b/web/packages/teleport/src/services/user/types.ts
@@ -109,6 +109,7 @@ export interface Acl {
accessMonitoringRule: Access;
contacts: Access;
fileTransferAccess: boolean;
+ gitServers: Access;
}
// AllTraits represent all the traits defined for a user.
diff --git a/web/packages/teleport/src/services/user/user.test.ts b/web/packages/teleport/src/services/user/user.test.ts
index cfbe71fe43f6a..5442173a22a6e 100644
--- a/web/packages/teleport/src/services/user/user.test.ts
+++ b/web/packages/teleport/src/services/user/user.test.ts
@@ -289,6 +289,13 @@ test('undefined values in context response gives proper default values', async (
desktopSessionRecordingEnabled: true,
directorySharingEnabled: true,
fileTransferAccess: true,
+ gitServers: {
+ list: false,
+ read: false,
+ edit: false,
+ create: false,
+ remove: false,
+ },
};
expect(response).toEqual({
diff --git a/web/packages/teleport/src/stores/storeUserContext.ts b/web/packages/teleport/src/stores/storeUserContext.ts
index f909670b9859f..1b8f20dcf216d 100644
--- a/web/packages/teleport/src/stores/storeUserContext.ts
+++ b/web/packages/teleport/src/stores/storeUserContext.ts
@@ -258,4 +258,8 @@ export default class StoreUserContext extends Store {
getContactsAccess() {
return this.state.acl.contacts;
}
+
+ getGitServersAccess() {
+ return this.state.acl.gitServers;
+ }
}
diff --git a/web/packages/teleport/src/teleportContext.tsx b/web/packages/teleport/src/teleportContext.tsx
index 3cacff180e27b..59406c4cafa68 100644
--- a/web/packages/teleport/src/teleportContext.tsx
+++ b/web/packages/teleport/src/teleportContext.tsx
@@ -242,6 +242,9 @@ class TeleportContext implements types.Context {
addBots: userContext.getBotsAccess().create,
editBots: userContext.getBotsAccess().edit,
removeBots: userContext.getBotsAccess().remove,
+ gitServers:
+ userContext.getGitServersAccess().list &&
+ userContext.getGitServersAccess().read,
};
}
}
@@ -282,6 +285,7 @@ export const disabledFeatureFlags: types.FeatureFlags = {
listBots: false,
editBots: false,
removeBots: false,
+ gitServers: false,
};
export default TeleportContext;
diff --git a/web/packages/teleport/src/types.ts b/web/packages/teleport/src/types.ts
index 69e909cd5011c..daa3758e3919b 100644
--- a/web/packages/teleport/src/types.ts
+++ b/web/packages/teleport/src/types.ts
@@ -206,6 +206,7 @@ export interface FeatureFlags {
addBots: boolean;
editBots: boolean;
removeBots: boolean;
+ gitServers: boolean;
}
// LockedFeatures are used for determining which features are disabled in the user's cluster.