From 561fd232b5f7730d78ce893187f18c5afd27c7f3 Mon Sep 17 00:00:00 2001 From: William Will <10997562+willwill96@users.noreply.github.com> Date: Sat, 12 Apr 2025 00:02:01 +0000 Subject: [PATCH 01/17] feat: [ISS-2950] Add Ability to authenticate with AWS redis services --- go.mod | 14 ++++ go.sum | 36 ++++++++++ pkg/apis/options/options.go | 4 ++ pkg/apis/options/sessions.go | 4 ++ pkg/sessions/redis/aws-iam/auth.go | 105 +++++++++++++++++++++++++++++ pkg/sessions/redis/redis_store.go | 23 +++++++ 6 files changed, 186 insertions(+) create mode 100644 pkg/sessions/redis/aws-iam/auth.go diff --git a/go.mod b/go.mod index 24f316e4..31c9412a 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index caa8e2a0..3da39083 100644 --- a/go.sum +++ b/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= diff --git a/pkg/apis/options/options.go b/pkg/apis/options/options.go index 8fa72c7c..a5dd0d35 100644 --- a/pkg/apis/options/options.go +++ b/pkg/apis/options/options.go @@ -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") diff --git a/pkg/apis/options/sessions.go b/pkg/apis/options/sessions.go index c90c0ac2..0f4273f9 100644 --- a/pkg/apis/options/sessions.go +++ b/pkg/apis/options/sessions.go @@ -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"` diff --git a/pkg/sessions/redis/aws-iam/auth.go b/pkg/sessions/redis/aws-iam/auth.go new file mode 100644 index 00000000..a153d7c6 --- /dev/null +++ b/pkg/sessions/redis/aws-iam/auth.go @@ -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 +} diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index 4e846e9b..a8605db3 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -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 { From 3ac52f38531e9c560e598b125a52ade0a95b69dc Mon Sep 17 00:00:00 2001 From: William Will <10997562+willwill96@users.noreply.github.com> Date: Wed, 16 Apr 2025 04:38:34 +0000 Subject: [PATCH 02/17] ensure credentials are regenerated on every connection --- pkg/sessions/redis/aws-iam/auth.go | 35 ++++++++++++------------------ 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/pkg/sessions/redis/aws-iam/auth.go b/pkg/sessions/redis/aws-iam/auth.go index a153d7c6..39a75db6 100644 --- a/pkg/sessions/redis/aws-iam/auth.go +++ b/pkg/sessions/redis/aws-iam/auth.go @@ -32,8 +32,8 @@ type AuthTokenGenerator struct { region string req *http.Request - credentials aws.Credentials - signer *v4.Signer + credentialsProvider aws.CredentialsProvider + signer *v4.Signer } func New(serviceName, clusterName, userName string) (*AuthTokenGenerator, error) { @@ -44,17 +44,6 @@ func New(serviceName, clusterName, userName string) (*AuthTokenGenerator, error) 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}, @@ -75,19 +64,23 @@ func New(serviceName, clusterName, userName string) (*AuthTokenGenerator, error) } return &AuthTokenGenerator{ - serviceName: serviceName, - region: cfg.Region, - req: req, - credentials: credentials, - signer: v4.NewSigner(), + serviceName: serviceName, + region: cfg.Region, + req: req, + credentialsProvider: cfg.Credentials, + signer: v4.NewSigner(), }, nil } func (atg AuthTokenGenerator) Generate() (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( - context.Background(), - atg.credentials, + ctx, + credentials, atg.req, hexEncodedSHA256EmptyString, atg.serviceName, From 4e939cac378d1f5dfbe33357484d84c8945ded34 Mon Sep 17 00:00:00 2001 From: William Will <10997562+willwill96@users.noreply.github.com> Date: Wed, 16 Apr 2025 04:55:38 +0000 Subject: [PATCH 03/17] add max lifetime to connections --- pkg/sessions/redis/redis_store.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index a8605db3..72b67e64 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -199,6 +199,8 @@ func setupAWSIAMAuth(opts options.RedisStoreOptions, opt *redis.Options) error { } return opts.AWSUsername, token } + // AWS services has a max connection lifetime of 12 hours. This is set to 11 hours to give some buffer time + opt.ConnMaxLifetime = 11 * time.Hour return nil } From e81d50a39132c0cde5036e73108c6a85c473c1e5 Mon Sep 17 00:00:00 2001 From: William Will <10997562+willwill96@users.noreply.github.com> Date: Wed, 16 Apr 2025 05:03:02 +0000 Subject: [PATCH 04/17] fix lint issue --- pkg/sessions/redis/aws-iam/auth.go | 8 ++++---- pkg/sessions/redis/redis_store.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/sessions/redis/aws-iam/auth.go b/pkg/sessions/redis/aws-iam/auth.go index 39a75db6..0019f7c4 100644 --- a/pkg/sessions/redis/aws-iam/auth.go +++ b/pkg/sessions/redis/aws-iam/auth.go @@ -27,7 +27,7 @@ const ( hexEncodedSHA256EmptyString = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ) -type AuthTokenGenerator struct { +type IAMTokenGenerator struct { serviceName string region string req *http.Request @@ -36,7 +36,7 @@ type AuthTokenGenerator struct { signer *v4.Signer } -func New(serviceName, clusterName, userName string) (*AuthTokenGenerator, error) { +func New(serviceName, clusterName, userName string) (*IAMTokenGenerator, error) { ctx := context.Background() cfg, err := config.LoadDefaultConfig(ctx) @@ -63,7 +63,7 @@ func New(serviceName, clusterName, userName string) (*AuthTokenGenerator, error) return nil, err } - return &AuthTokenGenerator{ + return &IAMTokenGenerator{ serviceName: serviceName, region: cfg.Region, req: req, @@ -72,7 +72,7 @@ func New(serviceName, clusterName, userName string) (*AuthTokenGenerator, error) }, nil } -func (atg AuthTokenGenerator) Generate() (string, error) { +func (atg IAMTokenGenerator) Generate() (string, error) { ctx := context.Background() credentials, err := atg.credentialsProvider.Retrieve(ctx) if err != nil { diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index 72b67e64..6d1a5f85 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -12,7 +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" + auth "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/redis/aws-iam" "github.com/redis/go-redis/v9" ) @@ -188,7 +188,7 @@ 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) + generator, err := auth.New(opts.AWSServiceName, opts.AWSClusterName, opts.AWSUsername) if err != nil { return fmt.Errorf("error creating AWS IAM auth token generator: %v", err) } From 7ce6e861ef4032c8ebb6972fd951cf14afa752df Mon Sep 17 00:00:00 2001 From: William Will <10997562+willwill96@users.noreply.github.com> Date: Wed, 16 Apr 2025 05:20:19 +0000 Subject: [PATCH 05/17] do not use aws iam if not opted in --- pkg/sessions/redis/redis_store.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index 6d1a5f85..c1daaec2 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -185,6 +185,9 @@ func buildStandaloneClient(opts options.RedisStoreOptions) (Client, error) { } func setupAWSIAMAuth(opts options.RedisStoreOptions, opt *redis.Options) error { + if !opts.UseAWSIAMAuth { + return nil + } if opts.AWSServiceName != "elasticache" && opts.AWSServiceName != "memorydb" { return fmt.Errorf("AWS IAM auth is only supported for elasticache and memorydb") } From b5f409d7b298b2fa50c48cc2f65a18e648a76731 Mon Sep 17 00:00:00 2001 From: William Will <10997562+willwill96@users.noreply.github.com> Date: Thu, 7 Aug 2025 12:27:58 -0600 Subject: [PATCH 06/17] remove aws fields as flags --- pkg/apis/options/options.go | 4 ---- pkg/apis/options/sessions.go | 8 ++++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/pkg/apis/options/options.go b/pkg/apis/options/options.go index a5dd0d35..8fa72c7c 100644 --- a/pkg/apis/options/options.go +++ b/pkg/apis/options/options.go @@ -159,10 +159,6 @@ 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") diff --git a/pkg/apis/options/sessions.go b/pkg/apis/options/sessions.go index 0f4273f9..86b0d9b8 100644 --- a/pkg/apis/options/sessions.go +++ b/pkg/apis/options/sessions.go @@ -30,10 +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"` + UseAWSIAMAuth bool `cfg:"redis_aws_use_iam_auth"` + AWSServiceName string `cfg:"redis_aws_service_name"` + AWSClusterName string `cfg:"redis_aws_cluster_name"` + AWSUsername string `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"` From 61df473f820c0d112e6fe8b637f36404db889e16 Mon Sep 17 00:00:00 2001 From: William Will <10997562+willwill96@users.noreply.github.com> Date: Thu, 7 Aug 2025 18:10:37 -0600 Subject: [PATCH 07/17] add additional comments --- pkg/sessions/redis/aws-iam/auth.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/sessions/redis/aws-iam/auth.go b/pkg/sessions/redis/aws-iam/auth.go index 0019f7c4..526a8069 100644 --- a/pkg/sessions/redis/aws-iam/auth.go +++ b/pkg/sessions/redis/aws-iam/auth.go @@ -27,7 +27,8 @@ const ( hexEncodedSHA256EmptyString = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ) -type IAMTokenGenerator struct { +// IAMTokenGenerator generates an IAM token for AWS Redis authentication. +type IAMTokenGenerator interface { serviceName string region string req *http.Request @@ -36,6 +37,7 @@ type IAMTokenGenerator struct { signer *v4.Signer } +// New creates a new IAMTokenGenerator instance func New(serviceName, clusterName, userName string) (*IAMTokenGenerator, error) { ctx := context.Background() @@ -91,7 +93,8 @@ func (atg IAMTokenGenerator) Generate() (string, error) { 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 From ede2770ea49b3c86dc7c100080bb1ccefb9b20d2 Mon Sep 17 00:00:00 2001 From: William Will <10997562+willwill96@users.noreply.github.com> Date: Fri, 8 Aug 2025 09:58:43 -0600 Subject: [PATCH 08/17] Switch to alpha options --- pkg/apis/options/alpha_options.go | 5 +++++ pkg/apis/options/aws_iam.go | 10 ++++++++++ pkg/apis/options/options.go | 1 + pkg/apis/options/sessions.go | 29 +++++++++++++---------------- pkg/sessions/redis/aws-iam/auth.go | 10 +++++++--- pkg/sessions/redis/redis_store.go | 10 +++++----- 6 files changed, 41 insertions(+), 24 deletions(-) create mode 100644 pkg/apis/options/aws_iam.go diff --git a/pkg/apis/options/alpha_options.go b/pkg/apis/options/alpha_options.go index a438518c..b40d650b 100644 --- a/pkg/apis/options/alpha_options.go +++ b/pkg/apis/options/alpha_options.go @@ -45,6 +45,9 @@ type AlphaOptions struct { // yet working.** [This feature is tracked in // #925](https://github.com/oauth2-proxy/oauth2-proxy/issues/926) Providers Providers `json:"providers,omitempty"` + + // AWS IAM Options is used to configure IAM authentication for your redis instance. + AWSIAMOptions *AWSIAMOptions `json:"awsIAM,omitempty"` } // MergeInto replaces alpha options in the Options struct with the values @@ -56,6 +59,7 @@ func (a *AlphaOptions) MergeInto(opts *Options) { opts.Server = a.Server opts.MetricsServer = a.MetricsServer opts.Providers = a.Providers + opts.Session.Redis.AWSIAMConfig = a.AWSIAMOptions } // ExtractFrom populates the fields in the AlphaOptions with the values from @@ -67,4 +71,5 @@ func (a *AlphaOptions) ExtractFrom(opts *Options) { a.Server = opts.Server a.MetricsServer = opts.MetricsServer a.Providers = opts.Providers + a.AWSIAMOptions = opts.Session.Redis.AWSIAMConfig } diff --git a/pkg/apis/options/aws_iam.go b/pkg/apis/options/aws_iam.go new file mode 100644 index 00000000..01093b45 --- /dev/null +++ b/pkg/apis/options/aws_iam.go @@ -0,0 +1,10 @@ +package options + +type AWSIAMOptions struct { + // AWS service redis service being used. "elasticache" or "memorydb" + ServiceName string `json:"serviceName,omitempty` + // AWS Cluster name + ClusterName string `json:"clusterName,omitempty` + // AWS Username + Username string `json:"userName,omitempty` +} diff --git a/pkg/apis/options/options.go b/pkg/apis/options/options.go index 8fa72c7c..9e4b6369 100644 --- a/pkg/apis/options/options.go +++ b/pkg/apis/options/options.go @@ -159,6 +159,7 @@ 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.String("signature-key", "", "GAP-Signature request signature key (algorithm:secretkey)") flagSet.Bool("gcp-healthchecks", false, "Enable GCP/GKE healthcheck endpoints") diff --git a/pkg/apis/options/sessions.go b/pkg/apis/options/sessions.go index 86b0d9b8..4fd8d59f 100644 --- a/pkg/apis/options/sessions.go +++ b/pkg/apis/options/sessions.go @@ -22,22 +22,19 @@ type CookieStoreOptions struct { // RedisStoreOptions contains configuration options for the RedisSessionStore. type RedisStoreOptions struct { - ConnectionURL string `flag:"redis-connection-url" cfg:"redis_connection_url"` - Username string `flag:"redis-username" cfg:"redis_username"` - Password string `flag:"redis-password" cfg:"redis_password"` - UseSentinel bool `flag:"redis-use-sentinel" cfg:"redis_use_sentinel"` - SentinelPassword string `flag:"redis-sentinel-password" cfg:"redis_sentinel_password"` - 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 `cfg:"redis_aws_use_iam_auth"` - AWSServiceName string `cfg:"redis_aws_service_name"` - AWSClusterName string `cfg:"redis_aws_cluster_name"` - AWSUsername string `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"` - IdleTimeout int `flag:"redis-connection-idle-timeout" cfg:"redis_connection_idle_timeout"` + ConnectionURL string `flag:"redis-connection-url" cfg:"redis_connection_url"` + Username string `flag:"redis-username" cfg:"redis_username"` + Password string `flag:"redis-password" cfg:"redis_password"` + UseSentinel bool `flag:"redis-use-sentinel" cfg:"redis_use_sentinel"` + SentinelPassword string `flag:"redis-sentinel-password" cfg:"redis_sentinel_password"` + 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"` + 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"` + IdleTimeout int `flag:"redis-connection-idle-timeout" cfg:"redis_connection_idle_timeout"` + AWSIAMConfig *AWSIAMOptions `cfg:",internal"` } func sessionOptionsDefaults() SessionOptions { diff --git a/pkg/sessions/redis/aws-iam/auth.go b/pkg/sessions/redis/aws-iam/auth.go index 526a8069..b2387abf 100644 --- a/pkg/sessions/redis/aws-iam/auth.go +++ b/pkg/sessions/redis/aws-iam/auth.go @@ -27,8 +27,12 @@ const ( hexEncodedSHA256EmptyString = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ) +type TokenGenerator interface { + GenerateToken() (string, error) +} + // IAMTokenGenerator generates an IAM token for AWS Redis authentication. -type IAMTokenGenerator interface { +type iamTokenGenerator struct { serviceName string region string req *http.Request @@ -65,7 +69,7 @@ func New(serviceName, clusterName, userName string) (*IAMTokenGenerator, error) return nil, err } - return &IAMTokenGenerator{ + return &iamTokenGenerator{ serviceName: serviceName, region: cfg.Region, req: req, @@ -74,7 +78,7 @@ func New(serviceName, clusterName, userName string) (*IAMTokenGenerator, error) }, nil } -func (atg IAMTokenGenerator) Generate() (string, error) { +func (atg iamTokenGenerator) GenerateToken() (string, error) { ctx := context.Background() credentials, err := atg.credentialsProvider.Retrieve(ctx) if err != nil { diff --git a/pkg/sessions/redis/redis_store.go b/pkg/sessions/redis/redis_store.go index c1daaec2..a9775740 100644 --- a/pkg/sessions/redis/redis_store.go +++ b/pkg/sessions/redis/redis_store.go @@ -185,22 +185,22 @@ func buildStandaloneClient(opts options.RedisStoreOptions) (Client, error) { } func setupAWSIAMAuth(opts options.RedisStoreOptions, opt *redis.Options) error { - if !opts.UseAWSIAMAuth { + if opts.AWSIAMConfig == nil { return nil } - if opts.AWSServiceName != "elasticache" && opts.AWSServiceName != "memorydb" { + if opts.AWSIAMConfig.ServiceName != "elasticache" && opts.AWSIAMConfig.ServiceName != "memorydb" { return fmt.Errorf("AWS IAM auth is only supported for elasticache and memorydb") } - generator, err := auth.New(opts.AWSServiceName, opts.AWSClusterName, opts.AWSUsername) + generator, err := auth.New(opts.AWSIAMConfig.ServiceName, opts.AWSIAMConfig.ClusterName, opts.AWSIAMConfig.Username) 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() + token, err := generator.GenerateToken() if err != nil { logger.Errorf("error generating AWS IAM auth token: %v", err) } - return opts.AWSUsername, token + return opts.AWSIAMConfig.Username, token } // AWS services has a max connection lifetime of 12 hours. This is set to 11 hours to give some buffer time opt.ConnMaxLifetime = 11 * time.Hour From 474d869d2ec93bc8048bcf427ad9d61c3ed5ec39 Mon Sep 17 00:00:00 2001 From: William Will <10997562+willwill96@users.noreply.github.com> Date: Fri, 8 Aug 2025 11:04:53 -0600 Subject: [PATCH 09/17] Add unit tests --- pkg/sessions/redis/aws-iam/auth.go | 2 +- pkg/sessions/redis/aws-iam/auth_test.go | 35 +++++++++++++++++++++++ pkg/sessions/redis/redis_store_test.go | 38 +++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 pkg/sessions/redis/aws-iam/auth_test.go diff --git a/pkg/sessions/redis/aws-iam/auth.go b/pkg/sessions/redis/aws-iam/auth.go index b2387abf..8d86a1f2 100644 --- a/pkg/sessions/redis/aws-iam/auth.go +++ b/pkg/sessions/redis/aws-iam/auth.go @@ -42,7 +42,7 @@ type iamTokenGenerator struct { } // New creates a new IAMTokenGenerator instance -func New(serviceName, clusterName, userName string) (*IAMTokenGenerator, error) { +func New(serviceName, clusterName, userName string) (TokenGenerator, error) { ctx := context.Background() cfg, err := config.LoadDefaultConfig(ctx) diff --git a/pkg/sessions/redis/aws-iam/auth_test.go b/pkg/sessions/redis/aws-iam/auth_test.go new file mode 100644 index 00000000..3b19ff20 --- /dev/null +++ b/pkg/sessions/redis/aws-iam/auth_test.go @@ -0,0 +1,35 @@ +package auth + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAWSIAMTokenGenerator(t *testing.T) { + // Set up the environment, so we don't make any external calls to AWS + t.Setenv("AWS_CONFIG_FILE", "file_not_exists") + t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "file_not_exists") + t.Setenv("AWS_ENDPOINT_URL", "http://localhost:9999/aws") + t.Setenv("AWS_ACCESS_KEY_ID", "access_key") + t.Setenv("AWS_SECRET_ACCESS_KEY", "secret_key") + t.Setenv("AWS_REGION", "us-east-1") + + tokenGenerator, err := New("elasticache", "test-cluster", "test-user") + require.NotNil(t, tokenGenerator) + require.NoError(t, err) + + token, err := tokenGenerator.GenerateToken() + require.NoError(t, err) + require.NotEmpty(t, token) + require.Contains(t, token, "X-Amz-Algorithm", "signed token should contain algorithm attribute") + require.Contains(t, token, "User=test-user", "signed token should contain user parameter") + require.Contains(t, token, "X-Amz-Credential", "signed token should contain credential attribute") + require.Contains(t, token, "X-Amz-Date", "signed token should contain date attribute") + require.Contains(t, token, "X-Amz-Expires", "signed token should contain expires attribute") + require.Contains(t, token, "X-Amz-SignedHeaders", "signed token should contain signed headers attribute") + require.Contains(t, token, "X-Amz-Signature", "signed token should contain signature attribute") + require.Contains(t, token, "Action=connect", "signed token should contain connect action") + require.False(t, strings.HasPrefix(token, "http://"), "token should not have http:// scheme") +} diff --git a/pkg/sessions/redis/redis_store_test.go b/pkg/sessions/redis/redis_store_test.go index 1bff6855..6edd5b2e 100644 --- a/pkg/sessions/redis/redis_store_test.go +++ b/pkg/sessions/redis/redis_store_test.go @@ -11,6 +11,7 @@ import ( "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions/tests" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/redis/go-redis/v9" ) const ( @@ -271,4 +272,41 @@ var _ = Describe("Redis SessionStore Tests", func() { Expect(opts).To(BeNil()) }) }) + + Describe("AWSIAMAuth", func() { + Context("with AWS IAM options", func() { + It("should initialize CredentialsProvider when AWSIAMConfig is present", func() { + redisOpts := options.RedisStoreOptions{ + AWSIAMConfig: &options.AWSIAMOptions{ + ServiceName: "elasticache", + ClusterName: "test-cluster", + Username: "test-user", + }, + } + + var opt *redis.Options + opt = &redis.Options{} + + err := setupAWSIAMAuth(redisOpts, opt) + Expect(err).ToNot(HaveOccurred()) + Expect(opt.CredentialsProvider).ToNot(BeNil()) + + // Verify the CredentialsProvider returns the expected username + username, _ := opt.CredentialsProvider() + Expect(username).To(Equal("test-user")) + }) + It("should not initialize CredentialsProvider when AWSIAMConfig is nil", func() { + redisOpts := options.RedisStoreOptions{ + AWSIAMConfig: nil, + } + + var opt *redis.Options + opt = &redis.Options{} + + err := setupAWSIAMAuth(redisOpts, opt) + Expect(err).ToNot(HaveOccurred()) + Expect(opt.CredentialsProvider).To(BeNil()) + }) + }) + }) }) From cbd3b8ccb3e08e04b5e5de63b2040543ae06a552 Mon Sep 17 00:00:00 2001 From: William Will <10997562+willwill96@users.noreply.github.com> Date: Fri, 8 Aug 2025 11:29:49 -0600 Subject: [PATCH 10/17] rename alpha option to be more explicit --- pkg/apis/options/alpha_options.go | 6 +++--- pkg/apis/options/aws_iam.go | 2 +- pkg/apis/options/sessions.go | 2 +- pkg/sessions/redis/redis_store_test.go | 4 +--- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/pkg/apis/options/alpha_options.go b/pkg/apis/options/alpha_options.go index b40d650b..44a60b26 100644 --- a/pkg/apis/options/alpha_options.go +++ b/pkg/apis/options/alpha_options.go @@ -47,7 +47,7 @@ type AlphaOptions struct { Providers Providers `json:"providers,omitempty"` // AWS IAM Options is used to configure IAM authentication for your redis instance. - AWSIAMOptions *AWSIAMOptions `json:"awsIAM,omitempty"` + redisSessionAWSIAMConfig *AWSIAMConfig `json:"redisSessionAWSIAMConfig,omitempty"` } // MergeInto replaces alpha options in the Options struct with the values @@ -59,7 +59,7 @@ func (a *AlphaOptions) MergeInto(opts *Options) { opts.Server = a.Server opts.MetricsServer = a.MetricsServer opts.Providers = a.Providers - opts.Session.Redis.AWSIAMConfig = a.AWSIAMOptions + opts.Session.Redis.AWSIAMConfig = a.redisSessionAWSIAMConfig } // ExtractFrom populates the fields in the AlphaOptions with the values from @@ -71,5 +71,5 @@ func (a *AlphaOptions) ExtractFrom(opts *Options) { a.Server = opts.Server a.MetricsServer = opts.MetricsServer a.Providers = opts.Providers - a.AWSIAMOptions = opts.Session.Redis.AWSIAMConfig + a.redisSessionAWSIAMConfig = opts.Session.Redis.AWSIAMConfig } diff --git a/pkg/apis/options/aws_iam.go b/pkg/apis/options/aws_iam.go index 01093b45..54743a28 100644 --- a/pkg/apis/options/aws_iam.go +++ b/pkg/apis/options/aws_iam.go @@ -1,6 +1,6 @@ package options -type AWSIAMOptions struct { +type AWSIAMConfig struct { // AWS service redis service being used. "elasticache" or "memorydb" ServiceName string `json:"serviceName,omitempty` // AWS Cluster name diff --git a/pkg/apis/options/sessions.go b/pkg/apis/options/sessions.go index 4fd8d59f..80f0050c 100644 --- a/pkg/apis/options/sessions.go +++ b/pkg/apis/options/sessions.go @@ -34,7 +34,7 @@ type RedisStoreOptions struct { CAPath string `flag:"redis-ca-path" cfg:"redis_ca_path"` InsecureSkipTLSVerify bool `flag:"redis-insecure-skip-tls-verify" cfg:"redis_insecure_skip_tls_verify"` IdleTimeout int `flag:"redis-connection-idle-timeout" cfg:"redis_connection_idle_timeout"` - AWSIAMConfig *AWSIAMOptions `cfg:",internal"` + AWSIAMConfig *AWSIAMConfig `cfg:",internal"` } func sessionOptionsDefaults() SessionOptions { diff --git a/pkg/sessions/redis/redis_store_test.go b/pkg/sessions/redis/redis_store_test.go index 6edd5b2e..26044317 100644 --- a/pkg/sessions/redis/redis_store_test.go +++ b/pkg/sessions/redis/redis_store_test.go @@ -288,10 +288,8 @@ var _ = Describe("Redis SessionStore Tests", func() { opt = &redis.Options{} err := setupAWSIAMAuth(redisOpts, opt) - Expect(err).ToNot(HaveOccurred()) Expect(opt.CredentialsProvider).ToNot(BeNil()) - // Verify the CredentialsProvider returns the expected username username, _ := opt.CredentialsProvider() Expect(username).To(Equal("test-user")) }) @@ -304,7 +302,7 @@ var _ = Describe("Redis SessionStore Tests", func() { opt = &redis.Options{} err := setupAWSIAMAuth(redisOpts, opt) - Expect(err).ToNot(HaveOccurred()) + Expect(opt.CredentialsProvider).To(BeNil()) }) }) From cbfba29b3a640bb57539879066b71b36f526a3ce Mon Sep 17 00:00:00 2001 From: William Will <10997562+willwill96@users.noreply.github.com> Date: Fri, 8 Aug 2025 11:52:48 -0600 Subject: [PATCH 11/17] fix tests --- pkg/sessions/redis/redis_store_test.go | 6 ++-- test.yaml | 39 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 test.yaml diff --git a/pkg/sessions/redis/redis_store_test.go b/pkg/sessions/redis/redis_store_test.go index 26044317..a5471633 100644 --- a/pkg/sessions/redis/redis_store_test.go +++ b/pkg/sessions/redis/redis_store_test.go @@ -277,7 +277,7 @@ var _ = Describe("Redis SessionStore Tests", func() { Context("with AWS IAM options", func() { It("should initialize CredentialsProvider when AWSIAMConfig is present", func() { redisOpts := options.RedisStoreOptions{ - AWSIAMConfig: &options.AWSIAMOptions{ + AWSIAMConfig: &options.AWSIAMConfig{ ServiceName: "elasticache", ClusterName: "test-cluster", Username: "test-user", @@ -288,6 +288,8 @@ var _ = Describe("Redis SessionStore Tests", func() { opt = &redis.Options{} err := setupAWSIAMAuth(redisOpts, opt) + Expect(err).ToNot(HaveOccurred()) + Expect(opt.CredentialsProvider).ToNot(BeNil()) username, _ := opt.CredentialsProvider() @@ -302,7 +304,7 @@ var _ = Describe("Redis SessionStore Tests", func() { opt = &redis.Options{} err := setupAWSIAMAuth(redisOpts, opt) - + Expect(err).ToNot(HaveOccurred()) Expect(opt.CredentialsProvider).To(BeNil()) }) }) diff --git a/test.yaml b/test.yaml new file mode 100644 index 00000000..01bfc31f --- /dev/null +++ b/test.yaml @@ -0,0 +1,39 @@ +injectResponseHeaders: + - name: X-Auth-Request-User + values: + - claim: access_token +metricsServer: + BindAddress: ":5000" + SecureBindAddress: "" + TLS: null +providers: + - provider: keycloak-oidc + backendLogoutURL: /doesn't-matter + clientID: oauth2-proxy + clientSecret: oauth2-proxy + code_challenge_method: S256 + id: keycloak-oidc=oauth2-proxy + loginURLParameters: + - default: + - force + name: approval_prompt + oidcConfig: + audienceClaims: + - aud + emailClaim: email + extraAudiences: + - account + groupsClaim: groups + insecureSkipNonce: true + issuerURL: http://localhost:8080/realms/test + userIDClaim: email + +server: + BindAddress: ":4180" + SecureBindAddress: "" + TLS: null + +awsIam: + serviceName: elasticache + clusterName: test + userName: test From 951f6271886c20ff07939f1a3c75d9b74d7a4daf Mon Sep 17 00:00:00 2001 From: William Will <10997562+willwill96@users.noreply.github.com> Date: Fri, 8 Aug 2025 14:54:56 -0600 Subject: [PATCH 12/17] capitalization --- pkg/apis/options/alpha_options.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/apis/options/alpha_options.go b/pkg/apis/options/alpha_options.go index 44a60b26..2f54d73d 100644 --- a/pkg/apis/options/alpha_options.go +++ b/pkg/apis/options/alpha_options.go @@ -47,7 +47,7 @@ type AlphaOptions struct { Providers Providers `json:"providers,omitempty"` // AWS IAM Options is used to configure IAM authentication for your redis instance. - redisSessionAWSIAMConfig *AWSIAMConfig `json:"redisSessionAWSIAMConfig,omitempty"` + RedisSessionAWSIAMConfig *AWSIAMConfig `json:"redisSessionAWSIAMConfig,omitempty"` } // MergeInto replaces alpha options in the Options struct with the values @@ -59,7 +59,7 @@ func (a *AlphaOptions) MergeInto(opts *Options) { opts.Server = a.Server opts.MetricsServer = a.MetricsServer opts.Providers = a.Providers - opts.Session.Redis.AWSIAMConfig = a.redisSessionAWSIAMConfig + opts.Session.Redis.AWSIAMConfig = a.RedisSessionAWSIAMConfig } // ExtractFrom populates the fields in the AlphaOptions with the values from @@ -71,5 +71,5 @@ func (a *AlphaOptions) ExtractFrom(opts *Options) { a.Server = opts.Server a.MetricsServer = opts.MetricsServer a.Providers = opts.Providers - a.redisSessionAWSIAMConfig = opts.Session.Redis.AWSIAMConfig + a.RedisSessionAWSIAMConfig = opts.Session.Redis.AWSIAMConfig } From b68aff972d081d44400ee77424e397842aaf8133 Mon Sep 17 00:00:00 2001 From: William Will <10997562+willwill96@users.noreply.github.com> Date: Fri, 8 Aug 2025 17:31:11 -0600 Subject: [PATCH 13/17] remove test file --- test.yaml | 39 --------------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 test.yaml diff --git a/test.yaml b/test.yaml deleted file mode 100644 index 01bfc31f..00000000 --- a/test.yaml +++ /dev/null @@ -1,39 +0,0 @@ -injectResponseHeaders: - - name: X-Auth-Request-User - values: - - claim: access_token -metricsServer: - BindAddress: ":5000" - SecureBindAddress: "" - TLS: null -providers: - - provider: keycloak-oidc - backendLogoutURL: /doesn't-matter - clientID: oauth2-proxy - clientSecret: oauth2-proxy - code_challenge_method: S256 - id: keycloak-oidc=oauth2-proxy - loginURLParameters: - - default: - - force - name: approval_prompt - oidcConfig: - audienceClaims: - - aud - emailClaim: email - extraAudiences: - - account - groupsClaim: groups - insecureSkipNonce: true - issuerURL: http://localhost:8080/realms/test - userIDClaim: email - -server: - BindAddress: ":4180" - SecureBindAddress: "" - TLS: null - -awsIam: - serviceName: elasticache - clusterName: test - userName: test From 58e2e607e533338ae243b086fccfc97564f38bc9 Mon Sep 17 00:00:00 2001 From: Jan Larwig Date: Mon, 18 Aug 2025 21:38:59 +0200 Subject: [PATCH 14/17] add changelog entry Signed-off-by: Jan Larwig --- CHANGELOG.md | 1 + docs/docs/configuration/alpha_config.md | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bcc843b..f8f0dceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - [#2273](https://github.com/oauth2-proxy/oauth2-proxy/pull/2273) feat: add Cidaas provider (@Bibob7, @Teko012) - [#3166](https://github.com/oauth2-proxy/oauth2-proxy/pull/3166) chore(dep): upgrade to latest golang 1.24.6 (@tuunit) +- [#3029](https://github.com/oauth2-proxy/oauth2-proxy/pull/3029) feat: IAM auth for AWS redis (@willwill96) # V7.11.0 diff --git a/docs/docs/configuration/alpha_config.md b/docs/docs/configuration/alpha_config.md index 018a2941..dc2cd1bb 100644 --- a/docs/docs/configuration/alpha_config.md +++ b/docs/docs/configuration/alpha_config.md @@ -150,6 +150,18 @@ You must remove these options before starting OAuth2 Proxy with `--alpha-config` | ----- | ---- | ----------- | | `skipScope` | _bool_ | Skip adding the scope parameter in login request
Default value is 'false' | +### AWSIAMConfig + +(**Appears on:** [AlphaOptions](#alphaoptions)) + + + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `ServiceName` | _string_ | AWS service redis service being used. "elasticache" or "memorydb" | +| `ClusterName` | _string_ | AWS Cluster name | +| `Username` | _string_ | AWS Username | + ### AlphaOptions AlphaOptions contains alpha structured configuration options. @@ -169,6 +181,7 @@ They may change between releases without notice. | `server` | _[Server](#server)_ | Server is used to configure the HTTP(S) server for the proxy application.
You may choose to run both HTTP and HTTPS servers simultaneously.
This can be done by setting the BindAddress and the SecureBindAddress simultaneously.
To use the secure server you must configure a TLS certificate and key. | | `metricsServer` | _[Server](#server)_ | MetricsServer is used to configure the HTTP(S) server for metrics.
You may choose to run both HTTP and HTTPS servers simultaneously.
This can be done by setting the BindAddress and the SecureBindAddress simultaneously.
To use the secure server you must configure a TLS certificate and key. | | `providers` | _[Providers](#providers)_ | Providers is used to configure your provider. **Multiple-providers is not
yet working.** [This feature is tracked in
#925](https://github.com/oauth2-proxy/oauth2-proxy/issues/926) | +| `redisSessionAWSIAMConfig` | _[AWSIAMConfig](#awsiamconfig)_ | AWS IAM Options is used to configure IAM authentication for your redis instance. | ### AzureOptions From a273f6087d203675e5fdbf336cbc291c61b89529 Mon Sep 17 00:00:00 2001 From: William Will <10997562+willwill96@users.noreply.github.com> Date: Tue, 19 Aug 2025 11:21:02 -0600 Subject: [PATCH 15/17] linting --- pkg/apis/options/aws_iam.go | 6 +++--- pkg/apis/options/sessions.go | 26 +++++++++++++------------- pkg/sessions/redis/redis_store_test.go | 6 ++---- 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/pkg/apis/options/aws_iam.go b/pkg/apis/options/aws_iam.go index 54743a28..534ba718 100644 --- a/pkg/apis/options/aws_iam.go +++ b/pkg/apis/options/aws_iam.go @@ -2,9 +2,9 @@ package options type AWSIAMConfig struct { // AWS service redis service being used. "elasticache" or "memorydb" - ServiceName string `json:"serviceName,omitempty` + ServiceName string `json:"serviceName,omitempty"` // AWS Cluster name - ClusterName string `json:"clusterName,omitempty` + ClusterName string `json:"clusterName,omitempty"` // AWS Username - Username string `json:"userName,omitempty` + Username string `json:"userName,omitempty"` } diff --git a/pkg/apis/options/sessions.go b/pkg/apis/options/sessions.go index 80f0050c..d0085ff5 100644 --- a/pkg/apis/options/sessions.go +++ b/pkg/apis/options/sessions.go @@ -22,19 +22,19 @@ type CookieStoreOptions struct { // RedisStoreOptions contains configuration options for the RedisSessionStore. type RedisStoreOptions struct { - ConnectionURL string `flag:"redis-connection-url" cfg:"redis_connection_url"` - Username string `flag:"redis-username" cfg:"redis_username"` - Password string `flag:"redis-password" cfg:"redis_password"` - UseSentinel bool `flag:"redis-use-sentinel" cfg:"redis_use_sentinel"` - SentinelPassword string `flag:"redis-sentinel-password" cfg:"redis_sentinel_password"` - 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"` - 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"` - IdleTimeout int `flag:"redis-connection-idle-timeout" cfg:"redis_connection_idle_timeout"` - AWSIAMConfig *AWSIAMConfig `cfg:",internal"` + ConnectionURL string `flag:"redis-connection-url" cfg:"redis_connection_url"` + Username string `flag:"redis-username" cfg:"redis_username"` + Password string `flag:"redis-password" cfg:"redis_password"` + UseSentinel bool `flag:"redis-use-sentinel" cfg:"redis_use_sentinel"` + SentinelPassword string `flag:"redis-sentinel-password" cfg:"redis_sentinel_password"` + 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"` + 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"` + IdleTimeout int `flag:"redis-connection-idle-timeout" cfg:"redis_connection_idle_timeout"` + AWSIAMConfig *AWSIAMConfig `cfg:",internal"` } func sessionOptionsDefaults() SessionOptions { diff --git a/pkg/sessions/redis/redis_store_test.go b/pkg/sessions/redis/redis_store_test.go index a5471633..ca0af0ef 100644 --- a/pkg/sessions/redis/redis_store_test.go +++ b/pkg/sessions/redis/redis_store_test.go @@ -284,8 +284,7 @@ var _ = Describe("Redis SessionStore Tests", func() { }, } - var opt *redis.Options - opt = &redis.Options{} + var opt = &redis.Options{} err := setupAWSIAMAuth(redisOpts, opt) Expect(err).ToNot(HaveOccurred()) @@ -300,8 +299,7 @@ var _ = Describe("Redis SessionStore Tests", func() { AWSIAMConfig: nil, } - var opt *redis.Options - opt = &redis.Options{} + var opt = &redis.Options{} err := setupAWSIAMAuth(redisOpts, opt) Expect(err).ToNot(HaveOccurred()) From ea6d62939b6d5fb2a1e4af433b77e43966e9dd28 Mon Sep 17 00:00:00 2001 From: William Will <10997562+willwill96@users.noreply.github.com> Date: Mon, 25 Aug 2025 09:36:29 -0600 Subject: [PATCH 16/17] fix alpha options --- docs/docs/configuration/alpha_config.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/configuration/alpha_config.md b/docs/docs/configuration/alpha_config.md index 8be7490b..cf854837 100644 --- a/docs/docs/configuration/alpha_config.md +++ b/docs/docs/configuration/alpha_config.md @@ -158,9 +158,9 @@ You must remove these options before starting OAuth2 Proxy with `--alpha-config` | Field | Type | Description | | ----- | ---- | ----------- | -| `ServiceName` | _string_ | AWS service redis service being used. "elasticache" or "memorydb" | -| `ClusterName` | _string_ | AWS Cluster name | -| `Username` | _string_ | AWS Username | +| `serviceName` | _string_ | AWS service redis service being used. "elasticache" or "memorydb" | +| `clusterName` | _string_ | AWS Cluster name | +| `username` | _string_ | AWS Username | ### AlphaOptions From f7f52a577e1e04b5c753f3daa8f660b6dec6dc41 Mon Sep 17 00:00:00 2001 From: William Will <10997562+willwill96@users.noreply.github.com> Date: Mon, 25 Aug 2025 09:39:19 -0600 Subject: [PATCH 17/17] fixes --- CHANGELOG.md | 3 ++- docs/docs/configuration/alpha_config.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cbfd0fd..2683b090 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ## Changes since v7.12.0 +- [#3029](https://github.com/oauth2-proxy/oauth2-proxy/pull/3029) feat: IAM auth for AWS redis (@willwill96) + # V7.12.0 ## Release Highlights @@ -26,7 +28,6 @@ - [#2273](https://github.com/oauth2-proxy/oauth2-proxy/pull/2273) feat: add Cidaas provider (@Bibob7, @Teko012) - [#3166](https://github.com/oauth2-proxy/oauth2-proxy/pull/3166) chore(dep): upgrade to latest golang 1.24.6 (@tuunit) -- [#3029](https://github.com/oauth2-proxy/oauth2-proxy/pull/3029) feat: IAM auth for AWS redis (@willwill96) - [#3156](https://github.com/oauth2-proxy/oauth2-proxy/pull/3156) feat: allow disable-keep-alives configuration for upstream (@jet-go) - [#3150](https://github.com/oauth2-proxy/oauth2-proxy/pull/3150) fix: Gitea team membership (@MagicRB, @tuunit) diff --git a/docs/docs/configuration/alpha_config.md b/docs/docs/configuration/alpha_config.md index cf854837..a8cd024f 100644 --- a/docs/docs/configuration/alpha_config.md +++ b/docs/docs/configuration/alpha_config.md @@ -160,7 +160,7 @@ You must remove these options before starting OAuth2 Proxy with `--alpha-config` | ----- | ---- | ----------- | | `serviceName` | _string_ | AWS service redis service being used. "elasticache" or "memorydb" | | `clusterName` | _string_ | AWS Cluster name | -| `username` | _string_ | AWS Username | +| `userName` | _string_ | AWS Username | ### AlphaOptions