feat: [ISS-2950] Add Ability to authenticate with AWS redis services
This commit is contained in:
		
							parent
							
								
									3978b2f27f
								
							
						
					
					
						commit
						561fd232b5
					
				
							
								
								
									
										14
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										14
									
								
								go.mod
								
								
								
								
							|  | @ -45,6 +45,20 @@ require ( | |||
| require ( | ||||
| 	cloud.google.com/go/auth v0.16.2 // indirect | ||||
| 	cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect | ||||
| 	github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2/config v1.29.14 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect | ||||
| 	github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect | ||||
| 	github.com/aws/smithy-go v1.22.2 // indirect | ||||
| 	github.com/beorn7/perks v1.0.1 // indirect | ||||
| 	github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect | ||||
| 	github.com/cespare/xxhash/v2 v2.3.0 // indirect | ||||
|  |  | |||
							
								
								
									
										36
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										36
									
								
								go.sum
								
								
								
								
							|  | @ -14,6 +14,42 @@ github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGn | |||
| github.com/alicebob/miniredis/v2 v2.11.1/go.mod h1:UA48pmi7aSazcGAvcdKcBB49z521IC9VjTTRz2nIaJE= | ||||
| github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI= | ||||
| github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= | ||||
| github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= | ||||
| github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= | ||||
| github.com/aws/aws-sdk-go-v2/config v1.29.9 h1:Kg+fAYNaJeGXp1vmjtidss8O2uXIsXwaRqsQJKXVr+0= | ||||
| github.com/aws/aws-sdk-go-v2/config v1.29.9/go.mod h1:oU3jj2O53kgOU4TXq/yipt6ryiooYjlkqqVaZk7gY/U= | ||||
| github.com/aws/aws-sdk-go-v2/config v1.29.14 h1:f+eEi/2cKCg9pqKBoAIwRGzVb70MRKqWX4dg1BDcSJM= | ||||
| github.com/aws/aws-sdk-go-v2/config v1.29.14/go.mod h1:wVPHWcIFv3WO89w0rE10gzf17ZYy+UVS1Geq8Iei34g= | ||||
| github.com/aws/aws-sdk-go-v2/credentials v1.17.62 h1:fvtQY3zFzYJ9CfixuAQ96IxDrBajbBWGqjNTCa79ocU= | ||||
| github.com/aws/aws-sdk-go-v2/credentials v1.17.62/go.mod h1:ElETBxIQqcxej++Cs8GyPBbgMys5DgQPTwo7cUPDKt8= | ||||
| github.com/aws/aws-sdk-go-v2/credentials v1.17.67 h1:9KxtdcIA/5xPNQyZRgUSpYOE6j9Bc4+D7nZua0KGYOM= | ||||
| github.com/aws/aws-sdk-go-v2/credentials v1.17.67/go.mod h1:p3C44m+cfnbv763s52gCqrjaqyPikj9Sg47kUVaNZQQ= | ||||
| github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw= | ||||
| github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M= | ||||
| github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q= | ||||
| github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY= | ||||
| github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0= | ||||
| github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q= | ||||
| github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= | ||||
| github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= | ||||
| github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE= | ||||
| github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= | ||||
| github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM= | ||||
| github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY= | ||||
| github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 h1:8JdC7Gr9NROg1Rusk25IcZeTO59zLxsKgE0gkh5O6h0= | ||||
| github.com/aws/aws-sdk-go-v2/service/sso v1.25.1/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI= | ||||
| github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 h1:1Gw+9ajCV1jogloEv1RRnvfRFia2cL6c9cuKV2Ps+G8= | ||||
| github.com/aws/aws-sdk-go-v2/service/sso v1.25.3/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI= | ||||
| github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 h1:KwuLovgQPcdjNMfFt9OhUd9a2OwcOKhxfvF4glTzLuA= | ||||
| github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs= | ||||
| github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 h1:hXmVKytPfTy5axZ+fYbR5d0cFmC3JvwLm5kM83luako= | ||||
| github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs= | ||||
| github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 h1:PZV5W8yk4OtH1JAuhV2PXwwO9v5G5Aoj+eMCn4T+1Kc= | ||||
| github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4= | ||||
| github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 h1:1XuUZ8mYJw9B6lzAkXhqHlJd/XvaX32evhproijJEZY= | ||||
| github.com/aws/aws-sdk-go-v2/service/sts v1.33.19/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4= | ||||
| github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= | ||||
| github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= | ||||
| github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= | ||||
| github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= | ||||
| github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= | ||||
|  |  | |||
|  | @ -159,6 +159,10 @@ func NewFlagSet() *pflag.FlagSet { | |||
| 	flagSet.Bool("redis-use-cluster", false, "Connect to redis cluster. Must set --redis-cluster-connection-urls to use this feature") | ||||
| 	flagSet.StringSlice("redis-cluster-connection-urls", []string{}, "List of Redis cluster connection URLs (eg redis://[USER[:PASSWORD]@]HOST[:PORT]). Used in conjunction with --redis-use-cluster") | ||||
| 	flagSet.Int("redis-connection-idle-timeout", 0, "Redis connection idle timeout seconds, if Redis timeout option is non-zero, the --redis-connection-idle-timeout must be less then Redis timeout option") | ||||
| 	flagSet.Bool("redis-aws-use-iam-auth", false, "Use AWS IAM authentication for Redis. Must set --redis-aws-service-name, --redis-aws-cluster-name, & --redis-aws-username to use this feature") | ||||
| 	flagSet.String("redis-aws-service-name", "", "AWS service name for Redis IAM authentication -  `elasticache` or  `memorydb`") | ||||
| 	flagSet.String("redis-aws-cluster-name", "", "AWS cluster name for Redis IAM authentication") | ||||
| 	flagSet.String("redis-aws-username", "", "AWS username for Redis IAM authentication") | ||||
| 	flagSet.String("signature-key", "", "GAP-Signature request signature key (algorithm:secretkey)") | ||||
| 	flagSet.Bool("gcp-healthchecks", false, "Enable GCP/GKE healthcheck endpoints") | ||||
| 
 | ||||
|  |  | |||
|  | @ -30,6 +30,10 @@ type RedisStoreOptions struct { | |||
| 	SentinelMasterName     string   `flag:"redis-sentinel-master-name" cfg:"redis_sentinel_master_name"` | ||||
| 	SentinelConnectionURLs []string `flag:"redis-sentinel-connection-urls" cfg:"redis_sentinel_connection_urls"` | ||||
| 	UseCluster             bool     `flag:"redis-use-cluster" cfg:"redis_use_cluster"` | ||||
| 	UseAWSIAMAuth          bool     `flag:"redis-aws-use-iam-auth" cfg:"redis_aws_use_iam_auth"` | ||||
| 	AWSServiceName         string   `flag:"redis-aws-service-name" cfg:"redis_aws_service_name"` | ||||
| 	AWSClusterName         string   `flag:"redis-aws-cluster-name" cfg:"redis_aws_cluster_name"` | ||||
| 	AWSUsername            string   `flag:"redis-aws-username" cfg:"redis_aws_username"` | ||||
| 	ClusterConnectionURLs  []string `flag:"redis-cluster-connection-urls" cfg:"redis_cluster_connection_urls"` | ||||
| 	CAPath                 string   `flag:"redis-ca-path" cfg:"redis_ca_path"` | ||||
| 	InsecureSkipTLSVerify  bool     `flag:"redis-insecure-skip-tls-verify" cfg:"redis_insecure_skip_tls_verify"` | ||||
|  |  | |||
|  | @ -0,0 +1,105 @@ | |||
| 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 AuthTokenGenerator struct { | ||||
| 	serviceName string | ||||
| 	region      string | ||||
| 	req         *http.Request | ||||
| 
 | ||||
| 	credentials aws.Credentials | ||||
| 	signer      *v4.Signer | ||||
| } | ||||
| 
 | ||||
| func New(serviceName, clusterName, userName string) (*AuthTokenGenerator, error) { | ||||
| 
 | ||||
| 	ctx := context.Background() | ||||
| 	cfg, err := config.LoadDefaultConfig(ctx) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	credentials, err := cfg.Credentials.Retrieve(ctx) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if credentials.AccessKeyID == "" || credentials.SecretAccessKey == "" { | ||||
| 		return nil, fmt.Errorf("AccessKeyID or SecretAccessKey is empty") | ||||
| 	} | ||||
| 
 | ||||
| 	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 &AuthTokenGenerator{ | ||||
| 		serviceName: serviceName, | ||||
| 		region:      cfg.Region, | ||||
| 		req:         req, | ||||
| 		credentials: credentials, | ||||
| 		signer:      v4.NewSigner(), | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func (atg AuthTokenGenerator) Generate() (string, error) { | ||||
| 
 | ||||
| 	signedURL, _, err := atg.signer.PresignHTTP( | ||||
| 		context.Background(), | ||||
| 		atg.credentials, | ||||
| 		atg.req, | ||||
| 		hexEncodedSHA256EmptyString, | ||||
| 		atg.serviceName, | ||||
| 		atg.region, | ||||
| 		time.Now().UTC(), | ||||
| 	) | ||||
| 
 | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("AWS IAM request signing failed - %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	signedURL = strings.Replace(signedURL, "http://", "", 1) | ||||
| 
 | ||||
| 	return signedURL, nil | ||||
| } | ||||
|  | @ -12,6 +12,7 @@ import ( | |||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions" | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger" | ||||
| 	"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/persistence" | ||||
| 	awsAuth "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/redis/aws-iam" | ||||
| 	"github.com/redis/go-redis/v9" | ||||
| ) | ||||
| 
 | ||||
|  | @ -173,12 +174,34 @@ func buildStandaloneClient(opts options.RedisStoreOptions) (Client, error) { | |||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if err := setupAWSIAMAuth(opts, opt); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	opt.ConnMaxIdleTime = time.Duration(opts.IdleTimeout) * time.Second | ||||
| 
 | ||||
| 	client := redis.NewClient(opt) | ||||
| 	return newClient(client), nil | ||||
| } | ||||
| 
 | ||||
| func setupAWSIAMAuth(opts options.RedisStoreOptions, opt *redis.Options) error { | ||||
| 	if opts.AWSServiceName != "elasticache" && opts.AWSServiceName != "memorydb" { | ||||
| 		return fmt.Errorf("AWS IAM auth is only supported for elasticache and memorydb") | ||||
| 	} | ||||
| 	generator, err := awsAuth.New(opts.AWSServiceName, opts.AWSClusterName, opts.AWSUsername) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("error creating AWS IAM auth token generator: %v", err) | ||||
| 	} | ||||
| 	opt.CredentialsProvider = func() (username string, password string) { | ||||
| 		token, err := generator.Generate() | ||||
| 		if err != nil { | ||||
| 			logger.Errorf("error generating AWS IAM auth token: %v", err) | ||||
| 		} | ||||
| 		return opts.AWSUsername, token | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // setupTLSConfig sets the TLSConfig if the TLS option is given in redis.Options
 | ||||
| func setupTLSConfig(opts options.RedisStoreOptions, opt *redis.Options) error { | ||||
| 	if opts.InsecureSkipTLSVerify { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue