Documentation
¶
Overview ¶
Package awsig implements server-side verification of
- AWS Signature Version 4 (SigV4) - AWS Signature Version 2 (SigV2)
meant for building AWS-compatible services.
Example ¶
package main
import (
"context"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"time"
)
// (1) Implement awsig.CredentialsProvider:
type (
MyAuthData struct {
AccessKeyID string
}
MyCredentialsProvider struct {
secretAccessKeys map[string]string
}
)
func (p *MyCredentialsProvider) Provide(_ context.Context, accessKeyID string) (secretAccessKey string, _ MyAuthData, _ error) {
var data MyAuthData
secretAccessKey, ok := p.secretAccessKeys[accessKeyID]
if !ok {
return "", data, ErrInvalidAccessKeyID
}
data.AccessKeyID = accessKeyID
return secretAccessKey, data, nil
}
func NewMyCredentialsProvider() *MyCredentialsProvider {
return &MyCredentialsProvider{
secretAccessKeys: map[string]string{
"AKIAIOSFODNN7EXAMPLE": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
},
}
}
func main() {
// (2) Create a combined V2/V4 verifier for S3 in us-east-1.
// You can also create a standalone V2-only or V4-only verifier:
v2v4 := NewV2V4(NewMyCredentialsProvider(), V4Config{
Region: "us-east-1",
Service: "s3",
})
h := func(w http.ResponseWriter, r *http.Request) {
// (3) Verify the incoming request:
vr, err := v2v4.Verify(r, "virtual-hosted-bucket-indication-for-v2")
if err != nil {
errToHTTPError(w, err)
return
}
fmt.Printf("verified the incoming request with Access Key ID=%s\n", vr.AuthData().AccessKeyID)
// (4) If the request is a multipart/form-data POST, you can access the parsed form values:
if vr.PostForm() != nil {
fmt.Println("this request is a multipart/form-data POST")
}
//
// Important: if you intend to read the body, use vr.Reader() instead of r.Body.
//
// (5) Declare which checksums you want to be verified/computed:
var reqs []ChecksumRequest
{
req, err := NewChecksumRequest(AlgorithmSHA1, "CgqfKmdylCVXq1NV12r0Qvj2XgE=")
if err != nil {
errToHTTPError(w, err)
return
}
reqs = append(reqs, req)
}
if r.Header.Get("x-amz-trailer") == "x-amz-checksum-crc32" {
req, err := NewTrailingChecksumRequest(AlgorithmCRC32)
if err != nil {
errToHTTPError(w, err)
return
}
reqs = append(reqs, req)
}
// (6) Read the body. Notes:
//
// - requested checksums are verified automatically
// - if the request includes a trailing checksum header, at least one checksum must be requested
// - if not explicitly requested:
// - MD5 is always computed and available after reading
// - SHA256 is computed and available after reading, depending on the request type
body, err := vr.Reader(reqs...)
if err != nil {
errToHTTPError(w, err)
return
}
_, err = io.Copy(w, body) // copy or do something else with the body
if err != nil {
errToHTTPError(w, err)
return
}
// (7) Access computed/verified checksums as needed:
checksums, err := body.Checksums()
if err != nil {
errToHTTPError(w, err)
return
}
for algo, sum := range checksums {
fmt.Printf("%s of the received content is %x\n", algo, sum)
}
// Perform additional application logic as needed…
}
rec := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPut, "https://bucket.s3-compatible.provider.com/object.txt", strings.NewReader("Hello, World!"))
req.Header.Set("Authorization", "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20000101/us-east-1/s3/aws4_request, SignedHeaders=content-length;host;x-amz-content-sha256;x-amz-date, Signature=f7e9ff55dfc3b67c3ad92147a3056687e986e907e36f7971eaea693065bf999e")
req.Header.Set("Content-Length", "13")
req.Header.Set("X-Amz-Content-Sha256", "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f")
req.Header.Set("X-Amz-Date", "20000101T000000Z")
serveHTTPAt(v2v4, h, rec, req, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC))
fmt.Println(rec.Body.String())
}
func errToHTTPError(w http.ResponseWriter, _ error) {
defaultCode := http.StatusInternalServerError
// TODO: match awsig errors to HTTP codes
http.Error(w, http.StatusText(defaultCode), defaultCode)
}
func serveHTTPAt[T any](v *V2V4[T], h http.HandlerFunc, w http.ResponseWriter, r *http.Request, t time.Time) {
prev2 := v.v2.now
prev4 := v.v4.now
now := func() time.Time { return t }
v.v2.now = now
v.v4.now = now
h(w, r)
v.v2.now = prev2
v.v4.now = prev4
}
Output: verified the incoming request with Access Key ID=AKIAIOSFODNN7EXAMPLE md5 of the received content is 65a8e27d8879283831b664bd8b7f0ad4 sha1 of the received content is 0a0a9f2a6772942557ab5355d76af442f8f65e01 sha256 of the received content is dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f Hello, World!
Index ¶
- Variables
- type ChecksumAlgorithm
- type ChecksumMismatch
- type ChecksumMismatchError
- type ChecksumRequest
- type CredentialsProvider
- type PostForm
- func (f PostForm) Add(key string, value PostFormElement)
- func (f PostForm) Clone() PostForm
- func (f PostForm) Del(key string)
- func (f PostForm) FileName() string
- func (f PostForm) Get(key string) PostFormElement
- func (f PostForm) Has(key string) bool
- func (f PostForm) Set(key string, value PostFormElement)
- func (f PostForm) Values(key string) []PostFormElement
- type PostFormElement
- type Reader
- type V2
- type V2V4
- type V2VerifiedRequest
- type V4
- type V4Config
- type V4VerifiedRequest
- type VerifiedRequest
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrAccessDenied indicates the AccessDenied error code. ErrAccessDenied = errors.New("access denied") // ErrAuthorizationHeaderMalformed indicates the AuthorizationHeaderMalformed error code. ErrAuthorizationHeaderMalformed = errors.New("the authorization header that you provided is not valid") // ErrBadDigest indicates the BadDigest error code. ErrBadDigest = errors.New("the Content-MD5 or checksum value that you specified did not match what the server received") // ErrContentLengthWithTransferEncoding indicates that both the Content-Length and Transfer-Encoding headers were provided. ErrContentLengthWithTransferEncoding = errors.New("the Content-Length and Transfer-Encoding headers must not both be provided") // ErrEntityTooLarge indicates the EntityTooLarge error code. ErrEntityTooLarge = errors.New("your proposed upload exceeds the maximum allowed object size") // ErrEntityTooSmall indicates the EntityTooSmall error code. ErrEntityTooSmall = errors.New("your proposed upload is smaller than the minimum allowed object size") // ErrInvalidAccessKeyID indicates the InvalidAccessKeyID error code. ErrInvalidAccessKeyID = errors.New("the AWS access key ID that you provided does not exist in our records") // ErrInvalidArgument indicates the InvalidArgument error code. ErrInvalidArgument = errors.New("invalid argument") // ErrInvalidDateHeader indicates that the Date or X-Amz-Date header is not valid. ErrInvalidDateHeader = errors.New("AWS authentication requires a valid Date or x-amz-date header") // ErrInvalidDigest indicates the InvalidDigest error code. ErrInvalidDigest = errors.New("the Content-MD5 or checksum value that you specified is not valid") // ErrInvalidPOSTDate indicates that the X-Amz-Date form field is malformed. ErrInvalidPOSTDate = errors.New("the X-Amz-Date form field does not contain a valid date") // ErrInvalidPresignedDate indicates that the date provided in a presigned URL is malformed. ErrInvalidPresignedDate = errors.New("the X-Amz-Date query parameter does not contain a valid date") // ErrInvalidPresignedExpiration indicates that the expiration provided in a presigned URL is not a valid integer. ErrInvalidPresignedExpiration = errors.New("the X-Amz-Expires query parameter does not contain a valid integer") // ErrInvalidPresignedXAmzContentSHA256 is returned when attempting to read from the body of a presigned request // that provided an invalid value for the X-Amz-Content-Sha256 header. ErrInvalidPresignedXAmzContentSHA256 = errors.New("the provided 'x-amz-content-sha256' header does not match what was computed") // ErrInvalidRequest indicates the InvalidRequest error code. ErrInvalidRequest = errors.New("invalid request") // ErrInvalidSignature indicates the InvalidSignature error code. ErrInvalidSignature = errors.New("the request signature that the server calculated does not match the signature that you provided") // ErrInvalidXAmzContentSHA256 indicates that the X-Amz-Content-Sha256 header has an invalid value. ErrInvalidXAmzContentSHA256 = errors.New("the x-amz-content-sha256 header does not contain a valid value") // ErrInvalidXAmzDecodedContentSHA256 indicates that the X-Amz-Decoded-Content-Length header has an invalid value. ErrInvalidXAmzDecodedContentSHA256 = errors.New("the x-amz-decoded-content-length header does not contain a valid integer") // ErrMalformedPOSTRequest indicates that a POST request is malformed. ErrMalformedPOSTRequest = errors.New("unable to parse multipart form data") // ErrMissingContentLength indicates the MissingContentLength error code. ErrMissingContentLength = errors.New("you must provide the Content-Length HTTP header") // ErrMissingPOSTPolicy indicates that the POST policy was not provided. ErrMissingPOSTPolicy = errors.New("the Policy form field is missing") // ErrMissingSecurityHeader indicates the MissingSecurityHeader error code. ErrMissingSecurityHeader = errors.New("your request is missing a required header") // ErrNegativePresignedExpiration indicates that the expiration provided in a presigned URL is negative integer. ErrNegativePresignedExpiration = errors.New("the X-Amz-Expires query parameter is negative") // ErrNotImplemented indicates the NotImplemented error code. ErrNotImplemented = errors.New("a header that you provided implies functionality that is not implemented") // ErrPresignedExpirationTooLarge indicates that the expiration provided in a presigned URL is too large. ErrPresignedExpirationTooLarge = errors.New("the X-Amz-Expires query parameter exceeds the maximum of 604800 seconds (7 days)") // ErrRequestExpired indicates that the request's expiration date has passed. ErrRequestExpired = errors.New("the request has expired") // ErrRequestNotYetValid indicates that the request's date is in the future. ErrRequestNotYetValid = errors.New("the request is not yet valid") // ErrRequestTimeTooSkewed indicates the RequestTimeTooSkewed error code. ErrRequestTimeTooSkewed = errors.New("the difference between the request time and the server's time is too large") // ErrSignatureDoesNotMatch indicates the SignatureDoesNotMatch error code. ErrSignatureDoesNotMatch = errors.New("the request signature that the server calculated does not match the signature that you provided") // ErrUnsupportedSignature indicates the UnsupportedSignature error code. ErrUnsupportedSignature = errors.New("the provided request is signed with an unsupported STS Token version or the signature version is not supported") )
Functions ¶
This section is empty.
Types ¶
type ChecksumAlgorithm ¶
type ChecksumAlgorithm int
ChecksumAlgorithm represents different checksum algorithms supported by this package.
const ( // AlgorithmCRC32 represents the CRC-32 checksum algorithm. AlgorithmCRC32 ChecksumAlgorithm = iota // AlgorithmCRC32C represents the CRC-32C checksum algorithm. AlgorithmCRC32C // AlgorithmCRC64NVME represents the CRC-64/NVME checksum algorithm. AlgorithmCRC64NVME // AlgorithmMD5 represents the MD5 checksum algorithm. AlgorithmMD5 // AlgorithmSHA1 represents the SHA-1 checksum algorithm. AlgorithmSHA1 // AlgorithmSHA256 represents the SHA-256 checksum algorithm. AlgorithmSHA256 )
func (ChecksumAlgorithm) String ¶
func (a ChecksumAlgorithm) String() string
type ChecksumMismatch ¶
type ChecksumMismatch struct {
Algorithm ChecksumAlgorithm
ClientChecksum []byte
CalculatedChecksum []byte
// IsContentSHA256 indicates whether the expected checksum was specified in the X-Amz-Content-Sha256 header.
// This can only be true if ChecksumAlgorithm is AlgorithmSHA256.
IsContentSHA256 bool
}
ChecksumMismatch contains information about a mismatch between a computed checksum and client-provided checksum.
type ChecksumMismatchError ¶
type ChecksumMismatchError struct {
Mismatches []ChecksumMismatch
}
ChecksumMismatchError is the error used when a set of computed checksums don't match those provided by the client.
func (ChecksumMismatchError) Error ¶
func (err ChecksumMismatchError) Error() string
Error implements the error interface.
type ChecksumRequest ¶
type ChecksumRequest struct {
// contains filtered or unexported fields
}
ChecksumRequest represents a request to compute or verify a specific checksum.
func NewChecksumRequest ¶
func NewChecksumRequest(algorithm ChecksumAlgorithm, encodedValue string) (ChecksumRequest, error)
NewChecksumRequest creates a new ChecksumRequest with the specified algorithm and encoded value.
func NewTrailingChecksumRequest ¶
func NewTrailingChecksumRequest(algorithm ChecksumAlgorithm) (ChecksumRequest, error)
NewTrailingChecksumRequest creates a new ChecksumRequest for trailing checksum verification.
type CredentialsProvider ¶
type CredentialsProvider[T any] interface { Provide(ctx context.Context, accessKeyID string) (secretAccessKey string, data T, _ error) }
CredentialsProvider is the interface that all users of this package must implement. Provide is called by signature verifiers. If the given accessKeyID is unknown to the implementation, it should return zero values alongside the ErrInvalidAccessKeyID error.
type PostForm ¶
type PostForm map[string][]PostFormElement
PostForm maps a string key to a list of values. It represents a parsed multipart form data. Unlike in url.Values, the keys in PostForm are case-insensitive.
func (PostForm) Add ¶
func (f PostForm) Add(key string, value PostFormElement)
Add adds the value to key. It appends to any existing values associated with key.
func (PostForm) Get ¶
func (f PostForm) Get(key string) PostFormElement
Get gets the first value associated with the given key. If there are no values associated with the key, Get returns the empty PostFormElement.
func (PostForm) Set ¶
func (f PostForm) Set(key string, value PostFormElement)
Set sets the key to value. It replaces any existing values.
func (PostForm) Values ¶
func (f PostForm) Values(key string) []PostFormElement
Values returns all values associated with the given key. The returned slice is not a copy.
type PostFormElement ¶
type PostFormElement struct {
Headers textproto.MIMEHeader
Value string
}
PostFormElement represents a single element in a multipart form.
type Reader ¶
type Reader interface {
io.Reader
// Checksums returns computed checksums of the read data.
// Checksums are only available after reaching EOF.
// If called before reaching EOF, it returns an error.
//
// If not previously requested:
//
// - MD5 is always computed
// - SHA-256 is computed if the request has a hashed payload
Checksums() (map[ChecksumAlgorithm][]byte, error)
}
Reader is an io.Reader to be used to read the body of a verified request with optional checksum computation and auto-verification.
type V2 ¶
type V2[T any] struct { // contains filtered or unexported fields }
V2 implements AWS Signature Version 2 verification.
func NewV2 ¶
func NewV2[T any](provider CredentialsProvider[T]) *V2[T]
NewV2 creates a new V2 with the given provider.
type V2V4 ¶
type V2V4[T any] struct { // contains filtered or unexported fields }
V2V4 implements AWS Signature Version 2 and AWS Signature Version 4 verification.
type V2VerifiedRequest ¶
type V2VerifiedRequest[T any] struct { // contains filtered or unexported fields }
V2VerifiedRequest implements VerifiedRequest for AWS Signature Version 2.
func (*V2VerifiedRequest[T]) AuthData ¶
func (vr *V2VerifiedRequest[T]) AuthData() T
AuthData implements VerifiedRequest.
func (*V2VerifiedRequest[T]) PostForm ¶
func (vr *V2VerifiedRequest[T]) PostForm() PostForm
PostForm implements VerifiedRequest.
func (*V2VerifiedRequest[T]) Reader ¶
func (vr *V2VerifiedRequest[T]) Reader(reqs ...ChecksumRequest) (Reader, error)
Reader implements VerifiedRequest.
type V4 ¶
type V4[T any] struct { // contains filtered or unexported fields }
V4 implements AWS Signature Version 4 verification.
type V4VerifiedRequest ¶
type V4VerifiedRequest[T any] struct { // contains filtered or unexported fields }
V4VerifiedRequest implements VerifiedRequest for AWS Signature Version 4.
func (*V4VerifiedRequest[T]) AuthData ¶
func (vr *V4VerifiedRequest[T]) AuthData() T
AuthData implements VerifiedRequest.
func (*V4VerifiedRequest[T]) PostForm ¶
func (vr *V4VerifiedRequest[T]) PostForm() PostForm
PostForm implements VerifiedRequest.
func (*V4VerifiedRequest[T]) Reader ¶
func (vr *V4VerifiedRequest[T]) Reader(reqs ...ChecksumRequest) (Reader, error)
Reader implements VerifiedRequest.
type VerifiedRequest ¶
type VerifiedRequest[T any] interface { // AuthData returns data collected while providing credentials // via CredentialsProvider. AuthData's type is determined by the // generic type parameter T to allow flexibility. For example, a // caller that would need to access Access Key ID could call one // of the verifiers with T reserving space for Access Key ID, // which would be filled by the CredentialsProvider // implementation. AuthData() T // PostForm returns the parsed multipart form data if the // request is a POST with "multipart/form-data" Content-Type. PostForm() PostForm // Reader returns a Reader to read the body of the verified // request. Reader can be called multiple times, but only the // first call can request checksums. Checksum requests must have // distinct algorithms. If the request includes a trailing // checksum header, at least one checksum must be requested. Reader(...ChecksumRequest) (Reader, error) }
VerifiedRequest represents a successfully verified AWS Signature Version 4 or AWS Signature Version 2 request.