Skip to content

Commit

Permalink
fix: Signature verification on write requests
Browse files Browse the repository at this point in the history
Co-Authored-By: Sebastián Vargas <[email protected]>
  • Loading branch information
achetronic and sebastocorp committed Oct 17, 2024
1 parent 4348c74 commit 70c47b9
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 87 deletions.
4 changes: 2 additions & 2 deletions charts/bifrost/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ type: application
description: >-
A Helm chart for bifrost, a lightweight S3 proxy that re-signs requests between
your customers and buckets, supporting multiple client authentication methods.
version: 0.4.0 # chart version
appVersion: 0.4.0 # application version
version: 0.4.1 # chart version
appVersion: 0.4.1 # application version
kubeVersion: ">=1.22.0-0" # kubernetes version
home: https://github.com/freepik-company/bifrost
sources:
Expand Down
64 changes: 0 additions & 64 deletions docs/samples/b.yaml

This file was deleted.

27 changes: 19 additions & 8 deletions internal/httpserver/httpserver.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package httpserver

import (
"bytes"
"crypto/tls"
"fmt"
"io"
Expand Down Expand Up @@ -132,17 +133,16 @@ func getBucketCredential(request *http.Request) (*api.BucketCredentialT, error)

// isValidSignature verifies the signature of the request using the provided bucket credential
// It produces another signature over the same request and compares them
func isValidSignature(request *http.Request, bucketCredential *api.BucketCredentialT) (bool, error) {
func isValidSignature(bucketCredential *api.BucketCredentialT, request *http.Request, requestBody *[]byte) (bool, error) {

// Craft a new request to be fake-signed
simulatedReq, err := http.NewRequest(request.Method, request.URL.String(), request.Body)
simulatedReq, err := http.NewRequest(request.Method, request.URL.String(), io.NopCloser(bytes.NewReader(*requestBody)))
if err != nil {
return false, fmt.Errorf("failed to create simulated request: %s", err.Error())
}

// Copy relevant data from original request
simulatedReq.Host = request.Host
simulatedReq.Body = request.Body

// Copy only the signed headers from the original request
originAuthSignedHeadersParam, err := getRequestAuthParam(request, "signedheaders")
Expand All @@ -151,12 +151,13 @@ func isValidSignature(request *http.Request, bucketCredential *api.BucketCredent
}

requestSignedHeaders := strings.Split(originAuthSignedHeadersParam, ";")

for _, signedHeader := range requestSignedHeaders {
simulatedReq.Header.Add(signedHeader, request.Header.Get(signedHeader))
}

// Sign the faked request with provided credentials
err = signature.SignS3Version4(simulatedReq, bucketCredential.AwsConfig)
err = signature.SignS3Version4(bucketCredential.AwsConfig, simulatedReq, requestBody)
if err != nil {
return false, fmt.Errorf("failed to sign simulated request: %s", err.Error())
}
Expand Down Expand Up @@ -209,6 +210,16 @@ func (s *HttpServer) handleRequest(response http.ResponseWriter, request *http.R
}
}()

// Consume the body to have it stored for later use
var bodyBytes []byte
if request.Body != nil {
bodyBytes, err = io.ReadAll(request.Body)
if err != nil {
err = fmt.Errorf("failed to read request body: %s", err.Error())
return
}
}

// Get a proper bucket credential for the current request
bucketCredential, localErr := getBucketCredential(request)
if localErr != nil {
Expand All @@ -220,7 +231,7 @@ func (s *HttpServer) handleRequest(response http.ResponseWriter, request *http.R
if globals.Application.Config.Authentication.ClientCredentials.Type == "s3" &&
globals.Application.Config.Authentication.ClientCredentials.S3.SignatureVerification {

isValid, localErr := isValidSignature(request, bucketCredential)
isValid, localErr := isValidSignature(bucketCredential, request, &bodyBytes)
if localErr != nil {
err = fmt.Errorf("failed to validate request signature: %s", localErr.Error())
return
Expand Down Expand Up @@ -250,18 +261,18 @@ func (s *HttpServer) handleRequest(response http.ResponseWriter, request *http.R
// Create a bare new request against the target
// This is a clone of the original request with some params changed such as: the Host and the URL
targetHostString := strings.Join([]string{globals.Application.Config.Target.Host, globals.Application.Config.Target.Port}, ":")
_ = targetHostString

targetRequestUrl := fmt.Sprintf("%s://%s%s",
globals.Application.Config.Target.Scheme, targetHostString, request.URL.Path+"?"+request.URL.RawQuery)

targetReq, localErr := http.NewRequest(request.Method, targetRequestUrl, request.Body)
targetReq, localErr := http.NewRequest(request.Method, targetRequestUrl, io.NopCloser(bytes.NewReader(bodyBytes)))
if localErr != nil {
err = fmt.Errorf("failed to create request: %s", localErr.Error())
return
}
targetReq.Host = targetHostString
targetReq.Header = request.Header
targetReq.ContentLength = int64(len(bodyBytes))

// Apply the modifiers to the request before sending it (order matters)
for _, modifier := range globals.Application.Config.Modifiers {
Expand All @@ -275,7 +286,7 @@ func (s *HttpServer) handleRequest(response http.ResponseWriter, request *http.R
}

// Sign the request
localErr = signature.SignS3Version4(targetReq, bucketCredential.AwsConfig)
localErr = signature.SignS3Version4(bucketCredential.AwsConfig, targetReq, &bodyBytes)
if localErr != nil {
err = fmt.Errorf("failed to sign request: %s", localErr.Error())
return
Expand Down
21 changes: 8 additions & 13 deletions internal/signature/signature.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package signature

import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"fmt"
"io"
"log"
"net/http"
"strings"
Expand All @@ -20,7 +18,7 @@ import (

// Sign a S3 request using AWS Signature Version 4.
// Ref:
func SignS3Version4(req *http.Request, cfg *aws.Config) (err error) {
func SignS3Version4(cfg *aws.Config, req *http.Request, requestBody *[]byte) (err error) {

awsCredentials, err := cfg.Credentials.Retrieve(context.TODO())
if err != nil {
Expand All @@ -29,22 +27,19 @@ func SignS3Version4(req *http.Request, cfg *aws.Config) (err error) {

//
payloadHash := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
if req.Body != nil {
requestPayload, err := io.ReadAll(req.Body)
if err != nil {
return fmt.Errorf("error reading request body: %s", err.Error())
}

payloadHash = fmt.Sprintf("%x", sha256.Sum256(requestPayload))
req.Body = io.NopCloser(bytes.NewReader(requestPayload))
if len(*requestBody) > 0 {
payloadHash = fmt.Sprintf("%x", sha256.Sum256(*requestBody))
}

req.Header.Set("x-amz-content-sha256", payloadHash)

// Crear un nuevo firmador de la versión 4 (S3 utiliza la versión 4 de las firmas)
// Restore the content-length to the original value
req.ContentLength = int64(len(*requestBody))

// Create a new signer for the version 4 (S3 uses version 4 of the signatures)
signer := v4.NewSigner()

// Generar la firma de la solicitud usando las credenciales cargadas
// Generate the signature for the request using the loaded credentials
err = signer.SignHTTP(context.TODO(), awsCredentials, req, payloadHash, "s3", cfg.Region, time.Now())
if err != nil {
return fmt.Errorf("error signing request: %s", err.Error())
Expand Down

0 comments on commit 70c47b9

Please sign in to comment.