Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Discussion options

Greetings, (Long time lurker, first time poster/code writer)

I've recently attended a recent "Agent is in" episode and had some great off-episode commentary with Nick (and others). I'm to the point that I can readily understand and do on my own. So now, I need some code-specific help.

Problem: We're trying to revamp some NFTables tasks to get the following to happen, in sequence. (This gets so far and then doesn't execute any methods):

  1. Place files where we want them.
  2. If those promises end up replacing/updating files, set appropriate classes to run nft's check functionality.
  3. If the command promiser in item 2 returns without error, go to the appropriate method and do what we want re: start, restart, etc.

Nick's advice suggestion of a workaround was to use depends_on to influence normal ordering. My original attack on this was passing/referencing classes between bundles (or using namespace) was how I was going to do this -- but I'm still stuck regardless of method.

As you look at the code below, there are similar modules for things like ipset/iptables that I didn't include. So, if you see some different styles of code between the top "firewall(config) stanza and proceeding stuff, that would be why.

bundle agent firewall(config) {                                                                                                               
  meta:
    "purpose" string => "High-level promise bundle to abstract firewall policy application.";
  classes:
    "bundle__firewall_$(config)" expression => "any", scope => "namespace";
    "bundle__firewall" expression => "any", scope => "namespace";
     
  methods:
     
    el9::
      "Apply nftables config $(config)"
        comment => "Apply nftables firewall config $(config)",
        usebundle => nftables_main("$(config)");
}    
     
bundle agent nftables_main(config) {
     
  methods:
    "nftables_common" usebundle => nftables_common;
    "nftables_config" usebundle => nftables_config("$(config)");
    "nftables_check" usebundle => nftables_check;
    "nftables_actions" usebundle => nftables_actions;
}    

bundle common nftables_common {
     
  classes:
    "firewalld_active" expression => returnszero("/bin/systemctl -q is-active firewalld","useshell");
    "firewalld_enabled" expression => returnszero("/bin/systemctl -q is-enabled firewalld","useshell");
    "install_nftables" expression  => islessthan("$(nftables_count)", "1");
    "nftables_active" expression => returnszero("/bin/systemctl -q is-active nftables", "useshell");
    "nftables_enabled" expression => returnszero("/bin/systemctl -q is-enabled nftables", "useshell");
    "nftables_systemd_failed" expression => returnszero("/bin/systemctl -q is-failed nftables", "useshell");
    "nftables_reload_failed" expression => returnszero("/bin/systemctl status nftables | /bin/grep Process | /bin/grep -i fail", "useshell"); 
    "required_files_present" expression => "any", ifvarclass => filesexist(@(nft_file_require));
       
  vars:
      "f" string => "$(g.f)/firewall";
      "nftables_count" int => length(packagesmatching("nftables", ".*", ".*", ".*"));
      "nft_addin_path" string => "/etc/nftables.d/";
      "nft_addin_rules" string => "addin.nft";
       
      "nft_base_rules" slist => {                                                                                                                                                                                                                   
         "ipsets.nft",
         "standard.nft",
         "zz_deny.nft"
      };
    "nft_config" string => "nftables.conf";
    "nft_filepath" string => "/etc/nftables/";
    "nft_file_require" slist => {
       "/etc/sysconfig/nftables.conf",
       "/etc/nftables/ipsets.nft",
       "/etc/nftables/standard.nft",
       "/etc/nftables/zz_deny.nft"
      };
    "sysconfig_path" string => "/etc/sysconfig/";
}      
 

bundle agent nftables_config(config) {
     
  files:
     
    el9::                                                                                                                                     
     
     "$(nftables_common.nft_addin_path)."
       comment => "Create and ensure permissions on $(this.promiser)",
       perms => mog("0750", "root", "root"),
       create => "true",
       action => fix_and_log,
       classes => if_repaired("nft_restart_needed");
     
     "$(nftables_common.sysconfig_path)$(nftables_common.nft_config)"
       comment => "Service config for nftables",
       perms => mog("0600","root","root"),
       copy_from => secure_cp("$(nftables_common.f)/default/$(nftables_common.nft_config)", "@(g.policyhosts)"),
       action => fix_and_log,
       classes => if_repaired("nft_restart_needed");
     
     "$(nftables_common.nft_filepath)$(nftables_common.nft_base_rules)"
       comment => "Base firewall for nftables",
       perms => mog("0600","root","root"),
       copy_from => secure_cp("$(nftables_common.f)/default/$(nftables_common.nft_base_rules)", "@(g.policyhosts)"),
       action => fix_and_log,
       classes => if_repaired("nft_reload_needed");
     
     "$(nftables_common.nft_addin_path)$(nftables_common.nft_addin_rules)"
       comment => "Addin firewall rules for nftables",
       handle => "addin_file_copy",
       perms => mog("0600","root","root"),
       copy_from => secure_cp("$(nftables_common.f)/$(config)/$(nftables_common.nft_addin_rules)", "@(g.policyhosts)"),
       action => fix_and_log,
       classes => if_repaired("nft_reload_needed");
     
   packages:
     
     el9::
     
       install_nftables::
         "nftables"
            policy => "present",
            package_module => yum;
}   


