Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """A bare-bones test server for testing cloud policy support. | 5 """A bare-bones test server for testing cloud policy support. |
| 6 | 6 |
| 7 This implements a simple cloud policy test server that can be used to test | 7 This implements a simple cloud policy test server that can be used to test |
| 8 chrome's device management service client. The policy information is read from | 8 chrome's device management service client. The policy information is read from |
| 9 the file named device_management in the server's data directory. It contains | 9 the file named device_management in the server's data directory. It contains |
| 10 enforced and recommended policies for the device and user scope, and a list | 10 enforced and recommended policies for the device and user scope, and a list |
| (...skipping 863 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 874 payload = self.CreatePolicyForExternalPolicyData(policy_key) | 874 payload = self.CreatePolicyForExternalPolicyData(policy_key) |
| 875 else: | 875 else: |
| 876 response.error_code = 400 | 876 response.error_code = 400 |
| 877 response.error_message = 'Invalid policy type' | 877 response.error_message = 'Invalid policy type' |
| 878 return | 878 return |
| 879 else: | 879 else: |
| 880 response.error_code = 400 | 880 response.error_code = 400 |
| 881 response.error_message = 'Request not allowed for the token used' | 881 response.error_message = 'Request not allowed for the token used' |
| 882 return | 882 return |
| 883 | 883 |
| 884 # Sign with 'current_key_index', defaulting to key 0. | 884 # Determine the current key on the client. |
| 885 signing_key = None | 885 client_key_version = None |
| 886 req_key = None | 886 client_key = None |
| 887 current_key_index = policy.get('current_key_index', 0) | 887 if msg.HasField('public_key_version'): |
| 888 nkeys = len(self.server.keys) | 888 client_key_version = msg.public_key_version |
| 889 if (msg.signature_type == dm.PolicyFetchRequest.SHA1_RSA and | 889 client_key = self.server.GetKeyByVersion(client_key_version) |
| 890 current_key_index in range(nkeys)): | 890 if client_key is None: |
| 891 signing_key = self.server.keys[current_key_index] | 891 response.error_code = 400 |
| 892 if msg.public_key_version in range(1, nkeys + 1): | 892 response.error_message = 'Invalid public key version' |
| 893 # requested key exists, use for signing and rotate. | 893 return |
| 894 req_key = self.server.keys[msg.public_key_version - 1]['private_key'] | 894 |
| 895 # Choose the key for signing the policy. | |
| 896 signing_key_version = self.server.GetKeyVersionForSigning( | |
| 897 client_key_version) | |
| 898 signing_key = self.server.GetKeyByVersion(signing_key_version) | |
| 899 assert signing_key is not None | |
| 895 | 900 |
| 896 # Fill the policy data protobuf. | 901 # Fill the policy data protobuf. |
| 897 policy_data = dm.PolicyData() | 902 policy_data = dm.PolicyData() |
| 898 policy_data.policy_type = msg.policy_type | 903 policy_data.policy_type = msg.policy_type |
| 899 policy_data.timestamp = int(time.time() * 1000) | 904 policy_data.timestamp = int(time.time() * 1000) |
| 900 policy_data.request_token = token_info['device_token'] | 905 policy_data.request_token = token_info['device_token'] |
| 901 policy_data.policy_value = payload | 906 policy_data.policy_value = payload |
| 902 policy_data.machine_name = token_info['machine_name'] | 907 policy_data.machine_name = token_info['machine_name'] |
| 903 policy_data.valid_serial_number_missing = ( | 908 policy_data.valid_serial_number_missing = ( |
| 904 token_info['machine_id'] in BAD_MACHINE_IDS) | 909 token_info['machine_id'] in BAD_MACHINE_IDS) |
| 905 policy_data.settings_entity_id = msg.settings_entity_id | 910 policy_data.settings_entity_id = msg.settings_entity_id |
| 906 policy_data.service_account_identity = policy.get( | 911 policy_data.service_account_identity = policy.get( |
| 907 'service_account_identity', | 912 'service_account_identity', |
| 908 'policy_testserver.py-service_account_identity') | 913 'policy_testserver.py-service_account_identity') |
| 909 invalidation_source = policy.get('invalidation_source') | 914 invalidation_source = policy.get('invalidation_source') |
| 910 if invalidation_source is not None: | 915 if invalidation_source is not None: |
| 911 policy_data.invalidation_source = invalidation_source | 916 policy_data.invalidation_source = invalidation_source |
| 912 # Since invalidation_name is type bytes in the proto, the Unicode name | 917 # Since invalidation_name is type bytes in the proto, the Unicode name |
| 913 # provided needs to be encoded as ASCII to set the correct byte pattern. | 918 # provided needs to be encoded as ASCII to set the correct byte pattern. |
| 914 invalidation_name = policy.get('invalidation_name') | 919 invalidation_name = policy.get('invalidation_name') |
| 915 if invalidation_name is not None: | 920 if invalidation_name is not None: |
| 916 policy_data.invalidation_name = invalidation_name.encode('ascii') | 921 policy_data.invalidation_name = invalidation_name.encode('ascii') |
| 917 | 922 |
| 918 if signing_key: | 923 if msg.signature_type != dm.PolicyFetchRequest.NONE: |
| 919 policy_data.public_key_version = current_key_index + 1 | 924 policy_data.public_key_version = signing_key_version |
| 920 | 925 |
| 921 if username: | 926 if username: |
| 922 policy_data.username = username | 927 policy_data.username = username |
| 923 else: | 928 else: |
| 924 # If the correct |username| is unknown, rely on a manually-configured | 929 # If the correct |username| is unknown, rely on a manually-configured |
| 925 # username from the configuration file or use a default. | 930 # username from the configuration file or use a default. |
| 926 policy_data.username = policy.get('policy_user', 'username@example.com') | 931 policy_data.username = policy.get('policy_user', 'username@example.com') |
| 927 policy_data.device_id = token_info['device_id'] | 932 policy_data.device_id = token_info['device_id'] |
| 928 | 933 |
| 929 # Set affiliation IDs so that user was managed on the device. | 934 # Set affiliation IDs so that user was managed on the device. |
| 930 device_affiliation_ids = policy.get('device_affiliation_ids') | 935 device_affiliation_ids = policy.get('device_affiliation_ids') |
| 931 if device_affiliation_ids: | 936 if device_affiliation_ids: |
| 932 policy_data.device_affiliation_ids.extend(device_affiliation_ids) | 937 policy_data.device_affiliation_ids.extend(device_affiliation_ids) |
| 933 | 938 |
| 934 user_affiliation_ids = policy.get('user_affiliation_ids') | 939 user_affiliation_ids = policy.get('user_affiliation_ids') |
| 935 if user_affiliation_ids: | 940 if user_affiliation_ids: |
| 936 policy_data.user_affiliation_ids.extend(user_affiliation_ids) | 941 policy_data.user_affiliation_ids.extend(user_affiliation_ids) |
| 937 | 942 |
| 938 signed_data = policy_data.SerializeToString() | 943 response.policy_data = policy_data.SerializeToString() |
| 939 | 944 |
| 940 response.policy_data = signed_data | 945 # Sign the serialized policy data |
| 941 if signing_key: | 946 if msg.signature_type == dm.PolicyFetchRequest.SHA1_RSA: |
| 942 response.policy_data_signature = ( | 947 response.policy_data_signature = bytes( |
| 943 bytes(signing_key['private_key'].hashAndSign(signed_data))) | 948 signing_key['private_key'].hashAndSign(response.policy_data)) |
| 944 if msg.public_key_version != current_key_index + 1: | 949 if msg.public_key_version != signing_key_version: |
| 945 response.new_public_key = signing_key['public_key'] | 950 response.new_public_key = signing_key['public_key'] |
| 946 | 951 |
| 947 # Set the verification signature appropriate for the policy domain. | 952 # Set the verification signature appropriate for the policy domain. |
| 948 # TODO(atwilson): Use the enrollment domain for public accounts when | 953 # TODO(atwilson): Use the enrollment domain for public accounts when |
| 949 # we add key validation for ChromeOS (http://crbug.com/328038). | 954 # we add key validation for ChromeOS (http://crbug.com/328038). |
| 950 if 'signatures' in signing_key: | 955 if 'signatures' in signing_key: |
| 951 verification_sig = self.GetSignatureForDomain( | 956 verification_sig = self.GetSignatureForDomain( |
| 952 signing_key['signatures'], policy_data.username) | 957 signing_key['signatures'], policy_data.username) |
| 953 | 958 |
| 954 if verification_sig: | 959 if verification_sig: |
| 955 assert len(verification_sig) == 256, \ | 960 assert len(verification_sig) == 256, \ |
| 956 'bad signature size: %d' % len(verification_sig) | 961 'bad signature size: %d' % len(verification_sig) |
| 957 response.new_public_key_verification_signature_deprecated = ( | 962 response.new_public_key_verification_signature_deprecated = ( |
| 958 verification_sig) | 963 verification_sig) |
| 959 | 964 |
| 960 if req_key: | 965 if client_key is not None: |
| 961 response.new_public_key_signature = ( | 966 response.new_public_key_signature = bytes( |
| 962 bytes(req_key.hashAndSign(response.new_public_key))) | 967 client_key['private_key'].hashAndSign(response.new_public_key)) |
| 963 | 968 |
| 964 return (200, response.SerializeToString()) | 969 return (200, response.SerializeToString()) |
| 965 | 970 |
| 966 def GetSignatureForDomain(self, signatures, username): | 971 def GetSignatureForDomain(self, signatures, username): |
| 967 parsed_username = username.split("@", 1) | 972 parsed_username = username.split("@", 1) |
| 968 if len(parsed_username) != 2: | 973 if len(parsed_username) != 2: |
| 969 logging.error('Could not extract domain from username: %s' % username) | 974 logging.error('Could not extract domain from username: %s' % username) |
| 970 return None | 975 return None |
| 971 domain = parsed_username[1] | 976 domain = parsed_username[1] |
| 972 | 977 |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1018 def DumpMessage(self, label, msg): | 1023 def DumpMessage(self, label, msg): |
| 1019 """Helper for logging an ASCII dump of a protobuf message.""" | 1024 """Helper for logging an ASCII dump of a protobuf message.""" |
| 1020 logging.debug('%s\n%s' % (label, str(msg))) | 1025 logging.debug('%s\n%s' % (label, str(msg))) |
| 1021 | 1026 |
| 1022 | 1027 |
| 1023 class PolicyTestServer(testserver_base.BrokenPipeHandlerMixIn, | 1028 class PolicyTestServer(testserver_base.BrokenPipeHandlerMixIn, |
| 1024 testserver_base.StoppableHTTPServer): | 1029 testserver_base.StoppableHTTPServer): |
| 1025 """Handles requests and keeps global service state.""" | 1030 """Handles requests and keeps global service state.""" |
| 1026 | 1031 |
| 1027 def __init__(self, server_address, data_dir, policy_path, client_state_file, | 1032 def __init__(self, server_address, data_dir, policy_path, client_state_file, |
| 1028 private_key_paths, server_base_url): | 1033 private_key_paths, rotate_keys_automatically, server_base_url): |
| 1029 """Initializes the server. | 1034 """Initializes the server. |
| 1030 | 1035 |
| 1031 Args: | 1036 Args: |
| 1032 server_address: Server host and port. | 1037 server_address: Server host and port. |
| 1033 policy_path: Names the file to read JSON-formatted policy from. | 1038 policy_path: Names the file to read JSON-formatted policy from. |
| 1034 private_key_paths: List of paths to read private keys from. | 1039 private_key_paths: List of paths to read private keys from. |
| 1040 rotate_keys_automatically: Whether the keys should be rotated in a | |
| 1041 round-robin fashion for each policy request (by default, either the | |
| 1042 key specified in the config or the first key will be used for all | |
|
Andrew T Wilson (Slow)
2016/11/25 14:50:09
This is fine. As we discussed, we might get slight
emaxx
2016/11/25 16:17:38
This is a valid concern.
I think ideally it should
| |
| 1043 requests). | |
| 1035 """ | 1044 """ |
| 1036 testserver_base.StoppableHTTPServer.__init__(self, server_address, | 1045 testserver_base.StoppableHTTPServer.__init__(self, server_address, |
| 1037 PolicyRequestHandler) | 1046 PolicyRequestHandler) |
| 1038 self._registered_tokens = {} | 1047 self._registered_tokens = {} |
| 1039 self.data_dir = data_dir | 1048 self.data_dir = data_dir |
| 1040 self.policy_path = policy_path | 1049 self.policy_path = policy_path |
| 1041 self.client_state_file = client_state_file | 1050 self.client_state_file = client_state_file |
| 1051 self.rotate_keys_automatically = rotate_keys_automatically | |
| 1042 self.server_base_url = server_base_url | 1052 self.server_base_url = server_base_url |
| 1043 | 1053 |
| 1044 self.keys = [] | 1054 self.keys = [] |
| 1045 if private_key_paths: | 1055 if private_key_paths: |
| 1046 # Load specified keys from the filesystem. | 1056 # Load specified keys from the filesystem. |
| 1047 for key_path in private_key_paths: | 1057 for key_path in private_key_paths: |
| 1048 try: | 1058 try: |
| 1049 key_str = open(key_path).read() | 1059 key_str = open(key_path).read() |
| 1050 except IOError: | 1060 except IOError: |
| 1051 print 'Failed to load private key from %s' % key_path | 1061 print 'Failed to load private key from %s' % key_path |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1110 policy = {} | 1120 policy = {} |
| 1111 if json is None: | 1121 if json is None: |
| 1112 logging.error('No JSON module, cannot parse policy information') | 1122 logging.error('No JSON module, cannot parse policy information') |
| 1113 else : | 1123 else : |
| 1114 try: | 1124 try: |
| 1115 policy = json.loads(open(self.policy_path).read(), strict=False) | 1125 policy = json.loads(open(self.policy_path).read(), strict=False) |
| 1116 except IOError: | 1126 except IOError: |
| 1117 logging.error('Failed to load policies from %s' % self.policy_path) | 1127 logging.error('Failed to load policies from %s' % self.policy_path) |
| 1118 return policy | 1128 return policy |
| 1119 | 1129 |
| 1130 def GetKeyByVersion(self, key_version): | |
| 1131 """Obtains the object containing key properties, given the key version. | |
| 1132 | |
| 1133 Args: | |
| 1134 key_version: Integer key version. | |
| 1135 | |
| 1136 Returns: | |
| 1137 The object containing key properties, or None if the key is not found. | |
| 1138 """ | |
| 1139 key_index = key_version - 1 | |
| 1140 if key_index < 0: | |
| 1141 return None | |
| 1142 if key_index >= len(self.keys): | |
| 1143 if self.rotate_keys_automatically: | |
| 1144 key_index %= len(self.keys) | |
| 1145 else: | |
| 1146 return None | |
| 1147 return self.keys[key_index] | |
| 1148 | |
| 1149 def GetKeyVersionForSigning(self, client_key_version): | |
| 1150 """Determines the version of the key that should be used for signing policy. | |
| 1151 | |
| 1152 Args: | |
| 1153 client_key_version: Either an integer representing the current key version | |
| 1154 provided by the client, or None if the client didn't provide any. | |
| 1155 | |
| 1156 Returns: | |
| 1157 An integer representing the signing key version. | |
| 1158 """ | |
| 1159 if self.rotate_keys_automatically and client_key_version is not None: | |
| 1160 return client_key_version + 1 | |
| 1161 return self.GetPolicies().get('current_key_index', 0) + 1 | |
| 1162 | |
| 1120 def ResolveUser(self, auth_token): | 1163 def ResolveUser(self, auth_token): |
| 1121 """Tries to resolve an auth token to the corresponding user name. | 1164 """Tries to resolve an auth token to the corresponding user name. |
| 1122 | 1165 |
| 1123 If enabled, this makes a request to the token info endpoint to determine the | 1166 If enabled, this makes a request to the token info endpoint to determine the |
| 1124 user ID corresponding to the token. If token resolution is disabled or the | 1167 user ID corresponding to the token. If token resolution is disabled or the |
| 1125 request fails, this will return the policy_user config parameter. | 1168 request fails, this will return the policy_user config parameter. |
| 1126 """ | 1169 """ |
| 1127 config = self.GetPolicies() | 1170 config = self.GetPolicies() |
| 1128 token_info_url = config.get('token_info_url') | 1171 token_info_url = config.get('token_info_url') |
| 1129 if token_info_url is not None: | 1172 if token_info_url is not None: |
| (...skipping 245 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1375 super(PolicyServerRunner, self).__init__() | 1418 super(PolicyServerRunner, self).__init__() |
| 1376 | 1419 |
| 1377 def create_server(self, server_data): | 1420 def create_server(self, server_data): |
| 1378 data_dir = self.options.data_dir or '' | 1421 data_dir = self.options.data_dir or '' |
| 1379 config_file = (self.options.config_file or | 1422 config_file = (self.options.config_file or |
| 1380 os.path.join(data_dir, 'device_management')) | 1423 os.path.join(data_dir, 'device_management')) |
| 1381 server = PolicyTestServer((self.options.host, self.options.port), | 1424 server = PolicyTestServer((self.options.host, self.options.port), |
| 1382 data_dir, config_file, | 1425 data_dir, config_file, |
| 1383 self.options.client_state_file, | 1426 self.options.client_state_file, |
| 1384 self.options.policy_keys, | 1427 self.options.policy_keys, |
| 1428 self.options.rotate_keys_automatically, | |
| 1385 self.options.server_base_url) | 1429 self.options.server_base_url) |
| 1386 server_data['port'] = server.server_port | 1430 server_data['port'] = server.server_port |
| 1387 return server | 1431 return server |
| 1388 | 1432 |
| 1389 def add_options(self): | 1433 def add_options(self): |
| 1390 testserver_base.TestServerRunner.add_options(self) | 1434 testserver_base.TestServerRunner.add_options(self) |
| 1391 self.option_parser.add_option('--client-state', dest='client_state_file', | 1435 self.option_parser.add_option('--client-state', dest='client_state_file', |
| 1392 help='File that client state should be ' | 1436 help='File that client state should be ' |
| 1393 'persisted to. This allows the server to be ' | 1437 'persisted to. This allows the server to be ' |
| 1394 'seeded by a list of pre-registered clients ' | 1438 'seeded by a list of pre-registered clients ' |
| 1395 'and restarts without abandoning registered ' | 1439 'and restarts without abandoning registered ' |
| 1396 'clients.') | 1440 'clients.') |
| 1397 self.option_parser.add_option('--policy-key', action='append', | 1441 self.option_parser.add_option('--policy-key', action='append', |
| 1398 dest='policy_keys', | 1442 dest='policy_keys', |
| 1399 help='Specify a path to a PEM-encoded ' | 1443 help='Specify a path to a PEM-encoded ' |
| 1400 'private key to use for policy signing. May ' | 1444 'private key to use for policy signing. May ' |
| 1401 'be specified multiple times in order to ' | 1445 'be specified multiple times in order to ' |
| 1402 'load multiple keys into the server. If the ' | 1446 'load multiple keys into the server. The ' |
| 1403 'server has multiple keys, it will rotate ' | 1447 'server will use a canned key if none is ' |
| 1404 'through them in at each request in a ' | 1448 'specified on the command line. The test ' |
| 1405 'round-robin fashion. The server will ' | 1449 'server will also look for a verification ' |
| 1406 'use a canned key if none is specified ' | 1450 'signature file in the same location: ' |
| 1407 'on the command line. The test server will ' | 1451 '<filename>.sig and if present will add the ' |
| 1408 'also look for a verification signature file ' | 1452 'signature to the policy blob as appropriate ' |
| 1409 'in the same location: <filename>.sig and if ' | 1453 'via the ' |
| 1410 'present will add the signature to the ' | |
| 1411 'policy blob as appropriate via the ' | |
| 1412 'new_public_key_verification_signature_deprecated ' | 1454 'new_public_key_verification_signature_deprecated ' |
| 1413 'field.') | 1455 'field.') |
| 1456 self.option_parser.add_option('--rotate-policy-keys-automatically', | |
| 1457 action='store_true', | |
| 1458 dest='rotate_keys_automatically', | |
| 1459 help='If present, then the policy keys will ' | |
| 1460 'be rotated in a round-robin fashion for ' | |
| 1461 'each policy request (by default, either the ' | |
| 1462 'key specified in the config or the first ' | |
| 1463 'key will be used for all requests).') | |
| 1414 self.option_parser.add_option('--log-level', dest='log_level', | 1464 self.option_parser.add_option('--log-level', dest='log_level', |
| 1415 default='WARN', | 1465 default='WARN', |
| 1416 help='Log level threshold to use.') | 1466 help='Log level threshold to use.') |
| 1417 self.option_parser.add_option('--config-file', dest='config_file', | 1467 self.option_parser.add_option('--config-file', dest='config_file', |
| 1418 help='Specify a configuration file to use ' | 1468 help='Specify a configuration file to use ' |
| 1419 'instead of the default ' | 1469 'instead of the default ' |
| 1420 '<data_dir>/device_management') | 1470 '<data_dir>/device_management') |
| 1421 self.option_parser.add_option('--server-base-url', dest='server_base_url', | 1471 self.option_parser.add_option('--server-base-url', dest='server_base_url', |
| 1422 help='The server base URL to use when ' | 1472 help='The server base URL to use when ' |
| 1423 'constructing URLs to return to the client.') | 1473 'constructing URLs to return to the client.') |
| 1424 | 1474 |
| 1425 def run_server(self): | 1475 def run_server(self): |
| 1426 logger = logging.getLogger() | 1476 logger = logging.getLogger() |
| 1427 logger.setLevel(getattr(logging, str(self.options.log_level).upper())) | 1477 logger.setLevel(getattr(logging, str(self.options.log_level).upper())) |
| 1428 if (self.options.log_to_console): | 1478 if (self.options.log_to_console): |
| 1429 logger.addHandler(logging.StreamHandler()) | 1479 logger.addHandler(logging.StreamHandler()) |
| 1430 if (self.options.log_file): | 1480 if (self.options.log_file): |
| 1431 logger.addHandler(logging.FileHandler(self.options.log_file)) | 1481 logger.addHandler(logging.FileHandler(self.options.log_file)) |
| 1432 | 1482 |
| 1433 testserver_base.TestServerRunner.run_server(self) | 1483 testserver_base.TestServerRunner.run_server(self) |
| 1434 | 1484 |
| 1435 | 1485 |
| 1436 if __name__ == '__main__': | 1486 if __name__ == '__main__': |
| 1437 sys.exit(PolicyServerRunner().main()) | 1487 sys.exit(PolicyServerRunner().main()) |
| OLD | NEW |