Skip Menu |
 

From: ghudson@mit.edu
To: rt@kerborg-prod-app-1.mit.edu
Date: Mon, 01 Apr 2024 12:58:36 -0400
Subject: profile write operation interactions with multiple files
The profile library supports profile objects containing multiple files.  This feature is most commonly used in the KDC and similar programs, which merge kdc.conf and krb5.conf into a single relation tree.  The file structure is preserved in the memory representation of a profile--that is, instead of parsing all of the files to generate an in-memory tree, each file is parsed separately, and query operations iterate over the trees to compose a list of relation values.

The profile library supports write operations such as profile_clear_relation() and profile_add_relation().  Modifications can be explicitly written to the backing file with profile_flush(), implicitly written during profile_release() (profile_abandon() can be used to omit this), or written to a different file with profile_flush_to_file().  Comments are lost and includes are flattened when changes are written.  The Solaris kclient(8) program is the main historical user of the profile write APIs that I am aware of; it uses these APIs to programmatically modify krb5.conf.  FreeRDP has introduced another use: to ephemerally modify the default profile for the purpose of changing the realm KDC setting for an operation.

Currently, if a profile is modified, modifications happen to the tree representing the first file in the profile.  Only that tree is marked as dirty and only that tree's modifications will be written out by a flush operation  This is fine for kclient, but it does not work well for the FreeRDP use case if the default profile doesn't contain exactly one file.  If there are no files, it crashes (ticket 9110, fix pending).  If there are multiple files, the following edge cases are likely true (though untested):

* profile_clear_relation() will not clear all values of a relation if some of the values don't come from the first file.
* profile_add_relation() will insert values at the end of the first file's values for the relation, but before any values appearing in other files. 
* profile_update_relation() will fail if the old value does not appear in the first file.
* profile_rename_section() will fail if the section does not appear in the first file, and will leave behind any identically named sections in the second or later file.
 
Some thoughts about the potential solutions here:

* The null solution is to document that profile write operations can only affect the first file.  This essentially leaves the FreeRDP use case broken for some (probably rare) configurations, as there would be no way to guarantee that the realm KDC values from the default profile are cleared and replaced.

* We could make profile_clear_relation() work using the final flag, and leave the other modification operations broken for multiple files.  This is closer to a comprehensive fix than it first seems, as pretty much any FreeRDP-like use case is likely to start with profile_clear_relation() to clear out any values of affected relations from the default profile.  The final flag currently only applies to sections, but we could make it apply to relations as well.  The relation and its containing sections may not appear in the first file, so profile_clear_relation() would have to develop additional logic to add a dummy relation and mark it deleted/final if there are values in subsequent files.  Although this solution requires a lot of special-case logic within profile_clear_relation(), it requires far fewer changes than any of the following ones.

* We could augment the tree data model to support the kind of overrides necessary to make all of the write operations work within the first file while semantically affecting the values across all files.  For instance, profile_rename_section() might need to place tags in the source and destination nodes of the first file's tree, indicating that subsequent files' trees should contain no values at the source and the source section's values at the destination.  The number of edge cases for this solution is pretty large, and the profile data model is already stretched pretty fine, but this option provides the cleanest semantics without changing any caller-visible aspects of the profile model.

