def validate_mmds(ssh_connection, data_store): """Validate that MMDS contents fetched from the guest.""" # Configure interface to route MMDS requests cmd = 'ip route add {} dev {}'.format(IPV4_ADDRESS, net_iface_for_mmds.dev_name) _, stdout, stderr = ssh_connection.execute_command(cmd) assert stdout.read() == stderr.read() == '' # Fetch metadata to ensure MMDS is accessible. token = generate_mmds_session_token(ssh_connection, IPV4_ADDRESS, token_ttl=60) cmd = generate_mmds_get_request(IPV4_ADDRESS, token=token) _, stdout, _ = ssh_connection.execute_command(cmd) assert json.load(stdout) == data_store
def _build_cmd_to_fetch_metadata(ssh_connection, version, ipv4_address): """ Build command to fetch metadata from the guest's side. The request is built based on the MMDS version configured. If MMDSv2 is used, a session token must be created before the `GET` request. """ # Fetch data from MMDS from the guest's side. if version == "V2": # If MMDS is configured to version 2, so we need to create # the session token first. token = generate_mmds_session_token(ssh_connection, ipv4_address, token_ttl=60) else: token = None return generate_mmds_get_request(ipv4_address, token)
def _test_mmds(vm, mmds_net_iface): # Populate MMDS. data_store = {"latest": {"meta-data": {"ami-id": "ami-12345678"}}} _populate_data_store(vm, data_store) mmds_ipv4_address = "169.254.169.254" vm.ssh_config["hostname"] = mmds_net_iface.guest_ip ssh_connection = net_tools.SSHConnection(vm.ssh_config) # Insert new rule into the routing table of the guest. cmd = "ip route add {} dev {}".format( mmds_net_iface.guest_ip, mmds_net_iface.dev_name ) code, _, _ = ssh_connection.execute_command(cmd) assert code == 0 # The base microVM had MMDS version 2 configured, which was persisted # across the snapshot-restore. token = generate_mmds_session_token(ssh_connection, mmds_ipv4_address, token_ttl=60) cmd = generate_mmds_get_request(mmds_ipv4_address, token=token) _, stdout, _ = ssh_connection.execute_command(cmd) assert json.load(stdout) == data_store
def test_mmds_snapshot(bin_cloner_path): """ Exercise MMDS behavior with snapshots. Ensures that MMDS V2 behavior is not affected by taking a snapshot and that MMDS V2 is not available after snapshot load. @type: functional """ vm_builder = MicrovmBuilder(bin_cloner_path) net_iface = NetIfaceConfig() vm_instance = vm_builder.build_vm_nano( net_ifaces=[net_iface], diff_snapshots=True ) test_microvm = vm_instance.vm root_disk = vm_instance.disks[0] ssh_key = vm_instance.ssh_key ipv4_address = '169.254.169.250' # Configure MMDS version with custom IPv4 address. _configure_mmds( test_microvm, version='V2', iface_id=DEFAULT_DEV_NAME, ipv4_address=ipv4_address ) data_store = { 'latest': { 'meta-data': { 'ami-id': 'ami-12345678' } } } _populate_data_store(test_microvm, data_store) test_microvm.start() snapshot_builder = SnapshotBuilder(test_microvm) disks = [root_disk.local_path()] ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config) cmd = 'ip route add {} dev eth0'.format(ipv4_address) _, stdout, stderr = ssh_connection.execute_command(cmd) _assert_out(stdout, stderr, '') # Generate token. token = generate_mmds_session_token( ssh_connection, ipv4_address=ipv4_address, token_ttl=60 ) pre = 'curl -m 2 -s' pre += ' -X GET' pre += ' -H "X-metadata-token: {}"'.format(token) pre += ' http://{}/'.format(ipv4_address) # Fetch metadata. cmd = pre + 'latest/meta-data/' _, stdout, stderr = ssh_connection.execute_command(cmd) _assert_out(stdout, stderr, "ami-id") # Create diff snapshot. snapshot = snapshot_builder.create(disks, ssh_key, SnapshotType.DIFF) # Resume microVM and ensure session token is still valid on the base. response = test_microvm.vm.patch(state='Resumed') assert test_microvm.api_session.is_status_no_content(response.status_code) _, stdout, stderr = ssh_connection.execute_command( pre + 'latest/meta-data/' ) _assert_out(stdout, stderr, "ami-id") # Kill base microVM. test_microvm.kill() # Load microVM clone from snapshot. test_microvm, _ = vm_builder.build_from_snapshot(snapshot, resume=True, diff_snapshots=True) _populate_data_store(test_microvm, data_store) ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config) # Mmds V2 is not available with snapshots. # Test that `PUT` requests are not allowed. cmd = 'curl -m 2 -s' cmd += ' -X PUT' cmd += ' -H "X-metadata-token-ttl-seconds: 1"' cmd += ' http://{}/latest/api/token'.format(ipv4_address) _, stdout, stderr = ssh_connection.execute_command(cmd) expected = "Not allowed HTTP method." _assert_out(stdout, stderr, expected) # Fetch metadata using V1 requests and ensure IPv4 configuration # is persistent between snapshots. cmd = 'curl -s http://{}/latest/meta-data/ami-id/'.format(ipv4_address) _, stdout, stderr = ssh_connection.execute_command(cmd) _assert_out(stdout, stderr, 'ami-12345678')
def test_custom_ipv4(test_microvm_with_api, network_config, version): """ Test the API for MMDS custom ipv4 support. @type: functional """ test_microvm = test_microvm_with_api test_microvm.spawn() data_store = { 'latest': { 'meta-data': { 'ami-id': 'ami-12345678', 'reservation-id': 'r-fea54097', 'local-hostname': 'ip-10-251-50-12.ec2.internal', 'public-hostname': 'ec2-203-0-113-25.compute-1.amazonaws.com', 'network': { 'interfaces': { 'macs': { '02:29:96:8f:6a:2d': { 'device-number': '13345342', 'local-hostname': 'localhost', 'subnet-id': 'subnet-be9b61d' } } } } } } } _populate_data_store(test_microvm, data_store) # Attach network device. _tap = test_microvm.ssh_network_config(network_config, '1') # Invalid values IPv4 address. response = test_microvm.mmds.put_config(json={ 'ipv4_address': '', 'network_interfaces': ['1'] }) assert test_microvm.api_session.is_status_bad_request(response.status_code) response = test_microvm.mmds.put_config(json={ 'ipv4_address': '1.1.1.1', 'network_interfaces': ['1'] }) assert test_microvm.api_session.is_status_bad_request(response.status_code) ipv4_address = '169.254.169.250' # Configure MMDS with custom IPv4 address. _configure_mmds( test_microvm, iface_id='1', version=version, ipv4_address=ipv4_address ) test_microvm.basic_config(vcpu_count=1) test_microvm.start() ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config) cmd = 'ip route add {} dev eth0'.format(ipv4_address) _, stdout, stderr = ssh_connection.execute_command(cmd) _assert_out(stdout, stderr, '') if version == 'V2': # Generate token. token = generate_mmds_session_token( ssh_connection, ipv4_address=ipv4_address, token_ttl=60 ) pre = generate_mmds_v2_get_request( ipv4_address=ipv4_address, token=token ) else: pre = 'curl -s -H "Accept: application/json" ' \ 'http://{}/'.format(ipv4_address) cmd = pre + 'latest/meta-data/ami-id' _, stdout, _ = ssh_connection.execute_command(cmd) assert json.load(stdout) == 'ami-12345678' # The request is still valid if we append a # trailing slash to a leaf node. cmd = pre + 'latest/meta-data/ami-id/' _, stdout, _ = ssh_connection.execute_command(cmd) assert json.load(stdout) == 'ami-12345678' cmd = pre + 'latest/meta-data/network/interfaces/macs/' \ '02:29:96:8f:6a:2d/subnet-id' _, stdout, _ = ssh_connection.execute_command(cmd) assert json.load(stdout) == 'subnet-be9b61d' # Test reading a non-leaf node WITHOUT a trailing slash. cmd = pre + 'latest/meta-data' _, stdout, _ = ssh_connection.execute_command(cmd) assert json.load(stdout) == data_store['latest']['meta-data'] # Test reading a non-leaf node with a trailing slash. cmd = pre + 'latest/meta-data/' _, stdout, _ = ssh_connection.execute_command(cmd) assert json.load(stdout) == data_store['latest']['meta-data']
def test_guest_mmds_hang(test_microvm_with_api, network_config, version): """ Test the MMDS json endpoint when Content-Length larger than actual length. @type: functional """ test_microvm = test_microvm_with_api test_microvm.spawn() # Attach network device. _tap = test_microvm.ssh_network_config(network_config, '1') # Configure MMDS version. _configure_mmds(test_microvm, iface_id='1', version=version) data_store = { 'latest': { 'meta-data': { 'ami-id': 'ami-12345678' } } } _populate_data_store(test_microvm, data_store) test_microvm.basic_config(vcpu_count=1) test_microvm.start() ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config) cmd = 'ip route add {} dev eth0'.format(DEFAULT_IPV4) _, stdout, stderr = ssh_connection.execute_command(cmd) _assert_out(stdout, stderr, '') get_cmd = 'curl -m 2 -s' get_cmd += ' -X GET' get_cmd += ' -H "Content-Length: 100"' get_cmd += ' -H "Accept: application/json"' get_cmd += ' -d "some body"' get_cmd += ' http://{}/'.format(DEFAULT_IPV4) if version == 'V1': _, stdout, _ = ssh_connection.execute_command(get_cmd) assert 'Invalid request' in stdout.read() else: # Generate token. token = generate_mmds_session_token( ssh_connection, ipv4_address=DEFAULT_IPV4, token_ttl=60 ) get_cmd += ' -H "X-metadata-token: {}"'.format(token) _, stdout, _ = ssh_connection.execute_command(get_cmd) assert 'Invalid request' in stdout.read() # Do the same for a PUT request. cmd = 'curl -m 2 -s' cmd += ' -X PUT' cmd += ' -H "Content-Length: 100"' cmd += ' -H "X-metadata-token: {}"'.format(token) cmd += ' -H "Accept: application/json"' cmd += ' -d "some body"' cmd += ' http://{}/'.format(DEFAULT_IPV4) _, stdout, _ = ssh_connection.execute_command(cmd) assert 'Invalid request' in stdout.read()
def test_larger_than_mss_payloads( test_microvm_with_api, network_config, version): """ Test MMDS content for payloads larger than MSS. @type: functional """ test_microvm = test_microvm_with_api test_microvm.spawn() # Attach network device. _tap = test_microvm.ssh_network_config(network_config, '1') # Configure MMDS version. _configure_mmds(test_microvm, iface_id='1', version=version) # The MMDS is empty at this point. response = test_microvm.mmds.get() assert test_microvm.api_session.is_status_ok(response.status_code) assert response.json() == {} test_microvm.basic_config(vcpu_count=1) test_microvm.start() # Make sure MTU is 1500 bytes. ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config) cmd = 'ip link set dev eth0 mtu 1500' _, stdout, stderr = ssh_connection.execute_command(cmd) _assert_out(stdout, stderr, "") cmd = 'ip a s eth0 | grep -i mtu | tr -s " " | cut -d " " -f 4,5' _, stdout, stderr = ssh_connection.execute_command(cmd) _assert_out(stdout, stderr, "mtu 1500\n") # These values are usually used by booted up guest network interfaces. mtu = 1500 ipv4_packet_headers_len = 20 tcp_segment_headers_len = 20 mss = mtu - ipv4_packet_headers_len - tcp_segment_headers_len # Generate a random MMDS content, double of MSS. letters = string.ascii_lowercase larger_than_mss = ''.join(random.choice(letters) for i in range(2 * mss)) mss_equal = ''.join(random.choice(letters) for i in range(mss)) lower_than_mss = ''.join(random.choice(letters) for i in range(mss - 2)) data_store = { 'larger_than_mss': larger_than_mss, 'mss_equal': mss_equal, 'lower_than_mss': lower_than_mss } response = test_microvm.mmds.put(json=data_store) assert test_microvm.api_session.is_status_no_content(response.status_code) response = test_microvm.mmds.get() assert test_microvm.api_session.is_status_ok(response.status_code) assert response.json() == data_store cmd = 'ip route add {} dev eth0'.format(DEFAULT_IPV4) _, stdout, stderr = ssh_connection.execute_command(cmd) _assert_out(stdout, stderr, '') if version == 'V2': # Generate token. token = generate_mmds_session_token( ssh_connection, ipv4_address=DEFAULT_IPV4, token_ttl=60 ) pre = generate_mmds_v2_get_request( ipv4_address=DEFAULT_IPV4, token=token, app_json=False ) else: pre = 'curl -s http://{}/'.format(DEFAULT_IPV4) cmd = pre + 'larger_than_mss' _, stdout, stderr = ssh_connection.execute_command(cmd) _assert_out(stdout, stderr, larger_than_mss) cmd = pre + 'mss_equal' _, stdout, stderr = ssh_connection.execute_command(cmd) _assert_out(stdout, stderr, mss_equal) cmd = pre + 'lower_than_mss' _, stdout, stderr = ssh_connection.execute_command(cmd) _assert_out(stdout, stderr, lower_than_mss)
def test_mmds_response(test_microvm_with_api, network_config, version): """ Test MMDS responses to various datastore requests. @type: functional """ test_microvm = test_microvm_with_api test_microvm.spawn() data_store = { 'latest': { 'meta-data': { 'ami-id': 'ami-12345678', 'reservation-id': 'r-fea54097', 'local-hostname': 'ip-10-251-50-12.ec2.internal', 'public-hostname': 'ec2-203-0-113-25.compute-1.amazonaws.com', 'dummy_obj': { 'res_key': 'res_value', }, 'dummy_array': [ 'arr_val1', 'arr_val2' ] }, "Limits": { "CPU": 512, "Memory": 512 }, "Usage": { "CPU": 12.12 } } } # Attach network device. _tap = test_microvm.ssh_network_config(network_config, '1') # Configure MMDS version. _configure_mmds(test_microvm, iface_id='1', version=version) # Populate data store with contents. _populate_data_store(test_microvm, data_store) test_microvm.basic_config(vcpu_count=1) test_microvm.start() ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config) cmd = 'ip route add {} dev eth0'.format(DEFAULT_IPV4) _, stdout, stderr = ssh_connection.execute_command(cmd) _assert_out(stdout, stderr, '') if version == 'V2': # Generate token. token = generate_mmds_session_token( ssh_connection, ipv4_address=DEFAULT_IPV4, token_ttl=60 ) pre = generate_mmds_v2_get_request( ipv4_address=DEFAULT_IPV4, token=token, app_json=False ) else: pre = 'curl -s http://{}/'.format(DEFAULT_IPV4) cmd = pre + 'latest/meta-data/' _, stdout, stderr = ssh_connection.execute_command(cmd) expected = "ami-id\n" \ "dummy_array\n" \ "dummy_obj/\n" \ "local-hostname\n" \ "public-hostname\n" \ "reservation-id" _assert_out(stdout, stderr, expected) cmd = pre + 'latest/meta-data/ami-id/' _, stdout, stderr = ssh_connection.execute_command(cmd) _assert_out(stdout, stderr, 'ami-12345678') cmd = pre + 'latest/meta-data/dummy_array/0' _, stdout, stderr = ssh_connection.execute_command(cmd) _assert_out(stdout, stderr, 'arr_val1') cmd = pre + 'latest/Usage/CPU' _, stdout, stderr = ssh_connection.execute_command(cmd) _assert_out(stdout, stderr, 'Cannot retrieve value. The value has an' ' unsupported type.') cmd = pre + 'latest/Limits/CPU' _, stdout, stderr = ssh_connection.execute_command(cmd) _assert_out(stdout, stderr, 'Cannot retrieve value. The value has an' ' unsupported type.')
def test_json_response(test_microvm_with_api, network_config, version): """ Test the MMDS json response. @type: functional """ test_microvm = test_microvm_with_api test_microvm.spawn() data_store = { 'latest': { 'meta-data': { 'ami-id': 'ami-12345678', 'reservation-id': 'r-fea54097', 'local-hostname': 'ip-10-251-50-12.ec2.internal', 'public-hostname': 'ec2-203-0-113-25.compute-1.amazonaws.com', 'dummy_res': ['res1', 'res2'] }, "Limits": { "CPU": 512, "Memory": 512 }, "Usage": { "CPU": 12.12 } } } # Attach network device. _tap = test_microvm.ssh_network_config(network_config, '1') # Configure MMDS version. _configure_mmds(test_microvm, iface_id='1', version=version) # Populate data store with contents. _populate_data_store(test_microvm, data_store) test_microvm.basic_config(vcpu_count=1) test_microvm.start() ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config) cmd = 'ip route add {} dev eth0'.format(DEFAULT_IPV4) _, stdout, stderr = ssh_connection.execute_command(cmd) _assert_out(stdout, stderr, '') if version == 'V2': # Generate token. token = generate_mmds_session_token( ssh_connection, ipv4_address=DEFAULT_IPV4, token_ttl=60 ) pre = generate_mmds_v2_get_request(DEFAULT_IPV4, token) else: pre = 'curl -s -H "Accept: application/json"' \ ' http://{}/'.format(DEFAULT_IPV4) cmd = pre + 'latest/meta-data/' _, stdout, _ = ssh_connection.execute_command(cmd) assert json.load(stdout) == data_store['latest']['meta-data'] cmd = pre + 'latest/meta-data/ami-id/' _, stdout, stderr = ssh_connection.execute_command(cmd) assert json.load(stdout) == 'ami-12345678' cmd = pre + 'latest/meta-data/dummy_res/0' _, stdout, stderr = ssh_connection.execute_command(cmd) assert json.load(stdout) == 'res1' cmd = pre + 'latest/Usage/CPU' _, stdout, stderr = ssh_connection.execute_command(cmd) assert json.load(stdout) == 12.12 cmd = pre + 'latest/Limits/CPU' _, stdout, stderr = ssh_connection.execute_command(cmd) assert json.load(stdout) == 512
def _validate_mmds_snapshot( vm_instance, vm_builder, version, target_fc_version=None, fc_path=None, jailer_path=None ): """Test MMDS behaviour across snap-restore.""" basevm = vm_instance.vm root_disk = vm_instance.disks[0] disks = [root_disk.local_path()] ssh_key = vm_instance.ssh_key ipv4_address = '169.254.169.250' # Configure MMDS version with custom IPv4 address. configure_mmds( basevm, version=version, iface_ids=[DEFAULT_DEV_NAME], ipv4_address=ipv4_address, fc_version=target_fc_version ) # Check if the FC version supports the latest format for mmds-config. # If target_fc_version is None, we assume the current version is used. if target_fc_version is None or \ (target_fc_version is not None and compare_versions(target_fc_version, "1.0.0") >= 0): expected_mmds_config = { "version": version, "ipv4_address": ipv4_address, "network_interfaces": [DEFAULT_DEV_NAME] } response = basevm.full_cfg.get() assert basevm.api_session.is_status_ok(response.status_code) assert response.json()["mmds-config"] == expected_mmds_config data_store = { 'latest': { 'meta-data': { 'ami-id': 'ami-12345678' } } } _populate_data_store(basevm, data_store) basevm.start() snapshot_builder = SnapshotBuilder(basevm) ssh_connection = net_tools.SSHConnection(basevm.ssh_config) _run_guest_cmd(ssh_connection, f'ip route add {ipv4_address} dev eth0', '') # Generate token if needed. token = None if version == "V2": token = generate_mmds_session_token( ssh_connection, ipv4_address, token_ttl=60 ) # Fetch metadata. cmd = generate_mmds_get_request( ipv4_address, token=token, ) _run_guest_cmd(ssh_connection, cmd, data_store, use_json=True) # Create snapshot. snapshot = snapshot_builder.create(disks, ssh_key, SnapshotType.FULL, target_version=target_fc_version) # Resume microVM and ensure session token is still valid on the base. response = basevm.vm.patch(state='Resumed') assert basevm.api_session.is_status_no_content(response.status_code) # Fetch metadata again using the same token. _run_guest_cmd(ssh_connection, cmd, data_store, use_json=True) # Kill base microVM. basevm.kill() # Load microVM clone from snapshot. microvm, _ = vm_builder.build_from_snapshot(snapshot, resume=True, fc_binary=fc_path, jailer_binary=jailer_path) ssh_connection = net_tools.SSHConnection(microvm.ssh_config) # Check the reported mmds config. In versions up to (including) v1.0.0 this # was not populated after restore. if target_fc_version is not None and \ compare_versions("1.0.0", target_fc_version) < 0: response = microvm.full_cfg.get() assert microvm.api_session.is_status_ok(response.status_code) assert response.json()["mmds-config"] == expected_mmds_config if version == 'V1': # Verify that V2 requests don't work assert generate_mmds_session_token( ssh_connection, ipv4_address, token_ttl=60 ) == "Not allowed HTTP method." token = None else: # Attempting to reuse the token across a restore must fail. cmd = generate_mmds_get_request(ipv4_address, token=token) _run_guest_cmd(ssh_connection, cmd, 'MMDS token not valid.') # Generate token. token = generate_mmds_session_token( ssh_connection, ipv4_address, token_ttl=60 ) # Data store is empty after a restore. cmd = generate_mmds_get_request(ipv4_address, token=token) _run_guest_cmd(ssh_connection, cmd, 'null') # Now populate the store. _populate_data_store(microvm, data_store) # Fetch metadata. _run_guest_cmd(ssh_connection, cmd, data_store, use_json=True)
def test_json_response(test_microvm_with_api, network_config, version): """ Test the MMDS json response. @type: functional """ test_microvm = test_microvm_with_api test_microvm.spawn() data_store = { 'latest': { 'meta-data': { 'ami-id': 'ami-12345678', 'reservation-id': 'r-fea54097', 'local-hostname': 'ip-10-251-50-12.ec2.internal', 'public-hostname': 'ec2-203-0-113-25.compute-1.amazonaws.com', 'dummy_res': ['res1', 'res2'] }, "Limits": { "CPU": 512, "Memory": 512 }, "Usage": { "CPU": 12.12 } } } # Attach network device. _tap = test_microvm.ssh_network_config(network_config, '1') # Configure MMDS version. configure_mmds(test_microvm, iface_ids=['1'], version=version) # Populate data store with contents. _populate_data_store(test_microvm, data_store) test_microvm.basic_config(vcpu_count=1) test_microvm.start() ssh_connection = net_tools.SSHConnection(test_microvm.ssh_config) cmd = 'ip route add {} dev eth0'.format(DEFAULT_IPV4) _run_guest_cmd(ssh_connection, cmd, '') token = None if version == 'V2': # Generate token. token = generate_mmds_session_token( ssh_connection, DEFAULT_IPV4, token_ttl=60 ) pre = generate_mmds_get_request(DEFAULT_IPV4, token) cmd = pre + 'latest/meta-data/' _run_guest_cmd(ssh_connection, cmd, data_store['latest']['meta-data'], use_json=True) cmd = pre + 'latest/meta-data/ami-id/' _run_guest_cmd(ssh_connection, cmd, 'ami-12345678', use_json=True) cmd = pre + 'latest/meta-data/dummy_res/0' _run_guest_cmd(ssh_connection, cmd, 'res1', use_json=True) cmd = pre + 'latest/Usage/CPU' _run_guest_cmd(ssh_connection, cmd, 12.12, use_json=True) cmd = pre + 'latest/Limits/CPU' _run_guest_cmd(ssh_connection, cmd, 512, use_json=True)