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
Conversation
Issues: #33811 |
b329002
to
12c39dc
Compare
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. |
0654815
to
8f323c9
Compare
app/models/host/managed.rb
Outdated
@@ -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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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. |
There was a problem hiding this 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:
- There's a mistake in the root_pass_check though
- 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. - the test that covers the HG change (as @lzap pointed out) should be added
- the test for root_pass_check with HG nesting should also be added
app/models/concerns/host_common.rb
Outdated
end | ||
|
||
def root_pass_check | ||
self[:root_pass].present? |
There was a problem hiding this comment.
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.
app/models/host/managed.rb
Outdated
@@ -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 |
There was a problem hiding this comment.
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.
b1fb5e2
to
7a9b12f
Compare
There was a problem hiding this 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.
app/models/host/managed.rb
Outdated
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? |
There was a problem hiding this comment.
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.
app/models/host/managed.rb
Outdated
@@ -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 |
There was a problem hiding this comment.
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?
app/models/concerns/host_common.rb
Outdated
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 |
There was a problem hiding this comment.
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.
app/models/hostgroup.rb
Outdated
return true if nested_root_pw | ||
self[:root_pass].present? |
There was a problem hiding this comment.
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]
app/models/concerns/host_common.rb
Outdated
|
||
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) |
There was a problem hiding this comment.
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.
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) |
app/models/hostgroup.rb
Outdated
@@ -251,6 +251,11 @@ def render_template(template:, **params) | |||
template.render(host: self, **params) | |||
end | |||
|
|||
def root_pass_check |
There was a problem hiding this comment.
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.
There was a problem hiding this 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
app/models/concerns/host_common.rb
Outdated
unencrypted_pass.starts_with?("$") | ||
end | ||
|
||
if is_actually_encrypted |
There was a problem hiding this comment.
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
There was a problem hiding this 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 👍
app/models/host/managed.rb
Outdated
@@ -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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
All looks green, so I think it's ready for merge. What do you think, @ezr-ondrej ? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @domitea ! 👍
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.