oauth2-proxy/pkg/sessions/redis/aws-iam/auth.go

106 lines
2.6 KiB
Go

package auth
import (
"context"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
"github.com/aws/aws-sdk-go-v2/config"
)
// This code was largely copied from this repo: https://github.com/build-on-aws/aws-redis-iam-auth-golang/tree/main
const (
// "The IAM authentication token is valid for 15 minutes"
// https://docs.aws.amazon.com/memorydb/latest/devguide/auth-iam.html#auth-iam-limits
tokenValiditySeconds = 900
connectAction = "connect"
// If the request has no payload you should use the hex encoded SHA-256 of an empty string as the payloadHash value.
hexEncodedSHA256EmptyString = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
)
type TokenGenerator interface {
GenerateToken() (string, error)
}
// IAMTokenGenerator generates an IAM token for AWS Redis authentication.
type iamTokenGenerator struct {
serviceName string
region string
req *http.Request
credentialsProvider aws.CredentialsProvider
signer *v4.Signer
}
// New creates a new IAMTokenGenerator instance
func New(serviceName, clusterName, userName string) (TokenGenerator, error) {
ctx := context.Background()
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return nil, err
}
queryParams := url.Values{
"Action": {connectAction},
"User": {userName},
"X-Amz-Expires": {strconv.FormatInt(int64(tokenValiditySeconds), 10)},
}
authURL := url.URL{
Host: clusterName,
Scheme: "http",
Path: "/",
RawQuery: queryParams.Encode(),
}
req, err := http.NewRequest(http.MethodGet, authURL.String(), nil)
if err != nil {
return nil, err
}
return &iamTokenGenerator{
serviceName: serviceName,
region: cfg.Region,
req: req,
credentialsProvider: cfg.Credentials,
signer: v4.NewSigner(),
}, nil
}
func (atg iamTokenGenerator) GenerateToken() (string, error) {
ctx := context.Background()
credentials, err := atg.credentialsProvider.Retrieve(ctx)
if err != nil {
return "", fmt.Errorf("AWS IAM credentials retrieval failed - %v", err)
}
signedURL, _, err := atg.signer.PresignHTTP(
ctx,
credentials,
atg.req,
hexEncodedSHA256EmptyString,
atg.serviceName,
atg.region,
time.Now().UTC(),
)
if err != nil {
return "", fmt.Errorf("AWS IAM request signing failed - %v", err)
}
// AWS expects the scheme to be removed before using as an auth token
// https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/auth-iam.html#auth-iam-Connecting
signedURL = strings.Replace(signedURL, "http://", "", 1)
return signedURL, nil
}