Beispiel #1
0
	def post(self):
		# paramters, assume failure, response type
		params = {}
		params['response'] = "error"
		self.response.headers['Content-Type'] = "application/json"

		# get appliance variables
		try:
			packet = json.loads(self.request.body)
			apitoken = packet['appliance']['apitoken']
		except:
			params['message'] = "You must submit a valid JSON object with a token."
			self.response.set_status(401)
			return self.render_template('api/response.json', **params)	
		
		# load the appliance
		appliance = Appliance.get_by_token(apitoken)

		if not appliance:
			params['message'] = "Token is not valid."
			self.response.set_status(401)
			return self.render_template('api/response.json', **params)

		if appliance.activated == False:
			# appliance not activated
			params['message'] = "Appliance has been disabled by pool controller. Please contact support."
			self.response.set_status(409)
			return self.render_template('api/response.json', **params)

		# update appliance info
		latitude = float(packet['appliance']['location']['latitude'])
		longitude = float(packet['appliance']['location']['longitude'])
		appliance.location = ndb.GeoPt(latitude, longitude)
		appliance.dynamicimages = bool(packet['appliance']['dynamicimages'])			
		appliance.put()

		# loop through instances being advertised for sale
		for appliance_instance in packet['instances']:
			# pass in appliance_instance and appliance object
			#logging.info("instance: %s" % appliance_instance['name'])
			instance = Instance.push(appliance_instance, appliance)

		# build parameter list
		params = {}
		params['response'] = "success"
		params['message'] = "Instances accepted for sale."
		self.response.headers['Content-Type'] = 'application/json'
		
		return self.render_template('api/response.json', **params)
Beispiel #2
0
	def post(self):
		# paramters, assume failure, response type
		params = {}
		params['response'] = "fail"
		self.response.headers['Content-Type'] = "application/json"

		# get appliance variables
		try:
			packet = json.loads(self.request.body)
			apitoken = packet['appliance']['apitoken']
		except:
			params['message'] = "You must submit a valid JSON object with a token."
			self.response.set_status(401)
			return self.render_template('api/response.json', **params)	
		
		# load the appliance
		appliance = Appliance.get_by_token(apitoken)

		if not appliance:
			params['message'] = "Token is not valid."
			self.response.set_status(401)
			return self.render_template('api/response.json', **params)

		if appliance.activated == False:
			# appliance not activated
			params['message'] = "Appliance has been disabled by pool controller. Please contact support."
			self.response.set_status(409)
			return self.render_template('api/response.json', **params)

		# update appliance info
		latitude = float(packet['appliance']['location']['latitude'])
		longitude = float(packet['appliance']['location']['longitude'])
		appliance.location = ndb.GeoPt(latitude, longitude)
		appliance.dynamicimages = bool(packet['appliance']['dynamicimages'])			
		appliance.put()

		# respond with success
		params['response'] = "success"
		params['message'] = "Appliance token authenticated."
		return self.render_template('api/response.json', **params)
Beispiel #3
0
	def prepare_appliance(self, val):
		appliance = Appliance.get_by_token(val.token)
		return [
			('appliance', appliance.key),
			('owner', appliance.owner),
			('group', appliance.group),]
Beispiel #4
0
	def post(self, instance_name):
		# paramters, assume failure, response type
		params = {}
		params['response'] = "fail"
		self.response.headers['Content-Type'] = "application/json"

		# get appliance variables
		try:
			packet = json.loads(self.request.body)
			apitoken = packet['appliance']['apitoken']
		except:
			params['message'] = "You must submit a valid JSON object with a token."
			self.response.set_status(401)
			return self.render_template('api/response.json', **params)	
		
		# load the appliance
		appliance = Appliance.get_by_token(apitoken)

		if not appliance:
			params['message'] = "Token is not valid."
			self.response.set_status(401)
			return self.render_template('api/response.json', **params)

		if appliance.activated == False:
			# appliance not activated
			params['message'] = "Appliance has been disabled by pool controller. Please contact support."
			self.response.set_status(409)
			return self.render_template('api/response.json', **params)

		# pull out the appliance's instance
		try:
			appliance_instance = packet['instance']
		except:
			params['response'] = "fail"
			params['result'] = "JSON instance data not found."
			self.response.set_status(404)
			return self.render_template('api/response.json', **params)
		
		# grab the instance name and check the url
		try:
			name = appliance_instance['name']
			# same name?
			if instance_name != name:
				raise
		except:
			params['response'] = "fail"
			params['result'] = "JSON instance name needs to match resource URI."
			self.response.set_status(401)
			self.response.headers['Content-Type'] = 'application/json'
			return self.render_template('api/response.json', **params)			

		# grab the rest of the instance info
		try:
			# grab the rest of appliance POST data
			flavor_name = appliance_instance['flavor']
			ask = appliance_instance['ask']
			expires = datetime.fromtimestamp(appliance_instance['expires'])
			address = appliance_instance['address'] # bitcoin address
		except:
			params['response'] = "fail"
			params['result'] = "JSON instance data not found.  Flavor, ask, expires or address missing."
			self.response.set_status(404)
			return self.render_template('api/response.json', **params)

		# look up the pool's version of this instance
		instance = Instance.get_by_name_appliance(name, appliance.key)

		# create a new instance for this appliance because we've never seen it
		if not instance:
			instance = Instance().push(appliance_instance, appliance)
			wisp = Wisp.get_user_default(appliance.owner)
			instance.wisp = wisp.key
			instance.put()

		# grab the instance's wisp
		if instance.wisp:
			wisp = Wisp.get_by_id(instance.wisp.id())
		else:
			# we need a decent fallback for how to boot an image without a wisp
			wisp = Wisp.get_user_default(instance.owner)

		# get the value or return None if not present
		dynamic_image_url = wisp.dynamic_image_url if wisp.dynamic_image_url > "" else None
		callback_url = wisp.callback_url if wisp.callback_url > "" else None
		image = wisp.image.get().name if wisp.image else None
		
		# pop the ssh_key script into an array
		if wisp.ssh_key:
			ssh_key = []
			for line in iter(wisp.ssh_key.splitlines()):
				ssh_key.append(line)
		else:
			ssh_key = [""]

		# pop the post creation script into an array
		if wisp.post_creation:
			post_creation = []
			for line in iter(wisp.post_creation.splitlines()):
				post_creation.append(line)
		else:
			post_creation = [""]

		# load the instance info back into the response
		params = {
			'response': "success",
			'instance_name': name,
			'image': image,
			'dynamic_image_url': dynamic_image_url,
			'callback_url': callback_url,
			'ssh_key': ssh_key,
			'post_creation': post_creation 
		}

		self.response.headers['Content-Type'] = 'application/json'
		return self.render_template('api/instances.json', **params)
Beispiel #5
0
    def post(self):
        # lookup user's auth info
        user_info = User.get_by_id(long(self.user_id))

        # initialize form choices for group
        self.form.group.choices = []

        # add list of user's groups, if any
        groups = GroupMembers.get_user_groups(user_info.key)
        for group in groups:
            self.form.group.choices.insert(0,
                                           (str(group.key.id()), group.name))

        # public group
        self.form.group.choices.insert(0, ('public', "Public"))

        # check if we are getting a custom group entry
        if self.form.group.data == "custom":
            # check if the group exists
            if Group.get_by_name(self.form.custom.data.strip()):
                self.add_message("A group with that name already exists!",
                                 "error")
                return self.redirect_to('account-appliances')

            # make the new group
            group = Group(name=self.form.custom.data.strip(),
                          owner=user_info.key)
            group.put()
            group_key = group.key

            # create the group member entry
            groupmember = GroupMembers(
                group=group_key,
                member=user_info.key,
                invitor=user_info.key,  # same same
                active=True)
            groupmember.put()

            # hack the form with new group
            self.form.group.choices.insert(0, ('custom', "Custom"))
        else:
            # grab an existing group
            if self.form.group.data.strip() == 'public':
                # no group for public appliances
                group_key = None
            else:
                # check membership
                group = Group.get_by_id(int(self.form.group.data.strip()))
                if GroupMembers.is_member(user_info.key, group.key):
                    group_key = group.key
                else:
                    group_key = None

        # check what was returned from the rest of the form validates
        if not self.form.validate():
            self.add_message("The new appliance form did not validate.",
                             "error")
            return self.get()

        # load form values
        name = self.form.name.data.strip()
        token = self.form.token.data.strip()

        # check if we have it already - all that work bitches?
        if Appliance.get_by_token(token):
            self.add_message("An appliance with that token already exists!",
                             "error")
            return self.redirect_to('account-appliances')

        # save the new appliance in our database
        appliance = Appliance(name=name,
                              token=token,
                              group=group_key,
                              owner=user_info.key)
        appliance.put()

        # log to alert
        self.add_message("Appliance %s successfully created!" % name,
                         "success")

        # give it a few seconds to update db, then redirect
        time.sleep(1)
        return self.redirect_to('account-appliances')
