Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cert issuer #472

Merged
merged 4 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -88,6 +87,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
Loading