Skip to content

Commit

Permalink
csiaddons: add support for TLS
Browse files Browse the repository at this point in the history
Adds required permissions

Signed-off-by: Bipul Adhikari <[email protected]>
  • Loading branch information
bipuladh committed Nov 13, 2024
1 parent 85fb846 commit 3ea6c94
Show file tree
Hide file tree
Showing 8 changed files with 402 additions and 8 deletions.
11 changes: 11 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ func main() {
var probeAddr string
var secureMetrics bool
var enableHTTP2 bool
var enableCertificateGeneration bool
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
Expand All @@ -66,6 +67,7 @@ func main() {
"If set the metrics endpoint is served securely")
flag.BoolVar(&enableHTTP2, "enable-http2", false,
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
flag.BoolVar(&enableCertificateGeneration, "enable-cert-generation", false, "If set, CertificateSigningRequests will be used to generate certificates.")
opts := zap.Options{
Development: true,
}
Expand Down Expand Up @@ -122,6 +124,15 @@ func main() {
os.Exit(1)
}

if enableCertificateGeneration {
if err = (&controller.CertsReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Certificates")
}
}

if err = (&controller.DriverReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Expand Down
2 changes: 2 additions & 0 deletions config/manager/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
resources:
- manager.yaml
- manager_role.yaml
- manager_role_binding.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
patches:
Expand Down
14 changes: 14 additions & 0 deletions config/manager/managaer_role.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: manager-role
rules:
# Permissions for CSR management
- apiGroups: ["certificates.k8s.io"]
resources: ["certificatesigningrequests"]
verbs: ["create", "update", "get", "list", "approve"]

# Permissions for Secret management
- apiGroups: [""]
resources: ["secrets"]
verbs: ["create", "update", "get", "list"]
11 changes: 11 additions & 0 deletions config/manager/manager_role_binding.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: manager-role-binding
subjects:
- kind: ServiceAccount
name: controller-manageer
roleRef:
kind: Role
name: manager-role
apiGroup: rbac.authorization.k8s.io
132 changes: 132 additions & 0 deletions internal/controller/csr_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
Copyright 2024.
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 controller

import (
"context"
"crypto/x509"
"encoding/pem"
"errors"
"time"

certv1 "k8s.io/api/certificates/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

//+kubebuilder:rbac:groups=csi.ceph.io,resources=drivers,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=csi.ceph.io,resources=drivers/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=csi.ceph.io,resources=drivers/finalizers,verbs=update
//+kubebuilder:rbac:groups=csi.ceph.io,resources=operatorconfigs,verbs=get;list;watch
//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update
//+kubebuilder:rbac:groups=storage.k8s.io,resources=csidrivers,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=apps,resources=daemonsets,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete

type CertsReconciler struct {
client.Client
Scheme *runtime.Scheme
}

func filterCsiCertificateSigningRequests() predicate.Predicate {
return predicate.NewPredicateFuncs(func(object client.Object) bool {
labels := object.GetLabels()
return labels != nil && labels["managed-by"] == "ceph-csi-operator"
})
}

func (r *CertsReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).For(&certv1.CertificateSigningRequest{}).WithEventFilter(filterCsiCertificateSigningRequests()).Complete(r)
}

func (c *CertsReconciler) retrieveSignedCertificate(csr *certv1.CertificateSigningRequest) (ctrl.Result, error) {
if csr.Status.Certificate == nil {
// Retry later if the certificate is not ready yet
return ctrl.Result{RequeueAfter: time.Second * 5}, nil
}
return ctrl.Result{}, nil
}

func (r *CertsReconciler) Reconcile(ctx context.Context, request ctrl.Request) (reconcile.Result, error) {
log := ctrllog.FromContext(ctx)
log.Info("Starting reconcile for Certificate signing requests", "req", request)

csr := &certv1.CertificateSigningRequest{}
if err := r.Get(ctx, request.NamespacedName, csr); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}

if isCSRApproved(csr) {
return r.retrieveSignedCertificate(csr)
}

// Validate CSR contents to ensure it meets the requirements
if err := validateCSR(csr); err != nil {
// Log the error, or update the status to indicate failure
return ctrl.Result{}, err
}

// Approve the CSR
csr.Status.Conditions = append(csr.Status.Conditions, certv1.CertificateSigningRequestCondition{
Type: certv1.CertificateApproved,
Status: v1.ConditionTrue,
Reason: "AutoApproved",
Message: "CSR auto-approved by Ceph CSI CSR Controller",
LastUpdateTime: metav1.Now(),
})

if err := r.Status().Update(ctx, csr); err != nil {
return ctrl.Result{}, err
}

return ctrl.Result{}, nil
}

func isCSRApproved(csr *certv1.CertificateSigningRequest) bool {
for _, condition := range csr.Status.Conditions {
if condition.Type == certv1.CertificateApproved {
return true
}
}
return false
}

// validateCSR validates CSR fields for the required certificate attributes
func validateCSR(csr *certv1.CertificateSigningRequest) error {
csrBytes, _ := pem.Decode(csr.Spec.Request)
if csrBytes == nil {
return errors.New("failed to parse CSR PEM")
}
certRequest, err := x509.ParseCertificateRequest(csrBytes.Bytes)
if err != nil {
return err
}

// Validate required fields (e.g., organization, common name, etc.)
if len(certRequest.Subject.Organization) == 0 || certRequest.Subject.CommonName == "" {
return errors.New("CSR is missing required fields")
}

return nil
}
89 changes: 83 additions & 6 deletions internal/controller/driver_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,68 @@ var nameRegExp, _ = regexp.Compile(fmt.Sprintf(
// DriverReconciler reconciles a Driver object
type DriverReconciler struct {
client.Client
Scheme *runtime.Scheme
Scheme *runtime.Scheme
enableTLS bool
}

// A local reconcile object tied to a single reconcile iteration
type driverReconcile struct {
DriverReconciler

ctx context.Context
log logr.Logger
driver csiv1a1.Driver
driverType DriverType
images map[string]string
ctx context.Context
log logr.Logger
driver csiv1a1.Driver
driverType DriverType
images map[string]string
certificateGenerated bool
}

func createK8sSecret(csrPEM, privKeyPEM []byte, secretName, namespace string) (*corev1.Secret, error) {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: namespace,
},
Data: map[string][]byte{
"tls.crt": csrPEM, // Store the CSR as the certificate (it can be replaced with an actual cert later)
"tls.key": privKeyPEM,
},
}

return secret, nil
}

func (r *driverReconcile) performCertificateGeneration(Name string, log logr.Logger) error {

if r.isRdbDriver() {
if !r.isCertificateGenerated() {
secretName := "tls-cert"
requestObject, privateKey, err := utils.GetCertificateSigningRequest(r.driver.Name,
Name,
[]string{"rook"}, []string{strings.Join([]string{Name, "*"},
"kubernetes.io/kube-apiserver-client")}, "ceph-csi")
if err != nil {
log.Error(err, "Failed to create certificate signign request object")
return err
}
err = r.Client.Create(r.ctx, requestObject)
if err != nil {
log.Error(err, "Failed to create CertificateSigningRequest resource")
return err
}
secretObject, err := createK8sSecret(requestObject.Spec.Request, privateKey, secretName, r.driver.Namespace)
if err != nil {
log.Error(err, "Failed to create CertificateSigningRequest resource")
return err
}
err = r.Client.Create(r.ctx, secretObject)
if err != nil {
log.Error(err, "Failed to create secret resource")
return err
}
}
}
return nil
}

// SetupWithManager sets up the controller with the Manager.
Expand Down Expand Up @@ -516,6 +566,10 @@ func (r *driverReconcile) reconcileControllerPluginDeployment() error {
log := r.log.WithValues("deploymentName", deploy.Name)
log.Info("Reconciling controller plugin deployment")

if err := r.performCertificateGeneration(deploy.Name, log); err != nil {
return err
}

opResult, err := ctrlutil.CreateOrUpdate(r.ctx, r.Client, deploy, func() error {
if err := ctrlutil.SetControllerReference(&r.driver, deploy, r.Scheme); err != nil {
log.Error(err, "Failed setting an owner reference on deployment")
Expand Down Expand Up @@ -794,6 +848,9 @@ func (r *driverReconcile) reconcileControllerPluginDeployment() error {
if logRotationEnabled {
mounts = append(mounts, utils.LogsDirVolumeMount)
}
if r.enableTLS {
mounts = append(mounts, utils.RBDCtrlPluginTLSCertVolumeMount)
}
return mounts
}),
Resources: ptr.Deref(
Expand Down Expand Up @@ -908,6 +965,9 @@ func (r *driverReconcile) reconcileControllerPluginDeployment() error {
utils.LogRotateDirVolumeName(r.driver.Name),
)
}
if r.enableTLS {
volumes = append(volumes, utils.TlsCertsRBDCtrlPluginVolume)
}
return volumes
}),
},
Expand All @@ -929,6 +989,12 @@ func (r *driverReconcile) reconcileNodePluginDeamonSet() error {
log := r.log.WithValues("daemonSetName", daemonSet.Name)
log.Info("Reconciling node plugin deployment")

if err := r.performCertificateGeneration(daemonSet.Name, log); err != nil {
return err
}

// if is rbd driver and certificates are required then generate a certificate if not already

opResult, err := ctrlutil.CreateOrUpdate(r.ctx, r.Client, daemonSet, func() error {
if err := ctrlutil.SetControllerReference(&r.driver, daemonSet, r.Scheme); err != nil {
log.Error(err, "Failed setting an owner reference on deployment")
Expand Down Expand Up @@ -1129,6 +1195,7 @@ func (r *driverReconcile) reconcileNodePluginDeamonSet() error {
utils.If(logRotationEnabled, utils.LogToStdErrContainerArg, ""),
utils.If(logRotationEnabled, utils.AlsoLogToStdErrContainerArg, ""),
utils.If(logRotationEnabled, utils.LogFileContainerArg("csi-addons"), ""),
utils.If(r.enableTLS, "--enable-tls=true", ""),
},
),
Ports: []corev1.ContainerPort{
Expand All @@ -1147,6 +1214,9 @@ func (r *driverReconcile) reconcileNodePluginDeamonSet() error {
if logRotationEnabled {
mounts = append(mounts, utils.LogsDirVolumeMount)
}
if r.enableTLS {
mounts = append(mounts, utils.RBDNodePluginTLSCertVolumeMount)
}
return mounts
}),
Resources: ptr.Deref(
Expand Down Expand Up @@ -1250,6 +1320,9 @@ func (r *driverReconcile) reconcileNodePluginDeamonSet() error {
volumes,
utils.OidcTokenVolume,
)
if r.enableTLS {
volumes = append(volumes, utils.TlsCertsRBDNodePluginVolume)
}
}
if logRotationEnabled {
logHostPath := cmp.Or(logRotationSpec.LogHostPath, defaultLogHostPath)
Expand Down Expand Up @@ -1314,6 +1387,10 @@ func (r *driverReconcile) reconcileLivenessService() error {
}
}

func (r *driverReconcile) isCertificateGenerated() bool {
return r.certificateGenerated
}

func (r *driverReconcile) isRdbDriver() bool {
return r.driverType == RbdDriverType
}
Expand Down
Loading

0 comments on commit 3ea6c94

Please sign in to comment.