Files
odo/pkg/kclient/secrets.go
Parthvi Vala 4c1982a6a5 odo dev deletes remote resources not present in the Devfile (#6189)
* Add integration test

* odo dev deletes remote resources not present in the Devfile

* Delete the servicebinding secret without SBO

* Fix integration tests failure for when SBO is installed

* code Refactor

* Refactor the code

* Fix components_test.go

* add todos to integration tests

* Code review from feloy

* Move secret removal out of UnbindWithLibrary; rename deleteServiceBindingSecrets; add spinner end defer

* Attempt at making resource deletion concurrent

* Temp changes

* temp change

* Use errgroup wg to handle go routines

* Use single spinner for all the resources

* Fix validation failures

* Temp changes

* Optimize the code

* Optimize error messages; remove the unnecessary use of 'Kubernetes'
2022-11-18 15:24:51 -05:00

227 lines
8.0 KiB
Go

package kclient
import (
"bytes"
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"strings"
"time"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/klog"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ComponentPortAnnotationName annotation is used on the secrets that are created for each exposed port of the component
const ComponentPortAnnotationName = "component-port"
var SecretGVK = corev1.SchemeGroupVersion.WithKind("Secret")
// CreateTLSSecret creates a TLS Secret with the given certificate and private key
// serviceName is the name of the service for the target reference
// ingressDomain is the ingress domain to use for the ingress
func (c *Client) CreateTLSSecret(tlsCertificate []byte, tlsPrivKey []byte, objectMeta metav1.ObjectMeta) (*corev1.Secret, error) {
if objectMeta.Name == "" {
return nil, fmt.Errorf("tlsSecret name is empty")
}
data := make(map[string][]byte)
data["tls.crt"] = tlsCertificate
data["tls.key"] = tlsPrivKey
secretTemplate := corev1.Secret{
ObjectMeta: objectMeta,
Type: corev1.SecretTypeTLS,
Data: data,
}
secret, err := c.KubeClient.CoreV1().Secrets(c.Namespace).Create(context.TODO(), &secretTemplate, metav1.CreateOptions{FieldManager: FieldManager})
if err != nil {
return nil, fmt.Errorf("unable to create secret %s: %w", objectMeta.Name, err)
}
return secret, nil
}
// SelfSignedCertificate struct is the return type of function GenerateSelfSignedCertificate
// CertPem is the byte array for certificate pem encode
// KeyPem is the byte array for key pem encode
type SelfSignedCertificate struct {
CertPem []byte
KeyPem []byte
}
// GenerateSelfSignedCertificate creates a self-signed SSl certificate
func GenerateSelfSignedCertificate(host string) (SelfSignedCertificate, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return SelfSignedCertificate{}, fmt.Errorf("unable to generate rsa key: %w", err)
}
template := x509.Certificate{
SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{
CommonName: "Odo self-signed certificate",
Organization: []string{"Odo"},
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24 * 365 * 10),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
BasicConstraintsValid: true,
DNSNames: []string{"*." + host},
}
certificateDerEncoding, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
if err != nil {
return SelfSignedCertificate{}, fmt.Errorf("unable to create certificate: %w", err)
}
out := &bytes.Buffer{}
err = pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: certificateDerEncoding})
if err != nil {
return SelfSignedCertificate{}, fmt.Errorf("unable to encode certificate: %w", err)
}
certPemEncode := out.String()
certPemByteArr := []byte(certPemEncode)
tlsPrivKeyEncoding := x509.MarshalPKCS1PrivateKey(privateKey)
err = pem.Encode(out, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: tlsPrivKeyEncoding})
if err != nil {
return SelfSignedCertificate{}, fmt.Errorf("unable to encode rsa private key: %w", err)
}
keyPemEncode := out.String()
keyPemByteArr := []byte(keyPemEncode)
return SelfSignedCertificate{CertPem: certPemByteArr, KeyPem: keyPemByteArr}, nil
}
// GetSecret returns the Secret object in the given namespace
func (c *Client) GetSecret(name, namespace string) (*corev1.Secret, error) {
secret, err := c.KubeClient.CoreV1().Secrets(namespace).Get(context.TODO(), name, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("unable to get the secret %s: %w", secret, err)
}
return secret, nil
}
// UpdateSecret updates the given Secret object in the given namespace
func (c *Client) UpdateSecret(secret *corev1.Secret, namespace string) (*corev1.Secret, error) {
secret, err := c.KubeClient.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{})
if err != nil {
return nil, fmt.Errorf("unable to update the secret %s: %w", secret, err)
}
return secret, nil
}
// DeleteSecret updates the given Secret object in the given namespace
func (c *Client) DeleteSecret(secretName, namespace string) error {
err := c.KubeClient.CoreV1().Secrets(namespace).Delete(context.TODO(), secretName, metav1.DeleteOptions{})
if err != nil {
return fmt.Errorf("unable to delete the secret %s: %w", secretName, err)
}
return nil
}
// CreateSecret generates and creates the secret
// commonObjectMeta is the ObjectMeta for the service
func (c *Client) CreateSecret(objectMeta metav1.ObjectMeta, data map[string]string, ownerReference metav1.OwnerReference) error {
secret := corev1.Secret{
ObjectMeta: objectMeta,
Type: corev1.SecretTypeOpaque,
StringData: data,
}
secret.SetOwnerReferences(append(secret.GetOwnerReferences(), ownerReference))
_, err := c.KubeClient.CoreV1().Secrets(c.Namespace).Create(context.TODO(), &secret, metav1.CreateOptions{FieldManager: FieldManager})
if err != nil {
return fmt.Errorf("unable to create secret for %s: %w", objectMeta.Name, err)
}
return nil
}
// CreateSecrets creates a secret for each port, containing the host and port of the component
// This is done so other components can later inject the secret into the environment
// and have the "coordinates" to communicate with this component
func (c *Client) CreateSecrets(componentName string, commonObjectMeta metav1.ObjectMeta, svc *corev1.Service, ownerReference metav1.OwnerReference) error {
originalName := commonObjectMeta.Name
for _, svcPort := range svc.Spec.Ports {
portAsString := fmt.Sprintf("%v", svcPort.Port)
// we need to create multiple secrets, so each one has to contain the port in it's name
// so we change the name of each secret by adding the port number
commonObjectMeta.Name = fmt.Sprintf("%v-%v", originalName, portAsString)
// we also add the port as an annotation to the secret
// this comes in handy when we need to "query" for the appropriate secret
// of a component based on the port
commonObjectMeta.Annotations[ComponentPortAnnotationName] = portAsString
err := c.CreateSecret(
commonObjectMeta,
map[string]string{
secretKeyName(componentName, "host"): svc.Name,
secretKeyName(componentName, "port"): portAsString,
},
ownerReference)
if err != nil {
return fmt.Errorf("unable to create Secret for %s: %w", commonObjectMeta.Name, err)
}
}
// restore the original values of the fields we changed
commonObjectMeta.Name = originalName
delete(commonObjectMeta.Annotations, ComponentPortAnnotationName)
return nil
}
// ListSecrets lists all the secrets based on the given label selector
func (c *Client) ListSecrets(labelSelector string) ([]corev1.Secret, error) {
listOptions := metav1.ListOptions{}
if len(labelSelector) > 0 {
listOptions = metav1.ListOptions{
LabelSelector: labelSelector,
}
}
secretList, err := c.KubeClient.CoreV1().Secrets(c.Namespace).List(context.TODO(), listOptions)
if err != nil {
return nil, fmt.Errorf("unable to get secret list: %w", err)
}
return secretList.Items, nil
}
// WaitAndGetSecret blocks and waits until the secret is available
func (c *Client) WaitAndGetSecret(name string, namespace string) (*corev1.Secret, error) {
klog.V(3).Infof("Waiting for secret %s to become available", name)
w, err := c.KubeClient.CoreV1().Secrets(namespace).Watch(context.TODO(), metav1.ListOptions{
FieldSelector: fields.Set{"metadata.name": name}.AsSelector().String(),
})
if err != nil {
return nil, fmt.Errorf("unable to watch secret: %w", err)
}
defer w.Stop()
for {
val, ok := <-w.ResultChan()
if !ok {
break
}
if e, ok := val.Object.(*corev1.Secret); ok {
klog.V(3).Infof("Secret %s now exists", e.Name)
return e, nil
}
}
return nil, fmt.Errorf("unknown error while waiting for secret '%s'", name)
}
func secretKeyName(componentName, baseKeyName string) string {
return fmt.Sprintf("COMPONENT_%v_%v", strings.Replace(strings.ToUpper(componentName), "-", "_", -1), strings.ToUpper(baseKeyName))
}