Ver3.5 ๐ ์ฌ์ฉ์ ๊ถํ ๊ด๋ฆฌ์ ์ต์ ๊ถํ ์์น(Least Privilege) โ DBยท์๋ฒ ๋ณด์์ ํต์ฌ ์ ๋ต

๐ ์ฌ์ฉ์ ๊ถํ ๊ด๋ฆฌ์ ์ต์ ๊ถํ ์์น(Least Privilege) โ DBยท์๋ฒ ๋ณด์์ ํต์ฌ ์ ๋ต
DB / ์๋ฒ ๋ณด์ | ํ๋ก๊ทธ๋จ ๊ฐ๋ฐ
๐ก๏ธ ๋ณด์ ๐๏ธ DB โ๏ธ ์๋ฒ ๐ ๊ถํ๊ด๋ฆฌ์ผ ์์งํ ๋งํด์, ๊ฐ๋ฐํ๋ค ๋ณด๋ฉด ๊ถํ ์ค์ ๊ท์ฐฎ์์ ๊ทธ๋ฅ root ๊ณ์ ์ผ๋ก ๋ค ์ฐ๊ฒฐํด๋ณธ ์ ์์ง ์์? ใ ใ ใ ๋๋ง ๊ทธ๋ฐ ๊ฑฐ ์๋์์์. ๊ทผ๋ฐ ๊ทธ๊ฒ ์ผ๋ง๋ ์ํํ ์ง์ธ์ง ์๊ณ ๋๋ฉด ์ง์ง ๋ฑ๊ณจ์ด ์๋ํด์ง ๐ฑ
์ค๋์ ์ฌ์ฉ์ ๊ถํ ๊ด๋ฆฌ์ ์ต์ ๊ถํ ์์น(Principle of Least Privilege, PoLP)์ ๋ํด์ DBยท์๋ฒ ๊ด์ ์์ ์์ ์ ๋๋ก ํํค์ณ๋ณผ ๊ฑฐ์ผ. ์ด๊ฑฐ ๊ทธ๋ฅ ์ด๋ก ์๊ธฐ๊ฐ ์๋๋ผ ์ค์ ๋ก ํ์ ์์ ์ด๋ป๊ฒ ์ฐ์ด๋์ง, ์ ์ค์ํ์ง, ์ด๋ป๊ฒ ๊ตฌํํ๋์ง๊น์ง ๋ค ๋ด์์ผ๋๊น ๋๊น์ง ์ฝ์ด๋ด ๐
๐ ์ต์ ๊ถํ ์์น(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๊ฐ์ง ํต์ฌ ์์
์ ๋ฌด ์ํ์ ํ์ํ ๊ธฐ๋ฅ๋ง ํ์ฑํ. ์ ์ฐ๋ ๊ธฐ๋ฅ์ ๋นํ์ฑํํ๊ฑฐ๋ ์ ๊ฑฐ.
ํ์ํ ๋ฆฌ์์ค์๋ง ์ ๊ทผ ๊ฐ๋ฅํ๋๋ก ์ ํ. ๋ค๋ฅธ DB, ๋ค๋ฅธ ํ ์ด๋ธ, ๋ค๋ฅธ ์๋ฒ๋ ์ ๊ทผ ๋ถ๊ฐ.
ํ์ํ ์๊ฐ ๋์๋ง ๊ถํ ์ ์ง. ์์ ์๋ฃ ํ ์ฆ์ ๊ถํ ํ์. ์์ ๊ถํ์ ๋ง๋ฃ ์๊ฐ ์ค์ .
๐ฏ ์ ์ด๊ฒ ์ค์ํ๊ฐ? โ ์ค์ ์ฌ๊ณ ์ฌ๋ก
์ด๋ก ๋ง ์๊ธฐํ๋ฉด ์ฌ๋ฏธ์์ผ๋๊น ์ค์ ์ฌ๋ก ๋ณด์ ๐ค
๐ ์ฌ๋ก 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';
๐ 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์ด์ด์ผ ํด!
๐ญ 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
๐จ ๊ฐ๋ฐ์๋ค์ด ์์ฃผ ํ๋ ๊ถํ ๊ด๋ จ ์ค์ 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 ์ฃผ์ ๋ฑ์ ๋ถ์ํ์ฌ ๋ถ๋ฒ์ ์ธ ๊ฐ์ธ์ ๋ณด ์ ์ถ ์๋ ํ์ง
๐ฏ ํต์ฌ ์ ๋ฆฌ โ ์ค๋ ๋ฐฐ์ด ๊ฒ๋ค
์ ์ง์ง ๊ธธ์๋ค ใ ใ ใ ์ฌ๊ธฐ๊น์ง ์ฝ์ด์ค์ ๊ณ ๋ง์! ๋ง์ง๋ง์ผ๋ก ํต์ฌ๋ง ์์ ์ ๋ฆฌํด์ค๊ฒ ๐
1975๋ ๋ถํฐ ๊ฒ์ฆ๋ ์์น. ํดํน ํผํด๋ฅผ ์ต์ํํ๋ ๊ฐ์ฅ ํจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ์ด์ผ.
์ฝ๊ธฐ ์ ์ฉ, ์ฐ๊ธฐ, ๋ฐฐ์น, ๋ฐฐํฌ ๋ฑ ์ฉ๋๋ณ๋ก ๊ณ์ ์ ๋๋๊ณ ๊ฐ๊ฐ ํ์ํ ๊ถํ๋ง ๋ถ์ฌ.
root ์ง์ ์ฌ์ฉ ๊ธ์ง, ์๋น์ค๋ณ ์ ์ฉ ๊ณ์ , sudo ์ต์ํ, ํ์ผ ๊ถํ ์ ์ ํ ์ค์ .
์ญํ ์ ๊ถํ์ ๋ฌถ๊ณ ์ฌ์ฉ์์๊ฒ ์ญํ ์ ๋ถ์ฌํ๋ ๋ฐฉ์์ผ๋ก ๊ด๋ฆฌ ํจ์จ์ฑ ํฅ์.
๊ถํ ์ค์ ํ ์ง์์ ์ธ ๊ฐ์ฌ ์์ด๋ ์๊ฐ์ด ์ง๋๋ฉด์ ๊ถํ ํฌ๋ฆฌํ ๋ฐ์. ์ ๊ธฐ ๋ฆฌ๋ทฐ ํ์.
Terraform ๋ฑ์ผ๋ก ๊ถํ ์ค์ ์ ์ฝ๋๋ก ๊ด๋ฆฌํ๋ฉด ๋ฒ์ ๊ด๋ฆฌ, ๋ฆฌ๋ทฐ, ์๋ํ๊ฐ ๊ฐ๋ฅํด์ง.
๊ถํ ๊ด๋ฆฌ๋ ์ฒ์ ์ค์ ํ ๋ ์ข ๊ท์ฐฎ๊ณ ๋ฒ๊ฑฐ๋กญ๊ฒ ๋๊ปด์ง ์ ์์ด. ๊ทผ๋ฐ ํ ๋ฒ ์ ๋๋ก ํด๋์ผ๋ฉด ๋์ค์ ์ฌ๊ณ ๋ฌ์ ๋ "์ ๊ทธ๋ ์ ๋๋ก ํด๋จ์ผ๋ฉด..." ํ๋ ํํ๋ฅผ ์ ํด๋ ๋ผ ๐ค
๊ฐ๋ฐ์๋ก์ ์ฝ๋ ํ์ง๋งํผ์ด๋ ๋ณด์ ์ค๊ณ๋ ์ค์ํ ์ญ๋์ด์ผ. ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์๋ ๋ค์ํ ๋ณด์ ์ ๋ฌธ๊ฐ๋ค์ด ํ๋ํ๊ณ ์์ผ๋, ๋ ๊น์ด ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด ์ ๋ฌธ๊ฐ์ ๋์์ ๋ฐ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ ๐
"๋ณด์์ ์ ํ์ด ์๋๋ผ ํ๋ก์ธ์ค๋ค." โ Bruce Schneier
ํ ๋ฒ ์ค์ ํ๊ณ ๋์ด ์๋๋ผ, ์ง์์ ์ผ๋ก ๊ฒํ ํ๊ณ ๊ฐ์ ํ๋ ํ๋ก์ธ์ค๋ก ๋ง๋ค์ด์ผ ์ง์ง ๋ณด์์ด ๋๋ ๊ฑฐ์ผ. ์ค๋๋ถํฐ ๋น์ฅ ์ฌ๋ฌ๋ถ์ DB ๊ณ์ ๊ถํ ํ๋ฒ ํ์ธํด๋ด! ํน์ root ๊ณ์ ์ผ๋ก ์ฑ ์ฐ๊ฒฐํ๊ณ ์๋ ๊ฑฐ ์๋์ง? ใ ใ ใ ๐
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.