Linux Upskill 7. 로그 회전, inode & symbolic link, shell script

jiffydev·2021년 4월 10일
0

linux upskill

목록 보기
7/8

본 포스트는 박재호님의 유튜브 강의를 보고 개인적으로 정리한 내용입니다.

1. 로그 파일 회전시키기

리눅스로 서버를 운영하면 로그를 통해 문제를 파악하는 경우가 많다. 이럴 때를 대비해 항상 로그를 쌓도록 되어 있는데, 매일매일의 로그가 쌓이다보면 엄청난 양이 되기에 이를 처리하는 과정도 필요하다.
리눅스에서는 로그 회전을 뜻하는 logrotate를 통해 일정 주기마다 쌓인 로그를 처리하고 새 로그가 입력될 수 있도록 한다.
이번 포스트에는 이 로그 회전에 대해 공부해 보고자 한다.

1-1. 명령어 실습

1-1-1. logrotate

우선 daily log가 어떻게 저장되고 처리되는지 알아보자.

ubuntu@ip-172-26-9-205:~$ less /etc/cron.daily/logrotate

위 파일을 읽으면 다음과 같은 내용이 뜰 것이다.

하단을 보면 /user/sbin/logrotate/etc/logrotate.conf 이라는 configuration(설정) 파일을 읽어서 실행된다는 것을 유추할 수 있다.
그러면 설정 파일을 확인하자.

ubuntu@ip-172-26-9-205:~$ less /etc/logrotate.conf


하이라이트 된 부분과 설명을 보면, 로그 회전한 정보를 /etc/logrotate.d에 넣는다고 되어 있다. 그러면 저 디렉토리에 우리가 원하는 로그 회전과 관련된 정보가 들어 있을테니 그 안에서 apache2 파일을 읽어보자.

ubuntu@ip-172-26-9-205:~$ less /etc/logrotate.d/apache2


그러면 다음과 같은 스크립트가 나올 것이다. 이 내용을 분석하기 전에, 이미 저장된 로그들이 이 스크립트의 방식과 일치하는지 보기 위해 지금까지 로그가 어떻게 쌓여 왔는지 확인해 보겠다.

.log에 숫자 없는것과 1 붙은 것이 있고, 빨간색으로 된 파일들은 숫자가 2이상에 gzip으로 압축되어 있다. 이 부분을 잘 기억하면서 다시 스크립트로 돌아와서 한 문장씩 살펴보자.
우선 이 스크립트는 *.log 즉 모든 로그 파일에 대해 적용된다.

  • daily: 매일 logrotate를 실행
  • missingok: 로그파일이 없어도 문제 없음
  • rotate 14: 14일에 한 번씩 순회하며 그보다 오래된 파일은 삭제. 만약 daily->weekly, 14->52로 바꾸면 일주일에 한 번씩 실행하고 1년동안 파일을 보관하게 된다.
  • compress: 회전될 로그파일을 압축
  • delaycompress: 회전되면서 바로 압축하지 않고 한 텀 쉬고 다음부터(여기서는 daily로 설정했으므로 1일 쉼) 압축하게 된다. 실제로 위에서 로그 파일을 보면 .log.1은 압축되어 있지 않다.
  • notifyempty: 파일이 비어있으면 회전하지 않는다.
  • create 640 root adm: 권한을 사용자/그룹/기타 6/4/0으로 만들고 사용자를 root, 그룹을 adm으로 설정한다. 여기서 권한을 숫자로 표시하고 있는데 이는 8진법으로 read=4, write=2, execute=1에 해당한다. 그래서 각 숫자를 더하게 되면 가능한 권한이 무엇인지 알 수 있다.
  • sharedscripts: 로그 파일이 여러개 있을 경우에도 밑의 postrotate, prerotate 스크립트를 한 번만 수행한다.
  • prerotate: 회전하기 전에 /etc/logrotate/httpd-prerotate 파일이 존재하면 이를 실행하는데, 현재는 없으므로 prerotate는 수행되지 않음
  • postrotate: 회전 후 apache2의 status를 확인해서, 디바이스별로 표준 출력을 안보이게(/dev/null) 만들고 표준 오류를 표준 출력으로 바꾼다.(2>&1) 즉 오류가 발생하면 이를 표준 출력으로 바꾸고, reload 하도록 한다.