* We could create an operation to flatten a multi-file profile into a single tree, rendering all of the write operations effective on data originating from any of the files.  This operation could be explicit, or could be implicit on rw_setup() or profile_copy().  Making it explicit requires attention from callers, who might not realize it is necessary to handle rare configurations.  Making it implicit on rw_setup() calls into question the implicit flush that happens in profile_release(), as the flattened profile would potentially have different contents than the original backing file.  Flattened profiles would also need to be marked NO_RELOAD, which might be problematic if flattening is implicit on profile_copy().
Date: Fri, 12 Apr 2024 17:47:49 +0200
Subject: Re: [krbdev.mit.edu #9117] [Comment] profile write operation interactions with multiple files
To: rt-comment@kerborg-prod-app-1.mit.edu
From: "Julien Rische" <jrische@redhat.com>
Download (untitled) / with headers
text/plain 2.7KiB
I just wanted to highlight the fact that for Fedora, CentOS, and RHEL,
we try to keep the content of the first krb5 configuration file
(/etc/krb5.conf) to the minimum (mainly libdefaults global
parameters)[1].

We start the file by including the /etc/krb5.conf.d configuration
folder. This allows us to have a more modular configuration system. It
is very convenient to delegate realm configuration and domain/realm
mapping to application maintainers. We also do so to delegate
permitted encryption types configuration to system-wide policy tools.

With the points above in mind, I have the following concerns about the
current limitations of the profile API for our case:

* About profile_add_relation(), the mention "at the end of the first
file's values for the relation, but before any values appearing in
other files" is confusing me. I was having the impression that
relations were added to the tree in the file parsing order. At least I
believe that if a value A is set for a certain parameter in a
sub-file, before the value B being set to the same parameter in the
first file, the list of resolved values will still be "A, B" (in this
order). So even if the trees of all files are not merged, it seems the
parsing order does still apply somehow.
* profile_clear_relation(), profile_update_relation(), and
profile_rename_section() will only have effect on the few default
settings from /etc/krb5.conf. In our case most of these relations are
single value parameters. If there are multiple values set for this
kind of parameters the first one is selected. This tends to confuse
users a lot, because it seems the norm for configuration files is the
variable assignment logic (the last assignment of a given parameter in
the parsing order is the one that is actually applied), which is the
complete opposite of the present approach. Hence if some
/etc/krb5.conf parameters are overridden in /etc/krb5.conf.d/*, these
3 functions won't have any effect on these parameters, since the
relations they will modify in /etc/krb5.conf are not the ones that are
resolved first.

If I got it right, the FreeRDP use case is to export a modified
version of the tree where the list of KDC addresses for a given realm
is replaced by another address. I believe the reason why they use such
an approach is because of the "*" marker limitation for relations in
subsections[2]. If this was supported, they could simply configure
KRB5_CONFIG with a generated file such as:

[realms]
EXAMPLE.COM = {
kdc* = kdc.example.com
}
include /etc/krb5.conf

This would completely override any /realms/EXAMPLE.COM/kdc relations,
while still applying the rest of the system configuration.

[1] https://src.fedoraproject.org/rpms/krb5/blob/rawhide/f/krb5.conf
[2] https://github.com/krb5/krb5/blob/krb5-1.21.2-final/src/util/profile/final2.ini#L1-L3
Subject: Re: [Comment] [krbdev.mit.edu #9117] profile write operation interactions with multiple files
From: "Ken Hornstein" <kenh@cmf.nrl.navy.mil>
To: rt-comment@kerborg-prod-app-1.mit.edu
Date: Fri, 12 Apr 2024 14:04:55 -0400
Show quoted text
>If I got it right, the FreeRDP use case is to export a modified
>version of the tree where the list of KDC addresses for a given realm
>is replaced by another address. I believe the reason why they use such
>an approach is because of the "*" marker limitation for relations in
>subsections[2].

Even if the "*" finalization marker was supported for subsection relations,
I believe that only works across files specified in the search path.
E.g., a KRB5_CONFIG file that specified "/etc/krb5-1.conf:/etc/krb5-2.conf",
it would work finalize a section in krb5-1.conf. But files included
using the "include" directive don't count as separate files for
this purpose. Sadly this makes finalization much less useful.

--Ken
profile include directives are handled at parse time, so the profile library's representation of a Fedora /etc/krb5.conf file is a single tree.  The concerns in this ticket only apply if you do something like KRB5_CONFIG=$HOME/krb5.conf:/etc/krb.conf so that the profile object contains two separate trees.

Unfortunately, we are not historically consistent about how to handle multiple assignments to a single-valued relation.  libkrb5 uses the first value, while libkadm5 uses the last one (presumably following the reasoning you gave about users' intuitions around variable assignment).  Changing either behavior to match the other risks breaking existing configurations.  In hindsight, I think this is a good argument against using repeated assignments to represent lists in a configuration language, but we can't do much about that now.

It is true that beefing up final-flag support would give FreeRDP a way to do what it wants without the profile write APIs, and to handle similar use cases around modifying the default configuration.  Perhaps that is a good variant of the null solution: document that modifying the default profile is discouraged, and instead encourage prepending an override tree to the default configuration.  I think we might need to provide an additional API to make that easy, as simply including /etc/krb5.conf would not be correct for all process environments.  One concern for this direction is that applications would be relying on all-new functionality, whereas the current FreeRDP approach *almost* works with existing krb5.