Documentation
¶
Overview ¶
Package console is the GitHub module's half of the console: the handlers for connecting an organisation, the Apps view, the catalogue and runner Apps, the link App and people's links, moved out of the shared console so that the process which serves the console holds no App private key, no webhook secret and no connection record (docs/decisions/0071, v1.76 D-v176-1 b).
The console authenticates a person, takes a snapshot of who they are and calls these through the module's server (Register); the handlers act on that snapshot with the rules in internal/consoleauthz. A provider callback is still an HTTP route on the issuer, which verifies the signed state and the browser's cookie and forwards the one-time code to the module's `complete_*` method; the module never sees a session key.
Index ¶
- Constants
- func Register(s *modcall.Server, svc *Service)
- func WithCaller(ctx context.Context, c consolecall.Caller) context.Context
- type Catalogue
- type Connections
- type Deps
- type MintRing
- type Service
- func (c *Service) BeginGitHubAppConnect(ctx context.Context, r consolewire.BeginAppConnectRequest) (consolewire.Begun, error)
- func (c *Service) BeginGitHubCatalogueAppConnect(ctx context.Context, id string) (consolewire.Begun, error)
- func (c *Service) BeginGitHubConnect(ctx context.Context, org, requestedOwner string) (consolewire.Begun, error)
- func (c *Service) BeginGitHubLinkAppConnect(ctx context.Context, owner string) (consolewire.Begun, error)
- func (c *Service) BeginGitHubRunnerAppConnect(ctx context.Context, org, tier string) (consolewire.Begun, error)
- func (c *Service) ChangeGitHubOrganisationOwner(ctx context.Context, ...) (*connect.Response[directoryrosterv1.ChangeGitHubOrganisationOwnerResponse], ...)
- func (c *Service) CheckGitHubApp(ctx context.Context, ...) (*connect.Response[directoryrosterv1.CheckGitHubAppResponse], error)
- func (c *Service) CheckGitHubCatalogueApp(ctx context.Context, ...) (*connect.Response[directoryrosterv1.CheckGitHubCatalogueAppResponse], error)
- func (c *Service) CompleteGitHubCatalogueCreate(ctx context.Context, f consolewire.Flow) (consolewire.Continued, error)
- func (c *Service) CompleteGitHubCatalogueSetup(ctx context.Context, f consolewire.Flow) (consolewire.Done, error)
- func (c *Service) CompleteGitHubCreate(ctx context.Context, f consolewire.Flow) (consolewire.Continued, error)
- func (c *Service) CompleteGitHubInstall(ctx context.Context, f consolewire.Flow) (consolewire.Done, error)
- func (c *Service) CompleteGitHubLinkApp(ctx context.Context, f consolewire.Flow) (consolewire.Done, error)
- func (c *Service) CompleteGitHubRunnerCreate(ctx context.Context, f consolewire.Flow) (consolewire.Continued, error)
- func (c *Service) CompleteGitHubRunnerSetup(ctx context.Context, f consolewire.Flow) (consolewire.Done, error)
- func (c *Service) ConfirmGitHubRemovals(ctx context.Context, ...) (*connect.Response[directoryrosterv1.ConfirmGitHubRemovalsResponse], error)
- func (c *Service) DisconnectGitHubApp(ctx context.Context, ...) (*connect.Response[directoryrosterv1.DisconnectGitHubAppResponse], error)
- func (c *Service) DisconnectGitHubCatalogueApp(ctx context.Context, ...) (*connect.Response[directoryrosterv1.DisconnectGitHubCatalogueAppResponse], ...)
- func (c *Service) DisconnectGitHubLinkApp(ctx context.Context, ...) (*connect.Response[directoryrosterv1.DisconnectGitHubLinkAppResponse], error)
- func (c *Service) DisconnectGitHubOrganisation(ctx context.Context, ...) (*connect.Response[directoryrosterv1.DisconnectGitHubOrganisationResponse], ...)
- func (c *Service) DisconnectGitHubRunnerApp(ctx context.Context, ...) (*connect.Response[directoryrosterv1.DisconnectGitHubRunnerAppResponse], error)
- func (c *Service) GetGitHubApp(ctx context.Context, ...) (*connect.Response[directoryrosterv1.GetGitHubAppResponse], error)
- func (c *Service) GetGitHubStatus(ctx context.Context, ...) (*connect.Response[directoryrosterv1.GetGitHubStatusResponse], error)
- func (c *Service) GitHubLinkComplete(ctx context.Context, r consolewire.LinkComplete) (consolewire.Linked, error)
- func (c *Service) GitHubLinkPage(ctx context.Context) (consolewire.LinkPage, error)
- func (c *Service) ImportGitHubLinks(ctx context.Context, ...) (*connect.Response[directoryrosterv1.ImportGitHubLinksResponse], error)
- func (c *Service) ListGitHubAppTokens(ctx context.Context, ...) (*connect.Response[directoryrosterv1.ListGitHubAppTokensResponse], error)
- func (c *Service) ListGitHubApps(ctx context.Context, ...) (*connect.Response[directoryrosterv1.ListGitHubAppsResponse], error)
- func (c *Service) RequestGitHubPass(ctx context.Context, ...) (*connect.Response[directoryrosterv1.RequestGitHubPassResponse], error)
- func (c *Service) RotateGitHubAppWebhook(ctx context.Context, ...) (*connect.Response[directoryrosterv1.RotateGitHubAppWebhookResponse], error)
- func (c *Service) SetClock(now func() time.Time)
- func (c *Service) SetDeps(d Deps)
Constants ¶
const FlowDeadline = 25 * time.Second
FlowDeadline bounds a call that finishes a provider's flow. The code the provider returned is single use, so the call is never retried and is given longer than an ordinary one: a lost answer after the exchange loses the key.
Variables ¶
This section is empty.
Functions ¶
func Register ¶
Register makes the module answer the console's methods on s, to the console class alone (consolecall.Handle allows no other).
func WithCaller ¶
WithCaller is ctx with the snapshot of who the console authorized.
Types ¶
type Connections ¶
type Connections = connection.Store
Connections is where connected organisations are kept: a record the console shows, a credential only the controller acts with.
type Deps ¶
type Deps struct {
// Log is where the module logs what is not an audit record; nil is the
// default logger.
Log *slog.Logger
// Audit records what a person did through the console, as the actor of the
// caller snapshot. Nil records nothing.
Audit audit.Recorder
// Trigger is told which organisation a write concerns, so the module's own
// tick runs soon (docs/decisions/0029): the self-tick after a write. Nil
// notifies nobody.
Trigger port.Trigger
// Policy is the policy in force: which organisations it binds.
Policy func() *policy.Set
// PublicURL is where a browser reaches the console, and RootURL the origin
// the bootstrap callbacks are at; empty falls back to PublicURL.
PublicURL, RootURL string
// Orgs is where connected organisations are kept. Nil is a deployment
// keeping no state in Kubernetes, which can connect none.
Orgs Connections
// OrgAppName is the name the organisation's App is created with, empty for
// the default (<org>-access-roster). Nil is all defaults.
OrgAppName func(org string) string
// Reports is where the GitHub controller's reports are read. Nil is a
// deployment keeping no state in Kubernetes, which has nowhere for a
// controller to report to.
Reports status.ReportReader
// Confirmations is where operators confirm removal sets over the limit. Nil
// confirms nothing.
Confirmations connection.ConfirmationStore
// LinkApp is where the link App is kept, and Links where people's links
// are. Nil is a deployment where nobody can link an account.
LinkApp link.AppStore
Links link.Links
// LinkAppName is the name the link App is created with, empty for the
// default (<owner>-access-roster-link).
LinkAppName string
// RunnerApps is where runner Apps are kept, and RunnerTiers the tiers an
// operator may create an App for. Empty tiers keep no runner Apps.
RunnerApps runnerapp.Store
RunnerTiers []string
// CatalogueApps is where catalogue Apps are kept. Nil is a deployment
// keeping no state in Kubernetes, which can create none.
CatalogueApps catalogueapp.Store
// Mints is the last installation tokens asked of each App, kept by the half
// of the service that mints them. Nil is a deployment that mints none, or
// whose mint is another process.
Mints *mints.Ring
// WebhookHTTP sends the signed ping a webhook rotation checks a new target
// with. Nil is a client with a short timeout that follows no redirect.
WebhookHTTP *http.Client
// WebhookPingWindow is how long a rotation waits for the new target to
// accept the new secret, and WebhookPingEvery how often it asks. Zero is
// two minutes and five seconds.
WebhookPingWindow, WebhookPingEvery time.Duration
// Catalogue is every App the deployment declares. Nil declares none.
Catalogue *catalogue.Catalogue
// HTTP makes the calls to GitHub that connecting and disconnecting need.
// Nil is a client with a short timeout.
HTTP *http.Client
}
Deps is what the module's console handlers act on.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service is the module's console handlers.
func (*Service) BeginGitHubAppConnect ¶
func (c *Service) BeginGitHubAppConnect(ctx context.Context, r consolewire.BeginAppConnectRequest) (consolewire.Begun, error)
BeginGitHubAppConnect starts creating any App, or finishing installing one created before.
It hands the work to the same code the per-kind calls run, so the signed state, the cookie it is pinned to and the callback that finishes the flow are the ones that kind of App has always used: a browser part-way through a flow is unaffected by which call started it.
func (*Service) BeginGitHubCatalogueAppConnect ¶
func (c *Service) BeginGitHubCatalogueAppConnect(ctx context.Context, id string) (consolewire.Begun, error)
BeginGitHubCatalogueAppConnect starts creating a catalogue App, or finishing installing one created before. The URL carries the state placeholder the issuer signs.
func (*Service) BeginGitHubConnect ¶
func (c *Service) BeginGitHubConnect(ctx context.Context, org, requestedOwner string) (consolewire.Begun, error)
BeginGitHubConnect starts connecting an organisation.
Only one the policy binds. Connecting an organisation nothing will manage installs an App with members:write for no reason, and the likeliest cause is a typo in the login — which GitHub would answer with a 404 on its create page, after the operator has left this one.
The URL it returns carries consolewire.StatePlaceholder where the signed state belongs: the issuer signs it, with the caller as its actor.
func (*Service) BeginGitHubLinkAppConnect ¶
func (c *Service) BeginGitHubLinkAppConnect(ctx context.Context, owner string) (consolewire.Begun, error)
BeginGitHubLinkAppConnect starts creating the link App. The URL carries the state placeholder the issuer signs.
func (*Service) BeginGitHubRunnerAppConnect ¶
func (c *Service) BeginGitHubRunnerAppConnect(ctx context.Context, org, tier string) (consolewire.Begun, error)
BeginGitHubRunnerAppConnect starts creating a runner App, or finishing installing one created before.
Only in an organisation the policy binds, for the same reason as an organisation's App: a typo in the login would otherwise meet GitHub's 404 after the operator has left this page.
func (*Service) ChangeGitHubOrganisationOwner ¶
func (c *Service) ChangeGitHubOrganisationOwner( ctx context.Context, req *connect.Request[directoryrosterv1.ChangeGitHubOrganisationOwnerRequest], ) (*connect.Response[directoryrosterv1.ChangeGitHubOrganisationOwnerResponse], error)
ChangeGitHubOrganisationOwner changes the directory recorded as a connected organisation's owner, or removes it. The installation-wide operator alone: an owner that could hand its organisation to another, or take one, would make "who may operate this" a thing its operators decide.
func (*Service) CheckGitHubApp ¶
func (c *Service) CheckGitHubApp( ctx context.Context, req *connect.Request[directoryrosterv1.CheckGitHubAppRequest], ) (*connect.Response[directoryrosterv1.CheckGitHubAppResponse], error)
CheckGitHubApp asks GitHub again about one App, whichever kind it is.
func (*Service) CheckGitHubCatalogueApp ¶
func (c *Service) CheckGitHubCatalogueApp( ctx context.Context, req *connect.Request[directoryrosterv1.CheckGitHubCatalogueAppRequest], ) (*connect.Response[directoryrosterv1.CheckGitHubCatalogueAppResponse], error)
CheckGitHubCatalogueApp asks GitHub again about one App.
func (*Service) CompleteGitHubCatalogueCreate ¶
func (c *Service) CompleteGitHubCatalogueCreate(ctx context.Context, f consolewire.Flow) (consolewire.Continued, error)
CompleteGitHubCatalogueCreate is where GitHub sends the owner after creating a catalogue App, with a one-time code for its key. The key is kept and the owner is sent straight on to Install.
func (*Service) CompleteGitHubCatalogueSetup ¶
func (c *Service) CompleteGitHubCatalogueSetup(ctx context.Context, f consolewire.Flow) (consolewire.Done, error)
CompleteGitHubCatalogueSetup is where GitHub sends the owner after installing a catalogue App. What is kept is where GitHub says the App is installed, asked as the App.
func (*Service) CompleteGitHubCreate ¶
func (c *Service) CompleteGitHubCreate(ctx context.Context, f consolewire.Flow) (consolewire.Continued, error)
CompleteGitHubCreate is where GitHub sends the owner after Create, with a one-time code for the App's key. The key is kept and the owner is sent straight on to Install, under a fresh state: the one that brought them here is spent. The code is single use, so a lost answer after the exchange loses the key, as it always did: the call is not retried.
func (*Service) CompleteGitHubInstall ¶
func (c *Service) CompleteGitHubInstall(ctx context.Context, f consolewire.Flow) (consolewire.Done, error)
CompleteGitHubInstall is where GitHub sends the owner after Install. The installation id in the query is a browser's word for it; what is kept is what GitHub says, asked as the App.
func (*Service) CompleteGitHubLinkApp ¶
func (c *Service) CompleteGitHubLinkApp(ctx context.Context, f consolewire.Flow) (consolewire.Done, error)
CompleteGitHubLinkApp is where GitHub sends the owner after creating the link App. There is nothing to install: the App is used by being authorized, never by being installed.
func (*Service) CompleteGitHubRunnerCreate ¶
func (c *Service) CompleteGitHubRunnerCreate(ctx context.Context, f consolewire.Flow) (consolewire.Continued, error)
CompleteGitHubRunnerCreate is where GitHub sends the owner after creating a runner App, with a one-time code for its key. The key is kept and the owner is sent straight on to Install.
func (*Service) CompleteGitHubRunnerSetup ¶
func (c *Service) CompleteGitHubRunnerSetup(ctx context.Context, f consolewire.Flow) (consolewire.Done, error)
CompleteGitHubRunnerSetup is where GitHub sends the owner after installing a runner App. What is kept is where GitHub says the App is installed, asked as the App.
func (*Service) ConfirmGitHubRemovals ¶
func (c *Service) ConfirmGitHubRemovals( ctx context.Context, req *connect.Request[directoryrosterv1.ConfirmGitHubRemovalsRequest], ) (*connect.Response[directoryrosterv1.ConfirmGitHubRemovalsResponse], error)
ConfirmGitHubRemovals lets exactly the removal set the operator saw go ahead. The fingerprint must be the one the organisation's latest report shows: confirming a set that has since changed would confirm people nobody looked at.
func (*Service) DisconnectGitHubApp ¶
func (c *Service) DisconnectGitHubApp( ctx context.Context, req *connect.Request[directoryrosterv1.DisconnectGitHubAppRequest], ) (*connect.Response[directoryrosterv1.DisconnectGitHubAppResponse], error)
DisconnectGitHubApp uninstalls any App, then forgets it.
func (*Service) DisconnectGitHubCatalogueApp ¶
func (c *Service) DisconnectGitHubCatalogueApp( ctx context.Context, req *connect.Request[directoryrosterv1.DisconnectGitHubCatalogueAppRequest], ) (*connect.Response[directoryrosterv1.DisconnectGitHubCatalogueAppResponse], error)
DisconnectGitHubCatalogueApp uninstalls a catalogue App, then forgets it. A failed uninstall still forgets, and says what is left to do by hand. The App stays on GitHub: the API cannot delete one.
func (*Service) DisconnectGitHubLinkApp ¶
func (c *Service) DisconnectGitHubLinkApp( ctx context.Context, _ *connect.Request[directoryrosterv1.DisconnectGitHubLinkAppRequest], ) (*connect.Response[directoryrosterv1.DisconnectGitHubLinkAppResponse], error)
DisconnectGitHubLinkApp forgets the link App, and makes every link made with it unverifiable — before forgetting, so that no pass can ever check one of those tokens with some later App's credentials and read GitHub's "unknown token" as a revocation.
func (*Service) DisconnectGitHubOrganisation ¶
func (c *Service) DisconnectGitHubOrganisation( ctx context.Context, req *connect.Request[directoryrosterv1.DisconnectGitHubOrganisationRequest], ) (*connect.Response[directoryrosterv1.DisconnectGitHubOrganisationResponse], error)
DisconnectGitHubOrganisation revokes, then forgets.
The revoke is uninstalling: GitHub's API cannot delete an App, so the registration stays for its owner to delete, and the response says where. Once uninstalled and forgotten nothing can act through it — nobody else ever held its key. A failed uninstall still forgets: the operator asked for this organisation to stop being managed, and the detail says what is left to do by hand.
func (*Service) DisconnectGitHubRunnerApp ¶
func (c *Service) DisconnectGitHubRunnerApp( ctx context.Context, req *connect.Request[directoryrosterv1.DisconnectGitHubRunnerAppRequest], ) (*connect.Response[directoryrosterv1.DisconnectGitHubRunnerAppResponse], error)
DisconnectGitHubRunnerApp uninstalls a runner App, then forgets it. A failed uninstall still forgets, and says what is left to do by hand.
func (*Service) GetGitHubApp ¶
func (c *Service) GetGitHubApp( ctx context.Context, req *connect.Request[directoryrosterv1.GetGitHubAppRequest], ) (*connect.Response[directoryrosterv1.GetGitHubAppResponse], error)
GetGitHubApp implements one App's page.
func (*Service) GetGitHubStatus ¶
func (c *Service) GetGitHubStatus( ctx context.Context, _ *connect.Request[directoryrosterv1.GetGitHubStatusRequest], ) (*connect.Response[directoryrosterv1.GetGitHubStatusResponse], error)
GetGitHubStatus implements the GitHub page: every organisation the policy binds or the controller reports on, its bindings beside its report.
The join happens here rather than in the browser so that the page has one answer to render. Bindings come from the policy in force and reports from the controller's ConfigMap; neither is authoritative for the other, and the page shows where they disagree — a bound team the controller has not reported, a report for a team the policy no longer binds — rather than hiding either side.
A viewer of some directory sees the organisations that directory owns (the owner recorded when each was connected) and no others; the installation-wide viewer sees all. A report names the members of every bound team, which is more than a scoped viewer may see of another company. The link App and every link are people's across all organisations, so they are the installation-wide viewer's alone.
func (*Service) GitHubLinkComplete ¶
func (c *Service) GitHubLinkComplete(ctx context.Context, r consolewire.LinkComplete) (consolewire.Linked, error)
GitHubLinkComplete finishes a link: GitHub's word on who the account is and which addresses it verifies, and the directory's word on which of those it knows. The issuer verified the signed state and the browser's cookie.
func (*Service) GitHubLinkPage ¶
GitHubLinkPage is where a person starts linking: the address GitHub's authorize page is at. The page has no session, so the call carries no caller; the issuer signs the state the URL carries and pins it to the person's browser.
func (*Service) ImportGitHubLinks ¶
func (c *Service) ImportGitHubLinks( ctx context.Context, req *connect.Request[directoryrosterv1.ImportGitHubLinksRequest], ) (*connect.Response[directoryrosterv1.ImportGitHubLinksResponse], error)
ImportGitHubLinks adopts approved pairings as links, after three checks each: somebody approved the pairing, an address is a live account the directory vouches for, and the account is a member of a connected organisation. A link the person made is never displaced.
func (*Service) ListGitHubAppTokens ¶
func (c *Service) ListGitHubAppTokens( ctx context.Context, req *connect.Request[directoryrosterv1.ListGitHubAppTokensRequest], ) (*connect.Response[directoryrosterv1.ListGitHubAppTokensResponse], error)
ListGitHubAppTokens implements an App's *Recent tokens*: the last installation tokens asked of it, minted or refused, newest first.
From this service's own memory of them rather than from the audit trail. The trail is the record and holds every request, but narrowing it by kind and target scans object after object of every hour in turn: measured against a real bucket the call was still running when the gateway gave up on it, and the section on the page was a sentence apologising. What it costs to keep the answer to this one question beside the process that answers it is a bounded ring per App.
Operator, not viewer: a request names who asked for it, as the Audit page does.
func (*Service) ListGitHubApps ¶
func (c *Service) ListGitHubApps( ctx context.Context, _ *connect.Request[directoryrosterv1.ListGitHubAppsRequest], ) (*connect.Response[directoryrosterv1.ListGitHubAppsResponse], error)
ListGitHubApps implements the Apps list: every App in one shape.
func (*Service) RequestGitHubPass ¶
func (c *Service) RequestGitHubPass( ctx context.Context, req *connect.Request[directoryrosterv1.RequestGitHubPassRequest], ) (*connect.Response[directoryrosterv1.RequestGitHubPassResponse], error)
RequestGitHubPass asks the GitHub controller to pass over now. The request is a marker the controller notices within its poll (see the controller's watch); a second one under a minute after the first is refused, so the button cannot be leaned on. It is recorded as a configuration change.
func (*Service) RotateGitHubAppWebhook ¶
func (c *Service) RotateGitHubAppWebhook( ctx context.Context, req *connect.Request[directoryrosterv1.RotateGitHubAppWebhookRequest], ) (*connect.Response[directoryrosterv1.RotateGitHubAppWebhookResponse], error)
RotateGitHubAppWebhook replaces the secret a catalogue App's webhook deliveries are signed with.
Neither Argo CD nor Kargo accepts two secrets, so a rotation cannot overlap: the consumers must hold the new secret before GitHub signs with it, and GitHub holds exactly one. The order is therefore: keep the new secret where the consumers read it; wait until the (new) target accepts a ping signed with it, which proves the consumer reloaded; only then tell GitHub, the secret and a Kargo receiver's derived URL in one call. A failure before that restores the old secret, so deliveries go on exactly as they were.