Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add token list/delete endpoints #42402

Merged
merged 1 commit into from
Jun 7, 2024
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
16 changes: 12 additions & 4 deletions api/types/provisioning.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ type ProvisionToken interface {
GetJoinMethod() JoinMethod
// GetBotName returns the BotName field which must be set for joining bots.
GetBotName() string

// IsStatic returns true if the token is statically configured
IsStatic() bool
// GetSuggestedLabels returns the set of labels that the resource should add when adding itself to the cluster
GetSuggestedLabels() Labels

Expand Down Expand Up @@ -394,6 +395,11 @@ func (p *ProvisionTokenV2) GetJoinMethod() JoinMethod {
return p.Spec.JoinMethod
}

// IsStatic returns true if the token is statically configured
func (p *ProvisionTokenV2) IsStatic() bool {
return p.Origin() == OriginConfigFile
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I added the IsStatic method but I am not relying on the time anymore (it seemed fickle to me from the start). When we fetch static tokens, we create a new ProvisionToken from the available static tokens. I've just set their origin to OriginConfiguration instead of being nil and now IsStatic just checks if Origin() == OriginConfiguration. It seems to be more correct and resilient this way. Let me know what you think

// GetBotName returns the BotName field which must be set for joining bots.
func (p *ProvisionTokenV2) GetBotName() string {
return p.Spec.BotName
Expand Down Expand Up @@ -535,14 +541,16 @@ func ProvisionTokensToV1(in []ProvisionToken) []ProvisionTokenV1 {
return out
}

// ProvisionTokensFromV1 converts V1 provision tokens to resource list
func ProvisionTokensFromV1(in []ProvisionTokenV1) []ProvisionToken {
// ProvisionTokensFromStatic converts static tokens to resource list
func ProvisionTokensFromStatic(in []ProvisionTokenV1) []ProvisionToken {
Copy link
Contributor Author

@avatus avatus Jun 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added this method to replace ProvisionTokensFromV1 and all instances that I could find using the old method had to deal with static tokens. I could have just thrown in this SetOrigin there but I was worried it would be used for something that isnt a static token. If that isn't the case, I'll remove this new ProvisionTokensFromStatic method and throw it into the old one. Curious your thoughts

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's fine, but it would be better if we changed it at its creation instead of when converting to something else.
So, either on their CheckAndSetDefaults or when parsing the token from the file.

if err := st.CheckAndSetDefaults(); err != nil {

tokens, err := st.Parse()

if in == nil {
return nil
}
out := make([]ProvisionToken, len(in))
for i := range in {
out[i] = in[i].V2()
tok := in[i].V2()
tok.SetOrigin(OriginConfigFile)
out[i] = tok
}
return out
}
Expand Down
2 changes: 1 addition & 1 deletion api/types/statictokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func (c *StaticTokensV2) SetStaticTokens(s []ProvisionToken) {

// GetStaticTokens gets the list of static tokens used to provision nodes.
func (c *StaticTokensV2) GetStaticTokens() []ProvisionToken {
return ProvisionTokensFromV1(c.Spec.StaticTokens)
return ProvisionTokensFromStatic(c.Spec.StaticTokens)
}

// setStaticFields sets static resource header and metadata fields.
Expand Down
4 changes: 2 additions & 2 deletions lib/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1009,7 +1009,7 @@ func TestUpdateConfig(t *testing.T) {
require.Equal(t, cn.GetClusterName(), s.clusterName.GetClusterName())
st, err = s.a.GetStaticTokens()
require.NoError(t, err)
require.Equal(t, st.GetStaticTokens(), types.ProvisionTokensFromV1([]types.ProvisionTokenV1{{
require.Equal(t, st.GetStaticTokens(), types.ProvisionTokensFromStatic([]types.ProvisionTokenV1{{
Token: "bar",
Roles: types.SystemRoles{types.SystemRole("baz")},
}}))
Expand All @@ -1018,7 +1018,7 @@ func TestUpdateConfig(t *testing.T) {
// new static tokens
st, err = authServer.GetStaticTokens()
require.NoError(t, err)
require.Equal(t, st.GetStaticTokens(), types.ProvisionTokensFromV1([]types.ProvisionTokenV1{{
require.Equal(t, st.GetStaticTokens(), types.ProvisionTokensFromStatic([]types.ProvisionTokenV1{{
Token: "bar",
Roles: types.SystemRoles{types.SystemRole("baz")},
}}))
Expand Down
10 changes: 9 additions & 1 deletion lib/auth/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,9 +374,17 @@ func NewTestAuthServer(cfg TestAuthServerConfig) (*TestAuthServer, error) {
return nil, trace.Wrap(err)
}

token, err := types.NewProvisionTokenFromSpec("static-token", time.Unix(0, 0).UTC(), types.ProvisionTokenSpecV2{
Roles: types.SystemRoles{types.RoleNode},
})
if err != nil {
return nil, trace.Wrap(err)
}
// set static tokens
staticTokens, err := types.NewStaticTokens(types.StaticTokensSpecV2{
StaticTokens: []types.ProvisionTokenV1{},
StaticTokens: []types.ProvisionTokenV1{
*token.V1(),
},
})
if err != nil {
return nil, trace.Wrap(err)
Expand Down
4 changes: 2 additions & 2 deletions lib/auth/tls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4636,12 +4636,12 @@ func TestGRPCServer_GetTokens(t *testing.T) {
)
require.NoError(t, err)

t.Run("no tokens", func(t *testing.T) {
t.Run("no extra tokens", func(t *testing.T) {
client, err := testSrv.NewClient(TestUser(privilegedUser.GetName()))
require.NoError(t, err)
toks, err := client.GetTokens(ctx)
require.NoError(t, err)
require.Empty(t, toks)
require.Len(t, toks, 1) // only a single static token exists
})

// Create tokens to then assert are returned
Expand Down
7 changes: 7 additions & 0 deletions lib/auth/trustedcluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,13 @@ func (a *Server) validateTrustedCluster(ctx context.Context, validateRequest *au
if err != nil {
return nil, trace.Wrap(err)
}

originLabel, ok := tokenLabels[types.OriginLabel]
if ok && originLabel == types.OriginConfigFile {
// static tokens have an OriginLabel of OriginConfigFile and we don't want
// to propegate that to the trusted cluster
delete(tokenLabels, types.OriginLabel)
}
if len(tokenLabels) != 0 {
meta := remoteCluster.GetMetadata()
meta.Labels = utils.CopyStringsMap(tokenLabels)
Expand Down
2 changes: 1 addition & 1 deletion lib/config/configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,7 @@ func TestApplyConfig(t *testing.T) {
require.NoError(t, err)

require.Equal(t, "join-token", token)
require.Equal(t, types.ProvisionTokensFromV1([]types.ProvisionTokenV1{
require.Equal(t, types.ProvisionTokensFromStatic([]types.ProvisionTokenV1{
{
Token: "xxx",
Roles: types.SystemRoles([]types.SystemRole{"Proxy", "Node"}),
Expand Down
4 changes: 3 additions & 1 deletion lib/web/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -800,8 +800,10 @@ func (h *Handler) bindDefaultEndpoints() {
h.GET("/webapi/sites/:site/auth/export", h.authExportPublic)
h.GET("/webapi/auth/export", h.authExportPublic)

// token generation
// join token handlers
h.POST("/webapi/token", h.WithAuth(h.createTokenHandle))
h.GET("/webapi/tokens", h.WithAuth(h.getTokens))
h.DELETE("/webapi/tokens", h.WithAuth(h.deleteToken))

// join scripts
h.GET("/scripts/:token/install-node.sh", h.WithLimiter(h.getNodeJoinScriptHandle))
Expand Down
45 changes: 45 additions & 0 deletions lib/web/join_tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import (

const (
stableCloudChannelRepo = "stable/cloud"
HeaderTokenName = "X-Teleport-TokenName"
)

// nodeJoinToken contains node token fields for the UI.
Expand Down Expand Up @@ -92,6 +93,50 @@ func automaticUpgrades(features proto.Features) bool {
return features.AutomaticUpgrades && features.Cloud
}

// Currently we aren't paginating this endpoint as we don't
// expect many tokens to exist at a time. I'm leaving it in a "paginated" form
// without a nextKey for now so implementing pagination won't change the response shape
// TODO (avatus) implement pagination

// GetTokensResponse returns a list of JoinTokens.
type GetTokensResponse struct {
Items []ui.JoinToken `json:"items"`
}

func (h *Handler) getTokens(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)
}

tokens, err := clt.GetTokens(r.Context())
if err != nil {
return nil, trace.Wrap(err)
}

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

func (h *Handler) deleteToken(w http.ResponseWriter, r *http.Request, params httprouter.Params, ctx *SessionContext) (interface{}, error) {
token := r.Header.Get(HeaderTokenName)
if token == "" {
return nil, trace.BadParameter("requires a token to delete")
}

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

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

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 {
Expand Down
Loading
Loading