10 min read

애저 펑션을 이용해서 애저 키 저장소 시크릿을 백업/복원하기

Justin Yoo

지난 포스트에서는 애저 로직 앱을 이용해서 애저 키 저장소를 백업하고 복구하는 방법에 대해 알아 보았다. 로직 앱을 사용하면 코드를 거의 사용할 일이 없기 때문에 굉장히 편리한 점이 있긴 하지만, 반대로 코드를 통해 키 저장소의 시크릿 값들을 백업하고 복구하는 요구사항도 분명히 있게 마련이다. 따라서, 이 포스트에서는 애저 펑션 앱을 통해 키 저장소의 시크릿 값들을 백업하고 복구하는 방법에 대해 알아보기로 한다.

이 포스트에 쓰인 펑션 앱 코드는 이 깃헙 리포지토리에서 다운로드 받을 수 있다.

애저 펑션에 관리 ID 활성화 시키기

애저 펑션이 키 저장소에 손쉽게 접근하기 위해서는 우선 관리 ID 기능이 활성화 되어 있어야 한다. 이와 관련해서는 이전 포스트에서 이미 한 번 다룬 적이 있으므로 여기서는 더이상 언급하지는 않기로 한다.

키 저장소 백업 워크플로우

이전 포스트에서 다룬 바와 같이 키 저장소 백업을 위한 워크플로우는 동일하다.

  1. 시크릿 리스트를 가져온다
  2. 리스트를 루프로 돌며 개별적으로 시크릿을 백업한다
  3. 백업 결과를 배열로 저장한다
  4. 배열을 직렬화해서 애저 블롭 저장소에 업로드한다

아래와 같이 전체 시크릿 리스트를 가져오는 코드를 작성한다. 이 코드를 실행시키면 시크릿 이름들을 리스트로 반환한다.

public async Task<List<string>> GetSecretsAsync()
{
// Declares a KeyVaultClient instance.
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var kv = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
// Gets the list of secrets.
var baseUri = "https://myu-keyvault.vault.azure.net/";
var secrets = await kv.GetSecretsAsync(baseUri)
.ConfigureAwait(false);
// Returns the list of secret names.
return secrets.Select(p => p.Identifier.Name).ToList();
}
view raw get-secrets.cs hosted with ❤ by GitHub

이 시크릿 리스트를 이용해서 다음에는 개별 시크릿들을 백업한다. 현재 한 번에 벌크로 백업하는 기능은 지원하지 않으므로 아래와 같은 방식으로 반복문을 돌려야 한다.

public async Task<List<BackupSecretResult>> BackupSecretsAsync(List<string> secrets)
{
// Declares a KeyVaultClient instance.
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var kv = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
// Performs the backup and add the result into the list.
var results = new List<BackupSecretResult>();
var baseUri = "https://my-keyvault.vault.azure.net/";
foreach (var name in secrets)
{
var result = await kv.BackupSecretAsync(baseUri, name)
.ConfigureAwait(false);
results.Add(result);
}
// Returns the backup results.
return results;
}

이제 백업 결과를 리스트로 받았으니, 이를 애저 블롭 저장소에 업로드 할 차례이다. 아래 코드를 실행시켜 리스트를 직렬화한 후 업로드한다.

public async Task<bool> UploadAsync(List<BackupSecretResult> results)
{
// Declares the BlobClient instance.
var connectionString = Environment.GetEnvironmentVariable("AzureWebJobsStorage");
var blob = CloudStorageAccount.Parse(connectionString)
.CreateCloudBlobClient();
// Gets the Blob container.
var containerName = "backups";
var container = blob.GetContainerReference(containerName);
await container.CreateIfNotExistsAsync().ConfigureAwait(false);
// Gets the Blob.
var blobName = $"{DateTimeOffset.UtcNow.ToString("yyyyMMdd")}.json";
var blob = container.GetBlockBlobReference(blobName);
// Serialises the backup result.
var serialised = JsonConvert.SerializeObject(results);
// Uploads the backup result to Blob Storage.
await blob.UploadTextAsync(serialised).ConfigureAwait(false);
// Returns true, if everything is OK.
return true;
}
view raw upload.cs hosted with ❤ by GitHub

코드가 완성이 되었다. 이제 이를 HTTP 트리거 안에서 하나의 워크플로우로 만들어 호출한다.

[FunctionName(nameof(BackupSecrets))]
public async Task<IActionResult> BackupSecrets(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "secrets/backup")] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
// Gets the list of secrets.
var secrets = await this.GetSecretsAsync().ConfigureAwait(false);
// Performs the backup.
var results = await this.BackupSecretsAsync(secrets).ConfigureAwait(false);
// Uploads the backup data.
var uploaded = await this.UploadAsync(results).ConfigureAwait(false);
return new OkObjectResult(results);
}

