cfe_internal/enterprise/federation/federation.cf

Table of Contents

This policy file handles Federated Reporting setup and ongoing operations.

file bodies

control

Prototype: control

Implementation:

body file control
{
        namespace => "cfengine_enterprise_federation";
}

contain bodies

cfengine_enterprise_federation:cfpostgres_user

Prototype: cfengine_enterprise_federation:cfpostgres_user

Implementation:

body contain cfpostgres_user
{
  useshell   => "useshell";
  exec_owner => "cfpostgres";
  exec_group => "cfpostgres";
  chdir      => "/tmp";
  no_output  => "false";
}

cfengine_enterprise_federation:contain_transport_user

Prototype: cfengine_enterprise_federation:contain_transport_user

Implementation:

body contain contain_transport_user
{
    exec_owner => "$(cfengine_enterprise_federation:config.transport_user)";
    exec_group => "$(cfengine_enterprise_federation:config.transport_user)";
    chdir => "$(cfengine_enterprise_federation:config.transport_home)";
    useshell => "true";
}

classes bodies

cfengine_enterprise_federation:psql_wrapper_exit_codes

Prototype: cfengine_enterprise_federation:psql_wrapper_exit_codes

Implementation:

body classes psql_wrapper_exit_codes
{
  kept_returncodes => { "0" };
  repaired_returncodes => { "1" };
  failed_returncodes => { "2" };
}

file bodies

cfengine_enterprise_federation:control

Prototype: cfengine_enterprise_federation:control

Implementation:

body file control
{
        namespace => "default";
}

agent bundles

cfengine_enterprise_federation:config

Prototype: cfengine_enterprise_federation:config

Description: Read/parse config JSON, define variables and classes for use later

Implementation:

