Tiếp tục từ Send-MailMessage: Gửi email từ PowerShell bằng API đồ thị
Ngừng xác thực cơ bản
Như đã thảo luận trước đó, Microsoft đã lớn tiếng tuyên bố cần phải ngừng sử dụng xác thực cơ bản và bắt đầu sử dụng xác thực hiện đại. Đây là một vấn đề lớn vì tin tặc tiếp tục xâm phạm tài khoản và người thuê Office 365 bằng các kỹ thuật như tấn công lần dò mật khẩu. Chuyển sang xác thực hiện đại và bảo vệ nhiều tài khoản nhất có thể bằng xác thực đa yếu tố (đặc biệt là tài khoản quản trị viên theo quy định của Mặc định bảo mật Azure AD) là những bước đi đúng đắn cần thực hiện.
Cập nhật (ngày 25 tháng 9 năm 2021): Kế hoạch mới nhất của Microsoft nhằm ngừng hoạt động xác thực cơ bản cho các giao thức kết nối email không bao gồm SMTP AUTH. Hiện tại, Microsoft đang tập trung loại bỏ xác thực cơ bản cho các giao thức như IMAP4 và POP3, nhưng họ sẽ quay lại loại bỏ xác thực cơ bản cho SMTP AUTH. Do đó, điều khôn ngoan là bạn nên cân nhắc cách thay thế giao thức này trong sản xuất khi thời điểm đó đến.
Cập nhật (ngày 15 tháng 11 năm 2022): Có ba cách để gửi email bằng Biểu đồ. Sử dụng API được mô tả ở đây là một. Đọc bài viết này để biết chi tiết đầy đủ.
Câu hỏi hóc búa về việc gửi tin nhắn
Phần lớn là do lịch sử, Exchange Online hỗ trợ nhiều giao thức kết nối khác nhau. Microsoft đang đạt được một số tiến bộ trong việc thuyết phục khách hàng vô hiệu hóa xác thực cơ bản cho các giao thức mà họ không bao giờ sử dụng, đồng thời đã nâng cấp các giao thức cũ hơn như POP3 và IMAP4 để sử dụng OAuth 2.0 cho xác thực hiện đại. Như đã thảo luận trong blog này, người thuê sẽ cần tìm các tập lệnh PowerShell gọi lệnh ghép ngắn Send-MailMessage và cuối cùng nâng cấp mã bằng phương thức hiện đại hơn để gửi email.
Lệnh ghép ngắn Send-MailMessage phụ thuộc vào giao thức SMTP AUTH để gửi email bằng xác thực cơ bản. Microsoft đã công bố hỗ trợ OAuth 2.0 cho SMTP AUTH vào tháng 4 năm 2020, nhưng điều này không có nghĩa là có sẵn lệnh ghép ngắn thay thế sẵn có. Microsoft cho biết thông báo “dành cho các ứng dụng tương tác kích hoạt OAuth cho IMAP và SMTP [AUTH].” Trên thực tế, điều này có nghĩa là ứng dụng thư khách hoặc các ứng dụng khác gửi, đọc hoặc xử lý email. Việc truy cập nhanh đến trang được tham chiếu sẽ không còn nghi ngờ gì nữa rằng điều này có ý nghĩa nhiều hơn việc thay thế một vài dòng mã trong tập lệnh PowerShell.
Nếu không có sự thay thế rõ ràng nào cho Send-MailMessage, bạn nên làm gì nếu muốn loại bỏ xác thực cơ bản nhưng có nhiều tập lệnh sử dụng lệnh ghép ngắn để gửi email? Đó là một câu hỏi hóc búa.
Nhập biểu đồ
Ghi chú của Microsoft về SMTP AUTH hướng mọi người đến Microsoft Graph API như một phương pháp thay thế để gửi email. Lệnh gọi SendMail là một phần của API Graph Outlook. Giống như tất cả lệnh gọi API Đồ thị, nó có thể được gọi thông qua PowerShell.
SendMail có thể được sử dụng theo hai cách, tùy thuộc vào quyền của ứng dụng gọi nó:
- Nếu ứng dụng có quyền ứng dụng Mail.Send thì ứng dụng có thể gửi email với tư cách bất kỳ người dùng hoặc hộp thư chung nào (nhưng không phải hộp thư nhóm) trong tổ chức. Quản trị viên phải đồng ý cho ứng dụng sử dụng quyền của ứng dụng.
- Nếu ứng dụng có quyền được ủy quyền Mail.Send thì ứng dụng có thể gửi thư với tư cách là người dùng đã đăng nhập.
Do đó, việc sử dụng quyền của ứng dụng để gửi email là cực kỳ mạnh mẽ (hoặc nguy hiểm). Đồ thị bỏ qua Exchange Gửi dưới dạng hoặc Gửi thay mặt các quyền hiện có cho hộp thư và có thể mạo danh bất kỳ người dùng hoặc hộp thư chung nào để gửi email như thể thư có nguồn gốc từ hộp thư đó.
Chuyển đổi tập lệnh PowerShell từ Send-MailMessage
Để kiểm tra xem việc chuyển đổi tập lệnh từ “cổ điển: Mã Send-MailMessage sang sử dụng lệnh gọi SendMail dễ dàng như thế nào, tôi đã lấy một tập lệnh mẫu được viết để minh họa cách gửi tin nhắn chào mừng tới người dùng mới. Các bước cơ bản trong quá trình chuyển đổi là:
- Tạo một ứng dụng đã đăng ký trong Azure AD.
- Gán quyền Mail.Send Graph (ứng dụng hoặc được ủy quyền) cho ứng dụng.
- Lưu ý GUID cho mã định danh ứng dụng và mã định danh đối tượng thuê, đồng thời tạo bí mật ứng dụng (nếu sử dụng quyền ứng dụng).
- Trong tập lệnh, hãy thêm mã để tạo mã thông báo truy cập và thay thế mã để gọi tới Send-MailMessage bằng mã tương đương cho SendMail.
Đầu tiên, hãy xem xét việc sử dụng quyền của ứng dụng. Mã để tạo mã thông báo truy cập trông giống như mã được hiển thị bên dưới. Các giá trị được chuyển cho id ứng dụng ($AppId) và mã định danh đối tượng thuê ($TenantId) sẽ khác nhau trong tổ chức của bạn.
$AppId = "980e01d1-ce75-46ba-a054-4b61c787f682"
$AppSecret = "~_Q2.IPI3kpbmhOG.VtCb1r0_J9G4-D8jG"
$TenantId = "b562313f-14fc-43a2-9a7a-d2e27f4f3478"
# Construct URI and body needed for authentication
$uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
$body = @{
client_id = $AppId
scope = "https://graph.microsoft.com/.default"
client_secret = $AppSecret
grant_type = "client_credentials"
}
$tokenRequest = Invoke-WebRequest -Method Post -Uri $uri -ContentType "application/x-www-form-urlencoded" -Body $body -UseBasicParsing
# Unpack Access Token
$token = ($tokenRequest.Content | ConvertFrom-Json).access_token
$Headers = @{
'Content-Type' = "application\json"
'Authorization' = "Bearer $Token" }
Với mã thông báo truy cập hợp lệ được bảo mật, giờ đây tập lệnh có thể thực hiện lệnh gọi API Đồ thị bằng cách sử dụng các quyền được mã hóa trong mã thông báo. Tập lệnh tìm tập hợp tài khoản người dùng được tạo gần đây và lặp lại từng người dùng để soạn thư chào mừng tùy chỉnh. Phần lớn việc này cần phải được thực hiện để sử dụng Gửi-MailMessage, vì vậy phần thay đổi là hoán đổi việc tạo tham số để tạo và gửi thư có mã để thực hiện công việc với SendMail .
Các bước sau đây xảy ra trong quy trình:
- Điền biến $MsgFrom (người gửi thư) bằng địa chỉ email hợp lệ cho người gửi. Vì API SendMail (có quyền của ứng dụng) có thể gửi dưới dạng bất kỳ hộp thư (được cấp phép) hợp lệ nào nên bạn có thể sử dụng địa chỉ proxy SMTP cho bất kỳ người dùng hoặc hộp thư chung nào, bao gồm cả địa chỉ proxy phụ. Tất cả vấn đề là địa chỉ được sử dụng có thể phân giải được cho loại hộp thư được hỗ trợ. SendMail xác thực người gửi khi cố gắng gửi thư và trả về lỗi 404 nếu không tìm thấy địa chỉ người gửi trong thư mục đối tượng thuê.
- Lặp lại tập hợp các hộp thư được tạo gần đây để tạo nội dung HMTL tùy chỉnh cho thư. Sau đó, điền cấu trúc tham số có định dạng JSON xác định nội dung thư (Người nhận đến và CC cũng như chủ đề thư), tệp đính kèm cùng với các lệnh được sử dụng để gửi thư đến Exchange Online. Đảm bảo rằng bạn sử dụng địa chỉ SMTP chính của người nhận, có thể bao gồm nhóm phân phối, liên hệ thư, tài khoản khách và Nhóm Microsoft 365.
- Gọi lệnh ghép ngắn Invoke-RestMethod để xử lý các tham số và gửi tin nhắn.
$MsgFrom = “HRWelcomeMailbox@office365itpros.com”
$ccRecipient1 = “Kim.Akers@office365itpros.com”
$ccRecipient2 = “James.Ryan@Office365itpros.com”
# Define attachment to send to new users
$AttachmentFile = "C:\temp\WelcomeToOffice365ITPros.docx"
$ContentBase64 = [convert]::ToBase64String( [system.io.file]::readallbytes($AttachmentFile))
ForEach ($User in $Users) {
$EmailRecipient = $User.PrimarySmtpAddress
Write-Host "Sending welcome email to" $User.DisplayName
$MsgSubject = "A Hundred Thousand Welcomes to " + $User.DisplayName
$htmlHeaderUser = "<h2>New User " + $User.DisplayName + "</h2>"
$htmlline1 = "<p><b>Welcome to Office 365</b></p>"
$htmlline2 = "<p>You can open Office Online by clicking <a href=https://www.office.com/?auth=2>here</a> </p>"
$htmlline3 = "<p>Have a great time and be sure to call the help desk if you need assistance.</p>"
$htmlbody = $htmlheaderUser + $htmlline1 + $htmlline2 + $htmlline3 + "<p>"
$HtmlMsg = "</body></html>" + $HtmlHead + $HtmlBody
# Create message body and properties and send
$MessageParams = @{
"URI" = "https://graph.microsoft.com/v1.0/users/$MsgFrom/sendMail"
"Headers" = $Headers
"Method" = "POST"
"ContentType" = 'application/json'
"Body" = (@{
"message" = @{
"subject" = $MsgSubject
"body" = @{
"contentType" = 'HTML'
"content" = $htmlMsg }
"attachments" = @(
@{
"@odata.type" = "#microsoft.graph.fileAttachment"
"name" = $AttachmentFile
"contentType" = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
"contentBytes" = $ContentBase64 })
"toRecipients" = @(
@{
"emailAddress" = @{"address" = $EmailRecipient }
} )
"ccRecipients" = @(
@{
"emailAddress" = @{"address" = $ccRecipient1 }
} ,
@{
"emailAddress" = @{"address" = $ccRecipient2 }
} )
}
}) | ConvertTo-JSON -Depth 6
} # Send the message
Invoke-RestMethod @Messageparams
}
Bạn có thể tải xuống tập lệnh đầy đủ từ GitHub. Như bạn sẽ lưu ý, việc thêm tệp đính kèm vào thư không đơn giản như với Send-MailMessage. Hai điểm cần lưu ý là:
- Tải lên tệp đính kèm khi gửi tin nhắn hoạt động với tệp đính kèm có kích thước tối đa 3 MB. Nếu cần gửi các tệp đính kèm lớn hơn lên tới 150 MB, bạn cần tải chúng lên bằng cách khác (xem ghi chú của Microsoft). Tất nhiên, các yếu tố khác như hộp thư và giới hạn tổ chức sẽ ảnh hưởng đến kích thước tối đa của thư.
- Loại nội dung là loại MIME cho tệp đính kèm (hãy nhớ rằng chúng tôi đang sử dụng email, đó là lý do tại sao nội dung tệp đính kèm phải được mã hóa Base64). Tôi nhận thấy danh sách các loại MIME có sẵn ở đây rất hữu ích. Tôi chưa kiểm tra mọi loại tệp đính kèm có thể có nhưng tôi biết rằng khi tôi gặp sự cố, đôi khi loại nội dung “nhiều phần/hỗn hợp” hoạt động trong khi loại MIME chính thức thì không.
API Mail.Send có thể đặt tiêu đề x trong thư, đây là điều mà lệnh ghép ngắn Send-MailMessage không thể thực hiện được. Đây là một lý do tại sao mọi người sử dụng lớp .Net SmtpClient (hiện không được dùng nữa) để tạo và gửi email dưới dạng tập lệnh. Bạn nên chuyển đổi các tập lệnh này để sử dụng Biểu đồ thay thế.
Câu hỏi cấp phép lớn đó
Nếu bạn không lo lắng về việc có một ứng dụng có thể gửi email cho bất kỳ hộp thư nào trong tổ chức, bạn có thể dễ dàng sử dụng chính ứng dụng đó làm cơ sở để nâng cấp tất cả các tập lệnh. Mã để bảo mật mã thông báo truy cập vẫn giữ nguyên và có thể được dán vào từng tập lệnh. Nhiệm vụ còn lại sau đó là cập nhật mã Send-MailMessage để sử dụng SendMail.
Việc có một ứng dụng gửi thư toàn năng sẽ giúp các lập trình viên dễ dàng hơn (hoặc ít nhất, giúp việc chuyển đổi dễ dàng hơn) nhưng nhóm bảo mật công ty có thể không chấp nhận được trừ khi có đảm bảo rằng ứng dụng không thể bị lạm dụng. Cho rằng sự đảm bảo như vậy là không thể, bạn có thể biết vấn đề nằm ở đâu.
Sử dụng Chính sách truy cập ứng dụng để hạn chế hộp thư được ứng dụng sử dụng
Một giải pháp là sử dụng chính sách truy cập ứng dụng để cho phép ứng dụng chỉ truy cập một số hộp thư nhất định. Chính sách truy cập ứng dụng kết hợp ba yếu tố:
- Xác định từ chối hoặc cho phép truy cập.
- Bao gồm một hoặc nhiều ứng dụng được đăng ký trong Azure AD.
- Áp dụng cho các lệnh gọi API Đồ thị hoặc Dịch vụ Web Exchange được thực hiện tới hộp thư dành cho các thành viên của nhóm bảo mật hỗ trợ thư hoặc người dùng cá nhân (các đối tượng có nguyên tắc bảo mật).
Vì vậy, chúng ta có thể tạo chính sách truy cập ứng dụng để hạn chế quyền truy cập vào một bộ hộp thư. Ví dụ:
New-ApplicationAccessPolicy -AppId 970e01d1-ce75-46ba-a054-4b61c787f682 -PolicyScopeGroupId SendMailApp.Control@office365itpros.com -AccessRight RestrictAccess -Description "Restrict access to app allowed to send email using the Graph SendMail API"Chính sách này hạn chế quyền truy cập vào Ứng dụng có số nhận dạng 970e01d1-ce75-46ba-a054-4b61c787f682 đối với các hộp thư được xác định trong nhóm bảo mật SendMailApp.Control@office365itpros.com. Sau khi tạo chính sách, chỉ có thể sử dụng hộp thư dành cho các thành viên của nhóm bảo mật để gửi email bằng lệnh gọi API SendMail. Việc cố gắng gửi email bằng bất kỳ tài khoản nào khác sẽ tạo ra lỗi 403 khi gọi SendMail vì chính sách từ chối quyền truy cập vào hộp thư.
Exchange Online lưu trữ thành viên nhóm vào bộ nhớ đệm cho các chính sách truy cập để giúp việc kiểm tra hoạt động tốt hơn và bạn có thể phải đợi khoảng một giờ trước khi các thay đổi về tư cách thành viên có hiệu lực nhưng các hạn chế về chính sách hoạt động rất tốt.
Sử dụng quyền được ủy quyền
Một tùy chọn khác là sử dụng ứng dụng đã đăng ký Azure AD với quyền Mail.Send được ủy quyền. Điều này hạn chế khả năng gửi email đến tài khoản đã đăng nhập, do đó nó phù hợp hơn với cách hoạt động của Gửi-MailMessage. Mã để tạo và gửi tin nhắn vẫn giữ nguyên; điều duy nhất thay đổi là mã thông báo truy cập do Azure AD tạo khi ứng dụng xác thực sẽ hạn chế việc sử dụng mã thông báo này đối với người dùng đã đăng nhập. Trong hầu hết các trường hợp, sử dụng quyền được ủy quyền để gọi API SendMail là lựa chọn tốt hơn. Tất nhiên trừ khi bạn thực sự muốn có thể gửi tin nhắn với tư cách là mọi người trong đối tượng thuê.