Skip to content

Commit

Permalink
Add initial JoinTokens ui
Browse files Browse the repository at this point in the history
This PR is part of the new join tokens feature. This will make the join
tokens UI available on /web/tokens but not enable it in the navigation.
You will have to manually view it. We will add it to the navigation just
before the backport to the release branch. I did it this way so we can
start PRing the larger chunks while we work on the "create token" forms.
So obviously, the "create token" button doesn't exist on here. this is
for viewing/editting for now.
  • Loading branch information
avatus committed Jul 9, 2024
1 parent 2443ad7 commit 8b4fc4c
Show file tree
Hide file tree
Showing 19 changed files with 753 additions and 24 deletions.
1 change: 1 addition & 0 deletions lib/web/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,7 @@ func (h *Handler) bindDefaultEndpoints() {
h.GET("/webapi/auth/export", h.authExportPublic)

// join token handlers
h.PUT("/webapi/token/yaml", h.WithAuth(h.upsertTokenContent))
h.POST("/webapi/token", h.WithAuth(h.createTokenHandle))
h.GET("/webapi/tokens", h.WithAuth(h.getTokens))
h.DELETE("/webapi/tokens", h.WithAuth(h.deleteToken))
Expand Down
53 changes: 49 additions & 4 deletions lib/web/join_tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import (
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/httplib"
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/web/scripts"
Expand Down Expand Up @@ -114,8 +115,13 @@ func (h *Handler) getTokens(w http.ResponseWriter, r *http.Request, params httpr
return nil, trace.Wrap(err)
}

uiTokens, err := ui.MakeJoinTokens(tokens)
if err != nil {
return nil, trace.Wrap(err)
}

return GetTokensResponse{
Items: ui.MakeJoinTokens(tokens),
Items: uiTokens,
}, nil
}

Expand All @@ -137,17 +143,56 @@ func (h *Handler) deleteToken(w http.ResponseWriter, r *http.Request, params htt
return OK(), nil
}

func (h *Handler) createTokenHandle(w http.ResponseWriter, r *http.Request, params httprouter.Params, ctx *SessionContext) (interface{}, error) {
var req types.ProvisionTokenSpecV2
if err := httplib.ReadJSON(r, &req); err != nil {
type CreateTokenRequest struct {
Content string `json:"content"`
}

func (h *Handler) upsertTokenContent(w http.ResponseWriter, r *http.Request, params httprouter.Params, sctx *SessionContext) (interface{}, error) {
var yaml CreateTokenRequest
if err := httplib.ReadJSON(r, &yaml); err != nil {
return nil, trace.Wrap(err)
}

extractedRes, err := ExtractResourceAndValidate(yaml.Content)
if err != nil {
return nil, trace.Wrap(err)
}

token, err := services.UnmarshalProvisionToken(extractedRes.Raw)
if err != nil {
return nil, trace.Wrap(err)
}

clt, err := sctx.GetClient()
if err != nil {
return nil, trace.Wrap(err)
}

err = clt.UpsertToken(r.Context(), token)
if err != nil {
return nil, trace.Wrap(err)
}

uiToken, err := ui.MakeJoinToken(token)
if err != nil {
return nil, trace.Wrap(err)
}

return uiToken, trace.Wrap(err)

}

func (h *Handler) createTokenHandle(w http.ResponseWriter, r *http.Request, params httprouter.Params, ctx *SessionContext) (interface{}, error) {
clt, err := ctx.GetClient()
if err != nil {
return nil, trace.Wrap(err)
}

var req types.ProvisionTokenSpecV2
if err := httplib.ReadJSON(r, &req); err != nil {
return nil, trace.Wrap(err)
}

var expires time.Time
var tokenName string
switch req.JoinMethod {
Expand Down
13 changes: 8 additions & 5 deletions lib/web/join_tokens_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import (
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/gravitational/trace"
"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -101,6 +103,7 @@ func TestGetTokens(t *testing.T) {
Expiry: time.Unix(0, 0).UTC(),
IsStatic: true,
Method: types.JoinMethodToken,
Content: "kind: token\nmetadata:\n expires: \"1970-01-01T00:00:00Z\"\n labels:\n teleport.dev/origin: config-file\n name: static-token\nspec:\n join_method: token\n roles:\n - Node\nversion: v2\n",
}

tt := []struct {
Expand Down Expand Up @@ -160,7 +163,6 @@ func TestGetTokens(t *testing.T) {
},
},
expected: []ui.JoinToken{
staticUIToken,
{
ID: "test-token",
SafeName: "**********",
Expand Down Expand Up @@ -194,9 +196,11 @@ func TestGetTokens(t *testing.T) {
},
Method: types.JoinMethodToken,
},
staticUIToken,
},
},
}

for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
env := newWebPack(t, 1)
Expand Down Expand Up @@ -250,7 +254,7 @@ func TestGetTokens(t *testing.T) {
resp := GetTokensResponse{}
require.NoError(t, json.Unmarshal(re.Bytes(), &resp))
require.Len(t, resp.Items, len(tc.expected))
require.ElementsMatch(t, resp.Items, tc.expected)
require.Empty(t, cmp.Diff(resp.Items, tc.expected, cmpopts.IgnoreFields(ui.JoinToken{}, "Content")))
})
}
}
Expand All @@ -269,6 +273,7 @@ func TestDeleteToken(t *testing.T) {
Expiry: time.Unix(0, 0).UTC(),
IsStatic: true,
Method: types.JoinMethodToken,
Content: "kind: token\nmetadata:\n expires: \"1970-01-01T00:00:00Z\"\n labels:\n teleport.dev/origin: config-file\n name: static-token\nspec:\n join_method: token\n roles:\n - Node\nversion: v2\n",
}

