Skip to content

Commit

Permalink
Merge pull request #472 from kube-tarian/agent-ca-cert-issuer
Browse files Browse the repository at this point in the history
cert issuer
  • Loading branch information
vramk23 authored May 2, 2024
2 parents e739bdb + 3df7b70 commit d96d6cb
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 7 deletions.
8 changes: 6 additions & 2 deletions capten/agent/internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import (
"os/signal"
"syscall"

"google.golang.org/grpc"

"github.com/intelops/go-common/logging"
agentapi "github.com/kube-tarian/kad/capten/agent/internal/api"
captenstore "github.com/kube-tarian/kad/capten/agent/internal/capten-store"
Expand All @@ -24,6 +22,7 @@ import (
"github.com/kube-tarian/kad/capten/common-pkg/cluster-plugins/clusterpluginspb"
pluginconfigtore "github.com/kube-tarian/kad/capten/common-pkg/pluginconfig-store"
"github.com/pkg/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)

Expand Down Expand Up @@ -90,6 +89,11 @@ func Start() {
}
}()

err = setupCACertIssuser(cfg.ClusterCAIssuerName)
if err != nil {
log.Fatalf("Failed to setupt CA Cert Issuer in cert-manager %v", err)
}

err = registerK8SWatcher(as)
if err != nil {
log.Fatalf("Failed to initialize k8s watchers %v", err)
Expand Down
148 changes: 148 additions & 0 deletions capten/agent/internal/app/ca_cert_issuer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package app

import (
"context"
"fmt"
"os"
"time"

v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned"
"github.com/kube-tarian/kad/capten/common-pkg/cert"
"github.com/kube-tarian/kad/capten/common-pkg/k8s"
"github.com/pkg/errors"
k8serror "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
)

const (
certFileName = "server.cert"
keyFileName = "server.key"
namespace = "capten"
serverCertSecretName = "agent-server-mtls"
)

func setupCACertIssuser(clusterIssuerName string) error {
k8sclient, err := k8s.NewK8SClient(log)
if err != nil {
log.Errorf("failed to initalize k8s client, %v", err)
return err
}

_, err = setupCertificateIssuer(k8sclient, clusterIssuerName)
if err != nil {
log.Errorf("Setup Certificates Issuer failed, %v", err)
return err
}

err = generateServerCertificates(k8sclient, clusterIssuerName)
if err != nil {
log.Errorf("Server certificates generation failed, %v", err)
return err
}

// r.RunTLS(fmt.Sprintf("%s:%d", cfg.Host, cfg.RestPort), certFileName, keyFileName)
// r.Run(fmt.Sprintf("%s:%d", cfg.Host, cfg.RestPort))
return nil
}

// Setup agent certificate issuer
func setupCertificateIssuer(k8sclient *k8s.K8SClient, clusterIssuerName string) (*cert.CertificatesData, error) {
// TODO: Check certificates exist in Vault and control plan cluster
// If exist skip
// Else
// 1. generate root certificates
// 2. Create Certificate Issuer
// 3. Store in Vault
certsData, err := cert.GenerateRootCerts()
if err != nil {
return nil, err
}

err = k8s.CreateOrUpdateClusterCAIssuerSecret(k8sclient, certsData.RootCert.CertData, certsData.RootKey.KeyData, certsData.CaChainCertData)
if err != nil {
return nil, fmt.Errorf("failed to create/update CA Issuer Secret: %v", err)
}

err = k8s.CreateOrUpdateClusterIssuer(clusterIssuerName)
if err != nil {
return nil, fmt.Errorf("failed to create/update CA Issuer %s in cert-manager: %v", clusterIssuerName, err)
}

return certsData, nil
}

func generateServerCertificates(k8sClient *k8s.K8SClient, clusterIssuerName string) error {
config, err := rest.InClusterConfig()
if err != nil {
return errors.WithMessage(err, "error while building kubeconfig")
}
cmClient, err := cmclient.NewForConfig(config)
if err != nil {
return err
}

err = k8sClient.CreateNamespace(context.Background(), namespace)
if err != nil {
return fmt.Errorf("failed to create namespace: %v", err)
}

err = generateCertManagerServerCertificate(cmClient, namespace, serverCertSecretName, clusterIssuerName)
if err != nil {
return fmt.Errorf("failed to genereate server certificate: %v", err)
}

// TODO: it may take some time for certificate to get create
// So have to keep wait and retry
time.Sleep(10 * time.Second)
secretData, err := k8sClient.GetSecretData(namespace, serverCertSecretName)
if err != nil {
return fmt.Errorf("failed to fetch certificates from secret, %v", err)
}

// Write certificates to files
os.WriteFile(certFileName, []byte(secretData.Data["cert"]), cert.FilePermission)
os.WriteFile(keyFileName, []byte(secretData.Data["key"]), cert.FilePermission)
return nil
}

func generateCertManagerServerCertificate(cmClient *cmclient.Clientset, namespace string, certName string, issuerRefName string) error {
usages := []v1.KeyUsage{v1.UsageDigitalSignature, v1.UsageKeyEncipherment, v1.UsageServerAuth}

_, err := cmClient.CertmanagerV1().Certificates(namespace).Create(
context.TODO(),
&v1.Certificate{
ObjectMeta: metav1.ObjectMeta{
Name: certName,
},
Spec: v1.CertificateSpec{
IssuerRef: cmmeta.ObjectReference{
Name: issuerRefName, // "capten-ca-issuer"
Kind: v1.ClusterIssuerKind,
},
SecretName: certName,
CommonName: certName,
Usages: usages,
PrivateKey: &v1.CertificatePrivateKey{
Algorithm: v1.RSAKeyAlgorithm,
Size: 2048,
Encoding: v1.PKCS1,
},
},
},
metav1.CreateOptions{},
)
if k8serror.IsAlreadyExists(err) {
log.Infof("%v Certificate already exists", certName)
return nil
}
if err != nil {
log.Infof("%v Certificate generation failed, reason: %v", certName, err)
} else {
log.Infof("%v Certificate generation successful", certName)
}

return err
}
1 change: 1 addition & 0 deletions capten/agent/internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type SericeConfig struct {
TektonSyncJobEnabled bool `envconfig:"TEKTON_SYNC_JOB_ENABLED" default:"true"`
TektonSyncJobInterval string `envconfig:"TEKTON_SYNC_JOB_INTERVAL" default:"@every 1h"`
DomainName string `envconfig:"DOMAIN_NAME" default:"example.com"`
ClusterCAIssuerName string `envconfig:"AGENT_CLUSTER_CA_ISSUER_NAME" default:"agent-ca-issuer"`
}

func GetServiceConfig() (*SericeConfig, error) {
Expand Down
91 changes: 91 additions & 0 deletions capten/common-pkg/cert/generate_certs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package cert

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

"github.com/pkg/errors"
)

const (
FilePermission os.FileMode = 0644
caBitSize = 4096
OrgName = "Intelops"
RootCACommonName = "Capten Agent Root CA"
ClusterCACertSecretName = "agent-ca-cert"
CertManagerNamespace = "cert-manager"
)

type Key struct {
Key *rsa.PrivateKey
KeyData []byte
}

type Cert struct {
Cert *x509.Certificate
CertData []byte
}

type CertificatesData struct {
RootKey *Key
RootCert *Cert
CaChainCertData []byte
}

func GenerateRootCerts() (*CertificatesData, error) {
rootKey, rootCertTemplate, err := generateCACert()
if err != nil {
return nil, err
}

return &CertificatesData{
RootKey: rootKey,
RootCert: rootCertTemplate,
CaChainCertData: rootCertTemplate.CertData,
}, nil
}

func generateCACert() (*Key, *Cert, error) { //(rootKey *rsa.PrivateKey, rootCertTemplate *x509.Certificate, err error) {
rootKey, err := rsa.GenerateKey(rand.Reader, caBitSize)
if err != nil {
err = errors.WithMessage(err, "failed to generate RSA key for root certificate")
return nil, nil, err
}

rootCertTemplate := &x509.Certificate{
Subject: pkix.Name{
Organization: []string{OrgName},
CommonName: RootCACommonName,
},
SerialNumber: big.NewInt(1),
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(5, 0, 0),
IsCA: true,
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}

rootCert, err := x509.CreateCertificate(rand.Reader, rootCertTemplate, rootCertTemplate, &rootKey.PublicKey, rootKey)
if err != nil {
err = errors.WithMessage(err, "failed to create root CA certificate")
return nil, nil, err
}

rootCertPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCert})
rootKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rootKey)})

