Add cert per-node password authentication

pull/369/head
Erik Wilson 6 years ago
parent 055a574fee
commit e64c0298f2

@ -3,7 +3,9 @@ package config
import (
"bufio"
"context"
cryptorand "crypto/rand"
"crypto/tls"
"encoding/hex"
"encoding/pem"
"fmt"
"io/ioutil"
@ -56,7 +58,7 @@ func Request(path string, info *clientaccess.Info, requester HTTPRequester) ([]b
return requester(u.String(), clientaccess.GetHTTPClient(info.CACerts), username, password)
}
func getNodeNamedCrt(nodeName string) HTTPRequester {
func getNodeNamedCrt(nodeName, nodePasswordFile string) HTTPRequester {
return func(u string, client *http.Client, username, password string) ([]byte, error) {
req, err := http.NewRequest(http.MethodGet, u, nil)
if err != nil {
@ -68,6 +70,12 @@ func getNodeNamedCrt(nodeName string) HTTPRequester {
}
req.Header.Set("K3s-Node-Name", nodeName)
nodePassword, err := ensureNodePassword(nodePasswordFile)
if err != nil {
return nil, err
}
req.Header.Set("K3s-Node-Password", hex.EncodeToString(nodePassword))
resp, err := client.Do(req)
if err != nil {
return nil, err
@ -82,8 +90,20 @@ func getNodeNamedCrt(nodeName string) HTTPRequester {
}
}
func getNodeCert(nodeName, nodeCertFile, nodeKeyFile string, info *clientaccess.Info) (*tls.Certificate, error) {
nodeCert, err := Request("/v1-k3s/node.crt", info, getNodeNamedCrt(nodeName))
func ensureNodePassword(nodePasswordFile string) ([]byte, error) {
if _, err := os.Stat(nodePasswordFile); err == nil {
return ioutil.ReadFile(nodePasswordFile)
}
password := make([]byte, 16, 16)
_, err := cryptorand.Read(password)
if err != nil {
return nil, err
}
return password, ioutil.WriteFile(nodePasswordFile, password, 0600)
}
func getNodeCert(nodeName, nodeCertFile, nodeKeyFile, nodePasswordFile string, info *clientaccess.Info) (*tls.Certificate, error) {
nodeCert, err := Request("/v1-k3s/node.crt", info, getNodeNamedCrt(nodeName, nodePasswordFile))
if err != nil {
return nil, err
}
@ -233,7 +253,9 @@ func get(envInfo *cmds.Agent) (*config.Node, error) {
nodeCertFile := filepath.Join(envInfo.DataDir, "token-node.crt")
nodeKeyFile := filepath.Join(envInfo.DataDir, "token-node.key")
nodeCert, err := getNodeCert(nodeName, nodeCertFile, nodeKeyFile, info)
nodePasswordFile := filepath.Join(envInfo.DataDir, "node-password.bin")
nodeCert, err := getNodeCert(nodeName, nodeCertFile, nodeKeyFile, nodePasswordFile, info)
if err != nil {
return nil, err
}

@ -1,13 +1,18 @@
package server
import (
"bufio"
"crypto/rsa"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/gorilla/mux"
"github.com/rancher/k3s/pkg/daemons/config"
@ -67,10 +72,21 @@ func nodeCrt(server *config.Control) http.Handler {
return
}
var nodeName string
nodeNames := req.Header["K3s-Node-Name"]
if len(nodeNames) == 1 {
nodeName = nodeNames[0]
if len(nodeNames) != 1 || nodeNames[0] == "" {
sendError(errors.New("node name not set"), 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 {
sendError(err, resp, http.StatusForbidden)
return
}
nodeKey, err := ioutil.ReadFile(server.Runtime.NodeKey)
@ -119,7 +135,7 @@ func nodeCrt(server *config.Control) http.Handler {
CommonName: "kubernetes",
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
AltNames: certutil.AltNames{
DNSNames: []string{"kubernetes.default.svc", "kubernetes.default", "kubernetes", "localhost", nodeName},
DNSNames: []string{"kubernetes.default.svc", "kubernetes.default", "kubernetes", "localhost", nodeNames[0]},
IPs: []net.IP{apiServerServiceIP, net.ParseIP("127.0.0.1")},
},
}
@ -190,8 +206,48 @@ func serveStatic(urlPrefix, staticDir string) http.Handler {
return http.StripPrefix(urlPrefix, http.FileServer(http.Dir(staticDir)))
}
func sendError(err error, resp http.ResponseWriter) {
func sendError(err error, resp http.ResponseWriter, status ...int) {
code := http.StatusInternalServerError
if len(status) == 1 {
code = status[0]
}
logrus.Error(err)
resp.WriteHeader(http.StatusInternalServerError)
resp.WriteHeader(code)
resp.Write([]byte(err.Error()))
}
func ensureNodePassword(passwdFile, nodeName, passwd string) error {
f, err := os.Open(passwdFile)
if err != nil {
return err
}
defer f.Close()
user := strings.ToLower("node:" + nodeName)
buf := &strings.Builder{}
scan := bufio.NewScanner(f)
for scan.Scan() {
line := scan.Text()
parts := strings.Split(line, ",")
if len(parts) < 4 {
continue
}
if parts[1] == user {
if parts[0] == passwd {
return nil
}
return fmt.Errorf("Node password validation failed for [%s]", nodeName)
}
buf.WriteString(line)
buf.WriteString("\n")
}
buf.WriteString(fmt.Sprintf("%s,%s,%s,system:masters\n", passwd, user, user))
if scan.Err() != nil {
return scan.Err()
}
f.Close()
return ioutil.WriteFile(passwdFile, []byte(buf.String()), 0600)
}

Loading…
Cancel
Save