bundle agent config
{
  vars:
    enterprise_edition.(policy_server|am_policy_hub)::
      "federation_dir" string => "/opt/cfengine/federation";
      "bin_dir" string => "$(federation_dir)/bin";
      "path" string => "$(federation_dir)/cfapache/federation-config.json";
      "path_setup_status" string => "$(federation_dir)/cfapache/setup-status.json";

      # TODO: Instrument augments
      "dump_interval" -> {"ENT-4806"}
        int => "20",
        comment => "Dump data on the feeders every 20 minutes";

      # TODO: don't hard-code cftransport user
      "transport_user" -> { "ENT-4610" } string => "cftransport";
      "transport_home"
        string => "$(cfengine_enterprise_federation:config.federation_dir)/cftransport";

    config_present::
      "data" data => readjson( $(path) );

  classes:
    enterprise_edition.(policy_server|am_policy_hub)::
      "config_present"
        expression => fileexists( $(path) );
    config_present::
      "enabled" expression => or(strcmp("on", "$(data[target_state])"),
                                 strcmp("paused", "$(data[target_state])")),
        scope => "namespace";
      "am_off" expression => strcmp("off", "$(data[target_state])"),
        scope => "namespace";
      "am_on" expression => strcmp("on", "$(data[target_state])"),
        scope => "namespace";

      # _stdlib_path_exists_getenforce and paths.getenforce are defined by masterfiles/lib/paths.cf
      default:_stdlib_path_exists_getenforce::
        "selinux_enabled"
          expression => strcmp("Enforcing", execresult("$(paths.getenforce)", useshell)),
          scope => "namespace";


  vars:
    enabled::
      "role" string => "$(data[role])";
      "remotes" slist => getindices( @(data[remote_hubs]) );
      "login" string => ""; # default
      "login"
        string => "$(data[remote_hubs][$(remotes)][transport][ssh_user])@$(data[remote_hubs][$(remotes)][transport][ssh_host])",
            if => and(
                       # To ensure we are using a remote hub that's actually enabled
                       strcmp( "on", "$(data[remote_hubs][$(remotes)][target_state])" ),
                       # To ensure the remote we are pushing to actually needs the data (is a superhub)
                       strcmp( "superhub", "$(data[remote_hubs][$(remotes)][role])" ));

    am_superhub::
      # Public keys of enabled pushing feeders need to be trusted (on a superhub)
      "pubkey[$(remotes)]" string => "$(data[remote_hubs][$(remotes)][transport][ssh_pubkey])",
            if => and( strcmp( "on", "$(data[remote_hubs][$(remotes)][target_state])" ),
                       strcmp( "feeder", "$(data[remote_hubs][$(remotes)][role])" ),
                       strcmp( "push_over_rsync", "$(data[remote_hubs][$(remotes)][transport][mode])"));

    am_feeder::
      # Public key(s) of enabled pulling superhub(s) need(s) to be trusted (on a feeder)
      "pubkey[$(remotes)]" string => "$(data[remote_hubs][$(remotes)][transport][ssh_pubkey])",
            if => and( strcmp( "on", "$(data[remote_hubs][$(remotes)][target_state])" ),
                       strcmp( "superhub", "$(data[remote_hubs][$(remotes)][role])" ),
                       strcmp( "pull_over_rsync", "$(data[remote_hubs][$(remotes)][transport][mode])"));

    am_superhub|am_feeder::
      "pubkeys" slist => getvalues( pubkey );

      "fingerprint[$(data[remote_hubs][$(remotes)][transport][ssh_host])]"
        slist => string_split("$(data[remote_hubs][$(remotes)][transport][ssh_fingerprint])", "$(const.n)", "inf"),
        # To ensure we are using a remote hub that's enabled
        if => strcmp( "on", "$(data[remote_hubs][$(remotes)][target_state])" );
      "fingerprints" slist => maparray("$(this.k) $(this.v)", fingerprint);
      "feeder[$(remotes)]" string => "$(data[remote_hubs][$(remotes)][hostkey])",
        if => strcmp( "feeder", "$(data[remote_hubs][$(remotes)][role])" );

  classes:
    enabled::

      # Knowing if feeder or superhub is based on explicit setting of role in
      # path (federation-config.json)
      "am_feeder"
        expression => strcmp("feeder", "$(data[role])"),
        scope => "namespace";

      "am_superhub"
        expression => strcmp("superhub", "$(data[role])"),
        scope => "namespace";

      "am_pusher"
        and => {strcmp("superhub", "$(data[remote_hubs][$(remotes)][role])"),
                strcmp("on", "$(data[remote_hubs][$(remotes)][target_state])"),
                strcmp("push_over_rsync", "$(data[remote_hubs][$(remotes)][transport][mode])")},
        comment => "Has an enabled remote superhub with push as transport method, should run push transport",
        scope => "namespace";

      "am_puller"
        and => {"am_superhub",
                strcmp("on", "$(data[remote_hubs][$(remotes)][target_state])"),
                strcmp("pull_over_rsync", "$(data[remote_hubs][$(remotes)][transport][mode])")},
        comment => "Superhub with some enabled remote hub with pull as transport method, should run pull transport",
        scope => "namespace";

      "am_transporter"
        or => {"am_pusher", "am_puller"},
        scope => "namespace";

      "am_paused"
        expression => strcmp("paused", "$(data[target_state])"),
        scope => "namespace";

  # Note: in order to see these debugs you must either define the default DEBUG class
  # or the namespace prefixed class like:
  # cf-agent -KI -DDEBUG
  # or
  # cf-agent -KI -Dcfengine_enterprise_federation:DEBUG_config

  reports:
    enabled.(default:DEBUG|DEBUG_config)::
      "Federation enabled!";
    am_superhub.(default:DEBUG|DEBUG_config)::
      "I'm a superhub!";
    am_feeder.(default:DEBUG|DEBUG_config)::
      "I'm a feeder!";
    am_pusher.(default:DEBUG|DEBUG_config)::
      "I'm pushing dumps!";
    am_puller.(default:DEBUG|DEBUG_config)::
      "I'm pulling dumps!";
    am_transporter.(default:DEBUG|DEBUG_config)::
      "I'm a transporter!";
    am_paused.(default:DEBUG|DEBUG_config)::
      "I'm paused so won't do any import/dump";
}

cfengine_enterprise_federation:semanage_installed

Prototype: cfengine_enterprise_federation:semanage_installed

Description: Install semanage utility if selinux enabled and cfengine_mp_fr_dependencies_auto_install class is defined if not defined then only warn

Implementation:

