Skip to content

Commit e82b89a

Browse files
authored
Merge pull request #761 from nadvornik/proxy_secrets
Use podman secrets for SSL on proxy
2 parents ff2edad + 6f1f8aa commit e82b89a

9 files changed

Lines changed: 226 additions & 5 deletions

File tree

mgrpxy/cmd/install/utils.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ func installForPodman(
3535
return shared_utils.Errorf(err, L("failed to retrieve proxy config files"))
3636
}
3737

38+
if err := podman.ExtractSecrets(); err != nil {
39+
return err
40+
}
41+
3842
hostData, err := shared_podman.InspectHost()
3943
if err != nil {
4044
return err

mgrpxy/cmd/uninstall/podman.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-FileCopyrightText: 2025 SUSE LLC
1+
// SPDX-FileCopyrightText: 2026 SUSE LLC
22
//
33
// SPDX-License-Identifier: Apache-2.0
44

@@ -90,9 +90,10 @@ func uninstallForPodman(
9090

9191
podman.DeleteNetwork(dryRun)
9292

93-
if podman.HasSecret(pxy_podman.SystemIDSecret) {
94-
podman.DeleteSecret(pxy_podman.SystemIDSecret, false)
95-
}
93+
podman.DeleteSecret(pxy_podman.SystemIDSecret, dryRun)
94+
podman.DeleteSecret(podman.CASecret, dryRun)
95+
podman.DeleteSecret(podman.ProxySSLCertSecret, dryRun)
96+
podman.DeleteSecret(podman.ProxySSLKeySecret, dryRun)
9697

9798
err := systemd.ReloadDaemon(dryRun)
9899

mgrpxy/shared/podman/podman.go

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/uyuni-project/uyuni-tools/shared/podman"
2626
"github.com/uyuni-project/uyuni-tools/shared/types"
2727
shared_utils "github.com/uyuni-project/uyuni-tools/shared/utils"
28+
"gopkg.in/yaml.v2"
2829
)
2930

3031
const (
@@ -44,6 +45,7 @@ const (
4445
var contextRunner = shared_utils.NewRunnerWithContext
4546
var newRunner = shared_utils.NewRunner
4647
var systemdGenerator func(shared_utils.Template, string, string, string) error = generateSystemdFile
48+
var createSecret = podman.CreateSecret
4749

4850
// PodmanProxyFlags are the flags used by podman proxy install and upgrade command.
4951
type PodmanProxyFlags struct {
@@ -86,6 +88,15 @@ func GenerateSystemdService(
8688
if podman.HasSecret(SystemIDSecret) {
8789
dataHttpd.SystemIDSecret = SystemIDSecret
8890
}
91+
if podman.HasSecret(podman.CASecret) {
92+
dataHttpd.CaSecret = podman.CASecret
93+
}
94+
if podman.HasSecret(podman.ProxySSLCertSecret) {
95+
dataHttpd.CertSecret = podman.ProxySSLCertSecret
96+
}
97+
if podman.HasSecret(podman.ProxySSLKeySecret) {
98+
dataHttpd.KeySecret = podman.ProxySSLKeySecret
99+
}
89100

90101
additionHttpdTuningSettings := ""
91102
additionHTTPConfPath, err := getPathOrDefault(flags.Tuning.Httpd, defaultApacheConf)
@@ -159,6 +170,9 @@ func GenerateSystemdService(
159170
Volumes: shared_utils.ProxyTftpdVolumes,
160171
HTTPProxyFile: httpProxyConfig,
161172
}
173+
if podman.HasSecret(podman.CASecret) {
174+
dataTftpd.CaSecret = podman.CASecret
175+
}
162176
if err := systemdGenerator(dataTftpd, "tftpd", tftpdImage, ""); err != nil {
163177
return err
164178
}
@@ -308,6 +322,10 @@ func Upgrade(
308322
return err
309323
}
310324

325+
if err := ExtractSecrets(); err != nil {
326+
return err
327+
}
328+
311329
hostData, err := podman.InspectHost()
312330
if err != nil {
313331
return err
@@ -450,5 +468,114 @@ func GetSystemID() error {
450468
}
451469
log.Trace().Msgf("SystemID: %s", systemid)
452470

453-
return podman.CreateSecret(SystemIDSecret, systemid)
471+
return createSecret(SystemIDSecret, systemid)
472+
}
473+
474+
var proxyConfigDir = "/etc/uyuni/proxy"
475+
476+
// ExtractSecrets extracts certificates from YAML files and creates podman secrets.
477+
func ExtractSecrets() error {
478+
if err := extractCASecret(path.Join(proxyConfigDir, "config.yaml")); err != nil {
479+
return err
480+
}
481+
return extractHttpdSecrets(path.Join(proxyConfigDir, "httpd.yaml"))
482+
}
483+
484+
func extractCASecret(configPath string) error {
485+
config, err := loadYaml(configPath)
486+
if err != nil || config == nil {
487+
return err
488+
}
489+
490+
updated, err := extractKeyToSecret(config, "ca_crt", podman.CASecret)
491+
if err != nil {
492+
return err
493+
}
494+
495+
if updated {
496+
return saveYaml(configPath, config)
497+
}
498+
return nil
499+
}
500+
501+
func extractHttpdSecrets(httpdPath string) error {
502+
config, err := loadYaml(httpdPath)
503+
if err != nil || config == nil {
504+
return err
505+
}
506+
507+
httpd, ok := config["httpd"]
508+
if !ok {
509+
return nil
510+
}
511+
512+
httpdMap := ensureStringMap(httpd)
513+
if httpdMap == nil {
514+
return nil
515+
}
516+
config["httpd"] = httpdMap
517+
518+
updated1, err := extractKeyToSecret(httpdMap, "server_crt", podman.ProxySSLCertSecret)
519+
if err != nil {
520+
return err
521+
}
522+
updated2, err := extractKeyToSecret(httpdMap, "server_key", podman.ProxySSLKeySecret)
523+
if err != nil {
524+
return err
525+
}
526+
527+
if updated1 || updated2 {
528+
return saveYaml(httpdPath, config)
529+
}
530+
return nil
531+
}
532+
533+
func loadYaml(filePath string) (map[string]interface{}, error) {
534+
if !shared_utils.FileExists(filePath) {
535+
return nil, nil
536+
}
537+
data, err := os.ReadFile(filePath)
538+
if err != nil {
539+
return nil, shared_utils.Errorf(err, L("failed to read %s"), filePath)
540+
}
541+
var config map[string]interface{}
542+
if err := yaml.Unmarshal(data, &config); err != nil {
543+
return nil, shared_utils.Errorf(err, L("failed to unmarshal %s"), filePath)
544+
}
545+
return config, nil
546+
}
547+
548+
func saveYaml(filePath string, config map[string]interface{}) error {
549+
newData, err := yaml.Marshal(config)
550+
if err != nil {
551+
return shared_utils.Errorf(err, L("failed to marshal %s"), filePath)
552+
}
553+
if err := os.WriteFile(filePath, newData, 0644); err != nil {
554+
return shared_utils.Errorf(err, L("failed to write %s"), filePath)
555+
}
556+
return nil
557+
}
558+
559+
func extractKeyToSecret(m map[string]interface{}, key string, secretName string) (bool, error) {
560+
if val, ok := m[key].(string); ok && val != "" {
561+
if err := createSecret(secretName, val); err != nil {
562+
return false, shared_utils.Errorf(err, L("failed to create %s secret"), secretName)
563+
}
564+
delete(m, key)
565+
return true, nil
566+
}
567+
return false, nil
568+
}
569+
570+
func ensureStringMap(data interface{}) map[string]interface{} {
571+
if m, ok := data.(map[string]interface{}); ok {
572+
return m
573+
} else if m, ok := data.(map[interface{}]interface{}); ok {
574+
res := make(map[string]interface{})
575+
for k, v := range m {
576+
res[fmt.Sprintf("%v", k)] = v
577+
}
578+
return res
579+
}
580+
return nil
454581
}

mgrpxy/shared/podman/podman_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/uyuni-project/uyuni-tools/shared/testutils"
1818
"github.com/uyuni-project/uyuni-tools/shared/types"
1919
"github.com/uyuni-project/uyuni-tools/shared/utils"
20+
"gopkg.in/yaml.v2"
2021
)
2122

2223
func TestCheckDirPermissions(t *testing.T) {
@@ -217,3 +218,66 @@ func TestGenerateSystemdService(t *testing.T) {
217218
// Restore the mocked variables
218219
systemdGenerator = generateSystemdFile
219220
}
221+
222+
func TestExtractSecrets(t *testing.T) {
223+
tempDir := t.TempDir()
224+
oldProxyConfigDir := proxyConfigDir
225+
proxyConfigDir = tempDir
226+
defer func() { proxyConfigDir = oldProxyConfigDir }()
227+
228+
secrets := make(map[string]string)
229+
oldCreateSecret := createSecret
230+
createSecret = func(name string, value string) error {
231+
secrets[name] = value
232+
return nil
233+
}
234+
defer func() { createSecret = oldCreateSecret }()
235+
236+
configPath := path.Join(tempDir, "config.yaml")
237+
configData := "ca_crt: CA_CERT_CONTENT\nother_key: other_value\n"
238+
if err := os.WriteFile(configPath, []byte(configData), 0644); err != nil {
239+
t.Fatal(err)
240+
}
241+
242+
httpdPath := path.Join(tempDir, "httpd.yaml")
243+
httpdData := "httpd:\n server_crt: SERVER_CERT_CONTENT\n" +
244+
" server_key: SERVER_KEY_CONTENT\n other_httpd_key: other_httpd_value\n"
245+
if err := os.WriteFile(httpdPath, []byte(httpdData), 0644); err != nil {
246+
t.Fatal(err)
247+
}
248+
249+
if err := ExtractSecrets(); err != nil {
250+
t.Errorf("ExtractSecrets failed: %v", err)
251+
}
252+
253+
// Verify secrets
254+
testutils.AssertEquals(t, "Wrong CA secret", "CA_CERT_CONTENT", secrets[podman.CASecret])
255+
testutils.AssertEquals(t, "Wrong server cert secret", "SERVER_CERT_CONTENT", secrets[podman.ProxySSLCertSecret])
256+
testutils.AssertEquals(t, "Wrong server key secret", "SERVER_KEY_CONTENT", secrets[podman.ProxySSLKeySecret])
257+
258+
// Verify files are updated (keys removed)
259+
data, _ := os.ReadFile(configPath)
260+
var config map[string]interface{}
261+
if err := yaml.Unmarshal(data, &config); err != nil {
262+
t.Fatalf("Failed to unmarshal config.yaml: %v", err)
263+
}
264+
if _, ok := config["ca_crt"]; ok {
265+
t.Error("ca_crt was not removed from config.yaml")
266+
}
267+
testutils.AssertEquals(t, "other_key was modified", "other_value", config["other_key"])
268+
269+
data, _ = os.ReadFile(httpdPath)
270+
var httpdConfig map[string]interface{}
271+
if err := yaml.Unmarshal(data, &httpdConfig); err != nil {
272+
t.Fatalf("Failed to unmarshal httpd.yaml: %v", err)
273+
}
274+
275+
httpd := ensureStringMap(httpdConfig["httpd"])
276+
if _, ok := httpd["server_crt"]; ok {
277+
t.Error("server_crt was not removed from httpd.yaml")
278+
}
279+
if _, ok := httpd["server_key"]; ok {
280+
t.Error("server_key was not removed from httpd.yaml")
281+
}
282+
testutils.AssertEquals(t, "other_httpd_key was modified", "other_httpd_value", httpd["other_httpd_key"])
283+
}

mgrpxy/shared/templates/httpd.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ ExecStart=/bin/sh -c '/usr/bin/podman run \
3939
{{- if .SystemIDSecret }}
4040
--secret {{ .SystemIDSecret }},type=mount,mode=0444,target="/etc/sysconfig/rhn/systemid" \
4141
{{- end }}
42+
{{- if .CaSecret }}
43+
--secret {{ .CaSecret }},type=mount,mode=0444,target="/etc/pki/trust/anchors/RHN-ORG-TRUSTED-SSL-CERT" \
44+
{{- end }}
45+
{{- if .CertSecret }}
46+
--secret {{ .CertSecret }},type=mount,mode=0444,target="/etc/apache2/ssl.crt/server.crt" \
47+
{{- end }}
48+
{{- if .KeySecret }}
49+
--secret {{ .KeySecret }},type=mount,mode=0400,target="/etc/apache2/ssl.key/server.key" \
50+
{{- end }}
4251
{{- if .HTTPProxyFile }}
4352
-v {{ .HTTPProxyFile }}:{{ .HTTPProxyFile }}:ro \
4453
{{- end }}
@@ -60,6 +69,9 @@ type HttpdTemplateData struct {
6069
Volumes []types.VolumeMount
6170
HTTPProxyFile string
6271
SystemIDSecret string
72+
CaSecret string
73+
CertSecret string
74+
KeySecret string
6375
}
6476

6577
// Render will create the systemd configuration file.

mgrpxy/shared/templates/templates_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ func TestTemplatesRender(t *testing.T) {
2323
Volumes: []types.VolumeMount{{Name: "vol1", MountPath: "/mnt/vol1"}},
2424
HTTPProxyFile: "/etc/sysconfig/proxy",
2525
SystemIDSecret: "system-id-secret",
26+
CaSecret: "ca-secret",
27+
CertSecret: "cert-secret",
28+
KeySecret: "key-secret",
2629
},
2730
},
2831
{
@@ -58,6 +61,7 @@ func TestTemplatesRender(t *testing.T) {
5861
template: TFTPDTemplateData{
5962
Volumes: []types.VolumeMount{{Name: "tftp-vol", MountPath: "/srv/tftpboot"}},
6063
HTTPProxyFile: "/etc/sysconfig/proxy",
64+
CaSecret: "ca-secret",
6165
},
6266
},
6367
}

mgrpxy/shared/templates/tftpd.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ ExecStart=/bin/sh -c '/usr/bin/podman run \
3636
{{- range .Volumes }}
3737
-v {{ .Name }}:{{ .MountPath }} \
3838
{{- end }}
39+
{{- if .CaSecret }}
40+
--secret {{ .CaSecret }},type=mount,mode=0444,target="/etc/pki/trust/anchors/RHN-ORG-TRUSTED-SSL-CERT" \
41+
{{- end }}
3942
{{- if .HTTPProxyFile }}
4043
-v {{ .HTTPProxyFile }}:{{ .HTTPProxyFile }}:ro \
4144
{{- end }}
@@ -56,6 +59,7 @@ WantedBy=multi-user.target default.target
5659
type TFTPDTemplateData struct {
5760
Volumes []types.VolumeMount
5861
HTTPProxyFile string
62+
CaSecret string
5963
}
6064

6165
// Render will create the TFTPD systemd configuration file.

shared/podman/secret.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ const (
4040
DBSSLCertSecret = "uyuni-db-cert"
4141
// DBSSLKeySecret is the name of the podman secret containing the report database SSL certificate key.
4242
DBSSLKeySecret = "uyuni-db-key"
43+
// ProxySSLCertSecret is the name of the podman secret containing the proxy SSL certificate.
44+
ProxySSLCertSecret = "uyuni-proxy-cert"
45+
// ProxySSLKeySecret is the name of the podman secret containing the proxy SSL certificate key.
46+
ProxySSLKeySecret = "uyuni-proxy-key"
4347
)
4448

4549
// CreateCredentialsSecretsIfMissing creates the podman secrets, one for the user name and one for the password.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Use podman secrets for SSL on proxy

0 commit comments

Comments
 (0)