diff --git a/lib/web/join_tokens_test.go b/lib/web/join_tokens_test.go index 7c8537c92e713..51c25fd2e1181 100644 --- a/lib/web/join_tokens_test.go +++ b/lib/web/join_tokens_test.go @@ -99,6 +99,7 @@ func TestGenerateIAMTokenName(t *testing.T) { type tokenData struct { name string + labels map[string]string expiry time.Time spec types.ProvisionTokenSpecV2 } @@ -147,6 +148,30 @@ func TestGetTokens(t *testing.T) { }, includeUserToken: true, }, + { + name: "cloud managed", + tokenData: []tokenData{ + { + name: "cloud-iam", + spec: types.ProvisionTokenSpecV2{ + Roles: types.SystemRoles{types.RoleProxy}, + }, + labels: map[string]string{"teleport.internal/cloud/token": "iam"}, + expiry: expiry, + }, + }, + expected: []ui.JoinToken{ + { + ID: "cloud-iam", + SafeName: "*********", + Expiry: expiry, + Roles: types.SystemRoles{types.RoleProxy}, + IsCloudSystem: true, // due to presence of the teleport.internal/cloud/token label + Method: "token", + }, + staticUIToken, + }, + }, { name: "all tokens", tokenData: []tokenData{ @@ -318,6 +343,13 @@ func TestGetTokens(t *testing.T) { for _, td := range tc.tokenData { token, err := types.NewProvisionTokenFromSpec(td.name, td.expiry, td.spec) require.NoError(t, err) + + if len(td.labels) > 0 { + m := token.GetMetadata() + m.Labels = td.labels + token.SetMetadata(m) + } + err = env.server.Auth().CreateToken(ctx, token) require.NoError(t, err) } diff --git a/lib/web/ui/join_token.go b/lib/web/ui/join_token.go index 2319841375e22..1e38d387edfc5 100644 --- a/lib/web/ui/join_token.go +++ b/lib/web/ui/join_token.go @@ -41,6 +41,8 @@ type JoinToken struct { Roles types.SystemRoles `json:"roles"` // IsStatic is true if the token is statically configured IsStatic bool `json:"isStatic"` + // IsCloudSystem is true if the token is managed by the Teleport Cloud system. + IsCloudSystem bool `json:"isCloudSystem"` // Method is the join method that the token supports Method types.JoinMethod `json:"method"` // Allow is a list of allow rules @@ -58,16 +60,20 @@ func MakeJoinToken(token types.ProvisionToken) (*JoinToken, error) { if err != nil { return nil, trace.Wrap(err) } + + _, isCloudSystem := token.GetMetadata().Labels["teleport.internal/cloud/token"] + uiToken := &JoinToken{ - ID: token.GetName(), - SafeName: token.GetSafeName(), - BotName: token.GetBotName(), - Expiry: token.Expiry(), - Roles: token.GetRoles(), - IsStatic: token.IsStatic(), - Method: token.GetJoinMethod(), - Allow: token.GetAllowRules(), - Content: string(content[:]), + ID: token.GetName(), + SafeName: token.GetSafeName(), + BotName: token.GetBotName(), + Expiry: token.Expiry(), + Roles: token.GetRoles(), + IsStatic: token.IsStatic(), + IsCloudSystem: isCloudSystem, + Method: token.GetJoinMethod(), + Allow: token.GetAllowRules(), + Content: string(content[:]), } if uiToken.Method == types.JoinMethodGCP { diff --git a/web/packages/teleport/src/JoinTokens/JoinTokens.tsx b/web/packages/teleport/src/JoinTokens/JoinTokens.tsx index 94ece31cd1831..accbc2b136a86 100644 --- a/web/packages/teleport/src/JoinTokens/JoinTokens.tsx +++ b/web/packages/teleport/src/JoinTokens/JoinTokens.tsx @@ -18,7 +18,7 @@ import { addHours, isAfter } from 'date-fns'; import { useEffect, useState } from 'react'; -import styled from 'styled-components'; +import styled, { useTheme } from 'styled-components'; import { Alert, @@ -77,6 +77,8 @@ function makeTokenResource(token: JoinToken): Resource { export const JoinTokens = () => { const ctx = useTeleport(); + const theme = useTheme(); + const [creatingToken, setCreatingToken] = useState(false); const [editingToken, setEditingToken] = useState(null); const [tokenToDelete, setTokenToDelete] = useState(null); @@ -88,6 +90,15 @@ export const JoinTokens = () => { { join_token: '' } // we are only editing for now, so template can be empty ); + function getRowStyle(row: JoinToken): React.CSSProperties { + if (row.isCloudSystem) { + return { + background: theme.colors.interactive.tonal.neutral[0], + color: theme.colors.text.muted, + }; + } + } + function updateTokenList(token: JoinToken): JoinToken[] { let items = [...joinTokensAttempt.data.items]; if (creatingToken) { @@ -174,9 +185,12 @@ export const JoinTokens = () => { {joinTokensAttempt.statusText} )} {joinTokensAttempt.status === 'success' && ( - { ); }; +// This is necessary because the Table component styles +// hard-code the text color for the
element, preventing +// our row style from being applied. +const JoinTokenTable = styled(Table)` + & > tbody > tr > td { + color: inherit; + } +` as typeof Table; + export function searchMatcher( targetValue: any, searchValue: string, @@ -370,7 +393,7 @@ const StyledLabel = styled(Label)` margin: 1px 0; margin-right: ${props => props.theme.space[2]}px; background-color: ${props => props.theme.colors.interactive.tonal.neutral[0]}; - color: ${props => props.theme.colors.text.main}; + color: inherit; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -434,7 +457,7 @@ const ActionCell = ({ onDelete(): void; token: JoinToken; }) => { - const buttonProps = { width: '100px' }; + const buttonProps = { width: '100px', disabled: false }; if (token.isStatic) { return ( @@ -447,12 +470,29 @@ const ActionCell = ({ ); } + if (token.isCloudSystem) { + buttonProps.disabled = true; + } + + const Button = ( + + View/Edit... + Delete... + + ); + return ( - - View/Edit... - Delete... - + {token.isCloudSystem ? ( + + {Button} + + ) : ( + <>{Button} + )} ); }; diff --git a/web/packages/teleport/src/services/joinToken/makeJoinToken.ts b/web/packages/teleport/src/services/joinToken/makeJoinToken.ts index a4da7b2a47a62..d846e029f7508 100644 --- a/web/packages/teleport/src/services/joinToken/makeJoinToken.ts +++ b/web/packages/teleport/src/services/joinToken/makeJoinToken.ts @@ -28,6 +28,7 @@ export default function makeToken(json: any): JoinToken { id, roles, isStatic, + isCloudSystem, allow, gcp, github, @@ -44,6 +45,7 @@ export default function makeToken(json: any): JoinToken { return { id, isStatic, + isCloudSystem, safeName, bot_name, method, diff --git a/web/packages/teleport/src/services/joinToken/types.ts b/web/packages/teleport/src/services/joinToken/types.ts index 36c281992872b..d051acb4958de 100644 --- a/web/packages/teleport/src/services/joinToken/types.ts +++ b/web/packages/teleport/src/services/joinToken/types.ts @@ -27,6 +27,8 @@ export type JoinToken = { // bot_name is present on tokens with Bot in their join roles bot_name?: string; isStatic: boolean; + // the token is managed by the Teleport Cloud system and should not be edited by end users + isCloudSystem?: boolean; // the join method of the token method: string; // Roles are the roles granted to the token