bundle agent semanage_installed
{
  vars:
    "semanage_action"
      string => ifelse( "default:_stdlib_path_exists_semanage", "fix", "default:cfengine_mp_fr_dependencies_auto_install", "fix", "warn" ),
      comment => "We only want to use semanage if it's available, or if we have
                  indicated it's ok to install it automatically. This variable
                  is subsequently used by a commands and packages promises to
                  warn or fix based.";
    debian_6|debian_7|debian_8|ubuntu_12|ubuntu_14|ubuntu_16|rhel_5::
      "semanage_package" string => "policycoreutils";
    debian_9|debian_10|ubuntu_18|redhat_8|centos_8::
      "semanage_package" string => "policycoreutils-python-utils";
    redhat_6|centos_6|redhat_7|centos_7::
      "semanage_package" string => "policycoreutils-python";

  packages:
    debian|ubuntu::
      "$(semanage_package)"
        policy => "present",
        package_module => default:apt_get,
        action => default:policy ( $(semanage_action) );
    redhat::
      "$(semanage_package)"
        policy => "present",
        package_module => default:yum,
        action => default:policy ( $(semanage_action) );

  reports:
    default:DEBUG|DEBUG_semanage_installed::
      "paths.semanage = $(paths.semanage)";
    !default:_stdlib_path_exists_semanage.!default:cfengine_mp_fr_dependencies_auto_install::
      "semanage command is not available at $(paths.semanage). Will only install needed package if cfengine_mp_fr_dependencies_auto_install class is defined in augments(def.json) or with --define cf-agent option.";
}

cfengine_enterprise_federation:transport_user

Prototype: cfengine_enterprise_federation:transport_user

Description: Manage transport user and permissions for remote SSH access

Implementation:

bundle agent transport_user
{
  vars:
      "user"
        string => "$(cfengine_enterprise_federation:config.transport_user)";
      "home"
        string => "$(cfengine_enterprise_federation:config.transport_home)";
      "ssh_key_name" string => "id_FR";
      "ssh_priv_key" string => "$(home)/.ssh/$(ssh_key_name)";
      "ssh_pub_key" string => "$(ssh_priv_key).pub";
      "ssh_auth_keys" string => "$(home)/.ssh/authorized_keys";
      "ssh_known_hosts" string => "$(home)/.ssh/known_hosts";
      "ssh_config" string => "$(home)/.ssh/config";
      "create_files"
        slist => {
          "$(home)/.",
          "$(home)/.ssh/.",
          "$(home)/source/.",      # Dumps from feeders are taken from here
          "$(home)/destination/.", # And dropped here on superhub
          "$(ssh_auth_keys)",
          "$(ssh_known_hosts)",
          "$(ssh_config)"
        };

  classes:
    enabled.selinux_enabled::
        "incorrect_ssh_context"
          expression => not( or(
                               regcmp(".*[\s:]ssh_home_t[\s:].*",
                                      execresult("ls -Z $(home) | grep .ssh",
                                                 useshell)),
                               regcmp(".*[\s:]ssh_home_t[\s:].*",
                                      execresult("ls -Z $(ssh_auth_keys)",
                                                 useshell)),
                               regcmp(".*[\s:]ssh_home_t[\s:].*",
                                      execresult("ls -Z $(ssh_priv_key)",
                                                 useshell)),
                               regcmp(".*[\s:]ssh_home_t[\s:].*",
                                      execresult("ls -Z $(ssh_pub_key)",
                                                 useshell)),
                               regcmp(".*[\s:]ssh_home_t[\s:].*",
                                      execresult("ls -Z $(ssh_config)",
                                                 useshell))));
  users:
    "$(user)"
      policy => "present",
      home_dir => "$(home)";
  files:
    "$(create_files)"
      create => "true";
    "$(home)/."
      depth_search => default:recurse_with_base("inf"),
      file_select => default:all,
      perms => default:mog( "600", $(user), "root" );
    "$(ssh_auth_keys)"
      create => "true",
      handle => "ssh_auth_keys_configured",
      edit_template_string => "}$(const.n)",
      template_data => @(cfengine_enterprise_federation:config.pubkeys),
      template_method => "inline_mustache";
    "$(ssh_known_hosts)"
      create => "true",
      handle => "ssh_known_hosts_configured",
      edit_template_string => "}$(const.n)",
      template_data => @(cfengine_enterprise_federation:config.fingerprints),
      template_method => "inline_mustache",
      if => isvariable("cfengine_enterprise_federation:config.fingerprints");
    "$(ssh_config)"
      create => "true",
      handle => "ssh_config_configured",
      edit_line => default:insert_lines("IdentityFile $(ssh_priv_key)");

  methods:
    selinux_enabled::
      "semanage_installed" usebundle => semanage_installed;

  commands:
    # _stdlib_path_exists_<command> and paths.<command> are defined is masterfiles/lib/paths.cf
    selinux_enabled.incorrect_ssh_context.default:_stdlib_path_exists_semanage.default:_stdlib_path_exists_restorecon::
      "$(paths.semanage) fcontext -a -t ssh_home_t '$(home)/.ssh(/.*)?'";
      "$(paths.restorecon) -R -F $(home)/.ssh/";

    any::
      # Generate ssh keypair
      "/usr/bin/ssh-keygen"
        handle => "ssh_keys_configured",
        args => "-N '' -f $(ssh_priv_key)",
        if => and( isdir( "$(home)/.ssh" ),
                   not( fileexists( "$(ssh_priv_key)" )));

  reports:
    selinux_enabled.incorrect_ssh_context.!default:_stdlib_path_exists_semanage::
      "need to fix incorrect ssh context for transport user but semanage path in $(sys.libdir)/paths.cf ($paths.semanage) does not resolve";
    selinux_enabled.incorrect_ssh_context.!default:_stdlib_path_exists_restorecon)::
      "need to fix incorrect ssh context for transport user but restorecon path in $(sys.libdir)/paths.cf ($paths.restorecon) does not resolve";
}

