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

Merge mainline into dev branch #171

Merged
merged 9 commits into from
Jan 24, 2025
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
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
# Credentials Fetcher

NOTE: This branch is un-released, additional tests are not complete.
--------------------------------------------------------------------

`credentials-fetcher` is a Linux daemon that retrieves gMSA credentials from Active Directory over LDAP. It creates and refreshes kerberos tickets from gMSA credentials. Kerberos tickets can be used by containers to run apps/services that authenticate using Active Directory.

This daemon works in a similar way as ccg.exe and the gMSA plugin in Windows as described in - https://docs.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/manage-serviceaccounts#gmsa-architecture-and-improvements
Expand All @@ -26,7 +23,7 @@ https://docs.aws.amazon.com/AmazonECS/latest/developerguide/linux-gmsa.html#linu
dnf install -y samba-common-tools

# install custom credentials-fetcher rpm from branch - https://github.com/aws/credentials-fetcher/tree/fixes_for_DNS_and_distinguishedName gMSA credentials management for containers
curl -L -O https://github.com/aws/credentials-fetcher/raw/refs/heads/fixes_for_DNS_and_distinguishedName/rpm/credentials-fetcher-<major>.<minor>.<patch>-0.amzn2023.x86_64.rpm
curl -L -O https://github.com/aws/credentials-fetcher/raw/refs/heads/mainline/rpm/credentials-fetcher-<major>.<minor>.<patch>-0.amzn2023.x86_64.rpm
dnf install -y ./credentials-fetcher-<major>.<minor>.<patch>-0.amzn2023.x86_64.rpm

# start credentials-fetcher
Expand Down
2 changes: 1 addition & 1 deletion cdk/cdk-domainless-mode/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@
"ecr_repo_name": "my-ecr-repo",
"docker_image_tag": "latest",
"dockerfile_path": "./Dockerfile",
"rpm_file": "credentials-fetcher-1.3.65-0.amzn2023.x86_64.rpm",
"rpm_file": "xxxxxxxx",
"max_tasks_per_instance": 3
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def modify_task_definition(task_definition, ecs_cluster_arn, bucket_arn, s3_key)
task_definition["compatibilities"].append("FARGATE")

for container_def in task_definition['containerDefinitions']:
container_def['credentialSpecs']=[]
credspec = container_def['credentialSpecs']
credspec = [d for d in credspec if 'credentialspecdomainless' not in d]
credspec.append(f"credentialspecdomainless:{bucket_arn}/{s3_key}")
Expand Down
47 changes: 23 additions & 24 deletions cdk/cdk-domainless-mode/tests/create_secrets.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,28 @@ def create_secrets():
client = boto3.client('secretsmanager')

# Base path for the secrets
base_path = "aws/directoryservice/contoso/gmsa"
secret_name = "aws/directoryservice/contoso/gmsa"

for i in range(1, number_of_gmsa_accounts + 1):
# Create the secret name
secret_name = f"{base_path}/WebApp0{i}"
# Create the secret value
secret_value = {
"username": username,
"password": password,
"domainName": directory_name,
# "distinguishedName": f"CN=WebApp0{i},OU=MYOU,OU=Users,OU={netbios_name},DC={netbios_name},DC=com"
}

# Create the secret value
secret_value = {
"username": username,
"password": password,
"domainName": directory_name,
"distinguishedName": f"CN=WebApp0{i},OU=MYOU,OU=Users,OU={netbios_name},DC={netbios_name},DC=com"
}

try:
# Create the secret
response = client.create_secret(
Name=secret_name,
Description=f"Secret for WebApp0{i}",
SecretString=json.dumps(secret_value)
)
print(f"Created secret: {secret_name}")
except client.exceptions.ResourceExistsException:
print(f"Secret already exists: {secret_name}")
except Exception as e:
print(f"Error creating secret {secret_name}: {str(e)}")
try:
# Create the secret
response = client.create_secret(
Name=secret_name,
Description=f"Secret for WebApp01",
SecretString=json.dumps(secret_value)
)
print(f"Created secret: {secret_name}")

