How SSH finish script authentication work

Hello,

while I was working on some SSH key-related changes, during reviews we were unsure how SSH authentication works in this workflow. I hereby present you a summary on the topic. This post is all written by me, keeping it as short as possible while using AI to confirm how I remembered it.

Finish SSH script is a template that can be associated with a host through its operating system or host group. When the host is being provisioned (created), Foreman (Server) waits for the host to come up and tries to connect via SSH to the provision interface’s IPv6 address, then IPv4 address, and finally the given hostname if no IP address is available. It executes the provided template, typically a shell script. This is used for bootstrapping things like Ansible or Remote Execution.

In order to achieve this, the user must create an Image record for a particular Compute Resource. In this image, SSH user must be provided together with an optional password. There is also a user-data boolean flag and it must be turned off, otherwise no SSH finish script is scheduled for execution because user data is injected (typically through cloud-init) instead.

Now, here comes the tricky part. Only some Compute Resources do support SSH keys. When the Compute Resource is created, they automatically generate a fresh SSH key pair and store the private key in the Foreman database. The key pair is normally associated with the Compute Resource and reused for hosts created through it; it is not unique for each host.

Some plugins, including Azure RM and Google, also generate and store a key pair, although their capability advertising is not fully consistent with the core EC2/OpenStack implementation. Some integrations store only the private key in Foreman because the provider already stores or receives the public key; others may store both. That is a different story, and I want to keep this short. The list of supported Compute Resources:

  • EC2
  • OpenStack
  • Azure RM
  • Google

As you can see, this workflow is pretty much common for cloud, but not so much for virtualization: popular libvirt and VMWare are missing from here. The reason is that this dynamic injection of SSH keys needs dedicated infrastructure, which is provided by cloud providers (typically via cloud-init or other means).

Fun fact: the mentioned optional password field is ignored for the mentioned Compute Resources, it is only effective for other ones. It does not even work as a fallback when SSH key is wrong, in that case the SSH finish workflow just give up.

Marek made a good point that there was also a possibility to edit ~foreman/.ssh/id_rsa and provide your own key, which was accepted by the previous implementation. This has changed with the new implementation and no longer works. We should talk about that.

This feature has always been a bit shady, because all of this happens in the background. Foreman UI/API does advertise the key_pair capability where it is implemented, but the relationship between provider key-pair creation, guest key injection, and SSH finish authentication is not very visible to users. We do have this in our docs somewhere, or at least I hope that is the case. I hope at some point we can probably expose this to users more directly, or allow them to add their own SSH keys. Those would need to be handled separately from the automatically generated private keys and would only work if the corresponding public keys were also made available in the guest, for example through an image, cloud-init, or a custom provisioning template.

Hope it helps to shed some light on the matter. It definitely helped me to understand (again!) how this works.

@Marek_Hulan made a good point that there was also a possibility to edit ~foreman/.ssh/id_rsa and provide your own key, which was accepted by the previous implementation. This has changed with the new implementation and no longer works.

The question is do we want to keep this feature? It is a bit clunky.

In a a related post it was shown that this was actually a very common way to deploy:

It’s also my impression that it’s used when following Provisioning hosts unless you explicitly choose cloud-init:

If your image supports user data input such as cloud-init data, click the User data checkbox.

Furthermore, reading Provisioning hosts then it doesn’t actually use the UserData feature which means you need to create an image for every Foreman/Foreman proxy server you deploy.

I still think that providing data and leveraging tools like cloud-init is better than using SSH shell scripts, but right now it reads like a breaking change for many users.

Is this being tracked as a release note now?

Sure, I am aware of that, yet, this is not something which works out of box after VMWare deployment. But the post is relevant, thanks for the context I was not aware of this discussion.

I do not agree with the assessment “cloud-ini > ssh script”, it is a different approach and despite how clunky this feature is, it has its place. This is conclusion of the linked discussion after all.

