Skip to content

Commit

Permalink
Merge pull request CompositionalIT#977 from ahmed-ebaid/managed_ident…
Browse files Browse the repository at this point in the history
…ity_support

ACR Managed Identity Support for Container Groups and Container Apps
  • Loading branch information
ninjarobot authored Sep 21, 2022
2 parents c9c3a9d + f1c215e commit 043ee73
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 24 deletions.
3 changes: 3 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
Release Notes
=============
## 1.7.9
* Container Group: Support for Managed Identity
* Container App: Support for Managed Identity

## 1.7.8
* Route Tables: Initial support for Route Tables and Routes
Expand Down
1 change: 1 addition & 0 deletions docs/content/api-overview/resources/container-apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ The Container Apps builder (`containerApp`) is used to define one or more contai
| active_revision_mode | Indicates whether multiple version of a container app can be active at once.|
| add_registry_credentials | Adds container image registry credentials for images in this container app, which are a list of server and usernames. Passwords are supplied as secure parameters. |
| reference_registry_credentials | Adds container image registry credentials for images in this container app in the form of a list of Azure resource ids. |
| add_managed_identity_registry_credentials | Adds container app registry managed identity credentials for images in this container app, which are a list of server and identities. |
| add_containers | Adds a list of containers to this container app. All containers in the app share resources and scaling. |
| add_simple_container | Adds a single container that references a public docker image and version. |
| add_secret_parameter | Adds an application secret to the entire container app. This is passed as a secure parameter to the template, and an environment variable is automatically created which references the secret. |
Expand Down
4 changes: 3 additions & 1 deletion docs/content/api-overview/resources/container-group.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ The Container Group builder (`containerGroup`) defines a Container Group.
| system_identity | Activates the system identity of the container group. |
| add_registry_credentials | Adds a container image registry credential with a secure parameter for the password. |
| reference_registry_credentials | References credentials from a container image registry by resource ID. |
| add_managed_identity_registry_credentials | Adds container image registry managed identity credentials for images in this container group. |
| add_tcp_port | Adds a TCP port to be externally accessible. |
| add_udp_port | Adds a UDP port to be externally accessible. |
| add_volumes | Adds volumes to a container group so they are accessible to containers. |
Expand Down Expand Up @@ -151,6 +152,7 @@ let containerGroupUser = userAssignedIdentity {
}
let containerGroupLoggingWorkspace = logAnalytics { name "webapplogs" }
let managedIdentity = ManagedIdentity.Empty
let group = containerGroup {
name "webApp"
Expand All @@ -162,7 +164,7 @@ let group = containerGroup {
add_instances [ nginx ]
// Add registry credentials as a secure password
add_registry_credentials [
registry "mygregistry.azurecr.io" "registryuser"
registry "mygregistry.azurecr.io" "registryuser" managedIdentity
]
// or reference an Azure container registry to pull the credentials directly.
reference_registry_credentials [
Expand Down
27 changes: 27 additions & 0 deletions src/Farmer/Arm/App.fs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[<AutoOpen>]
module Farmer.Arm.App

open System
open Farmer.ContainerApp
open Farmer.Identity
open Farmer
Expand Down Expand Up @@ -122,6 +123,8 @@ type ContainerApp =
match credential with
| ImageRegistryAuthentication.Credential credential -> credential.Password
| ImageRegistryAuthentication.ListCredentials _ -> ()
| ImageRegistryAuthentication.ManagedIdentityCredential _ -> ()

]

interface IArmResource with
Expand Down Expand Up @@ -160,6 +163,17 @@ type ContainerApp =
$"listCredentials({resourceId.ArmExpression.Value}, '2019-05-01').passwords[0].value"
)
.Eval()
|}
| ImageRegistryAuthentication.ManagedIdentityCredential cred ->
{|
name = cred.Server
value =
if cred.Identity.Dependencies.Length > 0 then
cred.Identity.Dependencies.Head.ArmExpression.Eval()
else
String.Empty


|}
for setting in this.Secrets do
{|
Expand All @@ -180,6 +194,7 @@ type ContainerApp =
server = cred.Server
username = cred.Username
passwordSecretRef = cred.Username
identity = null
|}
| ImageRegistryAuthentication.ListCredentials resourceId ->
{|
Expand All @@ -191,6 +206,18 @@ type ContainerApp =
)
.Eval()
passwordSecretRef = usernameSecretName resourceId
identity = null
|}
| ImageRegistryAuthentication.ManagedIdentityCredential cred ->
{|
server = cred.Server
username = String.Empty
passwordSecretRef = null
identity =
if cred.Identity.Dependencies.Length > 0 then
cred.Identity.Dependencies.Head.ArmExpression.Eval()
else
String.Empty
|}
|]
ingress =
Expand Down
14 changes: 14 additions & 0 deletions src/Farmer/Arm/ContainerInstance.fs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ type ContainerGroup =
match credential with
| ImageRegistryAuthentication.Credential credential -> credential.Password
| ImageRegistryAuthentication.ListCredentials _ -> ()
| ImageRegistryAuthentication.ManagedIdentityCredential _ -> ()
for container in this.ContainerInstances do
for envVar in container.EnvironmentVariables do
match envVar.Value with
Expand Down Expand Up @@ -365,6 +366,7 @@ type ContainerGroup =
server = cred.Server
username = cred.Username
password = cred.Password.ArmExpression.Eval()
identity = null
|}
| ImageRegistryAuthentication.ListCredentials resourceId ->
{|
Expand All @@ -386,6 +388,18 @@ type ContainerGroup =
$"listCredentials({resourceId.ArmExpression.Value}, '2019-05-01').passwords[0].value"
)
.Eval()
identity = null
|}
| ImageRegistryAuthentication.ManagedIdentityCredential cred ->
{|
server = cred.Server
username = String.Empty
password = null
identity =
if cred.Identity.Dependencies.Length > 0 then
cred.Identity.Dependencies.Head.ArmExpression.Eval()
else
String.Empty
|})
ipAddress =
match this.IpAddress with
Expand Down
9 changes: 9 additions & 0 deletions src/Farmer/Builders/Builders.ContainerApps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,15 @@ type ContainerAppBuilder() =
@ (resourceIds |> List.map ImageRegistryAuthentication.ListCredentials)
}

