Azure Managed Identity로 AI 서비스 연결하기, API Key 없이 안전하게 인증하는 방법

지난주 금요일, 보안팀에서 한 통의 전화가 왔다. “Azure OpenAI API 키가 GitHub에 커밋된 게 발견됐어요. 즉시 로테이션 해주세요.” 상황이 얼마나 심각한지는 말할 필요가 없다. 한 명의 개발자가 실수로 키를 노출시켰는데, 그 키로 누구나 당신의 AI 서비스를 이용할 수 있다. 월 청구액이 수천 달러에 이를 수도 있다.

이게 처음 겪는 일이 아니다. 여러 프로젝트를 하면서 API 키 관리로 인한 문제를 여러 번 봤다. 환경 변수에 저장한 키가 로그에 남기도 했고, 테스트 코드에 실수로 넣기도 했고, 팀원끼리 공유하다가 보안팀에 걸리기도 했다. 관리할 키가 많아질수록 실수할 확률도 올라간다.

그런데 여기 완벽한 해결책이 있다. Azure Managed Identity다. 이 기능을 쓰면 API 키를 관리할 필요가 없다. Azure가 자동으로 인증을 처리해준다. 보안도 훨씬 강하고, 관리도 쉽다. 생각해보면 너무 당연한 해결책인데, 왜 많은 개발자들이 여전히 API 키를 쓰는지 모르겠다. 아마 제대로 알려지지 않아서겠지.

API 키는 보안의 악몽이다

먼저 현실을 직시해야 한다. API 키를 쓰는 것의 문제점이 얼마나 심각한지.

API 키는 기본적으로 “비밀번호”다. 그런데 비밀번호와 달리 사람이 기억하는 게 아니라 저장해야 한다. 저장하는 순간부터 위험이 시작된다. 환경 변수에 저장하든, 설정 파일에 저장하든, 데이터베이스에 저장하든, 어딘가는 저장해야 한다. 그리고 저장된 곳이라면 누군가 접근할 수 있을 가능성이 있다.

GitHub에 실수로 커밋하는 경우도 정말 많다. Git의 특성상 커밋 이력에는 계속 남아있다. 나중에 지워도 git history에는 전부 남아있다. 누군가 repo를 클론해서 git log를 보면 키를 찾을 수 있다. 특히 공개 저장소라면 더 위험하다. 봇들이 계속 GitHub을 스캔하면서 노출된 키를 찾는다.

환경 변수에 저장해도 문제다. 애플리케이션 크래시 덤프를 떨어뜨릴 때, 환경 변수가 포함될 수 있다. 로그 레벨을 DEBUG로 설정했을 때, 환경 변수가 로그에 남을 수 있다. 누군가 서버에 접근했을 때, 환경 변수를 읽을 수 있다.

컨테이너 이미지에 빌드 시간에 넣으면? 정말 위험하다. 이미지는 여러 사람이 접근할 수 있다. 레지스트리에 있는 이미지는 누구나 다운로드할 수 있을 수도 있다.

보안팀의 관점에서도 문제가 많다. 누가 어떤 키를 갖고 있는지 파악할 수 없다. 한 개발자가 떠났을 때 그 개발자가 갖던 키를 어떻게 처리해야 할지 애매하다. 키 로테이션을 할 때는 모든 애플리케이션에 새 키를 배포해야 한다. 한두 개 정도면 괜찮지만, 여러 팀이 여러 서비스를 운영한다면 nightmare다.

그리고 가장 큰 문제는 “누가 이 API를 썼는가”를 추적할 수 없다는 거다. 로그에는 API 키만 남고, 어떤 애플리케이션이 이 키를 썼는지 알 수 없다. 보안 감시 입장에서는 정말 답답하다.

Managed Identity, Azure의 우아한 해결책

이제 Managed Identity를 보자. 이건 마치 마법처럼 들릴 수 있지만, 실제 원리는 꽤 우아하다.

Managed Identity는 Azure의 내장 인증 메커니즘이다. Azure 내에서 실행 중인 애플리케이션에 자동으로 정체성(Identity)을 부여한다. 그리고 그 정체성을 사용해서 Azure의 다른 서비스에 접근할 수 있게 해준다. API 키를 쓸 필요가 없다.

원리를 간단히 설명하면 이렇다. 당신이 Container Apps에서 앱을 실행한다고 하자. Azure는 자동으로 그 앱에 대한 “서비스 주체(Service Principal)”를 만든다. 그리고 그 서비스 주체에게 Azure OpenAI 서비스에 접근할 권한을 부여한다. 앱이 Azure OpenAI를 호출할 때, Azure가 자동으로 인증을 처리한다. 앱 입장에서는 “API 키를 써야겠다”라는 생각을 할 필요가 없다.

