Skip to content

Commit a7cc613

Browse files
authored
Merge pull request #5055 from hjiawei/fluent-bit-syslog-ca-bundle
logcollector: give fluent-bit its own trusted bundle; user CA for Splunk HEC
2 parents 3d9ce54 + a1253c4 commit a7cc613

5 files changed

Lines changed: 124 additions & 17 deletions

File tree

pkg/controller/logcollector/logcollector_controller.go

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,14 @@ func add(mgr manager.Manager, c ctrlruntime.Controller) error {
142142
return fmt.Errorf("logcollector-controller failed to watch ConfigMap %s: %v", rlogcollector.FluentBitFilterConfigMapName, err)
143143
}
144144

145+
// Watch the user-supplied CA ConfigMaps so creating or rotating a syslog or
146+
// Splunk CA takes effect without waiting for an unrelated reconcile.
147+
for _, caConfigMap := range []string{rlogcollector.SyslogCAConfigMapName, rlogcollector.SplunkCAConfigMapName} {
148+
if err = utils.AddConfigMapWatch(c, caConfigMap, common.OperatorNamespace(), &handler.EnqueueRequestForObject{}); err != nil {
149+
return fmt.Errorf("logcollector-controller failed to watch ConfigMap %s: %v", caConfigMap, err)
150+
}
151+
}
152+
145153
// Watch the rendered configuration ConfigMaps so tampering with them
146154
// triggers a reconcile that restores the rendered content.
147155
for _, configMapName := range []string{
@@ -465,11 +473,11 @@ func (r *ReconcileLogCollector) Reconcile(ctx context.Context, request reconcile
465473
}
466474

467475
// Fluent Bit needs to mount system certificates in the case where Splunk, Syslog or AWS are used.
468-
trustedBundle, err := certificateManager.CreateTrustedBundleWithSystemRootCertificates(prometheusCertificate, linseedCertificate)
469-
if err != nil {
470-
r.status.SetDegraded(operatorv1.ResourceCreateError, "Unable to create tigera-ca-bundle configmap", err, reqLogger)
471-
return reconcile.Result{}, err
472-
}
476+
// The bundle carries fluent-bit's own name: calico-system's shared tigera-ca-bundle is rendered by
477+
// the core Installation controller with a different certificate set, and the component handler
478+
// replaces ConfigMap data wholesale — an unnamed bundle here would fight it, and additions like
479+
// the syslog user CA would be lost to whichever controller wrote last.
480+
trustedBundle := certificatemanagement.CreateNamedTrustedBundle(render.FluentBitNodeName, certificateManager.KeyPair(), true, prometheusCertificate, linseedCertificate)
473481

474482
certificateManager.AddToStatusManager(r.status, render.LogCollectorNamespace)
475483

@@ -524,7 +532,7 @@ func (r *ReconcileLogCollector) Reconcile(ctx context.Context, request reconcile
524532
var useSyslogCertificate bool
525533
if instance.Spec.AdditionalStores != nil {
526534
if instance.Spec.AdditionalStores.Syslog != nil && instance.Spec.AdditionalStores.Syslog.Encryption == operatorv1.EncryptionTLS {
527-
syslogCert, err := getSysLogCertificate(r.client)
535+
syslogCert, err := getUserCACertificate(r.client, rlogcollector.SyslogCAConfigMapName)
528536
if err != nil {
529537
r.status.SetDegraded(operatorv1.ResourceReadError, "Error loading Syslog certificate", err, reqLogger)
530538
return reconcile.Result{}, err
@@ -534,6 +542,22 @@ func (r *ReconcileLogCollector) Reconcile(ctx context.Context, request reconcile
534542
trustedBundle.AddCertificates(syslogCert)
535543
}
536544
}
545+
// The Splunk output verifies https HEC endpoints against the trusted
546+
// bundle, so a user CA for a self-hosted Splunk rides the same way as
547+
// the syslog one. Plain-http endpoints do no verification, so like
548+
// syslog's TLS gate the CA is only loaded for https.
549+
if splunk := instance.Spec.AdditionalStores.Splunk; splunk != nil {
550+
if proto, _, _, err := url.ParseEndpoint(splunk.Endpoint); err == nil && proto == "https" {
551+
splunkCert, err := getUserCACertificate(r.client, rlogcollector.SplunkCAConfigMapName)
552+
if err != nil {
553+
r.status.SetDegraded(operatorv1.ResourceReadError, "Error loading Splunk certificate", err, reqLogger)
554+
return reconcile.Result{}, err
555+
}
556+
if splunkCert != nil {
557+
trustedBundle.AddCertificates(splunkCert)
558+
}
559+
}
560+
}
537561
}
538562

539563
if instance.Spec.AdditionalStores != nil {
@@ -872,24 +896,25 @@ func getEksCloudwatchLogConfig(client client.Client, interval int32, region, gro
872896
}, nil
873897
}
874898

875-
func getSysLogCertificate(client client.Client) (certificatemanagement.CertificateInterface, error) {
899+
// getUserCACertificate reads an optional user-supplied CA from the named
900+
// ConfigMap in the operator namespace (key tls.crt), for stores whose TLS
901+
// endpoint is not signed by a publicly trusted CA.
902+
func getUserCACertificate(client client.Client, name string) (certificatemanagement.CertificateInterface, error) {
876903
cm := &corev1.ConfigMap{}
877904
cmNamespacedName := types.NamespacedName{
878-
Name: rlogcollector.SyslogCAConfigMapName,
905+
Name: name,
879906
Namespace: common.OperatorNamespace(),
880907
}
881908
if err := client.Get(context.Background(), cmNamespacedName, cm); err != nil {
882909
if errors.IsNotFound(err) {
883-
log.Info(fmt.Sprintf("ConfigMap %q is not found, assuming syslog's certificate is signed by publicly trusted CA", rlogcollector.SyslogCAConfigMapName))
910+
log.Info(fmt.Sprintf("ConfigMap %q is not found, assuming the endpoint's certificate is signed by publicly trusted CA", name))
884911
return nil, nil
885912
}
886-
return nil, fmt.Errorf("failed to read ConfigMap %q: %s", rlogcollector.SyslogCAConfigMapName, err)
913+
return nil, fmt.Errorf("failed to read ConfigMap %q: %s", name, err)
887914
}
888915
if len(cm.Data[corev1.TLSCertKey]) == 0 {
889-
log.Info(fmt.Sprintf("ConfigMap %q does not have a field named %q, assuming syslog's certificate is signed by publicly trusted CA", rlogcollector.SyslogCAConfigMapName, corev1.TLSCertKey))
916+
log.Info(fmt.Sprintf("ConfigMap %q does not have a field named %q, assuming the endpoint's certificate is signed by publicly trusted CA", name, corev1.TLSCertKey))
890917
return nil, nil
891918
}
892-
syslogCert := certificatemanagement.NewCertificate(rlogcollector.SyslogCAConfigMapName, common.OperatorNamespace(), []byte(cm.Data[corev1.TLSCertKey]), nil)
893-
894-
return syslogCert, nil
919+
return certificatemanagement.NewCertificate(name, common.OperatorNamespace(), []byte(cm.Data[corev1.TLSCertKey]), nil), nil
895920
}

pkg/controller/logcollector/logcollector_controller_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,49 @@ var _ = Describe("LogCollector controller tests", func() {
494494
Expect(conf).To(ContainSubstring(`"splunk_token": "${SPLUNK_HEC_TOKEN}"`))
495495
})
496496

497+
It("renders a user-supplied Splunk CA into fluent-bit's bundle", func() {
498+
caPEM := "-----BEGIN CERTIFICATE-----\nsplunk-user-ca\n-----END CERTIFICATE-----"
499+
Expect(c.Create(ctx, &corev1.ConfigMap{
500+
ObjectMeta: metav1.ObjectMeta{Name: rlogcollector.SplunkCAConfigMapName, Namespace: common.OperatorNamespace()},
501+
Data: map[string]string{corev1.TLSCertKey: caPEM},
502+
})).NotTo(HaveOccurred())
503+
504+
_, err := r.Reconcile(ctx, reconcile.Request{})
505+
Expect(err).ShouldNot(HaveOccurred())
506+
507+
bundle := corev1.ConfigMap{
508+
TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
509+
ObjectMeta: metav1.ObjectMeta{Name: "calico-fluent-bit-ca-bundle-system-certs", Namespace: render.LogCollectorNamespace},
510+
}
511+
Expect(test.GetResource(c, &bundle)).To(BeNil())
512+
Expect(bundle.Data["tigera-ca-bundle.crt"]).To(ContainSubstring(caPEM))
513+
})
514+
515+
It("does not load the Splunk CA for a plain-http endpoint", func() {
516+
lc := operatorv1.LogCollector{
517+
TypeMeta: metav1.TypeMeta{Kind: "LogCollector", APIVersion: "operator.tigera.io/v1"},
518+
ObjectMeta: metav1.ObjectMeta{Name: "tigera-secure"},
519+
}
520+
Expect(test.GetResource(c, &lc)).To(BeNil())
521+
lc.Spec.AdditionalStores.Splunk.Endpoint = "http://localhost:1234"
522+
Expect(c.Update(ctx, &lc)).NotTo(HaveOccurred())
523+
caPEM := "-----BEGIN CERTIFICATE-----\nsplunk-user-ca\n-----END CERTIFICATE-----"
524+
Expect(c.Create(ctx, &corev1.ConfigMap{
525+
ObjectMeta: metav1.ObjectMeta{Name: rlogcollector.SplunkCAConfigMapName, Namespace: common.OperatorNamespace()},
526+
Data: map[string]string{corev1.TLSCertKey: caPEM},
527+
})).NotTo(HaveOccurred())
528+
529+
_, err := r.Reconcile(ctx, reconcile.Request{})
530+
Expect(err).ShouldNot(HaveOccurred())
531+
532+
bundle := corev1.ConfigMap{
533+
TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
534+
ObjectMeta: metav1.ObjectMeta{Name: "calico-fluent-bit-ca-bundle-system-certs", Namespace: render.LogCollectorNamespace},
535+
}
536+
Expect(test.GetResource(c, &bundle)).To(BeNil())
537+
Expect(bundle.Data["tigera-ca-bundle.crt"]).NotTo(ContainSubstring(caPEM))
538+
})
539+
497540
Context("Disable feature via license", func() {
498541
BeforeEach(func() {
499542
By("Deleting the previous license")
@@ -584,6 +627,42 @@ var _ = Describe("LogCollector controller tests", func() {
584627
Expect(conf).To(ContainSubstring(`"call": "syslog_pack"`))
585628
})
586629

630+
It("renders the syslog user CA into fluent-bit's own bundle, not the shared tigera-ca-bundle", func() {
631+
By("Switching the syslog store to TLS with a user-supplied CA")
632+
lc := operatorv1.LogCollector{
633+
TypeMeta: metav1.TypeMeta{Kind: "LogCollector", APIVersion: "operator.tigera.io/v1"},
634+
ObjectMeta: metav1.ObjectMeta{Name: "tigera-secure"},
635+
}
636+
Expect(test.GetResource(c, &lc)).To(BeNil())
637+
lc.Spec.AdditionalStores.Syslog.Encryption = operatorv1.EncryptionTLS
638+
Expect(c.Update(ctx, &lc)).NotTo(HaveOccurred())
639+
caPEM := "-----BEGIN CERTIFICATE-----\nsyslog-user-ca\n-----END CERTIFICATE-----"
640+
Expect(c.Create(ctx, &corev1.ConfigMap{
641+
ObjectMeta: metav1.ObjectMeta{Name: rlogcollector.SyslogCAConfigMapName, Namespace: common.OperatorNamespace()},
642+
Data: map[string]string{corev1.TLSCertKey: caPEM},
643+
})).NotTo(HaveOccurred())
644+
645+
_, err := r.Reconcile(ctx, reconcile.Request{})
646+
Expect(err).ShouldNot(HaveOccurred())
647+
648+
// The bundle must be fluent-bit's own: the core Installation controller
649+
// renders calico-system's shared tigera-ca-bundle with a different
650+
// certificate set, so additions made there would be overwritten.
651+
bundle := corev1.ConfigMap{
652+
TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
653+
ObjectMeta: metav1.ObjectMeta{Name: "calico-fluent-bit-ca-bundle-system-certs", Namespace: render.LogCollectorNamespace},
654+
}
655+
Expect(test.GetResource(c, &bundle)).To(BeNil())
656+
Expect(bundle.Data["tigera-ca-bundle.crt"]).To(ContainSubstring(caPEM))
657+
658+
shared := corev1.ConfigMap{
659+
TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
660+
ObjectMeta: metav1.ObjectMeta{Name: "tigera-ca-bundle", Namespace: render.LogCollectorNamespace},
661+
}
662+
Expect(errors.IsNotFound(test.GetResource(c, &shared))).To(BeTrue(),
663+
"the logcollector controller must not render the shared tigera-ca-bundle")
664+
})
665+
587666
Context("Disable feature via license", func() {
588667
BeforeEach(func() {
589668
By("Deleting the previous license")

pkg/render/logcollector/fluentbit_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import (
4949
"github.com/tigera/operator/pkg/render/logcollector"
5050
"github.com/tigera/operator/pkg/render/testutils"
5151
"github.com/tigera/operator/pkg/tls"
52+
"github.com/tigera/operator/pkg/tls/certificatemanagement"
5253
"github.com/tigera/operator/test"
5354
)
5455

@@ -82,7 +83,7 @@ var _ = Describe("Tigera Secure Fluent Bit rendering tests", func() {
8283
},
8384
FluentBitKeyPair: metricsSecret,
8485
EKSLogForwarderKeyPair: eksSecret,
85-
TrustedBundle: certificateManager.CreateTrustedBundle(),
86+
TrustedBundle: certificatemanagement.CreateNamedTrustedBundle(render.FluentBitNodeName, certificateManager.KeyPair(), true),
8687
}
8788
})
8889

@@ -693,7 +694,7 @@ var _ = Describe("Tigera Secure Fluent Bit rendering tests", func() {
693694
for _, vol := range ds.Spec.Template.Spec.Volumes {
694695
volnames = append(volnames, vol.Name)
695696
}
696-
Expect(volnames).To(ContainElement("tigera-ca-bundle"))
697+
Expect(volnames).To(ContainElement("calico-fluent-bit-ca-bundle-system-certs"))
697698

698699
// Syslog TLS configuration is in the ConfigMap.
699700
cm := rtest.GetResource(resources, logcollector.FluentBitConfConfigMapName, render.LogCollectorNamespace, "", "v1", "ConfigMap").(*corev1.ConfigMap)

pkg/render/logcollector/logcollector.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ const (
7373
SysLogPublicCertKey = "ca-bundle.crt"
7474
SysLogPublicCAPath = SysLogPublicCADir + SysLogPublicCertKey
7575
SyslogCAConfigMapName = "syslog-ca"
76+
SplunkCAConfigMapName = "splunk-ca"
7677

7778
// Constants for Linseed token volume mounting in managed clusters.
7879
LinseedTokenVolumeName = render.LinseedTokenVolumeName

pkg/render/logcollector/rendered_config_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
rmeta "github.com/tigera/operator/pkg/render/common/meta"
3636
rtest "github.com/tigera/operator/pkg/render/common/test"
3737
"github.com/tigera/operator/pkg/render/logcollector"
38+
"github.com/tigera/operator/pkg/tls/certificatemanagement"
3839
)
3940

4041
// TestRenderedConfigGoldens pins the full fluent-bit configuration the
@@ -336,7 +337,7 @@ func goldenBaseConfig(t *testing.T) *logcollector.FluentBitConfiguration {
336337
},
337338
FluentBitKeyPair: metricsSecret,
338339
EKSLogForwarderKeyPair: eksSecret,
339-
TrustedBundle: certificateManager.CreateTrustedBundle(),
340+
TrustedBundle: certificatemanagement.CreateNamedTrustedBundle(render.FluentBitNodeName, certificateManager.KeyPair(), true),
340341
}
341342
}
342343

0 commit comments

Comments
 (0)