/// Adds container app registry managed identity credentials for images in this container app.
[<CustomOperation "add_managed_identity_registry_credentials">]
member _.ManagedIdentityRegistryCredentials(state: ContainerAppConfig, credentials) =
{ state with
ImageRegistryCredentials =
state.ImageRegistryCredentials
@ (credentials |> List.map ImageRegistryAuthentication.ManagedIdentityCredential)
}

/// Adds one or more containers to the container app.
[<CustomOperation "add_containers">]
member _.AddContainers(state: ContainerAppConfig, containers: ContainerConfig list) =
Expand Down
13 changes: 12 additions & 1 deletion src/Farmer/Builders/Builders.ContainerGroups.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
module Farmer.Builders.ContainerGroups

open Farmer
open Farmer.Arm
open Farmer.Builders
open Farmer.ContainerGroup
open Farmer.Identity
Expand Down Expand Up @@ -453,6 +454,15 @@ type ContainerGroupBuilder() =
@ (resourceIds |> List.map ImageRegistryAuthentication.ListCredentials)
}

/// Adds container image registry managed identity credentials for images in this container group.
[<CustomOperation "add_managed_identity_registry_credentials">]
member _.ManagedIdentityRegistryCredentials(state: ContainerGroupConfig, credentials) =
{ state with
ImageRegistryCredentials =
state.ImageRegistryCredentials
@ (credentials |> List.map ImageRegistryAuthentication.ManagedIdentityCredential)
}

/// Adds a collection of init containers to this group that run once on startup before other containers in the group.
[<CustomOperation "add_init_containers">]
member _.AddInitContainers(state: ContainerGroupConfig, initContainers) =
Expand Down Expand Up @@ -610,11 +620,12 @@ type ContainerGroupBuilder() =
}

/// Creates an image registry credential with a generated SecureParameter for the password.
let registry (server: string) (username: string) =
let registry (server: string) (username: string) (managedIdentity: ManagedIdentity) =
{
Server = server
Username = username
Password = SecureParameter $"{server}-password"
Identity = managedIdentity
}

type ContainerInstanceBuilder() =
Expand Down
5 changes: 5 additions & 0 deletions src/Farmer/Common.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1892,6 +1892,8 @@ module Identity =
UserAssigned = userAssignedIdentity :: managedIdentity.UserAssigned
}

open Identity

module Containers =
type DockerImage =
| PrivateImage of RegistryDomain: string * ContainerName: string * Version: string option
Expand Down Expand Up @@ -1927,6 +1929,7 @@ type ImageRegistryCredential =
Server: string
Username: string
Password: SecureParameter
Identity: ManagedIdentity
}

[<RequireQualifiedAccess>]
Expand All @@ -1935,6 +1938,8 @@ type ImageRegistryAuthentication =
| Credential of ImageRegistryCredential
/// Credentials for the container registry will be listed by ARM expression.
| ListCredentials of ResourceId
/// Credentials for the container registry are included with the identity as a template parameter.
| ManagedIdentityCredential of ImageRegistryCredential

