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
9 changes: 9 additions & 0 deletions docs/pages/reference/machine-id/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,15 @@ renewal_interval: 15m
# invocation of `kubectl`. Defaults to `false`.
disable_exec_plugin: false

# context_name_template determines the format of context names in the generated
# kubeconfig. It is a Go template string that supports the following variables:
#
# - {{.ClusterName}} - Name of the Teleport cluster
# - {{.KubeName}} - Name of the Kubernetes cluster resource
#
# By default, the following template will be used: "{{.ClusterName}}-{{.KubeName}}"
context_name_template: "{{.KubeName}}"

# name optionally overrides the name of the service used in logs and the `/readyz`
# endpoint. It must only contain letters, numbers, hyphens, underscores, and plus
# symbols.
Expand Down
6 changes: 3 additions & 3 deletions lib/tbot/services/k8s/argocd_output_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (

const ArgoCDOutputServiceType = "kubernetes/argo-cd"

var defaultArgoClusterNameTemplate = kubeconfig.ContextName("{{.ClusterName}}", "{{.KubeName}}")
var defaultContextNameTemplate = kubeconfig.ContextName("{{.ClusterName}}", "{{.KubeName}}")

// ArgoCDOutputConfig contains configuration for the service that registers
// Kubernetes cluster credentials in Argo CD.
Expand Down Expand Up @@ -81,7 +81,7 @@ type ArgoCDOutputConfig struct {
// when Namespaces is non-empty).
ClusterResources bool `yaml:"cluster_resources,omitempty"`

// cluster_name_template determines the format of cluster names in Argo CD.
// ClusterNameTemplate determines the format of cluster names in Argo CD.
// It is a "text/template" string that supports the following variables:
//
// - {{.ClusterName}} - Name of the Teleport cluster
Expand Down Expand Up @@ -136,7 +136,7 @@ func (o *ArgoCDOutputConfig) CheckAndSetDefaults() error {
}

if o.ClusterNameTemplate == "" {
o.ClusterNameTemplate = defaultArgoClusterNameTemplate
o.ClusterNameTemplate = defaultContextNameTemplate
} else {
if _, err := kubeconfig.ContextNameFromTemplate(o.ClusterNameTemplate, "", ""); err != nil {
return trace.BadParameter("cluster_name_template is invalid: %v", err)
Expand Down
18 changes: 12 additions & 6 deletions lib/tbot/services/k8s/output_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ func (s *OutputV2Service) render(
if s.cfg.DisableExecPlugin {
// If they've disabled the exec plugin, we just write the credentials
// directly into the kubeconfig.
kubeCfg, err = generateKubeConfigV2WithoutPlugin(status)
kubeCfg, err = s.generateKubeConfigV2WithoutPlugin(status)
if err != nil {
return trace.Wrap(err)
}
Expand All @@ -369,7 +369,7 @@ func (s *OutputV2Service) render(
return trace.Wrap(err)
}

kubeCfg, err = generateKubeConfigV2WithPlugin(status, destinationDir.Path, executablePath)
kubeCfg, err = s.generateKubeConfigV2WithPlugin(status, destinationDir.Path, executablePath)
if err != nil {
return trace.Wrap(err)
}
Expand Down Expand Up @@ -400,7 +400,7 @@ func encodePathComponent(input string) string {
// generateKubeConfigWithPlugin creates a Kubernetes config object with the
// given cluster config, using the `tbot kube credentials` auth helper plugin to
// fetch refreshed certificate data at runtime.
func generateKubeConfigV2WithPlugin(ks *kubernetesStatusV2, destPath string, executablePath string) (*clientcmdapi.Config, error) {
func (o *OutputV2Service) generateKubeConfigV2WithPlugin(ks *kubernetesStatusV2, destPath string, executablePath string) (*clientcmdapi.Config, error) {
config := clientcmdapi.NewConfig()

// Implementation note: tsh/kube.go generates a kubeconfig with all
Expand Down Expand Up @@ -442,7 +442,10 @@ func generateKubeConfigV2WithPlugin(ks *kubernetesStatusV2, destPath string, exe
}

for i, cluster := range ks.kubernetesClusterNames {
contextName := kubeconfig.ContextName(ks.teleportClusterName, cluster)
contextName, err := kubeconfig.ContextNameFromTemplate(o.cfg.ContextNameTemplate, ks.teleportClusterName, cluster)
if err != nil {
return nil, trace.Wrap(err, "templating context name")
}

suffix := fmt.Sprintf("/v1/teleport/%s/%s", encodePathComponent(ks.teleportClusterName), encodePathComponent(cluster))
config.Clusters[contextName] = &clientcmdapi.Cluster{
Expand Down Expand Up @@ -476,7 +479,7 @@ func generateKubeConfigV2WithPlugin(ks *kubernetesStatusV2, destPath string, exe
return config, nil
}

func generateKubeConfigV2WithoutPlugin(ks *kubernetesStatusV2) (*clientcmdapi.Config, error) {
func (o *OutputV2Service) generateKubeConfigV2WithoutPlugin(ks *kubernetesStatusV2) (*clientcmdapi.Config, error) {
config := clientcmdapi.NewConfig()

// Configure the cluster.
Expand All @@ -496,7 +499,10 @@ func generateKubeConfigV2WithoutPlugin(ks *kubernetesStatusV2) (*clientcmdapi.Co
}

for i, cluster := range ks.kubernetesClusterNames {
contextName := kubeconfig.ContextName(ks.teleportClusterName, cluster)
contextName, err := kubeconfig.ContextNameFromTemplate(o.cfg.ContextNameTemplate, ks.teleportClusterName, cluster)
if err != nil {
return nil, trace.Wrap(err, "templating context name")
}

suffix := fmt.Sprintf("/v1/teleport/%s/%s", encodePathComponent(ks.teleportClusterName), encodePathComponent(cluster))
config.Clusters[contextName] = &clientcmdapi.Cluster{
Expand Down
19 changes: 19 additions & 0 deletions lib/tbot/services/k8s/output_v2_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/gravitational/trace"
"gopkg.in/yaml.v3"

"github.com/gravitational/teleport/lib/kube/kubeconfig"
"github.com/gravitational/teleport/lib/tbot/bot"
"github.com/gravitational/teleport/lib/tbot/bot/destination"
"github.com/gravitational/teleport/lib/tbot/internal"
Expand Down Expand Up @@ -57,6 +58,16 @@ type OutputV2Config struct {
// CredentialLifetime contains configuration for how long credentials will
// last and the frequency at which they'll be renewed.
CredentialLifetime bot.CredentialLifetime `yaml:",inline"`

// ContextNameTemplate determines the format of context names in the
// generated kubeconfig. It is a "text/template" string that supports the
// following variables:
//
// - {{.ClusterName}} - Name of the Teleport cluster
// - {{.KubeName}} - Name of the Kubernetes cluster resource
//
// By default, the following template will be used: "{{.ClusterName}}-{{.KubeName}}".
ContextNameTemplate string `yaml:"context_name_template,omitempty"`
}

// GetName returns the user-given name of the service, used for validation purposes.
Expand All @@ -82,6 +93,14 @@ func (o *OutputV2Config) CheckAndSetDefaults() error {
}
}

if o.ContextNameTemplate == "" {
o.ContextNameTemplate = defaultContextNameTemplate
} else {
if _, err := kubeconfig.ContextNameFromTemplate(o.ContextNameTemplate, "", ""); err != nil {
return trace.BadParameter("context_name_template is invalid: %v", err)
}
}

return trace.Wrap(o.Destination.CheckAndSetDefaults())
}

Expand Down
22 changes: 21 additions & 1 deletion lib/tbot/services/k8s/output_v2_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func TestKubernetesV2Output_YAML(t *testing.T) {
TTL: 1 * time.Minute,
RenewalInterval: 30 * time.Second,
},
ContextNameTemplate: "{{.KubeName}}",
},
},
{
Expand Down Expand Up @@ -84,6 +85,7 @@ func TestKubernetesV2Output_CheckAndSetDefaults(t *testing.T) {
Selectors: []*KubernetesSelector{
{Name: "foo", Labels: map[string]string{}},
},
ContextNameTemplate: "{{.KubeName}}",
}
},
},
Expand All @@ -97,6 +99,7 @@ func TestKubernetesV2Output_CheckAndSetDefaults(t *testing.T) {
"foo": "bar",
}},
},
ContextNameTemplate: "{{.KubeName}}",
}
},
},
Expand All @@ -108,6 +111,7 @@ func TestKubernetesV2Output_CheckAndSetDefaults(t *testing.T) {
Selectors: []*KubernetesSelector{
{Name: "foo"},
},
ContextNameTemplate: "{{.KubeName}}",
}
},
wantErr: "no destination configured for output",
Expand All @@ -116,7 +120,8 @@ func TestKubernetesV2Output_CheckAndSetDefaults(t *testing.T) {
name: "missing selectors",
in: func() *OutputV2Config {
return &OutputV2Config{
Destination: destination.NewMemory(),
Destination: destination.NewMemory(),
ContextNameTemplate: "{{.KubeName}}",
}
},
wantErr: "at least one selector must be provided",
Expand All @@ -129,6 +134,7 @@ func TestKubernetesV2Output_CheckAndSetDefaults(t *testing.T) {
Selectors: []*KubernetesSelector{
{},
},
ContextNameTemplate: "{{.KubeName}}",
}
},
wantErr: "selectors: one of 'name' and 'labels' must be specified",
Expand All @@ -146,10 +152,24 @@ func TestKubernetesV2Output_CheckAndSetDefaults(t *testing.T) {
},
},
},
ContextNameTemplate: "{{.KubeName}}",
}
},
wantErr: "selectors: only one of 'name' and 'labels' may be specified",
},
{
name: "invalid context_name_template",
in: func() *OutputV2Config {
return &OutputV2Config{
Destination: destination.NewMemory(),
Selectors: []*KubernetesSelector{
{Name: "foo", Labels: map[string]string{}},
},
ContextNameTemplate: "{{.InvalidVariable}}",
}
},
wantErr: "can't evaluate field InvalidVariable",
},
}
testCheckAndSetDefaults(t, tests)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ selectors:
default_namespace: foo-namespace
credential_ttl: 1m0s
renewal_interval: 30s
context_name_template: '{{.KubeName}}'
Loading