์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿ” ์‚ฌ์šฉ์ž ๊ถŒํ•œ ๊ด€๋ฆฌ์™€ ์ตœ์†Œ ๊ถŒํ•œ ์›์น™(Least Privilege) โ€” DBยท์„œ๋ฒ„ ๋ณด์•ˆ์˜ ํ•ต์‹ฌ ์ „๋žต

๐Ÿ” ์‚ฌ์šฉ์ž ๊ถŒํ•œ ๊ด€๋ฆฌ์™€ ์ตœ์†Œ ๊ถŒํ•œ ์›์น™(Least Privilege) โ€” DBยท์„œ๋ฒ„ ๋ณด์•ˆ์˜ ํ•ต์‹ฌ ์ „๋žต

DB / ์„œ๋ฒ„ ๋ณด์•ˆ | ํ”„๋กœ๊ทธ๋žจ ๊ฐœ๋ฐœ

๐Ÿ›ก๏ธ ๋ณด์•ˆ ๐Ÿ—„๏ธ DB โš™๏ธ ์„œ๋ฒ„ ๐Ÿ”‘ ๊ถŒํ•œ๊ด€๋ฆฌ

์•ผ ์†”์งํžˆ ๋งํ•ด์„œ, ๊ฐœ๋ฐœํ•˜๋‹ค ๋ณด๋ฉด ๊ถŒํ•œ ์„ค์ • ๊ท€์ฐฎ์•„์„œ ๊ทธ๋ƒฅ root ๊ณ„์ •์œผ๋กœ ๋‹ค ์—ฐ๊ฒฐํ•ด๋ณธ ์  ์žˆ์ง€ ์•Š์Œ? ใ…‹ใ…‹ใ…‹ ๋‚˜๋งŒ ๊ทธ๋Ÿฐ ๊ฑฐ ์•„๋‹ˆ์ž–์•„์š”. ๊ทผ๋ฐ ๊ทธ๊ฒŒ ์–ผ๋งˆ๋‚˜ ์œ„ํ—˜ํ•œ ์ง“์ธ์ง€ ์•Œ๊ณ  ๋‚˜๋ฉด ์ง„์งœ ๋“ฑ๊ณจ์ด ์„œ๋Š˜ํ•ด์ง ๐Ÿ˜ฑ

์˜ค๋Š˜์€ ์‚ฌ์šฉ์ž ๊ถŒํ•œ ๊ด€๋ฆฌ์™€ ์ตœ์†Œ ๊ถŒํ•œ ์›์น™(Principle of Least Privilege, PoLP)์— ๋Œ€ํ•ด์„œ DBยท์„œ๋ฒ„ ๊ด€์ ์—์„œ ์™„์ „ ์ œ๋Œ€๋กœ ํŒŒํ—ค์ณ๋ณผ ๊ฑฐ์•ผ. ์ด๊ฑฐ ๊ทธ๋ƒฅ ์ด๋ก  ์–˜๊ธฐ๊ฐ€ ์•„๋‹ˆ๋ผ ์‹ค์ œ๋กœ ํ˜„์—…์—์„œ ์–ด๋–ป๊ฒŒ ์“ฐ์ด๋Š”์ง€, ์™œ ์ค‘์š”ํ•œ์ง€, ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š”์ง€๊นŒ์ง€ ๋‹ค ๋‹ด์•˜์œผ๋‹ˆ๊นŒ ๋๊นŒ์ง€ ์ฝ์–ด๋ด ๐Ÿ‘€

๐Ÿ” ๋ณด์•ˆ ๊ธฐ์ดˆ โ†’ ๐Ÿ—„๏ธ DB ๊ถŒํ•œ โ†’ ๐Ÿ–ฅ๏ธ ์„œ๋ฒ„ ๊ถŒํ•œ โ†’ ๐Ÿš€ ์‹ค์ „ ์ ์šฉ
์ตœ์†Œ ๊ถŒํ•œ ์›์น™ (Principle of Least Privilege) ํ•„์š”ํ•œ ๊ถŒํ•œ๋งŒ, ํ•„์š”ํ•œ ์‹œ๊ฐ„๋งŒ, ํ•„์š”ํ•œ ๋Œ€์ƒ์—๊ฒŒ๋งŒ โŒ ๊ณผ๋„ํ•œ ๊ถŒํ•œ ๋ถ€์—ฌ (์œ„ํ—˜!) ๐Ÿ‘ค ์ผ๋ฐ˜ ์•ฑ ์„œ๋น„์Šค ๊ณ„์ • SELECT DROP TABLE GRANT ALL ๐Ÿ’ฅ ํ•ดํ‚น ์‹œ ์ „์ฒด DB ์‚ญ์ œ ๊ฐ€๋Šฅ ๋ฐ์ดํ„ฐ ์œ ์ถœ ยท ๋žœ์„ฌ์›จ์–ด ยท ๊ถŒํ•œ ํƒˆ์ทจ โœ… ์ตœ์†Œ ๊ถŒํ•œ ๋ถ€์—ฌ (์•ˆ์ „!) ๐Ÿ‘ค ์ผ๋ฐ˜ ์•ฑ ์„œ๋น„์Šค ๊ณ„์ • SELECT only INSERT only ๐Ÿ›ก๏ธ ํ•ดํ‚น ์‹œ ํ”ผํ•ด ์ตœ์†Œํ™” ์ฝ๊ธฐยท์“ฐ๊ธฐ๋งŒ ๊ฐ€๋Šฅ, ์‚ญ์ œยท์ˆ˜์ • ๋ถˆ๊ฐ€ VS

๐Ÿ” ์ตœ์†Œ ๊ถŒํ•œ ์›์น™(PoLP)์ด ๋ญ”๋ฐ?

์ตœ์†Œ ๊ถŒํ•œ ์›์น™(Principle of Least Privilege, PoLP)์€ 1975๋…„ Jerome Saltzer์™€ Michael Schroeder๊ฐ€ ๋ฐœํ‘œํ•œ ๋…ผ๋ฌธ "The Protection of Information in Computer Systems"์—์„œ ์ฒ˜์Œ ๊ณต์‹ํ™”๋œ ๋ณด์•ˆ ์›์น™์ด์•ผ. ๋ฌด๋ ค 50๋…„ ๊ฐ€๊นŒ์ด ๋œ ๊ฐœ๋…์ธ๋ฐ ์ง€๊ธˆ๋„ ํ˜„์—…์—์„œ ํ•ต์‹ฌ ๋ณด์•ˆ ์›์น™์œผ๋กœ ์“ฐ์ด๊ณ  ์žˆ๋‹ค๋Š” ๊ฒŒ ์ง„์งœ ๋ ˆ์ „๋“œ์ž„ ใ„ทใ„ท

๋ชจ๋“  ํ”„๋กœ๊ทธ๋žจ๊ณผ ๋ชจ๋“  ์‚ฌ์šฉ์ž๋Š” ์ž์‹ ์˜ ์—…๋ฌด๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ์ตœ์†Œํ•œ์˜ ๊ถŒํ•œ๋งŒ ๊ฐ€์ ธ์•ผ ํ•œ๋‹ค.

์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด ์ด๊ฑฐ์•ผ ๐Ÿ‘‡

๐Ÿข ํšŒ์‚ฌ ๋น„์œ ๋กœ ์ดํ•ดํ•˜๊ธฐ

์‹ ์ž…์‚ฌ์›์ด ์ž…์‚ฌํ–ˆ๋‹ค๊ณ  ํ•ด๋ด. ์ด ์นœ๊ตฌํ•œํ…Œ ์ฒ˜์Œ๋ถ€ํ„ฐ CEO ๊ถŒํ•œ์„ ์ค„ ๊ฑฐ์•ผ? ๋‹น์—ฐํžˆ ์•„๋‹ˆ์ž–์•„.
์ž๊ธฐ ์—…๋ฌด์— ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ์ค˜์•ผ์ง€. ์˜์—…ํŒ€์ด๋ฉด ์˜์—… ๊ด€๋ จ ์‹œ์Šคํ…œ๋งŒ, ๊ฐœ๋ฐœํŒ€์ด๋ฉด ๊ฐœ๋ฐœ ์„œ๋ฒ„๋งŒ.

DBยท์„œ๋ฒ„๋„ ๋˜‘๊ฐ™์•„. ์•ฑ์ด ๋ฐ์ดํ„ฐ ์กฐํšŒ๋งŒ ํ•˜๋ฉด ๋˜๋ฉด SELECT ๊ถŒํ•œ๋งŒ, ๋กœ๊ทธ ์“ฐ๋Š” ์„œ๋น„์Šค๋ฉด INSERT ๊ถŒํ•œ๋งŒ ์ฃผ๋ฉด ๋ผ.

๐Ÿ“Œ PoLP์˜ 3๊ฐ€์ง€ ํ•ต์‹ฌ ์š”์†Œ

1
์ตœ์†Œ ๊ธฐ๋Šฅ(Minimum Functionality)
์—…๋ฌด ์ˆ˜ํ–‰์— ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋งŒ ํ™œ์„ฑํ™”. ์•ˆ ์“ฐ๋Š” ๊ธฐ๋Šฅ์€ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ฑฐ๋‚˜ ์ œ๊ฑฐ.
2
์ตœ์†Œ ์ ‘๊ทผ(Minimum Access)
ํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค์—๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก ์ œํ•œ. ๋‹ค๋ฅธ DB, ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”, ๋‹ค๋ฅธ ์„œ๋ฒ„๋Š” ์ ‘๊ทผ ๋ถˆ๊ฐ€.
3
์ตœ์†Œ ์‹œ๊ฐ„(Minimum Duration)
ํ•„์š”ํ•œ ์‹œ๊ฐ„ ๋™์•ˆ๋งŒ ๊ถŒํ•œ ์œ ์ง€. ์ž‘์—… ์™„๋ฃŒ ํ›„ ์ฆ‰์‹œ ๊ถŒํ•œ ํšŒ์ˆ˜. ์ž„์‹œ ๊ถŒํ•œ์€ ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ์„ค์ •.

๐ŸŽฏ ์™œ ์ด๊ฒŒ ์ค‘์š”ํ•œ๊ฐ€? โ€” ์‹ค์ œ ์‚ฌ๊ณ  ์‚ฌ๋ก€

์ด๋ก ๋งŒ ์–˜๊ธฐํ•˜๋ฉด ์žฌ๋ฏธ์—†์œผ๋‹ˆ๊นŒ ์‹ค์ œ ์‚ฌ๋ก€ ๋ณด์ž ๐Ÿ˜ค

๐Ÿ’€ ์‚ฌ๋ก€ 1: 2017๋…„ Equifax ๋ฐ์ดํ„ฐ ์œ ์ถœ ์‚ฌ๊ฑด

๋ฏธ๊ตญ ์‹ ์šฉํ‰๊ฐ€์‚ฌ Equifax์—์„œ 1์–ต 4,700๋งŒ ๋ช…์˜ ๊ฐœ์ธ์ •๋ณด๊ฐ€ ์œ ์ถœ๋์–ด. ์›์ธ ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋ฐ”๋กœ ๊ณผ๋„ํ•œ ๊ถŒํ•œ ์„ค์ •. ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ณ„์ •์ด DB ์ „์ฒด์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ๊ถŒํ•œ์„ ๊ฐ€์ง€๊ณ  ์žˆ์—ˆ๊ณ , ๊ณต๊ฒฉ์ž๋Š” ์ด ๊ณ„์ •์„ ํƒˆ์ทจํ•ด์„œ ์ˆ˜์‹ญ ๊ฐœ์˜ DB์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธ์–ด๊ฐ”์–ด. ํ”ผํ•ด์•ก? ์•ฝ 7์–ต ๋‹ฌ๋Ÿฌ(ํ•œํ™” ์•ฝ 9,000์–ต ์›) ์ด์ƒ ๐Ÿ˜ฑ

๐Ÿ’€ ์‚ฌ๋ก€ 2: 2020๋…„ Twitter ํ•ดํ‚น

์˜ค๋ฐ”๋งˆ, ๋นŒ ๊ฒŒ์ด์ธ , ์ผ๋ก  ๋จธ์Šคํฌ ๋“ฑ ์œ ๋ช…์ธ ๊ณ„์ •์ด ํ•ดํ‚น๋๋˜ ๊ทธ ์‚ฌ๊ฑด. ๋‚ด๋ถ€ ์ง์› ๊ณ„์ •์„ ์†Œ์…œ ์—”์ง€๋‹ˆ์–ด๋ง์œผ๋กœ ํƒˆ์ทจํ–ˆ๋Š”๋ฐ, ๊ทธ ๊ณ„์ •์ด ํ•„์š” ์ด์ƒ์˜ ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์„œ ํ”ผํ•ด๊ฐ€ ์ปค์กŒ์–ด. ์ตœ์†Œ ๊ถŒํ•œ ์›์น™์ด ์ œ๋Œ€๋กœ ์ ์šฉ๋๋‹ค๋ฉด ํ”ผํ•ด๋ฅผ ํ›จ์”ฌ ์ค„์ผ ์ˆ˜ ์žˆ์—ˆ์„ ๊ฑฐ์•ผ.

์ด๋Ÿฐ ์‚ฌ๊ณ ๋“ค์ด ์™œ ๋‚˜๋ƒ๋ฉด, ๊ฐœ๋ฐœํ•  ๋•Œ "์ผ๋‹จ ํŽธํ•˜๊ฒŒ ํ•˜์ž"๋Š” ๋งˆ์ธ๋“œ๋กœ ๊ถŒํ•œ์„ ํผ์ฃผ๋‹ค ๋ณด๋ฉด ๋‚˜์ค‘์— ๊ฐ๋‹น์ด ์•ˆ ๋˜๋Š” ๊ฑฐ์•ผ. ์ฒ˜์Œ๋ถ€ํ„ฐ ์ œ๋Œ€๋กœ ์„ค๊ณ„ํ•ด์•ผ ํ•ด ๐Ÿ’ช

๐Ÿ—„๏ธ DB ๊ถŒํ•œ ๊ด€๋ฆฌ โ€” MySQL/MariaDB ํŽธ

