5 min read

애저 펑션에서 애저 키 저장소 값을 참조하는 세 가지 방법

Justin Yoo

애저 펑션 또는 애저 앱 서비스를 사용하다 보면 API 인증 키 라든가, 데이터베이스 커넥션 스트링이라든가 하는 것들을 반드시 사용하게 된다. 애저 키 저장소를 이용해서 민감한 정보를 안전하게 보관하고 필요에 따라 그 값을 호출해서 사용하는 것도 한 가지 방법이다. 이 포스트에서는 애저 펑션에서 애저 키 저장소에 저장된 값을 직접 액세스하는 대신 레퍼런스값을 참조하는 형식으로 해서 사용하는 트릭을 세 가지 정도 소개하고자 한다.

이 포스트에 쓰인 샘플 코드는 KeyVault Reference Sample에서 다운로드 받을 수 있다.

키 저장소 레퍼런스 참조 원리

먼저 애저 키 저장소의 값을 애저 펑션에서 참조하는 원리에 대해 알아보자.

위 그림에서 볼 수 있다시피, 레퍼런스로 지정한 값은 Source 컬럼에 Key Vault Reference라고 되어 있는 것이 보인다.

그런데, 애저 펑션 앱은 이 구성(Configuration) 값을 환경 변수로 인식한다. 따라서 이 값들을 가져올 때 Environment.GetEnvironmentVariable() 메소드를 이용해서 가져온다.

내부적으로 이 값들을 비직렬화해서 가져올 수도 있는데, 이는 여기서는 다루지 않는다.

그런데, 키 저장소 레퍼런스 값은 환경 변수로 어떻게 바뀔까? 우리가 작성하는 앱에서는 단순히 환경 변수 값을 참조할 뿐이지 키 저장소 값을 참조하지 않는다. 이는 앱 서비스 인스턴스가 시작될 때 이를 내부적으로 참조하고 환경 변수로 치환하는 작업을 한다. 따라서, 우리는 굳이 자세한 내용을 알 필요는 없고, 그냥 참조값을 던져주고 쓰기만 하면 된다.

키 저장소 참조 방법 #1

그렇다면 공식 문서에서 제공하는 방법으로는 아래와 같이 둘 중 하나의 형식으로 참조값을 설정하면 된다.

