1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2025-03-05 16:27:05 +00:00

Implement TLS server authentication

Add support for TLS authentication. When enabled, nfd-worker verifies
that nfd-master has a valid certificate, i.e. signed by the given root
certificate and its Common Name (CN) matches the DNS name of the
nfd-master service being used. TLS authentication is enabled by
specifying --key-file and --cert-file on nfd-master, and, --ca-file on
nfd-worker.
This commit is contained in:
Markus Lehtonen 2019-01-29 16:21:48 +02:00
parent 97694c15d8
commit bca194f6e6
6 changed files with 66 additions and 9 deletions

View file

@ -29,6 +29,7 @@ import (
"github.com/docopt/docopt-go" "github.com/docopt/docopt-go"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"sigs.k8s.io/node-feature-discovery/pkg/apihelper" "sigs.k8s.io/node-feature-discovery/pkg/apihelper"
pb "sigs.k8s.io/node-feature-discovery/pkg/labeler" pb "sigs.k8s.io/node-feature-discovery/pkg/labeler"
"sigs.k8s.io/node-feature-discovery/pkg/version" "sigs.k8s.io/node-feature-discovery/pkg/version"
@ -62,6 +63,8 @@ type Annotations map[string]string
// Command line arguments // Command line arguments
type Args struct { type Args struct {
certFile string
keyFile string
labelWhiteList *regexp.Regexp labelWhiteList *regexp.Regexp
noPublish bool noPublish bool
port int port int
@ -95,7 +98,17 @@ func main() {
if err != nil { if err != nil {
stderrLogger.Fatalf("failed to listen: %v", err) stderrLogger.Fatalf("failed to listen: %v", err)
} }
grpcServer := grpc.NewServer()
serverOpts := []grpc.ServerOption{}
// Use TLS if --cert-file or --key-file is defined
if args.certFile != "" || args.keyFile != "" {
creds, err := credentials.NewServerTLSFromFile(args.certFile, args.keyFile)
if err != nil {
log.Fatalf("Failed to generate credentials %v", err)
}
serverOpts = append(serverOpts, grpc.Creds(creds))
}
grpcServer := grpc.NewServer(serverOpts...)
pb.RegisterLabelerServer(grpcServer, &labelerServer{args: args, apiHelper: helper}) pb.RegisterLabelerServer(grpcServer, &labelerServer{args: args, apiHelper: helper})
stdoutLogger.Printf("gRPC server serving on port: %d", args.port) stdoutLogger.Printf("gRPC server serving on port: %d", args.port)
grpcServer.Serve(lis) grpcServer.Serve(lis)
@ -109,6 +122,7 @@ func argsParse(argv []string) (Args, error) {
Usage: Usage:
%s [--no-publish] [--label-whitelist=<pattern>] [--port=<port>] %s [--no-publish] [--label-whitelist=<pattern>] [--port=<port>]
[--cert-file=<path>] [--key-file=<path>]
%s -h | --help %s -h | --help
%s --version %s --version
@ -117,6 +131,10 @@ func argsParse(argv []string) (Args, error) {
--version Output version and exit. --version Output version and exit.
--port=<port> Port on which to listen for connections. --port=<port> Port on which to listen for connections.
[Default: 8080] [Default: 8080]
--cert-file=<path> Certificate used for authenticating connections
[Default: ]
--key-file=<path> Private key matching --cert-file
[Default: ]
--no-publish Do not publish feature labels --no-publish Do not publish feature labels
--label-whitelist=<pattern> Regular expression to filter label names to --label-whitelist=<pattern> Regular expression to filter label names to
publish to the Kubernetes API server. [Default: ]`, publish to the Kubernetes API server. [Default: ]`,
@ -131,6 +149,8 @@ func argsParse(argv []string) (Args, error) {
// Parse argument values as usable types. // Parse argument values as usable types.
var err error var err error
args.certFile = arguments["--cert-file"].(string)
args.keyFile = arguments["--key-file"].(string)
args.noPublish = arguments["--no-publish"].(bool) args.noPublish = arguments["--no-publish"].(bool)
args.port, err = strconv.Atoi(arguments["--port"].(string)) args.port, err = strconv.Atoi(arguments["--port"].(string))
if err != nil { if err != nil {
@ -141,6 +161,15 @@ func argsParse(argv []string) (Args, error) {
return args, fmt.Errorf("error parsing whitelist regex (%s): %s", arguments["--label-whitelist"], err) return args, fmt.Errorf("error parsing whitelist regex (%s): %s", arguments["--label-whitelist"], err)
} }
// Check TLS related args
if args.certFile != "" || args.keyFile != "" {
if args.certFile == "" {
return args, fmt.Errorf("--cert-file needs to be specified alongside --key-file")
}
if args.keyFile == "" {
return args, fmt.Errorf("--key-file needs to be specified alongside --cert-file")
}
}
return args, nil return args, nil
} }

View file

@ -223,10 +223,12 @@ func TestArgsParse(t *testing.T) {
}) })
Convey("When valid args are specified", func() { Convey("When valid args are specified", func() {
args, err := argsParse([]string{"--label-whitelist=.*rdt.*", "--port=1234"}) args, err := argsParse([]string{"--label-whitelist=.*rdt.*", "--port=1234", "--cert-file=crt", "--key-file=key"})
Convey("Argument parsing should succeed and args set to correct values", func() { Convey("Argument parsing should succeed and args set to correct values", func() {
So(args.noPublish, ShouldBeFalse) So(args.noPublish, ShouldBeFalse)
So(args.port, ShouldEqual, 1234) So(args.port, ShouldEqual, 1234)
So(args.certFile, ShouldEqual, "crt")
So(args.keyFile, ShouldEqual, "key")
So(args.labelWhiteList.String(), ShouldResemble, ".*rdt.*") So(args.labelWhiteList.String(), ShouldResemble, ".*rdt.*")
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })
@ -237,5 +239,14 @@ func TestArgsParse(t *testing.T) {
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
}) })
Convey("When --cert-file or --key-file is specified on its own", func() {
_, err := argsParse([]string{"--cert-file=crt"})
_, err2 := argsParse([]string{"--key-file=key"})
Convey("argsParse should fail", func() {
So(err, ShouldNotBeNil)
So(err2, ShouldNotBeNil)
})
})
}) })
} }

View file

@ -29,6 +29,7 @@ import (
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation"
pb "sigs.k8s.io/node-feature-discovery/pkg/labeler" pb "sigs.k8s.io/node-feature-discovery/pkg/labeler"
"sigs.k8s.io/node-feature-discovery/pkg/version" "sigs.k8s.io/node-feature-discovery/pkg/version"
@ -82,6 +83,7 @@ type Annotations map[string]string
// Command line arguments // Command line arguments
type Args struct { type Args struct {
labelWhiteList string labelWhiteList string
caFile string
configFile string configFile string
noPublish bool noPublish bool
options string options string
@ -117,8 +119,17 @@ func main() {
} }
// Connect to NFD server // Connect to NFD server
opts := []grpc.DialOption{grpc.WithInsecure()} dialOpts := []grpc.DialOption{}
conn, err := grpc.Dial(args.server, opts...) if args.caFile != "" {
creds, err := credentials.NewClientTLSFromFile(args.caFile, "")
if err != nil {
stderrLogger.Fatalf("failed to create credentials %v", err)
}
dialOpts = append(dialOpts, grpc.WithTransportCredentials(creds))
} else {
dialOpts = append(dialOpts, grpc.WithInsecure())
}
conn, err := grpc.Dial(args.server, dialOpts...)
if err != nil { if err != nil {
stderrLogger.Fatalf("failed to connect: %v", err) stderrLogger.Fatalf("failed to connect: %v", err)
} }
@ -160,7 +171,7 @@ func argsParse(argv []string) (Args, error) {
Usage: Usage:
%s [--no-publish] [--sources=<sources>] [--label-whitelist=<pattern>] %s [--no-publish] [--sources=<sources>] [--label-whitelist=<pattern>]
[--oneshot | --sleep-interval=<seconds>] [--config=<path>] [--oneshot | --sleep-interval=<seconds>] [--config=<path>]
[--options=<config>] [--server=<server>] [--options=<config>] [--ca-file=<path>] [--server=<server>]
%s -h | --help %s -h | --help
%s --version %s --version
@ -174,6 +185,8 @@ func argsParse(argv []string) (Args, error) {
config file (i.e. json or yaml). These options config file (i.e. json or yaml). These options
will override settings read from the config file. will override settings read from the config file.
[Default: ] [Default: ]
--ca-file=<path> Root certificate for verifying connections
[Default: ]
--server=<server> NFD server address to connecto to. --server=<server> NFD server address to connecto to.
[Default: localhost:8080] [Default: localhost:8080]
--sources=<sources> Comma separated list of feature sources. --sources=<sources> Comma separated list of feature sources.
@ -197,6 +210,7 @@ func argsParse(argv []string) (Args, error) {
// Parse argument values as usable types. // Parse argument values as usable types.
var err error var err error
args.caFile = arguments["--ca-file"].(string)
args.configFile = arguments["--config"].(string) args.configFile = arguments["--config"].(string)
args.noPublish = arguments["--no-publish"].(bool) args.noPublish = arguments["--no-publish"].(bool)
args.options = arguments["--options"].(string) args.options = arguments["--options"].(string)

View file

@ -116,11 +116,12 @@ func TestArgsParse(t *testing.T) {
}) })
}) })
Convey("When --no-publish and --sources flag are passed and --sources flag is set to some value", func() { Convey("When valid args are specified", func() {
args, err := argsParse([]string{"--no-publish", "--sources=fake1,fake2,fake3"}) args, err := argsParse([]string{"--no-publish", "--sources=fake1,fake2,fake3", "--ca-file=ca"})
Convey("--no-publish is set and args.sources is set to appropriate values", func() { Convey("--no-publish is set and args.sources is set to appropriate values", func() {
So(args.noPublish, ShouldBeTrue) So(args.noPublish, ShouldBeTrue)
So(args.caFile, ShouldEqual, "ca")
So(args.sources, ShouldResemble, []string{"fake1", "fake2", "fake3"}) So(args.sources, ShouldResemble, []string{"fake1", "fake2", "fake3"})
So(len(args.labelWhiteList), ShouldEqual, 0) So(len(args.labelWhiteList), ShouldEqual, 0)
So(err, ShouldBeNil) So(err, ShouldBeNil)

View file

@ -14,6 +14,7 @@ spec:
app: nfd-worker app: nfd-worker
spec: spec:
hostNetwork: true hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
containers: containers:
- env: - env:
- name: NODE_NAME - name: NODE_NAME
@ -26,7 +27,7 @@ spec:
- "nfd-worker" - "nfd-worker"
args: args:
- "--sleep-interval=60s" - "--sleep-interval=60s"
- "--server=$(NFD_MASTER_SERVICE_HOST):$(NFD_MASTER_SERVICE_PORT)" - "--server=nfd-master:8080"
volumeMounts: volumeMounts:
- name: host-boot - name: host-boot
mountPath: "/host-boot" mountPath: "/host-boot"

View file

@ -13,6 +13,7 @@ spec:
app: node-feature-discovery app: node-feature-discovery
spec: spec:
hostNetwork: true hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
containers: containers:
- env: - env:
- name: NODE_NAME - name: NODE_NAME
@ -25,7 +26,7 @@ spec:
- "nfd-worker" - "nfd-worker"
args: args:
- "--oneshot" - "--oneshot"
- "--server=$(NFD_MASTER_SERVICE_HOST):$(NFD_MASTER_SERVICE_PORT)" - "--server=nfd-master:8080"
ports: ports:
- containerPort: 7156 - containerPort: 7156
hostPort: 7156 hostPort: 7156