Skip to content

Commit

Permalink
Merge pull request #18 from Frankie0702111/feature/aws-rds
Browse files Browse the repository at this point in the history
Add AWS RDS and update the unit test.
  • Loading branch information
Frankie0702111 authored Jul 15, 2024
2 parents ca3ef14 + 7dea371 commit 1a5a751
Show file tree
Hide file tree
Showing 16 changed files with 292 additions and 47 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,12 @@ jobs:
cp ./app.env.example ./app.env
cp ./internal/config/env.go.example ./internal/config/env.go
- name: Set up SSL certificate
run: |
mkdir -p internal/config/certs
echo "${{ secrets.RDS_CA_CERT }}" > internal/config/certs/rds-ca-2019-root.pem
- name: Run tests
env:
RDS_CA_CERT: ${{ secrets.RDS_CA_CERT }}
run: make go-test-ci
7 changes: 4 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@

# Dependency directories (remove the comment below to include it)
# vendor/
internal/config/env.go
app.env
target/
*.DS_Store
target/
app.env
internal/config/env.go
internal/config/certs/*.pem

# Go workspace file
go.work
12 changes: 7 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
include app.env

DOCKER = docker compose exec server
DOCKER_HOST=db
TEST_DB=test_db
YMD = _$$(date +'%Y%m%d')
number?=3
table :=
Expand All @@ -21,19 +23,19 @@ migrate-create:

# make migrate-up number=x
migrate-up:
$(DOCKER) migrate -path ./internal/migrations -database "$(DB)://$(DB_USER):$(DB_PASS)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)?sslmode=disable" up $(number)
$(DOCKER) migrate -path ./internal/migrations -database "$(DB)://$(DB_USER):$(DB_PASS)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)?sslmode=verify-full&sslrootcert=./internal/config/certs/rds-ca-2019-root.pem" up $(number)

# make migrate-down number=x
migrate-down:
$(DOCKER) migrate -path ./internal/migrations -database "$(DB)://$(DB_USER):$(DB_PASS)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)?sslmode=disable" down $(number)
$(DOCKER) migrate -path ./internal/migrations -database "$(DB)://$(DB_USER):$(DB_PASS)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)?sslmode=verify-full&sslrootcert=./internal/config/certs/rds-ca-2019-root.pem" down $(number)

# make migrate-test-up number=x
migrate-test-up:
$(DOCKER) migrate -path ./internal/migrations -database "$(DB)://$(DB_USER):$(DB_PASS)@$(DB_HOST):$(DB_PORT)/test_db?sslmode=disable" up $(number)
$(DOCKER) migrate -path ./internal/migrations -database "$(DB)://root:root@$(DOCKER_HOST):$(DB_PORT)/$(TEST_DB)?sslmode=disable" up $(number)

# make migrate-test-down number=x
migrate-test-down:
$(DOCKER) migrate -path ./internal/migrations -database "$(DB)://$(DB_USER):$(DB_PASS)@$(DB_HOST):$(DB_PORT)/test_db?sslmode=disable" down $(number)
$(DOCKER) migrate -path ./internal/migrations -database "$(DB)://root:root@$(DOCKER_HOST):$(DB_PORT)/$(TEST_DB)?sslmode=disable" down $(number)

clean-logs:
@echo "Cleaning log directories..."
Expand Down Expand Up @@ -75,7 +77,7 @@ go-test-single:
go-test-ci:
@set -e; \
start_time=$$(date +%s); \
migrate -path ./internal/migrations -database "$(DB)://$(DB_USER):$(DB_PASS)@$(DB_HOST):$(DB_PORT)/test_db?sslmode=disable" up; \
migrate -path ./internal/migrations -database "$(DB)://root:root@127.0.0.1:$(DB_PORT)/$(TEST_DB)?sslmode=disable" up; \
go test -v -short ./...; \
end_time=$$(date +%s); \
total_duration=$$((end_time - start_time)); \
Expand Down
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ This is a To-Do List project implemented using Go language and gRPC.
git clone https://github.com/Frankie0702111/go-todolist-grpc.git
```

## 2.Set up Docker information, such as database, Redis, Server
## 2.Set up Docker information, such as database, gRPC server, gateway server...
```bash
cd go-todolist-grpc
cp app.env.example app.env
Expand All @@ -63,13 +63,22 @@ docker compose up -d
docker compose stop
```

## 4.Set up basic information, such as database, Redis, AWS, JWT
## 4.Set up the testing information, such as database, AWS, JWT...
```bash
cp ./internal/config/env.go.example ./internal/config/env.go
vim ./internal/config/env.go
```

## 5.Generate db migrations
## 5.Download the AWS-RDS certificates
- **Documents**
- [Using SSL/TLS to encrypt a connection to a DB instance or cluster](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html)

- **1.Choosing the correct AWS region**
- [Download certificate bundles for Amazon RDS](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html#UsingWithRDS.SSL.CertificatesAllRegions)
- **2.Changing the .pem file name (name: rds-ca-2019-root.pem)**
- **3.Move the rds-ca-2019-root.pem file to internal/config/certs**

## 6.Generate db migrations
```bash
# Up all migration
make migrate-up
Expand All @@ -82,6 +91,18 @@ make migrate-up number=1
make migrate-down number=1
```

## 7.Create the DB Extensions(Options)
[Reference: Determining the SSL connection status](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/PostgreSQL.Concepts.General.SSL.html#PostgreSQL.Concepts.General.SSL.Status)
```sql
CREATE EXTENSION sslinfo;

SELECT ssl_is_used();
ssl_is_used
---------
t
(1 row)
```

# Folder structure
```
├── Dockerfile
Expand Down
2 changes: 2 additions & 0 deletions app.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ DB_PORT=5432
DB_USER=root
DB_PASS=root
DB_NAME=go-todolist-grpc-db
# SSL_MODE=verify-full
SSL_MODE=disable
DB_CONN_MAX_LT_SEC=1800
DB_MAX_CONN=50
DB_MAX_IDLE=5
Expand Down
1 change: 1 addition & 0 deletions cmd/go-todolist-grpc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func main() {
Username: cnf.DBUser,
Password: cnf.DBPassword,
DBName: cnf.DBName,
SSLMode: cnf.SSLMode,
ConnectionMaxLifeTimeSec: cnf.DBConnectionMaxLifeTimeSec,
MaxConn: cnf.DBMaxConnection,
MaxIdle: cnf.DBMaxIdle,
Expand Down
1 change: 1 addition & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Config struct {
DBUser string `mapstructure:"DB_USER"`
DBPassword string `mapstructure:"DB_PASS"`
DBName string `mapstructure:"DB_NAME"`
SSLMode string `mapstructure:"SSL_MODE"`
DBConnectionMaxLifeTimeSec *int `mapstructure:"DB_CONN_MAX_LT_SEC"`
DBMaxConnection *int `mapstructure:"DB_MAX_CONN"`
DBMaxIdle *int `mapstructure:"DB_MAX_IDLE"`
Expand Down
16 changes: 10 additions & 6 deletions internal/config/env.go.example
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
package config

// The config parameters for testing.

// Server
const (
HttpPort = "8642"
GrpcPort = "7531"
)

// Gorm
// GORM
const (
Source = "postgres"
SourceUser = "root"
SourcePassword = "root"
SourceHost = "127.0.0.1"
SourcePort = "5432"
SourceDataBase = "go-todolist-grpc-db"
SourceDataBase = "test_db"
SourceSSLMode = "disable"
SourceDBConnMaxLTSec = 1800
SourceMaxConn = 50
SourceMaxIdle = 5
)

// For testing
const (
TestSourceDataBase = "test_db"
AWSSourceUser = ""
AWSSourcePassword = ""
AWSSourceHost = ""
AWSSourceDataBase = "go_todolist_grpc"
AWSSourceSSLMode = "verify-full"
)

// JWT
Expand Down
5 changes: 3 additions & 2 deletions internal/model/mod_category_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ var sqlDBCategory *sql.DB
var sqlTxCategory *sql.Tx

func setUpModCategory() {
psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s application_name=otter sslmode=disable timezone=UTC",
psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s timezone=UTC",
config.SourceHost,
config.SourcePort,
config.SourceUser,
config.SourcePassword,
config.TestSourceDataBase,
config.SourceDataBase,
config.SourceSSLMode,
)

db, err := sql.Open("postgres", psqlInfo)
Expand Down
5 changes: 3 additions & 2 deletions internal/model/mod_task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ var sqlDBTask *sql.DB
var sqlTxTask *sql.Tx

func setUpModTask() {
psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s application_name=otter sslmode=disable timezone=UTC",
psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s timezone=UTC",
config.SourceHost,
config.SourcePort,
config.SourceUser,
config.SourcePassword,
config.TestSourceDataBase,
config.SourceDataBase,
config.SourceSSLMode,
)

db, err := sql.Open("postgres", psqlInfo)
Expand Down
5 changes: 3 additions & 2 deletions internal/model/mod_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ var sqlDBUser *sql.DB
var sqlTxUser *sql.Tx

func setUpModUser() {
psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s application_name=otter sslmode=disable timezone=UTC",
psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s timezone=UTC",
config.SourceHost,
config.SourcePort,
config.SourceUser,
config.SourcePassword,
config.TestSourceDataBase,
config.SourceDataBase,
config.SourceSSLMode,
)

db, err := sql.Open("postgres", psqlInfo)
Expand Down
98 changes: 91 additions & 7 deletions internal/pkg/db/db.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package db

import (
"crypto/x509"
"database/sql"
"encoding/pem"
"fmt"
"go-todolist-grpc/internal/pkg/log"
"os"
"path/filepath"
"runtime"
"time"

_ "github.com/lib/pq"
Expand All @@ -20,19 +25,40 @@ type Option struct {
Username string
Password string
DBName string
SSLMode string
ConnectionMaxLifeTimeSec *int
MaxConn *int
MaxIdle *int
}

func Init(opt *Option) error {
psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable timezone=UTC",
opt.Host,
opt.Port,
opt.Username,
opt.Password,
opt.DBName,
)
var psqlInfo string
if opt.SSLMode == "verify-full" {
sslRootCert := getRootCertPath()
err := verifyCertificate(sslRootCert)
if err != nil {
return fmt.Errorf("certificate verification failed: %w", err)
}

psqlInfo = fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s sslrootcert=%s timezone=UTC",
opt.Host,
opt.Port,
opt.Username,
opt.Password,
opt.DBName,
opt.SSLMode,
sslRootCert,
)
} else {
psqlInfo = fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s timezone=UTC",
opt.Host,
opt.Port,
opt.Username,
opt.Password,
opt.DBName,
opt.SSLMode,
)
}

log.Info.Printf("initial gRPC DB: %s:%s", opt.Host, opt.Port)

Expand Down Expand Up @@ -89,3 +115,61 @@ func GormDriver(db gorm.ConnPool) *gorm.DB {
func ResetConn() {
gRPCDB = nil
}

func getRootCertPath() string {
// For CI to run the unit tests.
if certContent := os.Getenv("RDS_CA_CERT"); certContent != "" {
tempDir, err := os.MkdirTemp("", "rds-cert")
if err != nil {
log.Error.Printf("Failed to create temp directory: %v", err)
return ""
}

tempFile := filepath.Join(tempDir, "rds-ca-2019-root.pem")
if err := os.WriteFile(tempFile, []byte(certContent), 0600); err != nil {
log.Error.Printf("Failed to write cert content: %v", err)
return ""
}

return tempFile
}

_, b, _, _ := runtime.Caller(0)
basepath := filepath.Dir(b)
return filepath.Join(basepath, "..", "..", "config", "certs", "rds-ca-2019-root.pem")
}

func GetRootCertPath() string {
return getRootCertPath()
}

func verifyCertificate(certPath string) error {
certData, err := os.ReadFile(certPath)
if err != nil {
return fmt.Errorf("failed to read certificate file: %w", err)
}

block, _ := pem.Decode(certData)
if block == nil {
return fmt.Errorf("failed to decode PEM block")
}

cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return fmt.Errorf("failed to parse certificate: %w", err)
}

// Verify the certificate issuer
expectedIssuer := "CN=Amazon RDS Root 2019 CA,OU=Amazon RDS,O=Amazon Web Services\\, Inc.,L=Seattle,ST=Washington,C=US"
if cert.Issuer.String() != expectedIssuer {
return fmt.Errorf("unexpected certificate issuer: got %s, want %s", cert.Issuer.String(), expectedIssuer)
}

// Verify the certificate's validity period
now := time.Now()
if now.Before(cert.NotBefore) || now.After(cert.NotAfter) {
return fmt.Errorf("certificate is not valid at the current time")
}

return nil
}
Loading

0 comments on commit 1a5a751

Please sign in to comment.