From b11b091c2fc52213a6a5a13a569255e78e8567af Mon Sep 17 00:00:00 2001
From: Christian Wolf <bc.christianwolf@googlemail.com>
Date: Thu, 29 Apr 2021 18:22:48 +0200
Subject: [PATCH] adapt files for stepclusterissuer

Signed-off-by: Tobias Gurtzick <magic@wizardtales.com>
Signed-off-by: Christian Wolf <bc.christianwolf@googlemail.com>
---
 api/v1beta1/stepclusterissuer_types.go        | 96 ++++++++++++++++++-
 ...nager_controller_approver_clusterrole.yaml |  1 +
 config/rbac/role.yaml                         |  2 +
 .../rbac/stepclusterissuer_editor_role.yaml   | 24 -----
 .../rbac/stepclusterissuer_viewer_role.yaml   | 20 ----
 ...certmanager_v1beta1_stepclusterissuer.yaml |  7 --
 config/samples/stepclusterissuer.yaml         | 16 ++++
 controllers/stepclusterissuer_controller.go   | 89 +++++++++++++++--
 main.go                                       | 18 ++--
 9 files changed, 200 insertions(+), 73 deletions(-)
 delete mode 100644 config/rbac/stepclusterissuer_editor_role.yaml
 delete mode 100644 config/rbac/stepclusterissuer_viewer_role.yaml
 delete mode 100644 config/samples/certmanager_v1beta1_stepclusterissuer.yaml
 create mode 100644 config/samples/stepclusterissuer.yaml

