Running The App
[root@ip-172-31-0-239 ~]# ./app
2025/09/01 10:23:12 β Failed to fetch initial secret: operation error Secrets Manager: GetSecretValue, failed to resolve service endpoint, endpoint rule error, Invalid Configuration: Missing Region
[root@ip-172-31-0-239 ~]# export AWS_REGION=ap-south-1
[root@ip-172-31-0-239 ~]# ./app
2025/09/01 10:25:34 π [AWS] Fetched new credentials from Secrets Manager
2025/09/01 10:25:34 π Server started at http://localhost:8080
Logs
2025/09/01 10:25:40 π [AWS] Fetched new credentials from Secrets Manager
2025/09/01 10:25:40 β
DB connected successfully with cached creds
2025/09/01 10:25:40 β±οΈ DB Time: 2025-09-01 10:25:40
2025/09/01 10:25:52 π¦ [Cache] Using cached credentials
2025/09/01 10:25:52 β
DB connected successfully with cached creds
2025/09/01 10:25:52 β±οΈ DB Time: 2025-09-01 10:25:52
2025/09/01 10:25:53 π¦ [Cache] Using cached credentials
2025/09/01 10:25:53 β
DB connected successfully with cached creds
2025/09/01 10:25:53 β±οΈ DB Time: 2025-09-01 10:25:53
2025/09/01 10:25:53 π¦ [Cache] Using cached credentials
2025/09/01 10:25:53 β
DB connected successfully with cached creds
2025/09/01 10:25:53 β±οΈ DB Time: 2025-09-01 10:25:53
2025/09/01 10:28:56 π¦ [Cache] Using cached credentials
2025/09/01 10:28:56 β
DB connected successfully with cached creds
2025/09/01 10:28:56 β±οΈ DB Time: 2025-09-01 10:28:56
2025/09/01 10:28:57 π¦ [Cache] Using cached credentials
2025/09/01 10:28:57 β
DB connected successfully with cached creds
2025/09/01 10:28:57 β±οΈ DB Time: 2025-09-01 10:28:57
2025/09/01 10:28:58 π¦ [Cache] Using cached credentials
2025/09/01 10:28:58 β DB connection failed. Retrying with fresh creds...
2025/09/01 10:28:58 π [AWS] Fetched new credentials from Secrets Manager
2025/09/01 10:28:59 π¦ [Cache] Using cached credentials
2025/09/01 10:28:59 β DB connection failed. Retrying with fresh creds...
2025/09/01 10:28:59 π [AWS] Fetched new credentials from Secrets Manager
2025/09/01 10:28:59 β
DB reconnected successfully with fresh creds
2025/09/01 10:28:59 β±οΈ DB Time: 2025-09-01 10:28:59
2025/09/01 10:29:00 π¦ [Cache] Using cached credentials
2025/09/01 10:29:00 β
DB connected successfully with cached creds
2025/09/01 10:29:00 β±οΈ DB Time: 2025-09-01 10:29:00
2025/09/01 10:29:01 π¦ [Cache] Using cached credentials
2025/09/01 10:29:01 β
DB connected successfully with cached creds
main.go
package main
import (
"context"
"database/sql"
"encoding/json"
"fmt"
"log"
"net/http"
"sync"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
)
type DBSecret struct {
Username string `json:"username"`
Password string `json:"password"`
Host string `json:"host"`
DBName string `json:"dbname"`
}
type SecretCache struct {
secret DBSecret
expiresAt time.Time
mu sync.RWMutex
}
type App struct {
client *secretsmanager.Client
secretName string
cache *SecretCache
db *sql.DB
}
func NewApp(ctx context.Context, secretName string) *App {
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
log.Fatalf("β Unable to load AWS SDK config: %v", err)
}
return &App{
client: secretsmanager.NewFromConfig(cfg),
secretName: secretName,
cache: &SecretCache{},
}
}
// fetchSecretFromAWS pulls latest secret JSON from Secrets Manager
func (a *App) fetchSecretFromAWS(ctx context.Context) (DBSecret, error) {
secretValue, err := a.client.GetSecretValue(ctx, &secretsmanager.GetSecretValueInput{
SecretId: &a.secretName,
})
if err != nil {
return DBSecret{}, err
}
var secret DBSecret
if err := json.Unmarshal([]byte(*secretValue.SecretString), &secret); err != nil {
return DBSecret{}, err
}
log.Println("π [AWS] Fetched new credentials from Secrets Manager")
return secret, nil
}
// getCachedSecret returns cached secret or fetches a new one if expired
func (a *App) getCachedSecret(ctx context.Context) (DBSecret, error) {
a.cache.mu.RLock()
if time.Now().Before(a.cache.expiresAt) {
secret := a.cache.secret
a.cache.mu.RUnlock()
log.Println("π¦ [Cache] Using cached credentials")
return secret, nil
}
a.cache.mu.RUnlock()
// Fetch new creds
secret, err := a.fetchSecretFromAWS(ctx)
if err != nil {
return DBSecret{}, err
}
// Update cache
a.cache.mu.Lock()
a.cache.secret = secret
a.cache.expiresAt = time.Now().Add(5 * time.Minute)
a.cache.mu.Unlock()
return secret, nil
}
// connectDB establishes DB connection using cached or new creds
func (a *App) connectDB(ctx context.Context) (*sql.DB, DBSecret, error) {
secret, err := a.getCachedSecret(ctx)
if err != nil {
return nil, DBSecret{}, err
}
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s",
secret.Username, secret.Password, secret.Host, secret.DBName)
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, secret, err
}
if err := db.Ping(); err != nil {
// If ping fails, fetch fresh creds and retry once
log.Println("β DB connection failed. Retrying with fresh creds...")
secret, err = a.fetchSecretFromAWS(ctx)
if err != nil {
return nil, DBSecret{}, err
}
a.cache.mu.Lock()
a.cache.secret = secret
a.cache.expiresAt = time.Now().Add(5 * time.Minute)
a.cache.mu.Unlock()
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s",
secret.Username, secret.Password, secret.Host, secret.DBName)
db, err = sql.Open("mysql", dsn)
if err != nil {
return nil, DBSecret{}, err
}
if err := db.Ping(); err != nil {
return nil, DBSecret{}, err
}
log.Println("β
DB reconnected successfully with fresh creds")
return db, secret, nil
}
log.Println("β
DB connected successfully with cached creds")
return db, secret, nil
}
func (a *App) secretHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
db, secret, err := a.connectDB(ctx)
if err != nil {
http.Error(w, fmt.Sprintf("DB connection error: %v", err), http.StatusInternalServerError)
return
}
defer db.Close()
// Just run a simple query
var now string
if err := db.QueryRow("SELECT NOW()").Scan(&now); err != nil {
http.Error(w, fmt.Sprintf("DB query error: %v", err), http.StatusInternalServerError)
return
}
log.Printf("β±οΈ DB Time: %s", now)
// Print secret in API response (only for demo!)
resp := map[string]string{
"db_time": now,
"user": secret.Username,
"host": secret.Host,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func main() {
ctx := context.Background()
app := NewApp(ctx, "MYSqlProd") // replace with your secret name
// Initial cache warm-up
_, err := app.fetchSecretFromAWS(ctx)
if err != nil {
log.Fatalf("β Failed to fetch initial secret: %v", err)
}
http.HandleFunc("/secret", app.secretHandler)
log.Println("π Server started at http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
go.mod
module myapp
go 1.24.5
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.38.3 // indirect
github.com/aws/aws-sdk-go-v2/config v1.31.6 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.18.10 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6 // indirect
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.39.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.29.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.38.2 // indirect
github.com/aws/smithy-go v1.23.0 // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect
)