26cd05e961
agentpr always read gitea/creds/unkin-agent from a bare const, so a service like repospawner could not run it as its own Gitea identity. - Replace the GiteaCredsPath const with a function: GITEA_CREDS_PATH when set, otherwise gitea/creds/<AGENT_LOGIN>. Unset env still resolves to gitea/creds/unkin-agent, so existing callers are unchanged. - Thread the creds path through fetchGiteaToken/readGiteaCreds instead of reading a package-level const, and report it in the error messages. - Make agentpr's help text login-agnostic and document both variables.
85 lines
2.5 KiB
Go
85 lines
2.5 KiB
Go
package agent
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// httpClient is shared by the Vault and Gitea calls. A modest timeout keeps a
|
|
// hung endpoint from wedging watchpr's poll loop.
|
|
var httpClient = &http.Client{Timeout: 30 * time.Second}
|
|
|
|
// approleLogin logs in with role_id only (no secret_id) and returns the
|
|
// resulting client_token.
|
|
func approleLogin(vaultAddr, roleID string) (string, error) {
|
|
body, _ := json.Marshal(map[string]string{"role_id": roleID})
|
|
url := strings.TrimRight(vaultAddr, "/") + "/v1/auth/approle/login"
|
|
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := httpClient.Do(req)
|
|
if err != nil {
|
|
return "", fmt.Errorf("vault approle login: %w", err)
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
data, _ := io.ReadAll(resp.Body)
|
|
if resp.StatusCode != http.StatusOK {
|
|
return "", fmt.Errorf("vault approle login: HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(data)))
|
|
}
|
|
|
|
var out struct {
|
|
Auth struct {
|
|
ClientToken string `json:"client_token"`
|
|
} `json:"auth"`
|
|
}
|
|
if err := json.Unmarshal(data, &out); err != nil {
|
|
return "", fmt.Errorf("vault approle login: decoding response: %w", err)
|
|
}
|
|
if out.Auth.ClientToken == "" {
|
|
return "", fmt.Errorf("vault approle login: no client_token in response")
|
|
}
|
|
return out.Auth.ClientToken, nil
|
|
}
|
|
|
|
// readGiteaCreds reads the Gitea creds secret at credsPath and returns the
|
|
// token field.
|
|
func readGiteaCreds(vaultAddr, clientToken, credsPath string) (string, error) {
|
|
url := strings.TrimRight(vaultAddr, "/") + "/v1/" + credsPath
|
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
req.Header.Set("X-Vault-Token", clientToken)
|
|
|
|
resp, err := httpClient.Do(req)
|
|
if err != nil {
|
|
return "", fmt.Errorf("vault read %s: %w", credsPath, err)
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
data, _ := io.ReadAll(resp.Body)
|
|
if resp.StatusCode != http.StatusOK {
|
|
return "", fmt.Errorf("vault read %s: HTTP %d: %s", credsPath, resp.StatusCode, strings.TrimSpace(string(data)))
|
|
}
|
|
|
|
var out struct {
|
|
Data struct {
|
|
Token string `json:"token"`
|
|
} `json:"data"`
|
|
}
|
|
if err := json.Unmarshal(data, &out); err != nil {
|
|
return "", fmt.Errorf("vault read %s: decoding response: %w", credsPath, err)
|
|
}
|
|
if out.Data.Token == "" {
|
|
return "", fmt.Errorf("vault read %s: no token field in secret", credsPath)
|
|
}
|
|
return out.Data.Token, nil
|
|
}
|