NovaApiValidationFramework
Nova API Validation Framework
Contents
Discussion
https://etherpad.openstack.org/NovaApiValidationFramework
Introduction
Nova is a RESTful API based HTTP service.
These APIs allow for cloud managed server operations.
For example, we can create a virtual machine instance by sending the following POST request to Nova's URI: /servers.
{ "server" : { "name" : "new-server-test", "imageRef" : "http://servers.api.openstack.org/1234/images/52415800-8b69-11e0-9b19-734f6f006e54", "flavorRef" : 1 } }
Nova contains many RESTful APIs to facilitate its many features.
Moreover, each RESTful API can be controlled flexibly through multiple parameters.
For example, the above "create a virtual machine instance" API has 19 parameters including "name", "imageRef", etc.
The HTTP service need to validate practically every API parameter in terms of acceptable type and minimum/maximum length and range.
Unnecessary workload can be avoided by validating API parameters before executing the API operation.
Moreover, API validation is an important feature from the viewpoint of HTTP service security.
As Nova is an HTTP service, it should be able to validate API parameters as follows:
- Validate every API parameter.
- Return an error response before API operation, if API parameter is invalid.
- Unify the error message format of the response, if the cause is the same.
(ex) ".. is too short.", ".. is too long.", ".. is not integer."
What is necessary for Nova-v3-API
This framework target is Nova-v3-API, because it is new and the API compatibility issues would not happen.
In Havana release, Nova-v3-API is experimental and the API will become generic in Icehouse.
In Icehouse development cycle, the web framework of Nova-v3-API will move to Pecan/WSME.
WSME has original API validation mechanism already, and we'd better to find good API validation feature based on current Nova-v3-API.
Investigation of Nova-v3-API parameters
This investigation is based on the following commit of Nova source code:
commit 5f0591252dc5d6e3f49682f85dcdbd99f692c07a Merge: 0211752 a19d72b Author: Jenkins <jenkins@review.openstack.org> Date: Mon Oct 7 03:14:43 2013 +0000 Merge "Fixes rescue doesn't honor enable password conf for v3"
The source files of Nova-v3-API are put under ./nova/api/openstack/compute/plugins/v3/, and there are 79 API methods.
In these APIs, 49 APIs use API parameters of a request body, and they have 146 API parameters.
The following table shows all API parameters and each API validating implementation.
Class | API method | API parameter | Required/Optional | Type | Length/Range | Data format |
---|---|---|---|---|---|---|
AdminActionsController | _create_backup() | name | Required | not validated | ||
backup_type | Required | not validated | ||||
rotation | Required | int | >= 0 | |||
metadata | Optional | dict | "{key: value} 1 < len(key) < 255" | |||
AdminActionsController | _migrate_live() | block_migration | Required | not validated | ||
disk_over_commit | Required | not validated | ||||
host | Required | not validated | ||||
AdminActionsController | _reset_state() | state | Required | str | "active"or "error" | |
AdminPasswordController | change_password() | admin_password | Required | str | ||
AgentController | create() | hypervisor | Required | not validated | ||
os | Required | not validated | ||||
architecture | Required | not validated | ||||
version | Required | not validated | ||||
url | Required | not validated | ||||
md5hash | Required | not validated | ||||
AgentController | update() | url | Required | not validated | ||
md5hash | Required | not validated | ||||
version | Required | not validated | ||||
AggregateController | create() | name | Required | str | 1 - 255 | |
availability_zone | Required | None or str | ||||
AggregateController | update() | name | Required if not specifying availability_zone | str | 1 - 255 | |
availability_zone | Required if not specifying name | None or str | ||||
AggregateController | _add_host() | host | Required | str | ||
AggregateController | _remove_host() | host | Required | str | ||
AggregateController | _set_metadata() | metadata | Required | dict | ||
InterfaceAttachmentController | create() | net_id | Optional, Should not specify both net_id and port_id | not validated | ||
port_id | Optional, Should not specify both net_id and port_id | not validated | ||||
ip_address | Optional, Should specify both req_ip and net_id | not validated | ||||
CellsController | create() | name | Required | str | not "!" and "." | |
type | Optional | str | "parent" or "child" | |||
CellsController | update() | name | Optional | str | not "!" and "." | |
type | Optional | str | "parent" or "child" | |||
CellsController | sync_instances() | project_id | Optional | not validated | ||
deleted | Optional | not validated | ||||
updated_since | Optional | not validated | ||||
ConsoleOutputController | get_console_output() | length | Optional | int | Cast by int(str(length)) | |
CoverageController | _start_coverage() | combine | Optional | bool | Cast by bool() | |
CoverageController | _report_coverage() | file | Optional | str | filename (if path != os.path.basename(path): Error) | |
xml | Optional | bool | ||||
html | Optional | bool | ||||
ServerDiskConfigController | create() | os-disk-config:disk_config | Optional | str | "MANUAL" or "AUTO" | |
ServerDiskConfigController | update() | os-disk-config:disk_config | Optional | str | "MANUAL" or "AUTO" | |
ServerDiskConfigController | _action_rebuild() | os-disk-config:disk_config | Required | str | "MANUAL" or "AUTO" | |
ServerDiskConfigController | _action_resize() | os-disk-config:disk_config | Required | str | "MANUAL" or "AUTO" | |
EvacuateController | _evacuate() | host | Required | not validated | ||
on_shared_storage | Required | str | boolean | |||
admin_password | Optional | not validated | ||||
ExtendedVolumesController | swap() | old_volume_id | Required | str | uuid | |
new_volume_id | Required | str | uuid | |||
ExtendedVolumesController | attach() | volume_id | Required | str | uuid | |
device | Required | not validated | ||||
ExtendedVolumesController | detach() | volume_id | Required | str | uuid | |
FlavorActionController | _add_tenant_access() | tenant_id | Required | not validated | ||
FlavorActionController | _remove_tenant_access() | tenant_id | Required | not validated | ||
FlavorManageController | _create() | name | Required | str | 1 - 255 | names can only contain [a-zA-Z0-9_.- ] |
id | Required | int or str | 1 - 255 | id can only contain [a-zA-Z0-9_.-] | ||
ram | Required | int | > 0 | |||
vcpus | Required | int | > 0 | |||
disk | Required | int | >= 0 | |||
ephemeral | Optional | int | >= 0 | |||
swap | Optional | int | >= 0 | |||
rxtx_factor | Optional | float | > 0 | |||
os-flavor-access:is_public | Optional | str | '1', 't', 'true', 'on', 'y', 'yes', '0', 'f', 'false', 'off', 'n', 'no' | |||
FlavorExtraSpecsController | create() | extra_specs | Optional | dict | ||
FlavorExtraSpecsController | update() | Optional | dict | |||
HostController | update() | status | Required if not specifying maintenance_mode | str | "enable" or "disable" | |
maintenance_mode | Required if not specifying status | str | "enable" or "disable" | |||
KeypairController | create() | name | Required | str? (not type checked) | 1 - 255 | |
public_key | Optional | not validated | ||||
MultinicController | _add_fixed_ip() | network_id | Required | not validated | ||
MultinicController | _remove_fixed_ip() | address | Required | not validated | ||
QuotaClassSetsController | update() | instances | Optional | int | ||
cores | Optional | int | ||||
ram | Optional | int | ||||
security_groups | Optional | int | ||||
floating_ips | Optional | int | ||||
fixed_ips | Optional | int | ||||
metadata_items | Optional | int | ||||
injected_files | Optional | int | ||||
injected_file_content_bytes | Optional | int | ||||
injected_file_path_bytes | Optional | int | ||||
security_group_rules | Optional | int | ||||
key_pairs | Optional | int | ||||
QuotaSetsController | update() | instances | Optional | int | ||
cores | Optional | int | ||||
ram | Optional | int | ||||
security_groups | Optional | int | ||||
floating_ips | Optional | int | ||||
fixed_ips | Optional | int | ||||
metadata_items | Optional | int | ||||
injected_files | Optional | int | ||||
injected_file_content_bytes | Optional | int | ||||
injected_file_path_bytes | Optional | int | ||||
security_group_rules | Optional | int | ||||
key_pairs | Optional | int | ||||
force | Optional | str | boolean | |||
RescueController | _rescue() | admin_pass | Optional | not validated | ||
SchedulerHintsController | create() | os-scheduler-hints:scheduler_hints | Optional | not validated | ||
ServerMetadataController | create() | metadata | Required | dict | ||
ServerMetadataController | update() | metadata | Required | dict | len(dict) == 1 | |
ServerMetadataController | update_all() | metadata | Required | dict | ||
ServersController | create() | admin_pass | Optional | str | validated with only Type | |
name | Required | str | 1 - 255 | |||
image_ref | Required if not specifying os-block-device-mapping:block_device_mapping | str | uuid or ".*/<uuid>" | |||
os-block-device-mapping:block_device_mapping | Required if not specifying image_ref | dict | ||||
source_type | Required | str | 'volume', 'image', 'snapshot', 'blank' | |||
uuid | Required if source_type is not 'blank' | not validated | ||||
os-availability-zone:availability_zone | Optional | not validated | ||||
os-config-drive:config_drive | Optional | not validated | ||||
auto_disk_config | Optional | not validated | ||||
key_name | Optional | not validated | ||||
os-multiple-create:min_count | Optional | int | >= 1 | |||
os-multiple-create:max_count | Optional | int | >= 1 | |||
os-multiple-create:return_reservation_id | Optional | bool | ||||
personality | Optional | dict | ||||
path | Required | not validated | ||||
contents | Required | str | base64 encoded | |||
scheduler_hints | Optional | not validated | ||||
os-security-groups:security_groups | Optional | list | ||||
name | Optional | str | ||||
os-user-data:user_data | Optional | not validated | ||||
networks | Optional | list of dict | ||||
fixed_ip | Optional, Should not specify both fixed_ip and port | validated by netaddr.valid_ipv4() | ||||
port | Optional, Should not specify both fixed_ip and port | uuid | ||||
access_ip_v4 | Optional | validated by netaddr.valid_ipv4() | ||||
access_ip_v6 | Optional | validated by netaddr.valid_ipv6() | ||||
flavor_ref | Required | not validated | ||||
metadata | Optional | {key: value}, 1 < len(key) < 255, len(value) < 255" | ||||
ServersController | update() | name | Optional | str | 1 - 255 | |
access_ip_v4 | Optional | validated by netaddr.valid_ipv4() | ||||
access_ip_v6 | Optional | validated by netaddr.valid_ipv6() | ||||
auto_disk_config | Optional | not validated | ||||
ServersController | _action_reboot() | type | Required | str | "HARD" or "SOFT", not allowed lower case. | |
ServersController | _action_resize() | flavor_ref | Required | not validated | validated with the existing flavors. | |
ServersController | _action_rebuild() | image_ref | Required | str | uuid or ".*/<uuid>" | |
admin_pass | Optional | not validated | ||||
name | Optional | str | 1 - 255 | |||
access_ip_v4 | Optional | validated by netaddr.valid_ipv4() | ||||
access_ip_v6 | Optional | validated by netaddr.valid_ipv6() | ||||
auto_disk_config | Optional | not validated | ||||
personality | Optional | not validated | ||||
path | Required | not validated | ||||
contents | Required | str | base64encoded | |||
ServersController | _action_create_image() | name | Required | not validated | ||
metadata | Optional | dict | ||||
ServiceController | update() | host | Required | not validated | ||
binary | Required | not validated | ||||
disabled_reason | Required if id is disable-log-reason | str | 1 - 255 |
Proposal
Validation point
Merits
Migration plan
TBD
Future
I wish the following implementations after applying this framework to every Nova APIs.
- Add the existence check of API parameter definition to Jenkins
OpenStack utilizes a good development process through Jenkins which is used to verify the coding style and to ensure functionality, when a code change is proposed. We feel it is good to add the existence check of API parameter definition to this process. By doing so, we will be able to keep the APIs secure.
- Create API specification document
We think it is possible to create the elements of an API specification document from the API schema definition.
OpenStack community has API specification documents of all OpenStack components[1] and ones for each component[2].
We can extract certain elements of all the API parameters from the API schema definition.
[1]: http://api.openstack.org/api-ref.html
[2]: Nova: http://docs.openstack.org/api/openstack-compute/2/content/
- Apply API validation framework to other components
By applying this framework to other components, we feel that the overall safety OpenStack APIs will be improved.
If we can successfully apply this framework to Nova, which contains many APIs with complex API parameters, we will be able to apply it to other components as well.
We hope to collaborate with many developers on this framework.
Implementation
Web Framework
Current Nova(also Havana) has been implemented with WSGI framework, which will be changed to the other framework "Pecan/WSME" in the future(Nova v4 API?).
We discussed API validation between WSME original validation and JSON Schema, as the result we have found the advantages of JSON Schema. [1]
In particularly, JSON Schema validation is useful for Nova v3 API, because the API has complex API parameters and JSON Schema can check complex parameters naturally.
Also we have found the way to overlap JSON Schema with Pecan/WSME, so we can use JSON Schema also when the web framework is changed to Pecan/WSME. [2]
Because of this situation, current patch of this API validation framework has been implemented with JSON Schema. [3]
API parameter type
The default types in the JSON Schema are basic types such as integer, float, string, and boolean.
Nova has many APIs, and we need additional types(uuid, url, etc.) for rigorous validation.
Current expected types are the following:
- integer
minimum, maximum - float
minimum, maximum - string
minLength, maxLength - uuid
- url
- ipv4 address
- ipv6 address
- base64 encoded data
API schema
For the creation of the prototype, each API schema was defined using JSON Schema.
Each API parameter was defined as needed using the additional types described in "3.1. API parameter type".
The following shows the API schema for "create a virtual machine instance".
Also, required API parameters were defined with "'required': True".
{ 'type' : 'object', 'properties' : { 'server': { 'properties' : { 'name': {'type': 'string', 'minLength': 1, 'maxLength': 255, 'required': True}, 'imageRef': {'type': 'intOrUuidOrUrl', 'required': True}, 'flavorRef': {'type': 'intOrUuidOrUrl', 'required': True}, 'min_count': {'type': 'integer', 'minimum': 1}, 'max_count': {'type': 'integer', 'minimum': 1}, 'accessIPv4': {'type': 'ipv4'}, 'accessIPv6': {'type': 'ipv6'}, [..] } } } }
Appendix
- [1]: http://lists.openstack.org/pipermail/openstack-dev/2013-June/009824.html
- [2]: http://lists.openstack.org/pipermail/openstack-dev/2013-June/010814.html
- [3] API validation framework for Nova[Reviewing]: https://review.openstack.org/#/c/25358/
- API validation framework for Oslo[Abondoned]: https://review.openstack.org/#/c/25884/
- Discussion: https://etherpad.openstack.org/NovaApiValidationFramework