안녕하세요 MANVSCLOUD 김수현입니다.
지난 [1편]에서는 Sub Account의 API를 이용하여 접속 최종 일시가 특정 기간이 초과될 경우 비활성화하는 Python 코드를 작성하고 자동화할 수 있는 방법을 공유드렸습니다.
- [NCLOUD] Sub Account 효율적으로 관리하기 [1편] : https://manvscloud.com/?p=2646
이번 포스팅에서는 [1편]에서 조금 더 업그레이드된 기능을 함께 만들어보는 시간을 가져보겠습니다.
1) 네이버 클라우드의 Cloud Outbound Mailer를 사용하여 비활성화된 사용자의 이메일로 비활성화가 되었으니 다시 Sub Account 사용이 필요한 경우 관리자에게 문의하라는 메일을 보내볼 것입니다.
2) 모든 코드를 Cloud Functions에서 동작하도록 구성해볼 것입니다.
위 두 과정을 어떻게 적용할 수 있는지 지금부터 알아봅시다.
Cloud Outbound Mailer

이 포스팅에서는 Cloud Outbound Mailer 생성 방법은 안내하고 있지않습니다. Cloud Outbound Mailer는 아래 가이드를 참고하여 생성해주세요.
- Cloud Outbound Mailer 사용 가이드 : https://guide.ncloud-docs.com/docs/email-email-1-1

위와 같이 도메인 등록이 되어있다면 Cloud Outbound Mailer의 API를 이용하여 메일 발송 테스트를 아래와 같이 진행해볼 수 있습니다.
import hashlib
import hmac
import base64
import requests
import time
def main():
timestamp = int(time.time() * 1000)
timestamp = str(timestamp)
access_key = "YOUR_ACCESS_KEY"
secret_key = "YOUR_SECRET_KEY"
secret_key = bytes(secret_key, 'UTF-8')
method = "POST"
api_server = "https://mail.apigw.ntruss.com"
uri = "/api/v1/mails"
uri = uri + "?responseFormatType=json"
message = method + " " + uri + "\n" + timestamp + "\n" + access_key
message = bytes(message, 'UTF-8')
signingKey = base64.b64encode(hmac.new(secret_key, message, digestmod=hashlib.sha256).digest())
http_header = {
'Content-Type': 'application/json',
'x-ncp-apigw-signature-v1': signingKey,
'x-ncp-apigw-timestamp': timestamp,
'x-ncp-iam-access-key': access_key
}
payload = {
"senderAddress" : "발신자 이메일",
"senderName" : "발신자 이름",
"title" : "제목",
"body" : "메일 내용",
"recipients" : [{"address" : "수신자 이메일", "type" : "R"}]
}
response = requests.post(api_server + uri, headers=http_header, json=payload)
if __name__ == '__main__':
main()
추가된 Access Key에는 당연히 메일을 발송할 수 있는 권한이 부여되어있어야 하며, payload에 발신자 이메일, 발신자 이름, 제목, 메일 내용, 수신자 이메일을 각각 추가해줍니다.
이제 [NCLOUD] Sub Account 효율적으로 관리하기 [1편]에서 만든 코드와 위 Cloud Outbound Mailer API로 메일을 발송하는 코드를 하나로 합쳐보겠습니다.
import requests
import json
import base64
import hashlib
import hmac
import time
from datetime import datetime, timedelta
class SubAccountAPI:
def __init__(self, access_key, secret_key, api_server, mail_server):
self.access_key = access_key
self.secret_key = bytes(secret_key, 'UTF-8')
self.api_server = api_server
self.mail_server = mail_server
def generate_signature(self, method, uri, timestamp):
message = f"{method} {uri}\n{timestamp}\n{self.access_key}"
signing_key = base64.b64encode(hmac.new(self.secret_key, bytes(message, 'UTF-8'), digestmod=hashlib.sha256).digest())
return signing_key
def make_request(self, method, uri, body=None, is_mail=False):
timestamp = str(int(time.time() * 1000))
signature = self.generate_signature(method, uri, timestamp)
headers = {
'Content-Type': 'application/json',
'x-ncp-apigw-timestamp': timestamp,
'x-ncp-iam-access-key': self.access_key,
'x-ncp-apigw-signature-v1' if is_mail else 'x-ncp-apigw-signature-v2': signature
}
full_uri = self.mail_server + uri if is_mail else self.api_server + uri
response = requests.request(method, full_uri, headers=headers, data=json.dumps(body))
return response.json()
def get_sub_accounts(self):
response_data = self.make_request("GET", "/api/v1/sub-accounts")
ninety_days_ago = datetime.now() - timedelta(days=90)
return [
item for item in response_data['items']
if item['canConsoleAccess'] and item.get('active', True) and item.get('lastLoginTime') and
datetime.strptime(item.get('lastLoginTime'), "%Y-%m-%dT%H:%M:%SZ") < ninety_days_ago
]
def modify_sub_account(self, sub_account_id, body_data):
self.make_request("PUT", f"/api/v1/sub-accounts/{sub_account_id}", body_data)
def send_email(self, recipient_email):
payload = {
"senderAddress": "발신자 이메일",
"senderName": "발신자 이름",
"title": "제목",
"body": "메일 내용",
"recipients": [{"address": recipient_email or "관리자 이메일", "type": "R"}]
}
self.make_request("POST", "/api/v1/mails?responseFormatType=json", body=payload, is_mail=True)
def main():
api = SubAccountAPI("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY", "https://subaccount.apigw.ntruss.com", "https://mail.apigw.ntruss.com")
for account in api.get_sub_accounts():
api.modify_sub_account(account['subAccountId'], {"active": False, "name": account.get('name')})
api.send_email(account.get('email', "관리자 이메일"))
if __name__ == '__main__':
main()
Sub Account에서 최종 접속 일시가 90일이 초과된 경우 해당 계정을 비활성화 시키고 Outbound Mailer를 이용하여 이메일을 발송하는 코드입니다. 기존 코드에서 추가로 더 업그레이드된 부분은 이미 비활성화된 계정의 경우 추가적으로 메일이 발송되면 안되기때문에 기존에 비활성화였던 Sub Account는 메일을 발송하지 않고 90일이 초과된 활성화 계정이 비활성화된 경우에만 메일이 발송되도록 변경하였습니다.
Cloud Functions
이제 마지막으로 위에서 만든 코드를 Cloud Functions에서 사용할 수 있도록 최종 리팩토링 작업을 진행해보도록 하겠습니다.

