// Package agent holds the plumbing shared by the agent-tools CLIs (agentpr, // watchpr, agentws and agentvault): the Vault AppRole login and its KV-v2 // client, talking to the Gitea and Authentik APIs, parsing PR references, and // deciding when a watched PR changed meaningfully. Every tool authenticates to // Vault the same way, so that logic lives here once. package agent import ( "os" "strings" "sync" ) const ( // DefaultVaultAddr is the OpenBao/Vault address used when VAULT_ADDR is unset. DefaultVaultAddr = "https://vault.service.consul:8200" // DefaultRoleID is the agent AppRole role_id used when AGENT_APPROLE_ROLE_ID // is unset. Login uses role_id only (no secret_id). DefaultRoleID = "ababbcd3-9c77-5c6a-be2d-287fce9214a6" // GiteaCredsPrefix is the Vault gitea secrets-engine creds prefix; the agent // login is appended to it to form the path that mints a scoped Gitea token. GiteaCredsPrefix = "gitea/creds/" // DefaultGiteaURL is the Gitea base URL used when GITEA_URL is unset. DefaultGiteaURL = "https://git.unkin.net" // DefaultAgentLogin is the Gitea login the tools act as: it selects the Vault // creds path, sets the agentws git identity and is the login whose own // comments watchpr ignores. Overridable via AGENT_LOGIN. DefaultAgentLogin = "unkin-agent" // DefaultAuthentikURL is the Authentik base URL used when AUTHENTIK_URL is // unset. identity.unkin.net has no DNS record; the k8s name is the real one. DefaultAuthentikURL = "https://identity.k8s.syd1.au.unkin.net" ) // VaultAddr returns the configured Vault address (env VAULT_ADDR or the default). func VaultAddr() string { if v := os.Getenv("VAULT_ADDR"); v != "" { return v } return DefaultVaultAddr } // RoleID returns the configured AppRole role_id (env AGENT_APPROLE_ROLE_ID or // the default). func RoleID() string { if v := os.Getenv("AGENT_APPROLE_ROLE_ID"); v != "" { return v } return DefaultRoleID } // GiteaURL returns the configured Gitea base URL (env GITEA_URL or the default). func GiteaURL() string { if v := os.Getenv("GITEA_URL"); v != "" { return v } return DefaultGiteaURL } // AgentLogin returns the Gitea login the tools act as (env AGENT_LOGIN or the // default). func AgentLogin() string { if v := os.Getenv("AGENT_LOGIN"); v != "" { return v } return DefaultAgentLogin } // GiteaCredsPath returns the Vault path that mints a scoped Gitea token: // GITEA_CREDS_PATH when set, otherwise gitea/creds/. So a service // running as its own identity only has to set AGENT_LOGIN. func GiteaCredsPath() string { // Trimmed because callers join this onto ".../v1/". if v := strings.Trim(strings.TrimSpace(os.Getenv("GITEA_CREDS_PATH")), "/"); v != "" { return v } return GiteaCredsPrefix + AgentLogin() } // AuthentikURL returns the configured Authentik base URL (env AUTHENTIK_URL or // the default). func AuthentikURL() string { if v := os.Getenv("AUTHENTIK_URL"); v != "" { return v } return DefaultAuthentikURL } var ( tokenOnce sync.Once tokenValue string tokenErr error ) // GiteaToken returns a Gitea token, minting it via Vault AppRole on first call // and caching it in-process for the lifetime of the command. func GiteaToken() (string, error) { tokenOnce.Do(func() { tokenValue, tokenErr = fetchGiteaToken(VaultAddr(), RoleID(), GiteaCredsPath()) }) return tokenValue, tokenErr } // fetchGiteaToken performs the AppRole login and reads the Gitea creds at // credsPath. It is separated from GiteaToken so tests can exercise it directly // against an httptest server without touching the process-wide cache. func fetchGiteaToken(vaultAddr, roleID, credsPath string) (string, error) { clientToken, err := approleLogin(vaultAddr, roleID) if err != nil { return "", err } return readGiteaCreds(vaultAddr, clientToken, credsPath) }