diff --git a/api/v1beta1/stepclusterissuer_types.go b/api/v1beta1/stepclusterissuer_types.go
index 5f3e561..9ae7174 100644
--- a/api/v1beta1/stepclusterissuer_types.go
+++ b/api/v1beta1/stepclusterissuer_types.go
@@ -22,19 +22,33 @@ import (
 // EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!
 // NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.
 
+func init() {
+	SchemeBuilder.Register(&StepClusterIssuer{}, &StepClusterIssuerList{})
+}
+
 // StepClusterIssuerSpec defines the desired state of StepClusterIssuer
 type StepClusterIssuerSpec struct {
 	// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
 	// Important: Run "make" to regenerate code after modifying this file
 
-	// Foo is an example field of StepClusterIssuer. Edit StepClusterIssuer_types.go to remove/update
-	Foo string `json:"foo,omitempty"`
+	// URL is the base URL for the step certificates instance.
+	URL string `json:"url"`
+
+	// Provisioner contains the step certificates provisioner configuration.
+	Provisioner StepProvisioner `json:"provisioner"`
+
+	// CABundle is a base64 encoded TLS certificate used to verify connections
+	// to the step certificates server. If not set the system root certificates
+	// are used to validate the TLS connection.
+	// +optional
+	CABundle []byte `json:"caBundle,omitempty"`
 }
 
 // StepClusterIssuerStatus defines the observed state of StepClusterIssuer
 type StepClusterIssuerStatus struct {
 	// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
 	// Important: Run "make" to regenerate code after modifying this file
+	Conditions []StepClusterIssuerCondition `json:"conditions,omitempty"`
 }
 
 // +kubebuilder:object:root=true
@@ -58,6 +72,80 @@ type StepClusterIssuerList struct {
 	Items           []StepClusterIssuer `json:"items"`
 }
 
-func init() {
-	SchemeBuilder.Register(&StepClusterIssuer{}, &StepClusterIssuerList{})
+// SecretKeySelector contains the reference to a secret.
+type SecretKeySelector struct {
+	// The name of the secret in the pod's namespace to select from.
+	Name string `json:"name"`
+
+	// The key of the secret to select from. Must be a valid secret key.
+	// +optional
+	Key string `json:"key,omitempty"`
+}
+
+// StepProvisioner contains the configuration used to create step certificate
+// tokens used to grant certificates.
+type StepProvisioner struct {
+	// Names is the name of the JWK provisioner.
+	Name string `json:"name"`
+
+	// KeyID is the kid property of the JWK provisioner.
+	KeyID string `json:"kid"`
+
+	// PasswordRef is a reference to a Secret containing the provisioner
+	// password used to decrypt the provisioner private key.
+	PasswordRef SecretKeySelector `json:"passwordRef"`
 }
+
+// ConditionType represents a StepClusterIssuer condition type.
+// +kubebuilder:validation:Enum=Ready
+type ConditionType string
+
+const (
+	// ConditionReady indicates that a StepClusterIssuer is ready for use.
+	ConditionReady ConditionType = "Ready"
+)
+
+// ConditionStatus represents a condition's status.
+// +kubebuilder:validation:Enum=True;False;Unknown
+type ConditionStatus string
+
+// These are valid condition statuses. "ConditionTrue" means a resource is in
+// the condition; "ConditionFalse" means a resource is not in the condition;
+// "ConditionUnknown" means kubernetes can't decide if a resource is in the
+// condition or not. In the future, we could add other intermediate
+// conditions, e.g. ConditionDegraded.
+const (
+	// ConditionTrue represents the fact that a given condition is true
+	ConditionTrue ConditionStatus = "True"
+
+	// ConditionFalse represents the fact that a given condition is false
+	ConditionFalse ConditionStatus = "False"
+
+	// ConditionUnknown represents the fact that a given condition is unknown
+	ConditionUnknown ConditionStatus = "Unknown"
+)
+
+// StepClusterIssuerCondition contains condition information for the step issuer.
+type StepClusterIssuerCondition struct {
+	// Type of the condition, currently ('Ready').
+	Type ConditionType `json:"type"`
+
+	// Status of the condition, one of ('True', 'False', 'Unknown').
+	// +kubebuilder:validation:Enum=True;False;Unknown
+	Status ConditionStatus `json:"status"`
+
+	// LastTransitionTime is the timestamp corresponding to the last status
+	// change of this condition.
+	// +optional
+	LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"`
+
+	// Reason is a brief machine readable explanation for the condition's last
+	// transition.
+	// +optional
+	Reason string `json:"reason,omitempty"`
+
+	// Message is a human readable description of the details of the last
+	// transition, complementing reason.
+	// +optional
+	Message string `json:"message,omitempty"`
+}
\ No newline at end of file
diff --git a/config/rbac/cert_manager_controller_approver_clusterrole.yaml b/config/rbac/cert_manager_controller_approver_clusterrole.yaml
index c8ab809..3c1f044 100644
--- a/config/rbac/cert_manager_controller_approver_clusterrole.yaml
+++ b/config/rbac/cert_manager_controller_approver_clusterrole.yaml
@@ -12,3 +12,4 @@ rules:
   - approve
   resourceNames:
   - stepissuers.certmanager.step.sm/*
+  - stepclusterissuers.certmanager.step.sm/*
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index 7fd8af7..edff2da 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -42,6 +42,7 @@ rules:
   - certmanager.step.sm
   resources:
   - stepissuers
+  - stepclusterissuers
   verbs:
   - create
   - delete
@@ -54,6 +55,7 @@ rules:
   - certmanager.step.sm
   resources:
   - stepissuers/status
+  - stepclusterissuers/status
   verbs:
   - get
   - patch
diff --git a/config/rbac/stepclusterissuer_editor_role.yaml b/config/rbac/stepclusterissuer_editor_role.yaml
deleted file mode 100644
index 2117244..0000000
--- a/config/rbac/stepclusterissuer_editor_role.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
-# permissions for end users to edit stepclusterissuers.
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: stepclusterissuer-editor-role
-rules:
-- apiGroups:
-  - certmanager.step.sm
-  resources:
-  - stepclusterissuers
-  verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - certmanager.step.sm
-  resources:
-  - stepclusterissuers/status
-  verbs:
-  - get
diff --git a/config/rbac/stepclusterissuer_viewer_role.yaml b/config/rbac/stepclusterissuer_viewer_role.yaml
deleted file mode 100644
index 629d62e..0000000
--- a/config/rbac/stepclusterissuer_viewer_role.yaml
+++ /dev/null
@@ -1,20 +0,0 @@
-# permissions for end users to view stepclusterissuers.
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: stepclusterissuer-viewer-role
-rules:
-- apiGroups:
-  - certmanager.step.sm
-  resources:
-  - stepclusterissuers
-  verbs:
-  - get
-  - list
-  - watch
-- apiGroups:
-  - certmanager.step.sm
-  resources:
-  - stepclusterissuers/status
-  verbs:
-  - get
diff --git a/config/samples/certmanager_v1beta1_stepclusterissuer.yaml b/config/samples/certmanager_v1beta1_stepclusterissuer.yaml
deleted file mode 100644
index 228375e..0000000
--- a/config/samples/certmanager_v1beta1_stepclusterissuer.yaml
+++ /dev/null
@@ -1,7 +0,0 @@
-apiVersion: certmanager.step.sm/v1beta1
-kind: StepClusterIssuer
-metadata:
-  name: stepclusterissuer-sample
-spec:
-  # Add fields here
-  foo: bar
diff --git a/config/samples/stepclusterissuer.yaml b/config/samples/stepclusterissuer.yaml
new file mode 100644
index 0000000..9e81427
--- /dev/null
+++ b/config/samples/stepclusterissuer.yaml
@@ -0,0 +1,16 @@
+apiVersion: certmanager.step.sm/v1beta1
+kind: StepClusterIssuer
+metadata:
+  name: step-cluster-issuer
+spec:
+  # The CA URL.
+  url: https://step-certificates.default.svc.cluster.local
+  # The base64 encoded version of the CA root certificate in PEM format.
+  caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJpekNDQVRHZ0F3SUJBZ0lRTytFQWg4eS8wVjlQMFhwSHJWajVOVEFLQmdncWhrak9QUVFEQWpBa01TSXcKSUFZRFZRUURFeGxUZEdWd0lFTmxjblJwWm1sallYUmxjeUJTYjI5MElFTkJNQjRYRFRFNU1EZ3hNekU1TVRVdwpNbG9YRFRJNU1EZ3hNREU1TVRVd01sb3dKREVpTUNBR0ExVUVBeE1aVTNSbGNDQkRaWEowYVdacFkyRjBaWE1nClVtOXZkQ0JEUVRCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQkFNVkw3VzBQbTNvSlVmSTR3WGQKa2xERW5uNVhTbWo4NlgwYW1DQTBnY08xdElUUG1DVzNCcGU0cE9vV1V2WlZlUWRvU2NxN3pua1V0Mi9HMnQxTgo3MWlqUlRCRE1BNEdBMVVkRHdFQi93UUVBd0lCQmpBU0JnTlZIUk1CQWY4RUNEQUdBUUgvQWdFQk1CMEdBMVVkCkRnUVdCQlJ1Y1ByVm5QdlpOMHI0QVU5TGcyL2VCcng3a2pBS0JnZ3Foa2pPUFFRREFnTklBREJGQWlCUlJBdGsKNXpMY0doQ2FobVBuVzIwZExpdEMzRVdNaVE0bERwN2FFeitFUEFJaEFJOWZWczVxb0l0bVQ4anA2WktVNVEydQphRFBrOGsyQ25OMjdyRnNZV3VwTAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
+  # The provisioner name, kid, and a reference to the provisioner password secret.
+  provisioner:
+    name: admin
+    kid: N6I99Yuk7iGDMk_eW3QaN2admCsrC9UuDN27dlFXUOs
+    passwordRef:
+      name: step-certificates-provisioner-password
+      key: password
\ No newline at end of file
diff --git a/controllers/stepclusterissuer_controller.go b/controllers/stepclusterissuer_controller.go
index 9ec339f..50bc59d 100644
--- a/controllers/stepclusterissuer_controller.go
+++ b/controllers/stepclusterissuer_controller.go
@@ -17,36 +17,105 @@ package controllers
 
 import (
 	"context"
+	"fmt"
 
 	"github.com/go-logr/logr"
-	"k8s.io/apimachinery/pkg/runtime"
+	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"
-
-	certmanagerv1beta1 "github.com/smallstep/step-issuer/api/v1beta1"
 )
 
 // StepClusterIssuerReconciler reconciles a StepClusterIssuer object
 type StepClusterIssuerReconciler struct {
 	client.Client
-	Log    logr.Logger
-	Scheme *runtime.Scheme
+	Log      logr.Logger
+	Clock    clock.Clock
+	Recorder record.EventRecorder
 }
 
 // +kubebuilder:rbac:groups=certmanager.step.sm,resources=stepclusterissuers,verbs=get;list;watch;create;update;patch;delete
 // +kubebuilder:rbac:groups=certmanager.step.sm,resources=stepclusterissuers/status,verbs=get;update;patch
+// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
+// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
 
+// Reconcile will read and validate the StepClusterIssuer resources, it will set the
+// status condition ready to true if everything is right.
 func (r *StepClusterIssuerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
-	_ = context.Background()
-	_ = r.Log.WithValues("stepclusterissuer", req.NamespacedName)
+	log := r.Log.WithValues("stepclusterissuer", req.NamespacedName)
+
+	iss := new(api.StepClusterIssuer)
+	if err := r.Client.Get(ctx, req.NamespacedName, iss); err != nil {
+		log.Error(err, "failed to retrieve StepClusterIssuer resource")
+		return ctrl.Result{}, client.IgnoreNotFound(err)
+	}
+
+	statusReconciler := newStepStatusReconciler(r, iss, log)
+	if err := validateStepClusterIssuerSpec(iss.Spec); err != nil {
+		log.Error(err, "failed to validate StepClusterIssuer resource")
+		statusReconciler.UpdateNoError(ctx, api.ConditionFalse, "Validation", "Failed to validate resource: %v", err)
+		return ctrl.Result{}, err
+	}
 
-	// your logic here
+	// 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 StepClusterIssuer provisioner secret", "namespace", secretNamespaceName.Namespace, "name", secretNamespaceName.Name)
+		if apierrors.IsNotFound(err) {
+			statusReconciler.UpdateNoError(ctx, api.ConditionFalse, "NotFound", "Failed to retrieve provisioner secret: %v", err)
+		} else {
+			statusReconciler.UpdateNoError(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 StepClusterIssuer provisioner secret", "namespace", secretNamespaceName.Namespace, "name", secretNamespaceName.Name)
+		statusReconciler.UpdateNoError(ctx, api.ConditionFalse, "NotFound", "Failed to retrieve provisioner secret: %v", err)
+		return ctrl.Result{}, err
+	}
 
-	return ctrl.Result{}, nil
+	// Initialize and store the provisioner
+	p, err := provisioners.New(iss, password)
+	if err != nil {
+		log.Error(err, "failed to initialize provisioner")
+		statusReconciler.UpdateNoError(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", "StepClusterIssuer verified and ready to sign certificates")
 }
 
 func (r *StepClusterIssuerReconciler) SetupWithManager(mgr ctrl.Manager) error {
 	return ctrl.NewControllerManagedBy(mgr).
-		For(&certmanagerv1beta1.StepClusterIssuer{}).
+		For(&api.StepClusterIssuer{}).
 		Complete(r)
 }
+
+func validateStepClusterIssuerSpec(s api.StepClusterIssuerSpec) 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
+	}
+}
\ No newline at end of file
diff --git a/main.go b/main.go
index f0fbced..0a9ad46 100644
--- a/main.go
+++ b/main.go
@@ -88,6 +88,16 @@ func main() {
 		os.Exit(1)
 	}
 
+	if err = (&controllers.StepClusterIssuerReconciler{
+		Client: mgr.GetClient(),
+		Log:    ctrl.Log.WithName("controllers").WithName("StepClusterIssuer"),
+		Clock:  clock.RealClock{},
+		Recorder: mgr.GetEventRecorderFor("stepclusterissuer-controller"),
+	}).SetupWithManager(mgr); err != nil {
+		setupLog.Error(err, "unable to create controller", "controller", "StepClusterIssuer")
+		os.Exit(1)
+	}
+
 	if err = (&controllers.CertificateRequestReconciler{
 		Client:                 mgr.GetClient(),
 		Log:                    ctrl.Log.WithName("controllers").WithName("CertificateRequest"),
@@ -99,14 +109,6 @@ func main() {
 		os.Exit(1)
 	}
 
-	if err = (&controllers.StepClusterIssuerReconciler{
-		Client: mgr.GetClient(),
-		Log:    ctrl.Log.WithName("controllers").WithName("StepClusterIssuer"),
-		Scheme: mgr.GetScheme(),
-	}).SetupWithManager(mgr); err != nil {
-		setupLog.Error(err, "unable to create controller", "controller", "StepClusterIssuer")
-		os.Exit(1)
-	}
 	// +kubebuilder:scaffold:builder
 
 	setupLog.Info("starting manager")
-- 
GitLab