Skip to content

Commit 036dccf

Browse files
authored
Merge pull request #295 from DevKor-github/codexd/harden-production-db-config
[codex] Harden production database configuration
2 parents 6e5769b + 4d850f9 commit 036dccf

7 files changed

Lines changed: 279 additions & 59 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,11 @@ jobs:
9393
SPRING_DATASOURCE_URL=${{ secrets.SPRING_DATASOURCE_URL }}
9494
SPRING_DATASOURCE_USERNAME=${{ secrets.SPRING_DATASOURCE_USERNAME }}
9595
SPRING_DATASOURCE_PASSWORD=${{ secrets.SPRING_DATASOURCE_PASSWORD }}
96-
SPRING_DATASOURCE_DRIVER_CLASS_NAME=${{ secrets.SPRING_DATASOURCE_DRIVER_CLASS_NAME }}
97-
SPRING_JPA_HIBERNATE_DDL_AUTO=${{ secrets.SPRING_JPA_HIBERNATE_DDL_AUTO }}
96+
SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver
97+
SPRING_JPA_HIBERNATE_DDL_AUTO=validate
9898
9999
SPRING_FLYWAY_ENABLED=true
100-
SPRING_FLYWAY_URL=${{ secrets.SPRING_FLYWAY_URL }}
101-
SPRING_FLYWAY_USER=${{ secrets.SPRING_FLYWAY_USER }}
102-
SPRING_FLYWAY_PASSWORD=${{ secrets.SPRING_FLYWAY_PASSWORD }}
103-
SPRING_FLYWAY_BASELINE_ON_MIGRATE=true
100+
SPRING_FLYWAY_BASELINE_ON_MIGRATE=false
104101
105102
JWT_SECRET_KEY=${{ secrets.JWT_SECRETKEY }}
106103
JWT_ACCESS_EXPIRATION=${{ secrets.JWT_ACCESS_EXPIRATION }}
@@ -139,6 +136,47 @@ jobs:
139136
FIREBASE_CREDENTIALS_BASE64=${{ secrets.FIREBASE_CREDENTIALS_BASE64 }}
140137
EOF
141138
139+
fail_deploy() {
140+
echo "Unsafe production database configuration: $1" >&2
141+
exit 1
142+
}
143+
144+
get_env_value() {
145+
grep -E "^$1=" .env | tail -n 1 | cut -d= -f2-
146+
}
147+
148+
DB_URL="$(get_env_value SPRING_DATASOURCE_URL)"
149+
DB_USERNAME="$(get_env_value SPRING_DATASOURCE_USERNAME)"
150+
DB_PASSWORD="$(get_env_value SPRING_DATASOURCE_PASSWORD)"
151+
DDL_AUTO="$(get_env_value SPRING_JPA_HIBERNATE_DDL_AUTO)"
152+
FLYWAY_BASELINE="$(get_env_value SPRING_FLYWAY_BASELINE_ON_MIGRATE)"
153+
NORMALIZED_DB_URL="$(printf '%s' "$DB_URL" | tr '[:upper:]' '[:lower:]')"
154+
NORMALIZED_DB_USERNAME="$(printf '%s' "$DB_USERNAME" | tr '[:upper:]' '[:lower:]')"
155+
156+
[ -n "$DB_URL" ] || fail_deploy "SPRING_DATASOURCE_URL is required."
157+
[ -n "$DB_USERNAME" ] || fail_deploy "SPRING_DATASOURCE_USERNAME is required."
158+
[ -n "$DB_PASSWORD" ] || fail_deploy "SPRING_DATASOURCE_PASSWORD is required."
159+
[ "$NORMALIZED_DB_USERNAME" != "root" ] || fail_deploy "SPRING_DATASOURCE_USERNAME must not be root."
160+
[ "$DDL_AUTO" = "validate" ] || fail_deploy "SPRING_JPA_HIBERNATE_DDL_AUTO must be validate."
161+
[ "$FLYWAY_BASELINE" = "false" ] || fail_deploy "SPRING_FLYWAY_BASELINE_ON_MIGRATE must be false."
162+
163+
case "$NORMALIZED_DB_URL" in
164+
*allowpublickeyretrieval=true*) fail_deploy "SPRING_DATASOURCE_URL must not enable allowPublicKeyRetrieval." ;;
165+
esac
166+
167+
case "$NORMALIZED_DB_URL" in
168+
*createdatabaseifnotexist=true*) fail_deploy "SPRING_DATASOURCE_URL must not create databases at startup." ;;
169+
esac
170+
171+
case "$NORMALIZED_DB_URL" in
172+
*usessl=false*) fail_deploy "SPRING_DATASOURCE_URL must not disable TLS." ;;
173+
esac
174+
175+
case "$NORMALIZED_DB_URL" in
176+
*sslmode=required*|*sslmode=verify_ca*|*sslmode=verify_identity*) ;;
177+
*) fail_deploy "SPRING_DATASOURCE_URL must declare sslMode=REQUIRED, VERIFY_CA, or VERIFY_IDENTITY." ;;
178+
esac
179+
142180
echo "${{ secrets.GHCR_READ_TOKEN }}" | sudo docker login ghcr.io -u "${{ secrets.GHCR_USERNAME }}" --password-stdin
143181
144182
if sudo docker compose version >/dev/null 2>&1; then

