Skip to content

Commit 068fc43

Browse files
committed
fix: keep conan auth out of client and relax nuget idle timeout
1 parent 864bccb commit 068fc43

3 files changed

Lines changed: 16 additions & 202 deletions

File tree

module/conan/conan_cmd.go

Lines changed: 15 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"bytes"
55
"context"
66
"encoding/base64"
7-
"encoding/json"
87
"fmt"
98
"github.com/murphysecurity/murphysec/errors"
109
"github.com/murphysecurity/murphysec/infra/logctx"
@@ -13,7 +12,6 @@ import (
1312
"go.uber.org/zap"
1413
"io"
1514
"math/rand"
16-
"net/url"
1715
"os"
1816
"os/exec"
1917
"path/filepath"
@@ -116,41 +114,30 @@ func ExecuteConanInfoCmd(ctx context.Context, cmdInfo *CmdInfo, dir string) (str
116114
}
117115
jsonP := getConanInfoJsonPath()
118116
major := ConanMajorVersion(cmdInfo.Version)
119-
remoteCreds, credErr := getConanRemoteCredentialsFromConfig(major)
120-
if credErr != nil {
121-
logger.Warn("Conan remote credential precheck failed when reading config", zap.Error(credErr))
122-
}
123117
logger.Sugar().Infof("Conan detected: path=%s version=%s major=%d", cmdInfo.Path, cmdInfo.Version, major)
124118
logger.Sugar().Infof("Conan verbose mode: -v %s", conanVerboseArg)
125119
logConanRemoteConfigPaths(logger, major)
126-
loginWarnings := ensureConanRemoteLogin(ctx, cmdInfo.Path, major, remoteCreds)
127120
logger.Sugar().Debugf("temp file: %s", jsonP)
128121
if major >= 2 {
129-
if e := ensureConan2DefaultProfile(ctx, cmdInfo.Path, major, remoteCreds); e != nil {
122+
if e := ensureConan2DefaultProfile(ctx, cmdInfo.Path); e != nil {
130123
return "", "", e
131124
}
132125
logger.Info("Conan mode selected: graph")
133-
if e := executeConanGraphInfoCmd(ctx, cmdInfo.Path, dir, jsonP, major, remoteCreds); e != nil {
134-
if len(loginWarnings) > 0 {
135-
return "", "", fmt.Errorf("%w; login warnings: %s", e, strings.Join(loginWarnings, " | "))
136-
}
126+
if e := executeConanGraphInfoCmd(ctx, cmdInfo.Path, dir, jsonP); e != nil {
137127
return "", "", e
138128
}
139129
logger.Info("Conan command completed with graph mode")
140130
return jsonP, ConanJsonKindGraph, nil
141131
}
142132
logger.Info("Conan mode selected: info")
143-
if e := executeConanInfoCmd(ctx, cmdInfo.Path, dir, jsonP, major, remoteCreds); e != nil {
144-
if len(loginWarnings) > 0 {
145-
return "", "", fmt.Errorf("%w; login warnings: %s", e, strings.Join(loginWarnings, " | "))
146-
}
133+
if e := executeConanInfoCmd(ctx, cmdInfo.Path, dir, jsonP); e != nil {
147134
return "", "", e
148135
}
149136
logger.Info("Conan command completed with info mode")
150137
return jsonP, ConanJsonKindInfo, nil
151138
}
152139

153-
func ensureConan2DefaultProfile(ctx context.Context, conanPath string, major int, remoteCreds []conanRemoteCredential) error {
140+
func ensureConan2DefaultProfile(ctx context.Context, conanPath string) error {
154141
logger := logctx.Use(ctx)
155142
home, err := os.UserHomeDir()
156143
if err != nil || home == "" {
@@ -166,10 +153,10 @@ func ensureConan2DefaultProfile(ctx context.Context, conanPath string, major int
166153
}
167154

168155
logger.Sugar().Infof("Conan default profile missing: %s, running detect", profilePath)
169-
args := conanArgs(major, "profile", "detect", "--force")
156+
args := conanArgs("profile", "detect", "--force")
170157
c := exec.CommandContext(ctx, conanPath, args...)
171158
logger.Sugar().Infof("Command: %s", c.String())
172-
c.Env = getEnvForConan(major, remoteCreds)
159+
c.Env = getEnvForConan()
173160
start := time.Now()
174161
sb := suffixbuf.NewSize(1024)
175162
logPipe := logpipe.New(logger, "conan")
@@ -189,12 +176,12 @@ func ensureConan2DefaultProfile(ctx context.Context, conanPath string, major int
189176
return nil
190177
}
191178

192-
func executeConanInfoCmd(ctx context.Context, conanPath string, dir string, jsonP string, major int, remoteCreds []conanRemoteCredential) error {
179+
func executeConanInfoCmd(ctx context.Context, conanPath string, dir string, jsonP string) error {
193180
logger := logctx.Use(ctx)
194-
args := conanArgs(major, "info", ".", "-j", jsonP)
181+
args := conanArgs("info", ".", "-j", jsonP)
195182
c := exec.Command(conanPath, args...)
196183
logger.Sugar().Infof("Command: %s", c.String())
197-
c.Env = getEnvForConan(major, remoteCreds)
184+
c.Env = getEnvForConan()
198185
c.Dir = dir
199186
start := time.Now()
200187
sb := suffixbuf.NewSize(1024)
@@ -210,12 +197,12 @@ func executeConanInfoCmd(ctx context.Context, conanPath string, dir string, json
210197
return nil
211198
}
212199

213-
func executeConanGraphInfoCmd(ctx context.Context, conanPath string, dir string, jsonP string, major int, remoteCreds []conanRemoteCredential) error {
200+
func executeConanGraphInfoCmd(ctx context.Context, conanPath string, dir string, jsonP string) error {
214201
logger := logctx.Use(ctx)
215-
args := conanArgs(major, "graph", "info", ".", "--format=json")
202+
args := conanArgs("graph", "info", ".", "--format=json")
216203
c := exec.Command(conanPath, args...)
217204
logger.Sugar().Infof("Command: %s", c.String())
218-
c.Env = getEnvForConan(major, remoteCreds)
205+
c.Env = getEnvForConan()
219206
c.Dir = dir
220207
start := time.Now()
221208
sb := suffixbuf.NewSize(1024)
@@ -235,105 +222,7 @@ func executeConanGraphInfoCmd(ctx context.Context, conanPath string, dir string,
235222
return nil
236223
}
237224

238-
type conanRemoteCredential struct {
239-
Name string
240-
Username string
241-
Password string
242-
}
243-
244-
func ensureConanRemoteLogin(ctx context.Context, conanPath string, major int, creds []conanRemoteCredential) []string {
245-
warnings := make([]string, 0)
246-
if major < 2 {
247-
return warnings
248-
}
249-
logger := logctx.Use(ctx)
250-
if len(creds) == 0 {
251-
logger.Info("Conan remote login skipped: no credentials found in remote URLs")
252-
return warnings
253-
}
254-
for _, cred := range creds {
255-
args := conanArgs(major, "remote", "auth", cred.Name, "--force")
256-
c := exec.CommandContext(ctx, conanPath, args...)
257-
c.Env = getEnvForConan(major, []conanRemoteCredential{cred})
258-
sb := suffixbuf.NewSize(1024)
259-
logPipe := logpipe.New(logger, "conan")
260-
logger.Sugar().Infof("Command: %s remote auth %s --force (credentials from env)", conanPath, cred.Name)
261-
c.Stdout = io.MultiWriter(sb, logPipe)
262-
c.Stderr = io.MultiWriter(sb, logPipe)
263-
if runErr := c.Run(); runErr != nil {
264-
logPipe.Close()
265-
msg := fmt.Sprintf("conan remote auth failed for %s(user=%s): %v, details: %s", cred.Name, cred.Username, runErr, strings.TrimSpace(string(sb.Bytes())))
266-
logger.Warn(msg)
267-
warnings = append(warnings, msg)
268-
continue
269-
}
270-
logPipe.Close()
271-
logger.Sugar().Infof("Conan remote auth completed: remote=%s user=%s", cred.Name, cred.Username)
272-
}
273-
return warnings
274-
}
275-
276-
type conanRemotesFile struct {
277-
Remotes []struct {
278-
Name string `json:"name"`
279-
URL string `json:"url"`
280-
} `json:"remotes"`
281-
}
282-
283-
func getConanRemoteCredentialsFromConfig(major int) ([]conanRemoteCredential, error) {
284-
path, err := conanRemotesConfigPath(major)
285-
if err != nil {
286-
return nil, err
287-
}
288-
data, err := os.ReadFile(path)
289-
if err != nil {
290-
return nil, err
291-
}
292-
var cfg conanRemotesFile
293-
if err = json.Unmarshal(data, &cfg); err != nil {
294-
return nil, fmt.Errorf("parse remotes config failed: %w", err)
295-
}
296-
rs := make([]conanRemoteCredential, 0)
297-
seen := make(map[string]struct{})
298-
for _, remote := range cfg.Remotes {
299-
u, parseErr := url.Parse(strings.TrimSpace(remote.URL))
300-
if parseErr != nil || u.User == nil {
301-
continue
302-
}
303-
username := u.User.Username()
304-
password, ok := u.User.Password()
305-
if username == "" || !ok || password == "" {
306-
continue
307-
}
308-
key := remote.Name + "|" + username
309-
if _, exists := seen[key]; exists {
310-
continue
311-
}
312-
seen[key] = struct{}{}
313-
rs = append(rs, conanRemoteCredential{
314-
Name: remote.Name,
315-
Username: username,
316-
Password: password,
317-
})
318-
}
319-
return rs, nil
320-
}
321-
322-
func conanRemotesConfigPath(major int) (string, error) {
323-
home, err := os.UserHomeDir()
324-
if err != nil || home == "" {
325-
return "", fmt.Errorf("cannot determine user home: %w", err)
326-
}
327-
if major >= 2 {
328-
return filepath.Join(home, ".conan2", "remotes.json"), nil
329-
}
330-
return filepath.Join(home, ".conan", "remotes.json"), nil
331-
}
332-
333-
func conanArgs(major int, args ...string) []string {
334-
if major >= 2 {
335-
args = append(args, "-cc", "core:non_interactive=True")
336-
}
225+
func conanArgs(args ...string) []string {
337226
return append(args, "-v", conanVerboseArg)
338227
}
339228

@@ -351,7 +240,7 @@ func LocateConan(ctx context.Context) (string, error) {
351240

352241
func GetConanVersion(ctx context.Context, conanPath string) (string, error) {
353242
c := exec.CommandContext(ctx, conanPath, "-v")
354-
c.Env = getBaseEnvForConan()
243+
c.Env = getEnvForConan()
355244
if data, e := c.Output(); e != nil {
356245
return "", errors.WithCause(ErrGetConanVersionFail, e)
357246
} else {
@@ -369,46 +258,9 @@ func ConanMajorVersion(version string) int {
369258
return v
370259
}
371260

372-
func getBaseEnvForConan() []string {
261+
func getEnvForConan() []string {
373262
osEnv := os.Environ()
374263
var rs = make([]string, 0, len(osEnv)+3)
375264
rs = append(rs, osEnv...)
376265
return append(rs, "CONAN_NON_INTERACTIVE=1", "NO_COLOR=1", "CLICOLOR=0")
377266
}
378-
379-
func getEnvForConan(major int, remoteCreds []conanRemoteCredential) []string {
380-
rs := getBaseEnvForConan()
381-
if major < 1 || len(remoteCreds) == 0 {
382-
return rs
383-
}
384-
for _, cred := range remoteCreds {
385-
suffix := conanRemoteEnvVarSuffix(cred.Name)
386-
if suffix == "" || cred.Username == "" || cred.Password == "" {
387-
continue
388-
}
389-
rs = append(rs,
390-
fmt.Sprintf("CONAN_LOGIN_USERNAME_%s=%s", suffix, cred.Username),
391-
fmt.Sprintf("CONAN_PASSWORD_%s=%s", suffix, cred.Password),
392-
)
393-
}
394-
return rs
395-
}
396-
397-
func conanRemoteEnvVarSuffix(remoteName string) string {
398-
remoteName = strings.TrimSpace(remoteName)
399-
if remoteName == "" {
400-
return ""
401-
}
402-
var b strings.Builder
403-
for _, r := range remoteName {
404-
switch {
405-
case r >= 'a' && r <= 'z':
406-
b.WriteRune(r - ('a' - 'A'))
407-
case (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9'):
408-
b.WriteRune(r)
409-
default:
410-
b.WriteRune('_')
411-
}
412-
}
413-
return b.String()
414-
}

module/conan/conan_cmd_test.go

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"github.com/murphysecurity/murphysec/utils/must"
66
"github.com/pkg/errors"
77
"os/exec"
8-
"strings"
98
"testing"
109
)
1110

@@ -17,40 +16,3 @@ func TestGetConanVersion(t *testing.T) {
1716
}
1817
t.Log(GetConanVersion(context.TODO(), must.A(LocateConan(context.TODO()))))
1918
}
20-
21-
func TestConanArgsV1DoesNotForceCoreNonInteractive(t *testing.T) {
22-
args := conanArgs(1, "info", ".")
23-
got := strings.Join(args, " ")
24-
if strings.Contains(got, "core:non_interactive=True") {
25-
t.Fatalf("unexpected v1 core:non_interactive flag in args: %v", args)
26-
}
27-
}
28-
29-
func TestConanArgsV2ForcesCoreNonInteractive(t *testing.T) {
30-
args := conanArgs(2, "graph", "info", ".")
31-
got := strings.Join(args, " ")
32-
if !strings.Contains(got, "core:non_interactive=True") {
33-
t.Fatalf("missing v2 core:non_interactive flag in args: %v", args)
34-
}
35-
}
36-
37-
func TestConanRemoteEnvVarSuffix(t *testing.T) {
38-
if got := conanRemoteEnvVarSuffix("conan-center.prod"); got != "CONAN_CENTER_PROD" {
39-
t.Fatalf("unexpected env suffix: %q", got)
40-
}
41-
}
42-
43-
func TestGetEnvForConanAddsRemoteCredentialEnv(t *testing.T) {
44-
env := getEnvForConan(2, []conanRemoteCredential{{
45-
Name: "conan-center",
46-
Username: "alice",
47-
Password: "secret",
48-
}})
49-
joined := strings.Join(env, "\n")
50-
if !strings.Contains(joined, "CONAN_LOGIN_USERNAME_CONAN_CENTER=alice") {
51-
t.Fatalf("missing username env, env=%v", env)
52-
}
53-
if !strings.Contains(joined, "CONAN_PASSWORD_CONAN_CENTER=secret") {
54-
t.Fatalf("missing password env, env=%v", env)
55-
}
56-
}

module/nuget/nuget_cmd_build.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ var _ErrDotnetNotFound = errors.New("dotnet not found")
3737

3838
const (
3939
nugetBuildMaxTimeout = 6 * time.Hour
40-
nugetCommandIdleTimeout = 30 * time.Second
40+
nugetCommandIdleTimeout = 60 * time.Second
4141
nugetPreflightTimeout = 8 * time.Second
4242
nugetRestoreBinlogDir = ".murphysec"
4343
nugetTmpBinlogDir = "murphysec-nuget-binlogs"

0 commit comments

Comments
 (0)