Skip to content

Commit 4dffcbb

Browse files
committed
Replace RDS password-based authentication with IAM role authentication
Updated Python Django app to use IAM role auth instead of base64 decoded password Updated Java Spring Boot app to use IAM role auth with useAWSIam=true parameter Updated Node.js app to use IAM role auth with SSL configuration Removed RDS_MYSQL_CLUSTER_PASSWORD environment variables from all Terraform configurations Removed password-related variables from Terraform variable files All applications now use IAM role authentication for RDS connections
1 parent a7bc14c commit 4dffcbb

File tree

17 files changed

+4761
-917
lines changed

17 files changed

+4761
-917
lines changed

.github/workflows/java-eks-test.yml

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -213,11 +213,6 @@ jobs:
213213
RDS_MYSQL_CLUSTER_SECRETS, ${{env.RDS_MYSQL_CLUSTER_CREDENTIAL_SECRET_NAME}}
214214
parse-json-secrets: true
215215

216-
- name: Convert RDS database credentials to base64
217-
continue-on-error: true
218-
run: |
219-
echo "RDS_MYSQL_CLUSTER_SECRETS_PASSWORD_BASE64=$(echo -n '${{env.RDS_MYSQL_CLUSTER_SECRETS_PASSWORD}}' | base64)" >> $GITHUB_ENV
220-
221216
- name: Initiate Terraform
222217
uses: ./.github/workflows/actions/execute_and_retry
223218
with:
@@ -254,11 +249,11 @@ jobs:
254249
-var="service_account_aws_access=service-account-${{ env.TESTING_ID }}" \
255250
-var="sample_app_image=${{ env.MAIN_SAMPLE_APP_IMAGE_ARN }}" \
256251
-var="sample_remote_app_image=${{ env.REMOTE_SAMPLE_APP_IMAGE_ARN }}" \
252+
-var="rds_mysql_cluster_database=information_schema" \
257253
-var="rds_mysql_cluster_endpoint=${{env.RDS_MYSQL_CLUSTER_ENDPOINT}}" \
258254
-var="rds_mysql_cluster_username=${{env.RDS_MYSQL_CLUSTER_SECRETS_USERNAME}}" \
259-
-var='rds_mysql_cluster_password=${{env.RDS_MYSQL_CLUSTER_SECRETS_PASSWORD_BASE64}}' \
260255
-var='account_id=${{ env.ACCOUNT_ID }}' \
261-
|| deployment_failed=$?
256+
|| deployment_failed=$?
262257
263258
if [ $deployment_failed -ne 0 ]; then
264259
echo "Terraform deployment was unsuccessful. Will attempt to retry deployment."

.github/workflows/node-eks-test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,11 +243,11 @@ jobs:
243243
-var="service_account_aws_access=service-account-${{ env.TESTING_ID }}" \
244244
-var="sample_app_image=${{ env.MAIN_SAMPLE_APP_IMAGE_ARN }}" \
245245
-var="sample_remote_app_image=${{ env.REMOTE_SAMPLE_APP_IMAGE_ARN }}" \
246+
-var="rds_mysql_cluster_database=information_schema" \
246247
-var="rds_mysql_cluster_endpoint=${{env.RDS_MYSQL_CLUSTER_ENDPOINT}}" \
247248
-var="rds_mysql_cluster_username=${{env.RDS_MYSQL_CLUSTER_SECRETS_USERNAME}}" \
248-
-var='rds_mysql_cluster_password=${{env.RDS_MYSQL_CLUSTER_SECRETS_PASSWORD}}' \
249249
-var='account_id=${{ env.ACCOUNT_ID }}' \
250-
|| deployment_failed=$?
250+
|| deployment_failed=$?
251251
252252
if [ $deployment_failed -ne 0 ]; then
253253
echo "Terraform deployment was unsuccessful. Will attempt to retry deployment."

.github/workflows/python-eks-test.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -214,11 +214,6 @@ jobs:
214214
RDS_MYSQL_CLUSTER_SECRETS, ${{env.RDS_MYSQL_CLUSTER_CREDENTIAL_SECRET_NAME}}
215215
parse-json-secrets: true
216216

217-
- name: Convert RDS database credentials to base64
218-
continue-on-error: true
219-
run: |
220-
echo "RDS_MYSQL_CLUSTER_SECRETS_PASSWORD_BASE64=$(echo -n '${{env.RDS_MYSQL_CLUSTER_SECRETS_PASSWORD}}' | base64)" >> $GITHUB_ENV
221-
222217
- name: Initiate Terraform
223218
uses: ./.github/workflows/actions/execute_and_retry
224219
with:
@@ -257,7 +252,6 @@ jobs:
257252
-var='python_remote_app_image=${{ env.REMOTE_SAMPLE_APP_IMAGE_ARN }}' \
258253
-var='rds_mysql_cluster_endpoint=${{env.RDS_MYSQL_CLUSTER_ENDPOINT}}' \
259254
-var='rds_mysql_cluster_username=${{env.RDS_MYSQL_CLUSTER_SECRETS_USERNAME}}' \
260-
-var='rds_mysql_cluster_password=${{env.RDS_MYSQL_CLUSTER_SECRETS_PASSWORD_BASE64}}' \
261255
-var='rds_mysql_cluster_database=information_schema' \
262256
-var='account_id=${{ env.ACCOUNT_ID }}' \
263257
|| deployment_failed=$?

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,7 @@ build
1010
node_modules/
1111

1212
# Ignore Bin files from validator folder
13-
validator/bin
13+
validator/bin
14+
15+
.venv
16+
__pycache__