@Microsoft.KeyVault(SecretUri=https://<key-vault-name>.vault.azure.net/secrets/<secret-name>/<secret-version>)
@Microsoft.KeyVault(VaultName=<key-vault-name>; SecretName=<secret-name>; SecretVersion=<secret-version>)

펑션 앱 코드에서는 동일하게 Environment.GetEnvironmentVariable("Hello") 또는 Environment.GetEnvironmentVariable("Hello2")를 호출하면 된다.

키 저장소 참조 방법 #2

그런데, 위의 방법이 큰 문제는 아니지만 조금 불편한 구석이 하나 있다. 시크릿 값이 바뀌면 버전도 따라서 같이 바뀌는데, 그 때 마다 매 번 SecretUri 값을 바꿔주거나 SecretVersion 값을 바꿔줘야 한다. 아직 프리뷰 단계이긴 하지만 애저 이벤트 그리드를 이용하면 키 저장소에서 값이 바뀌었을 경우 그 이벤트를 잡아 처리할 수도 있다. 그런데, 이렇게 하면 추가적인 코딩 작업이 필요하다. 그렇다면 별도의 코딩 작업 없이 새 시크릿 값을 곧바로 적용할 수 있는 방법이 있을까?

물론 있다. 아래의 SecretUri 값을 <scret-version> 부분만 빼고 넣는다. 여기서 명심해야 할 부분은 맨 마지막에 반드시 /로 끝나야 한다.

@Microsoft.KeyVault(SecretUri=https://<key-vault-name>.vault.azure.net/secrets/<secret-name>/)

이렇게 하면 항상 최신의 시크릿 버전 값을 참조해서 그 결과를 보내준다. 그런데, 앞서 애저 앱 서비스 내부적으로 키 저장소값을 변환하고 환경 변수 값으로 치환하는 과정이 있다고 했는데, 이 과정에서 만약 시크릿 버전을 명시하지 않으면 환경 변수로 변환되는 도중에 살짝 레이턴시가 있다. 정확한 값은 모르지만 대략 10초 정도의 레이턴시를 경험했다. 따라서, 이 부분만 유념한다면 시크릿 버전을 굳이 명시하지 않아도 되니 편리하다.

키 저장소 참조 방법 #3

위의 키 저장소 참조 문법인 @Microsoft.KeyVault(...)는 다 좋은데, 애저로 배포했을 경우에만 적용이 가능하다. 즉, 로컬 개발 환경에서는 적용할 수가 없다. 따라서, 로컬 개발 환경에서도 키 저장소 값을 참조하고 싶다면 약간의 코딩이 필요하다. 아래 코드를 살짝 보자. 먼저 정규표현식으로 키 저장소 참조 문법 패턴을 구성한다.

public class AppSettingsHandler
{
private static Regex regexSecretUri = new Regex(@"\@Microsoft\.KeyVault\(SecretUri\=(.*)\)", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
private static Regex regexVaultName = new Regex(@"\@Microsoft\.KeyVault\(VaultName\=(.*);\s*SecretName\=(.*);\s*SecretVersion\=(.*)\)", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
...
}

그리고 GetValueAsync(string key) 메소드를 통해 환경 변수 값을 확인한다. 만약 환경 변수값이 키 저장소 참조 형식이 아니라면 그냥 값을 반환한다.

public class AppSettingsHandler
{
...
public async Task<string> GetValueAsync(string key)
{
var reference = Environment.GetEnvironmentVariable(key);
if (!this.IsKeyVaultReference(reference))
{
return reference;
}
...
}
private bool IsKeyVaultReference(string value)
{
return value.StartsWith("@Microsoft.KeyVault(");
}
}

만약 키 저장소 참조 형식이라면, SecretUri 값을 참조하는지 우선 확인해서 그 값을 보내 시크릿 값을 가져온다.

public class AppSettingsHandler
{
...
public async Task<string> GetValueAsync(string key)
{
...
var bundle = default(SecretBundle);
var match = regexSecretUri.Match(reference);
if (match.Success)
{
var uri = match.Groups[1].Value;
bundle = await this._kv.GetSecretAsync(uri).ConfigureAwait(false);
return bundle.Value;
}
...
}
}

만약 VaultName 값을 참조한다면 정규표현식을 통해 필요한 값들을 추출해서 키 저장소를 호출한 후 시크릿 값을 가져온다.

public class AppSettingsHandler
{
...
public async Task<string> GetValueAsync(string key)
{
...
match = regexVaultName.Match(reference);
if (match.Success)
{
var vaultName = match.Groups[1].Value;
var secretName = match.Groups[2].Value;
var secretVersion = match.Groups[3].Value;
bundle = await this._kv.GetSecretAsync($"https://{vaultName}.vault.azure.net", secretName, secretVersion).ConfigureAwait(false);
return bundle.Value;
}
...
}
}

만약 이도 저도 아니면 그냥 null 값을 반환한다.

public class AppSettingsHandler
{
...
public async Task<string> GetValueAsync(string key)
{
...
return null;
}
}

이렇게 하면 기존의 Environment.GetEnvironmentVariable() 메소드를 호출했던 부분을 이 AppSettingsHandler.GetValueAsync() 메소드로 바꿔줘야 한다. 그렇게 함으로써, 로컬 개발 환경에서도 키 저장소를 참조해서 호출할 수 있다.

이 방법의 가장 큰 단점이 하나 있다. 메소드 안에서는 완벽하게 작동하지만, 만약 애저 펑션트리거와 바인딩에서는 작동하지 않는다. 트리거와 바인딩에서는 환경 변수를 직접 접근하지, 지금 위에서 작성한 핸들러를 통하지 않기 때문이다.


지금까지, 애저 펑션에서 애저 키 저장소 값을 참조하는 세 가지 방법에 대해 알아 보았다. 저마다 장점도 있고 단점도 있는지라, 어느 부분을 선택할지에 대해서는 전적으로 여러분의 몫이다.