이게 얼마나 우아한지는 다음과 같다. Azure에 앱이 있고, Azure에 서비스가 있다. 둘 다 Microsoft의 관할 아래 있다. Microsoft는 “이 앱이 저 서비스에 접근하고 싶어 하는군”이라는 걸 알고 있다. 그럼 Microsoft가 자동으로 인증을 처리하면 되는 거다. 제3자 메커니즘(API 키)이 필요 없다.

Managed Identity에는 두 가지 종류가 있다. System-assigned Managed Identity와 User-assigned Managed Identity다. System-assigned는 특정 Azure 리소스(예를 들어 Container Apps)에 종속된 정체성이다. 그 리소스가 삭제되면 정체성도 함께 삭제된다. User-assigned는 독립적으로 존재하는 정체성이다. 여러 리소스에 할당할 수 있다.

대부분의 경우 System-assigned로 충분하다. 간단하고, 관리할 게 적다. 하지만 여러 리소스가 같은 정체성으로 접근해야 한다면 User-assigned를 써야 한다.

실제 구현, 한 단계씩 따라하기

이제 실제로 어떻게 구현하는지 보자. Container Apps에서 실행 중인 Python 애플리케이션이 Azure OpenAI를 호출한다고 가정하자.

첫 번째 단계는 Container Apps에 Managed Identity를 활성화하는 거다. Azure Portal에서 Container Apps를 찾아서, “Settings” 아래 “Identity”를 클릭한다. 그리고 “Status”를 “On”으로 전환한다. 이게 끝이다. 정말 간단하다. Azure가 자동으로 System-assigned Managed Identity를 만들어준다.

두 번째 단계는 이 정체성에 권한을 부여하는 거다. Azure OpenAI 리소스로 가서, “Access control (IAM)”을 클릭한다. “Add”를 눌러서 role assignment를 추가한다. 역할은 “Cognitive Services OpenAI User”를 선택하고, 할당 대상은 방금 만든 Container Apps의 정체성을 선택한다.

이제 권한 설정이 끝났다. 애플리케이션 코드는 어떻게 될까?

from azure.identity import DefaultAzureCredential
from openai import AzureOpenAI

credential = DefaultAzureCredential()

client = AzureOpenAI(
    api_version="2024-02-15-preview",
    azure_endpoint="https://your-resource.openai.azure.com",
    azure_ad_token_provider=credential.get_token
)

response = client.chat.completions.create(
    model="gpt-4",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Hello!"}
    ]
)

print(response.choices[0].message.content)

보면 API 키가 어디에도 없다. 대신 DefaultAzureCredential을 사용한다. 이건 Azure SDK에 내장된 기능이다. Azure에서 실행 중인 애플리케이션이면, 자동으로 Managed Identity를 찾아서 인증 토큰을 얻는다.

코드는 정말 간단하다. API 키를 관리할 필요가 없다. 환경 변수를 설정할 필요도 없다. 그냥 코드를 쓰면 Azure가 자동으로 처리한다.

로컬 개발 환경에서도 작동한다

여기서 나올 질문이 하나 있다. “그럼 내 컴퓨터에서 로컬 개발할 때는 어떻게 하나?” 좋은 질문이다.

로컬에서도 Managed Identity를 쓸 수 있다. 방법은 두 가지다.

첫 번째는 Azure CLI를 쓰는 거다. 컴퓨터에 Azure CLI를 설치하고, az login 명령어로 로그인한다. 그러면 DefaultAzureCredential이 자동으로 당신의 Azure 계정을 찾는다. 당신의 계정에 충분한 권한이 있으면, 로컬에서도 Azure OpenAI를 호출할 수 있다.

두 번째는 서비스 주체(Service Principal)를 쓰는 거다. 개발용 서비스 주체를 만들고, 그것에 권한을 부여한다. 로컬 환경 변수에 서비스 주체의 인증 정보를 설정한다. 그러면 DefaultAzureCredential이 그 정보를 찾는다.

첫 번째 방법이 훨씬 간단하다. 따라서 로컬 개발할 때는 첫 번째 방법을 추천한다. 개발 편의성과 보안의 균형이 맞다.

고급 설정, 보안을 더 강화하기

기본적인 설정만으로도 충분하지만, 더 강화할 수 있다.

첫째, 정책을 더 세밀하게 설정할 수 있다. Role-Based Access Control(RBAC)은 기본이고, Azure Policy를 사용해서 어떤 리소스에 접근할 수 있는지 더 제한할 수 있다.

둘째, Private Endpoint를 사용해서 Azure OpenAI를 인터넷에서 접근 불가능하게 만들 수 있다. Container Apps가 Virtual Network 내에 있으면, Private Endpoint를 통해서만 Azure OpenAI에 접근할 수 있다. 인터넷 노출을 완전히 차단하는 거다.