bundle agent nftables_check {
 
   commands:
 
     el9.(nft_reload_needed|nft_restart_needed|nftables_reload_failed|nftables_systemd_failed)::
       "/usr/sbin/nft -c -f /etc/sysconfig/nftables.conf"
         contain => in_shell_and_silent,
         handle => "check_syntax",
         classes => results("namespace", "nft_syntax_ok");
 
 
   methods:
 
     el9.nft_syntax_ok_repaired::
       "nftables_actions" usebundle => nftables_actions,
       depends_on => { "check_syntax" };
 
}

bundle agent nftables_actions {
 
   methods:
 
     el9.firewalld_active::
          "firewalld" usebundle => standard_services("firewalld", "stop");
 
     el9.firewalld_enabled::
          "firewalld" usebundle => standard_services("firewalld", "disable");
 
     el9.!(nftables_enabled|nftables_active)::
         "nftables_start" usebundle => standard_services("nftables", "start");
         "nftables_enable" usebundle => standard_services("nftables", "enable");
 
     el9.nft_syntax_ok_repaired.(nft_restart_needed|nftables_systemd_failed)::
       "nftables_restart" usebundle => standard_services("nftables", "restart");
 
     el9.nft_syntax_ok_repaired.(nft_reload_needed|nftables_reload_failed)::
       "nftables_reload" usebundle => standard_services("nftables", "reload");
 
   reports:
 
     el9::
 
       "nft_syntax_ok_repaired is available and set"
          ifvarclass => "nft_syntax_ok_repaired";
 
       "nftables files installed; will attempt start on next run"
          ifvarclass => "nft_syntax_ok_repaired.!require_files_present";
 
       "nftables service successfully started or restarted with $(config)"
          ifvarclass => "nft_syntax_ok_repaired.(nft_reload_needed|nft_restart_needed)";
 
       "nftables service failed or syntax invalid"
          ifvarclass => "!nft_syntax_ok_repaired.(nftables_reload_failed|nftables_systemd_failed)";
}
You must be logged in to vote

Replies: 6 comments · 8 replies

Comment options

You must be logged in to vote
0 replies
Comment options

Thanks for the eyes @basvandervlies. The variable sysconfig_path in the common bundle has the trailing slash. So, this should enumerate to "/etc/sysconfig/nftables.conf" as writtten. This file is placed correctly at present with the variables from above:

"sysconfig_path" string => "/etc/sysconfig/";
"nft_config" string => "nftables.conf";

You must be logged in to vote
2 replies
@basvandervlies
Comment options

