aws-sdk-go: Connect to RDS using rdsutils.BuildAuthToken not working

Please fill out the sections below to help us address your issue.

Version of AWS SDK for Go?

v1.8.19-6-g7b500fb

Version of Go (go version)?

go 1.8

What issue did you see?

following the doc but unable to make successful DB connection using IAM role from EC2 Also, this is particularly hard to debug because of not actually able to run code as EC2 on dev to test

Steps to reproduce

I follow the below step in https://docs.aws.amazon.com/sdk-for-go/api/service/rds/rdsutils/#BuildAuthToken and use the exact same code snippet but I believe there’s some issue with the instruction:

authToken, err := BuildAuthToken(dbEndpoint, awsRegion, dbUser, awsCreds)

// Create the MySQL DNS string for the DB connection
// user:password@protocol(endpoint)/dbname?<params>
dnsStr = fmt.Sprintf("%s:%s@tcp(%s)/%s?tls=true",
   dbUser, authToken, dbEndpoint, dbName,
)

// Use db to perform SQL operations on database
db, err := sql.Open("mysql", dnsStr)

I have tried the above instruction but it was throwing signing errors

below is my latest code snippet, I have tried various different fortmat and this is the one i last end up with:

// c.Hostname = host.xxxx.us-east-1.rds.amazonaws.com
// c.Port = 3306

hostlocation := fmt.Sprintf("https://%v:%v", c.Hostname, c.Port)
token, stdErr := rdsutils.BuildAuthToken(hostlocation, "us-east-1", "appuser", stscreds.NewCredentials(sess, "arn:aws:iam::[AWS ID]:role/SomeRole"))

dnsStr := fmt.Sprintf("[appuser:%s@tcp(%s)]:%v/%s?tls=true", token, c.Hostname, c.Port, "dbname")

// Connect to database
db, stdErr := sql.Open("mysql", dnsStr)

for this one i got: StdError: invalid DSN: did you forget to escape a param value?

the token returned is actually in the following format:

host.xxxx.us-east-1.rds.amazonaws.com:3306?Action=connect&DBUser=appuser&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIAJHJ4MX7HB6STZ5QQ%2F20170504%2Fus-east-1%2Frds-db%2Faws4_request&X-Amz-Date=20170504T164655Z&X-Amz-Expires=900&X-Amz-Security-Token=FQoDYXdzENr%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDB3PYlunbHE2bh4ylCLWAevRs7cztGGATW3iJm0tpL1J2G%2FsqJjilAlhI2uj6VW%2BWH0txpt2bZ7DgQeZ0lutoJj3rffcznu7o0VIG%2F7L8MXC11BjXOIOEXFwx%2BhEIAqM%2F3v9vpa9Jp1L2xqPBs%2FLuOYmHFxufykYE3D9%2BdoPRp3srEj3AqGbv9Nanw6zRXbsRkAj96VzsAnFTzyTAyOknBUDkWpBzjR%2Fo1Gqdd9gwu6HdJRcp6H%2B9oI0FLrDuQqUfSZez5BUspe4EYDWctSEuNoNREQzzUkkoU2yXB69m4KxA4TyIy4ogLatyAU%3D&X-Amz-SignedHeaders=host&X-Amz-Signature=2b249517f6dc4ad145232cdab3ea59e9b8b20dedad6666b07bbe686e33b6859e

I am not sure if this is the right string that should replace the DSN/DNS password field as indicated in the doc

Can anyone please help me figure out what the actual and correct DSN or DNS will look like? some example will be very helpful as I can just follow the same structure

Thanks

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 24 (20 by maintainers)

Most upvoted comments

Hi, I believe I’ve managed to solve this - it does appear to me that the docs are missing a couple of things.

  1. When passing in endpoint, you must include the port
  2. When creating the db connection, you must register the RDS x509 cert & pass in a specific TLS config

1:

passwd, err := rdsutils.BuildAuthToken(fmt.Sprintf("%s:%d", host, 3306), region, cfg.User, creds)

2:

func RegisterRDSMysqlCerts(c *http.Client) error {
	resp, err := c.Get("https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem")
	if err != nil {
		return errs.Wrap(err)
	}

	defer fileutil.CloseLoggingAnyError(resp.Body)
	pem, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return errs.Wrap(err)
	}

	rootCertPool := x509.NewCertPool()
	if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
		return errs.New("couldn't append certs from pem")
	}

	err = mysql.RegisterTLSConfig("rds", &tls.Config{RootCAs: rootCertPool, InsecureSkipVerify: true})
	if err != nil {
		return errs.Wrap(err)
	}
	return nil
}

A full working example (you’ll need to fill in the appropriate information for your user/credentials/etc):

package main

import (
	"crypto/tls"
	"crypto/x509"
	"database/sql"
	"fmt"
	"io/ioutil"
	"net/http"

	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/service/rds/rdsutils"
	"github.com/go-sql-driver/mysql"
	"github.com/richardwilkes/errs"
	"jaxf-github.fanatics.corp/forge/furnace/fileutil"
)

func RegisterRDSMysqlCerts(c *http.Client) error {
	resp, err := c.Get("https://s3.amazonaws.com/rds-downloads/rds-combined-ca-bundle.pem")
	if err != nil {
		return errs.Wrap(err)
	}

	defer fileutil.CloseLoggingAnyError(resp.Body)
	pem, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return errs.Wrap(err)
	}

	rootCertPool := x509.NewCertPool()
	if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
		return errs.New("couldn't append certs from pem")
	}

	err = mysql.RegisterTLSConfig("rds", &tls.Config{RootCAs: rootCertPool, InsecureSkipVerify: true})
	if err != nil {
		return errs.Wrap(err)
	}
	return nil
}

func main() {
	// FILL THESE OUT:
	host := ""
	user := ""
	dbName := ""
	creds := credentials.NewSharedCredentials("", "")

	region := "us-east-1"

	host = fmt.Sprintf("%s:%d", host, 3306)
	cfg := &mysql.Config{
		User: user,
		Addr: host,
		Net:  "tcp",
		Params: map[string]string{
			"tls": "rds",
		},
		DBName: dbName,
	}
	cfg.AllowCleartextPasswords = true

	var err error
	cfg.Passwd, err = rdsutils.BuildAuthToken(host, region, cfg.User, creds)

	if err != nil {
		panic(err)
	}

	err = RegisterRDSMysqlCerts(http.DefaultClient)
	if err != nil {
		panic(err)
	}

	db, err := sql.Open("mysql", cfg.FormatDSN())
	if err != nil {
		panic(err)
	}

	err = db.Ping()
	if err != nil {
		panic(err)
	}
	fmt.Println("ok")
}

One additional tidbit - if you fail to provide a port in the connection string, you’ll encounter this: https://github.com/go-sql-driver/mysql/issues/717