Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SQL Azure - add support for AD admin #1047

Merged
merged 5 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Release Notes
## 1.7.25
* Network Security Groups: Fix bug where a SecurityRule without a source throws a meaningful exception
* Network Security Groups: Add rule to existing security group
* SQL Azure: Adds support for AD admin

## 1.7.24
* Network Interface: Adds support for network interface creation.
Expand Down
89 changes: 77 additions & 12 deletions docs/content/api-overview/resources/sql.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,29 @@ The SQL Azure module contains two builders - `sqlServer`, used to create SQL Azu
* SQL Azure server (`Microsoft.Sql/servers`)

#### SQL Server Builder Keywords
| Keyword | Purpose |
|-|-|
| name | Sets the name of the SQL server. |
| add_firewall_rule | Adds a custom firewall rule given a name, start and end IP address range. |
| add_firewall_rules | As add_firewall_rule but a list of rules |
| enable_azure_firewall | Adds a firewall rule that enables access to other Azure services. |
| admin_username | Sets the admin username of the server. |
| elastic_pool_name | Sets the name of the elastic pool, if required. If not set, Farmer will generate a name for you. |
| elastic_pool_sku | Sets the sku of the elastic pool, if required. If not set, Farmer will default to Basic 50. |
| elastic_pool_database_min_max | Sets the optional minimum and maximum DTUs for the elastic pool for each database. |
| elastic_pool_capacity | Sets the optional disk size in MB for the elastic pool for each database. |
| min_tls_version | Sets the minium TLS version for the SQL server |
| Keyword | Purpose |
|-|---------------------------------------------------------------------------------------------------------------------------------|
| name | Sets the name of the SQL server. |
| active_directory_admin | Sets Active Directory admin of the server |
| add_firewall_rule | Adds a custom firewall rule given a name, start and end IP address range. |
| add_firewall_rules | As add_firewall_rule but a list of rules |
| enable_azure_firewall | Adds a firewall rule that enables access to other Azure services. |
| admin_username | Sets the admin username of the server. |
| elastic_pool_name | Sets the name of the elastic pool, if required. If not set, Farmer will generate a name for you. |
| elastic_pool_sku | Sets the sku of the elastic pool, if required. If not set, Farmer will default to Basic 50. |
| elastic_pool_database_min_max | Sets the optional minimum and maximum DTUs for the elastic pool for each database. |
| elastic_pool_capacity | Sets the optional disk size in MB for the elastic pool for each database. |
| min_tls_version | Sets the minium TLS version for the SQL server |
| geo_replicate | Geo-replicate all the databases in this server to another location, having NameSuffix after original server and database names. |

#### ActiveDirectoryAdminSettings Members
| Member | Purpose |
|-|----------------------------------------------------------------------------|
| Login | Display name of AD admin |
| Sid | AD object id of AD admin (user or group) |
| PrincipalType | ActiveDirectoryPrincipalType User or Group |
| AdOnlyAuth | Disables SQL authentication. False value required admin_username to be set |

#### SQL Server Configuration Members
| Member | Purpose |
|-|-|
Expand All @@ -43,6 +52,8 @@ The SQL Azure module contains two builders - `sqlServer`, used to create SQL Azu
| use_encryption | Enables transparent data encryption of the database. |

#### Example

##### AD auth not set
```fsharp
open Farmer
open Farmer.Builders
Expand Down Expand Up @@ -84,3 +95,57 @@ template
template
|> Deploy.execute "my-resource-group" [ "password-for-my_server", "*****" ]
```

##### AD auth set
```fsharp
open Farmer
open Farmer.Builders
open Sql
open Farmer.Arm.Sql

let activeDirectoryAdmin: ActiveDirectoryAdminSettings =
{
Login = "adadmin"
Sid = "F9D49C34-01BA-4897-B7E2-3694BF3DE2CF"
PrincipalType = ActiveDirectoryPrincipalType.User
AdOnlyAuth = false // when false, admin_username is required
// when true admin_username is ignored
}

let myDatabases = sqlServer {
name "my_server"
active_directory_admin (Some(activeDirectoryAdmin))
admin_username "admin_username"
enable_azure_firewall

elastic_pool_name "mypool"
elastic_pool_sku PoolSku.Basic100

add_databases [
sqlDb { name "poolDb1" }
sqlDb { name "poolDb2" }
sqlDb { name "dtuDb"; sku Basic }
sqlDb { name "memoryDb"; sku M_8 }
sqlDb { name "cpuDb"; sku Fsv2_8 }
sqlDb { name "businessCriticalDb"; sku (BusinessCritical Gen5_2) }
sqlDb { name "hyperscaleDb"; sku (Hyperscale Gen5_2) }
sqlDb {
name "generalPurposeDb"
sku (GeneralPurpose Gen5_8)
db_size (1024<Mb> * 128)
hybrid_benefit
}
]
}

let template = arm {
location Location.NorthEurope
add_resource myDatabases
}

template
|> Writer.quickWrite "sql-example"

template
|> Deploy.execute "my-resource-group" [ "password-for-my_server", "*****" ]
```
112 changes: 99 additions & 13 deletions src/Farmer/Arm/Sql.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ open Farmer
open Farmer.Sql
open System.Net

