Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TLS and basic authentication integration to Logstash API server #7408

Merged
merged 46 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
de6c293
Logstash add TLS support to API server
kaisecheng Dec 20, 2023
4395ddf
lint
kaisecheng Dec 20, 2023
df50578
revert unwanted changes
kaisecheng Dec 20, 2023
2ca0154
add test
kaisecheng Dec 20, 2023
adedb01
lint
kaisecheng Dec 20, 2023
9d00d6f
update e2e with basic authentication
kaisecheng Dec 21, 2023
33456d4
add comment
kaisecheng Dec 21, 2023
367ffbc
Merge branch 'main' of github.com:elastic/cloud-on-k8s into logstash_…
kaisecheng Dec 21, 2023
106bce1
optional ca.crt in bash script
kaisecheng Dec 21, 2023
10f9854
add global ca test
kaisecheng Dec 28, 2023
639d297
set default sslEnabled to true
kaisecheng Dec 28, 2023
62b15b8
add APIService config tests
kaisecheng Jan 2, 2024
edec5e5
- add support to dollar sign variable that resolve from keystore (sec…
kaisecheng Jan 5, 2024
738903f
lint
kaisecheng Jan 5, 2024
f16b52a
lint fix nested if
kaisecheng Jan 5, 2024
67efa51
fix test
kaisecheng Jan 5, 2024
d259d16
add doc
kaisecheng Jan 6, 2024
97a60e2
Update docs/orchestrating-elastic-stack-applications/logstash.asciidoc
kaisecheng Jan 9, 2024
2cb8796
Update docs/orchestrating-elastic-stack-applications/logstash.asciidoc
kaisecheng Jan 9, 2024
57762bd
Merge branch 'main' of github.com:elastic/cloud-on-k8s into logstash_…
kaisecheng Jan 9, 2024
1cf0fad
Merge branch 'logstash_support_https_api' of github.com:kaisecheng/cl…
kaisecheng Jan 9, 2024
a800bb1
- update doc
kaisecheng Jan 9, 2024
082e8f5
fix doc callouts
kaisecheng Jan 9, 2024
0cf91d5
fix no container
kaisecheng Jan 9, 2024
a83f861
fix doc reference
kaisecheng Jan 9, 2024
a58ac0e
fix doc link
kaisecheng Jan 10, 2024
9cdbc24
Update docs/orchestrating-elastic-stack-applications/logstash.asciidoc
kaisecheng Jan 12, 2024
ddb53c9
- remove default value substitution
kaisecheng Jan 12, 2024
d488cd2
fix indentation
kaisecheng Jan 17, 2024
c2b9d32
fix doc
kaisecheng Jan 17, 2024
f699a5c
rename method
kaisecheng Jan 17, 2024
18dc0d8
remove unnecessary pointer
kaisecheng Jan 17, 2024
02917ca
Update docs/orchestrating-elastic-stack-applications/logstash.asciidoc
kaisecheng Jan 17, 2024
cb39c60
remove bool UseTLS
kaisecheng Jan 17, 2024
dfce474
remove bool UseTLS
kaisecheng Jan 18, 2024
60ba948
use ContainerByName
kaisecheng Jan 18, 2024
365c946
set min version to 8.12.0
kaisecheng Jan 18, 2024
c4afca9
Merge branch 'main' of github.com:elastic/cloud-on-k8s into logstash_…
kaisecheng Jan 18, 2024
9a7ae39
Update test/e2e/logstash/stack_monitoring_test.go
kaisecheng Jan 22, 2024
80c966f
Update pkg/controller/logstash/stackmon/sidecar_test.go
kaisecheng Jan 22, 2024
53b493d
fix builder deepCopy
kaisecheng Jan 22, 2024
38598be
- remove stack monitoring version check
kaisecheng Jan 22, 2024
763397e
fix tests
kaisecheng Jan 22, 2024
070d1a6
Update pkg/controller/logstash/config.go
kaisecheng Jan 23, 2024
fc29424
nit
kaisecheng Jan 23, 2024
6b803a3
Merge branch 'main' of github.com:elastic/cloud-on-k8s into logstash_…
kaisecheng Jan 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions pkg/apis/logstash/v1alpha1/logstash_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,21 @@ func (l *Logstash) MonitoringAssociation(esRef commonv1.ObjectSelector) commonv1
}
}

// APIServerService returns the user defined API Service
func (l *Logstash) APIServerService() LogstashService {
for _, service := range l.Spec.Services {
if UserServiceName(l.Name, service.Name) == APIServiceName(l.Name) {
return service
}
}
return LogstashService{}
}

// APIServerTLSOptions returns the user defined TLSOptions of API Service
func (l *Logstash) APIServerTLSOptions() commonv1.TLSOptions {
return l.APIServerService().TLS
}

