On Sat, 2020-08-15 at 22:51 +0300, Ionuț Leonte via networkmanager-list
wrote:
> Hello,
>
> The company I work for uses Cisco AnyConnect and I have been using
> NetworkManager with the OpenConnect plugin to connect to it for some
> time
> now. Soon, however, they will be enabling SAML (for 2FA with Azure
> AD)
> which is not supported in the OpenConnect plugin for NetworkManager.
>
> I've written a Python script that does the initial handshake/2FA and
> obtains
> the 3 secrets needed to connect - the cookie, gateway certificate
> hash and
> the gateway address. I am sure these secrets are correct because I
> can
> manually run openconnect, pass the secrets to it, and it successfully
> connects. This is functional but not great with regards to 'user
> experience'.
>
> I would like to programmatically configure a NetworkManager VPN
> connection
> and activate it using the secrets I obtain via the manual handshake.
> The
> problem I am facing is that I always get the VPN authentication
> dialog, even
> though I set the secrets on the connection.
>
> The question I have is: is there a way to tell NetworkManager that it
> can
> skip the authentication step and just bring up the connection with
> the
> secrets I already provided?
>
> Here is the code I have come up with so far:
>
> import uuid
> import gi
> gi.require_version('NM', '1.0')
> from gi.repository import NM, GLib
> g_vpn_server = 'vpn.example.com'
> (cookie, gwcert) = do_saml_2fa(g_vpn_server) # implemented
> elsewhere
>
> def create_profile(name: str) -> NM.SimpleConnection:
> profile = NM.SimpleConnection.new()
> s_con = NM.SettingConnection.new()
> s_con.set_property(NM.SETTING_CONNECTION_ID, name)
> s_con.set_property(NM.SETTING_CONNECTION_UUID,
> str(uuid.uuid4()))
> s_con.set_property(NM.SETTING_CONNECTION_TYPE, "vpn")
>
> s_ip4 = NM.SettingIP4Config.new()
> s_ip4.set_property(NM.SETTING_IP_CONFIG_METHOD, "auto")
>
> s_vpn = NM.SettingVpn.new()
> s_vpn.set_property(
> 'service-type',
> 'org.freedesktop.NetworkManager.openconnect')
>
> s_vpn.add_data_item('protocol', 'anyconnect')
> s_vpn.add_data_item('gateway', g_vpn_server)
> s_vpn.add_data_item('cacert', '<redacted>')
> s_vpn.add_data_item('usercert', '<redacted>')
> s_vpn.add_data_item('userkey', '<redacted>')
> s_vpn.add_data_item('enable_csd_trojan', 'no')
> s_vpn.add_data_item('pem_passphrase_fsid', 'no')
>
> s_vpn.add_secret('autoconnect', 'yes')
> s_vpn.add_secret('save_passwords', 'no')
>
> profile.add_setting(s_con)
> profile.add_setting(s_ip4)
> profile.add_setting(s_vpn)
>
> return profile
>
> def handle_connection_activate(nm, res, loop):
> try:
> ret = nm.activate_connection_finish(res)
> print(ret)
> except Exception as e:
> print(f'ERROR: {e}')
> loop.quit()
>
> def handle_changes_saved(rc, res, data):
> remote_conn, loop = data
> ret = remote_conn.commit_changes_finish(res)
> print(f'CHANGES SAVED: {ret}')
>
> if ret:
> nm.activate_connection_async(
> remote_conn,
> None,
> None,
> None,
> handle_connection_activate, loop)
> else:
> loop.quit()
>
> def handle_connection_add_finish(nm, res, data):
> loop, cookie, gateway, gwcert = data
> ret = nm.add_connection_finish(res)
> print(f'{type(ret)} - {ret.get_path()}')
> s_vpn = ret.get_setting_vpn()
> s_vpn.add_secret('cookie', cookie)
> s_vpn.add_secret('gateway', gateway)
> s_vpn.add_secret('gwcert', gwcert)
>
> s_vpn.set_secret_flags('cookie',
> NM.SettingSecretFlags.NOT_SAVED)
> s_vpn.set_secret_flags('gateway',
> NM.SettingSecretFlags.NOT_SAVED)
> s_vpn.set_secret_flags('gwcert',
> NM.SettingSecretFlags.NOT_SAVED)
>
> print(ret.need_secrets(), ret.verify_secrets())
> ret.commit_changes_async(
> False,
> None,
> handle_changes_saved,
> (ret, loop))
>
> nm = NM.Client.new(None)
> p = create_profile('vpn-sso')
> print(p.verify_secrets(), p.verify())
> loop = GLib.MainLoop()
> nm.add_connection_async(
> p,
> True,
> None,
> handle_connection_add_finish,
> (loop, cookie, g_vpn_server, gwcert))
> loop.run()
hi,
if you set
s_vpn.set_secret_flags('cookie', NM.SettingSecretFlags.NOT_SAVED)
during connection add/update, then the secret will be lost right away.
I don't think you can provide ALWAYS_ASK secrets this way.
I think you need to run a "secret-agent". That is basically what nm-
applet, nmcli, nmtui can do.
They use the NMSecretAgentOld class ([1]). That is public (and stable)
API, despite the odd name. The problem is, that this libnm API is
rather arcane. You have to subclass the type. I think you can do that
with pygobject, but it's probably not entirely convenient.
The alternative is to use the D-Bus API directly. libnm is only a
wrapper around D-Bus, so you can achieve the same result. Still,
implementing the D-Bus API isn't entirely straight forward either.
Your application needs to implement the D-Bus service [2] and you need
to register your application by calling [3].
[1]
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/80c93b0e5e1554fe894d5e94c59ebb29b702da42/libnm/nm-secret-agent-old.c
[2]
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/80c93b0e5e1554fe894d5e94c59ebb29b702da42/introspection/org.freedesktop.NetworkManager.SecretAgent.xml
[3]
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/80c93b0e5e1554fe894d5e94c59ebb29b702da42/introspection/org.freedesktop.NetworkManager.AgentManager.xml
The Secret API isn't great, patches for improvements are welcome (but
it's not so easy).
best,
Thomas
signature.asc
Description: This is a digitally signed message part
_______________________________________________ networkmanager-list mailing list [email protected] https://mail.gnome.org/mailman/listinfo/networkmanager-list
