Source code for

# Copyright 2017 The Forseti Security Authors. 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
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# See the License for the specific language governing permissions and
# limitations under the License.

"""Forseti Server program."""

# pylint: disable=line-too-long
import argparse
import os
import sys
import time

from concurrent import futures
import grpc

from import logger

from import ServiceConfig
from import GrpcExplainerFactory
from import GrpcInventoryFactory
from import GrpcModellerFactory
from import GrpcNotifierFactory
from import GrpcScannerFactory
from import GrpcServerConfigFactory

LOGGER = logger.get_logger(__name__)

    import googlecloudprofiler'Cloud Profiler library successfully imported.')
except ImportError:
    LOGGER.warning('Cannot enable Cloud Profiler because the '
                   '`googlecloudprofiler` library was not found. Run '
                   '`sudo pip3 install .[profiler]` to install '
                   'Cloud Profiler.')

    'explain': GrpcExplainerFactory,
    'inventory': GrpcInventoryFactory,
    'scanner': GrpcScannerFactory,
    'notifier': GrpcNotifierFactory,
    'model': GrpcModellerFactory,
    'server': GrpcServerConfigFactory

[docs]def serve(endpoint, services, forseti_db_connect_string, config_file_path, log_level, enable_console_log, max_workers=32, wait_shutdown_secs=3): """Instantiate the services and serves them via gRPC. Args: endpoint (str): the server channel endpoint services (list): services to register on the server forseti_db_connect_string (str): Forseti database string config_file_path (str): Path to Forseti configuration file. log_level (str): Sets the threshold for Forseti's logger. enable_console_log (bool): Enable console logging. max_workers (int): maximum number of workers for the crawler wait_shutdown_secs (int): seconds to wait before shutdown Raises: Exception: No services to start """ # Configuring log level for the application logger.set_logger_level_from_config(log_level) if enable_console_log: logger.enable_console_log() factories = [] for service in services: factories.append(SERVICE_MAP[service]) if not factories: raise Exception('No services to start.') # Server config service is always started. factories.append(SERVICE_MAP['server']) config = ServiceConfig( forseti_config_file_path=config_file_path, forseti_db_connect_string=forseti_db_connect_string, endpoint=endpoint) is_config_updated, error_msg = config.update_configuration() if not is_config_updated: update_config_msg = ( 'Please update the forseti_conf_server.yaml file on GCS ' 'and reset the server VM.') raise Exception(error_msg + ' ' + update_config_msg) server = grpc.server(futures.ThreadPoolExecutor(max_workers)) for factory in factories: factory(config).create_and_register_service(server) server.add_insecure_port(endpoint) server.start() while True: try: time.sleep(1) except KeyboardInterrupt: server.stop(wait_shutdown_secs).wait() return
[docs]def check_args(args): """Make sure the required args are present and valid. The exit codes are arbitrary and just serve the purpose of facilitating distinction betweeen the various error cases. Args: args (dict): the command line args Returns: tuple: 2-tuple with an exit code and error message. """ if not args['services']: return (1, 'ERROR: please specify at least one service.') if not args['config_file_path']: return (2, 'ERROR: please specify the Forseti config file.') if not os.path.isfile(args['config_file_path']): return (3, 'ERROR: "%s" is not a file.' % args['config_file_path']) if not os.access(args['config_file_path'], os.R_OK): return(4, 'ERROR: "%s" is not readable.' % args['config_file_path']) if not args['forseti_db']: return(5, 'ERROR: please specify the Forseti database string.') return (0, 'All good!')
[docs]def main(): """Run.""" parser = argparse.ArgumentParser() parser.add_argument( '--endpoint', default='[::]:50051', help='Server endpoint') parser.add_argument( '--forseti_db', help=('Forseti database string, formatted as ' '"mysql+pymysql://<db_user>@<db_host>:<db_port>/<db_name>"')) parser.add_argument( '--config_file_path', help='Path to Forseti configuration file.') services = sorted(SERVICE_MAP.keys()) parser.add_argument( '--services', nargs='+', choices=services, help=('Forseti services i.e. at least one of: %s.' % ', '.join(services))) parser.add_argument( '--log_level', default='info', choices=['debug', 'info', 'warning', 'error'], help='Sets the threshold for Forseti\'s logger.' ' Logging messages which are less severe' ' than the level you set will be ignored.') parser.add_argument( '--enable_console_log', action='store_true', help='Print log to console.') parser.add_argument( '--enable_profiler', action='store_true', help='Enable Cloud Profiler.') args = vars(parser.parse_args()) exit_code, error_msg = check_args(args) if exit_code: sys.stderr.write('%s\n\n' % error_msg) parser.print_usage() sys.exit(exit_code) if args['enable_profiler'] and CLOUD_PROFILER_IMPORTED: try: googlecloudprofiler.start( service='forseti-server', verbose=2, # project_id must be set if not running on GCP, such as in your # local dev environment. # project_id='my_project_id', ) except (ValueError, NotImplementedError) as exc: LOGGER.warning('Unable to enable Cloud Profiler: %s', exc) serve(args['endpoint'], args['services'], args['forseti_db'], args['config_file_path'], args['log_level'], args['enable_console_log'])
if __name__ == '__main__': main()