func init() {
SchemeBuilder.Register(&Logstash{}, &LogstashList{})
}
2 changes: 1 addition & 1 deletion pkg/apis/logstash/v1alpha1/name_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,4 @@ func TestLogstashName(t *testing.T) {
}
})
}
}
}
9 changes: 9 additions & 0 deletions pkg/controller/common/pod/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,12 @@ func ContainerByName(podSpec corev1.PodSpec, name string) *corev1.Container {
}
return nil
}

func InitContainerByName(podSpec corev1.PodSpec, name string) *corev1.Container {
for i, c := range podSpec.InitContainers {
if c.Name == name {
return &podSpec.InitContainers[i]
}
}
return nil
}
60 changes: 51 additions & 9 deletions pkg/controller/logstash/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
package logstash

import (
"fmt"
"hash"
"strconv"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -16,18 +18,28 @@ import (
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/reconciler"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/settings"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/tracing"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/logstash/volume"
)

const (
ConfigFileName = "logstash.yml"
ConfigFileName = "logstash.yml"
APIKeystorePath = volume.ConfigMountPath + "/" + APIKeystoreFileName
APIKeystoreFileName = "api_keystore.p12" // #nosec G101
APIKeystoreDefaultPass = "ch@ng3m3" // #nosec G101
APIKeystorePassEnv = "API_KEYSTORE_PASS" // #nosec G101
)

func reconcileConfig(params Params, configHash hash.Hash) error {
func reconcileConfig(params Params, configHash hash.Hash) (*settings.CanonicalConfig, error) {
defer tracing.Span(&params.Context)()

cfgBytes, err := buildConfig(params)
cfg, err := buildConfig(params)
if err != nil {
return err
return nil, err
}

cfgBytes, err := cfg.Render()
if err != nil {
return nil, err
}

expected := corev1.Secret{
Expand All @@ -42,28 +54,33 @@ func reconcileConfig(params Params, configHash hash.Hash) error {
}

if _, err = reconciler.ReconcileSecret(params.Context, params.Client, expected, &params.Logstash); err != nil {
return err
return nil, err
}

_, _ = configHash.Write(cfgBytes)

return nil
return cfg, nil
}

func buildConfig(params Params) ([]byte, error) {
func buildConfig(params Params) (*settings.CanonicalConfig, error) {
userProvidedCfg, err := getUserConfig(params)
if err != nil {
return nil, err
}

cfg := defaultConfig()
tls := tlsConfig(params.UseTLS)

// merge with user settings last so they take precedence
if err := cfg.MergeWith(userProvidedCfg); err != nil {
if err := cfg.MergeWith(tls, userProvidedCfg); err != nil {
return nil, err
}

return cfg.Render()
if err = checkTLSConfig(cfg, params.UseTLS); err != nil {
return nil, err
}

return cfg, nil
}

// getUserConfig extracts the config either from the spec `config` field or from the Secret referenced by spec
Expand All @@ -85,3 +102,28 @@ func defaultConfig() *settings.CanonicalConfig {

return settings.MustCanonicalConfig(settingsMap)
}

func tlsConfig(useTLS bool) *settings.CanonicalConfig {
if !useTLS {
return nil
}
return settings.MustCanonicalConfig(map[string]interface{}{
"api.ssl.enabled": true,
"api.ssl.keystore.path": APIKeystorePath,
"api.ssl.keystore.password": APIKeystoreDefaultPass,
})
}

// checkTLSConfig ensures logstash config `api.ssl.enabled` matches the TLS setting of API service, otherwise throws error
func checkTLSConfig(cfg *settings.CanonicalConfig, useTLS bool) error {
sslEnabled, err := cfg.String("api.ssl.enabled")
if err != nil {
sslEnabled = "false"
}

if strconv.FormatBool(useTLS) != sslEnabled {
return fmt.Errorf("API Service `spec.services.tls.selfSignedCertificate.disabled` is set to `%t`, but logstash config `api.ssl.enabled` is set to `%s`", !useTLS, sslEnabled)
}

return nil
}
63 changes: 60 additions & 3 deletions pkg/controller/logstash/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func Test_newConfig(t *testing.T) {
tests := []struct {
name string
args args
useTLS bool
want string
wantErr bool
}{
Expand All @@ -43,6 +44,27 @@ func Test_newConfig(t *testing.T) {
config:
reload:
automatic: true
`,
wantErr: false,
},
{
name: "no user config with TLS",
args: args{
runtimeObjs: nil,
logstash: v1alpha1.Logstash{},
},
useTLS: true,
want: `api:
http:
host: 0.0.0.0
ssl:
enabled: true
keystore:
password: ch@ng3m3
path: /usr/share/logstash/config/api_keystore.p12
config:
reload:
automatic: true
`,
wantErr: false,
},
Expand All @@ -52,13 +74,20 @@ config:
runtimeObjs: nil,
logstash: v1alpha1.Logstash{
Spec: v1alpha1.LogstashSpec{Config: &commonv1.Config{Data: map[string]interface{}{
"log.level": "debug",
"log.level": "debug",
"api.ssl.keystore.password": "Str0ngP@ssw0rd",
}}},
},
},
useTLS: true,
want: `api:
http:
host: 0.0.0.0
ssl:
enabled: true
keystore:
password: Str0ngP@ssw0rd
path: /usr/share/logstash/config/api_keystore.p12
config:
reload:
automatic: true
Expand Down Expand Up @@ -110,6 +139,29 @@ log:
},
wantErr: true,
},
{
name: "logstash config disables TLS and service enables TLS",
args: args{
runtimeObjs: nil,
logstash: v1alpha1.Logstash{
Spec: v1alpha1.LogstashSpec{
Config: &commonv1.Config{Data: map[string]interface{}{
"api.ssl.enabled": "false",
}},
Services: []v1alpha1.LogstashService{{
Name: LogstashAPIServiceName,
TLS: commonv1.TLSOptions{
SelfSignedCertificate: &commonv1.SelfSignedCertificate{
Disabled: false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test passes regardless of whether the value here has the TLS block, or whether selfSignedCertificate.Disabled value is true or false, as only the useTLS flag is relevant here.

I think we are missing a layer of testing, of how the useTLS setting is derived from the TLSOptions

},
},
}},
},
},
},
useTLS: true,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand All @@ -119,19 +171,24 @@ log:
EventRecorder: record.NewFakeRecorder(10),
Watches: watches.NewDynamicWatches(),
Logstash: tt.args.logstash,
UseTLS: tt.useTLS,
}

got, err := buildConfig(params)
if (err != nil) != tt.wantErr {
t.Errorf("newConfig() error = %v, wantErr %v", err, tt.wantErr)
return
}

if tt.wantErr {
return // no point in checking the config contents
}
require.NoError(t, err)
if string(got) != tt.want {
t.Errorf("newConfig() got = \n%v\n, want \n%v\n", string(got), tt.want)

gotBytes, _ := got.Render()
gotStr := string(gotBytes)
if gotStr != tt.want {
t.Errorf("newConfig() got = \n%v\n, want \n%v\n", gotStr, tt.want)
}
})
}
Expand Down
38 changes: 34 additions & 4 deletions pkg/controller/logstash/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ import (
"k8s.io/client-go/tools/record"

logstashv1alpha1 "github.com/elastic/cloud-on-k8s/v2/pkg/apis/logstash/v1alpha1"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/certificates"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/events"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/keystore"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/operator"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/reconciler"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/settings"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/tracing"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/common/watches"
"github.com/elastic/cloud-on-k8s/v2/pkg/controller/logstash/stackmon"
Expand All @@ -37,6 +40,8 @@ type Params struct {

OperatorParams operator.Parameters
KeystoreResources *keystore.Resources
UseTLS bool // Logstash API Server uses TLS
LogstashConfig *settings.CanonicalConfig // logstash.yml config
}

// K8sClient returns the Kubernetes client.
Expand Down Expand Up @@ -74,19 +79,44 @@ func internalReconcile(params Params) (*reconciler.Results, logstashv1alpha1.Log
defer tracing.Span(&params.Context)()
results := reconciler.NewResult(params.Context)

_, err := reconcileServices(params)
_, apiSvc, err := reconcileServices(params)
if err != nil {
return results.WithError(err), params.Status
}

apiSvcTLS := params.Logstash.APIServerTLSOptions()

_, results = certificates.Reconciler{
K8sClient: params.Client,
DynamicWatches: params.Watches,
Owner: &params.Logstash,
TLSOptions: apiSvcTLS,
Namer: logstashv1alpha1.Namer,
Labels: NewLabels(params.Logstash),
Services: []corev1.Service{apiSvc},
GlobalCA: params.OperatorParams.GlobalCA,
CACertRotation: params.OperatorParams.CACertRotation,
CertRotation: params.OperatorParams.CertRotation,
GarbageCollectSecrets: true,
}.ReconcileCAAndHTTPCerts(params.Context)
if results.HasError() {
_, err := results.Aggregate()
k8s.MaybeEmitErrorEvent(params.Recorder(), err, &params.Logstash, events.EventReconciliationError, "Certificate reconciliation error: %v", err)
return results, params.Status
}

params.UseTLS = apiSvcTLS.Enabled()

configHash := fnv.New32a()

// reconcile beats config secrets if Stack Monitoring is defined
if err := stackmon.ReconcileConfigSecrets(params.Context, params.Client, params.Logstash); err != nil {
cfg, err := reconcileConfig(params, configHash)
if err != nil {
return results.WithError(err), params.Status
}
params.LogstashConfig = cfg

if err := reconcileConfig(params, configHash); err != nil {
// reconcile beats config secrets if Stack Monitoring is defined
if err := stackmon.ReconcileConfigSecrets(params.Context, params.Client, params.Logstash, params.UseTLS, params.LogstashConfig); err != nil {
return results.WithError(err), params.Status
}

Expand Down
Loading