let servers = ResourceType("Microsoft.Sql/servers", "2019-06-01-preview")
let servers = ResourceType("Microsoft.Sql/servers", "2022-05-01-preview")

let elasticPools =
ResourceType("Microsoft.Sql/servers/elasticPools", "2017-10-01-preview")
Expand All @@ -23,18 +23,111 @@ type DbKind =
| Standalone of DbPurchaseModel
| Pool of ResourceName

type ActiveDirectoryPrincipalType =
| User
| Group

type ActiveDirectoryAdminSettings =
{
/// Ideally same as AD name
Login: string
/// Active Directory object id of user or group
Sid: string
PrincipalType: ActiveDirectoryPrincipalType
AdOnlyAuth: bool
}

let (|MixedModeAuth|AdOnlyAuth|SqlOnlyAuth|) activeDirAdmin =
match activeDirAdmin with
| Some x when x.AdOnlyAuth -> AdOnlyAuth(x)
| Some x when not x.AdOnlyAuth -> MixedModeAuth(x)
| _ -> SqlOnlyAuth

type SqlServerADAdminJsonProperties =
{
administratorType: string
principalType: string
login: string
sid: string
azureADOnlyAuthentication: bool
}

type SqlServerJsonProperties =
{
version: string
minimalTlsVersion: string
administratorLogin: string
administratorLoginPassword: string
administrators: SqlServerADAdminJsonProperties
}

type Server =
{
ServerName: SqlAccountName
Location: Location
Credentials: {| Username: string
Password: SecureParameter |}
ActiveDirectoryAdmin: ActiveDirectoryAdminSettings option
MinTlsVersion: TlsVersion option
Tags: Map<string, string>
}

member private this.BuildSqlSeverPropertiesBase() : SqlServerJsonProperties =
{
version = "12.0"
minimalTlsVersion =
match this.MinTlsVersion with
| Some Tls10 -> "1.0"
| Some Tls11 -> "1.1"
| Some Tls12 -> "1.2"
| None -> null
administratorLogin = null
administratorLoginPassword = null
administrators = Unchecked.defaultof<SqlServerADAdminJsonProperties>
}

member private this.BuildSqlServerADOnlyAdmin(x: ActiveDirectoryAdminSettings) : SqlServerADAdminJsonProperties =
{
administratorType = "ActiveDirectory"
principalType =
match x.PrincipalType with
| Group -> "Group"
| User -> "User"
login = x.Login
sid = x.Sid
azureADOnlyAuthentication = true
}

member private this.BuildSqlServerPropertiesWithMixedModeAdministrator
(x: ActiveDirectoryAdminSettings)
: SqlServerJsonProperties =
{ this.BuildSqlSeverPropertiesBase() with
administratorLogin = this.Credentials.Username
administratorLoginPassword = this.Credentials.Password.ArmExpression.Eval()
administrators =
{ this.BuildSqlServerADOnlyAdmin(x) with
azureADOnlyAuthentication = false
}
}

member private this.BuildSqlServerPropertiesWithADOnlyAdministrator
(x: ActiveDirectoryAdminSettings)
: SqlServerJsonProperties =
{ this.BuildSqlSeverPropertiesBase() with
administrators = this.BuildSqlServerADOnlyAdmin(x)
}

member private this.BuildSqlServerPropertiesWithSqlOnlyAdministrator() : SqlServerJsonProperties =
{ this.BuildSqlSeverPropertiesBase() with
administratorLogin = this.Credentials.Username
administratorLoginPassword = this.Credentials.Password.ArmExpression.Eval()
}

interface IParameters with
member this.SecureParameters = [ this.Credentials.Password ]
member this.SecureParameters =
match this.ActiveDirectoryAdmin with
| Some (x) when x.AdOnlyAuth -> []
| _ -> [ this.Credentials.Password ]

interface IArmResource with
member this.ResourceId = servers.resourceId this.ServerName.ResourceName
Expand All @@ -46,17 +139,10 @@ type Server =
tags = (this.Tags |> Map.add "displayName" this.ServerName.ResourceName.Value)
) with
properties =
{|
administratorLogin = this.Credentials.Username
administratorLoginPassword = this.Credentials.Password.ArmExpression.Eval()
version = "12.0"
minimalTlsVersion =
match this.MinTlsVersion with
| Some Tls10 -> "1.0"
| Some Tls11 -> "1.1"
| Some Tls12 -> "1.2"
| None -> null
|}
match this.ActiveDirectoryAdmin with
| MixedModeAuth x -> this.BuildSqlServerPropertiesWithMixedModeAdministrator(x)
| AdOnlyAuth x -> this.BuildSqlServerPropertiesWithADOnlyAdministrator(x)
| SqlOnlyAuth -> this.BuildSqlServerPropertiesWithSqlOnlyAdministrator()
|}

