Project: StackRox (Provisioning / Security & Compliance)
File: api_guidelines.md

An overview of the set of standardized guidelines and practices to ensure that the APIs across different projects and teams adhere to a common structure and design. This consistency simplifies the process of understanding and maintaining APIs. These guidelines help identify potential pitfalls and design choices that could lead to issues to help developers avoid common mistakes and build more robust APIs. When in doubt, It is encouraged to seek resolution in slack channel . - - While certain data may exist in the database for internal purposes or other functionalities, it is important to exercise caution when deciding what data to make accessible through the API. The rule of thumb to follow is that the APIs must not expose data structures defined for internal purposes. Instead, define a data structure for the sole purpose of API use, generally in the same package where the API is defined. This applies to all APIs including the inter-component APIs. All services defined in the package must not import from the following packages: - All new services and methods defined in the package must not import from the following packages: - All new messages defined in the package must not import from the following packages: - All services of the type `APIServiceWithCustomRoutes` must not import from the following packages: - Structs used by APIs, such as those defined in , , must not be written to database. Exposing internal data structures, especially the ones representing and affecting database schema directly at the API can pose security risks. By carefully selecting and limiting the data exposed via the API, we can prevent unauthorized access and potential data breaches which can have compliance ramifications. Furthermore, exposing unnecessary data in the API can lead to overfetching, where the client receives more data than required. This inefficiency increases network traffic, consumes extra bandwidth, and impacts API performance. By limiting API data to what is actually needed by clients, we optimize resource usage and improve response times. Consider the following database/internal structures as an example data structure used in Central. ``` Deployment { string id string name repeated KeyValue labels repeated KeyValue annotations repeated Container containers } Container { string name string image_name repeated Volume volumes } ``` Exposing the above data structure in the API will always lead to reading all the `Container` bytes from the database, even if the user does not need them. Instead, design the API such that users can request the information they need, as below: ``` // GetDeploymentRequest requests deployment information. Deployment metadata is requested by default. GetDeploymentRequest { Options { bool getcontainerspec `json: "getContainerSpec"` } } // Default response GetDeploymentResponse { Metadata { string name `json: "name"` string id `json: "id"` } } // If `"getContainerSpec": true` GetDeploymentResponse { Metadata { string name `json: "name"` string id `json: "name"` ... } ContainerSpec { string name `json: "name"` string image_name `json: "imageName"` ... } } ``` As APIs evolve over time, adding or removing data fields becomes

By limiting the exposure of internal database structures, we decouple the API from the database schema, allowing us to modify the internal structures without breaking existing client applications. Consider the following database/internal structures as an example data structure exposed by APIs. ``` Alert { string id repeated Violation violations string policy_name string policy_description string policy_enforcement } ``` Changing the above `Alert` data structure to the following structure may break the client application. ``` Alert { string id; repeated Violation violations Policy { string policy_name string policy_description string policy_enforcement } } ``` Keeping the API focused and concise simplifies maintenance efforts and reduces the chances of introducing bugs. A clean and manageable codebase improves the overall maintainability and stability of the API. The service name must be unique and use a noun that generally refers to a resource or product component and must end with Service e.g. `DeploymentService`, `ReportService`, `ComplianceService`. Intuitive and well-known short forms or abbreviations may be used in some cases (and could even be preferable) for succinctness e.g. `ReportConfigService`, `RbacService`. All gRPC methods grouped into a single service must generally pertain to the primary resource of the service. Methods should be named such that they provide insights into the functionality. Let us look at a few examples.`StartComplianceScan`, `RunComplianceScan`, and `GetComplianceScan` are not the same. `StartComplianceScan` should return without waiting for the compliance scan to complete. `RunComplianceScan` is ambiguous because it is unclear if the call waits for the scan to complete. The ambiguity can be removed by adding a field to the request that helps clarify the expectation e.g. `bool waitforscan_completion` if set to `true` informs the method to wait for the compliance scan to complete. However, for long-running processes, it is recommended to create a job that finishes the process asynchronously and return the job ID to the users which can be tracked via dedicated job tracking method. `GetComplianceScan` should not run a compliance scan but only fetches a stored one. Typically, the method name should follow the VerbNoun convention. | Verb | Noun | Method name | |--|-|| | List | Deployment | `ListDeployments` | | Get | Deployment | `GetDeployment` | | Update | Deployment | `UpdateDeployment` | | Delete | Deployment | `DeleteDeployment` | | Notify | Violation | `NotifyViolation` | | Run | ComplianceScan | `RunComplianceScan` | It is recommended that the verbs be imperative instead of inquisitive. Generally, the noun should be the resource type. In some cases, the noun portion could be composed of multiple nouns e.g. `GetVulnerabilityDeferralState`, `RunPolicyScan`. | Inquisitive | Imperative | ||| | `IsRunComplete` | `GetRunStatus` | | `IsAdmin` | `GetUserRole` | | `IsVulnerabilityDeferred` | `GetVulnerabilityDeferralState` | The noun portion of methods that act on a single resource must be singular e.g. `GetDeployment`. Those methods that act on the collection of resources must be plural e.g. `ListDeployments`, `DeleteDeployments`. Avoid prepositions (e.g. for, by) in method names as much as possible. Typically, this can be addressed by using a distinct verb, adding a field to the request message, or restructuring

<table> <tr> <td><b>Instead of</b></td><td><b>Use</b></td> </tr> <tr> <td> `GetBaselineGeneratedNetworkPolicyForDeployment` </td> <td> ``` GenerateDeploymentNetworkPolicy GenerateDeploymentNetworkPolicyRequest { bool from_baseline; bool fromnetworkflows; } ``` </td> </tr> <tr> <td> `RunPolicyScanForDeployment` </td> <td> `RunDeploymentPolicyScan` </td> </tr> <tr> <td> `DeleteDeploymentsByQuery` </td> <td> ``` DeleteDeployments DeleteDeploymentsRequest { string query; } ``` </td> </tr> <tr> <td> ``` GetBaselineGeneratedNetworkPolicyForDeployment ``` </td> <td> `GetDeploymentBaselineNetworkPolicy` or merely `GetBaselineNetworkPolicy` if the concept of baselines applies to deployments only. The following example demonstrates design if that concept of baselines could apply to multiple resource types. ``` GetBaselineNetworkPolicy GetBaselineNetworkPolicyRequest { oneof resource { string deployment_id; string cluster_id; } } ``` </td> </tr> </table> The request and response messages must be named after method names with suffix `Request` and `Response` unless the request/response type is an empty message. Generally, resource type as response message should be avoided e.g. use `GetDeploymentResponse` response instead of `Deployment`. This allows augmenting the response with supplemental information in the future. | Verb | Noun | Method name | Request message | Response message | ||-||-|--| | List | Deployment | `ListDeployments` | `ListDeploymentRequest` | `ListDeploymentResponse` | | Get | Deployment | `GetDeployment` | `GetDeploymentRequest` | `GetDeploymentResponse` | | Update | Deployment | `UpdateDeployment` | `UpdateDeploymentRequest` | `UpdateDeploymentResponse` | | Delete | Deployment | `DeleteDeployment` | `DeleteDeploymentRequest` | `google.protobuf.Empty` | | Get | ReportStatus | `GetReportStatus` | `GetReportStatusRequest` | `GetReportStatusResponse` | | Run | ComplianceScan | `RunComplianceScan` | `RunComplianceScanRequest` | `RunComplianceScanResponse` | Avoid prepositions as much as possible (e.g. for, with; `DeploymentWithProcessInfo`, `DeploymentWithImageScan`). In case such a need arises, add a field to the request message and response message. <table> <tr> <td><b>Instead of</b></td><td><b>Use</b></td> </tr> <tr> <td> `GetDeploymentWithImageScanRequest` </td> <td> ``` GetDeploymentRequest { bool withimagescan; } ``` </td> </tr> <tr> <td> `GetDeploymentWithImageScanResponse` </td> <td> ``` GetDeploymentImageScanResponse { Image image; } ``` or, ``` GetDeploymentResponse { Deployment deployment; Image image; } ``` </td> </tr> <tr> <td> `RunPolicyScanForDeploymentRequest` </td> <td> `RunDeploymentPolicyScanRequest` </td> </tr> </table> All fields in the message must be lowercase and underscore separated names. The JSON names for the fields are autogenerated by the proto compiler. By default, field names are converted to camel case notation. | Proto field name | JSON field name | |-|| | `networkdatastart_time` | `networkDataStartTime` | | `expiry_date` | `expiryDate` | Be explicit about conveying the specific purpose of fields e.g. instead of `expireson` use `expirydate`(/`timestamp`) as it informs users if the field returns the date portion of the timestamp or the full timestamp, and use `networkdatastarttime` instead of `networkdata_since` for a similar reason. The fields should convey their purpose without requiring users to read the documentation. This section goes over key URL guidelines that could help avoid common mistakes when building APIs. All APIs, except custom HTTP routes, must be prefixed with API version. Custom routes should not be version prefixed due to current design limitation of we handle gRPC vs HTTP-only endpoints (which may be mitigated in the future). The version is typically followed by the plural form of resource noun in the service name e.g. `/v1/deployments`,

