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

fb_networkmanager: new cookbook to manage NetworkManager #127

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

jaymzh
Copy link
Collaborator

@jaymzh jaymzh commented May 13, 2020

This cookbook aims to manage NetworkManager in the most controlled
way possible. It might-could-should be sent over to CPE cookbooks,
I can redirect over there if preferred.

While, much to my dismay, some people do run NM on servers, and this
will work for that, it was built to handle this client machine case.

NetworkManager treats its config files as databases and can change them,
so they can't be handled simply as templates - more finesse is needed.

However, write-once doesn't provide the level of management that
FB-API-style cookbooks expect. This marries the two. All desired
settings are managed 100%, and NetworkManager is allowed to make
additive settings (such as "seen-bssids" or "password" [sic]) so that it
can continue to function for users.

It is immune to the format changes that NM can make - spacing and
ordering changes will not cause the file to be updated any more than
ignorable settings. When updating the file, care is taken to stick
close to NM's style.

I considered making blacklist/whitelists of fields that NM could
modify, but in practice since we merge in the user's config, the user's
config is effectively a blacklist, and whitelist would likely be far
too fragile.

It has full docs, including on it's Migration Feature which will be
useful for anyone who had their own Chef to do this but wants to
migrate.

It also comes with pretty good test coverage.

Copy link
Member

@davide125 davide125 left a comment

Choose a reason for hiding this comment

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

I'm fine with having this in the repo, but I'd like @michel-slm to review it as well.

cookbooks/fb_networkmanager/attributes/default.rb Outdated Show resolved Hide resolved
cookbooks/fb_networkmanager/metadata.rb Show resolved Hide resolved
cookbooks/fb_networkmanager/attributes/default.rb Outdated Show resolved Hide resolved
cookbooks/fb_networkmanager/libraries/default.rb Outdated Show resolved Hide resolved
cookbooks/fb_networkmanager/libraries/resource.rb Outdated Show resolved Hide resolved
cookbooks/fb_networkmanager/recipes/default.rb Outdated Show resolved Hide resolved
cookbooks/fb_networkmanager/spec/default_spec.rb Outdated Show resolved Hide resolved
cookbooks/fb_networkmanager/spec/resource_spec.rb Outdated Show resolved Hide resolved
@michel-slm
Copy link
Contributor

Also adding @jgoguen who writes the original CPE network manager cookbook.

@michel-slm
Copy link
Contributor

re: testing on Fedora - that can probably wait until after this review is done, and we then try switching to it and send further PRs to fix any issue we find.

content.each_pair do |sect, opts|
doc.section(sect, :option_sep => '=') do |section|
opts.each_pair do |opt, val|
v = [val].flatten.join(',')
Copy link
Contributor

Choose a reason for hiding this comment

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

NetworkManager connection files (but not NetworkManager.conf, because consistency!) use semicolons for multi-value separators, and requires a trailing semicolon for multi-value fields. Using a semicolon in a value makes IniParse wrap the value in quotes, which breaks NM parsing the file.

When I wrote a NetworkManager cookbook, I ended up having to manually generate the not-quite-INI text myself. You can imagine how impressed I was (still am) about having to do that when NM could have just used the proper INI format.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Can you give me an example to test? I had eap=peap; and dropped it to eap=peap and it worked fine.

Copy link
Collaborator Author

@jaymzh jaymzh May 14, 2020

Choose a reason for hiding this comment

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

@jgoguen I don't think the trailing semicolon is necessary, and here's some data:

[phil@ldx-vulturus ~]$ sudo grep ^eap /etc/NetworkManager/system-connections/fb_networkmanager_vicarious_wifi_hq
eap=peap
[phil@ldx-vulturus ~]$ nmcli c show 6f1f4851-15a3-321f-beec-fbfc873c9f85 | grep 802-1x.eap
802-1x.eap:                             peap

vs

root@ldx-vulturus:~# sudo grep ^eap /etc/NetworkManager/system-connections/fb_networkmanager_vicarious_wifi_hq
eap=peap;
root@ldx-vulturus:~# nmcli c reload
root@ldx-vulturus:~# nmcli c show 6f1f4851-15a3-321f-beec-fbfc873c9f85 | grep 802-1x.eap
802-1x.eap:                             peap

They're identical.

Copy link
Contributor

Choose a reason for hiding this comment

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

Interesting, what version of NetworkManager? I have 1.20 and even for connections manually created fresh it's still forcing semicolon-separate lists with a trailing semicolon.

I have 1.22 on another laptop, I can check what it does later tonight.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

1.10.6

Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder then if this INI-incompatible change is the new format. I created new connections on both 1.20 and 1.22, both forced trailing semicolons for multi-value fiends. It would work if I remove the trailing semicolon, if I restart NetworkManager itself not just reload connections, but as soon as anything modified the connection file it put the trailing semicolons back.

