Skip to content
Snippets Groups Projects
Commit 55a12d2b authored by Mariano Cano's avatar Mariano Cano
Browse files

Add working version of the StepIssuer controller.

parent 18026324
No related branches found
No related tags found
No related merge requests found
package controllers
import (
"context"
"fmt"
"github.com/go-logr/logr"
api "github.com/smallstep/step-issuer/api/v1beta1"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type stepStatusReconciler struct {
*StepIssuerReconciler
issuer *api.StepIssuer
logger logr.Logger
}
func newStepStatusReconciler(r *StepIssuerReconciler, iss *api.StepIssuer, log logr.Logger) *stepStatusReconciler {
return &stepStatusReconciler{
StepIssuerReconciler: r,
issuer: iss,
logger: log,
}
}
func (r *stepStatusReconciler) Update(ctx context.Context, status api.ConditionStatus, reason, message string, args ...interface{}) error {
completeMessage := fmt.Sprintf(message, args...)
r.setCondition(status, reason, completeMessage)
// Fire an Event to additionally inform users of the change
eventType := core.EventTypeNormal
if status == api.ConditionFalse {
eventType = core.EventTypeWarning
}
r.Recorder.Event(r.issuer, eventType, reason, completeMessage)
return r.Client.Update(ctx, r.issuer)
}
// setCondition will set a 'condition' on the given api.StepIssuer resource.
//
// - If no condition of the same type already exists, the condition will be
// inserted with the LastTransitionTime set to the current time.
// - If a condition of the same type and state already exists, the condition
// will be updated but the LastTransitionTime will not be modified.
// - If a condition of the same type and different state already exists, the
// condition will be updated and the LastTransitionTime set to the current
// time.
func (r *stepStatusReconciler) setCondition(status api.ConditionStatus, reason, message string) {
now := meta.NewTime(r.Clock.Now())
c := api.StepIssuerCondition{
Type: api.ConditionReady,
Status: status,
Reason: reason,
Message: message,
LastTransitionTime: &now,
}
// Search through existing conditions
for idx, cond := range r.issuer.Status.Conditions {
// Skip unrelated conditions
if cond.Type != api.ConditionReady {
continue
}
// If this update doesn't contain a state transition, we don't update
// the conditions LastTransitionTime to Now()
if cond.Status == status {
c.LastTransitionTime = cond.LastTransitionTime
} else {
r.logger.Info("found status change for StepIssuer condition; setting lastTransitionTime", "condition", cond.Type, "old_status", cond.Status, "new_status", status, "time", now.Time)
}
// Overwrite the existing condition
r.issuer.Status.Conditions[idx] = c
return
}
// If we've not found an existing condition of this type, we simply insert
// the new condition into the slice.
r.issuer.Status.Conditions = append(r.issuer.Status.Conditions, c)
r.logger.Info("setting lastTransitionTime for StepIssuer condition", "condition", api.ConditionReady, "time", now.Time)
}
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package controllers
import (
"context"
"fmt"
"github.com/go-logr/logr"
api "github.com/smallstep/step-issuer/api/v1beta1"
"github.com/smallstep/step-issuer/provisioners"
core "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
"k8s.io/utils/clock"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// StepIssuerReconciler reconciles a StepIssuer object
type StepIssuerReconciler struct {
client.Client
Log logr.Logger
Clock clock.Clock
Recorder record.EventRecorder
}
// +kubebuilder:rbac:groups=certmanager.step.sm,resources=stepissuers,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=certmanager.step.sm,resources=stepissuers/status,verbs=get;update;patch
// Reconcile will read and validate the StepIssuer resources, it will set the
// status condition ready to true if everything is right.
func (r *StepIssuerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx := context.Background()
log := r.Log.WithValues("stepissuer", req.NamespacedName)
iss := new(api.StepIssuer)
if err := r.Client.Get(ctx, req.NamespacedName, iss); err != nil {
log.Error(err, "failed to retrieve StepIssuer resource")
return ctrl.Result{}, client.IgnoreNotFound(err)
}
statusReconciler := newStepStatusReconciler(r, iss, log)
if err := validateStepIssuerSpec(iss.Spec); err != nil {
log.Error(err, "failed to validate StepIssuer resource")
statusReconciler.Update(ctx, api.ConditionFalse, "Validation", "Failed to validate resource: %v", err)
return ctrl.Result{}, err
}
// Fetch the provisioner password
var secret core.Secret
secretNamespaceName := types.NamespacedName{
Namespace: req.Namespace,
Name: iss.Spec.Provisioner.PasswordRef.Name,
}
if err := r.Client.Get(ctx, secretNamespaceName, &secret); err != nil {
log.Error(err, "failed to retrieve StepIssuer provisioner secret", "namespace", secretNamespaceName.Namespace, "name", secretNamespaceName.Name)
if apierrors.IsNotFound(err) {
statusReconciler.Update(ctx, api.ConditionFalse, "NotFound", "Failed to retrieve provisioner secret: %v", err)
} else {
statusReconciler.Update(ctx, api.ConditionFalse, "Error", "Failed to retrieve provisioner secret: %v", err)
}
return ctrl.Result{}, err
}
password, ok := secret.Data[iss.Spec.Provisioner.PasswordRef.Key]
if !ok {
err := fmt.Errorf("secret %s does not contain key %s", secret.Name, iss.Spec.Provisioner.PasswordRef.Key)
log.Error(err, "failed to retrieve StepIssuer provisioner secret", "namespace", secretNamespaceName.Namespace, "name", secretNamespaceName.Name)
statusReconciler.Update(ctx, api.ConditionFalse, "NotFound", "Failed to retrieve provisioner secret: %v", err)
return ctrl.Result{}, err
}
// Initialize and store the provisioner
p, err := provisioners.New(iss, password)
if err != nil {
log.Error(err, "failed to initialize provisioner")
statusReconciler.Update(ctx, api.ConditionFalse, "Error", "failed initialize provisioner")
return ctrl.Result{}, err
}
provisioners.Store(req.NamespacedName, p)
return ctrl.Result{}, statusReconciler.Update(ctx, api.ConditionTrue, "Verified", "StepIssuer verified and ready to sign certificates")
}
// SetupWithManager initializes the StepIssuer controller into the controller
// runtime.
func (r *StepIssuerReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&api.StepIssuer{}).
Complete(r)
}
func validateStepIssuerSpec(s api.StepIssuerSpec) error {
switch {
case s.URL == "":
return fmt.Errorf("spec.url cannot be empty")
case s.Provisioner.Name == "":
return fmt.Errorf("spec.provisioner.name cannot be empty")
case s.Provisioner.KeyID == "":
return fmt.Errorf("spec.provisioner.kid cannot be empty")
case s.Provisioner.PasswordRef.Name == "":
return fmt.Errorf("spec.provisioner.passwordRef.name cannot be empty")
case s.Provisioner.PasswordRef.Key == "":
return fmt.Errorf("spec.provisioner.passwordRef.key cannot be empty")
default:
return nil
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment