Skip to content

Commit

Permalink
gobgp,gobgpd: implement TLS client authentication.
Browse files Browse the repository at this point in the history
  • Loading branch information
icedream committed Mar 7, 2023
1 parent 4f52a30 commit 81ad318
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 20 deletions.
107 changes: 102 additions & 5 deletions cmd/gobgp/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ package main
import (
"bytes"
"context"
"crypto"
"crypto/tls"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"net"
"os"
Expand Down Expand Up @@ -191,19 +196,111 @@ func extractReserved(args []string, keys map[string]int) (map[string][]string, e
return m, nil
}

func loadCertificatePEM(filePath string) (*x509.Certificate, error) {
content, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}

rest := content
var block *pem.Block
var cert *x509.Certificate
for len(rest) > 0 {
block, rest = pem.Decode(content)
if block == nil {
// no PEM data found, rest will not have been modified
break
}
content = rest
switch block.Type {
case "CERTIFICATE":
cert, err = x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, err
}
return cert, err
default:
// not the PEM block we're looking for
continue
}
}
return nil, errors.New("no certificate PEM block found")
}

func loadKeyPEM(filePath string) (crypto.PrivateKey, error) {
content, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}

rest := content
var block *pem.Block
var key crypto.PrivateKey
for len(rest) > 0 {
block, rest = pem.Decode(content)
if block == nil {
// no PEM data found, rest will not have been modified
break
}
switch block.Type {
case "RSA PRIVATE KEY":
key, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return key, err
case "PRIVATE KEY":
key, err = x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return key, err
case "EC PRIVATE KEY":
key, err = x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return key, err
default:
// not the PEM block we're looking for
continue
}
}
return nil, errors.New("no private key PEM block found")
}

func newClient(ctx context.Context) (api.GobgpApiClient, context.CancelFunc, error) {
grpcOpts := []grpc.DialOption{grpc.WithBlock()}
if globalOpts.TLS {
var creds credentials.TransportCredentials
if globalOpts.CaFile == "" {
creds = credentials.NewClientTLSFromCert(nil, "")
} else {
var err error
creds, err = credentials.NewClientTLSFromFile(globalOpts.CaFile, "")
tlsConfig := new(tls.Config)
if len(globalOpts.CaFile) != 0 {
pemCerts, err := os.ReadFile(globalOpts.CaFile)
if err != nil {
exitWithError(err)
}
tlsConfig.RootCAs = x509.NewCertPool()
if !tlsConfig.RootCAs.AppendCertsFromPEM(pemCerts) {
exitWithError(errors.New("no valid CA certificates to load"))
}
}
if len(globalOpts.ClientCertFile) != 0 && len(globalOpts.ClientKeyFile) != 0 {
cert, err := loadCertificatePEM(globalOpts.ClientCertFile)
if err != nil {
exitWithError(fmt.Errorf("failed to load client certificate: %w", err))
}
key, err := loadKeyPEM(globalOpts.ClientKeyFile)
if err != nil {
exitWithError(fmt.Errorf("failed to load client key: %w", err))
}
tlsConfig.Certificates = []tls.Certificate{
{
Certificate: [][]byte{cert.Raw},
PrivateKey: key,
},
}
}
creds = credentials.NewTLS(tlsConfig)
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(creds))
} else {
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
Expand Down
32 changes: 19 additions & 13 deletions cmd/gobgp/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,25 @@ import (
)

var globalOpts struct {
Host string
Port int
Target string
Debug bool
Quiet bool
Json bool
GenCmpl bool
BashCmplFile string
PprofPort int
TLS bool
CaFile string
Host string
Port int
Target string
Debug bool
Quiet bool
Json bool
GenCmpl bool
BashCmplFile string
PprofPort int
TLS bool
ClientCertFile string
ClientKeyFile string
CaFile string
}

var client api.GobgpApiClient
var ctx context.Context
var (
client api.GobgpApiClient
ctx context.Context
)

func newRootCmd() *cobra.Command {
cobra.EnablePrefixMatching = true
Expand Down Expand Up @@ -92,6 +96,8 @@ func newRootCmd() *cobra.Command {
rootCmd.PersistentFlags().StringVarP(&globalOpts.BashCmplFile, "bash-cmpl-file", "", "gobgp-completion.bash", "bash cmpl filename")
rootCmd.PersistentFlags().IntVarP(&globalOpts.PprofPort, "pprof-port", "r", 0, "pprof port")
rootCmd.PersistentFlags().BoolVarP(&globalOpts.TLS, "tls", "", false, "connection uses TLS if true, else plain TCP")
rootCmd.PersistentFlags().StringVarP(&globalOpts.ClientCertFile, "tls-client-cert-file", "", "", "Optional file path to TLS client certificate")
rootCmd.PersistentFlags().StringVarP(&globalOpts.ClientKeyFile, "tls-client-key-file", "", "", "Optional file path to TLS client key")
rootCmd.PersistentFlags().StringVarP(&globalOpts.CaFile, "tls-ca-file", "", "", "The file containing the CA root cert file")

globalCmd := newGlobalCmd()
Expand Down
24 changes: 22 additions & 2 deletions cmd/gobgpd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package main

import (
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net/http"
Expand Down Expand Up @@ -63,6 +65,7 @@ func main() {
TLS bool `long:"tls" description:"enable TLS authentication for gRPC API"`
TLSCertFile string `long:"tls-cert-file" description:"The TLS cert file"`
TLSKeyFile string `long:"tls-key-file" description:"The TLS key file"`
TLSClientCAFile string `long:"tls-client-ca-file" description:"Optional TLS client CA file to authenticate clients against"`
Version bool `long:"version" description:"show version number"`
}
_, err := flags.Parse(&opts)
Expand Down Expand Up @@ -142,10 +145,27 @@ func main() {
maxSize := 256 << 20
grpcOpts := []grpc.ServerOption{grpc.MaxRecvMsgSize(maxSize), grpc.MaxSendMsgSize(maxSize)}
if opts.TLS {
creds, err := credentials.NewServerTLSFromFile(opts.TLSCertFile, opts.TLSKeyFile)
// server cert/key
cert, err := tls.LoadX509KeyPair(opts.TLSCertFile, opts.TLSKeyFile)
if err != nil {
logger.Fatalf("Failed to generate credentials: %v", err)
logger.Fatalf("Failed to load server certificate/keypair: %v", err)
}
tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}}

// client CA
if len(opts.TLSClientCAFile) != 0 {
tlsConfig.ClientCAs = x509.NewCertPool()
pemCerts, err := os.ReadFile(opts.TLSClientCAFile)
if err != nil {
logger.Fatalf("Failed to load client CA certificates from %q: %v", opts.TLSClientCAFile, err)
}
if ok := tlsConfig.ClientCAs.AppendCertsFromPEM(pemCerts); !ok {
logger.Fatalf("No valid client CA certificates in %q", opts.TLSClientCAFile)
}
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
}

creds := credentials.NewTLS(tlsConfig)
grpcOpts = append(grpcOpts, grpc.Creds(creds))
}

Expand Down

0 comments on commit 81ad318

Please sign in to comment.