RFC: Simple & automatic host registration WF

This also applies for activation keys, doesn’t it? That’s why I brought this concern up in the PR.

I don’t think this would be a problem. If the registration token has a short lifespan, I can totally imagine a button “revoke all my registration tokens” (or something better that refers to the functionality, not the technology “Revoke all global host registration links”).

Yes, but the AK grants access to explicit list of action you do. E.g. it assigns organization. By knowing the AK value, user proved they have access to it. I know, it’s weak, I’d like to replace it with the host group concept at some point and use the same token solution we’ll have for vanilla Foreman, but that’s not something we can solve here. After the subscription-manager registration, I think we still need to get the HRT though, where much more logic happens. There we need to make sure, user is properly authenticated and authorized.

I totally agree, it’s not. Just for the fair comparison, with x509 you can revoke specfic cert, with JWT we can only easily revoke all user’s registration tokens, not just the one that leaked. But again, IMHO that’s not an issue we should focus on right now.

Well, actually you could store all unique token ids that you want to revoke in the database and achieve the same result. It’s doable, but you lose the flexibility of being able to validate a token “offline”.

Fair enough!

For that we’d benefit of client cert, but given we have secured communication channel already, we can send the host unique identifier - subscription UUID. On client side, after the registration we can get it by subscription-manager identity. Then we find the host object id by searching the host with “subscription_uuid = $xyz”.

Given there is a strong pushback on forwarding GET /hosts on proxy (which I don’t understand), we may need to extend HRT API to also be able to search host by subscription_uuid, not just hostname. And that would need to be an extension done in Katello.

Then it is the same mechanism with same reliability and we don’t need a client certificate authentication for that.

AFAIK for now subscription-manager is only packaged on Red Hat-based systems. I know there are some efforts to package it for Debian, but let’s not complicate this RFC with that.

Good point. We should describe this as “configure subscription-manager” in the diagram. Today it happens via the consumer RPM but that’s an implementation detail and there are talks about changing it.

Ok, perhaps bad wording but the point is that there’s a specific URL that’s called to retrieve the HRT. I should probably have mentioned that I didn’t look up the exact URL. Perhaps I should have said something like foreman_url('hrt') in the diagram.

I think it’s actually a bad thing to scope it to a user. I view the HRT as effectively configuration management and it’s similar to the ENC: there is a host profile that must be applied and the host is the relevant thing. We already know the user has root on the target system.

I’m worried about a scenario where the senior ops person sets up a workflow. Because the senior is responsible for the Foreman instance instance, this person is also a Foreman admin.

Now the senior registers a system, checks it’s all OK and hands off the job to a junior with limited permissions. The senior forgets to set up some permissions and registration doesn’t work anymore. The senior then tries to register a system and it does work.

I’ll agree that there’s something to say for both arguments.

IMHO the main benefit of client certs is that you exactly know which system it is. It is embedded in the certificate and the server can read all the data. You are correct that it is more complex when you do proxy.

IMHO it’s about isolation. If you register via a proxy, the goal is to isolate it and only provide the minimal endpoints. Once you start to rely on the full Foreman API, that becomes something you need to ensure.

I can’t believe I’m about to say this (because I’m generally not in favor of this), but I think it’s good to be opinionated in this case.

IMHO the only goal of registration should be to register the system in Foreman and allow our existing management systems to take over. That may be Puppet, Ansible/REX or something else.

1 Like

Maybe I’m nit picking here, but it’s RHEL, not Red Hat specific then.

Ok, if we can agree on this endpoin being the same as in case 1 - the POST /register, then I’m good with that. This endpoints will need to behave like “find or create” and for the host it either found or created, it will render the HRT.

The fact that user has root access to one system in the infra does not imply, he or she should have admin permissions in Foreman. That Foreman instance can manage more hosts or even more infras.

This seems to be like we try to prevent users from making mistakes by turning off the authorization. We have user impersonation feature, senior admin can easily verify, whether it works under junior’s account.

But we know this already. In the original design, we got the host ID from the host creation. In this new design, the POST /register will render the template directly for the host that’s being registered. The only problem now appears when we want to implement “find or create” logic. It’s easy in case 2, when we’ll rely on subscription UUID, which reliable identifier. For case 1, we’ll need to rely on hostname anyway, there’s no guarantee, the has any trusted certificate.

I think the design never mentioned exposing the full Foreman API. We wanted to limit this just to POST /api/hosts and GET /api/hosts/:id. In the PR there were comments about adding more. But I think if we go with POST /register, we can easily achieve that. In fact @lstejskal came up with isolating also the GRT logic into the new controller. So we’ll have both actions necessary for the registration as a separate endpoints that can be forwarded.