Where it uses semicolons as list item separators, I wonder if IniParse would see the first value and then discard the rest treating it as a comment…

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

certainly it does like to add them back. I just didn't find any problem to not having them.

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'm going to assume there was something borked on my system. So as long as this can output semicolon-delimited lists for the connection files and comma-delimited lists for NetworkManager.conf it's good.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It doesn't today. I'll add that and tests. Thanks!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

oh huh, I didn't get to this... I'll come back to this


# we don't test to make sure IniParse does what IniParse does,
# instead we teste the things we're doing custom...
context '#to_ini' do
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a test for array fields. If you call FB::NetworkManager.to_ini on this hash:

{
  'connection' => {
    'permissions' => %w{user:jgoguen:},
  },
  '802-1x' => {
    'identity' => 'jgoguen',
    'eap' => %w{tls},
  },
}

I expect this to be the generated config file:

[connection]
permissions=user:jgoguen:;

[802-1x]
identity=jgoguen
eap=tls;

permissions and eap are two fields that NetworkManager requires be multi-value, including the trailing semicolon after the last value.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

define doesn't work? it doesn't complain for sure, and it connects...

Copy link
Contributor

Choose a reason for hiding this comment

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

In NM 1.20 and 1.22, what I found in my testing is that if I remove trailing semicolons I consistently have to restart NM for it to work with the changed connection file, but adding a trailing semicolon (which NM will do itself when it tries to update a multi-value field) works just by reloading the connections. So if we write out a connection file that removes a trailing semicolon, based on my experience we'll need to restart the entire NetworkManager daemon instead of just reloading the configs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I will experiment with this.

Comment on lines +123 to +151
let(:from_contents) do
<<~EOS
[oldsection]
key=value

[section1]
existing=stuff
optionA=previous_value

[section2]
blue=red
EOS
end
Copy link
Contributor

Choose a reason for hiding this comment

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

Add another test case where there's multi-value fields in the contents. This content:

[oldsection]
key=value
foo=bar;baz;

[section1]
existing=stuff
optionA=previous_value
optionB=red;orange;yellow;

must generate this hash:

{
  'oldsection' => {
    'key' => 'value',
    'foo' => [
      'bar',
      'baz',
    ],
  },
  'section1' => {
    'existing' => 'stuff',
    'optionA' => 'previous_value',
    'optionB' => [
      'red',
      'orange',
      'yellow',
    ],
  },
}

cookbooks/fb_networkmanager/libraries/resource.rb Outdated Show resolved Hide resolved
Comment on lines 22 to 44
vpnhome = '/var/lib/openvpn/chroot'
FB::Users.initialize_group(node, 'nm-openvpn')
node.default['fb_users']['users']['nm-openvpn'] = {
'gid' => 'nm-openvpn',
# If /var/lib/openvpn doesn't exist yet, we can't create it in time
# but we want to create the user, so for the first run, create it
# with /tmp
'home' => ::File.directory?(vpnhome) ? vpnhome : '/tmp',
'shell' => '/usr/sbin/nologin',
'action' => :add,
}

