MySQL 사용자 보안 설정하기 (Securing MySQL User Accounts)
이 글을 통해서 MySQL 사용자 보안을 향상하는 방법에 대해 알아 보겠습니다.
글을 작성하고 있는 현재, 활성화 된 LTS 버전은 MySQL 8.0 또는 MySQL 8.4 이므로 이 버전을 기준으로 다루겠습니다.
Infra Level 의 보안설정인 쿼리로그, 감사로그(audit log), general log 하는 방법은 이 글에서는 다루지 않습니다.

이 글에서는 MySQL 소프트웨어 에서 사용자 계정 (User Accounts) 보안을 향상시키는 방법에 대해서 알아보겠습니다.
최고 관리자(root)와 일반 사용자 전환이 빈번하니, 실습할 때 주의해 주세요.
비밀번호 변경 테스트
테스트 할 계정 생성

CREATE USER 'laeltest01'@'%' IDENTIFIED WITH caching_sha2_password BY 'laeltest01';
먼저 비밀번호 변경이 잘 되는지 테스트 하겠습니다.
방금 생성한 계정 확인 (root)
SELECT `Host`, `User`, `password_expired`, `password_last_changed` FROM `mysql`.`user` WHERE `User` = 'laeltest01';
MySQL 에서는 아무런 권한이 없어도, 자신의 비밀번호를 변경할 수 있습니다.
laeltest01 계정으로 로그인 한 후 다음의 쿼리 실행 (laeltest01)
ALTER USER USER() IDENTIFIED BY 'my-new-password@';
# 이 명령어로도 사용 가능. 하지만 ALTER USER 가 권장됨. (명확하게 하기 위해) SET PASSWORD = 'my-new-password@';

< 일반 사용자는 자신의 비밀번호를 변경할 수 있으며, mysql.user 테이블에 `password_last_changed` 값이 변경된 것을 확인 할 수 있음 >
비밀번호 변경 테스트를 완료했습니다.
비밀번호 만료 테스트
비밀번호를 강제로 만료 시킬 수 있습니다.
MySQL Workbench 를 사용할 경우 이 버튼을 누르면 비밀번호가 만료됩니다. (주의 : 확인 창 없이 즉시 만료되므로 조심히 누를 것)

사용자의 비밀번호를 만료시키는 SQL 쿼리는 다음과 같습니다. (root)
ALTER USER 'laeltest01'@'%' PASSWORD EXPIRE;

내부적으로는 mysql.user 테이블에서 password_expired 값만 Y 로 변경됩니다.
비밀번호가 만료된 사용자 테스트 (laeltest01)
비밀번호가 만료되면 로그인은 됩니다. 로그인 후 비밀번호 변경을 제외한 모든 명령어가 거부됩니다.
Please Note : MySQL 은 커넥션을 한 후 쿼리를 실행하는 방식인데, 이미 연결된 커넥션은 만료의 설정이 반영되지 않습니다. New Connection 에만 적용됨.
ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.

MySQL Workbench 같은 일부 클라이언트들은, 접속시 비밀번호가 만료된 경우 친절하게 비밀번호 변경 창을 표시해줍니다.

비밀번호가 만료 되었을 때 어떤 동작이 일어나는지 확인했고, 만료에서 벗어나는 방법을 알아보았습니다.
비밀번호 자동 만료 설정
비밀번호가 자동으로 만료되게 설정해봅시다. 이것은 비밀번호의 수명(password lifetime)을 설정해야합니다.
아쉽게도 mysql gui client 에서는 관련 항목이 없으므로, 이 작업은 모든 부분 SQL 쿼리로만 진행해야 합니다.
-- 셋 중 하나 실행 ALTER USER 'laeltest01'@'%' PASSWORD EXPIRE; # 즉시 만료 ALTER USER 'laeltest01'@'%' PASSWORD EXPIRE NEVER; # 만료 안함 ALTER USER 'laeltest01'@'%' PASSWORD EXPIRE INTERVAL 180 DAY; # 180일 후 만료
일반적으로, 서비스 계정은 만료하지 않습니다. 즉, 수동 교체.
사용자 계정은 만료를 설정해야 합니다.
이 글에서는 laeltest01 사용자의 비밀번호가 180일 후에 만료되도록 설정하겠습니다.
ALTER USER 'laeltest01'@'%' PASSWORD EXPIRE INTERVAL 180 DAY;
만료 설정 확인
SELECT `Host`, `User`, `password_expired`, `password_last_changed`, `password_lifetime` FROM `mysql`.`user` WHERE `User` = 'laeltest01';