์ž, ์ด์ œ ์‹ค์ „์œผ๋กœ ๋“ค์–ด๊ฐ€๋ณด์ž. DB์—์„œ ๊ถŒํ•œ ๊ด€๋ฆฌ๊ฐ€ ์–ด๋–ป๊ฒŒ ์ด๋ฃจ์–ด์ง€๋Š”์ง€ MySQL/MariaDB ๊ธฐ์ค€์œผ๋กœ ์„ค๋ช…ํ• ๊ฒŒ. PostgreSQL์ด๋‚˜ Oracle๋„ ๊ฐœ๋…์€ ๊ฐ™์œผ๋‹ˆ๊นŒ ์ฐธ๊ณ ํ•ด๋ด ๐Ÿ‘

๐Ÿ”‘ MySQL ๊ถŒํ•œ ์ฒด๊ณ„ ์ดํ•ดํ•˜๊ธฐ

MySQL์˜ ๊ถŒํ•œ์€ ํฌ๊ฒŒ 4๊ฐ€์ง€ ๋ ˆ๋ฒจ๋กœ ๋‚˜๋‰˜์–ด:

๊ถŒํ•œ ๋ ˆ๋ฒจ ๋ฒ”์œ„ ์˜ˆ์‹œ
Global ์„œ๋ฒ„ ์ „์ฒด SUPER, PROCESS, RELOAD
Database ํŠน์ • DB CREATE, DROP, ALTER
Table ํŠน์ • ํ…Œ์ด๋ธ” SELECT, INSERT, UPDATE, DELETE
Column ํŠน์ • ์ปฌ๋Ÿผ SELECT(col1), UPDATE(col2)

์ด ๋ ˆ๋ฒจ์„ ์ดํ•ดํ•˜๋Š” ๊ฒŒ ํ•ต์‹ฌ์ด์•ผ. ๋Œ€๋ถ€๋ถ„์˜ ์•ฑ ์„œ๋น„์Šค ๊ณ„์ •์€ Table ๋ ˆ๋ฒจ์—์„œ ํ•„์š”ํ•œ ๊ถŒํ•œ๋งŒ ์ฃผ๋ฉด ์ถฉ๋ถ„ํ•ด.

โšก ์‹ค์ „ ๊ถŒํ•œ ์„ค์ • โ€” ์ด๋ ‡๊ฒŒ ํ•ด!

์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ๊ณ„์ •์„ ๋งŒ๋“ค๊ณ  ๊ถŒํ•œ์„ ์ฃผ๋Š”์ง€ ์ฝ”๋“œ๋กœ ๋ณด์ž:

-- โŒ ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ ˆ๋Œ€ ์•ˆ ๋จ (root ๊ณ„์ • ์ง์ ‘ ์‚ฌ์šฉ)
-- ์•ฑ์—์„œ root๋กœ DB ์—ฐ๊ฒฐํ•˜๋Š” ๊ฑด ์ž์‚ดํ–‰์œ„์ž„ ใ„นใ…‡

-- โœ… ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ•: ์—ญํ• ๋ณ„ ๊ณ„์ • ๋ถ„๋ฆฌ

-- 1. ์ฝ๊ธฐ ์ „์šฉ ๊ณ„์ • (์กฐํšŒ API์šฉ)
CREATE USER 'app_reader'@'192.168.1.%' IDENTIFIED BY '๊ฐ•๋ ฅํ•œํŒจ์Šค์›Œ๋“œ!@#';
GRANT SELECT ON myapp_db.* TO 'app_reader'@'192.168.1.%';

-- 2. ์“ฐ๊ธฐ ๊ณ„์ • (์ผ๋ฐ˜ ์•ฑ ์„œ๋น„์Šค์šฉ)
CREATE USER 'app_writer'@'192.168.1.%' IDENTIFIED BY '๋‹ค๋ฅธ๊ฐ•๋ ฅํ•œํŒจ์Šค์›Œ๋“œ!@#';
GRANT SELECT, INSERT, UPDATE ON myapp_db.* TO 'app_writer'@'192.168.1.%';
-- DELETE๋Š” ์ฃผ์ง€ ์•Š์Œ! ์‹ค์ˆ˜๋กœ ๋ฐ์ดํ„ฐ ๋‚ ๋ฆฌ๋Š” ๊ฑฐ ๋ฐฉ์ง€

-- 3. ๋ฐฐ์น˜ ์ž‘์—…์šฉ ๊ณ„์ • (ํŠน์ • ํ…Œ์ด๋ธ”๋งŒ)
CREATE USER 'batch_user'@'localhost' IDENTIFIED BY '๋ฐฐ์น˜์ „์šฉํŒจ์Šค์›Œ๋“œ!@#';
GRANT SELECT, INSERT ON myapp_db.batch_logs TO 'batch_user'@'localhost';
GRANT SELECT ON myapp_db.users TO 'batch_user'@'localhost';

-- 4. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜/๋ฐฐํฌ์šฉ ๊ณ„์ • (์ž„์‹œ, ์ž‘์—… ํ›„ ์‚ญ์ œ)
CREATE USER 'deploy_user'@'localhost' IDENTIFIED BY '์ž„์‹œํŒจ์Šค์›Œ๋“œ!@#';
GRANT ALTER, CREATE, DROP, INDEX ON myapp_db.* TO 'deploy_user'@'localhost';
-- ๋ฐฐํฌ ์™„๋ฃŒ ํ›„: DROP USER 'deploy_user'@'localhost';

-- ๊ถŒํ•œ ์ ์šฉ
FLUSH PRIVILEGES;

-- ํ˜„์žฌ ๊ถŒํ•œ ํ™•์ธ
SHOW GRANTS FOR 'app_writer'@'192.168.1.%';

๐Ÿ’ก ํฌ์ธํŠธ! ์ ‘์† IP๋„ ์ œํ•œํ•˜๋Š” ๊ฑฐ ๋ณด์ด์ง€? 'app_reader'@'192.168.1.%'์ฒ˜๋Ÿผ ํŠน์ • IP ๋Œ€์—ญ์—์„œ๋งŒ ์ ‘์† ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋ฉด ์™ธ๋ถ€์—์„œ ์ง์ ‘ DB์— ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ ์ž์ฒด๋ฅผ ๋ง‰์„ ์ˆ˜ ์žˆ์–ด. ์ด๊ฒƒ๋„ ์ตœ์†Œ ๊ถŒํ•œ ์›์น™์˜ ์ผ๋ถ€์•ผ ๐Ÿ”’

๐ŸŽญ ์ปฌ๋Ÿผ ๋ ˆ๋ฒจ ๊ถŒํ•œ โ€” ์ด๋Ÿฐ ๊ฒƒ๋„ ์žˆ์–ด!

๋” ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ œ์–ดํ•˜๊ณ  ์‹ถ์œผ๋ฉด ์ปฌ๋Ÿผ ๋‹จ์œ„๋กœ๋„ ๊ถŒํ•œ์„ ์ค„ ์ˆ˜ ์žˆ์–ด. ์˜ˆ๋ฅผ ๋“ค์–ด ๊ณ ๊ฐ ์ •๋ณด ํ…Œ์ด๋ธ”์—์„œ ์ด๋ฆ„์ด๋ž‘ ์ด๋ฉ”์ผ์€ ๋ณผ ์ˆ˜ ์žˆ์ง€๋งŒ ์ฃผ๋ฏผ๋ฒˆํ˜ธ๋‚˜ ์นด๋“œ๋ฒˆํ˜ธ๋Š” ๋ชป ๋ณด๊ฒŒ ํ•˜๋Š” ๊ฑฐ์ง€:

-- ํŠน์ • ์ปฌ๋Ÿผ๋งŒ ์กฐํšŒ ๊ฐ€๋Šฅํ•˜๊ฒŒ ์„ค์ •
GRANT SELECT (user_id, username, email, created_at) 
ON myapp_db.users 
TO 'app_reader'@'192.168.1.%';

-- ํŠน์ • ์ปฌ๋Ÿผ๋งŒ ์—…๋ฐ์ดํŠธ ๊ฐ€๋Šฅ (๋น„๋ฐ€๋ฒˆํ˜ธ, ์ด๋ฉ”์ผ๋งŒ ์ˆ˜์ • ๊ฐ€๋Šฅ)
GRANT UPDATE (password_hash, email, updated_at) 
ON myapp_db.users 
TO 'app_writer'@'192.168.1.%';

-- ๋ฏผ๊ฐ ์ปฌ๋Ÿผ(์ฃผ๋ฏผ๋ฒˆํ˜ธ, ์นด๋“œ๋ฒˆํ˜ธ ๋“ฑ)์€ ์•„์˜ˆ ์ ‘๊ทผ ๋ถˆ๊ฐ€
-- ssn, card_number ์ปฌ๋Ÿผ์€ ๊ถŒํ•œ ๋ชฉ๋ก์—์„œ ์ œ์™ธ!

๐Ÿ”„ ๊ถŒํ•œ ํšŒ์ˆ˜๋„ ์ค‘์š”ํ•ด!

-- ํŠน์ • ๊ถŒํ•œ ํšŒ์ˆ˜
REVOKE DELETE ON myapp_db.* FROM 'app_writer'@'192.168.1.%';

-- ๋ชจ๋“  ๊ถŒํ•œ ํšŒ์ˆ˜
REVOKE ALL PRIVILEGES ON myapp_db.* FROM 'old_user'@'localhost';

-- ๊ณ„์ • ์™„์ „ ์‚ญ์ œ
DROP USER 'old_user'@'localhost';

-- ๊ถŒํ•œ ๋ณ€๊ฒฝ ํ›„ ๋ฐ˜๋“œ์‹œ ์ ์šฉ
FLUSH PRIVILEGES;

โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ!

๊ถŒํ•œ ํšŒ์ˆ˜ํ•  ๋•Œ REVOKE ALL PRIVILEGES๋งŒ ํ•˜๊ณ  GRANT OPTION์€ ๋ณ„๋„๋กœ ํšŒ์ˆ˜ํ•ด์•ผ ํ•ด. ์•ˆ ๊ทธ๋Ÿฌ๋ฉด ๊ทธ ๊ณ„์ •์ด ๋‹ค๋ฅธ ๊ณ„์ •์— ๊ถŒํ•œ์„ ์ค„ ์ˆ˜ ์žˆ๋Š” ๋Šฅ๋ ฅ์ด ๋‚จ์•„์žˆ์„ ์ˆ˜ ์žˆ์–ด!

REVOKE ALL PRIVILEGES, GRANT OPTION FROM 'user'@'host';
DB ๊ถŒํ•œ ๊ณ„์ธต ๊ตฌ์กฐ ๋ฐ ์—ญํ• ๋ณ„ ๊ณ„์ • ๋ถ„๋ฆฌ ๐Ÿ—„๏ธ MySQL DB ์„œ๋ฒ„ root (๊ด€๋ฆฌ์ž ์ „์šฉ, ์•ฑ ์—ฐ๊ฒฐ ๊ธˆ์ง€) ๐Ÿ‘๏ธ app_reader SELECT only ์กฐํšŒ API ์ „์šฉ IP: 192.168.1.x โœ๏ธ app_writer SELECT, INSERT UPDATE (DELETE ์ œ์™ธ) ์ผ๋ฐ˜ ์•ฑ ์„œ๋น„์Šค โš™๏ธ batch_user ํŠน์ • ํ…Œ์ด๋ธ”๋งŒ SELECT, INSERT localhost๋งŒ ๐Ÿš€ deploy_user ALTER, CREATE DROP, INDEX ์ž„์‹œ ๊ณ„์ • (๋ฐฐํฌ ํ›„ ์‚ญ์ œ) ๐Ÿ” ๊ถŒํ•œ ๋ถ„๋ฆฌ์˜ ํ•ต์‹ฌ ์›์น™ 1 ์—ญํ• ๋ณ„ ๊ณ„์ • ๋ถ„๋ฆฌ ํ•˜๋‚˜์˜ ๊ณ„์ •์ด ๋ชจ๋“  ๊ฑธ ํ•˜๋ฉด ์•ˆ ๋จ 2 IP ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œํ•œ ํ—ˆ์šฉ๋œ IP์—์„œ๋งŒ DB ์ ‘์† ๊ฐ€๋Šฅ 3 ์ž„์‹œ ๊ณ„์ • ์ฆ‰์‹œ ์‚ญ์ œ ๋ฐฐํฌยท๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ›„ ๊ณ„์ • ์ œ๊ฑฐ 4 ์ •๊ธฐ์  ๊ถŒํ•œ ๊ฐ์‚ฌ ๋ถˆํ•„์š”ํ•œ ๊ถŒํ•œ ์ฃผ๊ธฐ์ ์œผ๋กœ ๊ฒ€ํ† ยทํšŒ์ˆ˜

๐Ÿ˜ PostgreSQL ๊ถŒํ•œ ๊ด€๋ฆฌ

PostgreSQL์€ MySQL์ด๋ž‘ ๊ถŒํ•œ ์ฒด๊ณ„๊ฐ€ ์ข€ ๋‹ฌ๋ผ. ๋” ์„ธ๋ฐ€ํ•˜๊ณ  ๊ฐ•๋ ฅํ•œ ๊ถŒํ•œ ๊ด€๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•œ๋ฐ, ์ด๊ฒŒ ๋˜ ๋ณต์žกํ•˜๊ธฐ๋„ ํ•ด ใ…‹ใ…‹ใ…‹ ์ฐจ๊ทผ์ฐจ๊ทผ ๋ณด์ž!

๐ŸŽญ Role ๊ธฐ๋ฐ˜ ๊ถŒํ•œ ๊ด€๋ฆฌ

PostgreSQL์€ Role ๊ฐœ๋…์„ ์‚ฌ์šฉํ•ด. User์™€ Group์ด ๋”ฐ๋กœ ์—†๊ณ  ๋‹ค Role์ด์•ผ. Role์— ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๊ณ , ์‚ฌ์šฉ์ž Role์— ๋‹ค๋ฅธ Role์„ ๋ถ€์—ฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด:

-- ์—ญํ• (Role) ์ƒ์„ฑ - ๊ถŒํ•œ ๋ฌถ์Œ
CREATE ROLE readonly_role;
CREATE ROLE readwrite_role;
CREATE ROLE admin_role;

-- readonly_role์— ๊ถŒํ•œ ๋ถ€์—ฌ
GRANT CONNECT ON DATABASE myapp_db TO readonly_role;
GRANT USAGE ON SCHEMA public TO readonly_role;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_role;
-- ์•ž์œผ๋กœ ์ƒ์„ฑ๋  ํ…Œ์ด๋ธ”์—๋„ ์ž๋™ ์ ์šฉ
ALTER DEFAULT PRIVILEGES IN SCHEMA public 
  GRANT SELECT ON TABLES TO readonly_role;

-- readwrite_role์— ๊ถŒํ•œ ๋ถ€์—ฌ
GRANT readonly_role TO readwrite_role; -- readonly ๊ถŒํ•œ ์ƒ์†
GRANT INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO readwrite_role;
ALTER DEFAULT PRIVILEGES IN SCHEMA public 
  GRANT INSERT, UPDATE ON TABLES TO readwrite_role;

-- ์‹ค์ œ ์‚ฌ์šฉ์ž ๊ณ„์ • ์ƒ์„ฑ ๋ฐ ์—ญํ•  ๋ถ€์—ฌ
CREATE USER app_service WITH PASSWORD '๊ฐ•๋ ฅํ•œํŒจ์Šค์›Œ๋“œ!@#';
GRANT readwrite_role TO app_service;

CREATE USER report_service WITH PASSWORD '๋‹ค๋ฅธํŒจ์Šค์›Œ๋“œ!@#';
GRANT readonly_role TO report_service;

-- ํŠน์ • ํ…Œ์ด๋ธ”๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ๊ณ„์ •
CREATE USER log_service WITH PASSWORD '๋กœ๊ทธํŒจ์Šค์›Œ๋“œ!@#';
GRANT CONNECT ON DATABASE myapp_db TO log_service;
GRANT USAGE ON SCHEMA public TO log_service;
GRANT SELECT, INSERT ON TABLE app_logs TO log_service;

๐Ÿ” Row Level Security (RLS) โ€” ํ–‰ ๋‹จ์œ„ ๋ณด์•ˆ

PostgreSQL์˜ ์ง„์งœ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ! ๊ฐ™์€ ํ…Œ์ด๋ธ”์ด๋ผ๋„ ํ–‰(Row) ๋‹จ์œ„๋กœ ์ ‘๊ทผ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์–ด. ๋ฉ€ํ‹ฐํ…Œ๋„ŒํŠธ ์•ฑ์—์„œ ์ง„์งœ ์œ ์šฉํ•จ:

-- RLS ํ™œ์„ฑํ™”
ALTER TABLE user_data ENABLE ROW LEVEL SECURITY;

-- ์ •์ฑ… ์ƒ์„ฑ: ์ž๊ธฐ ๋ฐ์ดํ„ฐ๋งŒ ๋ณผ ์ˆ˜ ์žˆ์Œ
CREATE POLICY user_isolation_policy ON user_data
  USING (user_id = current_user::integer);

-- ๊ด€๋ฆฌ์ž๋Š” ๋ชจ๋“  ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ๊ฐ€๋Šฅ
CREATE POLICY admin_all_policy ON user_data
  TO admin_role
  USING (true);

-- ํŠน์ • ์กฐ๊ฑด์˜ ๋ฐ์ดํ„ฐ๋งŒ INSERT ๊ฐ€๋Šฅ
CREATE POLICY insert_own_data ON user_data
  FOR INSERT
  WITH CHECK (user_id = current_user::integer);

โœ… RLS ํ™œ์šฉ ์˜ˆ์‹œ

SaaS ์„œ๋น„์Šค์—์„œ A ํšŒ์‚ฌ ๋ฐ์ดํ„ฐ์™€ B ํšŒ์‚ฌ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฐ™์€ ํ…Œ์ด๋ธ”์— ์žˆ์„ ๋•Œ, RLS๋ฅผ ์“ฐ๋ฉด A ํšŒ์‚ฌ ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•˜๋ฉด A ํšŒ์‚ฌ ๋ฐ์ดํ„ฐ๋งŒ ๋ณด์ด๊ณ , B ํšŒ์‚ฌ ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•˜๋ฉด B ํšŒ์‚ฌ ๋ฐ์ดํ„ฐ๋งŒ ๋ณด์—ฌ. ์ฝ”๋“œ ๋ ˆ๋ฒจ์—์„œ WHERE ์กฐ๊ฑด ๋น ๋œจ๋ ค๋„ DB ๋ ˆ๋ฒจ์—์„œ ์ž๋™์œผ๋กœ ํ•„ํ„ฐ๋งํ•ด์คŒ ๐Ÿ‘

๐Ÿ“Š ํ˜„์žฌ ๊ถŒํ•œ ํ™•์ธํ•˜๊ธฐ

-- ํ˜„์žฌ ์‚ฌ์šฉ์ž ๊ถŒํ•œ ํ™•์ธ
\dp  -- psql์—์„œ ํ…Œ์ด๋ธ” ๊ถŒํ•œ ๋ชฉ๋ก

-- ํŠน์ • ํ…Œ์ด๋ธ” ๊ถŒํ•œ ํ™•์ธ
SELECT grantee, privilege_type, is_grantable
FROM information_schema.role_table_grants
WHERE table_name = 'users';

-- ์—ญํ•  ๋ฉค๋ฒ„์‹ญ ํ™•์ธ
SELECT r.rolname, m.rolname as member
FROM pg_roles r
JOIN pg_auth_members am ON r.oid = am.roleid
JOIN pg_roles m ON am.member = m.oid
ORDER BY r.rolname;

๐Ÿ–ฅ๏ธ ์„œ๋ฒ„(Linux) ๊ถŒํ•œ ๊ด€๋ฆฌ

DB๋งŒ ์ž˜ ๊ด€๋ฆฌํ•œ๋‹ค๊ณ  ๋์ด ์•„๋‹ˆ์•ผ. ์„œ๋ฒ„ ์ž์ฒด์˜ ๊ถŒํ•œ ๊ด€๋ฆฌ๋„ ์—„์ฒญ ์ค‘์š”ํ•ด. Linux ์„œ๋ฒ„ ๊ธฐ์ค€์œผ๋กœ ์„ค๋ช…ํ• ๊ฒŒ ๐Ÿง

๐Ÿ‘ค ์‚ฌ์šฉ์ž ๊ณ„์ • ๋ถ„๋ฆฌ ์›์น™

๐ŸŽฏ ์„œ๋ฒ„ ๊ณ„์ • ์„ค๊ณ„ ์›์น™

1. root ๊ณ„์ • ์ง์ ‘ ์‚ฌ์šฉ ๊ธˆ์ง€ โ€” SSH root ๋กœ๊ทธ์ธ ๋น„ํ™œ์„ฑํ™”
2. ์„œ๋น„์Šค๋ณ„ ์ „์šฉ ๊ณ„์ • โ€” nginx๋Š” nginx ๊ณ„์ •, mysql์€ mysql ๊ณ„์ •
3. sudo ๊ถŒํ•œ ์ตœ์†Œํ™” โ€” ํ•„์š”ํ•œ ๋ช…๋ น์–ด๋งŒ sudo ํ—ˆ์šฉ
4. ๊ฐœ๋ฐœ์ž ๊ณ„์ • ๋ถ„๋ฆฌ โ€” ๊ฐœ์ธ ๊ณ„์ •์œผ๋กœ ์ ‘์†, ๊ณต์œ  ๊ณ„์ • ๊ธˆ์ง€

# ์„œ๋น„์Šค ์ „์šฉ ๊ณ„์ • ์ƒ์„ฑ (๋กœ๊ทธ์ธ ๋ถˆ๊ฐ€ ๊ณ„์ •)
sudo useradd -r -s /bin/false -d /var/lib/myapp myapp_service
# -r: ์‹œ์Šคํ…œ ๊ณ„์ •
# -s /bin/false: ์‰˜ ๋กœ๊ทธ์ธ ๋ถˆ๊ฐ€
# -d: ํ™ˆ ๋””๋ ‰ํ† ๋ฆฌ ์ง€์ •

# ๊ฐœ๋ฐœ์ž ๊ณ„์ • ์ƒ์„ฑ
sudo useradd -m -s /bin/bash developer1
sudo passwd developer1

# ๊ฐœ๋ฐœ์ž๋ฅผ ํŠน์ • ๊ทธ๋ฃน์— ์ถ”๊ฐ€ (sudo ๊ทธ๋ฃน X, ํ•„์š”ํ•œ ๊ทธ๋ฃน๋งŒ)
sudo usermod -aG docker developer1
sudo usermod -aG www-data developer1

# SSH root ๋กœ๊ทธ์ธ ๋น„ํ™œ์„ฑํ™”
sudo nano /etc/ssh/sshd_config
# PermitRootLogin no
# PasswordAuthentication no  (ํ‚ค ๊ธฐ๋ฐ˜ ์ธ์ฆ๋งŒ ํ—ˆ์šฉ)
sudo systemctl restart sshd

๐Ÿ”ง sudo ๊ถŒํ•œ ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ œ์–ดํ•˜๊ธฐ

sudo๋ฅผ ๊ทธ๋ƒฅ ์ฃผ๋ฉด ์‚ฌ์‹ค์ƒ root ๊ถŒํ•œ์ด๋‚˜ ๋งˆ์ฐฌ๊ฐ€์ง€์•ผ. sudoers ํŒŒ์ผ์„ ํ†ตํ•ด ํŠน์ • ๋ช…๋ น์–ด๋งŒ ํ—ˆ์šฉํ•  ์ˆ˜ ์žˆ์–ด:

# /etc/sudoers.d/developer_rules ํŒŒ์ผ ์ƒ์„ฑ
# (visudo ๋ช…๋ น์–ด๋กœ ํŽธ์ง‘ํ•˜๋Š” ๊ฒŒ ์•ˆ์ „ํ•จ)

# developer1์€ nginx ์žฌ์‹œ์ž‘๋งŒ ๊ฐ€๋Šฅ
developer1 ALL=(ALL) NOPASSWD: /bin/systemctl restart nginx
developer1 ALL=(ALL) NOPASSWD: /bin/systemctl reload nginx

# deploy_team ๊ทธ๋ฃน์€ ๋ฐฐํฌ ์Šคํฌ๋ฆฝํŠธ๋งŒ ์‹คํ–‰ ๊ฐ€๋Šฅ
%deploy_team ALL=(ALL) NOPASSWD: /opt/scripts/deploy.sh

