Skip to content
Open
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
10 changes: 8 additions & 2 deletions internal/cmd/skupper/link/kube/link_generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,14 @@ func (cmd *CmdLinkGenerate) Run() error {
Subject: getSubjectsFromEndpoints(cmd.activeSite.Status.Endpoints),
},
}

_, err := cmd.Client.Certificates(cmd.Namespace).Create(context.TODO(), &certificate, metav1.CreateOptions{})
defaultIssuer := pkgutils.DefaultStr(cmd.activeSite.Status.DefaultIssuer, "skupper-site-ca")
defaultIssuerCert, err := cmd.Client.Certificates(cmd.Namespace).Get(context.TODO(), defaultIssuer, metav1.GetOptions{})
if err != nil {
return fmt.Errorf("unable to retrieve default issuer certificate %q: %w", defaultIssuer, err)
}
certificateController := defaultIssuerCert.Spec.GetCertificateController()
certificate.Spec.SetCertificateController(certificateController)
_, err = cmd.Client.Certificates(cmd.Namespace).Create(context.TODO(), &certificate, metav1.CreateOptions{})
if err != nil {
return err
}
Expand Down
20 changes: 20 additions & 0 deletions internal/cmd/skupper/link/kube/link_generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import (
"github.com/skupperproject/skupper/internal/cmd/skupper/common/utils"
fakeclient "github.com/skupperproject/skupper/internal/kube/client/fake"
"github.com/skupperproject/skupper/pkg/apis/skupper/v2alpha1"
fakeskupperv2alpha1 "github.com/skupperproject/skupper/pkg/generated/client/clientset/versioned/typed/skupper/v2alpha1/fake"
"gotest.tools/v3/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
k8stesting "k8s.io/client-go/testing"
)

func TestCmdLinkGenerate_ValidateInput(t *testing.T) {
Expand Down Expand Up @@ -1004,6 +1006,24 @@ func newCmdLinkGenerateWithMocks(namespace string, k8sObjects []runtime.Object,
if err != nil {
return nil, err
}
defaultIssuer := &v2alpha1.Certificate{
TypeMeta: v1.TypeMeta{
APIVersion: "skupper.io/v2alpha1",
Kind: "Certificate",
},
ObjectMeta: v1.ObjectMeta{
Name: "skupper-site-ca",
Namespace: namespace,
},
}
fakeSkupperCli := client.Skupper.SkupperV2alpha1().(*fakeskupperv2alpha1.FakeSkupperV2alpha1)
fakeSkupperCli.PrependReactor("get", "certificates", func(action k8stesting.Action) (handled bool, ret runtime.Object, err error) {
getAction := action.(k8stesting.GetAction)
if getAction.GetName() == "skupper-site-ca" {
return true, defaultIssuer, nil
}
return false, nil, nil
})
CmdLinkGenerate := &CmdLinkGenerate{
Client: client.GetSkupperClient().SkupperV2alpha1(),
KubeClient: client.GetKubeClient(),
Expand Down
117 changes: 98 additions & 19 deletions internal/kube/certificates/mgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import (
"strings"
"time"

"github.com/skupperproject/skupper/internal/utils"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"

Expand Down Expand Up @@ -39,26 +41,31 @@ type ControllerContext interface {
// the existence of a particular Certificate resource can be
// ensured. It is currently used by package internal/kube/site.
type CertificateManager interface {
EnsureCA(namespace string, name string, subject string, refs []metav1.OwnerReference) error
Ensure(namespace string, name string, ca string, subject string, hosts []string, client bool, server bool, refs []metav1.OwnerReference) error
EnsureCA(namespace string, name string, subject string, controller string, refs []metav1.OwnerReference) error
Ensure(namespace string, name string, ca string, subject string, hosts []string, client bool, server bool, controller string, refs []metav1.OwnerReference) error
}

type CertificateManagerImpl struct {
definitions map[string]*skupperv2alpha1.Certificate
secrets map[string]*corev1.Secret
certificateWatcher *watchers.CertificateWatcher
secretWatcher *watchers.SecretWatcher
processor *watchers.EventProcessor
context ControllerContext
certificateController string
delegated map[string]*skupperv2alpha1.Certificate
definitions map[string]*skupperv2alpha1.Certificate
secrets map[string]*corev1.Secret
certificateWatcher *watchers.CertificateWatcher
secretWatcher *watchers.SecretWatcher
processor *watchers.EventProcessor
context ControllerContext
}

// Returns a correctly initialised CertificateManager.
func NewCertificateManager(processor *watchers.EventProcessor) *CertificateManagerImpl {
return &CertificateManagerImpl{
definitions: map[string]*skupperv2alpha1.Certificate{},
secrets: map[string]*corev1.Secret{},
processor: processor,
}
func NewCertificateManager(processor *watchers.EventProcessor, certificateController string) *CertificateManagerImpl {
certMgr := &CertificateManagerImpl{
definitions: map[string]*skupperv2alpha1.Certificate{},
delegated: map[string]*skupperv2alpha1.Certificate{},
secrets: map[string]*corev1.Secret{},
processor: processor,
certificateController: certificateController,
}
return certMgr
}

// Allows a ControllerContext to be set for this CertificateManager.
Expand All @@ -72,6 +79,10 @@ func (m *CertificateManagerImpl) Watch(watchNamespace string) {
m.secretWatcher = m.processor.WatchAllSecrets(watchNamespace, watchers.FilterByNamespace(m.isControlled, m.checkSecret))
}

func (m *CertificateManagerImpl) GetCertificateController() string {
return m.certificateController
}

func (m *CertificateManagerImpl) isControlled(namespace string) bool {
if m.context != nil {
return m.context.IsControlled(namespace)
Expand Down Expand Up @@ -102,11 +113,12 @@ func (m *CertificateManagerImpl) Recover() {
// This method is called to ensure that a Certificate resource exists
// to represent a CA (i.e. certificate issuer) with the properties
// specified in the arguments.
func (m *CertificateManagerImpl) EnsureCA(namespace string, name string, subject string, refs []metav1.OwnerReference) error {
func (m *CertificateManagerImpl) EnsureCA(namespace string, name string, subject string, controller string, refs []metav1.OwnerReference) error {
spec := skupperv2alpha1.CertificateSpec{
Subject: subject,
Signing: true,
}
spec.SetCertificateController(utils.DefaultStr(controller, m.certificateController))
return m.ensure(namespace, name, spec, refs)
}

Expand All @@ -118,14 +130,15 @@ func (m *CertificateManagerImpl) EnsureCA(namespace string, name string, subject
// if the same owner changes the hosts then they will be changed on
// the certificate. This allows the same certificate to be used for
// multiple resources such as Routes.
func (m *CertificateManagerImpl) Ensure(namespace string, name string, ca string, subject string, hosts []string, client bool, server bool, refs []metav1.OwnerReference) error {
func (m *CertificateManagerImpl) Ensure(namespace string, name string, ca string, subject string, hosts []string, client bool, server bool, controller string, refs []metav1.OwnerReference) error {
spec := skupperv2alpha1.CertificateSpec{
Ca: ca,
Subject: subject,
Hosts: hosts,
Client: client,
Server: server,
}
spec.SetCertificateController(utils.DefaultStr(controller, m.certificateController))
return m.ensure(namespace, name, spec, refs)
}

Expand All @@ -135,6 +148,7 @@ var compareSpecUnordered []cmp.Option = []cmp.Option{
}

func (m *CertificateManagerImpl) ensure(namespace string, name string, spec skupperv2alpha1.CertificateSpec, refs []metav1.OwnerReference) error {
certsCli := m.processor.GetSkupperClient().SkupperV2alpha1().Certificates(namespace)
key := fmt.Sprintf("%s/%s", namespace, name)
if current, ok := m.definitions[key]; ok {
changed := false
Expand Down Expand Up @@ -184,8 +198,19 @@ func (m *CertificateManagerImpl) ensure(namespace string, name string, spec skup
if !changed {
return nil
}
updated, err := m.processor.GetSkupperClient().SkupperV2alpha1().Certificates(namespace).Update(context.Background(), current, metav1.UpdateOptions{})
m.setLatestResourceVersion(current)
updated, err := certsCli.Update(context.Background(), current, metav1.UpdateOptions{})
if err != nil {
log.Printf("Error updating certificate %s: %s", key, err)
if apierrors.IsConflict(err) {
latest, getErr := certsCli.Get(context.Background(), current.Name, metav1.GetOptions{})
if getErr != nil {
log.Printf("Error getting latest certificate state for %s: %s", key, err)
} else {
log.Printf("Restoring latest certificate state for %s", key)
m.definitions[key] = latest
}
}
return err
}
log.Printf("Updated certificate %s/%s", updated.Namespace, updated.Name)
Expand Down Expand Up @@ -214,15 +239,39 @@ func (m *CertificateManagerImpl) ensure(namespace string, name string, spec skup
m.context.SetAnnotations(namespace, cert.Name, "Certificate", cert.ObjectMeta.Annotations)
}

created, err := m.processor.GetSkupperClient().SkupperV2alpha1().Certificates(namespace).Create(context.Background(), cert, metav1.CreateOptions{})
created, err := certsCli.Create(context.Background(), cert, metav1.CreateOptions{})
if err != nil {
return err
if apierrors.IsAlreadyExists(err) {
log.Printf("Certificate %s/%s already exists - loading latest", namespace, name)
created, err = certsCli.Get(context.Background(), cert.Name, metav1.GetOptions{})
if err != nil {
return err
}
} else {
log.Printf("Error creating certificate %s: %s", key, err)
return err
}
}
m.definitions[key] = created
return nil
}
}

func (m *CertificateManagerImpl) setLatestResourceVersion(current *skupperv2alpha1.Certificate) {
if !current.Spec.HasCertificateController() {
return
}
certsCli := m.processor.GetSkupperClient().SkupperV2alpha1().Certificates(current.GetNamespace())
latest, err := certsCli.Get(context.Background(), current.Name, metav1.GetOptions{})
if err != nil {
log.Printf("Unable to retrieve latest certificate for %q: %v", current.Key(), err)
} else if current.GetResourceVersion() != latest.GetResourceVersion() {
log.Printf("Updating certificate generation for %q", current.Key())
current.ObjectMeta.Generation = latest.GetGeneration()
current.ObjectMeta.ResourceVersion = latest.GetResourceVersion()
}
}

// Called by EventProcessor whenever there is a change to a Certificate reasource.
func (m *CertificateManagerImpl) checkCertificate(key string, certificate *skupperv2alpha1.Certificate) error {
if certificate == nil {
Expand Down Expand Up @@ -259,6 +308,9 @@ func (m *CertificateManagerImpl) checkCertificate(key string, certificate *skupp
func (m *CertificateManagerImpl) reconcileSecret(key string, certificate *skupperv2alpha1.Certificate, secret *corev1.Secret) error {

var err error
if m.ensureDelegated(certificate) {
return nil
}
if secret != nil {
err = m.updateSecret(key, certificate, secret)
} else {
Expand Down Expand Up @@ -466,6 +518,33 @@ func hasControlledAnnotation(secret *corev1.Secret) bool {
return ok
}

func (m *CertificateManagerImpl) ensureDelegated(certificate *skupperv2alpha1.Certificate) bool {
var controller string
var delegate bool

if m.certificateController != "" {
controller = m.certificateController
delegate = true
} else if controller = certificate.Spec.GetCertificateController(); controller != "" {
delegate = true
}

key := certificate.Key()
_, isDelegated := m.delegated[key]
if delegate {
if !isDelegated {
log.Printf("Certificate '%s' has been delegated to '%s'", key, controller)
m.delegated[key] = certificate
}
} else {
if isDelegated {
log.Printf("Certificate '%s' is no longer delegated", key)
delete(m.delegated, key)
}
}
return delegate
}

func hasCertificateOwner(secret *corev1.Secret) bool {
for _, owner := range secret.ObjectMeta.OwnerReferences {
if owner.Kind == "Certificate" && owner.APIVersion == "skupper.io/v2alpha1" {
Expand Down
Loading