Skip to content

Commit b2d2507

Browse files
authored
Expose underlying git config calls as functions to further customize credential cache (#61)
1 parent e03df33 commit b2d2507

File tree

2 files changed

+234
-7
lines changed

2 files changed

+234
-7
lines changed

git/auth.go

Lines changed: 113 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,30 @@ import (
88
"github.com/sirupsen/logrus"
99
)
1010

11+
// CredentialOptions are the possible configurations options for configuring the git credential-cache helper.
12+
type CacheCredentialOptions struct {
13+
// Host is the VCS host where the cache credential helper should be triggered.
14+
// Set to "" if you want to apply the credential cache helper to all hosts.
15+
Host string
16+
17+
// DefaultUsername is the default username to use when authenticating to the VCS host.
18+
DefaultUsername string
19+
20+
// IncludeHTTPPath indicates whether to path through the git http path to the credential helper, enabling matching
21+
// with the path (e.g., the org/repo.git component of https://github.com/org/repo.git.
22+
IncludeHTTPPath bool
23+
24+
// SocketPath configures the path to the Unix socket file to use to interact with the cache credential daemon. When
25+
// blank, uses the default path baked into the command:
26+
// https://git-scm.com/docs/git-credential-cache#_options
27+
// This is useful when you are configuring the cache for the same host across multiple paths, as the cache
28+
// credential helper is known to break when you have an entry for a specific path and the generic all hosts.
29+
SocketPath string
30+
31+
// Timeout is the timeout in seconds for credentials in the cache.
32+
Timeout int
33+
}
34+
1135
// ConfigureForceHTTPS configures git to force usage of https endpoints instead of SSH based endpoints for the three
1236
// primary VCS platforms (GitHub, GitLab, BitBucket).
1337
func ConfigureForceHTTPS(logger *logrus.Logger) error {
@@ -44,27 +68,109 @@ func ConfigureForceHTTPS(logger *logrus.Logger) error {
4468
// with git over HTTPS. This uses the cache credentials store to configure the credentials. Refer to the git
4569
// documentation on credentials storage for more information:
4670
// https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage
47-
func ConfigureHTTPSAuth(logger *logrus.Logger, gitUsername string, gitOauthToken string, vcsHost string) error {
48-
opts := shell.NewShellOptions()
71+
// NOTE: this configures the cache credential helper globally, with a default timeout of 1 hour. If you want more
72+
// control over the configuration, use the ConfigureCacheCredentialsHelper and StoreCacheCredentials functions directly.
73+
func ConfigureHTTPSAuth(
74+
logger *logrus.Logger,
75+
gitUsername string,
76+
gitOauthToken string,
77+
vcsHost string,
78+
) error {
79+
// Legacy options that were in use when function was first introduced.
80+
cacheOpts := CacheCredentialOptions{
81+
Host: "",
82+
DefaultUsername: "",
83+
IncludeHTTPPath: false,
84+
SocketPath: "",
85+
Timeout: 3600,
86+
}
87+
if err := ConfigureCacheCredentialsHelper(logger, cacheOpts); err != nil {
88+
return err
89+
}
90+
return StoreCacheCredentials(logger, gitUsername, gitOauthToken, vcsHost, "", "")
91+
}
92+
93+
// ConfigureCacheCredentialsHelper configures git globally to use the cache credentials helper for authentication based
94+
// on the provided options configuration.
95+
func ConfigureCacheCredentialsHelper(logger *logrus.Logger, options CacheCredentialOptions) error {
96+
shellOpts := shell.NewShellOptions()
4997
if logger != nil {
50-
opts.Logger = logger
98+
shellOpts.Logger = logger
99+
}
100+
101+
credentialConfigPrefix := "credential"
102+
if options.Host != "" {
103+
credentialConfigPrefix += fmt.Sprintf(".%s", options.Host)
51104
}
52105

106+
helperOpts := fmt.Sprintf("--timeout %d", options.Timeout)
107+
if options.SocketPath != "" {
108+
helperOpts += fmt.Sprintf(" --socket %s", options.SocketPath)
109+
}
53110
if err := shell.RunShellCommand(
54-
opts,
111+
shellOpts,
55112
"git", "config", "--global",
56-
"credential.helper",
57-
"cache --timeout 3600",
113+
credentialConfigPrefix+".helper",
114+
"cache "+helperOpts,
58115
); err != nil {
59116
return err
60117
}
61118

119+
if options.DefaultUsername != "" {
120+
if err := shell.RunShellCommand(
121+
shellOpts,
122+
"git", "config", "--global",
123+
credentialConfigPrefix+".username",
124+
options.DefaultUsername,
125+
); err != nil {
126+
return err
127+
}
128+
}
129+
130+
if options.IncludeHTTPPath {
131+
if err := shell.RunShellCommand(
132+
shellOpts,
133+
"git", "config", "--global",
134+
credentialConfigPrefix+".useHttpPath",
135+
"true",
136+
); err != nil {
137+
return err
138+
}
139+
}
140+
141+
return nil
142+
}
143+
144+
// StoreCacheCredentials stores the given git credentials for the vcs host and path pair to the git credential-cache
145+
// helper.
146+
func StoreCacheCredentials(
147+
logger *logrus.Logger,
148+
gitUsername string,
149+
gitOauthToken string,
150+
vcsHost string,
151+
vcsPath string,
152+
socketPath string,
153+
) error {
154+
opts := shell.NewShellOptions()
155+
if logger != nil {
156+
opts.Logger = logger
157+
}
158+
62159
if gitUsername == "" {
63160
gitUsername = "git"
64161
}
65162
credentialsStoreInput := fmt.Sprintf(`protocol=https
66163
host=%s
67164
username=%s
68165
password=%s`, vcsHost, gitUsername, gitOauthToken)
69-
return shell.RunShellCommandWithInput(opts, credentialsStoreInput, "git", "credential-cache", "store")
166+
if vcsPath != "" {
167+
credentialsStoreInput += fmt.Sprintf("\npath=%s", vcsPath)
168+
}
169+
170+
cmdArgs := []string{"credential-cache"}
171+
if socketPath != "" {
172+
cmdArgs = append(cmdArgs, "--socket", socketPath)
173+
}
174+
cmdArgs = append(cmdArgs, "store")
175+
return shell.RunShellCommandWithInput(opts, credentialsStoreInput, "git", cmdArgs...)
70176
}

git/test/auth_test.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
"github.com/gruntwork-io/go-commons/git"
1313
"github.com/gruntwork-io/go-commons/logging"
1414
"github.com/gruntwork-io/terratest/modules/environment"
15+
ttlogger "github.com/gruntwork-io/terratest/modules/logger"
16+
"github.com/gruntwork-io/terratest/modules/shell"
1517
"github.com/stretchr/testify/assert"
1618
"github.com/stretchr/testify/require"
1719
)
@@ -29,6 +31,8 @@ var (
2931
// All these tests are also run in serial to avoid race conditions on the git config file.
3032

3133
func TestHTTPSAuth(t *testing.T) {
34+
defer cleanupGitConfig(t)
35+
3236
currentDir, err := os.Getwd()
3337
require.NoError(t, err)
3438
require.Equal(t, "/workspace/go-commons/git/test", currentDir)
@@ -43,7 +47,109 @@ func TestHTTPSAuth(t *testing.T) {
4347
assert.True(t, files.IsDir(filepath.Join(tmpDir, "modules/lambda")))
4448
}
4549

50+
func TestHTTPSAuthWithPath(t *testing.T) {
51+
defer cleanupGitConfig(t)
52+
53+
currentDir, err := os.Getwd()
54+
require.NoError(t, err)
55+
require.Equal(t, "/workspace/go-commons/git/test", currentDir)
56+
57+
environment.RequireEnvVar(t, gitPATEnvName)
58+
gitPAT := os.Getenv(gitPATEnvName)
59+
60+
lambdaGitURL := "https://github.com/gruntwork-io/terraform-aws-lambda.git"
61+
lambdaOpts := git.CacheCredentialOptions{
62+
Host: lambdaGitURL,
63+
DefaultUsername: "git",
64+
IncludeHTTPPath: true,
65+
SocketPath: "",
66+
Timeout: 3600,
67+
}
68+
require.NoError(
69+
t,
70+
git.ConfigureCacheCredentialsHelper(logger, lambdaOpts),
71+
)
72+
require.NoError(
73+
t,
74+
git.StoreCacheCredentials(logger, "git", gitPAT, "github.com", "gruntwork-io/terraform-aws-lambda.git", ""),
75+
)
76+
77+
tmpDir, err := ioutil.TempDir("", "git-test")
78+
require.NoError(t, err)
79+
require.NoError(t, git.Clone(logger, lambdaGitURL, tmpDir))
80+
assert.True(t, files.IsDir(filepath.Join(tmpDir, "modules/lambda")))
81+
}
82+
83+
func TestHTTPSAuthMixed(t *testing.T) {
84+
defer cleanupGitConfig(t)
85+
86+
currentDir, err := os.Getwd()
87+
require.NoError(t, err)
88+
require.Equal(t, "/workspace/go-commons/git/test", currentDir)
89+
90+
// Make sure the directory for git credential sockets exist
91+
socketPath := "/tmp/git-credential-sockets"
92+
require.NoError(t, os.MkdirAll(socketPath, 0o700))
93+
lambdaSocketPath := filepath.Join(socketPath, "lambda")
94+
githubSocketPath := filepath.Join(socketPath, "github")
95+
96+
environment.RequireEnvVar(t, gitPATEnvName)
97+
gitPAT := os.Getenv(gitPATEnvName)
98+
99+
lambdaGitURL := "https://github.com/gruntwork-io/terraform-aws-lambda.git"
100+
lambdaOpts := git.CacheCredentialOptions{
101+
Host: lambdaGitURL,
102+
DefaultUsername: "git",
103+
IncludeHTTPPath: true,
104+
SocketPath: lambdaSocketPath,
105+
Timeout: 3600,
106+
}
107+
require.NoError(
108+
t,
109+
git.ConfigureCacheCredentialsHelper(logger, lambdaOpts),
110+
)
111+
require.NoError(
112+
t,
113+
git.StoreCacheCredentials(logger, "git", gitPAT, "github.com", "gruntwork-io/terraform-aws-lambda.git", lambdaSocketPath),
114+
)
115+
116+
githubOpts := git.CacheCredentialOptions{
117+
Host: "https://github.com",
118+
DefaultUsername: "git",
119+
IncludeHTTPPath: false,
120+
SocketPath: githubSocketPath,
121+
Timeout: 3600,
122+
}
123+
require.NoError(
124+
t,
125+
git.ConfigureCacheCredentialsHelper(logger, githubOpts),
126+
)
127+
require.NoError(
128+
t,
129+
git.StoreCacheCredentials(logger, "git", "wrong-pat", "github.com", "", githubSocketPath),
130+
)
131+
132+
tmpDir, err := ioutil.TempDir("", "git-test")
133+
require.NoError(t, err)
134+
lambdaDir := filepath.Join(tmpDir, "terraform-aws-lambda")
135+
ciDir := filepath.Join(tmpDir, "terraform-aws-ci")
136+
require.NoError(t, os.Mkdir(lambdaDir, 0o755))
137+
require.NoError(t, os.Mkdir(ciDir, 0o755))
138+
139+
require.NoError(
140+
t,
141+
git.Clone(logger, lambdaGitURL, lambdaDir),
142+
)
143+
require.Error(
144+
t,
145+
git.Clone(logger, "https://github.com/gruntwork-io/terraform-aws-ci.git", ciDir),
146+
)
147+
148+
}
149+
46150
func TestForceHTTPS(t *testing.T) {
151+
defer cleanupGitConfig(t)
152+
47153
currentDir, err := os.Getwd()
48154
require.NoError(t, err)
49155
require.Equal(t, "/workspace/go-commons/git/test", currentDir)
@@ -58,3 +164,18 @@ func TestForceHTTPS(t *testing.T) {
58164
require.NoError(t, git.Clone(logger, "git@github.com:gruntwork-io/terraform-aws-lambda.git", tmpDir))
59165
assert.True(t, files.IsDir(filepath.Join(tmpDir, "modules/lambda")))
60166
}
167+
168+
// cleanupGitConfig will reset the git credential cache and git config
169+
func cleanupGitConfig(t *testing.T) {
170+
data, err := ioutil.ReadFile("/root/.gitconfig")
171+
require.NoError(t, err)
172+
ttlogger.Logf(t, string(data))
173+
174+
require.NoError(t, os.Remove("/root/.gitconfig"))
175+
176+
cmd := shell.Command{
177+
Command: "git",
178+
Args: []string{"credential-cache", "exit"},
179+
}
180+
shell.RunCommand(t, cmd)
181+
}

0 commit comments

Comments
 (0)