The resource noun may be singular if it refers to non-acting resources encapsulating acting resources e.g. `/v1/compliance`, `/v1/networkgraph`, `/v1/debug`, `/v1/auth`. Path parameters are variable components of a URL path. They are typically used to point to a specific resource. A URL can have several path parameters, each denoted with curly braces `{ }`. If the request URL contains one or more path parameters, the path parameter should clearly indicate the resource type otherwise by default they are associated with resource type in the preceding URL component. <table> <tr> <td> `/v1/compliance/profiles/{id}` </td> <td> Acts on a specific compliance profile </td> </tr> <tr> <td> `/v1/networkpolicies/{id}` </td> <td> Acts on a specific network policy. </td> </tr> <tr> <td> `/v1/networkpolicies/{deployment_id}` </td> <td> Not recommended. Consider query string parameter pattern instead. </td> </tr> </table> Keep it simple and descriptive; avoid long-worded URL components. If one object can contain another object, design the endpoint to reflect that regardless of whether the data is structured like this in the database. If the URL describes the action, nest the action within the resource. Avoid stop words (the, and, or, of, a, an, to, for, etc.) in a URL to make it shorter and more readable. | Instead of | Use | | |--|--|| | `GET: /v1/depoymentsbynamespace` | `GET: /v1/namespaces/{namespace}/deployments` | Get all deployments in a specific namespace | | `GET: /v1/reportsmetadata/{id}` | `GET: /v1/reports/jobs/{id}/metadata` | Get metadata of report job | | `GET: /v1/reports/status/{id}` | `GET: /v1/reports/jobs/{id}/status` | Get status of report job | | `GET: /v1/complianceprofiles` | `GET: /v1/compliance/profiles` | Get all compliance profiles | | `POST: /v1/resetbaselineforcluster/{clusterid}` | `POST: /v1/baselines/cluster/{clusterid}/reset` | Reset baseline for a specific cluster | If splitting the words into multiple URL components is not intuitive, it is recommended to use a hyphen(-). | Instead of | Use | |--|--| | `/v1/kernelsupport` | `/v1/kernel-support` | | `/v1/kernel_support` | `/v1/kernel-support` | | `/v1/securitypolicy` | `/v1/security-policy` | If the API acts on specific attribute of the resource, using URL query parameters is more intuitive over long-worded or hierarchical identifiers. | Instead of | Use | |--|--| | `/v1/deferredcves` | `/v1/cves?deferred=true` | | `/v1/inactiveviolations` | `/v1/violations?inactive=true` | | `/v1/fixablecves` | `/v1/cves?fixable=true` | | `/v1/runningreports` | `/v1/reports?status=running` | URLs should not be duplicated. A request URL and request method should uniquely identify an API. For example, `GET: /v1/deployments/{id}` conflicts with `GET: /v1/deployments/violations`. A `GET` API must not declare a body but instead specify the response criteria using path and query parameters, therefore, the gRPC request message fields should map to the URL path or query parameters. The parameters could be used for identifying, filtering, sorting, paginating, tracking the source, translation, etc. `POST` requests to create a resource must not accept the resource ID. IDs must be generated on the backend. Only `POST` requests to perform an action on a specific resource should accept a resource ID. A `GET` API must use an HTTP GET verb. For example, the following API configuration is not recommended and should be avoided: ``` rpc GetExistingProbes(GetExistingProbesRequest) returns (GetExistingProbesResponse) { option (google.api.http) = { post: "/v1/probeupload/getexisting" }; } ```