diff --git a/scionlab/forms/user_as_form.py b/scionlab/forms/user_as_form.py
index b70198c8..71add053 100644
--- a/scionlab/forms/user_as_form.py
+++ b/scionlab/forms/user_as_form.py
@@ -13,7 +13,7 @@
# limitations under the License.
from crispy_forms.bootstrap import AppendedText
from crispy_forms.helper import FormHelper
-from crispy_forms.layout import Layout, Field, Column, Row, Div
+from crispy_forms.layout import Layout, Field, HTML, Column, Row, Div
from django import forms
from django.forms import modelformset_factory
from django.core.exceptions import ValidationError
@@ -46,8 +46,21 @@ def _crispy_helper(instance):
),
Div(
Row(
- Column('public_ip', css_class='col-md-5'),
- Column('provide_vpn', css_class='col-md-5'),
+ Column('public_ip', css_class='col-md-6'),
+ Column('provide_vpn', css_class='col-md-4'),
+ ),
+ Row(
+ HTML("""""")
+ ),
+ Row(
+ Column(AppendedText('bind_ip', ''),
+ css_class='col-md-6'),
+ css_class="bind-row"
),
css_class="card-body", css_id="user-ap-card-body",
),
@@ -104,6 +117,12 @@ class UserASForm(forms.Form):
label="Public IP",
help_text="Public IP Address to be used for connections to child ASes"
)
+ bind_ip = forms.GenericIPAddressField(
+ required=False,
+ label="Bind IP address",
+ help_text="(Optional) Specify the local IP "
+ "if your border router is behind a NAT/firewall etc.",
+ )
provide_vpn = forms.BooleanField(
required=False,
label="Provide VPN",
@@ -144,6 +163,7 @@ def __init__(self, data=None, *args, **kwargs):
'label': self.instance.label,
'installation_type': self.instance.installation_type,
'public_ip': host.public_ip,
+ 'bind_ip': host.bind_ip,
'become_user_ap': is_ap,
'provide_vpn': has_vpn
})
@@ -193,6 +213,7 @@ def save(self, commit=True):
installation_type=self.cleaned_data['installation_type'],
label=self.cleaned_data['label'],
public_ip=self.cleaned_data['public_ip'],
+ bind_ip=self.cleaned_data['bind_ip'],
wants_user_ap=self.cleaned_data['become_user_ap'],
wants_vpn=self.cleaned_data['provide_vpn'],
)
@@ -203,6 +224,7 @@ def save(self, commit=True):
installation_type=self.cleaned_data['installation_type'],
label=self.cleaned_data['label'],
public_ip=self.cleaned_data['public_ip'],
+ bind_ip=self.cleaned_data['bind_ip'],
wants_user_ap=self.cleaned_data['become_user_ap'],
wants_vpn=self.cleaned_data['provide_vpn'],
)
diff --git a/scionlab/models/user_as.py b/scionlab/models/user_as.py
index 0a0a6f4c..e529cb05 100644
--- a/scionlab/models/user_as.py
+++ b/scionlab/models/user_as.py
@@ -51,7 +51,8 @@ def create(self,
owner: User,
installation_type: str,
isd: int,
- public_ip: str = "",
+ public_ip: str = None,
+ bind_ip: str = None,
wants_user_ap: bool = False,
wants_vpn: bool = False,
as_id: str = None,
@@ -62,6 +63,7 @@ def create(self,
:param str installation_type:
:param int isd:
:param str public_ip: optional public IP address for the host of the AS
+ :param str bind_ip: optional bind IP address for the host of the AS
:param bool wants_user_ap: optional boolean if the User AS should be AP
:param str wants_vpn: optional boolean if the User AP should provide a VPN
:param str as_id: optional as_id, if None is given, the next free ID is chosen
@@ -90,7 +92,7 @@ def create(self,
user_as.generate_keys()
user_as.generate_certs()
- user_as.init_default_services(public_ip=public_ip)
+ user_as.init_default_services(public_ip=public_ip, bind_ip=bind_ip)
if wants_user_ap:
host = user_as.hosts.first()
@@ -148,7 +150,7 @@ class Meta:
def get_absolute_url(self):
return urls.reverse('user_as_detail', kwargs={'pk': self.pk})
- def update(self, label: str, installation_type: str, public_ip: str = "",
+ def update(self, label: str, installation_type: str, public_ip: str = None, bind_ip: str = None,
wants_user_ap: bool = False, wants_vpn: bool = False):
"""
Updates the `UserAS` fields and immediately saves
@@ -161,7 +163,7 @@ def update(self, label: str, installation_type: str, public_ip: str = "",
self._unset_bind_ips_for_vagrant()
self.installation_type = installation_type
host = self.host
- host.update(public_ip=public_ip)
+ host.update(public_ip=public_ip, bind_ip=bind_ip)
if self.is_attachment_point():
# the case has_vpn and not wants_vpn will be ignored here because it's not allowed
ap = self.attachment_point_info
diff --git a/scionlab/openvpn_config.py b/scionlab/openvpn_config.py
index 5bdee1e9..f78583fa 100644
--- a/scionlab/openvpn_config.py
+++ b/scionlab/openvpn_config.py
@@ -213,7 +213,7 @@ def generate_vpn_server_config(vpn):
server_vpn_subnet = vpn.vpn_subnet()
server_config = string.Template(server_config_tmpl).substitute(
- ServerPublicIP=vpn.server.public_ip,
+ ServerPublicIP=vpn.server.bind_ip or vpn.server.public_ip,
ServerPort=vpn.server_port,
ServerVPNIP=vpn.server_vpn_ip,
Netmask=server_vpn_subnet.netmask,
diff --git a/scionlab/templates/scionlab/partials/user_as_form_script.html b/scionlab/templates/scionlab/partials/user_as_form_script.html
index fb9e38b3..e7aa3d39 100644
--- a/scionlab/templates/scionlab/partials/user_as_form_script.html
+++ b/scionlab/templates/scionlab/partials/user_as_form_script.html
@@ -65,7 +65,7 @@
}
$('.bind-row-collapser').each(function() {
// Init collapser and bind-row
- let bind_row = $(this).closest('.attachment').find('.bind-row')
+ let bind_row = $(this).closest('.card-body').find('.bind-row')
bind_row.addClass('collapse')
if (!should_collapse_bind_row(bind_row)) {
bind_row.addClass('show')
@@ -74,7 +74,7 @@
});
$('.bind-row-collapser').click(function() {
// Toggle collapse state for collapser and bind-row
- let bind_row = $(this).closest('.attachment').find('.bind-row')
+ let bind_row = $(this).closest('.card-body').find('.bind-row')
$(this).toggleClass('collapsed')
bind_row.collapse('toggle')
});