cfengine_enterprise_federation:clean_when_off

Prototype: cfengine_enterprise_federation:clean_when_off

Description: cleanup changes made for federated reporting on a feeder NOTE: a superhub turned off by removing federation-config.json or setting federation_manage_files will always run regardless of off or not so as to be prepared for enablement via Mission Portal UI

Implementation:

bundle agent clean_when_off
{
  vars:
    "user" string => "$(cfengine_enterprise_federation:transport_user.user)";
    "home" string => "$(cfengine_enterprise_federation:transport_user.home)";
@if minimum_version(3.15)
    "remote_hubs_table_row_count" string => execresult(`$(sys.bindir)/psql cfsettings --quiet --tuples-only --command "SELECT COUNT(*) FROM remote_hubs" 2>/dev/null`, useshell);
    "federated_reporting_settings_table_row_count" string => execresult(`$(sys.bindir)/psql cfsettings --quiet --tuples-only --command "SELECT COUNT(*) FROM federated_reporting_settings" 2>/dev/null`, useshell);
@endif

  users:
    "$(user)"
      policy => "absent";

  files:
      "$(cfengine_enterprise_federation:config.path_setup_status)" -> { "ENT-7233" }
        comment => "We must remove this file for Mission Portal to understand that the federation is not configured",
        delete => default:tidy;
      "$(cfengine_enterprise_federation:config.path)" -> { "ENT-7969" }
         comment => "We must remove this file for Mission Portal to understand that the federation is not configured",
         delete => default:tidy;

  methods:
    "rm_rf_cftransport_home_dir" usebundle => default:rm_rf("$(home)");

  classes:
    selinux_enabled.default:_stdlib_path_exists_semanage::
      "has_cftransport_fcontext" expression => returnszero("$(paths.semanage) fcontext -l | grep $(home)", "useshell");


  commands:
      # Oh, the humanity! Where for art thou databases: promises for psql!
      `$(sys.bindir)/psql cfsettings --quiet --command "TRUNCATE TABLE remote_hubs"` -> { "ENT-7233" }
        if => isgreaterthan( "$(remote_hubs_table_row_count)", "0" );
      `$(sys.bindir)/psql cfsettings --quiet --command "TRUNCATE TABLE federated_reporting_settings"` -> { "ENT-7233" }
        if => isgreaterthan( "$(federated_reporting_settings_table_row_count)", "0" );

      # _stdlib_path_exists_<command> and paths.<command> are defined in masterfiles/lib/paths.cf
    selinux_enabled.default:_stdlib_path_exists_semanage.has_cftransport_fcontext::
      "$(paths.semanage) fcontext -d '$(home)/.ssh(/.*)?'";

}

cfengine_enterprise_federation:inventory_refresh_cmd

Prototype: cfengine_enterprise_federation:inventory_refresh_cmd

Implementation:

bundle agent inventory_refresh_cmd
{
  vars:
    "cmd" data => parsejson('{"inventory_refresh_cmd":
                                "$(sys.workdir)/httpd/php/bin/php $(cfe_internal_hub_vars.docroot)/index.php cli_tasks inventory_refresh"}');
}

cfengine_enterprise_federation:federation_manage_files

Prototype: cfengine_enterprise_federation:federation_manage_files

Description: Manage files, directories and permissions in $(cfengine_enterprise_federation:config.federation_dir)

By default the import process will not prohibit the inclusion of duplicate hostkey data from feeders. By defining the cfengine_mp_fr_handle_duplicate_hostkeys class in augments a step will be performed during import which will find the which feeder's data is most recent for each duplicate hostkey and use that data. Duplicate hostkey data will be moved to a dup schema for analysis.

This class only applies to superhubs.

{
  "classes": {
    "cfengine_mp_fr_handle_duplicate_hostkeys": [ "any::" ]
  }
}

Implementation:

bundle agent federation_manage_files
{

  vars:
    "transport_user"
      string => "$(cfengine_enterprise_federation:config.transport_user)";
    "login" data => parsejson('{"login":"$(cfengine_enterprise_federation:config.login)"}');
    "feeder_username" data => parsejson('{"feeder_username":"$(cfengine_enterprise_federation:config.transport_user)"}');
    "feeder" data => parsejson('{"feeder": "$(sys.key_digest)"}');
    "cf_version" data => parsejson('{"cf_version":"$(sys.cf_version)"}');
    "handle_duplicates_value" string => ifelse("cfengine_mp_fr_handle_duplicate_hostkeys", "yes", "no");
    "handle_duplicates" data => parsejson('{"handle_duplicates":"$(handle_duplicates_value)"}');

  methods:
      "internal_vars" usebundle => "default:cfe_internal_hub_vars",
        comment => "We need the path to the inventory refresh PHP script before we use it.";
      "inventory_refresh_cmd" usebundle => "cfengine_enterprise_federation:inventory_refresh_cmd",
        comment => "Needs to be in a separate bundle so that it gets evaluated *after* cfe_internal_hub_vars.";

  files:
    enterprise_edition.(policy_server|am_policy_hub)::
      # Both cfpache and $(transport_user) need permission so adding o+x here
      "$(cfengine_enterprise_federation:config.federation_dir)/."
        create => "true",
        perms => default:mog( "661", "root", "root" );
      "$(cfengine_enterprise_federation:config.federation_dir)/cfapache/."
        create => "true",
        perms => default:mog( "600", "cfapache", "root" );
      "$(cfengine_enterprise_federation:config.federation_dir)/cfapache/."
        depth_search => default:recurse_with_base("inf"),
        file_select => default:all,
        perms => default:mog( "600", "cfapache", "root" );
    enabled::
      "$(cfengine_enterprise_federation:config.bin_dir)/."
        create => "true",
        perms => default:mog( "660", "root", "$(transport_user)" );
    am_superhub::
      "$(cfengine_enterprise_federation:config.federation_dir)/superhub/."
        create => "true",
        perms => default:mog( "770", "root", "$(transport_user)" );
      "$(cfengine_enterprise_federation:config.federation_dir)/superhub/import/."
        create => "true",
        perms => default:mog( "600", "root", "root" );
      "$(cfengine_enterprise_federation:config.federation_dir)/superhub/import/filters/."
        create => "true",
        perms => default:mog( "600", "root", "root" );
    am_feeder::
      "$(cfengine_enterprise_federation:config.federation_dir)/fedhub/."
        create => "true",
        perms => default:mog( "660", "root", "$(transport_user)" );
      "$(cfengine_enterprise_federation:config.federation_dir)/fedhub/dump/."
        create => "true",
        perms => default:mog( "660", "root", "$(transport_user)" );
      "$(cfengine_enterprise_federation:config.federation_dir)/fedhub/transport/."
        create => "true",
        perms => default:mog( "660", "root", "$(transport_user)" );
      "$(cfengine_enterprise_federation:config.federation_dir)/fedhub/dump/filters/."
        create => "true",
        perms => default:mog( "600", "root", "root" );

    am_feeder|am_transporter|am_superhub::
      # TODO: Instrument augments
      "$(cfengine_enterprise_federation:config.bin_dir)/config.sh"
        create => "true",
        template_method => "mustache",
        edit_template => "$(this.promise_dirname)/../../../templates/federated_reporting/config.sh.mustache",
        template_data => mergedata(@(login),
                                   @(feeder_username),
                                   @(feeder),
                                   @(cf_version),
                                   @(handle_duplicates),
                                   @(cfengine_enterprise_federation:inventory_refresh_cmd.cmd)),
        perms => default:mog( "640", "root", "$(transport_user)" );

      # TODO: Instrument augments
      "$(cfengine_enterprise_federation:config.bin_dir)/log.sh"
        create => "true",
        template_method => "mustache",
        edit_template => "$(this.promise_dirname)/../../../templates/federated_reporting/log.sh.mustache",
        perms => default:mog( "640", "root", "$(transport_user)" );

      "$(cfengine_enterprise_federation:config.bin_dir)/parallel.sh"
        copy_from => default:local_dcp( "$(this.promise_dirname)/../../../templates/federated_reporting/parallel.sh" ),
        perms => default:mog( "640", "root", "$(transport_user)" );

      "$(cfengine_enterprise_federation:config.bin_dir)/psql_wrapper.sh" -> { "ENT-4792"}
        create => "true",
        edit_template => "$(this.promise_dirname)/../../../templates/federated_reporting/psql_wrapper.sh.mustache",
        template_method => "mustache",
        perms => default:mog( "700", "root", "root" );

    am_feeder::
      "$(cfengine_enterprise_federation:config.bin_dir)/dump.sh"
        copy_from => default:local_dcp( "$(this.promise_dirname)/../../../templates/federated_reporting/dump.sh" ),
        perms => default:mog( "700", "root", "root" );

      "$(cfengine_enterprise_federation:config.federation_dir)/fedhub/dump/filters/50-merge_inserts.awk"
        copy_from => default:local_dcp( "$(this.promise_dirname)/../../../templates/federated_reporting/50-merge_inserts.awk" ),
        perms => default:mog( "600", "root", "root" );

    am_transporter::
      "$(cfengine_enterprise_federation:config.bin_dir)/transport.sh"
        copy_from => default:local_dcp( "$(this.promise_dirname)/../../../templates/federated_reporting/transport.sh" ),
        perms => default:mog( "500", "$(transport_user)", "root" );

    am_puller::
      "$(cfengine_enterprise_federation:config.bin_dir)/pull_dumps_from.sh"
        copy_from => default:local_dcp( "$(this.promise_dirname)/../../../templates/federated_reporting/pull_dumps_from.sh" ),
        perms => default:mog( "500", "$(transport_user)", "root" );

    am_superhub::
      "$(cfengine_enterprise_federation:config.bin_dir)/import.sh"
        copy_from => default:local_dcp( "$(this.promise_dirname)/../../../templates/federated_reporting/import.sh" ),
        perms => default:mog( "700", "root", "root" );

      "$(cfengine_enterprise_federation:config.bin_dir)/import_file.sh"
        copy_from => default:local_dcp( "$(this.promise_dirname)/../../../templates/federated_reporting/import_file.sh" ),
        perms => default:mog( "700", "root", "root" );

      "$(cfengine_enterprise_federation:config.federation_dir)/superhub/import/filters/10-base_filter.sed"
        copy_from => default:local_dcp( "$(this.promise_dirname)/../../../templates/federated_reporting/10-base_filter.sed" ),
        perms => default:mog( "600", "root", "root" );
}

