syndicate/core/__init__.py (172 lines of code) (raw):
"""
Copyright 2018 EPAM Systems, Inc.
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.
"""
import os
import re
from datetime import datetime, timedelta, timezone
from botocore.exceptions import ClientError
from syndicate.commons.log_helper import get_logger, get_user_logger
from syndicate.connection import ConnectionProvider
from syndicate.connection.sts_connection import STSConnection
from syndicate.core.conf.processor import ConfigHolder
from syndicate.core.project_state.project_state import ProjectState
from syndicate.core.resources.processors_mapping import ProcessorFacade
from syndicate.core.resources.resources_provider import ResourceProvider
from syndicate.core.conf.bucket_view import URIBucketView, RegexViewDigest, \
NAMED_S3_URI_PATTERN, S3_PATTERN_GROUP_NAMES
from syndicate.core.helper import handle_interruption
_LOG = get_logger(__name__)
USER_LOG = get_user_logger()
SESSION_TOKEN = 'aws_session_token'
SECRET_KEY = 'aws_secret_access_key'
ACCESS_KEY = 'aws_access_key_id'
CONFIRMATION_ANSWERS = ['y', 'yes']
# if temporary credentials will be expired in less than N minutes,
# syndicate will regenerate temp credentials instead of using old ones
CREDENTIALS_EXPIRATION_THRESHOLD_MIN = 15
def exception_handler(exception_type, exception, traceback):
print(exception)
# sys.excepthook = exception_handler
# CONF VARS ===================================================================
CONF_PATH = os.environ.get('SDCT_CONF')
CONFIG: ConfigHolder = None
CONN = None
CREDENTIALS = None
RESOURCES_PROVIDER: ResourceProvider = None
PROCESSOR_FACADE: ProcessorFacade = None
PROJECT_STATE: ProjectState = None
def _ready_to_assume():
return CONFIG.access_role and not CONFIG.use_temp_creds
def _ready_to_use_creds():
return not CONFIG.access_role and CONFIG.aws_access_key_id and \
CONFIG.aws_secret_access_key and not CONFIG.use_temp_creds
def _ready_to_use_provided_temp_creds():
has_temporary_creds_set = CONFIG.temp_aws_access_key_id \
and CONFIG.temp_aws_secret_access_key \
and CONFIG.temp_aws_session_token \
and CONFIG.expiration
credentials_valid = validate_temp_credentials(
aws_access_key_id=CONFIG.temp_aws_access_key_id,
aws_secret_access_key=CONFIG.temp_aws_secret_access_key,
aws_session_token=CONFIG.temp_aws_session_token,
expiration=CONFIG.expiration
)
return has_temporary_creds_set and credentials_valid
def _ready_to_generate_temp_creds():
return not CONFIG.access_role and CONFIG.use_temp_creds
def initialize_connection():
global CONFIG
global CONN
global CONF_PATH
global CREDENTIALS
global RESOURCES_PROVIDER
global PROCESSOR_FACADE
regex_digest = RegexViewDigest()
regex_digest.expression = NAMED_S3_URI_PATTERN
regex_digest.groups = S3_PATTERN_GROUP_NAMES
uri_bucket_view = URIBucketView()
uri_bucket_view.digest = regex_digest
CONFIG = ConfigHolder(CONF_PATH)
CONFIG.deploy_target_bucket_view = uri_bucket_view
sts = STSConnection(CONFIG.region, CONFIG.aws_access_key_id,
CONFIG.aws_secret_access_key, CONFIG.aws_session_token)
try:
CREDENTIALS = {
'region': CONFIG.region
}
if _ready_to_use_provided_temp_creds():
_LOG.debug(f'Going to use previously generated temporary '
f'credentials')
CREDENTIALS[ACCESS_KEY] = CONFIG.temp_aws_access_key_id
CREDENTIALS[SECRET_KEY] = CONFIG.temp_aws_secret_access_key
CREDENTIALS[SESSION_TOKEN] = CONFIG.temp_aws_session_token
elif _ready_to_assume():
_LOG.debug('Starting to assume role ...')
# get CREDENTIALS for N hours
token_code = prompt_mfa_code() if CONFIG.serial_number else None
temp_credentials = sts.get_temp_credentials(
role_arn=CONFIG.access_role,
acc_id=CONFIG.account_id,
duration=CONFIG.session_duration,
serial_number=CONFIG.serial_number,
token_code=token_code
)
_LOG.debug(f'Role {CONFIG.access_role} is assumed successfully'
f'for {CONFIG.session_duration} seconds')
CREDENTIALS[ACCESS_KEY] = temp_credentials['AccessKeyId']
CREDENTIALS[SECRET_KEY] = temp_credentials['SecretAccessKey']
CREDENTIALS[SESSION_TOKEN] = temp_credentials['SessionToken']
_LOG.debug(f'Temporary credentials have been successfully '
f'generated by assuming the role {CONFIG.access_role}, '
f'saving to config.')
CONFIG.set_temp_credentials_to_config(
temp_aws_access_key_id=temp_credentials['AccessKeyId'],
temp_aws_secret_access_key=temp_credentials['SecretAccessKey'],
temp_aws_session_token=temp_credentials['SessionToken'],
expiration=temp_credentials['Expiration']
)
elif _ready_to_generate_temp_creds():
_LOG.debug(f'Going to generate new temporary credentials')
token_code = None
if CONFIG.serial_number:
token_code = prompt_mfa_code()
temp_credentials = sts.get_session_token(
duration=CONFIG.session_duration,
serial_number=CONFIG.serial_number,
token_code=token_code
)
CREDENTIALS[ACCESS_KEY] = temp_credentials['AccessKeyId']
CREDENTIALS[SECRET_KEY] = temp_credentials['SecretAccessKey']
CREDENTIALS[SESSION_TOKEN] = temp_credentials['SessionToken']
_LOG.debug(f'Temporary credentials have been successfully '
f'generated, saving to config.')
CONFIG.set_temp_credentials_to_config(
temp_aws_access_key_id=temp_credentials['AccessKeyId'],
temp_aws_secret_access_key=temp_credentials['SecretAccessKey'],
temp_aws_session_token=temp_credentials['SessionToken'],
expiration=temp_credentials['Expiration']
)
elif _ready_to_use_creds():
_LOG.debug('Credentials access')
CREDENTIALS[ACCESS_KEY] = CONFIG.aws_access_key_id
CREDENTIALS[SECRET_KEY] = CONFIG.aws_secret_access_key
CONN = ConnectionProvider(CREDENTIALS)
RESOURCES_PROVIDER = ResourceProvider(config=CONFIG,
credentials=CREDENTIALS,
sts_conn=sts)
PROCESSOR_FACADE = ProcessorFacade(
resources_provider=RESOURCES_PROVIDER)
_LOG.debug('aws-syndicate has been initialized')
except ClientError as e:
message = f'An unexpected error has occurred trying to ' \
f'init connection: {e}'
_LOG.error(message)
raise AssertionError(message)
def initialize_project_state():
from syndicate.core.project_state.sync_processor import sync_project_state
global PROJECT_STATE
if not ProjectState.check_if_project_state_exists(CONFIG.project_path):
USER_LOG.warn("\033[93mConfig is set and generated but project "
"state does not exist, seems that you've come from the "
"previous version.\033[0m")
USER_LOG.warn("\033[93mGenerating project state file "
"(.syndicate) from the existing structure..."
"\033[0m")
PROJECT_STATE = ProjectState.build_from_structure(CONFIG)
else:
PROJECT_STATE = ProjectState(project_path=CONFIG.project_path)
def validate_temp_credentials(aws_access_key_id, aws_secret_access_key,
aws_session_token, expiration):
if not all((aws_access_key_id, aws_secret_access_key,
aws_session_token, expiration)):
return False
if not isinstance(expiration, datetime):
expiration = datetime.fromisoformat(expiration)
expiration_datetime = expiration - timedelta(
minutes=CREDENTIALS_EXPIRATION_THRESHOLD_MIN)
now_datetime = datetime.now(timezone.utc)
return expiration_datetime > now_datetime
def prompt_mfa_code():
mfa_code = input('Please enter your MFA code to generate '
'temp credentials: ')
while 1:
if len(mfa_code) == 6 and re.match('[0-9]{6}', mfa_code):
break
mfa_code = input(f'Token code must consist of 6 numbers. '
f'Try again: ')
return mfa_code
def initialize_signal_handling():
from signal import SIGINT, signal
signal(SIGINT, handle_interruption)