diff --git a/modules/common/util/errors.go b/modules/common/util/errors.go index 33f9b2d2..50a681a8 100644 --- a/modules/common/util/errors.go +++ b/modules/common/util/errors.go @@ -29,4 +29,6 @@ var ( ErrTemplateSubdirUnset = errors.New("template subdir not set") // ErrInstanceTypeUnsetWithMultiTemplateDir indicates InstanceType is empty while MultiTemplateDir is set ErrInstanceTypeUnsetWithMultiTemplateDir = errors.New("instance type not set") + // ErrCommonTemplateNotFound indicates a requested common template does not exist + ErrCommonTemplateNotFound = errors.New("common template not found") ) diff --git a/modules/common/util/template_util.go b/modules/common/util/template_util.go index 5ee4ec25..62369d9c 100644 --- a/modules/common/util/template_util.go +++ b/modules/common/util/template_util.go @@ -19,7 +19,9 @@ package util import ( "bufio" "bytes" + "embed" "fmt" + "io/fs" "os" "path" "path/filepath" @@ -29,6 +31,9 @@ import ( corev1 "k8s.io/api/core/v1" ) +//go:embed templates/common/config/* +var commonTemplates embed.FS + // TType - TemplateType type TType string @@ -60,6 +65,7 @@ type Template struct { SkipSetOwner bool // skip setting ownership on the associated configmap Version string // optional version string to separate templates inside the InstanceType/Type directory. E.g. placementapi/config/18.0 MultiTemplateDir string // templates dir for multi-group operators, e.g. nova/api; requires InstanceType to be set + CommonTemplates []string // list of common embedded templates to include (e.g. []string{"ssl.conf"}). } // GetTemplatesPath get path to templates, either running local or deployed as container @@ -293,6 +299,24 @@ func GetTemplateData(t Template) (map[string]string, error) { data := make(map[string]string) + // Render requested common embedded templates as fallback defaults. + // Note that local operator templates rendered below overwrite the common + // ones: if they share the same filename, service operators definition take + // precedence + if len(t.CommonTemplates) > 0 { + commonData, err := GetCommonTemplates(opts) + if err != nil { + return nil, err + } + for _, name := range t.CommonTemplates { + v, ok := commonData[name] + if !ok { + return nil, fmt.Errorf("%w: %s", ErrCommonTemplateNotFound, name) + } + data[name] = v + } + } + if t.Type != TemplateTypeNone { // If MultiTemplateDir is set but InstanceType is not, return an error // though we do not use InstanceType here, but it is used in secret.go @@ -344,3 +368,34 @@ func GetTemplateData(t Template) (map[string]string, error) { return data, nil } + +// GetCommonTemplates renders the common config templates (ssl.conf, etc.) +// shipped with lib-common using the provided configOptions, and returns +// map[filename]renderedContent. +// Callers merge the result into their Template.CustomData before calling +// EnsureSecrets/EnsureConfigMaps so that common templates are included in the +// generated Secret/ConfigMaps without each operator duplicating the files. +func GetCommonTemplates(configOptions map[string]any) (map[string]string, error) { + dir := "templates/common/config" + entries, err := fs.ReadDir(commonTemplates, dir) + if err != nil { + return nil, err + } + + result := make(map[string]string, len(entries)) + for _, e := range entries { + if e.IsDir() { + continue + } + data, err := fs.ReadFile(commonTemplates, path.Join(dir, e.Name())) + if err != nil { + return nil, err + } + rendered, err := ExecuteTemplateData(string(data), configOptions) + if err != nil { + return nil, err + } + result[e.Name()] = rendered + } + return result, nil +} diff --git a/modules/common/util/template_util_test.go b/modules/common/util/template_util_test.go index 75d3c505..f5baecde 100644 --- a/modules/common/util/template_util_test.go +++ b/modules/common/util/template_util_test.go @@ -575,6 +575,66 @@ function common_func { want: map[string]string{}, error: true, }, + { + name: "CommonTemplates are injected", + tmpl: Template{ + Name: "testservice", + Namespace: "somenamespace", + Type: TemplateTypeConfig, + InstanceType: "testservice", + CommonTemplates: []string{"ssl.conf"}, + ConfigOptions: map[string]any{ + "ServiceUser": "foo", + "Count": 1, + "Upper": "BAR", + }, + }, + want: map[string]string{ + "bar.conf": "[DEFAULT]\nstate_path = /var/lib/nova\ndebug=true\nsome_parameter_with_brackets=[test]\ncompute_driver = libvirt.LibvirtDriver\n\n[oslo_concurrency]\nlock_path = /var/lib/nova/tmp\n", + "config.json": "{\n \"command\": \"/usr/sbin/httpd -DFOREGROUND\",\n}\n", + "foo.conf": "username = foo\ncount = 1\nadd = 3\nlower = bar\n", + "ssl.conf": "", // placeholder, replaced below + }, + error: false, + }, + { + name: "Unknown CommonTemplate returns error", + tmpl: Template{ + Name: "testservice", + Namespace: "somenamespace", + Type: TemplateTypeConfig, + InstanceType: "testservice", + CommonTemplates: []string{"sls.conf"}, + }, + want: nil, + error: true, + }, + { + name: "Local service templates take precedence", + tmpl: Template{ + Name: "override", + Namespace: "somenamespace", + Type: TemplateTypeConfig, + InstanceType: "override", + CommonTemplates: []string{"ssl.conf"}, + ConfigOptions: map[string]any{}, + }, + want: map[string]string{ + "ssl.conf": "# operator-specific ssl.conf override\nSSLProtocol -all +TLSv1.3\n", + }, + error: false, + }, + } + + // Fill in the expected ssl.conf content for the CommonTemplates test + commonTmpls, err := GetCommonTemplates(map[string]any{}) + if err != nil { + panic("Failed to get common templates: " + err.Error()) + } + for i, tt := range tests { + if v, ok := tt.want["ssl.conf"]; ok && v == "" { + tests[i].want["ssl.conf"] = commonTmpls["ssl.conf"] + } } for _, tt := range tests { @@ -618,3 +678,16 @@ baz=1 g.Expect(cleaned2).To(Equal(cleaned)) } + +func TestGetCommonTemplates(t *testing.T) { + t.Run("Returns ssl.conf", func(t *testing.T) { + g := NewWithT(t) + + result, err := GetCommonTemplates(map[string]interface{}{}) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(result).To(HaveKey("ssl.conf")) + g.Expect(result["ssl.conf"]).To(ContainSubstring("mod_ssl")) + g.Expect(result["ssl.conf"]).To(ContainSubstring("SSLProtocol")) + g.Expect(result["ssl.conf"]).To(ContainSubstring("SSLCipherSuite")) + }) +} diff --git a/modules/common/util/templates/common/config/ssl.conf b/modules/common/util/templates/common/config/ssl.conf new file mode 100644 index 00000000..e3da4ecb --- /dev/null +++ b/modules/common/util/templates/common/config/ssl.conf @@ -0,0 +1,21 @@ + + SSLRandomSeed startup builtin + SSLRandomSeed startup file:/dev/urandom 512 + SSLRandomSeed connect builtin + SSLRandomSeed connect file:/dev/urandom 512 + + AddType application/x-x509-ca-cert .crt + AddType application/x-pkcs7-crl .crl + + SSLPassPhraseDialog builtin + SSLSessionCache "shmcb:/var/cache/mod_ssl/scache(512000)" + SSLSessionCacheTimeout 300 + Mutex default + SSLCryptoDevice builtin + SSLHonorCipherOrder On + SSLUseStapling Off + SSLStaplingCache "shmcb:/run/httpd/ssl_stapling(32768)" + SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5:!RC4:!3DES + SSLProtocol all -SSLv2 -SSLv3 -TLSv1 + SSLOptions StdEnvVars + diff --git a/modules/common/util/testdata/templates/override/config/ssl.conf b/modules/common/util/testdata/templates/override/config/ssl.conf new file mode 100644 index 00000000..5f9765d1 --- /dev/null +++ b/modules/common/util/testdata/templates/override/config/ssl.conf @@ -0,0 +1,2 @@ +# operator-specific ssl.conf override +SSLProtocol -all +TLSv1.3