diff --git a/.tito/packages/uyuni-tools b/.tito/packages/uyuni-tools index a3ec3f8d4..ba7fb84a0 100644 --- a/.tito/packages/uyuni-tools +++ b/.tito/packages/uyuni-tools @@ -1 +1 @@ -5.1.1-0 ./ +5.1.2-0 ./ diff --git a/mgradm/cmd/install/podman/podman.go b/mgradm/cmd/install/podman/podman.go index c62ddc877..d7adda236 100644 --- a/mgradm/cmd/install/podman/podman.go +++ b/mgradm/cmd/install/podman/podman.go @@ -31,6 +31,8 @@ NOTE: installing on a remote podman is not supported yet! Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { var flags podmanInstallFlags + flags.Coco.IsChanged = cmd.Flags().Changed("coco-replicas") + flags.HubXmlrpc.IsChanged = cmd.Flags().Changed("hubxmlrpc-replicas") return utils.CommandHelper(globalFlags, cmd, args, &flags, installForPodman) }, } diff --git a/mgradm/cmd/install/podman/utils.go b/mgradm/cmd/install/podman/utils.go index b78541419..3fbdbc59f 100644 --- a/mgradm/cmd/install/podman/utils.go +++ b/mgradm/cmd/install/podman/utils.go @@ -128,9 +128,13 @@ func installForPodman( } if flags.Coco.Replicas > 0 { + // This may need to be moved up later once more containers require DB access + if err := shared_podman.CreateDbSecrets(flags.Db.User, flags.Db.Password); err != nil { + return err + } if err := coco.SetupCocoContainer( authFile, flags.Image.Registry, flags.Coco, flags.Image, - flags.Db.Name, flags.Db.Port, flags.Db.User, flags.Db.Password, + flags.Db.Name, flags.Db.Port, ); err != nil { return err } diff --git a/mgradm/cmd/uninstall/podman.go b/mgradm/cmd/uninstall/podman.go index 2122e8711..8390b15a7 100644 --- a/mgradm/cmd/uninstall/podman.go +++ b/mgradm/cmd/uninstall/podman.go @@ -69,6 +69,9 @@ func uninstallForPodman( podman.DeleteNetwork(!flags.Force) + podman.DeleteSecret(podman.DbUserSecret, !flags.Force) + podman.DeleteSecret(podman.DbPassSecret, !flags.Force) + err := podman.ReloadDaemon(!flags.Force) if !flags.Force { diff --git a/mgradm/shared/coco/coco.go b/mgradm/shared/coco/coco.go index 95bbd847d..2c9b426d2 100644 --- a/mgradm/shared/coco/coco.go +++ b/mgradm/shared/coco/coco.go @@ -28,9 +28,11 @@ func Upgrade( dbUser string, dbPassword string, ) error { - if err := writeCocoServiceFiles( - authFile, registry, cocoFlags, baseImage, dbName, dbPort, dbUser, dbPassword, - ); err != nil { + if err := podman.CreateDbSecrets(dbUser, dbPassword); err != nil { + return err + } + + if err := writeCocoServiceFiles(authFile, registry, cocoFlags, baseImage, dbName, dbPort); err != nil { return err } @@ -48,8 +50,6 @@ func writeCocoServiceFiles( baseImage types.ImageFlags, dbName string, dbPort int, - dbUser string, - dbPassword string, ) error { image := cocoFlags.Image currentReplicas := systemd.CurrentReplicaCount(podman.ServerAttestationService) @@ -95,8 +95,7 @@ func writeCocoServiceFiles( environment := fmt.Sprintf(`Environment=UYUNI_IMAGE=%s Environment=database_connection=jdbc:postgresql://uyuni-server.mgr.internal:%d/%s -Environment=database_user=%s -Environment=database_password=%s`, preparedImage, dbPort, dbName, dbUser, dbPassword) +`, preparedImage, dbPort, dbName) if err := podman.GenerateSystemdConfFile( podman.ServerAttestationService+"@", "generated.conf", environment, true, @@ -119,11 +118,9 @@ func SetupCocoContainer( baseImage types.ImageFlags, dbName string, dbPort int, - dbUser string, - dbPassword string, ) error { if err := writeCocoServiceFiles( - authFile, registry, coco, baseImage, dbName, dbPort, dbUser, dbPassword, + authFile, registry, coco, baseImage, dbName, dbPort, ); err != nil { return err } diff --git a/mgradm/shared/podman/podman.go b/mgradm/shared/podman/podman.go index 6478858dc..4f910064f 100644 --- a/mgradm/shared/podman/podman.go +++ b/mgradm/shared/podman/podman.go @@ -5,7 +5,6 @@ package podman import ( - "errors" "fmt" "os/exec" "path" @@ -156,19 +155,19 @@ func UpdateSSLCertificate(cnx *shared.Connection, chain *types.CaChain, serverPa } // Check and install then using mgr-ssl-cert-setup - if _, err := utils.RunCmdOutput(zerolog.InfoLevel, "podman", args...); err != nil { - return errors.New(L("failed to update SSL certificate")) + if out, err := utils.RunCmdOutput(zerolog.DebugLevel, "podman", args...); err != nil { + return utils.Errorf(err, L("failed to update SSL certificate: %s"), out) } // Clean the copied files and the now useless ssl-build if err := utils.RunCmd("podman", "exec", podman.ServerContainerName, "rm", "-rf", certDir); err != nil { - return errors.New(L("failed to remove copied certificate files in the container")) + return utils.Errorf(err, L("failed to remove copied certificate files in the container")) } const sslbuildPath = "/root/ssl-build" if cnx.TestExistenceInPod(sslbuildPath) { if err := utils.RunCmd("podman", "exec", podman.ServerContainerName, "rm", "-rf", sslbuildPath); err != nil { - return errors.New(L("failed to remove now useless ssl-build folder in the container")) + return utils.Errorf(err, L("failed to remove now useless ssl-build folder in the container")) } } diff --git a/mgradm/shared/templates/attestationServiceTemplate.go b/mgradm/shared/templates/attestationServiceTemplate.go index bfc568f3e..c5aeb4fb9 100644 --- a/mgradm/shared/templates/attestationServiceTemplate.go +++ b/mgradm/shared/templates/attestationServiceTemplate.go @@ -7,6 +7,8 @@ package templates import ( "io" "text/template" + + "github.com/uyuni-project/uyuni-tools/shared/podman" ) const attestationServiceTemplate = ` @@ -28,8 +30,8 @@ ExecStart=/bin/sh -c '/usr/bin/podman run \ --sdnotify=conmon \ -d \ -e database_connection \ - -e database_user \ - -e database_password \ + --secret=` + podman.DbUserSecret + `,type=env,target=database_user \ + --secret=` + podman.DbPassSecret + `,type=env,target=database_password \ --replace \ --name {{ .NamePrefix }}-server-attestation-%i \ --hostname {{ .NamePrefix }}-server-attestation-%i.mgr.internal \ diff --git a/shared/api/api.go b/shared/api/api.go index d2c87598b..053a72af6 100644 --- a/shared/api/api.go +++ b/shared/api/api.go @@ -14,6 +14,7 @@ import ( "io" "net/http" "os" + "regexp" "time" "github.com/rs/zerolog" @@ -32,6 +33,12 @@ func AddAPIFlags(cmd *cobra.Command) { cmd.PersistentFlags().Bool("api-insecure", false, L("If set, server certificate will not be checked for validity")) } +var redactRegex = regexp.MustCompile(`(((pxt-session-cookie)|(JSESSIONID))=)[^ ";]+`) + +func redactHeaders(header string) string { + return redactRegex.ReplaceAllString(header, "${1}") +} + func logTraceHeader(v *http.Header) { // Return early when not in trace loglevel if log.Logger.GetLevel() != zerolog.TraceLevel { @@ -41,7 +48,7 @@ func logTraceHeader(v *http.Header) { if err != nil { return } - log.Trace().Msg(string(b)) + log.Trace().Msg(redactHeaders(string(b))) } func (c *APIClient) sendRequest(req *http.Request) (*http.Response, error) { diff --git a/shared/api/api_test.go b/shared/api/api_test.go new file mode 100644 index 000000000..227152cff --- /dev/null +++ b/shared/api/api_test.go @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2024 SUSE LLC +// +// SPDX-License-Identifier: Apache-2.0 + +package api + +import "testing" + +func TestRedactHeaders(t *testing.T) { + data := [][]string{ + { + `"JSESSIONID=supersecret; Path=/; Secure; HttpOnly; HttpOnly;HttpOnly;Secure"`, + `"JSESSIONID=; Path=/; Secure; HttpOnly; HttpOnly;HttpOnly;Secure"`, + }, + { + `"pxt-session-cookie=supersecret; Max-Age=0;"`, + `"pxt-session-cookie=; Max-Age=0;"`, + }, + } + + for i, testCase := range data { + input := testCase[0] + expected := testCase[1] + + actual := redactHeaders(input) + + if actual != expected { + t.Errorf("Testcase %d: Expected %s got %s when redacting %s", i, expected, actual, input) + } + } +} diff --git a/shared/podman/secret.go b/shared/podman/secret.go new file mode 100644 index 000000000..64e4d307c --- /dev/null +++ b/shared/podman/secret.go @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2024 SUSE LLC +// +// SPDX-License-Identifier: Apache-2.0 + +package podman + +import ( + "os" + "path" + "strings" + + "github.com/rs/zerolog/log" + . "github.com/uyuni-project/uyuni-tools/shared/l10n" + "github.com/uyuni-project/uyuni-tools/shared/utils" +) + +const ( + //DbUserSecret is the name of the podman secret containing the database username. + DbUserSecret = "uyuni-db-user" + //DbUserSecret is the name of the podman secret containing the database password. + DbPassSecret = "uyuni-db-pass" +) + +// CreateDbSecrets creates the podman secrets for the database credentials. +func CreateDbSecrets(user string, password string) error { + if err := createSecret(DbUserSecret, user); err != nil { + return err + } + return createSecret(DbPassSecret, password) +} + +// createSecret creates a podman secret. +func createSecret(name string, value string) error { + if hasSecret(name) { + return nil + } + + tmpDir, err := utils.TempDir() + if err != nil { + return err + } + defer os.RemoveAll(tmpDir) + + secretFile := path.Join(tmpDir, "secret") + if err := os.WriteFile(secretFile, []byte(value), 600); err != nil { + return utils.Errorf(err, L("failed to write %s secret to file"), name) + } + + if err := utils.RunCmd("podman", "secret", "create", name, secretFile); err != nil { + return utils.Errorf(err, L("failed to create podman secret %s"), name) + } + + return nil +} + +func hasSecret(name string) bool { + return utils.RunCmd("podman", "secret", "exists", name) == nil +} + +// DeleteSecret removes a podman secret. +func DeleteSecret(name string, dryRun bool) { + if !hasSecret(name) { + return + } + + args := []string{"secret", "rm", name} + command := "podman " + strings.Join(args, " ") + if dryRun { + log.Info().Msgf(L("Would run %s"), command) + } else { + log.Info().Msgf(L("Run %s"), command) + if err := utils.RunCmd("podman", args...); err != nil { + log.Error().Err(err).Msgf(L("Failed to delete %s secret"), name) + } + } +} diff --git a/uyuni-tools.changes b/uyuni-tools.changes index f51ec858a..f82038f61 100644 --- a/uyuni-tools.changes +++ b/uyuni-tools.changes @@ -1,3 +1,12 @@ +------------------------------------------------------------------- +Mon Nov 18 18:50:59 CET 2024 - marina.latini@suse.com + +- version 5.1.2-0 + * CVE-2024-22037: Use podman secret to store the database + credentials (bsc#1231497) + * Redact JSESSIONID and pxt-session-cookie values from logs and + console output (bsc#1231568) + ------------------------------------------------------------------- Mon Oct 14 15:32:26 CEST 2024 - rosuna@suse.com diff --git a/uyuni-tools.spec b/uyuni-tools.spec index 2ee3a3fbe..0a07f5d67 100644 --- a/uyuni-tools.spec +++ b/uyuni-tools.spec @@ -44,7 +44,7 @@ # 0%{?debian} || 0%{?ubuntu} Name: %{project} -Version: 5.1.1 +Version: 5.1.2 Release: 0 Summary: Tools for managing %{productname} container License: Apache-2.0