Manually managing groups on a large infrastructure can be a tedious task, and
is therefore best suited through automation software like CFEngine.
Unfortunately - at time of writing - CFEngine does not have any built-in
promise types for managing groups. But fear not; in CFEngine 3.17, custom
promise types were introduced. This new exhilarating feature does not only
allow for members of our community to make their own custom promise types, but
also lets the CFEngine Core developers prototype new future promise types. In
this blog post, I’d like to introduce one of our prototypes; the
experimental groups
promise type.
Getting started
Using CFEngine 3.18 LTS with the default policy set, it is quite easy to get
started using the groups
promise type. The first step consists
of adding the respective promise module and its support library to
/var/cfengine/masterfiles/modules/promises/
. The files you will need to copy
are the following:
groups.py
(Promise module)cfengine.py
(Library)
Next we’ll need to tell cf-agent
about our custom promise type, where to
find it, as well as what interpreter to use in order to execute it. This is
typically done through a promise type definition in
/var/cfengine/masterfiles/services/init.cf
. Open the respective file in your
favorite editor, and add the following lines:
promise agent groups
{
path => "$(sys.workdir)/modules/promises/groups.py";
interpreter => "/usr/bin/python3";
}
Awesome! We’re all set to start writing some groups promises.
The groups
promise type
Groups promises are promises about local groups on a host. They express
whether a group shall be present or absent; which users shall be included or
excluded as members of the group; as well as what GID the group shall have.
These properties are expressed through the attributes policy
, members
and
gid
respectively.
In the example bundle below we specify promises about three groups named
foo
, bar
and baz
.
bundle agent example
{
groups:
"foo"
policy => "present",
members => "$(foo.members)";
"bar"
members => "$(bar.members)",
gid => "1234";
"baz"
policy => "absent";
}
bundle agent foo
{
vars:
"members" string => '{ "include": ["alice", "bob"],
"exclude": ["malcom"] }';
}
bundle agent bar
{
vars:
"members" string => '{ "only": ["alice"] }';
}
In the first promise statement, we promise that a group named foo
shall be
present on the host and that its members shall include the users alice
and
bob
, but exclude the user malcom
.
Note that in the second promise statement, we do not specify the policy
attribute. Still we promise that a group named bar
shall be present. This is
due to the fact that the policy
attribute defaults to "present"
if not
specified.
Additionally, in the second promise statement, we promise that the members of
bar
shall only contain the user alice
, excluding all others. Please
note that the only
member-attribute cannot be used in combination with
include
or exclude
.
In the last promise statement, we promise that a group named baz
shall be
absent from the host, meaning that this groups shall not exist.
By now, you may have noticed that the member
attribute takes a JSON string.
And you’re probably be wondering why it does not take a member body as
illustrated below.
bundle agent example_2
{
groups:
"c_experts"
policy => "present",
members => c_expert_members;
}
body member c_expert_members
{
include => { "vpodzime", "olehermanse" };
exclude => { "larsewi" };
}
The reasoning behind this is that the use of bodies - unfortunately - are still not supported with custom promise types. But this is definitely something we will implement in the near future.
A complete example can be found here. By running this example, you can expect the following output:
$ /var/cfengine/bin/cf-agent -KIf /var/cfengine/masterfiles/services/groups.cf
info: User promise repaired
info: User promise repaired
info: User promise repaired
info: Created group 'foo'
info: Added user 'alice' to group 'foo'
info: Added user 'bob' to group 'foo'
info: Created group 'bar'
info: Members of group 'bar' set to only users ['alice']
Managing local groups with CMDB
Combining the groups
promise type with CMDB can lead to some
powerful use cases.
Lets say you have an issue with one of your hosts. And in order for the developers to debug the host, you would need to grant them temporary access using a local group.
With CMDB you can use Mission Portal to create a temp_members_debug
list variable, containing your most blessed developers:
In policy, a promise can add the users from this list as
members to the group debug
, if and only if the variable is present:
bundle agent add_temp_members_to_debug
{
groups:
"debug"
policy => "present",
members => '{ "include": ["$(data:variables.temp_members_debug)"] }',
if => isvariable("data:variables.temp_members_debug");
}
bundle agent __main__
{
methods:
"add_temp_members_to_debug";
}
Awesome, right? To expand on this, you could write policy to remove the users when the variable is removed from CMDB. Here is the output from running the example above:
$ cf-agent -KIf /var/cfengine/masterfiles/services/add_temp_members_to_debug.cf
info: Created group 'debug'
info: Added user 'vpodzime' to group 'debug'
info: Added user 'olehermanse' to group 'debug'
info: Added user 'larsewi' to group 'debug'
Share your thoughts
By now you know all there is to know about the groups
promise
type. If you choose to use this promise type, or if you have any tips on how
we can improve it; please let us know over at
GitHub Discussions, we are
eager to hear your feedback.