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') });