Instead of removing it, I would like us to plan a radical revamp of the feature. I can see a world where Foreman core simply only schedules Remote Execution Job which asynchronously executes the SSH finish script. This also solves several problems I see with the current approach:

  • Foreman Server no longer connect to random hosts directly (Foreman Proxy does, can be deployed close to the provisioned infra)
  • No auto-created (random) SSH private keys which are invisible to the user (only in Foreman DB)
  • More predictable workflow
  • Asynchronous processing (today the whole SSH transaction is currently a ActiveRecord hook and blocks the whole host creation)
  • The current capability contract is vague and only half of the CRs fully implement it

This of course assumes that the cloud/virtualization provider already contains Remote Execution master SSH public key. Today, it is the SSH script what deploys ReX keys, we would need this to be the other way around. Workflow:

  • New host is created (user data not checked)
  • Foreman core finds if the Compute Resource already has a ReX SSH public key of the Smart Proxy in use and is up-to-date
  • If not, it (re-)uploads it and stores the reference in the DB
  • The instance is launched using the SSH key
  • ReX job is scheduled with time_to_pickup set to 5 minutes (configurable)
  • Host is saved with build mode on
  • The SSH finish template calls home (currently it does not this is what orchestration code do)
  • ReX does the job and stores results
  • User can find detailed logs from the provisioning using ReX UI/API/CLI

In this new workflow, SSH finish script needs to call home because we need to keep the build window open for further actions like setting up Puppet.

I completely dropped SSH password from the picture, they would not be supported anymore.

This is a very rough idea that needs a full spike and research. Also, this is not a small task to do, we have other priorities.

So how about introducing Setting[:ssh_finish_identity_file] = "/var/lib/foreman/.ssh/finish_key" setting to keep the current behavior? This key would be used in addition to existing SSH keys for provisioning @Marek_Hulan ?

Thanks @lzap for raising this and sorry for pointing you to explore this further.

After thinking more about this, i’d be actually in favor of dropping the entire SSH based provisioning from core and optionally replacing it with REX plugin if people need it. I don’t like the fact that this connection is made from Foreman, while REX connection is made by foreman-proxy. Unfortunately, we don’t have any data on how many folks use this, but I’d suggest we either drop this and potentially significantly enhance this later. Status quo would be IMHO the worst option. If dropping would be too much work for now, we can simply ignore the SSH key feature, no need to restore that.

You mean the code path that was using any SSH key from Foreman’s HOME folder?

To keep the status quo, I added a one commit to the existing PR: when /var/lib/foreman/ssh_finish_key file exists (and has 600 permissions) then use it as a possible SSH key. This way users who were using SSH keys in Foreman home directory can still do this.

@ekohl do you want me to create a Jira Feature or Spike? This is definitely something we will not have time in 2026 or early 2027 but just in case.

Yes

Do we plan to document this? If not (and I don’t think we should), I’m quite sure no one will ever use it and hence I would not even introduce it. If we document it, we may get some users of taking the advantage of this, who will be disappointed if we drop it in future.

Interesting chain of thoughts, I am not gonna lie :slight_smile:

I think in the AI world, the fact that we discussed it right here in this thread is a window for LLMs to find it. And because the change is trivial (opts.push("-oIdentityFile=#{SSH_FINISH_KEY}") if finish_key) I would simply merge this without further documentation actions. Users will have their workaround and this is low effort.

Thinking about cloud-init, there is an additional requirement: The Foreman instance must be accessible by the cloud compute resource. But technically, the same applies to user_data and the phone-home call.

By using Smart Proxies, this might not be as much of an issue for our users as exposing the entire Foreman instance.

I think I might have been the only user and I don’t need the workaround :slight_smile: I’m just worried about unnecessary scope screen here. If we identified more users who need the workaround, it’d be trivial to add it as you say. Foreman is full of these hidden tricks and I’d prefer if we do not introduce new ones.

And that’s the point. We should either base this feature on top of REX plugin to reuse the key and initiate the connection from proxy or use cloud-init style. They differ in the direction of connectivity that’s required.

Foreman <----cloud init ----- VM (with option of going through the proxy)
Foreman ----- ssh --------> VM (no option to go through proxy)

going forward we should either get rid of the SSH entirely or replace it with

Foreman (REX) -----> Foreman Proxy (REX) ssh -----> VM

