Skip to content

Commit

Permalink
podman: generate the SSL certificates before the setup
Browse files Browse the repository at this point in the history
In order to generate certificates for more than one container, run the
rhn-ssl-tool in a container before running the setup. The setup then
takes care of their deployment.

Deploying existing certificates is then basically just mounting them
where expected by mgr-setup in the setup container.
  • Loading branch information
cbosdo committed Feb 13, 2025
1 parent 37b1b2f commit c69ebfa
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 26 deletions.
134 changes: 134 additions & 0 deletions mgradm/cmd/install/podman/ssl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0

package podman

import (
"fmt"
"strings"

"github.com/rs/zerolog/log"
adm_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
shared_podman "github.com/uyuni-project/uyuni-tools/shared/podman"
"github.com/uyuni-project/uyuni-tools/shared/ssl"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)

var noopCleaner = func() {
// Nothing to clean
}

// generateSSLCertificates creates the self-signed certificates if needed.
// It returns the podman arguments to mount them in the setup container, a cleaner function and possibly an error.
func generateSSLCertificates(image string, flags *adm_utils.ServerFlags, fqdn string) ([]string, func(), error) {
if flags.Installation.SSL.UseExisting() {
// OrderCas checks the chain of certificates to report problems early
if _, _, err := ssl.OrderCas(&flags.Installation.SSL.Ca, &flags.Installation.SSL.Server); err != nil {
return []string{}, noopCleaner, err
}

// Check that the private key is not encrypted
if err := ssl.CheckKey(flags.Installation.SSL.Server.Key); err != nil {
return []string{}, noopCleaner, err
}

// Add mount options for the existing files into the ssl directory
// The mgr-setup script expects the certificates in the /ssl folder with fixed names.
opts := []string{
"-v", flags.Installation.SSL.Ca.Root + ":/ssl/ca.crt",
"-v", flags.Installation.SSL.Server.Cert + ":/ssl/server.crt",
"-v", flags.Installation.SSL.Server.Key + ":/ssl/server.key",
}
for i, intermediate := range flags.Installation.SSL.Ca.Intermediate {
opts = append(opts, "-v", fmt.Sprintf("%s:/ssl/intermediate-%d.crt", intermediate, i))
}

return opts, noopCleaner, nil
}

tempDir, cleaner, err := utils.TempDir()
if err != nil {
return []string{}, cleaner, err
}

env := map[string]string{
"CERT_O": flags.Installation.SSL.Org,
"CERT_OU": flags.Installation.SSL.OU,
"CERT_CITY": flags.Installation.SSL.City,
"CERT_STATE": flags.Installation.SSL.State,
"CERT_COUNTRY": flags.Installation.SSL.Country,
"CERT_EMAIL": flags.Installation.SSL.Email,
"CERT_CNAMES": strings.Join(append([]string{fqdn}, flags.Installation.SSL.Cnames...), " "),
"CERT_PASS": flags.Installation.SSL.Password,
"HOSTNAME": fqdn,
}
envNames := []string{}
envValues := []string{}
for key, value := range env {
envNames = append(envNames, "-e", key)
envValues = append(envValues, fmt.Sprintf("%s=%s", key, value))
}

command := []string{
"run",
"--rm",
"--name", "uyuni-ssl-generator",
"--network", shared_podman.UyuniNetwork,
"-e", "TZ=" + flags.Installation.TZ,
"-v", utils.RootVolumeMount.Name + ":" + utils.RootVolumeMount.MountPath,
"-v", tempDir + ":/ssl:z", // Bind mount for the generated certificates
}
command = append(command, envNames...)
command = append(command, image)

command = append(command, "/usr/bin/sh", "-x", "-c", sslSetupScript)

if _, err := newRunner("podman", command...).Env(envValues).StdMapping().Exec(); err != nil {
return []string{}, cleaner, utils.Error(err, L("SSL certificates generation failed"))
}

log.Info().Msg(L("SSL certificates generated"))

return []string{"-v", tempDir + ":/ssl"}, cleaner, nil
}

