-
Notifications
You must be signed in to change notification settings - Fork 357
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
edns: implement %certificate kickstart feature #6045
base: main
Are you sure you want to change the base?
edns: implement %certificate kickstart feature #6045
Conversation
753748f
to
97f8ddf
Compare
97f8ddf
to
e564a96
Compare
61de0f5
to
e564a96
Compare
/build-image --boot.iso |
Images built based on commit e564a96:
Download the images from the bottom of the job status page. |
[Unit] | ||
Description=Import of certificates added in initramfs stage of Anaconda via kickstart | ||
Before=NetworkManager.service | ||
Before=anaconda.target |
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 I understand it correctly, that there is a period of time, where certificates are not available in the installation environment - right after initramfs switchroot and before this import service is executed?
Is there an option to import the certificates to the stage2 environment even before switchroot to stage2 to ensure the provided certificates are available throughout the whole boot process?
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.
Interesting idea, I'll think about pushing from initramfs instead of pulling from root.
For just moving files I guess it should be fine, one possible problem is that for other potential actions, like running import tool as in rvykydal@51c67c5#diff-572b90e7fb275a5450a384189ddd4651c02f9ab6059576366cdc25e5cdbec5f9R9
this might be a bit more tricky (running in /sysroot chroot?).
But to be honest I don't know if it is usual / reasonable thing to do so I'll consult dracut people.
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'll create a follow-up issue for this. I think it would be better to handle it separately.
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.
OK
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.
Sharing my so far results.
scripts/handle-sshpw
Outdated
@@ -37,6 +37,7 @@ if not os.path.exists(ksfile): | |||
handler = makeVersion(VERSION) | |||
ksparser = KickstartParser(handler, missingIncludeIsFatal=False) | |||
ksparser.registerSection(NullSection(handler, sectionOpen="%addon")) | |||
ksparser.registerSection(NullSection(handler, sectionOpen="%certificate")) |
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 is this required?
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 is some leftover from my initial stage of implementation. It is actually not needed. I'll remove 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.
Updated
@Certificates.setter | ||
@emits_properties_changed | ||
def Certificates(self, certificates: List[Structure]): |
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.
If it is not a requirement for something I would rather avoid setter support for two main reasons:
- UI is not expected to be able to set these
- security (anyone can use the API to add another certificate to the system)
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, I agree.
Initially I added it mostly for testing :).
I didn't think it would be fundamentally more insecure. Maybe anyone can use ReadKickstart(string) API on the security module instead to do the same thing, but yes there seem to be no valid reason to provide setter API.
I'll make it read-only property.
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.
Done
class SectionDataListString(): | ||
def __init__(self, data_list): | ||
self._data_list = data_list | ||
|
||
def __str__(self): | ||
retval = [] | ||
for data_obj in self._data_list: | ||
if isinstance(data_obj, data): | ||
retval.append(data_obj.__str__()) | ||
return "".join(retval) |
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.
Is there a reason to hide this class like this? Usually we are creating classes as top level.
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 reason is I don't feel the class is not ready / safe to be reused. It feels very single-purpose to me. I'll think more about 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.
Maybe also better name would be SectionDataListStrWrapper
. And put it at top level in the file?
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.
Updated
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.
Looks great to me overall. Great job! I found just a few notes on such a big change.
Also, I would recommend squashing a few commits together which don't have useful change. As fixups or switch to constants from strings.
if not cert.dir: | ||
raise SecurityInstallationError( | ||
"Certificate destination is missing for {}".format(cert.filename) | ||
) |
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.
Shouldn't there be a default path for certificate given by the pykickstart? I guess that most of the certificates will go to one place so we don't have to force this on users?
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.
What would be the default path?
At this point where we are just dumping a file to the path it does not make sense to me to have any.
I think even the tooling for certificate import is moving from
- put the certificate to the directory (
/etc/pki/ca-trust/source/anchors/
) - run a tool -
update-ca-trust extract
to
- run
trust anchor <filename>
(https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/securing_networks/using-shared-system-certificates_securing-networks#managing-trusted-system-certificates_using-shared-system-certificates)
So I think we don't want to own any default path.
It might be different if/when we offer support to define where we should import the cert (like --category global
or --category edns
) but still ideally we would just pass the category to the import tool (trust
). Something I drafted here: rvykydal@51c67c5
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.
Sounds good, so we can follow their example. Do we want to support only this even now?
I mean do we want the path solution instead of using only the category
?
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.
@jkonecny12 Hmm. It may be worth considering removing the --dir
solution completely. Depends if we feel comfortable offering the API. I think the --dir
--name
API is not very big burden given it just dumps the file, no more commitments (any more complex actions/imports would be done via --category
). And it can provide some flexibility for clients when the things (paths, solutions) are not settled completely. If we offer only --category
, (implementing only --category dns
with the path and name of the cert baked in anaconda) at this point, adaptations to any changes of paths etc would require rebuild of Anaconda. Also it wouldn't be probably possible to configure the destination via anaconda configuration as it is applied already in initramfs.
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.
@abbra do you think it would be feasible to provide only --category dns
API by anaconda kickstart or should we rather offer also simple --dir
and --filename
? (Please see 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.
Given that we don't yet have a category support in trust anchor
, having simple --dir
and --filename
is handy.
dracut/parse-kickstart
Outdated
def process_certificates(handler): | ||
"""Import certificates defined in %certificate sections.""" | ||
for cert in handler.certificates: | ||
|
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 guess an excessive blank line?
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.
On purpose, feeling more readable to me, but I'll remove it for consistency.
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.
OK.
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.
Fixed.
# to anaconda environment after switchroot. | ||
|
||
# certificates dumped to the specified file are copied to root | ||
cp -rv /run/install/certificates/path/* / |
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.
Is the path
at the end really expected. It just feels like a leftover.
Also are we sure that this directory will exist in all the cases? I mean the service will fail if the path is missing, isn't 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.
Is the
path
at the end really expected. It just feels like a leftover.
It is on purpose, this subdirectory of /run/install/certificates/path
should contain certificates that should be transported to switched root just by copying to the place, ie certificates only specified by --dir
in kickstart. Other subdirectories can potentially contain certificates of other types, for example /run/install/certificates/category/global
would contain certificates with option --category global
as in rvykydal@51c67c5#diff-572b90e7fb275a5450a384189ddd4651c02f9ab6059576366cdc25e5cdbec5f9R9. It is not so clear without the --category patch.
In other words, to be future-proof I don't want to use the root of the transport directory (/run/install/certificates
just for copying everything in it to root.
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.
Also are we sure that this directory will exist in all the cases? I mean the service will fail if the path is missing, isn't it?
No it will not, good point, I'll perhaps modify copy to:
cp -rv /run/install/certificates/path/* / || true
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.
Updated
I am going to clean up the structure a bit, squash, rename to --dir and --filename from the beginning, etc. |
e564a96
to
ae12329
Compare
ae12329
to
3c054f2
Compare
3c054f2
to
01f977f
Compare
/kickstart-test --testtype smoke |
dracut/parse-kickstart
Outdated
log.error("Certificate destination is missing for %s", cert.filename) | ||
return | ||
|
||
dst_dir = os.path.join(root+dump_dir.lstrip('/')) |
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 not os.path.join(root, dump_dir.lstrip('/'))
?
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.
Fixed
[Unit] | ||
Description=Import of certificates added in initramfs stage of Anaconda via kickstart | ||
Before=NetworkManager.service | ||
Before=anaconda.target |
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.
OK
return | ||
|
||
for cert in self._certificates: | ||
log.debug("Importing certificate %s", cert) |
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.
Does this log also the content of the certificate itself? I wonder if it is not enough to log just the certificate directory/filename for readability of the log file.
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.
Good point, I'll update the message.
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.
Done
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.
Looks good to me otherwise the comment below and @jstodola notes. Great job!
@@ -403,6 +405,44 @@ def ksnet_to_dracut(args, lineno, net, bootdev=False): | |||
|
|||
return " ".join(line) | |||
|
|||
|
|||
def _dump_certificate(cert, root="/", dump_dir=None): |
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 missing a test for this code.
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.
Indeed, I'll add 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.
Done
01f977f
to
f160aed
Compare
f160aed
to
3cbdc2d
Compare
I'll do the final review when the tests are fixed. |
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.
Looks good to me, thanks! :)
Just a couple suggestions. :)
@@ -213,6 +213,16 @@ class SpecificationF(KickstartSpecification): | |||
"my_test_2": TestData2 | |||
} | |||
|
|||
class SpecificationG(KickstartSpecification): |
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 is a typo ? SpecificationG
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 just following the pattern naming the test classes Specification.
|
||
def run(self): | ||
for cert in self._certificates: | ||
log.debug("Importing certificate with filename: %s dir: %s", cert.filename, cert.dir) |
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 find this bit a little weird - the task seemingly should be importing the certificates but it seems to just iterate over them and & log the fact they exist ?
Even if this is correct (eq. the files do get auto-imported) I still suggest noting how this works in a docstring or commit message, to prevent confusion. :)
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 find this bit a little weird - the task seemingly should be importing the certificates but it seems to just iterate over them and & log the fact they exist ?
I think you are referring to an initial task skeleton commit, in a follow-up/implementation commit _dump_certificate
is called in the loop.
@@ -66,6 +66,7 @@ | |||
) |
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 there is a typo in the commit message:
The certificates imported in initramfs are already imported earlier a service.
It should be "by a service" or something like that I guess ?
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.
Updated
pyanaconda/startup_utils.py
Outdated
@@ -642,6 +643,13 @@ def initialize_security(): | |||
if not flags.automatedInstall: | |||
security_proxy.FingerprintAuthEnabled = True | |||
|
|||
# Import certificates from kickstart | |||
# Most probably they have already been imported in iniramfs stage |
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.
Maybe "in the initramfs stage" or even make this more verbose about when the certificates were imported and by what ? :)
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.
Updated
"""Dump the certificate into specified file and directory.""" | ||
dst_dir = join_paths(root, cert.dir) | ||
if not os.path.exists(dst_dir): | ||
log.debug("Path %s for certificate does not exist, creating.", dst_dir) |
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.
Maybe also log for which certificate this new folder is being created ? Eq. something like:
log.debug("Path %s for certificate %s does not exist, creating.", dst_dir, cert.filename)
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.
Updated
@@ -68,6 +73,12 @@ def run(self): | |||
|
|||
Dump the certificates into specified files and directories. | |||
""" | |||
if self._phase == INSTALLATION_PHASE_PREINSTALL: | |||
if self._payload_type != PAYLOAD_TYPE_DNF: | |||
log.debug("Not importing certificates in pre install for %s payload.", |
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.
Maybe "in pre install phase" for consistency ?
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.
Updated
3cbdc2d
to
c3f1356
Compare
Kickstart %certificate section is used. Submodule, data structures, parsing. Resolves: INSTALLER-4030 Patch modified by rvykydal.
Unlike the %packages section where the data of all sections are merged into single data object the %certificates section holds the per instance section data in a list. Related: INSTALLER-4030
Resolves: INSTALLER-4030
Related: INSTALLER-4030
The certificates imported in initramfs are already imported earlier by a service. Resolves: INSTALLER-4030
Resolved: INSTALLER-4030
Resolves: INSTALLER-4030
In case they are needed or processed by package scriptlets Resolves: INSTALLER-4030
Resolves: INSTALLER-4030
Resolves: INSTALLER-4030
Don't require it on parsing level, don't own a default. Resolves: INSTALLER-4030
Resolves: INSTALLER-4030
Also dump for transfer durig switchroot so that the certificates can be potentially imported early after switchroot by a service. Resolves: INSTALLER-4030
Resolves: INSTALLER-4030
c3f1356
to
ad50f7f
Compare
This implementation should provide ground for follow-up/final modifications based on how the certificate location and import process/tooling will be specified (https://issues.redhat.com/browse/INSTALLER-4030).
Depends on pykickstart support: pykickstart/pykickstart#517
For the review it can be useful to follow the order of the commits to see (the reasoning behind) some decisions better.
At this point, there is only an implementation just dumping the certificate to the specified location (
--filename
and--dir
options) at various stages:Regarding the use cases the initramfs will be used most often. (Kickstart fetched without network or via network using already available certs).
At the end there is a patch adapting to options renaming in pykickstart. (pykickstart/pykickstart#514 (review),
--name
->--filename
,--path
->--dir
).Currently it seems that at this point only dump of certificate will be needed but considering the options and future, potential import via a tool (ca-certificates, trust), there is possible API extension with
--category
option: https://github.com/rvykydal/anaconda/commits/edns-certs-stage2-w-initramfs-category/ and pykickstart rvykydal/pykickstart@54b2592TODO: