Skip to content

Commit

Permalink
add tls authentication for transport
Browse files Browse the repository at this point in the history
Signed-off-by: Alay Patel <[email protected]>
  • Loading branch information
alaypatel07 committed Nov 18, 2021
1 parent 402af80 commit e10d983
Show file tree
Hide file tree
Showing 5 changed files with 347 additions and 81 deletions.
80 changes: 62 additions & 18 deletions transport/stunnel/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/backube/pvc-transfer/endpoint"
"github.com/backube/pvc-transfer/internal/utils"
"github.com/backube/pvc-transfer/transport"
"github.com/backube/pvc-transfer/transport/tls/certs"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -20,7 +21,7 @@ import (

const (
// TCP_NODELAY=1 bypasses Nagle's Delay algorithm
// this means that the tcp stack does not way of receiving an acc
// this means that the tcp stack does not wait for receiving an ack
// before sending the next packet https://en.wikipedia.org/wiki/Nagle%27s_algorithm
// At scale setting/unsetting this option might drive different network characteristics
stunnelServerConfTemplate = `foreground = yes
Expand All @@ -34,6 +35,8 @@ accept = {{ $.acceptPort }}
connect = {{ $.connectPort }}
key = /etc/stunnel/certs/tls.key
cert = /etc/stunnel/certs/tls.crt
CAfile = /etc/stunnel/certs/ca.crt
verify = 2
TIMEOUTclose = 0
`
stunnelConnectPort = 8080
Expand Down Expand Up @@ -191,7 +194,7 @@ func (s *server) prefixedName(name string) string {
}

func (s *server) reconcileSecret(ctx context.Context, c ctrlclient.Client) error {
_, _, found, err := getExistingCert(ctx, c, s.logger, s.namespacedName, s.secretNameSuffix())
_, _, found, err := getExistingCert(ctx, c, s.logger, s.namespacedName, serverSecretNameSuffix())
if found {
return nil
}
Expand All @@ -201,36 +204,77 @@ func (s *server) reconcileSecret(ctx context.Context, c ctrlclient.Client) error
return err
}

_, newCrt, newKey, err := transport.GenerateSSLCert()
crtBundle, err := certs.New()
if err != nil {
s.logger.Error(err, "error generating ssl certs for stunnel server")
return err
}

stunnelSecret := &corev1.Secret{
crtBundleSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: s.NamespacedName().Namespace,
Name: getResourceName(s.namespacedName, s.secretNameSuffix()),
Name: getResourceName(s.namespacedName, caBundleSecretNameSuffix()),
},
}
_, err = controllerutil.CreateOrUpdate(ctx, c, crtBundleSecret, func() error {
crtBundleSecret.Labels = s.options.Labels
crtBundleSecret.OwnerReferences = s.options.Owners

crtBundleSecret.Data = map[string][]byte{
"server.crt": crtBundle.ServerCrt.Bytes(),
"server.key": crtBundle.ServerKey.Bytes(),
"client.crt": crtBundle.ClientCrt.Bytes(),
"client.key": crtBundle.ClientKey.Bytes(),
"ca.crt": crtBundle.CACrt.Bytes(),
"ca.key": crtBundle.CAKey.Bytes(),
}
return nil
})
if err != nil {
return err
}

_, err = controllerutil.CreateOrUpdate(ctx, c, stunnelSecret, func() error {
stunnelSecret.Labels = s.options.Labels
stunnelSecret.OwnerReferences = s.options.Owners
serverSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: s.NamespacedName().Namespace,
Name: getResourceName(s.namespacedName, serverSecretNameSuffix()),
},
}
_, err = controllerutil.CreateOrUpdate(ctx, c, serverSecret, func() error {
serverSecret.Labels = s.options.Labels
serverSecret.OwnerReferences = s.options.Owners

serverSecret.Data = map[string][]byte{
"tls.crt": crtBundle.ServerCrt.Bytes(),
"tls.key": crtBundle.ServerKey.Bytes(),
"ca.crt": crtBundle.CACrt.Bytes(),
}
return nil
})
if err != nil {
return err
}

stunnelSecret.Data = map[string][]byte{
"tls.crt": newCrt.Bytes(),
"tls.key": newKey.Bytes(),
clientSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: s.NamespacedName().Namespace,
Name: getResourceName(s.namespacedName, clientSecretNameSuffix()),
},
}
_, err = controllerutil.CreateOrUpdate(ctx, c, clientSecret, func() error {
clientSecret.Labels = s.options.Labels
clientSecret.OwnerReferences = s.options.Owners

clientSecret.Data = map[string][]byte{
"tls.crt": crtBundle.ClientCrt.Bytes(),
"tls.key": crtBundle.ClientKey.Bytes(),
"ca.crt": crtBundle.CACrt.Bytes(),
}
return nil
})
return err
}

func (s *server) secretNameSuffix() string {
return "server-" + stunnelSecret
}