이제 180일 후에 확인해 보세요.
사용자 잠금 설정 테스트 (MySQL Account Lock)
사용자를 잠금 설정하면 새로운 로그인이 차단됩니다.
이미 연결되어 있는 커넥션은 잠금 설정의 영향을 받지 않습니다.
잠금에는 ACCOUNT LOCK 과 PASSWORD LOCK 이 있습니다.
ACCOUNT LOCK 테스트
laeltest01 사용자 잠그기
ALTER USER 'laeltest01'@'%' ACCOUNT LOCK;
laeltest01 사용자 잠금 해제
ALTER USER 'laeltest01'@'%' ACCOUNT UNLOCK;
잠근 계정을 로그인 할 때, 다음과 같은 메세지가 표시됩니다.
ERROR 3118 (HY000): Access denied for user 'laeltest01'@'localhost'. Account is locked.

계정이 비록 잠겨 있더라도, 로그인 시도는 할 수 있기 때문에, 보안적인 측면에서는 안좋습니다.
사용하지 않는 계정은 lock 대신 drop 하거나, lock 으로 관리하려면 unlock 할 때 반드시 비밀번호를 변경해주세요.
PASSWORD LOCK 테스트
연속 5회 실패시 1일간 로그인 시도 잠금
ALTER USER 'laeltest01'@'%' FAILED_LOGIN_ATTEMPTS 5 PASSWORD_LOCK_TIME 1;
PASSWORD_LOCK_TIME 의 단위는 Day 입니다. 정확하게 24시간 카운트. 시간 / 분 단위 설정 불가능.

PASSWORD LOCK 관련 상태 변수는 엔진의 메모리에서 관리됩니다.
PASSWORD LOCK 푸는 방법
- PASSWORD_LOCK_TIME 지나면 자동으로 풀림
- MySQL 을 재시작하면 풀림
- 새로운 PASSWORD LOCK 정책을 적용하면 풀림. (즉, 위의 ALTER USER 쿼리 다시 실행시 풀림)
- ACCOUNT 를 UNLOCK 할 때 풀림. (LOCK 할 때는 안풀림.)
PASSWORD LOCK 설정 조회
SELECT `Host`, `User`, `User_attributes` FROM `mysql`.`user` WHERE `User` = 'laeltest01';

결론
- password_last_changed 주기적으로 확인 할 것
- 일반 사용자는 password_lifetime 설정 할 것
- 당신이 부지런하다면 서비스 계정도 password_lifetime 설정 할 것. (제 때 변경하지 못하면 장애가 발생할 수 있음을 인지)
- Account lock 은 사용할 일이 거의 없다. Password lock은 연속 5회 실패시 1일 잠금이 이상적
password expire 나 account lock, password lock 은 이미 연결된 connection 에는 영향을 주지 않음.
다른 사람이 내 계정에 로그인 시도 할 수 없도록, 되도록 MySQL 사용자 `Host` 에는 ‘%’ 를 사용하지 말 것.
요약
다음의 쿼리를 실행
SELECT `Host`, `User`, `plugin`, `password_expired`, `password_last_changed`, `password_lifetime`, `account_locked` FROM `mysql`.`user`;
사용자 계정, 서비스 계정, 시스템 계정(손 못대는것) 식별
사용자 계정에 대해서 다음을 실행
ALTER USER 'user01'@'%' PASSWORD EXPIRE INTERVAL 180 DAY; ALTER USER 'user01'@'%' FAILED_LOGIN_ATTEMPTS 5 PASSWORD_LOCK_TIME 1;
서비스 계정에 대해서 다음을 실행
ALTER USER 'service01'@'%' PASSWORD EXPIRE NEVER; ALTER USER 'service01'@'%' FAILED_LOGIN_ATTEMPTS 0;