# ํŠน์ • ์‚ฌ์šฉ์ž๋Š” ๋กœ๊ทธ ํŒŒ์ผ๋งŒ ๋ณผ ์ˆ˜ ์žˆ์Œ
logviewer ALL=(ALL) NOPASSWD: /bin/cat /var/log/nginx/*.log
logviewer ALL=(ALL) NOPASSWD: /usr/bin/tail -f /var/log/nginx/access.log

# ์ ˆ๋Œ€ ์•ˆ ๋˜๋Š” ๊ฒƒ๋“ค ๋ช…์‹œ์  ๊ฑฐ๋ถ€
developer1 ALL=(ALL) !ALL  # ์œ„์—์„œ ํ—ˆ์šฉํ•œ ๊ฒƒ ์™ธ์—” ์ „๋ถ€ ๊ฑฐ๋ถ€

๐Ÿ“ ํŒŒ์ผ ์‹œ์Šคํ…œ ๊ถŒํ•œ ๊ด€๋ฆฌ

Linux์˜ ํŒŒ์ผ ๊ถŒํ•œ(chmod, chown)๋„ ์ตœ์†Œ ๊ถŒํ•œ ์›์น™์„ ์ ์šฉํ•ด์•ผ ํ•ด:

# ์›น ์„œ๋ฒ„ ํŒŒ์ผ ๊ถŒํ•œ ์„ค์ •
# ์†Œ์œ ์ž: root, ๊ทธ๋ฃน: www-data
sudo chown -R root:www-data /var/www/myapp

# ๋””๋ ‰ํ† ๋ฆฌ: 755 (์†Œ์œ ์ž rwx, ๊ทธ๋ฃน r-x, ๊ธฐํƒ€ r-x)
sudo find /var/www/myapp -type d -exec chmod 755 {} \;

# ํŒŒ์ผ: 644 (์†Œ์œ ์ž rw-, ๊ทธ๋ฃน r--, ๊ธฐํƒ€ r--)
sudo find /var/www/myapp -type f -exec chmod 644 {} \;

# ์„ค์ • ํŒŒ์ผ (DB ๋น„๋ฐ€๋ฒˆํ˜ธ ๋“ฑ): 600 (์†Œ์œ ์ž๋งŒ ์ฝ๊ธฐ/์“ฐ๊ธฐ)
sudo chmod 600 /var/www/myapp/.env
sudo chown myapp_service:myapp_service /var/www/myapp/.env

# ์—…๋กœ๋“œ ๋””๋ ‰ํ† ๋ฆฌ: 775 (๊ทธ๋ฃน ์“ฐ๊ธฐ ํ—ˆ์šฉ)
sudo chmod 775 /var/www/myapp/uploads
sudo chown myapp_service:www-data /var/www/myapp/uploads

# SUID/SGID ๋น„ํŠธ ํ™•์ธ (๋ณด์•ˆ ์ทจ์•ฝ์  ๊ฐ€๋Šฅ์„ฑ)
sudo find / -perm /4000 -type f 2>/dev/null  # SUID ํŒŒ์ผ ์ฐพ๊ธฐ
sudo find / -perm /2000 -type f 2>/dev/null  # SGID ํŒŒ์ผ ์ฐพ๊ธฐ

โš ๏ธ ํ”ํ•œ ์‹ค์ˆ˜๋“ค!

๐Ÿšซ chmod 777 โ€” "์ผ๋‹จ ๋˜๊ฒŒ ํ•˜์ž"๋Š” ๋งˆ์ธ๋“œ๋กœ 777 ์ฃผ๋Š” ๊ฑฐ ์ง„์งœ ์œ„ํ—˜ํ•ด. ๋ชจ๋“  ์‚ฌ์šฉ์ž๊ฐ€ ์ฝ๊ณ  ์“ฐ๊ณ  ์‹คํ–‰ ๊ฐ€๋Šฅํ•ด์ง
๐Ÿšซ chown -R www-data:www-data /var/www โ€” ์›น ์„œ๋ฒ„ ํ”„๋กœ์„ธ์Šค๊ฐ€ ํŒŒ์ผ์„ ์†Œ์œ ํ•˜๋ฉด ํ•ดํ‚น ์‹œ ํŒŒ์ผ ์ˆ˜์ • ๊ฐ€๋Šฅ
๐Ÿšซ ์„ค์ • ํŒŒ์ผ์— 644 ๊ถŒํ•œ โ€” DB ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๋‹ด๊ธด .env ํŒŒ์ผ์€ ๋ฐ˜๋“œ์‹œ 600์ด์–ด์•ผ ํ•ด!

๐Ÿง Linux ์„œ๋ฒ„ ๊ถŒํ•œ ๊ณ„์ธต ๊ตฌ์กฐ ๐Ÿ“ ํŒŒ์ผ ๊ถŒํ•œ ์ฒด๊ณ„ (chmod) -rwxr-xr-x ํŒŒ์ผ ์œ ํ˜• ์†Œ์œ ์ž ๊ทธ๋ฃน ๊ธฐํƒ€ r = ์ฝ๊ธฐ(4) w = ์“ฐ๊ธฐ(2) x = ์‹คํ–‰(1) 644 = rw-r--r-- (์ผ๋ฐ˜ ํŒŒ์ผ ๊ถŒ์žฅ) 755 = rwxr-xr-x (๋””๋ ‰ํ† ๋ฆฌ ๊ถŒ์žฅ) 600 = rw------- (์„ค์ • ํŒŒ์ผ ํ•„์ˆ˜!) ๐Ÿ‘ฅ ์„œ๋ฒ„ ๊ณ„์ • ๊ณ„์ธต ๐Ÿ”ด root (์ง์ ‘ ์‚ฌ์šฉ ๊ธˆ์ง€) ๐ŸŸก sudo ๊ทธ๋ฃน (์ œํ•œ์  ํ—ˆ์šฉ) ๐ŸŸข ๊ฐœ๋ฐœ์ž ๊ณ„์ • ๐Ÿ”ต ์„œ๋น„์Šค ๊ณ„์ • ๐ŸŸฃ ๋ฐฐ์น˜ ๊ณ„์ • ๐Ÿ›ก๏ธ ์„œ๋ฒ„ ๋ณด์•ˆ ํ•ต์‹ฌ ์ฒดํฌ๋ฆฌ์ŠคํŠธ โœ… SSH root ๋กœ๊ทธ์ธ ๋น„ํ™œ์„ฑํ™” โœ… ํŒจ์Šค์›Œ๋“œ ์ธ์ฆ ๋น„ํ™œ์„ฑํ™” (ํ‚ค ๊ธฐ๋ฐ˜๋งŒ) โœ… ์„œ๋น„์Šค๋ณ„ ์ „์šฉ ๊ณ„์ • ์‚ฌ์šฉ โœ… sudo ๋ช…๋ น์–ด ์ตœ์†Œํ™” โœ… ์„ค์ • ํŒŒ์ผ ๊ถŒํ•œ 600 ์„ค์ • โœ… ๋ถˆํ•„์š”ํ•œ ๊ณ„์ • ์ฆ‰์‹œ ์‚ญ์ œ โœ… ๋กœ๊ทธ์ธ ์‹คํŒจ ์•Œ๋ฆผ ์„ค์ • โœ… ์ •๊ธฐ์  ๊ถŒํ•œ ๊ฐ์‚ฌ ์‹ค์‹œ

๐ŸŽญ RBAC โ€” ์—ญํ•  ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œ์–ด

์ž, ์ด์ œ ์ข€ ๋” ์ฒด๊ณ„์ ์ธ ๊ถŒํ•œ ๊ด€๋ฆฌ ๋ฐฉ๋ฒ•๋ก ์ธ RBAC(Role-Based Access Control)์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž. ํ˜„๋Œ€ ์‹œ์Šคํ…œ์—์„œ ๊ฐ€์žฅ ๋„๋ฆฌ ์“ฐ์ด๋Š” ์ ‘๊ทผ ์ œ์–ด ๋ชจ๋ธ์ด์•ผ ๐ŸŽฏ

๐Ÿ“š RBAC์˜ ํ•ต์‹ฌ ๊ฐœ๋…

RBAC๋Š” ์‚ฌ์šฉ์ž โ†’ ์—ญํ•  โ†’ ๊ถŒํ•œ์˜ 3๋‹จ๊ณ„ ๊ตฌ์กฐ์•ผ. ์‚ฌ์šฉ์ž์—๊ฒŒ ์ง์ ‘ ๊ถŒํ•œ์„ ์ฃผ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ์—ญํ• ์— ๊ถŒํ•œ์„ ๋ฌถ๊ณ  ์‚ฌ์šฉ์ž์—๊ฒŒ ์—ญํ• ์„ ๋ถ€์—ฌํ•˜๋Š” ๋ฐฉ์‹์ด์ง€:

๊ตฌ์„ฑ ์š”์†Œ ์„ค๋ช… ์˜ˆ์‹œ
User (์‚ฌ์šฉ์ž) ์‹ค์ œ ์‚ฌ๋žŒ ๋˜๋Š” ์„œ๋น„์Šค ๊ณ„์ • developer1, app_service
Role (์—ญํ• ) ๊ถŒํ•œ์˜ ๋ฌถ์Œ, ์ง์ฑ…/์—ญํ•  ๋‹จ์œ„ admin, developer, viewer
Permission (๊ถŒํ•œ) ํŠน์ • ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ์ž‘์—… ํ—ˆ๊ฐ€ users:read, orders:write
Resource (๋ฆฌ์†Œ์Šค) ์ ‘๊ทผ ๋Œ€์ƒ DB ํ…Œ์ด๋ธ”, API ์—”๋“œํฌ์ธํŠธ, ํŒŒ์ผ

๐Ÿ—๏ธ RBAC ์„ค๊ณ„ ์˜ˆ์‹œ โ€” ์‡ผํ•‘๋ชฐ ์‹œ์Šคํ…œ

-- ์—ญํ•  ์ •์˜
CREATE ROLE viewer;      -- ์กฐํšŒ๋งŒ ๊ฐ€๋Šฅ
CREATE ROLE operator;    -- ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
CREATE ROLE manager;     -- ์ƒํ’ˆ ๊ด€๋ฆฌ ๊ฐ€๋Šฅ
CREATE ROLE admin;       -- ์ „์ฒด ๊ด€๋ฆฌ

-- viewer ๊ถŒํ•œ
GRANT SELECT ON products, categories TO viewer;
GRANT SELECT ON orders TO viewer;

-- operator ๊ถŒํ•œ (viewer ์ƒ์†)
GRANT viewer TO operator;
GRANT UPDATE (status, updated_at) ON orders TO operator;
GRANT INSERT ON order_logs TO operator;

-- manager ๊ถŒํ•œ (operator ์ƒ์†)
GRANT operator TO manager;
GRANT SELECT, INSERT, UPDATE ON products TO manager;
GRANT SELECT, INSERT, UPDATE ON categories TO manager;
GRANT SELECT ON users TO manager;  -- ๊ณ ๊ฐ ์ •๋ณด ์กฐํšŒ๋งŒ

-- admin ๊ถŒํ•œ (manager ์ƒ์†)
GRANT manager TO admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO admin;

-- ์‹ค์ œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์—ญํ•  ๋ถ€์—ฌ
CREATE USER customer_service_1 WITH PASSWORD '...';
GRANT operator TO customer_service_1;

CREATE USER product_manager_1 WITH PASSWORD '...';
GRANT manager TO product_manager_1;

๐Ÿ”„ RBAC vs ABAC vs DAC

๊ถŒํ•œ ๊ด€๋ฆฌ ๋ชจ๋ธ์ด RBAC๋งŒ ์žˆ๋Š” ๊ฑด ์•„๋‹ˆ์•ผ. ๋น„๊ตํ•ด๋ณด์ž:

๋ชจ๋ธ ํŠน์ง• ์ ํ•ฉํ•œ ์ƒํ™ฉ ๋ณต์žก๋„
DAC
(์ž„์˜์  ์ ‘๊ทผ ์ œ์–ด)
์†Œ์œ ์ž๊ฐ€ ์ง์ ‘ ๊ถŒํ•œ ์„ค์ • ์†Œ๊ทœ๋ชจ, ๋‹จ์ˆœ ์‹œ์Šคํ…œ ๋‚ฎ์Œ
RBAC
(์—ญํ•  ๊ธฐ๋ฐ˜)
์—ญํ•  ๋‹จ์œ„๋กœ ๊ถŒํ•œ ๊ด€๋ฆฌ ๋Œ€๋ถ€๋ถ„์˜ ๊ธฐ์—… ์‹œ์Šคํ…œ ์ค‘๊ฐ„
ABAC
(์†์„ฑ ๊ธฐ๋ฐ˜)
์†์„ฑ(์‹œ๊ฐ„, ์œ„์น˜, ๋ถ€์„œ ๋“ฑ) ๊ธฐ๋ฐ˜ ๋ณต์žกํ•œ ์กฐ๊ฑด๋ถ€ ์ ‘๊ทผ ์ œ์–ด ๋†’์Œ
MAC
(๊ฐ•์ œ์  ์ ‘๊ทผ ์ œ์–ด)
์‹œ์Šคํ…œ์ด ๊ฐ•์ œ๋กœ ์ •์ฑ… ์ ์šฉ ๊ตฐ์‚ฌ, ์ •๋ถ€ ์‹œ์Šคํ…œ ๋งค์šฐ ๋†’์Œ

์ผ๋ฐ˜์ ์ธ ์›น ์„œ๋น„์Šค๋‚˜ ์•ฑ ๊ฐœ๋ฐœ์—์„œ๋Š” RBAC๊ฐ€ ๊ฐ€์žฅ ์‹ค์šฉ์ ์ด์•ผ. ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๊ณ  ๊ด€๋ฆฌํ•˜๊ธฐ ํŽธํ•˜๊ฑฐ๋“  ๐Ÿ‘

โšก ์‹ค์ „ ๋ณด์•ˆ ํŒจํ„ด โ€” ์ด๋ ‡๊ฒŒ ๊ตฌํ˜„ํ•ด!

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ดค์œผ๋‹ˆ ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š”์ง€ ํŒจํ„ด๋ณ„๋กœ ๋ณด์ž. ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์„ ๊ฐœ๋ฐœํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ  ์˜ˆ์‹œ๋ฅผ ๋“ค์–ด๋ณผ๊ฒŒ ๐Ÿ˜Ž

๐Ÿ”‘ ํŒจํ„ด 1: ํ™˜๊ฒฝ๋ณ„ DB ๊ณ„์ • ๋ถ„๋ฆฌ

# .env.production
DB_HOST=prod-db.internal
DB_PORT=3306
DB_NAME=myapp_prod
DB_USER=app_prod_writer    # ํ”„๋กœ๋•์…˜ ์ „์šฉ ๊ณ„์ •
DB_PASSWORD=๋ณต์žกํ•œํŒจ์Šค์›Œ๋“œ!@#$

# .env.development  
DB_HOST=localhost
DB_PORT=3306
DB_NAME=myapp_dev
DB_USER=app_dev_user       # ๊ฐœ๋ฐœ ์ „์šฉ ๊ณ„์ • (๋” ๋งŽ์€ ๊ถŒํ•œ ํ—ˆ์šฉ)
DB_PASSWORD=๊ฐœ๋ฐœ์šฉํŒจ์Šค์›Œ๋“œ

# .env.test
DB_HOST=localhost
DB_PORT=3306
DB_NAME=myapp_test
DB_USER=app_test_user      # ํ…Œ์ŠคํŠธ ์ „์šฉ ๊ณ„์ •
DB_PASSWORD=ํ…Œ์ŠคํŠธํŒจ์Šค์›Œ๋“œ

๐Ÿ’ก ํ•ต์‹ฌ! ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์“ฐ๋˜ ๊ณ„์ •์„ ํ”„๋กœ๋•์…˜์— ๊ทธ๋Œ€๋กœ ์“ฐ๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์€๋ฐ, ์ด๊ฑฐ ์ง„์งœ ์œ„ํ—˜ํ•ด. ํ™˜๊ฒฝ๋ณ„๋กœ ๊ณ„์ •์„ ๋ถ„๋ฆฌํ•˜๊ณ  ํ”„๋กœ๋•์…˜ ๊ณ„์ •์€ ์ตœ์†Œ ๊ถŒํ•œ๋งŒ ์ค˜์•ผ ํ•ด!

๐Ÿ”‘ ํŒจํ„ด 2: ์ฝ๊ธฐ/์“ฐ๊ธฐ DB ๋ถ„๋ฆฌ (Read Replica)

// Node.js ์˜ˆ์‹œ - ์ฝ๊ธฐ/์“ฐ๊ธฐ DB ๋ถ„๋ฆฌ
const mysql = require('mysql2/promise');

// ์“ฐ๊ธฐ ์ „์šฉ ์—ฐ๊ฒฐ (Master DB)
const writePool = mysql.createPool({
  host: process.env.DB_WRITE_HOST,
  user: process.env.DB_WRITE_USER,    // INSERT, UPDATE ๊ถŒํ•œ๋งŒ
  password: process.env.DB_WRITE_PASS,
  database: process.env.DB_NAME,
});

// ์ฝ๊ธฐ ์ „์šฉ ์—ฐ๊ฒฐ (Read Replica)
const readPool = mysql.createPool({
  host: process.env.DB_READ_HOST,
  user: process.env.DB_READ_USER,     // SELECT ๊ถŒํ•œ๋งŒ
  password: process.env.DB_READ_PASS,
  database: process.env.DB_NAME,
});

// ์‚ฌ์šฉ ์˜ˆ์‹œ
async function getUser(userId) {
  // ์กฐํšŒ๋Š” ์ฝ๊ธฐ ์ „์šฉ DB ์‚ฌ์šฉ
  const [rows] = await readPool.execute(
    'SELECT id, username, email FROM users WHERE id = ?',
    [userId]
  );
  return rows[0];
}

async function updateUser(userId, data) {
  // ์ˆ˜์ •์€ ์“ฐ๊ธฐ DB ์‚ฌ์šฉ
  await writePool.execute(
    'UPDATE users SET email = ? WHERE id = ?',
    [data.email, userId]
  );
}

๐Ÿ”‘ ํŒจํ„ด 3: ์ž„์‹œ ๊ถŒํ•œ ์ƒ์Šน (Just-In-Time Access)

ํ‰์†Œ์—” ์ตœ์†Œ ๊ถŒํ•œ์œผ๋กœ ์šด์˜ํ•˜๋‹ค๊ฐ€ ํ•„์š”ํ•  ๋•Œ๋งŒ ์ž„์‹œ๋กœ ๊ถŒํ•œ์„ ์˜ฌ๋ฆฌ๋Š” ํŒจํ„ด์ด์•ผ. ํด๋ผ์šฐ๋“œ ํ™˜๊ฒฝ์—์„œ ํŠนํžˆ ์œ ์šฉํ•ด:

# AWS IAM ์˜ˆ์‹œ - ์ž„์‹œ ์ž๊ฒฉ์ฆ๋ช… ๋ฐœ๊ธ‰
# ํ‰์†Œ์—” ์ฝ๊ธฐ ๊ถŒํ•œ๋งŒ ์žˆ๋Š” ์—ญํ•  ์‚ฌ์šฉ
# ๋ฐฐํฌ ์‹œ์—๋งŒ ์ž„์‹œ๋กœ ์“ฐ๊ธฐ ๊ถŒํ•œ ์—ญํ•  assume

# ์ž„์‹œ ์ž๊ฒฉ์ฆ๋ช… ๋ฐœ๊ธ‰ (1์‹œ๊ฐ„ ์œ ํšจ)
aws sts assume-role \
  --role-arn "arn:aws:iam::123456789:role/DeployRole" \
  --role-session-name "deploy-session-$(date +%Y%m%d%H%M%S)" \
  --duration-seconds 3600

# ๋ฐœ๊ธ‰๋œ ์ž„์‹œ ์ž๊ฒฉ์ฆ๋ช…์œผ๋กœ ๋ฐฐํฌ ์ž‘์—… ์ˆ˜ํ–‰
export AWS_ACCESS_KEY_ID=์ž„์‹œํ‚ค
export AWS_SECRET_ACCESS_KEY=์ž„์‹œ์‹œํฌ๋ฆฟ
export AWS_SESSION_TOKEN=์ž„์‹œํ† ํฐ

# ๋ฐฐํฌ ์™„๋ฃŒ ํ›„ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ œ๊ฑฐ
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN

๐Ÿ”‘ ํŒจํ„ด 4: ์„œ๋น„์Šค ๊ณ„์ • ํ† ํฐ ๊ด€๋ฆฌ

// ์„œ๋น„์Šค ๊ฐ„ ํ†ต์‹  ์‹œ ์ตœ์†Œ ๊ถŒํ•œ ํ† ํฐ ์‚ฌ์šฉ
// JWT ํ† ํฐ์— ๊ถŒํ•œ ๋ฒ”์œ„(scope) ๋ช…์‹œ

const jwt = require('jsonwebtoken');

// ์„œ๋น„์Šค A๊ฐ€ ์„œ๋น„์Šค B์— ์š”์ฒญํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ํ† ํฐ
function generateServiceToken(serviceId, allowedScopes) {
  return jwt.sign(
    {
      sub: serviceId,
      scopes: allowedScopes,  // ํ—ˆ์šฉ๋œ ์ž‘์—…๋งŒ ๋ช…์‹œ
      iat: Math.floor(Date.now() / 1000),
      exp: Math.floor(Date.now() / 1000) + 300, // 5๋ถ„๋งŒ ์œ ํšจ
    },
    process.env.SERVICE_SECRET
  );
}

// ์‚ฌ์šฉ ์˜ˆ์‹œ
const token = generateServiceToken('order-service', [
  'users:read',        // ์‚ฌ์šฉ์ž ์กฐํšŒ๋งŒ
  'inventory:read',    // ์žฌ๊ณ  ์กฐํšŒ๋งŒ
  // 'users:write' ๋Š” ํฌํ•จ ์•ˆ ํ•จ!
]);

// ์„œ๋น„์Šค B์—์„œ ๊ถŒํ•œ ๊ฒ€์ฆ
function checkScope(token, requiredScope) {
  const decoded = jwt.verify(token, process.env.SERVICE_SECRET);
  if (!decoded.scopes.includes(requiredScope)) {
    throw new Error(`๊ถŒํ•œ ์—†์Œ: ${requiredScope} ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค`);
  }
  return decoded;
}

๐Ÿ“Š ๊ถŒํ•œ ๊ฐ์‚ฌ(Audit)์™€ ๋ชจ๋‹ˆํ„ฐ๋ง

๊ถŒํ•œ์„ ์ž˜ ์„ค์ •ํ–ˆ๋‹ค๊ณ  ๋์ด ์•„๋‹ˆ์•ผ. ์ง€์†์ ์ธ ๊ฐ์‚ฌ์™€ ๋ชจ๋‹ˆํ„ฐ๋ง์ด ์—†์œผ๋ฉด ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด์„œ ๊ถŒํ•œ์ด ์ ์  ๋Š˜์–ด๋‚˜๋Š” "๊ถŒํ•œ ํฌ๋ฆฌํ”„(Permission Creep)" ํ˜„์ƒ์ด ๋ฐœ์ƒํ•ด ๐Ÿ˜ฑ

๐Ÿ” MySQL ๊ฐ์‚ฌ ๋กœ๊ทธ ์„ค์ •

-- MySQL ์ผ๋ฐ˜ ์ฟผ๋ฆฌ ๋กœ๊ทธ ํ™œ์„ฑํ™” (๊ฐœ๋ฐœ/๊ฐ์‚ฌ ๋ชฉ์ )
SET GLOBAL general_log = 'ON';
SET GLOBAL general_log_file = '/var/log/mysql/general.log';

-- ๋А๋ฆฐ ์ฟผ๋ฆฌ ๋กœ๊ทธ (์„ฑ๋Šฅ + ๋ณด์•ˆ ์ด์ƒ ํƒ์ง€)
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 2;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';

-- MySQL Enterprise Audit Plugin (์ƒ์šฉ)
-- ๋˜๋Š” MariaDB Audit Plugin (๋ฌด๋ฃŒ)
INSTALL PLUGIN server_audit SONAME 'server_audit.so';
SET GLOBAL server_audit_logging = ON;
SET GLOBAL server_audit_events = 'CONNECT,QUERY,TABLE';
SET GLOBAL server_audit_file_path = '/var/log/mysql/audit.log';

-- ํŠน์ • ์‚ฌ์šฉ์ž ํ™œ๋™๋งŒ ๊ฐ์‚ฌ
SET GLOBAL server_audit_incl_users = 'app_writer,admin_user';

๐Ÿ” ๊ถŒํ•œ ํฌ๋ฆฌํ”„ ํƒ์ง€ ์ฟผ๋ฆฌ

-- ๊ณผ๋„ํ•œ ๊ถŒํ•œ์„ ๊ฐ€์ง„ ๊ณ„์ • ์ฐพ๊ธฐ
SELECT user, host, 
       Select_priv, Insert_priv, Update_priv, 
       Delete_priv, Create_priv, Drop_priv,
       Grant_priv, Super_priv
FROM mysql.user
WHERE Super_priv = 'Y' OR Grant_priv = 'Y'
ORDER BY user;

-- ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ๊ณ„์ • ์ฐพ๊ธฐ (90์ผ ์ด์ƒ ๋ฏธ์ ‘์†)
SELECT user, host, password_last_changed,
       DATEDIFF(NOW(), password_last_changed) as days_since_change
FROM mysql.user
WHERE password_last_changed < DATE_SUB(NOW(), INTERVAL 90 DAY)
ORDER BY days_since_change DESC;

-- ํŠน์ • DB์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๊ณ„์ • ๋ชฉ๋ก
SELECT user, host, db, 
       Select_priv, Insert_priv, Update_priv, Delete_priv
FROM mysql.db
WHERE db = 'myapp_db'
ORDER BY user;

๐Ÿ“‹ ์ •๊ธฐ ๊ถŒํ•œ ๊ฐ์‚ฌ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

๐Ÿ“… ์›”๊ฐ„ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

โ˜ ํ‡ด์ง/์ด์งํ•œ ์ง์› ๊ณ„์ • ๋น„ํ™œ์„ฑํ™”/์‚ญ์ œ ํ™•์ธ
โ˜ ์ž„์‹œ ๊ณ„์ •(๋ฐฐํฌ, ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์šฉ) ์‚ญ์ œ ํ™•์ธ
โ˜ ๊ฐ ๊ณ„์ •์˜ ์‹ค์ œ ์‚ฌ์šฉ ๊ถŒํ•œ vs ๋ถ€์—ฌ๋œ ๊ถŒํ•œ ๋น„๊ต
โ˜ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋งŒ๋ฃŒ ์ •์ฑ… ์ค€์ˆ˜ ์—ฌ๋ถ€ ํ™•์ธ
โ˜ ๋น„์ •์ƒ์ ์ธ ์ ‘์† ํŒจํ„ด (์‹œ๊ฐ„๋Œ€, IP) ๊ฒ€ํ† 

๐Ÿ“… ๋ถ„๊ธฐ๋ณ„ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

โ˜ ์ „์ฒด ๊ณ„์ • ๋ชฉ๋ก ๊ฒ€ํ†  ๋ฐ ๋ถˆํ•„์š”ํ•œ ๊ณ„์ • ์ •๋ฆฌ
โ˜ ์—ญํ• (Role) ์ •์˜ ์žฌ๊ฒ€ํ†  โ€” ์—…๋ฌด ๋ณ€๊ฒฝ ๋ฐ˜์˜
โ˜ ์„œ๋น„์Šค ๊ณ„์ • ๋น„๋ฐ€๋ฒˆํ˜ธ ๋กœํ…Œ์ด์…˜
โ˜ ๊ฐ์‚ฌ ๋กœ๊ทธ ๋ถ„์„ โ€” ์ด์ƒ ์ ‘๊ทผ ํŒจํ„ด ํƒ์ง€
โ˜ ๋ณด์•ˆ ์ •์ฑ… ์—…๋ฐ์ดํŠธ ๋ฐ ํŒ€ ๊ณต์œ 

๐Ÿšจ ์ด์ƒ ํƒ์ง€ ์ž๋™ํ™”

#!/bin/bash
# ๋น„์ •์ƒ DB ์ ‘๊ทผ ํƒ์ง€ ์Šคํฌ๋ฆฝํŠธ

# ์—…๋ฌด ์‹œ๊ฐ„ ์™ธ ์ ‘์† ํƒ์ง€ (22์‹œ ~ 06์‹œ)
SUSPICIOUS_LOGINS=$(mysql -u monitor_user -p"$MONITOR_PASS" -e "
  SELECT user, host, time, command
  FROM information_schema.processlist
  WHERE HOUR(NOW()) >= 22 OR HOUR(NOW()) <= 6
  AND user NOT IN ('root', 'monitor_user')
" 2>/dev/null)

if [ -n "$SUSPICIOUS_LOGINS" ]; then
  echo "โš ๏ธ ์—…๋ฌด ์‹œ๊ฐ„ ์™ธ DB ์ ‘์† ๊ฐ์ง€!" | \
  mail -s "[๋ณด์•ˆ ์•Œ๋ฆผ] ๋น„์ •์ƒ DB ์ ‘์†" security@company.com
fi

# ๋Œ€๋Ÿ‰ ์ฟผ๋ฆฌ ํƒ์ง€ (1๋ถ„์— 1000๊ฑด ์ด์ƒ)
HEAVY_QUERIES=$(mysql -u monitor_user -p"$MONITOR_PASS" -e "
  SELECT user, COUNT(*) as query_count
  FROM mysql.general_log
  WHERE event_time > DATE_SUB(NOW(), INTERVAL 1 MINUTE)
  GROUP BY user
  HAVING query_count > 1000
" 2>/dev/null)

if [ -n "$HEAVY_QUERIES" ]; then
  echo "โš ๏ธ ๋Œ€๋Ÿ‰ ์ฟผ๋ฆฌ ๊ฐ์ง€: $HEAVY_QUERIES" | \
  mail -s "[๋ณด์•ˆ ์•Œ๋ฆผ] ๋Œ€๋Ÿ‰ ์ฟผ๋ฆฌ ๊ฐ์ง€" security@company.com
fi

โ˜๏ธ ํด๋ผ์šฐ๋“œ ํ™˜๊ฒฝ์—์„œ์˜ ๊ถŒํ•œ ๊ด€๋ฆฌ

์š”์ฆ˜์€ ์˜จํ”„๋ ˆ๋ฏธ์Šค ์„œ๋ฒ„๋ณด๋‹ค AWS, GCP, Azure ๊ฐ™์€ ํด๋ผ์šฐ๋“œ๋ฅผ ๋งŽ์ด ์“ฐ์ž–์•„. ํด๋ผ์šฐ๋“œ ํ™˜๊ฒฝ์—์„œ์˜ ๊ถŒํ•œ ๊ด€๋ฆฌ๋Š” ๋˜ ๋‹ค๋ฅธ ์ฐจ์›์ด์•ผ ๐ŸŒ

๐Ÿ” AWS IAM ์ตœ์†Œ ๊ถŒํ•œ ์›์น™

AWS์—์„œ IAM(Identity and Access Management)์€ ํด๋ผ์šฐ๋“œ ๊ถŒํ•œ ๊ด€๋ฆฌ์˜ ํ•ต์‹ฌ์ด์•ผ:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowSpecificS3Actions",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": "arn:aws:s3:::myapp-uploads/*",
      "Condition": {
        "StringEquals": {
          "s3:prefix": ["uploads/user-content/"]
        }
      }
    },
    {
      "Sid": "AllowRDSConnect",
      "Effect": "Allow",
      "Action": [
        "rds-db:connect"
      ],
      "Resource": "arn:aws:rds-db:ap-northeast-2:123456789:dbuser:db-ABCDEFG/app_writer"
    },
    {
      "Sid": "DenyDangerousActions",
      "Effect": "Deny",
      "Action": [
        "s3:DeleteBucket",
        "rds:DeleteDBInstance",
        "ec2:TerminateInstances"
      ],
      "Resource": "*"
    }
  ]
}

๐Ÿ’ก AWS IAM ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

๐Ÿ”น IAM ์‚ฌ์šฉ์ž ๋Œ€์‹  IAM ์—ญํ•  ์‚ฌ์šฉ โ€” EC2, Lambda ๋“ฑ ์„œ๋น„์Šค์—๋Š” ์—ญํ•  ๋ถ€์—ฌ
๐Ÿ”น MFA ํ•„์ˆ˜ ์„ค์ • โ€” ์ฝ˜์†” ์ ‘๊ทผ ์‹œ ๋‹ค์ค‘ ์ธ์ฆ ํ•„์ˆ˜
๐Ÿ”น Access Key ์ฃผ๊ธฐ์  ๋กœํ…Œ์ด์…˜ โ€” 90์ผ๋งˆ๋‹ค ๊ฐฑ์‹ 
๐Ÿ”น CloudTrail ํ™œ์„ฑํ™” โ€” ๋ชจ๋“  API ํ˜ธ์ถœ ๋กœ๊น…
๐Ÿ”น AWS Config ๊ทœ์น™ โ€” ์ •์ฑ… ์œ„๋ฐ˜ ์ž๋™ ํƒ์ง€

๐Ÿณ ์ปจํ…Œ์ด๋„ˆ(Docker/K8s) ํ™˜๊ฒฝ ๊ถŒํ•œ ๊ด€๋ฆฌ

# Dockerfile - ์ตœ์†Œ ๊ถŒํ•œ ์›์น™ ์ ์šฉ
FROM node:18-alpine

# root๋กœ ์‹คํ–‰ํ•˜์ง€ ์•Š์Œ!
RUN addgroup -g 1001 -S appgroup && \
    adduser -u 1001 -S appuser -G appgroup

WORKDIR /app
COPY --chown=appuser:appgroup . .

RUN npm ci --only=production

# ๋น„root ์‚ฌ์šฉ์ž๋กœ ์ „ํ™˜
USER appuser

EXPOSE 3000
CMD ["node", "server.js"]
# Kubernetes - SecurityContext ์„ค์ •
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      securityContext:
        runAsNonRoot: true      # root๋กœ ์‹คํ–‰ ๊ธˆ์ง€
        runAsUser: 1001         # ํŠน์ • UID๋กœ ์‹คํ–‰
        runAsGroup: 1001
        fsGroup: 1001
      containers:
      - name: myapp
        image: myapp:latest
        securityContext:
          allowPrivilegeEscalation: false  # ๊ถŒํ•œ ์ƒ์Šน ๊ธˆ์ง€
          readOnlyRootFilesystem: true     # ๋ฃจํŠธ ํŒŒ์ผ์‹œ์Šคํ…œ ์ฝ๊ธฐ ์ „์šฉ
          capabilities:
            drop:
            - ALL                          # ๋ชจ๋“  Linux ๊ธฐ๋Šฅ ์ œ๊ฑฐ
            add:
            - NET_BIND_SERVICE             # ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ์ถ”๊ฐ€
        resources:
          limits:
            cpu: "500m"
            memory: "256Mi"

๐Ÿ” Secrets ๊ด€๋ฆฌ โ€” ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ฝ”๋“œ์— ๋„ฃ์ง€ ๋งˆ!

DB ๋น„๋ฐ€๋ฒˆํ˜ธ, API ํ‚ค ๊ฐ™์€ ๋ฏผ๊ฐ ์ •๋ณด๋ฅผ ์ฝ”๋“œ์— ํ•˜๋“œ์ฝ”๋”ฉํ•˜๊ฑฐ๋‚˜ .env ํŒŒ์ผ์„ Git์— ์˜ฌ๋ฆฌ๋Š” ๊ฑด ์ง„์งœ ์ตœ์•…์˜ ์‹ค์ˆ˜์•ผ ๐Ÿ˜ค

# โŒ ์ ˆ๋Œ€ ์ด๋ ‡๊ฒŒ ํ•˜์ง€ ๋งˆ
DB_PASSWORD=mypassword123  # .env ํŒŒ์ผ์„ Git์— ์˜ฌ๋ฆฌ๋ฉด ๋์žฅ

# โœ… ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ๋ฒ•๋“ค

# 1. AWS Secrets Manager ์‚ฌ์šฉ
import boto3
import json

def get_db_credentials():
    client = boto3.client('secretsmanager', region_name='ap-northeast-2')
    response = client.get_secret_value(SecretId='myapp/prod/db')
    return json.loads(response['SecretString'])

creds = get_db_credentials()
db_password = creds['password']

# 2. HashiCorp Vault ์‚ฌ์šฉ
import hvac

client = hvac.Client(url='https://vault.internal:8200')
client.auth.aws.iam_login(role='myapp-role')

secret = client.secrets.kv.v2.read_secret_version(
    path='myapp/db',
    mount_point='secret'
)
db_password = secret['data']['data']['password']

# 3. Kubernetes Secrets (์•”ํ˜ธํ™” ์ €์žฅ)
# kubectl create secret generic db-secret \
#   --from-literal=password=๋ณต์žกํ•œํŒจ์Šค์›Œ๋“œ \
#   --namespace=production
๐Ÿ›ก๏ธ ์‹ฌ์ธต ๋ฐฉ์–ด(Defense in Depth) โ€” ๊ถŒํ•œ ๋ณด์•ˆ ๋ ˆ์ด์–ด ์—ฌ๋Ÿฌ ๊ฒน์˜ ๋ณด์•ˆ ๋ ˆ์ด์–ด๋กœ ๊ณต๊ฒฉ์ž๊ฐ€ ๋šซ๊ธฐ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ค๊ธฐ ๐ŸŒ ๋ ˆ์ด์–ด 1: ๋„คํŠธ์›Œํฌ ๋ณด์•ˆ ๋ฐฉํ™”๋ฒฝ ยท VPN ยท IP ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ ยท VPC ๊ฒฉ๋ฆฌ ยท ํฌํŠธ ์ œํ•œ ๐Ÿ”‘ ๋ ˆ์ด์–ด 2: ์ธ์ฆ(Authentication) ๊ฐ•๋ ฅํ•œ ํŒจ์Šค์›Œ๋“œ ยท MFA ยท SSH ํ‚ค ์ธ์ฆ ยท ์ธ์ฆ์„œ ๊ธฐ๋ฐ˜ ์ธ์ฆ ๐ŸŽญ ๋ ˆ์ด์–ด 3: ๊ถŒํ•œ(Authorization) โ† ์˜ค๋Š˜์˜ ํ•ต์‹ฌ! RBAC ยท ์ตœ์†Œ ๊ถŒํ•œ ์›์น™ ยท ์—ญํ•  ๋ถ„๋ฆฌ ยท ์ปฌ๋Ÿผ/ํ–‰ ๋ ˆ๋ฒจ ๋ณด์•ˆ ๐Ÿ”’ ๋ ˆ์ด์–ด 4: ๋ฐ์ดํ„ฐ ์•”ํ˜ธํ™” ์ „์†ก ์•”ํ˜ธํ™”(TLS) ยท ์ €์žฅ ์•”ํ˜ธํ™” ยท ์ปฌ๋Ÿผ ์•”ํ˜ธํ™” ยท ํ‚ค ๊ด€๋ฆฌ ๐Ÿ“Š ๋ ˆ์ด์–ด 5: ๊ฐ์‚ฌ ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง ๊ฐ์‚ฌ ๋กœ๊ทธ ยท ์ด์ƒ ํƒ์ง€ ยท ์•Œ๋ฆผ ยท ์ •๊ธฐ ๊ฐ์‚ฌ ยท SIEM ๐Ÿ‘พ

๐Ÿšจ ๊ฐœ๋ฐœ์ž๋“ค์ด ์ž์ฃผ ํ•˜๋Š” ๊ถŒํ•œ ๊ด€๋ จ ์‹ค์ˆ˜ TOP 10

์ด๊ฑฐ ์ง„์งœ ํ˜„์—…์—์„œ ๋งŽ์ด ๋ณด์ด๋Š” ์‹ค์ˆ˜๋“ค์ด์•ผ. ๋‚˜๋„ ์ดˆ๋ฐ˜์— ๋‹ค ํ•ด๋ดค์Œ ใ…‹ใ…‹ใ…‹ ๋ฐ˜๋ฉด๊ต์‚ฌ ์‚ผ์•„๋ด ๐Ÿ˜…

๐Ÿšซ ์‹ค์ˆ˜ 1: root ๊ณ„์ •์œผ๋กœ ์•ฑ DB ์—ฐ๊ฒฐ
"๊ฐœ๋ฐœ ํŽธํ•˜๊ฒŒ ํ•˜๋ ค๊ณ " root ์“ฐ๋‹ค๊ฐ€ ํ”„๋กœ๋•์…˜์—๋„ ๊ทธ๋Œ€๋กœ ์˜ฌ๋ฆฌ๋Š” ๊ฒฝ์šฐ. ์ง„์งœ ์ตœ์•…์ด์•ผ.
โœ… ํ•ด๊ฒฐ: ์•ฑ ์ „์šฉ ๊ณ„์ • ์ƒ์„ฑ, ํ•„์š”ํ•œ ๊ถŒํ•œ๋งŒ ๋ถ€์—ฌ

๐Ÿšซ ์‹ค์ˆ˜ 2: .env ํŒŒ์ผ Git์— ์ปค๋ฐ‹
DB ๋น„๋ฐ€๋ฒˆํ˜ธ, API ํ‚ค๊ฐ€ ๋‹ด๊ธด .env ํŒŒ์ผ์„ ์‹ค์ˆ˜๋กœ Git์— ์˜ฌ๋ฆฌ๋Š” ๊ฒฝ์šฐ. GitHub์— ์˜ฌ๋ผ๊ฐ€๋ฉด ๋ด‡์ด ์ˆ˜์ดˆ ๋‚ด์— ์Šค์บ”ํ•ด์„œ ํƒˆ์ทจํ•ด๊ฐ.
โœ… ํ•ด๊ฒฐ: .gitignore์— .env ์ถ”๊ฐ€, git-secrets ๋„๊ตฌ ์‚ฌ์šฉ, Secrets Manager ํ™œ์šฉ

๐Ÿšซ ์‹ค์ˆ˜ 3: ๊ฐœ๋ฐœ/ํ”„๋กœ๋•์…˜ ๋™์ผ ๊ณ„์ • ์‚ฌ์šฉ
๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์“ฐ๋˜ ๊ณ„์ •์„ ํ”„๋กœ๋•์…˜์—๋„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ. ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์ด ๋šซ๋ฆฌ๋ฉด ํ”„๋กœ๋•์…˜๋„ ์œ„ํ—˜ํ•ด์ง.
โœ… ํ•ด๊ฒฐ: ํ™˜๊ฒฝ๋ณ„ ๊ณ„์ • ์™„์ „ ๋ถ„๋ฆฌ, ํ”„๋กœ๋•์…˜ ๊ณ„์ •์€ ์ตœ์†Œ ๊ถŒํ•œ๋งŒ

๐Ÿšซ ์‹ค์ˆ˜ 4: ํ‡ด์ง์ž ๊ณ„์ • ๋ฐฉ์น˜
ํŒ€์›์ด ํ‡ด์งํ–ˆ๋Š”๋ฐ DB ๊ณ„์ •, ์„œ๋ฒ„ ๊ณ„์ •์„ ์‚ญ์ œ ์•ˆ ํ•˜๋Š” ๊ฒฝ์šฐ. ํ‡ด์ง์ž๊ฐ€ ์•…์˜์ ์œผ๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์–ด.
โœ… ํ•ด๊ฒฐ: ํ‡ด์ง ๋‹น์ผ ์ฆ‰์‹œ ๋ชจ๋“  ๊ณ„์ • ๋น„ํ™œ์„ฑํ™”/์‚ญ์ œ ํ”„๋กœ์„ธ์Šค ์ˆ˜๋ฆฝ

๐Ÿšซ ์‹ค์ˆ˜ 5: ๊ณต์œ  ๊ณ„์ • ์‚ฌ์šฉ
ํŒ€ ์ „์ฒด๊ฐ€ ํ•˜๋‚˜์˜ DB ๊ณ„์ •์„ ๊ณต์œ ํ•˜๋Š” ๊ฒฝ์šฐ. ๋ˆ„๊ฐ€ ๋ญ˜ ํ–ˆ๋Š”์ง€ ์ถ”์  ๋ถˆ๊ฐ€๋Šฅ.
โœ… ํ•ด๊ฒฐ: ๊ฐœ์ธ๋ณ„ ๊ณ„์ • ๋ฐœ๊ธ‰, ๊ฐ์‚ฌ ๋กœ๊ทธ๋กœ ๊ฐœ์ธ ํ™œ๋™ ์ถ”์ 

๐Ÿšซ ์‹ค์ˆ˜ 6: ์ž„์‹œ ๊ถŒํ•œ ํšŒ์ˆ˜ ์•ˆ ํ•จ
"์ž ๊น๋งŒ ์“ธ๊ฒŒ"๋ผ๊ณ  ์ค€ ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์„ ํšŒ์ˆ˜ ์•ˆ ํ•˜๋Š” ๊ฒฝ์šฐ. ์ž„์‹œ๊ฐ€ ์˜๊ตฌ๊ฐ€ ๋˜๋Š” ๋งˆ๋ฒ• ใ…‹ใ…‹ใ…‹
โœ… ํ•ด๊ฒฐ: ์ž„์‹œ ๊ถŒํ•œ์— ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ์„ค์ •, ์ž‘์—… ์™„๋ฃŒ ํ›„ ์ฆ‰์‹œ ํšŒ์ˆ˜ ํ”„๋กœ์„ธ์Šค

๐Ÿšซ ์‹ค์ˆ˜ 7: ์•ฝํ•œ ํŒจ์Šค์›Œ๋“œ ์ •์ฑ…
DB ๊ณ„์ •์— "password123" ๊ฐ™์€ ์•ฝํ•œ ํŒจ์Šค์›Œ๋“œ ์‚ฌ์šฉ. ๋ธŒ๋ฃจํŠธํฌ์Šค ๊ณต๊ฒฉ์— ์ทจ์•ฝ.
โœ… ํ•ด๊ฒฐ: ์ตœ์†Œ 16์ž ์ด์ƒ, ๋Œ€์†Œ๋ฌธ์ž+์ˆซ์ž+ํŠน์ˆ˜๋ฌธ์ž ์กฐํ•ฉ, ํŒจ์Šค์›Œ๋“œ ๋งค๋‹ˆ์ € ์‚ฌ์šฉ

๐Ÿšซ ์‹ค์ˆ˜ 8: ๋ชจ๋“  IP์—์„œ DB ์ ‘๊ทผ ํ—ˆ์šฉ
'user'@'%'๋กœ ๊ณ„์ • ์ƒ์„ฑํ•ด์„œ ์–ด๋””์„œ๋“  ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒฝ์šฐ.
โœ… ํ•ด๊ฒฐ: ํŠน์ • IP ๋˜๋Š” IP ๋Œ€์—ญ๋งŒ ํ—ˆ์šฉ, ์™ธ๋ถ€์—์„œ ์ง์ ‘ DB ์ ‘๊ทผ ์ฐจ๋‹จ

๐Ÿšซ ์‹ค์ˆ˜ 9: ๋กœ๊ทธ ๋ชจ๋‹ˆํ„ฐ๋ง ์•ˆ ํ•จ
๊ถŒํ•œ ์„ค์ •์€ ์ž˜ ํ•ด๋†จ๋Š”๋ฐ ๊ฐ์‚ฌ ๋กœ๊ทธ๋ฅผ ์•„๋ฌด๋„ ์•ˆ ๋ณด๋Š” ๊ฒฝ์šฐ. ์นจํ•ด๊ฐ€ ์ผ์–ด๋‚˜๋„ ๋ชจ๋ฆ„.
โœ… ํ•ด๊ฒฐ: ๊ฐ์‚ฌ ๋กœ๊ทธ ์ž๋™ ๋ถ„์„, ์ด์ƒ ํŒจํ„ด ์•Œ๋ฆผ ์„ค์ •, ์ •๊ธฐ ๋ฆฌ๋ทฐ

๐Ÿšซ ์‹ค์ˆ˜ 10: ๊ถŒํ•œ ๋ฌธ์„œํ™” ์•ˆ ํ•จ
๋ˆ„๊ฐ€ ์–ด๋–ค ๊ถŒํ•œ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”์ง€ ๋ฌธ์„œ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ. ๋‹ด๋‹น์ž ํ‡ด์งํ•˜๋ฉด ์•„๋ฌด๋„ ๋ชจ๋ฅด๋Š” ์ƒํ™ฉ ๋ฐœ์ƒ.
โœ… ํ•ด๊ฒฐ: ๊ถŒํ•œ ๋งคํŠธ๋ฆญ์Šค ๋ฌธ์„œ ์ž‘์„ฑ ๋ฐ ์ฃผ๊ธฐ์  ์—…๋ฐ์ดํŠธ, IaC(Infrastructure as Code)๋กœ ๊ถŒํ•œ ์ฝ”๋“œํ™”

๐Ÿš€ ์‹ค์ „ ๊ตฌํ˜„ ๊ฐ€์ด๋“œ โ€” ์ง€๊ธˆ ๋‹น์žฅ ์‹œ์ž‘ํ•˜๊ธฐ

์ž, ์ด์ œ ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ์‹œ์ž‘ํ• ์ง€ ๋‹จ๊ณ„๋ณ„๋กœ ์ •๋ฆฌํ•ด์ค„๊ฒŒ. ์žฌ๋Šฅ๋„ท์ฒ˜๋Ÿผ ๋‹ค์–‘ํ•œ ์‚ฌ์šฉ์ž์™€ ์„œ๋น„์Šค๊ฐ€ ์žˆ๋Š” ํ”Œ๋žซํผ์„ ์šด์˜ํ•œ๋‹ค๋ฉด ์ด๋Ÿฐ ์ˆœ์„œ๋กœ ์ ‘๊ทผํ•˜๋ฉด ๋ผ ๐Ÿ’ช

๐Ÿ“‹ Step 1: ํ˜„์žฌ ์ƒํƒœ ํŒŒ์•… (๊ถŒํ•œ ๊ฐ์‚ฌ)

-- ํ˜„์žฌ ๋ชจ๋“  DB ๊ณ„์ •๊ณผ ๊ถŒํ•œ ํŒŒ์•…
SELECT 
  u.user,
  u.host,
  u.password_expired,
  u.account_locked,
  GROUP_CONCAT(DISTINCT CONCAT(d.db, ':', 
    IF(d.Select_priv='Y','R',''),
    IF(d.Insert_priv='Y','I',''),
    IF(d.Update_priv='Y','U',''),
    IF(d.Delete_priv='Y','D','')
  ) SEPARATOR ' | ') as db_permissions
FROM mysql.user u
LEFT JOIN mysql.db d ON u.user = d.user AND u.host = d.host
GROUP BY u.user, u.host
ORDER BY u.user;

๐Ÿ“‹ Step 2: ๊ถŒํ•œ ๋งคํŠธ๋ฆญ์Šค ์„ค๊ณ„

๊ณ„์ •๋ช… ์šฉ๋„ ํ—ˆ์šฉ DB ํ—ˆ์šฉ ๊ถŒํ•œ ํ—ˆ์šฉ IP
app_read ์กฐํšŒ API myapp SELECT 10.0.1.x
app_write ์ผ๋ฐ˜ ์„œ๋น„์Šค myapp SELECT, INSERT, UPDATE 10.0.1.x
app_admin ๊ด€๋ฆฌ์ž ๊ธฐ๋Šฅ myapp SELECT, INSERT, UPDATE, DELETE 10.0.2.x
batch_job ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ myapp (ํŠน์ • ํ…Œ์ด๋ธ”) SELECT, INSERT localhost
monitor ๋ชจ๋‹ˆํ„ฐ๋ง information_schema SELECT 10.0.3.x

๐Ÿ“‹ Step 3: ์ž๋™ํ™” ์Šคํฌ๋ฆฝํŠธ ์ž‘์„ฑ

#!/bin/bash
# db_setup.sh - DB ๊ณ„์ • ๋ฐ ๊ถŒํ•œ ์ž๋™ ์„ค์ • ์Šคํฌ๋ฆฝํŠธ

DB_HOST="localhost"
DB_ROOT_USER="root"
DB_ROOT_PASS="${DB_ROOT_PASSWORD}"  # ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ ์ฝ๊ธฐ
APP_DB="myapp_prod"

echo "๐Ÿ” DB ๊ถŒํ•œ ์„ค์ • ์‹œ์ž‘..."

mysql -h "$DB_HOST" -u "$DB_ROOT_USER" -p"$DB_ROOT_PASS" << EOF

-- ๊ธฐ์กด ์•ฑ ๊ณ„์ • ์‚ญ์ œ (์žฌ์„ค์ •)
DROP USER IF EXISTS 'app_read'@'10.0.1.%';
DROP USER IF EXISTS 'app_write'@'10.0.1.%';

-- ์ฝ๊ธฐ ์ „์šฉ ๊ณ„์ • ์ƒ์„ฑ
CREATE USER 'app_read'@'10.0.1.%' 
  IDENTIFIED BY '${APP_READ_PASSWORD}'
  PASSWORD EXPIRE INTERVAL 90 DAY
  FAILED_LOGIN_ATTEMPTS 5 
  PASSWORD_LOCK_TIME 1;

GRANT SELECT ON ${APP_DB}.* TO 'app_read'@'10.0.1.%';

-- ์“ฐ๊ธฐ ๊ณ„์ • ์ƒ์„ฑ
CREATE USER 'app_write'@'10.0.1.%' 
  IDENTIFIED BY '${APP_WRITE_PASSWORD}'
  PASSWORD EXPIRE INTERVAL 90 DAY
  FAILED_LOGIN_ATTEMPTS 5 
  PASSWORD_LOCK_TIME 1;

GRANT SELECT, INSERT, UPDATE ON ${APP_DB}.* TO 'app_write'@'10.0.1.%';

FLUSH PRIVILEGES;

-- ์„ค์ • ํ™•์ธ
SHOW GRANTS FOR 'app_read'@'10.0.1.%';
SHOW GRANTS FOR 'app_write'@'10.0.1.%';

EOF

echo "โœ… DB ๊ถŒํ•œ ์„ค์ • ์™„๋ฃŒ!"

๐Ÿ“‹ Step 4: IaC๋กœ ๊ถŒํ•œ ์ฝ”๋“œํ™” (Terraform ์˜ˆ์‹œ)

# terraform/db_users.tf
# ๊ถŒํ•œ ์„ค์ •์„ ์ฝ”๋“œ๋กœ ๊ด€๋ฆฌ โ†’ ๋ฒ„์ „ ๊ด€๋ฆฌ, ๋ฆฌ๋ทฐ ๊ฐ€๋Šฅ

resource "mysql_user" "app_reader" {
  user               = "app_reader"
  host               = "10.0.1.%"
  plaintext_password = var.app_reader_password
}

resource "mysql_grant" "app_reader_grant" {
  user       = mysql_user.app_reader.user
  host       = mysql_user.app_reader.host
  database   = "myapp_prod"
  privileges = ["SELECT"]
}

resource "mysql_user" "app_writer" {
  user               = "app_writer"
  host               = "10.0.1.%"
  plaintext_password = var.app_writer_password
}

resource "mysql_grant" "app_writer_grant" {
  user       = mysql_user.app_writer.user
  host       = mysql_user.app_writer.host
  database   = "myapp_prod"
  privileges = ["SELECT", "INSERT", "UPDATE"]
}

โœ… IaC์˜ ์žฅ์ !

๊ถŒํ•œ ์„ค์ •์„ ์ฝ”๋“œ๋กœ ๊ด€๋ฆฌํ•˜๋ฉด Git์—์„œ ๋ณ€๊ฒฝ ์ด๋ ฅ ์ถ”์  ๊ฐ€๋Šฅ, PR ๋ฆฌ๋ทฐ๋กœ ๊ถŒํ•œ ๋ณ€๊ฒฝ ๊ฒ€ํ†  ๊ฐ€๋Šฅ, ์‹ค์ˆ˜๋กœ ๊ถŒํ•œ ๋ณ€๊ฒฝ ์‹œ ๋กค๋ฐฑ ๊ฐ€๋Šฅ, ์ƒˆ ํ™˜๊ฒฝ ๊ตฌ์ถ• ์‹œ ๋™์ผํ•œ ๊ถŒํ•œ ์„ค์ • ์ž๋™ ์ ์šฉ ๋“ฑ ์—„์ฒญ๋‚œ ์ด์ ์ด ์žˆ์–ด ๐Ÿ‘

๐Ÿ“š ๊ด€๋ จ ๋ณด์•ˆ ํ”„๋ ˆ์ž„์›Œํฌ์™€ ๊ทœ์ •

์ตœ์†Œ ๊ถŒํ•œ ์›์น™์€ ๋‹จ์ˆœํ•œ ์ข‹์€ ์Šต๊ด€์ด ์•„๋‹ˆ๋ผ ์—ฌ๋Ÿฌ ๋ณด์•ˆ ํ”„๋ ˆ์ž„์›Œํฌ์™€ ๋ฒ•์  ๊ทœ์ •์—์„œ ์˜๋ฌด ์‚ฌํ•ญ์œผ๋กœ ์š”๊ตฌํ•˜๊ณ  ์žˆ์–ด. ์•Œ์•„๋‘๋ฉด ์ข‹์•„ ๐Ÿ“–

๐Ÿ›๏ธ ์ฃผ์š” ๋ณด์•ˆ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ์˜ ์ตœ์†Œ ๊ถŒํ•œ

ํ”„๋ ˆ์ž„์›Œํฌ/๊ทœ์ • ๊ด€๋ จ ์š”๊ตฌ์‚ฌํ•ญ ์ ์šฉ ๋Œ€์ƒ
ISO 27001 A.9.2 ์‚ฌ์šฉ์ž ์ ‘๊ทผ ๊ด€๋ฆฌ, A.9.4 ์‹œ์Šคํ…œ ๋ฐ ์‘์šฉํ”„๋กœ๊ทธ๋žจ ์ ‘๊ทผ ํ†ต์ œ ์ •๋ณด๋ณด์•ˆ ์ธ์ฆ ๊ธฐ์—…
NIST SP 800-53 AC-6 ์ตœ์†Œ ๊ถŒํ•œ, AC-2 ๊ณ„์ • ๊ด€๋ฆฌ ๋ฏธ๊ตญ ์—ฐ๋ฐฉ๊ธฐ๊ด€, ๊ณ„์•ฝ์—…์ฒด
PCI DSS Req 7: ์นด๋“œ ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ์ œํ•œ, Req 8: ์‚ฌ์šฉ์ž ์‹๋ณ„ ๋ฐ ์ธ์ฆ ์นด๋“œ ๊ฒฐ์ œ ์ฒ˜๋ฆฌ ๊ธฐ์—…
GDPR Article 25: ๋ฐ์ดํ„ฐ ๋ณดํ˜ธ ์„ค๊ณ„, Article 32: ์ฒ˜๋ฆฌ ๋ณด์•ˆ EU ๊ฐœ์ธ์ •๋ณด ์ฒ˜๋ฆฌ ๊ธฐ์—…
๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ๋ฒ• ์ œ29์กฐ: ์•ˆ์ „์„ฑ ํ™•๋ณด ์กฐ์น˜, ์ ‘๊ทผ ๊ถŒํ•œ ์ตœ์†Œํ™” ๊ตญ๋‚ด ๊ฐœ์ธ์ •๋ณด ์ฒ˜๋ฆฌ ๊ธฐ์—…
CIS Controls Control 6: ์ ‘๊ทผ ์ œ์–ด ๊ด€๋ฆฌ ์ผ๋ฐ˜ ๊ธฐ์—…

๐Ÿ’ก ์‹ค๋ฌด ํŒ!

์Šคํƒ€ํŠธ์—…์ด๋‚˜ ์†Œ๊ทœ๋ชจ ํŒ€์ด๋ผ๋ฉด ์ฒ˜์Œ๋ถ€ํ„ฐ ๋ชจ๋“  ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ๋‹ค ๋”ฐ๋ฅด๊ธฐ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์–ด. ๊ทธ๋Ÿด ๋•Œ๋Š” CIS Controls๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๋Š” ๊ฑธ ์ถ”์ฒœํ•ด. ์šฐ์„ ์ˆœ์œ„๊ฐ€ ์ž˜ ์ •๋ฆฌ๋˜์–ด ์žˆ๊ณ  ์‹ค์šฉ์ ์ด๊ฑฐ๋“ . ๋‚˜์ค‘์— ๊ทœ๋ชจ๊ฐ€ ์ปค์ง€๋ฉด ISO 27001์ด๋‚˜ PCI DSS ๊ฐ™์€ ์ธ์ฆ์„ ๋ฐ›๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ๊ฐ€๋ฉด ๋ผ ๐Ÿ‘

๐Ÿ‡ฐ๐Ÿ‡ท ๊ตญ๋‚ด ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ๋ฒ• ์š”๊ตฌ์‚ฌํ•ญ

๊ตญ๋‚ด ์„œ๋น„์Šค๋ผ๋ฉด ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ๋ฒ• ์ œ29์กฐ์™€ ๊ฐœ์ธ์ •๋ณด์˜ ์•ˆ์ „์„ฑ ํ™•๋ณด์กฐ์น˜ ๊ธฐ์ค€์„ ๋ฐ˜๋“œ์‹œ ์•Œ์•„์•ผ ํ•ด:

๐Ÿ“‹ ๊ฐœ์ธ์ •๋ณด์˜ ์•ˆ์ „์„ฑ ํ™•๋ณด์กฐ์น˜ ๊ธฐ์ค€ (์ฃผ์š” ๋‚ด์šฉ)

๐Ÿ”น ์ œ4์กฐ ์ ‘๊ทผ ๊ถŒํ•œ์˜ ๊ด€๋ฆฌ
- ๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ์‹œ์Šคํ…œ์— ๋Œ€ํ•œ ์ ‘๊ทผ ๊ถŒํ•œ์„ ์—…๋ฌด ์ˆ˜ํ–‰์— ํ•„์š”ํ•œ ์ตœ์†Œํ•œ์œผ๋กœ ๋ถ€์—ฌ
- ์ „๋ณด ๋˜๋Š” ํ‡ด์ง ๋“ฑ ์ธ์‚ฌ์ด๋™ ์‹œ ์ง€์ฒด ์—†์ด ์ ‘๊ทผ ๊ถŒํ•œ ๋ณ€๊ฒฝ ๋˜๋Š” ๋ง์†Œ
- ์ ‘๊ทผ ๊ถŒํ•œ ๋ถ€์—ฌยท๋ณ€๊ฒฝยท๋ง์†Œ ๋‚ด์—ญ์„ ๊ธฐ๋กํ•˜๊ณ  ์ตœ์†Œ 3๋…„๊ฐ„ ๋ณด๊ด€

๐Ÿ”น ์ œ5์กฐ ์ ‘๊ทผ ํ†ต์ œ
- ๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ์‹œ์Šคํ…œ์— ๋Œ€ํ•œ ๋ถˆ๋ฒ•์ ์ธ ์ ‘๊ทผ ๋ฐ ์นจํ•ด์‚ฌ๊ณ  ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ ์ ‘๊ทผ ํ†ต์ œ ์‹œ์Šคํ…œ ์„ค์น˜ยท์šด์˜
- ๊ฐœ์ธ์ •๋ณด์ฒ˜๋ฆฌ์‹œ์Šคํ…œ์— ์ ‘์†ํ•œ IP ์ฃผ์†Œ ๋“ฑ์„ ๋ถ„์„ํ•˜์—ฌ ๋ถˆ๋ฒ•์ ์ธ ๊ฐœ์ธ์ •๋ณด ์œ ์ถœ ์‹œ๋„ ํƒ์ง€

๐ŸŽฏ ํ•ต์‹ฌ ์ •๋ฆฌ โ€” ์˜ค๋Š˜ ๋ฐฐ์šด ๊ฒƒ๋“ค

์™€ ์ง„์งœ ๊ธธ์—ˆ๋‹ค ใ…‹ใ…‹ใ…‹ ์—ฌ๊ธฐ๊นŒ์ง€ ์ฝ์–ด์ค˜์„œ ๊ณ ๋งˆ์›Œ! ๋งˆ์ง€๋ง‰์œผ๋กœ ํ•ต์‹ฌ๋งŒ ์™์™ ์ •๋ฆฌํ•ด์ค„๊ฒŒ ๐ŸŽ

1
์ตœ์†Œ ๊ถŒํ•œ ์›์น™(PoLP)์€ ์„ ํƒ์ด ์•„๋‹Œ ํ•„์ˆ˜
1975๋…„๋ถ€ํ„ฐ ๊ฒ€์ฆ๋œ ์›์น™. ํ•ดํ‚น ํ”ผํ•ด๋ฅผ ์ตœ์†Œํ™”ํ•˜๋Š” ๊ฐ€์žฅ ํšจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ•์ด์•ผ.
2
DB ๊ณ„์ •์€ ์—ญํ• ๋ณ„๋กœ ๋ถ„๋ฆฌ
์ฝ๊ธฐ ์ „์šฉ, ์“ฐ๊ธฐ, ๋ฐฐ์น˜, ๋ฐฐํฌ ๋“ฑ ์šฉ๋„๋ณ„๋กœ ๊ณ„์ •์„ ๋‚˜๋ˆ„๊ณ  ๊ฐ๊ฐ ํ•„์š”ํ•œ ๊ถŒํ•œ๋งŒ ๋ถ€์—ฌ.
3
์„œ๋ฒ„๋„ ๋งˆ์ฐฌ๊ฐ€์ง€
root ์ง์ ‘ ์‚ฌ์šฉ ๊ธˆ์ง€, ์„œ๋น„์Šค๋ณ„ ์ „์šฉ ๊ณ„์ •, sudo ์ตœ์†Œํ™”, ํŒŒ์ผ ๊ถŒํ•œ ์ ์ ˆํžˆ ์„ค์ •.
4
RBAC๋กœ ์ฒด๊ณ„์ ์œผ๋กœ ๊ด€๋ฆฌ
์—ญํ• ์— ๊ถŒํ•œ์„ ๋ฌถ๊ณ  ์‚ฌ์šฉ์ž์—๊ฒŒ ์—ญํ• ์„ ๋ถ€์—ฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ด€๋ฆฌ ํšจ์œจ์„ฑ ํ–ฅ์ƒ.
5
๊ฐ์‚ฌ์™€ ๋ชจ๋‹ˆํ„ฐ๋ง์€ ํ•„์ˆ˜
๊ถŒํ•œ ์„ค์ • ํ›„ ์ง€์†์ ์ธ ๊ฐ์‚ฌ ์—†์ด๋Š” ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด์„œ ๊ถŒํ•œ ํฌ๋ฆฌํ”„ ๋ฐœ์ƒ. ์ •๊ธฐ ๋ฆฌ๋ทฐ ํ•„์ˆ˜.
6
IaC๋กœ ๊ถŒํ•œ์„ ์ฝ”๋“œํ™”
Terraform ๋“ฑ์œผ๋กœ ๊ถŒํ•œ ์„ค์ •์„ ์ฝ”๋“œ๋กœ ๊ด€๋ฆฌํ•˜๋ฉด ๋ฒ„์ „ ๊ด€๋ฆฌ, ๋ฆฌ๋ทฐ, ์ž๋™ํ™”๊ฐ€ ๊ฐ€๋Šฅํ•ด์ง.

๊ถŒํ•œ ๊ด€๋ฆฌ๋Š” ์ฒ˜์Œ ์„ค์ •ํ•  ๋•Œ ์ข€ ๊ท€์ฐฎ๊ณ  ๋ฒˆ๊ฑฐ๋กญ๊ฒŒ ๋А๊ปด์งˆ ์ˆ˜ ์žˆ์–ด. ๊ทผ๋ฐ ํ•œ ๋ฒˆ ์ œ๋Œ€๋กœ ํ•ด๋†“์œผ๋ฉด ๋‚˜์ค‘์— ์‚ฌ๊ณ  ๋‚ฌ์„ ๋•Œ "์•„ ๊ทธ๋•Œ ์ œ๋Œ€๋กœ ํ•ด๋†จ์œผ๋ฉด..." ํ•˜๋Š” ํ›„ํšŒ๋ฅผ ์•ˆ ํ•ด๋„ ๋ผ ๐Ÿ˜ค

๊ฐœ๋ฐœ์ž๋กœ์„œ ์ฝ”๋“œ ํ’ˆ์งˆ๋งŒํผ์ด๋‚˜ ๋ณด์•ˆ ์„ค๊ณ„๋„ ์ค‘์š”ํ•œ ์—ญ๋Ÿ‰์ด์•ผ. ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋„ ๋‹ค์–‘ํ•œ ๋ณด์•ˆ ์ „๋ฌธ๊ฐ€๋“ค์ด ํ™œ๋™ํ•˜๊ณ  ์žˆ์œผ๋‹ˆ, ๋” ๊นŠ์ด ๊ณต๋ถ€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์ „๋ฌธ๊ฐ€์˜ ๋„์›€์„ ๋ฐ›๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ ๐ŸŽ“

"๋ณด์•ˆ์€ ์ œํ’ˆ์ด ์•„๋‹ˆ๋ผ ํ”„๋กœ์„ธ์Šค๋‹ค." โ€” Bruce Schneier

ํ•œ ๋ฒˆ ์„ค์ •ํ•˜๊ณ  ๋์ด ์•„๋‹ˆ๋ผ, ์ง€์†์ ์œผ๋กœ ๊ฒ€ํ† ํ•˜๊ณ  ๊ฐœ์„ ํ•˜๋Š” ํ”„๋กœ์„ธ์Šค๋กœ ๋งŒ๋“ค์–ด์•ผ ์ง„์งœ ๋ณด์•ˆ์ด ๋˜๋Š” ๊ฑฐ์•ผ. ์˜ค๋Š˜๋ถ€ํ„ฐ ๋‹น์žฅ ์—ฌ๋Ÿฌ๋ถ„์˜ DB ๊ณ„์ • ๊ถŒํ•œ ํ•œ๋ฒˆ ํ™•์ธํ•ด๋ด! ํ˜น์‹œ root ๊ณ„์ •์œผ๋กœ ์•ฑ ์—ฐ๊ฒฐํ•˜๊ณ  ์žˆ๋Š” ๊ฑฐ ์•„๋‹ˆ์ง€? ใ…‹ใ…‹ใ…‹ ๐Ÿ˜…

๐Ÿš€ ์ง€๊ธˆ ๋‹น์žฅ ์‹ค์ฒœํ•  ์ˆ˜ ์žˆ๋Š” ์ฒดํฌ๋ฆฌ์ŠคํŠธ ๐Ÿ“Œ DB ๋ณด์•ˆ โœ“ root ๊ณ„์ • ์•ฑ ์—ฐ๊ฒฐ ์—ฌ๋ถ€ ํ™•์ธ โœ“ ์—ญํ• ๋ณ„ DB ๊ณ„์ • ๋ถ„๋ฆฌ โœ“ ๋ถˆํ•„์š”ํ•œ ๊ถŒํ•œ ์ œ๊ฑฐ (ํŠนํžˆ DROP) โœ“ IP ๊ธฐ๋ฐ˜ ์ ‘๊ทผ ์ œํ•œ ์„ค์ • โœ“ ๊ฐ์‚ฌ ๋กœ๊ทธ ํ™œ์„ฑํ™” โœ“ ํŒจ์Šค์›Œ๋“œ ๋งŒ๋ฃŒ ์ •์ฑ… ์„ค์ • โœ“ ํ‡ด์ง์ž ๊ณ„์ • ์ฆ‰์‹œ ์‚ญ์ œ โœ“ ๊ถŒํ•œ ๋งคํŠธ๋ฆญ์Šค ๋ฌธ์„œํ™” ๐Ÿ“Œ ์„œ๋ฒ„ ๋ณด์•ˆ โœ“ SSH root ๋กœ๊ทธ์ธ ๋น„ํ™œ์„ฑํ™” โœ“ SSH ํ‚ค ๊ธฐ๋ฐ˜ ์ธ์ฆ ์„ค์ • โœ“ ์„œ๋น„์Šค๋ณ„ ์ „์šฉ ๊ณ„์ • ์‚ฌ์šฉ โœ“ sudo ๊ถŒํ•œ ์ตœ์†Œํ™” โœ“ ์„ค์ • ํŒŒ์ผ ๊ถŒํ•œ 600 ์„ค์ • โœ“ .env ํŒŒ์ผ Git ์ œ์™ธ ํ™•์ธ โœ“ Secrets Manager ๋„์ž… โœ“ ์ •๊ธฐ ๋ณด์•ˆ ๊ฐ์‚ฌ ์ผ์ • ์ˆ˜๋ฆฝ
๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.