diff --git a/docs/source-2.0/aws/aws-auth.rst b/docs/source-2.0/aws/aws-auth.rst index cad4323005c..30a7f581ef1 100644 --- a/docs/source-2.0/aws/aws-auth.rst +++ b/docs/source-2.0/aws/aws-auth.rst @@ -33,7 +33,8 @@ Trait value - **Required**. The signature version 4 service signing name to use in the `credential scope`_ when signing requests. This value MUST NOT be empty. This value SHOULD match the ``arnNamespace`` property - of the :ref:`aws.api#service-trait`. + of the :ref:`aws.api#service-trait` if present and the ``name`` + property of the :ref:`aws.auth#sigv4a-trait` if present. If a request contains the ``Authorization`` header or a query string parameter with the name of ``X-Amz-Algorithm`` containing the value ``AWS4-HMAC-SHA256``, @@ -59,6 +60,65 @@ unauthenticated request. } +.. smithy-trait:: aws.auth#sigv4a +.. _aws.auth#sigv4a-trait: + +------------------------- +``aws.auth#sigv4a`` trait +------------------------- + +Trait summary + The ``aws.auth#sigv4a`` trait adds support for AWS Signature Version 4 + Asymmetric (SigV4A), an extension of `AWS signature version 4`_ (SigV4), to + a service. +Trait selector + ``service[trait|aws.auth#sigv4]`` +Trait value + An ``object`` that supports the following properties: + + .. list-table:: + :header-rows: 1 + :widths: 10 20 70 + + * - Property + - Type + - Description + * - name + - ``string`` + - **Required**. The signature version 4a service signing name to use + in the `credential scope`_ when signing requests. This value MUST + NOT be empty. This value SHOULD match the ``arnNamespace`` property + of the :ref:`aws.api#service-trait` if present and the ``name`` + property of the :ref:`aws.auth#sigv4-trait`. + +SigV4A is nearly identical to SigV4, but also uses public-private keys and +asymmetric cryptographic signatures for every request. Most notably, SigV4A +supports signatures for multi-region API requests. + +.. code-block:: smithy + + $version: "2" + + namespace aws.fooBaz + + use aws.api#service + use aws.auth#sigv4 + use aws.auth#sigv4a + use aws.protocols#restJson1 + + // This service is an AWS service that prioritizes SigV4A + // authentication before SigV4 authentication. + // Note that services that support SigV4A MUST support SigV4. + @service(sdkId: "Some Value") + @auth([sigv4a, sigv4]) + @sigv4(name: "foobaz") + @sigv4a(name: "foobaz") + @restJson1 + service FooBaz { + version: "2018-03-17" + } + + .. smithy-trait:: aws.auth#unsignedPayload .. _aws.auth#unsignedPayload-trait: diff --git a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/auth/SigV4ATrait.java b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/auth/SigV4ATrait.java new file mode 100644 index 00000000000..bdb702f83b5 --- /dev/null +++ b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/auth/SigV4ATrait.java @@ -0,0 +1,90 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.aws.traits.auth; + +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.AbstractTrait; +import software.amazon.smithy.model.traits.AbstractTraitBuilder; +import software.amazon.smithy.model.traits.Trait; +import software.amazon.smithy.utils.SmithyBuilder; +import software.amazon.smithy.utils.ToSmithyBuilder; + +/** + * Adds AWS Signature Version 4 Asymmetric authentication to a service or operation. + */ +public final class SigV4ATrait extends AbstractTrait implements ToSmithyBuilder { + public static final ShapeId ID = ShapeId.from("aws.auth#sigv4a"); + private static final String NAME = "name"; + + private final String name; + + private SigV4ATrait(Builder builder) { + super(ID, builder.getSourceLocation()); + this.name = SmithyBuilder.requiredState(NAME, builder.name); + } + + /** + * Gets the service signing name. + * + * @return the service signing name + */ + public String getName() { + return name; + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public Builder toBuilder() { + return builder() + .sourceLocation(getSourceLocation()) + .name(getName()); + } + + @Override + protected Node createNode() { + return Node.objectNodeBuilder() + .sourceLocation(getSourceLocation()) + .withMember(NAME, getName()) + .build(); + } + + public static final class Builder extends AbstractTraitBuilder { + private String name; + + private Builder() {} + + @Override + public SigV4ATrait build() { + return new SigV4ATrait(this); + } + + public Builder name(String name) { + this.name = name; + return this; + } + } + + public static final class Provider extends AbstractTrait.Provider { + public Provider() { + super(ID); + } + + @Override + public Trait createTrait(ShapeId target, Node value) { + ObjectNode objectNode = value.expectObjectNode(); + Builder builder = builder().sourceLocation(value); + builder.name(objectNode.expectStringMember(NAME).getValue()); + SigV4ATrait result = builder.build(); + result.setNodeCache(objectNode); + return result; + } + } +} diff --git a/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/auth/SigV4TraitsValidator.java b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/auth/SigV4TraitsValidator.java new file mode 100644 index 00000000000..ed64f87ec26 --- /dev/null +++ b/smithy-aws-traits/src/main/java/software/amazon/smithy/aws/traits/auth/SigV4TraitsValidator.java @@ -0,0 +1,95 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.aws.traits.auth; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import software.amazon.smithy.aws.traits.ServiceTrait; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.validation.AbstractValidator; +import software.amazon.smithy.model.validation.ValidationEvent; +import software.amazon.smithy.utils.SmithyInternalApi; + +/** + * Validates AWS Service, SigV4, and SigV4A traits. + */ +@SmithyInternalApi +public final class SigV4TraitsValidator extends AbstractValidator { + private static final ShapeId SERVICE_ARN_NAMESPACE = ServiceTrait.ID.withMember("arnNamespace"); + private static final ShapeId SIGV4_NAME = SigV4Trait.ID.withMember("name"); + private static final ShapeId SIGV4A_NAME = SigV4ATrait.ID.withMember("name"); + + @Override + public List validate(Model model) { + List events = new ArrayList<>(); + for (ServiceShape service : model.getServiceShapes()) { + events.addAll(validateService(model, service)); + } + return events; + } + + /** + * Validates Service and SigV4 traits. + * + * - service$arnNamespace, sigv4$name, and sigv4a$name SHOULD be equal. Otherwise, emits warnings. + */ + private List validateService(Model model, ServiceShape service) { + List events = new ArrayList<>(); + Optional serviceTraitOptional = service.getTrait(ServiceTrait.class); + Optional sigv4TraitOptional = service.getTrait(SigV4Trait.class); + Optional sigv4aTraitOptional = service.getTrait(SigV4ATrait.class); + if (serviceTraitOptional.isPresent()) { + String serviceArnNamespace = serviceTraitOptional.get().getArnNamespace(); + // Check service$arnNamespace with sigv4$name + if (sigv4TraitOptional.isPresent()) { + String sigv4Name = sigv4TraitOptional.get().getName(); + if (!serviceArnNamespace.equals(sigv4Name)) { + events.add(createValuesShouldMatchWarning( + service, + SERVICE_ARN_NAMESPACE, serviceArnNamespace, + SIGV4_NAME, sigv4Name)); + } + } + // Check service$arnNamespace with sigv4a$name + if (sigv4aTraitOptional.isPresent()) { + String sigv4aName = sigv4aTraitOptional.get().getName(); + if (!serviceArnNamespace.equals(sigv4aName)) { + events.add(createValuesShouldMatchWarning( + service, + SERVICE_ARN_NAMESPACE, serviceArnNamespace, + SIGV4A_NAME, sigv4aName)); + } + } + } + // Check sigv4$name with sigv4a$name + if (sigv4TraitOptional.isPresent() && sigv4aTraitOptional.isPresent()) { + String sigv4Name = sigv4TraitOptional.get().getName(); + String sigv4aName = sigv4aTraitOptional.get().getName(); + if (!sigv4Name.equals(sigv4aName)) { + events.add(createValuesShouldMatchWarning( + service, + SIGV4_NAME, sigv4Name, + SIGV4A_NAME, sigv4aName)); + } + } + return events; + } + + private ValidationEvent createValuesShouldMatchWarning( + ServiceShape service, + ShapeId member1, + String value1, + ShapeId member2, + String value2 + ) { + return warning(service, String.format( + "Value for `%s` \"%s\" and value for `%s` \"%s\" SHOULD match.", + member1.toString(), value1, member2.toString(), value2)); + } +} diff --git a/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService b/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService index 77e89519faa..6f5b0ca5ca3 100644 --- a/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService +++ b/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.traits.TraitService @@ -21,5 +21,6 @@ software.amazon.smithy.aws.traits.protocols.RestXmlTrait$Provider software.amazon.smithy.aws.traits.protocols.AwsJson1_0Trait$Provider software.amazon.smithy.aws.traits.protocols.AwsJson1_1Trait$Provider software.amazon.smithy.aws.traits.auth.SigV4Trait$Provider +software.amazon.smithy.aws.traits.auth.SigV4ATrait$Provider software.amazon.smithy.aws.traits.tagging.TagEnabledTrait$Provider software.amazon.smithy.aws.traits.tagging.TaggableTrait$Provider diff --git a/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator b/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator index eba9e65bd3f..80a840407e2 100644 --- a/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator +++ b/smithy-aws-traits/src/main/resources/META-INF/services/software.amazon.smithy.model.validation.Validator @@ -11,3 +11,4 @@ software.amazon.smithy.aws.traits.tagging.TaggableResourceValidator software.amazon.smithy.aws.traits.tagging.TagResourcePropertyTypeValidator software.amazon.smithy.aws.traits.tagging.TagResourcePropertyNameValidator software.amazon.smithy.aws.traits.ErrorRenameValidator +software.amazon.smithy.aws.traits.auth.SigV4TraitsValidator diff --git a/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.auth.smithy b/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.auth.smithy index 7c67bb40491..155960783cd 100644 --- a/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.auth.smithy +++ b/smithy-aws-traits/src/main/resources/META-INF/smithy/aws.auth.smithy @@ -19,18 +19,40 @@ structure cognitoUserPools { /// security, most requests to AWS must be signed with an access key, which consists /// of an access key ID and secret access key. These two keys are commonly referred to /// as your security credentials. -@authDefinition(traits: [unsignedPayload]) -@externalDocumentation( - Reference: "https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html" +@authDefinition( + traits: [unsignedPayload] ) +@externalDocumentation(Reference: "https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html") @trait(selector: "service") structure sigv4 { /// The signature version 4 service signing name to use in the credential /// scope when signing requests. This value SHOULD match the `arnNamespace` - /// property of the `aws.api#service-trait`. - @externalDocumentation( - Reference: "https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html" - ) + /// property of the `aws.api#service` trait if present and the `name` + /// property of the `aws.api#sigv4a` trait if present. + @externalDocumentation(Reference: "https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html") + @length(min: 1) + @required + name: String +} + +/// Signature Version 4 Asymmetric (SigV4A), an extension of Signature Version 4 (SigV4), is the +/// process to add authentication information to AWS requests sent by HTTP. SigV4A is nearly +/// identical to SigV4, but also uses public-private keys and asymmetric cryptographic signatures +/// for every request. Most notably, SigV4A supports signatures for multi-region API requests. +@authDefinition( + traits: [unsignedPayload] +) +@externalDocumentation( + Reference: "https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html" + Examples: "https://github.com/aws-samples/sigv4a-signing-examples" +) +@trait(selector: "service[trait|aws.auth#sigv4]") +structure sigv4a { + /// The signature version 4a service signing name to use in the credential + /// scope when signing requests. This value SHOULD match the `arnNamespace` + /// property of the `aws.api#service` trait if present and the `name` + /// property of the `aws.api#sigv4` trait. + @externalDocumentation(Reference: "https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html") @length(min: 1) @required name: String diff --git a/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/auth/SigV4ATraitTest.java b/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/auth/SigV4ATraitTest.java new file mode 100644 index 00000000000..5a2b0abc422 --- /dev/null +++ b/smithy-aws-traits/src/test/java/software/amazon/smithy/aws/traits/auth/SigV4ATraitTest.java @@ -0,0 +1,43 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.aws.traits.auth; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Optional; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.node.StringNode; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.Trait; +import software.amazon.smithy.model.traits.TraitFactory; + +public class SigV4ATraitTest { + private static final String MOCK_SIGNING_NAME = "mocksigningname"; + private static final ShapeId MOCK_TARGET = ShapeId.from("ns.qux#foo"); + + @Test + public void loadsTrait() { + Node node = ObjectNode.builder() + .withMember("name", StringNode.from(MOCK_SIGNING_NAME)) + .build(); + TraitFactory provider = TraitFactory.createServiceFactory(); + Optional trait = provider.createTrait(SigV4ATrait.ID, MOCK_TARGET, node); + + assertTrue(trait.isPresent()); + assertThat(trait.get(), instanceOf(SigV4ATrait.class)); + SigV4ATrait sigv4aTrait = (SigV4ATrait) trait.get(); + assertFalse(sigv4aTrait.getName().isEmpty()); + assertThat(sigv4aTrait.getName(), equalTo(MOCK_SIGNING_NAME)); + assertThat(sigv4aTrait.toNode(), equalTo(node)); + assertThat(sigv4aTrait.toBuilder().build(), equalTo(sigv4aTrait)); + } +} diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-service-mismatch.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-service-mismatch.errors new file mode 100644 index 00000000000..4c070e372b9 --- /dev/null +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-service-mismatch.errors @@ -0,0 +1 @@ +[WARNING] smithy.example#InvalidService: Value for `aws.api#service$arnNamespace` "invalidservice" and value for `aws.auth#sigv4$name` "signingname" SHOULD match. | SigV4Traits diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-service-mismatch.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-service-mismatch.smithy new file mode 100644 index 00000000000..50b9e51b616 --- /dev/null +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-service-mismatch.smithy @@ -0,0 +1,12 @@ +$version: "2.0" + +namespace smithy.example + +use aws.api#service +use aws.auth#sigv4 + +@service(sdkId: "servicename") +@sigv4(name: "signingname") +service InvalidService { + version: "2020-07-02" +} diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-sigv4a-mismatch.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-sigv4a-mismatch.errors new file mode 100644 index 00000000000..0e4a9d570db --- /dev/null +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-sigv4a-mismatch.errors @@ -0,0 +1 @@ +[WARNING] smithy.example#InvalidService: Value for `aws.auth#sigv4$name` "sigv4signingname" and value for `aws.auth#sigv4a$name` "sigv4asigningname" SHOULD match. | SigV4Traits diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-sigv4a-mismatch.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-sigv4a-mismatch.smithy new file mode 100644 index 00000000000..803527de31c --- /dev/null +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4-sigv4a-mismatch.smithy @@ -0,0 +1,13 @@ +$version: "2.0" + +namespace smithy.example + +use aws.auth#sigv4 +use aws.auth#sigv4a + +@auth([sigv4a, sigv4]) +@sigv4(name: "sigv4signingname") +@sigv4a(name: "sigv4asigningname") +service InvalidService { + version: "2020-07-02" +} diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-empty-name.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-empty-name.errors new file mode 100644 index 00000000000..6965fa0318d --- /dev/null +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-empty-name.errors @@ -0,0 +1,2 @@ +[ERROR] smithy.example#InvalidService: Error validating trait `aws.auth#sigv4a`.name: String value provided for `aws.auth#sigv4a$name` must be >= 1 characters, but the provided value is only 0 characters. | TraitValue +[WARNING] smithy.example#InvalidService: Value for `aws.auth#sigv4$name` "signingname" and value for `aws.auth#sigv4a$name` "" SHOULD match. | SigV4Traits diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-empty-name.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-empty-name.smithy new file mode 100644 index 00000000000..ef73c04f024 --- /dev/null +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-empty-name.smithy @@ -0,0 +1,13 @@ +$version: "2.0" + +namespace smithy.example + +use aws.auth#sigv4 +use aws.auth#sigv4a + +@auth([sigv4a, sigv4]) +@sigv4(name: "signingname") +@sigv4a(name: "") +service InvalidService { + version: "2020-07-02" +} diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-no-sigv4.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-no-sigv4.errors new file mode 100644 index 00000000000..cef5bbdd1a1 --- /dev/null +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-no-sigv4.errors @@ -0,0 +1 @@ +[ERROR] smithy.example#InvalidService: Trait `aws.auth#sigv4a` cannot be applied to `smithy.example#InvalidService`. This trait may only be applied to shapes that match the following selector: service[trait|aws.auth#sigv4] | TraitTarget diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-no-sigv4.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-no-sigv4.smithy new file mode 100644 index 00000000000..13b5f3644a4 --- /dev/null +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-no-sigv4.smithy @@ -0,0 +1,10 @@ +$version: "2.0" + +namespace smithy.example + +use aws.auth#sigv4a + +@sigv4a(name: "signingname") +service InvalidService { + version: "2020-07-02" +} diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-service-mismatch.errors b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-service-mismatch.errors new file mode 100644 index 00000000000..469c9815dd3 --- /dev/null +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-service-mismatch.errors @@ -0,0 +1,2 @@ +[WARNING] smithy.example#InvalidService: Value for `aws.api#service$arnNamespace` "invalidservice" and value for `aws.auth#sigv4a$name` "signingname" SHOULD match. | SigV4Traits +[WARNING] smithy.example#InvalidService: Value for `aws.auth#sigv4$name` "invalidservice" and value for `aws.auth#sigv4a$name` "signingname" SHOULD match. | SigV4Traits diff --git a/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-service-mismatch.smithy b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-service-mismatch.smithy new file mode 100644 index 00000000000..1c7c3a864b7 --- /dev/null +++ b/smithy-aws-traits/src/test/resources/software/amazon/smithy/aws/traits/errorfiles/auth/sigv4a-service-mismatch.smithy @@ -0,0 +1,15 @@ +$version: "2.0" + +namespace smithy.example + +use aws.api#service +use aws.auth#sigv4 +use aws.auth#sigv4a + +@service(sdkId: "servicename") +@auth([sigv4a, sigv4]) +@sigv4(name: "invalidservice") +@sigv4a(name: "signingname") +service InvalidService { + version: "2020-07-02" +}