cfengine_enterprise_federation:postgres_config

Prototype: cfengine_enterprise_federation:postgres_config

Description: Customize postgres config for superhub

Implementation:

bundle agent postgres_config
{
  vars:
    am_superhub::
      "c[shared_buffers]" -> { "ENT-8617" }
        string => ifelse( isvariable( "cfengine_enterprise_federation:postgres_config.shared_buffers"),
                          $(cfengine_enterprise_federation:postgres_config.shared_buffers),
                          "1GB"),
        comment => "Changing this setting requires restarting the database.";
      "c[max_locks_per_transaction]" -> { "ENT-8617" }
        string => ifelse( isvariable( "cfengine_enterprise_federation:postgres_config.max_locks_per_transaction"),
                          $(cfengine_enterprise_federation:postgres_config.max_locks_per_transaction),
                          "4000"),
        comment => "Changing this setting requires restarting the database.";
      "c[log_lock_waits]" -> { "ENT-8617" }
        string => ifelse( isvariable( "cfengine_enterprise_federation:postgres_config.log_lock_waits"),
                          $(cfengine_enterprise_federation:postgres_config.log_lock_waits),
                          "on"),
        comment => "Changing this setting requires restarting the database.";

      "c[max_wal_size]" -> { "ENT-8617" }
        string => ifelse( isvariable( "cfengine_enterprise_federation:postgres_config.max_wal_size"),
                          $(cfengine_enterprise_federation:postgres_config.max_wal_size),
                          "1GB");

      "c[checkpoint_timeout]" -> { "ENT-8617" }
        string => ifelse( isvariable( "cfengine_enterprise_federation:postgres_config.checkpoint_timeout"),
                          $(cfengine_enterprise_federation:postgres_config.checkpoint_timeout),
                          "5min");

  files:
    am_superhub::
      "$(sys.statedir)/pg/data/postgresql.conf"
        edit_line => default:set_line_based( "$(this.namespace):$(this.bundle).c",
                                     "=",
                                     "\s*=\s*",
                                     ".*",
                                     ""),
        classes => default:results( "bundle", "postgresql_conf" );

  commands:
    am_superhub.postgresql_conf_repaired.!systemd::
      # smart mode tries to wait for operations to finish and clients to
      # disconnect, fast mode terminates open connections gracefully
      "$(sys.bindir)/pg_ctl --pgdata $(sys.statedir)/pg/data --log /var/log/postgresql.log --wait --mode smart restart ||
       $(sys.bindir)/pg_ctl --pgdata $(sys.statedir)/pg/data --log /var/log/postgresql.log --wait --mode fast  restart"
        contain => cfpostgres_user;

  services:
    am_superhub.postgresql_conf_repaired.systemd::
      "cf-postgres"
        service_method => default:standard_services,
        service_policy => "restart";
}