import requests
import json
import base64
import hashlib
import hmac
import time
from datetime import datetime, timedelta
def generate_signature(secret_key, access_key, method, uri, timestamp):
message = f"{method} {uri}\n{timestamp}\n{access_key}"
signing_key = base64.b64encode(hmac.new(bytes(secret_key, 'UTF-8'), bytes(message, 'UTF-8'), digestmod=hashlib.sha256).digest())
return signing_key
def make_request(args, method, uri, body=None, is_mail=False):
timestamp = str(int(time.time() * 1000))
signature = generate_signature(args["NCLOUD_SECRET_KEY"], args["NCLOUD_ACCESS_KEY"], method, uri, timestamp)
headers = {
'Content-Type': 'application/json',
'x-ncp-apigw-timestamp': timestamp,
'x-ncp-iam-access-key': args["NCLOUD_ACCESS_KEY"],
'x-ncp-apigw-signature-v1' if is_mail else 'x-ncp-apigw-signature-v2': signature
}
full_uri = args["MAIL_SERVER"] + uri if is_mail else args["SUBACCOUNT_SERVER"] + uri
response = requests.request(method, full_uri, headers=headers, data=json.dumps(body))
return response.json()
def get_sub_accounts(args):
response_data = make_request(args, "GET", "/api/v1/sub-accounts")
ninety_days_ago = datetime.now() - timedelta(days=int(args["DAYS"]))
return [
item for item in response_data['items']
if item['canConsoleAccess'] and item.get('active', True) and item.get('lastLoginTime') and
datetime.strptime(item.get('lastLoginTime'), "%Y-%m-%dT%H:%M:%SZ") < ninety_days_ago
]
def modify_sub_account(args, sub_account_id, body_data):
make_request(args, "PUT", f"/api/v1/sub-accounts/{sub_account_id}", body_data)
def send_email(args, recipient_email):
payload = {
"senderAddress": args["SENDER_EMAIL"],
"senderName": args["SENDER_NAME"],
"title": args["TITLE"],
"body": args["BODY"],
"recipients": [{"address": recipient_email or args["ADMIN_EMAIL"], "type": "R"}]
}
make_request(args, "POST", "/api/v1/mails?responseFormatType=json", body=payload, is_mail=True)
def main(args):
accounts = get_sub_accounts(args)
for account in accounts:
modify_sub_account(args, account['subAccountId'], {"active": False, "name": account.get('name')})
send_email(args, account.get('email', args["ADMIN_EMAIL"]))
return {"message": "Process completed"}
{"ADMIN_EMAIL":"관리자이메일","BODY":"Sub Account가 비활성화 처리되었습니다. 재사용이 필요하실 경우 관리자에게 문의주세요.","SENDER_EMAIL":"발송자 이메일","MAIL_SERVER":"https://mail.apigw.ntruss.com","TITLE":"[알림] Sub Account 비활성화 처리","NCLOUD_ACCESS_KEY":"YOUR_ACCESS_KEY","NCLOUD_SECRET_KEY":"YOUR_ACCESS_KEY","SENDER_NAME":"발송자 이름","SUBACCOUNT_SERVER":"https://subaccount.apigw.ntruss.com","DAYS":"90"}
디폴트 파라미터는 위와 같이 사용할 수 있습니다. 파라미터 값을 원하는대로 지정해주세요.
- ADMIN_EMAIL : Sub Account에 이메일이 등록되지 않을 경우 대표로 받을 관리자 이메일을 입력해주세요. ex) admin@manvscloud.com
- BODY : 메일 발송 시 메일의 내용입니다.
- SENDER_EMAIL : 발신자 이메일입니다. Cloud Outbound Mailer에 등록된 도메인을 사용해주세요. ex) noti@manvscloud.com
- TITLE : 메일 발송 시 메일의 제목입니다.
- NCLOUD_ACCESS_KEY : Sub Account 조회 및 비활성화 권한 그리고 Cloud Outbound Mailer의 메일 발송 권한이 있는 Access Key
- NCLOUD_SECRET_KEY : Sub Account 조회 및 비활성화 권한 그리고 Cloud Outbound Mailer의 메일 발송 권한이 있는 Access Key의 Secret Key
- SENDER_NAME : 발신자 이름
- DAYS : 최종 접속 일시 탐지 기간 ex) 90 으로 설정 시 90일