module Servers =
Expand Down
3 changes: 2 additions & 1 deletion src/Farmer/Builders/Builders.ContainerService.fs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,8 @@ type AgentPoolBuilder() =

/// Sets the agent pool to user mode.
[<CustomOperation "user_mode">]
member _.UserMode(state: AgentPoolConfig) = { state with Mode = User }
member _.UserMode(state: AgentPoolConfig) =
{ state with Mode = AgentPoolMode.User }

/// Sets the disk size for the VM's in the agent pool.
[<CustomOperation "disk_size">]
Expand Down
49 changes: 36 additions & 13 deletions src/Farmer/Builders/Builders.Sql.fs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type SqlAzureConfig =
Name: SqlAccountName
AdministratorCredentials: {| UserName: string
Password: SecureParameter |}
ActiveDirectoryAdmin: ActiveDirectoryAdminSettings option
MinTlsVersion: TlsVersion option
FirewallRules: {| Name: ResourceName
Start: IPAddress
Expand Down Expand Up @@ -74,10 +75,14 @@ type SqlAzureConfig =
ServerName = this.Name
Location = location
Credentials =
{|
Username = this.AdministratorCredentials.UserName
Password = this.AdministratorCredentials.Password
|}
match this.ActiveDirectoryAdmin with
| AdOnlyAuth _ -> Unchecked.defaultof<_>
| _ ->
{|
Username = this.AdministratorCredentials.UserName
Password = this.AdministratorCredentials.Password
|}
ActiveDirectoryAdmin = this.ActiveDirectoryAdmin
MinTlsVersion = this.MinTlsVersion
Tags = this.Tags
}
Expand Down Expand Up @@ -144,6 +149,7 @@ type SqlAzureConfig =
Username = this.AdministratorCredentials.UserName
Password = this.AdministratorCredentials.Password
|}
ActiveDirectoryAdmin = this.ActiveDirectoryAdmin
MinTlsVersion = this.MinTlsVersion
Tags = this.Tags
}
Expand Down Expand Up @@ -277,6 +283,7 @@ type SqlServerBuilder() =
UserName = ""
Password = SecureParameter ""
|}
ActiveDirectoryAdmin = None
ElasticPoolSettings =
{|
Name = None
Expand All @@ -295,16 +302,25 @@ type SqlServerBuilder() =
if state.Name.ResourceName = ResourceName.Empty then
raiseFarmer "No SQL Server account name has been set."

{ state with
AdministratorCredentials =
if System.String.IsNullOrWhiteSpace state.AdministratorCredentials.UserName then
raiseFarmer
$"You must specify the admin_username for SQL Server instance {state.Name.ResourceName.Value}"
let getStateWithAdminCredentials () =
if System.String.IsNullOrWhiteSpace state.AdministratorCredentials.UserName then
raiseFarmer
$"You must specify the admin_username for SQL Server instance {state.Name.ResourceName.Value}"

{| state.AdministratorCredentials with
Password = SecureParameter state.PasswordParameter
|}
}
{ state with
AdministratorCredentials =
{| state.AdministratorCredentials with
Password = SecureParameter state.PasswordParameter
|}
}

match state.ActiveDirectoryAdmin with
| AdOnlyAuth _ ->
{ state with
AdministratorCredentials = Unchecked.defaultof<_>
}
| MixedModeAuth _ -> getStateWithAdminCredentials ()
| SqlOnlyAuth -> getStateWithAdminCredentials ()

/// Sets the name of the SQL server.
[<CustomOperation "name">]
Expand Down Expand Up @@ -420,6 +436,13 @@ type SqlServerBuilder() =
GeoReplicaServer = Some replicaSettings
}

/// Sets the active directory admin and optionally turns on AD only auth.
[<CustomOperation "active_directory_admin">]
member _.SetActiveDirectoryAdmin(state: SqlAzureConfig, activeDirectoryAdminSettings) =
{ state with
ActiveDirectoryAdmin = activeDirectoryAdminSettings
}

interface ITaggable<SqlAzureConfig> with
member _.Add state tags =
{ state with
Expand Down
6 changes: 1 addition & 5 deletions src/Tests/AllTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,4 @@ let allTests =
let main _ =
printfn "Running tests!"

runTests
{ defaultConfig with
verbosity = Logging.Info
}
allTests
Tests.runTestsWithCLIArgs [] [| (*"--debug"*) |] allTests
Loading