diff --git a/charts/kafka-operator/templates/operator-rbac.yaml b/charts/kafka-operator/templates/operator-rbac.yaml index 371cee9c7..29557e617 100644 --- a/charts/kafka-operator/templates/operator-rbac.yaml +++ b/charts/kafka-operator/templates/operator-rbac.yaml @@ -267,6 +267,18 @@ rules: - patch - update - watch +- apiGroups: + - certificates.k8s.io + resources: + - certificatesigningrequests/approval + verbs: + - update +- apiGroups: + - certificates.k8s.io + resources: + - signers + verbs: + - approve - apiGroups: - coordination.k8s.io resources: diff --git a/config/base/rbac/role.yaml b/config/base/rbac/role.yaml index 58e792520..6b4a6e8dc 100644 --- a/config/base/rbac/role.yaml +++ b/config/base/rbac/role.yaml @@ -148,6 +148,18 @@ rules: - patch - update - watch +- apiGroups: + - certificates.k8s.io + resources: + - certificatesigningrequests/approval + verbs: + - update +- apiGroups: + - certificates.k8s.io + resources: + - signers + verbs: + - approve - apiGroups: - coordination.k8s.io resources: diff --git a/controllers/kafkauser_controller.go b/controllers/kafkauser_controller.go index e84b7ab78..1b5cb744c 100644 --- a/controllers/kafkauser_controller.go +++ b/controllers/kafkauser_controller.go @@ -159,6 +159,8 @@ type KafkaUserReconciler struct { // +kubebuilder:rbac:groups=cert-manager.io,resources=issuers,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=cert-manager.io,resources=clusterissuers,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=certificates.k8s.io,resources=certificatesigningrequests,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=certificates.k8s.io,resources=certificatesigningrequests/approval,verbs=update +// +kubebuilder:rbac:groups=certificates.k8s.io,resources=signers,verbs=approve // Reconcile reads that state of the cluster for a KafkaUser object and makes changes based on the state read // and what is in the KafkaUser.Spec diff --git a/pkg/pki/k8scsrpki/k8scsr.go b/pkg/pki/k8scsrpki/k8scsr.go index b9a54e941..1ecfae4a9 100644 --- a/pkg/pki/k8scsrpki/k8scsr.go +++ b/pkg/pki/k8scsrpki/k8scsr.go @@ -22,8 +22,9 @@ import ( ) const ( - DependingCsrAnnotation string = "banzaicloud.io/csr" - IncludeFullChainAnnotation string = "csr.banzaicloud.io/fullchain" + DependingCsrAnnotation string = "banzaicloud.io/csr" + IncludeFullChainAnnotation string = "csr.banzaicloud.io/fullchain" + CertManagerSignerNamePrefix string = "clusterissuers.cert-manager.io" ) type K8sCSR interface { diff --git a/pkg/pki/k8scsrpki/k8scsr_user.go b/pkg/pki/k8scsrpki/k8scsr_user.go index 8fa399ca7..bbc5b606d 100644 --- a/pkg/pki/k8scsrpki/k8scsr_user.go +++ b/pkg/pki/k8scsrpki/k8scsr_user.go @@ -19,6 +19,7 @@ import ( "crypto/x509" "encoding/pem" "fmt" + "strings" "emperror.dev/errors" "github.com/go-logr/logr" @@ -38,12 +39,15 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + csrclient "k8s.io/client-go/kubernetes/typed/certificates/v1" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) const ( notApprovedErrMsg = "instance is not approved" - notFoundApprovedCsrErrMsg = "could not find approved csr" + notFoundApprovedCsrErrMsg = "could not find approved csr and the operator is not capable of approving the csr" + approveReason = "ApprovedByPolicy" ) // ReconcileUserCertificate ensures and returns a user certificate - should be idempotent @@ -158,8 +162,15 @@ func (c *k8sCSR) ReconcileUserCertificate( } if !foundApproved { - return nil, errorfactory.New(errorfactory.FatalReconcileError{}, errors.New(notApprovedErrMsg), - notFoundApprovedCsrErrMsg, "csrName", signingReq.GetName()) + if strings.Split(signingReq.Spec.SignerName, "/")[0] == CertManagerSignerNamePrefix { + err = c.Approve(ctx, signingReq) + if err != nil { + return nil, err + } + } else { + return nil, errorfactory.New(errorfactory.FatalReconcileError{}, errors.New(notApprovedErrMsg), + notFoundApprovedCsrErrMsg, "csrName", signingReq.GetName()) + } } if len(signingReq.Status.Certificate) == 0 { return nil, errorfactory.New(errorfactory.ResourceNotReady{}, @@ -309,3 +320,27 @@ func isKafkaUserCertificateReady(secret *corev1.Secret, includeJKS bool) bool { return true } + +// Approve approves certificate signing requests +func (c *k8sCSR) Approve(ctx context.Context, signingReq *certsigningreqv1.CertificateSigningRequest) error { + cond := certsigningreqv1.CertificateSigningRequestCondition{ + Type: certsigningreqv1.CertificateApproved, + Status: corev1.ConditionTrue, + Reason: approveReason, + Message: "CSR has been approved by Koperator", + } + signingReq.Status.Conditions = append(signingReq.Status.Conditions, cond) + + restConfig, err := ctrl.GetConfig() + if err != nil { + return err + } + csrClient := csrclient.NewForConfigOrDie(restConfig).CertificateSigningRequests() + + signingReq, err = csrClient.UpdateApproval(ctx, signingReq.Name, signingReq, metav1.UpdateOptions{}) //nolint:staticcheck + if err != nil { + return err + } + + return nil +}