Skip to content
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

ncm-metaconfig: support running arbitrary commands in various steps #1451

Merged
merged 7 commits into from
Apr 13, 2021

Conversation

stdweird
Copy link
Member

No description provided.


my ($err, $out);
my %opts = (
shell => 1,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think allowing shell=>1 is a really bad idea as it allows all sorts of security risks to be introduced by template admins. I'm OK with arbitrary commands from the profile but arbitrary shell expansions and injection attacks is probably a step too far. :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gombasg wanted filecopy powers 😉

what do you propose: we try to split the string in a command and run it without shell => 1. you know any good libraries that do that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the key question is: do we allow more than one command? If not, then make it a list and pass that through. If we want to allow multiple commands... but why? I am trying to remember what other (good) use cases we have for arbitrary commands in file copy. Mostly it's restarting daemons with custom scripts, which tbh should all be moving to systemd so it's a legacy use case.

Validation seems a valid use case for an arbitrary command, but is there a use use for multiple commands to execute validation? Seems unlikely? Probably best to wait for Gabor to chime in tomorrow but restricting this to the identified use cases to avoid abuse seems valid here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ned21 see also #1377 (comment) and comments below

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, I've also found cases in our templates when errors from restarting/reloading the service needs to be suppressed, so we use service foo reload || true. A common reason is the initial build process, when the service is not expected to be operational yet, but we don't want the rest of the build process to fail. Changing every occurrence of restart to condrestart/try-restart could help in some cases, but systemd does not have try-reload. Hmm, if there was a component-level flag which could in turn tell CAF::Service to ignore all errors, then we could set that flag during the build...

Anyway, I'd like to repeat the idea of separating the definition of a command from triggering a command - @ned21, just like how startup actions work. So something like:

prefix "/software/components/metaconfig",

"commands/dconf_update" = "dconf update";

"services/{/etc/dconf/db/gdm.d/00-login-screen}" = dict(
     "contents", ...,
     "actions/post", "dconf_update",
);

This way, it would be more difficult to "hide" arbitrary commands in the noise, because all such commands would end up in a single block in the profile, suitable for reporting/auditing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just spotted this use of shell commands in our code base:

/usr/sbin/alternatives --set mta /usr/sbin/sendmail.sendmail; systemctl restart sendmail.service

But then with this code, I think we could still do that? The alternatives command would be run by the changed or post action, and then the restart would happen via the standard daemons attribute.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the daemons are only triggered by a change, you can also run the alternatives in the pre section.
when is it triggered now? only on change?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, on change only via file copy.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in that case, yes this would work (but only because you are lucky enough that last command is a daemon action).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but my point is that a lot of where we currently have shell "tricks" could be re-thought to be cleaner. But if we give people a $shell=>1 crutch they'll just port directly across from filecopy without trying to find a better way.

What we found with file copy is that once sites are using shell=1, there's now way to back out of it without risk of breakage. So it's much better to start with shell=0 and then only turn it on if we find a proper use case that can't be solved any other way.

@stdweird
Copy link
Member Author

@gombasg i tried to add it. the naming is now a total mess far beyond my capabilties

@stdweird stdweird force-pushed the metaconfig_arbitrary_code_fc_styke branch from f725f72 to bad2f07 Compare June 11, 2020 08:41
@@ -118,9 +130,26 @@ type ${project.artifactId}_config = {
'contents' : ${project.artifactId}_extension
@{Predefined conversions from EDG::WP4::CCM::TextRender}
'convert' ? ${project.artifactId}_textrender_convert
@{Actions (i.e. named registered commands) to run on pre, validation and/or post step.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actions (i.e. names found /software/components/metadata/commands) to run when processing the service. Refer to the ${project.artifactId}_actions type definition for the available hooks for when a command may be run.

} = dict();

type ${project.artifactId}_component = {
include structure_component
'services' : ${project.artifactId}_config{} with valid_absolute_file_paths(SELF)
@{Command registry for allowed actions, keys should be used as action value}
'commands' ? string{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to say something about how commands are executed. i.e. (depending on discussion below): The commands will be executed via CAF::Process without shell.


my ($err, $out);
my %opts = (
shell => 1,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd still vote for shell=>0. Looking at the docs, this should work just fine if we want the input to be strings?

my $cmd = CAF::Process->new(["ls -lh"], log => $self);

One of the reasons this structure is nice is that I can also nullify or replace any command I don't like with a central dict update:

'metaconfig/commands/not-for-me' = '/bin/true';

without having to loop through all services to find the right one.

I could potentially also do that conditionally using Aquilon's build status to e.g. avoid running commands that I know won't work during anaconda and but allowing them to run fine once we've left buildstatus=build.

(yeah, that's a bit ugly but potentially less so than the cmd||/bin/true hacks we have at the moment)

@ned21
Copy link
Contributor

ned21 commented Jun 12, 2020

This is a long thread so let me try summarise. I think we're pretty close to a conclusion:

  • I think we should use shell=0 rather than =1 because we can't disable it later.
  • I made a wording suggestion on the docs.
  • One of the points that Gabor made is that sometimes we don't care if a command fails (it's one of the drivers for shell syntax to allow ||/bin/true), or rather we don't want it to cause the component to "fail" and thus prevent any downstream dependencies from running. Should we support a flag, or a different hook since it's only applicable to changed and post, that is "run but don't error on failure"?

@stdweird
Copy link
Member Author

@ned21

  • i'll look for a splitter, if you know one, please do share
  • for gabor's request: we can do something systemd style, and the command that starts with a - can fail without error.

other question: can we force that all commands start with a absolute path to the executable? otherwsie, i have to invent my own path resolution too.

@stdweird
Copy link
Member Author

@ned21 i'll use use Text::ParseWords qw(old_shellwords) for the splitting (has el7 and el8 packages)

@ned21
Copy link
Contributor

ned21 commented Jun 12, 2020

i'll use use Text::ParseWords qw(old_shellwords) for the splitting (has el7 and el8 packages)

What about el6? :-( Do we need a splitter though? the CAF::Process doc says it accepts a string with shell=0. Or is that a mis-reading on my part?

for gabor's request: we can do something systemd style, and the command that starts with a - can fail without error.

I like that.

other question: can we force that all commands start with a absolute path to the executable? otherwsie, i have to invent my own path resolution too.

Yes, absolutely paths only.

@stdweird
Copy link
Member Author

@ned21 for el6, i find no packages, and some sites seem to suggest it is part of perl 5.10 itself. can you check that? and where did you read that about CAF?

@ned21
Copy link
Contributor

ned21 commented Jun 12, 2020

Can't find any trace of Text-Parsewords on an EL6 system. However I tested:

use CAF::Process;
my $proc = CAF::Process->new(["ls /"]);
my $output = $proc->output();
$proc->execute();

Shows the contents of /.

my $proc = CAF::Process->new(["ls /d*"]);
gives: ls: cannot access /d*: No such file or directory.

So I think this confirms it's not necessary.

@stdweird
Copy link
Member Author

@ned21 that splits on whitespace only, so no proper quote handling for args with space.
so perl -e 'use Text::ParseWords' fails on el6? what about repoquery --whatprovides "perl(Text::ParseWords)"

@gombasg
Copy link
Contributor

gombasg commented Jun 12, 2020 via email

@stdweird
Copy link
Member Author

@gombasg only when the maven config has the dependecies explicitly set. otherwise, it's the usual perl.req script that adds them (in the perl(A::B::C) form).
can you open an issue about the el8 problems in the maven repo?

@stdweird
Copy link
Member Author

@ned21 @gombasg i think i addressed the issues

@stdweird stdweird force-pushed the metaconfig_arbitrary_code_fc_styke branch from 85e443c to 5ca3913 Compare June 12, 2020 21:36
@ned21
Copy link
Contributor

ned21 commented Jun 13, 2020

ah, yep: perl -e 'use Text::ParseWords' was a good call. I was looking in /usr/lib64/perl5/Text not /usr/share/perl5/Text/

Copy link
Contributor

@ned21 ned21 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes. Just a few questions about docs, fixing some typos, etc.

type ${project.artifactId}_actions = {
@{Always run, happens before possible modifications}
'pre' ? string
@{Always run, happens before possible modifications, the file content is passed om stdin.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@{Always run, happens before possible modifications, the file content is passed om stdin.
@{Always run, happens before possible modifications, the new file content is passed on stdin.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@{Always run, happens before possible modifications, the file content is passed om stdin.
@{Always run before possible modifications with the new (or unchanged) file content is passed on stdin. A failure will cancel any file modification, unless the command is prefixed with -.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i find new (or unchanged) confusing. i would expect new/changed (or unchanged),but maybe i need some sleep ;)

this is actually the generated content, and this runs before anything is written. on failure, it just stops, writing to disk or even comparing with previous content is not attempted (i think, i would have to look at the code in FileWriter).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was unsure about "unchanged", if it clouds the situation then let's just go for:

Always run before possible modifications with the generated file content is passed on stdin. A failure will cancel any file modification, unless the command is prefixed with -.

ncm-metaconfig/src/main/perl/metaconfig.pm Outdated Show resolved Hide resolved
ncm-metaconfig/src/main/perl/metaconfig.pm Outdated Show resolved Hide resolved
ncm-metaconfig/src/main/perl/metaconfig.pm Outdated Show resolved Hide resolved
type ${project.artifactId}_actions = {
@{Always run, happens before possible modifications}
'pre' ? string
@{Always run, happens before possible modifications, the file content is passed om stdin.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@{Always run, happens before possible modifications, the file content is passed om stdin.
@{Always run before possible modifications with the new (or unchanged) file content is passed on stdin. A failure will cancel any file modification, unless the command is prefixed with -.

@@ -86,6 +86,18 @@ type ${project.artifactId}_textrender_convert = {

type caf_service_action = string with match(SELF, '^(restart|reload|stop_sleep_start)$');

type ${project.artifactId}_actions = {
@{Always run, happens before possible modifications}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe also add A failure will cancel any file modification, unless the command is prefixed with -. here (because it's the case)?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good idea.

@jrha jrha added this to the 21.3 milestone Oct 8, 2020
@stdweird stdweird force-pushed the metaconfig_arbitrary_code_fc_styke branch from 5ca3913 to c4a029d Compare October 28, 2020 13:57
Copy link
Contributor

@ned21 ned21 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I have missed something, I think my documentation suggestions are still pending?

@stdweird
Copy link
Member Author

@ned21 please check again, i think it's ok now

@stdweird
Copy link
Member Author

@ned21 changes pushed

ned21
ned21 previously approved these changes Nov 2, 2020
@stdweird stdweird force-pushed the metaconfig_arbitrary_code_fc_styke branch from 51f7b3f to 1a391bc Compare November 3, 2020 13:34
@stdweird
Copy link
Member Author

stdweird commented Nov 3, 2020

@jrha conflicts resolved

@jrha jrha requested a review from ned21 November 5, 2020 16:11
@ned21 ned21 modified the milestones: 21.6, 21.3 Mar 5, 2021
@ned21
Copy link
Contributor

ned21 commented Mar 30, 2021

Anything else that's needed here? We are really keen to have #1452 which depends on this.

@stdweird
Copy link
Member Author

@ned21 i'm happy with it 😉

@jrha jrha merged commit c0085ae into quattor:master Apr 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants