mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-24 16:17:11 +08:00
Make tctl auth sign to write out kube TLS server name if TLS routing is enabled (#15536)
* Make tctl write out kube tls server name if tls routing is enabled Fixes #14489 * Extract kube TLS name generation function to shareable location We want tctl to be able to use this function as well, so we're locating it in a place where both tctl and tsh can use it. Also changing input parameter to k8s host, since it doesn't really care about port number.
This commit is contained in:
@@ -1345,6 +1345,25 @@ func (c *Config) DatabaseProxyHostPort(db tlsca.RouteToDatabase) (string, int) {
|
||||
return c.WebProxyHostPort()
|
||||
}
|
||||
|
||||
// GetKubeTLSServerName returns k8s server name used in KUBECONFIG to leverage TLS Routing.
|
||||
func GetKubeTLSServerName(k8host string) string {
|
||||
isIPFormat := net.ParseIP(k8host) != nil
|
||||
|
||||
if k8host == "" || isIPFormat {
|
||||
// If proxy is configured without public_addr set the ServerName to the 'kube.teleport.cluster.local' value.
|
||||
// The k8s server name needs to be a valid hostname but when public_addr is missing from proxy settings
|
||||
// the web_listen_addr is used thus webHost will contain local proxy IP address like: 0.0.0.0 or 127.0.0.1
|
||||
// TODO(smallinsky) UPGRADE IN 10.0. Switch to KubeTeleportProxyALPNPrefix instead.
|
||||
return addSubdomainPrefix(constants.APIDomain, constants.KubeSNIPrefix)
|
||||
}
|
||||
// TODO(smallinsky) UPGRADE IN 10.0. Switch to KubeTeleportProxyALPNPrefix instead.
|
||||
return addSubdomainPrefix(k8host, constants.KubeSNIPrefix)
|
||||
}
|
||||
|
||||
func addSubdomainPrefix(domain, prefix string) string {
|
||||
return fmt.Sprintf("%s%s", prefix, domain)
|
||||
}
|
||||
|
||||
// ProxyHost returns the hostname of the proxy server (without any port numbers)
|
||||
func ProxyHost(proxyHost string) string {
|
||||
host, _, err := net.SplitHostPort(proxyHost)
|
||||
|
||||
@@ -425,6 +425,42 @@ func TestWebProxyHostPort(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetKubeTLSServerName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
kubeProxyAddr string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "ipv4 format, API domain should be used",
|
||||
kubeProxyAddr: "127.0.0.1",
|
||||
want: "kube.teleport.cluster.local",
|
||||
},
|
||||
{
|
||||
name: "empty host, API domain should be used",
|
||||
kubeProxyAddr: "",
|
||||
want: "kube.teleport.cluster.local",
|
||||
},
|
||||
{
|
||||
name: "ipv4 unspecified, API domain should be used ",
|
||||
kubeProxyAddr: "0.0.0.0",
|
||||
want: "kube.teleport.cluster.local",
|
||||
},
|
||||
{
|
||||
name: "valid hostname",
|
||||
kubeProxyAddr: "example.com",
|
||||
want: "kube.example.com",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := GetKubeTLSServerName(tt.kubeProxyAddr)
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestApplyProxySettings validates that settings received from the proxy's
|
||||
// ping endpoint are correctly applied to Teleport client.
|
||||
func TestApplyProxySettings(t *testing.T) {
|
||||
|
||||
@@ -144,6 +144,8 @@ type WriteConfig struct {
|
||||
// KubeProxyAddr is the public address of the proxy with its kubernetes
|
||||
// port. KubeProxyAddr is only used when Format is FormatKubernetes.
|
||||
KubeProxyAddr string
|
||||
// KubeTLSServerName is the SNI host value passed to the server.
|
||||
KubeTLSServerName string
|
||||
// OverwriteDestination forces all existing destination files to be
|
||||
// overwritten. When false, user will be prompted for confirmation of
|
||||
// overwrite first.
|
||||
@@ -331,6 +333,7 @@ func Write(cfg WriteConfig) (filesWritten []string, err error) {
|
||||
TeleportClusterName: cfg.Key.ClusterName,
|
||||
ClusterAddr: cfg.KubeProxyAddr,
|
||||
Credentials: cfg.Key,
|
||||
TLSServerName: cfg.KubeTLSServerName,
|
||||
}); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -160,9 +160,10 @@ func TestWrite(t *testing.T) {
|
||||
cfg.OutputPath = filepath.Join(outputDir, "kubeconfig")
|
||||
cfg.Format = FormatKubernetes
|
||||
cfg.KubeProxyAddr = "far.away.cluster"
|
||||
cfg.KubeTLSServerName = "kube.far.away.cluster"
|
||||
_, err = Write(cfg)
|
||||
require.NoError(t, err)
|
||||
assertKubeconfigContents(t, cfg.OutputPath, key.ClusterName, "far.away.cluster")
|
||||
assertKubeconfigContents(t, cfg.OutputPath, key.ClusterName, "far.away.cluster", cfg.KubeTLSServerName)
|
||||
}
|
||||
|
||||
func TestKubeconfigOverwrite(t *testing.T) {
|
||||
@@ -183,17 +184,18 @@ func TestKubeconfigOverwrite(t *testing.T) {
|
||||
cfg.KubeProxyAddr = "far.away.cluster"
|
||||
_, err = Write(cfg)
|
||||
require.NoError(t, err)
|
||||
assertKubeconfigContents(t, cfg.OutputPath, key.ClusterName, "far.away.cluster")
|
||||
assertKubeconfigContents(t, cfg.OutputPath, key.ClusterName, "far.away.cluster", "")
|
||||
|
||||
// Write a kubeconfig for a different cluster to the same file path. It
|
||||
// should be overwritten.
|
||||
cfg.KubeProxyAddr = "other.cluster"
|
||||
cfg.KubeTLSServerName = "kube.other.cluster"
|
||||
_, err = Write(cfg)
|
||||
require.NoError(t, err)
|
||||
assertKubeconfigContents(t, cfg.OutputPath, key.ClusterName, "other.cluster")
|
||||
assertKubeconfigContents(t, cfg.OutputPath, key.ClusterName, "other.cluster", cfg.KubeTLSServerName)
|
||||
}
|
||||
|
||||
func assertKubeconfigContents(t *testing.T, path, clusterName, serverAddr string) {
|
||||
func assertKubeconfigContents(t *testing.T, path, clusterName, serverAddr, kubeTLSName string) {
|
||||
t.Helper()
|
||||
|
||||
kc, err := kubeconfig.Load(path)
|
||||
@@ -203,4 +205,5 @@ func assertKubeconfigContents(t *testing.T, path, clusterName, serverAddr string
|
||||
require.Len(t, kc.Contexts, 1)
|
||||
require.Len(t, kc.Clusters, 1)
|
||||
require.Equal(t, kc.Clusters[clusterName].Server, serverAddr)
|
||||
require.Equal(t, kc.Clusters[clusterName].TLSServerName, kubeTLSName)
|
||||
}
|
||||
|
||||
@@ -680,12 +680,25 @@ func (a *AuthCommand) generateUserKeys(ctx context.Context, clusterAPI auth.Clie
|
||||
}
|
||||
key.TrustedCA = auth.AuthoritiesToTrustedCerts(hostCAs)
|
||||
|
||||
networkConfig, err := clusterAPI.GetClusterNetworkingConfig(ctx)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
kubeTLSServerName := ""
|
||||
// Is TLS routing enabled?
|
||||
if networkConfig.GetProxyListenerMode() == types.ProxyListenerMode_Multiplex {
|
||||
// If we're in multiplexed mode get SNI name for kube from single multiplexed proxy addr
|
||||
kubeTLSServerName = client.GetKubeTLSServerName(a.config.Proxy.WebAddr.Host())
|
||||
}
|
||||
|
||||
// write the cert+private key to the output:
|
||||
filesWritten, err := identityfile.Write(identityfile.WriteConfig{
|
||||
OutputPath: a.output,
|
||||
Key: key,
|
||||
Format: a.outputFormat,
|
||||
KubeProxyAddr: a.proxyAddr,
|
||||
KubeTLSServerName: kubeTLSServerName,
|
||||
OverwriteDestination: a.signOverwrite,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -274,11 +274,20 @@ type mockClient struct {
|
||||
appServices []types.AppServer
|
||||
dbServices []types.DatabaseServer
|
||||
appSession types.WebSession
|
||||
networkConfig types.ClusterNetworkingConfig
|
||||
}
|
||||
|
||||
func (c *mockClient) GetClusterName(...services.MarshalOption) (types.ClusterName, error) {
|
||||
return c.clusterName, nil
|
||||
}
|
||||
|
||||
func (c *mockClient) GetClusterNetworkingConfig(ctx context.Context, opts ...services.MarshalOption) (types.ClusterNetworkingConfig, error) {
|
||||
if c.networkConfig == nil {
|
||||
return &types.ClusterNetworkingConfigV2{}, nil
|
||||
}
|
||||
return c.networkConfig, nil
|
||||
}
|
||||
|
||||
func (c *mockClient) GenerateUserCerts(ctx context.Context, userCertsReq proto.UserCertsRequest) (*proto.Certs, error) {
|
||||
c.userCertsReq = &userCertsReq
|
||||
return c.userCerts, nil
|
||||
|
||||
+2
-23
@@ -20,7 +20,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"sort"
|
||||
@@ -30,7 +29,6 @@ import (
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
"github.com/gravitational/teleport/api/client/proto"
|
||||
"github.com/gravitational/teleport/api/constants"
|
||||
"github.com/gravitational/teleport/api/profile"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
apiutils "github.com/gravitational/teleport/api/utils"
|
||||
@@ -1012,32 +1010,13 @@ func fetchKubeStatus(ctx context.Context, tc *client.TeleportClient) (*kubernete
|
||||
}
|
||||
|
||||
if tc.TLSRoutingEnabled {
|
||||
kubeStatus.tlsServerName = getKubeTLSServerName(tc)
|
||||
k8host, _ := tc.KubeProxyHostPort()
|
||||
kubeStatus.tlsServerName = client.GetKubeTLSServerName(k8host)
|
||||
}
|
||||
|
||||
return kubeStatus, nil
|
||||
}
|
||||
|
||||
// getKubeTLSServerName returns k8s server name used in KUBECONFIG to leverage TLS Routing.
|
||||
func getKubeTLSServerName(tc *client.TeleportClient) string {
|
||||
k8host, _ := tc.KubeProxyHostPort()
|
||||
|
||||
isIPFormat := net.ParseIP(k8host) != nil
|
||||
if k8host == "" || isIPFormat {
|
||||
// If proxy is configured without public_addr set the ServerName to the 'kube.teleport.cluster.local' value.
|
||||
// The k8s server name needs to be a valid hostname but when public_addr is missing from proxy settings
|
||||
// the web_listen_addr is used thus webHost will contain local proxy IP address like: 0.0.0.0 or 127.0.0.1
|
||||
// TODO(smallinsky) UPGRADE IN 10.0. Switch to KubeTeleportProxyALPNPrefix instead.
|
||||
return addSubdomainPrefix(constants.APIDomain, constants.KubeSNIPrefix)
|
||||
}
|
||||
// TODO(smallinsky) UPGRADE IN 10.0. Switch to KubeTeleportProxyALPNPrefix instead.
|
||||
return addSubdomainPrefix(k8host, constants.KubeSNIPrefix)
|
||||
}
|
||||
|
||||
func addSubdomainPrefix(domain, prefix string) string {
|
||||
return fmt.Sprintf("%s%s", prefix, domain)
|
||||
}
|
||||
|
||||
// buildKubeConfigUpdate returns a kubeconfig.Values suitable for updating the user's kubeconfig
|
||||
// based on the CLI parameters and the given kubernetesStatus.
|
||||
func buildKubeConfigUpdate(cf *CLIConf, kubeStatus *kubernetesStatus) (*kubeconfig.Values, error) {
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
Copyright 2021 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/gravitational/teleport/lib/client"
|
||||
)
|
||||
|
||||
func TestGetKubeTLSServerName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
kubeProxyAddr string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "ipv4 format, API domain should be used",
|
||||
kubeProxyAddr: "127.0.0.1",
|
||||
want: "kube.teleport.cluster.local",
|
||||
},
|
||||
{
|
||||
name: "ipv4 with port, API domain should be used",
|
||||
kubeProxyAddr: "127.0.0.1:3080",
|
||||
want: "kube.teleport.cluster.local",
|
||||
},
|
||||
{
|
||||
name: "ipv4 missing host, API domain should be used",
|
||||
kubeProxyAddr: ":3080",
|
||||
want: "kube.teleport.cluster.local",
|
||||
},
|
||||
{
|
||||
name: "ipv4 unspecified, API domain should be used ",
|
||||
kubeProxyAddr: "0.0.0.0:3080",
|
||||
want: "kube.teleport.cluster.local",
|
||||
},
|
||||
{
|
||||
name: "valid hostname with port",
|
||||
kubeProxyAddr: "example.com:3080",
|
||||
want: "kube.example.com",
|
||||
},
|
||||
{
|
||||
name: "valid hostname without port",
|
||||
kubeProxyAddr: "example.com",
|
||||
want: "kube.example.com",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tc := &client.TeleportClient{
|
||||
Config: client.Config{
|
||||
WebProxyAddr: tt.kubeProxyAddr,
|
||||
},
|
||||
}
|
||||
got := getKubeTLSServerName(tc)
|
||||
require.Equal(t, tt.want, got)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user