Skip to content

Commit

Permalink
add cert pkg to implement mtls
Browse files Browse the repository at this point in the history
Signed-off-by: rksharma95 <ramakant@accuknox.com>
  • Loading branch information
rksharma95 committed Feb 19, 2024
1 parent 1e31e1a commit 4cb41b1
Show file tree
Hide file tree
Showing 6 changed files with 498 additions and 5 deletions.
211 changes: 211 additions & 0 deletions KubeArmor/cert/cert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2022 Authors of KubeArmor

// Package cert is responsible for generating certs dynamically and loading the certs from external sources.

package cert

import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net"
"os"
"path/filepath"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)

const (
// ORG kubearmor
KubeArmor_ORG string = "kubearmor"
KubeArmor_CN string = "kubearmor"
)

var DefaultKubeArmorServerConfig = CertConfig{
CN: KubeArmor_CN,
Organization: KubeArmor_ORG,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
}

var DefaultKubeArmorClientConfig = CertConfig{
CN: KubeArmor_CN,
Organization: KubeArmor_ORG,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
}

var DefaultKubeArmorCAConfig = CertConfig{
CN: KubeArmor_CN,
Organization: KubeArmor_ORG,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
}

type CertConfig struct {
CN string // Common Name
Organization string
DNS []string
IPs []string
KeyUsage x509.KeyUsage
ExtKeyUsage []x509.ExtKeyUsage
NotAfter time.Time
}

// CertKeyPair type
type CertKeyPair struct {
Crt *x509.Certificate
Key *rsa.PrivateKey
}

// CertBytes type
type CertBytes struct {
Crt []byte
Key []byte
}

type CertPath struct {
Base string
CertFile string
KeyFile string // Not Required if CertOnly:true
CertOnly bool // if true read certificate only
}

func GetCertKeyPairFromCertBytes(certBytes *CertBytes) (*CertKeyPair, error) {
// Parse CA certificate and key
certPem, _ := pem.Decode(certBytes.Crt)
keyPem, _ := pem.Decode(certBytes.Key)

crt, err := x509.ParseCertificate(certPem.Bytes)
if err != nil {
fmt.Println("Error parsing CA certificate:", err)
return nil, err
}

key, err := x509.ParsePKCS1PrivateKey(keyPem.Bytes)
if err != nil {
fmt.Println("Error parsing CA private key:", err)
return nil, err
}

return &CertKeyPair{
Crt: crt,
Key: key,
}, nil
}

// ReadCertFromFile func reads certificate key pair from the given path
func ReadCertFromFile(certPath *CertPath) (*CertBytes, error) {
var certBytes, keyBytes []byte
certFile := filepath.Clean(filepath.Join(certPath.Base, certPath.CertFile))
keyFile := filepath.Clean(filepath.Join(certPath.Base, certPath.KeyFile))

// Check if certificate file exists
if _, err := os.Stat(certFile); os.IsNotExist(err) {
fmt.Println("certificate file does not exist:", err)
return nil, err
}

certBytes, err := os.ReadFile(certFile)
if err != nil {
fmt.Println("Error reading CA certificate file:", err)
return nil, err
}

if !certPath.CertOnly {
// Check if key file exists
if _, err := os.Stat(keyFile); os.IsNotExist(err) {
fmt.Println("CA key file does not exist:", err)
return nil, err
}

keyBytes, err = os.ReadFile(keyFile)
if err != nil {
fmt.Println("Error reading CA key file:", err)
return nil, err
}
}

return &CertBytes{
Crt: certBytes,
Key: keyBytes,
}, nil
}

// ReadCertFromK8sSecret func reads cert from the k8s tls secret
// it assumes the cert and key file exists with tls.crt and tls.key names respectively
// that is true in case of kubernetes.io/tls secret type,
// https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets
func ReadCertFromK8sSecret(client *kubernetes.Clientset, namespace, secret string) (*CertBytes, error) {

// get secret
cert, err := client.CoreV1().Secrets(namespace).Get(context.Background(), secret, metav1.GetOptions{})
if err != nil {
return nil, err
}

return &CertBytes{
Crt: cert.Data["tls.crt"],
Key: cert.Data["tls.key"],
}, nil
}

func GenerateCert(cfg *CertConfig) (*CertKeyPair, error) {
// Generate a new RSA private key for the server
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
fmt.Println("error generating cert private key:", err)
return nil, err
}

// Create a template for the certificate
template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: cfg.CN, Organization: []string{cfg.Organization}},
NotBefore: time.Now(),
NotAfter: cfg.NotAfter,
KeyUsage: cfg.KeyUsage,
ExtKeyUsage: cfg.ExtKeyUsage,
BasicConstraintsValid: true,
}

for _, ip := range cfg.IPs {
template.IPAddresses = append(template.IPAddresses, net.ParseIP(ip))
}

return &CertKeyPair{Crt: &template, Key: key}, nil
}

// GenerateSelfSignedCert func generates cert and key signed by provided CA
func GenerateSelfSignedCert(ca *CertKeyPair, cfg *CertConfig) (*CertBytes, error) {
certKeyPair, err := GenerateCert(cfg)
if err != nil {
return nil, err
}

// Create the certificate signed by the CA
certBytes, err := x509.CreateCertificate(rand.Reader, certKeyPair.Crt, ca.Crt, &certKeyPair.Key.PublicKey, ca.Key)
if err != nil {
fmt.Println("Error creating certificate:", err)
return nil, err
}

certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes})
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(certKeyPair.Key)})

if certPEM == nil || keyPEM == nil {
return nil, fmt.Errorf("error encoding certificate")
}

return &CertBytes{
Crt: certPEM,
Key: keyPEM,
}, nil
}
108 changes: 108 additions & 0 deletions KubeArmor/cert/certloader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2022 Authors of KubeArmor

// Package cert is responsible for generating certs dynamically and loading the certs from external sources.
package cert

import (
"crypto/tls"
"crypto/x509"

"k8s.io/client-go/kubernetes"
)

type CertLoader interface {

Check warning on line 14 in KubeArmor/cert/certloader.go

View workflow job for this annotation

GitHub Actions / go-lint

type name will be used as cert.CertLoader by other packages, and that stutters; consider calling this Loader

Check warning on line 14 in KubeArmor/cert/certloader.go

View workflow job for this annotation

GitHub Actions / go-lint

exported type CertLoader should have comment or be unexported
GetCertificateAndCaPool() (*tls.Certificate, *x509.CertPool, error)
}

// generate self sign certificate dynamically
type SelfSignedCertLoader struct {
CaCertPath CertPath
CertConfig CertConfig
}

func (loader *SelfSignedCertLoader) GetCertificateAndCaPool() (*tls.Certificate, *x509.CertPool, error) {
// load ca certificate and ca key from given path
caCertBytes, err := ReadCertFromFile(&loader.CaCertPath)
if err != nil {
return nil, nil, err
}
caCert, err := GetCertKeyPairFromCertBytes(caCertBytes)
if err != nil {
return nil, nil, err
}
caCertPool := x509.NewCertPool()
caCertPool.AddCert(caCert.Crt)
// create certificate signed with provided ca certificate
certBytes, err := GenerateSelfSignedCert(caCert, &loader.CertConfig)
if err != nil {
return nil, nil, err
}
cert, err := tls.X509KeyPair(certBytes.Crt, certBytes.Key)
if err != nil {
return nil, nil, err
}
return &cert, caCertPool, nil
}

// load certificates provided by external source using file
type ExternalCertLoader struct {
CaCertPath CertPath
CertPath CertPath
}

func (loader *ExternalCertLoader) GetCertificateAndCaPool() (*tls.Certificate, *x509.CertPool, error) {
// load ca certificate from cert path, assuming only ca.crt is present
loader.CaCertPath.CertOnly = true
caCertBytes, err := ReadCertFromFile(&loader.CaCertPath)
if err != nil {
return nil, nil, err
}
caCert, err := GetCertKeyPairFromCertBytes(caCertBytes)
if err != nil {
return nil, nil, err
}
caCertPool := x509.NewCertPool()
caCertPool.AddCert(caCert.Crt)
// load server/client certificate from cert path
certBytes, err := ReadCertFromFile(&loader.CertPath)
if err != nil {
return nil, nil, err
}
cert, err := tls.X509KeyPair(certBytes.Crt, certBytes.Key)
if err != nil {
return nil, nil, err
}
return &cert, caCertPool, nil
}

type K8sCertLoader struct {
CertConfig CertConfig
K8sClient *kubernetes.Clientset
Namespace string
Secret string
}

func (loader *K8sCertLoader) GetCertificateAndCaPool() (*tls.Certificate, *x509.CertPool, error) {
// load certificate from k8s secret
caCertBytes, err := ReadCertFromK8sSecret(loader.K8sClient, loader.Namespace, loader.Secret)
if err != nil {
return nil, nil, err
}
caCert, err := GetCertKeyPairFromCertBytes(caCertBytes)
if err != nil {
return nil, nil, err
}
caCertPool := x509.NewCertPool()
caCertPool.AddCert(caCert.Crt)
// create certificate signed with provided ca certificate
certBytes, err := GenerateSelfSignedCert(caCert, &loader.CertConfig)
if err != nil {
return nil, nil, err
}
cert, err := tls.X509KeyPair(certBytes.Crt, certBytes.Key)
if err != nil {
return nil, nil, err
}
return &cert, caCertPool, nil
}
Loading

0 comments on commit 4cb41b1

Please sign in to comment.