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 networkmanager-list@gnome.org https://mail.gnome.org/mailman/listinfo/networkmanager-list