def passthrough(url=''): log.debug('Did not match credentials request url; passing through.') req = requests.get('{0}/{1}'.format(app.config['METADATA_URL'], url), stream=True) return Response(stream_with_context(req.iter_content()), content_type=req.headers['content-type'], status=req.status_code)
def check_role_name_from_ip(ip, requested_role): role_name = get_role_name_from_ip(ip) if role_name == requested_role: log.debug('Detected Role: {0}, Requested Role: {1}'.format( role_name, requested_role)) return True return False
def get_iam_info(api_version, junk=None): role_params_from_ip = roles.get_role_params_from_ip(request.remote_addr) if role_params_from_ip['name']: log.debug('Providing IAM role info for {0}'.format(role_params_from_ip['name'])) return jsonify(roles.get_role_info_from_params(role_params_from_ip)) else: log.error('Role name not found; returning 404.') return '', 404
def get_iam_info(api_version, junk=None): role_name_from_ip = roles.get_role_name_from_ip(request.remote_addr) if role_name_from_ip: log.debug('Providing IAM role info for {0}'.format(role_name_from_ip)) return jsonify(roles.get_role_info_from_ip(request.remote_addr)) else: log.error('Role name not found; returning 404.') return '', 404
def find_container(ip): pattern = re.compile(app.config['HOSTNAME_MATCH_REGEX']) client = docker_client() # Try looking at the container mapping cache first if ip in CONTAINER_MAPPING: log.info('Container id for IP {0} in cache'.format(ip)) try: with PrintingBlockTimer('Container inspect'): container = client.inspect_container(CONTAINER_MAPPING[ip]) return container except docker.errors.NotFound: msg = 'Container id {0} no longer mapped to {1}' log.error(msg.format(CONTAINER_MAPPING[ip], ip)) del CONTAINER_MAPPING[ip] _fqdn = None with PrintingBlockTimer('Reverse DNS'): if app.config['ROLE_REVERSE_LOOKUP']: try: _fqdn = socket.gethostbyaddr(ip)[0] except socket.error as e: log.error('gethostbyaddr failed: {0}'.format(e.args)) pass with PrintingBlockTimer('Container fetch'): _ids = [c['Id'] for c in client.containers()] for _id in _ids: try: with PrintingBlockTimer('Container inspect'): c = client.inspect_container(_id) except docker.errors.NotFound: log.error('Container id {0} not found'.format(_id)) continue # Try matching container to caller by IP address _ip = c['NetworkSettings']['IPAddress'] if ip == _ip: msg = 'Container id {0} mapped to {1} by IP match' log.debug(msg.format(_id, ip)) CONTAINER_MAPPING[ip] = _id return c # Try matching container to caller by hostname match if app.config['ROLE_REVERSE_LOOKUP']: hostname = c['Config']['Hostname'] domain = c['Config']['Domainname'] fqdn = '{0}.{1}'.format(hostname, domain) # Default pattern matches _fqdn == fqdn _groups = re.match(pattern, _fqdn).groups() groups = re.match(pattern, fqdn).groups() if _groups and groups: if groups[0] == _groups[0]: msg = 'Container id {0} mapped to {1} by FQDN match' log.debug(msg.format(_id, ip)) CONTAINER_MAPPING[ip] = _id return c log.error('No container found for ip {0}'.format(ip)) return None
def passthrough(url=''): log.debug('Did not match credentials request url; passing through.') req = requests.get( '{0}/{1}'.format(app.config['METADATA_URL'], url), stream=True ) return Response( stream_with_context(req.iter_content()), content_type=req.headers['content-type'] )
def iam_role_info(api_version, junk=None): if not _supports_iam(api_version): return passthrough(request.path) role_name_from_ip = roles.get_role_name_from_ip(request.remote_addr) if role_name_from_ip: log.debug('Providing IAM role info for {0}'.format(role_name_from_ip)) return jsonify(roles.get_role_info_from_ip(request.remote_addr)) else: log.error('Role name not found; returning 404.') return '', 404
def iam_role_info(api_version, junk=None): if not _supports_iam(api_version): return passthrough(request.path) role_params_from_ip = roles.get_role_params_from_ip(request.remote_addr) if role_params_from_ip['name']: log.debug('Providing IAM role info for {0}'.format( role_params_from_ip['name'])) return jsonify(roles.get_role_info_from_params(role_params_from_ip)) else: log.error('Role name not found; returning 404.') return '', 404
def iam_sts_credentials(api_version, requested_role, junk=None): if not _supports_iam(api_version): return passthrough(request.path) if not roles.check_role_name_from_ip(request.remote_addr, requested_role): msg = "Role name {0} doesn't match expected role for container" log.error(msg.format(requested_role)) return '', 404 role_name = roles.get_role_name_from_ip(request.remote_addr, stripped=False) log.debug('Providing assumed role credentials for {0}'.format(role_name)) assumed_role = roles.get_assumed_role_credentials(requested_role=role_name, api_version=api_version) return jsonify(assumed_role)
def iam_sts_credentials(api_version, requested_role, junk=None): if not _supports_iam(api_version): return passthrough(request.path) try: role_params = roles.get_role_params_from_ip( request.remote_addr, requested_role=requested_role) except roles.UnexpectedRoleError: msg = "Role name {0} doesn't match expected role for container" log.error(msg.format(requested_role)) return '', 404 log.debug('Providing assumed role credentials for {0}'.format( role_params['name'])) assumed_role = roles.get_assumed_role_credentials(role_params=role_params, api_version=api_version) return jsonify(assumed_role)
def iam_sts_credentials(api_version, requested_role, junk=None): if not _supports_iam(api_version): return passthrough(request.path) if not roles.check_role_name_from_ip(request.remote_addr, requested_role): msg = "Role name {0} doesn't match expected role for container" log.error(msg.format(requested_role)) return '', 404 role_name = roles.get_role_name_from_ip( request.remote_addr, stripped=False ) log.debug('Providing assumed role credentials for {0}'.format(role_name)) assumed_role = roles.get_assumed_role_credentials( requested_role=role_name, api_version=api_version ) return jsonify(assumed_role)
def iam_sts_credentials(api_version, requested_role, junk=None): if not _supports_iam(api_version): return passthrough(request.path) try: role_params = roles.get_role_params_from_ip( request.remote_addr, requested_role=requested_role ) except roles.UnexpectedRoleError: msg = "Role name {0} doesn't match expected role for container" log.error(msg.format(requested_role)) return '', 404 log.debug('Providing assumed role credentials for {0}'.format(role_params['name'])) assumed_role = roles.get_assumed_role_credentials( role_params=role_params, api_version=api_version ) return jsonify(assumed_role)
def get_role_name_from_ip(ip, stripped=True): if app.config['ROLE_MAPPING_FILE']: return ROLE_MAPPINGS.get(ip, app.config['DEFAULT_ROLE']) container = find_container(ip) if container: env = container['Config']['Env'] for e in env: key, val = e.split('=', 1) if key == 'IAM_ROLE': if stripped: return val.split('@')[0] else: return val msg = "Couldn't find IAM_ROLE variable. Returning DEFAULT_ROLE: {0}" log.debug(msg.format(app.config['DEFAULT_ROLE'])) if stripped: return app.config['DEFAULT_ROLE'].split('@')[0] else: return app.config['DEFAULT_ROLE'] else: return None
def get_role_params_from_ip(ip, requested_role=None): params = { 'name': None, 'account_id': None, 'external_id': None, 'session_name': None } role_name = None if app.config['ROLE_MAPPING_FILE']: role = ROLE_MAPPINGS.get(ip, app.config['DEFAULT_ROLE']) if isinstance(role, dict): params.update(role) else: role_name = role else: container = find_container(ip) if container: env = container['Config']['Env'] or [] # Look up IAM_ROLE and IAM_EXTERNAL_ID values from environment for e in env: key, val = e.split('=', 1) if key == 'IAM_ROLE': if val.startswith('arn:aws'): m = RE_IAM_ARN.match(val) val = '{0}@{1}'.format(m.group(2), m.group(1)) role_name = val elif key == 'IAM_EXTERNAL_ID': params['external_id'] = val if not role_name: msg = "Couldn't find IAM_ROLE variable. Returning DEFAULT_ROLE: {0}" log.debug(msg.format(app.config['DEFAULT_ROLE'])) role_name = app.config['DEFAULT_ROLE'] # Optionally, look up role session name from environment or labels if app.config['ROLE_SESSION_KEY']: skey = app.config['ROLE_SESSION_KEY'] sval = None if skey.startswith('Env:'): skey = skey[4:] for e in env: key, val = e.split('=', 1) if skey == key: sval = val elif skey.startswith('Labels:'): skey = skey[7:] if container['Config']['Labels'] and skey in container[ 'Config']['Labels']: sval = container['Config']['Labels'][skey] if sval and len(sval) > 1: # The docs on RoleSessionName are slightly contradictory, and state: # > The regex used to validate this parameter is a string of characters consisting # > of upper- and lower-case alphanumeric characters with no spaces. You can also # > include underscores or any of the following characters: =,.@- # > Type: String # > Length Constraints: Minimum length of 2. Maximum length of 64. # > Pattern: [\w+=,.@-]* # We replace any invalid chars with underscore, and trim to 64. params['session_name'] = re.sub(r'[^\w+=,.@-]', '_', sval)[:64] if role_name: role_parts = role_name.split('@') params['name'] = role_parts[0] if len(role_parts) > 1: params['account_id'] = role_parts[1] if requested_role and requested_role != params['name']: raise UnexpectedRoleError return params
def find_container(ip): pattern = re.compile(app.config['HOSTNAME_MATCH_REGEX']) client = docker_client() # Try looking at the container mapping cache first if ip in CONTAINER_MAPPING: log.info('Container id for IP {0} in cache'.format(ip)) try: with PrintingBlockTimer('Container inspect'): container = client.inspect_container(CONTAINER_MAPPING[ip]) # Only return a cached container if it is running. if container['State']['Running']: return container else: log.error('Container id {0} is no longger running'.format(ip)) del CONTAINER_MAPPING[ip] except docker.errors.NotFound: msg = 'Container id {0} no longer mapped to {1}' log.error(msg.format(CONTAINER_MAPPING[ip], ip)) del CONTAINER_MAPPING[ip] _fqdn = None with PrintingBlockTimer('Reverse DNS'): if app.config['ROLE_REVERSE_LOOKUP']: try: _fqdn = socket.gethostbyaddr(ip)[0] except socket.error as e: log.error('gethostbyaddr failed: {0}'.format(e.args)) pass with PrintingBlockTimer('Container fetch'): _ids = [c['Id'] for c in client.containers()] for _id in _ids: try: with PrintingBlockTimer('Container inspect'): c = client.inspect_container(_id) except docker.errors.NotFound: log.error('Container id {0} not found'.format(_id)) continue # Try matching container to caller by IP address _ip = c['NetworkSettings']['IPAddress'] if ip == _ip: msg = 'Container id {0} mapped to {1} by IP match' log.debug(msg.format(_id, ip)) CONTAINER_MAPPING[ip] = _id return c # Try matching container to caller by hostname match if app.config['ROLE_REVERSE_LOOKUP']: hostname = c['Config']['Hostname'] domain = c['Config']['Domainname'] fqdn = '{0}.{1}'.format(hostname, domain) # Default pattern matches _fqdn == fqdn _groups = re.match(pattern, _fqdn).groups() groups = re.match(pattern, fqdn).groups() if _groups and groups: if groups[0] == _groups[0]: msg = 'Container id {0} mapped to {1} by FQDN match' log.debug(msg.format(_id, ip)) CONTAINER_MAPPING[ip] = _id return c log.error('No container found for ip {0}'.format(ip)) return None
def block_user_data(api_version): log.debug('Blocking request for user-data.') return 'ACCESS FORBIDDEN', 403
def find_container(ip): pattern = re.compile(app.config['HOSTNAME_MATCH_REGEX']) client = docker_client() # Try looking at the container mapping cache first container_id = CONTAINER_MAPPING.get(ip) if container_id: log.info('Container id for IP {0} in cache'.format(ip)) try: with PrintingBlockTimer('Container inspect'): container = client.inspect_container(container_id) # Only return a cached container if it is running. if container['State']['Running']: return container else: log.error('Container id {0} is no longer running'.format(ip)) if ip in CONTAINER_MAPPING: del CONTAINER_MAPPING[ip] except docker.errors.NotFound: msg = 'Container id {0} no longer mapped to {1}' log.error(msg.format(container_id, ip)) if ip in CONTAINER_MAPPING: del CONTAINER_MAPPING[ip] _fqdn = None with PrintingBlockTimer('Reverse DNS'): if app.config['ROLE_REVERSE_LOOKUP']: try: _fqdn = socket.gethostbyaddr(ip)[0] except socket.error as e: log.error('gethostbyaddr failed: {0}'.format(e.args)) pass with PrintingBlockTimer('Container fetch'): _ids = [c['Id'] for c in client.containers()] for _id in _ids: try: with PrintingBlockTimer('Container inspect'): c = client.inspect_container(_id) except docker.errors.NotFound: log.error('Container id {0} not found'.format(_id)) continue # Try matching container to caller by IP address _ip = c['NetworkSettings']['IPAddress'] if ip == _ip: msg = 'Container id {0} mapped to {1} by IP match' log.debug(msg.format(_id, ip)) CONTAINER_MAPPING[ip] = _id return c # Try matching container to caller by sub network IP address _networks = c['NetworkSettings']['Networks'] if _networks: for _network in _networks: if _networks[_network]['IPAddress'] == ip: msg = 'Container id {0} mapped to {1} by sub-network IP match' log.debug(msg.format(_id, ip)) CONTAINER_MAPPING[ip] = _id return c # Not Found ? Let's see if we are running under rancher 1.2+,which uses a label to store the IP try: _labels = c.get('Config', {}).get('Labels', {}) except (KeyError, ValueError): _labels = {} try: if _labels.get('io.rancher.container.ip'): _ip = _labels.get('io.rancher.container.ip').split("/")[0] except docker.errors.NotFound: log.error('Container: {0} Label container.ip not found'.format(_id)) if ip == _ip: msg = 'Container id {0} mapped to {1} by Rancher IP match' log.debug(msg.format(_id, ip)) CONTAINER_MAPPING[ip] = _id return c # Try matching container to caller by hostname match if app.config['ROLE_REVERSE_LOOKUP']: hostname = c['Config']['Hostname'] domain = c['Config']['Domainname'] fqdn = '{0}.{1}'.format(hostname, domain) # Default pattern matches _fqdn == fqdn _groups = re.match(pattern, _fqdn).groups() groups = re.match(pattern, fqdn).groups() if _groups and groups: if groups[0] == _groups[0]: msg = 'Container id {0} mapped to {1} by FQDN match' log.debug(msg.format(_id, ip)) CONTAINER_MAPPING[ip] = _id return c log.error('No container found for ip {0}'.format(ip)) return None
def __exit__(self, *args): super(PrintingBlockTimer, self).__exit__(*args) msg = "Execution took {0:f}s".format(self.exec_duration) if self.prefix: msg = self.prefix + ': ' + msg log.debug(msg)
def get_role_params_from_ip(ip, requested_role=None): params = {'name': None, 'account_id': None, 'external_id': None, 'session_name': None} role_name = None if app.config['ROLE_MAPPING_FILE']: role = ROLE_MAPPINGS.get(ip, app.config['DEFAULT_ROLE']) if isinstance(role, dict): params.update(role) else: role_name = role else: container = find_container(ip) if container: env = container['Config']['Env'] or [] # Look up IAM_ROLE and IAM_EXTERNAL_ID values from environment for e in env: key, val = e.split('=', 1) if key == 'IAM_ROLE': if val.startswith('arn:aws'): m = RE_IAM_ARN.match(val) val = '{0}@{1}'.format(m.group(2), m.group(1)) role_name = val elif key == 'IAM_EXTERNAL_ID': params['external_id'] = val if not role_name: msg = "Couldn't find IAM_ROLE variable. Returning DEFAULT_ROLE: {0}" log.debug(msg.format(app.config['DEFAULT_ROLE'])) role_name = app.config['DEFAULT_ROLE'] # Optionally, look up role session name from environment or labels if app.config['ROLE_SESSION_KEY']: skey = app.config['ROLE_SESSION_KEY'] sval = None if skey.startswith('Env:'): skey = skey[4:] for e in env: key, val = e.split('=', 1) if skey == key: sval = val elif skey.startswith('Labels:'): skey = skey[7:] if container['Config']['Labels'] and skey in container['Config']['Labels']: sval = container['Config']['Labels'][skey] if sval and len(sval) > 1: # The docs on RoleSessionName are slightly contradictory, and state: # > The regex used to validate this parameter is a string of characters consisting # > of upper- and lower-case alphanumeric characters with no spaces. You can also # > include underscores or any of the following characters: =,.@- # > Type: String # > Length Constraints: Minimum length of 2. Maximum length of 64. # > Pattern: [\w+=,.@-]* # We replace any invalid chars with underscore, and trim to 64. params['session_name'] = re.sub(r'[^\w+=,.@-]', '_', sval)[:64] if role_name: role_parts = role_name.split('@') params['name'] = role_parts[0] if len(role_parts) > 1: params['account_id'] = role_parts[1] if requested_role and requested_role != params['name']: raise UnexpectedRoleError return params
def find_container(ip): pattern = re.compile(app.config['HOSTNAME_MATCH_REGEX']) client = docker_client() # Try looking at the container mapping cache first if ip in CONTAINER_MAPPING: log.info('Container id for IP {0} in cache'.format(ip)) try: with PrintingBlockTimer('Container inspect'): container = client.inspect_container(CONTAINER_MAPPING[ip]) # Only return a cached container if it is running. if container['State']['Running']: return container else: log.error('Container id {0} is no longer running'.format(ip)) del CONTAINER_MAPPING[ip] except docker.errors.NotFound: msg = 'Container id {0} no longer mapped to {1}' log.error(msg.format(CONTAINER_MAPPING[ip], ip)) del CONTAINER_MAPPING[ip] _fqdn = None with PrintingBlockTimer('Reverse DNS'): if app.config['ROLE_REVERSE_LOOKUP']: try: _fqdn = socket.gethostbyaddr(ip)[0] except socket.error as e: log.error('gethostbyaddr failed: {0}'.format(e.args)) pass with PrintingBlockTimer('Container fetch'): _ids = [c['Id'] for c in client.containers()] for _id in _ids: try: with PrintingBlockTimer('Container inspect'): c = client.inspect_container(_id) except docker.errors.NotFound: log.error('Container id {0} not found'.format(_id)) continue # Try matching container to caller by IP address _ip = c['NetworkSettings']['IPAddress'] if ip == _ip: msg = 'Container id {0} mapped to {1} by IP match' log.debug(msg.format(_id, ip)) CONTAINER_MAPPING[ip] = _id return c # Try matching container to caller by sub network IP address _networks = c['NetworkSettings']['Networks'] if _networks: for _network in _networks: if _networks[_network]['IPAddress'] == ip: msg = 'Container id {0} mapped to {1} by sub-network IP match' log.debug(msg.format(_id, ip)) CONTAINER_MAPPING[ip] = _id return c # Not Found ? Let's see if we are running under rancher 1.2+,which uses a label to store the IP try: _labels = c.get('Config', {}).get('Labels', {}) except (KeyError, ValueError): _labels = {} try: if _labels.get('io.rancher.container.ip'): _ip = _labels.get('io.rancher.container.ip').split("/")[0] except docker.errors.NotFound: log.error( 'Container: {0} Label container.ip not found'.format(_id)) if ip == _ip: msg = 'Container id {0} mapped to {1} by Rancher IP match' log.debug(msg.format(_id, ip)) CONTAINER_MAPPING[ip] = _id return c # Try matching container to caller by hostname match if app.config['ROLE_REVERSE_LOOKUP']: hostname = c['Config']['Hostname'] domain = c['Config']['Domainname'] fqdn = '{0}.{1}'.format(hostname, domain) # Default pattern matches _fqdn == fqdn _groups = re.match(pattern, _fqdn).groups() groups = re.match(pattern, fqdn).groups() if _groups and groups: if groups[0] == _groups[0]: msg = 'Container id {0} mapped to {1} by FQDN match' log.debug(msg.format(_id, ip)) CONTAINER_MAPPING[ip] = _id return c log.error('No container found for ip {0}'.format(ip)) return None
def get_role_params_from_ip(ip, requested_role=None): params = { 'name': None, 'account_id': None, 'external_id': None, 'session_name': None } role_name = None if app.config['ROLE_MAPPING_FILE']: role = ROLE_MAPPINGS.get(ip, app.config['DEFAULT_ROLE']) if isinstance(role, dict): params.update(role) else: role_name = role else: container = find_container(ip) if container: env = container['Config']['Env'] or [] # Look up IAM_ROLE and IAM_EXTERNAL_ID values from environment aws_id = chain_ns = chain_role = proposer_number = voter_number = alloc_index = aws_subnet = None for e in env: key, val = split_envvar(e) if key == 'IAM_ROLE': m = RE_IAM_ARN.match(val) if m: val = '{0}@{1}'.format(m.group(2), m.group(1)) role_name = val elif key == 'IAM_EXTERNAL_ID': params['external_id'] = val elif key == 'NOMAD_META_AWS_ACCOUNT_ID': aws_id = val elif key == 'NOMAD_META_CHAIN_NS': chain_ns = val elif key == 'NOMAD_META_CHAIN_ROLE': chain_role = val elif key == 'NOMAD_META_PORPOSER_NUMBER': proposer_number = val elif key == 'NOMAD_META_VOTER_NUMBER': voter_number = val elif key == 'NOMAD_ALLOC_INDEX': alloc_index = val elif key == 'NOMAD_META_AWS_SUBNET': aws_subnet = val start_number = 0 if chain_role == 'proposer': data = ast.literal_eval(proposer_number) for key in sorted(data.keys()): if key != aws_subnet: start_number += int(data.get(key)) else: break chain_role = "accelerator" elif chain_role == 'voter': data = ast.literal_eval(voter_number) for key in sorted(data.keys()): if key != aws_subnet: start_number += int(data.get(key)) else: break chain_role = "consensus" if chain_ns and chain_role and alloc_index and aws_subnet: r_number = start_number + int(alloc_index) r_number = "%03d" % r_number role_name = '{0}-{1}-{2}@{3}'.format(chain_ns, chain_role, r_number, aws_id) if not role_name: msg = "Couldn't find IAM_ROLE variable. Returning DEFAULT_ROLE: {0}" log.debug(msg.format(app.config['DEFAULT_ROLE'])) role_name = app.config['DEFAULT_ROLE'] # Optionally, look up role session name from environment or labels if app.config['ROLE_SESSION_KEY']: skey = app.config['ROLE_SESSION_KEY'] sval = None if skey.startswith('Env:'): skey = skey[4:] for e in env: key, val = split_envvar(e) if skey == key: sval = val elif skey.startswith('Labels:'): skey = skey[7:] if container['Config']['Labels'] and skey in container[ 'Config']['Labels']: sval = container['Config']['Labels'][skey] if sval and len(sval) > 1: # The docs on RoleSessionName are slightly contradictory, and state: # > The regex used to validate this parameter is a string of characters consisting # > of upper- and lower-case alphanumeric characters with no spaces. You can also # > include underscores or any of the following characters: =,.@- # > Type: String # > Length Constraints: Minimum length of 2. Maximum length of 64. # > Pattern: [\w+=,.@-]* # We replace any invalid chars with underscore, and trim to 64. params['session_name'] = re.sub(r'[^\w+=,.@-]', '_', sval)[:64] if role_name: role_parts = role_name.split('@') params['name'] = role_parts[0] if len(role_parts) > 1: params['account_id'] = role_parts[1] if requested_role and requested_role != params['name']: raise UnexpectedRoleError return params