2-1. 명령어 실습

2-1-1. inode

inode는 리눅스의 파일시스템에서 사용하는 자료구조를 말한다. 모든 파일이나 디렉토리는 1개의 inode를 가지고 있고, 각 inode에는 해당 파일/디렉토리의 소유권, 접근권한, 위치 등의 정보가 저장되어 있다.

우선은 inode가 어떻게 생겼는지부터 보도록 하자. ls 명령에서 -i옵션이 inode를 표시하는 옵션이다.

ubuntu@ip-172-26-9-205:~$ ls -li /etc/hosts

여기서 가장 앞에 붙은 숫자(213)가 inode를 의미한다.
이제 이 파일의 더 자세한 상태를 보기 위해 stat 명령으로 들여다보자.

ubuntu@ip-172-26-9-205:~$ stat /etc/hosts


상태에서 유심히 봐야 할 부분은 Inode, Access, Uid, Gid이다.

이제 passwd 파일을 가지고 실습을 진행할 것이다. 우선은 stat으로 상태를 확인하면

inode는 12060이다.(이는 사용자마다 다를 수 있다.)

inode에 대해 추가적인 설명을 하자면, inode는 모든 파일에 붙어 있는 고유한 식별자이므로 inode를 통해 파일을 구별할 수 있다. 또한 inode는 파일시스템을 포맷하면 자동으로 생성되어, 번호가 미리 할당되어 있고 이 번호를 끌어다 사용하게 된다.
한편 inode를 고유한 식별자라고 했지만, 이는 한 파일시스템 내에서의 이야기이고 여러 파일시스템 사이에서는 유일성을 보장하지 않는다.

2-1-2. inode로 하드링크 걸기

inode를 사용해 두 파일 간에 하드링크를 걸 수 있는데, 하드링크란 두 파일이 하나의 inode를 바라보도록 만드는 것이다.
sudo ln명령을 통해 링크를 생성하게 되는데, 이 링크는 target 파일과 같은 inode를 갖게 된다.

ubuntu@ip-172-26-9-205:~$ sudo ln /etc/passwd link1


link1과 /etc/passwd는 분명 별개의 파일처럼 보이지만 같은 inode를 바라보는 것을 알 수 있다.
이러한 하드링크는 복사와는 전혀 다른데, 복사본은 원본을 수정하더라도 바뀌지 않지만 하드링크를 통한 inode의 공유는 원본이 바뀌면 링크된 파일도 바뀌게 된다.
그리고 위에서 설명했듯이 inode는 파일의 위치정보도 포함하기 때문에, 파일을 이동해도 링크는 이제 맞추어 따라다니게 된다.(같은 파일시스템이라는 전제 하에)
만약 다른 파일시스템으로 옮기게 된다면 위 이미지에서 보이는 Links의 숫자가 -1 될 것이다.

심볼릭 링크는 하드링크와는 다르게, 같은 inode를 공유하는 것보다는 약한 단계로 원본 파일의 위치에 대한 포인터만 포함되는 링크이다.
심볼릭 링크의 사용법은 두 가지 정도로 나눌 수 있다.

  1. 첫번째로 여러 버전이 있을 때 특정 버전을 실행할 수 있도록 표준적인 형태로 만들어 그것을 실행하도록 하며, 필요에 따라 버전을 바꿀 수 있게 하는 용도가 있다. 예를 들어 java도 버전이 여러개가 있는데 때에 따라 다른 버전을 사용해야 할 경우, 매번 버전을 붙여서 실행시키는 것도 귀찮을 것이다.
    이럴 때 심볼릭 링크로 내가 필요한 버전을 걸어 놓으면 버전을 붙이지 않아고 java 실행 명령으로 내가 필요한 버전이 실행되도록 할 수 있다.

  2. 두번째로는 실행 파일이 두 가지 일을 해야하는데, 이를 파일 이름에 따라 나누어 실행하도록 하는 용도가 있다. 예를 들어 vim의 경우 vi 명령이면 편집기를 실행하고 view 명령이면 읽기만 하는 용도로 쓰이는데, 같은 vim을 심볼릭 링크로 두고 있다.


    이처럼 vim, vi, view 모두 같은 /usr/bin/vim.basic 파일을 심볼릭 링크로 두고 있다. 여기에서 어떤 명령을 실행하느냐에 따라 동작방식이 결정되도록 정해놨을 것이다.

이제 심볼릭 링크를 걸어 볼텐데, 당연하겠지만 하드링크와는 다르게 sudo 명령이 필요 없다.
그리고 파일시스템이 달라져도 링크를 유연하게 걸 수 있다는 것도 장점이다.

ubuntu@ip-172-26-9-205:~$ ln -s /etc/passwd link2

link2에 /etc/passwd가 심볼릭 링크로 걸린 것을 볼 수 있다.
또한 link1과 원본은 inode가 같은 반면 link2는 다른 inode가 만들어 지면서 포인터만 가지고 있는 것을 확인할 수 있다.

하드링크는 분명 장점도 있지만 제약사항도 많기 때문에, 실제 시스템 관리상에서는 대부분 심볼릭 링크를 사용하게 된다.

3. shell script

3-1. 명령어 실습

우선은 실제로 작성된 셸 스크립트를 보면서 하나씩 이해해 나가도록 하자. 이번에 작성한 스크립트는 가장 마지막으로 실패한 로그인 시도를 보여주는 attacker라는 프로그램이다.
vi attacker로 편집기를 열고 다음과 같이 작성하면 된다.

#!/bin/bash
#
#   attacker - prints out the last failed login attempt
#
echo "The last failed login attempt came from IP address:"
grep -i "disconnected from" /var/log/auth.log | tail -1 | cut -d: -f4 | cut -f7 -d" "

셸 스크립트를 한 번이라도 봤다면 맨 위에 #!/bin/bash는 기억이 있을 것이다. #(shebang)으로 시작하는 저 문구는 아래 스크립트를 실행하는 인터프리터를 /bin/bash로 설정하겠다는 뜻이다.
echo는 뒤의 메시지를 출력하겠다는 뜻이고, grep은 이미 배웠다시피 해당 문자열을 검색해 출력해준다. 그래서 "disconnected from"이라는 문자열을 /var/log/auth.log에서 case insensitive(-i)하게 검색하라는 뜻이다.

그리고 tail은 전체 내용 중 마지막 10줄을 출력하는데 여기서는 -1을 붙였으므로 마지막 한 줄만 출력하게 된다. cut에서 -d는 구분자(delemiter)로, 여기서는 우선 콜론(:)을 구분자로 삼아 4번재 필드(f)를 가져오고, 다시 그 필드 안에서 공백(" ")을 구분자로 삼아 7번째 필드를 가져오겠다는 뜻이 된다.

이제 작성이 완료되었으니 파일을 실행해 볼 차례다. 실행을 위해서는 두 가지 방법이 있는데, 하나는 bash ./attacker로 실행하는 방법이고 다른 방법은 파일 권한을 변경해 실행 권한을 줘서 파일명만 입력하면 되도록 하는 방법이다.
여기서는 후자를 채택하여 chmod로 먼저 권한을 줄 것이다.

ubuntu@ip-172-26-9-205:~$ chmod +x attacker

여기서 원래는 ugo를 선택(혹은 전부)하여 +x를 해줄 수도 있지만, 전부 다 권한을 부여해도 문제 없으니 대상은 생략하고 +x만 입력해서 전부에게 실행 권한을 부여했다.

그리고 파일을 실행해 보면 다음과 같이 나올 것이다.

ip를 직접 보여주고 싶다면 -f4로 바꾸면 된다.

그러면 이제 가장 최근 로그인 실패가 아니라 가장 많이 실패한 사람을 찾는 스크립트를 작성해보자.
아까처럼 우선 작성된 스크립트를 확인하고 하나씩 설명해 나가도록 하겠다.

#!/bin/bash
#
##  	topattacker - list the most persistent attackers
#
if [ -z "$1" ]; then
echo -e "\nUsage: `basename $0` <num> - Lists the top <num> attackers by IP"
exit 0
fi
echo " "
echo "Persistant recent attackers"
echo " "
echo "Attempts   	IP "
echo "-----------------------"
grep "Disconnected from authenticating user root" /var/log/auth.log | cut -d: -f4 | \
cut -d" " -f7 | sort | uniq -c | sort -nr | head -$1

위와 같이 작성하고 동일하게 chmod로 실행 권한을 부여해 준 뒤 실행해 보면 뭔가 이상할 것이다.

뭔가 허전한데다가 <num>이라는 뭔가 변수같아보이는 것도 있다. 의미상 공격자의 숫자를 물어보는 것 같으니 인자로 숫자를 한번 줘 보자.

잘 나오는 것을 볼 수 있다. 제일 많은 놈은 40번씩 서버에 공격을 시도했다 ㄷㄷ

이제 본격적으로 스크립트를 확인해 보자. 사실 내용은 아까와 크게 다르지는 않고 가장 많은 시도를 한 순서로 정렬하는 내용 정도가 추가되어 있다.

if [ -z "$1" ]; then
echo -e "\nUsage: `basename $0` <num> - Lists the top <num> attackers by IP"
exit 0
fi

우선 이 부분은 우리가 인자로 아무것도 주지 않았을 때를 처리하는 곳이다. $1은 첫 번째 인자라는 뜻이므로 ./topattacker 다음에 올 인자를 말한다. -z는 셸 스크립트에서 if문 안에서 '다음 문자열의 길이가 0이면 참' 이라는 뜻이다. 따라서 인자를 아무것도 주지 않으면 길이가 0일 것이고, 참이 되어 then 이후가 실행된다.

\nUsage: `basename $0`

이 부분은 실행파일에서 파일의 절대경로를 다 가져오는 것이 아니라 실행파일 이름만 따오는 부분으로 $0을 사용해 인자도 제외하고 이름만 가져왔다. 또한 백틱(``)은 echo -e와 같이 사용해 basename $0 명령을 해석해, 그 결과를 치환해주는 역할을 하기 때문에 살행하면 파일명이 나오는 것이다.

echo가 실행되고 exit 0으로 파일이 종료된다. 그런데 셸 스크립트에서는 if를 썼으면 반드시 마지막에 fi로 닫아야 하는 점에 주의하자.

이제 grep을 보면

grep "Disconnected from authenticating user root" /var/log/auth.log | cut -d: -f4 | cut -d" " -f7

여기까지는 기존과 같다. (실행해 보면 ip주소가 주르륵 나올 것이다)

| sort | uniq -c | sort -nr | head -$1

이후 부분에 대해서는 우선 sort로 정렬을 해 준 후, uniq -c를 통해 AAA BB가 있을 때 A 3개 B 2개와 같이 유일한 것들의 개수를 세 준다.

그리고 다시 정렬을 하는데 -n은 숫자로 만들어 정렬하라는 뜻으로, 4와 19가 있을 때 숫자로 만들지 않으면 4와 19의 1을 비교해 4가 더 큰 숫자가 되어 정렬이 망가질 우려가 있기 때문에 사용한다. -r은 내림차순으로 정렬하라는 뜻이다.

마지막으로 head는 이미 알고 있듯이 파일의 처음 10개를 출력하는데, 여기서는 -옵션으로 인자 $1을 통해 받은 개수만큼 출력하도록 했다.

셸 스크립트를 작성할 때는 처음부터 끝까지 완벽하게 작성하려고 하면 머리가 아플 것이다. 그럴 때는 핵심이 되는 비즈니스 로직을 먼저 작성한 후 조건을 붙여나가며 확장하는 방법으로 작성해 나가면 조금은 작성이 쉬워질 것이다. (물론 필자에게도 아직 어렵다ㅠ)

profile
잘 & 열심히 살고싶은 개발자

0개의 댓글