Skip to content

Commit

Permalink
feat: Add LogPipeline TLS Certificate Validation (kyma-project#914)
Browse files Browse the repository at this point in the history
  • Loading branch information
hisarbalik authored Apr 3, 2024
1 parent f2e9df4 commit 0c1b1be
Show file tree
Hide file tree
Showing 13 changed files with 611 additions and 49 deletions.
3 changes: 2 additions & 1 deletion .gitleaksignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
791662e0244f1e326975d9010a611a55d4506623:internal/tls/validate_test.go:private-key:10
304d906b7d106982bf2fe8fe935a64da3b0fe22d:internal/tls/validate_test.go:private-key:23
304d906b7d106982bf2fe8fe935a64da3b0fe22d:internal/tls/validate_test.go:private-key:23
f7d7d265cb40c46f876bc0a825d7efffbbcc05db:internal/tls/cert/tls_cert_validator_test.go:private-key:26
19 changes: 13 additions & 6 deletions docs/user/resources/02-logpipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,17 @@ The status of the LogPipeline is determined by the condition types `AgentHealthy

> **NOTE:** The condition types `Running` and `Pending` are deprecated and will be removed soon from the status conditions.

| Condition Type | Condition Status | Condition Reason | Condition Message |
|------------------------|------------------|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| AgentHealthy | True | DaemonSetReady | Fluent Bit DaemonSet is ready |
| AgentHealthy | False | DaemonSetNotReady | Fluent Bit DaemonSet is not ready |
| ConfigurationGenerated | True | ConfigurationGenerated | |
| ConfigurationGenerated | False | ReferencedSecretMissing | One or more referenced Secrets are missing |
| Condition Type | Condition Status | Condition Reason | Condition Message |
|------------------------|------------------|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| AgentHealthy | True | DaemonSetReady | Fluent Bit DaemonSet is ready |
| AgentHealthy | False | DaemonSetNotReady | Fluent Bit DaemonSet is not ready |
| ConfigurationGenerated | True | ConfigurationGenerated | |
| ConfigurationGenerated | False | ReferencedSecretMissing | One or more referenced Secrets are missing |
| ConfigurationGenerated | False | UnsupportedLokiOutput | grafana-loki output is not supported anymore. For integration with a custom Loki installation, use the `custom` output and follow [Intergrate with Loki](https://kyma-project.io/#/telemetry-manager/user/integration/loki/README). |
| ConfigurationGenerated | False | InvalidTLSCert | TLS certificate invalid |
| ConfigurationGenerated | False | InvalidTLSPrivateKey | TLS private key invalid |
| ConfigurationGenerated | False | ExpiredTLSCert | TLS certificate expired on YYYY-MM-DD |
| ConfigurationGenerated | True | TLSCertAboutToExpire | TLS certificate is about to expire, configured certificate is valid until YYYY-MM-DD |



48 changes: 28 additions & 20 deletions internal/conditions/conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,24 @@ const (
)

const (
ReasonNoPipelineDeployed = "NoPipelineDeployed"
ReasonReferencedSecretMissing = "ReferencedSecretMissing"
ReasonMaxPipelinesExceeded = "MaxPipelinesExceeded"
ReasonResourceBlocksDeletion = "ResourceBlocksDeletion"
ReasonConfigurationGenerated = "ConfigurationGenerated"
ReasonDeploymentNotReady = "DeploymentNotReady"
ReasonDeploymentReady = "DeploymentReady"
ReasonDaemonSetNotReady = "DaemonSetNotReady"
ReasonDaemonSetReady = "DaemonSetReady"
ReasonAllDataDropped = "AllTelemetryDataDropped"
ReasonSomeDataDropped = "SomeTelemetryDataDropped"
ReasonBufferFillingUp = "BufferFillingUp"
ReasonGatewayThrottling = "GatewayThrottling"
ReasonFlowHealthy = "Healthy"
ReasonNoPipelineDeployed = "NoPipelineDeployed"
ReasonReferencedSecretMissing = "ReferencedSecretMissing"
ReasonMaxPipelinesExceeded = "MaxPipelinesExceeded"
ReasonResourceBlocksDeletion = "ResourceBlocksDeletion"
ReasonConfigurationGenerated = "ConfigurationGenerated"
ReasonDeploymentNotReady = "DeploymentNotReady"
ReasonDeploymentReady = "DeploymentReady"
ReasonDaemonSetNotReady = "DaemonSetNotReady"
ReasonDaemonSetReady = "DaemonSetReady"
ReasonAllDataDropped = "AllTelemetryDataDropped"
ReasonSomeDataDropped = "SomeTelemetryDataDropped"
ReasonBufferFillingUp = "BufferFillingUp"
ReasonGatewayThrottling = "GatewayThrottling"
ReasonFlowHealthy = "Healthy"
ReasonTLSCertificateInvalid = "TLSCertificateInvalid"
ReasonTLSPrivateKeyInvalid = "TLSPrivateKeyInvalid"
ReasonTLSCertificateExpired = "TLSCertificateExpired"
ReasonTLSCertificateAboutToExpire = "TLSCertAboutToExpire"

ReasonMetricAgentNotRequired = "AgentNotRequired"
ReasonMetricComponentsRunning = "MetricComponentsRunning"
Expand Down Expand Up @@ -83,12 +87,16 @@ var tracePipelineMessages = map[string]string{
}

var logPipelineMessages = map[string]string{
ReasonDaemonSetNotReady: "Fluent Bit DaemonSet is not ready",
ReasonDaemonSetReady: "Fluent Bit DaemonSet is ready",
ReasonFluentBitDSNotReady: "Fluent Bit DaemonSet is not ready",
ReasonFluentBitDSReady: "Fluent Bit DaemonSet is ready",
ReasonUnsupportedLokiOutput: "grafana-loki output is not supported anymore. For integration with a custom Loki installation, use the `custom` output and follow https://kyma-project.io/#/telemetry-manager/user/integration/loki/README",
ReasonLogComponentsRunning: "All log components are running",
ReasonDaemonSetNotReady: "Fluent Bit DaemonSet is not ready",
ReasonDaemonSetReady: "Fluent Bit DaemonSet is ready",
ReasonFluentBitDSNotReady: "Fluent Bit DaemonSet is not ready",
ReasonFluentBitDSReady: "Fluent Bit DaemonSet is ready",
ReasonUnsupportedLokiOutput: "grafana-loki output is not supported anymore. For integration with a custom Loki installation, use the `custom` output and follow https://kyma-project.io/#/telemetry-manager/user/integration/loki/README",
ReasonLogComponentsRunning: "All log components are running",
ReasonTLSCertificateInvalid: "TLS certificate invalid: %s",
ReasonTLSPrivateKeyInvalid: "TLS private key invalid: %s",
ReasonTLSCertificateExpired: "TLS certificate expired on %s",
ReasonTLSCertificateAboutToExpire: "TLS certificate is about to expire, configured certificate is valid until %s",
}

func MessageForLogPipeline(reason string) string {
Expand Down
15 changes: 9 additions & 6 deletions internal/reconciler/logpipeline/mocks/daemon_set_annotator.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 13 additions & 7 deletions internal/reconciler/logpipeline/mocks/daemon_set_prober.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions internal/reconciler/logpipeline/mocks/tls_cert_validator.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 60 additions & 2 deletions internal/reconciler/logpipeline/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package logpipeline
import (
"context"
"fmt"
"time"

"github.com/prometheus/client_golang/prometheus"
corev1 "k8s.io/api/core/v1"
Expand All @@ -38,6 +39,7 @@ import (
commonresources "github.com/kyma-project/telemetry-manager/internal/resources/common"
"github.com/kyma-project/telemetry-manager/internal/resources/fluentbit"
"github.com/kyma-project/telemetry-manager/internal/secretref"
"github.com/kyma-project/telemetry-manager/internal/tls/cert"
)

type Config struct {
Expand All @@ -64,6 +66,11 @@ type DaemonSetAnnotator interface {
SetAnnotation(ctx context.Context, name types.NamespacedName, key, value string) error
}

//go:generate mockery --name TLSCertValidator --filename tls_cert_validator.go
type TLSCertValidator interface {
ValidateCertificate(certPEM []byte, keyPEM []byte) cert.TLSCertValidationResult
}

type Reconciler struct {
client.Client
config Config
Expand All @@ -73,6 +80,7 @@ type Reconciler struct {
syncer syncer
overridesHandler *overrides.Handler
istioStatusChecker istiostatus.Checker
tlsCertValidator TLSCertValidator
}

func NewReconciler(client client.Client, config Config, prober DaemonSetProber, overridesHandler *overrides.Handler) *Reconciler {
Expand All @@ -86,6 +94,7 @@ func NewReconciler(client client.Client, config Config, prober DaemonSetProber,
r.syncer = syncer{client, config}
r.overridesHandler = overridesHandler
r.istioStatusChecker = istiostatus.NewChecker(client)
r.tlsCertValidator = &cert.TLSCertValidator{}

return &r
}
Expand Down Expand Up @@ -136,7 +145,7 @@ func (r *Reconciler) doReconcile(ctx context.Context, pipeline *telemetryv1alpha
return err
}

deployableLogPipelines := getDeployableLogPipelines(ctx, allPipelines.Items, r.Client)
deployableLogPipelines := getDeployableLogPipelines(ctx, allPipelines.Items, r.Client, r.tlsCertValidator)
if err = r.syncer.syncFluentBitConfig(ctx, pipeline, deployableLogPipelines); err != nil {
return err
}
Expand Down Expand Up @@ -295,9 +304,11 @@ func (r *Reconciler) calculateChecksum(ctx context.Context) (string, error) {

// getDeployableLogPipelines returns the list of log pipelines that are ready to be rendered into the Fluent Bit configuration.
// A pipeline is deployable if it is not being deleted, all secret references exist, and it doesn't have the legacy grafana-loki output defined.
func getDeployableLogPipelines(ctx context.Context, allPipelines []telemetryv1alpha1.LogPipeline, client client.Client) []telemetryv1alpha1.LogPipeline {
func getDeployableLogPipelines(ctx context.Context, allPipelines []telemetryv1alpha1.LogPipeline, client client.Client, certValidator TLSCertValidator) []telemetryv1alpha1.LogPipeline {
var deployablePipelines []telemetryv1alpha1.LogPipeline
for i := range allPipelines {
certValidationResult := getTLSCertValidationResult(ctx, &allPipelines[i], certValidator, client)

if !allPipelines[i].GetDeletionTimestamp().IsZero() {
continue
}
Expand All @@ -307,6 +318,10 @@ func getDeployableLogPipelines(ctx context.Context, allPipelines []telemetryv1al
if allPipelines[i].Spec.Output.IsLokiDefined() {
continue
}

if !certValidationResult.CertValid || !certValidationResult.PrivateKeyValid || time.Now().After(certValidationResult.Validity) {
continue
}
deployablePipelines = append(deployablePipelines, allPipelines[i])
}

Expand All @@ -319,3 +334,46 @@ func getFluentBitPorts() []int32 {
ports.HTTP,
}
}

func getTLSCertValidationResult(ctx context.Context, pipeline *telemetryv1alpha1.LogPipeline, validator TLSCertValidator, client client.Client) cert.TLSCertValidationResult {
if pipeline.Spec.Output.HTTP == nil || (pipeline.Spec.Output.HTTP.TLSConfig.Cert == nil && pipeline.Spec.Output.HTTP.TLSConfig.Key == nil) {
return cert.TLSCertValidationResult{
CertValid: true,
PrivateKeyValid: true,
Validity: time.Now().AddDate(1, 0, 0),
}
}

certValue := pipeline.Spec.Output.HTTP.TLSConfig.Cert
keyValue := pipeline.Spec.Output.HTTP.TLSConfig.Key

certData, err := resolveValue(ctx, client, *certValue)

if err != nil {
return cert.TLSCertValidationResult{
CertValid: false,
}
}

keyData, err := resolveValue(ctx, client, *keyValue)

if err != nil {
return cert.TLSCertValidationResult{
PrivateKeyValid: false,
}
}

return validator.ValidateCertificate(certData, keyData)

}

func resolveValue(ctx context.Context, c client.Reader, value telemetryv1alpha1.ValueType) ([]byte, error) {
if value.Value != "" {
return []byte(value.Value), nil
}
if value.ValueFrom.IsSecretKeyRef() {
return secretref.GetValue(ctx, c, *value.ValueFrom.SecretKeyRef)
}

return nil, fmt.Errorf("either value or secret key reference must be defined")
}
Loading

0 comments on commit 0c1b1be

Please sign in to comment.