accountserver

package module
v0.0.0-...-f0491b5 Latest Latest
Warning

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

Go to latest
Published: Nov 5, 2023 License: GPL-3.0 Imports: 33 Imported by: 0

README

accountserver

The accountserver is the main interface used to manage the database of user accounts. Other internal services use it to query and modify user account information (settings, credentials, etc). It implements all the validation and business logic related to accounts.

Motivations for this service stem from our experience with previous account management strategies:

  • the necessity for a high-level API on top of the database layer (accounts and resources, instead of nested LDAP objects or SQL tables), isolating applications from the database structure;
  • the desire to have a single authoritative implementation of the data validation logic, and to have every change to the database go through it;
  • a wish for a cleaner separation between the business logic of complex operations ("disable a resource", "move an account between hosts", etc) and their UI. In our ideal model, user interfaces (web panels, admin tools) are simply thin clients focused on presentation and interaction, and the logic is implemented in RPC servers.

The service is implemented as an RPC service, without any user interface. This approach has been preferred over others (like a library for clients to embed) for a few reasons:

  • information on accounts and resources might be aggregated from multiple backends (an LDAP database, Redis, MySQL for Noblogs, etc) and we'd like to limit the proliferation of network flows;
  • privilege separation: only the accountserver needs write credentials for the backends;
  • a single centralized service offers a simpler target for logging and monitoring.

Data model

The data model offered by accountserver is quite simple: the top-level object is the user (an account). Each user owns a number of resources, which can be of different types. Resources have a loose hierarchical structure, and might themselves contain sub-resources, expressing an association of some kind (as for instance between a website and the associated MySQL database).

This data model is meant to work with our legacy user database, but it is slightly overkill for the simplest cases: for instance a simple email service might consider users and their email accounts to be the same thing, while this model would present them as a user object containing an email resource with the same name. It is however at least easily adaptable to most use cases.

The schema is explicitly defined in types.go.

API

The service API is documented in API.md.

Extending the service

The business logic (account creation, validation, and all the high-level operations defined on them) is currently implemented as Go code within the accountserver itself, in the actions_*.go and validators.go files.

There are specific notes on how to add and modify functionality in CONTRIBUTING.md.

Testing

Running the integration tests requires, beyond a working Go development environment, a JRE: we start a test LDAP server locally (which is written in Java) in order to test the LDAP backend.

On a Debian system this should be enough:

sudo apt install default-jre-headless

Usage

The accountserver daemon simply listens on a port for HTTP(S) requests. Specify the address to listen on with the --addr command-line option.

Configuration

The configuration is stored in a YAML file, by default /etc/accountserver/config.yml. Known variables include:

  • shards: map of shards by service, for sharded (partitioned) services
    • available: map of available shards by service name, e.g. {"web": ["1", "2"]}. Used in resource creation.
    • allowed: map of allowed shards by service name
  • sso:
    • public_key: path to file with SSO public key
    • domain: SSO domain
    • service: SSO service for the accountserver
    • groups: list of allowed groups
    • admin_group: a specific group that will be granted admin privileges (the ability to read/write data about different users than oneself)
  • http_server: specifies standard parameters for the HTTP server
    • tls: server-side TLS configuration
      • cert: path to the server certificate
      • key: path to the server's private key
      • ca: path to the CA used to validate clients
      • acl: TLS-based access controls, a list of entries with the following attributes:
        • path is a regular expression to match the request URL path
        • cn is a regular expression that must match the CommonName part of the subject of the client certificate
    • max_inflight_requests: maximum number of in-flight requests to allow before server-side throttling kicks in
  • user_meta_server: connection parameters for the user-meta-server backend used to store user audit logs
    • url: URL for the user-meta-server service
    • sharded: if true, requests to the service will be partitioned according to the user's shard attribute
    • tls_config: client TLS configuration
      • cert: path to the client certificate
      • key: path to the private key
      • ca: path to the CA used to validate the server
  • auto_enable_encryption: if true, automatically enable user-level encryption when a user changes their primary authentication (password)
  • forbidden_usernames / forbidden_usernames_file: list (or file) containing forbidden usernames
  • forbidden_passwords / forbidden_passwords_file: list (or file) containing forbidden passwords
  • available_domains: list of available domains for email resources
  • website_root_dir: root directory of user websites
  • min_password_len: minimum length of passwords (default 8)
  • max_password_len: maximum length of passwords (default 128)
  • min_username_len: minimum username length (default 3)
  • max_username_len: maximum username length (default 64)
  • min_backend_uid: minimum auto-assigned UID (default 1000)
  • max_backend_uid: maximum auto-assigned UID (default 0, disabled)
  • ldap: configuration for the LDAP backend
    • uri: LDAP URI to connect to
    • bind_dn: LDAP bind DN
    • bind_pw / bind_pw_file: LDAP bind password, or file to read it from
    • base_dn: base DN for all LDAP queries
  • pwhash: password hashing parameters
    • algo: password hashing algorithm, one of argon2 or scrypt
    • params: parameters for the selected hashing algorithm, a map whose values will depend on the chosen algorithm: argon2 requires the time, mem and threads parameters (defaults to 1/4/4); scrypt requires n, r and p (defaults 16384/8/1)
  • cache: cache configuration
    • enabled: if set to true, enable a cache for User objects. Very useful to reduce latencies for backends with complex queries like LDAP (default false, cache is disabled).

Distributed operation

In a distributed scenario it might make sense to run multiple instances of the accountserver for reliability. The accountserver however is not a distributed application and does not include a mechanism for managing consensus: furthermore it relies on the characteristics of the underlying storage, which aren't under the accountserver's control (consider for instance the case of using a SQL or LDAP database with asynchronous replication).