Beispiel #6
0
    def post(self):
        # lookup user's auth info
        user_info = User.get_by_id(long(self.user_id))

        # initialize form choices for group
        self.form.group.choices = []

        # add list of user's groups, if any
        groups = GroupMembers.get_user_groups(user_info.key)
        for group in groups:
            self.form.group.choices.insert(0, (str(group.key.id()), group.name))

            # public group
        self.form.group.choices.insert(0, ("public", "Public"))

        # check if we are getting a custom group entry
        if self.form.group.data == "custom":
            # check if the group exists
            if Group.get_by_name(self.form.custom.data.strip()):
                self.add_message("A group with that name already exists!", "error")
                return self.redirect_to("account-appliances")

                # make the new group
            group = Group(name=self.form.custom.data.strip(), owner=user_info.key)
            group.put()
            group_key = group.key

            # create the group member entry
            groupmember = GroupMembers(
                group=group_key, member=user_info.key, invitor=user_info.key, active=True  # same same
            )
            groupmember.put()

            # hack the form with new group
            self.form.group.choices.insert(0, ("custom", "Custom"))
        else:
            # grab an existing group
            if self.form.group.data.strip() == "public":
                # no group for public appliances
                group_key = None
            else:
                # check membership
                group = Group.get_by_id(int(self.form.group.data.strip()))
                if GroupMembers.is_member(user_info.key, group.key):
                    group_key = group.key
                else:
                    group_key = None

                    # check what was returned from the rest of the form validates
        if not self.form.validate():
            self.add_message("The new appliance form did not validate.", "error")
            return self.get()

            # load form values
        name = self.form.name.data.strip()
        token = self.form.token.data.strip()

        # check if we have it already - all that work bitches?
        if Appliance.get_by_token(token):
            self.add_message("An appliance with that token already exists!", "error")
            return self.redirect_to("account-appliances")

            # save the new appliance in our database
        appliance = Appliance(name=name, token=token, group=group_key, owner=user_info.key)
        appliance.put()

        # log to alert
        self.add_message("Appliance %s successfully created!" % name, "success")

        # give it a few seconds to update db, then redirect
        time.sleep(1)
        return self.redirect_to("account-appliances")