I get that we want to get rid of net/ssh from core now. Great, let’s just not add or maintain unused functionality as this shouldn’t be the long term solution either.

As someone who argued heavily on keeping SSH based post-provisioning in the first thread, I guess I should chime in here, too.

We use the SSH based workflow exclusively for on-prem (currently only VMWare, soon also Proxmox), and the primary reason we do so is because user-data with VMWare is a pain and we were not able to get it to work whatsoever. So while I cannot speak on the whole cloud provider part, I can offer a perspective on daily usage of the feature in general.

The main pain points of SSH based post-provisioning are in my opinion:

  • Requirement to use password based authentication (at least for on-prem providers)
  • Only possible to run SSH scripts from the main Foreman server
  • If the script fails (for any reason) it is nearly impossible to debug from the Foreman side, because the log messages are a scrambled mess.
  • I believe have not yet run into a situation where the script being a blocking part of host creation caused any issues, but I could imagine this causing timeout problems on HTTP requests when the script takes to long to finish.

All of these points would be addressed by migrating the process to REX. So +1 to that approach from my end. The only downside I can see on that is that it requires people to have the REX plugin installed if they do not already, but I

Which new implementation are you referring to here? From the history of provision/ssh.rb, it looks like there have been no major changes to that workflow for at least the last 10 years. (Except one, which has been quickly reverted due to breaking stuff)
Is this about the open PR in How SSH finish script authentication work - #7 by lzap ? Maybe I just got confused by the order in this thread.

I would suggest switching the order of “Host is saved with build mode on” and “ReX job is scheduled” if possible. If saving the host fails due to validations (or similar things), there is no need to have a REX job scheduled.
Also, I would argue that pre-baking your existing ReX public key into the Image should be a valid option especially for compute providers that do not provide a SSH key capability.

More a personal note kind of comment, but I do not believe this is valid reasoning on whether or not something should be documented. First off, not everyone is using AI tools as their primary research methods. Second, there are (probably quite a few) organizations out there that heavily regulate which AI tools their employees are allowed to use and how they can be used, so even if everyone used AI research, there will probably be quite a few which will not pick this up within reasonable time. Third, I firmly believe that there should always be a way to cross-check the AI output. And an entry in the docs makes for a way better reference than a 10-20+ posts long forum discussion.
Plus, I agree on Marek’s point of disappointed users in the future, especially if it does not use the same path by default as the old solution and so will just cause more breaking changes.

The only downside I can see on that is that it requires people to have the REX plugin installed if they do not already, but I …

Unfinished sentence, please finish it, I really want to read your opinion in full! Cheers :slight_smile:

You can ignore the whole discussion about ~foreman/.ssh this was a quirk that worked with the old implementation (which was replaced this week with OpenSSH based one) and it no longer works. Irrelevant for ReX refactoring.

Yeah sorry, I knew it. Basically, you could “hack” SSH provisioning by inserting one or more SSH keys into Foreman’s home. Since the old SSH implementation in Ruby recognized this, you could use this to do provisioning of (e.g. libvirt) hosts where you manually installed that (public) key from the pair.

The new implementation removes that behavior, when I was working on the code I wanted the most possible secure and streamlined experience so the OpenSSH client process configuration is intentionally narrowed down.

I added the “hack” back into the mentioned PR as a separate commit but @Marek_Hulan did not like it so I made a note to remove it when I get back to it. If you want it, say the word and we can keep it.

Good point.

Indeed.

1 Like

Whoopsie, got confused while hopping around editing my own post :sweat_smile:

I do believe, requiring ReX for this is not a huge downside. ReX and f-tasks are already required for quite a lot of other plugins (f-ansible and Katello instantly come to mind) and from what I have seen on support threads in the forum, I would assume it is probably one of the most commonly used plugins anyway.

2 Likes

I do not need this, I personally only learned about that hack via this thread :wink:
In my opinion, SSH Key based workflows would reduce the potential for breakage in the Workflow (we had that quite a bit in the past, where randomly passwords would not match anymore) but I am happy to wait for a proper solution. No need for a temporary workaround from my end, the current intended workflow with password works for us, despite not being “pretty”.

1 Like