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
4 changes: 4 additions & 0 deletions api/types/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,10 @@ const (

// BotGenerationLabel is a label used to record the certificate generation counter.
BotGenerationLabel = "teleport.internal/bot-generation"

// InternalResourceIDLabel is a label used to store an ID to correlate between two resources
// A pratical example of this is to create a correlation between a Node Provision Token and the Node that used that token to join the cluster
InternalResourceIDLabel = "teleport.internal/resource-id"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should this be teleport.dev/resource-id instead? Or are we deprecating teleport.dev and standardizing on teleport.internal instead?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think the idea is to keep both and filter out teleport.internal labels from the UI (not sure if that's the case now).

Copy link
Copy Markdown
Contributor Author

@marcoandredinis marcoandredinis Aug 4, 2022

Choose a reason for hiding this comment

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

So, the WebUI doesn't filter teleport.dev either
image

I'm not sure which one we should use, but after looking into this code

func stripInternalTeleportLabels(verbose bool, labels map[string]string) string {

I would say we should use teleport.dev/ and create an issue to filter out these labels when displaying labels in the UI

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd say teleport.internal/ should be hidden, teleport.dev/ is also used for stuff like automatic labels from LDAP for desktop access - so I'm ok with teleport.internal/resource-id.

What I'm not ok with is that one bot label using teleport.internal/kebab-case while desktop access uses teleport.dev/snake_case, but that's not something that can be fixed here (or ever? 😭)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll keep it as teleport.internal/ then.

As for case, it seems teleport.internal/ uses kebab case and teleport.dev/ uses snake case
At least they are consistent within the namespace 😂
Although the teleport.internal/ only has one occurrence, so we can't really say if that's the rule or the exception 😅

)

// RequestableResourceKinds lists all Teleport resource kinds users can request access to.
Expand Down
9 changes: 9 additions & 0 deletions api/types/provisioning.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ type ProvisionToken interface {
GetJoinMethod() JoinMethod
// GetBotName returns the BotName field which must be set for joining bots.
GetBotName() string

// GetSuggestedLabels returns the set of labels that the resource should add when adding itself to the cluster
GetSuggestedLabels() Labels

// V1 returns V1 version of the resource
V1() *ProvisionTokenV1
// String returns user friendly representation of the resource
Expand Down Expand Up @@ -250,6 +254,11 @@ func (p *ProvisionTokenV2) SetMetadata(meta Metadata) {
p.Metadata = meta
}

// GetSuggestedLabels returns the labels the resource should set when using this token
func (p *ProvisionTokenV2) GetSuggestedLabels() Labels {
return p.Spec.SuggestedLabels
}

// V1 returns V1 version of the resource
func (p *ProvisionTokenV2) V1() *ProvisionTokenV1 {
return &ProvisionTokenV1{
Expand Down
1,605 changes: 827 additions & 778 deletions api/types/types.pb.go

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions api/types/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,13 @@ message ProvisionTokenSpecV2 {
[ (gogoproto.jsontag) = "join_method", (gogoproto.casttype) = "JoinMethod" ];
// BotName is the name of the bot this token grants access to, if any
string BotName = 5 [ (gogoproto.jsontag) = "bot_name,omitempty" ];
// SuggestedLabels is a set of labels that resources should set when using this token to enroll
// themselves in the cluster
wrappers.LabelValues SuggestedLabels = 6 [
(gogoproto.nullable) = false,
(gogoproto.jsontag) = "suggested_labels,omitempty",
(gogoproto.customtype) = "Labels"
];
}

// StaticTokensV2 implements the StaticTokens interface.
Expand Down
10 changes: 10 additions & 0 deletions lib/web/apiserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2171,6 +2171,16 @@ func TestTokenGeneration(t *testing.T) {
err = json.Unmarshal(re.Bytes(), &responseToken)
require.NoError(t, err)

require.NotEmpty(t, responseToken.SuggestedLabels)
require.Condition(t, func() (success bool) {
for _, uiLabel := range responseToken.SuggestedLabels {
if uiLabel.Name == types.InternalResourceIDLabel && uiLabel.Value != "" {
return true
}
}
return false
})

// generated token roles should match the requested ones
generatedToken, err := proxy.auth.Auth().GetToken(context.Background(), responseToken.ID)
require.NoError(t, err)
Expand Down
44 changes: 40 additions & 4 deletions lib/web/join_tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,17 @@ import (
"strings"
"time"

"github.com/google/uuid"
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/types"
apiutils "github.com/gravitational/teleport/api/utils"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/httplib"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/web/scripts"
"github.com/gravitational/teleport/lib/web/ui"
"github.com/gravitational/trace"
"github.com/julienschmidt/httprouter"
"k8s.io/apimachinery/pkg/util/validation"
Expand All @@ -52,6 +55,8 @@ type nodeJoinToken struct {
Expiry time.Time `json:"expiry,omitempty"`
// Method is the join method that the token supports
Method types.JoinMethod `json:"method"`
// SuggestedLabels contains the set of labels we expect the node to set when using this token
SuggestedLabels []ui.Label `json:"suggestedLabels,omitempty"`
}

// scriptSettings is used to hold values which are passed into the function that
Expand Down Expand Up @@ -115,6 +120,20 @@ func (h *Handler) createTokenHandle(w http.ResponseWriter, r *http.Request, para
expires = time.Now().UTC().Add(defaults.NodeJoinTokenTTL)
}

// If using the automatic method to add a Node, the `install.sh` will add the token's suggested labels
// as part of the initial Labels configuration for that Node
// Script install-node.sh:
// ...
// $ teleport configure ... --labels <suggested_label=value>,<suggested_label=value> ...
// ...
//
// We create an ID and return it as part of the Token, so the UI can use this ID to query the Node that joined using this token
// WebUI can then query the resources by this id and answer the question:
// - Which Node joined the cluster from this token Y?
req.SuggestedLabels = types.Labels{
types.InternalResourceIDLabel: apiutils.Strings{uuid.NewString()},
}

provisionToken, err := types.NewProvisionTokenFromSpec(tokenName, expires, req)
if err != nil {
return nil, trace.Wrap(err)
Expand All @@ -125,10 +144,20 @@ func (h *Handler) createTokenHandle(w http.ResponseWriter, r *http.Request, para
return nil, trace.Wrap(err)
}

suggestedLabels := make([]ui.Label, 0, len(req.SuggestedLabels))

for labelKey, labelValues := range req.SuggestedLabels {
suggestedLabels = append(suggestedLabels, ui.Label{
Name: labelKey,
Value: strings.Join(labelValues, " "),
})
}

return &nodeJoinToken{
ID: tokenName,
Expiry: expires,
Method: provisionToken.GetJoinMethod(),
ID: tokenName,
Expiry: expires,
Method: provisionToken.GetJoinMethod(),
SuggestedLabels: suggestedLabels,
}, nil
}

Expand Down Expand Up @@ -247,7 +276,7 @@ func getJoinScript(ctx context.Context, settings scriptSettings, m nodeAPIGetter

// The provided token can be attacker controlled, so we must validate
// it with the backend before using it to generate the script.
_, err := m.GetToken(ctx, settings.token)
token, err := m.GetToken(ctx, settings.token)
if err != nil {
return "", trace.BadParameter("invalid token")
}
Expand Down Expand Up @@ -278,6 +307,12 @@ func getJoinScript(ctx context.Context, settings scriptSettings, m nodeAPIGetter
return "", trace.Wrap(err)
}

labelsList := []string{}
for labelKey, labelValues := range token.GetSuggestedLabels() {
labels := strings.Join(labelValues, " ")
labelsList = append(labelsList, fmt.Sprintf("%s=%s", labelKey, labels))
}

var buf bytes.Buffer
// If app install mode is requested but parameters are blank for some reason,
// we need to return an error.
Expand Down Expand Up @@ -307,6 +342,7 @@ func getJoinScript(ctx context.Context, settings scriptSettings, m nodeAPIGetter
"appName": settings.appName,
"appURI": settings.appURI,
"joinMethod": settings.joinMethod,
"labels": strings.Join(labelsList, ","),
})
if err != nil {
return "", trace.Wrap(err)
Expand Down
17 changes: 17 additions & 0 deletions lib/web/join_tokens_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ package web
import (
"context"
"encoding/hex"
"fmt"
"testing"
"time"

"github.com/stretchr/testify/require"

"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/utils"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/fixtures"
"github.com/gravitational/trace"
Expand Down Expand Up @@ -286,6 +288,7 @@ func toHex(s string) string { return hex.EncodeToString([]byte(s)) }
func TestGetNodeJoinScript(t *testing.T) {
validToken := "f18da1c9f6630a51e8daf121e7451daa"
validIAMToken := "valid-iam-token"
internalResourceID := "967d38ff-7a61-4f42-bd2d-c61965b44db0"

m := &mockedNodeAPIGetter{
mockGetProxyServers: func() ([]types.Server, error) {
Expand All @@ -304,6 +307,11 @@ func TestGetNodeJoinScript(t *testing.T) {
Metadata: types.Metadata{
Name: token,
},
Spec: types.ProvisionTokenSpecV2{
SuggestedLabels: types.Labels{
types.InternalResourceIDLabel: utils.Strings{internalResourceID},
},
},
}, nil
}
return nil, trace.NotFound("token does not exist")
Expand Down Expand Up @@ -362,6 +370,15 @@ func TestGetNodeJoinScript(t *testing.T) {
require.Contains(t, script, "JOIN_METHOD='iam'")
},
},
{
desc: "internal resourceid label",
settings: scriptSettings{token: validToken},
errAssert: require.NoError,
extraAssertions: func(script string) {
require.Contains(t, script, "--labels ")
require.Contains(t, script, fmt.Sprintf("%s=%s", types.InternalResourceIDLabel, internalResourceID))
},
},
} {
t.Run(test.desc, func(t *testing.T) {
script, err := getJoinScript(context.Background(), test.settings, m)
Expand Down
9 changes: 8 additions & 1 deletion lib/web/scripts/node-join/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@ TARGET_PORT='{{.port}}'
JOIN_TOKEN='{{.token}}'
JOIN_METHOD='{{.joinMethod}}'
JOIN_METHOD_FLAG=""
[ ! -z "$JOIN_METHOD" ] && JOIN_METHOD_FLAG="--join-method ${JOIN_METHOD}"
[ -n "$JOIN_METHOD" ] && JOIN_METHOD_FLAG="--join-method ${JOIN_METHOD}"

# inject labels into the configuration
LABELS='{{.labels}}'
LABELS_FLAG=""
[ -n "$LABELS" ] && LABELS_FLAG=(--labels "${LABELS}")

# When all stanza generators have been updated to use the new
# `teleport <service> configure` commands CA_PIN_HASHES can be removed along
# with the script passing it in in `join_tokens.go`.
Expand Down Expand Up @@ -439,6 +445,7 @@ install_teleport_node_config() {
${JOIN_METHOD_FLAG} \
--ca-pin ${CA_PINS} \
--auth-server ${TARGET_HOSTNAME}:${TARGET_PORT} \
"${LABELS_FLAG[@]}" \
--output ${TELEPORT_CONFIG_PATH}
}
# checks whether the given host is running MacOS
Expand Down