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 d674d094248c8..3ae47b2376070 100644
--- a/lib/web/apiserver.go
+++ b/lib/web/apiserver.go
@@ -2948,6 +2948,7 @@ func makeUnifiedResourceRequest(r *http.Request) (*proto.ListUnifiedResourcesReq
types.KindWindowsDesktop,
types.KindKubernetesCluster,
types.KindSAMLIdPServiceProvider,
+ types.KindGitServer,
}
}
@@ -3077,11 +3078,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)
+ var logins []string
+ switch enriched.GetKind() {
+ case types.KindNode:
+ logins, err = calculateSSHLogins(identity, enriched.Logins)
+ if err != nil {
+ return nil, trace.Wrap(err)
+ }
+ case types.KindGitServer:
+ break
}
-
unifiedResources = append(unifiedResources, ui.MakeServer(site.GetName(), r, logins, enriched.RequiresRequest))
case types.DatabaseServer:
if !hasFetchedDBUsersAndNames {
diff --git a/lib/web/apiserver_test.go b/lib/web/apiserver_test.go
index 3a701fe65a26a..b0d1d1e2388d3 100644
--- a/lib/web/apiserver_test.go
+++ b/lib/web/apiserver_test.go
@@ -1281,6 +1281,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{
@@ -1377,6 +1378,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")
@@ -1427,7 +1437,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/server.go b/lib/web/ui/server.go
index 9921307c48134..ebdf4d34fdbc0 100644
--- a/lib/web/ui/server.go
+++ b/lib/web/ui/server.go
@@ -54,6 +54,8 @@ type Server struct {
AWS *AWSMetadata `json:"aws,omitempty"`
// 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"`
}
// AWSMetadata describes the AWS metadata for instances hosted in AWS.
@@ -67,6 +69,12 @@ type AWSMetadata struct {
SubnetID string `json:"subnetId"`
}
+// GitHubServerMetadata contains metadata for GitHub proxy severs.
+type GitHubServerMetadata struct {
+ Integration string `json:"integration"`
+ Organization string `json:"organization"`
+}
+
// MakeServer creates a server object for the web ui
func MakeServer(clusterName string, server types.Server, logins []string, requiresRequest bool) Server {
serverLabels := server.GetStaticLabels()
@@ -98,6 +106,15 @@ func MakeServer(clusterName string, server types.Server, logins []string, requir
}
}
+ if server.GetKind() == types.KindGitServer &&
+ 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/server_test.go b/lib/web/ui/server_test.go
index 9af621cf09557..e5795e92414f8 100644
--- a/lib/web/ui/server_test.go
+++ b/lib/web/ui/server_test.go
@@ -590,3 +590,62 @@ func TestSortedLabels(t *testing.T) {
})
}
}
+
+func TestMakeServer(t *testing.T) {
+ tests := []struct {
+ name string
+ inputServer types.Server
+ inputLogins []string
+ output Server
+ }{
+ {
+ name: "git server",
+ inputServer: makeGitServer(t, "org1"),
+ output: Server{
+ ClusterName: "cluster",
+ Kind: "git_server",
+ SubKind: "github",
+ Name: "org1",
+ Hostname: "org1.github-org",
+ GitHub: &GitHubServerMetadata{
+ Integration: "org1",
+ Organization: "org1",
+ },
+ // Internal labels get filtered.
+ Labels: []ui.Label{},
+ },
+ },
+ {
+ name: "node",
+ inputServer: makeTestServer(t, "server1", map[string]string{"env": "dev"}),
+ inputLogins: []string{"alice"},
+ output: Server{
+ ClusterName: "cluster",
+ Kind: "node",
+ SubKind: "teleport",
+ Name: "server1",
+ SSHLogins: []string{"alice"},
+ Labels: []ui.Label{{
+ Name: "env",
+ Value: "dev",
+ }},
+ },
+ },
+ }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ require.Equal(t, test.output, MakeServer("cluster", test.inputServer, test.inputLogins, false))
+ })
+ }
+}
+
+func makeGitServer(t *testing.T, org string) types.Server {
+ t.Helper()
+ server, err := types.NewGitHubServer(types.GitHubServerMetadata{
+ Integration: org,
+ Organization: org,
+ })
+ require.NoError(t, err)
+ server.SetName(org)
+ return server
+}
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 8595076221a6c..79d45a48ef765 100644
--- a/web/packages/design/src/ResourceIcon/icons.ts
+++ b/web/packages/design/src/ResourceIcon/icons.ts
@@ -122,6 +122,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';
@@ -408,6 +410,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 80007e3a1e4c8..82a10c062fa52 100644
--- a/web/packages/design/src/ResourceIcon/resourceIconSpecs.ts
+++ b/web/packages/design/src/ResourceIcon/resourceIconSpecs.ts
@@ -121,6 +121,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 fd4bc1578869d..ac6c883e5a07d 100644
--- a/web/packages/shared/components/AccessRequests/NewRequest/RequestCheckout/RequestCheckout.tsx
+++ b/web/packages/shared/components/AccessRequests/NewRequest/RequestCheckout/RequestCheckout.tsx
@@ -791,6 +791,8 @@ function getPrettyResourceKind(kind: RequestableResourceKind): string {
return 'SAML Application';
case 'namespace':
return 'Namespace';
+ 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 f56ad58110d25..38f1a4b678bfd 100644
--- a/web/packages/shared/components/AccessRequests/NewRequest/resource.ts
+++ b/web/packages/shared/components/AccessRequests/NewRequest/resource.ts
@@ -45,5 +45,6 @@ export function getEmptyResourceState(): ResourceMap {
role: {},
saml_idp_service_provider: {},
namespace: {},
+ git_server: {},
};
}
diff --git a/web/packages/shared/components/UnifiedResources/FilterPanel.tsx b/web/packages/shared/components/UnifiedResources/FilterPanel.tsx
index d470abe9a9e9e..680b27d2ea75f 100644
--- a/web/packages/shared/components/UnifiedResources/FilterPanel.tsx
+++ b/web/packages/shared/components/UnifiedResources/FilterPanel.tsx
@@ -47,6 +47,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 cd67aad58dd29..13d267254f9ac 100644
--- a/web/packages/shared/components/UnifiedResources/UnifiedResources.story.tsx
+++ b/web/packages/shared/components/UnifiedResources/UnifiedResources.story.tsx
@@ -25,6 +25,7 @@ import { databases, moreDatabases } from 'teleport/Databases/fixtures';
import { kubes, moreKubes } from 'teleport/Kubes/fixtures';
import { desktops, moreDesktops } from 'teleport/Desktops/fixtures';
import { moreNodes, nodes } from 'teleport/Nodes/fixtures';
+import { gitServers } from 'teleport/GitServers/fixtures';
import { UrlResourcesParams } from 'teleport/config';
import { ResourcesResponse } from 'teleport/services/agents';
@@ -70,6 +71,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 a9f0699018d42..65596eeb609ed 100644
--- a/web/packages/shared/components/UnifiedResources/UnifiedResources.tsx
+++ b/web/packages/shared/components/UnifiedResources/UnifiedResources.tsx
@@ -660,8 +660,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 9a70b5951a329..6a0dba7a804e6 100644
--- a/web/packages/shared/components/UnifiedResources/shared/viewItemsFactory.ts
+++ b/web/packages/shared/components/UnifiedResources/shared/viewItemsFactory.ts
@@ -22,6 +22,7 @@ import {
Kubernetes as KubernetesIcon,
Server as ServerIcon,
Desktop as DesktopIcon,
+ GitHub as GitHubIcon,
} from 'design/Icon';
import { ResourceIconName } from 'design/ResourceIcon';
@@ -37,6 +38,7 @@ import {
UnifiedResourceDesktop,
UnifiedResourceKube,
UnifiedResourceUserGroup,
+ UnifiedResourceGitServer,
SharedUnifiedResource,
} from '../types';
@@ -172,6 +174,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':
@@ -216,5 +238,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 22c0c52547181..bb6d0e1fa66fa 100644
--- a/web/packages/shared/components/UnifiedResources/types.ts
+++ b/web/packages/shared/components/UnifiedResources/types.ts
@@ -85,6 +85,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;
};
@@ -96,7 +109,8 @@ export type SharedUnifiedResource = {
| UnifiedResourceNode
| UnifiedResourceKube
| UnifiedResourceDesktop
- | UnifiedResourceUserGroup;
+ | UnifiedResourceUserGroup
+ | UnifiedResourceGitServer;
ui: UnifiedResourceUi;
};
diff --git a/web/packages/teleport/src/GitServers/ConnectDialog/ConnectDialog.story.tsx b/web/packages/teleport/src/GitServers/ConnectDialog/ConnectDialog.story.tsx
new file mode 100644
index 0000000000000..335a5970828c2
--- /dev/null
+++ b/web/packages/teleport/src/GitServers/ConnectDialog/ConnectDialog.story.tsx
@@ -0,0 +1,45 @@
+/**
+ * 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 React from 'react';
+
+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/ConnectDialog.tsx b/web/packages/teleport/src/GitServers/ConnectDialog/ConnectDialog.tsx
new file mode 100644
index 0000000000000..82f2152f9a911
--- /dev/null
+++ b/web/packages/teleport/src/GitServers/ConnectDialog/ConnectDialog.tsx
@@ -0,0 +1,104 @@
+/**
+ * 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 React from 'react';
+import { Text, Box, ButtonSecondary, Link } from 'design';
+import Dialog, {
+ DialogHeader,
+ DialogTitle,
+ DialogContent,
+ DialogFooter,
+} from 'design/Dialog';
+import { TextSelectCopy } from 'shared/components/TextSelectCopy';
+
+import { AuthType } from 'teleport/services/user';
+import { generateTshLoginCommand } from 'teleport/lib/util';
+
+export default function ConnectDialog({
+ username,
+ clusterId,
+ organization,
+ onClose,
+ authType,
+ accessRequestId,
+}: Props) {
+ let repoURL = `https://github.com/orgs/${organization}/repositories`;
+ let title = `Use 'git' for GitHub Organization '${organization}'`;
+ return (
+
+ );
+}
+
+export type Props = {
+ organization: string;
+ onClose: () => void;
+ username: string;
+ clusterId: string;
+ authType: AuthType;
+ accessRequestId?: string;
+};
diff --git a/web/packages/teleport/src/GitServers/ConnectDialog/index.ts b/web/packages/teleport/src/GitServers/ConnectDialog/index.ts
new file mode 100644
index 0000000000000..7d524172d41ec
--- /dev/null
+++ b/web/packages/teleport/src/GitServers/ConnectDialog/index.ts
@@ -0,0 +1,20 @@
+/**
+ * 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 ConnectDialog from './ConnectDialog';
+export default ConnectDialog;
diff --git a/web/packages/teleport/src/GitServers/fixtures/index.ts b/web/packages/teleport/src/GitServers/fixtures/index.ts
new file mode 100644
index 0000000000000..ff487ee0f0a13
--- /dev/null
+++ b/web/packages/teleport/src/GitServers/fixtures/index.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 'teleport/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/UnifiedResources/ResourceActionButton.tsx b/web/packages/teleport/src/UnifiedResources/ResourceActionButton.tsx
index ab51c11b2c4c4..814e5048c211f 100644
--- a/web/packages/teleport/src/UnifiedResources/ResourceActionButton.tsx
+++ b/web/packages/teleport/src/UnifiedResources/ResourceActionButton.tsx
@@ -32,8 +32,10 @@ import { Database } from 'teleport/services/databases';
import { openNewTab } from 'teleport/lib/util';
import { Kube } from 'teleport/services/kube';
import { Desktop } from 'teleport/services/desktops';
+import { GitServer } from 'teleport/services/gitservers';
import DbConnectDialog from 'teleport/Databases/ConnectDialog';
import KubeConnectDialog from 'teleport/Kubes/ConnectDialog';
+import GitServerConnectDialog from 'teleport/GitServers/ConnectDialog';
import useStickyClusterId from 'teleport/useStickyClusterId';
import { Node, sortNodeLogins } from 'teleport/services/nodes';
import { App } from 'teleport/services/apps';
@@ -59,6 +61,8 @@ export const ResourceActionButton = ({ resource }: Props) => {
return ;
case 'windows_desktop':
return ;
+ case 'git_server':
+ return ;
default:
return null;
}
@@ -327,6 +331,42 @@ 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
+ ? gitserver.github.organization
+ : undefined;
+ 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 0e2b314736e12..8b0fe89e3c8d3 100644
--- a/web/packages/teleport/src/UnifiedResources/UnifiedResources.tsx
+++ b/web/packages/teleport/src/UnifiedResources/UnifiedResources.tsx
@@ -95,6 +95,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 2d1378f964fd3..afc31a15d6c7d 100644
--- a/web/packages/teleport/src/mocks/contexts.ts
+++ b/web/packages/teleport/src/mocks/contexts.ts
@@ -77,6 +77,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 767f43b9af79d..81a3a31638bf7 100644
--- a/web/packages/teleport/src/services/agents/types.ts
+++ b/web/packages/teleport/src/services/agents/types.ts
@@ -23,8 +23,8 @@ import { Database } from 'teleport/services/databases';
import { Node } from 'teleport/services/nodes';
import { Kube } from 'teleport/services/kube';
import { Desktop } from 'teleport/services/desktops';
-
-import { UserGroup } from '../userGroups';
+import { UserGroup } from 'teleport/services/userGroups';
+import { GitServer } from 'teleport/services/gitservers';
import type { MfaAuthnResponse } from '../mfa';
import type { Platform } from 'design/platform';
@@ -35,7 +35,8 @@ export type UnifiedResource =
| Node
| Kube
| Desktop
- | UserGroup;
+ | UserGroup
+ | GitServer;
export type UnifiedResourceKind = UnifiedResource['kind'];
@@ -88,6 +89,7 @@ export type ResourceIdKind =
| 'kube_cluster'
| 'user_group'
| 'windows_desktop'
+ | 'git_server'
| 'saml_idp_service_provider';
export type AccessRequestScope =
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..b08f74ef17010
--- /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 { GitServer, GitHubMetadata } 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..29d93834058d9
--- /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..13b28d10483e6 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 f734942a9d4fa..19346907c4c39 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 e50f9fb9d6e61..8d2dfa7805e4d 100644
--- a/web/packages/teleport/src/stores/storeUserContext.ts
+++ b/web/packages/teleport/src/stores/storeUserContext.ts
@@ -259,4 +259,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 1ba4c3fde4380..43c7f4dd7c1bd 100644
--- a/web/packages/teleport/src/teleportContext.tsx
+++ b/web/packages/teleport/src/teleportContext.tsx
@@ -242,6 +242,8 @@ 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 +284,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 356dc95027ee1..a62f8604befdc 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.