547 lines
22 KiB
Python
547 lines
22 KiB
Python
# -*- coding: utf-8 -*- #
|
|
# Copyright 2019 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.
|
|
"""Command for creating VPN tunnels."""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import unicode_literals
|
|
|
|
import argparse
|
|
import re
|
|
|
|
from googlecloudsdk.api_lib.compute import base_classes
|
|
from googlecloudsdk.api_lib.compute.vpn_tunnels import vpn_tunnels_utils
|
|
from googlecloudsdk.calliope import arg_parsers
|
|
from googlecloudsdk.calliope import base
|
|
from googlecloudsdk.calliope import exceptions
|
|
from googlecloudsdk.command_lib.compute import flags as compute_flags
|
|
from googlecloudsdk.command_lib.compute import resource_manager_tags_utils
|
|
from googlecloudsdk.command_lib.compute.external_vpn_gateways import flags as external_vpn_gateway_flags
|
|
from googlecloudsdk.command_lib.compute.routers import flags as router_flags
|
|
from googlecloudsdk.command_lib.compute.target_vpn_gateways import flags as target_vpn_gateway_flags
|
|
from googlecloudsdk.command_lib.compute.vpn_gateways import flags as vpn_gateway_flags
|
|
from googlecloudsdk.command_lib.compute.vpn_tunnels import flags
|
|
import six
|
|
|
|
|
|
_PRINTABLE_CHARS_PATTERN = r'[ -~]+'
|
|
|
|
_ROUTER_ARG = router_flags.RouterArgumentForVpnTunnel(required=False)
|
|
_VPN_TUNNEL_ARG = flags.VpnTunnelArgument()
|
|
|
|
|
|
class DeprecatedArgumentException(exceptions.ToolException):
|
|
|
|
def __init__(self, arg, msg):
|
|
super(DeprecatedArgumentException, self).__init__(
|
|
'{0} is deprecated. {1}'.format(arg, msg))
|
|
|
|
|
|
def ValidateSimpleSharedSecret(possible_secret):
|
|
"""ValidateSimpleSharedSecret checks its argument is a vpn shared secret.
|
|
|
|
ValidateSimpleSharedSecret(v) returns v iff v matches [ -~]+.
|
|
|
|
Args:
|
|
possible_secret: str, The data to validate as a shared secret.
|
|
|
|
Returns:
|
|
The argument, if valid.
|
|
|
|
Raises:
|
|
ArgumentTypeError: The argument is not a valid vpn shared secret.
|
|
"""
|
|
|
|
if not possible_secret:
|
|
raise argparse.ArgumentTypeError(
|
|
'--shared-secret requires a non-empty argument.')
|
|
|
|
if re.match(_PRINTABLE_CHARS_PATTERN, possible_secret):
|
|
return possible_secret
|
|
|
|
raise argparse.ArgumentTypeError(
|
|
'The argument to --shared-secret is not valid it contains '
|
|
'non-printable charcters.')
|
|
|
|
|
|
@base.ReleaseTracks(base.ReleaseTrack.GA)
|
|
@base.UniverseCompatible
|
|
class CreateGA(base.CreateCommand):
|
|
"""Create a VPN tunnel.
|
|
|
|
*{command}* is used to create a Classic VPN tunnel between a target VPN
|
|
gateway in Google Cloud Platform and a peer address; or create Highly
|
|
Available VPN tunnel between HA VPN gateway and another HA VPN gateway, or
|
|
Highly Available VPN tunnel between HA VPN gateway and an external VPN
|
|
gateway.
|
|
"""
|
|
|
|
_TARGET_VPN_GATEWAY_ARG = (
|
|
target_vpn_gateway_flags.TargetVpnGatewayArgumentForVpnTunnel(
|
|
required=False))
|
|
_VPN_GATEWAY_ARG = (
|
|
vpn_gateway_flags.GetVpnGatewayArgumentForOtherResource(required=False))
|
|
|
|
_EXTERNAL_VPN_GATEWAY_ARG = (
|
|
external_vpn_gateway_flags.ExternalVpnGatewayArgumentForVpnTunnel(
|
|
required=False))
|
|
|
|
_PEER_GCP_GATEWAY_ARG = (
|
|
vpn_gateway_flags.GetPeerVpnGatewayArgumentForOtherResource(
|
|
required=False))
|
|
|
|
_support_cipher_suite = True
|
|
_support_tagging_at_creation = False
|
|
|
|
@classmethod
|
|
def _AddCommonFlags(cls, parser):
|
|
_ROUTER_ARG.AddArgument(parser)
|
|
|
|
parser.add_argument(
|
|
'--description',
|
|
help='An optional, textual description for the VPN tunnel.')
|
|
|
|
parser.add_argument(
|
|
'--ike-version',
|
|
choices=[1, 2],
|
|
type=int,
|
|
help='Internet Key Exchange protocol version number. Default is 2.')
|
|
|
|
parser.add_argument(
|
|
'--shared-secret',
|
|
type=ValidateSimpleSharedSecret,
|
|
required=True,
|
|
help="""\
|
|
Shared secret consisting of printable characters. Valid
|
|
arguments match the regular expression """ + _PRINTABLE_CHARS_PATTERN)
|
|
|
|
parser.add_argument(
|
|
'--ike-networks',
|
|
type=arg_parsers.ArgList(min_length=1),
|
|
hidden=True,
|
|
help='THIS ARGUMENT NEEDS HELP TEXT.')
|
|
|
|
@classmethod
|
|
def _AddCipherSuiteFlags(cls, parser):
|
|
parser.add_argument('--phase1-encryption',
|
|
metavar='ALGORITHMS',
|
|
type=arg_parsers.ArgList(min_length=1),
|
|
help='Phase 1 encryption algorithms.')
|
|
parser.add_argument('--phase1-integrity',
|
|
metavar='ALGORITHMS',
|
|
type=arg_parsers.ArgList(min_length=1),
|
|
help='Phase 1 integrity algorithms.')
|
|
parser.add_argument('--phase1-prf',
|
|
metavar='PSEUDORANDOM FUNCTIONS',
|
|
type=arg_parsers.ArgList(min_length=1),
|
|
help='Phase 1 pseudorandom functions.')
|
|
parser.add_argument('--phase1-dh',
|
|
metavar='GROUPS',
|
|
type=arg_parsers.ArgList(min_length=1),
|
|
help='Phase 1 Diffie-Hellman groups.')
|
|
parser.add_argument('--phase2-encryption',
|
|
metavar='ALGORITHMS',
|
|
type=arg_parsers.ArgList(min_length=1),
|
|
help='Phase 2 encryption algorithms.')
|
|
parser.add_argument('--phase2-integrity',
|
|
metavar='ALGORITHMS',
|
|
type=arg_parsers.ArgList(min_length=1),
|
|
help='Phase 2 integrity algorithms.')
|
|
parser.add_argument('--phase2-pfs',
|
|
metavar='ALGORITHMS',
|
|
type=arg_parsers.ArgList(min_length=1),
|
|
help='Phase 2 perfect forward secerecy algorithms.')
|
|
|
|
@classmethod
|
|
def Args(cls, parser):
|
|
"""Adds arguments to the supplied parser."""
|
|
# TODO(b/129011963): add e2e tests for HA VPN tunnels
|
|
parser.display_info.AddFormat(flags.HA_VPN_LIST_FORMAT)
|
|
|
|
_VPN_TUNNEL_ARG.AddArgument(parser, operation_type='create')
|
|
vpn_gateway_group_parser = parser.add_mutually_exclusive_group(
|
|
required=True)
|
|
cls._TARGET_VPN_GATEWAY_ARG.AddArgument(vpn_gateway_group_parser)
|
|
cls._VPN_GATEWAY_ARG.AddArgument(vpn_gateway_group_parser)
|
|
|
|
peer_vpn_gateway_group_parser = parser.add_mutually_exclusive_group(
|
|
required=True)
|
|
cls._EXTERNAL_VPN_GATEWAY_ARG.AddArgument(peer_vpn_gateway_group_parser)
|
|
cls._PEER_GCP_GATEWAY_ARG.AddArgument(peer_vpn_gateway_group_parser)
|
|
peer_vpn_gateway_group_parser.add_argument(
|
|
'--peer-address',
|
|
required=False,
|
|
help='Valid IPV4 address representing the remote tunnel endpoint, '
|
|
'the peer address must be specified when creating Classic VPN '
|
|
'tunnels from Classic Target VPN gateway')
|
|
|
|
cls._AddCommonFlags(parser)
|
|
|
|
parser.add_argument(
|
|
'--local-traffic-selector',
|
|
type=arg_parsers.ArgList(min_length=1),
|
|
metavar='CIDR',
|
|
help=("""\
|
|
Traffic selector is an agreement between IKE peers to permit traffic
|
|
through a tunnel if the traffic matches a specified pair of local and
|
|
remote addresses.
|
|
|
|
--local-traffic-selector allows to configure the local addresses that are
|
|
permitted. The value should be a comma separated list of CIDR formatted
|
|
strings. Example: 192.168.0.0/16,10.0.0.0/24.
|
|
|
|
Local traffic selector must be specified only for VPN tunnels that
|
|
do not use dynamic routing with a Cloud Router. Omit this flag when
|
|
creating a tunnel using dynamic routing, including a tunnel for a
|
|
Highly Available VPN gateway."""))
|
|
|
|
parser.add_argument(
|
|
'--remote-traffic-selector',
|
|
type=arg_parsers.ArgList(min_length=1),
|
|
metavar='CIDR',
|
|
help=("""\
|
|
Traffic selector is an agreement between IKE peers to permit traffic
|
|
through a tunnel if the traffic matches a specified pair of local and
|
|
remote addresses.
|
|
|
|
--remote-traffic-selector allows to configure the remote addresses that
|
|
are permitted. The value should be a comma separated list of CIDR
|
|
formatted strings. Example: 192.168.0.0/16,10.0.0.0/24.
|
|
|
|
Remote traffic selector must be specified for VPN tunnels that do
|
|
not use dynamic routing with a Cloud Router. Omit this flag when
|
|
creating a tunnel using dynamic routing, including a tunnel for a
|
|
Highly Available VPN gateway."""))
|
|
|
|
parser.add_argument(
|
|
'--interface',
|
|
choices=[0, 1],
|
|
type=int,
|
|
required=False,
|
|
help="""\
|
|
Numeric interface ID of the VPN gateway with which this VPN tunnel
|
|
is associated. This flag is required if the tunnel is being attached
|
|
to a Highly Available VPN gateway. This option is only available
|
|
for use with Highly Available VPN gateway and must be omitted if the
|
|
tunnel is going to be connected to a Classic VPN gateway.""")
|
|
|
|
parser.add_argument(
|
|
'--peer-external-gateway-interface',
|
|
choices=[0, 1, 2, 3],
|
|
type=int,
|
|
required=False,
|
|
help="""\
|
|
Interface ID of the external VPN gateway to which this VPN tunnel
|
|
is connected to.
|
|
This flag is required if the tunnel is being created from
|
|
a Highly Available VPN gateway to an External Vpn Gateway.""")
|
|
|
|
if(cls._support_cipher_suite):
|
|
cls._AddCipherSuiteFlags(parser)
|
|
|
|
if cls._support_tagging_at_creation:
|
|
parser.add_argument(
|
|
'--resource-manager-tags',
|
|
type=arg_parsers.ArgDict(),
|
|
metavar='KEY=VALUE',
|
|
help="""\
|
|
A comma-separated list of Resource Manager tags to apply to the VPN tunnel.
|
|
""",
|
|
)
|
|
|
|
parser.display_info.AddCacheUpdater(flags.VpnTunnelsCompleter)
|
|
|
|
def _ValidateHighAvailabilityVpnArgs(self, args):
|
|
if args.IsSpecified('vpn_gateway'):
|
|
if not args.IsSpecified('interface'):
|
|
raise exceptions.InvalidArgumentException(
|
|
'--interface',
|
|
'When creating Highly Available VPN tunnels, the VPN gateway '
|
|
'interface must be specified using the --interface flag.')
|
|
if not args.IsSpecified('router'):
|
|
raise exceptions.InvalidArgumentException(
|
|
'--router',
|
|
'When creating Highly Available VPN tunnels, a Cloud Router '
|
|
'must be specified using the --router flag.')
|
|
if not args.IsSpecified('peer_gcp_gateway') and not args.IsSpecified(
|
|
'peer_external_gateway'):
|
|
raise exceptions.InvalidArgumentException(
|
|
'--peer-gcp-gateway',
|
|
'When creating Highly Available VPN tunnels, either '
|
|
'--peer-gcp-gateway or --peer-external-gateway must be specified.')
|
|
if args.IsSpecified('peer_external_gateway') and not args.IsSpecified(
|
|
'peer_external_gateway_interface'):
|
|
raise exceptions.InvalidArgumentException(
|
|
'--peer-external-gateway-interface',
|
|
'The flag --peer-external-gateway-interface must be specified along'
|
|
' with --peer-external-gateway.')
|
|
if args.IsSpecified('local_traffic_selector'):
|
|
raise exceptions.InvalidArgumentException(
|
|
'--local-traffic-selector',
|
|
'Cannot specify local traffic selector with Highly Available '
|
|
'VPN tunnels.')
|
|
if args.IsSpecified('remote_traffic_selector'):
|
|
raise exceptions.InvalidArgumentException(
|
|
'--remote-traffic-selector',
|
|
'Cannot specify remote traffic selector with Highly Available '
|
|
'VPN tunnels.')
|
|
if args.IsSpecified('peer_address'):
|
|
raise exceptions.InvalidArgumentException(
|
|
'--peer-address',
|
|
'Cannot specify the flag peer address with Highly Available '
|
|
'VPN tunnels.')
|
|
|
|
def _ValidateClassicVpnArgs(self, args):
|
|
if args.IsSpecified('target_vpn_gateway'):
|
|
if not args.IsSpecified('peer_address'):
|
|
raise exceptions.InvalidArgumentException(
|
|
'--peer-address',
|
|
'When creating Classic VPN tunnels, the peer address '
|
|
'must be specified.')
|
|
if args.IsSpecified('router'):
|
|
raise exceptions.InvalidArgumentException(
|
|
'--router',
|
|
'Cannot specify router with Classic VPN tunnels.',
|
|
)
|
|
|
|
def _GetPeerGcpGateway(self, api_resource_registry, args):
|
|
if args.IsSpecified('peer_gcp_gateway'):
|
|
peer_gcp_gateway = self._PEER_GCP_GATEWAY_ARG.ResolveAsResource(
|
|
args, api_resource_registry).SelfLink()
|
|
return peer_gcp_gateway
|
|
return None
|
|
|
|
def _GetPeerExternalGateway(self, api_resource_registry, args):
|
|
if args.IsSpecified('peer_external_gateway'):
|
|
peer_external_gateway = self._EXTERNAL_VPN_GATEWAY_ARG.ResolveAsResource(
|
|
args, api_resource_registry).SelfLink()
|
|
return peer_external_gateway
|
|
return None
|
|
|
|
def _Run(self, args, is_vpn_gateway_supported):
|
|
holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
|
|
client = holder.client
|
|
helper = vpn_tunnels_utils.VpnTunnelHelper(holder)
|
|
|
|
# TODO(b/38253176) Add test coverage
|
|
if args.ike_networks is not None:
|
|
raise DeprecatedArgumentException(
|
|
'--ike-networks',
|
|
'It has been renamed to --local-traffic-selector.')
|
|
|
|
vpn_tunnel_ref = _VPN_TUNNEL_ARG.ResolveAsResource(
|
|
args,
|
|
holder.resources,
|
|
scope_lister=compute_flags.GetDefaultScopeLister(client))
|
|
|
|
router_link = None
|
|
if args.IsSpecified('router'):
|
|
args.router_region = vpn_tunnel_ref.region
|
|
router_ref = _ROUTER_ARG.ResolveAsResource(args, holder.resources)
|
|
router_link = router_ref.SelfLink()
|
|
|
|
target_vpn_gateway = None
|
|
vpn_gateway = None
|
|
vpn_gateway_interface = None
|
|
peer_external_gateway = None
|
|
peer_external_gateway_interface = None
|
|
peer_gcp_gateway = None
|
|
resource_manager_tags = None
|
|
|
|
if is_vpn_gateway_supported and args.IsSpecified('vpn_gateway'):
|
|
self._ValidateHighAvailabilityVpnArgs(args)
|
|
args.vpn_gateway_region = vpn_tunnel_ref.region
|
|
vpn_gateway = self._VPN_GATEWAY_ARG.ResolveAsResource(
|
|
args, holder.resources
|
|
).SelfLink()
|
|
vpn_gateway_interface = args.interface
|
|
peer_external_gateway = self._GetPeerExternalGateway(
|
|
holder.resources, args
|
|
)
|
|
peer_external_gateway_interface = args.peer_external_gateway_interface
|
|
peer_gcp_gateway = self._GetPeerGcpGateway(holder.resources, args)
|
|
else:
|
|
self._ValidateClassicVpnArgs(args)
|
|
args.target_vpn_gateway_region = vpn_tunnel_ref.region
|
|
target_vpn_gateway = self._TARGET_VPN_GATEWAY_ARG.ResolveAsResource(
|
|
args, holder.resources
|
|
).SelfLink()
|
|
|
|
if self._support_tagging_at_creation:
|
|
if args.resource_manager_tags is not None:
|
|
resource_manager_tags = self._CreateVpnTunnelParams(
|
|
client.messages, args.resource_manager_tags
|
|
)
|
|
|
|
if target_vpn_gateway:
|
|
if self._support_cipher_suite:
|
|
phase1_algo = helper.GetVpnTunnelPhase1Algorithms(
|
|
phase1_encryption=args.phase1_encryption,
|
|
phase1_integrity=args.phase1_integrity,
|
|
phase1_dh=args.phase1_dh,
|
|
phase1_prf=args.phase1_prf,
|
|
)
|
|
phase2_algo = helper.GetVpnTunnelPhase2Algorithms(
|
|
phase2_encryption=args.phase2_encryption,
|
|
phase2_integrity=args.phase2_integrity,
|
|
phase2_pfs=args.phase2_pfs,
|
|
)
|
|
cipher_suite = client.messages.VpnTunnelCipherSuite()
|
|
if phase1_algo:
|
|
cipher_suite.phase1 = phase1_algo
|
|
if phase2_algo:
|
|
cipher_suite.phase2 = phase2_algo
|
|
if not cipher_suite.phase1 and not cipher_suite.phase2:
|
|
cipher_suite = None
|
|
vpn_tunnel_to_insert = (
|
|
helper.GetClassicVpnTunnelForInsertWithCipherSuite(
|
|
name=vpn_tunnel_ref.Name(),
|
|
description=args.description,
|
|
ike_version=args.ike_version,
|
|
peer_ip=args.peer_address,
|
|
shared_secret=args.shared_secret,
|
|
target_vpn_gateway=target_vpn_gateway,
|
|
local_traffic_selector=args.local_traffic_selector,
|
|
remote_traffic_selector=args.remote_traffic_selector,
|
|
cipher_suite=cipher_suite,
|
|
params=resource_manager_tags,
|
|
support_tagging_at_creation=self._support_tagging_at_creation,
|
|
)
|
|
)
|
|
else:
|
|
vpn_tunnel_to_insert = helper.GetClassicVpnTunnelForInsert(
|
|
name=vpn_tunnel_ref.Name(),
|
|
description=args.description,
|
|
ike_version=args.ike_version,
|
|
peer_ip=args.peer_address,
|
|
shared_secret=args.shared_secret,
|
|
target_vpn_gateway=target_vpn_gateway,
|
|
local_traffic_selector=args.local_traffic_selector,
|
|
remote_traffic_selector=args.remote_traffic_selector,
|
|
params=resource_manager_tags,
|
|
support_tagging_at_creation=self._support_tagging_at_creation,
|
|
)
|
|
else:
|
|
if(self._support_cipher_suite):
|
|
phase1_algo = helper.GetVpnTunnelPhase1Algorithms(
|
|
phase1_encryption=args.phase1_encryption,
|
|
phase1_integrity=args.phase1_integrity,
|
|
phase1_dh=args.phase1_dh,
|
|
phase1_prf=args.phase1_prf,
|
|
)
|
|
phase2_algo = helper.GetVpnTunnelPhase2Algorithms(
|
|
phase2_encryption=args.phase2_encryption,
|
|
phase2_integrity=args.phase2_integrity,
|
|
phase2_pfs=args.phase2_pfs,
|
|
)
|
|
cipher_suite = client.messages.VpnTunnelCipherSuite()
|
|
if phase1_algo:
|
|
cipher_suite.phase1 = phase1_algo
|
|
if phase2_algo:
|
|
cipher_suite.phase2 = phase2_algo
|
|
if not cipher_suite.phase1 and not cipher_suite.phase2:
|
|
cipher_suite = None
|
|
vpn_tunnel_to_insert = (
|
|
helper.GetHighAvailabilityVpnTunnelForInsertWithCipherSuite(
|
|
name=vpn_tunnel_ref.Name(),
|
|
description=args.description,
|
|
ike_version=args.ike_version,
|
|
peer_ip=args.peer_address,
|
|
shared_secret=args.shared_secret,
|
|
vpn_gateway=vpn_gateway,
|
|
vpn_gateway_interface=vpn_gateway_interface,
|
|
router=router_link,
|
|
peer_external_gateway=peer_external_gateway,
|
|
peer_external_gateway_interface=peer_external_gateway_interface,
|
|
peer_gcp_gateway=peer_gcp_gateway,
|
|
cipher_suite=cipher_suite,
|
|
params=resource_manager_tags,
|
|
support_tagging_at_creation=self._support_tagging_at_creation,
|
|
)
|
|
)
|
|
else:
|
|
vpn_tunnel_to_insert = helper.GetHighAvailabilityVpnTunnelForInsert(
|
|
name=vpn_tunnel_ref.Name(),
|
|
description=args.description,
|
|
ike_version=args.ike_version,
|
|
# TODO(b/127839209): remove peer_ip for HA
|
|
# tunnels once peer gateway
|
|
# feature is enabled in Arcus.
|
|
peer_ip=args.peer_address,
|
|
shared_secret=args.shared_secret,
|
|
vpn_gateway=vpn_gateway,
|
|
vpn_gateway_interface=vpn_gateway_interface,
|
|
router=router_link,
|
|
peer_external_gateway=peer_external_gateway,
|
|
peer_external_gateway_interface=peer_external_gateway_interface,
|
|
peer_gcp_gateway=peer_gcp_gateway,
|
|
params=resource_manager_tags,
|
|
support_tagging_at_creation=self._support_tagging_at_creation,
|
|
)
|
|
|
|
operation_ref = helper.Create(vpn_tunnel_ref, vpn_tunnel_to_insert)
|
|
return helper.WaitForOperation(vpn_tunnel_ref, operation_ref,
|
|
'Creating VPN tunnel')
|
|
|
|
def Run(self, args):
|
|
"""Issues API requests to construct VPN Tunnels."""
|
|
return self._Run(args, is_vpn_gateway_supported=True)
|
|
|
|
def _CreateVpnTunnelParams(self, messages, resource_manager_tags):
|
|
resource_manager_tags_map = (
|
|
resource_manager_tags_utils.GetResourceManagerTags(
|
|
resource_manager_tags
|
|
)
|
|
)
|
|
params = messages.VpnTunnelParams
|
|
additional_properties = [
|
|
params.ResourceManagerTagsValue.AdditionalProperty(key=key, value=value)
|
|
for key, value in sorted(six.iteritems(resource_manager_tags_map))
|
|
]
|
|
return params(
|
|
resourceManagerTags=params.ResourceManagerTagsValue(
|
|
additionalProperties=additional_properties
|
|
)
|
|
)
|
|
|
|
|
|
@base.ReleaseTracks(base.ReleaseTrack.BETA)
|
|
class CreateBeta(CreateGA):
|
|
"""Create a VPN tunnel.
|
|
|
|
*{command}* is used to create a Classic VPN tunnel between a target VPN
|
|
gateway in Google Cloud Platform and a peer address; or create Highly
|
|
Available VPN tunnel between HA VPN gateway and another HA VPN gateway, or
|
|
Highly Available VPN tunnel between HA VPN gateway and an external VPN
|
|
gateway.
|
|
"""
|
|
|
|
_support_cipher_suite = True
|
|
_support_tagging_at_creation = False
|
|
|
|
|
|
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
|
|
class CreateAlpha(CreateBeta):
|
|
"""Create a VPN tunnel.
|
|
|
|
*{command}* is used to create a Classic VPN tunnel between a target VPN
|
|
gateway in Google Cloud Platform and a peer address; or create Highly
|
|
Available VPN tunnel between HA VPN gateway and another HA VPN gateway, or
|
|
Highly Available VPN tunnel between HA VPN gateway and an external VPN
|
|
gateway.
|
|
"""
|
|
_support_cipher_suite = True
|
|
_support_tagging_at_creation = True
|