I missed that one :-(. What I usually do is that global classes are in capitals and bundle classes lowercase. Then it for clear for me and my colleagues to read. Is just I suggestion. But just to ask none of the bundle are executed?

Just be sure is el9 the right class? II suspect something redhat_9 or is el9 somthing you have defined?

@ayoustic
Comment options

Thanks for the style feedback there -- something we definitely need to look at.

  • 'el9'/'redhat_9' is defined as something that matches a custom-defined item. So, yes, that's valid.
  • As written, the methods in nftables_actions aren't firing off. However....the reports do in the same bundle -- therein lies my frustration at present.
Comment options

Nick's advice was to use depends_on to influence normal ordering. My original
attack on this was passing/referencing classes between bundles
(or using namespace) was how I was going to do this – but I'm still stuck
regardless of method.

Well, I dunno that I would exactly call it advice, but yes, you can use depends_on to influence the order of promises within a bundle. I don't think that it's a good idea to use depends_on to try and influence order across multiple bundles, but it can be a good way to avoid doing something if you want to avoid a promise based on another one being notkept.

As written, the methods in nftables_actions aren't firing off. However….the reports do in the same bundle – therein lies my frustration at
present.

Your sure the bundles are not running from looking at verbose logs?

I would check the verbose logs to see if the bundles are getting run or not. You could also add additional reports that are similar to your policy:

reports:
     el9.firewalld_active::
          `Should "firewalld" usebundle => standard_services("firewalld", "stop");`;
You must be logged in to vote
1 reply
@ayoustic
Comment options

Hi Nick,

  1. Quite right -- was a workaround, vs a suggestion. I'll edit my original text to reflect this.
  2. I see that the class guards are "not met" but I can't seem to digest why. I've enclosed a file of output that's been redactecd and cut down to what's relevant to this discussion.

cfengine.out.txt

Comment options

There is a lot to load into my internal parser there, let's focus on one specific promise.

Which is the first promise you are expecting to trigger that is not triggereing?

This one?

     el9.firewalld_active::
          "firewalld" usebundle => standard_services("firewalld", "stop");
You must be logged in to vote
3 replies
@ayoustic
Comment options

The crux: In bundle nftables_actions: As written, none of the method promisers fire off except for firewalld (it's enabled and/or running). At this point, I think it's down to the evaluation of the class guards. Hopefully the verbose output is useful in illustrating that. My brain hurts looking at debug/verbose :).

So, let's focus on:

el9.nft_syntax_ok_repaired.(nft_restart_needed|nftables_systemd_failed)::
       "nftables_restart" usebundle => standard_services("nftables", "restart");

@nickanderson
Comment options

You can add in reports for these contexts to see what is or isn't in context.

First, you can simply report under that same context, but you can add some additional reports to see what is or isn't in context

reports:
el9.nft_syntax_ok_repaired.(nft_restart_needed|nftables_systemd_failed)::
       `SHOULD nftables_restart" usebundle => standard_services("nftables", "restart");`;

el9::
  "el9 is defined";

nft_syntax_ok_repaired::
  "nft_syntax_ok_repaired is defined";
  
nft_restart_needed::
  "nft_restart_needed is defined";

nftables_systemd_failed::
  "nftables_systemd_failed is defined";

What do you see emitted?

@nickanderson
Comment options

@ayoustic As an Enterprise user, we can take this to https://support.northern.tech if you wish, just open a ticket and we can continue there. Else we can continue here (which possibly other people might appreciate).

Comment options

Thanks @nickanderson. Is it possible to work on this via a ticket and then update this post with the solution so as not to spam folks? (If we close this thread, can I append it later?)

You must be logged in to vote
1 reply
@nickanderson
Comment options

Yeah, pretty sure we can still comment on answered topics and stuff.

Comment options

Alright -- I found an interesting combination helped out here:

  • Per feedback, I went ahead and added reports to each bundle (in my current revision NOT posted) that just reported if "x" class was set or not.

    • After doing this, I saw that some reports would run ok like:

    "nft_syntax_ok_repaired is available and set" ifvarclass => "nft_syntax_ok_repaired";

    • Nothing else was getting ran though.
    • To that end, I noticed that "class guards not met" was in the debug output which was something I knew about but didn't know how to combat what looked like the classes being set, but not acted upon as anticipated
  • I changed how I did this by using the if operator:

"firewalld_service_stop" usebundle => standard_services("firewalld", "stop"), if => "el9.firewalld_active";

     "firewalld_service_disable" usebundle => standard_services("firewalld", "disable"),
	  if => "el9.firewalld_enabled";

     "nftables_service_start" usebundle => standard_services("nftables", "start"),
	  if => "el9.!firewalld_active.!nftables_active";

     "nftables_service_enable" usebundle => standard_services("nftables", "enable"),
	  if => "el9.!firewalld_enabled.!nftables_enabled";

   	 "nftables_restart" usebundle => standard_services("nftables", "restart"),
 	  if => "(nft_restart_needed|nftables_systemd_failed).el9.nft_syntax_ok_repaired";

     "nftables" usebundle => standard_services("nftables", "reload"),
     	  if => "(nft_reload_needed|nftables_reload_failed).el9.nft_syntax_ok_repaired";

`

  • At first, converting to if statements didn't seem to make a difference...
  • I went with trying to use only or two conditions instead of some of the complexity that you see above
  • I ended up moving around the ORDER of the conditionals....and that seem to have effects that resulted in me consulting this, again:

https://docs.cfengine.com/docs/3.24/reference-language-concepts-classes.html#operators-and-precedence

  • Take away: Normal ordering answered the question re: vars and classes. However, it didn't click for me that CFEngine (apparently) evaluates order of class guards/conditionals differently.
    • The docs tell you the precdence of operators/precedence
    • However, it doesn't imply that it works in the same way that normal ordering seems to -- you HAVE to do these evaluations in a specific order manually (vs the engine evaluating them)

I am probably wrong on something above. However, whatever I did with those two changes did the trick. I'm continuing to test all the scenarios we need for this to go to prod. I'll update if I have other information for those that may find this. Feedback welcome on my observations. (or fallacies)

You must be logged in to vote
1 reply
@craigcomstock
Comment options

Thanks for the detailed response here. This sounds like something that begs a simple example case and possibly some clarification in the docs.

By "order of conditionals" do you mean order of classes in a single expression or order of individual promises?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
🙏
Q&A
Labels
None yet
4 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.