func (s *server) serverContainers() []corev1.Container {
return []corev1.Container{
{
Expand All @@ -254,7 +298,7 @@ func (s *server) serverContainers() []corev1.Container {
SubPath: "stunnel.conf",
},
{
Name: getResourceName(s.namespacedName, s.secretNameSuffix()),
Name: getResourceName(s.namespacedName, serverSecretNameSuffix()),
MountPath: "/etc/stunnel/certs",
},
},
Expand All @@ -275,10 +319,10 @@ func (s *server) serverVolumes() []corev1.Volume {
},
},
{
Name: getResourceName(s.namespacedName, s.secretNameSuffix()),
Name: getResourceName(s.namespacedName, serverSecretNameSuffix()),
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: getResourceName(s.namespacedName, s.secretNameSuffix()),
SecretName: getResourceName(s.namespacedName, serverSecretNameSuffix()),
Items: []corev1.KeyToPath{
{
Key: "tls.crt",
Expand Down
14 changes: 13 additions & 1 deletion transport/stunnel/stunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,21 @@ const (

const (
TransportTypeStunnel transport.Type = "stunnel"
Container = "stunnel"
Container = "stunnel"
)

func serverSecretNameSuffix() string {
return "server-" + stunnelSecret
}

func clientSecretNameSuffix() string {
return "client-" + stunnelSecret
}

func caBundleSecretNameSuffix() string {
return "ca-bundle-" + stunnelSecret
}

func getImage(options *transport.Options) string {
if options.Image == "" {
return defaultStunnelImage
Expand Down
168 changes: 168 additions & 0 deletions transport/tls/certs/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package certs

import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"time"
)

var (
keySize = 2048
defaultCASubject = &pkix.Name{
Country: []string{"US"},
Province: []string{"NC"},
Locality: []string{"RDU"},
Organization: []string{"Backube"},
OrganizationalUnit: []string{"Engineering"},
// This does not have to be a domain name, but certain implementations/configuration
// verify the requests from other side using this and SAN fields.
CommonName: "ca.backube.dev",
}
defaultCrtSubject = &pkix.Name{
Country: []string{"US"},
Province: []string{"NC"},
Locality: []string{"RDU"},
Organization: []string{"Backube"},
OrganizationalUnit: []string{"Engineering"},
CommonName: "cert.backube.dev",
}
)

// CertificateBundle stores the data used for creating a secret with tls bundle
// that includes a self signed CA (crt and key) as well as client and server certs
// (cert and key).
type CertificateBundle struct {
caRSAKey *rsa.PrivateKey
caCrtTemplate *x509.Certificate

CACrt *bytes.Buffer
CAKey *bytes.Buffer
ServerCrt *bytes.Buffer
ServerKey *bytes.Buffer
ClientCrt *bytes.Buffer
ClientKey *bytes.Buffer
}

// New returns CertificateBundle after populating all the public fields. It should
// ideally be persisted in kubernetes objects (secrets) by consumers. If the secret is
// lost or deleted, New should be called again to get a fresh bundle.
func New() (*CertificateBundle, error) {
c := &CertificateBundle{}
var err error
c.CACrt, c.caRSAKey, c.caCrtTemplate, err = GenerateCA(defaultCASubject)
if err != nil {
return nil, err
}

c.CAKey, err = rsaKeyBytes(c.caRSAKey)

c.ServerCrt, c.ServerKey, err = Generate(defaultCrtSubject, *c.caCrtTemplate, *c.caRSAKey)
if err != nil {
return nil, err
}

c.ClientCrt, c.ClientKey, err = Generate(defaultCrtSubject, *c.caCrtTemplate, *c.caRSAKey)
if err != nil {
return nil, err
}
return c, nil
}

// GenerateCA take a subject and returns caCrt, caKey and caCrtTemplate
// The caKey and caCrtTemplate should be passed into Generate
// along with a similar subject except the CN name should be different from
// the CA.
func GenerateCA(subject *pkix.Name) (caCrt *bytes.Buffer, caKey *rsa.PrivateKey, caCrtTemplate *x509.Certificate, err error) {
if subject == nil {
subject = defaultCASubject
}
caCrtTemplate = &x509.Certificate{
SerialNumber: big.NewInt(2021),
Subject: *subject,
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageAny},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
caCrt, caKey, err = createCrtKeyPair(caCrtTemplate, nil, nil)
if err != nil {
return
}
return
}

// Generate takes a subject, caCrtTemplate and caKey and returns crt, key and error
// if error is not nil, do not rely on crt or keys being not nil.
func Generate(subject *pkix.Name, caCrtTemplate x509.Certificate, caKey rsa.PrivateKey) (crt *bytes.Buffer, key *bytes.Buffer, err error) {
crtTemplate := &x509.Certificate{
SerialNumber: big.NewInt(2020),
Subject: *subject,
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
}

crt, rsaKey, err := createCrtKeyPair(crtTemplate, &caCrtTemplate, &caKey)
if err != nil {
return
}
key, err = rsaKeyBytes(rsaKey)
if err != nil {
return
}
return
}

func createCrtKeyPair(crtTemplate, parent *x509.Certificate, signer *rsa.PrivateKey) (crt *bytes.Buffer, key *rsa.PrivateKey, err error) {
key, err = rsa.GenerateKey(rand.Reader, keySize)
if err != nil {
return
}
if parent == nil {
parent = crtTemplate
}
if signer == nil {
signer = key
}

crtBytes, err := x509.CreateCertificate(
rand.Reader,
crtTemplate,
parent,
&key.PublicKey,
signer,
)
if err != nil {
return
}

crt = new(bytes.Buffer)
err = pem.Encode(crt, &pem.Block{
Type: "CERTIFICATE",
Bytes: crtBytes,
})
if err != nil {
return nil, nil, err
}
return
}

func rsaKeyBytes(key *rsa.PrivateKey) (keyBytes *bytes.Buffer, err error) {
keyBytes = new(bytes.Buffer)
err = pem.Encode(keyBytes, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
})
if err != nil {
return
}
return
}
Loading

0 comments on commit e10d983

Please sign in to comment.