Using associative containers (be it Perl hashes, C++ std::maps or YCP maps) may be certainly fun. Inserting/retrieving/changing values is efficient and easy, once we know the key. However, elements of every associative container are ordered, one way or the other, and one cannot really retrieve them all back in the order of insertion. Though it is certainly beneficial feature sometimes, it makes associative containers unusable in other cases, especially in those where the order matters.
Recently I had particularly excellent time with STL C++'s maps while fixing bug #439088 (a.k.a. "How to make hierarchical structure, which I cannot visualize, flat and keep the ordering of elements at the same time") ;-) :-D but that's not what I am going to write about. Now listen to the story of yast2-sudo:
openSUSE 10.2 was the first release that came with user-friendly way of creating/editing sudo rules in YaST. If you ever wondered what are these for, they enable 'normal' (non-root) users to run specified commands as root (or other user), without actually knowing his password. The rules are stored in /etc/sudoers file and ... yes, right, the order really matters there. What does it mean? As the user invokes sudo, sudoers file is searched for matching rules. If multiple rules happen to match our user, the last one of those is taken into account. Which may not, unfortunately, be also the best one ;-)
Consider the following example: on your machine, you want to enable Tux the Penguin (user tux) to install additional cool software from openSUSE repositories by running YaST software (sw_single) module, but you don't want disclose root password to him. Easy as that, you will add a new rule to sudoers file that will enable Tux to run yast sw_single as the user in root group (and we won't ask him for password, too) as follows:
Now, will this really do the job? As mentioned above, out of multiple matching rules, the last one is applied. Tux matches the first rule, but also the second one, which will be unfortunately used in this case ('ALL' is a catch-all alias for any user - the rule basically says that everybody can do anything, but needs to enter the target user's password in order to do so). So this does not really help us. We want to do something like this:
In other words, we want to place our rule to /etc/sudoers in such a way, that it will be the last matching one. And, last but not least, it would be also good if a configuration tool respected the order and didn't re-shuffle the rules whenever it sees fit, now wouldn't it?
yast2-sudo was the module that made me jump headlong into YaST development and, at the end, made a real YaSTie out of the shy girl I was before ;-) Writing it from scratch was the best way to understand how the components of YaST cooperate together (if you want to know more on this topic, here is some useful reading for you). However, it took almost two releases of openSUSE to discover the fundamental error I made in its design.
In YaST-speak, agent is the part of YaST module that grabs the configuration file, parses it and stuffs the data into some (more or less) reasonable data structure. As an inexperienced greenhorn, I wrote an agent in Perl that parses sudoers file. So far, so good. But as you might have already guessed, I used associative container (Perl hash, in this case) to hold sudoers data, not knowing that it will cause the rules to change their order, and mess everything up if the user is unlucky enough.
Now I had two options how to make yast2-sudo behave and preserve the order of sudo rules:
One more thing, though. I've added a small aid to user interface for you to help you prioritize your sudo rules:

Recently I had particularly excellent time with STL C++'s maps while fixing bug #439088 (a.k.a. "How to make hierarchical structure, which I cannot visualize, flat and keep the ordering of elements at the same time") ;-) :-D but that's not what I am going to write about. Now listen to the story of yast2-sudo:
Juggling with sudo rules
openSUSE 10.2 was the first release that came with user-friendly way of creating/editing sudo rules in YaST. If you ever wondered what are these for, they enable 'normal' (non-root) users to run specified commands as root (or other user), without actually knowing his password. The rules are stored in /etc/sudoers file and ... yes, right, the order really matters there. What does it mean? As the user invokes sudo, sudoers file is searched for matching rules. If multiple rules happen to match our user, the last one of those is taken into account. Which may not, unfortunately, be also the best one ;-)
Consider the following example: on your machine, you want to enable Tux the Penguin (user tux) to install additional cool software from openSUSE repositories by running YaST software (sw_single) module, but you don't want disclose root password to him. Easy as that, you will add a new rule to sudoers file that will enable Tux to run yast sw_single as the user in root group (and we won't ask him for password, too) as follows:
# don't try this at home, it will not do what you want :) tux ALL = (%root) NOPASSWD: /sbin/yast sw_single ALL ALL = (ALL) ALL
Now, will this really do the job? As mentioned above, out of multiple matching rules, the last one is applied. Tux matches the first rule, but also the second one, which will be unfortunately used in this case ('ALL' is a catch-all alias for any user - the rule basically says that everybody can do anything, but needs to enter the target user's password in order to do so). So this does not really help us. We want to do something like this:
# this is the right way ALL ALL = (ALL) ALL tux ALL = (%root) NOPASSWD: /sbin/yast sw_single
In other words, we want to place our rule to /etc/sudoers in such a way, that it will be the last matching one. And, last but not least, it would be also good if a configuration tool respected the order and didn't re-shuffle the rules whenever it sees fit, now wouldn't it?
Being an inexperienced greenhorn
yast2-sudo was the module that made me jump headlong into YaST development and, at the end, made a real YaSTie out of the shy girl I was before ;-) Writing it from scratch was the best way to understand how the components of YaST cooperate together (if you want to know more on this topic, here is some useful reading for you). However, it took almost two releases of openSUSE to discover the fundamental error I made in its design.
In YaST-speak, agent is the part of YaST module that grabs the configuration file, parses it and stuffs the data into some (more or less) reasonable data structure. As an inexperienced greenhorn, I wrote an agent in Perl that parses sudoers file. So far, so good. But as you might have already guessed, I used associative container (Perl hash, in this case) to hold sudoers data, not knowing that it will cause the rules to change their order, and mess everything up if the user is unlucky enough.
Cleaning up the mess
Now I had two options how to make yast2-sudo behave and preserve the order of sudo rules:
- Use Perl's tied hash in agent and make sure YCP preserves the order as well
- Store sudoers data in non-associative container and rewrite YCP stuff not to use maps, but lists
One more thing, though. I've added a small aid to user interface for you to help you prioritize your sudo rules:
- Mood:
drained


Comments
What I really miss in YCP-ui bindings (and libyui) is that one can't easily modify/replace one item at specific position in the table (and alike widgets), items can by replaced only all at once. That could be very pleasant side effect of implementing drag-and-drop feature ...