Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion apis/cluster/mysql/v1alpha1/provider_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,17 @@ const (
// should acquire credentials from a connection secret written by a managed
// resource that represents a MySQL server.
CredentialsSourceMySQLConnectionSecret xpv1.CredentialsSource = "MySQLConnectionSecret"

// CredentialsSourceAWSIAMAuth indicates that the provider should
// authenticate to an AWS RDS/Aurora instance using an IAM authentication
// token generated at connection time, instead of a static password.
CredentialsSourceAWSIAMAuth xpv1.CredentialsSource = "AWSIAMAuth"
)

// ProviderCredentials required to authenticate.
type ProviderCredentials struct {
// Source of the provider credentials.
// +kubebuilder:validation:Enum=MySQLConnectionSecret
// +kubebuilder:validation:Enum=MySQLConnectionSecret;AWSIAMAuth
Source xpv1.CredentialsSource `json:"source"`

// A CredentialsSecretRef is a reference to a MySQL connection secret
Expand All @@ -77,6 +82,12 @@ type ProviderCredentials struct {
// standard Crossplane keys are used: "endpoint", "port", "username", "password".
// +optional
SecretKeyMapping *SecretKeyMapping `json:"secretKeyMapping,omitempty"`

// Region is the AWS region used to generate the IAM authentication token
// when source is AWSIAMAuth. When unset it falls back to a "region" key in
// the connection secret, then to the controller's AWS environment.
// +optional
Region *string `json:"region,omitempty"`
}

// SecretKeyMapping allows overriding the default secret key names used to
Expand Down
5 changes: 5 additions & 0 deletions apis/cluster/mysql/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 12 additions & 1 deletion apis/cluster/postgresql/v1alpha1/provider_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,17 @@ const (
// should acquire credentials from a connection secret written by a managed
// resource that represents a PostgreSQL server.
CredentialsSourcePostgreSQLConnectionSecret xpv1.CredentialsSource = "PostgreSQLConnectionSecret"

// CredentialsSourceAWSIAMAuth indicates that the provider should
// authenticate to an AWS RDS/Aurora instance using an IAM authentication
// token generated at connection time, instead of a static password.
CredentialsSourceAWSIAMAuth xpv1.CredentialsSource = "AWSIAMAuth"
)

// ProviderCredentials required to authenticate.
type ProviderCredentials struct {
// Source of the provider credentials.
// +kubebuilder:validation:Enum=PostgreSQLConnectionSecret
// +kubebuilder:validation:Enum=PostgreSQLConnectionSecret;AWSIAMAuth
Source xpv1.CredentialsSource `json:"source"`

// A CredentialsSecretRef is a reference to a PostgreSQL connection secret
Expand All @@ -61,6 +66,12 @@ type ProviderCredentials struct {
// standard Crossplane keys are used: "endpoint", "port", "username", "password".
// +optional
SecretKeyMapping *SecretKeyMapping `json:"secretKeyMapping,omitempty"`

// Region is the AWS region used to generate the IAM authentication token
// when source is AWSIAMAuth. When unset it falls back to a "region" key in
// the connection secret, then to the controller's AWS environment.
// +optional
Region *string `json:"region,omitempty"`
}

// SecretKeyMapping allows overriding the default secret key names used to
Expand Down
5 changes: 5 additions & 0 deletions apis/cluster/postgresql/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion apis/namespaced/mysql/v1alpha1/cluster_provider_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ type ClusterProviderConfigSpec struct {
// ClusterProviderCredentials required to authenticate.
type ClusterProviderCredentials struct {
// Source of the provider credentials.
// +kubebuilder:validation:Enum=MySQLConnectionSecret
// +kubebuilder:validation:Enum=MySQLConnectionSecret;AWSIAMAuth
Source MySQLConnectionSecretSource `json:"source"`

// A CredentialsSecretRef is a reference to a MySQL connection secret
Expand All @@ -58,6 +58,12 @@ type ClusterProviderCredentials struct {
// standard Crossplane keys are used: "endpoint", "port", "username", "password".
// +optional
SecretKeyMapping *SecretKeyMapping `json:"secretKeyMapping,omitempty"`

// Region is the AWS region used to generate the IAM authentication token
// when source is AWSIAMAuth. When unset it falls back to a "region" key in
// the connection secret, then to the controller's AWS environment.
// +optional
Region *string `json:"region,omitempty"`
}

// A ClusterProviderConfigStatus reflects the observed state of a ClusterProviderConfig.
Expand Down
13 changes: 12 additions & 1 deletion apis/namespaced/mysql/v1alpha1/provider_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,17 @@ const (
// should acquire credentials from a connection secret written by a managed
// resource that represents a MySQL server.
CredentialsSourceMySQLConnectionSecret MySQLConnectionSecretSource = "MySQLConnectionSecret"

// CredentialsSourceAWSIAMAuth indicates that the provider should
// authenticate to an AWS RDS/Aurora instance using an IAM authentication
// token generated at connection time, instead of a static password.
CredentialsSourceAWSIAMAuth MySQLConnectionSecretSource = "AWSIAMAuth"
)

// ProviderCredentials required to authenticate.
type ProviderCredentials struct {
// Source of the provider credentials.
// +kubebuilder:validation:Enum=MySQLConnectionSecret
// +kubebuilder:validation:Enum=MySQLConnectionSecret;AWSIAMAuth
Source MySQLConnectionSecretSource `json:"source"`

// A CredentialsSecretRef is a reference to a MySQL connection secret
Expand All @@ -80,6 +85,12 @@ type ProviderCredentials struct {
// standard Crossplane keys are used: "endpoint", "port", "username", "password".
// +optional
SecretKeyMapping *SecretKeyMapping `json:"secretKeyMapping,omitempty"`

// Region is the AWS region used to generate the IAM authentication token
// when source is AWSIAMAuth. When unset it falls back to a "region" key in
// the connection secret, then to the controller's AWS environment.
// +optional
Region *string `json:"region,omitempty"`
}

// SecretKeyMapping allows overriding the default secret key names used to
Expand Down
10 changes: 10 additions & 0 deletions apis/namespaced/mysql/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type ClusterProviderConfigSpec struct {
// ClusterProviderCredentials required to authenticate.
type ClusterProviderCredentials struct {
// Source of the provider credentials.
// +kubebuilder:validation:Enum=PostgreSQLConnectionSecret
// +kubebuilder:validation:Enum=PostgreSQLConnectionSecret;AWSIAMAuth
Source PostgreSQLConnectionSource `json:"source"`

// A CredentialsSecretRef is a reference to a PostgreSQL connection secret
Expand All @@ -54,6 +54,12 @@ type ClusterProviderCredentials struct {
// standard Crossplane keys are used: "endpoint", "port", "username", "password".
// +optional
SecretKeyMapping *SecretKeyMapping `json:"secretKeyMapping,omitempty"`

// Region is the AWS region used to generate the IAM authentication token
// when source is AWSIAMAuth. When unset it falls back to a "region" key in
// the connection secret, then to the controller's AWS environment.
// +optional
Region *string `json:"region,omitempty"`
}

// A ClusterProviderConfigStatus reflects the observed state of a ClusterProviderConfig.
Expand Down
13 changes: 12 additions & 1 deletion apis/namespaced/postgresql/v1alpha1/provider_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,17 @@ const (
// should acquire credentials from a connection secret written by a managed
// resource that represents a PostgreSQL server.
CredentialsSourcePostgreSQLConnectionSecret PostgreSQLConnectionSource = "PostgreSQLConnectionSecret"

// CredentialsSourceAWSIAMAuth indicates that the provider should
// authenticate to an AWS RDS/Aurora instance using an IAM authentication
// token generated at connection time, instead of a static password.
CredentialsSourceAWSIAMAuth PostgreSQLConnectionSource = "AWSIAMAuth"
)

// ProviderCredentials required to authenticate.
type ProviderCredentials struct {
// Source of the provider credentials.
// +kubebuilder:validation:Enum=PostgreSQLConnectionSecret
// +kubebuilder:validation:Enum=PostgreSQLConnectionSecret;AWSIAMAuth
Source PostgreSQLConnectionSource `json:"source"`

// A CredentialsSecretRef is a reference to a PostgreSQL connection secret
Expand All @@ -64,6 +69,12 @@ type ProviderCredentials struct {
// standard Crossplane keys are used: "endpoint", "port", "username", "password".
// +optional
SecretKeyMapping *SecretKeyMapping `json:"secretKeyMapping,omitempty"`

// Region is the AWS region used to generate the IAM authentication token
// when source is AWSIAMAuth. When unset it falls back to a "region" key in
// the connection secret, then to the controller's AWS environment.
// +optional
Region *string `json:"region,omitempty"`
}

// SecretKeyMapping allows overriding the default secret key names used to
Expand Down
10 changes: 10 additions & 0 deletions apis/namespaced/postgresql/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

90 changes: 90 additions & 0 deletions docs/aws-iam-auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# AWS IAM database authentication

provider-sql can authenticate to Amazon Aurora / RDS (MySQL and PostgreSQL)
using [IAM database authentication](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html)
instead of a static password. The provider generates a short-lived IAM token at
connection time and uses it as the database password. Combined with EKS Pod
Identity or IRSA, this removes static database credentials entirely.

Select it with the `AWSIAMAuth` credentials source on a `ProviderConfig`:

```yaml
spec:
credentials:
source: AWSIAMAuth
connectionSecretRef:
name: aurora-conn # endpoint, port, username - no password
# region: us-east-1 # optional (see "Region resolution")
```

See the runnable examples: `examples/{cluster,namespaced}/{mysql,postgresql}/config_iam.yaml`.

## Prerequisites

1. **Enable IAM authentication** on the Aurora cluster / RDS instance.

2. **Create an IAM-enabled database user for the provider to connect as.** This
is a one-time bootstrap that needs a normal (password) connection, performed
out of band - the provider does not do it. Use a **dedicated provisioning
role, not the master user** (granting `rds_iam` to the Postgres master
permanently disables its password auth, and the MySQL plugin is fixed at
`CREATE USER` time):

- PostgreSQL: `CREATE USER crossplane_admin; GRANT rds_iam TO crossplane_admin;`
plus the privileges it needs to manage your databases/roles/grants.
- MySQL: `CREATE USER 'crossplane_admin'@'%' IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS';`
plus the required `GRANT`s.

(Creating these users is also possible declaratively through provider-sql's
own `Role`/`Grant` resources from a password-based `ProviderConfig`.)

3. **Allow the controller's IAM identity to connect** by attaching an
`rds-db:connect` policy for that database user, and give the provider pod that
identity via [EKS Pod Identity](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html)
or [IRSA](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html).
Credentials are discovered from the environment by the AWS SDK.

4. **Provide a connection secret** containing only `endpoint`, `port` and
`username` (no `password`).

## TLS and the Amazon RDS certificate authority

IAM authentication requires TLS - RDS rejects unencrypted connections. To
*verify* the server certificate, the client needs the Amazon RDS CA, which is
**not** in the default system trust store. provider-sql does **not** ship the RDS
CA bundle; making it available to the provider pod is an operator concern. Two
common approaches:

- **[trust-manager](https://cert-manager.io/docs/trust/trust-manager/)** - add the
[Amazon RDS CA bundle](https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem)
to the provider pod's trust store. Then set `tls: "true"` (MySQL) or
`sslMode: verify-full` (PostgreSQL) and the certificate verifies normally.
- **Mounted secret** - mount the RDS CA and reference it through the provider's
existing custom TLS mechanism (`tls: custom` + `tlsConfig` for MySQL;
`sslMode: verify-full` with the CA for PostgreSQL).

If you do neither, IAM auth still works but the connection is **encrypted
without certificate verification**:

| Engine | Verified (RDS CA available) | Default fallback |
|--------|-----------------------------|------------------|
| MySQL | `tls: "true"` or `tls: custom` | `skip-verify` (encrypted, unverified) |
| PostgreSQL | `sslMode: verify-full` | `sslMode: require` (encrypted, unverified) |

The provider always enforces at least an encrypted connection for IAM auth.

## Region resolution

The region used to sign the token is resolved in order:

1. `spec.credentials.region` on the `ProviderConfig`
2. a `region` key in the connection secret
3. the controller's AWS environment (`AWS_REGION` / instance metadata)

If none is set the token cannot be signed correctly, so configure one of them.

## Notes

- The provider opens a fresh connection each reconcile and generates a new token
each time, so the 15-minute token lifetime is never an issue.
- Setting `AWSIAMAuth` forces TLS on even if a weaker `tls`/`sslMode` was set.
30 changes: 30 additions & 0 deletions examples/cluster/mysql/config_iam.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
apiVersion: mysql.sql.crossplane.io/v1alpha1
kind: ProviderConfig
metadata:
name: aurora-iam
spec:
credentials:
source: AWSIAMAuth
connectionSecretRef:
namespace: crossplane-system
name: aurora-conn # endpoint, port, username - no password
# region: us-east-1 # optional; otherwise a "region" secret key, then the controller's AWS env
# tls "true" verifies the server certificate against the pod trust store. The
# Amazon RDS CA must be made available to the provider pod (see
# docs/aws-iam-auth.md), for example with trust-manager. Use tls: custom to
# point at a mounted RDS CA secret instead. If TLS is left unset, IAM auth
# falls back to an encrypted but unverified connection.
tls: "true"
---
# The connection secret holds only endpoint, port and username - no password.
# The provider generates a short-lived IAM token and uses it as the password.
# apiVersion: v1
# kind: Secret
# metadata:
# name: aurora-conn
# namespace: crossplane-system
# stringData:
# endpoint: my-cluster.cluster-xxxxxxxx.us-east-1.rds.amazonaws.com
# port: "3306"
# username: crossplane_admin
30 changes: 30 additions & 0 deletions examples/cluster/postgresql/config_iam.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
apiVersion: postgresql.sql.crossplane.io/v1alpha1
kind: ProviderConfig
metadata:
name: aurora-iam
spec:
# defaultDatabase: postgres
credentials:
source: AWSIAMAuth
connectionSecretRef:
namespace: crossplane-system
name: aurora-conn # endpoint, port, username - no password
# region: us-east-1 # optional; otherwise a "region" secret key, then the controller's AWS env
# IAM auth forces sslMode=require (the connection is encrypted but the server
# certificate is not verified). For full verification set sslMode: verify-full
# and make the Amazon RDS CA available to the provider pod (see
# docs/aws-iam-auth.md), for example with trust-manager.
sslMode: require
---
# The connection secret holds only endpoint, port and username - no password.
# The provider generates a short-lived IAM token and uses it as the password.
# apiVersion: v1
# kind: Secret
# metadata:
# name: aurora-conn
# namespace: crossplane-system
# stringData:
# endpoint: my-cluster.cluster-xxxxxxxx.us-east-1.rds.amazonaws.com
# port: "5432"
# username: crossplane_admin
Loading