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