diff --git a/README.md b/README.md index 3e3545d..55a9174 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Create `$XDG_CONFIG_HOME/nix-cache-login/config.toml` (default `~/.config/nix-ca ```toml issuer = "https://id.guardianproject.info/realms/gp" client_id = "nix-cache" -cache_host = "cache.guardianproject.info" +cache_host = "cache.guardianproject.dev" netrc_path = "$XDG_CONFIG_HOME/nix/netrc" ``` @@ -51,8 +51,8 @@ netrc_path = "$XDG_CONFIG_HOME/nix/netrc" ```toml issuer = "https://id.guardianproject.info/realms/gp" client_id = "nix-cache-server" -client_secret = "..." -cache_host = "cache.guardianproject.info" +client_secret_file = "/run/secrets/nix-cache-client-secret" +cache_host = "cache.guardianproject.dev" netrc_path = "$XDG_CONFIG_HOME/nix/netrc" ``` diff --git a/cmd/serviceAccount.go b/cmd/serviceAccount.go index 87542cb..4aa11bc 100644 --- a/cmd/serviceAccount.go +++ b/cmd/serviceAccount.go @@ -18,7 +18,7 @@ var serviceAccountCmd = &cobra.Command{ Use: "service-account", Short: "Authenticate using client credentials (for servers)", Long: `Authenticates using the OAuth2 client credentials flow for headless -server environments. Requires client_secret in the config file. +server environments. Requires client_secret_file in the config file. Exits 0 on success, 1 on failure. Designed to be called from a systemd timer.`, RunE: runServiceAccount, } @@ -31,7 +31,7 @@ func runServiceAccount(cmd *cobra.Command, args []string) error { ctx := context.Background() if cfg.ClientSecret == "" { - fmt.Fprintln(os.Stderr, "Error: client_secret is required in config for service-account mode.") + fmt.Fprintln(os.Stderr, "Error: client_secret_file is required in config for service-account mode.") os.Exit(1) } diff --git a/internal/config/config.go b/internal/config/config.go index df2804d..77faaa1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,17 +4,21 @@ import ( "fmt" "os" "path/filepath" + "strings" "github.com/adrg/xdg" toml "github.com/pelletier/go-toml/v2" ) type Config struct { - Issuer string `toml:"issuer"` - ClientID string `toml:"client_id"` - ClientSecret string `toml:"client_secret,omitempty"` - CacheHost string `toml:"cache_host"` - NetrcPath string `toml:"netrc_path"` + Issuer string `toml:"issuer"` + ClientID string `toml:"client_id"` + ClientSecretFile string `toml:"client_secret_file,omitempty"` + CacheHost string `toml:"cache_host"` + NetrcPath string `toml:"netrc_path"` + + // ClientSecret is populated at load time by reading ClientSecretFile. + ClientSecret string `toml:"-"` } // Load reads the config from the given path, or from the default XDG location. @@ -34,6 +38,15 @@ func Load(path string) (*Config, error) { } cfg.NetrcPath = os.ExpandEnv(cfg.NetrcPath) + cfg.ClientSecretFile = os.ExpandEnv(cfg.ClientSecretFile) + + if cfg.ClientSecretFile != "" { + secret, err := os.ReadFile(cfg.ClientSecretFile) + if err != nil { + return nil, fmt.Errorf("reading client_secret_file: %w", err) + } + cfg.ClientSecret = strings.TrimSpace(string(secret)) + } if err := cfg.validate(); err != nil { return nil, err diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 1e8efc9..b17bb0f 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -39,14 +39,19 @@ netrc_path = "/home/user/.config/nix/netrc" } } -func TestLoadConfigWithClientSecret(t *testing.T) { +func TestLoadConfigWithClientSecretFile(t *testing.T) { dir := t.TempDir() cfgFile := filepath.Join(dir, "config.toml") + secretFile := filepath.Join(dir, "secret") + + if err := os.WriteFile(secretFile, []byte("super-secret\n"), 0600); err != nil { + t.Fatal(err) + } content := ` issuer = "https://id.example.com/realms/test" client_id = "nix-cache-server" -client_secret = "super-secret" +client_secret_file = "` + secretFile + `" cache_host = "cache.example.com" netrc_path = "/tmp/netrc" ` @@ -64,6 +69,30 @@ netrc_path = "/tmp/netrc" } } +func TestLoadConfigClientSecretFileMissing(t *testing.T) { + dir := t.TempDir() + cfgFile := filepath.Join(dir, "config.toml") + + content := ` +issuer = "https://id.example.com/realms/test" +client_id = "nix-cache-server" +client_secret_file = "/nonexistent/secret" +cache_host = "cache.example.com" +netrc_path = "/tmp/netrc" +` + if err := os.WriteFile(cfgFile, []byte(content), 0644); err != nil { + t.Fatal(err) + } + + _, err := Load(cfgFile) + if err == nil { + t.Fatal("expected error, got nil") + } + if !contains(err.Error(), "client_secret_file") { + t.Errorf("error = %q, want to contain %q", err.Error(), "client_secret_file") + } +} + func TestEnvVarExpansionInNetrcPath(t *testing.T) { dir := t.TempDir() cfgFile := filepath.Join(dir, "config.toml")