sample-apps/java/springboot-main-service/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ dependencies {
4545
implementation("io.opentelemetry:opentelemetry-api:1.34.1")
4646
implementation("software.amazon.awssdk:s3")
4747
implementation("software.amazon.awssdk:sts")
48+
implementation("software.amazon.awssdk:rds")
4849
implementation("com.mysql:mysql-connector-j:8.4.0")
4950
implementation ("org.apache.httpcomponents:httpclient:4.5.13")
5051
implementation("org.jetbrains.kotlin:kotlin-stdlib:2.0.20")

sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@
4545
import org.springframework.web.bind.annotation.ResponseBody;
4646
import software.amazon.awssdk.services.s3.S3Client;
4747
import software.amazon.awssdk.services.s3.model.GetBucketLocationRequest;
48+
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
49+
import software.amazon.awssdk.regions.Region;
50+
import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain;
51+
import software.amazon.awssdk.services.rds.RdsUtilities;
52+
import software.amazon.awssdk.services.rds.model.GenerateAuthenticationTokenRequest;
4853

4954
@Controller
5055
public class FrontendServiceController {
@@ -146,19 +151,34 @@ public String asyncService() {
146151
return "{\"traceId\": \"1-00000000-000000000000000000000000\"}";
147152
}
148153

149-
// Uses the /mysql endpoint to make an SQL call
154+
// Uses the /mysql endpoint to make an SQL call with IAM authentication
150155
@GetMapping("/mysql")
151156
@ResponseBody
152157
public String mysql() {
153158
logger.info("mysql received");
154-
final String rdsMySQLClusterPassword = new String(new Base64().decode(System.getenv("RDS_MYSQL_CLUSTER_PASSWORD").getBytes()));
155159
try {
156-
Connection connection = DriverManager.getConnection(
157-
System.getenv("RDS_MYSQL_CLUSTER_CONNECTION_URL"),
158-
System.getenv("RDS_MYSQL_CLUSTER_USERNAME"),
159-
rdsMySQLClusterPassword);
160+
RdsUtilities rdsUtilities = RdsUtilities.builder()
161+
.credentialsProvider(DefaultCredentialsProvider.create())
162+
.region(DefaultAwsRegionProviderChain.builder().build().getRegion())
163+
.build();
164+
165+
String hostname = System.getenv("RDS_MYSQL_CLUSTER_ENDPOINT");
166+
String username = System.getenv("RDS_MYSQL_CLUSTER_USERNAME");
167+
String database = System.getenv("RDS_MYSQL_CLUSTER_DATABASE");
168+
GenerateAuthenticationTokenRequest tokenRequest = GenerateAuthenticationTokenRequest.builder()
169+
.hostname(hostname)
170+
.port(3306)
171+
.username(username)
172+
.build();
173+
174+
String authToken = rdsUtilities.generateAuthenticationToken(tokenRequest);
175+
String jdbcUrl = String.format("jdbc:mysql://%s:3306/%s?useSSL=true&requireSSL=true&serverTimezone=UTC",
176+
hostname, database);
177+
178+
Connection connection = DriverManager.getConnection(jdbcUrl, username, authToken);
160179
Statement statement = connection.createStatement();
161180
statement.executeQuery("SELECT * FROM tables LIMIT 1;");
181+
connection.close();
162182
} catch (SQLException e) {
163183
logger.error("Could not complete SQL request: {}", e.getMessage());
164184
throw new RuntimeException(e);

sample-apps/node/frontend-service/index.js

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const express = require('express');
55
const mysql = require('mysql2');
66
const bunyan = require('bunyan');
77
const { S3Client, GetBucketLocationCommand } = require('@aws-sdk/client-s3');
8+
const { Signer } = require('@aws-sdk/rds-signer');
89

910
const PORT = parseInt(process.env.SAMPLE_APP_PORT || '8000', 10);
1011

@@ -120,40 +121,59 @@ app.get('/client-call', (req, res) => {
120121
makeAsyncCall = true;
121122
});
122123

123-
app.get('/mysql', (req, res) => {
124-
// Create a connection to the MySQL database
125-
const connection = mysql.createConnection({
126-
host: process.env.RDS_MYSQL_CLUSTER_ENDPOINT,
127-
user: process.env.RDS_MYSQL_CLUSTER_USERNAME,
128-
password: process.env.RDS_MYSQL_CLUSTER_PASSWORD,
129-
database: process.env.RDS_MYSQL_CLUSTER_DATABASE,
130-
});
131-
132-
// Connect to the database
133-
connection.connect((err) => {
134-
if (err) {
135-
const msg = '/mysql called with an error: ' + err.errors;
136-
logger.error(msg);
137-
return res.status(500).send(msg);
138-
}
139-
140-
// Perform a simple query
141-
connection.query('SELECT * FROM tables LIMIT 1;', (queryErr, results) => {
142-
// Close the connection
143-
connection.end();
124+
app.get('/mysql', async (req, res) => {
125+
try {
126+
const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || 'us-east-1';
127+
const signer = new Signer({
128+
region: region,
129+
hostname: process.env.RDS_MYSQL_CLUSTER_ENDPOINT,
130+
port: 3306,
131+
username: process.env.RDS_MYSQL_CLUSTER_USERNAME
132+
});
133+
const token = await signer.getAuthToken();
134+
135+
// Create a connection to the MySQL database using IAM authentication
136+
const connection = mysql.createConnection({
137+
host: process.env.RDS_MYSQL_CLUSTER_ENDPOINT,
138+
user: process.env.RDS_MYSQL_CLUSTER_USERNAME,
139+
password: token,
140+
database: process.env.RDS_MYSQL_CLUSTER_DATABASE,
141+
ssl: 'Amazon RDS',
142+
authPlugins: {
143+
mysql_clear_password: () => () => Buffer.from(token + '\0')
144+
}
145+
});
144146

145-
if (queryErr) {
146-
const msg = 'Could not complete http request to RDS database:' + queryErr.message;
147+
// Connect to the database
148+
connection.connect((err) => {
149+
if (err) {
150+
const msg = '/mysql called with an error: ' + err.message;
147151
logger.error(msg);
148152
return res.status(500).send(msg);
149153
}
150154

151-
// Send the query results as the response
152-
const msg = `/outgoing-http-call response: ${results}`;
153-
logger.info(msg);
154-
res.send(msg);
155+
// Perform a simple query
156+
connection.query('SELECT * FROM tables LIMIT 1;', (queryErr, results) => {
157+
// Close the connection
158+
connection.end();
159+
160+
if (queryErr) {
161+
const msg = 'Could not complete http request to RDS database:' + queryErr.message;
162+
logger.error(msg);
163+
return res.status(500).send(msg);
164+
}
165+
166+
// Send the query results as the response
167+
const msg = `/mysql response: ${JSON.stringify(results)}`;
168+
logger.info(msg);
169+
res.send(msg);
170+
});
155171
});
156-
});
172+
} catch (error) {
173+
const msg = '/mysql called with IAM token generation error: ' + error.message;
174+
logger.error(msg);
175+
res.status(500).send(msg);
176+
}
157177
});
158178

159179
app.listen(PORT, () => {

0 commit comments

Comments
 (0)