Skip to content

Commit

Permalink
Add Foreman CA refresh template
Browse files Browse the repository at this point in the history
In this PR I am introducing a way to refresh CA certificate for Foreman
server. It will have the following parts:
 [X] Downloadable template to run directly on a server
 [ ] REX script template to be used with SSH REX provider
 [ ] REX Ansible template to be used with Ansible REX provider

All the ways would refresh `katello-server-ca.pem` file and refresh
CA root store accordingly.
  • Loading branch information
ShimShtein committed Jun 20, 2024
1 parent 58e8f94 commit f2505b4
Show file tree
Hide file tree
Showing 19 changed files with 92 additions and 44 deletions.
14 changes: 13 additions & 1 deletion app/controllers/unattended_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class UnattendedController < ApplicationController
# Maximum size of built/failed request body accepted to prevent DoS (in bytes)
MAX_BUILT_BODY = 65535

MAINTENANCE_TEMPLATE_KIND_NAME = 'maintenance'

def built
return unless verify_found_host
return head(:method_not_allowed) unless allowed_to_install?
Expand Down Expand Up @@ -60,6 +62,7 @@ def host_template
return head(:not_found) unless kind.present?

return if render_ipxe_template
return if render_maintenance_template(kind, params[:id])

return unless verify_found_host
return head(:method_not_allowed) unless allowed_to_install?
Expand All @@ -86,6 +89,15 @@ def render_error(message, options)
end
end

def render_maintenance_template(kind, name)
return false unless kind == MAINTENANCE_TEMPLATE_KIND_NAME
return false unless verify_found_host(false)

template = ProvisioningTemplate.joins(:template_kind).find_by(name: name, template_kinds: { name: kind })

render_template(template: template, type: kind)
end

def render_intermediate_template
ipxe_template_kind = TemplateKind.find_by(name: 'iPXE')
name = Setting[:intermediate_ipxe_script]
Expand Down Expand Up @@ -159,7 +171,7 @@ def load_host_details
@host = Foreman::UnattendedInstallation::HostFinder.new(query_params: query_params).search
end

def verify_found_host
def verify_found_host(needs_token = true)
host_verifier = Foreman::UnattendedInstallation::HostVerifier.new(@host, request_ip: request.remote_ip,
for_host_template: (action_name == 'host_template'))

Expand Down
2 changes: 2 additions & 0 deletions app/models/template_kind.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def self.default_template_labels
"registration" => N_("Registration template"),
"kexec" => N_("Discovery Kexec"),
"Bootdisk" => N_("Boot disk"),
"maintenance" => N_("Maintenance template"),
}
end

Expand All @@ -44,6 +45,7 @@ def self.default_template_descriptions
"POAP" => N_("Provisioning for switches running NX-OS."),
"cloud-init" => N_("Template for cloud-init unattended endpoint."),
"host_init_config" => N_("Contains the instructions in form of a bash script for the initial host configuration, after the host is registered in Foreman"),
"maintenance" => N_("Template to run on hosts to make sure Foreman configuration is intact"),
}
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ module UnattendedInstallation
class HostVerifier
attr_reader :errors, :host, :request_ip, :for_host_template, :controller_name

def initialize(host, request_ip:, for_host_template:)
def initialize(host, request_ip:, for_host_template:, needs_token: true)
@host = host
@errors = []
@for_host_template = for_host_template
@request_ip = request_ip
@controller_name = 'unattended'
@needs_token = needs_token
end

def valid?
Expand All @@ -25,6 +26,7 @@ def valid?
# In case the token expires during installation
# Only relevant when the verifier is being used with `for_host_template`
def valid_host_token?
return true unless @needs_token
return true unless for_host_template
return true unless @host&.token_expired?

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<%#
kind: maintenance
name: foreman_ca_refresh
model: ProvisioningTemplate
oses:
- AlmaLinux
- CentOS
- CentOS_Stream
- Fedora
- RedHat
- Rocky
description: |
This template is used to refresh foreman CA certificates on Katello-registered hosts
-%>
#!/bin/sh

