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
FIPS: replica install check #437
Conversation
b1eeace
to
1dacf22
Compare
| "Cannot install replica of a server of higher version ({}) than" | ||
| "the local version ({})".format(remote_version, api_version)) | ||
| raise ScriptError( | ||
| _("Cannot install replica of a server of higher version " |
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.
We don't use ugettext in installers, only in plugins
| if fips_mode != remote_fips_mode: | ||
| if fips_mode: | ||
| raise ScriptError( | ||
| _("Cannot join FIPS-enabled replica into existing topology: " |
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.
Same here
| "FIPS is not enabled on the master server.")) | ||
| else: | ||
| raise ScriptError( | ||
| _("Cannot join replica into existing FIPS-enabled topology: " |
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.
and here
|
I'm still afraid that users may want to create a FIPS replica from the non-FIPS master, even if it is not recommended due security. How can be this achieved? |
|
@MartinBasti Since this check is performed only during installation, the user could simply install non-FIPS replica and then turn FIPS on afterwards. There might be issues with this approach and thus it is neither recommended nor supported, as stated in the documentation. |
1dacf22
to
8b07c3b
Compare
|
@tomaskrizek on current versions of RHEL and fedora IPA doesn't start in FIPS, but upgrading first and then enabling FIPS might be the way |
ipalib/config.py
Outdated
| @@ -44,6 +44,7 @@ | |||
| from ipalib.constants import CONFIG_SECTION | |||
| from ipalib.constants import OVERRIDE_ERROR, SET_ERROR, DEL_ERROR | |||
| from ipalib import errors | |||
| from ipaplatform.tasks import tasks | |||
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.
Importing from ipaplatform to ipalib is forbidden, this will have to be worked around.
| raise ScriptError( | ||
| "Cannot install replica of a server of higher version " | ||
| "(%(remote_version)s) than the local version (%(api_version)s)" | ||
| % dict(remote_version=remote_version, api_version=api_version)) |
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.
NACK, .format() is the preferred way of formatting strings.
| @@ -1077,7 +1099,7 @@ def promote_check(installer): | |||
| remote_api.finalize() | |||
| installer._remote_api = remote_api | |||
|
|
|||
| check_remote_version(remote_api) | |||
| check_remote_compatibility(remote_api) | |||
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 check is executed only in replica promotion. There should be a check for classic replica install as well; IMO it should reject install when FIPS is enabled.
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.
Yes, that's what happens now with the is_fips_enabled() guards. Since FIPS is not available on DL0, it should fail.
ipalib/config.py
Outdated
| @@ -497,6 +498,10 @@ def _bootstrap(self, **overrides): | |||
| if 'plugins_on_demand' not in self: | |||
| self.plugins_on_demand = (self.context == 'cli') | |||
|
|
|||
| # Set fips_mode: | |||
| if 'fips_mode' not in self: | |||
| self.fips_mode = tasks.is_fips_enabled() | |||
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.
Do we want the user to be able to set env.fips_mode during api bootstrapping? I do not think so. This should be set amongst other run-time variables.
|
|
||
| # Check FIPS mode compatibility | ||
| remote_fips_mode = env['fips_mode'] | ||
| fips_mode = tasks.is_fips_enabled() |
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 think you could rather pass use api.env.fips_mode similar to what's done in the api_version.
| % dict(remote_version=remote_version, api_version=api_version)) | ||
|
|
||
| # Check FIPS mode compatibility | ||
| remote_fips_mode = env['fips_mode'] |
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.
env does not necessarily contain "fips_mode" key.
|
@tomaskrizek since you added a new key to the Env object, you will have to fix |
|
@MartinBasti I'm not sure from your comment if you would like to provide a way to change non-FIPS server into a FIPS server or just brainstorming ways how it can be worked around. In any case this path is not a goal and actually should be discouraged. http://www.freeipa.org/page/V4/FreeIPA-on-FIPS#Design |
|
@pvoborni more or less brainstorming, as I'm almost sure that people will want to migrate current deployments to FIPS mode |
8b07c3b
to
b2483d4
Compare
|
Thanks for the feedback. Hopefully I addressed all the concerns above in the update. |
ipalib/config.py
Outdated
| @@ -440,6 +444,9 @@ def _bootstrap(self, **overrides): | |||
| self.bin = path.dirname(self.script) | |||
| self.home = os.environ.get('HOME', None) | |||
|
|
|||
| if tasks is not None: | |||
| self.fips_mode = tasks.is_fips_enabled() | |||
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 needs an explanation in a comment.
| """ | ||
| env = client.forward(u'env', u'fips_mode')['result'] | ||
| remote_fips_mode = env.get('fips_mode', False) | ||
| fips_mode = tasks.is_fips_enabled() |
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 believe you left some mess here.
| Verify remote server's version is not higher than this server's version | ||
|
|
||
| :param client: RPC client | ||
|
|
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.
Please fill this empty line with the other parameter documentation. Don't leave a blank line before :raises, it's ugly and it's not usually done this way pythonhosted:Sphinx.
|
|
||
| :param client: RPC client | ||
|
|
||
| :raises: ``ScriptError`` if the checks fails |
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.
Should be :raises: ScripError: blahblah
| @@ -747,6 +786,10 @@ def install_check(installer): | |||
| ldap_uri=ldapuri) | |||
| remote_api.finalize() | |||
| installer._remote_api = remote_api | |||
|
|
|||
| with rpc_client(remote_api) as client: | |||
| check_remote_fips_mode(client, api.env.fips_mode) | |||
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 are you checking fips mode twice in DL1? Also, if this is supposed to address the issue "Explode if trying to install in FIPS on DL0", this won't work.
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.
The DL1 twice checking remark is false, thought this code was in common_check(). The other part still applies, though.
b2483d4
to
88f6d90
Compare
|
LGTM |
ipalib/constants.py
Outdated
| @@ -215,6 +215,7 @@ | |||
| ('script', object), # sys.argv[0] | |||
| ('bin', object), # The directory containing the script | |||
| ('home', object), # $HOME | |||
| ('fips_mode', object), # FIPS-mode of the server | |||
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.
Won't this cause api.env.fips_mode to be object rather than unset when ipaplatform is not installed?
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.
You're right. I didn't notice these settings are global, not just test-specific. I removed this bit and fixed the test instead.
88f6d90
to
bf8ee49
Compare
| if remote_version > api_version: | ||
| raise RuntimeError( | ||
| if remote_version > local_version: | ||
| raise ScriptError( |
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.
Note that ScriptError is really only necessary when you need the script to fail with a specific exit code. If you don't need that and are fine with exit code 1, RuntimeError is all you need.
ipatests/test_ipalib/test_config.py
Outdated
| @@ -563,7 +563,7 @@ def test_finalize_core(self): | |||
| # Test using DEFAULT_CONFIG: | |||
| defaults = dict(constants.DEFAULT_CONFIG) | |||
| (o, home) = self.finalize_core(None, **defaults) | |||
| assert list(o) == sorted(defaults) | |||
| assert set(o).issuperset(set(defaults)) | |||
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 would rather keep the original check, but make sure that o does not contain fips_mode before doing it.
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'm not sure how you'd like to make sure o doesn't contain fips_mode. Env object does not allow deletion and the only way I can think of would be to violate the object's "private" properties.
I propose we either:
- always set fips_mode: to
True,FalseorNone - accept that
Envcan now have optional attributes and use the test proposed above
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.
You can just add the fips_mode into ipalib/constants and set its value to object. We had to do this workaround when @tiran added nss_dir to the config (see https://github.com/freeipa/freeipa/blob/master/ipalib/constants.py#L228). The test in the original from will then just work(R).
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.
@martbab I tried to do that, but that also sets the fips_mode key for the env object, doesn't it? We want the fips_mode key to not be present if ipaplatform wasn't imported.
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 think this should work:
defaults = dict(constants.DEFAULT_CONFIG)
defaults['fips_mode'] = None
(o, home) = self.finalize_core(None, **defaults)
assert list(o) == sorted(defaults)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.
Ah, of course! fixed
3c816db
to
af915d1
Compare
144bbed
to
28c57e4
Compare
| @@ -738,6 +775,7 @@ def install_check(installer): | |||
| ldap_uri=ldapuri) | |||
| remote_api.finalize() | |||
| installer._remote_api = remote_api | |||
|
|
|||
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 was the "FIPS is not allowed in domain level 0" check removed?
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 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.
| @@ -562,6 +562,7 @@ def test_finalize_core(self): | |||
|
|
|||
| # Test using DEFAULT_CONFIG: | |||
| defaults = dict(constants.DEFAULT_CONFIG) | |||
| defaults['fips_mode'] = object | |||
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.
object. Huh?
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.
Yes, to skip the value check (few lines below - https://github.com/freeipa/freeipa/pull/437/files/28c57e48423e77756eb152068db44933cd0540f7#diff-c5fcc8900faeb561ad5f2a58fef94c85L568 ). If we set it to None, the test will check whether fips_mode value is None - which is not the desired behavior.
|
LGTM. |
|
3 LGTM + tests passing seems like a good enough reason for ACK to me. |
|
Needs rebase |
Variable fips_mode indicating whether machine is running in FIPS-enabled mode was added to env. https://fedorahosted.org/freeipa/ticket/5695
Add optional key fips_mode to Env object in tests. https://fedorahosted.org/freeipa/ticket/5695
Refactor function to use ScriptError exception and provide docstring.
Abstract creating rpc client into a context manager to allow re-use.
Check status of remote server's FIPS mode and proceed with installation only if it matches the current replica's FIPS mode. https://fedorahosted.org/freeipa/ticket/5695
28c57e4
to
33b0817
Compare
PR depends on the rest of the FIPS patches.