Skip to content

Commit

Permalink
Merge pull request #1147 from CompositionalIT/sql-auth-enhancements
Browse files Browse the repository at this point in the history
Sql auth enhancements
  • Loading branch information
isaacabraham authored Oct 27, 2024
2 parents 6dd7b38 + 63c0034 commit 606c117
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 267 deletions.
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
Release Notes
=============
## vNext
* SQL Azure: Clean up Entra ID authentication support.
* Az: Update `ad` commands to work with latest (breaking) structure.
* PostgreSQL: Fix a number of issues around the introduction of Flexible Servers.

Expand Down
37 changes: 17 additions & 20 deletions docs/content/api-overview/resources/sql.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,25 @@ 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 |
| 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 |
| 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. The password is supplied as a secret parameter at runtime. Optional if you use any of the `entra_id_admin` keywords. |
| entra_id_admin | Activates Entra ID authentication using the supplied login named, associated objectId and principal type of the administrator account. Optional if you use `admin_username`. |
| entra_id_admin_user | Activates Entra ID authentication for the User Principal Type using the supplied user's login name. You can determine the ObjectId using `Farmer.Builders.AccessPolicy.findUsers`. Optional if you use `admin_username`. |
| entra_id_admin_group | Activates Entra ID authentication for the Group Principal Type using the supplied group's login name. You can determine the ObjectId using `Farmer.Builders.AccessPolicy.findGroups`. Optional if you use `admin_username`. |
| 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 |
> You can set at least one of SQL user / pass (using `admin_username`) or Entra ID login (using one of the `entra_id_admin` variants).
> Setting both will leave both activated; setting only Entra ID will automatically explicitly deactivate user / pass authentication.
#### SQL Server Configuration Members
| Member | Purpose |
Expand Down Expand Up @@ -111,7 +108,7 @@ let activeDirectoryAdmin: ActiveDirectoryAdminSettings =
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))
Expand Down
17 changes: 8 additions & 9 deletions samples/SampleApp/SampleApp.fsproj
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Update="FSharp.Core" Version="5.0.0" />
<ProjectReference Include="..\..\src\Farmer\Farmer.fsproj"/>
<Compile Include="Program.fs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Farmer\Farmer.fsproj"/>
<Compile Include="Program.fs" />
</ItemGroup>

</Project>
140 changes: 48 additions & 92 deletions src/Farmer/Arm/Sql.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
module Farmer.Arm.Sql

open Farmer
open Farmer.Arm
open Farmer.Sql
open System.Net

Expand All @@ -23,109 +24,36 @@ type DbKind =
| Standalone of DbPurchaseModel
| Pool of ResourceName

type ActiveDirectoryPrincipalType =
| User
| Group

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

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 SqlAuthentication = {
Username: string
Password: SecureParameter
}

type SqlServerJsonProperties = {
version: string
minimalTlsVersion: string
administratorLogin: string
administratorLoginPassword: string
administrators: SqlServerADAdminJsonProperties
}
type SqlCredentials =
| SqlOnly of SqlAuthentication
| EntraOnly of EntraAuthentication
| SqlAndEntra of SqlAuthentication * EntraAuthentication

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

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 =
match this.ActiveDirectoryAdmin with
| Some(x) when x.AdOnlyAuth -> []
| _ -> [ this.Credentials.Password ]
match this.Credentials with
| EntraOnly _ -> []
| SqlOnly creds
| SqlAndEntra(creds, _) -> [ creds.Password ]

interface IArmResource with
member this.ResourceId = servers.resourceId this.ServerName.ResourceName
Expand All @@ -137,10 +65,38 @@ type Server = {
tags = (this.Tags |> Map.add "displayName" this.ServerName.ResourceName.Value)
) with
properties =
match this.ActiveDirectoryAdmin with
| MixedModeAuth x -> this.BuildSqlServerPropertiesWithMixedModeAdministrator(x)
| AdOnlyAuth x -> this.BuildSqlServerPropertiesWithADOnlyAdministrator(x)
| SqlOnlyAuth -> this.BuildSqlServerPropertiesWithSqlOnlyAdministrator()
Map [
"version", box "12.0"
match this.MinTlsVersion with
| Some tlsVersion -> "minimalTlsVersion", tlsVersion.ArmValue
| None -> ()
yield!
match this.Credentials with
| EntraOnly _ -> []
| SqlOnly sqlCredentials
| SqlAndEntra(sqlCredentials, _) -> [
"administratorLogin", box sqlCredentials.Username
"administratorLoginPassword", sqlCredentials.Password.ArmExpression.Eval()
]
yield!
match this.Credentials with
| SqlOnly _ -> []
| SqlAndEntra(_, entraCredentials)
| EntraOnly entraCredentials -> [
"administrators",
box {|
administratorType = "ActiveDirectory"
principalType = entraCredentials.PrincipalType.ArmValue
login = entraCredentials.Login
sid = entraCredentials.Sid.Value
azureADOnlyAuthentication =
match this.Credentials with
| EntraOnly _ -> true
| SqlAndEntra _
| SqlOnly _ -> false
|}
]
]
|}

module Servers =
Expand Down
Loading

0 comments on commit 606c117

Please sign in to comment.