diff --git a/lib/tbot/config/config.go b/lib/tbot/config/config.go
index 84992f5ea88df..f21f885520dd8 100644
--- a/lib/tbot/config/config.go
+++ b/lib/tbot/config/config.go
@@ -565,22 +565,7 @@ func ReadConfig(reader io.ReadSeeker, manualMigration bool) (*BotConfig, error)
switch version.Version {
case V1, "":
- if !manualMigration {
- log.WarnContext(
- context.TODO(), "Deprecated config version (V1) detected. Attempting to perform an on-the-fly in-memory migration to latest version. Please persist the config migration using `tbot migrate`.")
- }
- config := &configV1{}
- if err := decoder.Decode(config); err != nil {
- return nil, trace.BadParameter("failed parsing config file: %s", strings.ReplaceAll(err.Error(), "\n", ""))
- }
- latestConfig, err := config.migrate()
- if err != nil {
- return nil, trace.WithUserMessage(
- trace.Wrap(err, "migrating v1 config"),
- "Failed to migrate. Please contact Teleport support or use https://goteleport.com/docs/reference/machine-id/configuration/ to manually migrate your configuration.",
- )
- }
- return latestConfig, nil
+ return nil, trace.BadParameter("configuration version v1 is no longer supported")
case V2:
if manualMigration {
return nil, trace.BadParameter("configuration already the latest version. nothing to migrate.")
diff --git a/lib/tbot/config/migrate.go b/lib/tbot/config/migrate.go
deleted file mode 100644
index 925bfeff37ed6..0000000000000
--- a/lib/tbot/config/migrate.go
+++ /dev/null
@@ -1,426 +0,0 @@
-/*
- * Teleport
- * Copyright (C) 2023 Gravitational, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package config
-
-import (
- "slices"
- "time"
-
- "github.com/gravitational/trace"
- "gopkg.in/yaml.v3"
-
- "github.com/gravitational/teleport/lib/tbot/bot"
- "github.com/gravitational/teleport/lib/tbot/bot/destination"
- "github.com/gravitational/teleport/lib/tbot/bot/onboarding"
- "github.com/gravitational/teleport/lib/tbot/services/application"
- "github.com/gravitational/teleport/lib/tbot/services/database"
- "github.com/gravitational/teleport/lib/tbot/services/identity"
- "github.com/gravitational/teleport/lib/tbot/services/k8s"
- "github.com/gravitational/teleport/lib/tbot/services/ssh"
-)
-
-type destinationMixinV1 struct {
- Directory *destination.Directory `yaml:"directory"`
- Memory *destination.Memory `yaml:"memory"`
-}
-
-func (c *destinationMixinV1) migrate() (destination.Destination, error) {
- switch {
- case c.Memory != nil && c.Directory != nil:
- return nil, trace.BadParameter("both 'memory' and 'directory' cannot be specified")
- case c.Memory != nil:
- return c.Memory, nil
- case c.Directory != nil:
- return c.Directory, nil
- default:
- return nil, trace.BadParameter("at least one of `memory' and 'directory' must be specified")
- }
-}
-
-type storageConfigV1 struct {
- Mixin destinationMixinV1 `yaml:",inline"`
-}
-
-func (c *storageConfigV1) migrate() (*StorageConfig, error) {
- dest, err := c.Mixin.migrate()
- if err != nil {
- return nil, trace.Wrap(err, "migrating destination mixin")
- }
-
- return &StorageConfig{
- Destination: dest,
- }, nil
-}
-
-type configV1Database struct {
- Service string `yaml:"service"`
- Database string `yaml:"database"`
- Username string `yaml:"username"`
-}
-
-type configV1DestinationConfigHostCert struct {
- Principals []string `yaml:"principals"`
-}
-
-type configV1DestinationConfig struct {
- SSHClient map[string]any `yaml:"ssh_client"`
- Identity map[string]any `yaml:"identity"`
- TLS map[string]any `yaml:"tls"`
- TLSCAs map[string]any `yaml:"tls_cas"`
- Mongo map[string]any `yaml:"mongo"`
- Cockroach map[string]any `yaml:"cockroach"`
- Kubernetes map[string]any `yaml:"kubernetes"`
- SSHHostCert *configV1DestinationConfigHostCert `yaml:"ssh_host_cert"`
-}
-
-func (c *configV1DestinationConfig) UnmarshalYAML(node *yaml.Node) error {
- var simpleTemplate string
- if err := node.Decode(&simpleTemplate); err == nil {
- switch simpleTemplate {
- case TemplateSSHClientName:
- c.SSHClient = map[string]any{}
- case TemplateIdentityName:
- c.Identity = map[string]any{}
- case TemplateTLSName:
- c.TLS = map[string]any{}
- case TemplateTLSCAsName:
- c.TLSCAs = map[string]any{}
- case TemplateMongoName:
- c.Mongo = map[string]any{}
- case TemplateCockroachName:
- c.Cockroach = map[string]any{}
- case TemplateKubernetesName:
- c.Kubernetes = map[string]any{}
- case TemplateSSHHostCertName:
- c.SSHHostCert = &configV1DestinationConfigHostCert{}
- default:
- return trace.BadParameter("unrecognized config template %q", simpleTemplate)
- }
- return nil
- }
-
- // Fall back to the full struct; alias it to get standard unmarshal
- // behavior and avoid recursion
- type rawTemplate configV1DestinationConfig
- return trace.Wrap(node.Decode((*rawTemplate)(c)))
-}
-
-type configV1Destination struct {
- Mixin destinationMixinV1 `yaml:",inline"`
-
- Roles []string `yaml:"roles"`
- Configs []configV1DestinationConfig `yaml:"configs"`
-
- Database *configV1Database `yaml:"database"`
- KubernetesCluster string `yaml:"kubernetes_cluster"`
- App string `yaml:"app"`
- Cluster string `yaml:"cluster"`
-}
-
-func validateTemplates(configs []configV1DestinationConfig, allowedTypes []string, requiredTypes []string) error {
- var allConfiguredTypes []string
-
- configUnsupportedErr := func(typeName string) error {
- return trace.BadParameter("configuration options are not supported by migration for %s config template", typeName)
- }
-
- for _, templateConfig := range configs {
- var configuredTypes []string
- if templateConfig.SSHClient != nil {
- if len(templateConfig.SSHClient) > 0 {
- return configUnsupportedErr(TemplateSSHClientName)
- }
- configuredTypes = append(configuredTypes, TemplateSSHClientName)
- }
- if templateConfig.Identity != nil {
- if len(templateConfig.Identity) > 0 {
- return configUnsupportedErr(TemplateIdentityName)
- }
- configuredTypes = append(configuredTypes, TemplateIdentityName)
- }
- if templateConfig.TLS != nil {
- if len(templateConfig.TLS) > 0 {
- return configUnsupportedErr(TemplateTLSName)
- }
- configuredTypes = append(configuredTypes, TemplateTLSName)
- }
- if templateConfig.TLSCAs != nil {
- if len(templateConfig.TLSCAs) > 0 {
- return configUnsupportedErr(TemplateTLSCAsName)
- }
- configuredTypes = append(configuredTypes, TemplateTLSCAsName)
- }
- if templateConfig.Mongo != nil {
- if len(templateConfig.Mongo) > 0 {
- return configUnsupportedErr(TemplateMongoName)
- }
- configuredTypes = append(configuredTypes, TemplateMongoName)
- }
- if templateConfig.Cockroach != nil {
- if len(templateConfig.Cockroach) > 0 {
- return configUnsupportedErr(TemplateCockroachName)
- }
- configuredTypes = append(configuredTypes, TemplateCockroachName)
- }
- if templateConfig.Kubernetes != nil {
- if len(templateConfig.Kubernetes) > 0 {
- return configUnsupportedErr(TemplateKubernetesName)
- }
- configuredTypes = append(configuredTypes, TemplateKubernetesName)
- }
- if templateConfig.SSHHostCert != nil {
- if len(templateConfig.SSHHostCert.Principals) == 0 {
- return trace.BadParameter("no principals specified for %s config template", TemplateSSHHostCertName)
- }
- configuredTypes = append(configuredTypes, TemplateSSHHostCertName)
- }
-
- if len(configuredTypes) == 0 {
- return trace.BadParameter("config template must not be empty")
- }
- if len(configuredTypes) > 1 {
- return trace.BadParameter("config template must have exactly one configuration")
- }
-
- allConfiguredTypes = append(allConfiguredTypes, configuredTypes...)
- }
-
- // Ensure all types are allowed by the new output type
- for _, typeName := range allConfiguredTypes {
- if !slices.Contains(allowedTypes, typeName) {
- return trace.BadParameter("config template %q unsupported by new output type", typeName)
- }
- }
-
- // Ensure the required types are specified for the new output type
- for _, typeName := range requiredTypes {
- if !slices.Contains(allConfiguredTypes, typeName) {
- return trace.BadParameter("old config templates missing required template %s", typeName)
- }
- }
-
- // Check for any weird duplicates we can't handle correctly
- typeCounts := map[string]int{}
- for _, typeName := range allConfiguredTypes {
- typeCounts[typeName]++
- }
- for typeName, count := range typeCounts {
- if count > 1 {
- return trace.BadParameter("multiple config template entries found for %q", typeName)
- }
- }
-
- return nil
-}
-
-func (c *configV1Destination) migrate() (ServiceConfig, error) {
- dest, err := c.Mixin.migrate()
- if err != nil {
- return nil, trace.Wrap(err, "migrating destination")
- }
-
- appConfigured := c.App != ""
- databaseConfigured := c.Database != nil
- kubernetesConfigured := c.KubernetesCluster != ""
- hostCertConfigured := false
- for _, templateConfig := range c.Configs {
- if templateConfig.SSHHostCert != nil {
- hostCertConfigured = true
- }
- }
- outputTypesCount := 0
- for _, val := range []bool{appConfigured, databaseConfigured, kubernetesConfigured, hostCertConfigured} {
- if val {
- outputTypesCount++
- }
- }
- if outputTypesCount > 1 {
- return nil, trace.BadParameter("multiple potential output types detected, cannot determine correct type")
- }
-
- switch {
- case appConfigured:
- if err := validateTemplates(
- c.Configs,
- []string{TemplateTLSCAsName, TemplateTLSName, TemplateIdentityName},
- []string{},
- ); err != nil {
- return nil, trace.Wrap(err, "validating template configs")
- }
- specificTLSExtensions := false
- for _, templateConfig := range c.Configs {
- if templateConfig.TLS != nil {
- specificTLSExtensions = true
- break
- }
- }
- return &application.OutputConfig{
- Destination: dest,
- Roles: c.Roles,
- AppName: c.App,
- SpecificTLSExtensions: specificTLSExtensions,
- }, nil
- case databaseConfigured:
- if err := validateTemplates(
- c.Configs,
- []string{TemplateTLSCAsName, TemplateIdentityName, TemplateMongoName, TemplateCockroachName, TemplateTLSName},
- []string{},
- ); err != nil {
- return nil, trace.Wrap(err, "validating template configs")
- }
- format := database.UnspecifiedDatabaseFormat
- for _, templateConfig := range c.Configs {
- if templateConfig.Mongo != nil {
- if format != database.UnspecifiedDatabaseFormat {
- return nil, trace.BadParameter("multiple candidate formats for database output")
- }
- format = database.MongoDatabaseFormat
- }
- if templateConfig.Cockroach != nil {
- if format != database.UnspecifiedDatabaseFormat {
- return nil, trace.BadParameter("multiple candidate formats for database output")
- }
- format = database.CockroachDatabaseFormat
- }
- if templateConfig.TLS != nil {
- if format != database.UnspecifiedDatabaseFormat {
- return nil, trace.BadParameter("multiple candidate formats for database output")
- }
- format = database.TLSDatabaseFormat
- }
- }
- return &database.OutputConfig{
- Destination: dest,
- Roles: c.Roles,
- Format: format,
- Database: c.Database.Database,
- Service: c.Database.Service,
- Username: c.Database.Username,
- }, nil
- case kubernetesConfigured:
- if err := validateTemplates(
- c.Configs,
- []string{TemplateTLSCAsName, TemplateIdentityName, TemplateKubernetesName},
- []string{},
- ); err != nil {
- return nil, trace.Wrap(err, "validating template configs")
- }
- return &k8s.OutputV1Config{
- Destination: dest,
- Roles: c.Roles,
- KubernetesCluster: c.KubernetesCluster,
- }, nil
- case hostCertConfigured:
- if err := validateTemplates(
- c.Configs,
- []string{TemplateSSHHostCertName},
- []string{TemplateSSHHostCertName},
- ); err != nil {
- return nil, trace.Wrap(err, "validating template configs")
- }
-
- // Extract principals from template config
- principals := []string{}
- for _, c := range c.Configs {
- if c.SSHHostCert != nil {
- principals = c.SSHHostCert.Principals
- break
- }
- }
- return &ssh.HostOutputConfig{
- Destination: dest,
- Roles: c.Roles,
- Principals: principals,
- }, nil
- default:
- if err := validateTemplates(
- c.Configs,
- []string{TemplateTLSCAsName, TemplateIdentityName, TemplateSSHClientName},
- []string{},
- ); err != nil {
- return nil, trace.Wrap(err, "validating template configs")
- }
- return &identity.OutputConfig{
- Destination: dest,
- Roles: c.Roles,
- Cluster: c.Cluster,
- }, nil
- }
-}
-
-type configV1 struct {
- Onboarding onboarding.Config `yaml:"onboarding"`
- Debug bool `yaml:"debug"`
- AuthServer string `yaml:"auth_server"`
- CertificateTTL time.Duration `yaml:"certificate_ttl"`
- RenewalInterval time.Duration `yaml:"renewal_interval"`
- Oneshot bool `yaml:"oneshot"`
- FIPS bool `yaml:"fips"`
- DiagAddr string `yaml:"diag_addr"`
-
- Destinations []configV1Destination `yaml:"destinations"`
- StorageConfig *storageConfigV1 `yaml:"storage"`
-
- // This field doesn't exist in V1, but, it exists here so we can detect
- // a scenario where for some reason we're trying to migrate a V2 config
- // that's missing the version header.
- Outputs []any `yaml:"outputs"`
-}
-
-func (c *configV1) migrate() (*BotConfig, error) {
- if len(c.Outputs) > 0 {
- return nil, trace.BadParameter("config has been detected as potentially v1, but includes the v2 outputs field")
- }
-
- var storage *StorageConfig
- var err error
- if c.StorageConfig != nil {
- storage, err = c.StorageConfig.migrate()
- if err != nil {
- return nil, trace.Wrap(err, "migrating storage config")
- }
- }
-
- var outputs []ServiceConfig
- for _, d := range c.Destinations {
- o, err := d.migrate()
- if err != nil {
- return nil, trace.Wrap(err, "migrating output")
- }
- outputs = append(outputs, o)
- }
-
- return &BotConfig{
- Version: V2,
-
- Onboarding: c.Onboarding,
- Debug: c.Debug,
- AuthServer: c.AuthServer,
- CredentialLifetime: bot.CredentialLifetime{
- TTL: c.CertificateTTL,
- RenewalInterval: c.RenewalInterval,
- },
- Oneshot: c.Oneshot,
- FIPS: c.FIPS,
- DiagAddr: c.DiagAddr,
-
- Storage: storage,
- Services: outputs,
- }, nil
-}
diff --git a/lib/tbot/config/migrate_test.go b/lib/tbot/config/migrate_test.go
deleted file mode 100644
index a7d272f3f3f74..0000000000000
--- a/lib/tbot/config/migrate_test.go
+++ /dev/null
@@ -1,1159 +0,0 @@
-/*
- * Teleport
- * Copyright (C) 2023 Gravitational, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package config
-
-import (
- "bytes"
- "testing"
- "time"
-
- "github.com/stretchr/testify/require"
-
- "github.com/gravitational/teleport/api/types"
- "github.com/gravitational/teleport/lib/tbot/bot"
- "github.com/gravitational/teleport/lib/tbot/bot/destination"
- "github.com/gravitational/teleport/lib/tbot/bot/onboarding"
- "github.com/gravitational/teleport/lib/tbot/botfs"
- "github.com/gravitational/teleport/lib/tbot/services/application"
- "github.com/gravitational/teleport/lib/tbot/services/database"
- "github.com/gravitational/teleport/lib/tbot/services/identity"
- "github.com/gravitational/teleport/lib/tbot/services/k8s"
- "github.com/gravitational/teleport/lib/tbot/services/ssh"
-)
-
-func TestMigrate(t *testing.T) {
- tests := []struct {
- name string
- input string
- wantError string
- wantOutput *BotConfig
- }{
- {
- name: "very full config",
- input: `
-auth_server: example.teleport.sh:443
-oneshot: true
-debug: true
-certificate_ttl: 30m
-diag_addr: 127.0.0.1:621
-renewal_interval: 10m
-onboarding:
- join_method: "token"
- token: "my-token"
- ca_pins:
- - "sha256:my-pin"
-storage:
- directory:
- path: /path/storage
- acls: required
- symlinks: secure
-destinations:
-- directory:
- path: /path/destination
- roles: ["foo"]
- configs:
- - identity: {}
- - ssh_client
- - tls_cas
-- memory: true
- app: my-app
- configs:
- - identity: {}
- - tls_cas: {}
- - tls: {}
-- memory: true
- app: my-app
-- memory: {}
- kubernetes_cluster: my-kubernetes-cluster
- configs:
- - identity
- - tls_cas
- - kubernetes
-- memory: {}
- database:
- service: "my-db-service"
- database: "the-db"
- username: "alice"
-- memory: {}
- database:
- service: "my-db-service"
- configs:
- - mongo
-- memory: {}
- database:
- service: "my-db-service"
- configs:
- - tls
-- memory: {}
- database:
- service: "my-db-service"
- configs:
- - cockroach
-- memory: {}
- roles: ["foo"]
- configs:
- - ssh_host_cert:
- principals:
- - example.com
- - second.example.com
-`,
- wantOutput: &BotConfig{
- Version: V2,
- AuthServer: "example.teleport.sh:443",
- Oneshot: true,
- Debug: true,
- CredentialLifetime: bot.CredentialLifetime{
- RenewalInterval: time.Minute * 10,
- TTL: time.Minute * 30,
- },
- DiagAddr: "127.0.0.1:621",
- Onboarding: onboarding.Config{
- JoinMethod: types.JoinMethodToken,
- TokenValue: "my-token",
- CAPins: []string{
- "sha256:my-pin",
- },
- },
- Storage: &StorageConfig{
- Destination: &destination.Directory{
- Path: "/path/storage",
- ACLs: botfs.ACLRequired,
- Symlinks: botfs.SymlinksSecure,
- },
- },
- Services: ServiceConfigs{
- &identity.OutputConfig{
- Destination: &destination.Directory{
- Path: "/path/destination",
- },
- Roles: []string{"foo"},
- },
- &application.OutputConfig{
- Destination: &destination.Memory{},
- AppName: "my-app",
- SpecificTLSExtensions: true,
- },
- &application.OutputConfig{
- Destination: &destination.Memory{},
- AppName: "my-app",
- },
- &k8s.OutputV1Config{
- Destination: &destination.Memory{},
- KubernetesCluster: "my-kubernetes-cluster",
- },
- &database.OutputConfig{
- Destination: &destination.Memory{},
- Service: "my-db-service",
- Database: "the-db",
- Username: "alice",
- Format: database.UnspecifiedDatabaseFormat,
- },
- &database.OutputConfig{
- Destination: &destination.Memory{},
- Service: "my-db-service",
- Format: database.MongoDatabaseFormat,
- },
- &database.OutputConfig{
- Destination: &destination.Memory{},
- Service: "my-db-service",
- Format: database.TLSDatabaseFormat,
- },
- &database.OutputConfig{
- Destination: &destination.Memory{},
- Service: "my-db-service",
- Format: database.CockroachDatabaseFormat,
- },
- &ssh.HostOutputConfig{
- Destination: &destination.Memory{},
- Roles: []string{"foo"},
- Principals: []string{"example.com", "second.example.com"},
- },
- },
- },
- },
- // Backwards compat with GHA
- {
- name: "backwards compat with @teleport-actions/auth",
- input: `
-auth_server: example.teleport.sh:443
-oneshot: true
-debug: true
-onboarding:
- join_method: github
- token: my-token
-storage:
- memory: true
-destinations:
-- directory:
- path: /path/example
- symlinks: try-secure
- roles: []
-`,
- wantOutput: &BotConfig{
- Version: V2,
- AuthServer: "example.teleport.sh:443",
- Oneshot: true,
- Debug: true,
- Onboarding: onboarding.Config{
- JoinMethod: types.JoinMethodGitHub,
- TokenValue: "my-token",
- },
- Storage: &StorageConfig{
- Destination: &destination.Memory{},
- },
- Services: ServiceConfigs{
- &identity.OutputConfig{
- Destination: &destination.Directory{
- Path: "/path/example",
- Symlinks: "try-secure",
- },
- Roles: []string{},
- },
- },
- },
- },
- {
- name: "backwards compat with @teleport-actions/auth-k8s",
- input: `
-auth_server: example.teleport.sh:443
-oneshot: true
-debug: true
-onboarding:
- join_method: github
- token: my-token
-storage:
- memory: true
-destinations:
-- directory:
- path: /path/example
- symlinks: try-secure
- roles: []
- kubernetes_cluster: my-cluster
-`,
- wantOutput: &BotConfig{
- Version: V2,
- AuthServer: "example.teleport.sh:443",
- Oneshot: true,
- Debug: true,
- Onboarding: onboarding.Config{
- JoinMethod: types.JoinMethodGitHub,
- TokenValue: "my-token",
- },
- Storage: &StorageConfig{
- Destination: &destination.Memory{},
- },
- Services: ServiceConfigs{
- &k8s.OutputV1Config{
- Destination: &destination.Directory{
- Path: "/path/example",
- Symlinks: "try-secure",
- },
- Roles: []string{},
- KubernetesCluster: "my-cluster",
- },
- },
- },
- },
- {
- name: "backwards compat with @teleport-actions/auth-application",
- input: `
-auth_server: example.teleport.sh:443
-oneshot: true
-debug: true
-onboarding:
- join_method: github
- token: my-token
-storage:
- memory: true
-destinations:
-- directory:
- path: /path/example
- symlinks: try-secure
- roles: []
- app: my-app
-`,
- wantOutput: &BotConfig{
- Version: V2,
- AuthServer: "example.teleport.sh:443",
- Oneshot: true,
- Debug: true,
- Onboarding: onboarding.Config{
- JoinMethod: types.JoinMethodGitHub,
- TokenValue: "my-token",
- },
- Storage: &StorageConfig{
- Destination: &destination.Memory{},
- },
- Services: ServiceConfigs{
- &application.OutputConfig{
- Destination: &destination.Directory{
- Path: "/path/example",
- Symlinks: "try-secure",
- },
- Roles: []string{},
- AppName: "my-app",
- },
- },
- },
- },
- // Backwards compat with guides
- {
- name: "backwards compat with https://goteleport.com/docs/enroll-resources/machine-id/deployment/jenkins/",
- input: `
-auth_server: "auth.example.com:3025"
-onboarding:
- join_method: "token"
- token: "00000000000000000000000000000000"
- ca_pins:
- - "sha256:1111111111111111111111111111111111111111111111111111111111111111"
-storage:
- directory: /var/lib/teleport/bot
-destinations:
- - directory: /opt/machine-id
-`,
- wantOutput: &BotConfig{
- Version: V2,
- AuthServer: "auth.example.com:3025",
- Onboarding: onboarding.Config{
- JoinMethod: types.JoinMethodToken,
- TokenValue: "00000000000000000000000000000000",
- CAPins: []string{
- "sha256:1111111111111111111111111111111111111111111111111111111111111111",
- },
- },
- Storage: &StorageConfig{
- Destination: &destination.Directory{
- Path: "/var/lib/teleport/bot",
- },
- },
- Services: ServiceConfigs{
- &identity.OutputConfig{
- Destination: &destination.Directory{
- Path: "/opt/machine-id",
- },
- },
- },
- },
- },
- {
- name: "backwards compat with https://goteleport.com/docs/enroll-resources/machine-id/access-guides/databases/",
- input: `
-auth_server: "teleport.example.com:443"
-onboarding:
- join_method: "token"
- token: "abcd123-insecure-do-not-use-this"
- ca_pins:
- - "sha256:abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678"
-storage:
- directory: /var/lib/teleport/bot
-destinations:
- - directory: /opt/machine-id
-
- database:
- service: example-server
- username: alice
- database: example
-`,
- wantOutput: &BotConfig{
- Version: V2,
- AuthServer: "teleport.example.com:443",
- Onboarding: onboarding.Config{
- JoinMethod: types.JoinMethodToken,
- TokenValue: "abcd123-insecure-do-not-use-this",
- CAPins: []string{
- "sha256:abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678",
- },
- },
- Storage: &StorageConfig{
- Destination: &destination.Directory{
- Path: "/var/lib/teleport/bot",
- },
- },
- Services: ServiceConfigs{
- &database.OutputConfig{
- Destination: &destination.Directory{
- Path: "/opt/machine-id",
- },
- Service: "example-server",
- Username: "alice",
- Database: "example",
- },
- },
- },
- },
- {
- name: "backwards compat with https://goteleport.com/docs/enroll-resources/machine-id/access-guides/databases/ - mongo",
- input: `
-auth_server: "teleport.example.com:443"
-onboarding:
- join_method: "token"
- token: "abcd123-insecure-do-not-use-this"
- ca_pins:
- - "sha256:abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678"
-storage:
- directory: /var/lib/teleport/bot
-destinations:
- - directory: /opt/machine-id
-
- database:
- service: example-server
- username: alice
- database: example
-
- # If using MongoDB, be sure to include the Mongo-formatted certificates:
- configs:
- - mongo
-`,
- wantOutput: &BotConfig{
- Version: V2,
- AuthServer: "teleport.example.com:443",
- Onboarding: onboarding.Config{
- JoinMethod: types.JoinMethodToken,
- TokenValue: "abcd123-insecure-do-not-use-this",
- CAPins: []string{
- "sha256:abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678",
- },
- },
- Storage: &StorageConfig{
- Destination: &destination.Directory{
- Path: "/var/lib/teleport/bot",
- },
- },
- Services: ServiceConfigs{
- &database.OutputConfig{
- Destination: &destination.Directory{
- Path: "/opt/machine-id",
- },
- Format: database.MongoDatabaseFormat,
- Service: "example-server",
- Username: "alice",
- Database: "example",
- },
- },
- },
- },
- {
- name: "backwards compat with https://goteleport.com/docs/enroll-resources/machine-id/access-guides/databases/ - cockroach",
- input: `
-auth_server: "teleport.example.com:443"
-onboarding:
- join_method: "token"
- token: "abcd123-insecure-do-not-use-this"
- ca_pins:
- - "sha256:abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678"
-storage:
- directory: /var/lib/teleport/bot
-destinations:
- - directory: /opt/machine-id
-
- database:
- service: example-server
- username: alice
- database: example
-
- configs:
- - cockroach
-`,
- wantOutput: &BotConfig{
- Version: V2,
- AuthServer: "teleport.example.com:443",
- Onboarding: onboarding.Config{
- JoinMethod: types.JoinMethodToken,
- TokenValue: "abcd123-insecure-do-not-use-this",
- CAPins: []string{
- "sha256:abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678",
- },
- },
- Storage: &StorageConfig{
- Destination: &destination.Directory{
- Path: "/var/lib/teleport/bot",
- },
- },
- Services: ServiceConfigs{
- &database.OutputConfig{
- Destination: &destination.Directory{
- Path: "/opt/machine-id",
- },
- Format: database.CockroachDatabaseFormat,
- Service: "example-server",
- Username: "alice",
- Database: "example",
- },
- },
- },
- },
- {
- name: "backwards compat with https://goteleport.com/docs/enroll-resources/machine-id/access-guides/databases/ - tls",
- input: `
-auth_server: "teleport.example.com:443"
-onboarding:
- join_method: "token"
- token: "abcd123-insecure-do-not-use-this"
- ca_pins:
- - "sha256:abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678"
-storage:
- directory: /var/lib/teleport/bot
-destinations:
- - directory: /opt/machine-id
-
- database:
- service: example-server
- username: alice
- database: example
-
- configs:
- - tls
-`,
- wantOutput: &BotConfig{
- Version: V2,
- AuthServer: "teleport.example.com:443",
- Onboarding: onboarding.Config{
- JoinMethod: types.JoinMethodToken,
- TokenValue: "abcd123-insecure-do-not-use-this",
- CAPins: []string{
- "sha256:abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678",
- },
- },
- Storage: &StorageConfig{
- Destination: &destination.Directory{
- Path: "/var/lib/teleport/bot",
- },
- },
- Services: ServiceConfigs{
- &database.OutputConfig{
- Destination: &destination.Directory{
- Path: "/opt/machine-id",
- },
- Format: database.TLSDatabaseFormat,
- Service: "example-server",
- Username: "alice",
- Database: "example",
- },
- },
- },
- },
- {
- name: "backwards compat with https://goteleport.com/docs/enroll-resources/machine-id - host-certificate",
- input: `
-onboarding:
- token: "1234abcd5678efgh9"
- ca_path: ""
- ca_pins:
- - sha256:1234abcd5678efgh910ijklmnop
- join_method: token
-storage:
- directory:
- path: /var/lib/teleport/bot
- symlinks: secure
- acls: try
-destinations:
- - directory:
- path: /opt/machine-id
- configs:
- - ssh_host_cert:
- principals: [nodename.my.domain.com]
-debug: false
-auth_server: example.teleport.sh:443
-certificate_ttl: 1h0m0s
-renewal_interval: 20m0s
-oneshot: false
-`,
- wantOutput: &BotConfig{
- Version: V2,
- AuthServer: "example.teleport.sh:443",
- Onboarding: onboarding.Config{
- JoinMethod: types.JoinMethodToken,
- TokenValue: "1234abcd5678efgh9",
- CAPins: []string{
- "sha256:1234abcd5678efgh910ijklmnop",
- },
- },
- CredentialLifetime: bot.CredentialLifetime{
- RenewalInterval: DefaultRenewInterval,
- TTL: DefaultCertificateTTL,
- },
- Storage: &StorageConfig{
- Destination: &destination.Directory{
- Path: "/var/lib/teleport/bot",
- Symlinks: "secure",
- ACLs: "try",
- },
- },
- Services: ServiceConfigs{
- &ssh.HostOutputConfig{
- Destination: &destination.Directory{
- Path: "/opt/machine-id",
- },
- Principals: []string{"nodename.my.domain.com"},
- },
- },
- },
- },
- {
- name: "backwards compat with https://goteleport.com/docs/enroll-resources/machine-id/access-guides/applications/",
- input: `
-auth_server: "teleport.example.com:443"
-onboarding:
- join_method: "token"
- token: "abcd123-insecure-do-not-use-this"
- ca_pins:
- - "sha256:abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678"
-storage:
- directory: /var/lib/teleport/bot
-destinations:
- - directory: /opt/machine-id
- app: grafana-example
-`,
- wantOutput: &BotConfig{
- Version: V2,
- AuthServer: "teleport.example.com:443",
- Onboarding: onboarding.Config{
- JoinMethod: types.JoinMethodToken,
- TokenValue: "abcd123-insecure-do-not-use-this",
- CAPins: []string{
- "sha256:abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678",
- },
- },
- Storage: &StorageConfig{
- Destination: &destination.Directory{
- Path: "/var/lib/teleport/bot",
- },
- },
- Services: ServiceConfigs{
- &application.OutputConfig{
- Destination: &destination.Directory{
- Path: "/opt/machine-id",
- },
- AppName: "grafana-example",
- },
- },
- },
- },
- {
- name: "backwards compat with https://goteleport.com/docs/enroll-resources/machine-id/access-guides/applications/ - with tls config",
- input: `
-auth_server: "teleport.example.com:443"
-onboarding:
- join_method: "token"
- token: "abcd123-insecure-do-not-use-this"
- ca_pins:
- - "sha256:abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678"
-storage:
- directory: /var/lib/teleport/bot
-destinations:
- - directory: /opt/machine-id
- app: grafana-example
-
- configs:
- - tls
-`,
- wantOutput: &BotConfig{
- Version: V2,
- AuthServer: "teleport.example.com:443",
- Onboarding: onboarding.Config{
- JoinMethod: types.JoinMethodToken,
- TokenValue: "abcd123-insecure-do-not-use-this",
- CAPins: []string{
- "sha256:abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678",
- },
- },
- Storage: &StorageConfig{
- Destination: &destination.Directory{
- Path: "/var/lib/teleport/bot",
- },
- },
- Services: ServiceConfigs{
- &application.OutputConfig{
- Destination: &destination.Directory{
- Path: "/opt/machine-id",
- },
- AppName: "grafana-example",
- SpecificTLSExtensions: true,
- },
- },
- },
- },
- {
- name: "backwards compat with https://goteleport.com/docs/enroll-resources/machine-id/access-guides/kubernetes/",
- input: `
-auth_server: "teleport.example.com:443"
-onboarding:
- join_method: "token"
- token: "abcd123-insecure-do-not-use-this"
- ca_pins:
- - "sha256:abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678"
-storage:
- directory: /var/lib/teleport/bot
-destinations:
- - directory: /opt/machine-id
- kubernetes_cluster: example-k8s-cluster
-`,
- wantOutput: &BotConfig{
- Version: V2,
- AuthServer: "teleport.example.com:443",
- Onboarding: onboarding.Config{
- JoinMethod: types.JoinMethodToken,
- TokenValue: "abcd123-insecure-do-not-use-this",
- CAPins: []string{
- "sha256:abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678abdc1245efgh5678",
- },
- },
- Storage: &StorageConfig{
- Destination: &destination.Directory{
- Path: "/var/lib/teleport/bot",
- },
- },
- Services: ServiceConfigs{
- &k8s.OutputV1Config{
- Destination: &destination.Directory{
- Path: "/opt/machine-id",
- },
- KubernetesCluster: "example-k8s-cluster",
- },
- },
- },
- },
- // Niche cases
- {
- name: "no storage config",
- input: `
-auth_server: "teleport.example.com:443"
-onboarding:
- join_method: "token"
- token: "abcd123-insecure-do-not-use-this"
-`,
- wantOutput: &BotConfig{
- Version: V2,
- AuthServer: "teleport.example.com:443",
- Onboarding: onboarding.Config{
- JoinMethod: types.JoinMethodToken,
- TokenValue: "abcd123-insecure-do-not-use-this",
- },
- Storage: nil,
- Outputs: nil,
- },
- },
- // Real-world cases
- {
- name: "real world 1",
- input: `
-auth_server: "teleport.example.com:443"
-onboarding:
- join_method: "iam"
- token: "iam-token-kube"
-storage:
- directory:
- path: /var/lib/teleport/bot
- symlinks: insecure
- acls: off
-debug: true
-destinations:
- - directory:
- path: /opt/machine-id
- symlinks: insecure
- acls: off
- - directory:
- path: /opt/machine-id/tools
- symlinks: insecure
- acls: off
- kubernetes_cluster: "tools"
-`,
- wantOutput: &BotConfig{
- Version: V2,
- AuthServer: "teleport.example.com:443",
- Onboarding: onboarding.Config{
- JoinMethod: types.JoinMethodIAM,
- TokenValue: "iam-token-kube",
- },
- Storage: &StorageConfig{
- Destination: &destination.Directory{
- Path: "/var/lib/teleport/bot",
- Symlinks: botfs.SymlinksInsecure,
- ACLs: botfs.ACLOff,
- },
- },
- Debug: true,
- Services: ServiceConfigs{
- &identity.OutputConfig{
- Destination: &destination.Directory{
- Path: "/opt/machine-id",
- Symlinks: botfs.SymlinksInsecure,
- ACLs: botfs.ACLOff,
- },
- },
- &k8s.OutputV1Config{
- Destination: &destination.Directory{
- Path: "/opt/machine-id/tools",
- Symlinks: botfs.SymlinksInsecure,
- ACLs: botfs.ACLOff,
- },
- KubernetesCluster: "tools",
- },
- },
- },
- },
- {
- name: "real world 2",
- input: `
-storage:
- directory:
- path: /var/tmp/teleport/bot
- symlinks: insecure
-
-destinations:
- - directory:
- path: /var/tmp/machine-id
- symlinks: insecure
-`,
- wantOutput: &BotConfig{
- Version: V2,
- Storage: &StorageConfig{
- Destination: &destination.Directory{
- Path: "/var/tmp/teleport/bot",
- Symlinks: botfs.SymlinksInsecure,
- },
- },
- Services: ServiceConfigs{
- &identity.OutputConfig{
- Destination: &destination.Directory{
- Path: "/var/tmp/machine-id",
- Symlinks: botfs.SymlinksInsecure,
- },
- },
- },
- },
- },
- {
- name: "real world 3",
- input: `
-Machine ID config created at /etc/tbot.yaml:
-auth_server: teleportvm.example.com:443
-onboarding:
- token: redacted
-storage:
- directory: /var/lib/teleport/bot
-
-destinations:
- - directory: /opt/machine-id
- roles: [access]
- database:
- service: self-hosted
- username: alice
- database: Payroll
-`,
- wantOutput: &BotConfig{
- Version: V2,
- AuthServer: "teleportvm.example.com:443",
- Onboarding: onboarding.Config{
- TokenValue: "redacted",
- },
- Storage: &StorageConfig{
- Destination: &destination.Directory{
- Path: "/var/lib/teleport/bot",
- },
- },
- Services: ServiceConfigs{
- &database.OutputConfig{
- Destination: &destination.Directory{
- Path: "/opt/machine-id",
- },
- Roles: []string{"access"},
- Service: "self-hosted",
- Username: "alice",
- Database: "Payroll",
- },
- },
- },
- },
- {
- name: "real world 4",
- input: `
-auth_server: "redacted.teleport.sh:443"
-onboarding:
- join_method: "iam"
- token: "redacted-scanner-token"
- ca_pins:
- - "sha256:redacted"
-storage:
- directory: /var/lib/teleport/bot
-destinations:
- - directory: /opt/machine-id
- kubernetes_cluster: devops
-`,
- wantOutput: &BotConfig{
- Version: V2,
- AuthServer: "redacted.teleport.sh:443",
- Onboarding: onboarding.Config{
- TokenValue: "redacted-scanner-token",
- JoinMethod: types.JoinMethodIAM,
- CAPins: []string{
- "sha256:redacted",
- },
- },
- Storage: &StorageConfig{
- Destination: &destination.Directory{
- Path: "/var/lib/teleport/bot",
- },
- },
- Services: ServiceConfigs{
- &k8s.OutputV1Config{
- Destination: &destination.Directory{
- Path: "/opt/machine-id",
- },
- KubernetesCluster: "devops",
- },
- },
- },
- },
- {
- name: "real world 5",
- input: `
-auth_server: "redacted.teleport.sh:443"
-onboarding:
- join_method: "iam"
- token: "redacted-argocd-token"
- ca_pins:
- - "sha256:redacted"
-storage:
- directory: /var/lib/teleport/bot
-destinations:
- - directory:
- path: /mount/redacted-prod-global
- acls: off
- kubernetes_cluster: redacted-prod-global
- - directory:
- path: /mount/redacted-prod-au
- acls: off
- kubernetes_cluster: redacted-prod-au
- - directory:
- path: /mount/redacted-prod-eu2
- acls: off
- kubernetes_cluster: redacted-prod-eu2
- - directory:
- path: /mount/redacted-prod-ca
- acls: off
- kubernetes_cluster: redacted-prod-ca
- - directory:
- path: /mount/redacted-prod-us
- acls: off
- kubernetes_cluster: redacted-prod-us
-`,
- wantOutput: &BotConfig{
- Version: V2,
- AuthServer: "redacted.teleport.sh:443",
- Onboarding: onboarding.Config{
- TokenValue: "redacted-argocd-token",
- JoinMethod: types.JoinMethodIAM,
- CAPins: []string{
- "sha256:redacted",
- },
- },
- Storage: &StorageConfig{
- Destination: &destination.Directory{
- Path: "/var/lib/teleport/bot",
- },
- },
- Services: ServiceConfigs{
- &k8s.OutputV1Config{
- Destination: &destination.Directory{
- Path: "/mount/redacted-prod-global",
- ACLs: botfs.ACLOff,
- },
- KubernetesCluster: "redacted-prod-global",
- },
- &k8s.OutputV1Config{
- Destination: &destination.Directory{
- Path: "/mount/redacted-prod-au",
- ACLs: botfs.ACLOff,
- },
- KubernetesCluster: "redacted-prod-au",
- },
- &k8s.OutputV1Config{
- Destination: &destination.Directory{
- Path: "/mount/redacted-prod-eu2",
- ACLs: botfs.ACLOff,
- },
- KubernetesCluster: "redacted-prod-eu2",
- },
- &k8s.OutputV1Config{
- Destination: &destination.Directory{
- Path: "/mount/redacted-prod-ca",
- ACLs: botfs.ACLOff,
- },
- KubernetesCluster: "redacted-prod-ca",
- },
- &k8s.OutputV1Config{
- Destination: &destination.Directory{
- Path: "/mount/redacted-prod-us",
- ACLs: botfs.ACLOff,
- },
- KubernetesCluster: "redacted-prod-us",
- },
- },
- },
- },
- {
- name: "real world 6",
- // up to 10 roles/destinations depending on the environment
- input: `
-auth_server: "redacted.teleport.sh:443"
-onboarding:
- join_method: "token"
- token: "redacted"
-
-storage:
- directory: "/var/lib/teleport/tbot"
-
-destinations:
- - directory:
- acls: required
- path: /path/to/role1_creds
- roles:
- - role1
- - directory:
- acls: required
- path: /path/to/role2_creds
- roles:
- - role2
- - directory:
- acls: required
- path: /path/to/roleN_creds
- roles:
- - roleN
-`,
- wantOutput: &BotConfig{
- Version: V2,
- AuthServer: "redacted.teleport.sh:443",
- Onboarding: onboarding.Config{
- TokenValue: "redacted",
- JoinMethod: types.JoinMethodToken,
- },
- Storage: &StorageConfig{
- Destination: &destination.Directory{
- Path: "/var/lib/teleport/tbot",
- },
- },
- Services: ServiceConfigs{
- &identity.OutputConfig{
- Destination: &destination.Directory{
- Path: "/path/to/role1_creds",
- ACLs: botfs.ACLRequired,
- },
- Roles: []string{"role1"},
- },
- &identity.OutputConfig{
- Destination: &destination.Directory{
- Path: "/path/to/role2_creds",
- ACLs: botfs.ACLRequired,
- },
- Roles: []string{"role2"},
- },
- &identity.OutputConfig{
- Destination: &destination.Directory{
- Path: "/path/to/roleN_creds",
- ACLs: botfs.ACLRequired,
- },
- Roles: []string{"roleN"},
- },
- },
- },
- },
- // Error cases
- {
- name: "storage config with no destination",
- input: `storage: {}`,
- wantError: "at least one of `memory' and 'directory' must be specified",
- },
- {
- name: "storage config with absurd destination",
- input: `
-storage:
- memory: true
- directory:
- path: /opt/machine-id`,
- wantError: "both 'memory' and 'directory' cannot be specified",
- },
- {
- name: "destination with duplicate config types",
- input: `
-destinations:
-- memory: true
- configs:
- - ssh_client
- - ssh_client: {}`,
- wantError: `multiple config template entries found for "ssh_client"`,
- },
- {
- name: "destination with unsupported config type",
- input: `
-destinations:
-- memory: true
- app: my-app
- configs:
- - kubernetes`,
- wantError: `config template "kubernetes" unsupported by new output type`,
- },
- {
- name: "destination with empty config type",
- input: `
-destinations:
-- memory: true
- configs:
- - {}`,
- wantError: `config template must not be empty`,
- },
- {
- name: "destination with indeterminate type",
- input: `
-destinations:
-- memory: true
- app: my-app
- kubernetes_cluster: my-cluster
-`,
- wantError: `multiple potential output types detected, cannot determine correct type`,
- },
- {
- name: "v2 config without version field",
- input: `
-outputs:
- - type: identity
- destination:
- type: memory
- - type: identity
- destination:
- type: memory
-`,
- wantError: "config has been detected as potentially v1, but includes the v2 outputs field",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- r := bytes.NewReader([]byte(tt.input))
- out, err := ReadConfig(r, true)
- if tt.wantError != "" {
- require.ErrorContains(t, err, tt.wantError)
- return
- }
- require.NoError(t, err)
- require.Equal(t, tt.wantOutput, out)
-
- })
- }
-}