except client.exceptions.ResourceExistsException:
print(f"Secret already exists: {secret_name}")
except Exception as e:
print(f"Error creating secret {secret_name}: {str(e)}")
return False
return True
24 changes: 0 additions & 24 deletions cdk/cdk-domainless-mode/tests/delete_secrets.py

This file was deleted.

206 changes: 103 additions & 103 deletions cdk/cdk-domainless-mode/tests/gmsa.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,116 +7,109 @@
# 5) Add members to the security group that is allowed to retrieve gMSA password
# 6) Create gMSA accounts with PrincipalsAllowedToRetrievePassword set to the security group created in 4)

# 1) Install SSM agent
function Test-SSMAgentUpdate {
$ssm = Get-Service -Name "AmazonSSMAgent" -ErrorAction SilentlyContinue
if (-not $ssm) { return $false }
# Add additional version checking logic if needed
return $true
# Create a temporary directory for downloads
$tempDir = "C:\temp"
if (-not (Test-Path $tempDir)) {
New-Item -ItemType Directory -Path $tempDir
}

# 1) Install SSM agent
Write-Output "Updating SSM agent..."
[System.Net.ServicePointManager]::SecurityProtocol = 'TLS12'
$progressPreference = 'silentlyContinue'
Invoke-WebRequest https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/windows_amd64/AmazonSSMAgentSetup.exe -OutFile $env:USERPROFILE\Desktop\SSMAgent_latest.exe
Start-Process -FilePath $env:USERPROFILE\Desktop\SSMAgent_latest.exe -ArgumentList "/S"

# To install the AD module on Windows Server, run Install-WindowsFeature RSAT-AD-PowerShell
# To install the AD module on Windows 10 version 1809 or later, run Add-WindowsCapability -Online -Name 'Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0'
# To install the AD module on older versions of Windows 10, see https://aka.ms/rsat
try {
# 1) Check and Update SSM agent if needed
if (-not (Test-SSMAgentUpdate)) {
Write-Output "Updating SSM agent..."
[System.Net.ServicePointManager]::SecurityProtocol = 'TLS12'
$progressPreference = 'silentlyContinue'
Invoke-WebRequest https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/windows_amd64/AmazonSSMAgentSetup.exe -OutFile $env:USERPROFILE\Desktop\SSMAgent_latest.exe
Start-Process -FilePath $env:USERPROFILE\Desktop\SSMAgent_latest.exe -ArgumentList "/S"
}
Write-Output "Installing Active Directory management tools..."
Install-WindowsFeature -Name "RSAT-AD-Tools" -IncludeAllSubFeature
Install-WindowsFeature RSAT-AD-PowerShell
Install-Module CredentialSpec
Install-Module -Name SqlServer -AllowClobber -Force

# Check if AD tools are installed
if (-not (Get-WindowsFeature -Name "RSAT-AD-Tools").Installed) {
Write-Output "Installing Active Directory management tools..."
Install-WindowsFeature -Name "RSAT-AD-Tools" -IncludeAllSubFeature
Install-WindowsFeature RSAT-AD-PowerShell
Install-Module CredentialSpec -Force
Install-Module -Name SqlServer -AllowClobber -Force
}
$username = "admin@DOMAINNAME"
$password = "INPUTPASSWORD" | ConvertTo-SecureString -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($username, $password)
$groupAllowedToRetrievePassword = "WebAppAccounts_OU"
# This is the basedn path that needs to be in secrets manager as "distinguishedName" : "OU=MYOU,OU=Users,OU=ActiveDirectory,DC=contoso,DC=com"
$path = "OU=MYOU,OU=Users,OU=contoso,DC=NETBIOS_NAME,DC=com"
$supath = "OU=Users,OU=contoso,DC=contoso,DC=com"

$username = "admin@DOMAINNAME"
$password = "INPUTPASSWORD" | ConvertTo-SecureString -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($username, $password)
$groupAllowedToRetrievePassword = "WebAppAccounts_OU"
# This is the basedn path that needs to be in secrets manager as "distinguishedName" : "OU=MYOU,OU=Users,OU=ActiveDirectory,DC=contoso,DC=com"
$path = "OU=MYOU,OU=Users,OU=contoso,DC=NETBIOS_NAME,DC=com"

# 2) Create OU if it doesn't exist
if (-not (Get-ADOrganizationalUnit -Filter "Name -eq 'MYOU'" -ErrorAction SilentlyContinue)) {
New-ADOrganizationalUnit -Name "MYOU" -Path "OU=Users,OU=contoso,DC=NETBIOS_NAME,DC=com" -Credential $credential
}
# 2) Create OU
New-ADOrganizationalUnit -Name "MYOU" -Path "OU=Users,OU=contoso,DC=NETBIOS_NAME,DC=com" -Credential $credential

# 3) Create security group if it doesn't exist
if (-not (Get-ADGroup -Filter "SamAccountName -eq '$groupAllowedToRetrievePassword'" -ErrorAction SilentlyContinue)) {
New-ADGroup -Name "WebApp Authorized Accounts in OU" -SamAccountName $groupAllowedToRetrievePassword -Credential $credential -GroupScope DomainLocal -Server DOMAINNAME
}

# 4) Create standard user if it doesn't exist
if (-not (Get-ADUser -Filter "SamAccountName -eq 'StandardUser01'" -ErrorAction SilentlyContinue)) {
New-ADUser -Name "StandardUser01" -AccountPassword (ConvertTo-SecureString -AsPlainText "********" -Force) -Enabled 1 -Credential $credential -Path $path -Server DOMAINNAME
}

# 5) Add members to security group if not already members
$group = Get-ADGroup $groupAllowedToRetrievePassword
$members = Get-ADGroupMember $group | Select-Object -ExpandProperty SamAccountName
# 3) Create the security group
try {
New-ADGroup -Name "WebApp Authorized Accounts in OU" -SamAccountName $groupAllowedToRetrievePassword -Credential $credential -GroupScope DomainLocal -Server DOMAINNAME
} catch {
Write-Output "Security Group created"
}

foreach ($member in @("StandardUser01", "admin")) {
if ($member -notin $members) {
Add-ADGroupMember -Identity $groupAllowedToRetrievePassword -Members $member -Credential $credential -Server DOMAINNAME
}
}
# 4) Create a new standard user account, this account's username and password needs to be stored in a secret store like AWS secrets manager.
try {
New-ADUser -Name "StandardUser01" -AccountPassword (ConvertTo-SecureString -AsPlainText "p@ssw0rd" -Force) -Enabled 1 -Credential $credential -Path $supath -Server DOMAINNAME
} catch {
Write-Output "Created StandardUser01"
}

# 6) Create gMSA accounts if they don't exist
for (($i = 1); $i -le $NUMBER_OF_GMSA_ACCOUNTS; $i++) {
$gmsa_account_name = "WebApp0" + $i
$gmsa_account_with_domain = $gmsa_account_name + ".DOMAINNAME"
$gmsa_account_with_host = "host/" + $gmsa_account_name
$gmsa_account_with_host_and_domain = $gmsa_account_with_host + ".DOMAINNAME"
# 5) Add members to the security group that is allowed to retrieve gMSA password
try {
Add-ADGroupMember -Identity $groupAllowedToRetrievePassword -Members "StandardUser01" -Credential $credential -Server DOMAINNAME
Add-ADGroupMember -Identity $groupAllowedToRetrievePassword -Members "admin" -Credential $credential -Server DOMAINNAME
} catch {
Write-Output "Created AD Group $groupAllowedToRetrievePassword"
}

if (-not (Get-ADServiceAccount -Filter "Name -eq '$gmsa_account_name'" -ErrorAction SilentlyContinue)) {
# 6) Create gMSA accounts with PrincipalsAllowedToRetrievePassword set to the security group created in 4)
$string_err = ""
for (($i = 1); $i -le NUMBER_OF_GMSA_ACCOUNTS; $i++)
{
# Create the gMSA account
$gmsa_account_name = "WebApp0" + $i
$gmsa_account_with_domain = $gmsa_account_name + ".DOMAINNAME"
$gmsa_account_with_host = "host/" + $gmsa_account_name
$gmsa_account_with_host_and_domain = $gmsa_account_with_host + ".DOMAINNAME"

try {
# Check if the service account already exists
if (-not (Get-ADServiceAccount -Filter {Name -eq $gmsa_account_name} -ErrorAction SilentlyContinue)) {
New-ADServiceAccount -Name $gmsa_account_name `
-DnsHostName $gmsa_account_with_domain `
-ServicePrincipalNames $gmsa_account_with_host, $gmsa_account_with_host_and_domain `
-PrincipalsAllowedToRetrieveManagedPassword $groupAllowedToRetrievePassword `
-Path $path `
-Credential $credential `
-Server DOMAINNAME
-DnsHostName $gmsa_account_with_domain `
-ServicePrincipalNames $gmsa_account_with_host, $gmsa_account_with_host_and_domain `
-PrincipalsAllowedToRetrieveManagedPassword $groupAllowedToRetrievePassword `
-Path $path `
-Credential $credential `
-Server DOMAINNAME
Write-Output "Created new gMSA account: $gmsa_account_name"
} else {
Write-Output "gMSA account $gmsa_account_name already exists - skipping creation"
}
} catch {
$string_err = $_ | Out-String
Write-Output "Error while processing gMSA account $gmsa_account_name : $string_err"
}
}

# SQL Server Configuration
$sqlInstance = $env:computername

# Create firewall rules if they don't exist
$firewallRules = Get-NetFirewallRule | Select-Object -ExpandProperty DisplayName
# Set the SQL Server instance name
$sqlInstance = $env:computername

if ("SQLServer default instance" -notin $firewallRules) {
New-NetFirewallRule -DisplayName "SQLServer default instance" -Direction Inbound -LocalPort 1433 -Protocol TCP -Action Allow
}
if ("SQLServer Browser service" -notin $firewallRules) {
New-NetFirewallRule -DisplayName "SQLServer Browser service" -Direction Inbound -LocalPort 1434 -Protocol UDP -Action Allow
}
if ("AllowRDP" -notin $firewallRules) {
New-NetFirewallRule -DisplayName "AllowRDP" -Direction Inbound -Protocol TCP -LocalPort 3389 -Action Allow
}
if ("AllowSQLServer" -notin $firewallRules) {
New-NetFirewallRule -DisplayName "AllowSQLServer" -Direction Inbound -Protocol TCP -LocalPort 1433 -Action Allow
}
New-NetFirewallRule -DisplayName "SQLServer default instance" -Direction Inbound -LocalPort 1433 -Protocol TCP -Action Allow
New-NetFirewallRule -DisplayName "SQLServer Browser service" -Direction Inbound -LocalPort 1434 -Protocol UDP -Action Allow
netsh advfirewall firewall add rule name = SQLPort dir = in protocol = tcp action = allow localport = 1433 remoteip = localsubnet profile = DOMAIN
New-NetFirewallRule -DisplayName “AllowRDP” -Direction Inbound -Protocol TCP –LocalPort 3389 -Action Allow
New-NetFirewallRule -DisplayName "AllowSQLServer" -Direction Inbound -Protocol TCP -LocalPort 1433 -Action Allow

# SQL Database creation and configuration
$connectionString0 = "Server=$sqlInstance;Integrated Security=True;"
$connectionString1 = "Server=$sqlInstance;Database=EmployeesDB;Integrated Security=True;"

# Check if database exists
$dbExists = Invoke-Sqlcmd -ConnectionString $connectionString0 -Query "SELECT name FROM sys.databases WHERE name = 'EmployeesDB'"
# Create a connection string
$connectionString0 = "Server=$sqlInstance;Integrated Security=True;"
$connectionString1 = "Server=$sqlInstance;Database=EmployeesDB;Integrated Security=True;"

if (-not $dbExists) {
Invoke-Sqlcmd -ConnectionString $connectionString0 -Query "CREATE DATABASE EmployeesDB"
$createDatabaseQuery = "CREATE DATABASE EmployeesDB"

$query = @"
$query = @"
CREATE TABLE dbo.EmployeesTable (
EmpID INT IDENTITY(1,1) PRIMARY KEY,
EmpName VARCHAR(50) NOT NULL,
Expand All @@ -133,21 +126,28 @@ VALUES
('DEWANE PAUL', 'PROGRAMMER', 'IT', '2022-03-05 03:57:09.967'),
('MATTS', 'SR. PROGRAMMER', 'IT', '2022-03-05 03:57:09.967'),
('PLANK OTO', 'ACCOUNTANT', 'ACCOUNTS', '2022-03-05 03:57:09.967');
alter authorization on database::[EmployeesDB] to [WebApp01$]
"@

Invoke-Sqlcmd -ConnectionString $connectionString1 -Query $query
}

# Check if login exists before creating
$loginExists = Invoke-Sqlcmd -ConnectionString $connectionString0 -Query "SELECT name FROM sys.server_principals WHERE name = 'NETBIOS_NAME\webapp01$'"
Invoke-Sqlcmd -ConnectionString $connectionString0 -Query $createDatabaseQuery -QueryTimeout 60
Invoke-Sqlcmd -ConnectionString $connectionString1 -Query $query

# Sleep for 10 seconds
Start-Sleep -Seconds 10

# Loop through WebApp01$ to WebApp010$
for ($i = 1; $i -le NUMBER_OF_GMSA_ACCOUNTS; $i++) {
$webAppName = "WebApp0$i`$"

$createLoginQuery = @"
CREATE LOGIN [NETBIOS_NAME\$webAppName] FROM WINDOWS WITH DEFAULT_DATABASE = [master], DEFAULT_LANGUAGE = [us_english];
USE [EmployeesDB];
CREATE USER [$webAppName] FOR LOGIN [NETBIOS_NAME\$webAppName];
ALTER ROLE [db_owner] ADD MEMBER [$webAppName];
ALTER AUTHORIZATION ON DATABASE::[EmployeesDB] TO [$webAppName];
"@

if (-not $loginExists) {
$createLoginQuery = "CREATE LOGIN [NETBIOS_NAME\webapp01$] FROM WINDOWS WITH DEFAULT_DATABASE = [master], DEFAULT_LANGUAGE = [us_english]; EXEC sp_addrolemember 'db_owner', 'NETBIOS_NAME\webapp01$';"
Invoke-Sqlcmd -ConnectionString $connectionString0 -Query $createLoginQuery
}
Write-Host "Creating login and granting permissions for $webAppName"
Invoke-Sqlcmd -ConnectionString $connectionString0 -Query $createLoginQuery
}

} catch {
Write-Error "An error occurred: $_"
throw
}

1 change: 1 addition & 0 deletions cdk/cdk-domainless-mode/tests/parse_data_from_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def get_value(key):
password = data["password"]
windows_instance_tag = data["windows_instance_tag"]
domain_admin_password = data["domain_admin_password"]
containers_per_instance = data["max_tasks_per_instance"] * 10 # maximum number of container definitions per task is 10 (ref: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/service-quotas.html)

if "XXX" in bucket_name:
print("S3_PREFIX is not setup correctly, please set it and retry")
Expand Down
Loading
Loading