packages = %w{
network-manager
openvpn
network-manager-openvpn-gnome
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it assured we definitely want OpenVPN always configured? I'd prefer making VPN optional and allow choosing either/both of at least OpenVPN and openconnect.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This doesn't force anyone to use openvpn. It just happens to setup openvpn only because I didn't have a test-case for openconnect. It's not mutually-exclusive afaik?

Copy link
Member

Choose a reason for hiding this comment

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

Ah, good catch @jgoguen. Yes, we should gate this behind an attribute and make it optional. VPN setup is totally fine here, but we shouldn't do it by default, and we should write in a way that can support multiple backends (even if you only implement openvpn for now).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm just installing the right packages, you can make NM use whatever backened you want in the connection. We can install all the backends. This doesn't configure any VPNs... it just makes it available.

Copy link
Contributor

Choose a reason for hiding this comment

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

But if I'm never going to use OpenVPN, I need openconnect, there's no point to having it installed. If we gate it behind an attribute we can allow administrators to better control what gets installed.

As soon as we're going to have multiple VPN backends installed by Chef (plus any sanity work, like making sure the user is created) it may be better if we instead have a series of includes:

include_recipe 'fb_networkmanager::openvpn
include_recipe 'fb_networkmanager::openconnect

Each one can have an early return based on something like node['fb_networkmanager']['enable'] && node['fb_networkmanager'][vpntype]['enable']

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, yes you're right. It would need to be a set of resources (or a resource called multiple times with different actions, which just no).

@davide125 any strong opinion? I'd still like to have some gate for these but not strongly enough to demand that much extra work and testing for little gain.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You have a gate, manage_packages. :)

Copy link
Member

Choose a reason for hiding this comment

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

I'm not convinced we should be installing openvpn (and openconnect or vpnc or whatever for that matter) itself here; a bunch of these will happily drop off system services, and can have interesting side effects if not setup properly. I think the ideal state would be that we have fb_openvpn / fb_openconnect / etc to manage them properly, and that fb_networkmanager just uses their API and takes care of the n-m specific bits (in this case, the user and the plugin package). I do realize this is a fuckton of work though, and it doesn't make sense to block this PR on it.

How about this: we move the openvpn stuff to a fb_networkmanager::openvpn, and we just don't include it in the default recipe. In the README, we document that if you want openvpn you should include. This is akin to how we handle some of the more corner cases in fb_systemd iirc.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

except that doesn't make sense for fb_networkmanater to use an fb_openvpn API, because it's not configuring it statically. NetworkManager internally uses those things, but it needs the packages installed.

We can certainly make extra recipes for them and that's fine, but it's literally Just The Packages, to be clear.

Copy link
Member

Choose a reason for hiding this comment

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

I still contend we should split it out. If we had a fb_openvpn, the way I see it fb_networkmanager::openvpn would just include fb_openvpn::packages and then install its thing. For now, I think it'd be fine to install both openvpn and the other package directly in a fb_networkmanager::openvpn. N-M can support like 5 or 6 different type of VPNs, some with fairly complex and invasive dependencies. We shouldn't force the user to have them all installed by default.

This cookbook aims to manage NetworkManager in the most controlled
way possible. It might-could-should be sent over to CPE cookbooks,
I can redirect over there if preferred.

While, much to my dismay, some people do run NM on servers, and this
will work for that, it was built to handle this client machine case.

NetworkManager treats its config files as databases and can change them,
so they can't be handled simply as templates - more finesse is needed.

However, write-once doesn't provide the level of management that
FB-API-style cookbooks expect. This marries the two. All desired
settings are managed 100%, and NetworkManager is allowed to make
additive settings (such as "seen-bssids" or "password" [sic]) so that it
can continue to function for users.

It is immune to the format changes that NM can make - spacing and
ordering changes will not cause the file to be updated any more than
ignorable settings. When updating the file, care is taken to stick
close to NM's style.

I considered making blacklist/whitelists of fields that NM could
modify, but in practice since we merge in the user's config, the user's
config is effectively a blacklist, and whitelist would likely be far
too fragile.

It has full docs, including on it's Migration Feature which will be
useful for anyone who had their own Chef to do this but wants to
migrate.

It also comes with pretty good test coverage.

Signed-off-by: Phil Dibowitz <[email protected]>
@davide125
Copy link
Member

Overall logic lgtm, but I'd like @jgoguen to do another pass

Copy link
Contributor

@jgoguen jgoguen left a comment

Choose a reason for hiding this comment

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

Overall LGTM. I'd like to see the VPN parts split out, but setting manage_packages to false is reasonable enough in the meantime.


The rendered config here will set `connection.autoconnect-priority` to 100
if there is no value for it found in the existing file, but will use the value
in the file if it exists. The logic here is quote simple:
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
in the file if it exists. The logic here is quote simple:
in the file if it exists. The logic here is quite simple:

Comment on lines +26 to +52
if node['fb_users']
vpnhome = '/var/lib/openvpn/chroot'
FB::Users.initialize_group(node, 'nm-openvpn')
node.default['fb_users']['users']['nm-openvpn'] = {
'gid' => 'nm-openvpn',
# If /var/lib/openvpn doesn't exist yet, we can't create it in time
# but we want to create the user, so for the first run, create it
# with /tmp
'home' => ::File.directory?(vpnhome) ? vpnhome : '/tmp',
'shell' => '/usr/sbin/nologin',
'action' => :add,
}
end

packages = %w{
network-manager
openvpn
network-manager-openvpn-gnome
}

package packages do
only_if do
node['fb_networkmanager']['enable'] &&
node['fb_networkmanager']['manage_packages']
end
action :upgrade
end
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 like to see the VPN parts split out into a separate cookbook, but for now setting node.default['fb_networkmanager']['manage_packages'] to false is a reasonable enough workaround.

Copy link
Collaborator Author

@jaymzh jaymzh Dec 11, 2020

Choose a reason for hiding this comment

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

It definitely doesn't make sense in another cookbook. It's all network-manager configs. you don't configure vpn, you configure network-manager and it may or may not use backends.

I can make enable_openvpn_packages if we want, or make it a separate recipe... but I think a config value seems sane. Then I can something like

package "network manager packages" do
  only_if do
    node['fb_networkmanager']['enable'] &&
      node['fb_networkmanager']['manage_packages']
  end
  package_names lazy {
    p = %w{network-manager}
    if node['fb_networkmanager']['enable_openvpn_packages']
      p += %w{openvpn network-manager-openvpn-gnome}
    end
    p
  }
  action :upgrade
end

I don't love it, but if people feel strongly.

@jaymzh
Copy link
Collaborator Author

jaymzh commented Dec 11, 2020

I missed the "lists are different in the two files" and the trailing semi-colon thing, will do those soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants