1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2025-03-05 08:17:04 +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"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"sigs.k8s.io/node-feature-discovery/pkg/apihelper"
pb "sigs.k8s.io/node-feature-discovery/pkg/labeler"
"sigs.k8s.io/node-feature-discovery/pkg/version"
@ -62,6 +63,8 @@ type Annotations map[string]string
// Command line arguments
type Args struct {
certFile string
keyFile string
labelWhiteList *regexp.Regexp
noPublish bool
port int
@ -95,7 +98,17 @@ func main() {
if err != nil {
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})
stdoutLogger.Printf("gRPC server serving on port: %d", args.port)
grpcServer.Serve(lis)
@ -109,6 +122,7 @@ func argsParse(argv []string) (Args, error) {
Usage:
%s [--no-publish] [--label-whitelist=<pattern>] [--port=<port>]
[--cert-file=<path>] [--key-file=<path>]
%s -h | --help
%s --version
@ -117,6 +131,10 @@ func argsParse(argv []string) (Args, error) {
--version Output version and exit.
--port=<port> Port on which to listen for connections.
[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
--label-whitelist=<pattern> Regular expression to filter label names to
publish to the Kubernetes API server. [Default: ]`,
@ -131,6 +149,8 @@ func argsParse(argv []string) (Args, error) {
// Parse argument values as usable types.
var err error
args.certFile = arguments["--cert-file"].(string)
args.keyFile = arguments["--key-file"].(string)
args.noPublish = arguments["--no-publish"].(bool)
args.port, err = strconv.Atoi(arguments["--port"].(string))
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)
}
// 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
}

View file

@ -223,10 +223,12 @@ func TestArgsParse(t *testing.T) {
})
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() {
So(args.noPublish, ShouldBeFalse)
So(args.port, ShouldEqual, 1234)
So(args.certFile, ShouldEqual, "crt")
So(args.keyFile, ShouldEqual, "key")
So(args.labelWhiteList.String(), ShouldResemble, ".*rdt.*")
So(err, ShouldBeNil)
})
@ -237,5 +239,14 @@ func TestArgsParse(t *testing.T) {
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"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"k8s.io/apimachinery/pkg/util/validation"
pb "sigs.k8s.io/node-feature-discovery/pkg/labeler"
"sigs.k8s.io/node-feature-discovery/pkg/version"
@ -82,6 +83,7 @@ type Annotations map[string]string
// Command line arguments
type Args struct {
labelWhiteList string
caFile string
configFile string
noPublish bool
options string
@ -117,8 +119,17 @@ func main() {
}
// Connect to NFD server
opts := []grpc.DialOption{grpc.WithInsecure()}
conn, err := grpc.Dial(args.server, opts...)
dialOpts := []grpc.DialOption{}
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 {
stderrLogger.Fatalf("failed to connect: %v", err)
}
@ -160,7 +171,7 @@ func argsParse(argv []string) (Args, error) {
Usage:
%s [--no-publish] [--sources=<sources>] [--label-whitelist=<pattern>]
[--oneshot | --sleep-interval=<seconds>] [--config=<path>]
[--options=<config>] [--server=<server>]
[--options=<config>] [--ca-file=<path>] [--server=<server>]
%s -h | --help
%s --version
@ -174,6 +185,8 @@ func argsParse(argv []string) (Args, error) {
config file (i.e. json or yaml). These options
will override settings read from the config file.
[Default: ]
--ca-file=<path> Root certificate for verifying connections
[Default: ]
--server=<server> NFD server address to connecto to.
[Default: localhost:8080]
--sources=<sources> Comma separated list of feature sources.
@ -197,6 +210,7 @@ func argsParse(argv []string) (Args, error) {
// Parse argument values as usable types.
var err error
args.caFile = arguments["--ca-file"].(string)
args.configFile = arguments["--config"].(string)
args.noPublish = arguments["--no-publish"].(bool)
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() {
args, err := argsParse([]string{"--no-publish", "--sources=fake1,fake2,fake3"})
Convey("When valid args are specified", func() {
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() {
So(args.noPublish, ShouldBeTrue)
So(args.caFile, ShouldEqual, "ca")
So(args.sources, ShouldResemble, []string{"fake1", "fake2", "fake3"})
So(len(args.labelWhiteList), ShouldEqual, 0)
So(err, ShouldBeNil)

View file

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

View file

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