def AddAccountsFromDatabase(self, account_db_path): """Add accounts from the database at |account_db_path| to self. Overrides previously loaded accounts. Args: account_db_path: path to file containing an account database. """ raw_db = json_lib.ParseJsonFileWithComments(account_db_path) json_lib.AssertIsInstance(raw_db, dict, 'accounts database') # We don't mandate that an accounts database specify either field. raw_db.setdefault(USERS_KEY, []) raw_db.setdefault(GROUPS_KEY, []) user_list = json_lib.PopValueOfType(raw_db, USERS_KEY, list, 'list of users in accounts database') group_list = json_lib.PopValueOfType(raw_db, GROUPS_KEY, list, 'list of groups in accounts database') # We do mandate that the database contain only fields we know about. if raw_db: raise ValueError('Accounts database include unknown fields: %r' % raw_db.keys()) for user in user_list: json_lib.AssertIsInstance( user, dict, 'user specification in accounts database') self._AddUser(user) for group in group_list: json_lib.AssertIsInstance( group, dict, 'group specification in accounts database') self._AddGroup(group)
def testAssertIsInstance(self): """Test that AssertIsInstance is correct.""" self.assertRaises(ValueError, json_lib.AssertIsInstance, tuple(), list, 'a bad value') self.assertRaises(ValueError, json_lib.AssertIsInstance, 1, float, 'a bad value') self.assertRaises(ValueError, json_lib.AssertIsInstance, 1, bool, 'a bad value') json_lib.AssertIsInstance([1], list, 'good value') json_lib.AssertIsInstance(True, bool, 'good value') json_lib.AssertIsInstance({'foo': 2}, dict, 'good value')
def _FillInExecutableFromApp(self, wrapper, app): """Fill in the fields of a SandboxSpec.Executable object from |app|. Args: wrapper: instance of SandboxSpecWrapper. app: dictionary of information taken from the appc pod manifest. """ sub_app = json_lib.GetValueOfType( app, KEY_APP_SUB_APP, dict, 'per app app dict') user = json_lib.GetValueOfType( sub_app, KEY_SUB_APP_USER, unicode, 'app dict user') group = json_lib.GetValueOfType( sub_app, KEY_SUB_APP_GROUP, unicode, 'app dict group') if not self._user_db.UserExists(user): raise ValueError('Found invalid username "%s"' % user) if not self._user_db.GroupExists(group): raise ValueError('Found invalid groupname "%s"' % group) cmd = json_lib.GetValueOfType( sub_app, KEY_SUB_APP_EXEC, list, 'app command line') if not cmd: raise ValueError('App command line must give the executable to run.') self._CheckAbsPathToExecutable(cmd[0]) for cmd_piece in cmd: json_lib.AssertIsInstance(cmd_piece, unicode, 'app.exec fragment') port_list = sub_app.get(KEY_SUB_APP_PORTS, None) wrapper.AddExecutable(self._user_db.ResolveUsername(user), self._user_db.ResolveGroupname(group), cmd, _GetPortList(PROTOCOL_TCP, port_list), _GetPortList(PROTOCOL_UDP, port_list), _ExtractLinuxCapNames(sub_app))
def _FillInEndpointNamesFromAnnotations(self, wrapper, annotations): """Fill in the SandboxSpec endpoint_names field from |annotations|. An appc pod specification can contain a list of (mostly) arbitrary annotations that projects can use to add their own metadata fields. |annotations| is a list of dicts that each contain a name and value field, and this method looks for 'name' fields that are prefixed with ENDPOINT_NAME_ANNOTATION_PREFIX and treats the associated 'value' as the name of an endpoint that psyched will expect to be registered from within this sandbox. Args: wrapper: instance of SandboxSpecWrapper. annotations: list of dicts, each with a name and value field. """ for annotation in annotations: json_lib.AssertIsInstance(annotation, dict, 'a single annotation') name = json_lib.GetValueOfType( annotation, KEY_ANNOTATION_NAME, unicode, 'annotation name') if not IsValidAcName(name): raise ValueError('Annotation name "%s" contains illegal characters.' % name) if name.startswith(ENDPOINT_NAME_ANNOTATION_PREFIX): endpoint_name = json_lib.GetValueOfType( annotation, KEY_ANNOTATION_VALUE, unicode, 'endpoint name value') if not IsValidAcName(name): raise ValueError('Endpoint name "%s" contains illegal characters.' % endpoint_name) wrapper.AddEndpointName(endpoint_name)
def _ExtractLinuxCapNames(app_dict): """Parses the set of Linux capabilities for an executable. Args: app_dict: dictionary defining an executable. Returns: List of names of Linux capabilities (e.g. ['CAP_CHOWN']). """ if KEY_APP_ISOLATORS not in app_dict: return [] isolator_list = json_lib.GetValueOfType( app_dict, KEY_APP_ISOLATORS, list, 'list of isolators for application') linux_cap_isolators = [] # Look for any isolators related to capability sets. for isolator in isolator_list: json_lib.AssertIsInstance(isolator, dict, 'isolator instance') isolator_name = json_lib.GetValueOfType( isolator, ISOLATOR_KEY_NAME, unicode, 'isolator name') if not isolator_name.startswith(ISOLATOR_NAME_PREFIX): continue if isolator_name != ISOLATOR_NAME_RETAIN_SET: raise ValueError('Capabilities may only be specified as %s' % ISOLATOR_NAME_RETAIN_SET) linux_cap_isolators.append(isolator) # We may have only a single isolator. if len(linux_cap_isolators) > 1: raise ValueError('Found two lists of Linux caps for an executable') if not linux_cap_isolators: return [] value = json_lib.GetValueOfType( linux_cap_isolators[0], ISOLATOR_KEY_VALUE, dict, 'Linux cap isolator value') caps = json_lib.GetValueOfType( value, ISOLATOR_KEY_VALUE_SET, list, 'Linux cap isolator set') for cap in caps: json_lib.AssertIsInstance(cap, unicode, 'Linux capability in set.') return caps
def GetSandboxSpec(self, appc_contents, sandbox_spec_name): """Create a SandboxSpec encoding the information in an appc pod manifest. Args: appc_contents: string contents of an appc pod manifest sandbox_spec_name: string unique name of this sandbox. Returns: an instance of SandboxSpec. """ wrapper = SandboxSpecWrapper() overlay_name = None app_list = json_lib.GetValueOfType( appc_contents, KEY_APPS_LIST, list, 'app list') for app in app_list: json_lib.AssertIsInstance(app, dict, 'app') # Aid debugging of problems in specific apps. app_name = json_lib.GetValueOfType( app, KEY_APP_NAME, unicode, 'app name') if not IsValidAcName(app_name): raise ValueError('Application name "%s" contains illegal characters.' % app_name) logging.debug('Processing application "%s".', app_name) # Get the name of the image, check that it's consistent other image names. image = json_lib.GetValueOfType( app, KEY_APP_IMAGE, dict, 'image specification for app') image_name = json_lib.GetValueOfType( image, KEY_APP_IMAGE_NAME, unicode, 'image name') if not IsValidAcName(image_name): raise ValueError('Image name "%s" contains illegal characters.' % image_name) if overlay_name and overlay_name != image_name: raise ValueError( 'All elements of "apps" must have the same image.name.') overlay_name = image_name # Add the executable corresponding to this app to our SandboxSpec. self._FillInExecutableFromApp(wrapper, app) if not overlay_name: raise ValueError('Overlays must declare at least one app') annotation_list = json_lib.GetValueOfType( appc_contents, KEY_ANNOTATIONS_LIST, list, 'list of all annotations') self._FillInEndpointNamesFromAnnotations(wrapper, annotation_list) wrapper.SetName(sandbox_spec_name) return wrapper.sandbox_spec
def _AddGroup(self, group_spec): """Add a group to this account database based on |group_spec|. Args: group_spec: dict of information from an accounts database. This fragment is expected to have been parsed from developer supplied JSON and will be type checked. """ # By default, groups don't get a fixed GID. group_spec.setdefault(GROUP_FIXED_ID_KEY, False) # By default, groups don't get a password. group_spec.setdefault(GROUP_PASSWORD_KEY, u'!') # By default, groups are not defunct. group_spec.setdefault(GROUP_DEFUNCT_KEY, False) name = json_lib.PopValueOfType(group_spec, GROUP_NAME_KEY, six.text_type, 'groupname from group spec') password = json_lib.PopValueOfType(group_spec, GROUP_PASSWORD_KEY, six.text_type, 'password for group %s' % name) gid = json_lib.PopValueOfType(group_spec, GROUP_ID_KEY, int, 'gid for group %s' % name) users = json_lib.PopValueOfType(group_spec, GROUP_USERS_KEY, list, 'users in group %s' % name) is_fixed_id = json_lib.PopValueOfType( group_spec, GROUP_FIXED_ID_KEY, bool, 'whether GID for group %s is fixed' % name) is_defunct = json_lib.PopValueOfType( group_spec, GROUP_DEFUNCT_KEY, bool, 'whether group %s is defunct' % name) for username in users: json_lib.AssertIsInstance(username, six.text_type, 'user in group %s' % name) if group_spec: raise ValueError('Unexpected keys in group spec for group %s: %r' % (name, group_spec.keys())) self.groups[name] = Group(name=name, password=password, gid=gid, users=users, is_fixed_id=is_fixed_id, is_defunct=is_defunct)
def _GetPortList(desired_protocol, appc_port_list): """Get the list of ports opened for |desired_protocol| from |appc_port_list|. Args: desired_protocol: one of VALID_PROTOCOLS. appc_port_list: list of port specifications from a appc pod manifest. Returns: Instance of PortSpec. """ # The port specification is optional. if appc_port_list is None: return PortSpec(False, []) json_lib.AssertIsInstance(appc_port_list, list, 'port specification list') allow_all = False port_list = [] for port_dict in appc_port_list: json_lib.AssertIsInstance(port_dict, dict, 'port specification') port_dict = copy.deepcopy(port_dict) # By default, we open a single specified port. port_dict.setdefault(PORT_SPEC_COUNT, 1) # By default, don't set socket activated. port_dict.setdefault(PORT_SPEC_SOCKET_ACTIVATED, False) # We don't actually use the port name, but it's handy for documentation # and standard adherence to enforce its existence. port_name = json_lib.PopValueOfType( port_dict, PORT_SPEC_NAME, unicode, 'port name') logging.debug('Validating appc specifcation of "%s"', port_name) port = json_lib.PopValueOfType(port_dict, PORT_SPEC_PORT, int, 'port') protocol = json_lib.PopValueOfType( port_dict, PORT_SPEC_PROTOCOL, unicode, 'protocol') count = json_lib.PopValueOfType( port_dict, PORT_SPEC_COUNT, int, 'port range count') # We also don't use the socketActivated flag, but we should tolerate safe # values. socket_activated = json_lib.PopValueOfType( port_dict, PORT_SPEC_SOCKET_ACTIVATED, bool, 'socket activated flag') # Validate everything before acting on it. if protocol not in VALID_PROTOCOLS: raise ValueError('Port protocol must be in %r, not "%s"' % (VALID_PROTOCOLS, protocol)) if protocol != desired_protocol: continue if socket_activated != False: raise ValueError('No support for socketActivated==True in %s' % port_name) if port_dict: raise ValueError('Unknown keys found in port spec %s: %r' % (port_name, port_dict.keys())) if port == -1: # Remember that we're going to return that all ports are opened, but # continue validating all the remaining specifications. allow_all = True continue # Now we know it's not the wildcard port, and that we've never declared # a wildcard for this protocol. port = remote_access.NormalizePort(port) if count < 1: raise ValueError('May only specify positive port ranges for %s' % port_name) if port + count >= 65536: raise ValueError('Port range extends past max port number for %s' % port_name) for effective_port in xrange(port, port + count): port_list.append(effective_port) return PortSpec(allow_all, port_list)