nmcli, nmtui: update authentication for OpenConnect

Since OpenConnect 8.20, 'openconnect --authenticate' will return the
full gateway URL, including the hostname and the path. This allows
servers behind SNI-based proxies to work. To ensure we end up at the
same IP address even behind round-robin DNS, there is a separate
--resolve argument.

Update nmcli/nmtui to use this, as NetworkManager-openconnect does.

Shift some of the logic into the nm_vpn_openconnect_authenticate_helper()
function instead of duplicating it in the callers.

Also, pass the correct protocol in rather than only supporting Cisco
AnyConnect.
This commit is contained in:
David Woodhouse 2023-05-10 16:31:44 +01:00
parent fa715e2387
commit f8d82c7f10
4 changed files with 99 additions and 43 deletions

View file

@ -160,9 +160,10 @@ nm_vpn_get_secret_names(const char *service_type)
};
if (NM_IN_STRSET(type, "openconnect")) {
return _VPN_PASSWORD_LIST({"gateway", N_("Gateway")},
return _VPN_PASSWORD_LIST({"gateway", N_("Gateway URL")},
{"cookie", N_("Cookie")},
{"gwcert", N_("Gateway certificate hash")}, );
{"gwcert", N_("Gateway certificate hash")},
{"resolve", N_("Gateway DNS resolution ('host:IP')")}, );
};
return NULL;
@ -186,18 +187,24 @@ _extract_variable_value(char *line, const char *tag, char **value)
return TRUE;
}
#define OC_ARGS_MAX 10
gboolean
nm_vpn_openconnect_authenticate_helper(const char *host,
char **cookie,
char **gateway,
char **gwcert,
int *status,
GError **error)
nm_vpn_openconnect_authenticate_helper(NMSettingVpn *s_vpn,
char **cookie,
char **gateway,
char **gwcert,
char **resolve,
int *status,
GError **error)
{
gs_free char *output = NULL;
gs_free const char **output_v = NULL;
gs_free char *output = NULL;
gs_free char *legacy_host = NULL;
gs_free char *connect_url = NULL;
gs_free const char **output_v = NULL;
const char *const *iter;
const char *path;
const char *opt;
const char *const DEFAULT_PATHS[] = {
"/sbin/",
"/usr/sbin/",
@ -207,6 +214,13 @@ nm_vpn_openconnect_authenticate_helper(const char *host,
"/usr/local/bin/",
NULL,
};
const char *gw, *port;
const char *oc_argv[OC_ARGS_MAX];
int oc_argc = 0;
/* Get gateway and port */
gw = nm_setting_vpn_get_data_item(s_vpn, "gateway");
port = gw ? strrchr(gw, ':') : NULL;
path = nm_utils_file_search_in_paths("openconnect",
"/usr/sbin/openconnect",
@ -218,8 +232,21 @@ nm_vpn_openconnect_authenticate_helper(const char *host,
if (!path)
return FALSE;
oc_argv[oc_argc++] = path;
oc_argv[oc_argc++] = "--authenticate";
oc_argv[oc_argc++] = gw;
opt = nm_setting_vpn_get_data_item(s_vpn, "protocol");
if (opt) {
oc_argv[oc_argc++] = "--protocol";
oc_argv[oc_argc++] = opt;
}
oc_argv[oc_argc++] = NULL;
g_return_val_if_fail(oc_argc <= OC_ARGS_MAX, FALSE);
if (!g_spawn_sync(NULL,
(char **) NM_MAKE_STRV(path, "--authenticate", host),
(char **) oc_argv,
NULL,
G_SPAWN_SEARCH_PATH | G_SPAWN_CHILD_INHERITS_STDIN,
NULL,
@ -235,14 +262,37 @@ nm_vpn_openconnect_authenticate_helper(const char *host,
* COOKIE='loremipsum'
* HOST='1.2.3.4'
* FINGERPRINT='sha1:32bac90cf09a722e10ecc1942c67fe2ac8c21e2e'
*
* Since OpenConnect v8.20 (2022-02-20) OpenConnect has also passed e.g.:
*
* CONNECT_URL='https://vpn.example.com:8443/ConnectPath'
* RESOLVE=vpn.example.com:1.2.3.4
*/
output_v = nm_strsplit_set_with_empty(output, "\r\n");
for (iter = output_v; iter && *iter; iter++) {
char *s_mutable = (char *) *iter;
_extract_variable_value(s_mutable, "COOKIE=", cookie);
_extract_variable_value(s_mutable, "HOST=", gateway);
_extract_variable_value(s_mutable, "CONNECT_URL=", &connect_url);
_extract_variable_value(s_mutable, "HOST=", &legacy_host);
_extract_variable_value(s_mutable, "FINGERPRINT=", gwcert);
_extract_variable_value(s_mutable, "RESOLVE=", resolve);
}
if (connect_url) {
*gateway = g_steal_pointer(&connect_url);
} else {
if (!legacy_host) {
g_set_error(error,
NM_VPN_PLUGIN_ERROR,
NM_VPN_PLUGIN_ERROR_FAILED,
_("OpenConnect failed to return gateway URL"));
return FALSE;
}
if (port)
*gateway = g_strdup_printf("%s%s", legacy_host, port);
else
*gateway = g_steal_pointer(&legacy_host);
}
return TRUE;

View file

@ -19,11 +19,12 @@ gboolean nm_vpn_supports_ipv6(NMConnection *connection);
const NmcVpnPasswordName *nm_vpn_get_secret_names(const char *service_type);
gboolean nm_vpn_openconnect_authenticate_helper(const char *host,
char **cookie,
char **gateway,
char **gwcert,
int *status,
GError **error);
gboolean nm_vpn_openconnect_authenticate_helper(NMSettingVpn *s_vpn,
char **cookie,
char **gateway,
char **gwcert,
char **resolve,
int *status,
GError **error);
#endif /* __NM_VPN_HELPERS_H__ */

View file

@ -635,10 +635,10 @@ vpn_openconnect_get_secrets(NMConnection *connection, GPtrArray *secrets)
{
GError *error = NULL;
NMSettingVpn *s_vpn;
const char *gw, *port;
gs_free char *cookie = NULL;
gs_free char *gateway = NULL;
gs_free char *gwcert = NULL;
gs_free char *resolve = NULL;
int status = 0;
int i;
gboolean ret;
@ -653,12 +653,14 @@ vpn_openconnect_get_secrets(NMConnection *connection, GPtrArray *secrets)
if (!nm_streq0(nm_setting_vpn_get_service_type(s_vpn), NM_SECRET_AGENT_VPN_TYPE_OPENCONNECT))
return FALSE;
/* Get gateway and port */
gw = nm_setting_vpn_get_data_item(s_vpn, "gateway");
port = gw ? strrchr(gw, ':') : NULL;
/* Interactively authenticate to OpenConnect server and get secrets */
ret = nm_vpn_openconnect_authenticate_helper(gw, &cookie, &gateway, &gwcert, &status, &error);
ret = nm_vpn_openconnect_authenticate_helper(s_vpn,
&cookie,
&gateway,
&gwcert,
&resolve,
&status,
&error);
if (!ret) {
nmc_printerr(_("Error: openconnect failed: %s\n"), error->message);
g_clear_error(&error);
@ -671,13 +673,6 @@ vpn_openconnect_get_secrets(NMConnection *connection, GPtrArray *secrets)
} else if (WIFSIGNALED(status))
nmc_printerr(_("Error: openconnect failed with signal %d\n"), WTERMSIG(status));
/* Append port to the host value */
if (gateway && port) {
gs_free char *tmp = gateway;
gateway = g_strdup_printf("%s%s", tmp, port);
}
/* Fill secrets to the array */
for (i = 0; i < secrets->len; i++) {
NMSecretAgentSimpleSecret *secret = secrets->pdata[i];
@ -698,6 +693,10 @@ vpn_openconnect_get_secrets(NMConnection *connection, GPtrArray *secrets)
NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "gwcert")) {
g_free(secret->value);
secret->value = g_steal_pointer(&gwcert);
} else if (nm_streq0(secret->entry_id,
NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "resolve")) {
g_free(secret->value);
secret->value = g_steal_pointer(&resolve);
}
}

View file

@ -31,25 +31,32 @@
* before starting the command and restored after it returns.
*/
static gboolean
openconnect_authenticate(NMConnection *connection, char **cookie, char **gateway, char **gwcert)
openconnect_authenticate(NMConnection *connection,
char **cookie,
char **gateway,
char **gwcert,
char **resolve)
{
GError *error = NULL;
NMSettingVpn *s_vpn;
gboolean ret;
int status = 0;
const char *gw, *port;
nmt_newt_message_dialog(
_("openconnect will be run to authenticate.\nIt will return to nmtui when completed."));
/* Get port */
s_vpn = nm_connection_get_setting_vpn(connection);
gw = nm_setting_vpn_get_data_item(s_vpn, "gateway");
port = gw ? strrchr(gw, ':') : NULL;
newtSuspend();
ret = nm_vpn_openconnect_authenticate_helper(gw, cookie, gateway, gwcert, &status, &error);
ret = nm_vpn_openconnect_authenticate_helper(s_vpn,
cookie,
gateway,
gwcert,
resolve,
&status,
&error);
newtResume();
@ -69,12 +76,6 @@ openconnect_authenticate(NMConnection *connection, char **cookie, char **gateway
return FALSE;
}
if (gateway && *gateway && port) {
char *tmp = *gateway;
*gateway = g_strdup_printf("%s%s", *gateway, port);
g_free(tmp);
}
return TRUE;
}
@ -99,8 +100,9 @@ secrets_requested(NMSecretAgentSimple *agent,
gs_free char *cookie = NULL;
gs_free char *gateway = NULL;
gs_free char *gwcert = NULL;
gs_free char *resolve = NULL;
openconnect_authenticate(connection, &cookie, &gateway, &gwcert);
openconnect_authenticate(connection, &cookie, &gateway, &gwcert, &resolve);
for (i = 0; i < secrets->len; i++) {
NMSecretAgentSimpleSecret *secret = secrets->pdata[i];
@ -121,6 +123,10 @@ secrets_requested(NMSecretAgentSimple *agent,
NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "gwcert")) {
g_free(secret->value);
secret->value = g_steal_pointer(&gwcert);
} else if (nm_streq0(secret->entry_id,
NM_SECRET_AGENT_ENTRY_ID_PREFX_VPN_SECRETS "resolve")) {
g_free(secret->value);
secret->value = g_steal_pointer(&resolve);
}
}
}