<%= snippet('ca_registration') -%>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<%#
kind: snippet
name: ca_registration
model: ProvisioningTemplate
snippet: true
description: |
This template is used for updating Foreman's CA on hosts that are registered by Katello.
It replaces the CA used by subscription-manager and adds the CA to trusted anchors.
-%>

<% if plugin_present?('katello') -%>
# Define the path to the Katello server CA certificate
KATELLO_SERVER_CA_CERT=/etc/rhsm/ca/katello-server-ca.pem

# If katello ca cert file exists on host, update it and make sure it's in trust anchors
if [ -f "$KATELLO_SERVER_CA_CERT" ]; then
<%= save_to_file('"$KATELLO_SERVER_CA_CERT"', foreman_server_ca_cert) -%>

if [ -f /etc/debian_version ]; then
CA_TRUST_ANCHORS=/usr/local/share/ca-certificates/
else
CA_TRUST_ANCHORS=/etc/pki/ca-trust/source/anchors
fi

# Add the Katello CA certificate to the system-wide CA certificate store
cp $KATELLO_SERVER_CA_CERT $CA_TRUST_ANCHORS

if [ -f /etc/debian_version ]; then
update-ca-certificates
else
update-ca-trust
fi
fi
<% end -%>
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,6 @@ RHSM_CFG=/etc/rhsm/rhsm.conf
<% end -%>

<% if plugin_present?('katello') -%>
# Define the path to the Katello server CA certificate
KATELLO_SERVER_CA_CERT=/etc/rhsm/ca/katello-server-ca.pem

# If SSL_CA_CERT is not set, create a temporary file for it
if [ -z "$SSL_CA_CERT" ]; then
SSL_CA_CERT=$(mktemp)
cat << EOF > "$SSL_CA_CERT"
<%= foreman_server_ca_cert %>
EOF
fi

<% if @subman_setup_scenario == 'registration' -%>
# rhn-client-tools conflicts with subscription-manager package
# since rhn tools replaces subscription-manager, we need to explicitly
Expand All @@ -59,10 +48,15 @@ EOF
<% end -%>
<% end -%>

# Define the path to the Katello server CA certificate
KATELLO_SERVER_CA_CERT=/etc/rhsm/ca/katello-server-ca.pem

# Prepare the SSL certificate
mkdir -p /etc/rhsm/ca
cp -f $SSL_CA_CERT $KATELLO_SERVER_CA_CERT
touch $KATELLO_SERVER_CA_CERT
chmod 644 $KATELLO_SERVER_CA_CERT

<%= snippet('ca_registration') -%>
<% end -%>

# Prepare subscription-manager
Expand Down Expand Up @@ -133,25 +127,5 @@ else
sed -i "/baseurl/a $full_refresh_config" $RHSM_CFG
fi

<% if @subman_setup_scenario == 'provisioning' && plugin_present?('katello') -%>
if [ -f /etc/debian_version ]; then
CA_TRUST_ANCHORS=/usr/local/share/ca-certificates/
else
CA_TRUST_ANCHORS=/etc/pki/ca-trust/source/anchors
fi

# Add the Katello CA certificate to the system-wide CA certificate store
if [ -d $CA_TRUST_ANCHORS ]; then
if [ -f /etc/debian_version ]; then
cp $KATELLO_SERVER_CA_CERT $CA_TRUST_ANCHORS
update-ca-certificates
else
update-ca-trust enable
cp $KATELLO_SERVER_CA_CERT $CA_TRUST_ANCHORS
update-ca-trust
fi
fi
<% end -%>

# Restart yggdrasild if installed and running
systemctl try-restart yggdrasil >/dev/null 2>&1 || true
5 changes: 5 additions & 0 deletions test/controllers/unattended_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ class UnattendedControllerTest < ActionController::TestCase
assert_response :success
end