cfengine_enterprise_federation:exported_data

Prototype: cfengine_enterprise_federation:exported_data

Description: Run script to dump pg data on feeder hub

Implementation:

bundle agent exported_data
{
  methods:
    am_feeder.!am_paused::
      "Refresh Inventory"
        usebundle => "default:cfe_internal_refresh_inventory_view",
        handle => "fr_inventory_refresh",
        comment => "Use standard inventory refresh so that we don't run it twice";

  commands:
    am_feeder.!am_paused::
      "/bin/bash"
        arglist => {"$(cfengine_enterprise_federation:config.bin_dir)/dump.sh"},
        contain => default:in_shell,
        depends_on => { "fr_inventory_refresh" },
        comment => "Refresh Inventory must be completed before dumping data";
}

cfengine_enterprise_federation:data_transport

Prototype: cfengine_enterprise_federation:data_transport

Description: Run script to transport data from feeder to superhub

Implementation:

bundle agent data_transport
{
  vars:
    am_puller.!am_paused::
      # local copies of the variables to make using them below sane
      "remotes" slist => {@(cfengine_enterprise_federation:config.remotes)};
      "data"    data  => @(cfengine_enterprise_federation:config.data);

      "enabled_pull_hosts[$(remotes)]"
        string => "$(data[remote_hubs][$(remotes)][transport][ssh_host])",
        if => and(strcmp("on", "$(data[remote_hubs][$(remotes)][target_state])"),
                  strcmp("pull_over_rsync", "$(data[remote_hubs][$(remotes)][transport][mode])"));

      "pull_args" -> {"ENT-4499"}
        string => join(" ", getvalues(@(enabled_pull_hosts)));

  commands:
    am_pusher.!am_paused::
      "/bin/bash"
        arglist => {"$(cfengine_enterprise_federation:config.bin_dir)/transport.sh push"},
        contain => contain_transport_user;

    am_puller.!am_paused::
      "/bin/bash"
        arglist => {"$(cfengine_enterprise_federation:config.bin_dir)/transport.sh pull $(pull_args)"},
        contain => contain_transport_user;
}

cfengine_enterprise_federation:imported_data

Prototype: cfengine_enterprise_federation:imported_data

Description: Run script to import dumps on superhub

Implementation:

bundle agent imported_data
{
  commands:
    am_superhub.!am_paused::
      "/bin/bash"
        arglist => {"$(cfengine_enterprise_federation:config.bin_dir)/import.sh"},
        contain => default:in_shell;
}

cfengine_enterprise_federation:superhub_schema

Prototype: cfengine_enterprise_federation:superhub_schema

Description: Run SQL script to ensure schema is migrated to superhub partitioned tables architecture

Implementation:

bundle agent superhub_schema
{
  commands:
    am_superhub::
      "$(cfengine_enterprise_federation:config.bin_dir)/psql_wrapper.sh"
        arglist => {
                     "cfdb",
                     `"select superhub_schema('$(sys.key_digest)');"`,
                   },
        classes => psql_wrapper_exit_codes;
}

cfengine_enterprise_federation:ensure_feeders

Prototype: cfengine_enterprise_federation:ensure_feeders

Description: Run SQL function to ensure that all configured feeder hubs are in __hubs table

Implementation:

bundle agent ensure_feeders
{
  vars:
    am_superhub::
      "feeders" slist => getvalues( "cfengine_enterprise_federation:config.feeder");
      "feeders_arg" string => concat( "ARRAY['", join( "', '", feeders ), "']");

  commands:
    am_superhub::
      "$(cfengine_enterprise_federation:config.bin_dir)/psql_wrapper.sh"
        arglist => {
                     "cfdb",
                     `"select ensure_feeders($(feeders_arg));"`
                   },
        classes => psql_wrapper_exit_codes,
        if => isgreaterthan(length(feeders), 0);
}

