diff --git a/Documentation/config.md b/Documentation/config.md index 5c617290..5c605b03 100644 --- a/Documentation/config.md +++ b/Documentation/config.md @@ -12,6 +12,7 @@ Configuration arguments can be provided as flags or as environment variables. | -rpc-address | BOOTCFG_RPC_ADDRESS | (gRPC API disabled) | 127.0.0.1:8081 | | -cert-file | BOOTCFG_CERT_FILE | /etc/bootcfg/server.crt | ./examples/etc/bootcfg/server.crt | | -key-file | BOOTCFG_KEY_FILE | /etc/bootcfg/server.key | ./examples/etc/bootcfg/server.key +| -ca-file | BOOTCFG_CA_FILE | /etc/bootcfg/ca.crt | ./examples/etc/bootcfg/ca.crt | | -key-ring-path | BOOTCFG_KEY_RING_PATH | (no key ring) | ~/.secrets/vault/bootcfg/secring.gpg | | (no flag) | BOOTCFG_PASSPHRASE | (no passphrase) | "secret passphrase" | @@ -27,7 +28,9 @@ Configuration arguments can be provided as flags or as environment variables. |:---------|:--------------------------------------------------| | CA certificate | /etc/bootcfg/ca.crt | | Server certificate | /etc/bootcfg/server.crt | -| Server Private Key | /etc/bootcfg/server.key | +| Server private key | /etc/bootcfg/server.key | +| Client certificate | /etc/bootcfg/client.crt | +| Client private key | /etc/bootcfg/client.key | ## Version @@ -63,7 +66,7 @@ Run the Docker image. Mounts are used to add the provided examples. ### gRPC API -The gRPC API can be enabled with the `-rpc-address` flag and by providing a TLS server certificate and key with `-cert-file` and `-key-file`. gRPC clients (such as `bootcmd`) must verify the server's certificate with a CA bundle. +The gRPC API can be enabled with the `-rpc-address` flag and by providing a TLS server certificate and key with `-cert-file` and `-key-file` and a CA certificate for authenticating clients with `-ca-file`. gRPC clients (such as `bootcmd`) must verify the server's certificate with a CA bundle passed via `-ca-file` and present a client certificate and key via `-cert-file` and `-key-file`. Run the ACI with rkt and TLS credentials from `examples/etc/bootcfg`. @@ -71,7 +74,7 @@ Run the ACI with rkt and TLS credentials from `examples/etc/bootcfg`. A `bootcmd` client can call the gRPC API running at the IP used in the rkt example. - ./bin/bootcmd profile list --endpoints 172.15.0.2:8081 --ca-file examples/etc/bootcfg/ca.crt + ./bin/bootcmd profile list --endpoints 172.15.0.2:8081 --ca-file examples/etc/bootcfg/ca.crt --cert-file examples/etc/bootcfg/client.crt --key-file examples/etc/bootcfg/client.key Run the Docker image with TLS credentials from `examples/etc/bootcfg`. @@ -79,7 +82,7 @@ Run the Docker image with TLS credentials from `examples/etc/bootcfg`. A `bootcmd` client can call the gRPC API running at the IP used in the Docker example. - ./bin/bootcmd profile list --endpoints 127.0.0.1:8081 --ca-file examples/etc/bootcfg/root.crt + ./bin/bootcmd profile list --endpoints 127.0.0.1:8081 --ca-file examples/etc/bootcfg/ca.crt --cert-file examples/etc/bootcfg/client.crt --key-file examples/etc/bootcfg/client.key #### With [OpenPGP Signing](openpgp.md) diff --git a/bootcfg/cli/root.go b/bootcfg/cli/root.go index 4945c190..d962c51a 100644 --- a/bootcfg/cli/root.go +++ b/bootcfg/cli/root.go @@ -22,15 +22,20 @@ To get help about a resource or command, run "bootcmd help resource"`, // globalFlags can be set for any subcommand. globalFlags = struct { - Endpoints []string - CAFile string + endpoints []string + caFile string + certFile string + keyFile string }{} ) func init() { - RootCmd.PersistentFlags().StringSliceVar(&globalFlags.Endpoints, "endpoints", []string{"127.0.0.1:8081"}, "gRPC Endpoints") - // gRPC Client TLS - RootCmd.PersistentFlags().StringVar(&globalFlags.CAFile, "ca-file", "/etc/bootcfg/ca.crt", "Path to the CA bundle to verify certificates of TLS servers") + RootCmd.PersistentFlags().StringSliceVar(&globalFlags.endpoints, "endpoints", []string{"127.0.0.1:8081"}, "gRPC Endpoints") + // gRPC TLS Server Verification + RootCmd.PersistentFlags().StringVar(&globalFlags.caFile, "ca-file", "/etc/bootcfg/ca.crt", "Path to the CA bundle to verify certificates of TLS servers") + // gRPC TLS Client Authentication + RootCmd.PersistentFlags().StringVar(&globalFlags.certFile, "cert-file", "/etc/bootcfg/client.crt", "Path to the client TLS certificate file") + RootCmd.PersistentFlags().StringVar(&globalFlags.keyFile, "key-file", "/etc/bootcfg/client.key", "Path to the client TLS key file") cobra.EnablePrefixMatching = true } @@ -81,5 +86,18 @@ func tlsInfoFromCmd(cmd *cobra.Command) *tlsutil.TLSInfo { if err != nil { exitWithError(ExitBadArgs, err) } - return &tlsutil.TLSInfo{CAFile: caFile} + certFile, err := cmd.Flags().GetString("cert-file") + if err != nil { + exitWithError(ExitBadArgs, err) + } + keyFile, err := cmd.Flags().GetString("key-file") + if err != nil { + exitWithError(ExitBadArgs, err) + } + + return &tlsutil.TLSInfo{ + CAFile: caFile, + CertFile: certFile, + KeyFile: keyFile, + } } diff --git a/bootcfg/tlsutil/info.go b/bootcfg/tlsutil/info.go index b13713f2..846e5463 100644 --- a/bootcfg/tlsutil/info.go +++ b/bootcfg/tlsutil/info.go @@ -4,7 +4,7 @@ import ( "crypto/tls" ) -// TLSInfo prepares tls.Config's from TLS file inputs. +// TLSInfo prepares tls.Config's from TLS filename inputs. type TLSInfo struct { CAFile string CertFile string @@ -13,29 +13,49 @@ type TLSInfo struct { // ClientConfig returns a tls.Config for client use. func (info *TLSInfo) ClientConfig() (*tls.Config, error) { + // CA for verifying the server pool, err := NewCertPool([]string{info.CAFile}) if err != nil { return nil, err } + // client certificate (for authentication) + cert, err := tls.LoadX509KeyPair(info.CertFile, info.KeyFile) + if err != nil { + return nil, err + } + return &tls.Config{ MinVersion: tls.VersionTLS12, InsecureSkipVerify: false, // CA bundle the client should trust when verifying a server RootCAs: pool, + // Client certificates to authenticate to the server + Certificates: []tls.Certificate{cert}, }, nil } // ServerConfig returns a tls.Config for server use. func (info *TLSInfo) ServerConfig() (*tls.Config, error) { + // server certificate to present to clients cert, err := tls.LoadX509KeyPair(info.CertFile, info.KeyFile) if err != nil { return nil, err } + // CA for authenticating clients + pool, err := NewCertPool([]string{info.CAFile}) + if err != nil { + return nil, err + } + return &tls.Config{ MinVersion: tls.VersionTLS12, // Certificates the server should present to clients Certificates: []tls.Certificate{cert}, + // Client Authentication (required) + ClientAuth: tls.RequireAndVerifyClientCert, + // CA for verifying and authorizing client certificates + ClientCAs: pool, }, nil } diff --git a/cmd/bootcfg/main.go b/cmd/bootcfg/main.go index f1bde775..8d9a9e5c 100644 --- a/cmd/bootcfg/main.go +++ b/cmd/bootcfg/main.go @@ -34,6 +34,7 @@ func main() { logLevel string certFile string keyFile string + caFile string keyRingPath string version bool help bool @@ -49,6 +50,8 @@ func main() { // gRPC Server TLS flag.StringVar(&flags.certFile, "cert-file", "/etc/bootcfg/server.crt", "Path to the server TLS certificate file") flag.StringVar(&flags.keyFile, "key-file", "/etc/bootcfg/server.key", "Path to the server TLS key file") + // TLS Client Authentication + flag.StringVar(&flags.caFile, "ca-file", "/etc/bootcfg/ca.crt", "Path to the CA verify and authenticate client certificates") // Signing flag.StringVar(&flags.keyRingPath, "key-ring-path", "", "Path to a private keyring file") @@ -133,6 +136,7 @@ func main() { tlsinfo := tlsutil.TLSInfo{ CertFile: flags.certFile, KeyFile: flags.keyFile, + CAFile: flags.caFile, } tlscfg, err := tlsinfo.ServerConfig() if err != nil {