1# Copyright 2020 gRPC authors.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Configure Traffic Director for different GRPC Proxyless.
15
16This is intended as a debugging / local development helper and not executed
17as a part of interop test suites.
18
19Typical usage examples:
20
21    # Regular proxyless setup
22    python -m bin.run_td_setup --flagfile=config/local-dev.cfg
23
24    # Additional commands: cleanup, backend management, etc.
25    python -m bin.run_td_setup --flagfile=config/local-dev.cfg --cmd=cleanup
26
27    # PSM security setup options: mtls, tls, etc.
28    python -m bin.run_td_setup --flagfile=config/local-dev.cfg --security=mtls
29
30    # More information and usage options
31    python -m bin.run_td_setup --helpfull
32"""
33import logging
34
35from absl import app
36from absl import flags
37
38from framework import xds_flags
39from framework import xds_k8s_flags
40from framework.helpers import rand
41from framework.infrastructure import gcp
42from framework.infrastructure import k8s
43from framework.infrastructure import traffic_director
44from framework.test_app.runners.k8s import k8s_xds_server_runner
45
46logger = logging.getLogger(__name__)
47# Flags
48_CMD = flags.DEFINE_enum('cmd',
49                         default='create',
50                         enum_values=[
51                             'cycle', 'create', 'cleanup', 'backends-add',
52                             'backends-cleanup', 'unused-xds-port'
53                         ],
54                         help='Command')
55_SECURITY = flags.DEFINE_enum('security',
56                              default=None,
57                              enum_values=[
58                                  'mtls', 'tls', 'plaintext', 'mtls_error',
59                                  'server_authz_error'
60                              ],
61                              help='Configure TD with security')
62flags.adopt_module_key_flags(xds_flags)
63flags.adopt_module_key_flags(xds_k8s_flags)
64# Running outside of a test suite, so require explicit resource_suffix.
65flags.mark_flag_as_required(xds_flags.RESOURCE_SUFFIX.name)
66
67
68@flags.multi_flags_validator((xds_flags.SERVER_XDS_PORT.name, _CMD.name),
69                             message="Run outside of a test suite, must provide"
70                             " the exact port value (must be greater than 0).")
71def _check_server_xds_port_flag(flags_dict):
72    if flags_dict[_CMD.name] not in ('create', 'cycle'):
73        return True
74    return flags_dict[xds_flags.SERVER_XDS_PORT.name] > 0
75
76
77# Type aliases
78_KubernetesServerRunner = k8s_xds_server_runner.KubernetesServerRunner
79
80
81def main(argv):  # pylint: disable=too-many-locals,too-many-branches,too-many-statements
82    if len(argv) > 1:
83        raise app.UsageError('Too many command-line arguments.')
84
85    # Must be called before KubernetesApiManager or GcpApiManager init.
86    xds_flags.set_socket_default_timeout_from_flag()
87
88    command = _CMD.value
89    security_mode = _SECURITY.value
90
91    project: str = xds_flags.PROJECT.value
92    network: str = xds_flags.NETWORK.value
93
94    # Resource names.
95    resource_prefix: str = xds_flags.RESOURCE_PREFIX.value
96    resource_suffix: str = xds_flags.RESOURCE_SUFFIX.value
97
98    # Test server
99    server_name = xds_flags.SERVER_NAME.value
100    server_port = xds_flags.SERVER_PORT.value
101    server_maintenance_port = xds_flags.SERVER_MAINTENANCE_PORT.value
102    server_xds_host = xds_flags.SERVER_XDS_HOST.value
103    server_xds_port = xds_flags.SERVER_XDS_PORT.value
104    server_namespace = _KubernetesServerRunner.make_namespace_name(
105        resource_prefix, resource_suffix)
106
107    gcp_api_manager = gcp.api.GcpApiManager()
108
109    if security_mode is None:
110        td = traffic_director.TrafficDirectorManager(
111            gcp_api_manager,
112            project=project,
113            network=network,
114            resource_prefix=resource_prefix,
115            resource_suffix=resource_suffix)
116    else:
117        td = traffic_director.TrafficDirectorSecureManager(
118            gcp_api_manager,
119            project=project,
120            network=network,
121            resource_prefix=resource_prefix,
122            resource_suffix=resource_suffix)
123        if server_maintenance_port is None:
124            server_maintenance_port = \
125                _KubernetesServerRunner.DEFAULT_SECURE_MODE_MAINTENANCE_PORT
126
127    try:
128        if command in ('create', 'cycle'):
129            logger.info('Create mode')
130            if security_mode is None:
131                logger.info('No security')
132                td.setup_for_grpc(server_xds_host,
133                                  server_xds_port,
134                                  health_check_port=server_maintenance_port)
135
136            elif security_mode == 'mtls':
137                logger.info('Setting up mtls')
138                td.setup_for_grpc(server_xds_host,
139                                  server_xds_port,
140                                  health_check_port=server_maintenance_port)
141                td.setup_server_security(server_namespace=server_namespace,
142                                         server_name=server_name,
143                                         server_port=server_port,
144                                         tls=True,
145                                         mtls=True)
146                td.setup_client_security(server_namespace=server_namespace,
147                                         server_name=server_name,
148                                         tls=True,
149                                         mtls=True)
150
151            elif security_mode == 'tls':
152                logger.info('Setting up tls')
153                td.setup_for_grpc(server_xds_host,
154                                  server_xds_port,
155                                  health_check_port=server_maintenance_port)
156                td.setup_server_security(server_namespace=server_namespace,
157                                         server_name=server_name,
158                                         server_port=server_port,
159                                         tls=True,
160                                         mtls=False)
161                td.setup_client_security(server_namespace=server_namespace,
162                                         server_name=server_name,
163                                         tls=True,
164                                         mtls=False)
165
166            elif security_mode == 'plaintext':
167                logger.info('Setting up plaintext')
168                td.setup_for_grpc(server_xds_host,
169                                  server_xds_port,
170                                  health_check_port=server_maintenance_port)
171                td.setup_server_security(server_namespace=server_namespace,
172                                         server_name=server_name,
173                                         server_port=server_port,
174                                         tls=False,
175                                         mtls=False)
176                td.setup_client_security(server_namespace=server_namespace,
177                                         server_name=server_name,
178                                         tls=False,
179                                         mtls=False)
180
181            elif security_mode == 'mtls_error':
182                # Error case: server expects client mTLS cert,
183                # but client configured only for TLS
184                logger.info('Setting up mtls_error')
185                td.setup_for_grpc(server_xds_host,
186                                  server_xds_port,
187                                  health_check_port=server_maintenance_port)
188                td.setup_server_security(server_namespace=server_namespace,
189                                         server_name=server_name,
190                                         server_port=server_port,
191                                         tls=True,
192                                         mtls=True)
193                td.setup_client_security(server_namespace=server_namespace,
194                                         server_name=server_name,
195                                         tls=True,
196                                         mtls=False)
197
198            elif security_mode == 'server_authz_error':
199                # Error case: client does not authorize server
200                # because of mismatched SAN name.
201                logger.info('Setting up mtls_error')
202                td.setup_for_grpc(server_xds_host,
203                                  server_xds_port,
204                                  health_check_port=server_maintenance_port)
205                # Regular TLS setup, but with client policy configured using
206                # intentionality incorrect server_namespace.
207                td.setup_server_security(server_namespace=server_namespace,
208                                         server_name=server_name,
209                                         server_port=server_port,
210                                         tls=True,
211                                         mtls=False)
212                td.setup_client_security(
213                    server_namespace=f'incorrect-namespace-{rand.rand_string()}',
214                    server_name=server_name,
215                    tls=True,
216                    mtls=False)
217
218            logger.info('Works!')
219    except Exception:  # noqa pylint: disable=broad-except
220        logger.exception('Got error during creation')
221
222    if command in ('cleanup', 'cycle'):
223        logger.info('Cleaning up')
224        td.cleanup(force=True)
225
226    if command == 'backends-add':
227        logger.info('Adding backends')
228        k8s_api_manager = k8s.KubernetesApiManager(
229            xds_k8s_flags.KUBE_CONTEXT.value)
230        k8s_namespace = k8s.KubernetesNamespace(k8s_api_manager,
231                                                server_namespace)
232
233        neg_name, neg_zones = k8s_namespace.get_service_neg(
234            server_name, server_port)
235
236        td.load_backend_service()
237        td.backend_service_add_neg_backends(neg_name, neg_zones)
238        td.wait_for_backends_healthy_status()
239    elif command == 'backends-cleanup':
240        td.load_backend_service()
241        td.backend_service_remove_all_backends()
242    elif command == 'unused-xds-port':
243        try:
244            unused_xds_port = td.find_unused_forwarding_rule_port()
245            logger.info('Found unused forwarding rule port: %s',
246                        unused_xds_port)
247        except Exception:  # noqa pylint: disable=broad-except
248            logger.exception("Couldn't find unused forwarding rule port")
249
250
251if __name__ == '__main__':
252    app.run(main)
253