cloud

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 7, 2026 License: Apache-2.0 Imports: 30 Imported by: 0

Documentation

Overview

Package cloud is the multi-tenant hosted layer ("Keyway Cloud") on top of the open-source engine. It adds accounts, projects, and persisted analysis history around the same discovery/contract/diff/threat logic the CLI and self-hosted server use — so the static, config-driven half of Keyway (auth-contract discovery, drift, threat coverage) can run as a SaaS on repos users connect or upload. The live half (probing, canary, blast radius) stays self-hosted by design; the cloud never handles a customer's signing keys or staging traffic.

Index

Constants

This section is empty.

Variables

View Source
var ErrNotFound = errors.New("not found")

ErrNotFound is returned when a requested record does not exist (or is not visible to the caller — callers scope by owner before loading).

Functions

func Analyze

func Analyze(ctx context.Context, manifests map[string]string, prev *model.ContractVersion) (model.ContractVersion, []model.ChangeEvent, error)

Analyze runs the engine over a set of manifest files (path → YAML/JSON content) and returns the derived contract version plus the drift versus prev (nil for a first run). It reuses the exact discovery + contract + diff logic the CLI and self-hosted server use — the manifests are written to a throwaway temp dir and removed immediately, so nothing is persisted to disk.

Types

type Analysis

type Analysis struct {
	ID          string                `json:"id"`
	ProjectID   string                `json:"project_id"`
	CreatedAt   time.Time             `json:"created_at"`
	TriggerKind string                `json:"trigger_kind"` // upload | sync | manual
	TriggerRef  string                `json:"trigger_ref,omitempty"`
	Hash        string                `json:"hash"`
	IsBaseline  bool                  `json:"is_baseline"`
	Version     model.ContractVersion `json:"version"`
	Changes     []model.ChangeEvent   `json:"changes"`
	// Denormalized summary for cheap list rendering.
	ConsumerCount int `json:"consumer_count"`
	IssuerCount   int `json:"issuer_count"`
	ChangeCount   int `json:"change_count"`
}

Analysis is a single run over a project's config: the derived contract plus the drift (change events) versus the project's previous analysis. Stored so a project accrues history over time.

func (Analysis) Summary

func (a Analysis) Summary() AnalysisSummary

Summary projects an Analysis to its compact form.

type AnalysisSummary

type AnalysisSummary struct {
	ID            string    `json:"id"`
	CreatedAt     time.Time `json:"created_at"`
	TriggerKind   string    `json:"trigger_kind"`
	TriggerRef    string    `json:"trigger_ref,omitempty"`
	Hash          string    `json:"hash"`
	IsBaseline    bool      `json:"is_baseline"`
	ConsumerCount int       `json:"consumer_count"`
	IssuerCount   int       `json:"issuer_count"`
	ChangeCount   int       `json:"change_count"`
}

AnalysisSummary is the compact form used in list/history views.

type Config

type Config struct {
	Addr           string   // listen address, default :8090
	BaseURL        string   // public base URL, used to build the OAuth redirect
	FrontendURL    string   // where to send the user after login (the web app)
	SessionSecret  []byte   // HMAC key for session cookies
	GitHubClientID string   // GitHub OAuth app client id
	GitHubSecret   string   // GitHub OAuth app client secret
	AllowedOrigins []string // CORS allowlist (the frontend origins)
	DevLogin       bool     // enable a passwordless dev login (local only)
	SecureCookies  bool     // set the Secure flag on cookies (true in production/HTTPS)
}

Config is the cloud server's runtime configuration, sourced from the environment so it deploys anywhere without code changes.

func ConfigFromEnv

func ConfigFromEnv() Config

ConfigFromEnv builds a Config from environment variables:

KEYWAY_CLOUD_ADDR              (:8090)
KEYWAY_CLOUD_BASE_URL          (http://localhost:8090)
KEYWAY_CLOUD_FRONTEND_URL      (http://localhost:5173)
KEYWAY_CLOUD_SESSION_SECRET    (random if unset — sessions won't survive restart)
KEYWAY_CLOUD_ALLOWED_ORIGINS   (comma-separated; defaults to FRONTEND_URL)
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET
KEYWAY_CLOUD_DEV_LOGIN=1       (enable local passwordless login)

type GitHub

type GitHub struct {
	ClientID     string
	ClientSecret string
	// contains filtered or unexported fields
}

GitHub wraps the small slice of the GitHub API the cloud needs: OAuth login and reading manifest files from a repository. Uses only the standard library.

func NewGitHub

func NewGitHub(clientID, clientSecret string) *GitHub

NewGitHub builds a GitHub client. ClientID/Secret are required only for the OAuth login flow; public-repo manifest reads work without them.

func (*GitHub) AuthURL

func (g *GitHub) AuthURL(redirectURI, state, scope string) string

AuthURL is the GitHub authorize URL to redirect the user to. `read:user` is the only scope needed for login; add `repo` when private-repo sync is enabled.

func (*GitHub) Configured

func (g *GitHub) Configured() bool

Configured reports whether OAuth login is available (client id + secret set).

func (*GitHub) Exchange

func (g *GitHub) Exchange(ctx context.Context, code, redirectURI string) (string, error)

Exchange trades an OAuth code for an access token.

func (*GitHub) FetchManifests

func (g *GitHub) FetchManifests(ctx context.Context, repo, ref, subpath, token string) (map[string]string, string, error)

FetchManifests reads YAML manifest files from a repository (optionally under a subpath) and returns them keyed by repo path, plus the resolved commit SHA. A token is required for private repos; public repos work with an empty token.

func (*GitHub) FetchUser

func (g *GitHub) FetchUser(ctx context.Context, token string) (User, error)

FetchUser loads the authenticated user's profile for the given token.

type MemoryStore

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

MemoryStore is an in-process Store for local/dev and tests. Data is lost on restart — hosting should swap in the Postgres store (same interface).

func NewMemoryStore

func NewMemoryStore() *MemoryStore

NewMemoryStore returns an empty in-memory store.

func (*MemoryStore) Close

func (s *MemoryStore) Close() error

func (*MemoryStore) CreateProject

func (s *MemoryStore) CreateProject(_ context.Context, p Project) error

func (*MemoryStore) DeleteProject

func (s *MemoryStore) DeleteProject(_ context.Context, id string) error

func (*MemoryStore) GetAnalysis

func (s *MemoryStore) GetAnalysis(_ context.Context, id string) (Analysis, error)

func (*MemoryStore) GetProject

func (s *MemoryStore) GetProject(_ context.Context, id string) (Project, error)

func (*MemoryStore) GetUser

func (s *MemoryStore) GetUser(_ context.Context, id string) (User, error)

func (*MemoryStore) LatestAnalysis

func (s *MemoryStore) LatestAnalysis(ctx context.Context, projectID string) (Analysis, error)

func (*MemoryStore) ListAnalyses

func (s *MemoryStore) ListAnalyses(_ context.Context, projectID string, limit int) ([]Analysis, error)

func (*MemoryStore) ListProjects

func (s *MemoryStore) ListProjects(_ context.Context, ownerID string) ([]Project, error)

func (*MemoryStore) SaveAnalysis

func (s *MemoryStore) SaveAnalysis(_ context.Context, a Analysis) error

func (*MemoryStore) UpsertUser

func (s *MemoryStore) UpsertUser(_ context.Context, u User) error

type Project

type Project struct {
	ID               string    `json:"id"`
	OwnerID          string    `json:"owner_id"`
	Name             string    `json:"name"`
	Source           Source    `json:"source"`
	CreatedAt        time.Time `json:"created_at"`
	LatestAnalysisID string    `json:"latest_analysis_id,omitempty"`
}

Project is one tracked auth surface (a repo or an uploaded config set) owned by a user. It is the multi-tenant boundary: every project belongs to exactly one OwnerID, and all reads/writes are scoped to the requesting user's projects.

type Server

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

Server is the Keyway Cloud HTTP API. It owns accounts, projects, and analysis history; the analysis itself delegates to the shared engine (see analyze.go).

func NewServer

func NewServer(cfg Config, store Store) *Server

NewServer wires the cloud server over a store.

func (*Server) Routes

func (s *Server) Routes() http.Handler

Routes builds the HTTP handler.

type Source

type Source struct {
	Kind SourceKind `json:"kind"`
	Repo string     `json:"repo,omitempty"` // "owner/name" for github sources
	Ref  string     `json:"ref,omitempty"`  // branch or tag (default "main")
	Path string     `json:"path,omitempty"` // optional subdirectory within the repo
}

Source describes where a project reads its auth configuration from.

type SourceKind

type SourceKind string

SourceKind is where a project's auth config comes from.

const (
	SourceUpload SourceKind = "upload" // config files uploaded through the UI/API
	SourceGitHub SourceKind = "github" // fetched from a connected GitHub repository
)

type Store

type Store interface {
	// Accounts.
	UpsertUser(ctx context.Context, u User) error
	GetUser(ctx context.Context, id string) (User, error)

	// Projects (tenant boundary: always filter by owner).
	CreateProject(ctx context.Context, p Project) error
	ListProjects(ctx context.Context, ownerID string) ([]Project, error)
	GetProject(ctx context.Context, id string) (Project, error)
	DeleteProject(ctx context.Context, id string) error

	// Analyses.
	SaveAnalysis(ctx context.Context, a Analysis) error
	ListAnalyses(ctx context.Context, projectID string, limit int) ([]Analysis, error) // newest first
	GetAnalysis(ctx context.Context, id string) (Analysis, error)
	LatestAnalysis(ctx context.Context, projectID string) (Analysis, error)

	Close() error
}

Store is the persistence seam for the cloud layer. The in-memory implementation backs local/dev; a Postgres implementation (same interface) is the drop-in for hosting — every method is already tenant-scoped by OwnerID/ProjectID so no query leaks across tenants.

type User

type User struct {
	ID        string    `json:"id"` // stable internal id, e.g. "gh:42" (provider:providerID)
	Login     string    `json:"login"`
	Name      string    `json:"name,omitempty"`
	AvatarURL string    `json:"avatar_url,omitempty"`
	Email     string    `json:"email,omitempty"`
	CreatedAt time.Time `json:"created_at"`
}

User is an authenticated account (currently always backed by a GitHub login).

Jump to

Keyboard shortcuts

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