The accountserver load is heavily skewed towards reads, and the read and write paths have very different operational characteristics: writes require a centralized accountserver, due to the presence of many read-modify-update cycles in our API. The only way to improve this is to use some highly-available, serialized storage. Writes, however, are infrequent, and are not critical to the operation of the accountserver clients. Reads, instead, are very frequent and require caching for performance and latency reasons. It follows that prioritizing reads over writes would be a reasonable graceful degradation policy for the service.

If the storage layer has any form of read-only high-availability (much easier to achieve, this would be the case for most asynchronously-replicated setups for instance), this is exploitable by the accountserver by providing high-availability for the read path, which is basically a distributed caching problem. Given the tolerances of the upstream applications, the only real issue is the necessary connection between write path and read path which is required for cache invalidation on writes.

The simplest way to make this work is the following:

  • assume that the full configuration is available to each accountserver at all times: that is, each accountserver instance has a list of all the other accountserver instances;
  • one of the accountserver instances is selected as the leader by some external mechanism (including manually);
  • write requests are always forwarded to the leader: this keeps the client API simple, requiring no awareness of the accountserver topology;
  • every accountserver instance maintains its own read cache, and reads are always served by the local accountserver, never forwarded;
  • the leader accountserver, whenever it accepts a write, sends cache invalidation requests to every other accountserver instance.

The performance of the above is strictly not worse than that of the underlying storage, except for the possibility of serving stale data whenever we lose an invalidation request due to network trouble. This is generally an acceptable risk for our upstream applications.

Configuration

To enable distributed operations set attributes below the replication configuration variable:

  • replication
    • leader_url: URL of the leader accountserver instance. When this field is set, write requests to this instance will be forwarded (transparently to the caller) to this URL.
    • peers: list of peer URLs for the other accountserver instances. Do not include the current instance in this list, or you will create unexpected feedback loops.
    • tls: client TLS configuration
      • cert: path to the server certificate
      • key: path to the server's private key
      • ca: path to the CA used to validate clients

Note that setting peers is only necessary if the cache is enabled (see the Configuration section above). Due to implementation details, all instances should share the same setting for cache.enabled.

Documentation

Index

Constants

View Source
const (
	UserStatusActive   = "active"
	UserStatusInactive = "inactive"
)

Possible values for user status.

View Source
const (
	UserEncryptionKeyMainID     = "main"
	UserEncryptionKeyRecoveryID = "recovery"
)

Well-known user encryption key types, corresponding to primary and secondary passwords.

View Source
const (
	ResourceTypeEmail       = "email"
	ResourceTypeMailingList = "list"
	ResourceTypeNewsletter  = "newsletter"
	ResourceTypeWebsite     = "web"
	ResourceTypeDomain      = "domain"
	ResourceTypeDAV         = "dav"
	ResourceTypeDatabase    = "db"
)

Resource types.

View Source
const (
	ResourceStatusActive   = "active"
	ResourceStatusInactive = "inactive"
	ResourceStatusReadonly = "readonly"
	ResourceStatusArchived = "archived"
)

Resource status values.

Variables

View Source
var (
	// ErrUserNotFound is returned when a user object is not found.
	ErrUserNotFound = errors.New("user not found")

	// ErrResourceNotFound is returned when a resource object is not found.
	ErrResourceNotFound = errors.New("resource not found")
)
View Source
var DefaultPasswordHash pwhash.PasswordHash

The password hashing algorithm to use when updating credentials.

Functions

func IsAuthError

func IsAuthError(err error) bool

IsAuthError returns true if err is an authentication / authorization error.

func IsBackendError

func IsBackendError(err error) bool

IsBackendError returns true if err is a backend error.

func IsRequestError

func IsRequestError(err error) bool

IsRequestError returns true if err is a request error (bad request).

func IsValidationError

func IsValidationError(err error) bool

IsValidationError returns true if err is a validation error.

Types

type AccountRecoveryRequest

type AccountRecoveryRequest struct {
	Username         string `json:"username"`
	RecoveryPassword string `json:"recovery_password"`
	Password         string `json:"password"`
	RemoteAddr       string `json:"remote_addr"`
}

AccountRecoveryRequest lets users reset their password by providing secondary credentials, which we authenticate ourselves. It is not authenticated with SSO.

Two-factor authentication is disabled on successful recovery.

func (*AccountRecoveryRequest) Authorize

func (r *AccountRecoveryRequest) Authorize(rctx *RequestContext) error

Authorize the request.

func (*AccountRecoveryRequest) PopulateContext

func (r *AccountRecoveryRequest) PopulateContext(rctx *RequestContext) error

PopulateContext extracts information from the request and stores it into the RequestContext.

func (*AccountRecoveryRequest) Sanitize

func (r *AccountRecoveryRequest) Sanitize()

Sanitize the request.

func (*AccountRecoveryRequest) Serve