[<RequireQualifiedAccess>]
type LogAnalyticsWorkspace =
Expand Down
5 changes: 4 additions & 1 deletion src/Tests/ContainerApps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ let fullContainerAppDeployment =
}

let version = "1.0.0"
let managedIdentity = ManagedIdentity.Empty

let containerEnv =
containerEnvironment {
Expand All @@ -35,7 +36,9 @@ let fullContainerAppDeployment =
name "http"
add_identity msi
active_revision_mode Single
add_registry_credentials [ registry containerRegistryDomain containerRegistryName ]

add_registry_credentials
[ registry containerRegistryDomain containerRegistryName managedIdentity ]

add_containers
[
Expand Down
68 changes: 48 additions & 20 deletions src/Tests/ContainerGroup.fs
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,6 @@ let tests =
Expect.equal group.OsType "Linux" "OS should be Linux"
Expect.equal group.IpAddress.Ports.[1].PortProperty 123 "Incorrect udp port"

Expect.equal
group.NetworkProfile.Id
"[resourceId('Microsoft.Network/networkProfiles', 'test')]"
"Incorrect network profile reference"

let containerInstance = group.Containers.[0]
Expect.equal containerInstance.Image "nginx:1.17.6-alpine" "Incorrect image"
Expect.equal containerInstance.Name "nginx" "Incorrect instance name"
Expand Down Expand Up @@ -271,10 +266,12 @@ let tests =
}

test "Container group with private registry" {
let managedIdentity = ManagedIdentity.Empty

let group =
containerGroup {
add_instances [ nginx ]
add_registry_credentials [ registry "my-registry.azurecr.io" "user" ]
add_registry_credentials [ registry "my-registry.azurecr.io" "user" managedIdentity ]
}
|> asAzureResource

Expand All @@ -289,6 +286,51 @@ let tests =
"Container image registry password should be secure parameter"
}

test "Container group with managed identity to private registry" {
let userAssignedIdentity =
ResourceId.create (Arm.ManagedIdentity.userAssignedIdentities, ResourceName "user", "resourceGroup")
|> UserAssignedIdentity

let managedIdentity =
{ ManagedIdentity.Empty with
UserAssigned = [ userAssignedIdentity ]
}

let group =
containerGroup {
add_instances [ nginx ]

add_identity (
ResourceId.create (
Arm.ManagedIdentity.userAssignedIdentities,
ResourceName "user",
"resourceGroup"
)
|> UserAssignedIdentity
)

add_managed_identity_registry_credentials
[ registry "my-registry.azurecr.io" "user" managedIdentity ]
}
|> asAzureResource

Expect.hasLength
group.ImageRegistryCredentials
1
"Expected one image managed identity registry credential"

let credentials = group.ImageRegistryCredentials.[0]
Expect.equal credentials.Server "my-registry.azurecr.io" "Incorrect container image registry server"
Expect.equal credentials.Username String.Empty "Container image registry user should be null"

Expect.equal
credentials.Identity
(managedIdentity.Dependencies.Head.ArmExpression.Eval())
"Incorrect container image registry identity"

Expect.equal credentials.Password null "Container image registry password should be null"
}

test "Container group with reference to private registry" {
let group =
containerGroup {
Expand Down Expand Up @@ -889,21 +931,7 @@ async {

Expect.equal (string ipConfigName) "ipconfigProfile" "netprofile ipConfiguration has wrong name"
}
test "Can link a network profile directly to a container group" {
let profile = networkProfile { name "netprofile" }

let template =
containerGroup {
name "appWithHttpFrontend"
network_profile profile
}
|> asAzureResource

Expect.equal
"[resourceId('Microsoft.Network/networkProfiles', 'netprofile')]"
template.NetworkProfile.Id
"Incorrect profile name"
}
test "Support for additional dependencies" {
let storage = storageAccount { name "containerstorage" }

Expand Down
2 changes: 1 addition & 1 deletion src/Tests/Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
<PackageReference Include="Microsoft.Azure.Management.Dns" Version="3.0.1" />
<PackageReference Include="Microsoft.Azure.Management.CognitiveServices" Version="7.0.0" />
<PackageReference Include="Microsoft.Azure.Management.Compute" Version="35.2.0" />
<PackageReference Include="Microsoft.Azure.Management.ContainerInstance" Version="3.0.0" />
<PackageReference Include="Microsoft.Azure.Management.ContainerInstance" Version="7.0.0" />
<PackageReference Include="Microsoft.Azure.Management.ContainerService" Version="1.1.0" />
<PackageReference Include="Microsoft.Azure.Management.DeviceProvisioningServices" Version="1.0.0" />
<PackageReference Include="Microsoft.Azure.Management.IotHub" Version="2.10.0-preview" />
Expand Down

0 comments on commit 043ee73

Please sign in to comment.