pax_global_header 0000666 0000000 0000000 00000000064 15102203672 0014510 g ustar 00root root 0000000 0000000 52 comment=299f4049a6724834ef275ce37d7012e7839dfb99
smithy-go-1.23.2/ 0000775 0000000 0000000 00000000000 15102203672 0013515 5 ustar 00root root 0000000 0000000 smithy-go-1.23.2/.github/ 0000775 0000000 0000000 00000000000 15102203672 0015055 5 ustar 00root root 0000000 0000000 smithy-go-1.23.2/.github/CODEOWNERS 0000664 0000000 0000000 00000000121 15102203672 0016442 0 ustar 00root root 0000000 0000000 # Add core contributors to all PRs by default
* @aws/aws-sdk-go-team @aws/smithy
smithy-go-1.23.2/.github/PULL_REQUEST_TEMPLATE.md 0000664 0000000 0000000 00000000251 15102203672 0020654 0 ustar 00root root 0000000 0000000 *Issue #, if available:*
*Description of changes:*
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
smithy-go-1.23.2/.github/labels.yml 0000664 0000000 0000000 00000005761 15102203672 0017053 0 ustar 00root root 0000000 0000000 - name: bug
color: d73a4a
description: "This issue is a bug."
- name: duplicate
color: cfd8d7
description: "This issue is a duplicate."
- name: needs-triage
color: c7cafc
description: "This issue or PR still needs to be triaged."
- name: investigating
color: 0000ff
description: "This issue is being investigated and/or work is in progress to resolve the issue."
- name: response-requested
color: "666666"
description: "Waiting on additional info and feedback. Will move to 'closing-soon' in 5 days."
- name: closing-soon
color: "000000"
description: "This issue will automatically close in 2 days unless further comments are made."
- name: feature-request
color: 008672
description: "A feature should be added or improved."
- name: guidance
color: 8a19e7
description: "Question that needs advice or information."
- name: documentation
color: f7bc35
description: "This is a problem with documentation."
- name: third-party
color: 40C8B6
description: "This issue is related to third-party libraries or applications."
- name: dependencies
color: 40C8B6
description: "This issue is a problem in a dependency."
- name: blocked
color: 86499B
description: "Work is blocked on this issue for this codebase. Other labels or comments may indicate why."
- name: pending-release
color: 86499B
description: "This issue will be fixed by an approved PR that hasn't been released yet."
- name: service-api
color: E06A18
description: "This issue is due to a problem in a service API, not the SDK implementation."
- name: breaking-change
color: d85658
description: "This issue requires a breaking change to remediate."
- name: needs-reproduction
color: ffe79b
description: "This issue needs reproduction."
- name: needs-discussion
color: ffe79b
description: "This issue/PR requires more discussion with community."
- name: SECURITY
color: d85658
description: ""
- name: help wanted
color: ffe79b
description: "We are asking the community to submit a PR to resolve this issue."
- name: wontfix
color: d85658
description: "We have determined that we will not resolve the issue"
- name: pr/do-not-merge
color: 358F18
description: "This PR should not be merged at this time."
- name: pr/breaking-change
color: 358F18
description: "This PR is a breaking change. It needs to be modified to be allowed in the current major version."
- name: pr/blocked
color: 358F18
description: "This PR cannot be merged or reviewed, because it is blocked for some reason."
- name: pr/work-in-progress
color: 358F18
description: "This PR is a draft and needs further work."
- name: needs-review
color: f996e2
description: ""
- name: pr/ready-to-merge
color: 358F18
description: "This PR is ready to be merged."
- name: pr/needs-tests
color: 358F18
description: "This PR is missing tests."
- name: contribution/core
color: DA5F98
description: "This is a PR that came from AWS."
- name: no-auto-closure
color: 009698
description: "We want this issue to not be automatically closed."
smithy-go-1.23.2/.github/workflows/ 0000775 0000000 0000000 00000000000 15102203672 0017112 5 ustar 00root root 0000000 0000000 smithy-go-1.23.2/.github/workflows/api_diff_check.yml 0000664 0000000 0000000 00000001075 15102203672 0022536 0 ustar 00root root 0000000 0000000 name: Go
on:
push:
branches: [ main ]
pull_request:
branches: [ '*' ]
permissions:
contents: read
jobs:
build:
name: API Diff Check
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: "1.23"
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Get dependencies
run: |
(cd /tmp && go install golang.org/x/exp/cmd/gorelease@latest)
- name: Check APIs
run: $(go env GOPATH)/bin/gorelease
smithy-go-1.23.2/.github/workflows/codegen.yml 0000664 0000000 0000000 00000001732 15102203672 0021244 0 ustar 00root root 0000000 0000000 name: Codegen Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
permissions:
contents: read
jobs:
codegen-test:
name: SDK Codegen Test
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
go-version: ["1.23"]
env:
JAVA_TOOL_OPTIONS: "-Xmx2g"
steps:
- uses: actions/checkout@v2
- name: Download Coretto 17 JDK
run: |
download_url="https://corretto.aws/downloads/latest/amazon-corretto-17-x64-linux-jdk.tar.gz"
wget -O $RUNNER_TEMP/java_package.tar.gz $download_url
- name: Set up Coretto 17 JDK
uses: actions/setup-java@v2
with:
distribution: 'jdkfile'
jdkFile: ${{ runner.temp }}/java_package.tar.gz
java-version: 17
architecture: x64
- uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: SDK Codegen
run: cd codegen && ./gradlew clean build -Plog-tests
smithy-go-1.23.2/.github/workflows/go.yml 0000664 0000000 0000000 00000001017 15102203672 0020241 0 ustar 00root root 0000000 0000000 name: Go Tests
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
permissions:
contents: read
jobs:
unit-tests:
name: SDK Unit Tests
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
go-version: ["1.23", "1.24", "1.25"]
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Test
run: make unit-race
smithy-go-1.23.2/.github/workflows/labeler.yml 0000664 0000000 0000000 00000000725 15102203672 0021247 0 ustar 00root root 0000000 0000000 name: github
on:
push:
branches:
- "main"
permissions:
issues: write
jobs:
labeler:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Run Labeler
if: success()
uses: crazy-max/ghaction-github-labeler@v1
with:
yaml_file: .github/labels.yml
skip_delete: true
dry_run: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
smithy-go-1.23.2/.gitignore 0000664 0000000 0000000 00000000331 15102203672 0015502 0 ustar 00root root 0000000 0000000 # Eclipse
.classpath
.project
.settings/
# Intellij
.idea/
*.iml
*.iws
# Mac
.DS_Store
# Maven
target/
**/dependency-reduced-pom.xml
# Gradle
/.gradle
build/
*/out/
*/*/out/
# VS Code
bin/
.vscode/
# make
c.out
smithy-go-1.23.2/.travis.yml 0000664 0000000 0000000 00000000571 15102203672 0015631 0 ustar 00root root 0000000 0000000 language: go
sudo: true
dist: bionic
branches:
only:
- main
os:
- linux
- osx
# Travis doesn't work with windows and Go tip
#- windows
go:
- tip
matrix:
allow_failures:
- go: tip
before_install:
- if [ "$TRAVIS_OS_NAME" = "windows" ]; then choco install make; fi
- (cd /tmp/; go get golang.org/x/lint/golint)
script:
- make go test -v ./...;
smithy-go-1.23.2/CHANGELOG.md 0000664 0000000 0000000 00000030644 15102203672 0015335 0 ustar 00root root 0000000 0000000 # Release (2025-11-03)
## General Highlights
* **Dependency Update**: Updated to the latest SDK module versions
## Module Highlights
* `github.com/aws/smithy-go`: v1.23.2
* **Bug Fix**: Adjust the initial sizes of each middleware phase to avoid some unnecessary reallocation.
* **Bug Fix**: Avoid unnecessary allocation overhead from the metrics system when not in use.
# Release (2025-10-15)
## General Highlights
* **Dependency Update**: Bump minimum go version to 1.23.
* **Dependency Update**: Updated to the latest SDK module versions
# Release (2025-09-18)
## Module Highlights
* `github.com/aws/smithy-go/aws-http-auth`: [v1.1.0](aws-http-auth/CHANGELOG.md#v110-2025-09-18)
* **Feature**: Added support for SIG4/SIGV4A querystring authentication.
# Release (2025-08-27)
## General Highlights
* **Dependency Update**: Updated to the latest SDK module versions
## Module Highlights
* `github.com/aws/smithy-go`: v1.23.0
* **Feature**: Sort map keys in JSON Document types.
# Release (2025-07-24)
## General Highlights
* **Dependency Update**: Updated to the latest SDK module versions
## Module Highlights
* `github.com/aws/smithy-go`: v1.22.5
* **Feature**: Add HTTP interceptors.
# Release (2025-06-16)
## General Highlights
* **Dependency Update**: Updated to the latest SDK module versions
## Module Highlights
* `github.com/aws/smithy-go`: v1.22.4
* **Bug Fix**: Fix CBOR serd empty check for string and enum fields
* **Bug Fix**: Fix HTTP metrics data race.
* **Bug Fix**: Replace usages of deprecated ioutil package.
# Release (2025-02-17)
## General Highlights
* **Dependency Update**: Updated to the latest SDK module versions
## Module Highlights
* `github.com/aws/smithy-go`: v1.22.3
* **Dependency Update**: Bump minimum Go version to 1.22 per our language support policy.
# Release (2025-01-21)
## General Highlights
* **Dependency Update**: Updated to the latest SDK module versions
## Module Highlights
* `github.com/aws/smithy-go`: v1.22.2
* **Bug Fix**: Fix HTTP metrics data race.
* **Bug Fix**: Replace usages of deprecated ioutil package.
# Release (2024-11-15)
## General Highlights
* **Dependency Update**: Updated to the latest SDK module versions
## Module Highlights
* `github.com/aws/smithy-go`: v1.22.1
* **Bug Fix**: Fix failure to replace URI path segments when their names overlap.
# Release (2024-10-03)
## General Highlights
* **Dependency Update**: Updated to the latest SDK module versions
## Module Highlights
* `github.com/aws/smithy-go`: v1.22.0
* **Feature**: Add HTTP client metrics.
# Release (2024-09-25)
## Module Highlights
* `github.com/aws/smithy-go/aws-http-auth`: [v1.0.0](aws-http-auth/CHANGELOG.md#v100-2024-09-25)
* **Release**: Initial release of module aws-http-auth, which implements generically consumable SigV4 and SigV4a request signing.
# Release (2024-09-19)
## General Highlights
* **Dependency Update**: Updated to the latest SDK module versions
## Module Highlights
* `github.com/aws/smithy-go`: v1.21.0
* **Feature**: Add tracing and metrics APIs, and builtin instrumentation for both, in generated clients.
* `github.com/aws/smithy-go/metrics/smithyotelmetrics`: [v1.0.0](metrics/smithyotelmetrics/CHANGELOG.md#v100-2024-09-19)
* **Release**: Initial release of `smithyotelmetrics` module, which is used to adapt an OpenTelemetry SDK meter provider to be used with Smithy clients.
* `github.com/aws/smithy-go/tracing/smithyoteltracing`: [v1.0.0](tracing/smithyoteltracing/CHANGELOG.md#v100-2024-09-19)
* **Release**: Initial release of `smithyoteltracing` module, which is used to adapt an OpenTelemetry SDK tracer provider to be used with Smithy clients.
# Release (2024-08-14)
## Module Highlights
* `github.com/aws/smithy-go`: v1.20.4
* **Dependency Update**: Bump minimum Go version to 1.21.
# Release (2024-06-27)
## Module Highlights
* `github.com/aws/smithy-go`: v1.20.3
* **Bug Fix**: Fix encoding/cbor test overflow on x86.
# Release (2024-03-29)
* No change notes available for this release.
# Release (2024-02-21)
## Module Highlights
* `github.com/aws/smithy-go`: v1.20.1
* **Bug Fix**: Remove runtime dependency on go-cmp.
# Release (2024-02-13)
## Module Highlights
* `github.com/aws/smithy-go`: v1.20.0
* **Feature**: Add codegen definition for sigv4a trait.
* **Feature**: Bump minimum Go version to 1.20 per our language support policy.
# Release (2023-12-07)
## Module Highlights
* `github.com/aws/smithy-go`: v1.19.0
* **Feature**: Support modeled request compression.
# Release (2023-11-30)
* No change notes available for this release.
# Release (2023-11-29)
## Module Highlights
* `github.com/aws/smithy-go`: v1.18.0
* **Feature**: Expose Options() method on generated service clients.
# Release (2023-11-15)
## Module Highlights
* `github.com/aws/smithy-go`: v1.17.0
* **Feature**: Support identity/auth components of client reference architecture.
# Release (2023-10-31)
## Module Highlights
* `github.com/aws/smithy-go`: v1.16.0
* **Feature**: **LANG**: Bump minimum go version to 1.19.
# Release (2023-10-06)
## Module Highlights
* `github.com/aws/smithy-go`: v1.15.0
* **Feature**: Add `http.WithHeaderComment` middleware.
# Release (2023-08-18)
* No change notes available for this release.
# Release (2023-08-07)
## Module Highlights
* `github.com/aws/smithy-go`: v1.14.1
* **Bug Fix**: Prevent duplicated error returns in EndpointResolverV2 default implementation.
# Release (2023-07-31)
## General Highlights
* **Feature**: Adds support for smithy-modeled endpoint resolution.
# Release (2022-12-02)
* No change notes available for this release.
# Release (2022-10-24)
## Module Highlights
* `github.com/aws/smithy-go`: v1.13.4
* **Bug Fix**: fixed document type checking for encoding nested types
# Release (2022-09-14)
* No change notes available for this release.
# Release (v1.13.2)
* No change notes available for this release.
# Release (v1.13.1)
* No change notes available for this release.
# Release (v1.13.0)
## Module Highlights
* `github.com/aws/smithy-go`: v1.13.0
* **Feature**: Adds support for the Smithy httpBearerAuth authentication trait to smithy-go. This allows the SDK to support the bearer authentication flow for API operations decorated with httpBearerAuth. An API client will need to be provided with its own bearer.TokenProvider implementation or use the bearer.StaticTokenProvider implementation.
# Release (v1.12.1)
## Module Highlights
* `github.com/aws/smithy-go`: v1.12.1
* **Bug Fix**: Fixes a bug where JSON object keys were not escaped.
# Release (v1.12.0)
## Module Highlights
* `github.com/aws/smithy-go`: v1.12.0
* **Feature**: `transport/http`: Add utility for setting context metadata when operation serializer automatically assigns content-type default value.
# Release (v1.11.3)
## Module Highlights
* `github.com/aws/smithy-go`: v1.11.3
* **Dependency Update**: Updates smithy-go unit test dependency go-cmp to 0.5.8.
# Release (v1.11.2)
* No change notes available for this release.
# Release (v1.11.1)
## Module Highlights
* `github.com/aws/smithy-go`: v1.11.1
* **Bug Fix**: Updates the smithy-go HTTP Request to correctly handle building the request to an http.Request. Related to [aws/aws-sdk-go-v2#1583](https://github.com/aws/aws-sdk-go-v2/issues/1583)
# Release (v1.11.0)
## Module Highlights
* `github.com/aws/smithy-go`: v1.11.0
* **Feature**: Updates deserialization of header list to supported quoted strings
# Release (v1.10.0)
## Module Highlights
* `github.com/aws/smithy-go`: v1.10.0
* **Feature**: Add `ptr.Duration`, `ptr.ToDuration`, `ptr.DurationSlice`, `ptr.ToDurationSlice`, `ptr.DurationMap`, and `ptr.ToDurationMap` functions for the `time.Duration` type.
# Release (v1.9.1)
## Module Highlights
* `github.com/aws/smithy-go`: v1.9.1
* **Documentation**: Fixes various typos in Go package documentation.
# Release (v1.9.0)
## Module Highlights
* `github.com/aws/smithy-go`: v1.9.0
* **Feature**: sync: OnceErr, can be used to concurrently record a signal when an error has occurred.
* **Bug Fix**: `transport/http`: CloseResponseBody and ErrorCloseResponseBody middleware have been updated to ensure that the body is fully drained before closing.
# Release v1.8.1
### Smithy Go Module
* **Bug Fix**: Fixed an issue that would cause the HTTP Content-Length to be set to 0 if the stream body was not set.
* Fixes [aws/aws-sdk-go-v2#1418](https://github.com/aws/aws-sdk-go-v2/issues/1418)
# Release v1.8.0
### Smithy Go Module
* `time`: Add support for parsing additional DateTime timestamp format ([#324](https://github.com/aws/smithy-go/pull/324))
* Adds support for parsing DateTime timestamp formatted time similar to RFC 3339, but without the `Z` character, nor UTC offset.
* Fixes [#1387](https://github.com/aws/aws-sdk-go-v2/issues/1387)
# Release v1.7.0
### Smithy Go Module
* `ptr`: Handle error for deferred file close call ([#314](https://github.com/aws/smithy-go/pull/314))
* Handle error for defer close call
* `middleware`: Add Clone to Metadata ([#318](https://github.com/aws/smithy-go/pull/318))
* Adds a new Clone method to the middleware Metadata type. This provides a shallow clone of the entries in the Metadata.
* `document`: Add new package for document shape serialization support ([#310](https://github.com/aws/smithy-go/pull/310))
### Codegen
* Add Smithy Document Shape Support ([#310](https://github.com/aws/smithy-go/pull/310))
* Adds support for Smithy Document shapes and supporting types for protocols to implement support
# Release v1.6.0 (2021-07-15)
### Smithy Go Module
* `encoding/httpbinding`: Support has been added for encoding `float32` and `float64` values that are `NaN`, `Infinity`, or `-Infinity`. ([#316](https://github.com/aws/smithy-go/pull/316))
### Codegen
* Adds support for handling `float32` and `float64` `NaN` values in HTTP Protocol Unit Tests. ([#316](https://github.com/aws/smithy-go/pull/316))
* Adds support protocol generator implementations to override the error code string returned by `ErrorCode` methods on generated error types. ([#315](https://github.com/aws/smithy-go/pull/315))
# Release v1.5.0 (2021-06-25)
### Smithy Go module
* `time`: Update time parsing to not be as strict for HTTPDate and DateTime ([#307](https://github.com/aws/smithy-go/pull/307))
* Fixes [#302](https://github.com/aws/smithy-go/issues/302) by changing time to UTC before formatting so no local offset time is lost.
### Codegen
* Adds support for integrating client members via plugins ([#301](https://github.com/aws/smithy-go/pull/301))
* Fix serialization of enum types marked with payload trait ([#296](https://github.com/aws/smithy-go/pull/296))
* Update generation of API client modules to include a manifest of files generated ([#283](https://github.com/aws/smithy-go/pull/283))
* Update Group Java group ID for smithy-go generator ([#298](https://github.com/aws/smithy-go/pull/298))
* Support the delegation of determining the errors that can occur for an operation ([#304](https://github.com/aws/smithy-go/pull/304))
* Support for marking and documenting deprecated client config fields. ([#303](https://github.com/aws/smithy-go/pull/303))
# Release v1.4.0 (2021-05-06)
### Smithy Go module
* `encoding/xml`: Fix escaping of Next Line and Line Start in XML Encoder ([#267](https://github.com/aws/smithy-go/pull/267))
### Codegen
* Add support for Smithy 1.7 ([#289](https://github.com/aws/smithy-go/pull/289))
* Add support for httpQueryParams location
* Add support for model renaming conflict resolution with service closure
# Release v1.3.1 (2021-04-08)
### Smithy Go module
* `transport/http`: Loosen endpoint hostname validation to allow specifying port numbers. ([#279](https://github.com/aws/smithy-go/pull/279))
* `io`: Fix RingBuffer panics due to out of bounds index. ([#282](https://github.com/aws/smithy-go/pull/282))
# Release v1.3.0 (2021-04-01)
### Smithy Go module
* `transport/http`: Add utility to safely join string to url path, and url raw query.
### Codegen
* Update HttpBindingProtocolGenerator to use http/transport JoinPath and JoinQuery utility.
# Release v1.2.0 (2021-03-12)
### Smithy Go module
* Fix support for parsing shortened year format in HTTP Date header.
* Fix GitHub APIDiff action workflow to get gorelease tool correctly.
* Fix codegen artifact unit test for Go 1.16
### Codegen
* Fix generating paginator nil parameter handling before usage.
* Fix Serialize unboxed members decorated as required.
* Add ability to define resolvers at both client construction and operation invocation.
* Support for extending paginators with custom runtime trait
smithy-go-1.23.2/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000000465 15102203672 0016321 0 ustar 00root root 0000000 0000000 ## Code of Conduct
This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
opensource-codeofconduct@amazon.com with any additional questions or comments.
smithy-go-1.23.2/CONTRIBUTING.md 0000664 0000000 0000000 00000010756 15102203672 0015757 0 ustar 00root root 0000000 0000000 # Contributing Guidelines
Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional
documentation, we greatly value feedback and contributions from our community.
Please read through this document before submitting any issues or pull requests to ensure we have all the necessary
information to effectively respond to your bug report or contribution.
## Reporting Bugs/Feature Requests
We welcome you to use the GitHub issue tracker to report bugs or suggest features.
When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already
reported the issue. Please try to include as much information as you can. Details like these are incredibly useful:
* A reproducible test case or series of steps
* The version of our code being used
* Any modifications you've made relevant to the bug
* Anything unusual about your environment or deployment
## Contributing via Pull Requests
Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
1. You are working against the latest source on the *main* branch.
2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already.
3. You open an issue to discuss any significant work - we would hate for your time to be wasted.
To send us a pull request, please:
1. Fork the repository.
2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change.
3. Ensure local tests pass.
4. Commit to your fork using clear commit messages.
5. Send us a pull request, answering any default questions in the pull request interface.
6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation.
GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and
[creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
### Changelog Documents
(You can SKIP this step if you are only changing the code generator, and not the runtime).
When submitting a pull request please include a changelog file on a folder named `.changelog`.
These are used to generate the content `CHANGELOG.md` and Release Notes. The format of the file is as follows:
```
{
"id": "12345678-1234-1234-1234-123456789012"
"type": "bugfix"
"collapse": true
"description": "Fix improper use of printf-style functions.",
"modules": [
"."
]
}
```
* id: a UUID. This should also be used for the name of the file, so if your id is `12345678-1234-1234-1234-123456789012` the file should be named `12345678-1234-1234-1234-123456789012.json/`
* type: one of the following:
* bugfix: Fixing an existing bug
* Feature: Adding a new feature to an existing service
* Release: Releasing a new module
* Dependency: Updating dependencies
* Announcement: Making an announcement, like deprecation of a module
* collapse: whether this change should appear separately on the release notes on every module listed on `modules` (`"collapse": false`), or if it should show up as a single entry (`"collapse": true`)
* For the smithy-go repository this should always be `false`
* description: Description of this change. Most of the times is the same as the title of the PR
* modules: which Go modules does this change impact. The root module is expressed as "."
## Finding contributions to work on
Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start.
## Code of Conduct
This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct).
For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact
opensource-codeofconduct@amazon.com with any additional questions or comments.
## Security issue notifications
If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue.
## Licensing
See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution.
smithy-go-1.23.2/LICENSE 0000664 0000000 0000000 00000023636 15102203672 0014534 0 ustar 00root root 0000000 0000000
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
smithy-go-1.23.2/Makefile 0000664 0000000 0000000 00000010641 15102203672 0015157 0 ustar 00root root 0000000 0000000 PRE_RELEASE_VERSION ?=
RELEASE_MANIFEST_FILE ?=
RELEASE_CHGLOG_DESC_FILE ?=
REPOTOOLS_VERSION ?= latest
REPOTOOLS_MODULE = github.com/awslabs/aws-go-multi-module-repository-tools
REPOTOOLS_CMD_CALCULATE_RELEASE = ${REPOTOOLS_MODULE}/cmd/calculaterelease@${REPOTOOLS_VERSION}
REPOTOOLS_CMD_CALCULATE_RELEASE_ADDITIONAL_ARGS ?=
REPOTOOLS_CMD_UPDATE_REQUIRES = ${REPOTOOLS_MODULE}/cmd/updaterequires@${REPOTOOLS_VERSION}
REPOTOOLS_CMD_UPDATE_MODULE_METADATA = ${REPOTOOLS_MODULE}/cmd/updatemodulemeta@${REPOTOOLS_VERSION}
REPOTOOLS_CMD_GENERATE_CHANGELOG = ${REPOTOOLS_MODULE}/cmd/generatechangelog@${REPOTOOLS_VERSION}
REPOTOOLS_CMD_CHANGELOG = ${REPOTOOLS_MODULE}/cmd/changelog@${REPOTOOLS_VERSION}
REPOTOOLS_CMD_TAG_RELEASE = ${REPOTOOLS_MODULE}/cmd/tagrelease@${REPOTOOLS_VERSION}
REPOTOOLS_CMD_MODULE_VERSION = ${REPOTOOLS_MODULE}/cmd/moduleversion@${REPOTOOLS_VERSION}
REPOTOOLS_CMD_EACHMODULE = ${REPOTOOLS_MODULE}/cmd/eachmodule@${REPOTOOLS_VERSION}
UNIT_TEST_TAGS=
BUILD_TAGS=
ifneq ($(PRE_RELEASE_VERSION),)
REPOTOOLS_CMD_CALCULATE_RELEASE_ADDITIONAL_ARGS += -preview=${PRE_RELEASE_VERSION}
endif
smithy-publish-local:
cd codegen && ./gradlew publishToMavenLocal
smithy-build:
cd codegen && ./gradlew build
smithy-clean:
cd codegen && ./gradlew clean
GRADLE_RETRIES := 3
GRADLE_SLEEP := 2
# We're making a call to ./gradlew to trigger downloading Gradle and
# starting the daemon. Any call works, so using `./gradlew help`
ensure-gradle-up:
@cd codegen && for i in $(shell seq 1 $(GRADLE_RETRIES)); do \
echo "Checking if Gradle daemon is up, attempt $$i..."; \
if ./gradlew help; then \
echo "Gradle daemon is up!"; \
exit 0; \
fi; \
echo "Failed to start Gradle, retrying in $(GRADLE_SLEEP) seconds..."; \
sleep $(GRADLE_SLEEP); \
done; \
echo "Failed to start Gradle after $(GRADLE_RETRIES) attempts."; \
exit 1
##################
# Linting/Verify #
##################
.PHONY: verify vet cover
verify: vet
vet: vet-modules-.
vet-modules-%:
go run ${REPOTOOLS_CMD_EACHMODULE} -p $(subst vet-modules-,,$@) \
"go vet ${BUILD_TAGS} --all ./..."
cover:
go test ${BUILD_TAGS} -coverprofile c.out ./...
@cover=`go tool cover -func c.out | grep '^total:' | awk '{ print $$3+0 }'`; \
echo "total (statements): $$cover%";
################
# Unit Testing #
################
.PHONY: test unit unit-race
test: unit-race
unit: verify unit-modules-.
unit-modules-%:
go run ${REPOTOOLS_CMD_EACHMODULE} -p $(subst unit-modules-,,$@) \
"go test -timeout=1m ${UNIT_TEST_TAGS} ./..."
unit-race: verify unit-race-modules-.
unit-race-modules-%:
go run ${REPOTOOLS_CMD_EACHMODULE} -p $(subst unit-race-modules-,,$@) \
"go test -timeout=1m ${UNIT_TEST_TAGS} -race -cpu=4 ./..."
#####################
# Release Process #
#####################
.PHONY: preview-release pre-release-validation release
preview-release:
go run ${REPOTOOLS_CMD_CALCULATE_RELEASE} ${REPOTOOLS_CMD_CALCULATE_RELEASE_ADDITIONAL_ARGS}
pre-release-validation:
@if [[ -z "${RELEASE_MANIFEST_FILE}" ]]; then \
echo "RELEASE_MANIFEST_FILE is required to specify the file to write the release manifest" && false; \
fi
@if [[ -z "${RELEASE_CHGLOG_DESC_FILE}" ]]; then \
echo "RELEASE_CHGLOG_DESC_FILE is required to specify the file to write the release notes" && false; \
fi
release: pre-release-validation
go run ${REPOTOOLS_CMD_CALCULATE_RELEASE} -o ${RELEASE_MANIFEST_FILE} ${REPOTOOLS_CMD_CALCULATE_RELEASE_ADDITIONAL_ARGS}
go run ${REPOTOOLS_CMD_UPDATE_REQUIRES} -release ${RELEASE_MANIFEST_FILE}
go run ${REPOTOOLS_CMD_UPDATE_MODULE_METADATA} -release ${RELEASE_MANIFEST_FILE}
go run ${REPOTOOLS_CMD_GENERATE_CHANGELOG} -release ${RELEASE_MANIFEST_FILE} -o ${RELEASE_CHGLOG_DESC_FILE}
go run ${REPOTOOLS_CMD_CHANGELOG} rm -all
go run ${REPOTOOLS_CMD_TAG_RELEASE} -release ${RELEASE_MANIFEST_FILE}
module-version:
@go run ${REPOTOOLS_CMD_MODULE_VERSION} .
##############
# Repo Tools #
##############
.PHONY: install-changelog
external-changelog:
mkdir -p .changelog
cp changelog-template.json .changelog/00000000-0000-0000-0000-000000000000.json
@echo "Generate a new UUID and update the file at .changelog/00000000-0000-0000-0000-000000000000.json"
@echo "Make sure to rename the file with your new id, like .changelog/12345678-1234-1234-1234-123456789012.json"
@echo "See CONTRIBUTING.md 'Changelog Documents' and an example at https://github.com/aws/smithy-go/pull/543/files"
install-changelog:
go install ${REPOTOOLS_MODULE}/cmd/changelog@${REPOTOOLS_VERSION}
smithy-go-1.23.2/NOTICE 0000664 0000000 0000000 00000000103 15102203672 0014413 0 ustar 00root root 0000000 0000000 Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
smithy-go-1.23.2/README.md 0000664 0000000 0000000 00000010765 15102203672 0015005 0 ustar 00root root 0000000 0000000 # Smithy Go
[](https://github.com/aws/smithy-go/actions/workflows/go.yml)[](https://github.com/aws/smithy-go/actions/workflows/codegen.yml)
[Smithy](https://smithy.io/) code generators for Go and the accompanying smithy-go runtime.
The smithy-go runtime requires a minimum version of Go 1.23.
**WARNING: All interfaces are subject to change.**
## :no_entry_sign: DO NOT use the code generators in this repository
**The code generators in this repository do not generate working clients at
this time.**
In order to generate a usable smithy client you must provide a [protocol definition](https://github.com/aws/smithy-go/blob/main/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/ProtocolGenerator.java),
such as [AWS restJson1](https://smithy.io/2.0/aws/protocols/aws-restjson1-protocol.html),
in order to generate transport mechanisms and serialization/deserialization
code ("serde") accordingly.
The code generator does not currently support any protocols out of the box.
Support for all [AWS protocols](https://smithy.io/2.0/aws/protocols/index.html)
exists in [aws-sdk-go-v2](https://github.com/aws/aws-sdk-go-v2). We are
tracking the movement of those out of the SDK into smithy-go in
[#458](https://github.com/aws/smithy-go/issues/458), but there's currently no
timeline for doing so.
## Plugins
This repository implements the following Smithy build plugins:
| ID | GAV prefix | Description |
|----|------------|-------------|
| `go-codegen` | `software.amazon.smithy.go:smithy-go-codegen` | Implements Go client code generation for Smithy models. |
| `go-server-codegen` | `software.amazon.smithy.go:smithy-go-codegen` | Implements Go server code generation for Smithy models. |
| `go-shape-codegen` | `software.amazon.smithy.go:smithy-go-codegen` | Implements Go shape code generation (types only) for Smithy models. |
**NOTE: Build plugins are not currently published to mavenCentral. You must publish to mavenLocal to make the build plugins visible to the Smithy CLI. The artifact version is currently fixed at 0.1.0.**
## `go-codegen`
### Configuration
[`GoSettings`](codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoSettings.java)
contains all of the settings enabled from `smithy-build.json` and helper
methods and types. The up-to-date list of top-level properties enabled for
`go-client-codegen` can be found in `GoSettings::from()`.
| Setting | Type | Required | Description |
|-----------------|---------|----------|-----------------------------------------------------------------------------------------------------------------------------|
| `service` | string | yes | The Shape ID of the service for which to generate the client. |
| `module` | string | yes | Name of the module in `generated.json` (and `go.mod` if `generateGoMod` is enabled) and `doc.go`. |
| `generateGoMod` | boolean | | Whether to generate a default `go.mod` file. The default value is `false`. |
| `goDirective` | string | | [Go directive](https://go.dev/ref/mod#go-mod-file-go) of the module. The default value is the minimum supported Go version. |
### Supported protocols
| Protocol | Notes |
|----------|-------|
| [`smithy.protocols#rpcv2Cbor`](https://smithy.io/2.0/additional-specs/protocols/smithy-rpc-v2.html) | Event streaming not yet implemented. |
### Example
This example applies the `go-codegen` build plugin to the Smithy quickstart
example created from `smithy init`:
```json
{
"version": "1.0",
"sources": [
"models"
],
"maven": {
"dependencies": [
"software.amazon.smithy.go:smithy-go-codegen:0.1.0"
]
},
"plugins": {
"go-codegen": {
"service": "example.weather#Weather",
"module": "github.com/example/weather",
"generateGoMod": true,
"goDirective": "1.23"
}
}
}
```
## `go-server-codegen`
This plugin is a work-in-progress and is currently undocumented.
## `go-shape-codegen`
This plugin is a work-in-progress and is currently undocumented.
## License
This project is licensed under the Apache-2.0 License.
smithy-go-1.23.2/auth/ 0000775 0000000 0000000 00000000000 15102203672 0014456 5 ustar 00root root 0000000 0000000 smithy-go-1.23.2/auth/auth.go 0000664 0000000 0000000 00000000143 15102203672 0015744 0 ustar 00root root 0000000 0000000 // Package auth defines protocol-agnostic authentication types for smithy
// clients.
package auth
smithy-go-1.23.2/auth/bearer/ 0000775 0000000 0000000 00000000000 15102203672 0015716 5 ustar 00root root 0000000 0000000 smithy-go-1.23.2/auth/bearer/docs.go 0000664 0000000 0000000 00000000202 15102203672 0017167 0 ustar 00root root 0000000 0000000 // Package bearer provides middleware and utilities for authenticating API
// operation calls with a Bearer Token.
package bearer
smithy-go-1.23.2/auth/bearer/middleware.go 0000664 0000000 0000000 00000006717 15102203672 0020375 0 ustar 00root root 0000000 0000000 package bearer
import (
"context"
"fmt"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
// Message is the middleware stack's request transport message value.
type Message interface{}
// Signer provides an interface for implementations to decorate a request
// message with a bearer token. The signer is responsible for validating the
// message type is compatible with the signer.
type Signer interface {
SignWithBearerToken(context.Context, Token, Message) (Message, error)
}
// AuthenticationMiddleware provides the Finalize middleware step for signing
// an request message with a bearer token.
type AuthenticationMiddleware struct {
signer Signer
tokenProvider TokenProvider
}
// AddAuthenticationMiddleware helper adds the AuthenticationMiddleware to the
// middleware Stack in the Finalize step with the options provided.
func AddAuthenticationMiddleware(s *middleware.Stack, signer Signer, tokenProvider TokenProvider) error {
return s.Finalize.Add(
NewAuthenticationMiddleware(signer, tokenProvider),
middleware.After,
)
}
// NewAuthenticationMiddleware returns an initialized AuthenticationMiddleware.
func NewAuthenticationMiddleware(signer Signer, tokenProvider TokenProvider) *AuthenticationMiddleware {
return &AuthenticationMiddleware{
signer: signer,
tokenProvider: tokenProvider,
}
}
const authenticationMiddlewareID = "BearerTokenAuthentication"
// ID returns the resolver identifier
func (m *AuthenticationMiddleware) ID() string {
return authenticationMiddlewareID
}
// HandleFinalize implements the FinalizeMiddleware interface in order to
// update the request with bearer token authentication.
func (m *AuthenticationMiddleware) HandleFinalize(
ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler,
) (
out middleware.FinalizeOutput, metadata middleware.Metadata, err error,
) {
token, err := m.tokenProvider.RetrieveBearerToken(ctx)
if err != nil {
return out, metadata, fmt.Errorf("failed AuthenticationMiddleware wrap message, %w", err)
}
signedMessage, err := m.signer.SignWithBearerToken(ctx, token, in.Request)
if err != nil {
return out, metadata, fmt.Errorf("failed AuthenticationMiddleware sign message, %w", err)
}
in.Request = signedMessage
return next.HandleFinalize(ctx, in)
}
// SignHTTPSMessage provides a bearer token authentication implementation that
// will sign the message with the provided bearer token.
//
// Will fail if the message is not a smithy-go HTTP request or the request is
// not HTTPS.
type SignHTTPSMessage struct{}
// NewSignHTTPSMessage returns an initialized signer for HTTP messages.
func NewSignHTTPSMessage() *SignHTTPSMessage {
return &SignHTTPSMessage{}
}
// SignWithBearerToken returns a copy of the HTTP request with the bearer token
// added via the "Authorization" header, per RFC 6750, https://datatracker.ietf.org/doc/html/rfc6750.
//
// Returns an error if the request's URL scheme is not HTTPS, or the request
// message is not an smithy-go HTTP Request pointer type.
func (SignHTTPSMessage) SignWithBearerToken(ctx context.Context, token Token, message Message) (Message, error) {
req, ok := message.(*smithyhttp.Request)
if !ok {
return nil, fmt.Errorf("expect smithy-go HTTP Request, got %T", message)
}
if !req.IsHTTPS() {
return nil, fmt.Errorf("bearer token with HTTP request requires HTTPS")
}
reqClone := req.Clone()
reqClone.Header.Set("Authorization", "Bearer "+token.Value)
return reqClone, nil
}
smithy-go-1.23.2/auth/bearer/middleware_test.go 0000664 0000000 0000000 00000003502 15102203672 0021421 0 ustar 00root root 0000000 0000000 package bearer
import (
"context"
"net/url"
"reflect"
"strings"
"testing"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
func TestSignHTTPSMessage(t *testing.T) {
cases := map[string]struct {
message Message
token Token
expectMessage Message
expectErr string
}{
// Cases
"not smithyhttp.Request": {
message: struct{}{},
expectErr: "expect smithy-go HTTP Request",
},
"not https": {
message: func() Message {
r := smithyhttp.NewStackRequest().(*smithyhttp.Request)
r.URL, _ = url.Parse("http://example.aws")
return r
}(),
expectErr: "requires HTTPS",
},
"success": {
message: func() Message {
r := smithyhttp.NewStackRequest().(*smithyhttp.Request)
r.URL, _ = url.Parse("https://example.aws")
return r
}(),
token: Token{Value: "abc123"},
expectMessage: func() Message {
r := smithyhttp.NewStackRequest().(*smithyhttp.Request)
r.URL, _ = url.Parse("https://example.aws")
r.Header.Set("Authorization", "Bearer abc123")
return r
}(),
},
}
for name, c := range cases {
t.Run(name, func(t *testing.T) {
ctx := context.Background()
signer := SignHTTPSMessage{}
message, err := signer.SignWithBearerToken(ctx, c.token, c.message)
if c.expectErr != "" {
if err == nil {
t.Fatalf("expect error, got none")
}
if e, a := c.expectErr, err.Error(); !strings.Contains(a, e) {
t.Fatalf("expect %v in error %v", e, a)
}
return
} else if err != nil {
t.Fatalf("expect no error, got %v", err)
}
expect := c.expectMessage.(*smithyhttp.Request)
actual, ok := message.(*smithyhttp.Request)
if !ok {
t.Fatalf("*smithyhttp.Request != %T", actual)
}
if !reflect.DeepEqual(expect.Header, actual.Header) {
t.Errorf("%v != %v", expect.Header, actual.Header)
}
})
}
}
smithy-go-1.23.2/auth/bearer/token.go 0000664 0000000 0000000 00000002541 15102203672 0017367 0 ustar 00root root 0000000 0000000 package bearer
import (
"context"
"time"
)
// Token provides a type wrapping a bearer token and expiration metadata.
type Token struct {
Value string
CanExpire bool
Expires time.Time
}
// Expired returns if the token's Expires time is before or equal to the time
// provided. If CanExpires is false, Expired will always return false.
func (t Token) Expired(now time.Time) bool {
if !t.CanExpire {
return false
}
now = now.Round(0)
return now.Equal(t.Expires) || now.After(t.Expires)
}
// TokenProvider provides interface for retrieving bearer tokens.
type TokenProvider interface {
RetrieveBearerToken(context.Context) (Token, error)
}
// TokenProviderFunc provides a helper utility to wrap a function as a type
// that implements the TokenProvider interface.
type TokenProviderFunc func(context.Context) (Token, error)
// RetrieveBearerToken calls the wrapped function, returning the Token or
// error.
func (fn TokenProviderFunc) RetrieveBearerToken(ctx context.Context) (Token, error) {
return fn(ctx)
}
// StaticTokenProvider provides a utility for wrapping a static bearer token
// value within an implementation of a token provider.
type StaticTokenProvider struct {
Token Token
}
// RetrieveBearerToken returns the static token specified.
func (s StaticTokenProvider) RetrieveBearerToken(context.Context) (Token, error) {
return s.Token, nil
}
smithy-go-1.23.2/auth/bearer/token_cache.go 0000664 0000000 0000000 00000016102 15102203672 0020510 0 ustar 00root root 0000000 0000000 package bearer
import (
"context"
"fmt"
"sync/atomic"
"time"
smithycontext "github.com/aws/smithy-go/context"
"github.com/aws/smithy-go/internal/sync/singleflight"
)
// package variable that can be override in unit tests.
var timeNow = time.Now
// TokenCacheOptions provides a set of optional configuration options for the
// TokenCache TokenProvider.
type TokenCacheOptions struct {
// The duration before the token will expire when the credentials will be
// refreshed. If DisableAsyncRefresh is true, the RetrieveBearerToken calls
// will be blocking.
//
// Asynchronous refreshes are deduplicated, and only one will be in-flight
// at a time. If the token expires while an asynchronous refresh is in
// flight, the next call to RetrieveBearerToken will block on that refresh
// to return.
RefreshBeforeExpires time.Duration
// The timeout the underlying TokenProvider's RetrieveBearerToken call must
// return within, or will be canceled. Defaults to 0, no timeout.
//
// If 0 timeout, its possible for the underlying tokenProvider's
// RetrieveBearerToken call to block forever. Preventing subsequent
// TokenCache attempts to refresh the token.
//
// If this timeout is reached all pending deduplicated calls to
// TokenCache RetrieveBearerToken will fail with an error.
RetrieveBearerTokenTimeout time.Duration
// The minimum duration between asynchronous refresh attempts. If the next
// asynchronous recent refresh attempt was within the minimum delay
// duration, the call to retrieve will return the current cached token, if
// not expired.
//
// The asynchronous retrieve is deduplicated across multiple calls when
// RetrieveBearerToken is called. The asynchronous retrieve is not a
// periodic task. It is only performed when the token has not yet expired,
// and the current item is within the RefreshBeforeExpires window, and the
// TokenCache's RetrieveBearerToken method is called.
//
// If 0, (default) there will be no minimum delay between asynchronous
// refresh attempts.
//
// If DisableAsyncRefresh is true, this option is ignored.
AsyncRefreshMinimumDelay time.Duration
// Sets if the TokenCache will attempt to refresh the token in the
// background asynchronously instead of blocking for credentials to be
// refreshed. If disabled token refresh will be blocking.
//
// The first call to RetrieveBearerToken will always be blocking, because
// there is no cached token.
DisableAsyncRefresh bool
}
// TokenCache provides an utility to cache Bearer Authentication tokens from a
// wrapped TokenProvider. The TokenCache can be has options to configure the
// cache's early and asynchronous refresh of the token.
type TokenCache struct {
options TokenCacheOptions
provider TokenProvider
cachedToken atomic.Value
lastRefreshAttemptTime atomic.Value
sfGroup singleflight.Group
}
// NewTokenCache returns a initialized TokenCache that implements the
// TokenProvider interface. Wrapping the provider passed in. Also taking a set
// of optional functional option parameters to configure the token cache.
func NewTokenCache(provider TokenProvider, optFns ...func(*TokenCacheOptions)) *TokenCache {
var options TokenCacheOptions
for _, fn := range optFns {
fn(&options)
}
return &TokenCache{
options: options,
provider: provider,
}
}
// RetrieveBearerToken returns the token if it could be obtained, or error if a
// valid token could not be retrieved.
//
// The passed in Context's cancel/deadline/timeout will impacting only this
// individual retrieve call and not any other already queued up calls. This
// means underlying provider's RetrieveBearerToken calls could block for ever,
// and not be canceled with the Context. Set RetrieveBearerTokenTimeout to
// provide a timeout, preventing the underlying TokenProvider blocking forever.
//
// By default, if the passed in Context is canceled, all of its values will be
// considered expired. The wrapped TokenProvider will not be able to lookup the
// values from the Context once it is expired. This is done to protect against
// expired values no longer being valid. To disable this behavior, use
// smithy-go's context.WithPreserveExpiredValues to add a value to the Context
// before calling RetrieveBearerToken to enable support for expired values.
//
// Without RetrieveBearerTokenTimeout there is the potential for a underlying
// Provider's RetrieveBearerToken call to sit forever. Blocking in subsequent
// attempts at refreshing the token.
func (p *TokenCache) RetrieveBearerToken(ctx context.Context) (Token, error) {
cachedToken, ok := p.getCachedToken()
if !ok || cachedToken.Expired(timeNow()) {
return p.refreshBearerToken(ctx)
}
// Check if the token should be refreshed before it expires.
refreshToken := cachedToken.Expired(timeNow().Add(p.options.RefreshBeforeExpires))
if !refreshToken {
return cachedToken, nil
}
if p.options.DisableAsyncRefresh {
return p.refreshBearerToken(ctx)
}
p.tryAsyncRefresh(ctx)
return cachedToken, nil
}
// tryAsyncRefresh attempts to asynchronously refresh the token returning the
// already cached token. If it AsyncRefreshMinimumDelay option is not zero, and
// the duration since the last refresh is less than that value, nothing will be
// done.
func (p *TokenCache) tryAsyncRefresh(ctx context.Context) {
if p.options.AsyncRefreshMinimumDelay != 0 {
var lastRefreshAttempt time.Time
if v := p.lastRefreshAttemptTime.Load(); v != nil {
lastRefreshAttempt = v.(time.Time)
}
if timeNow().Before(lastRefreshAttempt.Add(p.options.AsyncRefreshMinimumDelay)) {
return
}
}
// Ignore the returned channel so this won't be blocking, and limit the
// number of additional goroutines created.
p.sfGroup.DoChan("async-refresh", func() (interface{}, error) {
res, err := p.refreshBearerToken(ctx)
if p.options.AsyncRefreshMinimumDelay != 0 {
var refreshAttempt time.Time
if err != nil {
refreshAttempt = timeNow()
}
p.lastRefreshAttemptTime.Store(refreshAttempt)
}
return res, err
})
}
func (p *TokenCache) refreshBearerToken(ctx context.Context) (Token, error) {
resCh := p.sfGroup.DoChan("refresh-token", func() (interface{}, error) {
ctx := smithycontext.WithSuppressCancel(ctx)
if v := p.options.RetrieveBearerTokenTimeout; v != 0 {
var cancel func()
ctx, cancel = context.WithTimeout(ctx, v)
defer cancel()
}
return p.singleRetrieve(ctx)
})
select {
case res := <-resCh:
return res.Val.(Token), res.Err
case <-ctx.Done():
return Token{}, fmt.Errorf("retrieve bearer token canceled, %w", ctx.Err())
}
}
func (p *TokenCache) singleRetrieve(ctx context.Context) (interface{}, error) {
token, err := p.provider.RetrieveBearerToken(ctx)
if err != nil {
return Token{}, fmt.Errorf("failed to retrieve bearer token, %w", err)
}
p.cachedToken.Store(&token)
return token, nil
}
// getCachedToken returns the currently cached token and true if found. Returns
// false if no token is cached.
func (p *TokenCache) getCachedToken() (Token, bool) {
v := p.cachedToken.Load()
if v == nil {
return Token{}, false
}
t := v.(*Token)
if t == nil || t.Value == "" {
return Token{}, false
}
return *t, true
}
smithy-go-1.23.2/auth/bearer/token_cache_test.go 0000664 0000000 0000000 00000033533 15102203672 0021556 0 ustar 00root root 0000000 0000000 package bearer
import (
"context"
"fmt"
"strconv"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
)
var _ TokenProvider = (*TokenCache)(nil)
func TestTokenCache_cache(t *testing.T) {
expectToken := Token{
Value: "abc123",
}
var retrieveCalled bool
provider := NewTokenCache(TokenProviderFunc(func(ctx context.Context) (Token, error) {
if retrieveCalled {
t.Fatalf("expect wrapped provider to be called once")
}
retrieveCalled = true
return expectToken, nil
}))
token, err := provider.RetrieveBearerToken(context.Background())
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if expectToken != token {
t.Errorf("expect token match: %v != %v", expectToken, token)
}
for i := 0; i < 100; i++ {
token, err := provider.RetrieveBearerToken(context.Background())
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if expectToken != token {
t.Errorf("expect token match: %v != %v", expectToken, token)
}
}
}
func TestTokenCache_cacheConcurrent(t *testing.T) {
expectToken := Token{
Value: "abc123",
}
var retrieveCalled bool
provider := NewTokenCache(TokenProviderFunc(func(ctx context.Context) (Token, error) {
if retrieveCalled {
t.Fatalf("expect wrapped provider to be called once")
}
retrieveCalled = true
return expectToken, nil
}))
token, err := provider.RetrieveBearerToken(context.Background())
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if expectToken != token {
t.Errorf("expect token match: %v != %v", expectToken, token)
}
for i := 0; i < 100; i++ {
t.Run(strconv.Itoa(i), func(t *testing.T) {
t.Parallel()
token, err := provider.RetrieveBearerToken(context.Background())
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if expectToken != token {
t.Errorf("expect token match: %v != %v", expectToken, token)
}
})
}
}
func TestTokenCache_expired(t *testing.T) {
origTimeNow := timeNow
defer func() { timeNow = origTimeNow }()
timeNow = func() time.Time { return time.Time{} }
expectToken := Token{
Value: "abc123",
CanExpire: true,
Expires: timeNow().Add(10 * time.Minute),
}
refreshedToken := Token{
Value: "refreshed-abc123",
CanExpire: true,
Expires: timeNow().Add(30 * time.Minute),
}
retrievedCount := new(int32)
provider := NewTokenCache(TokenProviderFunc(func(ctx context.Context) (Token, error) {
if atomic.AddInt32(retrievedCount, 1) > 1 {
return refreshedToken, nil
}
return expectToken, nil
}))
for i := 0; i < 10; i++ {
token, err := provider.RetrieveBearerToken(context.Background())
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if expectToken != token {
t.Errorf("expect token match: %v != %v", expectToken, token)
}
}
if e, a := 1, int(atomic.LoadInt32(retrievedCount)); e != a {
t.Errorf("expect %v provider calls, got %v", e, a)
}
// Offset time for refresh
timeNow = func() time.Time {
return (time.Time{}).Add(10 * time.Minute)
}
token, err := provider.RetrieveBearerToken(context.Background())
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if refreshedToken != token {
t.Errorf("expect refreshed token match: %v != %v", refreshedToken, token)
}
if e, a := 2, int(atomic.LoadInt32(retrievedCount)); e != a {
t.Errorf("expect %v provider calls, got %v", e, a)
}
}
func TestTokenCache_cancelled(t *testing.T) {
providerRunning := make(chan struct{})
providerDone := make(chan struct{})
var onceClose sync.Once
provider := NewTokenCache(TokenProviderFunc(func(ctx context.Context) (Token, error) {
onceClose.Do(func() { close(providerRunning) })
// Provider running never receives context cancel so that if the first
// retrieve call is canceled all subsequent retrieve callers won't get
// canceled as well.
select {
case <-providerDone:
return Token{Value: "abc123"}, nil
case <-ctx.Done():
return Token{}, fmt.Errorf("unexpected context canceled, %w", ctx.Err())
}
}))
ctx, cancel := context.WithCancel(context.Background())
cancel()
// Retrieve that will have its context canceled, should return error, but
// underlying provider retrieve will continue to block in the background.
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
_, err := provider.RetrieveBearerToken(ctx)
if err == nil {
t.Errorf("expect error, got none")
} else if e, a := "unexpected context canceled", err.Error(); strings.Contains(a, e) {
t.Errorf("unexpected context canceled received, %v", err)
} else if e, a := "context canceled", err.Error(); !strings.Contains(a, e) {
t.Errorf("expect %v error in, %v", e, a)
}
}()
<-providerRunning
// Retrieve that will be added to existing single flight group, (or create
// a new group). Returning valid token.
wg.Add(1)
go func() {
defer wg.Done()
token, err := provider.RetrieveBearerToken(context.Background())
if err != nil {
t.Errorf("expect no error, got %v", err)
} else {
expect := Token{Value: "abc123"}
if expect != token {
t.Errorf("expect token retrieve match: %v != %v", expect, token)
}
}
}()
close(providerDone)
wg.Wait()
}
func TestTokenCache_cancelledWithTimeout(t *testing.T) {
providerReady := make(chan struct{})
var providerReadCloseOnce sync.Once
provider := NewTokenCache(TokenProviderFunc(func(ctx context.Context) (Token, error) {
providerReadCloseOnce.Do(func() { close(providerReady) })
<-ctx.Done()
return Token{}, fmt.Errorf("token retrieve timeout, %w", ctx.Err())
}), func(o *TokenCacheOptions) {
o.RetrieveBearerTokenTimeout = time.Millisecond
})
var wg sync.WaitGroup
// Spin up additional retrieves that will be deduplicated and block on the
// original retrieve call.
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
<-providerReady
_, err := provider.RetrieveBearerToken(context.Background())
if err == nil {
t.Errorf("expect error, got none")
} else if e, a := "token retrieve timeout", err.Error(); !strings.Contains(a, e) {
t.Errorf("expect %v error in, %v", e, a)
}
}()
}
_, err := provider.RetrieveBearerToken(context.Background())
if err == nil {
t.Errorf("expect error, got none")
} else if e, a := "token retrieve timeout", err.Error(); !strings.Contains(a, e) {
t.Errorf("expect %v error in, %v", e, a)
}
wg.Wait()
}
func TestTokenCache_asyncRefresh(t *testing.T) {
origTimeNow := timeNow
defer func() { timeNow = origTimeNow }()
timeNow = func() time.Time { return time.Time{} }
expectToken := Token{
Value: "abc123",
CanExpire: true,
Expires: timeNow().Add(10 * time.Minute),
}
refreshedToken := Token{
Value: "refreshed-abc123",
CanExpire: true,
Expires: timeNow().Add(30 * time.Minute),
}
retrievedCount := new(int32)
provider := NewTokenCache(TokenProviderFunc(func(ctx context.Context) (Token, error) {
c := atomic.AddInt32(retrievedCount, 1)
switch {
case c == 1:
return expectToken, nil
case c > 1 && c < 5:
return Token{}, fmt.Errorf("some error")
case c == 5:
return refreshedToken, nil
default:
return Token{}, fmt.Errorf("unexpected error")
}
}), func(o *TokenCacheOptions) {
o.RefreshBeforeExpires = 5 * time.Minute
})
// 1: Initial retrieve to cache token
token, err := provider.RetrieveBearerToken(context.Background())
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if expectToken != token {
t.Errorf("expect token match: %v != %v", expectToken, token)
}
// 2-5: Offset time for subsequent calls to retrieve to trigger asynchronous
// refreshes.
timeNow = func() time.Time {
return (time.Time{}).Add(6 * time.Minute)
}
for i := 0; i < 4; i++ {
token, err := provider.RetrieveBearerToken(context.Background())
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if expectToken != token {
t.Errorf("expect token match: %v != %v", expectToken, token)
}
}
// Wait for all async refreshes to complete
testWaitAsyncRefreshDone(provider)
if c := int(atomic.LoadInt32(retrievedCount)); c < 2 || c > 5 {
t.Fatalf("expect async refresh to be called [2,5) times, got, %v", c)
}
// Ensure enough retrieves have been done to trigger refresh.
if c := atomic.LoadInt32(retrievedCount); c != 5 {
atomic.StoreInt32(retrievedCount, 4)
token, err := provider.RetrieveBearerToken(context.Background())
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if expectToken != token {
t.Errorf("expect token match: %v != %v", expectToken, token)
}
testWaitAsyncRefreshDone(provider)
}
// Last async refresh will succeed and update cached token, expect the next
// call to get refreshed token.
token, err = provider.RetrieveBearerToken(context.Background())
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if refreshedToken != token {
t.Errorf("expect refreshed token match: %v != %v", refreshedToken, token)
}
}
func TestTokenCache_asyncRefreshWithMinDelay(t *testing.T) {
origTimeNow := timeNow
defer func() { timeNow = origTimeNow }()
timeNow = func() time.Time { return time.Time{} }
expectToken := Token{
Value: "abc123",
CanExpire: true,
Expires: timeNow().Add(10 * time.Minute),
}
refreshedToken := Token{
Value: "refreshed-abc123",
CanExpire: true,
Expires: timeNow().Add(30 * time.Minute),
}
retrievedCount := new(int32)
provider := NewTokenCache(TokenProviderFunc(func(ctx context.Context) (Token, error) {
c := atomic.AddInt32(retrievedCount, 1)
switch {
case c == 1:
return expectToken, nil
case c > 1 && c < 5:
return Token{}, fmt.Errorf("some error")
case c == 5:
return refreshedToken, nil
default:
return Token{}, fmt.Errorf("unexpected error")
}
}), func(o *TokenCacheOptions) {
o.RefreshBeforeExpires = 5 * time.Minute
o.AsyncRefreshMinimumDelay = 30 * time.Second
})
// 1: Initial retrieve to cache token
token, err := provider.RetrieveBearerToken(context.Background())
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if expectToken != token {
t.Errorf("expect token match: %v != %v", expectToken, token)
}
// 2-5: Offset time for subsequent calls to retrieve to trigger asynchronous
// refreshes.
timeNow = func() time.Time {
return (time.Time{}).Add(6 * time.Minute)
}
for i := 0; i < 4; i++ {
token, err := provider.RetrieveBearerToken(context.Background())
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if expectToken != token {
t.Errorf("expect token match: %v != %v", expectToken, token)
}
// Wait for all async refreshes to complete ensure not deduped
testWaitAsyncRefreshDone(provider)
}
// Only a single refresh attempt is expected.
if e, a := 2, int(atomic.LoadInt32(retrievedCount)); e != a {
t.Fatalf("expect %v min async refresh, got %v", e, a)
}
// Move time forward to ensure another async refresh is triggered.
timeNow = func() time.Time { return (time.Time{}).Add(7 * time.Minute) }
// Make sure the next attempt refreshes the token
atomic.StoreInt32(retrievedCount, 4)
// Do async retrieve that will succeed refreshing in background.
token, err = provider.RetrieveBearerToken(context.Background())
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if expectToken != token {
t.Errorf("expect token match: %v != %v", expectToken, token)
}
// Wait for all async refreshes to complete ensure not deduped
testWaitAsyncRefreshDone(provider)
// Last async refresh will succeed and update cached token, expect the next
// call to get refreshed token.
token, err = provider.RetrieveBearerToken(context.Background())
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if refreshedToken != token {
t.Errorf("expect refreshed token match: %v != %v", refreshedToken, token)
}
}
func TestTokenCache_disableAsyncRefresh(t *testing.T) {
origTimeNow := timeNow
defer func() { timeNow = origTimeNow }()
timeNow = func() time.Time { return time.Time{} }
expectToken := Token{
Value: "abc123",
CanExpire: true,
Expires: timeNow().Add(10 * time.Minute),
}
refreshedToken := Token{
Value: "refreshed-abc123",
CanExpire: true,
Expires: timeNow().Add(30 * time.Minute),
}
retrievedCount := new(int32)
provider := NewTokenCache(TokenProviderFunc(func(ctx context.Context) (Token, error) {
c := atomic.AddInt32(retrievedCount, 1)
switch {
case c == 1:
return expectToken, nil
case c > 1 && c < 5:
return Token{}, fmt.Errorf("some error")
case c == 5:
return refreshedToken, nil
default:
return Token{}, fmt.Errorf("unexpected error")
}
}), func(o *TokenCacheOptions) {
o.RefreshBeforeExpires = 5 * time.Minute
o.DisableAsyncRefresh = true
})
// 1: Initial retrieve to cache token
token, err := provider.RetrieveBearerToken(context.Background())
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if expectToken != token {
t.Errorf("expect token match: %v != %v", expectToken, token)
}
// Update time into refresh window before token expires
timeNow = func() time.Time {
return (time.Time{}).Add(6 * time.Minute)
}
for i := 0; i < 3; i++ {
_, err = provider.RetrieveBearerToken(context.Background())
if err == nil {
t.Fatalf("expect error, got none")
}
if e, a := "some error", err.Error(); !strings.Contains(a, e) {
t.Fatalf("expect %v error in %v", e, a)
}
if e, a := i+2, int(atomic.LoadInt32(retrievedCount)); e != a {
t.Fatalf("expect %v retrieveCount, got %v", e, a)
}
}
if e, a := 4, int(atomic.LoadInt32(retrievedCount)); e != a {
t.Fatalf("expect %v retrieveCount, got %v", e, a)
}
// Last refresh will succeed and update cached token, expect the next
// call to get refreshed token.
token, err = provider.RetrieveBearerToken(context.Background())
if err != nil {
t.Fatalf("expect no error, got %v", err)
}
if refreshedToken != token {
t.Errorf("expect refreshed token match: %v != %v", refreshedToken, token)
}
}
func testWaitAsyncRefreshDone(provider *TokenCache) {
asyncResCh := provider.sfGroup.DoChan("async-refresh", func() (interface{}, error) {
return nil, nil
})
<-asyncResCh
}
smithy-go-1.23.2/auth/identity.go 0000664 0000000 0000000 00000002412 15102203672 0016635 0 ustar 00root root 0000000 0000000 package auth
import (
"context"
"time"
"github.com/aws/smithy-go"
)
// Identity contains information that identifies who the user making the
// request is.
type Identity interface {
Expiration() time.Time
}
// IdentityResolver defines the interface through which an Identity is
// retrieved.
type IdentityResolver interface {
GetIdentity(context.Context, smithy.Properties) (Identity, error)
}
// IdentityResolverOptions defines the interface through which an entity can be
// queried to retrieve an IdentityResolver for a given auth scheme.
type IdentityResolverOptions interface {
GetIdentityResolver(schemeID string) IdentityResolver
}
// AnonymousIdentity is a sentinel to indicate no identity.
type AnonymousIdentity struct{}
var _ Identity = (*AnonymousIdentity)(nil)
// Expiration returns the zero value for time, as anonymous identity never
// expires.
func (*AnonymousIdentity) Expiration() time.Time {
return time.Time{}
}
// AnonymousIdentityResolver returns AnonymousIdentity.
type AnonymousIdentityResolver struct{}
var _ IdentityResolver = (*AnonymousIdentityResolver)(nil)
// GetIdentity returns AnonymousIdentity.
func (*AnonymousIdentityResolver) GetIdentity(_ context.Context, _ smithy.Properties) (Identity, error) {
return &AnonymousIdentity{}, nil
}
smithy-go-1.23.2/auth/option.go 0000664 0000000 0000000 00000001143 15102203672 0016314 0 ustar 00root root 0000000 0000000 package auth
import "github.com/aws/smithy-go"
type (
authOptionsKey struct{}
)
// Option represents a possible authentication method for an operation.
type Option struct {
SchemeID string
IdentityProperties smithy.Properties
SignerProperties smithy.Properties
}
// GetAuthOptions gets auth Options from Properties.
func GetAuthOptions(p *smithy.Properties) ([]*Option, bool) {
v, ok := p.Get(authOptionsKey{}).([]*Option)
return v, ok
}
// SetAuthOptions sets auth Options on Properties.
func SetAuthOptions(p *smithy.Properties, options []*Option) {
p.Set(authOptionsKey{}, options)
}
smithy-go-1.23.2/auth/option_test.go 0000664 0000000 0000000 00000001055 15102203672 0017355 0 ustar 00root root 0000000 0000000 package auth
import (
"testing"
"reflect"
smithy "github.com/aws/smithy-go"
)
func TestAuthOptions(t *testing.T) {
var ip smithy.Properties
ip.Set("foo", "bar")
var sp smithy.Properties
sp.Set("foo", "bar")
expected := []*Option{
&Option{
SchemeID: "fakeSchemeID",
IdentityProperties: ip,
SignerProperties: sp,
},
}
var m smithy.Properties
SetAuthOptions(&m, expected)
actual, _ := GetAuthOptions(&m)
if !reflect.DeepEqual(expected, actual) {
t.Errorf("Expect AuthOptions to be equivalent %v != %v", expected, actual)
}
} smithy-go-1.23.2/auth/scheme_id.go 0000664 0000000 0000000 00000000633 15102203672 0016727 0 ustar 00root root 0000000 0000000 package auth
// Anonymous
const (
SchemeIDAnonymous = "smithy.api#noAuth"
)
// HTTP auth schemes
const (
SchemeIDHTTPBasic = "smithy.api#httpBasicAuth"
SchemeIDHTTPDigest = "smithy.api#httpDigestAuth"
SchemeIDHTTPBearer = "smithy.api#httpBearerAuth"
SchemeIDHTTPAPIKey = "smithy.api#httpApiKeyAuth"
)
// AWS auth schemes
const (
SchemeIDSigV4 = "aws.auth#sigv4"
SchemeIDSigV4A = "aws.auth#sigv4a"
)
smithy-go-1.23.2/aws-http-auth/ 0000775 0000000 0000000 00000000000 15102203672 0016223 5 ustar 00root root 0000000 0000000 smithy-go-1.23.2/aws-http-auth/CHANGELOG.md 0000664 0000000 0000000 00000000515 15102203672 0020035 0 ustar 00root root 0000000 0000000 # v1.1.1 (2025-10-15)
* **Dependency Update**: Bump minimum go version to 1.23.
# v1.1.0 (2025-09-18)
* **Feature**: Added support for SIG4/SIGV4A querystring authentication.
# v1.0.0 (2024-09-25)
* **Release**: Initial release of module aws-http-auth, which implements generically consumable SigV4 and SigV4a request signing.
smithy-go-1.23.2/aws-http-auth/credentials/ 0000775 0000000 0000000 00000000000 15102203672 0020520 5 ustar 00root root 0000000 0000000 smithy-go-1.23.2/aws-http-auth/credentials/credentials.go 0000664 0000000 0000000 00000000456 15102203672 0023351 0 ustar 00root root 0000000 0000000 // Package credentials exposes container types for AWS credentials.
package credentials
import (
"time"
)
// Credentials describes a shared-secret AWS credential identity.
type Credentials struct {
AccessKeyID string
SecretAccessKey string
SessionToken string
Expires time.Time
}
smithy-go-1.23.2/aws-http-auth/go.mod 0000664 0000000 0000000 00000000067 15102203672 0017334 0 ustar 00root root 0000000 0000000 module github.com/aws/smithy-go/aws-http-auth
go 1.23
smithy-go-1.23.2/aws-http-auth/go.sum 0000664 0000000 0000000 00000000000 15102203672 0017344 0 ustar 00root root 0000000 0000000 smithy-go-1.23.2/aws-http-auth/internal/ 0000775 0000000 0000000 00000000000 15102203672 0020037 5 ustar 00root root 0000000 0000000 smithy-go-1.23.2/aws-http-auth/internal/v4/ 0000775 0000000 0000000 00000000000 15102203672 0020370 5 ustar 00root root 0000000 0000000 smithy-go-1.23.2/aws-http-auth/internal/v4/signer.go 0000664 0000000 0000000 00000015543 15102203672 0022216 0 ustar 00root root 0000000 0000000 package v4
import (
"encoding/hex"
"fmt"
"io"
"net/http"
"sort"
"strings"
"time"
"github.com/aws/smithy-go/aws-http-auth/credentials"
v4 "github.com/aws/smithy-go/aws-http-auth/v4"
)
const (
// TimeFormat is the full-width form to be used in the X-Amz-Date header.
TimeFormat = "20060102T150405Z"
// ShortTimeFormat is the shortened form used in credential scope.
ShortTimeFormat = "20060102"
)
// Signer is the implementation structure for all variants of v4 signing.
type Signer struct {
Request *http.Request
PayloadHash []byte
Time time.Time
Credentials credentials.Credentials
Options v4.SignerOptions
// variant-specific inputs
Algorithm string
CredentialScope string
Finalizer Finalizer
SignatureType v4.SignatureType
}
// Finalizer performs the final step in v4 signing, deriving a signature for
// the string-to-sign with algorithm-specific key material.
type Finalizer interface {
SignString(string) (string, error)
}
// Do performs v4 signing, modifying the request in-place with the
// signature.
//
// Do should be called exactly once for a configured Signer. The behavior of
// doing otherwise is undefined.
func (s *Signer) Do() error {
if err := s.init(); err != nil {
return err
}
s.setRequiredHeaders()
// Build canonical headers first to get signedHeaders
canonHeaders, signedHeaders := s.buildCanonicalHeaders()
if s.SignatureType == v4.SignatureTypeQueryString {
s.addSignatureParametersToQuery(signedHeaders)
}
canonicalRequest := s.buildCanonicalRequestWithHeaders(canonHeaders, signedHeaders)
stringToSign := s.buildStringToSign(canonicalRequest)
signature, err := s.Finalizer.SignString(stringToSign)
if err != nil {
return err
}
if s.SignatureType == v4.SignatureTypeQueryString {
s.addSignatureToQuery(signature, signedHeaders)
} else {
s.Request.Header.Set("Authorization",
s.buildAuthorizationHeader(signature, signedHeaders))
}
return nil
}
func (s *Signer) init() error {
// it might seem like time should also get defaulted/normalized here, but
// in practice sigv4 and sigv4a both need to do that beforehand to
// calculate scope, so there's no point
if s.Options.HeaderRules == nil {
s.Options.HeaderRules = defaultHeaderRules{}
}
if err := s.resolvePayloadHash(); err != nil {
return err
}
return nil
}
// ensure we have a value for payload hash, whether that be explicit, implicit,
// or the unsigned sentinel
func (s *Signer) resolvePayloadHash() error {
if len(s.PayloadHash) > 0 {
return nil
}
rs, ok := s.Request.Body.(io.ReadSeeker)
if !ok || s.Options.DisableImplicitPayloadHashing {
s.PayloadHash = v4.UnsignedPayload()
return nil
}
p, err := rtosha(rs)
if err != nil {
return err
}
s.PayloadHash = p
return nil
}
func (s *Signer) setRequiredHeaders() {
headers := s.Request.Header
s.Request.Header.Set("Host", s.Request.Host)
// X-Amz-Date and X-Amz-Security-Token are only set as headers when using a header signature type
if s.SignatureType == v4.SignatureTypeHeader {
s.Request.Header.Set("X-Amz-Date", s.Time.Format(TimeFormat))
if len(s.Credentials.SessionToken) > 0 {
s.Request.Header.Set("X-Amz-Security-Token", s.Credentials.SessionToken)
}
} else {
// For query string auth, ensure these headers are not present
s.Request.Header.Del("X-Amz-Date")
s.Request.Header.Del("X-Amz-Security-Token")
}
if len(s.PayloadHash) > 0 && s.Options.AddPayloadHashHeader {
headers.Set("X-Amz-Content-Sha256", payloadHashString(s.PayloadHash))
}
}
func (s *Signer) buildCanonicalRequest() (string, string) {
canonHeaders, signedHeaders := s.buildCanonicalHeaders()
canonicalRequest := s.buildCanonicalRequestWithHeaders(canonHeaders, signedHeaders)
return canonicalRequest, signedHeaders
}
func (s *Signer) buildCanonicalRequestWithHeaders(canonHeaders, signedHeaders string) string {
canonPath := s.Request.URL.EscapedPath()
if len(canonPath) == 0 {
canonPath = "/"
}
if !s.Options.DisableDoublePathEscape {
canonPath = uriEncode(canonPath)
}
query := s.Request.URL.Query()
for key := range query {
sort.Strings(query[key])
}
canonQuery := strings.Replace(query.Encode(), "+", "%20", -1)
req := strings.Join([]string{
s.Request.Method,
canonPath,
canonQuery,
canonHeaders,
signedHeaders,
payloadHashString(s.PayloadHash),
}, "\n")
return req
}
func (s *Signer) buildCanonicalHeaders() (canon, signed string) {
var canonHeaders []string
signedHeaders := map[string][]string{}
// step 1: find what we're signing
for header, values := range s.Request.Header {
lowercase := strings.ToLower(header)
if !s.Options.HeaderRules.IsSigned(lowercase) {
continue
}
canonHeaders = append(canonHeaders, lowercase)
signedHeaders[lowercase] = values
}
sort.Strings(canonHeaders)
// step 2: indexing off of the list we built previously (which guarantees
// alphabetical order), build the canonical list
var ch strings.Builder
for i := range canonHeaders {
ch.WriteString(canonHeaders[i])
ch.WriteRune(':')
// headers can have multiple values
values := signedHeaders[canonHeaders[i]]
for j, value := range values {
ch.WriteString(strings.TrimSpace(value))
if j < len(values)-1 {
ch.WriteRune(',')
}
}
ch.WriteRune('\n')
}
return ch.String(), strings.Join(canonHeaders, ";")
}
func (s *Signer) buildStringToSign(canonicalRequest string) string {
return strings.Join([]string{
s.Algorithm,
s.Time.Format(TimeFormat),
s.CredentialScope,
hex.EncodeToString(Stosha(canonicalRequest)),
}, "\n")
}
func (s *Signer) buildAuthorizationHeader(signature, headers string) string {
return fmt.Sprintf("%s Credential=%s, SignedHeaders=%s, Signature=%s",
s.Algorithm,
s.Credentials.AccessKeyID+"/"+s.CredentialScope,
headers,
signature)
}
func (s *Signer) addSignatureParametersToQuery(signedHeaders string) {
query := s.Request.URL.Query()
query.Set("X-Amz-Algorithm", s.Algorithm)
query.Set("X-Amz-Credential", s.Credentials.AccessKeyID+"/"+s.CredentialScope)
query.Set("X-Amz-Date", s.Time.Format(TimeFormat))
query.Set("X-Amz-SignedHeaders", signedHeaders)
if len(s.Credentials.SessionToken) > 0 {
query.Set("X-Amz-Security-Token", s.Credentials.SessionToken)
}
s.Request.URL.RawQuery = query.Encode()
}
func (s *Signer) addSignatureToQuery(signature, signedHeaders string) {
query := s.Request.URL.Query()
query.Set("X-Amz-SignedHeaders", signedHeaders)
query.Set("X-Amz-Signature", signature)
s.Request.URL.RawQuery = query.Encode()
}
func payloadHashString(p []byte) string {
if string(p) == "UNSIGNED-PAYLOAD" {
return string(p) // sentinel, do not hex-encode
}
return hex.EncodeToString(p)
}
// ResolveTime initializes a time value for signing.
func ResolveTime(t time.Time) time.Time {
if t.IsZero() {
return time.Now().UTC()
}
return t.UTC()
}
type defaultHeaderRules struct{}
func (defaultHeaderRules) IsSigned(h string) bool {
return h == "host" || strings.HasPrefix(h, "x-amz-")
}
smithy-go-1.23.2/aws-http-auth/internal/v4/signer_test.go 0000664 0000000 0000000 00000031356 15102203672 0023255 0 ustar 00root root 0000000 0000000 package v4
import (
"fmt"
"io"
"net/http"
"strings"
"testing"
"time"
"github.com/aws/smithy-go/aws-http-auth/credentials"
v4 "github.com/aws/smithy-go/aws-http-auth/v4"
)
// Tests herein are meant to verify individual components of the v4 signer
// implementation and should generally not be calling Do() directly.
//
// The full algorithm contained in Do() is covered by tests for the
// Sigv4/Sigv4a APIs.
func seekable(v string) io.ReadSeekCloser {
return readseekcloser{strings.NewReader(v)}
}
type readseekcloser struct {
io.ReadSeeker
}
func (readseekcloser) Close() error { return nil }
type identityFinalizer struct{}
func (identityFinalizer) SignString(v string) (string, error) {
return v, nil
}
func TestQueryStringAuth_IncludesSignedHeadersInCanonicalRequest(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, "https://service.region.amazonaws.com/path", nil)
if err != nil {
t.Fatal(err)
}
req.URL.RawQuery = "existing=param"
s := &Signer{
Request: req,
PayloadHash: []byte("UNSIGNED-PAYLOAD"),
Time: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
Credentials: credentials.Credentials{AccessKeyID: "AKID", SecretAccessKey: "SECRET"},
Algorithm: "AWS4-HMAC-SHA256",
CredentialScope: "20240101/us-east-1/service/aws4_request",
Finalizer: identityFinalizer{},
SignatureType: v4.SignatureTypeQueryString,
}
err = s.Do()
if err != nil {
t.Fatal(err)
}
query := req.URL.Query()
if !query.Has("X-Amz-SignedHeaders") {
t.Error("X-Amz-SignedHeaders missing from query string")
}
if !query.Has("X-Amz-Algorithm") {
t.Error("X-Amz-Algorithm missing from query string")
}
if !query.Has("X-Amz-Credential") {
t.Error("X-Amz-Credential missing from query string")
}
if !query.Has("X-Amz-Date") {
t.Error("X-Amz-Date missing from query string")
}
if !query.Has("X-Amz-Signature") {
t.Error("X-Amz-Signature missing from query string")
}
if !query.Has("existing") {
t.Error("existing query parameter should be preserved")
}
}
func TestQueryStringAuth_WithSessionToken(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, "https://service.region.amazonaws.com/path", nil)
if err != nil {
t.Fatal(err)
}
s := &Signer{
Request: req,
PayloadHash: []byte("UNSIGNED-PAYLOAD"),
Time: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
Credentials: credentials.Credentials{AccessKeyID: "AKID", SecretAccessKey: "SECRET", SessionToken: "TOKEN123"},
Algorithm: "AWS4-HMAC-SHA256",
CredentialScope: "20240101/us-east-1/service/aws4_request",
Finalizer: identityFinalizer{},
SignatureType: v4.SignatureTypeQueryString,
}
err = s.Do()
if err != nil {
t.Fatal(err)
}
query := req.URL.Query()
if !query.Has("X-Amz-Security-Token") {
t.Error("X-Amz-Security-Token missing from query string")
}
if query.Get("X-Amz-Security-Token") != "TOKEN123" {
t.Errorf("X-Amz-Security-Token = %q, want %q", query.Get("X-Amz-Security-Token"), "TOKEN123")
}
}
func TestQueryStringAuth_WithRegionSet(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, "https://service.region.amazonaws.com/path", nil)
if err != nil {
t.Fatal(err)
}
// Pre-populate X-Amz-Region-Set as query parameter (simulating SigV4a)
query := req.URL.Query()
query.Set("X-Amz-Region-Set", "us-east-1,us-west-2")
req.URL.RawQuery = query.Encode()
s := &Signer{
Request: req,
PayloadHash: []byte("UNSIGNED-PAYLOAD"),
Time: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
Credentials: credentials.Credentials{AccessKeyID: "AKID", SecretAccessKey: "SECRET"},
Algorithm: "AWS4-ECDSA-P256-SHA256",
CredentialScope: "20240101/service/aws4_request",
Finalizer: identityFinalizer{},
SignatureType: v4.SignatureTypeQueryString,
}
err = s.Do()
if err != nil {
t.Fatal(err)
}
finalQuery := req.URL.Query()
if !finalQuery.Has("X-Amz-Region-Set") {
t.Error("X-Amz-Region-Set missing from final query string")
}
if finalQuery.Get("X-Amz-Region-Set") != "us-east-1,us-west-2" {
t.Errorf("X-Amz-Region-Set = %q, want %q", finalQuery.Get("X-Amz-Region-Set"), "us-east-1,us-west-2")
}
}
func TestBuildCanonicalRequest_SignedPayload(t *testing.T) {
req, err := http.NewRequest(http.MethodPost,
"https://service.region.amazonaws.com",
seekable("{}"))
if err != nil {
t.Fatal(err)
}
req.URL.Path = "/path1/path 2"
req.URL.RawQuery = "a=b"
req.Header.Set("Host", "service.region.amazonaws.com")
req.Header.Set("X-Amz-Foo", "\t \tbar ")
s := &Signer{
Request: req,
PayloadHash: Stosha("{}"),
Options: v4.SignerOptions{
HeaderRules: defaultHeaderRules{},
},
}
expect := `POST
/path1/path%25202
a=b
host:service.region.amazonaws.com
x-amz-foo:bar
host;x-amz-foo
44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a`
actual, _ := s.buildCanonicalRequest()
if expect != actual {
t.Errorf("canonical request\n%s\n!=\n%s", expect, actual)
}
}
func TestBuildCanonicalRequest_NoPath(t *testing.T) {
req, err := http.NewRequest(http.MethodPost,
"https://service.region.amazonaws.com",
seekable("{}"))
if err != nil {
t.Fatal(err)
}
req.URL.Path = ""
req.URL.RawQuery = "a=b"
req.Header.Set("Host", "service.region.amazonaws.com")
req.Header.Set("X-Amz-Foo", "\t \tbar ")
s := &Signer{
Request: req,
PayloadHash: Stosha("{}"),
Options: v4.SignerOptions{
HeaderRules: defaultHeaderRules{},
},
}
expect := `POST
/
a=b
host:service.region.amazonaws.com
x-amz-foo:bar
host;x-amz-foo
44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a`
actual, _ := s.buildCanonicalRequest()
if expect != actual {
t.Errorf("canonical request\n%s\n!=\n%s", expect, actual)
}
}
func TestBuildCanonicalRequest_DoubleHeader(t *testing.T) {
req, err := http.NewRequest(http.MethodPost,
"https://service.region.amazonaws.com",
seekable("{}"))
if err != nil {
t.Fatal(err)
}
req.URL.Path = "/"
req.Header.Set("X-Amz-Foo", "\t \tbar ")
req.Header.Set("Host", "service.region.amazonaws.com")
req.Header.Set("dontsignit", "dontsignit") // should be skipped
req.Header.Add("X-Amz-Foo", "\t \tbaz ")
s := &Signer{
Request: req,
PayloadHash: Stosha("{}"),
Options: v4.SignerOptions{
HeaderRules: defaultHeaderRules{},
},
}
expect := `POST
/
host:service.region.amazonaws.com
x-amz-foo:bar,baz
host;x-amz-foo
44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a`
actual, _ := s.buildCanonicalRequest()
if expect != actual {
t.Errorf("canonical request\n%s\n!=\n%s", expect, actual)
}
}
func TestBuildCanonicalRequest_SortQuery(t *testing.T) {
req, err := http.NewRequest(http.MethodPost,
"https://service.region.amazonaws.com",
seekable("{}"))
if err != nil {
t.Fatal(err)
}
req.URL.Path = "/"
req.URL.RawQuery = "a=b&%20b=c"
req.Header.Set("Host", "service.region.amazonaws.com")
s := &Signer{
Request: req,
PayloadHash: v4.UnsignedPayload(),
Options: v4.SignerOptions{
HeaderRules: defaultHeaderRules{},
},
}
expect := `POST
/
%20b=c&a=b
host:service.region.amazonaws.com
host
UNSIGNED-PAYLOAD`
actual, _ := s.buildCanonicalRequest()
if expect != actual {
t.Errorf("canonical request\n%s\n!=\n%s", expect, actual)
}
}
func TestBuildCanonicalRequest_EmptyQuery(t *testing.T) {
req, err := http.NewRequest(http.MethodPost,
"https://service.region.amazonaws.com",
seekable("{}"))
if err != nil {
t.Fatal(err)
}
req.URL.Path = "/"
req.URL.RawQuery = "foo"
req.Header.Set("Host", "service.region.amazonaws.com")
s := &Signer{
Request: req,
PayloadHash: v4.UnsignedPayload(),
Options: v4.SignerOptions{
HeaderRules: defaultHeaderRules{},
},
}
expect := `POST
/
foo=
host:service.region.amazonaws.com
host
UNSIGNED-PAYLOAD`
actual, _ := s.buildCanonicalRequest()
if expect != actual {
t.Errorf("canonical request\n%s\n!=\n%s", expect, actual)
}
}
func TestBuildCanonicalRequest_UnsignedPayload(t *testing.T) {
req, err := http.NewRequest(http.MethodPost,
"https://service.region.amazonaws.com",
seekable("{}"))
if err != nil {
t.Fatal(err)
}
req.URL.Path = "/path1/path 2"
req.URL.RawQuery = "a=b"
req.Header.Set("Host", "service.region.amazonaws.com")
req.Header.Set("X-Amz-Foo", "\t \tbar ")
s := &Signer{
Request: req,
PayloadHash: []byte("UNSIGNED-PAYLOAD"),
Options: v4.SignerOptions{
HeaderRules: defaultHeaderRules{},
},
}
expect := `POST
/path1/path%25202
a=b
host:service.region.amazonaws.com
x-amz-foo:bar
host;x-amz-foo
UNSIGNED-PAYLOAD`
actual, _ := s.buildCanonicalRequest()
if expect != actual {
t.Errorf("canonical request\n%s\n!=\n%s", expect, actual)
}
}
func TestBuildCanonicalRequest_DisableDoubleEscape(t *testing.T) {
req, err := http.NewRequest(http.MethodPost,
"https://service.region.amazonaws.com",
seekable("{}"))
if err != nil {
t.Fatal(err)
}
req.URL.Path = "/path1/path 2"
req.URL.RawQuery = "a=b"
req.Header.Set("Host", "service.region.amazonaws.com")
req.Header.Set("X-Amz-Foo", "\t \tbar ")
s := &Signer{
Request: req,
PayloadHash: []byte("UNSIGNED-PAYLOAD"),
Options: v4.SignerOptions{
HeaderRules: defaultHeaderRules{},
DisableDoublePathEscape: true,
},
}
expect := `POST
/path1/path%202
a=b
host:service.region.amazonaws.com
x-amz-foo:bar
host;x-amz-foo
UNSIGNED-PAYLOAD`
actual, _ := s.buildCanonicalRequest()
if expect != actual {
t.Errorf("canonical request\n%s\n!=\n%s", expect, actual)
}
}
func TestResolvePayloadHash_AlreadySet(t *testing.T) {
expect := "already set"
s := &Signer{
PayloadHash: []byte(expect),
}
err := s.resolvePayloadHash()
if err != nil {
t.Fatalf("expect no err, got %v", err)
}
if expect != string(s.PayloadHash) {
t.Fatalf("hash %q != %q", expect, s.PayloadHash)
}
}
func TestResolvePayloadHash_Disabled(t *testing.T) {
expect := "UNSIGNED-PAYLOAD"
s := &Signer{
Request: &http.Request{Body: seekable("foo")},
Options: v4.SignerOptions{
DisableImplicitPayloadHashing: true,
},
}
err := s.resolvePayloadHash()
if err != nil {
t.Fatalf("expect no err, got %v", err)
}
if expect != string(s.PayloadHash) {
t.Fatalf("hash %q != %q", expect, s.PayloadHash)
}
}
type seekexploder struct {
io.ReadCloser
}
func (seekexploder) Seek(int64, int) (int64, error) {
return 0, fmt.Errorf("boom")
}
func TestResolvePayloadHash_SeekBlowsUp(t *testing.T) {
s := &Signer{
Request: &http.Request{
Body: seekexploder{seekable("foo")},
},
}
err := s.resolvePayloadHash()
if err == nil {
t.Fatalf("expect err, got none")
}
}
func TestResolvePayloadHash_OK(t *testing.T) {
expect := string(Stosha("foo"))
s := &Signer{
Request: &http.Request{Body: seekable("foo")},
}
err := s.resolvePayloadHash()
if err != nil {
t.Fatalf("expect no err, got %v", err)
}
if expect != string(s.PayloadHash) {
t.Fatalf("hash %q != %q", expect, s.PayloadHash)
}
}
func TestSetRequiredHeaders_All(t *testing.T) {
s := &Signer{
Request: &http.Request{
Host: "foo.service.com",
Header: http.Header{},
},
PayloadHash: []byte{0, 1, 2, 3},
Time: time.Unix(0, 0).UTC(),
Credentials: credentials.Credentials{
SessionToken: "session_token",
},
Options: v4.SignerOptions{
AddPayloadHashHeader: true,
},
}
s.setRequiredHeaders()
if actual := s.Request.Header.Get("Host"); s.Request.Host != actual {
t.Errorf("region header %q != %q", s.Request.Host, actual)
}
if expect, actual := "19700101T000000Z", s.Request.Header.Get("X-Amz-Date"); expect != actual {
t.Errorf("date header %q != %q", expect, actual)
}
if expect, actual := "session_token", s.Request.Header.Get("X-Amz-Security-Token"); expect != actual {
t.Errorf("token header %q != %q", expect, actual)
}
if expect, actual := "00010203", s.Request.Header.Get("X-Amz-Content-Sha256"); expect != actual {
t.Errorf("sha256 header %q != %q", expect, actual)
}
}
func TestSetRequiredHeaders_UnsignedPayload(t *testing.T) {
s := &Signer{
Request: &http.Request{
Host: "foo.service.com",
Header: http.Header{},
},
PayloadHash: []byte("UNSIGNED-PAYLOAD"),
Time: time.Unix(0, 0).UTC(),
Credentials: credentials.Credentials{},
Options: v4.SignerOptions{
AddPayloadHashHeader: true,
},
}
s.setRequiredHeaders()
if actual := s.Request.Header.Get("Host"); s.Request.Host != actual {
t.Errorf("region header %q != %q", s.Request.Host, actual)
}
if expect, actual := "19700101T000000Z", s.Request.Header.Get("X-Amz-Date"); expect != actual {
t.Errorf("date header %q != %q", expect, actual)
}
if expect, actual := "", s.Request.Header.Get("X-Amz-Security-Token"); expect != actual {
t.Errorf("token header %q != %q", expect, actual)
}
if expect, actual := "UNSIGNED-PAYLOAD", s.Request.Header.Get("X-Amz-Content-Sha256"); expect != actual {
t.Errorf("sha256 header %q != %q", expect, actual)
}
}
smithy-go-1.23.2/aws-http-auth/internal/v4/strings.go 0000664 0000000 0000000 00000002141 15102203672 0022406 0 ustar 00root root 0000000 0000000 package v4
import (
"bytes"
"crypto/sha256"
"fmt"
"io"
)
var noEscape [256]bool
func init() {
for i := 0; i < len(noEscape); i++ {
// AWS expects every character except these to be escaped
noEscape[i] = (i >= 'A' && i <= 'Z') ||
(i >= 'a' && i <= 'z') ||
(i >= '0' && i <= '9') ||
i == '-' ||
i == '.' ||
i == '_' ||
i == '~' ||
i == '/'
}
}
// uriEncode implements "Amazon-style" URL escaping.
func uriEncode(path string) string {
var buf bytes.Buffer
for i := 0; i < len(path); i++ {
c := path[i]
if noEscape[c] {
buf.WriteByte(c)
} else {
fmt.Fprintf(&buf, "%%%02X", c)
}
}
return buf.String()
}
// rtosha computes the sha256 hash of the input Reader and rewinds it before
// returning.
func rtosha(r io.ReadSeeker) ([]byte, error) {
h := sha256.New()
if _, err := io.Copy(h, r); err != nil {
return nil, err
}
if _, err := r.Seek(0, io.SeekStart); err != nil {
return nil, err
}
return h.Sum(nil), nil
}
// Stosha computes the sha256 hash of the given string.
func Stosha(s string) []byte {
h := sha256.New()
h.Write([]byte(s))
return h.Sum(nil)
}
smithy-go-1.23.2/aws-http-auth/sigv4/ 0000775 0000000 0000000 00000000000 15102203672 0017257 5 ustar 00root root 0000000 0000000 smithy-go-1.23.2/aws-http-auth/sigv4/e2e_test.go 0000664 0000000 0000000 00000007242 15102203672 0021325 0 ustar 00root root 0000000 0000000 //go:build e2e
// +build e2e
package sigv4
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"math/rand"
"net/http"
"os"
"testing"
"github.com/aws/smithy-go/aws-http-auth/credentials"
)
type closer struct{ io.ReadSeeker }
func (closer) Close() error { return nil }
type SQSClient struct {
Region string
Credentials credentials.Credentials
HTTPClient *http.Client
Signer *Signer
}
// all of these method definitions are very repetitive, it would be useful if
// there was some sort of API model we could generate code from...
func (c *SQSClient) CreateQueue(ctx context.Context, in *CreateQueueInput) (*CreateQueueOutput, error) {
var out CreateQueueOutput
if err := c.do(ctx, "CreateQueue", in, &out); err != nil {
return nil, err
}
return &out, nil
}
type CreateQueueInput struct {
QueueName string `json:"QueueName,omitempty"` // This member is required.
Attributes map[string]string `json:"Attributes,omitempty"`
Tags map[string]string `json:"Tags,omitempty"`
}
type CreateQueueOutput struct {
QueueURL string `json:"QueueUrl"`
}
func (c *SQSClient) DeleteQueue(ctx context.Context, in *DeleteQueueInput) (*DeleteQueueOutput, error) {
var out DeleteQueueOutput
if err := c.do(ctx, "DeleteQueue", in, &out); err != nil {
return nil, err
}
return &out, nil
}
type DeleteQueueInput struct {
QueueURL string `json:"QueueUrl,omitempty"` // This member is required.
}
type DeleteQueueOutput struct{}
func (c *SQSClient) do(ctx context.Context, target string, in, out any) error {
// init (featuring budget resolve endpoint)
endpt := fmt.Sprintf("https://sqs.%s.amazonaws.com", c.Region)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpt, http.NoBody)
if err != nil {
return fmt.Errorf("new http request: %w", err)
}
// serialize
req.URL.Path = "/"
req.Header.Set("X-Amz-Target", fmt.Sprintf("AmazonSQS.%s", target))
req.Header.Set("Content-Type", "application/x-amz-json-1.0")
payload, err := json.Marshal(in)
if err != nil {
return fmt.Errorf("serialize request: %w", err)
}
req.Body = closer{bytes.NewReader(payload)}
req.ContentLength = int64(len(payload))
// sign
err = c.Signer.SignRequest(&SignRequestInput{
Request: req,
Credentials: c.Credentials,
Service: "sqs",
Region: c.Region,
})
if err != nil {
return fmt.Errorf("sign request: %w", err)
}
// round-trip
resp, err := c.HTTPClient.Do(req)
if err != nil {
return fmt.Errorf("do request: %w", err)
}
defer resp.Body.Close()
// deserialize
data, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read response body: %w", err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("request error: %s: %s", resp.Status, data)
}
if len(data) == 0 {
return nil
}
if err := json.Unmarshal(data, out); err != nil {
return fmt.Errorf("deserialize response: %w", err)
}
return nil
}
func TestE2E_SQS(t *testing.T) {
svc := &SQSClient{
Region: "us-east-1",
Credentials: credentials.Credentials{
AccessKeyID: os.Getenv("AWS_ACCESS_KEY_ID"),
SecretAccessKey: os.Getenv("AWS_SECRET_ACCESS_KEY"),
SessionToken: os.Getenv("AWS_SESSION_TOKEN"),
},
HTTPClient: http.DefaultClient,
Signer: New(),
}
queueName := fmt.Sprintf("aws-http-auth-e2etest-%d", rand.Int()%(2<<15))
out, err := svc.CreateQueue(context.Background(), &CreateQueueInput{
QueueName: queueName,
})
if err != nil {
t.Fatalf("create queue: %v", err)
}
queueURL := out.QueueURL
t.Logf("created test queue %s", queueURL)
_, err = svc.DeleteQueue(context.Background(), &DeleteQueueInput{
QueueURL: queueURL,
})
if err != nil {
t.Fatalf("delete queue: %v", err)
}
t.Log("deleted test queue")
}
smithy-go-1.23.2/aws-http-auth/sigv4/sigv4.go 0000664 0000000 0000000 00000012225 15102203672 0020644 0 ustar 00root root 0000000 0000000 // Package sigv4 implements request signing for the basic form AWS Signature
// Version 4.
package sigv4
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"net/http"
"strings"
"time"
"github.com/aws/smithy-go/aws-http-auth/credentials"
v4internal "github.com/aws/smithy-go/aws-http-auth/internal/v4"
v4 "github.com/aws/smithy-go/aws-http-auth/v4"
)
const algorithm = "AWS4-HMAC-SHA256"
// Signer signs requests with AWS Signature version 4.
type Signer struct {
options v4.SignerOptions
}
// New returns an instance of Signer with applied options.
func New(opts ...v4.SignerOption) *Signer {
options := v4.SignerOptions{}
for _, opt := range opts {
opt(&options)
}
return &Signer{options}
}
// SignRequestInput is the set of inputs for Sigv4 signing.
type SignRequestInput struct {
// The input request, which will modified in-place during signing.
Request *http.Request
// The SHA256 hash of the input request body.
//
// This value is NOT required to sign the request, but it is recommended to
// provide it (or provide a Body on the HTTP request that implements
// io.Seeker such that the signer can calculate it for you). Many services
// do not accept requests with unsigned payloads.
//
// If a value is not provided, and DisableImplicitPayloadHashing has not
// been set on SignerOptions, the signer will attempt to derive the payload
// hash itself. The request's Body MUST implement io.Seeker in order to do
// this, if it does not, the magic value for unsigned payload is used. If
// the body does implement io.Seeker, but a call to Seek returns an error,
// the signer will forward that error.
PayloadHash []byte
// The identity used to sign the request.
Credentials credentials.Credentials
// The service and region for which this request is to be signed.
//
// The appropriate values for these fields are determined by the service
// vendor.
Service, Region string
// Wall-clock time used for calculating the signature.
//
// If the zero-value is given (generally by the caller not setting it), the
// signer will instead use the current system clock time for the signature.
Time time.Time
// How the signature is transmitted (header or query string).
SignatureType v4.SignatureType
}
// SignRequest signs an HTTP request with AWS Signature Version 4, modifying
// the request in-place by adding the headers that constitute the signature.
//
// SignRequest will modify the request by setting the following headers:
// - Host: required in general for HTTP/1.1 as well as for v4-signed requests
// - X-Amz-Date: required for v4-signed requests
// - X-Amz-Security-Token: required for v4-signed requests IF present on
// credentials used to sign, otherwise this header will not be set
// - Authorization: contains the v4 signature string
//
// The request MUST have a Host value set at the time that this API is called,
// such that it can be included in the signature calculation. Standard library
// HTTP clients set this as a request header by default, meaning that a request
// signed without a Host value will end up transmitting with the Host header
// anyway, which will cause the request to be rejected by the service due to
// signature mismatch (the Host header is required to be signed with Sigv4).
//
// Generally speaking, using http.NewRequest will ensure that request instances
// are sufficiently initialized to be used with this API, though it is not
// strictly required.
//
// SignRequest may be called any number of times on an http.Request instance,
// the header values set as part of the signature will simply be overwritten
// with newer or re-calculated values (such as a new set of credentials with a
// new session token, which would in turn result in a different signature).
func (s *Signer) SignRequest(in *SignRequestInput, opts ...v4.SignerOption) error {
options := s.options
for _, opt := range opts {
opt(&options)
}
tm := v4internal.ResolveTime(in.Time)
signer := v4internal.Signer{
Request: in.Request,
PayloadHash: in.PayloadHash,
Time: tm,
Credentials: in.Credentials,
Options: options,
Algorithm: algorithm,
CredentialScope: scope(tm, in.Region, in.Service),
SignatureType: in.SignatureType,
Finalizer: &finalizer{
Secret: in.Credentials.SecretAccessKey,
Service: in.Service,
Region: in.Region,
Time: tm,
},
}
if err := signer.Do(); err != nil {
return err
}
return nil
}
func scope(signingTime time.Time, region, service string) string {
return strings.Join([]string{
signingTime.Format(v4internal.ShortTimeFormat),
region,
service,
"aws4_request",
}, "/")
}
type finalizer struct {
Secret string
Service, Region string
Time time.Time
}
func (f *finalizer) SignString(toSign string) (string, error) {
key := hmacSHA256([]byte("AWS4"+f.Secret), []byte(f.Time.Format(v4internal.ShortTimeFormat)))
key = hmacSHA256(key, []byte(f.Region))
key = hmacSHA256(key, []byte(f.Service))
key = hmacSHA256(key, []byte("aws4_request"))
return hex.EncodeToString(hmacSHA256(key, []byte(toSign))), nil
}
func hmacSHA256(key, data []byte) []byte {
hash := hmac.New(sha256.New, key)
hash.Write(data)
return hash.Sum(nil)
}
smithy-go-1.23.2/aws-http-auth/sigv4/sigv4_test.go 0000664 0000000 0000000 00000026771 15102203672 0021716 0 ustar 00root root 0000000 0000000 package sigv4
import (
"io"
"net/http"
"strings"
"testing"
"time"
"github.com/aws/smithy-go/aws-http-auth/credentials"
v4internal "github.com/aws/smithy-go/aws-http-auth/internal/v4"
v4 "github.com/aws/smithy-go/aws-http-auth/v4"
)
var credsSession = credentials.Credentials{
AccessKeyID: "AKID",
SecretAccessKey: "SECRET",
SessionToken: "SESSION",
}
var credsNoSession = credentials.Credentials{
AccessKeyID: "AKID",
SecretAccessKey: "SECRET",
}
type signAll struct{}
func (signAll) IsSigned(string) bool { return true }
func seekable(v string) io.ReadSeekCloser {
return readseekcloser{strings.NewReader(v)}
}
func nonseekable(v string) io.ReadCloser {
return io.NopCloser(strings.NewReader(v)) // io.NopCloser elides Seek()
}
type readseekcloser struct {
io.ReadSeeker
}
func (readseekcloser) Close() error { return nil }
func newRequest(body io.ReadCloser, opts ...func(*http.Request)) *http.Request {
// we initialize via NewRequest because it sets basic things like host and
// proto and is generally how we recommend the signing APIs are used
//
// the url doesn't actually need to match the signing name / region
req, err := http.NewRequest(http.MethodPost, "https://service.region.amazonaws.com", body)
if err != nil {
panic(err)
}
for _, opt := range opts {
opt(req)
}
return req
}
func expectSignature(t *testing.T, signed *http.Request, expectSignature, expectDate, expectToken string) {
if actual := signed.Header.Get("Authorization"); expectSignature != actual {
t.Errorf("expect signature:\n%s\n!=\n%s", expectSignature, actual)
}
if actual := signed.Header.Get("X-Amz-Date"); expectDate != actual {
t.Errorf("expect date: %s != %s", expectDate, actual)
}
if actual := signed.Header.Get("X-Amz-Security-Token"); expectToken != actual {
t.Errorf("expect token: %s != %s", expectToken, actual)
}
}
func TestSignRequest(t *testing.T) {
for name, tt := range map[string]struct {
Input *SignRequestInput
Opts v4.SignerOption
ExpectSignature string
ExpectDate string
ExpectToken string
}{
"minimal case, nonseekable": {
Input: &SignRequestInput{
Request: newRequest(nonseekable("{}")),
Credentials: credsSession,
Service: "dynamodb",
Region: "us-east-1",
Time: time.Unix(0, 0),
},
ExpectSignature: "AWS4-HMAC-SHA256 Credential=AKID/19700101/us-east-1/dynamodb/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=671ed63777ad2f28bfefd733087414652c1498b3301d9bdf272e44a3172c28c0",
ExpectDate: "19700101T000000Z",
ExpectToken: "SESSION",
},
"minimal case, seekable": {
Input: &SignRequestInput{
Request: newRequest(seekable("{}")),
Credentials: credsSession,
Service: "dynamodb",
Region: "us-east-1",
Time: time.Unix(0, 0),
},
ExpectSignature: "AWS4-HMAC-SHA256 Credential=AKID/19700101/us-east-1/dynamodb/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=e75efbd4e2b3d3a8218d8fc0125e8fc888844510125ca6f33be555fd76d9aa18",
ExpectDate: "19700101T000000Z",
ExpectToken: "SESSION",
},
"minimal case, no session": {
Input: &SignRequestInput{
Request: newRequest(nonseekable("{}")),
Credentials: credsNoSession,
Service: "dynamodb",
Region: "us-east-1",
Time: time.Unix(0, 0),
},
ExpectSignature: "AWS4-HMAC-SHA256 Credential=AKID/19700101/us-east-1/dynamodb/aws4_request, SignedHeaders=host;x-amz-date, Signature=6f249a4b86fd230f28cae603cdf92c2657b1d1ffc3fcccbd938e1339c4542e14",
ExpectDate: "19700101T000000Z",
ExpectToken: "",
},
"explicit unsigned payload": {
Input: &SignRequestInput{
Request: newRequest(seekable("{}")),
PayloadHash: v4.UnsignedPayload(),
Credentials: credsSession,
Service: "dynamodb",
Region: "us-east-1",
Time: time.Unix(0, 0),
},
ExpectSignature: "AWS4-HMAC-SHA256 Credential=AKID/19700101/us-east-1/dynamodb/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=671ed63777ad2f28bfefd733087414652c1498b3301d9bdf272e44a3172c28c0",
ExpectDate: "19700101T000000Z",
ExpectToken: "SESSION",
},
"explicit payload hash": {
Input: &SignRequestInput{
Request: newRequest(seekable("{}")),
PayloadHash: v4internal.Stosha("{}"),
Credentials: credsSession,
Service: "dynamodb",
Region: "us-east-1",
Time: time.Unix(0, 0),
},
ExpectSignature: "AWS4-HMAC-SHA256 Credential=AKID/19700101/us-east-1/dynamodb/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=e75efbd4e2b3d3a8218d8fc0125e8fc888844510125ca6f33be555fd76d9aa18",
ExpectDate: "19700101T000000Z",
ExpectToken: "SESSION",
},
"sign all headers": {
Input: &SignRequestInput{
Request: newRequest(seekable("{}"), func(r *http.Request) {
r.Header.Set("Content-Type", "application/json")
r.Header.Set("Foo", "bar")
r.Header.Set("Bar", "baz")
}),
PayloadHash: v4internal.Stosha("{}"),
Credentials: credsSession,
Service: "dynamodb",
Region: "us-east-1",
Time: time.Unix(0, 0),
},
Opts: func(o *v4.SignerOptions) {
o.HeaderRules = signAll{}
},
ExpectSignature: "AWS4-HMAC-SHA256 Credential=AKID/19700101/us-east-1/dynamodb/aws4_request, SignedHeaders=bar;content-type;foo;host;x-amz-date;x-amz-security-token, Signature=90673d8f57147fd36dbde4d4fe156f643ea25627e7b4d14c157c6369e685b80a",
ExpectDate: "19700101T000000Z",
ExpectToken: "SESSION",
},
"disable implicit payload hash": {
Input: &SignRequestInput{
Request: newRequest(seekable("{}")),
Credentials: credsSession,
Service: "dynamodb",
Region: "us-east-1",
Time: time.Unix(0, 0),
},
Opts: func(o *v4.SignerOptions) {
o.DisableImplicitPayloadHashing = true
},
ExpectSignature: "AWS4-HMAC-SHA256 Credential=AKID/19700101/us-east-1/dynamodb/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=671ed63777ad2f28bfefd733087414652c1498b3301d9bdf272e44a3172c28c0",
ExpectDate: "19700101T000000Z",
ExpectToken: "SESSION",
},
"s3 settings": {
Input: &SignRequestInput{
Request: newRequest(seekable("{}")),
Credentials: credsSession,
Service: "s3",
Region: "us-east-1",
Time: time.Unix(0, 0),
},
Opts: func(o *v4.SignerOptions) {
o.DisableDoublePathEscape = true
o.AddPayloadHashHeader = true
},
ExpectSignature: "AWS4-HMAC-SHA256 Credential=AKID/19700101/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token, Signature=0232da513d9e9830b12cf0d9f374834671494bc362ee173adb2a50267d0339e0",
ExpectDate: "19700101T000000Z",
ExpectToken: "SESSION",
},
} {
t.Run(name, func(t *testing.T) {
opt := tt.Opts
if opt == nil {
opt = func(o *v4.SignerOptions) {}
}
signer := New(opt)
if err := signer.SignRequest(tt.Input); err != nil {
t.Fatalf("expect no err, got %v", err)
}
req := tt.Input.Request
expectSignature(t, req, tt.ExpectSignature, tt.ExpectDate, tt.ExpectToken)
if host := req.Header.Get("Host"); req.Host != host {
t.Errorf("expect host header: %s != %s", req.Host, host)
}
})
}
}
func TestSignRequestQueryString(t *testing.T) {
signer := New()
req := newRequest(nil)
req.URL.RawQuery = "existing=param"
err := signer.SignRequest(&SignRequestInput{
Request: req,
Credentials: credsNoSession,
Service: "s3",
Region: "us-east-1",
Time: time.Unix(1375315200, 0),
SignatureType: v4.SignatureTypeQueryString,
})
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
// Should not have Authorization header
if auth := req.Header.Get("Authorization"); auth != "" {
t.Errorf("expected no Authorization header, got %s", auth)
}
// Should have query parameters
query := req.URL.Query()
if query.Get("X-Amz-Algorithm") != "AWS4-HMAC-SHA256" {
t.Errorf("expected X-Amz-Algorithm=AWS4-HMAC-SHA256, got %s", query.Get("X-Amz-Algorithm"))
}
if !strings.Contains(query.Get("X-Amz-Credential"), "AKID/20130801/us-east-1/s3/aws4_request") {
t.Errorf("unexpected X-Amz-Credential: %s", query.Get("X-Amz-Credential"))
}
if query.Get("X-Amz-Date") != "20130801T000000Z" {
t.Errorf("expected X-Amz-Date=20130801T000000Z, got %s", query.Get("X-Amz-Date"))
}
if query.Get("X-Amz-SignedHeaders") != "host" {
t.Errorf("expected X-Amz-SignedHeaders=host, got %s", query.Get("X-Amz-SignedHeaders"))
}
expectedSignature := "16e17486c8e6bb97596d140876d8a23164a13552e644900b81dba01ad092bdac"
if query.Get("X-Amz-Signature") != expectedSignature {
t.Errorf("expected X-Amz-Signature=%s, got %s", expectedSignature, query.Get("X-Amz-Signature"))
}
// Should preserve existing query params
if query.Get("existing") != "param" {
t.Errorf("expected existing=param, got existing=%s", query.Get("existing"))
}
}
func TestSignRequestQueryStringWithSession(t *testing.T) {
signer := New()
req := newRequest(nil)
err := signer.SignRequest(&SignRequestInput{
Request: req,
Credentials: credsSession,
Service: "s3",
Region: "us-east-1",
Time: time.Unix(1375315200, 0),
SignatureType: v4.SignatureTypeQueryString,
})
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
query := req.URL.Query()
if query.Get("X-Amz-SignedHeaders") != "host" {
t.Errorf("expected X-Amz-SignedHeaders=host, got %s", query.Get("X-Amz-SignedHeaders"))
}
if query.Get("X-Amz-Security-Token") != "SESSION" {
t.Errorf("expected X-Amz-Security-Token=SESSION, got %s", query.Get("X-Amz-Security-Token"))
}
expectedSignature := "485caefdc25560b950ea52c7cd87bc607084a43193387b1737bd2290ba3b699a"
if query.Get("X-Amz-Signature") != expectedSignature {
t.Errorf("expected X-Amz-Signature=%s, got %s", expectedSignature, query.Get("X-Amz-Signature"))
}
}
func TestSignRequestHeaderDoesNotAlterQueryString(t *testing.T) {
signer := New()
req := newRequest(nil)
req.URL.RawQuery = "existing=param&another=value"
originalQuery := req.URL.RawQuery
err := signer.SignRequest(&SignRequestInput{
Request: req,
Credentials: credsNoSession,
Service: "s3",
Region: "us-east-1",
Time: time.Unix(1375315200, 0),
SignatureType: v4.SignatureTypeHeader,
})
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
// Should have Authorization header
if auth := req.Header.Get("Authorization"); auth == "" {
t.Error("expected Authorization header to be set")
}
// Query string should be unchanged
if req.URL.RawQuery != originalQuery {
t.Errorf("expected query string unchanged, got %s, want %s", req.URL.RawQuery, originalQuery)
}
}
func TestBackwardsCompatibility(t *testing.T) {
signer := New()
req := newRequest(nil)
err := signer.SignRequest(&SignRequestInput{
Request: req,
Credentials: credsNoSession,
Service: "s3",
Region: "us-east-1",
Time: time.Unix(1375315200, 0),
})
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
// Should behave like header method (old behavior)
if auth := req.Header.Get("Authorization"); auth == "" {
t.Error("expected Authorization header to be set for backwards compatibility")
}
// Should not have query parameters
query := req.URL.Query()
if query.Get("X-Amz-Algorithm") != "" {
t.Error("expected no X-Amz-Algorithm in query string for backwards compatibility")
}
}
smithy-go-1.23.2/aws-http-auth/sigv4a/ 0000775 0000000 0000000 00000000000 15102203672 0017420 5 ustar 00root root 0000000 0000000 smithy-go-1.23.2/aws-http-auth/sigv4a/credentials.go 0000664 0000000 0000000 00000010450 15102203672 0022244 0 ustar 00root root 0000000 0000000 package sigv4a
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/hmac"
"crypto/sha256"
"encoding/binary"
"fmt"
"hash"
"math"
"math/big"
"sync"
"github.com/aws/smithy-go/aws-http-auth/credentials"
)
var (
p256 elliptic.Curve
nMinusTwoP256 *big.Int
one = new(big.Int).SetInt64(1)
)
func init() {
p256 = elliptic.P256()
nMinusTwoP256 = new(big.Int).SetBytes(p256.Params().N.Bytes())
nMinusTwoP256 = nMinusTwoP256.Sub(nMinusTwoP256, new(big.Int).SetInt64(2))
}
// ecdsaCache stores the result of deriving an ECDSA private key from a
// shared-secret identity.
type ecdsaCache struct {
mu sync.Mutex
akid string
priv *ecdsa.PrivateKey
}
// Derive computes and caches the ECDSA key-pair for the identity, returning
// the result.
//
// Future calls to Derive with the same set of credentials (identified by AKID)
// will short-circuit. Future calls with a different set of credentials
// (identified by AKID) will re-derive the value, overwriting the old result.
func (c *ecdsaCache) Derive(creds credentials.Credentials) (*ecdsa.PrivateKey, error) {
c.mu.Lock()
defer c.mu.Unlock()
if creds.AccessKeyID == c.akid {
return c.priv, nil
}
priv, err := derivePrivateKey(creds)
if err != nil {
return nil, err
}
c.akid = creds.AccessKeyID
c.priv = priv
return priv, nil
}
// derivePrivateKey derives a NIST P-256 PrivateKey from the given IAM
// AccessKey and SecretKey pair.
//
// Based on FIPS.186-4 Appendix B.4.2
func derivePrivateKey(creds credentials.Credentials) (*ecdsa.PrivateKey, error) {
akid := creds.AccessKeyID
secret := creds.SecretAccessKey
params := p256.Params()
bitLen := params.BitSize // Testing random candidates does not require an additional 64 bits
counter := 0x01
buffer := make([]byte, 1+len(akid)) // 1 byte counter + len(accessKey)
kdfContext := bytes.NewBuffer(buffer)
inputKey := append([]byte("AWS4A"), []byte(secret)...)
d := new(big.Int)
for {
kdfContext.Reset()
kdfContext.WriteString(akid)
kdfContext.WriteByte(byte(counter))
key, err := deriveHMACKey(sha256.New, bitLen, inputKey, []byte(algorithm), kdfContext.Bytes())
if err != nil {
return nil, err
}
cmp, err := cmpConst(key, nMinusTwoP256.Bytes())
if err != nil {
return nil, err
}
if cmp == -1 {
d.SetBytes(key)
break
}
counter++
if counter > 0xFF {
return nil, fmt.Errorf("exhausted single byte external counter")
}
}
d = d.Add(d, one)
priv := new(ecdsa.PrivateKey)
priv.PublicKey.Curve = p256
priv.D = d
priv.PublicKey.X, priv.PublicKey.Y = p256.ScalarBaseMult(d.Bytes())
return priv, nil
}
// deriveHMACKey provides an implementation of a NIST-800-108 of a KDF (Key
// Derivation Function) in Counter Mode. HMAC is used as the pseudorandom
// function, where the value of `r` is defined as a 4-byte counter.
func deriveHMACKey(hash func() hash.Hash, bitLen int, key []byte, label, context []byte) ([]byte, error) {
// verify that we won't overflow the counter
n := int64(math.Ceil((float64(bitLen) / 8) / float64(hash().Size())))
if n > 0x7FFFFFFF {
return nil, fmt.Errorf("unable to derive key of size %d using 32-bit counter", bitLen)
}
// verify the requested bit length is not larger then the length encoding size
if int64(bitLen) > 0x7FFFFFFF {
return nil, fmt.Errorf("bitLen is greater than 32-bits")
}
fixedInput := bytes.NewBuffer(nil)
fixedInput.Write(label)
fixedInput.WriteByte(0x00)
fixedInput.Write(context)
if err := binary.Write(fixedInput, binary.BigEndian, int32(bitLen)); err != nil {
return nil, fmt.Errorf("failed to write bit length to fixed input string: %v", err)
}
var output []byte
h := hmac.New(hash, key)
for i := int64(1); i <= n; i++ {
h.Reset()
if err := binary.Write(h, binary.BigEndian, int32(i)); err != nil {
return nil, err
}
_, err := h.Write(fixedInput.Bytes())
if err != nil {
return nil, err
}
output = append(output, h.Sum(nil)...)
}
return output[:bitLen/8], nil
}
// constant-time byte slice compare
func cmpConst(x, y []byte) (int, error) {
if len(x) != len(y) {
return 0, fmt.Errorf("slice lengths do not match")
}
xLarger, yLarger := 0, 0
for i := 0; i < len(x); i++ {
xByte, yByte := int(x[i]), int(y[i])
x := ((yByte - xByte) >> 8) & 1
y := ((xByte - yByte) >> 8) & 1
xLarger |= x &^ yLarger
yLarger |= y &^ xLarger
}
return xLarger - yLarger, nil
}
smithy-go-1.23.2/aws-http-auth/sigv4a/e2e_test.go 0000664 0000000 0000000 00000032660 15102203672 0021470 0 ustar 00root root 0000000 0000000 //go:build e2e
// +build e2e
package sigv4a
import (
"bytes"
"context"
"encoding/xml"
"fmt"
"io"
"math/rand"
"net/http"
"os"
"testing"
"time"
"github.com/aws/smithy-go/aws-http-auth/credentials"
"github.com/aws/smithy-go/aws-http-auth/sigv4"
v4 "github.com/aws/smithy-go/aws-http-auth/v4"
)
type closer struct{ io.ReadSeeker }
func (closer) Close() error { return nil }
type ToXML interface {
ToXML() []byte
}
type S3Client struct {
Region string
AccountID string
Credentials credentials.Credentials
HTTPClient *http.Client
V4 *sigv4.Signer
V4A *Signer
}
func (c *S3Client) CreateBucket(ctx context.Context, in *CreateBucketInput) (*CreateBucketOutput, error) {
var out CreateBucketOutput
endpoint := fmt.Sprintf("https://%s.s3.%s.amazonaws.com", in.Bucket, c.Region)
method := http.MethodPut
path := "/"
sign := signV4(c.V4, c.Credentials, c.Region)
if err := c.do(ctx, method, endpoint, path, in, &out, sign); err != nil {
return nil, err
}
return &out, nil
}
type CreateBucketInput struct {
Bucket string
}
func (*CreateBucketInput) ToXML() []byte {
return []byte("")
}
type CreateBucketOutput struct{}
func (c *S3Client) DeleteBucket(ctx context.Context, in *DeleteBucketInput) (*DeleteBucketOutput, error) {
var out DeleteBucketOutput
endpoint := fmt.Sprintf("https://%s.s3.%s.amazonaws.com", in.Bucket, c.Region)
method := http.MethodDelete
path := "/"
sign := signV4(c.V4, c.Credentials, c.Region)
if err := c.do(ctx, method, endpoint, path, in, &out, sign); err != nil {
return nil, err
}
return &out, nil
}
type DeleteBucketInput struct {
Bucket string
}
func (*DeleteBucketInput) ToXML() []byte {
return []byte("")
}
type DeleteBucketOutput struct{}
func (c *S3Client) PutObjectMRAP(ctx context.Context, in *PutObjectMRAPInput) (*PutObjectMRAPOutput, error) {
var out PutObjectMRAPOutput
endpoint := fmt.Sprintf("https://%s.accesspoint.s3-global.amazonaws.com", in.MRAPAlias)
method := http.MethodPut
path := "/" + in.Key
sign := signV4A(c.V4A, c.Credentials, true) // unsigned payload
if err := c.do(ctx, method, endpoint, path, in, &out, sign); err != nil {
return nil, err
}
return &out, nil
}
type PutObjectMRAPInput struct {
MRAPAlias string
Key string
ObjectData string
}
func (i *PutObjectMRAPInput) ToXML() []byte {
// not actually XML but good enough to get the object data into the request
// body
return []byte(i.ObjectData)
}
type PutObjectMRAPOutput struct{}
func (c *S3Client) DeleteObjectMRAP(ctx context.Context, in *DeleteObjectMRAPInput) (*DeleteObjectMRAPOutput, error) {
var out DeleteObjectMRAPOutput
endpoint := fmt.Sprintf("https://%s.accesspoint.s3-global.amazonaws.com", in.MRAPAlias)
method := http.MethodDelete
path := "/" + in.Key
sign := signV4A(c.V4A, c.Credentials, false)
if err := c.do(ctx, method, endpoint, path, in, &out, sign); err != nil {
return nil, err
}
return &out, nil
}
type DeleteObjectMRAPInput struct {
MRAPAlias string
Key string
}
func (i *DeleteObjectMRAPInput) ToXML() []byte {
return []byte("")
}
type DeleteObjectMRAPOutput struct{}
func signV4(signer *sigv4.Signer, creds credentials.Credentials, region string) func(*http.Request) error {
return func(r *http.Request) error {
return signer.SignRequest(&sigv4.SignRequestInput{
Request: r,
Credentials: creds,
Service: "s3",
Region: region,
})
}
}
func signV4A(signer *Signer, creds credentials.Credentials, isUnsignedPayload bool) func(*http.Request) error {
var payloadHash []byte
if isUnsignedPayload {
payloadHash = v4.UnsignedPayload()
}
return func(r *http.Request) error {
err := signer.SignRequest(&SignRequestInput{
Request: r,
PayloadHash: payloadHash,
Credentials: creds,
Service: "s3",
RegionSet: []string{"*"},
})
fmt.Println("signed request ------------------------------------------")
fmt.Printf("%s %s\n", r.Method, r.URL.EscapedPath())
for h := range r.Header {
fmt.Printf("%s: %s\n", h, r.Header.Get(h))
}
fmt.Println("---------------------------------------------------------")
return err
}
}
func (c *S3Client) do(ctx context.Context, method, endpoint, path string, in ToXML, out any, signRequest func(*http.Request) error) error {
// init
req, err := http.NewRequestWithContext(ctx, method, endpoint, http.NoBody)
if err != nil {
return fmt.Errorf("new http request: %w", err)
}
// serialize
req.URL.Path = path
req.Header.Set("Content-Type", "application/xml")
payload := in.ToXML()
req.Body = closer{bytes.NewReader(payload)}
req.ContentLength = int64(len(payload))
// sign
err = signRequest(req)
if err != nil {
return fmt.Errorf("sign request: %w", err)
}
// round-trip
resp, err := c.HTTPClient.Do(req)
if err != nil {
return fmt.Errorf("do request: %w", err)
}
defer resp.Body.Close()
// deserialize
data, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read response body: %w", err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("request error: %s: %s", resp.Status, data)
}
if len(data) == 0 {
return nil
}
if err := xml.Unmarshal(data, out); err != nil {
return fmt.Errorf("deserialize response: %w", err)
}
return nil
}
type S3ControlClient struct {
// s3control only does us-west-2
// Region string
AccountID string
Credentials credentials.Credentials
HTTPClient *http.Client
Signer *sigv4.Signer
}
func (c *S3ControlClient) GetMRAP(ctx context.Context, in *GetMRAPInput) (*GetMRAPOutput, error) {
var out GetMRAPOutput
method := http.MethodGet
path := "/v20180820/mrap/instances/" + in.Name
if err := c.do(ctx, c.AccountID, method, path, in, &out); err != nil {
return nil, err
}
return &out, nil
}
type GetMRAPInput struct {
Name string
}
func (i *GetMRAPInput) ToXML() []byte {
return []byte("")
}
type GetMRAPOutput struct {
AccessPoint struct {
Alias string
}
}
func (c *S3ControlClient) CreateMRAP(ctx context.Context, in *CreateMRAPInput) (*CreateMRAPOutput, error) {
var out CreateMRAPOutput
method := http.MethodPost
path := "/v20180820/async-requests/mrap/create"
if err := c.do(ctx, c.AccountID, method, path, in, &out); err != nil {
return nil, err
}
return &out, nil
}
type CreateMRAPInput struct {
Name string
Bucket string
}
func (i *CreateMRAPInput) ToXML() []byte {
const tmpl = `
%s
%s
%s
`
token := fmt.Sprintf("%d", rand.Int31())
return []byte(fmt.Sprintf(tmpl, token, i.Name, i.Bucket))
}
type CreateMRAPOutput struct {
RequestToken string `xml:"RequestTokenARN"`
}
func (c *S3ControlClient) DescribeMRAPOperation(ctx context.Context, in *DescribeMRAPOperationInput) (*DescribeMRAPOperationOutput, error) {
var out DescribeMRAPOperationOutput
method := http.MethodGet
path := "/v20180820/async-requests/mrap/" + in.RequestToken
if err := c.do(ctx, c.AccountID, method, path, in, &out); err != nil {
return nil, err
}
return &out, nil
}
type DescribeMRAPOperationInput struct {
RequestToken string
}
func (i *DescribeMRAPOperationInput) ToXML() []byte {
return []byte("")
}
type DescribeMRAPOperationOutput struct {
AsyncOperation struct {
RequestStatus string
}
}
func (c *S3ControlClient) DeleteMRAP(ctx context.Context, in *DeleteMRAPInput) (*DeleteMRAPOutput, error) {
var out DeleteMRAPOutput
method := http.MethodPost
path := "/v20180820/async-requests/mrap/delete"
if err := c.do(ctx, c.AccountID, method, path, in, &out); err != nil {
return nil, err
}
return &out, nil
}
type DeleteMRAPInput struct {
Name string
}
func (i *DeleteMRAPInput) ToXML() []byte {
const tmpl = `
%s
%s
`
token := fmt.Sprintf("%d", rand.Int31())
return []byte(fmt.Sprintf(tmpl, token, i.Name))
}
type DeleteMRAPOutput struct {
RequestToken string `xml:"RequestTokenARN"`
}
func (c *S3ControlClient) do(ctx context.Context, accountID, method, path string, in ToXML, out any) error {
// init
endpoint := fmt.Sprintf("https://%s.s3-control.us-west-2.amazonaws.com", accountID)
req, err := http.NewRequestWithContext(ctx, method, endpoint, http.NoBody)
if err != nil {
return fmt.Errorf("new http request: %w", err)
}
// serialize
req.URL.Path = path
req.Header.Set("Content-Type", "application/xml")
req.Header.Set("X-Amz-Account-Id", accountID)
payload := in.ToXML()
req.Body = closer{bytes.NewReader(payload)}
req.ContentLength = int64(len(payload))
// sign
err = c.Signer.SignRequest(&sigv4.SignRequestInput{
Request: req,
Credentials: c.Credentials,
Service: "s3",
Region: "us-west-2",
})
if err != nil {
return fmt.Errorf("sign request: %w", err)
}
// round-trip
resp, err := c.HTTPClient.Do(req)
if err != nil {
return fmt.Errorf("do request: %w", err)
}
defer resp.Body.Close()
// deserialize
data, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read response body: %w", err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("request error: %s: %s", resp.Status, data)
}
if len(data) == 0 {
return nil
}
if err := xml.Unmarshal(data, out); err != nil {
return fmt.Errorf("deserialize response: %w", err)
}
return nil
}
// WARNING: this test takes a while, because creating an MRAP is asynchronous
// and slow
//
// 1. creates a bucket in us-east-1
// 2. creates an MRAP that points to that bucket
// 3. polls MRAP status until created
// 4. puts object to MRAP
// 5. deletes object
// 6. deletes MRAP
// 7. deletes bucket
func TestE2E_S3MRAP(t *testing.T) {
svc := &S3Client{
Region: "us-east-1",
AccountID: os.Getenv("AWS_ACCOUNT_ID"),
Credentials: credentials.Credentials{
AccessKeyID: os.Getenv("AWS_ACCESS_KEY_ID"),
SecretAccessKey: os.Getenv("AWS_SECRET_ACCESS_KEY"),
SessionToken: os.Getenv("AWS_SESSION_TOKEN"),
},
HTTPClient: http.DefaultClient,
V4: sigv4.New(func(o *v4.SignerOptions) {
o.DisableDoublePathEscape = true
o.AddPayloadHashHeader = true
}),
V4A: New(func(o *v4.SignerOptions) {
o.DisableDoublePathEscape = true
o.AddPayloadHashHeader = true
}),
}
controlsvc := &S3ControlClient{
AccountID: os.Getenv("AWS_ACCOUNT_ID"),
Credentials: credentials.Credentials{
AccessKeyID: os.Getenv("AWS_ACCESS_KEY_ID"),
SecretAccessKey: os.Getenv("AWS_SECRET_ACCESS_KEY"),
SessionToken: os.Getenv("AWS_SESSION_TOKEN"),
},
HTTPClient: http.DefaultClient,
Signer: sigv4.New(func(o *v4.SignerOptions) {
o.AddPayloadHashHeader = true
}),
}
testid := rand.Int() % (2 << 15)
bucket := fmt.Sprintf("aws-http-auth-e2etest-bucket-%d", testid)
mrap := fmt.Sprintf("aws-http-auth-e2etest-mrap-%d", testid)
_, err := svc.CreateBucket(context.Background(), &CreateBucketInput{
Bucket: bucket,
})
if err != nil {
t.Fatalf("create bucket: %v", err)
}
t.Logf("created test bucket: %s", bucket)
createMRAPOutput, err := controlsvc.CreateMRAP(context.Background(), &CreateMRAPInput{
Name: mrap,
Bucket: bucket,
})
if err != nil {
t.Fatalf("create mrap: %v", err)
}
t.Logf("started mrap create... token %s", createMRAPOutput.RequestToken)
awaitS3ControlOperation(t, context.Background(), controlsvc, createMRAPOutput.RequestToken)
t.Logf("created test mrap: %s", mrap)
getMRAPOutput, err := controlsvc.GetMRAP(context.Background(), &GetMRAPInput{
Name: mrap,
})
if err != nil {
t.Fatalf("get mrap info: %v", err)
}
t.Logf("retrieved mrap alias: %s", getMRAPOutput.AccessPoint.Alias)
_, err = svc.PutObjectMRAP(context.Background(), &PutObjectMRAPInput{
MRAPAlias: getMRAPOutput.AccessPoint.Alias,
Key: "path1 / path2", // verify single-encode behavior
ObjectData: mrap,
})
if err != nil {
t.Fatalf("put object mrap: %v", err)
}
_, err = svc.DeleteObjectMRAP(context.Background(), &DeleteObjectMRAPInput{
MRAPAlias: getMRAPOutput.AccessPoint.Alias,
Key: "path1 / path2",
})
if err != nil {
t.Fatalf("delete object mrap: %v", err)
}
deleteMRAPOutput, err := controlsvc.DeleteMRAP(context.Background(), &DeleteMRAPInput{
Name: mrap,
})
if err != nil {
t.Fatalf("delete mrap: %v", err)
}
t.Logf("started mrap delete... token %s", deleteMRAPOutput.RequestToken)
awaitS3ControlOperation(t, context.Background(), controlsvc, deleteMRAPOutput.RequestToken)
_, err = svc.DeleteBucket(context.Background(), &DeleteBucketInput{
Bucket: bucket,
})
if err != nil {
t.Fatalf("delete bucket: %v", err)
}
t.Logf("deleted test bucket: %s", bucket)
}
func awaitS3ControlOperation(t *testing.T, ctx context.Context, svc *S3ControlClient, requestToken string) {
t.Helper()
start := time.Now()
for {
out, err := svc.DescribeMRAPOperation(ctx, &DescribeMRAPOperationInput{
RequestToken: requestToken,
})
if err != nil {
t.Fatalf("describe mrap operation: %v", err)
}
t.Logf("poll status: %s\n", out.AsyncOperation.RequestStatus)
time.Sleep(5 * time.Second)
// S3Control does not document the values for this field.
// Anecdotally:
// - returns NEW a few seconds after the operation starts
// - returns INPROGRESS until complete
// - returns SUCCEEDED when complete
if out.AsyncOperation.RequestStatus == "SUCCEEDED" {
break
}
}
t.Logf("operation completed after %v", time.Now().Sub(start))
}
smithy-go-1.23.2/aws-http-auth/sigv4a/sigv4a.go 0000664 0000000 0000000 00000015523 15102203672 0021152 0 ustar 00root root 0000000 0000000 // Package sigv4a implements request signing for AWS Signature Version 4a
// (asymmetric).
//
// The algorithm for Signature Version 4a is identical to that of plain v4
// apart from the following:
// - A request can be signed for multiple regions. This is represented in the
// signature using the X-Amz-Region-Set header. The credential scope string
// used in the calculation correspondingly lacks the region component from
// that of plain v4.
// - The string-to-sign component of the calculation is instead signed with
// an ECDSA private key. This private key is typically derived from your
// regular AWS credentials.
package sigv4a
import (
"crypto"
"crypto/ecdsa"
"crypto/rand"
"encoding/hex"
"net/http"
"strings"
"time"
"github.com/aws/smithy-go/aws-http-auth/credentials"
v4internal "github.com/aws/smithy-go/aws-http-auth/internal/v4"
v4 "github.com/aws/smithy-go/aws-http-auth/v4"
)
const algorithm = "AWS4-ECDSA-P256-SHA256"
// Signer signs requests with AWS Signature Version 4a.
//
// Unlike Sigv4, AWS SigV4a signs requests with an ECDSA private key. This is
// derived automatically from the AWS credential identity passed to
// SignRequest. This derivation result is cached on the Signer and is uniquely
// identified by the access key ID (AKID) of the credentials that were
// provided.
//
// Because of this, the caller is encouraged to create multiple instances of
// Signer for different underlying identities (e.g. IAM roles).
type Signer struct {
options v4.SignerOptions
// derived asymmetric credentials
privCache *ecdsaCache
}
// New returns an instance of Signer with applied options.
func New(opts ...v4.SignerOption) *Signer {
options := v4.SignerOptions{}
for _, opt := range opts {
opt(&options)
}
return &Signer{
options: options,
privCache: &ecdsaCache{},
}
}
// SignRequestInput is the set of inputs for the Sigv4a signing process.
type SignRequestInput struct {
// The input request, which will modified in-place during signing.
Request *http.Request
// The SHA256 hash of the input request body.
//
// This value is NOT required to sign the request, but it is recommended to
// provide it (or provide a Body on the HTTP request that implements
// io.Seeker such that the signer can calculate it for you). Many services
// do not accept requests with unsigned payloads.
//
// If a value is not provided, and DisableImplicitPayloadHashing has not
// been set on SignerOptions, the signer will attempt to derive the payload
// hash itself. The request's Body MUST implement io.Seeker in order to do
// this, if it does not, the magic value for unsigned payload is used. If
// the body does implement io.Seeker, but a call to Seek returns an error,
// the signer will forward that error.
PayloadHash []byte
// The identity used to sign the request.
Credentials credentials.Credentials
// The service for which this request is to be signed.
//
// The appropriate value for this field is determined by the service
// vendor.
Service string
// The set of regions for which this request is to be signed.
//
// The sentinel {"*"} is used to indicate that the signed request is valid
// in all regions. Callers MUST set a value for this field - the API will
// not fill in a default and the resulting signature will ultimately be
// invalid.
//
// The acceptable values for list entries of this field are determined by
// the service vendor.
RegionSet []string
// Wall-clock time used for calculating the signature.
//
// If the zero-value is given (generally by the caller not setting it), the
// signer will instead use the current system clock time for the signature.
Time time.Time
// How the signature is transmitted (header or query string).
SignatureType v4.SignatureType
}
// SignRequest signs an HTTP request with AWS Signature Version 4, modifying
// the request in-place by adding the headers that constitute the signature.
//
// SignRequest will modify the request by setting the following headers:
// - Host: required in general for HTTP/1.1 as well as for v4-signed requests
// - X-Amz-Date: required for v4a-signed requests
// - X-Amz-Region-Set: used to convey the regions for which the request is
// signed in v4a
// - X-Amz-Security-Token: required for v4a-signed requests IF present on
// credentials used to sign, otherwise this header will not be set
// - Authorization: contains the v4a signature string
//
// The request MUST have a Host value set at the time that this API is called,
// such that it can be included in the signature calculation. Standard library
// HTTP clients set this as a request header by default, meaning that a request
// signed without a Host value will end up transmitting with the Host header
// anyway, which will cause the request to be rejected by the service due to
// signature mismatch (the Host header is required to be signed with Sigv4).
//
// Generally speaking, using http.NewRequest will ensure that request instances
// are sufficiently initialized to be used with this API, though it is not
// strictly required.
//
// SignRequest may be called any number of times on an http.Request instance,
// the header values set as part of the signature will simply be re-calculated.
// Note that v4a signatures are non-deterministic due to the random component
// of ECDSA signing, callers should not expect two calls to SignRequest() to
// produce an identical signature.
func (s *Signer) SignRequest(in *SignRequestInput, opts ...v4.SignerOption) error {
options := s.options
for _, fn := range opts {
fn(&options)
}
priv, err := s.privCache.Derive(in.Credentials)
if err != nil {
return err
}
tm := v4internal.ResolveTime(in.Time)
// For query string auth, add X-Amz-Region-Set as query parameter
// For header auth, add it as header
if in.SignatureType == v4.SignatureTypeQueryString {
query := in.Request.URL.Query()
query.Set("X-Amz-Region-Set", strings.Join(in.RegionSet, ","))
in.Request.URL.RawQuery = query.Encode()
} else {
in.Request.Header.Set("X-Amz-Region-Set", strings.Join(in.RegionSet, ","))
}
signer := &v4internal.Signer{
Request: in.Request,
PayloadHash: in.PayloadHash,
Time: tm,
Credentials: in.Credentials,
Options: options,
Algorithm: algorithm,
CredentialScope: scope(tm, in.Service),
SignatureType: in.SignatureType,
Finalizer: &finalizer{priv},
}
if err := signer.Do(); err != nil {
return err
}
return nil
}
func scope(tm time.Time, service string) string {
return strings.Join([]string{
tm.Format(v4internal.ShortTimeFormat),
service,
"aws4_request",
}, "/")
}
type finalizer struct {
Secret *ecdsa.PrivateKey
}
func (f *finalizer) SignString(strToSign string) (string, error) {
sig, err := f.Secret.Sign(rand.Reader, v4internal.Stosha(strToSign), crypto.SHA256)
if err != nil {
return "", err
}
return hex.EncodeToString(sig), nil
}
smithy-go-1.23.2/aws-http-auth/sigv4a/sigv4a_test.go 0000664 0000000 0000000 00000035041 15102203672 0022206 0 ustar 00root root 0000000 0000000 package sigv4a
import (
"crypto/ecdsa"
"crypto/rand"
"encoding/asn1"
"encoding/hex"
"fmt"
"io"
"math/big"
"net/http"
"strings"
"testing"
"time"
"github.com/aws/smithy-go/aws-http-auth/credentials"
v4internal "github.com/aws/smithy-go/aws-http-auth/internal/v4"
v4 "github.com/aws/smithy-go/aws-http-auth/v4"
)
const (
accessKey = "AKISORANDOMAASORANDOM"
secretKey = "q+jcrXGc+0zWN6uzclKVhvMmUsIfRPa4rlRandom"
sessionToken = "TOKEN"
)
type signAll struct{}
func (signAll) IsSigned(string) bool { return true }
type ecdsaSignature struct {
R, S *big.Int
}
var credsSession = credentials.Credentials{
AccessKeyID: "AKID",
SecretAccessKey: "SECRET",
SessionToken: "SESSION",
}
var credsNoSession = credentials.Credentials{
AccessKeyID: "AKID",
SecretAccessKey: "SECRET",
}
func seekable(v string) io.ReadSeekCloser {
return readseekcloser{strings.NewReader(v)}
}
func nonseekable(v string) io.ReadCloser {
return io.NopCloser(strings.NewReader(v)) // io.NopCloser elides Seek()
}
type readseekcloser struct {
io.ReadSeeker
}
func (readseekcloser) Close() error { return nil }
func verifySignature(key *ecdsa.PublicKey, hash []byte, signature []byte) (bool, error) {
var sig ecdsaSignature
_, err := asn1.Unmarshal(signature, &sig)
if err != nil {
return false, err
}
return ecdsa.Verify(key, hash, sig.R, sig.S), nil
}
func TestDeriveECDSAKeyPairFromSecret(t *testing.T) {
t.Skip()
creds := credentials.Credentials{
AccessKeyID: accessKey,
SecretAccessKey: secretKey,
}
privateKey, err := derivePrivateKey(creds)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
expectedX := func() *big.Int {
t.Helper()
b, ok := new(big.Int).SetString("15D242CEEBF8D8169FD6A8B5A746C41140414C3B07579038DA06AF89190FFFCB", 16)
if !ok {
t.Fatalf("failed to parse big integer")
}
return b
}()
expectedY := func() *big.Int {
t.Helper()
b, ok := new(big.Int).SetString("515242CEDD82E94799482E4C0514B505AFCCF2C0C98D6A553BF539F424C5EC0", 16)
if !ok {
t.Fatalf("failed to parse big integer")
}
return b
}()
if privateKey.X.Cmp(expectedX) != 0 {
t.Errorf("expected % X, got % X", expectedX, privateKey.X)
}
if privateKey.Y.Cmp(expectedY) != 0 {
t.Errorf("expected % X, got % X", expectedY, privateKey.Y)
}
}
func newRequest(body io.ReadCloser, opts ...func(*http.Request)) *http.Request {
req, err := http.NewRequest(http.MethodPost, "https://service.region.amazonaws.com", body)
if err != nil {
panic(err)
}
for _, opt := range opts {
opt(req)
}
return req
}
func TestSignRequest(t *testing.T) {
for name, tt := range map[string]struct {
Input *SignRequestInput
Opts v4.SignerOption
ExpectPreamble string
ExpectSignedHeaders string
ExpectStringToSign string
ExpectDate string
ExpectToken string
ExpectRegionSetHeader string
}{
"minimal case, nonseekable": {
Input: &SignRequestInput{
Request: newRequest(nonseekable("{}")),
Credentials: credsSession,
Service: "dynamodb",
RegionSet: []string{"us-east-1", "us-west-1"},
Time: time.Unix(0, 0),
},
ExpectPreamble: "AWS4-ECDSA-P256-SHA256 Credential=AKID/19700101/dynamodb/aws4_request",
ExpectSignedHeaders: "SignedHeaders=host;x-amz-date;x-amz-region-set;x-amz-security-token",
ExpectStringToSign: `AWS4-ECDSA-P256-SHA256
19700101T000000Z
19700101/dynamodb/aws4_request
968265b4e87c6b10c8ec6bcfd63e8002814cb3256d74c6c381f0c31268c80b53`,
ExpectDate: "19700101T000000Z",
ExpectToken: "SESSION",
ExpectRegionSetHeader: "us-east-1,us-west-1",
},
"minimal case, seekable": {
Input: &SignRequestInput{
Request: newRequest(seekable("{}")),
Credentials: credsSession,
Service: "dynamodb",
RegionSet: []string{"us-east-1"},
Time: time.Unix(0, 0),
},
ExpectPreamble: "AWS4-ECDSA-P256-SHA256 Credential=AKID/19700101/dynamodb/aws4_request",
ExpectSignedHeaders: "SignedHeaders=host;x-amz-date;x-amz-region-set;x-amz-security-token",
ExpectStringToSign: `AWS4-ECDSA-P256-SHA256
19700101T000000Z
19700101/dynamodb/aws4_request
6fbe2f6247e506a47694e695d825477af6c604184f775050ce3b83e04674d9aa`,
ExpectDate: "19700101T000000Z",
ExpectToken: "SESSION",
ExpectRegionSetHeader: "us-east-1",
},
"minimal case, no session": {
Input: &SignRequestInput{
Request: newRequest(nonseekable("{}")),
Credentials: credsNoSession,
Service: "dynamodb",
RegionSet: []string{"us-east-1"},
Time: time.Unix(0, 0),
},
ExpectPreamble: "AWS4-ECDSA-P256-SHA256 Credential=AKID/19700101/dynamodb/aws4_request",
ExpectSignedHeaders: "SignedHeaders=host;x-amz-date;x-amz-region-set",
ExpectStringToSign: `AWS4-ECDSA-P256-SHA256
19700101T000000Z
19700101/dynamodb/aws4_request
825ea1f5e80bdb91ac8802e832504d1ff1c3b05b7619ffc273a1565a7600ff5a`,
ExpectDate: "19700101T000000Z",
ExpectToken: "",
ExpectRegionSetHeader: "us-east-1",
},
"explicit unsigned payload": {
Input: &SignRequestInput{
Request: newRequest(seekable("{}")),
PayloadHash: v4.UnsignedPayload(),
Credentials: credsSession,
Service: "dynamodb",
RegionSet: []string{"us-east-1"},
Time: time.Unix(0, 0),
},
ExpectPreamble: "AWS4-ECDSA-P256-SHA256 Credential=AKID/19700101/dynamodb/aws4_request",
ExpectSignedHeaders: "SignedHeaders=host;x-amz-date;x-amz-region-set;x-amz-security-token",
ExpectStringToSign: `AWS4-ECDSA-P256-SHA256
19700101T000000Z
19700101/dynamodb/aws4_request
69e5041f5ff858ee8f53a30e5f98cdb4c6bcbfe0f8e61b8aba537d2713bf41a4`,
ExpectDate: "19700101T000000Z",
ExpectToken: "SESSION",
ExpectRegionSetHeader: "us-east-1",
},
"explicit payload hash": {
Input: &SignRequestInput{
Request: newRequest(seekable("{}")),
PayloadHash: v4internal.Stosha("{}"),
Credentials: credsSession,
Service: "dynamodb",
RegionSet: []string{"us-east-1"},
Time: time.Unix(0, 0),
},
ExpectPreamble: "AWS4-ECDSA-P256-SHA256 Credential=AKID/19700101/dynamodb/aws4_request",
ExpectSignedHeaders: "SignedHeaders=host;x-amz-date;x-amz-region-set;x-amz-security-token",
ExpectStringToSign: `AWS4-ECDSA-P256-SHA256
19700101T000000Z
19700101/dynamodb/aws4_request
6fbe2f6247e506a47694e695d825477af6c604184f775050ce3b83e04674d9aa`,
ExpectDate: "19700101T000000Z",
ExpectToken: "SESSION",
ExpectRegionSetHeader: "us-east-1",
},
"sign all headers": {
Input: &SignRequestInput{
Request: newRequest(seekable("{}"), func(r *http.Request) {
r.Header.Set("Content-Type", "application/json")
r.Header.Set("Foo", "bar")
r.Header.Set("Bar", "baz")
}),
PayloadHash: v4internal.Stosha("{}"),
Credentials: credsSession,
Service: "dynamodb",
RegionSet: []string{"us-east-1"},
Time: time.Unix(0, 0),
},
Opts: func(o *v4.SignerOptions) {
o.HeaderRules = signAll{}
},
ExpectPreamble: "AWS4-ECDSA-P256-SHA256 Credential=AKID/19700101/dynamodb/aws4_request",
ExpectSignedHeaders: "SignedHeaders=bar;content-type;foo;host;x-amz-date;x-amz-region-set;x-amz-security-token",
ExpectStringToSign: `AWS4-ECDSA-P256-SHA256
19700101T000000Z
19700101/dynamodb/aws4_request
b28cca9faeaa86f4dbfcc3113b05b38f53cd41f41448a41e08e0171cea8ec363`,
ExpectDate: "19700101T000000Z",
ExpectToken: "SESSION",
ExpectRegionSetHeader: "us-east-1",
},
"disable implicit payload hash": {
Input: &SignRequestInput{
Request: newRequest(seekable("{}")),
Credentials: credsSession,
Service: "dynamodb",
RegionSet: []string{"us-east-1"},
Time: time.Unix(0, 0),
},
Opts: func(o *v4.SignerOptions) {
o.DisableImplicitPayloadHashing = true
},
ExpectPreamble: "AWS4-ECDSA-P256-SHA256 Credential=AKID/19700101/dynamodb/aws4_request",
ExpectSignedHeaders: "SignedHeaders=host;x-amz-date;x-amz-region-set;x-amz-security-token",
ExpectStringToSign: `AWS4-ECDSA-P256-SHA256
19700101T000000Z
19700101/dynamodb/aws4_request
69e5041f5ff858ee8f53a30e5f98cdb4c6bcbfe0f8e61b8aba537d2713bf41a4`,
ExpectDate: "19700101T000000Z",
ExpectToken: "SESSION",
ExpectRegionSetHeader: "us-east-1",
},
"s3 settings": {
Input: &SignRequestInput{
Request: newRequest(seekable("{}")),
Credentials: credsSession,
Service: "s3",
RegionSet: []string{"us-east-1"},
Time: time.Unix(0, 0),
},
Opts: func(o *v4.SignerOptions) {
o.DisableDoublePathEscape = true
o.AddPayloadHashHeader = true
},
ExpectPreamble: "AWS4-ECDSA-P256-SHA256 Credential=AKID/19700101/s3/aws4_request",
ExpectSignedHeaders: "SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-region-set;x-amz-security-token",
ExpectStringToSign: `AWS4-ECDSA-P256-SHA256
19700101T000000Z
19700101/s3/aws4_request
3cf4ba7f150421e93dbc22112484147af6e355676d08a75799cfd32424458d7f`,
ExpectDate: "19700101T000000Z",
ExpectToken: "SESSION",
ExpectRegionSetHeader: "us-east-1",
},
} {
t.Run(name, func(t *testing.T) {
opt := tt.Opts
if opt == nil {
opt = func(o *v4.SignerOptions) {}
}
signer := New(opt)
if err := signer.SignRequest(tt.Input); err != nil {
t.Fatalf("expect no err, got %v", err)
}
req := tt.Input.Request
expectSignature(t, req, tt.Input.Credentials,
tt.ExpectPreamble, tt.ExpectSignedHeaders, tt.ExpectStringToSign,
tt.ExpectDate, tt.ExpectToken, tt.ExpectRegionSetHeader)
if host := req.Header.Get("Host"); req.Host != host {
t.Errorf("expect host header: %s != %s", req.Host, host)
}
})
}
}
// v4a signatures are random because ECDSA itself is random
// to verify the signature, we effectively have to formally verify the other
// side of the ECDSA calculation
//
// note that this implicitly verifies the correctness of derivePrivateKey,
// otherwise signatures wouldn't match
func expectSignature(
t *testing.T, signed *http.Request, creds credentials.Credentials,
expectPreamble, expectSignedHeaders string, // fixed header components
expectStrToSign string, // for manual signature verification
expectDate, expectToken, expectRegionSet string, // fixed headers
) {
t.Helper()
preamble, signedHeaders, signature, err := getSignature(signed)
if err != nil {
t.Fatalf("get signature: %v", err)
}
if expectPreamble != preamble {
t.Errorf("preamble:\n%s\n!=\n%s", expectPreamble, preamble)
}
if signedHeaders != expectSignedHeaders {
t.Errorf("signed headers:\n%s\n!=\n%s", expectSignedHeaders, signedHeaders)
}
priv, err := derivePrivateKey(creds)
if err != nil {
t.Fatalf("derive private key: %v", err)
}
ok, err := verifySignature(&priv.PublicKey, v4internal.Stosha(expectStrToSign), signature)
if err != nil {
t.Fatalf("verify signature: %v", err)
}
if !ok {
t.Errorf("signature mismatch")
}
}
func getSignature(r *http.Request) (
preamble, headers string, signature []byte, err error,
) {
auth := r.Header.Get("Authorization")
if len(auth) == 0 {
err = fmt.Errorf("no authorization header")
return
}
parts := strings.Split(auth, ", ")
if len(parts) != 3 {
err = fmt.Errorf("auth header is malformed: %q", auth)
return
}
sigpart := parts[2]
sigparts := strings.Split(sigpart, "=")
if len(sigparts) != 2 {
err = fmt.Errorf("Signature= component is malformed: %s", sigpart)
return
}
sig, err := hex.DecodeString(sigparts[1])
if err != nil {
err = fmt.Errorf("decode signature hex: %w", err)
return
}
return parts[0], parts[1], sig, nil
}
type readexploder struct{}
func (readexploder) Read([]byte) (int, error) {
return 0, fmt.Errorf("readexploder boom")
}
func TestSignRequest_SignStringError(t *testing.T) {
randReader := rand.Reader
rand.Reader = readexploder{}
defer func() { rand.Reader = randReader }()
s := New()
err := s.SignRequest(&SignRequestInput{
Request: newRequest(http.NoBody),
PayloadHash: v4.UnsignedPayload(),
Credentials: credsSession,
})
if err == nil {
t.Fatal("expect error but didn't get one")
}
if expect := "readexploder boom"; expect != err.Error() {
t.Errorf("error mismatch: %v != %v", expect, err.Error())
}
}
func TestSignRequestQueryString(t *testing.T) {
signer := New()
req := newRequest(nil)
req.URL.RawQuery = "existing=param"
err := signer.SignRequest(&SignRequestInput{
Request: req,
Credentials: credsNoSession,
Service: "s3",
RegionSet: []string{"us-east-1"},
Time: time.Unix(1375315200, 0),
SignatureType: v4.SignatureTypeQueryString,
})
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
// Should not have Authorization header
if auth := req.Header.Get("Authorization"); auth != "" {
t.Errorf("expected no Authorization header, got %s", auth)
}
// Should have query parameters
query := req.URL.Query()
if query.Get("X-Amz-Algorithm") != "AWS4-ECDSA-P256-SHA256" {
t.Errorf("expected X-Amz-Algorithm=AWS4-ECDSA-P256-SHA256, got %s", query.Get("X-Amz-Algorithm"))
}
if !strings.Contains(query.Get("X-Amz-Credential"), "AKID/20130801/s3/aws4_request") {
t.Errorf("unexpected X-Amz-Credential: %s", query.Get("X-Amz-Credential"))
}
if query.Get("X-Amz-Date") != "20130801T000000Z" {
t.Errorf("expected X-Amz-Date=20130801T000000Z, got %s", query.Get("X-Amz-Date"))
}
if query.Get("X-Amz-SignedHeaders") == "" {
t.Error("expected X-Amz-SignedHeaders to be set")
}
if query.Get("X-Amz-Signature") == "" {
t.Error("expected X-Amz-Signature to be set")
}
// Should preserve existing query params
if query.Get("existing") != "param" {
t.Errorf("expected existing=param, got existing=%s", query.Get("existing"))
}
}
func TestSignRequestHeaderDoesNotAlterQueryString(t *testing.T) {
signer := New()
req := newRequest(nil)
req.URL.RawQuery = "existing=param&another=value"
originalQuery := req.URL.RawQuery
err := signer.SignRequest(&SignRequestInput{
Request: req,
Credentials: credsNoSession,
Service: "s3",
RegionSet: []string{"us-east-1"},
Time: time.Unix(1375315200, 0),
SignatureType: v4.SignatureTypeHeader,
})
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
// Should have Authorization header
if auth := req.Header.Get("Authorization"); auth == "" {
t.Error("expected Authorization header to be set")
}
// Query string should be unchanged
if req.URL.RawQuery != originalQuery {
t.Errorf("expected query string unchanged, got %s, want %s", req.URL.RawQuery, originalQuery)
}
}
smithy-go-1.23.2/aws-http-auth/v4/ 0000775 0000000 0000000 00000000000 15102203672 0016554 5 ustar 00root root 0000000 0000000 smithy-go-1.23.2/aws-http-auth/v4/v4.go 0000664 0000000 0000000 00000003400 15102203672 0017431 0 ustar 00root root 0000000 0000000 // Package v4 exposes common APIs for AWS Signature Version 4.
package v4
// SignatureType specifies how the signature is transmitted.
type SignatureType int
const (
// SignatureTypeHeader transmits signature via Authorization header (default).
SignatureTypeHeader SignatureType = iota
// SignatureTypeQueryString transmits signature via query parameters.
// See https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
SignatureTypeQueryString
)
// SignerOption applies configuration to a signer.
type SignerOption func(*SignerOptions)
// SignerOptions configures SigV4.
type SignerOptions struct {
// Rules to determine what headers are signed.
//
// By default, the signer will only include the minimum required headers:
// - Host
// - X-Amz-*
HeaderRules SignedHeaderRules
// Setting this flag will instead cause the signer to use the
// UNSIGNED-PAYLOAD sentinel if a hash is not explicitly provided.
DisableImplicitPayloadHashing bool
// Disables the automatic escaping of the URI path of the request for the
// siganture's canonical string's path.
//
// Amazon S3 is an example of a service that requires this setting.
DisableDoublePathEscape bool
// Adds the X-Amz-Content-Sha256 header to signed requests.
//
// Amazon S3 is an example of a service that requires this setting.
AddPayloadHashHeader bool
}
// SignedHeaderRules determines whether a request header should be included in
// the calculated signature.
//
// By convention, ShouldSign is invoked with lowercase values.
type SignedHeaderRules interface {
IsSigned(string) bool
}
// UnsignedPayload provides the sentinel value for a payload hash to indicate
// that a request's payload is unsigned.
func UnsignedPayload() []byte {
return []byte("UNSIGNED-PAYLOAD")
}
smithy-go-1.23.2/changelog-template.json 0000664 0000000 0000000 00000000313 15102203672 0020145 0 ustar 00root root 0000000 0000000 {
"id": "00000000-0000-0000-0000-000000000000",
"type": "feature|bugfix|dependency",
"description": "Description of your changes",
"collapse": false,
"modules": [
"."
]
}
smithy-go-1.23.2/codegen/ 0000775 0000000 0000000 00000000000 15102203672 0015121 5 ustar 00root root 0000000 0000000 smithy-go-1.23.2/codegen/.gitignore 0000664 0000000 0000000 00000000262 15102203672 0017111 0 ustar 00root root 0000000 0000000 # Eclipse
.classpath
.project
.settings/
# Intellij
.idea/
*.iml
*.iws
# Mac
.DS_Store
# Maven
target/
**/dependency-reduced-pom.xml
# Gradle
/.gradle
build/
*/out/
*/*/out/
smithy-go-1.23.2/codegen/README.md 0000664 0000000 0000000 00000001656 15102203672 0016410 0 ustar 00root root 0000000 0000000 ## Smithy Go
Smithy code generators for Go.
**WARNING: All interfaces are subject to change.**
## Setup
> Note: These steps assume your current working directory is `smithy-go/codegen` (the directory that contains this README)
1. Install Java 17. If you have multiple versions of Java installed on OSX, use `export JAVA_HOME=`/usr/libexec/java_home -v 17``. **Java 14 is not compatible with Grade 5.x**
2. Install Go 1.17 (follow directions for your platform)
3. Use `./gradlew` to automatically install the correct gradle version. **`brew install gradle` will install Gradle 6.x which is not compatible.**
4. `./gradlew test` to run the basic tests.
5. `cd smithy-go-codegen-test; ../gradlew build` to run the codegen tests.
> Note: since gradlew is a script within `smithy-go/codegen`, you need to use an appropriate relative path to access it from within the repo.
## License
This project is licensed under the Apache-2.0 License.
smithy-go-1.23.2/codegen/build.gradle.kts 0000664 0000000 0000000 00000017554 15102203672 0020214 0 ustar 00root root 0000000 0000000 /*
* Copyright 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.
*/
plugins {
`java-library`
`maven-publish`
signing
jacoco
id("com.github.spotbugs") version "4.7.4"
id("io.codearte.nexus-staging") version "0.30.0"
}
allprojects {
group = "software.amazon.smithy.go"
version = "0.1.0"
}
// The root project doesn't produce a JAR.
tasks["jar"].enabled = false
// Load the Sonatype user/password for use in publishing tasks.
val sonatypeUser: String? by project
val sonatypePassword: String? by project
/*
* Sonatype Staging Finalization
* ====================================================
*
* When publishing to Maven Central, we need to close the staging
* repository and release the artifacts after they have been
* validated. This configuration is for the root project because
* it operates at the "group" level.
*/
if (sonatypeUser != null && sonatypePassword != null) {
apply(plugin = "io.codearte.nexus-staging")
nexusStaging {
packageGroup = "software.amazon"
stagingProfileId = "e789115b6c941"
username = sonatypeUser
password = sonatypePassword
}
}
repositories {
mavenLocal()
mavenCentral()
}
subprojects {
val subproject = this
/*
* Java
* ====================================================
*/
if (subproject.name != "smithy-go-codegen-test") {
apply(plugin = "java-library")
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
tasks.withType {
options.encoding = "UTF-8"
}
// Use Junit5's test runner.
tasks.withType {
useJUnitPlatform()
}
// Apply junit 5 and hamcrest test dependencies to all java projects.
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-api:5.4.0")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.4.0")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.4.0")
testImplementation("org.hamcrest:hamcrest:2.1")
}
// Reusable license copySpec
val licenseSpec = copySpec {
from("${project.rootDir}/LICENSE")
from("${project.rootDir}/NOTICE")
}
// Set up tasks that build source and javadoc jars.
tasks.register("sourcesJar") {
metaInf.with(licenseSpec)
from(sourceSets.main.get().allJava)
archiveClassifier.set("sources")
}
tasks.register("javadocJar") {
metaInf.with(licenseSpec)
from(tasks.javadoc)
archiveClassifier.set("javadoc")
}
// Configure jars to include license related info
tasks.jar {
metaInf.with(licenseSpec)
inputs.property("moduleName", subproject.extra["moduleName"])
manifest {
attributes["Automatic-Module-Name"] = subproject.extra["moduleName"]
}
}
// Always run javadoc after build.
tasks["build"].finalizedBy(tasks["javadoc"])
/*
* Maven
* ====================================================
*/
apply(plugin = "maven-publish")
apply(plugin = "signing")
repositories {
mavenLocal()
mavenCentral()
}
publishing {
repositories {
mavenCentral {
url = uri("https://aws.oss.sonatype.org/service/local/staging/deploy/maven2/")
credentials {
username = sonatypeUser
password = sonatypePassword
}
}
}
publications {
create("mavenJava") {
from(components["java"])
// Ship the source and javadoc jars.
artifact(tasks["sourcesJar"])
artifact(tasks["javadocJar"])
// Include extra information in the POMs.
afterEvaluate {
pom {
name.set(subproject.extra["displayName"].toString())
description.set(subproject.description)
url.set("https://github.com/awslabs/smithy")
licenses {
license {
name.set("Apache License 2.0")
url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
distribution.set("repo")
}
}
developers {
developer {
id.set("smithy")
name.set("Smithy")
organization.set("Amazon Web Services")
organizationUrl.set("https://aws.amazon.com")
roles.add("developer")
}
}
scm {
url.set("https://github.com/awslabs/smithy.git")
}
}
}
}
}
}
// Don't sign the artifacts if we didn't get a key and password to use.
val signingKey: String? by project
val signingPassword: String? by project
if (signingKey != null && signingPassword != null) {
signing {
useInMemoryPgpKeys(signingKey, signingPassword)
sign(publishing.publications["mavenJava"])
}
}
/*
* Tests
* ====================================================
*
* Configure the running of tests.
*/
// Log on passed, skipped, and failed test events if the `-Plog-tests` property is set.
if (project.hasProperty("log-tests")) {
tasks.test {
testLogging.events("passed", "skipped", "failed")
}
}
/*
* Code coverage
* ====================================================
*/
apply(plugin = "jacoco")
// Always run the jacoco test report after testing.
tasks["test"].finalizedBy(tasks["jacocoTestReport"])
// Configure jacoco to generate an HTML report.
tasks.jacocoTestReport {
reports {
xml.isEnabled = false
csv.isEnabled = false
html.destination = file("$buildDir/reports/jacoco")
}
}
/*
* Spotbugs
* ====================================================
*/
apply(plugin = "com.github.spotbugs")
// We don't need to lint tests.
tasks["spotbugsTest"].enabled = false
// Configure the bug filter for spotbugs.
tasks.withType().configureEach {
effort.set(com.github.spotbugs.snom.Effort.MAX)
excludeFilter.set(file("${project.rootDir}/config/spotbugs/filter.xml"))
reports.maybeCreate("xml").isEnabled = false
reports.maybeCreate("html").isEnabled = true
}
}
}
smithy-go-1.23.2/codegen/config/ 0000775 0000000 0000000 00000000000 15102203672 0016366 5 ustar 00root root 0000000 0000000 smithy-go-1.23.2/codegen/config/spotbugs/ 0000775 0000000 0000000 00000000000 15102203672 0020234 5 ustar 00root root 0000000 0000000 smithy-go-1.23.2/codegen/config/spotbugs/filter.xml 0000664 0000000 0000000 00000001704 15102203672 0022245 0 ustar 00root root 0000000 0000000
smithy-go-1.23.2/codegen/go.mod 0000664 0000000 0000000 00000000061 15102203672 0016224 0 ustar 00root root 0000000 0000000 module github.com/aws/smithy-go/codegen
go 1.19
smithy-go-1.23.2/codegen/gradle.properties 0000664 0000000 0000000 00000000057 15102203672 0020477 0 ustar 00root root 0000000 0000000 smithyVersion=1.62.0
smithyGradleVersion=0.7.0
smithy-go-1.23.2/codegen/gradle/ 0000775 0000000 0000000 00000000000 15102203672 0016357 5 ustar 00root root 0000000 0000000 smithy-go-1.23.2/codegen/gradle/wrapper/ 0000775 0000000 0000000 00000000000 15102203672 0020037 5 ustar 00root root 0000000 0000000 smithy-go-1.23.2/codegen/gradle/wrapper/gradle-wrapper.jar 0000664 0000000 0000000 00000164220 15102203672 0023456 0 ustar 00root root 0000000 0000000 PK
A META-INF/ PK
A m±>=@ ? META-INF/MANIFEST.MFóMÌËLK-.Ñ
K-*ÎÌϳR0Ô3àåòÌ-ÈIÍMÍ+I,
ê†d–ä¤Z)¸%¦ä¤*„%¤ñrñr PK
A org/ PK
A org/gradle/ PK
A org/gradle/wrapper/ PK
A •%Ó¦¹ / org/gradle/wrapper/BootstrapMainStarter$1.classRËnÓ@=Ó¸u1¦„ôEy”@_IikÁ6ˆU‘@.,RuØLì!™ÊGã ü6 ±àø(ı ]t1ssÎÕ¹÷ÎÏ_ß xн 󸳈»îá¾M|4ži£ÝsZ«}.à穸k£ÞŒ‡=eÏd/c¦ç‰ÌÎ¥Õeié0“c“”=<ÚO¦õ±b»í+7.Pÿ£ômïB%œÀÚßM•
Ñ-°wźláƒ6i<øZ2³93Ööìèê³#ð‡Ò± w¹Úºl
hÂãâòø¹æx8F×èE´‚v~ÿÄ—ê9à½P%pw8Ð.ÑR3êSò ѵ²ÜAcî+jŸgèQEoN SzéÝB£z÷±Œ•ªÄjÅ\Ã:‡ÛØÀ
zQ^ÅÁoPK
A i,« $
- org/gradle/wrapper/BootstrapMainStarter.classVÛSWÿsÙ°,ˆ1*‰‚±BI /µV°V(Ð-m´Ú.É!¬nvéf£Ðjï½<÷·þ ö%Ø2µÓ—v¦“ãô;»IIb¼ÀÌÙ³ßõ÷]7ÿ>ýý1€£øIF 2^à C˜”0%cïÊðA•0#aVF—d\Æ{2æÇ¼Œd\ÁUq¼BNP®…p]ÂR2"¸!c7%|؉^|$AaQØÊ‹£ .aIÆ~ †¢„eeÊ4¹1´r™—‚§tSwN3øÉ+þŒUàÛUÝ䳕Ò"·çµEƒ(aÕÊkÆÍÖÅ{èw–u²‘T-»˜.ÚZÁàé»¶¶²Âíô9ËrʽÌhº™u4Ûáö8C ,®‰ëê-펖64³˜Î:¶nÇ=Šn¥/èwñhv‘<ìl#Ì {.'aénVfèð¸ÓšMèó–éðUÇ
[µ´'bÔÓ0¹“^˜SXB¹D¨]ÃŽß.I¸üî,[†Xƒ€Í—žwÒ$w7'nm¥ž¼H«ÙSçI¾‹r•¿=£¸btr6±šç+Žn™e ·¨:KºYPµŠ™_æ¶_¢%uÉPŒ-q
iûõgôÅóºMÐ-{\fŠç‚I‰jWË”0 ` ƒ
NãÊÂV8sÓÑKüØ«bâ¦åÄ
ʇÃãÎ2_t+¯c‹OŸ‹ëf\Ðɧ¾XâCƒå¡CÏ–“K‹·„Û
”ò³ç9%¥ 9
,¬HøX
Ê©£ ‚;
îŠc «Ö|‚OÜÃ}Ê.µwÊë¦TkÊ)¢à3|N*Z‚RÛRU_àK_ákê¤zÒ3†U梾
¾Á`\4‹Tä[ßá‚ïñ€¦ì‡làˆ‚ð#ÃÐ+*0ô>¯iò¶X
åìiFZ$K–]Òh²O&žÕÆñõêVoÑæ‰ÞÕFW¬€c-ÌM‘ßD²qh§Æ›ë:U“TŸ‘T…ñ–ÊÔ;#RäNvìðRózH$['tk=ÊeÍæ&Å›¬o²º§çh‰8ò6¿lsÜ•¯ØÂPý=ÒäÖ£
ðeîdÚ,±Xâî:º×¶Ø`»Ô¶Ùl"´zñϼ¤”žNòEë/¨›w¬Û¼¥-j=𒶨‘¨¨y1/[Ö\ûf òUÚe÷“v¡S/7,¶ƒxBY|·šÍ™ZI,<ñJ¶ìG x âÏ&–¯Ó[šžŒžá
°_é²
Ct]b :O IÓ“a£$EÊì$úî>¶ÙCÿ w¾\Øïÿœo4ûÁ
Hn"”#RGÎw¨
¹ŠNb(Utm {ÌÿÛ£þ*z¢þðŽ*Âch ¼“¤#9_xW¶ŠÝcÁhyGè}ÏÕ¨ÿ/ô®CŽúC”A€ö» Sè¤s/A݇ô¡Ÿ~8Œ#ŽE¯Rü7q%нBÑß#
ܤ@-8q;Dv˜{Kã0À*ŽÐÍG¶ÎÑ¢cäM%Ê8N¼›äãMœ „½UK•Ç;I¼1¢D°í ú%Ğↄ„ˆDvN¹˜Þ¦ŸDôí©Õâg¢ùèy|±Üpxïö©#Uôc€ýëèÙÄþÜâU˜ý]£ý¿@õ}Ⱥ¥iˆP8T©(Õj@§ ö1*BŽ“Ü ºqg)