return &Key{
Key: rootKey,
KeyData: rootKeyPEM,
},
&Cert{
Cert: rootCertTemplate,
CertData: rootCertPEM,
}, nil
}
13 changes: 13 additions & 0 deletions capten/common-pkg/cert/generate_certs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cert

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestGenerateRootCerts(t *testing.T) {
certInfo, err := GenerateRootCerts()
assert.NoError(t, err)
t.Log(certInfo)
}
79 changes: 79 additions & 0 deletions capten/common-pkg/k8s/cert_issuer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package k8s

import (
"context"

"github.com/intelops/go-common/logging"
"github.com/kube-tarian/kad/capten/common-pkg/cert"
"github.com/pkg/errors"

certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
)

var log = logging.NewLogger()

func CreateOrUpdateClusterIssuer(clusterCAIssuer string) error {
config, err := rest.InClusterConfig()
if err != nil {
return errors.WithMessage(err, "error while building kubeconfig")
}

cmClient, err := cmclient.NewForConfig(config)
if err != nil {
return err
}

issuer := &certmanagerv1.ClusterIssuer{
ObjectMeta: metav1.ObjectMeta{
Name: clusterCAIssuer,
},
Spec: certmanagerv1.IssuerSpec{
IssuerConfig: certmanagerv1.IssuerConfig{
CA: &certmanagerv1.CAIssuer{
SecretName: cert.ClusterCACertSecretName,
},
},
},
}

serverIssuer, err := cmClient.CertmanagerV1().ClusterIssuers().Get(context.Background(), issuer.Name, metav1.GetOptions{})
if err != nil && k8serrors.IsNotFound(err) {
result, err := cmClient.CertmanagerV1().ClusterIssuers().Create(context.Background(), issuer, metav1.CreateOptions{})
if err != nil {
return errors.WithMessage(err, "error in creating cert issuer")
}
log.Debugf("ClusterIssuer %s created successfully", result.Name)
return nil
}

serverIssuer.Spec.IssuerConfig.CA.SecretName = cert.ClusterCACertSecretName
issuerClient := cmClient.CertmanagerV1().ClusterIssuers()
result, err := issuerClient.Update(context.TODO(), serverIssuer, metav1.UpdateOptions{})
if err != nil {
return errors.WithMessage(err, "error while updating cluster issuer")
}
log.Debugf("ClusterIssuer %s updated successfully", result.Name)
return nil
}

func CreateOrUpdateClusterCAIssuerSecret(k8sClient *K8SClient, caCertData, caKeyData, caCertChainData []byte) error {
// Create the Secret object
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: cert.ClusterCACertSecretName,
Namespace: cert.CertManagerNamespace,
},
Data: map[string][]byte{
corev1.TLSCertKey: caCertData,
corev1.TLSPrivateKeyKey: caKeyData,
"ca.crt": caCertChainData,
},
Type: corev1.SecretTypeTLS,
}
return k8sClient.CreateOrUpdateSecretObject(context.TODO(), secret)
}
Loading

0 comments on commit d96d6cb

Please sign in to comment.