Esempio n. 1
0
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
Esempio n. 4
0
def test_mmds_v2_negative(test_microvm_with_api, network_config):
    """
    Test invalid MMDS GET/PUT requests when using V2.

    @type: negative
    """
    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, version='V2', iface_ids=['1'])

    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'
            }
        }
    }
    _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)

    _run_guest_cmd(ssh_connection, f'ip route add {DEFAULT_IPV4} dev eth0', '')

    # Check `GET` request fails when token is not provided.
    cmd = generate_mmds_get_request(DEFAULT_IPV4)
    expected = "No MMDS token provided. Use `X-metadata-token` header " \
               "to specify the session token."
    _run_guest_cmd(ssh_connection, cmd, expected)

    # Generic `GET` request.

    # Check `GET` request fails when token is not valid.
    _run_guest_cmd(ssh_connection, generate_mmds_get_request(
        DEFAULT_IPV4, token="foo"), "MMDS token not valid.")

    # Check `PUT` request fails when token TTL is not provided.
    cmd = f'curl -m 2 -s -X PUT http://{DEFAULT_IPV4}/latest/api/token'
    expected = "Token time to live value not found. Use " \
               "`X-metadata-token-ttl_seconds` header to specify " \
               "the token's lifetime."
    _run_guest_cmd(ssh_connection, cmd, expected)

    # Check `PUT` request fails when `X-Forwarded-For` header is provided.
    cmd = 'curl -m 2 -s'
    cmd += ' -X PUT'
    cmd += ' -H  "X-Forwarded-For: foo"'
    cmd += f' http://{DEFAULT_IPV4}'
    expected = "Invalid header. Reason: Unsupported header name. " \
               "Key: X-Forwarded-For"
    _run_guest_cmd(ssh_connection, cmd, expected)

    # Generic `PUT` request.
    put_cmd = 'curl -m 2 -s'
    put_cmd += ' -X PUT'
    put_cmd += ' -H  "X-metadata-token-ttl-seconds: {}"'
    put_cmd += f' {DEFAULT_IPV4}/latest/api/token'

    # Check `PUT` request fails when path is invalid.
    # Path is invalid because we remove the last character
    # at the end of the valid uri.
    _run_guest_cmd(
        ssh_connection, put_cmd[:-1].format(60),
        "Resource not found: /latest/api/toke."
    )

    # Check `PUT` request fails when token TTL is not valid.
    ttl_values = [MIN_TOKEN_TTL_SECONDS - 1, MAX_TOKEN_TTL_SECONDS + 1]
    for ttl in ttl_values:
        expected = "Invalid time to live value provided for token: {}. " \
                   "Please provide a value between {} and {}." \
            .format(ttl, MIN_TOKEN_TTL_SECONDS, MAX_TOKEN_TTL_SECONDS)
        _run_guest_cmd(ssh_connection, put_cmd.format(ttl), expected)

    # Valid `PUT` request to generate token.
    _, stdout, _ = ssh_connection.execute_command(put_cmd.format(1))
    token = stdout.read()
    assert len(token) > 0

    # Wait for token to expire.
    time.sleep(1)
    # Check `GET` request fails when expired token is provided.
    _run_guest_cmd(ssh_connection, generate_mmds_get_request(
        DEFAULT_IPV4, token=token), "MMDS token not valid.")
Esempio n. 5
0
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)
Esempio n. 6
0
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_ids=['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)

    _run_guest_cmd(ssh_connection, 'ip link set dev eth0 mtu 1500', '')

    cmd = 'ip a s eth0 | grep -i mtu | tr -s " " | cut -d " " -f 4,5'
    _run_guest_cmd(ssh_connection, cmd, '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

    _run_guest_cmd(ssh_connection, f'ip route add {DEFAULT_IPV4} dev eth0', '')

    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=token,
        app_json=False
    )

    cmd = pre + 'larger_than_mss'
    _run_guest_cmd(ssh_connection, cmd, larger_than_mss)

    cmd = pre + 'mss_equal'
    _run_guest_cmd(ssh_connection, cmd, mss_equal)

    cmd = pre + 'lower_than_mss'
    _run_guest_cmd(ssh_connection, cmd, lower_than_mss)
Esempio n. 7
0
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_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=token,
        app_json=False
    )

    cmd = pre + 'latest/meta-data/'
    expected = "ami-id\n" \
               "dummy_array\n" \
               "dummy_obj/\n" \
               "local-hostname\n" \
               "public-hostname\n" \
               "reservation-id"

    _run_guest_cmd(ssh_connection, cmd, expected)

    cmd = pre + 'latest/meta-data/ami-id/'
    _run_guest_cmd(ssh_connection, cmd, 'ami-12345678')

    cmd = pre + 'latest/meta-data/dummy_array/0'
    _run_guest_cmd(ssh_connection, cmd, 'arr_val1')

    cmd = pre + 'latest/Usage/CPU'
    _run_guest_cmd(ssh_connection, cmd, 'Cannot retrieve value. The value has'
                   ' an unsupported type.')

    cmd = pre + 'latest/Limits/CPU'
    _run_guest_cmd(ssh_connection, cmd, 'Cannot retrieve value. The value has'
                   ' an unsupported type.')
Esempio n. 8
0
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)
Esempio n. 9
0
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_ids=['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)

    _run_guest_cmd(ssh_connection, f'ip route add {ipv4_address} dev eth0', '')

    token = None
    if version == 'V2':
        # Generate token.
        token = generate_mmds_session_token(
            ssh_connection,
            ipv4_address,
            token_ttl=60
        )

    pre = generate_mmds_get_request(
        ipv4_address,
        token=token,
    )

    cmd = pre + 'latest/meta-data/ami-id'
    _run_guest_cmd(ssh_connection, cmd, 'ami-12345678', use_json=True)

    # The request is still valid if we append a
    # trailing slash to a leaf node.
    cmd = pre + 'latest/meta-data/ami-id/'
    _run_guest_cmd(ssh_connection, cmd, 'ami-12345678', use_json=True)

    cmd = pre + 'latest/meta-data/network/interfaces/macs/' \
                '02:29:96:8f:6a:2d/subnet-id'
    _run_guest_cmd(ssh_connection, cmd, 'subnet-be9b61d', use_json=True)

    # Test reading a non-leaf node WITHOUT a trailing slash.
    cmd = pre + 'latest/meta-data'
    _run_guest_cmd(ssh_connection, cmd,
                   data_store['latest']['meta-data'], use_json=True)

    # Test reading a non-leaf node with a trailing slash.
    cmd = pre + 'latest/meta-data/'
    _run_guest_cmd(ssh_connection, cmd,
                   data_store['latest']['meta-data'], use_json=True)