m365

package
v0.14.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 10, 2026 License: AGPL-3.0 Imports: 21 Imported by: 0

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

View Source
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

View Source
const (
	UnitUser = "user"
	UnitSite = "site"
	UnitTeam = "team"
)

Unit kinds: one PBS group per unit, named <job group>-<kind>-<name>

View Source
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)

View Source
const (
	AuthSecret      = "secret"
	AuthCertificate = "certificate"
)

App authentication methods

View Source
const DefaultConcurrency = 4

DefaultConcurrency is the number of units in flight (Graph requests and PBS sessions) when the job does not say

View Source
const ExpiryWarning = 30 * 24 * time.Hour

ExpiryWarning is how long before the expiry the UI warns

View Source
const ManifestSchemaVersion = 1

ManifestSchemaVersion is bumped on incompatible manifest changes

View Source
const MaxConcurrency = 32

MaxConcurrency bounds the job setting

Variables

View Source
var DefaultMailFolderExcludes = []string{"Deleted Items", "Junk Email", "Conversation History"}

DefaultMailFolderExcludes are the mail folders left out unless the job lists its own

View Source
var ErrInvalidSpec = errors.New("invalid Microsoft 365 job configuration")

ErrInvalidSpec wraps every validation failure

View Source
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

func DefaultBackupID(tenantName string) string

DefaultBackupID is the job group when the spec does not override it

func GroupPrefix

func GroupPrefix(jobBackupID string) string

GroupPrefix is what every unit group of the job must start with

func MatchesAny

func MatchesAny(name string, globs []string) bool

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

func SafeName(s string) string

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

func Selected(name string, include, exclude []string) bool

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.

func TokenURL

func TokenURL(authority, tenantID string) string

TokenURL is the tenant's v2.0 token endpoint

func UnitGroup

func UnitGroup(jobBackupID, kind, name string) string

UnitGroup names the PBS group of one unit

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) Clone

func (s *Spec) Clone() *Spec

Clone returns a deep copy

func (*Spec) EffConcurrency

func (s *Spec) EffConcurrency() int

EffConcurrency / EffMailFolderExcludes apply the defaults

func (*Spec) EffMailFolderExcludes

func (s *Spec) EffMailFolderExcludes() []string

func (*Spec) Summary

func (s *Spec) Summary() string

Summary is a short human-readable description (UI tables, logs)

func (*Spec) Validate

func (s *Spec) Validate() error

Validate normalises globs and patterns and checks the ranges

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"`
}

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) Expired

func (t *Tenant) Expired() bool

Expired / ExpiringSoon are the template-friendly forms of ExpiresWithin

func (*Tenant) ExpiresWithin

func (t *Tenant) ExpiresWithin(d time.Duration) bool

ExpiresWithin reports whether the credential expires before now+d (or has expired)

func (*Tenant) ExpiringSoon

func (t *Tenant) ExpiringSoon() bool

func (*Tenant) Sanitize

func (t *Tenant) Sanitize() *Tenant

Sanitize fills the derived fields

func (*Tenant) Validate

func (t *Tenant) Validate() error

Validate checks the identifiers and the credential of the chosen method. With the certificate method the PEM pair is parsed, the key must be RSA and match the certificate, and SecretExpires is set from the certificate.

type TenantInfo

type TenantInfo struct {
	ID          string `json:"id"`
	DisplayName string `json:"display_name"`
}

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 includes shared mailboxes (default true)
	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) Any

func (w Workloads) Any() bool

Any reports whether at least one workload is enabled

func (Workloads) EffEntra

func (w Workloads) EffEntra() bool

func (Workloads) EffMailboxes

func (w Workloads) EffMailboxes() bool

Enabled workloads, defaults applied

func (Workloads) EffOneDrive

func (w Workloads) EffOneDrive() bool

func (Workloads) EffSites

func (w Workloads) EffSites() bool

func (Workloads) EffTeams

func (w Workloads) EffTeams() bool

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL