Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #33811 - Fixing inheriting of root password for Hosts #8887

Merged
merged 1 commit into from Nov 24, 2021

Conversation

domitea
Copy link
Contributor

@domitea domitea commented Oct 29, 2021

Hosts need passwords, especially the root one for the provisioning. With the bigger amounts of hosts, hostgroups are used for handle and easily modify these attributes for hosts. Root password has special place in this chain but it isn't to great as it looks. When you're change the hostgroup for host, the root password became intanct. It makes sence for host that has own password defined but not for host that use hostgroup's password. So this PR tries to fix it.

@theforeman-bot
Copy link
Member

Issues: #33811

@domitea
Copy link
Contributor Author

domitea commented Nov 2, 2021

Testing if the host group related tests are really fixed. The PR is about passwords so it's on the table to careful.

And as I learned earlier, the green sign at local dev setup doesn't automatically mean green button here.

@domitea domitea force-pushed the fix_33811 branch 3 times, most recently from 0654815 to 8f323c9 Compare November 4, 2021 06:33
@@ -684,12 +684,12 @@ def provider
def root_pass
return self[:root_pass] if self[:root_pass].present?
return hostgroup.try(:root_pass) if hostgroup.try(:root_pass).present?
Setting[:root_pass]
crypt_root_pass(Setting[:root_pass])&.first
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not have time for full review, but what immediately strikes me is that previously this was just a getter, now this modifies state of the model. In the method both root_pass= and grub_pass= methods are called.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's the case. The method crypt_root_pass just uses local variables root_pass and grub_pass, then returns them in the array. From this array, we take the first value (root_pass hash for the given input) and use only that as a result of root_pass method.

What causes the confusion is imho the fact, that crypt_root_pass always gives you hashes for both root and grub passwords. IMHO it should accept the parameter saying which type the consumer is interested in.

@lzap
Copy link
Member

lzap commented Nov 4, 2021

Can you describe the solution and how are you trying to achieve the result?

Also I am missing a test that would simulate what the user complains about, the patch just modifies existing tests. I read that as its making a change for existing behavior.

Don't get me wrong but this feature is a can of worms really. We need to be extra careful for this one.

ares
ares previously requested changes Nov 5, 2021
Copy link
Member

@ares ares left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I can confirm the behavior works as this.

The root_pass is never stored on the host or host group level unless explicitly set. Therefore, even after you modify the host group of the host, the host.root_pass will inherit the value from the host group. If not value is set for the host group, it still takes the value from the settings. The root_pass method now also correctly returns crypted version of the password.

Previously, we saved the root_pass to the host, meaning if you changed the HG which had a different root pw, the host would still have the old root pw even after the rebuild. While I see some logic behind that, it was not expected by the users. Also previously it didn't crypt the password if we used the one from Settings, this is also now fixed.

Things to improve:

  1. There's a mistake in the root_pass_check though
  2. the crypt_root_pass looks a bit cryptic (pun intended). Since it's based on the old code, it tries to deal with the brub_pass too. However we often need it for the root_pass only. I think it should accept a parameter which password the callee is actually interested in.
  3. the test that covers the HG change (as @lzap pointed out) should be added
  4. the test for root_pass_check with HG nesting should also be added

end

def root_pass_check
self[:root_pass].present?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not work reliably for host group, host group that is nested, does not have a root password set directly but inherits it from parent would result in false. The root pass component then says it applies global setting. In reality, the root_pass method that is used in host/managed.rb (line 686) from the hostgroup supports the inheritance from parents.

@@ -684,12 +684,12 @@ def provider
def root_pass
return self[:root_pass] if self[:root_pass].present?
return hostgroup.try(:root_pass) if hostgroup.try(:root_pass).present?
Setting[:root_pass]
crypt_root_pass(Setting[:root_pass])&.first
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that's the case. The method crypt_root_pass just uses local variables root_pass and grub_pass, then returns them in the array. From this array, we take the first value (root_pass hash for the given input) and use only that as a result of root_pass method.

What causes the confusion is imho the fact, that crypt_root_pass always gives you hashes for both root and grub passwords. IMHO it should accept the parameter saying which type the consumer is interested in.

Copy link
Member