To summarize the current status of this discussion. I think we agreed on adding new endpoint POST /register, which saves us 1 request in case 1. In case 2, we’ll use the same endpoint, since it will be able to also work with existing hosts created e.g. by subscription-manager register). The only thing where I think we didn’t reach agreement is whether we want to rely/use client certificates. I think that’s only applicable in case 2. I suggest to remain consistent and use JWT, that we already rely on in case 1. Is that acceptable to move this forward?

After that is answered and the summary is confirmed by other interested parties (@ekohl, @TimoGoebel, @lstejskal), we can start talking about case 3 and 4 - through proxy registration.

Yes, I’d like that. I want to note though, that saving the processing time of the extra request is obviously not the reason why the dedicated endpoint makes sense. From my point of view we do it for usability reasons, robustness, and containment.

It is also available for SLES from our own client repository, but I still think in an old version and not fully functional at the moment. But there is already a thread for this.
Debian and Ubuntu packages are able to build from the build instructions in the upstream git and ATIX is providing them for orcharhino, so I opened a thread for a client repository for Debian/Ubuntu to also provide them.
So consider them at least as planed.

Totally correct to be nit picking in this case.
But I would say it is RHEL specific we can assume the user wants subscription-manager to be installed by default and on Fedora, CentOS and other derivates, SLES, Debian and Ubuntu we can assume subscription-manager registration is wanted repositories are synced to Katello and published with certificate authentication and/or subscription-manager is already installed.
Not sure if there is a way for RHEL without subscription-manager with the new more relaxed subscription management (I can not remember the correct term and can not find it) or if this still requires it.

No, subman is packaged in CentOS and Fedora. I don’t see why they can’t be used to register to Katello. This is what we do in our nightly pipeline.

Pretty much. I believe that if you start implementing this, you’ll quickly see whether it makes sense to combine both. It may be that you end up with essentially 2 controllers mashed together.

True, the comparison to the Puppet ENC isn’t quite the same since the system isn’t presented with the exact ENC but rather a compiled catalog. That may contain a lot less info.

Can you impersonate when running curl /register?

For case 1 you no longer search if you implement POST /register.

If you’re presenting a subscription UUID a PUT /register feels more natural to me than a POST. However, the intention is getting the HRT, not to modify anything. That’s why I suggested something like GET /register/hrt.

True, but the Smart Proxy implementation did expose /api/hosts altogether. Then it was suggested to start using POST /api/hosts/facts. That’s my worry.

I’m glad I was able to convince you of the alternative.

I still think that a GET for the HRT in case 2 is more appropriate.

It is indeed only applicable in case 2. The challenge with JWT here is that you need some call to get the token. It was my understanding that a different token was used to retrieve the HRT than the one to get the GRT. Since subman doesn’t provide you with this, you’ll end up one HTTP call to get the JWT and one to get the HRT. Client certs allow you reduce this to one, which is why I suggested this.

If we can provide a token up front as part of the GRT then I’m ok with JWT.

Exactly, anything networked can easily fail; often at the worst times (see Murphy’s law). Reducing networked interactions to their minimum is a good thing.

Sounds good to me!

Yes, from your post, I got the impression it’s company specific, not Red Hat distros specific. But I also see Dirk’s comments, there are more distros that can benefit from subscription-manager based registration. All good.

No, but you can impersonate in UI to get the initial curl command that starts the process by fetching the GRT. JWT is then generated for the junior sysadmin. All the rest is then authorized under the junior without revealing her password.

Not if we want to support “find or create” there too. I think that was mentioned in other comment, e.g. for existing VM that was imported from CR. But you’re right, for the pure registration in case one, we don’t need that.

Ok, the important part for me here is, the controller action or its logic is the same, we may have more routes for that.

I believe we can. The reason for extra request was primarilly to get ID of the host for a given UUID. Since we’ll be able to search for host by UUID, we no longer need this.

Cool so we have case 1 and 2 resolved. Let’s get back to work. I’ll see if I could come up with diagrams for case 3 (vanilla Foreman, through proxy registration) and 4 (with Katello, subscription-manager, through-proxy).

Case 3: Vanilla Foreman with registration done through proxy

    Participant Foreman
    Participant Proxy
    Participant Client

    Client->>Proxy: GET $proxy_url/register
    Proxy->>Foreman: GET $foreman_url/register?url=$proxy_url
    Foreman->>Proxy: GRT containing curl -X POST $proxy_url/register | sh
    Proxy->>Client: GRT containing curl -X POST $proxy_url/register | sh
    Client->>Proxy: POST $proxy_url/register
    Proxy->>Foreman: POST $foreman_url/register?url=$proxy_url
    Foreman->>Proxy: HRT with curl $proxy_url/built
    Proxy->>Client: HRT with curl $proxy_url/built
    Client->>Proxy: POST $proxy_url/built
    Proxy->>Foreman: POST $foreman_url/built

Steps in more details

  1. client contacts the proxy to initiate the process.
  2. proxy’s registration module computes proxy’s url based on the incoming request (more details later). It contacts Foreman at the $foreman_url based on the foreman_url proxy setting. Besides forwarding all parameters and headers client sent in request 1, it also adds url parameter.
  3. Foreman realizes that custom url was passed, so instead of using $foreman_url, it generates HRT curl with the hostname being a value of url parameter. For example if the client called https://proxy.example.com:8443/register, Foreman will receive https://proxy.example.com:8443 as url and inside of GRT, it will render curl -X POST https://proxy.example.com:8443/register.
  4. client gets the GRT and executes it, once it hits the HRT request, it continues with step 5
  5. proxy does the same as in step 3, forwards the request to Foreman, but tells it to use a different url for additional requests
  6. Foreman registers the host and renders HRT for it, again using params[:url] || foreman_url('built') logic, resulting in HRT which will call home through proxy.
  7. HRT template makes it to the proxy
  8. HRT template makes it to the client
  9. standard proxying of informing about host being built, no url magic needed here
  10. Foreman turns host into built, hooray

How proxy determines $proxy_url in steps 2 and 6?
Proxy looks at request.env['REQUEST_URI'] which contains the URL that client used in order to contact the proxy. In some edge cases, it may not be hostname -f of the proxy. It may not be the url of the proxy in Foreman. But it’s something we know client can resolve and talk to. The whole logic is request.env['REQUEST_URI']&.split('/register')&.first meaning we still keep the information about scheme, fqdn and port that was used.

Authentication of requests
GRT contains JWT that’s used to authenticate the request in step 5. Given it’s passed as a custom header, it gets automatically proxied in step 6. Foreman verifies the signature.

HRT contains the build token that is used to authenticate the POST /built after the execution of HRT is done. Please don’t ask for replacing this with JWT in Foreman 2.3 :slight_smile:

JWT is tied to the user account, so HRT rendering happens under that account permissions. The fact the user knows build token means he had the permission to created (register) the host. We trust he or she can switch this flag without proper authorization. This is the well known concept from provisioning, where token knowledge is enough to flip the build mode.

I hope this helps, questions please. Also once you’re happy with the design for case 3, ACK would be great at least from the same group.

This implies, that Foreman knows the proxy URL and renders that instead of the URL pointing to Foreman. How do we know which proxy we want to use?

So there are two ways to start the registration.

  1. I go to client VM shell and type the curl manually, therefore I must know the proxy url. In this case, Foreman does not need to know it
  2. I use the the Foreman UI to generate the command. The plan is to have the “New host registeration” page, where user has a simple form to pick hostgroup, organization, location, JWT expiration time etc and he’ll the curl command to run. I’d add a drop down selector for proxies we know about. Therefore we’d get the URL of the proxy that Foreman talks to. In edge case this may not be resolvable by the client, however I think in most cases it’s the same. In case people would need more flexibility, we can consider a text input for proxy url.

Could we also add a registration proxy to all subnets so users could alternatively choose the target subnet. This should be more user friendly because user’s don’t need to know details about the network topology (which a Foreman admin might know when setting everything up).

Interesting. I think that’s a good enhancement in general. Should we keep both options?

I guess subnet could have more proxies assigned right? Or would you see that more like 1:n? Would you prefer to keep both options in the form then? Explicit selection of the proxy or subnet. I like explicit proxy selection because if I as a user don’t want to define the subnet object, I could still register hosts throught external proxies I’ve deployed. If I don’t plan to use provisioning, I may not want to keep track of subnets in Foreman. Should the subnet selection also set it for the host primary interface?

We’re going slightly off-topic now though. The UI will go through multiple iterations I believe. Let’s say we can identify the proxy on the Foreman side somehow, does the flow in case 3 then looks alright?

Yes. This could be part of a Wizard where the user can choose the strategy.

Yes, this could be similar to REX. However, it might be a lot easier if we make admins choose one proxy per Subnet as we can’t render a single curl command that uses multiple proxies.

During host registration, I’d set the subnet if we can reliably guess it from the IP address.

It does.

1 Like

Overall the flow looks good, my comments are mostly about implementation details.

It looks like the Register module for the Smart Proxy needs (at least) 2 URLs:

  • /register
  • /built

I think these we should try not to pollute the top level API. Looking at the current code, the traditional provisioning uses /unattended/built. I think you mean to reuse this, also on the Foreman side.

That means the register module will depend on the templates module to provide that endpoint.

Am I capturing this right?

1 Like

:dart: , /built was meant to be /unattended/built. If we don’t want to add the endpoint forwarding to the new register module, we can reuse templates module. However, that means the forwarding for that endpoint is relying on templates_url setting of that module, right?

This RFC was implemented and shipped as part of Foreman 2.3 (and continues to receive updates).