실무에서 사용 시 KMS(Key Management Service)를 생성하여 반드시 NCLOUD_ACCESS_KEY와 NCLOUD_SECRET_KEY는 암호화 설정을 해주세요.
위 Action에 Cron과 같은 Trigger를 연결하여 사용한다면 하루마다 Cloud Functions이 한 번씩 오래된 Sub Account 계정이 있는지 체크하고 비활성화 처리 및 메일로 알림을 주게 됩니다.

여기까지 잘 따라왔다면 생성된 Action을 [실행]한 후 Sub Account가 비활성화 되었는지, 이메일이 Sub Account에 등록된 이메일로 잘 발송되었는지, Sub Account에 이메일이 등록되어있지 않다면 관리자 이메일로 전송이 되었는지 확인해보시기 바랍니다.

최종 접속 일시가 설정한 DAYS를 초과한 Sub Account가 비활성화되고 위 사진과 같이 메일이 정상적으로 왔다면 이 과정을 성공적으로 끝마친 것입니다.
Personal Comments
이번 “[NCLOUD] Sub Account 효율적으로 관리하기 [2편]”을 마지막으로 네이버 클라우드의 Sub Account를 더욱 효율적으로 관리하는 방법에 대해 조금 더 깊게 알아보았습니다.
최종 접속 일시가 90일을 초과한 Sub Account를 자동으로 비활성화하고 Cloud Outbound Mailer API를 활용하여 해당 계정의 사용자에게 계정이 비활성화되었음을 알리는 안내 메일을 발송하는 과정을 세밀하게 다루어 보았는데요.
또한 이 모든 과정을 Cloud Functions를 사용하여 서버리스 환경에서 구현함으로써 서버 운영에 따른 추가 비용 없이 이러한 자동화를 실현할 수 있음을 알 수 있었습니다.
이러한 접근 방식은 관리자가 수동으로 Sub Account를 검토하고 조치를 취하는 번거로움을 크게 줄여주며 네이버 클라우드 리소스의 효율적 사용을 가능하게 합니다.
또 안내 메일 발송을 통해 계정의 상태 변경에 대한 투명성을 제공하며 필요시 적절한 조치를 취할 수 있도록 사용자에게 알립니다.
이는 클라우드 환경의 보안과 관리 효율성을 동시에 강화하는 중요한 단계로 실무에서 사용할 수 있습니다.
긴 글 읽어주셔서 감사합니다.

No Comments