Skip to content

Commit

Permalink
Implement Refresh Worker Certificates Logic
Browse files Browse the repository at this point in the history
  • Loading branch information
mateoflorido committed Oct 7, 2024
1 parent 353cfa5 commit 4e06b89
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 8 deletions.
67 changes: 65 additions & 2 deletions bootstrap/controllers/certificates_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,9 @@ func (r *CertificatesReconciler) Reconcile(ctx context.Context, req ctrl.Request
return ctrl.Result{}, err
}
} else {
log.Info("worker nodes are not supported yet")
if err := r.refreshWorkerCertificates(ctx, scope); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}
}
Expand Down Expand Up @@ -188,7 +190,7 @@ func (r *CertificatesReconciler) refreshControlPlaneCertificates(ctx context.Con
extraSANs := controlPlaneConfig.ExtraSANs
extraSANs = append(extraSANs, controlPlaneEndpoint)

expirySecondsUnix, err := scope.Workload.RefreshCertificates(ctx, scope.Machine, *nodeToken, seconds, extraSANs)
expirySecondsUnix, err := scope.Workload.RefreshControlPlaneCertificates(ctx, scope.Machine, *nodeToken, seconds, extraSANs)
if err != nil {
r.recorder.Eventf(
scope.Machine,
Expand Down Expand Up @@ -245,3 +247,64 @@ func (r *CertificatesReconciler) updateExpiryDateAnnotation(ctx context.Context,

return nil
}

func (r *CertificatesReconciler) refreshWorkerCertificates(ctx context.Context, scope *CertificatesScope) error {
nodeToken, err := token.LookupNodeToken(ctx, r.Client, util.ObjectKey(scope.Cluster), scope.Machine.Name)
if err != nil {
return fmt.Errorf("failed to lookup node token: %w", err)
}

mAnnotations := scope.Machine.GetAnnotations()

refreshAnnotation, ok := mAnnotations[bootstrapv1.CertificatesRefreshAnnotation]
if !ok {
return nil
}

r.recorder.Eventf(
scope.Machine,
corev1.EventTypeNormal,
bootstrapv1.CertificatesRefreshInProgressEvent,
"Certificates refresh in progress. TTL: %s", refreshAnnotation,
)

seconds, err := utiltime.TTLToSeconds(refreshAnnotation)
if err != nil {
return fmt.Errorf("failed to parse expires-in annotation value: %w", err)
}

expirySecondsUnix, err := scope.Workload.RefreshWorkerCertificates(ctx, scope.Machine, *nodeToken, seconds)
if err != nil {
r.recorder.Eventf(
scope.Machine,
corev1.EventTypeWarning,
bootstrapv1.CertificatesRefreshFailedEvent,
"Failed to refresh certificates: %v", err,
)
return fmt.Errorf("failed to refresh certificates: %w", err)
}

expiryTime := time.Unix(int64(expirySecondsUnix), 0)

delete(mAnnotations, bootstrapv1.CertificatesRefreshAnnotation)
mAnnotations[bootstrapv1.MachineCertificatesExpiryDateAnnotation] = expiryTime.Format(time.RFC3339)
scope.Machine.SetAnnotations(mAnnotations)
if err := scope.Patcher.Patch(ctx, scope.Machine); err != nil {
return fmt.Errorf("failed to patch machine annotations: %w", err)
}

r.recorder.Eventf(
scope.Machine,
corev1.EventTypeNormal,
bootstrapv1.CertificatesRefreshDoneEvent,
"Certificates refreshed, will expire at %s", expiryTime,
)

scope.Log.Info("Certificates refreshed",
"cluster", scope.Cluster.Name,
"machine", scope.Machine.Name,
"expiry", expiryTime.Format(time.RFC3339),
)

return nil
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ require (
golang.org/x/mod v0.19.0
golang.org/x/net v0.23.0 // indirect
golang.org/x/oauth2 v0.18.0 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
95 changes: 90 additions & 5 deletions pkg/ck8s/workload_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

apiv1 "github.com/canonical/k8s-snap-api/api/v1"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -242,7 +243,32 @@ func (w *Workload) GetCertificatesExpiryDate(ctx context.Context, machine *clust
return response.ExpiryDate, nil
}

func (w *Workload) RefreshCertificates(ctx context.Context, machine *clusterv1.Machine, nodeToken string, expirationSeconds int, extraSANs []string) (int, error) {
type ApproveWorkerCSRRequest struct {
Seed int `json:"seed"`
}

type ApproveWorkerCSRResponse struct{}

func (w *Workload) ApproveCertificates(ctx context.Context, machine *clusterv1.Machine, capiToken string, seed int) error {
request := ApproveWorkerCSRRequest{}
response := &ApproveWorkerCSRResponse{}
k8sdProxy, err := w.GetK8sdProxyForControlPlane(ctx, k8sdProxyOptions{})
if err != nil {
return fmt.Errorf("failed to create k8sd proxy: %w", err)
}

header := map[string][]string{
"capi-auth-token": {w.authToken},
}

if err := w.doK8sdRequest(ctx, k8sdProxy, http.MethodPost, "1.0/x/capi/refresh-certs/approve", header, request, response); err != nil {
return fmt.Errorf("failed to approve certificates: %w", err)
}

return nil
}

func (w *Workload) refreshCertificatesPlan(ctx context.Context, machine *clusterv1.Machine, nodeToken string) (int, error) {
planRequest := apiv1.ClusterAPICertificatesPlanRequest{}
planResponse := &apiv1.ClusterAPICertificatesPlanResponse{}

Expand All @@ -259,17 +285,76 @@ func (w *Workload) RefreshCertificates(ctx context.Context, machine *clusterv1.M
return 0, fmt.Errorf("failed to refresh certificates: %w", err)
}

return planResponse.Seed, nil
}

func (w *Workload) refreshCertificatesRun(ctx context.Context, machine *clusterv1.Machine, nodeToken string, request *apiv1.ClusterAPICertificatesRunRequest) (int, error) {
runResponse := &apiv1.ClusterAPICertificatesRunResponse{}
header := map[string][]string{
"node-token": {nodeToken},
}

k8sdProxy, err := w.GetK8sdProxyForMachine(ctx, machine)
if err != nil {
return 0, fmt.Errorf("failed to create k8sd proxy: %w", err)
}

if err := w.doK8sdRequest(ctx, k8sdProxy, http.MethodPost, "1.0/x/capi/refresh-certs/run", header, request, runResponse); err != nil {
return 0, fmt.Errorf("failed to run refresh certificates: %w", err)
}

return runResponse.ExpirationSeconds, nil
}

func (w *Workload) RefreshWorkerCertificates(ctx context.Context, machine *clusterv1.Machine, nodeToken string, expirationSeconds int) (int, error) {
seed, err := w.refreshCertificatesPlan(ctx, machine, nodeToken)
if err != nil {
return 0, fmt.Errorf("failed to get refresh certificates plan: %w", err)
}

request := apiv1.ClusterAPICertificatesRunRequest{
Seed: seed,
ExpirationSeconds: expirationSeconds,
}

var seconds int

eg, ctx := errgroup.WithContext(ctx)
eg.Go(func() error {
seconds, err = w.refreshCertificatesRun(ctx, machine, nodeToken, &request)
return err
})

eg.Go(func() error {
return w.ApproveCertificates(ctx, machine, nodeToken, seed)
})

if err := eg.Wait(); err != nil {
return 0, fmt.Errorf("failed to refresh worker certificates: %w", err)
}

return seconds, nil

}

func (w *Workload) RefreshControlPlaneCertificates(ctx context.Context, machine *clusterv1.Machine, nodeToken string, expirationSeconds int, extraSANs []string) (int, error) {
seed, err := w.refreshCertificatesPlan(ctx, machine, nodeToken)
if err != nil {
return 0, fmt.Errorf("failed to get refresh certificates plan: %w", err)
}

runRequest := apiv1.ClusterAPICertificatesRunRequest{
ExpirationSeconds: expirationSeconds,
Seed: planResponse.Seed,
Seed: seed,
ExtraSANs: extraSANs,
}
runResponse := &apiv1.ClusterAPICertificatesRunResponse{}
if err := w.doK8sdRequest(ctx, k8sdProxy, http.MethodPost, "1.0/x/capi/refresh-certs/run", header, runRequest, runResponse); err != nil {

seconds, err := w.refreshCertificatesRun(ctx, machine, nodeToken, &runRequest)
if err != nil {
return 0, fmt.Errorf("failed to run refresh certificates: %w", err)
}

return runResponse.ExpirationSeconds, nil
return seconds, nil
}

func (w *Workload) RefreshMachine(ctx context.Context, machine *clusterv1.Machine, nodeToken string, upgradeOption string) (string, error) {
Expand Down

0 comments on commit 4e06b89

Please sign in to comment.