실제로 제대로 작동하는지 우선 로컬 개발 환경에서 확인해 보도록 하자. 로컬 개발 환경에서 관리 ID 기능을 사용하려면 애저 CLI로 먼저 로그인을 한 상태여야 한다. 실제로 VS 코드에서 디버깅 모드를 실행시킨 후 포스트맨으로 호출해 보면 아래와 같다.

로컬 애저 블롭 저장소 에뮬레이터인 Azurite에 제대로 잘 저장된 것을 확인할 수 있다.

지금까지 애저 키 저장소의 시크릿 값들을 애저 펑션을 이용해서 애저 블롭 저장소에 백업하는 방법에 대해 알아 보았다.

키 저장소 복구 워크플로우

이전 포스트에서는 일단 모든 백업 파일의 리스트를 가져와서 최신 백업 파일을 찾아 복구하는 워크플로우였다면, 이번에는 특정 일자의 백업 파일을 복구하는 워크플로우를 만들어 보자. 대략의 워크플로우는 아래와 같다.

  1. 복구하고자 하는 날짜의 타임스탬프 값을 입력 받는다
  2. 애저 블롭 저장소에서 타임스탬프에 해당하는 백업 파일을 다운로드 받는다
  3. 다운로드 받은 파일을 비직렬화한다
  4. 키 저장소에 복구한다

타임스탬프는 yyyyMMdd의 형식이고 이는 펑션 엔드포인트의 URL을 통해 입력받는다. 이 타임스탬프를 통해 애저 블롭 저장소에서 다운로드 받는 부분은 아래와 같다.

public async Task<List<BackupSecretResult>> DownloadAsync(string timestamp)
{
// Declares the BlobClient instance.
var connectionString = Environment.GetEnvironmentVariable("AzureWebJobsStorage");
var client = CloudStorageAccount.Parse(connectionString)
.CreateCloudBlobClient();
// Gets the Blob container.
var containerName = "backups";
var container = client.GetContainerReference(containerName);
// Gets the Blob.
var blobName = $"{timestamp}.json";
var blob = container.GetBlockBlobReference(blobName);
// Downloads the Blob content.
var downloaded = await blob.DownloadTextAsync().ConfigureAwait(false);
// Deserialises the contents.
var results = JsonConvert.DeserializeObject<List<BackupSecretResult>>(downloaded);
// Returns the result.
return results;
}
view raw download.cs hosted with ❤ by GitHub

다운로드 받은 파일을 비직렬화해서 반환하면 이 내용을 받은 아래 메소드는 반복문을 실행하면서 복구한다.

public async Task<List<string>> RestoreSecretsAsync(string key, List<BackupSecretResult> secrets)
{
// Declares a KeyVaultClient instance.
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var kv = new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(
azureServiceTokenProvider.KeyVaultTokenCallback));
// Performs the restore and add the result into the list.
var results = new List<SecretBundle>();
var baseUri = "https://my-keyvault.vault.azure.net/";
foreach (var secret in secrets)
{
var result = await kv.RestoreSecretAsync(baseUri, secret.Value)
.ConfigureAwait(false);
results.Add(result);
}
// Returns the list of secret names.
return results.Select(p => p.SecretIdentifier.Name).ToList();
}

이렇게 기본적인 복구 로직이 완성되었으니, 이를 애저 펑션 트리거에 워크플로우를 만들어 구성해 보자.

[FunctionName(nameof(RestoreSecrets))]
public async Task<IActionResult> RestoreSecrets(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = "secrets/restore/{timestamp}")] HttpRequest req,
string timestamp,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
// Downloads the backup of the given timestamp.
var secrets = await this._blob.DownloadAsync(timestamp).ConfigureAwait(false);
// Performs the restore from the backup.
var results = await this._secret.RestoreSecretsAsync("restore", secrets).ConfigureAwait(false);
return new OkObjectResult(results);
}

이렇게 만들어진 워크플로우를 포스트맨에서 실행시켜 보면 아래와 같은 결과를 받는다.

그리고, 실제로 애저 키 저장소에 이렇게 복구가 됐다.


지금까지 애저 펑션을 이용해서 애저 키 저장소의 시크릿 값들을 백업하고 복구하는 절차에 대해 알아보았다. 이 포스트에 올라온 코드는 제대로 작동을 하기는 하지만, 편의상 불필요한 부분은 생략하고 핵심만 나타냈다. 전체 코드라고 해도 생각보다 까다롭지는 않으므로 실제로 리포지토리에서 다운로드 받은 후, 애저 클라우드에 무료로 계정을 만들어서 연습해 보자.