cfengine_enterprise_federation:entry

Prototype: cfengine_enterprise_federation:entry

Description: Conditionally runs all federated reporting bundles

Implementation:

bundle agent entry
{
  meta:
    (policy_server|am_policy_hub).enterprise_edition::
      "tags" -> { "ENT-4383" }
        slist => { "enterprise_maintenance" };
  classes:
    enterprise_edition.(policy_server|am_policy_hub)::
      "config_exists"
        expression => fileexists("$(cfengine_enterprise_federation:config.federation_dir)/cfapache/federation-config.json");
    enterprise_edition.(policy_server|am_policy_hub)::
      "config_not_exists"
        expression => not(fileexists("$(cfengine_enterprise_federation:config.federation_dir)/cfapache/federation-config.json"));

  methods:
    config_exists::
      "CFEngine Enterprise Federation Configuration"
        handle => "config",
        usebundle => config;
    am_policy_hub.(am_off|config_not_exists)::
      "CFEngine Enterprise Federation Transport Off"
        handle => "clean_when_off",
        usebundle => clean_when_off;
    enabled.am_on::
      "CFEngine Enterprise Federation Transport User"
        handle => "transport_user",
        usebundle => transport_user;
    enterprise_edition.(policy_server|am_policy_hub)::
      "federation_manage_files"
        handle => "federation_manage_files",
        usebundle => federation_manage_files;
    enabled.am_on::
      "CFEngine Enterprise Federation Postgres Configuration"
        handle => "postgres_config",
        usebundle => postgres_config;
      "CFEngine Enterprise Federation Schema Migration"
        handle => "superhub_schema",
        depends_on => { "postgres_config" },
        usebundle => superhub_schema;
      "CFEngine Enterprise Federation Ensure Feeder Hubs in Database"
        handle => "ensure_feeders",
        depends_on => { "superhub_schema" },
        usebundle => ensure_feeders;
      "CFEngine Enterprise Federation Feeder Data Transport"
        handle => "data_transport",
        depends_on => { "transport_user" },
        usebundle => data_transport;
      "CFEngine Enterprise Federation Feeder Data Import"
        handle => "imported_data",
        depends_on => { "transport_user", "ensure_feeders" },
        usebundle => imported_data;
      "CFEngine Enterprise Federation Feeder Data Export"
        usebundle => exported_data,
        action => default:if_elapsed($(cfengine_enterprise_federation:config.dump_interval));
      "Configuration Status"
        usebundle => setup_status;
  reports:
    !enterprise_edition::
      "Federated reporting is only available in CFEngine Enterprise.";
    enterprise_edition.!(policy_server|am_policy_hub)::
      "Federated reporting is only available on the policy server / hub.";
}

cfengine_enterprise_federation:setup_status

Prototype: cfengine_enterprise_federation:setup_status

Implementation:

bundle agent setup_status
{
  vars:
    "role" string => "$(cfengine_enterprise_federation:config.role)";
    "ssh_pub_key"
      string => readfile( "$(cfengine_enterprise_federation:transport_user.ssh_pub_key)" ),
      if => fileexists( "$(cfengine_enterprise_federation:transport_user.ssh_pub_key)" );
    "ssh_server_fingerprint"
      # ssh-keyscan is used because it's more reliable/easy than trying to
      # parse sshd config to find the file and then readfile():
      string => execresult("ssh-keyscan localhost 2>/dev/null | sed 's/localhost //g' | sort", useshell);
  classes:
    "superhub_setup_status_complete"
      expression => "any",
      depends_on => {
                      "config",
                      "transport_user",
                      "postgres_config", # We are depending on a deep guard within this bundle
                      "federation_manage_files",
      };

  files:
    superhub_setup_status_complete::
      "$(cfengine_enterprise_federation:config.path_setup_status)"
        create => "true",
        perms => default:mog( "600", "cfapache", "root" ),
        template_method => "inline_mustache",
        edit_template_string => "$(const.n)",
        template_data => '{
          "configured": true,
          "role": "$(role)",
          "hostkey": "$(sys.key_digest)",
          "transport_ssh_public_key": "$(ssh_pub_key)",
          "transport_ssh_server_fingerprint": "$(ssh_server_fingerprint)",
        }',
        if => isvariable( ssh_pub_key );
}

main

Prototype: __main__

Description: You can run this policy file from shell without specifying bundle

Implementation:

bundle agent __main__
{
  methods:
    "entry" usebundle => cfengine_enterprise_federation:entry;
}