diff --git a/applications/luci-app-strongswan-swanctl/htdocs/luci-static/resources/view/strongswan-swanctl/strongswan.js b/applications/luci-app-strongswan-swanctl/htdocs/luci-static/resources/view/strongswan-swanctl/strongswan.js
index e1ce0118f4ad..bc4e0aed8b52 100644
--- a/applications/luci-app-strongswan-swanctl/htdocs/luci-static/resources/view/strongswan-swanctl/strongswan.js
+++ b/applications/luci-app-strongswan-swanctl/htdocs/luci-static/resources/view/strongswan-swanctl/strongswan.js
@@ -3,16 +3,28 @@
'require view';
'require tools.widgets as widgets';
+function addLogLevel(o) {
+ o.value('', _('Use daemon default'));
+ o.value('-1', _('Absolutely silent'));
+ o.value('0', _('Very basic auditing logs'));
+ o.value('1', _('Generic control flow with errors (default)'));
+ o.value('2', _('More detailed debugging control flow'));
+ o.value('3', _('Including RAW data dumps in hex'));
+ o.value('4', _('Also include sensitive material in dumps, e.g. keys'));
+ o.default = '';
+}
+
return view.extend({
render: function (result) {
let m, s, o;
- m = new form.Map('ipsec', _('Service configuration'),
+ m = new form.Map('swanctl', _('Service configuration'),
_('On this page, you can configure the IPsec service.'));
m.tabbed = true;
- // strongSwan General Settings
- s = m.section(form.NamedSection, 'globals', 'ipsec', _('General Settings'));
+ // general settings
+ s = m.section(form.NamedSection, 'globals', 'globals', _('General Settings'),
+ _('Configure global service parameters.'));
o = s.option(widgets.NetworkSelect, 'interface', _('Listening Interfaces'),
_('Interfaces that accept VPN traffic.') + '
' +
@@ -21,10 +33,74 @@ return view.extend({
o.nocreate = true;
o.optional = true;
- o = s.option(form.Value, 'debug', _('Debug Level'),
- _('Trace level: 0 is least verbose, 4 is most'));
+ // syslog plugin settings
+ s = m.section(form.NamedSection, 'syslog', 'syslog', _('Syslog Settings'),
+ _('Configure how strongswan logs events, errors, and debug information.'));
+
+ o = s.option(form.ListValue, 'app', 'app', _('Applications other than daemons'));
+ addLogLevel(o);
+
+ o = s.option(form.ListValue, 'asn', 'asn', _('Low-level encoding/decoding (ASN.1, X.509 etc.)'));
+ addLogLevel(o);
+
+ o = s.option(form.ListValue, 'cfg', 'cfg', _('Configuration management and plugins'));
+ addLogLevel(o);
+
+ o = s.option(form.ListValue, 'chd', 'chd', _('CHILD_SA/IPsec_SA'));
+ addLogLevel(o);
+
+ o = s.option(form.ListValue, 'dmn', 'dmn', _('Main daemon setup/cleanup/signal handling'));
+ addLogLevel(o);
+
+ o = s.option(form.ListValue, 'enc', 'enc', _('Packet encoding/decoding encryption/decryption operations'));
+ addLogLevel(o);
+
+ o = s.option(form.ListValue, 'esp', 'esp', '%s (%s)'.format(_('Library messages'), 'libipsec'));
+ addLogLevel(o);
+
+ o = s.option(form.ListValue, 'ike', 'ike', _('IKE_SA / ISAKMP SA'));
+ addLogLevel(o);
+
+ o = s.option(form.ListValue, 'imc', 'imc', _('Integrity Measurement Collector'));
+ addLogLevel(o);
+
+ o = s.option(form.ListValue, 'imv', 'imv', _('Integrity Measurement Verifier'));
+ addLogLevel(o);
+
+ o = s.option(form.ListValue, 'job', 'job', _('Jobs queuing/processing and thread pool management'));
+ addLogLevel(o);
+
+ o = s.option(form.ListValue, 'wch', 'wch', _('File descriptor watcher'));
+ addLogLevel(o);
+
+ o = s.option(form.ListValue, 'knl', 'knl', _('IPsec/Networking kernel interface'));
+ addLogLevel(o);
+
+ o = s.option(form.ListValue, 'lib', 'lib', '%s (%s)'.format(_('Library messages'), 'libstrongswan'));
+ addLogLevel(o);
+
+ o = s.option(form.ListValue, 'mgr', 'mgr', _('IKE_SA manager, handling synchronization for IKE_SA access'));
+ addLogLevel(o);
+
+ o = s.option(form.ListValue, 'net', 'net', _('IKE network communication'));
+ addLogLevel(o);
+
+ o = s.option(form.ListValue, 'pts', 'pts', _('Platform Trust Service'));
+ addLogLevel(o);
+
+ o = s.option(form.ListValue, 'tls', 'tls', '%s (%s)'.format(_('Library messages'), 'libtls'));
+ addLogLevel(o);
+
+ o = s.option(form.ListValue, 'tnc', 'tnc', _('Trusted Network Connect'));
+ addLogLevel(o);
+
+ // kernel-netlink plugin settings
+ s = m.section(form.NamedSection, 'netlink', 'netlink', _('Netlink Settings'),
+ _('Configure the kernel-netlink plugin options.'));
+
+ o = s.option(form.Flag, 'install_routes_xfrmi', _('Install routes via XFRM interfaces'),
+ _('When this is enabled, the routes for all XFRM interfaces are set automatically.'));
o.default = '0';
- o.datatype = 'range(0,4)';
return m.render();
}
diff --git a/applications/luci-app-strongswan-swanctl/htdocs/luci-static/resources/view/strongswan-swanctl/swanctl.js b/applications/luci-app-strongswan-swanctl/htdocs/luci-static/resources/view/strongswan-swanctl/swanctl.js
index 07288aef5f15..09c857d9c1b8 100644
--- a/applications/luci-app-strongswan-swanctl/htdocs/luci-static/resources/view/strongswan-swanctl/swanctl.js
+++ b/applications/luci-app-strongswan-swanctl/htdocs/luci-static/resources/view/strongswan-swanctl/swanctl.js
@@ -4,6 +4,7 @@
'require rpc';
'require uci';
'require ui';
+'require fs';
const callListAlgorithms = rpc.declare({
object: 'luci.swanctl',
@@ -36,37 +37,108 @@ function sectionNameCheck(extra_class) {
nameEl = el.querySelector('.cbi-section-create-name');
ui.addValidator(nameEl, 'uciname', true, function(v) {
let sections = [
- ...uci.sections('ipsec', 'remote'),
- ...uci.sections('ipsec', 'tunnel'),
- ...uci.sections('ipsec', 'crypto_proposal'),
+ ...uci.sections('swanctl', 'remote'),
+ ...uci.sections('swanctl', 'child'),
+ ...uci.sections('swanctl', 'shunt'),
+ ...uci.sections('swanctl', 'crypto_proposal'),
];
if (sections.find(function(s) {
return s['.name'] == v;
})) {
- return _('Remotes, Encryption Proposals and Tunnels may not share the same names.') + ' ' +
- _('Use combinations like tunnel1_phase1 that do not exceed 15 characters.');
+ return _('Remotes, Encryption Proposals, Children and Shunts may not share the same names.') + ' ' +
+ _('Use combinations like child1_phase1.');
}
- if (v.length > 15) return _('Name length shall not exceed 15 characters');
return true;
}, 'blur', 'keyup');
return el;
};
+let migrationOverlay = null;
+
+function renderMigrationContent(errorMessage) {
+ const dialog = migrationOverlay.firstElementChild;
+ const migrateButton = E('button', {
+ 'class': 'btn cbi-button cbi-button-apply',
+ 'click': handleMigrate
+ }, [_('Migrate')]);
+
+ const content = E([], [
+ E('h4', _('Migrate IPsec configuration')),
+ E('p', _('A legacy IPsec configuration was found.') + ' ' +
+ _('It must be migrated from the uci config \'/etc/config/ipsec\' ' +
+ 'to \'/etc/config/swanctl\' before it can be managed here.')),
+ E('p', _('After a successful migration, the file \'/etc/config/ipsec\' is moved to \'/etc/config/ipsec.bak\'.') + ' ' +
+ _('The migration may require manual intervention.') + ' ' +
+ _('Therefore, the VPN is not restarted.')),
+ E('p', _('Warning: The migration will drop all IPsec VPN connections.')),
+ E('p', _('This will execute the command \'/etc/init.d/swanctl migrate\'.'))
+ ]);
+
+ if (errorMessage)
+ content.appendChild(E('p', errorMessage));
+
+ content.appendChild(E('div', { 'class': 'right' }, [migrateButton]));
+
+ dialog.replaceChildren(content);
+}
+
+function showMigrationOverlay(viewNode) {
+ if (migrationOverlay)
+ return;
+
+ const dialog = E('div', { 'class': 'modal', 'style': 'margin:0;' });
+ migrationOverlay = E('div', {
+ 'class': 'migration-overlay',
+ 'style': 'position:absolute;top:0;right:0;bottom:0;left:0;z-index:900;display:flex;align-items:center;justify-content:center;padding:1em;pointer-events:all;'
+ }, [dialog]);
+
+ viewNode.style.position = 'relative';
+ viewNode.appendChild(migrationOverlay);
+
+ const maincontent = document.getElementById('maincontent');
+ if (maincontent)
+ maincontent.style.pointerEvents = 'none';
+
+ renderMigrationContent(null);
+}
+
+function handleMigrate() {
+ const dialog = migrationOverlay.firstElementChild;
+
+ dialog.replaceChildren(E([], [
+ E('h4', _('Migrate IPsec configuration')),
+ E('p', _('Migrating IPsec configuration…'))
+ ]));
+
+ fs.exec('/etc/init.d/swanctl', ['migrate']).then(function (result) {
+ if (result.code != 0)
+ renderMigrationContent(_('Migration failed: %s').format(result.stderr || result.stdout || _('unknown error')));
+ else
+ window.location.reload();
+ }).catch(function (e) {
+ renderMigrationContent(_('Migration failed: %s').format(e.message));
+ });
+}
+
return view.extend({
load: async function () {
await uci.load('network');
- return await callListAlgorithms();
+ const algorithms = await callListAlgorithms();
+ const legacyConfig = await L.resolveDefault(fs.stat('/etc/config/ipsec'), null);
+
+ return { algorithms, legacyConfig };
},
render: function (result) {
let m, s, o;
- const algorithms = result.data ?? {};
- const error = result.error;
+ const legacyConfig = !!result.legacyConfig;
+ const algorithms = result.algorithms.data ?? {};
+ const error = result.algorithms.error;
if (error)
ui.addNotification(null, E('p', _('Some options are unavailable because swanctl failed to load: %s').format(error)), 'warning');
- m = new form.Map('ipsec', _('Connection configurations'),
+ m = new form.Map('swanctl', _('Connection configurations'),
_('On this page, you can configure the IPsec connections.'));
m.tabbed = true;
@@ -109,7 +181,7 @@ return view.extend({
this.keylist = [];
this.vallist = [];
- var sections = uci.sections('ipsec', 'crypto_proposal').filter(function (section) {
+ var sections = uci.sections('swanctl', 'crypto_proposal').filter(function (section) {
return section.is_esp != '1';
});
if (sections.length == 0) {
@@ -124,18 +196,18 @@ return view.extend({
};
o.rmempty = false;
- o = s.taboption('general', form.MultiValue, 'tunnel', _('Tunnel'),
- _('The Tunnel containing the ESP (phase 2) section'));
+ o = s.taboption('general', form.MultiValue, 'child', _('Children'),
+ _('The Children containing the ESP (phase 2) section'));
o.load = function (section_id) {
this.keylist = [];
this.vallist = [];
- var sections = uci.sections('ipsec', 'tunnel');
+ var sections = uci.sections('swanctl', 'child');
if (sections.length == 0) {
- this.value('', _('Please create a Tunnel first'));
+ this.value('', _('Please create a Children first'));
} else {
sections.forEach(L.bind(function (section) {
- this.value(section['.name']);
+ this.value(section['.name'], '%s (%s)'.format(section['.name'], section['mode']));
}, this));
}
@@ -231,7 +303,7 @@ return view.extend({
o.placeholder = '3';
o.modalonly = true;
- o = s.taboption('advanced', form.Value, 'dpddelay', _('DPD Delay'),
+ o = s.taboption('advanced', form.Value, 'dpd_delay', _('DPD Delay'),
_('Interval to check liveness of a peer'));
o.validate = validateTimeFormat;
o.placeholder = '30s';
@@ -243,12 +315,12 @@ return view.extend({
o.placeholder = '0s';
o.modalonly = true;
- o = s.taboption('advanced', form.Value, 'rekeytime', _('Rekey Time'),
+ o = s.taboption('advanced', form.Value, 'rekey_time', _('Rekey Time'),
_('IKEv2 interval to refresh keying material; also used to compute lifetime'));
o.validate = validateTimeFormat;
o.modalonly = true;
- o = s.taboption('advanced', form.Value, 'overtime', _('Overtime'),
+ o = s.taboption('advanced', form.Value, 'over_time', _('Overtime'),
_('Limit on time to complete rekeying/reauthentication'));
o.validate = validateTimeFormat;
o.modalonly = true;
@@ -269,9 +341,9 @@ return view.extend({
o.default = 'ikev2';
o.modalonly = true;
- // Tunnel Configuration
- s = m.section(form.GridSection, 'tunnel', _('Tunnel Configuration'),
- _('Define Connection Children to be used as Tunnels in Remote Configurations.'));
+ // Children Configuration
+ s = m.section(form.GridSection, 'child', _('Children Configuration'),
+ _('Define Connection Children to be used in Remote Configurations.'));
s.addremove = true;
s.nodescriptions = true;
s.renderSectionAdd = sectionNameCheck;
@@ -279,6 +351,12 @@ return view.extend({
o = s.tab('general', _('General'));
o = s.tab('advanced', _('Advanced'));
+ o = s.taboption('general', form.ListValue, 'mode', _('Child mode'));
+ o.rmempty = false;
+ o.value('tunnel', _('Tunnel'));
+ o.value('transport', _('Transport'));
+ o.default = 'tunnel';
+
o = s.taboption('general', form.DynamicList, 'local_subnet', _('Local Subnet'),
_('Local network(s)'));
o.datatype = 'cidr';
@@ -311,20 +389,24 @@ return view.extend({
o.optional = true;
o.modalonly = true;
- o = s.taboption('general', form.ListValue, 'startaction', _('Start Action'),
+ o = s.taboption('general', form.ListValue, 'start_action', _('Start Action'),
_('Action on initial configuration load'));
- o.value('none');
+ o.value('', '%s (%s)'.format('none', _('default')));
o.value('trap');
o.value('start');
- o.default = 'trap';
+ o.optional = true;
+ o.default = '';
+ o.rmempty = true;
o.modalonly = true;
- o = s.taboption('general', form.ListValue, 'closeaction', _('Close Action'),
+ o = s.taboption('general', form.ListValue, 'close_action', _('Close Action'),
_('Action when CHILD_SA is closed'));
- o.value('none');
+ o.value('', '%s (%s)'.format('none', _('default')));
o.value('trap');
o.value('start');
o.optional = true;
+ o.default = '';
+ o.rmempty = true;
o.modalonly = true;
o = s.taboption('general', form.MultiValue, 'crypto_proposal',
@@ -334,7 +416,7 @@ return view.extend({
this.keylist = [];
this.vallist = [];
- var sections = uci.sections('ipsec', 'crypto_proposal').filter(function (section) {
+ var sections = uci.sections('swanctl', 'crypto_proposal').filter(function (section) {
return section.is_esp == '1';
});
if (sections.length == 0) {
@@ -354,16 +436,17 @@ return view.extend({
o.datatype = 'file';
o.modalonly = true;
- o = s.taboption('advanced', form.ListValue, 'dpdaction', _('DPD Action'),
+ o = s.taboption('advanced', form.ListValue, 'dpd_action', _('DPD Action'),
_('Action when DPD timeout occurs'));
- o.value('none');
- o.value('clear');
+ o.value('', '%s (%s)'.format('clear', _('default')));
o.value('trap');
o.value('start');
+ o.default = '';
+ o.rmempty = true;
o.optional = true;
o.modalonly = true;
- o = s.taboption('advanced', form.Value, 'rekeytime', _('Rekey Time'),
+ o = s.taboption('advanced', form.Value, 'rekey_time', _('Rekey Time'),
_('Interval before a CHILD_SA is rekeyed.') + ' ' +
_('Also used to derive lifetime (110% of this value).') + '
' +
_('If not configured, the default value is "1h".')
@@ -373,7 +456,7 @@ return view.extend({
o.rmempty = true;
o.modalonly = true;
- o = s.taboption('advanced', form.Value, 'lifetime', _('Life Time'),
+ o = s.taboption('advanced', form.Value, 'life_time', _('Life Time'),
_('Maximum time before the CHILD_SA gets closed, as a hard limit.')
);
o.validate = validateTimeFormat;
@@ -404,7 +487,7 @@ return view.extend({
o.datatype = 'uinteger';
o.modalonly = true;
- o = s.taboption('advanced', form.Value, 'rekeybytes', _('Rekey Bytes'),
+ o = s.taboption('advanced', form.Value, 'rekey_bytes', _('Rekey Bytes'),
_('Number of bytes processed before initiating CHILD_SA rekeying.') + ' ' +
_('Also used to derive lifebytes if set (110% of this value).') + ' ' +
_('Use "0" to disable byte based rekeying.')
@@ -414,7 +497,7 @@ return view.extend({
o.rmempty = true;
o.modalonly = true;
- o = s.taboption('advanced', form.Value, 'lifebytes', _('Life Bytes'),
+ o = s.taboption('advanced', form.Value, 'life_bytes', _('Life Bytes'),
_('Maximum number of bytes processed before the CHILD_SA gets closed.') + ' ' +
_('Use "0" to disable (default).')
);
@@ -423,7 +506,7 @@ return view.extend({
o.rmempty = true;
o.modalonly = true;
- o = s.taboption('advanced', form.Value, 'rekeypackets', _('Rekey Packets'),
+ o = s.taboption('advanced', form.Value, 'rekey_packets', _('Rekey Packets'),
_('Number of packets processed before initiating CHILD_SA rekeying.') + ' ' +
_('Also used to derive lifepackets if set (110% of this value).') + ' ' +
_('Use "0" to disable packet based rekeying (default).')
@@ -433,7 +516,7 @@ return view.extend({
o.rmempty = true;
o.modalonly = true;
- o = s.taboption('advanced', form.Value, 'lifepackets', _('Life Packets'),
+ o = s.taboption('advanced', form.Value, 'life_packets', _('Life Packets'),
_('Maximum number of packets processed before the CHILD_SA gets closed.') + ' ' +
_('Use "0" to disable (default).')
);
@@ -442,6 +525,40 @@ return view.extend({
o.rmempty = true;
o.modalonly = true;
+ // Shunt Configuration
+ s = m.section(form.GridSection, 'shunt', _('Shunt Configuration'),
+ _('Define Shunt pass/drop policies independent of remote connections.'));
+ s.addremove = true;
+ s.nodescriptions = true;
+ s.renderSectionAdd = sectionNameCheck;
+
+ o = s.option(form.Flag, 'enabled', _('Enabled'),
+ _('Configuration is enabled or not'));
+ o.rmempty = false;
+
+ o = s.option(form.ListValue, 'mode', _('Mode'),
+ _('Shunt policy mode'));
+ o.value('pass', _('Pass'));
+ o.value('drop', _('Drop'));
+ o.rmempty = false;
+
+ o = s.option(form.DynamicList, 'local_ts', _('Local Traffic Selectors'),
+ _('Local traffic selectors for the shunt policy'));
+ o.datatype = 'or(cidr,ipaddr)';
+
+ o = s.option(form.DynamicList, 'remote_ts', _('Remote Traffic Selectors'),
+ _('Remote traffic selectors for the shunt policy'));
+ o.datatype = 'or(cidr,ipaddr)';
+
+ o = s.option(form.Value, 'priority', _('Priority'),
+ _('Priority of the shunt policy (lower number means higher priority)'));
+ o.datatype = 'uinteger';
+ o.modalonly = true;
+
+ o = s.option(form.Value, 'interface', _('Interface'),
+ _('Network interface to bind the shunt policy to'));
+ o.modalonly = true;
+
// Crypto Proposals
s = m.section(form.GridSection, 'crypto_proposal',
_('Encryption Proposals'),
@@ -453,19 +570,32 @@ return view.extend({
o = s.option(form.Flag, 'is_esp', _('ESP Proposal'),
_('Whether this is an ESP (phase 2) proposal or not'));
+ o = s.option(form.Flag, 'use_custom_proposal', _('Use custom proposal'),
+ _('When enabled, you can specify your own proposal string.'));
+ o.default = '0';
+ o.rmempty = true;
+
+ o = s.option(form.Value, 'custom_proposal', _('Custom proposal'),
+ _('Using this option only if you know exactly what you are doing.') + '
' +
+ _('Manually defining a proposal can break compatibility with peers or cause connection failures.') + '
' +
+ _('Using this setting may prevent future updates or migrations.') + '
' +
+ _('You are responsible for maintaining compatibility!'));
+ o.depends('use_custom_proposal', '1');
+ o.rmempty = false;
+
o = s.option(form.ListValue, 'encryption_algorithm',
_('Encryption Algorithm'),
_('Algorithms marked with * are considered insecure'));
o.default = 'aes256gcm128';
+ o.depends('use_custom_proposal', '0');
addAlgorithms(o, algorithms.encryption);
addAlgorithms(o, algorithms.aead);
-
const encryptionAlgorithmNames = algorithms.encryption?.map(algorithm => algorithm.name);
o = s.option(form.ListValue, 'hash_algorithm', _('Hash Algorithm'),
_('Algorithms marked with * are considered insecure'));
encryptionAlgorithmNames?.forEach(function (algorithmName) {
- o.depends('encryption_algorithm', algorithmName);
+ o.depends({'encryption_algorithm': algorithmName, 'use_custom_proposal': '0'});
});
o.default = 'sha512';
o.rmempty = false;
@@ -474,6 +604,7 @@ return view.extend({
o = s.option(form.ListValue, 'dh_group', _('Diffie-Hellman Group'),
_('Algorithms marked with * are considered insecure'));
o.default = 'modp3072';
+ o.depends('use_custom_proposal', '0');
addAlgorithms(o, algorithms.ke);
o = s.option(form.ListValue, 'prf_algorithm', _('PRF Algorithm'),
@@ -489,9 +620,14 @@ return view.extend({
return true;
};
o.optional = true;
- o.depends('is_esp', '0');
+ o.depends({'is_esp': '0', 'use_custom_proposal': '0'});
addAlgorithms(o, algorithms.prf);
- return m.render();
+ return m.render().then(function (node) {
+ if (legacyConfig)
+ showMigrationOverlay(node);
+
+ return node;
+ });
}
});
diff --git a/applications/luci-app-strongswan-swanctl/root/usr/share/rpcd/acl.d/luci-app-strongswan-swanctl.json b/applications/luci-app-strongswan-swanctl/root/usr/share/rpcd/acl.d/luci-app-strongswan-swanctl.json
index 7407183247be..22290c45c005 100644
--- a/applications/luci-app-strongswan-swanctl/root/usr/share/rpcd/acl.d/luci-app-strongswan-swanctl.json
+++ b/applications/luci-app-strongswan-swanctl/root/usr/share/rpcd/acl.d/luci-app-strongswan-swanctl.json
@@ -3,22 +3,24 @@
"description": "Grant access to luci-app-strongswan-swanctl",
"read": {
"file": {
+ "/etc/config/ipsec": [ "list" ],
"/usr/sbin/swanmon version": [ "exec" ],
"/usr/sbin/swanmon stats": [ "exec" ]
},
"ubus": {
"luci.swanctl": [ "list-algs", "map-conns" ]
},
- "uci": [ "ipsec" ]
+ "uci": [ "swanctl" ]
},
"write": {
"file": {
+ "/etc/init.d/swanctl migrate": [ "exec" ],
"/usr/sbin/swanctl --initiate --ike *": [ "exec" ],
"/usr/sbin/swanctl --initiate --child *": [ "exec" ],
"/usr/sbin/swanctl --terminate --ike *": [ "exec" ],
"/usr/sbin/swanctl --terminate --child *": [ "exec" ]
},
- "uci": [ "ipsec" ]
+ "uci": [ "swanctl" ]
}
}
}