// create join token
Expand Down Expand Up @@ -319,9 +324,7 @@ func TestDeleteToken(t *testing.T) {
resp = GetTokensResponse{}
require.NoError(t, json.Unmarshal(re.Bytes(), &resp))
require.Len(t, resp.Items, 1 /* only static again */)
require.ElementsMatch(t, resp.Items, []ui.JoinToken{
staticUIToken,
})
require.Empty(t, cmp.Diff(resp.Items, []ui.JoinToken{staticUIToken}, cmpopts.IgnoreFields(ui.JoinToken{}, "Content")))
}

func TestGenerateAzureTokenName(t *testing.T) {
Expand Down
26 changes: 20 additions & 6 deletions lib/web/ui/join_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ package ui
import (
"time"

yaml "github.com/ghodss/yaml"
"github.com/gravitational/trace"

"github.com/gravitational/teleport/api/types"
)

Expand All @@ -40,22 +43,33 @@ type JoinToken struct {
Method types.JoinMethod `json:"method"`
// AllowRules is a list of allow rules
AllowRules []string `json:"allowRules,omitempty"`
// Content is resource yaml content.
Content string `json:"content"`
}

func MakeJoinToken(token types.ProvisionToken) JoinToken {
return JoinToken{
func MakeJoinToken(token types.ProvisionToken) (*JoinToken, error) {
content, err := yaml.Marshal(token)
if err != nil {
return nil, trace.Wrap(err)
}
return &JoinToken{
ID: token.GetName(),
SafeName: token.GetSafeName(),
Expiry: token.Expiry(),
Roles: token.GetRoles(),
IsStatic: token.IsStatic(),
Method: token.GetJoinMethod(),
}
Content: string(content[:]),
}, nil
}

func MakeJoinTokens(tokens []types.ProvisionToken) (joinTokens []JoinToken) {
func MakeJoinTokens(tokens []types.ProvisionToken) (joinTokens []JoinToken, err error) {
for _, t := range tokens {
joinTokens = append(joinTokens, MakeJoinToken(t))
uiToken, err := MakeJoinToken(t)
if err != nil {
return nil, trace.Wrap(err)
}
joinTokens = append(joinTokens, *uiToken)
}
return joinTokens
return joinTokens, nil
}
4 changes: 4 additions & 0 deletions web/packages/shared/components/ToolTip/HoverTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import React, { PropsWithChildren, useState } from 'react';
import styled from 'styled-components';
import { Popover, Flex, Text } from 'design';
import { JustifyContentProps } from 'design/system';

type OriginProps = {
vertical: string;
Expand All @@ -32,6 +33,7 @@ export const HoverTooltip: React.FC<
className?: string;
anchorOrigin?: OriginProps;
transformOrigin?: OriginProps;
justifyContentProps?: JustifyContentProps;
}>
> = ({
tipContent,
Expand All @@ -40,6 +42,7 @@ export const HoverTooltip: React.FC<
className,
anchorOrigin = { vertical: 'top', horizontal: 'center' },
transformOrigin = { vertical: 'bottom', horizontal: 'center' },
justifyContentProps = {},
}) => {
const [anchorEl, setAnchorEl] = useState<Element | undefined>();
const open = Boolean(anchorEl);
Expand Down Expand Up @@ -77,6 +80,7 @@ export const HoverTooltip: React.FC<
onMouseEnter={handlePopoverOpen}
onMouseLeave={handlePopoverClose}
className={className}
{...justifyContentProps}
>
{children}
<Popover
Expand Down
11 changes: 10 additions & 1 deletion web/packages/teleport/src/Apps/AddApp/AddApp.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,14 @@ const props = {
status: '',
statusText: '',
} as any,
token: { id: 'join-token', expiryText: '1 hour', expiry: null },
token: {
id: 'join-token',
expiryText: '1 hour',
expiry: null,
safeName: '',
isStatic: false,
method: 'kubernetes',
roles: [],
content: '',
},
};
11 changes: 10 additions & 1 deletion web/packages/teleport/src/Apps/AddApp/Automatically.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,16 @@ import { act } from '@testing-library/react';
import { Automatically, createAppBashCommand } from './Automatically';

test('render command only after form submit', async () => {
const token = { id: 'token', expiryText: '', expiry: null };
const token = {
id: 'token',
expiryText: '',
expiry: null,
safeName: '',
isStatic: false,
method: 'kubernetes',
roles: [],
content: '',
};
render(
<Automatically
token={token}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ describe('gitHub component', () => {
jest.spyOn(ctx.joinTokenService, 'fetchJoinToken').mockResolvedValue({
id: tokenName,
expiry: new Date('2020-01-01'),
safeName: '',
isStatic: false,
method: 'kubernetes',
roles: [],
content: '',
});
jest.spyOn(botService, 'createBot').mockResolvedValue();
jest.spyOn(botService, 'createBotToken').mockResolvedValue({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ export function AgentWaitingDialog({
// These are not used by usePingTeleport
// todo(anton): Refactor usePingTeleport to not require full join token.
expiry: undefined,
safeName: '',
isStatic: false,
method: 'kubernetes',
roles: [],
content: '',
expiryText: '',
id: '',
suggestedLabels: [],
Expand Down
Loading

0 comments on commit 8b4fc4c

Please sign in to comment.