Development
AWSSecretManager
Code

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
)

πŸ§™ AI Wizard - Instant Page Insights

Click the button below to analyze this page.
Get an AI-generated summary and key insights in seconds.
Powered by Perplexity AI!