Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(25)

Side by Side Diff: chrome/browser/policy/test/policy_testserver.py

Issue 2530023002: Fix policy test server key rotation feature (Closed)
Patch Set: Rename, add comments Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « chrome/browser/policy/test/local_policy_test_server.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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 # Convert the policy key version, which has to be positive according to the
1140 # policy protocol definition, to an index in the keys list.
1141 key_index = key_version - 1
1142 if key_index < 0:
1143 return None
1144 if key_index >= len(self.keys):
1145 if self.rotate_keys_automatically:
1146 key_index %= len(self.keys)
1147 else:
1148 return None
1149 return self.keys[key_index]
1150
1151 def GetKeyVersionForSigning(self, client_key_version):
1152 """Determines the version of the key that should be used for signing policy.
1153
1154 Args:
1155 client_key_version: Either an integer representing the current key version
1156 provided by the client, or None if the client didn't provide any.
1157
1158 Returns:
1159 An integer representing the signing key version.
1160 """
1161 if self.rotate_keys_automatically and client_key_version is not None:
1162 # Return the incremented version, which means that the key should be
1163 # rotated.
1164 return client_key_version + 1
1165 # Return the version that is specified by the config, defaulting to using
1166 # the very first key. Note that incrementing here is done due to conversion
1167 # between indices in the keys list and the key versions transmitted to the
1168 # client (where the latter have to be positive according to the policy
1169 # protocol definition).
1170 return self.GetPolicies().get('current_key_index', 0) + 1
1171
1120 def ResolveUser(self, auth_token): 1172 def ResolveUser(self, auth_token):
1121 """Tries to resolve an auth token to the corresponding user name. 1173 """Tries to resolve an auth token to the corresponding user name.
1122 1174
1123 If enabled, this makes a request to the token info endpoint to determine the 1175 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 1176 user ID corresponding to the token. If token resolution is disabled or the
1125 request fails, this will return the policy_user config parameter. 1177 request fails, this will return the policy_user config parameter.
1126 """ 1178 """
1127 config = self.GetPolicies() 1179 config = self.GetPolicies()
1128 token_info_url = config.get('token_info_url') 1180 token_info_url = config.get('token_info_url')
1129 if token_info_url is not None: 1181 if token_info_url is not None:
(...skipping 245 matching lines...) Expand 10 before | Expand all | Expand 10 after
1375 super(PolicyServerRunner, self).__init__() 1427 super(PolicyServerRunner, self).__init__()
1376 1428
1377 def create_server(self, server_data): 1429 def create_server(self, server_data):
1378 data_dir = self.options.data_dir or '' 1430 data_dir = self.options.data_dir or ''
1379 config_file = (self.options.config_file or 1431 config_file = (self.options.config_file or
1380 os.path.join(data_dir, 'device_management')) 1432 os.path.join(data_dir, 'device_management'))
1381 server = PolicyTestServer((self.options.host, self.options.port), 1433 server = PolicyTestServer((self.options.host, self.options.port),
1382 data_dir, config_file, 1434 data_dir, config_file,
1383 self.options.client_state_file, 1435 self.options.client_state_file,
1384 self.options.policy_keys, 1436 self.options.policy_keys,
1437 self.options.rotate_keys_automatically,
1385 self.options.server_base_url) 1438 self.options.server_base_url)
1386 server_data['port'] = server.server_port 1439 server_data['port'] = server.server_port
1387 return server 1440 return server
1388 1441
1389 def add_options(self): 1442 def add_options(self):
1390 testserver_base.TestServerRunner.add_options(self) 1443 testserver_base.TestServerRunner.add_options(self)
1391 self.option_parser.add_option('--client-state', dest='client_state_file', 1444 self.option_parser.add_option('--client-state', dest='client_state_file',
1392 help='File that client state should be ' 1445 help='File that client state should be '
1393 'persisted to. This allows the server to be ' 1446 'persisted to. This allows the server to be '
1394 'seeded by a list of pre-registered clients ' 1447 'seeded by a list of pre-registered clients '
1395 'and restarts without abandoning registered ' 1448 'and restarts without abandoning registered '
1396 'clients.') 1449 'clients.')
1397 self.option_parser.add_option('--policy-key', action='append', 1450 self.option_parser.add_option('--policy-key', action='append',
1398 dest='policy_keys', 1451 dest='policy_keys',
1399 help='Specify a path to a PEM-encoded ' 1452 help='Specify a path to a PEM-encoded '
1400 'private key to use for policy signing. May ' 1453 'private key to use for policy signing. May '
1401 'be specified multiple times in order to ' 1454 'be specified multiple times in order to '
1402 'load multiple keys into the server. If the ' 1455 'load multiple keys into the server. The '
1403 'server has multiple keys, it will rotate ' 1456 'server will use a canned key if none is '
1404 'through them in at each request in a ' 1457 'specified on the command line. The test '
1405 'round-robin fashion. The server will ' 1458 'server will also look for a verification '
1406 'use a canned key if none is specified ' 1459 'signature file in the same location: '
1407 'on the command line. The test server will ' 1460 '<filename>.sig and if present will add the '
1408 'also look for a verification signature file ' 1461 'signature to the policy blob as appropriate '
1409 'in the same location: <filename>.sig and if ' 1462 'via the '
1410 'present will add the signature to the '
1411 'policy blob as appropriate via the '
1412 'new_public_key_verification_signature_deprecated ' 1463 'new_public_key_verification_signature_deprecated '
1413 'field.') 1464 'field.')
1465 self.option_parser.add_option('--rotate-policy-keys-automatically',
1466 action='store_true',
1467 dest='rotate_keys_automatically',
1468 help='If present, then the policy keys will '
1469 'be rotated in a round-robin fashion for '
1470 'each policy request (by default, either the '
1471 'key specified in the config or the first '
1472 'key will be used for all requests).')
1414 self.option_parser.add_option('--log-level', dest='log_level', 1473 self.option_parser.add_option('--log-level', dest='log_level',
1415 default='WARN', 1474 default='WARN',
1416 help='Log level threshold to use.') 1475 help='Log level threshold to use.')
1417 self.option_parser.add_option('--config-file', dest='config_file', 1476 self.option_parser.add_option('--config-file', dest='config_file',
1418 help='Specify a configuration file to use ' 1477 help='Specify a configuration file to use '
1419 'instead of the default ' 1478 'instead of the default '
1420 '<data_dir>/device_management') 1479 '<data_dir>/device_management')
1421 self.option_parser.add_option('--server-base-url', dest='server_base_url', 1480 self.option_parser.add_option('--server-base-url', dest='server_base_url',
1422 help='The server base URL to use when ' 1481 help='The server base URL to use when '
1423 'constructing URLs to return to the client.') 1482 'constructing URLs to return to the client.')
1424 1483
1425 def run_server(self): 1484 def run_server(self):
1426 logger = logging.getLogger() 1485 logger = logging.getLogger()
1427 logger.setLevel(getattr(logging, str(self.options.log_level).upper())) 1486 logger.setLevel(getattr(logging, str(self.options.log_level).upper()))
1428 if (self.options.log_to_console): 1487 if (self.options.log_to_console):
1429 logger.addHandler(logging.StreamHandler()) 1488 logger.addHandler(logging.StreamHandler())
1430 if (self.options.log_file): 1489 if (self.options.log_file):
1431 logger.addHandler(logging.FileHandler(self.options.log_file)) 1490 logger.addHandler(logging.FileHandler(self.options.log_file))
1432 1491
1433 testserver_base.TestServerRunner.run_server(self) 1492 testserver_base.TestServerRunner.run_server(self)
1434 1493
1435 1494
1436 if __name__ == '__main__': 1495 if __name__ == '__main__':
1437 sys.exit(PolicyServerRunner().main()) 1496 sys.exit(PolicyServerRunner().main())
OLDNEW
« no previous file with comments | « chrome/browser/policy/test/local_policy_test_server.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698