Uploaded image for project: 'CFEngine Community'
  1. CFEngine Community
  2. CFE-1915

Variables should not be able to be defined in a remote bundle

    XMLWordPrintable

    Details

    • Type: Bug
    • Status: Done
    • Priority: Blocker
    • Resolution: Fixed
    • Affects Version/s: 3.7.0, 3.11.0
    • Fix Version/s: 3.12.0
    • Component/s: None
    • Labels:

      Description

      Apparently you can set a variable in a remote bundle by naming the variable 'bundlename.variable'. The parser does not see this as an error. I saw this being abused in the wild.

      Here is an example.

      bundle agent example
      {
        methods:
          "" usebundle => one;
          "" usebundle => two;
      }
      
      bundle agent one
      {
        vars:
          "two.variable" string => "had it's value set from bundle '$(this.bundle)'";
      }
      
      bundle agent two
      {
        reports:
          "The variable 'variable' in bundle $(this.bundle) $(variable)";
      }
      

      Example Output:

      > $ cf-agent -KIf ./example.cf -b example                                                                                         
      2015-04-27T21:25:37-0500     info: Using command line specified bundlesequence
      R: The variable 'variable' in bundle two had it's value set from bundle 'one'
      

      Additionally I found that you can even override sys variables in this way.

      Policy Examples and alternative methodologies

      What variable values are defined in bundles one and two?
      bundle agent one
      {
        vars:
            "SpecialVar" string => "Value set inside the bundle.";
      
        reports:
            "I have defined $(this.bundle).SpecialVar as '$(SpecialVar)'";
      }
      
      bundle agent two 
      {
        vars:
            "First" string => "First value set inside the bundle.";
      
        reports:
            "I have defined $(this.bundle).First as '$(First)'";
      }
      

      Here is an example of how easily I can bring confusion.

      Sneaking in new variable values abusing BUG CFE-1951
      bundle agent main
      {
        methods:
            "one";
            "two";
            "sneaky" usebundle => sneak( "one", "SpecialVar", "Hamburglar was here!" );
            "sneaky" usebundle => sneak( "two", "Second", "Snuck in something extra" );
            "Bundlestate" usebundle => report( "one" );
            "Bundlestate" usebundle => report( "two" );
      
        reports:
          "CFEngine $(sys.cf_version)";
      }
      
      bundle agent one
      {
        vars:
            "SpecialVar" string => "Value set inside the bundle.";
      
        reports:
            "I have defined $(this.bundle).SpecialVar as '$(SpecialVar)'";
      }
      
      bundle agent two 
      {
        vars:
            "First" string => "First value set inside the bundle.";
      
        reports:
            "I have defined $(this.bundle).First as '$(First)'";
      }
      
      bundle agent sneak( B, variable, value )
      {
        vars:
          # Set the variable and value as they are passed in as params
            "$(B).$(variable)" string => "$(value)";
      }
      
      bundle agent report( name )
      {
        reports:
            "State of bundle $(name)$(const.n)$(with)"
              with => string_mustache( "{{%-top-}}", bundlestate($(name)) );
      }
      

      Here is the output from the above policy.

      R: I have defined one.SpecialVar as 'Value set inside the bundle.'
      R: I have defined two.First as 'First value set inside the bundle.'
      R: State of bundle one
      {
        "SpecialVar": "Hamburglar was here!"
      }
      R: State of bundle two
      {
        "First": "First value set inside the bundle.",
        "Second": "Snuck in something extra"
      }
      R: CFEngine 3.11.0
      

      Parameters, are the proper way to pass data from the outside into a bundle. At least then the bundle is publishing it's intent to accept directed input from outside itself.

      Here is a simple example that shows using parameters to set variables in a bundle.

      Example of a "valid" "Push" model using parameters exposed by the bundle itself
      bundle agent main
      {
          methods:
          # Set variablename to value inside bundle bucket
            "" usebundle => bucket("myvar", "myvalue");
            "" usebundle => bucket("var2", "val2");
            "" usebundle => bucket("myvar", "over-written");
      
          # Report the bundlestate of the bundle bucket
            "" usebundle => report("bucket");
      }
      
      bundle agent bucket( variable, value )
      {
        vars:
          # Set the variable and value as they are passed in as params
            "$(variable)" string => "$(value)";
      }
      
      bundle agent report( name )
      {
        reports:
            "State of bundle $(name)$(const.n)$(with)"
              with => string_mustache( "{{%-top-}}", bundlestate($(name)) );
      }
      

      And here is the output from that policy.

      R: State of bundle bucket
      {
        "myvar": "over-written",
        "var2": "val2"
      }
      

      Using data type vars as a parameter allows a lot of flexibility. You can standardize all your bundles to take structured data. Each bundle can look for different keys in the provided data. This parameter model lends itself nicely to automated bundle actuation.

      Snippet showing paramaterized dynamic bundle actuation
      vars:
        "bundles" slist => { "one", "two", "three" };
      
      methods:
        "$(bundlename)" usebundle => $(bundlename)( "@(data_for_$(bundlename))";
      

      Augments are another way to define classes and variables. Augments happen near the very beginning of the agent execution. Using augments you can define variables in the def bundle scope by placing a def.json next to the policy entry (/var/cfengine/inputs/def.json). Beginning in 3.12 you will be able to specify additional augments to be merged on top of the base augments.

      Example augments file
      {
          "classes":{
              "services_autorun": [ "any" ]
          },
      
          "vars":{
              "my_var": "defined in def.json",
              "my_other_var": "Defined ONLY in def.json"
          },
      
          "augments": [
              "/var/cfengine/augments/$(sys.flavor).json",
              "/var/cfengine/augments/$(sys.fqhost).json",
          ]
      }
      

      I think one of the more interesting patterns is what I call "Collecting" or "Distributed Definition". You can find and collect variables based on their name matching a regular expression or having various tags defined. This allows things to happen outside the bundle in contexts that make sense but the bundle itself retains autonomy and determines what to pull in. I am familiar with users who have replaced all abuse of that bug with this pattern.

      Example showing Distributed Definition/Collecting pattern
      bundle agent main
      {
        methods:
            "one";
            "two";
            "collect_and_report" usebundle => collect_and_report( "port" );
            "collect_and_report" usebundle => collect_and_report( "mytag" );
            "collect_and_report" usebundle => collect_and_report( "another_tag" );
      
        reports:
          "CFEngine $(sys.cf_version)";
      }
      
      bundle agent one
      {
        vars:
            "cfengine"
              string => "5308",
              meta => { "port" };
      
            "list"
              slist => { "one", "string", "list" },
              meta => { "mytag" };
      
            "d"
              data => '{ "msg": "Hello from $(this.bundle)" }',
              meta => { "another_tag" };
      }
      
      bundle agent two
      {
        vars:
            "apache"
              string => "80",
              meta => { "port" };
      
            "list"
              slist => { "one", "string", "list" },
              meta => { "mytag" };
      
            "d"
              data => '{ "msg": "Hello from $(this.bundle)" }',
              meta => { "another_tag" };
      }
      
      bundle agent collect_and_report( tag )
      {
        reports:
            "Vars tagged $(tag)$(const.n)$(with)"
              with => string_mustache( "{{%-top-}}", variablesmatching_as_data( ".*", $(tag)) );
      }
      

      Here is the output from the above policy.

      R: Vars tagged port
      {
        "default:one.cfengine": "5308",
        "default:two.apache": "80"
      }
      R: Vars tagged mytag
      {
        "default:one.list": [
          "one",
          "string",
          "list"
        ],
        "default:two.list": [
          "one",
          "string",
          "list"
        ]
      }
      R: Vars tagged another_tag
      {
        "default:one.d": {
          "msg": "Hello from one"
        },
        "default:two.d": {
          "msg": "Hello from two"
        }
      }
      R: CFEngine 3.11.0
      

        Attachments

          Issue Links

            Activity

              People

              Assignee:
              vpodzime Vratislav Podzimek
              Reporter:
              a10042 Nick Anderson
              Votes:
              0 Vote for this issue
              Watchers:
              10 Start watching this issue

                Dates

                Created:
                Updated:
                Resolved: