def _enforce(self, req, action): """Authorize an action against the policy.json.""" try: self.policy.enforce(req.context, action) except heat_exception.Forbidden: msg = _("Action %s not allowed for user") % action raise exception.HeatAccessDeniedError(msg) except Exception: # We expect policy.enforce to either pass or raise Forbidden # however, if anything else happens, we want to raise # HeatInternalFailureError, failure to do this results in # the user getting a big stacktrace spew as an API response msg = _("Error authorizing action %s") % action raise exception.HeatInternalFailureError(msg)
def _authorize(self, req, auth_uri): # Read request signature and access id. # If we find X-Auth-User in the headers we ignore a key error # here so that we can use both authentication methods. # Returning here just means the user didn't supply AWS # authentication and we'll let the app try native keystone next. logger.info(_("Checking AWS credentials..")) signature = self._get_signature(req) if not signature: if 'X-Auth-User' in req.headers: return self.application else: logger.info(_("No AWS Signature found.")) raise exception.HeatIncompleteSignatureError() access = self._get_access(req) if not access: if 'X-Auth-User' in req.headers: return self.application else: logger.info(_("No AWSAccessKeyId/Authorization Credential")) raise exception.HeatMissingAuthenticationTokenError() logger.info(_("AWS credentials found, checking against keystone.")) if not auth_uri: logger.error(_("Ec2Token authorization failed, no auth_uri " "specified in config file")) raise exception.HeatInternalFailureError(_('Service ' 'misconfigured')) # Make a copy of args for authentication and signature verification. auth_params = dict(req.params) # 'Signature' param Not part of authentication args auth_params.pop('Signature', None) # Authenticate the request. # AWS v4 authentication requires a hash of the body body_hash = hashlib.sha256(req.body).hexdigest() creds = {'ec2Credentials': {'access': access, 'signature': signature, 'host': req.host, 'verb': req.method, 'path': req.path, 'params': auth_params, 'headers': req.headers, 'body_hash': body_hash }} creds_json = json.dumps(creds) headers = {'Content-Type': 'application/json'} keystone_ec2_uri = self._conf_get_keystone_ec2_uri(auth_uri) logger.info(_('Authenticating with %s') % keystone_ec2_uri) response = requests.post(keystone_ec2_uri, data=creds_json, headers=headers) result = response.json() try: token_id = result['access']['token']['id'] tenant = result['access']['token']['tenant']['name'] tenant_id = result['access']['token']['tenant']['id'] logger.info(_("AWS authentication successful.")) except (AttributeError, KeyError): logger.info(_("AWS authentication failure.")) # Try to extract the reason for failure so we can return the # appropriate AWS error via raising an exception try: reason = result['error']['message'] except KeyError: reason = None if reason == "EC2 access key not found.": raise exception.HeatInvalidClientTokenIdError() elif reason == "EC2 signature not supplied.": raise exception.HeatSignatureError() else: raise exception.HeatAccessDeniedError() # Authenticated! ec2_creds = {'ec2Credentials': {'access': access, 'signature': signature}} req.headers['X-Auth-EC2-Creds'] = json.dumps(ec2_creds) req.headers['X-Auth-Token'] = token_id req.headers['X-Tenant-Name'] = tenant req.headers['X-Tenant-Id'] = tenant_id req.headers['X-Auth-URL'] = auth_uri metadata = result['access'].get('metadata', {}) roles = metadata.get('roles', []) req.headers['X-Roles'] = ','.join(roles) return self.application
def create_or_update(self, req, action=None): """ Implements CreateStack and UpdateStack API actions. Create or update stack as defined in template file. """ def extract_args(params): """ Extract request parameters/arguments and reformat them to match the engine API. FIXME: we currently only support a subset of the AWS defined parameters (both here and in the engine) """ # TODO(shardy) : Capabilities, NotificationARNs keymap = {'TimeoutInMinutes': engine_api.PARAM_TIMEOUT, 'DisableRollback': engine_api.PARAM_DISABLE_ROLLBACK} if 'DisableRollback' in params and 'OnFailure' in params: msg = _('DisableRollback and OnFailure ' 'may not be used together') raise exception.HeatInvalidParameterCombinationError( detail=msg) result = {} for k in keymap: if k in params: result[keymap[k]] = params[k] if 'OnFailure' in params: value = params['OnFailure'] if value == 'DO_NOTHING': result[engine_api.PARAM_DISABLE_ROLLBACK] = 'true' elif value in ('ROLLBACK', 'DELETE'): result[engine_api.PARAM_DISABLE_ROLLBACK] = 'false' return result if action not in self.CREATE_OR_UPDATE_ACTION: msg = _("Unexpected action %(action)s") % ({'action': action}) # This should not happen, so return HeatInternalFailureError return exception.HeatInternalFailureError(detail=msg) engine_action = {self.CREATE_STACK: self.engine_rpcapi.create_stack, self.UPDATE_STACK: self.engine_rpcapi.update_stack} con = req.context # Extract the stack input parameters stack_parms = self._extract_user_params(req.params) # Extract any additional arguments ("Request Parameters") create_args = extract_args(req.params) try: templ = self._get_template(req) except socket.gaierror: msg = _('Invalid Template URL') return exception.HeatInvalidParameterValueError(detail=msg) if templ is None: msg = _("TemplateBody or TemplateUrl were not given.") return exception.HeatMissingParameterError(detail=msg) try: stack = template_format.parse(templ) except ValueError: msg = _("The Template must be a JSON or YAML document.") return exception.HeatInvalidParameterValueError(detail=msg) args = {'template': stack, 'params': stack_parms, 'files': {}, 'args': create_args} try: stack_name = req.params['StackName'] if action == self.CREATE_STACK: args['stack_name'] = stack_name else: args['stack_identity'] = self._get_identity(con, stack_name) result = engine_action[action](con, **args) except Exception as ex: return exception.map_remote_error(ex) try: identity = identifier.HeatIdentifier(**result) except (ValueError, TypeError): response = result else: response = {'StackId': identity.arn()} return api_utils.format_response(action, response)
def create_or_update(self, req, action=None): """ Implements CreateStack and UpdateStack API actions Create or update stack as defined in template file """ def extract_args(params): """ Extract request parameters/arguments and reformat them to match the engine API. FIXME: we currently only support a subset of the AWS defined parameters (both here and in the engine) """ # TODO : Capabilities, DisableRollback, NotificationARNs keymap = {'TimeoutInMinutes': engine_api.PARAM_TIMEOUT, } result = {} for k in keymap: if k in req.params: result[keymap[k]] = params[k] return result if action not in self.CREATE_OR_UPDATE_ACTION: msg = _("Unexpected action %s" % action) # This should not happen, so return HeatInternalFailureError return exception.HeatInternalFailureError(detail=msg) engine_action = {self.CREATE_STACK: self.engine_rpcapi.create_stack, self.UPDATE_STACK: self.engine_rpcapi.update_stack} con = req.context # Extract the stack input parameters stack_parms = self._extract_user_params(req.params) # Extract any additional arguments ("Request Parameters") create_args = extract_args(req.params) try: templ = self._get_template(req) except socket.gaierror: msg = _('Invalid Template URL') return exception.HeatInvalidParameterValueError(detail=msg) if templ is None: msg = _("TemplateBody or TemplateUrl were not given.") return exception.HeatMissingParameterError(detail=msg) try: stack = json.loads(templ) except ValueError: msg = _("The Template must be a JSON document.") return exception.HeatInvalidParameterValueError(detail=msg) args = {'template': stack, 'params': stack_parms, 'args': create_args} try: stack_name = req.params['StackName'] if action == self.CREATE_STACK: args['stack_name'] = stack_name else: args['stack_identity'] = self._get_identity(con, stack_name) result = engine_action[action](con, **args) except rpc_common.RemoteError as ex: return exception.map_remote_error(ex) try: identity = identifier.HeatIdentifier(**result) except (ValueError, TypeError): response = result else: response = {'StackId': identity.arn()} return api_utils.format_response(action, response)