Commit 2c944439 authored by Erik Wilson's avatar Erik Wilson

Refactor certs

parent c745be58
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kube-apiserver-kubelet-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:kubelet-api-admin
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: kube-apiserver
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
"github.com/rancher/k3s/pkg/cli/cmds" "github.com/rancher/k3s/pkg/cli/cmds"
"github.com/rancher/k3s/pkg/clientaccess" "github.com/rancher/k3s/pkg/clientaccess"
"github.com/rancher/k3s/pkg/daemons/config" "github.com/rancher/k3s/pkg/daemons/config"
"github.com/rancher/k3s/pkg/daemons/control"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/net" "k8s.io/apimachinery/pkg/util/net"
...@@ -82,6 +83,10 @@ func getNodeNamedCrt(nodeName, nodePasswordFile string) HTTPRequester { ...@@ -82,6 +83,10 @@ func getNodeNamedCrt(nodeName, nodePasswordFile string) HTTPRequester {
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode == http.StatusForbidden {
return nil, fmt.Errorf("Node password rejected, contents of '%s' may not match server passwd entry", nodePasswordFile)
}
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%s: %s", u, resp.Status) return nil, fmt.Errorf("%s: %s", u, resp.Status)
} }
...@@ -101,45 +106,55 @@ func ensureNodePassword(nodePasswordFile string) (string, error) { ...@@ -101,45 +106,55 @@ func ensureNodePassword(nodePasswordFile string) (string, error) {
return "", err return "", err
} }
nodePassword := hex.EncodeToString(password) nodePassword := hex.EncodeToString(password)
return nodePassword, ioutil.WriteFile(nodePasswordFile, []byte(nodePassword), 0600) return nodePassword, ioutil.WriteFile(nodePasswordFile, []byte(nodePassword+"\n"), 0600)
} }
func getNodeCert(nodeName, nodeCertFile, nodeKeyFile, nodePasswordFile string, info *clientaccess.Info) (*tls.Certificate, error) { func getServingCert(nodeName, servingCertFile, servingKeyFile, nodePasswordFile string, info *clientaccess.Info) (*tls.Certificate, error) {
nodeCert, err := Request("/v1-k3s/node.crt", info, getNodeNamedCrt(nodeName, nodePasswordFile)) servingCert, err := Request("/v1-k3s/serving-kubelet.crt", info, getNodeNamedCrt(nodeName, nodePasswordFile))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := ioutil.WriteFile(nodeCertFile, nodeCert, 0600); err != nil { if err := ioutil.WriteFile(servingCertFile, servingCert, 0600); err != nil {
return nil, errors.Wrapf(err, "failed to write node cert") return nil, errors.Wrapf(err, "failed to write node cert")
} }
nodeKey, err := clientaccess.Get("/v1-k3s/node.key", info) servingKey, err := clientaccess.Get("/v1-k3s/serving-kubelet.key", info)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := ioutil.WriteFile(nodeKeyFile, nodeKey, 0600); err != nil { if err := ioutil.WriteFile(servingKeyFile, servingKey, 0600); err != nil {
return nil, errors.Wrapf(err, "failed to write node key") return nil, errors.Wrapf(err, "failed to write node key")
} }
cert, err := tls.X509KeyPair(nodeCert, nodeKey) cert, err := tls.X509KeyPair(servingCert, servingKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &cert, nil return &cert, nil
} }
func writeNodeCA(dataDir string, nodeCert *tls.Certificate) (string, error) { func getHostFile(filename string, info *clientaccess.Info) error {
clientCABytes := pem.EncodeToMemory(&pem.Block{ basename := filepath.Base(filename)
Type: "CERTIFICATE", fileBytes, err := clientaccess.Get("/v1-k3s/"+basename, info)
Bytes: nodeCert.Certificate[1], if err != nil {
}) return err
}
clientCA := filepath.Join(dataDir, "client-ca.pem") if err := ioutil.WriteFile(filename, fileBytes, 0600); err != nil {
if err := ioutil.WriteFile(clientCA, clientCABytes, 0600); err != nil { return errors.Wrapf(err, "failed to write cert %s", filename)
return "", errors.Wrapf(err, "failed to write client CA")
} }
return nil
}
return clientCA, nil func getNodeNamedHostFile(filename, nodeName, nodePasswordFile string, info *clientaccess.Info) error {
basename := filepath.Base(filename)
fileBytes, err := Request("/v1-k3s/"+basename, info, getNodeNamedCrt(nodeName, nodePasswordFile))
if err != nil {
return err
}
if err := ioutil.WriteFile(filename, fileBytes, 0600); err != nil {
return errors.Wrapf(err, "failed to write cert %s", filename)
}
return nil
} }
func getHostnameAndIP(info cmds.Agent) (string, string, error) { func getHostnameAndIP(info cmds.Agent) (string, string, error) {
...@@ -169,17 +184,17 @@ func getHostnameAndIP(info cmds.Agent) (string, string, error) { ...@@ -169,17 +184,17 @@ func getHostnameAndIP(info cmds.Agent) (string, string, error) {
} }
func localAddress(controlConfig *config.Control) string { func localAddress(controlConfig *config.Control) string {
return fmt.Sprintf("127.0.0.1:%d", controlConfig.AdvertisePort) return fmt.Sprintf("127.0.0.1:%d", controlConfig.ProxyPort)
} }
func writeKubeConfig(envInfo *cmds.Agent, info clientaccess.Info, controlConfig *config.Control, nodeCert *tls.Certificate) (string, error) { func writeKubeConfig(envInfo *cmds.Agent, info clientaccess.Info, controlConfig *config.Control, tlsCert *tls.Certificate) (string, error) {
os.MkdirAll(envInfo.DataDir, 0700) os.MkdirAll(envInfo.DataDir, 0700)
kubeConfigPath := filepath.Join(envInfo.DataDir, "kubeconfig.yaml") kubeConfigPath := filepath.Join(envInfo.DataDir, "kubeconfig.yaml")
info.URL = "https://" + localAddress(controlConfig) info.URL = "https://" + localAddress(controlConfig)
info.CACerts = pem.EncodeToMemory(&pem.Block{ info.CACerts = pem.EncodeToMemory(&pem.Block{
Type: cert.CertificateBlockType, Type: cert.CertificateBlockType,
Bytes: nodeCert.Certificate[1], Bytes: tlsCert.Certificate[1],
}) })
return kubeConfigPath, info.WriteKubeConfig(kubeConfigPath) return kubeConfigPath, info.WriteKubeConfig(kubeConfigPath)
...@@ -253,36 +268,72 @@ func get(envInfo *cmds.Agent) (*config.Node, error) { ...@@ -253,36 +268,72 @@ func get(envInfo *cmds.Agent) (*config.Node, error) {
return nil, err return nil, err
} }
nodeCertFile := filepath.Join(envInfo.DataDir, "token-node.crt") hostLocal, err := exec.LookPath("host-local")
nodeKeyFile := filepath.Join(envInfo.DataDir, "token-node.key")
nodePasswordFile := filepath.Join(envInfo.DataDir, "node-password.txt")
nodeCert, err := getNodeCert(nodeName, nodeCertFile, nodeKeyFile, nodePasswordFile, info)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed to find host-local")
}
var flannelIface *sysnet.Interface
if !envInfo.NoFlannel && len(envInfo.FlannelIface) > 0 {
flannelIface, err = sysnet.InterfaceByName(envInfo.FlannelIface)
if err != nil {
return nil, errors.Wrapf(err, "unable to find interface")
}
}
proxyURL := "https://" + localAddress(controlConfig)
clientCAFile := filepath.Join(envInfo.DataDir, "client-ca.crt")
if err := getHostFile(clientCAFile, info); err != nil {
return nil, err return nil, err
} }
clientCA, err := writeNodeCA(envInfo.DataDir, nodeCert) serverCAFile := filepath.Join(envInfo.DataDir, "server-ca.crt")
if err != nil { if err := getHostFile(serverCAFile, info); err != nil {
return nil, err return nil, err
} }
kubeConfig, err := writeKubeConfig(envInfo, *info, controlConfig, nodeCert) servingKubeletCert := filepath.Join(envInfo.DataDir, "serving-kubelet.crt")
servingKubeletKey := filepath.Join(envInfo.DataDir, "serving-kubelet.key")
nodePasswordFile := filepath.Join(envInfo.DataDir, "node-password.txt")
servingCert, err := getServingCert(nodeName, servingKubeletCert, servingKubeletKey, nodePasswordFile, info)
if err != nil { if err != nil {
return nil, err return nil, err
} }
hostLocal, err := exec.LookPath("host-local") kubeconfigNode, err := writeKubeConfig(envInfo, *info, controlConfig, servingCert)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "failed to find host-local") return nil, err
} }
var flannelIface *sysnet.Interface clientKubeletCert := filepath.Join(envInfo.DataDir, "client-kubelet.crt")
if !envInfo.NoFlannel && len(envInfo.FlannelIface) > 0 { if err := getNodeNamedHostFile(clientKubeletCert, nodeName, nodePasswordFile, info); err != nil {
flannelIface, err = sysnet.InterfaceByName(envInfo.FlannelIface) return nil, err
if err != nil { }
return nil, errors.Wrapf(err, "unable to find interface")
} clientKubeletKey := filepath.Join(envInfo.DataDir, "client-kubelet.key")
if err := getHostFile(clientKubeletKey, info); err != nil {
return nil, err
}
kubeconfigKubelet := filepath.Join(envInfo.DataDir, "kubelet.kubeconfig")
if err := control.KubeConfig(kubeconfigKubelet, proxyURL, serverCAFile, clientKubeletCert, clientKubeletKey); err != nil {
return nil, err
}
clientKubeProxyCert := filepath.Join(envInfo.DataDir, "client-kube-proxy.crt")
if err := getHostFile(clientKubeProxyCert, info); err != nil {
return nil, err
}
clientKubeProxyKey := filepath.Join(envInfo.DataDir, "client-kube-proxy.key")
if err := getHostFile(clientKubeProxyKey, info); err != nil {
return nil, err
}
kubeconfigKubeproxy := filepath.Join(envInfo.DataDir, "kubeproxy.kubeconfig")
if err := control.KubeConfig(kubeconfigKubeproxy, proxyURL, serverCAFile, clientKubeProxyCert, clientKubeProxyKey); err != nil {
return nil, err
} }
nodeConfig := &config.Node{ nodeConfig := &config.Node{
...@@ -295,14 +346,16 @@ func get(envInfo *cmds.Agent) (*config.Node, error) { ...@@ -295,14 +346,16 @@ func get(envInfo *cmds.Agent) (*config.Node, error) {
nodeConfig.Images = filepath.Join(envInfo.DataDir, "images") nodeConfig.Images = filepath.Join(envInfo.DataDir, "images")
nodeConfig.AgentConfig.NodeIP = nodeIP nodeConfig.AgentConfig.NodeIP = nodeIP
nodeConfig.AgentConfig.NodeName = nodeName nodeConfig.AgentConfig.NodeName = nodeName
nodeConfig.AgentConfig.NodeCertFile = nodeCertFile nodeConfig.AgentConfig.ServingKubeletCert = servingKubeletCert
nodeConfig.AgentConfig.NodeKeyFile = nodeKeyFile nodeConfig.AgentConfig.ServingKubeletKey = servingKubeletKey
nodeConfig.AgentConfig.ClusterDNS = controlConfig.ClusterDNS nodeConfig.AgentConfig.ClusterDNS = controlConfig.ClusterDNS
nodeConfig.AgentConfig.ClusterDomain = controlConfig.ClusterDomain nodeConfig.AgentConfig.ClusterDomain = controlConfig.ClusterDomain
nodeConfig.AgentConfig.ResolvConf = locateOrGenerateResolvConf(envInfo) nodeConfig.AgentConfig.ResolvConf = locateOrGenerateResolvConf(envInfo)
nodeConfig.AgentConfig.CACertPath = clientCA nodeConfig.AgentConfig.ClientCA = clientCAFile
nodeConfig.AgentConfig.ListenAddress = "0.0.0.0" nodeConfig.AgentConfig.ListenAddress = "0.0.0.0"
nodeConfig.AgentConfig.KubeConfig = kubeConfig nodeConfig.AgentConfig.KubeConfigNode = kubeconfigNode
nodeConfig.AgentConfig.KubeConfigKubelet = kubeconfigKubelet
nodeConfig.AgentConfig.KubeConfigKubeProxy = kubeconfigKubeproxy
nodeConfig.AgentConfig.RootDir = filepath.Join(envInfo.DataDir, "kubelet") nodeConfig.AgentConfig.RootDir = filepath.Join(envInfo.DataDir, "kubelet")
nodeConfig.AgentConfig.PauseImage = envInfo.PauseImage nodeConfig.AgentConfig.PauseImage = envInfo.PauseImage
nodeConfig.CACerts = info.CACerts nodeConfig.CACerts = info.CACerts
...@@ -316,7 +369,7 @@ func get(envInfo *cmds.Agent) (*config.Node, error) { ...@@ -316,7 +369,7 @@ func get(envInfo *cmds.Agent) (*config.Node, error) {
nodeConfig.Containerd.Address = filepath.Join(nodeConfig.Containerd.State, "containerd.sock") nodeConfig.Containerd.Address = filepath.Join(nodeConfig.Containerd.State, "containerd.sock")
nodeConfig.Containerd.Template = filepath.Join(envInfo.DataDir, "etc/containerd/config.toml.tmpl") nodeConfig.Containerd.Template = filepath.Join(envInfo.DataDir, "etc/containerd/config.toml.tmpl")
nodeConfig.ServerAddress = serverURLParsed.Host nodeConfig.ServerAddress = serverURLParsed.Host
nodeConfig.Certificate = nodeCert nodeConfig.Certificate = servingCert
if !nodeConfig.NoFlannel { if !nodeConfig.NoFlannel {
nodeConfig.FlannelConf = filepath.Join(envInfo.DataDir, "etc/flannel/net-conf.json") nodeConfig.FlannelConf = filepath.Join(envInfo.DataDir, "etc/flannel/net-conf.json")
nodeConfig.AgentConfig.CNIBinDir = filepath.Dir(hostLocal) nodeConfig.AgentConfig.CNIBinDir = filepath.Dir(hostLocal)
......
...@@ -55,7 +55,7 @@ func Prepare(ctx context.Context, config *config.Node) error { ...@@ -55,7 +55,7 @@ func Prepare(ctx context.Context, config *config.Node) error {
func Run(ctx context.Context, config *config.Node) error { func Run(ctx context.Context, config *config.Node) error {
nodeName := config.AgentConfig.NodeName nodeName := config.AgentConfig.NodeName
restConfig, err := clientcmd.BuildConfigFromFlags("", config.AgentConfig.KubeConfig) restConfig, err := clientcmd.BuildConfigFromFlags("", config.AgentConfig.KubeConfigNode)
if err != nil { if err != nil {
return err return err
} }
...@@ -79,7 +79,7 @@ func Run(ctx context.Context, config *config.Node) error { ...@@ -79,7 +79,7 @@ func Run(ctx context.Context, config *config.Node) error {
} }
go func() { go func() {
err := flannel(ctx, config.FlannelIface, config.FlannelConf, config.AgentConfig.KubeConfig) err := flannel(ctx, config.FlannelIface, config.FlannelConf, config.AgentConfig.KubeConfigNode)
logrus.Fatalf("flannel exited: %v", err) logrus.Fatalf("flannel exited: %v", err)
}() }()
......
package proxy package proxy
import ( import (
"crypto/tls" "github.com/google/tcpproxy"
"net/http"
"github.com/pkg/errors"
"github.com/rancher/k3s/pkg/daemons/config" "github.com/rancher/k3s/pkg/daemons/config"
"github.com/rancher/k3s/pkg/proxy"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
func Run(config *config.Node) error { func Run(config *config.Node) error {
proxy, err := proxy.NewSimpleProxy(config.ServerAddress, config.CACerts, true) logrus.Infof("Starting proxy %s -> %s", config.LocalAddress, config.ServerAddress)
if err != nil { var proxy tcpproxy.Proxy
return err proxy.AddRoute(config.LocalAddress, tcpproxy.To(config.ServerAddress))
}
listener, err := tls.Listen("tcp", config.LocalAddress, &tls.Config{
Certificates: []tls.Certificate{
*config.Certificate,
},
})
if err != nil {
return errors.Wrap(err, "Failed to start tls listener")
}
go func() { go func() {
err := http.Serve(listener, proxy) err := proxy.Run()
logrus.Fatalf("TLS proxy stopped: %v", err) logrus.Fatalf("TLS proxy stopped: %v", err)
}() }()
return nil return nil
} }
...@@ -26,7 +26,7 @@ var ( ...@@ -26,7 +26,7 @@ var (
) )
func Setup(config *config.Node) error { func Setup(config *config.Node) error {
restConfig, err := clientcmd.BuildConfigFromFlags("", config.AgentConfig.KubeConfig) restConfig, err := clientcmd.BuildConfigFromFlags("", config.AgentConfig.KubeConfigNode)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -17,7 +17,7 @@ type Server struct { ...@@ -17,7 +17,7 @@ type Server struct {
DisableAgent bool DisableAgent bool
KubeConfigOutput string KubeConfigOutput string
KubeConfigMode string KubeConfigMode string
KnownIPs cli.StringSlice TLSSan cli.StringSlice
BindAddress string BindAddress string
ExtraAPIArgs cli.StringSlice ExtraAPIArgs cli.StringSlice
ExtraSchedulerArgs cli.StringSlice ExtraSchedulerArgs cli.StringSlice
...@@ -28,6 +28,8 @@ type Server struct { ...@@ -28,6 +28,8 @@ type Server struct {
StorageCAFile string StorageCAFile string
StorageCertFile string StorageCertFile string
StorageKeyFile string StorageKeyFile string
AdvertiseIP string
AdvertisePort int
} }
var ServerConfig Server var ServerConfig Server
...@@ -120,7 +122,7 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command { ...@@ -120,7 +122,7 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command {
cli.StringSliceFlag{ cli.StringSliceFlag{
Name: "tls-san", Name: "tls-san",
Usage: "Add additional hostname or IP as a Subject Alternative Name in the TLS cert", Usage: "Add additional hostname or IP as a Subject Alternative Name in the TLS cert",
Value: &ServerConfig.KnownIPs, Value: &ServerConfig.TLSSan,
}, },
cli.StringSliceFlag{ cli.StringSliceFlag{
Name: "kube-apiserver-arg", Name: "kube-apiserver-arg",
...@@ -172,6 +174,17 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command { ...@@ -172,6 +174,17 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command {
Destination: &ServerConfig.StorageKeyFile, Destination: &ServerConfig.StorageKeyFile,
EnvVar: "K3S_STORAGE_KEYFILE", EnvVar: "K3S_STORAGE_KEYFILE",
}, },
cli.StringFlag{
Name: "advertise-address",
Usage: "IP address that apiserver uses to advertise to members of the cluster",
Destination: &ServerConfig.AdvertiseIP,
},
cli.IntFlag{
Name: "advertise-port",
Usage: "Port that apiserver uses to advertise to members of the cluster",
Value: 0,
Destination: &ServerConfig.AdvertisePort,
},
NodeIPFlag, NodeIPFlag,
NodeNameFlag, NodeNameFlag,
DockerFlag, DockerFlag,
......
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
"k8s.io/apimachinery/pkg/util/net" "k8s.io/apimachinery/pkg/util/net"
"k8s.io/kubernetes/pkg/master"
"k8s.io/kubernetes/pkg/volume/csi" "k8s.io/kubernetes/pkg/volume/csi"
_ "github.com/go-sql-driver/mysql" // ensure we have mysql _ "github.com/go-sql-driver/mysql" // ensure we have mysql
...@@ -102,8 +103,16 @@ func run(app *cli.Context, cfg *cmds.Server) error { ...@@ -102,8 +103,16 @@ func run(app *cli.Context, cfg *cmds.Server) error {
serverConfig.Rootless = cfg.Rootless serverConfig.Rootless = cfg.Rootless
serverConfig.TLSConfig.HTTPSPort = cfg.HTTPSPort serverConfig.TLSConfig.HTTPSPort = cfg.HTTPSPort
serverConfig.TLSConfig.HTTPPort = cfg.HTTPPort serverConfig.TLSConfig.HTTPPort = cfg.HTTPPort
serverConfig.TLSConfig.KnownIPs = knownIPs(cfg.KnownIPs) for _, san := range knownIPs(cfg.TLSSan) {
addr := net2.ParseIP(san)
if addr != nil {
serverConfig.TLSConfig.KnownIPs = append(serverConfig.TLSConfig.KnownIPs, san)
} else {
serverConfig.TLSConfig.Domains = append(serverConfig.TLSConfig.Domains, san)
}
}
serverConfig.TLSConfig.BindAddress = cfg.BindAddress serverConfig.TLSConfig.BindAddress = cfg.BindAddress
serverConfig.ControlConfig.HTTPSPort = cfg.HTTPSPort
serverConfig.ControlConfig.ExtraAPIArgs = cfg.ExtraAPIArgs serverConfig.ControlConfig.ExtraAPIArgs = cfg.ExtraAPIArgs
serverConfig.ControlConfig.ExtraControllerArgs = cfg.ExtraControllerArgs serverConfig.ControlConfig.ExtraControllerArgs = cfg.ExtraControllerArgs
serverConfig.ControlConfig.ExtraSchedulerAPIArgs = cfg.ExtraSchedulerArgs serverConfig.ControlConfig.ExtraSchedulerAPIArgs = cfg.ExtraSchedulerArgs
...@@ -113,6 +122,15 @@ func run(app *cli.Context, cfg *cmds.Server) error { ...@@ -113,6 +122,15 @@ func run(app *cli.Context, cfg *cmds.Server) error {
serverConfig.ControlConfig.StorageCAFile = cfg.StorageCAFile serverConfig.ControlConfig.StorageCAFile = cfg.StorageCAFile
serverConfig.ControlConfig.StorageCertFile = cfg.StorageCertFile serverConfig.ControlConfig.StorageCertFile = cfg.StorageCertFile
serverConfig.ControlConfig.StorageKeyFile = cfg.StorageKeyFile serverConfig.ControlConfig.StorageKeyFile = cfg.StorageKeyFile
serverConfig.ControlConfig.AdvertiseIP = cfg.AdvertiseIP
serverConfig.ControlConfig.AdvertisePort = cfg.AdvertisePort
if serverConfig.ControlConfig.AdvertiseIP == "" && cmds.AgentConfig.NodeIP != "" {
serverConfig.ControlConfig.AdvertiseIP = cmds.AgentConfig.NodeIP
}
if serverConfig.ControlConfig.AdvertiseIP != "" {
serverConfig.TLSConfig.KnownIPs = append(serverConfig.TLSConfig.KnownIPs, serverConfig.ControlConfig.AdvertiseIP)
}
_, serverConfig.ControlConfig.ClusterIPRange, err = net2.ParseCIDR(cfg.ClusterCIDR) _, serverConfig.ControlConfig.ClusterIPRange, err = net2.ParseCIDR(cfg.ClusterCIDR)
if err != nil { if err != nil {
...@@ -123,6 +141,12 @@ func run(app *cli.Context, cfg *cmds.Server) error { ...@@ -123,6 +141,12 @@ func run(app *cli.Context, cfg *cmds.Server) error {
return errors.Wrapf(err, "Invalid CIDR %s: %v", cfg.ServiceCIDR, err) return errors.Wrapf(err, "Invalid CIDR %s: %v", cfg.ServiceCIDR, err)
} }
_, apiServerServiceIP, err := master.DefaultServiceIPRange(*serverConfig.ControlConfig.ServiceIPRange)
if err != nil {
return err
}
serverConfig.TLSConfig.KnownIPs = append(serverConfig.TLSConfig.KnownIPs, apiServerServiceIP.String())
// If cluster-dns CLI arg is not set, we set ClusterDNS address to be ServiceCIDR network + 10, // If cluster-dns CLI arg is not set, we set ClusterDNS address to be ServiceCIDR network + 10,
// i.e. when you set service-cidr to 192.168.0.0/16 and don't provide cluster-dns, it will be set to 192.168.0.10 // i.e. when you set service-cidr to 192.168.0.0/16 and don't provide cluster-dns, it will be set to 192.168.0.10
if cfg.ClusterDNS == "" { if cfg.ClusterDNS == "" {
......
...@@ -35,7 +35,7 @@ func kubeProxy(cfg *config.Agent) { ...@@ -35,7 +35,7 @@ func kubeProxy(cfg *config.Agent) {
argsMap := map[string]string{ argsMap := map[string]string{
"proxy-mode": "iptables", "proxy-mode": "iptables",
"healthz-bind-address": "127.0.0.1", "healthz-bind-address": "127.0.0.1",
"kubeconfig": cfg.KubeConfig, "kubeconfig": cfg.KubeConfigKubeProxy,
"cluster-cidr": cfg.ClusterCIDR.String(), "cluster-cidr": cfg.ClusterCIDR.String(),
} }
args := config.GetArgsList(argsMap, cfg.ExtraKubeProxyArgs) args := config.GetArgsList(argsMap, cfg.ExtraKubeProxyArgs)
...@@ -58,7 +58,7 @@ func kubelet(cfg *config.Agent) { ...@@ -58,7 +58,7 @@ func kubelet(cfg *config.Agent) {
"read-only-port": "0", "read-only-port": "0",
"allow-privileged": "true", "allow-privileged": "true",
"cluster-domain": cfg.ClusterDomain, "cluster-domain": cfg.ClusterDomain,
"kubeconfig": cfg.KubeConfig, "kubeconfig": cfg.KubeConfigKubelet,
"eviction-hard": "imagefs.available<5%,nodefs.available<5%", "eviction-hard": "imagefs.available<5%,nodefs.available<5%",
"eviction-minimum-reclaim": "imagefs.available=10%,nodefs.available=10%", "eviction-minimum-reclaim": "imagefs.available=10%,nodefs.available=10%",
"fail-swap-on": "false", "fail-swap-on": "false",
...@@ -95,13 +95,13 @@ func kubelet(cfg *config.Agent) { ...@@ -95,13 +95,13 @@ func kubelet(cfg *config.Agent) {
if cfg.ListenAddress != "" { if cfg.ListenAddress != "" {
argsMap["address"] = cfg.ListenAddress argsMap["address"] = cfg.ListenAddress
} }
if cfg.CACertPath != "" { if cfg.ClientCA != "" {
argsMap["anonymous-auth"] = "false" argsMap["anonymous-auth"] = "false"
argsMap["client-ca-file"] = cfg.CACertPath argsMap["client-ca-file"] = cfg.ClientCA
} }
if cfg.NodeCertFile != "" && cfg.NodeKeyFile != "" { if cfg.ServingKubeletCert != "" && cfg.ServingKubeletKey != "" {
argsMap["tls-cert-file"] = cfg.NodeCertFile argsMap["tls-cert-file"] = cfg.ServingKubeletCert
argsMap["tls-private-key-file"] = cfg.NodeKeyFile argsMap["tls-private-key-file"] = cfg.ServingKubeletKey
} }
if cfg.NodeName != "" { if cfg.NodeName != "" {
argsMap["hostname-override"] = cfg.NodeName argsMap["hostname-override"] = cfg.NodeName
......
...@@ -36,32 +36,41 @@ type Containerd struct { ...@@ -36,32 +36,41 @@ type Containerd struct {
} }
type Agent struct { type Agent struct {
NodeName string NodeName string
NodeCertFile string ClientKubeletCert string
NodeKeyFile string ClientKubeletKey string
ClusterCIDR net.IPNet ClientKubeProxyCert string
ClusterDNS net.IP ClientKubeProxyKey string
ClusterDomain string ServingKubeletCert string
ResolvConf string ServingKubeletKey string
RootDir string ClusterCIDR net.IPNet
KubeConfig string ClusterDNS net.IP
NodeIP string ClusterDomain string
RuntimeSocket string ResolvConf string
ListenAddress string RootDir string
CACertPath string KubeConfigNode string
CNIBinDir string KubeConfigKubelet string
CNIConfDir string KubeConfigKubeProxy string
ExtraKubeletArgs []string NodeIP string
ExtraKubeProxyArgs []string RuntimeSocket string
PauseImage string ListenAddress string
CNIPlugin bool ClientCA string
NodeTaints []string CNIBinDir string
NodeLabels []string CNIConfDir string
ExtraKubeletArgs []string
ExtraKubeProxyArgs []string
PauseImage string
CNIPlugin bool
NodeTaints []string
NodeLabels []string
} }
type Control struct { type Control struct {
AdvertisePort int AdvertisePort int
AdvertiseIP string
ListenPort int ListenPort int
HTTPSPort int
ProxyPort int
ClusterSecret string ClusterSecret string
ClusterIPRange *net.IPNet ClusterIPRange *net.IPNet
ServiceIPRange *net.IPNet ServiceIPRange *net.IPNet
...@@ -87,28 +96,44 @@ type Control struct { ...@@ -87,28 +96,44 @@ type Control struct {
} }
type ControlRuntime struct { type ControlRuntime struct {
TLSCert string ClientKubeAPICert string
TLSKey string ClientKubeAPIKey string
TLSCA string ClientCA string
TLSCAKey string ClientCAKey string
TokenCA string ServerCA string
TokenCAKey string ServerCAKey string
ServiceKey string ServiceKey string
PasswdFile string PasswdFile string
KubeConfigSystem string
KubeConfigAdmin string
NodeCert string KubeConfigController string
NodeKey string KubeConfigScheduler string
ClientToken string KubeConfigAPIServer string
NodeToken string
Handler http.Handler ServingKubeAPICert string
Tunnel http.Handler ServingKubeAPIKey string
Authenticator authenticator.Request ClientToken string
NodeToken string
Handler http.Handler
Tunnel http.Handler
Authenticator authenticator.Request
RequestHeaderCA string RequestHeaderCA string
RequestHeaderCAKey string RequestHeaderCAKey string
ClientAuthProxyCert string ClientAuthProxyCert string
ClientAuthProxyKey string ClientAuthProxyKey string
ClientAdminCert string
ClientAdminKey string
ClientControllerCert string
ClientControllerKey string
ClientSchedulerCert string
ClientSchedulerKey string
ClientKubeProxyCert string
ClientKubeProxyKey string
ServingKubeletKey string
ClientKubeletKey string
} }
type ArgString []string type ArgString []string
......
package server package server
import ( import (
"bufio" "crypto"
"crypto/rsa"
"crypto/x509" "crypto/x509"
"encoding/csv"
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
...@@ -17,10 +18,10 @@ import ( ...@@ -17,10 +18,10 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
certutil "github.com/rancher/dynamiclistener/cert" certutil "github.com/rancher/dynamiclistener/cert"
"github.com/rancher/k3s/pkg/daemons/config" "github.com/rancher/k3s/pkg/daemons/config"
"github.com/rancher/k3s/pkg/daemons/control"
"github.com/rancher/k3s/pkg/openapi" "github.com/rancher/k3s/pkg/openapi"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/json"
"k8s.io/kubernetes/pkg/master"
) )
const ( const (
...@@ -38,8 +39,14 @@ func router(serverConfig *config.Control, tunnel http.Handler, cacertsGetter CAC ...@@ -38,8 +39,14 @@ func router(serverConfig *config.Control, tunnel http.Handler, cacertsGetter CAC
authed.Use(authMiddleware(serverConfig)) authed.Use(authMiddleware(serverConfig))
authed.NotFoundHandler = serverConfig.Runtime.Handler authed.NotFoundHandler = serverConfig.Runtime.Handler
authed.Path("/v1-k3s/connect").Handler(tunnel) authed.Path("/v1-k3s/connect").Handler(tunnel)
authed.Path("/v1-k3s/node.crt").Handler(nodeCrt(serverConfig)) authed.Path("/v1-k3s/serving-kubelet.crt").Handler(servingKubeletCert(serverConfig))
authed.Path("/v1-k3s/node.key").Handler(nodeKey(serverConfig)) authed.Path("/v1-k3s/serving-kubelet.key").Handler(fileHandler(serverConfig.Runtime.ServingKubeletKey))
authed.Path("/v1-k3s/client-kubelet.crt").Handler(clientKubeletCert(serverConfig))
authed.Path("/v1-k3s/client-kubelet.key").Handler(fileHandler(serverConfig.Runtime.ClientKubeletKey))
authed.Path("/v1-k3s/client-kube-proxy.crt").Handler(fileHandler(serverConfig.Runtime.ClientKubeProxyCert))
authed.Path("/v1-k3s/client-kube-proxy.key").Handler(fileHandler(serverConfig.Runtime.ClientKubeProxyKey))
authed.Path("/v1-k3s/client-ca.crt").Handler(fileHandler(serverConfig.Runtime.ClientCA))
authed.Path("/v1-k3s/server-ca.crt").Handler(fileHandler(serverConfig.Runtime.ServerCA))
authed.Path("/v1-k3s/config").Handler(configHandler(serverConfig)) authed.Path("/v1-k3s/config").Handler(configHandler(serverConfig))
staticDir := filepath.Join(serverConfig.DataDir, "static") staticDir := filepath.Join(serverConfig.DataDir, "static")
...@@ -65,82 +72,122 @@ func cacerts(getter CACertsGetter) http.Handler { ...@@ -65,82 +72,122 @@ func cacerts(getter CACertsGetter) http.Handler {
}) })
} }
func nodeCrt(server *config.Control) http.Handler { func getNodeInfo(req *http.Request) (string, string, error) {
nodeNames := req.Header["K3s-Node-Name"]
if len(nodeNames) != 1 || nodeNames[0] == "" {
return "", "", errors.New("node name not set")
}
nodePasswords := req.Header["K3s-Node-Password"]
if len(nodePasswords) != 1 || nodePasswords[0] == "" {
return "", "", errors.New("node password not set")
}
return nodeNames[0], nodePasswords[0], nil
}
func getCACertAndKeys(caCertFile, caKeyFile, signingKeyFile string) ([]*x509.Certificate, crypto.Signer, crypto.Signer, error) {
keyBytes, err := ioutil.ReadFile(signingKeyFile)
if err != nil {
return nil, nil, nil, err
}
key, err := certutil.ParsePrivateKeyPEM(keyBytes)
if err != nil {
return nil, nil, nil, err
}
caKeyBytes, err := ioutil.ReadFile(caKeyFile)
if err != nil {
return nil, nil, nil, err
}
caKey, err := certutil.ParsePrivateKeyPEM(caKeyBytes)
if err != nil {
return nil, nil, nil, err
}
caBytes, err := ioutil.ReadFile(caCertFile)
if err != nil {
return nil, nil, nil, err
}
caCert, err := certutil.ParseCertsPEM(caBytes)
if err != nil {
return nil, nil, nil, err
}
return caCert, caKey.(crypto.Signer), key.(crypto.Signer), nil
}
func servingKubeletCert(server *config.Control) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
if req.TLS == nil { if req.TLS == nil {
resp.WriteHeader(http.StatusNotFound) resp.WriteHeader(http.StatusNotFound)
return return
} }
nodeNames := req.Header["K3s-Node-Name"] nodeName, nodePassword, err := getNodeInfo(req)
if len(nodeNames) != 1 || nodeNames[0] == "" { if err != nil {
sendError(errors.New("node name not set"), resp) sendError(err, resp)
return
}
nodePasswords := req.Header["K3s-Node-Password"]
if len(nodePasswords) != 1 || nodePasswords[0] == "" {
sendError(errors.New("node password not set"), resp)
return
} }
if err := ensureNodePassword(server.Runtime.PasswdFile, nodeNames[0], nodePasswords[0]); err != nil { if err := ensureNodePassword(server.Runtime.PasswdFile, nodeName, nodePassword); err != nil {
sendError(err, resp, http.StatusForbidden) sendError(err, resp, http.StatusForbidden)
return return
} }
nodeKey, err := ioutil.ReadFile(server.Runtime.NodeKey) caCert, caKey, key, err := getCACertAndKeys(server.Runtime.ServerCA, server.Runtime.ServerCAKey, server.Runtime.ServingKubeletKey)
if err != nil { if err != nil {
sendError(err, resp) sendError(err, resp)
return return
} }
key, err := certutil.ParsePrivateKeyPEM(nodeKey) cert, err := certutil.NewSignedCert(certutil.Config{
CommonName: nodeName,
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
AltNames: certutil.AltNames{
DNSNames: []string{nodeName, "localhost"},
IPs: []net.IP{net.ParseIP("127.0.0.1")},
},
}, key, caCert[0], caKey)
if err != nil { if err != nil {
sendError(err, resp) sendError(err, resp)
return return
} }
caKeyBytes, err := ioutil.ReadFile(server.Runtime.TokenCAKey) resp.Write(append(certutil.EncodeCertPEM(cert), certutil.EncodeCertPEM(caCert[0])...))
if err != nil { })
sendError(err, resp) }
return
}
caBytes, err := ioutil.ReadFile(server.Runtime.TokenCA) func clientKubeletCert(server *config.Control) http.Handler {
if err != nil { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
sendError(err, resp) if req.TLS == nil {
resp.WriteHeader(http.StatusNotFound)
return return
} }
caKey, err := certutil.ParsePrivateKeyPEM(caKeyBytes) nodeName, nodePassword, err := getNodeInfo(req)
if err != nil { if err != nil {
sendError(err, resp) sendError(err, resp)
return
} }
caCert, err := certutil.ParseCertsPEM(caBytes) if err := ensureNodePassword(server.Runtime.PasswdFile, nodeName, nodePassword); err != nil {
if err != nil { sendError(err, resp, http.StatusForbidden)
sendError(err, resp)
return return
} }
_, apiServerServiceIP, err := master.DefaultServiceIPRange(*server.ServiceIPRange) caCert, caKey, key, err := getCACertAndKeys(server.Runtime.ClientCA, server.Runtime.ClientCAKey, server.Runtime.ClientKubeletKey)
if err != nil { if err != nil {
sendError(err, resp) sendError(err, resp)
return return
} }
cfg := certutil.Config{ cert, err := certutil.NewSignedCert(certutil.Config{
CommonName: "kubernetes", CommonName: "system:node:" + nodeName,
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, Organization: []string{"system:nodes"},
AltNames: certutil.AltNames{ Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
DNSNames: []string{"kubernetes.default.svc", "kubernetes.default", "kubernetes", "localhost", nodeNames[0]}, }, key, caCert[0], caKey)
IPs: []net.IP{apiServerServiceIP, net.ParseIP("127.0.0.1")},
},
}
cert, err := certutil.NewSignedCert(cfg, key.(*rsa.PrivateKey), caCert[0], caKey.(*rsa.PrivateKey))
if err != nil { if err != nil {
sendError(err, resp) sendError(err, resp)
return return
...@@ -150,13 +197,13 @@ func nodeCrt(server *config.Control) http.Handler { ...@@ -150,13 +197,13 @@ func nodeCrt(server *config.Control) http.Handler {
}) })
} }
func nodeKey(server *config.Control) http.Handler { func fileHandler(fileName string) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
if req.TLS == nil { if req.TLS == nil {
resp.WriteHeader(http.StatusNotFound) resp.WriteHeader(http.StatusNotFound)
return return
} }
http.ServeFile(resp, req, server.Runtime.NodeKey) http.ServeFile(resp, req, fileName)
}) })
} }
...@@ -225,29 +272,29 @@ func ensureNodePassword(passwdFile, nodeName, passwd string) error { ...@@ -225,29 +272,29 @@ func ensureNodePassword(passwdFile, nodeName, passwd string) error {
defer f.Close() defer f.Close()
user := strings.ToLower("node:" + nodeName) user := strings.ToLower("node:" + nodeName)
buf := &strings.Builder{} records := [][]string{}
scan := bufio.NewScanner(f) reader := csv.NewReader(f)
for scan.Scan() { for {
line := scan.Text() record, err := reader.Read()
parts := strings.Split(line, ",") if err == io.EOF {
if len(parts) < 4 { break
continue }
if err != nil {
return err
} }
if parts[1] == user { if len(record) < 3 {
if parts[0] == passwd { return fmt.Errorf("password file '%s' must have at least 3 columns (password, user name, user uid), found %d", passwdFile, len(record))
}
if record[1] == user {
if record[0] == passwd {
return nil return nil
} }
return fmt.Errorf("Node password validation failed for [%s]", nodeName) return fmt.Errorf("Node password validation failed for '%s', using passwd file '%s'", nodeName, passwdFile)
} }
buf.WriteString(line) records = append(records, record)
buf.WriteString("\n")
}
buf.WriteString(fmt.Sprintf("%s,%s,%s,system:masters\n", passwd, user, user))
if scan.Err() != nil {
return scan.Err()
} }
records = append(records, []string{passwd, user, user, "system:node:" + nodeName})
f.Close() f.Close()
return ioutil.WriteFile(passwdFile, []byte(buf.String()), 0600) return control.WritePasswords(passwdFile, records)
} }
...@@ -77,6 +77,18 @@ func startWrangler(ctx context.Context, config *Config) (string, error) { ...@@ -77,6 +77,18 @@ func startWrangler(ctx context.Context, config *Config) (string, error) {
controlConfig = &config.ControlConfig controlConfig = &config.ControlConfig
) )
caBytes, err := ioutil.ReadFile(controlConfig.Runtime.ServerCA)
if err != nil {
return "", err
}
caKeyBytes, err := ioutil.ReadFile(controlConfig.Runtime.ServerCAKey)
if err != nil {
return "", err
}
tlsConfig.CACerts = string(caBytes)
tlsConfig.CAKey = string(caKeyBytes)
tlsConfig.Handler = router(controlConfig, controlConfig.Runtime.Tunnel, func() (string, error) { tlsConfig.Handler = router(controlConfig, controlConfig.Runtime.Tunnel, func() (string, error) {
if tlsServer == nil { if tlsServer == nil {
return "", nil return "", nil
...@@ -84,7 +96,7 @@ func startWrangler(ctx context.Context, config *Config) (string, error) { ...@@ -84,7 +96,7 @@ func startWrangler(ctx context.Context, config *Config) (string, error) {
return tlsServer.CACert() return tlsServer.CACert()
}) })
sc, err := newContext(ctx, controlConfig.Runtime.KubeConfigSystem) sc, err := newContext(ctx, controlConfig.Runtime.KubeConfigAdmin)
if err != nil { if err != nil {
return "", err return "", err
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment