Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions lib/web/join_tokens_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ func TestGenerateIAMTokenName(t *testing.T) {

type tokenData struct {
name string
labels map[string]string
expiry time.Time
spec types.ProvisionTokenSpecV2
}
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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)
}
Expand Down
24 changes: 15 additions & 9 deletions lib/web/ui/join_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down
56 changes: 48 additions & 8 deletions web/packages/teleport/src/JoinTokens/JoinTokens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -77,6 +77,8 @@ function makeTokenResource(token: JoinToken): Resource<KindJoinToken> {

export const JoinTokens = () => {
const ctx = useTeleport();
const theme = useTheme();

const [creatingToken, setCreatingToken] = useState(false);
const [editingToken, setEditingToken] = useState<JoinToken | null>(null);
const [tokenToDelete, setTokenToDelete] = useState<JoinToken | null>(null);
Expand All @@ -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) {
Expand Down Expand Up @@ -174,9 +185,12 @@ export const JoinTokens = () => {
<Alert kind="danger">{joinTokensAttempt.statusText}</Alert>
)}
{joinTokensAttempt.status === 'success' && (
<Table
<JoinTokenTable
isSearchable
data={joinTokensAttempt.data.items}
row={{
getStyle: getRowStyle,
}}
columns={[
{
key: 'id',
Expand Down Expand Up @@ -310,6 +324,15 @@ export const JoinTokens = () => {
);
};

// This is necessary because the Table component styles
// hard-code the text color for the <td> 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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -434,7 +457,7 @@ const ActionCell = ({
onDelete(): void;
token: JoinToken;
}) => {
const buttonProps = { width: '100px' };
const buttonProps = { width: '100px', disabled: false };
if (token.isStatic) {
return (
<Cell align="right">
Expand All @@ -447,12 +470,29 @@ const ActionCell = ({
</Cell>
);
}
if (token.isCloudSystem) {
buttonProps.disabled = true;
}

const Button = (
<MenuButton buttonProps={buttonProps}>
<MenuItem onClick={onEdit}>View/Edit...</MenuItem>
<MenuItem onClick={onDelete}>Delete...</MenuItem>
</MenuButton>
);

return (
<Cell align="right">
<MenuButton buttonProps={buttonProps}>
<MenuItem onClick={onEdit}>View/Edit...</MenuItem>
<MenuItem onClick={onDelete}>Delete...</MenuItem>
</MenuButton>
{token.isCloudSystem ? (
<HoverTooltip
placement="top-end"
tipContent="This token is managed by Teleport Cloud."
>
{Button}
</HoverTooltip>
) : (
<>{Button}</>
)}
</Cell>
);
};
Expand Down
2 changes: 2 additions & 0 deletions web/packages/teleport/src/services/joinToken/makeJoinToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default function makeToken(json: any): JoinToken {
id,
roles,
isStatic,
isCloudSystem,
allow,
gcp,
github,
Expand All @@ -44,6 +45,7 @@ export default function makeToken(json: any): JoinToken {
return {
id,
isStatic,
isCloudSystem,
safeName,
bot_name,
method,
Expand Down
2 changes: 2 additions & 0 deletions web/packages/teleport/src/services/joinToken/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading