Documentation
¶
Overview ¶
Package m365 holds the Microsoft 365 backup job specification, the tenant (Entra app registration) model, the Graph credential flow and the run manifest. It is shared by the server (validation, tenant API, session preparation) and the agent (execution), so it must stay free of process-execution dependencies.
Index ¶
- Constants
- Variables
- func ClientAssertion(clientID, tokenURL, x5t string, now time.Time, ...) (string, error)
- func DefaultBackupID(tenantName string) string
- func GroupPrefix(jobBackupID string) string
- func MatchesAny(name string, globs []string) bool
- func ParseCertificate(certPEM, keyPEM string) (*x509.Certificate, *rsa.PrivateKey, error)
- func SafeName(s string) string
- func Selected(name string, include, exclude []string) bool
- func TokenSource(ctx context.Context, c Credentials, authority string) (oauth2.TokenSource, error)
- func TokenURL(authority, tenantID string) string
- func UnitGroup(jobBackupID, kind, name string) string
- type ArchiveStats
- type Credentials
- type Generator
- type JobInfo
- type Manifest
- type Organization
- type Selection
- type Spec
- type Tenant
- type TenantInfo
- type UnitResult
- type UserSelection
- type Workloads
Constants ¶
const ( // ArchiveMailbox / ArchiveOneDrive are the two archives of a user group ArchiveMailbox = "mailbox" ArchiveOneDrive = "onedrive" // ArchiveFiles is the document libraries of a site group (and of a team group) ArchiveFiles = "files" // ArchiveTeams is the channel messages of a team group ArchiveTeams = "teams" // ArchiveEntra is the directory archive of the job's own group ArchiveEntra = "entra" // ManifestBlobName is the run manifest, a blob of the directory group's snapshot ManifestBlobName = "fallbck-m365.json" // UnitManifestName is the per-unit manifest stored as a blob of every unit snapshot UnitManifestName = "fallbck-m365-unit.json" )
Names inside the snapshots
const ( UnitUser = "user" UnitSite = "site" UnitTeam = "team" )
Unit kinds: one PBS group per unit, named <job group>-<kind>-<name>
const ( AuthorityURL = "https://login.microsoftonline.com" GraphURL = "https://graph.microsoft.com" // GraphScope is the client-credentials scope for application permissions GraphScope = GraphURL + "/.default" )
Microsoft global cloud endpoints (the only cloud supported: the mailbox export API exists nowhere else)
const ( AuthSecret = "secret" AuthCertificate = "certificate" )
App authentication methods
const DefaultConcurrency = 4
DefaultConcurrency is the number of units in flight (Graph requests and PBS sessions) when the job does not say
const ExpiryWarning = 30 * 24 * time.Hour
ExpiryWarning is how long before the expiry the UI warns
const ManifestSchemaVersion = 1
ManifestSchemaVersion is bumped on incompatible manifest changes
const MaxConcurrency = 32
MaxConcurrency bounds the job setting
Variables ¶
var DefaultMailFolderExcludes = []string{"Deleted Items", "Junk Email", "Conversation History"}
DefaultMailFolderExcludes are the mail folders left out unless the job lists its own
var ErrInvalidSpec = errors.New("invalid Microsoft 365 job configuration")
ErrInvalidSpec wraps every validation failure
var ErrInvalidTenant = errors.New("invalid Microsoft 365 tenant")
ErrInvalidTenant wraps every validation failure
Functions ¶
func ClientAssertion ¶
func ClientAssertion(clientID, tokenURL, x5t string, now time.Time, sign func(*jwt.Token) (string, error)) (string, error)
ClientAssertion builds the JWT Entra expects from a certificate credential: RS256, x5t header = SHA-1 thumbprint of the certificate, audience = token endpoint, issuer = subject = client id, valid for ten minutes
func DefaultBackupID ¶
DefaultBackupID is the job group when the spec does not override it
func GroupPrefix ¶
GroupPrefix is what every unit group of the job must start with
func MatchesAny ¶
MatchesAny reports whether name matches one of the globs (case-insensitive)
func ParseCertificate ¶
func ParseCertificate(certPEM, keyPEM string) (*x509.Certificate, *rsa.PrivateKey, error)
ParseCertificate parses the PEM certificate and its RSA private key (PKCS#1 or PKCS#8) and checks they belong together
func SafeName ¶
SafeName maps a UPN, site or team name into the PBS backup-id character set: lower-cased, '@' becomes '_', every other run of foreign characters becomes '-'. alice@contoso.com → alice_contoso.com. Collisions are the caller's problem (see the plan: append part of the object id).
func Selected ¶
Selected applies include/exclude globs: included when Include is empty or matches, and not excluded
func TokenSource ¶
func TokenSource(ctx context.Context, c Credentials, authority string) (oauth2.TokenSource, error)
TokenSource returns an oauth2 token source for the Graph application permissions of the credentials: the client-credentials flow with the secret, or with a freshly signed certificate client assertion on every token request (the assertion is short-lived, the access token lasts about an hour). authority is AuthorityURL unless a test substitutes an endpoint.
Types ¶
type ArchiveStats ¶
type ArchiveStats struct {
Name string `json:"name"`
Items int `json:"items"`
Bytes uint64 `json:"bytes"`
Downloaded uint64 `json:"downloaded"`
Reused uint64 `json:"reused"`
}
ArchiveStats counts one archive of a unit
type Credentials ¶
type Credentials struct {
TenantID string `json:"tenant_id"`
ClientID string `json:"client_id"`
AuthMethod string `json:"auth_method"`
Secret string `json:"secret,omitempty"`
CertificatePEM string `json:"certificate_pem,omitempty"`
KeyPEM string `json:"key_pem,omitempty"`
}
Credentials is what an agent fetches for a run (GET /api/v1/agents/m365-credentials)
type Generator ¶
type Generator struct {
Agent string `json:"agent"`
Version string `json:"version"`
Hostname string `json:"hostname"`
}
Generator identifies the agent that produced the backup
type JobInfo ¶
type JobInfo struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
RunID string `json:"run_id"`
BackupRunID string `json:"backup_run_id"`
// Group is the job's PBS group (directory archive, prefix of every unit group)
Group string `json:"group"`
}
JobInfo identifies the job and run
type Manifest ¶
type Manifest struct {
SchemaVersion int `json:"schema_version"`
Generator Generator `json:"generator"`
Job JobInfo `json:"job"`
Tenant TenantInfo `json:"tenant"`
StartedAt time.Time `json:"started_at"`
FinishedAt time.Time `json:"finished_at"`
Units []UnitResult `json:"units"`
Spec *Spec `json:"spec,omitempty"`
Warnings []string `json:"warnings,omitempty"`
Restore map[string]string `json:"restore"`
}
Manifest describes one Microsoft 365 run: the tenant, every unit group written and how to restore. Stored as fallbck-m365.json, a blob of the directory group's snapshot; each unit snapshot carries its own UnitResult as fallbck-m365-unit.json.
type Organization ¶
type Organization struct {
ID string `json:"id"`
DisplayName string `json:"display_name"`
Domains []string `json:"domains"`
}
Organization is what TestConnection learns about the tenant
func TestConnection ¶
func TestConnection(ctx context.Context, c Credentials, authority, graphURL string) (*Organization, error)
TestConnection acquires a token and reads /v1.0/organization. graphURL is GraphURL unless a test substitutes a server. The error text names the step that failed so an administrator can tell a wrong secret from a blocked egress or a missing permission.
type Selection ¶
type Selection struct {
Include []string `json:"include,omitempty"`
Exclude []string `json:"exclude,omitempty"`
}
Selection picks sites (by URL glob) or teams (by display name glob); empty = all
type Spec ¶
type Spec struct {
// TenantID is the m365_tenants row the job uses
TenantID uuid.UUID `json:"tenant_id"`
Workloads Workloads `json:"workloads"`
Users UserSelection `json:"users"`
Sites Selection `json:"sites"`
Teams Selection `json:"teams"`
// MailFolderExcludes are mail folder display names left out; absent (null) means
// DefaultMailFolderExcludes, an empty list means none
MailFolderExcludes []string `json:"mail_folder_excludes"`
// ExcludeTypes are message classes left out (IPM.Appointment, IPM.Contact, ...)
ExcludeTypes []string `json:"exclude_types,omitempty"`
// ExcludePaths are proxmox-backup-client style patterns against the archive paths
// of the drive archives (/Documents/Recordings/**)
ExcludePaths []string `json:"exclude_paths,omitempty"`
// Concurrency is the number of units in flight (default DefaultConcurrency)
Concurrency int `json:"concurrency,omitempty"`
// BackupID overrides the job's PBS group, the prefix of every unit group
// (default m365-<tenant short name>)
BackupID string `json:"backup_id,omitempty"`
}
Spec is the Microsoft 365 job configuration (backup_jobs.m365_config)
func (*Spec) EffConcurrency ¶
EffConcurrency / EffMailFolderExcludes apply the defaults
func (*Spec) EffMailFolderExcludes ¶
type Tenant ¶
type Tenant struct {
ID uuid.UUID `json:"id"`
// Name is the administrator's label; SafeName(Name) is the default group prefix
Name string `json:"name"`
// TenantID is the directory (tenant) id GUID or a verified domain
TenantID string `json:"tenant_id"`
// ClientID is the application (client) id of the app registration
ClientID string `json:"client_id"`
AuthMethod string `json:"auth_method"`
Secret string `json:"-"`
// CertificatePEM is the public certificate uploaded to the app registration;
// KeyPEM its private key (RSA)
CertificatePEM string `json:"-"`
KeyPEM string `json:"-"`
// SecretExpires is when the secret or certificate stops working (the health page
// warns 30 days before). Entered by the administrator for secrets, taken from the
// certificate otherwise.
SecretExpires *time.Time `json:"secret_expires,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// Derived, API-safe
HasSecret bool `json:"has_secret"`
HasCertificate bool `json:"has_certificate"`
// Jobs is the number of jobs referencing the tenant (filled by the list API)
Jobs int `json:"jobs"`
// Labels place the tenant in access scopes (db.Labels; a plain map here because
// pkg/db imports this package)
Labels map[string]string `json:"labels"`
}
Tenant is a Microsoft 365 tenant with the Entra app registration FallBCK uses to read it (m365_tenants). Secret and KeyPEM are in clear in memory only; the repository seals them with the server master key.
func (*Tenant) Credentials ¶
func (t *Tenant) Credentials() Credentials
Credentials extracts the agent's view of the tenant
func (*Tenant) ExpiresWithin ¶
ExpiresWithin reports whether the credential expires before now+d (or has expired)
func (*Tenant) ExpiringSoon ¶
type TenantInfo ¶
TenantInfo names the tenant as Graph reports it
type UnitResult ¶
type UnitResult struct {
Kind string `json:"kind"`
// Name is the UPN, site URL or team display name; ObjectID the Graph id
Name string `json:"name"`
ObjectID string `json:"object_id,omitempty"`
Group string `json:"group"`
// Status: completed | failed | skipped
Status string `json:"status"`
Error string `json:"error,omitempty"`
// SnapshotTime is the snapshot written for the unit (unix seconds), 0 when none
SnapshotTime int64 `json:"snapshot_time,omitempty"`
// Per archive: items in the tree, bytes fetched from Graph this run, bytes reused
// from the previous snapshot
Archives []ArchiveStats `json:"archives,omitempty"`
Resyncs int `json:"resyncs,omitempty"`
Warnings []string `json:"warnings,omitempty"`
}
UnitResult is one user, site or team of the run
type UserSelection ¶
type UserSelection struct {
// Groups restricts the selection to the transitive members of these groups
// (display names or object ids); empty = every user
Groups []string `json:"groups,omitempty"`
// Include / Exclude are UPN globs applied after Groups
Include []string `json:"include,omitempty"`
Exclude []string `json:"exclude,omitempty"`
// Unlicensed also backs up accounts without a licence (default false)
Unlicensed bool `json:"unlicensed,omitempty"`
Shared *bool `json:"shared,omitempty"`
}
UserSelection picks the users (mailbox + OneDrive) of the tenant
func (UserSelection) EffShared ¶
func (u UserSelection) EffShared() bool
Shared_ applies the default
type Workloads ¶
type Workloads struct {
Mailboxes *bool `json:"mailboxes,omitempty"`
OneDrive *bool `json:"onedrive,omitempty"`
Sites *bool `json:"sites,omitempty"`
Teams *bool `json:"teams,omitempty"`
Entra *bool `json:"entra,omitempty"`
}
Workloads selects what a job backs up. nil means enabled (the default is everything)
func (Workloads) EffMailboxes ¶
Enabled workloads, defaults applied