test "should get a foreman CA refresh for a host" do
get :host_template, params: { kind: 'maintenance', id: 'foreman_ca_refresh' }
assert_response :success
end

test "should get a kickstart when IPv6 mapped IPv4 address is used" do
@request.env["HTTP_X_FORWARDED_FOR"] = "::ffff:" + @rh_host.ip
@request.env["REMOTE_ADDR"] = "127.0.0.1"
Expand Down
4 changes: 4 additions & 0 deletions test/fixtures/template_kinds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ host_init_config:
registration:
name: registration
description: description for registration template

maintenance:
name: maintenance
description: description for maintenance template
8 changes: 8 additions & 0 deletions test/fixtures/templates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,11 @@ host_init_config:
operatingsystems: centos5_3, redhat
locked: true
type: ProvisioningTemplate

foreman_ca_refresh:
name: foreman_ca_refresh
template: 'echo "Refreshing certificates"'
template_kind: maintenance
operatingsystems: centos5_3, redhat
locked: true
type: ProvisioningTemplate
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ runcmd:
sed -i "/baseurl/a $full_refresh_config" $RHSM_CFG
fi


# Restart yggdrasild if installed and running
systemctl try-restart yggdrasil >/dev/null 2>&1 || true
# Avoid timeout accessing unreachable repo on air gapped infrastructure,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ else
sed -i "/baseurl/a $full_refresh_config" $RHSM_CFG
fi


# Restart yggdrasild if installed and running
systemctl try-restart yggdrasil >/dev/null 2>&1 || true
# Avoid timeout accessing unreachable repo on air gapped infrastructure,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ else
sed -i "/baseurl/a $full_refresh_config" $RHSM_CFG
fi


# Restart yggdrasild if installed and running
systemctl try-restart yggdrasil >/dev/null 2>&1 || true
# Avoid timeout accessing unreachable repo on air gapped infrastructure,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ else
sed -i "/baseurl/a $full_refresh_config" $RHSM_CFG
fi


# Restart yggdrasild if installed and running
systemctl try-restart yggdrasil >/dev/null 2>&1 || true
# Avoid timeout accessing unreachable repo on air gapped infrastructure,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ else
sed -i "/baseurl/a $full_refresh_config" $RHSM_CFG
fi


# Restart yggdrasild if installed and running
systemctl try-restart yggdrasil >/dev/null 2>&1 || true
# Avoid timeout accessing unreachable repo on air gapped infrastructure,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ else
sed -i "/baseurl/a $full_refresh_config" $RHSM_CFG
fi


# Restart yggdrasild if installed and running
systemctl try-restart yggdrasil >/dev/null 2>&1 || true
# Avoid timeout accessing unreachable repo on air gapped infrastructure,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ else
sed -i "/baseurl/a $full_refresh_config" $RHSM_CFG
fi


# Restart yggdrasild if installed and running
systemctl try-restart yggdrasil >/dev/null 2>&1 || true
# Avoid timeout accessing unreachable repo on air gapped infrastructure,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,6 @@ else
sed -i "/baseurl/a $full_refresh_config" $RHSM_CFG
fi


# Restart yggdrasild if installed and running
systemctl try-restart yggdrasil >/dev/null 2>&1 || true
# Avoid timeout accessing unreachable repo on air gapped infrastructure,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,6 @@ else
sed -i "/baseurl/a $full_refresh_config" $RHSM_CFG
fi


# Restart yggdrasild if installed and running
systemctl try-restart yggdrasil >/dev/null 2>&1 || true
# Avoid timeout accessing unreachable repo on air gapped infrastructure,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ else
sed -i "/baseurl/a $full_refresh_config" $RHSM_CFG
fi


# Restart yggdrasild if installed and running
systemctl try-restart yggdrasil >/dev/null 2>&1 || true
# Avoid timeout accessing unreachable repo on air gapped infrastructure,
Expand Down

0 comments on commit f2505b4

Please sign in to comment.