Repository: warpstreamlabs/bento
Pull request #168: add: IAM RDS auth to sql components and refactor
Author: login: gregfurman
Created: 2024-11-17T22:09:52Z | Merged: 2025-05-11T12:49:22Z

### Changes
- Refactors how AWS components are imported -- allowing for Bento distros without the AWS SDK to compile and use  SQL components.
- Adds support for generating an IAM authentication token for RDS.
- Some more refactoring to make both secret manager and IAM approaches better integrated.

###  TODO
- [x] Some integration testing using[ LocalStack against RDS with IAM](https://docs.localstack.cloud/user-guide/aws/rds/#iam-authentication-support). (thx @jem-davies 🫡 )
- [x] ~~Communicate any field changes with how IAM is configured.~~
- [x] Alter config definition such that it won't be a breaking change re. secret_name
- [x] Fix the RDS IAM Integration test - tested it against aws - possible issue with localstack 


Diff:
diff --git a/go.mod b/go.mod
index 98d07adb58..987c690759 100644
--- a/go.mod
+++ b/go.mod
@@ -30,10 +30,11 @@ require (
 	github.com/PaesslerAG/jsonpath v0.1.1
 	github.com/apache/pulsar-client-go v0.12.1
 	github.com/aws/aws-lambda-go v1.46.0
-	github.com/aws/aws-sdk-go-v2 v1.32.6
+	github.com/aws/aws-sdk-go-v2 v1.36.3
 	github.com/aws/aws-sdk-go-v2/config v1.27.43
 	github.com/aws/aws-sdk-go-v2/credentials v1.17.41
 	github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.6.17
+	github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.23
 	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.15
 	github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.32.2
 	github.com/aws/aws-sdk-go-v2/service/dynamodb v1.27.1
@@ -212,20 +213,22 @@ require (
 	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 // indirect
 	github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.12.17 // indirect
 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 // indirect
-	github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 // 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.1 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.10 // indirect
 	github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.18.7 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 // 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/checksum v1.2.10 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.11 // indirect
-	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 // indirect
+	github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
 	github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 // indirect
+	github.com/aws/aws-sdk-go-v2/service/rds v1.94.1
+	github.com/aws/aws-sdk-go-v2/service/redshift v1.54.1
 	github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.5
 	github.com/aws/aws-sdk-go-v2/service/sso v1.24.2 // indirect
 	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.2 // indirect
-	github.com/aws/smithy-go v1.22.1
+	github.com/aws/smithy-go v1.22.2
 	github.com/aymerick/douceur v0.2.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/bits-and-blooms/bitset v1.4.0 // indirect
diff --git a/go.sum b/go.sum
index 1df9bcbf7f..fee8d4cb5b 100644
--- a/go.sum
+++ b/go.sum
@@ -827,8 +827,8 @@ github.com/aws/aws-sdk-go v1.42.37/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRj
 github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
 github.com/aws/aws-sdk-go-v2 v1.16.2/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU=
 github.com/aws/aws-sdk-go-v2 v1.23.0/go.mod h1:i1XDttT4rnf6vxc9AuskLc6s7XBee8rlLilKlc03uAA=
-github.com/aws/aws-sdk-go-v2 v1.32.6 h1:7BokKRgRPuGmKkFMhEg/jSul+tB9VvXhcViILtfG8b4=
-github.com/aws/aws-sdk-go-v2 v1.32.6/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
+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/aws/protocol/eventstream v1.4.1/go.mod h1:n8Bs1ElDD2wJ9kCRTczA83gYbBmjSwZp3umc6zF4EeM=
 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.1/go.mod h1:t8PYl/6LzdAqsU4/9tz28V/kU+asFePvpOMkdul0gEQ=
 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.6 h1:pT3hpW0cOHRJx8Y0DfJUEQuqPild8jRGmSFmBgvydr0=
@@ -849,18 +849,20 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3/go.mod h1:uk1vhHHERfSVCUnq
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.4/go.mod h1:t4i+yGHMCcUNIX1x7YVYa6bH/Do7civ5I6cG/6PMfyA=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17 h1:TMH3f/SCAWdNtXXVPPu5D6wrr4G5hI1rAxbcocKfC7Q=
 github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.17/go.mod h1:1ZRXLdTpzdJb9fwTMXiLipENRxkGMTn1sfKexGllQCw=
+github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.23 h1:B2qK61ZXCQu8tkD6eG/gUiIt9Vw9tmWFD7Xo02JPdMY=
+github.com/aws/aws-sdk-go-v2/feature/rds/auth v1.4.23/go.mod h1:02rz9vMZsrOX9IwUcpoGZM4jPprFNPmtD6t9Ume9ECY=
 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.3/go.mod h1:0dHuD2HZZSiwfJSy1FO5bX1hQ1TxVV1QXXjpn3XUE44=
 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.14.0/go.mod h1:UcgIwJ9KHquYxs6Q5skC9qXjhYMK+JASDYcXQ4X7JZE=
 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.15 h1:2MUXyGW6dVaQz6aqycpbdLIH1NMcUI6kW6vQ0RabGYg=
 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.15/go.mod h1:aHbhbR6WEQgHAiRj41EQ2W47yOYwNtIkWTXmcAtYqj8=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9/go.mod h1:AnVH5pvai0pAF4lXRq0bmhbes1u9R8wTE+g+183bZNM=
 github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.3/go.mod h1:7sGSz1JCKHWWBHq98m6sMtWQikmYPpxjqOydDemiVoM=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 h1:s/fF4+yDQDoElYhfIVvSNyeCydfbuTKzhxSXDXCPasU=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25/go.mod h1:IgPfDv5jqFIzQSNbUEMoitNooSMXjRSDkhXv8jiROvU=
+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.4.3/go.mod h1:ssOhaLpRlh88H3UmEcsBoVKq309quMvm3Ds8e9d4eJM=
 github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.3/go.mod h1:ify42Rb7nKeDDPkFjKn7q1bPscVPu/+gmHH8d2c+anU=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25 h1:ZntTCl5EsYnhN/IygQEUugpdwbhdkom9uHcbCftiGgA=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.25/go.mod h1:DBdPrgeocww+CSl1C8cEV8PN1mHMBhuCDLpXezyvWkE=
+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.3.10/go.mod h1:8DcYQcz0+ZJaSxANlHIsbbi6S+zMwjwdDqwW3r9AzaE=
 github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
 github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
@@ -880,8 +882,8 @@ github.com/aws/aws-sdk-go-v2/service/firehose v1.24.0 h1:U3F5oeq3Lp1jv9ebLHNr1OS
 github.com/aws/aws-sdk-go-v2/service/firehose v1.24.0/go.mod h1:vHumFD15AwENJSM3SsWzcPpMK24s/7vGN1Xp5rLguz0=
 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1/go.mod h1:GeUru+8VzrTXV/83XyMJ80KpH8xO89VPoUileyNQ+tc=
 github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1/go.mod h1:l9ymW25HOqymeU2m1gbUQ3rUIsTwKs8gYHXkqDQUhiI=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0 h1:TToQNkvGguu209puTojY/ozlqy2d/SFNcoLIqTFi42g=
-github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.0/go.mod h1:0jp+ltwkf+SwG2fm/PKo8t4y8pJSgOCO4D8Lz3k0aHQ=
+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/checksum v1.1.3/go.mod h1:Seb8KNmD6kVTjwRjVEgOT5hPin6sq+v4C2ycJQDwuH8=
 github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.3/go.mod h1:R+/S1O4TYpcktbVwddeOYg+uwUfLhADP2S/x4QwsCTM=
 github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.10 h1:L0ai8WICYHozIKK+OtPzVJBugL7culcuM4E4JOpIEm8=
@@ -890,8 +892,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.11 h1:e9AV
 github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.8.11/go.mod h1:B90ZQJa36xo0ph9HsoteI1+r8owgQH/U1QNfqZQkj1Q=
 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3/go.mod h1:wlY6SVjuwvh3TVRpTqdy4I1JpBFLX4UGeKZdWntaocw=
 github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.3/go.mod h1:Owv1I59vaghv1Ax8zz8ELY8DN7/Y0rGS+WWAmjgi950=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2 h1:s7NA1SOw8q/5c0wr8477yOPp0z+uBaXBnLE0XYb0POA=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.2/go.mod h1:fnjjWyAW/Pj5HYOxl9LJqWtEwS7W2qgcRLWP+uWbss0=
+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/internal/s3shared v1.13.3/go.mod h1:Bm/v2IaN6rZ+Op7zX+bOUMdL4fsrYZiD0dsjLhNKwZc=
 github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.3/go.mod h1:KZgs2ny8HsxRIRbDwgvJcHHBZPOzQr/+NtGwnP+w2ec=
 github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.10 h1:KOxnQeWy5sXyS37fdKEvAsGHOr9fa/qvwxfJurR/BzE=
@@ -901,6 +903,10 @@ github.com/aws/aws-sdk-go-v2/service/kinesis v1.24.7/go.mod h1:xOJOknNQF6owzT/d+
 github.com/aws/aws-sdk-go-v2/service/kms v1.16.3/go.mod h1:QuiHPBqlOFCi4LqdSskYYAWpQlx3PKmohy+rE2F+o5g=
 github.com/aws/aws-sdk-go-v2/service/lambda v1.50.0 h1:fBJs+X3ZOEqpmiSb7as6DBqm7K2RTkbaxYL9RBGCZyE=
 github.com/aws/aws-sdk-go-v2/service/lambda v1.50.0/go.mod h1:yEO3Ejj0qBhdIDlRYQ8O9+gB5CAUKyaYYiFBkvGX8ZA=
+github.com/aws/aws-sdk-go-v2/service/rds v1.94.1 h1:OxrMHbabEdgwKLdMYvnHJju4XFyemN+rknceKU3lyvE=
+github.com/aws/aws-sdk-go-v2/service/rds v1.94.1/go.mod h1:CXiHj5rVyQ5Q3zNSoYzwaJfWm8IGDweyyCGfO8ei5fQ=
+github.com/aws/aws-sdk-go-v2/service/redshift v1.54.1 h1:atHdz2zvGf9wC7NfaOvPh3dHAwg+XwHcoYdmkq/i/Sc=
+github.com/aws/aws-sdk-go-v2/service/redshift v1.54.1/go.mod h1:TC8pNvjiikrjpX2MEzX/cEJ4/T4XIoSY4BskVvHj8bk=
 github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3/go.mod h1:g1qvDuRsJY+XghsV6zg00Z4KJ7DtFFCx8fJD2a491Ak=
 github.com/aws/aws-sdk-go-v2/service/s3 v1.43.0/go.mod h1:NXRKkiRF+erX2hnybnVU660cYT5/KChRD4iUgJ97cI8=
 github.com/aws/aws-sdk-go-v2/service/s3 v1.48.1 h1:5XNlsBsEvBZBMO6p82y+sqpWg8j5aBCe+5C2GBFgqBQ=
@@ -928,8 +934,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.32.2 h1:CiS7i0+FUe+/YY1GvIBLLrR/XNGZ
 github.com/aws/aws-sdk-go-v2/service/sts v1.32.2/go.mod h1:HtaiBI8CjYoNVde8arShXb94UbQQi9L4EMr6D+xGBwo=
 github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM=
 github.com/aws/smithy-go v1.17.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
-github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
-github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
+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/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
 github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
 github.com/beanstalkd/go-beanstalk v0.2.0 h1:6UOJugnu47uNB2jJO/lxyDgeD1Yds7owYi1USELqexA=
diff --git a/internal/impl/sql/aws/aws.go b/internal/impl/sql/aws/aws.go
new file mode 100644
index 0000000000..90b42cc78f
--- /dev/null
+++ b/internal/impl/sql/aws/aws.go
@@ -0,0 +1,182 @@
+package aws
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/url"
+
+	"github.com/aws/aws-sdk-go-v2/aws"
+	"github.com/aws/aws-sdk-go-v2/feature/rds/auth"
+	"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
+
+	baws "github.com/warpstreamlabs/bento/internal/impl/aws"
+	"github.com/warpstreamlabs/bento/internal/impl/sql"
+	"github.com/warpstreamlabs/bento/public/service"
+)
+
+func init() {
+	noop := func(dsn, driver string) (password string, err error) {
+		return "", nil
+	}
+
+	sql.AWSGetCredentialsGeneratorFn = func(conf *service.ParsedConfig) (func(dsn, driver string) (password string, err error), error) {
+
+		awsEnabled, err := sql.IsAWSEnabled(conf)
+		if err != nil {
+			return nil, err
+		}
+
+		if !awsEnabled {
+			return noop, nil
+		}
+
+		awsConfig, err := baws.GetSession(context.TODO(), conf)
+		if err != nil {
+			return nil, err
+		}
+
+		iamEnabled, err := conf.FieldBool("iam_enabled")
+		if err != nil {
+			return nil, err
+		}
+
+		if iamEnabled {
+
+			getCredentials := func(dbEndpoint string, dbUser string) (string, error) {
+				return buildIamAuthToken(dbEndpoint, dbUser, awsConfig)
+			}
+
+			wrapDsnBuilder := func(dsn, driver string) (string, error) {
+				return BuildAWSDsnFromIAMCredentials(dsn, driver, getCredentials)
+			}
+
+			return wrapDsnBuilder, nil
+
+		}
+
+		if conf.Contains("secret_name") {
+			secret, err := conf.FieldString("secret_name")
+			if err != nil {
+				return nil, err
+			}
+
+			if secret != "" {
+				getCredentials := func() (string, error) {
+					return getSecretFromAWSSecretManager(secret, awsConfig)
+				}
+
+				wrapDsnBuilder := func(dsn, driver string) (string, error) {
+					return BuildAWSDsnFromSecret(dsn, driver, getCredentials)
+				}
+
+				return wrapDsnBuilder, nil
+			}
+
+		}
+
+		return noop, nil
+	}
+}
+
+//------------------------------------------------------------------------------
+
+func BuildAWSDsnFromSecret(dsn, driver string, getAWSCredentialsFromSecret func() (string, error)) (string, error) {
+	if driver != "postgres" {
+		return "", errors.New("secret_name with DSN info currently only works for postgres DSNs")
+	}
+
+	parsedDSN, err := url.Parse(dsn)
+	if err != nil {
+		return "", fmt.Errorf("error parsing DSN URL: %w", err)
+	}
+
+	username := parsedDSN.User.Username()
+	password, _ := parsedDSN.User.Password()
+	host := parsedDSN.Hostname()
+	port := parsedDSN.Port()
+	path := parsedDSN.Path
+	rawQuery := parsedDSN.RawQuery
+
+	secretString, err := getAWSCredentialsFromSecret()
+	if err != nil {
+		return "", fmt.Errorf("error retrieving secret: %w", err)
+	}
+
+	var secrets map[string]interface{}
+	if err := json.Unmarshal([]byte(secretString), &secrets); err != nil {
+		return "", fmt.Errorf("error unmarshalling secret: %w", err)
+	}
+
+	if val, ok := secrets["username"].(string); ok && val != "" {
+		username = val
+	}
+	if val, ok := secrets["password"].(string); ok && val != "" {
+		password = val
+	}
+
+	newDSN := fmt.Sprintf("%s://%s:%s@%s:%s%s", driver, url.QueryEscape(username), url.QueryEscape(password), host, port, path)
+	if rawQuery != "" {
+		newDSN = fmt.Sprintf("%s?%s", newDSN, rawQuery)
+	}
+
+	return newDSN, nil
+}
+
+func BuildAWSDsnFromIAMCredentials(dsn string, driver string, generateIAMAuthToken func(dbEndpoint string, dbUser string) (string, error)) (string, error) {
+	if driver != "postgres" && driver != "mysql" {
+		return "", errors.New("cannot create DSN from IAM when driver is not postgres or mysql")
+	}
+
+	parsedDSN, err := url.Parse(dsn)
+	if err != nil {
+		return "", fmt.Errorf("error parsing DSN URL: %w", err)
+	}
+
+	username := parsedDSN.User.Username()
+	host := parsedDSN.Hostname()
+	port := parsedDSN.Port()
+	path := parsedDSN.Path
+	rawQuery := parsedDSN.RawQuery
+	endpoint := fmt.Sprintf("%s:%s", host, port)
+	iamToken, err := generateIAMAuthToken(endpoint, username)
+	if err != nil {
+		return "", fmt.Errorf("error retrieving IAM token: %w", err)
+	}
+
+	newDSN := fmt.Sprintf("%s://%s:%s@%s:%s%s", driver, url.QueryEscape(username), url.QueryEscape(iamToken), host, port, path)
+	if rawQuery != "" {
+		newDSN = fmt.Sprintf("%s?%s", newDSN, rawQuery)
+	}
+
+	return newDSN, nil
+
+}
+
+//------------------------------------------------------------------------------
+
+func getSecretFromAWSSecretManager(secretName string, awsConf aws.Config) (string, error) {
+	svc := secretsmanager.NewFromConfig(awsConf)
+
+	input := &secretsmanager.GetSecretValueInput{
+		SecretId: aws.String(secretName),
+	}
+	result, err := svc.GetSecretValue(context.TODO(), input)
+	if err != nil {
+		return "", err
+	}
+
+	return *result.SecretString, nil
+}
+
+func buildIamAuthToken(dbEndpoint, dbUser string, awsConf aws.Config) (string, error) {
+	authenticationToken, err := auth.BuildAuthToken(
+		context.TODO(), dbEndpoint, awsConf.Region, dbUser, awsConf.Credentials,
+	)
+	if err != nil {
+		return "", err
+	}
+
+	return authenticationToken, nil
+}
diff --git a/internal/impl/sql/cache_sql.go b/internal/impl/sql/cache_sql.go
index 5478c03c5b..0350e7b59a 100644
--- a/internal/impl/sql/cache_sql.go
+++ b/internal/impl/sql/cache_sql.go
@@ -14,6 +14,7 @@ import (
 	"github.com/warpstreamlabs/bento/public/service"
 
 	"github.com/aws/aws-sdk-go-v2/aws"
+
 	bento_aws "github.com/warpstreamlabs/bento/internal/impl/aws"
 )
 
@@ -69,7 +70,8 @@ The ` + "`add`" + ` operation is performed with a traditional ` + "`insert`" + `
 				"ON DUPLICATE KEY UPDATE bar=VALUES(bar)",
 				"ON CONFLICT (foo) DO UPDATE SET bar=excluded.bar",
 				"ON CONFLICT (foo) DO NOTHING",
-			))
+						)).
+		LintRule(SQLConnLintRule) // TODO: Move AWS related fields to an 'aws' object field in Bento v2
 
 	for _, f := range connFields() {
 		spec = spec.Field(f)
@@ -166,14 +168,19 @@ func newSQLCacheFromConfig(conf *service.ParsedConfig, mgr *service.Resources) (
 		return nil, err
 	}
 
-	if s.driver == "postgres" && connSettings.secretName != "" {
-		s.awsConf, err = bento_aws.GetSession(context.Background(), conf)
+	awsEnabled, err := IsAWSEnabled(conf)
+	if err != nil {
+		return nil, err
+	}
+
+	if awsEnabled {
+		s.awsConf, err = bento_aws.GetSession(context.Background(), conf.Namespace("aws"))
 		if err != nil {
 			return nil, err
 		}
 	}
 
-	if s.db, err = sqlOpenWithReworks(context.Background(), s.logger, s.driver, s.dsn, connSettings, s.awsConf); err != nil {
+	if s.db, err = sqlOpenWithReworks(context.Background(), s.logger, s.driver, s.dsn, connSettings); err != nil {
 		return nil, err
 	}
 	connSettings.apply(context.Background(), s.db, s.logger)
diff --git a/internal/impl/sql/conn_aws.go b/internal/impl/sql/conn_aws.go
new file mode 100644
index 0000000000..34c6847de2
--- /dev/null
+++ b/internal/impl/sql/conn_aws.go
@@ -0,0 +1,36 @@
+package sql
+
+import (
+	"errors"
+
+	"github.com/warpstreamlabs/bento/public/service"
+)
+
+const (
+	SQLConnLintRule = `root = match {this.iam_enabled && this.exists("secret_name") => "both iam_enabled and secret_name cannot be set"}`
+)
+
+func IsAWSEnabled(c *service.ParsedConfig) (enabled bool, err error) {
+	iam, _ := c.FieldBool("iam_enabled")
+
+	if c.Contains("secret_name") || iam {
+		return true, nil
+	}
+	return false, nil
+}
+
+// AWSGetCredentialsGeneratorFn is populated with the child `aws` package when imported.
+var AWSGetCredentialsGeneratorFn = func(c *service.ParsedConfig) (fn func(dsn, driver string) (password string, err error), err error) {
+	awsEnabled, err := IsAWSEnabled(c)
+	if err != nil {
+		return nil, err
+	}
+	if awsEnabled {
+		return nil, errors.New("unable to configure AWS authentication as this binary does not import components/aws")
+	}
+	return
+}
+
+var BuildAwsDsn = func(dsn, driver string) (password string, err error) {
+	return dsn, nil
+}
diff --git a/internal/impl/sql/conn_fields.go b/internal/impl/sql/conn_fields.go
index d638eec08f..c7cbdfba96 100644
--- a/internal/impl/sql/conn_fields.go
+++ b/internal/impl/sql/conn_fields.go
@@ -3,16 +3,12 @@ package sql
 import (
 	"context"
 	"database/sql"
-	"encoding/json"
-	"errors"
 	"fmt"
 	"net/url"
 	"strings"
 	"sync"
 	"time"
 
-	"github.com/aws/aws-sdk-go-v2/aws"
-	"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
 	"github.com/warpstreamlabs/bento/internal/impl/aws/config"
 	"github.com/warpstreamlabs/bento/public/service"
 )
@@ -109,8 +105,15 @@ CREATE TABLE IF NOT EXISTS some_table (
 			Optional().
 			Advanced(),
 		service.NewStringField("secret_name").
-			Description("An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to postgres.").
+			Description("An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to `postgres`.").
 			Optional().
+			Version("1.3.0").
+			Advanced(),
+		service.NewBoolField("iam_enabled").
+			Description("An optional field used to generate an IAM authentication token to connect to an Amazon Relational Database (RDS) DB instance. This will overwrite the Password in the DSN with the generated token only if the drivers are `mysql` or `postgres`.").
+			Optional().
+			Default(false).
+			Version("1.8.0").
 			Advanced(),
 	}
 
@@ -149,7 +152,6 @@ type connSettings struct {
 	initFileStatements [][2]string // (path,statement)
 	initStatement      string
 	initVerifyConn     bool
-	secretName         string
 }
 
 func (c *connSettings) apply(ctx context.Context, db *sql.DB, log *service.Logger) {
@@ -238,8 +240,12 @@ func connSettingsFromParsed(
 		}
 	}
 
-	if conf.Contains("secret_name") {
-		if c.secretName, err = conf.FieldString("secret_name"); err != nil {
+	awsEnabled, err := IsAWSEnabled(conf)
+	if err != nil {
+		return
+	}
+	if awsEnabled {
+		if BuildAwsDsn, err = AWSGetCredentialsGeneratorFn(conf); err != nil {
 			return
 		}
 	}
@@ -279,67 +285,7 @@ func reworkDSN(driver, dsn string) (string, error) {
 	return dsn, nil
 }
 
-func getSecretFromAWSSecretManager(secretName string, awsConf aws.Config) (secretString string, err error) {
-	svc := secretsmanager.NewFromConfig(awsConf)
-
-	input := &secretsmanager.GetSecretValueInput{
-		SecretId: aws.String(secretName),
-	}
-	result, err := svc.GetSecretValue(context.TODO(), input)
-	if err != nil {
-		return "", err
-	}
-
-	return *result.SecretString, nil
-}
-
-func BuildAwsDsn(dsn string, driver string, secretName string, awsConf aws.Config, getSecretFunc func(secretName string, awsConf aws.Config) (awsSecret string, err error)) (awsSecretDsn string, err error) {
-	if secretName != "" && driver == "postgres" {
-
-		parsedDSN, err := url.Parse(dsn)
-		if err != nil {
-			return "", fmt.Errorf("error parsing DSN URL: %w", err)
-		}
-
-		username := parsedDSN.User.Username()
-		password, _ := parsedDSN.User.Password()
-		host := parsedDSN.Hostname()
-		port := parsedDSN.Port()
-		path := parsedDSN.Path
-		rawQuery := parsedDSN.RawQuery
-
-		secretString, err := getSecretFunc(secretName, awsConf)
-		if err != nil {
-			return "", fmt.Errorf("error retrieving secret: %w", err)
-		}
-
-		var secrets map[string]interface{}
-		if err := json.Unmarshal([]byte(secretString), &secrets); err != nil {
-			return "", fmt.Errorf("error unmarshalling secret: %w", err)
-		}
-
-		if val, ok := secrets["username"].(string); ok && val != "" {
-			username = val
-		}
-		if val, ok := secrets["password"].(string); ok && val != "" {
-			password = val
-		}
-
-		newDSN := fmt.Sprintf("postgres://%s:%s@%s:%s%s", url.QueryEscape(username), url.QueryEscape(password), host, port, path)
-		if rawQuery != "" {
-			newDSN = fmt.Sprintf("%s?%s", newDSN, rawQuery)
-		}
-
-		return newDSN, nil
-
-	} else if secretName != "" && driver != "postgres" {
-		return "", errors.New("secret_name with DSN info currently only works for postgres DSNs")
-	}
-
-	return dsn, nil
-}
-
-func sqlOpenWithReworks(ctx context.Context, logger *service.Logger, driver, dsn string, connSettings *connSettings, awsConf aws.Config) (*sql.DB, error) {
+func sqlOpenWithReworks(ctx context.Context, logger *service.Logger, driver, dsn string, connSettings *connSettings) (*sql.DB, error) {
 	updatedDSN, err := reworkDSN(driver, dsn)
 	if err != nil {
 		return nil, err
@@ -349,13 +295,13 @@ func sqlOpenWithReworks(ctx context.Context, logger *service.Logger, driver, dsn
 		logger.Warnf("Detected old-style Clickhouse Data Source Name: '%v', replacing with new style: '%v'", dsn, updatedDSN)
 	}
 
-	updatedDSN, err = BuildAwsDsn(dsn, driver, connSettings.secretName, awsConf, getSecretFromAWSSecretManager)
+	updatedDSN, err = BuildAwsDsn(dsn, driver)
 	if err != nil {
 		return nil, err
 	}
 
 	if updatedDSN != dsn {
-		logger.Infof("Updated dsn with info from AWS Secret '%v'", connSettings.secretName)
+		logger.Info("Updated dsn with info from AWS")
 	}
 
 	db, err := sql.Open(driver, updatedDSN)
diff --git a/internal/impl/sql/conn_fields_test.go b/internal/impl/sql/conn_fields_test.go
index 04680844fa..864d748482 100644
--- a/internal/impl/sql/conn_fields_test.go
+++ b/internal/impl/sql/conn_fields_test.go
@@ -10,11 +10,11 @@ import (
 	"testing"
 	"time"
 
-	"github.com/aws/aws-sdk-go-v2/aws"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
-	bento_sql "github.com/warpstreamlabs/bento/internal/impl/sql"
+	baws "github.com/warpstreamlabs/bento/internal/impl/sql/aws"
+
 	"github.com/warpstreamlabs/bento/public/service"
 
 	_ "github.com/warpstreamlabs/bento/public/components/pure"
@@ -189,7 +189,7 @@ sql_select:
 	}, msgs)
 }
 
-func mockGetSecretFromAWS(secretName string, awsConf aws.Config) (secretString string, err error) {
+func mockGetSecretFromAWS(secretName string) (secretString string, err error) {
 	var secret map[string]interface{}
 	if secretName == "validFullSecret" {
 		secret = map[string]interface{}{
@@ -211,9 +211,7 @@ func mockGetSecretFromAWS(secretName string, awsConf aws.Config) (secretString s
 	return string(secretBytes), nil
 }
 
-func TestBuildAwsDsn(t *testing.T) {
-	awsConf := aws.Config{}
-
+func TestBuildAwsDsnFromSecret(t *testing.T) {
 	tests := []struct {
 		name          string
 		dsn           string
@@ -269,7 +267,89 @@ func TestBuildAwsDsn(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			awsSecretDsn, err := bento_sql.BuildAwsDsn(tt.dsn, tt.driver, tt.secretName, awsConf, mockGetSecretFromAWS)
+			fn := func() (string, error) {
+				return mockGetSecretFromAWS(tt.secretName)
+			}
+			awsSecretDsn, err := baws.BuildAWSDsnFromSecret(tt.dsn, tt.driver, fn)
+			if tt.expectedError {
+				assert.Error(t, err)
+				assert.Equal(t, tt.errorValue, err.Error())
+			} else {
+				assert.NoError(t, err)
+				assert.Equal(t, tt.expectedDSN, awsSecretDsn)
+			}
+		})
+	}
+}
+
+func mockGenerateIAMToken(dbEndpoint string, dbUser string) (string, error) {
+	tokenMap := map[string]string{
+		"mydb.cluster.region.rds.amazonaws.com:5432": "mock-postgres-token",
+		"mydb.cluster.region.rds.amazonaws.com:3306": "mock-mysql-token",
+	}
+
+	if dbEndpoint == "error.endpoint:5432" {
+		return "", fmt.Errorf("failed to generate IAM token")
+	}
+
+	if token, exists := tokenMap[dbEndpoint]; exists {
+		return token, nil
+	}
+
+	return "default-mock-token", nil
+}
+
+func TestBuildAwsDsnFromIAM(t *testing.T) {
+	tests := []struct {
+		name          string
+		dsn           string
+		driver        string
+		endpoint      string
+		username      string
+		expectedDSN   string
+		expectedError bool
+		errorValue    string
+	}{
+		{
+			name:          "valid_postgres_iam",
+			dsn:           "postgres://myuser:password@mydb.cluster.region.rds.amazonaws.com:5432/mydb?sslmode=require",
+			driver:        "postgres",
+			endpoint:      "mydb.cluster.region.rds.amazonaws.com:5432",
+			username:      "myuser",
+			expectedDSN:   "postgres://myuser:mock-postgres-token@mydb.cluster.region.rds.amazonaws.com:5432/mydb?sslmode=require",
+			expectedError: false,
+		},
+		{
+			name:          "valid_mysql_iam",
+			dsn:           "mysql://admin@mydb.cluster.region.rds.amazonaws.com:3306/mydb?tls=true",
+			driver:        "mysql",
+			endpoint:      "mydb.cluster.region.rds.amazonaws.com:3306",
+			username:      "admin",
+			expectedDSN:   "mysql://admin:mock-mysql-token@mydb.cluster.region.rds.amazonaws.com:3306/mydb?tls=true",
+			expectedError: false,
+		},
+		{
+			name:          "invalid_driver",
+			dsn:           "mongodb://user:pass@localhost:27017/db",
+			driver:        "mongodb",
+			expectedDSN:   "",
+			expectedError: true,
+			errorValue:    "cannot create DSN from IAM when driver is not postgres or mysql",
+		},
+		{
+			name:          "iam_token_error",
+			dsn:           "postgres://myuser:password@error.endpoint:5432/mydb",
+			driver:        "postgres",
+			username:      "myuser",
+			expectedDSN:   "",
+			expectedError: true,
+			errorValue:    "error retrieving IAM token: failed to generate IAM token",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			awsSecretDsn, err := baws.BuildAWSDsnFromIAMCredentials(tt.dsn, tt.driver, mockGenerateIAMToken)
 			if tt.expectedError {
 				assert.Error(t, err)
 				assert.Equal(t, tt.errorValue, err.Error())
@@ -277,6 +357,8 @@ func TestBuildAwsDsn(t *testing.T) {
 				assert.NoError(t, err)
 				assert.Equal(t, tt.expectedDSN, awsSecretDsn)
 			}
+
+			assert.Equal(t, tt.expectedDSN, awsSecretDsn)
 		})
 	}
 }
diff --git a/internal/impl/sql/input_sql_raw.go b/internal/impl/sql/input_sql_raw.go
index 51895f093a..e0ee35a3de 100644
--- a/internal/impl/sql/input_sql_raw.go
+++ b/internal/impl/sql/input_sql_raw.go
@@ -9,6 +9,7 @@ import (
 	"github.com/Jeffail/shutdown"
 
 	"github.com/aws/aws-sdk-go-v2/aws"
+
 	bento_aws "github.com/warpstreamlabs/bento/internal/impl/aws"
 
 	"github.com/warpstreamlabs/bento/public/bloblang"
@@ -30,7 +31,8 @@ func sqlRawInputConfig() *service.ConfigSpec {
 			Example("root = [ this.cat.meow, this.doc.woofs[0] ]").
 			Example(`root = [ metadata("user.id").string() ]`).
 			Optional()).
-		Field(service.NewAutoRetryNacksToggleField())
+		Field(service.NewAutoRetryNacksToggleField()).
+		LintRule(SQLConnLintRule) // TODO: Move AWS related fields to an 'aws' object field in Bento v2
 	for _, f := range connFields() {
 		spec = spec.Field(f)
 	}
@@ -124,13 +126,18 @@ func newSQLRawInputFromConfig(conf *service.ParsedConfig, mgr *service.Resources
 	if s.connSettings, err = connSettingsFromParsed(conf, mgr); err != nil {
 		return nil, err
 	}
-	if s.driver == "postgres" && s.connSettings.secretName != "" {
-		s.awsConf, err = bento_aws.GetSession(context.Background(), conf)
+
+	awsEnabled, err := IsAWSEnabled(conf)
+	if err != nil {
+		return nil, err
+	}
+
+	if awsEnabled {
+		s.awsConf, err = bento_aws.GetSession(context.Background(), conf.Namespace("aws"))
 		if err != nil {
 			return nil, err
 		}
 	}
-
 	return s, nil
 }
 
@@ -143,7 +150,7 @@ func (s *sqlRawInput) Connect(ctx context.Context) (err error) {
 	}
 
 	var db *sql.DB
-	if db, err = sqlOpenWithReworks(ctx, s.logger, s.driver, s.dsn, s.connSettings, s.awsConf); err != nil {
+	if db, err = sqlOpenWithReworks(ctx, s.logger, s.driver, s.dsn, s.connSettings); err != nil {
 		return err
 	}
 	defer func() {
diff --git a/internal/impl/sql/input_sql_select.go b/internal/impl/sql/input_sql_select.go
index 817fa16d07..673975c198 100644
--- a/internal/impl/sql/input_sql_select.go
+++ b/internal/impl/sql/input_sql_select.go
@@ -11,6 +11,7 @@ import (
 	"github.com/Jeffail/shutdown"
 
 	"github.com/aws/aws-sdk-go-v2/aws"
+
 	bento_aws "github.com/warpstreamlabs/bento/internal/impl/aws"
 
 	"github.com/warpstreamlabs/bento/public/bloblang"
@@ -49,7 +50,8 @@ func sqlSelectInputConfig() *service.ConfigSpec {
 			Description("An optional suffix to append to the select query.").
 			Optional().
 			Advanced()).
-		Field(service.NewAutoRetryNacksToggleField())
+		Field(service.NewAutoRetryNacksToggleField()).
+		LintRule(SQLConnLintRule) // TODO: Move AWS related fields to an 'aws' object field in Bento v2
 
 	for _, f := range connFields() {
 		spec = spec.Field(f)
@@ -177,8 +179,12 @@ func newSQLSelectInputFromConfig(conf *service.ParsedConfig, mgr *service.Resour
 		return nil, err
 	}
 
-	if s.driver == "postgres" && s.connSettings.secretName != "" {
-		s.awsConf, err = bento_aws.GetSession(context.Background(), conf)
+	awsEnabled, err := IsAWSEnabled(conf)
+	if err != nil {
+		return nil, err
+	}
+	if awsEnabled {
+		s.awsConf, err = bento_aws.GetSession(context.Background(), conf.Namespace("aws"))
 		if err != nil {
 			return nil, err
 		}
@@ -195,7 +201,7 @@ func (s *sqlSelectInput) Connect(ctx context.Context) (err error) {
 	}
 
 	var db *sql.DB
-	if db, err = sqlOpenWithReworks(ctx, s.logger, s.driver, s.dsn, s.connSettings, s.awsConf); err != nil {
+	if db, err = sqlOpenWithReworks(ctx, s.logger, s.driver, s.dsn, s.connSettings); err != nil {
 		return
 	}
 	defer func() {
diff --git a/internal/impl/sql/integration_test.go b/internal/impl/sql/integration_test.go
index 3c24cc6990..d0b200b456 100644
--- a/internal/impl/sql/integration_test.go
+++ b/internal/impl/sql/integration_test.go
@@ -1,14 +1,22 @@
+//nolint:staticcheck // Ignore SA1019
 package sql_test
 
 import (
 	"context"
 	"database/sql"
+	"errors"
 	"fmt"
+	"os"
+	"path/filepath"
 	"strings"
 	"sync"
 	"testing"
 	"time"
 
+	"github.com/aws/aws-sdk-go-v2/aws"
+	"github.com/aws/aws-sdk-go-v2/credentials"
+	"github.com/aws/aws-sdk-go-v2/service/rds"
+	"github.com/aws/aws-sdk-go-v2/service/redshift"
 	gonanoid "github.com/matoous/go-nanoid/v2"
 	"github.com/ory/dockertest/v3"
 	"github.com/stretchr/testify/assert"
@@ -1256,3 +1264,262 @@ args_mapping: 'root = [ this.foo ]'
 	require.NoError(t, err)
 	assert.JSONEq(t, `[{"foo": "blobfish", "bar": "ARE REALLY COOL", "baz": 41}]`, string(actBytes))
 }
+
+func TestIntegrationRedshiftSecret(t *testing.T) {
+	pool, err := dockertest.NewPool("")
+	if err != nil {
+		t.Skipf("Could not connect to docker: %s", err)
+	}
+	pool.MaxWait = 3 * time.Minute
+
+	// we need a localstack pro image
+	if os.Getenv("LOCALSTACK_AUTH_TOKEN") == "" {
+		t.Skip("LOCALSTACK_AUTH_TOKEN is not set")
+	}
+
+	tfPath, err := filepath.Abs("./resources/redshiftsecret.tf")
+	require.NoError(t, err)
+
+	resource, err := pool.RunWithOptions(&dockertest.RunOptions{
+		Repository: "localstack/localstack-pro",
+		Env: []string{
+			"LOCALSTACK_AUTH_TOKEN=" + os.Getenv("LOCALSTACK_AUTH_TOKEN"),
+			"EXTENSION_AUTO_INSTALL=localstack-extension-terraform-init",
+		},
+		Mounts: []string{
+			tfPath + ":/etc/localstack/init/ready.d/main.tf",
+			"/var/run/docker.sock:/var/run/docker.sock",
+		},
+		ExposedPorts: []string{"4566/tcp", "4510-4559/tcp"},
+	})
+	require.NoError(t, err)
+
+	t.Cleanup(func() {
+		if err = pool.Purge(resource); err != nil {
+			t.Logf("Failed to clean up docker resource: %s", err)
+		}
+	})
+
+	_ = resource.Expire(900)
+
+	// grab some ports and wait for the tf to boot up redshift etc.
+	localstackPort := resource.GetPort("4566/tcp")
+	redshiftPort := resource.GetPort("4510/tcp")
+
+	redshiftEndpointAddress, err := waitForRedshiftAndGetAddress(localstackPort, redshiftPort, "my-redshift-cluster")
+	require.NoError(t, err)
+
+	// run a config that will fetch the secrets and create 10 rows in the redshift db
+	builder := service.NewStreamBuilder()
+
+	err = builder.SetYAML(fmt.Sprintf(`input:
+  generate:
+    mapping: |
+        root = {"age":random_int(min: 0, max: 100), "name":fake("name")}
+    interval: 1ms
+    count: 10
+
+output:
+  sql_insert:
+    driver: postgres 
+    dsn: postgresql://username_from_secret:password_from_secret@%v/dev?sslmode=disable
+    table: test
+    columns: [age, name]
+    args_mapping: |
+      root = [
+        this.age,
+        this.name,
+      ]
+    init_statement: "CREATE TABLE test (name varchar(255), age int);"
+    secret_name: "redshift-password"
+    region: us-east-1
+    endpoint: http://localhost:%v
+    credentials:
+      id: test
+      secret: test`, redshiftEndpointAddress, localstackPort))
+
+	stream, err := builder.Build()
+	require.NoError(t, err)
+
+	err = stream.Run(context.Background())
+	require.NoError(t, err)
+
+	// check there is 10 rows in the db
+	connStr := fmt.Sprintf("postgres://admin:Password123@localhost:%v/dev?sslmode=disable", redshiftPort)
+	db, err := sql.Open("postgres", connStr)
+	require.NoError(t, err)
+	defer db.Close()
+
+	var count int
+	err = db.QueryRow("SELECT COUNT(*) FROM test;").Scan(&count)
+	require.NoError(t, err)
+	assert.Equal(t, 10, count)
+}
+
+func waitForRedshiftAndGetAddress(localstackPort string, redshiftPort string, clusterID string) (redshiftEndpointAddress string, err error) {
+	cfg := aws.Config{
+		Region:      "us-east-1",
+		Credentials: credentials.NewStaticCredentialsProvider("test", "test", ""),
+		EndpointResolverWithOptions: aws.EndpointResolverWithOptionsFunc(
+			func(service, region string, options ...interface{}) (aws.Endpoint, error) {
+				return aws.Endpoint{URL: fmt.Sprintf("http://localhost:%v", localstackPort)}, nil
+			},
+		),
+	}
+
+	client := redshift.NewFromConfig(cfg)
+	maxAttempts := 120
+	for range maxAttempts {
+		time.Sleep(time.Second)
+		resp, err := client.DescribeClusters(context.Background(), &redshift.DescribeClustersInput{
+			ClusterIdentifier: &clusterID,
+		})
+		if err != nil {
+			continue
+		}
+		if len(resp.Clusters) == 0 {
+			continue
+		}
+		if *resp.Clusters[0].ClusterStatus == "available" {
+			time.Sleep(time.Second * 5) // redshift reports available somewhat prematurely
+			for _, cluster := range resp.Clusters {
+				if *cluster.ClusterIdentifier == "my-redshift-cluster" {
+					redshiftEndpointAddress = fmt.Sprintf("%v:%v", *cluster.Endpoint.Address, redshiftPort)
+				}
+			}
+			return redshiftEndpointAddress, nil
+		}
+	}
+	return "", errors.New("polling localstack for ready Redshift failed")
+}
+
+func TestIntegrationRdsIamAuth(t *testing.T) {
+	// TODO SKIP TEST - LOCALSTACK ISSUE
+	t.Skip("issue with localstack and IAM Auth with RDS")
+
+	pool, err := dockertest.NewPool("")
+	if err != nil {
+		t.Skipf("Could not connect to docker: %s", err)
+	}
+	pool.MaxWait = 3 * time.Minute
+
+	// we need a localstack pro image
+	if os.Getenv("LOCALSTACK_AUTH_TOKEN") == "" {
+		t.Skip("LOCALSTACK_AUTH_TOKEN is not set")
+	}
+
+	tfPath, err := filepath.Abs("./resources/rdsIamAuth.tf")
+	require.NoError(t, err)
+
+	resource, err := pool.RunWithOptions(&dockertest.RunOptions{
+		Repository: "localstack/localstack-pro",
+		Env: []string{
+			"LOCALSTACK_AUTH_TOKEN=" + os.Getenv("LOCALSTACK_AUTH_TOKEN"),
+			"EXTENSION_AUTO_INSTALL=localstack-extension-terraform-init",
+		},
+		Mounts: []string{
+			tfPath + ":/etc/localstack/init/ready.d/main.tf",
+			"/var/run/docker.sock:/var/run/docker.sock",
+		},
+		ExposedPorts: []string{"4566/tcp", "4510-4559/tcp"},
+	})
+	require.NoError(t, err)
+
+	t.Cleanup(func() {
+		if err = pool.Purge(resource); err != nil {
+			t.Logf("Failed to clean up docker resource: %s", err)
+		}
+	})
+
+	// grab some ports and wait for the tf to boot up rds etc.
+	localstackPort := resource.GetPort("4566/tcp")
+	rdsPort := resource.GetPort("4510/tcp")
+
+	_ = resource.Expire(900)
+
+	err = waitForRds(localstackPort)
+	require.NoError(t, err)
+
+	// create user in DB
+	connStr := fmt.Sprintf("postgres://masterusername:password123@localhost.localstack.cloud:%v/testdb?sslmode=disable", rdsPort)
+
+	db, err := sql.Open("postgres", connStr)
+	require.NoError(t, err)
+	defer db.Close()
+
+	_, err = db.Exec("CREATE USER iam_user WITH LOGIN;")
+	require.NoError(t, err)
+
+	_, err = db.Exec("GRANT rds_iam TO iam_user;")
+	require.NoError(t, err)
+
+	// run a config that uses IAM authentication
+	builder := service.NewStreamBuilder()
+
+	err = builder.SetYAML(fmt.Sprintf(`input:
+  generate:
+    mapping: |
+        root = {"age":random_int(min: 0, max: 100), "name":fake("name")}
+    interval: 1ms
+    count: 10
+  
+output:
+  sql_insert:
+    driver: postgres 
+    dsn: postgresql://iam_user:password@localhost.localstack.cloud:%v/testdb?sslmode=disable
+    table: test
+    columns: [age, name]
+    args_mapping: |
+      root = [
+        this.age,
+        this.name,
+      ]
+    iam_enabled: true
+    init_statement: "CREATE TABLE test (name varchar(255), age int);"`, rdsPort))
+
+	stream, err := builder.Build()
+	require.NoError(t, err)
+
+	err = stream.Run(context.Background())
+	require.NoError(t, err)
+
+	var count int
+	err = db.QueryRow("SELECT COUNT(*) FROM test;").Scan(&count)
+	require.NoError(t, err)
+	assert.Equal(t, 10, count)
+}
+
+func waitForRds(localstackPort string) (err error) {
+	cfg := aws.Config{
+		Region:      "us-east-1",
+		Credentials: credentials.NewStaticCredentialsProvider("test", "test", ""),
+		EndpointResolverWithOptions: aws.EndpointResolverWithOptionsFunc(
+			func(service, region string, options ...interface{}) (aws.Endpoint, error) {
+				return aws.Endpoint{URL: fmt.Sprintf("http://localhost:%v", localstackPort)}, nil
+			},
+		),
+	}
+
+	client := rds.NewFromConfig(cfg)
+	maxAttempts := 120
+	for range maxAttempts {
+		time.Sleep(time.Second)
+
+		instance := "my-rds-instance"
+		resp, err := client.DescribeDBInstances(context.Background(), &rds.DescribeDBInstancesInput{
+			DBInstanceIdentifier: &instance,
+		})
+
+		if err != nil {
+			continue
+		}
+		if len(resp.DBInstances) == 0 {
+			continue
+		}
+		if *resp.DBInstances[0].DBInstanceStatus == "available" {
+			time.Sleep(5 * time.Second)
+			return nil
+		}
+	}
+	return errors.New("polling localstack for ready RDS failed")
+}
diff --git a/internal/impl/sql/output_sql_deprecated.go b/internal/impl/sql/output_sql_deprecated.go
index 8a1057ebe7..e2b397a615 100644
--- a/internal/impl/sql/output_sql_deprecated.go
+++ b/internal/impl/sql/output_sql_deprecated.go
@@ -3,6 +3,7 @@ package sql
 import (
 	"context"
 
+	"github.com/aws/aws-sdk-go-v2/aws"
 	"github.com/warpstreamlabs/bento/public/bloblang"
 	"github.com/warpstreamlabs/bento/public/service"
 
@@ -82,10 +83,17 @@ func newSQLDeprecatedOutputFromConfig(conf *service.ParsedConfig, mgr *service.R
 		return nil, err
 	}
 
-	awsConf, err := bento_aws.GetSession(context.Background(), conf)
+	awsEnabled, err := IsAWSEnabled(conf)
 	if err != nil {
 		return nil, err
 	}
+	var awsConf aws.Config
+	if awsEnabled {
+		awsConf, err = bento_aws.GetSession(context.Background(), conf.Namespace("aws"))
+		if err != nil {
+			return nil, err
+		}
+	}
 
 	return newSQLRawOutput(mgr.Logger(), driverStr, dsnStr, queryStatic, nil, argsMapping, connSettings, awsConf), nil
 }
diff --git a/internal/impl/sql/output_sql_insert.go b/internal/impl/sql/output_sql_insert.go
index 71a5020ea9..851bfd9ff4 100644
--- a/internal/impl/sql/output_sql_insert.go
+++ b/internal/impl/sql/output_sql_insert.go
@@ -11,6 +11,7 @@ import (
 	"github.com/Jeffail/shutdown"
 
 	"github.com/aws/aws-sdk-go-v2/aws"
+
 	bento_aws "github.com/warpstreamlabs/bento/internal/impl/aws"
 	"github.com/warpstreamlabs/bento/public/bloblang"
 	"github.com/warpstreamlabs/bento/public/service"
@@ -44,8 +45,9 @@ func sqlInsertOutputConfig() *service.ConfigSpec {
 			Advanced().
 			Example("ON CONFLICT (name) DO NOTHING")).
 		Field(service.NewIntField("max_in_flight").
-			Description("The maximum number of inserts to run in parallel.").
-			Default(64))
+						Description("The maximum number of inserts to run in parallel.").
+						Default(64)).
+		LintRule(SQLConnLintRule) // TODO: Move AWS related fields to an 'aws' object field in Bento v2
 
 	for _, f := range connFields() {
 		spec = spec.Field(f)
@@ -184,8 +186,12 @@ func newSQLInsertOutputFromConfig(conf *service.ParsedConfig, mgr *service.Resou
 		return nil, err
 	}
 
-	if s.driver == "postgres" && s.connSettings.secretName != "" {
-		s.awsConf, err = bento_aws.GetSession(context.Background(), conf)
+	awsEnabled, err := IsAWSEnabled(conf)
+	if err != nil {
+		return nil, err
+	}
+	if awsEnabled {
+		s.awsConf, err = bento_aws.GetSession(context.Background(), conf.Namespace("aws"))
 		if err != nil {
 			return nil, err
 		}
@@ -203,7 +209,7 @@ func (s *sqlInsertOutput) Connect(ctx context.Context) error {
 	}
 
 	var err error
-	if s.db, err = sqlOpenWithReworks(ctx, s.logger, s.driver, s.dsn, s.connSettings, s.awsConf); err != nil {
+	if s.db, err = sqlOpenWithReworks(ctx, s.logger, s.driver, s.dsn, s.connSettings); err != nil {
 		return err
 	}
 
diff --git a/internal/impl/sql/output_sql_raw.go b/internal/impl/sql/output_sql_raw.go
index 3c957c1e2d..e441ec756f 100644
--- a/internal/impl/sql/output_sql_raw.go
+++ b/internal/impl/sql/output_sql_raw.go
@@ -9,6 +9,7 @@ import (
 	"github.com/Jeffail/shutdown"
 
 	"github.com/aws/aws-sdk-go-v2/aws"
+
 	bento_aws "github.com/warpstreamlabs/bento/internal/impl/aws"
 	"github.com/warpstreamlabs/bento/public/bloblang"
 	"github.com/warpstreamlabs/bento/public/service"
@@ -34,8 +35,9 @@ func sqlRawOutputConfig() *service.ConfigSpec {
 			Example(`root = [ metadata("user.id").string() ]`).
 			Optional()).
 		Field(service.NewIntField("max_in_flight").
-			Description("The maximum number of inserts to run in parallel.").
-			Default(64))
+						Description("The maximum number of inserts to run in parallel.").
+						Default(64)).
+		LintRule(SQLConnLintRule) // TODO: Move AWS related fields to an 'aws' object field in Bento v2
 
 	for _, f := range connFields() {
 		spec = spec.Field(f)
@@ -138,10 +140,17 @@ func newSQLRawOutputFromConfig(conf *service.ParsedConfig, mgr *service.Resource
 		return nil, err
 	}
 
-	awsConf, err := bento_aws.GetSession(context.Background(), conf)
+	awsEnabled, err := IsAWSEnabled(conf)
 	if err != nil {
 		return nil, err
 	}
+	var awsConf aws.Config
+	if awsEnabled {
+		awsConf, err = bento_aws.GetSession(context.Background(), conf)
+		if err != nil {
+			return nil, err
+		}
+	}
 
 	return newSQLRawOutput(mgr.Logger(), driverStr, dsnStr, queryStatic, queryDyn, argsMapping, connSettings, awsConf), nil
 }
@@ -177,7 +186,7 @@ func (s *sqlRawOutput) Connect(ctx context.Context) error {
 	}
 
 	var err error
-	if s.db, err = sqlOpenWithReworks(ctx, s.logger, s.driver, s.dsn, s.connSettings, s.awsConf); err != nil {
+	if s.db, err = sqlOpenWithReworks(ctx, s.logger, s.driver, s.dsn, s.connSettings); err != nil {
 		return err
 	}
 
diff --git a/internal/impl/sql/processor_sql_deprecated.go b/internal/impl/sql/processor_sql_deprecated.go
index 8d1bdaf104..881d0982d7 100644
--- a/internal/impl/sql/processor_sql_deprecated.go
+++ b/internal/impl/sql/processor_sql_deprecated.go
@@ -1,9 +1,6 @@
 package sql
 
 import (
-	"context"
-
-	bento_aws "github.com/warpstreamlabs/bento/internal/impl/aws"
 	"github.com/warpstreamlabs/bento/public/bloblang"
 	"github.com/warpstreamlabs/bento/public/service"
 )
@@ -96,10 +93,5 @@ func NewSQLDeprecatedProcessorFromConfig(conf *service.ParsedConfig, mgr *servic
 		return nil, err
 	}
 
-	awsConf, err := bento_aws.GetSession(context.Background(), conf)
-	if err != nil {
-		return nil, err
-	}
-
-	return newSQLRawProcessor(mgr.Logger(), driverStr, dsnStr, queryStatic, queryDyn, onlyExec, argsMapping, connSettings, awsConf)
+	return newSQLRawProcessor(mgr.Logger(), driverStr, dsnStr, queryStatic, queryDyn, onlyExec, argsMapping, connSettings)
 }
diff --git a/internal/impl/sql/processor_sql_insert.go b/internal/impl/sql/processor_sql_insert.go
index 553bb06af7..d98b9e87e8 100644
--- a/internal/impl/sql/processor_sql_insert.go
+++ b/internal/impl/sql/processor_sql_insert.go
@@ -12,7 +12,6 @@ import (
 	"github.com/Jeffail/shutdown"
 
 	bento_aws "github.com/warpstreamlabs/bento/internal/impl/aws"
-
 	"github.com/warpstreamlabs/bento/public/bloblang"
 	"github.com/warpstreamlabs/bento/public/service"
 )
@@ -42,10 +41,11 @@ If the insert fails to execute then the message will still remain unchanged and
 			Optional().
 			Advanced()).
 		Field(service.NewStringField("suffix").
-			Description("An optional suffix to append to the insert query.").
-			Optional().
-			Advanced().
-			Example("ON CONFLICT (name) DO NOTHING"))
+						Description("An optional suffix to append to the insert query.").
+						Optional().
+						Advanced().
+						Example("ON CONFLICT (name) DO NOTHING")).
+		LintRule(SQLConnLintRule) // TODO: Move AWS related fields to an 'aws' object field in Bento v2
 
 	for _, f := range connFields() {
 		spec = spec.Field(f)
@@ -94,6 +94,7 @@ type sqlInsertProcessor struct {
 
 	useTxStmt   bool
 	argsMapping *bloblang.Executor
+	awsConf     aws.Config
 
 	logger  *service.Logger
 	shutSig *shutdown.Signaller
@@ -174,15 +175,18 @@ func NewSQLInsertProcessorFromConfig(conf *service.ParsedConfig, mgr *service.Re
 		return nil, err
 	}
 
-	var awsConf aws.Config
-	if driverStr == "postgres" && connSettings.secretName != "" {
-		awsConf, err = bento_aws.GetSession(context.Background(), conf)
+	awsEnabled, err := IsAWSEnabled(conf)
+	if err != nil {
+		return nil, err
+	}
+	if awsEnabled {
+		s.awsConf, err = bento_aws.GetSession(context.Background(), conf.Namespace("aws"))
 		if err != nil {
 			return nil, err
 		}
 	}
 
-	if s.db, err = sqlOpenWithReworks(context.Background(), mgr.Logger(), driverStr, dsnStr, connSettings, awsConf); err != nil {
+	if s.db, err = sqlOpenWithReworks(context.Background(), mgr.Logger(), driverStr, dsnStr, connSettings); err != nil {
 		return nil, err
 	}
 
diff --git a/internal/impl/sql/processor_sql_raw.go b/internal/impl/sql/processor_sql_raw.go
index bf43fdf326..78f97d2b2d 100644
--- a/internal/impl/sql/processor_sql_raw.go
+++ b/internal/impl/sql/processor_sql_raw.go
@@ -9,8 +9,8 @@ import (
 	"github.com/Jeffail/shutdown"
 
 	"github.com/aws/aws-sdk-go-v2/aws"
-	bento_aws "github.com/warpstreamlabs/bento/internal/impl/aws"
 
+	bento_aws "github.com/warpstreamlabs/bento/internal/impl/aws"
 	"github.com/warpstreamlabs/bento/public/bloblang"
 	"github.com/warpstreamlabs/bento/public/service"
 )
@@ -38,8 +38,9 @@ If the query fails to execute then the message will remain unchanged and the err
 			Example(`root = [ metadata("user.id").string() ]`).
 			Optional()).
 		Field(service.NewBoolField("exec_only").
-			Description("Whether the query result should be discarded. When set to `true` the message contents will remain unchanged, which is useful in cases where you are executing inserts, updates, etc.").
-			Default(false))
+						Description("Whether the query result should be discarded. When set to `true` the message contents will remain unchanged, which is useful in cases where you are executing inserts, updates, etc.").
+						Default(false)).
+		LintRule(SQLConnLintRule) // TODO: Move AWS related fields to an 'aws' object field in Bento v2
 
 	for _, f := range connFields() {
 		spec = spec.Field(f)
@@ -151,12 +152,26 @@ func NewSQLRawProcessorFromConfig(conf *service.ParsedConfig, mgr *service.Resou
 		return nil, err
 	}
 
-	awsConf, err := bento_aws.GetSession(context.Background(), conf)
+	awsEnabled, err := IsAWSEnabled(conf)
+	if err != nil {
+		return nil, err
+	}
+	var awsConf aws.Config
+	if awsEnabled {
+		awsConf, err = bento_aws.GetSession(context.Background(), conf)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	proc, err := newSQLRawProcessor(mgr.Logger(), driverStr, dsnStr, queryStatic, queryDyn, onlyExec, argsMapping, connSettings)
 	if err != nil {
 		return nil, err
 	}
 
-	return newSQLRawProcessor(mgr.Logger(), driverStr, dsnStr, queryStatic, queryDyn, onlyExec, argsMapping, connSettings, awsConf)
+	proc.awsConf = awsConf
+
+	return proc, nil
 }
 
 func newSQLRawProcessor(
@@ -167,7 +182,6 @@ func newSQLRawProcessor(
 	onlyExec bool,
 	argsMapping *bloblang.Executor,
 	connSettings *connSettings,
-	awsConf aws.Config,
 ) (*sqlRawProcessor, error) {
 	s := &sqlRawProcessor{
 		logger:      logger,
@@ -176,11 +190,10 @@ func newSQLRawProcessor(
 		queryDyn:    queryDyn,
 		onlyExec:    onlyExec,
 		argsMapping: argsMapping,
-		awsConf:     awsConf,
 	}
 
 	var err error
-	if s.db, err = sqlOpenWithReworks(context.Background(), logger, driverStr, dsnStr, connSettings, awsConf); err != nil {
+	if s.db, err = sqlOpenWithReworks(context.Background(), logger, driverStr, dsnStr, connSettings); err != nil {
 		return nil, err
 	}
 	connSettings.apply(context.Background(), s.db, s.logger)
diff --git a/internal/impl/sql/processor_sql_select.go b/internal/impl/sql/processor_sql_select.go
index 30525e7637..a3eb2db83c 100644
--- a/internal/impl/sql/processor_sql_select.go
+++ b/internal/impl/sql/processor_sql_select.go
@@ -11,6 +11,7 @@ import (
 	"github.com/Jeffail/shutdown"
 
 	"github.com/aws/aws-sdk-go-v2/aws"
+
 	bento_aws "github.com/warpstreamlabs/bento/internal/impl/aws"
 	"github.com/warpstreamlabs/bento/public/bloblang"
 	"github.com/warpstreamlabs/bento/public/service"
@@ -48,9 +49,10 @@ If the query fails to execute then the message will remain unchanged and the err
 			Optional().
 			Advanced()).
 		Field(service.NewStringField("suffix").
-			Description("An optional suffix to append to the select query.").
-			Optional().
-			Advanced())
+						Description("An optional suffix to append to the select query.").
+						Optional().
+						Advanced()).
+		LintRule(SQLConnLintRule) // TODO: Move AWS related fields to an 'aws' object field in Bento v2
 
 	for _, f := range connFields() {
 		spec = spec.Field(f)
@@ -175,14 +177,18 @@ func NewSQLSelectProcessorFromConfig(conf *service.ParsedConfig, mgr *service.Re
 		return nil, err
 	}
 
-	if driverStr == "postgres" && connSettings.secretName != "" {
-		s.awsConf, err = bento_aws.GetSession(context.Background(), conf)
+	awsEnabled, err := IsAWSEnabled(conf)
+	if err != nil {
+		return nil, err
+	}
+	if awsEnabled {
+		s.awsConf, err = bento_aws.GetSession(context.Background(), conf.Namespace("aws"))
 		if err != nil {
 			return nil, err
 		}
 	}
 
-	if s.db, err = sqlOpenWithReworks(context.Background(), mgr.Logger(), driverStr, dsnStr, connSettings, s.awsConf); err != nil {
+	if s.db, err = sqlOpenWithReworks(context.Background(), mgr.Logger(), driverStr, dsnStr, connSettings); err != nil {
 		return nil, err
 	}
 	connSettings.apply(context.Background(), s.db, s.logger)
diff --git a/internal/impl/sql/resources/rdsIamAuth.tf b/internal/impl/sql/resources/rdsIamAuth.tf
new file mode 100644
index 0000000000..4fc9d4e91c
--- /dev/null
+++ b/internal/impl/sql/resources/rdsIamAuth.tf
@@ -0,0 +1,137 @@
+provider "aws" {
+    region = "us-east-1"
+}
+
+resource "aws_vpc" "rds_vpc" {
+    cidr_block = "10.0.0.0/16"
+    instance_tenancy = "default"
+
+    enable_dns_support = true
+    enable_dns_hostnames = true
+
+    tags = {
+        Name = "rds-vpc"
+    }
+}
+
+resource "aws_internet_gateway" "rds_vpc_gw" {
+    vpc_id = aws_vpc.rds_vpc.id
+    depends_on = [aws_vpc.rds_vpc]
+}
+
+resource "aws_security_group" "rds_sg" {
+    vpc_id = aws_vpc.rds_vpc.id 
+
+    ingress {
+        from_port = 5432
+        to_port = 5432
+        protocol = "tcp"
+        cidr_blocks = ["0.0.0.0/0"]
+    }
+
+    egress {
+        from_port = 0 
+        to_port = 0 
+        protocol = "-1"
+        cidr_blocks = ["0.0.0.0/0"]
+    }
+
+    tags = {
+        Name = "rds-sg"
+    }
+
+    depends_on = [aws_vpc.rds_vpc]
+}
+
+resource "aws_route_table" "rds_route_table" {
+    vpc_id = aws_vpc.rds_vpc.id
+
+    route {
+        cidr_block = "0.0.0.0/0"
+        gateway_id = aws_internet_gateway.rds_vpc_gw.id
+    }
+
+    tags = {
+        Name = "rds-route-table"
+    }
+}
+
+resource "aws_subnet" "rds_subnet_1" {
+    vpc_id = aws_vpc.rds_vpc.id
+    cidr_block = "10.0.1.0/24"
+    availability_zone = "us-east-1a"
+    map_public_ip_on_launch = "true"
+
+    tags = {
+        Name = "rds-subnet-1"
+    }
+
+    depends_on = [aws_vpc.rds_vpc]
+}
+
+resource "aws_subnet" "rds_subnet_2" {
+    vpc_id = aws_vpc.rds_vpc.id
+    cidr_block = "10.0.2.0/24"
+    availability_zone = "us-east-1b"
+    map_public_ip_on_launch = "true"
+
+    tags = {
+        Name = "rds-subnet-2"
+    }
+
+    depends_on = [aws_vpc.rds_vpc]
+}
+
+resource "aws_subnet" "rds_subnet_3" {
+    vpc_id = aws_vpc.rds_vpc.id
+    cidr_block = "10.0.3.0/24"
+    availability_zone = "us-east-1c"
+    map_public_ip_on_launch = "true"
+
+    tags = {
+        Name = "rds-subnet-3"
+    }
+
+    depends_on = [aws_vpc.rds_vpc]
+}
+
+resource "aws_route_table_association" "rds_subnet_1_association" {
+    subnet_id = aws_subnet.rds_subnet_1.id
+    route_table_id = aws_route_table.rds_route_table.id
+}
+
+resource "aws_route_table_association" "rds_subnet_2_association" {
+  subnet_id      = aws_subnet.rds_subnet_2.id
+  route_table_id = aws_route_table.rds_route_table.id
+}
+
+resource "aws_route_table_association" "rds_subnet_3_association" {
+  subnet_id      = aws_subnet.rds_subnet_3.id
+  route_table_id = aws_route_table.rds_route_table.id
+}
+
+resource "aws_db_subnet_group" "rds_subnet_group" {
+  name = "rds-subnet-group"
+  subnet_ids = [aws_subnet.rds_subnet_1.id, aws_subnet.rds_subnet_2.id, aws_subnet.rds_subnet_3.id]
+
+  tags = {
+    Name = "rds-subnet-group"
+  }
+}
+
+resource "aws_db_instance" "rds_instance" {
+  identifier           = "my-rds-instance"
+  engine               = "postgres"
+  engine_version       = "15"
+  instance_class       = "db.t3.micro"
+  allocated_storage    = 20
+  storage_encrypted    = true
+  db_name              = "testdb"
+  username             = "masterusername"
+  password             = "password123"
+  skip_final_snapshot  = true
+  publicly_accessible  = true
+  iam_database_authentication_enabled = true
+  vpc_security_group_ids = [aws_security_group.rds_sg.id]
+  db_subnet_group_name = aws_db_subnet_group.rds_subnet_group.name
+}
\ No newline at end of file
diff --git a/internal/impl/sql/resources/redshiftsecret.tf b/internal/impl/sql/resources/redshiftsecret.tf
new file mode 100644
index 0000000000..a3484cd993
--- /dev/null
+++ b/internal/impl/sql/resources/redshiftsecret.tf
@@ -0,0 +1,138 @@
+provider "aws" {
+  region = "us-east-1"  # Change to your desired AWS region
+}
+
+resource "aws_vpc" "redshift_vpc" {
+  cidr_block = "10.0.0.0/16"
+  instance_tenancy = "default"
+  tags = {
+    Name = "redshift-vpc"
+  }
+}
+
+resource "aws_internet_gateway" "redshift_vpc_gw" {
+  vpc_id = aws_vpc.redshift_vpc.id
+  depends_on = [aws_vpc.redshift_vpc]
+}
+
+resource "aws_security_group" "redshift_sg" {
+  vpc_id = aws_vpc.redshift_vpc.id
+
+  ingress {
+    from_port   = 5439
+    to_port     = 5439
+    protocol    = "tcp"
+    cidr_blocks = ["0.0.0.0/0"]  # Change to your specific IP range
+  }
+
+  egress {
+    from_port   = 0
+    to_port     = 0
+    protocol    = "-1"
+    cidr_blocks = ["0.0.0.0/0"]
+  }
+
+  tags = {
+    Name = "redshift-sg"
+  }
+
+  depends_on = [aws_vpc.redshift_vpc]
+}
+
+resource "aws_route_table" "redshift_route_table" {
+  vpc_id = aws_vpc.redshift_vpc.id
+
+  route {
+    cidr_block = "0.0.0.0/0"
+    gateway_id = aws_internet_gateway.redshift_vpc_gw.id
+  }
+
+  tags = {
+    Name = "redshift-route-table"
+  }
+}
+
+resource "aws_subnet" "redshift_subnet_1" {
+  vpc_id = aws_vpc.redshift_vpc.id
+  cidr_block = "10.0.1.0/24"
+  availability_zone = "us-east-1a"
+  map_public_ip_on_launch = "true"
+
+  tags = {
+    Name = "redshift-subnet-1"
+  }
+
+  depends_on = [aws_vpc.redshift_vpc]
+}
+
+resource "aws_subnet" "redshift_subnet_2" {
+  vpc_id = aws_vpc.redshift_vpc.id
+  cidr_block = "10.0.2.0/24" 
+  availability_zone = "us-east-1b"
+  map_public_ip_on_launch = "true"
+
+  tags = {
+    Name = "redshift-subnet-2"
+  }
+
+  depends_on = [aws_vpc.redshift_vpc]
+}
+
+resource "aws_route_table_association" "redshift_subnet_1_association" {
+  subnet_id      = aws_subnet.redshift_subnet_1.id
+  route_table_id = aws_route_table.redshift_route_table.id
+}
+
+resource "aws_route_table_association" "redshift_subnet_2_association" {
+  subnet_id      = aws_subnet.redshift_subnet_2.id
+  route_table_id = aws_route_table.redshift_route_table.id
+}
+
+resource "aws_redshift_subnet_group" "redshift_subnet_group" {
+  name = "redshift-subnet-group"
+  subnet_ids = [aws_subnet.redshift_subnet_1.id, aws_subnet.redshift_subnet_2.id]
+
+  tags = {
+    Name = "redshift-subnet-group"
+  }
+}
+
+resource "aws_secretsmanager_secret" "redshift_master_password" {
+  name        = "redshift-password"
+}
+
+resource "aws_secretsmanager_secret_version" "redshift_master_password_version" {
+  secret_id     = aws_secretsmanager_secret.redshift_master_password.id
+  secret_string = jsonencode({
+    username = "admin"
+    password = "Password123"
+  })
+}
+
+resource "aws_redshift_cluster" "my_redshift" {
+  cluster_identifier      = "my-redshift-cluster"
+  database_name           = "dev"
+
+  # Use the Secrets Manager secret to get the username and password
+  master_username         = jsondecode(aws_secretsmanager_secret_version.redshift_master_password_version.secret_string)["username"]
+  master_password         = jsondecode(aws_secretsmanager_secret_version.redshift_master_password_version.secret_string)["password"]
+
+  node_type               = "dc2.large"
+  cluster_type            = "single-node"
+  number_of_nodes         = 1
+  cluster_subnet_group_name = aws_redshift_subnet_group.redshift_subnet_group.id
+  port                    = 5439
+  publicly_accessible     = true
+  vpc_security_group_ids  = [aws_security_group.redshift_sg.id]
+  skip_final_snapshot     = true
+
+  tags = {
+    Name = "MyRedshiftCluster"
+  }
+
+  depends_on = [
+    aws_vpc.redshift_vpc,
+    aws_security_group.redshift_sg,
+    aws_redshift_subnet_group.redshift_subnet_group,
+  ]
+}
diff --git a/public/components/sql/base/package.go b/public/components/sql/base/package.go
index 102e0c1cb3..a3209cb879 100644
--- a/public/components/sql/base/package.go
+++ b/public/components/sql/base/package.go
@@ -5,4 +5,5 @@ package base
 import (
 	// Bring in the internal plugin definitions.
 	_ "github.com/warpstreamlabs/bento/internal/impl/sql"
+	_ "github.com/warpstreamlabs/bento/internal/impl/sql/aws"
 )
diff --git a/website/docs/components/caches/sql.md b/website/docs/components/caches/sql.md
index f2d3e02e7e..03825c25e7 100644
--- a/website/docs/components/caches/sql.md
+++ b/website/docs/components/caches/sql.md
@@ -69,6 +69,7 @@ sql:
   conn_max_idle: 2
   conn_max_open: 0 # No default (optional)
   secret_name: "" # No default (optional)
+  iam_enabled: false
   region: ""
   endpoint: ""
   credentials:
@@ -302,10 +303,20 @@ Type: `int`
 
 ### `secret_name`
 
-An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to postgres.
+An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to `postgres`.
 
 
 Type: `string`  
+Requires version 1.3.0 or newer  
+
+### `iam_enabled`
+
+An optional field used to generate an IAM authentication token to connect to an Amazon Relational Database (RDS) DB instance. This will overwrite the Password in the DSN with the generated token only if the drivers are `mysql` or `postgres`.
+
+
+Type: `bool`  
+Default: `false`  
+Requires version 1.8.0 or newer  
 
 ### `region`
 
diff --git a/website/docs/components/inputs/sql_raw.md b/website/docs/components/inputs/sql_raw.md
index 923c77dde8..a7ba5bace4 100644
--- a/website/docs/components/inputs/sql_raw.md
+++ b/website/docs/components/inputs/sql_raw.md
@@ -69,6 +69,7 @@ input:
     conn_max_idle: 2
     conn_max_open: 0 # No default (optional)
     secret_name: "" # No default (optional)
+    iam_enabled: false
     region: ""
     endpoint: ""
     credentials:
@@ -300,10 +301,20 @@ Type: `int`
 
 ### `secret_name`
 
-An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to postgres.
+An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to `postgres`.
 
 
 Type: `string`  
+Requires version 1.3.0 or newer  
+
+### `iam_enabled`
+
+An optional field used to generate an IAM authentication token to connect to an Amazon Relational Database (RDS) DB instance. This will overwrite the Password in the DSN with the generated token only if the drivers are `mysql` or `postgres`.
+
+
+Type: `bool`  
+Default: `false`  
+Requires version 1.8.0 or newer  
 
 ### `region`
 
diff --git a/website/docs/components/inputs/sql_select.md b/website/docs/components/inputs/sql_select.md
index 8b7e94078b..c74b7c10c0 100644
--- a/website/docs/components/inputs/sql_select.md
+++ b/website/docs/components/inputs/sql_select.md
@@ -75,6 +75,7 @@ input:
     conn_max_idle: 2
     conn_max_open: 0 # No default (optional)
     secret_name: "" # No default (optional)
+    iam_enabled: false
     region: ""
     endpoint: ""
     credentials:
@@ -341,10 +342,20 @@ Type: `int`
 
 ### `secret_name`
 
-An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to postgres.
+An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to `postgres`.
 
 
 Type: `string`  
+Requires version 1.3.0 or newer  
+
+### `iam_enabled`
+
+An optional field used to generate an IAM authentication token to connect to an Amazon Relational Database (RDS) DB instance. This will overwrite the Password in the DSN with the generated token only if the drivers are `mysql` or `postgres`.
+
+
+Type: `bool`  
+Default: `false`  
+Requires version 1.8.0 or newer  
 
 ### `region`
 
diff --git a/website/docs/components/outputs/sql_insert.md b/website/docs/components/outputs/sql_insert.md
index 0c52a25f40..8b89acdeab 100644
--- a/website/docs/components/outputs/sql_insert.md
+++ b/website/docs/components/outputs/sql_insert.md
@@ -76,6 +76,7 @@ output:
     conn_max_idle: 2
     conn_max_open: 0 # No default (optional)
     secret_name: "" # No default (optional)
+    iam_enabled: false
     region: ""
     endpoint: ""
     credentials:
@@ -338,10 +339,20 @@ Type: `int`
 
 ### `secret_name`
 
-An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to postgres.
+An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to `postgres`.
 
 
 Type: `string`  
+Requires version 1.3.0 or newer  
+
+### `iam_enabled`
+
+An optional field used to generate an IAM authentication token to connect to an Amazon Relational Database (RDS) DB instance. This will overwrite the Password in the DSN with the generated token only if the drivers are `mysql` or `postgres`.
+
+
+Type: `bool`  
+Default: `false`  
+Requires version 1.8.0 or newer  
 
 ### `region`
 
diff --git a/website/docs/components/outputs/sql_raw.md b/website/docs/components/outputs/sql_raw.md
index 2a80134a5c..e268179f59 100644
--- a/website/docs/components/outputs/sql_raw.md
+++ b/website/docs/components/outputs/sql_raw.md
@@ -73,6 +73,7 @@ output:
     conn_max_idle: 2
     conn_max_open: 0 # No default (optional)
     secret_name: "" # No default (optional)
+    iam_enabled: false
     region: ""
     endpoint: ""
     credentials:
@@ -319,10 +320,20 @@ Type: `int`
 
 ### `secret_name`
 
-An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to postgres.
+An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to `postgres`.
 
 
 Type: `string`  
+Requires version 1.3.0 or newer  
+
+### `iam_enabled`
+
+An optional field used to generate an IAM authentication token to connect to an Amazon Relational Database (RDS) DB instance. This will overwrite the Password in the DSN with the generated token only if the drivers are `mysql` or `postgres`.
+
+
+Type: `bool`  
+Default: `false`  
+Requires version 1.8.0 or newer  
 
 ### `region`
 
diff --git a/website/docs/components/processors/sql_insert.md b/website/docs/components/processors/sql_insert.md
index 914448f506..97bcedee6f 100644
--- a/website/docs/components/processors/sql_insert.md
+++ b/website/docs/components/processors/sql_insert.md
@@ -66,6 +66,7 @@ sql_insert:
   conn_max_idle: 2
   conn_max_open: 0 # No default (optional)
   secret_name: "" # No default (optional)
+  iam_enabled: false
   region: ""
   endpoint: ""
   credentials:
@@ -316,10 +317,20 @@ Type: `int`
 
 ### `secret_name`
 
-An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to postgres.
+An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to `postgres`.
 
 
 Type: `string`  
+Requires version 1.3.0 or newer  
+
+### `iam_enabled`
+
+An optional field used to generate an IAM authentication token to connect to an Amazon Relational Database (RDS) DB instance. This will overwrite the Password in the DSN with the generated token only if the drivers are `mysql` or `postgres`.
+
+
+Type: `bool`  
+Default: `false`  
+Requires version 1.8.0 or newer  
 
 ### `region`
 
diff --git a/website/docs/components/processors/sql_raw.md b/website/docs/components/processors/sql_raw.md
index d37d1471fb..a5906fe103 100644
--- a/website/docs/components/processors/sql_raw.md
+++ b/website/docs/components/processors/sql_raw.md
@@ -65,6 +65,7 @@ sql_raw:
   conn_max_idle: 2
   conn_max_open: 0 # No default (optional)
   secret_name: "" # No default (optional)
+  iam_enabled: false
   region: ""
   endpoint: ""
   credentials:
@@ -323,10 +324,20 @@ Type: `int`
 
 ### `secret_name`
 
-An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to postgres.
+An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to `postgres`.
 
 
 Type: `string`  
+Requires version 1.3.0 or newer  
+
+### `iam_enabled`
+
+An optional field used to generate an IAM authentication token to connect to an Amazon Relational Database (RDS) DB instance. This will overwrite the Password in the DSN with the generated token only if the drivers are `mysql` or `postgres`.
+
+
+Type: `bool`  
+Default: `false`  
+Requires version 1.8.0 or newer  
 
 ### `region`
 
diff --git a/website/docs/components/processors/sql_select.md b/website/docs/components/processors/sql_select.md
index 624601892e..c10b716258 100644
--- a/website/docs/components/processors/sql_select.md
+++ b/website/docs/components/processors/sql_select.md
@@ -68,6 +68,7 @@ sql_select:
   conn_max_idle: 2
   conn_max_open: 0 # No default (optional)
   secret_name: "" # No default (optional)
+  iam_enabled: false
   region: ""
   endpoint: ""
   credentials:
@@ -332,10 +333,20 @@ Type: `int`
 
 ### `secret_name`
 
-An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to postgres.
+An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to `postgres`.
 
 
 Type: `string`  
+Requires version 1.3.0 or newer  
+
+### `iam_enabled`
+
+An optional field used to generate an IAM authentication token to connect to an Amazon Relational Database (RDS) DB instance. This will overwrite the Password in the DSN with the generated token only if the drivers are `mysql` or `postgres`.
+
+
+Type: `bool`  
+Default: `false`  
+Requires version 1.8.0 or newer  
 
 ### `region`
 


Reviews:
user: login: jem-davies
body: LGTM
state: DISMISSED
author_association: COLLABORATOR
submitted_at: 2025-02-21T15:47:39Z
user: login: gregfurman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-22T09:16:11Z
user: login: jem-davies
body: let's block the merge button until we have communicated the breaking changes 
state: DISMISSED
author_association: COLLABORATOR
submitted_at: 2025-03-27T12:11:13Z
user: login: jem-davies
state: DISMISSED
author_association: COLLABORATOR
submitted_at: 2025-05-10T16:10:57Z
user: login: jem-davies
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-10T18:58:16Z
user: login: gregfurman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-10T21:13:03Z
user: login: jem-davies
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-10T21:16:59Z
user: login: gregfurman
body: Built bento on my local w/out the AWS package imported, which should work with these changes.

Once you confirm this works for you, happy to merge this bad boy! (I can't approve since I opened)
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-10T21:19:12Z

Review comments:
diff_hunk: @@ -108,14 +104,24 @@ CREATE TABLE IF NOT EXISTS some_table (
 			Description("An optional maximum number of open connections to the database. If conn_max_idle is greater than 0 and the new conn_max_open is less than conn_max_idle, then conn_max_idle will be reduced to match the new conn_max_open limit. If `value <= 0`, then there is no limit on the number of open connections. The default is 0 (unlimited).").
 			Optional().
 			Advanced(),
-		service.NewStringField("secret_name").
-			Description("An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to postgres.").
-			Optional().
-			Advanced(),
+		service.NewBoolField("aws_enabled").
+			Description("Enables connectivity to AWS for credential retrieval.").
+			Default(false),
+		service.NewObjectField(SQLFieldAWS,
+			append([]*service.ConfigField{
+				service.NewBoolField("iam_enabled").
+					Description("An optional field used to generate an IAM authentication token to connect to an Amazon Relational Database (RDS) DB instance. This will overwrite the Password in the DSN with the generated token only if the drivers are `mysql` or `postgres`.").
+					Optional().
+					Advanced(),
+				service.NewStringField("secret_name").
+					Description("An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to `postgres`.").
path: internal/impl/sql/conn_fields.go
user: login: gregfurman
body: @jem-davies We should make a note that this field has changed to within the `aws` config section
created_at: 2025-02-22T09:16:11Z
author_association: COLLABORATOR
diff_hunk: @@ -1256,3 +1264,259 @@ args_mapping: 'root = [ this.foo ]'
 	require.NoError(t, err)
 	assert.JSONEq(t, `[{"foo": "blobfish", "bar": "ARE REALLY COOL", "baz": 41}]`, string(actBytes))
 }
+
+func TestIntegrationRedshiftSecret(t *testing.T) {
+	pool, err := dockertest.NewPool("")
+	if err != nil {
+		t.Skipf("Could not connect to docker: %s", err)
+	}
+	pool.MaxWait = 3 * time.Minute
+
+	// we need a localstack pro image
+	if os.Getenv("LOCALSTACK_AUTH_TOKEN") == "" {
+		t.Skip("LOCALSTACK_AUTH_TOKEN is not set")
+	}
+
+	tfPath, err := filepath.Abs("./resources/redshiftsecret.tf")
+	require.NoError(t, err)
+
+	resource, err := pool.RunWithOptions(&dockertest.RunOptions{
+		Repository: "localstack/localstack-pro",
+		Env: []string{
+			"LOCALSTACK_AUTH_TOKEN=" + os.Getenv("LOCALSTACK_AUTH_TOKEN"),
+			"EXTENSION_AUTO_INSTALL=localstack-extension-terraform-init",
+		},
+		Mounts: []string{
+			tfPath + ":/etc/localstack/init/ready.d/main.tf",
+			"/var/run/docker.sock:/var/run/docker.sock",
+		},
+		ExposedPorts: []string{"4566/tcp", "4510-4559/tcp"},
+	})
+	require.NoError(t, err)
+
+	t.Cleanup(func() {
+		if err = pool.Purge(resource); err != nil {
+			t.Logf("Failed to clean up docker resource: %s", err)
+		}
+	})
+
+	_ = resource.Expire(900)
+
+	// grab some ports and wait for the tf to boot up redshift etc.
+	localstackPort := resource.GetPort("4566/tcp")
+	redshiftPort := resource.GetPort("4510/tcp")
+
+	redshiftEndpointAddress, err := waitForRedshiftAndGetAddress(localstackPort, redshiftPort, "my-redshift-cluster")
+	require.NoError(t, err)
+
+	// run a config that will fetch the secrets and create 10 rows in the redshift db
+	builder := service.NewStreamBuilder()
+
+	err = builder.SetYAML(fmt.Sprintf(`input:
+  generate:
+    mapping: |
+        root = {"age":random_int(min: 0, max: 100), "name":fake("name")}
+    interval: 1ms
+    count: 10
+
+output:
+  sql_insert:
+    driver: postgres 
+    dsn: postgresql://username_from_secret:password_from_secret@%v/dev?sslmode=disable
+    table: test
+    columns: [age, name]
+    args_mapping: |
+      root = [
+        this.age,
+        this.name,
+      ]
+    init_statement: "CREATE TABLE test (name varchar(255), age int);"
+    secret_name: "redshift-password"
+    region: us-east-1
+    endpoint: http://localhost:%v
+    credentials:
+      id: test
+      secret: test`, redshiftEndpointAddress, localstackPort))
+
+	stream, err := builder.Build()
+	require.NoError(t, err)
+
+	err = stream.Run(context.Background())
+	require.NoError(t, err)
+
+	// check there is 10 rows in the db
+	connStr := fmt.Sprintf("postgres://admin:Password123@localhost:%v/dev?sslmode=disable", redshiftPort)
+	db, err := sql.Open("postgres", connStr)
+	require.NoError(t, err)
+	defer db.Close()
+
+	var count int
+	err = db.QueryRow("SELECT COUNT(*) FROM test;").Scan(&count)
+	require.NoError(t, err)
+	assert.Equal(t, 10, count)
+}
+
+func waitForRedshiftAndGetAddress(localstackPort string, redshiftPort string, clusterID string) (redshiftEndpointAddress string, err error) {
+	cfg := aws.Config{
+		Region:      "us-east-1",
+		Credentials: credentials.NewStaticCredentialsProvider("test", "test", ""),
+		EndpointResolverWithOptions: aws.EndpointResolverWithOptionsFunc(
+			func(service, region string, options ...interface{}) (aws.Endpoint, error) {
+				return aws.Endpoint{URL: fmt.Sprintf("http://localhost:%v", localstackPort)}, nil
+			},
+		),
+	}
+
+	client := redshift.NewFromConfig(cfg)
+	maxAttempts := 120
+	for range maxAttempts {
+		time.Sleep(time.Second)
+		resp, err := client.DescribeClusters(context.Background(), &redshift.DescribeClustersInput{
+			ClusterIdentifier: &clusterID,
+		})
+		if err != nil {
+			continue
+		}
+		if len(resp.Clusters) == 0 {
+			continue
+		}
+		if *resp.Clusters[0].ClusterStatus == "available" {
+			time.Sleep(time.Second * 5) // redshift reports available somewhat prematurely
+			for _, cluster := range resp.Clusters {
+				if *cluster.ClusterIdentifier == "my-redshift-cluster" {
+					redshiftEndpointAddress = fmt.Sprintf("%v:%v", *cluster.Endpoint.Address, redshiftPort)
+				}
+			}
+			return redshiftEndpointAddress, nil
+		}
+	}
+	return "", errors.New("polling localstack for ready Redshift failed")
+}
+
+func TestIntegrationRdsIamAuth(t *testing.T) {
path: internal/impl/sql/integration_test.go
user: login: jem-davies
body: Currently failing
created_at: 2025-05-10T16:10:53Z
author_association: COLLABORATOR
diff_hunk: @@ -1256,3 +1264,259 @@ args_mapping: 'root = [ this.foo ]'
 	require.NoError(t, err)
 	assert.JSONEq(t, `[{"foo": "blobfish", "bar": "ARE REALLY COOL", "baz": 41}]`, string(actBytes))
 }
+
+func TestIntegrationRedshiftSecret(t *testing.T) {
+	pool, err := dockertest.NewPool("")
+	if err != nil {
+		t.Skipf("Could not connect to docker: %s", err)
+	}
+	pool.MaxWait = 3 * time.Minute
+
+	// we need a localstack pro image
+	if os.Getenv("LOCALSTACK_AUTH_TOKEN") == "" {
+		t.Skip("LOCALSTACK_AUTH_TOKEN is not set")
+	}
+
+	tfPath, err := filepath.Abs("./resources/redshiftsecret.tf")
+	require.NoError(t, err)
+
+	resource, err := pool.RunWithOptions(&dockertest.RunOptions{
+		Repository: "localstack/localstack-pro",
+		Env: []string{
+			"LOCALSTACK_AUTH_TOKEN=" + os.Getenv("LOCALSTACK_AUTH_TOKEN"),
+			"EXTENSION_AUTO_INSTALL=localstack-extension-terraform-init",
+		},
+		Mounts: []string{
+			tfPath + ":/etc/localstack/init/ready.d/main.tf",
+			"/var/run/docker.sock:/var/run/docker.sock",
+		},
+		ExposedPorts: []string{"4566/tcp", "4510-4559/tcp"},
+	})
+	require.NoError(t, err)
+
+	t.Cleanup(func() {
+		if err = pool.Purge(resource); err != nil {
+			t.Logf("Failed to clean up docker resource: %s", err)
+		}
+	})
+
+	_ = resource.Expire(900)
+
+	// grab some ports and wait for the tf to boot up redshift etc.
+	localstackPort := resource.GetPort("4566/tcp")
+	redshiftPort := resource.GetPort("4510/tcp")
+
+	redshiftEndpointAddress, err := waitForRedshiftAndGetAddress(localstackPort, redshiftPort, "my-redshift-cluster")
+	require.NoError(t, err)
+
+	// run a config that will fetch the secrets and create 10 rows in the redshift db
+	builder := service.NewStreamBuilder()
+
+	err = builder.SetYAML(fmt.Sprintf(`input:
+  generate:
+    mapping: |
+        root = {"age":random_int(min: 0, max: 100), "name":fake("name")}
+    interval: 1ms
+    count: 10
+
+output:
+  sql_insert:
+    driver: postgres 
+    dsn: postgresql://username_from_secret:password_from_secret@%v/dev?sslmode=disable
+    table: test
+    columns: [age, name]
+    args_mapping: |
+      root = [
+        this.age,
+        this.name,
+      ]
+    init_statement: "CREATE TABLE test (name varchar(255), age int);"
+    secret_name: "redshift-password"
+    region: us-east-1
+    endpoint: http://localhost:%v
+    credentials:
+      id: test
+      secret: test`, redshiftEndpointAddress, localstackPort))
+
+	stream, err := builder.Build()
+	require.NoError(t, err)
+
+	err = stream.Run(context.Background())
+	require.NoError(t, err)
+
+	// check there is 10 rows in the db
+	connStr := fmt.Sprintf("postgres://admin:Password123@localhost:%v/dev?sslmode=disable", redshiftPort)
+	db, err := sql.Open("postgres", connStr)
+	require.NoError(t, err)
+	defer db.Close()
+
+	var count int
+	err = db.QueryRow("SELECT COUNT(*) FROM test;").Scan(&count)
+	require.NoError(t, err)
+	assert.Equal(t, 10, count)
+}
+
+func waitForRedshiftAndGetAddress(localstackPort string, redshiftPort string, clusterID string) (redshiftEndpointAddress string, err error) {
+	cfg := aws.Config{
+		Region:      "us-east-1",
+		Credentials: credentials.NewStaticCredentialsProvider("test", "test", ""),
+		EndpointResolverWithOptions: aws.EndpointResolverWithOptionsFunc(
+			func(service, region string, options ...interface{}) (aws.Endpoint, error) {
+				return aws.Endpoint{URL: fmt.Sprintf("http://localhost:%v", localstackPort)}, nil
+			},
+		),
+	}
+
+	client := redshift.NewFromConfig(cfg)
+	maxAttempts := 120
+	for range maxAttempts {
+		time.Sleep(time.Second)
+		resp, err := client.DescribeClusters(context.Background(), &redshift.DescribeClustersInput{
+			ClusterIdentifier: &clusterID,
+		})
+		if err != nil {
+			continue
+		}
+		if len(resp.Clusters) == 0 {
+			continue
+		}
+		if *resp.Clusters[0].ClusterStatus == "available" {
+			time.Sleep(time.Second * 5) // redshift reports available somewhat prematurely
+			for _, cluster := range resp.Clusters {
+				if *cluster.ClusterIdentifier == "my-redshift-cluster" {
+					redshiftEndpointAddress = fmt.Sprintf("%v:%v", *cluster.Endpoint.Address, redshiftPort)
+				}
+			}
+			return redshiftEndpointAddress, nil
+		}
+	}
+	return "", errors.New("polling localstack for ready Redshift failed")
+}
+
+func TestIntegrationRdsIamAuth(t *testing.T) {
path: internal/impl/sql/integration_test.go
user: login: jem-davies
body: Tested on AWS manually: 

Ran the ./rdsIamAuth.tf - against actual aws - created a iam user with policy: 

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "*"
            ],
            "Resource": [
                "arn:aws:rds-db:us-east-1:000000000000:dbuser:db-XXXXXXXXXXXXXXXXXXXXXXXXXX/db_userx"
            ]
        }
    ]
}
```

Created a new user via psql: 

```
CREATE USER db_userx;
GRANT rds_iam TO db_userx;
GRANT ALL ON ALL TABLES IN SCHEMA public TO db_userx;
GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO db_userx;
GRANT ALL ON ALL FUNCTIONS IN SCHEMA public TO db_userx;
GRANT USAGE ON SCHEMA public TO db_userx;
GRANT CREATE ON SCHEMA public TO db_userx;
ALTER SCHEMA public OWNER TO db_userx;
```

Ran the following Bento config from source: 

```yaml
input:
  generate:
    mapping: |
        root = {"age":random_int(min: 0, max: 100), "name":fake("name")}
    interval: 1ms
    count: 10
  
output:
  sql_insert:
    driver: postgres 
    dsn: postgresql://db_userx:password@my-rds-instance.xxxxxxxxxxxx.us-east-1.rds.amazonaws.com:5432/testdb
    table: test
    columns: [age, name]
    args_mapping: |
      root = [
        this.age,
        this.name,
      ]
    iam_enabled: true
    init_statement: "CREATE TABLE test (name varchar(255), age int);"
   ```

```
go run main.go -c ./tmp/iam_sql_connect/config.yaml
INFO Running main config from specified file       @service=bento bento_version="" path=./tmp/iam_sql_connect/config.yaml
INFO Listening for HTTP requests at: http://0.0.0.0:4195/  @service=bento
INFO Input type generate is now active             @service=bento label="" path=root.input
INFO Launching a Bento instance, use CTRL+C to close  @service=bento
INFO Updated dsn with info from AWS                @service=bento label="" path=root.output
WARN Failed to execute init_statement: pq: relation "test" already exists  @service=bento label="" path=root.output
INFO Output type sql_insert is now active          @service=bento label="" path=root.output
INFO Pipeline has terminated. Shutting down the service  @service=bento
```

```
select * from test;

          name           | age 
-------------------------+-----
 Lord Leif Cummings      |  59
 Lord Andres Hills       |  28
 Mr. Tremayne Heaney     |   9
 Mr. Junius Turcotte     |  23
 Prof. Cielo Botsford    |  47
 King Geovanny Armstrong |  15
 King Art Johnson        |  55
 Prof. Deonte Mayer      |  63
 Mr. Hilario Glover      |  46
 Dr. Caesar Pagac        |  32
```

created_at: 2025-05-10T18:58:16Z
author_association: COLLABORATOR
diff_hunk: @@ -109,8 +105,15 @@ CREATE TABLE IF NOT EXISTS some_table (
 			Optional().
 			Advanced(),
 		service.NewStringField("secret_name").
-			Description("An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to postgres.").
+			Description("An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to `postgres`.").
 			Optional().
+			Version("1.3.0").
+			Advanced(),
+		service.NewBoolField("iam_enabled").
+			Description("An optional field used to generate an IAM authentication token to connect to an Amazon Relational Database (RDS) DB instance. This will overwrite the Password in the DSN with the generated token only if the drivers are `mysql` or `postgres`.").
+			Optional().
+			Default(false).
+			Version("1.8.0").
path: internal/impl/sql/conn_fields.go
user: login: gregfurman
body: If we specify `secret_name` but not `iam_enabled`, will this break anyones existing config?
created_at: 2025-05-10T21:13:03Z
author_association: COLLABORATOR
diff_hunk: @@ -109,8 +105,15 @@ CREATE TABLE IF NOT EXISTS some_table (
 			Optional().
 			Advanced(),
 		service.NewStringField("secret_name").
-			Description("An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to postgres.").
+			Description("An optional field that can be used to get the Username + Password from AWS Secrets Manager. This will overwrite the Username + Password in the DSN with the values from the Secret only if the driver is set to `postgres`.").
 			Optional().
+			Version("1.3.0").
+			Advanced(),
+		service.NewBoolField("iam_enabled").
+			Description("An optional field used to generate an IAM authentication token to connect to an Amazon Relational Database (RDS) DB instance. This will overwrite the Password in the DSN with the generated token only if the drivers are `mysql` or `postgres`.").
+			Optional().
+			Default(false).
+			Version("1.8.0").
path: internal/impl/sql/conn_fields.go
user: login: jem-davies
body: You would have `secret_name` set to something other than `""` - and you would leave `iam_enabled` default false - should be ok with existing configs - the integration test for the secret replacement has a config like:

```yaml
output:
  sql_insert:
    driver: postgres 
    dsn: postgresql://username_from_secret:password_from_secret@%v/dev?sslmode=disable
    table: test
    columns: [age, name]
    args_mapping: |
      root = [
        this.age,
        this.name,
      ]
    init_statement: "CREATE TABLE test (name varchar(255), age int);"
    secret_name: "redshift-password"
    region: us-east-1
    endpoint: http://localhost:%v
    credentials:
      id: test
      secret: test
```
created_at: 2025-05-10T21:16:58Z
author_association: COLLABORATOR

PR comments:
user: login: jem-davies
created_at: 2025-05-07T11:45:28Z
body: @gregfurman we happy to merge this then now? 
author_association: COLLABORATOR
user: login: jem-davies
created_at: 2025-05-07T11:46:10Z
body: > let's block the merge button until we have communicated the breaking changes

We have updated the config spec such that it won't require updates to existing user configs 
author_association: COLLABORATOR
user: login: jem-davies
created_at: 2025-05-10T21:20:26Z
body: > Built bento on my local w/out the AWS package imported, which should work with these changes.
> 
> Once you confirm this works for you, happy to merge this bad boy! (I can't approve since I opened)

yeah good shout ill do another test tomorrow with a custom build to double check too 
author_association: COLLABORATOR