.github/workflows/test.yml

Lines changed: 3 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -62,62 +62,12 @@ jobs:
6262
6363
6464
65-
# 4. 환경 변수 설정 파일 생성
66-
- name: Create Config Files
67-
run: |
68-
mkdir -p ontime-back/src/main/resources
69-
mkdir -p ontime-back/src/main/resources/key
70-
echo "spring.application.name=${{ secrets.SPRING_APPLICATION_NAME }}" > ontime-back/src/main/resources/application.properties
71-
echo "spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test_db?serverTimezone=UTC&useSSL=false" >> ontime-back/src/main/resources/application.properties
72-
echo "spring.datasource.username=test_user" >> ontime-back/src/main/resources/application.properties
73-
echo "spring.datasource.password=test_password" >> ontime-back/src/main/resources/application.properties
74-
echo "spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver" >> ontime-back/src/main/resources/application.properties
75-
echo "spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect" >> ontime-back/src/main/resources/application.properties
76-
echo "spring.jpa.hibernate.ddl-auto=validate" >> ontime-back/src/main/resources/application.properties
77-
echo "spring.sql.init.mode=always" >> ontime-back/src/main/resources/application.properties
78-
echo "jwt.secret.key=${{ secrets.JWT_SECRETKEY }}" >> ontime-back/src/main/resources/application.properties
79-
echo "jwt.access.expiration=${{ secrets.JWT_ACCESS_EXPIRATION }}" >> ontime-back/src/main/resources/application.properties
80-
echo "jwt.refresh.expiration=${{ secrets.JWT_REFRESH_EXPIRATION }}" >> ontime-back/src/main/resources/application.properties
81-
echo "jwt.access.header=${{ secrets.JWT_ACCESS_HEADER }}" >> ontime-back/src/main/resources/application.properties
82-
echo "jwt.refresh.header=${{ secrets.JWT_REFRESH_HEADER }}" >> ontime-back/src/main/resources/application.properties
83-
echo "google.web.client-id = ${{ secrets.GOOGLE_WEB_CLIENT_ID }}" >> ontime-back/src/main/resources/application.properties
84-
echo "google.app.client-id = ${{ secrets.GOOGLE_APP_CLIENT_ID }}" >> ontime-back/src/main/resources/application.properties
85-
echo "spring.security.oauth2.client.registration.google.client-secret=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_CLIENT_SECRET }}" >> ontime-back/src/main/resources/application.properties
86-
echo "spring.security.oauth2.client.registration.google.scope=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_SCOPE }}" >> ontime-back/src/main/resources/application.properties
87-
echo "spring.security.oauth2.client.registration.google.redirect-uri=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_REDIRECT_URI }}" >> ontime-back/src/main/resources/application.properties
88-
echo "spring.security.oauth2.client.registration.google.authorization-grant-type=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_AUTHORIZATION_GRANT_TYPE }}" >> ontime-back/src/main/resources/application.properties
89-
echo "spring.security.oauth2.client.registration.google.client-name=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_GOOGLE_CLIENT_NAME }}" >> ontime-back/src/main/resources/application.properties
90-
echo "spring.security.oauth2.client.provider.google.authorization-uri=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_AUTHORIZATION_URI }}" >> ontime-back/src/main/resources/application.properties
91-
echo "spring.security.oauth2.client.provider.google.token-uri=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_TOKEN_URI }}" >> ontime-back/src/main/resources/application.properties
92-
echo "spring.security.oauth2.client.provider.google.user-info-uri=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_USER_INFO_URI }}" >> ontime-back/src/main/resources/application.properties
93-
echo "spring.security.oauth2.client.provider.google.user-name-attribute=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_GOOGLE_USER_NAME_ATTRIBUTE }}" >> ontime-back/src/main/resources/application.properties
94-
echo "spring.security.oauth2.client.registration.kakao.client-id=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_ID }}" >> ontime-back/src/main/resources/application.properties
95-
echo "spring.security.oauth2.client.registration.kakao.scope=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_SCOPE }}" >> ontime-back/src/main/resources/application.properties
96-
echo "spring.security.oauth2.client.registration.kakao.redirect-uri=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_REDIRECT_URI }}" >> ontime-back/src/main/resources/application.properties
97-
echo "spring.security.oauth2.client.registration.kakao.authorization-grant-type=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_AUTHORIZATION_GRANT_TYPE }}" >> ontime-back/src/main/resources/application.properties
98-
echo "spring.security.oauth2.client.registration.kakao.client-name=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_REGISTRATION_KAKAO_CLIENT_NAME }}" >> ontime-back/src/main/resources/application.properties
99-
echo "spring.security.oauth2.client.provider.kakao.authorization-uri=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_AUTHORIZATION_URI }}" >> ontime-back/src/main/resources/application.properties
100-
echo "spring.security.oauth2.client.provider.kakao.token-uri=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_TOKEN_URI }}" >> ontime-back/src/main/resources/application.properties
101-
echo "spring.security.oauth2.client.provider.kakao.user-info-uri=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_USER_INFO_URI }}" >> ontime-back/src/main/resources/application.properties
102-
echo "spring.security.oauth2.client.provider.kakao.user-name-attribute=${{ secrets.SPRING_SECURITY_OAUTH2_CLIENT_PROVIDER_KAKAO_USER_NAME_ATTRIBUTE }}" >> ontime-back/src/main/resources/application.properties
103-
echo "apple.client.id=${{ secrets.APPLE_CLIENT_ID }}" >> ontime-back/src/main/resources/application.properties
104-
echo "apple.client.secret=${{ secrets.APPLE_CLIENT_SECRET }}" >> ontime-back/src/main/resources/application.properties
105-
echo "apple.login.key=${{ secrets.APPLE_LOGIN_KEY }}" >> ontime-back/src/main/resources/application.properties
106-
echo "apple.team.id=${{ secrets.APPLE_TEAM_ID }}" >> ontime-back/src/main/resources/application.properties
107-
echo "spring.flyway.enabled=true" >> ontime-back/src/main/resources/application.properties
108-
echo "spring.flyway.url=jdbc:mysql://127.0.0.1:3306/test_db?serverTimezone=UTC&useSSL=false" >> ontime-back/src/main/resources/application.properties
109-
echo "spring.flyway.user=test_user" >> ontime-back/src/main/resources/application.properties
110-
echo "spring.flyway.password=test_password" >> ontime-back/src/main/resources/application.properties
111-
echo "spring.flyway.baseline-on-migrate=true" >> ontime-back/src/main/resources/application.properties
112-
echo "management.endpoints.web.exposure.include=health" >> ontime-back/src/main/resources/application.properties
113-
echo "management.endpoint.health.show-details=always" >> ontime-back/src/main/resources/application.properties
114-
echo "${{ secrets.ONTIME_PUSH_FIREBASE_ADMINSDK }}" > ontime-back/src/main/resources/ontime-c63f1-firebase-adminsdk-fbsvc-a043cdc829.json
115-
echo "${{ secrets.AUTHKEY_743M7R5W3W }}" > ontime-back/src/main/resources/key/AuthKey_743M7R5W3W.p8
116-
117-
# 5. Gradle 빌드 & JUnit 테스트 실행
65+
# 4. Gradle 빌드 & JUnit 테스트 실행
11866
- name: Run Tests with Gradle
11967
id: run-tests # 실행 결과를 output으로 저장할 id 추가
12068
continue-on-error: true
69+
env:
70+
SPRING_PROFILES_ACTIVE: test
12171
run: |
12272
cd ontime-back
12373
./gradlew test
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Database Configuration
2+
3+
Production uses MySQL with environment-provided database credentials.
4+
5+
## Production
6+
7+
- Spring profile: `prod`
8+
- Datasource URL: `SPRING_DATASOURCE_URL`
9+
- Datasource username: `SPRING_DATASOURCE_USERNAME`
10+
- Datasource password: `SPRING_DATASOURCE_PASSWORD`
11+
- Datasource driver: `com.mysql.cj.jdbc.Driver`
12+
- Hibernate DDL mode: `validate`
13+
- SQL logging: disabled
14+
- Formatted SQL logging: disabled
15+
- Flyway: enabled
16+
- Flyway baseline on migrate: disabled
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Database Configuration
2+
spring.datasource.url=${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/ontime_db?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&createDatabaseIfNotExist=true}
3+
spring.datasource.username=${SPRING_DATASOURCE_USERNAME:root}
4+
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD:ontime1234}
5+
spring.datasource.driver-class-name=${SPRING_DATASOURCE_DRIVER_CLASS_NAME:com.mysql.cj.jdbc.Driver}
6+
7+
# JPA / Hibernate
8+
spring.jpa.database=mysql
9+
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
10+
spring.jpa.hibernate.ddl-auto=update
11+
spring.jpa.show-sql=true
12+
spring.jpa.properties.hibernate.format_sql=true
13+
14+
# Flyway
15+
spring.flyway.enabled=true
16+
spring.flyway.baseline-on-migrate=true
17+
18+
# JWT Configuration
19+
jwt.secret.key=${JWT_SECRET_KEY:my_secret_key_for_ontime_back_application_development_environment_1234567890}
20+
jwt.access.expiration=${JWT_ACCESS_EXPIRATION:3600000}
21+
jwt.refresh.expiration=${JWT_REFRESH_EXPIRATION:1209600000}
22+
jwt.access.header=${JWT_ACCESS_HEADER:Authorization}
23+
jwt.refresh.header=${JWT_REFRESH_HEADER:Authorization-refresh}
24+
25+
# Google OAuth
26+
google.web.client-id=${GOOGLE_WEB_CLIENT_ID:599377893328-dljp16andl10374bnm9b1nnfp9uj5pvd.apps.googleusercontent.com}
27+
google.app.client-id=${GOOGLE_APP_CLIENT_ID:456571312261-r35ah9qi0qaq7al007e2db0e0jmjcmb4.apps.googleusercontent.com}
28+
29+
# Apple OAuth
30+
apple.client.id=${APPLE_CLIENT_ID:your_apple_client_id}
31+
apple.team.id=${APPLE_TEAM_ID:your_apple_team_id}
32+
apple.login.key=${APPLE_LOGIN_KEY:your_apple_key_id}
33+
apple.client.secret=${APPLE_CLIENT_SECRET:your_apple_private_key}
34+
apple.private-key.base64=${APPLE_PRIVATE_KEY_BASE64:}
35+
36+
# Firebase
37+
firebase.credentials.base64=${FIREBASE_CREDENTIALS_BASE64:}
38+
39+
# Logging
40+
logging.level.root=INFO
41+
logging.level.devkor.ontime_back=DEBUG
42+
43+
# Feature flags
44+
feature.apple-login.enabled=${FEATURE_APPLE_LOGIN_ENABLED:true}