셋째, Azure Key Vault를 함께 써서 기타 비밀 정보를 관리할 수 있다. API 키 같은 게 남아있다면, Managed Identity로 Key Vault에 접근해서 그곳에서 비밀을 꺼내면 된다.

넷째, Audit Logging을 활성화해서 누가 언제 뭘 접근했는지 기록할 수 있다. Managed Identity를 쓰면 이게 자동으로 된다. 로그에는 Container Apps의 정체성이 기록된다. 어떤 애플리케이션이 어떤 API를 호출했는지 추적할 수 있다.

실제 운영, 뭐가 바뀌는가

Managed Identity를 도입하면 운영이 정말 편해진다.

첫째, 보안팀이 행복해진다. API 키를 관리할 필요가 없으니까. 감사(Audit)도 쉽다. 모든 접근이 로그에 남으니까.

둘째, 개발자가 행복해진다. 키를 기억할 필요가 없다. 키를 설정할 필요도 없다. 그냥 코드를 쓰면 된다.

셋째, DevOps가 행복해진다. 배포할 때 비밀을 관리할 필요가 없다. 환경 변수를 설정할 필요도 없다. 그냥 Managed Identity를 활성화하고 권한을 부여하면 된다.

넷째, 보안 사고의 위험이 줄어든다. 키가 노출될 일이 없다. 키 로테이션도 Azure가 자동으로 한다.

실제로 내가 근무하는 조직에서 Managed Identity를 도입한 후에는 보안 관련 이슈가 거의 없어졌다. 발견되는 유일한 문제는 개발자들이 여전히 API 키를 쓰려고 한다는 거다. 오래된 습관이 쉽게 안 바뀐다. 하지만 코드 리뷰 때 “이걸 Managed Identity로 바꿔주세요”라고 피드백하면, 대부분 협조한다.

한계와 주의사항

Managed Identity가 완벽한 건 아니다. 몇 가지 제약이 있다.

첫째, Azure 내에서만 작동한다. 온프레미스 서버나 다른 클라우드에서는 Managed Identity를 쓸 수 없다. 그 경우 여전히 API 키나 다른 인증 방식을 써야 한다.

둘째, Azure 리소스에만 적용된다. 예를 들어 Container Apps나 App Service, Virtual Machines 같은 것들 말이다. GitHub Actions에서는 Managed Identity를 쓸 수 없다. (최근에는 OpenID Connect를 통해 가능해졌지만, 여전히 제한이 있다.)

셋째, Role Assignment를 제대로 해야 한다. 권한을 너무 넓게 주면 보안이 약해진다. “Contributor” 역할을 주면 안 된다. 정확히 필요한 역할만 부여해야 한다.

넷째, Managed Identity는 인증(Authentication)만 처리한다. 인가(Authorization)는 별도로 구현해야 한다. 즉, 애플리케이션이 사용자가 어떤 권한을 가졌는지 확인하는 로직은 여전히 필요하다.

결론: 왜 아직도 API 키를 쓰는가

지금까지 설명한 Managed Identity의 장점은 명확하다. 보안도 더 좋고, 관리도 더 쉽고, 코드도 더 간단하다. 그럼 왜 많은 개발자들이 여전히 API 키를 쓰는가?

아마도 알려지지 않아서일 거다. Managed Identity는 Azure의 기능 중에서도 비교적 새로운 기능이다. 10년 전만 해도 없었다. 레거시 코드들은 여전히 API 키로 작동한다. 그리고 만들 때의 패턴을 그대로 복사하는 게 쉽기 때문에, 새로운 프로젝트에서도 API 키를 쓰는 경향이 있다.

또 다른 이유는 “이걸 왜 써야 하는가”가 명확하지 않아서일 수도 있다. 개발 초기에는 API 키로 충분하다. 문제는 보통 나중에 생긴다. 팀이 커지고, 프로젝트가 많아지고, 보안 감시가 강해지면서. 그때쯤이면 이미 API 키를 쓰는 레거시 시스템이 많아져 있다.

하지만 앞으로 새로운 프로젝트를 시작한다면? Azure를 쓰면서 AI 서비스(Azure OpenAI, Cognitive Services 등)를 쓴다면? Managed Identity를 쓰는 게 당연하다. 왜냐하면 더 안전하고, 더 간단하고, 더 현대적이기 때문이다.

지금 당신이 API 키로 작동하는 애플리케이션을 갖고 있다면, Managed Identity로 마이그레이션하는 걸 진심으로 추천한다. 처음엔 몇 시간의 리팩토링이 필요하지만, 그 이후로는 보안과 관리 측면에서 엄청난 이득을 본다. 그리고 처음부터 Managed Identity로 설계하는 게 가장 좋다. 나중에 바꾸는 것보다 처음부터 제대로 하는 게 항상 쉽다.

Leave a Comment