const sslSetupScript = `
echo "Generating the self-signed SSL CA..."
mkdir -p /root/ssl-build
rhn-ssl-tool --gen-ca --no-rpm --force --dir /root/ssl-build \
--password $CERT_PASS \
--set-country $CERT_COUNTRY --set-state $CERT_STATE --set-city $CERT_CITY \
--set-org $CERT_O --set-org-unit $CERT_OU \
--set-common-name $HOSTNAME --cert-expiration 3650
cp /root/ssl-build/RHN-ORG-TRUSTED-SSL-CERT /ssl/ca.crt
echo "Generate apache certificate..."
cert_args=""
for CERT_CNAME in $CERT_CNAMES; do
cert_args=$cert_args --set-cname $CERT_CNAME
done
rhn-ssl-tool --gen-server --no-rpm --cert-expiration 3650 \
--dir /root/ssl-build --password $CERT_PASS \
--set-country $CERT_COUNTRY --set-state $CERT_STATE --set-city $CERT_CITY \
--set-org $CERT_O --set-org-unit $CERT_OU \
--set-hostname $HOSTNAME --cert-expiration 3650 --set-email $CERT_EMAIL \
$cert_args
NAME=${HOSTNAME%%.*}
cp /root/ssl-build/${NAME}/server.crt /ssl/server.crt
cp /root/ssl-build/${NAME}/server.key /ssl/server.key
echo "Generating DB certificate..."
rhn-ssl-tool --gen-server --no-rpm --cert-expiration 3650 \
--dir /root/ssl-build --password $CERT_PASS \
--set-country $CERT_COUNTRY --set-state $CERT_STATE --set-city $CERT_CITY \
--set-org $CERT_O --set-org-unit $CERT_OU \
--set-hostname reportdb.mgr.internal --cert-expiration 3650 --set-email $CERT_EMAIL \
$cert_args
cp /root/ssl-build/reportdb/server.crt /ssl/reportdb.crt
cp /root/ssl-build/reportdb/server.key /ssl/reportdb.key
`
19 changes: 9 additions & 10 deletions mgradm/cmd/install/podman/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,15 @@ func installForPodman(
return utils.Error(err, L("cannot setup network"))
}

sslArgs, cleaner, err := generateSSLCertificates(preparedImage, &flags.ServerFlags, fqdn)
defer cleaner()
if err != nil {
return err
}

log.Info().Msg(L("Run setup command in the container"))

if err := runSetup(preparedImage, &flags.ServerFlags, fqdn); err != nil {
if err := runSetup(preparedImage, &flags.ServerFlags, fqdn, sslArgs); err != nil {
return err
}

Expand Down Expand Up @@ -148,22 +154,14 @@ func installForPodman(
}
}

if flags.Installation.SSL.UseExisting() {
if err := podman.UpdateSSLCertificate(
cnx, &flags.Installation.SSL.Ca, &flags.Installation.SSL.Server,
); err != nil {
return utils.Errorf(err, L("cannot update SSL certificate"))
}
}

if err := shared_podman.EnablePodmanSocket(); err != nil {
return utils.Error(err, L("cannot enable podman socket"))
}
return nil
}