Beispiel #7
0
	def post(self, instance_name):
		# paramters, assume failure, response type
		params = {}
		params['response'] = "error"
		self.response.headers['Content-Type'] = "application/json"

		# request basics
		ip = self.request.remote_addr

		try:
			body = json.loads(self.request.body)
			instance_schema = schemas['InstanceSchema'](**body['instance'])
			appliance_schema = schemas['ApplianceSchema'](**body['appliance'])

			# try to authenticate appliance
			if not Appliance.authenticate(appliance_schema.apitoken.as_dict()):
				logging.error("%s is using an invalid token(%s) or appliance deactivated."
					% (ip, appliance_schema.apitoken.as_dict()))
				return error_response(self, "Token is not valid.", 401, params)

			# fetch appliance and instance
			appliance = Appliance.get_by_token(appliance_schema.apitoken.as_dict())

			instance = Instance.get_by_name_appliance(
				instance_schema.name.as_dict(), 
				appliance.key
			)

			# if instance doesn't already exist, create it
			if not instance:
				wisp = Wisp.get_user_default(appliance.owner)
				if not wisp:
					wisp = Wisp.get_system_default()
				instance = Instance(wisp=wisp.key)

			# wrap instance into api shim in order to translate values from structure
			# of api to structure of model. I hope at some point in the future the two
			# models are similar enough so we can entirely drop this shim
			instance_shim = InstanceApiShim(instance)

			# update instance with values from post
			ApiSchemaHelper.fill_object_from_schema(
				instance_schema, instance_shim)

			# associate instance with it's appliance
			instance_shim.appliance = appliance

		except Exception as e:
			return error_response(self, 'Error in creating or updating instance from '
				'post data, with message {0}'.format(str(e)), 500, {})


		# update local instance
		instance.put()

		# update appliance ip address hint
		if instance.state > 3 and instance.ipv4_address:
			appliance.ipv4enabled = True
		if instance.state > 3 and instance.ipv6_address:
			appliance.ipv6enabled = True
		appliance.put()

		# sleep for dev
		if config.debug:
			time.sleep(1)

		# send update information to channel
		if instance.token:

			output = {
				"name": instance.name,
				"token": instance.token,
				"state": instance.state,
			}
			channel.send_message(instance.token, json.dumps(output))		

		# pop a reload just in case user is on their cloud page
		if instance.owner:
			user_info = User.get_by_id(long(instance.owner.id()))
			channel.send_message(user_info.key.urlsafe(), "reload")

		# convert bid to instance
		# check if there is an instance bid reservation on this instance
		instancebid = InstanceBid.get_by_instance(instance.key)
		if instancebid:
			# check for a bid callback_url (entered in the callback field on the launcher)
			if instancebid.callback_url > "":
				# put the callback into the instance
				instance.callback_url = instancebid.callback_url
			
			elif instancebid.wisp:
				# otherwise, get the wisp's callback URL	
				callback_url = instancebid.wisp.get().callback_url
			
				# if the wisp has an empty callback URL, populate the instance with the wisp's bid details
				if callback_url == "" or callback_url == None:
					instance.wisp = instancebid.wisp
					instance.owner = instancebid.wisp.get().owner
					instance.cloud = instancebid.cloud
				else:
					# we have a custom callback in the wisp itself, so move to instance
					instance.callback_url = callback_url

			# update the instance
			instance.put()

			# delete the instance reservation
			instancebid.key.delete()

		# proxy custom callback
	
		"""
		BEGIN CODE CALLOUT
		"""
		# August 24, 2014
		#
		# The following code retrieves content from a remote server and proxies it to 
		# an appliance for starting an instance. It is at this point an anonymous user's
		# callback pattern is most venerable to being monitored and manipulated.
		# 
		# The author of this software issues a warrant canary tweet to @stackape which 
		# should appear on the 1st of each month indicating the absence of a gag order
		# having been issued to StackMonkey, Utter.io, or Kord Campbell.
		#
		# Please monitor the code block between BEGIN CODE CALLOUT and END CODE CALLOUT 
		# if the canary tweet does not appear on schedule. Please monitor any callback
		# file's retrieval patterns if the canary tweet does not appear on schedule.
		# Please report any findings to the community sections of the project/site.
		#
		# Remember, the code is Open Source. Use that fact to the community's advantage.
		# 
		# The warrant canary will likely be your only indication a change has possibly
		# taken place to the system.  These changes may or may not enable a government 
		# agency to intercept and monitor instances using a custom URL callback.
		#
		# Kord Campbell
		#
		if instance.callback_url:
			try:
				result = urlfetch.fetch(instance.callback_url, deadline=5)
			except Exception as ex:
				logging.error("Error fetching callback URL content.")
				instance.console_output = "Error fetching callback url=(%s)'s' content. %s" % (instance.callback_url, ex)
				instance.put()

				# user may be sitting on an instance reservation here, so reload the page
				# this will force the handler to redirect the user to the instance page
				channel.send_message(instance.token, "reload")
				return error_response(self, "Error fetching callback URL content.", 401, params)

			# return content retrieved from callback URL if the JSON returned by this method includes
			# a callback_url in the data, the appliance will follow the URL and will not call this API 
			# again during the life of the instance.
			self.response.headers['Content-Type'] = 'application/json'
			self.response.write(json.dumps(json.loads(result.content), sort_keys=True, indent=2))
			
			# return from here	
			return

		"""
		END CODE CALLOUT
		"""

		# at this point we have one of two scenarios:
		# 1. an external instance start (registered user with appliance, sans instancebid)
		# 2. registered user using a normal wisp WITHOUT a callback_url

		# grab the instance's wisp
		if instance.wisp:
			# if instance is using a wisp
			wisp = Wisp.get_by_id(instance.wisp.id())
		else:
			# no wisp on instance
			wisp = Wisp.get_user_default(instance.owner)

		# deliver default system wisp if none (external instance start)
		if not wisp:
			wisp = Wisp.get_system_default()

		# load wisp image
		if not wisp.use_dynamic_image:
			image = wisp.image.get()
		else:
			image = wisp.get_dynamic_image()

		# pop the ssh_key into an array
		if wisp.ssh_key:
			ssh_keys = []
			for line in iter(wisp.ssh_key.splitlines()):
				ssh_keys.append(line)
		else:
			ssh_keys = [""]

		# 
		# pop the post creation script into an array
		if wisp.post_creation:
			post_creation = []
			for line in iter(wisp.post_creation.splitlines()):
				post_creation.append(line)
		else:
			post_creation = [""]

		# some of replay's magic - need docs on this
		start_params = schemas['InstanceStartParametersSchema']()
		data = {
			'image': image,
			'callback_url': wisp.callback_url if wisp.callback_url else "",
			'ssh_keys': ssh_keys,
			'post_create': post_creation}
		ApiSchemaHelper.fill_schema_from_object(start_params, data)

		self.response.set_status(200)
		self.response.headers['Content-Type'] = 'application/json'

		# write dictionary as json string
		self.response.out.write(json.dumps(
				# retrieve dict from schema
				start_params.as_dict()))