ontime-back/src/main/resources/application-prod.properties

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
# Database Configuration
2+
spring.datasource.url=${SPRING_DATASOURCE_URL}
3+
spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
4+
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
5+
spring.datasource.driver-class-name=${SPRING_DATASOURCE_DRIVER_CLASS_NAME:com.mysql.cj.jdbc.Driver}
6+
7+
# JPA / Hibernate
8+
spring.jpa.database=mysql
9+
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
10+
spring.jpa.hibernate.ddl-auto=validate
11+
spring.jpa.show-sql=false
12+
spring.jpa.properties.hibernate.format_sql=false
13+
14+
# Flyway
15+
spring.flyway.enabled=true
16+
spring.flyway.baseline-on-migrate=false
17+
18+
# Logging
19+
logging.level.root=INFO
20+
logging.level.devkor.ontime_back=INFO
21+
22+
# Actuator
123
management.endpoint.health.probes.enabled=true
224
management.endpoints.web.exposure.include=health
325
management.health.readinessstate.enabled=true
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Database Configuration
2+
spring.datasource.url=${SPRING_DATASOURCE_URL:jdbc:mysql://127.0.0.1:3306/test_db?serverTimezone=UTC&useSSL=false}
3+
spring.datasource.username=${SPRING_DATASOURCE_USERNAME:test_user}
4+
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD:test_password}
5+
spring.datasource.driver-class-name=${SPRING_DATASOURCE_DRIVER_CLASS_NAME:com.mysql.cj.jdbc.Driver}
6+
7+
# JPA / Hibernate
8+
spring.jpa.database=mysql
9+
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
10+
spring.jpa.hibernate.ddl-auto=validate
11+
spring.jpa.show-sql=false
12+
spring.jpa.properties.hibernate.format_sql=false
13+
14+
# Flyway
15+
spring.flyway.enabled=true
16+
spring.flyway.baseline-on-migrate=false
17+
18+
# JWT Configuration
19+
jwt.secret.key=${JWT_SECRET_KEY:test_secret_key_for_ontime_back_application_tests_1234567890}
20+
jwt.access.expiration=${JWT_ACCESS_EXPIRATION:3600000}
21+
jwt.refresh.expiration=${JWT_REFRESH_EXPIRATION:1209600000}
22+
jwt.access.header=${JWT_ACCESS_HEADER:Authorization}
23+
jwt.refresh.header=${JWT_REFRESH_HEADER:Authorization-refresh}
24+
25+
# Google OAuth
26+
google.web.client-id=${GOOGLE_WEB_CLIENT_ID:test-google-web-client-id}
27+
google.app.client-id=${GOOGLE_APP_CLIENT_ID:test-google-app-client-id}
28+
29+
# Apple OAuth
30+
apple.client.id=${APPLE_CLIENT_ID:test-apple-client-id}
31+
apple.team.id=${APPLE_TEAM_ID:test-apple-team-id}
32+
apple.login.key=${APPLE_LOGIN_KEY:test-apple-key-id}
33+
apple.client.secret=${APPLE_CLIENT_SECRET:}
34+
apple.private-key.base64=${APPLE_PRIVATE_KEY_BASE64:}
35+
36+
# Firebase
37+
firebase.credentials.base64=${FIREBASE_CREDENTIALS_BASE64:}
38+
39+
# Logging
40+
logging.level.root=INFO
41+
logging.level.devkor.ontime_back=INFO
42+
43+
# Feature flags
44+
feature.apple-login.enabled=${FEATURE_APPLE_LOGIN_ENABLED:false}
45+
46+
# Actuator
47+
management.endpoints.web.exposure.include=health
48+
management.endpoint.health.show-details=always

0 commit comments

Comments
 (0)