Skip to content

Commit e7908cc

Browse files
committed
Forward SSH known-hosts and InsecureSkipTLSverify to fleet apply
Pass FLEET_KNOWN_HOSTS and FLEET_INSECURE_SKIP_TLS_VERIFY through to the bundlereader.Auth so that fleet apply CLI and the GitOps reconciler honour the same TLS and SSH host-key settings as the rest of the pipeline. Default the SSH username to 'git' when the URL has no user component, matching the controller behaviour. Add tests for createAuthFromOpts and addAuthToOpts, and update the gitjob test to assert that InsecureSkipTLSverify is forwarded.
1 parent 643feb9 commit e7908cc

5 files changed

Lines changed: 168 additions & 267 deletions

File tree

internal/cmd/cli/apply.go

Lines changed: 3 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"bytes"
55
"fmt"
66
"os"
7-
"strings"
87

98
gogit "github.com/go-git/go-git/v5"
109
"github.com/go-git/go-git/v5/plumbing"
@@ -134,13 +133,6 @@ func (a *Apply) run(cmd *cobra.Command, args []string) error {
134133
ImagescanEnabled: a.ImagescanEnabled,
135134
}
136135

137-
knownHostsPath, err := writeTmpKnownHosts()
138-
if err != nil {
139-
return err
140-
}
141-
142-
defer os.RemoveAll(knownHostsPath)
143-
144136
if err := a.addAuthToOpts(&opts, os.ReadFile, a.HelmBasicHTTP, a.HelmInsecureSkipTLS); err != nil {
145137
return fmt.Errorf("adding auth to opts: %w", err)
146138
}
@@ -170,13 +162,6 @@ func (a *Apply) run(cmd *cobra.Command, args []string) error {
170162
args = args[1:]
171163
}
172164

