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:
Anton Miniailo
2022-08-17 22:36:51 +00:00
committed by GitHub
parent 76d6dc8215
commit 1e5826d16c
8 changed files with 89 additions and 104 deletions
+19
View File
@@ -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)
+36
View File
@@ -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) {
+3
View File
@@ -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)
}
+7 -4
View File
@@ -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)
}
+13
View File
@@ -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 {
+9
View File
@@ -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
View File
@@ -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) {
-77
View File
@@ -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)
})
}
}