146 lines
5.8 KiB
Python
146 lines
5.8 KiB
Python
# -*- coding: utf-8 -*- #
|
|
# Copyright 2021 Google LLC. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""Troubleshoot VPC setting for ssh connection."""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import unicode_literals
|
|
|
|
from googlecloudsdk.api_lib.util import apis
|
|
from googlecloudsdk.command_lib.compute import ssh_troubleshooter
|
|
from googlecloudsdk.core import log
|
|
|
|
_API_COMPUTE_CLIENT_NAME = 'compute'
|
|
_API_CLIENT_VERSION_V1 = 'v1'
|
|
|
|
IAP_MESSAGE = (
|
|
'There is an issue with your IAP configuration\n'
|
|
'\n'
|
|
'Check the following items:\n'
|
|
' - The IAP firewall rule is valid.\n'
|
|
' - IAP tunneling is enabled.\n'
|
|
' - You are connecting using an IAP token.\n'
|
|
' - You have the IAM role of Project Owner, IAP-Secured Tunnel User, or '
|
|
'iap.tunnelInstances.accessViaIAP (preferred)\n'
|
|
' - Your organization hasn\'t blocked access to external IP addresses. '
|
|
'IAP changes the source traffic to 35.235.240.0/20 and the tunnel to '
|
|
'https://tunnel.cloudproxy.app.\n'
|
|
' - If your organization blocks access to public IP addresses, try '
|
|
'connecting through a bastion server.\n'
|
|
'\n'
|
|
'Help for IAP port forwarding: '
|
|
'https://cloud.google.com/iap/docs/using-tcp-forwarding\n'
|
|
'https://cloud.google.com/iap/docs/faq#what_domain_does_for_tcp_use'
|
|
'\n'
|
|
'Help for bastion server: '
|
|
'https://cloud.google.com/compute/docs/instances/connecting-advanced#bastion_host\n') # pylint: disable=line-too-long
|
|
|
|
|
|
DEFAULT_SSH_PORT_MESSAGE = (
|
|
'No ingress firewall rule allowing SSH found.\n'
|
|
'\n'
|
|
'If the project uses the default ingress firewall rule for SSH, connections'
|
|
' to all VMs are allowed on TCP port 22.\n'
|
|
'If the VPC network that the VM\'s network interface is in has a custom '
|
|
'firewall rule, make sure that custom rule allows ingress traffic on the '
|
|
'VM\'s SSH TCP port (usually, this is TCP port 22).\n'
|
|
'Help for default firewall rule: '
|
|
'https://cloud.google.com/vpc/docs/vpc#default-network\n'
|
|
'Help for custom firewall rule: '
|
|
'https://cloud.google.com/network-connectivity/docs/vpn/how-to/configuring-firewall-rules\n' # pylint: disable=line-too-long
|
|
'\n'
|
|
"If you need to investigate further, enable the VM's serial console. "
|
|
"Then connect through the VM serial port, find the SSH server's listen "
|
|
"port, and make sure the port number in the VM's firewall rules matches"
|
|
" the SSH server's listen port.\n"
|
|
'Help for serial console: https://cloud.google.com/compute/docs/instances/interacting-with-serial-console\n' # pylint: disable=line-too-long
|
|
'Help for serial port: https://cloud.google.com/compute/docs/instances/interacting-with-serial-console\n' # pylint: disable=line-too-long
|
|
'Help for firewall rules: https://cloud.google.com/vpc/docs/using-firewalls\n') # pylint: disable=line-too-long
|
|
|
|
|
|
class VPCTroubleshooter(ssh_troubleshooter.SshTroubleshooter):
|
|
"""Check VPC setting."""
|
|
|
|
project = None
|
|
zone = None
|
|
instance = None
|
|
iap_tunnel_args = None
|
|
|
|
def __init__(self, project, zone, instance, iap_tunnel_args):
|
|
self.project = project
|
|
self.zone = zone
|
|
self.instance = instance
|
|
self.iap_tunnel_args = iap_tunnel_args
|
|
self.compute_client = apis.GetClientInstance(_API_COMPUTE_CLIENT_NAME,
|
|
_API_CLIENT_VERSION_V1)
|
|
self.compute_message = apis.GetMessagesModule(_API_COMPUTE_CLIENT_NAME,
|
|
_API_CLIENT_VERSION_V1)
|
|
self.issues = {}
|
|
|
|
def check_prerequisite(self):
|
|
return
|
|
|
|
def cleanup_resources(self):
|
|
return
|
|
|
|
def troubleshoot(self):
|
|
log.status.Print('---- Checking VPC settings ----')
|
|
self._CheckDefaultSSHPort()
|
|
if self.iap_tunnel_args:
|
|
self._CheckIAPTunneling()
|
|
log.status.Print('VPC settings: {0} issue(s) found.\n'.format(
|
|
len(self.issues)))
|
|
for message in self.issues.values():
|
|
log.status.Print(message)
|
|
return
|
|
|
|
def _CheckIAPTunneling(self):
|
|
firewall_list = self._ListInstanceEffectiveFirewall()
|
|
for firewall in firewall_list:
|
|
if self._HasValidateIAPTunnelingRule(firewall):
|
|
return
|
|
self.issues['iap'] = IAP_MESSAGE
|
|
|
|
def _CheckDefaultSSHPort(self):
|
|
firewall_list = self._ListInstanceEffectiveFirewall()
|
|
for firewall in firewall_list:
|
|
if self._HasSSHProtocalAndPort(firewall):
|
|
return
|
|
self.issues['default_ssh_port'] = DEFAULT_SSH_PORT_MESSAGE
|
|
|
|
def _ListInstanceEffectiveFirewall(self):
|
|
req = self.compute_message.ComputeInstancesGetEffectiveFirewallsRequest(
|
|
instance=self.instance.name,
|
|
networkInterface='nic0',
|
|
project=self.project.name,
|
|
zone=self.zone)
|
|
return self.compute_client.instances.GetEffectiveFirewalls(req).firewalls
|
|
|
|
def _HasValidateIAPTunnelingRule(self, firewall):
|
|
if firewall.direction != self.compute_message.Firewall.DirectionValueValuesEnum.INGRESS:
|
|
return False
|
|
if all(range != '35.235.240.0/20' for range in firewall.sourceRanges):
|
|
return False
|
|
if not self._HasSSHProtocalAndPort(firewall):
|
|
return False
|
|
return True
|
|
|
|
def _HasSSHProtocalAndPort(self, firewall):
|
|
for allow_rule in firewall.allowed:
|
|
if allow_rule.IPProtocol == 'tcp' and any(
|
|
port == '22' for port in allow_rule.ports):
|
|
return True
|
|
return False
|