// runSetup execute the setup.
func runSetup(image string, flags *adm_utils.ServerFlags, fqdn string) error {
func runSetup(image string, flags *adm_utils.ServerFlags, fqdn string, sslArgs []string) error {
env := adm_utils.GetSetupEnv(flags.Mirror, &flags.Installation, fqdn, false)
envNames := []string{}
envValues := []string{}
Expand All @@ -181,6 +179,7 @@ func runSetup(image string, flags *adm_utils.ServerFlags, fqdn string) error {
"--network", shared_podman.UyuniNetwork,
"-e", "TZ=" + flags.Installation.TZ,
}
command = append(command, sslArgs...)
for _, volume := range utils.ServerVolumeMounts {
command = append(command, "-v", fmt.Sprintf("%s:%s:z", volume.Name, volume.MountPath))
}
Expand Down
4 changes: 3 additions & 1 deletion mgradm/shared/podman/podman.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ Environment="PODMAN_EXTRA_ARGS=%s"

// UpdateSSLCertificate update SSL certificate.
func UpdateSSLCertificate(cnx *shared.Connection, chain *types.CaChain, serverPair *types.SSLPair) error {
ssl.CheckPaths(chain, serverPair)
if err := ssl.CheckPaths(chain, serverPair); err != nil {
return err
}

// Copy the CAs, certificate and key to the container
const certDir = "/tmp/uyuni-tools"
Expand Down
5 changes: 1 addition & 4 deletions mgradm/shared/templates/mgrSetupScriptTemplate.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,9 @@ echo 'JAVA_OPTS=" $JAVA_OPTS -Xdebug -Xrunjdwp:transport=dt_socket,address=*:800
echo 'JAVA_OPTS=" $JAVA_OPTS -Xdebug -Xrunjdwp:transport=dt_socket,address=*:8002,server=y,suspend=n" ' >> /usr/share/rhn/config-defaults/rhn_search_daemon.conf
{{- end }}
/usr/lib/susemanager/bin/mgr-setup -s -n
/usr/lib/susemanager/bin/mgr-setup
RESULT=$?
# The CA needs to be added to the database for Kickstart use.
/usr/bin/rhn-ssl-dbstore --ca-cert=/etc/pki/trust/anchors/LOCAL-RHN-ORG-TRUSTED-SSL-CERT
if test -n "{{ .AdminPassword }}"; then
echo "starting tomcat..."
(su -s /usr/bin/sh -g tomcat -G www -G susemanager tomcat /usr/lib/tomcat/server start)&
Expand Down
10 changes: 0 additions & 10 deletions mgradm/shared/utils/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,6 @@ func GetSetupEnv(mirror string, flags *InstallationFlags, fqdn string, kubernete
if kubernetes {
env["NO_SSL"] = "Y"
} else {
// SSL setup for podman generated certificate
env["CERT_O"] = flags.SSL.Org
env["CERT_OU"] = flags.SSL.OU
env["CERT_CITY"] = flags.SSL.City
env["CERT_STATE"] = flags.SSL.State
env["CERT_COUNTRY"] = flags.SSL.Country
env["CERT_EMAIL"] = flags.SSL.Email
env["CERT_CNAMES"] = strings.Join(append([]string{fqdn}, flags.SSL.Cnames...), ",")
env["CERT_PASS"] = flags.SSL.Password

// Only add the credentials for podman as we have secret for Kubernetes.
env["MANAGER_USER"] = flags.DB.User
env["MANAGER_PASS"] = flags.DB.Password
Expand Down
20 changes: 19 additions & 1 deletion shared/ssl/ssl.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,9 @@ func sortCertificates(mapBySubjectHash map[string]certificate, serverCertHash st

// CheckPaths ensures that all the passed path exists and the required files are available.
func CheckPaths(chain *types.CaChain, serverPair *types.SSLPair) error {
mandatoryFile(chain.Root, "root CA")
if err := mandatoryFile(chain.Root, "root CA"); err != nil {
return err
}
for _, ca := range chain.Intermediate {
if err := optionalFile(ca); err != nil {
return err
Expand Down Expand Up @@ -305,3 +307,19 @@ func StripTextFromCertificate(certContent string) []byte {
}
return out
}

var newRunner = utils.NewRunner

// CheckKey verifies that the SSL key located at keyPath is valid and not encrypted.
func CheckKey(keyPath string) error {
if err := mandatoryFile(keyPath, L("server key is required")); err != nil {
return err
}

_, err := newRunner("openssl", "pkey", "-in", keyPath, "-passin", "pass:invalid", "-text", "-noout").Exec()
if err != nil {
return utils.Error(err, L("Invalid SSL key, it is probably encrypted"))
}

return nil
}
24 changes: 24 additions & 0 deletions shared/ssl/ssl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package ssl

import (
"fmt"
"strings"
"testing"

Expand Down Expand Up @@ -175,3 +176,26 @@ func TestGetRsaKey(t *testing.T) {
t.Errorf("Unexpected generated RSA key: %s", actual)
}
}

func TestCheckKey(t *testing.T) {
type testData struct {
file string
err string
}

testCases := []testData{
{"testdata/RootCA.key", "Invalid SSL key, it is probably encrypted"},
{"testdata/chain1/server.key", ""},
}

for i, testCase := range testCases {
err := CheckKey(testCase.file)
if testCase.err == "" {
testutils.AssertEquals(t, fmt.Sprintf("case %d: expected no error", i+1), nil, err)
} else {
testutils.AssertTrue(t, "Error message should state the key is probably encrypted",
strings.Contains(err.Error(), testCase.err),
)
}
}
}
1 change: 1 addition & 0 deletions uyuni-tools.changes.cbosdo.ssl-setup-refactoring
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Generate SSL certificates before the setup

0 comments on commit c69ebfa

Please sign in to comment.