-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #472 from kube-tarian/agent-ca-cert-issuer
cert issuer
- Loading branch information
Showing
11 changed files
with
382 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
Oops, something went wrong.