-
Notifications
You must be signed in to change notification settings - Fork 486
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce support to IAM authentication in the datastore #4828
Conversation
Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
// Open is the overridden method for opening a connection, using | ||
// AWS IAM authentication | ||
func (w *sqlDriverWrapper) Open(name string) (driver.Conn, error) { | ||
ctx, cancel := context.WithTimeout(context.Background(), timeOut) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to not use a derived context here and instead using a context.Background
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main reason is that sql.Open
does not allow to provide a context, so we are limited by the arguments that are there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could have injected the context to the wrapper so it could be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you @marcofranssen for you feedback.
The wrapper driver instance is created at initialization time. The wrapper struct could still have a context that could be set externally, but we would need to have a map between that context and a future call to Open (probably with the string used in the Open call as the key). All that seemed to be error-prone and adding a complexity that we may not really need. I would rather explore alternatives if we see that having a separate context causes problems.
} | ||
password, err := token.getAWSAuthToken(ctx, config, w.tokenBuilder) | ||
if err != nil { | ||
return nil, fmt.Errorf("could not get authorization token: %w", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return nil, fmt.Errorf("could not get authorization token: %w", err) | |
return nil, fmt.Errorf("could not get authentication token: %w", err) |
doc/plugin_server_datastore_sql.md
Outdated
@@ -135,6 +135,60 @@ If you need to use custom Root CA, just specify `root_ca_path` in the plugin con | |||
} | |||
``` | |||
|
|||
### IAM Authentication | |||
|
|||
With IAM database authentication, an authentication token is used instead of a password when connecting to the database. For that reason, a password must not be provided in the connection string. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With IAM database authentication, an authentication token is used instead of a password when connecting to the database. For that reason, a password must not be provided in the connection string. | |
AWS Identity and Access Management (IAM) authentication allows for secure authentication to databases hosted on AWS. Unlike traditional methods, it uses an authentication token instead of a password. When using IAM authentication, it is required to exclude the password from the connection string. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the suggested change. Since IAM authentication is not tied to AWS IAM only (we will be supporting IAM authentication in other services from other cloud providers) the description should not be AWS-specific.
I'm taking this but without the AWS-specific parts. Thanks for the suggestion, it's a lot more clear! than the original text
doc/plugin_server_datastore_sql.md
Outdated
|
||
With IAM database authentication, an authentication token is used instead of a password when connecting to the database. For that reason, a password must not be provided in the connection string. | ||
|
||
The `database_type` configuration can have additional settings when a database type supporting IAM authentication is used, and always takes the following form: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The `database_type` configuration can have additional settings when a database type supporting IAM authentication is used, and always takes the following form: | |
The database_type configuration allows specifying the type of database with IAM support. The configuration always follows this structure: |
... | ||
} | ||
``` | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: Replace `dbtype-with-iam-support` with the specific database type that supports IAM authentication. |
return nil, "", false, err | ||
} | ||
if c.Password != "" { | ||
return nil, "", false, errors.New("invalid postgres config: no password should be provided when using IAM authentication") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return nil, "", false, errors.New("invalid postgres config: no password should be provided when using IAM authentication") | |
return nil, "", false, errors.New("invalid postgres configuration: password should not be set when using IAM authentication.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion. Note that error strings should not end with punctuation, so I'll not end it with ".".
} | ||
|
||
type tokenGetter interface { | ||
getAWSAuthToken(ctx context.Context, params *Config, tokenBuilder authTokenBuilder) (string, error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rename to getAuthToken
to keep it consistent with the naming in the authTokenBuilder
interface.
MySQLDriverName = "aws-rds-mysql" | ||
PostgresDriverName = "aws-rds-postgres" | ||
iso8601BasicFormat = "20060102T150405Z" | ||
timeOut = time.Second * 30 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
timeOut = time.Second * 30 | |
connectionTimeout = time.Second * 30 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This timeout is not a connection timeout to the database, but a timeout building the authentication token. Maybe buildAuthTokenTimeout
is a better name.
const ( | ||
MySQLDriverName = "aws-rds-mysql" | ||
PostgresDriverName = "aws-rds-postgres" | ||
iso8601BasicFormat = "20060102T150405Z" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd move this constant to the other file where it's used.
return "", fmt.Errorf("could not parse connection string: %w", err) | ||
} | ||
if cfg.Password != "" { | ||
return "", errors.New("password was provided in the connection string") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return "", errors.New("password was provided in the connection string") | |
return "", errors.New("unexpected password in connection string for IAM authentication") |
Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
doc/plugin_server_datastore_sql.md
Outdated
```hcl | ||
DataStore "sql" { | ||
plugin_data { | ||
database_type "aws_postgres" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: indentation is off
|
||
func (a *authToken) isExpired() bool { | ||
clockSkew := time.Minute // Make sure that the authentication token is valid for one more minute. | ||
return nowFunc().Add(clockSkew).Sub(a.expiresAt) >= 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we be subtracting a minute from the current time if we want the token to live a little longer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, we should be subtracting the clock skew, not adding.
|
||
// FormatDSN returns a DSN string based on the configuration. | ||
func (c *Config) FormatDSN() (string, error) { | ||
dsn, err := json.Marshal(c) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this seems ok since it's wholly contained by the driver implementation. I wonder if encoded url.Values might be a little more natural choice...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would keep it as is unless you have a strong opinion to change to url.Values.
type sqlDriverWrapper struct { | ||
sqlDriver driver.Driver | ||
tokenBuilder authTokenBuilder | ||
tokensMap tokens |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can Open() be called concurrently? Do we need to protect this with a mutex?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went back and forth on this when I originally thought about this. I now think that to be in the safe side we should have a mutex.
if cfg.Password != "" { | ||
return "", errors.New("unexpected password in connection string for IAM authentication") | ||
} | ||
return fmt.Sprintf("%s password=%s", connString, password), nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the password value be quoted? (e.g. %q
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although we don't really expect spaces in an AWS auth token, it's a good point to consider.
Using %q wouldn't work here, as the value should be surrounded with single quotes, not double quotes.
To have an even more robust implementation, I'm thinking that I should escape the special characters '
and \
from this value.
I'll make the changes to enclose the value in single quotes and escape single quotes and backslashes. Thank you for bringing this up!
return cfg.FormatDSN(), nil | ||
} | ||
|
||
func init() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: init() functions should be moved to the top where we typically have other things that are initialized at package init time
return nil, d.err | ||
} | ||
|
||
func init() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
init should ideally go towards the top of the file
Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM; one non-blocking comment.
@@ -85,6 +94,9 @@ func (w *sqlDriverWrapper) Open(name string) (driver.Conn, error) { | |||
return nil, fmt.Errorf("could not unmarshal configuration: %w", err) | |||
} | |||
|
|||
w.tokensMapMtx.Lock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if sqlDriver.Open can block for some time, but if so, maybe we can shorten the scope of this lock so it doesn't block other attempts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a good idea to shorten the scope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me! 👍
Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @amartinezfayo !
Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
* Introduce support to IAM authentication in the datastore Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
This PR introduces support to authenticate to the datastore using IAM database authentication for PostgreSQL and MySQL databases hosted in AWS RDS.
This is done by supporting the new
aws_postgres
andaws_mysql
database types in the datastore configuration.Fixes #2283.