173-
restoreEnv, err := setEnv(knownHostsPath)
174-
if err != nil {
175-
return fmt.Errorf("setting git SSH command env var for known hosts: %w", err)
176-
}
177-
178-
defer restoreEnv() //nolint: errcheck // best-effort
179-
180165
ctx := cmd.Context()
181166
cfg := ctrl.GetConfigOrDie()
182167
client, err := client.New(cfg, client.Options{Scheme: scheme})
@@ -240,6 +225,9 @@ func (a *Apply) addAuthToOpts(opts *apply.Options, readFile readFile, helmBasicH
240225

241226
opts.Auth.BasicHTTP = helmBasicHTTP
242227
opts.Auth.InsecureSkipVerify = helmInsecureSkipTLS
228+
if raw := os.Getenv(ssh.KnownHostsEnvVar); raw != "" {
229+
opts.Auth.SSHKnownHosts = []byte(raw)
230+
}
243231

244232
return nil
245233
}
@@ -261,91 +249,6 @@ func currentCommit(dir string) string {
261249
// writeTmpKnownHosts creates a temporary file and writes known_hosts data to it, if such data is available from
262250
// environment variable `FLEET_KNOWN_HOSTS`.
263251
// It returns the name of the file and any error which may have happened while creating the file or writing to it.
264-
func writeTmpKnownHosts() (string, error) {
265-
knownHosts, isSet := os.LookupEnv(ssh.KnownHostsEnvVar)
266-
if !isSet || knownHosts == "" {
267-
return "", nil
268-
}
269-
270-
f, err := os.CreateTemp("", "known_hosts")
271-
if err != nil {
272-
return "", err
273-
}
274-
275-
knownHostsPath := f.Name()
276-
277-
if err := os.WriteFile(knownHostsPath, []byte(knownHosts), 0600); err != nil {
278-
return "", fmt.Errorf(
279-
"failed to write value of %q env var to known_hosts file %s: %w",
280-
ssh.KnownHostsEnvVar,
281-
knownHostsPath,
282-
err,
283-
)
284-
}
285-
286-
return knownHostsPath, nil
287-
}
288-
289-
// setEnv sets the `GIT_SSH_COMMAND` environment variable with a known_hosts flag pointing to the provided
290-
// knownHostsPath. It takes care of preserving existing flags in the existing value of the environment variable, if any,
291-
// except for other user known_hosts file flags.
292-
// It returns a function to restore the environment variable to its initial value, and any error that might have
293-
// occurred in the process.
294-
func setEnv(knownHostsPath string) (func() error, error) {
295-
commandEnvVar := "GIT_SSH_COMMAND"
296-
flagName := "UserKnownHostsFile"
297-
298-
initialCommand, isSet := os.LookupEnv(commandEnvVar)
299-
300-
fail := func(err error) (func() error, error) {
301-
return func() error { return nil }, err
302-
}
303-
304-
if !isSet {
305-
if err := os.Setenv(commandEnvVar, fmt.Sprintf("ssh -o %s=%s", flagName, knownHostsPath)); err != nil {
306-
return fail(err)
307-
}
308-
309-
return func() error { return os.Unsetenv(commandEnvVar) }, nil
310-
}
311-
312-
// Check if `UserKnownHostsFile` is already present (case-insensitive), even multiple times, and skip it if so.
313-
var newSSHCommand strings.Builder
314-
options := strings.Split(initialCommand, " -o ")
315-
for _, opt := range options {
316-
kv := strings.Split(opt, "=")
317-
if len(kv) != 2 { // first element, pre `-o`, or other flag
318-
if _, err := newSSHCommand.WriteString(opt); err != nil {
319-
return fail(err)
320-
}
321-
322-
continue
323-
}
324-
325-
if strings.EqualFold(kv[0], flagName) { // case-insensitive comparison
326-
continue
327-
}
328-
329-
if _, err := fmt.Fprintf(&newSSHCommand, " -o %s", opt); err != nil {
330-
return fail(err)
331-
}
332-
}
333-
334-
if _, err := fmt.Fprintf(&newSSHCommand, " -o %s=%s", flagName, knownHostsPath); err != nil {
335-
return fail(err)
336-
}
337-
338-
if err := os.Setenv(commandEnvVar, newSSHCommand.String()); err != nil {
339-
return fail(err)
340-
}
341-
342-
restore := func() error {
343-
return os.Setenv(commandEnvVar, initialCommand)
344-
}
345-
346-
return restore, nil
347-
}
348-
349252
func getEventRecorder(config *rest.Config, componentName string) (record.EventRecorder, error) {
350253
clientset, err := kubernetes.NewForConfig(config)
351254
if err != nil {

internal/cmd/cli/apply_test.go

Lines changed: 20 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
"github.com/rancher/fleet/internal/bundlereader"
1717
"github.com/rancher/fleet/internal/cmd/cli/apply"
18+
ssh "github.com/rancher/fleet/internal/ssh"
1819
)
1920

2021
const (
@@ -34,146 +35,25 @@ const (
3435

3536
var helmSecretsNameByPath_content = map[string]bundlereader.Auth{"path": {Username: username, Password: password_content}}
3637

37-
func TestSetEnv(t *testing.T) {
38-
tests := map[string]struct {
39-
envValue string
40-
knownHostsPath string
41-
expectedGitSSHCommand string
42-
expectedErr error
43-
}{
44-
"unset env var": {
45-
knownHostsPath: "/foo/bar",
46-
expectedGitSSHCommand: "ssh -o UserKnownHostsFile=/foo/bar",
47-
},
48-
"set env var without options": {
49-
envValue: "ssh",
50-
knownHostsPath: "/foo/bar",
51-
expectedGitSSHCommand: "ssh -o UserKnownHostsFile=/foo/bar",
52-
},
53-
"set env var with other options": {
54-
envValue: "ssh -o stricthostkeychecking=yes",
55-
knownHostsPath: "/foo/bar",
56-
expectedGitSSHCommand: "ssh -o stricthostkeychecking=yes -o UserKnownHostsFile=/foo/bar",
57-
},
58-
"set env var with other options and known hosts file option": {
59-
envValue: "ssh -o stricthostkeychecking=yes -o userknownhostsFile=/another/file",
60-
knownHostsPath: "/foo/bar",
61-
expectedGitSSHCommand: "ssh -o stricthostkeychecking=yes -o UserKnownHostsFile=/foo/bar",
62-
},
63-
"set env var with other options and known hosts file option specified multiple times": {
64-
envValue: "ssh -o userknownhostsFile=/another/file -o UserKnownHostsFile=/yet/another/file -o stricthostkeychecking=yes",
65-
knownHostsPath: "/foo/bar",
66-
expectedGitSSHCommand: "ssh -o stricthostkeychecking=yes -o UserKnownHostsFile=/foo/bar",
67-
},
68-
}
69-
70-
bkpEnv := os.Getenv("GIT_SSH_COMMAND")
71-
defer os.Setenv("GIT_SSH_COMMAND", bkpEnv)
72-
73-
for name, test := range tests {
74-
t.Run(name, func(t *testing.T) {
75-
defer os.Unsetenv("GIT_SSH_COMMAND")
76-
77-
if test.envValue != "" {
78-
os.Setenv("GIT_SSH_COMMAND", test.envValue)
79-
} else {
80-
os.Unsetenv("GIT_SSH_COMMAND")
81-
}
82-
83-
restore, err := setEnv(test.knownHostsPath)
84-
if !errors.Is(err, test.expectedErr) {
85-
t.Errorf("expected err %v, got %v", test.expectedErr, err)
86-
}
87-
88-
if gitSSHCommand := os.Getenv("GIT_SSH_COMMAND"); gitSSHCommand != test.expectedGitSSHCommand {
89-
t.Errorf("expected GIT_SSH_COMMAND %q, got %q", test.expectedGitSSHCommand, gitSSHCommand)
90-
}
91-
92-
if restoreErr := restore(); restoreErr != nil {
93-
t.Errorf("expected nil restore error, got %v", restoreErr)
94-
}
95-
96-
restoredEnvValue, isSet := os.LookupEnv("GIT_SSH_COMMAND")
97-
if restoredEnvValue != test.envValue {
98-
t.Errorf(
99-
"expected restored GIT_SSH_COMMAND value to be %q, got %t/%q",
100-
test.envValue,
101-
isSet,
102-
restoredEnvValue,
103-
)
104-
}
105-
})
106-
}
107-
}
108-
109-
func TestWriteTmpKnownHosts(t *testing.T) {
110-
tests := map[string]struct {
111-
knownHosts string
112-
isSet bool
113-
expectFileExists bool
114-
}{
115-
"does not write to known hosts file if FLEET_KNOWN_HOSTS is unset": {},
116-
"does not write to known hosts file if FLEET_KNOWN_HOSTS is empty": {isSet: true},
117-
"writes FLEET_KNOWN_HOSTS to custom known hosts file if set": {
118-
knownHosts: "foo",
119-
isSet: true,
120-
expectFileExists: true,
121-
},
122-
}
123-
124-
for name, test := range tests {
125-
t.Run(name, func(t *testing.T) {
126-
if test.isSet {
127-
if err := os.Setenv("FLEET_KNOWN_HOSTS", test.knownHosts); err != nil {
128-
t.Errorf("failed to set FLEET_KNOWN_HOSTS env var: %v", err)
129-
}
130-
131-
defer os.Unsetenv("FLEET_KNOWN_HOSTS")
132-
}
133-
134-
khPath, err := writeTmpKnownHosts()
135-
if err != nil {
136-
t.Errorf("expected nil error from writeTmpKnownHosts, got: %v", err)
137-
}
138-
139-
if !test.expectFileExists {
140-
return
141-
}
142-
143-
gotKnownHosts, err := os.ReadFile(khPath)
144-
if err != nil {
145-
t.Errorf("failed to read known_hosts file: %v", err)
146-
}
147-
148-
defer os.RemoveAll(khPath)
149-
150-
if test.knownHosts != "" {
151-
if string(gotKnownHosts) != test.knownHosts {
152-
t.Errorf("known_hosts mismatch: expected\n\t%s\ngot:\n\t%s", test.knownHosts, gotKnownHosts)
153-
}
154-
}
155-
})
156-
}
157-
}
158-
15938
func TestAddAuthToOpts(t *testing.T) {
16039
tests := map[string]struct {
161-
name string
162-
apply Apply
163-
knownHosts string
164-
expectedOpts *apply.Options
165-
expectedErr error
40+
name string
41+
apply Apply
42+
knownHosts string
43+
helmInsecureSkipTLS bool
44+
expectedOpts *apply.Options
45+
expectedErr error
16646
}{
16747
"Auth is empty if no arguments are provided": {
16848
apply: Apply{},
16949
expectedOpts: &apply.Options{},
17050
expectedErr: nil,
17151
},
172-
"known_hosts file is populated if the env var is set": {
52+
"FLEET_KNOWN_HOSTS env var sets SSHKnownHosts in opts": {
17353
apply: Apply{},
174-
expectedOpts: &apply.Options{},
54+
knownHosts: "some-known-host",
55+
expectedOpts: &apply.Options{Auth: bundlereader.Auth{SSHKnownHosts: []byte("some-known-host")}},
17556
expectedErr: nil,
176-
knownHosts: "foo",
17757
},
17858
"Auth contains values from username, password, caCerts and sshPrivatey when helmSecretsNameByPath not provided": {
17959
apply: Apply{PasswordFile: password_file, Username: username, CACertsFile: caCerts_file, SSHPrivateKeyFile: sshPrivateKey_file},
@@ -190,6 +70,12 @@ func TestAddAuthToOpts(t *testing.T) {
19070
expectedOpts: &apply.Options{AuthByPath: helmSecretsNameByPath_content},
19171
expectedErr: nil,
19272
},
73+
"HelmInsecureSkipTLS sets InsecureSkipVerify in opts": {
74+
apply: Apply{},
75+
helmInsecureSkipTLS: true,
76+
expectedOpts: &apply.Options{Auth: bundlereader.Auth{InsecureSkipVerify: true}},
77+
expectedErr: nil,
78+
},
19379
"Error if file doesn't exist": {
19480
apply: Apply{HelmCredentialsByPathFile: "notfound"},
19581
expectedOpts: &apply.Options{},
@@ -199,8 +85,11 @@ func TestAddAuthToOpts(t *testing.T) {
19985

20086
for name, test := range tests {
20187
t.Run(name, func(t *testing.T) {
88+
if test.knownHosts != "" {
89+
t.Setenv(ssh.KnownHostsEnvVar, test.knownHosts)
90+
}
20291
opts := &apply.Options{}
203-
err := test.apply.addAuthToOpts(opts, mockReadFile, false, false)
92+
err := test.apply.addAuthToOpts(opts, mockReadFile, false, test.helmInsecureSkipTLS)
20493
if !cmp.Equal(opts, test.expectedOpts) {
20594
t.Errorf("opts don't match: expected %v, got %v", test.expectedOpts, opts)
20695
}

0 commit comments

Comments
 (0)