in src/ccf/caci-ccf-provider/CAciRecoveryServiceInstanceProvider.cs [262:581]
private async Task<ContainerGroupData> CreateContainerGroup(
string instanceName,
string serviceName,
string akvEndpoint,
string maaEndpoint,
string managedIdentityId,
NetworkJoinPolicy networkJoinPolicy,
SecurityPolicyConfiguration policyOption,
JsonObject providerConfig,
string dnsNameLabel)
{
var client = new ArmClient(new DefaultAzureCredential());
string location = providerConfig["location"]!.ToString();
string subscriptionId = providerConfig["subscriptionId"]!.ToString();
string resourceGroupName = providerConfig["resourceGroupName"]!.ToString();
ResourceIdentifier resourceGroupResourceId =
ResourceGroupResource.CreateResourceIdentifier(subscriptionId, resourceGroupName);
ResourceGroupResource resourceGroupResource =
client.GetResourceGroupResource(resourceGroupResourceId);
ContainerGroupCollection collection = resourceGroupResource.GetContainerGroups();
string base64EncodedJoinPolicy = Convert.ToBase64String(
Encoding.UTF8.GetBytes(JsonSerializer.Serialize(networkJoinPolicy)));
var containerGroupSecurityPolicy = await GetContainerGroupSecurityPolicy();
ContainerGroupData data = CreateContainerGroupData(
location,
instanceName,
serviceName,
akvEndpoint,
maaEndpoint,
managedIdentityId,
networkJoinPolicy,
dnsNameLabel,
containerGroupSecurityPolicy);
this.logger.LogInformation(
$"Starting container group creation for recovery service: {instanceName}");
ArmOperation<ContainerGroupResource> lro = await collection.CreateOrUpdateAsync(
WaitUntil.Completed,
instanceName,
data);
ContainerGroupResource result = lro.Value;
// The variable result is a resource, you could call other operations on this instance as
// well.
ContainerGroupData resourceData = result.Data;
this.logger.LogInformation(
$"container group creation succeeded. " +
$"id: {resourceData.Id}, IP address: {resourceData.IPAddress.IP}, " +
$"fqdn: {resourceData.IPAddress.Fqdn}");
return resourceData;
async Task<ContainerGroupSecurityPolicy> GetContainerGroupSecurityPolicy()
{
this.logger.LogInformation($"policyCreationOption: {policyOption.PolicyCreationOption}");
if (policyOption.PolicyCreationOption == SecurityPolicyCreationOption.allowAll ||
policyOption.PolicyCreationOption == SecurityPolicyCreationOption.userSupplied)
{
var ccePolicyInput = policyOption.PolicyCreationOption ==
SecurityPolicyCreationOption.allowAll ?
AciConstants.AllowAllRegoBase64 : policyOption.Policy!;
return new ContainerGroupSecurityPolicy
{
ConfidentialComputeCcePolicy = ccePolicyInput,
Images = new()
{
{
AciConstants.ContainerName.CcfRecoveryService,
$"{ImageUtils.CcfRecoveryServiceImage()}:" +
$"{ImageUtils.CcfRecoveryServiceTag()}"
},
{
AciConstants.ContainerName.CcrAttestation,
$"{ImageUtils.CcrAttestationImage()}:{ImageUtils.CcrAttestationTag()}"
},
{
AciConstants.ContainerName.Skr,
$"{ImageUtils.SkrImage()}:{ImageUtils.SkrTag()}"
},
{
AciConstants.ContainerName.CcrProxy,
$"{ImageUtils.CcrProxyImage()}:{ImageUtils.CcrProxyTag()}"
}
}
};
}
(var policyRego, var policyDocument) = await this.DownloadAndExpandPolicy(
policyOption.PolicyCreationOption,
base64EncodedJoinPolicy);
var ccePolicy = Convert.ToBase64String(Encoding.UTF8.GetBytes(policyRego));
var policyContainers = policyDocument.Containers.ToDictionary(x => x.Name, x => x);
List<string> requiredContainers =
[
AciConstants.ContainerName.CcfRecoveryService,
AciConstants.ContainerName.CcrAttestation,
AciConstants.ContainerName.Skr,
AciConstants.ContainerName.CcrProxy
];
var missingContainers = requiredContainers.Where(r => !policyContainers.ContainsKey(r));
if (missingContainers.Any())
{
throw new Exception(
$"Policy document is missing the following required containers: " +
$"{JsonSerializer.Serialize(missingContainers)}");
}
var securityPolicy = new ContainerGroupSecurityPolicy
{
ConfidentialComputeCcePolicy = ccePolicy,
Images = []
};
foreach (var containerName in requiredContainers)
{
var pc = policyContainers[containerName];
securityPolicy.Images.Add(containerName, $"{pc.Image}@{pc.Digest}");
}
return securityPolicy;
}
ContainerGroupData CreateContainerGroupData(
string location,
string instanceName,
string serviceName,
string akvEndpoint,
string maaEndpoint,
string managedIdentityId,
NetworkJoinPolicy? networkJoinPolicy,
string dnsNameLabel,
ContainerGroupSecurityPolicy containerGroupSecurityPolicy)
{
#pragma warning disable MEN002 // Line is too long
return new ContainerGroupData(
new AzureLocation(location),
new ContainerInstanceContainer[]
{
new(
AciConstants.ContainerName.CcfRecoveryService,
containerGroupSecurityPolicy.Images[AciConstants.ContainerName.CcfRecoveryService],
new ContainerResourceRequirements(new ContainerResourceRequestsContent(1.5, 1)))
{
EnvironmentVariables =
{
new ContainerEnvironmentVariable("ASPNETCORE_URLS")
{
Value = $"http://+:{Ports.RecoveryServicePort}"
},
new ContainerEnvironmentVariable("AKV_ENDPOINT")
{
Value = akvEndpoint
},
new ContainerEnvironmentVariable("MAA_ENDPOINT")
{
Value = maaEndpoint
},
new ContainerEnvironmentVariable("SKR_ENDPOINT")
{
Value = $"http://localhost:{Ports.SkrPort}"
},
new ContainerEnvironmentVariable("SERVICE_CERT_LOCATION")
{
Value = ServiceCertPemFilePath
},
new ContainerEnvironmentVariable("CCF_NETWORK_INITIAL_JOIN_POLICY")
{
Value = base64EncodedJoinPolicy
}
},
VolumeMounts =
{
new ContainerVolumeMount("uds", "/mnt/uds"),
new ContainerVolumeMount("shared", "/app/service")
}
},
new(
AciConstants.ContainerName.CcrAttestation,
containerGroupSecurityPolicy.Images[AciConstants.ContainerName.CcrAttestation],
new ContainerResourceRequirements(
new ContainerResourceRequestsContent(0.5, 0.2)))
{
Command =
{
"app",
"-socket-address",
"/mnt/uds/sock"
},
VolumeMounts =
{
new ContainerVolumeMount("uds", "/mnt/uds")
}
},
new(
AciConstants.ContainerName.Skr,
containerGroupSecurityPolicy.Images[AciConstants.ContainerName.Skr],
new ContainerResourceRequirements(
new ContainerResourceRequestsContent(0.5, 0.2)))
{
Command =
{
"/skr.sh"
},
EnvironmentVariables =
{
new ContainerEnvironmentVariable("SkrSideCarArgs")
{
Value = "ewogICAiY2VydGNhY2hlIjogewogICAgICAiZW5kcG9pbnQiOiAiYW1lcmljYXMuYWNjY2FjaGUuYXp1cmUubmV0IiwKICAgICAgInRlZV90eXBlIjogIlNldlNucFZNIiwKICAgICAgImFwaV92ZXJzaW9uIjogImFwaS12ZXJzaW9uPTIwMjAtMTAtMTUtcHJldmlldyIKICAgfQp9"
},
new ContainerEnvironmentVariable("Port")
{
Value = $"{Ports.SkrPort}"
},
new ContainerEnvironmentVariable("LogLevel")
{
Value = "Info"
},
new ContainerEnvironmentVariable("LogFile")
{
Value = "skr.log"
}
}
},
new(
AciConstants.ContainerName.CcrProxy,
containerGroupSecurityPolicy.Images[AciConstants.ContainerName.CcrProxy],
new ContainerResourceRequirements(
new ContainerResourceRequestsContent(0.5, 0.2)))
{
Ports =
{
new ContainerPort(Ports.EnvoyPort)
},
Command =
{
"/bin/sh",
"https-http/bootstrap.sh"
},
EnvironmentVariables =
{
new ContainerEnvironmentVariable("CCR_ENVOY_DESTINATION_PORT")
{
Value = Ports.RecoveryServicePort.ToString()
},
new ContainerEnvironmentVariable("CCR_ENVOY_SERVICE_CERT_OUTPUT_FILE")
{
Value = ServiceCertPemFilePath
}
},
VolumeMounts =
{
new ContainerVolumeMount("shared", ServiceFolderMountPath)
}
},
},
ContainerInstanceOperatingSystemType.Linux)
{
Sku = ContainerGroupSku.Confidential,
ConfidentialComputeCcePolicy =
containerGroupSecurityPolicy.ConfidentialComputeCcePolicy,
Identity = new ManagedServiceIdentity(ManagedServiceIdentityType.UserAssigned)
{
UserAssignedIdentities =
{
{
new ResourceIdentifier(managedIdentityId),
new UserAssignedIdentity()
}
}
},
Tags =
{
{
AciConstants.CcfRecoveryServiceNameTag,
serviceName
},
{
AciConstants.CcfRecoveryServiceTypeTag,
"recovery-service"
},
{
AciConstants.CcfRecoveryServiceResourceNameTag,
instanceName
}
},
IPAddress = new ContainerGroupIPAddress(
new ContainerGroupPort[]
{
new(Ports.EnvoyPort)
{
Protocol = ContainerGroupNetworkProtocol.Tcp,
}
},
ContainerGroupIPAddressType.Public)
{
DnsNameLabel = dnsNameLabel,
AutoGeneratedDomainNameLabelScope = DnsNameLabelReusePolicy.Unsecure
},
Volumes =
{
new ContainerVolume("uds")
{
EmptyDir = BinaryData.FromObjectAsJson(new Dictionary<string, object>())
},
new ContainerVolume("shared")
{
EmptyDir = BinaryData.FromObjectAsJson(new Dictionary<string, object>())
}
}
};
#pragma warning restore MEN002 // Line is too long
}
}