Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adds http-checksum trait support #741

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 225 additions & 0 deletions docs/source/1.0/spec/core/behavior-traits.rst
Original file line number Diff line number Diff line change
Expand Up @@ -655,3 +655,228 @@ See
input: PutSomethingInput,
output: PutSomethingOutput
}


.. _httpChecksum-trait:

----------------------
``httpChecksum`` trait
----------------------

Summary
Indicates that an operation's HTTP request or response supports checksum
validation. At least one of request or response checksum properties must
be specified within the trait.

Trait selector
``operation``
Value type
``structure``

The ``httpChecksum`` trait is a structure that contains the following members:

.. list-table::
:header-rows: 1
:widths: 10 10 80

* - Property
- Type
- Description
* - request
- :ref:`HttpChecksumProperties structure <checksum-properties>`
- The ``request`` property defines checksum validation behavior for
HTTP requests.

* - response
- :ref:`HttpChecksumProperties structure <checksum-properties>`
- The ``response`` property defines checksum validation behavior for
HTTP responses.

.. tabs::

.. code-tab:: smithy

@httpChecksum(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we consider making these a little more explicit, customizable, and future proof by using a map similar to:

@httpChecksum(
    request: {
        sha256: {
            header: "x-checksum-sha256"
        },
        crc32: {
            header: "x-checksum-crc32"
        }
    }
)

Here, request and response are maps where each key is the algorithm, and each value is a union of possible locations (i.e., it can be set to header or trailer, and we could expand to query in the future for requests if needed). The value of the union is the binding location name (i.e., header name or trailing header name).

This provides more flexibility than the current proposal because each algorithm can use whatever header or query string parameter is necessary. The header/query binding is explicit and does not need prefixes. The names can now support anything, including Content-MD5 if we ever wanted to support it (we probably don't but just an example).

Implementations don't need to perform any prefix logic to determine the header name either.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea, have few concerns. Let's sync offline on this.

request: {
locations: ["header"],
prefix: "x-checksum-",
algorithms: ["sha256", "crc32"]
},
response: {
prefix: "x-checksum-",
algorithms: ["sha256", "crc32"]
}
)
operation PutSomething {
input: PutSomethingInput,
output: PutSomethingOutput
}

.. code-tab:: json

{
"smithy": "1.0",
"shapes": {
"smithy.example#Example": {
"type": "service",
"version": "2019-06-27",
},
"smithy.example#PutSomething": {
"type": "operation",
"input": {
"target": "smithy.example#PutSomethingInput"
},
"output": {
"target": "smithy.example#PutSomethingOutput"
},
"traits": {
"smithy.api#httpChecksum": {
"request": {
"locations": ["header"],
"prefix": "x-checksum-",
"algorithms": ["sha256", "crc32"]
},
"response": {
"locations": ["header"],
"prefix": "x-checksum-",
"algorithms": ["sha256", "crc32"]
}
}
}
}
}
}


.. _checksum-properties:

HttpChecksumProperties structure
================================

``HttpChecksumProperties`` defines checksum validation behavior using the
following members:

.. list-table::
:header-rows: 1
:widths: 10 10 80

* - Property
- Type
- Description
* - prefix
- ``string``
- **Required**. The prefix string is used to construct a header or
trailer name for a checksum type. To construct a header or trailer
name for usage with checksum, follow the pattern:
``prefix + algorithm-name``.

For example, if prefix is ``x-checksum-``
and ``sha256`` algorithm is used for computing checksum, resolved header
or trailer name will be ``x-checksum-sha256``.

Recommended ABNF for prefix is as follows:

.. code-block:: abnf

prefix = lower-alpha *(["-"](lower-alpha / DIGIT)) "-"
lower-alpha = %x61-7A

That is, it should start with a lowercase letter followed by any
number of lowercase letters, digits, or non-sequential hyphens,
and it should end in a hyphen.

A member with the :ref:`httpHeader-trait` or :ref:`httpPrefixHeaders-trait`
MAY conflict with a resolved ``httpChecksum`` header name, allowing a
checksum to be supplied directly. See :ref:`here for more behavior
details <httpChecksum_trait_header_conflict_behavior>`.

* - locations
- ``[string]``
- A priority-ordered list representing supported locations where checksum
can be supplied. Valid values are:

* ``header`` - Indicates the checksum should be placed in an HTTP header.
* ``trailer`` - Indicates the checksum should be placed in the `chunked trailer part`_
of the body.

The default value is a list only containing ``header``.

* - algorithms
- ``[string]``
- **Required**. List of string representing checksum algorithms
supported for the HTTP request or response. A valid algorithm
should follow ABNF:

.. code-block:: abnf

algorithms = 1*(lower-alpha / DIGIT)
lower-alpha = %x61-7A

That is, it should be comprised only of digits and lowercase letters.

.. _chunked trailer part: https://tools.ietf.org/html/rfc7230#section-4.1.2


.. _httpChecksum_trait_behavior:

HttpChecksum Behavior
=====================

.. rubric:: Client Behavior

If the ``httpChecksum`` trait has a modeled ``request`` section, for HTTP request,
the client MUST compute the request payload checksum, using an algorithm
defined in the algorithms property. This computed checksum MUST be supplied
at one of the locations defined in the location property of the trait. It SHOULD be
supplied at the first supported location defined in the locations property of the trait.
If the resolved location is ``header``, the client MUST put the checksum into the
HTTP request headers. If the resolved location is ``trailer``, the client MUST put
the checksum into the `chunked trailer part`_ of the body. The header or trailer
field name MUST be constructed using the prefix property as described in the
:ref:`HTTP checksum properties<checksum-properties>` section.

If the ``httpChecksum`` trait has a modeled ``response`` section, for HTTP
response, the client MUST look for a checksum at supported locations as per
defined properties. If a checksum is found, the client MUST validate the received
checksum value by computing the corresponding checksum of the received payload.

skotambkar marked this conversation as resolved.
Show resolved Hide resolved
.. _httpChecksum_trait_header_conflict_behavior:

An operation's ``input``, ``output``, and ``error`` structures MAY contain a
member bound to an HTTP header matching the constructed header or trailer name
for ``httpChecksum`` trait. For an HTTP request, if customer provides value
for an input shape member bound to an HTTP header matching the constructed
header or trailer name, the client MUST use the customer provided value as is,
and skip computing request payload checksum.

.. _chunked trailer part: https://tools.ietf.org/html/rfc7230#section-4.1.2


.. rubric:: Service Behavior

If the ``httpChecksum`` trait has a modeled ``request`` section, for HTTP request,
the service SHOULD validate the received checksum by computing corresponding
checksum of the request payload to ensure data integrity.

If the ``httpChecksum`` trait models a ``response`` section, for HTTP response,
the service SHOULD send at least one supported payload checksum at the first
supported location in the locations property. The header or trailer name used,
MUST be constructed using modeled prefix property as suggested in the
:ref:`HTTP checksum properties<checksum-properties>` section.


.. _checksum_trait_with_checksum_required:

Behavior with HttpChecksumRequired
----------------------------------

When both the ``httpChecksum`` trait with a modeled ``request`` section, and
the :ref:`httpChecksumRequired <httpChecksumRequired-trait>` trait are applied
on an operation, the client MUST prefer using a checksum algorithm modeled for
request in the ``httpChecksum`` trait over an MD5 digest, and place checksum
as per the client behavior defined in the
:ref:`httpChecksum trait behavior<httpChecksum_trait_behavior>` section.

The service MUST accept a checksum value received as per the ``httpChecksum``
trait's request property, to satisfy checksum validation requirements for the
operation's HTTP Request.
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.smithy.aws.traits;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import software.amazon.smithy.aws.traits.auth.SigV4Trait;
import software.amazon.smithy.aws.traits.protocols.AwsProtocolTrait;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.ServiceIndex;
import software.amazon.smithy.model.knowledge.TopDownIndex;
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.traits.HttpChecksumProperties.Location;
import software.amazon.smithy.model.traits.HttpChecksumTrait;
import software.amazon.smithy.model.traits.OptionalAuthTrait;
import software.amazon.smithy.model.traits.Trait;
import software.amazon.smithy.model.validation.AbstractValidator;
import software.amazon.smithy.model.validation.ValidationEvent;
import software.amazon.smithy.utils.SmithyInternalApi;

/**
* Validates checksum location modeling specific to AWS usage. For response property within httpChecksum trait,
* only "header" is a valid checksum location. If service, operation uses sigv4 authentication scheme, the
* request property within httpChecksum trait must include "header" as supported checksum location.
*/
@SmithyInternalApi
public class HttpChecksumTraitValidator extends AbstractValidator {

@Override
public List<ValidationEvent> validate(Model model) {
List<ValidationEvent> events = new ArrayList<>();
ServiceIndex serviceIndex = ServiceIndex.of(model);
TopDownIndex topDownIndex = TopDownIndex.of(model);

List<ServiceShape> services = model.shapes(ServiceShape.class).collect(Collectors.toList());
for (ServiceShape service : services) {
if (!isTargetProtocol(service)) {
continue;
}

for (OperationShape operation : topDownIndex.getContainedOperations(service)) {
if (operation.hasTrait(HttpChecksumTrait.class)) {
events.addAll(validateSupportedLocations(serviceIndex, service, operation));
}
}
}

return events;
}

/**
* Validates supported locations within httpChecksum trait. For response property, only "header"
* is a valid checksum location. For service, operation using sigv4, the request property must include
* "header" as supported checksum location.
*
* @param serviceIndex index resolving auth schemes
* @param service service shape for the API
* @param operation operation shape
* @return List of validation events that occurred when validating the model.
*/
protected List<ValidationEvent> validateSupportedLocations(
ServiceIndex serviceIndex,
ServiceShape service,
OperationShape operation
) {
List<ValidationEvent> events = new ArrayList<>();
HttpChecksumTrait trait = operation.expectTrait(HttpChecksumTrait.class);

// validate response property only supports "header" as location
trait.getResponseProperty().ifPresent(property -> {
Set<Location> locations = property.getLocations();
if (locations.size() > 1 || !locations.contains(Location.HEADER)) {
events.add(error(operation, trait,
String.format("For aws protocols, the `response` property of the `httpChecksum` trait "
+ "only supports `header` as `location`, found \"%s\".", locations)));
}
});

// if SigV4 auth scheme is used, validate request property locations contain "header" as supported location.
if (hasSigV4AuthScheme(serviceIndex, service, operation)) {
trait.getRequestProperty().ifPresent(property -> {
if (!property.getLocations().contains(Location.HEADER)) {
events.add(error(operation, trait,
"For operation using sigv4 auth scheme, the `request` property of the "
+ "`httpChecksum` trait must support `header` checksum location."));
}
});
}

return events;
}

/**
* isTargetProtocol returns true if service uses a target protocol. By default,
* target protocol resolves to aws protocol.
*
* @param service is the service shape for which target protocol usage is checked.
* @return boolean indicating target protocol is used by the service.
*/
protected boolean isTargetProtocol(ServiceShape service) {
// By default, target protocol is AWS protocol.
return service.hasTrait(AwsProtocolTrait.class);
}

/**
* Returns true if the SigV4Trait is a auth scheme for the service and operation.
*
* @param serviceIndex index resolving auth schemes
* @param service service shape for the API
* @param operation operation shape
* @return if SigV4 is an auth scheme for the operation and service.
*/
private boolean hasSigV4AuthScheme(ServiceIndex serviceIndex, ServiceShape service, OperationShape operation) {
Map<ShapeId, Trait> auth = serviceIndex.getEffectiveAuthSchemes(service.getId(), operation.getId());
return auth.containsKey(SigV4Trait.ID) && !operation.hasTrait(OptionalAuthTrait.class);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
software.amazon.smithy.aws.traits.ArnTemplateValidator
software.amazon.smithy.aws.traits.HttpChecksumTraitValidator
software.amazon.smithy.aws.traits.SdkServiceIdValidator
software.amazon.smithy.aws.traits.clientendpointdiscovery.ClientEndpointDiscoveryValidator
software.amazon.smithy.aws.traits.protocols.ProtocolHttpValidator
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[WARNING] ns.foo#ValidOperation: This shape applies a trait that is unstable: smithy.api#httpChecksum | UnstableTrait
[WARNING] ns.foo#InvalidOperation: This shape applies a trait that is unstable: smithy.api#httpChecksum | UnstableTrait
[WARNING] ns.foo#InvalidOperation2: This shape applies a trait that is unstable: smithy.api#httpChecksum | UnstableTrait
[ERROR] ns.foo#InvalidOperation: For aws protocols, the `response` property of the `httpChecksum` trait only supports `header` as `location`, found "[trailer, header]". | HttpChecksumTrait
[ERROR] ns.foo#InvalidOperation: For operation using sigv4 auth scheme, the `request` property of the `httpChecksum` trait must support `header` checksum location. | HttpChecksumTrait
[ERROR] ns.foo#InvalidOperation2: For aws protocols, the `response` property of the `httpChecksum` trait only supports `header` as `location`, found "[trailer]". | HttpChecksumTrait
Loading