Custom email device-mapping: implement the CLI (fleetd + fleetctl) changes (#15763)
Co-authored-by: Sarah Gillespie <73313222+gillespi314@users.noreply.github.com>
This commit is contained in:
co-authored by
Sarah Gillespie
parent
235d2cf2dc
commit
2e8da551d0
@@ -1 +1,2 @@
|
||||
* Added the `PUT /api/fleet/orbit/device_mapping` and `PUT /api/v1/fleet/hosts/{id}/device_mapping` endpoints (orbit-authenticated and user-authenticated) to set or replace the custom email address associated with a host.
|
||||
* Added the experimental `--end-user-email` flag to `fleetctl package` so that the email address associated with the host can be bundled in the `.msi` installer for Windows.
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
eefleetctl "github.com/fleetdm/fleet/v4/ee/fleetctl"
|
||||
"github.com/fleetdm/fleet/v4/orbit/pkg/packaging"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/rs/zerolog"
|
||||
zlog "github.com/rs/zerolog/log"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
@@ -227,6 +228,13 @@ func packageCommand() *cli.Command {
|
||||
EnvVars: []string{"FLEETCTL_HOST_IDENTIFIER"},
|
||||
Destination: &opt.HostIdentifier,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "end-user-email",
|
||||
Hidden: true, // experimental feature, we don't want to show it for now
|
||||
Usage: "Sets the email address of the user associated with the host when enrolling to Fleet. (requires Fleet >= v4.43.0)",
|
||||
EnvVars: []string{"FLEETCTL_END_USER_EMAIL"},
|
||||
Destination: &opt.EndUserEmail,
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
if opt.FleetURL != "" || opt.EnrollSecret != "" {
|
||||
@@ -280,6 +288,15 @@ func packageCommand() *cli.Command {
|
||||
Visit https://wixtoolset.org/ for more information about how to use WiX.`)
|
||||
}
|
||||
|
||||
if opt.EndUserEmail != "" && c.String("type") != "msi" {
|
||||
return errors.New("Can only set --end-user-email when building an MSI package.")
|
||||
}
|
||||
if opt.EndUserEmail != "" {
|
||||
if !fleet.IsLooseEmail(opt.EndUserEmail) {
|
||||
return errors.New("Invalid email address specified for --end-user-email.")
|
||||
}
|
||||
}
|
||||
|
||||
if opt.FleetCertificate != "" {
|
||||
err := checkPEMCertificate(opt.FleetCertificate)
|
||||
if err != nil {
|
||||
|
||||
@@ -183,6 +183,12 @@ func main() {
|
||||
EnvVars: []string{"ORBIT_HOST_IDENTIFIER"},
|
||||
Value: "uuid",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "end-user-email",
|
||||
Hidden: true, // experimental feature, we don't want to show it for now
|
||||
Usage: "Sets the email address of the user associated with the host when enrolling to Fleet. (requires Fleet >= v4.43.0)",
|
||||
EnvVars: []string{"ORBIT_END_USER_EMAIL"},
|
||||
},
|
||||
}
|
||||
app.Before = func(c *cli.Context) error {
|
||||
// handle old installations, which had default root dir set to /var/lib/orbit
|
||||
@@ -268,6 +274,10 @@ func main() {
|
||||
return fmt.Errorf("--host-identifier=%s is not supported, currently supported values are 'uuid' and 'instance'", hostIdentifier)
|
||||
}
|
||||
|
||||
if email := c.String("end-user-email"); email != "" && !fleet.IsLooseEmail(email) {
|
||||
return fmt.Errorf("the provided end-user email address %q is not a valid email address", email)
|
||||
}
|
||||
|
||||
if err := secure.MkdirAll(c.String("root-dir"), constant.DefaultDirMode); err != nil {
|
||||
return fmt.Errorf("initialize root dir: %w", err)
|
||||
}
|
||||
@@ -705,6 +715,8 @@ func main() {
|
||||
RootDir: c.String("root-dir"),
|
||||
})
|
||||
// Try performing a flags update to use latest configured osquery flags from get-go.
|
||||
// This also takes care of populating the server's capabilities as it calls the orbit
|
||||
// config endpoint.
|
||||
if _, err := flagRunner.DoFlagsUpdate(); err != nil {
|
||||
// Just log, OK to continue, since flagRunner will retry
|
||||
// in flagRunner.Execute.
|
||||
@@ -957,6 +969,19 @@ func main() {
|
||||
g.Add(desktopRunner.actor())
|
||||
}
|
||||
|
||||
// --end-user-email is only supported on Windows (for macOS it gets the
|
||||
// email from the enrollment profile)
|
||||
if runtime.GOOS == "windows" && c.String("end-user-email") != "" {
|
||||
if orbitClient.GetServerCapabilities().Has(fleet.CapabilityEndUserEmail) {
|
||||
log.Debug().Msg("sending end-user email to Fleet")
|
||||
if err := orbitClient.SetOrUpdateDeviceMappingEmail(c.String("end-user-email")); err != nil {
|
||||
log.Error().Err(err).Msg("error sending end-user email to Fleet")
|
||||
}
|
||||
} else {
|
||||
log.Info().Msg("an end-user email is provided, but the Fleet server doesn't have the capability to set it.")
|
||||
}
|
||||
}
|
||||
|
||||
// Install a signal handler
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
@@ -1300,6 +1325,11 @@ func (f *capabilitiesChecker) execute() error {
|
||||
log.Info().Msgf("%s capability changed, restarting", fleet.CapabilityTokenRotation)
|
||||
return nil
|
||||
}
|
||||
if oldCapabilities.Has(fleet.CapabilityEndUserEmail) !=
|
||||
newCapabilities.Has(fleet.CapabilityEndUserEmail) {
|
||||
log.Info().Msgf("%s capability changed, restarting", fleet.CapabilityEndUserEmail)
|
||||
return nil
|
||||
}
|
||||
case <-f.interruptCh:
|
||||
return nil
|
||||
|
||||
|
||||
@@ -117,6 +117,9 @@ type Options struct {
|
||||
LocalWixDir string
|
||||
// HostIdentifier is the host identifier to use in osquery.
|
||||
HostIdentifier string
|
||||
// EndUserEmail is the email address of the end user that uses the host on
|
||||
// which the agent is going to be installed.
|
||||
EndUserEmail string
|
||||
}
|
||||
|
||||
func initializeTempDir() (string, error) {
|
||||
|
||||
@@ -99,7 +99,7 @@ var windowsWixTemplate = template.Must(template.New("").Option("missingkey=error
|
||||
Start="auto"
|
||||
Type="ownProcess"
|
||||
Description="This service runs Fleet's osquery runtime and autoupdater (Orbit)."
|
||||
Arguments='--root-dir "[ORBITROOT]." --log-file "[System64Folder]config\systemprofile\AppData\Local\FleetDM\Orbit\Logs\orbit-osquery.log" --fleet-url "[FLEET_URL]"{{ if .FleetCertificate }} --fleet-certificate "[ORBITROOT]fleet.pem"{{ end }}{{ if .EnrollSecret }} --enroll-secret-path "[ORBITROOT]secret.txt"{{ end }}{{if .Insecure }} --insecure{{ end }}{{ if .Debug }} --debug{{ end }}{{ if .UpdateURL }} --update-url "{{ .UpdateURL }}"{{ end }}{{ if .UpdateTLSServerCertificate }} --update-tls-certificate "[ORBITROOT]update.pem"{{ end }}{{ if .DisableUpdates }} --disable-updates{{ end }}{{ if .Desktop }} --fleet-desktop --desktop-channel {{ .DesktopChannel }}{{ if .FleetDesktopAlternativeBrowserHost }} --fleet-desktop-alternative-browser-host {{ .FleetDesktopAlternativeBrowserHost }}{{ end }}{{ end }} --orbit-channel "{{ .OrbitChannel }}" --osqueryd-channel "{{ .OsquerydChannel }}" {{ if .EnableScripts }} --enable-scripts{{ end }}{{ if and (ne .HostIdentifier "") (ne .HostIdentifier "uuid") }}--host-identifier={{ .HostIdentifier }}{{ end }}'
|
||||
Arguments='--root-dir "[ORBITROOT]." --log-file "[System64Folder]config\systemprofile\AppData\Local\FleetDM\Orbit\Logs\orbit-osquery.log" --fleet-url "[FLEET_URL]"{{ if .FleetCertificate }} --fleet-certificate "[ORBITROOT]fleet.pem"{{ end }}{{ if .EnrollSecret }} --enroll-secret-path "[ORBITROOT]secret.txt"{{ end }}{{if .Insecure }} --insecure{{ end }}{{ if .Debug }} --debug{{ end }}{{ if .UpdateURL }} --update-url "{{ .UpdateURL }}"{{ end }}{{ if .UpdateTLSServerCertificate }} --update-tls-certificate "[ORBITROOT]update.pem"{{ end }}{{ if .DisableUpdates }} --disable-updates{{ end }}{{ if .Desktop }} --fleet-desktop --desktop-channel {{ .DesktopChannel }}{{ if .FleetDesktopAlternativeBrowserHost }} --fleet-desktop-alternative-browser-host {{ .FleetDesktopAlternativeBrowserHost }}{{ end }}{{ end }} --orbit-channel "{{ .OrbitChannel }}" --osqueryd-channel "{{ .OsquerydChannel }}" {{ if .EnableScripts }} --enable-scripts{{ end }}{{ if and (ne .HostIdentifier "") (ne .HostIdentifier "uuid") }}--host-identifier={{ .HostIdentifier }}{{ end }}{{ if .EndUserEmail }} --end-user-email "{{ .EndUserEmail }}"{{ end }}'
|
||||
>
|
||||
<util:ServiceConfig
|
||||
FirstFailureActionType="restart"
|
||||
@@ -157,7 +157,7 @@ var windowsWixTemplate = template.Must(template.New("").Option("missingkey=error
|
||||
DllEntry="WixQuietExec64"
|
||||
Execute="deferred"
|
||||
Return="check"
|
||||
Impersonate="no" />
|
||||
Impersonate="no" />
|
||||
|
||||
<SetProperty Id="CA_WaitOrbit"
|
||||
Before ="CA_WaitOrbit"
|
||||
@@ -265,7 +265,7 @@ var windowsPSInstallerUtils = template.Must(template.New("").Option("missingkey=
|
||||
[switch] $uninstallOsquery = $false,
|
||||
[switch] $uninstallOrbit = $false,
|
||||
[switch] $stopOrbit = $false,
|
||||
[string] $updateSecret = "",
|
||||
[string] $updateSecret = "",
|
||||
[switch] $help = $false
|
||||
)
|
||||
|
||||
@@ -464,8 +464,8 @@ public class RegistryUtils
|
||||
|
||||
Add-Type -TypeDefinition $code -Language CSharp
|
||||
|
||||
function Test-Administrator
|
||||
{
|
||||
function Test-Administrator
|
||||
{
|
||||
[OutputType([bool])]
|
||||
param()
|
||||
process {
|
||||
@@ -476,7 +476,7 @@ function Test-Administrator
|
||||
|
||||
function Do-Help {
|
||||
$programName = (Get-Item $PSCommandPath ).Name
|
||||
|
||||
|
||||
Write-Host "Usage: $programName (-uninstallOsquery|-uninstallOrbit|-stopOrbit|-updateSecret|-help)" -foregroundcolor Yellow
|
||||
Write-Host ""
|
||||
Write-Host " Only one of the following options can be used. Using multiple will result in "
|
||||
@@ -484,9 +484,9 @@ function Do-Help {
|
||||
Write-Host " -uninstallOsquery Uninstall Osquery"
|
||||
Write-Host " -uninstallOrbit Uninstall Orbit"
|
||||
Write-Host " -stopOrbit Stop Orbit"
|
||||
Write-Host " -updateSecret <secret> Update Orbit secret"
|
||||
Write-Host " -updateSecret <secret> Update Orbit secret"
|
||||
Write-Host " -help Shows this help screen"
|
||||
|
||||
|
||||
Exit 1
|
||||
}
|
||||
|
||||
@@ -510,10 +510,10 @@ function Resolve-Error-Detailed($ErrorRecord = $Error[0]) {
|
||||
function Stop-Osquery {
|
||||
|
||||
$kServiceName = "osqueryd"
|
||||
|
||||
|
||||
# Stop Service
|
||||
Stop-Service -Name $kServiceName -ErrorAction "Continue"
|
||||
Start-Sleep -Milliseconds 1000
|
||||
Stop-Service -Name $kServiceName -ErrorAction "Continue"
|
||||
Start-Sleep -Milliseconds 1000
|
||||
|
||||
# Ensure that no process left running
|
||||
Get-Process -Name $kServiceName -ErrorAction "SilentlyContinue" | Stop-Process -Force
|
||||
@@ -539,11 +539,11 @@ function Update-OrbitSecret {
|
||||
# Ensuring secret file is not empty
|
||||
if (-not ([string]::IsNullOrEmpty($updateSecret)) -and ($updateSecret -ne "dummy"))
|
||||
{
|
||||
Write-Host "Updating secret"
|
||||
$targetSecretFile = $Env:Programfiles + "\\Orbit\\secret.txt"
|
||||
Write-Host "Updating secret"
|
||||
$targetSecretFile = $Env:Programfiles + "\\Orbit\\secret.txt"
|
||||
Set-Content -NoNewline -Path $targetSecretFile -Value $updateSecret
|
||||
|
||||
Start-Sleep -Milliseconds 1000
|
||||
Start-Sleep -Milliseconds 1000
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,11 +568,11 @@ function Force-Remove-Orbit {
|
||||
|
||||
#Remove HKLM registry entries
|
||||
Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" -Recurse -ErrorAction "SilentlyContinue" | Where-Object {($_.ValueCount -gt 0)} | ForEach-Object {
|
||||
|
||||
|
||||
# Filter for osquery entries
|
||||
$properties = Get-ItemProperty $_.PSPath -ErrorAction "SilentlyContinue" | Where-Object {($_.DisplayName -eq "Fleet osquery")}
|
||||
$properties = Get-ItemProperty $_.PSPath -ErrorAction "SilentlyContinue" | Where-Object {($_.DisplayName -eq "Fleet osquery")}
|
||||
if ($properties) {
|
||||
|
||||
|
||||
#Remove Registry Entries
|
||||
$regKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" + $_.PSChildName
|
||||
|
||||
@@ -582,14 +582,14 @@ function Force-Remove-Orbit {
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
catch {
|
||||
Write-Host "There was a problem running Force-Remove-Orbit" -ForegroundColor Red
|
||||
Write-Host "====================================="
|
||||
Write-Host "$(Resolve-Error-Detailed)"
|
||||
Write-Host "====================================="
|
||||
return $false
|
||||
}
|
||||
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
@@ -610,28 +610,28 @@ function Force-Remove-Osquery {
|
||||
|
||||
#Remove HKLM registry entries and disk footprint
|
||||
Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" -Recurse -ErrorAction "SilentlyContinue" | Where-Object {($_.ValueCount -gt 0)} | ForEach-Object {
|
||||
|
||||
|
||||
# Filter for osquery entries
|
||||
$properties = Get-ItemProperty $_.PSPath -ErrorAction "SilentlyContinue" | Where-Object {($_.DisplayName -eq "osquery")}
|
||||
$properties = Get-ItemProperty $_.PSPath -ErrorAction "SilentlyContinue" | Where-Object {($_.DisplayName -eq "osquery")}
|
||||
if ($properties) {
|
||||
|
||||
#Remove files from osquery location
|
||||
#Remove files from osquery location
|
||||
if ($properties.InstallLocation){
|
||||
Remove-Item -LiteralPath $properties.InstallLocation -Force -Recurse -ErrorAction "SilentlyContinue"
|
||||
}
|
||||
|
||||
|
||||
#Remove Registry Entries
|
||||
$regKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" + $_.PSChildName
|
||||
Get-Item $regKey -ErrorAction "SilentlyContinue" | Remove-Item -Force -ErrorAction "SilentlyContinue"
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Remove user entries if present
|
||||
[RegistryUtils]::RemoveOsqueryInstallationFromUserHives()
|
||||
}
|
||||
catch {
|
||||
catch {
|
||||
Write-Host "There was a problem running Force-Remove-Osquery" -ForegroundColor Red
|
||||
Write-Host "====================================="
|
||||
Write-Host "$(Resolve-Error-Detailed)"
|
||||
@@ -657,40 +657,40 @@ function Graceful-Product-Uninstall($productName) {
|
||||
} elseif ($productName -eq "osquery") {
|
||||
Stop-Osquery
|
||||
}
|
||||
|
||||
|
||||
# Grabbing the location of msiexec.exe
|
||||
$targetBinPath = Resolve-Path "$env:windir\system32\msiexec.exe"
|
||||
if (!(Test-Path $targetBinPath)) {
|
||||
Write-Host "msiexec.exe cannot be located." -foregroundcolor Yellow
|
||||
return $false
|
||||
}
|
||||
|
||||
|
||||
# Creating a COM instance of the WindowsInstaller.Installer COM object
|
||||
$Installer = New-Object -ComObject WindowsInstaller.Installer
|
||||
if (!$Installer) {
|
||||
Write-Host "There was a problem retrieving the installed packages." -foregroundcolor Yellow
|
||||
return $false
|
||||
}
|
||||
|
||||
|
||||
# Enumerating the installed packages
|
||||
$ProductEnumFlag = 7 #installed packaged enumeration flag
|
||||
$InstallerProducts = $Installer.ProductsEx("", "", $ProductEnumFlag);
|
||||
$InstallerProducts = $Installer.ProductsEx("", "", $ProductEnumFlag);
|
||||
if (!$InstallerProducts) {
|
||||
Write-Host "Installed packages cannot be retrieved." -foregroundcolor Yellow
|
||||
return $false
|
||||
}
|
||||
|
||||
|
||||
# Iterating over the installed packages results and checking for osquery package
|
||||
ForEach ($Product in $InstallerProducts) {
|
||||
|
||||
|
||||
$ProductCode = $null
|
||||
$VersionString = $null
|
||||
$ProductPath = $null
|
||||
|
||||
|
||||
$ProductCode = $Product.ProductCode()
|
||||
$VersionString = $Product.InstallProperty("VersionString")
|
||||
$ProductPath = $Product.InstallProperty("ProductName")
|
||||
|
||||
|
||||
if ($ProductPath -like $productName) {
|
||||
Write-Host "Graceful uninstall of $ProductPath version $VersionString." -foregroundcolor Cyan
|
||||
$InstallProcess = Start-Process $targetBinPath -ArgumentList "/quiet /x $ProductCode" -PassThru -Verb RunAs -Wait
|
||||
@@ -703,12 +703,12 @@ function Graceful-Product-Uninstall($productName) {
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
catch {
|
||||
Write-Host "There was a problem running Graceful-Product-Uninstall" -ForegroundColor Red
|
||||
Write-Host "====================================="
|
||||
Write-Host "$(Resolve-Error-Detailed)"
|
||||
Write-Host "====================================="
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
@@ -727,30 +727,30 @@ function Main {
|
||||
if ($help) {
|
||||
Do-Help
|
||||
Exit -1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($uninstallOsquery) {
|
||||
Write-Host "About to uninstall Osquery." -foregroundcolor Yellow
|
||||
|
||||
|
||||
#if (Graceful-Product-Uninstall("osquery")) {
|
||||
if ($false) {
|
||||
Force-Remove-Osquery #best effort action to ensure cleanup after graceful uninstall
|
||||
Write-Host "Osquery was gracefully uninstalled." -foregroundcolor Cyan
|
||||
Exit 0
|
||||
|
||||
} else {
|
||||
} else {
|
||||
if (Force-Remove-Osquery) {
|
||||
Write-Host "Osquery was uninstalled." -foregroundcolor Cyan
|
||||
Exit 0
|
||||
} else {
|
||||
Write-Host "There was a problem uninstalling Osquery" -foregroundcolor Cyan
|
||||
Exit -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} elseif ($uninstallOrbit) {
|
||||
Write-Host "About to uninstall Orbit." -foregroundcolor Yellow
|
||||
|
||||
|
||||
#if (Graceful-Product-Uninstall("Fleet osquery")) {
|
||||
if ($false) {
|
||||
Force-Remove-Orbit #best effort action to ensure cleanup after graceful uninstall
|
||||
@@ -764,7 +764,7 @@ function Main {
|
||||
} else {
|
||||
Write-Host "There was a problem uninstalling Orbit" -foregroundcolor Cyan
|
||||
Exit -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} elseif ($stopOrbit) {
|
||||
@@ -782,14 +782,14 @@ function Main {
|
||||
|
||||
Write-Host "Orbit secret update was called." -foregroundcolor Cyan
|
||||
Exit 0
|
||||
|
||||
|
||||
} else {
|
||||
Write-Host "Invalid option selected: please see -help for usage details." -foregroundcolor Red
|
||||
Do-Help
|
||||
Exit -1
|
||||
}
|
||||
} catch {
|
||||
Write-Host "There was a problem running installer entry point logic" -ForegroundColor Red
|
||||
Write-Host "There was a problem running installer entry point logic" -ForegroundColor Red
|
||||
Write-Host "====================================="
|
||||
Write-Host "$(Resolve-Error-Detailed)"
|
||||
Write-Host "====================================="
|
||||
|
||||
@@ -1106,18 +1106,6 @@ func searchLikePattern(sql string, params []interface{}, match string, replacer
|
||||
return sql, params
|
||||
}
|
||||
|
||||
// very loosely checks that a string looks like an email:
|
||||
// has no spaces, a single @ character, a part before the @,
|
||||
// a part after the @, the part after has at least one dot
|
||||
// with something after the dot. I don't think this is perfectly
|
||||
// correct as the email format allows any chars including spaces
|
||||
// when inside double quotes, but this is an edge case that is
|
||||
// unlikely to matter much in practice. Another option that would
|
||||
// definitely not cut out any valid address is to just check for
|
||||
// the presence of @, which is arguably the most important check
|
||||
// in this.
|
||||
var rxLooseEmail = regexp.MustCompile(`^[^\s@]+@[^\s@\.]+\..+$`)
|
||||
|
||||
/*
|
||||
This regex matches any occurrence of a character from the ASCII character set followed by one or more characters that are not from the ASCII character set.
|
||||
The first part `[[:ascii:]]` matches any character that is within the ASCII range (0 to 127 in the ASCII table),
|
||||
@@ -1136,7 +1124,7 @@ func hostSearchLike(sql string, params []interface{}, match string, columns ...s
|
||||
|
||||
// special-case for hosts: if match looks like an email address, add searching
|
||||
// in host_emails table as an option, in addition to the provided columns.
|
||||
if rxLooseEmail.MatchString(match) {
|
||||
if fleet.IsLooseEmail(match) {
|
||||
matchesEmail = true
|
||||
// remove the closing paren and add the email condition to the list
|
||||
base = strings.TrimSuffix(base, ")") + " OR (" + ` EXISTS (SELECT 1 FROM host_emails he WHERE he.host_id = h.id AND he.email LIKE ?)))`
|
||||
|
||||
@@ -959,27 +959,6 @@ func TestCompareVersions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRxLooseEmail(t *testing.T) {
|
||||
testCases := []struct {
|
||||
str string
|
||||
match bool
|
||||
}{
|
||||
{"foo", false},
|
||||
{"", false},
|
||||
{"foo@example", false},
|
||||
{"foo@example.com", true},
|
||||
{"foo+bar@example.com", true},
|
||||
{"foo.bar@example.com", true},
|
||||
{"foo.bar@baz.example.com", true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.str, func(t *testing.T) {
|
||||
assert.Equal(t, tc.match, rxLooseEmail.MatchString(tc.str))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebugs(t *testing.T) {
|
||||
ds := CreateMySQLDS(t)
|
||||
|
||||
|
||||
@@ -75,12 +75,16 @@ const (
|
||||
// periodic rotation of device tokens
|
||||
CapabilityTokenRotation Capability = "token_rotation"
|
||||
CapabilityErrorReporting Capability = "error_reporting"
|
||||
// CapabilityEndUserEmail denotes the ability of the server to support
|
||||
// receiving the end-user email from orbit.
|
||||
CapabilityEndUserEmail Capability = "end_user_email"
|
||||
)
|
||||
|
||||
func GetServerOrbitCapabilities() CapabilityMap {
|
||||
return CapabilityMap{
|
||||
CapabilityOrbitEndpoints: {},
|
||||
CapabilityTokenRotation: {},
|
||||
CapabilityEndUserEmail: {},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package fleet
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -32,3 +33,21 @@ type PasswordResetRequest struct {
|
||||
UserID uint `db:"user_id"`
|
||||
Token string
|
||||
}
|
||||
|
||||
// very loosely checks that a string looks like an email:
|
||||
// has no spaces, a single @ character, a part before the @,
|
||||
// a part after the @, the part after has at least one dot
|
||||
// with something after the dot. I don't think this is perfectly
|
||||
// correct as the email format allows any chars including spaces
|
||||
// when inside double quotes, but this is an edge case that is
|
||||
// unlikely to matter much in practice. Another option that would
|
||||
// definitely not cut out any valid address is to just check for
|
||||
// the presence of @, which is arguably the most important check
|
||||
// in this.
|
||||
var rxLooseEmail = regexp.MustCompile(`^[^\s@]+@[^\s@\.]+\..+$`)
|
||||
|
||||
// IsLooseEmail loosely checks that the provided string looks like
|
||||
// an email.
|
||||
func IsLooseEmail(email string) bool {
|
||||
return rxLooseEmail.MatchString(email)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package fleet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsLooseEmail(t *testing.T) {
|
||||
testCases := []struct {
|
||||
str string
|
||||
match bool
|
||||
}{
|
||||
{"foo", false},
|
||||
{"", false},
|
||||
{"foo@example", false},
|
||||
{"foo@example.com", true},
|
||||
{"foo+bar@example.com", true},
|
||||
{"foo.bar@example.com", true},
|
||||
{"foo.bar@baz.example.com", true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.str, func(t *testing.T) {
|
||||
assert.Equal(t, tc.match, IsLooseEmail(tc.str))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -146,6 +146,20 @@ func (oc *OrbitClient) SetOrUpdateDeviceToken(deviceAuthToken string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetOrUpdateDeviceMappingEmail sends a request to the server to set or update the
|
||||
// device mapping email with the given value.
|
||||
func (oc *OrbitClient) SetOrUpdateDeviceMappingEmail(email string) error {
|
||||
verb, path := "PUT", "/api/fleet/orbit/device_mapping"
|
||||
params := orbitPutDeviceMappingRequest{
|
||||
Email: email,
|
||||
}
|
||||
var resp orbitPutDeviceMappingResponse
|
||||
if err := oc.authenticatedRequest(verb, path, ¶ms, &resp); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHostScript returns the script fetched from Fleet server to run on this
|
||||
// host.
|
||||
func (oc *OrbitClient) GetHostScript(execID string) (*fleet.HostScriptResult, error) {
|
||||
|
||||
Reference in New Issue
Block a user