carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1// The backend seam (spec §5): everything the console needs a backend to do,
2// and nothing an app ever calls.
3//
4// Every method is idempotent — "already exists" and "already gone" are both
5// successes — because every walk that drives one must be re-runnable after a
6// half-failure. That is not a style preference; it is the only reason a
7// crashed provisioning run can be repaired by running it again rather than
8// by hand.
9package mail
10
11import (
12 "context"
13 "errors"
14)
15
16// Identity is what EnsureIdentity answers: the ARN a policy names and the
17// DKIM tokens whose CNAMEs must be published. The tokens are STABLE for the
18// life of the identity — re-ensuring returns the same three, because fresh
19// ones would orphan DNS already published (and, for a custom domain, DNS
20// somebody else published by hand).
21type Identity struct {
22 ARN string
23 DKIMTokens []string
24 // Created is true when this call made the identity, false when it was
25 // already there. The walk records it, because an identity CARLOS did not
26 // create is not CARLOS's to delete (discussion #216).
27 Created bool
28}
29
30// ErrIdentityExists is EnsureIdentity's refusal, with adopt false, of a
31// domain that already has an identity. Nothing about the identity has been
32// read or changed when it is returned.
33var ErrIdentityExists = errors.New("mail: identity already exists")
34
35// Status is SES's own verdict on an identity. All three must read
36// "SUCCESS" before the identity is usable; a domain that is verified for
37// sending but whose MAIL FROM has not converged still sends, from the
38// wrong envelope domain, and fails DMARC alignment.
39type Status struct {
40 DKIM string
41 MailFrom string
42 Sending string
43 // ConfigSet is the configuration set SES currently has BOUND to this
44 // identity, empty for none. It is read here because
45 // GetEmailIdentity already returns it and this call already happens on
46 // every verify — which makes drift detection free (issue #272).
47 //
48 // The alternative was reconciling on every status read, which would put
49 // a GetEmailIdentity per domain on a path that today answers from the
50 // bucket alone, and would make a working read fail whenever AWS is
51 // unreachable. Reading it where the call is already being made costs
52 // nothing and catches the same drift a beat later.
53 ConfigSet string
54}
55
56// Verified reports whether every axis has converged.
57func (s Status) Verified() bool {
58 return s.DKIM == "SUCCESS" && s.MailFrom == "SUCCESS" && s.Sending == "SUCCESS"
59}
60
61type Provisioner interface {
62 // EnsureTenant creates the account's SES tenant if absent. Additive:
63 // a backend without tenant support returns nil and the walks still
64 // isolate correctly, because isolation is the IAM policy (spec §4).
65 EnsureTenant(ctx context.Context, name string) error
66 // EnsureConfigSet creates the event destination that feeds bounce and
67 // complaint ingestion (spec §9).
68 EnsureConfigSet(ctx context.Context, region, name, snsTopicARN string) error
69 // SetIdentityConfigSet binds an identity to the configuration set that
70 // carries SES's event destination (spec §9; C1, review follow-up).
71 // EnsureConfigSet alone creates the SET — it does not touch any
72 // identity. SES only emits events (send, bounce, complaint) for a
73 // message sent WITH a configuration set, so an identity that never
74 // gets this call generates NO events, ever, no matter how many
75 // messages it sends: every count stays zero forever, the daily cap
76 // never enforces, and the automatic abuse pause never fires. Called
77 // once per (region, domain) — idempotent, like every verb here.
78 SetIdentityConfigSet(ctx context.Context, region, domain, configSet string) error
79
80 // AssociateTenantResource associates an identity, a configuration set or
81 // a template with a tenant. Associating does not modify the resource and
82 // one resource may belong to many tenants, so this is safe to call on an
83 // identity another product owns. It has exactly one side effect on the
84 // owner: an identity cannot be deleted while an association exists,
85 // which is why DisassociateTenantResource has to run before
86 // DeleteIdentity. Idempotent, like every verb here.
87 AssociateTenantResource(ctx context.Context, tenant, resourceARN string) error
88 // DisassociateTenantResource removes one association. "Already gone" is
89 // success.
90 DisassociateTenantResource(ctx context.Context, tenant, resourceARN string) error
91
92 // EnsureIdentity creates the domain identity, enables Easy DKIM and
93 // sets the custom MAIL FROM subdomain, all idempotently.
94 //
95 // adopt says whether an identity that already exists may be taken over:
96 // its DKIM tokens read back and its MAIL FROM rewritten. With adopt
97 // false, an existing identity is refused with ErrIdentityExists and left
98 // exactly as it was — the walk passes false unless the identity is one
99 // this account already holds or has proved it controls (#216).
100 EnsureIdentity(ctx context.Context, region, domain, mailFrom string, adopt bool) (Identity, error)
101 IdentityStatus(ctx context.Context, region, domain string) (Status, error)
102 DeleteIdentity(ctx context.Context, region, domain string) error
103
104 // EnsureUser creates the principal if absent and writes its inline
105 // policy wholesale. It MUST refuse an empty grant rather than create a
106 // principal with no policy.
107 EnsureUser(ctx context.Context, name, path string, identityARNs, configSetARNs, domains []string) (arn string, err error)
108 MintKey(ctx context.Context, user string) (id, secret string, err error)
109 // SetKeyActive is the pause (spec §9): instant, reversible, and it
110 // needs no policy rewrite.
111 SetKeyActive(ctx context.Context, user, keyID string, active bool) error
112 DeleteKey(ctx context.Context, user, keyID string) error
113 // ReapKeys deletes every access key of user NOT named in keep. A key
114 // minted before a failed delivery is recorded nowhere, so only
115 // list-and-delete can find it.
116 ReapKeys(ctx context.Context, user string, keep []string) error
117 DeleteUser(ctx context.Context, user string) error
118}
119