func (r *AccountRecoveryRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

func (*AccountRecoveryRequest) Validate

func (r *AccountRecoveryRequest) Validate(rctx *RequestContext) error

Validate the request.

type AccountRecoveryResponse

type AccountRecoveryResponse struct {
	Hint string `json:"hint,omitempty"`
}

AccountRecoveryResponse is the response type for AccountRecoveryRequest.

type AccountService

type AccountService struct {
	// contains filtered or unexported fields
}

AccountService implements the business logic and high-level functionality of the user accounts management service. It provides common services that the various action handlers can use, such as validation, auditing, etc.

Action handlers aren't implemented as methods on AccountService, instead they get access to this object via the RequestContext.

func NewAccountService

func NewAccountService(backend Backend, config *Config) (*AccountService, error)

NewAccountService builds a new AccountService with the specified configuration.

func (*AccountService) Handle

func (s *AccountService) Handle(ctx context.Context, r Request) (interface{}, error)

Handle the given Request. Returns its result.

type AddEmailAliasRequest

type AddEmailAliasRequest struct {
	ResourceRequestBase
	Addr string `json:"addr"`
}

AddEmailAliasRequest adds an alias (additional address) to an email resource.

func (*AddEmailAliasRequest) Serve

func (r *AddEmailAliasRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

func (*AddEmailAliasRequest) Validate

func (r *AddEmailAliasRequest) Validate(rctx *RequestContext) error

Validate the request.

type AdminRequestBase

type AdminRequestBase struct {
	RequestBase
}

AdminRequestBase is a generic admin request.

func (*AdminRequestBase) Authorize

func (r *AdminRequestBase) Authorize(rctx *RequestContext) error

Authorize the request.

type AdminResourceRequestBase

type AdminResourceRequestBase struct {
	ResourceRequestBase
}

AdminResourceRequestBase is an admin-only version of ResourceRequestBase.

func (*AdminResourceRequestBase) Authorize

func (r *AdminResourceRequestBase) Authorize(rctx *RequestContext) error

Authorize the request.

type AdminUpdateResourceRequest

type AdminUpdateResourceRequest struct {
	AdminResourceRequestBase

	Resource *Resource `json:"resource"`
}

AdminUpdateResourceRequest updates arbitrary fields on a Resource (privileged management operation).

func (*AdminUpdateResourceRequest) Serve

func (r *AdminUpdateResourceRequest) Serve(rctx *RequestContext) (interface{}, error)

func (*AdminUpdateResourceRequest) Validate

func (r *AdminUpdateResourceRequest) Validate(rctx *RequestContext) error

type AdminUpdateUserRequest

type AdminUpdateUserRequest struct {
	AdminUserRequestBase
	Lang   string `json:"lang,omitempty"`
	Status string `json:"status"`
}

AdminUpdateUserRequest is the privileged version of UpdateUser and allows to update privileged attributes. It is a catch-all function for very simple changes that don't justify their own specialized method.

func (*AdminUpdateUserRequest) Serve

func (r *AdminUpdateUserRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

func (*AdminUpdateUserRequest) Validate

func (r *AdminUpdateUserRequest) Validate(rctx *RequestContext) error

Validate the request.

type AdminUserRequestBase

type AdminUserRequestBase struct {
	UserRequestBase
}

AdminUserRequestBase is an admin-only version of UserRequestBase.

func (*AdminUserRequestBase) Authorize

func (r *AdminUserRequestBase) Authorize(rctx *RequestContext) error

Authorize the request.

type App

type App struct {
	//Shard       string    `json:"shard"`
	Path        string    `json:"directory"`
	Site        string    `json:"site"`
	Name        string    `json:"appname"`
	Version     string    `json:"version"`
	SafeVersion string    `json:"safeversion"`
	State       string    `json:"state"`
	VulnInfo    string    `json:"vulninfo"`
	Timestamp   time.Time `json:"timestamp"`
}

App stores information about an application instance. The JSON fields should match the XML fields as used by 'freewvs --xml'.

type Auth

type Auth struct {
	Username string
	IsAdmin  bool
}

Auth parameters of an incoming request (validated).

type AuthError

type AuthError struct {
	// contains filtered or unexported fields
}

AuthError is an authentication error.

func (*AuthError) Unwrap

func (e *AuthError) Unwrap() error

type Backend

type Backend interface {
	NewTransaction() (TX, error)
}

Backend user database interface.

We are using a transactional interface even if the actual backend (LDAP) does not support atomic transactions, just so it is easy to add more backends in the future (like SQL).

type BackendError

type BackendError struct {
	// contains filtered or unexported fields
}

func (*BackendError) Unwrap

func (e *BackendError) Unwrap() error

type Blog

type Blog struct {
	Name string `json:"name"`
	URL  string `json:"url"`
}

Blog resource attributes.

type CMSInfo

type CMSInfo struct {
	Name    string `json:"name"`
	Version string `json:"version"`
	Status  string `json:"status"`
}

CMSInfo holds CMS-specific information.

type ChangeUserPasswordRequest

type ChangeUserPasswordRequest struct {
	PrivilegedRequestBase
	Password string `json:"password"`
}

ChangeUserPasswordRequest updates a user's password. It will also take care of re-encrypting the user encryption key, if present.

func (*ChangeUserPasswordRequest) Sanitize

func (r *ChangeUserPasswordRequest) Sanitize()

Sanitize the request.

func (*ChangeUserPasswordRequest) Serve

func (r *ChangeUserPasswordRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

func (*ChangeUserPasswordRequest) Validate

func (r *ChangeUserPasswordRequest) Validate(rctx *RequestContext) error

Validate the request.

type CheckResourceAvailabilityRequest

type CheckResourceAvailabilityRequest struct {
	Type string `json:"type"`
	Name string `json:"name"`
}

CheckResourceAvailabilityRequest is an unauthenticated request that can tell if a given resource ID is available or not.

func (*CheckResourceAvailabilityRequest) Authorize

Authorize the request - this one requires no authentication.

func (*CheckResourceAvailabilityRequest) PopulateContext

func (r *CheckResourceAvailabilityRequest) PopulateContext(rctx *RequestContext) error

PopulateContext is a no-op for this type.

func (*CheckResourceAvailabilityRequest) Serve

func (r *CheckResourceAvailabilityRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

func (*CheckResourceAvailabilityRequest) Validate

Validate the request.

type CheckResourceAvailabilityResponse

type CheckResourceAvailabilityResponse struct {
	Available bool `json:"available"`
}

CheckResourceAvailabilityResponse is the response type for CheckResourceAvailabilityRequest.

type Config

type Config struct {
	Validation ValidationConfig `yaml:",inline"`

	Shards struct {
		Available map[string][]string `yaml:"available"`
		Allowed   map[string][]string `yaml:"allowed"`
	} `yaml:"shards"`

	SSO struct {
		PublicKeyFile string   `yaml:"public_key"`
		Domain        string   `yaml:"domain"`
		Service       string   `yaml:"service"`
		Groups        []string `yaml:"groups"`
		AdminGroup    string   `yaml:"admin_group"`
	} `yaml:"sso"`

	UserMetaDB *clientutil.BackendConfig `yaml:"user_meta_server"`

	AuthSocket                 string `yaml:"auth_socket"`
	UserAuthService            string `yaml:"user_auth_service"`
	AccountRecoveryAuthService string `yaml:"account_recovery_auth_service"`

	EnableOpportunisticEncryption bool `yaml:"auto_enable_encryption"`
	AuditLogsToSyslog             bool `yaml:"audit_syslog"`
}

Config holds the configuration for the AccountService.

type CreateApplicationSpecificPasswordRequest

type CreateApplicationSpecificPasswordRequest struct {
	PrivilegedRequestBase
	Service string `json:"service"`
	Notes   string `json:"notes"`
}

CreateApplicationSpecificPasswordRequest lets users create their own ASPs.

func (*CreateApplicationSpecificPasswordRequest) Serve

func (r *CreateApplicationSpecificPasswordRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

func (*CreateApplicationSpecificPasswordRequest) Validate

Validate the request.

type CreateApplicationSpecificPasswordResponse

type CreateApplicationSpecificPasswordResponse struct {
	Password string `json:"password"`
}

CreateApplicationSpecificPasswordResponse is the response type for CreateApplicationSpecificPasswordRequest.

func (*CreateApplicationSpecificPasswordResponse) Sanitize

Sanitize the response.

type CreateResourcesRequest

type CreateResourcesRequest struct {
	AdminRequestBase

	// Username the resources will belong to (optional).
	Username string `json:"username"`

	// Resources to create. All must either be global resources
	// (no user ownership), or belong to the same user.
	Resources []*Resource `json:"resources"`
}

CreateResourcesRequest lets administrators create one or more resources.

func (*CreateResourcesRequest) PopulateContext

func (r *CreateResourcesRequest) PopulateContext(rctx *RequestContext) error

PopulateContext extracts information from the request and stores it into the RequestContext.

func (*CreateResourcesRequest) Serve

func (r *CreateResourcesRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

func (*CreateResourcesRequest) Validate

func (r *CreateResourcesRequest) Validate(rctx *RequestContext) error

Validate the request.

type CreateResourcesResponse

type CreateResourcesResponse struct {
	// Resources that were created.
	Resources []*Resource `json:"resources"`
}

CreateResourcesResponse is the response type for CreateResourcesRequest.

type CreateUserRequest

type CreateUserRequest struct {
	AdminRequestBase

	// User to create, along with associated resources.
	User *User `json:"user"`
}

CreateUserRequest lets administrators create a new user along with the associated resources.

func (*CreateUserRequest) Serve

func (r *CreateUserRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request

func (*CreateUserRequest) Validate

func (r *CreateUserRequest) Validate(rctx *RequestContext) error

Validate the request.

type CreateUserResponse

type CreateUserResponse struct {
	User     *User  `json:"user"`
	Password string `json:"password"`
}

CreateUserResponse is the response type for CreateUserRequest.

func (*CreateUserResponse) Sanitize

func (r *CreateUserResponse) Sanitize()

Sanitize the response.

type Database

type Database struct {
	DBUser string `json:"db_user"`
}

Database resource attributes.

type DeleteApplicationSpecificPasswordRequest

type DeleteApplicationSpecificPasswordRequest struct {
	UserRequestBase
	AspID string `json:"asp_id"`
}

DeleteApplicationSpecificPasswordRequest deletes an application-specific password, identified by its unique ID.

func (*DeleteApplicationSpecificPasswordRequest) Serve

func (r *DeleteApplicationSpecificPasswordRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

type DeleteEmailAliasRequest

type DeleteEmailAliasRequest struct {
	ResourceRequestBase
	Addr string `json:"addr"`
}

DeleteEmailAliasRequest removes an alias from an email resource.

func (*DeleteEmailAliasRequest) Serve

func (r *DeleteEmailAliasRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

func (*DeleteEmailAliasRequest) Validate

func (r *DeleteEmailAliasRequest) Validate(rctx *RequestContext) error

Validate the request.

type DisableOTPRequest

type DisableOTPRequest struct {
	UserRequestBase
}

DisableOTPRequest disables two-factor authentication for a user.

func (*DisableOTPRequest) Serve

func (r *DisableOTPRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

type DisableUserRequest

type DisableUserRequest struct {
	UserRequestBase
}

DisableUserRequest lets administrators or users themselves disable an entire account and all associated resources.

func (*DisableUserRequest) Serve

func (r *DisableUserRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

type Email

type Email struct {
	Aliases    []string    `json:"aliases,omitempty"`
	Maildir    string      `json:"maildir"`
	QuotaLimit int         `json:"quota_limit"`
	OpenPGPKey *OpenPGPKey `json:"openpgp"`
}

Email resource attributes.

type EnableOTPRequest

type EnableOTPRequest struct {
	UserRequestBase
	TOTPSecret string `json:"totp_secret"`
}

EnableOTPRequest enables OTP-based two-factor authentication for a user. The caller can generate the TOTP secret itself if needed (useful for UX that confirms that the user is able to login first), or it can let the server generate a new secret by passing an empty totp_secret.

func (*EnableOTPRequest) Sanitize

func (r *EnableOTPRequest) Sanitize()

Sanitize the request.

func (*EnableOTPRequest) Serve

func (r *EnableOTPRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

func (*EnableOTPRequest) Validate

func (r *EnableOTPRequest) Validate(_ *RequestContext) error

Validate the request.

type EnableOTPResponse

type EnableOTPResponse struct {
	TOTPSecret string `json:"totp_secret"`
}

EnableOTPResponse is the response type for AccountService.EnableOTP().

func (*EnableOTPResponse) Sanitize

func (r *EnableOTPResponse) Sanitize()

Sanitize the response.

type FindResourceRequest

type FindResourceRequest struct {
	Type string
	Name string
}

FindResourceRequest contains parameters for searching a resource by name.

type GetResourceRequest

type GetResourceRequest struct {
	AdminResourceRequestBase
}

GetResourceRequest requests a specific resource.

func (*GetResourceRequest) Serve

func (r *GetResourceRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

type GetResourceResponse

type GetResourceResponse struct {
	Resource *Resource `json:"resource"`
	Owner    string    `json:"owner"`
}

GetResourceResponse is the response type for GetResourceRequest.

type GetUserRequest

type GetUserRequest struct {
	UserRequestBase

	// Whether to return an inactive user.
	IncludeInactive bool `json:"include_inactive"`
}

GetUserRequest retrieves a specific User.

func (*GetUserRequest) Serve

func (r *GetUserRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

type MailingList

type MailingList struct {
	Admins []string `json:"admins"`
	Public bool     `json:"public"`
}

MailingList resource attributes.

type MoveResourceRequest

type MoveResourceRequest struct {
	AdminResourceRequestBase
	Shard string `json:"shard"`
}

MoveResourceRequest is an administrative operation to move resources between shards. Resources that are part of a group are moved all at once regardless of which individual ResourceID is provided as long as it belongs to the group.

func (*MoveResourceRequest) Serve

func (r *MoveResourceRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

func (*MoveResourceRequest) Validate

func (r *MoveResourceRequest) Validate(rctx *RequestContext) error

Validate the request.

type MoveResourceResponse

type MoveResourceResponse struct {
	MovedIDs []string `json:"moved_ids"`
}

MoveResourceResponse is the response type for MoveResourceRequest.

type Newsletter

type Newsletter struct {
	Admins []string `json:"admins"`
}

Newsletter resource attributes. Like a list, but with fewer options.

type OpenPGPKey

type OpenPGPKey struct {
	// Key ID is hex-encoded (to avoid JS int64 misrepresentation as float).
	ID string `json:"key_id"`

	// Multiple hashes to support aliases matching identities in the key.
	Hashes []string `json:"wkd_hash"`

	Expiry int64 `json:"expiry"`

	// Key data being a []byte will force base64-encoding when serializing to JSON.
	Key []byte `json:"key"`
}

type PrivilegedRequestBase

type PrivilegedRequestBase struct {
	UserRequestBase
	CurPassword string `json:"cur_password"`
	RemoteAddr  string `json:"remote_addr"`
}

PrivilegedRequestBase extends RequestBase with the user password, for privileged endpoints.

func (*PrivilegedRequestBase) Authorize

func (r *PrivilegedRequestBase) Authorize(rctx *RequestContext) error

Authorize the request.

func (*PrivilegedRequestBase) Sanitize

func (r *PrivilegedRequestBase) Sanitize()

Sanitize the request.

type RawResource

type RawResource struct {
	Resource
	Owner string `json:"owner"`
}

A RawResource associates a Resource with its (optional) owner.

type RawUser

type RawUser struct {
	User

	// Password for local authentication of privileged actions
	// (these are encrypted!).
	Password         string
	RecoveryPassword string

	// Encryption keys need to change whenever there is a change
	// in authentication parameters, so keep them around.
	Keys encryptedKeyList
}

RawUser extends User with private information (as stored in the database) that we have a direct use for.

Its methods manipulate authentication-related data and enforce its consistency, so they may have side effects such as maintaining encryption keys up to date, or disabling secondary authentication mechanisms. In any case both the database and the underlying User object are kept in sync.

The separation between User and RawUser makes it easier to prevent private data from being served over the API.

type Request

type Request interface {
	PopulateContext(*RequestContext) error
	Validate(*RequestContext) error
	Authorize(*RequestContext) error
	Serve(*RequestContext) (interface{}, error)
}

Request is the generic interface for request types. Each request type defines its own handler, built of composable objects that define its behavior with respect to validation, authentication and execution.

type RequestBase

type RequestBase struct {
	SSO string `json:"sso"`

	// Optional comment, will end up in audit logs.
	Comment string `json:"comment,omitempty"`
}

RequestBase contains parameters shared by all authenticated request types.

func (*RequestBase) PopulateContext

func (r *RequestBase) PopulateContext(rctx *RequestContext) error

PopulateContext extracts information from the request and stores it into the RequestContext.

func (*RequestBase) Sanitize

func (r *RequestBase) Sanitize()

Sanitize the request.

func (*RequestBase) Validate

func (r *RequestBase) Validate(rctx *RequestContext) error

Validate the request.

type RequestContext

type RequestContext struct {
	// Link to the infra services.
	*AccountService

	// Request-scoped read-only values.
	Context context.Context
	//HTTPRequest *http.Request
	TX TX

	// Request-scoped read-write parameters.
	Auth     Auth
	User     *RawUser
	Resource *Resource
	Comment  string
}

The RequestContext holds a large number of request-scoped variables populated at different stages of the action workflow. This is simpler than managing lots of custom Context vars and the associated boilerplate, but it's still a bit of an antipattern due to the loss of generality.

type RequestError

type RequestError struct {
	// contains filtered or unexported fields
}

RequestError indicates an issue with validating the request.

func (*RequestError) Unwrap

func (e *RequestError) Unwrap() error

type ResetPasswordRequest

type ResetPasswordRequest struct {
	AdminUserRequestBase
}

ResetPasswordRequest is an admin operation to forcefully reset the password for an account. A new password will be randomly generated by the accountserver. The user will lose access to all stored email (because the encryption keys will be reset) and to 2FA.

func (*ResetPasswordRequest) Serve

func (r *ResetPasswordRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

type ResetPasswordResponse

type ResetPasswordResponse struct {
	Password string `json:"password"`
}

ResetPasswordResponse is the response type for ResetPasswordRequest.

func (*ResetPasswordResponse) Sanitize

func (r *ResetPasswordResponse) Sanitize()

Sanitize the response.

type ResetResourcePasswordRequest

type ResetResourcePasswordRequest struct {
	ResourceRequestBase
}

ResetResourcePasswordRequest will reset the password associated with a resource (if the resource type supports it). It will generate a random password and return it to the caller.

func (*ResetResourcePasswordRequest) Serve

func (r *ResetResourcePasswordRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

func (*ResetResourcePasswordRequest) Validate

Validate the request.

type ResetResourcePasswordResponse

type ResetResourcePasswordResponse struct {
	Password string `json:"password"`
}

ResetResourcePasswordResponse is the response type for ResetResourcePasswordRequest.

type Resource

type Resource struct {
	// ID is a unique primary key in the resources space, with a
	// path-like representation. It must make sense to the
	// database backend and be reversible (i.e. there must be a
	// bidirectional mapping between database objects and resource
	// IDs).
	ID ResourceID `json:"id"`

	// Resource type.
	Type string `json:"type"`

	// Name of the resource, used for display purposes.
	Name string `json:"name"`

	// Optional attribute for hierarchical resources.
	ParentID ResourceID `json:"parent_id,omitempty"`

	// Optional attribute for resources that have a status.
	Status string `json:"status,omitempty"`

	// Optional attributes for sharded resources.
	Shard         string `json:"shard,omitempty"`
	OriginalShard string `json:"original_shard,omitempty"`

	// Creation date (no time recorded) of the resource. Since our
	// database contains legacy values with different formats, the
	// field is left unparsed as a string.
	CreatedAt string `json:"created_at"`

	// Resources can be 'grouped' together, for various reasons
	// (display purposes, service integrity). All resources in the
	// same group should have the same Shard. Group names can be
	// arbitrary strings.
	Group string `json:"group,omitempty"`

	// Usage (for filesystem-based resources).
	UsageBytes int64 `json:"usage_bytes"`

	// Details about the specific type (only one of these can be
	// set, depending on the value of 'type').
	Email      *Email       `json:"email,omitempty"`
	List       *MailingList `json:"list,omitempty"`
	Newsletter *Newsletter  `json:"newsletter,omitempty"`
	Website    *Website     `json:"website,omitempty"`
	DAV        *WebDAV      `json:"dav,omitempty"`
	Database   *Database    `json:"database,omitempty"`
}

Resource represents a somewhat arbitrary resource, identified by a unique name/type combination (a.k.a. its ID). A resource contains some common properties related to sharding and state, plus type-specific attributes.

func (*Resource) Copy

func (r *Resource) Copy() *Resource

Copy the resource (makes a deep copy).

func (*Resource) String

func (r *Resource) String() string

String returns a short handle for the resource, useful for debugging.

type ResourceID

type ResourceID string

ResourceID is an opaque ID that uniquely identifies a resource in the backend database. These should normally not be visible to users.

func ParseResourceID

func ParseResourceID(s string) (ResourceID, error)

ParseResourceID parses a string representation of a ResourceID.

func (ResourceID) Empty

func (i ResourceID) Empty() bool

Empty returns true if the ResourceID has the nil value.

func (ResourceID) Equal

func (i ResourceID) Equal(other ResourceID) bool

Equal returns true if the two IDs are the same.

func (ResourceID) MarshalJSON

func (i ResourceID) MarshalJSON() ([]byte, error)

MarshalJSON serializes a resource ID to JSON.

func (ResourceID) String

func (i ResourceID) String() string

func (*ResourceID) UnmarshalJSON

func (i *ResourceID) UnmarshalJSON(data []byte) error

UnmarshalJSON deserializes a resource ID from JSON.

type ResourceRequestBase

type ResourceRequestBase struct {
	RequestBase
	ResourceID ResourceID `json:"resource_id"`
}

ResourceRequestBase is the base type for resource-level requests.

func (*ResourceRequestBase) Authorize

func (r *ResourceRequestBase) Authorize(rctx *RequestContext) error

Authorize the request.

func (*ResourceRequestBase) PopulateContext

func (r *ResourceRequestBase) PopulateContext(rctx *RequestContext) error

PopulateContext extracts information from the request and stores it into the RequestContext.

type ResourceValidatorFunc

type ResourceValidatorFunc func(*RequestContext, *Resource, *User, bool) error

ResourceValidatorFunc is a composite type validator that checks various fields in a Resource, depending on its type.

type SearchResourceRequest

type SearchResourceRequest struct {
	AdminRequestBase

	Pattern string `json:"pattern"`
	Limit   int    `json:"limit"`
}

SearchResourceRequest searches for resources matching a pattern.

func (*SearchResourceRequest) Serve

func (r *SearchResourceRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

func (*SearchResourceRequest) Validate

func (r *SearchResourceRequest) Validate(rctx *RequestContext) error

Validate the request.

type SearchResourceResponse

type SearchResourceResponse struct {
	Results []*RawResource `json:"results"`
}

SearchResourceResponse is the response type for SearchResourceRequest.

type SearchUserRequest

type SearchUserRequest struct {
	AdminRequestBase

	Pattern string `json:"pattern"`
	Limit   int    `json:"limit"`
}

SearchUserRequest searches the database for users with names matching a given pattern. The actual pattern semantics are backend-specific (for LDAP, this is a prefix string search).

func (*SearchUserRequest) Serve

func (r *SearchUserRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

func (*SearchUserRequest) Validate

func (r *SearchUserRequest) Validate(rctx *RequestContext) error

Validate the request.

type SearchUserResponse

type SearchUserResponse struct {
	Usernames []string `json:"usernames"`
}

SearchUserResponse is the response type for SearchUserRequest.

type SetAccountRecoveryHintRequest

type SetAccountRecoveryHintRequest struct {
	PrivilegedRequestBase
	Hint     string `json:"recovery_hint"`
	Response string `json:"recovery_response"`
}

SetAccountRecoveryHintRequest lets users set the password recovery hint and expected response (secondary password).

func (*SetAccountRecoveryHintRequest) Sanitize

func (r *SetAccountRecoveryHintRequest) Sanitize()

Sanitize the request.

func (*SetAccountRecoveryHintRequest) Serve

func (r *SetAccountRecoveryHintRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

func (*SetAccountRecoveryHintRequest) Validate

Validate the request.

type SetOpenPGPKeyRequest

type SetOpenPGPKeyRequest struct {
	ResourceRequestBase

	// Set to empty value to delete key.
	OpenPGPKey []byte `json:"openpgp_key"`
	// contains filtered or unexported fields
}

SetOpenPGPKeyRequest allows users to set their own OpenPGP keys.

func (*SetOpenPGPKeyRequest) Serve

func (r *SetOpenPGPKeyRequest) Serve(rctx *RequestContext) (interface{}, error)

func (*SetOpenPGPKeyRequest) Validate

func (r *SetOpenPGPKeyRequest) Validate(rctx *RequestContext) error

type SetResourceStatusRequest

type SetResourceStatusRequest struct {
	ResourceRequestBase

	Status string `json:"status"`
}

SetResourceStatusRequest modifies the status of a resource belonging to the user (admin-only).

func (*SetResourceStatusRequest) Authorize

func (r *SetResourceStatusRequest) Authorize(rctx *RequestContext) error

Authorize self only when status == 'inactive'.

func (*SetResourceStatusRequest) Serve

func (r *SetResourceStatusRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

func (*SetResourceStatusRequest) Validate

func (r *SetResourceStatusRequest) Validate(rctx *RequestContext) error

Validate the request.

type TX

type TX interface {
	Commit(context.Context) error

	GetResource(context.Context, ResourceID) (*RawResource, error)
	UpdateResource(context.Context, *Resource) error
	CreateResources(context.Context, *User, []*Resource) ([]*Resource, error)
	SetResourcePassword(context.Context, *Resource, string) error
	FindResource(context.Context, FindResourceRequest) (*RawResource, error)
	HasAnyResource(context.Context, []FindResourceRequest) (bool, error)

	GetUser(context.Context, string) (*RawUser, error)
	UpdateUser(context.Context, *User) error
	CreateUser(context.Context, *User) (*User, error)
	SetUserPassword(context.Context, *User, string) error
	SetAccountRecoveryHint(context.Context, *User, string, string) error
	DeleteAccountRecoveryHint(context.Context, *User) error
	SetUserEncryptionKeys(context.Context, *User, []*ct.EncryptedKey) error
	SetUserEncryptionPublicKey(context.Context, *User, []byte) error
	SetApplicationSpecificPassword(context.Context, *User, *ct.AppSpecificPassword, string) error
	DeleteApplicationSpecificPassword(context.Context, *User, string) error
	SetUserTOTPSecret(context.Context, *User, string) error
	DeleteUserTOTPSecret(context.Context, *User) error

	// Lightweight user search (backend-specific pattern).
	// Returns list of matching usernames.
	SearchUser(context.Context, string, int) ([]string, error)

	// Resource search (backend-specific pattern).
	SearchResource(context.Context, string, int) ([]*RawResource, error)

	// Resource ACL check (does not necessarily hit the database).
	CanAccessResource(context.Context, string, *Resource) bool

	// Return the next (or any, really) available user ID.
	NextUID(context.Context) (int, error)
}

TX represents a single transaction with the backend and offers a high-level data management abstraction.

All methods share similar semantics: Get methods will return nil if the requested object is not found, and only return an error in case of trouble reaching the backend itself.

The backend enforces strict public/private data separation by having Get methods return public objects (as defined in types.go), and using specialized methods to modify the private (authentication-related) attributes.

The API passes around the full User object, where a simple username would usually suffice, because it needs to synchronize things between resources: this is primarily due to the coupling between account and email resource.

We might add more sophisticated resource query methods later, as admin-level functionality.

type UpdateUserRequest

type UpdateUserRequest struct {
	UserRequestBase

	Lang    string `json:"lang,omitempty"`
	SetLang bool   `json:"set_lang"`

	U2FRegistrations    []*ct.U2FRegistration `json:"u2f_registrations,omitempty"`
	SetU2FRegistrations bool                  `json:"set_u2f_registrations"`
}

UpdateUserRequest allows the caller to update a (very limited) selected set of fields on a User object. It is a catch-all function for very simple changes that don't justify their own specialized method. Fields are associated with a "set_field" attribute to allow for selective updates.

func (*UpdateUserRequest) Serve

func (r *UpdateUserRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

func (*UpdateUserRequest) Validate

func (r *UpdateUserRequest) Validate(rctx *RequestContext) error

Validate the request.

type User

type User struct {
	// Name of the user. Also its email.
	Name string `json:"name"`

	// Preferred language.
	Lang string `json:"lang"`

	// UNIX user id.
	UID int `json:"uid"`

	// Timestamp of last password change. This is serialized as a
	// RFC3339 string in JSON.
	LastPasswordChangeStamp time.Time `json:"last_password_change_stamp"`

	// User status.
	Status string `json:"status"`

	// Shard for temporary resources (must match the email resources).
	Shard string `json:"shard"`

	// Has2FA is true if the user has a second-factor authentication
	// mechanism properly set up. In practice, this is the case if either
	// HasOTP is true, or len(U2FRegistrations) > 0.
	Has2FA bool `json:"has_2fa"`

	// HasOTP is true if TOTP is set up.
	HasOTP bool `json:"has_otp"`

	// HasEncryptionKeys is true if encryption keys are properly set up for
	// this user.
	HasEncryptionKeys bool `json:"has_encryption_keys"`

	// The recovery hint for this account (empty if unset).
	AccountRecoveryHint string `json:"account_recovery_hint"`

	// List of application-specific passwords (metadata only).
	AppSpecificPasswords []*ct.AppSpecificPassword `json:"app_specific_passwords,omitempty"`

	// List of U2F registrations.
	U2FRegistrations []*ct.U2FRegistration `json:"u2f_registrations,omitempty"`

	// All the resources owned by this user.
	Resources []*Resource `json:"resources,omitempty"`
}

User information, public: includes data *about* credentials, but not the credentials themselves. Every user has a unique identifier, which may be an email address.

func (*User) AllEmailAddrs

func (u *User) AllEmailAddrs() []string

AllEmailAddrs is a convenience function that returns all (non-inactive) email addresses for this User.

func (*User) GetResourceByID

func (u *User) GetResourceByID(id ResourceID) *Resource

GetResourceByID returns the resource with the specified ID, or nil if not found.

func (*User) GetResourcesByGroup

func (u *User) GetResourcesByGroup(group string) []*Resource

GetResourcesByGroup returns all resources belonging to the specified group.

func (*User) GetResourcesByType

func (u *User) GetResourcesByType(resourceType string) []*Resource

GetResourcesByType returns all resources with the specified type.

func (*User) GetSingleResourceByType

func (u *User) GetSingleResourceByType(resourceType string) *Resource

GetSingleResourceByType returns a single resource of the specified type. If there are none, returns nil.

type UserRequestBase

type UserRequestBase struct {
	RequestBase
	Username string `json:"username"`
}

UserRequestBase is a generic request about a specific user.

func (*UserRequestBase) Authorize

func (r *UserRequestBase) Authorize(rctx *RequestContext) error

Authorize the request.

func (*UserRequestBase) PopulateContext

func (r *UserRequestBase) PopulateContext(rctx *RequestContext) error

PopulateContext extracts information from the request and stores it into the RequestContext.

func (*UserRequestBase) Validate

func (r *UserRequestBase) Validate(rctx *RequestContext) error

Validate the request.

type UserValidatorFunc

type UserValidatorFunc func(*RequestContext, *User, bool) error

UserValidatorFunc is a compound validator for User objects.

type ValidationConfig

type ValidationConfig struct {
	ForbiddenUsernames     []string            `yaml:"forbidden_usernames"`
	ForbiddenUsernamesFile string              `yaml:"forbidden_usernames_file"`
	ForbiddenPasswords     []string            `yaml:"forbidden_passwords"`
	ForbiddenPasswordsFile string              `yaml:"forbidden_passwords_file"`
	ForbiddenDomains       []string            `yaml:"forbidden_domains"`
	ForbiddenDomainsFile   string              `yaml:"forbidden_domains_file"`
	AvailableDomains       map[string][]string `yaml:"available_domains"`
	WebsiteRootDir         string              `yaml:"website_root_dir"`
	MinPasswordLen         int                 `yaml:"min_password_len"`
	MaxPasswordLen         int                 `yaml:"max_password_len"`
	MinUsernameLen         int                 `yaml:"min_username_len"`
	MaxUsernameLen         int                 `yaml:"max_username_len"`
	MinUID                 int                 `yaml:"min_backend_uid"`
	MaxUID                 int                 `yaml:"max_backend_uid"`
	// contains filtered or unexported fields
}

ValidationConfig specifies a large number of validation-related configurable parameters.

type ValidationError

type ValidationError struct {
	// contains filtered or unexported fields
}

ValidationError holds field-specific information that can be serialized as JSON if desired.

func (*ValidationError) Error

func (v *ValidationError) Error() string

func (*ValidationError) JSON

func (v *ValidationError) JSON() []byte

type ValidatorFunc

type ValidatorFunc func(*RequestContext, string) error

ValidatorFunc is the generic interface for unstructured data field (string) validators.

type VulnInfo

type VulnInfo struct {
	Name       string    `json:"name"`
	Path       string    `json:"path"`
	DetectedAt time.Time `json:"detected_at"`
}

VulnInfo stores information about vulnerabilities detected by our automated scanners.

type WebDAV

type WebDAV struct {
	UID     int    `json:"uid"`
	Homedir string `json:"homedir"`
}

WebDAV represents a hosting account.

type WebSetPHPVersionRequest

type WebSetPHPVersionRequest struct {
	ResourceRequestBase
	PHPVersion string `json:"php_version"`
}

WebSetPHPVersion sets the PHP version for a website.

func (*WebSetPHPVersionRequest) Serve

func (r *WebSetPHPVersionRequest) Serve(rctx *RequestContext) (interface{}, error)

Serve the request.

func (*WebSetPHPVersionRequest) Validate

func (r *WebSetPHPVersionRequest) Validate(rctx *RequestContext) error

Validate the request.

type Website

type Website struct {
	URL          string            `json:"url,omitempty"`
	UID          int               `json:"uid"`
	ParentDomain string            `json:"parent_domain,omitempty"`
	AcceptMail   bool              `json:"accept_mail"`
	Options      []string          `json:"options,omitempty"`
	Categories   []string          `json:"categories,omitempty"`
	Description  map[string]string `json:"description,omitempty"`
	DocumentRoot string            `json:"document_root"`
	StatsID      int               `json:"stats_id"`

	CMSInfo []*App `json:"cms_info,omitempty"`
}

Website resource attributes. Used for both normal websites (a.k.a. "subsites" of some parent domain) and domains.

Directories

Path Synopsis
backend
cmd
Package integrationtest runs a test suite on the accountserver with a real LDAP database, using the HTTP API.
Package integrationtest runs a test suite on the accountserver with a real LDAP database, using the HTTP API.

Jump to

Keyboard shortcuts

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