@ezr-ondrej ezr-ondrej left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of my comments are not blockers, but please take a look at them and let me know what you think. Only real blocker is the double present? as Boolean#present? will lead to unexpected results.

return N_("host") if self[:root_pass].present?
return N_("hostgroup") if hostgroup.try(:root_pass).present?
return N_("host") if root_pass_check
return N_("hostgroup") if hostgroup.try(:root_pass_check).present?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is double present now. Probably not what we want.

@@ -1034,4 +1034,8 @@ def validate_association_taxonomy(association_name)
def compute_resource_in_taxonomy
validate_association_taxonomy(:compute_resource)
end

def root_pass_check
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have method now?

self.root_pass = operatingsystem.nil? ? PasswordCrypt.passw_crypt(unencrypted_pass) : PasswordCrypt.passw_crypt(unencrypted_pass, operatingsystem.password_hash)
self.grub_pass = PasswordCrypt.grub2_passw_crypt(unencrypted_pass)
end
def save_crypted_root_pass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we renamed the method? We are still just setting the password, we are not saving it in this method, are we?

I think crypt_host_passwords would describe the method better.

Comment on lines 255 to 256
return true if nested_root_pw
self[:root_pass].present?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we swap these?
nested_root_pw is quite inefficient method and we can skip it if self[:root_pass]


def crypt_pass(unencrypted_pass, pass_kind)
return unless unencrypted_pass.present?
raise "Correct type of password is not used. You can use only :root or :grub" if [:root, :grub].exclude?(pass_kind)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is bit hard to read and decode what does it mean.

Suggested change
raise "Correct type of password is not used. You can use only :root or :grub" if [:root, :grub].exclude?(pass_kind)
raise "Incorrect type of password. Only one of :root, :grub is supported" unless [:root, :grub].include?(pass_kind)

@@ -251,6 +251,11 @@ def render_template(template:, **params)
template.render(host: self, **params)
end

def root_pass_check
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could that be root_pass_present? so it is more clear?
In case we decide to keep the method, which I believe might be overkill given it's used once.

Copy link
Member

@ares ares left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see another review has been added meanwhile, so just leaving a comment for one code construction

unencrypted_pass.starts_with?("$")
end

if is_actually_encrypted
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about

# Grub_pass and root_pass are the same, so return the right pass is correct everytime
return unencrypted_pass if is_actually_encrypted

case pass_kind 
when :root
  operatingsystem.nil? ? PasswordCrypt.passw_crypt(unencrypted_pass) : PasswordCrypt.passw_crypt(unencrypted_pass, operatingsystem.password_hash)
when :grub
  PasswordCrypt.grub2_passw_crypt(unencrypted_pass)
else
  raise 'Unsupported pass type'
end	

That would be imho slightly more readable and safer in case someone uses the method in a wrong way

Copy link
Member

@ezr-ondrej ezr-ondrej left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart of one nit, looks correct to me 👍

@@ -658,13 +658,13 @@ def provider
# no need to store anything in the db if the password is our default
def root_pass
return self[:root_pass] if self[:root_pass].present?
return hostgroup.try(:root_pass) if hostgroup.try(:root_pass).present?
Setting[:root_pass]
return hostgroup.try(:root_pass) if hostgroup.try(:root_pass)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we removed present? here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed it because there was a present? and ignored the method where is used. On the second look, it should be there.

Hosts need passwords, especially the root one for the provisioning. With the bigger amounts of hosts, hostgroups are used for handle and easily modify these attributes for hosts. Root password has special place in this chain but it isn't to great as it looks. When you're change the hostgroup for host, the root password became intanct. It makes sence for host that has own password defined but not for host that use hostgroup's password. So this PR tries to fix it.
@domitea
Copy link
Contributor Author

domitea commented Nov 23, 2021

All looks green, so I think it's ready for merge. What do you think, @ezr-ondrej ?

Copy link
Member

@ezr-ondrej ezr-ondrej left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @domitea ! 👍

@ezr-ondrej ezr-ondrej dismissed ares’s stale review November 24, 2021 19:49

All concerns were addressed.

@ezr-ondrej ezr-ondrej merged commit 60d53c2 into theforeman:develop Nov 24, 2021
@ezr-ondrej
Copy link
Member

Thanks @ares and @lzap for the reviews! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
5 participants