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:
parent
97694c15d8
commit
bca194f6e6
6 changed files with 66 additions and 9 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue