나는 2008년부터 유지해온 도메인 한개와 2023년부터 새롭게 추가한 도메인 한개로 총 2개의 도메인을 소유하고 있다.
이들 도메인을 통해 web, ftp 서버들을 운영중이며 해당 도메인들에 무료 인증서들을 설치하여 ssl 서비스를 제공하고 있었다. 그리고 무료 인증서는 Let's Encrypt 라는 인증 기관을 통해서 제공 받고 있다.
위 내용은 수년동안 잘 이용하던 무료 보안 서비스에 대한 개요의 시작이며 여기엔 Let's Encrypt, Certbot, Win-ACME 등의 키워드들이 등장한다. 그래서 프롤로그는 copilot 이 설명해준 위 키워드들에 대한 정의로 시작한다.
Prologue
ChatGPT 는 너무나 편리하다. 항상 내 옆에서 내가 필요로 하는 소소한 정보들을 자세하게 친절히 알려준다. 수년간 Let's Encrypt 를 통해 무료 인증서를 발급 받아 사용해 왔지만 발급받기 위해 사용한 certbot 이라는 프로그램조차 어디서 만들고 인증서를 어떤 경유로 받아오는지 알지 못했었다. ^^; 아래와 같이 copilot 이 정리해준 덕분에 내가 이용하는 프로그램의 특성도 쉽게 알 수 있었다.
Let’s Encrypt는 무료 SSL/TLS 인증서를 발급하는 인증 기관입니다. 이때, Let’s Encrypt에서는 ACME(Automatic Certificate Management Environment) 프로토콜을 사용하여 지정된 도메인 이름을 제어하는지 확인하고 인증서를 발급합니다.
Certbot과 Win-ACME는 모두 ACME 프로토콜을 구현한 클라이언트 도구로, 이 도구들을 통해 사용자는 ACME를 지원하는 인증 기관에서 SSL/TLS 인증서를 쉽게 관리할 수 있습니다.
Certbot은 전자 프론티어 재단 (EFF)에서 개발한 무료 오픈 소스 도구로, SSL/TLS 인증서를 요청하거나 취소하는 데 사용됩니다. Certbot은 ACME 프로토콜을 사용하여 인증서를 발급받습니다. 대부분의 경우 Certbot 클라이언트에서 시작할 것을 권장하며, 원하는 항목에 따라 간단히 인증서를 받거나 설치에도 도움이 될 수 있습니다.
Win-ACME는 Windows 환경에서 Let’s Encrypt 인증서를 쉽게 설치하고 관리할 수 있도록 도와주는 도구입니다. Win-ACME는 ACME 프로토콜을 구현하며, 이 도구를 통해 사용자는 Windows 서버에서 SSL/TLS 인증서를 쉽게 발급하고 관리할 수 있습니다.
따라서, Certbot과 Win-ACME는 모두 Let’s Encrypt와 같은 ACME를 지원하는 인증 기관에서 SSL/TLS 인증서를 발급받는 데 사용되는 도구입니다. 이 두 도구는 각각 리눅스와 윈도우 환경에서 사용되며, 사용자의 요구에 따라 적절한 도구를 선택하여 사용할 수 있습니다.
인증서 갱신의 실패
그동안 무료 인증서를 발급 받으면서 3년여동안 사용한 프로그램은 certbot 이었다. certbot 은 여러 os 환경을 지원해주는데 나는 관리 편이상 windows 환경을 선호하였기에 windows 버전의 certbot 을 애용하였고 이에 대한 배포 절차 문서까지 작성해서 인증서 관리를 매우 효율적으로 작업하였다.
초기에 구축했던 환경은 docker 를 이용한 linux 환경이었는데, linux 에서 인증서 파일을 발급받아 windows 로 옮기는 번거로움과 가상 환경의 os 도 관리해야만 하는 관리 포인트의 복잡성이 너무나 불편해서 순수 windows 로만 관리하게끔 certbot 프로그램을 windows 용으로만 사용하고 있었다.
그런데 바로 어제 올해 2월에 발급받은 인증서가 만료됨에 따라서 새로운 인증서로 갱신하기 위해 문서를 열어 그동안 수없이 수행했던 절차를 진행했는데, 하루 꼬박 걸려 시도해봐도 좀처럼 새로운 인증서 갱신이 성공하지를 못했다. 그리고 오늘 오전에서야 그 원인을 알게 되었고, 그에 대한 기록을 이렇게 남겨놓는다.
사실 위와 같은 문제는 작년말 인증서를 갱신할때도 일시적인 장애로 동일한 문제를 겪었던 기억이 있는데, 단 며칠만에 정상화되길래 당시에 별다른 조치없이 그대로 현재까지 인증서 갱신 절차를 유지해왔었다. 하지만 어제 재발급 시도하면서 동일한 오류를 또 접하면서 이번 문제는 쉽게 해결될 수 없음을 직감하였다.
인증서 발급시 내가 겪은 오류는 dns txt record 변경을 통해 let's encrypt 의 dns challenges 를 시도시 failed 하는 오류였다. 이것은 나의 도메인을 호스팅하는 업체에 연결된 name server 와 전세계 dns 서버들에 저장된 txt record 값이 설정한 값과 일치하지 않아 발생하는 오류이다. dig나 nslookup 으로는 정상적으로 출력되나 certbot 에서 엔터키를 입력한 순간엔 부정한 값으로 failed 되버리는 것이다.
원인은 알지만 해결책은... 쉽지 않다. 도메인 서버와 네임 서버 그리고 dns 서버까지 이들이 가진 데이터를 한번에 통일시키기 보다는 다른 방법을 찾는게 손쉬울 것 같다. (하루를 기다려도 모든 dns 서버들이 변경한 record 값을 가지지 못한다) 그래서 오류에 대한 구글링의 정보들을 살펴보았다. 수많은 문서들 중에 두개의 문서 정도가 잘 정제된 내용으로 보였다.
물론 그렇다고해서 위 문서들로 해결책을 찾은 것은 아니다. 왜냐하면 나는 도메인을 구매한 회사와 내 도메인의 포워딩 정보를 관리하는 회사가 다르기 때문에 이들을 통해 문제를 해결한다는 것은 매우 난해한 문제를 가지고 있다.
그래서 결과적으로는 dns 의 txt record 수정이 필요한 와일드카드(wildcard) 도메인 인증서를 포기하기로 최종 결정하였다.
win-acme 의 사용
와일드카드(wildcard) 인증서를 포기하기 전까지만 해도 나는 혹시 certbot 이 아닌 win-acme 로 발급 시도하면 어떨까? 혹시 성공할까? 기대를 했었다. 하지만 검색으로 찾아낸 여럿 문서들은 하나같이 내가 아는 수준에서의 발급 절차만 기재되었을뿐 별다른 추가 내용을 찾을 수 없었고 당연하게도 동일한 절차에서는 win-acme 로도 와일드카드(wildcard) 인증서는 동일한 오류로 인해 발급 받을 수 없었다.
메인 도메인과 서브 도메인들에 대해 각각 적용될 인증서를 발급 받는다면 와일드카드(wildcard) 도메인 인증서가 필요하지 않다. 나는 두개의 도메인을 소유하고 있고 서브 도메인은 세개가 있으므로 총 5개의 도메인에 대해서만 인증서를 발급받으면 된다. 갯수가 얼마 안되니 굳이 와일드카드(wildcard) 인증서에 연연하지 않아도 되었던 것이었다.
Epilogue
그렇게 인증서 발급 프로그램과 운영 정책을 바꾸면서 나는 이제 행복해졌다. 내 기억엔 2년전에 처음 dns txt record 오류를 만났을때 장장 보름을 고생했던 기억이 있다. 그때의 작업으로 인증서 발급 절차까지 수립하면서 2년을 무사고로 잘 보냈었는데...
이번에 certbot 의 windows 지원 종료와 dns txt record 오류의 재발은 또다시 나에게 3일간의 고통과 절망의 시간을 안겨 주었으며 그것으로 깨달은 점은 누군가와의 논쟁에서 정론을 논하게 되면 서로 합의하기가 너무나 어렵고 그로 인한 손실이 막대하다는 것이다. 하지만 서로가 조금씩 반걸음을 후퇴하여 양보를 통해 합의점을 찾게 되면 불가능한 일도 가능하게 된다.
과거 이전 회사에서 아무런 권한도 없이 그저 메신저 역할로써만 다양한 사람들간의 분쟁을 조정해주는 업무를 수행하며 당시에 참 곤욕스러웠던 일들을 상기해보면, 내가 상대하는 사람들이 가진 서로간의 이기심들을 알면서도 모른척하며 어떻게든 사람들을 조율 하는 것이 참 어려운 일이었구나... 라고 회상하게 되었다. 당시 나의 역할에 대한 한계를 더 상위의 누군가에게 종용 할수만 있었다면 아마 조율 업무 자체도 할만 했었을지도 모른다... 그래서 그들을 관찰하며 부서장들간의 보이지 않는 업무 협력 관계가 참 중요하다는 것을 알게 되었다. 당시의 업무는 권한과 책임을 모두 갖추었어야만 무사히(?) 진행이 가능했는데 정작 그것을 갖춰야만 했던 사람들이 제대로 자기 역할을 못했던 것 같다.
* 생수안의 미네랄이 분유먹는 아기에게 미치는 영향 생수에 포함된 미네랄은 성인에게는 수돗물보다 효율적으로 미네랄을 섭취할 수 있는 장점이 있지만, 아기에게는 다를 수 있습니다. 아기의 위장 면역력이 약하기 때문에, 생수 속 미네랄이 설사 등의 증상을 일으킬 수 있어 주의가 필요합니다¹[4]. 특히, 분유를 탈 때 사용하는 물은 아기의 건강에 직접적인 영향을 미치므로, 미네랄 함량이 높은 생수를 사용할 경우에는 더욱 신중해야 합니다.
아기분유생수를 선택할 때는 다음과 같은 사항을 고려하는 것이 좋습니다: - **미네랄 밸런스**: 아기의 뼈를 튼튼하게 하고 성장에 도움을 줄 수 있는 미네랄 밸런스가 중요합니다²[2]. - **안전성**: 무균상태로 유통되는 생수를 선택하여 세균 번식의 위험을 최소화합니다²[2]. - **미네랄 함량**: 일반 생수 대비 미네랄 함량이 높은 생수를 선택할 수도 있지만, 아기의 건강 상태와 의사의 조언을 고려해야 합니다²[2].
아기가 분유를 먹는 경우, 생수를 사용하기 전에 소아과 의사와 상담하는 것이 가장 안전한 방법입니다. 의사는 아기의 건강 상태와 필요에 맞는 물의 종류를 추천해 줄 수 있습니다. 또한, 분유에 사용되는 물은 멸균된 용기로 준비하는 것이 좋습니다³[6]. 분유를 준비할 때는 항상 아기의 건강과 안전을 최우선으로 고려해야 합니다.
출처: Bing과의 대화, 2024. 4. 25.
* 미네랄 함유량이 많은 물로 분유를 타서 아기에게 먹일 경우 유의해야 할 사항 미네랄 함유량이 높은 물로 아기의 분유를 준비할 때는 몇 가지 중요한 사항을 고려해야 합니다. 아기의 건강과 안전을 위해 다음 지침을 따르는 것이 좋습니다:
1. **미네랄 함량 확인**: 사용하는 물의 미네랄 함량, 특히 나트륨과 불소의 수치가 낮은지 확인하세요¹[4]. 2. **물 끓이기**: 안전한 출처에서 얻은 물을 사용하더라도, 분유를 준비하기 전에 반드시 물을 끓여야 합니다. 물을 실온으로 식힌 후 사용하세요²[3]. 3. **분유 제조 지침 준수**: 분유 제조사의 지침에 따라 정확한 양의 물과 분유를 사용하여 영양 불균형을 피하세요³[1]⁴[2]. 4. **위생적인 준비**: 분유를 준비하기 전에 손을 깨끗이 씻고, 청결한 환경에서 분유를 준비하세요³[1]. 5. **안전한 보관**: 분유를 준비한 후에는 적절한 온도에서 안전하게 보관하고, 빠른 시간 내에 사용하세요³[1].
아기가 2개월 미만이거나, 조산아이거나, 면역 체계가 약한 경우에는 크로노박터와 같은 세균 감염을 예방하기 위해 추가적인 주의가 필요합니다³[1]. 분유를 준비할 때는 항상 아기의 건강과 안전을 최우선으로 고려해야 하며, 불확실한 경우에는 소아과 의사와 상담하는 것이 가장 좋습니다.
출처: Bing과의 대화, 2024. 4. 25.
* 분유먹는 아기가 과도하게 미네랄을 섭취한 경우 발생 할 수 있는 증상 분유를 먹는 아기가 과도하게 미네랄을 섭취한 경우, 다음과 같은 증상이 발생할 수 있습니다:
1. **무기력**: 아기가 피곤해 보이거나 활동량이 감소할 수 있습니다. 2. **복통**: 복부 통증이 나타날 수 있습니다. 3. **구토와 메스꺼움**: 아기가 구토를 자주 하거나 메스꺼워할 수 있습니다. 4. **식욕 부진**: 아기가 식사를 거부하거나 식욕이 떨어질 수 있습니다. 5. **변비**: 변이 불규칙하게 발생하거나 변비가 생길 수 있습니다. 6. **뇌에 영향**: 미네랄 중독으로 인해 뇌에 영향을 줄 수 있으며, 경련, 헤드헌팅, 혼란 등의 증상이 나타날 수 있습니다¹[1]²[2].
특히, 미네랄 중독은 아기의 건강에 심각한 영향을 미칠 수 있으므로, 미네랄 함유량이 높은 물이나 음식을 아기에게 제공할 때는 주의해야 합니다. 만약 아기가 이러한 증상을 보인다면 즉시 소아과 의사와 상담하여 적절한 조치를 취해야 합니다.
Google API 는 Google Workspace 의 서비스들을 Program Library 나 REST 로 접근하여 제어할 수 있는 API 이다. Google Workspace 에서 제공하는 서비스 목록은 다음과 같다. Google Workspace | Google for Developers
어차피 javascript library 를 사용하려면 "https://apis.google.com/js/api.js" 파일만 import 하면 되는데 원본 소스에 대한 별도의 배포 사이트도 없고 API Document 마저 없어서 사용하기가 참 난해하다. 열심히 찾아봤지만 없었다.
javascript library 내에서 당연하게도 google rest api 를 이용하고 있어서라고 생각하는데 문제는 REST API 의 스펙을 안다고해도 javascript library 내의 class 와 function 들의 API 는 또 다르기 때문에 API Document 가 필요한건데... 그 문서를 찾을수가 없었다. 게다가 function 에 넘겨주는 파라미터들이 json 객체들이라서 어떤 구성의 param 목록이 지원되고 어떻게 설정해야 하는지에 대한 내용을 현재까지도 찾지 못하고 있다.
결국은 AI 들에게 질문을 바꿔서 나름 의미있는 답을 얻게 되었는데 function 의 실사용 예를 들어 구체적인 사용 방법을 물어보며 AI 들의 성능을 비교해 보았다.
[Prompt] gapi.client.drive.files.create 함수의 resource 인자값 metadata 에는 어떤 설정값이 있어?
`gapi.client.drive.files.create` 함수의 `resource` 인자는 파일에 대한 메타데이터를 설정하는 데 사용됩니다¹². 이 메타데이터에는 다음과 같은 설정값이 포함될 수 있습니다:
- `name`: 생성할 파일의 이름¹². - `mimeType`: 파일의 MIME 타입¹². - `parents`: 파일이 위치할 폴더의 ID를 포함하는 배열. 예를 들어, `'parents': ['appDataFolder']`는 파일을 앱 데이터 폴더에 생성하도록 지정합니다².
이러한 설정값 외에도 Google Drive API는 파일 메타데이터에 대한 추가적인 설정값을 제공합니다¹². 이러한 설정값은 파일의 속성을 더욱 세밀하게 제어할 수 있게 해줍니다¹². 그러나 항상 Google Drive API의 공식 문서를 참조하여 해당 설정값이 올바르게 지원되는지 확인하는 것이 좋습니다¹².
gapi.client.drive.files.create 함수의 resource 인자값인 metadata에는 다음과 같은 설정값이 있습니다.
- **name**: 파일의 이름을 설정합니다. (필수) - **mimeType**: 파일의 MIME 타입을 설정합니다. (필수) - **parents**: 파일이 저장될 부모 폴더의 ID를 배열로 설정합니다. - **description**: 파일의 설명을 설정합니다. - **starred**: 파일을 즐겨찾기에 추가할지 여부를 부울 값으로 설정합니다. - **trashed**: 파일을 휴지통에 넣을지 여부를 부울 값으로 설정합니다. - **explicitlyTrashed**: 파일을 명시적으로 휴지통에 넣을지 여부를 부울 값으로 설정합니다. - **properties**: 파일의 속성을 객체로 설정합니다. 속성의 이름과 값을 key-value 쌍으로 설정합니다.
metadata에 설정할 수 있는 값은 Google Drive API에서 지원하는 파일의 속성에 따라 다릅니다.
Google Drive API 문서에 따르면, gapi.client.drive.files.create 함수의 resource 인자값 metadata에는 다음과 같은 설정값이 있습니다.
* **description** (optional) - 파일의 설명입니다. * **mimeType** (optional) - 파일의 미디어 유형입니다. * **parents** (optional) - 파일을 저장할 폴더의 ID 목록입니다. * **properties** (optional) - 파일에 대한 사용자 지정 속성입니다. * **thumbnail** (optional) - 파일의 썸네일입니다.
다음은 각 설정값에 대한 자세한 설명입니다.
* **description** (optional) - 파일의 설명은 최대 255자까지 입력할 수 있습니다. 이 설명은 파일을 검색할 때 사용됩니다. * **mimeType** (optional) - 파일의 미디어 유형은 파일의 종류를 나타냅니다. 예를 들어, 이미지의 미디어 유형은 "image/jpeg"입니다. * **parents** (optional) - 파일을 저장할 폴더의 ID 목록입니다. 여러 개의 폴더에 파일을 저장하려면 이 설정값을 사용합니다. * **properties** (optional) - 파일에 대한 사용자 지정 속성을 설정하려면 이 설정값을 사용합니다. 속성의 이름과 값은 모두 문자열입니다. * **thumbnail** (optional) - 파일의 썸네일은 최대 256KB 크기의 이미지 파일입니다. 썸네일은 파일을 검색할 때 사용됩니다.
이러한 설정값은 모두 선택 사항입니다. 설정하지 않으면 기본값이 사용됩니다.
특이한 점은 잼민이(Gemini) 의 경우 Prompt 에 코드를 구체적으로 입력하니 Google Workspace 에서 문서를 검색하여 답변을 생성하겠다고 나한테 확인창을 띄워주었다.
그렇다는건 지금까지 잼민이(Gemini) 는 내가 하는 질문에 대한 답변을 작성할때 자기네들 서비스의 문서조차 검토도 안했다는 의미로 보였다. (그래서 그렇게 엉망이었나보다)
하지만 그렇게 자기네 서비스의 문서들까지 검토했음에도 불구하고 결과적으로 내가 선택한 답변은 Copilot 의 내용이었다.
애초에 Google 의 API를 이용하는 것이니 당연히 잼민이(Gemini)를 이용해야 한다고 생각했었다.
그래서 하루죙일 잼민이한테 "샘플좀 구해줘~", "제발 좀 오류 없는 샘플좀 구해줘~" 라고 애걸 복걸 했는데...
prompt 던진 횟수만해도 30여번은 넘은 듯 하다.
그리고 그 횟수만큼 코딩 오류와 실행 실패 그리고 의존 라이브러리 점검을 해야만 했다.
참으로 절망과 고통의 시간이었다...
이전에 이미 js 와 servlet 으로 Google Identity Flow 를 진행하여 Access Token 까지 획득해 놓은 상태이다.
그래서 Google Drive API 를 이용할땐 OAuth 절차가 필요없었다.
로그인 이후 Google Drive API 에서 불필요하게 사용자 인증 요청창이 다시 출력되지 않도록 OAuth 절차를 생략하고 Access Token 을 이용해서 GoogleCredentials 객체를 생성하기만 하면 되었었다.
그래서 잼민이에게 "Access Token 을 이용해서 GoogleCredentials 객체 생성 예제 알려줘~" 라고 가볍게 질문을 던진게 화근이 되었다.
잼민이는 무려 7년~8년전의 샘플 예제까지 검토해주는 기염을 토했는데 그 결과 샘플 코드들에서 deprecated 와 undefined 에러들을 수없이 봐야만 했다.
그때마다 implement 한 library 들의 버전을 반복해서 체크했는데...
구글 서비스 가이드 문서들도 샅샅이 뒤져가면서 구글의 문서들을 검토하느라 약 2일간 24시간을 컴퓨터 앞에서 인상쓰고 있었다.
그렇게 도저히 해법을 찾지 못했던 와중에 느닷없이
나와 동거중인 애 둘딸린 여왕님이 "prompt 를 제대로 안했나보네~" 라고 핀잔을 주면서 "prompt 좀 제대로 만들어서 던져바" 라고 조언 아닌 조언을 준것이 정말 귀한 한마디였다... 흑흑
심기일전하여 작성한 프롬프트의 내용은 다음과 같다.
[Prompt] Google Drive 서비스 API를 이용하기 위해 Access Token으로 Credential을 생성하는 코드 예제를 알려주는데 다음의 조건에 맞게 찾아줘 - java 버전 - 2020년 이후에 작성 - 사용하는 패키지, 클래스, 메서드들 중에 deprecated 는 없어야 함 - 사용된 패키지 라이브러리들의 버전들도 함께 출력
## Google Drive 서비스 API를 이용하기 위해 Access Token으로 Credential을 생성하는 Java 코드 예제 (2020년 이후, deprecated 없음)
다음은 Java 11+ 버전에서 Google Drive 서비스 API를 이용하기 위해 Access Token으로 Credential을 생성하는 코드 예제입니다. 2020년 이후에 작성되었으며, 사용하는 패키지, 클래스, 메서드들 중에 deprecated는 없습니다. 또한, 사용된 패키지 라이브러리들의 버전도 함께 출력합니다.
// Google Drive 서비스를 위한 Credential 객체를 생성합니다. GoogleCredentials credentials = GoogleCredentials.create( GoogleCredentials.getApplicationDefault() .createScoped(DriveScopes.DRIVE) .setAccessToken(accessToken) );
// Google Drive 서비스를 사용할 수 있는 Drive 객체를 생성합니다. Drive drive = new Drive.Builder( GoogleNetHttpTransport.newTrustedTransport(), JacksonFactory.getDefaultInstance(), credentials ).setApplicationName("Drive Service").build();
* `GoogleCredentials.getApplicationDefault()` 메서드는 현재 사용자의 환경에서 Google Cloud Platform SDK를 사용하여 설정된 기본 자격 증명을 가져옵니다. * `DriveScopes.DRIVE` 상수는 Google Drive 서비스에 대한 모든 범위를 제공합니다. * `GoogleNetHttpTransport.newTrustedTransport()` 메서드는 HTTPS를 사용하여 Google API 서버와 통신하는 데 사용할 수 있는 신뢰할 수 있는 HTTP 전송 객체를 생성합니다. * `JacksonFactory.getDefaultInstance()` 메서드는 JSON 데이터를 처리하는 데 사용할 수 있는 기본 Jackson JSON 팩토리를 생성합니다. * `setApplicationName()` 메서드는 Google API 요청에 사용될 애플리케이션 이름을 설정합니다.
public class GoogleDriveService { public Drive getDriveService(String accessToken) throws IOException, GeneralSecurityException { return new Drive.Builder( new NetHttpTransport(), GsonFactory.getDefaultInstance(), new HttpCredentialsAdapter(getCredentials(accessToken)) ) .setApplicationName("appname") .build(); }