From 6ebad037254b0c254638f6e6dfbd48e451a1ceeb Mon Sep 17 00:00:00 2001
From: Bogdan Dobrelya <bdobreli@redhat.com>
Date: Wed, 16 Aug 2017 09:14:06 +0200
Subject: Access UI via a bastion node (#596)

When using a bastion and a single master, use the lb-secgrp
to access UI port allowed from the ingress bastion node cidr.
For HA (masters>1), UI still should be accessed via
the LB node's ingress cidr, omitting the bastion.

Signed-off-by: Bogdan Dobrelya <bdobreli@redhat.com>
---
 playbooks/provisioning/openstack/README.md           | 18 ++++++++++++++++++
 playbooks/provisioning/openstack/stack_params.yaml   |  1 +
 roles/openstack-stack/defaults/main.yml              |  1 +
 roles/openstack-stack/templates/heat_stack.yaml.j2   | 20 +++++++++++++++-----
 roles/static_inventory/defaults/main.yml             |  6 ++++++
 roles/static_inventory/tasks/main.yml                |  7 +++++++
 roles/static_inventory/tasks/sshtun.yml              | 15 +++++++++++++++
 .../static_inventory/templates/ssh-tunnel.service.j2 | 20 ++++++++++++++++++++
 8 files changed, 83 insertions(+), 5 deletions(-)
 create mode 100644 roles/static_inventory/tasks/sshtun.yml
 create mode 100644 roles/static_inventory/templates/ssh-tunnel.service.j2

diff --git a/playbooks/provisioning/openstack/README.md b/playbooks/provisioning/openstack/README.md
index 79e153fe1..d7fa76b0f 100644
--- a/playbooks/provisioning/openstack/README.md
+++ b/playbooks/provisioning/openstack/README.md
@@ -251,6 +251,24 @@ Once it succeeds, you can install openshift by running:
 
     ansible-playbook openshift-ansible/playbooks/byo/config.yml
 
+### Access UI
+
+OpenShift UI may be accessed via the 1st master node FQDN, port 8443.
+
+When using a bastion, you may want to make an SSH tunnel from your control node
+to access UI on the `https://localhost:8443`, with this inventory variable:
+
+   openshift_ui_ssh_tunnel: True
+
+Note, this requires sudo rights on the ansible control node and an absolute path
+for the `openstack_private_ssh_key`. You should also update the control node's
+`/etc/hosts`:
+
+    127.0.0.1 master-0.openshift.example.com
+
+In order to access UI, the ssh-tunnel service will be created and started on the
+control node. Make sure to remove these changes and the service manually, when not
+needed anymore.
 
 ## License
 
diff --git a/playbooks/provisioning/openstack/stack_params.yaml b/playbooks/provisioning/openstack/stack_params.yaml
index 6c920d2a2..8f36d5c4f 100644
--- a/playbooks/provisioning/openstack/stack_params.yaml
+++ b/playbooks/provisioning/openstack/stack_params.yaml
@@ -29,3 +29,4 @@ app_volume_size: "{{ docker_volume_size }}"
 infra_volume_size: "{{ docker_volume_size }}"
 nodes_to_remove: "{{ openstack_nodes_to_remove | default([]) |  to_yaml }}"
 use_bastion: "{{ openstack_use_bastion|default(False) }}"
+ui_ssh_tunnel: "{{ openshift_ui_ssh_tunnel|default(False) }}"
diff --git a/roles/openstack-stack/defaults/main.yml b/roles/openstack-stack/defaults/main.yml
index 803a96389..c8529612e 100644
--- a/roles/openstack-stack/defaults/main.yml
+++ b/roles/openstack-stack/defaults/main.yml
@@ -13,3 +13,4 @@ num_infra: 1
 nodes_to_remove: []
 etcd_volume_size: 2
 use_bastion: False
+ui_ssh_tunnel: False
diff --git a/roles/openstack-stack/templates/heat_stack.yaml.j2 b/roles/openstack-stack/templates/heat_stack.yaml.j2
index c41bf15be..a670ff0e3 100644
--- a/roles/openstack-stack/templates/heat_stack.yaml.j2
+++ b/roles/openstack-stack/templates/heat_stack.yaml.j2
@@ -439,7 +439,7 @@ resources:
           port_range_min: 53
           port_range_max: 53
           remote_ip_prefix: "{{ openstack_subnet_prefix }}.0/24"
-{% if num_masters > 1 %}
+{% if num_masters > 1 or ui_ssh_tunnel|bool %}
   lb-secgrp:
     type: OS::Neutron::SecurityGroup
     properties:
@@ -450,14 +450,21 @@ resources:
         protocol: tcp
         port_range_min: {{ openshift_master_api_port | default(8443) }}
         port_range_max: {{ openshift_master_api_port | default(8443) }}
-        remote_ip_prefix: {{ lb_ingress_cidr }}
-  {% if openshift_master_console_port is defined and openshift_master_console_port != openshift_master_api_port %}
+        remote_ip_prefix: {{ lb_ingress_cidr | default(bastion_ingress_cidr) }}
+{% if ui_ssh_tunnel|bool %}
+      - direction: ingress
+        protocol: tcp
+        port_range_min: {{ openshift_master_api_port | default(8443) }}
+        port_range_max: {{ openshift_master_api_port | default(8443) }}
+        remote_ip_prefix: {{ ssh_ingress_cidr }}
+{% endif %}
+{% if openshift_master_console_port is defined and openshift_master_console_port != openshift_master_api_port %}
       - direction: ingress
         protocol: tcp
         port_range_min: {{ openshift_master_console_port | default(8443) }}
         port_range_max: {{ openshift_master_console_port | default(8443) }}
-        remote_ip_prefix: {{ lb_ingress_cidr }}
-  {% endif %}
+        remote_ip_prefix: {{ lb_ingress_cidr | default(bastion_ingress_cidr) }}
+{% endif %}
 {% endif %}
 
   etcd:
@@ -695,6 +702,9 @@ resources:
             - { get_resource: flat-secgrp }
 {% else %}
             - { get_resource: node-secgrp }
+{% endif %}
+{% if ui_ssh_tunnel|bool and num_masters < 2 %}
+            - { get_resource: lb-secgrp }
 {% endif %}
             - { get_resource: infra-secgrp }
             - { get_resource: common-secgrp }
diff --git a/roles/static_inventory/defaults/main.yml b/roles/static_inventory/defaults/main.yml
index 5b8aacf5c..871700f8c 100644
--- a/roles/static_inventory/defaults/main.yml
+++ b/roles/static_inventory/defaults/main.yml
@@ -20,4 +20,10 @@ private_ssh_key: ~/.ssh/openshift
 # The patch to store the generated config to access bastion/hosts
 ssh_config_path: /tmp/ssh.config.ansible
 
+# The IP:port to make an SSH tunnel to access UI on the 1st master
+# via bastion node (requires sudo on the ansible control node)
+ui_ssh_tunnel: False
+ui_port: "{{ openshift_master_api_port | default(8443) }}"
+target_ip: "{{ hostvars[groups['masters.' + stack_name|quote][0]].private_v4 }}"
+
 openstack_private_network: private
diff --git a/roles/static_inventory/tasks/main.yml b/roles/static_inventory/tasks/main.yml
index b58866017..24e11beb6 100644
--- a/roles/static_inventory/tasks/main.yml
+++ b/roles/static_inventory/tasks/main.yml
@@ -8,3 +8,10 @@
 - name: Generate SSH config for accessing hosts via bastion
   include: sshconfig.yml
   when: use_bastion|bool
+
+- name: Configure SSH tunneling to access UI
+  include: sshtun.yml
+  become: true
+  when:
+    - use_bastion|bool
+    - ui_ssh_tunnel|bool
diff --git a/roles/static_inventory/tasks/sshtun.yml b/roles/static_inventory/tasks/sshtun.yml
new file mode 100644
index 000000000..b0e4c832c
--- /dev/null
+++ b/roles/static_inventory/tasks/sshtun.yml
@@ -0,0 +1,15 @@
+---
+- name: Create ssh tunnel systemd service
+  template:
+    src: ssh-tunnel.service.j2
+    dest: /etc/systemd/system/ssh-tunnel.service
+    mode: 0644
+
+- name: reload the systemctl daemon after file update
+  command: systemctl daemon-reload
+
+- name: Enable ssh tunnel service
+  service:
+    name: ssh-tunnel
+    enabled: true
+    state: restarted
diff --git a/roles/static_inventory/templates/ssh-tunnel.service.j2 b/roles/static_inventory/templates/ssh-tunnel.service.j2
new file mode 100644
index 000000000..0d1cf8f79
--- /dev/null
+++ b/roles/static_inventory/templates/ssh-tunnel.service.j2
@@ -0,0 +1,20 @@
+[Unit]
+Description=Set up ssh tunneling for OpenShift cluster UI
+After=network.target
+
+[Service]
+ExecStart=/usr/bin/ssh -NT -o \
+   ServerAliveInterval=60 -o \
+   UserKnownHostsFile=/dev/null -o \
+   StrictHostKeyChecking=no -o \
+   ExitOnForwardFailure=no -i \
+   {{ private_ssh_key }} {{ ssh_user }}@{{ hostvars['bastion'].ansible_host }} \
+   -L 0.0.0.0:{{ ui_port }}:{{ target_ip }}:{{ ui_port }}
+
+
+# Restart every >2 seconds to avoid StartLimitInterval failure
+RestartSec=5
+Restart=always
+
+[Install]
+WantedBy=multi-user.target
-- 
cgit v1.2.3