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

Attachment: 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

Reply via email to