From af0b496ef3ada09c6bce44a1d7b08d40fc3ba357 Mon Sep 17 00:00:00 2001 From: Brad Davidson Date: Mon, 28 Mar 2022 13:45:39 -0700 Subject: [PATCH] Add client certificate authentication support to core Authenticator This is required to make the websocket tunnel server functional on etcd-only nodes, and will save some code on the RKE2 side once pulled through. Signed-off-by: Brad Davidson --- pkg/authenticator/authenticator.go | 56 +++++++++++++++++++ pkg/authenticator/basicauth/basicauth.go | 5 +- .../{ => basicauth}/interfaces.go | 2 +- pkg/daemons/control/auth.go | 30 ---------- pkg/daemons/control/server.go | 11 +++- pkg/daemons/control/tunnel.go | 9 ++- 6 files changed, 75 insertions(+), 38 deletions(-) create mode 100644 pkg/authenticator/authenticator.go rename pkg/authenticator/{ => basicauth}/interfaces.go (97%) delete mode 100644 pkg/daemons/control/auth.go diff --git a/pkg/authenticator/authenticator.go b/pkg/authenticator/authenticator.go new file mode 100644 index 0000000000..07d4aa856d --- /dev/null +++ b/pkg/authenticator/authenticator.go @@ -0,0 +1,56 @@ +package authenticator + +import ( + "strings" + + "github.com/k3s-io/k3s/pkg/authenticator/basicauth" + "github.com/k3s-io/k3s/pkg/authenticator/passwordfile" + "k8s.io/apiserver/pkg/authentication/authenticator" + "k8s.io/apiserver/pkg/authentication/group" + "k8s.io/apiserver/pkg/authentication/request/union" + "k8s.io/apiserver/pkg/authentication/request/x509" + "k8s.io/apiserver/pkg/server/dynamiccertificates" +) + +func FromArgs(args []string) (authenticator.Request, error) { + var authenticators []authenticator.Request + basicFile := getArg("--basic-auth-file", args) + if basicFile != "" { + basicAuthenticator, err := passwordfile.NewCSV(basicFile) + if err != nil { + return nil, err + } + authenticators = append(authenticators, basicauth.New(basicAuthenticator)) + } + + clientCA := getArg("--client-ca-file", args) + if clientCA != "" { + ca, err := dynamiccertificates.NewDynamicCAContentFromFile("client-ca", clientCA) + if err != nil { + return nil, err + } + authenticators = append(authenticators, x509.NewDynamic(ca.VerifyOptions, x509.CommonNameUserConversion)) + } + + return Combine(authenticators...), nil +} + +func getArg(key string, args []string) string { + for _, arg := range args { + if !strings.HasPrefix(arg, key) { + continue + } + return strings.SplitN(arg, "=", 2)[1] + } + return "" +} + +func Combine(auths ...authenticator.Request) authenticator.Request { + var authenticators []authenticator.Request + for _, auth := range auths { + if auth != nil { + authenticators = append(authenticators, auth) + } + } + return group.NewAuthenticatedGroupAdder(union.New(authenticators...)) +} diff --git a/pkg/authenticator/basicauth/basicauth.go b/pkg/authenticator/basicauth/basicauth.go index 0eff4474e4..b9bdb29e8d 100644 --- a/pkg/authenticator/basicauth/basicauth.go +++ b/pkg/authenticator/basicauth/basicauth.go @@ -20,17 +20,16 @@ import ( "errors" "net/http" - localAuthenticator "github.com/k3s-io/k3s/pkg/authenticator" "k8s.io/apiserver/pkg/authentication/authenticator" ) // Authenticator authenticates requests using basic auth type Authenticator struct { - auth localAuthenticator.Password + auth Password } // New returns a request authenticator that validates credentials using the provided password authenticator -func New(auth localAuthenticator.Password) *Authenticator { +func New(auth Password) *Authenticator { return &Authenticator{auth} } diff --git a/pkg/authenticator/interfaces.go b/pkg/authenticator/basicauth/interfaces.go similarity index 97% rename from pkg/authenticator/interfaces.go rename to pkg/authenticator/basicauth/interfaces.go index 6cffe6df95..22c75e63b9 100644 --- a/pkg/authenticator/interfaces.go +++ b/pkg/authenticator/basicauth/interfaces.go @@ -11,7 +11,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package authenticator +package basicauth import ( "context" diff --git a/pkg/daemons/control/auth.go b/pkg/daemons/control/auth.go deleted file mode 100644 index c78839f8bf..0000000000 --- a/pkg/daemons/control/auth.go +++ /dev/null @@ -1,30 +0,0 @@ -package control - -import ( - "github.com/k3s-io/k3s/pkg/authenticator/basicauth" - "github.com/k3s-io/k3s/pkg/authenticator/passwordfile" - "k8s.io/apiserver/pkg/authentication/authenticator" - "k8s.io/apiserver/pkg/authentication/group" - "k8s.io/apiserver/pkg/authentication/request/union" -) - -func basicAuthenticator(basicAuthFile string) (authenticator.Request, error) { - if basicAuthFile == "" { - return nil, nil - } - basicAuthenticator, err := passwordfile.NewCSV(basicAuthFile) - if err != nil { - return nil, err - } - return basicauth.New(basicAuthenticator), nil -} - -func combineAuthenticators(auths ...authenticator.Request) authenticator.Request { - var authenticators []authenticator.Request - for _, auth := range auths { - if auth != nil { - authenticators = append(authenticators, auth) - } - } - return group.NewAuthenticatedGroupAdder(union.New(authenticators...)) -} diff --git a/pkg/daemons/control/server.go b/pkg/daemons/control/server.go index d517a62e8c..d979761681 100644 --- a/pkg/daemons/control/server.go +++ b/pkg/daemons/control/server.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/k3s-io/k3s/pkg/authenticator" "github.com/k3s-io/k3s/pkg/cluster" "github.com/k3s-io/k3s/pkg/daemons/config" "github.com/k3s-io/k3s/pkg/daemons/control/deps" @@ -56,11 +57,15 @@ func Server(ctx context.Context, cfg *config.Control) error { cfg.Runtime.Tunnel = setupTunnel() proxyutil.DisableProxyHostnameCheck = true - basicAuth, err := basicAuthenticator(cfg.Runtime.PasswdFile) + authArgs := []string{ + "--basic-auth-file=" + cfg.Runtime.PasswdFile, + "--client-ca-file=" + cfg.Runtime.ClientCA, + } + auth, err := authenticator.FromArgs(authArgs) if err != nil { return err } - cfg.Runtime.Authenticator = basicAuth + cfg.Runtime.Authenticator = auth if !cfg.DisableAPIServer { go waitForAPIServerHandlers(ctx, cfg.Runtime) @@ -399,7 +404,7 @@ func waitForAPIServerHandlers(ctx context.Context, runtime *config.ControlRuntim if err != nil { logrus.Fatalf("Failed to get request handlers from apiserver: %v", err) } - runtime.Authenticator = combineAuthenticators(runtime.Authenticator, auth) + runtime.Authenticator = authenticator.Combine(runtime.Authenticator, auth) runtime.APIServer = handler } diff --git a/pkg/daemons/control/tunnel.go b/pkg/daemons/control/tunnel.go index 4c758bc64f..a82d5c1b4b 100644 --- a/pkg/daemons/control/tunnel.go +++ b/pkg/daemons/control/tunnel.go @@ -9,13 +9,20 @@ import ( "github.com/rancher/remotedialer" "github.com/rancher/wrangler/pkg/kv" + "github.com/sirupsen/logrus" utilnet "k8s.io/apimachinery/pkg/util/net" "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/kubernetes/cmd/kube-apiserver/app" ) +func loggingErrorWriter(rw http.ResponseWriter, req *http.Request, code int, err error) { + logrus.Debugf("remoteDialer error: %d %v", code, err) + rw.WriteHeader(code) + rw.Write([]byte(err.Error())) +} + func setupTunnel() http.Handler { - tunnelServer := remotedialer.New(authorizer, remotedialer.DefaultErrorWriter) + tunnelServer := remotedialer.New(authorizer, loggingErrorWriter) setupProxyDialer(tunnelServer) return tunnelServer }